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~
*.SWP
.claude/
capture/

View file

@ -77,7 +77,7 @@ AppDescriptorT appDescriptor = {
typedef struct {
char name[64];
char path[280];
char path[DVX_MAX_PATH];
} FileEntryT;
// ============================================================
@ -119,7 +119,7 @@ static WidgetT *sColorSwatch = NULL;
static WidgetT *sWallpaperLbl = NULL;
static WidgetT *sWpaperList = NULL;
static WidgetT *sWpModeDrop = NULL;
static char sWallpaperPath[280];
static char sWallpaperPath[DVX_MAX_PATH];
static FileEntryT *sWpaperEntries = 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" },
{ "All Files (*.*)", "*.*" }
};
char path[260];
char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Load Theme", FD_OPEN, THEME_DIR, filters, 2, path, sizeof(path))) {
dvxLoadTheme(sAc, path);
@ -591,7 +591,7 @@ static void onSaveTheme(WidgetT *w) {
{ "Theme Files (*.thm)", "*.thm" },
{ "All Files (*.*)", "*.*" }
};
char path[260];
char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Save Theme", FD_SAVE, THEME_DIR, filters, 2, path, sizeof(path))) {
dvxSaveTheme(sAc, path);
@ -637,7 +637,7 @@ static void onChooseWallpaper(WidgetT *w) {
{ "Images (*.bmp;*.jpg;*.png)", "*.bmp;*.jpg;*.png" },
{ "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 (dvxSetWallpaper(sAc, path)) {
@ -867,7 +867,7 @@ static void onClose(WindowT *win) {
// saveSnapshot / restoreSnapshot
// ============================================================
static char sSavedWallpaperPath[260];
static char sSavedWallpaperPath[DVX_MAX_PATH];
static void saveSnapshot(void) {
memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb));
@ -948,7 +948,7 @@ static void scanThemes(void) {
memcpy(entry.name, ent->d_name, nameLen);
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);
}
@ -992,7 +992,7 @@ static void scanWallpapers(void) {
FileEntryT entry = {0};
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);
}

View file

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

View file

@ -3,3 +3,10 @@ icon32 icon icon32.bmp
name text "DVX BASIC"
author text "DVX Project"
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 "dvxPlatform.h"
#include "dvxDialog.h"
#include "dvxPrefs.h"
#include "dvxWidget.h"
#include "dvxWidgetPlugin.h"
#include "dvxWm.h"
#include "shellApp.h"
#include "widgetBox.h"
#include "widgetImageButton.h"
#include "widgetLabel.h"
#include "widgetTextInput.h"
#include "widgetDropdown.h"
@ -66,6 +68,12 @@
#define CMD_WIN_TOOLBOX 112
#define CMD_WIN_PROPS 113
#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_DESIGN_W 400
#define IDE_DESIGN_H 300
@ -83,6 +91,8 @@ static void loadFilePath(const char *path);
static void saveFile(void);
static void onTbSave(WidgetT *w);
static void onClose(WindowT *win);
static void onContentFocus(WindowT *win);
static WindowT *getLastFocusWin(void);
static void onMenu(WindowT *win, int32_t menuId);
static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx);
static void evaluateImmediate(const char *expr);
@ -128,6 +138,8 @@ static WidgetT *sOutput = NULL;
static WidgetT *sImmediate = NULL;
static WidgetT *sObjDropdown = NULL;
static WidgetT *sEvtDropdown = NULL;
static WidgetT *sToolbar = NULL;
static WidgetT *sStatusBar = NULL;
static WidgetT *sStatus = NULL;
static BasVmT *sVm = NULL; // VM instance (non-NULL while running)
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 *sToolboxWin = NULL;
static WindowT *sPropsWin = NULL;
static WindowT *sLastFocusWin = NULL; // last focused non-toolbar window
static char sSourceBuf[IDE_MAX_SOURCE];
static char sOutputBuf[IDE_MAX_OUTPUT];
static int32_t sOutputLen = 0;
static char sFilePath[260];
static char sFilePath[DVX_MAX_PATH];
// Procedure table for Object/Event dropdowns
typedef struct {
@ -175,6 +188,27 @@ int32_t appMain(DxeAppContextT *ctx) {
basStringSystemInit();
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';
sSourceBuf[0] = '\0';
sOutputBuf[0] = '\0';
@ -187,6 +221,25 @@ int32_t appMain(DxeAppContextT *ctx) {
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
// ============================================================
@ -211,6 +264,12 @@ static void buildWindow(void) {
wmAddMenuItem(fileMenu, "E&xit", CMD_EXIT);
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);
MenuT *runMenu = wmAddMenu(menuBar, "&Run");
@ -223,6 +282,9 @@ static void buildWindow(void) {
MenuT *viewMenu = wmAddMenu(menuBar, "&View");
wmAddMenuItem(viewMenu, "&Code\tF7", CMD_VIEW_CODE);
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");
wmAddMenuItem(winMenu, "&Code Editor", CMD_WIN_CODE);
@ -243,27 +305,35 @@ static void buildWindow(void) {
sWin->accelTable = accel;
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;
wgtSetTooltip(tbOpen, "Open (Ctrl+O)");
WidgetT *tbSave = wgtButton(tb, "Save");
WidgetT *tbSave = loadTbIcon(tb, "tb_save", "Save");
tbSave->onClick = onTbSave;
wgtSetTooltip(tbSave, "Save (Ctrl+S)");
WidgetT *tbRun = wgtButton(tb, "Run");
WidgetT *tbRun = loadTbIcon(tb, "tb_run", "Run");
tbRun->onClick = onTbRun;
wgtSetTooltip(tbRun, "Run (F5)");
WidgetT *tbStop = wgtButton(tb, "Stop");
WidgetT *tbStop = loadTbIcon(tb, "tb_stop", "Stop");
tbStop->onClick = onTbStop;
wgtSetTooltip(tbStop, "Stop (Esc)");
WidgetT *tbCode = wgtButton(tb, "Code");
WidgetT *tbCode = loadTbIcon(tb, "tb_code", "Code");
tbCode->onClick = onTbCode;
wgtSetTooltip(tbCode, "Code View (F7)");
WidgetT *tbDesign = wgtButton(tb, "Design");
WidgetT *tbDesign = loadTbIcon(tb, "tb_design", "Design");
tbDesign->onClick = onTbDesign;
wgtSetTooltip(tbDesign, "Design View (Shift+F7)");
WidgetT *statusBar = wgtStatusBar(tbRoot);
sStatusBar = wgtStatusBar(tbRoot);
WidgetT *statusBar = sStatusBar;
sStatus = wgtLabel(statusBar, "");
sStatus->weight = 100;
@ -792,7 +862,7 @@ static void loadFile(void) {
{ "All Files (*.*)", "*.*" }
};
char path[260];
char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Open BASIC File", FD_OPEN, NULL, filters, 3, path, sizeof(path))) {
loadFilePath(path);
@ -833,7 +903,7 @@ static void saveFile(void) {
// Save the .frm if the designer has form data
if (sDesigner.form && sDesigner.form->dirty) {
char frmPath[260];
char frmPath[DVX_MAX_PATH];
snprintf(frmPath, sizeof(frmPath), "%s", sFilePath);
char *dot = strrchr(frmPath, '.');
@ -875,7 +945,7 @@ static void loadFrmFiles(BasFormRtT *rt) {
}
// Build .frm path from .bas path
char frmPath[260];
char frmPath[DVX_MAX_PATH];
snprintf(frmPath, sizeof(frmPath), "%s", sFilePath);
// 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
// ============================================================
@ -954,6 +1044,7 @@ static void onClose(WindowT *win) {
sCodeWin = NULL;
sOutWin = NULL;
sImmWin = NULL;
sLastFocusWin = NULL;
sEditor = NULL;
sOutput = NULL;
sImmediate = NULL;
@ -1045,6 +1136,23 @@ static void onMenu(WindowT *win, int32_t menuId) {
}
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:
if (sFormWin && sDesigner.selectedIdx >= 0) {
int32_t prevCount = (int32_t)arrlen(sDesigner.form->controls);
@ -1059,6 +1167,26 @@ static void onMenu(WindowT *win, int32_t menuId) {
}
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:
if (sWin) {
onClose(sWin);
@ -1446,7 +1574,7 @@ static void switchToDesign(void) {
// Load .frm if we don't have a form yet
if (!sDesigner.form) {
if (sFilePath[0]) {
char frmPath[260];
char frmPath[DVX_MAX_PATH];
snprintf(frmPath, sizeof(frmPath), "%s", sFilePath);
char *dot = strrchr(frmPath, '.');
@ -1575,7 +1703,9 @@ static void showCodeWindow(void) {
if (sCodeWin) {
sCodeWin->onMenu = onMenu;
sCodeWin->onFocus = onContentFocus;
sCodeWin->accelTable = sWin ? sWin->accelTable : NULL;
sLastFocusWin = 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);
if (sOutWin) {
sOutWin->onFocus = onContentFocus;
sLastFocusWin = sOutWin;
WidgetT *outRoot = wgtInitWindow(sAc, sOutWin);
sOutput = wgtTextArea(outRoot, IDE_MAX_OUTPUT);
sOutput->weight = 100;
@ -1630,7 +1763,6 @@ static void showOutputWindow(void) {
if (sOutputLen > 0) {
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);
if (sImmWin) {
sImmWin->onFocus = onContentFocus;
sLastFocusWin = sImmWin;
WidgetT *immRoot = wgtInitWindow(sAc, sImmWin);
if (immRoot) {

View file

@ -110,47 +110,28 @@ WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) {
snprintf(entry.typeName, DSGN_MAX_NAME, "%s", iface->basName);
// Load icon data and tooltip from the .wgt file resources
uint8_t *iconData = NULL;
int32_t iconW = 0;
int32_t iconH = 0;
uint8_t *iconData = NULL;
int32_t iconW = 0;
int32_t iconH = 0;
int32_t iconPitch = 0;
const char *wgtPath = wgtIfaceGetPath(wgtName);
int32_t pathIdx = wgtIfaceGetPathIndex(wgtName);
if (wgtPath) {
DvxResHandleT *res = dvxResOpen(wgtPath);
// Build suffixed resource names: "icon16", "icon16-2", etc.
char iconResName[32];
char nameResName[32];
if (res) {
// Build suffixed resource names: "icon16", "icon16-2", "icon16-3", etc.
char iconResName[32];
char nameResName[32];
if (pathIdx <= 1) {
snprintf(iconResName, sizeof(iconResName), "icon16");
snprintf(nameResName, sizeof(nameResName), "name");
} else {
snprintf(iconResName, sizeof(iconResName), "icon16-%d", (int)pathIdx);
snprintf(nameResName, sizeof(nameResName), "name-%d", (int)pathIdx);
}
uint32_t iconSize = 0;
void *iconBuf = dvxResRead(res, iconResName, &iconSize);
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 (pathIdx <= 1) {
snprintf(iconResName, sizeof(iconResName), "icon16");
snprintf(nameResName, sizeof(nameResName), "name");
} else {
snprintf(iconResName, sizeof(iconResName), "icon16-%d", (int)pathIdx);
snprintf(nameResName, sizeof(nameResName), "name-%d", (int)pathIdx);
}
iconData = dvxResLoadIcon(ctx, wgtPath, iconResName, &iconW, &iconH, &iconPitch);
dvxResLoadText(wgtPath, nameResName, entry.tooltip, sizeof(entry.tooltip));
}
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) {
switch (menuId) {
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))) {
char msg[300];
@ -175,7 +175,7 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
}
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))) {
char msg[300];

View file

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

View file

@ -55,7 +55,7 @@
static DxeAppContextT *sCtx = NULL;
static WindowT *sWin = 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
static uint32_t sCleanHash = 0;
@ -171,7 +171,7 @@ static void doOpen(void) {
{ "Text Files (*.txt)", "*.txt" },
{ "All Files (*.*)", "*.*" }
};
char path[260];
char path[DVX_MAX_PATH];
if (!dvxFileDialog(sCtx->shellCtx, "Open", FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
return;
@ -257,7 +257,7 @@ static void doSaveAs(void) {
{ "Text Files (*.txt)", "*.txt" },
{ "All Files (*.*)", "*.*" }
};
char path[260];
char path[DVX_MAX_PATH];
if (!dvxFileDialog(sCtx->shellCtx, "Save As", FD_SAVE, NULL, filters, 2, path, sizeof(path))) {
return;

View file

@ -445,45 +445,15 @@ static void scanAppsDirRecurse(const char *dirPath) {
}
// 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) {
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);
}
uint32_t nameSize = 0;
void *nameBuf = dvxResRead(res, "name", &nameSize);
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);
if (!entry->iconData) {
dvxLog("Progman: no icon32 resource in %s", ent->d_name);
}
dvxResLoadText(fullPath, "name", entry->name, SHELL_APP_NAME_MAX);
dvxResLoadText(fullPath, "description", entry->tooltip, sizeof(entry->tooltip));
dvxLog("Progman: found %s (%s) icon=%s", entry->name, ent->d_name, entry->iconData ? "yes" : "no");
sAppCount++;
}
@ -563,7 +533,7 @@ int32_t appMain(DxeAppContextT *ctx) {
sAc = ctx->shellCtx;
// Load saved preferences
char prefsPath[260];
char prefsPath[DVX_MAX_PATH];
shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath));
prefsLoad(prefsPath);
sMinOnRun = prefsGetBool("options", "minimizeOnRun", false);

View file

@ -38,6 +38,7 @@
#include "dvxCursor.h"
#include "dvxPlatform.h"
#include "dvxResource.h"
#include "thirdparty/stb_ds_wrap.h"
#include <string.h>
@ -1942,7 +1943,7 @@ static void interactiveScreenshot(AppContextT *ctx) {
{ "PNG Images (*.png)", "*.png" },
{ "BMP Images (*.bmp)", "*.bmp" }
};
char path[260];
char path[DVX_MAX_PATH];
int32_t scrW = ctx->display.width;
int32_t scrH = ctx->display.height;
@ -1977,7 +1978,7 @@ static void interactiveWindowScreenshot(AppContextT *ctx, WindowT *win) {
{ "PNG Images (*.png)", "*.png" },
{ "BMP Images (*.bmp)", "*.bmp" }
};
char path[260];
char path[DVX_MAX_PATH];
int32_t capW = win->contentW;
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
// ============================================================

View file

@ -112,7 +112,7 @@ typedef struct AppContextT {
// kept so the image can be reloaded after a video mode or mode change.
uint8_t *wallpaperBuf; // pixel data (screen width * height * bpp/8)
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
} AppContextT;
@ -315,4 +315,27 @@ void dvxClipboardCopy(const char *text, int32_t len);
// NULL if the clipboard is empty.
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

View file

@ -18,6 +18,12 @@
#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
#define DVX_RES_ICON 1 // image data (BMP icon: 16x16, 32x32, etc.)
#define DVX_RES_TEXT 2 // null-terminated string (author, copyright, etc.)
@ -48,7 +54,7 @@ typedef struct {
// Runtime resource handle (opaque to callers)
typedef struct {
char path[260];
char path[DVX_MAX_PATH];
DvxResDirEntryT *entries;
uint32_t entryCount;
} DvxResHandleT;

View file

@ -11,6 +11,15 @@
#include <stdint.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
// ============================================================
@ -557,6 +566,8 @@ typedef struct WindowT {
void (*onMenu)(struct WindowT *win, int32_t menuId);
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
void (*onFocus)(struct WindowT *win); // window gained focus
void (*onBlur)(struct WindowT *win); // window lost focus
} 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
// ============================================================
@ -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;
}
@ -2618,8 +2725,10 @@ void wmSetFocus(WindowStackT *stack, DirtyListT *dl, int32_t idx) {
}
// Unfocus old window
WindowT *oldWin = NULL;
if (stack->focusedIdx >= 0 && stack->focusedIdx < stack->count) {
WindowT *oldWin = stack->windows[stack->focusedIdx];
oldWin = stack->windows[stack->focusedIdx];
oldWin->focused = false;
// Dirty the old title bar
@ -2635,6 +2744,15 @@ void wmSetFocus(WindowStackT *stack, DirtyListT *dl, int32_t idx) {
// Dirty the new title bar
dirtyListAdd(dl, newWin->x, newWin->y,
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.
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
// child MenuT to populate, or NULL on allocation failure.
// 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.
int32_t platformMkdirRecursive(const char *path) {
char buf[260];
char buf[DVX_MAX_PATH];
strncpy(buf, path, sizeof(buf) - 1);
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
typedef struct {
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.
} IfaceEntryT;

View file

@ -66,7 +66,7 @@ void dvxLog(const char *fmt, ...) {
// ============================================================
typedef struct {
char path[260];
char path[DVX_MAX_PATH];
char baseName[16];
char **deps;
bool loaded;
@ -134,7 +134,7 @@ static void extractBaseName(const char *path, const char *ext, char *out, int32_
static void readDeps(ModuleT *mod) {
// Build dep file path: replace extension with .dep
char depPath[260];
char depPath[DVX_MAX_PATH];
strncpy(depPath, mod->path, sizeof(depPath) - 1);
depPath[sizeof(depPath) - 1] = '\0';
@ -200,7 +200,7 @@ static void scanDir(const char *dirPath, const char *ext, ModuleT **mods) {
continue;
}
char path[260];
char path[DVX_MAX_PATH];
snprintf(path, sizeof(path), "%s/%s", dirPath, name);
// Check for matching extension
@ -443,7 +443,7 @@ static void *findSymbol(void **handles, const char *symbol) {
int main(int argc, char *argv[]) {
// Change to the directory containing the executable so relative
// paths (LIBS/, WIDGETS/, APPS/, CONFIG/) resolve correctly.
char exeDir[260];
char exeDir[DVX_MAX_PATH];
strncpy(exeDir, argv[0], sizeof(exeDir) - 1);
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
// slot directly -- no need to dlopen again.
const char *loadPath = path;
char tempPath[260] = {0};
char tempPath[DVX_MAX_PATH] = {0};
ShellAppT *existing = findLoadedPath(path);
if (existing) {
@ -428,6 +428,9 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
app->dxeCtx->shellCtx = ctx;
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).
// This lets apps load resources relative to their own location rather
// than the shell's working directory.

View file

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

View file

@ -10,11 +10,14 @@ BINDIR = ../bin
.PHONY: all clean
all: $(BINDIR)/dvxres
all: $(BINDIR)/dvxres $(BINDIR)/mktbicon
$(BINDIR)/dvxres: dvxres.c ../core/dvxResource.c ../core/dvxResource.h | $(BINDIR)
$(CC) $(CFLAGS) -o $@ dvxres.c ../core/dvxResource.c
$(BINDIR)/mktbicon: mktbicon.c | $(BINDIR)
$(CC) $(CFLAGS) -o $@ mktbicon.c
$(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;
}