summaryrefslogtreecommitdiff
path: root/src/macos/pardes.h
blob: 29f8a37dc10fe6036685124a62d9e719dbe066a9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
// libpardes — the C ABI the macOS app links against.
//
// Hand-written, and kept honest by a test: build.zig translate-C's this file
// into the unit-test build, and src/macos.zig asserts every constant and
// struct layout below against the Zig side (ghostty's trick — see docs/macos.md).
// Change nothing here without running `zig build unit-test -Dplatform=macos`.
//
// Division of labour, which is the whole design in two lines:
//   Zig  owns the core, the ptys, every effect, and the worker threads.
//   Swift owns NSApplication, the window, input translation, and drawing.
//
// There is exactly one core per process, so there are no handles: the state is
// a file-scoped singleton, same as src/web.zig. Every function below must be
// called from the main thread. The one exception is the `wakeup` callback,
// which fires from a pty reader thread.

#ifndef PARDES_H
#define PARDES_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

// ---------------------------------------------------------------- frame

// One rendered cell. Mirrors pardes.Cell (src/pardes.zig) flattened for the
// wire; the identical encoding is spelled a second time for the browser in
// src/web.zig as WebCell.
typedef struct {
  // UTF-8 grapheme, `len` bytes. The core caps graphemes at seven bytes; the
  // eighth is always zero so a debugger prints something sensible.
  uint8_t text[8];
  // 0x01000000            terminal default
  // 0x02000000 | index    256-color palette entry
  // 0x00RRGGBB            direct color
  uint32_t fg;
  uint32_t bg;
  // bit 0 bold, 1 dim, 2 italic, 3 blink, 4 reverse, 5 invisible,
  // 6 strikethrough; bits 8.. hold PARDES_UL_*.
  uint16_t attrs;
  uint8_t len;
  // bit 0: the core never painted this cell — draw it as the default cell
  // (background only), which is what makes a partial redraw cheap.
  // bit 1: rasterize text and its colour band with the tagline face's measured
  // height. Logical cell geometry stays body-sized; this is a visual role.
  uint8_t flags;
} pardes_cell_s;

#define PARDES_COLOR_DEFAULT 0x01000000u
#define PARDES_COLOR_INDEXED 0x02000000u
#define PARDES_COLOR_TAG_MASK 0xff000000u
#define PARDES_COLOR_RGB_MASK 0x00ffffffu

#define PARDES_ATTR_BOLD 0x0001u
#define PARDES_ATTR_DIM 0x0002u
#define PARDES_ATTR_ITALIC 0x0004u
#define PARDES_ATTR_BLINK 0x0008u
#define PARDES_ATTR_REVERSE 0x0010u
#define PARDES_ATTR_INVISIBLE 0x0020u
#define PARDES_ATTR_STRIKETHROUGH 0x0040u
#define PARDES_ATTR_UL_SHIFT 8
// Literals rather than (1u << n): Swift's macro importer is dependable on a
// plain integer and less so on an expression, and nothing here can compile the
// Swift side to find out. The ABI guard asserts each value against the encoder.

#define PARDES_UL_OFF 0
#define PARDES_UL_SINGLE 1
#define PARDES_UL_DOUBLE 2
#define PARDES_UL_CURLY 3
#define PARDES_UL_DOTTED 4
#define PARDES_UL_DASHED 5

#define PARDES_CELL_DEFAULT 0x01u
#define PARDES_CELL_TAGLINE 0x02u
// The look-hover affordance: the word under the pointer is a real Look or
// Exec operand. Hosts that draw the affordance as more than the quiet tint
// (macOS composes a liquid-glass overlay) read this instead of guessing
// from the background colour, which a real selection shares.
#define PARDES_CELL_HOVER 0x04u

// One coherent snapshot for the full-window shader pass (CRT);
// time/frame advance only on pardes_animation_tick's display clock.
typedef struct {
  uint32_t flags;
  float time_seconds;
  uint32_t frame;
} pardes_scene_s;

#define PARDES_SCENE_CRT 0x01u

// One backend-neutral pane transition. Boxes are logical cell rectangles;
// the native shader converts them to backing pixels using the same measured
// cell as the renderer. The list is already in paint order: moving first,
// opening next, frozen closing tombstones last, and slot order within a phase.
typedef struct {
  float x;
  float y;
  float w;
  float h;
} pardes_panel_box_s;

typedef struct {
  uint32_t serial;
  uint8_t pane;
  uint8_t phase;
  uint8_t effect;
  uint8_t reserved0;
  uint16_t frame;
  uint16_t frame_count;
  pardes_panel_box_s from;
  pardes_panel_box_s to;
} pardes_panel_track_s;

#define PARDES_PANEL_OPENING 0u
#define PARDES_PANEL_MOVING 1u
#define PARDES_PANEL_CLOSING 2u

#define PARDES_PANEL_OFF 0u
#define PARDES_PANEL_SLIDE 1u
#define PARDES_PANEL_ZOOM 2u
#define PARDES_PANEL_DISSOLVE 3u
#define PARDES_PANEL_ASCII 4u
#define PARDES_PANEL_VERTICAL 5u
// Character effects composed in the core: the host receives finished glyphs
// and only clips the panel batch for them.
#define PARDES_PANEL_EDGES 6u
#define PARDES_PANEL_FALL 7u
#define PARDES_PANEL_WAVE 8u
#define PARDES_PANEL_CURTAIN 9u
#define PARDES_PANEL_SCRAMBLE 10u
#define PARDES_PANEL_TYPEWRITER 11u

// ---------------------------------------------------------------- input

// Modifier bitmask shared by key and mouse events.
#define PARDES_MOD_CTRL 0x0001u
#define PARDES_MOD_ALT 0x0002u
#define PARDES_MOD_SHIFT 0x0004u

// Keys that carry no text. The four editing keys are their ASCII controls;
// everything else lives in a private-use plane so it can never collide with a
// real codepoint arriving as text.
#define PARDES_KEY_ENTER 0x0Du
#define PARDES_KEY_ESCAPE 0x1Bu
#define PARDES_KEY_TAB 0x09u
#define PARDES_KEY_BACKSPACE 0x7Fu
#define PARDES_KEY_UP 0xF0001u
#define PARDES_KEY_DOWN 0xF0002u
#define PARDES_KEY_LEFT 0xF0003u
#define PARDES_KEY_RIGHT 0xF0004u
#define PARDES_KEY_HOME 0xF0005u
#define PARDES_KEY_END 0xF0006u
#define PARDES_KEY_PAGE_UP 0xF0007u
#define PARDES_KEY_PAGE_DOWN 0xF0008u
#define PARDES_KEY_DELETE 0xF0009u

// acme's three buttons carry the whole vocabulary: 1 selects, 2 executes,
// 3 looks. Wheel buttons are ordinary buttons, not a separate axis.
typedef enum {
  PARDES_MOUSE_LEFT = 0,
  PARDES_MOUSE_MIDDLE = 1,
  PARDES_MOUSE_RIGHT = 2,
  PARDES_MOUSE_WHEEL_UP = 3,
  PARDES_MOUSE_WHEEL_DOWN = 4,
  PARDES_MOUSE_WHEEL_LEFT = 5,
  PARDES_MOUSE_WHEEL_RIGHT = 6,
  PARDES_MOUSE_NONE = 7,
  PARDES_MOUSE_BACK = 8,
  PARDES_MOUSE_FORWARD = 9,
} pardes_mouse_button_e;

typedef enum {
  PARDES_MOUSE_PRESS = 0,
  PARDES_MOUSE_RELEASE = 1,
  PARDES_MOUSE_MOTION = 2,
  PARDES_MOUSE_DRAG = 3,
} pardes_mouse_kind_e;

// What the core just did, for a shell that can answer with something the hand
// feels. Taken with pardes_take_haptic once per pump; the two acme verbs are
// distinguished because they deserve distinct taps.
typedef enum {
  PARDES_HAPTIC_NONE = 0,
  PARDES_HAPTIC_EXEC = 1,
  PARDES_HAPTIC_LOOK = 2,
} pardes_haptic_e;

// ---------------------------------------------------------------- runtime

// What the host lends the core. Three callbacks, because everything else the
// core wants done it already does itself: it owns the ptys, and it opens URLs
// through /usr/bin/open. Copied by value during pardes_init, so the struct
// need only outlive that call.
typedef struct {
  // Passed back to every callback below. Conventionally the AppDelegate.
  void *userdata;

  // "Your state moved; please pump me." Called from a pty reader thread, so
  // the host must hop to the main thread before calling pardes_tick — see
  // AppDelegate.wakeup, which does exactly one DispatchQueue.main.async.
  void (*wakeup)(void *userdata);

  // The yank register changed. `text` is borrowed for the duration of the
  // call only. Main thread, inside pardes_tick.
  void (*set_clipboard)(void *userdata, const char *text, size_t len);

  // Ask the host for the system clipboard. The host answers by calling
  // pardes_paste, which may be synchronous inside this call. Main thread,
  // inside pardes_tick.
  void (*read_clipboard)(void *userdata);
} pardes_runtime_s;

// ---------------------------------------------------------------- lifecycle

// Returns 0 on success, or a nonzero opaque error code. Pass the real window
// grid, not a placeholder: init delivers it as the first resize EVENT (the
// core defers each shell's greeting until it has seen one), and the first
// forkpty takes its winsize straight off the core — boot at 80x24 and the
// shell draws its first prompt to a width the window never had.
int pardes_init(const pardes_runtime_s *runtime, uint16_t cols, uint16_t rows);
void pardes_deinit(void);

// Drain pty output into the core and perform the effects it queued. Call after
// every input function and on every wakeup.
//
// The return value is whether this tick did any IO — bytes arrived from a pty,
// or an effect was performed. It is NOT a repaint signal, and a host that uses
// it as one shows a stale screen: moving the cursor, extending a selection,
// changing mode and scrolling all mutate the grid while queueing nothing and
// performing nothing, so they tick false. Mark the view dirty after any call
// into the core and use this only to decide whether there was work.
bool pardes_tick(void);

// Advance one frame-count animation step. Call only from the scheduled ~60 Hz
// callback, never from ordinary input or pty pumps: those drain work but do
// not represent elapsed display time. Returns whether anything advanced.
bool pardes_animation_tick(void);

// The core asked to exit (the Exit builtin, or the last pane closing).
bool pardes_should_quit(void);

// A theme/pane transition, scene effect, or rotation coast wants ~60 Hz ticks.
bool pardes_animating(void);

// What to paint where the grid does not: the window background behind the
// titlebar and behind a live resize the view has not caught up with. The
// theme's own background, so it changes the instant the theme does — chrome
// (taglines, the move box) fades instead, which is why this is not it.
//
// PARDES_COLOR_DEFAULT means the theme declares NO background of its own. A
// terminal wears whatever it was already wearing; a window has nothing to
// wear, so the host should go transparent and show its own backdrop.
uint32_t pardes_theme_bg(void);

// The percentage the host must paint window BACKGROUNDS at: the ground behind
// the grid, cell and band backgrounds, and the chrome rules over them. Glyph
// ink and the block cursor are never scaled by it — see shaders/ui.frag.glsl,
// which states the same rule for the SDL shell.
//
// Read it once per pump beside pardes_theme_bg(): reading also acknowledges
// whatever `WindowOpacity` asked for. 100 is opaque and is the default.
uint8_t pardes_window_opacity(void);
// Backdrop material strength, 0..100; zero disables blur. Not a pixel radius.
uint8_t pardes_window_blur(void);

// Pane taglines use this percentage of the current body font size. It is the
// embedded default before init and the live core setting afterwards; the host
// polls it and rebuilds only tagline glyph/metric state.
uint8_t pardes_gui_tagline_font_percent(void);

// Where a reduced-height tagline band sits inside its body-sized grid row, in
// PHYSICAL PIXELS down from the row's top, and the rule joining the topbar band
// to the first pane-tag band. The core owns this geometry so that this shell and
// the SDL one cannot disagree about it; centring every band is what this shell
// used to do, and it left a strip of window background between the two.
//
// Points in, pixels out: multiply by the backing scale before calling and divide
// the answer back, the same snapping the cell metrics already use.
uint32_t pardes_tagline_band_offset(uint16_t row, float canvas_h, uint32_t cell_h,
                                   uint32_t tagline_h);
uint32_t pardes_topbar_pane_border_px(uint32_t cell_h, uint32_t tagline_h);

// The column a compact tagline band anchors at: the pane's left edge, so a tag
// row advances on the tagline face's own narrower pitch instead of centring a
// smaller glyph inside every body-width cell. CELLS, not pixels — the host
// knows both widths — and fractional, because an animating panel's origin is.
//
// glyph_x = origin * body_cell_w + (col - origin) * tagline_cell_w
float pardes_tagline_origin_col(uint16_t col, uint16_t row);

// The inverse, for the pointer: which grid column `x` falls in on `row`, given
// that a tag row's glyphs step at the narrower pitch. Compacting the text
// without compacting this makes a click drift one word further right for every
// word along the row. `x` and both widths must share a unit; only the ratio is
// read, so a host measuring in points passes points.
uint16_t pardes_grid_col_at(float x, uint16_t row, float body_w, float tagline_w);

// The tag band's own background, the base a compact tag row is painted on: the
// pane-wide band goes down in THIS colour on the body grid, then each cell's
// own background on the narrower grid its glyph uses. One pass would put a
// highlighted word's box on body pitch and its letters on tagline pitch.
// PARDES_COLOR_DEFAULT before there is a session to ask.
uint32_t pardes_tagline_bg(void);

// The fallback font PREFERENCE ORDER, shared with the SDL shell. Only the order
// travels: resolving a name is the host's business, because SDL matches font
// FILE STEMS while walking the font directories and CoreText matches PostScript
// and family names, which for one face are routinely different strings.
//
// `pardes_fallback_font_name` returns a borrowed, NOT NUL-terminated pointer
// and writes its byte length through `len`; NULL past the end.
uint32_t pardes_fallback_font_count(void);
const char *pardes_fallback_font_name(uint32_t index, uint32_t *len);

// Colour of that rule: a compiled override, else the theme's scrollbar track.
// PARDES_COLOR_DEFAULT before there is a session to ask — do not draw it then.
uint32_t pardes_topbar_pane_border_rgb(void);

// Rows the global topbar occupies, and therefore the row index of the FIRST
// pane tagline — the one the rule above joins the topbar band to. Mirrors
// pardes.TOPBAR_H, and src/macos.zig asserts the two agree.
#define PARDES_TOPBAR_H 1u

// The active pane's tag row background, for hosts that afford the active
// tagline more than a tint — macOS raises it like an old-school bevel.
uint32_t pardes_tag_active_bg(void);

// Persistent full-window effect switches and their display-clock time. A zero
// flags word means draw the CoreText frame directly; any nonzero combination
// is one postprocess pass. Snapshot by value, with no borrowed storage.
pardes_scene_s pardes_scene(void);

// The shared Metal/Core Image pass is unavailable for this session. Clears
// scene effects and future panel transitions so Config reflects what the host
// can actually present and the display clock cannot retry forever.
void pardes_postprocessor_unavailable(void);

// A transient postprocess failure will be presented as the canonical grid.
// Drop current panel tracks so a later scene retry cannot resume them midway.
void pardes_panel_animation_failed(void);

// ---------------------------------------------------------------- events in

// `cp` is a codepoint or one of PARDES_KEY_*; `text`/`len` are the host's
// already-composed characters for printable keys and empty for functional
// ones. `text` is borrowed for the call.
void pardes_key(uint32_t cp, const char *text, size_t len, uint32_t mods);
void pardes_paste(const char *text, size_t len);
void pardes_mouse(pardes_mouse_button_e button, pardes_mouse_kind_e kind,
                  uint16_t col, uint16_t row, uint32_t mods);
// The pointer left the view. A clamped motion on its final cell is not the
// same event: it would keep hover previews and resize hints alive off-window.
void pardes_mouse_pixel(pardes_mouse_button_e button, pardes_mouse_kind_e kind,
    uint16_t col, uint16_t row, uint32_t mods, float x, float y,
    float body_w, float body_h, float tagline_w, float tagline_h);
void pardes_row_metrics(uint16_t body_w, uint16_t body_h, uint16_t tagline_w, uint16_t tagline_h);
uint32_t pardes_tag_text_inset(void);
uint32_t pardes_grip_columns(void);
uint32_t pardes_tag_layer_limit(void);
// Tag layer fields 11 and 12 carry tag-bottom placement and chrome border RGB.
uint32_t pardes_tag_layer_value(uint32_t index, uint32_t field);
const pardes_cell_s *pardes_tag_layer_cells(uint32_t index);
uint32_t pardes_body_layer_limit(void);
uint32_t pardes_body_layer_value(uint32_t index, uint32_t field);
const pardes_cell_s *pardes_body_layer_cells(uint32_t index);
void pardes_pointer_leave(void);

// Host file watchers call this after their debounce interval. `pane` is the
// opaque watch slot (including pardes' private ThemeFile slot), and `generation`
// makes events queued before a replacement inert. The callback only schedules
// the ordinary main-thread pump; text, PDF, and theme IO happen in pardes_tick.
void pardes_watch_changed(uint8_t pane, uint32_t generation);

// Trackpad/precision wheel distance in CELLS, sign following the grid
// (positive scrolls down and right). The core has no fractional scroll — it
// moves a row or a column at a time — so libpardes accumulates here and emits
// whole wheel presses, keeping the remainder. Both other shells do this same
// accumulation host-side (stepScroll in gui.zig, the drain loop in
// web/app.mjs); it lives in Zig here so the Swift side stays a translator, and
// so the quantizer is unit-tested on a machine with no trackpad. A discrete
// wheel notch should go through pardes_mouse instead.
void pardes_scroll(float delta_rows, float delta_cols, uint16_t col,
                   uint16_t row);

// The gesture boundaries around pardes_scroll, and the whole of scroll
// momentum. `begin` is fingers down: it catches a coast the way a hand catches
// a spinning dial, and drops whatever the last gesture left banked. `end` is
// fingers up: a swipe still moving when it ended becomes a fling, in
// proportion to how hard it was thrown, on the same curve pardes_rotate_end
// uses — momentum ramps up from zero rather than switching on at a threshold.
//
// A coast makes pardes_animating true and is spent by pardes_animation_tick,
// and it stops at the end of a document instead of spinning against the edge.
//
// A host whose windowing system already flings for it needs no suppression:
// the system's momentum arrives as ordinary pardes_scroll calls, every one of
// which cancels the coast before spending its own travel, so that momentum
// simply takes the gesture over. A host that calls neither — a discrete wheel
// has no phases — scrolls with no momentum at all.
void pardes_scroll_begin(void);
void pardes_scroll_end(void);

// A two-finger trackpad rotation, in degrees since the last call, positive
// counterclockwise (AppKit's sign, unchanged). The core has no rotation: this
// is spent as the search-step keys, clockwise `n` and counterclockwise `N`, a
// notch at a time with the remainder kept — the same accumulate-and-spend
// shape as pardes_scroll, and in Zig for the same reason. Feed it the raw
// per-event delta; pass 0 at gesture start to drop a stale remainder and to
// stop a fling still coasting.
void pardes_rotate(float degrees);

// The fingers lifted. How fast they were moving decides everything: a slow
// twist stops exactly where it was put, a flick keeps turning in proportion to
// how hard it was thrown, and the two are the same curve — momentum ramps up
// from zero rather than switching on at a threshold.
//
// A coast makes pardes_animating true and is spent by
// pardes_animation_tick, so a host that already re-pumps for theme transitions
// needs no new machinery; one that never calls this simply has a dial with no
// momentum.
void pardes_rotate_end(void);

// Run one builtin command line, exactly as executing the same text in a tag
// would. This is the core's own `command` event, which is how a nested pardes
// talks to its host; here it is what a menu item is made of, and what opens
// the file named on argv or dropped on the Dock icon (`Look <path>`).
void pardes_command(const char *text, size_t len);

// `cell_w`/`cell_h` are one cell in physical pixels, which only the native PDF
// placement path reads. Pass the backing-store size, not points.
void pardes_resize(uint16_t cols, uint16_t rows, uint16_t cell_w,
                   uint16_t cell_h);

// ---------------------------------------------------------------- frame out

// Render one frame. Returns the cell count (cols * rows), or 0 on failure.
// The buffer returned by pardes_frame_cells is valid until the next call.
uint32_t pardes_frame(void);
const pardes_cell_s *pardes_frame_cells(void);
// Frozen old grid and its byte-per-cell semantic change mask. Both are either
// present for exactly cols*rows cells or NULL together, and are borrowed until
// the next pardes_frame call just like the canonical cells.
const pardes_cell_s *pardes_frame_previous_cells(void);
const uint8_t *pardes_frame_changed_cells(void);
uint16_t pardes_frame_cols(void);
uint16_t pardes_frame_rows(void);

// One rasterized pixel attachment: a PDF page, or an image pane's pixels.
//
// Geometry is in PHYSICAL PIXELS, the space pardes_resize's cell_w/cell_h put
// the core in. `cell_x`/`cell_y` are the pane body's origin in CELLS and the
// only thing to multiply out; `dst_*` is relative to that origin and `src_*`
// is the crop of the raster to take. Both are already clipped to the viewport,
// so a continuous-scroll page needs no overflow clip of its own — but the body
// (`cell_w` x `cell_h` cells) is still the rectangle nothing may paint past.
//
// `serial`, `page` and `revision` together are the cache key: a host holds its
// decoded texture while all three hold still, and panning, fit and scrolling
// deliberately do not move them.
typedef struct {
  uint32_t serial;
  uint32_t page;
  uint32_t revision;
  uint16_t cell_x;
  uint16_t cell_y;
  uint16_t cell_w;
  uint16_t cell_h;
  uint32_t dst_x;
  uint32_t dst_y;
  uint32_t dst_w;
  uint32_t dst_h;
  uint32_t src_x;
  uint32_t src_y;
  uint32_t src_w;
  uint32_t src_h;
  // subpixel vertical displacement a proportional wheel kept
  float offset_y;
  uint32_t iw;
  uint32_t ih;
  // iw * ih * 4 bytes, RGBA8, borrowed until the next pardes_frame
  const uint8_t *rgba;
  // PDF paper RGB; PARDES_COLOR_DEFAULT means an ordinary image.
  uint32_t paper_bg;
} pardes_image_s;

// This frame's attachments, in paint order. Ask after pardes_frame; both are
// valid until the next one, exactly like the cell buffer.
uint32_t pardes_frame_images(void);
const pardes_image_s *pardes_frame_image_list(void);

// Shader transition records for this frame, borrowed until pardes_frame is
// called again. Empty means draw the canonical grid without a panel pass.
uint32_t pardes_frame_panel_tracks(void);
const pardes_panel_track_s *pardes_frame_panel_track_list(void);
// Call only after the host has put this frame on screen. `true` commits the
// borrowed panel-track list above; `false` records a direct canonical fallback.
// Returns true only when remapping a stationary pointer newly started a clock
// the host was not already running.
bool pardes_frame_presented(bool animated_panels);

// The file behind the FOCUSED pane, or NULL when there is none: a terminal, an
// output buffer, or nothing focused. PDFs and images count — they are real
// paths, and a titlebar proxy icon is about the file, not about who may edit
// it. Borrowed until the next call, like pardes_font_take.
const char *pardes_active_path(void);

// ...and whether that pane holds edits which are not on disk. Always false for
// anything with no buffer to save, PDFs and images included.
bool pardes_active_dirty(void);

// -1 when the cursor is hidden. `bar` asks for a thin insert-mode caret.
int32_t pardes_cursor_x(void);
int32_t pardes_cursor_y(void);
bool pardes_cursor_bar(void);
// Mouse pointer affordance: 0 = default text pointer, 1 = link hand,
// 2 = a validated Look/Exec target under the pointer (the acme arrow).
uint32_t pardes_pointer_shape(void);

// The Look or Exec the core performed since this was last asked, and clears
// it. Call it once per pump, after pardes_tick — the input functions run the
// dispatch synchronously, so a gesture's pulse is already waiting by the time
// its tick returns. PARDES_HAPTIC_NONE means nothing to feel.
pardes_haptic_e pardes_take_haptic(void);

// The font file the `Font` builtin asked for since this was last called, and
// clears it; NULL when nothing was asked. Ask once per pump, beside the haptic
// above. The string is a NUL-terminated absolute path owned by libpardes and
// valid until the next call.
//
// A path rather than a family name: the core found the file by walking the
// font directories itself, so both shells agree on which faces exist and
// neither has to ask its platform to resolve a name it might resolve
// differently. A `.ttc` collection names its first face, which is the cut the
// file is named after.
//
// The host loads it, re-measures its cell, and reports the new grid through
// pardes_resize. A file the host cannot load must be rejected explicitly while
// keeping the face that works; otherwise Config would leave the request pending
// forever or claim a fallback was accepted.
// Optional size in native points; zero preserves the current size.
const char *pardes_font_take(uint16_t *size_hundredths);

// Boot, zoom and display-scale changes observe the face already on screen;
// this never consumes a pending request. `point_hundredths` is the effective
// body size (14pt => 1400).
bool pardes_font_observe(const char *effective_name, size_t len,
                         uint16_t point_hundredths);

// A taken Font request is not effective until the host says what CoreText
// actually accepted. Calling this without a request from pardes_font_take is
// inert, so an unrelated host report cannot consume the request.
bool pardes_font_ack(const char *effective_name, size_t len,
                     uint16_t point_hundredths);
void pardes_font_reject(void);

#ifdef __cplusplus
}
#endif

#endif // PARDES_H