65816-llvm-mos/runtime/src/cursor.c
Scott Duensing da095402ec Updated
2026-06-02 23:17:57 -05:00

158 lines
5.8 KiB
C

// cursor.c - iigs/cursor.h implementation. Push/pop stack of
// CursorRecord COPIES so transient cursor swaps (busy, I-beam, etc.)
// can be installed and restored without the caller juggling pointers
// into Memory-Manager-relocatable storage.
//
// Phase 2.5 (2026-06-01) scope: thin wrappers + Wait/IBeam ROM shapes.
// Embedded cursor blobs are NOT in scope; callers wanting a custom
// cursor should construct their own ORCA-shape Cursor record and pass
// it to SetCursor() directly.
//
// The save stack stores 128-byte COPIES (not pointers) - the largest
// standard IIgs cursor is 16x16 (ROM arrow): 4-byte header + 32 bytes
// data + 32 bytes mask + 4 bytes hotspot = 72 bytes. 128 is generous.
// Copying the whole record is mandatory: toolset-owned cursors live in
// MM-relocatable handles and the live pointer can move out from under
// us between push and pop if the heap compacts.
#include "iigs/cursor.h"
#include "iigs/toolbox.h"
// Size of one save slot. Covers the full 16x16 ROM-style cursor with
// headroom for slightly larger custom records (24x16 etc). Pushes of
// cursors larger than this truncate the copy and return success - the
// pop will then restore a partial record which still has valid header
// + data + mask but a (possibly garbage) hotspot. Document this
// limitation in the header if a larger cursor ever ships.
#define CURSOR_COPY_BYTES 128
// CursorRecord prefix layout (per ORCA quickdraw.h:112-118):
// Word cursorHeight - size in BYTES (not pixels)
// Word cursorWidth - enclosing rectangle width in WORDS
// Word cursorData[] - cursorHeight/2 words of bitmap
// Word cursorMask[] - cursorHeight/2 words of mask
// Point cursorHotSpot - 4 bytes (h, v)
// So total = 4 (header) + 2*cursorHeight (data+mask) + 4 (hotspot).
// We compute the live record size from the header so partial copies
// don't drag in trailing slop from neighboring Memory Mgr blocks.
#define CURSOR_HEADER_BYTES 4
#define CURSOR_HOTSPOT_BYTES 4
static unsigned char gCursorStack[IIGS_CURSOR_STACK_DEPTH][CURSOR_COPY_BYTES];
static unsigned short gCursorStackBytes[IIGS_CURSOR_STACK_DEPTH];
static unsigned short gCursorStackDepth = 0;
// Application-registered "default" cursor. Pop returns to this when
// the save stack underflows; that way a mismatched push/pop pair still
// lands the user in a known cursor instead of leaking ROM state.
static const IigsCursorT *gRegisteredCursor = (const IigsCursorT *)0;
// Compute the byte length of a live CursorRecord from its header.
// Returns 0 if the pointer is NULL. Clamps to CURSOR_COPY_BYTES so
// the memcpy below never overruns the save slot.
static unsigned short cursorRecordBytes(const void *p) {
if (!p) {
return 0;
}
const unsigned short *w = (const unsigned short *)p;
// cursorHeight is in bytes; data + mask occupy 2*cursorHeight.
unsigned short height = w[0];
unsigned short total = (unsigned short)(CURSOR_HEADER_BYTES + 2U * height + CURSOR_HOTSPOT_BYTES);
if (total > CURSOR_COPY_BYTES) {
total = CURSOR_COPY_BYTES;
}
return total;
}
// Save the currently-active cursor into the next stack slot. Returns
// 0 on success, nonzero on stack overflow or NULL live cursor (which
// means InitCursor() never ran - the InitCursor invariant from the
// header).
static uint16_t pushCurrent(void) {
if (gCursorStackDepth >= IIGS_CURSOR_STACK_DEPTH) {
return 1;
}
void *live = GetCursorAdr();
if (!live) {
// Cursor Mgr never initialized. Hard-error per the
// InitCursor invariant - SetCursor on a NULL save buffer
// would walk through 0 in ROM.
return 2;
}
unsigned short n = cursorRecordBytes(live);
unsigned char *dst = gCursorStack[gCursorStackDepth];
const unsigned char *src = (const unsigned char *)live;
for (unsigned short i = 0; i < n; i++) {
dst[i] = src[i];
}
gCursorStackBytes[gCursorStackDepth] = n;
gCursorStackDepth++;
return 0;
}
uint16_t iigsCursorPushArrow(void) {
uint16_t rc = pushCurrent();
if (rc != 0) {
return rc;
}
// InitCursor reinstalls the ROM arrow shape without reallocating
// the Cursor Mgr save buffer (idempotent after first call from
// startdesk()). Same effect as SetCursor(romArrow) without us
// having to fish the arrow's address out of toolset internals.
InitCursor();
return 0;
}
uint16_t iigsCursorPushBusy(void) {
uint16_t rc = pushCurrent();
if (rc != 0) {
return rc;
}
// WaitCursor (QDAuxiliary 0x0A12) installs the ROM wristwatch
// cursor. Internally calls SetCursor on the ROM busy shape.
WaitCursor();
return 0;
}
uint16_t iigsCursorPop(void) {
if (gCursorStackDepth == 0) {
// Underflow: try the registered fallback so a stray Pop
// doesn't leave us with whatever transient cursor happens to
// be live. If the application never called Register either,
// hard-error so the caller notices the mismatch.
if (gRegisteredCursor) {
SetCursor((void *)gRegisteredCursor);
return 0;
}
return 1;
}
gCursorStackDepth--;
// Install our saved COPY directly. SetCursor reads the record by
// pointer and copies bytes into the Cursor Mgr's working area; it
// does NOT retain our pointer past the call, so it's safe to hand
// it a pointer into our gCursorStack[].
SetCursor(gCursorStack[gCursorStackDepth]);
return 0;
}
uint16_t iigsCursorRegister(const IigsCursorT *cursor) {
gRegisteredCursor = cursor;
if (cursor) {
if (!GetCursorAdr()) {
// InitCursor invariant: refuse to install before the
// Cursor Mgr has been brought up. Keep the pointer
// registered for a later (post-InitCursor) Pop fallback.
return 2;
}
SetCursor((void *)cursor);
}
return 0;
}