package com.explodingpixels.macwidgets;
import com.explodingpixels.widgets.WindowDragger;
import com.explodingpixels.widgets.WindowUtils;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
/**
* <p>
* An implementation of an OS X Transparent Panel, also known as a Heads Up Display (HUD). For a
* full descrption of what a Transparent Panel is, see the <a href="http://developer.apple.com/documentation/UserExperience/Conceptual/AppleHIGuidelines/XHIGWindows/chapter_18_section_6.html#//apple_ref/doc/uid/20000961-SW24">Transparent Panels</a>
* section of Apple's Human Interface Guidelines.
* </p>
* <p>
* HUD's are designed to offer a lightweight way to unobtrusivley offer controls to the user. The window
* looks like this:
* <br>
* <img src="../../../../graphics/HeadsUpDisplay.png">
* <br>
* <p>
* As Apple points out, this component is not appropriate for all situations and should be used
* judiciously.
* </p>
*/
public class HudWindow {
private final JDialog fDialog;
private JComponent fContentPane;
private final TitlePanel fTitlePanel;
private final HudPanel fHudPanel = new HudPanel();
private final BottomPanel fBottomPanel;
private static final int ROUNDED_RECT_DIAMETER = 16;
/**
* Creates a Heads Up Display style window.
*/
public HudWindow() {
this("");
}
/**
* Creates a Heads Up Display style window.
*
* @param title the title to use for this window.
*/
public HudWindow(String title) {
this(title, null);
}
/**
* Creates a Heads Up Display style window.
*
* @param title the title to use for this window.
* @param owner the {@link Frame} that this HUD is parented to. Can be null.
*/
public HudWindow(String title, Frame owner) {
fDialog = new JDialog(owner);
fDialog.setTitle(title);
fTitlePanel = new TitlePanel(title, createCloseButtonActionListener());
fBottomPanel = new BottomPanel(fDialog);
init();
}
private void init() {
// indicate that this frame should not make all the content draggable. by default, when you
// set the opacity to 0 (like we do below) this property automatically gets set to true.
// also note that this client property must be set *before* changing the opacity (not sure
// why).
fDialog.getRootPane().putClientProperty("apple.awt.draggableWindowBackground", Boolean.FALSE);
fDialog.setUndecorated(true);
fDialog.setBackground(new Color(0, 0, 0, 0));
fHudPanel.add(fTitlePanel, BorderLayout.NORTH);
// fHudPanel.add(fBottomPanel, BorderLayout.SOUTH);
// set the backing frame's content pane.
fDialog.setContentPane(fHudPanel);
// set the HUD panel's content pane to a blank JPanel by default.
JPanel panel = new JPanel();
panel.setOpaque(false);
setContentPane(panel);
// listen to the frame's title property so that we can update the label rendering the title.
fDialog.addPropertyChangeListener("title", createTitlePropertyChangeListener());
WindowUtils.createAndInstallRepaintWindowFocusListener(fDialog);
// listen for focus changes in the window so that we can update the focus state of the title
// panel (e.g. the font color).
fTitlePanel.addPropertyChangeListener(
WindowUtils.FRAME_ACTIVE_PROPERTY, createFrameFocusPropertyChangeListener());
new WindowDragger(fDialog, fTitlePanel);
}
/**
* Gets the {@link JDialog} backing this {@code HudWindow}.
*
* @return the {@code JDialog} backing this {@code HudWindow}.
*/
public JDialog getJDialog() {
return fDialog;
}
/**
* Gets the {@link JComponent} to add content to.
*
* @return the container to add content to.
*/
public JComponent getContentPane() {
return fContentPane;
}
/**
* Sets the {@link JComponent} to use as the container for this {@code HudWindow}'s content.
*
* @param contentPane the container for this {@code HudWindow}'s content.
*/
public void setContentPane(JComponent contentPane) {
// remove the old content pane if there was one.
if (fContentPane != null) {
fHudPanel.remove(fContentPane);
}
fContentPane = contentPane;
fHudPanel.add(fContentPane, BorderLayout.CENTER);
}
private PropertyChangeListener createTitlePropertyChangeListener() {
return new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
fTitlePanel.setTitle(fDialog.getTitle());
}
};
}
private PropertyChangeListener createFrameFocusPropertyChangeListener() {
return new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
fTitlePanel.updateFocusState();
}
};
}
private ActionListener createCloseButtonActionListener() {
return new ActionListener() {
public void actionPerformed(ActionEvent e) {
// simulate clicking the "real" close button on a window.
fDialog.dispatchEvent(new WindowEvent(fDialog, WindowEvent.WINDOW_CLOSING));
}
};
}
private static class TitlePanel extends JPanel {
private static final Color FONT_COLOR = new Color(255, 255, 255, 255);
private static final Color UNFOCUSED_FONT_COLOR = new Color(0xcccccc);
private static final Color HIGHLIGHT = new Color(255, 255, 255, 25);
private static final Color TOP_BACKGROUND_TOP = new Color(255, 255, 255, 59);
private static final Color TOP_BACKGROUND_BOTTOM = new Color(196, 196, 196, 59);
private static final Color BOTTOM_BACKGROUND = new Color(255, 255, 255, 30);
private static final Color UNFOCUSED_BACKGROUND = new Color(0, 0, 0, 10);
private static final Icon CLOSE_ICON = new ImageIcon(
TitlePanel.class.getResource(
"/com/explodingpixels/macwidgets/images/close.png"));
private static final Icon CLOSE_HOVER_ICON = new ImageIcon(
TitlePanel.class.getResource(
"/com/explodingpixels/macwidgets/images/close_hover.png"));
private static final Icon CLOSE_PRESSED_ICON = new ImageIcon(
TitlePanel.class.getResource(
"/com/explodingpixels/macwidgets/images/close_pressed.png"));
private final JButton fCloseButton = new JButton(CLOSE_ICON);
private final JLabel fLabel;
private TitlePanel(String title, ActionListener closeButtonActionListener) {
fLabel = new JLabel(title, SwingConstants.CENTER);
fLabel.setFont(fLabel.getFont().deriveFont(Font.BOLD, 11.0f));
fCloseButton.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
fCloseButton.setVerticalAlignment(SwingConstants.CENTER);
fCloseButton.setOpaque(false);
fCloseButton.setFocusable(false);
fCloseButton.setBorderPainted(false);
fCloseButton.setContentAreaFilled(false);
fCloseButton.setRolloverIcon(CLOSE_HOVER_ICON);
fCloseButton.setPressedIcon(CLOSE_PRESSED_ICON);
fCloseButton.addActionListener(closeButtonActionListener);
setOpaque(false);
setPreferredSize(new Dimension(-1, 20));
updateFocusState();
setLayout(new BorderLayout());
add(fCloseButton, BorderLayout.WEST);
add(fLabel, BorderLayout.CENTER);
add(MacWidgetFactory.createSpacer(
fCloseButton.getPreferredSize().width, 0), BorderLayout.EAST);
}
private void setTitle(String title) {
fLabel.setText(title);
}
private void updateFocusState() {
Boolean focused = (Boolean) getClientProperty(WindowUtils.FRAME_ACTIVE_PROPERTY);
fLabel.setForeground(focused == null || focused ? FONT_COLOR : UNFOCUSED_FONT_COLOR);
}
@Override
protected void paintComponent(Graphics g) {
// create a copy of the graphics object and turn on anti-aliasing.
Graphics2D graphics2d = (Graphics2D) g.create();
graphics2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// calculate the point in the title bar at which to change the background color.
int midPointY = ROUNDED_RECT_DIAMETER / 2 + 3;
// if the window has focus, draw a shiny title bar.
// else draw a flat background.
if (WindowUtils.isParentWindowFocused(this)) {
// 1. The top half --------------------------------------------------------------//
// create and set the shiny paint.
GradientPaint paint =
new GradientPaint(0, 0, TOP_BACKGROUND_TOP, 0, midPointY, TOP_BACKGROUND_BOTTOM);
graphics2d.setPaint(paint);
// create a rounded rectangle area as big as the entire title bar, then subtract
// off the bottom half (roughly) in order to have perfectly square edges.
Area titleArea = new Area(new Area(new RoundRectangle2D.Double(
0, 0, getWidth(), getHeight(), ROUNDED_RECT_DIAMETER, ROUNDED_RECT_DIAMETER)));
titleArea.subtract(new Area(new Rectangle(0, midPointY, getWidth(), midPointY)));
// draw the top half of the title bar (the shine).
graphics2d.fill(titleArea);
// 2. The bottom half -----------------------------------------------------------//
// draw the bottom half of the title bar.
int bottomHeight = getHeight() - midPointY;
graphics2d.setColor(BOTTOM_BACKGROUND);
graphics2d.fillRect(0, midPointY, getWidth(), bottomHeight);
} else {
// create an area that has rounded corners at the top and square corners at the
// bottom.
graphics2d.setColor(UNFOCUSED_BACKGROUND);
Area titleArea = new Area(new Area(new RoundRectangle2D.Double(
0, 0, getWidth(), getHeight(), ROUNDED_RECT_DIAMETER, ROUNDED_RECT_DIAMETER)));
titleArea.subtract(new Area(
new Rectangle(0, midPointY, getWidth(), midPointY)));
graphics2d.fill(titleArea);
graphics2d.setColor(HIGHLIGHT);
graphics2d.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1);
}
graphics2d.dispose();
}
}
private static class HudPanel extends JPanel {
private static final Color HIGHLIGHT = new Color(255, 255, 255, 59);
private static final Color HIGHLIGHT_BOTTOM = new Color(255, 255, 255, 25);
private static final Color BACKGROUND = new Color(30, 30, 30, 216);
private HudPanel() {
setLayout(new BorderLayout());
setOpaque(false);
}
@Override
protected void paintBorder(Graphics g) {
// create a copy of the graphics object and turn on anti-aliasing.
Graphics2D graphics2d = (Graphics2D) g.create();
graphics2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// paint a border around the window that fades slightly to give a more pleasnt highlight
// to the window edges.
GradientPaint paint = new GradientPaint(0, 0, HIGHLIGHT, 0, getHeight(), HIGHLIGHT_BOTTOM);
graphics2d.setPaint(paint);
graphics2d.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, ROUNDED_RECT_DIAMETER,
ROUNDED_RECT_DIAMETER);
graphics2d.dispose();
}
@Override
protected void paintComponent(Graphics g) {
// create a copy of the graphics object and turn on anti-aliasing.
Graphics2D graphics2d = (Graphics2D) g.create();
graphics2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics2d.setComposite(AlphaComposite.Src);
// draw the rounded rectangle background of the window.
graphics2d.setColor(BACKGROUND);
graphics2d.fillRoundRect(0, 0, getWidth(), getHeight(),
ROUNDED_RECT_DIAMETER, ROUNDED_RECT_DIAMETER);
// tell the shadow to revalidate.
getRootPane().putClientProperty("apple.awt.windowShadow.revalidateNow", new Object());
graphics2d.dispose();
}
}
private static class BottomPanel extends JPanel {
private static final Icon RESIZE_ICON = new ImageIcon(
TitlePanel.class.getResource(
"/com/explodingpixels/macwidgets/images/resize_corner_dark.png"));
private final Window fWindow;
private final JLabel fResizeCorner = new JLabel(RESIZE_ICON);
private int fXOffsetToWindowEdge;
private int fYOffsetToWidnowEdge;
public BottomPanel(Window window) {
super(new FlowLayout(SwingConstants.RIGHT));
fWindow = window;
add(fResizeCorner);
fResizeCorner.addMouseListener(createMouseListener());
fResizeCorner.addMouseMotionListener(createMouseMotionListener());
}
private MouseAdapter createMouseListener() {
return new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
Point windowPoint =
SwingUtilities.convertPoint(fResizeCorner, e.getPoint(), fWindow);
fXOffsetToWindowEdge = fWindow.getWidth() - windowPoint.x;
fYOffsetToWidnowEdge = fWindow.getHeight() - windowPoint.y;
}
};
}
private MouseMotionListener createMouseMotionListener() {
return new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
Point windowPoint = SwingUtilities.convertPoint(fResizeCorner, e.getPoint(), fWindow);
fWindow.setSize(windowPoint.x + fXOffsetToWindowEdge,
windowPoint.y + fYOffsetToWidnowEdge);
}
};
}
}
}