2015-05-29 10:48:03 +02:00
|
|
|
/*
|
|
|
|
* opsu! - an open-source osu! client
|
|
|
|
* Copyright (C) 2014, 2015 Jeffrey Han
|
|
|
|
*
|
|
|
|
* opsu! is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* opsu! is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with opsu!. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package itdelatrisu.opsu.ui;
|
|
|
|
|
|
|
|
import itdelatrisu.opsu.ErrorHandler;
|
|
|
|
import itdelatrisu.opsu.GameImage;
|
|
|
|
import itdelatrisu.opsu.Opsu;
|
|
|
|
import itdelatrisu.opsu.Options;
|
|
|
|
import itdelatrisu.opsu.Utils;
|
|
|
|
import itdelatrisu.opsu.skins.Skin;
|
2015-08-29 04:29:21 +02:00
|
|
|
import itdelatrisu.opsu.ui.animations.AnimationEquation;
|
2015-05-29 10:48:03 +02:00
|
|
|
|
2015-09-05 19:46:01 +02:00
|
|
|
import java.awt.Point;
|
2015-05-29 10:48:03 +02:00
|
|
|
import java.nio.IntBuffer;
|
|
|
|
import java.util.LinkedList;
|
|
|
|
|
|
|
|
import org.lwjgl.BufferUtils;
|
|
|
|
import org.lwjgl.LWJGLException;
|
2016-09-30 19:38:02 +02:00
|
|
|
import org.newdawn.slick.*;
|
2015-05-29 10:48:03 +02:00
|
|
|
import org.newdawn.slick.state.StateBasedGame;
|
2016-09-30 19:38:02 +02:00
|
|
|
import yugecin.opsudance.Dancer;
|
2015-05-29 10:48:03 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates and draws the cursor.
|
|
|
|
*/
|
|
|
|
public class Cursor {
|
|
|
|
/** Empty cursor. */
|
|
|
|
private static org.lwjgl.input.Cursor emptyCursor;
|
|
|
|
|
|
|
|
/** Last cursor coordinates. */
|
2015-09-05 19:46:01 +02:00
|
|
|
private Point lastPosition;
|
2015-05-29 10:48:03 +02:00
|
|
|
|
|
|
|
/** Cursor rotation angle. */
|
2016-09-30 19:38:02 +02:00
|
|
|
private static float cursorAngle = 0f;
|
2015-05-29 10:48:03 +02:00
|
|
|
|
2015-08-28 03:48:08 +02:00
|
|
|
/** The time in milliseconds when the cursor was last pressed, used for the scaling animation. */
|
2015-08-27 18:32:40 +02:00
|
|
|
private long lastCursorPressTime = 0L;
|
2015-08-28 03:48:08 +02:00
|
|
|
|
|
|
|
/** Whether or not the cursor was pressed in the last frame, used for the scaling animation. */
|
2015-08-27 18:32:40 +02:00
|
|
|
private boolean lastCursorPressState = false;
|
|
|
|
|
2015-08-28 03:48:08 +02:00
|
|
|
/** The amount the cursor scale increases, if enabled, when pressed. */
|
2015-08-27 18:32:40 +02:00
|
|
|
private static final float CURSOR_SCALE_CHANGE = 0.25f;
|
2015-08-28 03:48:08 +02:00
|
|
|
|
|
|
|
/** The time it takes for the cursor to scale, in milliseconds. */
|
2015-08-27 18:32:40 +02:00
|
|
|
private static final float CURSOR_SCALE_TIME = 125;
|
|
|
|
|
2015-05-29 10:48:03 +02:00
|
|
|
/** Stores all previous cursor locations to display a trail. */
|
2015-09-05 19:46:01 +02:00
|
|
|
private LinkedList<Point> trail = new LinkedList<Point>();
|
2015-05-29 10:48:03 +02:00
|
|
|
|
|
|
|
// game-related variables
|
|
|
|
private static GameContainer container;
|
|
|
|
private static StateBasedGame game;
|
|
|
|
private static Input input;
|
|
|
|
|
2016-09-30 19:38:02 +02:00
|
|
|
public static Color lastObjColor = Color.white;
|
|
|
|
public static Color lastMirroredObjColor = Color.white;
|
|
|
|
|
|
|
|
private boolean isMirrored;
|
|
|
|
|
2015-05-29 10:48:03 +02:00
|
|
|
/**
|
|
|
|
* Initializes the class.
|
|
|
|
* @param container the game container
|
|
|
|
* @param game the game object
|
|
|
|
*/
|
|
|
|
public static void init(GameContainer container, StateBasedGame game) {
|
|
|
|
Cursor.container = container;
|
|
|
|
Cursor.game = game;
|
|
|
|
Cursor.input = container.getInput();
|
|
|
|
|
|
|
|
// create empty cursor to simulate hiding the cursor
|
|
|
|
try {
|
|
|
|
int min = org.lwjgl.input.Cursor.getMinCursorSize();
|
|
|
|
IntBuffer tmp = BufferUtils.createIntBuffer(min * min);
|
|
|
|
emptyCursor = new org.lwjgl.input.Cursor(min, min, min/2, min/2, 1, tmp, null);
|
|
|
|
} catch (LWJGLException e) {
|
|
|
|
ErrorHandler.error("Failed to create hidden cursor.", e, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
*/
|
2016-09-27 15:29:38 +02:00
|
|
|
public Cursor() {
|
2016-09-30 19:38:02 +02:00
|
|
|
this(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Cursor(boolean isMirrored) {
|
2016-09-27 15:29:38 +02:00
|
|
|
resetLocations();
|
2016-09-30 19:38:02 +02:00
|
|
|
this.isMirrored = isMirrored;
|
2016-09-27 15:29:38 +02:00
|
|
|
}
|
2015-05-29 10:48:03 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws the cursor.
|
|
|
|
*/
|
|
|
|
public void draw() {
|
|
|
|
int state = game.getCurrentStateID();
|
|
|
|
boolean mousePressed =
|
|
|
|
(((state == Opsu.STATE_GAME || state == Opsu.STATE_GAMEPAUSEMENU) && Utils.isGameKeyPressed()) ||
|
|
|
|
((input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON) || input.isMouseButtonDown(Input.MOUSE_RIGHT_BUTTON)) &&
|
|
|
|
!(state == Opsu.STATE_GAME && Options.isMouseDisabled())));
|
|
|
|
draw(input.getMouseX(), input.getMouseY(), mousePressed);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draws the cursor.
|
|
|
|
* @param mouseX the mouse x coordinate
|
|
|
|
* @param mouseY the mouse y coordinate
|
|
|
|
* @param mousePressed whether or not the mouse button is pressed
|
|
|
|
*/
|
|
|
|
public void draw(int mouseX, int mouseY, boolean mousePressed) {
|
2015-09-11 17:43:19 +02:00
|
|
|
if (Options.isCursorDisabled())
|
|
|
|
return;
|
|
|
|
|
2015-05-29 10:48:03 +02:00
|
|
|
// determine correct cursor image
|
|
|
|
Image cursor = null, cursorMiddle = null, cursorTrail = null;
|
2015-08-24 04:16:28 +02:00
|
|
|
boolean beatmapSkinned = GameImage.CURSOR.hasBeatmapSkinImage();
|
2015-05-29 10:48:03 +02:00
|
|
|
boolean newStyle, hasMiddle;
|
2015-08-24 04:16:28 +02:00
|
|
|
Skin skin = Options.getSkin();
|
|
|
|
if (beatmapSkinned) {
|
2015-05-29 10:48:03 +02:00
|
|
|
newStyle = true; // osu! currently treats all beatmap cursors as new-style cursors
|
2015-08-24 03:41:09 +02:00
|
|
|
hasMiddle = GameImage.CURSOR_MIDDLE.hasBeatmapSkinImage();
|
2015-05-29 10:48:03 +02:00
|
|
|
} else
|
|
|
|
newStyle = hasMiddle = Options.isNewCursorEnabled();
|
2015-08-24 04:16:28 +02:00
|
|
|
if (newStyle || beatmapSkinned) {
|
2015-05-29 10:48:03 +02:00
|
|
|
cursor = GameImage.CURSOR.getImage();
|
|
|
|
cursorTrail = GameImage.CURSOR_TRAIL.getImage();
|
|
|
|
} else {
|
2015-08-24 04:16:28 +02:00
|
|
|
cursor = GameImage.CURSOR.hasGameSkinImage() ? GameImage.CURSOR.getImage() : GameImage.CURSOR_OLD.getImage();
|
|
|
|
cursorTrail = GameImage.CURSOR_TRAIL.hasGameSkinImage() ? GameImage.CURSOR_TRAIL.getImage() : GameImage.CURSOR_TRAIL_OLD.getImage();
|
2015-05-29 10:48:03 +02:00
|
|
|
}
|
|
|
|
if (hasMiddle)
|
|
|
|
cursorMiddle = GameImage.CURSOR_MIDDLE.getImage();
|
|
|
|
|
2015-06-14 19:30:33 +02:00
|
|
|
// scale cursor
|
2015-08-28 03:48:08 +02:00
|
|
|
float cursorScaleAnimated = 1f;
|
2015-08-27 18:32:40 +02:00
|
|
|
if (skin.isCursorExpanded()) {
|
|
|
|
if (lastCursorPressState != mousePressed) {
|
|
|
|
lastCursorPressState = mousePressed;
|
|
|
|
lastCursorPressTime = System.currentTimeMillis();
|
|
|
|
}
|
|
|
|
|
2015-08-28 03:48:08 +02:00
|
|
|
float cursorScaleChange = CURSOR_SCALE_CHANGE * AnimationEquation.IN_OUT_CUBIC.calc(
|
|
|
|
Utils.clamp(System.currentTimeMillis() - lastCursorPressTime, 0, CURSOR_SCALE_TIME) / CURSOR_SCALE_TIME);
|
|
|
|
cursorScaleAnimated = 1f + ((mousePressed) ? cursorScaleChange : CURSOR_SCALE_CHANGE - cursorScaleChange);
|
2015-08-27 18:32:40 +02:00
|
|
|
}
|
2015-08-28 03:48:08 +02:00
|
|
|
float cursorScale = cursorScaleAnimated * Options.getCursorScale();
|
2015-06-14 19:30:33 +02:00
|
|
|
if (cursorScale != 1f) {
|
|
|
|
cursor = cursor.getScaledCopy(cursorScale);
|
|
|
|
cursorTrail = cursorTrail.getScaledCopy(cursorScale);
|
|
|
|
}
|
|
|
|
|
2015-05-29 10:48:03 +02:00
|
|
|
// TODO: use an image buffer
|
2015-08-24 04:16:28 +02:00
|
|
|
int removeCount = 0;
|
2016-09-27 15:29:38 +02:00
|
|
|
float FPSmod = Math.max(container.getFPS(), 1) / 30f;
|
2015-05-29 10:48:03 +02:00
|
|
|
if (newStyle) {
|
|
|
|
// new style: add all points between cursor movements
|
2016-10-01 14:54:05 +02:00
|
|
|
if ((lastPosition.x == 0 && lastPosition.y == 0) || !addCursorPoints(lastPosition.x, lastPosition.y, mouseX, mouseY)) {
|
2016-09-27 15:29:38 +02:00
|
|
|
trail.add(new Point(mouseX, mouseY));
|
2015-05-29 10:48:03 +02:00
|
|
|
}
|
2015-09-05 19:46:01 +02:00
|
|
|
lastPosition.move(mouseX, mouseY);
|
2015-05-29 10:48:03 +02:00
|
|
|
|
2015-09-05 19:46:01 +02:00
|
|
|
removeCount = (int) (trail.size() / (6 * FPSmod)) + 1;
|
2015-05-29 10:48:03 +02:00
|
|
|
} else {
|
|
|
|
// old style: sample one point at a time
|
2015-09-05 19:46:01 +02:00
|
|
|
trail.add(new Point(mouseX, mouseY));
|
2015-05-29 10:48:03 +02:00
|
|
|
|
2015-07-17 01:14:46 +02:00
|
|
|
int max = (int) (10 * FPSmod);
|
2015-09-05 19:46:01 +02:00
|
|
|
if (trail.size() > max)
|
|
|
|
removeCount = trail.size() - max;
|
2015-05-29 10:48:03 +02:00
|
|
|
}
|
|
|
|
|
2016-10-01 12:14:39 +02:00
|
|
|
if (Dancer.cursortraillength > 20) {
|
|
|
|
removeCount = trail.size() - Dancer.cursortraillength;
|
|
|
|
}
|
|
|
|
|
2015-05-29 10:48:03 +02:00
|
|
|
// remove points from the lists
|
2015-09-05 19:46:01 +02:00
|
|
|
for (int i = 0; i < removeCount && !trail.isEmpty(); i++)
|
|
|
|
trail.remove();
|
2015-05-29 10:48:03 +02:00
|
|
|
|
2016-10-01 11:53:33 +02:00
|
|
|
Color filter;
|
|
|
|
if (isMirrored) {
|
|
|
|
filter = Dancer.cursorColorMirrorOverride.getMirrorColor();
|
|
|
|
} else {
|
|
|
|
filter = Dancer.cursorColorOverride.getColor();
|
|
|
|
}
|
2016-09-30 19:38:02 +02:00
|
|
|
|
2015-05-29 10:48:03 +02:00
|
|
|
// draw a fading trail
|
|
|
|
float alpha = 0f;
|
2015-09-05 19:46:01 +02:00
|
|
|
float t = 2f / trail.size();
|
2015-09-05 16:32:26 +02:00
|
|
|
int cursorTrailWidth = cursorTrail.getWidth(), cursorTrailHeight = cursorTrail.getHeight();
|
|
|
|
float cursorTrailRotation = (skin.isCursorTrailRotated()) ? cursorAngle : 0;
|
|
|
|
cursorTrail.startUse();
|
2015-09-05 19:46:01 +02:00
|
|
|
for (Point p : trail) {
|
2015-05-29 10:48:03 +02:00
|
|
|
alpha += t;
|
2016-09-30 19:38:02 +02:00
|
|
|
cursorTrail.setImageColor(filter.r, filter.g, filter.b, alpha);
|
2015-09-05 19:46:01 +02:00
|
|
|
cursorTrail.drawEmbedded(
|
|
|
|
p.x - (cursorTrailWidth / 2f), p.y - (cursorTrailHeight / 2f),
|
|
|
|
cursorTrailWidth, cursorTrailHeight, cursorTrailRotation);
|
2015-05-29 10:48:03 +02:00
|
|
|
}
|
2015-09-05 16:32:26 +02:00
|
|
|
cursorTrail.drawEmbedded(
|
|
|
|
mouseX - (cursorTrailWidth / 2f), mouseY - (cursorTrailHeight / 2f),
|
|
|
|
cursorTrailWidth, cursorTrailHeight, cursorTrailRotation);
|
|
|
|
cursorTrail.endUse();
|
2015-05-29 10:48:03 +02:00
|
|
|
|
|
|
|
// draw the other components
|
|
|
|
if (newStyle && skin.isCursorRotated())
|
|
|
|
cursor.setRotation(cursorAngle);
|
2016-10-04 15:22:13 +02:00
|
|
|
cursor.drawCentered(mouseX, mouseY, Dancer.onlycolortrail ? Color.white : filter);
|
2015-05-29 10:48:03 +02:00
|
|
|
if (hasMiddle)
|
2016-10-04 15:22:13 +02:00
|
|
|
cursorMiddle.drawCentered(mouseX, mouseY, Dancer.onlycolortrail ? Color.white : filter);
|
2016-09-30 19:38:02 +02:00
|
|
|
}
|
|
|
|
|
2015-05-29 10:48:03 +02:00
|
|
|
/**
|
|
|
|
* Adds all points between (x1, y1) and (x2, y2) to the cursor point lists.
|
|
|
|
* @author http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#Java
|
|
|
|
*/
|
2016-09-27 15:29:38 +02:00
|
|
|
private boolean addCursorPoints(int x1, int y1, int x2, int y2) {
|
2015-05-29 10:48:03 +02:00
|
|
|
// delta of exact value and rounded value of the dependent variable
|
2016-09-27 15:29:38 +02:00
|
|
|
boolean added = false;
|
2015-05-29 10:48:03 +02:00
|
|
|
int d = 0;
|
|
|
|
int dy = Math.abs(y2 - y1);
|
|
|
|
int dx = Math.abs(x2 - x1);
|
|
|
|
|
|
|
|
int dy2 = (dy << 1); // slope scaling factors to avoid floating
|
|
|
|
int dx2 = (dx << 1); // point
|
|
|
|
int ix = x1 < x2 ? 1 : -1; // increment direction
|
|
|
|
int iy = y1 < y2 ? 1 : -1;
|
|
|
|
|
|
|
|
int k = 5; // sample size
|
|
|
|
if (dy <= dx) {
|
|
|
|
for (int i = 0; ; i++) {
|
|
|
|
if (i == k) {
|
2015-09-05 19:46:01 +02:00
|
|
|
trail.add(new Point(x1, y1));
|
2016-09-27 15:29:38 +02:00
|
|
|
added = true;
|
2015-05-29 10:48:03 +02:00
|
|
|
i = 0;
|
|
|
|
}
|
|
|
|
if (x1 == x2)
|
|
|
|
break;
|
|
|
|
x1 += ix;
|
|
|
|
d += dy2;
|
|
|
|
if (d > dx) {
|
|
|
|
y1 += iy;
|
|
|
|
d -= dx2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (int i = 0; ; i++) {
|
|
|
|
if (i == k) {
|
2015-09-05 19:46:01 +02:00
|
|
|
trail.add(new Point(x1, y1));
|
2016-09-27 15:29:38 +02:00
|
|
|
added = true;
|
2015-05-29 10:48:03 +02:00
|
|
|
i = 0;
|
|
|
|
}
|
|
|
|
if (y1 == y2)
|
|
|
|
break;
|
|
|
|
y1 += iy;
|
|
|
|
d += dx2;
|
|
|
|
if (d > dy) {
|
|
|
|
x1 += ix;
|
|
|
|
d -= dy2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-27 15:29:38 +02:00
|
|
|
return added;
|
2015-05-29 10:48:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Rotates the cursor by a degree determined by a delta interval.
|
|
|
|
* If the old style cursor is being used, this will do nothing.
|
|
|
|
* @param delta the delta interval since the last call
|
|
|
|
*/
|
|
|
|
public void update(int delta) {
|
|
|
|
cursorAngle += delta / 40f;
|
|
|
|
cursorAngle %= 360;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-08-24 03:41:09 +02:00
|
|
|
* Resets all cursor data and beatmap skins.
|
2015-05-29 10:48:03 +02:00
|
|
|
*/
|
|
|
|
public void reset() {
|
|
|
|
// destroy skin images
|
2015-08-24 03:41:09 +02:00
|
|
|
GameImage.CURSOR.destroyBeatmapSkinImage();
|
|
|
|
GameImage.CURSOR_MIDDLE.destroyBeatmapSkinImage();
|
|
|
|
GameImage.CURSOR_TRAIL.destroyBeatmapSkinImage();
|
2015-05-29 10:48:03 +02:00
|
|
|
|
|
|
|
// reset locations
|
|
|
|
resetLocations();
|
|
|
|
|
|
|
|
// reset angles
|
|
|
|
cursorAngle = 0f;
|
|
|
|
GameImage.CURSOR.getImage().setRotation(0f);
|
|
|
|
GameImage.CURSOR_TRAIL.getImage().setRotation(0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resets all cursor location data.
|
|
|
|
*/
|
|
|
|
public void resetLocations() {
|
2016-09-27 15:29:38 +02:00
|
|
|
lastPosition = new Point(0, 0);
|
2015-09-05 19:46:01 +02:00
|
|
|
trail.clear();
|
2015-05-29 10:48:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns whether or not the cursor is skinned.
|
|
|
|
*/
|
2015-08-24 03:41:09 +02:00
|
|
|
public boolean isBeatmapSkinned() {
|
|
|
|
return (GameImage.CURSOR.hasBeatmapSkinImage() ||
|
|
|
|
GameImage.CURSOR_MIDDLE.hasBeatmapSkinImage() ||
|
|
|
|
GameImage.CURSOR_TRAIL.hasBeatmapSkinImage());
|
2015-05-29 10:48:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hides the cursor, if possible.
|
|
|
|
*/
|
|
|
|
public void hide() {
|
|
|
|
if (emptyCursor != null) {
|
|
|
|
try {
|
|
|
|
container.setMouseCursor(emptyCursor, 0, 0);
|
|
|
|
} catch (SlickException e) {
|
|
|
|
ErrorHandler.error("Failed to hide the cursor.", e, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unhides the cursor.
|
|
|
|
*/
|
|
|
|
public void show() {
|
|
|
|
container.setDefaultMouseCursor();
|
|
|
|
}
|
|
|
|
}
|