add navigation to optionmenu

This commit is contained in:
yugecin 2017-05-28 16:11:05 +02:00
parent 2deef7c3a9
commit 6413392f1e
4 changed files with 214 additions and 30 deletions

View File

@ -465,6 +465,10 @@ public class DisplayContainer implements ErrorDumpable, ResolutionChangedListene
return (Sys.getTime() * 1000) / Sys.getTimerResolution();
}
public boolean isWidescreen() {
return width * 1000 / height == 1777; // 16:9
}
@Override
public void writeErrorDump(StringWriter dump) {
dump.append("> DisplayContainer dump\n");

View File

@ -18,12 +18,14 @@
package yugecin.opsudance.options;
import itdelatrisu.opsu.GameImage;
import static yugecin.opsudance.options.Options.*;
public class OptionGroups {
public static final OptionTab[] normalOptions = new OptionTab[] {
new OptionTab("GENERAL", null),
new OptionTab("General", GameImage.SEARCH),
new OptionTab("GENERAL", new Option[]{
OPTION_DISABLE_UPDATER,
OPTION_ENABLE_WATCH_SERVICE
@ -31,7 +33,7 @@ public class OptionGroups {
new OptionTab("LANGUAGE", new Option[]{
OPTION_SHOW_UNICODE,
}),
new OptionTab("GRAPHICS", null),
new OptionTab("Graphics", GameImage.SEARCH),
new OptionTab("RENDERER", new Option[] {
OPTION_SCREEN_RESOLUTION,
OPTION_ALLOW_LARGER_RESOLUTIONS,
@ -54,7 +56,7 @@ public class OptionGroups {
OPTION_DANCING_CIRCLES,
OPTION_DANCING_CIRCLES_MULTIPLIER,
}),
new OptionTab("SKIN", null),
new OptionTab("Skin", GameImage.SEARCH),
new OptionTab("SKIN", new Option[]{
OPTION_SKIN,
OPTION_IGNORE_BEATMAP_SKINS,
@ -69,7 +71,7 @@ public class OptionGroups {
OPTION_DISABLE_CURSOR
// TODO use combo colour as tint for slider ball option
}),
new OptionTab("AUDIO", null),
new OptionTab("Audio", GameImage.SEARCH),
new OptionTab("VOLUME", new Option[]{
OPTION_MASTER_VOLUME,
OPTION_MUSIC_VOLUME,
@ -82,7 +84,7 @@ public class OptionGroups {
OPTION_DISABLE_SOUNDS,
OPTION_ENABLE_THEME_SONG
}),
new OptionTab("GAMEPLAY", null),
new OptionTab("Gameplay", GameImage.SEARCH),
new OptionTab("GENERAL", new Option[] {
OPTION_BACKGROUND_DIM,
OPTION_FORCE_DEFAULT_PLAYFIELD,
@ -96,7 +98,7 @@ public class OptionGroups {
OPTION_MAP_END_DELAY,
OPTION_EPILEPSY_WARNING,
}),
new OptionTab("INPUT", null),
new OptionTab("Input", GameImage.SEARCH),
new OptionTab("KEY MAPPING", new Option[]{
OPTION_KEY_LEFT,
OPTION_KEY_RIGHT,
@ -105,7 +107,7 @@ public class OptionGroups {
OPTION_DISABLE_MOUSE_WHEEL,
OPTION_DISABLE_MOUSE_BUTTONS,
}),
new OptionTab("CUSTOM", null),
new OptionTab("Custom", GameImage.SEARCH),
new OptionTab("DIFFICULTY", new Option[]{
OPTION_FIXED_CS,
OPTION_FIXED_HP,
@ -116,7 +118,7 @@ public class OptionGroups {
OPTION_CHECKPOINT,
OPTION_REPLAY_SEEKING,
}),
new OptionTab("DANCE", null),
new OptionTab("Dance", GameImage.SEARCH),
new OptionTab("MOVER", new Option[]{
OPTION_DANCE_MOVER,
OPTION_DANCE_EXGON_DELAY,
@ -143,7 +145,7 @@ public class OptionGroups {
new OptionTab("MIRROR", new Option[] {
OPTION_DANCE_MIRROR,
}),
new OptionTab("ADVANCED DISPLAY", null),
new OptionTab("Advanced Display", GameImage.SEARCH),
new OptionTab("OBJECTS", new Option[]{
OPTION_DANCE_DRAW_APPROACH,
OPTION_DANCE_OBJECT_COLOR_OVERRIDE,
@ -163,7 +165,7 @@ public class OptionGroups {
OPTION_DANCE_REMOVE_BG,
OPTION_DANCE_ENABLE_SB,
}),
new OptionTab ("PIPPI", null),
new OptionTab ("Pippi", GameImage.SEARCH),
new OptionTab ("GENERAL", new Option[]{
OPTION_PIPPI_ENABLE,
OPTION_PIPPI_RADIUS_PERCENT,

View File

@ -17,15 +17,25 @@
*/
package yugecin.opsudance.options;
import itdelatrisu.opsu.GameImage;
public class OptionTab {
public final String name;
public final Option[] options;
public final GameImage icon;
public boolean filtered;
public OptionTab(String name, GameImage icon) {
this.name = name;
this.icon = icon;
this.options = null;
}
public OptionTab(String name, Option[] options) {
this.name = name;
this.options = options;
this.icon = null;
}
}

View File

@ -49,6 +49,12 @@ public class OptionsOverlay extends OverlayOpsuState {
private static final Color COL_GREY = new Color(55, 55, 57);
private static final Color COL_BLUE = new Color(Colors.BLUE_BACKGROUND);
private static final Color COL_COMBOBOX_HOVER = new Color(185, 19, 121);
private static final Color COL_NAV_BG = new Color(COL_BG);
private static final Color COL_NAV_INDICATOR = new Color(COL_PINK);
private static final Color COL_NAV_WHITE = new Color(COL_WHITE);
private static final Color COL_NAV_FILTERED = new Color(37, 37, 37);
private static final Color COL_NAV_INACTIVE = new Color(153, 153, 153);
private static final Color COL_NAV_FILTERED_HOVERED = new Color(58, 58, 58);
private static final float INDICATOR_ALPHA = 0.8f;
private static final Color COL_INDICATOR = new Color(Color.black);
@ -84,6 +90,8 @@ public class OptionsOverlay extends OverlayOpsuState {
private Image searchImg;
private OptionTab[] sections;
private OptionTab activeSection;
private OptionTab hoveredNavigationEntry;
private Option hoverOption;
private Option selectedOption;
@ -102,6 +110,13 @@ public class OptionsOverlay extends OverlayOpsuState {
private int width;
private int height;
private int navButtonSize;
private int navStartY;
private int navExpadedWidth;
private int navWidth;
private int navHoverTime;
private int navIndicatorSize;
private int optionWidth;
private int optionStartX;
private int optionStartY;
@ -166,8 +181,13 @@ public class OptionsOverlay extends OverlayOpsuState {
height = displayContainer.height;
// calculate positions
float navIconWidthRatio = displayContainer.isWidescreen() ? 0.046875f : 0.065f;
// non-widescreen ratio is not accurate
navButtonSize = (int) (displayContainer.width * navIconWidthRatio);
navIndicatorSize = navButtonSize / 10;
navExpadedWidth = (int) (finalWidth * 0.45f) - navButtonSize;
paddingRight = (int) (displayContainer.width * 0.009375f); // not so accurate
paddingLeft = (int) (displayContainer.width * 0.0180f); // not so accurate
paddingLeft = navButtonSize + (int) (displayContainer.width * 0.0180f); // not so accurate
paddingTextLeft = paddingLeft + LINEWIDTH + (int) (displayContainer.width * 0.00625f); // not so accurate
optionStartX = paddingTextLeft;
textOptionsY = Fonts.LARGE.getLineHeight() * 2;
@ -191,9 +211,11 @@ public class OptionsOverlay extends OverlayOpsuState {
checkOnImg = GameImage.CONTROL_CHECK_ON.getImage().getScaledCopy(controlImageSize, controlImageSize);
checkOffImg = GameImage.CONTROL_CHECK_OFF.getImage().getScaledCopy(controlImageSize, controlImageSize);
int navTotalHeight = 0;
dropdownMenus.clear();
for (OptionTab section : sections) {
if (section.options == null) {
navTotalHeight += navButtonSize;
continue;
}
for (final Option option : section.options) {
@ -229,6 +251,7 @@ public class OptionsOverlay extends OverlayOpsuState {
dropdownMenus.put(listOption, menu);
}
}
navStartY = (height - navTotalHeight) / 2;
int searchImgSize = (int) (Fonts.LARGE.getLineHeight() * 0.75f);
searchImg = GameImage.SEARCH.getImage().getScaledCopy(searchImgSize, searchImgSize);
@ -236,11 +259,11 @@ public class OptionsOverlay extends OverlayOpsuState {
@Override
public void onRender(Graphics g) {
g.setClip(0, 0, width, height);
g.setClip(navButtonSize, 0, width - navButtonSize, height);
// bg
g.setColor(COL_BG);
g.fillRect(0, 0, width, height);
g.fillRect(navButtonSize, 0, width, height);
// title
renderTitle();
@ -263,6 +286,8 @@ public class OptionsOverlay extends OverlayOpsuState {
g.fillRect(width - 5, scrollHandler.getPosition() / maxScrollOffset * (height - 45), 5, 45);
g.clearClip();
renderNavigation(g);
// UI
UI.getBackButton().draw(g);
@ -275,6 +300,57 @@ public class OptionsOverlay extends OverlayOpsuState {
}
}
private void renderNavigation(Graphics g) {
navWidth = navButtonSize;
if (navHoverTime >= 600) {
navWidth += navExpadedWidth;
} else if (navHoverTime > 300) {
AnimationEquation anim = AnimationEquation.IN_EXPO;
if (displayContainer.mouseX < navWidth) {
anim = AnimationEquation.OUT_EXPO;
}
float progress = anim.calc((navHoverTime - 300f) / 300f);
navWidth += (int) (progress * navExpadedWidth);
}
g.setClip(0, 0, navWidth, height);
g.setColor(COL_NAV_BG);
g.fillRect(0, 0, navWidth, displayContainer.height);
int y = navStartY;
float iconSize = navButtonSize / 2.5f;
float iconPadding = iconSize * 0.75f;
int fontOffsetX = navButtonSize + navIndicatorSize;
int fontOffsetY = (navButtonSize - Fonts.MEDIUM.getLineHeight()) / 2;
for (OptionTab section : sections) {
if (section.icon == null) {
continue;
}
Color iconCol = COL_NAV_INACTIVE;
Color fontCol = COL_NAV_WHITE;
if (section == activeSection) {
iconCol = COL_NAV_WHITE;
g.fillRect(0, y, navWidth, navButtonSize);
g.setColor(COL_NAV_INDICATOR);
g.fillRect(navWidth - navIndicatorSize, y, navIndicatorSize, navButtonSize);
} else if (section == hoveredNavigationEntry) {
iconCol = COL_NAV_WHITE;
}
if (section.filtered) {
iconCol = fontCol = COL_NAV_FILTERED;
if (section == hoveredNavigationEntry) {
iconCol = COL_NAV_FILTERED_HOVERED;
}
}
section.icon.getImage().draw(iconPadding, y + iconPadding, iconSize, iconSize, iconCol);
if (navHoverTime > 0) {
Fonts.MEDIUM.drawString(fontOffsetX, y + fontOffsetY, section.name, fontCol);
}
y += navButtonSize;
}
g.clearClip();
}
private void renderIndicator(Graphics g) {
g.setColor(COL_INDICATOR);
int indicatorPos = this.indicatorPos;
@ -289,7 +365,7 @@ public class OptionsOverlay extends OverlayOpsuState {
indicatorPos += AnimationEquation.OUT_BACK.calc((float) indicatorMoveAnimationTime / INDICATORMOVEANIMATIONTIME) * indicatorOffsetToNextPos;
}
}
g.fillRect(0, indicatorPos - scrollHandler.getPosition(), width, optionHeight);
g.fillRect(navButtonSize, indicatorPos - scrollHandler.getPosition(), width, optionHeight);
}
private void renderKeyEntry(Graphics g) {
@ -324,12 +400,12 @@ public class OptionsOverlay extends OverlayOpsuState {
continue;
}
int lineStartY = (int) (y + Fonts.LARGE.getLineHeight() * 0.6f);
if (render) {
if (section.options == null) {
FontUtil.drawRightAligned(Fonts.XLARGE, width, -paddingRight, (int) (y + Fonts.XLARGE.getLineHeight() * 0.3f), section.name, COL_CYAN);
} else {
Fonts.MEDIUMBOLD.drawString(paddingTextLeft, lineStartY, section.name, COL_WHITE);
}
if (section.options == null) {
FontUtil.drawRightAligned(Fonts.XLARGE, width, -paddingRight,
(int) (y + Fonts.XLARGE.getLineHeight() * 0.3f), section.name.toUpperCase(),
COL_CYAN);
} else {
Fonts.MEDIUMBOLD.drawString(paddingTextLeft, lineStartY, section.name, COL_WHITE);
}
y += sectionLineHeight;
maxScrollOffset += sectionLineHeight;
@ -479,8 +555,11 @@ public class OptionsOverlay extends OverlayOpsuState {
}
private void renderTitle() {
FontUtil.drawCentered(Fonts.LARGE, width, 0, textOptionsY - scrollHandler.getIntPosition(), "Options", COL_WHITE);
FontUtil.drawCentered(Fonts.MEDIUM, width, 0, textChangeY - scrollHandler.getIntPosition(), "Change the way opsu! behaves", COL_PINK);
int textWidth = width - navButtonSize;
FontUtil.drawCentered(Fonts.LARGE, textWidth, navButtonSize,
textOptionsY - scrollHandler.getIntPosition(), "Options", COL_WHITE);
FontUtil.drawCentered(Fonts.MEDIUM, textWidth, navButtonSize,
textChangeY - scrollHandler.getIntPosition(), "Change the way opsu! behaves", COL_PINK);
}
private void renderSearch(Graphics g) {
@ -488,14 +567,14 @@ public class OptionsOverlay extends OverlayOpsuState {
if (scrollHandler.getIntPosition() > posSearchY) {
ypos = textSearchYOffset;
g.setColor(COL_BG);
g.fillRect(0, 0, width, textSearchYOffset * 2 + Fonts.LARGE.getLineHeight());
g.fillRect(navButtonSize, 0, width, textSearchYOffset * 2 + Fonts.LARGE.getLineHeight());
}
String searchText = "Type to search!";
if (lastSearchText.length() > 0) {
searchText = lastSearchText;
}
FontUtil.drawCentered(Fonts.LARGE, width, 0, ypos, searchText, COL_WHITE);
int imgPosX = (width - Fonts.LARGE.getWidth(searchText)) / 2 - searchImg.getWidth() - 10;
FontUtil.drawCentered(Fonts.LARGE, width, navButtonSize, ypos, searchText, COL_WHITE);
int imgPosX = navButtonSize + (width - Fonts.LARGE.getWidth(searchText)) / 2 - searchImg.getWidth() - 10;
searchImg.draw(imgPosX, ypos + Fonts.LARGE.getLineHeight() * 0.25f, COL_WHITE);
}
@ -510,6 +589,7 @@ public class OptionsOverlay extends OverlayOpsuState {
@Override
public void show() {
navHoverTime = 0;
indicatorPos = -optionHeight;
indicatorOffsetToNextPos = 0;
indicatorMoveAnimationTime = 0;
@ -546,10 +626,20 @@ public class OptionsOverlay extends OverlayOpsuState {
sliderSoundDelay -= delta;
}
if (mouseX < navWidth) {
if (navHoverTime < 600) {
navHoverTime += delta;
}
} else if (navHoverTime > 0) {
navHoverTime -= delta;
}
if (mouseX - prevMouseX == 0 && mouseY - prevMouseY == 0) {
updateIndicatorAlpha();
return;
}
updateActiveSection();
updateHoverNavigation(mouseX, mouseY);
prevMouseX = mouseX;
prevMouseY = mouseY;
updateHoverOption(mouseX, mouseY);
@ -565,6 +655,24 @@ public class OptionsOverlay extends OverlayOpsuState {
}
}
private void updateHoverNavigation(int mouseX, int mouseY) {
hoveredNavigationEntry = null;
if (mouseX >= navWidth) {
return;
}
int y = navStartY;
for (OptionTab section : sections) {
if (section.options != null) {
continue;
}
int nextY = y + navButtonSize;
if (y <= mouseY && mouseY < nextY) {
hoveredNavigationEntry = section;
}
y = nextY;
}
}
private void updateIndicatorAlpha() {
if (hoverOption == null) {
if (indicatorHideAnimationTime < INDICATORHIDEANIMATIONTIME) {
@ -594,20 +702,28 @@ public class OptionsOverlay extends OverlayOpsuState {
// if acceptInput is false, it means that we're currently hiding ourselves
float progress;
// navigation elemenst fade out with a different animation
float navProgress;
if (acceptInput) {
animationtime += delta;
if (animationtime >= SHOWANIMATIONTIME) {
animationtime = SHOWANIMATIONTIME;
}
progress = AnimationEquation.OUT_EXPO.calc((float) animationtime / SHOWANIMATIONTIME);
progress = (float) animationtime / SHOWANIMATIONTIME;
navProgress = Utils.clamp(progress * 10f, 0f, 1f);
progress = AnimationEquation.OUT_EXPO.calc(progress);
} else {
animationtime -= delta;
if (animationtime < 0) {
animationtime = 0;
}
progress = hideAnimationStartProgress * AnimationEquation.IN_EXPO.calc((float) animationtime / hideAnimationTime);
progress = (float) animationtime / hideAnimationTime;
navProgress = hideAnimationStartProgress * AnimationEquation.IN_CIRC.calc(progress);
progress = hideAnimationStartProgress * AnimationEquation.IN_EXPO.calc(progress);
}
width = (int) (progress * finalWidth);
width = navButtonSize + (int) (progress * (finalWidth - navButtonSize));
COL_NAV_FILTERED.a = COL_NAV_INACTIVE.a = COL_NAV_FILTERED_HOVERED.a = COL_NAV_INDICATOR.a =
COL_NAV_WHITE.a = COL_NAV_BG.a = navProgress;
COL_BG.a = BG_ALPHA * progress;
COL_WHITE.a = progress;
COL_PINK.a = progress;
@ -692,6 +808,28 @@ public class OptionsOverlay extends OverlayOpsuState {
}
}
if (hoveredNavigationEntry != null && !hoveredNavigationEntry.filtered) {
int sectionPosition = 0;
for (OptionTab section : sections) {
if (section == hoveredNavigationEntry) {
break;
}
if (section.filtered) {
continue;
}
sectionPosition += sectionLineHeight;
if (section.options == null) {
continue;
}
for (Option option : section.options) {
if (!option.isFiltered() && option.showCondition()) {
sectionPosition += optionHeight;
}
}
}
scrollHandler.scrollToPosition(sectionPosition);
}
if (UI.getBackButton().contains(x, y)){
hide();
if (listener != null) {
@ -776,6 +914,33 @@ public class OptionsOverlay extends OverlayOpsuState {
o.setValue(Utils.clamp(value, o.min, o.max));
}
private void updateActiveSection() {
// active section is the one that is visible in the top half of the screen
activeSection = sections[0];
int virtualY = optionStartY;
for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) {
OptionTab section = sections[sectionIndex];
if (section.filtered) {
continue;
}
virtualY += sectionLineHeight;
if (virtualY > scrollHandler.getPosition() + height / 2) {
return;
}
if (section.options == null) {
activeSection = section;
continue;
}
for (int optionIndex = 0; optionIndex < section.options.length; optionIndex++) {
Option option = section.options[optionIndex];
if (option.isFiltered() || !option.showCondition()) {
continue;
}
virtualY += optionHeight;
}
}
}
private void updateHoverOption(int mouseX, int mouseY) {
if (openDropdownMenu != null || keyEntryLeft || keyEntryRight) {
return;
@ -805,7 +970,7 @@ public class OptionsOverlay extends OverlayOpsuState {
continue;
}
if (mouseVirtualY <= optionHeight) {
if (mouseVirtualY >= 0) {
if (mouseX > navWidth && mouseVirtualY >= 0) {
int indicatorPos = scrollHandler.getIntPosition() + mouseY - mouseVirtualY;
if (indicatorPos != this.indicatorPos + indicatorOffsetToNextPos) {
this.indicatorPos += indicatorOffsetToNextPos; // finish the current moving animation
@ -843,7 +1008,9 @@ public class OptionsOverlay extends OverlayOpsuState {
if (section.options == null) {
lastBigSectionMatches = sectionMatches;
lastBigSection = section;
section.filtered = true;
if (!lastBigSectionMatches) {
section.filtered = true;
}
continue;
}
section.filtered = true;
@ -855,6 +1022,7 @@ public class OptionsOverlay extends OverlayOpsuState {
}
if (!option.filter(lastSearchText)) {
section.filtered = false;
//noinspection ConstantConditions
lastBigSection.filtered = false;
}
}