diff --git a/src/yugecin/opsudance/core/DisplayContainer.java b/src/yugecin/opsudance/core/DisplayContainer.java
index 1e918fc3..01246ed5 100644
--- a/src/yugecin/opsudance/core/DisplayContainer.java
+++ b/src/yugecin/opsudance/core/DisplayContainer.java
@@ -38,6 +38,7 @@ import yugecin.opsudance.core.errorhandling.ErrorDumpable;
import yugecin.opsudance.core.inject.InstanceContainer;
import yugecin.opsudance.core.state.OpsuState;
import yugecin.opsudance.core.state.specialstates.BarNotificationState;
+import yugecin.opsudance.core.state.specialstates.BubbleNotificationState;
import yugecin.opsudance.core.state.specialstates.FpsRenderState;
import yugecin.opsudance.core.state.transitions.*;
import yugecin.opsudance.events.ResolutionChangedEvent;
@@ -59,6 +60,7 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
private FpsRenderState fpsState;
private BarNotificationState barNotifState;
+ private BubbleNotificationState bubNotifState;
private TransitionState outTransitionState;
private TransitionState inTransitionState;
@@ -76,6 +78,9 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
public int width;
public int height;
+ public int mouseX;
+ public int mouseY;
+
public int targetRenderInterval;
public int targetBackgroundRenderInterval;
@@ -126,6 +131,7 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
fpsState = instanceContainer.provide(FpsRenderState.class);
barNotifState = instanceContainer.provide(BarNotificationState.class);
+ bubNotifState = instanceContainer.provide(BubbleNotificationState.class);
}
@@ -136,6 +142,8 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
timeSinceLastRender += delta;
input.poll(width, height);
+ mouseX = input.getMouseX();
+ mouseY = input.getMouseY();
state.update(delta);
int maxRenderInterval;
@@ -159,6 +167,7 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
state.render(graphics);
fpsState.render(graphics);
barNotifState.render(graphics, timeSinceLastRender);
+ bubNotifState.render(graphics, timeSinceLastRender);
realRenderInterval = timeSinceLastRender;
timeSinceLastRender = 0;
@@ -319,6 +328,9 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen
@Override
public void mouseReleased(int button, int x, int y) {
+ if (bubNotifState.mouseReleased(x, y)) {
+ return;
+ }
state.mouseReleased(button, x, y);
}
diff --git a/src/yugecin/opsudance/core/inject/OpsuDanceInjector.java b/src/yugecin/opsudance/core/inject/OpsuDanceInjector.java
index 9a772786..0617463c 100644
--- a/src/yugecin/opsudance/core/inject/OpsuDanceInjector.java
+++ b/src/yugecin/opsudance/core/inject/OpsuDanceInjector.java
@@ -21,6 +21,7 @@ import yugecin.opsudance.PreStartupInitializer;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.events.EventBus;
import yugecin.opsudance.core.state.specialstates.BarNotificationState;
+import yugecin.opsudance.core.state.specialstates.BubbleNotificationState;
import yugecin.opsudance.core.state.specialstates.FpsRenderState;
import yugecin.opsudance.core.state.transitions.EmptyTransitionState;
import yugecin.opsudance.core.state.transitions.FadeInTransitionState;
@@ -41,6 +42,7 @@ public class OpsuDanceInjector extends Injector {
bind(FpsRenderState.class).asEagerSingleton();
bind(BarNotificationState.class).asEagerSingleton();
+ bind(BubbleNotificationState.class).asEagerSingleton();
bind(EmptyTransitionState.class).asEagerSingleton();
bind(FadeInTransitionState.class).asEagerSingleton();
diff --git a/src/yugecin/opsudance/core/state/specialstates/BubbleNotificationState.java b/src/yugecin/opsudance/core/state/specialstates/BubbleNotificationState.java
new file mode 100644
index 00000000..e6c7f5f3
--- /dev/null
+++ b/src/yugecin/opsudance/core/state/specialstates/BubbleNotificationState.java
@@ -0,0 +1,232 @@
+/*
+ * opsu!dance - fork of opsu! with cursordance auto
+ * Copyright (C) 2017 yugecin
+ *
+ * opsu!dance is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * opsu!dance is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with opsu!dance. If not, see .
+ */
+package yugecin.opsudance.core.state.specialstates;
+
+import itdelatrisu.opsu.ui.Fonts;
+import itdelatrisu.opsu.ui.animations.AnimationEquation;
+import org.newdawn.slick.Color;
+import org.newdawn.slick.Graphics;
+import yugecin.opsudance.core.DisplayContainer;
+import yugecin.opsudance.core.events.EventBus;
+import yugecin.opsudance.core.events.EventListener;
+import yugecin.opsudance.events.BubbleNotificationEvent;
+import yugecin.opsudance.events.ResolutionChangedEvent;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+
+public class BubbleNotificationState implements EventListener {
+
+ public static final int IN_TIME = 633;
+ public static final int DISPLAY_TIME = 7000 + IN_TIME;
+ public static final int OUT_TIME = 433;
+ public static final int TOTAL_TIME = DISPLAY_TIME + OUT_TIME;
+
+ private final DisplayContainer displayContainer;
+ private final LinkedList bubbles;
+
+ private int addAnimationTime;
+ private int addAnimationHeight;
+
+ public BubbleNotificationState(DisplayContainer displayContainer, EventBus eventBus) {
+ this.displayContainer = displayContainer;
+ this.bubbles = new LinkedList<>();
+ this.addAnimationTime = IN_TIME;
+ eventBus.subscribe(BubbleNotificationEvent.class, this);
+ eventBus.subscribe(ResolutionChangedEvent.class, new EventListener() {
+ @Override
+ public void onEvent(ResolutionChangedEvent event) {
+ calculatePositions();
+ }
+ });
+ }
+
+ public void render(Graphics g, int delta) {
+ ListIterator iter = bubbles.listIterator();
+ if (!iter.hasNext()) {
+ return;
+ }
+ addAnimationTime += delta;
+ if (addAnimationTime > IN_TIME) {
+ finishAddAnimation();
+ }
+ boolean animateUp = false;
+ do {
+ Notification next = iter.next();
+ if (animateUp && addAnimationTime < IN_TIME) {
+ next.y = next.baseY - (int) (addAnimationHeight * AnimationEquation.OUT_QUINT.calc((float) addAnimationTime / IN_TIME));
+ }
+ if (next.render(g, displayContainer.mouseX, displayContainer.mouseY, delta)) {
+ iter.remove();
+ }
+ animateUp = true;
+ } while (iter.hasNext());
+ }
+
+ public boolean mouseReleased(int x, int y) {
+ if (x < displayContainer.width - Notification.width) {
+ return false;
+ }
+ for (Notification bubble : bubbles) {
+ if (bubble.mouseReleased(x, y)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void calculatePositions() {
+ Notification.width = (int) (displayContainer.width * 0.1703125f);
+ Notification.baseLine = (int) (displayContainer.height * 0.9645f);
+ Notification.paddingY = (int) (displayContainer.height * 0.0144f);
+ Notification.finalX = displayContainer.width - Notification.width - (int) (displayContainer.width * 0.01);
+ Notification.fontPaddingX = (int) (Notification.width * 0.02f);
+ Notification.fontPaddingY = (int) (Fonts.SMALLBOLD.getLineHeight() / 4f);
+ Notification.lineHeight = Fonts.SMALLBOLD.getLineHeight();
+ if (bubbles.isEmpty()) {
+ return;
+ }
+ finishAddAnimation();
+ }
+
+ private void finishAddAnimation() {
+ if (bubbles.isEmpty()) {
+ addAnimationHeight = 0;
+ addAnimationTime = IN_TIME;
+ return;
+ }
+ ListIterator iter = bubbles.listIterator();
+ iter.next();
+ while (iter.hasNext()) {
+ Notification bubble = iter.next();
+ bubble.y = bubble.baseY - addAnimationHeight;
+ bubble.baseY = bubble.y;
+ }
+ addAnimationHeight = 0;
+ addAnimationTime = IN_TIME;
+ }
+
+ @Override
+ public void onEvent(BubbleNotificationEvent event) {
+ finishAddAnimation();
+ Notification newBubble = new Notification(event.message, event.borderColor);
+ bubbles.add(0, newBubble);
+ addAnimationTime = 0;
+ addAnimationHeight = newBubble.height + Notification.paddingY;
+ ListIterator iter = bubbles.listIterator();
+ iter.next();
+ while (iter.hasNext()) {
+ Notification next = iter.next();
+ next.baseY = next.y;
+ }
+ }
+
+ private static class Notification {
+
+ private final static int HOVER_ANIM_TIME = 150;
+
+ private static int width;
+ private static int finalX;
+ private static int baseLine;
+ private static int fontPaddingX;
+ private static int fontPaddingY;
+ private static int lineHeight;
+ private static int paddingY;
+
+ private final Color bgcol;
+ private final Color textColor;
+ private final Color borderColor;
+ private final Color targetBorderColor;
+
+ private int timeShown;
+ private int x;
+ private int y;
+ private int baseY;
+ private int height;
+ private List lines;
+ private boolean isFading;
+
+ private int hoverTime;
+
+ private Notification(String message, Color borderColor) {
+ this.lines = Fonts.wrap(Fonts.SMALLBOLD, message, (int) (width * 0.96f), true);
+ this.height = (int) (Fonts.SMALLBOLD.getLineHeight() * (lines.size() + 0.5f));
+ this.targetBorderColor = borderColor;
+ this.borderColor = new Color(borderColor);
+ this.textColor = new Color(Color.white);
+ this.bgcol = new Color(Color.black);
+ this.y = baseLine - height;
+ this.baseY = this.y;
+ }
+
+ private boolean render(Graphics g, int mouseX, int mouseY, int delta) {
+ timeShown += delta;
+ processAnimations(isMouseHovered(mouseX, mouseY), delta);
+ g.setColor(bgcol);
+ g.fillRoundRect(x, y, width, height, 6);
+ g.setLineWidth(2f);
+ g.setColor(borderColor);
+ g.drawRoundRect(x, y, width, height, 6);
+ int y = this.y + fontPaddingY;
+ for (String line : lines) {
+ Fonts.SMALLBOLD.drawString(x + fontPaddingX, y, line, textColor);
+ y += lineHeight;
+ }
+ return timeShown > BubbleNotificationState.TOTAL_TIME;
+ }
+
+ private void processAnimations(boolean mouseHovered, int delta) {
+ if (mouseHovered) {
+ hoverTime = Math.min(HOVER_ANIM_TIME, hoverTime + delta);
+ } else {
+ hoverTime = Math.max(0, hoverTime - delta);
+ }
+ float hoverProgress = (float) hoverTime / HOVER_ANIM_TIME;
+ borderColor.r = targetBorderColor.r + (0.977f - targetBorderColor.r) * hoverProgress;
+ borderColor.g = targetBorderColor.g + (0.977f - targetBorderColor.g) * hoverProgress;
+ borderColor.b = targetBorderColor.b + (0.977f - targetBorderColor.b) * hoverProgress;
+ if (timeShown < BubbleNotificationState.IN_TIME) {
+ float progress = (float) timeShown / BubbleNotificationState.IN_TIME;
+ this.x = finalX + (int) ((1 - AnimationEquation.OUT_BACK.calc(progress)) * width / 2);
+ textColor.a = borderColor.a = bgcol.a = progress;
+ return;
+ }
+ x = Notification.finalX;
+ if (timeShown > BubbleNotificationState.DISPLAY_TIME) {
+ isFading = true;
+ float progress = (float) (timeShown - BubbleNotificationState.DISPLAY_TIME) / BubbleNotificationState.OUT_TIME;
+ textColor.a = borderColor.a = bgcol.a = 1f - progress;
+ }
+ }
+
+ private boolean mouseReleased(int x, int y) {
+ if (!isFading && isMouseHovered(x, y)) {
+ timeShown = BubbleNotificationState.DISPLAY_TIME;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isMouseHovered(int x, int y) {
+ return this.x <= x && x < this.x + width && this.y <= y && y <= this.y + this.height;
+ }
+
+ }
+
+}
diff --git a/src/yugecin/opsudance/events/BubbleNotificationEvent.java b/src/yugecin/opsudance/events/BubbleNotificationEvent.java
new file mode 100644
index 00000000..caf5b736
--- /dev/null
+++ b/src/yugecin/opsudance/events/BubbleNotificationEvent.java
@@ -0,0 +1,37 @@
+/*
+ * opsu!dance - fork of opsu! with cursordance auto
+ * Copyright (C) 2017 yugecin
+ *
+ * opsu!dance is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * opsu!dance is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with opsu!dance. If not, see .
+ */
+package yugecin.opsudance.events;
+
+import org.newdawn.slick.Color;
+
+public class BubbleNotificationEvent {
+
+ public static final Color COMMONCOLOR_RED = new Color(138, 72, 51);
+ public static final Color COMMONCOLOR_GREEN = new Color(98, 131, 59);
+ public static final Color COMMONCOLOR_WHITE = new Color(220, 220, 220);
+ public static final Color COMMONCOLOR_PURPLE = new Color(94, 46, 149);
+
+ public final String message;
+ public final Color borderColor;
+
+ public BubbleNotificationEvent(String message, Color borderColor) {
+ this.message = message;
+ this.borderColor = borderColor;
+ }
+
+}
diff --git a/src/yugecin/opsudance/states/EmptyRedState.java b/src/yugecin/opsudance/states/EmptyRedState.java
index dd132f32..c6bdd317 100644
--- a/src/yugecin/opsudance/states/EmptyRedState.java
+++ b/src/yugecin/opsudance/states/EmptyRedState.java
@@ -22,6 +22,7 @@ import org.newdawn.slick.Graphics;
import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.state.OpsuState;
import yugecin.opsudance.events.BarNotificationEvent;
+import yugecin.opsudance.events.BubbleNotificationEvent;
import java.io.StringWriter;
@@ -73,7 +74,7 @@ public class EmptyRedState implements OpsuState {
@Override
public boolean keyPressed(int key, char c) {
- System.out.println("pressed");
+ displayContainer.eventBus.post(new BubbleNotificationEvent("this is a bubble notification... bubbly bubbly bubbly linewraaaaaaaaaap", BubbleNotificationEvent.COMMONCOLOR_RED));
return false;
}
@@ -84,6 +85,7 @@ public class EmptyRedState implements OpsuState {
@Override
public boolean mouseWheelMoved(int delta) {
+ displayContainer.eventBus.post(new BubbleNotificationEvent("Life is like a box of chocolates. It's all going to melt by the end of the day.\n-Emily", BubbleNotificationEvent.COMMONCOLOR_PURPLE));
return false;
}