Add dropdowns and a popup color picker
Popup draws one element above the rest of the screen and gets mouse events first; clicking or scrolling outside closes it. ElementContext now carries each element's position on screen so an input inside a scroll can open a popup beside itself. Select is a dropdown: the current value opens an OptionList with the current option ticked, scrolling for long lists and opening upward when there's no room below. The color picker moved out of ColorInput into a ColorPicker popup, so scroll areas no longer clip it and later rows no longer draw over it, and all three of its parts can be dragged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
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, or above it when there isn't
|
||||
* room below.
|
||||
*
|
||||
* @param originX the owner's top-left corner on the screen
|
||||
* @param originY
|
||||
*/
|
||||
public static void openBelow(Element owner, Element popup, int originX, int originY) {
|
||||
int y = originY + owner.height + OWNER_GAP;
|
||||
|
||||
if (y + popup.height > screenHeight - SCREEN_MARGIN) {
|
||||
y = originY - OWNER_GAP - popup.height;
|
||||
}
|
||||
|
||||
open(owner, popup, originX, 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user