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 }