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

338 lines
8.4 KiB
C

// minicad.c - faithful port of ORCA-C's MiniCAD.cc sample.
//
// Mike Westerfield, Byte Works 1989. Original at
// tools/orca-c/C.Samples/Desktop.Samples/MiniCAD.cc.
//
// A simple multi-window CAD: File>New opens a drawing window (up to
// 4), click+drag inside a window's content rubber-bands a line,
// release commits it. File>Close closes the front window. Each
// window's lines are remembered so the WM can repaint on update.
//
// Phase 4.1 migration: menu mini-format strings, AlertTemplate, and
// NewWindowParm folded into iigs/uiBuilder.h.
#include "iigs/toolbox.h"
#include "iigs/desktop.h"
#include "iigs/eventLoop.h"
#include "iigs/uiBuilder.h"
#define apple_About 257
#define file_Quit 256
#define file_New 258
#define file_Close 255
#define wInGoAway 17
#define wInContent 19
#define mUpMask 0x0002
#define modeCopy 0
#define modeXOR 2
#define maxWindows 4
#define maxLines 50
typedef struct { short v, h; } Point;
typedef struct { Point p1, p2; } LineRec;
typedef struct {
unsigned short wmWhat;
unsigned long wmMessage;
unsigned long wmWhen;
short wmWhereV, wmWhereH;
unsigned short wmModifiers;
} EventRec;
typedef struct {
void *wPtr;
unsigned char *name;
unsigned short numLines;
LineRec lines[maxLines];
} WindowRecord;
// --- alphabetised forward decls -----------------------------------
static void doClose(void);
static void doNew(void);
static void drawWindow(void);
static void onAbout(uint16_t cmdId);
static void onCloseMenu(uint16_t cmdId);
static void onMenu(uint16_t menuId, uint16_t itemId);
static void onNew(uint16_t cmdId);
static void onQuit(uint16_t cmdId);
static void sketch(const IigsEventT *ev);
static void onNew(uint16_t cmdId) {
(void)cmdId;
doNew();
}
static void onCloseMenu(uint16_t cmdId) {
(void)cmdId;
doClose();
}
static const UiCmdHandlerT gCmdTable[] = {
{ apple_About, onAbout },
{ file_Quit, onQuit },
{ file_New, onNew },
{ file_Close, onCloseMenu },
};
static const UiMenuItemT gEditItems[] = {
{ 250, "Undo", 'Z', MI_CHECKED },
{ 251, "Cut", 'X', 0 },
{ 252, "Copy", 'C', 0 },
{ 253, "Paste", 'V', 0 },
{ 254, "Clear", 0, 0 },
};
static const UiMenuItemT gFileItems[] = {
{ 258, "New", 'N', 0 },
{ 255, "Close", 0, MI_CHECKED },
{ 256, "Quit", 'Q', 0 },
};
static const UiMenuItemT gAppleItems[] = {
{ 257, "About...", 0, MI_CHECKED },
};
static const UiMenuT gMenus[] = {
{ 1, "Apple", MN_APPLE, 1, gAppleItems },
{ 2, " File ", 0, 3, gFileItems },
{ 3, " Edit ", 0, 5, gEditItems },
};
static unsigned char gTitle0[] = "\x07Paint 1";
static unsigned char gTitle1[] = "\x07Paint 2";
static unsigned char gTitle2[] = "\x07Paint 3";
static unsigned char gTitle3[] = "\x07Paint 4";
static WindowRecord gWindows[maxWindows] = {
{ (void *)0, gTitle0, 0, { { {0,0}, {0,0} } } },
{ (void *)0, gTitle1, 0, { { {0,0}, {0,0} } } },
{ (void *)0, gTitle2, 0, { { {0,0}, {0,0} } } },
{ (void *)0, gTitle3, 0, { { {0,0}, {0,0} } } }
};
// Window-content def-proc. Called by the WM with our bank set up
// (Loader sets DBR via JSL). Uses GetWRefCon to identify which
// gWindows[] entry to redraw.
static void drawWindow(void) {
unsigned long refcon = (unsigned long)GetWRefCon(GetPort());
unsigned short i = (unsigned short)refcon;
if (i >= maxWindows) {
return;
}
WindowRecord *wp = &gWindows[i];
if (wp->numLines == 0) {
return;
}
SetPenMode(modeCopy);
SetSolidPenPat(0);
SetPenSize(2, 1);
for (unsigned short j = 0; j < wp->numLines; j++) {
LineRec *lp = &wp->lines[j];
MoveTo(lp->p1.h, lp->p1.v);
LineTo(lp->p2.h, lp->p2.v);
}
}
static void doNew(void) {
unsigned short i = 0;
while (i < maxWindows && gWindows[i].wPtr != (void *)0) {
i++;
}
if (i >= maxWindows) {
return;
}
gWindows[i].numLines = 0;
// We pass a Pascal title directly via uiBuilderOpenWindow's
// contract... but uiBuilder takes a C string. Convert by skipping
// the pascal length byte and stuffing into a temporary.
char title[16];
unsigned short tn = gWindows[i].name[0];
if (tn > 14) {
tn = 14;
}
for (unsigned short k = 0; k < tn; k++) {
title[k] = (char)gWindows[i].name[k + 1];
}
title[tn] = '\0';
UiWindowT spec = {
title,
UW_STD_DOC_GZ,
{ (int16_t)(25 + i * 10), (int16_t)(10 + i * 10),
(int16_t)(180 + i * 10), (int16_t)(600 + i * 10) },
188, 615,
(uint32_t)i,
(void *)&drawWindow
};
gWindows[i].wPtr = uiBuilderOpenWindow(&spec);
if (i == maxWindows - 1) {
DisableMItem(file_New);
}
}
static void doClose(void) {
void *fw = FrontWindow();
if (!fw) {
return;
}
unsigned short i = (unsigned short)(unsigned long)GetWRefCon(fw);
if (i >= maxWindows) {
return;
}
CloseWindow(gWindows[i].wPtr);
gWindows[i].wPtr = (void *)0;
EnableMItem(file_New);
}
static void onAbout(uint16_t cmdId) {
(void)cmdId;
uiBuilderAlert(UA_NOTE,
"Mini-CAD 1.0\r"
"Copyright 1989\r"
"Byte Works, Inc.\r\r"
"By Mike Westerfield");
}
static volatile uint16_t gDone;
static void onQuit(uint16_t cmdId) {
(void)cmdId;
gDone = 1;
iigsEventLoopQuit();
}
static void onMenu(uint16_t menuId, uint16_t itemId) {
(void)menuId;
uiBuilderDispatch(itemId, gCmdTable, (uint16_t)(sizeof gCmdTable / sizeof gCmdTable[0]));
}
static void sketch(const IigsEventT *ev) {
void *fw = FrontWindow();
if (!fw) {
return;
}
unsigned short i = (unsigned short)(unsigned long)GetWRefCon(fw);
if (i >= maxWindows) {
return;
}
if (gWindows[i].numLines >= maxLines) {
uiBuilderAlert(UA_STOP,
"The window is full -\r"
"more lines cannot be\r"
"added.");
return;
}
StartDrawing(fw);
SetSolidPenPat(15);
SetPenSize(2, 1);
SetPenMode(modeXOR);
Point firstPt;
firstPt.h = ev->whereX;
firstPt.v = ev->whereY;
GlobalToLocal(&firstPt);
MoveTo(firstPt.h, firstPt.v);
LineTo(firstPt.h, firstPt.v);
Point endPt = firstPt;
EventRec evDrag;
while (!GetNextEvent(mUpMask, &evDrag)) {
Point cur;
cur.h = evDrag.wmWhereH;
cur.v = evDrag.wmWhereV;
GlobalToLocal(&cur);
if (cur.h != endPt.h || cur.v != endPt.v) {
MoveTo(firstPt.h, firstPt.v);
LineTo(endPt.h, endPt.v);
MoveTo(firstPt.h, firstPt.v);
LineTo(cur.h, cur.v);
endPt = cur;
}
}
// Erase final XOR line.
MoveTo(firstPt.h, firstPt.v);
LineTo(endPt.h, endPt.v);
if (firstPt.h != endPt.h || firstPt.v != endPt.v) {
unsigned short n = gWindows[i].numLines++;
gWindows[i].lines[n].p1 = firstPt;
gWindows[i].lines[n].p2 = endPt;
SetPenMode(modeCopy);
SetSolidPenPat(0);
MoveTo(firstPt.h, firstPt.v);
LineTo(endPt.h, endPt.v);
}
}
int main(void) {
unsigned short userId = startdesk(640);
(void)userId;
paintDesktopBackdrop();
uiBuilderInstallMenuBar(gMenus, (uint16_t)(sizeof gMenus / sizeof gMenus[0]));
ShowCursor();
// Open one window so the demo has visible content immediately.
doNew();
// Use a direct TaskMaster loop so the watchdog increments on
// every iteration regardless of TaskMaster's return code.
// iigsEventLoop's onIdle only ticks on EV_NULL which TaskMaster
// rarely emits with our task mask.
IigsEventT ev;
{
unsigned char *p = (unsigned char *)&ev;
for (uint16_t i = 0; i < sizeof ev; i++) {
p[i] = 0;
}
}
ev.taskMask = 0x1FFF;
uint16_t watchdog = 0;
do {
uint16_t code = TaskMaster(0x076E, &ev);
switch (code) {
case 3: // wInMenuBar
case 25: // wInSpecial
onMenu(0, (uint16_t)ev.taskData);
break;
case wInGoAway:
doClose();
break;
case wInContent:
sketch(&ev);
break;
default:
break;
}
watchdog++;
} while (!gDone && watchdog < 4000);
*(volatile unsigned char *)0x70 = 0x99;
return 0;
}