diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index 46b9299a..3a9fa21d 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -262,6 +262,9 @@ public class SongMenu extends BasicGameState { /** The star stream. */ private StarStream starStream; + /** Whether the menu is currently scrolling to the focus node (blocks other actions). */ + private boolean isScrollingToFocus = false; + // game-related variables private GameContainer container; private StateBasedGame game; @@ -679,15 +682,25 @@ public class SongMenu extends BasicGameState { searchTransitionTimer = SEARCH_TRANSITION_TIME; } - // Scores + // scores if (focusScores != null) { startScorePos.setMinMax(0, (focusScores.length - MAX_SCORE_BUTTONS) * ScoreData.getButtonOffset()); startScorePos.update(delta); } - // mouse hover + // scrolling songScrolling.update(delta); + if (isScrollingToFocus) { + float distanceDiff = Math.abs(songScrolling.getPosition() - songScrolling.getTargetPosition()); + if (distanceDiff <= buttonOffset / 8f) { // close enough, stop blocking input + songScrolling.scrollToPosition(songScrolling.getTargetPosition()); + songScrolling.setSpeedMultiplier(1f); + isScrollingToFocus = false; + } + } updateDrawnSongPosition(); + + // mouse hover boolean isHover = false; if (mouseY > headerY && mouseY < footerY) { BeatmapSetNode node = startNode; @@ -733,12 +746,18 @@ public class SongMenu extends BasicGameState { @Override public void mousePressed(int button, int x, int y) { + if (isScrollingToFocus) + return; + songScrolling.pressed(); startScorePos.pressed(); } @Override public void mouseReleased(int button, int x, int y) { + if (isScrollingToFocus) + return; + songScrolling.released(); startScorePos.released(); } @@ -750,7 +769,7 @@ public class SongMenu extends BasicGameState { return; // block input - if (reloadThread != null || beatmapMenuTimer > -1) + if (isInputBlocked()) return; // back @@ -869,7 +888,7 @@ public class SongMenu extends BasicGameState { @Override public void keyPressed(int key, char c) { // block input - if ((reloadThread != null && !(key == Input.KEY_ESCAPE || key == Input.KEY_F12)) || beatmapMenuTimer > -1) + if ((reloadThread != null && !(key == Input.KEY_ESCAPE || key == Input.KEY_F12)) || beatmapMenuTimer > -1 || isScrollingToFocus) return; switch (key) { @@ -1022,7 +1041,7 @@ public class SongMenu extends BasicGameState { @Override public void mouseDragged(int oldx, int oldy, int newx, int newy) { // block input - if (reloadThread != null || beatmapMenuTimer > -1) + if (isInputBlocked()) return; int diff = newy - oldy; @@ -1056,7 +1075,7 @@ public class SongMenu extends BasicGameState { } // block input - if (reloadThread != null || beatmapMenuTimer > -1) + if (isInputBlocked()) return; int shift = (newValue < 0) ? 1 : -1; @@ -1082,7 +1101,9 @@ public class SongMenu extends BasicGameState { selectOptionsButton.resetHover(); hoverOffset.setTime(0); hoverIndex = null; + isScrollingToFocus = false; songScrolling.released(); + songScrolling.setSpeedMultiplier(1f); startScorePos.setPosition(0); beatmapMenuTimer = -1; searchTransitionTimer = SEARCH_TRANSITION_TIME; @@ -1333,10 +1354,9 @@ public class SongMenu extends BasicGameState { startScorePos.setPosition(0); if (oldFocus != null && oldFocus.getBeatmapSet() != node.getBeatmapSet()) { - // Close previous node + // close previous node if (node.index > oldFocus.index) { float offset = (oldFocus.getBeatmapSet().size() - 1) * buttonOffset; - //updateSongPos(-offset); songScrolling.addOffset(-offset); } @@ -1354,8 +1374,15 @@ public class SongMenu extends BasicGameState { } // change the focus node - if (changeStartNode || (startNode.index == 0 && startNode.beatmapIndex == -1 && startNode.prev == null)) - songScrolling.setPosition((node.index - 1) * buttonOffset); + if (changeStartNode || (startNode.index == 0 && startNode.beatmapIndex == -1 && startNode.prev == null)) { + if (startNode == null || game.getCurrentStateID() != Opsu.STATE_SONGMENU) + songScrolling.setPosition((node.index - 1) * buttonOffset); + else { + isScrollingToFocus = true; + songScrolling.setSpeedMultiplier(2f); + songScrolling.released(); + } + } updateDrawnSongPosition(); @@ -1519,6 +1546,14 @@ public class SongMenu extends BasicGameState { reloadThread.start(); } + /** + * Returns whether a delayed/animated event is currently blocking user input. + * @return true if blocking input + */ + private boolean isInputBlocked() { + return (reloadThread != null || beatmapMenuTimer > -1 || isScrollingToFocus); + } + /** * Calculates all star ratings for a beatmap set. * @param beatmapSet the set of beatmaps diff --git a/src/itdelatrisu/opsu/ui/KineticScrolling.java b/src/itdelatrisu/opsu/ui/KineticScrolling.java index 062f6509..baaaaa35 100644 --- a/src/itdelatrisu/opsu/ui/KineticScrolling.java +++ b/src/itdelatrisu/opsu/ui/KineticScrolling.java @@ -28,7 +28,7 @@ public class KineticScrolling { /** The moving averaging constant. */ private static final float AVG_CONST = 0.2f, ONE_MINUS_AVG_CONST = 1 - AVG_CONST; - /** The constant used to determine how fast the target position will be reach. */ + /** The constant used to determine how fast the target position will be reached. */ private static final int TIME_CONST = 200; /** The constant used to determine how much of the velocity will be used to launch to the target. */ @@ -58,12 +58,21 @@ public class KineticScrolling { /** The moving average of the velocity. */ private float avgVelocity; + /** The speed multiplier (divides {@link #TIME_CONST}). */ + private float speedMultiplier = 1f; + /** - * Returns the current Position. + * Returns the current position. * @return the position */ public float getPosition() { return position; } + /** + * Returns the target position. + * @return the target position + */ + public float getTargetPosition() { return target; } + /** * Updates the scrolling. * @param delta the elapsed time since the last update @@ -71,7 +80,7 @@ public class KineticScrolling { public void update(float delta) { if (!pressed) { totalDelta += delta; - position = target + (float) (-amplitude * Math.exp(-totalDelta / TIME_CONST)); + position = target + (float) (-amplitude * Math.exp(-totalDelta / (TIME_CONST / speedMultiplier))); } else { avgVelocity = (ONE_MINUS_AVG_CONST * avgVelocity + AVG_CONST * (deltaPosition * 1000f / delta)); @@ -165,4 +174,15 @@ public class KineticScrolling { this.min = min; this.max = max; } + + /** + * Sets the multiplier for how fast the target position will be reached. + * @param multiplier the speed multiplier (e.g. 1f = normal speed, 2f = reaches in half the time) + * @throws IllegalArgumentException if the multiplier is negative or zero + */ + public void setSpeedMultiplier(float multiplier) { + if (multiplier <= 0f) + throw new IllegalArgumentException("Speed multiplier must be above zero."); + this.speedMultiplier = multiplier; + } }