package trident.swing; import java.awt.*; import java.awt.geom.GeneralPath; import java.util.Deque; import java.util.LinkedList; import javax.swing.*; import org.pushingpixels.trident.Timeline; import org.pushingpixels.trident.TridentConfig; import org.pushingpixels.trident.Timeline.RepeatBehavior; import org.pushingpixels.trident.Timeline.TimelineState; import org.pushingpixels.trident.callback.TimelineCallbackAdapter; import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter; import org.pushingpixels.trident.swing.SwingRepaintTimeline; public class StarDust extends JFrame { public class Star { private double x; private double y; private float alpha; private double outerSpan; private Color color; private float rotation; public Star(double startX, double startY, double startOuterSpan) { this.x = startX; this.y = startY; this.outerSpan = startOuterSpan; this.color = Color.white; this.alpha = 1.0f; this.rotation = 0.0f; } public void setX(double x) { this.x = x; } public void setY(double y) { this.y = y; } public void setAlpha(float alpha) { this.alpha = alpha; } public void setRotation(float rotation) { this.rotation = rotation; } public void setOuterSpan(double outerSpan) { this.outerSpan = outerSpan; } public void setColor(Color color) { this.color = color; } public Shape getCurrentShape() { double innerSpan = this.outerSpan * (0.1f + 0.1f * outerSpan / 20); int POINTS = 5; GeneralPath result = new GeneralPath(); for (int i = 0; i < POINTS; i++) { double outerAngle = this.rotation + i * 2 * Math.PI / POINTS; double xOuter = this.x + this.outerSpan * Math.cos(outerAngle); double yOuter = this.y + this.outerSpan * Math.sin(outerAngle); if (i == 0) { result.moveTo(xOuter, yOuter); } else { result.lineTo(xOuter, yOuter); } double innerAngle = outerAngle + Math.PI / POINTS; double xInner = this.x + innerSpan * Math.cos(innerAngle); double yInner = this.y + innerSpan * Math.sin(innerAngle); result.lineTo(xInner, yInner); } result.closePath(); return result; } } private Deque<Star> stars; private class MainPanel extends JPanel { @Override protected void paintComponent(Graphics g) { g.setColor(Color.black); g.fillRect(0, 0, getWidth(), getHeight()); Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); synchronized (stars) { for (Star star : stars) { g2d.setColor(star.color); g2d.setComposite(AlphaComposite.SrcOver.derive(star.alpha)); Shape starShape = star.getCurrentShape(); g2d.fill(starShape); } } g2d.dispose(); } } public StarDust() { super("Star dust"); final MainPanel mainPanel = new MainPanel(); this.add(mainPanel); // higher pulse rate to create 50 stars a second TridentConfig.getInstance().setPulseSource( new TridentConfig.FixedRatePulseSource(20)); this.stars = new LinkedList<Star>(); Timeline spawner = new Timeline(); spawner.addCallback(new UIThreadTimelineCallbackAdapter() { private float currHue = 0.0f; @Override public void onTimelinePulse(float durationFraction, float timelinePosition) { Point mouseLoc = MouseInfo.getPointerInfo().getLocation(); SwingUtilities.convertPointFromScreen(mouseLoc, mainPanel); double currX = mouseLoc.getX(); double currY = mouseLoc.getY(); if ((currX < 0) || (currY < 0) || (currX > mainPanel.getWidth()) || (currY > mainPanel.getHeight())) return; double outerStartSpan = 5; double outerFinalSpan = 20; final Star star = new Star(currX, currY, outerStartSpan); synchronized (stars) { stars.addFirst(star); } Timeline starTimeline = new Timeline(star); double angle = Math.random() * 2.0 * Math.PI; double distance = 20.0 + 30.0 * Math.random(); starTimeline.addPropertyToInterpolate("x", currX, currX + distance * Math.cos(angle)); starTimeline.addPropertyToInterpolate("y", currY, currY + distance * Math.sin(angle)); starTimeline.addPropertyToInterpolate("alpha", 1.0f, 0.0f); starTimeline.addPropertyToInterpolate("rotation", 0.0f, (float) (2 * Math.PI * Math.random())); starTimeline.addPropertyToInterpolate("outerSpan", outerStartSpan, outerFinalSpan); starTimeline.addPropertyToInterpolate("color", Color.white, new Color(Color.HSBtoRGB(currHue, 0.8f, 0.7f))); currHue += 0.01f; starTimeline.addCallback(new TimelineCallbackAdapter() { @Override public void onTimelineStateChanged(TimelineState oldState, TimelineState newState, float durationFraction, float timelinePosition) { if (newState == TimelineState.DONE) { // should be the last one in the list synchronized (stars) { stars.removeLast(); } } } }); starTimeline.setDuration(3000); starTimeline.play(); } }); spawner.playLoop(RepeatBehavior.LOOP); new SwingRepaintTimeline(mainPanel).playLoop(RepeatBehavior.LOOP); this.setSize(400, 300); this.setLocationRelativeTo(null); this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new StarDust().setVisible(true); } }); } }