diff options
Diffstat (limited to 'src/macos/Sources')
| -rw-r--r-- | src/macos/Sources/PardesView.swift | 105 |
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. |
