diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java index 61453458..658136a1 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/GenericStyledArea.java @@ -2091,23 +2091,7 @@ private Bounds getParagraphBoundsOnScreen(Cell, ParagraphB Bounds nodeScreen = cell.getNode().localToScreen(nodeLocal); Bounds areaLocal = getBoundsInLocal(); Bounds areaScreen = localToScreen(areaLocal); - - // use area's minX if scrolled right and paragraph's left is not visible - double minX = nodeScreen.getMinX() < areaScreen.getMinX() - ? areaScreen.getMinX() - : nodeScreen.getMinX(); - // use area's minY if scrolled down vertically and paragraph's top is not visible - double minY = nodeScreen.getMinY() < areaScreen.getMinY() - ? areaScreen.getMinY() - : nodeScreen.getMinY(); - // use area's width whether paragraph spans outside of it or not - // so that short or long paragraph takes up the entire space - double width = areaScreen.getWidth(); - // use area's maxY if scrolled up vertically and paragraph's bottom is not visible - double maxY = nodeScreen.getMaxY() < areaScreen.getMaxY() - ? nodeScreen.getMaxY() - : areaScreen.getMaxY(); - return new BoundingBox(minX, minY, width, maxY - minY); + return new ScreenBounds(areaScreen).getParagraphBoundsFrom(nodeScreen); } private Optional getRangeBoundsOnScreen(int paragraphIndex, int from, int to) { diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/ScreenBounds.java b/richtextfx/src/main/java/org/fxmisc/richtext/ScreenBounds.java new file mode 100644 index 00000000..ab745f46 --- /dev/null +++ b/richtextfx/src/main/java/org/fxmisc/richtext/ScreenBounds.java @@ -0,0 +1,29 @@ +package org.fxmisc.richtext; + +import javafx.geometry.BoundingBox; +import javafx.geometry.Bounds; + +class ScreenBounds { + private final Bounds screen; + + public ScreenBounds(Bounds screen) { + this.screen = screen; + } + + /** + * Evaluate the paragraph node bounds in the area. + * @param paragraph the paragraph bound position on screen + * @return the bounds inside the area of the provided paragraph bounds. + */ + public Bounds getParagraphBoundsFrom(Bounds paragraph) { + // use area's minX,minY if scrolled right/down and paragraph's left/top is not visible + double minX = Math.max(paragraph.getMinX(), screen.getMinX()); + double minY = Math.max(paragraph.getMinY(), screen.getMinY()); + // use area's width whether paragraph spans outside of it or not + // so that short or long paragraph takes up the entire space + double width = screen.getWidth(); + // use area's maxY if scrolled up vertically and paragraph's bottom is not visible + double maxY = Math.min(paragraph.getMaxY(), screen.getMaxY()); + return new BoundingBox(minX, minY, width, maxY - minY); + } +} diff --git a/richtextfx/src/test/java/org/fxmisc/richtext/BoundsEvaluatorTest.java b/richtextfx/src/test/java/org/fxmisc/richtext/BoundsEvaluatorTest.java new file mode 100644 index 00000000..91ba101c --- /dev/null +++ b/richtextfx/src/test/java/org/fxmisc/richtext/BoundsEvaluatorTest.java @@ -0,0 +1,45 @@ +package org.fxmisc.richtext; + +import javafx.geometry.BoundingBox; +import javafx.geometry.Bounds; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BoundsEvaluatorTest { + private void checkBounds(Bounds bounds, double xMin, double yMin, double xMax, double yMax) { + assertEquals(xMin, bounds.getMinX(), "Invalid min X value"); + assertEquals(yMin, bounds.getMinY(), "Invalid min Y value"); + assertEquals(xMax, bounds.getMaxX(), "Invalid max X value"); + assertEquals(yMax, bounds.getMaxY(), "Invalid max Y value"); + } + + @Test + @DisplayName("Evaluate paragraph bounds on screen") + void evaluateParagraphBoundsOnScreen() { + ScreenBounds boundsEvaluator = new ScreenBounds(new BoundingBox(20, 20, 100, 100)); + // Start < Screen ; End < Screen + checkBounds(boundsEvaluator.getParagraphBoundsFrom(new BoundingBox(10, 10, 50, 50)), + 20, 20, 120, 60); + // Start < Screen ; End > Screen + checkBounds(boundsEvaluator.getParagraphBoundsFrom(new BoundingBox(10, 10, 111, 111)), + 20, 20, 120, 120); + // Start > Screen ; End > Screen + checkBounds(boundsEvaluator.getParagraphBoundsFrom(new BoundingBox(21, 21, 100, 100)), + 21, 21, 121, 120); + // Start > Screen ; End < Screen + checkBounds(boundsEvaluator.getParagraphBoundsFrom(new BoundingBox(21, 21, 98, 98)), + 21, 21, 121, 119); + // Start = Screen ; End = Screen + checkBounds(boundsEvaluator.getParagraphBoundsFrom(new BoundingBox(20, 20, 100, 100)), + 20, 20, 120, 120); + // Miscellaneous + checkBounds(boundsEvaluator.getParagraphBoundsFrom(new BoundingBox(0, 0, 80, 80)), + 20, 20, 120, 80); + checkBounds(boundsEvaluator.getParagraphBoundsFrom(new BoundingBox(30, 30, 120, 120)), + 30, 30, 130, 120); + checkBounds(boundsEvaluator.getParagraphBoundsFrom(new BoundingBox(110, 110, 130, 130)), + 110, 110, 210, 120); + } +}