summaryrefslogtreecommitdiff
path: root/src/macos/Sources
diff options
context:
space:
mode:
Diffstat (limited to 'src/macos/Sources')
-rw-r--r--src/macos/Sources/AppDelegate.swift2
-rw-r--r--src/macos/Sources/PardesView.swift133
2 files changed, 126 insertions, 9 deletions
diff --git a/src/macos/Sources/AppDelegate.swift b/src/macos/Sources/AppDelegate.swift
index eb499f1b..96f4a184 100644
--- a/src/macos/Sources/AppDelegate.swift
+++ b/src/macos/Sources/AppDelegate.swift
@@ -671,6 +671,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, PardesViewDelegate {
grid.cols, grid.rows,
UInt16((view.cellWidth * scale).rounded()),
UInt16((view.cellHeight * scale).rounded()))
+ pardes_row_metrics(UInt16(max(1,(view.cellWidth*scale).rounded())), UInt16(max(1,(view.cellHeight*scale).rounded())),
+ UInt16(max(1,(view.taglineCellWidth*scale).rounded())), UInt16(max(1,(view.taglineBandHeight*scale).rounded())))
pump()
}
diff --git a/src/macos/Sources/PardesView.swift b/src/macos/Sources/PardesView.swift
index 2160047d..b14e1aa0 100644
--- a/src/macos/Sources/PardesView.swift
+++ b/src/macos/Sources/PardesView.swift
@@ -736,12 +736,15 @@ final class PardesView: NSView {
guard percent != taglines.percent else { return false }
taglines = TaglineMetrics(body: metrics, percent: percent, scale: metricsScale)
taglineGlyphCache.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
return true
}
var taglinePercent: UInt8 { taglines.percent }
var taglineBandHeight: CGFloat { taglines.height }
+ var taglineCellWidth: CGFloat { taglines.width }
/// Seed runtime Config after init and update it after every successful
/// adopt/zoom. Points are the native owner's unit; the cell's backing-pixel
@@ -905,7 +908,10 @@ final class PardesView: NSView {
// Only a canonical presentation advances the attachment baseline.
// During a transition deleted panes keep their cropped CGImages alive
// here even after both the core placement and image cache entry vanish.
- if !animatedPanels { presentedImages = frameImages }
+ if !animatedPanels {
+ presentedImages = frameImages
+ presentedBodyLayers = snapshotBodyLayers()
+ }
presentationSerial &+= 1
let animationWoke = pardes_frame_presented(animatedPanels)
let pointerMoved = refreshPresentedPointer()
@@ -936,9 +942,9 @@ final class PardesView: NSView {
guard changed else { return false }
if let button = latchedButton {
latchedCell = at
- pardes_mouse(button, PARDES_MOUSE_DRAG, at.col, at.row, pointerModifiers)
+ sendMouse(button, PARDES_MOUSE_DRAG, at.col, at.row, pointerModifiers)
} else {
- pardes_mouse(PARDES_MOUSE_NONE, PARDES_MOUSE_MOTION,
+ sendMouse(PARDES_MOUSE_NONE, PARDES_MOUSE_MOTION,
at.col, at.row, pointerModifiers)
}
return true
@@ -1159,6 +1165,101 @@ final class PardesView: NSView {
width: caretWidth, height: caretHeight), fg, 1)
}
}
+ if frozenImages == nil {
+ drawBodyLayers(ctx, hideCursor:hideCursor)
+ } else {
+ for layer in presentedBodyLayers {
+ layer.cells.withUnsafeBufferPointer { cells in
+ if let base = cells.baseAddress { drawBodyLayer(ctx, values:layer.values, cells:base, hideCursor:true) }
+ }
+ }
+ }
+ }
+
+ private struct FrozenBodyLayer {
+ let values: [Int]
+ let cells: [pardes_cell_s]
+ }
+ private var presentedBodyLayers: [FrozenBodyLayer] = []
+
+ private func snapshotBodyLayers() -> [FrozenBodyLayer] {
+ var result: [FrozenBodyLayer] = []
+ for index in 0..<pardes_body_layer_limit() {
+ let values = (0..<11).map { Int(pardes_body_layer_value(index, UInt32($0))) }
+ if values[0] == 0 { continue }
+ if let cells = pardes_body_layer_cells(index) {
+ result.append(FrozenBodyLayer(values:values, cells:Array(UnsafeBufferPointer(start:cells,count:values[0]*values[1]))))
+ }
+ }
+ return result
+ }
+
+ private func drawBodyLayers(_ ctx: CGContext, hideCursor:Bool) {
+ for index in 0..<pardes_body_layer_limit() {
+ let values = (0..<11).map { Int(pardes_body_layer_value(index, UInt32($0))) }
+ if values[0] == 0 { continue }
+ if let cells = pardes_body_layer_cells(index) { drawBodyLayer(ctx, values:values, cells:cells, hideCursor:hideCursor) }
+ }
+ }
+
+ private func drawBodyLayer(_ ctx: CGContext, values: [Int], cells: UnsafePointer<pardes_cell_s>, hideCursor: Bool) {
+ func value(_ field: Int) -> Int { values[field] }
+ let rows = value(0), cols = value(1), contexts = value(2)
+ let left = CGFloat(value(3)) * cellWidth, top = CGFloat(value(4)) * cellHeight
+ let viewport = CGRect(x: left, y: top, width: CGFloat(value(5))*cellWidth,
+ height: CGFloat(value(6))*cellHeight)
+ ctx.saveGState()
+ ctx.clip(to: viewport)
+ fill(ctx, viewport, themeBG ?? pardesDefaultBG, 1)
+ for row in 0..<rows {
+ let compact = row < contexts
+ let advance = compact ? taglines.width : cellWidth
+ let height = compact ? taglines.height : cellHeight
+ let y = top + CGFloat(min(row, contexts))*taglines.height + CGFloat(max(0, row-contexts))*cellHeight
+ if y >= viewport.maxY { break }
+ let rowStyle = resolve(cells[row*cols], block: false, ground: themeBG ?? pardesDefaultBG, clearGround: false)
+ fill(ctx, CGRect(x: left, y: y, width: viewport.width, height: height), rowStyle.bg, 1)
+ for pass in 0...1 {
+ for col in 0..<cols {
+ var cell = cells[row*cols+col]
+ let isCursor = !hideCursor && col == value(8) && row == value(9)
+ let style = resolve(cell, block: isCursor && value(10) == 0,
+ ground: themeBG ?? pardesDefaultBG, clearGround: false)
+ let x = left + CGFloat(col)*advance
+ if pass == 0 {
+ fill(ctx, CGRect(x: x, y: y, width: advance, height: height), style.bg, 1)
+ continue
+ }
+ let text = withUnsafeBytes(of: &cell.text) { bytes in
+ String(decoding: bytes.prefix(Int(cell.len)), as: UTF8.self)
+ }
+ if style.visible && !text.isEmpty && text != " " {
+ let face = Face(bold: cell.attrs & UInt16(PARDES_ATTR_BOLD) != 0,
+ italic: cell.attrs & UInt16(PARDES_ATTR_ITALIC) != 0)
+ let font = compact ? taglines.fonts[face.rawValue] : metrics.fonts[face.rawValue]
+ ctx.saveGState()
+ ctx.translateBy(x: 0, y: bounds.height)
+ ctx.scaleBy(x: 1, y: -1)
+ let baseline = bounds.height - y - (compact ? taglines.ascent : metrics.ascent)
+ drawRules(ctx, cell, style, x: x, width: advance, baseline: baseline)
+ setFill(ctx, style.fg, style.alpha)
+ ctx.textPosition = CGPoint(x: x, y: baseline)
+ let line = NSAttributedString(string: text, attributes: [fontAttribute: font])
+ CTLineDraw(CTLineCreateWithAttributedString(line as CFAttributedString), ctx)
+ ctx.restoreGState()
+ }
+ if isCursor && value(10) != 0 {
+ fill(ctx, CGRect(x: x, y: y, width: max(1, advance/8), height: height), style.fg, 1)
+ }
+ }
+ }
+ }
+ let border = pardes_topbar_pane_border_rgb()
+ if border != UInt32(PARDES_COLOR_DEFAULT) {
+ fill(ctx, CGRect(x: left, y: top+CGFloat(contexts)*taglines.height,
+ width: viewport.width, height: 1/taglines.scale), border, 1)
+ }
+ ctx.restoreGState()
}
/// What identifies a decoded raster: the pane's lifetime, the page, and the
@@ -1523,23 +1624,36 @@ final class PardesView: NSView {
// keyboard state; the NSEvent overrides always pass the real mask. Ctrl is
// the one the core actually consults — a left press with it held is
// goto-definition — so dropping it here would silently delete a feature.
+ private var sampledBodyPointer: CGPoint?
+
+ private func sendMouse(_ button: pardes_mouse_button_e, _ kind: pardes_mouse_kind_e,
+ _ col: UInt16, _ row: UInt16, _ mods: UInt32) {
+ guard let point = sampledBodyPointer else {
+ pardes_mouse(button, kind, col, row, mods)
+ return
+ }
+ let scale = metricsScale
+ pardes_mouse_pixel(button, kind, col, row, mods, Float(point.x*scale), Float(point.y*scale),
+ Float(cellWidth*scale), Float(cellHeight*scale), Float(taglines.width*scale), Float(taglines.height*scale))
+ }
+
func press(_ button: pardes_mouse_button_e, at cell: GridPoint, mods: UInt32 = 0) {
- pardes_mouse(button, PARDES_MOUSE_PRESS, cell.col, cell.row, mods)
+ sendMouse(button, PARDES_MOUSE_PRESS, cell.col, cell.row, mods)
fed()
}
func release(_ button: pardes_mouse_button_e, at cell: GridPoint, mods: UInt32 = 0) {
- pardes_mouse(button, PARDES_MOUSE_RELEASE, cell.col, cell.row, mods)
+ sendMouse(button, PARDES_MOUSE_RELEASE, cell.col, cell.row, mods)
fed()
}
func drag(_ button: pardes_mouse_button_e, to cell: GridPoint, mods: UInt32 = 0) {
- pardes_mouse(button, PARDES_MOUSE_DRAG, cell.col, cell.row, mods)
+ sendMouse(button, PARDES_MOUSE_DRAG, cell.col, cell.row, mods)
fed()
}
func motion(to cell: GridPoint, mods: UInt32 = 0) {
- pardes_mouse(PARDES_MOUSE_NONE, PARDES_MOUSE_MOTION, cell.col, cell.row, mods)
+ sendMouse(PARDES_MOUSE_NONE, PARDES_MOUSE_MOTION, cell.col, cell.row, mods)
fed()
}
@@ -1553,7 +1667,7 @@ final class PardesView: NSView {
/// One discrete wheel notch, as opposed to the continuous travel below.
func wheel(_ button: pardes_mouse_button_e, at cell: GridPoint, mods: UInt32 = 0) {
- pardes_mouse(button, PARDES_MOUSE_PRESS, cell.col, cell.row, mods)
+ sendMouse(button, PARDES_MOUSE_PRESS, cell.col, cell.row, mods)
fed()
}
@@ -1721,7 +1835,7 @@ final class PardesView: NSView {
// before one pump notification so no frame can observe a held button
// whose pointer is already outside the displayed scene.
if let last = latchedCell {
- pardes_mouse(button, PARDES_MOUSE_RELEASE, last.col, last.row,
+ sendMouse(button, PARDES_MOUSE_RELEASE, last.col, last.row,
modifiers(event.modifierFlags))
}
latchedCell = nil
@@ -1891,6 +2005,7 @@ final class PardesView: NSView {
} else {
sampled = point
}
+ sampledBodyPointer = sampled
let row = min(max(Int(sampled.y / cellHeight), 0), rows - 1)
// The column comes from the core, because a tag row's glyphs step at
// the tagline face's narrower pitch and a body-pitch click drifts one