From 7ae7ea1a97e299a1e6a76fddbfb47a1c21aed11f Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Sun, 22 Mar 2026 18:02:30 -0500 Subject: [PATCH] Code cleanup. --- apps/clock/clock.c | 15 +- apps/cpanel/cpanel.c | 88 ++++++----- apps/imgview/imgview.c | 6 +- apps/notepad/notepad.c | 12 +- apps/progman/progman.c | 21 +-- dvx/dvxApp.c | 284 +++++++++++++++++++++------------- dvx/dvxWidget.h | 3 + dvx/platform/dvxPlatformDos.c | 22 ++- dvx/widgets/widgetComboBox.c | 17 +- dvx/widgets/widgetCore.c | 38 +++++ dvx/widgets/widgetDropdown.c | 21 +-- dvx/widgets/widgetEvent.c | 2 + dvx/widgets/widgetInternal.h | 6 + dvx/widgets/widgetListBox.c | 12 +- dvx/widgets/widgetListView.c | 19 +++ dvx/widgets/widgetOps.c | 21 +++ dvx/widgets/widgetTreeView.c | 45 +++++- dvxshell/shellTaskMgr.c | 110 +++++++------ 18 files changed, 463 insertions(+), 279 deletions(-) diff --git a/apps/clock/clock.c b/apps/clock/clock.c index d0e904f..0327cba 100644 --- a/apps/clock/clock.c +++ b/apps/clock/clock.c @@ -35,6 +35,11 @@ // Module state // ============================================================ +#define CLOCK_WIN_W 200 +#define CLOCK_WIN_H 100 +#define CLOCK_MARGIN 40 +#define CLOCK_DATE_GAP 8 + typedef struct { bool quit; char timeStr[32]; @@ -125,7 +130,7 @@ static void onPaint(WindowT *win, RectT *dirty) { // Date string (centered below) int32_t dateW = textWidth(font, sState.dateStr); int32_t dateX = (win->contentW - dateW) / 2; - int32_t dateY = timeY + font->charHeight + 8; + int32_t dateY = timeY + font->charHeight + CLOCK_DATE_GAP; drawText(&cd, ops, font, dateX, dateY, sState.dateStr, colors->contentFg, colors->contentBg, true); } @@ -182,10 +187,10 @@ int32_t appMain(DxeAppContextT *ctx) { updateTime(); // Position in the upper-right corner, out of the way of other windows - int32_t winW = 200; - int32_t winH = 100; - int32_t winX = ac->display.width - winW - 40; - int32_t winY = 40; + int32_t winW = CLOCK_WIN_W; + int32_t winH = CLOCK_WIN_H; + int32_t winX = ac->display.width - winW - CLOCK_MARGIN; + int32_t winY = CLOCK_MARGIN; // resizable=false: clock has a fixed size, no resize handle sWin = dvxCreateWindow(ac, "Clock", winX, winY, winW, winH, false); diff --git a/apps/cpanel/cpanel.c b/apps/cpanel/cpanel.c index 4ceefba..adb41d4 100644 --- a/apps/cpanel/cpanel.c +++ b/apps/cpanel/cpanel.c @@ -29,9 +29,23 @@ // Constants // ============================================================ -#define CP_WIN_W 460 -#define CP_WIN_H 340 -#define WPAPER_DIR "CONFIG/WPAPER" +#define CP_WIN_W 460 +#define CP_WIN_H 340 +#define CP_SPACING 8 +#define CP_SPACING_SMALL 4 +#define CP_THEME_BTN_W 60 +#define CP_SAVE_BTN_W 70 +#define CP_SWATCH_W 64 +#define CP_SWATCH_H 24 +#define CP_DBLCLICK_LBL_W 52 +#define CP_TEST_BTN_W 100 +#define CP_APPLY_BTN_W 100 +#define CP_OK_CANCEL_BTN_W 80 +#define CP_CONFIRM_W 300 +#define CP_CONFIRM_H 100 +#define CP_CONFIRM_BTN_W 70 +#define CP_WPAPER_BTN_W 90 +#define WPAPER_DIR "CONFIG/WPAPER" #define THEME_DIR "CONFIG/THEMES" #define THEME_EXT ".THM" @@ -156,12 +170,12 @@ static void buildColorsTab(WidgetT *page) { // Color list on the left, sliders on the right WidgetT *hbox = wgtHBox(page); hbox->weight = 100; - hbox->spacing = wgtPixels(8); + hbox->spacing = wgtPixels(CP_SPACING); // Left side: color name list + theme controls WidgetT *leftVbox = wgtVBox(hbox); leftVbox->weight = 50; - leftVbox->spacing = wgtPixels(4); + leftVbox->spacing = wgtPixels(CP_SPACING_SMALL); wgtLabel(leftVbox, "System Colors:"); @@ -180,7 +194,7 @@ static void buildColorsTab(WidgetT *page) { // Theme row WidgetT *themeRow = wgtHBox(leftVbox); - themeRow->spacing = wgtPixels(4); + themeRow->spacing = wgtPixels(CP_SPACING_SMALL); wgtLabel(themeRow, "Theme:"); @@ -191,19 +205,19 @@ static void buildColorsTab(WidgetT *page) { WidgetT *loadBtn = wgtButton(themeRow, "Apply"); loadBtn->onClick = onApplyTheme; - loadBtn->prefW = wgtPixels(60); + loadBtn->prefW = wgtPixels(CP_THEME_BTN_W); WidgetT *browseBtn = wgtButton(themeRow, "Load..."); browseBtn->onClick = onBrowseTheme; - browseBtn->prefW = wgtPixels(60); + browseBtn->prefW = wgtPixels(CP_THEME_BTN_W); WidgetT *saveBtn = wgtButton(themeRow, "Save As..."); saveBtn->onClick = onSaveTheme; - saveBtn->prefW = wgtPixels(70); + saveBtn->prefW = wgtPixels(CP_SAVE_BTN_W); WidgetT *resetBtn = wgtButton(themeRow, "Reset"); resetBtn->onClick = onResetColors; - resetBtn->prefW = wgtPixels(60); + resetBtn->prefW = wgtPixels(CP_THEME_BTN_W); // Right side: RGB sliders in a non-weighted inner box so they stay // at natural size. The outer box absorbs extra vertical space. @@ -211,7 +225,7 @@ static void buildColorsTab(WidgetT *page) { rightVbox->weight = 50; WidgetT *sliderBox = wgtVBox(rightVbox); - sliderBox->spacing = wgtPixels(4); + sliderBox->spacing = wgtPixels(CP_SPACING_SMALL); wgtLabel(sliderBox, "Red:"); sRedSldr = wgtSlider(sliderBox, 0, 255); @@ -232,7 +246,7 @@ static void buildColorsTab(WidgetT *page) { wgtLabelSetAlign(sBlueLbl, AlignEndE); wgtLabel(sliderBox, "Preview:"); - sColorSwatch = wgtCanvas(sliderBox, 64, 24); + sColorSwatch = wgtCanvas(sliderBox, CP_SWATCH_W, CP_SWATCH_H); // Absorb remaining vertical space below the sliders wgtSpacer(rightVbox)->weight = 100; @@ -257,19 +271,19 @@ static void buildDesktopTab(WidgetT *page) { sWallpaperLbl = wgtLabel(page, sWallpaperPath[0] ? sWallpaperPath : "(none)"); WidgetT *btnRow = wgtHBox(page); - btnRow->spacing = wgtPixels(8); + btnRow->spacing = wgtPixels(CP_SPACING); WidgetT *applyBtn = wgtButton(btnRow, "Apply"); applyBtn->onClick = onApplyWallpaper; - applyBtn->prefW = wgtPixels(90); + applyBtn->prefW = wgtPixels(CP_WPAPER_BTN_W); WidgetT *chooseBtn = wgtButton(btnRow, "Browse..."); chooseBtn->onClick = onChooseWallpaper; - chooseBtn->prefW = wgtPixels(90); + chooseBtn->prefW = wgtPixels(CP_WPAPER_BTN_W); WidgetT *clearBtn = wgtButton(btnRow, "Clear"); clearBtn->onClick = onClearWallpaper; - clearBtn->prefW = wgtPixels(90); + clearBtn->prefW = wgtPixels(CP_WPAPER_BTN_W); wgtSpacer(btnRow)->weight = 100; wgtLabel(btnRow, "Mode:"); @@ -291,7 +305,7 @@ static void buildMouseTab(WidgetT *page) { // Scroll direction WidgetT *wheelRow = wgtHBox(page); - wheelRow->spacing = wgtPixels(8); + wheelRow->spacing = wgtPixels(CP_SPACING); wgtLabel(wheelRow, "Scroll Wheel:"); static const char *wheelItems[] = {"Normal", "Reversed"}; @@ -301,13 +315,13 @@ static void buildMouseTab(WidgetT *page) { wgtDropdownSetItems(sWheelDrop, wheelItems, 2); wgtDropdownSetSelected(sWheelDrop, sAc->wheelDirection < 0 ? 1 : 0); - wgtSpacer(page)->prefH = wgtPixels(4); + wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL); // Double-click speed wgtLabel(page, "Double-Click Speed:"); WidgetT *dblRow = wgtHBox(page); - dblRow->spacing = wgtPixels(8); + dblRow->spacing = wgtPixels(CP_SPACING); wgtLabel(dblRow, "Fast"); sDblClickSldr = wgtSlider(dblRow, 200, 900); @@ -318,14 +332,14 @@ static void buildMouseTab(WidgetT *page) { wgtSliderSetValue(sDblClickSldr, dblMs); wgtLabel(dblRow, "Slow "); sDblClickLbl = wgtLabel(dblRow, ""); - sDblClickLbl->prefW = wgtPixels(52); + sDblClickLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W); updateDblClickLabel(); - wgtSpacer(page)->prefH = wgtPixels(4); + wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL); // Acceleration WidgetT *accelRow = wgtHBox(page); - accelRow->spacing = wgtPixels(8); + accelRow->spacing = wgtPixels(CP_SPACING); wgtLabel(accelRow, "Acceleration:"); static const char *accelItems[] = {"Off", "Low", "Medium", "High"}; @@ -346,15 +360,15 @@ static void buildMouseTab(WidgetT *page) { wgtDropdownSetSelected(sAccelDrop, 2); } - wgtSpacer(page)->prefH = wgtPixels(4); + wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL); // Double-click test area WidgetT *testRow = wgtHBox(page); - testRow->spacing = wgtPixels(8); + testRow->spacing = wgtPixels(CP_SPACING); WidgetT *testBtn = wgtButton(testRow, "Test Area"); testBtn->onDblClick = onDblClickTest; - testBtn->prefW = wgtPixels(100); + testBtn->prefW = wgtPixels(CP_TEST_BTN_W); sDblTestLbl = wgtLabel(testRow, "Double-click the button to test"); } @@ -410,7 +424,7 @@ static void buildVideoTab(WidgetT *page) { WidgetT *applyBtn = wgtButton(page, "Apply Mode"); applyBtn->onClick = onVideoApply; - applyBtn->prefW = wgtPixels(100); + applyBtn->prefW = wgtPixels(CP_APPLY_BTN_W); } @@ -710,7 +724,7 @@ static void onVideoApply(WidgetT *w) { // clicks Yes the mode is kept; No or timeout reverts to the old mode. #define CONFIRM_SECONDS 10 - WindowT *confirmWin = dvxCreateWindowCentered(sAc, "Keep this mode?", 300, 100, false); + WindowT *confirmWin = dvxCreateWindowCentered(sAc, "Keep this mode?", CP_CONFIRM_W, CP_CONFIRM_H, false); if (!confirmWin) { dvxChangeVideoMode(sAc, oldW, oldH, oldBpp); @@ -723,7 +737,7 @@ static void onVideoApply(WidgetT *w) { sAc->modalWindow = confirmWin; WidgetT *root = wgtInitWindow(sAc, confirmWin); - root->spacing = wgtPixels(8); + root->spacing = wgtPixels(CP_SPACING); static char msgBuf[80]; snprintf(msgBuf, sizeof(msgBuf), "Reverting in %d seconds...", CONFIRM_SECONDS); @@ -731,14 +745,14 @@ static void onVideoApply(WidgetT *w) { WidgetT *btnRow = wgtHBox(root); btnRow->align = AlignEndE; - btnRow->spacing = wgtPixels(8); + btnRow->spacing = wgtPixels(CP_SPACING); WidgetT *yesBtn = wgtButton(btnRow, "Yes"); - yesBtn->prefW = wgtPixels(70); + yesBtn->prefW = wgtPixels(CP_CONFIRM_BTN_W); yesBtn->onClick = onVideoConfirmYes; WidgetT *noBtn = wgtButton(btnRow, "No"); - noBtn->prefW = wgtPixels(70); + noBtn->prefW = wgtPixels(CP_CONFIRM_BTN_W); noBtn->onClick = onVideoConfirmNo; dvxFitWindow(sAc, confirmWin); @@ -849,7 +863,7 @@ static void saveSnapshot(void) { memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb)); sSavedWheelDir = sAc->wheelDirection; sSavedDblClick = (int32_t)(sAc->dblClickTicks * 1000 / CLOCKS_PER_SEC); - sSavedAccel = 0; + sSavedAccel = wgtDropdownGetSelected(sAccelDrop); sSavedVideoW = sAc->display.width; sSavedVideoH = sAc->display.height; sSavedVideoBpp = sAc->display.format.bitsPerPixel; @@ -867,8 +881,8 @@ static void restoreSnapshot(void) { dvxApplyColorScheme(sAc); // Restore mouse - const char *accelStr = prefsGetString("mouse", "acceleration", "medium"); - int32_t accelVal = mapAccelName(accelStr); + const char *accelName = mapAccelValue(sSavedAccel); + int32_t accelVal = mapAccelName(accelName); dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, accelVal); // Restore video mode if changed @@ -1083,15 +1097,15 @@ int32_t appMain(DxeAppContextT *ctx) { // OK / Cancel buttons at bottom WidgetT *btnRow = wgtHBox(root); btnRow->align = AlignEndE; - btnRow->spacing = wgtPixels(8); + btnRow->spacing = wgtPixels(CP_SPACING); WidgetT *okBtn = wgtButton(btnRow, "OK"); okBtn->onClick = onOk; - okBtn->prefW = wgtPixels(80); + okBtn->prefW = wgtPixels(CP_OK_CANCEL_BTN_W); WidgetT *cancelBtn = wgtButton(btnRow, "Cancel"); cancelBtn->onClick = onCancel; - cancelBtn->prefW = wgtPixels(80); + cancelBtn->prefW = wgtPixels(CP_OK_CANCEL_BTN_W); dvxFitWindow(sAc, sWin); return 0; diff --git a/apps/imgview/imgview.c b/apps/imgview/imgview.c index 9147fd9..b493d9f 100644 --- a/apps/imgview/imgview.c +++ b/apps/imgview/imgview.c @@ -175,10 +175,8 @@ static void buildScaled(int32_t fitW, int32_t fitH) { static void loadAndDisplay(const char *path) { // Free previous image - if (sImgRgb) { - stbi_image_free(sImgRgb); - sImgRgb = NULL; - } + stbi_image_free(sImgRgb); + sImgRgb = NULL; int32_t channels; sImgRgb = stbi_load(path, &sImgW, &sImgH, &channels, 3); diff --git a/apps/notepad/notepad.c b/apps/notepad/notepad.c index f91fb27..78fd380 100644 --- a/apps/notepad/notepad.c +++ b/apps/notepad/notepad.c @@ -42,6 +42,10 @@ #define CMD_PASTE 202 #define CMD_SELALL 203 +#define NP_WIN_W 480 +#define NP_WIN_H 360 +#define NP_CASCADE_OFFSET 20 + // ============================================================ // Module state // ============================================================ @@ -333,10 +337,10 @@ int32_t appMain(DxeAppContextT *ctx) { int32_t screenW = ac->display.width; int32_t screenH = ac->display.height; - int32_t winW = 480; - int32_t winH = 360; - int32_t winX = (screenW - winW) / 2 + 20; - int32_t winY = (screenH - winH) / 3 + 20; + int32_t winW = NP_WIN_W; + int32_t winH = NP_WIN_H; + int32_t winX = (screenW - winW) / 2 + NP_CASCADE_OFFSET; + int32_t winY = (screenH - winH) / 3 + NP_CASCADE_OFFSET; sWin = dvxCreateWindow(ac, "Untitled - Notepad", winX, winY, winW, winH, true); diff --git a/apps/progman/progman.c b/apps/progman/progman.c index 2fd2104..48f345f 100644 --- a/apps/progman/progman.c +++ b/apps/progman/progman.c @@ -48,6 +48,11 @@ #define PM_GRID_COLS 4 #define PM_BTN_W 100 #define PM_BTN_H 24 +#define PM_WIN_W 440 +#define PM_WIN_H 340 +#define PM_GRID_SPACING 8 +#define PM_SYSINFO_WIN_W 400 +#define PM_SYSINFO_WIN_H 360 // Menu command IDs #define CMD_RUN 100 @@ -124,8 +129,8 @@ AppDescriptorT appDescriptor = { static void buildPmWindow(void) { int32_t screenW = sAc->display.width; int32_t screenH = sAc->display.height; - int32_t winW = 440; - int32_t winH = 340; + int32_t winW = PM_WIN_W; + int32_t winH = PM_WIN_H; int32_t winX = (screenW - winW) / 2; int32_t winY = (screenH - winH) / 4; @@ -170,20 +175,17 @@ static void buildPmWindow(void) { appFrame->weight = 100; if (sAppCount == 0) { - WidgetT *lbl = wgtLabel(appFrame, "(No applications found in apps/ directory)"); - (void)lbl; + wgtLabel(appFrame, "(No applications found in apps/ directory)"); } else { // Build rows of buttons. Each row is an HBox holding PM_GRID_COLS // buttons. userData points back to the AppEntryT so the click // callback knows which app to launch. - int32_t row = 0; WidgetT *hbox = NULL; for (int32_t i = 0; i < sAppCount; i++) { if (i % PM_GRID_COLS == 0) { hbox = wgtHBox(appFrame); - hbox->spacing = wgtPixels(8); - row++; + hbox->spacing = wgtPixels(PM_GRID_SPACING); } WidgetT *btn = wgtButton(hbox, sAppFiles[i].name); @@ -194,7 +196,6 @@ static void buildPmWindow(void) { btn->onClick = onAppButtonClick; } - (void)row; } // Status bar at bottom; weight=100 on the label makes it fill the bar @@ -420,8 +421,8 @@ static void showSystemInfo(void) { // Create a window with a read-only text area int32_t screenW = sAc->display.width; int32_t screenH = sAc->display.height; - int32_t winW = 400; - int32_t winH = 360; + int32_t winW = PM_SYSINFO_WIN_W; + int32_t winH = PM_SYSINFO_WIN_H; int32_t winX = (screenW - winW) / 2; int32_t winY = (screenH - winH) / 4; diff --git a/dvx/dvxApp.c b/dvx/dvxApp.c index d9d98c8..a16832d 100644 --- a/dvx/dvxApp.c +++ b/dvx/dvxApp.c @@ -67,6 +67,27 @@ #define POPUP_ITEM_PAD_H 8 // extra horizontal padding in popup items #define MENU_TAB_GAP_CHARS 3 // char-widths gap between label and shortcut +// Cursor dirty region: covers all cursor shapes (16x16 with hotspot at 0,0 or 7,7) +#define CURSOR_HOTSPOT_X 7 +#define CURSOR_HOTSPOT_Y 7 +#define CURSOR_DIRTY_SIZE 23 + +// 16.16 fixed-point constants for bilinear wallpaper scaling +#define FP_ONE 65536 +#define FP_FRAC_SHIFT 8 // shift to extract 8-bit fractional part +#define FP_FRAC_MASK 0xFF // mask for 8-bit fractional part +#define FP_BLEND_MAX 256 // 1.0 in 8-bit fractional domain + +// Wallpaper scaler yields to the event loop every N rows +#define WALLPAPER_YIELD_ROWS 32 + +// Cascade windows default to 2/3 of screen dimensions +#define CASCADE_SIZE_NUMER 2 +#define CASCADE_SIZE_DENOM 3 + +// RGB pixel stride (bytes per pixel in 24-bit RGB) +#define RGB_CHANNELS 3 + // ============================================================ // Prototypes // ============================================================ @@ -81,7 +102,9 @@ static void closeSysMenu(AppContextT *ctx); static void interactiveScreenshot(AppContextT *ctx); static void interactiveWindowScreenshot(AppContextT *ctx, WindowT *win); static void compositeAndFlush(AppContextT *ctx); +static int32_t countVisibleWindows(const AppContextT *ctx); static void dirtyCursorArea(AppContextT *ctx, int32_t x, int32_t y); +static void ditherRgb(int32_t *r, int32_t *g, int32_t *b, int32_t row, int32_t col); static bool dispatchAccelKey(AppContextT *ctx, char key); static void dispatchEvents(AppContextT *ctx); static void drawCursorAt(AppContextT *ctx, int32_t x, int32_t y); @@ -91,6 +114,7 @@ static WindowT *findWindowById(AppContextT *ctx, int32_t id); static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t buttons); static void enumModeCb(int32_t w, int32_t h, int32_t bpp, void *userData); static void initColorScheme(AppContextT *ctx); +static void invalidateAllWindows(AppContextT *ctx); static void openContextMenu(AppContextT *ctx, WindowT *win, MenuT *menu, int32_t screenX, int32_t screenY); static void openPopupAtMenu(AppContextT *ctx, WindowT *win, int32_t menuIdx); static void openSubMenu(AppContextT *ctx); @@ -111,6 +135,15 @@ static void updateTooltip(AppContextT *ctx); // keyboard activation was registered, matching Win3.x/Motif behavior. WidgetT *sKeyPressedBtn = NULL; +// 4x4 Bayer dithering offsets for ordered dithering when converting +// 24-bit wallpaper images to 15/16-bit pixel formats. +static const int32_t sBayerMatrix[4][4] = { + { -7, 1, -5, 3}, + { 5, -3, 7, -1}, + { -4, 4, -6, 2}, + { 6, -2, 8, 0} +}; + // ============================================================ @@ -124,7 +157,7 @@ WidgetT *sKeyPressedBtn = NULL; // The 8bpp path uses the VGA palette for lookup. static uint8_t *bufferToRgb(const DisplayT *d, const uint8_t *buf, int32_t w, int32_t h, int32_t pitch) { - uint8_t *rgb = (uint8_t *)malloc((size_t)w * h * 3); + uint8_t *rgb = (uint8_t *)malloc((size_t)w * h * RGB_CHANNELS); if (!rgb) { return NULL; @@ -149,9 +182,9 @@ static uint8_t *bufferToRgb(const DisplayT *d, const uint8_t *buf, int32_t w, in if (d->format.bitsPerPixel == 8) { int32_t idx = pixel & 0xFF; - dst[0] = d->palette[idx * 3 + 0]; - dst[1] = d->palette[idx * 3 + 1]; - dst[2] = d->palette[idx * 3 + 2]; + dst[0] = d->palette[idx * RGB_CHANNELS + 0]; + dst[1] = d->palette[idx * RGB_CHANNELS + 1]; + dst[2] = d->palette[idx * RGB_CHANNELS + 2]; } else { uint32_t rv = (pixel >> d->format.redShift) & ((1u << d->format.redBits) - 1); uint32_t gv = (pixel >> d->format.greenShift) & ((1u << d->format.greenBits) - 1); @@ -161,7 +194,7 @@ static uint8_t *bufferToRgb(const DisplayT *d, const uint8_t *buf, int32_t w, in dst[2] = (uint8_t)(bv << (8 - d->format.blueBits)); } - dst += 3; + dst += RGB_CHANNELS; } } @@ -580,6 +613,25 @@ static void compositeAndFlush(AppContextT *ctx) { } +// ============================================================ +// countVisibleWindows -- count non-minimized visible windows +// ============================================================ + +static int32_t countVisibleWindows(const AppContextT *ctx) { + int32_t count = 0; + + for (int32_t i = 0; i < ctx->stack.count; i++) { + WindowT *win = ctx->stack.windows[i]; + + if (!win->minimized && win->visible) { + count++; + } + } + + return count; +} + + // ============================================================ // dirtyCursorArea // ============================================================ @@ -593,7 +645,37 @@ static void compositeAndFlush(AppContextT *ctx) { // cursor footprints (16x16 with hotspot at 0,0 or 7,7). static void dirtyCursorArea(AppContextT *ctx, int32_t x, int32_t y) { - dirtyListAdd(&ctx->dirty, x - 7, y - 7, 23, 23); + dirtyListAdd(&ctx->dirty, x - CURSOR_HOTSPOT_X, y - CURSOR_HOTSPOT_Y, CURSOR_DIRTY_SIZE, CURSOR_DIRTY_SIZE); +} + + +// ============================================================ +// ditherRgb -- apply Bayer ordered dithering to an RGB triplet +// ============================================================ + +static void ditherRgb(int32_t *r, int32_t *g, int32_t *b, int32_t row, int32_t col) { + int32_t d = sBayerMatrix[row & 3][col & 3]; + *r += d; + *g += d; + *b += d; + + if (*r < 0) { + *r = 0; + } else if (*r > 255) { + *r = 255; + } + + if (*g < 0) { + *g = 0; + } else if (*g > 255) { + *g = 255; + } + + if (*b < 0) { + *b = 0; + } else if (*b > 255) { + *b = 255; + } } @@ -1597,19 +1679,7 @@ void dvxApplyColorScheme(AppContextT *ctx) { ctx->cursorFg = ctx->colors.cursorFg; ctx->cursorBg = ctx->colors.cursorBg; - // Repaint all windows with new colors - for (int32_t i = 0; i < ctx->stack.count; i++) { - WindowT *win = ctx->stack.windows[i]; - - if (win->widgetRoot) { - wgtInvalidate(win->widgetRoot); - } else { - dvxInvalidateWindow(ctx, win); - } - } - - // Repaint desktop area too (wallpaper/background between windows) - dirtyListAdd(&ctx->dirty, 0, 0, d->width, d->height); + invalidateAllWindows(ctx); } @@ -1641,8 +1711,8 @@ void dvxCascadeWindows(AppContextT *ctx) { int32_t offsetY = 0; int32_t step = CHROME_TITLE_HEIGHT + CHROME_BORDER_WIDTH; - int32_t winW = screenW * 2 / 3; - int32_t winH = screenH * 2 / 3; + int32_t winW = screenW * CASCADE_SIZE_NUMER / CASCADE_SIZE_DENOM; + int32_t winH = screenH * CASCADE_SIZE_NUMER / CASCADE_SIZE_DENOM; if (winW < MIN_WINDOW_W) { winW = MIN_WINDOW_W; @@ -2124,13 +2194,7 @@ void dvxSetColor(AppContextT *ctx, ColorIdE id, uint8_t r, uint8_t g, uint8_t b) 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); + invalidateAllWindows(ctx); } @@ -2157,14 +2221,8 @@ static uint8_t *buildWallpaperBuf(AppContextT *ctx, const uint8_t *rgb, int32_t int32_t bpp = ctx->display.format.bitsPerPixel; int32_t bytesPerPx = ctx->display.format.bytesPerPixel; int32_t pitch = screenW * bytesPerPx; - int32_t srcStride = imgW * 3; + int32_t srcStride = imgW * RGB_CHANNELS; - 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); @@ -2177,61 +2235,78 @@ static uint8_t *buildWallpaperBuf(AppContextT *ctx, const uint8_t *rgb, int32_t // 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 (bytesPerPx == 1) { + memset(buf, bgPx & 0xFF, pitch * screenH); + } else if (bytesPerPx == 2) { + uint16_t px16 = (uint16_t)bgPx; + uint32_t fill32 = ((uint32_t)px16 << 16) | px16; + for (int32_t y = 0; y < screenH; y++) { + uint32_t *row = (uint32_t *)(buf + y * pitch); + int32_t pairs = screenW / 2; + for (int32_t i = 0; i < pairs; i++) { + row[i] = fill32; + } + if (screenW & 1) { + *(uint16_t *)(buf + y * pitch + (screenW - 1) * 2) = px16; + } + } + } else { + uint32_t *row32 = (uint32_t *)buf; + for (int32_t i = 0; i < pitch * screenH / 4; i++) { + row32[i] = bgPx; } } 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) { + if ((y & (WALLPAPER_YIELD_ROWS - 1)) == 0 && y > 0) { dvxUpdate(ctx); } - int32_t srcYfp = (int32_t)((int64_t)y * imgH * 65536 / screenH); + int32_t srcYfp = (int32_t)((int64_t)y * imgH * FP_ONE / 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; + int32_t fy = (srcYfp >> FP_FRAC_SHIFT) & FP_FRAC_MASK; + int32_t ify = FP_BLEND_MAX - 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 srcXfp = (int32_t)((int64_t)x * imgW * FP_ONE / 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; + int32_t fx = (srcXfp >> FP_FRAC_SHIFT) & FP_FRAC_MASK; + int32_t ifx = FP_BLEND_MAX - 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; + const uint8_t *p00 = row0 + sx0 * RGB_CHANNELS; + const uint8_t *p10 = row0 + sx1 * RGB_CHANNELS; + const uint8_t *p01 = row1 + sx0 * RGB_CHANNELS; + const uint8_t *p11 = row1 + sx1 * RGB_CHANNELS; 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; + ditherRgb(&r, &g, &b, y, x); } - writePixel(dst, x, packColor(&ctx->display, (uint8_t)r, (uint8_t)g, (uint8_t)b), bpp); + uint32_t px = packColor(&ctx->display, (uint8_t)r, (uint8_t)g, (uint8_t)b); + if (bytesPerPx == 2) { + ((uint16_t *)dst)[x] = (uint16_t)px; + } else if (bytesPerPx == 4) { + ((uint32_t *)dst)[x] = px; + } else { + dst[x] = (uint8_t)px; + } } } } 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) { + if ((y & (WALLPAPER_YIELD_ROWS - 1)) == 0 && y > 0) { dvxUpdate(ctx); } @@ -2241,21 +2316,24 @@ static uint8_t *buildWallpaperBuf(AppContextT *ctx, const uint8_t *rgb, int32_t for (int32_t x = 0; x < screenW; x++) { int32_t srcX = x % imgW; - const uint8_t *src = srcRow + srcX * 3; + const uint8_t *src = srcRow + srcX * RGB_CHANNELS; 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; + ditherRgb(&r, &g, &b, y, x); } - writePixel(dst, x, packColor(&ctx->display, (uint8_t)r, (uint8_t)g, (uint8_t)b), bpp); + uint32_t px = packColor(&ctx->display, (uint8_t)r, (uint8_t)g, (uint8_t)b); + if (bytesPerPx == 2) { + ((uint16_t *)dst)[x] = (uint16_t)px; + } else if (bytesPerPx == 4) { + ((uint32_t *)dst)[x] = px; + } else { + dst[x] = (uint8_t)px; + } } } } else { @@ -2279,7 +2357,7 @@ static uint8_t *buildWallpaperBuf(AppContextT *ctx, const uint8_t *rgb, int32_t } for (int32_t sy = srcStartY; sy < srcEndY; sy++) { - if (((sy - srcStartY) & 31) == 0 && sy > srcStartY) { + if (((sy - srcStartY) & (WALLPAPER_YIELD_ROWS - 1)) == 0 && sy > srcStartY) { dvxUpdate(ctx); } @@ -2290,20 +2368,23 @@ static uint8_t *buildWallpaperBuf(AppContextT *ctx, const uint8_t *rgb, int32_t for (int32_t sx = srcStartX; sx < srcEndX; sx++) { int32_t dx = offX + sx; - const uint8_t *src = srcRow + sx * 3; + const uint8_t *src = srcRow + sx * RGB_CHANNELS; 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; + ditherRgb(&r, &g, &b, dy, dx); } - writePixel(dst, dx, packColor(&ctx->display, (uint8_t)r, (uint8_t)g, (uint8_t)b), bpp); + uint32_t px = packColor(&ctx->display, (uint8_t)r, (uint8_t)g, (uint8_t)b); + if (bytesPerPx == 2) { + ((uint16_t *)dst)[dx] = (uint16_t)px; + } else if (bytesPerPx == 4) { + ((uint32_t *)dst)[dx] = px; + } else { + dst[dx] = (uint8_t)px; + } } } } @@ -2430,7 +2511,7 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_ // popup menu item index calculation can use multiply+shift instead // of division. On a 486, integer divide is 40+ cycles; this // reciprocal trick reduces it to ~10 cycles (imul + shr). - ctx->charHeightRecip = ((uint32_t)0x10000 + (uint32_t)ctx->font.charHeight - 1) / (uint32_t)ctx->font.charHeight; + ctx->charHeightRecip = ((uint32_t)FP_ONE + (uint32_t)ctx->font.charHeight - 1) / (uint32_t)ctx->font.charHeight; // Dirty the entire screen so the first compositeAndFlush paints everything dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height); @@ -2476,7 +2557,7 @@ uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, int32_t *outW, i for (int32_t y = 0; y < imgH; y++) { for (int32_t x = 0; x < imgW; x++) { - const uint8_t *src = rgb + (y * imgW + x) * 3; + const uint8_t *src = rgb + (y * imgW + x) * RGB_CHANNELS; uint32_t color = packColor(d, src[0], src[1], src[2]); uint8_t *dst = buf + y * pitch + x * bpp; @@ -2673,7 +2754,7 @@ int32_t dvxSaveImage(const AppContextT *ctx, const uint8_t *data, int32_t w, int return -1; } - int32_t result = stbi_write_png(path, w, h, 3, rgb, w * 3) ? 0 : -1; + int32_t result = stbi_write_png(path, w, h, RGB_CHANNELS, rgb, w * RGB_CHANNELS) ? 0 : -1; free(rgb); @@ -2699,7 +2780,7 @@ int32_t dvxScreenshot(AppContextT *ctx, const char *path) { return -1; } - int32_t result = stbi_write_png(path, d->width, d->height, 3, rgb, d->width * 3) ? 0 : -1; + int32_t result = stbi_write_png(path, d->width, d->height, RGB_CHANNELS, rgb, d->width * RGB_CHANNELS) ? 0 : -1; free(rgb); @@ -2784,7 +2865,7 @@ int32_t dvxWindowScreenshot(AppContextT *ctx, WindowT *win, const char *path) { return -1; } - int32_t result = stbi_write_png(path, win->contentW, win->contentH, 3, rgb, win->contentW * 3) ? 0 : -1; + int32_t result = stbi_write_png(path, win->contentW, win->contentH, RGB_CHANNELS, rgb, win->contentW * RGB_CHANNELS) ? 0 : -1; free(rgb); @@ -2809,17 +2890,7 @@ int32_t dvxWindowScreenshot(AppContextT *ctx, WindowT *win, const char *path) { void dvxTileWindows(AppContextT *ctx) { int32_t screenW = ctx->display.width; int32_t screenH = ctx->display.height; - - // Count eligible windows - int32_t count = 0; - - for (int32_t i = 0; i < ctx->stack.count; i++) { - WindowT *win = ctx->stack.windows[i]; - - if (!win->minimized && win->visible) { - count++; - } - } + int32_t count = countVisibleWindows(ctx); if (count == 0) { return; @@ -2880,17 +2951,7 @@ void dvxTileWindows(AppContextT *ctx) { void dvxTileWindowsH(AppContextT *ctx) { int32_t screenW = ctx->display.width; int32_t screenH = ctx->display.height; - - // Count eligible windows - int32_t count = 0; - - for (int32_t i = 0; i < ctx->stack.count; i++) { - WindowT *win = ctx->stack.windows[i]; - - if (!win->minimized && win->visible) { - count++; - } - } + int32_t count = countVisibleWindows(ctx); if (count == 0) { return; @@ -2928,17 +2989,7 @@ void dvxTileWindowsH(AppContextT *ctx) { void dvxTileWindowsV(AppContextT *ctx) { int32_t screenW = ctx->display.width; int32_t screenH = ctx->display.height; - - // Count eligible windows - int32_t count = 0; - - for (int32_t i = 0; i < ctx->stack.count; i++) { - WindowT *win = ctx->stack.windows[i]; - - if (!win->minimized && win->visible) { - count++; - } - } + int32_t count = countVisibleWindows(ctx); if (count == 0) { return; @@ -3325,6 +3376,17 @@ static void initColorScheme(AppContextT *ctx) { } +// ============================================================ +// invalidateAllWindows -- repaint all windows and desktop +// ============================================================ + +static void invalidateAllWindows(AppContextT *ctx) { + for (int32_t i = 0; i < ctx->stack.count; i++) { + dvxInvalidateWindow(ctx, ctx->stack.windows[i]); + } + + dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height); +} // ============================================================ diff --git a/dvx/dvxWidget.h b/dvx/dvxWidget.h index 1953706..b97904d 100644 --- a/dvx/dvxWidget.h +++ b/dvx/dvxWidget.h @@ -521,6 +521,9 @@ typedef struct WidgetT { struct WidgetT *dragItem; // item being dragged (NULL = none) struct WidgetT *dropTarget; // insertion target (NULL = none) bool dropAfter; // true = insert after target, false = before + int32_t cachedTotalHeight; // cached result of calcTreeItemsHeight + int32_t cachedMaxWidth; // cached result of calcTreeItemsMaxWidth + bool dimsValid; // false = recalculate on next scrollbar check } treeView; struct { diff --git a/dvx/platform/dvxPlatformDos.c b/dvx/platform/dvxPlatformDos.c index b5cde22..669264f 100644 --- a/dvx/platform/dvxPlatformDos.c +++ b/dvx/platform/dvxPlatformDos.c @@ -47,6 +47,14 @@ #include #include +// VESA mode scoring weights: higher score = preferred mode +#define MODE_SCORE_16BPP 100 // 16bpp: fastest span fill (half the bytes of 32bpp) +#define MODE_SCORE_15BPP 90 // 15bpp: slightly below 16 (some BIOSes conflate them) +#define MODE_SCORE_32BPP 85 // 32bpp: true color but slower bus traffic +#define MODE_SCORE_8BPP 70 // 8bpp: palette management adds complexity +#define MODE_SCORE_PREF_BPP 20 // bonus if bpp matches user preference +#define MODE_SCORE_EXACT_RES 10 // bonus/penalty for exact/oversize resolution + // ============================================================ // Prototypes // ============================================================ @@ -376,25 +384,25 @@ static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requ int32_t s = 0; if (bpp == 16) { - s = 100; + s = MODE_SCORE_16BPP; } else if (bpp == 15) { - s = 90; + s = MODE_SCORE_15BPP; } else if (bpp == 32) { - s = 85; + s = MODE_SCORE_32BPP; } else if (bpp == 8) { - s = 70; + s = MODE_SCORE_8BPP; } // Prefer the user's preferred bpp if (bpp == preferredBpp) { - s += 20; + s += MODE_SCORE_PREF_BPP; } // Exact resolution match is preferred if (w == requestedW && h == requestedH) { - s += 10; + s += MODE_SCORE_EXACT_RES; } else { - s -= 10; + s -= MODE_SCORE_EXACT_RES; } *score = s; diff --git a/dvx/widgets/widgetComboBox.c b/dvx/widgets/widgetComboBox.c index 594591d..3fbb5c5 100644 --- a/dvx/widgets/widgetComboBox.c +++ b/dvx/widgets/widgetComboBox.c @@ -84,17 +84,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. - int32_t maxLen = 0; - - for (int32_t i = 0; i < count; i++) { - int32_t slen = (int32_t)strlen(items[i]); - - if (slen > maxLen) { - maxLen = slen; - } - } - - w->as.comboBox.maxItemLen = maxLen; + w->as.comboBox.maxItemLen = widgetMaxItemLen(items, count); if (w->as.comboBox.selectedIdx >= count) { w->as.comboBox.selectedIdx = -1; @@ -346,10 +336,7 @@ void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow; int32_t arrowX = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2; int32_t arrowY = w->y + w->h / 2 - 1; - - for (int32_t i = 0; i < 4; i++) { - drawHLine(d, ops, arrowX - 3 + i, arrowY + i, 7 - i * 2, arrowFg); - } + widgetDrawDropdownArrow(d, ops, arrowX, arrowY, arrowFg); } diff --git a/dvx/widgets/widgetCore.c b/dvx/widgets/widgetCore.c index abea208..b2cb818 100644 --- a/dvx/widgets/widgetCore.c +++ b/dvx/widgets/widgetCore.c @@ -272,6 +272,21 @@ void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t conten } +// ============================================================ +// widgetDrawDropdownArrow +// ============================================================ +// +// Draws a small downward-pointing filled triangle (7, 5, 3, 1 pixels +// wide across 4 rows) centered at the given position. Used by both +// Dropdown and ComboBox for the drop button arrow glyph. + +void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color) { + for (int32_t i = 0; i < 4; i++) { + drawHLine(d, ops, centerX - 3 + i, centerY + i, 7 - i * 2, color); + } +} + + // ============================================================ // widgetFindByAccel // ============================================================ @@ -542,6 +557,29 @@ bool widgetIsHorizContainer(WidgetTypeE type) { } +// ============================================================ +// widgetMaxItemLen +// ============================================================ +// +// Scans an array of string items and returns the maximum strlen. +// Shared by ListBox, Dropdown, and ComboBox to cache the widest +// item length for calcMinSize without duplicating the loop. + +int32_t widgetMaxItemLen(const char **items, int32_t count) { + int32_t maxLen = 0; + + for (int32_t i = 0; i < count; i++) { + int32_t slen = (int32_t)strlen(items[i]); + + if (slen > maxLen) { + maxLen = slen; + } + } + + return maxLen; +} + + // ============================================================ // widgetNavigateIndex // ============================================================ diff --git a/dvx/widgets/widgetDropdown.c b/dvx/widgets/widgetDropdown.c index 9a47a7f..5401b85 100644 --- a/dvx/widgets/widgetDropdown.c +++ b/dvx/widgets/widgetDropdown.c @@ -59,17 +59,7 @@ void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) { w->as.dropdown.itemCount = count; // Cache max item strlen to avoid recomputing in calcMinSize - int32_t maxLen = 0; - - for (int32_t i = 0; i < count; i++) { - int32_t slen = (int32_t)strlen(items[i]); - - if (slen > maxLen) { - maxLen = slen; - } - } - - w->as.dropdown.maxItemLen = maxLen; + w->as.dropdown.maxItemLen = widgetMaxItemLen(items, count); if (w->as.dropdown.selectedIdx >= count) { w->as.dropdown.selectedIdx = -1; @@ -254,16 +244,11 @@ void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit btnBevel.width = 2; drawBevel(d, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel); - // Down arrow glyph in button -- a small filled triangle drawn as horizontal - // lines of decreasing width (7, 5, 3, 1 pixels). This creates a 4-pixel - // tall downward-pointing triangle centered in the button. + // Down arrow glyph in button uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow; int32_t arrowX = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2; int32_t arrowY = w->y + w->h / 2 - 1; - - for (int32_t i = 0; i < 4; i++) { - drawHLine(d, ops, arrowX - 3 + i, arrowY + i, 7 - i * 2, arrowFg); - } + widgetDrawDropdownArrow(d, ops, arrowX, arrowY, arrowFg); if (w->focused) { drawFocusRect(d, ops, w->x + 3, w->y + 3, textAreaW - 6, w->h - 6, fg); diff --git a/dvx/widgets/widgetEvent.c b/dvx/widgets/widgetEvent.c index 20fd9ac..4678213 100644 --- a/dvx/widgets/widgetEvent.c +++ b/dvx/widgets/widgetEvent.c @@ -925,6 +925,8 @@ void widgetReorderDrop(WidgetT *w) { return; } + w->as.treeView.dimsValid = false; + // Unlink drag from its current parent WidgetT *oldParent = drag->parent; diff --git a/dvx/widgets/widgetInternal.h b/dvx/widgets/widgetInternal.h index 637c5d2..9a64a85 100644 --- a/dvx/widgets/widgetInternal.h +++ b/dvx/widgets/widgetInternal.h @@ -237,6 +237,12 @@ int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t // Compute popup position for a dropdown/combobox, clamped to screen bounds. void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH); +// Draw the small downward-pointing triangle glyph used by dropdown buttons. +void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color); + +// Compute the maximum strlen across an array of string items. +int32_t widgetMaxItemLen(const char **items, int32_t count); + // Paint a generic popup item list (used by dropdown, combobox, and popup menus). void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos); diff --git a/dvx/widgets/widgetListBox.c b/dvx/widgets/widgetListBox.c index 998f6c6..29f0cad 100644 --- a/dvx/widgets/widgetListBox.c +++ b/dvx/widgets/widgetListBox.c @@ -238,17 +238,7 @@ void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) { w->as.listBox.itemCount = count; // Cache max item strlen to avoid recomputing in calcMinSize - int32_t maxLen = 0; - - for (int32_t i = 0; i < count; i++) { - int32_t slen = (int32_t)strlen(items[i]); - - if (slen > maxLen) { - maxLen = slen; - } - } - - w->as.listBox.maxItemLen = maxLen; + w->as.listBox.maxItemLen = widgetMaxItemLen(items, count); if (w->as.listBox.selectedIdx >= count) { w->as.listBox.selectedIdx = count > 0 ? 0 : -1; diff --git a/dvx/widgets/widgetListView.c b/dvx/widgets/widgetListView.c index dcb71ce..25b61e7 100644 --- a/dvx/widgets/widgetListView.c +++ b/dvx/widgets/widgetListView.c @@ -405,6 +405,16 @@ void wgtListViewSetColumns(WidgetT *w, const ListViewColT *cols, int32_t count) w->as.listView->cols = cols; w->as.listView->colCount = count; w->as.listView->totalColW = 0; + + // Eagerly resolve column widths if data is already set + if (w->as.listView->rowCount > 0) { + AppContextT *ctx = wgtGetContext(w); + + if (ctx) { + resolveColumnWidths(w, &ctx->font); + } + } + wgtInvalidate(w); } @@ -446,6 +456,15 @@ void wgtListViewSetData(WidgetT *w, const char **cellData, int32_t rowCount) { w->as.listView->selBits[w->as.listView->selectedIdx] = 1; } + // Eagerly resolve column widths if columns are defined + if (w->as.listView->colCount > 0) { + AppContextT *ctx = wgtGetContext(w); + + if (ctx) { + resolveColumnWidths(w, &ctx->font); + } + } + wgtInvalidate(w); } diff --git a/dvx/widgets/widgetOps.c b/dvx/widgets/widgetOps.c index af3053b..14b0156 100644 --- a/dvx/widgets/widgetOps.c +++ b/dvx/widgets/widgetOps.c @@ -153,6 +153,16 @@ void wgtDestroy(WidgetT *w) { return; } + // Invalidate tree dimension cache if destroying a tree item + if (w->type == WidgetTreeItemE && w->parent) { + for (WidgetT *p = w->parent; p; p = p->parent) { + if (p->type == WidgetTreeViewE) { + p->as.treeView.dimsValid = false; + break; + } + } + } + if (w->parent) { widgetRemoveChild(w->parent, w); } @@ -555,6 +565,17 @@ void wgtSetTooltip(WidgetT *w, const char *text) { void wgtSetVisible(WidgetT *w, bool visible) { if (w) { w->visible = visible; + + // Invalidate tree dimension cache if this is a tree item + if (w->type == WidgetTreeItemE && w->parent) { + for (WidgetT *p = w->parent; p; p = p->parent) { + if (p->type == WidgetTreeViewE) { + p->as.treeView.dimsValid = false; + break; + } + } + } + wgtInvalidate(w); } } diff --git a/dvx/widgets/widgetTreeView.c b/dvx/widgets/widgetTreeView.c index a8a80a3..0b929be 100644 --- a/dvx/widgets/widgetTreeView.c +++ b/dvx/widgets/widgetTreeView.c @@ -42,10 +42,10 @@ // the actual node reparenting via widgetRemoveChild/widgetAddChild. // // Performance note: calcTreeItemsHeight and calcTreeItemsMaxWidth -// walk the full visible tree on every call. For trees with hundreds -// of items this could be optimized with caching (as TextArea does -// for line count). In practice, DOS-era tree views are small enough -// that the linear scan is fast on a Pentium. +// walk the full visible tree, but results are cached in the TreeView's +// cachedTotalHeight/cachedMaxWidth fields. The cache is invalidated +// (dimsValid = false) whenever the tree structure changes: item add/ +// remove, expand/collapse, text change, or drag-reorder. #include "widgetInternal.h" @@ -57,6 +57,7 @@ static int32_t calcTreeItemsHeight(WidgetT *parent, const BitmapFontT *font); static int32_t calcTreeItemsMaxWidth(WidgetT *parent, const BitmapFontT *font, int32_t depth); static void clearAllSelections(WidgetT *parent); static WidgetT *firstVisibleItem(WidgetT *treeView); +static void invalidateTreeDims(WidgetT *w); static void layoutTreeItems(WidgetT *parent, const BitmapFontT *font, int32_t x, int32_t *y, int32_t width, int32_t depth); static WidgetT *nextVisibleItem(WidgetT *item, WidgetT *treeView); static void paintReorderIndicator(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t innerW); @@ -167,6 +168,24 @@ static WidgetT *firstVisibleItem(WidgetT *treeView) { } +// ============================================================ +// invalidateTreeDims +// ============================================================ +// +// Walk up from any widget (typically a TreeItem) to find the ancestor +// TreeView and mark its cached dimensions invalid. This ensures the +// next treeCalcScrollbarNeeds call will recompute height and width. + +static void invalidateTreeDims(WidgetT *w) { + for (WidgetT *p = w; p; p = p->parent) { + if (p->type == WidgetTreeViewE) { + p->as.treeView.dimsValid = false; + return; + } + } +} + + // ============================================================ // layoutTreeItems // ============================================================ @@ -489,8 +508,14 @@ static void selectRange(WidgetT *treeView, WidgetT *from, WidgetT *to) { // needed, accounting for the mutual space dependency between them. static void treeCalcScrollbarNeeds(WidgetT *w, const BitmapFontT *font, int32_t *outTotalH, int32_t *outTotalW, int32_t *outInnerH, int32_t *outInnerW, bool *outNeedVSb, bool *outNeedHSb) { - int32_t totalH = calcTreeItemsHeight(w, font); - int32_t totalW = calcTreeItemsMaxWidth(w, font, 0); + if (!w->as.treeView.dimsValid) { + w->as.treeView.cachedTotalHeight = calcTreeItemsHeight(w, font); + w->as.treeView.cachedMaxWidth = calcTreeItemsMaxWidth(w, font, 0); + w->as.treeView.dimsValid = true; + } + + int32_t totalH = w->as.treeView.cachedTotalHeight; + int32_t totalW = w->as.treeView.cachedMaxWidth; int32_t innerH = w->h - TREE_BORDER * 2; int32_t innerW = w->w - TREE_BORDER * 2; bool needVSb = (totalH > innerH); @@ -611,6 +636,7 @@ WidgetT *wgtTreeItem(WidgetT *parent, const char *text) { if (w) { w->as.treeItem.text = text; w->as.treeItem.expanded = false; + invalidateTreeDims(w); } return w; @@ -632,6 +658,7 @@ const char *widgetTreeItemGetText(const WidgetT *w) { void widgetTreeItemSetText(WidgetT *w, const char *text) { w->as.treeItem.text = text; + invalidateTreeDims(w); } @@ -654,6 +681,7 @@ void wgtTreeItemSetExpanded(WidgetT *w, bool expanded) { VALIDATE_WIDGET_VOID(w, WidgetTreeItemE); w->as.treeItem.expanded = expanded; + invalidateTreeDims(w); wgtInvalidate(w); } @@ -682,6 +710,7 @@ static void treeDefaultDblClick(WidgetT *w) { if (hasChildren) { sel->as.treeItem.expanded = !sel->as.treeItem.expanded; + invalidateTreeDims(sel); if (sel->onChange) { sel->onChange(sel); @@ -875,6 +904,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) { if (hasChildren) { if (!sel->as.treeItem.expanded) { sel->as.treeItem.expanded = true; + w->as.treeView.dimsValid = false; if (sel->onChange) { sel->onChange(sel); @@ -893,6 +923,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) { if (sel) { if (sel->as.treeItem.expanded) { sel->as.treeItem.expanded = false; + w->as.treeView.dimsValid = false; if (sel->onChange) { sel->onChange(sel); @@ -915,6 +946,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) { if (hasChildren) { sel->as.treeItem.expanded = !sel->as.treeItem.expanded; + w->as.treeView.dimsValid = false; if (sel->onChange) { sel->onChange(sel); @@ -1218,6 +1250,7 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) if (vx >= iconX && vx < iconX + TREE_EXPAND_SIZE) { clickedExpandIcon = true; item->as.treeItem.expanded = !item->as.treeItem.expanded; + hit->as.treeView.dimsValid = false; // Clamp scroll positions if collapsing reduced content size if (!item->as.treeItem.expanded) { diff --git a/dvxshell/shellTaskMgr.c b/dvxshell/shellTaskMgr.c index 47f7e00..2c2afb0 100644 --- a/dvxshell/shellTaskMgr.c +++ b/dvxshell/shellTaskMgr.c @@ -21,17 +21,23 @@ #define TM_COL_COUNT 5 #define TM_MAX_PATH 260 +#define TM_WIN_W 520 +#define TM_WIN_H 280 +#define TM_LIST_PREF_H 160 +#define TM_BTN_SPACING 8 +#define TM_BTN_W 90 // ============================================================ // Prototypes // ============================================================ -static void onTmClose(WindowT *win); -static void onTmEndTask(WidgetT *w); -static void onTmRun(WidgetT *w); -static void onTmSwitchTo(WidgetT *w); -static void refreshTaskList(void); -static void updateStatusText(void); +static ShellAppT *getRunningAppByIndex(int32_t sel); +static void onTmClose(WindowT *win); +static void onTmEndTask(WidgetT *w); +static void onTmRun(WidgetT *w); +static void onTmSwitchTo(WidgetT *w); +static void refreshTaskList(void); +static void updateStatusText(void); // ============================================================ // Module state @@ -52,6 +58,28 @@ static const char **sCells = NULL; // dynamic array of cell pointers static TmRowStringsT *sRowStrs = NULL; // dynamic array of per-row strings +// ============================================================ +// getRunningAppByIndex +// ============================================================ + +static ShellAppT *getRunningAppByIndex(int32_t sel) { + int32_t idx = 0; + + for (int32_t i = 1; i < SHELL_MAX_APPS; i++) { + ShellAppT *app = shellGetApp(i); + + if (app && app->state == AppStateRunningE) { + if (idx == sel) { + return app; + } + idx++; + } + } + + return NULL; +} + + // ============================================================ // onTmClose // ============================================================ @@ -86,22 +114,11 @@ static void onTmEndTask(WidgetT *w) { return; } - // Re-walk the app slot table in the same order as refreshTaskList() - // to map the selected row index back to the correct ShellAppT. - int32_t idx = 0; + ShellAppT *app = getRunningAppByIndex(sel); - for (int32_t i = 1; i < SHELL_MAX_APPS; i++) { - ShellAppT *app = shellGetApp(i); - - if (app && app->state == AppStateRunningE) { - if (idx == sel) { - shellForceKillApp(sCtx, app); - shellDesktopUpdate(); - return; - } - - idx++; - } + if (app) { + shellForceKillApp(sCtx, app); + shellDesktopUpdate(); } } @@ -142,34 +159,25 @@ static void onTmSwitchTo(WidgetT *w) { return; } - // Same index-to-appId mapping as refreshTaskList. Scan the window - // stack top-down (highest Z first) to find the app's topmost window, - // restore it if minimized, then raise and focus it. - int32_t idx = 0; + ShellAppT *app = getRunningAppByIndex(sel); - for (int32_t i = 1; i < SHELL_MAX_APPS; i++) { - ShellAppT *app = shellGetApp(i); + if (!app) { + return; + } - if (app && app->state == AppStateRunningE) { - if (idx == sel) { - for (int32_t j = sCtx->stack.count - 1; j >= 0; j--) { - WindowT *win = sCtx->stack.windows[j]; + // Scan the window stack top-down (highest Z first) to find the app's + // topmost window, restore it if minimized, then raise and focus it. + for (int32_t j = sCtx->stack.count - 1; j >= 0; j--) { + WindowT *win = sCtx->stack.windows[j]; - if (win->appId == i) { - if (win->minimized) { - wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, win); - } - - wmRaiseWindow(&sCtx->stack, &sCtx->dirty, j); - wmSetFocus(&sCtx->stack, &sCtx->dirty, sCtx->stack.count - 1); - return; - } - } - - return; + if (win->appId == app->appId) { + if (win->minimized) { + wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, win); } - idx++; + wmRaiseWindow(&sCtx->stack, &sCtx->dirty, j); + wmSetFocus(&sCtx->stack, &sCtx->dirty, sCtx->stack.count - 1); + return; } } } @@ -283,8 +291,8 @@ void shellTaskMgrOpen(AppContextT *ctx) { return; } - int32_t winW = 520; - int32_t winH = 280; + int32_t winW = TM_WIN_W; + int32_t winH = TM_WIN_H; int32_t winX = (ctx->display.width - winW) / 2; int32_t winY = (ctx->display.height - winH) / 3; @@ -318,26 +326,26 @@ void shellTaskMgrOpen(AppContextT *ctx) { sTmListView = wgtListView(root); sTmListView->weight = 100; - sTmListView->prefH = wgtPixels(160); + sTmListView->prefH = wgtPixels(TM_LIST_PREF_H); wgtListViewSetColumns(sTmListView, tmCols, TM_COL_COUNT); WidgetT *btnRow = wgtHBox(root); - btnRow->spacing = wgtPixels(8); + btnRow->spacing = wgtPixels(TM_BTN_SPACING); sTmStatusLbl = wgtLabel(btnRow, ""); sTmStatusLbl->weight = 100; WidgetT *switchBtn = wgtButton(btnRow, "Switch To"); switchBtn->onClick = onTmSwitchTo; - switchBtn->prefW = wgtPixels(90); + switchBtn->prefW = wgtPixels(TM_BTN_W); WidgetT *endBtn = wgtButton(btnRow, "End Task"); endBtn->onClick = onTmEndTask; - endBtn->prefW = wgtPixels(90); + endBtn->prefW = wgtPixels(TM_BTN_W); WidgetT *runBtn = wgtButton(btnRow, "Run..."); runBtn->onClick = onTmRun; - runBtn->prefW = wgtPixels(90); + runBtn->prefW = wgtPixels(TM_BTN_W); shellRegisterDesktopUpdate(shellTaskMgrRefresh); refreshTaskList();