CurveProperty holds the easing curves in the order Animation.getCurve takes them, replacing three copies of the list in zoom, freelook and the animation config; it's saved the same way. Its dropdown opens a CurveOptions popup: a CurvePreview graph beside the list, with a dot running along the hovered curve, or the current one. Co-Authored-By: Claude Opus 5.5 <[email protected]>
154 lines
5.1 KiB
Java
154 lines
5.1 KiB
Java
package org.saturnclient.ui;
|
|
|
|
import java.util.List;
|
|
|
|
/**
|
|
* A single element drawn above the rest of the screen, such as a dropdown's
|
|
* options or a color picker. It gets mouse events before anything else, and a
|
|
* click or scroll outside it closes it.
|
|
*
|
|
* Only one screen is open at a time, so the popup is kept here rather than on
|
|
* the screen; {@link SaturnScreen} forwards to it and clears it when it's
|
|
* rebuilt.
|
|
*/
|
|
public final class Popup {
|
|
private static final int SCREEN_MARGIN = 8;
|
|
private static final int OWNER_GAP = 4;
|
|
|
|
private static Element popup;
|
|
private static Element owner;
|
|
private static int screenWidth, screenHeight;
|
|
|
|
private Popup() {
|
|
}
|
|
|
|
/**
|
|
* Opens {@code popup} with its top-left corner at ({@code x}, {@code y}) on
|
|
* the screen, moved as needed to stay on it.
|
|
*
|
|
* @param owner the element that opened it, for {@link #isOpenFor}
|
|
*/
|
|
public static void open(Element owner, Element popup, int x, int y) {
|
|
Popup.owner = owner;
|
|
Popup.popup = popup;
|
|
|
|
popup.x = clamp(x, popup.width, screenWidth);
|
|
popup.y = clamp(y, popup.height, screenHeight);
|
|
}
|
|
|
|
/**
|
|
* Opens {@code popup} just below its owner if it fits in the part of the
|
|
* screen where the owner can be seen, such as a scroll's box; otherwise
|
|
* above it if it fits there, or else on the side with more room.
|
|
*
|
|
* @param ownerContext the owner's context from its last render
|
|
*/
|
|
public static void openBelow(Element owner, Element popup, ElementContext ownerContext) {
|
|
openBelow(owner, popup, ownerContext, 0);
|
|
}
|
|
|
|
/**
|
|
* Like {@link #openBelow(Element, Element, ElementContext)}, with the popup
|
|
* moved {@code offsetX} from the owner's left edge.
|
|
*/
|
|
public static void openBelow(Element owner, Element popup, ElementContext ownerContext, int offsetX) {
|
|
int top = Math.max(ownerContext.visibleTop, SCREEN_MARGIN);
|
|
int bottom = Math.min(ownerContext.visibleBottom, screenHeight - SCREEN_MARGIN);
|
|
|
|
int below = ownerContext.originY + owner.height + OWNER_GAP;
|
|
int above = ownerContext.originY - OWNER_GAP - popup.height;
|
|
|
|
boolean fitsBelow = below + popup.height <= bottom;
|
|
boolean fitsAbove = above >= top;
|
|
boolean moreRoomBelow = bottom - below >= ownerContext.originY - OWNER_GAP - top;
|
|
|
|
int y = fitsBelow || (!fitsAbove && moreRoomBelow) ? below : above;
|
|
open(owner, popup, ownerContext.originX + offsetX, y);
|
|
}
|
|
|
|
public static void close() {
|
|
popup = null;
|
|
owner = null;
|
|
}
|
|
|
|
public static boolean isOpenFor(Element element) {
|
|
return popup != null && owner == element;
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// Called by SaturnScreen
|
|
// ---------------------------------------------------------------
|
|
|
|
static void reset(int width, int height) {
|
|
close();
|
|
screenWidth = width;
|
|
screenHeight = height;
|
|
}
|
|
|
|
static void render(RenderScope renderScope, long elapsed, int mouseX, int mouseY) {
|
|
if (popup != null) {
|
|
ElementRenderer.render(List.of(popup), elapsed, renderScope, mouseX, mouseY);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return whether the popup took the click; a click outside it closes it
|
|
* and is taken too, so it doesn't reach what's underneath
|
|
*/
|
|
static boolean mouseClicked(double mouseX, double mouseY, int button) {
|
|
if (popup == null) {
|
|
return false;
|
|
}
|
|
|
|
if (isInside(mouseX, mouseY)) {
|
|
ElementRenderer.mouseClicked(List.of(popup), mouseX, mouseY, button);
|
|
} else {
|
|
close();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
|
|
if (popup == null) {
|
|
return false;
|
|
}
|
|
|
|
ElementRenderer.mouseDragged(List.of(popup), mouseX, mouseY, button, deltaX, deltaY);
|
|
return true;
|
|
}
|
|
|
|
static void mouseReleased(double mouseX, double mouseY, int button) {
|
|
if (popup != null) {
|
|
ElementRenderer.mouseReleased(List.of(popup), mouseX, mouseY, button);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return whether the popup took the scroll; scrolling outside it closes it,
|
|
* since it wouldn't move with what's underneath
|
|
*/
|
|
static boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) {
|
|
if (popup == null) {
|
|
return false;
|
|
}
|
|
|
|
if (isInside(mouseX, mouseY)) {
|
|
ElementRenderer.mouseScrolled(List.of(popup), mouseX, mouseY, horizontalAmount, verticalAmount);
|
|
} else {
|
|
close();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static boolean isInside(double mouseX, double mouseY) {
|
|
return mouseX >= popup.x && mouseX <= popup.x + popup.width
|
|
&& mouseY >= popup.y && mouseY <= popup.y + popup.height;
|
|
}
|
|
|
|
private static int clamp(int position, int size, int screenSize) {
|
|
return Math.max(SCREEN_MARGIN, Math.min(position, screenSize - SCREEN_MARGIN - size));
|
|
}
|
|
}
|