diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..09811c3 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +# DVX GUI — Top-level Makefile +# +# Builds the full DVX stack: library, task switcher, shell, and apps. + +.PHONY: all clean dvx tasks dvxshell apps + +all: dvx tasks dvxshell apps + +dvx: + $(MAKE) -C dvx + +tasks: + $(MAKE) -C tasks + +dvxshell: dvx tasks + $(MAKE) -C dvxshell + +apps: dvx tasks dvxshell + $(MAKE) -C apps + +clean: + $(MAKE) -C dvx clean + $(MAKE) -C tasks clean + $(MAKE) -C dvxshell clean + $(MAKE) -C apps clean + -rmdir obj/dvx/widgets obj/dvx/platform obj/dvx obj/tasks obj/dvxshell obj/apps obj 2>/dev/null + -rmdir bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin lib 2>/dev/null diff --git a/apps/Makefile b/apps/Makefile index cef0bd5..f440fb5 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -10,25 +10,32 @@ OBJDIR = ../obj/apps BINDIR = ../bin/apps # App definitions: each is a subdir with a single .c file -APPS = progman notepad clock +APPS = progman notepad clock dvxdemo .PHONY: all clean $(APPS) all: $(APPS) -progman: $(BINDIR)/progman.app -notepad: $(BINDIR)/notepad.app -clock: $(BINDIR)/clock.app +progman: $(BINDIR)/progman/progman.app +notepad: $(BINDIR)/notepad/notepad.app +clock: $(BINDIR)/clock/clock.app +dvxdemo: $(BINDIR)/dvxdemo/dvxdemo.app -$(BINDIR)/progman.app: $(OBJDIR)/progman.o | $(BINDIR) +$(BINDIR)/progman/progman.app: $(OBJDIR)/progman.o | $(BINDIR)/progman $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $< -$(BINDIR)/notepad.app: $(OBJDIR)/notepad.o | $(BINDIR) +$(BINDIR)/notepad/notepad.app: $(OBJDIR)/notepad.o | $(BINDIR)/notepad $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $< -$(BINDIR)/clock.app: $(OBJDIR)/clock.o | $(BINDIR) +$(BINDIR)/clock/clock.app: $(OBJDIR)/clock.o | $(BINDIR)/clock $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -E _appShutdown -U $< +DVXDEMO_BMPS = logo.bmp new.bmp open.bmp sample.bmp save.bmp + +$(BINDIR)/dvxdemo/dvxdemo.app: $(OBJDIR)/dvxdemo.o $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) | $(BINDIR)/dvxdemo + $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $< + cp $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) $(BINDIR)/dvxdemo/ + $(OBJDIR)/progman.o: progman/progman.c | $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< @@ -38,16 +45,33 @@ $(OBJDIR)/notepad.o: notepad/notepad.c | $(OBJDIR) $(OBJDIR)/clock.o: clock/clock.c | $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c | $(OBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< + $(OBJDIR): mkdir -p $(OBJDIR) -$(BINDIR): - mkdir -p $(BINDIR) +$(BINDIR)/progman: + mkdir -p $(BINDIR)/progman + +$(BINDIR)/notepad: + mkdir -p $(BINDIR)/notepad + +$(BINDIR)/clock: + mkdir -p $(BINDIR)/clock + +$(BINDIR)/dvxdemo: + mkdir -p $(BINDIR)/dvxdemo # Dependencies $(OBJDIR)/progman.o: progman/progman.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvxshell/shellApp.h $(OBJDIR)/notepad.o: notepad/notepad.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvxshell/shellApp.h $(OBJDIR)/clock.o: clock/clock.c ../dvx/dvxApp.h ../dvx/dvxWidget.h ../dvx/dvxDraw.h ../dvx/dvxVideo.h ../dvxshell/shellApp.h ../tasks/taskswitch.h +$(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvx/dvxVideo.h ../dvxshell/shellApp.h clean: - rm -rf $(OBJDIR) $(BINDIR) + rm -f $(OBJDIR)/progman.o $(OBJDIR)/notepad.o $(OBJDIR)/clock.o $(OBJDIR)/dvxdemo.o + rm -f $(BINDIR)/progman/progman.app + rm -f $(BINDIR)/notepad/notepad.app + rm -f $(BINDIR)/clock/clock.app + rm -f $(BINDIR)/dvxdemo/dvxdemo.app $(addprefix $(BINDIR)/dvxdemo/,$(DVXDEMO_BMPS)) diff --git a/dvxdemo/demo.c b/apps/dvxdemo/dvxdemo.c similarity index 76% rename from dvxdemo/demo.c rename to apps/dvxdemo/dvxdemo.c index 9da323a..596442b 100644 --- a/dvxdemo/demo.c +++ b/apps/dvxdemo/dvxdemo.c @@ -1,15 +1,19 @@ -// demo.c — DVX GUI demonstration application +// dvxdemo.c — DVX GUI demonstration app (DXE version) +// +// Callback-only app that opens several windows showcasing the DVX +// widget system, paint callbacks, menus, accelerators, etc. #include "dvxApp.h" #include "dvxDialog.h" #include "dvxWidget.h" +#include "dvxWm.h" +#include "shellApp.h" +#include +#include #include #include #include -#include - -#include "thirdparty/stb_image.h" // ============================================================ // Menu command IDs @@ -49,7 +53,8 @@ // Prototypes // ============================================================ -static uint8_t *loadBmpPixels(AppContextT *ctx, const char *path, int32_t *outW, int32_t *outH, int32_t *outPitch); +int32_t appMain(DxeAppContextT *ctx); +static void onCanvasDraw(WidgetT *w, int32_t cx, int32_t cy, bool drag); static void onCloseCb(WindowT *win); static void onCloseMainCb(WindowT *win); static void onMenuCb(WindowT *win, int32_t menuId); @@ -58,102 +63,58 @@ static void onPaintColor(WindowT *win, RectT *dirtyArea); static void onPaintPattern(WindowT *win, RectT *dirtyArea); static void onPaintText(WindowT *win, RectT *dirtyArea); static void onToolbarClick(WidgetT *w); -static void setupControlsWindow(AppContextT *ctx); -static void setupMainWindow(AppContextT *ctx); -static void setupTerminalWindow(AppContextT *ctx); -static void setupWidgetDemo(AppContextT *ctx); +static void setupControlsWindow(void); +static void setupMainWindow(void); +static void setupTerminalWindow(void); +static void setupWidgetDemo(void); // ============================================================ -// Globals +// Module state // ============================================================ -static AppContextT *sCtx = NULL; - +static AppContextT *sAc = NULL; +static DxeAppContextT *sDxeCtx = NULL; // ============================================================ -// loadBmpPixels — load a BMP/PNG file into display-format pixels +// App descriptor // ============================================================ -static uint8_t *loadBmpPixels(AppContextT *ctx, const char *path, int32_t *outW, int32_t *outH, int32_t *outPitch) { - int imgW; - int imgH; - int channels; - uint8_t *rgb = stbi_load(path, &imgW, &imgH, &channels, 3); +AppDescriptorT appDescriptor = { + .name = "DVX Demo", + .hasMainLoop = false, + .stackSize = 0, + .priority = TS_PRIORITY_NORMAL +}; - if (!rgb) { - return NULL; +// ============================================================ +// Callbacks +// ============================================================ + +static void onCanvasDraw(WidgetT *w, int32_t cx, int32_t cy, bool drag) { + if (drag && w->as.canvas.lastX >= 0) { + wgtCanvasDrawLine(w, w->as.canvas.lastX, w->as.canvas.lastY, cx, cy); + } else { + wgtCanvasDrawLine(w, cx, cy, cx, cy); } - - const DisplayT *d = dvxGetDisplay(ctx); - int32_t bpp = d->format.bytesPerPixel; - int32_t pitch = imgW * bpp; - uint8_t *data = (uint8_t *)malloc(pitch * imgH); - - if (!data) { - stbi_image_free(rgb); - return NULL; - } - - for (int32_t y = 0; y < imgH; y++) { - for (int32_t x = 0; x < imgW; x++) { - uint8_t *src = rgb + (y * imgW + x) * 3; - uint32_t color = packColor(d, src[0], src[1], src[2]); - uint8_t *dst = data + y * pitch + x * bpp; - - if (bpp == 1) { - *dst = (uint8_t)color; - } else if (bpp == 2) { - *(uint16_t *)dst = (uint16_t)color; - } else { - *(uint32_t *)dst = color; - } - } - } - - stbi_image_free(rgb); - *outW = imgW; - *outH = imgH; - *outPitch = pitch; - return data; } -// ============================================================ -// onCloseCb -// ============================================================ - static void onCloseCb(WindowT *win) { - AppContextT *ctx = (AppContextT *)win->userData; - - if (ctx) { - dvxDestroyWindow(ctx, win); - } + dvxDestroyWindow(sAc, win); } -// ============================================================ -// onCloseMainCb -// ============================================================ - static void onCloseMainCb(WindowT *win) { - AppContextT *ctx = (AppContextT *)win->userData; + int32_t result = dvxMessageBox(sAc, "Close", + "Close this window?", + MB_YESNO | MB_ICONQUESTION); - if (ctx) { - int32_t result = dvxMessageBox(ctx, "Exit", - "Are you sure you want to exit?", - MB_YESNO | MB_ICONQUESTION); - - if (result == ID_YES) { - dvxQuit(ctx); - } + if (result == ID_YES) { + dvxDestroyWindow(sAc, win); } } -// ============================================================ -// onMenuCb -// ============================================================ - static const FileFilterT sFileFilters[] = { {"All Files (*.*)", "*.*"}, {"Text Files (*.txt)", "*.txt"}, @@ -163,16 +124,14 @@ static const FileFilterT sFileFilters[] = { }; static void onMenuCb(WindowT *win, int32_t menuId) { - AppContextT *ctx = (AppContextT *)win->userData; - switch (menuId) { case CMD_FILE_OPEN: { char path[260]; - if (dvxFileDialog(ctx, "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]; snprintf(msg, sizeof(msg), "Selected: %s", path); - dvxMessageBox(ctx, "Open", msg, MB_OK | MB_ICONINFO); + dvxMessageBox(sAc, "Open", msg, MB_OK | MB_ICONINFO); } break; @@ -181,96 +140,84 @@ static void onMenuCb(WindowT *win, int32_t menuId) { case CMD_FILE_SAVE: { char path[260]; - if (dvxFileDialog(ctx, "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]; snprintf(msg, sizeof(msg), "Save to: %s", path); - dvxMessageBox(ctx, "Save", msg, MB_OK | MB_ICONINFO); + dvxMessageBox(sAc, "Save", msg, MB_OK | MB_ICONINFO); } break; } case CMD_FILE_EXIT: - if (ctx) { - int32_t result = dvxMessageBox(ctx, "Exit", - "Are you sure you want to exit?", - MB_YESNO | MB_ICONQUESTION); - - if (result == ID_YES) { - dvxQuit(ctx); - } - } + onCloseMainCb(win); break; case CMD_VIEW_TERM: - setupTerminalWindow(ctx); + setupTerminalWindow(); break; case CMD_VIEW_CTRL: - setupControlsWindow(ctx); + setupControlsWindow(); break; case CMD_VIEW_DEBUG_LYT: { static bool sDebugLyt = false; sDebugLyt = !sDebugLyt; - wgtSetDebugLayout(ctx, sDebugLyt); + wgtSetDebugLayout(sAc, sDebugLyt); break; } case CMD_WIN_CASCADE: - dvxCascadeWindows(ctx); + dvxCascadeWindows(sAc); break; case CMD_WIN_TILE_H: - dvxTileWindowsH(ctx); + dvxTileWindowsH(sAc); break; case CMD_WIN_TILE_V: - dvxTileWindowsV(ctx); + dvxTileWindowsV(sAc); break; case CMD_WIN_TILE: - dvxTileWindows(ctx); + dvxTileWindows(sAc); break; case CMD_HELP_ABOUT: - dvxMessageBox(sCtx, "About DVX Demo", + dvxMessageBox(sAc, "About DVX Demo", "DVX GUI Demonstration\n\n" "A DOS Visual eXecutive windowing system for DOS.", MB_OK | MB_ICONINFO); break; case CMD_CTX_CUT: - dvxMessageBox(ctx, "Context Menu", "Cut selected.", MB_OK | MB_ICONINFO); + dvxMessageBox(sAc, "Context Menu", "Cut selected.", MB_OK | MB_ICONINFO); break; case CMD_CTX_COPY: - dvxMessageBox(ctx, "Context Menu", "Copied to clipboard.", MB_OK | MB_ICONINFO); + dvxMessageBox(sAc, "Context Menu", "Copied to clipboard.", MB_OK | MB_ICONINFO); break; case CMD_CTX_PASTE: - dvxMessageBox(ctx, "Context Menu", "Pasted from clipboard.", MB_OK | MB_ICONINFO); + dvxMessageBox(sAc, "Context Menu", "Pasted from clipboard.", MB_OK | MB_ICONINFO); break; case CMD_CTX_DELETE: - dvxMessageBox(ctx, "Context Menu", "Deleted.", MB_OK | MB_ICONINFO); + dvxMessageBox(sAc, "Context Menu", "Deleted.", MB_OK | MB_ICONINFO); break; case CMD_CTX_SELALL: - dvxMessageBox(ctx, "Context Menu", "Selected all.", MB_OK | MB_ICONINFO); + dvxMessageBox(sAc, "Context Menu", "Selected all.", MB_OK | MB_ICONINFO); break; case CMD_CTX_PROPS: - dvxMessageBox(ctx, "Properties", "Window properties dialog would appear here.", MB_OK | MB_ICONINFO); + dvxMessageBox(sAc, "Properties", "Window properties dialog would appear here.", MB_OK | MB_ICONINFO); break; } } -// ============================================================ -// onOkClick -// ============================================================ - static void onOkClick(WidgetT *w) { WidgetT *root = w; @@ -286,20 +233,15 @@ static void onOkClick(WidgetT *w) { } -// ============================================================ -// onPaintColor -// ============================================================ - static void onPaintColor(WindowT *win, RectT *dirtyArea) { (void)dirtyArea; - AppContextT *ctx = (AppContextT *)win->userData; - if (!win->contentBuf || !ctx) { + if (!win->contentBuf || !sAc) { return; } - const DisplayT *d = dvxGetDisplay(ctx); - const BlitOpsT *ops = dvxGetBlitOps(ctx); + const DisplayT *d = dvxGetDisplay(sAc); + const BlitOpsT *ops = dvxGetBlitOps(sAc); for (int32_t y = 0; y < win->contentH; y++) { uint8_t r = (uint8_t)((y * 255) / (win->contentH > 1 ? win->contentH - 1 : 1)); @@ -314,19 +256,14 @@ static void onPaintColor(WindowT *win, RectT *dirtyArea) { } -// ============================================================ -// onPaintPattern -// ============================================================ - static void onPaintPattern(WindowT *win, RectT *dirtyArea) { (void)dirtyArea; - AppContextT *ctx = (AppContextT *)win->userData; - if (!win->contentBuf || !ctx) { + if (!win->contentBuf || !sAc) { return; } - const DisplayT *d = dvxGetDisplay(ctx); + const DisplayT *d = dvxGetDisplay(sAc); int32_t bpp = d->format.bytesPerPixel; int32_t sq = 16; @@ -350,21 +287,16 @@ static void onPaintPattern(WindowT *win, RectT *dirtyArea) { } -// ============================================================ -// onPaintText -// ============================================================ - static void onPaintText(WindowT *win, RectT *dirtyArea) { (void)dirtyArea; - AppContextT *ctx = (AppContextT *)win->userData; - if (!win->contentBuf || !ctx) { + if (!win->contentBuf || !sAc) { return; } - const DisplayT *d = dvxGetDisplay(ctx); - const BlitOpsT *ops = dvxGetBlitOps(ctx); - const BitmapFontT *font = dvxGetFont(ctx); + const DisplayT *d = dvxGetDisplay(sAc); + const BlitOpsT *ops = dvxGetBlitOps(sAc); + const BitmapFontT *font = dvxGetFont(sAc); int32_t bpp = d->format.bytesPerPixel; uint32_t bg = packColor(d, 255, 255, 255); @@ -447,10 +379,6 @@ static void onPaintText(WindowT *win, RectT *dirtyArea) { } -// ============================================================ -// onToolbarClick -// ============================================================ - static void onToolbarClick(WidgetT *w) { WidgetT *root = w; @@ -473,17 +401,16 @@ static void onToolbarClick(WidgetT *w) { static const char *colorItems[] = {"Red", "Green", "Blue", "Yellow", "Cyan", "Magenta"}; static const char *sizeItems[] = {"Small", "Medium", "Large", "Extra Large"}; -static void setupControlsWindow(AppContextT *ctx) { - WindowT *win = dvxCreateWindow(ctx, "Advanced Widgets", 380, 50, 360, 440, true); +static void setupControlsWindow(void) { + WindowT *win = dvxCreateWindow(sAc, "Advanced Widgets", 380, 50, 360, 440, true); if (!win) { return; } - win->userData = ctx; - win->onClose = onCloseCb; + win->onClose = onCloseCb; - WidgetT *root = wgtInitWindow(ctx, win); + WidgetT *root = wgtInitWindow(sAc, win); // TabControl at top WidgetT *tabs = wgtTabControl(root); @@ -617,58 +544,35 @@ static void setupControlsWindow(AppContextT *ctx) { wgtLabel(sp, "Bottom of scroll area"); wgtButton(sp, "Scrolled &Button"); - // --- Tab 5: Toolbar (ImageButtons + VSeparator) --- + // --- Tab 5: Toolbar (text buttons + VSeparator) --- WidgetT *page5tb = wgtTabPage(tabs, "Tool&bar"); WidgetT *tb = wgtToolbar(page5tb); - int32_t imgW; - int32_t imgH; - int32_t imgPitch; - - uint8_t *newData = loadBmpPixels(ctx, "new.bmp", &imgW, &imgH, &imgPitch); - if (newData) { - WidgetT *btnNew = wgtImageButton(tb, newData, imgW, imgH, imgPitch); - wgtSetName(btnNew, "New"); - btnNew->onClick = onToolbarClick; - } - - uint8_t *openData = loadBmpPixels(ctx, "open.bmp", &imgW, &imgH, &imgPitch); - if (openData) { - WidgetT *btnOpen = wgtImageButton(tb, openData, imgW, imgH, imgPitch); - wgtSetName(btnOpen, "Open"); - btnOpen->onClick = onToolbarClick; - } - - uint8_t *saveData = loadBmpPixels(ctx, "save.bmp", &imgW, &imgH, &imgPitch); - if (saveData) { - WidgetT *btnSave = wgtImageButton(tb, saveData, imgW, imgH, imgPitch); - wgtSetName(btnSave, "Save"); - btnSave->onClick = onToolbarClick; - } + WidgetT *btnNew = wgtButton(tb, "&New"); + btnNew->onClick = onToolbarClick; + WidgetT *btnOpen = wgtButton(tb, "&Open"); + btnOpen->onClick = onToolbarClick; + WidgetT *btnSave = wgtButton(tb, "&Save"); + btnSave->onClick = onToolbarClick; wgtVSeparator(tb); WidgetT *btnHelp = wgtButton(tb, "&Help"); btnHelp->onClick = onToolbarClick; - wgtLabel(page5tb, "ImageButtons with VSeparator."); + wgtLabel(page5tb, "Toolbar with text buttons and VSeparator."); - // --- Tab 6: Media (Image, ImageFromFile) --- + // --- Tab 6: Media (Image from file) --- WidgetT *page6m = wgtTabPage(tabs, "&Media"); + char samplePath[272]; + snprintf(samplePath, sizeof(samplePath), "%s/sample.bmp", sDxeCtx->appDir); wgtLabel(page6m, "ImageFromFile (sample.bmp):"); - wgtImageFromFile(page6m, "sample.bmp"); + WidgetT *img = wgtImageFromFile(page6m, samplePath); - wgtHSeparator(page6m); - - wgtLabel(page6m, "Image (logo.bmp):"); - WidgetT *imgRow = wgtHBox(page6m); - uint8_t *logoData = loadBmpPixels(ctx, "logo.bmp", &imgW, &imgH, &imgPitch); - if (logoData) { - wgtImage(imgRow, logoData, imgW, imgH, imgPitch); + if (!img) { + wgtLabel(page6m, "(File not found)"); } - wgtVSeparator(imgRow); - wgtLabel(imgRow, "32x32 DVX logo"); // --- Tab 7: Editor (TextArea, Canvas) --- WidgetT *page7e = wgtTabPage(tabs, "&Editor"); @@ -681,7 +585,7 @@ static void setupControlsWindow(AppContextT *ctx) { wgtHSeparator(page7e); wgtLabel(page7e, "Canvas (draw with mouse):"); - const DisplayT *d = dvxGetDisplay(ctx); + const DisplayT *d = dvxGetDisplay(sAc); WidgetT *cv = wgtCanvas(page7e, 280, 80); wgtCanvasSetPenColor(cv, packColor(d, 200, 0, 0)); wgtCanvasDrawRect(cv, 5, 5, 50, 35); @@ -690,6 +594,7 @@ static void setupControlsWindow(AppContextT *ctx) { wgtCanvasSetPenColor(cv, packColor(d, 0, 150, 0)); wgtCanvasDrawLine(cv, 70, 5, 130, 70); wgtCanvasSetPenColor(cv, packColor(d, 0, 0, 0)); + wgtCanvasSetMouseCallback(cv, onCanvasDraw); // --- Tab 8: Splitter --- WidgetT *page8s = wgtTabPage(tabs, "S&plit"); @@ -831,12 +736,11 @@ static void setupControlsWindow(AppContextT *ctx) { // setupMainWindow — info window + paint demos // ============================================================ -static void setupMainWindow(AppContextT *ctx) { +static void setupMainWindow(void) { // Window 1: Text information window with menu bar - WindowT *win1 = dvxCreateWindow(ctx, "DVX Information", 50, 40, 340, 350, true); + WindowT *win1 = dvxCreateWindow(sAc, "DVX Information", 50, 40, 340, 350, true); if (win1) { - win1->userData = ctx; win1->onPaint = onPaintText; win1->onClose = onCloseMainCb; win1->onMenu = onMenuCb; @@ -924,36 +828,34 @@ static void setupMainWindow(AppContextT *ctx) { win1->contextMenu = winCtx; wmUpdateContentRect(win1); - wmReallocContentBuf(win1, &ctx->display); + wmReallocContentBuf(win1, &sAc->display); RectT fullRect = {0, 0, win1->contentW, win1->contentH}; win1->onPaint(win1, &fullRect); } // Window 2: Color gradient - WindowT *win2 = dvxCreateWindow(ctx, "Color Gradient", 200, 100, 280, 250, true); + WindowT *win2 = dvxCreateWindow(sAc, "Color Gradient", 200, 100, 280, 250, true); if (win2) { - win2->userData = ctx; - win2->onPaint = onPaintColor; - win2->onClose = onCloseCb; + win2->onPaint = onPaintColor; + win2->onClose = onCloseCb; RectT fullRect = {0, 0, win2->contentW, win2->contentH}; win2->onPaint(win2, &fullRect); } // Window 3: Checkerboard pattern with scrollbars - WindowT *win3 = dvxCreateWindow(ctx, "Pattern", 400, 150, 250, 220, true); + WindowT *win3 = dvxCreateWindow(sAc, "Pattern", 400, 150, 250, 220, true); if (win3) { - win3->userData = ctx; - win3->onPaint = onPaintPattern; - win3->onClose = onCloseCb; + win3->onPaint = onPaintPattern; + win3->onClose = onCloseCb; wmAddVScrollbar(win3, 0, 100, 25); wmAddHScrollbar(win3, 0, 100, 25); wmUpdateContentRect(win3); - wmReallocContentBuf(win3, &ctx->display); + wmReallocContentBuf(win3, &sAc->display); RectT fullRect = {0, 0, win3->contentW, win3->contentH}; win3->onPaint(win3, &fullRect); @@ -965,17 +867,16 @@ static void setupMainWindow(AppContextT *ctx) { // setupTerminalWindow — ANSI terminal widget demo // ============================================================ -static void setupTerminalWindow(AppContextT *ctx) { - WindowT *win = dvxCreateWindow(ctx, "ANSI Terminal", 60, 60, 660, 420, true); +static void setupTerminalWindow(void) { + WindowT *win = dvxCreateWindow(sAc, "ANSI Terminal", 60, 60, 660, 420, true); if (!win) { return; } - win->userData = ctx; - win->onClose = onCloseCb; + win->onClose = onCloseCb; - WidgetT *root = wgtInitWindow(ctx, win); + WidgetT *root = wgtInitWindow(sAc, win); WidgetT *term = wgtAnsiTerm(root, 80, 25); term->weight = 100; @@ -1036,8 +937,8 @@ static void setupTerminalWindow(AppContextT *ctx) { WidgetT *sb = wgtStatusBar(root); wgtLabel(sb, "80x25 [Local]"); - dvxFitWindow(ctx, win); - dvxMinimizeWindow(ctx, win); + dvxFitWindow(sAc, win); + dvxMinimizeWindow(sAc, win); } @@ -1045,18 +946,17 @@ static void setupTerminalWindow(AppContextT *ctx) { // setupWidgetDemo — form with accelerators // ============================================================ -static void setupWidgetDemo(AppContextT *ctx) { - WindowT *win = dvxCreateWindow(ctx, "Widget Demo", 80, 200, 280, 360, true); +static void setupWidgetDemo(void) { + WindowT *win = dvxCreateWindow(sAc, "Widget Demo", 80, 200, 280, 360, true); if (!win) { return; } - win->userData = ctx; - win->onClose = onCloseCb; - win->onMenu = onMenuCb; + win->onClose = onCloseCb; + win->onMenu = onMenuCb; - WidgetT *root = wgtInitWindow(ctx, win); + WidgetT *root = wgtInitWindow(sAc, win); // Status label at top WidgetT *status = wgtLabel(root, "Ready."); @@ -1134,48 +1034,17 @@ static void setupWidgetDemo(AppContextT *ctx) { // ============================================================ -// main +// Entry point // ============================================================ -int main(int argc, char **argv) { - (void)argc; +int32_t appMain(DxeAppContextT *ctx) { + sDxeCtx = ctx; + sAc = ctx->shellCtx; - // Change to executable's directory so relative BMP paths work - char exeDir[260]; - strncpy(exeDir, argv[0], sizeof(exeDir) - 1); - exeDir[sizeof(exeDir) - 1] = '\0'; - char *lastSep = strrchr(exeDir, '/'); - char *lastBs = strrchr(exeDir, '\\'); - if (lastBs > lastSep) { - lastSep = lastBs; - } - if (lastSep) { - *lastSep = '\0'; - chdir(exeDir); - } - - AppContextT ctx; - - printf("DVX GUI Demo\n"); - printf("Initializing VESA video...\n"); - - if (dvxInit(&ctx, 1024, 768, 16) != 0) { - fprintf(stderr, "Failed to initialize DVX GUI\n"); - return 1; - } - - sCtx = &ctx; - - setupMainWindow(&ctx); - setupWidgetDemo(&ctx); - setupControlsWindow(&ctx); - setupTerminalWindow(&ctx); - - dvxRun(&ctx); - - dvxShutdown(&ctx); - - printf("DVX GUI Demo ended.\n"); + setupMainWindow(); + setupWidgetDemo(); + setupControlsWindow(); + setupTerminalWindow(); return 0; } diff --git a/dvxdemo/logo.bmp b/apps/dvxdemo/logo.bmp similarity index 100% rename from dvxdemo/logo.bmp rename to apps/dvxdemo/logo.bmp diff --git a/dvxdemo/new.bmp b/apps/dvxdemo/new.bmp similarity index 100% rename from dvxdemo/new.bmp rename to apps/dvxdemo/new.bmp diff --git a/dvxdemo/open.bmp b/apps/dvxdemo/open.bmp similarity index 100% rename from dvxdemo/open.bmp rename to apps/dvxdemo/open.bmp diff --git a/dvxdemo/sample.bmp b/apps/dvxdemo/sample.bmp similarity index 100% rename from dvxdemo/sample.bmp rename to apps/dvxdemo/sample.bmp diff --git a/dvxdemo/save.bmp b/apps/dvxdemo/save.bmp similarity index 100% rename from dvxdemo/save.bmp rename to apps/dvxdemo/save.bmp diff --git a/apps/progman/progman.c b/apps/progman/progman.c index 7f4153f..976ad6c 100644 --- a/apps/progman/progman.c +++ b/apps/progman/progman.c @@ -35,6 +35,7 @@ #define CMD_TILE 201 #define CMD_TILE_H 202 #define CMD_TILE_V 203 +#define CMD_MIN_ON_RUN 104 #define CMD_ABOUT 300 #define CMD_TASK_MGR 301 @@ -52,6 +53,7 @@ static AppContextT *sAc = NULL; static int32_t sMyAppId = 0; static WindowT *sPmWindow = NULL; static WidgetT *sStatusLabel = NULL; +static bool sMinOnRun = false; static AppEntryT sAppFiles[MAX_APP_FILES]; static int32_t sAppCount = 0; @@ -112,11 +114,14 @@ static void buildPmWindow(void) { // Menu bar MenuBarT *menuBar = wmAddMenuBar(sPmWindow); - MenuT *fileMenu = wmAddMenu(menuBar, "&File"); + MenuT *fileMenu = wmAddMenu(menuBar, "&File"); wmAddMenuItem(fileMenu, "&Run...", CMD_RUN); wmAddMenuSeparator(fileMenu); wmAddMenuItem(fileMenu, "E&xit Shell", CMD_EXIT); + MenuT *optMenu = wmAddMenu(menuBar, "&Options"); + wmAddMenuCheckItem(optMenu, "&Minimize on Run", CMD_MIN_ON_RUN, false); + MenuT *windowMenu = wmAddMenu(menuBar, "&Window"); wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE); wmAddMenuItem(windowMenu, "&Tile", CMD_TILE); @@ -242,6 +247,10 @@ static void onAppButtonClick(WidgetT *w) { shellLoadApp(sAc, entry->path); updateStatusText(); + + if (sMinOnRun && sPmWindow) { + dvxMinimizeWindow(sAc, sPmWindow); + } } @@ -271,6 +280,10 @@ static void onPmMenu(WindowT *win, int32_t menuId) { if (dvxFileDialog(sAc, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) { shellLoadApp(sAc, path); updateStatusText(); + + if (sMinOnRun && sPmWindow) { + dvxMinimizeWindow(sAc, sPmWindow); + } } } break; @@ -295,6 +308,10 @@ static void onPmMenu(WindowT *win, int32_t menuId) { dvxTileWindowsV(sAc); break; + case CMD_MIN_ON_RUN: + sMinOnRun = !sMinOnRun; + break; + case CMD_ABOUT: showAboutDialog(); break; @@ -485,7 +502,7 @@ static void scanAppsDir(void) { static void showAboutDialog(void) { dvxMessageBox(sAc, "About DVX Shell", - "DVX Shell 1.0\n\nA DOS Visual eXecutive desktop shell for DJGPP/DPMI. Using DXE3 dynamic loading for application modules.", + "DVX Shell 1.0\nA DOS Visual eXecutive desktop shell for DJGPP/DPMI. Using DXE3 dynamic loading for application modules.", MB_OK | MB_ICONINFO); } diff --git a/dvx/Makefile b/dvx/Makefile index f491cd6..b8a28e7 100644 --- a/dvx/Makefile +++ b/dvx/Makefile @@ -130,4 +130,4 @@ $(WOBJDIR)/widgetScrollbar.o: widgets/widgetScrollbar.c $(WIDGET_DEPS) $(WOBJDIR)/widgetTreeView.o: widgets/widgetTreeView.c $(WIDGET_DEPS) clean: - rm -rf $(OBJDIR) $(LIBDIR) + rm -f $(OBJS) $(POBJS) $(WOBJS) $(TARGET) diff --git a/dvx/dvxApp.c b/dvx/dvxApp.c index c0476a5..73b2641 100644 --- a/dvx/dvxApp.c +++ b/dvx/dvxApp.c @@ -19,8 +19,12 @@ #define KB_MOVE_STEP 8 #define MENU_CHECK_WIDTH 14 #define SUBMENU_ARROW_WIDTH 12 +#define SUBMENU_ARROW_HALF 3 // half-size of submenu arrow glyph #define TOOLTIP_DELAY_MS 500 #define TOOLTIP_PAD 3 +#define POPUP_BEVEL_WIDTH 2 // popup menu border bevel thickness +#define POPUP_ITEM_PAD_H 8 // extra horizontal padding in popup items +#define MENU_TAB_GAP_CHARS 3 // char-widths gap between label and shortcut // ============================================================ // Prototypes @@ -133,7 +137,7 @@ static void calcPopupSize(const AppContextT *ctx, const MenuT *menu, int32_t *pw memcpy(leftBuf, label, leftLen); leftBuf[leftLen] = '\0'; - itemW = textWidthAccel(&ctx->font, leftBuf) + ctx->font.charWidth * 3 + textWidth(&ctx->font, tab + 1); + itemW = textWidthAccel(&ctx->font, leftBuf) + ctx->font.charWidth * MENU_TAB_GAP_CHARS + textWidth(&ctx->font, tab + 1); } else { itemW = textWidthAccel(&ctx->font, label); } @@ -151,8 +155,8 @@ static void calcPopupSize(const AppContextT *ctx, const MenuT *menu, int32_t *pw } } - *pw = maxW + CHROME_TITLE_PAD * 2 + 8 + (hasSub ? SUBMENU_ARROW_WIDTH : 0) + (hasCheck ? MENU_CHECK_WIDTH : 0); - *ph = menu->itemCount * ctx->font.charHeight + 4; + *pw = maxW + CHROME_TITLE_PAD * 2 + POPUP_ITEM_PAD_H + (hasSub ? SUBMENU_ARROW_WIDTH : 0) + (hasCheck ? MENU_CHECK_WIDTH : 0); + *ph = menu->itemCount * ctx->font.charHeight + POPUP_BEVEL_WIDTH * 2; } @@ -380,16 +384,16 @@ static void compositeAndFlush(AppContextT *ctx) { smBevel.highlight = ctx->colors.windowHighlight; smBevel.shadow = ctx->colors.windowShadow; smBevel.face = ctx->colors.menuBg; - smBevel.width = 2; + smBevel.width = POPUP_BEVEL_WIDTH; drawBevel(d, ops, ctx->sysMenu.popupX, ctx->sysMenu.popupY, ctx->sysMenu.popupW, ctx->sysMenu.popupH, &smBevel); - int32_t itemY = ctx->sysMenu.popupY + 2; + int32_t itemY = ctx->sysMenu.popupY + POPUP_BEVEL_WIDTH; for (int32_t k = 0; k < ctx->sysMenu.itemCount; k++) { SysMenuItemT *item = &ctx->sysMenu.items[k]; if (item->separator) { - drawHLine(d, ops, ctx->sysMenu.popupX + 2, itemY + ctx->font.charHeight / 2, ctx->sysMenu.popupW - 4, ctx->colors.windowShadow); + drawHLine(d, ops, ctx->sysMenu.popupX + POPUP_BEVEL_WIDTH, itemY + ctx->font.charHeight / 2, ctx->sysMenu.popupW - POPUP_BEVEL_WIDTH * 2, ctx->colors.windowShadow); itemY += ctx->font.charHeight; continue; } @@ -402,8 +406,8 @@ static void compositeAndFlush(AppContextT *ctx) { fg = ctx->colors.menuHighlightFg; } - rectFill(d, ops, ctx->sysMenu.popupX + 2, itemY, ctx->sysMenu.popupW - 4, ctx->font.charHeight, bg); - drawTextAccel(d, ops, &ctx->font, ctx->sysMenu.popupX + CHROME_TITLE_PAD + 2, itemY, item->label, fg, bg, true); + rectFill(d, ops, ctx->sysMenu.popupX + POPUP_BEVEL_WIDTH, itemY, ctx->sysMenu.popupW - POPUP_BEVEL_WIDTH * 2, ctx->font.charHeight, bg); + drawTextAccel(d, ops, &ctx->font, ctx->sysMenu.popupX + CHROME_TITLE_PAD + POPUP_BEVEL_WIDTH, itemY, item->label, fg, bg, true); itemY += ctx->font.charHeight; } @@ -661,7 +665,7 @@ static void dispatchEvents(AppContextT *ctx) { my >= ctx->sysMenu.popupY && my < ctx->sysMenu.popupY + ctx->sysMenu.popupH) { // Hover tracking - int32_t relY = my - ctx->sysMenu.popupY - 2; + int32_t relY = my - ctx->sysMenu.popupY - POPUP_BEVEL_WIDTH; int32_t itemIdx = (relY >= 0) ? (int32_t)((uint32_t)relY * ctx->charHeightRecip >> 16) : 0; if (itemIdx >= 0 && itemIdx < ctx->sysMenu.itemCount && ctx->sysMenu.items[itemIdx].separator) { @@ -701,7 +705,7 @@ static void dispatchEvents(AppContextT *ctx) { if (inCurrent) { // Hover tracking in current level - int32_t relY = my - ctx->popup.popupY - 2; + int32_t relY = my - ctx->popup.popupY - POPUP_BEVEL_WIDTH; int32_t itemIdx = (relY >= 0) ? (int32_t)((uint32_t)relY * ctx->charHeightRecip >> 16) : 0; if (itemIdx < 0) { @@ -781,7 +785,7 @@ static void dispatchEvents(AppContextT *ctx) { closePopupLevel(ctx); // Now current level IS this parent — update hover - int32_t relY = my - ctx->popup.popupY - 2; + int32_t relY = my - ctx->popup.popupY - POPUP_BEVEL_WIDTH; int32_t itemIdx = (relY >= 0) ? (int32_t)((uint32_t)relY * ctx->charHeightRecip >> 16) : 0; if (itemIdx < 0) { @@ -970,17 +974,17 @@ static void drawPopupLevel(AppContextT *ctx, DisplayT *d, const BlitOpsT *ops, c popBevel.highlight = ctx->colors.windowHighlight; popBevel.shadow = ctx->colors.windowShadow; popBevel.face = ctx->colors.menuBg; - popBevel.width = 2; + popBevel.width = POPUP_BEVEL_WIDTH; drawBevel(d, ops, px, py, pw, ph, &popBevel); // Draw menu items - int32_t itemY = py + 2; + int32_t itemY = py + POPUP_BEVEL_WIDTH; for (int32_t k = 0; k < menu->itemCount; k++) { const MenuItemT *item = &menu->items[k]; if (item->separator) { - drawHLine(d, ops, px + 2, itemY + ctx->font.charHeight / 2, pw - 4, ctx->colors.windowShadow); + drawHLine(d, ops, px + POPUP_BEVEL_WIDTH, itemY + ctx->font.charHeight / 2, pw - POPUP_BEVEL_WIDTH * 2, ctx->colors.windowShadow); itemY += ctx->font.charHeight; continue; } @@ -993,7 +997,7 @@ static void drawPopupLevel(AppContextT *ctx, DisplayT *d, const BlitOpsT *ops, c fg = ctx->colors.menuHighlightFg; } - rectFill(d, ops, px + 2, itemY, pw - 4, ctx->font.charHeight, bg); + rectFill(d, ops, px + POPUP_BEVEL_WIDTH, itemY, pw - POPUP_BEVEL_WIDTH * 2, ctx->font.charHeight, bg); // Split label at tab: left part is the menu text, right part is the shortcut const char *tab = strchr(item->label, '\t'); @@ -1009,11 +1013,11 @@ static void drawPopupLevel(AppContextT *ctx, DisplayT *d, const BlitOpsT *ops, c memcpy(leftBuf, item->label, leftLen); leftBuf[leftLen] = '\0'; - drawTextAccel(d, ops, &ctx->font, px + CHROME_TITLE_PAD + 2 + checkMargin, itemY, leftBuf, fg, bg, true); + drawTextAccel(d, ops, &ctx->font, px + CHROME_TITLE_PAD + POPUP_BEVEL_WIDTH + checkMargin, itemY, leftBuf, fg, bg, true); const char *right = tab + 1; int32_t rightW = textWidth(&ctx->font, right); - int32_t rightX = px + pw - rightW - CHROME_TITLE_PAD - 4; + int32_t rightX = px + pw - rightW - CHROME_TITLE_PAD - POPUP_BEVEL_WIDTH * 2; if (item->subMenu) { rightX -= SUBMENU_ARROW_WIDTH; @@ -1021,13 +1025,13 @@ static void drawPopupLevel(AppContextT *ctx, DisplayT *d, const BlitOpsT *ops, c drawText(d, ops, &ctx->font, rightX, itemY, right, fg, bg, true); } else { - drawTextAccel(d, ops, &ctx->font, px + CHROME_TITLE_PAD + 2 + checkMargin, itemY, item->label, fg, bg, true); + drawTextAccel(d, ops, &ctx->font, px + CHROME_TITLE_PAD + POPUP_BEVEL_WIDTH + checkMargin, itemY, item->label, fg, bg, true); } // Draw check/radio indicator if (item->checked) { int32_t cy = itemY + ctx->font.charHeight / 2; - int32_t cx = px + 2 + MENU_CHECK_WIDTH / 2; + int32_t cx = px + POPUP_BEVEL_WIDTH + MENU_CHECK_WIDTH / 2; if (item->type == MenuItemCheckE) { // Checkmark: small tick shape @@ -1049,14 +1053,14 @@ static void drawPopupLevel(AppContextT *ctx, DisplayT *d, const BlitOpsT *ops, c // Draw submenu arrow indicator if (item->subMenu) { - int32_t arrowX = px + pw - SUBMENU_ARROW_WIDTH - 2; + int32_t arrowX = px + pw - SUBMENU_ARROW_WIDTH - POPUP_BEVEL_WIDTH; int32_t arrowY = itemY + ctx->font.charHeight / 2; - for (int32_t row = -3; row <= 3; row++) { - int32_t len = 4 - (row < 0 ? -row : row); + for (int32_t row = -SUBMENU_ARROW_HALF; row <= SUBMENU_ARROW_HALF; row++) { + int32_t len = SUBMENU_ARROW_HALF + 1 - (row < 0 ? -row : row); if (len > 0) { - drawHLine(d, ops, arrowX + (row < 0 ? 3 + row : 3 - row), arrowY + row, len, fg); + drawHLine(d, ops, arrowX + (row < 0 ? SUBMENU_ARROW_HALF + row : SUBMENU_ARROW_HALF - row), arrowY + row, len, fg); } } } @@ -2078,8 +2082,8 @@ static void openSubMenu(AppContextT *ctx) { // Open submenu at right edge of current popup, aligned with hovered item ctx->popup.menu = item->subMenu; - ctx->popup.popupX = pl->popupX + pl->popupW - 2; - ctx->popup.popupY = pl->popupY + 2 + idx * ctx->font.charHeight; + ctx->popup.popupX = pl->popupX + pl->popupW - POPUP_BEVEL_WIDTH; + ctx->popup.popupY = pl->popupY + POPUP_BEVEL_WIDTH + idx * ctx->font.charHeight; ctx->popup.hoverItem = -1; calcPopupSize(ctx, ctx->popup.menu, &ctx->popup.popupW, &ctx->popup.popupH); @@ -2178,8 +2182,8 @@ static void openSysMenu(AppContextT *ctx, WindowT *win) { } } - ctx->sysMenu.popupW = maxW + CHROME_TITLE_PAD * 2 + 8; - ctx->sysMenu.popupH = ctx->sysMenu.itemCount * ctx->font.charHeight + 4; + ctx->sysMenu.popupW = maxW + CHROME_TITLE_PAD * 2 + POPUP_ITEM_PAD_H; + ctx->sysMenu.popupH = ctx->sysMenu.itemCount * ctx->font.charHeight + POPUP_BEVEL_WIDTH * 2; ctx->sysMenu.hoverItem = -1; ctx->sysMenu.active = true; diff --git a/dvx/dvxDialog.c b/dvx/dvxDialog.c index a981a66..bf795e1 100644 --- a/dvx/dvxDialog.c +++ b/dvx/dvxDialog.c @@ -17,12 +17,18 @@ // Constants // ============================================================ -#define MSG_MAX_WIDTH 320 -#define MSG_PADDING 8 -#define ICON_AREA_WIDTH 40 -#define BUTTON_WIDTH 80 -#define BUTTON_HEIGHT 24 -#define BUTTON_GAP 8 +#define MSG_MAX_WIDTH 320 +#define MSG_PADDING 8 +#define ICON_AREA_WIDTH 40 +#define BUTTON_WIDTH 80 +#define BUTTON_HEIGHT 24 +#define BUTTON_GAP 8 +#define ICON_GLYPH_SIZE 24 // icon glyph drawing area (pixels) +#define ICON_GLYPH_CENTER 12 // center of icon glyph (ICON_GLYPH_SIZE / 2) +#define ICON_OUTER_R2 144 // outer circle radius squared (12*12) +#define ICON_INNER_R2 100 // inner circle radius squared (10*10) +#define FD_DIALOG_WIDTH 360 // file dialog window width +#define FD_DIALOG_HEIGHT 340 // file dialog window height // ============================================================ // Prototypes @@ -75,13 +81,13 @@ static MsgBoxStateT sMsgBox; static void drawIconGlyph(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t iconType, uint32_t color) { if (iconType == MB_ICONINFO) { // Circle outline with 'i' - for (int32_t row = 0; row < 24; row++) { - for (int32_t col = 0; col < 24; col++) { - int32_t dx = col - 12; - int32_t dy = row - 12; + for (int32_t row = 0; row < ICON_GLYPH_SIZE; row++) { + for (int32_t col = 0; col < ICON_GLYPH_SIZE; col++) { + int32_t dx = col - ICON_GLYPH_CENTER; + int32_t dy = row - ICON_GLYPH_CENTER; int32_t d2 = dx * dx + dy * dy; - if (d2 <= 144 && d2 >= 100) { + if (d2 <= ICON_OUTER_R2 && d2 >= ICON_INNER_R2) { rectFill(d, ops, x + col, y + row, 1, 1, color); } } @@ -91,15 +97,15 @@ static void drawIconGlyph(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y rectFill(d, ops, x + 11, y + 10, 2, 8, color); } else if (iconType == MB_ICONWARNING) { // Triangle outline with '!' - for (int32_t row = 0; row < 24; row++) { + for (int32_t row = 0; row < ICON_GLYPH_SIZE; row++) { int32_t halfW = row / 2; - int32_t lx = 12 - halfW; - int32_t rx = 12 + halfW; + int32_t lx = ICON_GLYPH_CENTER - halfW; + int32_t rx = ICON_GLYPH_CENTER + halfW; rectFill(d, ops, x + lx, y + row, 1, 1, color); rectFill(d, ops, x + rx, y + row, 1, 1, color); - if (row == 23) { + if (row == ICON_GLYPH_SIZE - 1) { drawHLine(d, ops, x + lx, y + row, rx - lx + 1, color); } } @@ -108,31 +114,31 @@ static void drawIconGlyph(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y rectFill(d, ops, x + 11, y + 19, 2, 2, color); } else if (iconType == MB_ICONERROR) { // Circle outline with X - for (int32_t row = 0; row < 24; row++) { - for (int32_t col = 0; col < 24; col++) { - int32_t dx = col - 12; - int32_t dy = row - 12; + for (int32_t row = 0; row < ICON_GLYPH_SIZE; row++) { + for (int32_t col = 0; col < ICON_GLYPH_SIZE; col++) { + int32_t dx = col - ICON_GLYPH_CENTER; + int32_t dy = row - ICON_GLYPH_CENTER; int32_t d2 = dx * dx + dy * dy; - if (d2 <= 144 && d2 >= 100) { + if (d2 <= ICON_OUTER_R2 && d2 >= ICON_INNER_R2) { rectFill(d, ops, x + col, y + row, 1, 1, color); } } } - for (int32_t i = 0; i < 12; i++) { + for (int32_t i = 0; i < ICON_GLYPH_CENTER; i++) { rectFill(d, ops, x + 6 + i, y + 6 + i, 2, 2, color); rectFill(d, ops, x + 18 - i, y + 6 + i, 2, 2, color); } } else if (iconType == MB_ICONQUESTION) { // Circle outline with '?' - for (int32_t row = 0; row < 24; row++) { - for (int32_t col = 0; col < 24; col++) { - int32_t dx = col - 12; - int32_t dy = row - 12; + for (int32_t row = 0; row < ICON_GLYPH_SIZE; row++) { + for (int32_t col = 0; col < ICON_GLYPH_SIZE; col++) { + int32_t dx = col - ICON_GLYPH_CENTER; + int32_t dy = row - ICON_GLYPH_CENTER; int32_t d2 = dx * dx + dy * dy; - if (d2 <= 144 && d2 >= 100) { + if (d2 <= ICON_OUTER_R2 && d2 >= ICON_INNER_R2) { rectFill(d, ops, x + col, y + row, 1, 1, color); } } @@ -214,7 +220,7 @@ int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message, // Calculate content area sizes int32_t msgAreaH = textH + MSG_PADDING * 2; - int32_t iconAreaH = hasIcon ? (24 + MSG_PADDING * 2) : 0; + int32_t iconAreaH = hasIcon ? (ICON_GLYPH_SIZE + MSG_PADDING * 2) : 0; if (msgAreaH < iconAreaH) { msgAreaH = iconAreaH; @@ -982,8 +988,8 @@ bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const cha sFd.curDir[FD_MAX_PATH - 1] = '\0'; // Create dialog window - int32_t dlgW = 360; - int32_t dlgH = 340; + int32_t dlgW = FD_DIALOG_WIDTH; + int32_t dlgH = FD_DIALOG_HEIGHT; int32_t winX = (ctx->display.width - dlgW) / 2; int32_t winY = (ctx->display.height - dlgH) / 2; diff --git a/dvx/dvxTypes.h b/dvx/dvxTypes.h index 574c4e5..619eb74 100644 --- a/dvx/dvxTypes.h +++ b/dvx/dvxTypes.h @@ -86,6 +86,8 @@ typedef struct { // Bitmap font // ============================================================ +#define FONT_CHAR_WIDTH 8 // fixed glyph width in pixels + typedef struct { int32_t charWidth; // fixed width per glyph (e.g. 8) int32_t charHeight; // e.g. 14 or 16 diff --git a/dvx/dvxWidget.h b/dvx/dvxWidget.h index 8bb6ad4..b4a02ac 100644 --- a/dvx/dvxWidget.h +++ b/dvx/dvxWidget.h @@ -448,6 +448,7 @@ typedef struct WidgetT { int32_t penSize; int32_t lastX; int32_t lastY; + void (*onMouse)(struct WidgetT *w, int32_t cx, int32_t cy, bool drag); } canvas; AnsiTermDataT *ansiTerm; @@ -680,6 +681,10 @@ void wgtCanvasSetPenColor(WidgetT *w, uint32_t color); // Set the pen size in pixels (diameter). void wgtCanvasSetPenSize(WidgetT *w, int32_t size); +// Set the mouse callback. Called on click (drag=false) and drag (drag=true) +// with canvas-relative coordinates. If NULL (default), mouse events are ignored. +void wgtCanvasSetMouseCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag)); + // Save the canvas to a PNG file. Returns 0 on success, -1 on failure. int32_t wgtCanvasSave(WidgetT *w, const char *path); diff --git a/dvx/dvxWm.c b/dvx/dvxWm.c index a07bde9..772b865 100644 --- a/dvx/dvxWm.c +++ b/dvx/dvxWm.c @@ -15,8 +15,16 @@ // Constants // ============================================================ -#define GADGET_PAD 2 -#define GADGET_INSET 2 // inset from title bar edges +#define GADGET_PAD 2 +#define GADGET_INSET 2 // inset from title bar edges +#define MENU_BAR_GAP 8 // horizontal gap between menu bar labels +#define RESIZE_BREAK_INSET 16 // distance from corner to resize groove +#define CLOSE_ICON_INSET 3 // close bar inset from gadget edges +#define MAXIMIZE_ICON_INSET 2 // maximize box inset from gadget edges +#define RESTORE_ICON_INSET 4 // restore box inset from gadget edges +#define MINIMIZE_ICON_SIZE 4 // filled square size for minimize icon +#define SB_ARROW_ROWS 4 // number of rows in scrollbar arrow glyph +#define SB_ARROW_HALF 2 // half-width of scrollbar arrow tip // ============================================================ // Title bar gadget geometry @@ -78,7 +86,7 @@ static void computeMenuBarPositions(WindowT *win, const BitmapFontT *font) { menu->barX = x; menu->barW = labelW; - x += labelW; + x += labelW + MENU_BAR_GAP; } win->menuBar->positionsDirty = false; @@ -201,6 +209,14 @@ static void drawMenuBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fon rectFill(d, ops, win->x + CHROME_BORDER_WIDTH, barY, win->w - CHROME_BORDER_WIDTH * 2, barH, colors->menuBg); + // Clip menu labels to the menu bar area + int32_t savedClipX = d->clipX; + int32_t savedClipY = d->clipY; + int32_t savedClipW = d->clipW; + int32_t savedClipH = d->clipH; + setClipRect(d, win->x + CHROME_BORDER_WIDTH, barY, + win->w - CHROME_BORDER_WIDTH * 2, barH); + // Draw each menu label for (int32_t i = 0; i < win->menuBar->menuCount; i++) { MenuT *menu = &win->menuBar->menus[i]; @@ -211,6 +227,8 @@ static void drawMenuBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fon colors->menuFg, colors->menuBg, true); } + setClipRect(d, savedClipX, savedClipY, savedClipW, savedClipH); + // Draw bottom separator line drawHLine(d, ops, win->x + CHROME_BORDER_WIDTH, barY + barH - 1, win->w - CHROME_BORDER_WIDTH * 2, colors->windowShadow); @@ -234,7 +252,7 @@ static void drawResizeBreaks(DisplayT *d, const BlitOpsT *ops, const ColorScheme uint32_t hi = colors->windowHighlight; uint32_t sh = colors->windowShadow; int32_t bw = CHROME_BORDER_WIDTH; - int32_t inset = 16; // distance from corner to groove + int32_t inset = RESIZE_BREAK_INSET; int32_t wx = win->x; int32_t wy = win->y; int32_t ww = win->w; @@ -416,23 +434,23 @@ static void drawScrollbarArrow(DisplayT *d, const BlitOpsT *ops, const ColorSche int32_t cy = y + size / 2; uint32_t fg = colors->contentFg; - // Draw a small 5-row triangle - for (int32_t i = 0; i < 4; i++) { + // Draw a small triangle + for (int32_t i = 0; i < SB_ARROW_ROWS; i++) { switch (dir) { case 0: // up - drawHLine(d, ops, cx - i, cy - 2 + i, 1 + i * 2, fg); + drawHLine(d, ops, cx - i, cy - SB_ARROW_HALF + i, 1 + i * 2, fg); break; case 1: // down - drawHLine(d, ops, cx - i, cy + 2 - i, 1 + i * 2, fg); + drawHLine(d, ops, cx - i, cy + SB_ARROW_HALF - i, 1 + i * 2, fg); break; case 2: // left - drawVLine(d, ops, cx - 2 + i, cy - i, 1 + i * 2, fg); + drawVLine(d, ops, cx - SB_ARROW_HALF + i, cy - i, 1 + i * 2, fg); break; case 3: // right - drawVLine(d, ops, cx + 2 - i, cy - i, 1 + i * 2, fg); + drawVLine(d, ops, cx + SB_ARROW_HALF - i, cy - i, 1 + i * 2, fg); break; } } @@ -456,32 +474,34 @@ static void drawTitleBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fo // Close gadget on the LEFT (system menu / close) drawTitleGadget(d, ops, colors, g.closeX, g.gadgetY, g.gadgetS); // Horizontal bar icon inside close gadget - drawHLine(d, ops, g.closeX + 3, g.gadgetY + g.gadgetS / 2, - g.gadgetS - 6, colors->contentFg); + drawHLine(d, ops, g.closeX + CLOSE_ICON_INSET, g.gadgetY + g.gadgetS / 2, + g.gadgetS - CLOSE_ICON_INSET * 2, colors->contentFg); if (g.maxX >= 0) { // Maximize/restore gadget on the far RIGHT drawTitleGadget(d, ops, colors, g.maxX, g.gadgetY, g.gadgetS); if (win->maximized) { - // Restore icon: larger box outline filling more of the gadget - int32_t bx = g.maxX + 2; - int32_t by = g.gadgetY + 2; - int32_t bs = g.gadgetS - 4; + // Restore icon: small box outline + int32_t bx = g.maxX + RESTORE_ICON_INSET; + int32_t by = g.gadgetY + RESTORE_ICON_INSET; + int32_t bs = g.gadgetS - RESTORE_ICON_INSET * 2; + rectFill(d, ops, bx, by, bs, bs, colors->buttonFace); + drawHLine(d, ops, bx, by, bs, colors->contentFg); + drawHLine(d, ops, bx, by + bs - 1, bs, colors->contentFg); + drawVLine(d, ops, bx, by, bs, colors->contentFg); + drawVLine(d, ops, bx + bs - 1, by, bs, colors->contentFg); + } else { + // Maximize icon: larger box outline filling more of the gadget + int32_t bx = g.maxX + MAXIMIZE_ICON_INSET; + int32_t by = g.gadgetY + MAXIMIZE_ICON_INSET; + int32_t bs = g.gadgetS - MAXIMIZE_ICON_INSET * 2; rectFill(d, ops, bx, by, bs, bs, colors->buttonFace); drawHLine(d, ops, bx, by, bs, colors->contentFg); drawHLine(d, ops, bx, by + 1, bs, colors->contentFg); drawHLine(d, ops, bx, by + bs - 1, bs, colors->contentFg); drawVLine(d, ops, bx, by, bs, colors->contentFg); drawVLine(d, ops, bx + bs - 1, by, bs, colors->contentFg); - } else { - // Maximize icon: small box outline - rectFill(d, ops, g.maxX + 3, g.gadgetY + 3, - g.gadgetS - 6, g.gadgetS - 6, colors->buttonFace); - drawHLine(d, ops, g.maxX + 3, g.gadgetY + 3, g.gadgetS - 6, colors->contentFg); - drawHLine(d, ops, g.maxX + 3, g.gadgetY + g.gadgetS - 4, g.gadgetS - 6, colors->contentFg); - drawVLine(d, ops, g.maxX + 3, g.gadgetY + 3, g.gadgetS - 6, colors->contentFg); - drawVLine(d, ops, g.maxX + g.gadgetS - 4, g.gadgetY + 3, g.gadgetS - 6, colors->contentFg); } } @@ -489,9 +509,8 @@ static void drawTitleBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fo if (g.minX >= 0) { drawTitleGadget(d, ops, colors, g.minX, g.gadgetY, g.gadgetS); // Small square centered in minimize gadget - int32_t miniSize = 4; - rectFill(d, ops, g.minX + (g.gadgetS - miniSize) / 2, g.gadgetY + (g.gadgetS - miniSize) / 2, - miniSize, miniSize, colors->contentFg); + rectFill(d, ops, g.minX + (g.gadgetS - MINIMIZE_ICON_SIZE) / 2, g.gadgetY + (g.gadgetS - MINIMIZE_ICON_SIZE) / 2, + MINIMIZE_ICON_SIZE, MINIMIZE_ICON_SIZE, colors->contentFg); } // Title text — centered between close gadget and minimize, truncated to fit @@ -1343,9 +1362,9 @@ int32_t wmMinimizedIconHit(const WindowStackT *stack, const DisplayT *d, int32_t static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH) { // Title bar: close gadget + padding + 1 char of title + padding - int32_t gadgetS = CHROME_TITLE_HEIGHT - 4; - int32_t gadgetPad = 2; - int32_t charW = 8; // bitmap font char width + int32_t gadgetS = CHROME_TITLE_HEIGHT - GADGET_INSET * 2; + int32_t gadgetPad = GADGET_PAD; + int32_t charW = FONT_CHAR_WIDTH; // Minimum title bar width: close gadget + 1 char + minimize gadget + padding int32_t titleMinW = gadgetPad + gadgetS + gadgetPad + charW + gadgetPad + gadgetS + gadgetPad; @@ -1357,6 +1376,16 @@ static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH) { *minW = titleMinW + CHROME_BORDER_WIDTH * 2; + // Menu bar width: ensure window is wide enough to show all menu labels + if (win->menuBar && win->menuBar->menuCount > 0) { + MenuT *last = &win->menuBar->menus[win->menuBar->menuCount - 1]; + int32_t menuW = last->barX + last->barW + CHROME_TOTAL_SIDE; + + if (menuW > *minW) { + *minW = menuW; + } + } + // Minimum height: border + title + inner border + some content + bottom chrome *minH = CHROME_TOTAL_TOP + CHROME_TOTAL_BOTTOM + 1; diff --git a/dvx/widgets/widgetButton.c b/dvx/widgets/widgetButton.c index a3a3acc..0864a5c 100644 --- a/dvx/widgets/widgetButton.c +++ b/dvx/widgets/widgetButton.c @@ -98,8 +98,8 @@ void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma int32_t textY = w->y + (w->h - font->charHeight) / 2; if (w->as.button.pressed) { - textX++; - textY++; + textX += BUTTON_PRESS_OFFSET; + textY += BUTTON_PRESS_OFFSET; } if (!w->enabled) { @@ -109,7 +109,7 @@ void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma } if (w->focused) { - int32_t off = w->as.button.pressed ? 1 : 0; - drawFocusRect(d, ops, w->x + 3 + off, w->y + 3 + off, w->w - 6, w->h - 6, fg); + int32_t off = w->as.button.pressed ? BUTTON_PRESS_OFFSET : 0; + drawFocusRect(d, ops, w->x + BUTTON_FOCUS_INSET + off, w->y + BUTTON_FOCUS_INSET + off, w->w - BUTTON_FOCUS_INSET * 2, w->h - BUTTON_FOCUS_INSET * 2, fg); } } diff --git a/dvx/widgets/widgetCanvas.c b/dvx/widgets/widgetCanvas.c index bd36cb6..70094f7 100644 --- a/dvx/widgets/widgetCanvas.c +++ b/dvx/widgets/widgetCanvas.c @@ -621,6 +621,17 @@ int32_t wgtCanvasSave(WidgetT *w, const char *path) { } +// ============================================================ +// wgtCanvasSetMouseCallback +// ============================================================ + +void wgtCanvasSetMouseCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag)) { + if (w && w->type == WidgetCanvasE) { + w->as.canvas.onMouse = cb; + } +} + + // ============================================================ // wgtCanvasSetPenColor // ============================================================ @@ -689,26 +700,25 @@ void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font) { void widgetCanvasOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) { (void)root; + + if (!hit->as.canvas.onMouse) { + return; + } + // Convert widget coords to canvas coords int32_t cx = vx - hit->x - CANVAS_BORDER; int32_t cy = vy - hit->y - CANVAS_BORDER; - if (sDrawingCanvas == hit) { - // Continuation of a drag stroke — draw line from last to current - if (hit->as.canvas.lastX >= 0) { - canvasDrawLine(hit, hit->as.canvas.lastX, hit->as.canvas.lastY, cx, cy); - } else { - canvasDrawDot(hit, cx, cy); - } - } else { - // First click — start drawing, place a dot + bool drag = (sDrawingCanvas == hit); + + if (!drag) { sDrawingCanvas = hit; - canvasDrawDot(hit, cx, cy); } hit->as.canvas.lastX = cx; hit->as.canvas.lastY = cy; + hit->as.canvas.onMouse(hit, cx, cy, drag); wgtInvalidatePaint(hit); } diff --git a/dvx/widgets/widgetEvent.c b/dvx/widgets/widgetEvent.c index 3e29c3d..5301f56 100644 --- a/dvx/widgets/widgetEvent.c +++ b/dvx/widgets/widgetEvent.c @@ -262,8 +262,8 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) { int32_t delta = x - sResizeStartX; int32_t newW = sResizeOrigW + delta; - if (newW < 20) { - newW = 20; + if (newW < LISTVIEW_MIN_COL_W) { + newW = LISTVIEW_MIN_COL_W; } if (newW != sResizeListView->as.listView->resolvedColW[sResizeCol]) { diff --git a/dvx/widgets/widgetInternal.h b/dvx/widgets/widgetInternal.h index 0b6ed38..b4711d2 100644 --- a/dvx/widgets/widgetInternal.h +++ b/dvx/widgets/widgetInternal.h @@ -61,6 +61,8 @@ extern const WidgetClassT *widgetClassTable[]; #define SEPARATOR_THICKNESS 2 #define BUTTON_PAD_H 8 #define BUTTON_PAD_V 4 +#define BUTTON_FOCUS_INSET 3 // focus rect inset from button edge +#define BUTTON_PRESS_OFFSET 1 // text/focus shift when button is pressed #define CHECKBOX_BOX_SIZE 12 #define CHECKBOX_GAP 4 #define FRAME_BEVEL_BORDER 2 @@ -75,6 +77,7 @@ extern const WidgetClassT *widgetClassTable[]; #define TAB_BORDER 2 #define LISTBOX_BORDER 2 #define LISTVIEW_BORDER 2 +#define LISTVIEW_MIN_COL_W 20 // minimum column width during resize #define TREE_INDENT 16 #define TREE_EXPAND_SIZE 9 #define TREE_ICON_GAP 4 @@ -84,6 +87,8 @@ extern const WidgetClassT *widgetClassTable[]; #define SB_MIN_THUMB 14 #define SPLITTER_BAR_W 5 #define SPLITTER_MIN_PANE 20 +#define TOOLBAR_PAD 2 // padding inside toolbar/statusbar +#define TOOLBAR_GAP 2 // gap between toolbar/statusbar children // ============================================================ // Inline helpers diff --git a/dvx/widgets/widgetLayout.c b/dvx/widgets/widgetLayout.c index 1a89f2c..054ef2b 100644 --- a/dvx/widgets/widgetLayout.c +++ b/dvx/widgets/widgetLayout.c @@ -33,8 +33,8 @@ void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font) { // Toolbar and StatusBar use tighter padding if (w->type == WidgetToolbarE || w->type == WidgetStatusBarE) { - pad = 2; - gap = 2; + pad = TOOLBAR_PAD; + gap = TOOLBAR_GAP; } for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { @@ -145,8 +145,8 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) { // Toolbar and StatusBar use tighter padding if (w->type == WidgetToolbarE || w->type == WidgetStatusBarE) { - pad = 2; - gap = 2; + pad = TOOLBAR_PAD; + gap = TOOLBAR_GAP; } int32_t innerX = w->x + pad + fb; diff --git a/dvxdemo/Makefile b/dvxdemo/Makefile deleted file mode 100644 index 7e7499b..0000000 --- a/dvxdemo/Makefile +++ /dev/null @@ -1,48 +0,0 @@ -# DVX GUI Demo Makefile for DJGPP cross-compilation - -DJGPP_PREFIX = $(HOME)/djgpp/djgpp -CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc -EXE2COFF = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/exe2coff -CWSDSTUB = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/CWSDSTUB.EXE -CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../dvx -LDFLAGS = -L../lib -ldvx -lm - -OBJDIR = ../obj/dvxdemo -BINDIR = ../bin -LIBDIR = ../lib - -SRCS = demo.c -OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) -TARGET = $(BINDIR)/demo.exe -BMPS = $(wildcard *.bmp) - -.PHONY: all clean lib - -all: lib $(TARGET) $(addprefix $(BINDIR)/,$(BMPS)) - -lib: - $(MAKE) -C ../dvx - -$(TARGET): $(OBJS) $(LIBDIR)/libdvx.a | $(BINDIR) - $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) - $(EXE2COFF) $@ - cat $(CWSDSTUB) $(BINDIR)/demo > $@ - rm -f $(BINDIR)/demo - -$(OBJDIR)/%.o: %.c | $(OBJDIR) - $(CC) $(CFLAGS) -c -o $@ $< - -$(OBJDIR): - mkdir -p $(OBJDIR) - -$(BINDIR): - mkdir -p $(BINDIR) - -$(BINDIR)/%.bmp: %.bmp | $(BINDIR) - cp $< $@ - -# Dependencies -$(OBJDIR)/demo.o: demo.c ../dvx/dvxApp.h ../dvx/dvxWidget.h - -clean: - rm -rf $(OBJDIR) $(BINDIR) diff --git a/dvxshell/Makefile b/dvxshell/Makefile index 267f01d..d905232 100644 --- a/dvxshell/Makefile +++ b/dvxshell/Makefile @@ -14,7 +14,7 @@ LIBDIR = ../lib SRCS = shellMain.c shellApp.c shellExport.c OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) -TARGET = $(BINDIR)/dvxshell.exe +TARGET = $(BINDIR)/dvx.exe .PHONY: all clean libs @@ -25,10 +25,10 @@ libs: $(MAKE) -C ../tasks $(TARGET): $(OBJS) $(LIBDIR)/libdvx.a $(LIBDIR)/libtasks.a | $(BINDIR) - $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) -Wl,-Map=$(BINDIR)/dvxshell.map + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) -Wl,-Map=$(BINDIR)/dvx.map $(EXE2COFF) $@ - cat $(CWSDSTUB) $(BINDIR)/dvxshell > $@ - rm -f $(BINDIR)/dvxshell + cat $(CWSDSTUB) $(BINDIR)/dvx > $@ + rm -f $(BINDIR)/dvx $(OBJDIR)/%.o: %.c | $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< @@ -44,4 +44,4 @@ $(OBJDIR)/shellMain.o: shellMain.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialo $(OBJDIR)/shellApp.o: shellApp.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../tasks/taskswitch.h $(OBJDIR)/shellExport.o: shellExport.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxDraw.h ../dvx/dvxVideo.h ../dvx/dvxWm.h ../tasks/taskswitch.h clean: - rm -rf $(OBJDIR) $(TARGET) + rm -f $(OBJS) $(TARGET) $(BINDIR)/dvx.map $(BINDIR)/dvx.log diff --git a/dvxshell/shellApp.c b/dvxshell/shellApp.c index 424ab41..b6fca70 100644 --- a/dvxshell/shellApp.c +++ b/dvxshell/shellApp.c @@ -182,6 +182,23 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) { app->dxeCtx.shellCtx = ctx; app->dxeCtx.appId = id; + // Derive app directory from path (everything up to last '/' or '\') + snprintf(app->dxeCtx.appDir, sizeof(app->dxeCtx.appDir), "%s", path); + + char *lastSlash = strrchr(app->dxeCtx.appDir, '/'); + char *lastBack = strrchr(app->dxeCtx.appDir, '\\'); + + if (lastBack > lastSlash) { + lastSlash = lastBack; + } + + if (lastSlash) { + *lastSlash = '\0'; + } else { + app->dxeCtx.appDir[0] = '.'; + app->dxeCtx.appDir[1] = '\0'; + } + // Launch sCurrentAppId = id; diff --git a/dvxshell/shellApp.h b/dvxshell/shellApp.h index 61dd66d..7f204e9 100644 --- a/dvxshell/shellApp.h +++ b/dvxshell/shellApp.h @@ -30,6 +30,7 @@ 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 } DxeAppContextT; // ============================================================ @@ -95,7 +96,7 @@ int32_t shellRunningAppCount(void); // Logging // ============================================================ -// Write a printf-style message to SHELL.LOG +// Write a printf-style message to DVX.LOG void shellLog(const char *fmt, ...); // ============================================================ diff --git a/dvxshell/shellDesktop.c b/dvxshell/shellDesktop.c index 61502f9..86c8e85 100644 --- a/dvxshell/shellDesktop.c +++ b/dvxshell/shellDesktop.c @@ -10,6 +10,7 @@ #include #include #include +#include // ============================================================ // Constants @@ -55,12 +56,13 @@ static void onAppButtonClick(WidgetT *w); static void onPmClose(WindowT *win); static void onPmMenu(WindowT *win, int32_t menuId); static void scanAppsDir(void); +static void scanAppsDirRecurse(const char *dirPath); static void showAboutDialog(void); static void updateStatusText(void); // Task Manager static WindowT *sTmWindow = NULL; -static WidgetT *sTmListBox = NULL; +static WidgetT *sTmListView = NULL; static void buildTaskManager(void); static void onTmClose(WindowT *win); static void onTmEndTask(WidgetT *w); @@ -181,10 +183,17 @@ static void buildTaskManager(void) { WidgetT *root = wgtInitWindow(sCtx, sTmWindow); - // List box of running apps - sTmListBox = wgtListBox(root); - sTmListBox->weight = 100; - sTmListBox->prefH = wgtPixels(160); + // List view of running apps + static const ListViewColT tmCols[] = { + { "Name", wgtPercent(50), ListViewAlignLeftE }, + { "Type", wgtPercent(25), ListViewAlignLeftE }, + { "Status", wgtPercent(25), ListViewAlignLeftE }, + }; + + sTmListView = wgtListView(root); + sTmListView->weight = 100; + sTmListView->prefH = wgtPixels(160); + wgtListViewSetColumns(sTmListView, tmCols, 3); // Button row WidgetT *btnRow = wgtHBox(root); @@ -278,8 +287,8 @@ static void onPmMenu(WindowT *win, int32_t menuId) { static void onTmClose(WindowT *win) { - sTmListBox = NULL; - sTmWindow = NULL; + sTmListView = NULL; + sTmWindow = NULL; dvxDestroyWindow(sCtx, win); } @@ -287,11 +296,11 @@ static void onTmClose(WindowT *win) { static void onTmEndTask(WidgetT *w) { (void)w; - if (!sTmListBox) { + if (!sTmListView) { return; } - int32_t sel = wgtListBoxGetSelected(sTmListBox); + int32_t sel = wgtListViewGetSelected(sTmListView); if (sel < 0) { return; @@ -320,11 +329,11 @@ static void onTmEndTask(WidgetT *w) { static void onTmSwitchTo(WidgetT *w) { (void)w; - if (!sTmListBox) { + if (!sTmListView) { return; } - int32_t sel = wgtListBoxGetSelected(sTmListBox); + int32_t sel = wgtListViewGetSelected(sTmListView); if (sel < 0) { return; @@ -363,41 +372,69 @@ static void onTmSwitchTo(WidgetT *w) { static void refreshTaskList(void) { - if (!sTmListBox) { + if (!sTmListView) { return; } - static const char *items[SHELL_MAX_APPS]; - static char names[SHELL_MAX_APPS][SHELL_APP_NAME_MAX + 16]; - int32_t count = 0; + // 3 columns per row: Name, Type, Status + static const char *cells[SHELL_MAX_APPS * 3]; + static char typeStrs[SHELL_MAX_APPS][12]; + int32_t rowCount = 0; for (int32_t i = 1; i < SHELL_MAX_APPS; i++) { ShellAppT *app = shellGetApp(i); if (app && app->state == AppStateRunningE) { - const char *state = app->hasMainLoop ? "task" : "callback"; - snprintf(names[count], sizeof(names[count]), "%s [%s]", app->name, state); - items[count] = names[count]; - count++; + int32_t base = rowCount * 3; + cells[base] = app->name; + snprintf(typeStrs[rowCount], sizeof(typeStrs[rowCount]), "%s", app->hasMainLoop ? "Task" : "Callback"); + cells[base + 1] = typeStrs[rowCount]; + cells[base + 2] = "Running"; + rowCount++; } } - wgtListBoxSetItems(sTmListBox, items, count); + wgtListViewSetData(sTmListView, cells, rowCount); } static void scanAppsDir(void) { - DIR *dir = opendir("apps"); + sDxeCount = 0; + scanAppsDirRecurse("apps"); + shellLog("Shell: found %ld app(s)", (long)sDxeCount); +} + + +static void scanAppsDirRecurse(const char *dirPath) { + DIR *dir = opendir(dirPath); if (!dir) { - shellLog("Shell: apps/ directory not found"); + if (sDxeCount == 0) { + shellLog("Shell: %s directory not found", dirPath); + } + return; } - sDxeCount = 0; struct dirent *ent; while ((ent = readdir(dir)) != NULL && sDxeCount < MAX_DXE_FILES) { + // Skip . and .. + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) { + continue; + } + + char fullPath[MAX_PATH_LEN]; + snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, ent->d_name); + + // Check if this is a directory — recurse into it + struct stat st; + + if (stat(fullPath, &st) == 0 && S_ISDIR(st.st_mode)) { + scanAppsDirRecurse(fullPath); + continue; + } + int32_t len = strlen(ent->d_name); if (len < 5) { @@ -428,12 +465,11 @@ static void scanAppsDir(void) { entry->name[0] -= 32; } - snprintf(entry->path, sizeof(entry->path), "apps/%s", ent->d_name); + snprintf(entry->path, sizeof(entry->path), "%s", fullPath); sDxeCount++; } closedir(dir); - shellLog("Shell: found %ld app(s)", (long)sDxeCount); } diff --git a/dvxshell/shellExport.c b/dvxshell/shellExport.c index 34b3e99..5e4344e 100644 --- a/dvxshell/shellExport.c +++ b/dvxshell/shellExport.c @@ -167,6 +167,8 @@ DXE_EXPORT_TABLE(shellExportTable) DXE_EXPORT(wmSetIcon) DXE_EXPORT(wmCreateMenu) DXE_EXPORT(wmFreeMenu) + DXE_EXPORT(wmUpdateContentRect) + DXE_EXPORT(wmReallocContentBuf) // dvxWidget.h — window integration DXE_EXPORT(wgtInitWindow) @@ -278,6 +280,7 @@ DXE_EXPORT_TABLE(shellExportTable) // dvxWidget.h — canvas DXE_EXPORT(wgtCanvas) DXE_EXPORT(wgtCanvasClear) + DXE_EXPORT(wgtCanvasSetMouseCallback) DXE_EXPORT(wgtCanvasSetPenColor) DXE_EXPORT(wgtCanvasSetPenSize) DXE_EXPORT(wgtCanvasSave) @@ -301,6 +304,7 @@ DXE_EXPORT_TABLE(shellExportTable) // dvxWidget.h — operations DXE_EXPORT(wgtInvalidate) DXE_EXPORT(wgtInvalidatePaint) + DXE_EXPORT(wgtSetDebugLayout) DXE_EXPORT(wgtSetText) DXE_EXPORT(wgtGetText) DXE_EXPORT(wgtSetEnabled) diff --git a/dvxshell/shellMain.c b/dvxshell/shellMain.c index a72d594..792b4ec 100644 --- a/dvxshell/shellMain.c +++ b/dvxshell/shellMain.c @@ -131,7 +131,7 @@ static void logCrash(int sig) { // ============================================================ -// shellLog — append a line to SHELL.LOG +// shellLog — append a line to DVX.LOG // ============================================================ void shellLog(const char *fmt, ...) { @@ -163,7 +163,7 @@ void shellRegisterDesktopUpdate(void (*updateFn)(void)) { // ============================================================ int main(void) { - sLogFile = fopen("shell.log", "w"); + sLogFile = fopen("dvx.log", "w"); shellLog("DVX Shell starting..."); // Initialize GUI diff --git a/tasks/Makefile b/tasks/Makefile index 545c1bc..c5a1f38 100644 --- a/tasks/Makefile +++ b/tasks/Makefile @@ -23,7 +23,9 @@ DEMO_TARGET = $(BINDIR)/tsdemo.exe .PHONY: all clean -all: $(TARGET) $(DEMO_TARGET) +all: $(TARGET) + +demo: $(DEMO_TARGET) $(TARGET): $(OBJS) | $(LIBDIR) $(AR) rcs $@ $(OBJS) @@ -52,4 +54,4 @@ $(OBJDIR)/taskswitch.o: taskswitch.c taskswitch.h thirdparty/stb_ds.h $(OBJDIR)/demo.o: demo.c taskswitch.h clean: - rm -rf $(OBJDIR) $(TARGET) $(DEMO_TARGET) + rm -f $(OBJS) $(DEMO_OBJS) $(TARGET) $(DEMO_TARGET)