From 5a1332d024f3a67a2f46197fce050184353fef0b Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Fri, 20 Mar 2026 19:10:53 -0500 Subject: [PATCH] Unicode removed from source. Lots of bugs fixed. Close to Alpha 2! --- Makefile | 2 +- README.md | 24 +- apps/Makefile | 2 +- apps/clock/clock.c | 12 +- apps/cpanel/cpanel.c | 114 ++++-- apps/dvxdemo/dvxdemo.c | 18 +- apps/imgview/imgview.c | 69 +++- apps/notepad/notepad.c | 8 +- apps/progman/progman.c | 19 +- dvx/dvxApp.c | 637 ++++++++++++++++++++++---------- dvx/dvxApp.h | 36 +- dvx/dvxComp.c | 22 +- dvx/dvxComp.h | 6 +- dvx/dvxCursor.h | 87 +++-- dvx/dvxDialog.c | 62 ++-- dvx/dvxDialog.h | 2 +- dvx/dvxDraw.c | 46 +-- dvx/dvxDraw.h | 6 +- dvx/dvxFont.h | 8 +- dvx/dvxIcon.c | 4 +- dvx/dvxImageWrite.c | 4 +- dvx/dvxPalette.h | 4 +- dvx/dvxPrefs.c | 10 +- dvx/dvxPrefs.h | 2 +- dvx/dvxTypes.h | 35 +- dvx/dvxVideo.c | 10 +- dvx/dvxVideo.h | 4 +- dvx/dvxWidget.h | 32 +- dvx/dvxWm.c | 70 ++-- dvx/dvxWm.h | 12 +- dvx/platform/dvxPlatform.h | 27 +- dvx/platform/dvxPlatformDos.c | 116 ++++-- dvx/widgets/widgetAnsiTerm.c | 116 +++--- dvx/widgets/widgetBox.c | 18 +- dvx/widgets/widgetButton.c | 8 +- dvx/widgets/widgetCanvas.c | 24 +- dvx/widgets/widgetCheckbox.c | 4 +- dvx/widgets/widgetClass.c | 26 +- dvx/widgets/widgetComboBox.c | 12 +- dvx/widgets/widgetCore.c | 22 +- dvx/widgets/widgetDropdown.c | 8 +- dvx/widgets/widgetEvent.c | 34 +- dvx/widgets/widgetImage.c | 8 +- dvx/widgets/widgetImageButton.c | 6 +- dvx/widgets/widgetInternal.h | 36 +- dvx/widgets/widgetLabel.c | 29 +- dvx/widgets/widgetLayout.c | 6 +- dvx/widgets/widgetListBox.c | 12 +- dvx/widgets/widgetListView.c | 32 +- dvx/widgets/widgetOps.c | 20 +- dvx/widgets/widgetProgressBar.c | 2 +- dvx/widgets/widgetRadio.c | 8 +- dvx/widgets/widgetScrollPane.c | 14 +- dvx/widgets/widgetScrollbar.c | 27 +- dvx/widgets/widgetSeparator.c | 2 +- dvx/widgets/widgetSlider.c | 14 +- dvx/widgets/widgetSpacer.c | 2 +- dvx/widgets/widgetSpinner.c | 40 +- dvx/widgets/widgetSplitter.c | 14 +- dvx/widgets/widgetStatusBar.c | 2 +- dvx/widgets/widgetTabControl.c | 12 +- dvx/widgets/widgetTextInput.c | 72 ++-- dvx/widgets/widgetTimer.c | 6 +- dvx/widgets/widgetToolbar.c | 2 +- dvx/widgets/widgetTreeView.c | 63 +++- dvxshell/README.md | 8 +- dvxshell/shellApp.c | 71 +++- dvxshell/shellApp.h | 22 +- dvxshell/shellExport.c | 88 +++-- dvxshell/shellInfo.c | 6 +- dvxshell/shellInfo.h | 2 +- dvxshell/shellMain.c | 48 ++- dvxshell/shellTaskMgr.c | 4 +- dvxshell/shellTaskMgr.h | 2 +- mkcd.sh | 4 +- packet/README.md | 16 +- packet/packet.c | 18 +- packet/packet.h | 2 +- proxy/Makefile | 8 +- proxy/README.md | 6 +- proxy/proxy.c | 18 +- proxy/sockShim.c | 8 +- proxy/sockShim.h | 2 +- proxy/stubs/go32.h | 2 +- proxy/stubs/pc.h | 2 +- proxy/stubs/sys/farptr.h | 2 +- rs232/README.md | 24 +- rs232/rs232.c | 14 +- rs232/rs232.h | 4 +- seclink/README.md | 22 +- seclink/secLink.c | 10 +- seclink/secLink.h | 20 +- security/README.md | 14 +- security/security.c | 10 +- security/security.h | 4 +- tasks/demo.c | 2 +- tasks/taskswitch.c | 36 +- tasks/taskswitch.h | 20 +- termdemo/README.md | 16 +- termdemo/termdemo.c | 20 +- themes/cde.thm | 4 +- themes/geos.thm | 4 +- themes/win31.thm | 4 +- 103 files changed, 1682 insertions(+), 1096 deletions(-) diff --git a/Makefile b/Makefile index 5ee9df6..d78783d 100644 --- a/Makefile +++ b/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. diff --git a/README.md b/README.md index 22d8887..5e56f00 100644 --- a/README.md +++ b/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 windowed GUI compositor, cooperative task switcher, and DXE3 dynamic loading to @@ -103,20 +103,20 @@ mcopy -o -i bin/dvx.exe ::DVX.EXE mcopy -s -o -i 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. ## Documentation Each component directory has its own README with detailed API reference: -- [`dvx/README.md`](dvx/README.md) — GUI library architecture and API -- [`tasks/README.md`](tasks/README.md) — Task switcher API -- [`dvxshell/README.md`](dvxshell/README.md) — Shell internals and DXE app contract -- [`apps/README.md`](apps/README.md) — Writing DXE applications -- [`rs232/README.md`](rs232/README.md) — Serial port driver -- [`packet/README.md`](packet/README.md) — Packet transport protocol -- [`security/README.md`](security/README.md) — Cryptographic primitives -- [`seclink/README.md`](seclink/README.md) — Secure serial link -- [`proxy/README.md`](proxy/README.md) — Linux SecLink proxy -- [`termdemo/README.md`](termdemo/README.md) — Encrypted terminal demo +- [`dvx/README.md`](dvx/README.md) -- GUI library architecture and API +- [`tasks/README.md`](tasks/README.md) -- Task switcher API +- [`dvxshell/README.md`](dvxshell/README.md) -- Shell internals and DXE app contract +- [`apps/README.md`](apps/README.md) -- Writing DXE applications +- [`rs232/README.md`](rs232/README.md) -- Serial port driver +- [`packet/README.md`](packet/README.md) -- Packet transport protocol +- [`security/README.md`](security/README.md) -- Cryptographic primitives +- [`seclink/README.md`](seclink/README.md) -- Secure serial link +- [`proxy/README.md`](proxy/README.md) -- Linux SecLink proxy +- [`termdemo/README.md`](termdemo/README.md) -- Encrypted terminal demo diff --git a/apps/Makefile b/apps/Makefile index f8aefff..8ab719f 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -1,4 +1,4 @@ -# DVX Shell Applications Makefile — builds DXE3 modules +# DVX Shell Applications Makefile -- builds DXE3 modules DJGPP_PREFIX = $(HOME)/djgpp/djgpp DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib diff --git a/apps/clock/clock.c b/apps/clock/clock.c index 53b4b4b..d0e904f 100644 --- a/apps/clock/clock.c +++ b/apps/clock/clock.c @@ -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- // only apps like notepad or progman. The difference is fundamental: @@ -8,14 +8,14 @@ // 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 -// 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 // and schedules this task alongside the main event loop. // // 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 // 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. #include "dvxApp.h" @@ -87,7 +87,7 @@ static void onClose(WindowT *win) { // onPaint demonstrates the raw paint callback approach (no widget tree). // Instead of using wgtInitWindow + widgets, this app renders directly into // 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 // so the drawing primitives (rectFill, drawText) can operate on the window @@ -197,13 +197,13 @@ int32_t appMain(DxeAppContextT *ctx) { sWin->onClose = onClose; sWin->onPaint = onPaint; - // Initial paint — dvxInvalidateWindow calls onPaint automatically + // Initial paint -- dvxInvalidateWindow calls onPaint automatically dvxInvalidateWindow(ac, sWin); // Main loop: check if the second has changed, repaint if so, then yield. // tsYield() transfers control back to the shell's task scheduler. // 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 // update the content buffer before the compositor flushes it. while (!sState.quit) { diff --git a/apps/cpanel/cpanel.c b/apps/cpanel/cpanel.c index 8464888..c84976a 100644 --- a/apps/cpanel/cpanel.c +++ b/apps/cpanel/cpanel.c @@ -1,10 +1,10 @@ -// ctrlpanel.c — DVX Control Panel +// ctrlpanel.c -- DVX Control Panel // // System configuration app with four tabs: -// Mouse — scroll direction, double-click speed, acceleration -// Colors — all 18 system colors, theme load/save -// Desktop — wallpaper image (stretch mode) -// Video — resolution and color depth +// Mouse -- scroll direction, double-click speed, acceleration +// Colors -- all 18 system colors, theme load/save +// Desktop -- wallpaper image (stretch mode) +// Video -- resolution and color depth // // Changes preview live. OK saves to DVX.INI, Cancel reverts to the // state captured when the control panel was opened. @@ -71,8 +71,8 @@ static int32_t sSavedDblClick; static int32_t sSavedAccel; static int32_t sSavedVideoW; static int32_t sSavedVideoH; -static int32_t sSavedVideoBpp; -static bool sSavedHadWallpaper; +static int32_t sSavedVideoBpp; +static WallpaperModeE sSavedWpMode; // Mouse tab widgets static WidgetT *sWheelDrop = NULL; @@ -94,6 +94,7 @@ static WidgetT *sColorSwatch = NULL; // Desktop tab widgets static WidgetT *sWallpaperLbl = NULL; static WidgetT *sWpaperList = NULL; +static WidgetT *sWpModeDrop = NULL; static char sWallpaperPath[260]; static FileEntryT *sWpaperEntries = 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 onResetColors(WidgetT *w); static void onApplyWallpaper(WidgetT *w); +static void onWallpaperMode(WidgetT *w); static void onCancel(WidgetT *w); static void onChooseWallpaper(WidgetT *w); static void onClearWallpaper(WidgetT *w); @@ -167,7 +169,7 @@ static void buildColorsTab(WidgetT *page) { static const char *colorNames[ColorCountE]; for (int32_t i = 0; i < ColorCountE; i++) { - colorNames[i] = dvxColorName((ColorIdE)i); + colorNames[i] = dvxColorLabel((ColorIdE)i); } sColorList = wgtListBox(leftVbox); @@ -203,28 +205,37 @@ static void buildColorsTab(WidgetT *page) { resetBtn->onClick = onResetColors; 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); rightVbox->weight = 50; - rightVbox->spacing = wgtPixels(4); - wgtLabel(rightVbox, "Red:"); - sRedSldr = wgtSlider(rightVbox, 0, 255); + WidgetT *sliderBox = wgtVBox(rightVbox); + sliderBox->spacing = wgtPixels(4); + + wgtLabel(sliderBox, "Red:"); + sRedSldr = wgtSlider(sliderBox, 0, 255); sRedSldr->onChange = onColorSlider; - sRedLbl = wgtLabel(rightVbox, "0"); + sRedLbl = wgtLabel(sliderBox, "0"); + wgtLabelSetAlign(sRedLbl, AlignEndE); - wgtLabel(rightVbox, "Green:"); - sGreenSldr = wgtSlider(rightVbox, 0, 255); + wgtLabel(sliderBox, "Green:"); + sGreenSldr = wgtSlider(sliderBox, 0, 255); sGreenSldr->onChange = onColorSlider; - sGreenLbl = wgtLabel(rightVbox, "0"); + sGreenLbl = wgtLabel(sliderBox, "0"); + wgtLabelSetAlign(sGreenLbl, AlignEndE); - wgtLabel(rightVbox, "Blue:"); - sBlueSldr = wgtSlider(rightVbox, 0, 255); + wgtLabel(sliderBox, "Blue:"); + sBlueSldr = wgtSlider(sliderBox, 0, 255); sBlueSldr->onChange = onColorSlider; - sBlueLbl = wgtLabel(rightVbox, "0"); + sBlueLbl = wgtLabel(sliderBox, "0"); + wgtLabelSetAlign(sBlueLbl, AlignEndE); - wgtLabel(rightVbox, "Preview:"); - sColorSwatch = wgtCanvas(rightVbox, 64, 24); + wgtLabel(sliderBox, "Preview:"); + sColorSwatch = wgtCanvas(sliderBox, 64, 24); + + // Absorb remaining vertical space below the sliders + wgtSpacer(rightVbox)->weight = 100; updateColorSliders(); } @@ -259,6 +270,15 @@ static void buildDesktopTab(WidgetT *page) { WidgetT *clearBtn = wgtButton(btnRow, "Clear"); clearBtn->onClick = onClearWallpaper; 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) { @@ -472,7 +492,7 @@ static void onAccelChange(WidgetT *w) { // ============================================================ -// Callbacks — Colors tab +// Callbacks -- Colors tab // ============================================================ 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) { @@ -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; @@ -743,7 +772,7 @@ static void onVideoApply(WidgetT *w) { // ============================================================ -// Callbacks — OK / Cancel / Close +// Callbacks -- OK / Cancel / Close // ============================================================ static void onOk(WidgetT *w) { @@ -774,6 +803,16 @@ static void onOk(WidgetT *w) { 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 prefsSetInt("video", "width", sAc->display.width); prefsSetInt("video", "height", sAc->display.height); @@ -804,6 +843,8 @@ static void onClose(WindowT *win) { // saveSnapshot / restoreSnapshot // ============================================================ +static char sSavedWallpaperPath[260]; + static void saveSnapshot(void) { memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb)); sSavedWheelDir = sAc->wheelDirection; @@ -812,16 +853,11 @@ static void saveSnapshot(void) { sSavedVideoW = sAc->display.width; sSavedVideoH = sAc->display.height; sSavedVideoBpp = sAc->display.format.bitsPerPixel; - sSavedHadWallpaper = (sAc->wallpaperBuf != NULL); - const char *wp = prefsGetString("desktop", "wallpaper", NULL); - - if (wp) { - strncpy(sWallpaperPath, wp, sizeof(sWallpaperPath) - 1); - sWallpaperPath[sizeof(sWallpaperPath) - 1] = '\0'; - } else { - sWallpaperPath[0] = '\0'; - } + // Save the active wallpaper path and mode from the context + snprintf(sSavedWallpaperPath, sizeof(sSavedWallpaperPath), "%s", sAc->wallpaperPath); + snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sAc->wallpaperPath); + sSavedWpMode = sAc->wallpaperMode; } @@ -842,9 +878,11 @@ static void restoreSnapshot(void) { dvxChangeVideoMode(sAc, sSavedVideoW, sSavedVideoH, sSavedVideoBpp); } - // Restore wallpaper - if (sSavedHadWallpaper && sWallpaperPath[0]) { - dvxSetWallpaper(sAc, sWallpaperPath); + // Restore wallpaper mode and image + sAc->wallpaperMode = sSavedWpMode; + + if (sSavedWallpaperPath[0]) { + dvxSetWallpaper(sAc, sSavedWallpaperPath); } else { dvxSetWallpaper(sAc, NULL); } diff --git a/apps/dvxdemo/dvxdemo.c b/apps/dvxdemo/dvxdemo.c index 4841f4b..79c8aa8 100644 --- a/apps/dvxdemo/dvxdemo.c +++ b/apps/dvxdemo/dvxdemo.c @@ -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 // showcasing the DVX widget system, paint callbacks, menus, accelerators, etc. @@ -12,7 +12,7 @@ // - setupTerminalWindow: ANSI terminal emulator widget // // 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 "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 // 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) { 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 // 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. static void onPaintText(WindowT *win, RectT *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, @@ -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): @@ -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 @@ -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 // 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 -// 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) { WindowT *win = dvxCreateWindow(sAc, "Widget Demo", 80, 200, 280, 360, true); diff --git a/apps/imgview/imgview.c b/apps/imgview/imgview.c index da778a0..9147fd9 100644 --- a/apps/imgview/imgview.c +++ b/apps/imgview/imgview.c @@ -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 // the window while preserving aspect ratio. Resize the window to zoom. @@ -61,19 +61,23 @@ static int32_t sImgW = 0; static int32_t sImgH = 0; // Scaled image in native pixel format (for direct blit) -static uint8_t *sScaled = NULL; -static int32_t sScaledW = 0; -static int32_t sScaledH = 0; +static uint8_t *sScaled = NULL; +static int32_t sScaledW = 0; +static int32_t sScaledH = 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) { free(sScaled); - sScaled = NULL; + sScaled = NULL; + sLastFitW = fitW; + sLastFitH = fitH; if (!sImgRgb || fitW < 1 || fitH < 1) { return; @@ -113,7 +117,6 @@ static void buildScaled(int32_t fitW, int32_t fitH) { int32_t srcStride = sImgW * 3; for (int32_t y = 0; y < dstH; y++) { - // Yield every 32 rows so the UI stays responsive if ((y & 31) == 0 && y > 0) { dvxUpdate(sAc); } @@ -236,6 +239,15 @@ static void onMenu(WindowT *win, int32_t menuId) { static void onPaint(WindowT *win, RectT *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; cd.backBuf = win->contentBuf; cd.width = win->contentW; @@ -250,22 +262,42 @@ static void onPaint(WindowT *win, RectT *dirty) { uint32_t bg = packColor(&sAc->display, 32, 32, 32); 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) { int32_t offX = (win->contentW - sScaledW) / 2; int32_t offY = (win->contentH - sScaledH) / 2; int32_t bpp = sAc->display.format.bytesPerPixel; - for (int32_t y = 0; y < sScaledH; y++) { - int32_t dstY = offY + y; + // Compute visible region (clip source and dest) + int32_t srcX = 0; + int32_t srcY = 0; + int32_t blitW = sScaledW; + int32_t blitH = sScaledH; - if (dstY < 0 || dstY >= win->contentH) { - continue; - } + if (offX < 0) { + srcX = -offX; + blitW += offX; + offX = 0; + } - uint8_t *src = sScaled + y * sScaledPitch; - uint8_t *dst = win->contentBuf + dstY * win->contentPitch + offX * bpp; - memcpy(dst, src, sScaledW * bpp); + if (offY < 0) { + srcY = -offY; + 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) { (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. } diff --git a/apps/notepad/notepad.c b/apps/notepad/notepad.c index 799a584..f91fb27 100644 --- a/apps/notepad/notepad.c +++ b/apps/notepad/notepad.c @@ -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 // editing with file I/O. Demonstrates the standard DXE app pattern: @@ -87,7 +87,7 @@ AppDescriptorT appDescriptor = { // 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. // False negatives are theoretically possible but vanishingly unlikely for // 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 - // 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. fseek(f, 0, SEEK_END); 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 // 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. WidgetT *root = wgtInitWindow(ac, sWin); diff --git a/apps/progman/progman.c b/apps/progman/progman.c index 8ef8f4a..2fd2104 100644 --- a/apps/progman/progman.c +++ b/apps/progman/progman.c @@ -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. // Double-click or Enter launches an app. Includes Task Manager (Ctrl+Esc). @@ -20,8 +20,10 @@ #include "dvxApp.h" #include "dvxDialog.h" +#include "dvxPrefs.h" #include "dvxWidget.h" #include "dvxWm.h" +#include "platform/dvxPlatform.h" #include "shellApp.h" #include "shellTaskMgr.h" #include "shellInfo.h" @@ -144,7 +146,7 @@ static void buildPmWindow(void) { wmAddMenuItem(fileMenu, "E&xit Shell", CMD_EXIT); 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"); wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE); @@ -293,6 +295,9 @@ static void onPmMenu(WindowT *win, int32_t menuId) { case CMD_MIN_ON_RUN: sMinOnRun = !sMinOnRun; + shellEnsureConfigDir(sCtx); + prefsSetBool("options", "minimizeOnRun", sMinOnRun); + prefsSave(); break; case CMD_ABOUT: @@ -346,7 +351,7 @@ static void scanAppsDirRecurse(const char *dirPath) { char fullPath[MAX_PATH_LEN]; 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; if (stat(fullPath, &st) == 0 && S_ISDIR(st.st_mode)) { @@ -431,7 +436,7 @@ static void showSystemInfo(void) { ta->weight = 100; 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); } @@ -467,6 +472,12 @@ int32_t appMain(DxeAppContextT *ctx) { sCtx = ctx; sAc = ctx->shellCtx; + // Load saved preferences + char prefsPath[260]; + shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath)); + prefsLoad(prefsPath); + sMinOnRun = prefsGetBool("options", "minimizeOnRun", false); + scanAppsDir(); buildPmWindow(); diff --git a/dvx/dvxApp.c b/dvx/dvxApp.c index b3ed9ca..2073f99 100644 --- a/dvx/dvxApp.c +++ b/dvx/dvxApp.c @@ -1,4 +1,4 @@ -// dvx_app.c — Layer 5: Application API for DVX GUI +// dvx_app.c -- Layer 5: Application API for DVX GUI // // Top-level layer of the DVX windowing system. This is the only layer // that application code interacts with directly. It owns the main event @@ -104,7 +104,7 @@ static void repositionWindow(AppContextT *ctx, WindowT *win, int32_t x, int32_t static void updateCursorShape(AppContextT *ctx); static void updateTooltip(AppContextT *ctx); -// Button pressed via keyboard — shared with widgetEvent.c for Space/Enter. +// Button pressed via keyboard -- shared with widgetEvent.c for Space/Enter. // Non-static so widgetEvent.c can set it when Space/Enter triggers a button. // The button stays visually pressed for one frame (see dvxUpdate), then the // click callback fires. This gives the user visual feedback that the @@ -114,7 +114,7 @@ WidgetT *sKeyPressedBtn = NULL; // ============================================================ -// bufferToRgb — convert native pixel format to 24-bit RGB +// bufferToRgb -- convert native pixel format to 24-bit RGB // ============================================================ // // Screenshots must produce standard RGB data for stb_image_write, but the @@ -170,7 +170,7 @@ static uint8_t *bufferToRgb(const DisplayT *d, const uint8_t *buf, int32_t w, in // ============================================================ -// calcPopupSize — compute popup width and height for a menu +// calcPopupSize -- compute popup width and height for a menu // ============================================================ // // Popup width is determined by the widest item, plus conditional margins @@ -230,7 +230,7 @@ static void calcPopupSize(const AppContextT *ctx, const MenuT *menu, int32_t *pw // ============================================================ -// checkAccelTable — test key against window's accelerator table +// checkAccelTable -- test key against window's accelerator table // ============================================================ // // Accelerator tables map key+modifier combos (e.g., Ctrl+S) to menu @@ -279,7 +279,7 @@ static bool checkAccelTable(AppContextT *ctx, WindowT *win, int32_t key, int32_t // ============================================================ -// clickMenuCheckRadio — toggle check or select radio on click +// clickMenuCheckRadio -- toggle check or select radio on click // ============================================================ // // Check items simply toggle. Radio items use an implicit grouping @@ -315,7 +315,7 @@ static void clickMenuCheckRadio(MenuT *menu, int32_t itemIdx) { // ============================================================ -// closeAllPopups — dirty all popup levels and deactivate +// closeAllPopups -- dirty all popup levels and deactivate // ============================================================ // // Popup menus can be nested (submenus). The popup system uses a stack @@ -353,7 +353,7 @@ static void closeAllPopups(AppContextT *ctx) { // ============================================================ -// closePopupLevel — close one submenu level (or deactivate if top) +// closePopupLevel -- close one submenu level (or deactivate if top) // ============================================================ // // Pops one level off the popup stack. If we're already at the top @@ -587,7 +587,7 @@ static void compositeAndFlush(AppContextT *ctx) { // Dirties a 23x23 pixel area centered on the worst-case cursor bounds. // We use a fixed size that covers ALL cursor shapes rather than the // current shape's exact bounds. This handles the case where the cursor -// shape changes between frames (e.g., arrow to resize) — we need to +// shape changes between frames (e.g., arrow to resize) -- we need to // erase the old shape AND draw the new one, and both might have // different hotspot offsets. The 23x23 area is the union of all possible // cursor footprints (16x16 with hotspot at 0,0 or 7,7). @@ -598,7 +598,7 @@ static void dirtyCursorArea(AppContextT *ctx, int32_t x, int32_t y) { // ============================================================ -// dispatchAccelKey — route Alt+key to menu or widget +// dispatchAccelKey -- route Alt+key to menu or widget // ============================================================ // // Handles Alt+letter keypresses. Menu bar accelerators are checked first @@ -607,7 +607,7 @@ static void dirtyCursorArea(AppContextT *ctx, int32_t x, int32_t y) { // accelerator character, matching Windows/Motif conventions. // // For non-focusable widgets like labels and frames, the accelerator -// transfers focus to the next focusable sibling — this lets labels act +// transfers focus to the next focusable sibling -- this lets labels act // as access keys for adjacent input fields, following standard GUI idiom. static bool dispatchAccelKey(AppContextT *ctx, char key) { @@ -819,7 +819,19 @@ static void dispatchEvents(AppContextT *ctx) { ctx->mouseY = clampY; } } else { + // Resize drag ended. Call onPaint one final time with + // resizeWindow == -1 so the app can do deferred work + // (e.g. image viewer rescale). + WindowT *rWin = ctx->stack.windows[ctx->stack.resizeWindow]; wmResizeEnd(&ctx->stack); + + if (rWin->onPaint) { + RectT fullRect = {0, 0, rWin->contentW, rWin->contentH}; + rWin->onPaint(rWin, &fullRect); + rWin->contentDirty = true; + } + + dirtyListAdd(&ctx->dirty, rWin->x, rWin->y, rWin->w, rWin->h); } return; } @@ -866,7 +878,7 @@ static void dispatchEvents(AppContextT *ctx) { return; } - // Click outside system menu — close it, let event fall through + // Click outside system menu -- close it, let event fall through if ((buttons & MOUSE_LEFT) && !(prevBtn & MOUSE_LEFT)) { closeSysMenu(ctx); } @@ -917,7 +929,7 @@ static void dispatchEvents(AppContextT *ctx) { if (prevHover >= 0 && ctx->popup.menu && prevHover < ctx->popup.menu->itemCount) { // Already handled: openSubMenu replaces the child, and if current // item is not a submenu, no child opens. But we may still have - // a stale child — check if depth was increased by a previous hover. + // a stale child -- check if depth was increased by a previous hover. // This case is handled below by the parent-level hit test popping levels. } } @@ -951,7 +963,7 @@ static void dispatchEvents(AppContextT *ctx) { } } } else { - // Mouse is not in current popup — check parent levels (deepest first) + // Mouse is not in current popup -- check parent levels (deepest first) bool inParent = false; for (int32_t lvl = ctx->popup.depth - 1; lvl >= 0; lvl--) { @@ -968,7 +980,7 @@ static void dispatchEvents(AppContextT *ctx) { // Now close the level that was the "current" when we entered this parent closePopupLevel(ctx); - // Now current level IS this parent — update hover + // Now current level IS this parent -- update hover int32_t relY = my - ctx->popup.popupY - POPUP_BEVEL_WIDTH; int32_t itemIdx = (relY >= 0) ? (int32_t)((uint32_t)relY * ctx->charHeightRecip >> 16) : 0; @@ -1049,7 +1061,7 @@ static void dispatchEvents(AppContextT *ctx) { handleMouseButton(ctx, mx, my, buttons); } - // Handle right button press — context menus. + // Handle right button press -- context menus. // Context menu resolution walks UP the widget tree from the hit widget // to find the nearest ancestor with a contextMenu set, then falls back // to the window-level context menu. This lets containers provide menus @@ -1095,7 +1107,7 @@ static void dispatchEvents(AppContextT *ctx) { } } - // Handle button release on content — send to focused window + // Handle button release on content -- send to focused window if (!(buttons & MOUSE_LEFT) && (prevBtn & MOUSE_LEFT)) { if (ctx->stack.focusedIdx >= 0) { WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx]; @@ -1108,7 +1120,7 @@ static void dispatchEvents(AppContextT *ctx) { } } - // Mouse movement in content area — send to focused window + // Mouse movement in content area -- send to focused window if ((mx != ctx->prevMouseX || my != ctx->prevMouseY) && ctx->stack.focusedIdx >= 0 && (buttons & MOUSE_LEFT)) { WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx]; @@ -1120,12 +1132,63 @@ static void dispatchEvents(AppContextT *ctx) { } } - // Mouse wheel — scroll the focused window's vertical scrollbar. - // Each notch moves MOUSE_WHEEL_STEP lines. If no vertical scrollbar, - // try horizontal (for windows with only horizontal scroll). + // Mouse wheel -- scroll the focused window. First try the focused + // widget's key handler with Up/Down arrows (which scrollable widgets + // like ListBox, TextArea, TreeView handle natively). Fall back to + // WM-level window scrollbars if no widget consumes it. if (ctx->mouseWheel != 0 && ctx->stack.focusedIdx >= 0) { WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx]; - ScrollbarT *sb = win->vScroll ? win->vScroll : win->hScroll; + + // If a scrollable widget has focus, forward wheel to it. + // Otherwise fall through to WM-level window scrollbars. + bool consumed = false; + + if (win->widgetRoot) { + WidgetT *focus = wgtGetFocused(); + + if (focus && focus->wclass && focus->wclass->onKey) { + // Find the scrollable target: either the focused widget + // itself or a ScrollPane ancestor that contains it. + WidgetT *target = NULL; + + if (focus->type == WidgetListBoxE || + focus->type == WidgetListViewE || + focus->type == WidgetTreeViewE || + focus->type == WidgetTextAreaE || + focus->type == WidgetAnsiTermE || + focus->type == WidgetSliderE || + focus->type == WidgetSpinnerE || + focus->type == WidgetDropdownE || + focus->type == WidgetComboBoxE) { + target = focus; + } else { + // Walk up to find a ScrollPane ancestor + for (WidgetT *p = focus->parent; p; p = p->parent) { + if (p->type == WidgetScrollPaneE) { + target = p; + break; + } + } + } + + if (target && target->wclass && target->wclass->onKey) { + int32_t delta = ctx->mouseWheel * ctx->wheelDirection; + int32_t arrowKey = (delta > 0) ? (0x50 | 0x100) : (0x48 | 0x100); + int32_t steps = abs(delta) * MOUSE_WHEEL_STEP; + + for (int32_t s = 0; s < steps; s++) { + target->wclass->onKey(target, arrowKey, 0); + } + + // Ensure the window repaints even if the widget's + // onKey didn't invalidate (belt and suspenders) + wgtInvalidatePaint(target); + consumed = true; + } + } + } + + ScrollbarT *sb = !consumed ? (win->vScroll ? win->vScroll : win->hScroll) : NULL; if (sb) { int32_t oldValue = sb->value; @@ -1173,7 +1236,7 @@ static void drawCursorAt(AppContextT *ctx, int32_t x, int32_t y) { // ============================================================ -// drawPopupLevel — draw one popup menu (bevel + items) +// drawPopupLevel -- draw one popup menu (bevel + items) // ============================================================ // // Draws a single popup menu level (the compositor calls this for each @@ -1345,6 +1408,23 @@ WindowT *dvxCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t } } + // Clamp to screen so the window is fully visible + if (x + w > ctx->display.width) { + x = ctx->display.width - w; + } + + if (y + h > ctx->display.height) { + y = ctx->display.height - h; + } + + if (x < 0) { + x = 0; + } + + if (y < 0) { + y = 0; + } + WindowT *win = wmCreateWindow(&ctx->stack, &ctx->display, title, x, y, w, h, resizable); if (win) { @@ -1406,7 +1486,7 @@ void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmd // // The clipboard is a simple in-process text buffer managed by the // platform layer (clipboardCopy/clipboardGet). There is no inter-process -// clipboard because DVX runs as a single-process windowing system — all +// clipboard because DVX runs as a single-process windowing system -- all // windows share the same address space. The thin wrappers here exist to // keep the platform layer out of application code's include path. @@ -1421,25 +1501,38 @@ const char *dvxClipboardGet(int32_t *outLen) { // ============================================================ -// Color scheme — name table and indexed access +// Color scheme -- name table and indexed access // ============================================================ +// INI/theme key names (must not change -- used for save/load) static const char *sColorNames[ColorCountE] = { "desktop", "windowFace", "windowHighlight", "windowShadow", "activeTitleBg", "activeTitleFg", "inactiveTitleBg", "inactiveTitleFg", "contentBg", "contentFg", "menuBg", "menuFg", "menuHighlightBg", "menuHighlightFg", "buttonFace", - "scrollbarBg", "scrollbarFg", "scrollbarTrough" + "scrollbarBg", "scrollbarFg", "scrollbarTrough", + "cursorColor", "cursorOutline" +}; + +// Human-readable display names for the UI +static const char *sColorLabels[ColorCountE] = { + "Desktop", "Window Face", "Window Highlight", + "Window Shadow", "Active Title Bar", "Active Title Text", + "Inactive Title Bar", "Inactive Title Text", "Content Background", + "Content Text", "Menu Background", "Menu Text", + "Menu Highlight", "Menu Highlight Text", "Button Face", + "Scrollbar Background", "Scrollbar Foreground", "Scrollbar Trough", + "Cursor Color", "Cursor Outline" }; // Default GEOS Ensemble Motif-style colors (RGB triplets) static const uint8_t sDefaultColors[ColorCountE][3] = { - { 0, 128, 128}, // desktop — GEOS teal + { 0, 128, 128}, // desktop -- GEOS teal {192, 192, 192}, // windowFace {255, 255, 255}, // windowHighlight {128, 128, 128}, // windowShadow - { 48, 48, 48}, // activeTitleBg — dark charcoal + { 48, 48, 48}, // activeTitleBg -- dark charcoal {255, 255, 255}, // activeTitleFg {160, 160, 160}, // inactiveTitleBg { 64, 64, 64}, // inactiveTitleFg @@ -1453,6 +1546,8 @@ static const uint8_t sDefaultColors[ColorCountE][3] = { {192, 192, 192}, // scrollbarBg {128, 128, 128}, // scrollbarFg {160, 160, 160}, // scrollbarTrough + {255, 255, 255}, // cursorFg -- white + { 0, 0, 0}, // cursorBg -- black }; // Access the packed color value in ColorSchemeT by index. @@ -1474,6 +1569,19 @@ const char *dvxColorName(ColorIdE id) { } +// ============================================================ +// dvxColorLabel +// ============================================================ + +const char *dvxColorLabel(ColorIdE id) { + if (id < 0 || id >= ColorCountE) { + return "Unknown"; + } + + return sColorLabels[id]; +} + + // ============================================================ // dvxApplyColorScheme // ============================================================ @@ -1485,6 +1593,10 @@ void dvxApplyColorScheme(AppContextT *ctx) { *colorSlot(&ctx->colors, (ColorIdE)i) = packColor(d, ctx->colorRgb[i][0], ctx->colorRgb[i][1], ctx->colorRgb[i][2]); } + // Sync cursor colors from the scheme so the compositor uses them + ctx->cursorFg = ctx->colors.cursorFg; + ctx->cursorBg = ctx->colors.cursorBg; + // Repaint everything dirtyListAdd(&ctx->dirty, 0, 0, d->width, d->height); } @@ -1564,7 +1676,7 @@ int32_t dvxChangeVideoMode(AppContextT *ctx, int32_t requestedW, int32_t request // Save old state for rollback DisplayT oldDisplay = ctx->display; - // Stash old wallpaper (don't free — we may need it for rollback) + // Stash old wallpaper (don't free -- we may need it for rollback) uint8_t *oldWpBuf = ctx->wallpaperBuf; int32_t oldWpPitch = ctx->wallpaperPitch; ctx->wallpaperBuf = NULL; @@ -1581,7 +1693,7 @@ int32_t dvxChangeVideoMode(AppContextT *ctx, int32_t requestedW, int32_t request ctx->display.palette = NULL; if (videoInit(&ctx->display, oldDisplay.width, oldDisplay.height, oldDisplay.format.bitsPerPixel) != 0) { - // Both failed — catastrophic + // Both failed -- catastrophic return -1; } @@ -1593,7 +1705,7 @@ int32_t dvxChangeVideoMode(AppContextT *ctx, int32_t requestedW, int32_t request return -1; } - // New mode succeeded — free old wallpaper buffer + // New mode succeeded -- free old wallpaper buffer free(oldWpBuf); // Reinit blit ops for new pixel format @@ -1602,10 +1714,6 @@ int32_t dvxChangeVideoMode(AppContextT *ctx, int32_t requestedW, int32_t request // Repack all colors for new pixel format dvxApplyColorScheme(ctx); - // Repack cursor colors - ctx->cursorFg = packColor(&ctx->display, 255, 255, 255); - ctx->cursorBg = packColor(&ctx->display, 0, 0, 0); - // Reinit mouse range platformMouseInit(ctx->display.width, ctx->display.height); ctx->hasMouseWheel = platformMouseWheelInit(); @@ -1751,6 +1859,24 @@ void dvxFitWindow(AppContextT *ctx, WindowT *win) { // Resize win->w = newW; win->h = newH; + + // Shift position so the window stays fully on screen + if (win->x + newW > ctx->display.width) { + win->x = ctx->display.width - newW; + } + + if (win->y + newH > ctx->display.height) { + win->y = ctx->display.height - newH; + } + + if (win->x < 0) { + win->x = 0; + } + + if (win->y < 0) { + win->y = 0; + } + wmUpdateContentRect(win); wmReallocContentBuf(win, &ctx->display); @@ -1827,7 +1953,7 @@ const VideoModeInfoT *dvxGetVideoModes(const AppContextT *ctx, int32_t *count) { // ============================================================ -// enumModeCb — used during dvxInit to capture available modes +// enumModeCb -- used during dvxInit to capture available modes // ============================================================ static void enumModeCb(int32_t w, int32_t h, int32_t bpp, void *userData) { @@ -1977,7 +2103,22 @@ void dvxSetColor(AppContextT *ctx, ColorIdE id, uint8_t r, uint8_t g, uint8_t b) ctx->colorRgb[id][1] = g; ctx->colorRgb[id][2] = b; - *colorSlot(&ctx->colors, id) = packColor(&ctx->display, r, g, b); + uint32_t packed = packColor(&ctx->display, r, g, b); + *colorSlot(&ctx->colors, id) = packed; + + // Keep cursor color cache in sync + if (id == ColorCursorFgE) { + ctx->cursorFg = packed; + } else if (id == ColorCursorBgE) { + ctx->cursorBg = packed; + } + + // Invalidate all windows so scrollbar/chrome changes are visible + for (int32_t i = 0; i < ctx->stack.count; i++) { + WindowT *win = ctx->stack.windows[i]; + win->contentDirty = true; + } + dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height); } @@ -1986,8 +2127,181 @@ void dvxSetColor(AppContextT *ctx, ColorIdE id, uint8_t r, uint8_t g, uint8_t b) // dvxSetWallpaper // ============================================================ +// writePixel -- write a packed pixel to a buffer at position x +static void writePixel(uint8_t *row, int32_t x, uint32_t px, int32_t bpp) { + if (bpp == 8) { + row[x] = (uint8_t)px; + } else if (bpp == 15 || bpp == 16) { + ((uint16_t *)row)[x] = (uint16_t)px; + } else { + ((uint32_t *)row)[x] = px; + } +} + + +// buildWallpaperBuf -- render the full-screen wallpaper buffer from RGB source +static uint8_t *buildWallpaperBuf(AppContextT *ctx, const uint8_t *rgb, int32_t imgW, int32_t imgH, WallpaperModeE mode) { + int32_t screenW = ctx->display.width; + int32_t screenH = ctx->display.height; + int32_t bpp = ctx->display.format.bitsPerPixel; + int32_t bytesPerPx = ctx->display.format.bytesPerPixel; + int32_t pitch = screenW * bytesPerPx; + int32_t srcStride = imgW * 3; + + static const int32_t bayerMatrix[4][4] = { + { -7, 1, -5, 3}, + { 5, -3, 7, -1}, + { -4, 4, -6, 2}, + { 6, -2, 8, 0} + }; + bool dither = (bpp == 15 || bpp == 16); + + uint8_t *buf = (uint8_t *)malloc(pitch * screenH); + + if (!buf) { + return NULL; + } + + // Fill entire buffer with desktop color first (used by center mode + // for the border area, but also ensures no garbage pixels) + uint32_t bgPx = ctx->colors.desktop; + + for (int32_t y = 0; y < screenH; y++) { + uint8_t *dst = buf + y * pitch; + + for (int32_t x = 0; x < screenW; x++) { + writePixel(dst, x, bgPx, bpp); + } + } + + if (mode == WallpaperStretchE) { + // Bilinear scale to screen dimensions with optional dither + for (int32_t y = 0; y < screenH; y++) { + if ((y & 31) == 0 && y > 0) { + dvxUpdate(ctx); + } + + int32_t srcYfp = (int32_t)((int64_t)y * imgH * 65536 / screenH); + int32_t sy0 = srcYfp >> 16; + int32_t sy1 = (sy0 + 1 < imgH) ? sy0 + 1 : imgH - 1; + int32_t fy = (srcYfp >> 8) & 0xFF; + int32_t ify = 256 - fy; + uint8_t *dst = buf + y * pitch; + const uint8_t *row0 = rgb + sy0 * srcStride; + const uint8_t *row1 = rgb + sy1 * srcStride; + + for (int32_t x = 0; x < screenW; x++) { + int32_t srcXfp = (int32_t)((int64_t)x * imgW * 65536 / screenW); + int32_t sx0 = srcXfp >> 16; + int32_t sx1 = (sx0 + 1 < imgW) ? sx0 + 1 : imgW - 1; + int32_t fx = (srcXfp >> 8) & 0xFF; + int32_t ifx = 256 - fx; + + const uint8_t *p00 = row0 + sx0 * 3; + const uint8_t *p10 = row0 + sx1 * 3; + const uint8_t *p01 = row1 + sx0 * 3; + const uint8_t *p11 = row1 + sx1 * 3; + + int32_t r = (p00[0] * ifx * ify + p10[0] * fx * ify + p01[0] * ifx * fy + p11[0] * fx * fy) >> 16; + int32_t g = (p00[1] * ifx * ify + p10[1] * fx * ify + p01[1] * ifx * fy + p11[1] * fx * fy) >> 16; + int32_t b = (p00[2] * ifx * ify + p10[2] * fx * ify + p01[2] * ifx * fy + p11[2] * fx * fy) >> 16; + + if (dither) { + int32_t d = bayerMatrix[y & 3][x & 3]; + r += d; g += d; b += d; + if (r < 0) r = 0; if (r > 255) r = 255; + if (g < 0) g = 0; if (g > 255) g = 255; + if (b < 0) b = 0; if (b > 255) b = 255; + } + + writePixel(dst, x, packColor(&ctx->display, (uint8_t)r, (uint8_t)g, (uint8_t)b), bpp); + } + } + } else if (mode == WallpaperTileE) { + // Tile: repeat the image at native size across the screen + for (int32_t y = 0; y < screenH; y++) { + if ((y & 31) == 0 && y > 0) { + dvxUpdate(ctx); + } + + int32_t srcY = y % imgH; + uint8_t *dst = buf + y * pitch; + const uint8_t *srcRow = rgb + srcY * srcStride; + + for (int32_t x = 0; x < screenW; x++) { + int32_t srcX = x % imgW; + const uint8_t *src = srcRow + srcX * 3; + + int32_t r = src[0]; + int32_t g = src[1]; + int32_t b = src[2]; + + if (dither) { + int32_t d = bayerMatrix[y & 3][x & 3]; + r += d; g += d; b += d; + if (r < 0) r = 0; if (r > 255) r = 255; + if (g < 0) g = 0; if (g > 255) g = 255; + if (b < 0) b = 0; if (b > 255) b = 255; + } + + writePixel(dst, x, packColor(&ctx->display, (uint8_t)r, (uint8_t)g, (uint8_t)b), bpp); + } + } + } else { + // Center: place at native size in the center of the screen. + // Buffer was already filled with desktop color above. + // Clip the source region to only iterate visible pixels. + int32_t offX = (screenW - imgW) / 2; + int32_t offY = (screenH - imgH) / 2; + + int32_t srcStartX = (offX < 0) ? -offX : 0; + int32_t srcStartY = (offY < 0) ? -offY : 0; + int32_t srcEndX = imgW; + int32_t srcEndY = imgH; + + if (offX + srcEndX > screenW) { + srcEndX = screenW - offX; + } + + if (offY + srcEndY > screenH) { + srcEndY = screenH - offY; + } + + for (int32_t sy = srcStartY; sy < srcEndY; sy++) { + if (((sy - srcStartY) & 31) == 0 && sy > srcStartY) { + dvxUpdate(ctx); + } + + int32_t dy = offY + sy; + uint8_t *dst = buf + dy * pitch; + const uint8_t *srcRow = rgb + sy * srcStride; + + for (int32_t sx = srcStartX; sx < srcEndX; sx++) { + int32_t dx = offX + sx; + + const uint8_t *src = srcRow + sx * 3; + int32_t r = src[0]; + int32_t g = src[1]; + int32_t b = src[2]; + + if (dither) { + int32_t d = bayerMatrix[dy & 3][dx & 3]; + r += d; g += d; b += d; + if (r < 0) r = 0; if (r > 255) r = 255; + if (g < 0) g = 0; if (g > 255) g = 255; + if (b < 0) b = 0; if (b > 255) b = 255; + } + + writePixel(dst, dx, packColor(&ctx->display, (uint8_t)r, (uint8_t)g, (uint8_t)b), bpp); + } + } + } + + return buf; +} + + bool dvxSetWallpaper(AppContextT *ctx, const char *path) { - // Free existing wallpaper free(ctx->wallpaperBuf); ctx->wallpaperBuf = NULL; ctx->wallpaperPitch = 0; @@ -1998,7 +2312,6 @@ bool dvxSetWallpaper(AppContextT *ctx, const char *path) { return true; } - // Store path for reload after video mode change strncpy(ctx->wallpaperPath, path, sizeof(ctx->wallpaperPath) - 1); ctx->wallpaperPath[sizeof(ctx->wallpaperPath) - 1] = '\0'; @@ -2011,117 +2324,35 @@ bool dvxSetWallpaper(AppContextT *ctx, const char *path) { return false; } - // Pre-scale to screen dimensions using bilinear interpolation and - // convert to native pixel format. Bilinear samples the 4 nearest - // source pixels and blends by fractional distance, producing smooth - // gradients instead of blocky nearest-neighbor artifacts. Uses - // 8-bit fixed-point weights (256 = 1.0) to avoid floating point. - // - // For 15/16bpp modes, ordered dithering (4x4 Bayer matrix) breaks - // up color banding that occurs when quantizing 24-bit gradients to - // 5-6-5 or 5-5-5. The dither offset is added before packColor so - // the quantization error is distributed spatially. - static const int32_t bayerMatrix[4][4] = { - { -7, 1, -5, 3}, - { 5, -3, 7, -1}, - { -4, 4, -6, 2}, - { 6, -2, 8, 0} - }; - bool dither = (ctx->display.format.bitsPerPixel == 15 || ctx->display.format.bitsPerPixel == 16); - int32_t screenW = ctx->display.width; - int32_t screenH = ctx->display.height; - int32_t bpp = ctx->display.format.bitsPerPixel; - int32_t bytesPerPx = ctx->display.format.bytesPerPixel; - int32_t pitch = screenW * bytesPerPx; - uint8_t *buf = (uint8_t *)malloc(pitch * screenH); + int32_t pitch = ctx->display.width * ctx->display.format.bytesPerPixel; - if (!buf) { - stbi_image_free(rgb); - return false; - } - - int32_t srcStride = imgW * 3; - - for (int32_t y = 0; y < screenH; y++) { - // Yield every 32 rows so the UI stays responsive - if ((y & 31) == 0 && y > 0) { - dvxUpdate(ctx); - } - - // Fixed-point source Y: 16.16 - int32_t srcYfp = (int32_t)((int64_t)y * imgH * 65536 / screenH); - int32_t sy0 = srcYfp >> 16; - int32_t sy1 = sy0 + 1; - int32_t fy = (srcYfp >> 8) & 0xFF; // fractional Y (0-255) - int32_t ify = 256 - fy; - uint8_t *dst = buf + y * pitch; - - if (sy1 >= imgH) { - sy1 = imgH - 1; - } - - uint8_t *row0 = rgb + sy0 * srcStride; - uint8_t *row1 = rgb + sy1 * srcStride; - - for (int32_t x = 0; x < screenW; x++) { - int32_t srcXfp = (int32_t)((int64_t)x * imgW * 65536 / screenW); - int32_t sx0 = srcXfp >> 16; - int32_t sx1 = sx0 + 1; - int32_t fx = (srcXfp >> 8) & 0xFF; - int32_t ifx = 256 - fx; - - if (sx1 >= imgW) { - sx1 = imgW - 1; - } - - // Sample 4 source pixels - uint8_t *p00 = row0 + sx0 * 3; - uint8_t *p10 = row0 + sx1 * 3; - uint8_t *p01 = row1 + sx0 * 3; - uint8_t *p11 = row1 + sx1 * 3; - - // Bilinear blend (8-bit fixed-point, 256 = 1.0) - int32_t r = (p00[0] * ifx * ify + p10[0] * fx * ify + p01[0] * ifx * fy + p11[0] * fx * fy) >> 16; - int32_t g = (p00[1] * ifx * ify + p10[1] * fx * ify + p01[1] * ifx * fy + p11[1] * fx * fy) >> 16; - int32_t b = (p00[2] * ifx * ify + p10[2] * fx * ify + p01[2] * ifx * fy + p11[2] * fx * fy) >> 16; - - // Ordered dither for 15/16bpp to reduce color banding - if (dither) { - int32_t d = bayerMatrix[y & 3][x & 3]; - - r += d; - g += d; - b += d; - - if (r < 0) r = 0; - if (r > 255) r = 255; - if (g < 0) g = 0; - if (g > 255) g = 255; - if (b < 0) b = 0; - if (b > 255) b = 255; - } - - uint32_t px = packColor(&ctx->display, (uint8_t)r, (uint8_t)g, (uint8_t)b); - - if (bpp == 8) { - dst[x] = (uint8_t)px; - } else if (bpp == 15 || bpp == 16) { - ((uint16_t *)dst)[x] = (uint16_t)px; - } else { - ((uint32_t *)dst)[x] = px; - } - } - } + ctx->wallpaperBuf = buildWallpaperBuf(ctx, rgb, imgW, imgH, ctx->wallpaperMode); + ctx->wallpaperPitch = pitch; stbi_image_free(rgb); - ctx->wallpaperBuf = buf; - ctx->wallpaperPitch = pitch; - dirtyListAdd(&ctx->dirty, 0, 0, screenW, screenH); + if (!ctx->wallpaperBuf) { + return false; + } + + dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height); return true; } +// ============================================================ +// dvxSetWallpaperMode +// ============================================================ + +void dvxSetWallpaperMode(AppContextT *ctx, WallpaperModeE mode) { + ctx->wallpaperMode = mode; + + if (ctx->wallpaperPath[0]) { + dvxSetWallpaper(ctx, ctx->wallpaperPath); + } +} + + // ============================================================ // dvxInit // ============================================================ @@ -2166,10 +2397,6 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_ initColorScheme(ctx); // Pre-pack cursor colors once. packColor converts RGB to the native - // pixel format, which is too expensive to do per-frame. - ctx->cursorFg = packColor(&ctx->display, 255, 255, 255); - ctx->cursorBg = packColor(&ctx->display, 0, 0, 0); - platformMouseInit(ctx->display.width, ctx->display.height); ctx->hasMouseWheel = platformMouseWheelInit(); ctx->mouseX = ctx->display.width / 2; @@ -2290,7 +2517,7 @@ void dvxInvalidateRect(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int void dvxInvalidateWindow(AppContextT *ctx, WindowT *win) { // Call the window's paint callback to update the content buffer // before marking the screen dirty. This means raw-paint apps only - // need to call dvxInvalidateWindow — onPaint fires automatically. + // need to call dvxInvalidateWindow -- onPaint fires automatically. if (win->onPaint) { RectT fullRect = {0, 0, win->contentW, win->contentH}; win->onPaint(win, &fullRect); @@ -2357,7 +2584,7 @@ void dvxRun(AppContextT *ctx) { // work (e.g., polling serial ports, processing network data) when the // GUI has nothing to paint. Without it, the loop would busy-wait or // yield the CPU slice. With it, the application gets a callback to do -// useful work. platformYield is the fallback — it calls INT 28h (DOS +// useful work. platformYield is the fallback -- it calls INT 28h (DOS // idle) or SDL_Delay (Linux) to avoid burning CPU when truly idle. bool dvxUpdate(AppContextT *ctx) { @@ -2729,7 +2956,7 @@ void dvxTileWindowsV(AppContextT *ctx) { // ============================================================ -// interactiveScreenshot — snapshot screen, prompt for save path +// interactiveScreenshot -- snapshot screen, prompt for save path // ============================================================ static void interactiveScreenshot(AppContextT *ctx) { @@ -2758,7 +2985,7 @@ static void interactiveScreenshot(AppContextT *ctx) { // ============================================================ -// interactiveWindowScreenshot — snapshot window content, prompt for save path +// interactiveWindowScreenshot -- snapshot window content, prompt for save path // ============================================================ static void interactiveWindowScreenshot(AppContextT *ctx, WindowT *win) { @@ -2795,7 +3022,7 @@ static void interactiveWindowScreenshot(AppContextT *ctx, WindowT *win) { // ============================================================ // // Executes a system menu (window control menu) command. The system menu -// is the DESQview/X equivalent of the Win3.x control-menu box — it +// is the DESQview/X equivalent of the Win3.x control-menu box -- it // provides Restore, Move, Size, Minimize, Maximize, and Close. Keyboard // move/resize mode is entered by setting kbMoveResize state, which causes // pollKeyboard to intercept arrow keys until Enter/Esc. @@ -2895,7 +3122,7 @@ static WindowT *findWindowById(AppContextT *ctx, int32_t id) { // hitPart 0: content area (forwarded to window's onMouse callback) // hitPart 1: title bar (begins mouse drag) // hitPart 2: close/sys-menu gadget (single-click opens sys menu, -// double-click closes — DESQview/X convention) +// double-click closes -- DESQview/X convention) // hitPart 3: resize border (begins edge/corner resize) // hitPart 4: menu bar (opens popup for clicked menu) // hitPart 5/6: vertical/horizontal scrollbar @@ -2934,7 +3161,7 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t wmRestoreMinimized(&ctx->stack, &ctx->dirty, iconWin); ctx->lastIconClickId = -1; } else { - // First click — record for double-click detection + // First click -- record for double-click detection ctx->lastIconClickTime = now; ctx->lastIconClickId = iconWin->id; } @@ -3090,7 +3317,7 @@ static void initColorScheme(AppContextT *ctx) { // ============================================================ -// openContextMenu — open a context menu at a screen position +// openContextMenu -- open a context menu at a screen position // ============================================================ // // Context menus reuse the same popup system as menu bar popups but with @@ -3136,7 +3363,7 @@ static void openContextMenu(AppContextT *ctx, WindowT *win, MenuT *menu, int32_t // ============================================================ -// openPopupAtMenu — open top-level popup for a menu bar menu +// openPopupAtMenu -- open top-level popup for a menu bar menu // ============================================================ // // Opens the dropdown for a menu bar item (e.g., "File", "Edit"). Any @@ -3177,7 +3404,7 @@ static void openPopupAtMenu(AppContextT *ctx, WindowT *win, int32_t menuIdx) { // ============================================================ -// openSubMenu — open submenu for the currently hovered item +// openSubMenu -- open submenu for the currently hovered item // ============================================================ // // Pushes the current popup state onto parentStack and opens the submenu @@ -3260,7 +3487,7 @@ static void openSysMenu(AppContextT *ctx, WindowT *win) { item->enabled = win->maximized; item->accelKey = accelParse(item->label); - // Move — disabled when maximized + // Move -- disabled when maximized item = &ctx->sysMenu.items[ctx->sysMenu.itemCount++]; strncpy(item->label, "&Move", MAX_MENU_LABEL - 1); item->cmd = SysMenuMoveE; @@ -3268,7 +3495,7 @@ static void openSysMenu(AppContextT *ctx, WindowT *win) { item->enabled = !win->maximized; item->accelKey = accelParse(item->label); - // Size — only if resizable and not maximized + // Size -- only if resizable and not maximized item = &ctx->sysMenu.items[ctx->sysMenu.itemCount++]; strncpy(item->label, "&Size", MAX_MENU_LABEL - 1); item->cmd = SysMenuSizeE; @@ -3276,7 +3503,7 @@ static void openSysMenu(AppContextT *ctx, WindowT *win) { item->enabled = win->resizable && !win->maximized; item->accelKey = accelParse(item->label); - // Minimize — not available on modal windows + // Minimize -- not available on modal windows item = &ctx->sysMenu.items[ctx->sysMenu.itemCount++]; strncpy(item->label, "Mi&nimize", MAX_MENU_LABEL - 1); item->cmd = SysMenuMinimizeE; @@ -3284,7 +3511,7 @@ static void openSysMenu(AppContextT *ctx, WindowT *win) { item->enabled = !win->modal; item->accelKey = accelParse(item->label); - // Maximize — only if resizable and not maximized + // Maximize -- only if resizable and not maximized item = &ctx->sysMenu.items[ctx->sysMenu.itemCount++]; strncpy(item->label, "Ma&ximize", MAX_MENU_LABEL - 1); item->cmd = SysMenuMaximizeE; @@ -3326,7 +3553,7 @@ static void openSysMenu(AppContextT *ctx, WindowT *win) { item->enabled = true; item->accelKey = accelParse(item->label); - // Compute popup geometry — position below the close gadget + // Compute popup geometry -- position below the close gadget ctx->sysMenu.popupX = win->x + CHROME_BORDER_WIDTH; ctx->sysMenu.popupY = win->y + CHROME_BORDER_WIDTH + CHROME_TITLE_HEIGHT; @@ -3356,7 +3583,7 @@ static void openSysMenu(AppContextT *ctx, WindowT *win) { // ============================================================ -// pollAnsiTermWidgets — poll and repaint all ANSI term widgets +// pollAnsiTermWidgets -- poll and repaint all ANSI term widgets // ============================================================ // // ANSI terminal widgets have asynchronous data sources (PTYs, serial @@ -3364,7 +3591,7 @@ static void openSysMenu(AppContextT *ctx, WindowT *win) { // window's widget tree looking for AnsiTerm widgets, polls them for new // data, and if data arrived, triggers a targeted repaint of just the // affected rows. The fine-grained dirty rect (just the changed rows -// rather than the whole window) is critical for terminal performance — +// rather than the whole window) is critical for terminal performance -- // a single character echo should only flush one row to the LFB, not // the entire terminal viewport. @@ -3380,7 +3607,7 @@ static void pollAnsiTermWidgets(AppContextT *ctx) { // ============================================================ -// pollAnsiTermWidgetsWalk — recursive helper +// pollAnsiTermWidgetsWalk -- recursive helper // ============================================================ static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win) { @@ -3415,21 +3642,21 @@ static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win) // ============================================================ // // Drains the keyboard buffer and dispatches each key through a priority -// chain. The priority order is important — higher priority handlers +// chain. The priority order is important -- higher priority handlers // consume the key and skip lower ones via 'continue': // -// 1. Alt+Tab / Shift+Alt+Tab — window cycling (always works) -// 2. Alt+F4 — close focused window -// 3. Ctrl+F12 / Ctrl+Shift+F12 — screenshot (full / window) -// 4. Ctrl+Esc — system-wide hotkey (task manager) -// 5. F10 — activate/toggle menu bar +// 1. Alt+Tab / Shift+Alt+Tab -- window cycling (always works) +// 2. Alt+F4 -- close focused window +// 3. Ctrl+F12 / Ctrl+Shift+F12 -- screenshot (full / window) +// 4. Ctrl+Esc -- system-wide hotkey (task manager) +// 5. F10 -- activate/toggle menu bar // 4. Keyboard move/resize mode (arrow keys captured exclusively) -// 5. Alt+Space — system menu toggle +// 5. Alt+Space -- system menu toggle // 6. System menu keyboard navigation (arrows, enter, esc, accel) -// 7. Alt+key — menu bar / widget accelerator dispatch +// 7. Alt+key -- menu bar / widget accelerator dispatch // 8. Popup menu keyboard navigation (arrows, enter, esc, accel) // 9. Accelerator table on focused window (Ctrl+S, etc.) -// 10. Tab/Shift+Tab — widget focus cycling +// 10. Tab/Shift+Tab -- widget focus cycling // 11. Fall-through to focused window's onKey callback // // Key encoding: ASCII keys use their ASCII value; extended keys (arrows, @@ -3447,8 +3674,8 @@ static void pollKeyboard(AppContextT *ctx) { int32_t scancode = evt.scancode; int32_t ascii = evt.ascii; - // Alt+Tab / Shift+Alt+Tab — cycle windows. - // Unlike Windows, there's no task-switcher overlay here — each press + // Alt+Tab / Shift+Alt+Tab -- cycle windows. + // Unlike Windows, there's no task-switcher overlay here -- each press // immediately rotates the window stack and focuses the new top. // Alt+Tab rotates the top window to the bottom of the stack (so the // second window becomes visible). Shift+Alt+Tab does the reverse, @@ -3483,7 +3710,7 @@ static void pollKeyboard(AppContextT *ctx) { continue; } - // Alt+F4 — close focused window + // Alt+F4 -- close focused window if (ascii == 0 && scancode == 0x6B) { if (ctx->stack.focusedIdx >= 0) { WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx]; @@ -3496,8 +3723,8 @@ static void pollKeyboard(AppContextT *ctx) { continue; } - // Ctrl+F12 — save full screen screenshot - // Ctrl+Shift+F12 — save focused window screenshot + // Ctrl+F12 -- save full screen screenshot + // Ctrl+Shift+F12 -- save focused window screenshot // BIOS returns scancode 0x58 for F12; Ctrl+F12 = scancode 0x8A. if (ascii == 0 && scancode == 0x8A && (shiftFlags & KEY_MOD_CTRL)) { if (shiftHeld && ctx->stack.focusedIdx >= 0) { @@ -3509,7 +3736,7 @@ static void pollKeyboard(AppContextT *ctx) { continue; } - // Ctrl+Esc — system-wide hotkey (e.g. task manager) + // Ctrl+Esc -- system-wide hotkey (e.g. task manager) if (scancode == 0x01 && ascii == 0x1B && (shiftFlags & KEY_MOD_CTRL)) { if (ctx->onCtrlEsc) { ctx->onCtrlEsc(ctx->ctrlEscCtx); @@ -3518,7 +3745,7 @@ static void pollKeyboard(AppContextT *ctx) { continue; } - // F10 — activate menu bar + // F10 -- activate menu bar if (ascii == 0 && scancode == 0x44) { if (ctx->stack.focusedIdx >= 0) { WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx]; @@ -3535,7 +3762,7 @@ static void pollKeyboard(AppContextT *ctx) { continue; } - // Keyboard move/resize mode — intercept all keys + // Keyboard move/resize mode -- intercept all keys if (ctx->kbMoveResize.mode != KbModeNoneE) { WindowT *kbWin = findWindowById(ctx, ctx->kbMoveResize.windowId); @@ -3545,7 +3772,7 @@ static void pollKeyboard(AppContextT *ctx) { } if (ascii == 0x1B) { - // Cancel — restore original position/size + // Cancel -- restore original position/size dirtyListAdd(&ctx->dirty, kbWin->x, kbWin->y, kbWin->w, kbWin->h); kbWin->x = ctx->kbMoveResize.origX; kbWin->y = ctx->kbMoveResize.origY; @@ -3609,6 +3836,7 @@ static void pollKeyboard(AppContextT *ctx) { } if (kbWin->x != oldX || kbWin->y != oldY) { + kbWin->maximized = false; dirtyListAdd(&ctx->dirty, oldX, oldY, kbWin->w, kbWin->h); dirtyListAdd(&ctx->dirty, kbWin->x, kbWin->y, kbWin->w, kbWin->h); } @@ -3656,6 +3884,7 @@ static void pollKeyboard(AppContextT *ctx) { } if (newW != kbWin->w || newH != kbWin->h) { + kbWin->maximized = false; dirtyListAdd(&ctx->dirty, kbWin->x, kbWin->y, kbWin->w, kbWin->h); kbWin->w = newW; kbWin->h = newH; @@ -3677,7 +3906,7 @@ static void pollKeyboard(AppContextT *ctx) { continue; } - // Alt+Space — open/close system menu + // Alt+Space -- open/close system menu // Enhanced INT 16h: Alt+Space returns scancode 0x39, ascii 0x20 // Must check Alt modifier (bit 3) to distinguish from plain Space if (scancode == 0x39 && ascii == 0x20 && (shiftFlags & KEY_MOD_ALT)) { @@ -3692,7 +3921,7 @@ static void pollKeyboard(AppContextT *ctx) { // System menu keyboard navigation if (ctx->sysMenu.active) { - // Alt+key — close system menu and let it fall through to accel dispatch + // Alt+key -- close system menu and let it fall through to accel dispatch if (ascii == 0 && platformAltScanToChar(scancode)) { closeSysMenu(ctx); // Fall through to dispatchAccelKey below @@ -3738,7 +3967,7 @@ static void pollKeyboard(AppContextT *ctx) { dirtyListAdd(&ctx->dirty, ctx->sysMenu.popupX, ctx->sysMenu.popupY, ctx->sysMenu.popupW, ctx->sysMenu.popupH); continue; } else if (ascii == 0x0D) { - // Enter — execute selected item + // Enter -- execute selected item if (ctx->sysMenu.hoverItem >= 0 && ctx->sysMenu.hoverItem < ctx->sysMenu.itemCount) { SysMenuItemT *item = &ctx->sysMenu.items[ctx->sysMenu.hoverItem]; @@ -3760,7 +3989,7 @@ static void pollKeyboard(AppContextT *ctx) { } } - // No sys menu match — try menu bar accelerators + // No sys menu match -- try menu bar accelerators closeSysMenu(ctx); if (ctx->stack.focusedIdx >= 0) { WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx]; @@ -3841,7 +4070,7 @@ static void pollKeyboard(AppContextT *ctx) { continue; } - // Left arrow — close submenu, or switch to previous top-level menu + // Left arrow -- close submenu, or switch to previous top-level menu if (scancode == 0x4B) { if (ctx->popup.depth > 0) { closePopupLevel(ctx); @@ -3862,7 +4091,7 @@ static void pollKeyboard(AppContextT *ctx) { continue; } - // Right arrow — open submenu, or switch to next top-level menu + // Right arrow -- open submenu, or switch to next top-level menu if (scancode == 0x4D) { // If hovered item has a submenu, open it if (curMenu && ctx->popup.hoverItem >= 0 && ctx->popup.hoverItem < curMenu->itemCount) { @@ -3946,7 +4175,7 @@ static void pollKeyboard(AppContextT *ctx) { } } - // No match in current popup — try switching to another top-level menu + // No match in current popup -- try switching to another top-level menu WindowT *win = findWindowById(ctx, ctx->popup.windowId); if (win && win->menuBar) { @@ -3999,7 +4228,7 @@ static void pollKeyboard(AppContextT *ctx) { } } - // Tab / Shift-Tab — cycle focus between widgets + // Tab / Shift-Tab -- cycle focus between widgets // Tab: scancode=0x0F, ascii=0x09 // Shift-Tab: scancode=0x0F, ascii=0x00 if (scancode == 0x0F && (ascii == 0x09 || ascii == 0)) { @@ -4017,7 +4246,7 @@ static void pollKeyboard(AppContextT *ctx) { WidgetT *w = fstack[--ftop]; if (w->focused && widgetIsFocusable(w->type)) { - // Don't tab out of the terminal — it swallows Tab + // Don't tab out of the terminal -- it swallows Tab if (w->type == WidgetAnsiTermE) { current = NULL; break; @@ -4034,7 +4263,7 @@ static void pollKeyboard(AppContextT *ctx) { } } - // Terminal swallowed Tab — send to widget system instead + // Terminal swallowed Tab -- send to widget system instead if (current == NULL) { // Check if a terminal is focused ftop = 0; @@ -4057,7 +4286,7 @@ static void pollKeyboard(AppContextT *ctx) { } if (termFocused) { - // Terminal has focus — send Tab to it + // Terminal has focus -- send Tab to it if (win->onKey) { win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags); } @@ -4192,13 +4421,13 @@ static void refreshMinimizedIcons(AppContextT *ctx) { iconIdx++; } - // Wrapped past the end — reset for next cycle + // Wrapped past the end -- reset for next cycle ctx->iconRefreshIdx = 0; } // ============================================================ -// repositionWindow — move/resize a window, dirty old & new, fire callbacks +// repositionWindow -- move/resize a window, dirty old & new, fire callbacks // ============================================================ // // Shared helper for tiling/cascading. Dirties both the old and new @@ -4248,12 +4477,12 @@ static void repositionWindow(AppContextT *ctx, WindowT *win, int32_t x, int32_t // rather than using a hardware cursor because VESA VBE doesn't provide // hardware cursor support, and hardware cursors on VGA are limited to // text mode. The shape priority is: -// 1. Active resize drag — keep the edge-specific resize cursor +// 1. Active resize drag -- keep the edge-specific resize cursor // 2. ListView column resize drag // 3. Splitter drag -// 4. Hover over resize edge — show directional resize cursor -// 5. Hover over ListView column border — horizontal resize cursor -// 6. Hover over splitter bar — orientation-specific resize cursor +// 4. Hover over resize edge -- show directional resize cursor +// 5. Hover over ListView column border -- horizontal resize cursor +// 6. Hover over splitter bar -- orientation-specific resize cursor // 7. Default arrow cursor static void updateCursorShape(AppContextT *ctx) { @@ -4288,7 +4517,7 @@ static void updateCursorShape(AppContextT *ctx) { else if (sDragSplitter) { newCursor = sDragSplitter->as.splitter.vertical ? CURSOR_RESIZE_H : CURSOR_RESIZE_V; } - // Not in an active drag/resize — check what we're hovering + // Not in an active drag/resize -- check what we're hovering else if (ctx->stack.dragWindow < 0 && ctx->stack.scrollWindow < 0) { int32_t hitPart; int32_t hitIdx = wmHitTest(&ctx->stack, mx, my, &hitPart); @@ -4313,7 +4542,7 @@ static void updateCursorShape(AppContextT *ctx) { newCursor = CURSOR_RESIZE_V; } } else if (hitIdx >= 0 && hitPart == HIT_CONTENT) { - // Hovering over content area — check for ListView column border + // Hovering over content area -- check for ListView column border WindowT *win = ctx->stack.windows[hitIdx]; if (win->widgetRoot) { @@ -4348,7 +4577,7 @@ static void updateCursorShape(AppContextT *ctx) { break; } - // Not on this splitter's bar — check children for nested splitters + // Not on this splitter's bar -- check children for nested splitters WidgetT *inner = NULL; for (WidgetT *c = hit->firstChild; c; c = c->nextSibling) { @@ -4372,7 +4601,7 @@ static void updateCursorShape(AppContextT *ctx) { // ============================================================ -// updateTooltip — show/hide tooltip based on hover state +// updateTooltip -- show/hide tooltip based on hover state // ============================================================ // // Tooltip lifecycle: when the mouse stops moving over a widget that has @@ -4391,7 +4620,7 @@ static void updateTooltip(AppContextT *ctx) { int32_t mx = ctx->mouseX; int32_t my = ctx->mouseY; - // Mouse moved or button pressed — hide tooltip and reset timer + // Mouse moved or button pressed -- hide tooltip and reset timer if (mx != ctx->prevMouseX || my != ctx->prevMouseY || ctx->mouseButtons) { if (ctx->tooltipText) { // Dirty old tooltip area diff --git a/dvx/dvxApp.h b/dvx/dvxApp.h index 651b896..c381855 100644 --- a/dvx/dvxApp.h +++ b/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 // all lower layers into a single AppContextT and provides a clean interface // 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. // // The event loop (dvxRun/dvxUpdate) follows a cooperative model: poll mouse // 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. #ifndef DVX_APP_H #define DVX_APP_H @@ -26,7 +26,7 @@ // ============================================================ // // 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 // 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 @@ -82,7 +82,7 @@ typedef struct AppContextT { void *ctrlEscCtx; void (*onTitleChange)(void *ctx); // called when any window title changes 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 // re-measuring on every paint frame. clock_t tooltipHoverStart; // when mouse stopped moving @@ -104,12 +104,13 @@ typedef struct AppContextT { // Available video modes (enumerated once at init) VideoModeInfoT *videoModes; // stb_ds dynamic array 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 - // kept so the image can be reloaded after a video mode change. - uint8_t *wallpaperBuf; // pixel data (screen width * height * bpp/8) - int32_t wallpaperPitch; // bytes per row - char wallpaperPath[260]; // source image path (empty = none) + // kept so the image can be reloaded after a video mode or mode change. + uint8_t *wallpaperBuf; // pixel data (screen width * height * bpp/8) + int32_t wallpaperPitch; // bytes per row + char wallpaperPath[260]; // source image path (empty = none) + WallpaperModeE wallpaperMode; // stretch, tile, or center } AppContextT; // 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"). const char *dvxColorName(ColorIdE id); +// Return a human-readable display label (e.g. "Desktop", "Cursor Color"). +const char *dvxColorLabel(ColorIdE id); + // ============================================================ // Wallpaper // ============================================================ -// Load and apply a wallpaper image (stretched to screen). Pass NULL -// to clear the wallpaper and revert to the solid desktop color. +// Load and apply a wallpaper image. Uses the current wallpaperMode +// (stretch/tile/center). Pass NULL to clear the wallpaper. 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). // count receives the number of entries. 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. 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. // 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); // 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. void dvxClipboardCopy(const char *text, int32_t len); diff --git a/dvx/dvxComp.c b/dvx/dvxComp.c index 63ae600..32c09c6 100644 --- a/dvx/dvxComp.c +++ b/dvx/dvxComp.c @@ -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 // avoids full-screen redraws, which would be prohibitively expensive on the // 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 // actually changed and flushing only those regions from the system RAM // 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 // 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. // 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 @@ -26,7 +26,7 @@ // overlap. A small gap tolerance absorbs jitter from mouse movement and // closely-spaced UI invalidations (e.g. title bar + content during a drag) // 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. #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 -// = 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. // // 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. 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)) { return; } @@ -64,7 +64,7 @@ void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) { dirtyListMerge(dl); 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 // repaint a potentially large region, but at least we won't lose // 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, // 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 -// 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. // a diagonal scatter of tiny rects). The cap of 3 was chosen empirically: // 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. // // 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. 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 // 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 -// 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. 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 // (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 // non-overlapping rects). Bounding-box was chosen because the merge // list is bounded to 128 entries and the extra repaint cost of a few diff --git a/dvx/dvxComp.h b/dvx/dvxComp.h index 494bc99..af5b8c9 100644 --- a/dvx/dvxComp.h +++ b/dvx/dvxComp.h @@ -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 // 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 // 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); // 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 // scanline of the rect. void flushRect(DisplayT *d, const RectT *r); diff --git a/dvx/dvxCursor.h b/dvx/dvxCursor.h index 83ff298..0635549 100644 --- a/dvx/dvxCursor.h +++ b/dvx/dvxCursor.h @@ -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 // before any file I/O infrastructure is ready, and embedding them avoids // a runtime dependency on a file path. @@ -37,7 +37,7 @@ // XOR data: 0 = black, 1 = white (where AND = 0) // // 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] = { 0x3FFF, // 0011111111111111 row 0 @@ -91,42 +91,57 @@ static const uint16_t cursorArrowXor[16] = { // .X..XX..X. // ..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] = { - 0xFFFF, // row 0 (transparent) - 0xFFFF, // row 1 - 0xFFFF, // row 2 - 0xF7EF, // 1111011111101111 row 3 arrows start - 0xE3C7, // 1110001111000111 row 4 - 0xC1A3, // 1100000110100011 row 5 - 0x8081, // 1000000010000001 row 6 - 0x0000, // 0000000000000000 row 7 center row - 0x8081, // 1000000010000001 row 8 - 0xC1A3, // 1100000110100011 row 9 - 0xE3C7, // 1110001111000111 row 10 - 0xF7EF, // 1111011111101111 row 11 - 0xFFFF, // row 12 - 0xFFFF, // row 13 - 0xFFFF, // row 14 - 0xFFFF // row 15 + 0xFFFF, // 1111111111111111 row 0 + 0xFFFF, // 1111111111111111 row 1 + 0xFFFF, // 1111111111111111 row 2 + 0xF18F, // 1111000110001111 row 3 outline top + 0xE187, // 1110000110000111 row 4 + 0xC183, // 1100000110000011 row 5 + 0x8001, // 1000000000000001 row 6 + 0x8001, // 1000000000000001 row 7 center + 0x8001, // 1000000000000001 row 8 + 0xC183, // 1100000110000011 row 9 + 0xE187, // 1110000110000111 row 10 + 0xF18F, // 1111000110001111 row 11 outline bottom + 0xFFFF, // 1111111111111111 row 12 + 0xFFFF, // 1111111111111111 row 13 + 0xFFFF, // 1111111111111111 row 14 + 0xFFFF // 1111111111111111 row 15 }; static const uint16_t cursorResizeHXor[16] = { - 0x0000, // row 0 - 0x0000, // row 1 - 0x0000, // row 2 - 0x0000, // row 3 - 0x0810, // 0000100000010000 row 4 - 0x1C38, // 0001110000111000 row 5 - 0x3E7C, // 0011111001111100 row 6 - 0x7FFE, // 0111111111111110 row 7 - 0x3E7C, // 0011111001111100 row 8 - 0x1C38, // 0001110000111000 row 9 - 0x0810, // 0000100000010000 row 10 - 0x0000, // row 11 - 0x0000, // row 12 - 0x0000, // row 13 - 0x0000, // row 14 - 0x0000 // row 15 + 0x0000, // 0000000000000000 row 0 + 0x0000, // 0000000000000000 row 1 + 0x0000, // 0000000000000000 row 2 + 0x0000, // 0000000000000000 row 3 + 0x0420, // 0000010000100000 row 4 arrow tips + 0x0C30, // 0000110000110000 row 5 + 0x1C38, // 0001110000111000 row 6 + 0x3FFC, // 0011111111111100 row 7 bases + stem + 0x1C38, // 0001110000111000 row 8 + 0x0C30, // 0000110000110000 row 9 + 0x0420, // 0000010000100000 row 10 arrow tips + 0x0000, // 0000000000000000 row 11 + 0x0000, // 0000000000000000 row 12 + 0x0000, // 0000000000000000 row 13 + 0x0000, // 0000000000000000 row 14 + 0x0000 // 0000000000000000 row 15 }; // ============================================================ @@ -302,7 +317,7 @@ static const CursorT dvxCursors[CURSOR_COUNT] = { { 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. static const CursorT dvxCursor = { 16, 16, 0, 0, cursorArrowAnd, cursorArrowXor }; diff --git a/dvx/dvxDialog.c b/dvx/dvxDialog.c index 389c385..2f285d8 100644 --- a/dvx/dvxDialog.c +++ b/dvx/dvxDialog.c @@ -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. // Both use the nested-event-loop modal pattern: the dialog creates a // window, sets it as the AppContext's modalWindow, then runs dvxUpdate // 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. // // 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, // 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 // keeps the state accessible to callback functions without threading // 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: @@ -122,7 +122,7 @@ static MsgBoxStateT sMsgBox; // 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 // 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 // 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 // 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. 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 // 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. WindowT *prevModal = ctx->modalWindow; 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 // button onClick callback or the window close callback. 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 @@ -454,7 +454,7 @@ static void onMsgBoxPaint(WindowT *win, RectT *dirtyArea) { cd.clipW = win->contentW; 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); // 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 // 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). // 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. 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 // 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 // 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: "*.*", "*", @@ -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) { @@ -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 @@ -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 @@ -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 @@ -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) { @@ -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 @@ -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) { @@ -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 @@ -1031,7 +1031,7 @@ static void fdOnListDblClick(WidgetT *w) { fdNavigate(dirName); wgtSetText(sFd.nameInput, ""); } 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]); 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: @@ -1055,7 +1055,7 @@ static void fdOnOk(WidgetT *w) { // 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, - // 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') { 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) { @@ -1116,7 +1116,7 @@ static void fdOnCancel(WidgetT *w) { // ============================================================ -// fdOnClose — window close button +// fdOnClose -- window close button // ============================================================ 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) { @@ -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) { @@ -1223,7 +1223,7 @@ bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const cha wgtLabel(filterRow, "F&ilter:"); 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]; int32_t fc = filterCount < 16 ? filterCount : 16; diff --git a/dvx/dvxDialog.h b/dvx/dvxDialog.h index c392063..64cd472 100644 --- a/dvx/dvxDialog.h +++ b/dvx/dvxDialog.h @@ -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 // block the caller and run their own event loop via dvxUpdate() until the diff --git a/dvx/dvxDraw.c b/dvx/dvxDraw.c index d0e3f59..e7d159a 100644 --- a/dvx/dvxDraw.c +++ b/dvx/dvxDraw.c @@ -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 // 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), // the pixel loops are fully unrolled into 8 direct array stores // 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. // // Clip rectangle handling: All draw functions clip against @@ -94,7 +94,7 @@ char accelParse(const char *text) { text++; if (*text == '&') { - // Escaped && — literal &, not an accelerator + // Escaped && -- literal &, not an accelerator text++; continue; } @@ -136,9 +136,9 @@ char accelParse(const char *text) { // clip region, w or h will be <= 0 and callers bail out. // // 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. -// __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 // four branches fall through untaken. On Pentium this keeps the // 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 // that emit exact spans via rectFill rather than looping. This // 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 // call already handles clipping internally, so the bevels clip // 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 // 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 -// 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), // 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) { // Opaque mode: every pixel in the cell gets written (fg or bg). // 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 // direct array stores with no loop, no table lookup. 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 // rep stosl), then iterate visible columns with sGlyphBit[] // 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. for (int32_t row = rowStart; row < rowEnd; 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). // // 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 // 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 // ============================================================ // -// 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 // 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), // so the indirect call overhead (~5 cycles on Pentium for the // mispredicted first call, then predicted afterward) is amortized -// over an entire row of pixels. The alternative — a switch inside -// rectFill's inner loop — would branch every scanline for no gain. +// over an entire row of pixels. The alternative -- a switch inside +// rectFill's inner loop -- would branch every scanline for no gain. // // The platform implementations (dvxPlatformDos.c) use inline asm: // 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 // within the visible (clipped) columns. For fully transparent rows // (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) { 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 // single bulk pass. Instead each cell is rendered individually, // 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. // // 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++; if (*text == '&') { - // Escaped && — draw literal & + // Escaped && -- draw literal & if (x + cw > d->clipX) { 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) { - // Accelerator character — draw it then underline + // Accelerator character -- draw it then underline if (x + cw > d->clipX) { drawChar(d, ops, font, x, y, *text, fg, bg, opaque); 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 -// 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 // different scanline. Instead we advance by d->pitch per pixel // 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 // regions (no memmove). That's fine because the source is always a // 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) { 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, // then fills one scanline at a time via the spanFill function // 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 // 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 -// 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 // over the string. This is used heavily for layout calculations // (centering text in buttons, sizing menu popups, etc.). @@ -1334,14 +1334,14 @@ int32_t textWidthAccel(const BitmapFontT *font, const char *text) { text++; if (*text == '&') { - // Escaped && — counts as one character + // Escaped && -- counts as one character w += font->charWidth; text++; continue; } if (*text) { - // Accelerator character — counts as one character, & is skipped + // Accelerator character -- counts as one character, & is skipped w += font->charWidth; text++; continue; diff --git a/dvx/dvxDraw.h b/dvx/dvxDraw.h index e39b44f..4c362d8 100644 --- a/dvx/dvxDraw.h +++ b/dvx/dvxDraw.h @@ -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 // rendering, bevels, lines, and cursor/icon rendering. Every function in // 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. // 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); // 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. void drawBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const BevelStyleT *style); diff --git a/dvx/dvxFont.h b/dvx/dvxFont.h index e59f1e7..f2b8f78 100644 --- a/dvx/dvxFont.h +++ b/dvx/dvxFont.h @@ -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 // 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 // pixel. Each glyph occupies charHeight consecutive bytes (14 or 16). // 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. // // CP437 includes the full ASCII printable range (32-126) plus box-drawing @@ -29,7 +29,7 @@ #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 // ============================================================ @@ -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 // ============================================================ diff --git a/dvx/dvxIcon.c b/dvx/dvxIcon.c index 7428a1e..ad08c3b 100644 --- a/dvx/dvxIcon.c +++ b/dvx/dvxIcon.c @@ -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. // 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) // // 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 // - Public domain license, no linking restrictions // - Small code footprint suitable for DOS targets diff --git a/dvx/dvxImageWrite.c b/dvx/dvxImageWrite.c index 4403ecb..da7f581 100644 --- a/dvx/dvxImageWrite.c +++ b/dvx/dvxImageWrite.c @@ -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 // (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 // so projects that don't need screenshot support can omit this file and // save the code size. diff --git a/dvx/dvxPalette.h b/dvx/dvxPalette.h index d823c4b..350f68f 100644 --- a/dvx/dvxPalette.h +++ b/dvx/dvxPalette.h @@ -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) // video modes. The palette layout follows the same strategy as the X11 // 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 // formula (r*36 + g*6 + b) enables O(1) color lookup without // searching. diff --git a/dvx/dvxPrefs.c b/dvx/dvxPrefs.c index 46d9907..69756ea 100644 --- a/dvx/dvxPrefs.c +++ b/dvx/dvxPrefs.c @@ -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 // 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) { 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"); if (!fp) { return false; } - sFilePath = dupStr(filename); - char line[512]; char *currentSection = dupStr(""); @@ -236,7 +238,7 @@ bool prefsLoad(const char *filename) { p++; } - // Blank line — store as comment to preserve formatting + // Blank line -- store as comment to preserve formatting if (*p == '\0') { PrefsEntryT e = {0}; e.section = dupStr(currentSection); diff --git a/dvx/dvxPrefs.h b/dvx/dvxPrefs.h index 5766cc8..96dfe37 100644 --- a/dvx/dvxPrefs.h +++ b/dvx/dvxPrefs.h @@ -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 // with caller-supplied defaults. Values can be modified at runtime diff --git a/dvx/dvxTypes.h b/dvx/dvxTypes.h index 1d8a377..9d346bc 100644 --- a/dvx/dvxTypes.h +++ b/dvx/dvxTypes.h @@ -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 // 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. // Populated once at startup from the VBE mode info block and then // 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 // them on the fly, which matters when called per-glyph during text // rendering on a 486. @@ -49,13 +49,13 @@ typedef struct { // The double-buffer strategy (backBuf in system RAM, lfb is the real // framebuffer) exists because writes to video memory over the PCI bus // 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 // copies (rep movsd). This is the single most important performance // decision in the entire compositor. // // 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 // extra parameter on every draw function and mirrors how classic windowing // systems (X11, GDI) handle clipping. @@ -123,7 +123,7 @@ typedef struct { // // Bevels are the defining visual element of the Motif/DESQview/X aesthetic. // 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. // // 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_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_SB_BUTTON(cs) (BevelStyleT){ (cs)->windowHighlight, (cs)->windowShadow, (cs)->scrollbarBg, 1 } // ============================================================ // Bitmap font @@ -154,7 +155,7 @@ typedef struct { // // Two font sizes are provided (8x14 and 8x16), matching the standard // 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 // 640x480 screens. The 8-pixel width also aligns nicely with byte // 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: // windowHighlight/windowShadow form bevel pairs, activeTitleBg gives // 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. typedef struct { @@ -204,6 +205,8 @@ typedef struct { uint32_t scrollbarBg; uint32_t scrollbarFg; uint32_t scrollbarTrough; + uint32_t cursorFg; + uint32_t cursorBg; } ColorSchemeT; // Color IDs for addressing individual colors in ColorSchemeT. @@ -227,9 +230,17 @@ typedef enum { ColorScrollbarBgE, ColorScrollbarFgE, ColorScrollbarTroughE, + ColorCursorFgE, + ColorCursorBgE, ColorCountE } ColorIdE; +typedef enum { + WallpaperStretchE, + WallpaperTileE, + WallpaperCenterE +} WallpaperModeE; + // Video mode entry (enumerated at init, available to apps) typedef struct { int32_t w; @@ -261,7 +272,7 @@ typedef struct { // // 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 -// 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. // // 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 // 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 // hardware: dragging a window across others doesn't force every obscured // window to repaint. @@ -533,7 +544,7 @@ typedef struct WindowT { // Accelerator table (NULL if none, caller owns allocation) 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 // low (no queuing/dispatching overhead) and matches the callback-driven // model that DOS programs expect. The widget system installs its own @@ -562,7 +573,7 @@ typedef struct WindowT { // which matters because WindowT is large. // // 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. typedef struct { @@ -596,7 +607,7 @@ typedef struct { // 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 // 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. // // 16 pixels wide using uint16_t rows, one word per scanline. This keeps diff --git a/dvx/dvxVideo.c b/dvx/dvxVideo.c index 409b33a..ffe2eb8 100644 --- a/dvx/dvxVideo.c +++ b/dvx/dvxVideo.c @@ -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 // 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 // bank math, allows rep movsd bulk copies, and simplifies every span // 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). // // 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 // 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 -// 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 // 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 -// lifting — VBE enumeration, mode scoring, LFB DPMI mapping, -// backbuffer allocation — lives in dvxPlatformDos.c so this file +// lifting -- VBE enumeration, mode scoring, LFB DPMI mapping, +// backbuffer allocation -- lives in dvxPlatformDos.c so this file // stays portable. The platform layer fills in every field of // DisplayT (dimensions, pitch, pixel format, lfb pointer, // backBuf pointer, palette, initial clip rect). diff --git a/dvx/dvxVideo.h b/dvx/dvxVideo.h index 877f127..5a8ba9c 100644 --- a/dvx/dvxVideo.h +++ b/dvx/dvxVideo.h @@ -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 // negotiation, linear framebuffer (LFB) mapping via DPMI, system RAM @@ -22,7 +22,7 @@ // Probes VBE for a mode matching the requested resolution and depth, // enables it, maps the LFB into the DPMI linear address space, and // 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); // Restores VGA text mode (INT 10h AH=0, mode 3), unmaps the LFB, and diff --git a/dvx/dvxWidget.h b/dvx/dvxWidget.h index aba5fe3..1953706 100644 --- a/dvx/dvxWidget.h +++ b/dvx/dvxWidget.h @@ -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. // Widgets form a tree (parent-child via firstChild/lastChild/nextSibling @@ -150,7 +150,7 @@ typedef enum { // ============================================================ typedef enum { - FrameInE, // beveled inward (sunken) — default + FrameInE, // beveled inward (sunken) -- default FrameOutE, // beveled outward (raised) FrameFlatE // solid color line } FrameStyleE; @@ -176,7 +176,7 @@ typedef enum { // state. The pointer indirection adds one dereference but saves // 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 // and DOS ANSI art rendering: cursor movement, color attributes (16-color // with bold-as-bright), scrolling regions, and blink. The parser is a @@ -201,7 +201,7 @@ typedef struct { // Scrolling region (0-based, inclusive) int32_t scrollTop; // top 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). // scrollPos tracks the view offset: when equal to scrollbackCount, // the user sees the live screen; when less, they're viewing history. @@ -216,7 +216,7 @@ typedef struct { // Cursor blink bool cursorOn; // current cursor blink phase 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 // 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 @@ -235,7 +235,7 @@ typedef struct { int32_t selEndLine; int32_t selEndCol; 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 // knowing the transport details. When all are NULL, the terminal is in // 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); } 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 // cellData[row * colCount + col]. The sortIndex is an indirection array // that maps displayed row numbers to data row numbers, allowing sort @@ -282,7 +282,7 @@ typedef struct WidgetT { WidgetTypeE type; // wclass points to the vtable for this widget type. Looked up once at // 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. const struct WidgetClassT *wclass; char name[MAX_WIDGET_NAME]; @@ -304,7 +304,7 @@ typedef struct WidgetT { int32_t w; 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 // its children if it's a container). The layout engine uses these as // the starting point for space allocation. @@ -356,7 +356,7 @@ typedef struct WidgetT { void (*onFocus)(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 // equivalent of a discriminated union / variant type. Using a union // instead of separate structs per widget type keeps all widget data @@ -365,6 +365,7 @@ typedef struct WidgetT { union { struct { const char *text; + WidgetAlignE textAlign; // AlignStartE=left, AlignCenterE, AlignEndE=right } label; struct { @@ -389,7 +390,7 @@ typedef struct WidgetT { // Text input has its own edit buffer (not a pointer to external // storage) so the widget fully owns its text lifecycle. The undo // 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. struct { char *buf; @@ -605,7 +606,7 @@ typedef struct WidgetT { // that fills the window's content area, and installs callback handlers // (onPaint, onMouse, onKey, onResize) that dispatch events to the widget // 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. 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); +void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align); WidgetT *wgtButton(WidgetT *parent, const char *text); WidgetT *wgtCheckbox(WidgetT *parent, const char *text); 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 -// 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 // lines for visual grouping. @@ -895,7 +897,7 @@ int32_t wgtAnsiTermPoll(WidgetT *w); // full widget paint pipeline (which would repaint the entire widget), this // renders only the dirty rows (tracked via the dirtyRows bitmask) directly // 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 // change rather than the full 80x25 grid. Returns the number of rows // 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). -// 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); // ============================================================ diff --git a/dvx/dvxWm.c b/dvx/dvxWm.c index 67e9115..5b2fc4f 100644 --- a/dvx/dvxWm.c +++ b/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 // (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 // 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 // avoids the latency of synchronous repaint-on-expose. // @@ -66,7 +66,7 @@ // Extracted into a struct so that both drawTitleBar() and wmHitTest() compute // identical geometry from the same code path (computeTitleGeom). Without this, // 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 { 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. // Positions are cached and only recomputed when positionsDirty is set (after // 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 // the window's screen position. 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 1: highlight (doubled for visual weight at low resolutions) // 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) // // 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 // 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) -// 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. 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 // saved/restored rather than set to the full dirty rect because this // 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. 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 -// 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 // notch (shadow line + highlight line) cutting across the full 4px border // 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 // buttons and thumb use 1px raised bevels for a clickable appearance; // 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 // 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) { int32_t x = winX + sb->x; @@ -492,7 +492,7 @@ static void drawScrollbar(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT * BevelStyleT btnBevel; btnBevel.highlight = colors->windowHighlight; btnBevel.shadow = colors->windowShadow; - btnBevel.face = colors->buttonFace; + btnBevel.face = colors->scrollbarBg; btnBevel.width = 1; 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 // isoceles triangle. SB_ARROW_ROWS (4) rows gives a 7-pixel-wide base, // 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. 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 cy = y + size / 2; - uint32_t fg = colors->contentFg; + uint32_t fg = colors->scrollbarFg; // Draw a small triangle 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. // // 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 // focus change; the rest of the chrome stays the same. This is why // 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 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. 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), // 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 -// 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. 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. // // 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 // will never wrap in practice. // // The content buffer is initialized to 0xFF (white) so newly created // 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). // // 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 // 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. 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 // 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 // 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->y = mouseY - stack->dragOffY; + if (win->maximized) { + win->maximized = false; + } + // Clamp: keep title bar reachable if (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 // 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. // The clip rect is saved/restored because the caller (compositeAndFlush) // 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 // drawing primitive, because we've already computed the exact intersection // 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. 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) // 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 // is rendered from the existing content buffer, so no extra rendering // 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, win->contentBuf, win->contentW, win->contentH, win->contentPitch, bpp); } else { - // No content — draw grey fill + // No content -- draw grey fill 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; } - // Title bar (drag area — between gadgets) + // Title bar (drag area -- between gadgets) if (my >= g.titleY && my < g.titleY + CHROME_TITLE_HEIGHT && mx >= g.titleX && mx < g.titleX + g.titleW) { *hitPart = HIT_TITLE; @@ -1653,7 +1657,7 @@ void wmInit(WindowStackT *stack) { // The content buffer must be reallocated because the content area changes // size. After reallocation, onResize notifies the app of the new dimensions, // 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 // 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 // (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 // 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). // // 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) { 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. // 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) // 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 // + 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 -// 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 edge = RESIZE_NONE; @@ -2047,12 +2051,12 @@ void wmResizeEnd(WindowStackT *stack) { // 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 // 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. // // If the user resizes while maximized, the maximized flag is cleared. // 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. 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 // 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 -// covered — it shows the window face color from drawBorderFrame). +// covered -- it shows the window face color from drawBorderFrame). void wmUpdateContentRect(WindowT *win) { int32_t topChrome = CHROME_TOTAL_TOP; diff --git a/dvx/dvxWm.h b/dvx/dvxWm.h index f709be1..5b56276 100644 --- a/dvx/dvxWm.h +++ b/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, // 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 // window's content is owned by the application (via callbacks or the widget // 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. // // 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 // 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. 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 // 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. 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); // 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. 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); // 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. void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *clipTo); diff --git a/dvx/platform/dvxPlatform.h b/dvx/platform/dvxPlatform.h index 503ad7f..1bd0dda 100644 --- a/dvx/platform/dvxPlatform.h +++ b/dvx/platform/dvxPlatform.h @@ -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 // interface. To port DVX to a new platform, implement a new // dvxPlatformXxx.c against this header. // // 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 -// 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 // // 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. 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 // multitaskers (Windows 3.x, OS/2, DESQview). On Linux this calls // 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 // 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. // Each scanline is copied as a contiguous block; rep movsd on DOS gives // 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) // ============================================================ // -// 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 // use inline assembly: rep stosl for fills (one instruction fills an // 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); // ============================================================ -// Input — Mouse +// Input -- Mouse // ============================================================ // 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, // 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 // to detect press/release edges. void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons); // Detect and activate mouse wheel support. Returns true if the mouse // 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. bool platformMouseWheelInit(void); @@ -150,7 +150,7 @@ void platformMouseSetAccel(int32_t threshold); void platformMouseWarp(int32_t x, int32_t y); // ============================================================ -// Input — Keyboard +// Input -- Keyboard // ============================================================ // Return the current modifier key state in BIOS shift-state format: @@ -167,7 +167,7 @@ int32_t platformKeyboardGetModifiers(void); bool platformKeyboardRead(PlatformKeyEventT *evt); // 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 // to their unshifted letter/digit. Returns 0 for scancodes that don't // 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. 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 // chdir() does not switch drives under DJGPP; this wrapper calls setdisk() // first when the path contains a drive prefix (e.g. "A:\DVX"). diff --git a/dvx/platform/dvxPlatformDos.c b/dvx/platform/dvxPlatformDos.c index d57a8f3..b5cde22 100644 --- a/dvx/platform/dvxPlatformDos.c +++ b/dvx/platform/dvxPlatformDos.c @@ -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 // 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 // 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 // a compatible driver. Polling via function 03h avoids the complexity // of installing a real-mode callback for mouse events. @@ -31,14 +31,16 @@ #include #include +#include #include #include #include #include #include +#include #include -// DJGPP-specific headers — this is the ONLY file that includes these +// DJGPP-specific headers -- this is the ONLY file that includes these #include #include #include @@ -132,7 +134,7 @@ static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t pref 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 // can read/write. We split it into seg:off for the INT 10h call. 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 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); if (vbeVersion < 0x0200) { 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. // // 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) { __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: // 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); if (!(attr & 0x0080)) { @@ -442,14 +444,14 @@ static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requ // access. // // 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. // This is necessary because DPMI runs in protected mode with // 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 // 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 // LFB address. Without this, all LFB access would require far // 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. // 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. // // 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. -// 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. // // Two code paths: @@ -580,7 +582,7 @@ void platformFlushRect(const DisplayT *d, const RectT *r) { *dst++ = *src++; } } 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 remainder = rowBytes & 3; 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]; @@ -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 @@ -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 @@ -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 // ============================================================ void platformInit(void) { - // Disable Ctrl+C/Break so the user can't accidentally kill the - // GUI while in graphics mode (which would leave the display in - // an unusable state without restoring text mode first). + // Disable Ctrl+C/Break at every level so the user can't + // accidentally kill the GUI while in graphics mode. + + // 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); + + // 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). // // 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 // 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). // 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 // for position/button state via polling (function 03h). @@ -1390,7 +1444,7 @@ char *platformPathDirEnd(const char *path) { // bus speed. // // 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. 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 -// 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) { __asm__ __volatile__ ( @@ -1487,7 +1541,7 @@ void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count) { uint8_t c = (uint8_t)color; 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)) { while (((uintptr_t)dst & 3) && count > 0) { *dst++ = c; @@ -1555,7 +1609,7 @@ void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count) { // 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 // 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: @@ -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 // 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. // 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: -// 1. findBestMode() — enumerate VESA modes and pick the best match -// 2. setVesaMode() — actually switch to the chosen mode with LFB -// 3. mapLfb() — DPMI-map the physical framebuffer into linear memory +// 1. findBestMode() -- enumerate VESA modes and pick the best match +// 2. setVesaMode() -- actually switch to the chosen mode with LFB +// 3. mapLfb() -- DPMI-map the physical framebuffer into linear memory // 4. Allocate system RAM backbuffer (same size as LFB) // 5. Set up 8-bit palette if needed // 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) // 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. void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) { diff --git a/dvx/widgets/widgetAnsiTerm.c b/dvx/widgets/widgetAnsiTerm.c index 103202a..044c762 100644 --- a/dvx/widgets/widgetAnsiTerm.c +++ b/dvx/widgets/widgetAnsiTerm.c @@ -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 // connecting to BBS systems over the serial link. The terminal uses a @@ -7,15 +7,15 @@ // chosen because: // 1. It maps directly to the BBS/ANSI art paradigm (CP437 character set, // 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 // 3. Dirty-row tracking via a 32-bit bitmask allows sub-millisecond // 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, // 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. // // Scrollback is implemented as a circular buffer of row snapshots. Only @@ -28,7 +28,7 @@ // - Fast repaint (wgtAnsiTermRepaint): bypasses the widget pipeline // entirely, rendering dirty rows directly into the window's content // 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. // // 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. -// 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 // is O(1) per row regardless of scrollback size, which matters when BBS // 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). // 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 // terminal selections (80 cols * 50 rows = 4000 chars max). 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). -// 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 // 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. @@ -345,7 +345,7 @@ static void ansiTermDeleteLines(WidgetT *w, int32_t count) { // Central CSI dispatcher. After the parser accumulates parameters in the CSI // 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 // parameter namespace than standard ECMA-48 sequences. 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) { // Respond as VT100 with advanced video option (AVO). // 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. const uint8_t reply[] = "\033[?1;2c"; 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 (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 // to synchronize cursor positioning in door games. char reply[16]; @@ -673,7 +673,7 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) { w->as.ansiTerm->scrollTop = top; w->as.ansiTerm->scrollBot = bot; } else { - // Invalid or reset — restore full screen + // Invalid or reset -- restore full screen w->as.ansiTerm->scrollTop = 0; 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. // 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). // The wasAtBottom check ensures auto-scroll tracking: if the user was // 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 ansiTermFillCells(w, 0, cur + 1); } 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); 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. // 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 // 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 // combined scrollback+screen view. -// lineIndex < scrollbackCount → scrollback line -// lineIndex >= scrollbackCount → screen line +// lineIndex < scrollbackCount -> scrollback 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 // circular buffer index computation uses modular arithmetic to map from // 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. -// 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 // (no tables, no indirect calls) because this runs on every incoming byte // and branch prediction on a 486/Pentium benefits from straightforward // if/switch chains. // // In PARSE_NORMAL, C0 control characters (CR, LF, BS, TAB, FF, BEL, ESC) -// are handled first. All other bytes — including CP437 graphic characters -// in the 0x01-0x1F range that aren't C0 controls, plus 0x80-0xFF — are +// are handled first. All other bytes -- including CP437 graphic characters +// in the 0x01-0x1F range that aren't C0 controls, plus 0x80-0xFF -- are // treated as printable and placed at the cursor via ansiTermPutChar. 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; } } else if (ch == '\f') { - // Form feed — clear screen and home cursor + // Form feed -- clear screen and home cursor ansiTermEraseDisplay(w, 2); w->as.ansiTerm->cursorRow = 0; w->as.ansiTerm->cursorCol = 0; } else if (ch == '\a') { - // Bell — ignored + // Bell -- ignored } else { // CP437 graphic characters (smileys, card suits, etc.) // and all printable characters @@ -962,15 +962,15 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) { w->as.ansiTerm->csiPrivate = false; memset(w->as.ansiTerm->params, 0, sizeof(w->as.ansiTerm->params)); } else if (ch == 'D') { - // IND — scroll up one line + // IND -- scroll up one line ansiTermScrollUp(w); w->as.ansiTerm->parseState = PARSE_NORMAL; } else if (ch == 'M') { - // RI — scroll down one line + // RI -- scroll down one line ansiTermScrollDown(w); w->as.ansiTerm->parseState = PARSE_NORMAL; } else if (ch == 'c') { - // RIS — terminal reset + // RIS -- terminal reset w->as.ansiTerm->cursorRow = 0; w->as.ansiTerm->cursorCol = 0; w->as.ansiTerm->curAttr = ANSI_DEFAULT_ATTR; @@ -983,7 +983,7 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) { ansiTermEraseDisplay(w, 2); w->as.ansiTerm->parseState = PARSE_NORMAL; } else { - // Unknown escape — return to normal + // Unknown escape -- return to normal w->as.ansiTerm->parseState = PARSE_NORMAL; } break; @@ -1011,11 +1011,11 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) { w->as.ansiTerm->paramCount++; } } else if (ch >= 0x40 && ch <= 0x7E) { - // Final byte — dispatch the CSI sequence + // Final byte -- dispatch the CSI sequence ansiTermDispatchCsi(w, ch); w->as.ansiTerm->parseState = PARSE_NORMAL; } else { - // Unexpected byte — abort sequence + // Unexpected byte -- abort sequence w->as.ansiTerm->parseState = PARSE_NORMAL; } break; @@ -1061,7 +1061,7 @@ static void ansiTermProcessSgr(WidgetT *w) { w->as.ansiTerm->bold = true; fg |= 8; } 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; } else if (code == 25) { // Blink off @@ -1072,7 +1072,7 @@ static void ansiTermProcessSgr(WidgetT *w) { fg = bg; bg = tmp; } else if (code == 8) { - // Invisible — foreground same as background + // Invisible -- foreground same as background fg = bg & 0x07; } else if (code == 22) { // 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 -// 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 // small; terminal state is substantial (~100+ bytes of fields plus the // 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 // from the application's main loop at a reasonable frequency. It handles three // 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 // 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. -// 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 // responsiveness (smaller = more frequent repaints) and throughput // (larger = fewer function call overhead per byte). int32_t wgtAnsiTermPoll(WidgetT *w) { 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 blinkInterval = (clock_t)BLINK_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. // Fast repaint: renders dirty rows directly into the window's content buffer, -// completely bypassing the normal widget paint pipeline (widgetOnPaint → -// widgetPaintOne → full tree walk). Returns the number of rows repainted +// completely bypassing the normal widget paint pipeline (widgetOnPaint -> +// widgetPaintOne -> full tree walk). Returns the number of rows repainted // and optionally reports the vertical extent via outY/outH so the caller // can issue a minimal compositor dirty rect. // // 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 // 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. int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH) { 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 -// 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 // 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)) { @@ -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 // selection is cleared first since the screen content is changing and 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. void wgtAnsiTermWrite(WidgetT *w, const uint8_t *data, int32_t len) { 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 -// 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 // convention of 80x25 or similar fixed screen sizes. void widgetAnsiTermCalcMinSize(WidgetT *w, const BitmapFontT *font) { @@ -1763,7 +1763,7 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod) { 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 @@ -1806,39 +1806,39 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod) { buf[0] = 0x08; len = 1; } else if (key == (0x48 | 0x100)) { - // Up arrow → ESC[A + // Up arrow -> ESC[A buf[0] = 0x1B; buf[1] = '['; buf[2] = 'A'; len = 3; } else if (key == (0x50 | 0x100)) { - // Down arrow → ESC[B + // Down arrow -> ESC[B buf[0] = 0x1B; buf[1] = '['; buf[2] = 'B'; len = 3; } else if (key == (0x4D | 0x100)) { - // Right arrow → ESC[C + // Right arrow -> ESC[C buf[0] = 0x1B; buf[1] = '['; buf[2] = 'C'; len = 3; } else if (key == (0x4B | 0x100)) { - // Left arrow → ESC[D + // Left arrow -> ESC[D buf[0] = 0x1B; buf[1] = '['; buf[2] = 'D'; len = 3; } else if (key == (0x47 | 0x100)) { - // Home → ESC[H + // Home -> ESC[H buf[0] = 0x1B; buf[1] = '['; buf[2] = 'H'; len = 3; } else if (key == (0x4F | 0x100)) { - // End → ESC[F + // End -> ESC[F buf[0] = 0x1B; buf[1] = '['; buf[2] = 'F'; len = 3; } else if (key == (0x49 | 0x100)) { - // PgUp → ESC[5~ + // PgUp -> ESC[5~ buf[0] = 0x1B; buf[1] = '['; buf[2] = '5'; buf[3] = '~'; len = 4; } else if (key == (0x51 | 0x100)) { - // PgDn → ESC[6~ + // PgDn -> ESC[6~ buf[0] = 0x1B; buf[1] = '['; buf[2] = '6'; buf[3] = '~'; len = 4; } else if (key == (0x53 | 0x100)) { - // Delete → ESC[3~ + // Delete -> ESC[3~ buf[0] = 0x1B; buf[1] = '['; buf[2] = '3'; buf[3] = '~'; len = 4; } 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 // page-up/page-down regions, with proportional thumb positioning. // 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) { AppContextT *actx = (AppContextT *)root->userData; 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 arrowH = ANSI_SB_W; - // Click in text area — start selection + // Click in text area -- start selection if (vx < sbX) { int32_t baseX = hit->x + 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 hit->as.ansiTerm->scrollPos++; } 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 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 // widget paint pipeline (e.g., on window expose or full invalidation). // 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 terminal renders its own scrollbar rather than using the shared // 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 // virtual content area. 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; 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); return; } diff --git a/dvx/widgets/widgetBox.c b/dvx/widgets/widgetBox.c index bb16fd7..f6caecb 100644 --- a/dvx/widgets/widgetBox.c +++ b/dvx/widgets/widgetBox.c @@ -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 -// 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 // widgetLayout.c (widgetCalcMinSizeBox / widgetLayoutBox) which handles // weight-based space distribution, spacing, padding, and alignment. // // VBox and HBox are distinguished by a flag (WCLASS_HORIZ_CONTAINER) in // 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". // // Frame is a labeled grouping box with a Motif-style beveled border. // It acts as a VBox for layout purposes (children stack vertically inside // the frame's padded interior). The title text sits centered vertically // 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. #include "widgetInternal.h" @@ -27,14 +27,14 @@ // 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. -// 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 // border pixels, then the title is drawn on top. // // Three border styles are supported: -// FrameFlatE — single-pixel solid color outline -// FrameInE — Motif "groove" (inset bevel: shadow-then-highlight) -// FrameOutE — Motif "ridge" (outset bevel: highlight-then-shadow) +// FrameFlatE -- single-pixel solid color outline +// FrameInE -- Motif "groove" (inset bevel: shadow-then-highlight) +// FrameOutE -- Motif "ridge" (outset bevel: highlight-then-shadow) // 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) { 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 // (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 *w = widgetAlloc(parent, WidgetFrameE); diff --git a/dvx/widgets/widgetButton.c b/dvx/widgets/widgetButton.c index bf4ef69..6ea624b 100644 --- a/dvx/widgets/widgetButton.c +++ b/dvx/widgets/widgetButton.c @@ -1,9 +1,9 @@ -// widgetButton.c — Button widget +// widgetButton.c -- Button widget // // Standard push button with text label, Motif-style 2px beveled border, // and press animation. The button uses a two-phase press model: // - 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, // 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 @@ -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. -// 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 // 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. void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg; diff --git a/dvx/widgets/widgetCanvas.c b/dvx/widgets/widgetCanvas.c index ce4cac9..3741e50 100644 --- a/dvx/widgets/widgetCanvas.c +++ b/dvx/widgets/widgetCanvas.c @@ -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 // format that applications can draw into directly. It stores pixels in // 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 // 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 // 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 // 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 // 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, -// 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. 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 // 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 - // 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; for (int32_t dy = -rad; dy <= rad; dy++) { @@ -106,7 +106,7 @@ static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) { 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 rem = r2 - dy2; 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. // 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 -// 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). 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 // 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 -// 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. WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h) { if (!parent || w <= 0 || h <= 0) { @@ -401,7 +401,7 @@ void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) { 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 rem = r2 - dy2; 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. // 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. int32_t wgtCanvasLoad(WidgetT *w, const char *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 // 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. void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *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 // 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. void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) { (void)font; diff --git a/dvx/widgets/widgetCheckbox.c b/dvx/widgets/widgetCheckbox.c index 1ab7dfb..a7f35be 100644 --- a/dvx/widgets/widgetCheckbox.c +++ b/dvx/widgets/widgetCheckbox.c @@ -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 // 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. // // State management is simple: a boolean 'checked' flag toggles on each click diff --git a/dvx/widgets/widgetClass.c b/dvx/widgets/widgetClass.c index 65b8ac6..28cb682 100644 --- a/dvx/widgets/widgetClass.c +++ b/dvx/widgets/widgetClass.c @@ -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. // 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 // their class definitions, enabling O(1) dispatch. // // 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 // statements need modification, reducing the risk of forgetting // a case. @@ -20,13 +20,13 @@ // like hit testing and layout. // // Class flags: -// WCLASS_FOCUSABLE — widget can receive keyboard focus (Tab order) -// WCLASS_BOX_CONTAINER — uses the generic box layout engine (VBox/HBox) -// WCLASS_HORIZ_CONTAINER — lays out children horizontally (HBox variant) -// WCLASS_PAINTS_CHILDREN — widget handles its own child painting (TabControl, +// WCLASS_FOCUSABLE -- widget can receive keyboard focus (Tab order) +// WCLASS_BOX_CONTAINER -- uses the generic box layout engine (VBox/HBox) +// WCLASS_HORIZ_CONTAINER -- lays out children horizontally (HBox variant) +// WCLASS_PAINTS_CHILDREN -- widget handles its own child painting (TabControl, // TreeView, ScrollPane, Splitter). The default paint // 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 // manage their own internal click regions (TreeView, // ScrollPane, ListView, Splitter). @@ -328,9 +328,9 @@ static const WidgetClassT sClassToolbar = { }; // 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) -// 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 // and item Y coordinates, rather than letting the hit tester // recurse into child TreeItem widgets @@ -347,7 +347,7 @@ static const WidgetClassT sClassTreeView = { .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. // TreeItem exists as a WidgetT so it can participate in the tree // 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. // Indexed by the WidgetTypeE enum, it provides O(1) lookup of any // widget type's class definition. Every WidgetT stores a pointer // 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. // // Using C99 designated initializers ensures the array slots match diff --git a/dvx/widgets/widgetComboBox.c b/dvx/widgets/widgetComboBox.c index 35bca24..594591d 100644 --- a/dvx/widgets/widgetComboBox.c +++ b/dvx/widgets/widgetComboBox.c @@ -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 // supports full editing (cursor movement, selection, undo, clipboard) via @@ -12,7 +12,7 @@ // // The popup list is painted as an overlay (widgetComboBoxPaintPopup) that // 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 // 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 // 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; 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; } - // Button click — toggle popup + // Button click -- toggle popup w->as.comboBox.open = !w->as.comboBox.open; w->as.comboBox.hoverIdx = w->as.comboBox.selectedIdx; sOpenPopup = w->as.comboBox.open ? w : NULL; } else { - // Text area click — focus for editing + // Text area click -- focus for editing clearOtherSelections(w); AppContextT *ctx = (AppContextT *)root->userData; @@ -300,7 +300,7 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) { // 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 // selection highlighting (up to 3 text runs: pre-selection, selection, // post-selection). The dropdown button has a small triangular arrow glyph diff --git a/dvx/widgets/widgetCore.c b/dvx/widgets/widgetCore.c index 438370f..abea208 100644 --- a/dvx/widgets/widgetCore.c +++ b/dvx/widgets/widgetCore.c @@ -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, // 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 // 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. // // 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 // 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, // 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 // not visible (inactive tab), its accelKey is still checked. This // 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. 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 // after 'after' in depth-first tree order. The two-pass approach // (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 // during traversal. Once past it, the next focusable widget is the // 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) { if (!w->visible || !w->enabled) { @@ -364,7 +364,7 @@ WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) { return found; } - // Wrap around — search from the beginning + // Wrap around -- search from the beginning pastAfter = true; 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, // 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 // TreeView, ScrollPane, ListView, and Splitter, which need to manage // their own internal regions (scrollbars, column headers, tree @@ -496,7 +496,7 @@ WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y) { 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; 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. // // 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. // // 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 // (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 // much content is scrollable. SB_MIN_THUMB prevents the thumb from // becoming too small to grab with a mouse. diff --git a/dvx/widgets/widgetDropdown.c b/dvx/widgets/widgetDropdown.c index f821b22..9a47a7f 100644 --- a/dvx/widgets/widgetDropdown.c +++ b/dvx/widgets/widgetDropdown.c @@ -1,7 +1,7 @@ -// widgetDropdown.c — Dropdown (select) widget +// widgetDropdown.c -- Dropdown (select) widget // // A non-editable dropdown list (HTML