Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,16 @@ public class TextLayoutManager: NSObject {
delegate?.layoutManagerMaxWidthDidChange(newWidth: maxLineWidth + edgeInsets.horizontal)
}
}
/// The maximum width available to lay out lines in.

/// The maximum width available to lay out lines in, used to determine how much space is available for laying out
/// lines. Evals to `.greatestFiniteMagnitude` when ``wrapLines`` is `false`.
var maxLineLayoutWidth: CGFloat {
wrapLines ? (delegate?.textViewportSize().width ?? .greatestFiniteMagnitude) - edgeInsets.horizontal
: .greatestFiniteMagnitude
wrapLines ? wrapLinesWidth : .greatestFiniteMagnitude
}

/// The width of the space available to draw text fragments when wrapping lines.
var wrapLinesWidth: CGFloat {
(delegate?.textViewportSize().width ?? .greatestFiniteMagnitude) - edgeInsets.horizontal
}

/// Contains all data required to perform layout on a text line.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,56 +9,42 @@ import Foundation

extension TextSelectionManager {
/// Calculate a set of rects for a text selection suitable for filling with the selection color to indicate a
/// multi-line selection.
///
/// The returned rects are inset by edge insets passed to the text view, the given `rect` parameter can be the 'raw'
/// rect to draw in, no need to inset it before this method call.
/// multi-line selection. The returned rects surround all selected line fragments for the given selection,
/// following the available text layout space, rather than the available selection layout space.
///
/// - Parameters:
/// - rect: The bounding rect of available draw space.
/// - textSelection: The selection to use.
/// - Returns: An array of rects that the selection overlaps.
func getFillRects(in rect: NSRect, for textSelection: TextSelection) -> [CGRect] {
guard let layoutManager else { return [] }
let range = textSelection.range

var fillRects: [CGRect] = []
guard let firstLinePosition = layoutManager.lineStorage.getLine(atOffset: range.location),
let lastLinePosition = range.max == layoutManager.lineStorage.length
? layoutManager.lineStorage.last
: layoutManager.lineStorage.getLine(atOffset: range.max) else {
guard let layoutManager,
let range = textSelection.range.intersection(textView?.visibleTextRange ?? .zero) else {
return []
}

let insetXPos = max(edgeInsets.left, rect.minX)
let insetWidth = max(0, rect.maxX - insetXPos - edgeInsets.right)
let insetRect = NSRect(x: insetXPos, y: rect.origin.y, width: insetWidth, height: rect.height)

// Calculate the first line and any rects selected
// If the last line position is not the same as the first, calculate any rects from that line.
// If there's > 0 space between the first and last positions, add a rect between them to cover any
// intermediate lines.
var fillRects: [CGRect] = []

let firstLineRects = getFillRects(in: rect, selectionRange: range, forPosition: firstLinePosition)
let lastLineRects: [CGRect] = if lastLinePosition.range != firstLinePosition.range {
getFillRects(in: rect, selectionRange: range, forPosition: lastLinePosition)
let textWidth = if layoutManager.maxLineLayoutWidth == .greatestFiniteMagnitude {
layoutManager.maxLineWidth
} else {
[]
layoutManager.maxLineLayoutWidth
}
let maxWidth = max(textWidth, layoutManager.wrapLinesWidth)
let validTextDrawingRect = CGRect(
x: layoutManager.edgeInsets.left,
y: rect.minY,
width: maxWidth,
height: rect.height
).intersection(rect)

fillRects.append(contentsOf: firstLineRects + lastLineRects)

if firstLinePosition.yPos + firstLinePosition.height < lastLinePosition.yPos {
fillRects.append(CGRect(
x: insetXPos,
y: firstLinePosition.yPos + firstLinePosition.height,
width: insetWidth,
height: lastLinePosition.yPos - (firstLinePosition.yPos + firstLinePosition.height)
))
for linePosition in layoutManager.lineStorage.linesInRange(range) {
fillRects.append(
contentsOf: getFillRects(in: validTextDrawingRect, selectionRange: range, forPosition: linePosition)
)
}

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

/// Find fill rects for a specific line position.
Expand Down