158 lines
5.8 KiB
C
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;
|
|
}
|