806 lines
23 KiB
C
806 lines
23 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 <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 MAX_VIDEO_MODES 64
|
|
#define MAX_THEME_FILES 32
|
|
#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
|
|
};
|
|
|
|
// ============================================================
|
|
// Video mode entry
|
|
// ============================================================
|
|
|
|
typedef struct {
|
|
int32_t w;
|
|
int32_t h;
|
|
int32_t bpp;
|
|
char label[32];
|
|
} VideoModeEntryT;
|
|
|
|
// ============================================================
|
|
// 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;
|
|
|
|
// 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;
|
|
|
|
// Desktop tab widgets
|
|
static WidgetT *sWallpaperLbl = NULL;
|
|
static char sWallpaperPath[260];
|
|
|
|
// Video tab
|
|
static VideoModeEntryT sVideoModes[MAX_VIDEO_MODES];
|
|
static const char *sVideoLabels[MAX_VIDEO_MODES];
|
|
static int32_t sVideoModeCount = 0;
|
|
static WidgetT *sVideoList = NULL;
|
|
|
|
// Theme list
|
|
static char sThemeNames[MAX_THEME_FILES][64];
|
|
static const char *sThemeLabels[MAX_THEME_FILES];
|
|
static char sThemePaths[MAX_THEME_FILES][260];
|
|
static int32_t sThemeCount = 0;
|
|
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 void enumVideoModesCb(int32_t w, int32_t h, int32_t bpp, void *userData);
|
|
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 onResetColors(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 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 scanThemes(void);
|
|
static void updateColorSliders(void);
|
|
static void updateDblClickLabel(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, sThemeCount);
|
|
|
|
WidgetT *loadBtn = wgtButton(themeRow, "Apply");
|
|
loadBtn->onClick = onApplyTheme;
|
|
loadBtn->prefW = wgtPixels(60);
|
|
|
|
WidgetT *saveBtn = wgtButton(themeRow, "Save...");
|
|
saveBtn->onClick = onSaveTheme;
|
|
saveBtn->prefW = wgtPixels(60);
|
|
|
|
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");
|
|
|
|
updateColorSliders();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// buildDesktopTab
|
|
// ============================================================
|
|
|
|
static void buildDesktopTab(WidgetT *page) {
|
|
wgtLabel(page, "Wallpaper:");
|
|
|
|
sWallpaperLbl = wgtLabel(page, sWallpaperPath[0] ? sWallpaperPath : "(none)");
|
|
|
|
WidgetT *btnRow = wgtHBox(page);
|
|
btnRow->spacing = wgtPixels(8);
|
|
|
|
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) {
|
|
// 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);
|
|
|
|
// 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(page, "");
|
|
updateDblClickLabel();
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// buildVideoTab
|
|
// ============================================================
|
|
|
|
static void buildVideoTab(WidgetT *page) {
|
|
wgtLabel(page, "Available Video Modes:");
|
|
|
|
sVideoModeCount = 0;
|
|
platformVideoEnumModes(enumVideoModesCb, NULL);
|
|
|
|
sVideoList = wgtListBox(page);
|
|
sVideoList->weight = 100;
|
|
wgtListBoxSetItems(sVideoList, sVideoLabels, sVideoModeCount);
|
|
|
|
// Select the current mode
|
|
for (int32_t i = 0; i < sVideoModeCount; 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);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// enumVideoModesCb
|
|
// ============================================================
|
|
|
|
static void enumVideoModesCb(int32_t w, int32_t h, int32_t bpp, void *userData) {
|
|
(void)userData;
|
|
|
|
if (sVideoModeCount >= MAX_VIDEO_MODES) {
|
|
return;
|
|
}
|
|
|
|
VideoModeEntryT *m = &sVideoModes[sVideoModeCount];
|
|
m->w = w;
|
|
m->h = h;
|
|
m->bpp = bpp;
|
|
snprintf(m->label, sizeof(m->label), "%ldx%ld %ldbpp", (long)w, (long)h, (long)bpp);
|
|
sVideoLabels[sVideoModeCount] = m->label;
|
|
sVideoModeCount++;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// 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 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);
|
|
}
|
|
|
|
|
|
static void onApplyTheme(WidgetT *w) {
|
|
(void)w;
|
|
|
|
int32_t sel = wgtDropdownGetSelected(sThemeList);
|
|
|
|
if (sel < 0 || sel >= sThemeCount) {
|
|
return;
|
|
}
|
|
|
|
dvxLoadTheme(sAc, sThemePaths[sel]);
|
|
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, sThemeCount);
|
|
}
|
|
}
|
|
|
|
|
|
static void onResetColors(WidgetT *w) {
|
|
(void)w;
|
|
dvxResetColorScheme(sAc);
|
|
updateColorSliders();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Callbacks — Desktop tab
|
|
// ============================================================
|
|
|
|
static void onChooseWallpaper(WidgetT *w) {
|
|
(void)w;
|
|
|
|
FileFilterT filters[] = {
|
|
{ "Images (*.bmp;*.png;*.jpg)", "*.bmp" },
|
|
{ "All Files (*.*)", "*.*" }
|
|
};
|
|
char path[260];
|
|
|
|
if (dvxFileDialog(sAc, "Choose Wallpaper", FD_OPEN, NULL, 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 void onVideoApply(WidgetT *w) {
|
|
(void)w;
|
|
|
|
int32_t sel = wgtListBoxGetSelected(sVideoList);
|
|
|
|
if (sel < 0 || sel >= sVideoModeCount) {
|
|
return;
|
|
}
|
|
|
|
VideoModeEntryT *m = &sVideoModes[sel];
|
|
|
|
if (m->w == sAc->display.width &&
|
|
m->h == sAc->display.height &&
|
|
m->bpp == sAc->display.format.bitsPerPixel) {
|
|
return;
|
|
}
|
|
|
|
if (dvxChangeVideoMode(sAc, m->w, m->h, m->bpp) != 0) {
|
|
dvxMessageBox(sAc, "Error", "Failed to change video mode.", MB_OK | MB_ICONERROR);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// 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) {
|
|
sThemeCount = 0;
|
|
|
|
// Scan THEME_DIR for .THM files
|
|
DIR *dir = opendir(THEME_DIR);
|
|
|
|
if (!dir) {
|
|
return;
|
|
}
|
|
|
|
struct dirent *ent;
|
|
|
|
while ((ent = readdir(dir)) != NULL && sThemeCount < MAX_THEME_FILES) {
|
|
char *dot = strrchr(ent->d_name, '.');
|
|
|
|
if (!dot) {
|
|
continue;
|
|
}
|
|
|
|
// Case-insensitive extension check
|
|
if (strcasecmp(dot, THEME_EXT) != 0) {
|
|
continue;
|
|
}
|
|
|
|
// Use filename without extension as display name
|
|
int32_t nameLen = (int32_t)(dot - ent->d_name);
|
|
|
|
if (nameLen >= 64) {
|
|
nameLen = 63;
|
|
}
|
|
|
|
memcpy(sThemeNames[sThemeCount], ent->d_name, nameLen);
|
|
sThemeNames[sThemeCount][nameLen] = '\0';
|
|
sThemeLabels[sThemeCount] = sThemeNames[sThemeCount];
|
|
|
|
snprintf(sThemePaths[sThemeCount], sizeof(sThemePaths[sThemeCount]), "%s/%s", THEME_DIR, ent->d_name);
|
|
sThemeCount++;
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// 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);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// updateDblClickLabel
|
|
// ============================================================
|
|
|
|
static void updateDblClickLabel(void) {
|
|
static char buf[32];
|
|
snprintf(buf, sizeof(buf), "%ld ms", (long)wgtSliderGetValue(sDblClickSldr));
|
|
wgtSetText(sDblClickLbl, buf);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// 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;
|
|
}
|