Beatmap downloader improvements and fixes.

Updates:
- Added some buttons to downloads menu: clear inactive downloads, import beatmaps, reset search, and show/hide unranked maps.
- Small changes to OsuParser, OszUnpacker, and OsuGroupList (mostly adding return values) to allow parsing only newly unpacked beatmaps.
- Added alpha fade hover effect to MenuButton, as an alternative to expanding (used for 3-part menu buttons).
- Added text rendering fields to MenuButton (also for the 3-part menu buttons).
- Added sound effects to downloads menu.

Fixes:
- Check downloads for illegal filename characters, and remove them if necessary.
- The number of results and downloads shown now supports all resolutions.
- Confirmation dialog no longer appears when restarting the application (since downloads are static).
- Do not set a focus node immediately if the theme song will be played.
- Always play the theme song if no songs are loaded (even if disabled in settings).

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2015-02-02 00:15:16 -05:00
parent cfc0449ab2
commit ec90d6fd03
20 changed files with 517 additions and 183 deletions

View File

@ -115,7 +115,7 @@ public class Container extends AppGameContainer {
@Override @Override
public void exit() { public void exit() {
// show confirmation dialog if any downloads are active // show confirmation dialog if any downloads are active
if (DownloadList.get().hasActiveDownloads() && DownloadList.showExitConfirmation()) if (forceExit && DownloadList.get().hasActiveDownloads() && DownloadList.showExitConfirmation())
return; return;
super.exit(); super.exit();

View File

@ -441,12 +441,7 @@ public enum GameImage {
return img.getScaledCopy(MENU_LOGO.getImage().getWidth() * 0.66f / img.getWidth()); return img.getScaledCopy(MENU_LOGO.getImage().getWidth() * 0.66f / img.getWidth());
} }
}, },
MENU_BUTTON_MID ("button-middle", "png", false, false) { MENU_BUTTON_MID ("button-middle", "png", false, false),
@Override
protected Image process_sub(Image img, int w, int h) {
return img.getScaledCopy(w / 2, img.getHeight());
}
},
MENU_BUTTON_LEFT ("button-left", "png", false, false), MENU_BUTTON_LEFT ("button-left", "png", false, false),
MENU_BUTTON_RIGHT ("button-right", "png", false, false), MENU_BUTTON_RIGHT ("button-right", "png", false, false),
MUSIC_PLAY ("music-play", "png", false, false), MUSIC_PLAY ("music-play", "png", false, false),

View File

@ -143,7 +143,7 @@ public enum GameMod {
// create button // create button
this.button = new MenuButton(img, x + (offsetX * id), y); this.button = new MenuButton(img, x + (offsetX * id), y);
this.button.setHoverScale(1.15f); this.button.setHoverExpand(1.15f);
// reset state // reset state
this.active = false; this.active = false;
@ -245,10 +245,9 @@ public enum GameMod {
public boolean contains(float x, float y) { return button.contains(x, y); } public boolean contains(float x, float y) { return button.contains(x, y); }
/** /**
* Sets the current button scale (for hovering). * Resets the hover fields for the button.
* @param scale the new scale (default 1.0f)
*/ */
public void setScale(float scale) { button.setScale(scale); } public void resetHover() { button.resetHover(); }
/** /**
* Updates the scale of the button depending on whether or not the cursor * Updates the scale of the button depending on whether or not the cursor

View File

@ -20,6 +20,7 @@ package itdelatrisu.opsu;
import org.newdawn.slick.Animation; import org.newdawn.slick.Animation;
import org.newdawn.slick.Color; import org.newdawn.slick.Color;
import org.newdawn.slick.Font;
import org.newdawn.slick.Image; import org.newdawn.slick.Image;
/** /**
@ -43,8 +44,26 @@ public class MenuButton {
/** The x and y radius of the button (scaled). */ /** The x and y radius of the button (scaled). */
private float xRadius, yRadius; private float xRadius, yRadius;
/** The text to draw on the button. */
private String text;
/** The font to draw the text with. */
private Font font;
/** The color to draw the text with. */
private Color color;
/** Hover action types. */
private enum HoverAction { NONE, EXPAND, FADE };
/** The hover action for this button. */
private HoverAction hoverAction = HoverAction.NONE;
/** The current and max scale of the button (for hovering). */ /** The current and max scale of the button (for hovering). */
private float scale, hoverScale = 1.25f; private float scale = 1f, hoverScale = 1.25f;
/** The current and base alpha level of the button (for hovering). */
private float alpha = 1f, baseAlpha = 0.75f;
/** The scaled expansion direction for the button (for hovering). */ /** The scaled expansion direction for the button (for hovering). */
private Expand dir = Expand.CENTER; private Expand dir = Expand.CENTER;
@ -64,7 +83,6 @@ public class MenuButton {
this.y = y; this.y = y;
this.xRadius = img.getWidth() / 2f; this.xRadius = img.getWidth() / 2f;
this.yRadius = img.getHeight() / 2f; this.yRadius = img.getHeight() / 2f;
this.scale = 1f;
} }
/** /**
@ -83,7 +101,6 @@ public class MenuButton {
this.y = y; this.y = y;
this.xRadius = (img.getWidth() + imgL.getWidth() + imgR.getWidth()) / 2f; this.xRadius = (img.getWidth() + imgL.getWidth() + imgR.getWidth()) / 2f;
this.yRadius = img.getHeight() / 2f; this.yRadius = img.getHeight() / 2f;
this.scale = 1f;
} }
/** /**
@ -98,7 +115,6 @@ public class MenuButton {
this.y = y; this.y = y;
this.xRadius = anim.getWidth() / 2f; this.xRadius = anim.getWidth() / 2f;
this.yRadius = anim.getHeight() / 2f; this.yRadius = anim.getHeight() / 2f;
this.scale = 1f;
} }
/** /**
@ -121,6 +137,18 @@ public class MenuButton {
*/ */
public float getY() { return y; } public float getY() { return y; }
/**
* Sets text to draw in the middle of the button.
* @param text the text to draw
* @param font the font to use when drawing
* @color the color to draw the text
*/
public void setText(String text, Font font, Color color) {
this.text = text;
this.font = font;
this.color = color;
}
/** /**
* Returns the associated image. * Returns the associated image.
*/ */
@ -137,16 +165,39 @@ public class MenuButton {
public void draw() { public void draw() {
if (img != null) { if (img != null) {
if (imgL == null) { if (imgL == null) {
if (hoverAction == HoverAction.EXPAND) {
Image imgScaled = (scale == 1f) ? img : img.getScaledCopy(scale); Image imgScaled = (scale == 1f) ? img : img.getScaledCopy(scale);
imgScaled.setAlpha(img.getAlpha()); imgScaled.setAlpha(img.getAlpha());
imgScaled.draw(x - xRadius, y - yRadius); imgScaled.draw(x - xRadius, y - yRadius);
} else if (hoverAction == HoverAction.FADE) {
float a = img.getAlpha();
img.setAlpha(alpha);
img.draw(x - xRadius, y - yRadius);
img.setAlpha(a);
} else
img.draw(x - xRadius, y - yRadius);
} else {
if (hoverAction == HoverAction.FADE) {
float a = img.getAlpha(), aL = imgL.getAlpha(), aR = imgR.getAlpha();
img.setAlpha(alpha);
imgL.setAlpha(alpha);
imgR.setAlpha(alpha);
img.draw(x - xRadius + imgL.getWidth(), y - yRadius);
imgL.draw(x - xRadius, y - yRadius);
imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius);
img.setAlpha(a);
imgL.setAlpha(aL);
imgR.setAlpha(aR);
} else { } else {
img.draw(x - xRadius + imgL.getWidth(), y - yRadius); img.draw(x - xRadius + imgL.getWidth(), y - yRadius);
imgL.draw(x - xRadius, y - yRadius); imgL.draw(x - xRadius, y - yRadius);
imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius); imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius);
} }
}
} else } else
anim.draw(x - xRadius, y - yRadius); anim.draw(x - xRadius, y - yRadius);
if (text != null)
font.drawString(x - font.getWidth(text) / 2f, y - font.getLineHeight() / 2f, text, color);
} }
/** /**
@ -155,15 +206,40 @@ public class MenuButton {
*/ */
public void draw(Color filter) { public void draw(Color filter) {
if (img != null) { if (img != null) {
if (imgL == null) if (imgL == null) {
img.getScaledCopy(scale).draw(x - xRadius, y - yRadius, filter); if (hoverAction == HoverAction.EXPAND) {
else { Image imgScaled = (scale == 1f) ? img : img.getScaledCopy(scale);
imgScaled.setAlpha(img.getAlpha());
imgScaled.draw(x - xRadius, y - yRadius, filter);
} else if (hoverAction == HoverAction.FADE) {
float a = img.getAlpha();
img.setAlpha(alpha);
img.draw(x - xRadius, y - yRadius, filter);
img.setAlpha(a);
} else
img.draw(x - xRadius, y - yRadius, filter);
} else {
if (hoverAction == HoverAction.FADE) {
float a = img.getAlpha(), aL = imgL.getAlpha(), aR = imgR.getAlpha();
img.setAlpha(alpha);
imgL.setAlpha(alpha);
imgR.setAlpha(alpha);
img.draw(x - xRadius + imgL.getWidth(), y - yRadius, filter);
imgL.draw(x - xRadius, y - yRadius, filter);
imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius, filter);
img.setAlpha(a);
imgL.setAlpha(aL);
imgR.setAlpha(aR);
} else {
img.draw(x - xRadius + imgL.getWidth(), y - yRadius, filter); img.draw(x - xRadius + imgL.getWidth(), y - yRadius, filter);
imgL.draw(x - xRadius, y - yRadius, filter); imgL.draw(x - xRadius, y - yRadius, filter);
imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius, filter); imgR.draw(x + xRadius - imgR.getWidth(), y - yRadius, filter);
} }
}
} else } else
anim.draw(x - xRadius, y - yRadius, filter); anim.draw(x - xRadius, y - yRadius, filter);
if (text != null)
font.drawString(x - font.getWidth(text) / 2f, y - font.getLineHeight() / 2f, text, color);
} }
/** /**
@ -177,45 +253,91 @@ public class MenuButton {
} }
/** /**
* Sets the current button scale (for hovering). * Resets the hover fields for the button.
* @param scale the new scale (default 1.0f)
*/ */
public void setScale(float scale) { public void resetHover() {
this.scale = scale; if (hoverAction == HoverAction.EXPAND) {
this.scale = 1f;
setHoverRadius(); setHoverRadius();
} else if (hoverAction == HoverAction.FADE)
this.alpha = baseAlpha;
} }
/** /**
* Sets the maximum scale factor for the button (for hovering). * Sets the hover action to "expand".
*/
public void setHoverExpand() { this.hoverAction = HoverAction.EXPAND; }
/**
* Sets the hover action to "expand".
* @param scale the maximum scale factor (default 1.25f) * @param scale the maximum scale factor (default 1.25f)
*/ */
public void setHoverScale(float scale) { this.hoverScale = scale; } public void setHoverExpand(float scale) { setHoverExpand(scale, this.dir); }
/** /**
* Sets the expansion direction when hovering over the button. * Sets the hover action to "expand".
* @param dir the direction * @param dir the expansion direction
*/ */
public void setHoverDir(Expand dir) { this.dir = dir; } public void setHoverExpand(Expand dir) { setHoverExpand(this.hoverScale, dir); }
/** /**
* Updates the scale of the button depending on whether or not the cursor * Sets the hover action to "expand".
* @param scale the maximum scale factor (default 1.25f)
* @param dir the expansion direction
*/
public void setHoverExpand(float scale, Expand dir) {
this.hoverAction = HoverAction.EXPAND;
this.hoverScale = scale;
this.dir = dir;
}
/**
* Sets the hover action to "fade".
*/
public void setHoverFade() { this.hoverAction = HoverAction.FADE; }
/**
* Sets the hover action to "fade".
* @param baseAlpha the base alpha level to fade in from (default 0.7f)
*/
public void setHoverFade(float baseAlpha) {
this.hoverAction = HoverAction.FADE;
this.baseAlpha = baseAlpha;
}
/**
* Processes a hover action depending on whether or not the cursor
* is hovering over the button. * is hovering over the button.
* @param delta the delta interval * @param delta the delta interval
* @param cx the x coordinate * @param cx the x coordinate
* @param cy the y coordinate * @param cy the y coordinate
*/ */
public void hoverUpdate(int delta, float cx, float cy) { public void hoverUpdate(int delta, float cx, float cy) {
if (hoverAction == HoverAction.NONE)
return;
boolean isHover = contains(cx, cy); boolean isHover = contains(cx, cy);
if (isHover && scale < hoverScale) { if (hoverAction == HoverAction.EXPAND) {
scale += (hoverScale - 1f) * delta / 100f; // scale the button
if (scale > hoverScale) int sign;
scale = hoverScale; if (isHover && scale < hoverScale)
setHoverRadius(); sign = 1;
} else if (!isHover && scale > 1f) { else if (!isHover && scale > 1f)
scale -= (hoverScale - 1f) * delta / 100f; sign = -1;
if (scale < 1f) else
scale = 1f; return;
scale = Utils.getBoundedValue(scale, sign * (hoverScale - 1f) * delta / 100f, 1, hoverScale);
setHoverRadius(); setHoverRadius();
} else {
// fade the button
int sign;
if (isHover && alpha < 1f)
sign = 1;
else if (!isHover && alpha > baseAlpha)
sign = -1;
else
return;
alpha = Utils.getBoundedValue(alpha, sign * (1f - baseAlpha) * delta / 200f, baseAlpha, 1f);
} }
} }

View File

@ -89,10 +89,13 @@ public class OsuGroupList {
/** /**
* Adds a song group. * Adds a song group.
* @param osuFiles the list of OsuFile objects in the group * @param osuFiles the list of OsuFile objects in the group
* @return the new OsuGroupNode
*/ */
public void addSongGroup(ArrayList<OsuFile> osuFiles) { public OsuGroupNode addSongGroup(ArrayList<OsuFile> osuFiles) {
parsedNodes.add(new OsuGroupNode(osuFiles)); OsuGroupNode node = new OsuGroupNode(osuFiles);
parsedNodes.add(node);
mapCount += osuFiles.size(); mapCount += osuFiles.size();
return node;
} }
/** /**

View File

@ -53,23 +53,38 @@ public class OsuParser {
private OsuParser() {} private OsuParser() {}
/** /**
* Invokes parser for each OSU file in a root directory. * Invokes parser for each OSU file in a root directory and
* adds the OsuFiles to a new OsuGroupList.
* @param root the root directory (search has depth 1) * @param root the root directory (search has depth 1)
*/ */
public static void parseAllFiles(File root) { public static void parseAllFiles(File root) {
// create a new OsuGroupList // create a new OsuGroupList
OsuGroupList.create(); OsuGroupList.create();
// progress tracking // parse all directories
File[] folders = root.listFiles(); parseDirectories(root.listFiles());
currentDirectoryIndex = 0; }
totalDirectories = folders.length;
for (File folder : folders) { /**
* Invokes parser for each directory in the given array and
* adds the OsuFiles to the existing OsuGroupList.
* @param dirs the array of directories to parse
* @return the last OsuGroupNode parsed
*/
public static OsuGroupNode parseDirectories(File[] dirs) {
// progress tracking
currentDirectoryIndex = 0;
totalDirectories = dirs.length;
// parse directories
OsuGroupNode lastNode = null;
for (File dir : dirs) {
currentDirectoryIndex++; currentDirectoryIndex++;
if (!folder.isDirectory()) if (!dir.isDirectory())
continue; continue;
File[] files = folder.listFiles(new FilenameFilter() {
// find all OSU files
File[] files = dir.listFiles(new FilenameFilter() {
@Override @Override
public boolean accept(File dir, String name) { public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".osu"); return name.toLowerCase().endsWith(".osu");
@ -90,10 +105,10 @@ public class OsuParser {
if (!osuFiles.isEmpty()) { // add entry if non-empty if (!osuFiles.isEmpty()) { // add entry if non-empty
osuFiles.trimToSize(); osuFiles.trimToSize();
Collections.sort(osuFiles); Collections.sort(osuFiles);
OsuGroupList.get().addSongGroup(osuFiles); lastNode = OsuGroupList.get().addSongGroup(osuFiles);
} }
// stop parsing files (interrupt sent by Splash) // stop parsing files (interrupted)
if (Thread.interrupted()) if (Thread.interrupted())
break; break;
} }
@ -104,6 +119,7 @@ public class OsuParser {
currentFile = null; currentFile = null;
currentDirectoryIndex = -1; currentDirectoryIndex = -1;
totalDirectories = -1; totalDirectories = -1;
return lastNode;
} }
/** /**

View File

@ -20,6 +20,8 @@ package itdelatrisu.opsu;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.List;
import net.lingala.zip4j.core.ZipFile; import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException; import net.lingala.zip4j.exception.ZipException;
@ -41,8 +43,13 @@ public class OszUnpacker {
* Invokes the unpacker for each OSZ archive in a root directory. * Invokes the unpacker for each OSZ archive in a root directory.
* @param root the root directory * @param root the root directory
* @param dest the destination directory * @param dest the destination directory
* @return an array containing the new (unpacked) directories, or null
* if no OSZs found
*/ */
public static void unpackAllFiles(File root, File dest) { public static File[] unpackAllFiles(File root, File dest) {
List<File> dirs = new ArrayList<File>();
// find all OSZ files
files = root.listFiles(new FilenameFilter() { files = root.listFiles(new FilenameFilter() {
@Override @Override
public boolean accept(File dir, String name) { public boolean accept(File dir, String name) {
@ -51,9 +58,10 @@ public class OszUnpacker {
}); });
if (files.length < 1) { if (files.length < 1) {
files = null; files = null;
return; return new File[0];
} }
// unpack OSZs
for (File file : files) { for (File file : files) {
fileIndex++; fileIndex++;
String dirName = file.getName().substring(0, file.getName().lastIndexOf('.')); String dirName = file.getName().substring(0, file.getName().lastIndexOf('.'));
@ -62,11 +70,13 @@ public class OszUnpacker {
songDir.mkdir(); songDir.mkdir();
unzip(file, songDir); unzip(file, songDir);
file.delete(); // delete the OSZ when finished file.delete(); // delete the OSZ when finished
dirs.add(songDir);
} }
} }
fileIndex = -1; fileIndex = -1;
files = null; files = null;
return dirs.toArray(new File[dirs.size()]);
} }
/** /**

View File

@ -28,6 +28,7 @@ import java.io.File;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -109,6 +110,19 @@ public class Utils {
/** Set of all Unicode strings already loaded. */ /** Set of all Unicode strings already loaded. */
private static HashSet<String> loadedGlyphs = new HashSet<String>(); private static HashSet<String> loadedGlyphs = new HashSet<String>();
/**
* List of illegal filename characters.
* @see #cleanFileName(String, char)
*/
private final static int[] illegalChars = {
34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47
};
static {
Arrays.sort(illegalChars);
}
// game-related variables // game-related variables
private static GameContainer container; private static GameContainer container;
private static StateBasedGame game; private static StateBasedGame game;
@ -211,7 +225,7 @@ public class Utils {
backButton = new MenuButton(back, backButton = new MenuButton(back,
back.getWidth() / 2f, back.getWidth() / 2f,
height - (back.getHeight() / 2f)); height - (back.getHeight() / 2f));
backButton.setHoverDir(MenuButton.Expand.UP_RIGHT); backButton.setHoverExpand(MenuButton.Expand.UP_RIGHT);
} }
/** /**
@ -704,4 +718,24 @@ public class Utils {
char pre = "KMGTPE".charAt(exp - 1); char pre = "KMGTPE".charAt(exp - 1);
return String.format("%.1f %cB", bytes / Math.pow(1024, exp), pre); return String.format("%.1f %cB", bytes / Math.pow(1024, exp), pre);
} }
/**
* Cleans a file name.
* @param badFileName the original name string
* @param replace the character to replace illegal characters with (or 0 if none)
* @return the cleaned file name
* @author Sarel Botha (http://stackoverflow.com/a/5626340)
*/
public static String cleanFileName(String badFileName, char replace) {
boolean doReplace = (replace > 0 && Arrays.binarySearch(illegalChars, replace) < 0);
StringBuilder cleanName = new StringBuilder();
for (int i = 0, n = badFileName.length(); i < n; i++) {
int c = badFileName.charAt(i);
if (Arrays.binarySearch(illegalChars, c) < 0)
cleanName.append((char) c);
else if (doReplace)
cleanName.append(replace);
}
return cleanName.toString();
}
} }

View File

@ -19,6 +19,7 @@
package itdelatrisu.opsu.downloads; package itdelatrisu.opsu.downloads;
import itdelatrisu.opsu.ErrorHandler; import itdelatrisu.opsu.ErrorHandler;
import itdelatrisu.opsu.Utils;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -155,8 +156,9 @@ public class Download {
rbc.close(); rbc.close();
fos.close(); fos.close();
if (rename != null) { if (rename != null) {
String cleanedName = Utils.cleanFileName(rename, '-');
Path source = new File(localPath).toPath(); Path source = new File(localPath).toPath();
Files.move(source, source.resolveSibling(rename), StandardCopyOption.REPLACE_EXISTING); Files.move(source, source.resolveSibling(cleanedName), StandardCopyOption.REPLACE_EXISTING);
} }
} }
} catch (Exception e) { } catch (Exception e) {

View File

@ -22,6 +22,7 @@ import itdelatrisu.opsu.ErrorHandler;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -126,6 +127,21 @@ public class DownloadList {
} }
} }
/**
* Removes all inactive downloads from the list.
*/
public void clearInactiveDownloads() {
Iterator<DownloadNode> iter = nodes.iterator();
while (iter.hasNext()) {
DownloadNode node = iter.next();
Download dl = node.getDownload();
if (dl != null && !dl.isActive()) {
iter.remove();
map.remove(node.getID());
}
}
}
/** /**
* Shows a confirmation dialog (used before exiting the game). * Shows a confirmation dialog (used before exiting the game).
* @return true if user selects "yes", false otherwise * @return true if user selects "yes", false otherwise

View File

@ -23,7 +23,6 @@ import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.Options; import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.Utils; import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.downloads.Download.Status; import itdelatrisu.opsu.downloads.Download.Status;
import itdelatrisu.opsu.states.DownloadsMenu;
import java.io.File; import java.io.File;
@ -59,6 +58,9 @@ public class DownloadNode {
/** Information drawing values. */ /** Information drawing values. */
private static float infoBaseX, infoBaseY, infoWidth, infoHeight; private static float infoBaseX, infoBaseY, infoWidth, infoHeight;
/** Maximum number of results and downloads to display on one screen. */
private static int maxResultsShown, maxDownloadsShown;
/** Container dimensions. */ /** Container dimensions. */
private static int containerWidth, containerHeight; private static int containerWidth, containerHeight;
@ -89,8 +91,23 @@ public class DownloadNode {
infoBaseY = height * 0.07f + Utils.FONT_LARGE.getLineHeight() * 2f; infoBaseY = height * 0.07f + Utils.FONT_LARGE.getLineHeight() * 2f;
infoWidth = width * 0.25f; infoWidth = width * 0.25f;
infoHeight = Utils.FONT_DEFAULT.getLineHeight() * 2.4f; infoHeight = Utils.FONT_DEFAULT.getLineHeight() * 2.4f;
float searchY = (height * 0.05f) + Utils.FONT_LARGE.getLineHeight();
float buttonHeight = height * 0.038f;
maxResultsShown = (int) ((height - buttonBaseY - searchY) / buttonOffset);
maxDownloadsShown = (int) ((height - infoBaseY - searchY - buttonHeight) / infoHeight);
} }
/**
* Returns the max number of search result buttons to be shown at a time.
*/
public static int maxResultsShown() { return maxResultsShown; }
/**
* Returns the max number of downloads to be shown at a time.
*/
public static int maxDownloadsShown() { return maxDownloadsShown; }
/** /**
* Returns true if the coordinates are within the bounds of the * Returns true if the coordinates are within the bounds of the
* download result button at the given index. * download result button at the given index.
@ -112,7 +129,7 @@ public class DownloadNode {
*/ */
public static boolean resultAreaContains(float cx, float cy) { public static boolean resultAreaContains(float cx, float cy) {
return ((cx > buttonBaseX && cx < buttonBaseX + buttonWidth) && return ((cx > buttonBaseX && cx < buttonBaseX + buttonWidth) &&
(cy > buttonBaseY && cy < buttonBaseY + buttonOffset * DownloadsMenu.MAX_RESULT_BUTTONS)); (cy > buttonBaseY && cy < buttonBaseY + buttonOffset * maxResultsShown));
} }
/** /**
@ -152,7 +169,7 @@ public class DownloadNode {
*/ */
public static boolean downloadAreaContains(float cx, float cy) { public static boolean downloadAreaContains(float cx, float cy) {
return ((cx > infoBaseX && cx <= containerWidth) && return ((cx > infoBaseX && cx <= containerWidth) &&
(cy > infoBaseY && cy < infoBaseY + infoHeight * DownloadsMenu.MAX_DOWNLOADS_SHOWN)); (cy > infoBaseY && cy < infoBaseY + infoHeight * maxDownloadsShown));
} }
/** /**
@ -165,10 +182,10 @@ public class DownloadNode {
float scrollbarWidth = containerWidth * 0.00347f; float scrollbarWidth = containerWidth * 0.00347f;
float heightRatio = 0.0016f * (total * total) - 0.0705f * total + 0.9965f; float heightRatio = 0.0016f * (total * total) - 0.0705f * total + 0.9965f;
float scrollbarHeight = containerHeight * heightRatio; float scrollbarHeight = containerHeight * heightRatio;
float heightDiff = buttonHeight + buttonOffset * (DownloadsMenu.MAX_RESULT_BUTTONS - 1) - scrollbarHeight; float heightDiff = buttonHeight + buttonOffset * (maxResultsShown - 1) - scrollbarHeight;
float offsetY = heightDiff * ((float) index / (total - DownloadsMenu.MAX_RESULT_BUTTONS)); float offsetY = heightDiff * ((float) index / (total - maxResultsShown));
g.setColor(BG_NORMAL); g.setColor(BG_NORMAL);
g.fillRect(buttonBaseX + buttonWidth * 1.005f, buttonBaseY, scrollbarWidth, buttonOffset * DownloadsMenu.MAX_RESULT_BUTTONS); g.fillRect(buttonBaseX + buttonWidth * 1.005f, buttonBaseY, scrollbarWidth, buttonOffset * maxResultsShown);
g.setColor(Color.white); g.setColor(Color.white);
g.fillRect(buttonBaseX + buttonWidth * 1.005f, buttonBaseY + offsetY, scrollbarWidth, scrollbarHeight); g.fillRect(buttonBaseX + buttonWidth * 1.005f, buttonBaseY + offsetY, scrollbarWidth, scrollbarHeight);
} }
@ -183,10 +200,10 @@ public class DownloadNode {
float scrollbarWidth = containerWidth * 0.00347f; float scrollbarWidth = containerWidth * 0.00347f;
float heightRatio = 0.0016f * (total * total) - 0.0705f * total + 0.9965f; float heightRatio = 0.0016f * (total * total) - 0.0705f * total + 0.9965f;
float scrollbarHeight = containerHeight * heightRatio; float scrollbarHeight = containerHeight * heightRatio;
float heightDiff = infoHeight + infoHeight * (DownloadsMenu.MAX_DOWNLOADS_SHOWN - 1) - scrollbarHeight; float heightDiff = infoHeight + infoHeight * (maxDownloadsShown - 1) - scrollbarHeight;
float offsetY = heightDiff * ((float) index / (total - DownloadsMenu.MAX_DOWNLOADS_SHOWN)); float offsetY = heightDiff * ((float) index / (total - maxDownloadsShown));
g.setColor(BG_NORMAL); g.setColor(BG_NORMAL);
g.fillRect(infoBaseX + infoWidth - scrollbarWidth, infoBaseY, scrollbarWidth, infoHeight * DownloadsMenu.MAX_DOWNLOADS_SHOWN); g.fillRect(infoBaseX + infoWidth - scrollbarWidth, infoBaseY, scrollbarWidth, infoHeight * maxDownloadsShown);
g.setColor(Color.white); g.setColor(Color.white);
g.fillRect(infoBaseX + infoWidth - scrollbarWidth, infoBaseY + offsetY, scrollbarWidth, scrollbarHeight); g.fillRect(infoBaseX + infoWidth - scrollbarWidth, infoBaseY + offsetY, scrollbarWidth, scrollbarHeight);
} }

View File

@ -21,6 +21,11 @@ package itdelatrisu.opsu.states;
import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.MenuButton; import itdelatrisu.opsu.MenuButton;
import itdelatrisu.opsu.Opsu; import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.Options;
import itdelatrisu.opsu.OsuGroupList;
import itdelatrisu.opsu.OsuGroupNode;
import itdelatrisu.opsu.OsuParser;
import itdelatrisu.opsu.OszUnpacker;
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;
@ -30,6 +35,7 @@ import itdelatrisu.opsu.downloads.DownloadList;
import itdelatrisu.opsu.downloads.DownloadNode; import itdelatrisu.opsu.downloads.DownloadNode;
import itdelatrisu.opsu.downloads.DownloadServer; import itdelatrisu.opsu.downloads.DownloadServer;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import org.newdawn.slick.Color; import org.newdawn.slick.Color;
@ -48,12 +54,6 @@ import org.newdawn.slick.state.transition.FadeOutTransition;
* Downloads menu. * Downloads menu.
*/ */
public class DownloadsMenu extends BasicGameState { public class DownloadsMenu extends BasicGameState {
/** The max number of search result buttons to be shown at a time. */
public static final int MAX_RESULT_BUTTONS = 10;
/** The max number of downloads to be shown at a time. */
public static final int MAX_DOWNLOADS_SHOWN = 11;
/** Delay time, in milliseconds, between each search. */ /** Delay time, in milliseconds, between each search. */
private static final int SEARCH_DELAY = 700; private static final int SEARCH_DELAY = 700;
@ -129,8 +129,11 @@ public class DownloadsMenu extends BasicGameState {
/** Previous and next page buttons. */ /** Previous and next page buttons. */
private MenuButton prevPage, nextPage; private MenuButton prevPage, nextPage;
/** "Ranked only?" checkbox coordinates. */ /** Buttons. */
private float rankedBoxX, rankedBoxY, rankedBoxLength; private MenuButton clearButton, importButton, resetButton, rankedButton;
/** Beatmap importing thread. */
private Thread importThread;
// game-related variables // game-related variables
private StateBasedGame game; private StateBasedGame game;
@ -150,14 +153,15 @@ public class DownloadsMenu extends BasicGameState {
int width = container.getWidth(); int width = container.getWidth();
int height = container.getHeight(); int height = container.getHeight();
float baseX = width * 0.024f; float baseX = width * 0.024f;
float searchY = (height * 0.05f) + Utils.FONT_LARGE.getLineHeight();
float searchWidth = width * 0.35f;
// search // search
searchTimer = SEARCH_DELAY; searchTimer = SEARCH_DELAY;
searchResultString = "Type to search!"; searchResultString = "Type to search!";
search = new TextField( search = new TextField(
container, Utils.FONT_DEFAULT, container, Utils.FONT_DEFAULT, (int) baseX, (int) searchY,
(int) baseX, (int) (height * 0.05f) + Utils.FONT_LARGE.getLineHeight(), (int) searchWidth, Utils.FONT_MEDIUM.getLineHeight()
(int) (width * 0.35f), Utils.FONT_MEDIUM.getLineHeight()
); );
search.setBackgroundColor(DownloadNode.BG_NORMAL); search.setBackgroundColor(DownloadNode.BG_NORMAL);
search.setBorderColor(Color.white); search.setBorderColor(Color.white);
@ -165,22 +169,46 @@ public class DownloadsMenu extends BasicGameState {
search.setConsumeEvents(false); search.setConsumeEvents(false);
search.setMaxLength(255); search.setMaxLength(255);
// ranked only?
rankedBoxX = search.getX() + search.getWidth() * 1.2f;
rankedBoxY = search.getY();
rankedBoxLength = search.getHeight();
// page buttons // page buttons
float buttonY = height * 0.2f; float pageButtonY = height * 0.2f;
float buttonWidth = width * 0.7f; float pageButtonWidth = width * 0.7f;
Image prevImg = GameImage.MUSIC_PREVIOUS.getImage(); Image prevImg = GameImage.MUSIC_PREVIOUS.getImage();
Image nextImg = GameImage.MUSIC_NEXT.getImage(); Image nextImg = GameImage.MUSIC_NEXT.getImage();
prevPage = new MenuButton(prevImg, baseX + prevImg.getWidth() / 2f, prevPage = new MenuButton(prevImg, baseX + prevImg.getWidth() / 2f,
buttonY - prevImg.getHeight() / 2f); pageButtonY - prevImg.getHeight() / 2f);
nextPage = new MenuButton(nextImg, baseX + buttonWidth - nextImg.getWidth() / 2f, nextPage = new MenuButton(nextImg, baseX + pageButtonWidth - nextImg.getWidth() / 2f,
buttonY - nextImg.getHeight() / 2f); pageButtonY - nextImg.getHeight() / 2f);
prevPage.setHoverScale(1.5f); prevPage.setHoverExpand(1.5f);
nextPage.setHoverScale(1.5f); nextPage.setHoverExpand(1.5f);
// buttons
float buttonWidth = width * 0.12f;
float buttonMarginX = width * 0.004f;
float buttonHeight = height * 0.038f;
float topButtonY = searchY + Utils.FONT_MEDIUM.getLineHeight() / 2f;
float lowerButtonY = height * 0.995f - searchY - buttonHeight / 2f;
Image button = GameImage.MENU_BUTTON_MID.getImage();
Image buttonL = GameImage.MENU_BUTTON_LEFT.getImage();
Image buttonR = GameImage.MENU_BUTTON_RIGHT.getImage();
buttonL = buttonL.getScaledCopy(buttonHeight / buttonL.getHeight());
buttonR = buttonR.getScaledCopy(buttonHeight / buttonR.getHeight());
button = button.getScaledCopy((int) buttonWidth - buttonL.getWidth() - buttonR.getWidth(), (int) buttonHeight);
float fullButtonWidth = button.getWidth() + buttonL.getWidth() + buttonR.getWidth();
clearButton = new MenuButton(button, buttonL, buttonR,
width * 0.75f + buttonMarginX + fullButtonWidth / 2f, lowerButtonY);
importButton = new MenuButton(button, buttonL, buttonR,
width - buttonMarginX - fullButtonWidth / 2f, lowerButtonY);
resetButton = new MenuButton(button, buttonL, buttonR,
baseX + searchWidth + buttonMarginX + fullButtonWidth / 2f, topButtonY);
rankedButton = new MenuButton(button, buttonL, buttonR,
baseX + searchWidth + buttonMarginX * 2f + fullButtonWidth * 3 / 2f, topButtonY);
clearButton.setText("Clear", Utils.FONT_MEDIUM, Color.white);
importButton.setText("Import All", Utils.FONT_MEDIUM, Color.white);
resetButton.setText("Reset Search", Utils.FONT_MEDIUM, Color.white);
clearButton.setHoverFade();
importButton.setHoverFade();
resetButton.setHoverFade();
rankedButton.setHoverFade();
} }
@Override @Override
@ -204,17 +232,11 @@ public class DownloadsMenu extends BasicGameState {
searchResultString, Color.white searchResultString, Color.white
); );
// ranked only?
if (rankedOnly)
g.fillRect(rankedBoxX, rankedBoxY, rankedBoxLength, rankedBoxLength);
else
g.drawRect(rankedBoxX, rankedBoxY, rankedBoxLength, rankedBoxLength);
Utils.FONT_MEDIUM.drawString(rankedBoxX + rankedBoxLength * 1.5f, rankedBoxY, "Show ranked maps only?", Color.white);
// search results // search results
DownloadNode[] nodes = resultList; DownloadNode[] nodes = resultList;
if (nodes != null) { if (nodes != null) {
for (int i = 0; i < MAX_RESULT_BUTTONS; i++) { int maxResultsShown = DownloadNode.maxResultsShown();
for (int i = 0; i < maxResultsShown; i++) {
int index = startResult + i; int index = startResult + i;
if (index >= nodes.length) if (index >= nodes.length)
break; break;
@ -222,7 +244,7 @@ public class DownloadsMenu extends BasicGameState {
} }
// scroll bar // scroll bar
if (nodes.length > MAX_RESULT_BUTTONS) if (nodes.length > maxResultsShown)
DownloadNode.drawResultScrollbar(g, startResult, nodes.length); DownloadNode.drawResultScrollbar(g, startResult, nodes.length);
// pages // pages
@ -250,7 +272,8 @@ public class DownloadsMenu extends BasicGameState {
Utils.FONT_LARGE.drawString(downloadsX + width * 0.015f, downloadsY + height * 0.015f, "Downloads", Color.white); Utils.FONT_LARGE.drawString(downloadsX + width * 0.015f, downloadsY + height * 0.015f, "Downloads", Color.white);
int downloadsSize = DownloadList.get().size(); int downloadsSize = DownloadList.get().size();
if (downloadsSize > 0) { if (downloadsSize > 0) {
for (int i = 0; i < MAX_DOWNLOADS_SHOWN; i++) { int maxDownloadsShown = DownloadNode.maxDownloadsShown();
for (int i = 0; i < maxDownloadsShown; i++) {
int index = startDownloadIndex + i; int index = startDownloadIndex + i;
if (index >= downloadsSize) if (index >= downloadsSize)
break; break;
@ -258,11 +281,30 @@ public class DownloadsMenu extends BasicGameState {
} }
// scroll bar // scroll bar
if (downloadsSize > MAX_DOWNLOADS_SHOWN) if (downloadsSize > maxDownloadsShown)
DownloadNode.drawDownloadScrollbar(g, startDownloadIndex, downloadsSize); DownloadNode.drawDownloadScrollbar(g, startDownloadIndex, downloadsSize);
} }
// buttons
clearButton.draw(Color.gray);
importButton.draw(Color.orange);
resetButton.draw(Color.red);
rankedButton.setText((rankedOnly) ? "Show Unranked" : "Hide Unranked", Utils.FONT_MEDIUM, Color.white);
rankedButton.draw(Color.magenta);
// importing beatmaps
if (importThread != null) {
// darken the screen
g.setColor(Utils.COLOR_BLACK_ALPHA);
g.fillRect(0, 0, width, height);
Utils.drawLoadingProgress(g);
}
// back button
else
Utils.getBackButton().draw(); Utils.getBackButton().draw();
Utils.drawVolume(g); Utils.drawVolume(g);
Utils.drawFPS(); Utils.drawFPS();
Utils.drawCursor(); Utils.drawCursor();
@ -277,6 +319,10 @@ public class DownloadsMenu extends BasicGameState {
Utils.getBackButton().hoverUpdate(delta, mouseX, mouseY); Utils.getBackButton().hoverUpdate(delta, mouseX, mouseY);
prevPage.hoverUpdate(delta, mouseX, mouseY); prevPage.hoverUpdate(delta, mouseX, mouseY);
nextPage.hoverUpdate(delta, mouseX, mouseY); nextPage.hoverUpdate(delta, mouseX, mouseY);
clearButton.hoverUpdate(delta, mouseX, mouseY);
importButton.hoverUpdate(delta, mouseX, mouseY);
resetButton.hoverUpdate(delta, mouseX, mouseY);
rankedButton.hoverUpdate(delta, mouseX, mouseY);
// focus timer // focus timer
if (focusResult != -1 && focusTimer < FOCUS_DELAY) if (focusResult != -1 && focusTimer < FOCUS_DELAY)
@ -285,7 +331,7 @@ public class DownloadsMenu extends BasicGameState {
// search // search
search.setFocus(true); search.setFocus(true);
searchTimer += delta; searchTimer += delta;
if (searchTimer >= SEARCH_DELAY) { if (searchTimer >= SEARCH_DELAY && importThread == null) {
searchTimer = 0; searchTimer = 0;
searchTimerReset = false; searchTimerReset = false;
@ -367,6 +413,10 @@ public class DownloadsMenu extends BasicGameState {
if (button != Input.MOUSE_LEFT_BUTTON) if (button != Input.MOUSE_LEFT_BUTTON)
return; return;
// block input during beatmap importing
if (importThread != null)
return;
// back // back
if (Utils.getBackButton().contains(x, y)) { if (Utils.getBackButton().contains(x, y)) {
SoundController.playSound(SoundEffect.MENUBACK); SoundController.playSound(SoundEffect.MENUBACK);
@ -375,25 +425,17 @@ public class DownloadsMenu extends BasicGameState {
return; return;
} }
// ranked only?
if ((x > rankedBoxX && x < rankedBoxX + rankedBoxLength) &&
(y > rankedBoxY && y < rankedBoxY + rankedBoxLength)) {
rankedOnly = !rankedOnly;
lastQuery = null;
pageDir = Page.CURRENT;
resetSearchTimer();
return;
}
// search results // search results
DownloadNode[] nodes = resultList; DownloadNode[] nodes = resultList;
if (nodes != null) { if (nodes != null) {
if (DownloadNode.resultAreaContains(x, y)) { if (DownloadNode.resultAreaContains(x, y)) {
for (int i = 0; i < MAX_RESULT_BUTTONS; i++) { int maxResultsShown = DownloadNode.maxResultsShown();
for (int i = 0; i < maxResultsShown; i++) {
int index = startResult + i; int index = startResult + i;
if (index >= nodes.length) if (index >= nodes.length)
break; break;
if (DownloadNode.resultContains(x, y, i)) { if (DownloadNode.resultContains(x, y, i)) {
SoundController.playSound(SoundEffect.MENUCLICK);
if (index == focusResult) { if (index == focusResult) {
if (focusTimer >= FOCUS_DELAY) { if (focusTimer >= FOCUS_DELAY) {
// too slow for double-click // too slow for double-click
@ -424,6 +466,7 @@ public class DownloadsMenu extends BasicGameState {
if (lastQueryDir == Page.PREVIOUS && queryThread != null && queryThread.isAlive()) if (lastQueryDir == Page.PREVIOUS && queryThread != null && queryThread.isAlive())
; // don't send consecutive requests ; // don't send consecutive requests
else { else {
SoundController.playSound(SoundEffect.MENUCLICK);
pageDir = Page.PREVIOUS; pageDir = Page.PREVIOUS;
lastQuery = null; lastQuery = null;
resetSearchTimer(); resetSearchTimer();
@ -434,6 +477,7 @@ public class DownloadsMenu extends BasicGameState {
if (lastQueryDir == Page.NEXT && queryThread != null && queryThread.isAlive()) if (lastQueryDir == Page.NEXT && queryThread != null && queryThread.isAlive())
; // don't send consecutive requests ; // don't send consecutive requests
else { else {
SoundController.playSound(SoundEffect.MENUCLICK);
pageDir = Page.NEXT; pageDir = Page.NEXT;
lastQuery = null; lastQuery = null;
resetSearchTimer(); resetSearchTimer();
@ -443,9 +487,58 @@ public class DownloadsMenu extends BasicGameState {
} }
} }
// buttons
if (clearButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUCLICK);
DownloadList.get().clearInactiveDownloads();
return;
}
if (importButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUCLICK);
// import songs in new thread
importThread = new Thread() {
@Override
public void run() {
// invoke unpacker and parser
File[] dirs = OszUnpacker.unpackAllFiles(Options.getOSZDir(), Options.getBeatmapDir());
if (dirs != null && dirs.length > 0) {
OsuGroupNode node = OsuParser.parseDirectories(dirs);
if (node != null) {
// initialize song list
OsuGroupList.get().reset();
OsuGroupList.get().init();
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).setFocus(node, -1, true);
}
}
importThread = null;
}
};
importThread.start();
return;
}
if (resetButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUCLICK);
search.setText("");
lastQuery = null;
pageDir = Page.RESET;
resetSearchTimer();
return;
}
if (rankedButton.contains(x, y)) {
SoundController.playSound(SoundEffect.MENUCLICK);
rankedOnly = !rankedOnly;
lastQuery = null;
pageDir = Page.CURRENT;
resetSearchTimer();
return;
}
// downloads // downloads
if (!DownloadList.get().isEmpty() && DownloadNode.downloadAreaContains(x, y)) { if (!DownloadList.get().isEmpty() && DownloadNode.downloadAreaContains(x, y)) {
for (int i = 0, n = DownloadList.get().size(); i < MAX_DOWNLOADS_SHOWN; i++) { int maxDownloadsShown = DownloadNode.maxDownloadsShown();
for (int i = 0, n = DownloadList.get().size(); i < maxDownloadsShown; i++) {
int index = startDownloadIndex + i; int index = startDownloadIndex + i;
if (index >= n) if (index >= n)
break; break;
@ -472,6 +565,10 @@ public class DownloadsMenu extends BasicGameState {
@Override @Override
public void mouseWheelMoved(int newValue) { public void mouseWheelMoved(int newValue) {
// block input during beatmap importing
if (importThread != null)
return;
int shift = (newValue < 0) ? 1 : -1; int shift = (newValue < 0) ? 1 : -1;
int mouseX = input.getMouseX(), mouseY = input.getMouseY(); int mouseX = input.getMouseX(), mouseY = input.getMouseY();
scrollLists(mouseX, mouseY, shift); scrollLists(mouseX, mouseY, shift);
@ -479,6 +576,10 @@ public class DownloadsMenu extends BasicGameState {
@Override @Override
public void mouseDragged(int oldx, int oldy, int newx, int newy) { public void mouseDragged(int oldx, int oldy, int newx, int newy) {
// block input during beatmap importing
if (importThread != null)
return;
// check mouse button // check mouse button
if (!input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON) && if (!input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON) &&
!input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) !input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON))
@ -493,9 +594,16 @@ public class DownloadsMenu extends BasicGameState {
@Override @Override
public void keyPressed(int key, char c) { public void keyPressed(int key, char c) {
// block input during beatmap importing
if (importThread != null && !(key == Input.KEY_ESCAPE || key == Input.KEY_F12))
return;
switch (key) { switch (key) {
case Input.KEY_ESCAPE: case Input.KEY_ESCAPE:
if (!search.getText().isEmpty()) { if (importThread != null) {
// beatmap importing: stop parsing OsuFiles by sending interrupt to OsuParser
importThread.interrupt();
} else if (!search.getText().isEmpty()) {
// clear search text // clear search text
search.setText(""); search.setText("");
pageDir = Page.RESET; pageDir = Page.RESET;
@ -514,6 +622,7 @@ public class DownloadsMenu extends BasicGameState {
} }
break; break;
case Input.KEY_F5: case Input.KEY_F5:
SoundController.playSound(SoundEffect.MENUCLICK);
lastQuery = null; lastQuery = null;
pageDir = Page.CURRENT; pageDir = Page.CURRENT;
resetSearchTimer(); resetSearchTimer();
@ -534,9 +643,13 @@ public class DownloadsMenu extends BasicGameState {
@Override @Override
public void enter(GameContainer container, StateBasedGame game) public void enter(GameContainer container, StateBasedGame game)
throws SlickException { throws SlickException {
Utils.getBackButton().setScale(1f); Utils.getBackButton().resetHover();
prevPage.setScale(1f); prevPage.resetHover();
nextPage.setScale(1f); nextPage.resetHover();
clearButton.resetHover();
importButton.resetHover();
resetButton.resetHover();
rankedButton.resetHover();
focusResult = -1; focusResult = -1;
startResult = 0; startResult = 0;
startDownloadIndex = 0; startDownloadIndex = 0;
@ -567,18 +680,18 @@ public class DownloadsMenu extends BasicGameState {
// search results // search results
if (DownloadNode.resultAreaContains(cx, cy)) { if (DownloadNode.resultAreaContains(cx, cy)) {
DownloadNode[] nodes = resultList; DownloadNode[] nodes = resultList;
if (nodes != null && nodes.length >= MAX_RESULT_BUTTONS) { if (nodes != null && nodes.length >= DownloadNode.maxResultsShown()) {
int newStartResult = startResult + shift; int newStartResult = startResult + shift;
if (newStartResult >= 0 && newStartResult + MAX_RESULT_BUTTONS <= nodes.length) if (newStartResult >= 0 && newStartResult + DownloadNode.maxResultsShown() <= nodes.length)
startResult = newStartResult; startResult = newStartResult;
} }
} }
// downloads // downloads
else if (DownloadNode.downloadAreaContains(cx, cy)) { else if (DownloadNode.downloadAreaContains(cx, cy)) {
if (DownloadList.get().size() >= MAX_DOWNLOADS_SHOWN) { if (DownloadList.get().size() >= DownloadNode.maxDownloadsShown()) {
int newStartDownloadIndex = startDownloadIndex + shift; int newStartDownloadIndex = startDownloadIndex + shift;
if (newStartDownloadIndex >= 0 && newStartDownloadIndex + MAX_DOWNLOADS_SHOWN <= DownloadList.get().size()) if (newStartDownloadIndex >= 0 && newStartDownloadIndex + DownloadNode.maxDownloadsShown() <= DownloadList.get().size())
startDownloadIndex = newStartDownloadIndex; startDownloadIndex = newStartDownloadIndex;
} }
} }

View File

@ -730,7 +730,7 @@ public class Game extends BasicGameState {
restart = Restart.FALSE; restart = Restart.FALSE;
} }
skipButton.setScale(1f); skipButton.resetHover();
} }
// @Override // @Override
@ -806,7 +806,7 @@ public class Game extends BasicGameState {
skipButton = new MenuButton(skip, skipButton = new MenuButton(skip,
width - (skip.getWidth() / 2f), width - (skip.getWidth() / 2f),
height - (skip.getHeight() / 2f)); height - (skip.getHeight() / 2f));
skipButton.setHoverDir(MenuButton.Expand.UP_LEFT); skipButton.setHoverExpand(MenuButton.Expand.UP_LEFT);
// load other images... // load other images...
((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages(); ((GamePauseMenu) game.getState(Opsu.STATE_GAMEPAUSEMENU)).loadImages();

View File

@ -196,9 +196,9 @@ public class GamePauseMenu extends BasicGameState {
SoundController.playSound(SoundEffect.FAIL); SoundController.playSound(SoundEffect.FAIL);
} else } else
MusicController.pause(); MusicController.pause();
continueButton.setScale(1f); continueButton.resetHover();
retryButton.setScale(1f); retryButton.resetHover();
backButton.setScale(1f); backButton.resetHover();
} }
/** /**
@ -212,5 +212,8 @@ public class GamePauseMenu extends BasicGameState {
continueButton = new MenuButton(GameImage.PAUSE_CONTINUE.getImage(), width / 2f, height * 0.25f); continueButton = new MenuButton(GameImage.PAUSE_CONTINUE.getImage(), width / 2f, height * 0.25f);
retryButton = new MenuButton(GameImage.PAUSE_RETRY.getImage(), width / 2f, height * 0.5f); retryButton = new MenuButton(GameImage.PAUSE_RETRY.getImage(), width / 2f, height * 0.5f);
backButton = new MenuButton(GameImage.PAUSE_BACK.getImage(), width / 2f, height * 0.75f); backButton = new MenuButton(GameImage.PAUSE_BACK.getImage(), width / 2f, height * 0.75f);
continueButton.setHoverExpand();
retryButton.setHoverExpand();
backButton.setHoverExpand();
} }
} }

View File

@ -170,7 +170,7 @@ public class GameRanking extends BasicGameState {
public void enter(GameContainer container, StateBasedGame game) public void enter(GameContainer container, StateBasedGame game)
throws SlickException { throws SlickException {
Display.setTitle(game.getTitle()); Display.setTitle(game.getTitle());
Utils.getBackButton().setScale(1f); Utils.getBackButton().resetHover();
if (!data.isGameplay()) { if (!data.isGameplay()) {
if (!MusicController.isTrackDimmed()) if (!MusicController.isTrackDimmed())
MusicController.toggleTrackDimmed(0.5f); MusicController.toggleTrackDimmed(0.5f);

View File

@ -126,9 +126,9 @@ public class MainMenu extends BasicGameState {
exitButton = new MenuButton(exitImg, exitButton = new MenuButton(exitImg,
width * 0.75f - exitOffset, (height / 2) + (exitImg.getHeight() / 2f) width * 0.75f - exitOffset, (height / 2) + (exitImg.getHeight() / 2f)
); );
logo.setHoverScale(1.05f); logo.setHoverExpand(1.05f);
playButton.setHoverScale(1.05f); playButton.setHoverExpand(1.05f);
exitButton.setHoverScale(1.05f); exitButton.setHoverExpand(1.05f);
// initialize music buttons // initialize music buttons
int musicWidth = 48; int musicWidth = 48;
@ -137,16 +137,15 @@ public class MainMenu extends BasicGameState {
musicPause = new MenuButton(GameImage.MUSIC_PAUSE.getImage(), width - (2 * musicWidth), musicHeight); musicPause = new MenuButton(GameImage.MUSIC_PAUSE.getImage(), width - (2 * musicWidth), musicHeight);
musicNext = new MenuButton(GameImage.MUSIC_NEXT.getImage(), width - musicWidth, musicHeight); musicNext = new MenuButton(GameImage.MUSIC_NEXT.getImage(), width - musicWidth, musicHeight);
musicPrevious = new MenuButton(GameImage.MUSIC_PREVIOUS.getImage(), width - (3 * musicWidth), musicHeight); musicPrevious = new MenuButton(GameImage.MUSIC_PREVIOUS.getImage(), width - (3 * musicWidth), musicHeight);
musicPlay.setHoverScale(1.5f); musicPlay.setHoverExpand(1.5f);
musicPause.setHoverScale(1.5f); musicPause.setHoverExpand(1.5f);
musicNext.setHoverScale(1.5f); musicNext.setHoverExpand(1.5f);
musicPrevious.setHoverScale(1.5f); musicPrevious.setHoverExpand(1.5f);
// initialize downloads button // initialize downloads button
Image dlImg = GameImage.DOWNLOADS.getImage(); Image dlImg = GameImage.DOWNLOADS.getImage();
downloadsButton = new MenuButton(dlImg, width - dlImg.getWidth() / 2f, height / 2f); downloadsButton = new MenuButton(dlImg, width - dlImg.getWidth() / 2f, height / 2f);
downloadsButton.setHoverDir(Expand.LEFT); downloadsButton.setHoverExpand(1.03f, Expand.LEFT);
downloadsButton.setHoverScale(1.05f);
// initialize repository button // initialize repository button
if (Desktop.isDesktopSupported()) { // only if a webpage can be opened if (Desktop.isDesktopSupported()) { // only if a webpage can be opened
@ -154,6 +153,7 @@ public class MainMenu extends BasicGameState {
repoButton = new MenuButton(repoImg, repoButton = new MenuButton(repoImg,
(width * 0.997f) - repoImg.getWidth(), (height * 0.997f) - repoImg.getHeight() (width * 0.997f) - repoImg.getWidth(), (height * 0.997f) - repoImg.getHeight()
); );
repoButton.setHoverExpand();
} }
reset(); reset();
@ -318,23 +318,23 @@ public class MainMenu extends BasicGameState {
// reset button hover states if mouse is not currently hovering over the button // reset button hover states if mouse is not currently hovering over the button
int mouseX = input.getMouseX(), mouseY = input.getMouseY(); int mouseX = input.getMouseX(), mouseY = input.getMouseY();
if (!logo.contains(mouseX, mouseY)) if (!logo.contains(mouseX, mouseY))
logo.setScale(1f); logo.resetHover();
if (!playButton.contains(mouseX, mouseY)) if (!playButton.contains(mouseX, mouseY))
playButton.setScale(1f); playButton.resetHover();
if (!exitButton.contains(mouseX, mouseY)) if (!exitButton.contains(mouseX, mouseY))
exitButton.setScale(1f); exitButton.resetHover();
if (!musicPlay.contains(mouseX, mouseY)) if (!musicPlay.contains(mouseX, mouseY))
musicPlay.setScale(1f); musicPlay.resetHover();
if (!musicPause.contains(mouseX, mouseY)) if (!musicPause.contains(mouseX, mouseY))
musicPause.setScale(1f); musicPause.resetHover();
if (!musicNext.contains(mouseX, mouseY)) if (!musicNext.contains(mouseX, mouseY))
musicNext.setScale(1f); musicNext.resetHover();
if (!musicPrevious.contains(mouseX, mouseY)) if (!musicPrevious.contains(mouseX, mouseY))
musicPrevious.setScale(1f); musicPrevious.resetHover();
if (repoButton != null && !repoButton.contains(mouseX, mouseY)) if (repoButton != null && !repoButton.contains(mouseX, mouseY))
repoButton.setScale(1f); repoButton.resetHover();
if (!downloadsButton.contains(mouseX, mouseY)) if (!downloadsButton.contains(mouseX, mouseY))
downloadsButton.setScale(1f); downloadsButton.resetHover();
} }
@Override @Override
@ -452,13 +452,13 @@ public class MainMenu extends BasicGameState {
logoClicked = false; logoClicked = false;
logoTimer = 0; logoTimer = 0;
logo.setScale(1f); logo.resetHover();
playButton.setScale(1f); playButton.resetHover();
exitButton.setScale(1f); exitButton.resetHover();
musicPlay.setScale(1f); musicPlay.resetHover();
musicPause.setScale(1f); musicPause.resetHover();
musicNext.setScale(1f); musicNext.resetHover();
musicPrevious.setScale(1f); musicPrevious.resetHover();
downloadsButton.setScale(1f); downloadsButton.resetHover();
} }
} }

View File

@ -51,6 +51,7 @@ public class MainMenuExit extends BasicGameState {
// game-related variables // game-related variables
private GameContainer container; private GameContainer container;
private StateBasedGame game; private StateBasedGame game;
private Input input;
private int state; private int state;
public MainMenuExit(int state) { public MainMenuExit(int state) {
@ -62,6 +63,7 @@ public class MainMenuExit extends BasicGameState {
throws SlickException { throws SlickException {
this.container = container; this.container = container;
this.game = game; this.game = game;
this.input = container.getInput();
int width = container.getWidth(); int width = container.getWidth();
int height = container.getHeight(); int height = container.getHeight();
@ -70,14 +72,19 @@ public class MainMenuExit extends BasicGameState {
// initialize buttons // initialize buttons
Image button = GameImage.MENU_BUTTON_MID.getImage(); Image button = GameImage.MENU_BUTTON_MID.getImage();
button = button.getScaledCopy(width / 2, button.getHeight());
Image buttonL = GameImage.MENU_BUTTON_LEFT.getImage(); Image buttonL = GameImage.MENU_BUTTON_LEFT.getImage();
Image buttonR = GameImage.MENU_BUTTON_RIGHT.getImage(); Image buttonR = GameImage.MENU_BUTTON_RIGHT.getImage();
yesButton = new MenuButton(button, buttonL, buttonR, yesButton = new MenuButton(button, buttonL, buttonR,
width / 2f + centerOffset, height * 0.2f width / 2f + centerOffset, height * 0.2f
); );
yesButton.setText("1. Yes", Utils.FONT_XLARGE, Color.white);
noButton = new MenuButton(button, buttonL, buttonR, noButton = new MenuButton(button, buttonL, buttonR,
width / 2f - centerOffset, height * 0.2f + (button.getHeight() * 1.25f) width / 2f - centerOffset, height * 0.2f + (button.getHeight() * 1.25f)
); );
noButton.setText("2. No", Utils.FONT_XLARGE, Color.white);
yesButton.setHoverFade();
noButton.setHoverFade();
} }
@Override @Override
@ -92,16 +99,6 @@ public class MainMenuExit extends BasicGameState {
// draw buttons // draw buttons
yesButton.draw(Color.green); yesButton.draw(Color.green);
noButton.draw(Color.red); noButton.draw(Color.red);
Utils.FONT_XLARGE.drawString(
yesButton.getX() - (Utils.FONT_XLARGE.getWidth("1. Yes") / 2f),
yesButton.getY() - (Utils.FONT_XLARGE.getLineHeight() / 2f),
"1. Yes", Color.white
);
Utils.FONT_XLARGE.drawString(
noButton.getX() - (Utils.FONT_XLARGE.getWidth("2. No") / 2f),
noButton.getY() - (Utils.FONT_XLARGE.getLineHeight() / 2f),
"2. No", Color.white
);
Utils.drawVolume(g); Utils.drawVolume(g);
Utils.drawFPS(); Utils.drawFPS();
@ -113,6 +110,9 @@ public class MainMenuExit extends BasicGameState {
throws SlickException { throws SlickException {
Utils.updateCursor(delta); Utils.updateCursor(delta);
Utils.updateVolumeDisplay(delta); Utils.updateVolumeDisplay(delta);
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
yesButton.hoverUpdate(delta, mouseX, mouseY);
noButton.hoverUpdate(delta, mouseX, mouseY);
// move buttons to center // move buttons to center
float yesX = yesButton.getX(), noX = noButton.getX(); float yesX = yesButton.getX(), noX = noButton.getX();
@ -160,5 +160,7 @@ public class MainMenuExit extends BasicGameState {
float center = container.getWidth() / 2f; float center = container.getWidth() / 2f;
yesButton.setX(center - centerOffset); yesButton.setX(center - centerOffset);
noButton.setX(center + centerOffset); noButton.setX(center + centerOffset);
yesButton.resetHover();
noButton.resetHover();
} }
} }

View File

@ -415,9 +415,9 @@ public class OptionsMenu extends BasicGameState {
public void enter(GameContainer container, StateBasedGame game) public void enter(GameContainer container, StateBasedGame game)
throws SlickException { throws SlickException {
currentTab = OptionTab.DISPLAY; currentTab = OptionTab.DISPLAY;
Utils.getBackButton().setScale(1f); Utils.getBackButton().resetHover();
for (GameMod mod : GameMod.values()) for (GameMod mod : GameMod.values())
mod.setScale(1f); mod.resetHover();
} }
/** /**

View File

@ -220,7 +220,7 @@ public class SongMenu extends BasicGameState {
// options button // options button
Image optionsIcon = GameImage.MENU_OPTIONS.getImage(); Image optionsIcon = GameImage.MENU_OPTIONS.getImage();
optionsButton = new MenuButton(optionsIcon, search.getX() - (optionsIcon.getWidth() * 1.5f), search.getY()); optionsButton = new MenuButton(optionsIcon, search.getX() - (optionsIcon.getWidth() * 1.5f), search.getY());
optionsButton.setHoverScale(1.75f); optionsButton.setHoverExpand(1.75f);
// loader // loader
int loaderDim = GameImage.MENU_MUSICNOTE.getImage().getWidth(); int loaderDim = GameImage.MENU_MUSICNOTE.getImage().getWidth();
@ -565,7 +565,6 @@ public class SongMenu extends BasicGameState {
case Input.KEY_ESCAPE: case Input.KEY_ESCAPE:
if (reloadThread != null) { if (reloadThread != null) {
// beatmap reloading: stop parsing OsuFiles by sending interrupt to OsuParser // beatmap reloading: stop parsing OsuFiles by sending interrupt to OsuParser
if (reloadThread != null)
reloadThread.interrupt(); reloadThread.interrupt();
} else if (!search.getText().isEmpty()) { } else if (!search.getText().isEmpty()) {
// clear search text // clear search text
@ -626,7 +625,7 @@ public class SongMenu extends BasicGameState {
if (OsuGroupList.get().size() > 0) { if (OsuGroupList.get().size() > 0) {
OsuGroupList.get().init(); OsuGroupList.get().init();
setFocus(OsuGroupList.get().getRandomNode(), -1, true); setFocus(OsuGroupList.get().getRandomNode(), -1, true);
} else if (Options.isThemSongEnabled()) } else
MusicController.playThemeSong(); MusicController.playThemeSong();
reloadThread = null; reloadThread = null;
@ -756,15 +755,15 @@ public class SongMenu extends BasicGameState {
public void enter(GameContainer container, StateBasedGame game) public void enter(GameContainer container, StateBasedGame game)
throws SlickException { throws SlickException {
Display.setTitle(game.getTitle()); Display.setTitle(game.getTitle());
Utils.getBackButton().setScale(1f); Utils.getBackButton().resetHover();
optionsButton.setScale(1f); optionsButton.resetHover();
hoverOffset = 0f; hoverOffset = 0f;
hoverIndex = -1; hoverIndex = -1;
startScore = 0; startScore = 0;
// stop playing the theme song // set focus node if not set (e.g. theme song playing)
if (MusicController.isThemePlaying() && focusNode != null) if (focusNode == null && OsuGroupList.get().size() > 0)
MusicController.play(focusNode.osuFiles.get(focusNode.osuFileIndex), true); setFocus(OsuGroupList.get().getRandomNode(), -1, true);
// reset music track // reset music track
else if (resetTrack) { else if (resetTrack) {

View File

@ -126,11 +126,14 @@ public class Splash extends BasicGameState {
// initialize song list // initialize song list
if (OsuGroupList.get().size() > 0) { if (OsuGroupList.get().size() > 0) {
OsuGroupList.get().init(); OsuGroupList.get().init();
if (Options.isThemSongEnabled())
MusicController.playThemeSong();
else
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).setFocus(OsuGroupList.get().getRandomNode(), -1, true); ((SongMenu) game.getState(Opsu.STATE_SONGMENU)).setFocus(OsuGroupList.get().getRandomNode(), -1, true);
} }
// play the theme song // play the theme song
if (Options.isThemSongEnabled()) else
MusicController.playThemeSong(); MusicController.playThemeSong();
game.enterState(Opsu.STATE_MAINMENU); game.enterState(Opsu.STATE_MAINMENU);