Skip to content

Commit 1792167

Browse files
Final Text & Selection Inset Fix (#67)
1 parent 8ceb3fd commit 1792167

File tree

2 files changed

+29
-37
lines changed

2 files changed

+29
-37
lines changed

Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,16 @@ public class TextLayoutManager: NSObject {
9797
delegate?.layoutManagerMaxWidthDidChange(newWidth: maxLineWidth + edgeInsets.horizontal)
9898
}
9999
}
100-
/// The maximum width available to lay out lines in.
100+
101+
/// The maximum width available to lay out lines in, used to determine how much space is available for laying out
102+
/// lines. Evals to `.greatestFiniteMagnitude` when ``wrapLines`` is `false`.
101103
var maxLineLayoutWidth: CGFloat {
102-
wrapLines ? (delegate?.textViewportSize().width ?? .greatestFiniteMagnitude) - edgeInsets.horizontal
103-
: .greatestFiniteMagnitude
104+
wrapLines ? wrapLinesWidth : .greatestFiniteMagnitude
105+
}
106+
107+
/// The width of the space available to draw text fragments when wrapping lines.
108+
var wrapLinesWidth: CGFloat {
109+
(delegate?.textViewportSize().width ?? .greatestFiniteMagnitude) - edgeInsets.horizontal
104110
}
105111

106112
/// Contains all data required to perform layout on a text line.

Sources/CodeEditTextView/TextSelectionManager/TextSelectionManager+FillRects.swift

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,56 +9,42 @@ import Foundation
99

1010
extension TextSelectionManager {
1111
/// Calculate a set of rects for a text selection suitable for filling with the selection color to indicate a
12-
/// multi-line selection.
13-
///
14-
/// The returned rects are inset by edge insets passed to the text view, the given `rect` parameter can be the 'raw'
15-
/// rect to draw in, no need to inset it before this method call.
12+
/// multi-line selection. The returned rects surround all selected line fragments for the given selection,
13+
/// following the available text layout space, rather than the available selection layout space.
1614
///
1715
/// - Parameters:
1816
/// - rect: The bounding rect of available draw space.
1917
/// - textSelection: The selection to use.
2018
/// - Returns: An array of rects that the selection overlaps.
2119
func getFillRects(in rect: NSRect, for textSelection: TextSelection) -> [CGRect] {
22-
guard let layoutManager else { return [] }
23-
let range = textSelection.range
24-
25-
var fillRects: [CGRect] = []
26-
guard let firstLinePosition = layoutManager.lineStorage.getLine(atOffset: range.location),
27-
let lastLinePosition = range.max == layoutManager.lineStorage.length
28-
? layoutManager.lineStorage.last
29-
: layoutManager.lineStorage.getLine(atOffset: range.max) else {
20+
guard let layoutManager,
21+
let range = textSelection.range.intersection(textView?.visibleTextRange ?? .zero) else {
3022
return []
3123
}
3224

33-
let insetXPos = max(edgeInsets.left, rect.minX)
34-
let insetWidth = max(0, rect.maxX - insetXPos - edgeInsets.right)
35-
let insetRect = NSRect(x: insetXPos, y: rect.origin.y, width: insetWidth, height: rect.height)
36-
37-
// Calculate the first line and any rects selected
38-
// If the last line position is not the same as the first, calculate any rects from that line.
39-
// If there's > 0 space between the first and last positions, add a rect between them to cover any
40-
// intermediate lines.
25+
var fillRects: [CGRect] = []
4126

42-
let firstLineRects = getFillRects(in: rect, selectionRange: range, forPosition: firstLinePosition)
43-
let lastLineRects: [CGRect] = if lastLinePosition.range != firstLinePosition.range {
44-
getFillRects(in: rect, selectionRange: range, forPosition: lastLinePosition)
27+
let textWidth = if layoutManager.maxLineLayoutWidth == .greatestFiniteMagnitude {
28+
layoutManager.maxLineWidth
4529
} else {
46-
[]
30+
layoutManager.maxLineLayoutWidth
4731
}
32+
let maxWidth = max(textWidth, layoutManager.wrapLinesWidth)
33+
let validTextDrawingRect = CGRect(
34+
x: layoutManager.edgeInsets.left,
35+
y: rect.minY,
36+
width: maxWidth,
37+
height: rect.height
38+
).intersection(rect)
4839

49-
fillRects.append(contentsOf: firstLineRects + lastLineRects)
50-
51-
if firstLinePosition.yPos + firstLinePosition.height < lastLinePosition.yPos {
52-
fillRects.append(CGRect(
53-
x: insetXPos,
54-
y: firstLinePosition.yPos + firstLinePosition.height,
55-
width: insetWidth,
56-
height: lastLinePosition.yPos - (firstLinePosition.yPos + firstLinePosition.height)
57-
))
40+
for linePosition in layoutManager.lineStorage.linesInRange(range) {
41+
fillRects.append(
42+
contentsOf: getFillRects(in: validTextDrawingRect, selectionRange: range, forPosition: linePosition)
43+
)
5844
}
5945

6046
// Pixel align these to avoid aliasing on the edges of each rect that should be a solid box.
61-
return fillRects.map { $0.intersection(insetRect).pixelAligned }
47+
return fillRects.map { $0.intersection(validTextDrawingRect).pixelAligned }
6248
}
6349

6450
/// Find fill rects for a specific line position.

0 commit comments

Comments
 (0)