From 8b58fe919d4b34bf76a6f283869da3a9641eb07c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 27 Sep 2026 18:05:26 +0200 Subject: [PATCH] Open dropdowns within the visible part of their list ElementContext carries the part of the screen where an element can be seen, passed down in a Frame and narrowed by each Scroll to its own box. A dropdown opens below only if its list fits in that part, otherwise above if it fits there, or else on the side with more room, so one at the bottom of a scrolled list no longer opens past the list's edge. Co-Authored-By: Claude Opus 5.5 --- .../org/saturnclient/ui/ElementContext.java | 36 +++++++++++++++++-- .../org/saturnclient/ui/ElementRenderer.java | 9 +++-- src/main/java/org/saturnclient/ui/Popup.java | 25 +++++++------ .../ui/components/inputs/Select.java | 9 +++-- .../ui/elements/AnimationStagger.java | 3 +- .../org/saturnclient/ui/elements/Scroll.java | 3 +- 6 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/saturnclient/ui/ElementContext.java b/src/main/java/org/saturnclient/ui/ElementContext.java index c9c3638..d78f6cf 100644 --- a/src/main/java/org/saturnclient/ui/ElementContext.java +++ b/src/main/java/org/saturnclient/ui/ElementContext.java @@ -1,24 +1,54 @@ package org.saturnclient.ui; public class ElementContext { + /** + * Where a container's children are drawn: the screen position of their + * (0, 0), and the part of the screen, top to bottom, where they can be seen. + */ + public record Frame(int x, int y, int visibleTop, int visibleBottom) { + /** The whole screen, for top-level elements */ + public static final Frame SCREEN = new Frame(0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + public int mouseX, mouseY; /** The element's top-left corner on the screen, for placing popups next to it */ public int originX, originY; + /** The part of the screen, top to bottom, where the element can be seen */ + public int visibleTop, visibleBottom; public int elementWidth, elementHeight; public float elementScale; public long elapsed; - public ElementContext(long elapsed, int mouseX, int mouseY, int parentX, int parentY, Element e) { + public ElementContext(long elapsed, int mouseX, int mouseY, Frame frame, Element e) { this.mouseX = mouseX - e.x; this.mouseY = mouseY - e.y; - this.originX = parentX + e.x; - this.originY = parentY + e.y; + this.originX = frame.x() + e.x; + this.originY = frame.y() + e.y; + this.visibleTop = frame.visibleTop(); + this.visibleBottom = frame.visibleBottom(); this.elementWidth = e.width; this.elementHeight = e.height; this.elementScale = e.scale; this.elapsed = elapsed; } + /** + * The frame for this element's children, drawn {@code dx}, {@code dy} from + * its corner and seen only between {@code clipTop} and {@code clipBottom} + * (relative to its corner). + */ + public Frame childFrame(int dx, int dy, int clipTop, int clipBottom) { + return new Frame(originX + dx, originY + dy, + Math.max(visibleTop, originY + clipTop), Math.min(visibleBottom, originY + clipBottom)); + } + + /** + * The frame for children drawn at this element's corner and not clipped. + */ + public Frame childFrame() { + return new Frame(originX, originY, visibleTop, visibleBottom); + } + public boolean isHovering() { return Utils.isHovering(mouseX, mouseY, elementWidth, elementHeight, elementScale); } diff --git a/src/main/java/org/saturnclient/ui/ElementRenderer.java b/src/main/java/org/saturnclient/ui/ElementRenderer.java index 4dade83..ec2cd96 100644 --- a/src/main/java/org/saturnclient/ui/ElementRenderer.java +++ b/src/main/java/org/saturnclient/ui/ElementRenderer.java @@ -15,15 +15,14 @@ public class ElementRenderer { } public static void render(List elements, long elapsed, RenderScope renderScope, int mouseX, int mouseY) { - render(elements, elapsed, renderScope, mouseX, mouseY, 0, 0); + render(elements, elapsed, renderScope, mouseX, mouseY, ElementContext.Frame.SCREEN); } /** - * Renders elements whose parent's top-left corner is at - * ({@code parentX}, {@code parentY}) on the screen. + * Renders a container's children within its {@code frame}. */ public static void render(List elements, long elapsed, RenderScope renderScope, int mouseX, int mouseY, - int parentX, int parentY) { + ElementContext.Frame frame) { for (Element element : elements) { element.playAnimationFrame(elapsed); @@ -31,7 +30,7 @@ public class ElementRenderer { renderScope.setOpacity(element.opacity); renderScope.getMatrixStack().translate(element.x, element.y); renderScope.getMatrixStack().scale(element.scale, element.scale); - element.render(renderScope, new ElementContext(elapsed, mouseX, mouseY, parentX, parentY, element)); + element.render(renderScope, new ElementContext(elapsed, mouseX, mouseY, frame, element)); renderScope.getMatrixStack().pop(); } } diff --git a/src/main/java/org/saturnclient/ui/Popup.java b/src/main/java/org/saturnclient/ui/Popup.java index 14859ea..c487e4f 100644 --- a/src/main/java/org/saturnclient/ui/Popup.java +++ b/src/main/java/org/saturnclient/ui/Popup.java @@ -37,20 +37,25 @@ public final class Popup { } /** - * Opens {@code popup} just below its owner, or above it when there isn't - * room below. + * 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 originX the owner's top-left corner on the screen - * @param originY + * @param ownerContext the owner's context from its last render */ - public static void openBelow(Element owner, Element popup, int originX, int originY) { - int y = originY + owner.height + OWNER_GAP; + public static void openBelow(Element owner, Element popup, ElementContext ownerContext) { + int top = Math.max(ownerContext.visibleTop, SCREEN_MARGIN); + int bottom = Math.min(ownerContext.visibleBottom, screenHeight - SCREEN_MARGIN); - if (y + popup.height > screenHeight - SCREEN_MARGIN) { - y = originY - OWNER_GAP - popup.height; - } + int below = ownerContext.originY + owner.height + OWNER_GAP; + int above = ownerContext.originY - OWNER_GAP - popup.height; - open(owner, popup, originX, y); + 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, y); } public static void close() { diff --git a/src/main/java/org/saturnclient/ui/components/inputs/Select.java b/src/main/java/org/saturnclient/ui/components/inputs/Select.java index fd67806..e5f6cfc 100644 --- a/src/main/java/org/saturnclient/ui/components/inputs/Select.java +++ b/src/main/java/org/saturnclient/ui/components/inputs/Select.java @@ -21,8 +21,8 @@ public class Select extends Element { private final SelectProperty prop; - /** Where this was last drawn on the screen, to open the list under it */ - private int originX, originY; + /** The context this was last drawn with, to open the list next to it */ + private ElementContext lastContext; public Select(SelectProperty prop) { this.prop = prop; @@ -32,8 +32,7 @@ public class Select extends Element { @Override public void render(RenderScope renderScope, ElementContext ctx) { - originX = ctx.originX; - originY = ctx.originY; + lastContext = ctx; boolean open = Popup.isOpenFor(this); Field.drawBox(renderScope, width, height, ctx.isHovering(), open); @@ -55,6 +54,6 @@ public class Select extends Element { Popup.close(); }); - Popup.openBelow(this, list, originX, originY); + Popup.openBelow(this, list, lastContext); } } diff --git a/src/main/java/org/saturnclient/ui/elements/AnimationStagger.java b/src/main/java/org/saturnclient/ui/elements/AnimationStagger.java index edbd87f..80dba99 100644 --- a/src/main/java/org/saturnclient/ui/elements/AnimationStagger.java +++ b/src/main/java/org/saturnclient/ui/elements/AnimationStagger.java @@ -42,8 +42,7 @@ public class AnimationStagger extends Element { ElementRenderer.render(children, ctx.elapsed, renderScope, ctx.mouseX, ctx.mouseY, - ctx.originX, - ctx.originY); + ctx.childFrame()); } @Override diff --git a/src/main/java/org/saturnclient/ui/elements/Scroll.java b/src/main/java/org/saturnclient/ui/elements/Scroll.java index d7d6940..4726555 100644 --- a/src/main/java/org/saturnclient/ui/elements/Scroll.java +++ b/src/main/java/org/saturnclient/ui/elements/Scroll.java @@ -81,8 +81,7 @@ public class Scroll extends Element { ElementRenderer.render(children, ctx.elapsed, renderScope, ctx.mouseX - padding, ctx.mouseY - padding + offset, - ctx.originX + padding, - ctx.originY + padding - offset); + ctx.childFrame(padding, padding - offset, padding, height - padding)); renderScope.getMatrixStack().pop(); renderScope.disableScissor();