Unicode removed from source. Lots of bugs fixed. Close to Alpha 2!
This commit is contained in:
parent
70459616cc
commit
5a1332d024
103 changed files with 1682 additions and 1096 deletions
2
Makefile
2
Makefile
|
|
@ -1,4 +1,4 @@
|
||||||
# DVX GUI — Top-level Makefile
|
# DVX GUI -- Top-level Makefile
|
||||||
#
|
#
|
||||||
# Builds the full DVX stack: library, task switcher, shell, and apps.
|
# Builds the full DVX stack: library, task switcher, shell, and apps.
|
||||||
|
|
||||||
|
|
|
||||||
24
README.md
24
README.md
|
|
@ -1,4 +1,4 @@
|
||||||
# DVX — DOS Visual eXecutive
|
# DVX -- DOS Visual eXecutive
|
||||||
|
|
||||||
A Windows 3.x-style desktop shell for DOS, built with DJGPP/DPMI. Combines a
|
A Windows 3.x-style desktop shell for DOS, built with DJGPP/DPMI. Combines a
|
||||||
windowed GUI compositor, cooperative task switcher, and DXE3 dynamic loading to
|
windowed GUI compositor, cooperative task switcher, and DXE3 dynamic loading to
|
||||||
|
|
@ -103,20 +103,20 @@ mcopy -o -i <floppy.img> bin/dvx.exe ::DVX.EXE
|
||||||
mcopy -s -o -i <floppy.img> bin/apps ::APPS
|
mcopy -s -o -i <floppy.img> bin/apps ::APPS
|
||||||
```
|
```
|
||||||
|
|
||||||
The `apps/` directory structure must be preserved on the target — Program Manager
|
The `apps/` directory structure must be preserved on the target -- Program Manager
|
||||||
recursively scans `apps/` for `.app` files at startup.
|
recursively scans `apps/` for `.app` files at startup.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Each component directory has its own README with detailed API reference:
|
Each component directory has its own README with detailed API reference:
|
||||||
|
|
||||||
- [`dvx/README.md`](dvx/README.md) — GUI library architecture and API
|
- [`dvx/README.md`](dvx/README.md) -- GUI library architecture and API
|
||||||
- [`tasks/README.md`](tasks/README.md) — Task switcher API
|
- [`tasks/README.md`](tasks/README.md) -- Task switcher API
|
||||||
- [`dvxshell/README.md`](dvxshell/README.md) — Shell internals and DXE app contract
|
- [`dvxshell/README.md`](dvxshell/README.md) -- Shell internals and DXE app contract
|
||||||
- [`apps/README.md`](apps/README.md) — Writing DXE applications
|
- [`apps/README.md`](apps/README.md) -- Writing DXE applications
|
||||||
- [`rs232/README.md`](rs232/README.md) — Serial port driver
|
- [`rs232/README.md`](rs232/README.md) -- Serial port driver
|
||||||
- [`packet/README.md`](packet/README.md) — Packet transport protocol
|
- [`packet/README.md`](packet/README.md) -- Packet transport protocol
|
||||||
- [`security/README.md`](security/README.md) — Cryptographic primitives
|
- [`security/README.md`](security/README.md) -- Cryptographic primitives
|
||||||
- [`seclink/README.md`](seclink/README.md) — Secure serial link
|
- [`seclink/README.md`](seclink/README.md) -- Secure serial link
|
||||||
- [`proxy/README.md`](proxy/README.md) — Linux SecLink proxy
|
- [`proxy/README.md`](proxy/README.md) -- Linux SecLink proxy
|
||||||
- [`termdemo/README.md`](termdemo/README.md) — Encrypted terminal demo
|
- [`termdemo/README.md`](termdemo/README.md) -- Encrypted terminal demo
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# DVX Shell Applications Makefile — builds DXE3 modules
|
# DVX Shell Applications Makefile -- builds DXE3 modules
|
||||||
|
|
||||||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||||
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
|
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// clock.c — Clock DXE application (main-loop with tsYield)
|
// clock.c -- Clock DXE application (main-loop with tsYield)
|
||||||
//
|
//
|
||||||
// This is a main-loop DXE app (hasMainLoop = true), in contrast to callback-
|
// This is a main-loop DXE app (hasMainLoop = true), in contrast to callback-
|
||||||
// only apps like notepad or progman. The difference is fundamental:
|
// only apps like notepad or progman. The difference is fundamental:
|
||||||
|
|
@ -8,14 +8,14 @@
|
||||||
// to cooperatively yield to the shell and other tasks.
|
// to cooperatively yield to the shell and other tasks.
|
||||||
//
|
//
|
||||||
// A main-loop app is needed when the app has ongoing work that can't be
|
// A main-loop app is needed when the app has ongoing work that can't be
|
||||||
// expressed purely as event callbacks — here, polling the system clock
|
// expressed purely as event callbacks -- here, polling the system clock
|
||||||
// every second and repainting. The shell allocates a dedicated task stack
|
// every second and repainting. The shell allocates a dedicated task stack
|
||||||
// and schedules this task alongside the main event loop.
|
// and schedules this task alongside the main event loop.
|
||||||
//
|
//
|
||||||
// The onPaint/onClose callbacks still execute in task 0 during dvxUpdate(),
|
// The onPaint/onClose callbacks still execute in task 0 during dvxUpdate(),
|
||||||
// not in this task. The main loop only handles the clock-tick polling; the
|
// not in this task. The main loop only handles the clock-tick polling; the
|
||||||
// actual rendering and window management happen through the normal callback
|
// actual rendering and window management happen through the normal callback
|
||||||
// path. tsYield() is what makes this cooperative — without it, this task
|
// path. tsYield() is what makes this cooperative -- without it, this task
|
||||||
// would starve the shell and all other apps.
|
// would starve the shell and all other apps.
|
||||||
|
|
||||||
#include "dvxApp.h"
|
#include "dvxApp.h"
|
||||||
|
|
@ -87,7 +87,7 @@ static void onClose(WindowT *win) {
|
||||||
// onPaint demonstrates the raw paint callback approach (no widget tree).
|
// onPaint demonstrates the raw paint callback approach (no widget tree).
|
||||||
// Instead of using wgtInitWindow + widgets, this app renders directly into
|
// Instead of using wgtInitWindow + widgets, this app renders directly into
|
||||||
// the window's content buffer. This is the lower-level alternative to the
|
// the window's content buffer. This is the lower-level alternative to the
|
||||||
// widget system — useful for custom rendering like this centered clock display.
|
// widget system -- useful for custom rendering like this centered clock display.
|
||||||
//
|
//
|
||||||
// We create a temporary DisplayT struct pointing at the window's content buffer
|
// We create a temporary DisplayT struct pointing at the window's content buffer
|
||||||
// so the drawing primitives (rectFill, drawText) can operate on the window
|
// so the drawing primitives (rectFill, drawText) can operate on the window
|
||||||
|
|
@ -197,13 +197,13 @@ int32_t appMain(DxeAppContextT *ctx) {
|
||||||
sWin->onClose = onClose;
|
sWin->onClose = onClose;
|
||||||
sWin->onPaint = onPaint;
|
sWin->onPaint = onPaint;
|
||||||
|
|
||||||
// Initial paint — dvxInvalidateWindow calls onPaint automatically
|
// Initial paint -- dvxInvalidateWindow calls onPaint automatically
|
||||||
dvxInvalidateWindow(ac, sWin);
|
dvxInvalidateWindow(ac, sWin);
|
||||||
|
|
||||||
// Main loop: check if the second has changed, repaint if so, then yield.
|
// Main loop: check if the second has changed, repaint if so, then yield.
|
||||||
// tsYield() transfers control back to the shell's task scheduler.
|
// tsYield() transfers control back to the shell's task scheduler.
|
||||||
// On a 486, time() resolution is 1 second, so we yield many times per
|
// On a 486, time() resolution is 1 second, so we yield many times per
|
||||||
// second between actual updates — this keeps CPU usage near zero.
|
// second between actual updates -- this keeps CPU usage near zero.
|
||||||
// dvxInvalidateWindow marks the window dirty and calls onPaint to
|
// dvxInvalidateWindow marks the window dirty and calls onPaint to
|
||||||
// update the content buffer before the compositor flushes it.
|
// update the content buffer before the compositor flushes it.
|
||||||
while (!sState.quit) {
|
while (!sState.quit) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
// ctrlpanel.c — DVX Control Panel
|
// ctrlpanel.c -- DVX Control Panel
|
||||||
//
|
//
|
||||||
// System configuration app with four tabs:
|
// System configuration app with four tabs:
|
||||||
// Mouse — scroll direction, double-click speed, acceleration
|
// Mouse -- scroll direction, double-click speed, acceleration
|
||||||
// Colors — all 18 system colors, theme load/save
|
// Colors -- all 18 system colors, theme load/save
|
||||||
// Desktop — wallpaper image (stretch mode)
|
// Desktop -- wallpaper image (stretch mode)
|
||||||
// Video — resolution and color depth
|
// Video -- resolution and color depth
|
||||||
//
|
//
|
||||||
// Changes preview live. OK saves to DVX.INI, Cancel reverts to the
|
// Changes preview live. OK saves to DVX.INI, Cancel reverts to the
|
||||||
// state captured when the control panel was opened.
|
// state captured when the control panel was opened.
|
||||||
|
|
@ -72,7 +72,7 @@ static int32_t sSavedAccel;
|
||||||
static int32_t sSavedVideoW;
|
static int32_t sSavedVideoW;
|
||||||
static int32_t sSavedVideoH;
|
static int32_t sSavedVideoH;
|
||||||
static int32_t sSavedVideoBpp;
|
static int32_t sSavedVideoBpp;
|
||||||
static bool sSavedHadWallpaper;
|
static WallpaperModeE sSavedWpMode;
|
||||||
|
|
||||||
// Mouse tab widgets
|
// Mouse tab widgets
|
||||||
static WidgetT *sWheelDrop = NULL;
|
static WidgetT *sWheelDrop = NULL;
|
||||||
|
|
@ -94,6 +94,7 @@ static WidgetT *sColorSwatch = NULL;
|
||||||
// Desktop tab widgets
|
// Desktop tab widgets
|
||||||
static WidgetT *sWallpaperLbl = NULL;
|
static WidgetT *sWallpaperLbl = NULL;
|
||||||
static WidgetT *sWpaperList = NULL;
|
static WidgetT *sWpaperList = NULL;
|
||||||
|
static WidgetT *sWpModeDrop = NULL;
|
||||||
static char sWallpaperPath[260];
|
static char sWallpaperPath[260];
|
||||||
static FileEntryT *sWpaperEntries = NULL; // stb_ds dynamic array
|
static FileEntryT *sWpaperEntries = NULL; // stb_ds dynamic array
|
||||||
static const char **sWpaperLabels = NULL; // stb_ds dynamic array
|
static const char **sWpaperLabels = NULL; // stb_ds dynamic array
|
||||||
|
|
@ -125,6 +126,7 @@ static void onApplyTheme(WidgetT *w);
|
||||||
static void onBrowseTheme(WidgetT *w);
|
static void onBrowseTheme(WidgetT *w);
|
||||||
static void onResetColors(WidgetT *w);
|
static void onResetColors(WidgetT *w);
|
||||||
static void onApplyWallpaper(WidgetT *w);
|
static void onApplyWallpaper(WidgetT *w);
|
||||||
|
static void onWallpaperMode(WidgetT *w);
|
||||||
static void onCancel(WidgetT *w);
|
static void onCancel(WidgetT *w);
|
||||||
static void onChooseWallpaper(WidgetT *w);
|
static void onChooseWallpaper(WidgetT *w);
|
||||||
static void onClearWallpaper(WidgetT *w);
|
static void onClearWallpaper(WidgetT *w);
|
||||||
|
|
@ -167,7 +169,7 @@ static void buildColorsTab(WidgetT *page) {
|
||||||
static const char *colorNames[ColorCountE];
|
static const char *colorNames[ColorCountE];
|
||||||
|
|
||||||
for (int32_t i = 0; i < ColorCountE; i++) {
|
for (int32_t i = 0; i < ColorCountE; i++) {
|
||||||
colorNames[i] = dvxColorName((ColorIdE)i);
|
colorNames[i] = dvxColorLabel((ColorIdE)i);
|
||||||
}
|
}
|
||||||
|
|
||||||
sColorList = wgtListBox(leftVbox);
|
sColorList = wgtListBox(leftVbox);
|
||||||
|
|
@ -203,28 +205,37 @@ static void buildColorsTab(WidgetT *page) {
|
||||||
resetBtn->onClick = onResetColors;
|
resetBtn->onClick = onResetColors;
|
||||||
resetBtn->prefW = wgtPixels(60);
|
resetBtn->prefW = wgtPixels(60);
|
||||||
|
|
||||||
// Right side: RGB sliders
|
// Right side: RGB sliders in a non-weighted inner box so they stay
|
||||||
|
// at natural size. The outer box absorbs extra vertical space.
|
||||||
WidgetT *rightVbox = wgtVBox(hbox);
|
WidgetT *rightVbox = wgtVBox(hbox);
|
||||||
rightVbox->weight = 50;
|
rightVbox->weight = 50;
|
||||||
rightVbox->spacing = wgtPixels(4);
|
|
||||||
|
|
||||||
wgtLabel(rightVbox, "Red:");
|
WidgetT *sliderBox = wgtVBox(rightVbox);
|
||||||
sRedSldr = wgtSlider(rightVbox, 0, 255);
|
sliderBox->spacing = wgtPixels(4);
|
||||||
|
|
||||||
|
wgtLabel(sliderBox, "Red:");
|
||||||
|
sRedSldr = wgtSlider(sliderBox, 0, 255);
|
||||||
sRedSldr->onChange = onColorSlider;
|
sRedSldr->onChange = onColorSlider;
|
||||||
sRedLbl = wgtLabel(rightVbox, "0");
|
sRedLbl = wgtLabel(sliderBox, "0");
|
||||||
|
wgtLabelSetAlign(sRedLbl, AlignEndE);
|
||||||
|
|
||||||
wgtLabel(rightVbox, "Green:");
|
wgtLabel(sliderBox, "Green:");
|
||||||
sGreenSldr = wgtSlider(rightVbox, 0, 255);
|
sGreenSldr = wgtSlider(sliderBox, 0, 255);
|
||||||
sGreenSldr->onChange = onColorSlider;
|
sGreenSldr->onChange = onColorSlider;
|
||||||
sGreenLbl = wgtLabel(rightVbox, "0");
|
sGreenLbl = wgtLabel(sliderBox, "0");
|
||||||
|
wgtLabelSetAlign(sGreenLbl, AlignEndE);
|
||||||
|
|
||||||
wgtLabel(rightVbox, "Blue:");
|
wgtLabel(sliderBox, "Blue:");
|
||||||
sBlueSldr = wgtSlider(rightVbox, 0, 255);
|
sBlueSldr = wgtSlider(sliderBox, 0, 255);
|
||||||
sBlueSldr->onChange = onColorSlider;
|
sBlueSldr->onChange = onColorSlider;
|
||||||
sBlueLbl = wgtLabel(rightVbox, "0");
|
sBlueLbl = wgtLabel(sliderBox, "0");
|
||||||
|
wgtLabelSetAlign(sBlueLbl, AlignEndE);
|
||||||
|
|
||||||
wgtLabel(rightVbox, "Preview:");
|
wgtLabel(sliderBox, "Preview:");
|
||||||
sColorSwatch = wgtCanvas(rightVbox, 64, 24);
|
sColorSwatch = wgtCanvas(sliderBox, 64, 24);
|
||||||
|
|
||||||
|
// Absorb remaining vertical space below the sliders
|
||||||
|
wgtSpacer(rightVbox)->weight = 100;
|
||||||
|
|
||||||
updateColorSliders();
|
updateColorSliders();
|
||||||
}
|
}
|
||||||
|
|
@ -259,6 +270,15 @@ static void buildDesktopTab(WidgetT *page) {
|
||||||
WidgetT *clearBtn = wgtButton(btnRow, "Clear");
|
WidgetT *clearBtn = wgtButton(btnRow, "Clear");
|
||||||
clearBtn->onClick = onClearWallpaper;
|
clearBtn->onClick = onClearWallpaper;
|
||||||
clearBtn->prefW = wgtPixels(90);
|
clearBtn->prefW = wgtPixels(90);
|
||||||
|
|
||||||
|
wgtSpacer(btnRow)->weight = 100;
|
||||||
|
wgtLabel(btnRow, "Mode:");
|
||||||
|
|
||||||
|
static const char *modeItems[] = {"Stretch", "Tile", "Center"};
|
||||||
|
sWpModeDrop = wgtDropdown(btnRow);
|
||||||
|
sWpModeDrop->onChange = onWallpaperMode;
|
||||||
|
wgtDropdownSetItems(sWpModeDrop, modeItems, 3);
|
||||||
|
wgtDropdownSetSelected(sWpModeDrop, (int32_t)sAc->wallpaperMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -420,7 +440,7 @@ static const char *mapAccelValue(int32_t idx) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Callbacks — Mouse tab
|
// Callbacks -- Mouse tab
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void onWheelChange(WidgetT *w) {
|
static void onWheelChange(WidgetT *w) {
|
||||||
|
|
@ -472,7 +492,7 @@ static void onAccelChange(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Callbacks — Colors tab
|
// Callbacks -- Colors tab
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void onColorSelect(WidgetT *w) {
|
static void onColorSelect(WidgetT *w) {
|
||||||
|
|
@ -565,7 +585,7 @@ static void onResetColors(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Callbacks — Desktop tab
|
// Callbacks -- Desktop tab
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void onApplyWallpaper(WidgetT *w) {
|
static void onApplyWallpaper(WidgetT *w) {
|
||||||
|
|
@ -615,8 +635,17 @@ static void onClearWallpaper(WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void onWallpaperMode(WidgetT *w) {
|
||||||
|
int32_t sel = wgtDropdownGetSelected(w);
|
||||||
|
|
||||||
|
if (sel >= 0 && sel <= 2) {
|
||||||
|
dvxSetWallpaperMode(sAc, (WallpaperModeE)sel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Callbacks — Video tab
|
// Callbacks -- Video tab
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static int32_t sVideoConfirmResult = -1;
|
static int32_t sVideoConfirmResult = -1;
|
||||||
|
|
@ -743,7 +772,7 @@ static void onVideoApply(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Callbacks — OK / Cancel / Close
|
// Callbacks -- OK / Cancel / Close
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void onOk(WidgetT *w) {
|
static void onOk(WidgetT *w) {
|
||||||
|
|
@ -774,6 +803,16 @@ static void onOk(WidgetT *w) {
|
||||||
prefsRemove("desktop", "wallpaper");
|
prefsRemove("desktop", "wallpaper");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *modeStr = "stretch";
|
||||||
|
|
||||||
|
if (sAc->wallpaperMode == WallpaperTileE) {
|
||||||
|
modeStr = "tile";
|
||||||
|
} else if (sAc->wallpaperMode == WallpaperCenterE) {
|
||||||
|
modeStr = "center";
|
||||||
|
}
|
||||||
|
|
||||||
|
prefsSetString("desktop", "mode", modeStr);
|
||||||
|
|
||||||
// Save video settings
|
// Save video settings
|
||||||
prefsSetInt("video", "width", sAc->display.width);
|
prefsSetInt("video", "width", sAc->display.width);
|
||||||
prefsSetInt("video", "height", sAc->display.height);
|
prefsSetInt("video", "height", sAc->display.height);
|
||||||
|
|
@ -804,6 +843,8 @@ static void onClose(WindowT *win) {
|
||||||
// saveSnapshot / restoreSnapshot
|
// saveSnapshot / restoreSnapshot
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
static char sSavedWallpaperPath[260];
|
||||||
|
|
||||||
static void saveSnapshot(void) {
|
static void saveSnapshot(void) {
|
||||||
memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb));
|
memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb));
|
||||||
sSavedWheelDir = sAc->wheelDirection;
|
sSavedWheelDir = sAc->wheelDirection;
|
||||||
|
|
@ -812,16 +853,11 @@ static void saveSnapshot(void) {
|
||||||
sSavedVideoW = sAc->display.width;
|
sSavedVideoW = sAc->display.width;
|
||||||
sSavedVideoH = sAc->display.height;
|
sSavedVideoH = sAc->display.height;
|
||||||
sSavedVideoBpp = sAc->display.format.bitsPerPixel;
|
sSavedVideoBpp = sAc->display.format.bitsPerPixel;
|
||||||
sSavedHadWallpaper = (sAc->wallpaperBuf != NULL);
|
|
||||||
|
|
||||||
const char *wp = prefsGetString("desktop", "wallpaper", NULL);
|
// Save the active wallpaper path and mode from the context
|
||||||
|
snprintf(sSavedWallpaperPath, sizeof(sSavedWallpaperPath), "%s", sAc->wallpaperPath);
|
||||||
if (wp) {
|
snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sAc->wallpaperPath);
|
||||||
strncpy(sWallpaperPath, wp, sizeof(sWallpaperPath) - 1);
|
sSavedWpMode = sAc->wallpaperMode;
|
||||||
sWallpaperPath[sizeof(sWallpaperPath) - 1] = '\0';
|
|
||||||
} else {
|
|
||||||
sWallpaperPath[0] = '\0';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -842,9 +878,11 @@ static void restoreSnapshot(void) {
|
||||||
dvxChangeVideoMode(sAc, sSavedVideoW, sSavedVideoH, sSavedVideoBpp);
|
dvxChangeVideoMode(sAc, sSavedVideoW, sSavedVideoH, sSavedVideoBpp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore wallpaper
|
// Restore wallpaper mode and image
|
||||||
if (sSavedHadWallpaper && sWallpaperPath[0]) {
|
sAc->wallpaperMode = sSavedWpMode;
|
||||||
dvxSetWallpaper(sAc, sWallpaperPath);
|
|
||||||
|
if (sSavedWallpaperPath[0]) {
|
||||||
|
dvxSetWallpaper(sAc, sSavedWallpaperPath);
|
||||||
} else {
|
} else {
|
||||||
dvxSetWallpaper(sAc, NULL);
|
dvxSetWallpaper(sAc, NULL);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvxdemo.c — DVX GUI demonstration app (DXE version)
|
// dvxdemo.c -- DVX GUI demonstration app (DXE version)
|
||||||
//
|
//
|
||||||
// Callback-only DXE app (hasMainLoop = false) that opens several windows
|
// Callback-only DXE app (hasMainLoop = false) that opens several windows
|
||||||
// showcasing the DVX widget system, paint callbacks, menus, accelerators, etc.
|
// showcasing the DVX widget system, paint callbacks, menus, accelerators, etc.
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// - setupTerminalWindow: ANSI terminal emulator widget
|
// - setupTerminalWindow: ANSI terminal emulator widget
|
||||||
//
|
//
|
||||||
// Each window is independent; closing one doesn't affect the others.
|
// Each window is independent; closing one doesn't affect the others.
|
||||||
// The app has no persistent state — it's purely a showcase.
|
// The app has no persistent state -- it's purely a showcase.
|
||||||
|
|
||||||
#include "dvxApp.h"
|
#include "dvxApp.h"
|
||||||
#include "dvxDialog.h"
|
#include "dvxDialog.h"
|
||||||
|
|
@ -234,7 +234,7 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
|
||||||
|
|
||||||
// Walk up the widget tree to find the root, then use wgtFind() to locate
|
// Walk up the widget tree to find the root, then use wgtFind() to locate
|
||||||
// the named status label. This pattern avoids static coupling between the
|
// the named status label. This pattern avoids static coupling between the
|
||||||
// button and the status label — they're connected only by the name string.
|
// button and the status label -- they're connected only by the name string.
|
||||||
static void onOkClick(WidgetT *w) {
|
static void onOkClick(WidgetT *w) {
|
||||||
WidgetT *root = w;
|
WidgetT *root = w;
|
||||||
|
|
||||||
|
|
@ -314,7 +314,7 @@ static void onPaintPattern(WindowT *win, RectT *dirtyArea) {
|
||||||
|
|
||||||
// Renders text manually using the bitmap font glyph data, bypassing the
|
// Renders text manually using the bitmap font glyph data, bypassing the
|
||||||
// normal drawText helper. This demonstrates direct glyph rendering and
|
// normal drawText helper. This demonstrates direct glyph rendering and
|
||||||
// also serves as a regression test for the font subsystem — if the glyph
|
// also serves as a regression test for the font subsystem -- if the glyph
|
||||||
// data format ever changes, this paint callback would visibly break.
|
// data format ever changes, this paint callback would visibly break.
|
||||||
static void onPaintText(WindowT *win, RectT *dirtyArea) {
|
static void onPaintText(WindowT *win, RectT *dirtyArea) {
|
||||||
(void)dirtyArea;
|
(void)dirtyArea;
|
||||||
|
|
@ -424,7 +424,7 @@ static void onToolbarClick(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// setupControlsWindow — advanced widgets with tabs
|
// setupControlsWindow -- advanced widgets with tabs
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Item arrays are static because dropdown/combobox widgets store pointers,
|
// Item arrays are static because dropdown/combobox widgets store pointers,
|
||||||
|
|
@ -790,7 +790,7 @@ static void setupControlsWindow(void) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// setupMainWindow — info window + paint demos
|
// setupMainWindow -- info window + paint demos
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Creates three windows that demonstrate raw onPaint rendering (no widgets):
|
// Creates three windows that demonstrate raw onPaint rendering (no widgets):
|
||||||
|
|
@ -931,7 +931,7 @@ static void setupMainWindow(void) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// setupTerminalWindow — ANSI terminal widget demo
|
// setupTerminalWindow -- ANSI terminal widget demo
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Creates an ANSI terminal widget with sample output demonstrating all
|
// Creates an ANSI terminal widget with sample output demonstrating all
|
||||||
|
|
@ -1014,13 +1014,13 @@ static void setupTerminalWindow(void) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// setupWidgetDemo — form with accelerators
|
// setupWidgetDemo -- form with accelerators
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Demonstrates the standard form pattern: labeled inputs in frames, checkbox
|
// Demonstrates the standard form pattern: labeled inputs in frames, checkbox
|
||||||
// and radio groups, list boxes (single and multi-select with context menus),
|
// and radio groups, list boxes (single and multi-select with context menus),
|
||||||
// and a button row. The '&' in label text marks the Alt+key mnemonic that
|
// and a button row. The '&' in label text marks the Alt+key mnemonic that
|
||||||
// moves focus to the associated widget — this is the accelerator mechanism.
|
// moves focus to the associated widget -- this is the accelerator mechanism.
|
||||||
static void setupWidgetDemo(void) {
|
static void setupWidgetDemo(void) {
|
||||||
WindowT *win = dvxCreateWindow(sAc, "Widget Demo", 80, 200, 280, 360, true);
|
WindowT *win = dvxCreateWindow(sAc, "Widget Demo", 80, 200, 280, 360, true);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// imgview.c — DVX Image Viewer
|
// imgview.c -- DVX Image Viewer
|
||||||
//
|
//
|
||||||
// Displays BMP, PNG, JPG, and GIF images. The image is scaled to fit
|
// Displays BMP, PNG, JPG, and GIF images. The image is scaled to fit
|
||||||
// the window while preserving aspect ratio. Resize the window to zoom.
|
// the window while preserving aspect ratio. Resize the window to zoom.
|
||||||
|
|
@ -65,15 +65,19 @@ static uint8_t *sScaled = NULL;
|
||||||
static int32_t sScaledW = 0;
|
static int32_t sScaledW = 0;
|
||||||
static int32_t sScaledH = 0;
|
static int32_t sScaledH = 0;
|
||||||
static int32_t sScaledPitch = 0;
|
static int32_t sScaledPitch = 0;
|
||||||
|
static int32_t sLastFitW = 0; // window size the image was last scaled for
|
||||||
|
static int32_t sLastFitH = 0;
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// buildScaled — scale source image to fit window
|
// buildScaled -- scale source image to fit window
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void buildScaled(int32_t fitW, int32_t fitH) {
|
static void buildScaled(int32_t fitW, int32_t fitH) {
|
||||||
free(sScaled);
|
free(sScaled);
|
||||||
sScaled = NULL;
|
sScaled = NULL;
|
||||||
|
sLastFitW = fitW;
|
||||||
|
sLastFitH = fitH;
|
||||||
|
|
||||||
if (!sImgRgb || fitW < 1 || fitH < 1) {
|
if (!sImgRgb || fitW < 1 || fitH < 1) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -113,7 +117,6 @@ static void buildScaled(int32_t fitW, int32_t fitH) {
|
||||||
int32_t srcStride = sImgW * 3;
|
int32_t srcStride = sImgW * 3;
|
||||||
|
|
||||||
for (int32_t y = 0; y < dstH; y++) {
|
for (int32_t y = 0; y < dstH; y++) {
|
||||||
// Yield every 32 rows so the UI stays responsive
|
|
||||||
if ((y & 31) == 0 && y > 0) {
|
if ((y & 31) == 0 && y > 0) {
|
||||||
dvxUpdate(sAc);
|
dvxUpdate(sAc);
|
||||||
}
|
}
|
||||||
|
|
@ -236,6 +239,15 @@ static void onMenu(WindowT *win, int32_t menuId) {
|
||||||
static void onPaint(WindowT *win, RectT *dirty) {
|
static void onPaint(WindowT *win, RectT *dirty) {
|
||||||
(void)dirty;
|
(void)dirty;
|
||||||
|
|
||||||
|
// If the resize drag has ended and the window size changed since
|
||||||
|
// the last scale, rebuild now. During drag, just show the old
|
||||||
|
// scaled image (or dark background) to avoid expensive per-frame scaling.
|
||||||
|
if (sImgRgb && sAc->stack.resizeWindow < 0) {
|
||||||
|
if (sLastFitW != win->contentW || sLastFitH != win->contentH) {
|
||||||
|
buildScaled(win->contentW, win->contentH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DisplayT cd = sAc->display;
|
DisplayT cd = sAc->display;
|
||||||
cd.backBuf = win->contentBuf;
|
cd.backBuf = win->contentBuf;
|
||||||
cd.width = win->contentW;
|
cd.width = win->contentW;
|
||||||
|
|
@ -250,22 +262,42 @@ static void onPaint(WindowT *win, RectT *dirty) {
|
||||||
uint32_t bg = packColor(&sAc->display, 32, 32, 32);
|
uint32_t bg = packColor(&sAc->display, 32, 32, 32);
|
||||||
rectFill(&cd, &sAc->blitOps, 0, 0, win->contentW, win->contentH, bg);
|
rectFill(&cd, &sAc->blitOps, 0, 0, win->contentW, win->contentH, bg);
|
||||||
|
|
||||||
// Blit scaled image centered
|
// Blit scaled image centered, clipped to content bounds
|
||||||
if (sScaled) {
|
if (sScaled) {
|
||||||
int32_t offX = (win->contentW - sScaledW) / 2;
|
int32_t offX = (win->contentW - sScaledW) / 2;
|
||||||
int32_t offY = (win->contentH - sScaledH) / 2;
|
int32_t offY = (win->contentH - sScaledH) / 2;
|
||||||
int32_t bpp = sAc->display.format.bytesPerPixel;
|
int32_t bpp = sAc->display.format.bytesPerPixel;
|
||||||
|
|
||||||
for (int32_t y = 0; y < sScaledH; y++) {
|
// Compute visible region (clip source and dest)
|
||||||
int32_t dstY = offY + y;
|
int32_t srcX = 0;
|
||||||
|
int32_t srcY = 0;
|
||||||
|
int32_t blitW = sScaledW;
|
||||||
|
int32_t blitH = sScaledH;
|
||||||
|
|
||||||
if (dstY < 0 || dstY >= win->contentH) {
|
if (offX < 0) {
|
||||||
continue;
|
srcX = -offX;
|
||||||
|
blitW += offX;
|
||||||
|
offX = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *src = sScaled + y * sScaledPitch;
|
if (offY < 0) {
|
||||||
uint8_t *dst = win->contentBuf + dstY * win->contentPitch + offX * bpp;
|
srcY = -offY;
|
||||||
memcpy(dst, src, sScaledW * bpp);
|
blitH += offY;
|
||||||
|
offY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offX + blitW > win->contentW) {
|
||||||
|
blitW = win->contentW - offX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offY + blitH > win->contentH) {
|
||||||
|
blitH = win->contentH - offY;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t y = 0; y < blitH; y++) {
|
||||||
|
uint8_t *src = sScaled + (srcY + y) * sScaledPitch + srcX * bpp;
|
||||||
|
uint8_t *dst = win->contentBuf + (offY + y) * win->contentPitch + offX * bpp;
|
||||||
|
memcpy(dst, src, blitW * bpp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -277,7 +309,10 @@ static void onPaint(WindowT *win, RectT *dirty) {
|
||||||
|
|
||||||
static void onResize(WindowT *win, int32_t contentW, int32_t contentH) {
|
static void onResize(WindowT *win, int32_t contentW, int32_t contentH) {
|
||||||
(void)win;
|
(void)win;
|
||||||
buildScaled(contentW, contentH);
|
(void)contentW;
|
||||||
|
(void)contentH;
|
||||||
|
// Don't rescale here -- onPaint handles it after the drag ends.
|
||||||
|
// This avoids expensive bilinear scaling on every frame of a drag.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// notepad.c — Simple text editor DXE application (callback-only)
|
// notepad.c -- Simple text editor DXE application (callback-only)
|
||||||
//
|
//
|
||||||
// A callback-only DXE app (hasMainLoop = false) that provides basic text
|
// A callback-only DXE app (hasMainLoop = false) that provides basic text
|
||||||
// editing with file I/O. Demonstrates the standard DXE app pattern:
|
// editing with file I/O. Demonstrates the standard DXE app pattern:
|
||||||
|
|
@ -87,7 +87,7 @@ AppDescriptorT appDescriptor = {
|
||||||
// Dirty tracking
|
// Dirty tracking
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// djb2-xor hash for dirty detection. Not cryptographic — just a fast way
|
// djb2-xor hash for dirty detection. Not cryptographic -- just a fast way
|
||||||
// to detect changes without storing a full copy of the last-saved text.
|
// to detect changes without storing a full copy of the last-saved text.
|
||||||
// False negatives are theoretically possible but vanishingly unlikely for
|
// False negatives are theoretically possible but vanishingly unlikely for
|
||||||
// text edits. This avoids the memory cost of keeping a shadow buffer.
|
// text edits. This avoids the memory cost of keeping a shadow buffer.
|
||||||
|
|
@ -181,7 +181,7 @@ static void doOpen(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read entire file into a temporary buffer. Files larger than the
|
// Read entire file into a temporary buffer. Files larger than the
|
||||||
// TextArea's buffer are silently truncated — this matches the behavior
|
// TextArea's buffer are silently truncated -- this matches the behavior
|
||||||
// of Windows Notepad on large files.
|
// of Windows Notepad on large files.
|
||||||
fseek(f, 0, SEEK_END);
|
fseek(f, 0, SEEK_END);
|
||||||
long size = ftell(f);
|
long size = ftell(f);
|
||||||
|
|
@ -368,7 +368,7 @@ int32_t appMain(DxeAppContextT *ctx) {
|
||||||
|
|
||||||
// The widget tree is minimal: just a TextArea filling the entire content
|
// The widget tree is minimal: just a TextArea filling the entire content
|
||||||
// area (weight=100). The TextArea widget provides editing, scrolling,
|
// area (weight=100). The TextArea widget provides editing, scrolling,
|
||||||
// selection, copy/paste, and undo — all driven by keyboard events that
|
// selection, copy/paste, and undo -- all driven by keyboard events that
|
||||||
// the shell dispatches to the focused widget.
|
// the shell dispatches to the focused widget.
|
||||||
WidgetT *root = wgtInitWindow(ac, sWin);
|
WidgetT *root = wgtInitWindow(ac, sWin);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// progman.c — Program Manager application for DVX Shell
|
// progman.c -- Program Manager application for DVX Shell
|
||||||
//
|
//
|
||||||
// Displays a grid of available apps from the apps/ directory.
|
// Displays a grid of available apps from the apps/ directory.
|
||||||
// Double-click or Enter launches an app. Includes Task Manager (Ctrl+Esc).
|
// Double-click or Enter launches an app. Includes Task Manager (Ctrl+Esc).
|
||||||
|
|
@ -20,8 +20,10 @@
|
||||||
|
|
||||||
#include "dvxApp.h"
|
#include "dvxApp.h"
|
||||||
#include "dvxDialog.h"
|
#include "dvxDialog.h"
|
||||||
|
#include "dvxPrefs.h"
|
||||||
#include "dvxWidget.h"
|
#include "dvxWidget.h"
|
||||||
#include "dvxWm.h"
|
#include "dvxWm.h"
|
||||||
|
#include "platform/dvxPlatform.h"
|
||||||
#include "shellApp.h"
|
#include "shellApp.h"
|
||||||
#include "shellTaskMgr.h"
|
#include "shellTaskMgr.h"
|
||||||
#include "shellInfo.h"
|
#include "shellInfo.h"
|
||||||
|
|
@ -144,7 +146,7 @@ static void buildPmWindow(void) {
|
||||||
wmAddMenuItem(fileMenu, "E&xit Shell", CMD_EXIT);
|
wmAddMenuItem(fileMenu, "E&xit Shell", CMD_EXIT);
|
||||||
|
|
||||||
MenuT *optMenu = wmAddMenu(menuBar, "&Options");
|
MenuT *optMenu = wmAddMenu(menuBar, "&Options");
|
||||||
wmAddMenuCheckItem(optMenu, "&Minimize on Run", CMD_MIN_ON_RUN, false);
|
wmAddMenuCheckItem(optMenu, "&Minimize on Run", CMD_MIN_ON_RUN, sMinOnRun);
|
||||||
|
|
||||||
MenuT *windowMenu = wmAddMenu(menuBar, "&Window");
|
MenuT *windowMenu = wmAddMenu(menuBar, "&Window");
|
||||||
wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE);
|
wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE);
|
||||||
|
|
@ -293,6 +295,9 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
||||||
|
|
||||||
case CMD_MIN_ON_RUN:
|
case CMD_MIN_ON_RUN:
|
||||||
sMinOnRun = !sMinOnRun;
|
sMinOnRun = !sMinOnRun;
|
||||||
|
shellEnsureConfigDir(sCtx);
|
||||||
|
prefsSetBool("options", "minimizeOnRun", sMinOnRun);
|
||||||
|
prefsSave();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_ABOUT:
|
case CMD_ABOUT:
|
||||||
|
|
@ -346,7 +351,7 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
||||||
char fullPath[MAX_PATH_LEN];
|
char fullPath[MAX_PATH_LEN];
|
||||||
snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, ent->d_name);
|
snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, ent->d_name);
|
||||||
|
|
||||||
// Check if this is a directory — recurse into it
|
// Check if this is a directory -- recurse into it
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
||||||
if (stat(fullPath, &st) == 0 && S_ISDIR(st.st_mode)) {
|
if (stat(fullPath, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
|
|
@ -431,7 +436,7 @@ static void showSystemInfo(void) {
|
||||||
ta->weight = 100;
|
ta->weight = 100;
|
||||||
wgtSetText(ta, info);
|
wgtSetText(ta, info);
|
||||||
|
|
||||||
// Don't disable — wgtSetEnabled(false) blocks all input including scrollbar
|
// Don't disable -- wgtSetEnabled(false) blocks all input including scrollbar
|
||||||
wgtSetReadOnly(ta, true);
|
wgtSetReadOnly(ta, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -467,6 +472,12 @@ int32_t appMain(DxeAppContextT *ctx) {
|
||||||
sCtx = ctx;
|
sCtx = ctx;
|
||||||
sAc = ctx->shellCtx;
|
sAc = ctx->shellCtx;
|
||||||
|
|
||||||
|
// Load saved preferences
|
||||||
|
char prefsPath[260];
|
||||||
|
shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath));
|
||||||
|
prefsLoad(prefsPath);
|
||||||
|
sMinOnRun = prefsGetBool("options", "minimizeOnRun", false);
|
||||||
|
|
||||||
scanAppsDir();
|
scanAppsDir();
|
||||||
buildPmWindow();
|
buildPmWindow();
|
||||||
|
|
||||||
|
|
|
||||||
635
dvx/dvxApp.c
635
dvx/dvxApp.c
File diff suppressed because it is too large
Load diff
30
dvx/dvxApp.h
30
dvx/dvxApp.h
|
|
@ -1,14 +1,14 @@
|
||||||
// dvx_app.h — Layer 5: Application API for DVX GUI
|
// dvx_app.h -- Layer 5: Application API for DVX GUI
|
||||||
//
|
//
|
||||||
// The topmost layer and the public-facing API for applications. Aggregates
|
// The topmost layer and the public-facing API for applications. Aggregates
|
||||||
// all lower layers into a single AppContextT and provides a clean interface
|
// all lower layers into a single AppContextT and provides a clean interface
|
||||||
// for window creation, event dispatch, and utilities. Applications interact
|
// for window creation, event dispatch, and utilities. Applications interact
|
||||||
// exclusively through dvx*() functions and window callbacks — they never
|
// exclusively through dvx*() functions and window callbacks -- they never
|
||||||
// need to call the lower WM, compositor, or draw layers directly.
|
// need to call the lower WM, compositor, or draw layers directly.
|
||||||
//
|
//
|
||||||
// The event loop (dvxRun/dvxUpdate) follows a cooperative model: poll mouse
|
// The event loop (dvxRun/dvxUpdate) follows a cooperative model: poll mouse
|
||||||
// and keyboard, dispatch events to the focused window, run the compositor
|
// and keyboard, dispatch events to the focused window, run the compositor
|
||||||
// for dirty regions, then yield. There's no preemptive scheduling — the
|
// for dirty regions, then yield. There's no preemptive scheduling -- the
|
||||||
// application must return from callbacks promptly.
|
// application must return from callbacks promptly.
|
||||||
#ifndef DVX_APP_H
|
#ifndef DVX_APP_H
|
||||||
#define DVX_APP_H
|
#define DVX_APP_H
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Single monolithic context that owns all GUI state. Allocated on the
|
// Single monolithic context that owns all GUI state. Allocated on the
|
||||||
// caller's stack (or statically) — no internal heap allocation for the
|
// caller's stack (or statically) -- no internal heap allocation for the
|
||||||
// context itself. This "god struct" approach keeps the API simple (one
|
// context itself. This "god struct" approach keeps the API simple (one
|
||||||
// pointer to pass everywhere) and avoids the overhead of a handle-based
|
// pointer to pass everywhere) and avoids the overhead of a handle-based
|
||||||
// system with opaque lookups. The tradeoff is a large struct, but on DOS
|
// system with opaque lookups. The tradeoff is a large struct, but on DOS
|
||||||
|
|
@ -82,7 +82,7 @@ typedef struct AppContextT {
|
||||||
void *ctrlEscCtx;
|
void *ctrlEscCtx;
|
||||||
void (*onTitleChange)(void *ctx); // called when any window title changes
|
void (*onTitleChange)(void *ctx); // called when any window title changes
|
||||||
void *titleChangeCtx;
|
void *titleChangeCtx;
|
||||||
// Tooltip state — tooltip appears after the mouse hovers over a widget
|
// Tooltip state -- tooltip appears after the mouse hovers over a widget
|
||||||
// with a tooltip string for a brief delay. Pre-computing W/H avoids
|
// with a tooltip string for a brief delay. Pre-computing W/H avoids
|
||||||
// re-measuring on every paint frame.
|
// re-measuring on every paint frame.
|
||||||
clock_t tooltipHoverStart; // when mouse stopped moving
|
clock_t tooltipHoverStart; // when mouse stopped moving
|
||||||
|
|
@ -104,12 +104,13 @@ typedef struct AppContextT {
|
||||||
// Available video modes (enumerated once at init)
|
// Available video modes (enumerated once at init)
|
||||||
VideoModeInfoT *videoModes; // stb_ds dynamic array
|
VideoModeInfoT *videoModes; // stb_ds dynamic array
|
||||||
int32_t videoModeCount;
|
int32_t videoModeCount;
|
||||||
// Wallpaper — pre-scaled to screen dimensions in native pixel format.
|
// Wallpaper -- pre-rendered to screen dimensions in native pixel format.
|
||||||
// NULL means no wallpaper (solid desktop color). wallpaperPath is
|
// NULL means no wallpaper (solid desktop color). wallpaperPath is
|
||||||
// kept so the image can be reloaded after a video mode change.
|
// kept so the image can be reloaded after a video mode or mode change.
|
||||||
uint8_t *wallpaperBuf; // pixel data (screen width * height * bpp/8)
|
uint8_t *wallpaperBuf; // pixel data (screen width * height * bpp/8)
|
||||||
int32_t wallpaperPitch; // bytes per row
|
int32_t wallpaperPitch; // bytes per row
|
||||||
char wallpaperPath[260]; // source image path (empty = none)
|
char wallpaperPath[260]; // source image path (empty = none)
|
||||||
|
WallpaperModeE wallpaperMode; // stretch, tile, or center
|
||||||
} AppContextT;
|
} AppContextT;
|
||||||
|
|
||||||
// Initialize the entire GUI stack: video mode, input devices, font,
|
// Initialize the entire GUI stack: video mode, input devices, font,
|
||||||
|
|
@ -154,14 +155,21 @@ bool dvxSaveTheme(const AppContextT *ctx, const char *filename);
|
||||||
// Return the INI key name for a color ID (e.g. "desktop", "windowFace").
|
// Return the INI key name for a color ID (e.g. "desktop", "windowFace").
|
||||||
const char *dvxColorName(ColorIdE id);
|
const char *dvxColorName(ColorIdE id);
|
||||||
|
|
||||||
|
// Return a human-readable display label (e.g. "Desktop", "Cursor Color").
|
||||||
|
const char *dvxColorLabel(ColorIdE id);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Wallpaper
|
// Wallpaper
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Load and apply a wallpaper image (stretched to screen). Pass NULL
|
// Load and apply a wallpaper image. Uses the current wallpaperMode
|
||||||
// to clear the wallpaper and revert to the solid desktop color.
|
// (stretch/tile/center). Pass NULL to clear the wallpaper.
|
||||||
bool dvxSetWallpaper(AppContextT *ctx, const char *path);
|
bool dvxSetWallpaper(AppContextT *ctx, const char *path);
|
||||||
|
|
||||||
|
// Change the wallpaper display mode and re-render the buffer.
|
||||||
|
// Has no effect if no wallpaper is loaded.
|
||||||
|
void dvxSetWallpaperMode(AppContextT *ctx, WallpaperModeE mode);
|
||||||
|
|
||||||
// Return the list of available video modes (enumerated at init).
|
// Return the list of available video modes (enumerated at init).
|
||||||
// count receives the number of entries.
|
// count receives the number of entries.
|
||||||
const VideoModeInfoT *dvxGetVideoModes(const AppContextT *ctx, int32_t *count);
|
const VideoModeInfoT *dvxGetVideoModes(const AppContextT *ctx, int32_t *count);
|
||||||
|
|
@ -255,7 +263,7 @@ void dvxFreeAccelTable(AccelTableT *table);
|
||||||
// for menu item IDs so the same handler works for both menus and hotkeys.
|
// for menu item IDs so the same handler works for both menus and hotkeys.
|
||||||
void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmdId);
|
void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmdId);
|
||||||
|
|
||||||
// Window arrangement functions — operate on all visible, non-minimized
|
// Window arrangement functions -- operate on all visible, non-minimized
|
||||||
// windows. These mirror the classic Windows 3.x "Window" menu commands.
|
// windows. These mirror the classic Windows 3.x "Window" menu commands.
|
||||||
|
|
||||||
// Cascade: offset each window diagonally by the title bar height.
|
// Cascade: offset each window diagonally by the title bar height.
|
||||||
|
|
@ -285,7 +293,7 @@ void dvxFreeImage(uint8_t *data);
|
||||||
int32_t dvxSaveImage(const AppContextT *ctx, const uint8_t *data, int32_t w, int32_t h, int32_t pitch, const char *path);
|
int32_t dvxSaveImage(const AppContextT *ctx, const uint8_t *data, int32_t w, int32_t h, int32_t pitch, const char *path);
|
||||||
|
|
||||||
// Copy text to the process-wide clipboard buffer. The clipboard is a
|
// Copy text to the process-wide clipboard buffer. The clipboard is a
|
||||||
// simple static buffer (not inter-process) — adequate for copy/paste
|
// simple static buffer (not inter-process) -- adequate for copy/paste
|
||||||
// within the DVX environment on single-tasking DOS.
|
// within the DVX environment on single-tasking DOS.
|
||||||
void dvxClipboardCopy(const char *text, int32_t len);
|
void dvxClipboardCopy(const char *text, int32_t len);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// dvx_comp.c — Layer 3: Dirty rectangle compositor for DVX GUI (optimized)
|
// dvx_comp.c -- Layer 3: Dirty rectangle compositor for DVX GUI (optimized)
|
||||||
//
|
//
|
||||||
// This layer implements dirty rectangle tracking and merging. The compositor
|
// This layer implements dirty rectangle tracking and merging. The compositor
|
||||||
// avoids full-screen redraws, which would be prohibitively expensive on the
|
// avoids full-screen redraws, which would be prohibitively expensive on the
|
||||||
// target 486/Pentium hardware over ISA bus VESA LFB. A full 640x480x16bpp
|
// target 486/Pentium hardware over ISA bus VESA LFB. A full 640x480x16bpp
|
||||||
// framebuffer is ~600KB — at ISA's ~8MB/s theoretical peak, a blind full
|
// framebuffer is ~600KB -- at ISA's ~8MB/s theoretical peak, a blind full
|
||||||
// flush costs ~75ms (>1 frame at 60Hz). By tracking which rectangles have
|
// flush costs ~75ms (>1 frame at 60Hz). By tracking which rectangles have
|
||||||
// actually changed and flushing only those regions from the system RAM
|
// actually changed and flushing only those regions from the system RAM
|
||||||
// backbuffer to the LFB, the bandwidth consumed per frame scales with the
|
// backbuffer to the LFB, the bandwidth consumed per frame scales with the
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
//
|
//
|
||||||
// The compositing loop lives in dvxApp.c (compositeAndFlush). For each dirty
|
// The compositing loop lives in dvxApp.c (compositeAndFlush). For each dirty
|
||||||
// rect, it repaints the desktop, then walks the window stack bottom-to-top
|
// rect, it repaints the desktop, then walks the window stack bottom-to-top
|
||||||
// painting chrome, content, scrollbars, popup menus, and the cursor — all
|
// painting chrome, content, scrollbars, popup menus, and the cursor -- all
|
||||||
// clipped to the dirty rect. Only then is the dirty rect flushed to the LFB.
|
// clipped to the dirty rect. Only then is the dirty rect flushed to the LFB.
|
||||||
// This means each pixel in a dirty region is written to system RAM potentially
|
// This means each pixel in a dirty region is written to system RAM potentially
|
||||||
// multiple times (painter's algorithm), but the expensive LFB write happens
|
// multiple times (painter's algorithm), but the expensive LFB write happens
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
// overlap. A small gap tolerance absorbs jitter from mouse movement and
|
// overlap. A small gap tolerance absorbs jitter from mouse movement and
|
||||||
// closely-spaced UI invalidations (e.g. title bar + content during a drag)
|
// closely-spaced UI invalidations (e.g. title bar + content during a drag)
|
||||||
// without bloating merged rects excessively. The value 4 was chosen to match
|
// without bloating merged rects excessively. The value 4 was chosen to match
|
||||||
// the chrome border width — adjacent chrome/content invalidations merge
|
// the chrome border width -- adjacent chrome/content invalidations merge
|
||||||
// naturally.
|
// naturally.
|
||||||
#define DIRTY_MERGE_GAP 4
|
#define DIRTY_MERGE_GAP 4
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ static inline void rectUnion(const RectT *a, const RectT *b, RectT *result);
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Appends a dirty rect to the list. Uses a fixed-size array (MAX_DIRTY_RECTS
|
// Appends a dirty rect to the list. Uses a fixed-size array (MAX_DIRTY_RECTS
|
||||||
// = 128) rather than a dynamic allocation — this is called on every UI
|
// = 128) rather than a dynamic allocation -- this is called on every UI
|
||||||
// mutation (drag, repaint, focus change) so allocation overhead must be zero.
|
// mutation (drag, repaint, focus change) so allocation overhead must be zero.
|
||||||
//
|
//
|
||||||
// When the list fills up, an eager merge pass tries to consolidate rects.
|
// When the list fills up, an eager merge pass tries to consolidate rects.
|
||||||
|
|
@ -54,7 +54,7 @@ static inline void rectUnion(const RectT *a, const RectT *b, RectT *result);
|
||||||
// GUI mutations tend to cluster spatially.
|
// GUI mutations tend to cluster spatially.
|
||||||
|
|
||||||
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
|
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
|
||||||
// Branch hint: degenerate rects are rare — callers usually validate first
|
// Branch hint: degenerate rects are rare -- callers usually validate first
|
||||||
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
|
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +64,7 @@ void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
|
||||||
dirtyListMerge(dl);
|
dirtyListMerge(dl);
|
||||||
|
|
||||||
if (dl->count >= MAX_DIRTY_RECTS) {
|
if (dl->count >= MAX_DIRTY_RECTS) {
|
||||||
// Still full — collapse the entire list plus the new rect into one
|
// Still full -- collapse the entire list plus the new rect into one
|
||||||
// bounding box. This is a last resort; it means the next flush will
|
// bounding box. This is a last resort; it means the next flush will
|
||||||
// repaint a potentially large region, but at least we won't lose
|
// repaint a potentially large region, but at least we won't lose
|
||||||
// dirty information or crash.
|
// dirty information or crash.
|
||||||
|
|
@ -123,7 +123,7 @@ void dirtyListInit(DirtyListT *dl) {
|
||||||
// Algorithm: O(N^2) pairwise sweep with bounded restarts. For each rect i,
|
// Algorithm: O(N^2) pairwise sweep with bounded restarts. For each rect i,
|
||||||
// scan all rects j>i and merge any that overlap or are within DIRTY_MERGE_GAP
|
// scan all rects j>i and merge any that overlap or are within DIRTY_MERGE_GAP
|
||||||
// pixels. When a merge happens, rect i grows and may now overlap rects that
|
// pixels. When a merge happens, rect i grows and may now overlap rects that
|
||||||
// it previously missed, so the inner scan restarts — but restarts are capped
|
// it previously missed, so the inner scan restarts -- but restarts are capped
|
||||||
// at 3 per slot to prevent O(N^3) cascading in pathological layouts (e.g.
|
// at 3 per slot to prevent O(N^3) cascading in pathological layouts (e.g.
|
||||||
// a diagonal scatter of tiny rects). The cap of 3 was chosen empirically:
|
// a diagonal scatter of tiny rects). The cap of 3 was chosen empirically:
|
||||||
// typical GUI operations produce clustered invalidations that converge in
|
// typical GUI operations produce clustered invalidations that converge in
|
||||||
|
|
@ -172,7 +172,7 @@ void dirtyListMerge(DirtyListT *dl) {
|
||||||
// on 486+ to move aligned 32-bit words, maximizing bus utilization.
|
// on 486+ to move aligned 32-bit words, maximizing bus utilization.
|
||||||
//
|
//
|
||||||
// Crucially, we flush per dirty rect AFTER all painting for that rect is
|
// Crucially, we flush per dirty rect AFTER all painting for that rect is
|
||||||
// complete. This avoids visible tearing — the LFB is never in a half-painted
|
// complete. This avoids visible tearing -- the LFB is never in a half-painted
|
||||||
// state for any given region.
|
// state for any given region.
|
||||||
|
|
||||||
void flushRect(DisplayT *d, const RectT *r) {
|
void flushRect(DisplayT *d, const RectT *r) {
|
||||||
|
|
@ -187,7 +187,7 @@ void flushRect(DisplayT *d, const RectT *r) {
|
||||||
// Used heavily in the compositing loop to test whether a window overlaps
|
// Used heavily in the compositing loop to test whether a window overlaps
|
||||||
// a dirty rect before painting it. The branch hint marks the non-overlapping
|
// a dirty rect before painting it. The branch hint marks the non-overlapping
|
||||||
// case as unlikely because the compositing loop already does a coarse AABB
|
// case as unlikely because the compositing loop already does a coarse AABB
|
||||||
// check before calling this — when we get here, intersection is expected.
|
// check before calling this -- when we get here, intersection is expected.
|
||||||
// The min/max formulation avoids branches in the hot path.
|
// The min/max formulation avoids branches in the hot path.
|
||||||
|
|
||||||
bool rectIntersect(const RectT *a, const RectT *b, RectT *result) {
|
bool rectIntersect(const RectT *a, const RectT *b, RectT *result) {
|
||||||
|
|
@ -246,7 +246,7 @@ static inline bool rectsOverlapOrAdjacent(const RectT *a, const RectT *b, int32_
|
||||||
//
|
//
|
||||||
// Axis-aligned bounding box of two rects. Supports in-place operation
|
// Axis-aligned bounding box of two rects. Supports in-place operation
|
||||||
// (result == a) for the merge loop. Note that this may grow the rect
|
// (result == a) for the merge loop. Note that this may grow the rect
|
||||||
// substantially if the two inputs are far apart — this is the inherent
|
// substantially if the two inputs are far apart -- this is the inherent
|
||||||
// cost of bounding-box merging vs. maintaining a true region (list of
|
// cost of bounding-box merging vs. maintaining a true region (list of
|
||||||
// non-overlapping rects). Bounding-box was chosen because the merge
|
// non-overlapping rects). Bounding-box was chosen because the merge
|
||||||
// list is bounded to 128 entries and the extra repaint cost of a few
|
// list is bounded to 128 entries and the extra repaint cost of a few
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// dvx_comp.h — Layer 3: Dirty rectangle compositor for DVX GUI
|
// dvx_comp.h -- Layer 3: Dirty rectangle compositor for DVX GUI
|
||||||
//
|
//
|
||||||
// The compositor tracks which screen regions have changed and ensures
|
// The compositor tracks which screen regions have changed and ensures
|
||||||
// only those regions are redrawn and flushed to video memory. This is
|
// only those regions are redrawn and flushed to video memory. This is
|
||||||
// the key to acceptable frame rates on 486/Pentium hardware — a full
|
// the key to acceptable frame rates on 486/Pentium hardware -- a full
|
||||||
// 640x480x32bpp frame is 1.2 MB, but a typical dirty region during
|
// 640x480x32bpp frame is 1.2 MB, but a typical dirty region during
|
||||||
// normal interaction (e.g. typing in a text field) might be a few KB.
|
// normal interaction (e.g. typing in a text field) might be a few KB.
|
||||||
//
|
//
|
||||||
|
|
@ -40,7 +40,7 @@ void dirtyListMerge(DirtyListT *dl);
|
||||||
void dirtyListClear(DirtyListT *dl);
|
void dirtyListClear(DirtyListT *dl);
|
||||||
|
|
||||||
// Copy a rectangle from the system RAM backbuffer to the LFB (video memory).
|
// Copy a rectangle from the system RAM backbuffer to the LFB (video memory).
|
||||||
// This is the final step — the only place where the real framebuffer is
|
// This is the final step -- the only place where the real framebuffer is
|
||||||
// written. Uses platform-specific fast copy (rep movsd on DOS) for each
|
// written. Uses platform-specific fast copy (rep movsd on DOS) for each
|
||||||
// scanline of the rect.
|
// scanline of the rect.
|
||||||
void flushRect(DisplayT *d, const RectT *r);
|
void flushRect(DisplayT *d, const RectT *r);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// dvxCursor.h — Embedded mouse cursor bitmaps for DVX GUI
|
// dvxCursor.h -- Embedded mouse cursor bitmaps for DVX GUI
|
||||||
//
|
//
|
||||||
// All cursor shapes are compiled in as static const data — no external
|
// All cursor shapes are compiled in as static const data -- no external
|
||||||
// cursor files to load. This is intentional: the cursors are needed
|
// cursor files to load. This is intentional: the cursors are needed
|
||||||
// before any file I/O infrastructure is ready, and embedding them avoids
|
// before any file I/O infrastructure is ready, and embedding them avoids
|
||||||
// a runtime dependency on a file path.
|
// a runtime dependency on a file path.
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
// XOR data: 0 = black, 1 = white (where AND = 0)
|
// XOR data: 0 = black, 1 = white (where AND = 0)
|
||||||
//
|
//
|
||||||
// The cursor is a left-pointing arrow with black outline and white fill.
|
// The cursor is a left-pointing arrow with black outline and white fill.
|
||||||
// Hot spot is at (0, 0) — top-left corner of the arrow tip.
|
// Hot spot is at (0, 0) -- top-left corner of the arrow tip.
|
||||||
|
|
||||||
static const uint16_t cursorArrowAnd[16] = {
|
static const uint16_t cursorArrowAnd[16] = {
|
||||||
0x3FFF, // 0011111111111111 row 0
|
0x3FFF, // 0011111111111111 row 0
|
||||||
|
|
@ -91,42 +91,57 @@ static const uint16_t cursorArrowXor[16] = {
|
||||||
// .X..XX..X.
|
// .X..XX..X.
|
||||||
// ..XX..XX..
|
// ..XX..XX..
|
||||||
|
|
||||||
|
// Left-right resize cursor -- exact 90 deg rotation of the vertical cursor.
|
||||||
|
// Left arrow tip at col 2, right arrow tip at col 13, 1px stem at row 7.
|
||||||
|
// Bit 15 = leftmost pixel (col 0), bit 0 = rightmost (col 15).
|
||||||
|
//
|
||||||
|
// XOR (1=white): Outline (AND=0 border):
|
||||||
|
// row 4: ....X......X.... row 3: ...XXX....XXX...
|
||||||
|
// row 5: ...XX......XX... row 4: ..XXXX....XXXX..
|
||||||
|
// row 6: ..XXX......XXX.. row 5: .XXXXX....XXXXX.
|
||||||
|
// row 7: .XXXXXXXXXXXX.. row 6: XXXXXXXXXXXXXX.. (1px border around all)
|
||||||
|
// row 8: ..XXX......XXX.. row 7: XXXXXXXXXXXXXXXX
|
||||||
|
// row 9: ...XX......XX... row 8: XXXXXXXXXXXXXX..
|
||||||
|
// row 10: ....X......X.... row 9: .XXXXX....XXXXX.
|
||||||
|
// row 10: ..XXXX....XXXX..
|
||||||
|
// row 11: ...XXX....XXX...
|
||||||
|
|
||||||
static const uint16_t cursorResizeHAnd[16] = {
|
static const uint16_t cursorResizeHAnd[16] = {
|
||||||
0xFFFF, // row 0 (transparent)
|
0xFFFF, // 1111111111111111 row 0
|
||||||
0xFFFF, // row 1
|
0xFFFF, // 1111111111111111 row 1
|
||||||
0xFFFF, // row 2
|
0xFFFF, // 1111111111111111 row 2
|
||||||
0xF7EF, // 1111011111101111 row 3 arrows start
|
0xF18F, // 1111000110001111 row 3 outline top
|
||||||
0xE3C7, // 1110001111000111 row 4
|
0xE187, // 1110000110000111 row 4
|
||||||
0xC1A3, // 1100000110100011 row 5
|
0xC183, // 1100000110000011 row 5
|
||||||
0x8081, // 1000000010000001 row 6
|
0x8001, // 1000000000000001 row 6
|
||||||
0x0000, // 0000000000000000 row 7 center row
|
0x8001, // 1000000000000001 row 7 center
|
||||||
0x8081, // 1000000010000001 row 8
|
0x8001, // 1000000000000001 row 8
|
||||||
0xC1A3, // 1100000110100011 row 9
|
0xC183, // 1100000110000011 row 9
|
||||||
0xE3C7, // 1110001111000111 row 10
|
0xE187, // 1110000110000111 row 10
|
||||||
0xF7EF, // 1111011111101111 row 11
|
0xF18F, // 1111000110001111 row 11 outline bottom
|
||||||
0xFFFF, // row 12
|
0xFFFF, // 1111111111111111 row 12
|
||||||
0xFFFF, // row 13
|
0xFFFF, // 1111111111111111 row 13
|
||||||
0xFFFF, // row 14
|
0xFFFF, // 1111111111111111 row 14
|
||||||
0xFFFF // row 15
|
0xFFFF // 1111111111111111 row 15
|
||||||
};
|
};
|
||||||
|
|
||||||
static const uint16_t cursorResizeHXor[16] = {
|
static const uint16_t cursorResizeHXor[16] = {
|
||||||
0x0000, // row 0
|
0x0000, // 0000000000000000 row 0
|
||||||
0x0000, // row 1
|
0x0000, // 0000000000000000 row 1
|
||||||
0x0000, // row 2
|
0x0000, // 0000000000000000 row 2
|
||||||
0x0000, // row 3
|
0x0000, // 0000000000000000 row 3
|
||||||
0x0810, // 0000100000010000 row 4
|
0x0420, // 0000010000100000 row 4 arrow tips
|
||||||
0x1C38, // 0001110000111000 row 5
|
0x0C30, // 0000110000110000 row 5
|
||||||
0x3E7C, // 0011111001111100 row 6
|
0x1C38, // 0001110000111000 row 6
|
||||||
0x7FFE, // 0111111111111110 row 7
|
0x3FFC, // 0011111111111100 row 7 bases + stem
|
||||||
0x3E7C, // 0011111001111100 row 8
|
0x1C38, // 0001110000111000 row 8
|
||||||
0x1C38, // 0001110000111000 row 9
|
0x0C30, // 0000110000110000 row 9
|
||||||
0x0810, // 0000100000010000 row 10
|
0x0420, // 0000010000100000 row 10 arrow tips
|
||||||
0x0000, // row 11
|
0x0000, // 0000000000000000 row 11
|
||||||
0x0000, // row 12
|
0x0000, // 0000000000000000 row 12
|
||||||
0x0000, // row 13
|
0x0000, // 0000000000000000 row 13
|
||||||
0x0000, // row 14
|
0x0000, // 0000000000000000 row 14
|
||||||
0x0000 // row 15
|
0x0000 // 0000000000000000 row 15
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -302,7 +317,7 @@ static const CursorT dvxCursors[CURSOR_COUNT] = {
|
||||||
{ 16, 16, 7, 7, cursorResizeDiagNESWAnd, cursorResizeDiagNESWXor }, // CURSOR_RESIZE_DIAG_NESW
|
{ 16, 16, 7, 7, cursorResizeDiagNESWAnd, cursorResizeDiagNESWXor }, // CURSOR_RESIZE_DIAG_NESW
|
||||||
};
|
};
|
||||||
|
|
||||||
// Legacy alias — kept for backward compatibility with code that predates
|
// Legacy alias -- kept for backward compatibility with code that predates
|
||||||
// the multi-cursor support.
|
// the multi-cursor support.
|
||||||
static const CursorT dvxCursor = { 16, 16, 0, 0, cursorArrowAnd, cursorArrowXor };
|
static const CursorT dvxCursor = { 16, 16, 0, 0, cursorArrowAnd, cursorArrowXor };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
// dvxDialog.c — Modal dialogs for DVX GUI
|
// dvxDialog.c -- Modal dialogs for DVX GUI
|
||||||
//
|
//
|
||||||
// Provides two standard dialog types: message boxes and file dialogs.
|
// Provides two standard dialog types: message boxes and file dialogs.
|
||||||
// Both use the nested-event-loop modal pattern: the dialog creates a
|
// Both use the nested-event-loop modal pattern: the dialog creates a
|
||||||
// window, sets it as the AppContext's modalWindow, then runs dvxUpdate
|
// window, sets it as the AppContext's modalWindow, then runs dvxUpdate
|
||||||
// in a tight loop until the user dismisses the dialog. This blocks the
|
// in a tight loop until the user dismisses the dialog. This blocks the
|
||||||
// caller's code flow, which is the simplest possible modal API — the
|
// caller's code flow, which is the simplest possible modal API -- the
|
||||||
// caller gets the result as a return value, no callbacks needed.
|
// caller gets the result as a return value, no callbacks needed.
|
||||||
//
|
//
|
||||||
// The nested loop approach works because dvxUpdate is re-entrant: it
|
// The nested loop approach works because dvxUpdate is re-entrant: it
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
//
|
//
|
||||||
// State for each dialog type is stored in a single static struct (sMsgBox,
|
// State for each dialog type is stored in a single static struct (sMsgBox,
|
||||||
// sFd) rather than on the heap. This means only one dialog of each type
|
// sFd) rather than on the heap. This means only one dialog of each type
|
||||||
// can be active at a time, but that's fine — you never need two file
|
// can be active at a time, but that's fine -- you never need two file
|
||||||
// dialogs simultaneously. The static approach avoids malloc/free and
|
// dialogs simultaneously. The static approach avoids malloc/free and
|
||||||
// keeps the state accessible to callback functions without threading
|
// keeps the state accessible to callback functions without threading
|
||||||
// context pointers through every widget callback.
|
// context pointers through every widget callback.
|
||||||
|
|
@ -110,7 +110,7 @@ static MsgBoxStateT sMsgBox;
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// drawIconGlyph — draw a simple icon shape
|
// drawIconGlyph -- draw a simple icon shape
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Procedurally draws message box icons using only integer math:
|
// Procedurally draws message box icons using only integer math:
|
||||||
|
|
@ -122,7 +122,7 @@ static MsgBoxStateT sMsgBox;
|
||||||
// Circles use the integer distance-squared test: for each pixel, compute
|
// Circles use the integer distance-squared test: for each pixel, compute
|
||||||
// dx*dx + dy*dy and check if it falls between INNER_R2 and OUTER_R2
|
// dx*dx + dy*dy and check if it falls between INNER_R2 and OUTER_R2
|
||||||
// to draw a 2-pixel-wide ring. This is a brute-force O(n^2) approach
|
// to draw a 2-pixel-wide ring. This is a brute-force O(n^2) approach
|
||||||
// but n is only 24 pixels, so it's 576 iterations — trivial even on a 486.
|
// but n is only 24 pixels, so it's 576 iterations -- trivial even on a 486.
|
||||||
//
|
//
|
||||||
// The inner symbols (i, !, X, ?) are drawn with hardcoded rectFill calls
|
// The inner symbols (i, !, X, ?) are drawn with hardcoded rectFill calls
|
||||||
// at specific pixel offsets. This is less elegant than using the font
|
// at specific pixel offsets. This is less elegant than using the font
|
||||||
|
|
@ -222,7 +222,7 @@ static void drawIconGlyph(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y
|
||||||
//
|
//
|
||||||
// Button labels use '&' to mark accelerator keys (e.g., "&OK" makes
|
// Button labels use '&' to mark accelerator keys (e.g., "&OK" makes
|
||||||
// Alt+O activate the button). Button IDs are stored in widget->userData
|
// Alt+O activate the button). Button IDs are stored in widget->userData
|
||||||
// via intptr_t cast — a common pattern when you need to associate a
|
// via intptr_t cast -- a common pattern when you need to associate a
|
||||||
// small integer with a widget without allocating a separate struct.
|
// small integer with a widget without allocating a separate struct.
|
||||||
|
|
||||||
int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message, int32_t flags) {
|
int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message, int32_t flags) {
|
||||||
|
|
@ -381,12 +381,12 @@ int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message,
|
||||||
|
|
||||||
// Save previous modal so stacked modals work correctly. This happens
|
// Save previous modal so stacked modals work correctly. This happens
|
||||||
// when a message box opens from within a file dialog (e.g., overwrite
|
// when a message box opens from within a file dialog (e.g., overwrite
|
||||||
// confirmation) — the file dialog's modal is pushed and restored
|
// confirmation) -- the file dialog's modal is pushed and restored
|
||||||
// when the message box closes.
|
// when the message box closes.
|
||||||
WindowT *prevModal = ctx->modalWindow;
|
WindowT *prevModal = ctx->modalWindow;
|
||||||
ctx->modalWindow = win;
|
ctx->modalWindow = win;
|
||||||
|
|
||||||
// Nested event loop — blocks here until user clicks a button or closes.
|
// Nested event loop -- blocks here until user clicks a button or closes.
|
||||||
// dvxUpdate handles all input/rendering; sMsgBox.done is set by the
|
// dvxUpdate handles all input/rendering; sMsgBox.done is set by the
|
||||||
// button onClick callback or the window close callback.
|
// button onClick callback or the window close callback.
|
||||||
while (!sMsgBox.done && ctx->running) {
|
while (!sMsgBox.done && ctx->running) {
|
||||||
|
|
@ -422,7 +422,7 @@ static void onMsgBoxClose(WindowT *win) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// onMsgBoxPaint — custom paint: background + text/icon + widgets
|
// onMsgBoxPaint -- custom paint: background + text/icon + widgets
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// The message box uses a custom onPaint callback rather than pure widgets
|
// The message box uses a custom onPaint callback rather than pure widgets
|
||||||
|
|
@ -454,7 +454,7 @@ static void onMsgBoxPaint(WindowT *win, RectT *dirtyArea) {
|
||||||
cd.clipW = win->contentW;
|
cd.clipW = win->contentW;
|
||||||
cd.clipH = win->contentH;
|
cd.clipH = win->contentH;
|
||||||
|
|
||||||
// Fill background with window face color (not content bg — dialog style)
|
// Fill background with window face color (not content bg -- dialog style)
|
||||||
rectFill(&cd, &ctx->blitOps, 0, 0, win->contentW, win->contentH, ctx->colors.windowFace);
|
rectFill(&cd, &ctx->blitOps, 0, 0, win->contentW, win->contentH, ctx->colors.windowFace);
|
||||||
|
|
||||||
// Draw word-wrapped message text
|
// Draw word-wrapped message text
|
||||||
|
|
@ -488,14 +488,14 @@ static void onMsgBoxPaint(WindowT *win, RectT *dirtyArea) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// wordWrapDraw — draw word-wrapped text
|
// wordWrapDraw -- draw word-wrapped text
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Simple greedy word-wrap: fill each line with as many characters as fit
|
// Simple greedy word-wrap: fill each line with as many characters as fit
|
||||||
// within maxW, breaking at the last space if the line overflows. If a
|
// within maxW, breaking at the last space if the line overflows. If a
|
||||||
// single word is longer than maxW, it gets its own line (may be clipped).
|
// single word is longer than maxW, it gets its own line (may be clipped).
|
||||||
// Explicit newlines are honored. This is a fixed-width font, so "max
|
// Explicit newlines are honored. This is a fixed-width font, so "max
|
||||||
// chars per line" is just maxW / charWidth — no per-character width
|
// chars per line" is just maxW / charWidth -- no per-character width
|
||||||
// accumulation needed.
|
// accumulation needed.
|
||||||
|
|
||||||
static void wordWrapDraw(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t maxW, uint32_t fg, uint32_t bg) {
|
static void wordWrapDraw(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t maxW, uint32_t fg, uint32_t bg) {
|
||||||
|
|
@ -550,12 +550,12 @@ static void wordWrapDraw(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fo
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// wordWrapHeight — compute height of word-wrapped text
|
// wordWrapHeight -- compute height of word-wrapped text
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Duplicates the word-wrap logic from wordWrapDraw but only counts lines
|
// Duplicates the word-wrap logic from wordWrapDraw but only counts lines
|
||||||
// instead of drawing. This is needed to compute the dialog height before
|
// instead of drawing. This is needed to compute the dialog height before
|
||||||
// creating the window. The duplication is intentional — combining them
|
// creating the window. The duplication is intentional -- combining them
|
||||||
// into a single function with a "just measure" flag would add branching
|
// into a single function with a "just measure" flag would add branching
|
||||||
// to the draw path and make both harder to read.
|
// to the draw path and make both harder to read.
|
||||||
|
|
||||||
|
|
@ -650,7 +650,7 @@ static FileDialogStateT sFd;
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdFilterMatch — check if filename matches a glob pattern
|
// fdFilterMatch -- check if filename matches a glob pattern
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Supports only the most common DOS file filter patterns: "*.*", "*",
|
// Supports only the most common DOS file filter patterns: "*.*", "*",
|
||||||
|
|
@ -732,7 +732,7 @@ static bool fdFilterMatch(const char *name, const char *pattern) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdFreeEntries — free allocated entry name strings
|
// fdFreeEntries -- free allocated entry name strings
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void fdFreeEntries(void) {
|
static void fdFreeEntries(void) {
|
||||||
|
|
@ -746,7 +746,7 @@ static void fdFreeEntries(void) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdEntryCompare — sort: dirs first, then alphabetical
|
// fdEntryCompare -- sort: dirs first, then alphabetical
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Sort comparator for the indirect sort in fdLoadDir. Uses an index
|
// Sort comparator for the indirect sort in fdLoadDir. Uses an index
|
||||||
|
|
@ -769,7 +769,7 @@ static int fdEntryCompare(const void *a, const void *b) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdLoadDir — read directory contents into state
|
// fdLoadDir -- read directory contents into state
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Reads the current directory, applies the active file filter, sorts
|
// Reads the current directory, applies the active file filter, sorts
|
||||||
|
|
@ -872,7 +872,7 @@ static void fdLoadDir(void) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdNavigate — change to a new directory
|
// fdNavigate -- change to a new directory
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Handles both absolute and relative paths. Relative paths are resolved
|
// Handles both absolute and relative paths. Relative paths are resolved
|
||||||
|
|
@ -918,7 +918,7 @@ static void fdNavigate(const char *path) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdValidateFilename — check filename is valid for target OS
|
// fdValidateFilename -- check filename is valid for target OS
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static bool fdValidateFilename(const char *name) {
|
static bool fdValidateFilename(const char *name) {
|
||||||
|
|
@ -934,7 +934,7 @@ static bool fdValidateFilename(const char *name) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdAcceptFile — confirm and accept the selected filename
|
// fdAcceptFile -- confirm and accept the selected filename
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Validates the filename (platform-specific rules), then checks for
|
// Validates the filename (platform-specific rules), then checks for
|
||||||
|
|
@ -979,7 +979,7 @@ static bool fdAcceptFile(const char *name) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdOnListClick — file list selection changed
|
// fdOnListClick -- file list selection changed
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void fdOnListClick(WidgetT *w) {
|
static void fdOnListClick(WidgetT *w) {
|
||||||
|
|
@ -996,7 +996,7 @@ static void fdOnListClick(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdOnListDblClick — file list double-click
|
// fdOnListDblClick -- file list double-click
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Double-click on a directory navigates into it. Double-click on a file
|
// Double-click on a directory navigates into it. Double-click on a file
|
||||||
|
|
@ -1031,7 +1031,7 @@ static void fdOnListDblClick(WidgetT *w) {
|
||||||
fdNavigate(dirName);
|
fdNavigate(dirName);
|
||||||
wgtSetText(sFd.nameInput, "");
|
wgtSetText(sFd.nameInput, "");
|
||||||
} else {
|
} else {
|
||||||
// Double-click on file — accept it (with confirmation if needed)
|
// Double-click on file -- accept it (with confirmation if needed)
|
||||||
wgtSetText(sFd.nameInput, sFd.entryNames[sel]);
|
wgtSetText(sFd.nameInput, sFd.entryNames[sel]);
|
||||||
fdAcceptFile(sFd.entryNames[sel]);
|
fdAcceptFile(sFd.entryNames[sel]);
|
||||||
}
|
}
|
||||||
|
|
@ -1039,7 +1039,7 @@ static void fdOnListDblClick(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdOnOk — OK button clicked
|
// fdOnOk -- OK button clicked
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// OK has three behaviors depending on context:
|
// OK has three behaviors depending on context:
|
||||||
|
|
@ -1055,7 +1055,7 @@ static void fdOnOk(WidgetT *w) {
|
||||||
|
|
||||||
// If the filename input is empty and a directory is selected in the
|
// If the filename input is empty and a directory is selected in the
|
||||||
// list, navigate into it. But if the user has typed a filename,
|
// list, navigate into it. But if the user has typed a filename,
|
||||||
// always accept it — don't let the listbox selection override.
|
// always accept it -- don't let the listbox selection override.
|
||||||
if (!name || name[0] == '\0') {
|
if (!name || name[0] == '\0') {
|
||||||
int32_t sel = wgtListBoxGetSelected(sFd.fileList);
|
int32_t sel = wgtListBoxGetSelected(sFd.fileList);
|
||||||
|
|
||||||
|
|
@ -1105,7 +1105,7 @@ static void fdOnOk(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdOnCancel — Cancel button clicked
|
// fdOnCancel -- Cancel button clicked
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void fdOnCancel(WidgetT *w) {
|
static void fdOnCancel(WidgetT *w) {
|
||||||
|
|
@ -1116,7 +1116,7 @@ static void fdOnCancel(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdOnClose — window close button
|
// fdOnClose -- window close button
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void fdOnClose(WindowT *win) {
|
static void fdOnClose(WindowT *win) {
|
||||||
|
|
@ -1127,7 +1127,7 @@ static void fdOnClose(WindowT *win) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdOnFilterChange — filter dropdown changed
|
// fdOnFilterChange -- filter dropdown changed
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void fdOnFilterChange(WidgetT *w) {
|
static void fdOnFilterChange(WidgetT *w) {
|
||||||
|
|
@ -1137,7 +1137,7 @@ static void fdOnFilterChange(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// fdOnPathSubmit — enter pressed in path input
|
// fdOnPathSubmit -- enter pressed in path input
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void fdOnPathSubmit(WidgetT *w) {
|
static void fdOnPathSubmit(WidgetT *w) {
|
||||||
|
|
@ -1223,7 +1223,7 @@ bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const cha
|
||||||
wgtLabel(filterRow, "F&ilter:");
|
wgtLabel(filterRow, "F&ilter:");
|
||||||
sFd.filterDd = wgtDropdown(filterRow);
|
sFd.filterDd = wgtDropdown(filterRow);
|
||||||
|
|
||||||
// Build filter label array (static — lives for dialog lifetime)
|
// Build filter label array (static -- lives for dialog lifetime)
|
||||||
static const char *filterLabels[16];
|
static const char *filterLabels[16];
|
||||||
int32_t fc = filterCount < 16 ? filterCount : 16;
|
int32_t fc = filterCount < 16 ? filterCount : 16;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvxDialog.h — Modal dialogs for DVX GUI
|
// dvxDialog.h -- Modal dialogs for DVX GUI
|
||||||
//
|
//
|
||||||
// Provides pre-built modal dialog boxes (message box, file dialog) that
|
// Provides pre-built modal dialog boxes (message box, file dialog) that
|
||||||
// block the caller and run their own event loop via dvxUpdate() until the
|
// block the caller and run their own event loop via dvxUpdate() until the
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvx_draw.c — Layer 2: Drawing primitives for DVX GUI (optimized)
|
// dvx_draw.c -- Layer 2: Drawing primitives for DVX GUI (optimized)
|
||||||
//
|
//
|
||||||
// This is the second layer of the DVX compositor stack, sitting on top
|
// This is the second layer of the DVX compositor stack, sitting on top
|
||||||
// of dvxVideo (layer 1) and below dvxComp (layer 3). It provides all
|
// of dvxVideo (layer 1) and below dvxComp (layer 3). It provides all
|
||||||
|
|
@ -39,7 +39,7 @@
|
||||||
// 3) For the most critical glyph paths (unclipped 32bpp and 16bpp),
|
// 3) For the most critical glyph paths (unclipped 32bpp and 16bpp),
|
||||||
// the pixel loops are fully unrolled into 8 direct array stores
|
// the pixel loops are fully unrolled into 8 direct array stores
|
||||||
// with literal bit masks. This eliminates the sGlyphBit[] table
|
// with literal bit masks. This eliminates the sGlyphBit[] table
|
||||||
// lookup, the loop counter, and the loop branch — saving ~3 cycles
|
// lookup, the loop counter, and the loop branch -- saving ~3 cycles
|
||||||
// per pixel on a 486. The clipped path falls back to the table.
|
// per pixel on a 486. The clipped path falls back to the table.
|
||||||
//
|
//
|
||||||
// Clip rectangle handling: All draw functions clip against
|
// Clip rectangle handling: All draw functions clip against
|
||||||
|
|
@ -94,7 +94,7 @@ char accelParse(const char *text) {
|
||||||
text++;
|
text++;
|
||||||
|
|
||||||
if (*text == '&') {
|
if (*text == '&') {
|
||||||
// Escaped && — literal &, not an accelerator
|
// Escaped && -- literal &, not an accelerator
|
||||||
text++;
|
text++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -136,9 +136,9 @@ char accelParse(const char *text) {
|
||||||
// clip region, w or h will be <= 0 and callers bail out.
|
// clip region, w or h will be <= 0 and callers bail out.
|
||||||
//
|
//
|
||||||
// Marked static inline because this is called on every rectFill,
|
// Marked static inline because this is called on every rectFill,
|
||||||
// rectCopy, and indirectly on every glyph — it must compile to
|
// rectCopy, and indirectly on every glyph -- it must compile to
|
||||||
// straight-line clamp instructions with zero call overhead.
|
// straight-line clamp instructions with zero call overhead.
|
||||||
// __builtin_expect(…, 0) marks clipping as unlikely; in the
|
// __builtin_expect(..., 0) marks clipping as unlikely; in the
|
||||||
// common case windows are fully within the clip rect and all
|
// common case windows are fully within the clip rect and all
|
||||||
// four branches fall through untaken. On Pentium this keeps the
|
// four branches fall through untaken. On Pentium this keeps the
|
||||||
// branch predictor happy (static not-taken prediction for forward
|
// branch predictor happy (static not-taken prediction for forward
|
||||||
|
|
@ -183,7 +183,7 @@ static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *
|
||||||
// The implementation has special-cased fast paths for bw==2 and bw==1
|
// The implementation has special-cased fast paths for bw==2 and bw==1
|
||||||
// that emit exact spans via rectFill rather than looping. This
|
// that emit exact spans via rectFill rather than looping. This
|
||||||
// matters because drawBevel is called for every window frame, button,
|
// matters because drawBevel is called for every window frame, button,
|
||||||
// menu, and scrollbar element on every repaint — the loop overhead
|
// menu, and scrollbar element on every repaint -- the loop overhead
|
||||||
// and extra rectFill calls in the general case add up. Each rectFill
|
// and extra rectFill calls in the general case add up. Each rectFill
|
||||||
// call already handles clipping internally, so the bevels clip
|
// call already handles clipping internally, so the bevels clip
|
||||||
// correctly even when a window is partially off-screen.
|
// correctly even when a window is partially off-screen.
|
||||||
|
|
@ -248,7 +248,7 @@ void drawBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w
|
||||||
// (bit 7 = leftmost pixel). This is the standard VGA/PC BIOS font
|
// (bit 7 = leftmost pixel). This is the standard VGA/PC BIOS font
|
||||||
// format. We use 8-pixel-wide glyphs exclusively because 8 bits fit
|
// format. We use 8-pixel-wide glyphs exclusively because 8 bits fit
|
||||||
// in one byte per scanline, making the inner loop a single byte load
|
// in one byte per scanline, making the inner loop a single byte load
|
||||||
// plus 8 bit tests — no multi-byte glyph row assembly needed.
|
// plus 8 bit tests -- no multi-byte glyph row assembly needed.
|
||||||
//
|
//
|
||||||
// The function has six specialized code paths (3 bpp x 2 modes),
|
// The function has six specialized code paths (3 bpp x 2 modes),
|
||||||
// chosen with if/else chains rather than function pointers. On 486
|
// chosen with if/else chains rather than function pointers. On 486
|
||||||
|
|
@ -319,7 +319,7 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
||||||
if (opaque) {
|
if (opaque) {
|
||||||
// Opaque mode: every pixel in the cell gets written (fg or bg).
|
// Opaque mode: every pixel in the cell gets written (fg or bg).
|
||||||
// The unclipped 32bpp and 16bpp paths use branchless ternary
|
// The unclipped 32bpp and 16bpp paths use branchless ternary
|
||||||
// stores — the compiler emits cmov or conditional-set sequences
|
// stores -- the compiler emits cmov or conditional-set sequences
|
||||||
// that avoid branch misprediction penalties. Each row is 8
|
// that avoid branch misprediction penalties. Each row is 8
|
||||||
// direct array stores with no loop, no table lookup.
|
// direct array stores with no loop, no table lookup.
|
||||||
if (unclipped && bpp == 4) {
|
if (unclipped && bpp == 4) {
|
||||||
|
|
@ -357,7 +357,7 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
||||||
// Clipped path or 8bpp: use spanFill for bg (leveraging
|
// Clipped path or 8bpp: use spanFill for bg (leveraging
|
||||||
// rep stosl), then iterate visible columns with sGlyphBit[]
|
// rep stosl), then iterate visible columns with sGlyphBit[]
|
||||||
// table for fg. 8bpp always takes this path because 8-bit
|
// table for fg. 8bpp always takes this path because 8-bit
|
||||||
// stores can't be branchlessly ternary'd as efficiently —
|
// stores can't be branchlessly ternary'd as efficiently --
|
||||||
// the compiler can't cmov into a byte store.
|
// the compiler can't cmov into a byte store.
|
||||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||||
int32_t py = y + row;
|
int32_t py = y + row;
|
||||||
|
|
@ -658,7 +658,7 @@ void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_
|
||||||
// expressed as a span fill (which writes uniform color).
|
// expressed as a span fill (which writes uniform color).
|
||||||
//
|
//
|
||||||
// The parity calculations on the bottom and right edges ensure the
|
// The parity calculations on the bottom and right edges ensure the
|
||||||
// dot pattern is visually continuous around corners — the starting
|
// dot pattern is visually continuous around corners -- the starting
|
||||||
// pixel of each edge is offset so dots don't double up or gap at
|
// pixel of each edge is offset so dots don't double up or gap at
|
||||||
// the corner where two edges meet.
|
// the corner where two edges meet.
|
||||||
//
|
//
|
||||||
|
|
@ -723,7 +723,7 @@ void drawFocusRect(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32
|
||||||
// drawHLine
|
// drawHLine
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Thin convenience wrapper — a horizontal line is just a 1px-tall rect.
|
// Thin convenience wrapper -- a horizontal line is just a 1px-tall rect.
|
||||||
// Delegates to rectFill which handles clipping and uses spanFill (rep
|
// Delegates to rectFill which handles clipping and uses spanFill (rep
|
||||||
// stosl) for the actual write.
|
// stosl) for the actual write.
|
||||||
|
|
||||||
|
|
@ -745,8 +745,8 @@ void drawHLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w
|
||||||
// spanFill and spanCopy are called per-scanline (not per-pixel),
|
// spanFill and spanCopy are called per-scanline (not per-pixel),
|
||||||
// so the indirect call overhead (~5 cycles on Pentium for the
|
// so the indirect call overhead (~5 cycles on Pentium for the
|
||||||
// mispredicted first call, then predicted afterward) is amortized
|
// mispredicted first call, then predicted afterward) is amortized
|
||||||
// over an entire row of pixels. The alternative — a switch inside
|
// over an entire row of pixels. The alternative -- a switch inside
|
||||||
// rectFill's inner loop — would branch every scanline for no gain.
|
// rectFill's inner loop -- would branch every scanline for no gain.
|
||||||
//
|
//
|
||||||
// The platform implementations (dvxPlatformDos.c) use inline asm:
|
// The platform implementations (dvxPlatformDos.c) use inline asm:
|
||||||
// spanFill8/16/32 -> rep stosl (fills 4 bytes per clock)
|
// spanFill8/16/32 -> rep stosl (fills 4 bytes per clock)
|
||||||
|
|
@ -799,7 +799,7 @@ void drawInit(BlitOpsT *ops, const DisplayT *d) {
|
||||||
// The colMask optimization pre-computes which bits in each row fall
|
// The colMask optimization pre-computes which bits in each row fall
|
||||||
// within the visible (clipped) columns. For fully transparent rows
|
// within the visible (clipped) columns. For fully transparent rows
|
||||||
// (all visible bits have andMask=1), the entire row is skipped with
|
// (all visible bits have andMask=1), the entire row is skipped with
|
||||||
// a single bitwise AND + compare — no per-pixel iteration needed.
|
// a single bitwise AND + compare -- no per-pixel iteration needed.
|
||||||
|
|
||||||
void drawMaskedBitmap(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *andMask, const uint16_t *xorData, uint32_t fgColor, uint32_t bgColor) {
|
void drawMaskedBitmap(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *andMask, const uint16_t *xorData, uint32_t fgColor, uint32_t bgColor) {
|
||||||
int32_t bpp = ops->bytesPerPixel;
|
int32_t bpp = ops->bytesPerPixel;
|
||||||
|
|
@ -893,7 +893,7 @@ void drawMaskedBitmap(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, in
|
||||||
// can have a different fg/bg pair, so the bg can't be filled in a
|
// can have a different fg/bg pair, so the bg can't be filled in a
|
||||||
// single bulk pass. Instead each cell is rendered individually,
|
// single bulk pass. Instead each cell is rendered individually,
|
||||||
// always in opaque mode (every pixel gets a write). The bpp branch
|
// always in opaque mode (every pixel gets a write). The bpp branch
|
||||||
// is still hoisted outside the per-pixel loop — the outer loop
|
// is still hoisted outside the per-pixel loop -- the outer loop
|
||||||
// selects the bpp path once, then iterates cells within it.
|
// selects the bpp path once, then iterates cells within it.
|
||||||
//
|
//
|
||||||
// blinkVisible controls the blink phase: when false, fg is replaced
|
// blinkVisible controls the blink phase: when false, fg is replaced
|
||||||
|
|
@ -1095,7 +1095,7 @@ void drawTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, in
|
||||||
text++;
|
text++;
|
||||||
|
|
||||||
if (*text == '&') {
|
if (*text == '&') {
|
||||||
// Escaped && — draw literal &
|
// Escaped && -- draw literal &
|
||||||
if (x + cw > d->clipX) {
|
if (x + cw > d->clipX) {
|
||||||
drawChar(d, ops, font, x, y, '&', fg, bg, opaque);
|
drawChar(d, ops, font, x, y, '&', fg, bg, opaque);
|
||||||
}
|
}
|
||||||
|
|
@ -1106,7 +1106,7 @@ void drawTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, in
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*text) {
|
if (*text) {
|
||||||
// Accelerator character — draw it then underline
|
// Accelerator character -- draw it then underline
|
||||||
if (x + cw > d->clipX) {
|
if (x + cw > d->clipX) {
|
||||||
drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
|
drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
|
||||||
drawHLine(d, ops, x, y + font->charHeight - 1, cw, fg);
|
drawHLine(d, ops, x, y + font->charHeight - 1, cw, fg);
|
||||||
|
|
@ -1135,7 +1135,7 @@ void drawTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, in
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Draws a vertical line pixel-by-pixel. Unlike drawHLine (which
|
// Draws a vertical line pixel-by-pixel. Unlike drawHLine (which
|
||||||
// delegates to rectFill → spanFill for a single-row span), a
|
// delegates to rectFill -> spanFill for a single-row span), a
|
||||||
// vertical line can't use spanFill because each pixel is on a
|
// vertical line can't use spanFill because each pixel is on a
|
||||||
// different scanline. Instead we advance by d->pitch per pixel
|
// different scanline. Instead we advance by d->pitch per pixel
|
||||||
// and write directly, branching on bpp once at the top.
|
// and write directly, branching on bpp once at the top.
|
||||||
|
|
@ -1224,7 +1224,7 @@ static inline void putPixel(uint8_t *dst, uint32_t color, int32_t bpp) {
|
||||||
// This function does NOT handle overlapping source and destination
|
// This function does NOT handle overlapping source and destination
|
||||||
// regions (no memmove). That's fine because the source is always a
|
// regions (no memmove). That's fine because the source is always a
|
||||||
// per-window content buffer and the destination is the shared
|
// per-window content buffer and the destination is the shared
|
||||||
// backbuffer — they never overlap.
|
// backbuffer -- they never overlap.
|
||||||
|
|
||||||
void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h) {
|
void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h) {
|
||||||
int32_t bpp = ops->bytesPerPixel;
|
int32_t bpp = ops->bytesPerPixel;
|
||||||
|
|
@ -1268,7 +1268,7 @@ void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, cons
|
||||||
// The workhorse fill primitive. Clips to the display clip rect,
|
// The workhorse fill primitive. Clips to the display clip rect,
|
||||||
// then fills one scanline at a time via the spanFill function
|
// then fills one scanline at a time via the spanFill function
|
||||||
// pointer (which routes to rep stosl on DOS). This is the most
|
// pointer (which routes to rep stosl on DOS). This is the most
|
||||||
// frequently called function in the draw layer — it backs rectFill
|
// frequently called function in the draw layer -- it backs rectFill
|
||||||
// directly, plus drawHLine, drawBevel interior fills, and the bg
|
// directly, plus drawHLine, drawBevel interior fills, and the bg
|
||||||
// fill in opaque text rendering.
|
// fill in opaque text rendering.
|
||||||
//
|
//
|
||||||
|
|
@ -1299,7 +1299,7 @@ void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w,
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Returns the pixel width of a null-terminated string. Because all
|
// Returns the pixel width of a null-terminated string. Because all
|
||||||
// fonts are fixed-width, this is just strlen * charWidth — but we
|
// fonts are fixed-width, this is just strlen * charWidth -- but we
|
||||||
// iterate manually rather than calling strlen to avoid a second pass
|
// iterate manually rather than calling strlen to avoid a second pass
|
||||||
// over the string. This is used heavily for layout calculations
|
// over the string. This is used heavily for layout calculations
|
||||||
// (centering text in buttons, sizing menu popups, etc.).
|
// (centering text in buttons, sizing menu popups, etc.).
|
||||||
|
|
@ -1334,14 +1334,14 @@ int32_t textWidthAccel(const BitmapFontT *font, const char *text) {
|
||||||
text++;
|
text++;
|
||||||
|
|
||||||
if (*text == '&') {
|
if (*text == '&') {
|
||||||
// Escaped && — counts as one character
|
// Escaped && -- counts as one character
|
||||||
w += font->charWidth;
|
w += font->charWidth;
|
||||||
text++;
|
text++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*text) {
|
if (*text) {
|
||||||
// Accelerator character — counts as one character, & is skipped
|
// Accelerator character -- counts as one character, & is skipped
|
||||||
w += font->charWidth;
|
w += font->charWidth;
|
||||||
text++;
|
text++;
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// dvx_draw.h — Layer 2: Drawing primitives for DVX GUI
|
// dvx_draw.h -- Layer 2: Drawing primitives for DVX GUI
|
||||||
//
|
//
|
||||||
// Provides all 2D drawing operations: rectangle fills, bitmap blits, text
|
// Provides all 2D drawing operations: rectangle fills, bitmap blits, text
|
||||||
// rendering, bevels, lines, and cursor/icon rendering. Every function in
|
// rendering, bevels, lines, and cursor/icon rendering. Every function in
|
||||||
// this layer draws into the display's backbuffer and clips to the display's
|
// this layer draws into the display's backbuffer and clips to the display's
|
||||||
// current clip rectangle — no direct LFB writes happen here.
|
// current clip rectangle -- no direct LFB writes happen here.
|
||||||
//
|
//
|
||||||
// This layer is deliberately stateless beyond the clip rect on DisplayT.
|
// This layer is deliberately stateless beyond the clip rect on DisplayT.
|
||||||
// All drawing context (colors, fonts, bevel styles) is passed explicitly
|
// All drawing context (colors, fonts, bevel styles) is passed explicitly
|
||||||
|
|
@ -34,7 +34,7 @@ void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w,
|
||||||
void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h);
|
void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h);
|
||||||
|
|
||||||
// Draw a beveled frame. The bevel is drawn as overlapping horizontal and
|
// Draw a beveled frame. The bevel is drawn as overlapping horizontal and
|
||||||
// vertical spans — top/left in highlight color, bottom/right in shadow.
|
// vertical spans -- top/left in highlight color, bottom/right in shadow.
|
||||||
// The face color fills the interior if non-zero.
|
// The face color fills the interior if non-zero.
|
||||||
void drawBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const BevelStyleT *style);
|
void drawBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const BevelStyleT *style);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvx_font.h — Embedded VGA bitmap font data (CP437) for DVX GUI
|
// dvx_font.h -- Embedded VGA bitmap font data (CP437) for DVX GUI
|
||||||
//
|
//
|
||||||
// Contains the raw glyph bitmaps for two standard VGA ROM fonts (8x14 and
|
// Contains the raw glyph bitmaps for two standard VGA ROM fonts (8x14 and
|
||||||
// 8x16) covering the full IBM Code Page 437 character set (256 glyphs).
|
// 8x16) covering the full IBM Code Page 437 character set (256 glyphs).
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
// The glyph format is 1 bit per pixel, 8 pixels wide, with MSB = leftmost
|
// The glyph format is 1 bit per pixel, 8 pixels wide, with MSB = leftmost
|
||||||
// pixel. Each glyph occupies charHeight consecutive bytes (14 or 16).
|
// pixel. Each glyph occupies charHeight consecutive bytes (14 or 16).
|
||||||
// The drawing code in dvxDraw.c processes one byte per scanline, testing
|
// The drawing code in dvxDraw.c processes one byte per scanline, testing
|
||||||
// each bit to decide foreground vs. background — the 8-pixel width means
|
// each bit to decide foreground vs. background -- the 8-pixel width means
|
||||||
// no bit-shifting across byte boundaries is ever needed.
|
// no bit-shifting across byte boundaries is ever needed.
|
||||||
//
|
//
|
||||||
// CP437 includes the full ASCII printable range (32-126) plus box-drawing
|
// CP437 includes the full ASCII printable range (32-126) plus box-drawing
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
#include "dvxTypes.h"
|
#include "dvxTypes.h"
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 8x14 VGA ROM font — standard IBM CP437 character set
|
// 8x14 VGA ROM font -- standard IBM CP437 character set
|
||||||
// 256 glyphs, 14 bytes per glyph, MSB = leftmost pixel
|
// 256 glyphs, 14 bytes per glyph, MSB = leftmost pixel
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
|
@ -551,7 +551,7 @@ static const uint8_t font8x14[256 * 14] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 8x16 VGA ROM font — standard IBM CP437 character set
|
// 8x16 VGA ROM font -- standard IBM CP437 character set
|
||||||
// 256 glyphs, 16 bytes per glyph, MSB = leftmost pixel
|
// 256 glyphs, 16 bytes per glyph, MSB = leftmost pixel
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvxIcon.c — stb_image implementation for DVX GUI
|
// dvxIcon.c -- stb_image implementation for DVX GUI
|
||||||
//
|
//
|
||||||
// This file exists solely to instantiate the stb_image implementation.
|
// This file exists solely to instantiate the stb_image implementation.
|
||||||
// stb_image is a single-header library: you #define STB_IMAGE_IMPLEMENTATION
|
// stb_image is a single-header library: you #define STB_IMAGE_IMPLEMENTATION
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
// 3. Doesn't slow down incremental rebuilds (only recompiles if stb changes)
|
// 3. Doesn't slow down incremental rebuilds (only recompiles if stb changes)
|
||||||
//
|
//
|
||||||
// stb_image was chosen over libpng/libjpeg because:
|
// stb_image was chosen over libpng/libjpeg because:
|
||||||
// - Single header, no external dependencies — critical for DJGPP cross-compile
|
// - Single header, no external dependencies -- critical for DJGPP cross-compile
|
||||||
// - Supports BMP, PNG, JPEG, GIF with one include
|
// - Supports BMP, PNG, JPEG, GIF with one include
|
||||||
// - Public domain license, no linking restrictions
|
// - Public domain license, no linking restrictions
|
||||||
// - Small code footprint suitable for DOS targets
|
// - Small code footprint suitable for DOS targets
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// dvxImageWrite.c — stb_image_write implementation for DVX GUI
|
// dvxImageWrite.c -- stb_image_write implementation for DVX GUI
|
||||||
//
|
//
|
||||||
// Companion to dvxIcon.c: instantiates stb_image_write for PNG output
|
// Companion to dvxIcon.c: instantiates stb_image_write for PNG output
|
||||||
// (used by dvxScreenshot and dvxWindowScreenshot). Same rationale as
|
// (used by dvxScreenshot and dvxWindowScreenshot). Same rationale as
|
||||||
// dvxIcon.c for using stb — zero external dependencies, single header,
|
// dvxIcon.c for using stb -- zero external dependencies, single header,
|
||||||
// public domain. Kept in a separate translation unit from the read side
|
// public domain. Kept in a separate translation unit from the read side
|
||||||
// so projects that don't need screenshot support can omit this file and
|
// so projects that don't need screenshot support can omit this file and
|
||||||
// save the code size.
|
// save the code size.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
// dvx_palette.h — 8-bit mode palette definition for DVX GUI
|
// dvx_palette.h -- 8-bit mode palette definition for DVX GUI
|
||||||
//
|
//
|
||||||
// Defines the 256-color palette used in 8-bit (VGA Mode 13h / VESA 8bpp)
|
// Defines the 256-color palette used in 8-bit (VGA Mode 13h / VESA 8bpp)
|
||||||
// video modes. The palette layout follows the same strategy as the X11
|
// video modes. The palette layout follows the same strategy as the X11
|
||||||
// default colormap and Netscape's web-safe colors:
|
// default colormap and Netscape's web-safe colors:
|
||||||
//
|
//
|
||||||
// 0-215: 6x6x6 color cube — 6 levels per channel (0, 51, 102, 153,
|
// 0-215: 6x6x6 color cube -- 6 levels per channel (0, 51, 102, 153,
|
||||||
// 204, 255), giving 216 uniformly distributed colors. The index
|
// 204, 255), giving 216 uniformly distributed colors. The index
|
||||||
// formula (r*36 + g*6 + b) enables O(1) color lookup without
|
// formula (r*36 + g*6 + b) enables O(1) color lookup without
|
||||||
// searching.
|
// searching.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvxPrefs.c — INI-based preferences system (read/write)
|
// dvxPrefs.c -- INI-based preferences system (read/write)
|
||||||
//
|
//
|
||||||
// Custom INI parser and writer. Stores entries as a dynamic array of
|
// Custom INI parser and writer. Stores entries as a dynamic array of
|
||||||
// section/key/value triples using stb_ds. Preserves insertion order
|
// section/key/value triples using stb_ds. Preserves insertion order
|
||||||
|
|
@ -210,14 +210,16 @@ const char *prefsGetString(const char *section, const char *key, const char *def
|
||||||
bool prefsLoad(const char *filename) {
|
bool prefsLoad(const char *filename) {
|
||||||
prefsFree();
|
prefsFree();
|
||||||
|
|
||||||
|
// Always store the path so prefsSave can create the file
|
||||||
|
// even if it doesn't exist yet.
|
||||||
|
sFilePath = dupStr(filename);
|
||||||
|
|
||||||
FILE *fp = fopen(filename, "rb");
|
FILE *fp = fopen(filename, "rb");
|
||||||
|
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
sFilePath = dupStr(filename);
|
|
||||||
|
|
||||||
char line[512];
|
char line[512];
|
||||||
char *currentSection = dupStr("");
|
char *currentSection = dupStr("");
|
||||||
|
|
||||||
|
|
@ -236,7 +238,7 @@ bool prefsLoad(const char *filename) {
|
||||||
p++;
|
p++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blank line — store as comment to preserve formatting
|
// Blank line -- store as comment to preserve formatting
|
||||||
if (*p == '\0') {
|
if (*p == '\0') {
|
||||||
PrefsEntryT e = {0};
|
PrefsEntryT e = {0};
|
||||||
e.section = dupStr(currentSection);
|
e.section = dupStr(currentSection);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvxPrefs.h — INI-based preferences system (read/write)
|
// dvxPrefs.h -- INI-based preferences system (read/write)
|
||||||
//
|
//
|
||||||
// Loads a configuration file at startup and provides typed accessors
|
// Loads a configuration file at startup and provides typed accessors
|
||||||
// with caller-supplied defaults. Values can be modified at runtime
|
// with caller-supplied defaults. Values can be modified at runtime
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvx_types.h — Shared type definitions for DVX GUI
|
// dvx_types.h -- Shared type definitions for DVX GUI
|
||||||
//
|
//
|
||||||
// Central type definitions shared across all five layers of the DVX GUI
|
// Central type definitions shared across all five layers of the DVX GUI
|
||||||
// stack (video, draw, comp, wm, app). Every header includes this file,
|
// stack (video, draw, comp, wm, app). Every header includes this file,
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
// Describes the pixel encoding for the active VESA video mode.
|
// Describes the pixel encoding for the active VESA video mode.
|
||||||
// Populated once at startup from the VBE mode info block and then
|
// Populated once at startup from the VBE mode info block and then
|
||||||
// treated as read-only. Storing shift/mask/bits separately avoids
|
// treated as read-only. Storing shift/mask/bits separately avoids
|
||||||
// repeated bit-scanning when packing colors — packColor() uses these
|
// repeated bit-scanning when packing colors -- packColor() uses these
|
||||||
// fields directly for shift-and-mask arithmetic rather than computing
|
// fields directly for shift-and-mask arithmetic rather than computing
|
||||||
// them on the fly, which matters when called per-glyph during text
|
// them on the fly, which matters when called per-glyph during text
|
||||||
// rendering on a 486.
|
// rendering on a 486.
|
||||||
|
|
@ -49,13 +49,13 @@ typedef struct {
|
||||||
// The double-buffer strategy (backBuf in system RAM, lfb is the real
|
// The double-buffer strategy (backBuf in system RAM, lfb is the real
|
||||||
// framebuffer) exists because writes to video memory over the PCI bus
|
// framebuffer) exists because writes to video memory over the PCI bus
|
||||||
// are dramatically slower than writes to main RAM on 486/Pentium hardware
|
// are dramatically slower than writes to main RAM on 486/Pentium hardware
|
||||||
// — often 10-50x slower for random-access patterns. All drawing goes to
|
// -- often 10-50x slower for random-access patterns. All drawing goes to
|
||||||
// backBuf, and only dirty rectangles are flushed to lfb via fast aligned
|
// backBuf, and only dirty rectangles are flushed to lfb via fast aligned
|
||||||
// copies (rep movsd). This is the single most important performance
|
// copies (rep movsd). This is the single most important performance
|
||||||
// decision in the entire compositor.
|
// decision in the entire compositor.
|
||||||
//
|
//
|
||||||
// The clip rectangle is mutable state set before each draw call so that
|
// The clip rectangle is mutable state set before each draw call so that
|
||||||
// the drawing layer doesn't need per-window context — it just clips to
|
// the drawing layer doesn't need per-window context -- it just clips to
|
||||||
// whatever rectangle is currently set on the display. This avoids an
|
// whatever rectangle is currently set on the display. This avoids an
|
||||||
// extra parameter on every draw function and mirrors how classic windowing
|
// extra parameter on every draw function and mirrors how classic windowing
|
||||||
// systems (X11, GDI) handle clipping.
|
// systems (X11, GDI) handle clipping.
|
||||||
|
|
@ -123,7 +123,7 @@ typedef struct {
|
||||||
//
|
//
|
||||||
// Bevels are the defining visual element of the Motif/DESQview/X aesthetic.
|
// Bevels are the defining visual element of the Motif/DESQview/X aesthetic.
|
||||||
// Swapping highlight and shadow colors flips between raised and sunken
|
// Swapping highlight and shadow colors flips between raised and sunken
|
||||||
// appearance — the BEVEL_RAISED and BEVEL_SUNKEN macros encode this
|
// appearance -- the BEVEL_RAISED and BEVEL_SUNKEN macros encode this
|
||||||
// convention so callers don't get the colors backwards.
|
// convention so callers don't get the colors backwards.
|
||||||
//
|
//
|
||||||
// A width of 2 pixels is the standard Motif bevel size. Using 1 creates
|
// A width of 2 pixels is the standard Motif bevel size. Using 1 creates
|
||||||
|
|
@ -142,6 +142,7 @@ typedef struct {
|
||||||
#define BEVEL_RAISED(cs, bw) (BevelStyleT){ (cs)->windowHighlight, (cs)->windowShadow, (cs)->windowFace, (bw) }
|
#define BEVEL_RAISED(cs, bw) (BevelStyleT){ (cs)->windowHighlight, (cs)->windowShadow, (cs)->windowFace, (bw) }
|
||||||
#define BEVEL_SUNKEN(cs, face, bw) (BevelStyleT){ (cs)->windowShadow, (cs)->windowHighlight, (face), (bw) }
|
#define BEVEL_SUNKEN(cs, face, bw) (BevelStyleT){ (cs)->windowShadow, (cs)->windowHighlight, (face), (bw) }
|
||||||
#define BEVEL_TROUGH(cs) (BevelStyleT){ (cs)->windowShadow, (cs)->windowHighlight, (cs)->scrollbarTrough, 1 }
|
#define BEVEL_TROUGH(cs) (BevelStyleT){ (cs)->windowShadow, (cs)->windowHighlight, (cs)->scrollbarTrough, 1 }
|
||||||
|
#define BEVEL_SB_BUTTON(cs) (BevelStyleT){ (cs)->windowHighlight, (cs)->windowShadow, (cs)->scrollbarBg, 1 }
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Bitmap font
|
// Bitmap font
|
||||||
|
|
@ -154,7 +155,7 @@ typedef struct {
|
||||||
//
|
//
|
||||||
// Two font sizes are provided (8x14 and 8x16), matching the standard
|
// Two font sizes are provided (8x14 and 8x16), matching the standard
|
||||||
// VGA ROM fonts and CP437 encoding. Proportional fonts would require
|
// VGA ROM fonts and CP437 encoding. Proportional fonts would require
|
||||||
// glyph-width tables, kerning, and per-character positioning — none of
|
// glyph-width tables, kerning, and per-character positioning -- none of
|
||||||
// which is worth the complexity for a DOS-era window manager targeting
|
// which is worth the complexity for a DOS-era window manager targeting
|
||||||
// 640x480 screens. The 8-pixel width also aligns nicely with byte
|
// 640x480 screens. The 8-pixel width also aligns nicely with byte
|
||||||
// boundaries, enabling per-scanline glyph rendering without bit shifting.
|
// boundaries, enabling per-scanline glyph rendering without bit shifting.
|
||||||
|
|
@ -182,7 +183,7 @@ typedef struct {
|
||||||
// The color set mirrors classic Motif/Windows 3.x conventions:
|
// The color set mirrors classic Motif/Windows 3.x conventions:
|
||||||
// windowHighlight/windowShadow form bevel pairs, activeTitleBg gives
|
// windowHighlight/windowShadow form bevel pairs, activeTitleBg gives
|
||||||
// the focused window's title bar its distinctive color, and so on.
|
// the focused window's title bar its distinctive color, and so on.
|
||||||
// Keeping all colors in one struct makes theme support trivial — just
|
// Keeping all colors in one struct makes theme support trivial -- just
|
||||||
// swap the struct.
|
// swap the struct.
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -204,6 +205,8 @@ typedef struct {
|
||||||
uint32_t scrollbarBg;
|
uint32_t scrollbarBg;
|
||||||
uint32_t scrollbarFg;
|
uint32_t scrollbarFg;
|
||||||
uint32_t scrollbarTrough;
|
uint32_t scrollbarTrough;
|
||||||
|
uint32_t cursorFg;
|
||||||
|
uint32_t cursorBg;
|
||||||
} ColorSchemeT;
|
} ColorSchemeT;
|
||||||
|
|
||||||
// Color IDs for addressing individual colors in ColorSchemeT.
|
// Color IDs for addressing individual colors in ColorSchemeT.
|
||||||
|
|
@ -227,9 +230,17 @@ typedef enum {
|
||||||
ColorScrollbarBgE,
|
ColorScrollbarBgE,
|
||||||
ColorScrollbarFgE,
|
ColorScrollbarFgE,
|
||||||
ColorScrollbarTroughE,
|
ColorScrollbarTroughE,
|
||||||
|
ColorCursorFgE,
|
||||||
|
ColorCursorBgE,
|
||||||
ColorCountE
|
ColorCountE
|
||||||
} ColorIdE;
|
} ColorIdE;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
WallpaperStretchE,
|
||||||
|
WallpaperTileE,
|
||||||
|
WallpaperCenterE
|
||||||
|
} WallpaperModeE;
|
||||||
|
|
||||||
// Video mode entry (enumerated at init, available to apps)
|
// Video mode entry (enumerated at init, available to apps)
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int32_t w;
|
int32_t w;
|
||||||
|
|
@ -261,7 +272,7 @@ typedef struct {
|
||||||
//
|
//
|
||||||
// These define the pixel geometry of the window frame (border, title bar,
|
// These define the pixel geometry of the window frame (border, title bar,
|
||||||
// menu bar). They're compile-time constants because the DV/X look demands
|
// menu bar). They're compile-time constants because the DV/X look demands
|
||||||
// a fixed, uniform chrome thickness across all windows — there's no
|
// a fixed, uniform chrome thickness across all windows -- there's no
|
||||||
// per-window customization of frame geometry.
|
// per-window customization of frame geometry.
|
||||||
//
|
//
|
||||||
// CHROME_TOTAL_TOP/SIDE/BOTTOM are the total chrome insets from the
|
// CHROME_TOTAL_TOP/SIDE/BOTTOM are the total chrome insets from the
|
||||||
|
|
@ -430,7 +441,7 @@ typedef struct {
|
||||||
//
|
//
|
||||||
// WindowT is the central object of the window manager. Each window owns
|
// WindowT is the central object of the window manager. Each window owns
|
||||||
// its own content backbuffer, which persists across frames so that
|
// its own content backbuffer, which persists across frames so that
|
||||||
// applications don't need to repaint on every expose event — only when
|
// applications don't need to repaint on every expose event -- only when
|
||||||
// their actual content changes. This is crucial for performance on slow
|
// their actual content changes. This is crucial for performance on slow
|
||||||
// hardware: dragging a window across others doesn't force every obscured
|
// hardware: dragging a window across others doesn't force every obscured
|
||||||
// window to repaint.
|
// window to repaint.
|
||||||
|
|
@ -533,7 +544,7 @@ typedef struct WindowT {
|
||||||
// Accelerator table (NULL if none, caller owns allocation)
|
// Accelerator table (NULL if none, caller owns allocation)
|
||||||
AccelTableT *accelTable;
|
AccelTableT *accelTable;
|
||||||
|
|
||||||
// Callbacks — the application's interface to the window manager.
|
// Callbacks -- the application's interface to the window manager.
|
||||||
// Using function pointers rather than a message queue keeps latency
|
// Using function pointers rather than a message queue keeps latency
|
||||||
// low (no queuing/dispatching overhead) and matches the callback-driven
|
// low (no queuing/dispatching overhead) and matches the callback-driven
|
||||||
// model that DOS programs expect. The widget system installs its own
|
// model that DOS programs expect. The widget system installs its own
|
||||||
|
|
@ -562,7 +573,7 @@ typedef struct WindowT {
|
||||||
// which matters because WindowT is large.
|
// which matters because WindowT is large.
|
||||||
//
|
//
|
||||||
// Drag/resize/scroll state lives here rather than on individual windows
|
// Drag/resize/scroll state lives here rather than on individual windows
|
||||||
// because only one interaction can be active at a time system-wide —
|
// because only one interaction can be active at a time system-wide --
|
||||||
// you can't drag two windows simultaneously with a single mouse.
|
// you can't drag two windows simultaneously with a single mouse.
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -596,7 +607,7 @@ typedef struct {
|
||||||
// four pixel states: transparent (AND=1, XOR=0), inverted (AND=1, XOR=1),
|
// four pixel states: transparent (AND=1, XOR=0), inverted (AND=1, XOR=1),
|
||||||
// black (AND=0, XOR=0), and white (AND=0, XOR=1). The cursor is drawn
|
// black (AND=0, XOR=0), and white (AND=0, XOR=1). The cursor is drawn
|
||||||
// in software on top of the composited frame because VESA VBE doesn't
|
// in software on top of the composited frame because VESA VBE doesn't
|
||||||
// provide a hardware sprite — the cursor is painted into the backbuffer
|
// provide a hardware sprite -- the cursor is painted into the backbuffer
|
||||||
// and the affected region is flushed to the LFB each frame.
|
// and the affected region is flushed to the LFB each frame.
|
||||||
//
|
//
|
||||||
// 16 pixels wide using uint16_t rows, one word per scanline. This keeps
|
// 16 pixels wide using uint16_t rows, one word per scanline. This keeps
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvx_video.c — Layer 1: Video backend for DVX GUI
|
// dvx_video.c -- Layer 1: Video backend for DVX GUI
|
||||||
//
|
//
|
||||||
// Platform-independent video utilities. The actual VESA/VBE code
|
// Platform-independent video utilities. The actual VESA/VBE code
|
||||||
// now lives in dvxPlatformDos.c (or the platform file for whatever
|
// now lives in dvxPlatformDos.c (or the platform file for whatever
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
// of dirty rects per frame. Requiring LFB eliminates per-scanline
|
// of dirty rects per frame. Requiring LFB eliminates per-scanline
|
||||||
// bank math, allows rep movsd bulk copies, and simplifies every span
|
// bank math, allows rep movsd bulk copies, and simplifies every span
|
||||||
// operation in the draw layer. The tradeoff is dropping cards that
|
// operation in the draw layer. The tradeoff is dropping cards that
|
||||||
// only expose banked modes — acceptable since the target is 486+ with
|
// only expose banked modes -- acceptable since the target is 486+ with
|
||||||
// a VESA 2.0 BIOS (virtually universal by 1994-95).
|
// a VESA 2.0 BIOS (virtually universal by 1994-95).
|
||||||
//
|
//
|
||||||
// System-RAM backbuffer with dirty-rect flushing: Drawing directly
|
// System-RAM backbuffer with dirty-rect flushing: Drawing directly
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
// For direct-color modes (15/16/32-bit), we right-shift each channel
|
// For direct-color modes (15/16/32-bit), we right-shift each channel
|
||||||
// to discard the low bits that don't fit in the mode's color field,
|
// to discard the low bits that don't fit in the mode's color field,
|
||||||
// then left-shift into position. This approach truncates rather than
|
// then left-shift into position. This approach truncates rather than
|
||||||
// rounds, which is a deliberate simplicity tradeoff — dithering or
|
// rounds, which is a deliberate simplicity tradeoff -- dithering or
|
||||||
// rounding would add per-pixel branches on a 486 for minimal visual
|
// rounding would add per-pixel branches on a 486 for minimal visual
|
||||||
// benefit in a desktop GUI.
|
// benefit in a desktop GUI.
|
||||||
|
|
||||||
|
|
@ -136,8 +136,8 @@ void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Thin wrapper that delegates to the platform layer. All the heavy
|
// Thin wrapper that delegates to the platform layer. All the heavy
|
||||||
// lifting — VBE enumeration, mode scoring, LFB DPMI mapping,
|
// lifting -- VBE enumeration, mode scoring, LFB DPMI mapping,
|
||||||
// backbuffer allocation — lives in dvxPlatformDos.c so this file
|
// backbuffer allocation -- lives in dvxPlatformDos.c so this file
|
||||||
// stays portable. The platform layer fills in every field of
|
// stays portable. The platform layer fills in every field of
|
||||||
// DisplayT (dimensions, pitch, pixel format, lfb pointer,
|
// DisplayT (dimensions, pitch, pixel format, lfb pointer,
|
||||||
// backBuf pointer, palette, initial clip rect).
|
// backBuf pointer, palette, initial clip rect).
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvx_video.h — Layer 1: VESA VBE video backend for DVX GUI
|
// dvx_video.h -- Layer 1: VESA VBE video backend for DVX GUI
|
||||||
//
|
//
|
||||||
// The lowest layer in the DVX stack. Responsible for VESA VBE mode
|
// The lowest layer in the DVX stack. Responsible for VESA VBE mode
|
||||||
// negotiation, linear framebuffer (LFB) mapping via DPMI, system RAM
|
// negotiation, linear framebuffer (LFB) mapping via DPMI, system RAM
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
// Probes VBE for a mode matching the requested resolution and depth,
|
// Probes VBE for a mode matching the requested resolution and depth,
|
||||||
// enables it, maps the LFB into the DPMI linear address space, and
|
// enables it, maps the LFB into the DPMI linear address space, and
|
||||||
// allocates a system RAM backbuffer of the same size. preferredBpp is
|
// allocates a system RAM backbuffer of the same size. preferredBpp is
|
||||||
// a hint — if the exact depth isn't available, the closest match is used.
|
// a hint -- if the exact depth isn't available, the closest match is used.
|
||||||
int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
|
int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
|
||||||
|
|
||||||
// Restores VGA text mode (INT 10h AH=0, mode 3), unmaps the LFB, and
|
// Restores VGA text mode (INT 10h AH=0, mode 3), unmaps the LFB, and
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvxWidget.h — Widget system for DVX GUI
|
// dvxWidget.h -- Widget system for DVX GUI
|
||||||
//
|
//
|
||||||
// A retained-mode widget toolkit layered on top of the DVX window manager.
|
// A retained-mode widget toolkit layered on top of the DVX window manager.
|
||||||
// Widgets form a tree (parent-child via firstChild/lastChild/nextSibling
|
// Widgets form a tree (parent-child via firstChild/lastChild/nextSibling
|
||||||
|
|
@ -150,7 +150,7 @@ typedef enum {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
FrameInE, // beveled inward (sunken) — default
|
FrameInE, // beveled inward (sunken) -- default
|
||||||
FrameOutE, // beveled outward (raised)
|
FrameOutE, // beveled outward (raised)
|
||||||
FrameFlatE // solid color line
|
FrameFlatE // solid color line
|
||||||
} FrameStyleE;
|
} FrameStyleE;
|
||||||
|
|
@ -176,7 +176,7 @@ typedef enum {
|
||||||
// state. The pointer indirection adds one dereference but saves
|
// state. The pointer indirection adds one dereference but saves
|
||||||
// significant memory across a typical widget tree.
|
// significant memory across a typical widget tree.
|
||||||
|
|
||||||
// AnsiTermDataT — full VT100/ANSI terminal emulator state.
|
// AnsiTermDataT -- full VT100/ANSI terminal emulator state.
|
||||||
// Implements a subset of DEC VT100 escape sequences sufficient for BBS
|
// Implements a subset of DEC VT100 escape sequences sufficient for BBS
|
||||||
// and DOS ANSI art rendering: cursor movement, color attributes (16-color
|
// and DOS ANSI art rendering: cursor movement, color attributes (16-color
|
||||||
// with bold-as-bright), scrolling regions, and blink. The parser is a
|
// with bold-as-bright), scrolling regions, and blink. The parser is a
|
||||||
|
|
@ -201,7 +201,7 @@ typedef struct {
|
||||||
// Scrolling region (0-based, inclusive)
|
// Scrolling region (0-based, inclusive)
|
||||||
int32_t scrollTop; // top row of scroll region
|
int32_t scrollTop; // top row of scroll region
|
||||||
int32_t scrollBot; // bottom row of scroll region
|
int32_t scrollBot; // bottom row of scroll region
|
||||||
// Scrollback — circular buffer so old lines age out naturally without
|
// Scrollback -- circular buffer so old lines age out naturally without
|
||||||
// memmove. Each line is cols*2 bytes (same ch/attr format as cells).
|
// memmove. Each line is cols*2 bytes (same ch/attr format as cells).
|
||||||
// scrollPos tracks the view offset: when equal to scrollbackCount,
|
// scrollPos tracks the view offset: when equal to scrollbackCount,
|
||||||
// the user sees the live screen; when less, they're viewing history.
|
// the user sees the live screen; when less, they're viewing history.
|
||||||
|
|
@ -216,7 +216,7 @@ typedef struct {
|
||||||
// Cursor blink
|
// Cursor blink
|
||||||
bool cursorOn; // current cursor blink phase
|
bool cursorOn; // current cursor blink phase
|
||||||
clock_t cursorTime; // timestamp of last cursor toggle
|
clock_t cursorTime; // timestamp of last cursor toggle
|
||||||
// Dirty tracking — a 32-bit bitmask where each bit corresponds to one
|
// Dirty tracking -- a 32-bit bitmask where each bit corresponds to one
|
||||||
// terminal row. Only dirty rows are repainted, which is critical because
|
// terminal row. Only dirty rows are repainted, which is critical because
|
||||||
// the ANSI terminal can receive data every frame (at 9600+ baud) and
|
// the ANSI terminal can receive data every frame (at 9600+ baud) and
|
||||||
// re-rendering all 25 rows of 80 columns each frame would dominate the
|
// re-rendering all 25 rows of 80 columns each frame would dominate the
|
||||||
|
|
@ -235,7 +235,7 @@ typedef struct {
|
||||||
int32_t selEndLine;
|
int32_t selEndLine;
|
||||||
int32_t selEndCol;
|
int32_t selEndCol;
|
||||||
bool selecting;
|
bool selecting;
|
||||||
// Communications interface — abstracted so the terminal can connect to
|
// Communications interface -- abstracted so the terminal can connect to
|
||||||
// different backends (serial port, secLink channel, local pipe) without
|
// different backends (serial port, secLink channel, local pipe) without
|
||||||
// knowing the transport details. When all are NULL, the terminal is in
|
// knowing the transport details. When all are NULL, the terminal is in
|
||||||
// offline/disconnected mode (useful for viewing .ANS files).
|
// offline/disconnected mode (useful for viewing .ANS files).
|
||||||
|
|
@ -244,7 +244,7 @@ typedef struct {
|
||||||
int32_t (*commWrite)(void *ctx, const uint8_t *buf, int32_t len);
|
int32_t (*commWrite)(void *ctx, const uint8_t *buf, int32_t len);
|
||||||
} AnsiTermDataT;
|
} AnsiTermDataT;
|
||||||
|
|
||||||
// ListViewDataT — multi-column list with sortable headers and optional
|
// ListViewDataT -- multi-column list with sortable headers and optional
|
||||||
// multi-select. cellData is a flat array of strings indexed as
|
// multi-select. cellData is a flat array of strings indexed as
|
||||||
// cellData[row * colCount + col]. The sortIndex is an indirection array
|
// cellData[row * colCount + col]. The sortIndex is an indirection array
|
||||||
// that maps displayed row numbers to data row numbers, allowing sort
|
// that maps displayed row numbers to data row numbers, allowing sort
|
||||||
|
|
@ -282,7 +282,7 @@ typedef struct WidgetT {
|
||||||
WidgetTypeE type;
|
WidgetTypeE type;
|
||||||
// wclass points to the vtable for this widget type. Looked up once at
|
// wclass points to the vtable for this widget type. Looked up once at
|
||||||
// creation from widgetClassTable[type]. This avoids a switch on type
|
// creation from widgetClassTable[type]. This avoids a switch on type
|
||||||
// in every paint/layout/event dispatch — the cost is one pointer per
|
// in every paint/layout/event dispatch -- the cost is one pointer per
|
||||||
// widget, which is negligible.
|
// widget, which is negligible.
|
||||||
const struct WidgetClassT *wclass;
|
const struct WidgetClassT *wclass;
|
||||||
char name[MAX_WIDGET_NAME];
|
char name[MAX_WIDGET_NAME];
|
||||||
|
|
@ -304,7 +304,7 @@ typedef struct WidgetT {
|
||||||
int32_t w;
|
int32_t w;
|
||||||
int32_t h;
|
int32_t h;
|
||||||
|
|
||||||
// Computed minimum size — set bottom-up by calcMinSize during layout.
|
// Computed minimum size -- set bottom-up by calcMinSize during layout.
|
||||||
// These represent the smallest possible size for this widget (including
|
// These represent the smallest possible size for this widget (including
|
||||||
// its children if it's a container). The layout engine uses these as
|
// its children if it's a container). The layout engine uses these as
|
||||||
// the starting point for space allocation.
|
// the starting point for space allocation.
|
||||||
|
|
@ -356,7 +356,7 @@ typedef struct WidgetT {
|
||||||
void (*onFocus)(struct WidgetT *w);
|
void (*onFocus)(struct WidgetT *w);
|
||||||
void (*onBlur)(struct WidgetT *w);
|
void (*onBlur)(struct WidgetT *w);
|
||||||
|
|
||||||
// Type-specific data — tagged union keyed by the `type` field.
|
// Type-specific data -- tagged union keyed by the `type` field.
|
||||||
// Only the member corresponding to `type` is valid. This is the C
|
// Only the member corresponding to `type` is valid. This is the C
|
||||||
// equivalent of a discriminated union / variant type. Using a union
|
// equivalent of a discriminated union / variant type. Using a union
|
||||||
// instead of separate structs per widget type keeps all widget data
|
// instead of separate structs per widget type keeps all widget data
|
||||||
|
|
@ -365,6 +365,7 @@ typedef struct WidgetT {
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
const char *text;
|
const char *text;
|
||||||
|
WidgetAlignE textAlign; // AlignStartE=left, AlignCenterE, AlignEndE=right
|
||||||
} label;
|
} label;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -389,7 +390,7 @@ typedef struct WidgetT {
|
||||||
// Text input has its own edit buffer (not a pointer to external
|
// Text input has its own edit buffer (not a pointer to external
|
||||||
// storage) so the widget fully owns its text lifecycle. The undo
|
// storage) so the widget fully owns its text lifecycle. The undo
|
||||||
// buffer holds a single-level snapshot taken before each edit
|
// buffer holds a single-level snapshot taken before each edit
|
||||||
// operation — Ctrl+Z restores to the snapshot. This is simpler
|
// operation -- Ctrl+Z restores to the snapshot. This is simpler
|
||||||
// than a full undo stack but sufficient for single-line fields.
|
// than a full undo stack but sufficient for single-line fields.
|
||||||
struct {
|
struct {
|
||||||
char *buf;
|
char *buf;
|
||||||
|
|
@ -605,7 +606,7 @@ typedef struct WidgetT {
|
||||||
// that fills the window's content area, and installs callback handlers
|
// that fills the window's content area, and installs callback handlers
|
||||||
// (onPaint, onMouse, onKey, onResize) that dispatch events to the widget
|
// (onPaint, onMouse, onKey, onResize) that dispatch events to the widget
|
||||||
// tree. After this call, the window is fully managed by the widget system
|
// tree. After this call, the window is fully managed by the widget system
|
||||||
// — the application builds its UI by adding child widgets to the returned
|
// -- the application builds its UI by adding child widgets to the returned
|
||||||
// root container. The window's userData is set to the AppContextT pointer.
|
// root container. The window's userData is set to the AppContextT pointer.
|
||||||
WidgetT *wgtInitWindow(struct AppContextT *ctx, WindowT *win);
|
WidgetT *wgtInitWindow(struct AppContextT *ctx, WindowT *win);
|
||||||
|
|
||||||
|
|
@ -628,6 +629,7 @@ WidgetT *wgtFrame(WidgetT *parent, const char *title);
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
WidgetT *wgtLabel(WidgetT *parent, const char *text);
|
WidgetT *wgtLabel(WidgetT *parent, const char *text);
|
||||||
|
void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align);
|
||||||
WidgetT *wgtButton(WidgetT *parent, const char *text);
|
WidgetT *wgtButton(WidgetT *parent, const char *text);
|
||||||
WidgetT *wgtCheckbox(WidgetT *parent, const char *text);
|
WidgetT *wgtCheckbox(WidgetT *parent, const char *text);
|
||||||
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
|
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
|
||||||
|
|
@ -651,7 +653,7 @@ WidgetT *wgtRadio(WidgetT *parent, const char *text);
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Spacer is an invisible flexible widget (weight=100) that absorbs
|
// Spacer is an invisible flexible widget (weight=100) that absorbs
|
||||||
// extra space — useful for pushing subsequent siblings to the end of
|
// extra space -- useful for pushing subsequent siblings to the end of
|
||||||
// a container (like CSS flex: 1 auto). Separators are thin beveled
|
// a container (like CSS flex: 1 auto). Separators are thin beveled
|
||||||
// lines for visual grouping.
|
// lines for visual grouping.
|
||||||
|
|
||||||
|
|
@ -895,7 +897,7 @@ int32_t wgtAnsiTermPoll(WidgetT *w);
|
||||||
// full widget paint pipeline (which would repaint the entire widget), this
|
// full widget paint pipeline (which would repaint the entire widget), this
|
||||||
// renders only the dirty rows (tracked via the dirtyRows bitmask) directly
|
// renders only the dirty rows (tracked via the dirtyRows bitmask) directly
|
||||||
// into the window's content buffer. This is essential for responsive terminal
|
// into the window's content buffer. This is essential for responsive terminal
|
||||||
// output — incoming serial data can dirty a few rows per frame, and
|
// output -- incoming serial data can dirty a few rows per frame, and
|
||||||
// repainting only those rows keeps the cost proportional to the actual
|
// repainting only those rows keeps the cost proportional to the actual
|
||||||
// change rather than the full 80x25 grid. Returns the number of rows
|
// change rather than the full 80x25 grid. Returns the number of rows
|
||||||
// repainted; outY/outH report the affected region for dirty-rect tracking.
|
// repainted; outY/outH report the affected region for dirty-rect tracking.
|
||||||
|
|
@ -972,7 +974,7 @@ void wgtListBoxSetReorderable(WidgetT *w, bool reorderable);
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Set tooltip text for a widget (NULL to remove).
|
// Set tooltip text for a widget (NULL to remove).
|
||||||
// Caller owns the string — it must outlive the widget.
|
// Caller owns the string -- it must outlive the widget.
|
||||||
void wgtSetTooltip(WidgetT *w, const char *text);
|
void wgtSetTooltip(WidgetT *w, const char *text);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
70
dvx/dvxWm.c
70
dvx/dvxWm.c
|
|
@ -1,4 +1,4 @@
|
||||||
// dvx_wm.c — Layer 4: Window manager for DVX GUI
|
// dvx_wm.c -- Layer 4: Window manager for DVX GUI
|
||||||
//
|
//
|
||||||
// This layer manages the window stack (z-order), window chrome rendering
|
// This layer manages the window stack (z-order), window chrome rendering
|
||||||
// (title bars, borders, bevels, gadgets, menu bars, scrollbars), and user
|
// (title bars, borders, bevels, gadgets, menu bars, scrollbars), and user
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
//
|
//
|
||||||
// - Each window has its own content backbuffer (contentBuf). The WM blits
|
// - Each window has its own content backbuffer (contentBuf). The WM blits
|
||||||
// this to the display backbuffer during compositing. This means window
|
// this to the display backbuffer during compositing. This means window
|
||||||
// content survives being occluded — apps don't get expose events and don't
|
// content survives being occluded -- apps don't get expose events and don't
|
||||||
// need to repaint when uncovered, which hugely simplifies app code and
|
// need to repaint when uncovered, which hugely simplifies app code and
|
||||||
// avoids the latency of synchronous repaint-on-expose.
|
// avoids the latency of synchronous repaint-on-expose.
|
||||||
//
|
//
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
// Extracted into a struct so that both drawTitleBar() and wmHitTest() compute
|
// Extracted into a struct so that both drawTitleBar() and wmHitTest() compute
|
||||||
// identical geometry from the same code path (computeTitleGeom). Without this,
|
// identical geometry from the same code path (computeTitleGeom). Without this,
|
||||||
// a discrepancy between draw and hit-test coordinates would cause clicks to
|
// a discrepancy between draw and hit-test coordinates would cause clicks to
|
||||||
// land on the wrong gadget — a subtle bug that's hard to reproduce visually.
|
// land on the wrong gadget -- a subtle bug that's hard to reproduce visually.
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int32_t titleX;
|
int32_t titleX;
|
||||||
|
|
@ -110,7 +110,7 @@ static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH);
|
||||||
// padding (CHROME_TITLE_PAD) on both sides and MENU_BAR_GAP between labels.
|
// padding (CHROME_TITLE_PAD) on both sides and MENU_BAR_GAP between labels.
|
||||||
// Positions are cached and only recomputed when positionsDirty is set (after
|
// Positions are cached and only recomputed when positionsDirty is set (after
|
||||||
// adding/removing a menu), avoiding redundant textWidthAccel calls on every
|
// adding/removing a menu), avoiding redundant textWidthAccel calls on every
|
||||||
// paint — font metric calculation is expensive relative to a simple flag check.
|
// paint -- font metric calculation is expensive relative to a simple flag check.
|
||||||
// barX values are relative to the window, not the screen; the draw path adds
|
// barX values are relative to the window, not the screen; the draw path adds
|
||||||
// the window's screen position.
|
// the window's screen position.
|
||||||
static void computeMenuBarPositions(WindowT *win, const BitmapFontT *font) {
|
static void computeMenuBarPositions(WindowT *win, const BitmapFontT *font) {
|
||||||
|
|
@ -198,7 +198,7 @@ static void computeTitleGeom(const WindowT *win, TitleGeomT *g) {
|
||||||
// Row 0: highlight (bright edge catches the "light" from top-left)
|
// Row 0: highlight (bright edge catches the "light" from top-left)
|
||||||
// Row 1: highlight (doubled for visual weight at low resolutions)
|
// Row 1: highlight (doubled for visual weight at low resolutions)
|
||||||
// Row 2: face (flat middle band)
|
// Row 2: face (flat middle band)
|
||||||
// Row 3: shadow (inner crease — makes the border feel like two beveled
|
// Row 3: shadow (inner crease -- makes the border feel like two beveled
|
||||||
// ridges rather than one flat edge)
|
// ridges rather than one flat edge)
|
||||||
//
|
//
|
||||||
// Bottom/right are mirror-reversed (shadow, shadow, face, highlight). This
|
// Bottom/right are mirror-reversed (shadow, shadow, face, highlight). This
|
||||||
|
|
@ -253,7 +253,7 @@ static void drawBorderFrame(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT
|
||||||
// Draws a small raised beveled square (1px bevel) used as the base for
|
// Draws a small raised beveled square (1px bevel) used as the base for
|
||||||
// close, minimize, and maximize buttons. The icon (bar, box, dot) is drawn
|
// close, minimize, and maximize buttons. The icon (bar, box, dot) is drawn
|
||||||
// on top by the caller. Using 1px bevels on gadgets (vs 4px on the frame)
|
// on top by the caller. Using 1px bevels on gadgets (vs 4px on the frame)
|
||||||
// keeps them visually subordinate to the window border — a Motif convention
|
// keeps them visually subordinate to the window border -- a Motif convention
|
||||||
// that helps users distinguish clickable controls from structural chrome.
|
// that helps users distinguish clickable controls from structural chrome.
|
||||||
|
|
||||||
static void drawTitleGadget(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t size) {
|
static void drawTitleGadget(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t size) {
|
||||||
|
|
@ -278,7 +278,7 @@ static void drawTitleGadget(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT
|
||||||
// that long label text doesn't bleed into the window border. The clip is
|
// that long label text doesn't bleed into the window border. The clip is
|
||||||
// saved/restored rather than set to the full dirty rect because this
|
// saved/restored rather than set to the full dirty rect because this
|
||||||
// function is called from wmDrawChrome which has already set the clip to
|
// function is called from wmDrawChrome which has already set the clip to
|
||||||
// the dirty rect — we need to intersect with both. The separator line at
|
// the dirty rect -- we need to intersect with both. The separator line at
|
||||||
// the bottom visually separates the menu bar from the content area.
|
// the bottom visually separates the menu bar from the content area.
|
||||||
|
|
||||||
static void drawMenuBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, WindowT *win) {
|
static void drawMenuBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, WindowT *win) {
|
||||||
|
|
@ -331,7 +331,7 @@ static void drawMenuBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fon
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// GEOS Ensemble Motif-style resize indicators: perpendicular grooves that
|
// GEOS Ensemble Motif-style resize indicators: perpendicular grooves that
|
||||||
// cut across the window border near each corner. Two breaks per corner —
|
// cut across the window border near each corner. Two breaks per corner --
|
||||||
// one on each edge meeting at that corner. Each groove is a 2px sunken
|
// one on each edge meeting at that corner. Each groove is a 2px sunken
|
||||||
// notch (shadow line + highlight line) cutting across the full 4px border
|
// notch (shadow line + highlight line) cutting across the full 4px border
|
||||||
// width, perpendicular to the edge direction.
|
// width, perpendicular to the edge direction.
|
||||||
|
|
@ -479,11 +479,11 @@ static void drawScaledRect(DisplayT *d, int32_t dstX, int32_t dstY, int32_t dstW
|
||||||
// over the trough ends, and finally the thumb bevel in the middle. Arrow
|
// over the trough ends, and finally the thumb bevel in the middle. Arrow
|
||||||
// buttons and thumb use 1px raised bevels for a clickable appearance;
|
// buttons and thumb use 1px raised bevels for a clickable appearance;
|
||||||
// the trough uses a 1px sunken bevel (swapped highlight/shadow) to appear
|
// the trough uses a 1px sunken bevel (swapped highlight/shadow) to appear
|
||||||
// recessed — standard Motif scrollbar convention.
|
// recessed -- standard Motif scrollbar convention.
|
||||||
//
|
//
|
||||||
// winX/winY are the window's screen position; sb->x/y are relative to the
|
// winX/winY are the window's screen position; sb->x/y are relative to the
|
||||||
// window origin. This split lets scrollbar positions survive window drags
|
// window origin. This split lets scrollbar positions survive window drags
|
||||||
// without recalculation — only winX/winY change.
|
// without recalculation -- only winX/winY change.
|
||||||
|
|
||||||
static void drawScrollbar(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, const ScrollbarT *sb, int32_t winX, int32_t winY) {
|
static void drawScrollbar(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, const ScrollbarT *sb, int32_t winX, int32_t winY) {
|
||||||
int32_t x = winX + sb->x;
|
int32_t x = winX + sb->x;
|
||||||
|
|
@ -492,7 +492,7 @@ static void drawScrollbar(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
|
||||||
BevelStyleT btnBevel;
|
BevelStyleT btnBevel;
|
||||||
btnBevel.highlight = colors->windowHighlight;
|
btnBevel.highlight = colors->windowHighlight;
|
||||||
btnBevel.shadow = colors->windowShadow;
|
btnBevel.shadow = colors->windowShadow;
|
||||||
btnBevel.face = colors->buttonFace;
|
btnBevel.face = colors->scrollbarBg;
|
||||||
btnBevel.width = 1;
|
btnBevel.width = 1;
|
||||||
|
|
||||||
if (sb->orient == ScrollbarVerticalE) {
|
if (sb->orient == ScrollbarVerticalE) {
|
||||||
|
|
@ -563,13 +563,13 @@ static void drawScrollbar(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
|
||||||
// row is 2 pixels wider (1 pixel on each side), producing a symmetric
|
// row is 2 pixels wider (1 pixel on each side), producing a symmetric
|
||||||
// isoceles triangle. SB_ARROW_ROWS (4) rows gives a 7-pixel-wide base,
|
// isoceles triangle. SB_ARROW_ROWS (4) rows gives a 7-pixel-wide base,
|
||||||
// which fits well inside SCROLLBAR_WIDTH (16px) buttons. The glyph is
|
// which fits well inside SCROLLBAR_WIDTH (16px) buttons. The glyph is
|
||||||
// centered on the button using integer division — no sub-pixel alignment
|
// centered on the button using integer division -- no sub-pixel alignment
|
||||||
// needed at these sizes.
|
// needed at these sizes.
|
||||||
|
|
||||||
static void drawScrollbarArrow(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t size, int32_t dir) {
|
static void drawScrollbarArrow(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t size, int32_t dir) {
|
||||||
int32_t cx = x + size / 2;
|
int32_t cx = x + size / 2;
|
||||||
int32_t cy = y + size / 2;
|
int32_t cy = y + size / 2;
|
||||||
uint32_t fg = colors->contentFg;
|
uint32_t fg = colors->scrollbarFg;
|
||||||
|
|
||||||
// Draw a small triangle
|
// Draw a small triangle
|
||||||
for (int32_t i = 0; i < SB_ARROW_ROWS; i++) {
|
for (int32_t i = 0; i < SB_ARROW_ROWS; i++) {
|
||||||
|
|
@ -602,7 +602,7 @@ static void drawScrollbarArrow(DisplayT *d, const BlitOpsT *ops, const ColorSche
|
||||||
// and maximize gadgets (right), and centered title text.
|
// and maximize gadgets (right), and centered title text.
|
||||||
//
|
//
|
||||||
// The title bar background uses active/inactive colors to provide a strong
|
// The title bar background uses active/inactive colors to provide a strong
|
||||||
// visual cue for which window has keyboard focus — the same convention used
|
// visual cue for which window has keyboard focus -- the same convention used
|
||||||
// by Windows 3.x, Motif, and CDE. Only the title bar changes color on
|
// by Windows 3.x, Motif, and CDE. Only the title bar changes color on
|
||||||
// focus change; the rest of the chrome stays the same. This is why
|
// focus change; the rest of the chrome stays the same. This is why
|
||||||
// wmSetFocus dirties only the title bar area, not the entire window.
|
// wmSetFocus dirties only the title bar area, not the entire window.
|
||||||
|
|
@ -667,7 +667,7 @@ static void drawTitleBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fo
|
||||||
|
|
||||||
// Title text is centered in the available space between gadgets. If the
|
// Title text is centered in the available space between gadgets. If the
|
||||||
// title is too long, it's truncated by character count (not pixel width)
|
// title is too long, it's truncated by character count (not pixel width)
|
||||||
// since the font is fixed-width. No ellipsis is added — at these sizes,
|
// since the font is fixed-width. No ellipsis is added -- at these sizes,
|
||||||
// ellipsis would consume 3 characters that could show useful text instead.
|
// ellipsis would consume 3 characters that could show useful text instead.
|
||||||
int32_t availW = g.textRightEdge - g.textLeftEdge;
|
int32_t availW = g.textRightEdge - g.textLeftEdge;
|
||||||
|
|
||||||
|
|
@ -1003,7 +1003,7 @@ void wmAddMenuSeparator(MenuT *menu) {
|
||||||
// Creates a cascading submenu. Unlike top-level menus (inline in MenuBarT),
|
// Creates a cascading submenu. Unlike top-level menus (inline in MenuBarT),
|
||||||
// submenus are heap-allocated because the nesting depth is unpredictable and
|
// submenus are heap-allocated because the nesting depth is unpredictable and
|
||||||
// the submenu is owned by its parent item. The item's id is set to -1 to
|
// the submenu is owned by its parent item. The item's id is set to -1 to
|
||||||
// distinguish it from leaf items during event dispatch — clicking a submenu
|
// distinguish it from leaf items during event dispatch -- clicking a submenu
|
||||||
// item opens the child rather than firing a command.
|
// item opens the child rather than firing a command.
|
||||||
|
|
||||||
MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label) {
|
MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label) {
|
||||||
|
|
@ -1070,13 +1070,13 @@ ScrollbarT *wmAddVScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
|
||||||
// the frame.
|
// the frame.
|
||||||
//
|
//
|
||||||
// Each window gets a unique monotonic ID (static nextId) used for lookup
|
// Each window gets a unique monotonic ID (static nextId) used for lookup
|
||||||
// by the event loop and DXE app system. IDs are never reused — with 2^31
|
// by the event loop and DXE app system. IDs are never reused -- with 2^31
|
||||||
// IDs available and windows being created/destroyed interactively, this
|
// IDs available and windows being created/destroyed interactively, this
|
||||||
// will never wrap in practice.
|
// will never wrap in practice.
|
||||||
//
|
//
|
||||||
// The content buffer is initialized to 0xFF (white) so newly created
|
// The content buffer is initialized to 0xFF (white) so newly created
|
||||||
// windows have a clean background before the app's first onPaint fires.
|
// windows have a clean background before the app's first onPaint fires.
|
||||||
// maxW/maxH default to -1 meaning "use screen dimensions" — apps can
|
// maxW/maxH default to -1 meaning "use screen dimensions" -- apps can
|
||||||
// override this to constrain maximized size (e.g. for dialog-like windows).
|
// override this to constrain maximized size (e.g. for dialog-like windows).
|
||||||
//
|
//
|
||||||
// New windows are added at stack->count (the top), so they appear in front
|
// New windows are added at stack->count (the top), so they appear in front
|
||||||
|
|
@ -1213,7 +1213,7 @@ void wmDestroyWindow(WindowStackT *stack, WindowT *win) {
|
||||||
//
|
//
|
||||||
// Initiates a window drag by recording the mouse offset from the window
|
// Initiates a window drag by recording the mouse offset from the window
|
||||||
// origin. This offset is maintained throughout the drag so that the window
|
// origin. This offset is maintained throughout the drag so that the window
|
||||||
// tracks the mouse cursor without jumping — the window position under the
|
// tracks the mouse cursor without jumping -- the window position under the
|
||||||
// cursor stays consistent from mousedown to mouseup.
|
// cursor stays consistent from mousedown to mouseup.
|
||||||
|
|
||||||
void wmDragBegin(WindowStackT *stack, int32_t idx, int32_t mouseX, int32_t mouseY) {
|
void wmDragBegin(WindowStackT *stack, int32_t idx, int32_t mouseX, int32_t mouseY) {
|
||||||
|
|
@ -1244,7 +1244,7 @@ void wmDragEnd(WindowStackT *stack) {
|
||||||
//
|
//
|
||||||
// Unlike some WMs that use an "outline drag" (drawing a wireframe while
|
// Unlike some WMs that use an "outline drag" (drawing a wireframe while
|
||||||
// dragging and moving the real window on mouse-up), we do full content
|
// dragging and moving the real window on mouse-up), we do full content
|
||||||
// dragging. This is feasible because the content buffer is persistent —
|
// dragging. This is feasible because the content buffer is persistent --
|
||||||
// we don't need to ask the app to repaint during the drag, just blit from
|
// we don't need to ask the app to repaint during the drag, just blit from
|
||||||
// its buffer at the new position.
|
// its buffer at the new position.
|
||||||
|
|
||||||
|
|
@ -1261,6 +1261,10 @@ void wmDragMove(WindowStackT *stack, DirtyListT *dl, int32_t mouseX, int32_t mou
|
||||||
win->x = mouseX - stack->dragOffX;
|
win->x = mouseX - stack->dragOffX;
|
||||||
win->y = mouseY - stack->dragOffY;
|
win->y = mouseY - stack->dragOffY;
|
||||||
|
|
||||||
|
if (win->maximized) {
|
||||||
|
win->maximized = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Clamp: keep title bar reachable
|
// Clamp: keep title bar reachable
|
||||||
if (win->y < 0) {
|
if (win->y < 0) {
|
||||||
win->y = 0;
|
win->y = 0;
|
||||||
|
|
@ -1293,7 +1297,7 @@ void wmDragMove(WindowStackT *stack, DirtyListT *dl, int32_t mouseX, int32_t mou
|
||||||
//
|
//
|
||||||
// The clip rect is set to the dirty rect so all draw operations are
|
// The clip rect is set to the dirty rect so all draw operations are
|
||||||
// automatically clipped. This is the mechanism by which partial chrome
|
// automatically clipped. This is the mechanism by which partial chrome
|
||||||
// repaints work — if only the title bar is dirty, the border fill runs
|
// repaints work -- if only the title bar is dirty, the border fill runs
|
||||||
// but its output is clipped away for scanlines outside the dirty rect.
|
// but its output is clipped away for scanlines outside the dirty rect.
|
||||||
// The clip rect is saved/restored because the caller (compositeAndFlush)
|
// The clip rect is saved/restored because the caller (compositeAndFlush)
|
||||||
// manages its own clip state across multiple windows.
|
// manages its own clip state across multiple windows.
|
||||||
|
|
@ -1359,7 +1363,7 @@ void wmDrawChrome(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, con
|
||||||
// The blit uses direct memcpy rather than going through the rectCopy
|
// The blit uses direct memcpy rather than going through the rectCopy
|
||||||
// drawing primitive, because we've already computed the exact intersection
|
// drawing primitive, because we've already computed the exact intersection
|
||||||
// and don't need the general-purpose clip logic. On 486/Pentium, memcpy
|
// and don't need the general-purpose clip logic. On 486/Pentium, memcpy
|
||||||
// compiles to rep movsd which saturates the memory bus — no further
|
// compiles to rep movsd which saturates the memory bus -- no further
|
||||||
// optimization is possible at this level.
|
// optimization is possible at this level.
|
||||||
|
|
||||||
void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *clipTo) {
|
void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *clipTo) {
|
||||||
|
|
@ -1406,7 +1410,7 @@ void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *
|
||||||
// 2. A live thumbnail of the window's content buffer (also scaled)
|
// 2. A live thumbnail of the window's content buffer (also scaled)
|
||||||
// 3. A grey fill if neither is available
|
// 3. A grey fill if neither is available
|
||||||
//
|
//
|
||||||
// The live thumbnail approach (option 2) is a DESQview/X homage — DV/X
|
// The live thumbnail approach (option 2) is a DESQview/X homage -- DV/X
|
||||||
// showed miniature window contents in its icon/task view. The thumbnail
|
// showed miniature window contents in its icon/task view. The thumbnail
|
||||||
// is rendered from the existing content buffer, so no extra rendering
|
// is rendered from the existing content buffer, so no extra rendering
|
||||||
// pass is needed. contentDirty tracks whether the content has changed
|
// pass is needed. contentDirty tracks whether the content has changed
|
||||||
|
|
@ -1459,7 +1463,7 @@ void wmDrawMinimizedIcons(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
|
||||||
drawScaledRect(d, contentX, contentY, ICON_SIZE, ICON_SIZE,
|
drawScaledRect(d, contentX, contentY, ICON_SIZE, ICON_SIZE,
|
||||||
win->contentBuf, win->contentW, win->contentH, win->contentPitch, bpp);
|
win->contentBuf, win->contentW, win->contentH, win->contentPitch, bpp);
|
||||||
} else {
|
} else {
|
||||||
// No content — draw grey fill
|
// No content -- draw grey fill
|
||||||
rectFill(d, ops, contentX, contentY, ICON_SIZE, ICON_SIZE, colors->windowFace);
|
rectFill(d, ops, contentX, contentY, ICON_SIZE, ICON_SIZE, colors->windowFace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1557,7 +1561,7 @@ int32_t wmHitTest(const WindowStackT *stack, int32_t mx, int32_t my, int32_t *hi
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Title bar (drag area — between gadgets)
|
// Title bar (drag area -- between gadgets)
|
||||||
if (my >= g.titleY && my < g.titleY + CHROME_TITLE_HEIGHT &&
|
if (my >= g.titleY && my < g.titleY + CHROME_TITLE_HEIGHT &&
|
||||||
mx >= g.titleX && mx < g.titleX + g.titleW) {
|
mx >= g.titleX && mx < g.titleX + g.titleW) {
|
||||||
*hitPart = HIT_TITLE;
|
*hitPart = HIT_TITLE;
|
||||||
|
|
@ -1653,7 +1657,7 @@ void wmInit(WindowStackT *stack) {
|
||||||
// The content buffer must be reallocated because the content area changes
|
// The content buffer must be reallocated because the content area changes
|
||||||
// size. After reallocation, onResize notifies the app of the new dimensions,
|
// size. After reallocation, onResize notifies the app of the new dimensions,
|
||||||
// then onPaint requests a full repaint into the new buffer. This is
|
// then onPaint requests a full repaint into the new buffer. This is
|
||||||
// synchronous — the maximize completes in one frame, avoiding flicker.
|
// synchronous -- the maximize completes in one frame, avoiding flicker.
|
||||||
//
|
//
|
||||||
// Both old and new positions are dirtied: old to expose what was behind the
|
// Both old and new positions are dirtied: old to expose what was behind the
|
||||||
// window at its previous size, new to paint the window at its maximized size.
|
// window at its previous size, new to paint the window at its maximized size.
|
||||||
|
|
@ -1706,7 +1710,7 @@ void wmMaximize(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT
|
||||||
//
|
//
|
||||||
// Minimizes a window: marks it minimized so it's skipped during compositing
|
// Minimizes a window: marks it minimized so it's skipped during compositing
|
||||||
// (except for icon drawing) and moves focus to the next available window.
|
// (except for icon drawing) and moves focus to the next available window.
|
||||||
// The window's geometry and content buffer are preserved — no reallocation
|
// The window's geometry and content buffer are preserved -- no reallocation
|
||||||
// is needed since the window retains its size and will be restored to the
|
// is needed since the window retains its size and will be restored to the
|
||||||
// same position.
|
// same position.
|
||||||
//
|
//
|
||||||
|
|
@ -1788,7 +1792,7 @@ int32_t wmMinimizedIconHit(const WindowStackT *stack, const DisplayT *d, int32_t
|
||||||
// + menu bar (if present) + scrollbar space (if present).
|
// + menu bar (if present) + scrollbar space (if present).
|
||||||
//
|
//
|
||||||
// This function is called on every resize move event, so it must be cheap.
|
// This function is called on every resize move event, so it must be cheap.
|
||||||
// No allocations, no string operations — just arithmetic on cached values.
|
// No allocations, no string operations -- just arithmetic on cached values.
|
||||||
|
|
||||||
static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH) {
|
static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH) {
|
||||||
int32_t gadgetS = CHROME_TITLE_HEIGHT - GADGET_INSET * 2;
|
int32_t gadgetS = CHROME_TITLE_HEIGHT - GADGET_INSET * 2;
|
||||||
|
|
@ -1932,7 +1936,7 @@ void wmRaiseWindow(WindowStackT *stack, DirtyListT *dl, int32_t idx) {
|
||||||
//
|
//
|
||||||
// Frees and reallocates the content buffer to match the current contentW/H.
|
// Frees and reallocates the content buffer to match the current contentW/H.
|
||||||
// Called after any geometry change (resize, maximize, restore). The old
|
// Called after any geometry change (resize, maximize, restore). The old
|
||||||
// buffer contents are discarded — the caller is expected to trigger
|
// buffer contents are discarded -- the caller is expected to trigger
|
||||||
// onResize + onPaint to refill it. This is simpler (and on 486, faster)
|
// onResize + onPaint to refill it. This is simpler (and on 486, faster)
|
||||||
// than copying and scaling the old content.
|
// than copying and scaling the old content.
|
||||||
//
|
//
|
||||||
|
|
@ -1993,7 +1997,7 @@ void wmResizeBegin(WindowStackT *stack, int32_t idx, int32_t edge, int32_t mouse
|
||||||
// The grab area extends 2 pixels beyond the visual border (CHROME_BORDER_WIDTH
|
// The grab area extends 2 pixels beyond the visual border (CHROME_BORDER_WIDTH
|
||||||
// + 2 = 6px) to make edges easier to grab, especially the 4px-wide border
|
// + 2 = 6px) to make edges easier to grab, especially the 4px-wide border
|
||||||
// which would be frustratingly small on its own. This is a common usability
|
// which would be frustratingly small on its own. This is a common usability
|
||||||
// trick — the visual border is narrower than the hit zone.
|
// trick -- the visual border is narrower than the hit zone.
|
||||||
|
|
||||||
int32_t wmResizeEdgeHit(const WindowT *win, int32_t mx, int32_t my) {
|
int32_t wmResizeEdgeHit(const WindowT *win, int32_t mx, int32_t my) {
|
||||||
int32_t edge = RESIZE_NONE;
|
int32_t edge = RESIZE_NONE;
|
||||||
|
|
@ -2047,12 +2051,12 @@ void wmResizeEnd(WindowStackT *stack) {
|
||||||
// only on axes where the resize was actually applied. If clamped (window at
|
// only on axes where the resize was actually applied. If clamped (window at
|
||||||
// min/max size), dragOff is NOT updated on that axis, so the accumulated
|
// min/max size), dragOff is NOT updated on that axis, so the accumulated
|
||||||
// delta tracks how far the mouse moved past the border. When the user
|
// delta tracks how far the mouse moved past the border. When the user
|
||||||
// reverses direction, the border immediately follows — it "sticks" to
|
// reverses direction, the border immediately follows -- it "sticks" to
|
||||||
// the mouse pointer instead of creating a dead zone.
|
// the mouse pointer instead of creating a dead zone.
|
||||||
//
|
//
|
||||||
// If the user resizes while maximized, the maximized flag is cleared.
|
// If the user resizes while maximized, the maximized flag is cleared.
|
||||||
// This prevents wmRestore from snapping back to the pre-maximize geometry,
|
// This prevents wmRestore from snapping back to the pre-maximize geometry,
|
||||||
// which would be confusing — the user's manual resize represents their
|
// which would be confusing -- the user's manual resize represents their
|
||||||
// new intent.
|
// new intent.
|
||||||
|
|
||||||
void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_t *mouseX, int32_t *mouseY) {
|
void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_t *mouseX, int32_t *mouseY) {
|
||||||
|
|
@ -2665,7 +2669,7 @@ void wmSetTitle(WindowT *win, DirtyListT *dl, const char *title) {
|
||||||
// scrollbar's presence. This creates the standard L-shaped layout where the
|
// scrollbar's presence. This creates the standard L-shaped layout where the
|
||||||
// scrollbars meet at the bottom-right corner with a small dead zone between
|
// scrollbars meet at the bottom-right corner with a small dead zone between
|
||||||
// them (the corner square where both scrollbars would overlap is simply not
|
// them (the corner square where both scrollbars would overlap is simply not
|
||||||
// covered — it shows the window face color from drawBorderFrame).
|
// covered -- it shows the window face color from drawBorderFrame).
|
||||||
|
|
||||||
void wmUpdateContentRect(WindowT *win) {
|
void wmUpdateContentRect(WindowT *win) {
|
||||||
int32_t topChrome = CHROME_TOTAL_TOP;
|
int32_t topChrome = CHROME_TOTAL_TOP;
|
||||||
|
|
|
||||||
12
dvx/dvxWm.h
12
dvx/dvxWm.h
|
|
@ -1,4 +1,4 @@
|
||||||
// dvx_wm.h — Layer 4: Window manager for DVX GUI
|
// dvx_wm.h -- Layer 4: Window manager for DVX GUI
|
||||||
//
|
//
|
||||||
// Manages the window lifecycle, Z-order stack, chrome drawing, hit testing,
|
// Manages the window lifecycle, Z-order stack, chrome drawing, hit testing,
|
||||||
// and interactive operations (drag, resize, scroll). This layer bridges the
|
// and interactive operations (drag, resize, scroll). This layer bridges the
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
// Design philosophy: the WM owns window geometry and chrome, but the
|
// Design philosophy: the WM owns window geometry and chrome, but the
|
||||||
// window's content is owned by the application (via callbacks or the widget
|
// window's content is owned by the application (via callbacks or the widget
|
||||||
// system). This separation means the WM can move, resize, and repaint
|
// system). This separation means the WM can move, resize, and repaint
|
||||||
// window frames without involving the application at all — only content
|
// window frames without involving the application at all -- only content
|
||||||
// changes trigger application callbacks.
|
// changes trigger application callbacks.
|
||||||
//
|
//
|
||||||
// All WM operations that change visible screen state accept a DirtyListT*
|
// All WM operations that change visible screen state accept a DirtyListT*
|
||||||
|
|
@ -24,7 +24,7 @@ void wmInit(WindowStackT *stack);
|
||||||
|
|
||||||
// Allocate a new WindowT, initialize its geometry and content buffer, and
|
// Allocate a new WindowT, initialize its geometry and content buffer, and
|
||||||
// push it to the top of the Z-order stack. The returned window has default
|
// push it to the top of the Z-order stack. The returned window has default
|
||||||
// callbacks (all NULL) — the caller should set onPaint/onKey/etc. before
|
// callbacks (all NULL) -- the caller should set onPaint/onKey/etc. before
|
||||||
// the next event loop iteration.
|
// the next event loop iteration.
|
||||||
WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable);
|
WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable);
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ void wmUpdateContentRect(WindowT *win);
|
||||||
|
|
||||||
// Reallocate the per-window content backbuffer to match the current
|
// Reallocate the per-window content backbuffer to match the current
|
||||||
// contentW/H. Returns 0 on success, -1 if allocation fails. The old
|
// contentW/H. Returns 0 on success, -1 if allocation fails. The old
|
||||||
// buffer contents are lost — the caller should trigger a full repaint
|
// buffer contents are lost -- the caller should trigger a full repaint
|
||||||
// via onPaint after a successful realloc.
|
// via onPaint after a successful realloc.
|
||||||
int32_t wmReallocContentBuf(WindowT *win, const DisplayT *d);
|
int32_t wmReallocContentBuf(WindowT *win, const DisplayT *d);
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ void wmAddMenuItem(MenuT *menu, const char *label, int32_t id);
|
||||||
void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked);
|
void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked);
|
||||||
|
|
||||||
// Add a radio-style item. Radio groups are defined implicitly by
|
// Add a radio-style item. Radio groups are defined implicitly by
|
||||||
// consecutive radio items in the same menu — selecting one unchecks
|
// consecutive radio items in the same menu -- selecting one unchecks
|
||||||
// the others in the group.
|
// the others in the group.
|
||||||
void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked);
|
void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked);
|
||||||
|
|
||||||
|
|
@ -101,7 +101,7 @@ ScrollbarT *wmAddHScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
|
||||||
void wmDrawChrome(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, WindowT *win, const RectT *clipTo);
|
void wmDrawChrome(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, WindowT *win, const RectT *clipTo);
|
||||||
|
|
||||||
// Blit the window's content backbuffer into the display backbuffer,
|
// Blit the window's content backbuffer into the display backbuffer,
|
||||||
// clipped to the dirty rect. This is a pure copy (no drawing) — the
|
// clipped to the dirty rect. This is a pure copy (no drawing) -- the
|
||||||
// content was already rendered by the application into contentBuf.
|
// content was already rendered by the application into contentBuf.
|
||||||
void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *clipTo);
|
void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *clipTo);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
// dvxPlatform.h — Platform abstraction layer for DVX GUI
|
// dvxPlatform.h -- Platform abstraction layer for DVX GUI
|
||||||
//
|
//
|
||||||
// All OS-specific and CPU-specific code is isolated behind this
|
// All OS-specific and CPU-specific code is isolated behind this
|
||||||
// interface. To port DVX to a new platform, implement a new
|
// interface. To port DVX to a new platform, implement a new
|
||||||
// dvxPlatformXxx.c against this header.
|
// dvxPlatformXxx.c against this header.
|
||||||
//
|
//
|
||||||
// Currently two implementations exist:
|
// Currently two implementations exist:
|
||||||
// dvxPlatformDos.c — DJGPP/DPMI: real VESA VBE, INT 33h mouse,
|
// dvxPlatformDos.c -- DJGPP/DPMI: real VESA VBE, INT 33h mouse,
|
||||||
// INT 16h keyboard, rep movsd/stosl asm spans
|
// INT 16h keyboard, rep movsd/stosl asm spans
|
||||||
// dvxPlatformLinux.c — SDL2: software rendering to an SDL window,
|
// dvxPlatformLinux.c -- SDL2: software rendering to an SDL window,
|
||||||
// used for development and testing on Linux
|
// used for development and testing on Linux
|
||||||
//
|
//
|
||||||
// The abstraction covers five areas: video mode setup, framebuffer
|
// The abstraction covers five areas: video mode setup, framebuffer
|
||||||
|
|
@ -45,7 +45,7 @@ typedef struct {
|
||||||
// for clean shutdown on Ctrl+C/Ctrl+Break. On Linux this initializes SDL.
|
// for clean shutdown on Ctrl+C/Ctrl+Break. On Linux this initializes SDL.
|
||||||
void platformInit(void);
|
void platformInit(void);
|
||||||
|
|
||||||
// Cooperative yield — give up the CPU timeslice when the event loop has
|
// Cooperative yield -- give up the CPU timeslice when the event loop has
|
||||||
// nothing to do. On DOS this calls __dpmi_yield() to be friendly to
|
// nothing to do. On DOS this calls __dpmi_yield() to be friendly to
|
||||||
// multitaskers (Windows 3.x, OS/2, DESQview). On Linux this calls
|
// multitaskers (Windows 3.x, OS/2, DESQview). On Linux this calls
|
||||||
// SDL_Delay(1) to avoid busy-spinning at 100% CPU.
|
// SDL_Delay(1) to avoid busy-spinning at 100% CPU.
|
||||||
|
|
@ -82,7 +82,7 @@ void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t cou
|
||||||
|
|
||||||
// Copy a rectangle from the system RAM backbuffer (d->backBuf) to the
|
// Copy a rectangle from the system RAM backbuffer (d->backBuf) to the
|
||||||
// display surface (d->lfb). On DOS this copies to real video memory via
|
// display surface (d->lfb). On DOS this copies to real video memory via
|
||||||
// the LFB mapping — the critical path where PCI bus write speed matters.
|
// the LFB mapping -- the critical path where PCI bus write speed matters.
|
||||||
// On Linux this copies to the SDL surface, then SDL_UpdateRect is called.
|
// On Linux this copies to the SDL surface, then SDL_UpdateRect is called.
|
||||||
// Each scanline is copied as a contiguous block; rep movsd on DOS gives
|
// Each scanline is copied as a contiguous block; rep movsd on DOS gives
|
||||||
// near-optimal bus utilization for aligned 32-bit writes.
|
// near-optimal bus utilization for aligned 32-bit writes.
|
||||||
|
|
@ -92,7 +92,7 @@ void platformFlushRect(const DisplayT *d, const RectT *r);
|
||||||
// Optimised memory operations (span fill / copy)
|
// Optimised memory operations (span fill / copy)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// These are the innermost loops of the renderer — called once per
|
// These are the innermost loops of the renderer -- called once per
|
||||||
// scanline of every rectangle fill, blit, and text draw. On DOS they
|
// scanline of every rectangle fill, blit, and text draw. On DOS they
|
||||||
// use inline assembly: rep stosl for fills (one instruction fills an
|
// use inline assembly: rep stosl for fills (one instruction fills an
|
||||||
// entire scanline) and rep movsd for copies. On Linux they use memset/
|
// entire scanline) and rep movsd for copies. On Linux they use memset/
|
||||||
|
|
@ -112,7 +112,7 @@ void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count);
|
||||||
void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count);
|
void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Input — Mouse
|
// Input -- Mouse
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Initialize the mouse driver and constrain movement to the screen bounds.
|
// Initialize the mouse driver and constrain movement to the screen bounds.
|
||||||
|
|
@ -122,14 +122,14 @@ void platformMouseInit(int32_t screenW, int32_t screenH);
|
||||||
|
|
||||||
// Poll the current mouse state. Buttons is a bitmask: bit 0 = left,
|
// Poll the current mouse state. Buttons is a bitmask: bit 0 = left,
|
||||||
// bit 1 = right, bit 2 = middle. Polling (rather than event-driven
|
// bit 1 = right, bit 2 = middle. Polling (rather than event-driven
|
||||||
// callbacks) is the natural model for a cooperative event loop — the
|
// callbacks) is the natural model for a cooperative event loop -- the
|
||||||
// main loop polls once per frame and compares with the previous state
|
// main loop polls once per frame and compares with the previous state
|
||||||
// to detect press/release edges.
|
// to detect press/release edges.
|
||||||
void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons);
|
void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons);
|
||||||
|
|
||||||
// Detect and activate mouse wheel support. Returns true if the mouse
|
// Detect and activate mouse wheel support. Returns true if the mouse
|
||||||
// driver supports the CuteMouse Wheel API (INT 33h AX=0011h). This
|
// driver supports the CuteMouse Wheel API (INT 33h AX=0011h). This
|
||||||
// call also activates wheel reporting — after it returns true, function
|
// call also activates wheel reporting -- after it returns true, function
|
||||||
// 03h will return wheel delta in BH. Must be called after platformMouseInit.
|
// 03h will return wheel delta in BH. Must be called after platformMouseInit.
|
||||||
bool platformMouseWheelInit(void);
|
bool platformMouseWheelInit(void);
|
||||||
|
|
||||||
|
|
@ -150,7 +150,7 @@ void platformMouseSetAccel(int32_t threshold);
|
||||||
void platformMouseWarp(int32_t x, int32_t y);
|
void platformMouseWarp(int32_t x, int32_t y);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Input — Keyboard
|
// Input -- Keyboard
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Return the current modifier key state in BIOS shift-state format:
|
// Return the current modifier key state in BIOS shift-state format:
|
||||||
|
|
@ -167,7 +167,7 @@ int32_t platformKeyboardGetModifiers(void);
|
||||||
bool platformKeyboardRead(PlatformKeyEventT *evt);
|
bool platformKeyboardRead(PlatformKeyEventT *evt);
|
||||||
|
|
||||||
// Translate an Alt+key scancode to its corresponding ASCII character.
|
// Translate an Alt+key scancode to its corresponding ASCII character.
|
||||||
// When Alt is held, DOS doesn't provide the ASCII value — only the
|
// When Alt is held, DOS doesn't provide the ASCII value -- only the
|
||||||
// scancode. This function contains a lookup table mapping scancodes
|
// scancode. This function contains a lookup table mapping scancodes
|
||||||
// to their unshifted letter/digit. Returns 0 for scancodes that don't
|
// to their unshifted letter/digit. Returns 0 for scancodes that don't
|
||||||
// map to a printable character (e.g. Alt+F1).
|
// map to a printable character (e.g. Alt+F1).
|
||||||
|
|
@ -204,6 +204,11 @@ const char *platformValidateFilename(const char *name);
|
||||||
// and free physical memory in kilobytes. Returns false if unavailable.
|
// and free physical memory in kilobytes. Returns false if unavailable.
|
||||||
bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb);
|
bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb);
|
||||||
|
|
||||||
|
// Create a directory and all parent directories (like mkdir -p).
|
||||||
|
// Returns 0 on success, -1 on failure. Existing directories are not
|
||||||
|
// an error.
|
||||||
|
int32_t platformMkdirRecursive(const char *path);
|
||||||
|
|
||||||
// Change the working directory, including drive letter on DOS. Standard
|
// Change the working directory, including drive letter on DOS. Standard
|
||||||
// chdir() does not switch drives under DJGPP; this wrapper calls setdisk()
|
// chdir() does not switch drives under DJGPP; this wrapper calls setdisk()
|
||||||
// first when the path contains a drive prefix (e.g. "A:\DVX").
|
// first when the path contains a drive prefix (e.g. "A:\DVX").
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// dvxPlatformDos.c — DOS/DJGPP platform implementation for DVX GUI
|
// dvxPlatformDos.c -- DOS/DJGPP platform implementation for DVX GUI
|
||||||
//
|
//
|
||||||
// All BIOS calls, DPMI functions, port I/O, inline assembly, and
|
// All BIOS calls, DPMI functions, port I/O, inline assembly, and
|
||||||
// DOS-specific file handling are isolated in this single file.
|
// DOS-specific file handling are isolated in this single file.
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
// IRQ1 to feed the BIOS buffer. The BIOS approach is simpler and more
|
// IRQ1 to feed the BIOS buffer. The BIOS approach is simpler and more
|
||||||
// portable across emulators (DOSBox, 86Box, PCem all handle it correctly).
|
// portable across emulators (DOSBox, 86Box, PCem all handle it correctly).
|
||||||
//
|
//
|
||||||
// Why INT 33h for mouse: same rationale — the mouse driver handles
|
// Why INT 33h for mouse: same rationale -- the mouse driver handles
|
||||||
// PS/2 and serial mice transparently, and every DOS emulator provides
|
// PS/2 and serial mice transparently, and every DOS emulator provides
|
||||||
// a compatible driver. Polling via function 03h avoids the complexity
|
// a compatible driver. Polling via function 03h avoids the complexity
|
||||||
// of installing a real-mode callback for mouse events.
|
// of installing a real-mode callback for mouse events.
|
||||||
|
|
@ -31,14 +31,16 @@
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <dir.h>
|
#include <dir.h>
|
||||||
|
#include <errno.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
// DJGPP-specific headers — this is the ONLY file that includes these
|
// DJGPP-specific headers -- this is the ONLY file that includes these
|
||||||
#include <dpmi.h>
|
#include <dpmi.h>
|
||||||
#include <go32.h>
|
#include <go32.h>
|
||||||
#include <pc.h>
|
#include <pc.h>
|
||||||
|
|
@ -132,7 +134,7 @@ static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t pref
|
||||||
|
|
||||||
memset(&bestDisplay, 0, sizeof(bestDisplay));
|
memset(&bestDisplay, 0, sizeof(bestDisplay));
|
||||||
|
|
||||||
// Get VBE controller info — the transfer buffer (__tb) is the DJGPP-
|
// Get VBE controller info -- the transfer buffer (__tb) is the DJGPP-
|
||||||
// provided region in conventional memory that real-mode BIOS calls
|
// provided region in conventional memory that real-mode BIOS calls
|
||||||
// can read/write. We split it into seg:off for the INT 10h call.
|
// can read/write. We split it into seg:off for the INT 10h call.
|
||||||
uint32_t infoSeg = __tb >> 4;
|
uint32_t infoSeg = __tb >> 4;
|
||||||
|
|
@ -172,7 +174,7 @@ static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t pref
|
||||||
|
|
||||||
// VBE 2.0+ is required for LFB (Linear Frame Buffer) support.
|
// VBE 2.0+ is required for LFB (Linear Frame Buffer) support.
|
||||||
// VBE 1.x only supports bank switching, which we explicitly don't
|
// VBE 1.x only supports bank switching, which we explicitly don't
|
||||||
// implement — the complexity isn't worth it for 486+ targets.
|
// implement -- the complexity isn't worth it for 486+ targets.
|
||||||
uint16_t vbeVersion = _farpeekw(_dos_ds, __tb + 4);
|
uint16_t vbeVersion = _farpeekw(_dos_ds, __tb + 4);
|
||||||
if (vbeVersion < 0x0200) {
|
if (vbeVersion < 0x0200) {
|
||||||
fprintf(stderr, "VBE: Version %d.%d too old (need 2.0+)\n",
|
fprintf(stderr, "VBE: Version %d.%d too old (need 2.0+)\n",
|
||||||
|
|
@ -323,7 +325,7 @@ void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *
|
||||||
// pixels can't use dword-aligned rep stosl fills without masking.
|
// pixels can't use dword-aligned rep stosl fills without masking.
|
||||||
//
|
//
|
||||||
// The physical LFB address is temporarily stored in d->lfb as a raw
|
// The physical LFB address is temporarily stored in d->lfb as a raw
|
||||||
// integer cast — it will be properly mapped via DPMI in mapLfb() later.
|
// integer cast -- it will be properly mapped via DPMI in mapLfb() later.
|
||||||
|
|
||||||
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
|
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
|
||||||
__dpmi_regs r;
|
__dpmi_regs r;
|
||||||
|
|
@ -343,7 +345,7 @@ static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requ
|
||||||
|
|
||||||
// VBE mode attribute word at offset 0:
|
// VBE mode attribute word at offset 0:
|
||||||
// bit 7 = LFB available, bit 4 = graphics mode (not text)
|
// bit 7 = LFB available, bit 4 = graphics mode (not text)
|
||||||
// Both are required — we never bank-switch and never want text modes.
|
// Both are required -- we never bank-switch and never want text modes.
|
||||||
uint16_t attr = _farpeekw(_dos_ds, __tb + 0);
|
uint16_t attr = _farpeekw(_dos_ds, __tb + 0);
|
||||||
|
|
||||||
if (!(attr & 0x0080)) {
|
if (!(attr & 0x0080)) {
|
||||||
|
|
@ -442,14 +444,14 @@ static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requ
|
||||||
// access.
|
// access.
|
||||||
//
|
//
|
||||||
// The mapping process has three steps:
|
// The mapping process has three steps:
|
||||||
// 1. __dpmi_physical_address_mapping() — asks the DPMI host to
|
// 1. __dpmi_physical_address_mapping() -- asks the DPMI host to
|
||||||
// create a linear address mapping for the physical framebuffer.
|
// create a linear address mapping for the physical framebuffer.
|
||||||
// This is necessary because DPMI runs in protected mode with
|
// This is necessary because DPMI runs in protected mode with
|
||||||
// paging; physical addresses aren't directly accessible.
|
// paging; physical addresses aren't directly accessible.
|
||||||
// 2. __dpmi_lock_linear_region() — pins the mapped pages so they
|
// 2. __dpmi_lock_linear_region() -- pins the mapped pages so they
|
||||||
// can't be swapped out. The LFB is memory-mapped I/O to the
|
// can't be swapped out. The LFB is memory-mapped I/O to the
|
||||||
// video card; paging it would be catastrophic.
|
// video card; paging it would be catastrophic.
|
||||||
// 3. __djgpp_nearptr_enable() — disables DJGPP's default segment
|
// 3. __djgpp_nearptr_enable() -- disables DJGPP's default segment
|
||||||
// limit checking so we can use plain C pointers to access the
|
// limit checking so we can use plain C pointers to access the
|
||||||
// LFB address. Without this, all LFB access would require far
|
// LFB address. Without this, all LFB access would require far
|
||||||
// pointer calls (_farpokeb etc.), which are much slower because
|
// pointer calls (_farpokeb etc.), which are much slower because
|
||||||
|
|
@ -457,7 +459,7 @@ static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requ
|
||||||
//
|
//
|
||||||
// Why near pointers: the performance difference is dramatic.
|
// Why near pointers: the performance difference is dramatic.
|
||||||
// platformFlushRect() copies thousands of dwords per frame using
|
// platformFlushRect() copies thousands of dwords per frame using
|
||||||
// rep movsl — this only works with near pointers. Far pointer access
|
// rep movsl -- this only works with near pointers. Far pointer access
|
||||||
// would add ~10 cycles per byte and make 60fps impossible on a 486.
|
// would add ~10 cycles per byte and make 60fps impossible on a 486.
|
||||||
//
|
//
|
||||||
// The final pointer calculation adds __djgpp_conventional_base, which
|
// The final pointer calculation adds __djgpp_conventional_base, which
|
||||||
|
|
@ -529,7 +531,7 @@ void platformChdir(const char *path) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Copies a dirty rectangle from the system RAM backbuffer to the LFB.
|
// Copies a dirty rectangle from the system RAM backbuffer to the LFB.
|
||||||
// This is the critical path for display updates — the compositor calls
|
// This is the critical path for display updates -- the compositor calls
|
||||||
// it once per dirty rect per frame.
|
// it once per dirty rect per frame.
|
||||||
//
|
//
|
||||||
// Two code paths:
|
// Two code paths:
|
||||||
|
|
@ -580,7 +582,7 @@ void platformFlushRect(const DisplayT *d, const RectT *r) {
|
||||||
*dst++ = *src++;
|
*dst++ = *src++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Partial scanlines — copy row by row with rep movsd
|
// Partial scanlines -- copy row by row with rep movsd
|
||||||
int32_t dwords = rowBytes >> 2;
|
int32_t dwords = rowBytes >> 2;
|
||||||
int32_t remainder = rowBytes & 3;
|
int32_t remainder = rowBytes & 3;
|
||||||
for (int32_t i = 0; i < h; i++) {
|
for (int32_t i = 0; i < h; i++) {
|
||||||
|
|
@ -607,7 +609,7 @@ void platformFlushRect(const DisplayT *d, const RectT *r) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// System information — static buffer and helpers
|
// System information -- static buffer and helpers
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static char sSysInfoBuf[PLATFORM_SYSINFO_MAX];
|
static char sSysInfoBuf[PLATFORM_SYSINFO_MAX];
|
||||||
|
|
@ -637,7 +639,7 @@ static void sysInfoAppend(const char *fmt, ...) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// estimateClockMhz — RDTSC calibration via BIOS timer
|
// estimateClockMhz -- RDTSC calibration via BIOS timer
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Measures TSC ticks over 3 BIOS timer ticks (~165 ms). The BIOS timer
|
// Measures TSC ticks over 3 BIOS timer ticks (~165 ms). The BIOS timer
|
||||||
|
|
@ -688,7 +690,7 @@ static uint32_t estimateClockMhz(void) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// hasCpuid — check if CPUID instruction is available
|
// hasCpuid -- check if CPUID instruction is available
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// The CPUID instruction exists if bit 21 (ID flag) of EFLAGS can be
|
// The CPUID instruction exists if bit 21 (ID flag) of EFLAGS can be
|
||||||
|
|
@ -1079,15 +1081,67 @@ bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// platformMkdirRecursive
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Creates a directory and all parent directories that don't exist.
|
||||||
|
// Works by walking the path from left to right, creating each
|
||||||
|
// component. mkdir() on an existing directory returns EEXIST which
|
||||||
|
// is silently ignored.
|
||||||
|
|
||||||
|
int32_t platformMkdirRecursive(const char *path) {
|
||||||
|
char buf[260];
|
||||||
|
strncpy(buf, path, sizeof(buf) - 1);
|
||||||
|
buf[sizeof(buf) - 1] = '\0';
|
||||||
|
|
||||||
|
for (char *p = buf; *p; p++) {
|
||||||
|
// Skip drive letter (e.g. "C:\")
|
||||||
|
if (p == buf + 1 && *p == ':') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*p == '/' || *p == '\\') {
|
||||||
|
*p = '\0';
|
||||||
|
|
||||||
|
if (buf[0] != '\0') {
|
||||||
|
mkdir(buf, 0755);
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the final directory
|
||||||
|
if (mkdir(buf, 0755) != 0 && errno != EEXIST) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// platformInit
|
// platformInit
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void platformInit(void) {
|
void platformInit(void) {
|
||||||
// Disable Ctrl+C/Break so the user can't accidentally kill the
|
// Disable Ctrl+C/Break at every level so the user can't
|
||||||
// GUI while in graphics mode (which would leave the display in
|
// accidentally kill the GUI while in graphics mode.
|
||||||
// an unusable state without restoring text mode first).
|
|
||||||
|
// 1. DJGPP-level: disable Ctrl+C detection in the runtime
|
||||||
|
__djgpp_set_ctrl_c(0);
|
||||||
|
|
||||||
|
// 2. C-level: ignore SIGINT if it somehow fires
|
||||||
signal(SIGINT, SIG_IGN);
|
signal(SIGINT, SIG_IGN);
|
||||||
|
|
||||||
|
// 3. DOS-level: disable break checking during INT 21h calls
|
||||||
|
// (function 33h, subfunction 01h, DL=0 = break off)
|
||||||
|
__dpmi_regs r;
|
||||||
|
memset(&r, 0, sizeof(r));
|
||||||
|
r.x.ax = 0x3301;
|
||||||
|
r.x.dx = 0x0000;
|
||||||
|
__dpmi_int(0x21, &r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1125,7 +1179,7 @@ int32_t platformKeyboardGetModifiers(void) {
|
||||||
// functions have been standard since AT-class machines (1984).
|
// functions have been standard since AT-class machines (1984).
|
||||||
//
|
//
|
||||||
// The two-step peek-then-read is necessary because function 10h
|
// The two-step peek-then-read is necessary because function 10h
|
||||||
// (read key) blocks until a key is available — there's no non-blocking
|
// (read key) blocks until a key is available -- there's no non-blocking
|
||||||
// read in the BIOS API. Function 11h (check key) peeks without
|
// read in the BIOS API. Function 11h (check key) peeks without
|
||||||
// consuming, letting us poll without blocking the event loop.
|
// consuming, letting us poll without blocking the event loop.
|
||||||
|
|
||||||
|
|
@ -1200,7 +1254,7 @@ int32_t platformStripLineEndings(char *buf, int32_t len) {
|
||||||
// because the default range may be 640x200 (CGA text mode).
|
// because the default range may be 640x200 (CGA text mode).
|
||||||
// Without this, mouse coordinates would be wrong or clipped.
|
// Without this, mouse coordinates would be wrong or clipped.
|
||||||
//
|
//
|
||||||
// The hardware cursor is never shown — DVX composites its own
|
// The hardware cursor is never shown -- DVX composites its own
|
||||||
// software cursor on top of the backbuffer. We only use INT 33h
|
// software cursor on top of the backbuffer. We only use INT 33h
|
||||||
// for position/button state via polling (function 03h).
|
// for position/button state via polling (function 03h).
|
||||||
|
|
||||||
|
|
@ -1390,7 +1444,7 @@ char *platformPathDirEnd(const char *path) {
|
||||||
// bus speed.
|
// bus speed.
|
||||||
//
|
//
|
||||||
// rep movsl moves 4 bytes per iteration with hardware loop decrement,
|
// rep movsl moves 4 bytes per iteration with hardware loop decrement,
|
||||||
// which is faster than a C for-loop — the CPU string move pipeline
|
// which is faster than a C for-loop -- the CPU string move pipeline
|
||||||
// optimizes sequential memory access patterns.
|
// optimizes sequential memory access patterns.
|
||||||
|
|
||||||
void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count) {
|
void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||||||
|
|
@ -1461,7 +1515,7 @@ void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// 32-bit pixels are inherently dword-aligned, so no alignment
|
// 32-bit pixels are inherently dword-aligned, so no alignment
|
||||||
// preamble is needed — straight to rep movsl.
|
// preamble is needed -- straight to rep movsl.
|
||||||
|
|
||||||
void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count) {
|
void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count) {
|
||||||
__asm__ __volatile__ (
|
__asm__ __volatile__ (
|
||||||
|
|
@ -1487,7 +1541,7 @@ void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count) {
|
||||||
uint8_t c = (uint8_t)color;
|
uint8_t c = (uint8_t)color;
|
||||||
uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24);
|
uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24);
|
||||||
|
|
||||||
// Align to 4 bytes — skip if already aligned
|
// Align to 4 bytes -- skip if already aligned
|
||||||
if (__builtin_expect((uintptr_t)dst & 3, 0)) {
|
if (__builtin_expect((uintptr_t)dst & 3, 0)) {
|
||||||
while (((uintptr_t)dst & 3) && count > 0) {
|
while (((uintptr_t)dst & 3) && count > 0) {
|
||||||
*dst++ = c;
|
*dst++ = c;
|
||||||
|
|
@ -1555,7 +1609,7 @@ void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count) {
|
||||||
// platformSpanFill32
|
// platformSpanFill32
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// 32-bit fill is the simplest case — each pixel is already a dword,
|
// 32-bit fill is the simplest case -- each pixel is already a dword,
|
||||||
// so rep stosl writes exactly one pixel per iteration with no
|
// so rep stosl writes exactly one pixel per iteration with no
|
||||||
// alignment or packing concerns.
|
// alignment or packing concerns.
|
||||||
|
|
||||||
|
|
@ -1570,7 +1624,7 @@ void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// platformValidateFilename — DOS 8.3 filename validation
|
// platformValidateFilename -- DOS 8.3 filename validation
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Validates that a filename conforms to DOS 8.3 conventions:
|
// Validates that a filename conforms to DOS 8.3 conventions:
|
||||||
|
|
@ -1580,7 +1634,7 @@ void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count) {
|
||||||
//
|
//
|
||||||
// The reserved name check compares the base name only (before the
|
// The reserved name check compares the base name only (before the
|
||||||
// dot), case-insensitive, because DOS treats "CON.TXT" the same
|
// dot), case-insensitive, because DOS treats "CON.TXT" the same
|
||||||
// as the CON device — the extension is ignored for device names.
|
// as the CON device -- the extension is ignored for device names.
|
||||||
//
|
//
|
||||||
// Returns NULL on success, or a human-readable error string on failure.
|
// Returns NULL on success, or a human-readable error string on failure.
|
||||||
// On non-DOS platforms, this function would be replaced with one that
|
// On non-DOS platforms, this function would be replaced with one that
|
||||||
|
|
@ -1664,9 +1718,9 @@ const char *platformValidateFilename(const char *name) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Complete video initialization sequence:
|
// Complete video initialization sequence:
|
||||||
// 1. findBestMode() — enumerate VESA modes and pick the best match
|
// 1. findBestMode() -- enumerate VESA modes and pick the best match
|
||||||
// 2. setVesaMode() — actually switch to the chosen mode with LFB
|
// 2. setVesaMode() -- actually switch to the chosen mode with LFB
|
||||||
// 3. mapLfb() — DPMI-map the physical framebuffer into linear memory
|
// 3. mapLfb() -- DPMI-map the physical framebuffer into linear memory
|
||||||
// 4. Allocate system RAM backbuffer (same size as LFB)
|
// 4. Allocate system RAM backbuffer (same size as LFB)
|
||||||
// 5. Set up 8-bit palette if needed
|
// 5. Set up 8-bit palette if needed
|
||||||
// 6. Initialize clip rect to full display
|
// 6. Initialize clip rect to full display
|
||||||
|
|
@ -1755,7 +1809,7 @@ int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, i
|
||||||
//
|
//
|
||||||
// Direct port I/O is used instead of VBE function 09h (set palette)
|
// Direct port I/O is used instead of VBE function 09h (set palette)
|
||||||
// because the VGA DAC ports are faster (no BIOS call overhead) and
|
// because the VGA DAC ports are faster (no BIOS call overhead) and
|
||||||
// universally compatible — even VBE 3.0 cards still have the standard
|
// universally compatible -- even VBE 3.0 cards still have the standard
|
||||||
// VGA DAC at ports 0x3C8/0x3C9.
|
// VGA DAC at ports 0x3C8/0x3C9.
|
||||||
|
|
||||||
void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) {
|
void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetAnsiTerm.c — ANSI BBS terminal emulator widget
|
// widgetAnsiTerm.c -- ANSI BBS terminal emulator widget
|
||||||
//
|
//
|
||||||
// Implements a VT100/ANSI-compatible terminal emulator widget designed for
|
// Implements a VT100/ANSI-compatible terminal emulator widget designed for
|
||||||
// connecting to BBS systems over the serial link. The terminal uses a
|
// connecting to BBS systems over the serial link. The terminal uses a
|
||||||
|
|
@ -7,15 +7,15 @@
|
||||||
// chosen because:
|
// chosen because:
|
||||||
// 1. It maps directly to the BBS/ANSI art paradigm (CP437 character set,
|
// 1. It maps directly to the BBS/ANSI art paradigm (CP437 character set,
|
||||||
// 16-color CGA palette, blink attribute)
|
// 16-color CGA palette, blink attribute)
|
||||||
// 2. Cell-based storage is extremely compact — 2 bytes per cell means an
|
// 2. Cell-based storage is extremely compact -- 2 bytes per cell means an
|
||||||
// 80x25 screen is only 4000 bytes, fitting in L1 cache on a 486
|
// 80x25 screen is only 4000 bytes, fitting in L1 cache on a 486
|
||||||
// 3. Dirty-row tracking via a 32-bit bitmask allows sub-millisecond
|
// 3. Dirty-row tracking via a 32-bit bitmask allows sub-millisecond
|
||||||
// incremental repaints without scanning the entire buffer
|
// incremental repaints without scanning the entire buffer
|
||||||
//
|
//
|
||||||
// The ANSI parser is a 3-state machine (NORMAL → ESC → CSI) that handles
|
// The ANSI parser is a 3-state machine (NORMAL -> ESC -> CSI) that handles
|
||||||
// the subset of sequences commonly used by DOS BBS software: cursor movement,
|
// the subset of sequences commonly used by DOS BBS software: cursor movement,
|
||||||
// screen/line erase, scrolling regions, SGR colors, and a few DEC private modes.
|
// screen/line erase, scrolling regions, SGR colors, and a few DEC private modes.
|
||||||
// Full VT100 conformance is explicitly NOT a goal — only sequences actually
|
// Full VT100 conformance is explicitly NOT a goal -- only sequences actually
|
||||||
// emitted by real BBS systems are implemented.
|
// emitted by real BBS systems are implemented.
|
||||||
//
|
//
|
||||||
// Scrollback is implemented as a circular buffer of row snapshots. Only
|
// Scrollback is implemented as a circular buffer of row snapshots. Only
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
// - Fast repaint (wgtAnsiTermRepaint): bypasses the widget pipeline
|
// - Fast repaint (wgtAnsiTermRepaint): bypasses the widget pipeline
|
||||||
// entirely, rendering dirty rows directly into the window's content
|
// entirely, rendering dirty rows directly into the window's content
|
||||||
// buffer. This is critical for serial communication where ACK turnaround
|
// buffer. This is critical for serial communication where ACK turnaround
|
||||||
// time matters — the fewer milliseconds between receiving data and
|
// time matters -- the fewer milliseconds between receiving data and
|
||||||
// displaying it, the higher the effective throughput.
|
// displaying it, the higher the effective throughput.
|
||||||
//
|
//
|
||||||
// Communication is abstracted through read/write function pointers, allowing
|
// Communication is abstracted through read/write function pointers, allowing
|
||||||
|
|
@ -135,7 +135,7 @@ static void ansiTermSelectionRange(const WidgetT *w, int32_t *startLine, int32_t
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Copy a screen row into the scrollback circular buffer.
|
// Copy a screen row into the scrollback circular buffer.
|
||||||
// The circular buffer avoids any need for memmove when the buffer fills —
|
// The circular buffer avoids any need for memmove when the buffer fills --
|
||||||
// the head index simply wraps around, overwriting the oldest entry. This
|
// the head index simply wraps around, overwriting the oldest entry. This
|
||||||
// is O(1) per row regardless of scrollback size, which matters when BBS
|
// is O(1) per row regardless of scrollback size, which matters when BBS
|
||||||
// software rapidly dumps text (e.g. file listings, ANSI art).
|
// software rapidly dumps text (e.g. file listings, ANSI art).
|
||||||
|
|
@ -225,7 +225,7 @@ static void ansiTermCopySelection(WidgetT *w) {
|
||||||
|
|
||||||
// Build text from selected cells (strip trailing spaces per line).
|
// Build text from selected cells (strip trailing spaces per line).
|
||||||
// Trailing spaces are stripped because BBS text mode fills the entire
|
// Trailing spaces are stripped because BBS text mode fills the entire
|
||||||
// row with spaces — without stripping, pasting would include many
|
// row with spaces -- without stripping, pasting would include many
|
||||||
// unwanted trailing blanks. Fixed 4KB buffer is sufficient for typical
|
// unwanted trailing blanks. Fixed 4KB buffer is sufficient for typical
|
||||||
// terminal selections (80 cols * 50 rows = 4000 chars max).
|
// terminal selections (80 cols * 50 rows = 4000 chars max).
|
||||||
char buf[4096];
|
char buf[4096];
|
||||||
|
|
@ -269,7 +269,7 @@ static void ansiTermCopySelection(WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Mark rows dirty that are touched by a cell range [startCell, startCell+count).
|
// Mark rows dirty that are touched by a cell range [startCell, startCell+count).
|
||||||
// Uses a 32-bit bitmask — one bit per row — which limits tracking to the first
|
// Uses a 32-bit bitmask -- one bit per row -- which limits tracking to the first
|
||||||
// 32 rows. This is fine because standard terminal sizes are 24-25 rows, and
|
// 32 rows. This is fine because standard terminal sizes are 24-25 rows, and
|
||||||
// bitmask operations are single-cycle on the target CPU. The bitmask approach
|
// bitmask operations are single-cycle on the target CPU. The bitmask approach
|
||||||
// is much cheaper than maintaining a dirty rect list for per-row tracking.
|
// is much cheaper than maintaining a dirty rect list for per-row tracking.
|
||||||
|
|
@ -345,7 +345,7 @@ static void ansiTermDeleteLines(WidgetT *w, int32_t count) {
|
||||||
|
|
||||||
// Central CSI dispatcher. After the parser accumulates parameters in the CSI
|
// Central CSI dispatcher. After the parser accumulates parameters in the CSI
|
||||||
// state, the final byte triggers dispatch here. Only sequences commonly used
|
// state, the final byte triggers dispatch here. Only sequences commonly used
|
||||||
// by BBS software are implemented — exotic VT220+ sequences are silently ignored.
|
// by BBS software are implemented -- exotic VT220+ sequences are silently ignored.
|
||||||
// DEC private modes (ESC[?...) are handled separately since they use a different
|
// DEC private modes (ESC[?...) are handled separately since they use a different
|
||||||
// parameter namespace than standard ECMA-48 sequences.
|
// parameter namespace than standard ECMA-48 sequences.
|
||||||
static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
|
|
@ -474,7 +474,7 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
if (w->as.ansiTerm->commWrite) {
|
if (w->as.ansiTerm->commWrite) {
|
||||||
// Respond as VT100 with advanced video option (AVO).
|
// Respond as VT100 with advanced video option (AVO).
|
||||||
// Many BBS door games query DA to detect terminal capabilities.
|
// Many BBS door games query DA to detect terminal capabilities.
|
||||||
// Claiming VT100+AVO is the safest response — it tells the remote
|
// Claiming VT100+AVO is the safest response -- it tells the remote
|
||||||
// side we support ANSI color without implying VT220+ features.
|
// side we support ANSI color without implying VT220+ features.
|
||||||
const uint8_t reply[] = "\033[?1;2c";
|
const uint8_t reply[] = "\033[?1;2c";
|
||||||
w->as.ansiTerm->commWrite(w->as.ansiTerm->commCtx, reply, 7);
|
w->as.ansiTerm->commWrite(w->as.ansiTerm->commCtx, reply, 7);
|
||||||
|
|
@ -632,7 +632,7 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
|
|
||||||
if (w->as.ansiTerm->commWrite) {
|
if (w->as.ansiTerm->commWrite) {
|
||||||
if (mode == 6) {
|
if (mode == 6) {
|
||||||
// CPR — cursor position report: ESC[row;colR (1-based).
|
// CPR -- cursor position report: ESC[row;colR (1-based).
|
||||||
// BBS software uses this for screen-size detection and
|
// BBS software uses this for screen-size detection and
|
||||||
// to synchronize cursor positioning in door games.
|
// to synchronize cursor positioning in door games.
|
||||||
char reply[16];
|
char reply[16];
|
||||||
|
|
@ -673,7 +673,7 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
w->as.ansiTerm->scrollTop = top;
|
w->as.ansiTerm->scrollTop = top;
|
||||||
w->as.ansiTerm->scrollBot = bot;
|
w->as.ansiTerm->scrollBot = bot;
|
||||||
} else {
|
} else {
|
||||||
// Invalid or reset — restore full screen
|
// Invalid or reset -- restore full screen
|
||||||
w->as.ansiTerm->scrollTop = 0;
|
w->as.ansiTerm->scrollTop = 0;
|
||||||
w->as.ansiTerm->scrollBot = rows - 1;
|
w->as.ansiTerm->scrollBot = rows - 1;
|
||||||
}
|
}
|
||||||
|
|
@ -706,7 +706,7 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
|
|
||||||
// Erase Display (ED): mode 0 = cursor to end, 1 = start to cursor, 2 = all.
|
// Erase Display (ED): mode 0 = cursor to end, 1 = start to cursor, 2 = all.
|
||||||
// Mode 2 pushes all visible lines to scrollback before clearing, preserving
|
// Mode 2 pushes all visible lines to scrollback before clearing, preserving
|
||||||
// content that was on screen — this is what users expect when a BBS sends
|
// content that was on screen -- this is what users expect when a BBS sends
|
||||||
// a clear-screen sequence (they can scroll back to see previous content).
|
// a clear-screen sequence (they can scroll back to see previous content).
|
||||||
// The wasAtBottom check ensures auto-scroll tracking: if the user was
|
// The wasAtBottom check ensures auto-scroll tracking: if the user was
|
||||||
// already viewing the latest content, they stay at the bottom after new
|
// already viewing the latest content, they stay at the bottom after new
|
||||||
|
|
@ -723,7 +723,7 @@ static void ansiTermEraseDisplay(WidgetT *w, int32_t mode) {
|
||||||
// Erase from start to cursor
|
// Erase from start to cursor
|
||||||
ansiTermFillCells(w, 0, cur + 1);
|
ansiTermFillCells(w, 0, cur + 1);
|
||||||
} else if (mode == 2) {
|
} else if (mode == 2) {
|
||||||
// Erase entire screen — push all lines to scrollback first
|
// Erase entire screen -- push all lines to scrollback first
|
||||||
bool wasAtBottom = (w->as.ansiTerm->scrollPos == w->as.ansiTerm->scrollbackCount);
|
bool wasAtBottom = (w->as.ansiTerm->scrollPos == w->as.ansiTerm->scrollbackCount);
|
||||||
|
|
||||||
for (int32_t r = 0; r < rows; r++) {
|
for (int32_t r = 0; r < rows; r++) {
|
||||||
|
|
@ -768,7 +768,7 @@ static void ansiTermEraseLine(WidgetT *w, int32_t mode) {
|
||||||
//
|
//
|
||||||
// Fill a range of cells with space + current attribute.
|
// Fill a range of cells with space + current attribute.
|
||||||
// Uses the current attribute (not default) so that erasing respects the
|
// Uses the current attribute (not default) so that erasing respects the
|
||||||
// currently active background color — this is correct per ECMA-48 and
|
// currently active background color -- this is correct per ECMA-48 and
|
||||||
// matches what BBS software expects (e.g., colored backgrounds that
|
// matches what BBS software expects (e.g., colored backgrounds that
|
||||||
// persist after a line erase).
|
// persist after a line erase).
|
||||||
|
|
||||||
|
|
@ -801,10 +801,10 @@ static void ansiTermFillCells(WidgetT *w, int32_t start, int32_t count) {
|
||||||
//
|
//
|
||||||
// Get a pointer to the cell data for a given line index in the
|
// Get a pointer to the cell data for a given line index in the
|
||||||
// combined scrollback+screen view.
|
// combined scrollback+screen view.
|
||||||
// lineIndex < scrollbackCount → scrollback line
|
// lineIndex < scrollbackCount -> scrollback line
|
||||||
// lineIndex >= scrollbackCount → screen line
|
// lineIndex >= scrollbackCount -> screen line
|
||||||
//
|
//
|
||||||
// The unified line index simplifies paint and selection code — they don't
|
// The unified line index simplifies paint and selection code -- they don't
|
||||||
// need to know whether a given line is in scrollback or on screen. The
|
// need to know whether a given line is in scrollback or on screen. The
|
||||||
// circular buffer index computation uses modular arithmetic to map from
|
// circular buffer index computation uses modular arithmetic to map from
|
||||||
// logical scrollback line number to physical buffer position.
|
// logical scrollback line number to physical buffer position.
|
||||||
|
|
@ -909,15 +909,15 @@ static void ansiTermNewline(WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Feed one byte through the ANSI parser state machine.
|
// Feed one byte through the ANSI parser state machine.
|
||||||
// This is the hot path for data reception — called once per byte received
|
// This is the hot path for data reception -- called once per byte received
|
||||||
// from the serial link. The state machine is kept as simple as possible
|
// from the serial link. The state machine is kept as simple as possible
|
||||||
// (no tables, no indirect calls) because this runs on every incoming byte
|
// (no tables, no indirect calls) because this runs on every incoming byte
|
||||||
// and branch prediction on a 486/Pentium benefits from straightforward
|
// and branch prediction on a 486/Pentium benefits from straightforward
|
||||||
// if/switch chains.
|
// if/switch chains.
|
||||||
//
|
//
|
||||||
// In PARSE_NORMAL, C0 control characters (CR, LF, BS, TAB, FF, BEL, ESC)
|
// In PARSE_NORMAL, C0 control characters (CR, LF, BS, TAB, FF, BEL, ESC)
|
||||||
// are handled first. All other bytes — including CP437 graphic characters
|
// are handled first. All other bytes -- including CP437 graphic characters
|
||||||
// in the 0x01-0x1F range that aren't C0 controls, plus 0x80-0xFF — are
|
// in the 0x01-0x1F range that aren't C0 controls, plus 0x80-0xFF -- are
|
||||||
// treated as printable and placed at the cursor via ansiTermPutChar.
|
// treated as printable and placed at the cursor via ansiTermPutChar.
|
||||||
|
|
||||||
static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
|
static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
|
||||||
|
|
@ -942,12 +942,12 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
|
||||||
w->as.ansiTerm->cursorCol = w->as.ansiTerm->cols - 1;
|
w->as.ansiTerm->cursorCol = w->as.ansiTerm->cols - 1;
|
||||||
}
|
}
|
||||||
} else if (ch == '\f') {
|
} else if (ch == '\f') {
|
||||||
// Form feed — clear screen and home cursor
|
// Form feed -- clear screen and home cursor
|
||||||
ansiTermEraseDisplay(w, 2);
|
ansiTermEraseDisplay(w, 2);
|
||||||
w->as.ansiTerm->cursorRow = 0;
|
w->as.ansiTerm->cursorRow = 0;
|
||||||
w->as.ansiTerm->cursorCol = 0;
|
w->as.ansiTerm->cursorCol = 0;
|
||||||
} else if (ch == '\a') {
|
} else if (ch == '\a') {
|
||||||
// Bell — ignored
|
// Bell -- ignored
|
||||||
} else {
|
} else {
|
||||||
// CP437 graphic characters (smileys, card suits, etc.)
|
// CP437 graphic characters (smileys, card suits, etc.)
|
||||||
// and all printable characters
|
// and all printable characters
|
||||||
|
|
@ -962,15 +962,15 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
|
||||||
w->as.ansiTerm->csiPrivate = false;
|
w->as.ansiTerm->csiPrivate = false;
|
||||||
memset(w->as.ansiTerm->params, 0, sizeof(w->as.ansiTerm->params));
|
memset(w->as.ansiTerm->params, 0, sizeof(w->as.ansiTerm->params));
|
||||||
} else if (ch == 'D') {
|
} else if (ch == 'D') {
|
||||||
// IND — scroll up one line
|
// IND -- scroll up one line
|
||||||
ansiTermScrollUp(w);
|
ansiTermScrollUp(w);
|
||||||
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
||||||
} else if (ch == 'M') {
|
} else if (ch == 'M') {
|
||||||
// RI — scroll down one line
|
// RI -- scroll down one line
|
||||||
ansiTermScrollDown(w);
|
ansiTermScrollDown(w);
|
||||||
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
||||||
} else if (ch == 'c') {
|
} else if (ch == 'c') {
|
||||||
// RIS — terminal reset
|
// RIS -- terminal reset
|
||||||
w->as.ansiTerm->cursorRow = 0;
|
w->as.ansiTerm->cursorRow = 0;
|
||||||
w->as.ansiTerm->cursorCol = 0;
|
w->as.ansiTerm->cursorCol = 0;
|
||||||
w->as.ansiTerm->curAttr = ANSI_DEFAULT_ATTR;
|
w->as.ansiTerm->curAttr = ANSI_DEFAULT_ATTR;
|
||||||
|
|
@ -983,7 +983,7 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
|
||||||
ansiTermEraseDisplay(w, 2);
|
ansiTermEraseDisplay(w, 2);
|
||||||
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
||||||
} else {
|
} else {
|
||||||
// Unknown escape — return to normal
|
// Unknown escape -- return to normal
|
||||||
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -1011,11 +1011,11 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
|
||||||
w->as.ansiTerm->paramCount++;
|
w->as.ansiTerm->paramCount++;
|
||||||
}
|
}
|
||||||
} else if (ch >= 0x40 && ch <= 0x7E) {
|
} else if (ch >= 0x40 && ch <= 0x7E) {
|
||||||
// Final byte — dispatch the CSI sequence
|
// Final byte -- dispatch the CSI sequence
|
||||||
ansiTermDispatchCsi(w, ch);
|
ansiTermDispatchCsi(w, ch);
|
||||||
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
||||||
} else {
|
} else {
|
||||||
// Unexpected byte — abort sequence
|
// Unexpected byte -- abort sequence
|
||||||
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
w->as.ansiTerm->parseState = PARSE_NORMAL;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -1061,7 +1061,7 @@ static void ansiTermProcessSgr(WidgetT *w) {
|
||||||
w->as.ansiTerm->bold = true;
|
w->as.ansiTerm->bold = true;
|
||||||
fg |= 8;
|
fg |= 8;
|
||||||
} else if (code == 5) {
|
} else if (code == 5) {
|
||||||
// Blink — sets bit 7 of attr byte via bg bit 3
|
// Blink -- sets bit 7 of attr byte via bg bit 3
|
||||||
bg |= 8;
|
bg |= 8;
|
||||||
} else if (code == 25) {
|
} else if (code == 25) {
|
||||||
// Blink off
|
// Blink off
|
||||||
|
|
@ -1072,7 +1072,7 @@ static void ansiTermProcessSgr(WidgetT *w) {
|
||||||
fg = bg;
|
fg = bg;
|
||||||
bg = tmp;
|
bg = tmp;
|
||||||
} else if (code == 8) {
|
} else if (code == 8) {
|
||||||
// Invisible — foreground same as background
|
// Invisible -- foreground same as background
|
||||||
fg = bg & 0x07;
|
fg = bg & 0x07;
|
||||||
} else if (code == 22) {
|
} else if (code == 22) {
|
||||||
// Normal intensity
|
// Normal intensity
|
||||||
|
|
@ -1267,7 +1267,7 @@ static void ansiTermSelectionRange(const WidgetT *w, int32_t *startLine, int32_t
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Create a new ANSI terminal widget. The cell buffer is allocated as a
|
// Create a new ANSI terminal widget. The cell buffer is allocated as a
|
||||||
// flat array of (char, attr) pairs — rows * cols * 2 bytes. The separate
|
// flat array of (char, attr) pairs -- rows * cols * 2 bytes. The separate
|
||||||
// heap allocation for AnsiTermDataT (via calloc) keeps the WidgetT union
|
// heap allocation for AnsiTermDataT (via calloc) keeps the WidgetT union
|
||||||
// small; terminal state is substantial (~100+ bytes of fields plus the
|
// small; terminal state is substantial (~100+ bytes of fields plus the
|
||||||
// cell and scrollback buffers) so it's pointed to rather than inlined.
|
// cell and scrollback buffers) so it's pointed to rather than inlined.
|
||||||
|
|
@ -1434,19 +1434,19 @@ void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines) {
|
||||||
// Poll the terminal for incoming data and update timers. This should be called
|
// Poll the terminal for incoming data and update timers. This should be called
|
||||||
// from the application's main loop at a reasonable frequency. It handles three
|
// from the application's main loop at a reasonable frequency. It handles three
|
||||||
// things:
|
// things:
|
||||||
// 1. Text blink timer (500ms) — toggles visibility of cells with the blink
|
// 1. Text blink timer (500ms) -- toggles visibility of cells with the blink
|
||||||
// attribute. Only rows containing blink cells are dirtied, avoiding
|
// attribute. Only rows containing blink cells are dirtied, avoiding
|
||||||
// unnecessary repaints of static content.
|
// unnecessary repaints of static content.
|
||||||
// 2. Cursor blink timer (250ms) — faster than text blink to feel responsive.
|
// 2. Cursor blink timer (250ms) -- faster than text blink to feel responsive.
|
||||||
// Only the cursor's row is dirtied.
|
// Only the cursor's row is dirtied.
|
||||||
// 3. Comm read — pulls up to 256 bytes from the transport and feeds them
|
// 3. Comm read -- pulls up to 256 bytes from the transport and feeds them
|
||||||
// through the ANSI parser. The 256-byte chunk size balances between
|
// through the ANSI parser. The 256-byte chunk size balances between
|
||||||
// responsiveness (smaller = more frequent repaints) and throughput
|
// responsiveness (smaller = more frequent repaints) and throughput
|
||||||
// (larger = fewer function call overhead per byte).
|
// (larger = fewer function call overhead per byte).
|
||||||
int32_t wgtAnsiTermPoll(WidgetT *w) {
|
int32_t wgtAnsiTermPoll(WidgetT *w) {
|
||||||
VALIDATE_WIDGET(w, WidgetAnsiTermE, 0);
|
VALIDATE_WIDGET(w, WidgetAnsiTermE, 0);
|
||||||
|
|
||||||
// Text blink timer — toggle visibility and dirty rows with blinking cells
|
// Text blink timer -- toggle visibility and dirty rows with blinking cells
|
||||||
clock_t now = clock();
|
clock_t now = clock();
|
||||||
clock_t blinkInterval = (clock_t)BLINK_MS * CLOCKS_PER_SEC / 1000;
|
clock_t blinkInterval = (clock_t)BLINK_MS * CLOCKS_PER_SEC / 1000;
|
||||||
clock_t curInterval = (clock_t)CURSOR_MS * CLOCKS_PER_SEC / 1000;
|
clock_t curInterval = (clock_t)CURSOR_MS * CLOCKS_PER_SEC / 1000;
|
||||||
|
|
@ -1509,15 +1509,15 @@ int32_t wgtAnsiTermPoll(WidgetT *w) {
|
||||||
// for the serial link.
|
// for the serial link.
|
||||||
|
|
||||||
// Fast repaint: renders dirty rows directly into the window's content buffer,
|
// Fast repaint: renders dirty rows directly into the window's content buffer,
|
||||||
// completely bypassing the normal widget paint pipeline (widgetOnPaint →
|
// completely bypassing the normal widget paint pipeline (widgetOnPaint ->
|
||||||
// widgetPaintOne → full tree walk). Returns the number of rows repainted
|
// widgetPaintOne -> full tree walk). Returns the number of rows repainted
|
||||||
// and optionally reports the vertical extent via outY/outH so the caller
|
// and optionally reports the vertical extent via outY/outH so the caller
|
||||||
// can issue a minimal compositor dirty rect.
|
// can issue a minimal compositor dirty rect.
|
||||||
//
|
//
|
||||||
// This exists because the normal paint path clears the entire content area
|
// This exists because the normal paint path clears the entire content area
|
||||||
// and repaints all widgets, which is far too expensive to do on every
|
// and repaints all widgets, which is far too expensive to do on every
|
||||||
// incoming serial byte. With fast repaint, the path from data reception
|
// incoming serial byte. With fast repaint, the path from data reception
|
||||||
// to LFB flush is: poll → write → dirtyRows → repaint → compositor dirty,
|
// to LFB flush is: poll -> write -> dirtyRows -> repaint -> compositor dirty,
|
||||||
// keeping the round-trip under 1ms on a Pentium.
|
// keeping the round-trip under 1ms on a Pentium.
|
||||||
int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH) {
|
int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH) {
|
||||||
if (!w || w->type != WidgetAnsiTermE || !w->window) {
|
if (!w || w->type != WidgetAnsiTermE || !w->window) {
|
||||||
|
|
@ -1626,7 +1626,7 @@ int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Attach communication callbacks to the terminal. The read/write function
|
// Attach communication callbacks to the terminal. The read/write function
|
||||||
// pointers are transport-agnostic — the terminal doesn't care whether
|
// pointers are transport-agnostic -- the terminal doesn't care whether
|
||||||
// data comes from a raw UART, the secLink encrypted channel, or a
|
// data comes from a raw UART, the secLink encrypted channel, or a
|
||||||
// socket-based proxy. The ctx pointer is passed through opaquely.
|
// socket-based proxy. The ctx pointer is passed through opaquely.
|
||||||
void wgtAnsiTermSetComm(WidgetT *w, void *ctx, int32_t (*readFn)(void *, uint8_t *, int32_t), int32_t (*writeFn)(void *, const uint8_t *, int32_t)) {
|
void wgtAnsiTermSetComm(WidgetT *w, void *ctx, int32_t (*readFn)(void *, uint8_t *, int32_t), int32_t (*writeFn)(void *, const uint8_t *, int32_t)) {
|
||||||
|
|
@ -1645,7 +1645,7 @@ void wgtAnsiTermSetComm(WidgetT *w, void *ctx, int32_t (*readFn)(void *, uint8_t
|
||||||
// Write raw bytes into the terminal for parsing and display. Any active
|
// Write raw bytes into the terminal for parsing and display. Any active
|
||||||
// selection is cleared first since the screen content is changing and the
|
// selection is cleared first since the screen content is changing and the
|
||||||
// selection coordinates would become stale. Each byte is fed through the
|
// selection coordinates would become stale. Each byte is fed through the
|
||||||
// ANSI parser individually — the parser maintains state between calls so
|
// ANSI parser individually -- the parser maintains state between calls so
|
||||||
// multi-byte sequences split across writes are handled correctly.
|
// multi-byte sequences split across writes are handled correctly.
|
||||||
void wgtAnsiTermWrite(WidgetT *w, const uint8_t *data, int32_t len) {
|
void wgtAnsiTermWrite(WidgetT *w, const uint8_t *data, int32_t len) {
|
||||||
if (!w || w->type != WidgetAnsiTermE || !data || len <= 0) {
|
if (!w || w->type != WidgetAnsiTermE || !data || len <= 0) {
|
||||||
|
|
@ -1677,7 +1677,7 @@ void widgetAnsiTermDestroy(WidgetT *w) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Min size = exact pixel dimensions needed for the grid plus border and
|
// Min size = exact pixel dimensions needed for the grid plus border and
|
||||||
// scrollbar. The terminal is not designed to be resizable — the grid
|
// scrollbar. The terminal is not designed to be resizable -- the grid
|
||||||
// dimensions (cols x rows) are fixed at creation time, matching the BBS
|
// dimensions (cols x rows) are fixed at creation time, matching the BBS
|
||||||
// convention of 80x25 or similar fixed screen sizes.
|
// convention of 80x25 or similar fixed screen sizes.
|
||||||
void widgetAnsiTermCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
void widgetAnsiTermCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
|
|
@ -1763,7 +1763,7 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No selection — fall through to send ^C to terminal
|
// No selection -- fall through to send ^C to terminal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+V: paste from clipboard to terminal
|
// Ctrl+V: paste from clipboard to terminal
|
||||||
|
|
@ -1806,39 +1806,39 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
buf[0] = 0x08;
|
buf[0] = 0x08;
|
||||||
len = 1;
|
len = 1;
|
||||||
} else if (key == (0x48 | 0x100)) {
|
} else if (key == (0x48 | 0x100)) {
|
||||||
// Up arrow → ESC[A
|
// Up arrow -> ESC[A
|
||||||
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'A';
|
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'A';
|
||||||
len = 3;
|
len = 3;
|
||||||
} else if (key == (0x50 | 0x100)) {
|
} else if (key == (0x50 | 0x100)) {
|
||||||
// Down arrow → ESC[B
|
// Down arrow -> ESC[B
|
||||||
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'B';
|
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'B';
|
||||||
len = 3;
|
len = 3;
|
||||||
} else if (key == (0x4D | 0x100)) {
|
} else if (key == (0x4D | 0x100)) {
|
||||||
// Right arrow → ESC[C
|
// Right arrow -> ESC[C
|
||||||
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'C';
|
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'C';
|
||||||
len = 3;
|
len = 3;
|
||||||
} else if (key == (0x4B | 0x100)) {
|
} else if (key == (0x4B | 0x100)) {
|
||||||
// Left arrow → ESC[D
|
// Left arrow -> ESC[D
|
||||||
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'D';
|
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'D';
|
||||||
len = 3;
|
len = 3;
|
||||||
} else if (key == (0x47 | 0x100)) {
|
} else if (key == (0x47 | 0x100)) {
|
||||||
// Home → ESC[H
|
// Home -> ESC[H
|
||||||
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'H';
|
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'H';
|
||||||
len = 3;
|
len = 3;
|
||||||
} else if (key == (0x4F | 0x100)) {
|
} else if (key == (0x4F | 0x100)) {
|
||||||
// End → ESC[F
|
// End -> ESC[F
|
||||||
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'F';
|
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'F';
|
||||||
len = 3;
|
len = 3;
|
||||||
} else if (key == (0x49 | 0x100)) {
|
} else if (key == (0x49 | 0x100)) {
|
||||||
// PgUp → ESC[5~
|
// PgUp -> ESC[5~
|
||||||
buf[0] = 0x1B; buf[1] = '['; buf[2] = '5'; buf[3] = '~';
|
buf[0] = 0x1B; buf[1] = '['; buf[2] = '5'; buf[3] = '~';
|
||||||
len = 4;
|
len = 4;
|
||||||
} else if (key == (0x51 | 0x100)) {
|
} else if (key == (0x51 | 0x100)) {
|
||||||
// PgDn → ESC[6~
|
// PgDn -> ESC[6~
|
||||||
buf[0] = 0x1B; buf[1] = '['; buf[2] = '6'; buf[3] = '~';
|
buf[0] = 0x1B; buf[1] = '['; buf[2] = '6'; buf[3] = '~';
|
||||||
len = 4;
|
len = 4;
|
||||||
} else if (key == (0x53 | 0x100)) {
|
} else if (key == (0x53 | 0x100)) {
|
||||||
// Delete → ESC[3~
|
// Delete -> ESC[3~
|
||||||
buf[0] = 0x1B; buf[1] = '['; buf[2] = '3'; buf[3] = '~';
|
buf[0] = 0x1B; buf[1] = '['; buf[2] = '3'; buf[3] = '~';
|
||||||
len = 4;
|
len = 4;
|
||||||
} else if (key >= 1 && key < 32) {
|
} else if (key >= 1 && key < 32) {
|
||||||
|
|
@ -1866,7 +1866,7 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
// The scrollbar area uses direct hit testing on up/down arrow buttons and
|
// The scrollbar area uses direct hit testing on up/down arrow buttons and
|
||||||
// page-up/page-down regions, with proportional thumb positioning.
|
// page-up/page-down regions, with proportional thumb positioning.
|
||||||
// Selection drag is handled externally by the widget event dispatcher via
|
// Selection drag is handled externally by the widget event dispatcher via
|
||||||
// sDragTextSelect — on mouse-down we set the anchor and enable drag mode.
|
// sDragTextSelect -- on mouse-down we set the anchor and enable drag mode.
|
||||||
void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
AppContextT *actx = (AppContextT *)root->userData;
|
AppContextT *actx = (AppContextT *)root->userData;
|
||||||
const BitmapFontT *font = &actx->font;
|
const BitmapFontT *font = &actx->font;
|
||||||
|
|
@ -1880,7 +1880,7 @@ void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
||||||
int32_t sbH = rows * font->charHeight;
|
int32_t sbH = rows * font->charHeight;
|
||||||
int32_t arrowH = ANSI_SB_W;
|
int32_t arrowH = ANSI_SB_W;
|
||||||
|
|
||||||
// Click in text area — start selection
|
// Click in text area -- start selection
|
||||||
if (vx < sbX) {
|
if (vx < sbX) {
|
||||||
int32_t baseX = hit->x + ANSI_BORDER;
|
int32_t baseX = hit->x + ANSI_BORDER;
|
||||||
int32_t baseY = hit->y + ANSI_BORDER;
|
int32_t baseY = hit->y + ANSI_BORDER;
|
||||||
|
|
@ -1964,7 +1964,7 @@ void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
||||||
// Down arrow
|
// Down arrow
|
||||||
hit->as.ansiTerm->scrollPos++;
|
hit->as.ansiTerm->scrollPos++;
|
||||||
} else if (vy >= sbY + arrowH && vy < sbY + sbH - arrowH) {
|
} else if (vy >= sbY + arrowH && vy < sbY + sbH - arrowH) {
|
||||||
// Track area — compute thumb position to determine page direction
|
// Track area -- compute thumb position to determine page direction
|
||||||
int32_t trackY = sbY + arrowH;
|
int32_t trackY = sbY + arrowH;
|
||||||
int32_t trackH = sbH - arrowH * 2;
|
int32_t trackH = sbH - arrowH * 2;
|
||||||
|
|
||||||
|
|
@ -2014,12 +2014,12 @@ void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
||||||
// rows, selection overlay, and scrollbar. This is called through the normal
|
// rows, selection overlay, and scrollbar. This is called through the normal
|
||||||
// widget paint pipeline (e.g., on window expose or full invalidation).
|
// widget paint pipeline (e.g., on window expose or full invalidation).
|
||||||
// For incremental updates during data reception, wgtAnsiTermRepaint is used
|
// For incremental updates during data reception, wgtAnsiTermRepaint is used
|
||||||
// instead — it's much faster since it only repaints dirty rows and skips
|
// instead -- it's much faster since it only repaints dirty rows and skips
|
||||||
// the border/scrollbar.
|
// the border/scrollbar.
|
||||||
//
|
//
|
||||||
// The terminal renders its own scrollbar rather than using the shared
|
// The terminal renders its own scrollbar rather than using the shared
|
||||||
// widgetDrawScrollbarV because the scrollbar's total/visible/position
|
// widgetDrawScrollbarV because the scrollbar's total/visible/position
|
||||||
// semantics are different — the terminal scrollbar represents scrollback
|
// semantics are different -- the terminal scrollbar represents scrollback
|
||||||
// lines (historical content above the screen), not a viewport over a
|
// lines (historical content above the screen), not a viewport over a
|
||||||
// virtual content area.
|
// virtual content area.
|
||||||
void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||||
|
|
@ -2082,7 +2082,7 @@ void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
int32_t arrowH = ANSI_SB_W;
|
int32_t arrowH = ANSI_SB_W;
|
||||||
|
|
||||||
if (sbCount == 0) {
|
if (sbCount == 0) {
|
||||||
// No scrollback — fill scrollbar area with trough color
|
// No scrollback -- fill scrollbar area with trough color
|
||||||
rectFill(d, ops, sbX, sbY, sbW, sbH, colors->scrollbarTrough);
|
rectFill(d, ops, sbX, sbY, sbW, sbH, colors->scrollbarTrough);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
// widgetBox.c — VBox, HBox, and Frame container widgets
|
// widgetBox.c -- VBox, HBox, and Frame container widgets
|
||||||
//
|
//
|
||||||
// VBox and HBox are the primary layout containers. They have no visual
|
// VBox and HBox are the primary layout containers. They have no visual
|
||||||
// representation of their own — they exist purely to arrange children
|
// representation of their own -- they exist purely to arrange children
|
||||||
// vertically or horizontally. The actual layout algorithm lives in
|
// vertically or horizontally. The actual layout algorithm lives in
|
||||||
// widgetLayout.c (widgetCalcMinSizeBox / widgetLayoutBox) which handles
|
// widgetLayout.c (widgetCalcMinSizeBox / widgetLayoutBox) which handles
|
||||||
// weight-based space distribution, spacing, padding, and alignment.
|
// weight-based space distribution, spacing, padding, and alignment.
|
||||||
//
|
//
|
||||||
// VBox and HBox are distinguished by a flag (WCLASS_HORIZ_CONTAINER) in
|
// VBox and HBox are distinguished by a flag (WCLASS_HORIZ_CONTAINER) in
|
||||||
// the class table rather than having separate code. This keeps the layout
|
// the class table rather than having separate code. This keeps the layout
|
||||||
// engine unified — the same algorithm works in both orientations by
|
// engine unified -- the same algorithm works in both orientations by
|
||||||
// swapping which axis is "major" vs "minor".
|
// swapping which axis is "major" vs "minor".
|
||||||
//
|
//
|
||||||
// Frame is a labeled grouping box with a Motif-style beveled border.
|
// Frame is a labeled grouping box with a Motif-style beveled border.
|
||||||
// It acts as a VBox for layout purposes (children stack vertically inside
|
// It acts as a VBox for layout purposes (children stack vertically inside
|
||||||
// the frame's padded interior). The title text sits centered vertically
|
// the frame's padded interior). The title text sits centered vertically
|
||||||
// on the top border line, with a small background-filled gap to visually
|
// on the top border line, with a small background-filled gap to visually
|
||||||
// "break" the border behind the title — this is the classic Win3.1/Motif
|
// "break" the border behind the title -- this is the classic Win3.1/Motif
|
||||||
// group box appearance.
|
// group box appearance.
|
||||||
|
|
||||||
#include "widgetInternal.h"
|
#include "widgetInternal.h"
|
||||||
|
|
@ -27,14 +27,14 @@
|
||||||
|
|
||||||
// Paint the frame border and optional title. The border is offset down by
|
// Paint the frame border and optional title. The border is offset down by
|
||||||
// half the font height so the title text can sit centered on the top edge.
|
// half the font height so the title text can sit centered on the top edge.
|
||||||
// This creates the illusion of the title "interrupting" the border — a
|
// This creates the illusion of the title "interrupting" the border -- a
|
||||||
// background-colored rectangle is drawn behind the title to erase the
|
// background-colored rectangle is drawn behind the title to erase the
|
||||||
// border pixels, then the title is drawn on top.
|
// border pixels, then the title is drawn on top.
|
||||||
//
|
//
|
||||||
// Three border styles are supported:
|
// Three border styles are supported:
|
||||||
// FrameFlatE — single-pixel solid color outline
|
// FrameFlatE -- single-pixel solid color outline
|
||||||
// FrameInE — Motif "groove" (inset bevel: shadow-then-highlight)
|
// FrameInE -- Motif "groove" (inset bevel: shadow-then-highlight)
|
||||||
// FrameOutE — Motif "ridge" (outset bevel: highlight-then-shadow)
|
// FrameOutE -- Motif "ridge" (outset bevel: highlight-then-shadow)
|
||||||
// The groove/ridge are each two nested 1px bevels with swapped colors.
|
// The groove/ridge are each two nested 1px bevels with swapped colors.
|
||||||
void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||||
int32_t fb = widgetFrameBorderWidth(w);
|
int32_t fb = widgetFrameBorderWidth(w);
|
||||||
|
|
@ -100,7 +100,7 @@ void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
||||||
|
|
||||||
// Create a Frame container. The title string supports accelerator keys
|
// Create a Frame container. The title string supports accelerator keys
|
||||||
// (prefixed with '&') which are parsed by accelParse. The title pointer
|
// (prefixed with '&') which are parsed by accelParse. The title pointer
|
||||||
// is stored directly (not copied) — the caller must ensure it remains valid.
|
// is stored directly (not copied) -- the caller must ensure it remains valid.
|
||||||
WidgetT *wgtFrame(WidgetT *parent, const char *title) {
|
WidgetT *wgtFrame(WidgetT *parent, const char *title) {
|
||||||
WidgetT *w = widgetAlloc(parent, WidgetFrameE);
|
WidgetT *w = widgetAlloc(parent, WidgetFrameE);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// widgetButton.c — Button widget
|
// widgetButton.c -- Button widget
|
||||||
//
|
//
|
||||||
// Standard push button with text label, Motif-style 2px beveled border,
|
// Standard push button with text label, Motif-style 2px beveled border,
|
||||||
// and press animation. The button uses a two-phase press model:
|
// and press animation. The button uses a two-phase press model:
|
||||||
// - Mouse press: sets pressed=true and stores the widget in sPressedButton.
|
// - Mouse press: sets pressed=true and stores the widget in sPressedButton.
|
||||||
// The event dispatcher tracks mouse movement — if the mouse leaves the
|
// The event dispatcher tracks mouse movement -- if the mouse leaves the
|
||||||
// button bounds, pressed is cleared (visual feedback), and if it re-enters,
|
// button bounds, pressed is cleared (visual feedback), and if it re-enters,
|
||||||
// pressed is re-set. The onClick callback fires only on mouse-up while
|
// pressed is re-set. The onClick callback fires only on mouse-up while
|
||||||
// still inside the button. This gives the user a chance to cancel by
|
// still inside the button. This gives the user a chance to cancel by
|
||||||
|
|
@ -105,10 +105,10 @@ void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Paint: draws the beveled border, centered text, and optional focus rect.
|
// Paint: draws the beveled border, centered text, and optional focus rect.
|
||||||
// When pressed, the bevel colors swap (highlight↔shadow) creating the sunken
|
// When pressed, the bevel colors swap (highlight<->shadow) creating the sunken
|
||||||
// appearance, and the text shifts by BUTTON_PRESS_OFFSET pixels. Disabled
|
// appearance, and the text shifts by BUTTON_PRESS_OFFSET pixels. Disabled
|
||||||
// buttons use the "embossed" text technique (highlight color at +1,+1, then
|
// buttons use the "embossed" text technique (highlight color at +1,+1, then
|
||||||
// shadow color at 0,0) to create a chiseled/etched look — this is the
|
// shadow color at 0,0) to create a chiseled/etched look -- this is the
|
||||||
// standard Windows 3.1 disabled control appearance.
|
// standard Windows 3.1 disabled control appearance.
|
||||||
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// widgetCanvas.c — Drawable canvas widget (freehand draw, PNG save/load)
|
// widgetCanvas.c -- Drawable canvas widget (freehand draw, PNG save/load)
|
||||||
//
|
//
|
||||||
// The canvas widget provides a pixel buffer in the display's native pixel
|
// The canvas widget provides a pixel buffer in the display's native pixel
|
||||||
// format that applications can draw into directly. It stores pixels in
|
// format that applications can draw into directly. It stores pixels in
|
||||||
// display format (not always RGB) to avoid per-pixel conversion on every
|
// display format (not always RGB) to avoid per-pixel conversion on every
|
||||||
// repaint — the paint function just does a straight rectCopy blit from
|
// repaint -- the paint function just does a straight rectCopy blit from
|
||||||
// the canvas buffer to the display. This is critical on a 486 where
|
// the canvas buffer to the display. This is critical on a 486 where
|
||||||
// per-pixel format conversion during repaint would be prohibitively slow.
|
// per-pixel format conversion during repaint would be prohibitively slow.
|
||||||
//
|
//
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
// Bresenham's algorithm placing dots along the path. This gives smooth
|
// Bresenham's algorithm placing dots along the path. This gives smooth
|
||||||
// freehand drawing with variable pen widths.
|
// freehand drawing with variable pen widths.
|
||||||
//
|
//
|
||||||
// Canvas coordinates are independent of widget position — (0,0) is the
|
// Canvas coordinates are independent of widget position -- (0,0) is the
|
||||||
// top-left of the canvas content, not the widget. Mouse events translate
|
// top-left of the canvas content, not the widget. Mouse events translate
|
||||||
// widget-space coordinates to canvas-space by subtracting the border offset.
|
// widget-space coordinates to canvas-space by subtracting the border offset.
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32
|
||||||
// Read/write a single pixel at the given address, respecting the display's
|
// Read/write a single pixel at the given address, respecting the display's
|
||||||
// bytes-per-pixel depth (1=8-bit palette, 2=16-bit hicolor, 4=32-bit truecolor).
|
// bytes-per-pixel depth (1=8-bit palette, 2=16-bit hicolor, 4=32-bit truecolor).
|
||||||
// These are inline because they're called per-pixel in tight loops (circle fill,
|
// These are inline because they're called per-pixel in tight loops (circle fill,
|
||||||
// line draw) — the function call overhead would dominate on a 486. The bpp
|
// line draw) -- the function call overhead would dominate on a 486. The bpp
|
||||||
// branch is predictable since it doesn't change within a single draw operation.
|
// branch is predictable since it doesn't change within a single draw operation.
|
||||||
|
|
||||||
static inline uint32_t canvasGetPixel(const uint8_t *src, int32_t bpp) {
|
static inline uint32_t canvasGetPixel(const uint8_t *src, int32_t bpp) {
|
||||||
|
|
@ -96,7 +96,7 @@ static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
|
||||||
// center), compute the horizontal extent using the circle equation
|
// center), compute the horizontal extent using the circle equation
|
||||||
// dx^2 + dy^2 <= r^2. The horizontal half-span is floor(sqrt(r^2 - dy^2)).
|
// dx^2 + dy^2 <= r^2. The horizontal half-span is floor(sqrt(r^2 - dy^2)).
|
||||||
// This approach is faster than checking each pixel individually because
|
// This approach is faster than checking each pixel individually because
|
||||||
// the inner loop just fills a horizontal run — no per-pixel distance check.
|
// the inner loop just fills a horizontal run -- no per-pixel distance check.
|
||||||
int32_t r2 = rad * rad;
|
int32_t r2 = rad * rad;
|
||||||
|
|
||||||
for (int32_t dy = -rad; dy <= rad; dy++) {
|
for (int32_t dy = -rad; dy <= rad; dy++) {
|
||||||
|
|
@ -106,7 +106,7 @@ static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute horizontal half-span: dx² <= r² - dy²
|
// Compute horizontal half-span: dx^2 <= r^2 - dy^2
|
||||||
int32_t dy2 = dy * dy;
|
int32_t dy2 = dy * dy;
|
||||||
int32_t rem = r2 - dy2;
|
int32_t rem = r2 - dy2;
|
||||||
int32_t hspan = 0;
|
int32_t hspan = 0;
|
||||||
|
|
@ -158,7 +158,7 @@ static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
|
||||||
// Bresenham line from (x0,y0) to (x1,y1), placing dots along the path.
|
// Bresenham line from (x0,y0) to (x1,y1), placing dots along the path.
|
||||||
// Each point on the line gets a full pen dot (canvasDrawDot), which means
|
// Each point on the line gets a full pen dot (canvasDrawDot), which means
|
||||||
// lines with large pen sizes are smooth rather than aliased. Bresenham was
|
// lines with large pen sizes are smooth rather than aliased. Bresenham was
|
||||||
// chosen over DDA because it's pure integer arithmetic — no floating point
|
// chosen over DDA because it's pure integer arithmetic -- no floating point
|
||||||
// needed, which matters on 486SX (no FPU).
|
// needed, which matters on 486SX (no FPU).
|
||||||
|
|
||||||
static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
|
static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
|
||||||
|
|
@ -202,7 +202,7 @@ static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32
|
||||||
// is allocated in the display's native pixel format by walking up the widget
|
// is allocated in the display's native pixel format by walking up the widget
|
||||||
// tree to find the AppContextT (which holds the display format info). This
|
// tree to find the AppContextT (which holds the display format info). This
|
||||||
// tree-walk pattern is necessary because the widget doesn't have direct access
|
// tree-walk pattern is necessary because the widget doesn't have direct access
|
||||||
// to the display — only the root widget's userData points to the AppContextT.
|
// to the display -- only the root widget's userData points to the AppContextT.
|
||||||
// The buffer is initialized to white using spanFill for performance.
|
// The buffer is initialized to white using spanFill for performance.
|
||||||
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h) {
|
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h) {
|
||||||
if (!parent || w <= 0 || h <= 0) {
|
if (!parent || w <= 0 || h <= 0) {
|
||||||
|
|
@ -401,7 +401,7 @@ void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute horizontal half-span: dx² <= r² - dy²
|
// Compute horizontal half-span: dx^2 <= r^2 - dy^2
|
||||||
int32_t dy2 = dy * dy;
|
int32_t dy2 = dy * dy;
|
||||||
int32_t rem = r2 - dy2;
|
int32_t rem = r2 - dy2;
|
||||||
int32_t hspan = 0;
|
int32_t hspan = 0;
|
||||||
|
|
@ -526,7 +526,7 @@ uint32_t wgtCanvasGetPixel(const WidgetT *w, int32_t x, int32_t y) {
|
||||||
|
|
||||||
// Load an image file into the canvas, replacing the current content.
|
// Load an image file into the canvas, replacing the current content.
|
||||||
// Delegates to dvxLoadImage for format decoding and pixel conversion.
|
// Delegates to dvxLoadImage for format decoding and pixel conversion.
|
||||||
// The old buffer is freed and replaced with the new one — canvas
|
// The old buffer is freed and replaced with the new one -- canvas
|
||||||
// dimensions change to match the loaded image.
|
// dimensions change to match the loaded image.
|
||||||
int32_t wgtCanvasLoad(WidgetT *w, const char *path) {
|
int32_t wgtCanvasLoad(WidgetT *w, const char *path) {
|
||||||
if (!w || w->type != WidgetCanvasE || !path) {
|
if (!w || w->type != WidgetCanvasE || !path) {
|
||||||
|
|
@ -648,7 +648,7 @@ void widgetCanvasDestroy(WidgetT *w) {
|
||||||
|
|
||||||
// The canvas requests exactly its pixel dimensions plus the sunken bevel
|
// The canvas requests exactly its pixel dimensions plus the sunken bevel
|
||||||
// border. The font parameter is unused since the canvas has no text content.
|
// border. The font parameter is unused since the canvas has no text content.
|
||||||
// The canvas is not designed to scale — it reports its exact size as the
|
// The canvas is not designed to scale -- it reports its exact size as the
|
||||||
// minimum, and the layout engine should respect that.
|
// minimum, and the layout engine should respect that.
|
||||||
void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
(void)font;
|
(void)font;
|
||||||
|
|
@ -697,7 +697,7 @@ void widgetCanvasOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
|
|
||||||
// Paint: draws a sunken bevel border then blits the canvas buffer. Because
|
// Paint: draws a sunken bevel border then blits the canvas buffer. Because
|
||||||
// the canvas stores pixels in the display's native format, rectCopy is a
|
// the canvas stores pixels in the display's native format, rectCopy is a
|
||||||
// straight memcpy per scanline — no per-pixel conversion needed. This makes
|
// straight memcpy per scanline -- no per-pixel conversion needed. This makes
|
||||||
// repaint essentially free relative to the display bandwidth.
|
// repaint essentially free relative to the display bandwidth.
|
||||||
void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||||
(void)font;
|
(void)font;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// widgetCheckbox.c — Checkbox widget
|
// widgetCheckbox.c -- Checkbox widget
|
||||||
//
|
//
|
||||||
// Classic checkbox: a small box with a sunken bevel (1px) on the left, a text
|
// Classic checkbox: a small box with a sunken bevel (1px) on the left, a text
|
||||||
// label to the right. The check mark is drawn as two diagonal lines forming an
|
// label to the right. The check mark is drawn as two diagonal lines forming an
|
||||||
// "X" pattern rather than a traditional checkmark glyph — this is simpler to
|
// "X" pattern rather than a traditional checkmark glyph -- this is simpler to
|
||||||
// render with drawHLine primitives and matches the DV/X aesthetic.
|
// render with drawHLine primitives and matches the DV/X aesthetic.
|
||||||
//
|
//
|
||||||
// State management is simple: a boolean 'checked' flag toggles on each click
|
// State management is simple: a boolean 'checked' flag toggles on each click
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
// widgetClass.c — Widget class vtable definitions
|
// widgetClass.c -- Widget class vtable definitions
|
||||||
//
|
//
|
||||||
// This file implements a C vtable pattern for the widget type system.
|
// This file implements a C vtable pattern for the widget type system.
|
||||||
// Each widget type has a static const WidgetClassT struct that defines
|
// Each widget type has a static const WidgetClassT struct that defines
|
||||||
// its behavior — paint, layout, mouse handling, keyboard handling, etc.
|
// its behavior -- paint, layout, mouse handling, keyboard handling, etc.
|
||||||
// A master table (widgetClassTable[]) maps WidgetTypeE enum values to
|
// A master table (widgetClassTable[]) maps WidgetTypeE enum values to
|
||||||
// their class definitions, enabling O(1) dispatch.
|
// their class definitions, enabling O(1) dispatch.
|
||||||
//
|
//
|
||||||
// Why a vtable approach instead of switch statements:
|
// Why a vtable approach instead of switch statements:
|
||||||
// 1. Adding a new widget type is purely additive — define a new class
|
// 1. Adding a new widget type is purely additive -- define a new class
|
||||||
// struct and add one entry to the table. No existing switch
|
// struct and add one entry to the table. No existing switch
|
||||||
// statements need modification, reducing the risk of forgetting
|
// statements need modification, reducing the risk of forgetting
|
||||||
// a case.
|
// a case.
|
||||||
|
|
@ -20,13 +20,13 @@
|
||||||
// like hit testing and layout.
|
// like hit testing and layout.
|
||||||
//
|
//
|
||||||
// Class flags:
|
// Class flags:
|
||||||
// WCLASS_FOCUSABLE — widget can receive keyboard focus (Tab order)
|
// WCLASS_FOCUSABLE -- widget can receive keyboard focus (Tab order)
|
||||||
// WCLASS_BOX_CONTAINER — uses the generic box layout engine (VBox/HBox)
|
// WCLASS_BOX_CONTAINER -- uses the generic box layout engine (VBox/HBox)
|
||||||
// WCLASS_HORIZ_CONTAINER — lays out children horizontally (HBox variant)
|
// WCLASS_HORIZ_CONTAINER -- lays out children horizontally (HBox variant)
|
||||||
// WCLASS_PAINTS_CHILDREN — widget handles its own child painting (TabControl,
|
// WCLASS_PAINTS_CHILDREN -- widget handles its own child painting (TabControl,
|
||||||
// TreeView, ScrollPane, Splitter). The default paint
|
// TreeView, ScrollPane, Splitter). The default paint
|
||||||
// walker skips children for these widgets.
|
// walker skips children for these widgets.
|
||||||
// WCLASS_NO_HIT_RECURSE — hit testing stops at this widget instead of
|
// WCLASS_NO_HIT_RECURSE -- hit testing stops at this widget instead of
|
||||||
// recursing into children. Used by widgets that
|
// recursing into children. Used by widgets that
|
||||||
// manage their own internal click regions (TreeView,
|
// manage their own internal click regions (TreeView,
|
||||||
// ScrollPane, ListView, Splitter).
|
// ScrollPane, ListView, Splitter).
|
||||||
|
|
@ -328,9 +328,9 @@ static const WidgetClassT sClassToolbar = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// TreeView uses all three special flags:
|
// TreeView uses all three special flags:
|
||||||
// PAINTS_CHILDREN — it renders tree items itself (with indentation,
|
// PAINTS_CHILDREN -- it renders tree items itself (with indentation,
|
||||||
// expand/collapse buttons, and selection highlighting)
|
// expand/collapse buttons, and selection highlighting)
|
||||||
// NO_HIT_RECURSE — mouse clicks go to the TreeView widget, which
|
// NO_HIT_RECURSE -- mouse clicks go to the TreeView widget, which
|
||||||
// figures out which tree item was clicked based on scroll position
|
// figures out which tree item was clicked based on scroll position
|
||||||
// and item Y coordinates, rather than letting the hit tester
|
// and item Y coordinates, rather than letting the hit tester
|
||||||
// recurse into child TreeItem widgets
|
// recurse into child TreeItem widgets
|
||||||
|
|
@ -347,7 +347,7 @@ static const WidgetClassT sClassTreeView = {
|
||||||
.setText = NULL
|
.setText = NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
// TreeItem has no paint/mouse/key handlers — it's a data-only node.
|
// TreeItem has no paint/mouse/key handlers -- it's a data-only node.
|
||||||
// The TreeView parent widget handles all rendering and interaction.
|
// The TreeView parent widget handles all rendering and interaction.
|
||||||
// TreeItem exists as a WidgetT so it can participate in the tree
|
// TreeItem exists as a WidgetT so it can participate in the tree
|
||||||
// structure (parent/child/sibling links) for hierarchical data.
|
// structure (parent/child/sibling links) for hierarchical data.
|
||||||
|
|
@ -485,14 +485,14 @@ static const WidgetClassT sClassSpinner = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Class table — indexed by WidgetTypeE
|
// Class table -- indexed by WidgetTypeE
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// This array is the central dispatch table for the widget system.
|
// This array is the central dispatch table for the widget system.
|
||||||
// Indexed by the WidgetTypeE enum, it provides O(1) lookup of any
|
// Indexed by the WidgetTypeE enum, it provides O(1) lookup of any
|
||||||
// widget type's class definition. Every WidgetT stores a pointer
|
// widget type's class definition. Every WidgetT stores a pointer
|
||||||
// to its class (w->wclass) set at allocation time, so per-widget
|
// to its class (w->wclass) set at allocation time, so per-widget
|
||||||
// dispatch doesn't even need to index this table — it's a direct
|
// dispatch doesn't even need to index this table -- it's a direct
|
||||||
// pointer dereference through the vtable.
|
// pointer dereference through the vtable.
|
||||||
//
|
//
|
||||||
// Using C99 designated initializers ensures the array slots match
|
// Using C99 designated initializers ensures the array slots match
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetComboBox.c — ComboBox widget (editable text + dropdown list)
|
// widgetComboBox.c -- ComboBox widget (editable text + dropdown list)
|
||||||
//
|
//
|
||||||
// Combines a single-line text input with a dropdown list. The text area
|
// Combines a single-line text input with a dropdown list. The text area
|
||||||
// supports full editing (cursor movement, selection, undo, clipboard) via
|
// supports full editing (cursor movement, selection, undo, clipboard) via
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
//
|
//
|
||||||
// The popup list is painted as an overlay (widgetComboBoxPaintPopup) that
|
// The popup list is painted as an overlay (widgetComboBoxPaintPopup) that
|
||||||
// renders on top of all other widgets. Popup visibility is coordinated
|
// renders on top of all other widgets. Popup visibility is coordinated
|
||||||
// through the sOpenPopup global — only one popup can be open at a time.
|
// through the sOpenPopup global -- only one popup can be open at a time.
|
||||||
// The sClosedPopup mechanism prevents click-to-close from immediately
|
// The sClosedPopup mechanism prevents click-to-close from immediately
|
||||||
// reopening the popup when the close click lands on the dropdown button.
|
// reopening the popup when the close click lands on the dropdown button.
|
||||||
//
|
//
|
||||||
|
|
@ -83,7 +83,7 @@ void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||||
|
|
||||||
// Cache max item string length so calcMinSize doesn't need to re-scan
|
// Cache max item string length so calcMinSize doesn't need to re-scan
|
||||||
// the entire item array on every layout pass. Items are stored as
|
// the entire item array on every layout pass. Items are stored as
|
||||||
// external pointers (not copied) — the caller owns the string data.
|
// external pointers (not copied) -- the caller owns the string data.
|
||||||
int32_t maxLen = 0;
|
int32_t maxLen = 0;
|
||||||
|
|
||||||
for (int32_t i = 0; i < count; i++) {
|
for (int32_t i = 0; i < count; i++) {
|
||||||
|
|
@ -282,12 +282,12 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Button click — toggle popup
|
// Button click -- toggle popup
|
||||||
w->as.comboBox.open = !w->as.comboBox.open;
|
w->as.comboBox.open = !w->as.comboBox.open;
|
||||||
w->as.comboBox.hoverIdx = w->as.comboBox.selectedIdx;
|
w->as.comboBox.hoverIdx = w->as.comboBox.selectedIdx;
|
||||||
sOpenPopup = w->as.comboBox.open ? w : NULL;
|
sOpenPopup = w->as.comboBox.open ? w : NULL;
|
||||||
} else {
|
} else {
|
||||||
// Text area click — focus for editing
|
// Text area click -- focus for editing
|
||||||
clearOtherSelections(w);
|
clearOtherSelections(w);
|
||||||
|
|
||||||
AppContextT *ctx = (AppContextT *)root->userData;
|
AppContextT *ctx = (AppContextT *)root->userData;
|
||||||
|
|
@ -300,7 +300,7 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
// widgetComboBoxPaint
|
// widgetComboBoxPaint
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Paint: two regions side-by-side — a sunken text area (left) and a raised
|
// Paint: two regions side-by-side -- a sunken text area (left) and a raised
|
||||||
// dropdown button (right). The text area renders the edit buffer with optional
|
// dropdown button (right). The text area renders the edit buffer with optional
|
||||||
// selection highlighting (up to 3 text runs: pre-selection, selection,
|
// selection highlighting (up to 3 text runs: pre-selection, selection,
|
||||||
// post-selection). The dropdown button has a small triangular arrow glyph
|
// post-selection). The dropdown button has a small triangular arrow glyph
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetCore.c — Core widget infrastructure (alloc, tree ops, helpers)
|
// widgetCore.c -- Core widget infrastructure (alloc, tree ops, helpers)
|
||||||
//
|
//
|
||||||
// This file provides the foundation for the widget tree: allocation,
|
// This file provides the foundation for the widget tree: allocation,
|
||||||
// parent-child linking, focus management, hit testing, and shared
|
// parent-child linking, focus management, hit testing, and shared
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
//
|
//
|
||||||
// Each pointer is set when an interaction begins (e.g. mouse-down on a
|
// Each pointer is set when an interaction begins (e.g. mouse-down on a
|
||||||
// slider) and cleared when it ends (mouse-up). The event dispatcher in
|
// slider) and cleared when it ends (mouse-up). The event dispatcher in
|
||||||
// widgetEvent.c checks these before normal hit testing — active drags
|
// widgetEvent.c checks these before normal hit testing -- active drags
|
||||||
// take priority over everything else.
|
// take priority over everything else.
|
||||||
//
|
//
|
||||||
// All of these must be NULLed when the pointed-to widget is destroyed,
|
// All of these must be NULLed when the pointed-to widget is destroyed,
|
||||||
|
|
@ -90,7 +90,7 @@ void widgetAddChild(WidgetT *parent, WidgetT *child) {
|
||||||
// class vtable via widgetClassTable[], and optionally adds it as
|
// class vtable via widgetClassTable[], and optionally adds it as
|
||||||
// a child of the given parent.
|
// a child of the given parent.
|
||||||
//
|
//
|
||||||
// The memset to 0 is intentional — it establishes sane defaults
|
// The memset to 0 is intentional -- it establishes sane defaults
|
||||||
// for all fields: NULL pointers, zero coordinates, no focus,
|
// for all fields: NULL pointers, zero coordinates, no focus,
|
||||||
// no accel key, etc. Only visible and enabled default to true.
|
// no accel key, etc. Only visible and enabled default to true.
|
||||||
//
|
//
|
||||||
|
|
@ -282,7 +282,7 @@ void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t conten
|
||||||
// Special case for TabPage widgets: even if the tab page itself is
|
// Special case for TabPage widgets: even if the tab page itself is
|
||||||
// not visible (inactive tab), its accelKey is still checked. This
|
// not visible (inactive tab), its accelKey is still checked. This
|
||||||
// allows Alt+key to switch to a different tab. However, children
|
// allows Alt+key to switch to a different tab. However, children
|
||||||
// of invisible tab pages are NOT searched — their accelerators
|
// of invisible tab pages are NOT searched -- their accelerators
|
||||||
// should not be active when the tab is hidden.
|
// should not be active when the tab is hidden.
|
||||||
|
|
||||||
WidgetT *widgetFindByAccel(WidgetT *root, char key) {
|
WidgetT *widgetFindByAccel(WidgetT *root, char key) {
|
||||||
|
|
@ -323,12 +323,12 @@ WidgetT *widgetFindByAccel(WidgetT *root, char key) {
|
||||||
// Implements Tab-order navigation: finds the next focusable widget
|
// Implements Tab-order navigation: finds the next focusable widget
|
||||||
// after 'after' in depth-first tree order. The two-pass approach
|
// after 'after' in depth-first tree order. The two-pass approach
|
||||||
// (search from 'after' to end, then wrap to start) ensures circular
|
// (search from 'after' to end, then wrap to start) ensures circular
|
||||||
// tabbing — Tab on the last focusable widget wraps to the first.
|
// tabbing -- Tab on the last focusable widget wraps to the first.
|
||||||
//
|
//
|
||||||
// The pastAfter flag tracks whether we've passed the 'after' widget
|
// The pastAfter flag tracks whether we've passed the 'after' widget
|
||||||
// during traversal. Once past it, the next focusable widget is the
|
// during traversal. Once past it, the next focusable widget is the
|
||||||
// answer. This avoids collecting all focusable widgets into an array
|
// answer. This avoids collecting all focusable widgets into an array
|
||||||
// just to find the next one — the common case returns quickly.
|
// just to find the next one -- the common case returns quickly.
|
||||||
|
|
||||||
static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter) {
|
static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter) {
|
||||||
if (!w->visible || !w->enabled) {
|
if (!w->visible || !w->enabled) {
|
||||||
|
|
@ -364,7 +364,7 @@ WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) {
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap around — search from the beginning
|
// Wrap around -- search from the beginning
|
||||||
pastAfter = true;
|
pastAfter = true;
|
||||||
return findNextFocusableImpl(root, NULL, &pastAfter);
|
return findNextFocusableImpl(root, NULL, &pastAfter);
|
||||||
}
|
}
|
||||||
|
|
@ -476,7 +476,7 @@ int32_t widgetFrameBorderWidth(const WidgetT *w) {
|
||||||
// on top of earlier ones). This is important for overlapping widgets,
|
// on top of earlier ones). This is important for overlapping widgets,
|
||||||
// though in practice the layout engine rarely produces overlap.
|
// though in practice the layout engine rarely produces overlap.
|
||||||
//
|
//
|
||||||
// Widgets with WCLASS_NO_HIT_RECURSE stop the recursion — the parent
|
// Widgets with WCLASS_NO_HIT_RECURSE stop the recursion -- the parent
|
||||||
// widget handles all mouse events for its children. This is used by
|
// widget handles all mouse events for its children. This is used by
|
||||||
// TreeView, ScrollPane, ListView, and Splitter, which need to manage
|
// TreeView, ScrollPane, ListView, and Splitter, which need to manage
|
||||||
// their own internal regions (scrollbars, column headers, tree
|
// their own internal regions (scrollbars, column headers, tree
|
||||||
|
|
@ -496,7 +496,7 @@ WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y) {
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check children — take the last match (topmost in Z-order)
|
// Check children -- take the last match (topmost in Z-order)
|
||||||
WidgetT *hit = NULL;
|
WidgetT *hit = NULL;
|
||||||
|
|
||||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||||
|
|
@ -551,7 +551,7 @@ bool widgetIsHorizContainer(WidgetTypeE type) {
|
||||||
// so each widget doesn't have to reimplement index clamping.
|
// so each widget doesn't have to reimplement index clamping.
|
||||||
//
|
//
|
||||||
// Key values use the 0x100 flag to mark extended scan codes (arrow
|
// Key values use the 0x100 flag to mark extended scan codes (arrow
|
||||||
// keys, Home, End, etc.) — this is the DVX convention for passing
|
// keys, Home, End, etc.) -- this is the DVX convention for passing
|
||||||
// scan codes through the same int32_t channel as ASCII values.
|
// scan codes through the same int32_t channel as ASCII values.
|
||||||
//
|
//
|
||||||
// Returns -1 for unrecognized keys so callers can check whether the
|
// Returns -1 for unrecognized keys so callers can check whether the
|
||||||
|
|
@ -648,7 +648,7 @@ void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *f
|
||||||
// Used by both the WM-level scrollbars and widget-internal scrollbars
|
// Used by both the WM-level scrollbars and widget-internal scrollbars
|
||||||
// (ListBox, TreeView, etc.) to maintain consistent scrollbar behavior.
|
// (ListBox, TreeView, etc.) to maintain consistent scrollbar behavior.
|
||||||
//
|
//
|
||||||
// The thumb size is proportional to visibleSize/totalSize — a larger
|
// The thumb size is proportional to visibleSize/totalSize -- a larger
|
||||||
// visible area means a larger thumb, giving visual feedback about how
|
// visible area means a larger thumb, giving visual feedback about how
|
||||||
// much content is scrollable. SB_MIN_THUMB prevents the thumb from
|
// much content is scrollable. SB_MIN_THUMB prevents the thumb from
|
||||||
// becoming too small to grab with a mouse.
|
// becoming too small to grab with a mouse.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// widgetDropdown.c — Dropdown (select) widget
|
// widgetDropdown.c -- Dropdown (select) widget
|
||||||
//
|
//
|
||||||
// A non-editable dropdown list (HTML <select> equivalent). Unlike ComboBox,
|
// A non-editable dropdown list (HTML <select> equivalent). Unlike ComboBox,
|
||||||
// the user cannot type a custom value — they can only choose from the
|
// the user cannot type a custom value -- they can only choose from the
|
||||||
// predefined items. This simplifies the widget considerably: no text buffer,
|
// predefined items. This simplifies the widget considerably: no text buffer,
|
||||||
// no undo, no cursor, no text editing key handling.
|
// no undo, no cursor, no text editing key handling.
|
||||||
//
|
//
|
||||||
|
|
@ -135,7 +135,7 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
(void)mod;
|
(void)mod;
|
||||||
|
|
||||||
if (w->as.dropdown.open) {
|
if (w->as.dropdown.open) {
|
||||||
// Popup is open — navigate items
|
// Popup is open -- navigate items
|
||||||
if (key == (0x48 | 0x100)) {
|
if (key == (0x48 | 0x100)) {
|
||||||
if (w->as.dropdown.hoverIdx > 0) {
|
if (w->as.dropdown.hoverIdx > 0) {
|
||||||
w->as.dropdown.hoverIdx--;
|
w->as.dropdown.hoverIdx--;
|
||||||
|
|
@ -254,7 +254,7 @@ void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
btnBevel.width = 2;
|
btnBevel.width = 2;
|
||||||
drawBevel(d, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
|
drawBevel(d, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
|
||||||
|
|
||||||
// Down arrow glyph in button — a small filled triangle drawn as horizontal
|
// Down arrow glyph in button -- a small filled triangle drawn as horizontal
|
||||||
// lines of decreasing width (7, 5, 3, 1 pixels). This creates a 4-pixel
|
// lines of decreasing width (7, 5, 3, 1 pixels). This creates a 4-pixel
|
||||||
// tall downward-pointing triangle centered in the button.
|
// tall downward-pointing triangle centered in the button.
|
||||||
uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow;
|
uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetEvent.c — Window event handlers for widget system
|
// widgetEvent.c -- Window event handlers for widget system
|
||||||
//
|
//
|
||||||
// This file routes window-level events (mouse, keyboard, paint, resize,
|
// This file routes window-level events (mouse, keyboard, paint, resize,
|
||||||
// scroll) into the widget tree. It serves as the bridge between the
|
// scroll) into the widget tree. It serves as the bridge between the
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
#include "widgetInternal.h"
|
#include "widgetInternal.h"
|
||||||
|
|
||||||
// Widget whose popup was just closed by click-outside — prevents
|
// Widget whose popup was just closed by click-outside -- prevents
|
||||||
// immediate re-open on the same click. Without this, clicking the
|
// immediate re-open on the same click. Without this, clicking the
|
||||||
// dropdown button to close its popup would immediately hit-test the
|
// dropdown button to close its popup would immediately hit-test the
|
||||||
// button again and re-open the popup in the same event.
|
// button again and re-open the popup in the same event.
|
||||||
|
|
@ -148,7 +148,7 @@ void widgetManageScrollbars(WindowT *win, AppContextT *ctx) {
|
||||||
// The cached pointer avoids an O(n) tree walk to find the focused
|
// The cached pointer avoids an O(n) tree walk to find the focused
|
||||||
// widget on every keypress.
|
// widget on every keypress.
|
||||||
//
|
//
|
||||||
// There is no keyboard event bubbling — if the focused widget doesn't
|
// There is no keyboard event bubbling -- if the focused widget doesn't
|
||||||
// handle a key, it's simply dropped. Accelerators (Alt+key) are
|
// handle a key, it's simply dropped. Accelerators (Alt+key) are
|
||||||
// handled at a higher level in the app event loop, not here.
|
// handled at a higher level in the app event loop, not here.
|
||||||
|
|
||||||
|
|
@ -159,7 +159,7 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use cached focus pointer — O(1) instead of O(n) tree walk
|
// Use cached focus pointer -- O(1) instead of O(n) tree walk
|
||||||
WidgetT *focus = sFocusedWidget;
|
WidgetT *focus = sFocusedWidget;
|
||||||
|
|
||||||
if (!focus || !focus->focused || focus->window != win) {
|
if (!focus || !focus->focused || focus->window != win) {
|
||||||
|
|
@ -191,7 +191,7 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
// Only if no interaction is active does the event fall through to
|
// Only if no interaction is active does the event fall through to
|
||||||
// normal hit testing.
|
// normal hit testing.
|
||||||
//
|
//
|
||||||
// Coordinates (x, y) are in content-buffer space — the window manager
|
// Coordinates (x, y) are in content-buffer space -- the window manager
|
||||||
// has already subtracted the window chrome offset. Widget positions
|
// has already subtracted the window chrome offset. Widget positions
|
||||||
// are also in content-buffer space (set during layout), so no
|
// are also in content-buffer space (set during layout), so no
|
||||||
// coordinate transform is needed for hit testing.
|
// coordinate transform is needed for hit testing.
|
||||||
|
|
@ -412,7 +412,15 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
// Handle scrollbar thumb drag (mouse move while pressed)
|
// Handle scrollbar thumb drag (mouse move while pressed)
|
||||||
if (sDragScrollbar && (buttons & MOUSE_LEFT)) {
|
if (sDragScrollbar && (buttons & MOUSE_LEFT)) {
|
||||||
widgetScrollbarDragUpdate(sDragScrollbar, sDragScrollbarOrient, sDragScrollbarOff, x, y);
|
widgetScrollbarDragUpdate(sDragScrollbar, sDragScrollbarOrient, sDragScrollbarOff, x, y);
|
||||||
|
|
||||||
|
// ScrollPane needs full relayout because it positions children
|
||||||
|
// at absolute coordinates incorporating the scroll offset.
|
||||||
|
if (sDragScrollbar->type == WidgetScrollPaneE) {
|
||||||
|
wgtInvalidate(sDragScrollbar);
|
||||||
|
} else {
|
||||||
wgtInvalidatePaint(root);
|
wgtInvalidatePaint(root);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -523,7 +531,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click outside popup — close it and remember which widget it was
|
// Click outside popup -- close it and remember which widget it was
|
||||||
sClosedPopup = sOpenPopup;
|
sClosedPopup = sOpenPopup;
|
||||||
|
|
||||||
if (sOpenPopup->type == WidgetDropdownE) {
|
if (sOpenPopup->type == WidgetDropdownE) {
|
||||||
|
|
@ -554,7 +562,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
|
|
||||||
// Clear focus from the previously focused widget. This is done via
|
// Clear focus from the previously focused widget. This is done via
|
||||||
// the cached sFocusedWidget pointer rather than walking the tree to
|
// the cached sFocusedWidget pointer rather than walking the tree to
|
||||||
// find the focused widget — an O(1) operation vs O(n).
|
// find the focused widget -- an O(1) operation vs O(n).
|
||||||
WidgetT *prevFocus = sFocusedWidget;
|
WidgetT *prevFocus = sFocusedWidget;
|
||||||
|
|
||||||
if (sFocusedWidget) {
|
if (sFocusedWidget) {
|
||||||
|
|
@ -568,7 +576,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
hit->wclass->onMouse(hit, root, vx, vy);
|
hit->wclass->onMouse(hit, root, vx, vy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Universal click/double-click callbacks — fire for ALL widget types
|
// Universal click/double-click callbacks -- fire for ALL widget types
|
||||||
// after the type-specific handler has run. Buttons and image buttons
|
// after the type-specific handler has run. Buttons and image buttons
|
||||||
// are excluded from onClick because they use press-release semantics
|
// are excluded from onClick because they use press-release semantics
|
||||||
// (onClick fires on button-up, not button-down) and already handle it
|
// (onClick fires on button-up, not button-down) and already handle it
|
||||||
|
|
@ -619,7 +627,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
// Scroll offset is applied by shifting the root widget's position
|
// Scroll offset is applied by shifting the root widget's position
|
||||||
// to negative coordinates (-scrollX, -scrollY). This elegantly makes
|
// to negative coordinates (-scrollX, -scrollY). This elegantly makes
|
||||||
// scrolling work without any special scroll handling in individual
|
// scrolling work without any special scroll handling in individual
|
||||||
// widgets — their positions are simply offset, and the clip rect
|
// widgets -- their positions are simply offset, and the clip rect
|
||||||
// on the DisplayT limits drawing to the visible area.
|
// on the DisplayT limits drawing to the visible area.
|
||||||
//
|
//
|
||||||
// The conditional re-layout avoids redundant layout passes when only
|
// The conditional re-layout avoids redundant layout passes when only
|
||||||
|
|
@ -728,7 +736,7 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
|
||||||
(void)orient;
|
(void)orient;
|
||||||
(void)value;
|
(void)value;
|
||||||
|
|
||||||
// Repaint with new scroll position — dvxInvalidateWindow calls onPaint
|
// Repaint with new scroll position -- dvxInvalidateWindow calls onPaint
|
||||||
if (win->widgetRoot) {
|
if (win->widgetRoot) {
|
||||||
AppContextT *ctx = wgtGetContext(win->widgetRoot);
|
AppContextT *ctx = wgtGetContext(win->widgetRoot);
|
||||||
|
|
||||||
|
|
@ -740,7 +748,7 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// widgetReorderDrop — finalize drag-reorder on mouse release
|
// widgetReorderDrop -- finalize drag-reorder on mouse release
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Completes a drag-reorder operation for ListBox, ListView, or TreeView.
|
// Completes a drag-reorder operation for ListBox, ListView, or TreeView.
|
||||||
|
|
@ -963,7 +971,7 @@ void widgetReorderDrop(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// widgetReorderUpdate — update drop position during drag
|
// widgetReorderUpdate -- update drop position during drag
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Tracks the mouse during a drag-reorder, updating the drop indicator
|
// Tracks the mouse during a drag-reorder, updating the drop indicator
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetImage.c — Image widget (displays bitmap, responds to clicks)
|
// widgetImage.c -- Image widget (displays bitmap, responds to clicks)
|
||||||
//
|
//
|
||||||
// Displays a bitmap image, optionally responding to clicks. The image data
|
// Displays a bitmap image, optionally responding to clicks. The image data
|
||||||
// must be in the display's native pixel format (pre-converted). Two creation
|
// must be in the display's native pixel format (pre-converted). Two creation
|
||||||
|
|
@ -9,11 +9,11 @@
|
||||||
//
|
//
|
||||||
// The widget supports a simple press effect (1px offset on click) and fires
|
// The widget supports a simple press effect (1px offset on click) and fires
|
||||||
// onClick immediately on mouse-down. Unlike Button which has press/release
|
// onClick immediately on mouse-down. Unlike Button which has press/release
|
||||||
// tracking, Image fires instantly — this is intentional for image-based
|
// tracking, Image fires instantly -- this is intentional for image-based
|
||||||
// click targets where visual press feedback is less important than
|
// click targets where visual press feedback is less important than
|
||||||
// responsiveness.
|
// responsiveness.
|
||||||
//
|
//
|
||||||
// No border or bevel is drawn — the image fills its widget bounds with
|
// No border or bevel is drawn -- the image fills its widget bounds with
|
||||||
// centering if the widget is larger than the image.
|
// centering if the widget is larger than the image.
|
||||||
|
|
||||||
#include "widgetInternal.h"
|
#include "widgetInternal.h"
|
||||||
|
|
@ -114,7 +114,7 @@ void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
|
|
||||||
// Mouse click: briefly sets pressed=true (for the 1px offset effect),
|
// Mouse click: briefly sets pressed=true (for the 1px offset effect),
|
||||||
// invalidates to show the press, fires onClick, then immediately clears
|
// invalidates to show the press, fires onClick, then immediately clears
|
||||||
// pressed. The press is purely visual — there's no release tracking like
|
// pressed. The press is purely visual -- there's no release tracking like
|
||||||
// Button has, since Image clicks are meant for instant actions (e.g.,
|
// Button has, since Image clicks are meant for instant actions (e.g.,
|
||||||
// clicking a logo or icon area).
|
// clicking a logo or icon area).
|
||||||
void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetImageButton.c — Image button widget (button with image instead of text)
|
// widgetImageButton.c -- Image button widget (button with image instead of text)
|
||||||
//
|
//
|
||||||
// Combines a Button's beveled border and press behavior with an Image's
|
// Combines a Button's beveled border and press behavior with an Image's
|
||||||
// bitmap rendering. The image is centered within the button bounds and
|
// bitmap rendering. The image is centered within the button bounds and
|
||||||
|
|
@ -9,11 +9,11 @@
|
||||||
// and the event dispatcher handles release/cancel. The onClick callback
|
// and the event dispatcher handles release/cancel. The onClick callback
|
||||||
// fires on release, not press.
|
// fires on release, not press.
|
||||||
//
|
//
|
||||||
// The widget takes ownership of the image data buffer — if widget creation
|
// The widget takes ownership of the image data buffer -- if widget creation
|
||||||
// fails, the data is freed to prevent leaks.
|
// fails, the data is freed to prevent leaks.
|
||||||
//
|
//
|
||||||
// The 4px added to min size (widgetImageButtonCalcMinSize) accounts for
|
// The 4px added to min size (widgetImageButtonCalcMinSize) accounts for
|
||||||
// the 2px bevel on each side — no extra padding is added beyond that,
|
// the 2px bevel on each side -- no extra padding is added beyond that,
|
||||||
// keeping image buttons compact for toolbar use.
|
// keeping image buttons compact for toolbar use.
|
||||||
|
|
||||||
#include "widgetInternal.h"
|
#include "widgetInternal.h"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetInternal.h — Shared internal header for widget implementation files
|
// widgetInternal.h -- Shared internal header for widget implementation files
|
||||||
//
|
//
|
||||||
// This header is included only by widget implementation .c files (in the
|
// This header is included only by widget implementation .c files (in the
|
||||||
// widgets/ directory), never by application code. It exposes the vtable
|
// widgets/ directory), never by application code. It exposes the vtable
|
||||||
|
|
@ -6,11 +6,11 @@
|
||||||
// widget implementations need but the public API should not expose.
|
// widget implementations need but the public API should not expose.
|
||||||
//
|
//
|
||||||
// The widget system is split across multiple .c files by concern:
|
// The widget system is split across multiple .c files by concern:
|
||||||
// widgetCore.c — allocation, tree ops, focus management, shared helpers
|
// widgetCore.c -- allocation, tree ops, focus management, shared helpers
|
||||||
// widgetLayout.c — measure + layout algorithms for box containers
|
// widgetLayout.c -- measure + layout algorithms for box containers
|
||||||
// widgetEvent.c — mouse/keyboard dispatch, scrollbar management
|
// widgetEvent.c -- mouse/keyboard dispatch, scrollbar management
|
||||||
// widgetOps.c — paint dispatch, overlay rendering
|
// widgetOps.c -- paint dispatch, overlay rendering
|
||||||
// widget*.c — per-type paint, event, and layout implementations
|
// widget*.c -- per-type paint, event, and layout implementations
|
||||||
#ifndef WIDGET_INTERNAL_H
|
#ifndef WIDGET_INTERNAL_H
|
||||||
#define WIDGET_INTERNAL_H
|
#define WIDGET_INTERNAL_H
|
||||||
|
|
||||||
|
|
@ -35,12 +35,12 @@
|
||||||
//
|
//
|
||||||
// Flags encode static properties that the framework needs without calling
|
// Flags encode static properties that the framework needs without calling
|
||||||
// into the vtable:
|
// into the vtable:
|
||||||
// FOCUSABLE — can receive keyboard focus (Tab navigation)
|
// FOCUSABLE -- can receive keyboard focus (Tab navigation)
|
||||||
// BOX_CONTAINER — uses the generic VBox/HBox layout algorithm
|
// BOX_CONTAINER -- uses the generic VBox/HBox layout algorithm
|
||||||
// HORIZ_CONTAINER — lays out children horizontally (vs. default vertical)
|
// HORIZ_CONTAINER -- lays out children horizontally (vs. default vertical)
|
||||||
// PAINTS_CHILDREN — the widget's paint function handles child rendering
|
// PAINTS_CHILDREN -- the widget's paint function handles child rendering
|
||||||
// (e.g. TabControl only paints the active tab page)
|
// (e.g. TabControl only paints the active tab page)
|
||||||
// NO_HIT_RECURSE — hit testing stops at this widget and doesn't recurse
|
// NO_HIT_RECURSE -- hit testing stops at this widget and doesn't recurse
|
||||||
// into children (e.g. ListBox handles its own items)
|
// into children (e.g. ListBox handles its own items)
|
||||||
//
|
//
|
||||||
// NULL function pointers are valid and mean "no-op" for that operation.
|
// NULL function pointers are valid and mean "no-op" for that operation.
|
||||||
|
|
@ -66,7 +66,7 @@ typedef struct WidgetClassT {
|
||||||
void (*setText)(WidgetT *w, const char *text);
|
void (*setText)(WidgetT *w, const char *text);
|
||||||
} WidgetClassT;
|
} WidgetClassT;
|
||||||
|
|
||||||
// Global vtable array — one entry per WidgetTypeE value. Defined in
|
// Global vtable array -- one entry per WidgetTypeE value. Defined in
|
||||||
// widgetCore.c. Must stay in sync with the WidgetTypeE enum order.
|
// widgetCore.c. Must stay in sync with the WidgetTypeE enum order.
|
||||||
extern const WidgetClassT *widgetClassTable[];
|
extern const WidgetClassT *widgetClassTable[];
|
||||||
|
|
||||||
|
|
@ -149,7 +149,7 @@ static inline int32_t clampInt(int32_t val, int32_t lo, int32_t hi) {
|
||||||
// The illusion works by drawing the text twice: first offset +1,+1 in
|
// The illusion works by drawing the text twice: first offset +1,+1 in
|
||||||
// the highlight color (creating a light "shadow" behind the text), then
|
// the highlight color (creating a light "shadow" behind the text), then
|
||||||
// at the original position in the shadow color. The result is text that
|
// at the original position in the shadow color. The result is text that
|
||||||
// appears chiseled into the surface — the universal visual indicator for
|
// appears chiseled into the surface -- the universal visual indicator for
|
||||||
// "greyed out" in Motif and Windows 3.x era GUIs.
|
// "greyed out" in Motif and Windows 3.x era GUIs.
|
||||||
static inline void drawTextEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors) {
|
static inline void drawTextEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors) {
|
||||||
drawText(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
|
drawText(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
|
||||||
|
|
@ -170,7 +170,7 @@ static inline void drawTextAccelEmbossed(DisplayT *d, const BlitOpsT *ops, const
|
||||||
// slider and resize a listview column simultaneously). Using globals
|
// slider and resize a listview column simultaneously). Using globals
|
||||||
// rather than per-widget state avoids bloating every widget with fields
|
// rather than per-widget state avoids bloating every widget with fields
|
||||||
// that are only relevant during a single drag operation. The tradeoff
|
// that are only relevant during a single drag operation. The tradeoff
|
||||||
// is that these are truly global — but since the GUI is single-threaded
|
// is that these are truly global -- but since the GUI is single-threaded
|
||||||
// and only one mouse can exist, this is safe.
|
// and only one mouse can exist, this is safe.
|
||||||
|
|
||||||
extern bool sCursorBlinkOn; // text cursor blink phase (toggled by wgtUpdateCursorBlink)
|
extern bool sCursorBlinkOn; // text cursor blink phase (toggled by wgtUpdateCursorBlink)
|
||||||
|
|
@ -209,13 +209,13 @@ void widgetDestroyChildren(WidgetT *w);
|
||||||
// Allocate a new widget, set its type and vtable, and add it as a child.
|
// Allocate a new widget, set its type and vtable, and add it as a child.
|
||||||
WidgetT *widgetAlloc(WidgetT *parent, WidgetTypeE type);
|
WidgetT *widgetAlloc(WidgetT *parent, WidgetTypeE type);
|
||||||
|
|
||||||
// Focus management — Tab/Shift-Tab navigation walks the tree in creation
|
// Focus management -- Tab/Shift-Tab navigation walks the tree in creation
|
||||||
// order, skipping non-focusable and hidden widgets.
|
// order, skipping non-focusable and hidden widgets.
|
||||||
void widgetClearFocus(WidgetT *root);
|
void widgetClearFocus(WidgetT *root);
|
||||||
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
|
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
|
||||||
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);
|
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);
|
||||||
|
|
||||||
// Alt+key accelerator lookup — searches the tree for a widget whose
|
// Alt+key accelerator lookup -- searches the tree for a widget whose
|
||||||
// accelKey matches, used for keyboard navigation of labeled controls.
|
// accelKey matches, used for keyboard navigation of labeled controls.
|
||||||
WidgetT *widgetFindByAccel(WidgetT *root, char key);
|
WidgetT *widgetFindByAccel(WidgetT *root, char key);
|
||||||
|
|
||||||
|
|
@ -226,7 +226,7 @@ bool widgetIsFocusable(WidgetTypeE type);
|
||||||
bool widgetIsBoxContainer(WidgetTypeE type);
|
bool widgetIsBoxContainer(WidgetTypeE type);
|
||||||
bool widgetIsHorizContainer(WidgetTypeE type);
|
bool widgetIsHorizContainer(WidgetTypeE type);
|
||||||
|
|
||||||
// Hit testing — find the deepest widget containing the point (x,y).
|
// Hit testing -- find the deepest widget containing the point (x,y).
|
||||||
// Respects WCLASS_NO_HIT_RECURSE to stop at list/tree widgets.
|
// Respects WCLASS_NO_HIT_RECURSE to stop at list/tree widgets.
|
||||||
WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
|
WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
|
||||||
|
|
||||||
|
|
@ -498,7 +498,7 @@ void widgetTreeViewOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void widgetTimerCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
void widgetTimerCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
void widgetTimerDestroy(WidgetT *w);
|
void widgetTimerDestroy(WidgetT *w);
|
||||||
|
|
||||||
// Drag-reorder helpers — generic drag-and-drop reordering shared by
|
// Drag-reorder helpers -- generic drag-and-drop reordering shared by
|
||||||
// ListBox, ListView, and TreeView. The update function tracks the mouse
|
// ListBox, ListView, and TreeView. The update function tracks the mouse
|
||||||
// position and computes the drop target, the drop function commits the
|
// position and computes the drop target, the drop function commits the
|
||||||
// reorder by relinking the item in the data structure.
|
// reorder by relinking the item in the data structure.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
// widgetLabel.c — Label widget
|
// widgetLabel.c -- Label widget
|
||||||
//
|
//
|
||||||
// Static text display — the simplest widget. Not focusable, not interactive.
|
// Static text display -- the simplest widget. Not focusable, not interactive.
|
||||||
// Supports accelerator keys via '&' prefix: when the user presses Alt+key,
|
// Supports accelerator keys via '&' prefix: when the user presses Alt+key,
|
||||||
// focus moves to the next focusable widget after this label. This follows
|
// focus moves to the next focusable widget after this label. This follows
|
||||||
// the Win3.1 convention where labels act as keyboard shortcuts for adjacent
|
// the Win3.1 convention where labels act as keyboard shortcuts for adjacent
|
||||||
// controls (e.g., a label "&Name:" before a text input).
|
// controls (e.g., a label "&Name:" before a text input).
|
||||||
//
|
//
|
||||||
// The text pointer is stored directly (not copied) — the caller must ensure
|
// The text pointer is stored directly (not copied) -- the caller must ensure
|
||||||
// the string remains valid for the widget's lifetime, or use widgetLabelSetText
|
// the string remains valid for the widget's lifetime, or use widgetLabelSetText
|
||||||
// to update it. This avoids unnecessary allocations for the common case of
|
// to update it. This avoids unnecessary allocations for the common case of
|
||||||
// literal string labels.
|
// literal string labels.
|
||||||
|
|
@ -68,15 +68,34 @@ void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetLabelPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
void widgetLabelPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||||
|
int32_t textW = textWidthAccel(font, w->as.label.text);
|
||||||
|
int32_t textX = w->x;
|
||||||
int32_t textY = w->y + (w->h - font->charHeight) / 2;
|
int32_t textY = w->y + (w->h - font->charHeight) / 2;
|
||||||
|
|
||||||
|
if (w->as.label.textAlign == AlignCenterE) {
|
||||||
|
textX = w->x + (w->w - textW) / 2;
|
||||||
|
} else if (w->as.label.textAlign == AlignEndE) {
|
||||||
|
textX = w->x + w->w - textW;
|
||||||
|
}
|
||||||
|
|
||||||
if (!w->enabled) {
|
if (!w->enabled) {
|
||||||
drawTextAccelEmbossed(d, ops, font, w->x, textY, w->as.label.text, colors);
|
drawTextAccelEmbossed(d, ops, font, textX, textY, w->as.label.text, colors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||||
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||||
|
|
||||||
drawTextAccel(d, ops, font, w->x, textY, w->as.label.text, fg, bg, false);
|
drawTextAccel(d, ops, font, textX, textY, w->as.label.text, fg, bg, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtLabelSetAlign
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align) {
|
||||||
|
if (w && w->type == WidgetLabelE) {
|
||||||
|
w->as.label.textAlign = align;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetLayout.c — Layout engine (measure + arrange)
|
// widgetLayout.c -- Layout engine (measure + arrange)
|
||||||
//
|
//
|
||||||
// Implements a two-pass layout algorithm inspired by CSS flexbox:
|
// Implements a two-pass layout algorithm inspired by CSS flexbox:
|
||||||
// Pass 1 (Measure): bottom-up calculation of each widget's minimum
|
// Pass 1 (Measure): bottom-up calculation of each widget's minimum
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
// be bottom-up before arrange can be top-down.
|
// be bottom-up before arrange can be top-down.
|
||||||
//
|
//
|
||||||
// Why flexbox-like rather than constraint-based:
|
// Why flexbox-like rather than constraint-based:
|
||||||
// Flexbox maps naturally to the VBox/HBox container model — it's simple
|
// Flexbox maps naturally to the VBox/HBox container model -- it's simple
|
||||||
// to understand, implement, and debug. Constraint-based layout (like
|
// to understand, implement, and debug. Constraint-based layout (like
|
||||||
// Cassowary) would add significant complexity for little benefit in a
|
// Cassowary) would add significant complexity for little benefit in a
|
||||||
// DOS GUI where most layouts are linear stacks of widgets.
|
// DOS GUI where most layouts are linear stacks of widgets.
|
||||||
|
|
@ -406,7 +406,7 @@ void wgtLayout(WidgetT *root, int32_t availW, int32_t availH, const BitmapFontT
|
||||||
// The tradeoff is that pixel values are limited to 30 bits (~1 billion),
|
// The tradeoff is that pixel values are limited to 30 bits (~1 billion),
|
||||||
// which is far more than any supported display resolution.
|
// which is far more than any supported display resolution.
|
||||||
//
|
//
|
||||||
// A value of 0 means "auto" (use intrinsic/calculated size) — the
|
// A value of 0 means "auto" (use intrinsic/calculated size) -- the
|
||||||
// caller checks for 0 before calling this function.
|
// caller checks for 0 before calling this function.
|
||||||
|
|
||||||
int32_t wgtResolveSize(int32_t taggedSize, int32_t parentSize, int32_t charWidth) {
|
int32_t wgtResolveSize(int32_t taggedSize, int32_t parentSize, int32_t charWidth) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetListBox.c — ListBox widget (single and multi-select)
|
// widgetListBox.c -- ListBox widget (single and multi-select)
|
||||||
//
|
//
|
||||||
// Scrollable list of text items with single-select or multi-select modes.
|
// Scrollable list of text items with single-select or multi-select modes.
|
||||||
// Items are stored as external string pointers (not copied), with a vertical
|
// Items are stored as external string pointers (not copied), with a vertical
|
||||||
|
|
@ -50,7 +50,7 @@ static void selectRange(WidgetT *w, int32_t from, int32_t to);
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Allocate (or re-allocate) the selection bits array. Only allocated in
|
// Allocate (or re-allocate) the selection bits array. Only allocated in
|
||||||
// multi-select mode — in single-select, selectedIdx alone tracks the
|
// multi-select mode -- in single-select, selectedIdx alone tracks the
|
||||||
// selection. calloc initializes all bits to 0 (nothing selected).
|
// selection. calloc initializes all bits to 0 (nothing selected).
|
||||||
static void allocSelBits(WidgetT *w) {
|
static void allocSelBits(WidgetT *w) {
|
||||||
if (w->as.listBox.selBits) {
|
if (w->as.listBox.selBits) {
|
||||||
|
|
@ -362,13 +362,13 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
bool ctrl = (mod & KEY_MOD_CTRL) != 0;
|
bool ctrl = (mod & KEY_MOD_CTRL) != 0;
|
||||||
int32_t sel = w->as.listBox.selectedIdx;
|
int32_t sel = w->as.listBox.selectedIdx;
|
||||||
|
|
||||||
// Ctrl+A — select all (multi-select only)
|
// Ctrl+A -- select all (multi-select only)
|
||||||
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
|
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
|
||||||
wgtListBoxSelectAll(w);
|
wgtListBoxSelectAll(w);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Space — toggle current item (multi-select only)
|
// Space -- toggle current item (multi-select only)
|
||||||
if (multi && key == ' ') {
|
if (multi && key == ' ') {
|
||||||
if (sel >= 0 && w->as.listBox.selBits) {
|
if (sel >= 0 && w->as.listBox.selBits) {
|
||||||
w->as.listBox.selBits[sel] ^= 1;
|
w->as.listBox.selBits[sel] ^= 1;
|
||||||
|
|
@ -383,7 +383,7 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter — activate selected item (same as double-click)
|
// Enter -- activate selected item (same as double-click)
|
||||||
if (key == '\r' || key == '\n') {
|
if (key == '\r' || key == '\n') {
|
||||||
if (w->onDblClick && sel >= 0) {
|
if (w->onDblClick && sel >= 0) {
|
||||||
w->onDblClick(w);
|
w->onDblClick(w);
|
||||||
|
|
@ -557,7 +557,7 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
// Paint: sunken bevel border, then iterate visible rows drawing text.
|
// Paint: sunken bevel border, then iterate visible rows drawing text.
|
||||||
// Selected items get highlight background (full row width) with contrasting
|
// Selected items get highlight background (full row width) with contrasting
|
||||||
// text color. In multi-select mode, the cursor item (selectedIdx) gets a
|
// text color. In multi-select mode, the cursor item (selectedIdx) gets a
|
||||||
// dotted focus rect overlay — this is separate from the selection highlight
|
// dotted focus rect overlay -- this is separate from the selection highlight
|
||||||
// so the user can see which item the keyboard cursor is on even when multiple
|
// so the user can see which item the keyboard cursor is on even when multiple
|
||||||
// items are selected. The scrollbar is drawn only when needed (item count
|
// items are selected. The scrollbar is drawn only when needed (item count
|
||||||
// exceeds visible rows), and the content width is reduced accordingly.
|
// exceeds visible rows), and the content width is reduced accordingly.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetListView.c — ListView (multi-column list) widget
|
// widgetListView.c -- ListView (multi-column list) widget
|
||||||
//
|
//
|
||||||
// A multi-column list with clickable/sortable column headers, horizontal
|
// A multi-column list with clickable/sortable column headers, horizontal
|
||||||
// and vertical scrolling, column resize by dragging header borders, and
|
// and vertical scrolling, column resize by dragging header borders, and
|
||||||
|
|
@ -11,16 +11,16 @@
|
||||||
//
|
//
|
||||||
// ListView state is heap-allocated separately (ListViewDataT*) rather than
|
// ListView state is heap-allocated separately (ListViewDataT*) rather than
|
||||||
// inlined in the widget union because the state is too large for a union
|
// inlined in the widget union because the state is too large for a union
|
||||||
// member — it includes per-column resolved widths, sort index, selection
|
// member -- it includes per-column resolved widths, sort index, selection
|
||||||
// bits, and numerous scroll/drag state fields.
|
// bits, and numerous scroll/drag state fields.
|
||||||
//
|
//
|
||||||
// Sort: clicking a column header toggles sort direction. Sorting uses an
|
// Sort: clicking a column header toggles sort direction. Sorting uses an
|
||||||
// indirection array (sortIndex) rather than rearranging the cellData. This
|
// indirection array (sortIndex) rather than rearranging the cellData. This
|
||||||
// is critical because:
|
// is critical because:
|
||||||
// 1. The application owns cellData and may not expect it to be mutated
|
// 1. The application owns cellData and may not expect it to be mutated
|
||||||
// 2. Multi-select selBits are indexed by data row, not display row —
|
// 2. Multi-select selBits are indexed by data row, not display row --
|
||||||
// rearranging data would invalidate all selection state
|
// rearranging data would invalidate all selection state
|
||||||
// 3. The indirection allows O(1) display↔data row mapping
|
// 3. The indirection allows O(1) display<->data row mapping
|
||||||
//
|
//
|
||||||
// Insertion sort is used because it's stable (preserves original order for
|
// Insertion sort is used because it's stable (preserves original order for
|
||||||
// equal keys) and has good performance for the typical case of partially-sorted
|
// equal keys) and has good performance for the typical case of partially-sorted
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
// Horizontal scrolling is needed when total column width exceeds the widget's
|
// Horizontal scrolling is needed when total column width exceeds the widget's
|
||||||
// inner width. Both headers and data rows are offset by scrollPosH. The
|
// inner width. Both headers and data rows are offset by scrollPosH. The
|
||||||
// horizontal scrollbar appears automatically when needed, and its presence
|
// horizontal scrollbar appears automatically when needed, and its presence
|
||||||
// may trigger the vertical scrollbar to appear (and vice versa) — this
|
// may trigger the vertical scrollbar to appear (and vice versa) -- this
|
||||||
// two-pass logic handles the interdependency correctly.
|
// two-pass logic handles the interdependency correctly.
|
||||||
|
|
||||||
#include "widgetInternal.h"
|
#include "widgetInternal.h"
|
||||||
|
|
@ -71,7 +71,7 @@ static void resolveColumnWidths(WidgetT *w, const BitmapFontT *font);
|
||||||
// allocListViewSelBits
|
// allocListViewSelBits
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Same allocation strategy as ListBox's selBits — one byte per row,
|
// Same allocation strategy as ListBox's selBits -- one byte per row,
|
||||||
// only allocated when multiSelect is enabled.
|
// only allocated when multiSelect is enabled.
|
||||||
static void allocListViewSelBits(WidgetT *w) {
|
static void allocListViewSelBits(WidgetT *w) {
|
||||||
if (w->as.listView->selBits) {
|
if (w->as.listView->selBits) {
|
||||||
|
|
@ -97,16 +97,16 @@ static void allocListViewSelBits(WidgetT *w) {
|
||||||
|
|
||||||
// Build the sort indirection array. Rather than rearranging cellData (which
|
// Build the sort indirection array. Rather than rearranging cellData (which
|
||||||
// the application owns and indexes into), we maintain a separate array that
|
// the application owns and indexes into), we maintain a separate array that
|
||||||
// maps display-row → data-row. Paint and keyboard navigation work in display-row
|
// maps display-row -> data-row. Paint and keyboard navigation work in display-row
|
||||||
// space, then map through sortIndex to get the data row for cell lookups and
|
// space, then map through sortIndex to get the data row for cell lookups and
|
||||||
// selection operations. This decoupling is essential because selBits are
|
// selection operations. This decoupling is essential because selBits are
|
||||||
// indexed by data row — sorting must not invalidate selection state.
|
// indexed by data row -- sorting must not invalidate selection state.
|
||||||
static void listViewBuildSortIndex(WidgetT *w) {
|
static void listViewBuildSortIndex(WidgetT *w) {
|
||||||
int32_t rowCount = w->as.listView->rowCount;
|
int32_t rowCount = w->as.listView->rowCount;
|
||||||
int32_t sortCol = w->as.listView->sortCol;
|
int32_t sortCol = w->as.listView->sortCol;
|
||||||
int32_t colCount = w->as.listView->colCount;
|
int32_t colCount = w->as.listView->colCount;
|
||||||
|
|
||||||
// No sort active — clear index
|
// No sort active -- clear index
|
||||||
if (sortCol < 0 || w->as.listView->sortDir == ListViewSortNoneE || rowCount <= 0) {
|
if (sortCol < 0 || w->as.listView->sortDir == ListViewSortNoneE || rowCount <= 0) {
|
||||||
if (w->as.listView->sortIndex) {
|
if (w->as.listView->sortIndex) {
|
||||||
free(w->as.listView->sortIndex);
|
free(w->as.listView->sortIndex);
|
||||||
|
|
@ -134,7 +134,7 @@ static void listViewBuildSortIndex(WidgetT *w) {
|
||||||
idx[i] = i;
|
idx[i] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insertion sort — stable (equal elements keep their original order),
|
// Insertion sort -- stable (equal elements keep their original order),
|
||||||
// O(n^2) worst case but with very low constant factors. For typical
|
// O(n^2) worst case but with very low constant factors. For typical
|
||||||
// ListView row counts (10s to low 100s), this beats quicksort because
|
// ListView row counts (10s to low 100s), this beats quicksort because
|
||||||
// there's no recursion overhead, no stack usage, and the inner loop
|
// there's no recursion overhead, no stack usage, and the inner loop
|
||||||
|
|
@ -491,7 +491,7 @@ void wgtListViewSetReorderable(WidgetT *w, bool reorderable) {
|
||||||
|
|
||||||
w->as.listView->reorderable = reorderable;
|
w->as.listView->reorderable = reorderable;
|
||||||
|
|
||||||
// Disable sorting when reorderable — sort order conflicts with manual order
|
// Disable sorting when reorderable -- sort order conflicts with manual order
|
||||||
if (reorderable) {
|
if (reorderable) {
|
||||||
w->as.listView->sortCol = -1;
|
w->as.listView->sortCol = -1;
|
||||||
w->as.listView->sortDir = ListViewSortNoneE;
|
w->as.listView->sortDir = ListViewSortNoneE;
|
||||||
|
|
@ -596,7 +596,7 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
int32_t rowCount = w->as.listView->rowCount;
|
int32_t rowCount = w->as.listView->rowCount;
|
||||||
int32_t *sortIdx = w->as.listView->sortIndex;
|
int32_t *sortIdx = w->as.listView->sortIndex;
|
||||||
|
|
||||||
// Enter — activate selected item (same as double-click)
|
// Enter -- activate selected item (same as double-click)
|
||||||
if (key == '\r' || key == '\n') {
|
if (key == '\r' || key == '\n') {
|
||||||
if (w->onDblClick && w->as.listView->selectedIdx >= 0) {
|
if (w->onDblClick && w->as.listView->selectedIdx >= 0) {
|
||||||
w->onDblClick(w);
|
w->onDblClick(w);
|
||||||
|
|
@ -605,13 +605,13 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+A — select all (multi-select only)
|
// Ctrl+A -- select all (multi-select only)
|
||||||
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
|
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
|
||||||
wgtListViewSelectAll(w);
|
wgtListViewSelectAll(w);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Space — toggle current item (multi-select only)
|
// Space -- toggle current item (multi-select only)
|
||||||
if (multi && key == ' ') {
|
if (multi && key == ' ') {
|
||||||
int32_t sel = w->as.listView->selectedIdx;
|
int32_t sel = w->as.listView->selectedIdx;
|
||||||
|
|
||||||
|
|
@ -738,7 +738,7 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
// distinct hit regions (checked in priority order):
|
// distinct hit regions (checked in priority order):
|
||||||
// 1. Vertical scrollbar (right edge, if visible)
|
// 1. Vertical scrollbar (right edge, if visible)
|
||||||
// 2. Horizontal scrollbar (bottom edge, if visible)
|
// 2. Horizontal scrollbar (bottom edge, if visible)
|
||||||
// 3. Dead corner (when both scrollbars present — no action)
|
// 3. Dead corner (when both scrollbars present -- no action)
|
||||||
// 4. Column headers: resize drag (border +-3px) or sort toggle (click)
|
// 4. Column headers: resize drag (border +-3px) or sort toggle (click)
|
||||||
// 5. Data rows: item selection with Ctrl/Shift modifiers, double-click,
|
// 5. Data rows: item selection with Ctrl/Shift modifiers, double-click,
|
||||||
// and drag-reorder initiation
|
// and drag-reorder initiation
|
||||||
|
|
@ -950,7 +950,7 @@ void widgetListViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
||||||
colX += cw;
|
colX += cw;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not on a border — check for sort click (disabled when reorderable
|
// Not on a border -- check for sort click (disabled when reorderable
|
||||||
// since sorting conflicts with manual ordering). Clicking a column
|
// since sorting conflicts with manual ordering). Clicking a column
|
||||||
// toggles between ascending/descending; clicking a different column
|
// toggles between ascending/descending; clicking a different column
|
||||||
// starts ascending.
|
// starts ascending.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetOps.c — Paint dispatcher and public widget operations
|
// widgetOps.c -- Paint dispatcher and public widget operations
|
||||||
//
|
//
|
||||||
// This file contains two categories of functions:
|
// This file contains two categories of functions:
|
||||||
// 1. The paint dispatcher (widgetPaintOne, widgetPaintOverlays, wgtPaint)
|
// 1. The paint dispatcher (widgetPaintOne, widgetPaintOverlays, wgtPaint)
|
||||||
|
|
@ -57,7 +57,7 @@ static void debugContainerBorder(WidgetT *w, DisplayT *d, const BlitOpsT *ops) {
|
||||||
//
|
//
|
||||||
// Recursive paint walker. For each visible widget:
|
// Recursive paint walker. For each visible widget:
|
||||||
// 1. Call the widget's paint function (if any) via vtable.
|
// 1. Call the widget's paint function (if any) via vtable.
|
||||||
// 2. If the widget has WCLASS_PAINTS_CHILDREN, stop recursion —
|
// 2. If the widget has WCLASS_PAINTS_CHILDREN, stop recursion --
|
||||||
// the widget's paint function already handled its children
|
// the widget's paint function already handled its children
|
||||||
// (e.g. TabControl only paints the active tab's children).
|
// (e.g. TabControl only paints the active tab's children).
|
||||||
// 3. Otherwise, recurse into children (default child painting).
|
// 3. Otherwise, recurse into children (default child painting).
|
||||||
|
|
@ -190,7 +190,7 @@ void wgtDestroy(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// widgetHashName — djb2 hash for widget name lookup
|
// widgetHashName -- djb2 hash for widget name lookup
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// The djb2 hash function (Dan Bernstein) is used for fast name-based
|
// The djb2 hash function (Dan Bernstein) is used for fast name-based
|
||||||
|
|
@ -212,7 +212,7 @@ static uint32_t widgetHashName(const char *s) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// wgtFindByHash — recursive search with hash fast-reject
|
// wgtFindByHash -- recursive search with hash fast-reject
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Depth-first search by name with hash pre-screening. The hash
|
// Depth-first search by name with hash pre-screening. The hash
|
||||||
|
|
@ -297,7 +297,7 @@ AppContextT *wgtGetContext(const WidgetT *w) {
|
||||||
// wgtGetText
|
// wgtGetText
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Polymorphic text getter — dispatches through the vtable to the
|
// Polymorphic text getter -- dispatches through the vtable to the
|
||||||
// appropriate getText implementation for the widget's type. Returns
|
// appropriate getText implementation for the widget's type. Returns
|
||||||
// an empty string (not NULL) if the widget has no text or no getText
|
// an empty string (not NULL) if the widget has no text or no getText
|
||||||
// handler, so callers don't need NULL checks.
|
// handler, so callers don't need NULL checks.
|
||||||
|
|
@ -326,7 +326,7 @@ const char *wgtGetText(const WidgetT *w) {
|
||||||
// The root widget's userData points to the AppContextT, which is
|
// The root widget's userData points to the AppContextT, which is
|
||||||
// the bridge back to the display, font, colors, and blitOps needed
|
// the bridge back to the display, font, colors, and blitOps needed
|
||||||
// for painting. This avoids threading the context through every
|
// for painting. This avoids threading the context through every
|
||||||
// widget function — any widget can retrieve it via wgtGetContext().
|
// widget function -- any widget can retrieve it via wgtGetContext().
|
||||||
|
|
||||||
WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
|
WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
|
||||||
WidgetT *root = widgetAlloc(NULL, WidgetVBoxE);
|
WidgetT *root = widgetAlloc(NULL, WidgetVBoxE);
|
||||||
|
|
@ -355,7 +355,7 @@ WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
|
||||||
// Full invalidation: re-measures the widget tree, manages scrollbars,
|
// Full invalidation: re-measures the widget tree, manages scrollbars,
|
||||||
// re-lays out, repaints, and dirties the window on screen.
|
// re-lays out, repaints, and dirties the window on screen.
|
||||||
//
|
//
|
||||||
// This is the "something structural changed" path — use when widget
|
// This is the "something structural changed" path -- use when widget
|
||||||
// sizes may have changed (text changed, children added/removed,
|
// sizes may have changed (text changed, children added/removed,
|
||||||
// visibility toggled). If only visual state changed (cursor blink,
|
// visibility toggled). If only visual state changed (cursor blink,
|
||||||
// selection highlight), use wgtInvalidatePaint() instead to skip
|
// selection highlight), use wgtInvalidatePaint() instead to skip
|
||||||
|
|
@ -389,7 +389,7 @@ void wgtInvalidate(WidgetT *w) {
|
||||||
widgetManageScrollbars(w->window, ctx);
|
widgetManageScrollbars(w->window, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dirty the window — dvxInvalidateWindow calls onPaint automatically
|
// Dirty the window -- dvxInvalidateWindow calls onPaint automatically
|
||||||
dvxInvalidateWindow(ctx, w->window);
|
dvxInvalidateWindow(ctx, w->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,7 +398,7 @@ void wgtInvalidate(WidgetT *w) {
|
||||||
// wgtInvalidatePaint
|
// wgtInvalidatePaint
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Lightweight repaint — skips measure/layout/scrollbar management.
|
// Lightweight repaint -- skips measure/layout/scrollbar management.
|
||||||
// Use when only visual state changed (slider value, cursor blink,
|
// Use when only visual state changed (slider value, cursor blink,
|
||||||
// selection highlight, checkbox toggle) but widget sizes are stable.
|
// selection highlight, checkbox toggle) but widget sizes are stable.
|
||||||
|
|
||||||
|
|
@ -419,7 +419,7 @@ void wgtInvalidatePaint(WidgetT *w) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dirty the window — dvxInvalidateWindow calls onPaint automatically
|
// Dirty the window -- dvxInvalidateWindow calls onPaint automatically
|
||||||
dvxInvalidateWindow(ctx, w->window);
|
dvxInvalidateWindow(ctx, w->window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetProgressBar.c — ProgressBar widget
|
// widgetProgressBar.c -- ProgressBar widget
|
||||||
//
|
//
|
||||||
// A non-interactive fill-bar widget for displaying bounded progress.
|
// A non-interactive fill-bar widget for displaying bounded progress.
|
||||||
// Supports both horizontal and vertical orientations via separate
|
// Supports both horizontal and vertical orientations via separate
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetRadio.c — RadioGroup and Radio button widgets
|
// widgetRadio.c -- RadioGroup and Radio button widgets
|
||||||
//
|
//
|
||||||
// Two-level architecture: RadioGroupE is an invisible container that
|
// Two-level architecture: RadioGroupE is an invisible container that
|
||||||
// holds the selection state (selectedIdx), while RadioE children are
|
// holds the selection state (selectedIdx), while RadioE children are
|
||||||
|
|
@ -130,7 +130,7 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
|
||||||
wgtInvalidatePaint(w);
|
wgtInvalidatePaint(w);
|
||||||
} else if (key == (0x50 | 0x100) || key == (0x4D | 0x100)) {
|
} else if (key == (0x50 | 0x100) || key == (0x4D | 0x100)) {
|
||||||
// Down or Right — next radio in group
|
// Down or Right -- next radio in group
|
||||||
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
||||||
WidgetT *next = NULL;
|
WidgetT *next = NULL;
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == (0x48 | 0x100) || key == (0x4B | 0x100)) {
|
} else if (key == (0x48 | 0x100) || key == (0x4B | 0x100)) {
|
||||||
// Up or Left — previous radio in group
|
// Up or Left -- previous radio in group
|
||||||
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
||||||
WidgetT *prev = NULL;
|
WidgetT *prev = NULL;
|
||||||
|
|
||||||
|
|
@ -238,7 +238,7 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diamond border — upper-left edges get highlight, lower-right get shadow
|
// Diamond border -- upper-left edges get highlight, lower-right get shadow
|
||||||
for (int32_t i = 0; i < mid; i++) {
|
for (int32_t i = 0; i < mid; i++) {
|
||||||
int32_t left = mid - i;
|
int32_t left = mid - i;
|
||||||
int32_t right = mid + i;
|
int32_t right = mid + i;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetScrollPane.c — ScrollPane container widget
|
// widgetScrollPane.c -- ScrollPane container widget
|
||||||
//
|
//
|
||||||
// A clipping container that allows its children (laid out as a
|
// A clipping container that allows its children (laid out as a
|
||||||
// vertical box) to overflow and be scrolled into view. This is the
|
// vertical box) to overflow and be scrolled into view. This is the
|
||||||
|
|
@ -150,7 +150,7 @@ static void drawSPVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// spCalcNeeds — determine scrollbar needs and inner dimensions
|
// spCalcNeeds -- determine scrollbar needs and inner dimensions
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Central sizing function called by layout, paint, and mouse handlers.
|
// Central sizing function called by layout, paint, and mouse handlers.
|
||||||
|
|
@ -234,7 +234,7 @@ void widgetScrollPaneCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
widgetCalcMinSizeTree(c, font);
|
widgetCalcMinSizeTree(c, font);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The scroll pane's own min size is small — the whole point is scrolling
|
// The scroll pane's own min size is small -- the whole point is scrolling
|
||||||
w->calcMinW = SP_SB_W * 3 + SP_BORDER * 2;
|
w->calcMinW = SP_SB_W * 3 + SP_BORDER * 2;
|
||||||
w->calcMinH = SP_SB_W * 3 + SP_BORDER * 2;
|
w->calcMinH = SP_SB_W * 3 + SP_BORDER * 2;
|
||||||
}
|
}
|
||||||
|
|
@ -424,7 +424,7 @@ void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
|
||||||
w->as.scrollPane.scrollPosV = clampInt(w->as.scrollPane.scrollPosV, 0, maxScrollV);
|
w->as.scrollPane.scrollPosV = clampInt(w->as.scrollPane.scrollPosV, 0, maxScrollV);
|
||||||
w->as.scrollPane.scrollPosH = clampInt(w->as.scrollPane.scrollPosH, 0, maxScrollH);
|
w->as.scrollPane.scrollPosH = clampInt(w->as.scrollPane.scrollPosH, 0, maxScrollH);
|
||||||
wgtInvalidatePaint(w);
|
wgtInvalidate(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -508,7 +508,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
||||||
}
|
}
|
||||||
|
|
||||||
hit->as.scrollPane.scrollPosV = clampInt(hit->as.scrollPane.scrollPosV, 0, maxScrollV);
|
hit->as.scrollPane.scrollPosV = clampInt(hit->as.scrollPane.scrollPosV, 0, maxScrollV);
|
||||||
wgtInvalidatePaint(hit);
|
wgtInvalidate(hit);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -552,7 +552,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
||||||
}
|
}
|
||||||
|
|
||||||
hit->as.scrollPane.scrollPosH = clampInt(hit->as.scrollPane.scrollPosH, 0, maxScrollH);
|
hit->as.scrollPane.scrollPosH = clampInt(hit->as.scrollPane.scrollPosH, 0, maxScrollH);
|
||||||
wgtInvalidatePaint(hit);
|
wgtInvalidate(hit);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -567,7 +567,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click on content area — forward to child widgets
|
// Click on content area -- forward to child widgets
|
||||||
// Children are already positioned at scroll-adjusted coordinates
|
// Children are already positioned at scroll-adjusted coordinates
|
||||||
WidgetT *child = NULL;
|
WidgetT *child = NULL;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetScrollbar.c — Shared scrollbar painting and hit-testing
|
// widgetScrollbar.c -- Shared scrollbar painting and hit-testing
|
||||||
//
|
//
|
||||||
// These are not widgets themselves -- they are stateless rendering and
|
// These are not widgets themselves -- they are stateless rendering and
|
||||||
// hit-testing utilities shared by ScrollPane, TreeView, TextArea,
|
// hit-testing utilities shared by ScrollPane, TreeView, TextArea,
|
||||||
|
|
@ -47,14 +47,14 @@ void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
|
||||||
drawBevel(d, ops, sbX, sbY, sbW, WGT_SB_W, &troughBevel);
|
drawBevel(d, ops, sbX, sbY, sbW, WGT_SB_W, &troughBevel);
|
||||||
|
|
||||||
// Left arrow button
|
// Left arrow button
|
||||||
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
|
BevelStyleT btnBevel = BEVEL_SB_BUTTON(colors);
|
||||||
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
|
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
|
||||||
|
|
||||||
// Left arrow triangle
|
// Left arrow triangle
|
||||||
{
|
{
|
||||||
int32_t cx = sbX + WGT_SB_W / 2;
|
int32_t cx = sbX + WGT_SB_W / 2;
|
||||||
int32_t cy = sbY + WGT_SB_W / 2;
|
int32_t cy = sbY + WGT_SB_W / 2;
|
||||||
uint32_t fg = colors->contentFg;
|
uint32_t fg = colors->scrollbarFg;
|
||||||
|
|
||||||
for (int32_t i = 0; i < 4; i++) {
|
for (int32_t i = 0; i < 4; i++) {
|
||||||
drawVLine(d, ops, cx - 2 + i, cy - i, 1 + i * 2, fg);
|
drawVLine(d, ops, cx - 2 + i, cy - i, 1 + i * 2, fg);
|
||||||
|
|
@ -69,7 +69,7 @@ void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
|
||||||
{
|
{
|
||||||
int32_t cx = rightX + WGT_SB_W / 2;
|
int32_t cx = rightX + WGT_SB_W / 2;
|
||||||
int32_t cy = sbY + WGT_SB_W / 2;
|
int32_t cy = sbY + WGT_SB_W / 2;
|
||||||
uint32_t fg = colors->contentFg;
|
uint32_t fg = colors->scrollbarFg;
|
||||||
|
|
||||||
for (int32_t i = 0; i < 4; i++) {
|
for (int32_t i = 0; i < 4; i++) {
|
||||||
drawVLine(d, ops, cx + 2 - i, cy - i, 1 + i * 2, fg);
|
drawVLine(d, ops, cx + 2 - i, cy - i, 1 + i * 2, fg);
|
||||||
|
|
@ -103,14 +103,14 @@ void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
|
||||||
drawBevel(d, ops, sbX, sbY, WGT_SB_W, sbH, &troughBevel);
|
drawBevel(d, ops, sbX, sbY, WGT_SB_W, sbH, &troughBevel);
|
||||||
|
|
||||||
// Up arrow button
|
// Up arrow button
|
||||||
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
|
BevelStyleT btnBevel = BEVEL_SB_BUTTON(colors);
|
||||||
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
|
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
|
||||||
|
|
||||||
// Up arrow triangle
|
// Up arrow triangle
|
||||||
{
|
{
|
||||||
int32_t cx = sbX + WGT_SB_W / 2;
|
int32_t cx = sbX + WGT_SB_W / 2;
|
||||||
int32_t cy = sbY + WGT_SB_W / 2;
|
int32_t cy = sbY + WGT_SB_W / 2;
|
||||||
uint32_t fg = colors->contentFg;
|
uint32_t fg = colors->scrollbarFg;
|
||||||
|
|
||||||
for (int32_t i = 0; i < 4; i++) {
|
for (int32_t i = 0; i < 4; i++) {
|
||||||
drawHLine(d, ops, cx - i, cy - 2 + i, 1 + i * 2, fg);
|
drawHLine(d, ops, cx - i, cy - 2 + i, 1 + i * 2, fg);
|
||||||
|
|
@ -125,7 +125,7 @@ void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
|
||||||
{
|
{
|
||||||
int32_t cx = sbX + WGT_SB_W / 2;
|
int32_t cx = sbX + WGT_SB_W / 2;
|
||||||
int32_t cy = downY + WGT_SB_W / 2;
|
int32_t cy = downY + WGT_SB_W / 2;
|
||||||
uint32_t fg = colors->contentFg;
|
uint32_t fg = colors->scrollbarFg;
|
||||||
|
|
||||||
for (int32_t i = 0; i < 4; i++) {
|
for (int32_t i = 0; i < 4; i++) {
|
||||||
drawHLine(d, ops, cx - i, cy + 2 - i, 1 + i * 2, fg);
|
drawHLine(d, ops, cx - i, cy + 2 - i, 1 + i * 2, fg);
|
||||||
|
|
@ -405,9 +405,12 @@ void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int3
|
||||||
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||||
const BitmapFontT *font = &ctx->font;
|
const BitmapFontT *font = &ctx->font;
|
||||||
|
|
||||||
// Compute content min size by measuring children
|
// Compute content min size (must match spCalcNeeds in widgetScrollPane.c)
|
||||||
int32_t contentMinW = 0;
|
int32_t contentMinW = 0;
|
||||||
int32_t contentMinH = 0;
|
int32_t contentMinH = 0;
|
||||||
|
int32_t pad = wgtResolveSize(w->padding, 0, font->charWidth);
|
||||||
|
int32_t gap = wgtResolveSize(w->spacing, 0, font->charWidth);
|
||||||
|
int32_t count = 0;
|
||||||
|
|
||||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||||
if (c->visible) {
|
if (c->visible) {
|
||||||
|
|
@ -420,9 +423,17 @@ void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int3
|
||||||
}
|
}
|
||||||
|
|
||||||
contentMinH += c->calcMinH;
|
contentMinH += c->calcMinH;
|
||||||
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (count > 1) {
|
||||||
|
contentMinH += gap * (count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentMinW += pad * 2;
|
||||||
|
contentMinH += pad * 2;
|
||||||
|
|
||||||
int32_t innerH = w->h - SP_BORDER * 2;
|
int32_t innerH = w->h - SP_BORDER * 2;
|
||||||
int32_t innerW = w->w - SP_BORDER * 2;
|
int32_t innerW = w->w - SP_BORDER * 2;
|
||||||
bool needVSb = (contentMinH > innerH);
|
bool needVSb = (contentMinH > innerH);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetSeparator.c — Separator widget (horizontal and vertical)
|
// widgetSeparator.c -- Separator widget (horizontal and vertical)
|
||||||
//
|
//
|
||||||
// A purely decorative widget that draws a 2px etched line (shadow +
|
// A purely decorative widget that draws a 2px etched line (shadow +
|
||||||
// highlight pair) to visually divide groups of widgets. The etched
|
// highlight pair) to visually divide groups of widgets. The etched
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetSlider.c — Slider (trackbar) widget
|
// widgetSlider.c -- Slider (trackbar) widget
|
||||||
//
|
//
|
||||||
// A continuous-value selector with a draggable thumb on a groove track.
|
// A continuous-value selector with a draggable thumb on a groove track.
|
||||||
// Supports both horizontal and vertical orientation. The value maps
|
// Supports both horizontal and vertical orientation. The value maps
|
||||||
|
|
@ -127,9 +127,9 @@ void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (key == (0x4B | 0x100)) {
|
if (key == (0x4B | 0x100) || key == (0x48 | 0x100)) {
|
||||||
w->as.slider.value -= 1;
|
w->as.slider.value -= 1;
|
||||||
} else if (key == (0x4D | 0x100)) {
|
} else if (key == (0x4D | 0x100) || key == (0x50 | 0x100)) {
|
||||||
w->as.slider.value += 1;
|
w->as.slider.value += 1;
|
||||||
} else if (key == (0x49 | 0x100)) {
|
} else if (key == (0x49 | 0x100)) {
|
||||||
w->as.slider.value -= pageStep;
|
w->as.slider.value -= pageStep;
|
||||||
|
|
@ -186,11 +186,11 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
mousePos = vy - hit->y;
|
mousePos = vy - hit->y;
|
||||||
|
|
||||||
if (mousePos >= thumbPos && mousePos < thumbPos + SLIDER_THUMB_W) {
|
if (mousePos >= thumbPos && mousePos < thumbPos + SLIDER_THUMB_W) {
|
||||||
// Click on thumb — start drag
|
// Click on thumb -- start drag
|
||||||
sDragSlider = hit;
|
sDragSlider = hit;
|
||||||
sDragOffset = mousePos - thumbPos;
|
sDragOffset = mousePos - thumbPos;
|
||||||
} else {
|
} else {
|
||||||
// Click on track — jump to position
|
// Click on track -- jump to position
|
||||||
int32_t newVal = hit->as.slider.minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
|
int32_t newVal = hit->as.slider.minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
|
||||||
|
|
||||||
if (newVal < hit->as.slider.minValue) { newVal = hit->as.slider.minValue; }
|
if (newVal < hit->as.slider.minValue) { newVal = hit->as.slider.minValue; }
|
||||||
|
|
@ -208,11 +208,11 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
mousePos = vx - hit->x;
|
mousePos = vx - hit->x;
|
||||||
|
|
||||||
if (mousePos >= thumbPos && mousePos < thumbPos + SLIDER_THUMB_W) {
|
if (mousePos >= thumbPos && mousePos < thumbPos + SLIDER_THUMB_W) {
|
||||||
// Click on thumb — start drag
|
// Click on thumb -- start drag
|
||||||
sDragSlider = hit;
|
sDragSlider = hit;
|
||||||
sDragOffset = mousePos - thumbPos;
|
sDragOffset = mousePos - thumbPos;
|
||||||
} else {
|
} else {
|
||||||
// Click on track — jump to position
|
// Click on track -- jump to position
|
||||||
int32_t newVal = hit->as.slider.minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
|
int32_t newVal = hit->as.slider.minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
|
||||||
|
|
||||||
if (newVal < hit->as.slider.minValue) { newVal = hit->as.slider.minValue; }
|
if (newVal < hit->as.slider.minValue) { newVal = hit->as.slider.minValue; }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetSpacer.c — Spacer widget (invisible stretching element)
|
// widgetSpacer.c -- Spacer widget (invisible stretching element)
|
||||||
//
|
//
|
||||||
// A zero-sized invisible widget with weight=100, used purely for
|
// A zero-sized invisible widget with weight=100, used purely for
|
||||||
// layout control. It absorbs leftover space in box layouts, pushing
|
// layout control. It absorbs leftover space in box layouts, pushing
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetSpinner.c — Spinner (numeric up/down) widget
|
// widgetSpinner.c -- Spinner (numeric up/down) widget
|
||||||
//
|
//
|
||||||
// A hybrid widget combining a single-line text editor with up/down
|
// A hybrid widget combining a single-line text editor with up/down
|
||||||
// arrow buttons for numeric value entry. The user can either click
|
// arrow buttons for numeric value entry. The user can either click
|
||||||
|
|
@ -155,7 +155,7 @@ const char *widgetSpinnerGetText(const WidgetT *w) {
|
||||||
void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
int32_t step = w->as.spinner.step;
|
int32_t step = w->as.spinner.step;
|
||||||
|
|
||||||
// Up arrow — increment
|
// Up arrow -- increment
|
||||||
if (key == (0x48 | 0x100)) {
|
if (key == (0x48 | 0x100)) {
|
||||||
spinnerCommitEdit(w);
|
spinnerCommitEdit(w);
|
||||||
w->as.spinner.value += step;
|
w->as.spinner.value += step;
|
||||||
|
|
@ -169,7 +169,7 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Down arrow — decrement
|
// Down arrow -- decrement
|
||||||
if (key == (0x50 | 0x100)) {
|
if (key == (0x50 | 0x100)) {
|
||||||
spinnerCommitEdit(w);
|
spinnerCommitEdit(w);
|
||||||
w->as.spinner.value -= step;
|
w->as.spinner.value -= step;
|
||||||
|
|
@ -183,7 +183,7 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page Up — increment by step * 10
|
// Page Up -- increment by step * 10
|
||||||
if (key == (0x49 | 0x100)) {
|
if (key == (0x49 | 0x100)) {
|
||||||
spinnerCommitEdit(w);
|
spinnerCommitEdit(w);
|
||||||
w->as.spinner.value += step * 10;
|
w->as.spinner.value += step * 10;
|
||||||
|
|
@ -197,7 +197,7 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page Down — decrement by step * 10
|
// Page Down -- decrement by step * 10
|
||||||
if (key == (0x51 | 0x100)) {
|
if (key == (0x51 | 0x100)) {
|
||||||
spinnerCommitEdit(w);
|
spinnerCommitEdit(w);
|
||||||
w->as.spinner.value -= step * 10;
|
w->as.spinner.value -= step * 10;
|
||||||
|
|
@ -211,7 +211,7 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter — commit edit
|
// Enter -- commit edit
|
||||||
if (key == '\r' || key == '\n') {
|
if (key == '\r' || key == '\n') {
|
||||||
if (w->as.spinner.editing) {
|
if (w->as.spinner.editing) {
|
||||||
spinnerCommitEdit(w);
|
spinnerCommitEdit(w);
|
||||||
|
|
@ -226,7 +226,7 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape — cancel edit, revert to current value
|
// Escape -- cancel edit, revert to current value
|
||||||
if (key == 27) {
|
if (key == 27) {
|
||||||
if (w->as.spinner.editing) {
|
if (w->as.spinner.editing) {
|
||||||
w->as.spinner.editing = false;
|
w->as.spinner.editing = false;
|
||||||
|
|
@ -266,6 +266,32 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
w->as.spinner.undoBuf, &w->as.spinner.undoLen,
|
w->as.spinner.undoBuf, &w->as.spinner.undoLen,
|
||||||
&w->as.spinner.undoCursor);
|
&w->as.spinner.undoCursor);
|
||||||
|
|
||||||
|
// Validate buffer after paste -- reject non-numeric content.
|
||||||
|
// Allow optional leading minus and digits only.
|
||||||
|
bool valid = true;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < w->as.spinner.len; i++) {
|
||||||
|
char c = w->as.spinner.buf[i];
|
||||||
|
|
||||||
|
if (c == '-' && i == 0 && w->as.spinner.minValue < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c < '0' || c > '9') {
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
// Revert to the undo buffer (pre-paste state)
|
||||||
|
memcpy(w->as.spinner.buf, w->as.spinner.undoBuf, sizeof(w->as.spinner.buf));
|
||||||
|
w->as.spinner.len = w->as.spinner.undoLen;
|
||||||
|
w->as.spinner.cursorPos = w->as.spinner.undoCursor;
|
||||||
|
w->as.spinner.selStart = 0;
|
||||||
|
w->as.spinner.selEnd = 0;
|
||||||
|
}
|
||||||
|
|
||||||
wgtInvalidatePaint(w);
|
wgtInvalidatePaint(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetSplitter.c — Splitter (draggable divider between two panes)
|
// widgetSplitter.c -- Splitter (draggable divider between two panes)
|
||||||
//
|
//
|
||||||
// A container that divides its area between exactly two child widgets
|
// A container that divides its area between exactly two child widgets
|
||||||
// with a draggable divider bar between them. The divider position
|
// with a draggable divider bar between them. The divider position
|
||||||
|
|
@ -39,7 +39,7 @@ static WidgetT *spSecondChild(WidgetT *w);
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// spFirstChild — get first visible child
|
// spFirstChild -- get first visible child
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// These helpers skip invisible children so the splitter works
|
// These helpers skip invisible children so the splitter works
|
||||||
|
|
@ -58,7 +58,7 @@ static WidgetT *spFirstChild(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// spSecondChild — get second visible child
|
// spSecondChild -- get second visible child
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static WidgetT *spSecondChild(WidgetT *w) {
|
static WidgetT *spSecondChild(WidgetT *w) {
|
||||||
|
|
@ -79,7 +79,7 @@ static WidgetT *spSecondChild(WidgetT *w) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// widgetSplitterClampPos — clamp divider to child minimums
|
// widgetSplitterClampPos -- clamp divider to child minimums
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetSplitterClampPos(WidgetT *w, int32_t *pos) {
|
void widgetSplitterClampPos(WidgetT *w, int32_t *pos) {
|
||||||
|
|
@ -316,14 +316,14 @@ void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
setClipRect(d, oldClipX, oldClipY, oldClipW, oldClipH);
|
setClipRect(d, oldClipX, oldClipY, oldClipW, oldClipH);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw divider bar — raised bevel
|
// Draw divider bar -- raised bevel
|
||||||
BevelStyleT bevel = BEVEL_RAISED(colors, 1);
|
BevelStyleT bevel = BEVEL_RAISED(colors, 1);
|
||||||
|
|
||||||
if (w->as.splitter.vertical) {
|
if (w->as.splitter.vertical) {
|
||||||
int32_t barX = w->x + pos;
|
int32_t barX = w->x + pos;
|
||||||
drawBevel(d, ops, barX, w->y, SPLITTER_BAR_W, w->h, &bevel);
|
drawBevel(d, ops, barX, w->y, SPLITTER_BAR_W, w->h, &bevel);
|
||||||
|
|
||||||
// Gripper — row of embossed 2x2 bumps centered vertically
|
// Gripper -- row of embossed 2x2 bumps centered vertically
|
||||||
int32_t gx = barX + 1;
|
int32_t gx = barX + 1;
|
||||||
int32_t midY = w->y + w->h / 2;
|
int32_t midY = w->y + w->h / 2;
|
||||||
int32_t count = 5;
|
int32_t count = 5;
|
||||||
|
|
@ -337,7 +337,7 @@ void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
int32_t barY = w->y + pos;
|
int32_t barY = w->y + pos;
|
||||||
drawBevel(d, ops, w->x, barY, w->w, SPLITTER_BAR_W, &bevel);
|
drawBevel(d, ops, w->x, barY, w->w, SPLITTER_BAR_W, &bevel);
|
||||||
|
|
||||||
// Gripper — row of embossed 2x2 bumps centered horizontally
|
// Gripper -- row of embossed 2x2 bumps centered horizontally
|
||||||
int32_t gy = barY + 1;
|
int32_t gy = barY + 1;
|
||||||
int32_t midX = w->x + w->w / 2;
|
int32_t midX = w->x + w->w / 2;
|
||||||
int32_t count = 5;
|
int32_t count = 5;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetStatusBar.c — StatusBar widget
|
// widgetStatusBar.c -- StatusBar widget
|
||||||
//
|
//
|
||||||
// A horizontal container that draws a sunken border around each visible
|
// A horizontal container that draws a sunken border around each visible
|
||||||
// child to create the classic segmented status bar appearance. Children
|
// child to create the classic segmented status bar appearance. Children
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetTabControl.c — TabControl and TabPage widgets
|
// widgetTabControl.c -- TabControl and TabPage widgets
|
||||||
//
|
//
|
||||||
// Two-level architecture: TabControlE is the container holding
|
// Two-level architecture: TabControlE is the container holding
|
||||||
// selection state and rendering the tab header strip. TabPageE
|
// selection state and rendering the tab header strip. TabPageE
|
||||||
|
|
@ -46,7 +46,7 @@ static bool tabNeedScroll(const WidgetT *w, const BitmapFontT *font);
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// tabClosePopup — close any open dropdown/combobox popup
|
// tabClosePopup -- close any open dropdown/combobox popup
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void tabClosePopup(void) {
|
static void tabClosePopup(void) {
|
||||||
|
|
@ -63,7 +63,7 @@ static void tabClosePopup(void) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// tabEnsureVisible — scroll so active tab is visible
|
// tabEnsureVisible -- scroll so active tab is visible
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void tabEnsureVisible(WidgetT *w, const BitmapFontT *font) {
|
static void tabEnsureVisible(WidgetT *w, const BitmapFontT *font) {
|
||||||
|
|
@ -119,7 +119,7 @@ static void tabEnsureVisible(WidgetT *w, const BitmapFontT *font) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// tabHeaderTotalW — total width of all tab headers
|
// tabHeaderTotalW -- total width of all tab headers
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static int32_t tabHeaderTotalW(const WidgetT *w, const BitmapFontT *font) {
|
static int32_t tabHeaderTotalW(const WidgetT *w, const BitmapFontT *font) {
|
||||||
|
|
@ -136,7 +136,7 @@ static int32_t tabHeaderTotalW(const WidgetT *w, const BitmapFontT *font) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// tabNeedScroll — do tab headers overflow?
|
// tabNeedScroll -- do tab headers overflow?
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static bool tabNeedScroll(const WidgetT *w, const BitmapFontT *font) {
|
static bool tabNeedScroll(const WidgetT *w, const BitmapFontT *font) {
|
||||||
|
|
@ -469,7 +469,7 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab headers — clip to header area
|
// Tab headers -- clip to header area
|
||||||
int32_t headerLeft = w->x + 2 + (scroll ? TAB_ARROW_W : 0);
|
int32_t headerLeft = w->x + 2 + (scroll ? TAB_ARROW_W : 0);
|
||||||
int32_t headerRight = scroll ? (w->x + w->w - TAB_ARROW_W) : (w->x + w->w);
|
int32_t headerRight = scroll ? (w->x + w->w - TAB_ARROW_W) : (w->x + w->w);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
// widgetTextInput.c — TextInput and TextArea widgets
|
// widgetTextInput.c -- TextInput and TextArea widgets
|
||||||
//
|
//
|
||||||
// This file implements three text editing widgets plus shared
|
// This file implements three text editing widgets plus shared
|
||||||
// infrastructure:
|
// infrastructure:
|
||||||
//
|
//
|
||||||
// 1. TextInput — single-line text field with scroll, selection,
|
// 1. TextInput -- single-line text field with scroll, selection,
|
||||||
// undo, password masking, and masked input (e.g., phone/SSN).
|
// undo, password masking, and masked input (e.g., phone/SSN).
|
||||||
// 2. TextArea — multi-line text editor with row/col cursor,
|
// 2. TextArea -- multi-line text editor with row/col cursor,
|
||||||
// dual-axis scrolling, and full selection/clipboard support.
|
// dual-axis scrolling, and full selection/clipboard support.
|
||||||
// 3. Shared infrastructure — clipboard, multi-click detection,
|
// 3. Shared infrastructure -- clipboard, multi-click detection,
|
||||||
// word boundary logic, cross-widget selection clearing, and
|
// word boundary logic, cross-widget selection clearing, and
|
||||||
// the single-line text editing engine (widgetTextEditOnKey).
|
// the single-line text editing engine (widgetTextEditOnKey).
|
||||||
//
|
//
|
||||||
|
|
@ -760,7 +760,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
|
||||||
(void)shift;
|
(void)shift;
|
||||||
|
|
||||||
// Ctrl+A — select all
|
// Ctrl+A -- select all
|
||||||
if (key == 1) {
|
if (key == 1) {
|
||||||
w->as.textInput.selStart = 0;
|
w->as.textInput.selStart = 0;
|
||||||
w->as.textInput.selEnd = maskLen;
|
w->as.textInput.selEnd = maskLen;
|
||||||
|
|
@ -768,7 +768,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+C — copy formatted text
|
// Ctrl+C -- copy formatted text
|
||||||
if (key == 3) {
|
if (key == 3) {
|
||||||
if (w->as.textInput.selStart >= 0 && w->as.textInput.selEnd >= 0 && w->as.textInput.selStart != w->as.textInput.selEnd) {
|
if (w->as.textInput.selStart >= 0 && w->as.textInput.selEnd >= 0 && w->as.textInput.selStart != w->as.textInput.selEnd) {
|
||||||
int32_t selLo = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selStart : w->as.textInput.selEnd;
|
int32_t selLo = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selStart : w->as.textInput.selEnd;
|
||||||
|
|
@ -790,7 +790,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+V — paste valid chars into slots
|
// Ctrl+V -- paste valid chars into slots
|
||||||
if (key == 22) {
|
if (key == 22) {
|
||||||
int32_t clipLen;
|
int32_t clipLen;
|
||||||
const char *clip = clipboardGet(&clipLen);
|
const char *clip = clipboardGet(&clipLen);
|
||||||
|
|
@ -835,7 +835,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+X — copy and clear selected slots
|
// Ctrl+X -- copy and clear selected slots
|
||||||
if (key == 24) {
|
if (key == 24) {
|
||||||
if (w->as.textInput.selStart >= 0 && w->as.textInput.selEnd >= 0 && w->as.textInput.selStart != w->as.textInput.selEnd) {
|
if (w->as.textInput.selStart >= 0 && w->as.textInput.selEnd >= 0 && w->as.textInput.selStart != w->as.textInput.selEnd) {
|
||||||
int32_t selLo = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selStart : w->as.textInput.selEnd;
|
int32_t selLo = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selStart : w->as.textInput.selEnd;
|
||||||
|
|
@ -873,7 +873,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+Z — undo
|
// Ctrl+Z -- undo
|
||||||
if (key == 26 && w->as.textInput.undoBuf) {
|
if (key == 26 && w->as.textInput.undoBuf) {
|
||||||
char tmpBuf[256];
|
char tmpBuf[256];
|
||||||
int32_t tmpLen = maskLen + 1 < (int32_t)sizeof(tmpBuf) ? maskLen + 1 : (int32_t)sizeof(tmpBuf);
|
int32_t tmpLen = maskLen + 1 < (int32_t)sizeof(tmpBuf) ? maskLen + 1 : (int32_t)sizeof(tmpBuf);
|
||||||
|
|
@ -897,7 +897,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key >= 32 && key < 127) {
|
if (key >= 32 && key < 127) {
|
||||||
// Printable character — place at current slot if valid
|
// Printable character -- place at current slot if valid
|
||||||
if (*pCur < maskLen && maskIsSlot(mask[*pCur]) && maskCharValid(mask[*pCur], (char)key)) {
|
if (*pCur < maskLen && maskIsSlot(mask[*pCur]) && maskCharValid(mask[*pCur], (char)key)) {
|
||||||
if (w->as.textInput.undoBuf) {
|
if (w->as.textInput.undoBuf) {
|
||||||
textEditSaveUndo(buf, maskLen, *pCur, w->as.textInput.undoBuf, &w->as.textInput.undoLen, &w->as.textInput.undoCursor, w->as.textInput.bufSize);
|
textEditSaveUndo(buf, maskLen, *pCur, w->as.textInput.undoBuf, &w->as.textInput.undoLen, &w->as.textInput.undoCursor, w->as.textInput.bufSize);
|
||||||
|
|
@ -914,7 +914,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == 8) {
|
} else if (key == 8) {
|
||||||
// Backspace — clear previous slot
|
// Backspace -- clear previous slot
|
||||||
int32_t prev = maskPrevSlot(mask, *pCur);
|
int32_t prev = maskPrevSlot(mask, *pCur);
|
||||||
|
|
||||||
if (prev != *pCur) {
|
if (prev != *pCur) {
|
||||||
|
|
@ -933,7 +933,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == (0x53 | 0x100)) {
|
} else if (key == (0x53 | 0x100)) {
|
||||||
// Delete — clear current slot
|
// Delete -- clear current slot
|
||||||
if (*pCur < maskLen && maskIsSlot(mask[*pCur])) {
|
if (*pCur < maskLen && maskIsSlot(mask[*pCur])) {
|
||||||
if (w->as.textInput.undoBuf) {
|
if (w->as.textInput.undoBuf) {
|
||||||
textEditSaveUndo(buf, maskLen, *pCur, w->as.textInput.undoBuf, &w->as.textInput.undoLen, &w->as.textInput.undoCursor, w->as.textInput.bufSize);
|
textEditSaveUndo(buf, maskLen, *pCur, w->as.textInput.undoBuf, &w->as.textInput.undoLen, &w->as.textInput.undoCursor, w->as.textInput.bufSize);
|
||||||
|
|
@ -949,7 +949,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == (0x4B | 0x100)) {
|
} else if (key == (0x4B | 0x100)) {
|
||||||
// Left arrow — move to previous slot
|
// Left arrow -- move to previous slot
|
||||||
int32_t prev = maskPrevSlot(mask, *pCur);
|
int32_t prev = maskPrevSlot(mask, *pCur);
|
||||||
|
|
||||||
if (shift) {
|
if (shift) {
|
||||||
|
|
@ -966,7 +966,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
*pCur = prev;
|
*pCur = prev;
|
||||||
}
|
}
|
||||||
} else if (key == (0x4D | 0x100)) {
|
} else if (key == (0x4D | 0x100)) {
|
||||||
// Right arrow — move to next slot
|
// Right arrow -- move to next slot
|
||||||
int32_t next = maskNextSlot(mask, *pCur);
|
int32_t next = maskNextSlot(mask, *pCur);
|
||||||
|
|
||||||
if (shift) {
|
if (shift) {
|
||||||
|
|
@ -983,7 +983,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
*pCur = next;
|
*pCur = next;
|
||||||
}
|
}
|
||||||
} else if (key == (0x47 | 0x100)) {
|
} else if (key == (0x47 | 0x100)) {
|
||||||
// Home — first slot
|
// Home -- first slot
|
||||||
if (shift) {
|
if (shift) {
|
||||||
if (w->as.textInput.selStart < 0) {
|
if (w->as.textInput.selStart < 0) {
|
||||||
w->as.textInput.selStart = *pCur;
|
w->as.textInput.selStart = *pCur;
|
||||||
|
|
@ -998,7 +998,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
*pCur = maskFirstSlot(mask);
|
*pCur = maskFirstSlot(mask);
|
||||||
}
|
}
|
||||||
} else if (key == (0x4F | 0x100)) {
|
} else if (key == (0x4F | 0x100)) {
|
||||||
// End — past last slot
|
// End -- past last slot
|
||||||
int32_t last = maskLen;
|
int32_t last = maskLen;
|
||||||
|
|
||||||
if (shift) {
|
if (shift) {
|
||||||
|
|
@ -1299,7 +1299,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+A — select all
|
// Ctrl+A -- select all
|
||||||
if (key == 1) {
|
if (key == 1) {
|
||||||
*pSA = 0;
|
*pSA = 0;
|
||||||
*pSC = *pLen;
|
*pSC = *pLen;
|
||||||
|
|
@ -1310,7 +1310,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+C — copy
|
// Ctrl+C -- copy
|
||||||
if (key == 3) {
|
if (key == 3) {
|
||||||
if (HAS_SEL()) {
|
if (HAS_SEL()) {
|
||||||
clipboardCopy(buf + SEL_LO(), SEL_HI() - SEL_LO());
|
clipboardCopy(buf + SEL_LO(), SEL_HI() - SEL_LO());
|
||||||
|
|
@ -1324,7 +1324,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
goto navigation;
|
goto navigation;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+V — paste
|
// Ctrl+V -- paste
|
||||||
if (key == 22) {
|
if (key == 22) {
|
||||||
if (sClipboardLen > 0) {
|
if (sClipboardLen > 0) {
|
||||||
textEditSaveUndo(buf, *pLen, CUR_OFF(), w->as.textArea.undoBuf, &w->as.textArea.undoLen, &w->as.textArea.undoCursor, bufSize);
|
textEditSaveUndo(buf, *pLen, CUR_OFF(), w->as.textArea.undoBuf, &w->as.textArea.undoLen, &w->as.textArea.undoCursor, bufSize);
|
||||||
|
|
@ -1363,7 +1363,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+X — cut
|
// Ctrl+X -- cut
|
||||||
if (key == 24) {
|
if (key == 24) {
|
||||||
if (HAS_SEL()) {
|
if (HAS_SEL()) {
|
||||||
clipboardCopy(buf + SEL_LO(), SEL_HI() - SEL_LO());
|
clipboardCopy(buf + SEL_LO(), SEL_HI() - SEL_LO());
|
||||||
|
|
@ -1390,7 +1390,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+Z — undo
|
// Ctrl+Z -- undo
|
||||||
if (key == 26) {
|
if (key == 26) {
|
||||||
if (w->as.textArea.undoBuf && w->as.textArea.undoLen >= 0) {
|
if (w->as.textArea.undoBuf && w->as.textArea.undoLen >= 0) {
|
||||||
// Swap current and undo
|
// Swap current and undo
|
||||||
|
|
@ -1434,7 +1434,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter — insert newline
|
// Enter -- insert newline
|
||||||
if (key == 0x0D) {
|
if (key == 0x0D) {
|
||||||
if (*pLen < bufSize - 1) {
|
if (*pLen < bufSize - 1) {
|
||||||
textEditSaveUndo(buf, *pLen, CUR_OFF(), w->as.textArea.undoBuf, &w->as.textArea.undoLen, &w->as.textArea.undoCursor, bufSize);
|
textEditSaveUndo(buf, *pLen, CUR_OFF(), w->as.textArea.undoBuf, &w->as.textArea.undoLen, &w->as.textArea.undoCursor, bufSize);
|
||||||
|
|
@ -1581,7 +1581,7 @@ navigation:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+Left — word left
|
// Ctrl+Left -- word left
|
||||||
if (key == (0x73 | 0x100)) {
|
if (key == (0x73 | 0x100)) {
|
||||||
SEL_BEGIN();
|
SEL_BEGIN();
|
||||||
int32_t off = CUR_OFF();
|
int32_t off = CUR_OFF();
|
||||||
|
|
@ -1594,7 +1594,7 @@ navigation:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+Right — word right
|
// Ctrl+Right -- word right
|
||||||
if (key == (0x74 | 0x100)) {
|
if (key == (0x74 | 0x100)) {
|
||||||
SEL_BEGIN();
|
SEL_BEGIN();
|
||||||
int32_t off = CUR_OFF();
|
int32_t off = CUR_OFF();
|
||||||
|
|
@ -1905,7 +1905,7 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click on text area — place cursor
|
// Click on text area -- place cursor
|
||||||
int32_t relX = vx - innerX;
|
int32_t relX = vx - innerX;
|
||||||
int32_t relY = vy - innerY;
|
int32_t relY = vy - innerY;
|
||||||
|
|
||||||
|
|
@ -1978,7 +1978,7 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// widgetTextDragUpdate — update selection during mouse drag
|
// widgetTextDragUpdate -- update selection during mouse drag
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Called by the event loop on mouse-move while sDragTextSelect is set.
|
// Called by the event loop on mouse-move while sDragTextSelect is set.
|
||||||
|
|
@ -2164,7 +2164,7 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
selHi = w->as.textArea.selAnchor < w->as.textArea.selCursor ? w->as.textArea.selCursor : w->as.textArea.selAnchor;
|
selHi = w->as.textArea.selAnchor < w->as.textArea.selCursor ? w->as.textArea.selCursor : w->as.textArea.selAnchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw lines — compute first visible line offset once, then advance incrementally
|
// Draw lines -- compute first visible line offset once, then advance incrementally
|
||||||
int32_t textX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
|
int32_t textX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
|
||||||
int32_t textY = w->y + TEXTAREA_BORDER;
|
int32_t textY = w->y + TEXTAREA_BORDER;
|
||||||
int32_t lineOff = textAreaLineStart(buf, len, w->as.textArea.scrollRow);
|
int32_t lineOff = textAreaLineStart(buf, len, w->as.textArea.scrollRow);
|
||||||
|
|
@ -2244,7 +2244,7 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No selection on this line — single run
|
// No selection on this line -- single run
|
||||||
if (drawStart < drawEnd) {
|
if (drawStart < drawEnd) {
|
||||||
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, fg, bg, true);
|
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, fg, bg, true);
|
||||||
}
|
}
|
||||||
|
|
@ -2559,7 +2559,7 @@ void widgetTextInputSetText(WidgetT *w, const char *text) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// widgetTextEditOnKey — shared single-line text editing logic
|
// widgetTextEditOnKey -- shared single-line text editing logic
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// This is the core single-line text editing engine, parameterized by
|
// This is the core single-line text editing engine, parameterized by
|
||||||
|
|
@ -2603,7 +2603,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+A — select all
|
// Ctrl+A -- select all
|
||||||
if (key == 1 && pSelStart && pSelEnd) {
|
if (key == 1 && pSelStart && pSelEnd) {
|
||||||
*pSelStart = 0;
|
*pSelStart = 0;
|
||||||
*pSelEnd = *pLen;
|
*pSelEnd = *pLen;
|
||||||
|
|
@ -2611,7 +2611,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
|
||||||
goto adjustScroll;
|
goto adjustScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+C — copy
|
// Ctrl+C -- copy
|
||||||
if (key == 3) {
|
if (key == 3) {
|
||||||
if (hasSel) {
|
if (hasSel) {
|
||||||
clipboardCopy(buf + selLo, selHi - selLo);
|
clipboardCopy(buf + selLo, selHi - selLo);
|
||||||
|
|
@ -2620,7 +2620,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+V — paste
|
// Ctrl+V -- paste
|
||||||
if (key == 22) {
|
if (key == 22) {
|
||||||
if (sClipboardLen > 0) {
|
if (sClipboardLen > 0) {
|
||||||
if (undoBuf) {
|
if (undoBuf) {
|
||||||
|
|
@ -2666,7 +2666,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
|
||||||
goto adjustScroll;
|
goto adjustScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+X — cut
|
// Ctrl+X -- cut
|
||||||
if (key == 24) {
|
if (key == 24) {
|
||||||
if (hasSel) {
|
if (hasSel) {
|
||||||
clipboardCopy(buf + selLo, selHi - selLo);
|
clipboardCopy(buf + selLo, selHi - selLo);
|
||||||
|
|
@ -2685,7 +2685,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
|
||||||
goto adjustScroll;
|
goto adjustScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ctrl+Z — undo
|
// Ctrl+Z -- undo
|
||||||
if (key == 26 && undoBuf && pUndoLen && pUndoCursor) {
|
if (key == 26 && undoBuf && pUndoLen && pUndoCursor) {
|
||||||
// Swap current and undo
|
// Swap current and undo
|
||||||
char tmpBuf[CLIPBOARD_MAX];
|
char tmpBuf[CLIPBOARD_MAX];
|
||||||
|
|
@ -2827,7 +2827,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == (0x73 | 0x100)) {
|
} else if (key == (0x73 | 0x100)) {
|
||||||
// Ctrl+Left — word left
|
// Ctrl+Left -- word left
|
||||||
int32_t newPos = wordBoundaryLeft(buf, *pCursor);
|
int32_t newPos = wordBoundaryLeft(buf, *pCursor);
|
||||||
|
|
||||||
if (shift && pSelStart && pSelEnd) {
|
if (shift && pSelStart && pSelEnd) {
|
||||||
|
|
@ -2850,7 +2850,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
|
||||||
*pCursor = newPos;
|
*pCursor = newPos;
|
||||||
}
|
}
|
||||||
} else if (key == (0x74 | 0x100)) {
|
} else if (key == (0x74 | 0x100)) {
|
||||||
// Ctrl+Right — word right
|
// Ctrl+Right -- word right
|
||||||
int32_t newPos = wordBoundaryRight(buf, *pLen, *pCursor);
|
int32_t newPos = wordBoundaryRight(buf, *pLen, *pCursor);
|
||||||
|
|
||||||
if (shift && pSelStart && pSelEnd) {
|
if (shift && pSelStart && pSelEnd) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// widgetTimer.c — Invisible timer widget
|
// widgetTimer.c -- Invisible timer widget
|
||||||
//
|
//
|
||||||
// Fires onChange callbacks at a configurable interval. Supports both
|
// Fires onChange callbacks at a configurable interval. Supports both
|
||||||
// one-shot and repeating modes. The timer is invisible — it takes no
|
// one-shot and repeating modes. The timer is invisible -- it takes no
|
||||||
// space in layout and produces no visual output.
|
// space in layout and produces no visual output.
|
||||||
//
|
//
|
||||||
// Active timers are tracked via a module-level linked list (using the
|
// Active timers are tracked via a module-level linked list (using the
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
#include "thirdparty/stb_ds.h"
|
#include "thirdparty/stb_ds.h"
|
||||||
|
|
||||||
|
|
||||||
// Active timer list — avoids walking the widget tree per frame
|
// Active timer list -- avoids walking the widget tree per frame
|
||||||
static WidgetT **sActiveTimers = NULL;
|
static WidgetT **sActiveTimers = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetToolbar.c — Toolbar widget
|
// widgetToolbar.c -- Toolbar widget
|
||||||
//
|
//
|
||||||
// A horizontal container with a raised 1px bevel background, used to
|
// A horizontal container with a raised 1px bevel background, used to
|
||||||
// hold buttons, separators, spacers, and other control widgets in a
|
// hold buttons, separators, spacers, and other control widgets in a
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// widgetTreeView.c — TreeView and TreeItem widgets
|
// widgetTreeView.c -- TreeView and TreeItem widgets
|
||||||
//
|
//
|
||||||
// A hierarchical list with expand/collapse nodes, scrolling, multi-select,
|
// A hierarchical list with expand/collapse nodes, scrolling, multi-select,
|
||||||
// and drag-reorder support.
|
// and drag-reorder support.
|
||||||
|
|
@ -419,7 +419,7 @@ static WidgetT *prevVisibleItem(WidgetT *item, WidgetT *treeView) {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No previous sibling — go to parent (if it's a tree item)
|
// No previous sibling -- go to parent (if it's a tree item)
|
||||||
if (item->parent && item->parent != treeView && item->parent->type == WidgetTreeItemE) {
|
if (item->parent && item->parent != treeView && item->parent->type == WidgetTreeItemE) {
|
||||||
return item->parent;
|
return item->parent;
|
||||||
}
|
}
|
||||||
|
|
@ -496,7 +496,7 @@ static void treeCalcScrollbarNeeds(WidgetT *w, const BitmapFontT *font, int32_t
|
||||||
bool needVSb = (totalH > innerH);
|
bool needVSb = (totalH > innerH);
|
||||||
bool needHSb = (totalW > innerW);
|
bool needHSb = (totalW > innerW);
|
||||||
|
|
||||||
// V scrollbar reduces available width — may trigger H scrollbar
|
// V scrollbar reduces available width -- may trigger H scrollbar
|
||||||
if (needVSb) {
|
if (needVSb) {
|
||||||
innerW -= WGT_SB_W;
|
innerW -= WGT_SB_W;
|
||||||
|
|
||||||
|
|
@ -505,7 +505,7 @@ static void treeCalcScrollbarNeeds(WidgetT *w, const BitmapFontT *font, int32_t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// H scrollbar reduces available height — may trigger V scrollbar
|
// H scrollbar reduces available height -- may trigger V scrollbar
|
||||||
if (needHSb) {
|
if (needHSb) {
|
||||||
innerH -= WGT_SB_W;
|
innerH -= WGT_SB_W;
|
||||||
|
|
||||||
|
|
@ -662,11 +662,48 @@ void wgtTreeItemSetExpanded(WidgetT *w, bool expanded) {
|
||||||
// wgtTreeView
|
// wgtTreeView
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
// Default double-click handler: toggle expand/collapse on the selected
|
||||||
|
// item if it has children, otherwise fire the item's onDblClick/onClick.
|
||||||
|
static void treeDefaultDblClick(WidgetT *w) {
|
||||||
|
WidgetT *sel = w->as.treeView.selectedItem;
|
||||||
|
|
||||||
|
if (!sel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasChildren = false;
|
||||||
|
|
||||||
|
for (WidgetT *c = sel->firstChild; c; c = c->nextSibling) {
|
||||||
|
if (c->type == WidgetTreeItemE) {
|
||||||
|
hasChildren = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChildren) {
|
||||||
|
sel->as.treeItem.expanded = !sel->as.treeItem.expanded;
|
||||||
|
|
||||||
|
if (sel->onChange) {
|
||||||
|
sel->onChange(sel);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
} else {
|
||||||
|
if (sel->onDblClick) {
|
||||||
|
sel->onDblClick(sel);
|
||||||
|
} else if (sel->onClick) {
|
||||||
|
sel->onClick(sel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
WidgetT *wgtTreeView(WidgetT *parent) {
|
WidgetT *wgtTreeView(WidgetT *parent) {
|
||||||
WidgetT *w = widgetAlloc(parent, WidgetTreeViewE);
|
WidgetT *w = widgetAlloc(parent, WidgetTreeViewE);
|
||||||
|
|
||||||
if (w) {
|
if (w) {
|
||||||
w->weight = 100;
|
w->weight = 100;
|
||||||
|
w->onDblClick = treeDefaultDblClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
return w;
|
return w;
|
||||||
|
|
@ -802,7 +839,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
WidgetT *sel = w->as.treeView.selectedItem;
|
WidgetT *sel = w->as.treeView.selectedItem;
|
||||||
|
|
||||||
if (key == (0x50 | 0x100)) {
|
if (key == (0x50 | 0x100)) {
|
||||||
// Down arrow — next visible item
|
// Down arrow -- next visible item
|
||||||
if (!sel) {
|
if (!sel) {
|
||||||
w->as.treeView.selectedItem = firstVisibleItem(w);
|
w->as.treeView.selectedItem = firstVisibleItem(w);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -813,7 +850,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == (0x48 | 0x100)) {
|
} else if (key == (0x48 | 0x100)) {
|
||||||
// Up arrow — previous visible item
|
// Up arrow -- previous visible item
|
||||||
if (!sel) {
|
if (!sel) {
|
||||||
w->as.treeView.selectedItem = firstVisibleItem(w);
|
w->as.treeView.selectedItem = firstVisibleItem(w);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -824,7 +861,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == (0x4D | 0x100)) {
|
} else if (key == (0x4D | 0x100)) {
|
||||||
// Right arrow — expand if collapsed, else move to first child
|
// Right arrow -- expand if collapsed, else move to first child
|
||||||
if (sel) {
|
if (sel) {
|
||||||
bool hasChildren = false;
|
bool hasChildren = false;
|
||||||
|
|
||||||
|
|
@ -852,7 +889,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == (0x4B | 0x100)) {
|
} else if (key == (0x4B | 0x100)) {
|
||||||
// Left arrow — collapse if expanded, else move to parent
|
// Left arrow -- collapse if expanded, else move to parent
|
||||||
if (sel) {
|
if (sel) {
|
||||||
if (sel->as.treeItem.expanded) {
|
if (sel->as.treeItem.expanded) {
|
||||||
sel->as.treeItem.expanded = false;
|
sel->as.treeItem.expanded = false;
|
||||||
|
|
@ -865,7 +902,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == 0x0D) {
|
} else if (key == 0x0D) {
|
||||||
// Enter — toggle expand/collapse for parents, onClick for leaves
|
// Enter -- toggle expand/collapse for parents, onClick for leaves
|
||||||
if (sel) {
|
if (sel) {
|
||||||
bool hasChildren = false;
|
bool hasChildren = false;
|
||||||
|
|
||||||
|
|
@ -891,7 +928,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (key == ' ' && multi) {
|
} else if (key == ' ' && multi) {
|
||||||
// Space — toggle selection of current item in multi-select
|
// Space -- toggle selection of current item in multi-select
|
||||||
if (sel) {
|
if (sel) {
|
||||||
sel->as.treeItem.selected = !sel->as.treeItem.selected;
|
sel->as.treeItem.selected = !sel->as.treeItem.selected;
|
||||||
w->as.treeView.anchorItem = sel;
|
w->as.treeView.anchorItem = sel;
|
||||||
|
|
@ -1112,7 +1149,7 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click in dead corner (both scrollbars present) — ignore
|
// Click in dead corner (both scrollbars present) -- ignore
|
||||||
if (needVSb && needHSb) {
|
if (needVSb && needHSb) {
|
||||||
int32_t cornerX = hit->x + hit->w - TREE_BORDER - WGT_SB_W;
|
int32_t cornerX = hit->x + hit->w - TREE_BORDER - WGT_SB_W;
|
||||||
int32_t cornerY = hit->y + hit->h - TREE_BORDER - WGT_SB_W;
|
int32_t cornerY = hit->y + hit->h - TREE_BORDER - WGT_SB_W;
|
||||||
|
|
@ -1122,7 +1159,7 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tree item click — adjust for scroll offsets
|
// Tree item click -- adjust for scroll offsets
|
||||||
int32_t curY = hit->y + TREE_BORDER - hit->as.treeView.scrollPos;
|
int32_t curY = hit->y + TREE_BORDER - hit->as.treeView.scrollPos;
|
||||||
|
|
||||||
WidgetT *item = treeItemAtY(hit, vy, &curY, font);
|
WidgetT *item = treeItemAtY(hit, vy, &curY, font);
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,10 @@ Requires `lib/libdvx.a` and `lib/libtasks.a` to be built first.
|
||||||
|
|
||||||
Each iteration of the main loop:
|
Each iteration of the main loop:
|
||||||
|
|
||||||
1. `dvxUpdate()` — process input events, dispatch callbacks, composite dirty rects
|
1. `dvxUpdate()` -- process input events, dispatch callbacks, composite dirty rects
|
||||||
2. `tsYield()` — give CPU time to main-loop app tasks
|
2. `tsYield()` -- give CPU time to main-loop app tasks
|
||||||
3. `shellReapApps()` — clean up apps that terminated this frame
|
3. `shellReapApps()` -- clean up apps that terminated this frame
|
||||||
4. `desktopUpdate()` — notify the desktop app if anything changed
|
4. `desktopUpdate()` -- notify the desktop app if anything changed
|
||||||
|
|
||||||
An idle callback (`idleYield`) yields to app tasks during quiet periods when
|
An idle callback (`idleYield`) yields to app tasks during quiet periods when
|
||||||
there are no events or dirty rects to process.
|
there are no events or dirty rects to process.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// shellApp.c — DVX Shell application loading, lifecycle, and reaping
|
// shellApp.c -- DVX Shell application loading, lifecycle, and reaping
|
||||||
//
|
//
|
||||||
// Manages DXE app loading via dlopen/dlsym, resource tracking through
|
// Manages DXE app loading via dlopen/dlsym, resource tracking through
|
||||||
// sCurrentAppId, and clean teardown of both callback-only and main-loop apps.
|
// sCurrentAppId, and clean teardown of both callback-only and main-loop apps.
|
||||||
|
|
@ -8,9 +8,11 @@
|
||||||
#include "platform/dvxPlatform.h"
|
#include "platform/dvxPlatform.h"
|
||||||
|
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <strings.h>
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Module state
|
// Module state
|
||||||
|
|
@ -32,7 +34,7 @@ static int32_t allocSlot(void);
|
||||||
static void appTaskWrapper(void *arg);
|
static void appTaskWrapper(void *arg);
|
||||||
static const char *baseName(const char *path);
|
static const char *baseName(const char *path);
|
||||||
static void cleanupTempFile(ShellAppT *app);
|
static void cleanupTempFile(ShellAppT *app);
|
||||||
static int32_t copyFile(AppContextT *ctx, const char *src, const char *dst);
|
static int32_t copyFile(const char *src, const char *dst);
|
||||||
static ShellAppT *findLoadedPath(const char *path);
|
static ShellAppT *findLoadedPath(const char *path);
|
||||||
static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t outSize);
|
static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t outSize);
|
||||||
void shellAppInit(void);
|
void shellAppInit(void);
|
||||||
|
|
@ -69,7 +71,7 @@ static int32_t allocSlot(void) {
|
||||||
// state is set to Terminating so the shell's main loop will reap it.
|
// state is set to Terminating so the shell's main loop will reap it.
|
||||||
//
|
//
|
||||||
// If the app crashes (signal), the signal handler longjmps to main and
|
// If the app crashes (signal), the signal handler longjmps to main and
|
||||||
// this function never completes — the task is killed externally via
|
// this function never completes -- the task is killed externally via
|
||||||
// shellForceKillApp + tsKill.
|
// shellForceKillApp + tsKill.
|
||||||
static void appTaskWrapper(void *arg) {
|
static void appTaskWrapper(void *arg) {
|
||||||
ShellAppT *app = (ShellAppT *)arg;
|
ShellAppT *app = (ShellAppT *)arg;
|
||||||
|
|
@ -78,7 +80,7 @@ static void appTaskWrapper(void *arg) {
|
||||||
app->entryFn(&app->dxeCtx);
|
app->entryFn(&app->dxeCtx);
|
||||||
sCurrentAppId = 0;
|
sCurrentAppId = 0;
|
||||||
|
|
||||||
// App returned from its main loop — mark for reaping
|
// App returned from its main loop -- mark for reaping
|
||||||
app->state = AppStateTerminatingE;
|
app->state = AppStateTerminatingE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,16 +106,18 @@ static void cleanupTempFile(ShellAppT *app) {
|
||||||
|
|
||||||
|
|
||||||
// Binary file copy. Returns 0 on success, -1 on failure.
|
// Binary file copy. Returns 0 on success, -1 on failure.
|
||||||
static int32_t copyFile(AppContextT *ctx, const char *src, const char *dst) {
|
static int32_t copyFile(const char *src, const char *dst) {
|
||||||
FILE *in = fopen(src, "rb");
|
FILE *in = fopen(src, "rb");
|
||||||
|
|
||||||
if (!in) {
|
if (!in) {
|
||||||
|
shellLog("copyFile: failed to open source '%s' (errno=%d)", src, errno);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *out = fopen(dst, "wb");
|
FILE *out = fopen(dst, "wb");
|
||||||
|
|
||||||
if (!out) {
|
if (!out) {
|
||||||
|
shellLog("copyFile: failed to create dest '%s' (errno=%d)", dst, errno);
|
||||||
fclose(in);
|
fclose(in);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
@ -123,14 +127,12 @@ static int32_t copyFile(AppContextT *ctx, const char *src, const char *dst) {
|
||||||
|
|
||||||
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
|
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
|
||||||
if (fwrite(buf, 1, n, out) != n) {
|
if (fwrite(buf, 1, n, out) != n) {
|
||||||
|
shellLog("copyFile: write failed '%s' -> '%s' (errno=%d)", src, dst, errno);
|
||||||
fclose(in);
|
fclose(in);
|
||||||
fclose(out);
|
fclose(out);
|
||||||
remove(dst);
|
remove(dst);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yield so the UI stays responsive during large copies
|
|
||||||
dvxUpdate(ctx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(in);
|
fclose(in);
|
||||||
|
|
@ -196,7 +198,25 @@ void shellAppInit(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Forcible kill — no shutdown hook is called. Used for crashed apps
|
// ============================================================
|
||||||
|
// shellConfigPath
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void shellConfigPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize) {
|
||||||
|
snprintf(outPath, outSize, "%s/%s", ctx->configDir, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// shellEnsureConfigDir
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
int32_t shellEnsureConfigDir(const DxeAppContextT *ctx) {
|
||||||
|
return platformMkdirRecursive(ctx->configDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Forcible kill -- no shutdown hook is called. Used for crashed apps
|
||||||
// (where running more app code would be unsafe) and for "End Task".
|
// (where running more app code would be unsafe) and for "End Task".
|
||||||
// Cleanup order matters: windows first (removes them from the compositor),
|
// Cleanup order matters: windows first (removes them from the compositor),
|
||||||
// then the task (frees the stack), then the DXE handle (unmaps the code).
|
// then the task (frees the stack), then the DXE handle (unmaps the code).
|
||||||
|
|
@ -248,14 +268,14 @@ ShellAppT *shellGetApp(int32_t appId) {
|
||||||
|
|
||||||
|
|
||||||
// Load a DXE app: dlopen the module, resolve symbols, set up context, launch.
|
// Load a DXE app: dlopen the module, resolve symbols, set up context, launch.
|
||||||
// DXE3 is DJGPP's dynamic linking system — similar to dlopen/dlsym on Unix.
|
// DXE3 is DJGPP's dynamic linking system -- similar to dlopen/dlsym on Unix.
|
||||||
// Each .app file is a DXE3 shared object that exports _appDescriptor and
|
// Each .app file is a DXE3 shared object that exports _appDescriptor and
|
||||||
// _appMain (and optionally _appShutdown). The leading underscore is the
|
// _appMain (and optionally _appShutdown). The leading underscore is the
|
||||||
// COFF symbol convention; DJGPP's dlsym expects it.
|
// COFF symbol convention; DJGPP's dlsym expects it.
|
||||||
//
|
//
|
||||||
// Multi-instance support: DXE3's dlopen returns the same handle for the
|
// Multi-instance support: DXE3's dlopen returns the same handle for the
|
||||||
// same path (reference-counted), so two loads of the same .dxe share all
|
// same path (reference-counted), so two loads of the same .dxe share all
|
||||||
// global/static state — only one main loop runs and closing one kills both.
|
// global/static state -- only one main loop runs and closing one kills both.
|
||||||
// If the app's descriptor sets multiInstance=true, we copy the .dxe to a
|
// If the app's descriptor sets multiInstance=true, we copy the .dxe to a
|
||||||
// unique temp file before dlopen, giving each instance its own code+data.
|
// unique temp file before dlopen, giving each instance its own code+data.
|
||||||
// If multiInstance=false (the default), a second load of the same path is
|
// If multiInstance=false (the default), a second load of the same path is
|
||||||
|
|
@ -271,7 +291,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
|
|
||||||
// Check if this DXE is already loaded. If so, check whether the app
|
// Check if this DXE is already loaded. If so, check whether the app
|
||||||
// allows multiple instances. We read the descriptor from the existing
|
// allows multiple instances. We read the descriptor from the existing
|
||||||
// slot directly — no need to dlopen again.
|
// slot directly -- no need to dlopen again.
|
||||||
const char *loadPath = path;
|
const char *loadPath = path;
|
||||||
char tempPath[260] = {0};
|
char tempPath[260] = {0};
|
||||||
ShellAppT *existing = findLoadedPath(path);
|
ShellAppT *existing = findLoadedPath(path);
|
||||||
|
|
@ -291,7 +311,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
// an independent code+data image.
|
// an independent code+data image.
|
||||||
makeTempPath(path, id, tempPath, sizeof(tempPath));
|
makeTempPath(path, id, tempPath, sizeof(tempPath));
|
||||||
|
|
||||||
if (copyFile(ctx, path, tempPath) != 0) {
|
if (copyFile(path, tempPath) != 0) {
|
||||||
char msg[320];
|
char msg[320];
|
||||||
snprintf(msg, sizeof(msg), "Failed to create instance copy of %s.", baseName(path));
|
snprintf(msg, sizeof(msg), "Failed to create instance copy of %s.", baseName(path));
|
||||||
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
||||||
|
|
@ -381,6 +401,25 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
app->dxeCtx.appDir[1] = '\0';
|
app->dxeCtx.appDir[1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Derive config directory: replace the APPS/ prefix with CONFIG/.
|
||||||
|
// e.g. "APPS/GAMES/TETRIS" -> "CONFIG/GAMES/TETRIS"
|
||||||
|
// If the path doesn't start with "apps/" or "APPS/", fall back to
|
||||||
|
// "CONFIG/<appname>" using the descriptor name.
|
||||||
|
const char *appDirStr = app->dxeCtx.appDir;
|
||||||
|
const char *appsPrefix = NULL;
|
||||||
|
|
||||||
|
if (strncasecmp(appDirStr, "apps/", 5) == 0 || strncasecmp(appDirStr, "apps\\", 5) == 0) {
|
||||||
|
appsPrefix = appDirStr + 5;
|
||||||
|
} else if (strncasecmp(appDirStr, "APPS/", 5) == 0 || strncasecmp(appDirStr, "APPS\\", 5) == 0) {
|
||||||
|
appsPrefix = appDirStr + 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appsPrefix && appsPrefix[0]) {
|
||||||
|
snprintf(app->dxeCtx.configDir, sizeof(app->dxeCtx.configDir), "CONFIG/%s", appsPrefix);
|
||||||
|
} else {
|
||||||
|
snprintf(app->dxeCtx.configDir, sizeof(app->dxeCtx.configDir), "CONFIG/%s", desc->name);
|
||||||
|
}
|
||||||
|
|
||||||
// Launch. Set sCurrentAppId before any app code runs so that window
|
// Launch. Set sCurrentAppId before any app code runs so that window
|
||||||
// creation wrappers stamp the correct owner. Reset to 0 afterward so
|
// creation wrappers stamp the correct owner. Reset to 0 afterward so
|
||||||
// shell-initiated operations (e.g., message boxes) aren't misattributed.
|
// shell-initiated operations (e.g., message boxes) aren't misattributed.
|
||||||
|
|
@ -418,7 +457,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Graceful reap — called from shellReapApps when an app has reached
|
// Graceful reap -- called from shellReapApps when an app has reached
|
||||||
// the Terminating state. Unlike forceKill, this calls the app's
|
// the Terminating state. Unlike forceKill, this calls the app's
|
||||||
// shutdown hook (if provided) giving it a chance to save state, close
|
// shutdown hook (if provided) giving it a chance to save state, close
|
||||||
// files, etc. The sCurrentAppId is set during the shutdown call so
|
// files, etc. The sCurrentAppId is set during the shutdown call so
|
||||||
|
|
@ -464,7 +503,7 @@ void shellReapApp(AppContextT *ctx, ShellAppT *app) {
|
||||||
// Called every frame from the shell's main loop. Scans for apps that have
|
// Called every frame from the shell's main loop. Scans for apps that have
|
||||||
// entered the Terminating state (either their task returned or their last
|
// entered the Terminating state (either their task returned or their last
|
||||||
// window was closed) and cleans them up. The deferred-reap design avoids
|
// window was closed) and cleans them up. The deferred-reap design avoids
|
||||||
// destroying resources in the middle of a callback chain — the app marks
|
// destroying resources in the middle of a callback chain -- the app marks
|
||||||
// itself for termination, and cleanup happens at a safe top-level point.
|
// itself for termination, and cleanup happens at a safe top-level point.
|
||||||
bool shellReapApps(AppContextT *ctx) {
|
bool shellReapApps(AppContextT *ctx) {
|
||||||
bool reaped = false;
|
bool reaped = false;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// shellApp.h — DVX Shell application lifecycle types and API
|
// shellApp.h -- DVX Shell application lifecycle types and API
|
||||||
//
|
//
|
||||||
// The shell supports two kinds of DXE apps:
|
// The shell supports two kinds of DXE apps:
|
||||||
//
|
//
|
||||||
|
|
@ -61,12 +61,13 @@ typedef struct {
|
||||||
// the shell's GUI context (for creating windows, drawing, etc.) and
|
// the shell's GUI context (for creating windows, drawing, etc.) and
|
||||||
// its own identity. appDir is derived from the .app file path at load
|
// its own identity. appDir is derived from the .app file path at load
|
||||||
// time so the app can find its own resources (icons, data files) via
|
// time so the app can find its own resources (icons, data files) via
|
||||||
// relative paths — important because the working directory is shared
|
// relative paths -- important because the working directory is shared
|
||||||
// by all apps and can't be changed per-app in DOS.
|
// by all apps and can't be changed per-app in DOS.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
AppContextT *shellCtx; // the shell's GUI context
|
AppContextT *shellCtx; // the shell's GUI context
|
||||||
int32_t appId; // this app's ID
|
int32_t appId; // this app's ID
|
||||||
char appDir[260]; // directory containing the .app file
|
char appDir[260]; // directory containing the .app file
|
||||||
|
char configDir[260]; // writable config directory (CONFIG/<apppath>/)
|
||||||
} DxeAppContextT;
|
} DxeAppContextT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -76,7 +77,7 @@ typedef struct {
|
||||||
// State machine: Free -> Loaded -> Running -> Terminating -> Free
|
// State machine: Free -> Loaded -> Running -> Terminating -> Free
|
||||||
// LoadedE is transient (only during shellLoadApp before entry is called).
|
// LoadedE is transient (only during shellLoadApp before entry is called).
|
||||||
// TerminatingE means the app's task has exited but cleanup (window
|
// TerminatingE means the app's task has exited but cleanup (window
|
||||||
// destruction, DXE unload) hasn't happened yet — the shell's main loop
|
// destruction, DXE unload) hasn't happened yet -- the shell's main loop
|
||||||
// reaps these each frame via shellReapApps().
|
// reaps these each frame via shellReapApps().
|
||||||
typedef enum {
|
typedef enum {
|
||||||
AppStateFreeE, // slot available
|
AppStateFreeE, // slot available
|
||||||
|
|
@ -132,11 +133,11 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path);
|
||||||
// when appMain returns (via appTaskWrapper marking AppStateTerminatingE).
|
// when appMain returns (via appTaskWrapper marking AppStateTerminatingE).
|
||||||
bool shellReapApps(AppContextT *ctx);
|
bool shellReapApps(AppContextT *ctx);
|
||||||
|
|
||||||
// Gracefully shut down a single app — calls shutdownFn if present,
|
// Gracefully shut down a single app -- calls shutdownFn if present,
|
||||||
// destroys windows, kills task, closes DXE handle.
|
// destroys windows, kills task, closes DXE handle.
|
||||||
void shellReapApp(AppContextT *ctx, ShellAppT *app);
|
void shellReapApp(AppContextT *ctx, ShellAppT *app);
|
||||||
|
|
||||||
// Forcibly kill an app (Task Manager "End Task"). Skips shutdownFn —
|
// Forcibly kill an app (Task Manager "End Task"). Skips shutdownFn --
|
||||||
// used when the app is hung or has crashed and cannot be trusted to
|
// used when the app is hung or has crashed and cannot be trusted to
|
||||||
// run its own cleanup code.
|
// run its own cleanup code.
|
||||||
void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
|
void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
|
||||||
|
|
@ -157,6 +158,15 @@ int32_t shellRunningAppCount(void);
|
||||||
// Write a printf-style message to DVX.LOG
|
// Write a printf-style message to DVX.LOG
|
||||||
void shellLog(const char *fmt, ...);
|
void shellLog(const char *fmt, ...);
|
||||||
|
|
||||||
|
// Ensure an app's config directory exists (creates all parent dirs).
|
||||||
|
// Returns 0 on success, -1 on failure.
|
||||||
|
int32_t shellEnsureConfigDir(const DxeAppContextT *ctx);
|
||||||
|
|
||||||
|
// Build a full path to a file in the app's config directory.
|
||||||
|
// e.g. shellConfigPath(ctx, "settings.ini", buf, sizeof(buf))
|
||||||
|
// -> "CONFIG/PROGMAN/settings.ini"
|
||||||
|
void shellConfigPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// DXE export table
|
// DXE export table
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// shellExport.c — DXE export table and wrapper functions for DVX Shell
|
// shellExport.c -- DXE export table and wrapper functions for DVX Shell
|
||||||
//
|
//
|
||||||
// Exports all dvx*/wgt*/ts* symbols that DXE apps need. A few functions
|
// Exports all dvx*/wgt*/ts* symbols that DXE apps need. A few functions
|
||||||
// are wrapped for resource tracking (window ownership via appId).
|
// are wrapped for resource tracking (window ownership via appId).
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
// 1. Wrapped functions: dvxCreateWindow, dvxCreateWindowCentered,
|
// 1. Wrapped functions: dvxCreateWindow, dvxCreateWindowCentered,
|
||||||
// dvxDestroyWindow. These are intercepted to stamp win->appId for
|
// dvxDestroyWindow. These are intercepted to stamp win->appId for
|
||||||
// resource ownership tracking. The DXE sees them under their original
|
// resource ownership tracking. The DXE sees them under their original
|
||||||
// names — the app code calls dvxCreateWindow() normally and gets our
|
// names -- the app code calls dvxCreateWindow() normally and gets our
|
||||||
// wrapper transparently.
|
// wrapper transparently.
|
||||||
//
|
//
|
||||||
// 2. Direct exports: all other dvx/wgt/wm/ts functions. These are safe
|
// 2. Direct exports: all other dvx/wgt/wm/ts functions. These are safe
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
// libc, but DJGPP's DXE3 loader requires explicit re-export of any
|
// libc, but DJGPP's DXE3 loader requires explicit re-export of any
|
||||||
// libc symbols the module references. Without these entries, the DXE
|
// libc symbols the module references. Without these entries, the DXE
|
||||||
// would fail to load with unresolved symbol errors. This is a DXE3
|
// would fail to load with unresolved symbol errors. This is a DXE3
|
||||||
// design limitation — there's no automatic fallback to the host's libc.
|
// design limitation -- there's no automatic fallback to the host's libc.
|
||||||
|
|
||||||
#include "shellApp.h"
|
#include "shellApp.h"
|
||||||
#include "shellInfo.h"
|
#include "shellInfo.h"
|
||||||
|
|
@ -55,12 +55,12 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
// stb headers (no IMPLEMENTATION — symbols are in libdvx.a / libtasks.a)
|
// stb headers (no IMPLEMENTATION -- symbols are in libdvx.a / libtasks.a)
|
||||||
#include "thirdparty/stb_image.h"
|
#include "thirdparty/stb_image.h"
|
||||||
#include "thirdparty/stb_image_write.h"
|
#include "thirdparty/stb_image_write.h"
|
||||||
#include "thirdparty/stb_ds.h"
|
#include "thirdparty/stb_ds.h"
|
||||||
|
|
||||||
// DJGPP stdio internals — the stdin/stdout/stderr macros dereference
|
// DJGPP stdio internals -- the stdin/stdout/stderr macros dereference
|
||||||
// these pointers. Without exporting them, any DXE that uses printf
|
// these pointers. Without exporting them, any DXE that uses printf
|
||||||
// or fprintf gets an unresolved symbol.
|
// or fprintf gets an unresolved symbol.
|
||||||
extern FILE __dj_stdin;
|
extern FILE __dj_stdin;
|
||||||
|
|
@ -85,13 +85,13 @@ static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *titl
|
||||||
static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win);
|
static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Wrapper: dvxCreateWindow — stamps win->appId
|
// Wrapper: dvxCreateWindow -- stamps win->appId
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// The wrapper calls the real dvxCreateWindow, then tags the result with
|
// The wrapper calls the real dvxCreateWindow, then tags the result with
|
||||||
// sCurrentAppId. This is how the shell knows which app owns which window,
|
// sCurrentAppId. This is how the shell knows which app owns which window,
|
||||||
// enabling per-app window cleanup on crash/termination. The app never
|
// enabling per-app window cleanup on crash/termination. The app never
|
||||||
// sees the difference — the wrapper has the same signature and is
|
// sees the difference -- the wrapper has the same signature and is
|
||||||
// exported under the same name as the original function.
|
// exported under the same name as the original function.
|
||||||
static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable) {
|
static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable) {
|
||||||
WindowT *win = dvxCreateWindow(ctx, title, x, y, w, h, resizable);
|
WindowT *win = dvxCreateWindow(ctx, title, x, y, w, h, resizable);
|
||||||
|
|
@ -105,7 +105,7 @@ static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Wrapper: dvxCreateWindowCentered — stamps win->appId
|
// Wrapper: dvxCreateWindowCentered -- stamps win->appId
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable) {
|
static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable) {
|
||||||
|
|
@ -120,7 +120,7 @@ static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *titl
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Wrapper: dvxDestroyWindow — checks for last-window reap
|
// Wrapper: dvxDestroyWindow -- checks for last-window reap
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Beyond just destroying the window, this wrapper implements the lifecycle
|
// Beyond just destroying the window, this wrapper implements the lifecycle
|
||||||
|
|
@ -162,7 +162,7 @@ static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// DXE_EXPORT_TABLE generates a DXE symbol table array. DXE_EXPORT(fn)
|
// DXE_EXPORT_TABLE generates a DXE symbol table array. DXE_EXPORT(fn)
|
||||||
// expands to { "_fn", (void *)fn } — the underscore prefix matches COFF
|
// expands to { "_fn", (void *)fn } -- the underscore prefix matches COFF
|
||||||
// symbol naming. For wrapped functions we use raw entries with explicit
|
// symbol naming. For wrapped functions we use raw entries with explicit
|
||||||
// names so the DXE sees "_dvxCreateWindow" but gets our wrapper's address.
|
// names so the DXE sees "_dvxCreateWindow" but gets our wrapper's address.
|
||||||
|
|
||||||
|
|
@ -172,10 +172,11 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
{ "_dvxCreateWindow", (void *)shellWrapCreateWindow },
|
{ "_dvxCreateWindow", (void *)shellWrapCreateWindow },
|
||||||
{ "_dvxDestroyWindow", (void *)shellWrapDestroyWindow },
|
{ "_dvxDestroyWindow", (void *)shellWrapDestroyWindow },
|
||||||
|
|
||||||
// dvxPlatform.h — platform abstraction
|
// dvxPlatform.h -- platform abstraction
|
||||||
DXE_EXPORT(platformLineEnding)
|
DXE_EXPORT(platformLineEnding)
|
||||||
DXE_EXPORT(platformChdir)
|
DXE_EXPORT(platformChdir)
|
||||||
DXE_EXPORT(platformGetMemoryInfo)
|
DXE_EXPORT(platformGetMemoryInfo)
|
||||||
|
DXE_EXPORT(platformMkdirRecursive)
|
||||||
DXE_EXPORT(platformMouseSetAccel)
|
DXE_EXPORT(platformMouseSetAccel)
|
||||||
DXE_EXPORT(platformMouseWarp)
|
DXE_EXPORT(platformMouseWarp)
|
||||||
DXE_EXPORT(platformPathDirEnd)
|
DXE_EXPORT(platformPathDirEnd)
|
||||||
|
|
@ -183,7 +184,7 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(platformValidateFilename)
|
DXE_EXPORT(platformValidateFilename)
|
||||||
DXE_EXPORT(platformVideoEnumModes)
|
DXE_EXPORT(platformVideoEnumModes)
|
||||||
|
|
||||||
// dvxPrefs.h — preferences
|
// dvxPrefs.h -- preferences
|
||||||
DXE_EXPORT(prefsGetBool)
|
DXE_EXPORT(prefsGetBool)
|
||||||
DXE_EXPORT(prefsGetInt)
|
DXE_EXPORT(prefsGetInt)
|
||||||
DXE_EXPORT(prefsGetString)
|
DXE_EXPORT(prefsGetString)
|
||||||
|
|
@ -195,9 +196,10 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(prefsSetInt)
|
DXE_EXPORT(prefsSetInt)
|
||||||
DXE_EXPORT(prefsSetString)
|
DXE_EXPORT(prefsSetString)
|
||||||
|
|
||||||
// dvxApp.h — direct exports
|
// dvxApp.h -- direct exports
|
||||||
DXE_EXPORT(dvxApplyColorScheme)
|
DXE_EXPORT(dvxApplyColorScheme)
|
||||||
DXE_EXPORT(dvxChangeVideoMode)
|
DXE_EXPORT(dvxChangeVideoMode)
|
||||||
|
DXE_EXPORT(dvxColorLabel)
|
||||||
DXE_EXPORT(dvxColorName)
|
DXE_EXPORT(dvxColorName)
|
||||||
DXE_EXPORT(dvxGetColor)
|
DXE_EXPORT(dvxGetColor)
|
||||||
DXE_EXPORT(dvxInit)
|
DXE_EXPORT(dvxInit)
|
||||||
|
|
@ -207,6 +209,7 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(dvxSetColor)
|
DXE_EXPORT(dvxSetColor)
|
||||||
DXE_EXPORT(dvxSetMouseConfig)
|
DXE_EXPORT(dvxSetMouseConfig)
|
||||||
DXE_EXPORT(dvxSetWallpaper)
|
DXE_EXPORT(dvxSetWallpaper)
|
||||||
|
DXE_EXPORT(dvxSetWallpaperMode)
|
||||||
DXE_EXPORT(dvxShutdown)
|
DXE_EXPORT(dvxShutdown)
|
||||||
DXE_EXPORT(dvxUpdate)
|
DXE_EXPORT(dvxUpdate)
|
||||||
{ "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered },
|
{ "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered },
|
||||||
|
|
@ -280,36 +283,37 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(wmUpdateContentRect)
|
DXE_EXPORT(wmUpdateContentRect)
|
||||||
DXE_EXPORT(wmReallocContentBuf)
|
DXE_EXPORT(wmReallocContentBuf)
|
||||||
|
|
||||||
// dvxWidget.h — window integration
|
// dvxWidget.h -- window integration
|
||||||
DXE_EXPORT(wgtInitWindow)
|
DXE_EXPORT(wgtInitWindow)
|
||||||
|
|
||||||
// dvxWidget.h — containers
|
// dvxWidget.h -- containers
|
||||||
DXE_EXPORT(wgtVBox)
|
DXE_EXPORT(wgtVBox)
|
||||||
DXE_EXPORT(wgtHBox)
|
DXE_EXPORT(wgtHBox)
|
||||||
DXE_EXPORT(wgtFrame)
|
DXE_EXPORT(wgtFrame)
|
||||||
|
|
||||||
// dvxWidget.h — basic widgets
|
// dvxWidget.h -- basic widgets
|
||||||
DXE_EXPORT(wgtLabel)
|
DXE_EXPORT(wgtLabel)
|
||||||
|
DXE_EXPORT(wgtLabelSetAlign)
|
||||||
DXE_EXPORT(wgtButton)
|
DXE_EXPORT(wgtButton)
|
||||||
DXE_EXPORT(wgtCheckbox)
|
DXE_EXPORT(wgtCheckbox)
|
||||||
DXE_EXPORT(wgtTextInput)
|
DXE_EXPORT(wgtTextInput)
|
||||||
DXE_EXPORT(wgtPasswordInput)
|
DXE_EXPORT(wgtPasswordInput)
|
||||||
DXE_EXPORT(wgtMaskedInput)
|
DXE_EXPORT(wgtMaskedInput)
|
||||||
|
|
||||||
// dvxWidget.h — radio buttons
|
// dvxWidget.h -- radio buttons
|
||||||
DXE_EXPORT(wgtRadioGroup)
|
DXE_EXPORT(wgtRadioGroup)
|
||||||
DXE_EXPORT(wgtRadio)
|
DXE_EXPORT(wgtRadio)
|
||||||
|
|
||||||
// dvxWidget.h — spacing
|
// dvxWidget.h -- spacing
|
||||||
DXE_EXPORT(wgtSpacer)
|
DXE_EXPORT(wgtSpacer)
|
||||||
DXE_EXPORT(wgtHSeparator)
|
DXE_EXPORT(wgtHSeparator)
|
||||||
DXE_EXPORT(wgtVSeparator)
|
DXE_EXPORT(wgtVSeparator)
|
||||||
|
|
||||||
// dvxWidget.h — complex widgets
|
// dvxWidget.h -- complex widgets
|
||||||
DXE_EXPORT(wgtListBox)
|
DXE_EXPORT(wgtListBox)
|
||||||
DXE_EXPORT(wgtTextArea)
|
DXE_EXPORT(wgtTextArea)
|
||||||
|
|
||||||
// dvxWidget.h — dropdown/combo
|
// dvxWidget.h -- dropdown/combo
|
||||||
DXE_EXPORT(wgtDropdown)
|
DXE_EXPORT(wgtDropdown)
|
||||||
DXE_EXPORT(wgtDropdownSetItems)
|
DXE_EXPORT(wgtDropdownSetItems)
|
||||||
DXE_EXPORT(wgtDropdownGetSelected)
|
DXE_EXPORT(wgtDropdownGetSelected)
|
||||||
|
|
@ -319,35 +323,35 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(wgtComboBoxGetSelected)
|
DXE_EXPORT(wgtComboBoxGetSelected)
|
||||||
DXE_EXPORT(wgtComboBoxSetSelected)
|
DXE_EXPORT(wgtComboBoxSetSelected)
|
||||||
|
|
||||||
// dvxWidget.h — progress bar
|
// dvxWidget.h -- progress bar
|
||||||
DXE_EXPORT(wgtProgressBar)
|
DXE_EXPORT(wgtProgressBar)
|
||||||
DXE_EXPORT(wgtProgressBarV)
|
DXE_EXPORT(wgtProgressBarV)
|
||||||
DXE_EXPORT(wgtProgressBarSetValue)
|
DXE_EXPORT(wgtProgressBarSetValue)
|
||||||
DXE_EXPORT(wgtProgressBarGetValue)
|
DXE_EXPORT(wgtProgressBarGetValue)
|
||||||
|
|
||||||
// dvxWidget.h — slider
|
// dvxWidget.h -- slider
|
||||||
DXE_EXPORT(wgtSlider)
|
DXE_EXPORT(wgtSlider)
|
||||||
DXE_EXPORT(wgtSliderSetValue)
|
DXE_EXPORT(wgtSliderSetValue)
|
||||||
DXE_EXPORT(wgtSliderGetValue)
|
DXE_EXPORT(wgtSliderGetValue)
|
||||||
|
|
||||||
// dvxWidget.h — spinner
|
// dvxWidget.h -- spinner
|
||||||
DXE_EXPORT(wgtSpinner)
|
DXE_EXPORT(wgtSpinner)
|
||||||
DXE_EXPORT(wgtSpinnerSetValue)
|
DXE_EXPORT(wgtSpinnerSetValue)
|
||||||
DXE_EXPORT(wgtSpinnerGetValue)
|
DXE_EXPORT(wgtSpinnerGetValue)
|
||||||
DXE_EXPORT(wgtSpinnerSetRange)
|
DXE_EXPORT(wgtSpinnerSetRange)
|
||||||
DXE_EXPORT(wgtSpinnerSetStep)
|
DXE_EXPORT(wgtSpinnerSetStep)
|
||||||
|
|
||||||
// dvxWidget.h — tab control
|
// dvxWidget.h -- tab control
|
||||||
DXE_EXPORT(wgtTabControl)
|
DXE_EXPORT(wgtTabControl)
|
||||||
DXE_EXPORT(wgtTabPage)
|
DXE_EXPORT(wgtTabPage)
|
||||||
DXE_EXPORT(wgtTabControlSetActive)
|
DXE_EXPORT(wgtTabControlSetActive)
|
||||||
DXE_EXPORT(wgtTabControlGetActive)
|
DXE_EXPORT(wgtTabControlGetActive)
|
||||||
|
|
||||||
// dvxWidget.h — status bar / toolbar
|
// dvxWidget.h -- status bar / toolbar
|
||||||
DXE_EXPORT(wgtStatusBar)
|
DXE_EXPORT(wgtStatusBar)
|
||||||
DXE_EXPORT(wgtToolbar)
|
DXE_EXPORT(wgtToolbar)
|
||||||
|
|
||||||
// dvxWidget.h — tree view
|
// dvxWidget.h -- tree view
|
||||||
DXE_EXPORT(wgtTreeView)
|
DXE_EXPORT(wgtTreeView)
|
||||||
DXE_EXPORT(wgtTreeViewGetSelected)
|
DXE_EXPORT(wgtTreeViewGetSelected)
|
||||||
DXE_EXPORT(wgtTreeViewSetSelected)
|
DXE_EXPORT(wgtTreeViewSetSelected)
|
||||||
|
|
@ -359,14 +363,14 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(wgtTreeItemIsSelected)
|
DXE_EXPORT(wgtTreeItemIsSelected)
|
||||||
DXE_EXPORT(wgtTreeItemSetSelected)
|
DXE_EXPORT(wgtTreeItemSetSelected)
|
||||||
|
|
||||||
// dvxWidget.h — timer
|
// dvxWidget.h -- timer
|
||||||
DXE_EXPORT(wgtTimer)
|
DXE_EXPORT(wgtTimer)
|
||||||
DXE_EXPORT(wgtTimerIsRunning)
|
DXE_EXPORT(wgtTimerIsRunning)
|
||||||
DXE_EXPORT(wgtTimerSetInterval)
|
DXE_EXPORT(wgtTimerSetInterval)
|
||||||
DXE_EXPORT(wgtTimerStart)
|
DXE_EXPORT(wgtTimerStart)
|
||||||
DXE_EXPORT(wgtTimerStop)
|
DXE_EXPORT(wgtTimerStop)
|
||||||
|
|
||||||
// dvxWidget.h — list view
|
// dvxWidget.h -- list view
|
||||||
DXE_EXPORT(wgtListView)
|
DXE_EXPORT(wgtListView)
|
||||||
DXE_EXPORT(wgtListViewSetColumns)
|
DXE_EXPORT(wgtListViewSetColumns)
|
||||||
DXE_EXPORT(wgtListViewSetData)
|
DXE_EXPORT(wgtListViewSetData)
|
||||||
|
|
@ -381,13 +385,13 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(wgtListViewClearSelection)
|
DXE_EXPORT(wgtListViewClearSelection)
|
||||||
DXE_EXPORT(wgtListViewSetReorderable)
|
DXE_EXPORT(wgtListViewSetReorderable)
|
||||||
|
|
||||||
// dvxWidget.h — scroll pane / splitter
|
// dvxWidget.h -- scroll pane / splitter
|
||||||
DXE_EXPORT(wgtScrollPane)
|
DXE_EXPORT(wgtScrollPane)
|
||||||
DXE_EXPORT(wgtSplitter)
|
DXE_EXPORT(wgtSplitter)
|
||||||
DXE_EXPORT(wgtSplitterSetPos)
|
DXE_EXPORT(wgtSplitterSetPos)
|
||||||
DXE_EXPORT(wgtSplitterGetPos)
|
DXE_EXPORT(wgtSplitterGetPos)
|
||||||
|
|
||||||
// dvxWidget.h — image / image button
|
// dvxWidget.h -- image / image button
|
||||||
DXE_EXPORT(wgtImageButton)
|
DXE_EXPORT(wgtImageButton)
|
||||||
DXE_EXPORT(wgtImageButtonFromFile)
|
DXE_EXPORT(wgtImageButtonFromFile)
|
||||||
DXE_EXPORT(wgtImageButtonSetData)
|
DXE_EXPORT(wgtImageButtonSetData)
|
||||||
|
|
@ -395,7 +399,7 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(wgtImageFromFile)
|
DXE_EXPORT(wgtImageFromFile)
|
||||||
DXE_EXPORT(wgtImageSetData)
|
DXE_EXPORT(wgtImageSetData)
|
||||||
|
|
||||||
// dvxWidget.h — canvas
|
// dvxWidget.h -- canvas
|
||||||
DXE_EXPORT(wgtCanvas)
|
DXE_EXPORT(wgtCanvas)
|
||||||
DXE_EXPORT(wgtCanvasClear)
|
DXE_EXPORT(wgtCanvasClear)
|
||||||
DXE_EXPORT(wgtCanvasSetMouseCallback)
|
DXE_EXPORT(wgtCanvasSetMouseCallback)
|
||||||
|
|
@ -410,7 +414,7 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(wgtCanvasSetPixel)
|
DXE_EXPORT(wgtCanvasSetPixel)
|
||||||
DXE_EXPORT(wgtCanvasGetPixel)
|
DXE_EXPORT(wgtCanvasGetPixel)
|
||||||
|
|
||||||
// dvxWidget.h — ANSI terminal
|
// dvxWidget.h -- ANSI terminal
|
||||||
DXE_EXPORT(wgtAnsiTerm)
|
DXE_EXPORT(wgtAnsiTerm)
|
||||||
DXE_EXPORT(wgtAnsiTermWrite)
|
DXE_EXPORT(wgtAnsiTermWrite)
|
||||||
DXE_EXPORT(wgtAnsiTermClear)
|
DXE_EXPORT(wgtAnsiTermClear)
|
||||||
|
|
@ -419,7 +423,7 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(wgtAnsiTermPoll)
|
DXE_EXPORT(wgtAnsiTermPoll)
|
||||||
DXE_EXPORT(wgtAnsiTermRepaint)
|
DXE_EXPORT(wgtAnsiTermRepaint)
|
||||||
|
|
||||||
// dvxWidget.h — operations
|
// dvxWidget.h -- operations
|
||||||
DXE_EXPORT(wgtInvalidate)
|
DXE_EXPORT(wgtInvalidate)
|
||||||
DXE_EXPORT(wgtInvalidatePaint)
|
DXE_EXPORT(wgtInvalidatePaint)
|
||||||
DXE_EXPORT(wgtSetDebugLayout)
|
DXE_EXPORT(wgtSetDebugLayout)
|
||||||
|
|
@ -436,7 +440,7 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(wgtFind)
|
DXE_EXPORT(wgtFind)
|
||||||
DXE_EXPORT(wgtDestroy)
|
DXE_EXPORT(wgtDestroy)
|
||||||
|
|
||||||
// dvxWidget.h — list box ops
|
// dvxWidget.h -- list box ops
|
||||||
DXE_EXPORT(wgtListBoxSetItems)
|
DXE_EXPORT(wgtListBoxSetItems)
|
||||||
DXE_EXPORT(wgtListBoxGetSelected)
|
DXE_EXPORT(wgtListBoxGetSelected)
|
||||||
DXE_EXPORT(wgtListBoxSetSelected)
|
DXE_EXPORT(wgtListBoxSetSelected)
|
||||||
|
|
@ -447,14 +451,14 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(wgtListBoxClearSelection)
|
DXE_EXPORT(wgtListBoxClearSelection)
|
||||||
DXE_EXPORT(wgtListBoxSetReorderable)
|
DXE_EXPORT(wgtListBoxSetReorderable)
|
||||||
|
|
||||||
// dvxWidget.h — layout
|
// dvxWidget.h -- layout
|
||||||
DXE_EXPORT(wgtResolveSize)
|
DXE_EXPORT(wgtResolveSize)
|
||||||
DXE_EXPORT(wgtLayout)
|
DXE_EXPORT(wgtLayout)
|
||||||
DXE_EXPORT(wgtPaint)
|
DXE_EXPORT(wgtPaint)
|
||||||
|
|
||||||
// taskswitch.h — only yield and query functions are exported.
|
// taskswitch.h -- only yield and query functions are exported.
|
||||||
// tsCreate/tsKill/etc. are NOT exported because apps should not
|
// tsCreate/tsKill/etc. are NOT exported because apps should not
|
||||||
// manipulate the task system directly — the shell manages task
|
// manipulate the task system directly -- the shell manages task
|
||||||
// lifecycle through shellLoadApp/shellForceKillApp.
|
// lifecycle through shellLoadApp/shellForceKillApp.
|
||||||
DXE_EXPORT(tsActiveCount)
|
DXE_EXPORT(tsActiveCount)
|
||||||
DXE_EXPORT(tsCreate)
|
DXE_EXPORT(tsCreate)
|
||||||
|
|
@ -468,15 +472,17 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(tsSetPriority)
|
DXE_EXPORT(tsSetPriority)
|
||||||
DXE_EXPORT(tsYield)
|
DXE_EXPORT(tsYield)
|
||||||
|
|
||||||
// dvxWm.h — direct window management
|
// dvxWm.h -- direct window management
|
||||||
DXE_EXPORT(wmRaiseWindow)
|
DXE_EXPORT(wmRaiseWindow)
|
||||||
DXE_EXPORT(wmSetFocus)
|
DXE_EXPORT(wmSetFocus)
|
||||||
DXE_EXPORT(wmRestoreMinimized)
|
DXE_EXPORT(wmRestoreMinimized)
|
||||||
|
|
||||||
// Shell API
|
// Shell API
|
||||||
DXE_EXPORT(shellLog)
|
DXE_EXPORT(shellConfigPath)
|
||||||
DXE_EXPORT(shellLoadApp)
|
DXE_EXPORT(shellEnsureConfigDir)
|
||||||
DXE_EXPORT(shellGetApp)
|
DXE_EXPORT(shellGetApp)
|
||||||
|
DXE_EXPORT(shellLoadApp)
|
||||||
|
DXE_EXPORT(shellLog)
|
||||||
DXE_EXPORT(shellTaskMgrOpen)
|
DXE_EXPORT(shellTaskMgrOpen)
|
||||||
DXE_EXPORT(shellForceKillApp)
|
DXE_EXPORT(shellForceKillApp)
|
||||||
DXE_EXPORT(shellRunningAppCount)
|
DXE_EXPORT(shellRunningAppCount)
|
||||||
|
|
@ -711,7 +717,7 @@ DXE_EXPORT_END
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// dlregsym registers our export table with DJGPP's DXE3 runtime.
|
// dlregsym registers our export table with DJGPP's DXE3 runtime.
|
||||||
// Must be called once before any dlopen — subsequent dlopen calls
|
// Must be called once before any dlopen -- subsequent dlopen calls
|
||||||
// will search this table to resolve DXE symbol references.
|
// will search this table to resolve DXE symbol references.
|
||||||
static void shellRegisterExports(void) {
|
static void shellRegisterExports(void) {
|
||||||
dlregsym(shellExportTable);
|
dlregsym(shellExportTable);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// shellInfo.c — System information wrapper for DVX Shell
|
// shellInfo.c -- System information wrapper for DVX Shell
|
||||||
//
|
//
|
||||||
// Delegates hardware detection to the platform layer via
|
// Delegates hardware detection to the platform layer via
|
||||||
// platformGetSystemInfo(), then logs the result line-by-line
|
// platformGetSystemInfo(), then logs the result line-by-line
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
static const char *sCachedInfo = NULL;
|
static const char *sCachedInfo = NULL;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// shellGetSystemInfo — return the cached info text
|
// shellGetSystemInfo -- return the cached info text
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
const char *shellGetSystemInfo(void) {
|
const char *shellGetSystemInfo(void) {
|
||||||
|
|
@ -27,7 +27,7 @@ const char *shellGetSystemInfo(void) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// shellInfoInit — gather info and log it
|
// shellInfoInit -- gather info and log it
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void shellInfoInit(AppContextT *ctx) {
|
void shellInfoInit(AppContextT *ctx) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// shellInfo.h — System information display for DVX Shell
|
// shellInfo.h -- System information display for DVX Shell
|
||||||
//
|
//
|
||||||
// Thin wrapper around platformGetSystemInfo(). Calls the platform
|
// Thin wrapper around platformGetSystemInfo(). Calls the platform
|
||||||
// layer to gather hardware info, logs each line to DVX.LOG, and
|
// layer to gather hardware info, logs each line to DVX.LOG, and
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// shellMain.c — DVX Shell entry point and main loop
|
// shellMain.c -- DVX Shell entry point and main loop
|
||||||
//
|
//
|
||||||
// Initializes the GUI, task system, DXE export table, and loads
|
// Initializes the GUI, task system, DXE export table, and loads
|
||||||
// the desktop app. Runs the cooperative main loop, yielding to
|
// the desktop app. Runs the cooperative main loop, yielding to
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
// point in main(). This works because longjmp restores the main task's
|
// point in main(). This works because longjmp restores the main task's
|
||||||
// stack frame regardless of which task was running. tsRecoverToMain()
|
// stack frame regardless of which task was running. tsRecoverToMain()
|
||||||
// then fixes the scheduler's bookkeeping, and the crashed app is killed.
|
// then fixes the scheduler's bookkeeping, and the crashed app is killed.
|
||||||
// This gives the shell Windows 3.1-style fault tolerance — one bad app
|
// This gives the shell Windows 3.1-style fault tolerance -- one bad app
|
||||||
// doesn't take down the whole system.
|
// doesn't take down the whole system.
|
||||||
|
|
||||||
#include "shellApp.h"
|
#include "shellApp.h"
|
||||||
|
|
@ -68,7 +68,7 @@ static void logCrash(int sig);
|
||||||
static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData);
|
static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// crashHandler — catch page faults and other fatal signals
|
// crashHandler -- catch page faults and other fatal signals
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Signal handler for fatal exceptions. DJGPP uses System V signal
|
// Signal handler for fatal exceptions. DJGPP uses System V signal
|
||||||
|
|
@ -79,7 +79,7 @@ static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData);
|
||||||
// we're on (potentially a crashed app's task stack) and restores the
|
// we're on (potentially a crashed app's task stack) and restores the
|
||||||
// main task's stack frame to the setjmp point in main(). This is safe
|
// main task's stack frame to the setjmp point in main(). This is safe
|
||||||
// because cooperative switching means the main task's stack is always
|
// because cooperative switching means the main task's stack is always
|
||||||
// intact — it was cleanly suspended at a yield point. The crashed
|
// intact -- it was cleanly suspended at a yield point. The crashed
|
||||||
// task's stack is abandoned (and later freed by tsKill).
|
// task's stack is abandoned (and later freed by tsKill).
|
||||||
static void crashHandler(int sig) {
|
static void crashHandler(int sig) {
|
||||||
logCrash(sig);
|
logCrash(sig);
|
||||||
|
|
@ -93,7 +93,7 @@ static void crashHandler(int sig) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// shellDesktopUpdate — notify desktop app of state change
|
// shellDesktopUpdate -- notify desktop app of state change
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void shellDesktopUpdate(void) {
|
void shellDesktopUpdate(void) {
|
||||||
|
|
@ -104,7 +104,7 @@ void shellDesktopUpdate(void) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ctrlEscHandler — system-wide Ctrl+Esc callback
|
// ctrlEscHandler -- system-wide Ctrl+Esc callback
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void ctrlEscHandler(void *ctx) {
|
static void ctrlEscHandler(void *ctx) {
|
||||||
|
|
@ -113,7 +113,7 @@ static void ctrlEscHandler(void *ctx) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// titleChangeHandler — refresh listeners when a window title changes
|
// titleChangeHandler -- refresh listeners when a window title changes
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void titleChangeHandler(void *ctx) {
|
static void titleChangeHandler(void *ctx) {
|
||||||
|
|
@ -123,12 +123,12 @@ static void titleChangeHandler(void *ctx) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// idleYield — called when no dirty rects need compositing
|
// idleYield -- called when no dirty rects need compositing
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Registered as sCtx.idleCallback. dvxUpdate calls this when it has
|
// Registered as sCtx.idleCallback. dvxUpdate calls this when it has
|
||||||
// processed all pending events and there are no dirty rects to composite.
|
// processed all pending events and there are no dirty rects to composite.
|
||||||
// Instead of busy-spinning, we yield to app tasks — this is where most
|
// Instead of busy-spinning, we yield to app tasks -- this is where most
|
||||||
// of the CPU time for main-loop apps comes from when the UI is idle.
|
// of the CPU time for main-loop apps comes from when the UI is idle.
|
||||||
// The tsActiveCount > 1 check avoids the overhead of a tsYield call
|
// The tsActiveCount > 1 check avoids the overhead of a tsYield call
|
||||||
// (which would do a scheduler scan) when the shell is the only task.
|
// (which would do a scheduler scan) when the shell is the only task.
|
||||||
|
|
@ -153,12 +153,12 @@ static void installCrashHandler(void) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// logCrash — write exception details to the log
|
// logCrash -- write exception details to the log
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// Dump as much diagnostic info as possible before longjmp destroys the
|
// Dump as much diagnostic info as possible before longjmp destroys the
|
||||||
// crash context. This runs inside the signal handler, so only
|
// crash context. This runs inside the signal handler, so only
|
||||||
// async-signal-safe functions should be used — but since we're in
|
// async-signal-safe functions should be used -- but since we're in
|
||||||
// DJGPP (single-threaded DOS), reentrancy isn't a practical concern
|
// DJGPP (single-threaded DOS), reentrancy isn't a practical concern
|
||||||
// and vfprintf/fflush are safe to call here.
|
// and vfprintf/fflush are safe to call here.
|
||||||
static void logCrash(int sig) {
|
static void logCrash(int sig) {
|
||||||
|
|
@ -191,7 +191,7 @@ static void logCrash(int sig) {
|
||||||
|
|
||||||
// __djgpp_exception_state_ptr is a DJGPP extension that captures the
|
// __djgpp_exception_state_ptr is a DJGPP extension that captures the
|
||||||
// full CPU register state at the point of the exception. This gives
|
// full CPU register state at the point of the exception. This gives
|
||||||
// us the faulting EIP, stack pointer, and all GPRs — invaluable for
|
// us the faulting EIP, stack pointer, and all GPRs -- invaluable for
|
||||||
// post-mortem debugging of app crashes from the log file.
|
// post-mortem debugging of app crashes from the log file.
|
||||||
jmp_buf *estate = __djgpp_exception_state_ptr;
|
jmp_buf *estate = __djgpp_exception_state_ptr;
|
||||||
|
|
||||||
|
|
@ -213,7 +213,7 @@ static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// shellLog — append a line to DVX.LOG
|
// shellLog -- append a line to DVX.LOG
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void shellLog(const char *fmt, ...) {
|
void shellLog(const char *fmt, ...) {
|
||||||
|
|
@ -297,7 +297,7 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
int32_t videoW = prefsGetInt("video", "width", 640);
|
int32_t videoW = prefsGetInt("video", "width", 640);
|
||||||
int32_t videoH = prefsGetInt("video", "height", 480);
|
int32_t videoH = prefsGetInt("video", "height", 480);
|
||||||
int32_t videoBpp = prefsGetInt("video", "bpp", 32);
|
int32_t videoBpp = prefsGetInt("video", "bpp", 16);
|
||||||
shellLog("Preferences: video %ldx%ld %ldbpp", (long)videoW, (long)videoH, (long)videoBpp);
|
shellLog("Preferences: video %ldx%ld %ldbpp", (long)videoW, (long)videoH, (long)videoBpp);
|
||||||
|
|
||||||
// Initialize GUI
|
// Initialize GUI
|
||||||
|
|
@ -352,12 +352,22 @@ int main(int argc, char *argv[]) {
|
||||||
shellLog("Preferences: loaded custom color scheme");
|
shellLog("Preferences: loaded custom color scheme");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply saved wallpaper
|
// Apply saved wallpaper mode and image
|
||||||
|
const char *wpMode = prefsGetString("desktop", "mode", "stretch");
|
||||||
|
|
||||||
|
if (strcmp(wpMode, "tile") == 0) {
|
||||||
|
sCtx.wallpaperMode = WallpaperTileE;
|
||||||
|
} else if (strcmp(wpMode, "center") == 0) {
|
||||||
|
sCtx.wallpaperMode = WallpaperCenterE;
|
||||||
|
} else {
|
||||||
|
sCtx.wallpaperMode = WallpaperStretchE;
|
||||||
|
}
|
||||||
|
|
||||||
const char *wpPath = prefsGetString("desktop", "wallpaper", NULL);
|
const char *wpPath = prefsGetString("desktop", "wallpaper", NULL);
|
||||||
|
|
||||||
if (wpPath) {
|
if (wpPath) {
|
||||||
if (dvxSetWallpaper(&sCtx, wpPath)) {
|
if (dvxSetWallpaper(&sCtx, wpPath)) {
|
||||||
shellLog("Preferences: loaded wallpaper %s", wpPath);
|
shellLog("Preferences: loaded wallpaper %s (%s)", wpPath, wpMode);
|
||||||
} else {
|
} else {
|
||||||
shellLog("Preferences: failed to load wallpaper %s", wpPath);
|
shellLog("Preferences: failed to load wallpaper %s", wpPath);
|
||||||
}
|
}
|
||||||
|
|
@ -416,7 +426,7 @@ int main(int argc, char *argv[]) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install crash handler after everything is initialized — if
|
// Install crash handler after everything is initialized -- if
|
||||||
// initialization itself crashes, we want the default DJGPP behavior
|
// initialization itself crashes, we want the default DJGPP behavior
|
||||||
// (abort with register dump) rather than our recovery path, because
|
// (abort with register dump) rather than our recovery path, because
|
||||||
// the system isn't in a recoverable state yet.
|
// the system isn't in a recoverable state yet.
|
||||||
|
|
@ -454,7 +464,7 @@ int main(int argc, char *argv[]) {
|
||||||
shellDesktopUpdate();
|
shellDesktopUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main loop — runs until dvxQuit() sets sCtx.running = false.
|
// Main loop -- runs until dvxQuit() sets sCtx.running = false.
|
||||||
// Two yield points per iteration: one explicit (below) and one via
|
// Two yield points per iteration: one explicit (below) and one via
|
||||||
// the idle callback inside dvxUpdate. The explicit yield here ensures
|
// the idle callback inside dvxUpdate. The explicit yield here ensures
|
||||||
// app tasks get CPU time even during busy frames (lots of repaints).
|
// app tasks get CPU time even during busy frames (lots of repaints).
|
||||||
|
|
@ -469,7 +479,7 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reap terminated apps and notify desktop if anything changed.
|
// Reap terminated apps and notify desktop if anything changed.
|
||||||
// This is the safe point for cleanup — we're at the top of the
|
// This is the safe point for cleanup -- we're at the top of the
|
||||||
// main loop, not inside any callback or compositor operation.
|
// main loop, not inside any callback or compositor operation.
|
||||||
if (shellReapApps(&sCtx)) {
|
if (shellReapApps(&sCtx)) {
|
||||||
shellDesktopUpdate();
|
shellDesktopUpdate();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// shellTaskMgr.c — System Task Manager
|
// shellTaskMgr.c -- System Task Manager
|
||||||
//
|
//
|
||||||
// Shell-level Task Manager window. Accessible via Ctrl+Esc regardless
|
// Shell-level Task Manager window. Accessible via Ctrl+Esc regardless
|
||||||
// of which app is focused or whether the desktop app is running. Lists
|
// of which app is focused or whether the desktop app is running. Lists
|
||||||
|
|
@ -271,7 +271,7 @@ void shellTaskMgrOpen(AppContextT *ctx) {
|
||||||
sCtx = ctx;
|
sCtx = ctx;
|
||||||
|
|
||||||
if (sTmWindow) {
|
if (sTmWindow) {
|
||||||
// Already open — raise and focus
|
// Already open -- raise and focus
|
||||||
for (int32_t i = 0; i < ctx->stack.count; i++) {
|
for (int32_t i = 0; i < ctx->stack.count; i++) {
|
||||||
if (ctx->stack.windows[i] == sTmWindow) {
|
if (ctx->stack.windows[i] == sTmWindow) {
|
||||||
wmRaiseWindow(&ctx->stack, &ctx->dirty, i);
|
wmRaiseWindow(&ctx->stack, &ctx->dirty, i);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// shellTaskMgr.h — System Task Manager
|
// shellTaskMgr.h -- System Task Manager
|
||||||
//
|
//
|
||||||
// The Task Manager is a shell-level component, not tied to any app.
|
// The Task Manager is a shell-level component, not tied to any app.
|
||||||
// It is always available via Ctrl+Esc and persists even if the desktop
|
// It is always available via Ctrl+Esc and persists even if the desktop
|
||||||
|
|
|
||||||
4
mkcd.sh
4
mkcd.sh
|
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# mkcd.sh — Build DVX and create a CD-ROM ISO image for 86Box
|
# mkcd.sh -- Build DVX and create a CD-ROM ISO image for 86Box
|
||||||
#
|
#
|
||||||
# Usage: ./mkcd.sh
|
# Usage: ./mkcd.sh
|
||||||
#
|
#
|
||||||
|
|
@ -22,7 +22,7 @@ make -C "$SCRIPT_DIR" all
|
||||||
|
|
||||||
# Verify build output exists
|
# Verify build output exists
|
||||||
if [ ! -f "$SCRIPT_DIR/bin/dvx.exe" ]; then
|
if [ ! -f "$SCRIPT_DIR/bin/dvx.exe" ]; then
|
||||||
echo "ERROR: bin/dvx.exe not found — build failed?"
|
echo "ERROR: bin/dvx.exe not found -- build failed?"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Packet — Reliable Serial Transport
|
# Packet -- Reliable Serial Transport
|
||||||
|
|
||||||
Packetized serial transport with HDLC-style framing, CRC-16 error
|
Packetized serial transport with HDLC-style framing, CRC-16 error
|
||||||
detection, and a Go-Back-N sliding window protocol for reliable,
|
detection, and a Go-Back-N sliding window protocol for reliable,
|
||||||
|
|
@ -71,7 +71,7 @@ The protocol uses Go-Back-N with a configurable sliding window
|
||||||
### Types
|
### Types
|
||||||
|
|
||||||
```c
|
```c
|
||||||
// Receive callback — called for each verified, in-order packet
|
// Receive callback -- called for each verified, in-order packet
|
||||||
typedef void (*PktRecvCallbackT)(void *ctx, const uint8_t *data, int len);
|
typedef void (*PktRecvCallbackT)(void *ctx, const uint8_t *data, int len);
|
||||||
|
|
||||||
// Opaque connection handle
|
// Opaque connection handle
|
||||||
|
|
@ -106,10 +106,10 @@ PktConnT *pktOpen(int com, int windowSize,
|
||||||
|
|
||||||
Creates a packetized connection over an already-open COM port.
|
Creates a packetized connection over an already-open COM port.
|
||||||
|
|
||||||
- `com` — RS232 port index (`RS232_COM1`..`RS232_COM4`)
|
- `com` -- RS232 port index (`RS232_COM1`..`RS232_COM4`)
|
||||||
- `windowSize` — sliding window size (1-8), 0 for default (4)
|
- `windowSize` -- sliding window size (1-8), 0 for default (4)
|
||||||
- `callback` — called from `pktPoll()` for each received packet
|
- `callback` -- called from `pktPoll()` for each received packet
|
||||||
- `callbackCtx` — user pointer passed to callback
|
- `callbackCtx` -- user pointer passed to callback
|
||||||
|
|
||||||
Returns a connection handle, or `NULL` on failure.
|
Returns a connection handle, or `NULL` on failure.
|
||||||
|
|
||||||
|
|
@ -129,8 +129,8 @@ int pktSend(PktConnT *conn, const uint8_t *data, int len, bool block);
|
||||||
|
|
||||||
Sends a packet. `len` must be 1..`PKT_MAX_PAYLOAD`.
|
Sends a packet. `len` must be 1..`PKT_MAX_PAYLOAD`.
|
||||||
|
|
||||||
- `block = true` — waits for window space, polling for ACKs internally
|
- `block = true` -- waits for window space, polling for ACKs internally
|
||||||
- `block = false` — returns `PKT_ERR_TX_FULL` if the window is full
|
- `block = false` -- returns `PKT_ERR_TX_FULL` if the window is full
|
||||||
|
|
||||||
The packet is stored in the retransmit buffer until acknowledged.
|
The packet is stored in the retransmit buffer until acknowledged.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
#define RETRANSMIT_TIMEOUT_MS 500
|
#define RETRANSMIT_TIMEOUT_MS 500
|
||||||
|
|
||||||
// Receive state machine: three states for HDLC deframing.
|
// Receive state machine: three states for HDLC deframing.
|
||||||
// HUNT: discarding bytes until a flag (0x7E) is seen — sync acquisition.
|
// HUNT: discarding bytes until a flag (0x7E) is seen -- sync acquisition.
|
||||||
// ACTIVE: accumulating frame bytes, watching for flag (end of frame) or
|
// ACTIVE: accumulating frame bytes, watching for flag (end of frame) or
|
||||||
// escape (next byte is XOR'd).
|
// escape (next byte is XOR'd).
|
||||||
// ESCAPE: the previous byte was 0x7D; XOR this byte with 0x20 to recover
|
// ESCAPE: the previous byte was 0x7D; XOR this byte with 0x20 to recover
|
||||||
|
|
@ -171,7 +171,7 @@ static int txSlotIndex(PktConnT *conn, uint8_t seq);
|
||||||
|
|
||||||
// Table-driven CRC-16-CCITT. Processing one byte per iteration with a
|
// Table-driven CRC-16-CCITT. Processing one byte per iteration with a
|
||||||
// 256-entry table is ~10x faster than bit-by-bit on a 486. The table
|
// 256-entry table is ~10x faster than bit-by-bit on a 486. The table
|
||||||
// costs 512 bytes of .rodata — a worthwhile trade for a function called
|
// costs 512 bytes of .rodata -- a worthwhile trade for a function called
|
||||||
// on every frame received and transmitted.
|
// on every frame received and transmitted.
|
||||||
static uint16_t crcCalc(const uint8_t *data, int len) {
|
static uint16_t crcCalc(const uint8_t *data, int len) {
|
||||||
uint16_t crc = 0xFFFF;
|
uint16_t crc = 0xFFFF;
|
||||||
|
|
@ -258,7 +258,7 @@ static void sendRst(PktConnT *conn) {
|
||||||
|
|
||||||
// Check if a sequence number falls within a window starting at base.
|
// Check if a sequence number falls within a window starting at base.
|
||||||
// Works correctly with 8-bit wrap-around because unsigned subtraction
|
// Works correctly with 8-bit wrap-around because unsigned subtraction
|
||||||
// wraps mod 256 — if seq is "ahead" of base by less than size, diff
|
// wraps mod 256 -- if seq is "ahead" of base by less than size, diff
|
||||||
// will be a small positive number.
|
// will be a small positive number.
|
||||||
static int seqInWindow(uint8_t seq, uint8_t base, int size) {
|
static int seqInWindow(uint8_t seq, uint8_t base, int size) {
|
||||||
uint8_t diff = seq - base;
|
uint8_t diff = seq - base;
|
||||||
|
|
@ -282,7 +282,7 @@ static int txSlotIndex(PktConnT *conn, uint8_t seq) {
|
||||||
// Handle a complete, de-stuffed frame. CRC is verified first; on failure,
|
// Handle a complete, de-stuffed frame. CRC is verified first; on failure,
|
||||||
// we NAK to request retransmission of the frame we actually expected.
|
// we NAK to request retransmission of the frame we actually expected.
|
||||||
//
|
//
|
||||||
// For DATA frames: Go-Back-N receiver logic — only accept if seq matches
|
// For DATA frames: Go-Back-N receiver logic -- only accept if seq matches
|
||||||
// rxExpectSeq (strictly in-order). Out-of-order frames within the window
|
// rxExpectSeq (strictly in-order). Out-of-order frames within the window
|
||||||
// trigger a NAK; duplicates and out-of-window frames are silently dropped.
|
// trigger a NAK; duplicates and out-of-window frames are silently dropped.
|
||||||
//
|
//
|
||||||
|
|
@ -306,7 +306,7 @@ static void processFrame(PktConnT *conn, const uint8_t *frame, int len) {
|
||||||
calcCrc = crcCalc(frame, len - CRC_SIZE);
|
calcCrc = crcCalc(frame, len - CRC_SIZE);
|
||||||
rxCrc = frame[len - 2] | ((uint16_t)frame[len - 1] << 8);
|
rxCrc = frame[len - 2] | ((uint16_t)frame[len - 1] << 8);
|
||||||
if (calcCrc != rxCrc) {
|
if (calcCrc != rxCrc) {
|
||||||
// CRC mismatch — request retransmit of what we expect
|
// CRC mismatch -- request retransmit of what we expect
|
||||||
sendNak(conn, conn->rxExpectSeq);
|
sendNak(conn, conn->rxExpectSeq);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -330,7 +330,7 @@ static void processFrame(PktConnT *conn, const uint8_t *frame, int len) {
|
||||||
conn->rxExpectSeq++;
|
conn->rxExpectSeq++;
|
||||||
sendAck(conn, conn->rxExpectSeq);
|
sendAck(conn, conn->rxExpectSeq);
|
||||||
} else if (seqInWindow(seq, conn->rxExpectSeq, conn->windowSize)) {
|
} else if (seqInWindow(seq, conn->rxExpectSeq, conn->windowSize)) {
|
||||||
// Out of order but in window — NAK the one we want
|
// Out of order but in window -- NAK the one we want
|
||||||
sendNak(conn, conn->rxExpectSeq);
|
sendNak(conn, conn->rxExpectSeq);
|
||||||
}
|
}
|
||||||
// else: duplicate or out of window, silently discard
|
// else: duplicate or out of window, silently discard
|
||||||
|
|
@ -361,7 +361,7 @@ static void processFrame(PktConnT *conn, const uint8_t *frame, int len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case FRAME_RST:
|
case FRAME_RST:
|
||||||
// Remote requested reset — clear state and respond with RST
|
// Remote requested reset -- clear state and respond with RST
|
||||||
conn->txNextSeq = 0;
|
conn->txNextSeq = 0;
|
||||||
conn->txAckSeq = 0;
|
conn->txAckSeq = 0;
|
||||||
conn->txCount = 0;
|
conn->txCount = 0;
|
||||||
|
|
@ -429,7 +429,7 @@ static void rxProcessByte(PktConnT *conn, uint8_t byte) {
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
// Timer-based retransmission for slots that haven't been ACK'd within the
|
// Timer-based retransmission for slots that haven't been ACK'd within the
|
||||||
// timeout. This handles the case where an ACK or NAK was lost — without
|
// timeout. This handles the case where an ACK or NAK was lost -- without
|
||||||
// this, the connection would stall forever. Each slot is retransmitted
|
// this, the connection would stall forever. Each slot is retransmitted
|
||||||
// independently and its timer is reset, creating exponential backoff
|
// independently and its timer is reset, creating exponential backoff
|
||||||
// behavior naturally (each retransmit resets the timer).
|
// behavior naturally (each retransmit resets the timer).
|
||||||
|
|
@ -504,7 +504,7 @@ PktConnT *pktOpen(int com, int windowSize, PktRecvCallbackT callback, void *call
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Main polling function — must be called frequently (each iteration of the
|
// Main polling function -- must be called frequently (each iteration of the
|
||||||
// app's main loop or event loop). It drains the serial port's RX buffer,
|
// app's main loop or event loop). It drains the serial port's RX buffer,
|
||||||
// feeds bytes through the deframing state machine, and checks for
|
// feeds bytes through the deframing state machine, and checks for
|
||||||
// retransmit timeouts. The callback is invoked synchronously for each
|
// retransmit timeouts. The callback is invoked synchronously for each
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
// product of a serial link. On a 115200 bps local serial connection, the
|
// product of a serial link. On a 115200 bps local serial connection, the
|
||||||
// round-trip time is negligible, so the window rarely fills. GBN's
|
// round-trip time is negligible, so the window rarely fills. GBN's
|
||||||
// retransmit-all-from-NAK behavior wastes bandwidth on lossy links, but
|
// retransmit-all-from-NAK behavior wastes bandwidth on lossy links, but
|
||||||
// serial links are nearly lossless — the CRC check is primarily a safety
|
// serial links are nearly lossless -- the CRC check is primarily a safety
|
||||||
// net for electrical noise, not a routine error recovery mechanism.
|
// net for electrical noise, not a routine error recovery mechanism.
|
||||||
//
|
//
|
||||||
// CRC-16-CCITT:
|
// CRC-16-CCITT:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# SecLink Proxy — Linux build
|
# SecLink Proxy -- Linux build
|
||||||
# Compiles the packet, security, and secLink layers against a socket
|
# Compiles the packet, security, and secLink layers against a socket
|
||||||
# shim instead of the DJGPP rs232 driver.
|
# shim instead of the DJGPP rs232 driver.
|
||||||
|
|
||||||
|
|
@ -27,15 +27,15 @@ $(OBJDIR)/sockShim.o: sockShim.c sockShim.h | $(OBJDIR)
|
||||||
$(OBJDIR)/proxy.o: proxy.c sockShim.h | $(OBJDIR)
|
$(OBJDIR)/proxy.o: proxy.c sockShim.h | $(OBJDIR)
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
# Packet layer — block real rs232.h, inject socket shim
|
# Packet layer -- block real rs232.h, inject socket shim
|
||||||
$(OBJDIR)/packet.o: ../packet/packet.c sockShim.h | $(OBJDIR)
|
$(OBJDIR)/packet.o: ../packet/packet.c sockShim.h | $(OBJDIR)
|
||||||
$(CC) $(CFLAGS) -I. -Istubs/ -include sockShim.h -c -o $@ $<
|
$(CC) $(CFLAGS) -I. -Istubs/ -include sockShim.h -c -o $@ $<
|
||||||
|
|
||||||
# Security layer — stub DOS-specific headers
|
# Security layer -- stub DOS-specific headers
|
||||||
$(OBJDIR)/security.o: ../security/security.c | $(OBJDIR)
|
$(OBJDIR)/security.o: ../security/security.c | $(OBJDIR)
|
||||||
$(CC) $(CFLAGS) -Istubs/ -c -o $@ $<
|
$(CC) $(CFLAGS) -Istubs/ -c -o $@ $<
|
||||||
|
|
||||||
# SecLink layer — block real rs232.h, inject socket shim
|
# SecLink layer -- block real rs232.h, inject socket shim
|
||||||
$(OBJDIR)/secLink.o: ../seclink/secLink.c sockShim.h | $(OBJDIR)
|
$(OBJDIR)/secLink.o: ../seclink/secLink.c sockShim.h | $(OBJDIR)
|
||||||
$(CC) $(CFLAGS) -I. -include sockShim.h -c -o $@ $<
|
$(CC) $(CFLAGS) -I. -include sockShim.h -c -o $@ $<
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ side is plain telnet over TCP.
|
||||||
TCP:2323 TCP:23
|
TCP:2323 TCP:23
|
||||||
| |
|
| |
|
||||||
+--- secproxy ----------------------------------+
|
+--- secproxy ----------------------------------+
|
||||||
secLink ←→ plaintext
|
secLink <-> plaintext
|
||||||
(encrypted, reliable)
|
(encrypted, reliable)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -59,10 +59,10 @@ secproxy --help # show usage
|
||||||
The main loop uses `poll()` with a 10ms timeout to multiplex between
|
The main loop uses `poll()` with a 10ms timeout to multiplex between
|
||||||
the two TCP connections:
|
the two TCP connections:
|
||||||
|
|
||||||
- **86Box → BBS**: `secLinkPoll()` reads from the 86Box socket via
|
- **86Box -> BBS**: `secLinkPoll()` reads from the 86Box socket via
|
||||||
the socket shim, decrypts incoming packets, and the receive callback
|
the socket shim, decrypts incoming packets, and the receive callback
|
||||||
writes plaintext to the BBS socket.
|
writes plaintext to the BBS socket.
|
||||||
- **BBS → 86Box**: `read()` from the BBS socket, then
|
- **BBS -> 86Box**: `read()` from the BBS socket, then
|
||||||
`secLinkSend()` encrypts and sends to 86Box via the socket shim.
|
`secLinkSend()` encrypts and sends to 86Box via the socket shim.
|
||||||
- **Maintenance**: `secLinkPoll()` also handles packet-layer retransmit
|
- **Maintenance**: `secLinkPoll()` also handles packet-layer retransmit
|
||||||
timers on every iteration.
|
timers on every iteration.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// SecLink proxy — bridges an 86Box serial connection to a telnet BBS
|
// SecLink proxy -- bridges an 86Box serial connection to a telnet BBS
|
||||||
//
|
//
|
||||||
// Architecture:
|
// Architecture:
|
||||||
// 86Box (DOS terminal) ←→ TCP ←→ proxy ←→ TCP ←→ BBS
|
// 86Box (DOS terminal) <-> TCP <-> proxy <-> TCP <-> BBS
|
||||||
// secLink protocol plain telnet
|
// secLink protocol plain telnet
|
||||||
//
|
//
|
||||||
// The proxy runs on Linux and sits between two TCP connections:
|
// The proxy runs on Linux and sits between two TCP connections:
|
||||||
|
|
@ -172,7 +172,7 @@ static int createListenSocket(int port) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// secLink receive callback — called when decrypted data arrives from the DOS
|
// secLink receive callback -- called when decrypted data arrives from the DOS
|
||||||
// terminal. Before the BBS is connected, we watch for an ENTER keypress as
|
// terminal. Before the BBS is connected, we watch for an ENTER keypress as
|
||||||
// a "ready" signal so the user can see the terminal is working before we
|
// a "ready" signal so the user can see the terminal is working before we
|
||||||
// connect to the BBS (which would immediately start sending ANSI art).
|
// connect to the BBS (which would immediately start sending ANSI art).
|
||||||
|
|
@ -203,7 +203,7 @@ static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t chann
|
||||||
|
|
||||||
|
|
||||||
// On Linux, /dev/urandom provides high-quality entropy without blocking.
|
// On Linux, /dev/urandom provides high-quality entropy without blocking.
|
||||||
// This is vastly better than the PIT-based entropy on DOS — one of the
|
// This is vastly better than the PIT-based entropy on DOS -- one of the
|
||||||
// advantages of running the proxy on the host OS.
|
// advantages of running the proxy on the host OS.
|
||||||
static void seedRng(void) {
|
static void seedRng(void) {
|
||||||
uint8_t entropy[32];
|
uint8_t entropy[32];
|
||||||
|
|
@ -246,7 +246,7 @@ static void telnetRespond(int bbsFd, uint8_t cmd, uint8_t opt) {
|
||||||
// We accept ECHO and SGA (Suppress Go Ahead) because most BBSes require them
|
// We accept ECHO and SGA (Suppress Go Ahead) because most BBSes require them
|
||||||
// for character-at-a-time mode. We accept TTYPE and NAWS for terminal type
|
// for character-at-a-time mode. We accept TTYPE and NAWS for terminal type
|
||||||
// and window size negotiation. Everything else is refused. Subnegotiations
|
// and window size negotiation. Everything else is refused. Subnegotiations
|
||||||
// (SB...SE) are consumed silently — we don't actually respond with terminal
|
// (SB...SE) are consumed silently -- we don't actually respond with terminal
|
||||||
// type or window size data, but accepting the option prevents the BBS from
|
// type or window size data, but accepting the option prevents the BBS from
|
||||||
// falling back to line mode.
|
// falling back to line mode.
|
||||||
// Returns the number of clean data bytes written to 'out'.
|
// Returns the number of clean data bytes written to 'out'.
|
||||||
|
|
@ -268,7 +268,7 @@ static int telnetFilter(int bbsFd, const uint8_t *in, int inLen, uint8_t *out) {
|
||||||
case TS_IAC:
|
case TS_IAC:
|
||||||
switch (b) {
|
switch (b) {
|
||||||
case TEL_IAC:
|
case TEL_IAC:
|
||||||
// Escaped 0xFF — emit literal
|
// Escaped 0xFF -- emit literal
|
||||||
out[outLen++] = 0xFF;
|
out[outLen++] = 0xFF;
|
||||||
sTelState = TS_DATA;
|
sTelState = TS_DATA;
|
||||||
break;
|
break;
|
||||||
|
|
@ -295,7 +295,7 @@ static int telnetFilter(int bbsFd, const uint8_t *in, int inLen, uint8_t *out) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TS_WILL:
|
case TS_WILL:
|
||||||
// Server offers to do something — accept ECHO and SGA, refuse others
|
// Server offers to do something -- accept ECHO and SGA, refuse others
|
||||||
printf(" TEL RX: WILL %d\n", b);
|
printf(" TEL RX: WILL %d\n", b);
|
||||||
if (b == TELOPT_ECHO || b == TELOPT_SGA) {
|
if (b == TELOPT_ECHO || b == TELOPT_SGA) {
|
||||||
telnetRespond(bbsFd, TEL_DO, b);
|
telnetRespond(bbsFd, TEL_DO, b);
|
||||||
|
|
@ -312,7 +312,7 @@ static int telnetFilter(int bbsFd, const uint8_t *in, int inLen, uint8_t *out) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TS_DO:
|
case TS_DO:
|
||||||
// Server asks us to do something — accept TTYPE and NAWS, refuse others
|
// Server asks us to do something -- accept TTYPE and NAWS, refuse others
|
||||||
printf(" TEL RX: DO %d\n", b);
|
printf(" TEL RX: DO %d\n", b);
|
||||||
if (b == TELOPT_TTYPE || b == TELOPT_NAWS) {
|
if (b == TELOPT_TTYPE || b == TELOPT_NAWS) {
|
||||||
telnetRespond(bbsFd, TEL_WILL, b);
|
telnetRespond(bbsFd, TEL_WILL, b);
|
||||||
|
|
@ -329,7 +329,7 @@ static int telnetFilter(int bbsFd, const uint8_t *in, int inLen, uint8_t *out) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TS_SB:
|
case TS_SB:
|
||||||
// Inside subnegotiation — skip until IAC SE
|
// Inside subnegotiation -- skip until IAC SE
|
||||||
if (b == TEL_IAC) {
|
if (b == TEL_IAC) {
|
||||||
sTelState = TS_SB_IAC;
|
sTelState = TS_SB_IAC;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
// Socket shim — rs232-compatible API over TCP sockets
|
// Socket shim -- rs232-compatible API over TCP sockets
|
||||||
//
|
//
|
||||||
// Maps up to 4 "COM ports" to TCP socket file descriptors. Reads use
|
// Maps up to 4 "COM ports" to TCP socket file descriptors. Reads use
|
||||||
// MSG_DONTWAIT (non-blocking) to match rs232Read's non-blocking semantics.
|
// MSG_DONTWAIT (non-blocking) to match rs232Read's non-blocking semantics.
|
||||||
// Writes are blocking (loop until all bytes sent) to match rs232Write's
|
// Writes are blocking (loop until all bytes sent) to match rs232Write's
|
||||||
// guarantee of complete delivery.
|
// guarantee of complete delivery.
|
||||||
//
|
//
|
||||||
// The shim does NOT close sockets in rs232Close — socket lifecycle is
|
// The shim does NOT close sockets in rs232Close -- socket lifecycle is
|
||||||
// managed by the proxy's main() function. This avoids double-close bugs
|
// managed by the proxy's main() function. This avoids double-close bugs
|
||||||
// when secLinkClose calls rs232Close but the proxy still needs the fd.
|
// when secLinkClose calls rs232Close but the proxy still needs the fd.
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ int rs232Close(int com) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Serial parameters are ignored — TCP handles framing, flow control, and
|
// Serial parameters are ignored -- TCP handles framing, flow control, and
|
||||||
// error correction. We just validate that a socket has been assigned.
|
// error correction. We just validate that a socket has been assigned.
|
||||||
int rs232Open(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake) {
|
int rs232Open(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake) {
|
||||||
(void)bps;
|
(void)bps;
|
||||||
|
|
@ -79,7 +79,7 @@ int rs232Read(int com, char *data, int len) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
// TCP FIN — peer closed connection
|
// TCP FIN -- peer closed connection
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Socket shim — provides rs232-compatible API backed by TCP sockets
|
// Socket shim -- provides rs232-compatible API backed by TCP sockets
|
||||||
// Used by the Linux proxy to reuse the packet and secLink layers.
|
// Used by the Linux proxy to reuse the packet and secLink layers.
|
||||||
//
|
//
|
||||||
// Design: the packet and secLink layers call rs232Read/rs232Write assuming
|
// Design: the packet and secLink layers call rs232Read/rs232Write assuming
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Stub for DJGPP <go32.h> — Linux proxy build
|
// Stub for DJGPP <go32.h> -- Linux proxy build
|
||||||
#ifndef GO32_H_STUB
|
#ifndef GO32_H_STUB
|
||||||
#define GO32_H_STUB
|
#define GO32_H_STUB
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Stub for DJGPP <pc.h> — Linux proxy build
|
// Stub for DJGPP <pc.h> -- Linux proxy build
|
||||||
#ifndef PC_H_STUB
|
#ifndef PC_H_STUB
|
||||||
#define PC_H_STUB
|
#define PC_H_STUB
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Stub for DJGPP <sys/farptr.h> — Linux proxy build
|
// Stub for DJGPP <sys/farptr.h> -- Linux proxy build
|
||||||
#ifndef FARPTR_H_STUB
|
#ifndef FARPTR_H_STUB
|
||||||
#define FARPTR_H_STUB
|
#define FARPTR_H_STUB
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# RS232 — Serial Port Library for DJGPP
|
# RS232 -- Serial Port Library for DJGPP
|
||||||
|
|
||||||
ISR-driven UART communication library supporting up to 4 simultaneous
|
ISR-driven UART communication library supporting up to 4 simultaneous
|
||||||
COM ports with ring buffers and hardware/software flow control.
|
COM ports with ring buffers and hardware/software flow control.
|
||||||
|
|
@ -35,10 +35,10 @@ All functions take a COM port index (`int com`) as their first argument:
|
||||||
| Constant | Value | Description |
|
| Constant | Value | Description |
|
||||||
|---------------------|-------|--------------------------------------|
|
|---------------------|-------|--------------------------------------|
|
||||||
| `RS232_UART_UNKNOWN`| 0 | Unknown or undetected |
|
| `RS232_UART_UNKNOWN`| 0 | Unknown or undetected |
|
||||||
| `RS232_UART_8250` | 1 | 8250 — no FIFO, no scratch register |
|
| `RS232_UART_8250` | 1 | 8250 -- no FIFO, no scratch register |
|
||||||
| `RS232_UART_16450` | 2 | 16450 — scratch register, no FIFO |
|
| `RS232_UART_16450` | 2 | 16450 -- scratch register, no FIFO |
|
||||||
| `RS232_UART_16550` | 3 | 16550 — broken FIFO (unusable) |
|
| `RS232_UART_16550` | 3 | 16550 -- broken FIFO (unusable) |
|
||||||
| `RS232_UART_16550A` | 4 | 16550A — working 16-byte FIFO |
|
| `RS232_UART_16550A` | 4 | 16550A -- working 16-byte FIFO |
|
||||||
|
|
||||||
### Handshaking Modes
|
### Handshaking Modes
|
||||||
|
|
||||||
|
|
@ -83,13 +83,13 @@ int rs232Open(int com, int32_t bps, int dataBits, char parity,
|
||||||
Opens a COM port. Detects the UART base address from the BIOS data
|
Opens a COM port. Detects the UART base address from the BIOS data
|
||||||
area, auto-detects the IRQ, installs the ISR, and configures the port.
|
area, auto-detects the IRQ, installs the ISR, and configures the port.
|
||||||
|
|
||||||
- `bps` — baud rate (50, 75, 110, 150, 300, 600, 1200, 1800, 2400,
|
- `bps` -- baud rate (50, 75, 110, 150, 300, 600, 1200, 1800, 2400,
|
||||||
3800, 4800, 7200, 9600, 19200, 38400, 57600, 115200)
|
3800, 4800, 7200, 9600, 19200, 38400, 57600, 115200)
|
||||||
- `dataBits` — 5, 6, 7, or 8
|
- `dataBits` -- 5, 6, 7, or 8
|
||||||
- `parity` — `'N'` (none), `'O'` (odd), `'E'` (even), `'M'` (mark),
|
- `parity` -- `'N'` (none), `'O'` (odd), `'E'` (even), `'M'` (mark),
|
||||||
`'S'` (space)
|
`'S'` (space)
|
||||||
- `stopBits` — 1 or 2
|
- `stopBits` -- 1 or 2
|
||||||
- `handshake` — `RS232_HANDSHAKE_*` constant
|
- `handshake` -- `RS232_HANDSHAKE_*` constant
|
||||||
|
|
||||||
```c
|
```c
|
||||||
int rs232Close(int com);
|
int rs232Close(int com);
|
||||||
|
|
@ -154,9 +154,9 @@ int rs232GetUartType(int com); // UART type (RS232_UART_* constant)
|
||||||
|
|
||||||
`rs232GetUartType` probes the UART hardware to identify the chip:
|
`rs232GetUartType` probes the UART hardware to identify the chip:
|
||||||
|
|
||||||
1. **Scratch register test** — writes two values to register 7 and
|
1. **Scratch register test** -- writes two values to register 7 and
|
||||||
reads them back. The 8250 lacks this register, so readback fails.
|
reads them back. The 8250 lacks this register, so readback fails.
|
||||||
2. **FIFO test** — enables the FIFO via the FCR, then reads IIR bits
|
2. **FIFO test** -- enables the FIFO via the FCR, then reads IIR bits
|
||||||
7:6. `0b11` = 16550A (working FIFO), `0b10` = 16550 (broken FIFO),
|
7:6. `0b11` = 16550A (working FIFO), `0b10` = 16550 (broken FIFO),
|
||||||
`0b00` = 16450 (no FIFO). The original FCR value is restored after
|
`0b00` = 16450 (no FIFO). The original FCR value is restored after
|
||||||
probing.
|
probing.
|
||||||
|
|
|
||||||
|
|
@ -325,7 +325,7 @@ static void removeIrqHandler(int irq);
|
||||||
// 4. CLI, send EOI to PIC, re-enable COM IRQs, STI before IRET
|
// 4. CLI, send EOI to PIC, re-enable COM IRQs, STI before IRET
|
||||||
//
|
//
|
||||||
// This "mask-then-STI" pattern is standard for slow device ISRs on PC
|
// This "mask-then-STI" pattern is standard for slow device ISRs on PC
|
||||||
// hardware — it prevents the same IRQ from re-entering while still allowing
|
// hardware -- it prevents the same IRQ from re-entering while still allowing
|
||||||
// the timer tick and keyboard to function during potentially long UART
|
// the timer tick and keyboard to function during potentially long UART
|
||||||
// processing.
|
// processing.
|
||||||
static void comGeneralIsr(void) {
|
static void comGeneralIsr(void) {
|
||||||
|
|
@ -449,7 +449,7 @@ static void comGeneralIsr(void) {
|
||||||
|
|
||||||
// These functions wrap DJGPP's DPMI interface for installing protected-mode
|
// These functions wrap DJGPP's DPMI interface for installing protected-mode
|
||||||
// interrupt handlers. Under DPMI, the ISR code and data must be locked in
|
// interrupt handlers. Under DPMI, the ISR code and data must be locked in
|
||||||
// physical memory to prevent page faults during interrupt handling — a page
|
// physical memory to prevent page faults during interrupt handling -- a page
|
||||||
// fault inside an ISR would be fatal. The IRET wrapper is also allocated
|
// fault inside an ISR would be fatal. The IRET wrapper is also allocated
|
||||||
// by DPMI to handle the real-mode-to-protected-mode transition.
|
// by DPMI to handle the real-mode-to-protected-mode transition.
|
||||||
|
|
||||||
|
|
@ -603,7 +603,7 @@ static int findIrq(int comport) {
|
||||||
|
|
||||||
|
|
||||||
// Release this port's IRQ. If another port shares the same IRQ line,
|
// Release this port's IRQ. If another port shares the same IRQ line,
|
||||||
// the handler stays installed — only the last user removes it. This is
|
// the handler stays installed -- only the last user removes it. This is
|
||||||
// critical for COM1/COM3 IRQ sharing.
|
// critical for COM1/COM3 IRQ sharing.
|
||||||
static void freeIrq(int comport) {
|
static void freeIrq(int comport) {
|
||||||
Rs232StateT *com = &sComPorts[comport];
|
Rs232StateT *com = &sComPorts[comport];
|
||||||
|
|
@ -998,8 +998,8 @@ int rs232GetTxBuffered(int com) {
|
||||||
|
|
||||||
|
|
||||||
// Detect UART type by probing hardware features. The detection sequence:
|
// Detect UART type by probing hardware features. The detection sequence:
|
||||||
// 1. Write/read scratch register — 8250 doesn't have one
|
// 1. Write/read scratch register -- 8250 doesn't have one
|
||||||
// 2. Enable FIFO and check IIR bits 7:6 — distinguishes 16450/16550/16550A
|
// 2. Enable FIFO and check IIR bits 7:6 -- distinguishes 16450/16550/16550A
|
||||||
// This matters because only 16550A has a reliable 16-byte FIFO; the
|
// This matters because only 16550A has a reliable 16-byte FIFO; the
|
||||||
// original 16550 FIFO is buggy and should not be enabled.
|
// original 16550 FIFO is buggy and should not be enabled.
|
||||||
int rs232GetUartType(int com) {
|
int rs232GetUartType(int com) {
|
||||||
|
|
@ -1014,7 +1014,7 @@ int rs232GetUartType(int com) {
|
||||||
return RS232_ERR_NOT_OPEN;
|
return RS232_ERR_NOT_OPEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test scratch register — 8250 lacks it
|
// Test scratch register -- 8250 lacks it
|
||||||
outportb(port->base + UART_SCR, 0xA5);
|
outportb(port->base + UART_SCR, 0xA5);
|
||||||
scratch = inportb(port->base + UART_SCR);
|
scratch = inportb(port->base + UART_SCR);
|
||||||
if (scratch != 0xA5) {
|
if (scratch != 0xA5) {
|
||||||
|
|
@ -1026,7 +1026,7 @@ int rs232GetUartType(int com) {
|
||||||
return RS232_UART_8250;
|
return RS232_UART_8250;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has scratch register — at least 16450. Try enabling FIFO.
|
// Has scratch register -- at least 16450. Try enabling FIFO.
|
||||||
outportb(port->base + UART_FCR, FCR_ENABLE);
|
outportb(port->base + UART_FCR, FCR_ENABLE);
|
||||||
iir = inportb(port->base + UART_IIR);
|
iir = inportb(port->base + UART_IIR);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
// pace from application-level processing.
|
// pace from application-level processing.
|
||||||
//
|
//
|
||||||
// Ring buffers are power-of-2 sized (2048 bytes) so head/tail index
|
// Ring buffers are power-of-2 sized (2048 bytes) so head/tail index
|
||||||
// wrapping is a single AND, not a modulo — critical for ISR speed.
|
// wrapping is a single AND, not a modulo -- critical for ISR speed.
|
||||||
//
|
//
|
||||||
// Flow control (XON/XOFF, RTS/CTS, DTR/DSR) operates entirely within
|
// Flow control (XON/XOFF, RTS/CTS, DTR/DSR) operates entirely within
|
||||||
// the ISR using watermark thresholds. When the RX buffer is 80% full,
|
// the ISR using watermark thresholds. When the RX buffer is 80% full,
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
// prevents buffer overflow without application involvement.
|
// prevents buffer overflow without application involvement.
|
||||||
//
|
//
|
||||||
// The ISR and its data structures are locked in memory via DPMI to
|
// The ISR and its data structures are locked in memory via DPMI to
|
||||||
// prevent page faults during interrupt handling — a requirement for
|
// prevent page faults during interrupt handling -- a requirement for
|
||||||
// any ISR running under a DPMI host (DOS extender, Windows, etc.).
|
// any ISR running under a DPMI host (DOS extender, Windows, etc.).
|
||||||
|
|
||||||
#ifndef RS232_H
|
#ifndef RS232_H
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
# SecLink — Secure Serial Link Library
|
# SecLink -- Secure Serial Link Library
|
||||||
|
|
||||||
SecLink is a convenience wrapper that ties together three lower-level
|
SecLink is a convenience wrapper that ties together three lower-level
|
||||||
libraries into a single API for reliable, optionally encrypted serial
|
libraries into a single API for reliable, optionally encrypted serial
|
||||||
communication:
|
communication:
|
||||||
|
|
||||||
- **rs232** — ISR-driven UART I/O with ring buffers and flow control
|
- **rs232** -- ISR-driven UART I/O with ring buffers and flow control
|
||||||
- **packet** — HDLC-style framing with CRC-16 and sliding window reliability
|
- **packet** -- HDLC-style framing with CRC-16 and sliding window reliability
|
||||||
- **security** — 1024-bit Diffie-Hellman key exchange and XTEA-CTR encryption
|
- **security** -- 1024-bit Diffie-Hellman key exchange and XTEA-CTR encryption
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ Cleartext packets can be sent immediately after `secLinkOpen()`.
|
||||||
### Types
|
### Types
|
||||||
|
|
||||||
```c
|
```c
|
||||||
// Receive callback — called for each incoming packet with plaintext
|
// Receive callback -- called for each incoming packet with plaintext
|
||||||
typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
||||||
|
|
||||||
// Opaque connection handle
|
// Opaque connection handle
|
||||||
|
|
@ -150,9 +150,9 @@ int secLinkSend(SecLinkT *link, const uint8_t *data, int len,
|
||||||
|
|
||||||
Sends up to `SECLINK_MAX_PAYLOAD` (255) bytes on the given channel.
|
Sends up to `SECLINK_MAX_PAYLOAD` (255) bytes on the given channel.
|
||||||
|
|
||||||
- `channel` — logical channel number (0-127)
|
- `channel` -- logical channel number (0-127)
|
||||||
- `encrypt` — if `true`, encrypts the payload (requires completed handshake)
|
- `encrypt` -- if `true`, encrypts the payload (requires completed handshake)
|
||||||
- `block` — if `true`, waits for transmit window space; if `false`,
|
- `block` -- if `true`, waits for transmit window space; if `false`,
|
||||||
returns `SECLINK_ERR_SEND` when the window is full
|
returns `SECLINK_ERR_SEND` when the window is full
|
||||||
|
|
||||||
Cleartext packets (`encrypt = false`) can be sent before the handshake.
|
Cleartext packets (`encrypt = false`) can be sent before the handshake.
|
||||||
|
|
@ -288,8 +288,8 @@ Link against all four libraries:
|
||||||
|
|
||||||
SecLink requires these libraries (all in `../lib/`):
|
SecLink requires these libraries (all in `../lib/`):
|
||||||
|
|
||||||
- `librs232.a` — serial port driver
|
- `librs232.a` -- serial port driver
|
||||||
- `libpacket.a` — packet framing and reliability
|
- `libpacket.a` -- packet framing and reliability
|
||||||
- `libsecurity.a` — DH key exchange and XTEA cipher
|
- `libsecurity.a` -- DH key exchange and XTEA cipher
|
||||||
|
|
||||||
Target: DJGPP cross-compiler, 486+ CPU.
|
Target: DJGPP cross-compiler, 486+ CPU.
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
// Secure serial link — ties rs232, packet, and security into one API
|
// Secure serial link -- ties rs232, packet, and security into one API
|
||||||
//
|
//
|
||||||
// Handshake protocol:
|
// Handshake protocol:
|
||||||
// 1. Both sides send their DH public key (128 bytes) as a single packet
|
// 1. Both sides send their DH public key (128 bytes) as a single packet
|
||||||
// 2. On receiving the remote key, compute shared secret immediately
|
// 2. On receiving the remote key, compute shared secret immediately
|
||||||
// 3. Derive separate TX/RX cipher keys based on public key ordering
|
// 3. Derive separate TX/RX cipher keys based on public key ordering
|
||||||
// 4. Transition to READY — all subsequent encrypted packets use the keys
|
// 4. Transition to READY -- all subsequent encrypted packets use the keys
|
||||||
//
|
//
|
||||||
// The handshake uses the packet layer's reliable delivery, so lost packets
|
// The handshake uses the packet layer's reliable delivery, so lost packets
|
||||||
// are automatically retransmitted. Both sides can send their public key
|
// are automatically retransmitted. Both sides can send their public key
|
||||||
// simultaneously — there's no initiator/responder distinction.
|
// simultaneously -- there's no initiator/responder distinction.
|
||||||
//
|
//
|
||||||
// Directionality: the side with the lexicographically lower public key
|
// Directionality: the side with the lexicographically lower public key
|
||||||
// uses master XOR 0xAA for TX and master XOR 0x55 for RX. The other
|
// uses master XOR 0xAA for TX and master XOR 0x55 for RX. The other
|
||||||
|
|
@ -79,7 +79,7 @@ static void internalRecv(void *ctx, const uint8_t *data, int len);
|
||||||
// Called when we've received the remote's public key. Computes the DH
|
// Called when we've received the remote's public key. Computes the DH
|
||||||
// shared secret, derives directional cipher keys, and transitions to READY.
|
// shared secret, derives directional cipher keys, and transitions to READY.
|
||||||
// After this, the DH context (containing the private key) is destroyed
|
// After this, the DH context (containing the private key) is destroyed
|
||||||
// immediately — forward secrecy principle: even if the long-term state is
|
// immediately -- forward secrecy principle: even if the long-term state is
|
||||||
// compromised later, past session keys can't be recovered.
|
// compromised later, past session keys can't be recovered.
|
||||||
static void completeHandshake(SecLinkT *link) {
|
static void completeHandshake(SecLinkT *link) {
|
||||||
uint8_t masterKey[SEC_XTEA_KEY_SIZE];
|
uint8_t masterKey[SEC_XTEA_KEY_SIZE];
|
||||||
|
|
@ -103,7 +103,7 @@ static void completeHandshake(SecLinkT *link) {
|
||||||
memset(txKey, 0, sizeof(txKey));
|
memset(txKey, 0, sizeof(txKey));
|
||||||
memset(rxKey, 0, sizeof(rxKey));
|
memset(rxKey, 0, sizeof(rxKey));
|
||||||
|
|
||||||
// Destroy DH context — private key no longer needed
|
// Destroy DH context -- private key no longer needed
|
||||||
secDhDestroy(link->dh);
|
secDhDestroy(link->dh);
|
||||||
link->dh = 0;
|
link->dh = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
// Secure serial link — convenience wrapper tying rs232 + packet + security
|
// Secure serial link -- convenience wrapper tying rs232 + packet + security
|
||||||
//
|
//
|
||||||
// This is the top-level API for the serial/networking stack. It composes
|
// This is the top-level API for the serial/networking stack. It composes
|
||||||
// three layers into one:
|
// three layers into one:
|
||||||
// rs232 — ISR-driven UART I/O with ring buffers
|
// rs232 -- ISR-driven UART I/O with ring buffers
|
||||||
// packet — HDLC framing + CRC-16 + Go-Back-N ARQ (reliable delivery)
|
// packet -- HDLC framing + CRC-16 + Go-Back-N ARQ (reliable delivery)
|
||||||
// security — DH key exchange + XTEA-CTR encryption
|
// security -- DH key exchange + XTEA-CTR encryption
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
// 1. secLinkOpen() — opens COM port, sets up packet framing
|
// 1. secLinkOpen() -- opens COM port, sets up packet framing
|
||||||
// 2. secLinkHandshake() — DH key exchange (blocks until both sides complete)
|
// 2. secLinkHandshake() -- DH key exchange (blocks until both sides complete)
|
||||||
// 3. secLinkSend() — send data (optionally encrypted) on a channel
|
// 3. secLinkSend() -- send data (optionally encrypted) on a channel
|
||||||
// 4. secLinkPoll() — receive, decrypt if needed, deliver to callback
|
// 4. secLinkPoll() -- receive, decrypt if needed, deliver to callback
|
||||||
// 5. secLinkClose() — tear everything down
|
// 5. secLinkClose() -- tear everything down
|
||||||
//
|
//
|
||||||
// Channel multiplexing:
|
// Channel multiplexing:
|
||||||
// Each packet carries a one-byte header: bit 7 = encrypted flag,
|
// Each packet carries a one-byte header: bit 7 = encrypted flag,
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
// Channel limits
|
// Channel limits
|
||||||
#define SECLINK_MAX_CHANNEL 127
|
#define SECLINK_MAX_CHANNEL 127
|
||||||
|
|
||||||
// Receive callback — delivers plaintext with channel number
|
// Receive callback -- delivers plaintext with channel number
|
||||||
typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
||||||
|
|
||||||
// Opaque handle
|
// Opaque handle
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Security — DH Key Exchange and XTEA-CTR Cipher
|
# Security -- DH Key Exchange and XTEA-CTR Cipher
|
||||||
|
|
||||||
Cryptographic library providing Diffie-Hellman key exchange and XTEA
|
Cryptographic library providing Diffie-Hellman key exchange and XTEA
|
||||||
symmetric encryption, optimized for 486-class DOS hardware running
|
symmetric encryption, optimized for 486-class DOS hardware running
|
||||||
|
|
@ -16,8 +16,8 @@ under DJGPP/DPMI.
|
||||||
### XTEA Cipher (CTR Mode)
|
### XTEA Cipher (CTR Mode)
|
||||||
|
|
||||||
- 128-bit key, 64-bit block size, 32 rounds
|
- 128-bit key, 64-bit block size, 32 rounds
|
||||||
- CTR mode — encrypt and decrypt are the same XOR operation
|
- CTR mode -- encrypt and decrypt are the same XOR operation
|
||||||
- No lookup tables, no key schedule — just shifts, adds, and XORs
|
- No lookup tables, no key schedule -- just shifts, adds, and XORs
|
||||||
- Ideal for constrained environments with small key setup cost
|
- Ideal for constrained environments with small key setup cost
|
||||||
|
|
||||||
### Pseudo-Random Number Generator
|
### Pseudo-Random Number Generator
|
||||||
|
|
@ -148,7 +148,7 @@ starts at zero.
|
||||||
void secCipherCrypt(SecCipherT *c, uint8_t *data, int len);
|
void secCipherCrypt(SecCipherT *c, uint8_t *data, int len);
|
||||||
```
|
```
|
||||||
|
|
||||||
Encrypts or decrypts `data` in place. CTR mode is symmetric — the
|
Encrypts or decrypts `data` in place. CTR mode is symmetric -- the
|
||||||
same operation encrypts and decrypts.
|
same operation encrypts and decrypts.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
|
|
@ -200,7 +200,7 @@ uint8_t message[] = "Secret message";
|
||||||
secCipherCrypt(cipher, message, sizeof(message));
|
secCipherCrypt(cipher, message, sizeof(message));
|
||||||
// message is now encrypted
|
// message is now encrypted
|
||||||
|
|
||||||
// Decrypt (same operation — CTR mode is symmetric)
|
// Decrypt (same operation -- CTR mode is symmetric)
|
||||||
// Reset counter first if using the same cipher context
|
// Reset counter first if using the same cipher context
|
||||||
secCipherSetNonce(cipher, 0, 0);
|
secCipherSetNonce(cipher, 0, 0);
|
||||||
secCipherCrypt(cipher, message, sizeof(message));
|
secCipherCrypt(cipher, message, sizeof(message));
|
||||||
|
|
@ -240,8 +240,8 @@ The CIOS (Coarsely Integrated Operand Scanning) variant computes
|
||||||
`a * b * R^-1 mod m` in a single pass with implicit division by the
|
`a * b * R^-1 mod m` in a single pass with implicit division by the
|
||||||
word base. Constants are computed once on first DH use:
|
word base. Constants are computed once on first DH use:
|
||||||
|
|
||||||
- `R^2 mod p` — via 2048 iterations of shift-and-conditional-subtract
|
- `R^2 mod p` -- via 2048 iterations of shift-and-conditional-subtract
|
||||||
- `-p[0]^-1 mod 2^32` — via Newton's method (5 iterations)
|
- `-p[0]^-1 mod 2^32` -- via Newton's method (5 iterations)
|
||||||
|
|
||||||
### Secure Zeroing
|
### Secure Zeroing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
// are 256 bits for fast computation on 486-class hardware.
|
// are 256 bits for fast computation on 486-class hardware.
|
||||||
//
|
//
|
||||||
// XTEA in CTR mode provides symmetric encryption. No lookup tables,
|
// XTEA in CTR mode provides symmetric encryption. No lookup tables,
|
||||||
// no key schedule — just shifts, adds, and XORs.
|
// no key schedule -- just shifts, adds, and XORs.
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
@ -393,7 +393,7 @@ static void dhInit(void) {
|
||||||
|
|
||||||
|
|
||||||
// Volatile pointer prevents the compiler from optimizing away the zeroing
|
// Volatile pointer prevents the compiler from optimizing away the zeroing
|
||||||
// as a dead store. Critical for clearing key material — without volatile,
|
// as a dead store. Critical for clearing key material -- without volatile,
|
||||||
// the compiler sees that ptr is about to be freed and removes the memset.
|
// the compiler sees that ptr is about to be freed and removes the memset.
|
||||||
static void secureZero(void *ptr, int len) {
|
static void secureZero(void *ptr, int len) {
|
||||||
volatile uint8_t *p = (volatile uint8_t *)ptr;
|
volatile uint8_t *p = (volatile uint8_t *)ptr;
|
||||||
|
|
@ -406,7 +406,7 @@ static void secureZero(void *ptr, int len) {
|
||||||
|
|
||||||
// XTEA block cipher: encrypts an 8-byte block in-place. The Feistel network
|
// XTEA block cipher: encrypts an 8-byte block in-place. The Feistel network
|
||||||
// uses 32 rounds (vs TEA's 32 or 64). Each round mixes the halves using
|
// uses 32 rounds (vs TEA's 32 or 64). Each round mixes the halves using
|
||||||
// shifts, adds, and XORs — no S-boxes, no lookup tables, no key schedule.
|
// shifts, adds, and XORs -- no S-boxes, no lookup tables, no key schedule.
|
||||||
// The delta constant (golden ratio * 2^32) ensures each round uses a
|
// The delta constant (golden ratio * 2^32) ensures each round uses a
|
||||||
// different effective key, preventing slide attacks.
|
// different effective key, preventing slide attacks.
|
||||||
static void xteaEncryptBlock(uint32_t v[2], const uint32_t key[4]) {
|
static void xteaEncryptBlock(uint32_t v[2], const uint32_t key[4]) {
|
||||||
|
|
@ -494,7 +494,7 @@ void secRngBytes(uint8_t *buf, int len) {
|
||||||
// and provide ~10 bits of entropy per read (depending on timing jitter).
|
// and provide ~10 bits of entropy per read (depending on timing jitter).
|
||||||
// The BIOS tick at 18.2 Hz adds a few more bits. Two PIT readings with
|
// The BIOS tick at 18.2 Hz adds a few more bits. Two PIT readings with
|
||||||
// the intervening code execution provide some jitter. Total: roughly 20
|
// the intervening code execution provide some jitter. Total: roughly 20
|
||||||
// bits of real entropy — not enough alone, but sufficient to seed the DRBG
|
// bits of real entropy -- not enough alone, but sufficient to seed the DRBG
|
||||||
// when supplemented by user interaction timing.
|
// when supplemented by user interaction timing.
|
||||||
int secRngGatherEntropy(uint8_t *buf, int len) {
|
int secRngGatherEntropy(uint8_t *buf, int len) {
|
||||||
int out = 0;
|
int out = 0;
|
||||||
|
|
@ -524,7 +524,7 @@ int secRngGatherEntropy(uint8_t *buf, int len) {
|
||||||
|
|
||||||
|
|
||||||
// Initialize the RNG from entropy. The 64-byte discard at the end is
|
// Initialize the RNG from entropy. The 64-byte discard at the end is
|
||||||
// standard DRBG practice — it advances the state past any weak initial
|
// standard DRBG practice -- it advances the state past any weak initial
|
||||||
// output that might leak information about the seed material.
|
// output that might leak information about the seed material.
|
||||||
void secRngSeed(const uint8_t *entropy, int len) {
|
void secRngSeed(const uint8_t *entropy, int len) {
|
||||||
memset(&sRng, 0, sizeof(sRng));
|
memset(&sRng, 0, sizeof(sRng));
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
// ~20 instructions per round (shifts, adds, XORs). This makes it ideal
|
// ~20 instructions per round (shifts, adds, XORs). This makes it ideal
|
||||||
// for a 486 where cache is tiny (8KB) and AES's 4KB S-boxes would
|
// for a 486 where cache is tiny (8KB) and AES's 4KB S-boxes would
|
||||||
// thrash it. DES is similarly table-heavy and also has a complex key
|
// thrash it. DES is similarly table-heavy and also has a complex key
|
||||||
// schedule. XTEA has no library dependencies — the entire cipher fits
|
// schedule. XTEA has no library dependencies -- the entire cipher fits
|
||||||
// in a dozen lines of C. At 32 rounds, XTEA provides 128-bit security
|
// in a dozen lines of C. At 32 rounds, XTEA provides 128-bit security
|
||||||
// with negligible per-byte cost on even the slowest target hardware.
|
// with negligible per-byte cost on even the slowest target hardware.
|
||||||
//
|
//
|
||||||
|
|
@ -46,7 +46,7 @@ typedef struct SecDhS SecDhT;
|
||||||
typedef struct SecCipherS SecCipherT;
|
typedef struct SecCipherS SecCipherT;
|
||||||
|
|
||||||
|
|
||||||
// RNG — seed before generating keys. Hardware entropy is weak (~20 bits);
|
// RNG -- seed before generating keys. Hardware entropy is weak (~20 bits);
|
||||||
// callers should supplement with keyboard timing, mouse jitter, etc.
|
// callers should supplement with keyboard timing, mouse jitter, etc.
|
||||||
int secRngGatherEntropy(uint8_t *buf, int len);
|
int secRngGatherEntropy(uint8_t *buf, int len);
|
||||||
void secRngAddEntropy(const uint8_t *data, int len);
|
void secRngAddEntropy(const uint8_t *data, int len);
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ int main(void) {
|
||||||
// After all tasks from phases 1-4 have terminated, their slots are free.
|
// After all tasks from phases 1-4 have terminated, their slots are free.
|
||||||
// Creating new tasks should recycle those slots (IDs 1-4) rather than
|
// Creating new tasks should recycle those slots (IDs 1-4) rather than
|
||||||
// growing the array. Each "wave" creates 3 tasks, lets them finish,
|
// growing the array. Each "wave" creates 3 tasks, lets them finish,
|
||||||
// then creates 3 more — the IDs should repeat across waves.
|
// then creates 3 more -- the IDs should repeat across waves.
|
||||||
printf("\n--- Phase 5: Slot reuse ---\n");
|
printf("\n--- Phase 5: Slot reuse ---\n");
|
||||||
printf("[main] active before: %u\n", (unsigned)tsActiveCount());
|
printf("[main] active before: %u\n", (unsigned)tsActiveCount());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
// Why inline asm instead of setjmp/longjmp for context switching:
|
// Why inline asm instead of setjmp/longjmp for context switching:
|
||||||
// setjmp/longjmp only save callee-saved registers and don't give us
|
// setjmp/longjmp only save callee-saved registers and don't give us
|
||||||
// control over the stack pointer in a portable way. We need to set up
|
// control over the stack pointer in a portable way. We need to set up
|
||||||
// a brand-new stack for each task and jump into a trampoline — setjmp
|
// a brand-new stack for each task and jump into a trampoline -- setjmp
|
||||||
// can't bootstrap a fresh stack. The asm approach also avoids ABI
|
// can't bootstrap a fresh stack. The asm approach also avoids ABI
|
||||||
// differences in jmp_buf layout across DJGPP versions.
|
// differences in jmp_buf layout across DJGPP versions.
|
||||||
//
|
//
|
||||||
|
|
@ -65,10 +65,10 @@ typedef struct {
|
||||||
} TaskContextT;
|
} TaskContextT;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Task control block — one per task slot. The 'allocated' flag tracks
|
// Task control block -- one per task slot. The 'allocated' flag tracks
|
||||||
// whether the slot is live or recyclable, separate from the state enum,
|
// whether the slot is live or recyclable, separate from the state enum,
|
||||||
// because we need to distinguish "never used" from "terminated and reaped".
|
// because we need to distinguish "never used" from "terminated and reaped".
|
||||||
// The 'isMain' flag protects task 0 from kill/pause — destroying the
|
// The 'isMain' flag protects task 0 from kill/pause -- destroying the
|
||||||
// main task would orphan all other tasks with no scheduler to resume them.
|
// main task would orphan all other tasks with no scheduler to resume them.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char name[TS_NAME_MAX];
|
char name[TS_NAME_MAX];
|
||||||
|
|
@ -167,7 +167,7 @@ static void __attribute__((noinline)) contextSwitch(TaskContextT *save, TaskCont
|
||||||
// RIP-relative lea captures the resume point address
|
// RIP-relative lea captures the resume point address
|
||||||
"leaq 1f(%%rip), %%rax\n\t"
|
"leaq 1f(%%rip), %%rax\n\t"
|
||||||
"movq %%rax, 56(%%rdi)\n\t"
|
"movq %%rax, 56(%%rdi)\n\t"
|
||||||
// Restore new context — once rsp is swapped we're on the other
|
// Restore new context -- once rsp is swapped we're on the other
|
||||||
// task's stack. The jmp completes the switch.
|
// task's stack. The jmp completes the switch.
|
||||||
"movq 0(%%rsi), %%rbx\n\t"
|
"movq 0(%%rsi), %%rbx\n\t"
|
||||||
"movq 8(%%rsi), %%r12\n\t"
|
"movq 8(%%rsi), %%r12\n\t"
|
||||||
|
|
@ -219,7 +219,7 @@ static void __attribute__((noinline)) contextSwitch(TaskContextT *save, TaskCont
|
||||||
// Find a free (terminated or unallocated) slot in the task array.
|
// Find a free (terminated or unallocated) slot in the task array.
|
||||||
// Returns the index, or -1 if no free slot exists.
|
// Returns the index, or -1 if no free slot exists.
|
||||||
// Starts at 1 because slot 0 is always the main task and cannot be reused.
|
// Starts at 1 because slot 0 is always the main task and cannot be reused.
|
||||||
// Linear scan is fine — SHELL_MAX_APPS caps the practical limit at ~32 tasks.
|
// Linear scan is fine -- SHELL_MAX_APPS caps the practical limit at ~32 tasks.
|
||||||
static int32_t findFreeSlot(void) {
|
static int32_t findFreeSlot(void) {
|
||||||
ptrdiff_t count = arrlen(tasks);
|
ptrdiff_t count = arrlen(tasks);
|
||||||
for (ptrdiff_t i = 1; i < count; i++) {
|
for (ptrdiff_t i = 1; i < count; i++) {
|
||||||
|
|
@ -244,7 +244,7 @@ static int32_t findFreeSlot(void) {
|
||||||
// 4. Scan again after refill
|
// 4. Scan again after refill
|
||||||
//
|
//
|
||||||
// The round-robin scan starts at (currentIdx + 1) and wraps, ensuring
|
// The round-robin scan starts at (currentIdx + 1) and wraps, ensuring
|
||||||
// fairness among tasks with equal priority — no task gets picked twice
|
// fairness among tasks with equal priority -- no task gets picked twice
|
||||||
// in a row unless it's the only ready task.
|
// in a row unless it's the only ready task.
|
||||||
//
|
//
|
||||||
// If no ready tasks exist at all (everything paused/terminated), return
|
// If no ready tasks exist at all (everything paused/terminated), return
|
||||||
|
|
@ -262,7 +262,7 @@ static uint32_t scheduleNext(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All credits exhausted — start a new epoch by refilling every ready task
|
// All credits exhausted -- start a new epoch by refilling every ready task
|
||||||
bool anyReady = false;
|
bool anyReady = false;
|
||||||
for (uint32_t i = 0; i < count; i++) {
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
if (tasks[i].allocated && tasks[i].state == TaskStateReady) {
|
if (tasks[i].allocated && tasks[i].state == TaskStateReady) {
|
||||||
|
|
@ -291,7 +291,7 @@ static uint32_t scheduleNext(void) {
|
||||||
// Entry point for every new task. The first context switch into a new task
|
// Entry point for every new task. The first context switch into a new task
|
||||||
// jumps here (via the EIP/RIP set up in tsCreate). This is a trampoline
|
// jumps here (via the EIP/RIP set up in tsCreate). This is a trampoline
|
||||||
// rather than calling entry directly because we need to call tsExit() when
|
// rather than calling entry directly because we need to call tsExit() when
|
||||||
// the entry function returns — if we just set EIP to the entry function,
|
// the entry function returns -- if we just set EIP to the entry function,
|
||||||
// it would return to a garbage address (the dummy 0 on the stack).
|
// it would return to a garbage address (the dummy 0 on the stack).
|
||||||
// The trampoline ensures clean task termination even if the app forgets
|
// The trampoline ensures clean task termination even if the app forgets
|
||||||
// to call tsExit() explicitly.
|
// to call tsExit() explicitly.
|
||||||
|
|
@ -364,7 +364,7 @@ int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t stackSi
|
||||||
// Set up initial stack (grows downward, 16-byte aligned).
|
// Set up initial stack (grows downward, 16-byte aligned).
|
||||||
// The ABI requires 16-byte stack alignment at function entry. We align
|
// The ABI requires 16-byte stack alignment at function entry. We align
|
||||||
// the top, then push a dummy return address (0) to simulate a CALL
|
// the top, then push a dummy return address (0) to simulate a CALL
|
||||||
// instruction — this keeps the stack aligned for the trampoline.
|
// instruction -- this keeps the stack aligned for the trampoline.
|
||||||
// The dummy address is never used because taskTrampoline calls tsExit()
|
// The dummy address is never used because taskTrampoline calls tsExit()
|
||||||
// which switches away without returning, but it satisfies debuggers
|
// which switches away without returning, but it satisfies debuggers
|
||||||
// and ABI checkers that expect a return address at the bottom of each frame.
|
// and ABI checkers that expect a return address at the bottom of each frame.
|
||||||
|
|
@ -401,7 +401,7 @@ uint32_t tsCurrentId(void) {
|
||||||
|
|
||||||
|
|
||||||
// Self-termination. Frees resources and switches to the next task.
|
// Self-termination. Frees resources and switches to the next task.
|
||||||
// This function never returns — the terminated task's context is abandoned.
|
// This function never returns -- the terminated task's context is abandoned.
|
||||||
// We save to tasks[prev].context even though we'll never restore it because
|
// We save to tasks[prev].context even though we'll never restore it because
|
||||||
// contextSwitch always writes to the save pointer; the data is harmless
|
// contextSwitch always writes to the save pointer; the data is harmless
|
||||||
// and will be overwritten when the slot is recycled.
|
// and will be overwritten when the slot is recycled.
|
||||||
|
|
@ -412,7 +412,7 @@ void tsExit(void) {
|
||||||
|
|
||||||
tasks[currentIdx].state = TaskStateTerminated;
|
tasks[currentIdx].state = TaskStateTerminated;
|
||||||
|
|
||||||
// Free the stack immediately — safe because we're about to switch
|
// Free the stack immediately -- safe because we're about to switch
|
||||||
// away and never return. The context switch itself doesn't touch
|
// away and never return. The context switch itself doesn't touch
|
||||||
// the old stack after swapping ESP/RSP.
|
// the old stack after swapping ESP/RSP.
|
||||||
free(tasks[currentIdx].stack);
|
free(tasks[currentIdx].stack);
|
||||||
|
|
@ -465,7 +465,7 @@ TaskStateE tsGetState(uint32_t taskId) {
|
||||||
|
|
||||||
// Register the calling context as task 0 (main). No stack is allocated
|
// Register the calling context as task 0 (main). No stack is allocated
|
||||||
// because the main task uses the process stack. The main task's context
|
// because the main task uses the process stack. The main task's context
|
||||||
// struct is filled in lazily by contextSwitch on the first tsYield() —
|
// struct is filled in lazily by contextSwitch on the first tsYield() --
|
||||||
// until then, the saved EIP/ESP are zero, which is fine because we
|
// until then, the saved EIP/ESP are zero, which is fine because we
|
||||||
// never restore task 0 from a cold start.
|
// never restore task 0 from a cold start.
|
||||||
int32_t tsInit(void) {
|
int32_t tsInit(void) {
|
||||||
|
|
@ -492,11 +492,11 @@ int32_t tsInit(void) {
|
||||||
|
|
||||||
|
|
||||||
// Forcibly terminate another task. This is safe in a cooperative system
|
// Forcibly terminate another task. This is safe in a cooperative system
|
||||||
// because the target is guaranteed to be suspended at a yield point — it
|
// because the target is guaranteed to be suspended at a yield point -- it
|
||||||
// cannot be in the middle of a critical section. The stack is freed and
|
// cannot be in the middle of a critical section. The stack is freed and
|
||||||
// the slot is recycled immediately.
|
// the slot is recycled immediately.
|
||||||
//
|
//
|
||||||
// Cannot kill self (use tsExit instead) — killing self would free the
|
// Cannot kill self (use tsExit instead) -- killing self would free the
|
||||||
// stack we're currently executing on. Cannot kill main (task 0) because
|
// stack we're currently executing on. Cannot kill main (task 0) because
|
||||||
// the shell's main loop must always be runnable for crash recovery.
|
// the shell's main loop must always be runnable for crash recovery.
|
||||||
//
|
//
|
||||||
|
|
@ -546,7 +546,7 @@ int32_t tsPause(uint32_t taskId) {
|
||||||
|
|
||||||
tasks[taskId].state = TaskStatePaused;
|
tasks[taskId].state = TaskStatePaused;
|
||||||
|
|
||||||
// If we paused ourselves, must yield immediately — a paused task
|
// If we paused ourselves, must yield immediately -- a paused task
|
||||||
// won't be selected by scheduleNext, so staying on CPU would deadlock.
|
// won't be selected by scheduleNext, so staying on CPU would deadlock.
|
||||||
// If pausing another task, no yield needed; it will simply be skipped
|
// If pausing another task, no yield needed; it will simply be skipped
|
||||||
// the next time the scheduler scans.
|
// the next time the scheduler scans.
|
||||||
|
|
@ -574,7 +574,7 @@ int32_t tsPause(uint32_t taskId) {
|
||||||
// app task. This function fixes the bookkeeping so the scheduler treats
|
// app task. This function fixes the bookkeeping so the scheduler treats
|
||||||
// task 0 as the running task again.
|
// task 0 as the running task again.
|
||||||
//
|
//
|
||||||
// The crashed task's slot is NOT freed here — its stack is corrupt and
|
// The crashed task's slot is NOT freed here -- its stack is corrupt and
|
||||||
// the caller (shellMain's crash recovery) must call shellForceKillApp
|
// the caller (shellMain's crash recovery) must call shellForceKillApp
|
||||||
// to clean it up properly (destroying windows, closing DXE, etc.).
|
// to clean it up properly (destroying windows, closing DXE, etc.).
|
||||||
void tsRecoverToMain(void) {
|
void tsRecoverToMain(void) {
|
||||||
|
|
@ -643,7 +643,7 @@ void tsShutdown(void) {
|
||||||
|
|
||||||
// The core cooperative yield. Called explicitly by app code (or implicitly
|
// The core cooperative yield. Called explicitly by app code (or implicitly
|
||||||
// via the shell's idle callback and main loop). If no other task is ready,
|
// via the shell's idle callback and main loop). If no other task is ready,
|
||||||
// returns immediately — no context switch overhead when running solo.
|
// returns immediately -- no context switch overhead when running solo.
|
||||||
//
|
//
|
||||||
// The state transition: current task moves Running -> Ready (still
|
// The state transition: current task moves Running -> Ready (still
|
||||||
// schedulable), next task moves Ready -> Running. The previous task will
|
// schedulable), next task moves Ready -> Running. The previous task will
|
||||||
|
|
@ -660,7 +660,7 @@ void tsYield(void) {
|
||||||
|
|
||||||
uint32_t prev = currentIdx;
|
uint32_t prev = currentIdx;
|
||||||
|
|
||||||
// Only transition to Ready if still Running — a task that paused itself
|
// Only transition to Ready if still Running -- a task that paused itself
|
||||||
// will already be in Paused state when tsYield is called from tsPause.
|
// will already be in Paused state when tsYield is called from tsPause.
|
||||||
if (tasks[prev].state == TaskStateRunning) {
|
if (tasks[prev].state == TaskStateRunning) {
|
||||||
tasks[prev].state = TaskStateReady;
|
tasks[prev].state = TaskStateReady;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
// Cooperative (non-preemptive) multitasking for DOS protected mode (DJGPP/DPMI).
|
// Cooperative (non-preemptive) multitasking for DOS protected mode (DJGPP/DPMI).
|
||||||
//
|
//
|
||||||
// Why cooperative instead of preemptive:
|
// Why cooperative instead of preemptive:
|
||||||
// 1. DOS is single-threaded — there is no kernel scheduler to preempt us.
|
// 1. DOS is single-threaded -- there is no kernel scheduler to preempt us.
|
||||||
// 2. DPMI provides no thread or timer-based preemption primitives.
|
// 2. DPMI provides no thread or timer-based preemption primitives.
|
||||||
// 3. The DVX GUI event model is inherently single-threaded: one compositor,
|
// 3. The DVX GUI event model is inherently single-threaded: one compositor,
|
||||||
// one input queue, one window stack. Preemption would require locking
|
// one input queue, one window stack. Preemption would require locking
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
//
|
//
|
||||||
// This is a weighted fair-share scheduler: a priority-10 task gets 11
|
// This is a weighted fair-share scheduler: a priority-10 task gets 11
|
||||||
// turns per round while a priority-0 task gets 1, but the low-priority
|
// turns per round while a priority-0 task gets 1, but the low-priority
|
||||||
// task is guaranteed to run eventually — no starvation is possible.
|
// task is guaranteed to run eventually -- no starvation is possible.
|
||||||
|
|
||||||
#ifndef TASKSWITCH_H
|
#ifndef TASKSWITCH_H
|
||||||
#define TASKSWITCH_H
|
#define TASKSWITCH_H
|
||||||
|
|
@ -35,20 +35,20 @@
|
||||||
#define TS_ERR_NOMEM (-4)
|
#define TS_ERR_NOMEM (-4)
|
||||||
#define TS_ERR_STATE (-5)
|
#define TS_ERR_STATE (-5)
|
||||||
|
|
||||||
// 32KB default stack — generous for callback-style app tasks that spend
|
// 32KB default stack -- generous for callback-style app tasks that spend
|
||||||
// most of their time in the shell's GUI code. Apps needing deeper recursion
|
// most of their time in the shell's GUI code. Apps needing deeper recursion
|
||||||
// or large stack allocations can request more via AppDescriptorT.stackSize.
|
// or large stack allocations can request more via AppDescriptorT.stackSize.
|
||||||
#define TS_DEFAULT_STACK_SIZE 32768
|
#define TS_DEFAULT_STACK_SIZE 32768
|
||||||
#define TS_NAME_MAX 32
|
#define TS_NAME_MAX 32
|
||||||
|
|
||||||
// Priority levels — the credit count is (priority + 1), so LOW gets 1 turn
|
// Priority levels -- the credit count is (priority + 1), so LOW gets 1 turn
|
||||||
// per round, NORMAL gets 6, HIGH gets 11. The shell's main task runs at
|
// per round, NORMAL gets 6, HIGH gets 11. The shell's main task runs at
|
||||||
// HIGH to keep UI responsive; app tasks default to NORMAL.
|
// HIGH to keep UI responsive; app tasks default to NORMAL.
|
||||||
#define TS_PRIORITY_LOW 0
|
#define TS_PRIORITY_LOW 0
|
||||||
#define TS_PRIORITY_NORMAL 5
|
#define TS_PRIORITY_NORMAL 5
|
||||||
#define TS_PRIORITY_HIGH 10
|
#define TS_PRIORITY_HIGH 10
|
||||||
|
|
||||||
// Task states — only Ready tasks participate in scheduling. The Running
|
// Task states -- only Ready tasks participate in scheduling. The Running
|
||||||
// state is cosmetic (marks the currently executing task). Paused tasks
|
// state is cosmetic (marks the currently executing task). Paused tasks
|
||||||
// are skipped until explicitly resumed. Terminated slots are recycled.
|
// are skipped until explicitly resumed. Terminated slots are recycled.
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|
@ -58,14 +58,14 @@ typedef enum {
|
||||||
TaskStateTerminated = 3
|
TaskStateTerminated = 3
|
||||||
} TaskStateE;
|
} TaskStateE;
|
||||||
|
|
||||||
// Task entry function signature — the void* arg lets the caller pass
|
// Task entry function signature -- the void* arg lets the caller pass
|
||||||
// arbitrary context (e.g., a ShellAppT* for the DVX shell's app wrapper)
|
// arbitrary context (e.g., a ShellAppT* for the DVX shell's app wrapper)
|
||||||
typedef void (*TaskEntryT)(void *arg);
|
typedef void (*TaskEntryT)(void *arg);
|
||||||
|
|
||||||
// Initialize the task system. The calling context becomes task 0 (main).
|
// Initialize the task system. The calling context becomes task 0 (main).
|
||||||
// Task 0 is special: it cannot be killed or paused, and the crash recovery
|
// Task 0 is special: it cannot be killed or paused, and the crash recovery
|
||||||
// path (tsRecoverToMain) always returns control here. No separate stack is
|
// path (tsRecoverToMain) always returns control here. No separate stack is
|
||||||
// allocated for task 0 — it uses the process stack directly.
|
// allocated for task 0 -- it uses the process stack directly.
|
||||||
int32_t tsInit(void);
|
int32_t tsInit(void);
|
||||||
|
|
||||||
// Shut down the task system and free all resources.
|
// Shut down the task system and free all resources.
|
||||||
|
|
@ -77,7 +77,7 @@ void tsShutdown(void);
|
||||||
int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t stackSize, int32_t priority);
|
int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t stackSize, int32_t priority);
|
||||||
|
|
||||||
// Yield CPU to the next eligible ready task (credit-based round-robin).
|
// Yield CPU to the next eligible ready task (credit-based round-robin).
|
||||||
// This is the sole mechanism for task switching — every app must call
|
// This is the sole mechanism for task switching -- every app must call
|
||||||
// this (or a GUI function that calls it) periodically, or it will
|
// this (or a GUI function that calls it) periodically, or it will
|
||||||
// monopolize the CPU.
|
// monopolize the CPU.
|
||||||
void tsYield(void);
|
void tsYield(void);
|
||||||
|
|
@ -108,7 +108,7 @@ const char *tsGetName(uint32_t taskId);
|
||||||
|
|
||||||
// Terminate the calling task. Must not be called from the main task.
|
// Terminate the calling task. Must not be called from the main task.
|
||||||
// The stack is freed immediately and the slot is marked for reuse.
|
// The stack is freed immediately and the slot is marked for reuse.
|
||||||
// Internally performs a context switch to the next ready task — this
|
// Internally performs a context switch to the next ready task -- this
|
||||||
// function never returns to the caller.
|
// function never returns to the caller.
|
||||||
void tsExit(void);
|
void tsExit(void);
|
||||||
|
|
||||||
|
|
@ -121,7 +121,7 @@ int32_t tsKill(uint32_t taskId);
|
||||||
|
|
||||||
// Crash recovery: force scheduler back to main task (id 0).
|
// Crash recovery: force scheduler back to main task (id 0).
|
||||||
// Call after longjmp from a signal handler that fired in a non-main task.
|
// Call after longjmp from a signal handler that fired in a non-main task.
|
||||||
// The crashed task is NOT cleaned up — call tsKill() afterward.
|
// The crashed task is NOT cleaned up -- call tsKill() afterward.
|
||||||
// This exists because longjmp unwinds the crashed task's stack but the
|
// This exists because longjmp unwinds the crashed task's stack but the
|
||||||
// scheduler's currentIdx still points to it. We must fix the bookkeeping
|
// scheduler's currentIdx still points to it. We must fix the bookkeeping
|
||||||
// before doing anything else.
|
// before doing anything else.
|
||||||
|
|
|
||||||
|
|
@ -67,18 +67,18 @@ DOS console shows progress messages during connection setup.
|
||||||
|
|
||||||
Each iteration:
|
Each iteration:
|
||||||
|
|
||||||
1. `dvxUpdate()` — process mouse, keyboard, paint, and window events
|
1. `dvxUpdate()` -- process mouse, keyboard, paint, and window events
|
||||||
2. `secLinkPoll()` — read serial data, decrypt, deliver to ring buffer
|
2. `secLinkPoll()` -- read serial data, decrypt, deliver to ring buffer
|
||||||
3. `wgtAnsiTermPoll()` — drain ring buffer into the ANSI parser
|
3. `wgtAnsiTermPoll()` -- drain ring buffer into the ANSI parser
|
||||||
|
|
||||||
## Data Flow
|
## Data Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
BBS → proxy → serial → secLinkPoll() → onRecv() → ring buffer
|
BBS -> proxy -> serial -> secLinkPoll() -> onRecv() -> ring buffer
|
||||||
→ commRead() → wgtAnsiTermWrite() → ANSI parser → screen
|
-> commRead() -> wgtAnsiTermWrite() -> ANSI parser -> screen
|
||||||
|
|
||||||
Keyboard → widgetAnsiTermOnKey() → commWrite()
|
Keyboard -> widgetAnsiTermOnKey() -> commWrite()
|
||||||
→ secLinkSend() → serial → proxy → BBS
|
-> secLinkSend() -> serial -> proxy -> BBS
|
||||||
```
|
```
|
||||||
|
|
||||||
A 4KB ring buffer bridges the SecLink receive callback (which fires
|
A 4KB ring buffer bridges the SecLink receive callback (which fires
|
||||||
|
|
@ -88,7 +88,7 @@ during `secLinkPoll()`) and the terminal widget's comm read interface
|
||||||
## GUI
|
## GUI
|
||||||
|
|
||||||
- **Window**: resizable, titled "SecLink Terminal"
|
- **Window**: resizable, titled "SecLink Terminal"
|
||||||
- **Menu bar**: File → Quit
|
- **Menu bar**: File -> Quit
|
||||||
- **Terminal**: 80x25 ANSI terminal widget with 1000-line scrollback
|
- **Terminal**: 80x25 ANSI terminal widget with 1000-line scrollback
|
||||||
- **Status bar**: shows COM port, baud rate, and encryption status
|
- **Status bar**: shows COM port, baud rate, and encryption status
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// termdemo.c — SecLink terminal emulator demo
|
// termdemo.c -- SecLink terminal emulator demo
|
||||||
//
|
//
|
||||||
// A standalone DVX GUI application (NOT a DXE app — this has its own main())
|
// A standalone DVX GUI application (NOT a DXE app -- this has its own main())
|
||||||
// that combines the ANSI terminal widget with the SecLink encrypted serial
|
// that combines the ANSI terminal widget with the SecLink encrypted serial
|
||||||
// stack to create a BBS terminal client.
|
// stack to create a BBS terminal client.
|
||||||
//
|
//
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
// during its paint cycle.
|
// during its paint cycle.
|
||||||
//
|
//
|
||||||
// Usage: termdemo [com_port] [baud_rate]
|
// Usage: termdemo [com_port] [baud_rate]
|
||||||
// com_port — 1-4 (default 1)
|
// com_port -- 1-4 (default 1)
|
||||||
// baud_rate — baud rate (default 115200)
|
// baud_rate -- baud rate (default 115200)
|
||||||
|
|
||||||
#include "dvxApp.h"
|
#include "dvxApp.h"
|
||||||
#include "dvxWidget.h"
|
#include "dvxWidget.h"
|
||||||
|
|
@ -71,7 +71,7 @@ static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// commRead — drain receive ring buffer for terminal widget
|
// commRead -- drain receive ring buffer for terminal widget
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen) {
|
static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen) {
|
||||||
|
|
@ -88,7 +88,7 @@ static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// commWrite — send keystrokes via secLink (encrypted, channel 0)
|
// commWrite -- send keystrokes via secLink (encrypted, channel 0)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len) {
|
static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len) {
|
||||||
|
|
@ -100,7 +100,7 @@ static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// idlePoll — poll serial link instead of yielding CPU
|
// idlePoll -- poll serial link instead of yielding CPU
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void idlePoll(void *ctx) {
|
static void idlePoll(void *ctx) {
|
||||||
|
|
@ -110,7 +110,7 @@ static void idlePoll(void *ctx) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// onCloseCb — quit when the terminal window is closed
|
// onCloseCb -- quit when the terminal window is closed
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void onCloseCb(WindowT *win) {
|
static void onCloseCb(WindowT *win) {
|
||||||
|
|
@ -123,7 +123,7 @@ static void onCloseCb(WindowT *win) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// onMenuCb — handle menu commands
|
// onMenuCb -- handle menu commands
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void onMenuCb(WindowT *win, int32_t menuId) {
|
static void onMenuCb(WindowT *win, int32_t menuId) {
|
||||||
|
|
@ -140,7 +140,7 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// onRecv — secLink receive callback, fills ring buffer
|
// onRecv -- secLink receive callback, fills ring buffer
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel) {
|
static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel) {
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue