diff --git a/README.md b/README.md index 2827413d..a6960d73 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ folder if placed in the `SongPacks` directory. ### First Run The `Music Offset` value will likely need to be adjusted when playing for the first time, or whenever hit objects are out of sync with the music. This and -other game options can be accessed by clicking the wrench icon in the song menu. +other game options can be accessed by clicking the "Other Options" button in +the song menu. ## Building opsu! is distributed as a Maven project. diff --git a/res/options.png b/res/options.png deleted file mode 100644 index 74c38e45..00000000 Binary files a/res/options.png and /dev/null differ diff --git a/res/search.png b/res/search.png deleted file mode 100644 index 2382d419..00000000 Binary files a/res/search.png and /dev/null differ diff --git a/res/selection-mods-over.png b/res/selection-mods-over.png new file mode 100644 index 00000000..890daa54 Binary files /dev/null and b/res/selection-mods-over.png differ diff --git a/res/selection-mods.png b/res/selection-mods.png new file mode 100644 index 00000000..53078a2e Binary files /dev/null and b/res/selection-mods.png differ diff --git a/res/selection-options-over.png b/res/selection-options-over.png new file mode 100644 index 00000000..ab5f95e5 Binary files /dev/null and b/res/selection-options-over.png differ diff --git a/res/selection-options.png b/res/selection-options.png new file mode 100644 index 00000000..f94051fe Binary files /dev/null and b/res/selection-options.png differ diff --git a/res/selection-random-over.png b/res/selection-random-over.png new file mode 100644 index 00000000..9daf6072 Binary files /dev/null and b/res/selection-random-over.png differ diff --git a/res/selection-random.png b/res/selection-random.png new file mode 100644 index 00000000..8afd55d3 Binary files /dev/null and b/res/selection-random.png differ diff --git a/res/selection-selectoptions-over.png b/res/selection-selectoptions-over.png new file mode 100644 index 00000000..27af71de Binary files /dev/null and b/res/selection-selectoptions-over.png differ diff --git a/res/selection-selectoptions.png b/res/selection-selectoptions.png new file mode 100644 index 00000000..ab60811f Binary files /dev/null and b/res/selection-selectoptions.png differ diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index ec6a3ff3..cdb4e999 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -368,6 +368,54 @@ public enum GameImage { }, // Non-Game Components + SELECTION_MODS ("selection-mods", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_MODS_OVERLAY ("selection-mods-over", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_RANDOM ("selection-random", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_RANDOM_OVERLAY ("selection-random-over", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_OPTIONS ("selection-options", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_OPTIONS_OVERLAY ("selection-options-over", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_OTHER_OPTIONS ("selection-selectoptions", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_OTHER_OPTIONS_OVERLAY ("selection-selectoptions-over", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, VOLUME ("volume-bg", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { @@ -383,7 +431,8 @@ public enum GameImage { MENU_BUTTON_BG ("menu-button-background", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy(w / 2, h / SongMenu.MAX_SONG_BUTTONS); + // TODO: scale these properly (messy due to non-cropped images) + return img.getScaledCopy(w / 2, (int) (h * 0.95f) / SongMenu.MAX_SONG_BUTTONS); } }, MENU_TAB ("selection-tab", "png", false, false) { @@ -392,18 +441,6 @@ public enum GameImage { return img.getScaledCopy((h * 0.033f) / img.getHeight()); } }, - MENU_SEARCH ("search", "png", false, false) { - @Override - protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy(Utils.FONT_BOLD.getLineHeight() * 2f / img.getHeight()); - } - }, - MENU_OPTIONS ("options", "png", false, false) { - @Override - protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy(Utils.FONT_BOLD.getLineHeight() * 2f / img.getHeight()); - } - }, MENU_MUSICNOTE ("music-note", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { diff --git a/src/itdelatrisu/opsu/OsuGroupNode.java b/src/itdelatrisu/opsu/OsuGroupNode.java index 69e8eb39..3cefe22e 100644 --- a/src/itdelatrisu/opsu/OsuGroupNode.java +++ b/src/itdelatrisu/opsu/OsuGroupNode.java @@ -54,38 +54,54 @@ public class OsuGroupNode { * Draws the button. * @param x the x coordinate * @param y the y coordinate + * @param headerY the header end y coordinate (for cropping) + * @param footerY the footer start y coordinate (for cropping) * @param grade the highest grade, if any * @param focus true if this is the focused node */ - public void draw(float x, float y, Grade grade, boolean focus) { + public void draw(float x, float y, float headerY, float footerY, Grade grade, boolean focus) { boolean expanded = (osuFileIndex > -1); - float xOffset = 0f; OsuFile osu; - Color textColor = Color.lightGray; Image bg = GameImage.MENU_BUTTON_BG.getImage(); + Color bgColor; + Color textColor = Color.lightGray; + // draw song button background if (expanded) { // expanded - xOffset = bg.getWidth() / 10f; + x -= bg.getWidth() / 10f; if (focus) { - bg.draw(x - xOffset, y, Color.white); + bgColor = Color.white; textColor = Color.white; } else - bg.draw(x - xOffset, y, Utils.COLOR_BLUE_BUTTON); + bgColor = Utils.COLOR_BLUE_BUTTON; osu = osuFiles.get(osuFileIndex); } else { - bg.draw(x, y, Utils.COLOR_ORANGE_BUTTON); + bgColor = Utils.COLOR_ORANGE_BUTTON; osu = osuFiles.get(0); } + // crop image if necessary + if (y < headerY) { + int cropHeight = (int) (headerY - y); + Image bgCropped = bg.getSubImage(0, cropHeight, bg.getWidth(), bg.getHeight() - cropHeight); + bgCropped.draw(x, headerY, bgColor); + } else if (y + bg.getHeight() > footerY) { + int cropHeight = (int) (footerY - y); + Image bgCropped = bg.getSubImage(0, 0, bg.getWidth(), cropHeight); + bgCropped.draw(x, y, bgColor); + } else + bg.draw(x, y, bgColor); - float cx = x + (bg.getWidth() * 0.05f) - xOffset; + float cx = x + (bg.getWidth() * 0.05f); float cy = y + (bg.getHeight() * 0.2f) - 3; + // draw grade if (grade != Grade.NULL) { Image gradeImg = grade.getMenuImage(); gradeImg.drawCentered(cx - bg.getWidth() * 0.01f + gradeImg.getWidth() / 2f, y + bg.getHeight() / 2.2f); cx += gradeImg.getWidth(); } + // draw text Utils.FONT_MEDIUM.drawString(cx, cy, osu.getTitle(), textColor); Utils.FONT_DEFAULT.drawString(cx, cy + Utils.FONT_MEDIUM.getLineHeight() - 4, String.format("%s // %s", osu.getArtist(), osu.creator), textColor); diff --git a/src/itdelatrisu/opsu/ScoreData.java b/src/itdelatrisu/opsu/ScoreData.java index 5ad238a1..a08ea718 100644 --- a/src/itdelatrisu/opsu/ScoreData.java +++ b/src/itdelatrisu/opsu/ScoreData.java @@ -78,13 +78,13 @@ public class ScoreData implements Comparable { /** * Initializes the base coordinates for drawing. - * @param width the container width - * @param height the container height + * @param containerWidth the container width + * @param topY the top y coordinate */ - public static void init(int width, int height) { - baseX = width * 0.01f; - baseY = height * 0.16f; - buttonWidth = width * 0.4f; + public static void init(int containerWidth, float topY) { + baseX = containerWidth * 0.01f; + baseY = topY; + buttonWidth = containerWidth * 0.4f; float gradeHeight = GameImage.MENU_BUTTON_BG.getImage().getHeight() * 0.45f; buttonHeight = Math.max(gradeHeight, Utils.FONT_DEFAULT.getLineHeight() * 3.03f); buttonOffset = buttonHeight + gradeHeight / 10f; diff --git a/src/itdelatrisu/opsu/SongSort.java b/src/itdelatrisu/opsu/SongSort.java index f2a8016f..5b28ea90 100644 --- a/src/itdelatrisu/opsu/SongSort.java +++ b/src/itdelatrisu/opsu/SongSort.java @@ -145,21 +145,21 @@ public enum SongSort { /** * Initializes the sort tab. - * @param width the container width - * @param height the container height + * @param containerWidth the container width + * @param bottomY the bottom y coordinate */ - public void init(int width, int height) { + public void init(int containerWidth, float bottomY) { Image tab = GameImage.MENU_TAB.getImage(); int tabWidth = tab.getWidth(); - float buttonX = width / 2f; - float tabOffset = (width - buttonX - tabWidth) / (SIZE - 1); + float buttonX = containerWidth / 2f; + float tabOffset = (containerWidth - buttonX - tabWidth) / (SIZE - 1); if (tabOffset > tabWidth) { // prevent tabs from being spaced out tabOffset = tabWidth; - buttonX = (width * 0.99f) - (tabWidth * SIZE); + buttonX = (containerWidth * 0.99f) - (tabWidth * SIZE); } this.tab = new MenuButton(tab, (buttonX + (tabWidth / 2f)) + (id * tabOffset), - (height * 0.15f) - (tab.getHeight() / 2f) - 2f + bottomY - (tab.getHeight() / 2f) ); } diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 39270d73..2d126c37 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -82,7 +82,8 @@ public class Utils { COLOR_GREEN = new Color(137, 201, 79), COLOR_LIGHT_ORANGE = new Color(255,192,128), COLOR_LIGHT_GREEN = new Color(128,255,128), - COLOR_LIGHT_BLUE = new Color(128,128,255); + COLOR_LIGHT_BLUE = new Color(128,128,255), + COLOR_GREEN_SEARCH = new Color(173, 255, 47); /** The default map colors, used when a map does not provide custom colors. */ public static final Color[] DEFAULT_COMBO = { @@ -215,16 +216,9 @@ public class Utils { for (GameMod mod : GameMod.values()) mod.init(width, height); - // initialize sorts - for (SongSort sort : SongSort.values()) - sort.init(width, height); - // initialize hit objects OsuHitObject.init(width, height); - // initialize score data buttons - ScoreData.init(width, height); - // initialize download nodes DownloadNode.init(width, height); diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index be4d93be..d616125c 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -83,6 +83,12 @@ public class SongMenu extends BasicGameState { /** Maximum x offset of song buttons for mouse hover, in pixels. */ private static final float MAX_HOVER_OFFSET = 30f; + /** Time, in milliseconds, for the search bar to fade in or out. */ + private static final int SEARCH_TRANSITION_TIME = 250; + + /** Line width of the header/footer divider. */ + private static final int DIVIDER_LINE_WIDTH = 4; + /** Song node class representing an OsuGroupNode and file index. */ private static class SongNode { /** Song node. */ @@ -136,8 +142,8 @@ public class SongMenu extends BasicGameState { /** Current index of hovered song button. */ private int hoverIndex = -1; - /** The options button (to enter the "Game Options" menu). */ - private MenuButton optionsButton; + /** The selection buttons. */ + private MenuButton selectModsButton, selectRandomButton, selectMapOptionsButton, selectOptionsButton; /** The search textfield. */ private TextField search; @@ -146,10 +152,10 @@ public class SongMenu extends BasicGameState { * Delay timer, in milliseconds, before running another search. * This is overridden by character entry (reset) and 'esc' (immediate search). */ - private int searchTimer; + private int searchTimer = 0; /** Information text to display based on the search query. */ - private String searchResultString; + private String searchResultString = null; /** Loader animation. */ private Animation loader; @@ -184,6 +190,12 @@ public class SongMenu extends BasicGameState { /** Current start score (topmost score entry). */ private int startScore = 0; + /** Header and footer end and start y coordinates, respectively. */ + private float headerY, footerY; + + /** Time, in milliseconds, for fading the search bar. */ + private int searchTransitionTimer = SEARCH_TRANSITION_TIME; + // game-related variables private GameContainer container; private StateBasedGame game; @@ -204,26 +216,35 @@ public class SongMenu extends BasicGameState { int width = container.getWidth(); int height = container.getHeight(); + // header/footer coordinates + headerY = height * 0.0075f + GameImage.MENU_MUSICNOTE.getImage().getHeight() + + Utils.FONT_BOLD.getLineHeight() + Utils.FONT_DEFAULT.getLineHeight() + + Utils.FONT_SMALL.getLineHeight(); + footerY = height - GameImage.SELECTION_MODS.getImage().getHeight(); + + // initialize sorts + for (SongSort sort : SongSort.values()) + sort.init(width, headerY - SongMenu.DIVIDER_LINE_WIDTH / 2); + + // initialize score data buttons + ScoreData.init(width, headerY + height * 0.01f); + // song button background & graphics context Image menuBackground = GameImage.MENU_BUTTON_BG.getImage(); // song button coordinates buttonX = width * 0.6f; - buttonY = height * 0.16f; + buttonY = headerY; buttonWidth = menuBackground.getWidth(); buttonHeight = menuBackground.getHeight(); - buttonOffset = (height * 0.8f) / MAX_SONG_BUTTONS; + buttonOffset = (footerY - headerY - DIVIDER_LINE_WIDTH) / MAX_SONG_BUTTONS; // search - searchTimer = 0; - searchResultString = "Type to search!"; - Image searchIcon = GameImage.MENU_SEARCH.getImage(); - Image tab = GameImage.MENU_TAB.getImage(); + int textFieldX = (int) (width * 0.7125f + Utils.FONT_BOLD.getWidth("Search: ")); + int textFieldY = (int) (headerY + Utils.FONT_BOLD.getLineHeight() / 2); search = new TextField( - container, Utils.FONT_DEFAULT, - (int) buttonX + (tab.getWidth() / 2) + searchIcon.getWidth(), - (int) ((height * 0.15f) - (tab.getHeight() * 2.5f)), - (int) (buttonWidth / 2), Utils.FONT_DEFAULT.getLineHeight() + container, Utils.FONT_BOLD, textFieldX, textFieldY, + (int) (width * 0.99f) - textFieldX, Utils.FONT_BOLD.getLineHeight() ); search.setBackgroundColor(Color.transparent); search.setBorderColor(Color.transparent); @@ -231,10 +252,22 @@ public class SongMenu extends BasicGameState { search.setConsumeEvents(false); search.setMaxLength(60); - // options button - Image optionsIcon = GameImage.MENU_OPTIONS.getImage(); - optionsButton = new MenuButton(optionsIcon, search.getX() - (optionsIcon.getWidth() * 1.5f), search.getY()); - optionsButton.setHoverExpand(1.75f); + // selection buttons + float selectX = GameImage.MENU_BACK.getImage().getWidth() * 1.75f; + float selectY = height - GameImage.SELECTION_MODS.getImage().getHeight() / 2f; + float selectOffset = GameImage.SELECTION_MODS.getImage().getWidth() * 1.05f; + selectModsButton = new MenuButton(GameImage.SELECTION_MODS_OVERLAY.getImage(), + selectX, selectY); + selectRandomButton = new MenuButton(GameImage.SELECTION_RANDOM_OVERLAY.getImage(), + selectX + selectOffset, selectY); + selectMapOptionsButton = new MenuButton(GameImage.SELECTION_OPTIONS_OVERLAY.getImage(), + selectX + selectOffset * 2f, selectY); + selectOptionsButton = new MenuButton(GameImage.SELECTION_OTHER_OPTIONS_OVERLAY.getImage(), + selectX + selectOffset * 3f, selectY); + selectModsButton.setHoverFade(0f); + selectRandomButton.setHoverFade(0f); + selectMapOptionsButton.setHoverFade(0f); + selectOptionsButton.setHoverFade(0f); // loader int loaderDim = GameImage.MENU_MUSICNOTE.getImage().getWidth(); @@ -258,19 +291,19 @@ public class SongMenu extends BasicGameState { GameImage.PLAYFIELD.getImage().draw(); } - // header setup - float lowerBound = height * 0.15f; - g.setColor(Utils.COLOR_BLACK_ALPHA); - g.fillRect(0, 0, width, lowerBound); + // top/bottom bars + g.setColor(Color.black); + g.fillRect(0, 0, width, headerY); + g.fillRect(0, footerY, width, height - footerY); g.setColor(Utils.COLOR_BLUE_DIVIDER); - g.setLineWidth(2f); - g.drawLine(0, lowerBound, width, lowerBound); + g.setLineWidth(DIVIDER_LINE_WIDTH); + g.drawLine(0, headerY, width, headerY); + g.drawLine(0, footerY - DIVIDER_LINE_WIDTH / 2, width, footerY - DIVIDER_LINE_WIDTH / 2); g.resetLineWidth(); // header if (focusNode != null) { float marginX = width * 0.005f, marginY = height * 0.005f; - Image musicNote = GameImage.MENU_MUSICNOTE.getImage(); if (MusicController.isTrackLoading()) loader.draw(marginX, marginY); @@ -282,14 +315,14 @@ public class SongMenu extends BasicGameState { if (songInfo == null) songInfo = focusNode.getInfo(); marginX += 5; - Utils.FONT_LARGE.drawString(marginX + iconWidth, marginY, songInfo[0], Color.white); - Utils.FONT_DEFAULT.drawString(marginX + iconWidth, marginY + Utils.FONT_LARGE.getLineHeight() * 0.75f, songInfo[1], Color.white); - float headerY = marginY + iconHeight; - Utils.FONT_BOLD.drawString(marginX, headerY, songInfo[2], Color.white); - headerY += Utils.FONT_BOLD.getLineHeight() - 6; - Utils.FONT_DEFAULT.drawString(marginX, headerY, songInfo[3], Color.white); - headerY += Utils.FONT_DEFAULT.getLineHeight() - 4; - Utils.FONT_SMALL.drawString(marginX, headerY, songInfo[4], Color.white); + Utils.FONT_LARGE.drawString(marginX + iconWidth * 1.05f, marginY, songInfo[0], Color.white); + Utils.FONT_DEFAULT.drawString(marginX + iconWidth * 1.05f, marginY + Utils.FONT_LARGE.getLineHeight() * 0.75f, songInfo[1], Color.white); + float headerTextY = marginY + iconHeight; + Utils.FONT_BOLD.drawString(marginX, headerTextY, songInfo[2], Color.white); + headerTextY += Utils.FONT_BOLD.getLineHeight() - 6; + Utils.FONT_DEFAULT.drawString(marginX, headerTextY, songInfo[3], Color.white); + headerTextY += Utils.FONT_DEFAULT.getLineHeight() - 4; + Utils.FONT_SMALL.drawString(marginX, headerTextY, songInfo[4], Color.white); } // song buttons @@ -300,6 +333,7 @@ public class SongMenu extends BasicGameState { ScoreData[] scores = getScoreDataForNode(node, false); node.draw( buttonX - offset, buttonY + (i*buttonOffset), + headerY + DIVIDER_LINE_WIDTH / 2, footerY - DIVIDER_LINE_WIDTH, (scores == null) ? Grade.NULL : scores[0].getGrade(), (node == focusNode) ); @@ -324,8 +358,16 @@ public class SongMenu extends BasicGameState { ScoreData.drawScrollbar(g, startScore, focusScores.length); } - // options button - optionsButton.draw(); + // selection buttons + // TODO +// GameImage.SELECTION_MODS.getImage().drawCentered(selectModsButton.getX(), selectModsButton.getY()); +// selectModsButton.draw(); + GameImage.SELECTION_RANDOM.getImage().drawCentered(selectRandomButton.getX(), selectRandomButton.getY()); + selectRandomButton.draw(); + GameImage.SELECTION_OPTIONS.getImage().drawCentered(selectMapOptionsButton.getX(), selectMapOptionsButton.getY()); + selectMapOptionsButton.draw(); + GameImage.SELECTION_OTHER_OPTIONS.getImage().drawCentered(selectOptionsButton.getX(), selectOptionsButton.getY()); + selectOptionsButton.draw(); // sorting tabs SongSort currentSort = SongSort.getSort(); @@ -343,15 +385,37 @@ public class SongMenu extends BasicGameState { currentSort.draw(true, false); // search - Image searchIcon = GameImage.MENU_SEARCH.getImage(); - Utils.FONT_BOLD.drawString( - search.getX(), search.getY() - Utils.FONT_BOLD.getLineHeight(), - searchResultString, Color.white - ); - searchIcon.draw(search.getX() - searchIcon.getWidth(), - search.getY() - Utils.FONT_DEFAULT.getLineHeight()); - g.setColor(Color.white); - search.render(container, g); + boolean searchEmpty = search.getText().isEmpty(); + int searchX = search.getX(), searchY = search.getY(); + float searchBaseX = width * 0.7f; + float searchTextX = width * 0.7125f; + float searchRectHeight = Utils.FONT_BOLD.getLineHeight() * 2; + float searchExtraHeight = Utils.FONT_DEFAULT.getLineHeight() * 0.7f; + float searchProgress = (searchTransitionTimer < SEARCH_TRANSITION_TIME) ? + ((float) searchTransitionTimer / SEARCH_TRANSITION_TIME) : 1f; + float oldAlpha = Utils.COLOR_BLACK_ALPHA.a; + if (searchEmpty) { + searchRectHeight += (1f - searchProgress) * searchExtraHeight; + Utils.COLOR_BLACK_ALPHA.a = 0.5f - searchProgress * 0.3f; + } else { + searchRectHeight += searchProgress * searchExtraHeight; + Utils.COLOR_BLACK_ALPHA.a = 0.2f + searchProgress * 0.3f; + } + g.setColor(Utils.COLOR_BLACK_ALPHA); + g.fillRect(searchBaseX, headerY + DIVIDER_LINE_WIDTH / 2, width - searchBaseX, searchRectHeight); + Utils.COLOR_BLACK_ALPHA.a = oldAlpha; + Utils.FONT_BOLD.drawString(searchTextX, searchY, "Search:", Utils.COLOR_GREEN_SEARCH); + if (searchEmpty) + Utils.FONT_BOLD.drawString(searchX, searchY, "Type to search!", Color.white); + else { + g.setColor(Color.white); + // TODO: why is this needed to correctly position the TextField? + search.setLocation(searchX - 3, searchY - 1); + search.render(container, g); + search.setLocation(searchX, searchY); + Utils.FONT_DEFAULT.drawString(searchTextX, searchY + Utils.FONT_BOLD.getLineHeight(), + (searchResultString == null) ? "Searching..." : searchResultString, Color.white); + } // scroll bar if (focusNode != null) { @@ -364,7 +428,7 @@ public class SongMenu extends BasicGameState { else if (startNode.index == focusNode.index) startIndex += startNode.osuFileIndex; Utils.drawScrollbar(g, startIndex, totalNodes, MAX_SONG_BUTTONS, - width, height * 0.16f, 0, buttonHeight, buttonOffset, + width, headerY + DIVIDER_LINE_WIDTH / 2, 0, buttonOffset - DIVIDER_LINE_WIDTH * 1.5f, buttonOffset, Utils.COLOR_BLACK_ALPHA, Color.white, true); } } @@ -394,7 +458,10 @@ public class SongMenu extends BasicGameState { Utils.updateVolumeDisplay(delta); int mouseX = input.getMouseX(), mouseY = input.getMouseY(); Utils.getBackButton().hoverUpdate(delta, mouseX, mouseY); - optionsButton.hoverUpdate(delta, mouseX, mouseY); + selectModsButton.hoverUpdate(delta, mouseX, mouseY); + selectRandomButton.hoverUpdate(delta, mouseX, mouseY); + selectMapOptionsButton.hoverUpdate(delta, mouseX, mouseY); + selectOptionsButton.hoverUpdate(delta, mouseX, mouseY); // beatmap menu timer if (beatmapMenuTimer > -1) { @@ -425,7 +492,7 @@ public class SongMenu extends BasicGameState { // empty search if (search.getText().isEmpty()) - searchResultString = "Type to search!"; + searchResultString = null; // search produced new list: re-initialize it startNode = focusNode = null; @@ -450,39 +517,45 @@ public class SongMenu extends BasicGameState { searchResultString = "No matches found. Hit 'esc' to reset."; } } + if (searchTransitionTimer < SEARCH_TRANSITION_TIME) { + searchTransitionTimer += delta; + if (searchTransitionTimer > SEARCH_TRANSITION_TIME) + searchTransitionTimer = SEARCH_TRANSITION_TIME; + } // slide buttons int height = container.getHeight(); - float targetY = height * 0.16f; - if (buttonY > targetY) { + if (buttonY > headerY) { buttonY -= height * delta / 20000f; - if (buttonY < targetY) - buttonY = targetY; - } else if (buttonY < targetY) { + if (buttonY < headerY) + buttonY = headerY; + } else if (buttonY < headerY) { buttonY += height * delta / 20000f; - if (buttonY > targetY) - buttonY = targetY; + if (buttonY > headerY) + buttonY = headerY; } // mouse hover - OsuGroupNode node = startNode; boolean isHover = false; - for (int i = 0; i < MAX_SONG_BUTTONS && node != null; i++, node = node.next) { - float cx = (node.index == OsuGroupList.get().getExpandedIndex()) ? buttonX * 0.9f : buttonX; - if ((mouseX > cx && mouseX < cx + buttonWidth) && - (mouseY > buttonY + (i * buttonOffset) && mouseY < buttonY + (i * buttonOffset) + buttonHeight)) { - if (i == hoverIndex) { - if (hoverOffset < MAX_HOVER_OFFSET) { - hoverOffset += delta / 3f; - if (hoverOffset > MAX_HOVER_OFFSET) - hoverOffset = MAX_HOVER_OFFSET; + if (mouseY > headerY && mouseY < footerY) { + OsuGroupNode node = startNode; + for (int i = 0; i < MAX_SONG_BUTTONS && node != null; i++, node = node.next) { + float cx = (node.index == OsuGroupList.get().getExpandedIndex()) ? buttonX * 0.9f : buttonX; + if ((mouseX > cx && mouseX < cx + buttonWidth) && + (mouseY > buttonY + (i * buttonOffset) && mouseY < buttonY + (i * buttonOffset) + buttonHeight)) { + if (i == hoverIndex) { + if (hoverOffset < MAX_HOVER_OFFSET) { + hoverOffset += delta / 3f; + if (hoverOffset > MAX_HOVER_OFFSET) + hoverOffset = MAX_HOVER_OFFSET; + } + } else { + hoverIndex = i; + hoverOffset = 0f; } - } else { - hoverIndex = i; - hoverOffset = 0f; + isHover = true; + break; } - isHover = true; - break; } } if (!isHover) { @@ -512,8 +585,17 @@ public class SongMenu extends BasicGameState { return; } - // options - if (optionsButton.contains(x, y)) { + // selection buttons + if (selectModsButton.contains(x, y)) { + this.keyPressed(Input.KEY_F1, '\0'); + return; + } else if (selectRandomButton.contains(x, y)) { + this.keyPressed(Input.KEY_F2, '\0'); + return; + } else if (selectMapOptionsButton.contains(x, y)) { + this.keyPressed(Input.KEY_F3, '\0'); + return; + } else if (selectOptionsButton.contains(x, y)) { SoundController.playSound(SoundEffect.MENUHIT); game.enterState(Opsu.STATE_OPTIONSMENU, new EmptyTransition(), new FadeInTransition(Color.black)); return; @@ -539,46 +621,48 @@ public class SongMenu extends BasicGameState { } // song buttons - int expandedIndex = OsuGroupList.get().getExpandedIndex(); - OsuGroupNode node = startNode; - for (int i = 0; i < MAX_SONG_BUTTONS && node != null; i++, node = node.next) { - // is button at this index clicked? - float cx = (node.index == expandedIndex) ? buttonX * 0.9f : buttonX; - if ((x > cx && x < cx + buttonWidth) && - (y > buttonY + (i * buttonOffset) && y < buttonY + (i * buttonOffset) + buttonHeight)) { - float oldHoverOffset = hoverOffset; - int oldHoverIndex = hoverIndex; - - // clicked node is already expanded - if (node.index == expandedIndex) { - if (node.osuFileIndex == focusNode.osuFileIndex) { - // if already focused, load the beatmap - if (button != Input.MOUSE_RIGHT_BUTTON) - startGame(); - else + if (y > headerY && y < footerY) { + int expandedIndex = OsuGroupList.get().getExpandedIndex(); + OsuGroupNode node = startNode; + for (int i = 0; i < MAX_SONG_BUTTONS && node != null; i++, node = node.next) { + // is button at this index clicked? + float cx = (node.index == expandedIndex) ? buttonX * 0.9f : buttonX; + if ((x > cx && x < cx + buttonWidth) && + (y > buttonY + (i * buttonOffset) && y < buttonY + (i * buttonOffset) + buttonHeight)) { + float oldHoverOffset = hoverOffset; + int oldHoverIndex = hoverIndex; + + // clicked node is already expanded + if (node.index == expandedIndex) { + if (node.osuFileIndex == focusNode.osuFileIndex) { + // if already focused, load the beatmap + if (button != Input.MOUSE_RIGHT_BUTTON) + startGame(); + else + SoundController.playSound(SoundEffect.MENUCLICK); + } else { + // focus the node SoundController.playSound(SoundEffect.MENUCLICK); - } else { - // focus the node - SoundController.playSound(SoundEffect.MENUCLICK); - setFocus(node, 0, false); + setFocus(node, 0, false); + } } + + // clicked node is a new group + else { + SoundController.playSound(SoundEffect.MENUCLICK); + setFocus(node, -1, false); + } + + // restore hover data + hoverOffset = oldHoverOffset; + hoverIndex = oldHoverIndex; + + // open beatmap menu + if (button == Input.MOUSE_RIGHT_BUTTON) + beatmapMenuTimer = (node.index == expandedIndex) ? BEATMAP_MENU_DELAY * 4 / 5 : 0; + + return; } - - // clicked node is a new group - else { - SoundController.playSound(SoundEffect.MENUCLICK); - setFocus(node, -1, false); - } - - // restore hover data - hoverOffset = oldHoverOffset; - hoverIndex = oldHoverIndex; - - // open beatmap menu - if (button == Input.MOUSE_RIGHT_BUTTON) - beatmapMenuTimer = (node.index == expandedIndex) ? BEATMAP_MENU_DELAY * 4 / 5 : 0; - - return; } } @@ -621,6 +705,7 @@ public class SongMenu extends BasicGameState { // clear search text search.setText(""); searchTimer = SEARCH_DELAY; + searchTransitionTimer = 0; } else { // return to main menu SoundController.playSound(SoundEffect.MENUBACK); @@ -629,12 +714,14 @@ public class SongMenu extends BasicGameState { } break; case Input.KEY_F1: - SoundController.playSound(SoundEffect.MENUHIT); - game.enterState(Opsu.STATE_OPTIONSMENU, new EmptyTransition(), new FadeInTransition(Color.black)); + // TODO: mods menu +// SoundController.playSound(SoundEffect.MENUHIT); +// game.enterState(); break; case Input.KEY_F2: if (focusNode == null) break; + SoundController.playSound(SoundEffect.MENUHIT); if (input.isKeyDown(Input.KEY_RSHIFT) || input.isKeyDown(Input.KEY_LSHIFT)) { // shift key: previous random track SongNode prev; @@ -718,8 +805,16 @@ public class SongMenu extends BasicGameState { break; default: // wait for user to finish typing - if (Character.isLetterOrDigit(c) || key == Input.KEY_BACK) + // TODO: accept all characters (current conditions are from TextField class) + if ((c > 31 && c < 127) || key == Input.KEY_BACK) { searchTimer = 0; + int textLength = search.getText().length(); + if (key == Input.KEY_BACK) { + if (textLength == 0) + searchTransitionTimer = 0; + } else if (textLength == 1) + searchTransitionTimer = 0; + } break; } } @@ -782,11 +877,15 @@ public class SongMenu extends BasicGameState { throws SlickException { Display.setTitle(game.getTitle()); Utils.getBackButton().resetHover(); - optionsButton.resetHover(); + selectModsButton.resetHover(); + selectRandomButton.resetHover(); + selectMapOptionsButton.resetHover(); + selectOptionsButton.resetHover(); hoverOffset = 0f; hoverIndex = -1; startScore = 0; beatmapMenuTimer = -1; + searchTransitionTimer = SEARCH_TRANSITION_TIME; // reset song stack randomStack = new Stack(); @@ -920,7 +1019,8 @@ public class SongMenu extends BasicGameState { hoverIndex = -1; search.setText(""); searchTimer = SEARCH_DELAY; - searchResultString = "Type to search!"; + searchTransitionTimer = SEARCH_TRANSITION_TIME; + searchResultString = null; // reload songs in new thread reloadThread = new Thread() { @@ -977,16 +1077,16 @@ public class SongMenu extends BasicGameState { if (n < 0 && startNode.prev != null) { startNode = startNode.prev; buttonY += buttonOffset / 4; - if (buttonY > height * 0.18f) - buttonY = height * 0.18f; + if (buttonY > headerY + height * 0.02f) + buttonY = headerY + height * 0.02f; n++; shifted = true; } else if (n > 0 && startNode.next != null && OsuGroupList.get().getNode(startNode, MAX_SONG_BUTTONS) != null) { startNode = startNode.next; buttonY -= buttonOffset / 4; - if (buttonY < height * 0.14f) - buttonY = height * 0.14f; + if (buttonY < headerY - height * 0.02f) + buttonY = headerY - height * 0.02f; n--; shifted = true; } else