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
// ============================================================
#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);

View file

@ -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;

View file

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

View file

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

View file

@ -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;

View file

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

View file

@ -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 {

View file

@ -47,6 +47,14 @@
#include <sys/nearptr.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
// ============================================================
@ -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;

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
// 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);
}

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

View file

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

View file

@ -925,6 +925,8 @@ void widgetReorderDrop(WidgetT *w) {
return;
}
w->as.treeView.dimsValid = false;
// Unlink drag from its current 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.
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);

View file

@ -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;

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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) {

View file

@ -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();