Code cleanup.

This commit is contained in:
Scott Duensing 2026-03-22 18:02:30 -05:00
parent b1040a655e
commit 7ae7ea1a97
18 changed files with 463 additions and 279 deletions

View file

@ -35,6 +35,11 @@
// Module state // Module state
// ============================================================ // ============================================================
#define CLOCK_WIN_W 200
#define CLOCK_WIN_H 100
#define CLOCK_MARGIN 40
#define CLOCK_DATE_GAP 8
typedef struct { typedef struct {
bool quit; bool quit;
char timeStr[32]; char timeStr[32];
@ -125,7 +130,7 @@ static void onPaint(WindowT *win, RectT *dirty) {
// Date string (centered below) // Date string (centered below)
int32_t dateW = textWidth(font, sState.dateStr); int32_t dateW = textWidth(font, sState.dateStr);
int32_t dateX = (win->contentW - dateW) / 2; 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); drawText(&cd, ops, font, dateX, dateY, sState.dateStr, colors->contentFg, colors->contentBg, true);
} }
@ -182,10 +187,10 @@ int32_t appMain(DxeAppContextT *ctx) {
updateTime(); updateTime();
// Position in the upper-right corner, out of the way of other windows // Position in the upper-right corner, out of the way of other windows
int32_t winW = 200; int32_t winW = CLOCK_WIN_W;
int32_t winH = 100; int32_t winH = CLOCK_WIN_H;
int32_t winX = ac->display.width - winW - 40; int32_t winX = ac->display.width - winW - CLOCK_MARGIN;
int32_t winY = 40; int32_t winY = CLOCK_MARGIN;
// resizable=false: clock has a fixed size, no resize handle // resizable=false: clock has a fixed size, no resize handle
sWin = dvxCreateWindow(ac, "Clock", winX, winY, winW, winH, false); sWin = dvxCreateWindow(ac, "Clock", winX, winY, winW, winH, false);

View file

@ -31,6 +31,20 @@
#define CP_WIN_W 460 #define CP_WIN_W 460
#define CP_WIN_H 340 #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 WPAPER_DIR "CONFIG/WPAPER"
#define THEME_DIR "CONFIG/THEMES" #define THEME_DIR "CONFIG/THEMES"
#define THEME_EXT ".THM" #define THEME_EXT ".THM"
@ -156,12 +170,12 @@ static void buildColorsTab(WidgetT *page) {
// Color list on the left, sliders on the right // Color list on the left, sliders on the right
WidgetT *hbox = wgtHBox(page); WidgetT *hbox = wgtHBox(page);
hbox->weight = 100; hbox->weight = 100;
hbox->spacing = wgtPixels(8); hbox->spacing = wgtPixels(CP_SPACING);
// Left side: color name list + theme controls // Left side: color name list + theme controls
WidgetT *leftVbox = wgtVBox(hbox); WidgetT *leftVbox = wgtVBox(hbox);
leftVbox->weight = 50; leftVbox->weight = 50;
leftVbox->spacing = wgtPixels(4); leftVbox->spacing = wgtPixels(CP_SPACING_SMALL);
wgtLabel(leftVbox, "System Colors:"); wgtLabel(leftVbox, "System Colors:");
@ -180,7 +194,7 @@ static void buildColorsTab(WidgetT *page) {
// Theme row // Theme row
WidgetT *themeRow = wgtHBox(leftVbox); WidgetT *themeRow = wgtHBox(leftVbox);
themeRow->spacing = wgtPixels(4); themeRow->spacing = wgtPixels(CP_SPACING_SMALL);
wgtLabel(themeRow, "Theme:"); wgtLabel(themeRow, "Theme:");
@ -191,19 +205,19 @@ static void buildColorsTab(WidgetT *page) {
WidgetT *loadBtn = wgtButton(themeRow, "Apply"); WidgetT *loadBtn = wgtButton(themeRow, "Apply");
loadBtn->onClick = onApplyTheme; loadBtn->onClick = onApplyTheme;
loadBtn->prefW = wgtPixels(60); loadBtn->prefW = wgtPixels(CP_THEME_BTN_W);
WidgetT *browseBtn = wgtButton(themeRow, "Load..."); WidgetT *browseBtn = wgtButton(themeRow, "Load...");
browseBtn->onClick = onBrowseTheme; browseBtn->onClick = onBrowseTheme;
browseBtn->prefW = wgtPixels(60); browseBtn->prefW = wgtPixels(CP_THEME_BTN_W);
WidgetT *saveBtn = wgtButton(themeRow, "Save As..."); WidgetT *saveBtn = wgtButton(themeRow, "Save As...");
saveBtn->onClick = onSaveTheme; saveBtn->onClick = onSaveTheme;
saveBtn->prefW = wgtPixels(70); saveBtn->prefW = wgtPixels(CP_SAVE_BTN_W);
WidgetT *resetBtn = wgtButton(themeRow, "Reset"); WidgetT *resetBtn = wgtButton(themeRow, "Reset");
resetBtn->onClick = onResetColors; 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 // Right side: RGB sliders in a non-weighted inner box so they stay
// at natural size. The outer box absorbs extra vertical space. // at natural size. The outer box absorbs extra vertical space.
@ -211,7 +225,7 @@ static void buildColorsTab(WidgetT *page) {
rightVbox->weight = 50; rightVbox->weight = 50;
WidgetT *sliderBox = wgtVBox(rightVbox); WidgetT *sliderBox = wgtVBox(rightVbox);
sliderBox->spacing = wgtPixels(4); sliderBox->spacing = wgtPixels(CP_SPACING_SMALL);
wgtLabel(sliderBox, "Red:"); wgtLabel(sliderBox, "Red:");
sRedSldr = wgtSlider(sliderBox, 0, 255); sRedSldr = wgtSlider(sliderBox, 0, 255);
@ -232,7 +246,7 @@ static void buildColorsTab(WidgetT *page) {
wgtLabelSetAlign(sBlueLbl, AlignEndE); wgtLabelSetAlign(sBlueLbl, AlignEndE);
wgtLabel(sliderBox, "Preview:"); wgtLabel(sliderBox, "Preview:");
sColorSwatch = wgtCanvas(sliderBox, 64, 24); sColorSwatch = wgtCanvas(sliderBox, CP_SWATCH_W, CP_SWATCH_H);
// Absorb remaining vertical space below the sliders // Absorb remaining vertical space below the sliders
wgtSpacer(rightVbox)->weight = 100; wgtSpacer(rightVbox)->weight = 100;
@ -257,19 +271,19 @@ static void buildDesktopTab(WidgetT *page) {
sWallpaperLbl = wgtLabel(page, sWallpaperPath[0] ? sWallpaperPath : "(none)"); sWallpaperLbl = wgtLabel(page, sWallpaperPath[0] ? sWallpaperPath : "(none)");
WidgetT *btnRow = wgtHBox(page); WidgetT *btnRow = wgtHBox(page);
btnRow->spacing = wgtPixels(8); btnRow->spacing = wgtPixels(CP_SPACING);
WidgetT *applyBtn = wgtButton(btnRow, "Apply"); WidgetT *applyBtn = wgtButton(btnRow, "Apply");
applyBtn->onClick = onApplyWallpaper; applyBtn->onClick = onApplyWallpaper;
applyBtn->prefW = wgtPixels(90); applyBtn->prefW = wgtPixels(CP_WPAPER_BTN_W);
WidgetT *chooseBtn = wgtButton(btnRow, "Browse..."); WidgetT *chooseBtn = wgtButton(btnRow, "Browse...");
chooseBtn->onClick = onChooseWallpaper; chooseBtn->onClick = onChooseWallpaper;
chooseBtn->prefW = wgtPixels(90); chooseBtn->prefW = wgtPixels(CP_WPAPER_BTN_W);
WidgetT *clearBtn = wgtButton(btnRow, "Clear"); WidgetT *clearBtn = wgtButton(btnRow, "Clear");
clearBtn->onClick = onClearWallpaper; clearBtn->onClick = onClearWallpaper;
clearBtn->prefW = wgtPixels(90); clearBtn->prefW = wgtPixels(CP_WPAPER_BTN_W);
wgtSpacer(btnRow)->weight = 100; wgtSpacer(btnRow)->weight = 100;
wgtLabel(btnRow, "Mode:"); wgtLabel(btnRow, "Mode:");
@ -291,7 +305,7 @@ static void buildMouseTab(WidgetT *page) {
// Scroll direction // Scroll direction
WidgetT *wheelRow = wgtHBox(page); WidgetT *wheelRow = wgtHBox(page);
wheelRow->spacing = wgtPixels(8); wheelRow->spacing = wgtPixels(CP_SPACING);
wgtLabel(wheelRow, "Scroll Wheel:"); wgtLabel(wheelRow, "Scroll Wheel:");
static const char *wheelItems[] = {"Normal", "Reversed"}; static const char *wheelItems[] = {"Normal", "Reversed"};
@ -301,13 +315,13 @@ static void buildMouseTab(WidgetT *page) {
wgtDropdownSetItems(sWheelDrop, wheelItems, 2); wgtDropdownSetItems(sWheelDrop, wheelItems, 2);
wgtDropdownSetSelected(sWheelDrop, sAc->wheelDirection < 0 ? 1 : 0); wgtDropdownSetSelected(sWheelDrop, sAc->wheelDirection < 0 ? 1 : 0);
wgtSpacer(page)->prefH = wgtPixels(4); wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL);
// Double-click speed // Double-click speed
wgtLabel(page, "Double-Click Speed:"); wgtLabel(page, "Double-Click Speed:");
WidgetT *dblRow = wgtHBox(page); WidgetT *dblRow = wgtHBox(page);
dblRow->spacing = wgtPixels(8); dblRow->spacing = wgtPixels(CP_SPACING);
wgtLabel(dblRow, "Fast"); wgtLabel(dblRow, "Fast");
sDblClickSldr = wgtSlider(dblRow, 200, 900); sDblClickSldr = wgtSlider(dblRow, 200, 900);
@ -318,14 +332,14 @@ static void buildMouseTab(WidgetT *page) {
wgtSliderSetValue(sDblClickSldr, dblMs); wgtSliderSetValue(sDblClickSldr, dblMs);
wgtLabel(dblRow, "Slow "); wgtLabel(dblRow, "Slow ");
sDblClickLbl = wgtLabel(dblRow, ""); sDblClickLbl = wgtLabel(dblRow, "");
sDblClickLbl->prefW = wgtPixels(52); sDblClickLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W);
updateDblClickLabel(); updateDblClickLabel();
wgtSpacer(page)->prefH = wgtPixels(4); wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL);
// Acceleration // Acceleration
WidgetT *accelRow = wgtHBox(page); WidgetT *accelRow = wgtHBox(page);
accelRow->spacing = wgtPixels(8); accelRow->spacing = wgtPixels(CP_SPACING);
wgtLabel(accelRow, "Acceleration:"); wgtLabel(accelRow, "Acceleration:");
static const char *accelItems[] = {"Off", "Low", "Medium", "High"}; static const char *accelItems[] = {"Off", "Low", "Medium", "High"};
@ -346,15 +360,15 @@ static void buildMouseTab(WidgetT *page) {
wgtDropdownSetSelected(sAccelDrop, 2); wgtDropdownSetSelected(sAccelDrop, 2);
} }
wgtSpacer(page)->prefH = wgtPixels(4); wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL);
// Double-click test area // Double-click test area
WidgetT *testRow = wgtHBox(page); WidgetT *testRow = wgtHBox(page);
testRow->spacing = wgtPixels(8); testRow->spacing = wgtPixels(CP_SPACING);
WidgetT *testBtn = wgtButton(testRow, "Test Area"); WidgetT *testBtn = wgtButton(testRow, "Test Area");
testBtn->onDblClick = onDblClickTest; testBtn->onDblClick = onDblClickTest;
testBtn->prefW = wgtPixels(100); testBtn->prefW = wgtPixels(CP_TEST_BTN_W);
sDblTestLbl = wgtLabel(testRow, "Double-click the button to test"); sDblTestLbl = wgtLabel(testRow, "Double-click the button to test");
} }
@ -410,7 +424,7 @@ static void buildVideoTab(WidgetT *page) {
WidgetT *applyBtn = wgtButton(page, "Apply Mode"); WidgetT *applyBtn = wgtButton(page, "Apply Mode");
applyBtn->onClick = onVideoApply; 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. // clicks Yes the mode is kept; No or timeout reverts to the old mode.
#define CONFIRM_SECONDS 10 #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) { if (!confirmWin) {
dvxChangeVideoMode(sAc, oldW, oldH, oldBpp); dvxChangeVideoMode(sAc, oldW, oldH, oldBpp);
@ -723,7 +737,7 @@ static void onVideoApply(WidgetT *w) {
sAc->modalWindow = confirmWin; sAc->modalWindow = confirmWin;
WidgetT *root = wgtInitWindow(sAc, confirmWin); WidgetT *root = wgtInitWindow(sAc, confirmWin);
root->spacing = wgtPixels(8); root->spacing = wgtPixels(CP_SPACING);
static char msgBuf[80]; static char msgBuf[80];
snprintf(msgBuf, sizeof(msgBuf), "Reverting in %d seconds...", CONFIRM_SECONDS); snprintf(msgBuf, sizeof(msgBuf), "Reverting in %d seconds...", CONFIRM_SECONDS);
@ -731,14 +745,14 @@ static void onVideoApply(WidgetT *w) {
WidgetT *btnRow = wgtHBox(root); WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignEndE; btnRow->align = AlignEndE;
btnRow->spacing = wgtPixels(8); btnRow->spacing = wgtPixels(CP_SPACING);
WidgetT *yesBtn = wgtButton(btnRow, "Yes"); WidgetT *yesBtn = wgtButton(btnRow, "Yes");
yesBtn->prefW = wgtPixels(70); yesBtn->prefW = wgtPixels(CP_CONFIRM_BTN_W);
yesBtn->onClick = onVideoConfirmYes; yesBtn->onClick = onVideoConfirmYes;
WidgetT *noBtn = wgtButton(btnRow, "No"); WidgetT *noBtn = wgtButton(btnRow, "No");
noBtn->prefW = wgtPixels(70); noBtn->prefW = wgtPixels(CP_CONFIRM_BTN_W);
noBtn->onClick = onVideoConfirmNo; noBtn->onClick = onVideoConfirmNo;
dvxFitWindow(sAc, confirmWin); dvxFitWindow(sAc, confirmWin);
@ -849,7 +863,7 @@ static void saveSnapshot(void) {
memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb)); memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb));
sSavedWheelDir = sAc->wheelDirection; sSavedWheelDir = sAc->wheelDirection;
sSavedDblClick = (int32_t)(sAc->dblClickTicks * 1000 / CLOCKS_PER_SEC); sSavedDblClick = (int32_t)(sAc->dblClickTicks * 1000 / CLOCKS_PER_SEC);
sSavedAccel = 0; sSavedAccel = wgtDropdownGetSelected(sAccelDrop);
sSavedVideoW = sAc->display.width; sSavedVideoW = sAc->display.width;
sSavedVideoH = sAc->display.height; sSavedVideoH = sAc->display.height;
sSavedVideoBpp = sAc->display.format.bitsPerPixel; sSavedVideoBpp = sAc->display.format.bitsPerPixel;
@ -867,8 +881,8 @@ static void restoreSnapshot(void) {
dvxApplyColorScheme(sAc); dvxApplyColorScheme(sAc);
// Restore mouse // Restore mouse
const char *accelStr = prefsGetString("mouse", "acceleration", "medium"); const char *accelName = mapAccelValue(sSavedAccel);
int32_t accelVal = mapAccelName(accelStr); int32_t accelVal = mapAccelName(accelName);
dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, accelVal); dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, accelVal);
// Restore video mode if changed // Restore video mode if changed
@ -1083,15 +1097,15 @@ int32_t appMain(DxeAppContextT *ctx) {
// OK / Cancel buttons at bottom // OK / Cancel buttons at bottom
WidgetT *btnRow = wgtHBox(root); WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignEndE; btnRow->align = AlignEndE;
btnRow->spacing = wgtPixels(8); btnRow->spacing = wgtPixels(CP_SPACING);
WidgetT *okBtn = wgtButton(btnRow, "OK"); WidgetT *okBtn = wgtButton(btnRow, "OK");
okBtn->onClick = onOk; okBtn->onClick = onOk;
okBtn->prefW = wgtPixels(80); okBtn->prefW = wgtPixels(CP_OK_CANCEL_BTN_W);
WidgetT *cancelBtn = wgtButton(btnRow, "Cancel"); WidgetT *cancelBtn = wgtButton(btnRow, "Cancel");
cancelBtn->onClick = onCancel; cancelBtn->onClick = onCancel;
cancelBtn->prefW = wgtPixels(80); cancelBtn->prefW = wgtPixels(CP_OK_CANCEL_BTN_W);
dvxFitWindow(sAc, sWin); dvxFitWindow(sAc, sWin);
return 0; return 0;

View file

@ -175,10 +175,8 @@ static void buildScaled(int32_t fitW, int32_t fitH) {
static void loadAndDisplay(const char *path) { static void loadAndDisplay(const char *path) {
// Free previous image // Free previous image
if (sImgRgb) {
stbi_image_free(sImgRgb); stbi_image_free(sImgRgb);
sImgRgb = NULL; sImgRgb = NULL;
}
int32_t channels; int32_t channels;
sImgRgb = stbi_load(path, &sImgW, &sImgH, &channels, 3); sImgRgb = stbi_load(path, &sImgW, &sImgH, &channels, 3);

View file

@ -42,6 +42,10 @@
#define CMD_PASTE 202 #define CMD_PASTE 202
#define CMD_SELALL 203 #define CMD_SELALL 203
#define NP_WIN_W 480
#define NP_WIN_H 360
#define NP_CASCADE_OFFSET 20
// ============================================================ // ============================================================
// Module state // Module state
// ============================================================ // ============================================================
@ -333,10 +337,10 @@ int32_t appMain(DxeAppContextT *ctx) {
int32_t screenW = ac->display.width; int32_t screenW = ac->display.width;
int32_t screenH = ac->display.height; int32_t screenH = ac->display.height;
int32_t winW = 480; int32_t winW = NP_WIN_W;
int32_t winH = 360; int32_t winH = NP_WIN_H;
int32_t winX = (screenW - winW) / 2 + 20; int32_t winX = (screenW - winW) / 2 + NP_CASCADE_OFFSET;
int32_t winY = (screenH - winH) / 3 + 20; int32_t winY = (screenH - winH) / 3 + NP_CASCADE_OFFSET;
sWin = dvxCreateWindow(ac, "Untitled - Notepad", winX, winY, winW, winH, true); sWin = dvxCreateWindow(ac, "Untitled - Notepad", winX, winY, winW, winH, true);

View file

@ -48,6 +48,11 @@
#define PM_GRID_COLS 4 #define PM_GRID_COLS 4
#define PM_BTN_W 100 #define PM_BTN_W 100
#define PM_BTN_H 24 #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 // Menu command IDs
#define CMD_RUN 100 #define CMD_RUN 100
@ -124,8 +129,8 @@ AppDescriptorT appDescriptor = {
static void buildPmWindow(void) { static void buildPmWindow(void) {
int32_t screenW = sAc->display.width; int32_t screenW = sAc->display.width;
int32_t screenH = sAc->display.height; int32_t screenH = sAc->display.height;
int32_t winW = 440; int32_t winW = PM_WIN_W;
int32_t winH = 340; int32_t winH = PM_WIN_H;
int32_t winX = (screenW - winW) / 2; int32_t winX = (screenW - winW) / 2;
int32_t winY = (screenH - winH) / 4; int32_t winY = (screenH - winH) / 4;
@ -170,20 +175,17 @@ static void buildPmWindow(void) {
appFrame->weight = 100; appFrame->weight = 100;
if (sAppCount == 0) { if (sAppCount == 0) {
WidgetT *lbl = wgtLabel(appFrame, "(No applications found in apps/ directory)"); wgtLabel(appFrame, "(No applications found in apps/ directory)");
(void)lbl;
} else { } else {
// Build rows of buttons. Each row is an HBox holding PM_GRID_COLS // Build rows of buttons. Each row is an HBox holding PM_GRID_COLS
// buttons. userData points back to the AppEntryT so the click // buttons. userData points back to the AppEntryT so the click
// callback knows which app to launch. // callback knows which app to launch.
int32_t row = 0;
WidgetT *hbox = NULL; WidgetT *hbox = NULL;
for (int32_t i = 0; i < sAppCount; i++) { for (int32_t i = 0; i < sAppCount; i++) {
if (i % PM_GRID_COLS == 0) { if (i % PM_GRID_COLS == 0) {
hbox = wgtHBox(appFrame); hbox = wgtHBox(appFrame);
hbox->spacing = wgtPixels(8); hbox->spacing = wgtPixels(PM_GRID_SPACING);
row++;
} }
WidgetT *btn = wgtButton(hbox, sAppFiles[i].name); WidgetT *btn = wgtButton(hbox, sAppFiles[i].name);
@ -194,7 +196,6 @@ static void buildPmWindow(void) {
btn->onClick = onAppButtonClick; btn->onClick = onAppButtonClick;
} }
(void)row;
} }
// Status bar at bottom; weight=100 on the label makes it fill the bar // 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 // Create a window with a read-only text area
int32_t screenW = sAc->display.width; int32_t screenW = sAc->display.width;
int32_t screenH = sAc->display.height; int32_t screenH = sAc->display.height;
int32_t winW = 400; int32_t winW = PM_SYSINFO_WIN_W;
int32_t winH = 360; int32_t winH = PM_SYSINFO_WIN_H;
int32_t winX = (screenW - winW) / 2; int32_t winX = (screenW - winW) / 2;
int32_t winY = (screenH - winH) / 4; int32_t winY = (screenH - winH) / 4;

View file

@ -67,6 +67,27 @@
#define POPUP_ITEM_PAD_H 8 // extra horizontal padding in popup items #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 #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 // Prototypes
// ============================================================ // ============================================================
@ -81,7 +102,9 @@ static void closeSysMenu(AppContextT *ctx);
static void interactiveScreenshot(AppContextT *ctx); static void interactiveScreenshot(AppContextT *ctx);
static void interactiveWindowScreenshot(AppContextT *ctx, WindowT *win); static void interactiveWindowScreenshot(AppContextT *ctx, WindowT *win);
static void compositeAndFlush(AppContextT *ctx); 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 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 bool dispatchAccelKey(AppContextT *ctx, char key);
static void dispatchEvents(AppContextT *ctx); static void dispatchEvents(AppContextT *ctx);
static void drawCursorAt(AppContextT *ctx, int32_t x, int32_t y); 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 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 enumModeCb(int32_t w, int32_t h, int32_t bpp, void *userData);
static void initColorScheme(AppContextT *ctx); 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 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 openPopupAtMenu(AppContextT *ctx, WindowT *win, int32_t menuIdx);
static void openSubMenu(AppContextT *ctx); static void openSubMenu(AppContextT *ctx);
@ -111,6 +135,15 @@ static void updateTooltip(AppContextT *ctx);
// keyboard activation was registered, matching Win3.x/Motif behavior. // keyboard activation was registered, matching Win3.x/Motif behavior.
WidgetT *sKeyPressedBtn = NULL; 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. // 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) { 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) { if (!rgb) {
return NULL; 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) { if (d->format.bitsPerPixel == 8) {
int32_t idx = pixel & 0xFF; int32_t idx = pixel & 0xFF;
dst[0] = d->palette[idx * 3 + 0]; dst[0] = d->palette[idx * RGB_CHANNELS + 0];
dst[1] = d->palette[idx * 3 + 1]; dst[1] = d->palette[idx * RGB_CHANNELS + 1];
dst[2] = d->palette[idx * 3 + 2]; dst[2] = d->palette[idx * RGB_CHANNELS + 2];
} else { } else {
uint32_t rv = (pixel >> d->format.redShift) & ((1u << d->format.redBits) - 1); uint32_t rv = (pixel >> d->format.redShift) & ((1u << d->format.redBits) - 1);
uint32_t gv = (pixel >> d->format.greenShift) & ((1u << d->format.greenBits) - 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[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 // dirtyCursorArea
// ============================================================ // ============================================================
@ -593,7 +645,37 @@ static void compositeAndFlush(AppContextT *ctx) {
// cursor footprints (16x16 with hotspot at 0,0 or 7,7). // cursor footprints (16x16 with hotspot at 0,0 or 7,7).
static void dirtyCursorArea(AppContextT *ctx, int32_t x, int32_t y) { 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->cursorFg = ctx->colors.cursorFg;
ctx->cursorBg = ctx->colors.cursorBg; ctx->cursorBg = ctx->colors.cursorBg;
// Repaint all windows with new colors invalidateAllWindows(ctx);
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);
} }
@ -1641,8 +1711,8 @@ void dvxCascadeWindows(AppContextT *ctx) {
int32_t offsetY = 0; int32_t offsetY = 0;
int32_t step = CHROME_TITLE_HEIGHT + CHROME_BORDER_WIDTH; int32_t step = CHROME_TITLE_HEIGHT + CHROME_BORDER_WIDTH;
int32_t winW = screenW * 2 / 3; int32_t winW = screenW * CASCADE_SIZE_NUMER / CASCADE_SIZE_DENOM;
int32_t winH = screenH * 2 / 3; int32_t winH = screenH * CASCADE_SIZE_NUMER / CASCADE_SIZE_DENOM;
if (winW < MIN_WINDOW_W) { if (winW < MIN_WINDOW_W) {
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; ctx->cursorBg = packed;
} }
// Invalidate all windows so scrollbar/chrome changes are visible invalidateAllWindows(ctx);
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);
} }
@ -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 bpp = ctx->display.format.bitsPerPixel;
int32_t bytesPerPx = ctx->display.format.bytesPerPixel; int32_t bytesPerPx = ctx->display.format.bytesPerPixel;
int32_t pitch = screenW * bytesPerPx; 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); bool dither = (bpp == 15 || bpp == 16);
uint8_t *buf = (uint8_t *)malloc(pitch * screenH); 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) // for the border area, but also ensures no garbage pixels)
uint32_t bgPx = ctx->colors.desktop; uint32_t bgPx = ctx->colors.desktop;
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++) { for (int32_t y = 0; y < screenH; y++) {
uint8_t *dst = buf + y * pitch; uint32_t *row = (uint32_t *)(buf + y * pitch);
int32_t pairs = screenW / 2;
for (int32_t x = 0; x < screenW; x++) { for (int32_t i = 0; i < pairs; i++) {
writePixel(dst, x, bgPx, bpp); 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) { if (mode == WallpaperStretchE) {
// Bilinear scale to screen dimensions with optional dither // Bilinear scale to screen dimensions with optional dither
for (int32_t y = 0; y < screenH; y++) { 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); 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 sy0 = srcYfp >> 16;
int32_t sy1 = (sy0 + 1 < imgH) ? sy0 + 1 : imgH - 1; int32_t sy1 = (sy0 + 1 < imgH) ? sy0 + 1 : imgH - 1;
int32_t fy = (srcYfp >> 8) & 0xFF; int32_t fy = (srcYfp >> FP_FRAC_SHIFT) & FP_FRAC_MASK;
int32_t ify = 256 - fy; int32_t ify = FP_BLEND_MAX - fy;
uint8_t *dst = buf + y * pitch; uint8_t *dst = buf + y * pitch;
const uint8_t *row0 = rgb + sy0 * srcStride; const uint8_t *row0 = rgb + sy0 * srcStride;
const uint8_t *row1 = rgb + sy1 * srcStride; const uint8_t *row1 = rgb + sy1 * srcStride;
for (int32_t x = 0; x < screenW; x++) { 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 sx0 = srcXfp >> 16;
int32_t sx1 = (sx0 + 1 < imgW) ? sx0 + 1 : imgW - 1; int32_t sx1 = (sx0 + 1 < imgW) ? sx0 + 1 : imgW - 1;
int32_t fx = (srcXfp >> 8) & 0xFF; int32_t fx = (srcXfp >> FP_FRAC_SHIFT) & FP_FRAC_MASK;
int32_t ifx = 256 - fx; int32_t ifx = FP_BLEND_MAX - fx;
const uint8_t *p00 = row0 + sx0 * 3; const uint8_t *p00 = row0 + sx0 * RGB_CHANNELS;
const uint8_t *p10 = row0 + sx1 * 3; const uint8_t *p10 = row0 + sx1 * RGB_CHANNELS;
const uint8_t *p01 = row1 + sx0 * 3; const uint8_t *p01 = row1 + sx0 * RGB_CHANNELS;
const uint8_t *p11 = row1 + sx1 * 3; 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 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 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; int32_t b = (p00[2] * ifx * ify + p10[2] * fx * ify + p01[2] * ifx * fy + p11[2] * fx * fy) >> 16;
if (dither) { if (dither) {
int32_t d = bayerMatrix[y & 3][x & 3]; ditherRgb(&r, &g, &b, y, x);
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); 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) { } else if (mode == WallpaperTileE) {
// Tile: repeat the image at native size across the screen // Tile: repeat the image at native size across the screen
for (int32_t y = 0; y < screenH; y++) { 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); 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++) { for (int32_t x = 0; x < screenW; x++) {
int32_t srcX = x % imgW; 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 r = src[0];
int32_t g = src[1]; int32_t g = src[1];
int32_t b = src[2]; int32_t b = src[2];
if (dither) { if (dither) {
int32_t d = bayerMatrix[y & 3][x & 3]; ditherRgb(&r, &g, &b, y, x);
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); 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 { } 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++) { 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); 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++) { for (int32_t sx = srcStartX; sx < srcEndX; sx++) {
int32_t dx = offX + 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 r = src[0];
int32_t g = src[1]; int32_t g = src[1];
int32_t b = src[2]; int32_t b = src[2];
if (dither) { if (dither) {
int32_t d = bayerMatrix[dy & 3][dx & 3]; ditherRgb(&r, &g, &b, dy, dx);
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); 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 // popup menu item index calculation can use multiply+shift instead
// of division. On a 486, integer divide is 40+ cycles; this // of division. On a 486, integer divide is 40+ cycles; this
// reciprocal trick reduces it to ~10 cycles (imul + shr). // 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 // Dirty the entire screen so the first compositeAndFlush paints everything
dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height); 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 y = 0; y < imgH; y++) {
for (int32_t x = 0; x < imgW; x++) { 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]); uint32_t color = packColor(d, src[0], src[1], src[2]);
uint8_t *dst = buf + y * pitch + x * bpp; 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; 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); free(rgb);
@ -2699,7 +2780,7 @@ int32_t dvxScreenshot(AppContextT *ctx, const char *path) {
return -1; 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); free(rgb);
@ -2784,7 +2865,7 @@ int32_t dvxWindowScreenshot(AppContextT *ctx, WindowT *win, const char *path) {
return -1; 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); free(rgb);
@ -2809,17 +2890,7 @@ int32_t dvxWindowScreenshot(AppContextT *ctx, WindowT *win, const char *path) {
void dvxTileWindows(AppContextT *ctx) { void dvxTileWindows(AppContextT *ctx) {
int32_t screenW = ctx->display.width; int32_t screenW = ctx->display.width;
int32_t screenH = ctx->display.height; int32_t screenH = ctx->display.height;
int32_t count = countVisibleWindows(ctx);
// 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++;
}
}
if (count == 0) { if (count == 0) {
return; return;
@ -2880,17 +2951,7 @@ void dvxTileWindows(AppContextT *ctx) {
void dvxTileWindowsH(AppContextT *ctx) { void dvxTileWindowsH(AppContextT *ctx) {
int32_t screenW = ctx->display.width; int32_t screenW = ctx->display.width;
int32_t screenH = ctx->display.height; int32_t screenH = ctx->display.height;
int32_t count = countVisibleWindows(ctx);
// 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++;
}
}
if (count == 0) { if (count == 0) {
return; return;
@ -2928,17 +2989,7 @@ void dvxTileWindowsH(AppContextT *ctx) {
void dvxTileWindowsV(AppContextT *ctx) { void dvxTileWindowsV(AppContextT *ctx) {
int32_t screenW = ctx->display.width; int32_t screenW = ctx->display.width;
int32_t screenH = ctx->display.height; int32_t screenH = ctx->display.height;
int32_t count = countVisibleWindows(ctx);
// 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++;
}
}
if (count == 0) { if (count == 0) {
return; 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);
}
// ============================================================ // ============================================================

View file

@ -521,6 +521,9 @@ typedef struct WidgetT {
struct WidgetT *dragItem; // item being dragged (NULL = none) struct WidgetT *dragItem; // item being dragged (NULL = none)
struct WidgetT *dropTarget; // insertion target (NULL = none) struct WidgetT *dropTarget; // insertion target (NULL = none)
bool dropAfter; // true = insert after target, false = before 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; } treeView;
struct { struct {

View file

@ -47,6 +47,14 @@
#include <sys/nearptr.h> #include <sys/nearptr.h>
#include <sys/farptr.h> #include <sys/farptr.h>
// 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 // Prototypes
// ============================================================ // ============================================================
@ -376,25 +384,25 @@ static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requ
int32_t s = 0; int32_t s = 0;
if (bpp == 16) { if (bpp == 16) {
s = 100; s = MODE_SCORE_16BPP;
} else if (bpp == 15) { } else if (bpp == 15) {
s = 90; s = MODE_SCORE_15BPP;
} else if (bpp == 32) { } else if (bpp == 32) {
s = 85; s = MODE_SCORE_32BPP;
} else if (bpp == 8) { } else if (bpp == 8) {
s = 70; s = MODE_SCORE_8BPP;
} }
// Prefer the user's preferred bpp // Prefer the user's preferred bpp
if (bpp == preferredBpp) { if (bpp == preferredBpp) {
s += 20; s += MODE_SCORE_PREF_BPP;
} }
// Exact resolution match is preferred // Exact resolution match is preferred
if (w == requestedW && h == requestedH) { if (w == requestedW && h == requestedH) {
s += 10; s += MODE_SCORE_EXACT_RES;
} else { } else {
s -= 10; s -= MODE_SCORE_EXACT_RES;
} }
*score = s; *score = s;

View file

@ -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 // Cache max item string length so calcMinSize doesn't need to re-scan
// the entire item array on every layout pass. Items are stored as // the entire item array on every layout pass. Items are stored as
// external pointers (not copied) -- the caller owns the string data. // external pointers (not copied) -- the caller owns the string data.
int32_t maxLen = 0; w->as.comboBox.maxItemLen = widgetMaxItemLen(items, count);
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;
if (w->as.comboBox.selectedIdx >= count) { if (w->as.comboBox.selectedIdx >= count) {
w->as.comboBox.selectedIdx = -1; 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; uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow;
int32_t arrowX = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2; int32_t arrowX = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2;
int32_t arrowY = w->y + w->h / 2 - 1; int32_t arrowY = w->y + w->h / 2 - 1;
widgetDrawDropdownArrow(d, ops, arrowX, arrowY, arrowFg);
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, arrowX - 3 + i, arrowY + i, 7 - i * 2, arrowFg);
}
} }

View file

@ -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 // 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 // widgetNavigateIndex
// ============================================================ // ============================================================

View file

@ -59,17 +59,7 @@ void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) {
w->as.dropdown.itemCount = count; w->as.dropdown.itemCount = count;
// Cache max item strlen to avoid recomputing in calcMinSize // Cache max item strlen to avoid recomputing in calcMinSize
int32_t maxLen = 0; w->as.dropdown.maxItemLen = widgetMaxItemLen(items, count);
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;
if (w->as.dropdown.selectedIdx >= count) { if (w->as.dropdown.selectedIdx >= count) {
w->as.dropdown.selectedIdx = -1; w->as.dropdown.selectedIdx = -1;
@ -254,16 +244,11 @@ void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
btnBevel.width = 2; btnBevel.width = 2;
drawBevel(d, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel); drawBevel(d, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
// Down arrow glyph in button -- a small filled triangle drawn as horizontal // Down arrow glyph in button
// lines of decreasing width (7, 5, 3, 1 pixels). This creates a 4-pixel
// tall downward-pointing triangle centered in the button.
uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow; uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow;
int32_t arrowX = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2; int32_t arrowX = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2;
int32_t arrowY = w->y + w->h / 2 - 1; int32_t arrowY = w->y + w->h / 2 - 1;
widgetDrawDropdownArrow(d, ops, arrowX, arrowY, arrowFg);
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, arrowX - 3 + i, arrowY + i, 7 - i * 2, arrowFg);
}
if (w->focused) { if (w->focused) {
drawFocusRect(d, ops, w->x + 3, w->y + 3, textAreaW - 6, w->h - 6, fg); drawFocusRect(d, ops, w->x + 3, w->y + 3, textAreaW - 6, w->h - 6, fg);

View file

@ -925,6 +925,8 @@ void widgetReorderDrop(WidgetT *w) {
return; return;
} }
w->as.treeView.dimsValid = false;
// Unlink drag from its current parent // Unlink drag from its current parent
WidgetT *oldParent = drag->parent; WidgetT *oldParent = drag->parent;

View file

@ -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. // 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); 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). // 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); 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);

View file

@ -238,17 +238,7 @@ void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
w->as.listBox.itemCount = count; w->as.listBox.itemCount = count;
// Cache max item strlen to avoid recomputing in calcMinSize // Cache max item strlen to avoid recomputing in calcMinSize
int32_t maxLen = 0; w->as.listBox.maxItemLen = widgetMaxItemLen(items, count);
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;
if (w->as.listBox.selectedIdx >= count) { if (w->as.listBox.selectedIdx >= count) {
w->as.listBox.selectedIdx = count > 0 ? 0 : -1; w->as.listBox.selectedIdx = count > 0 ? 0 : -1;

View file

@ -405,6 +405,16 @@ void wgtListViewSetColumns(WidgetT *w, const ListViewColT *cols, int32_t count)
w->as.listView->cols = cols; w->as.listView->cols = cols;
w->as.listView->colCount = count; w->as.listView->colCount = count;
w->as.listView->totalColW = 0; 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); 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; 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); wgtInvalidate(w);
} }

View file

@ -153,6 +153,16 @@ void wgtDestroy(WidgetT *w) {
return; 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) { if (w->parent) {
widgetRemoveChild(w->parent, w); widgetRemoveChild(w->parent, w);
} }
@ -555,6 +565,17 @@ void wgtSetTooltip(WidgetT *w, const char *text) {
void wgtSetVisible(WidgetT *w, bool visible) { void wgtSetVisible(WidgetT *w, bool visible) {
if (w) { if (w) {
w->visible = visible; 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); wgtInvalidate(w);
} }
} }

View file

@ -42,10 +42,10 @@
// the actual node reparenting via widgetRemoveChild/widgetAddChild. // the actual node reparenting via widgetRemoveChild/widgetAddChild.
// //
// Performance note: calcTreeItemsHeight and calcTreeItemsMaxWidth // Performance note: calcTreeItemsHeight and calcTreeItemsMaxWidth
// walk the full visible tree on every call. For trees with hundreds // walk the full visible tree, but results are cached in the TreeView's
// of items this could be optimized with caching (as TextArea does // cachedTotalHeight/cachedMaxWidth fields. The cache is invalidated
// for line count). In practice, DOS-era tree views are small enough // (dimsValid = false) whenever the tree structure changes: item add/
// that the linear scan is fast on a Pentium. // remove, expand/collapse, text change, or drag-reorder.
#include "widgetInternal.h" #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 int32_t calcTreeItemsMaxWidth(WidgetT *parent, const BitmapFontT *font, int32_t depth);
static void clearAllSelections(WidgetT *parent); static void clearAllSelections(WidgetT *parent);
static WidgetT *firstVisibleItem(WidgetT *treeView); 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 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 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); 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 // layoutTreeItems
// ============================================================ // ============================================================
@ -489,8 +508,14 @@ static void selectRange(WidgetT *treeView, WidgetT *from, WidgetT *to) {
// needed, accounting for the mutual space dependency between them. // 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) { 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); if (!w->as.treeView.dimsValid) {
int32_t totalW = calcTreeItemsMaxWidth(w, font, 0); 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 innerH = w->h - TREE_BORDER * 2;
int32_t innerW = w->w - TREE_BORDER * 2; int32_t innerW = w->w - TREE_BORDER * 2;
bool needVSb = (totalH > innerH); bool needVSb = (totalH > innerH);
@ -611,6 +636,7 @@ WidgetT *wgtTreeItem(WidgetT *parent, const char *text) {
if (w) { if (w) {
w->as.treeItem.text = text; w->as.treeItem.text = text;
w->as.treeItem.expanded = false; w->as.treeItem.expanded = false;
invalidateTreeDims(w);
} }
return w; return w;
@ -632,6 +658,7 @@ const char *widgetTreeItemGetText(const WidgetT *w) {
void widgetTreeItemSetText(WidgetT *w, const char *text) { void widgetTreeItemSetText(WidgetT *w, const char *text) {
w->as.treeItem.text = text; w->as.treeItem.text = text;
invalidateTreeDims(w);
} }
@ -654,6 +681,7 @@ void wgtTreeItemSetExpanded(WidgetT *w, bool expanded) {
VALIDATE_WIDGET_VOID(w, WidgetTreeItemE); VALIDATE_WIDGET_VOID(w, WidgetTreeItemE);
w->as.treeItem.expanded = expanded; w->as.treeItem.expanded = expanded;
invalidateTreeDims(w);
wgtInvalidate(w); wgtInvalidate(w);
} }
@ -682,6 +710,7 @@ static void treeDefaultDblClick(WidgetT *w) {
if (hasChildren) { if (hasChildren) {
sel->as.treeItem.expanded = !sel->as.treeItem.expanded; sel->as.treeItem.expanded = !sel->as.treeItem.expanded;
invalidateTreeDims(sel);
if (sel->onChange) { if (sel->onChange) {
sel->onChange(sel); sel->onChange(sel);
@ -875,6 +904,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (hasChildren) { if (hasChildren) {
if (!sel->as.treeItem.expanded) { if (!sel->as.treeItem.expanded) {
sel->as.treeItem.expanded = true; sel->as.treeItem.expanded = true;
w->as.treeView.dimsValid = false;
if (sel->onChange) { if (sel->onChange) {
sel->onChange(sel); sel->onChange(sel);
@ -893,6 +923,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (sel) { if (sel) {
if (sel->as.treeItem.expanded) { if (sel->as.treeItem.expanded) {
sel->as.treeItem.expanded = false; sel->as.treeItem.expanded = false;
w->as.treeView.dimsValid = false;
if (sel->onChange) { if (sel->onChange) {
sel->onChange(sel); sel->onChange(sel);
@ -915,6 +946,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (hasChildren) { if (hasChildren) {
sel->as.treeItem.expanded = !sel->as.treeItem.expanded; sel->as.treeItem.expanded = !sel->as.treeItem.expanded;
w->as.treeView.dimsValid = false;
if (sel->onChange) { if (sel->onChange) {
sel->onChange(sel); 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) { if (vx >= iconX && vx < iconX + TREE_EXPAND_SIZE) {
clickedExpandIcon = true; clickedExpandIcon = true;
item->as.treeItem.expanded = !item->as.treeItem.expanded; item->as.treeItem.expanded = !item->as.treeItem.expanded;
hit->as.treeView.dimsValid = false;
// Clamp scroll positions if collapsing reduced content size // Clamp scroll positions if collapsing reduced content size
if (!item->as.treeItem.expanded) { if (!item->as.treeItem.expanded) {

View file

@ -21,11 +21,17 @@
#define TM_COL_COUNT 5 #define TM_COL_COUNT 5
#define TM_MAX_PATH 260 #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 // Prototypes
// ============================================================ // ============================================================
static ShellAppT *getRunningAppByIndex(int32_t sel);
static void onTmClose(WindowT *win); static void onTmClose(WindowT *win);
static void onTmEndTask(WidgetT *w); static void onTmEndTask(WidgetT *w);
static void onTmRun(WidgetT *w); static void onTmRun(WidgetT *w);
@ -52,6 +58,28 @@ static const char **sCells = NULL; // dynamic array of cell pointers
static TmRowStringsT *sRowStrs = NULL; // dynamic array of per-row strings 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 // onTmClose
// ============================================================ // ============================================================
@ -86,22 +114,11 @@ static void onTmEndTask(WidgetT *w) {
return; return;
} }
// Re-walk the app slot table in the same order as refreshTaskList() ShellAppT *app = getRunningAppByIndex(sel);
// to map the selected row index back to the correct ShellAppT.
int32_t idx = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) { if (app) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
if (idx == sel) {
shellForceKillApp(sCtx, app); shellForceKillApp(sCtx, app);
shellDesktopUpdate(); shellDesktopUpdate();
return;
}
idx++;
}
} }
} }
@ -142,20 +159,18 @@ static void onTmSwitchTo(WidgetT *w) {
return; return;
} }
// Same index-to-appId mapping as refreshTaskList. Scan the window ShellAppT *app = getRunningAppByIndex(sel);
// 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;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) { if (!app) {
ShellAppT *app = shellGetApp(i); return;
}
if (app && app->state == AppStateRunningE) { // Scan the window stack top-down (highest Z first) to find the app's
if (idx == sel) { // topmost window, restore it if minimized, then raise and focus it.
for (int32_t j = sCtx->stack.count - 1; j >= 0; j--) { for (int32_t j = sCtx->stack.count - 1; j >= 0; j--) {
WindowT *win = sCtx->stack.windows[j]; WindowT *win = sCtx->stack.windows[j];
if (win->appId == i) { if (win->appId == app->appId) {
if (win->minimized) { if (win->minimized) {
wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, win); wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, win);
} }
@ -165,13 +180,6 @@ static void onTmSwitchTo(WidgetT *w) {
return; return;
} }
} }
return;
}
idx++;
}
}
} }
@ -283,8 +291,8 @@ void shellTaskMgrOpen(AppContextT *ctx) {
return; return;
} }
int32_t winW = 520; int32_t winW = TM_WIN_W;
int32_t winH = 280; int32_t winH = TM_WIN_H;
int32_t winX = (ctx->display.width - winW) / 2; int32_t winX = (ctx->display.width - winW) / 2;
int32_t winY = (ctx->display.height - winH) / 3; int32_t winY = (ctx->display.height - winH) / 3;
@ -318,26 +326,26 @@ void shellTaskMgrOpen(AppContextT *ctx) {
sTmListView = wgtListView(root); sTmListView = wgtListView(root);
sTmListView->weight = 100; sTmListView->weight = 100;
sTmListView->prefH = wgtPixels(160); sTmListView->prefH = wgtPixels(TM_LIST_PREF_H);
wgtListViewSetColumns(sTmListView, tmCols, TM_COL_COUNT); wgtListViewSetColumns(sTmListView, tmCols, TM_COL_COUNT);
WidgetT *btnRow = wgtHBox(root); WidgetT *btnRow = wgtHBox(root);
btnRow->spacing = wgtPixels(8); btnRow->spacing = wgtPixels(TM_BTN_SPACING);
sTmStatusLbl = wgtLabel(btnRow, ""); sTmStatusLbl = wgtLabel(btnRow, "");
sTmStatusLbl->weight = 100; sTmStatusLbl->weight = 100;
WidgetT *switchBtn = wgtButton(btnRow, "Switch To"); WidgetT *switchBtn = wgtButton(btnRow, "Switch To");
switchBtn->onClick = onTmSwitchTo; switchBtn->onClick = onTmSwitchTo;
switchBtn->prefW = wgtPixels(90); switchBtn->prefW = wgtPixels(TM_BTN_W);
WidgetT *endBtn = wgtButton(btnRow, "End Task"); WidgetT *endBtn = wgtButton(btnRow, "End Task");
endBtn->onClick = onTmEndTask; endBtn->onClick = onTmEndTask;
endBtn->prefW = wgtPixels(90); endBtn->prefW = wgtPixels(TM_BTN_W);
WidgetT *runBtn = wgtButton(btnRow, "Run..."); WidgetT *runBtn = wgtButton(btnRow, "Run...");
runBtn->onClick = onTmRun; runBtn->onClick = onTmRun;
runBtn->prefW = wgtPixels(90); runBtn->prefW = wgtPixels(TM_BTN_W);
shellRegisterDesktopUpdate(shellTaskMgrRefresh); shellRegisterDesktopUpdate(shellTaskMgrRefresh);
refreshTaskList(); refreshTaskList();