package com.explodingpixels.widgets.plaf; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import com.explodingpixels.painter.MacWidgetsPainter; import com.explodingpixels.widgets.WindowUtils; /** * An implementation of {@link com.explodingpixels.painter.MacWidgetsPainter} that paints a scroll thumb using images. */ public class ScrollThumbImagePainter implements MacWidgetsPainter<Component> { private final ScrollBarOrientation fOrientation; private final ImageSet fDisabledImageSet; private final ImageSet fInactiveImageSet; private final ImageSet fActiveImageSet; private ScrollThumbImagePainter( ScrollBarOrientation orientation, Image disabledLowerBound, Image disabledMiddle, Image disabledUpperBound, Image inactiveLowerBound, Image inactiveMiddle, Image inactiveUpperBound, Image activeLowerBound, Image activeMiddle, Image activeUpperBound) { // TODO add null checking. fOrientation = orientation; fDisabledImageSet = new ImageSet(disabledLowerBound, disabledMiddle, disabledUpperBound); fInactiveImageSet = new ImageSet(inactiveLowerBound, inactiveMiddle, inactiveUpperBound); fActiveImageSet = new ImageSet(activeLowerBound, activeMiddle, activeUpperBound); } /** * Creates an image-based vertical scroll thumb painter using the given images. Lower bound * refers to the area closest to the minimum value (top or left). Upper bound refers to the area * closest to the maximum value (bottom or right). * * @param disabledLowerBound the lower bound disabled image. * @param disabledMiddle the middle disabled image. * @param disabledUpperBound the upper bound disabled image. * @param inactiveLowerBound the lower bound inactiveimage * @param inactiveMiddle the middle inactive image * @param inactiveUpperBound the upper bound inactive image. * @param activeLowerBound the lower bound active image. * @param activeMiddle the middle active image. * @param activeUpperBound the upper bound active image. * @return a state based (disabled or inactive or active) scroll thumb painter. */ public static ScrollThumbImagePainter createVerticalScrollThumbImagePainter( Image disabledLowerBound, Image disabledMiddle, Image disabledUpperBound, Image inactiveLowerBound, Image inactiveMiddle, Image inactiveUpperBound, Image activeLowerBound, Image activeMiddle, Image activeUpperBound) { return new ScrollThumbImagePainter(ScrollBarOrientation.VERTICAL, disabledLowerBound, disabledMiddle, disabledUpperBound, inactiveLowerBound, inactiveMiddle, inactiveUpperBound, activeLowerBound, activeMiddle, activeUpperBound); } /** * Creates an image-based horizontal scroll thumb painter using the given images. Lower bound * refers to the area closest to the minimum value (top or left). Upper bound refers to the area * closest to the maximum value (bottom or right). * * @param disabledLowerBound the lower bound disabled image. * @param disabledMiddle the middle disabled image. * @param disabledUpperBound the upper bound disabled image. * @param inactiveLowerBound the lower bound inactiveimage * @param inactiveMiddle the middle inactive image * @param inactiveUpperBound the upper bound inactive image. * @param activeLowerBound the lower bound active image. * @param activeMiddle the middle active image. * @param activeUpperBound the upper bound active image. * @return a state based (disabled or inactive or active) scroll thumb painter. */ public static ScrollThumbImagePainter createHorizontalScrollThumbImagePainter( Image disabledLowerBound, Image disabledMiddle, Image disabledUpperBound, Image inactiveLowerBound, Image inactiveMiddle, Image inactiveUpperBound, Image activeLowerBound, Image activeMiddle, Image activeUpperBound) { return new ScrollThumbImagePainter(ScrollBarOrientation.HORIZONTAL, disabledLowerBound, disabledMiddle, disabledUpperBound, inactiveLowerBound, inactiveMiddle, inactiveUpperBound, activeLowerBound, activeMiddle, activeUpperBound); } private ImageSet getImageSet(Component objectToPaint) { ImageSet retVal; if (!objectToPaint.isEnabled()) { retVal = fDisabledImageSet; } else if (WindowUtils.isParentWindowFocused(objectToPaint)) { retVal = fActiveImageSet; } else { retVal = fInactiveImageSet; } return retVal; } public void paint(Graphics2D graphics, Component objectToPaint, int width, int height) { ImageSet imageSet = getImageSet(objectToPaint); // grab the size of the scroll bar in dimension agnostic terms. Dimension scrollerSize = new Dimension(width, height); int scrollerLength = fOrientation.getLength(scrollerSize); int scrollerThickness = fOrientation.getThickness(imageSet.getLowerBoundImageSize()); // calculate the starting point of the lower bound image in dimension agnostic terms, such // that the image is draw centered in the containing component. note that we pass in -1 for // the length of the resultant bounds rectangle as we don't care about creating a second // coordinate. int lowerPosition = 0; Point lowerPoint = fOrientation.createCenteredBounds( objectToPaint, lowerPosition, scrollerThickness, -1).getLocation(); graphics.drawImage(imageSet.getLowerBoundImage(), lowerPoint.x, lowerPoint.y, null); // calculate the starting point of the middle bound image in dimension agnostic terms. start // by determing the position (the value in the scrolling dimension) and length to draw the // image. then create bounds from that position and length. int upperLength = fOrientation.getLength(imageSet.getUpperBoundImageSize()); int middleStartPosition = fOrientation.getLength(imageSet.getLowerBoundImageSize()); int middleEndPosition = scrollerLength - upperLength; int middleLength = middleEndPosition - middleStartPosition; Rectangle middleBounds = fOrientation.createCenteredBounds( objectToPaint, middleStartPosition, scrollerThickness, middleLength); Image middleImage = imageSet.getMiddleImage(); graphics.drawImage(middleImage, middleBounds.x, middleBounds.y, middleBounds.x + middleBounds.width, middleBounds.y + middleBounds.height, 0, 0, middleImage.getWidth(null), middleImage.getHeight(null), null); // calculate the position (the value in the scrolling dimension) of the upper bound image, // such that the image is draw centered in the containing component. note that we pass in -1 // for the length of the resultant bounds rectangle as we don't care about creating a second // coordinate. int upperPosition = scrollerLength - upperLength; Point upperPoint = fOrientation.createCenteredBounds( objectToPaint, upperPosition, scrollerThickness, -1).getLocation(); graphics.drawImage(imageSet.getUpperBoundImage(), upperPoint.x, upperPoint.y, null); } // ImageSet implementation. /////////////////////////////////////////////////////////////////// private static class ImageSet { private final Image fLowerBoundImage; private final Image fMiddleImage; private final Image fUpperBoundImage; private Dimension fLowerBoundImageSize; private Dimension fMiddleBoundImageSize; private Dimension fUppoerBoundImageSize; private ImageSet(Image lowerBoundImage, Image middleImage, Image upperBoundImage) { fLowerBoundImage = lowerBoundImage; fMiddleImage = middleImage; fUpperBoundImage = upperBoundImage; fLowerBoundImageSize = createImageSize(fLowerBoundImage); fMiddleBoundImageSize = createImageSize(fMiddleImage); fUppoerBoundImageSize = createImageSize(fUpperBoundImage); } public Image getLowerBoundImage() { return fLowerBoundImage; } public Image getMiddleImage() { return fMiddleImage; } public Image getUpperBoundImage() { return fUpperBoundImage; } public Dimension getLowerBoundImageSize() { return fLowerBoundImageSize; } public Dimension getMiddleBoundImageSize() { return fMiddleBoundImageSize; } public Dimension getUpperBoundImageSize() { return fUppoerBoundImageSize; } private static Dimension createImageSize(Image image) { return new Dimension(image.getWidth(null), image.getHeight(null)); } } }