1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
package com.explodingpixels.widgets;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.plaf.basic.BasicButtonUI;
/**
* A button backed by an image. Additionally, a click mask can be provided. Any fully
* non-transparent pixels in the mask will not be clickable.
*/
public class ImageButton extends JButton {
// create a static index for the alpha channel of a raster image. i'm not exactly sure where
// it's specified that red = channel 0, green = channel 1, blue = channel 2, and
// alpha = channel 3, but this have been the values i've observed.
private static final int ALPHA_BAND = 3;
// a buffered image representing the mask for this button.
private final BufferedImage fMask;
private Icon fInactiveIcon;
/**
* Creates an image based button.
*
* @param icon the icon to use for the button.
*/
public ImageButton(Icon icon) {
this(icon, icon);
}
/**
* Creates an image based button with the given click mask.
*
* @param icon the icon to use for the button.
* @param mask the click mask to use for the button.
* @throws IllegalArgumentException if the given icon is null, the given mask is null or
* the given mask's bounds do not match the given icons bounds.
*/
public ImageButton(Icon icon, Icon mask) {
super(icon);
if (icon == null) {
throw new IllegalArgumentException("The icon cannot be null.");
}
if (mask == null) {
throw new IllegalArgumentException("The mask cannot be null.");
}
checkIconMatchesMaskBounds(icon, mask);
// remove the margins from this button, request that the content area not be filled, and
// indicate that the border not be painted.
setMargin(new Insets(0, 0, 0, 0));
setBorder(BorderFactory.createEmptyBorder());
setContentAreaFilled(false);
// create the mask from the supplied icon.
fMask = createMask(mask);
// repaint this button when the parent window's focus state changes so
// that we can correctly show the active or inactive icon.
WindowUtils.installJComponentRepainterOnWindowFocusChanged(this);
}
private BufferedImage createMask(Icon mask) {
// create a BufferedImage to paint the mask into so that we can later retrieve pixel data
// out of the image.
BufferedImage image = new BufferedImage(
mask.getIconWidth(), mask.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics graphics = image.getGraphics();
mask.paintIcon(null, graphics, 0, 0);
graphics.dispose();
return image;
}
public Icon getIcon() {
return WindowUtils.isParentWindowFocused(this) || fInactiveIcon == null
? super.getIcon() : fInactiveIcon;
}
@Override
public void setIcon(Icon defaultIcon) {
super.setIcon(defaultIcon);
// if this class has already been initialized, ensure that the new icon matches the bounds
// of the current mask.
if (fMask != null) {
checkIconMatchesMaskBounds(defaultIcon, new ImageIcon(fMask));
}
}
public void setInactiveIcon(Icon inactiveIcon) {
checkIconMatchesMaskBounds(inactiveIcon, new ImageIcon(fMask));
fInactiveIcon = inactiveIcon;
}
@Override
public void updateUI() {
// install the custom ui delegate to track the icon rectangle and answer the contains
// method.
setUI(new CustomButtonUI());
}
private static void checkIconMatchesMaskBounds(Icon icon, Icon mask) {
if (mask.getIconWidth() != icon.getIconWidth()
|| mask.getIconHeight() != icon.getIconHeight()) {
throw new IllegalArgumentException("The mask must be the same size as the icon.");
}
}
// CustomButtonUI implementation so that we can maintain the icon rectangle. //////////////////
private class CustomButtonUI extends BasicButtonUI {
private Rectangle fIconRect;
private boolean maskContains(int x, int y) {
return fIconRect != null && fIconRect.contains(x, y)
&& fMask.getRaster().getSample(x - fIconRect.x, y - fIconRect.y, ALPHA_BAND) > 0;
}
@Override
public boolean contains(JComponent c, int x, int y) {
return maskContains(x, y);
}
@Override
protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect) {
super.paintIcon(g, c, iconRect);
// capture where the icon is being painted within the bounds of this button so we can
// later use this information in the contains calculation.
if (fIconRect == null || !fIconRect.equals(iconRect)) {
// create a copy of the icon rectangle, as the given iconRect is a static variable
// in BasicButtonUI that will be updated for each button painted.
fIconRect = new Rectangle(iconRect);
}
}
}
}
|