package com.explodingpixels.macwidgets.plaf; import java.awt.Color; import java.awt.Dimension; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.RoundRectangle2D; import javax.swing.JComponent; import javax.swing.JSlider; import javax.swing.plaf.basic.BasicSliderUI; /** * Creates a Heads Up Display (HUD) style slider, similar to that seen in various iApps * (e.g. iPhoto). * <br> * <img src="../../../../../graphics/HUDSliderUI-round.png"> * <br/> * <img src="../../../../../graphics/HUDSliderUI-pointy.png"> */ public class HudSliderUI extends BasicSliderUI { private static final int SLIDER_KNOB_WIDTH = 11; private static final int SLIDER_KNOB_HEIGHT_NO_TICKS = 11; private static final int SLIDER_KNOB_HEIGHT_WITH_TICKS = 13; private static final int TRACK_HEIGHT = 4; private static final Color TRACK_BACKGROUND_COLOR = new Color(143, 147, 144, 100); private static final Color TRACK_BORDER_COLOR = new Color(255, 255, 255, 200); private static final Color TOP_SLIDER_KNOB_COLOR = new Color(0x555555); private static final Color BOTTOM_SLIDER_KNOB_COLOR = new Color(0x393939); private static final Color TOP_SLIDER_KNOB_PRESSED_COLOR = new Color(0xb0b2b6); private static final Color BOTTOM_SLIDER_KNOB_PRESSED_COLOR = new Color(0x86888b); private static final HudPaintingUtils.ShapeProvider NO_TICKS_SHAPE_PROVIDER = createCircularSliderKnobShapeProvider(); private static final HudPaintingUtils.ShapeProvider TICKS_SHAPE_PROVIDER = createPointedSliderKnobShapeProvider(); public HudSliderUI(JSlider b) { super(b); } @Override protected void installDefaults(JSlider slider) { super.installDefaults(slider); slider.setOpaque(false); } @Override protected Dimension getThumbSize() { int sliderKnobHeight = slider.getPaintTicks() ? SLIDER_KNOB_HEIGHT_WITH_TICKS : SLIDER_KNOB_HEIGHT_NO_TICKS; return new Dimension(SLIDER_KNOB_WIDTH, sliderKnobHeight); } @Override public void paint(Graphics g, JComponent c) { HudPaintingUtils.updateGraphicsToPaintDisabledControlIfNecessary((Graphics2D) g, c); super.paint(g, c); } @Override public void paintThumb(Graphics graphics) { Paint paint = createSliderKnobButtonPaint(isDragging(), thumbRect.height); HudPaintingUtils.ShapeProvider shapeProvider = slider.getPaintTicks() ? TICKS_SHAPE_PROVIDER : NO_TICKS_SHAPE_PROVIDER; HudPaintingUtils.paintHudControlBackground((Graphics2D) graphics, thumbRect, shapeProvider, paint); } @Override public void paintTrack(Graphics graphics) { Graphics2D graphics2d = (Graphics2D) graphics; graphics2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); double trackY = slider.getHeight() / 2.0 - TRACK_HEIGHT / 2.0; RoundRectangle2D track = new RoundRectangle2D.Double( 0, trackY, slider.getWidth() - 1, TRACK_HEIGHT - 1, 4, 2); graphics.setColor(TRACK_BACKGROUND_COLOR); graphics2d.fill(track); graphics2d.setColor(TRACK_BORDER_COLOR); graphics2d.draw(track); } @Override protected int getTickLength() { return 5; } @Override protected void calculateThumbLocation() { super.calculateThumbLocation(); if (slider.getOrientation() == JSlider.HORIZONTAL && slider.getPaintTicks()) { // shift the thumb down three pixels if we're drawing the pointy style thumb. thumbRect.y += 3; } else { // TODO handle vertical slider. } } @Override protected void calculateTickRect() { super.calculateTickRect(); if (slider.getOrientation() == JSlider.HORIZONTAL) { // shift the ticks down by two pixels so they aren't right up agains the track. tickRect.y += 1; } else { // TODO handle vertical slider. } } @Override protected void paintMajorTickForHorizSlider(Graphics g, Rectangle tickBounds, int x) { g.setColor(Color.WHITE); super.paintMajorTickForHorizSlider(g, tickBounds, x); } @Override public void setThumbLocation(int x, int y) { super.setThumbLocation(x, y); // repaint the whole slider -- it's easier than trying to figure out whats dirty, especially // since the thumb will be drawn outside of the thumbRect (the shadow part). slider.repaint(); } @Override public void paintFocus(Graphics g) { // don't paint focus. } private static Paint createSliderKnobButtonPaint(boolean isPressed, int height) { Color topColor = isPressed ? TOP_SLIDER_KNOB_PRESSED_COLOR : TOP_SLIDER_KNOB_COLOR; Color bottomColor = isPressed ? BOTTOM_SLIDER_KNOB_PRESSED_COLOR : BOTTOM_SLIDER_KNOB_COLOR; // compenstate for the two pixel shadow drawn below the slider thumb. int bottomY = height - 2; return new GradientPaint(0, 0, topColor, 0, bottomY, bottomColor); } /** * Creates a simple circle. */ private static HudPaintingUtils.ShapeProvider createCircularSliderKnobShapeProvider() { return new HudPaintingUtils.ShapeProvider() { public Shape createShape(double x, double y, double width, double height) { return new Ellipse2D.Double(x, y, width, height); } }; } /** * Cerates a pointy slider thumb: * <pre> * +----+ * / \ * + + * | | * + + * \ / * \/ * </pre> */ private static HudPaintingUtils.ShapeProvider createPointedSliderKnobShapeProvider() { return new HudPaintingUtils.ShapeProvider() { public Shape createShape(double x, double y, double width, double height) { float xFloat = (float) x; float yFloat = (float) y; float widthFloat = (float) width; float heightFloat = (float) height; // draw the thumb shape based on the given height and width. GeneralPath path = new GeneralPath(); // move in two pixels so that we can curve down to the next point. path.moveTo(xFloat + 2.0f, yFloat); // curve down to the second point. path.curveTo(xFloat + 0.25f, yFloat + 0.25f, xFloat - 0.25f, yFloat + 2.0f, xFloat, yFloat + 2.0f); // move straight down to the next point. path.lineTo(xFloat, yFloat + heightFloat / 1.60f); // move down and right to form the left half of the pointy section. path.lineTo(xFloat + widthFloat / 2, yFloat + heightFloat); // move up and right to form the right half of the pointy section. path.lineTo(xFloat + widthFloat, yFloat + heightFloat / 1.60f); // move straight up to the point to curve from. path.lineTo(xFloat + widthFloat, yFloat + 2.0f); // curve up and right to the top of the thumb. path.curveTo(xFloat + widthFloat - 0.25f, yFloat + 2.0f, xFloat + widthFloat - 0.25f, yFloat + 0.25f, xFloat + widthFloat - 2.0f, yFloat); path.closePath(); return path; } }; } }