// ctrlpanel.c — DVX Control Panel // // System configuration app with four tabs: // Mouse — scroll direction, double-click speed, acceleration // Colors — all 18 system colors, theme load/save // Desktop — wallpaper image (stretch mode) // Video — resolution and color depth // // Changes preview live. OK saves to DVX.INI, Cancel reverts to the // state captured when the control panel was opened. #include "dvxApp.h" #include "dvxDialog.h" #include "dvxPrefs.h" #include "dvxWidget.h" #include "dvxWm.h" #include "platform/dvxPlatform.h" #include "shellApp.h" #include "thirdparty/stb_ds.h" #include #include #include #include #include #include // ============================================================ // Constants // ============================================================ #define CP_WIN_W 460 #define CP_WIN_H 340 #define WPAPER_DIR "CONFIG/WPAPER" #define THEME_DIR "CONFIG/THEMES" #define THEME_EXT ".THM" // ============================================================ // App descriptor // ============================================================ AppDescriptorT appDescriptor = { .name = "Control Panel", .hasMainLoop = false, .multiInstance = false, .stackSize = SHELL_STACK_DEFAULT, .priority = TS_PRIORITY_NORMAL }; // ============================================================ // Entry types for dynamic lists // ============================================================ typedef struct { char name[64]; char path[260]; } FileEntryT; // ============================================================ // Module state // ============================================================ static DxeAppContextT *sCtx = NULL; static AppContextT *sAc = NULL; static WindowT *sWin = NULL; // Saved state for Cancel static uint8_t sSavedColorRgb[ColorCountE][3]; static int32_t sSavedWheelDir; static int32_t sSavedDblClick; static int32_t sSavedAccel; static int32_t sSavedVideoW; static int32_t sSavedVideoH; static int32_t sSavedVideoBpp; static bool sSavedHadWallpaper; // Mouse tab widgets static WidgetT *sWheelDrop = NULL; static WidgetT *sDblClickSldr = NULL; static WidgetT *sDblClickLbl = NULL; static WidgetT *sAccelDrop = NULL; static WidgetT *sDblTestLbl = NULL; // Colors tab widgets static WidgetT *sColorList = NULL; static WidgetT *sRedSldr = NULL; static WidgetT *sGreenSldr = NULL; static WidgetT *sBlueSldr = NULL; static WidgetT *sRedLbl = NULL; static WidgetT *sGreenLbl = NULL; static WidgetT *sBlueLbl = NULL; static WidgetT *sColorSwatch = NULL; // Desktop tab widgets static WidgetT *sWallpaperLbl = NULL; static WidgetT *sWpaperList = NULL; static char sWallpaperPath[260]; static FileEntryT *sWpaperEntries = NULL; // stb_ds dynamic array static const char **sWpaperLabels = NULL; // stb_ds dynamic array // Video tab static const VideoModeInfoT *sVideoModes = NULL; // points into ctx, not owned static int32_t sVideoCount = 0; static const char **sVideoLabels = NULL; // stb_ds dynamic array static WidgetT *sVideoList = NULL; // Theme list static FileEntryT *sThemeEntries = NULL; // stb_ds dynamic array static const char **sThemeLabels = NULL; // stb_ds dynamic array static WidgetT *sThemeList = NULL; // ============================================================ // Prototypes // ============================================================ int32_t appMain(DxeAppContextT *ctx); static void buildMouseTab(WidgetT *page); static void buildColorsTab(WidgetT *page); static void buildDesktopTab(WidgetT *page); static void buildVideoTab(WidgetT *page); static int32_t mapAccelName(const char *name); static const char *mapAccelValue(int32_t val); static void onAccelChange(WidgetT *w); static void onApplyTheme(WidgetT *w); static void onBrowseTheme(WidgetT *w); static void onResetColors(WidgetT *w); static void onApplyWallpaper(WidgetT *w); static void onCancel(WidgetT *w); static void onChooseWallpaper(WidgetT *w); static void onClearWallpaper(WidgetT *w); static void onClose(WindowT *win); static void onColorSelect(WidgetT *w); static void onColorSlider(WidgetT *w); static void onDblClickSlider(WidgetT *w); static void onDblClickTest(WidgetT *w); static void onOk(WidgetT *w); static void onSaveTheme(WidgetT *w); static void onVideoApply(WidgetT *w); static void onWheelChange(WidgetT *w); static void saveSnapshot(void); static void restoreSnapshot(void); static void scanWallpapers(void); static void scanThemes(void); static void updateColorSliders(void); static void updateDblClickLabel(void); static void updateSwatch(void); // ============================================================ // buildColorsTab // ============================================================ 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); // Left side: color name list + theme controls WidgetT *leftVbox = wgtVBox(hbox); leftVbox->weight = 50; leftVbox->spacing = wgtPixels(4); wgtLabel(leftVbox, "System Colors:"); // Build color name list static const char *colorNames[ColorCountE]; for (int32_t i = 0; i < ColorCountE; i++) { colorNames[i] = dvxColorName((ColorIdE)i); } sColorList = wgtListBox(leftVbox); sColorList->weight = 100; sColorList->onChange = onColorSelect; wgtListBoxSetItems(sColorList, colorNames, ColorCountE); wgtListBoxSetSelected(sColorList, 0); // Theme row WidgetT *themeRow = wgtHBox(leftVbox); themeRow->spacing = wgtPixels(4); wgtLabel(themeRow, "Theme:"); scanThemes(); sThemeList = wgtDropdown(themeRow); sThemeList->weight = 100; wgtDropdownSetItems(sThemeList, sThemeLabels, arrlen(sThemeEntries)); WidgetT *loadBtn = wgtButton(themeRow, "Apply"); loadBtn->onClick = onApplyTheme; loadBtn->prefW = wgtPixels(60); WidgetT *browseBtn = wgtButton(themeRow, "Load..."); browseBtn->onClick = onBrowseTheme; browseBtn->prefW = wgtPixels(60); WidgetT *saveBtn = wgtButton(themeRow, "Save As..."); saveBtn->onClick = onSaveTheme; saveBtn->prefW = wgtPixels(70); WidgetT *resetBtn = wgtButton(themeRow, "Reset"); resetBtn->onClick = onResetColors; resetBtn->prefW = wgtPixels(60); // Right side: RGB sliders WidgetT *rightVbox = wgtVBox(hbox); rightVbox->weight = 50; rightVbox->spacing = wgtPixels(4); wgtLabel(rightVbox, "Red:"); sRedSldr = wgtSlider(rightVbox, 0, 255); sRedSldr->onChange = onColorSlider; sRedLbl = wgtLabel(rightVbox, "0"); wgtLabel(rightVbox, "Green:"); sGreenSldr = wgtSlider(rightVbox, 0, 255); sGreenSldr->onChange = onColorSlider; sGreenLbl = wgtLabel(rightVbox, "0"); wgtLabel(rightVbox, "Blue:"); sBlueSldr = wgtSlider(rightVbox, 0, 255); sBlueSldr->onChange = onColorSlider; sBlueLbl = wgtLabel(rightVbox, "0"); wgtLabel(rightVbox, "Preview:"); sColorSwatch = wgtCanvas(rightVbox, 64, 24); updateColorSliders(); } // ============================================================ // buildDesktopTab // ============================================================ static void buildDesktopTab(WidgetT *page) { wgtLabel(page, "Wallpaper:"); scanWallpapers(); sWpaperList = wgtListBox(page); sWpaperList->weight = 100; sWpaperList->onDblClick = onApplyWallpaper; wgtListBoxSetItems(sWpaperList, sWpaperLabels, arrlen(sWpaperEntries)); sWallpaperLbl = wgtLabel(page, sWallpaperPath[0] ? sWallpaperPath : "(none)"); WidgetT *btnRow = wgtHBox(page); btnRow->spacing = wgtPixels(8); WidgetT *applyBtn = wgtButton(btnRow, "Apply"); applyBtn->onClick = onApplyWallpaper; applyBtn->prefW = wgtPixels(90); WidgetT *chooseBtn = wgtButton(btnRow, "Browse..."); chooseBtn->onClick = onChooseWallpaper; chooseBtn->prefW = wgtPixels(90); WidgetT *clearBtn = wgtButton(btnRow, "Clear"); clearBtn->onClick = onClearWallpaper; clearBtn->prefW = wgtPixels(90); } // ============================================================ // buildMouseTab // ============================================================ static void buildMouseTab(WidgetT *page) { page->spacing = wgtPixels(6); // Scroll direction WidgetT *wheelRow = wgtHBox(page); wheelRow->spacing = wgtPixels(8); wgtLabel(wheelRow, "Scroll Wheel:"); static const char *wheelItems[] = {"Normal", "Reversed"}; sWheelDrop = wgtDropdown(wheelRow); sWheelDrop->weight = 100; sWheelDrop->onChange = onWheelChange; wgtDropdownSetItems(sWheelDrop, wheelItems, 2); wgtDropdownSetSelected(sWheelDrop, sAc->wheelDirection < 0 ? 1 : 0); wgtSpacer(page)->prefH = wgtPixels(4); // Double-click speed wgtLabel(page, "Double-Click Speed:"); WidgetT *dblRow = wgtHBox(page); dblRow->spacing = wgtPixels(8); wgtLabel(dblRow, "Fast"); sDblClickSldr = wgtSlider(dblRow, 200, 900); sDblClickSldr->weight = 100; sDblClickSldr->onChange = onDblClickSlider; int32_t dblMs = prefsGetInt("mouse", "doubleclick", 500); wgtSliderSetValue(sDblClickSldr, dblMs); wgtLabel(dblRow, "Slow "); sDblClickLbl = wgtLabel(dblRow, ""); sDblClickLbl->prefW = wgtPixels(52); updateDblClickLabel(); wgtSpacer(page)->prefH = wgtPixels(4); // Acceleration WidgetT *accelRow = wgtHBox(page); accelRow->spacing = wgtPixels(8); wgtLabel(accelRow, "Acceleration:"); static const char *accelItems[] = {"Off", "Low", "Medium", "High"}; sAccelDrop = wgtDropdown(accelRow); sAccelDrop->weight = 100; sAccelDrop->onChange = onAccelChange; wgtDropdownSetItems(sAccelDrop, accelItems, 4); const char *accelStr = prefsGetString("mouse", "acceleration", "medium"); if (strcmp(accelStr, "off") == 0) { wgtDropdownSetSelected(sAccelDrop, 0); } else if (strcmp(accelStr, "low") == 0) { wgtDropdownSetSelected(sAccelDrop, 1); } else if (strcmp(accelStr, "high") == 0) { wgtDropdownSetSelected(sAccelDrop, 3); } else { wgtDropdownSetSelected(sAccelDrop, 2); } wgtSpacer(page)->prefH = wgtPixels(4); // Double-click test area WidgetT *testRow = wgtHBox(page); testRow->spacing = wgtPixels(8); WidgetT *testBtn = wgtButton(testRow, "Test Area"); testBtn->onDblClick = onDblClickTest; testBtn->prefW = wgtPixels(100); sDblTestLbl = wgtLabel(testRow, "Double-click the button to test"); } // ============================================================ // buildVideoTab // ============================================================ static void buildVideoTab(WidgetT *page) { wgtLabel(page, "Available Video Modes:"); // Read modes enumerated at init time sVideoModes = dvxGetVideoModes(sAc, &sVideoCount); // Build label strings arrsetlen(sVideoLabels, 0); static char (*labelBufs)[48] = NULL; arrfree(labelBufs); labelBufs = NULL; arrsetlen(labelBufs, sVideoCount); for (int32_t i = 0; i < sVideoCount; i++) { const char *depthName; switch (sVideoModes[i].bpp) { case 8: depthName = "256 colors"; break; case 15: depthName = "32 thousand colors"; break; case 16: depthName = "65 thousand colors"; break; case 24: depthName = "16 million colors"; break; case 32: depthName = "16 million colors+"; break; default: depthName = ""; break; } snprintf(labelBufs[i], 48, "%ldx%ld %s", (long)sVideoModes[i].w, (long)sVideoModes[i].h, depthName); arrput(sVideoLabels, (const char *)labelBufs[i]); } sVideoList = wgtListBox(page); sVideoList->weight = 100; sVideoList->onDblClick = onVideoApply; wgtListBoxSetItems(sVideoList, sVideoLabels, sVideoCount); // Select the current mode for (int32_t i = 0; i < sVideoCount; i++) { if (sVideoModes[i].w == sAc->display.width && sVideoModes[i].h == sAc->display.height && sVideoModes[i].bpp == sAc->display.format.bitsPerPixel) { wgtListBoxSetSelected(sVideoList, i); break; } } WidgetT *applyBtn = wgtButton(page, "Apply Mode"); applyBtn->onClick = onVideoApply; applyBtn->prefW = wgtPixels(100); } // ============================================================ // mapAccelName / mapAccelValue // ============================================================ static int32_t mapAccelName(const char *name) { if (strcmp(name, "off") == 0) return 10000; if (strcmp(name, "low") == 0) return 100; if (strcmp(name, "medium") == 0) return 64; if (strcmp(name, "high") == 0) return 32; return 64; } static const char *mapAccelValue(int32_t idx) { switch (idx) { case 0: return "off"; case 1: return "low"; case 2: return "medium"; case 3: return "high"; default: return "medium"; } } // ============================================================ // Callbacks — Mouse tab // ============================================================ static void onWheelChange(WidgetT *w) { int32_t sel = wgtDropdownGetSelected(w); int32_t dir = (sel == 1) ? -1 : 1; int32_t dbl = wgtSliderGetValue(sDblClickSldr); const char *accelName = mapAccelValue(wgtDropdownGetSelected(sAccelDrop)); int32_t accelVal = mapAccelName(accelName); dvxSetMouseConfig(sAc, dir, dbl, accelVal); } static void onDblClickSlider(WidgetT *w) { (void)w; updateDblClickLabel(); int32_t dir = (wgtDropdownGetSelected(sWheelDrop) == 1) ? -1 : 1; int32_t dbl = wgtSliderGetValue(sDblClickSldr); const char *accelName = mapAccelValue(wgtDropdownGetSelected(sAccelDrop)); int32_t accelVal = mapAccelName(accelName); dvxSetMouseConfig(sAc, dir, dbl, accelVal); } static void onDblClickTest(WidgetT *w) { (void)w; static int32_t count = 0; count++; static char buf[48]; snprintf(buf, sizeof(buf), "Double-click detected! (%ld)", (long)count); wgtSetText(sDblTestLbl, buf); } static void onAccelChange(WidgetT *w) { int32_t dir = (wgtDropdownGetSelected(sWheelDrop) == 1) ? -1 : 1; int32_t dbl = wgtSliderGetValue(sDblClickSldr); const char *accelName = mapAccelValue(wgtDropdownGetSelected(w)); int32_t accelVal = mapAccelName(accelName); dvxSetMouseConfig(sAc, dir, dbl, accelVal); } // ============================================================ // Callbacks — Colors tab // ============================================================ static void onColorSelect(WidgetT *w) { (void)w; updateColorSliders(); } static void onColorSlider(WidgetT *w) { (void)w; int32_t sel = wgtListBoxGetSelected(sColorList); if (sel < 0 || sel >= ColorCountE) { return; } uint8_t r = (uint8_t)wgtSliderGetValue(sRedSldr); uint8_t g = (uint8_t)wgtSliderGetValue(sGreenSldr); uint8_t b = (uint8_t)wgtSliderGetValue(sBlueSldr); // Update label text static char rBuf[8]; static char gBuf[8]; static char bBuf[8]; snprintf(rBuf, sizeof(rBuf), "%d", r); snprintf(gBuf, sizeof(gBuf), "%d", g); snprintf(bBuf, sizeof(bBuf), "%d", b); wgtSetText(sRedLbl, rBuf); wgtSetText(sGreenLbl, gBuf); wgtSetText(sBlueLbl, bBuf); dvxSetColor(sAc, (ColorIdE)sel, r, g, b); updateSwatch(); } static void onApplyTheme(WidgetT *w) { (void)w; int32_t sel = wgtDropdownGetSelected(sThemeList); if (sel < 0 || sel >= arrlen(sThemeEntries)) { return; } dvxLoadTheme(sAc, sThemeEntries[sel].path); updateColorSliders(); } static void onBrowseTheme(WidgetT *w) { (void)w; FileFilterT filters[] = { { "Theme Files (*.thm)", "*.thm" }, { "All Files (*.*)", "*.*" } }; char path[260]; if (dvxFileDialog(sAc, "Load Theme", FD_OPEN, THEME_DIR, filters, 2, path, sizeof(path))) { dvxLoadTheme(sAc, path); updateColorSliders(); } } static void onSaveTheme(WidgetT *w) { (void)w; FileFilterT filters[] = { { "Theme Files (*.thm)", "*.thm" }, { "All Files (*.*)", "*.*" } }; char path[260]; if (dvxFileDialog(sAc, "Save Theme", FD_SAVE, THEME_DIR, filters, 2, path, sizeof(path))) { dvxSaveTheme(sAc, path); scanThemes(); wgtDropdownSetItems(sThemeList, sThemeLabels, arrlen(sThemeEntries)); } } static void onResetColors(WidgetT *w) { (void)w; dvxResetColorScheme(sAc); updateColorSliders(); } // ============================================================ // Callbacks — Desktop tab // ============================================================ static void onApplyWallpaper(WidgetT *w) { (void)w; int32_t sel = wgtListBoxGetSelected(sWpaperList); if (sel < 0 || sel >= arrlen(sWpaperEntries)) { return; } if (dvxSetWallpaper(sAc, sWpaperEntries[sel].path)) { snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sWpaperEntries[sel].path); wgtSetText(sWallpaperLbl, sWallpaperPath); } else { dvxMessageBox(sAc, "Error", "Could not load wallpaper image.", MB_OK | MB_ICONERROR); } } static void onChooseWallpaper(WidgetT *w) { (void)w; FileFilterT filters[] = { { "Images (*.bmp;*.jpg;*.png)", "*.bmp;*.jpg;*.png" }, { "All Files (*.*)", "*.*" } }; char path[260]; if (dvxFileDialog(sAc, "Choose Wallpaper", FD_OPEN, "CONFIG/WPAPER", filters, 2, path, sizeof(path))) { if (dvxSetWallpaper(sAc, path)) { strncpy(sWallpaperPath, path, sizeof(sWallpaperPath) - 1); sWallpaperPath[sizeof(sWallpaperPath) - 1] = '\0'; wgtSetText(sWallpaperLbl, sWallpaperPath); } else { dvxMessageBox(sAc, "Error", "Could not load wallpaper image.", MB_OK | MB_ICONERROR); } } } static void onClearWallpaper(WidgetT *w) { (void)w; dvxSetWallpaper(sAc, NULL); sWallpaperPath[0] = '\0'; wgtSetText(sWallpaperLbl, "(none)"); } // ============================================================ // Callbacks — Video tab // ============================================================ static int32_t sVideoConfirmResult = -1; static void onVideoConfirmYes(WidgetT *w) { (void)w; sVideoConfirmResult = 1; } static void onVideoConfirmNo(WidgetT *w) { (void)w; sVideoConfirmResult = 0; } static void onVideoConfirmClose(WindowT *win) { (void)win; sVideoConfirmResult = 0; } static void onVideoApply(WidgetT *w) { (void)w; // Guard against re-entrancy: the confirmation dialog runs a nested // dvxUpdate loop which can process pending events that call us again. static bool sInProgress = false; if (sInProgress) { return; } sInProgress = true; int32_t sel = wgtListBoxGetSelected(sVideoList); if (sel < 0 || sel >= sVideoCount) { sInProgress = false; return; } const VideoModeInfoT *m = &sVideoModes[sel]; int32_t oldW = sAc->display.width; int32_t oldH = sAc->display.height; int32_t oldBpp = sAc->display.format.bitsPerPixel; if (m->w == oldW && m->h == oldH && m->bpp == oldBpp) { sInProgress = false; return; } if (dvxChangeVideoMode(sAc, m->w, m->h, m->bpp) != 0) { dvxMessageBox(sAc, "Error", "Failed to change video mode.", MB_OK | MB_ICONERROR); sInProgress = false; return; } // Confirmation with 10-second auto-revert countdown. Run a nested // event loop that updates the dialog title each second. If the user // 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); if (!confirmWin) { dvxChangeVideoMode(sAc, oldW, oldH, oldBpp); sInProgress = false; return; } confirmWin->modal = true; confirmWin->onClose = onVideoConfirmClose; sAc->modalWindow = confirmWin; WidgetT *root = wgtInitWindow(sAc, confirmWin); root->spacing = wgtPixels(8); static char msgBuf[80]; snprintf(msgBuf, sizeof(msgBuf), "Reverting in %d seconds...", CONFIRM_SECONDS); WidgetT *msgLbl = wgtLabel(root, msgBuf); WidgetT *btnRow = wgtHBox(root); btnRow->align = AlignEndE; btnRow->spacing = wgtPixels(8); WidgetT *yesBtn = wgtButton(btnRow, "Yes"); yesBtn->prefW = wgtPixels(70); yesBtn->onClick = onVideoConfirmYes; WidgetT *noBtn = wgtButton(btnRow, "No"); noBtn->prefW = wgtPixels(70); noBtn->onClick = onVideoConfirmNo; dvxFitWindow(sAc, confirmWin); sVideoConfirmResult = -1; clock_t lastSecond = clock(); int32_t secondsLeft = CONFIRM_SECONDS; while (sAc->running && secondsLeft > 0 && sVideoConfirmResult < 0) { dvxUpdate(sAc); clock_t now = clock(); if ((now - lastSecond) >= CLOCKS_PER_SEC) { lastSecond = now; secondsLeft--; snprintf(msgBuf, sizeof(msgBuf), "Reverting in %ld seconds...", (long)secondsLeft); wgtSetText(msgLbl, msgBuf); } } sAc->modalWindow = NULL; dvxDestroyWindow(sAc, confirmWin); if (sVideoConfirmResult != 1) { dvxChangeVideoMode(sAc, oldW, oldH, oldBpp); } sInProgress = false; } // ============================================================ // Callbacks — OK / Cancel / Close // ============================================================ static void onOk(WidgetT *w) { (void)w; // Save mouse settings int32_t wheelSel = wgtDropdownGetSelected(sWheelDrop); prefsSetString("mouse", "wheel", wheelSel == 1 ? "reversed" : "normal"); prefsSetInt("mouse", "doubleclick", wgtSliderGetValue(sDblClickSldr)); prefsSetString("mouse", "acceleration", mapAccelValue(wgtDropdownGetSelected(sAccelDrop))); // Save colors to INI for (int32_t i = 0; i < ColorCountE; i++) { uint8_t r; uint8_t g; uint8_t b; dvxGetColor(sAc, (ColorIdE)i, &r, &g, &b); char val[16]; snprintf(val, sizeof(val), "%d,%d,%d", r, g, b); prefsSetString("colors", dvxColorName((ColorIdE)i), val); } // Save desktop settings if (sWallpaperPath[0]) { prefsSetString("desktop", "wallpaper", sWallpaperPath); } else { prefsRemove("desktop", "wallpaper"); } // Save video settings prefsSetInt("video", "width", sAc->display.width); prefsSetInt("video", "height", sAc->display.height); prefsSetInt("video", "bpp", sAc->display.format.bitsPerPixel); prefsSave(); dvxDestroyWindow(sAc, sWin); sWin = NULL; } static void onCancel(WidgetT *w) { (void)w; restoreSnapshot(); dvxDestroyWindow(sAc, sWin); sWin = NULL; } static void onClose(WindowT *win) { restoreSnapshot(); dvxDestroyWindow(sAc, win); sWin = NULL; } // ============================================================ // saveSnapshot / restoreSnapshot // ============================================================ static void saveSnapshot(void) { memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb)); sSavedWheelDir = sAc->wheelDirection; sSavedDblClick = (int32_t)(sAc->dblClickTicks * 1000 / CLOCKS_PER_SEC); sSavedAccel = 0; sSavedVideoW = sAc->display.width; sSavedVideoH = sAc->display.height; sSavedVideoBpp = sAc->display.format.bitsPerPixel; sSavedHadWallpaper = (sAc->wallpaperBuf != NULL); const char *wp = prefsGetString("desktop", "wallpaper", NULL); if (wp) { strncpy(sWallpaperPath, wp, sizeof(sWallpaperPath) - 1); sWallpaperPath[sizeof(sWallpaperPath) - 1] = '\0'; } else { sWallpaperPath[0] = '\0'; } } static void restoreSnapshot(void) { // Restore colors memcpy(sAc->colorRgb, sSavedColorRgb, sizeof(sSavedColorRgb)); dvxApplyColorScheme(sAc); // Restore mouse const char *accelStr = prefsGetString("mouse", "acceleration", "medium"); int32_t accelVal = mapAccelName(accelStr); dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, accelVal); // Restore video mode if changed if (sAc->display.width != sSavedVideoW || sAc->display.height != sSavedVideoH || sAc->display.format.bitsPerPixel != sSavedVideoBpp) { dvxChangeVideoMode(sAc, sSavedVideoW, sSavedVideoH, sSavedVideoBpp); } // Restore wallpaper if (sSavedHadWallpaper && sWallpaperPath[0]) { dvxSetWallpaper(sAc, sWallpaperPath); } else { dvxSetWallpaper(sAc, NULL); } } // ============================================================ // scanThemes // ============================================================ static void scanThemes(void) { arrsetlen(sThemeEntries, 0); arrsetlen(sThemeLabels, 0); DIR *dir = opendir(THEME_DIR); if (!dir) { return; } struct dirent *ent; while ((ent = readdir(dir)) != NULL) { char *dot = strrchr(ent->d_name, '.'); if (!dot || strcasecmp(dot, THEME_EXT) != 0) { continue; } FileEntryT entry = {0}; int32_t nameLen = (int32_t)(dot - ent->d_name); if (nameLen >= (int32_t)sizeof(entry.name)) { nameLen = (int32_t)sizeof(entry.name) - 1; } memcpy(entry.name, ent->d_name, nameLen); entry.name[nameLen] = '\0'; snprintf(entry.path, sizeof(entry.path), "%s/%s", THEME_DIR, ent->d_name); arrput(sThemeEntries, entry); } closedir(dir); // Build label array now that sThemeEntries is stable for (int32_t i = 0; i < arrlen(sThemeEntries); i++) { arrput(sThemeLabels, sThemeEntries[i].name); } } // ============================================================ // scanWallpapers // ============================================================ static void scanWallpapers(void) { arrsetlen(sWpaperEntries, 0); arrsetlen(sWpaperLabels, 0); DIR *dir = opendir(WPAPER_DIR); if (!dir) { return; } struct dirent *ent; while ((ent = readdir(dir)) != NULL) { char *dot = strrchr(ent->d_name, '.'); if (!dot) { continue; } if (strcasecmp(dot, ".BMP") != 0 && strcasecmp(dot, ".JPG") != 0 && strcasecmp(dot, ".PNG") != 0) { continue; } FileEntryT entry = {0}; snprintf(entry.name, sizeof(entry.name), "%s", ent->d_name); snprintf(entry.path, sizeof(entry.path), "%s/%s", WPAPER_DIR, ent->d_name); arrput(sWpaperEntries, entry); } closedir(dir); // Build label array now that sWpaperEntries is stable for (int32_t i = 0; i < arrlen(sWpaperEntries); i++) { arrput(sWpaperLabels, sWpaperEntries[i].name); } } // ============================================================ // updateColorSliders // ============================================================ static void updateColorSliders(void) { int32_t sel = wgtListBoxGetSelected(sColorList); if (sel < 0 || sel >= ColorCountE) { return; } uint8_t r; uint8_t g; uint8_t b; dvxGetColor(sAc, (ColorIdE)sel, &r, &g, &b); wgtSliderSetValue(sRedSldr, r); wgtSliderSetValue(sGreenSldr, g); wgtSliderSetValue(sBlueSldr, b); static char rBuf[8]; static char gBuf[8]; static char bBuf[8]; snprintf(rBuf, sizeof(rBuf), "%d", r); snprintf(gBuf, sizeof(gBuf), "%d", g); snprintf(bBuf, sizeof(bBuf), "%d", b); wgtSetText(sRedLbl, rBuf); wgtSetText(sGreenLbl, gBuf); wgtSetText(sBlueLbl, bBuf); updateSwatch(); } // ============================================================ // updateDblClickLabel // ============================================================ static void updateDblClickLabel(void) { static char buf[32]; snprintf(buf, sizeof(buf), "%ld ms", (long)wgtSliderGetValue(sDblClickSldr)); wgtSetText(sDblClickLbl, buf); } // ============================================================ // updateSwatch // ============================================================ static void updateSwatch(void) { if (!sColorSwatch) { return; } uint8_t r = (uint8_t)wgtSliderGetValue(sRedSldr); uint8_t g = (uint8_t)wgtSliderGetValue(sGreenSldr); uint8_t b = (uint8_t)wgtSliderGetValue(sBlueSldr); uint32_t color = packColor(&sAc->display, r, g, b); wgtCanvasClear(sColorSwatch, color); } // ============================================================ // appMain // ============================================================ int32_t appMain(DxeAppContextT *ctx) { sCtx = ctx; sAc = ctx->shellCtx; int32_t winX = (sAc->display.width - CP_WIN_W) / 2; int32_t winY = (sAc->display.height - CP_WIN_H) / 2; sWin = dvxCreateWindow(sAc, "Control Panel", winX, winY, CP_WIN_W, CP_WIN_H, true); if (!sWin) { return -1; } sWin->onClose = onClose; saveSnapshot(); WidgetT *root = wgtInitWindow(sAc, sWin); // Tab control fills the window WidgetT *tabs = wgtTabControl(root); tabs->weight = 100; WidgetT *mouseTab = wgtTabPage(tabs, "Mouse"); WidgetT *colorsTab = wgtTabPage(tabs, "Colors"); WidgetT *desktopTab = wgtTabPage(tabs, "Desktop"); WidgetT *videoTab = wgtTabPage(tabs, "Video"); buildMouseTab(mouseTab); buildColorsTab(colorsTab); buildDesktopTab(desktopTab); buildVideoTab(videoTab); // OK / Cancel buttons at bottom WidgetT *btnRow = wgtHBox(root); btnRow->align = AlignEndE; btnRow->spacing = wgtPixels(8); WidgetT *okBtn = wgtButton(btnRow, "OK"); okBtn->onClick = onOk; okBtn->prefW = wgtPixels(80); WidgetT *cancelBtn = wgtButton(btnRow, "Cancel"); cancelBtn->onClick = onCancel; cancelBtn->prefW = wgtPixels(80); dvxFitWindow(sAc, sWin); return 0; }