More IDE changes and a few core changes to help support easier app development.
This commit is contained in:
parent
d094205ed0
commit
344ab4794d
29 changed files with 731 additions and 116 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -7,3 +7,4 @@ lib/
|
|||
.gitattributes~
|
||||
*.SWP
|
||||
.claude/
|
||||
capture/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -118,10 +118,7 @@ WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) {
|
|||
int32_t pathIdx = wgtIfaceGetPathIndex(wgtName);
|
||||
|
||||
if (wgtPath) {
|
||||
DvxResHandleT *res = dvxResOpen(wgtPath);
|
||||
|
||||
if (res) {
|
||||
// Build suffixed resource names: "icon16", "icon16-2", "icon16-3", etc.
|
||||
// Build suffixed resource names: "icon16", "icon16-2", etc.
|
||||
char iconResName[32];
|
||||
char nameResName[32];
|
||||
|
||||
|
|
@ -133,24 +130,8 @@ WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) {
|
|||
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);
|
||||
}
|
||||
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
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
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
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
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
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
BIN
apps/dvxbasic/tb_stop.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -445,44 +445,14 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
|||
}
|
||||
|
||||
// Override from embedded resources if available
|
||||
DvxResHandleT *res = dvxResOpen(fullPath);
|
||||
|
||||
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);
|
||||
entry->iconData = dvxResLoadIcon(sAc, fullPath, "icon32", &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);
|
||||
}
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
120
core/dvxWm.c
120
core/dvxWm.c
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
243
tools/mktbicon.c
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue