use dropdown menus

This commit is contained in:
yugecin 2017-01-30 00:53:28 +01:00
parent 4b2a2b12f1
commit e7e3672491
2 changed files with 153 additions and 166 deletions

View File

@ -39,7 +39,7 @@ public class DropdownMenu<E> extends Component {
private E[] items; private E[] items;
private String[] itemNames; private String[] itemNames;
private int selectedItemIndex = 0; private int selectedItemIndex;
private boolean expanded; private boolean expanded;
private AnimatedValue expandProgress = new AnimatedValue(300, 0f, 1f, AnimationEquation.LINEAR); private AnimatedValue expandProgress = new AnimatedValue(300, 0f, 1f, AnimationEquation.LINEAR);
@ -65,6 +65,39 @@ public class DropdownMenu<E> extends Component {
init(items, x, y, width); init(items, x, y, width);
} }
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
if (expanded) {
return height;
}
return baseHeight;
}
public boolean isOpen() {
return expanded;
}
@Override
public void keyPressed(int key, char c) {
if (key == Input.KEY_ESCAPE) {
this.expanded = false;
}
}
/**
* Selects the item at the given index.
* @param index the list item index
* @throws IllegalArgumentException if {@code index} is negative or greater than or equal to size
*/
public void setSelectedIndex(int index) {
if (index < 0 || index >= items.length)
throw new IllegalArgumentException();
this.selectedItemIndex = index;
}
private int getMaxItemWidth() { private int getMaxItemWidth() {
int maxWidth = 0; int maxWidth = 0;
for (String itemName : itemNames) { for (String itemName : itemNames) {
@ -255,4 +288,9 @@ public class DropdownMenu<E> extends Component {
public void setChevronRightColor(Color c) { public void setChevronRightColor(Color c) {
this.chevronRightColor = c; this.chevronRightColor = c;
} }
public void setTextColor(Color c) {
this.textColor = c;
}
} }

View File

@ -24,10 +24,7 @@ import itdelatrisu.opsu.Options.GameOption.OptionType;
import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundController;
import itdelatrisu.opsu.audio.SoundEffect; import itdelatrisu.opsu.audio.SoundEffect;
import itdelatrisu.opsu.ui.Colors; import itdelatrisu.opsu.ui.*;
import itdelatrisu.opsu.ui.Fonts;
import itdelatrisu.opsu.ui.KineticScrolling;
import itdelatrisu.opsu.ui.UI;
import itdelatrisu.opsu.ui.animations.AnimationEquation; import itdelatrisu.opsu.ui.animations.AnimationEquation;
import org.newdawn.slick.*; import org.newdawn.slick.*;
import org.newdawn.slick.gui.TextField; import org.newdawn.slick.gui.TextField;
@ -35,6 +32,9 @@ import yugecin.opsudance.core.DisplayContainer;
import yugecin.opsudance.core.state.OverlayOpsuState; import yugecin.opsudance.core.state.OverlayOpsuState;
import yugecin.opsudance.utils.FontUtil; import yugecin.opsudance.utils.FontUtil;
import java.util.HashMap;
import java.util.LinkedList;
public class OptionsOverlay extends OverlayOpsuState { public class OptionsOverlay extends OverlayOpsuState {
private final DisplayContainer displayContainer; private final DisplayContainer displayContainer;
@ -80,30 +80,22 @@ public class OptionsOverlay extends OverlayOpsuState {
private Image sliderBallImg; private Image sliderBallImg;
private Image checkOnImg; private Image checkOnImg;
private Image checkOffImg; private Image checkOffImg;
private Image chevronDownImg;
private Image chevronRightImg;
private Image searchImg; private Image searchImg;
private OptionTab[] sections; private OptionTab[] sections;
private GameOption hoverOption; private GameOption hoverOption;
private GameOption selectedOption; private GameOption selectedOption;
private GameOption selectedListOption;
private int sliderOptionStartX; private int sliderOptionStartX;
private int sliderOptionLength; private int sliderOptionLength;
private boolean isAdjustingSlider; private boolean isAdjustingSlider;
private static final int LISTOPENANIMATIONTIME = 200; private final HashMap<GameOption, DropdownMenu<Object>> dropdownMenus;
private static final int LISTCLOSEANIMATIONTIME = 175; private final LinkedList<DropdownMenu<Object>> visibleDropdownMenus;
private int listAnimationTime; private int dropdownMenuPaddingY;
private boolean isListOptionOpen; private DropdownMenu<Object> openDropdownMenu;
private int listItemHeight; private int openDropdownVirtualY;
private int listStartX;
private int listStartY;
private int listWidth;
private int listHeight;
private int listHoverIndex;
private int finalWidth; private int finalWidth;
private int width; private int width;
@ -149,7 +141,9 @@ public class OptionsOverlay extends OverlayOpsuState {
this.sections = sections; this.sections = sections;
listHoverIndex = -1; dropdownMenus = new HashMap<>();
visibleDropdownMenus = new LinkedList<>();
searchField = new TextField(displayContainer, null, 0, 0, 0, 0); searchField = new TextField(displayContainer, null, 0, 0, 0, 0);
searchField.setMaxLength(20); searchField.setMaxLength(20);
@ -179,9 +173,13 @@ public class OptionsOverlay extends OverlayOpsuState {
textSearchYOffset = Fonts.MEDIUM.getLineHeight() / 2; textSearchYOffset = Fonts.MEDIUM.getLineHeight() / 2;
optionStartY = posSearchY + Fonts.MEDIUM.getLineHeight() + Fonts.LARGE.getLineHeight(); optionStartY = posSearchY + Fonts.MEDIUM.getLineHeight() + Fonts.LARGE.getLineHeight();
if (active) {
width = finalWidth;
optionWidth = width - optionStartX - paddingRight;
}
optionHeight = (int) (Fonts.MEDIUM.getLineHeight() * 1.3f); optionHeight = (int) (Fonts.MEDIUM.getLineHeight() * 1.3f);
optionTextOffsetY = (int) ((optionHeight - Fonts.MEDIUM.getLineHeight()) / 2f); optionTextOffsetY = (int) ((optionHeight - Fonts.MEDIUM.getLineHeight()) / 2f);
listItemHeight = (int) (optionHeight * 4f / 5f);
controlImageSize = (int) (Fonts.MEDIUM.getLineHeight() * 0.7f); controlImageSize = (int) (Fonts.MEDIUM.getLineHeight() * 0.7f);
controlImagePadding = (optionHeight - controlImageSize) / 2; controlImagePadding = (optionHeight - controlImageSize) / 2;
@ -189,9 +187,43 @@ public class OptionsOverlay extends OverlayOpsuState {
checkOnImg = GameImage.CONTROL_CHECK_ON.getImage().getScaledCopy(controlImageSize, controlImageSize); checkOnImg = GameImage.CONTROL_CHECK_ON.getImage().getScaledCopy(controlImageSize, controlImageSize);
checkOffImg = GameImage.CONTROL_CHECK_OFF.getImage().getScaledCopy(controlImageSize, controlImageSize); checkOffImg = GameImage.CONTROL_CHECK_OFF.getImage().getScaledCopy(controlImageSize, controlImageSize);
chevronDownImg = GameImage.CHEVRON_DOWN.getImage().getScaledCopy(controlImageSize, controlImageSize); dropdownMenus.clear();
chevronRightImg = GameImage.CHEVRON_RIGHT.getImage().getScaledCopy(controlImageSize, controlImageSize); for (OptionTab section : sections) {
chevronRightImg.setImageColor(0f, 0f, 0f); if (section.options == null) {
continue;
}
for (final GameOption option : section.options) {
Object[] items = option.getListItems();
if (items == null) {
continue;
}
DropdownMenu<Object> menu = new DropdownMenu<Object>(displayContainer, items, 0, 0, 0) {
@Override
public void itemSelected(int index, Object item) {
option.clickListItem(index);
openDropdownMenu = null;
}
};
// not the best way to determine the selected option AT ALL, but seems like it's the only one right now...
String selectedValue = option.getValueString();
int idx = 0;
for (Object item : items) {
if (item.toString().equals(selectedValue)) {
break;
}
idx++;
}
menu.setSelectedIndex(idx);
menu.setBackgroundColor(COL_BG);
menu.setBorderColor(Color.transparent);
menu.setChevronDownColor(COL_WHITE);
menu.setChevronRightColor(COL_BG);
menu.setHighlightColor(COL_COMBOBOX_HOVER);
menu.setTextColor(COL_WHITE);
dropdownMenuPaddingY = (optionHeight - menu.getHeight()); // TODO why isn't this /2 ?
dropdownMenus.put(option, menu);
}
}
int searchImgSize = (int) (Fonts.LARGE.getLineHeight() * 0.75f); int searchImgSize = (int) (Fonts.LARGE.getLineHeight() * 0.75f);
searchImg = GameImage.SEARCH.getImage().getScaledCopy(searchImgSize, searchImgSize); searchImg = GameImage.SEARCH.getImage().getScaledCopy(searchImgSize, searchImgSize);
@ -212,7 +244,12 @@ public class OptionsOverlay extends OverlayOpsuState {
// options // options
renderOptions(g); renderOptions(g);
renderOpenList(g); if (openDropdownMenu != null) {
openDropdownMenu.render(g);
if (!openDropdownMenu.isOpen()) {
openDropdownMenu = null;
}
}
renderSearch(g); renderSearch(g);
@ -271,6 +308,7 @@ public class OptionsOverlay extends OverlayOpsuState {
} }
private void renderOptions(Graphics g) { private void renderOptions(Graphics g) {
visibleDropdownMenus.clear();
int y = -scrollHandler.getIntPosition() + optionStartY; int y = -scrollHandler.getIntPosition() + optionStartY;
maxScrollOffset = optionStartY; maxScrollOffset = optionStartY;
boolean render = true; boolean render = true;
@ -299,7 +337,7 @@ public class OptionsOverlay extends OverlayOpsuState {
if (!option.showCondition() || option.isFiltered()) { if (!option.showCondition() || option.isFiltered()) {
continue; continue;
} }
if (y > -optionHeight || (isListOptionOpen && hoverOption == option)) { if (y > -optionHeight || (openDropdownMenu != null && openDropdownMenu.equals(dropdownMenus.get(option)))) {
renderOption(g, option, y); renderOption(g, option, y);
} }
y += optionHeight; y += optionHeight;
@ -321,81 +359,21 @@ public class OptionsOverlay extends OverlayOpsuState {
maxScrollOffset += sections[sectionIndex].options.length * optionHeight; maxScrollOffset += sections[sectionIndex].options.length * optionHeight;
} }
} }
maxScrollOffset -= height * 2 / 3; if (openDropdownMenu != null) {
if (isListOptionOpen) { maxScrollOffset = Math.max(maxScrollOffset, openDropdownVirtualY + openDropdownMenu.getHeight());
maxScrollOffset = Math.max(maxScrollOffset, listHeight);
} }
maxScrollOffset -= height * 2 / 3;
if (maxScrollOffset < 0) { if (maxScrollOffset < 0) {
maxScrollOffset = 0; maxScrollOffset = 0;
} }
scrollHandler.setMinMax(0f, maxScrollOffset); scrollHandler.setMinMax(0, maxScrollOffset);
}
private void renderOpenList(Graphics g) {
if (!isListOptionOpen && listAnimationTime == 0) {
return;
}
float progress;
int listItemHeight;
if (isListOptionOpen) {
listAnimationTime = Math.min(LISTOPENANIMATIONTIME, listAnimationTime + displayContainer.renderDelta);
progress = (float) listAnimationTime / LISTOPENANIMATIONTIME;
} else {
listAnimationTime -= displayContainer.renderDelta;
if (listAnimationTime <= 0) {
listAnimationTime = 0;
return;
}
progress = (float) listAnimationTime / LISTCLOSEANIMATIONTIME;
}
listItemHeight = (int) (this.listItemHeight * progress);
listHeight = (int) (this.listHeight * progress);
final int borderRadius = 6;
float blackAlphaA = Colors.BLACK_ALPHA_85.a;
float whiteA = COL_WHITE.a;
float bgA = COL_BG.a;
COL_WHITE.a *= progress;
COL_BG.a *= progress;
Colors.BLACK_ALPHA_85.a *= progress;
g.setColor(Colors.BLACK_ALPHA_85);
g.fillRoundRect(listStartX, listStartY, listWidth, listHeight, borderRadius, 15);
Object[] listItems = selectedListOption.getListItems();
if (listHoverIndex != -1) {
g.setColor(COL_COMBOBOX_HOVER);
if (listHoverIndex == 0) {
g.fillRoundRect(listStartX, listStartY + listHoverIndex * listItemHeight, listWidth, listItemHeight, borderRadius, 15);
g.fillRect(listStartX, listStartY + listHoverIndex * listItemHeight + listItemHeight / 2, listWidth, listItemHeight / 2);
} else if (listHoverIndex == listItems.length - 1) {
g.fillRoundRect(listStartX, listStartY + listHoverIndex * listItemHeight, listWidth, listItemHeight, borderRadius, 15);
g.fillRect(listStartX, listStartY + listHoverIndex * listItemHeight, listWidth, listItemHeight / 2);
} else {
g.fillRect(listStartX, listStartY + listHoverIndex * listItemHeight, listWidth, listItemHeight);
}
chevronRightImg.draw(listStartX + 2, listStartY + listHoverIndex * listItemHeight + (listItemHeight - controlImageSize) / 2, COL_BG);
}
int y = listStartY;
String selectedValue = selectedListOption.getValueString();
for (Object item : listItems) {
String text = item.toString();
Font font;
if (text.equals(selectedValue)) {
font = Fonts.MEDIUMBOLD;
} else {
font = Fonts.MEDIUM;
}
font.drawString(listStartX + 20, y - Fonts.MEDIUM.getLineHeight() * 0.05f, item.toString(), COL_WHITE);
y += listItemHeight;
}
COL_BG.a = bgA;
COL_WHITE.a = whiteA;
Colors.BLACK_ALPHA_85.a = blackAlphaA;
} }
private void renderOption(Graphics g, GameOption option, int y) { private void renderOption(Graphics g, GameOption option, int y) {
OptionType type = option.getType(); OptionType type = option.getType();
Object[] listItems = option.getListItems(); Object[] listItems = option.getListItems();
if (listItems != null) { if (listItems != null) {
renderListOption(g, option, y, listItems); renderListOption(g, option, y);
} else if (type == OptionType.BOOLEAN) { } else if (type == OptionType.BOOLEAN) {
renderCheckOption(option, y); renderCheckOption(option, y);
} else if (type == OptionType.NUMERIC) { } else if (type == OptionType.NUMERIC) {
@ -405,33 +383,30 @@ public class OptionsOverlay extends OverlayOpsuState {
} }
} }
private void renderListOption(Graphics g, GameOption option, int y, Object[] listItems) { private void renderListOption(Graphics g, GameOption option, int y) {
int nameLen = Fonts.MEDIUM.getWidth(option.getName()); // draw option name
int nameWith = Fonts.MEDIUM.getWidth(option.getName());
Fonts.MEDIUM.drawString(optionStartX, y + optionTextOffsetY, option.getName(), COL_WHITE); Fonts.MEDIUM.drawString(optionStartX, y + optionTextOffsetY, option.getName(), COL_WHITE);
final int padding = (int) (optionHeight / 10f); nameWith += 15;
nameLen += 15; int comboboxStartX = optionStartX + nameWith;
final int comboboxStartX = optionStartX + nameLen; int comboboxWidth = optionWidth - nameWith;
final int comboboxWidth = optionWidth - nameLen; if (comboboxWidth < controlImageSize) {
final int borderRadius = 6;
if (comboboxWidth <= controlImageSize) {
return; return;
} }
Color backColor = COL_BG; DropdownMenu<Object> dropdown = dropdownMenus.get(option);
if (hoverOption == option if (dropdown == null) {
&& comboboxStartX <= displayContainer.mouseX && displayContainer.mouseX < comboboxStartX + comboboxWidth return;
&& y + padding <= displayContainer.mouseY && displayContainer.mouseY < y + padding + listItemHeight) {
backColor = COL_COMBOBOX_HOVER;
} }
g.setColor(backColor); visibleDropdownMenus.add(dropdown);
g.fillRoundRect(comboboxStartX, y + padding, comboboxWidth, listItemHeight, borderRadius, 15); dropdown.setWidth(comboboxWidth);
Fonts.MEDIUM.drawString(comboboxStartX + 4, y + optionTextOffsetY, option.getValueString(), COL_WHITE); dropdown.x = comboboxStartX;
chevronDownImg.draw(width - paddingRight - controlImageSize / 3 - controlImageSize, y + controlImagePadding, COL_WHITE); dropdown.y = y + dropdownMenuPaddingY;
if (isListOptionOpen && hoverOption == option) { if (dropdown.isOpen()) {
listStartX = comboboxStartX; openDropdownMenu = dropdown;
listStartY = y + optionHeight; openDropdownVirtualY = maxScrollOffset;
listWidth = comboboxWidth; return;
listHeight = listItems.length * listItemHeight;
} }
dropdown.render(g);
} }
private void renderCheckOption(GameOption option, int y) { private void renderCheckOption(GameOption option, int y) {
@ -538,6 +513,14 @@ public class OptionsOverlay extends OverlayOpsuState {
scrollHandler.update(delta); scrollHandler.update(delta);
if (openDropdownMenu == null) {
for (DropdownMenu<Object> menu : visibleDropdownMenus) {
menu.updateHover(mouseX, mouseY);
}
} else {
openDropdownMenu.updateHover(mouseX, mouseY);
}
updateShowHideAnimation(delta); updateShowHideAnimation(delta);
if (animationtime <= 0) { if (animationtime <= 0) {
active = false; active = false;
@ -548,14 +531,6 @@ public class OptionsOverlay extends OverlayOpsuState {
sliderSoundDelay -= delta; sliderSoundDelay -= delta;
} }
if (isListOptionOpen) {
if (listStartX <= mouseX && mouseX < listStartX + listWidth && listStartY <= mouseY && mouseY < listStartY + listHeight) {
listHoverIndex = (mouseY - listStartY) / listItemHeight;
} else {
listHoverIndex = -1;
}
}
if (mouseX - prevMouseX == 0 && mouseY - prevMouseY == 0) { if (mouseX - prevMouseX == 0 && mouseY - prevMouseY == 0) {
updateIndicatorAlpha(); updateIndicatorAlpha();
return; return;
@ -635,23 +610,6 @@ public class OptionsOverlay extends OverlayOpsuState {
return true; return true;
} }
if (isListOptionOpen) {
if (listStartX <= x && x < listStartX + listWidth && listStartY <= y && y < listStartY + listHeight) {
if (0 <= listHoverIndex && listHoverIndex < hoverOption.getListItems().length) {
hoverOption.clickListItem(listHoverIndex);
if (listener != null) {
listener.onSaveOption(hoverOption);
}
}
SoundController.playSound(SoundEffect.MENUCLICK);
}
isListOptionOpen = false;
listAnimationTime = LISTCLOSEANIMATIONTIME;
listHoverIndex = -1;
updateHoverOption(x, y);
return false;
}
if (x > width) { if (x > width) {
return false; return false;
} }
@ -661,16 +619,10 @@ public class OptionsOverlay extends OverlayOpsuState {
mousePressY = y; mousePressY = y;
selectedOption = hoverOption; selectedOption = hoverOption;
if (hoverOption != null) { if (hoverOption != null && hoverOption.getType() == OptionType.NUMERIC) {
if (hoverOption.getListItems() != null) { isAdjustingSlider = sliderOptionStartX <= x && x < sliderOptionStartX + sliderOptionLength;
isListOptionOpen = true; if (isAdjustingSlider) {
selectedListOption = hoverOption; updateSliderOption();
listAnimationTime = 0;
} else if (hoverOption.getType() == OptionType.NUMERIC) {
isAdjustingSlider = sliderOptionStartX <= x && x < sliderOptionStartX + sliderOptionLength;
if (isAdjustingSlider) {
updateSliderOption();
}
} }
} }
@ -686,6 +638,19 @@ public class OptionsOverlay extends OverlayOpsuState {
isAdjustingSlider = false; isAdjustingSlider = false;
sliderOptionLength = 0; sliderOptionLength = 0;
if (openDropdownMenu != null) {
openDropdownMenu.mouseReleased(button);
updateHoverOption(x, y);
return true;
} else {
for (DropdownMenu<Object> menu : visibleDropdownMenus) {
menu.mouseReleased(button);
if (menu.isOpen()) {
return true;
}
}
}
scrollHandler.released(); scrollHandler.released();
// check if clicked, not dragged // check if clicked, not dragged
@ -712,20 +677,6 @@ public class OptionsOverlay extends OverlayOpsuState {
} }
} }
/*
// check if tab was clicked
int tScrollOffset = 0;
for (OptionTab tab : tabs) {
if (tab.button.contains(x, y)) {
scrollOffset = tScrollOffset;
SoundController.playSound(SoundEffect.MENUCLICK);
return true;
}
tScrollOffset += Fonts.MEDIUM.getLineHeight() * 2;
tScrollOffset += tab.options.length * optionHeight;
}
*/
if (UI.getBackButton().contains(x, y)){ if (UI.getBackButton().contains(x, y)){
hide(); hide();
if (listener != null) { if (listener != null) {
@ -770,10 +721,8 @@ public class OptionsOverlay extends OverlayOpsuState {
} }
if (key == Input.KEY_ESCAPE) { if (key == Input.KEY_ESCAPE) {
if (isListOptionOpen) { if (openDropdownMenu != null) {
isListOptionOpen = false; openDropdownMenu.keyPressed(key, c);
listAnimationTime = LISTCLOSEANIMATIONTIME;
listHoverIndex = -1;
return true; return true;
} }
if (lastSearchText.length() != 0) { if (lastSearchText.length() != 0) {
@ -810,7 +759,7 @@ public class OptionsOverlay extends OverlayOpsuState {
} }
private void updateHoverOption(int mouseX, int mouseY) { private void updateHoverOption(int mouseX, int mouseY) {
if (isListOptionOpen || keyEntryLeft || keyEntryRight) { if (openDropdownMenu != null || keyEntryLeft || keyEntryRight) {
return; return;
} }
if (selectedOption != null) { if (selectedOption != null) {