package com.explodingpixels.macwidgets.plaf; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.GradientPaint; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.RoundRectangle2D; import javax.swing.AbstractButton; import javax.swing.JComponent; import javax.swing.UIManager; import com.explodingpixels.macwidgets.MacFontUtils; import com.explodingpixels.macwidgets.WidgetBaseColors; /** * A collection of utilty method for painting Heads Up Style widgets. See the following for examples * of HUD widgets: * <ul> * <li>{@link HudButtonUI}</li> * <li>{@link HudCheckBoxUI}</li> * <li>{@link HudComboBoxUI}</li> * <li>{@link HudLabelUI}</li> * </ul> */ public class HudPaintingUtils { public static final Color FONT_COLOR = Color.WHITE; public static final Color FONT_DISABLED_COLOR = new Color(0xaaaaaa); public static final Color PRESSED_MARK_COLOR = new Color(0, 0, 0, 225); public static final Color BORDER_COLOR = new Color(0xc5c8cf); private static final int BORDER_WIDTH = 1; private static final Color LIGHT_SHADOW_COLOR = new Color(0, 0, 0, 145); private static final Color DARK_SHADOW_COLOR = new Color(0, 0, 0, 50); private HudPaintingUtils() { // utility class - no constructor needed. } /** * Initializes the given {@link javax.swing.JComponent} as a HUD style widget. This includes setting the * font, foreground and opacity of the given component. * * @param component the component to initialize as a HUD component. */ public static void initHudComponent(JComponent component, boolean isDarkColorScheme) { component.setFont(HudPaintingUtils.getHudFont()); if (isDarkColorScheme) component.setForeground(WidgetBaseColors.DARK_FONT_COLOR); else component.setForeground(WidgetBaseColors.LIGHT_FONT_COLOR); // component.setForeground(HudPaintingUtils.FONT_COLOR); component.setOpaque(false); } /** * Gets the font used by HUD style widgets. * * @return the font used by HUD style widgets. */ public static Font getHudFont() { return MacFontUtils.HUD_BUTTON_FONT; } /** * Gets the number of pixels that a HUD style widget's shadow takes up. HUD button's have a * shadow directly below them, that is, there is no top, left or right component to the shadow. * * @param button the button that the shadow is drawn on. * @return the number of pixels that a HUD style widget's shadow takes up. */ public static int getHudControlShadowSize(AbstractButton button) { // TODO use the button's font size to figure this out. return 2; } /** * Paints a HUD style button background onto the given {@link java.awt.Graphics2D} context using the * given {@link com.explodingpixels.macwidgets.plaf.HudPaintingUtils.Roundedness}. The background will be painted from 0,0 to width/height. * * @param graphics the graphics context to paint onto. * @param button the button being painted. * @param width the width of the area to paint. * @param height the height of the area to paint. * @param roundedness the roundedness to use when painting the background. * @param isDarkColorScheme whether to use the light or dark color scheme for this button */ public static void paintHudControlBackground(Graphics2D graphics, AbstractButton button, int width, int height, Roundedness roundedness, boolean isDarkColorScheme) { Paint paint = HudPaintingUtils.createButtonPaint(button, BORDER_WIDTH, isDarkColorScheme); paintHudControlBackground(graphics, new Rectangle(0, 0, width, height), roundedness.getShapeProvider(), paint); } /** * Paints a HUD style background in the given shape. This includes a drop shadow which will be * drawn under the shape to be painted. The shadow will be draw outside the given bounds. * * @param graphics the {@code Graphics2D} context to draw in. * @param bounds the bounds to paint in. * @param shapeProvider the delegate to request the {@link java.awt.Shape} from. * @param paint the {@link java.awt.Paint} to use to fill the {@code Shape}. */ public static void paintHudControlBackground(Graphics2D graphics, Rectangle bounds, ShapeProvider shapeProvider, Paint paint) { graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // TODO replace with real drop shadow painting. int x = bounds.x; int y = bounds.y; int width = bounds.width; int height = bounds.height; graphics.setColor(LIGHT_SHADOW_COLOR); graphics.draw(shapeProvider.createShape(x, y, width - 1, height)); graphics.setColor(DARK_SHADOW_COLOR); graphics.draw(shapeProvider.createShape(x, y, width - 1, height + 1)); graphics.setPaint(paint); graphics.fill(shapeProvider.createShape(x, y + 1, width, height - 1)); graphics.setColor(BORDER_COLOR); graphics.draw(shapeProvider.createShape(x, y, width - 1, height - 1)); } private static Paint createButtonPaint(AbstractButton button, int lineBorderWidth, boolean isDarkColorScheme) { boolean isPressed = button.getModel().isPressed(); Color topPressedColor = isDarkColorScheme ? WidgetBaseColors.DARK_ACTIVE_SELECTED_TOP_COLOR : WidgetBaseColors.LIGHT_ACTIVE_SELECTED_TOP_COLOR; Color topUnpressedColor = isDarkColorScheme ? WidgetBaseColors.DARK_ACTIVE_TOP_COLOR : WidgetBaseColors.LIGHT_ACTIVE_TOP_COLOR; Color bottomPressedColor = isDarkColorScheme ? WidgetBaseColors.DARK_ACTIVE_SELECTED_BOTTOM_COLOR : WidgetBaseColors.LIGHT_ACTIVE_SELECTED_BOTTOM_COLOR; Color bottomUnpressedColor = isDarkColorScheme ? WidgetBaseColors.DARK_ACTIVE_BOTTOM_COLOR : WidgetBaseColors.LIGHT_ACTIVE_BOTTOM_COLOR; Color topColor = isPressed ? topPressedColor : topUnpressedColor; Color bottomColor = isPressed ? bottomPressedColor : bottomUnpressedColor; int bottomY = button.getHeight() - lineBorderWidth * 2; return new GradientPaint(0, lineBorderWidth, topColor, 0, bottomY, bottomColor); } /** * Installs an {@link java.awt.AlphaComposite} on the given {@link java.awt.Graphics2D) if the given * {@link java.awt.Component} is disabled. * * @param graphics the {@code Graphics2D} to adjust. * @param component the {@code Component} whos enablement state should be queried. */ public static void updateGraphicsToPaintDisabledControlIfNecessary(Graphics2D graphics, Component component) { if (!component.isEnabled()) { graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); } } /** * An enumeration representing the roundness styles of HUD buttons. */ public enum Roundedness { ROUNDED_BUTTON(.95), COMBO_BUTTON(0.45), CHECK_BOX(0.4), RADIO(1.0), SLIDER_KNOB(1.0); private final double fRoundedPercentage; private final ShapeProvider fShapeProvider; private Roundedness(double roundedPercentage) { fRoundedPercentage = roundedPercentage; fShapeProvider = createShapeProvider(); } private int getRoundedDiameter(int controlHeight) { // TODO make roudedness based on font size rather than component height. int roundedDiameter = (int) (controlHeight * fRoundedPercentage); int makeItEven = roundedDiameter % 2; return roundedDiameter - makeItEven; } private ShapeProvider getShapeProvider() { return fShapeProvider; } private ShapeProvider createShapeProvider() { return new ShapeProvider() { public Shape createShape(double x, double y, double width, double height) { double arcDiameter = getRoundedDiameter((int) height); return new RoundRectangle2D.Double(x, y, width, height, arcDiameter, arcDiameter); } }; } } public interface ShapeProvider { Shape createShape(double x, double y, double width, double height); } }