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; }