package magic.ui.widget.throbber; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.HierarchyEvent; import java.awt.image.BufferedImage; import magic.ui.helpers.ImageHelper; @SuppressWarnings("serial") public class ImageThrobber extends AbstractThrobber { // The original reference image. private final BufferedImage sourceImage; // The actual image displayed. Can be resized, recolored or exactly the same as source. private Image displayImage; // Optional builder property will scale image to fit container. Default is false. private final boolean sizeToFit; // If image is exactly the same size as this component no scaling required. private boolean isExactFit = false; // The center point of this component. Updated on resize. private Point centerPoint; public static abstract class Init<T extends Init<T>> extends AbstractThrobber.Init<Init<T>> { // mandatory, must be set via Builder constructor. protected BufferedImage sourceImage; // optional properties specific to this class with default values private boolean sizeToFit = false; public Init<T> sizeToFit(boolean b) { this.sizeToFit = b; return self(); } public ImageThrobber build() { return new ImageThrobber(this); } } public static class Builder extends Init<Builder> { // assume image has already been optimized. public Builder(final BufferedImage image) { this.sourceImage = image; } @Override protected Builder self() { return this; } } // CTR protected ImageThrobber(final Init<?> init) { super(init); this.sourceImage = init.sourceImage; this.sizeToFit = init.sizeToFit; setPreferredSize(new Dimension(sourceImage.getWidth(), sourceImage.getHeight())); addHierarchyListener((e) -> { final boolean isShowingChanged = (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) == HierarchyEvent.SHOWING_CHANGED; if (isShowingChanged) { displayImage = isColorSet ? ImageHelper.getColoredImage(sourceImage, getForeground()) : sourceImage; isExactFit = (sourceImage.getWidth() == getWidth() && sourceImage.getHeight() == getHeight()); centerPoint = new Point(getWidth() / 2, getHeight() / 2); } }); addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { isExactFit = (sourceImage.getWidth() == getWidth() && sourceImage.getHeight() == getHeight()); centerPoint = new Point(getWidth() / 2, getHeight() / 2); } }); setBounds(0, 0, sourceImage.getWidth(), sourceImage.getHeight()); } @Override protected void drawFrame(Graphics2D g2, int angle) { if (displayImage == null) return; if (centerPoint != null) { if (sizeToFit && isExactFit == false) { drawSizeToFit(g2, angle); } else { drawOriginalSize(g2, angle); } } } private void drawSizeToFit(Graphics2D g2, int angle) { final double radiansToRotate = getSpinDirection() == SpinDirection.CLOCKWISE ? getRadians(angle) : -getRadians(angle); // translate(cx, cy) -> rotate(theta) -> translate(-cx, -cy); g2.rotate(radiansToRotate, centerPoint.x, centerPoint.y); // draw and scale to container dimensions. g2.drawImage(displayImage, 0, 0, getWidth(), getHeight(), null); g2.dispose(); } private void drawOriginalSize(Graphics2D g2, int angle) { final double radiansToRotate = getSpinDirection() == SpinDirection.CLOCKWISE ? getRadians(angle) : -getRadians(angle); // move origin to center of container before rotating. g2.translate(centerPoint.x, centerPoint.y); g2.rotate(radiansToRotate); // draw image so that its center point = container center point. final int cx = displayImage.getWidth(null) / 2; final int cy = displayImage.getHeight(null) / 2; g2.translate(-cx, -cy); g2.drawImage(displayImage, 0, 0, null); g2.dispose(); } }