Merge pull request #131 from fluddokt/KinecticScrolling

Kinetic scrolling
This commit is contained in:
Jeffrey Han 2015-09-15 22:53:10 -04:00
commit e1becb3962
7 changed files with 485 additions and 202 deletions

View File

@ -85,7 +85,7 @@ public class ScoreData implements Comparable<ScoreData> {
private String tooltip; private String tooltip;
/** Drawing values. */ /** Drawing values. */
private static float baseX, baseY, buttonWidth, buttonHeight, buttonOffset; private static float baseX, baseY, buttonWidth, buttonHeight, buttonOffset, buttonAreaHeight;
/** /**
* Initializes the base coordinates for drawing. * Initializes the base coordinates for drawing.
@ -99,6 +99,14 @@ public class ScoreData implements Comparable<ScoreData> {
float gradeHeight = GameImage.MENU_BUTTON_BG.getImage().getHeight() * 0.45f; float gradeHeight = GameImage.MENU_BUTTON_BG.getImage().getHeight() * 0.45f;
buttonHeight = Math.max(gradeHeight, Fonts.DEFAULT.getLineHeight() * 3.03f); buttonHeight = Math.max(gradeHeight, Fonts.DEFAULT.getLineHeight() * 3.03f);
buttonOffset = buttonHeight + gradeHeight / 10f; buttonOffset = buttonHeight + gradeHeight / 10f;
buttonAreaHeight = (SongMenu.MAX_SCORE_BUTTONS - 1) * buttonOffset + buttonHeight;
}
/**
* Returns the Buttons Offset
*/
public static float getButtonOffset() {
return buttonOffset;
} }
/** /**
@ -131,9 +139,17 @@ public class ScoreData implements Comparable<ScoreData> {
* @param index the start button index * @param index the start button index
* @param total the total number of buttons * @param total the total number of buttons
*/ */
public static void drawScrollbar(Graphics g, int index, int total) { public static void drawScrollbar(Graphics g, float pos, float total) {
UI.drawScrollbar(g, index, total, SongMenu.MAX_SCORE_BUTTONS, 0, baseY, UI.drawScrollbar(g, pos, total, SongMenu.MAX_SCORE_BUTTONS * buttonOffset, 0, baseY,
0, buttonHeight, buttonOffset, null, Color.white, false); 0, buttonAreaHeight, null, Color.white, false);
}
/**
* Sets a clip to the area.
* @param g the graphics context
*/
public static void clipToDownloadArea(Graphics g) {
g.setClip((int) baseX, (int) baseY, (int) buttonWidth, (int) (buttonAreaHeight));
} }
/** /**
@ -228,11 +244,11 @@ public class ScoreData implements Comparable<ScoreData> {
* @param focus whether the button is focused * @param focus whether the button is focused
* @param t the animation progress [0,1] * @param t the animation progress [0,1]
*/ */
public void draw(Graphics g, int index, int rank, long prevScore, boolean focus, float t) { public void draw(Graphics g, float position, int rank, long prevScore, boolean focus, float t) {
float x = baseX - buttonWidth * (1 - AnimationEquation.OUT_BACK.calc(t)) / 2.5f; float x = baseX - buttonWidth * (1 - AnimationEquation.OUT_BACK.calc(t)) / 2.5f;
float textX = x + buttonWidth * 0.24f; float textX = x + buttonWidth * 0.24f;
float edgeX = x + buttonWidth * 0.98f; float edgeX = x + buttonWidth * 0.98f;
float y = baseY + index * (buttonOffset); float y = baseY + position;
float midY = y + buttonHeight / 2f; float midY = y + buttonHeight / 2f;
float marginY = Fonts.DEFAULT.getLineHeight() * 0.01f; float marginY = Fonts.DEFAULT.getLineHeight() * 0.01f;
Color c = Colors.WHITE_FADE; Color c = Colors.WHITE_FADE;

View File

@ -154,6 +154,14 @@ public class DownloadNode {
g.setClip((int) buttonBaseX, (int) buttonBaseY, (int) buttonWidth, (int) (buttonOffset * maxResultsShown)); g.setClip((int) buttonBaseX, (int) buttonBaseY, (int) buttonWidth, (int) (buttonOffset * maxResultsShown));
} }
/**
* Sets a clip to the download area.
* @param g the graphics context
*/
public static void clipToDownloadArea(Graphics g) {
g.setClip((int) infoBaseX, (int) infoBaseY, (int) infoWidth, (int) (infoHeight * maxDownloadsShown));
}
/** /**
* Returns true if the coordinates are within the bounds of the * Returns true if the coordinates are within the bounds of the
* download information button at the given index. * download information button at the given index.
@ -183,6 +191,21 @@ public class DownloadNode {
(cy > y + marginY && cy < y + marginY + iconWidth)); (cy > y + marginY && cy < y + marginY + iconWidth));
} }
/**
* Returns the button(Results) offset.
* @return the button offset
*/
public static float getButtonOffset(){
return buttonOffset;
}
/**
* Returns the info(Download) height.
* @return the infoHeight
*/
public static float getInfoHeight(){
return infoHeight;
}
/** /**
* Returns true if the coordinates are within the bounds of the * Returns true if the coordinates are within the bounds of the
* download information button area. * download information button area.
@ -197,12 +220,13 @@ public class DownloadNode {
/** /**
* Draws the scroll bar for the download result buttons. * Draws the scroll bar for the download result buttons.
* @param g the graphics context * @param g the graphics context
* @param index the start button index * @param position the start button index
* @param total the total number of buttons * @param total the total number of buttons
*/ */
public static void drawResultScrollbar(Graphics g, int index, int total) { public static void drawResultScrollbar(Graphics g, float position, float total) {
UI.drawScrollbar(g, index, total, maxResultsShown, buttonBaseX, buttonBaseY, UI.drawScrollbar(g, position, total, maxResultsShown * buttonOffset, buttonBaseX, buttonBaseY,
buttonWidth * 1.01f, buttonHeight, buttonOffset, Colors.BLACK_BG_NORMAL, Color.white, true); buttonWidth * 1.01f, (maxResultsShown-1) * buttonOffset + buttonHeight,
Colors.BLACK_BG_NORMAL, Color.white, true);
} }
/** /**
@ -211,9 +235,9 @@ public class DownloadNode {
* @param index the start index * @param index the start index
* @param total the total number of downloads * @param total the total number of downloads
*/ */
public static void drawDownloadScrollbar(Graphics g, int index, int total) { public static void drawDownloadScrollbar(Graphics g, float index, float total) {
UI.drawScrollbar(g, index, total, maxDownloadsShown, infoBaseX, infoBaseY, UI.drawScrollbar(g, index, total, maxDownloadsShown * infoHeight, infoBaseX, infoBaseY,
infoWidth, infoHeight, infoHeight, Colors.BLACK_BG_NORMAL, Color.white, true); infoWidth, maxDownloadsShown * infoHeight, Colors.BLACK_BG_NORMAL, Color.white, true);
} }
/** /**
@ -314,15 +338,15 @@ public class DownloadNode {
/** /**
* Draws the download result as a rectangular button. * Draws the download result as a rectangular button.
* @param g the graphics context * @param g the graphics context
* @param index the index (to offset the button from the topmost button) * @param position the index (to offset the button from the topmost button)
* @param hover true if the mouse is hovering over this button * @param hover true if the mouse is hovering over this button
* @param focus true if the button is focused * @param focus true if the button is focused
* @param previewing true if the beatmap is currently being previewed * @param previewing true if the beatmap is currently being previewed
*/ */
public void drawResult(Graphics g, int index, boolean hover, boolean focus, boolean previewing) { public void drawResult(Graphics g, float position, boolean hover, boolean focus, boolean previewing) {
float textX = buttonBaseX + buttonWidth * 0.001f; float textX = buttonBaseX + buttonWidth * 0.001f;
float edgeX = buttonBaseX + buttonWidth * 0.985f; float edgeX = buttonBaseX + buttonWidth * 0.985f;
float y = buttonBaseY + index * buttonOffset; float y = buttonBaseY + position;
float marginY = buttonHeight * 0.04f; float marginY = buttonHeight * 0.04f;
Download dl = DownloadList.get().getDownload(beatmapSetID); Download dl = DownloadList.get().getDownload(beatmapSetID);
@ -356,12 +380,13 @@ public class DownloadNode {
Fonts.loadGlyphs(Fonts.BOLD, getTitle()); Fonts.loadGlyphs(Fonts.BOLD, getTitle());
Fonts.loadGlyphs(Fonts.BOLD, getArtist()); Fonts.loadGlyphs(Fonts.BOLD, getArtist());
} }
g.setClip((int) textX, (int) (y + marginY), (int) (edgeX - textX - Fonts.DEFAULT.getWidth(creator)), Fonts.BOLD.getLineHeight()); // TODO can't set clip again or else old clip will be cleared
//g.setClip((int) textX, (int) (y + marginY), (int) (edgeX - textX - Fonts.DEFAULT.getWidth(creator)), Fonts.BOLD.getLineHeight());
Fonts.BOLD.drawString( Fonts.BOLD.drawString(
textX, y + marginY, textX, y + marginY,
String.format("%s - %s%s", getArtist(), getTitle(), String.format("%s - %s%s", getArtist(), getTitle(),
(dl != null) ? String.format(" [%s]", dl.getStatus().getName()) : ""), Color.white); (dl != null) ? String.format(" [%s]", dl.getStatus().getName()) : ""), Color.white);
g.clearClip(); //g.clearClip();
Fonts.DEFAULT.drawString( Fonts.DEFAULT.drawString(
textX, y + marginY + Fonts.BOLD.getLineHeight(), textX, y + marginY + Fonts.BOLD.getLineHeight(),
String.format("Last updated: %s", date), Color.white); String.format("Last updated: %s", date), Color.white);
@ -373,11 +398,11 @@ public class DownloadNode {
/** /**
* Draws the download information. * Draws the download information.
* @param g the graphics context * @param g the graphics context
* @param index the index (to offset from the topmost position) * @param position the index (to offset from the topmost position)
* @param id the list index * @param id the list index
* @param hover true if the mouse is hovering over this button * @param hover true if the mouse is hovering over this button
*/ */
public void drawDownload(Graphics g, int index, int id, boolean hover) { public void drawDownload(Graphics g, float position, int id, boolean hover) {
Download download = this.download; // in case clearDownload() is called asynchronously Download download = this.download; // in case clearDownload() is called asynchronously
if (download == null) { if (download == null) {
ErrorHandler.error("Trying to draw download information for button without Download object.", null, false); ErrorHandler.error("Trying to draw download information for button without Download object.", null, false);
@ -386,7 +411,7 @@ public class DownloadNode {
float textX = infoBaseX + infoWidth * 0.02f; float textX = infoBaseX + infoWidth * 0.02f;
float edgeX = infoBaseX + infoWidth * 0.985f; float edgeX = infoBaseX + infoWidth * 0.985f;
float y = infoBaseY + index * infoHeight; float y = infoBaseY + position;
float marginY = infoHeight * 0.04f; float marginY = infoHeight * 0.04f;
// rectangle outline // rectangle outline

View File

@ -41,6 +41,7 @@ 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.DropdownMenu;
import itdelatrisu.opsu.ui.Fonts; import itdelatrisu.opsu.ui.Fonts;
import itdelatrisu.opsu.ui.KineticScrolling;
import itdelatrisu.opsu.ui.MenuButton; import itdelatrisu.opsu.ui.MenuButton;
import itdelatrisu.opsu.ui.UI; import itdelatrisu.opsu.ui.UI;
@ -94,7 +95,7 @@ public class DownloadsMenu extends BasicGameState {
private int focusTimer = 0; private int focusTimer = 0;
/** Current start result button (topmost entry). */ /** Current start result button (topmost entry). */
private int startResult = 0; KineticScrolling startResultPos = new KineticScrolling();
/** Total number of results for current query. */ /** Total number of results for current query. */
private int totalResults = 0; private int totalResults = 0;
@ -115,7 +116,7 @@ public class DownloadsMenu extends BasicGameState {
private boolean rankedOnly = true; private boolean rankedOnly = true;
/** Current start download index. */ /** Current start download index. */
private int startDownloadIndex = 0; KineticScrolling startDownloadIndexPos = new KineticScrolling();
/** Query thread. */ /** Query thread. */
private Thread queryThread; private Thread queryThread;
@ -223,7 +224,7 @@ public class DownloadsMenu extends BasicGameState {
resultList = nodes; resultList = nodes;
totalResults = server.totalResults(); totalResults = server.totalResults();
focusResult = -1; focusResult = -1;
startResult = 0; startResultPos.setPosition(0);
if (nodes == null) if (nodes == null)
searchResultString = "An error has occurred."; searchResultString = "An error has occurred.";
else { else {
@ -336,7 +337,7 @@ public class DownloadsMenu extends BasicGameState {
@Override @Override
public void itemSelected(int index, DownloadServer item) { public void itemSelected(int index, DownloadServer item) {
resultList = null; resultList = null;
startResult = 0; startResultPos.setPosition(0);
focusResult = -1; focusResult = -1;
totalResults = 0; totalResults = 0;
page = 0; page = 0;
@ -393,18 +394,24 @@ public class DownloadsMenu extends BasicGameState {
if (nodes != null) { if (nodes != null) {
DownloadNode.clipToResultArea(g); DownloadNode.clipToResultArea(g);
int maxResultsShown = DownloadNode.maxResultsShown(); int maxResultsShown = DownloadNode.maxResultsShown();
for (int i = 0; i < maxResultsShown; i++) { int startResult = (int) (startResultPos.getPosition() / DownloadNode.getButtonOffset());
int offset = (int) (-startResultPos.getPosition() + startResult * DownloadNode.getButtonOffset());
for (int i = 0; i < maxResultsShown + 1; i++) {
int index = startResult + i; int index = startResult + i;
if(index < 0)
continue;
if (index >= nodes.length) if (index >= nodes.length)
break; break;
nodes[index].drawResult(g, i, DownloadNode.resultContains(mouseX, mouseY, i) && !inDropdownMenu, nodes[index].drawResult(g, offset + i * DownloadNode.getButtonOffset(),
DownloadNode.resultContains(mouseX, mouseY - offset, i) && !inDropdownMenu,
(index == focusResult), (previewID == nodes[index].getID())); (index == focusResult), (previewID == nodes[index].getID()));
} }
g.clearClip(); g.clearClip();
// scroll bar // scroll bar
if (nodes.length > maxResultsShown) if (nodes.length > maxResultsShown)
DownloadNode.drawResultScrollbar(g, startResult, nodes.length); DownloadNode.drawResultScrollbar(g, startResultPos.getPosition(), nodes.length * DownloadNode.getButtonOffset());
// pages // pages
if (nodes.length > 0) { if (nodes.length > 0) {
@ -432,19 +439,26 @@ public class DownloadsMenu extends BasicGameState {
int downloadsSize = DownloadList.get().size(); int downloadsSize = DownloadList.get().size();
if (downloadsSize > 0) { if (downloadsSize > 0) {
int maxDownloadsShown = DownloadNode.maxDownloadsShown(); int maxDownloadsShown = DownloadNode.maxDownloadsShown();
for (int i = 0; i < maxDownloadsShown; i++) { int startDownloadIndex = (int) (startDownloadIndexPos.getPosition() / DownloadNode.getInfoHeight());
int index = startDownloadIndex + i; int offset = (int) (-startDownloadIndexPos.getPosition() + startDownloadIndex * DownloadNode.getInfoHeight());
if (index >= downloadsSize)
break; DownloadNode.clipToDownloadArea(g);
DownloadNode node = DownloadList.get().getNode(index); for (int i = 0; i < maxDownloadsShown + 1; i++) {
if (node == null) int index = startDownloadIndex + i;
break; if (index >= downloadsSize)
node.drawDownload(g, i, index, DownloadNode.downloadContains(mouseX, mouseY, i)); break;
} DownloadNode node = DownloadList.get().getNode(index);
if (node == null)
break;
node.drawDownload(g, i * DownloadNode.getInfoHeight() + offset, index,
DownloadNode.downloadContains(mouseX, mouseY - offset, i));
}
g.clearClip();
// scroll bar // scroll bar
if (downloadsSize > maxDownloadsShown) if (downloadsSize > maxDownloadsShown)
DownloadNode.drawDownloadScrollbar(g, startDownloadIndex, downloadsSize); DownloadNode.drawDownloadScrollbar(g, startDownloadIndexPos.getPosition(), downloadsSize * DownloadNode.getInfoHeight());
} }
// buttons // buttons
@ -488,6 +502,13 @@ public class DownloadsMenu extends BasicGameState {
resetButton.hoverUpdate(delta, mouseX, mouseY); resetButton.hoverUpdate(delta, mouseX, mouseY);
rankedButton.hoverUpdate(delta, mouseX, mouseY); rankedButton.hoverUpdate(delta, mouseX, mouseY);
if (DownloadList.get() != null)
startDownloadIndexPos.setMinMax(0, DownloadNode.getInfoHeight() * (DownloadList.get().size() - DownloadNode.maxDownloadsShown()));
startDownloadIndexPos.update(delta);
if (resultList != null)
startResultPos.setMinMax(0, DownloadNode.getButtonOffset() * (resultList.length - DownloadNode.maxResultsShown()));
startResultPos.update(delta);
// focus timer // focus timer
if (focusResult != -1 && focusTimer < FOCUS_DELAY) if (focusResult != -1 && focusTimer < FOCUS_DELAY)
focusTimer += delta; focusTimer += delta;
@ -553,19 +574,23 @@ public class DownloadsMenu extends BasicGameState {
DownloadNode[] nodes = resultList; DownloadNode[] nodes = resultList;
if (nodes != null) { if (nodes != null) {
if (DownloadNode.resultAreaContains(x, y)) { if (DownloadNode.resultAreaContains(x, y)) {
startResultPos.pressed();
int maxResultsShown = DownloadNode.maxResultsShown(); int maxResultsShown = DownloadNode.maxResultsShown();
for (int i = 0; i < maxResultsShown; i++) { for (int i = 0; i < maxResultsShown + 1; i++) {
int startResult = (int) (startResultPos.getPosition() / DownloadNode.getButtonOffset());
int offset = (int) (-startResultPos.getPosition() + startResult * DownloadNode.getButtonOffset());
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 - offset, i)) {
final DownloadNode node = nodes[index]; final DownloadNode node = nodes[index];
// check if map is already loaded // check if map is already loaded
boolean isLoaded = BeatmapSetList.get().containsBeatmapSetID(node.getID()); boolean isLoaded = BeatmapSetList.get().containsBeatmapSetID(node.getID());
// track preview // track preview
if (DownloadNode.resultIconContains(x, y, i)) { if (DownloadNode.resultIconContains(x, y - offset, i)) {
// set focus // set focus
if (!isLoaded) { if (!isLoaded) {
SoundController.playSound(SoundEffect.MENUCLICK); SoundController.playSound(SoundEffect.MENUCLICK);
@ -738,12 +763,15 @@ public class DownloadsMenu extends BasicGameState {
// downloads // downloads
if (!DownloadList.get().isEmpty() && DownloadNode.downloadAreaContains(x, y)) { if (!DownloadList.get().isEmpty() && DownloadNode.downloadAreaContains(x, y)) {
startDownloadIndexPos.pressed();
int maxDownloadsShown = DownloadNode.maxDownloadsShown(); int maxDownloadsShown = DownloadNode.maxDownloadsShown();
for (int i = 0, n = DownloadList.get().size(); i < maxDownloadsShown; i++) { int startDownloadIndex = (int) (startDownloadIndexPos.getPosition() / DownloadNode.getInfoHeight());
int offset = (int) (-startDownloadIndexPos.getPosition() + startDownloadIndex * DownloadNode.getInfoHeight());
for (int i = 0, n = DownloadList.get().size(); i < maxDownloadsShown + 1; i++) {
int index = startDownloadIndex + i; int index = startDownloadIndex + i;
if (index >= n) if (index >= n)
break; break;
if (DownloadNode.downloadIconContains(x, y, i)) { if (DownloadNode.downloadIconContains(x, y - offset, i)) {
SoundController.playSound(SoundEffect.MENUCLICK); SoundController.playSound(SoundEffect.MENUCLICK);
DownloadNode node = DownloadList.get().getNode(index); DownloadNode node = DownloadList.get().getNode(index);
if (node == null) if (node == null)
@ -767,6 +795,12 @@ public class DownloadsMenu extends BasicGameState {
} }
} }
@Override
public void mouseReleased(int button, int x, int y) {
startDownloadIndexPos.release();
startResultPos.release();
}
@Override @Override
public void mouseWheelMoved(int newValue) { public void mouseWheelMoved(int newValue) {
// change volume // change volume
@ -798,8 +832,8 @@ public class DownloadsMenu extends BasicGameState {
int diff = newy - oldy; int diff = newy - oldy;
if (diff == 0) if (diff == 0)
return; return;
int shift = (diff < 0) ? 1 : -1; startDownloadIndexPos.dragged(-diff);
scrollLists(oldx, oldy, shift); startResultPos.dragged(-diff);
} }
@Override @Override
@ -871,8 +905,8 @@ public class DownloadsMenu extends BasicGameState {
serverMenu.activate(); serverMenu.activate();
serverMenu.reset(); serverMenu.reset();
focusResult = -1; focusResult = -1;
startResult = 0; startResultPos.setPosition(0);
startDownloadIndex = 0; startDownloadIndexPos.setPosition(0);
pageDir = Page.RESET; pageDir = Page.RESET;
previewID = -1; previewID = -1;
if (barNotificationOnLoad != null) { if (barNotificationOnLoad != null) {
@ -913,21 +947,12 @@ public class DownloadsMenu extends BasicGameState {
private void scrollLists(int cx, int cy, int shift) { private void scrollLists(int cx, int cy, int shift) {
// search results // search results
if (DownloadNode.resultAreaContains(cx, cy)) { if (DownloadNode.resultAreaContains(cx, cy)) {
DownloadNode[] nodes = resultList; startResultPos.scrollOffset(shift * DownloadNode.getButtonOffset());
if (nodes != null && nodes.length >= DownloadNode.maxResultsShown()) {
int newStartResult = startResult + shift;
if (newStartResult >= 0 && newStartResult + DownloadNode.maxResultsShown() <= nodes.length)
startResult = newStartResult;
}
} }
// downloads // downloads
else if (DownloadNode.downloadAreaContains(cx, cy)) { else if (DownloadNode.downloadAreaContains(cx, cy)) {
if (DownloadList.get().size() >= DownloadNode.maxDownloadsShown()) { startDownloadIndexPos.scrollOffset(shift * DownloadNode.getInfoHeight());
int newStartDownloadIndex = startDownloadIndex + shift;
if (newStartDownloadIndex >= 0 && newStartDownloadIndex + DownloadNode.maxDownloadsShown() <= DownloadList.get().size())
startDownloadIndex = newStartDownloadIndex;
}
} }
} }

View File

@ -44,6 +44,7 @@ import itdelatrisu.opsu.beatmap.OszUnpacker;
import itdelatrisu.opsu.db.BeatmapDB; import itdelatrisu.opsu.db.BeatmapDB;
import itdelatrisu.opsu.db.ScoreDB; import itdelatrisu.opsu.db.ScoreDB;
import itdelatrisu.opsu.states.ButtonMenu.MenuState; import itdelatrisu.opsu.states.ButtonMenu.MenuState;
import itdelatrisu.opsu.ui.KineticScrolling;
import itdelatrisu.opsu.ui.Colors; import itdelatrisu.opsu.ui.Colors;
import itdelatrisu.opsu.ui.Fonts; import itdelatrisu.opsu.ui.Fonts;
import itdelatrisu.opsu.ui.MenuButton; import itdelatrisu.opsu.ui.MenuButton;
@ -136,6 +137,12 @@ public class SongMenu extends BasicGameState {
/** Current start node (topmost menu entry). */ /** Current start node (topmost menu entry). */
private BeatmapSetNode startNode; private BeatmapSetNode startNode;
/** The first node is about this high above the header. */
KineticScrolling songScrolling = new KineticScrolling();
/** The number of Nodes to offset from the top to the startNode. */
private int startNodeOffset;
/** Current focused (selected) node. */ /** Current focused (selected) node. */
private BeatmapSetNode focusNode; private BeatmapSetNode focusNode;
@ -155,7 +162,7 @@ public class SongMenu extends BasicGameState {
private AnimatedValue hoverOffset = new AnimatedValue(250, 0, MAX_HOVER_OFFSET, AnimationEquation.OUT_QUART); private AnimatedValue hoverOffset = new AnimatedValue(250, 0, MAX_HOVER_OFFSET, AnimationEquation.OUT_QUART);
/** Current index of hovered song button. */ /** Current index of hovered song button. */
private int hoverIndex = -1; private BeatmapSetNode hoverIndex = null;
/** The selection buttons. */ /** The selection buttons. */
private MenuButton selectModsButton, selectRandomButton, selectMapOptionsButton, selectOptionsButton; private MenuButton selectModsButton, selectRandomButton, selectMapOptionsButton, selectOptionsButton;
@ -203,7 +210,8 @@ public class SongMenu extends BasicGameState {
private ScoreData[] focusScores; private ScoreData[] focusScores;
/** Current start score (topmost score entry). */ /** Current start score (topmost score entry). */
private int startScore = 0; KineticScrolling startScorePos = new KineticScrolling();
/** Header and footer end and start y coordinates, respectively. */ /** Header and footer end and start y coordinates, respectively. */
private float headerY, footerY; private float headerY, footerY;
@ -293,7 +301,7 @@ public class SongMenu extends BasicGameState {
// song button coordinates // song button coordinates
buttonX = width * 0.6f; buttonX = width * 0.6f;
buttonY = headerY; //buttonY = headerY;
buttonWidth = menuBackground.getWidth(); buttonWidth = menuBackground.getWidth();
buttonHeight = menuBackground.getHeight(); buttonHeight = menuBackground.getHeight();
buttonOffset = (footerY - headerY - DIVIDER_LINE_WIDTH) / MAX_SONG_BUTTONS; buttonOffset = (footerY - headerY - DIVIDER_LINE_WIDTH) / MAX_SONG_BUTTONS;
@ -378,15 +386,63 @@ public class SongMenu extends BasicGameState {
songButtonIndex = -1; songButtonIndex = -1;
} }
g.setClip(0, (int) (headerY + DIVIDER_LINE_WIDTH / 2), width, (int) (footerY - headerY)); g.setClip(0, (int) (headerY + DIVIDER_LINE_WIDTH / 2), width, (int) (footerY - headerY));
for (int i = songButtonIndex; i <= MAX_SONG_BUTTONS && node != null; i++, node = node.next) { for (int i = startNodeOffset + songButtonIndex; i < MAX_SONG_BUTTONS + 1 && node != null; i++, node = node.next) {
// draw the node // draw the node
float offset = (i == hoverIndex) ? hoverOffset.getValue() : 0f; float offset = (node == hoverIndex) ? hoverOffset.getValue() : 0f;
float ypos = buttonY + (i*buttonOffset) ;
float mid = height/2 - ypos - buttonOffset/2;
final float circleRadi = 700 * GameImage.getUIscale();
//finds points along a very large circle (x^2 = h^2 - y^2)
float t = circleRadi * circleRadi - (mid * mid);
float xpos = (float)(t>0?Math.sqrt(t):0) - circleRadi + 50 * GameImage.getUIscale();
ScoreData[] scores = getScoreDataForNode(node, false); ScoreData[] scores = getScoreDataForNode(node, false);
node.draw(buttonX - offset, buttonY + (i*buttonOffset) + DIVIDER_LINE_WIDTH / 2, node.draw(buttonX - offset - xpos, ypos,
(scores == null) ? Grade.NULL : scores[0].getGrade(), (node == focusNode)); (scores == null) ? Grade.NULL : scores[0].getGrade(), (node == focusNode));
} }
g.clearClip(); g.clearClip();
// scroll bar
if (focusNode != null && startNode != null) {
int focusNodes = focusNode.getBeatmapSet().size();
int totalNodes = BeatmapSetList.get().size() + focusNodes - 1;
if (totalNodes > MAX_SONG_BUTTONS) {
UI.drawScrollbar(g,
songScrolling.getPosition(),
totalNodes * buttonOffset,
MAX_SONG_BUTTONS * buttonOffset,
width, headerY + DIVIDER_LINE_WIDTH / 2,
0, MAX_SONG_BUTTONS * buttonOffset,
Colors.BLACK_ALPHA, Color.white, true);
}
}
// score buttons
if (focusScores != null) {
ScoreData.clipToDownloadArea(g);
int startScore = (int) (startScorePos.getPosition() / ScoreData.getButtonOffset());
int offset = (int) (-startScorePos.getPosition() + startScore * ScoreData.getButtonOffset());
int scoreButtons = Math.min(focusScores.length - startScore, MAX_SCORE_BUTTONS + 1);
float timerScale = 1f - (1 / 3f) * ((MAX_SCORE_BUTTONS - scoreButtons) / (float) (MAX_SCORE_BUTTONS - 1));
int duration = (int) (songChangeTimer.getDuration() * timerScale);
int segmentDuration = (int) ((2 / 3f) * songChangeTimer.getDuration());
int time = songChangeTimer.getTime();
for (int i = 0, rank = startScore; i < scoreButtons; i++, rank++) {
if (rank < 0)
continue;
long prevScore = (rank + 1 < focusScores.length) ? focusScores[rank + 1].score : -1;
float t = Utils.clamp((time - (i * (duration - segmentDuration) / scoreButtons)) / (float) segmentDuration, 0f, 1f);
boolean focus = (t >= 0.9999f && ScoreData.buttonContains(mouseX, mouseY - offset, i));
focusScores[rank].draw(g, offset + i*ScoreData.getButtonOffset(), rank, prevScore, focus, t);
}
g.clearClip();
// scroll bar
if (focusScores.length > MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY))
ScoreData.drawScrollbar(g, startScorePos.getPosition() , focusScores.length * ScoreData.getButtonOffset());
}
// top/bottom bars // top/bottom bars
g.setColor(Colors.BLACK_ALPHA); g.setColor(Colors.BLACK_ALPHA);
g.fillRect(0, 0, width, headerY); g.fillRect(0, 0, width, headerY);
@ -463,25 +519,6 @@ public class SongMenu extends BasicGameState {
c.a = oldAlpha; c.a = oldAlpha;
} }
// score buttons
if (focusScores != null) {
int scoreButtons = Math.min(focusScores.length - startScore, MAX_SCORE_BUTTONS);
float timerScale = 1f - (1 / 3f) * ((MAX_SCORE_BUTTONS - scoreButtons) / (float) (MAX_SCORE_BUTTONS - 1));
int duration = (int) (songChangeTimer.getDuration() * timerScale);
int segmentDuration = (int) ((2 / 3f) * songChangeTimer.getDuration());
int time = songChangeTimer.getTime();
for (int i = 0, rank = startScore; i < scoreButtons; i++, rank++) {
long prevScore = (rank + 1 < focusScores.length) ? focusScores[rank + 1].score : -1;
float t = Utils.clamp((time - (i * (duration - segmentDuration) / scoreButtons)) / (float) segmentDuration, 0f, 1f);
boolean focus = (t >= 0.9999f && ScoreData.buttonContains(mouseX, mouseY, i));
focusScores[rank].draw(g, i, rank, prevScore, focus, t);
}
// scroll bar
if (focusScores.length > MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY))
ScoreData.drawScrollbar(g, startScore, focusScores.length);
}
// selection buttons // selection buttons
GameImage.SELECTION_MODS.getImage().drawCentered(selectModsButton.getX(), selectModsButton.getY()); GameImage.SELECTION_MODS.getImage().drawCentered(selectModsButton.getX(), selectModsButton.getY());
selectModsButton.draw(); selectModsButton.draw();
@ -540,22 +577,6 @@ public class SongMenu extends BasicGameState {
(searchResultString == null) ? "Searching..." : searchResultString, Color.white); (searchResultString == null) ? "Searching..." : searchResultString, Color.white);
} }
// scroll bar
if (focusNode != null) {
int focusNodes = focusNode.getBeatmapSet().size();
int totalNodes = BeatmapSetList.get().size() + focusNodes - 1;
if (totalNodes > MAX_SONG_BUTTONS) {
int startIndex = startNode.index;
if (startNode.index > focusNode.index)
startIndex += focusNodes;
else if (startNode.index == focusNode.index)
startIndex += startNode.beatmapIndex;
UI.drawScrollbar(g, startIndex, totalNodes, MAX_SONG_BUTTONS,
width, headerY + DIVIDER_LINE_WIDTH / 2, 0, buttonOffset - DIVIDER_LINE_WIDTH * 1.5f, buttonOffset,
Colors.BLACK_ALPHA, Color.white, true);
}
}
// reloading beatmaps // reloading beatmaps
if (reloadThread != null) { if (reloadThread != null) {
// darken the screen // darken the screen
@ -660,30 +681,26 @@ public class SongMenu extends BasicGameState {
searchTransitionTimer = SEARCH_TRANSITION_TIME; searchTransitionTimer = SEARCH_TRANSITION_TIME;
} }
// slide buttons // Scores
int height = container.getHeight(); if (focusScores != null) {
if (buttonY > headerY) { startScorePos.setMinMax(0, (focusScores.length - MAX_SCORE_BUTTONS) * ScoreData.getButtonOffset());
buttonY -= height * delta / 20000f; startScorePos.update(delta);
if (buttonY < headerY)
buttonY = headerY;
} else if (buttonY < headerY) {
buttonY += height * delta / 20000f;
if (buttonY > headerY)
buttonY = headerY;
} }
// mouse hover // mouse hover
songScrolling.update(delta);
updateDrawnSongPos();
boolean isHover = false; boolean isHover = false;
if (mouseY > headerY && mouseY < footerY) { if (mouseY > headerY && mouseY < footerY) {
BeatmapSetNode node = startNode; BeatmapSetNode node = startNode;
for (int i = 0; i < MAX_SONG_BUTTONS && node != null; i++, node = node.next) { for (int i = 0; i < MAX_SONG_BUTTONS + 1 && node != null; i++, node = node.next) {
float cx = (node.index == BeatmapSetList.get().getExpandedIndex()) ? buttonX * 0.9f : buttonX; float cx = (node.index == BeatmapSetList.get().getExpandedIndex()) ? buttonX * 0.9f : buttonX;
if ((mouseX > cx && mouseX < cx + buttonWidth) && if ((mouseX > cx && mouseX < cx + buttonWidth) &&
(mouseY > buttonY + (i * buttonOffset) && mouseY < buttonY + (i * buttonOffset) + buttonHeight)) { (mouseY > buttonY + (i * buttonOffset) && mouseY < buttonY + (i * buttonOffset) + buttonHeight)) {
if (i == hoverIndex) if (node == hoverIndex) {
hoverOffset.update(delta); hoverOffset.update(delta);
else { } else {
hoverIndex = i; hoverIndex = node ;
hoverOffset.setTime(0); hoverOffset.setTime(0);
} }
isHover = true; isHover = true;
@ -693,15 +710,19 @@ public class SongMenu extends BasicGameState {
} }
if (!isHover) { if (!isHover) {
hoverOffset.setTime(0); hoverOffset.setTime(0);
hoverIndex = -1; hoverIndex = null;
} else } else
return; return;
// tooltips // tooltips
if (focusScores != null && ScoreData.areaContains(mouseX, mouseY)) { if (focusScores != null && ScoreData.areaContains(mouseX, mouseY)) {
int startScore = (int) (startScorePos.getPosition() / ScoreData.getButtonOffset());
int offset = (int) (-startScorePos.getPosition() + startScore * ScoreData.getButtonOffset());
int scoreButtons = Math.min(focusScores.length - startScore, MAX_SCORE_BUTTONS); int scoreButtons = Math.min(focusScores.length - startScore, MAX_SCORE_BUTTONS);
for (int i = 0, rank = startScore; i < scoreButtons; i++, rank++) { for (int i = 0, rank = startScore; i < scoreButtons; i++, rank++) {
if (ScoreData.buttonContains(mouseX, mouseY, i)) { if (rank < 0)
continue;
if (ScoreData.buttonContains(mouseX, mouseY - offset, i)) {
UI.updateTooltip(delta, focusScores[rank].getTooltipString(), true); UI.updateTooltip(delta, focusScores[rank].getTooltipString(), true);
break; break;
} }
@ -714,6 +735,18 @@ public class SongMenu extends BasicGameState {
@Override @Override
public void mousePressed(int button, int x, int y) { public void mousePressed(int button, int x, int y) {
songScrolling.pressed();
startScorePos.pressed();
}
@Override
public void mouseReleased(int button, int x, int y) {
songScrolling.release();
startScorePos.release();
}
@Override
public void mouseClicked(int button, int x, int y, int clickCount) {
// check mouse button // check mouse button
if (button == Input.MOUSE_MIDDLE_BUTTON) if (button == Input.MOUSE_MIDDLE_BUTTON)
return; return;
@ -769,13 +802,13 @@ public class SongMenu extends BasicGameState {
if (y > headerY && y < footerY) { if (y > headerY && y < footerY) {
int expandedIndex = BeatmapSetList.get().getExpandedIndex(); int expandedIndex = BeatmapSetList.get().getExpandedIndex();
BeatmapSetNode node = startNode; BeatmapSetNode node = startNode;
for (int i = 0; i < MAX_SONG_BUTTONS && node != null; i++, node = node.next) { for (int i = startNodeOffset; i < MAX_SONG_BUTTONS + 1 && node != null; i++, node = node.next) {
// is button at this index clicked? // is button at this index clicked?
float cx = (node.index == expandedIndex) ? buttonX * 0.9f : buttonX; float cx = (node.index == expandedIndex) ? buttonX * 0.9f : buttonX;
if ((x > cx && x < cx + buttonWidth) && if ((x > cx && x < cx + buttonWidth) &&
(y > buttonY + (i * buttonOffset) && y < buttonY + (i * buttonOffset) + buttonHeight)) { (y > buttonY + (i * buttonOffset) && y < buttonY + (i * buttonOffset) + buttonHeight)) {
int oldHoverOffsetTime = hoverOffset.getTime(); int oldHoverOffsetTime = hoverOffset.getTime();
int oldHoverIndex = hoverIndex; BeatmapSetNode oldHoverIndex = hoverIndex;
// clicked node is already expanded // clicked node is already expanded
if (node.index == expandedIndex) { if (node.index == expandedIndex) {
@ -813,9 +846,11 @@ public class SongMenu extends BasicGameState {
// score buttons // score buttons
if (focusScores != null && ScoreData.areaContains(x, y)) { if (focusScores != null && ScoreData.areaContains(x, y)) {
int startScore = (int) (startScorePos.getPosition() / ScoreData.getButtonOffset());
int offset = (int) (-startScorePos.getPosition() + startScore * ScoreData.getButtonOffset());
int scoreButtons = Math.min(focusScores.length - startScore, MAX_SCORE_BUTTONS); int scoreButtons = Math.min(focusScores.length - startScore, MAX_SCORE_BUTTONS);
for (int i = 0, rank = startScore; i < scoreButtons; i++, rank++) { for (int i = 0, rank = startScore; i < scoreButtons; i++, rank++) {
if (ScoreData.buttonContains(x, y, i)) { if (ScoreData.buttonContains(x, y - offset, i)) {
SoundController.playSound(SoundEffect.MENUHIT); SoundController.playSound(SoundEffect.MENUHIT);
if (button != Input.MOUSE_RIGHT_BUTTON) { if (button != Input.MOUSE_RIGHT_BUTTON) {
// view score // view score
@ -937,7 +972,7 @@ public class SongMenu extends BasicGameState {
SoundController.playSound(SoundEffect.MENUCLICK); SoundController.playSound(SoundEffect.MENUCLICK);
BeatmapSetNode oldStartNode = startNode; BeatmapSetNode oldStartNode = startNode;
int oldHoverOffsetTime = hoverOffset.getTime(); int oldHoverOffsetTime = hoverOffset.getTime();
int oldHoverIndex = hoverIndex; BeatmapSetNode oldHoverIndex = hoverIndex;
setFocus(next, 0, false, true); setFocus(next, 0, false, true);
if (startNode == oldStartNode) { if (startNode == oldStartNode) {
hoverOffset.setTime(oldHoverOffsetTime); hoverOffset.setTime(oldHoverOffsetTime);
@ -953,7 +988,7 @@ public class SongMenu extends BasicGameState {
SoundController.playSound(SoundEffect.MENUCLICK); SoundController.playSound(SoundEffect.MENUCLICK);
BeatmapSetNode oldStartNode = startNode; BeatmapSetNode oldStartNode = startNode;
int oldHoverOffsetTime = hoverOffset.getTime(); int oldHoverOffsetTime = hoverOffset.getTime();
int oldHoverIndex = hoverIndex; BeatmapSetNode oldHoverIndex = hoverIndex;
setFocus(prev, (prev.index == focusNode.index) ? 0 : prev.getBeatmapSet().size() - 1, false, true); setFocus(prev, (prev.index == focusNode.index) ? 0 : prev.getBeatmapSet().size() - 1, false, true);
if (startNode == oldStartNode) { if (startNode == oldStartNode) {
hoverOffset.setTime(oldHoverOffsetTime); hoverOffset.setTime(oldHoverOffsetTime);
@ -995,12 +1030,11 @@ public class SongMenu extends BasicGameState {
int diff = newy - oldy; int diff = newy - oldy;
if (diff == 0) if (diff == 0)
return; return;
int shift = (diff < 0) ? 1 : -1;
// check mouse button (right click scrolls faster on songs) // check mouse button (right click scrolls faster on songs)
int multiplier; int multiplier;
if (input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON)) if (input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON))
multiplier = 4; multiplier = 10;
else if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) else if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON))
multiplier = 1; multiplier = 1;
else else
@ -1008,14 +1042,13 @@ public class SongMenu extends BasicGameState {
// score buttons // score buttons
if (focusScores != null && focusScores.length >= MAX_SCORE_BUTTONS && ScoreData.areaContains(oldx, oldy)) { if (focusScores != null && focusScores.length >= MAX_SCORE_BUTTONS && ScoreData.areaContains(oldx, oldy)) {
int newStartScore = startScore + shift; startScorePos.dragged(-diff * multiplier);
if (newStartScore >= 0 && newStartScore + MAX_SCORE_BUTTONS <= focusScores.length)
startScore = newStartScore;
} }
// song buttons // song buttons
else else
changeIndex(shift * multiplier); songScrolling.dragged(-diff * multiplier);
} }
@Override @Override
@ -1035,9 +1068,7 @@ public class SongMenu extends BasicGameState {
// score buttons // score buttons
if (focusScores != null && focusScores.length >= MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY)) { if (focusScores != null && focusScores.length >= MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY)) {
int newStartScore = startScore + shift; startScorePos.scrollOffset(ScoreData.getButtonOffset() * shift);
if (newStartScore >= 0 && newStartScore + MAX_SCORE_BUTTONS <= focusScores.length)
startScore = newStartScore;
} }
// song buttons // song buttons
@ -1055,8 +1086,8 @@ public class SongMenu extends BasicGameState {
selectMapOptionsButton.resetHover(); selectMapOptionsButton.resetHover();
selectOptionsButton.resetHover(); selectOptionsButton.resetHover();
hoverOffset.setTime(0); hoverOffset.setTime(0);
hoverIndex = -1; hoverIndex = null;
startScore = 0; startScorePos.setPosition(0);
beatmapMenuTimer = -1; beatmapMenuTimer = -1;
searchTransitionTimer = SEARCH_TRANSITION_TIME; searchTransitionTimer = SEARCH_TRANSITION_TIME;
songInfo = null; songInfo = null;
@ -1132,7 +1163,7 @@ public class SongMenu extends BasicGameState {
ScoreDB.deleteScore(stateActionScore); ScoreDB.deleteScore(stateActionScore);
scoreMap = ScoreDB.getMapSetScores(focusNode.getSelectedBeatmap()); scoreMap = ScoreDB.getMapSetScores(focusNode.getSelectedBeatmap());
focusScores = getScoreDataForNode(focusNode, true); focusScores = getScoreDataForNode(focusNode, true);
startScore = 0; startScorePos.setPosition(0);
break; break;
case BEATMAP_DELETE_CONFIRM: // delete song group case BEATMAP_DELETE_CONFIRM: // delete song group
if (stateActionNode == null) if (stateActionNode == null)
@ -1218,38 +1249,46 @@ public class SongMenu extends BasicGameState {
if (shift == 0) if (shift == 0)
return; return;
int n = shift; songScrolling.scrollOffset(shift * buttonOffset);
boolean shifted = false;
while (n != 0) {
if (startNode == null)
break;
int height = container.getHeight();
if (n < 0 && startNode.prev != null) {
startNode = startNode.prev;
buttonY += buttonOffset / 4;
if (buttonY > headerY + height * 0.02f)
buttonY = headerY + height * 0.02f;
n++;
shifted = true;
} else if (n > 0 && startNode.next != null &&
BeatmapSetList.get().getNode(startNode, MAX_SONG_BUTTONS) != null) {
startNode = startNode.next;
buttonY -= buttonOffset / 4;
if (buttonY < headerY - height * 0.02f)
buttonY = headerY - height * 0.02f;
n--;
shifted = true;
} else
break;
}
if (shifted) {
hoverOffset.setTime(0);
hoverIndex = -1;
}
return;
} }
/**
* Updates the song list data required for drawing.
*/
private void updateDrawnSongPos() {
float songNodePosDrawn = songScrolling.getPosition();
int startNodeIndex = (int) (songNodePosDrawn / buttonOffset);
buttonY = -songNodePosDrawn + buttonOffset * startNodeIndex + headerY - DIVIDER_LINE_WIDTH;
float max = (BeatmapSetList.get().size() + (focusNode != null ? focusNode.getBeatmapSet().size() : 0));
songScrolling.setMinMax(0 - buttonOffset * 2, (max - MAX_SONG_BUTTONS- 1 + 2) * buttonOffset);
//negative startNodeIndex means the first Node is below the header so offset it.
if (startNodeIndex <= 0) {
startNodeOffset = -startNodeIndex;
startNodeIndex = 0;
} else {
startNodeOffset = 0;
}
// Finds the start node with the expanded focus node in mind.
if (focusNode != null && startNodeIndex >= focusNode.index) {
//below the focus node.
if (startNodeIndex <= focusNode.index + focusNode.getBeatmapSet().size()) {
//inside the focus nodes expanded nodes.
int nodeIndex = startNodeIndex - focusNode.index;
startNode = BeatmapSetList.get().getBaseNode(focusNode.index);
startNode = startNode.next;
for (int i = 0; i < nodeIndex; i++)
startNode = startNode.next;
} else {
startNodeIndex -= focusNode.getBeatmapSet().size() - 1;
startNode = BeatmapSetList.get().getBaseNode(startNodeIndex);
}
} else
startNode = BeatmapSetList.get().getBaseNode(startNodeIndex);
}
/** /**
* Sets a new focus node. * Sets a new focus node.
* @param node the base node; it will be expanded if it isn't already * @param node the base node; it will be expanded if it isn't already
@ -1263,7 +1302,7 @@ public class SongMenu extends BasicGameState {
return null; return null;
hoverOffset.setTime(0); hoverOffset.setTime(0);
hoverIndex = -1; hoverIndex = null;
songInfo = null; songInfo = null;
songChangeTimer.setTime(0); songChangeTimer.setTime(0);
musicIconBounceTimer.setTime(0); musicIconBounceTimer.setTime(0);
@ -1287,9 +1326,6 @@ public class SongMenu extends BasicGameState {
if (beatmapIndex < 0 || beatmapIndex > length - 1) // set a random index if (beatmapIndex < 0 || beatmapIndex > length - 1) // set a random index
beatmapIndex = (int) (Math.random() * length); beatmapIndex = (int) (Math.random() * length);
// change the focus node
if (changeStartNode || (startNode.index == 0 && startNode.beatmapIndex == -1 && startNode.prev == null))
startNode = node;
focusNode = BeatmapSetList.get().getNode(node, beatmapIndex); focusNode = BeatmapSetList.get().getNode(node, beatmapIndex);
Beatmap beatmap = focusNode.getSelectedBeatmap(); Beatmap beatmap = focusNode.getSelectedBeatmap();
MusicController.play(beatmap, false, preview); MusicController.play(beatmap, false, preview);
@ -1297,31 +1333,58 @@ public class SongMenu extends BasicGameState {
// load scores // load scores
scoreMap = ScoreDB.getMapSetScores(beatmap); scoreMap = ScoreDB.getMapSetScores(beatmap);
focusScores = getScoreDataForNode(focusNode, true); focusScores = getScoreDataForNode(focusNode, true);
startScore = 0; startScorePos.setPosition(0);
// check startNode bounds if (oldFocus != null && oldFocus.getBeatmapSet() != node.getBeatmapSet()){
while (startNode.index >= BeatmapSetList.get().size() + length - MAX_SONG_BUTTONS && startNode.prev != null) //Close previous node
startNode = startNode.prev; if(node.index > oldFocus.index){
float offset = (oldFocus.getBeatmapSet().size() - 1) * buttonOffset;
//updateSongPos(-offset);
songScrolling.addOffset(-offset);
}
// make sure focusNode is on the screen (TODO: cleanup...) if (Math.abs(node.index - oldFocus.index) > MAX_SONG_BUTTONS) {
int val = focusNode.index + focusNode.beatmapIndex - (startNode.index + MAX_SONG_BUTTONS) + 1; // open from the middle
if (val > 0) // below screen float offset = ((node.getBeatmapSet().size() - 1) * buttonOffset) / 2f;
changeIndex(val); songScrolling.addOffset(offset);
else { // above screen } else if (node.index > oldFocus.index) {
if (focusNode.index == startNode.index) { // open from the bottom
val = focusNode.index + focusNode.beatmapIndex - (startNode.index + startNode.beatmapIndex); float offset = (node.getBeatmapSet().size() - 1) * buttonOffset;
if (val < 0) songScrolling.addOffset(offset);
changeIndex(val); } else {
} else if (startNode.index > focusNode.index) { // open from the top
val = focusNode.index - focusNode.getBeatmapSet().size() + focusNode.beatmapIndex - startNode.index + 1;
if (val < 0)
changeIndex(val);
} }
} }
// if start node is expanded and on group node, move it // change the focus node
if (startNode.index == focusNode.index && startNode.beatmapIndex == -1) if (changeStartNode || (startNode.index == 0 && startNode.beatmapIndex == -1 && startNode.prev == null)){
changeIndex(1); songScrolling.setPosition((node.index - 1) * buttonOffset);
updateDrawnSongPos();
}
updateDrawnSongPos();
// make sure focusNode is on the screen
int val = focusNode.index + focusNode.beatmapIndex;
if (val <= startNode.index)
songScrolling.scrollToPosition(val * buttonOffset);
else if (val > startNode.index + MAX_SONG_BUTTONS - 1)
songScrolling.scrollToPosition((val - MAX_SONG_BUTTONS + 1) * buttonOffset);
/*
//Centers selected node
int val = focusNode.index + focusNode.beatmapIndex - MAX_SONG_BUTTONS/2;
songScrolling.scrollToPosition(val * buttonOffset);
//*/
/*
//Attempts to make all nodes in the set at least visible
if( focusNode.index * buttonOffset < songScrolling.getPosition())
songScrolling.scrollToPosition(focusNode.index * buttonOffset);
if ( ( focusNode.index + focusNode.getBeatmapSet().size() ) * buttonOffset > songScrolling.getPosition() + footerY - headerY)
songScrolling.scrollToPosition((focusNode.index + focusNode.getBeatmapSet().size() ) * buttonOffset - (footerY - headerY));
//*/
// load background image // load background image
beatmap.loadBackground(); beatmap.loadBackground();
@ -1425,7 +1488,7 @@ public class SongMenu extends BasicGameState {
randomStack = new Stack<SongNode>(); randomStack = new Stack<SongNode>();
songInfo = null; songInfo = null;
hoverOffset.setTime(0); hoverOffset.setTime(0);
hoverIndex = -1; hoverIndex = null;
search.setText(""); search.setText("");
searchTimer = SEARCH_DELAY; searchTimer = SEARCH_DELAY;
searchTransitionTimer = SEARCH_TRANSITION_TIME; searchTransitionTimer = SEARCH_TRANSITION_TIME;

View File

@ -0,0 +1,155 @@
package itdelatrisu.opsu.ui;
/**
* Drag to scroll based on:
* http://ariya.ofilabs.com/2013/11/javascript-kinetic-scrolling-part-2.html
*
* @author fluddokt (https://github.com/fluddokt)
*/
public class KineticScrolling {
/** The moving averaging constant */
final static private float AVG_CONST = 0.2f;
final static private float ONE_MINUS_AVG_CONST = 1 - AVG_CONST;
/** The constant used to determine how fast the target position will be reach. */
final static private int TIME_CONST = 200;
/** The constant used to determine how much of the velocity will be used to launch to the target. */
final static private float AMPLITUDE_CONST = 0.25f;
/** The current position. */
float position;
/** The offset to scroll to the target position. */
float amplitude;
/** The current target to scroll to. */
float target;
/** The total amount of time since the mouse button was released. */
float totalDelta;
/** The maximum and minimum value the position can reach. */
float max = Float.MAX_VALUE;
float min = -Float.MAX_VALUE;
/** Whether the mouse is currently pressed or not */
boolean pressed = false;
/** The change in position since the last update */
float deltaPosition;
/** The moving average of the velocity. */
float avgVelocity;
/**
* Returns the current Position.
* @return the position.
*/
public float getPosition() {
return position;
}
/**
* Updates the scrolling.
* @param delta the elapsed time since the last update
*/
public void update(float delta) {
if (!pressed) {
totalDelta += delta;
position = target + (float) (-amplitude * Math.exp(-totalDelta / TIME_CONST));
} else {
avgVelocity = (ONE_MINUS_AVG_CONST * avgVelocity + AVG_CONST * (deltaPosition * 1000f / delta));
position += deltaPosition;
target = position ;
deltaPosition = 0;
}
if (position > max) {
amplitude = 0;
target = position = max;
}
if (position < min) {
amplitude = 0;
target = position = min;
}
}
/**
* Scrolls to the position.
* @param newPosition the position to scroll to.
*/
public void scrollToPosition(float newPosition) {
amplitude = newPosition - position;
target = newPosition;
totalDelta = 0;
}
/**
* Scrolls to an offset from target.
* @param offset the offset from the target to scroll to.
*/
public void scrollOffset(float offset) {
scrollToPosition(target + offset);
}
/**
* Sets the position.
* @param newPosition the position to be set
*/
public void setPosition(float newPosition) {
pressed();
release();
target = newPosition;
position = target;
}
/**
* Set the position relative to an offset.
* @param offset the offset from the position.
*/
public void addOffset(float offset) {
setPosition(position + offset);
}
/**
* Call this when the mouse button has been pressed.
*/
public void pressed() {
if (pressed)
return;
pressed = true;
avgVelocity = 0;
}
/**
* Call this when the mouse button has been released.
*/
public void release() {
if (!pressed)
return;
pressed = false;
amplitude = AMPLITUDE_CONST * avgVelocity;
target = Math.round(target + amplitude);
totalDelta = 0;
}
/**
* Call this when the mouse has been dragged.
* @param distance the amount that the mouse has been dragged
*/
public void dragged(float distance) {
deltaPosition += distance;
}
/**
* Set the minimum and maximum bound.
* @param min the minimum bound
* @param max the maximum bound
*/
public void setMinMax(float min, float max) {
this.min = min;
this.max = max;
}
}

View File

@ -316,28 +316,25 @@ public class UI {
/** /**
* Draws a scroll bar. * Draws a scroll bar.
* @param g the graphics context * @param g the graphics context
* @param unitIndex the unit index * @param position the position in the virtual area
* @param totalUnits the total number of units * @param totalLength the total length of the virtual area
* @param maxShown the maximum number of units shown at one time * @param lengthShown the length of the virtual area shown
* @param unitBaseX the base x coordinate of the units * @param unitBaseX the base x coordinate
* @param unitBaseY the base y coordinate of the units * @param unitBaseY the base y coordinate
* @param unitWidth the width of a unit * @param unitWidth the width of a unit
* @param unitHeight the height of a unit * @param scrollAreaHeight the height of the scroll area
* @param unitOffsetY the y offset between units
* @param bgColor the scroll bar area background color (null if none) * @param bgColor the scroll bar area background color (null if none)
* @param scrollbarColor the scroll bar color * @param scrollbarColor the scroll bar color
* @param right whether or not to place the scroll bar on the right side of the unit * @param right whether or not to place the scroll bar on the right side of the unit
*/ */
public static void drawScrollbar( public static void drawScrollbar(
Graphics g, int unitIndex, int totalUnits, int maxShown, Graphics g, float position, float totalLength, float lengthShown,
float unitBaseX, float unitBaseY, float unitWidth, float unitHeight, float unitOffsetY, float unitBaseX, float unitBaseY, float unitWidth, float scrollAreaHeight,
Color bgColor, Color scrollbarColor, boolean right Color bgColor, Color scrollbarColor, boolean right
) { ) {
float scrollbarWidth = container.getWidth() * 0.00347f; float scrollbarWidth = container.getWidth() * 0.00347f;
float heightRatio = (float) (2.6701f * Math.exp(-0.81 * Math.log(totalUnits))); float scrollbarHeight = scrollAreaHeight * lengthShown / totalLength;
float scrollbarHeight = container.getHeight() * heightRatio; float offsetY = (scrollAreaHeight - scrollbarHeight) * (position / (totalLength - lengthShown));
float scrollAreaHeight = unitHeight + unitOffsetY * (maxShown - 1);
float offsetY = (scrollAreaHeight - scrollbarHeight) * ((float) unitIndex / (totalUnits - maxShown));
float scrollbarX = unitBaseX + unitWidth - ((right) ? scrollbarWidth : 0); float scrollbarX = unitBaseX + unitWidth - ((right) ? scrollbarWidth : 0);
if (bgColor != null) { if (bgColor != null) {
g.setColor(bgColor); g.setColor(bgColor);

View File

@ -1240,6 +1240,8 @@ public class Input {
pressedX = (int) (xoffset + (Mouse.getEventX() * scaleX)); pressedX = (int) (xoffset + (Mouse.getEventX() * scaleX));
pressedY = (int) (yoffset + ((height-Mouse.getEventY()) * scaleY)); pressedY = (int) (yoffset + ((height-Mouse.getEventY()) * scaleY));
lastMouseX = pressedX;
lastMouseY = pressedY;
for (int i=0;i<mouseListeners.size();i++) { for (int i=0;i<mouseListeners.size();i++) {
MouseListener listener = (MouseListener) mouseListeners.get(i); MouseListener listener = (MouseListener) mouseListeners.get(i);