package trident.swing; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Ellipse2D; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.CountDownLatch; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import org.pushingpixels.trident.Timeline; import org.pushingpixels.trident.TimelineScenario; import org.pushingpixels.trident.Timeline.RepeatBehavior; import org.pushingpixels.trident.callback.TimelineScenarioCallback; import org.pushingpixels.trident.ease.Spline; import org.pushingpixels.trident.swing.SwingRepaintTimeline; public final class Fireworks extends JFrame { private Set<VolleyExplosion> volleys; private Map<VolleyExplosion, TimelineScenario> volleyScenarios; private JPanel mainPanel; public class SingleExplosion { float x; float y; float radius; float opacity; Color color; public SingleExplosion(Color color, float x, float y, float radius) { this.color = color; this.x = x; this.y = y; this.radius = radius; this.opacity = 1.0f; } public void setX(float x) { this.x = x; } public void setY(float y) { this.y = y; } public void setRadius(float radius) { this.radius = radius; } public void setOpacity(float opacity) { this.opacity = opacity; } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setComposite(AlphaComposite.SrcOver.derive(this.opacity)); g2d.setColor(this.color); g2d.fill(new Ellipse2D.Float(this.x - this.radius, this.y - this.radius, 2 * radius, 2 * radius)); g2d.dispose(); } } public class VolleyExplosion { private int x; private int y; private Color color; private Set<SingleExplosion> circles; public VolleyExplosion(int x, int y, Color color) { this.x = x; this.y = y; this.color = color; this.circles = new HashSet<SingleExplosion>(); } public TimelineScenario getExplosionScenario() { TimelineScenario scenario = new TimelineScenario.Parallel(); Random randomizer = new Random(); int duration = 1000 + randomizer.nextInt(1000); for (int i = 0; i < 18; i++) { float dist = (float) (50 + 10 * Math.random()); float radius = (float) (2 + 2 * Math.random()); for (float delta = 0.6f; delta <= 1.0f; delta += 0.2f) { float circleRadius = radius * delta; double degrees = 20.0 * (i + Math.random()); float radians = (float) (2.0 * Math.PI * degrees / 360.0); float initDist = delta * dist / 10.0f; float finalDist = delta * dist; float initX = (float) (this.x + initDist * Math.cos(radians)); float initY = (float) (this.y + initDist * Math.sin(radians)); float finalX = (float) (this.x + finalDist * Math.cos(radians)); float finalY = (float) (this.y + finalDist * Math.sin(radians)); SingleExplosion circle = new SingleExplosion(this.color, initX, initY, circleRadius); Timeline timeline = new Timeline(circle); timeline.addPropertyToInterpolate("x", initX, finalX); timeline.addPropertyToInterpolate("y", initY, finalY); timeline.addPropertyToInterpolate("opacity", 1.0f, 0.0f); timeline.setDuration(duration - 200 + randomizer.nextInt(400)); timeline.setEase(new Spline(0.4f)); synchronized (this.circles) { circles.add(circle); } scenario.addScenarioActor(timeline); } } return scenario; } public void paint(Graphics g) { synchronized (this.circles) { for (SingleExplosion circle : this.circles) { circle.paint(g); } } } } public Fireworks() { super("Swing Fireworks"); this.mainPanel = new JPanel() { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); synchronized (volleys) { for (VolleyExplosion exp : volleys) exp.paint(g); } } }; this.mainPanel.setBackground(Color.black); this.mainPanel.setPreferredSize(new Dimension(480, 320)); Timeline repaint = new SwingRepaintTimeline(this); repaint.playLoop(RepeatBehavior.LOOP); this.volleys = new HashSet<VolleyExplosion>(); this.volleyScenarios = new HashMap<VolleyExplosion, TimelineScenario>(); this.mainPanel.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { synchronized (volleys) { for (TimelineScenario scenario : volleyScenarios.values()) scenario.suspend(); } } @Override public void mouseReleased(MouseEvent e) { synchronized (volleys) { for (TimelineScenario scenario : volleyScenarios.values()) scenario.resume(); } } }); mainPanel.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { if ((mainPanel.getWidth() == 0) || (mainPanel.getHeight() == 0)) return; new Thread() { @Override public void run() { while (true) { addExplosions(5); } } }.start(); } }); this.add(mainPanel); this.pack(); this.setLocationRelativeTo(null); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } private void addExplosions(int count) { final CountDownLatch latch = new CountDownLatch(count); Random randomizer = new Random(); for (int i = 0; i < count; i++) { int r = randomizer.nextInt(255); int g = 100 + randomizer.nextInt(155); int b = 50 + randomizer.nextInt(205); Color color = new Color(r, g, b); int x = 60 + randomizer.nextInt(mainPanel.getWidth() - 120); int y = 60 + randomizer.nextInt(mainPanel.getHeight() - 120); final VolleyExplosion exp = new VolleyExplosion(x, y, color); synchronized (volleys) { volleys.add(exp); TimelineScenario scenario = exp.getExplosionScenario(); scenario.addCallback(new TimelineScenarioCallback() { @Override public void onTimelineScenarioDone() { synchronized (volleys) { volleys.remove(exp); volleyScenarios.remove(exp); latch.countDown(); } } }); volleyScenarios.put(exp, scenario); scenario.play(); } } try { latch.await(); } catch (Exception exc) { } } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new Fireworks().setVisible(true); } }); } }