Added a dropdown menu class, similar to the one in osu!.
Using 'fa-chevron-down' and 'fa-chevron-right' icons from Font Awesome v4.4.0 (https://github.com/FortAwesome/Font-Awesome). The dropdown menu is currently used only in the downloads menu to select servers. Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
parent
6c369f6329
commit
643a7c4589
BIN
res/chevron-down.png
Normal file
BIN
res/chevron-down.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
res/chevron-right.png
Normal file
BIN
res/chevron-right.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -310,6 +310,7 @@ public enum GameImage {
|
||||||
MUSIC_PAUSE ("music-pause", "png", false, false),
|
MUSIC_PAUSE ("music-pause", "png", false, false),
|
||||||
MUSIC_NEXT ("music-next", "png", false, false),
|
MUSIC_NEXT ("music-next", "png", false, false),
|
||||||
MUSIC_PREVIOUS ("music-previous", "png", false, false),
|
MUSIC_PREVIOUS ("music-previous", "png", false, false),
|
||||||
|
|
||||||
DOWNLOADS ("downloads", "png", false, false) {
|
DOWNLOADS ("downloads", "png", false, false) {
|
||||||
@Override
|
@Override
|
||||||
protected Image process_sub(Image img, int w, int h) {
|
protected Image process_sub(Image img, int w, int h) {
|
||||||
|
@ -361,6 +362,8 @@ public enum GameImage {
|
||||||
return img.getScaledCopy(w, h);
|
return img.getScaledCopy(w, h);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
CHEVRON_DOWN ("chevron-down", "png", false, false),
|
||||||
|
CHEVRON_RIGHT ("chevron-right", "png", false, false),
|
||||||
|
|
||||||
// TODO: ensure this image hasn't been modified (checksum?)
|
// TODO: ensure this image hasn't been modified (checksum?)
|
||||||
ALPHA_MAP ("alpha", "png", false, false);
|
ALPHA_MAP ("alpha", "png", false, false);
|
||||||
|
|
|
@ -75,4 +75,7 @@ public abstract class DownloadServer {
|
||||||
public String getPreviewURL(int beatmapSetID) {
|
public String getPreviewURL(int beatmapSetID) {
|
||||||
return String.format(PREVIEW_URL, beatmapSetID);
|
return String.format(PREVIEW_URL, beatmapSetID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() { return getName(); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import itdelatrisu.opsu.downloads.servers.MengSkyServer;
|
||||||
import itdelatrisu.opsu.downloads.servers.MnetworkServer;
|
import itdelatrisu.opsu.downloads.servers.MnetworkServer;
|
||||||
import itdelatrisu.opsu.downloads.servers.YaSOnlineServer;
|
import itdelatrisu.opsu.downloads.servers.YaSOnlineServer;
|
||||||
import itdelatrisu.opsu.ui.Colors;
|
import itdelatrisu.opsu.ui.Colors;
|
||||||
|
import itdelatrisu.opsu.ui.DropdownMenu;
|
||||||
import itdelatrisu.opsu.ui.Fonts;
|
import itdelatrisu.opsu.ui.Fonts;
|
||||||
import itdelatrisu.opsu.ui.MenuButton;
|
import itdelatrisu.opsu.ui.MenuButton;
|
||||||
import itdelatrisu.opsu.ui.UI;
|
import itdelatrisu.opsu.ui.UI;
|
||||||
|
@ -83,9 +84,6 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
new MnetworkServer(), new MengSkyServer()
|
new MnetworkServer(), new MengSkyServer()
|
||||||
};
|
};
|
||||||
|
|
||||||
/** The beatmap download server index. */
|
|
||||||
private int serverIndex = 0;
|
|
||||||
|
|
||||||
/** The current list of search results. */
|
/** The current list of search results. */
|
||||||
private DownloadNode[] resultList;
|
private DownloadNode[] resultList;
|
||||||
|
|
||||||
|
@ -147,7 +145,10 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
private MenuButton prevPage, nextPage;
|
private MenuButton prevPage, nextPage;
|
||||||
|
|
||||||
/** Buttons. */
|
/** Buttons. */
|
||||||
private MenuButton clearButton, importButton, resetButton, rankedButton, serverButton;
|
private MenuButton clearButton, importButton, resetButton, rankedButton;
|
||||||
|
|
||||||
|
/** Dropdown menu. */
|
||||||
|
private DropdownMenu<DownloadServer> serverMenu;
|
||||||
|
|
||||||
/** Beatmap importing thread. */
|
/** Beatmap importing thread. */
|
||||||
private Thread importThread;
|
private Thread importThread;
|
||||||
|
@ -297,7 +298,6 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
float buttonHeight = height * 0.038f;
|
float buttonHeight = height * 0.038f;
|
||||||
float resetWidth = width * 0.085f;
|
float resetWidth = width * 0.085f;
|
||||||
float rankedWidth = width * 0.15f;
|
float rankedWidth = width * 0.15f;
|
||||||
float serverWidth = width * 0.12f;
|
|
||||||
float lowerWidth = width * 0.12f;
|
float lowerWidth = width * 0.12f;
|
||||||
float topButtonY = searchY + Fonts.MEDIUM.getLineHeight() / 2f;
|
float topButtonY = searchY + Fonts.MEDIUM.getLineHeight() / 2f;
|
||||||
float lowerButtonY = height * 0.995f - searchY - buttonHeight / 2f;
|
float lowerButtonY = height * 0.995f - searchY - buttonHeight / 2f;
|
||||||
|
@ -309,11 +309,9 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
int lrButtonWidth = buttonL.getWidth() + buttonR.getWidth();
|
int lrButtonWidth = buttonL.getWidth() + buttonR.getWidth();
|
||||||
Image resetButtonImage = button.getScaledCopy((int) resetWidth - lrButtonWidth, (int) buttonHeight);
|
Image resetButtonImage = button.getScaledCopy((int) resetWidth - lrButtonWidth, (int) buttonHeight);
|
||||||
Image rankedButtonImage = button.getScaledCopy((int) rankedWidth - lrButtonWidth, (int) buttonHeight);
|
Image rankedButtonImage = button.getScaledCopy((int) rankedWidth - lrButtonWidth, (int) buttonHeight);
|
||||||
Image serverButtonImage = button.getScaledCopy((int) serverWidth - lrButtonWidth, (int) buttonHeight);
|
|
||||||
Image lowerButtonImage = button.getScaledCopy((int) lowerWidth - lrButtonWidth, (int) buttonHeight);
|
Image lowerButtonImage = button.getScaledCopy((int) lowerWidth - lrButtonWidth, (int) buttonHeight);
|
||||||
float resetButtonWidth = resetButtonImage.getWidth() + lrButtonWidth;
|
float resetButtonWidth = resetButtonImage.getWidth() + lrButtonWidth;
|
||||||
float rankedButtonWidth = rankedButtonImage.getWidth() + lrButtonWidth;
|
float rankedButtonWidth = rankedButtonImage.getWidth() + lrButtonWidth;
|
||||||
float serverButtonWidth = serverButtonImage.getWidth() + lrButtonWidth;
|
|
||||||
float lowerButtonWidth = lowerButtonImage.getWidth() + lrButtonWidth;
|
float lowerButtonWidth = lowerButtonImage.getWidth() + lrButtonWidth;
|
||||||
clearButton = new MenuButton(lowerButtonImage, buttonL, buttonR,
|
clearButton = new MenuButton(lowerButtonImage, buttonL, buttonR,
|
||||||
width * 0.75f + buttonMarginX + lowerButtonWidth / 2f, lowerButtonY);
|
width * 0.75f + buttonMarginX + lowerButtonWidth / 2f, lowerButtonY);
|
||||||
|
@ -323,8 +321,6 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
baseX + searchWidth + buttonMarginX + resetButtonWidth / 2f, topButtonY);
|
baseX + searchWidth + buttonMarginX + resetButtonWidth / 2f, topButtonY);
|
||||||
rankedButton = new MenuButton(rankedButtonImage, buttonL, buttonR,
|
rankedButton = new MenuButton(rankedButtonImage, buttonL, buttonR,
|
||||||
baseX + searchWidth + buttonMarginX * 2f + resetButtonWidth + rankedButtonWidth / 2f, topButtonY);
|
baseX + searchWidth + buttonMarginX * 2f + resetButtonWidth + rankedButtonWidth / 2f, topButtonY);
|
||||||
serverButton = new MenuButton(serverButtonImage, buttonL, buttonR,
|
|
||||||
baseX + searchWidth + buttonMarginX * 3f + resetButtonWidth + rankedButtonWidth + serverButtonWidth / 2f, topButtonY);
|
|
||||||
clearButton.setText("Clear", Fonts.MEDIUM, Color.white);
|
clearButton.setText("Clear", Fonts.MEDIUM, Color.white);
|
||||||
importButton.setText("Import All", Fonts.MEDIUM, Color.white);
|
importButton.setText("Import All", Fonts.MEDIUM, Color.white);
|
||||||
resetButton.setText("Reset", Fonts.MEDIUM, Color.white);
|
resetButton.setText("Reset", Fonts.MEDIUM, Color.white);
|
||||||
|
@ -332,7 +328,14 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
importButton.setHoverFade();
|
importButton.setHoverFade();
|
||||||
resetButton.setHoverFade();
|
resetButton.setHoverFade();
|
||||||
rankedButton.setHoverFade();
|
rankedButton.setHoverFade();
|
||||||
serverButton.setHoverFade();
|
|
||||||
|
// dropdown menu
|
||||||
|
int serverWidth = (int) (width * 0.12f);
|
||||||
|
serverMenu = new DropdownMenu<DownloadServer>(SERVERS,
|
||||||
|
baseX + searchWidth + buttonMarginX * 3f + resetButtonWidth + rankedButtonWidth, searchY, serverWidth);
|
||||||
|
serverMenu.setBackgroundColor(Colors.BLACK_BG_HOVER);
|
||||||
|
serverMenu.setBorderColor(Color.black);
|
||||||
|
serverMenu.setChevronRightColor(Color.white);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -341,6 +344,7 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
int width = container.getWidth();
|
int width = container.getWidth();
|
||||||
int height = container.getHeight();
|
int height = container.getHeight();
|
||||||
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
|
||||||
|
boolean inDropdownMenu = serverMenu.contains(mouseX, mouseY);
|
||||||
|
|
||||||
// background
|
// background
|
||||||
GameImage.SEARCH_BG.getImage().draw();
|
GameImage.SEARCH_BG.getImage().draw();
|
||||||
|
@ -366,7 +370,7 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
int index = startResult + i;
|
int index = startResult + i;
|
||||||
if (index >= nodes.length)
|
if (index >= nodes.length)
|
||||||
break;
|
break;
|
||||||
nodes[index].drawResult(g, i, DownloadNode.resultContains(mouseX, mouseY, i),
|
nodes[index].drawResult(g, i, DownloadNode.resultContains(mouseX, mouseY, i) && !inDropdownMenu,
|
||||||
(index == focusResult), (previewID == nodes[index].getID()));
|
(index == focusResult), (previewID == nodes[index].getID()));
|
||||||
}
|
}
|
||||||
g.clearClip();
|
g.clearClip();
|
||||||
|
@ -422,8 +426,9 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
resetButton.draw(Color.red);
|
resetButton.draw(Color.red);
|
||||||
rankedButton.setText((rankedOnly) ? "Show Unranked" : "Hide Unranked", Fonts.MEDIUM, Color.white);
|
rankedButton.setText((rankedOnly) ? "Show Unranked" : "Hide Unranked", Fonts.MEDIUM, Color.white);
|
||||||
rankedButton.draw(Color.magenta);
|
rankedButton.draw(Color.magenta);
|
||||||
serverButton.setText(SERVERS[serverIndex].getName(), Fonts.MEDIUM, Color.white);
|
|
||||||
serverButton.draw(Color.blue);
|
// dropdown menu
|
||||||
|
serverMenu.draw(g, mouseX, mouseY);
|
||||||
|
|
||||||
// importing beatmaps
|
// importing beatmaps
|
||||||
if (importThread != null) {
|
if (importThread != null) {
|
||||||
|
@ -455,7 +460,7 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
importButton.hoverUpdate(delta, mouseX, mouseY);
|
importButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
resetButton.hoverUpdate(delta, mouseX, mouseY);
|
resetButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
rankedButton.hoverUpdate(delta, mouseX, mouseY);
|
rankedButton.hoverUpdate(delta, mouseX, mouseY);
|
||||||
serverButton.hoverUpdate(delta, mouseX, mouseY);
|
serverMenu.update(delta);
|
||||||
|
|
||||||
// focus timer
|
// focus timer
|
||||||
if (focusResult != -1 && focusTimer < FOCUS_DELAY)
|
if (focusResult != -1 && focusTimer < FOCUS_DELAY)
|
||||||
|
@ -469,7 +474,7 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
searchTimerReset = false;
|
searchTimerReset = false;
|
||||||
|
|
||||||
String query = search.getText().trim().toLowerCase();
|
String query = search.getText().trim().toLowerCase();
|
||||||
DownloadServer server = SERVERS[serverIndex];
|
DownloadServer server = serverMenu.getSelectedItem();
|
||||||
if ((lastQuery == null || !query.equals(lastQuery)) &&
|
if ((lastQuery == null || !query.equals(lastQuery)) &&
|
||||||
(query.length() == 0 || query.length() >= server.minQueryLength())) {
|
(query.length() == 0 || query.length() >= server.minQueryLength())) {
|
||||||
lastQuery = query;
|
lastQuery = query;
|
||||||
|
@ -493,7 +498,7 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
UI.updateTooltip(delta, "Reset the current search.", false);
|
UI.updateTooltip(delta, "Reset the current search.", false);
|
||||||
else if (rankedButton.contains(mouseX, mouseY))
|
else if (rankedButton.contains(mouseX, mouseY))
|
||||||
UI.updateTooltip(delta, "Toggle the display of unranked maps.\nSome download servers may not support this option.", true);
|
UI.updateTooltip(delta, "Toggle the display of unranked maps.\nSome download servers may not support this option.", true);
|
||||||
else if (serverButton.contains(mouseX, mouseY))
|
else if (serverMenu.baseContains(mouseX, mouseY))
|
||||||
UI.updateTooltip(delta, "Select a download server.", false);
|
UI.updateTooltip(delta, "Select a download server.", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,6 +523,27 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dropdown menu
|
||||||
|
if (serverMenu.click(x, y)) {
|
||||||
|
SoundController.playSound(SoundEffect.MENUCLICK);
|
||||||
|
resultList = null;
|
||||||
|
startResult = 0;
|
||||||
|
focusResult = -1;
|
||||||
|
totalResults = 0;
|
||||||
|
page = 0;
|
||||||
|
pageResultTotal = 1;
|
||||||
|
pageDir = Page.RESET;
|
||||||
|
searchResultString = "Loading data from server...";
|
||||||
|
lastQuery = null;
|
||||||
|
pageDir = Page.RESET;
|
||||||
|
if (searchQuery != null)
|
||||||
|
searchQuery.interrupt();
|
||||||
|
resetSearchTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (serverMenu.contains(x, y))
|
||||||
|
return;
|
||||||
|
|
||||||
// search results
|
// search results
|
||||||
DownloadNode[] nodes = resultList;
|
DownloadNode[] nodes = resultList;
|
||||||
if (nodes != null) {
|
if (nodes != null) {
|
||||||
|
@ -549,7 +575,7 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
} else {
|
} else {
|
||||||
// play preview
|
// play preview
|
||||||
try {
|
try {
|
||||||
final URL url = new URL(SERVERS[serverIndex].getPreviewURL(node.getID()));
|
final URL url = new URL(serverMenu.getSelectedItem().getPreviewURL(node.getID()));
|
||||||
MusicController.pause();
|
MusicController.pause();
|
||||||
new Thread() {
|
new Thread() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -593,7 +619,7 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
} else {
|
} else {
|
||||||
// start download
|
// start download
|
||||||
if (!DownloadList.get().contains(node.getID())) {
|
if (!DownloadList.get().contains(node.getID())) {
|
||||||
node.createDownload(SERVERS[serverIndex]);
|
node.createDownload(serverMenu.getSelectedItem());
|
||||||
if (node.getDownload() == null)
|
if (node.getDownload() == null)
|
||||||
UI.sendBarNotification("The download could not be started.");
|
UI.sendBarNotification("The download could not be started.");
|
||||||
else {
|
else {
|
||||||
|
@ -704,24 +730,6 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
resetSearchTimer();
|
resetSearchTimer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (serverButton.contains(x, y)) {
|
|
||||||
SoundController.playSound(SoundEffect.MENUCLICK);
|
|
||||||
resultList = null;
|
|
||||||
startResult = 0;
|
|
||||||
focusResult = -1;
|
|
||||||
totalResults = 0;
|
|
||||||
page = 0;
|
|
||||||
pageResultTotal = 1;
|
|
||||||
pageDir = Page.RESET;
|
|
||||||
searchResultString = "Loading data from server...";
|
|
||||||
serverIndex = (serverIndex + 1) % SERVERS.length;
|
|
||||||
lastQuery = null;
|
|
||||||
pageDir = Page.RESET;
|
|
||||||
if (searchQuery != null)
|
|
||||||
searchQuery.interrupt();
|
|
||||||
resetSearchTimer();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// downloads
|
// downloads
|
||||||
if (!DownloadList.get().isEmpty() && DownloadNode.downloadAreaContains(x, y)) {
|
if (!DownloadList.get().isEmpty() && DownloadNode.downloadAreaContains(x, y)) {
|
||||||
|
@ -855,7 +863,7 @@ public class DownloadsMenu extends BasicGameState {
|
||||||
importButton.resetHover();
|
importButton.resetHover();
|
||||||
resetButton.resetHover();
|
resetButton.resetHover();
|
||||||
rankedButton.resetHover();
|
rankedButton.resetHover();
|
||||||
serverButton.resetHover();
|
serverMenu.reset();
|
||||||
focusResult = -1;
|
focusResult = -1;
|
||||||
startResult = 0;
|
startResult = 0;
|
||||||
startDownloadIndex = 0;
|
startDownloadIndex = 0;
|
||||||
|
|
364
src/itdelatrisu/opsu/ui/DropdownMenu.java
Normal file
364
src/itdelatrisu/opsu/ui/DropdownMenu.java
Normal file
|
@ -0,0 +1,364 @@
|
||||||
|
/*
|
||||||
|
* opsu! - an open-source osu! client
|
||||||
|
* Copyright (C) 2014, 2015 Jeffrey Han
|
||||||
|
*
|
||||||
|
* opsu! 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! 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!. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package itdelatrisu.opsu.ui;
|
||||||
|
|
||||||
|
import itdelatrisu.opsu.GameImage;
|
||||||
|
import itdelatrisu.opsu.ui.animations.AnimatedValue;
|
||||||
|
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
||||||
|
|
||||||
|
import org.newdawn.slick.Color;
|
||||||
|
import org.newdawn.slick.Font;
|
||||||
|
import org.newdawn.slick.Graphics;
|
||||||
|
import org.newdawn.slick.Image;
|
||||||
|
import org.newdawn.slick.UnicodeFont;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple dropdown menu.
|
||||||
|
*/
|
||||||
|
public class DropdownMenu<E> {
|
||||||
|
/** Padding ratios for drawing. */
|
||||||
|
private static final float PADDING_Y = 0.1f, CHEVRON_X = 0.03f;
|
||||||
|
|
||||||
|
/** The menu items. */
|
||||||
|
private E[] items;
|
||||||
|
|
||||||
|
/** The menu item names. */
|
||||||
|
private String[] itemNames;
|
||||||
|
|
||||||
|
/** The index of the selected item. */
|
||||||
|
private int itemIndex = 0;
|
||||||
|
|
||||||
|
/** Whether the menu is expanded. */
|
||||||
|
private boolean expanded = false;
|
||||||
|
|
||||||
|
/** The expanding animation progress. */
|
||||||
|
private AnimatedValue expandProgress = new AnimatedValue(300, 0f, 1f, AnimationEquation.LINEAR);
|
||||||
|
|
||||||
|
/** The top-left coordinates. */
|
||||||
|
private float x, y;
|
||||||
|
|
||||||
|
/** The width and height of the dropdown menu. */
|
||||||
|
private int width, height;
|
||||||
|
|
||||||
|
/** The height of the base item. */
|
||||||
|
private int baseHeight;
|
||||||
|
|
||||||
|
/** The vertical offset between items. */
|
||||||
|
private float offsetY;
|
||||||
|
|
||||||
|
/** The colors to use. */
|
||||||
|
private Color
|
||||||
|
textColor = Color.white, backgroundColor = Color.black,
|
||||||
|
highlightColor = Colors.BLUE_DIVIDER, borderColor = Colors.BLUE_DIVIDER,
|
||||||
|
chevronDownColor = textColor, chevronRightColor = backgroundColor;
|
||||||
|
|
||||||
|
/** The fonts to use. */
|
||||||
|
private UnicodeFont fontNormal = Fonts.MEDIUM, fontSelected = Fonts.MEDIUMBOLD;
|
||||||
|
|
||||||
|
/** The chevron images. */
|
||||||
|
private Image chevronDown, chevronRight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new dropdown menu.
|
||||||
|
* @param items the list of items (with names given as their {@code toString()} methods)
|
||||||
|
* @param x the top-left x coordinate
|
||||||
|
* @param y the top-left y coordinate
|
||||||
|
*/
|
||||||
|
public DropdownMenu(E[] items, float x, float y) {
|
||||||
|
this(items, x, y, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new dropdown menu with the given fonts.
|
||||||
|
* @param items the list of items (with names given as their {@code toString()} methods)
|
||||||
|
* @param x the top-left x coordinate
|
||||||
|
* @param y the top-left y coordinate
|
||||||
|
* @param normal the normal font
|
||||||
|
* @param selected the font for the selected item
|
||||||
|
*/
|
||||||
|
public DropdownMenu(E[] items, float x, float y, UnicodeFont normal, UnicodeFont selected) {
|
||||||
|
this(items, x, y, 0, normal, selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new dropdown menu with the given width.
|
||||||
|
* @param items the list of items (with names given as their {@code toString()} methods)
|
||||||
|
* @param x the top-left x coordinate
|
||||||
|
* @param y the top-left y coordinate
|
||||||
|
* @param width the menu width
|
||||||
|
*/
|
||||||
|
public DropdownMenu(E[] items, float x, float y, int width) {
|
||||||
|
init(items, x, y, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new dropdown menu with the given width and fonts.
|
||||||
|
* @param items the list of items (with names given as their {@code toString()} methods)
|
||||||
|
* @param x the top-left x coordinate
|
||||||
|
* @param y the top-left y coordinate
|
||||||
|
* @param width the menu width
|
||||||
|
* @param normal the normal font
|
||||||
|
* @param selected the font for the selected item
|
||||||
|
*/
|
||||||
|
public DropdownMenu(E[] items, float x, float y, int width, UnicodeFont normal, UnicodeFont selected) {
|
||||||
|
this.fontNormal = normal;
|
||||||
|
this.fontSelected = selected;
|
||||||
|
init(items, x, y, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum item width from the list.
|
||||||
|
*/
|
||||||
|
private int getMaxItemWidth() {
|
||||||
|
int maxWidth = 0;
|
||||||
|
for (int i = 0; i < itemNames.length; i++) {
|
||||||
|
int w = fontSelected.getWidth(itemNames[i]);
|
||||||
|
if (w > maxWidth)
|
||||||
|
maxWidth = w;
|
||||||
|
}
|
||||||
|
return maxWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the component.
|
||||||
|
*/
|
||||||
|
private void init(E[] items, float x, float y, int width) {
|
||||||
|
this.items = items;
|
||||||
|
this.itemNames = new String[items.length];
|
||||||
|
for (int i = 0; i < itemNames.length; i++)
|
||||||
|
itemNames[i] = items[i].toString();
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.baseHeight = fontNormal.getLineHeight();
|
||||||
|
this.offsetY = baseHeight + baseHeight * PADDING_Y;
|
||||||
|
this.height = (int) (offsetY * (items.length + 1));
|
||||||
|
int chevronDownSize = baseHeight * 4 / 5;
|
||||||
|
this.chevronDown = GameImage.CHEVRON_DOWN.getImage().getScaledCopy(chevronDownSize, chevronDownSize);
|
||||||
|
int chevronRightSize = baseHeight * 2 / 3;
|
||||||
|
this.chevronRight = GameImage.CHEVRON_RIGHT.getImage().getScaledCopy(chevronRightSize, chevronRightSize);
|
||||||
|
int maxItemWidth = getMaxItemWidth();
|
||||||
|
int minWidth = maxItemWidth + chevronRight.getWidth() * 2;
|
||||||
|
this.width = Math.max(width, minWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the width of the menu.
|
||||||
|
*/
|
||||||
|
public int getWidth() { return width; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the height of the base item.
|
||||||
|
*/
|
||||||
|
public int getHeight() { return baseHeight; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the dropdown menu is currently open.
|
||||||
|
* @return true if open, false otherwise
|
||||||
|
*/
|
||||||
|
public boolean isOpen() { return expanded; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the coordinates are within the menu bounds.
|
||||||
|
* @param cx the x coordinate
|
||||||
|
* @param cy the y coordinate
|
||||||
|
*/
|
||||||
|
public boolean contains(float cx, float cy) {
|
||||||
|
return (cx > x && cx < x + width && (
|
||||||
|
(cy > y && cy < y + baseHeight) ||
|
||||||
|
(expanded && cy > y + offsetY && cy < y + height)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the coordinates are within the base item bounds.
|
||||||
|
* @param cx the x coordinate
|
||||||
|
* @param cy the y coordinate
|
||||||
|
*/
|
||||||
|
public boolean baseContains(float cx, float cy) {
|
||||||
|
return (cx > x && cx < x + width && cy > y && cy < y + baseHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the dropdown menu.
|
||||||
|
* @param g the graphics context
|
||||||
|
* @param cx the mouse x coordinate
|
||||||
|
* @param cy the mouse y coordinate
|
||||||
|
*/
|
||||||
|
public void draw(Graphics g, float cx, float cy) {
|
||||||
|
int idx = getIndexAt(cx, cy);
|
||||||
|
float t = expandProgress.getValue();
|
||||||
|
if (expanded)
|
||||||
|
t = AnimationEquation.OUT_CUBIC.calc(t);
|
||||||
|
|
||||||
|
// background and border
|
||||||
|
Color oldGColor = g.getColor();
|
||||||
|
float oldLineWidth = g.getLineWidth();
|
||||||
|
final int cornerRadius = 6;
|
||||||
|
g.setLineWidth(1f);
|
||||||
|
g.setColor((idx == -1) ? highlightColor : backgroundColor);
|
||||||
|
g.fillRoundRect(x, y, width, baseHeight, cornerRadius);
|
||||||
|
g.setColor(borderColor);
|
||||||
|
g.drawRoundRect(x, y, width, baseHeight, cornerRadius);
|
||||||
|
if (expanded || t >= 0.0001) {
|
||||||
|
float oldBackgroundAlpha = backgroundColor.a;
|
||||||
|
backgroundColor.a *= t;
|
||||||
|
g.setColor(backgroundColor);
|
||||||
|
g.fillRoundRect(x, y + offsetY, width, (height - offsetY) * t, cornerRadius);
|
||||||
|
backgroundColor.a = oldBackgroundAlpha;
|
||||||
|
}
|
||||||
|
if (idx >= 0 && t >= 0.9999) {
|
||||||
|
g.setColor(highlightColor);
|
||||||
|
float yPos = y + offsetY + (offsetY * idx);
|
||||||
|
int yOff = 0, hOff = 0;
|
||||||
|
if (idx == 0 || idx == items.length - 1) {
|
||||||
|
g.fillRoundRect(x, yPos, width, offsetY, cornerRadius);
|
||||||
|
if (idx == 0)
|
||||||
|
yOff = cornerRadius;
|
||||||
|
hOff = cornerRadius;
|
||||||
|
}
|
||||||
|
g.fillRect(x, yPos + yOff, width, offsetY - hOff);
|
||||||
|
}
|
||||||
|
g.setColor(oldGColor);
|
||||||
|
g.setLineWidth(oldLineWidth);
|
||||||
|
|
||||||
|
// text
|
||||||
|
chevronDown.draw(x + width - chevronDown.getWidth() - width * CHEVRON_X, y + (baseHeight - chevronDown.getHeight()) / 2f, chevronDownColor);
|
||||||
|
fontNormal.drawString(x + (width * 0.03f), y + (fontNormal.getPaddingTop() + fontNormal.getPaddingBottom()) / 2f, itemNames[itemIndex], textColor);
|
||||||
|
float oldTextAlpha = textColor.a;
|
||||||
|
textColor.a *= t;
|
||||||
|
if (expanded || t >= 0.0001) {
|
||||||
|
for (int i = 0; i < itemNames.length; i++) {
|
||||||
|
Font f = (i == itemIndex) ? fontSelected : fontNormal;
|
||||||
|
if (i == idx && t >= 0.999)
|
||||||
|
chevronRight.draw(x, y + offsetY + (offsetY * i) + (offsetY - chevronRight.getHeight()) / 2f, chevronRightColor);
|
||||||
|
f.drawString(x + chevronRight.getWidth(), y + offsetY + (offsetY * i * t), itemNames[i], textColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textColor.a = oldTextAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the item at the given location, -1 for the base item,
|
||||||
|
* and -2 if there is no item at the location.
|
||||||
|
* @param cx the x coordinate
|
||||||
|
* @param cy the y coordinate
|
||||||
|
*/
|
||||||
|
private int getIndexAt(float cx, float cy) {
|
||||||
|
if (!contains(cx, cy))
|
||||||
|
return -2;
|
||||||
|
if (cy <= y + baseHeight)
|
||||||
|
return -1;
|
||||||
|
if (!expanded)
|
||||||
|
return -2;
|
||||||
|
return (int) ((cy - (y + offsetY)) / offsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the animations by a delta interval.
|
||||||
|
* @param delta the delta interval since the last call
|
||||||
|
*/
|
||||||
|
public void update(int delta) {
|
||||||
|
expandProgress.update((expanded) ? delta : -delta * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a click at the given location.
|
||||||
|
* If the base item is clicked and the menu is unexpanded, it will be expanded;
|
||||||
|
* in all other cases, the menu will be unexpanded. If an item different from
|
||||||
|
* the current one is selected, this will select that item and return {@code true}.
|
||||||
|
* Otherwise, this will return {@code false}.
|
||||||
|
* @param cx the x coordinate
|
||||||
|
* @param cy the y coordinate
|
||||||
|
*/
|
||||||
|
public boolean click(float cx, float cy) {
|
||||||
|
int idx = getIndexAt(cx, cy);
|
||||||
|
if (idx == -2) {
|
||||||
|
this.expanded = false;
|
||||||
|
return false;
|
||||||
|
} else if (idx == -1) {
|
||||||
|
this.expanded = !expanded;
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this.expanded = false;
|
||||||
|
if (itemIndex == idx)
|
||||||
|
return false;
|
||||||
|
else {
|
||||||
|
itemIndex = idx;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the menu state.
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
expanded = false;
|
||||||
|
expandProgress.setTime(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the item at the given index.
|
||||||
|
* @param index the list item index
|
||||||
|
* @throws IllegalArgumentException if index < -1 or index is greater than or equal to size
|
||||||
|
*/
|
||||||
|
public void setSelectedIndex(int index) {
|
||||||
|
if (index < 0 || index >= items.length)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
this.itemIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the selected item.
|
||||||
|
*/
|
||||||
|
public int getSelectedIndex() { return itemIndex; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the selected item.
|
||||||
|
*/
|
||||||
|
public E getSelectedItem() { return items[itemIndex]; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the item at the given index.
|
||||||
|
* @param index the list item index
|
||||||
|
*/
|
||||||
|
public E getItemAt(int index) { return items[index]; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of items in the list.
|
||||||
|
*/
|
||||||
|
public int getItemCount() { return items.length; }
|
||||||
|
|
||||||
|
/** Sets the text color. */
|
||||||
|
public void setTextColor(Color c) { this.textColor = c; }
|
||||||
|
|
||||||
|
/** Sets the background color. */
|
||||||
|
public void setBackgroundColor(Color c) { this.backgroundColor = c; }
|
||||||
|
|
||||||
|
/** Sets the highlight color. */
|
||||||
|
public void setHighlightColor(Color c) { this.highlightColor = c; }
|
||||||
|
|
||||||
|
/** Sets the border color. */
|
||||||
|
public void setBorderColor(Color c) { this.borderColor = c; }
|
||||||
|
|
||||||
|
/** Sets the down chevron color. */
|
||||||
|
public void setChevronDownColor(Color c) { this.chevronDownColor = c; }
|
||||||
|
|
||||||
|
/** Sets the right chevron color. */
|
||||||
|
public void setChevronRightColor(Color c) { this.chevronRightColor = c; }
|
||||||
|
}
|
|
@ -40,7 +40,7 @@ import org.newdawn.slick.util.ResourceLoader;
|
||||||
* Fonts used for drawing.
|
* Fonts used for drawing.
|
||||||
*/
|
*/
|
||||||
public class Fonts {
|
public class Fonts {
|
||||||
public static UnicodeFont DEFAULT, BOLD, XLARGE, LARGE, MEDIUM, SMALL;
|
public static UnicodeFont DEFAULT, BOLD, XLARGE, LARGE, MEDIUM, MEDIUMBOLD, SMALL;
|
||||||
|
|
||||||
/** Set of all Unicode strings already loaded per font. */
|
/** Set of all Unicode strings already loaded per font. */
|
||||||
private static HashMap<UnicodeFont, HashSet<String>> loadedGlyphs = new HashMap<UnicodeFont, HashSet<String>>();
|
private static HashMap<UnicodeFont, HashSet<String>> loadedGlyphs = new HashMap<UnicodeFont, HashSet<String>>();
|
||||||
|
@ -63,6 +63,7 @@ public class Fonts {
|
||||||
XLARGE = new UnicodeFont(font.deriveFont(fontBase * 3));
|
XLARGE = new UnicodeFont(font.deriveFont(fontBase * 3));
|
||||||
LARGE = new UnicodeFont(font.deriveFont(fontBase * 2));
|
LARGE = new UnicodeFont(font.deriveFont(fontBase * 2));
|
||||||
MEDIUM = new UnicodeFont(font.deriveFont(fontBase * 3 / 2));
|
MEDIUM = new UnicodeFont(font.deriveFont(fontBase * 3 / 2));
|
||||||
|
MEDIUMBOLD = new UnicodeFont(font.deriveFont(Font.BOLD, fontBase * 3 / 2));
|
||||||
SMALL = new UnicodeFont(font.deriveFont(fontBase));
|
SMALL = new UnicodeFont(font.deriveFont(fontBase));
|
||||||
ColorEffect colorEffect = new ColorEffect();
|
ColorEffect colorEffect = new ColorEffect();
|
||||||
loadFont(DEFAULT, colorEffect);
|
loadFont(DEFAULT, colorEffect);
|
||||||
|
@ -70,6 +71,7 @@ public class Fonts {
|
||||||
loadFont(XLARGE, colorEffect);
|
loadFont(XLARGE, colorEffect);
|
||||||
loadFont(LARGE, colorEffect);
|
loadFont(LARGE, colorEffect);
|
||||||
loadFont(MEDIUM, colorEffect);
|
loadFont(MEDIUM, colorEffect);
|
||||||
|
loadFont(MEDIUMBOLD, colorEffect);
|
||||||
loadFont(SMALL, colorEffect);
|
loadFont(SMALL, colorEffect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user