From 33f5df030c60b313bf336b633a6c750bcd26f8e7 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Tue, 23 Dec 2014 23:41:37 -0500 Subject: [PATCH] Expand menu buttons when hovering. Added hovering capabilities to GUIMenuButton. The max scale and direction to expand the image can be modified per object. Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/GUIMenuButton.java | 128 +++++++++++++++--- src/itdelatrisu/opsu/GameMod.java | 16 +++ src/itdelatrisu/opsu/Utils.java | 1 + .../opsu/states/GamePauseMenu.java | 7 + src/itdelatrisu/opsu/states/GameRanking.java | 5 + src/itdelatrisu/opsu/states/MainMenu.java | 24 ++++ src/itdelatrisu/opsu/states/Options.java | 7 + src/itdelatrisu/opsu/states/SongMenu.java | 6 + 8 files changed, 174 insertions(+), 20 deletions(-) diff --git a/src/itdelatrisu/opsu/GUIMenuButton.java b/src/itdelatrisu/opsu/GUIMenuButton.java index 66e89f13..a64a5c5c 100644 --- a/src/itdelatrisu/opsu/GUIMenuButton.java +++ b/src/itdelatrisu/opsu/GUIMenuButton.java @@ -25,6 +25,7 @@ import org.newdawn.slick.Image; /** * A convenience class for menu buttons. * Consists of an image or animation and coordinates. + * Multi-part images and animations currently do not support hover updates. */ public class GUIMenuButton { /** @@ -48,20 +49,37 @@ public class GUIMenuButton { private float x, y; /** - * The x and y radius of the button. + * The x and y radius of the button (scaled). */ private float xRadius, yRadius; + /** + * The current and max scale of the button (for hovering). + */ + private float scale, hoverScale = 1.25f; + + /** + * The scaled expansion direction for the botton (for hovering). + */ + private Expand dir = Expand.CENTER; + + /** + * Scaled expansion directions (for hovering). + */ + public enum Expand { + CENTER, UP_RIGHT, UP_LEFT, DOWN_RIGHT, DOWN_LEFT; + } + /** * Creates a new button from an Image. */ public GUIMenuButton(Image img, float x, float y) { this.img = img; - this.x = x; - this.y = y; - - xRadius = img.getWidth() / 2f; - yRadius = img.getHeight() / 2f; + this.x = x; + this.y = y; + this.xRadius = img.getWidth() / 2f; + this.yRadius = img.getHeight() / 2f; + this.scale = 1f; } /** @@ -72,11 +90,11 @@ public class GUIMenuButton { this.img = imgCenter; this.imgL = imgLeft; this.imgR = imgRight; - this.x = x; - this.y = y; - - xRadius = (img.getWidth() + imgL.getWidth() + imgR.getWidth()) / 2f; - yRadius = img.getHeight() / 2f; + this.x = x; + this.y = y; + this.xRadius = (img.getWidth() + imgL.getWidth() + imgR.getWidth()) / 2f; + this.yRadius = img.getHeight() / 2f; + this.scale = 1f; } /** @@ -84,11 +102,11 @@ public class GUIMenuButton { */ public GUIMenuButton(Animation anim, float x, float y) { this.anim = anim; - this.x = x; - this.y = y; - - xRadius = anim.getWidth() / 2f; - yRadius = anim.getHeight() / 2f; + this.x = x; + this.y = y; + this.xRadius = anim.getWidth() / 2f; + this.yRadius = anim.getHeight() / 2f; + this.scale = 1f; } /** @@ -110,9 +128,11 @@ public class GUIMenuButton { */ public void draw() { if (img != null) { - if (imgL == null) - img.draw(x - xRadius, y - yRadius); - else { + if (imgL == null) { + Image imgScaled = img.getScaledCopy(scale); + imgScaled.setAlpha(img.getAlpha()); + imgScaled.draw(x - xRadius, y - yRadius); + } else { img.draw(x - xRadius + imgL.getWidth(), y - yRadius); imgL.draw(x - xRadius, y - yRadius); imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius); @@ -128,7 +148,7 @@ public class GUIMenuButton { public void draw(Color filter) { if (img != null) { if (imgL == null) - img.draw(x - xRadius, y - yRadius, filter); + img.getScaledCopy(scale).draw(x - xRadius, y - yRadius, filter); else { img.draw(x - xRadius + imgL.getWidth(), y - yRadius, filter); imgL.draw(x - xRadius, y - yRadius, filter); @@ -140,9 +160,77 @@ public class GUIMenuButton { /** * 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) { return ((cx > x - xRadius && cx < x + xRadius) && (cy > y - yRadius && cy < y + yRadius)); } + + /** + * Sets the current button scale (for hovering). + * @param scale the new scale (default 1.0f) + */ + public void setScale(float scale) { + this.scale = scale; + setHoverRadius(); + } + + /** + * Sets the maximum scale factor for the button (for hovering). + * @param scale the maximum scale factor (default 1.25f) + */ + public void setHoverScale(float scale) { + this.hoverScale = scale; + } + + /** + * Sets the expansion direction when hovering over the button. + * @param dir the direction + */ + public void setHoverDir(Expand dir) { + this.dir = dir; + } + + /** + * Updates the scale of the button 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, float cx, float cy) { + boolean isHover = contains(cx, cy); + if (isHover && scale < hoverScale) { + scale += (hoverScale - 1f) * delta / 100f; + if (scale > hoverScale) + scale = hoverScale; + setHoverRadius(); + } else if (!isHover && scale > 1f) { + scale -= (hoverScale - 1f) * delta / 100f; + if (scale < 1f) + scale = 1f; + setHoverRadius(); + } + } + + /** + * Set x and y radius of the button based on current scale factor + * and expansion direction. + */ + private void setHoverRadius() { + int xOffset = 0, yOffset = 0; + if (dir != Expand.CENTER) { + // offset by difference between normal/scaled image dimensions + xOffset = (int) ((scale - 1f) * img.getWidth()); + yOffset = (int) ((scale - 1f) * img.getHeight()); + if (dir == Expand.DOWN_RIGHT || dir == Expand.UP_RIGHT) + xOffset *= -1; // flip x for right + if (dir == Expand.DOWN_LEFT || dir == Expand.DOWN_RIGHT) + yOffset *= -1; // flip y for down + } + this.xRadius = ((img.getWidth() * scale) + xOffset) / 2f; + this.yRadius = ((img.getHeight() * scale) + yOffset) / 2f; + } } \ No newline at end of file diff --git a/src/itdelatrisu/opsu/GameMod.java b/src/itdelatrisu/opsu/GameMod.java index 78ccaf10..c3177060 100644 --- a/src/itdelatrisu/opsu/GameMod.java +++ b/src/itdelatrisu/opsu/GameMod.java @@ -107,6 +107,7 @@ public enum GameMod { // create button img.setAlpha(0.5f); this.button = new GUIMenuButton(img, x + (offsetX * id), y); + this.button.setHoverScale(1.15f); } catch (SlickException e) { Log.error(String.format("Failed to initialize game mod '%s'.", this), e); } @@ -177,4 +178,19 @@ public enum GameMod { * @return true if within bounds */ public boolean contains(float x, float y) { return button.contains(x, y); } + + /** + * Sets the current button scale (for hovering). + * @param scale the new scale (default 1.0f) + */ + public void setScale(float scale) { button.setScale(scale); } + + /** + * Updates the scale of the button depending on whether or not the cursor + * is hovering over the button. + * @param delta the delta interval + * @param x the x coordinate + * @param y the y coordinate + */ + public void hoverUpdate(int delta, float x, float y) { button.hoverUpdate(delta, x, y); } } diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 247f5073..b7660c3c 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -197,6 +197,7 @@ public class Utils { backButton = new GUIMenuButton(back, back.getWidth() / 2f, height - (back.getHeight() / 2f)); + backButton.setHoverDir(GUIMenuButton.Expand.UP_RIGHT); // set default game images for (GameImage img : GameImage.values()) diff --git a/src/itdelatrisu/opsu/states/GamePauseMenu.java b/src/itdelatrisu/opsu/states/GamePauseMenu.java index ad77292c..34ab925a 100644 --- a/src/itdelatrisu/opsu/states/GamePauseMenu.java +++ b/src/itdelatrisu/opsu/states/GamePauseMenu.java @@ -101,6 +101,10 @@ public class GamePauseMenu extends BasicGameState { public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException { Utils.updateCursor(delta); + int mouseX = input.getMouseX(), mouseY = input.getMouseY(); + continueButton.hoverUpdate(delta, mouseX, mouseY); + retryButton.hoverUpdate(delta, mouseX, mouseY); + backButton.hoverUpdate(delta, mouseX, mouseY); } @Override @@ -165,6 +169,9 @@ public class GamePauseMenu extends BasicGameState { SoundController.playSound(SoundController.SOUND_FAIL); } else MusicController.pause(); + continueButton.setScale(1f); + retryButton.setScale(1f); + backButton.setScale(1f); } /** diff --git a/src/itdelatrisu/opsu/states/GameRanking.java b/src/itdelatrisu/opsu/states/GameRanking.java index ce6fce9b..677d3fbc 100644 --- a/src/itdelatrisu/opsu/states/GameRanking.java +++ b/src/itdelatrisu/opsu/states/GameRanking.java @@ -61,6 +61,7 @@ public class GameRanking extends BasicGameState { // game-related variables private StateBasedGame game; private int state; + private Input input; public GameRanking(int state) { this.state = state; @@ -70,6 +71,7 @@ public class GameRanking extends BasicGameState { public void init(GameContainer container, StateBasedGame game) throws SlickException { this.game = game; + this.input = container.getInput(); score = Game.getGameScore(); @@ -138,6 +140,8 @@ public class GameRanking extends BasicGameState { public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException { Utils.updateCursor(delta); + int mouseX = input.getMouseX(), mouseY = input.getMouseY(); + Utils.getBackButton().hoverUpdate(delta, mouseX, mouseY); } @Override @@ -185,6 +189,7 @@ public class GameRanking extends BasicGameState { public void enter(GameContainer container, StateBasedGame game) throws SlickException { Display.setTitle(game.getTitle()); + Utils.getBackButton().setScale(1f); SoundController.playSound(SoundController.SOUND_APPLAUSE); } } diff --git a/src/itdelatrisu/opsu/states/MainMenu.java b/src/itdelatrisu/opsu/states/MainMenu.java index 43abdc62..ccf153c6 100644 --- a/src/itdelatrisu/opsu/states/MainMenu.java +++ b/src/itdelatrisu/opsu/states/MainMenu.java @@ -103,6 +103,7 @@ public class MainMenu extends BasicGameState { // game-related variables private GameContainer container; private StateBasedGame game; + private Input input; private int state; public MainMenu(int state) { @@ -114,6 +115,7 @@ public class MainMenu extends BasicGameState { throws SlickException { this.container = container; this.game = game; + this.input = container.getInput(); osuStartTime = System.currentTimeMillis(); previous = new Stack(); @@ -126,6 +128,7 @@ public class MainMenu extends BasicGameState { float buttonScale = (height / 1.2f) / logoImg.getHeight(); Image logoImgScaled = logoImg.getScaledCopy(buttonScale); logo = new GUIMenuButton(logoImgScaled, width / 2f, height / 2f); + logo.setHoverScale(1.05f); Image playImg = new Image("menu-play.png"); Image exitImg = new Image("menu-exit.png"); @@ -138,6 +141,8 @@ public class MainMenu extends BasicGameState { exitButton = new GUIMenuButton(exitImg.getScaledCopy(buttonScale), width * 0.75f - exitOffset, (height / 2) + (exitImg.getHeight() / 2f) ); + playButton.setHoverScale(1.05f); + exitButton.setHoverScale(1.05f); // initialize music buttons int musicWidth = 48; @@ -146,6 +151,10 @@ public class MainMenu extends BasicGameState { musicPause = new GUIMenuButton(new Image("music-pause.png"), width - (2 * musicWidth), musicHeight); musicNext = new GUIMenuButton(new Image("music-next.png"), width - musicWidth, musicHeight); musicPrevious = new GUIMenuButton(new Image("music-previous.png"), width - (3 * musicWidth), musicHeight); + musicPlay.setHoverScale(1.5f); + musicPause.setHoverScale(1.5f); + musicNext.setHoverScale(1.5f); + musicPrevious.setHoverScale(1.5f); // menu background try { @@ -226,6 +235,14 @@ public class MainMenu extends BasicGameState { public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException { Utils.updateCursor(delta); + int mouseX = input.getMouseX(), mouseY = input.getMouseY(); + logo.hoverUpdate(delta, mouseX, mouseY); + playButton.hoverUpdate(delta, mouseX, mouseY); + exitButton.hoverUpdate(delta, mouseX, mouseY); + musicPlay.hoverUpdate(delta, mouseX, mouseY); + musicPause.hoverUpdate(delta, mouseX, mouseY); + musicNext.hoverUpdate(delta, mouseX, mouseY); + musicPrevious.hoverUpdate(delta, mouseX, mouseY); // fade in background if (bgAlpha < 0.9f) { @@ -280,6 +297,13 @@ public class MainMenu extends BasicGameState { logoClicked = false; logoTimer = 0; logo.setX(container.getWidth() / 2); + logo.setScale(1f); + playButton.setScale(1f); + exitButton.setScale(1f); + musicPlay.setScale(1f); + musicPause.setScale(1f); + musicNext.setScale(1f); + musicPrevious.setScale(1f); } @Override diff --git a/src/itdelatrisu/opsu/states/Options.java b/src/itdelatrisu/opsu/states/Options.java index 4fa40c33..4d75a8d3 100644 --- a/src/itdelatrisu/opsu/states/Options.java +++ b/src/itdelatrisu/opsu/states/Options.java @@ -525,6 +525,10 @@ public class Options extends BasicGameState { public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException { Utils.updateCursor(delta); + int mouseX = input.getMouseX(), mouseY = input.getMouseY(); + Utils.getBackButton().hoverUpdate(delta, mouseX, mouseY); + for (GameMod mod : GameMod.values()) + mod.hoverUpdate(delta, mouseX, mouseY); } @Override @@ -782,6 +786,9 @@ public class Options extends BasicGameState { public void enter(GameContainer container, StateBasedGame game) throws SlickException { currentTab = TAB_DISPLAY; + Utils.getBackButton().setScale(1f); + for (GameMod mod : GameMod.values()) + mod.setScale(1f); } /** diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index 8cf5c84a..e7ed47b0 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -180,6 +180,7 @@ public class SongMenu extends BasicGameState { // options button Image optionsIcon = new Image("options.png").getScaledCopy(iconScale); optionsButton = new GUIMenuButton(optionsIcon, search.getX() - (optionsIcon.getWidth() * 1.5f), search.getY()); + optionsButton.setHoverScale(1.75f); // music note int musicNoteDim = (int) (Utils.FONT_LARGE.getLineHeight() * 0.75f + Utils.FONT_DEFAULT.getLineHeight()); @@ -281,6 +282,9 @@ public class SongMenu extends BasicGameState { public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException { Utils.updateCursor(delta); + int mouseX = input.getMouseX(), mouseY = input.getMouseY(); + Utils.getBackButton().hoverUpdate(delta, mouseX, mouseY); + optionsButton.hoverUpdate(delta, mouseX, mouseY); // search search.setFocus(true); @@ -501,6 +505,8 @@ public class SongMenu extends BasicGameState { public void enter(GameContainer container, StateBasedGame game) throws SlickException { Display.setTitle(game.getTitle()); + Utils.getBackButton().setScale(1f); + optionsButton.setScale(1f); // stop playing the theme song if (MusicController.isThemePlaying() && focusNode != null)