|
21 | 21 | import javafx.scene.control.IndexRange; |
22 | 22 | import javafx.scene.paint.Color; |
23 | 23 | import javafx.scene.paint.Paint; |
| 24 | +import javafx.scene.shape.LineTo; |
| 25 | +import javafx.scene.shape.MoveTo; |
24 | 26 | import javafx.scene.shape.Path; |
25 | 27 | import javafx.scene.shape.PathElement; |
26 | 28 | import javafx.scene.shape.StrokeLineCap; |
@@ -201,7 +203,7 @@ public Bounds getCaretBoundsOnScreen() { |
201 | 203 |
|
202 | 204 | public Bounds getRangeBoundsOnScreen(int from, int to) { |
203 | 205 | layout(); // ensure layout, is a no-op if not dirty |
204 | | - PathElement[] rangeShape = getRangeShape(from, to); |
| 206 | + PathElement[] rangeShape = getRangeShapeSafely(from, to); |
205 | 207 |
|
206 | 208 | // switch out shapes to calculate the bounds on screen |
207 | 209 | // Must take a copy of the list contents, not just a reference: |
@@ -248,8 +250,60 @@ private void updateCaretShape() { |
248 | 250 | private void updateSelectionShape() { |
249 | 251 | int start = selection.get().getStart(); |
250 | 252 | int end = selection.get().getEnd(); |
251 | | - PathElement[] shape = getRangeShape(start, end); |
252 | | - selectionShape.getElements().setAll(shape); |
| 253 | + selectionShape.getElements().setAll(getRangeShapeSafely(start, end)); |
| 254 | + } |
| 255 | + |
| 256 | + /** |
| 257 | + * Gets the range shape for the given positions within the text, including the newline character, if range |
| 258 | + * defined by the start/end arguments include it. |
| 259 | + * |
| 260 | + * @param start the start position of the range shape |
| 261 | + * @param end the end position of the range shape. If {@code end == paragraph.length() + 1}, the newline character |
| 262 | + * will be included in the selection by selecting the rest of the line |
| 263 | + */ |
| 264 | + private PathElement[] getRangeShapeSafely(int start, int end) { |
| 265 | + PathElement[] shape; |
| 266 | + if (end <= paragraph.length()) { |
| 267 | + // selection w/o newline char |
| 268 | + shape = getRangeShape(start, end); |
| 269 | + } else { |
| 270 | + // Selection includes a newline character. |
| 271 | + if (paragraph.length() == 0) { |
| 272 | + // empty paragraph |
| 273 | + shape = createRectangle(getLayoutX(), getLayoutY(), getWidth(), getHeight()); |
| 274 | + } else if (start == paragraph.length()) { |
| 275 | + // selecting only the newline char |
| 276 | + |
| 277 | + // calculate the bounds of the last character |
| 278 | + shape = getRangeShape(start - 1, start); |
| 279 | + LineTo lineToTopRight = (LineTo) shape[shape.length - 4]; |
| 280 | + shape = createRectangle(lineToTopRight.getX(), lineToTopRight.getY(), getWidth(), getHeight()); |
| 281 | + } else { |
| 282 | + shape = getRangeShape(start, paragraph.length()); |
| 283 | + // Since this might be a wrapped multi-line paragraph, |
| 284 | + // there may be multiple groups of (1 MoveTo, 3 LineTo objects) for each line: |
| 285 | + // MoveTo(topLeft), LineTo(topRight), LineTo(bottomRight), LineTo(bottomLeft) |
| 286 | + |
| 287 | + // We only need to adjust the top right and bottom right corners to extend to the |
| 288 | + // width/height of the line, simulating a full line selection. |
| 289 | + int length = shape.length; |
| 290 | + int bottomRightIndex = length - 3; |
| 291 | + int topRightIndex = bottomRightIndex - 1; |
| 292 | + LineTo lineToTopRight = (LineTo) shape[topRightIndex]; |
| 293 | + shape[topRightIndex] = new LineTo(getWidth(), lineToTopRight.getY()); |
| 294 | + shape[bottomRightIndex] = new LineTo(getWidth(), getHeight()); |
| 295 | + } |
| 296 | + } |
| 297 | + return shape; |
| 298 | + } |
| 299 | + |
| 300 | + private PathElement[] createRectangle(double topLeftX, double topLeftY, double bottomRightX, double bottomRightY) { |
| 301 | + return new PathElement[] { |
| 302 | + new MoveTo(topLeftX, topLeftY), |
| 303 | + new LineTo(bottomRightX, topLeftY), |
| 304 | + new LineTo(bottomRightX, bottomRightY), |
| 305 | + new LineTo(topLeftX, bottomRightY) |
| 306 | + }; |
253 | 307 | } |
254 | 308 |
|
255 | 309 | private void updateBackgroundShapes() { |
|
0 commit comments