diff --git a/res/menu-back-chevron.png b/res/menu-back-chevron.png new file mode 100644 index 00000000..0f8d924e Binary files /dev/null and b/res/menu-back-chevron.png differ diff --git a/res/menu-back-slope.png b/res/menu-back-slope.png new file mode 100644 index 00000000..3385f226 Binary files /dev/null and b/res/menu-back-slope.png differ diff --git a/res/menu-back.png b/res/menu-back.png index 6f09cd10..e3678753 100644 Binary files a/res/menu-back.png and b/res/menu-back.png differ diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index f7ce5c83..a30400f7 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -253,6 +253,8 @@ public enum GameImage { } }, MENU_BACK ("menu-back", "menu-back-%d", "png"), + MENU_BACK_CHEVRON ("menu-back-chevron", "png"), + MENU_BACK_SLOPE("menu-back-slope", "png"), MENU_BUTTON_BG ("menu-button-background", "png", false, false), MENU_TAB ("selection-tab", "png", false, false) { @Override diff --git a/src/itdelatrisu/opsu/states/DownloadsMenu.java b/src/itdelatrisu/opsu/states/DownloadsMenu.java index ddb1d5a3..da518b84 100644 --- a/src/itdelatrisu/opsu/states/DownloadsMenu.java +++ b/src/itdelatrisu/opsu/states/DownloadsMenu.java @@ -519,7 +519,7 @@ public class DownloadsMenu extends ComplexOpsuState { // back button else - UI.getBackButton().draw(); + UI.getBackButton().draw(g); UI.draw(g); } diff --git a/src/itdelatrisu/opsu/states/GameRanking.java b/src/itdelatrisu/opsu/states/GameRanking.java index 8c74a4a4..224adadc 100644 --- a/src/itdelatrisu/opsu/states/GameRanking.java +++ b/src/itdelatrisu/opsu/states/GameRanking.java @@ -99,7 +99,7 @@ public class GameRanking extends BaseOpsuState { replayButton.draw(); if (data.isGameplay() && !GameMod.AUTO.isActive()) retryButton.draw(); - UI.getBackButton().draw(); + UI.getBackButton().draw(g); UI.draw(g); diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index c8f4e666..ec54b372 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -715,7 +715,7 @@ public class SongMenu extends ComplexOpsuState { // back button else - UI.getBackButton().draw(); + UI.getBackButton().draw(g); UI.draw(g); diff --git a/src/itdelatrisu/opsu/ui/UI.java b/src/itdelatrisu/opsu/ui/UI.java index 6b057da8..586075b1 100644 --- a/src/itdelatrisu/opsu/ui/UI.java +++ b/src/itdelatrisu/opsu/ui/UI.java @@ -33,6 +33,8 @@ import org.newdawn.slick.Color; import org.newdawn.slick.Graphics; import org.newdawn.slick.Image; import yugecin.opsudance.core.DisplayContainer; +import yugecin.opsudance.events.ResolutionOrSkinChangedEvent; +import yugecin.opsudance.ui.BackButton; /** * Draws common UI components. @@ -40,7 +42,7 @@ import yugecin.opsudance.core.DisplayContainer; public class UI { /** Back button. */ - private static MenuButton backButton; + private static BackButton backButton; /** Time to show volume image, in milliseconds. */ private static final int VOLUME_DISPLAY_TIME = 1500; @@ -68,18 +70,10 @@ public class UI { */ public static void init(DisplayContainer displayContainer) { UI.displayContainer = displayContainer; + } - // back button - if (GameImage.MENU_BACK.getImages() != null) { - Animation back = GameImage.MENU_BACK.getAnimation(120); - backButton = new MenuButton(back, back.getWidth() / 2f, displayContainer.height - (back.getHeight() / 2f)); - } else { - Image back = GameImage.MENU_BACK.getImage(); - backButton = new MenuButton(back, back.getWidth() / 2f, displayContainer.height - (back.getHeight() / 2f)); - } - backButton.setHoverAnimationDuration(350); - backButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_BACK); - backButton.setHoverExpand(MenuButton.Expand.UP_RIGHT); + public static void revalidate() { + backButton = new BackButton(displayContainer); } /** @@ -110,7 +104,7 @@ public class UI { /** * Returns the 'menu-back' MenuButton. */ - public static MenuButton getBackButton() { return backButton; } + public static BackButton getBackButton() { return backButton; } /** * Draws a tab image and text centered at a location. diff --git a/src/yugecin/opsudance/core/DisplayContainer.java b/src/yugecin/opsudance/core/DisplayContainer.java index 9d019e94..fe7549f0 100644 --- a/src/yugecin/opsudance/core/DisplayContainer.java +++ b/src/yugecin/opsudance/core/DisplayContainer.java @@ -350,6 +350,7 @@ public class DisplayContainer implements ErrorDumpable, KeyListener, MouseListen Fonts.init(); eventBus.post(new ResolutionOrSkinChangedEvent()); + UI.revalidate(); // TODO this shouldn't be here } public void resetCursor() { diff --git a/src/yugecin/opsudance/ui/BackButton.java b/src/yugecin/opsudance/ui/BackButton.java new file mode 100644 index 00000000..3e72d21f --- /dev/null +++ b/src/yugecin/opsudance/ui/BackButton.java @@ -0,0 +1,225 @@ +/* + * 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.ui; + +import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.audio.MusicController; +import itdelatrisu.opsu.ui.Fonts; +import itdelatrisu.opsu.ui.MenuButton; +import itdelatrisu.opsu.ui.animations.AnimationEquation; +import org.newdawn.slick.*; +import yugecin.opsudance.core.DisplayContainer; + +public class BackButton { + + /** Skinned back button. */ + private MenuButton backButton; + + /** Colors. */ + private static final Color + COLOR_PINK = new Color(238, 51, 153), + COLOR_DARKPINK = new Color(186, 19, 121); + + /** Target duration, in ms, of the button animations. */ + private static final int ANIMATION_TIME = 500; + + /** How much time passed for the animations. */ + private int animationTime; + + /** The size of the slope image (square shape). */ + private int slopeImageSize; + + /** The width of the slope part in the slope image. */ + private int slopeImageSlopeWidth; + + /** The width of the first part of the button. */ + private int firstButtonWidth; + + /** The width of the second part of the button. */ + private int secondButtonSize; + + /** Variable to hold the hovered state, to not recalculate it twice per frame. */ + private boolean isHovered; + + /** The width of the "back" text to draw. */ + private int textWidth; + + /** Y padding for the text and general positioning. */ + private float paddingY; + + /** X padding for the text. */ + private float paddingX; + + /** Y text offset because getHeight is not so accurate. */ + private float textOffset; + + /** The base size of the chevron. */ + private float chevronBaseSize; + + /** The Y position of where the button starts. */ + private int buttonYpos; + + /** Variable holding the slope image. */ + private Image slopeImage; + + /** The real button with, determined by the size and animations. */ + private int realButtonWidth; + + public BackButton(DisplayContainer container) { + //if (!GameImage.MENU_BACK.hasGameSkinImage()) { + if (GameImage.MENU_BACK.getImage() != null && GameImage.MENU_BACK.getImage().getWidth() < 2) { + backButton = null; + textWidth = Fonts.MEDIUM.getWidth("back"); + paddingY = Fonts.MEDIUM.getHeight("back"); + // getHeight doesn't seem to be so accurate + textOffset = paddingY * 0.264f; + paddingY *= 0.736f; + paddingX = paddingY / 2f; + chevronBaseSize = paddingY * 3f / 2f; + buttonYpos = (int) (container.height - paddingY * 4f); + slopeImageSize = (int) (paddingY * 3f); + slopeImageSlopeWidth = (int) (slopeImageSize * 0.295f); + firstButtonWidth = slopeImageSize; + secondButtonSize = (int) (slopeImageSlopeWidth + paddingX * 2 + textWidth); + slopeImage = GameImage.MENU_BACK_SLOPE.getImage().getScaledCopy(slopeImageSize, slopeImageSize); + return; + } + + if (GameImage.MENU_BACK.getImages() != null) { + Animation back = GameImage.MENU_BACK.getAnimation(120); + backButton = new MenuButton(back, back.getWidth() / 2f, container.height - (back.getHeight() / 2f)); + } else { + Image back = GameImage.MENU_BACK.getImage(); + backButton = new MenuButton(back, back.getWidth() / 2f, container.height - (back.getHeight() / 2f)); + } + backButton.setHoverAnimationDuration(350); + backButton.setHoverAnimationEquation(AnimationEquation.IN_OUT_BACK); + backButton.setHoverExpand(MenuButton.Expand.UP_RIGHT); + } + + /** + * Draws the backbutton. + */ + public void draw(Graphics g) { + // draw image if it's skinned + if (backButton != null) { + backButton.draw(); + return; + } + + // calc chevron size + Float beatProgress = MusicController.getBeatProgress(); + if (beatProgress == null) { + beatProgress = 0f; + } + int chevronSize = (int) (chevronBaseSize - (isHovered ? 6f : 3f) * beatProgress); + + // calc button sizes + AnimationEquation anim; + if (isHovered) { + anim = AnimationEquation.OUT_ELASTIC; + } else { + anim = AnimationEquation.IN_ELASTIC; + } + float progress = anim.calc((float) animationTime / ANIMATION_TIME); + float firstSize = firstButtonWidth + (firstButtonWidth - slopeImageSlopeWidth * 2) * progress; + float secondSize = secondButtonSize + secondButtonSize * 0.25f * progress; + realButtonWidth = (int) (firstSize + secondSize); + + // right part + g.setColor(COLOR_PINK); + g.fillRect(0, buttonYpos, firstSize + secondSize - slopeImageSlopeWidth, slopeImageSize); + slopeImage.draw(firstSize + secondSize - slopeImageSize, buttonYpos, COLOR_PINK); + + // left part + Color hoverColor = new Color(0f, 0f, 0f); + hoverColor.r = COLOR_PINK.r + (COLOR_DARKPINK.r - COLOR_PINK.r) * progress; + hoverColor.g = COLOR_PINK.g + (COLOR_DARKPINK.g - COLOR_PINK.g) * progress; + hoverColor.b = COLOR_PINK.b + (COLOR_DARKPINK.b - COLOR_PINK.b) * progress; + g.setColor(hoverColor); + g.fillRect(0, buttonYpos, firstSize - slopeImageSlopeWidth, slopeImageSize); + slopeImage.draw(firstSize - slopeImageSize, buttonYpos, hoverColor); + + // chevron + GameImage.MENU_BACK_CHEVRON.getImage().getScaledCopy(chevronSize, chevronSize).drawCentered((firstSize - slopeImageSlopeWidth / 2) / 2, buttonYpos + paddingY * 1.5f); + + // text + float textY = buttonYpos + paddingY - textOffset; + float textX = firstSize + (secondSize - paddingX * 2 - textWidth) / 2; + Fonts.MEDIUM.drawString(textX, textY + 1, "back", Color.black); + Fonts.MEDIUM.drawString(textX, textY, "back", Color.white); + } + + /** + * Processes a hover action depending on whether or not the cursor + * is hovering over the button. + * @param delta the delta interval + * @param cx the x coordinate + * @param cy the y coordinate + */ + public void hoverUpdate(int delta, int cx, int cy) { + if (backButton != null) { + backButton.hoverUpdate(delta, cx, cy); + return; + } + boolean wasHovered = isHovered; + isHovered = buttonYpos - paddingY < cy && cx < realButtonWidth; + if (isHovered) { + if (!wasHovered) { + animationTime = 0; + } + animationTime += delta; + if (animationTime > ANIMATION_TIME) { + animationTime = ANIMATION_TIME; + } + } else { + if (wasHovered) { + animationTime = ANIMATION_TIME; + } + animationTime -= delta; + if (animationTime < 0) { + animationTime = 0; + } + } + } + + /** + * Returns true if the coordinates are within the button bounds. + * @param cx the x coordinate + * @param cy the y coordinate + */ + public boolean contains(float cx, float cy) { + if (backButton != null) { + return backButton.contains(cx, cy); + } + return buttonYpos - paddingY < cy && cx < realButtonWidth; + } + + /** + * Resets the hover fields for the button. + */ + public void resetHover() { + if (backButton != null) { + backButton.resetHover(); + return; + } + isHovered = false; + animationTime = 0; + } + +} diff --git a/src/yugecin/opsudance/ui/OptionsOverlay.java b/src/yugecin/opsudance/ui/OptionsOverlay.java index f6e97989..f177c93f 100644 --- a/src/yugecin/opsudance/ui/OptionsOverlay.java +++ b/src/yugecin/opsudance/ui/OptionsOverlay.java @@ -259,7 +259,7 @@ public class OptionsOverlay extends OverlayOpsuState { g.clearClip(); // UI - UI.getBackButton().draw(); + UI.getBackButton().draw(g); // tooltip renderTooltip(g);