diff --git a/src/org/openstreetmap/josm/gui/GettingStarted.java b/src/org/openstreetmap/josm/gui/GettingStarted.java
index 0a008f9969b..b82f41c1610 100644
--- a/src/org/openstreetmap/josm/gui/GettingStarted.java
+++ b/src/org/openstreetmap/josm/gui/GettingStarted.java
@@ -6,6 +6,10 @@
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
@@ -14,15 +18,18 @@
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
+import javax.swing.event.MouseInputListener;
import org.openstreetmap.josm.actions.DownloadAction;
import org.openstreetmap.josm.actions.DownloadPrimitiveAction;
import org.openstreetmap.josm.actions.HistoryInfoAction;
import org.openstreetmap.josm.data.Version;
+import org.openstreetmap.josm.gui.animation.AnimationExtension;
import org.openstreetmap.josm.gui.animation.AnimationExtensionManager;
import org.openstreetmap.josm.gui.datatransfer.OpenTransferHandler;
import org.openstreetmap.josm.gui.dialogs.MenuItemSearchDialog;
@@ -47,7 +54,7 @@
* It downloads and displays the so called message of the day, which
* contains news about recent major changes, warning in case of outdated versions, etc.
*/
-public final class GettingStarted extends JPanel implements ProxyPreferenceListener {
+public final class GettingStarted extends JPanel implements ProxyPreferenceListener, MouseInputListener {
private final LinkGeneral lg;
private String content = "";
@@ -154,6 +161,11 @@ public GettingStarted() {
scroller.setViewportBorder(new EmptyBorder(10, 100, 10, 100));
add(scroller, BorderLayout.CENTER);
+ scroller.addMouseMotionListener(this);
+ scroller.addMouseListener(this);
+ lg.addMouseMotionListener(this);
+ lg.addMouseListener(this);
+
getMOTD();
setTransferHandler(new OpenTransferHandler());
@@ -175,11 +187,57 @@ public void removeNotify() {
super.removeNotify();
}
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ final AnimationExtension extension = AnimationExtensionManager.getExtension();
+ if (extension instanceof MouseMotionListener) {
+ ((MouseMotionListener) extension).mouseMoved(e);
+ }
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ // Ignored
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ final AnimationExtension extension = AnimationExtensionManager.getExtension();
+ if (extension instanceof MouseListener) {
+ ((MouseListener) extension).mousePressed(e);
+ }
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ // Ignored
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ // Ignored
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ final AnimationExtension extension = AnimationExtensionManager.getExtension();
+ if (extension instanceof MouseListener) {
+ ((MouseListener) extension).mouseReleased(e);
+ }
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ // Ignored
+ }
+
@Override
public void paint(Graphics g) {
super.paint(g);
if (isShowing()) {
- AnimationExtensionManager.getExtension().adjustForSize(getWidth(), getHeight());
+ Point p = new Point(0, 0);
+ SwingUtilities.convertPointToScreen(p, this);
+ AnimationExtensionManager.getExtension().adjustForSize(getWidth(), getHeight(), p.x, p.y);
AnimationExtensionManager.getExtension().animate();
AnimationExtensionManager.getExtension().paint(g);
}
diff --git a/src/org/openstreetmap/josm/gui/animation/AnimationExtension.java b/src/org/openstreetmap/josm/gui/animation/AnimationExtension.java
index 7936d7ebad3..f5cad2791dc 100644
--- a/src/org/openstreetmap/josm/gui/animation/AnimationExtension.java
+++ b/src/org/openstreetmap/josm/gui/animation/AnimationExtension.java
@@ -15,8 +15,10 @@ public interface AnimationExtension {
* Adjusts for size.
* @param w width
* @param h height
+ * @param x x origin of view area
+ * @param y y origin of view area
*/
- void adjustForSize(int w, int h);
+ void adjustForSize(int w, int h, int x, int y);
/**
* Paints static contents.
diff --git a/src/org/openstreetmap/josm/gui/animation/AnimationExtensionManager.java b/src/org/openstreetmap/josm/gui/animation/AnimationExtensionManager.java
index efb32f3e444..3b367aa3970 100644
--- a/src/org/openstreetmap/josm/gui/animation/AnimationExtensionManager.java
+++ b/src/org/openstreetmap/josm/gui/animation/AnimationExtensionManager.java
@@ -2,6 +2,7 @@
package org.openstreetmap.josm.gui.animation;
import java.time.LocalDate;
+import java.time.Month;
import java.time.ZoneId;
import org.openstreetmap.josm.data.preferences.BooleanProperty;
@@ -27,8 +28,14 @@ private AnimationExtensionManager() {
*/
public static AnimationExtension getExtension() {
if (currentExtension == null) {
- currentExtension = Boolean.TRUE.equals(PROP_ANIMATION.get()) && isChristmas() ? new ChristmasExtension()
- : new NoExtension();
+ final boolean isAnimated = Boolean.TRUE.equals(PROP_ANIMATION.get());
+ if (isAnimated && isChristmas()) {
+ currentExtension = new ChristmasExtension();
+ } else if (isAnimated && isBirthday()) {
+ currentExtension = new BirthdayExtension();
+ } else {
+ currentExtension = new NoExtension();
+ }
}
return currentExtension;
}
@@ -45,4 +52,13 @@ public static boolean isExtensionEnabled() {
private static boolean isChristmas() {
return LocalDate.now(ZoneId.systemDefault()).getDayOfYear() > 350;
}
+
+ /**
+ * The first commit of JOSM to svn (r1) was on 2005-09-27
+ * @return {@code true} if today is JOSM's birthday
+ */
+ private static boolean isBirthday() {
+ LocalDate l = LocalDate.now(ZoneId.systemDefault());
+ return l.getMonth() == Month.SEPTEMBER && l.getDayOfMonth() == 27;
+ }
}
diff --git a/src/org/openstreetmap/josm/gui/animation/BirthdayExtension.java b/src/org/openstreetmap/josm/gui/animation/BirthdayExtension.java
new file mode 100644
index 00000000000..bfe374bf771
--- /dev/null
+++ b/src/org/openstreetmap/josm/gui/animation/BirthdayExtension.java
@@ -0,0 +1,344 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.animation;
+
+import java.awt.event.MouseMotionListener;
+import java.util.ArrayList;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Dimension;
+import java.awt.Image;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+import javax.swing.JPanel;
+
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Birthday animation game extension.
+ * @author Pauline Thiele
+ * @since xxx
+ */
+public class BirthdayExtension extends JPanel implements AnimationExtension, MouseListener, MouseMotionListener {
+ private static final int SIZE = 40;
+ private final Image myImage;
+ private double w;
+ private double h;
+ private double Mx;
+ private double My;
+ private int originX;
+ private int originY;
+ private boolean isClicked;
+ private boolean isHappyBirthday;
+ private int giftNumber = 1;
+
+ private final ArrayList giftList = new ArrayList<>();
+ private long startTime;
+ private long lastSpawnTime;
+
+ /**
+ * Creates a circle with a position, radius and checks if two circles ovelaps
+ */
+ private static final class Circle {
+ double x;
+ double y;
+ double radius = 1;
+
+ void setPosition(double x, double y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ void setRadius(double radius) {
+ this.radius = radius;
+ }
+
+ boolean overlaps(Circle other) {
+ double dx = x - other.x;
+ double dy = y - other.y;
+ double dr = radius + other.radius;
+ return dx*dx + dy*dy < dr*dr;
+ }
+ }
+
+ /**
+ * Creates a player with a position, radius and updates position
+ */
+ private final class Player {
+ Circle circle = new Circle();
+
+ void setPosition(double x, double y) {
+ circle.setPosition(x, y);
+ }
+
+ void setRadius(double radius) {
+ circle.setRadius(radius);
+ }
+
+ void update() {
+ setPosition(Mx, My);
+ }
+ }
+
+ /**
+ * Creates a gift with a position, radius, updates position and renders the image
+ */
+ private final class Gift {
+ Image myImage = getImage();
+ Circle circle = new Circle();
+ int offsX = 4;
+ int offsY = 4;
+
+ void setPosition(double x, double y) {
+ circle.setPosition(x, y);
+ }
+
+ void setRadius(double radius) {
+ circle.setRadius(radius);
+ }
+
+ void update() {
+ circle.x += offsX;
+ circle.y += offsY;
+
+ if (circle.x <= 0) { // left
+ offsX *= -1;
+ circle.x = 0;
+ if (circle.y == 0) {
+ offsY *= -1;
+ }
+ } else if (circle.x >= (w - SIZE)) { // right
+ offsX *= -1;
+ circle.x = w - SIZE;
+ }
+
+ if (circle.y <= 0) { // top
+ offsY *= -1;
+ circle.y = 0;
+ } else if (circle.y >= (h - SIZE)) { // bottom
+ offsY *= -1;
+ circle.y = h - SIZE;
+ }
+ }
+
+ void render(Graphics g) {
+ double x = circle.x;
+ double y = circle.y;
+ g.drawImage(myImage, (int) x, (int) y, null);
+ }
+ }
+
+ Player player = new Player();
+
+ BirthdayExtension() {
+ this.myImage = new ImageProvider("presets/shop/present").
+ setMaxSize(new Dimension(SIZE, SIZE)).get().getImage();
+ player.setRadius(11);
+ lastSpawnTime = System.currentTimeMillis();
+ }
+
+ /**
+ * Creates gifts on a random position on screen
+ */
+ void spawnGifts() {
+ final byte maxGifts = 30;
+ if (giftList.size() < maxGifts) {
+ lastSpawnTime = System.currentTimeMillis();
+
+ Gift gift = new Gift();
+ giftList.add(gift);
+ gift.myImage = getImage();
+ gift.setRadius(32);
+
+ double x = (w - originX) / 2;
+ double y = (h - originY) / 2;
+
+ if (giftList.size() > 1) {
+ double radius = w / 2;
+ double angle = 2 * Math.PI * Math.random();
+ x += Math.cos(angle) * radius;
+ y += Math.sin(angle) * radius;
+ gift.setPosition(x, y);
+ } else {
+ gift.setPosition(x, y);
+ }
+ }
+ }
+
+ /**
+ * Manages the creation, updating and deletion of gifts
+ */
+ private void gameLogic() {
+ if ((giftList.isEmpty() && !isHappyBirthday) ||
+ (System.currentTimeMillis() - lastSpawnTime > 5000 && !isHappyBirthday)) {
+ spawnGifts();
+ lastSpawnTime = System.currentTimeMillis();
+ }
+
+ player.update();
+
+ for (Gift gift : giftList) {
+ gift.update();
+ }
+
+ int index = 0;
+ for (Gift gift : giftList) {
+ if (gift.circle.overlaps(player.circle) && isClicked) {
+ startTime = System.currentTimeMillis();
+ giftList.remove(index);
+ break;
+ }
+ index += 1;
+ }
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ isClicked = true;
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ isClicked = false;
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {}
+
+ @Override
+ public void mouseExited(MouseEvent e) {}
+
+ @Override
+ public void mouseClicked(MouseEvent e) {}
+
+ @Override
+ public void mouseDragged(MouseEvent e) {}
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ Mx = e.getXOnScreen()- originX;
+ My = e.getYOnScreen()- originY;
+
+ player.setPosition(Mx, My);
+
+ huntGift();
+ }
+
+ @Override
+ public final void adjustForSize(int w, int h, int x, int y) {
+ this.w = w;
+ this.h = h;
+ originX = x;
+ originY = y;
+ }
+
+ /**
+ * Visualizes the gifts and the "Happy Birthday JOSM!" text
+ */
+ @Override
+ public void paint(Graphics g) {
+ gameLogic();
+ for (Gift gift : giftList) {
+ gift.render(g);
+ }
+
+ Graphics2D g2d = (Graphics2D) g;
+
+ /* keep a bit of debugging code
+ if (Logging.isTraceEnabled()) {
+ // draws circles around mouse and gifts
+ for (Gift gift : giftList) {
+ Ellipse2D.Double circleG = new Ellipse2D.Double(gift.circle.x - gift.circle.radius / 2,
+ gift.circle.y - gift.circle.radius / 2, gift.circle.radius * 2, gift.circle.radius * 2);
+ g2d.draw(circleG);
+ }
+ Ellipse2D.Double circleM = new Ellipse2D.Double(player.circle.x - player.circle.radius / 2,
+ player.circle.y - player.circle.radius / 2, player.circle.radius * 2, player.circle.radius * 2);
+ g2d.draw(circleM);
+ }
+ */
+
+ if (giftList.isEmpty()) {
+ if (System.currentTimeMillis() - startTime <= 5000) {
+ int fontSize = SIZE /2;
+ g2d.setFont(new Font("Arial", Font.BOLD, fontSize));
+ g2d.setColor(Color.RED);
+
+ String text = "Happy Birthday JOSM! ";
+
+ FontMetrics fm = g2d.getFontMetrics();
+ int textWidth = fm.stringWidth(text);
+ int x = 0;
+ int y = SIZE /2;
+
+ for (int i = 0; i < (w / textWidth) + 1; i++) {
+ g2d.drawString(text, x, y);
+ g2d.drawString(text, x, (int) h-y);
+ x += textWidth;
+ }
+ isHappyBirthday = true;
+ } else {
+ giftNumber += 1;
+ for (int i = 0; i < giftNumber; i += 1) {
+ spawnGifts();
+ }
+ startTime = System.currentTimeMillis();
+ isHappyBirthday = false;
+ }
+ }
+ }
+
+ @Override
+ public void animate() {
+ }
+
+ /**
+ * Hunts the gift
+ */
+ private void huntGift() {
+ for (Gift gift : giftList) {
+ double dx = gift.circle.x - player.circle.x;
+ double dy = gift.circle.y - player.circle.y;
+
+ double len = Math.sqrt(dx*dx + dy*dy);
+
+ if ((dx < -100 || dx > 100) && (dy < -100 || dy > 100)) {
+ dx = 0;
+ dy = 0;
+ } else if (len > 0 && dx != 0 && dy != 0) {
+ dx /= len;
+ dy /= len;
+ }
+
+ double giftSpeed;
+
+ if (dx != 0 && dy != 0 && gift.circle.x == 0 && gift.circle.y == 0) { // top left corner
+ giftSpeed = 5.0f;
+ dy += 5;
+ } else if (dx != 0 && dy != 0 && gift.circle.x >= w - SIZE && gift.circle.y == 0) { // top right corner
+ giftSpeed = 5.0f;
+ dy += 5;
+ } else if (dx != 0 && dy != 0 && gift.circle.x == 0 && gift.circle.y >= h - SIZE) { // bottom left corner
+ giftSpeed = 5.0f;
+ dx += 5;
+ } else if (dx != 0 && dy != 0 && gift.circle.x >= w - SIZE && gift.circle.y >= h - SIZE) { // bottom right corner
+ giftSpeed = 5.0f;
+ dx -= 5;
+ } else {
+ giftSpeed = 3.0f;
+ }
+
+ gift.circle.x += dx * giftSpeed;
+ gift.circle.y += dy * giftSpeed;
+ }
+ }
+
+ private Image getImage() {
+ return this.myImage;
+ }
+}
diff --git a/src/org/openstreetmap/josm/gui/animation/ChristmasExtension.java b/src/org/openstreetmap/josm/gui/animation/ChristmasExtension.java
index cf777e28e16..7ba632c54c7 100644
--- a/src/org/openstreetmap/josm/gui/animation/ChristmasExtension.java
+++ b/src/org/openstreetmap/josm/gui/animation/ChristmasExtension.java
@@ -28,7 +28,7 @@ public void animate() {
}
@Override
- public final void adjustForSize(int w, int h) {
+ public final void adjustForSize(int w, int h, int x, int y) {
int count = w / (2 * (Star.averageStarWidth + 1));
while (objs.size() > count) {
objs.remove(objs.size() - 1);
diff --git a/src/org/openstreetmap/josm/gui/animation/NoExtension.java b/src/org/openstreetmap/josm/gui/animation/NoExtension.java
index f61bcd46da7..a5da985a3fc 100644
--- a/src/org/openstreetmap/josm/gui/animation/NoExtension.java
+++ b/src/org/openstreetmap/josm/gui/animation/NoExtension.java
@@ -15,7 +15,7 @@ public class NoExtension implements AnimationExtension {
}
@Override
- public void adjustForSize(int w, int h) {
+ public void adjustForSize(int w, int h, int x, int y) {
// No-op
}