1098 lines
31 KiB
C
1098 lines
31 KiB
C
// 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 <dirent.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <time.h>
|
|
|
|
// ============================================================
|
|
// 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 WallpaperModeE sSavedWpMode;
|
|
|
|
// 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 WidgetT *sWpModeDrop = 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 onWallpaperMode(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] = dvxColorLabel((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 in a non-weighted inner box so they stay
|
|
// at natural size. The outer box absorbs extra vertical space.
|
|
WidgetT *rightVbox = wgtVBox(hbox);
|
|
rightVbox->weight = 50;
|
|
|
|
WidgetT *sliderBox = wgtVBox(rightVbox);
|
|
sliderBox->spacing = wgtPixels(4);
|
|
|
|
wgtLabel(sliderBox, "Red:");
|
|
sRedSldr = wgtSlider(sliderBox, 0, 255);
|
|
sRedSldr->onChange = onColorSlider;
|
|
sRedLbl = wgtLabel(sliderBox, "0");
|
|
wgtLabelSetAlign(sRedLbl, AlignEndE);
|
|
|
|
wgtLabel(sliderBox, "Green:");
|
|
sGreenSldr = wgtSlider(sliderBox, 0, 255);
|
|
sGreenSldr->onChange = onColorSlider;
|
|
sGreenLbl = wgtLabel(sliderBox, "0");
|
|
wgtLabelSetAlign(sGreenLbl, AlignEndE);
|
|
|
|
wgtLabel(sliderBox, "Blue:");
|
|
sBlueSldr = wgtSlider(sliderBox, 0, 255);
|
|
sBlueSldr->onChange = onColorSlider;
|
|
sBlueLbl = wgtLabel(sliderBox, "0");
|
|
wgtLabelSetAlign(sBlueLbl, AlignEndE);
|
|
|
|
wgtLabel(sliderBox, "Preview:");
|
|
sColorSwatch = wgtCanvas(sliderBox, 64, 24);
|
|
|
|
// Absorb remaining vertical space below the sliders
|
|
wgtSpacer(rightVbox)->weight = 100;
|
|
|
|
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);
|
|
|
|
wgtSpacer(btnRow)->weight = 100;
|
|
wgtLabel(btnRow, "Mode:");
|
|
|
|
static const char *modeItems[] = {"Stretch", "Tile", "Center"};
|
|
sWpModeDrop = wgtDropdown(btnRow);
|
|
sWpModeDrop->onChange = onWallpaperMode;
|
|
wgtDropdownSetItems(sWpModeDrop, modeItems, 3);
|
|
wgtDropdownSetSelected(sWpModeDrop, (int32_t)sAc->wallpaperMode);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// 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)");
|
|
}
|
|
|
|
|
|
static void onWallpaperMode(WidgetT *w) {
|
|
int32_t sel = wgtDropdownGetSelected(w);
|
|
|
|
if (sel >= 0 && sel <= 2) {
|
|
dvxSetWallpaperMode(sAc, (WallpaperModeE)sel);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// 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");
|
|
}
|
|
|
|
const char *modeStr = "stretch";
|
|
|
|
if (sAc->wallpaperMode == WallpaperTileE) {
|
|
modeStr = "tile";
|
|
} else if (sAc->wallpaperMode == WallpaperCenterE) {
|
|
modeStr = "center";
|
|
}
|
|
|
|
prefsSetString("desktop", "mode", modeStr);
|
|
|
|
// Save video settings
|
|
prefsSetInt("video", "width", sAc->display.width);
|
|
prefsSetInt("video", "height", sAc->display.height);
|
|
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 char sSavedWallpaperPath[260];
|
|
|
|
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;
|
|
|
|
// Save the active wallpaper path and mode from the context
|
|
snprintf(sSavedWallpaperPath, sizeof(sSavedWallpaperPath), "%s", sAc->wallpaperPath);
|
|
snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sAc->wallpaperPath);
|
|
sSavedWpMode = sAc->wallpaperMode;
|
|
}
|
|
|
|
|
|
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 only if path or mode changed
|
|
if (strcmp(sAc->wallpaperPath, sSavedWallpaperPath) != 0 ||
|
|
sAc->wallpaperMode != sSavedWpMode) {
|
|
sAc->wallpaperMode = sSavedWpMode;
|
|
|
|
if (sSavedWallpaperPath[0]) {
|
|
dvxSetWallpaper(sAc, sSavedWallpaperPath);
|
|
} 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;
|
|
}
|