Color Swatch & Color Selector for Java
The color selector (JColorChooser) for Java is rather ugly, so I decided to write my own color chooser. The color chooser is made up of two components:
- ColorSelector: A button which displays the currently selected color and pops up the ColorSwatch when clicked.
- ColorSwatch: Displays a color swatch with clickable colors. The color swatch expects a two-dimensional array of colors as input that will be displayed in the swatch.
The color selector and swatch will be used for Age of Conquest to make it more convenient to select a color in the map editor (less cumbersome). Please note, the five columns to the left are specifically designed colors for Age of Conquest to give maps a medieval look & feel. Although other colors can be chosen as well, it is recommended to use the medieval colors when creating new scenarios for the game.
Here is the code for both components. Please consider the code Public Domain. Feel free to use and modify it any way you please. Although, I would appreciate to be credited, it is not necessary to do so. Enjoy!
ColorSelector.java
import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.ButtonModel; import javax.swing.JButton; import javax.swing.JPopupMenu; /** * Represents a color selector. * * @author noblemaster * @since August 16, 2010 */ public class ColorSelector extends JButton { /** The default colors. */ private static final int[][] DEFAULT_COLORS = new int[][] { { 0xFFFFFF, 0xEEEEEE, 0xDDDDDD, 0xCCCCCC , 0xBBBBBB, 0xAAAAAA, 0xFFCC00, 0xFF9900 , 0xFF6600, 0xFF3300, 0x999999, 0x888888 , 0x666666, 0x444444, 0x222222, 0x000000 }, { 0x99CC00, 0x000000, 0x000000, 0x000000 , 0x000000, 0xCC9900, 0xFFCC33, 0xFFCC66 , 0xFF9966, 0xFF6633, 0xCC3300, 0x000000 , 0x000000, 0x000000, 0x000000, 0xCC0033 }, { 0xCCFF00, 0xCCFF33, 0x333300, 0x666600 , 0x999900, 0xCCCC00, 0xFFFF00, 0xCC9933 , 0xCC6633, 0x330000, 0x660000, 0x990000 , 0xCC0000, 0xFF0000, 0xFF3366, 0xFF0033 }, { 0x99FF00, 0xCCFF66, 0x99CC33, 0x666633 , 0x999933, 0xCCCC33, 0xFFFF33, 0x996600 , 0x993300, 0x663333, 0x993333, 0xCC3333 , 0xFF3333, 0xCC3366, 0xFF6699, 0xFF0066 }, { 0x66FF00, 0x99FF66, 0x66CC33, 0x669900 , 0x999966, 0xCCCC66, 0xFFFF66, 0x996633 , 0x663300, 0x996666, 0xCC6666, 0xFF6666 , 0x990033, 0xCC3399, 0xFF66CC, 0xFF0099 }, { 0x33FF00, 0x66FF33, 0x339900, 0x66CC00 , 0x99FF33, 0xCCCC99, 0xFFFF99, 0xCC9966 , 0xCC6600, 0xCC9999, 0xFF9999, 0xFF3399 , 0xCC0066, 0x990066, 0xFF33CC, 0xFF00CC }, { 0x00CC00, 0x33CC00, 0x336600, 0x669933 , 0x99CC66, 0xCCFF99, 0xFFFFCC, 0xFFCC99 , 0xFF9933, 0xFFCCCC, 0xFF99CC, 0xCC6699 , 0x993366, 0x660033, 0xCC0099, 0x330033 }, { 0x33CC33, 0x66CC66, 0x00FF00, 0x33FF33 , 0x66FF66, 0x99FF99, 0xCCFFCC, 0xFFFFE3 , 0xFFFFFF, 0xFFE3FF, 0xCC99CC, 0x996699 , 0x993399, 0x990099, 0x663366, 0x660066 }, { 0x006600, 0x336633, 0x009900, 0x339933 , 0x669966, 0x99CC99, 0xE3FFFF, 0xFFFFFF , 0xFFFFFF, 0xFFCCFF, 0xFF99FF, 0xFF66FF , 0xFF33FF, 0xFF00FF, 0xCC66CC, 0xCC33CC }, { 0x003300, 0x00CC33, 0x006633, 0x339966 , 0x66CC99, 0x99FFCC, 0xCCFFFF, 0x3399FF , 0x99CCFF, 0xCCCCFF, 0xCC99FF, 0x9966CC , 0x663399, 0x330066, 0x9900CC, 0xCC00CC }, { 0x00FF33, 0x33FF66, 0x009933, 0x00CC66 , 0x33FF99, 0x99FFFF, 0x99CCCC, 0x0066CC , 0x6699CC, 0x9999FF, 0x9999CC, 0x9933FF , 0x6600CC, 0x660099, 0xCC33FF, 0xCC00FF }, { 0x00FF66, 0x66FF99, 0x33CC66, 0x009966 , 0x66FFFF, 0x66CCCC, 0x669999, 0x003366 , 0x336699, 0x6666FF, 0x6666CC, 0x666699 , 0x330099, 0x9933CC, 0xCC66FF, 0x9900FF }, { 0x00FF99, 0x66FFCC, 0x33CC99, 0x33FFFF , 0x33CCCC, 0x339999, 0x336666, 0x006699 , 0x003399, 0x3333FF, 0x3333CC, 0x333399 , 0x333366, 0x6633CC, 0x9966FF, 0x6600FF }, { 0x00FFCC, 0x33FFCC, 0x00FFFF, 0x00CCCC , 0x009999, 0x006666, 0x003333, 0x3399CC , 0x3366CC, 0x0000FF, 0x0000CC, 0x000099 , 0x000066, 0x000033, 0x6633FF, 0x3300FF }, { 0x00CC99, 0x000000, 0x000000, 0x000000 , 0x000000, 0x0099CC, 0x33CCFF, 0x66CCFF , 0x6699FF, 0x3366FF, 0x0033CC, 0x000000 , 0x000000, 0x000000, 0x000000, 0x3300CC }, { 0x000000, 0x222222, 0x444444, 0x666666 , 0x888888, 0x999999, 0x00CCFF, 0x0099FF , 0x0066FF, 0x0033FF, 0xAAAAAA, 0xBBBBBB , 0xCCCCCC, 0xDDDDDD, 0xEEEEEE, 0xFFFFFF }, }; /** The rollover color. */ private static final Color ROLLOVER_COLOR = new Color(0x20ffffff, true); /** The current color. */ private Color color; /** The available colors. */ private Color[][] colors; /** * The constructor. */ public ColorSelector() { this(Color.RED, getDefaultColors()); } /** * The constructor. * * @param color The active color. * @param colors The colors to select from. */ public ColorSelector(Color color, Color[][] colors) { this.color = color; this.colors = colors; // set background setOpaque(false); // listen to clicks and display popup as needed addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { // create and show swatch final JPopupMenu popup = new JPopupMenu(); popup.setOpaque(false); ColorSwatch swatch = new ColorSwatch(ColorSelector.this.colors); swatch.addColorListener(new ColorSwatch.ColorListener() { public void handleColor(Color color) { // set the new color if (color != null) { ColorSelector.this.setColor(color); } // hide the popup popup.setVisible(false); } }); popup.add(swatch); popup.show(ColorSelector.this, getWidth() / 2, getHeight() / 2); } }); } /** * Returns the color. * * @return The color. */ public Color getColor() { return color; } /** * Sets the color. * * @param color The color. */ public void setColor(Color color) { this.color = color; } /** * Returns the colors. * * @return The colors. */ public Color[][] getColors() { return colors; } /** * Sets the colors. * * @param colors The colors. */ public void setColors(Color[][] colors) { this.colors = colors; } /** * Returns the base colors. * * @return The base colors. */ public static Color[][] getDefaultColors() { Color[][] colors = new Color[DEFAULT_COLORS.length][DEFAULT_COLORS[0].length]; for (int y = 0; y < colors.length; y++) { for (int x = 0; x < colors[0].length; x++) { colors[y][x] = new Color(DEFAULT_COLORS[y][x]); } } return colors; } /** * Returns the preferred size. * * @return The preferred size. */ @Override public Dimension getPreferredSize() { return new Dimension(60, 30); } /** * Returns the margin. * * @return The margin. */ private int margin() { return 3; } /** * Draws this component. * * @param g Where to draw to. */ @Override public void paint(Graphics g) { // use antialiasing Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int width = getWidth(); int height = getHeight(); int x = 0; int y = 0; int margin = margin(); // draw border getBorder().paintBorder(this, g, 0, 0, width - 1, height - 1); // draw the color g.setColor(color); g.fillRoundRect(x + margin, y + margin , width - (2 * margin), height - (2 * margin), 5, 5); // draw effect as need ButtonModel model = getModel(); if (model.isPressed()) { g.setColor(ROLLOVER_COLOR); g.fillRoundRect(x + margin, y + margin , width - (2 * margin), height - (2 * margin), 5, 5); g.fillRoundRect(x + margin, y + margin , width - (2 * margin), height - (2 * margin), 5, 5); } else if (model.isRollover()) { g.setColor(ROLLOVER_COLOR); g.fillRoundRect(x + margin, y + margin , width - (2 * margin), height - (2 * margin), 5, 5); } } }
ColorSwatch.java:
import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.event.MouseInputListener; /** * Displays a color swatch. * * @author noblemaster * @since August 16, 2010 */ public class ColorSwatch extends JPanel { /** The rollover color. */ private static final Color ROLLOVER_COLOR = new Color(0x80ffffff, true); /** The listener. */ public static interface ColorListener { /** * Called if a color has been activated. * * @param color The color. */ void handleColor(Color color); } /** The listeners. */ private transient List listeners = new ArrayList(); /** The colors. */ private Color[][] colors; /** The rollover color. */ private Color rolloverColor = null; /** * The constructor. */ public ColorSwatch() { this(new Color[0][0]); } /** * The constructor. * * @param colors The colors to select from. */ public ColorSwatch(Color[][] colors) { this.colors = colors; // set look setOpaque(true); setBackground(Color.WHITE); setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); // add listener MouseInputListener mouseListener = new MouseInputListener() { public void mousePressed(MouseEvent event) { Color color = getColor(event.getX(), event.getY()); // notify about selection for (int i = 0; i < listeners.size(); i++) { listeners.get(i).handleColor(color); } } public void mouseMoved(MouseEvent event) { rolloverColor = getColor(event.getX(), event.getY()); repaint(); } public void mouseDragged(MouseEvent event) { // not used } public void mouseReleased(MouseEvent event) { // not used } public void mouseClicked(MouseEvent arg0) { // not used } public void mouseEntered(MouseEvent arg0) { // not used } public void mouseExited(MouseEvent arg0) { rolloverColor = null; repaint(); } }; addMouseListener(mouseListener); addMouseMotionListener(mouseListener); } /** * Returns the colors. * * @return The colors. */ public Color[][] getColors() { return colors; } /** * Sets the colors. * * @param colors The colors. */ public void setColors(Color[][] colors) { this.colors = colors; } /** * Returns the preferred size. * * @return The preferred size. */ @Override public Dimension getPreferredSize() { int colorSize = getColorSize(); int colorSpacing = getColorSpacing(); int margin = getMargin(); return new Dimension((colorSize + colorSpacing) * colors[0].length + (2 * margin) - colorSpacing , (colorSize + colorSpacing) * colors.length + (2 * margin) - colorSpacing); } /** * Returns the margin/insets from where the colors are painted. Override this method to paint the colors * further inside. * * @return The margin. */ public int getMargin() { return 2; } /** * Returns the color for the given coordinate. * * @param x The x coordinate. * @param y The y coordinate. * @return The color or null if not found. */ private Color getColor(int x, int y) { int margin = getMargin(); int colorSize = getColorSize(); int colorSpacing = getColorSpacing(); x -= margin; y -= margin; int col = x / (colorSize + colorSpacing); int row = y / (colorSize + colorSpacing); if ((col < 0) || (col >= colors[0].length) || (row < 0) || (row >= colors.length)) { return null; } else { return colors[row][col]; } } /** * Returns the spacing. * * @return The spacing. */ private int getColorSpacing() { return getColorSize() > 2 ? 1 : 0; } /** * Returns the size of a color thingy. * * @return The color size in pixels. */ private int getColorSize() { if ((colors[0].length <= 12) && (colors.length <= 8)) { return 15; } else if ((colors[0].length <= 24) && (colors.length <= 16)) { return 7; } else if ((colors[0].length <= 48) && (colors.length <= 32)) { return 5; } else if ((colors[0].length <= 96) && (colors.length <= 64)) { return 3; } else { return 1; } } /** * Draws this component. * * @param g Where to draw to. */ @Override protected void paintComponent(Graphics g) { // use antialiasing Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int width = getWidth(); int height = getHeight(); int margin = getMargin(); int colorSize = getColorSize(); int colorSpacing = getColorSpacing(); // background fill g.setColor(getBackground()); g.fillRect(0, 0, width, height); // draw the colors for (int y = 0; y < colors.length; y++) { for (int x = 0; x < colors[0].length; x++) { Color color = colors[y][x]; g.setColor(color); g.fillRect(x * (colorSize + colorSpacing) + margin , y * (colorSize + colorSpacing) + margin , colorSize , colorSize); // draw rollover? if (colorSpacing > 0) { if (color == rolloverColor) { g.setColor(ROLLOVER_COLOR); g.fillRect(x * (colorSize + colorSpacing) + margin , y * (colorSize + colorSpacing) + margin , colorSize , colorSize); } } } } } /** * Adds a listener. * * @param listener The listener. */ public void addColorListener(ColorListener listener) { listeners.add(listener); } /** * Removes a listener. * * @param listener The listener. */ public void removeColorListener(ColorListener listener) { listeners.remove(listener); } }
Not bad, it looks good. The question I have on it is with the 5 columns for AoC map making. Most of those colors (in tiny boxes) look like they lack the contrast to easily be discernible. Have these colors been tried and test as proven colors that work well with each other on a map?
Comment by fodder — August 20, 2010 @ 08:43
great work !
Comment by Risen — August 20, 2010 @ 14:42
Yes, the “medieval” colors have been used for some of the new version III maps! Obviously, if a scenario has more than 25 empires, the “medieval” colors will not be enough to provide the necessary contrast, that’s why an additional color spectrum is added. Using the “medieval” colors will make the map look more harmonic.
Comment by noblemaster — August 20, 2010 @ 14:57
ColorSwatch.java:
073 listeners.get(i).handleColor(color);
cannot find symbol method handleColor(java.awt.Color)
why this error?
Comment by licon — July 15, 2012 @ 17:28
Did you copy paste the two classes into your project exactly as listed above? Maybe you somehow lost the ColorListener interface in ColorSwatch.java?
Comment by noblemaster — August 11, 2012 @ 01:00