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;
/** 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.
@ -99,6 +99,14 @@ public class ScoreData implements Comparable<ScoreData> {
float gradeHeight = GameImage.MENU_BUTTON_BG.getImage().getHeight() * 0.45f;
buttonHeight = Math.max(gradeHeight, Fonts.DEFAULT.getLineHeight() * 3.03f);
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 total the total number of buttons
*/
public static void drawScrollbar(Graphics g, int index, int total) {
UI.drawScrollbar(g, index, total, SongMenu.MAX_SCORE_BUTTONS, 0, baseY,
0, buttonHeight, buttonOffset, null, Color.white, false);
public static void drawScrollbar(Graphics g, float pos, float total) {
UI.drawScrollbar(g, pos, total, SongMenu.MAX_SCORE_BUTTONS * buttonOffset, 0, baseY,
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 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 textX = x + buttonWidth * 0.24f;
float edgeX = x + buttonWidth * 0.98f;
float y = baseY + index * (buttonOffset);
float y = baseY + position;
float midY = y + buttonHeight / 2f;
float marginY = Fonts.DEFAULT.getLineHeight() * 0.01f;
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));
}
/**
* 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
* download information button at the given index.
@ -183,6 +191,21 @@ public class DownloadNode {
(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
* download information button area.
@ -197,12 +220,13 @@ public class DownloadNode {
/**
* Draws the scroll bar for the download result buttons.
* @param g the graphics context
* @param index the start button index
* @param position the start button index
* @param total the total number of buttons
*/
public static void drawResultScrollbar(Graphics g, int index, int total) {
UI.drawScrollbar(g, index, total, maxResultsShown, buttonBaseX, buttonBaseY,
buttonWidth * 1.01f, buttonHeight, buttonOffset, Colors.BLACK_BG_NORMAL, Color.white, true);
public static void drawResultScrollbar(Graphics g, float position, float total) {
UI.drawScrollbar(g, position, total, maxResultsShown * buttonOffset, buttonBaseX, buttonBaseY,
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 total the total number of downloads
*/
public static void drawDownloadScrollbar(Graphics g, int index, int total) {
UI.drawScrollbar(g, index, total, maxDownloadsShown, infoBaseX, infoBaseY,
infoWidth, infoHeight, infoHeight, Colors.BLACK_BG_NORMAL, Color.white, true);
public static void drawDownloadScrollbar(Graphics g, float index, float total) {
UI.drawScrollbar(g, index, total, maxDownloadsShown * infoHeight, infoBaseX, infoBaseY,
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.
* @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 focus true if the button is focused
* @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 edgeX = buttonBaseX + buttonWidth * 0.985f;
float y = buttonBaseY + index * buttonOffset;
float y = buttonBaseY + position;
float marginY = buttonHeight * 0.04f;
Download dl = DownloadList.get().getDownload(beatmapSetID);
@ -356,12 +380,13 @@ public class DownloadNode {
Fonts.loadGlyphs(Fonts.BOLD, getTitle());
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(
textX, y + marginY,
String.format("%s - %s%s", getArtist(), getTitle(),
(dl != null) ? String.format(" [%s]", dl.getStatus().getName()) : ""), Color.white);
g.clearClip();
//g.clearClip();
Fonts.DEFAULT.drawString(
textX, y + marginY + Fonts.BOLD.getLineHeight(),
String.format("Last updated: %s", date), Color.white);
@ -373,11 +398,11 @@ public class DownloadNode {
/**
* Draws the download information.
* @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 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
if (download == null) {
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 edgeX = infoBaseX + infoWidth * 0.985f;
float y = infoBaseY + index * infoHeight;
float y = infoBaseY + position;
float marginY = infoHeight * 0.04f;
// rectangle outline

View File

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

View File

@ -44,6 +44,7 @@ import itdelatrisu.opsu.beatmap.OszUnpacker;
import itdelatrisu.opsu.db.BeatmapDB;
import itdelatrisu.opsu.db.ScoreDB;
import itdelatrisu.opsu.states.ButtonMenu.MenuState;
import itdelatrisu.opsu.ui.KineticScrolling;
import itdelatrisu.opsu.ui.Colors;
import itdelatrisu.opsu.ui.Fonts;
import itdelatrisu.opsu.ui.MenuButton;
@ -136,6 +137,12 @@ public class SongMenu extends BasicGameState {
/** Current start node (topmost menu entry). */
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. */
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);
/** Current index of hovered song button. */
private int hoverIndex = -1;
private BeatmapSetNode hoverIndex = null;
/** The selection buttons. */
private MenuButton selectModsButton, selectRandomButton, selectMapOptionsButton, selectOptionsButton;
@ -203,7 +210,8 @@ public class SongMenu extends BasicGameState {
private ScoreData[] focusScores;
/** Current start score (topmost score entry). */
private int startScore = 0;
KineticScrolling startScorePos = new KineticScrolling();
/** Header and footer end and start y coordinates, respectively. */
private float headerY, footerY;
@ -293,7 +301,7 @@ public class SongMenu extends BasicGameState {
// song button coordinates
buttonX = width * 0.6f;
buttonY = headerY;
//buttonY = headerY;
buttonWidth = menuBackground.getWidth();
buttonHeight = menuBackground.getHeight();
buttonOffset = (footerY - headerY - DIVIDER_LINE_WIDTH) / MAX_SONG_BUTTONS;
@ -378,15 +386,63 @@ public class SongMenu extends BasicGameState {
songButtonIndex = -1;
}
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
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);
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));
}
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
g.setColor(Colors.BLACK_ALPHA);
g.fillRect(0, 0, width, headerY);
@ -463,25 +519,6 @@ public class SongMenu extends BasicGameState {
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
GameImage.SELECTION_MODS.getImage().drawCentered(selectModsButton.getX(), selectModsButton.getY());
selectModsButton.draw();
@ -540,22 +577,6 @@ public class SongMenu extends BasicGameState {
(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
if (reloadThread != null) {
// darken the screen
@ -660,30 +681,26 @@ public class SongMenu extends BasicGameState {
searchTransitionTimer = SEARCH_TRANSITION_TIME;
}
// slide buttons
int height = container.getHeight();
if (buttonY > headerY) {
buttonY -= height * delta / 20000f;
if (buttonY < headerY)
buttonY = headerY;
} else if (buttonY < headerY) {
buttonY += height * delta / 20000f;
if (buttonY > headerY)
buttonY = headerY;
// Scores
if (focusScores != null) {
startScorePos.setMinMax(0, (focusScores.length - MAX_SCORE_BUTTONS) * ScoreData.getButtonOffset());
startScorePos.update(delta);
}
// mouse hover
songScrolling.update(delta);
updateDrawnSongPos();
boolean isHover = false;
if (mouseY > headerY && mouseY < footerY) {
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;
if ((mouseX > cx && mouseX < cx + buttonWidth) &&
(mouseY > buttonY + (i * buttonOffset) && mouseY < buttonY + (i * buttonOffset) + buttonHeight)) {
if (i == hoverIndex)
if (node == hoverIndex) {
hoverOffset.update(delta);
else {
hoverIndex = i;
} else {
hoverIndex = node ;
hoverOffset.setTime(0);
}
isHover = true;
@ -693,15 +710,19 @@ public class SongMenu extends BasicGameState {
}
if (!isHover) {
hoverOffset.setTime(0);
hoverIndex = -1;
hoverIndex = null;
} else
return;
// tooltips
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);
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);
break;
}
@ -714,6 +735,18 @@ public class SongMenu extends BasicGameState {
@Override
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
if (button == Input.MOUSE_MIDDLE_BUTTON)
return;
@ -769,13 +802,13 @@ public class SongMenu extends BasicGameState {
if (y > headerY && y < footerY) {
int expandedIndex = BeatmapSetList.get().getExpandedIndex();
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?
float cx = (node.index == expandedIndex) ? buttonX * 0.9f : buttonX;
if ((x > cx && x < cx + buttonWidth) &&
(y > buttonY + (i * buttonOffset) && y < buttonY + (i * buttonOffset) + buttonHeight)) {
int oldHoverOffsetTime = hoverOffset.getTime();
int oldHoverIndex = hoverIndex;
BeatmapSetNode oldHoverIndex = hoverIndex;
// clicked node is already expanded
if (node.index == expandedIndex) {
@ -813,9 +846,11 @@ public class SongMenu extends BasicGameState {
// score buttons
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);
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);
if (button != Input.MOUSE_RIGHT_BUTTON) {
// view score
@ -937,7 +972,7 @@ public class SongMenu extends BasicGameState {
SoundController.playSound(SoundEffect.MENUCLICK);
BeatmapSetNode oldStartNode = startNode;
int oldHoverOffsetTime = hoverOffset.getTime();
int oldHoverIndex = hoverIndex;
BeatmapSetNode oldHoverIndex = hoverIndex;
setFocus(next, 0, false, true);
if (startNode == oldStartNode) {
hoverOffset.setTime(oldHoverOffsetTime);
@ -953,7 +988,7 @@ public class SongMenu extends BasicGameState {
SoundController.playSound(SoundEffect.MENUCLICK);
BeatmapSetNode oldStartNode = startNode;
int oldHoverOffsetTime = hoverOffset.getTime();
int oldHoverIndex = hoverIndex;
BeatmapSetNode oldHoverIndex = hoverIndex;
setFocus(prev, (prev.index == focusNode.index) ? 0 : prev.getBeatmapSet().size() - 1, false, true);
if (startNode == oldStartNode) {
hoverOffset.setTime(oldHoverOffsetTime);
@ -995,12 +1030,11 @@ public class SongMenu extends BasicGameState {
int diff = newy - oldy;
if (diff == 0)
return;
int shift = (diff < 0) ? 1 : -1;
// check mouse button (right click scrolls faster on songs)
int multiplier;
if (input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON))
multiplier = 4;
multiplier = 10;
else if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON))
multiplier = 1;
else
@ -1008,14 +1042,13 @@ public class SongMenu extends BasicGameState {
// score buttons
if (focusScores != null && focusScores.length >= MAX_SCORE_BUTTONS && ScoreData.areaContains(oldx, oldy)) {
int newStartScore = startScore + shift;
if (newStartScore >= 0 && newStartScore + MAX_SCORE_BUTTONS <= focusScores.length)
startScore = newStartScore;
startScorePos.dragged(-diff * multiplier);
}
// song buttons
else
changeIndex(shift * multiplier);
songScrolling.dragged(-diff * multiplier);
}
@Override
@ -1035,9 +1068,7 @@ public class SongMenu extends BasicGameState {
// score buttons
if (focusScores != null && focusScores.length >= MAX_SCORE_BUTTONS && ScoreData.areaContains(mouseX, mouseY)) {
int newStartScore = startScore + shift;
if (newStartScore >= 0 && newStartScore + MAX_SCORE_BUTTONS <= focusScores.length)
startScore = newStartScore;
startScorePos.scrollOffset(ScoreData.getButtonOffset() * shift);
}
// song buttons
@ -1055,8 +1086,8 @@ public class SongMenu extends BasicGameState {
selectMapOptionsButton.resetHover();
selectOptionsButton.resetHover();
hoverOffset.setTime(0);
hoverIndex = -1;
startScore = 0;
hoverIndex = null;
startScorePos.setPosition(0);
beatmapMenuTimer = -1;
searchTransitionTimer = SEARCH_TRANSITION_TIME;
songInfo = null;
@ -1132,7 +1163,7 @@ public class SongMenu extends BasicGameState {
ScoreDB.deleteScore(stateActionScore);
scoreMap = ScoreDB.getMapSetScores(focusNode.getSelectedBeatmap());
focusScores = getScoreDataForNode(focusNode, true);
startScore = 0;
startScorePos.setPosition(0);
break;
case BEATMAP_DELETE_CONFIRM: // delete song group
if (stateActionNode == null)
@ -1218,38 +1249,46 @@ public class SongMenu extends BasicGameState {
if (shift == 0)
return;
int n = shift;
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;
songScrolling.scrollOffset(shift * buttonOffset);
}
/**
* 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.
* @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;
hoverOffset.setTime(0);
hoverIndex = -1;
hoverIndex = null;
songInfo = null;
songChangeTimer.setTime(0);
musicIconBounceTimer.setTime(0);
@ -1287,9 +1326,6 @@ public class SongMenu extends BasicGameState {
if (beatmapIndex < 0 || beatmapIndex > length - 1) // set a random index
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);
Beatmap beatmap = focusNode.getSelectedBeatmap();
MusicController.play(beatmap, false, preview);
@ -1297,31 +1333,58 @@ public class SongMenu extends BasicGameState {
// load scores
scoreMap = ScoreDB.getMapSetScores(beatmap);
focusScores = getScoreDataForNode(focusNode, true);
startScore = 0;
startScorePos.setPosition(0);
// check startNode bounds
while (startNode.index >= BeatmapSetList.get().size() + length - MAX_SONG_BUTTONS && startNode.prev != null)
startNode = startNode.prev;
if (oldFocus != null && oldFocus.getBeatmapSet() != node.getBeatmapSet()){
//Close previous node
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...)
int val = focusNode.index + focusNode.beatmapIndex - (startNode.index + MAX_SONG_BUTTONS) + 1;
if (val > 0) // below screen
changeIndex(val);
else { // above screen
if (focusNode.index == startNode.index) {
val = focusNode.index + focusNode.beatmapIndex - (startNode.index + startNode.beatmapIndex);
if (val < 0)
changeIndex(val);
} else if (startNode.index > focusNode.index) {
val = focusNode.index - focusNode.getBeatmapSet().size() + focusNode.beatmapIndex - startNode.index + 1;
if (val < 0)
changeIndex(val);
if (Math.abs(node.index - oldFocus.index) > MAX_SONG_BUTTONS) {
// open from the middle
float offset = ((node.getBeatmapSet().size() - 1) * buttonOffset) / 2f;
songScrolling.addOffset(offset);
} else if (node.index > oldFocus.index) {
// open from the bottom
float offset = (node.getBeatmapSet().size() - 1) * buttonOffset;
songScrolling.addOffset(offset);
} else {
// open from the top
}
}
// if start node is expanded and on group node, move it
if (startNode.index == focusNode.index && startNode.beatmapIndex == -1)
changeIndex(1);
// change the focus node
if (changeStartNode || (startNode.index == 0 && startNode.beatmapIndex == -1 && startNode.prev == null)){
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
beatmap.loadBackground();
@ -1425,7 +1488,7 @@ public class SongMenu extends BasicGameState {
randomStack = new Stack<SongNode>();
songInfo = null;
hoverOffset.setTime(0);
hoverIndex = -1;
hoverIndex = null;
search.setText("");
searchTimer = SEARCH_DELAY;
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.
* @param g the graphics context
* @param unitIndex the unit index
* @param totalUnits the total number of units
* @param maxShown the maximum number of units shown at one time
* @param unitBaseX the base x coordinate of the units
* @param unitBaseY the base y coordinate of the units
* @param position the position in the virtual area
* @param totalLength the total length of the virtual area
* @param lengthShown the length of the virtual area shown
* @param unitBaseX the base x coordinate
* @param unitBaseY the base y coordinate
* @param unitWidth the width of a unit
* @param unitHeight the height of a unit
* @param unitOffsetY the y offset between units
* @param scrollAreaHeight the height of the scroll area
* @param bgColor the scroll bar area background color (null if none)
* @param scrollbarColor the scroll bar color
* @param right whether or not to place the scroll bar on the right side of the unit
*/
public static void drawScrollbar(
Graphics g, int unitIndex, int totalUnits, int maxShown,
float unitBaseX, float unitBaseY, float unitWidth, float unitHeight, float unitOffsetY,
Graphics g, float position, float totalLength, float lengthShown,
float unitBaseX, float unitBaseY, float unitWidth, float scrollAreaHeight,
Color bgColor, Color scrollbarColor, boolean right
) {
float scrollbarWidth = container.getWidth() * 0.00347f;
float heightRatio = (float) (2.6701f * Math.exp(-0.81 * Math.log(totalUnits)));
float scrollbarHeight = container.getHeight() * heightRatio;
float scrollAreaHeight = unitHeight + unitOffsetY * (maxShown - 1);
float offsetY = (scrollAreaHeight - scrollbarHeight) * ((float) unitIndex / (totalUnits - maxShown));
float scrollbarHeight = scrollAreaHeight * lengthShown / totalLength;
float offsetY = (scrollAreaHeight - scrollbarHeight) * (position / (totalLength - lengthShown));
float scrollbarX = unitBaseX + unitWidth - ((right) ? scrollbarWidth : 0);
if (bgColor != null) {
g.setColor(bgColor);

View File

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