More IDE changes and a few core changes to help support easier app development.

This commit is contained in:
Scott Duensing 2026-03-29 22:06:01 -05:00
parent d094205ed0
commit 344ab4794d
29 changed files with 731 additions and 116 deletions

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ lib/
.gitattributes~ .gitattributes~
*.SWP *.SWP
.claude/ .claude/
capture/

View file

@ -77,7 +77,7 @@ AppDescriptorT appDescriptor = {
typedef struct { typedef struct {
char name[64]; char name[64];
char path[280]; char path[DVX_MAX_PATH];
} FileEntryT; } FileEntryT;
// ============================================================ // ============================================================
@ -119,7 +119,7 @@ static WidgetT *sColorSwatch = NULL;
static WidgetT *sWallpaperLbl = NULL; static WidgetT *sWallpaperLbl = NULL;
static WidgetT *sWpaperList = NULL; static WidgetT *sWpaperList = NULL;
static WidgetT *sWpModeDrop = NULL; static WidgetT *sWpModeDrop = NULL;
static char sWallpaperPath[280]; static char sWallpaperPath[DVX_MAX_PATH];
static FileEntryT *sWpaperEntries = NULL; // stb_ds dynamic array static FileEntryT *sWpaperEntries = NULL; // stb_ds dynamic array
static const char **sWpaperLabels = NULL; // stb_ds dynamic array static const char **sWpaperLabels = NULL; // stb_ds dynamic array
@ -575,7 +575,7 @@ static void onBrowseTheme(WidgetT *w) {
{ "Theme Files (*.thm)", "*.thm" }, { "Theme Files (*.thm)", "*.thm" },
{ "All Files (*.*)", "*.*" } { "All Files (*.*)", "*.*" }
}; };
char path[260]; char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Load Theme", FD_OPEN, THEME_DIR, filters, 2, path, sizeof(path))) { if (dvxFileDialog(sAc, "Load Theme", FD_OPEN, THEME_DIR, filters, 2, path, sizeof(path))) {
dvxLoadTheme(sAc, path); dvxLoadTheme(sAc, path);
@ -591,7 +591,7 @@ static void onSaveTheme(WidgetT *w) {
{ "Theme Files (*.thm)", "*.thm" }, { "Theme Files (*.thm)", "*.thm" },
{ "All Files (*.*)", "*.*" } { "All Files (*.*)", "*.*" }
}; };
char path[260]; char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Save Theme", FD_SAVE, THEME_DIR, filters, 2, path, sizeof(path))) { if (dvxFileDialog(sAc, "Save Theme", FD_SAVE, THEME_DIR, filters, 2, path, sizeof(path))) {
dvxSaveTheme(sAc, path); dvxSaveTheme(sAc, path);
@ -637,7 +637,7 @@ static void onChooseWallpaper(WidgetT *w) {
{ "Images (*.bmp;*.jpg;*.png)", "*.bmp;*.jpg;*.png" }, { "Images (*.bmp;*.jpg;*.png)", "*.bmp;*.jpg;*.png" },
{ "All Files (*.*)", "*.*" } { "All Files (*.*)", "*.*" }
}; };
char path[260]; char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Choose Wallpaper", FD_OPEN, "CONFIG/WPAPER", filters, 2, path, sizeof(path))) { if (dvxFileDialog(sAc, "Choose Wallpaper", FD_OPEN, "CONFIG/WPAPER", filters, 2, path, sizeof(path))) {
if (dvxSetWallpaper(sAc, path)) { if (dvxSetWallpaper(sAc, path)) {
@ -867,7 +867,7 @@ static void onClose(WindowT *win) {
// saveSnapshot / restoreSnapshot // saveSnapshot / restoreSnapshot
// ============================================================ // ============================================================
static char sSavedWallpaperPath[260]; static char sSavedWallpaperPath[DVX_MAX_PATH];
static void saveSnapshot(void) { static void saveSnapshot(void) {
memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb)); memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb));
@ -948,7 +948,7 @@ static void scanThemes(void) {
memcpy(entry.name, ent->d_name, nameLen); memcpy(entry.name, ent->d_name, nameLen);
entry.name[nameLen] = '\0'; entry.name[nameLen] = '\0';
snprintf(entry.path, sizeof(entry.path), "%s/%s", THEME_DIR, ent->d_name); snprintf(entry.path, sizeof(entry.path), "%s/%.240s", THEME_DIR, ent->d_name);
arrput(sThemeEntries, entry); arrput(sThemeEntries, entry);
} }
@ -992,7 +992,7 @@ static void scanWallpapers(void) {
FileEntryT entry = {0}; FileEntryT entry = {0};
snprintf(entry.name, sizeof(entry.name), "%.63s", ent->d_name); snprintf(entry.name, sizeof(entry.name), "%.63s", ent->d_name);
snprintf(entry.path, sizeof(entry.path), "%s/%s", WPAPER_DIR, ent->d_name); snprintf(entry.path, sizeof(entry.path), "%s/%.240s", WPAPER_DIR, ent->d_name);
arrput(sWpaperEntries, entry); arrput(sWpaperEntries, entry);
} }

View file

@ -330,6 +330,10 @@ BasTokenTypeE basLexerNext(BasLexerT *lex) {
lex->token.type = TOK_HASH; lex->token.type = TOK_HASH;
break; break;
case '?':
lex->token.type = TOK_PRINT;
break;
case '=': case '=':
lex->token.type = TOK_EQ; lex->token.type = TOK_EQ;
break; break;

View file

@ -3,3 +3,10 @@ icon32 icon icon32.bmp
name text "DVX BASIC" name text "DVX BASIC"
author text "DVX Project" author text "DVX Project"
description text "BASIC language IDE and runtime" description text "BASIC language IDE and runtime"
# Toolbar icons (16x16)
tb_open icon tb_open.bmp
tb_save icon tb_save.bmp
tb_run icon tb_run.bmp
tb_stop icon tb_stop.bmp
tb_code icon tb_code.bmp
tb_design icon tb_design.bmp

View file

@ -11,11 +11,13 @@
#include "dvxCursor.h" #include "dvxCursor.h"
#include "dvxPlatform.h" #include "dvxPlatform.h"
#include "dvxDialog.h" #include "dvxDialog.h"
#include "dvxPrefs.h"
#include "dvxWidget.h" #include "dvxWidget.h"
#include "dvxWidgetPlugin.h" #include "dvxWidgetPlugin.h"
#include "dvxWm.h" #include "dvxWm.h"
#include "shellApp.h" #include "shellApp.h"
#include "widgetBox.h" #include "widgetBox.h"
#include "widgetImageButton.h"
#include "widgetLabel.h" #include "widgetLabel.h"
#include "widgetTextInput.h" #include "widgetTextInput.h"
#include "widgetDropdown.h" #include "widgetDropdown.h"
@ -66,6 +68,12 @@
#define CMD_WIN_TOOLBOX 112 #define CMD_WIN_TOOLBOX 112
#define CMD_WIN_PROPS 113 #define CMD_WIN_PROPS 113
#define CMD_DELETE 114 #define CMD_DELETE 114
#define CMD_CUT 115
#define CMD_COPY 116
#define CMD_PASTE 117
#define CMD_SELECT_ALL 118
#define CMD_VIEW_TOOLBAR 119
#define CMD_VIEW_STATUS 120
#define IDE_MAX_IMM 1024 #define IDE_MAX_IMM 1024
#define IDE_DESIGN_W 400 #define IDE_DESIGN_W 400
#define IDE_DESIGN_H 300 #define IDE_DESIGN_H 300
@ -83,6 +91,8 @@ static void loadFilePath(const char *path);
static void saveFile(void); static void saveFile(void);
static void onTbSave(WidgetT *w); static void onTbSave(WidgetT *w);
static void onClose(WindowT *win); static void onClose(WindowT *win);
static void onContentFocus(WindowT *win);
static WindowT *getLastFocusWin(void);
static void onMenu(WindowT *win, int32_t menuId); static void onMenu(WindowT *win, int32_t menuId);
static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx); static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx);
static void evaluateImmediate(const char *expr); static void evaluateImmediate(const char *expr);
@ -128,6 +138,8 @@ static WidgetT *sOutput = NULL;
static WidgetT *sImmediate = NULL; static WidgetT *sImmediate = NULL;
static WidgetT *sObjDropdown = NULL; static WidgetT *sObjDropdown = NULL;
static WidgetT *sEvtDropdown = NULL; static WidgetT *sEvtDropdown = NULL;
static WidgetT *sToolbar = NULL;
static WidgetT *sStatusBar = NULL;
static WidgetT *sStatus = NULL; static WidgetT *sStatus = NULL;
static BasVmT *sVm = NULL; // VM instance (non-NULL while running) static BasVmT *sVm = NULL; // VM instance (non-NULL while running)
static BasModuleT *sCachedModule = NULL; // Last compiled module (for Ctrl+F5) static BasModuleT *sCachedModule = NULL; // Last compiled module (for Ctrl+F5)
@ -135,11 +147,12 @@ static DsgnStateT sDesigner;
static WindowT *sFormWin = NULL; // Form designer window (separate) static WindowT *sFormWin = NULL; // Form designer window (separate)
static WindowT *sToolboxWin = NULL; static WindowT *sToolboxWin = NULL;
static WindowT *sPropsWin = NULL; static WindowT *sPropsWin = NULL;
static WindowT *sLastFocusWin = NULL; // last focused non-toolbar window
static char sSourceBuf[IDE_MAX_SOURCE]; static char sSourceBuf[IDE_MAX_SOURCE];
static char sOutputBuf[IDE_MAX_OUTPUT]; static char sOutputBuf[IDE_MAX_OUTPUT];
static int32_t sOutputLen = 0; static int32_t sOutputLen = 0;
static char sFilePath[260]; static char sFilePath[DVX_MAX_PATH];
// Procedure table for Object/Event dropdowns // Procedure table for Object/Event dropdowns
typedef struct { typedef struct {
@ -175,6 +188,27 @@ int32_t appMain(DxeAppContextT *ctx) {
basStringSystemInit(); basStringSystemInit();
buildWindow(); buildWindow();
// Load persisted settings
char prefsPath[DVX_MAX_PATH];
shellConfigPath(sCtx, "dvxbasic.ini", prefsPath, sizeof(prefsPath));
prefsLoad(prefsPath);
if (sToolbar && sWin && sWin->menuBar) {
bool showTb = prefsGetBool("view", "toolbar", true);
sToolbar->visible = showTb;
wmMenuItemSetChecked(sWin->menuBar, CMD_VIEW_TOOLBAR, showTb);
}
if (sStatusBar && sWin && sWin->menuBar) {
bool showSb = prefsGetBool("view", "statusbar", true);
sStatusBar->visible = showSb;
wmMenuItemSetChecked(sWin->menuBar, CMD_VIEW_STATUS, showSb);
}
if (sWin) {
dvxFitWindowH(sAc, sWin);
}
sFilePath[0] = '\0'; sFilePath[0] = '\0';
sSourceBuf[0] = '\0'; sSourceBuf[0] = '\0';
sOutputBuf[0] = '\0'; sOutputBuf[0] = '\0';
@ -187,6 +221,25 @@ int32_t appMain(DxeAppContextT *ctx) {
return 0; return 0;
} }
// ============================================================
// loadTbIcon -- load a toolbar icon from the app's resources
// ============================================================
static WidgetT *loadTbIcon(WidgetT *parent, const char *resName, const char *fallbackText) {
int32_t iconW = 0;
int32_t iconH = 0;
int32_t iconPitch = 0;
uint8_t *data = dvxResLoadIcon(sAc, sCtx->appPath, resName, &iconW, &iconH, &iconPitch);
if (data) {
return wgtImageButton(parent, data, iconW, iconH, iconPitch);
}
// Fallback to text button if icon not found
return wgtButton(parent, fallbackText);
}
// ============================================================ // ============================================================
// buildWindow // buildWindow
// ============================================================ // ============================================================
@ -211,6 +264,12 @@ static void buildWindow(void) {
wmAddMenuItem(fileMenu, "E&xit", CMD_EXIT); wmAddMenuItem(fileMenu, "E&xit", CMD_EXIT);
MenuT *editMenu = wmAddMenu(menuBar, "&Edit"); MenuT *editMenu = wmAddMenu(menuBar, "&Edit");
wmAddMenuItem(editMenu, "Cu&t\tCtrl+X", CMD_CUT);
wmAddMenuItem(editMenu, "&Copy\tCtrl+C", CMD_COPY);
wmAddMenuItem(editMenu, "&Paste\tCtrl+V", CMD_PASTE);
wmAddMenuSeparator(editMenu);
wmAddMenuItem(editMenu, "Select &All\tCtrl+A", CMD_SELECT_ALL);
wmAddMenuSeparator(editMenu);
wmAddMenuItem(editMenu, "&Delete\tDel", CMD_DELETE); wmAddMenuItem(editMenu, "&Delete\tDel", CMD_DELETE);
MenuT *runMenu = wmAddMenu(menuBar, "&Run"); MenuT *runMenu = wmAddMenu(menuBar, "&Run");
@ -223,6 +282,9 @@ static void buildWindow(void) {
MenuT *viewMenu = wmAddMenu(menuBar, "&View"); MenuT *viewMenu = wmAddMenu(menuBar, "&View");
wmAddMenuItem(viewMenu, "&Code\tF7", CMD_VIEW_CODE); wmAddMenuItem(viewMenu, "&Code\tF7", CMD_VIEW_CODE);
wmAddMenuItem(viewMenu, "&Object\tShift+F7", CMD_VIEW_DESIGN); wmAddMenuItem(viewMenu, "&Object\tShift+F7", CMD_VIEW_DESIGN);
wmAddMenuSeparator(viewMenu);
wmAddMenuCheckItem(viewMenu, "&Toolbar", CMD_VIEW_TOOLBAR, true);
wmAddMenuCheckItem(viewMenu, "&Status Bar", CMD_VIEW_STATUS, true);
MenuT *winMenu = wmAddMenu(menuBar, "&Window"); MenuT *winMenu = wmAddMenu(menuBar, "&Window");
wmAddMenuItem(winMenu, "&Code Editor", CMD_WIN_CODE); wmAddMenuItem(winMenu, "&Code Editor", CMD_WIN_CODE);
@ -243,27 +305,35 @@ static void buildWindow(void) {
sWin->accelTable = accel; sWin->accelTable = accel;
WidgetT *tbRoot = wgtInitWindow(sAc, sWin); WidgetT *tbRoot = wgtInitWindow(sAc, sWin);
WidgetT *tb = wgtToolbar(tbRoot); sToolbar = wgtToolbar(tbRoot);
WidgetT *tb = sToolbar;
WidgetT *tbOpen = wgtButton(tb, "Open"); WidgetT *tbOpen = loadTbIcon(tb, "tb_open", "Open");
tbOpen->onClick = onTbOpen; tbOpen->onClick = onTbOpen;
wgtSetTooltip(tbOpen, "Open (Ctrl+O)");
WidgetT *tbSave = wgtButton(tb, "Save"); WidgetT *tbSave = loadTbIcon(tb, "tb_save", "Save");
tbSave->onClick = onTbSave; tbSave->onClick = onTbSave;
wgtSetTooltip(tbSave, "Save (Ctrl+S)");
WidgetT *tbRun = wgtButton(tb, "Run"); WidgetT *tbRun = loadTbIcon(tb, "tb_run", "Run");
tbRun->onClick = onTbRun; tbRun->onClick = onTbRun;
wgtSetTooltip(tbRun, "Run (F5)");
WidgetT *tbStop = wgtButton(tb, "Stop"); WidgetT *tbStop = loadTbIcon(tb, "tb_stop", "Stop");
tbStop->onClick = onTbStop; tbStop->onClick = onTbStop;
wgtSetTooltip(tbStop, "Stop (Esc)");
WidgetT *tbCode = wgtButton(tb, "Code"); WidgetT *tbCode = loadTbIcon(tb, "tb_code", "Code");
tbCode->onClick = onTbCode; tbCode->onClick = onTbCode;
wgtSetTooltip(tbCode, "Code View (F7)");
WidgetT *tbDesign = wgtButton(tb, "Design"); WidgetT *tbDesign = loadTbIcon(tb, "tb_design", "Design");
tbDesign->onClick = onTbDesign; tbDesign->onClick = onTbDesign;
wgtSetTooltip(tbDesign, "Design View (Shift+F7)");
WidgetT *statusBar = wgtStatusBar(tbRoot); sStatusBar = wgtStatusBar(tbRoot);
WidgetT *statusBar = sStatusBar;
sStatus = wgtLabel(statusBar, ""); sStatus = wgtLabel(statusBar, "");
sStatus->weight = 100; sStatus->weight = 100;
@ -792,7 +862,7 @@ static void loadFile(void) {
{ "All Files (*.*)", "*.*" } { "All Files (*.*)", "*.*" }
}; };
char path[260]; char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Open BASIC File", FD_OPEN, NULL, filters, 3, path, sizeof(path))) { if (dvxFileDialog(sAc, "Open BASIC File", FD_OPEN, NULL, filters, 3, path, sizeof(path))) {
loadFilePath(path); loadFilePath(path);
@ -833,7 +903,7 @@ static void saveFile(void) {
// Save the .frm if the designer has form data // Save the .frm if the designer has form data
if (sDesigner.form && sDesigner.form->dirty) { if (sDesigner.form && sDesigner.form->dirty) {
char frmPath[260]; char frmPath[DVX_MAX_PATH];
snprintf(frmPath, sizeof(frmPath), "%s", sFilePath); snprintf(frmPath, sizeof(frmPath), "%s", sFilePath);
char *dot = strrchr(frmPath, '.'); char *dot = strrchr(frmPath, '.');
@ -875,7 +945,7 @@ static void loadFrmFiles(BasFormRtT *rt) {
} }
// Build .frm path from .bas path // Build .frm path from .bas path
char frmPath[260]; char frmPath[DVX_MAX_PATH];
snprintf(frmPath, sizeof(frmPath), "%s", sFilePath); snprintf(frmPath, sizeof(frmPath), "%s", sFilePath);
// Find the extension and replace with .frm // Find the extension and replace with .frm
@ -928,6 +998,26 @@ static void loadFrmFiles(BasFormRtT *rt) {
// ============================================================
// onContentFocus -- track last focused content window for clipboard
// ============================================================
static void onContentFocus(WindowT *win) {
sLastFocusWin = win;
}
static WindowT *getLastFocusWin(void) {
if (sLastFocusWin == sCodeWin ||
sLastFocusWin == sOutWin ||
sLastFocusWin == sImmWin) {
return sLastFocusWin;
}
sLastFocusWin = NULL;
return NULL;
}
// ============================================================ // ============================================================
// onClose // onClose
// ============================================================ // ============================================================
@ -954,6 +1044,7 @@ static void onClose(WindowT *win) {
sCodeWin = NULL; sCodeWin = NULL;
sOutWin = NULL; sOutWin = NULL;
sImmWin = NULL; sImmWin = NULL;
sLastFocusWin = NULL;
sEditor = NULL; sEditor = NULL;
sOutput = NULL; sOutput = NULL;
sImmediate = NULL; sImmediate = NULL;
@ -1045,6 +1136,23 @@ static void onMenu(WindowT *win, int32_t menuId) {
} }
break; break;
case CMD_CUT:
case CMD_COPY:
case CMD_PASTE:
case CMD_SELECT_ALL: {
// Send the corresponding Ctrl+key to the last focused content window
static const int32_t keys[] = { 24, 3, 22, 1 }; // Ctrl+X, C, V, A
int32_t key = keys[menuId - CMD_CUT];
WindowT *target = getLastFocusWin();
if (target && target->onKey) {
target->onKey(target, key, ACCEL_CTRL);
}
break;
}
case CMD_DELETE: case CMD_DELETE:
if (sFormWin && sDesigner.selectedIdx >= 0) { if (sFormWin && sDesigner.selectedIdx >= 0) {
int32_t prevCount = (int32_t)arrlen(sDesigner.form->controls); int32_t prevCount = (int32_t)arrlen(sDesigner.form->controls);
@ -1059,6 +1167,26 @@ static void onMenu(WindowT *win, int32_t menuId) {
} }
break; break;
case CMD_VIEW_TOOLBAR:
if (sToolbar && sWin->menuBar) {
bool show = wmMenuItemIsChecked(sWin->menuBar, CMD_VIEW_TOOLBAR);
sToolbar->visible = show;
dvxFitWindowH(sAc, sWin);
prefsSetBool("view", "toolbar", show);
prefsSave();
}
break;
case CMD_VIEW_STATUS:
if (sStatusBar && sWin->menuBar) {
bool show = wmMenuItemIsChecked(sWin->menuBar, CMD_VIEW_STATUS);
sStatusBar->visible = show;
dvxFitWindowH(sAc, sWin);
prefsSetBool("view", "statusbar", show);
prefsSave();
}
break;
case CMD_EXIT: case CMD_EXIT:
if (sWin) { if (sWin) {
onClose(sWin); onClose(sWin);
@ -1446,7 +1574,7 @@ static void switchToDesign(void) {
// Load .frm if we don't have a form yet // Load .frm if we don't have a form yet
if (!sDesigner.form) { if (!sDesigner.form) {
if (sFilePath[0]) { if (sFilePath[0]) {
char frmPath[260]; char frmPath[DVX_MAX_PATH];
snprintf(frmPath, sizeof(frmPath), "%s", sFilePath); snprintf(frmPath, sizeof(frmPath), "%s", sFilePath);
char *dot = strrchr(frmPath, '.'); char *dot = strrchr(frmPath, '.');
@ -1575,7 +1703,9 @@ static void showCodeWindow(void) {
if (sCodeWin) { if (sCodeWin) {
sCodeWin->onMenu = onMenu; sCodeWin->onMenu = onMenu;
sCodeWin->onFocus = onContentFocus;
sCodeWin->accelTable = sWin ? sWin->accelTable : NULL; sCodeWin->accelTable = sWin ? sWin->accelTable : NULL;
sLastFocusWin = sCodeWin;
WidgetT *codeRoot = wgtInitWindow(sAc, sCodeWin); WidgetT *codeRoot = wgtInitWindow(sAc, sCodeWin);
@ -1622,6 +1752,9 @@ static void showOutputWindow(void) {
sOutWin = dvxCreateWindow(sAc, "Output", 0, outY, sAc->display.width / 2, outH, true); sOutWin = dvxCreateWindow(sAc, "Output", 0, outY, sAc->display.width / 2, outH, true);
if (sOutWin) { if (sOutWin) {
sOutWin->onFocus = onContentFocus;
sLastFocusWin = sOutWin;
WidgetT *outRoot = wgtInitWindow(sAc, sOutWin); WidgetT *outRoot = wgtInitWindow(sAc, sOutWin);
sOutput = wgtTextArea(outRoot, IDE_MAX_OUTPUT); sOutput = wgtTextArea(outRoot, IDE_MAX_OUTPUT);
sOutput->weight = 100; sOutput->weight = 100;
@ -1630,7 +1763,6 @@ static void showOutputWindow(void) {
if (sOutputLen > 0) { if (sOutputLen > 0) {
wgtSetText(sOutput, sOutputBuf); wgtSetText(sOutput, sOutputBuf);
} }
} }
} }
@ -1650,6 +1782,9 @@ static void showImmediateWindow(void) {
sImmWin = dvxCreateWindow(sAc, "Immediate", sAc->display.width / 2, outY, sAc->display.width / 2, outH, true); sImmWin = dvxCreateWindow(sAc, "Immediate", sAc->display.width / 2, outY, sAc->display.width / 2, outH, true);
if (sImmWin) { if (sImmWin) {
sImmWin->onFocus = onContentFocus;
sLastFocusWin = sImmWin;
WidgetT *immRoot = wgtInitWindow(sAc, sImmWin); WidgetT *immRoot = wgtInitWindow(sAc, sImmWin);
if (immRoot) { if (immRoot) {

View file

@ -118,10 +118,7 @@ WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) {
int32_t pathIdx = wgtIfaceGetPathIndex(wgtName); int32_t pathIdx = wgtIfaceGetPathIndex(wgtName);
if (wgtPath) { if (wgtPath) {
DvxResHandleT *res = dvxResOpen(wgtPath); // Build suffixed resource names: "icon16", "icon16-2", etc.
if (res) {
// Build suffixed resource names: "icon16", "icon16-2", "icon16-3", etc.
char iconResName[32]; char iconResName[32];
char nameResName[32]; char nameResName[32];
@ -133,24 +130,8 @@ WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) {
snprintf(nameResName, sizeof(nameResName), "name-%d", (int)pathIdx); snprintf(nameResName, sizeof(nameResName), "name-%d", (int)pathIdx);
} }
uint32_t iconSize = 0; iconData = dvxResLoadIcon(ctx, wgtPath, iconResName, &iconW, &iconH, &iconPitch);
void *iconBuf = dvxResRead(res, iconResName, &iconSize); dvxResLoadText(wgtPath, nameResName, entry.tooltip, sizeof(entry.tooltip));
if (iconBuf) {
iconData = dvxLoadImageFromMemory(ctx, (const uint8_t *)iconBuf, (int32_t)iconSize, &iconW, &iconH, &iconPitch);
free(iconBuf);
}
uint32_t nameSize = 0;
void *nameBuf = dvxResRead(res, nameResName, &nameSize);
if (nameBuf) {
snprintf(entry.tooltip, sizeof(entry.tooltip), "%s", (const char *)nameBuf);
free(nameBuf);
}
dvxResClose(res);
}
} }
if (!entry.tooltip[0]) { if (!entry.tooltip[0]) {

BIN
apps/dvxbasic/tb_code.bmp (Stored with Git LFS) Normal file

Binary file not shown.

BIN
apps/dvxbasic/tb_design.bmp (Stored with Git LFS) Normal file

Binary file not shown.

BIN
apps/dvxbasic/tb_open.bmp (Stored with Git LFS) Normal file

Binary file not shown.

BIN
apps/dvxbasic/tb_run.bmp (Stored with Git LFS) Normal file

Binary file not shown.

BIN
apps/dvxbasic/tb_save.bmp (Stored with Git LFS) Normal file

Binary file not shown.

BIN
apps/dvxbasic/tb_stop.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -163,7 +163,7 @@ static const FileFilterT sFileFilters[] = {
static void onMenuCb(WindowT *win, int32_t menuId) { static void onMenuCb(WindowT *win, int32_t menuId) {
switch (menuId) { switch (menuId) {
case CMD_FILE_OPEN: { case CMD_FILE_OPEN: {
char path[260]; char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Open File", FD_OPEN, NULL, sFileFilters, 5, path, sizeof(path))) { if (dvxFileDialog(sAc, "Open File", FD_OPEN, NULL, sFileFilters, 5, path, sizeof(path))) {
char msg[300]; char msg[300];
@ -175,7 +175,7 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
} }
case CMD_FILE_SAVE: { case CMD_FILE_SAVE: {
char path[260]; char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Save As", FD_SAVE, NULL, sFileFilters, 5, path, sizeof(path))) { if (dvxFileDialog(sAc, "Save As", FD_SAVE, NULL, sFileFilters, 5, path, sizeof(path))) {
char msg[300]; char msg[300];

View file

@ -331,7 +331,7 @@ static void openFile(void) {
{ "Images (*.bmp;*.jpg;*.png;*.gif)", "*.bmp;*.jpg;*.png;*.gif" }, { "Images (*.bmp;*.jpg;*.png;*.gif)", "*.bmp;*.jpg;*.png;*.gif" },
{ "All Files (*.*)", "*.*" } { "All Files (*.*)", "*.*" }
}; };
char path[260]; char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Open Image", FD_OPEN, NULL, filters, 2, path, sizeof(path))) { if (dvxFileDialog(sAc, "Open Image", FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
loadAndDisplay(path); loadAndDisplay(path);

View file

@ -55,7 +55,7 @@
static DxeAppContextT *sCtx = NULL; static DxeAppContextT *sCtx = NULL;
static WindowT *sWin = NULL; static WindowT *sWin = NULL;
static WidgetT *sTextArea = NULL; static WidgetT *sTextArea = NULL;
static char sFilePath[260] = ""; static char sFilePath[DVX_MAX_PATH] = "";
// Hash of text content at last save/open, used for cheap dirty detection // Hash of text content at last save/open, used for cheap dirty detection
static uint32_t sCleanHash = 0; static uint32_t sCleanHash = 0;
@ -171,7 +171,7 @@ static void doOpen(void) {
{ "Text Files (*.txt)", "*.txt" }, { "Text Files (*.txt)", "*.txt" },
{ "All Files (*.*)", "*.*" } { "All Files (*.*)", "*.*" }
}; };
char path[260]; char path[DVX_MAX_PATH];
if (!dvxFileDialog(sCtx->shellCtx, "Open", FD_OPEN, NULL, filters, 2, path, sizeof(path))) { if (!dvxFileDialog(sCtx->shellCtx, "Open", FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
return; return;
@ -257,7 +257,7 @@ static void doSaveAs(void) {
{ "Text Files (*.txt)", "*.txt" }, { "Text Files (*.txt)", "*.txt" },
{ "All Files (*.*)", "*.*" } { "All Files (*.*)", "*.*" }
}; };
char path[260]; char path[DVX_MAX_PATH];
if (!dvxFileDialog(sCtx->shellCtx, "Save As", FD_SAVE, NULL, filters, 2, path, sizeof(path))) { if (!dvxFileDialog(sCtx->shellCtx, "Save As", FD_SAVE, NULL, filters, 2, path, sizeof(path))) {
return; return;

View file

@ -445,44 +445,14 @@ static void scanAppsDirRecurse(const char *dirPath) {
} }
// Override from embedded resources if available // Override from embedded resources if available
DvxResHandleT *res = dvxResOpen(fullPath); entry->iconData = dvxResLoadIcon(sAc, fullPath, "icon32", &entry->iconW, &entry->iconH, &entry->iconPitch);
if (res) {
uint32_t iconSize = 0;
void *iconBuf = dvxResRead(res, "icon32", &iconSize);
if (iconBuf) {
entry->iconData = dvxLoadImageFromMemory(sAc, (const uint8_t *)iconBuf, (int32_t)iconSize, &entry->iconW, &entry->iconH, &entry->iconPitch);
if (!entry->iconData) { if (!entry->iconData) {
dvxLog("Progman: failed to decode icon for %s (%d bytes)", ent->d_name, (int)iconSize);
}
free(iconBuf);
} else {
dvxLog("Progman: no icon32 resource in %s", ent->d_name); dvxLog("Progman: no icon32 resource in %s", ent->d_name);
} }
uint32_t nameSize = 0; dvxResLoadText(fullPath, "name", entry->name, SHELL_APP_NAME_MAX);
void *nameBuf = dvxResRead(res, "name", &nameSize); dvxResLoadText(fullPath, "description", entry->tooltip, sizeof(entry->tooltip));
if (nameBuf) {
snprintf(entry->name, SHELL_APP_NAME_MAX, "%s", (const char *)nameBuf);
free(nameBuf);
}
uint32_t descSize = 0;
void *descBuf = dvxResRead(res, "description", &descSize);
if (descBuf) {
snprintf(entry->tooltip, sizeof(entry->tooltip), "%s", (const char *)descBuf);
free(descBuf);
}
dvxResClose(res);
} else {
dvxLog("Progman: no resources in %s", ent->d_name);
}
dvxLog("Progman: found %s (%s) icon=%s", entry->name, ent->d_name, entry->iconData ? "yes" : "no"); dvxLog("Progman: found %s (%s) icon=%s", entry->name, ent->d_name, entry->iconData ? "yes" : "no");
sAppCount++; sAppCount++;
@ -563,7 +533,7 @@ int32_t appMain(DxeAppContextT *ctx) {
sAc = ctx->shellCtx; sAc = ctx->shellCtx;
// Load saved preferences // Load saved preferences
char prefsPath[260]; char prefsPath[DVX_MAX_PATH];
shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath)); shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath));
prefsLoad(prefsPath); prefsLoad(prefsPath);
sMinOnRun = prefsGetBool("options", "minimizeOnRun", false); sMinOnRun = prefsGetBool("options", "minimizeOnRun", false);

View file

@ -38,6 +38,7 @@
#include "dvxCursor.h" #include "dvxCursor.h"
#include "dvxPlatform.h" #include "dvxPlatform.h"
#include "dvxResource.h"
#include "thirdparty/stb_ds_wrap.h" #include "thirdparty/stb_ds_wrap.h"
#include <string.h> #include <string.h>
@ -1942,7 +1943,7 @@ static void interactiveScreenshot(AppContextT *ctx) {
{ "PNG Images (*.png)", "*.png" }, { "PNG Images (*.png)", "*.png" },
{ "BMP Images (*.bmp)", "*.bmp" } { "BMP Images (*.bmp)", "*.bmp" }
}; };
char path[260]; char path[DVX_MAX_PATH];
int32_t scrW = ctx->display.width; int32_t scrW = ctx->display.width;
int32_t scrH = ctx->display.height; int32_t scrH = ctx->display.height;
@ -1977,7 +1978,7 @@ static void interactiveWindowScreenshot(AppContextT *ctx, WindowT *win) {
{ "PNG Images (*.png)", "*.png" }, { "PNG Images (*.png)", "*.png" },
{ "BMP Images (*.bmp)", "*.bmp" } { "BMP Images (*.bmp)", "*.bmp" }
}; };
char path[260]; char path[DVX_MAX_PATH];
int32_t capW = win->contentW; int32_t capW = win->contentW;
int32_t capH = win->contentH; int32_t capH = win->contentH;
@ -3701,6 +3702,87 @@ const char *dvxClipboardGet(int32_t *outLen) {
} }
// ============================================================
// dvxResLoadIcon
// ============================================================
uint8_t *dvxResLoadIcon(AppContextT *ctx, const char *dxePath, const char *resName, int32_t *outW, int32_t *outH, int32_t *outPitch) {
if (!ctx || !dxePath || !resName) {
return NULL;
}
DvxResHandleT *res = dvxResOpen(dxePath);
if (!res) {
return NULL;
}
uint32_t size = 0;
void *buf = dvxResRead(res, resName, &size);
dvxResClose(res);
if (!buf) {
return NULL;
}
uint8_t *pixels = dvxLoadImageFromMemory(ctx, (const uint8_t *)buf, (int32_t)size, outW, outH, outPitch);
free(buf);
return pixels;
}
// ============================================================
// dvxResLoadText
// ============================================================
bool dvxResLoadText(const char *dxePath, const char *resName, char *buf, int32_t bufSize) {
if (!dxePath || !resName || !buf || bufSize <= 0) {
return false;
}
DvxResHandleT *res = dvxResOpen(dxePath);
if (!res) {
return false;
}
uint32_t size = 0;
void *data = dvxResRead(res, resName, &size);
dvxResClose(res);
if (!data) {
return false;
}
int32_t copyLen = (int32_t)size < bufSize - 1 ? (int32_t)size : bufSize - 1;
memcpy(buf, data, copyLen);
buf[copyLen] = '\0';
free(data);
return true;
}
// ============================================================
// dvxResLoadData
// ============================================================
void *dvxResLoadData(const char *dxePath, const char *resName, uint32_t *outSize) {
if (!dxePath || !resName) {
return NULL;
}
DvxResHandleT *res = dvxResOpen(dxePath);
if (!res) {
return NULL;
}
void *data = dvxResRead(res, resName, outSize);
dvxResClose(res);
return data;
}
// ============================================================ // ============================================================
// dvxColorLabel // dvxColorLabel
// ============================================================ // ============================================================

View file

@ -112,7 +112,7 @@ typedef struct AppContextT {
// kept so the image can be reloaded after a video mode or mode change. // kept so the image can be reloaded after a video mode or mode change.
uint8_t *wallpaperBuf; // pixel data (screen width * height * bpp/8) uint8_t *wallpaperBuf; // pixel data (screen width * height * bpp/8)
int32_t wallpaperPitch; // bytes per row int32_t wallpaperPitch; // bytes per row
char wallpaperPath[260]; // source image path (empty = none) char wallpaperPath[DVX_MAX_PATH]; // source image path (empty = none)
WallpaperModeE wallpaperMode; // stretch, tile, or center WallpaperModeE wallpaperMode; // stretch, tile, or center
} AppContextT; } AppContextT;
@ -315,4 +315,27 @@ void dvxClipboardCopy(const char *text, int32_t len);
// NULL if the clipboard is empty. // NULL if the clipboard is empty.
const char *dvxClipboardGet(int32_t *outLen); const char *dvxClipboardGet(int32_t *outLen);
// ============================================================
// Resource loading helpers
// ============================================================
//
// Convenience functions for loading typed resources from DXE files.
// These combine dvxResOpen/Read/Close with type-specific processing
// so callers don't need to manage the resource handle or temp buffers.
// Load an icon/image resource and decode it to native pixel format.
// Returns the decoded pixel data (caller must free with dvxFreeImage),
// or NULL if not found. Sets outW/outH/outPitch on success.
uint8_t *dvxResLoadIcon(AppContextT *ctx, const char *dxePath, const char *resName, int32_t *outW, int32_t *outH, int32_t *outPitch);
// Load a text resource into a caller-provided buffer.
// Returns true on success. The text is null-terminated and truncated
// to fit bufSize. Returns false if the resource is not found.
bool dvxResLoadText(const char *dxePath, const char *resName, char *buf, int32_t bufSize);
// Load a raw binary resource. Returns a malloc'd buffer that the
// caller must free, or NULL if not found. Sets *outSize to the
// data size in bytes.
void *dvxResLoadData(const char *dxePath, const char *resName, uint32_t *outSize);
#endif // DVX_APP_H #endif // DVX_APP_H

View file

@ -18,6 +18,12 @@
#include <stdint.h> #include <stdint.h>
// Fallback for the native dvxres tool, which includes this header
// without dvxTypes.h. The canonical definition is in dvxTypes.h.
#ifndef DVX_MAX_PATH
#define DVX_MAX_PATH 260
#endif
// Resource type IDs // Resource type IDs
#define DVX_RES_ICON 1 // image data (BMP icon: 16x16, 32x32, etc.) #define DVX_RES_ICON 1 // image data (BMP icon: 16x16, 32x32, etc.)
#define DVX_RES_TEXT 2 // null-terminated string (author, copyright, etc.) #define DVX_RES_TEXT 2 // null-terminated string (author, copyright, etc.)
@ -48,7 +54,7 @@ typedef struct {
// Runtime resource handle (opaque to callers) // Runtime resource handle (opaque to callers)
typedef struct { typedef struct {
char path[260]; char path[DVX_MAX_PATH];
DvxResDirEntryT *entries; DvxResDirEntryT *entries;
uint32_t entryCount; uint32_t entryCount;
} DvxResHandleT; } DvxResHandleT;

View file

@ -11,6 +11,15 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
// ============================================================
// Path limits
// ============================================================
//
// Maximum file path length. 260 matches DOS MAX_PATH. Future
// platforms (Linux, Windows) should increase this.
#define DVX_MAX_PATH 260
// ============================================================ // ============================================================
// Pixel format descriptor // Pixel format descriptor
// ============================================================ // ============================================================
@ -557,6 +566,8 @@ typedef struct WindowT {
void (*onMenu)(struct WindowT *win, int32_t menuId); void (*onMenu)(struct WindowT *win, int32_t menuId);
void (*onScroll)(struct WindowT *win, ScrollbarOrientE orient, int32_t value); void (*onScroll)(struct WindowT *win, ScrollbarOrientE orient, int32_t value);
int32_t (*onCursorQuery)(struct WindowT *win, int32_t x, int32_t y); // return CURSOR_* or 0 for default int32_t (*onCursorQuery)(struct WindowT *win, int32_t x, int32_t y); // return CURSOR_* or 0 for default
void (*onFocus)(struct WindowT *win); // window gained focus
void (*onBlur)(struct WindowT *win); // window lost focus
} WindowT; } WindowT;
// ============================================================ // ============================================================

View file

@ -1031,6 +1031,105 @@ void wmAddMenuSeparator(MenuT *menu) {
} }
// ============================================================
// wmMenuFindItem -- find menu item by command ID across all menus
// ============================================================
static MenuItemT *wmMenuFindItem(MenuBarT *bar, int32_t id, MenuT **outMenu) {
if (!bar) {
return NULL;
}
for (int32_t m = 0; m < bar->menuCount; m++) {
MenuT *menu = &bar->menus[m];
for (int32_t i = 0; i < menu->itemCount; i++) {
if (menu->items[i].id == id) {
if (outMenu) {
*outMenu = menu;
}
return &menu->items[i];
}
// Search submenus recursively
if (menu->items[i].subMenu) {
for (int32_t j = 0; j < menu->items[i].subMenu->itemCount; j++) {
if (menu->items[i].subMenu->items[j].id == id) {
if (outMenu) {
*outMenu = menu->items[i].subMenu;
}
return &menu->items[i].subMenu->items[j];
}
}
}
}
}
return NULL;
}
// ============================================================
// wmMenuItemIsChecked
// ============================================================
bool wmMenuItemIsChecked(MenuBarT *bar, int32_t id) {
MenuItemT *item = wmMenuFindItem(bar, id, NULL);
return item ? item->checked : false;
}
// ============================================================
// wmMenuItemSetChecked
// ============================================================
void wmMenuItemSetChecked(MenuBarT *bar, int32_t id, bool checked) {
MenuT *menu = NULL;
MenuItemT *item = wmMenuFindItem(bar, id, &menu);
if (!item) {
return;
}
if (item->type == MenuItemRadioE && checked && menu) {
// Find the radio group and uncheck all others
int32_t idx = (int32_t)(item - menu->items);
int32_t groupStart = idx;
while (groupStart > 0 && menu->items[groupStart - 1].type == MenuItemRadioE) {
groupStart--;
}
int32_t groupEnd = idx;
while (groupEnd < menu->itemCount - 1 && menu->items[groupEnd + 1].type == MenuItemRadioE) {
groupEnd++;
}
for (int32_t i = groupStart; i <= groupEnd; i++) {
menu->items[i].checked = (i == idx);
}
} else {
item->checked = checked;
}
}
// ============================================================
// wmMenuItemSetEnabled
// ============================================================
void wmMenuItemSetEnabled(MenuBarT *bar, int32_t id, bool enabled) {
MenuItemT *item = wmMenuFindItem(bar, id, NULL);
if (item) {
item->enabled = enabled;
}
}
// ============================================================ // ============================================================
// wmAddSubMenu // wmAddSubMenu
// ============================================================ // ============================================================
@ -1782,6 +1881,14 @@ void wmMinimize(WindowStackT *stack, DirtyListT *dl, WindowT *win) {
} }
} }
if (stack->focusedIdx >= 0 && stack->focusedIdx < stack->count) {
WindowT *oldWin = stack->windows[stack->focusedIdx];
if (oldWin->onBlur) {
oldWin->onBlur(oldWin);
}
}
stack->focusedIdx = -1; stack->focusedIdx = -1;
} }
@ -2618,8 +2725,10 @@ void wmSetFocus(WindowStackT *stack, DirtyListT *dl, int32_t idx) {
} }
// Unfocus old window // Unfocus old window
WindowT *oldWin = NULL;
if (stack->focusedIdx >= 0 && stack->focusedIdx < stack->count) { if (stack->focusedIdx >= 0 && stack->focusedIdx < stack->count) {
WindowT *oldWin = stack->windows[stack->focusedIdx]; oldWin = stack->windows[stack->focusedIdx];
oldWin->focused = false; oldWin->focused = false;
// Dirty the old title bar // Dirty the old title bar
@ -2635,6 +2744,15 @@ void wmSetFocus(WindowStackT *stack, DirtyListT *dl, int32_t idx) {
// Dirty the new title bar // Dirty the new title bar
dirtyListAdd(dl, newWin->x, newWin->y, dirtyListAdd(dl, newWin->x, newWin->y,
newWin->w, CHROME_BORDER_WIDTH + CHROME_TITLE_HEIGHT); newWin->w, CHROME_BORDER_WIDTH + CHROME_TITLE_HEIGHT);
// Fire callbacks (after state is updated)
if (oldWin && oldWin != newWin && oldWin->onBlur) {
oldWin->onBlur(oldWin);
}
if (newWin != oldWin && newWin->onFocus) {
newWin->onFocus(newWin);
}
} }

View file

@ -79,6 +79,15 @@ void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked
// Insert a horizontal separator line. Separators are not interactive. // Insert a horizontal separator line. Separators are not interactive.
void wmAddMenuSeparator(MenuT *menu); void wmAddMenuSeparator(MenuT *menu);
// Query or set the checked state of a menu item by command ID.
// Searches all menus in the menu bar. For radio items, setting
// checked=true also unchecks other radio items in the same group.
bool wmMenuItemIsChecked(MenuBarT *bar, int32_t id);
void wmMenuItemSetChecked(MenuBarT *bar, int32_t id, bool checked);
// Enable or disable a menu item by command ID.
void wmMenuItemSetEnabled(MenuBarT *bar, int32_t id, bool enabled);
// Create a cascading submenu attached to the parent menu. Returns the // Create a cascading submenu attached to the parent menu. Returns the
// child MenuT to populate, or NULL on allocation failure. // child MenuT to populate, or NULL on allocation failure.
// The child MenuT is heap-allocated and freed when the parent window // The child MenuT is heap-allocated and freed when the parent window

View file

@ -1306,7 +1306,7 @@ void dvxMemResetApp(int32_t appId) {
// is silently ignored. // is silently ignored.
int32_t platformMkdirRecursive(const char *path) { int32_t platformMkdirRecursive(const char *path) {
char buf[260]; char buf[DVX_MAX_PATH];
strncpy(buf, path, sizeof(buf) - 1); strncpy(buf, path, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0'; buf[sizeof(buf) - 1] = '\0';

View file

@ -32,7 +32,7 @@ static ApiMapEntryT *sApiMap = NULL;
// stb_ds string hashmap: key = widget name, value = iface + path // stb_ds string hashmap: key = widget name, value = iface + path
typedef struct { typedef struct {
const WgtIfaceT *iface; const WgtIfaceT *iface;
char path[260]; char path[DVX_MAX_PATH];
int32_t pathIndex; // 1-based: 1 = first widget from this file, 2 = second, etc. int32_t pathIndex; // 1-based: 1 = first widget from this file, 2 = second, etc.
} IfaceEntryT; } IfaceEntryT;

View file

@ -66,7 +66,7 @@ void dvxLog(const char *fmt, ...) {
// ============================================================ // ============================================================
typedef struct { typedef struct {
char path[260]; char path[DVX_MAX_PATH];
char baseName[16]; char baseName[16];
char **deps; char **deps;
bool loaded; bool loaded;
@ -134,7 +134,7 @@ static void extractBaseName(const char *path, const char *ext, char *out, int32_
static void readDeps(ModuleT *mod) { static void readDeps(ModuleT *mod) {
// Build dep file path: replace extension with .dep // Build dep file path: replace extension with .dep
char depPath[260]; char depPath[DVX_MAX_PATH];
strncpy(depPath, mod->path, sizeof(depPath) - 1); strncpy(depPath, mod->path, sizeof(depPath) - 1);
depPath[sizeof(depPath) - 1] = '\0'; depPath[sizeof(depPath) - 1] = '\0';
@ -200,7 +200,7 @@ static void scanDir(const char *dirPath, const char *ext, ModuleT **mods) {
continue; continue;
} }
char path[260]; char path[DVX_MAX_PATH];
snprintf(path, sizeof(path), "%s/%s", dirPath, name); snprintf(path, sizeof(path), "%s/%s", dirPath, name);
// Check for matching extension // Check for matching extension
@ -443,7 +443,7 @@ static void *findSymbol(void **handles, const char *symbol) {
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
// Change to the directory containing the executable so relative // Change to the directory containing the executable so relative
// paths (LIBS/, WIDGETS/, APPS/, CONFIG/) resolve correctly. // paths (LIBS/, WIDGETS/, APPS/, CONFIG/) resolve correctly.
char exeDir[260]; char exeDir[DVX_MAX_PATH];
strncpy(exeDir, argv[0], sizeof(exeDir) - 1); strncpy(exeDir, argv[0], sizeof(exeDir) - 1);
exeDir[sizeof(exeDir) - 1] = '\0'; exeDir[sizeof(exeDir) - 1] = '\0';

View file

@ -312,7 +312,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
// allows multiple instances. We read the descriptor from the existing // allows multiple instances. We read the descriptor from the existing
// slot directly -- no need to dlopen again. // slot directly -- no need to dlopen again.
const char *loadPath = path; const char *loadPath = path;
char tempPath[260] = {0}; char tempPath[DVX_MAX_PATH] = {0};
ShellAppT *existing = findLoadedPath(path); ShellAppT *existing = findLoadedPath(path);
if (existing) { if (existing) {
@ -428,6 +428,9 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
app->dxeCtx->shellCtx = ctx; app->dxeCtx->shellCtx = ctx;
app->dxeCtx->appId = id; app->dxeCtx->appId = id;
// Store the full app path so the app can load its own resources.
snprintf(app->dxeCtx->appPath, sizeof(app->dxeCtx->appPath), "%s", path);
// Derive app directory from path (everything up to the last separator). // Derive app directory from path (everything up to the last separator).
// This lets apps load resources relative to their own location rather // This lets apps load resources relative to their own location rather
// than the shell's working directory. // than the shell's working directory.

View file

@ -62,8 +62,9 @@ typedef struct {
typedef struct { typedef struct {
AppContextT *shellCtx; // the shell's GUI context AppContextT *shellCtx; // the shell's GUI context
int32_t appId; // this app's ID int32_t appId; // this app's ID
char appDir[260]; // directory containing the .app file char appPath[DVX_MAX_PATH]; // full path to the .app file
char configDir[260]; // writable config directory (CONFIG/<apppath>/) char appDir[DVX_MAX_PATH]; // directory containing the .app file
char configDir[DVX_MAX_PATH]; // writable config directory (CONFIG/<apppath>/)
} DxeAppContextT; } DxeAppContextT;
// ============================================================ // ============================================================
@ -85,8 +86,8 @@ typedef enum {
typedef struct { typedef struct {
int32_t appId; // unique ID = slot index (1-based; 0 = shell) int32_t appId; // unique ID = slot index (1-based; 0 = shell)
char name[SHELL_APP_NAME_MAX]; char name[SHELL_APP_NAME_MAX];
char path[260]; char path[DVX_MAX_PATH];
char tempPath[260]; // temp copy path for multi-instance (empty = not a copy) char tempPath[DVX_MAX_PATH]; // temp copy path for multi-instance (empty = not a copy)
void *dxeHandle; // dlopen() handle void *dxeHandle; // dlopen() handle
AppStateE state; AppStateE state;
bool hasMainLoop; bool hasMainLoop;

View file

@ -10,11 +10,14 @@ BINDIR = ../bin
.PHONY: all clean .PHONY: all clean
all: $(BINDIR)/dvxres all: $(BINDIR)/dvxres $(BINDIR)/mktbicon
$(BINDIR)/dvxres: dvxres.c ../core/dvxResource.c ../core/dvxResource.h | $(BINDIR) $(BINDIR)/dvxres: dvxres.c ../core/dvxResource.c ../core/dvxResource.h | $(BINDIR)
$(CC) $(CFLAGS) -o $@ dvxres.c ../core/dvxResource.c $(CC) $(CFLAGS) -o $@ dvxres.c ../core/dvxResource.c
$(BINDIR)/mktbicon: mktbicon.c | $(BINDIR)
$(CC) $(CFLAGS) -o $@ mktbicon.c
$(BINDIR): $(BINDIR):
mkdir -p $(BINDIR) mkdir -p $(BINDIR)

243
tools/mktbicon.c Normal file
View file

@ -0,0 +1,243 @@
// mktbicon.c -- Generate 16x16 BMP toolbar icons for DVX BASIC IDE
//
// Usage: mktbicon <output.bmp> <type>
// Types: open, save, run, stop, code, design
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define W 16
#define H 16
static uint8_t pixels[H][W][3]; // BGR
static void clear(uint8_t r, uint8_t g, uint8_t b) {
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
pixels[y][x][0] = b;
pixels[y][x][1] = g;
pixels[y][x][2] = r;
}
}
}
static void px(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
if (x >= 0 && x < W && y >= 0 && y < H) {
pixels[y][x][0] = b;
pixels[y][x][1] = g;
pixels[y][x][2] = r;
}
}
static void rect(int x0, int y0, int w, int h, uint8_t r, uint8_t g, uint8_t b) {
for (int y = y0; y < y0 + h && y < H; y++) {
for (int x = x0; x < x0 + w && x < W; x++) {
px(x, y, r, g, b);
}
}
}
static void hline(int x0, int y, int w, uint8_t r, uint8_t g, uint8_t b) {
for (int x = x0; x < x0 + w; x++) {
px(x, y, r, g, b);
}
}
static void vline(int x, int y0, int h, uint8_t r, uint8_t g, uint8_t b) {
for (int y = y0; y < y0 + h; y++) {
px(x, y, r, g, b);
}
}
static void outline(int x0, int y0, int w, int h, uint8_t r, uint8_t g, uint8_t b) {
hline(x0, y0, w, r, g, b);
hline(x0, y0 + h - 1, w, r, g, b);
vline(x0, y0, h, r, g, b);
vline(x0 + w - 1, y0, h, r, g, b);
}
// Open: folder icon (yellow folder with tab)
static void iconOpen(void) {
clear(192, 192, 192);
// Folder body
rect(1, 5, 14, 9, 220, 180, 50);
outline(1, 5, 14, 9, 160, 120, 20);
// Folder tab
rect(2, 3, 5, 3, 220, 180, 50);
hline(2, 3, 5, 160, 120, 20);
vline(2, 3, 2, 160, 120, 20);
vline(6, 3, 2, 160, 120, 20);
// Front flap (lighter)
rect(2, 7, 12, 6, 240, 210, 80);
hline(2, 7, 12, 160, 120, 20);
}
// Save: floppy disk icon (blue disk)
static void iconSave(void) {
clear(192, 192, 192);
// Disk body
rect(2, 1, 12, 14, 60, 60, 160);
outline(2, 1, 12, 14, 30, 30, 100);
// Metal slider (silver top)
rect(5, 1, 6, 5, 200, 200, 200);
outline(5, 1, 6, 5, 120, 120, 120);
// Slot in metal
rect(7, 2, 2, 3, 60, 60, 160);
// Label (white bottom)
rect(4, 9, 8, 5, 240, 240, 240);
outline(4, 9, 8, 5, 120, 120, 120);
// Lines on label
hline(5, 11, 6, 160, 160, 160);
hline(5, 12, 6, 160, 160, 160);
}
// Run: green play triangle
static void iconRun(void) {
clear(192, 192, 192);
// Play triangle pointing right
for (int y = 0; y < 12; y++) {
int w = (y < 6) ? y + 1 : 12 - y;
hline(4, 2 + y, w, 0, 160, 0);
}
// Darker outline on top and bottom edges
for (int y = 0; y < 12; y++) {
int w = (y < 6) ? y + 1 : 12 - y;
px(4, 2 + y, 0, 100, 0);
px(4 + w - 1, 2 + y, 0, 100, 0);
}
}
// Stop: red square
static void iconStop(void) {
clear(192, 192, 192);
rect(3, 3, 10, 10, 200, 40, 40);
outline(3, 3, 10, 10, 140, 20, 20);
}
// Code: text/code icon (page with angle brackets)
static void iconCode(void) {
clear(192, 192, 192);
// Page
rect(3, 1, 10, 14, 255, 255, 255);
outline(3, 1, 10, 14, 80, 80, 80);
// Dog-ear fold
rect(10, 1, 3, 3, 192, 192, 192);
hline(10, 3, 3, 80, 80, 80);
vline(10, 1, 3, 80, 80, 80);
px(10, 1, 80, 80, 80);
px(11, 2, 80, 80, 80);
px(12, 3, 80, 80, 80);
// "<" bracket
px(5, 6, 0, 0, 180);
px(4, 7, 0, 0, 180);
px(5, 8, 0, 0, 180);
// ">" bracket
px(9, 6, 0, 0, 180);
px(10, 7, 0, 0, 180);
px(9, 8, 0, 0, 180);
// "/" between
px(8, 5, 0, 0, 180);
px(7, 7, 0, 0, 180);
px(6, 9, 0, 0, 180);
// Text lines
hline(5, 11, 6, 160, 160, 160);
hline(5, 13, 4, 160, 160, 160);
}
// Design: form/window with grid dots
static void iconDesign(void) {
clear(192, 192, 192);
// Window frame
rect(1, 1, 14, 14, 255, 255, 255);
outline(1, 1, 14, 14, 0, 0, 128);
// Title bar
rect(2, 2, 12, 3, 0, 0, 128);
// Grid dots
for (int y = 7; y < 14; y += 2) {
for (int x = 3; x < 14; x += 2) {
px(x, y, 160, 160, 160);
}
}
// Small widget rectangle in the form
rect(4, 8, 6, 3, 192, 192, 192);
outline(4, 8, 6, 3, 80, 80, 80);
}
static void writeBmp(const char *path) {
int32_t rowPad = (4 - (W * 3) % 4) % 4;
int32_t rowSize = W * 3 + rowPad;
int32_t dataSize = rowSize * H;
int32_t fileSize = 54 + dataSize;
uint8_t header[54];
memset(header, 0, sizeof(header));
header[0] = 'B';
header[1] = 'M';
*(int32_t *)&header[2] = fileSize;
*(int32_t *)&header[10] = 54;
*(int32_t *)&header[14] = 40;
*(int32_t *)&header[18] = W;
*(int32_t *)&header[22] = H;
*(int16_t *)&header[26] = 1;
*(int16_t *)&header[28] = 24;
*(int32_t *)&header[34] = dataSize;
FILE *f = fopen(path, "wb");
if (!f) {
fprintf(stderr, "Cannot write: %s\n", path);
return;
}
fwrite(header, 1, 54, f);
uint8_t pad[3] = {0};
for (int y = H - 1; y >= 0; y--) {
fwrite(pixels[y], 1, W * 3, f);
if (rowPad > 0) {
fwrite(pad, 1, rowPad, f);
}
}
fclose(f);
}
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "Usage: mktbicon <output.bmp> <type>\n");
fprintf(stderr, "Types: open, save, run, stop, code, design\n");
return 1;
}
const char *path = argv[1];
const char *type = argv[2];
if (strcmp(type, "open") == 0) { iconOpen(); }
else if (strcmp(type, "save") == 0) { iconSave(); }
else if (strcmp(type, "run") == 0) { iconRun(); }
else if (strcmp(type, "stop") == 0) { iconStop(); }
else if (strcmp(type, "code") == 0) { iconCode(); }
else if (strcmp(type, "design") == 0) { iconDesign(); }
else {
fprintf(stderr, "Unknown icon type: %s\n", type);
return 1;
}
writeBmp(path);
printf("Generated %s (%s)\n", path, type);
return 0;
}