summaryrefslogtreecommitdiff
path: root/src/macos
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-15 17:36:32 -0300
committerGabriel Schneider <[email protected]>2026-09-15 17:43:13 -0300
commitcf7cfd96a62b26321de970496670005cf411c94c (patch)
treefd3da24fb47b5d88cf2135874900a35397f7e1f9 /src/macos
parente462d566f710e714e3b7176672e98fc82a6dea62 (diff)
downloadpardes-cf7cfd96a62b26321de970496670005cf411c94c.tar.gz
pardes-cf7cfd96a62b26321de970496670005cf411c94c.zip
Center GUI pane indicators using cached font ink bounds
Diffstat (limited to 'src/macos')
-rw-r--r--src/macos/Sources/PardesView.swift67
1 files changed, 63 insertions, 4 deletions
diff --git a/src/macos/Sources/PardesView.swift b/src/macos/Sources/PardesView.swift
index e2234264..f64678ee 100644
--- a/src/macos/Sources/PardesView.swift
+++ b/src/macos/Sources/PardesView.swift
@@ -562,6 +562,11 @@ final class PardesView: NSView {
// body and tagline variants whenever the body face changes.
private var bodyGlyphCache: [UInt32: CGGlyph] = [:]
private var taglineGlyphCache: [UInt32: CGGlyph] = [:]
+ private struct GripGlyph {
+ let line: CTLine
+ let ink: CGRect
+ }
+ private var gripGlyphCache: [String: GripGlyph] = [:]
// Scratch for one batched run of glyphs. Held rather than made per row so a
// full redraw does not allocate 24 times.
private var runGlyphs: [CGGlyph] = []
@@ -710,6 +715,7 @@ final class PardesView: NSView {
// it would draw a different character, not a missing one.
bodyGlyphCache.removeAll(keepingCapacity: true)
taglineGlyphCache.removeAll(keepingCapacity: true)
+ gripGlyphCache.removeAll(keepingCapacity: true)
metrics = next
taglines = TaglineMetrics(body: next, percent: taglines.percent, scale: scale)
metricsScale = scale
@@ -728,6 +734,7 @@ final class PardesView: NSView {
guard percent != taglines.percent else { return false }
taglines = TaglineMetrics(body: metrics, percent: percent, scale: metricsScale)
taglineGlyphCache.removeAll(keepingCapacity: true)
+ gripGlyphCache.removeAll(keepingCapacity: true)
pardes_row_metrics(UInt16(max(1,(cellWidth*metricsScale).rounded())),UInt16(max(1,(cellHeight*metricsScale).rounded())),
UInt16(max(1,(taglines.width*metricsScale).rounded())),UInt16(max(1,(taglines.height*metricsScale).rounded())))
needsDisplay = true
@@ -1085,11 +1092,25 @@ final class PardesView: NSView {
ctx.translateBy(x: 0, y: bounds.height)
ctx.scaleBy(x: 1, y: -1)
let height = bounds.height
+ var paneGripCells = Set<Int>()
+ if frozenImages == nil {
+ for index in 0..<pardes_tag_layer_limit() {
+ if pardes_tag_layer_value(index, 0) == 0 || pardes_tag_layer_value(index, 1) != 2 { continue }
+ let x = Int(pardes_tag_layer_value(index, 4)) - Int(pardes_tag_text_inset())
+ let y = Int(pardes_tag_layer_value(index, 5))
+ if x >= 0 && x < cols && y >= 0 && y < rows { paneGripCells.insert(y * cols + x) }
+ }
+ } else {
+ for layer in presentedTagLayers where layer.values[1] == 2 {
+ let x = layer.values[4] - Int(pardes_tag_text_inset()), y = layer.values[5]
+ if x >= 0 && x < cols && y >= 0 && y < rows { paneGripCells.insert(y * cols + x) }
+ }
+ }
for row in 0..<rows {
drawRow(ctx, cells, base: row * cols, cols: cols, row: row,
baseline: height - (CGFloat(row) * cellHeight + metrics.ascent),
taglineTop: taglines.top(row: row, canvasHeight: canvasHeight),
- blockCol: blockY == row ? blockX : -1)
+ blockCol: blockY == row ? blockX : -1, paneGripCells: paneGripCells)
}
ctx.restoreGState()
@@ -1170,16 +1191,49 @@ final class PardesView: NSView {
}
}
+ private func gripGlyph(_ text: String, face: Face) -> GripGlyph? {
+ let key = "\(face.rawValue):\(text)"
+ if let cached = gripGlyphCache[key] { return cached }
+ let attributed = NSAttributedString(string: text, attributes: [fontAttribute: taglines.fonts[face.rawValue],
+ NSAttributedString.Key(kCTForegroundColorFromContextAttributeName as String): true])
+ let line = CTLineCreateWithAttributedString(attributed as CFAttributedString)
+ let ink = CTLineGetBoundsWithOptions(line, [.useGlyphPathBounds])
+ guard !ink.isNull, !ink.isInfinite, !ink.isEmpty else { return nil }
+ let measured = GripGlyph(line: line, ink: ink)
+ gripGlyphCache[key] = measured
+ return measured
+ }
+
private func drawTagGrip(_ ctx: CGContext, values: [Int], canonical: UnsafePointer<pardes_cell_s>, cols: Int, rows: Int) {
let width = Int(pardes_grip_columns())
let x = values[4] - Int(pardes_tag_text_inset()), y = values[5]
- if values[1] != 2 || x < 0 || x >= cols || y >= rows || taglines.height < 6 || taglines.width < 3 { return }
+ if values[1] != 2 || x < 0 || x >= cols || y < 0 || y >= rows { return }
var cell = canonical[y * cols + x]
let text = withUnsafeBytes(of: &cell.text) { String(decoding: $0.prefix(Int(cell.len)), as: UTF8.self) }
- if text != " " { return }
+ if text.isEmpty { return }
let style = resolve(cell, block:false, ground:themeBG ?? pardesDefaultBG, clearGround:false)
let centerX = (CGFloat(x) + CGFloat(width) / 2) * cellWidth
let centerY = CGFloat(y) * cellHeight + taglines.top(row:y, canvasHeight:bounds.height) + taglines.height / 2
+ if text != " " {
+ let face = Face(bold: cell.attrs & UInt16(PARDES_ATTR_BOLD) != 0,
+ italic: cell.attrs & UInt16(PARDES_ATTR_ITALIC) != 0)
+ guard style.visible, let measured = gripGlyph(text, face: face) else { return }
+ let originX = ((centerX - measured.ink.midX) * metricsScale).rounded() / metricsScale
+ let baseline = ((bounds.height - centerY - measured.ink.midY) * metricsScale).rounded() / metricsScale
+ ctx.saveGState()
+ ctx.clip(to: CGRect(x: CGFloat(x) * cellWidth,
+ y: CGFloat(y) * cellHeight + taglines.top(row:y, canvasHeight:bounds.height),
+ width: CGFloat(width) * cellWidth, height: taglines.height))
+ ctx.translateBy(x: 0, y: bounds.height)
+ ctx.scaleBy(x: 1, y: -1)
+ ctx.textMatrix = .identity
+ ctx.textPosition = CGPoint(x: originX, y: baseline)
+ setFill(ctx, style.fg, style.alpha)
+ CTLineDraw(measured.line, ctx)
+ ctx.restoreGState()
+ return
+ }
+ if taglines.height < 6 || taglines.width < 3 { return }
let halfWidth = max(1, min(3, cellWidth - 2))
fill(ctx, CGRect(x:floor(centerX-halfWidth), y:floor(centerY-2), width:2*halfWidth, height:1), style.fg, 1)
fill(ctx, CGRect(x:floor(centerX-halfWidth), y:floor(centerY+1), width:2*halfWidth, height:1), style.fg, 1)
@@ -1482,7 +1536,8 @@ final class PardesView: NSView {
/// rather than recomputed per cell: it is one answer per row, and the
/// glyph has to sit in the band the background pass painted.
taglineTop: CGFloat,
- blockCol: Int
+ blockCol: Int,
+ paneGripCells: Set<Int>
) {
var runFace = Face.regular
var runTagline = false
@@ -1544,6 +1599,10 @@ final class PardesView: NSView {
let face = Face(bold: cell.attrs & UInt16(PARDES_ATTR_BOLD) != 0,
italic: cell.attrs & UInt16(PARDES_ATTR_ITALIC) != 0)
+ if paneGripCells.contains(base + col) && gripGlyph(text, face: face) != nil {
+ // The layer pass centers this symbol by its actual ink bounds.
+ continue
+ }
let units = text.utf16
let known = units.count == 1 ? glyph(face, units.first!, tagline: tagline) : 0
if known != 0 {