diff --git a/src/itdelatrisu/opsu/states/OptionsMenu.java b/src/itdelatrisu/opsu/states/OptionsMenu.java index f4810c4c..8c02d96c 100644 --- a/src/itdelatrisu/opsu/states/OptionsMenu.java +++ b/src/itdelatrisu/opsu/states/OptionsMenu.java @@ -22,39 +22,32 @@ import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Options.GameOption; -import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; -import itdelatrisu.opsu.ui.Colors; -import itdelatrisu.opsu.ui.Fonts; -import itdelatrisu.opsu.ui.MenuButton; import itdelatrisu.opsu.ui.UI; -import java.util.*; - -import org.newdawn.slick.Color; import org.newdawn.slick.GameContainer; import org.newdawn.slick.Graphics; -import org.newdawn.slick.Image; import org.newdawn.slick.Input; import org.newdawn.slick.SlickException; import org.newdawn.slick.state.BasicGameState; import org.newdawn.slick.state.StateBasedGame; import org.newdawn.slick.state.transition.FadeInTransition; import org.newdawn.slick.state.transition.EmptyTransition; -import yugecin.opsudance.ui.ItemList; -import yugecin.opsudance.ui.RWM; +import yugecin.opsudance.ui.OptionsOverlay; +import yugecin.opsudance.ui.OptionsOverlay.OptionTab; /** * "Game Options" state. *

* Players are able to view and change various game settings in this state. */ -public class OptionsMenu extends BasicGameState { +public class OptionsMenu extends BasicGameState implements OptionsOverlay.Parent { + /** Option tabs. */ - private enum OptionTab { - DISPLAY ("Display", new GameOption[] { + private static final OptionTab[] options = new OptionsOverlay.OptionTab[]{ + new OptionTab("Display", new GameOption[]{ GameOption.SCREEN_RESOLUTION, GameOption.FULLSCREEN, GameOption.ALLOW_LARGER_RESOLUTIONS, @@ -67,7 +60,7 @@ public class OptionsMenu extends BasicGameState { GameOption.LOAD_HD_IMAGES, GameOption.LOAD_VERBOSE }), - MUSIC ("Music", new GameOption[] { + new OptionTab("Music", new GameOption[] { GameOption.MASTER_VOLUME, GameOption.MUSIC_VOLUME, GameOption.EFFECT_VOLUME, @@ -77,7 +70,7 @@ public class OptionsMenu extends BasicGameState { GameOption.DISABLE_SOUNDS, GameOption.ENABLE_THEME_SONG }), - GAMEPLAY ("Gameplay", new GameOption[] { + new OptionTab("Gameplay", new GameOption[] { GameOption.BACKGROUND_DIM, GameOption.FORCE_DEFAULT_PLAYFIELD, GameOption.IGNORE_BEATMAP_SKINS, @@ -94,7 +87,7 @@ public class OptionsMenu extends BasicGameState { GameOption.MAP_END_DELAY, GameOption.EPILEPSY_WARNING, }), - INPUT ("Input", new GameOption[] { + new OptionTab("Input", new GameOption[] { GameOption.KEY_LEFT, GameOption.KEY_RIGHT, GameOption.DISABLE_MOUSE_WHEEL, @@ -103,7 +96,7 @@ public class OptionsMenu extends BasicGameState { GameOption.NEW_CURSOR, GameOption.DISABLE_CURSOR }), - CUSTOM ("Custom", new GameOption[] { + new OptionTab("Custom", new GameOption[] { GameOption.FIXED_CS, GameOption.FIXED_HP, GameOption.FIXED_AR, @@ -113,7 +106,7 @@ public class OptionsMenu extends BasicGameState { GameOption.DISABLE_UPDATER, GameOption.ENABLE_WATCH_SERVICE }), - DANCE ("Dance", new GameOption[] { + new OptionTab("Dance", new GameOption[] { GameOption.DANCE_MOVER, GameOption.DANCE_QUAD_BEZ_AGGRESSIVENESS, GameOption.DANCE_QUAD_BEZ_SLIDER_AGGRESSIVENESS_FACTOR, @@ -130,7 +123,7 @@ public class OptionsMenu extends BasicGameState { GameOption.DANCE_CIRLCE_IN_LAZY_SLIDERS, GameOption.DANCE_MIRROR, }), - DANCEDISP ("Dance display", new GameOption[] { + new OptionTab("Dance display", new GameOption[] { GameOption.DANCE_DRAW_APPROACH, GameOption.DANCE_OBJECT_COLOR_OVERRIDE, GameOption.DANCE_OBJECT_COLOR_OVERRIDE_MIRRORED, @@ -146,94 +139,31 @@ public class OptionsMenu extends BasicGameState { GameOption.DANCE_ENABLE_SB, GameOption.DANCE_HIDE_WATERMARK, }), - PIPPI ("Pippi", new GameOption[] { + new OptionTab ("Pippi", new GameOption[] { GameOption.PIPPI_ENABLE, GameOption.PIPPI_RADIUS_PERCENT, GameOption.PIPPI_ANGLE_INC_MUL, GameOption.PIPPI_ANGLE_INC_MUL_SLIDER, GameOption.PIPPI_SLIDER_FOLLOW_EXPAND, GameOption.PIPPI_PREVENT_WOBBLY_STREAMS, - }); + }) + }; - /** Total number of tabs. */ - public static final int SIZE = values().length; - - /** Array of OptionTab objects in reverse order. */ - public static final OptionTab[] VALUES_REVERSED; - static { - VALUES_REVERSED = values(); - Collections.reverse(Arrays.asList(VALUES_REVERSED)); - } - - /** Enum values. */ - private static OptionTab[] values = values(); - - /** Tab name. */ - private final String name; - - /** Options array. */ - public final GameOption[] options; - - /** Associated tab button. */ - public MenuButton button; - - /** - * Constructor. - * @param name the tab name - * @param options the options to display under the tab - */ - OptionTab(String name, GameOption[] options) { - this.name = name; - this.options = options; - } - - /** - * Returns the tab name. - */ - public String getName() { return name; } - - /** - * Returns the next tab. - */ - public OptionTab next() { return values[(this.ordinal() + 1) % values.length]; } - - /** - * Returns the previous tab. - */ - public OptionTab prev() { return values[(this.ordinal() + (SIZE - 1)) % values.length]; } - } - - /** Current tab. */ - private OptionTab currentTab; - - /** Key entry states. */ - private boolean keyEntryLeft = false, keyEntryRight = false; - - /** Game option coordinate modifiers (for drawing). */ - private int textY, offsetY; - - // game-related variables private GameContainer container; private StateBasedGame game; private Input input; private Graphics g; private final int state; - private GameOption selectedOption; - private final ItemList list; - private final RWM rwm; + private OptionsOverlay optionsOverlay; public OptionsMenu(int state) { this.state = state; - list = new ItemList(); - rwm = new RWM(); } @Override public void init(GameContainer container, StateBasedGame game) throws SlickException { - list.init(container); - rwm.init(container); this.container = container; this.game = game; @@ -243,319 +173,53 @@ public class OptionsMenu extends BasicGameState { int width = container.getWidth(); int height = container.getHeight(); - // option tabs - Image tabImage = GameImage.MENU_TAB.getImage(); - float tabX = width * 0.032f + (tabImage.getWidth() / 3); - float tabY = Fonts.XLARGE.getLineHeight() + Fonts.DEFAULT.getLineHeight() + - height * 0.015f - (tabImage.getHeight() / 2f); - int tabOffset = Math.min(tabImage.getWidth(), width / OptionTab.SIZE); - for (OptionTab tab : OptionTab.values()) - tab.button = new MenuButton(tabImage, tabX + (tab.ordinal() * tabOffset), tabY); - - // game option coordinate modifiers - textY = (int) (tabY + tabImage.getHeight()); - //int backHeight = GameImage.MENU_BACK.getAnimation(1).getHeight(); - offsetY = (int) ((Fonts.MEDIUM.getLineHeight()) * 1.1f); + optionsOverlay = new OptionsOverlay(this, options, 5, width, height); } @Override - public void render(GameContainer container, StateBasedGame game, Graphics g) - throws SlickException { - int width = container.getWidth(); - int height = container.getHeight(); - int mouseX = input.getMouseX(), mouseY = input.getMouseY(); - + public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException { // background GameImage.OPTIONS_BG.getImage().draw(); - // title - float marginX = width * 0.015f, marginY = height * 0.01f; - Fonts.XLARGE.drawString(marginX, marginY, "Options", Color.white); - Fonts.DEFAULT.drawString(marginX + Fonts.XLARGE.getWidth("Options") * 1.2f, marginY + Fonts.XLARGE.getLineHeight() * 0.9f - Fonts.DEFAULT.getLineHeight(), - "Change the way opsu! behaves", Color.white); - - // game options - g.setLineWidth(1f); - GameOption hoverOption = (keyEntryLeft) ? GameOption.KEY_LEFT : - (keyEntryRight) ? GameOption.KEY_RIGHT : - getOptionAt(mouseY); - if (selectedOption != null) { - hoverOption = selectedOption; - } - for (int i = 0, j = 0; i < currentTab.options.length; i++) { - GameOption option = currentTab.options[i]; - if (!option.showCondition()) { - continue; - } - drawOption(option, j++, hoverOption == option); - } - - // option tabs - OptionTab hoverTab = null; - for (OptionTab tab : OptionTab.values()) { - if (tab.button.contains(mouseX, mouseY)) { - hoverTab = tab; - break; - } - } - for (OptionTab tab : OptionTab.VALUES_REVERSED) { - if (tab != currentTab) - UI.drawTab(tab.button.getX(), tab.button.getY(), - tab.getName(), false, tab == hoverTab && !list.isVisible()); - } - UI.drawTab(currentTab.button.getX(), currentTab.button.getY(), - currentTab.getName(), true, false); - g.setColor(Color.white); - g.setLineWidth(2f); - float lineY = OptionTab.DISPLAY.button.getY() + (GameImage.MENU_TAB.getImage().getHeight() / 2f); - g.drawLine(0, lineY, width, lineY); - g.resetLineWidth(); - - if (list.isVisible()) { - list.render(container, game, g); - } - if (rwm.isVisible()) { - rwm.render(container, game, g); - } - - UI.getBackButton().draw(); - - // key entry state - if (keyEntryLeft || keyEntryRight) { - g.setColor(Colors.BLACK_ALPHA); - g.fillRect(0, 0, width, height); - g.setColor(Color.white); - String prompt = (keyEntryLeft) ? - "Please press the new left-click key." : - "Please press the new right-click key."; - Fonts.LARGE.drawString( - (width / 2) - (Fonts.LARGE.getWidth(prompt) / 2), - (height / 2) - Fonts.LARGE.getLineHeight(), prompt - ); - } - - UI.draw(g); - - // current hovering option - if (hoverOption != null && !list.isVisible() && !rwm.isVisible()) { - String optionDescription = hoverOption.getDescription(); - float textWidth = Fonts.SMALL.getWidth(optionDescription); - Color.black.a = 0.7f; - g.setColor(Color.black); - g.fillRect(mouseX + 10, mouseY + 10, 10 + textWidth, 10 + Fonts.SMALL.getLineHeight()); - Fonts.SMALL.drawString(mouseX + 15, mouseY + 15, optionDescription, Color.white); - Color.black.a = 1f; - } + int mouseX = input.getMouseX(), mouseY = input.getMouseY(); + optionsOverlay.render(g, mouseX, mouseY); } @Override - public void update(GameContainer container, StateBasedGame game, int delta) - throws SlickException { - rwm.update(delta); + public void update(GameContainer container, StateBasedGame game, int delta) throws SlickException { UI.update(delta); MusicController.loopTrackIfEnded(false); - int mouseX = input.getMouseX(), mouseY = input.getMouseY(); - UI.getBackButton().hoverUpdate(delta, mouseX, mouseY); + optionsOverlay.update(delta, input.getMouseX(), input.getMouseY()); } @Override - public int getID() { return state; } + public int getID() { + return state; + } @Override public void mouseReleased(int button, int x, int y) { - selectedOption = null; - if (list.isVisible()) { - list.mouseReleased(button, x, y); - } - if (rwm.isVisible()) { - rwm.mouseReleased(button, x, y); - } + optionsOverlay.mouseReleased(button, x, y); } @Override public void mousePressed(int button, int x, int y) { - if (list.isVisible()) { - list.mousePressed(button, x, y); - return; - } - if (rwm.isVisible()) { - return; - } - - // key entry state - if (keyEntryLeft || keyEntryRight) { - keyEntryLeft = keyEntryRight = false; - return; - } - - // check mouse button - if (button == Input.MOUSE_MIDDLE_BUTTON) - return; - - // back - if (UI.getBackButton().contains(x, y)) { - SoundController.playSound(SoundEffect.MENUBACK); - game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition()); - return; - } - - // option tabs - for (OptionTab tab : OptionTab.values()) { - if (tab.button.contains(x, y)) { - if (tab != currentTab) { - currentTab = tab; - SoundController.playSound(SoundEffect.MENUCLICK); - } - return; - } - } - - // options (click only) - final GameOption option = getOptionAt(y); - if (option != null) { - selectedOption = option; - Object[] listItems = option.getListItems(); - if (listItems == null) { - if (option.showRWM()) { - rwm.show(); - } else { - option.click(container); - } - } else { - list.setItems(listItems); - list.setClickListener(new Observer() { - @Override - public void update(Observable o, Object arg) { - option.clickListItem((int) arg); - } - }); - list.show(); - } - } - - // special key entry states - if (option == GameOption.KEY_LEFT) { - keyEntryLeft = true; - keyEntryRight = false; - } else if (option == GameOption.KEY_RIGHT) { - keyEntryLeft = false; - keyEntryRight = true; - } - - // ctrl+click to reset slider options - if (selectedOption != null && selectedOption.isDragOption() && (input.isKeyDown(Input.KEY_LCONTROL) || input.isKeyDown(Input.KEY_RCONTROL))) { - selectedOption.setValue(selectedOption.getDefaultVal() - 1); - // trigger update - selectedOption.drag(container, 1); - } + optionsOverlay.mousePressed(button, x, y); } @Override public void mouseDragged(int oldx, int oldy, int newx, int newy) { - if (list.isVisible()) { - list.mouseDragged(oldx, oldy, newx, newy); - return; - } - if (rwm.isVisible()) { - return; - } - - // key entry state - if (keyEntryLeft || keyEntryRight) - return; - - if (selectedOption == null || !selectedOption.isDragOption()) { - return; - } - - // check control keys (reset to default value on ctrl+click) - if (input.isKeyDown(Input.KEY_LCONTROL) || input.isKeyDown(Input.KEY_RCONTROL)) { - selectedOption.setValue(selectedOption.getDefaultVal() - 1); - // trigger update - selectedOption.drag(container, 1); - return; - } - - // check mouse button (right click scrolls faster) - int multiplier; - if (input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON)) - multiplier = 4; - else if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) - multiplier = 1; - else - return; - - // get direction - int diff = newx - oldx; - if (diff == 0) - return; - diff = ((diff > 0) ? 1 : -1) * multiplier; - - // options (drag only) - selectedOption.drag(container, diff); + optionsOverlay.mouseDragged(oldx, oldy, newx, newy); } @Override public void mouseWheelMoved(int newValue) { - if (list.isVisible()) { - list.mouseWheelMoved(newValue); - } - if (input.isKeyDown(Input.KEY_LALT) || input.isKeyDown(Input.KEY_RALT)) - UI.changeVolume((newValue < 0) ? -1 : 1); + optionsOverlay.mouseWheelMoved(newValue); } @Override public void keyPressed(int key, char c) { - if (list.isVisible()) { - list.keyPressed(key, c); - return; - } - if (rwm.isVisible()) { - rwm.keyPressed(key, c); - return; - } - - // key entry state - if (keyEntryLeft || keyEntryRight) { - if (keyEntryLeft) - Options.setGameKeyLeft(key); - else - Options.setGameKeyRight(key); - keyEntryLeft = keyEntryRight = false; - return; - } - - switch (key) { - case Input.KEY_ESCAPE: - SoundController.playSound(SoundEffect.MENUBACK); - game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition()); - break; - case Input.KEY_F5: - // restart application - if ((input.isKeyDown(Input.KEY_RCONTROL) || input.isKeyDown(Input.KEY_LCONTROL)) && - (input.isKeyDown(Input.KEY_RSHIFT) || input.isKeyDown(Input.KEY_LSHIFT))) { - container.setForceExit(false); - container.exit(); - } - break; - case Input.KEY_F7: - Options.setNextFPS(container); - break; - case Input.KEY_F10: - Options.toggleMouseDisabled(); - break; - case Input.KEY_F12: - Utils.takeScreenShot(); - break; - case Input.KEY_TAB: - // change tabs - if (input.isKeyDown(Input.KEY_LSHIFT) || input.isKeyDown(Input.KEY_RSHIFT)) - currentTab = currentTab.prev(); - else - currentTab = currentTab.next(); - SoundController.playSound(SoundEffect.MENUCLICK); - break; - } + optionsOverlay.keyPressed(key, c); } /** @@ -569,7 +233,6 @@ public class OptionsMenu extends BasicGameState { public void enter(GameContainer container, StateBasedGame game) throws SlickException { UI.enter(); - currentTab = OptionTab.DANCE; restartOptions = "" + Options.getResolutionIdx() + Options.isFullscreen() + Options.allowLargeResolutions() + Options.getSkinName(); } @@ -578,56 +241,19 @@ public class OptionsMenu extends BasicGameState { if (!("" + Options.getResolutionIdx() + Options.isFullscreen() + Options.allowLargeResolutions() + Options.getSkinName()).equals(restartOptions)) { container.setForceExit(false); container.exit(); + return; } + SoundController.playSound(SoundEffect.MENUBACK); } - /** - * Draws a game option. - * @param option the option - * @param pos the position to draw at - * @param focus whether the option is currently focused - */ - private void drawOption(GameOption option, int pos, boolean focus) { - int width = container.getWidth(); - int textHeight = Fonts.MEDIUM.getLineHeight(); - float y = textY + (pos * offsetY); - Color color = (focus) ? Color.cyan : Color.white; - - Fonts.MEDIUM.drawString(width / 30, y, option.getName(), color); - Fonts.MEDIUM.drawString(width / 2, y, option.getValueString(), color); - g.setColor(Colors.WHITE_ALPHA); - if (option.isDragOption()) { - float availableWidth = width / 2 - width / 30; - g.fillRect(width / 2, y + textHeight, 20, 10); - g.fillRect(width / 2 + availableWidth - 20, y + textHeight, 20, 10); - availableWidth -= 40 + 100; - float optionPercent = (float) (option.getIntegerValue() - option.getMinValue()) / (option.getMaxValue() - option.getMinValue()); - g.fillRect(width / 2 + 20 + optionPercent * availableWidth, y + textHeight, 100, 10); - } - g.drawLine(0, y + textHeight, width, y + textHeight); + @Override + public void onLeave() { + game.enterState(Opsu.STATE_SONGMENU, new EmptyTransition(), new FadeInTransition()); } - /** - * Returns the option at the given y coordinate. - * @param y the y coordinate - * @return the option, or GameOption.NULL if no such option exists - */ - private GameOption getOptionAt(int y) { - if (y < textY) - return null; + @Override + public void onSaveOption(GameOption option) { - int index = (y - textY) / offsetY; - if (index >= currentTab.options.length) - return null; - - for (GameOption option : currentTab.options) { - if (option.showCondition()) { - index--; - } - if (index < 0) { - return option; - } - } - return null; } + } diff --git a/src/itdelatrisu/opsu/ui/Colors.java b/src/itdelatrisu/opsu/ui/Colors.java index 7f00603a..c43d0893 100644 --- a/src/itdelatrisu/opsu/ui/Colors.java +++ b/src/itdelatrisu/opsu/ui/Colors.java @@ -26,6 +26,7 @@ import org.newdawn.slick.Color; public class Colors { public static final Color BLACK_ALPHA = new Color(0, 0, 0, 0.5f), + BLACK_ALPHA_75 = new Color(0, 0, 0, 0.75f), WHITE_ALPHA = new Color(255, 255, 255, 0.5f), BLUE_DIVIDER = new Color(49, 94, 237), BLUE_BACKGROUND = new Color(74, 130, 255), diff --git a/src/yugecin/opsudance/ui/OptionsOverlay.java b/src/yugecin/opsudance/ui/OptionsOverlay.java new file mode 100644 index 00000000..81c711a0 --- /dev/null +++ b/src/yugecin/opsudance/ui/OptionsOverlay.java @@ -0,0 +1,243 @@ +/* + * opsu!dance - fork of opsu! with cursordance auto + * Copyright (C) 2016 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.Options.GameOption; +import itdelatrisu.opsu.Utils; +import itdelatrisu.opsu.ui.Colors; +import itdelatrisu.opsu.ui.Fonts; +import itdelatrisu.opsu.ui.MenuButton; +import itdelatrisu.opsu.ui.UI; +import org.newdawn.slick.Color; +import org.newdawn.slick.Graphics; +import org.newdawn.slick.Image; +import org.newdawn.slick.Input; + +@SuppressWarnings("unused") +public class OptionsOverlay { + + private Parent parent; + + private OptionTab[] tabs; + private OptionTab hoverTab; + private int selectedTab; + private GameOption hoverOption; + private GameOption selectedOption; + + private int width; + private int height; + + private int optionWidth; + private int optionStartX; + private int optionStartY; + private int optionHeight; + + private int scrollOffset; + private final int maxScrollOffset; + + public OptionsOverlay(Parent parent, OptionTab[] tabs, int defaultSelectedTabIndex, int containerWidth, int containerHeight) { + this.parent = parent; + + this.tabs = tabs; + selectedTab = defaultSelectedTabIndex; + + width = containerWidth; + height = containerHeight; + + // calculate positions + optionWidth = width / 2; + optionHeight = (int) ((Fonts.MEDIUM.getLineHeight()) * 1.1f); + optionStartX = optionWidth / 2; + + // initialize tabs + Image tabImage = GameImage.MENU_TAB.getImage(); + float tabX = width * 0.032f + (tabImage.getWidth() / 3); + float tabY = Fonts.XLARGE.getLineHeight() + Fonts.DEFAULT.getLineHeight() + height * 0.015f - (tabImage.getHeight() / 2f); + int tabOffset = Math.min(tabImage.getWidth(), width / tabs.length); + int maxScrollOffset = Fonts.MEDIUM.getLineHeight() * 2 * tabs.length; + for (int i = 0; i < tabs.length; i++) { + maxScrollOffset += tabs[i].options.length * optionHeight; + tabs[i].tabIndex = i; + tabs[i].button = new MenuButton(tabImage, tabX, tabY); + tabX += tabOffset; + if (tabX + tabOffset > width) { + tabX = 0; + tabY += GameImage.MENU_TAB.getImage().getHeight() / 2f; + } + } + this.maxScrollOffset = maxScrollOffset; + + // calculate other positions + optionStartY = (int) (tabY + tabImage.getHeight() / 2 + 2); // +2 for the separator line + } + + public void render(Graphics g, int mouseX, int mouseY) { + // bg + g.setColor(Colors.BLACK_ALPHA_75); + g.fillRect(0, 0, width, height); + + // title + renderTitle(); + + // option tabs + renderTabs(mouseX, mouseY); + + // line separator + g.setColor(Color.white); + g.setLineWidth(2f); + g.drawLine(0, optionStartY - 1, width, optionStartY - 1); + g.resetLineWidth(); + + // options + renderOptions(g); + + UI.getBackButton().draw(); + UI.draw(g); + } + + private void renderOptions(Graphics g) { + g.setClip(0, optionStartY, width, height - optionStartY); + int y = -scrollOffset + optionStartY; + for (int tabIndex = 0; tabIndex < tabs.length; tabIndex++) { + OptionTab tab = tabs[tabIndex]; + if (y > 0) { + Fonts.LARGE.drawString(optionStartX, y + Fonts.LARGE.getLineHeight() * 0.6f, tab.name, Color.cyan); + } + y += Fonts.MEDIUM.getLineHeight() * 2; + for (int optionIndex = 0; optionIndex < tab.options.length; optionIndex++) { + GameOption option = tab.options[optionIndex]; + if (!option.showCondition()) { + continue; + } + if (y > 0) { + renderOption(g, option, y, option == hoverOption); + } + y += optionHeight; + if (y > height) { + tabIndex = tabs.length; + break; + } + } + } + g.clearClip(); + } + + private void renderOption(Graphics g, GameOption option, int y, boolean focus) { + Color col = focus ? Colors.GREEN : Colors.WHITE_FADE; + Fonts.MEDIUM.drawString(optionStartX, y, option.getName(), col); + Fonts.MEDIUM.drawString(optionStartX + optionWidth / 2, y, option.getValueString(), col); + } + + public void renderTabs(int mouseX, int mouseY) { + for (int i = 0; i < tabs.length; i++) { + OptionTab tab = tabs[i]; + boolean hovering = tab.button.contains(mouseX, mouseY); + UI.drawTab(tab.button.getX(), tab.button.getY(), tab.name, i == selectedTab, hovering); + } + } + + private void renderTitle() { + float marginX = width * 0.015f; + float marginY = height * 0.01f; + Fonts.XLARGE.drawString(marginX, marginY, "Options", Color.white); + marginX += Fonts.XLARGE.getWidth("Options") * 1.2f; + marginY += Fonts.XLARGE.getLineHeight() * 0.9f - Fonts.DEFAULT.getLineHeight(); + Fonts.DEFAULT.drawString(marginX, marginY, "Change the way opsu! behaves", Color.white); + } + + public void update(int delta, int mouseX, int mouseY) { + updateHoverOption(mouseX, mouseY); + UI.getBackButton().hoverUpdate(delta, mouseX, mouseY); + } + + public void mousePressed(int button, int x, int y) { + selectedOption = hoverOption; + + if (UI.getBackButton().contains(x, y)) { + parent.onLeave(); + return; + } + } + + public void mouseReleased(int button, int x, int y) { + selectedOption = null; + } + + public void mouseDragged(int oldx, int oldy, int newx, int newy) { + } + + public void mouseWheelMoved(int delta) { + scrollOffset = Utils.clamp(scrollOffset - delta, 0, maxScrollOffset - optionStartY); + } + + public void keyPressed(int key, char c) { + switch (key) { + case Input.KEY_ESCAPE: + parent.onLeave(); + break; + } + } + + private void updateHoverOption(int mouseX, int mouseY) { + hoverOption = null; + if (mouseY < optionStartY || mouseX < optionStartX || mouseX > optionStartX + optionWidth) { + return; + } + + int mouseVirtualY = scrollOffset + mouseY - optionStartY; + for (OptionTab tab : tabs) { + mouseVirtualY -= Fonts.MEDIUM.getLineHeight() * 2; + for (int optionIndex = 0; optionIndex < tab.options.length; optionIndex++) { + GameOption option = tab.options[optionIndex]; + if (!option.showCondition()) { + continue; + } + if (mouseVirtualY <= optionHeight) { + if (mouseVirtualY >= 0) { + hoverOption = option; + } + return; + } + mouseVirtualY -= optionHeight; + } + } + } + + public static class OptionTab { + + public final String name; + public final GameOption[] options; + private MenuButton button; + private int tabIndex; + + public OptionTab(String name, GameOption[] options) { + this.name = name; + this.options = options; + } + + } + + public interface Parent { + + void onLeave(); + void onSaveOption(GameOption option); + + } + +}