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();