summaryrefslogtreecommitdiff
path: root/src/macos/Sources
diff options
context:
space:
mode:
Diffstat (limited to 'src/macos/Sources')
-rw-r--r--src/macos/Sources/PardesView.swift105
1 files changed, 97 insertions, 8 deletions
diff --git a/src/macos/Sources/PardesView.swift b/src/macos/Sources/PardesView.swift
index aa8ac425..10c7fd8b 100644
--- a/src/macos/Sources/PardesView.swift
+++ b/src/macos/Sources/PardesView.swift
@@ -327,11 +327,17 @@ private struct Metrics {
private struct TaglineMetrics {
let percent: UInt8
let fonts: [CTFont]
- let baseline: CGFloat
+ /// The tagline face's OWN ascent. Deliberately not folded together with the
+ /// band's top offset the way it used to be: that offset is per-row now, and
+ /// a baseline carrying one row's offset would pin every band back to centre.
+ let ascent: CGFloat
let xOffset: CGFloat
- let top: CGFloat
let height: CGFloat
let asciiGlyphs: [[CGGlyph]]
+ /// Kept so the band rules below can work in the physical pixels the core
+ /// states them in, and hand back points.
+ private let scale: CGFloat
+ private let bodyCellHeight: CGFloat
init(body: Metrics, percent: UInt8, scale rawScale: CGFloat) {
let scale = max(1, rawScale)
@@ -339,6 +345,8 @@ private struct TaglineMetrics {
(value * scale).rounded(rule) / scale
}
self.percent = percent
+ self.scale = scale
+ bodyCellHeight = body.cellHeight
let size = CTFontGetSize(body.fonts[0]) * CGFloat(percent) / 100
let faces = body.fonts.map { CTFontCreateCopyWithAttributes($0, size, nil, nil) }
fonts = faces
@@ -349,9 +357,7 @@ private struct TaglineMetrics {
// clamp here too: a malformed/custom host value must not paint a tag
// over the body row below it.
height = min(body.cellHeight, measuredHeight)
- top = snap(max(0, (body.cellHeight - height) / 2), .toNearestOrAwayFromZero)
- let ascent = max(1 / scale, snap(CTFontGetAscent(face), .toNearestOrAwayFromZero))
- baseline = snap(top + ascent, .toNearestOrAwayFromZero)
+ ascent = max(1 / scale, snap(CTFontGetAscent(face), .toNearestOrAwayFromZero))
xOffset = snap(max(0, (body.cellWidth - advance(face, 0x4D)) / 2), .toNearestOrAwayFromZero)
asciiGlyphs = faces.map { font in
var chars = Array(UniChar(0)..<UniChar(128))
@@ -360,6 +366,33 @@ private struct TaglineMetrics {
return glyphs
}
}
+
+ private var cellPixels: UInt32 { UInt32(max(1, (bodyCellHeight * scale).rounded())) }
+ private var bandPixels: UInt32 { UInt32(max(1, (height * scale).rounded())) }
+
+ /// Where this band sits inside row `row`, measured down from the row's top.
+ /// The rule belongs to the core (`pardes_tagline_band_offset`) so that this
+ /// shell and the SDL one cannot disagree about it: row zero and the first
+ /// pane-tag row meet at a shared rule, a Tagbottom band on the last row
+ /// faces the window edge, and everything between is centred.
+ func top(row: Int, canvasHeight: CGFloat) -> CGFloat {
+ CGFloat(pardes_tagline_band_offset(
+ UInt16(clamping: row), Float(canvasHeight * scale), cellPixels, bandPixels)) / scale
+ }
+
+ /// Thickness of the rule joining the topbar band to the first pane-tag band,
+ /// zero when the two are meant to join directly.
+ var borderThickness: CGFloat {
+ CGFloat(pardes_topbar_pane_border_px(cellPixels, bandPixels)) / scale
+ }
+
+ /// Top of that rule. Placed the way the SDL shell places it, in WHOLE
+ /// pixels: half the rule above the topbar/pane boundary, floored, so a
+ /// one-pixel rule lands on the boundary instead of smeared across two rows.
+ var borderTop: CGFloat {
+ let px = pardes_topbar_pane_border_px(cellPixels, bandPixels)
+ return (CGFloat(cellPixels * UInt32(PARDES_TOPBAR_H)) - CGFloat(px / 2)) / scale
+ }
}
private func modifiers(_ flags: NSEvent.ModifierFlags) -> UInt32 {
@@ -794,9 +827,15 @@ final class PardesView: NSView {
// that feel slow. Antialiasing is off because touching rects share an
// edge, and blending that edge twice draws a visible seam.
ctx.setShouldAntialias(false)
+ // Band geometry is stated against the whole canvas: the last row's rule
+ // depends on where the window edge is, not just on the row index.
+ let canvasHeight = bounds.height
for row in 0..<rows {
let base = row * cols
let y = CGFloat(row) * cellHeight
+ // One offset per ROW, not per run: it is the same answer for every
+ // cell in the row, and it is a call across the ABI.
+ let bandTop = taglines.top(row: row, canvasHeight: canvasHeight)
var start = 0
var color = resolve(cells[base], block: blockY == row && blockX == 0,
ground: ground, clearGround: clearGround).bg
@@ -814,7 +853,7 @@ final class PardesView: NSView {
// bgClear runs are the ground showing through, and the ground is
// already clear — painting them would be painting the hole shut.
if color != bgClear {
- let bandY = tagline ? y + taglines.top : y
+ let bandY = tagline ? y + bandTop : y
let bandHeight = tagline ? taglines.height : cellHeight
fill(ctx, CGRect(x: CGFloat(start) * cellWidth, y: bandY,
width: CGFloat(col - start) * cellWidth, height: bandHeight),
@@ -826,6 +865,50 @@ final class PardesView: NSView {
}
}
+ // The rule joining the topbar band to the first pane-tag band. Both were
+ // centred in their own rows until now, and centring two reduced-height
+ // bands is exactly what leaves their unused half-bands touching with a
+ // strip of window background showing between them — the case
+ // config.gui_topbar_pane_border_px exists to close. Drawn only when that
+ // first pane row really carries a band, so a session whose second row is
+ // pane BODY gets no stray line across it.
+ let ruleThickness = taglines.borderThickness
+ let ruleColor = pardes_topbar_pane_border_rgb()
+ if ruleThickness > 0, ruleColor != UInt32(PARDES_COLOR_DEFAULT),
+ rows > Int(PARDES_TOPBAR_H) {
+ let base = Int(PARDES_TOPBAR_H) * cols
+ let joins = (0..<cols).contains { col in
+ let flags = cells[base + col].flags
+ return flags & UInt8(PARDES_CELL_DEFAULT) == 0
+ && flags & UInt8(PARDES_CELL_TAGLINE) != 0
+ }
+ if joins {
+ fill(ctx, CGRect(x: 0, y: taglines.borderTop, width: bounds.width,
+ height: ruleThickness), ruleColor, 1)
+ }
+ }
+
+ // The core grid contains only complete cells, so any window height that
+ // is not a whole multiple of the cell leaves a strip below the last row.
+ // A Tagbottom band there is bottom-aligned against the window edge, and
+ // without this that strip shows the page colour under it.
+ let gridBottom = CGFloat(rows) * cellHeight
+ if gridBottom < canvasHeight, rows > 0 {
+ let base = (rows - 1) * cols
+ for col in 0..<cols {
+ let cell = cells[base + col]
+ guard cell.flags & UInt8(PARDES_CELL_DEFAULT) == 0,
+ cell.flags & UInt8(PARDES_CELL_TAGLINE) != 0 else { continue }
+ let bg = resolve(cell, block: false, ground: ground,
+ clearGround: clearGround).bg
+ if bg != bgClear {
+ fill(ctx, CGRect(x: 0, y: gridBottom, width: bounds.width,
+ height: canvasHeight - gridBottom), bg, 1)
+ }
+ break
+ }
+ }
+
// CTFontDrawGlyphs lays glyph outlines out with +y up, and isFlipped hands
// us a y-down CTM, so drawing text directly in view space renders every
// line mirrored. Un-flip once for the whole glyph pass and convert each
@@ -852,6 +935,7 @@ final class PardesView: NSView {
for row in 0..<rows {
drawRow(ctx, cells, base: row * cols, cols: cols,
baseline: height - (CGFloat(row) * cellHeight + metrics.ascent),
+ taglineTop: taglines.top(row: row, canvasHeight: canvasHeight),
blockCol: blockY == row ? blockX : -1)
}
ctx.restoreGState()
@@ -876,7 +960,8 @@ final class PardesView: NSView {
let fg = resolve(cell, block: false,
ground: themeBG ?? pardesDefaultBG, clearGround: false).fg
let tagline = cell.flags & UInt8(PARDES_CELL_TAGLINE) != 0
- let caretY = CGFloat(y) * cellHeight + (tagline ? taglines.top : 0)
+ let caretY = CGFloat(y) * cellHeight
+ + (tagline ? taglines.top(row: y, canvasHeight: bounds.height) : 0)
let caretHeight = tagline ? taglines.height : cellHeight
ctx.setShouldAntialias(false)
fill(ctx, CGRect(x: CGFloat(x) * cellWidth, y: caretY,
@@ -1044,6 +1129,10 @@ final class PardesView: NSView {
base: Int,
cols: Int,
baseline: CGFloat,
+ /// Where this row's tagline band starts, from the row's top. Passed in
+ /// 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
) {
var runFace = Face.regular
@@ -1069,7 +1158,7 @@ final class PardesView: NSView {
if cell.flags & UInt8(PARDES_CELL_DEFAULT) != 0 { continue }
let tagline = cell.flags & UInt8(PARDES_CELL_TAGLINE) != 0
let cellBaseline = tagline
- ? baseline + metrics.ascent - taglines.baseline
+ ? baseline + metrics.ascent - (taglineTop + taglines.ascent)
: baseline
// clearGround: false — this pass only reads `fg`, and a glyph is
// never the hole in the ground.