package org.multibit.hd.ui.views.animations;
import org.multibit.hd.ui.views.components.ImageDecorator;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
/**
* <p>Icon to provide the following to UI:</p>
* <ul>
* <li>Self-contained rotating icon</li>
* </ul>
*
* @since 0.0.1
*
*/
public class RotatingIcon implements Icon {
private final Icon delegateIcon;
private final int width;
private final int height;
/**
* Required to correct for variation in the centroid of Font Awesome icons
*/
private final int[][] rotationOffsets = new int[][]{
// 0
{0, 0},
{0, 0},
{0, 0},
{0, 1},
// 4
{0, 1},
{0, 1},
{0, 1},
{-1, 1},
// 8
{-1, 1},
{-1, 1},
{-1, 1},
{-1, 0},
// 12
{-1, 0},
{-1, 0},
{-1, 0},
{0, 0},
};
/**
* The number of steps to make per rotation (power of 2)
*/
private final static int maxStepCount = 16;
/**
* Starting position is "North"
*/
private double theta = 0;
/**
* Delta is in radians is a complete circle split into max steps
*/
private double delta = 2 * Math.PI / maxStepCount;
/**
* The current step count (start at the halfway point)
*/
private int stepCount = 0;
/**
* Handles periodic increments of rotation
*/
private final Timer timer;
/**
* @param icon The icon to rotate
* @param component The containing component (e.g. label or button)
*/
public RotatingIcon(Icon icon, final JComponent component) {
delegateIcon = icon;
width = delegateIcon.getIconWidth();
height = delegateIcon.getIconHeight();
// Timer needs to be fairly fast to appear responsive
timer = new Timer(75, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Guaranteed to be on the EDT
incrementRotation(component);
}
});
component.repaint();
// Run continuously
timer.setRepeats(true);
timer.start();
}
/**
* Increment the rotation
*
* @param component The containing component (e.g. label or button)
*/
public void incrementRotation(final JComponent component) {
// Increment theta
theta += delta;
// Rollover to start
stepCount++;
if (stepCount >= maxStepCount) {
theta = 0;
stepCount = 0;
}
component.repaint();
}
public void decrementRotation(JComponent component) {
theta -= delta;
// Rollover to end
if (stepCount == 0) {
theta = 2 * Math.PI - delta;
stepCount = maxStepCount - 1;
} else {
stepCount--;
}
component.repaint();
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
// Prevent timer events during the paint
timer.stop();
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHints(ImageDecorator.smoothRenderingHints());
// Required to center the image within the component
int xCenteringOffset = 0;
int yCenteringOffset = 0;
// Required to compensate for centroid movements during placement within the component
int xRotationOffset = rotationOffsets[stepCount][0];
int yRotationOffset = rotationOffsets[stepCount][1];
// Calculate the centroid
double centerX = xRotationOffset + xCenteringOffset + x + (width / 2);
double centerY = yRotationOffset + yCenteringOffset + y + (height / 2);
// Create rotation transform
AffineTransform tx = new AffineTransform();
tx.rotate(theta, centerX, centerY);
// Paint the icon onto the component
g2.setTransform(tx);
delegateIcon.paintIcon(c, g2, xRotationOffset + xCenteringOffset + x, yRotationOffset + yCenteringOffset + y);
g2.dispose();
// Start the timer again
timer.start();
}
@Override
public int getIconWidth() {
return width;
}
@Override
public int getIconHeight() {
return height;
}
}