From 8abe947b8b1d8f2914399489a45c1bbc4c4ce3d2 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Mon, 13 Apr 2026 22:04:09 -0500 Subject: [PATCH] Transparent image support. Image alignment in help. --- .gitattributes | 4 + Makefile | 4 + apps/dvxbasic/formrt/formrt.c | 133 +++++++++++++++++++++++++++++++++ apps/dvxbasic/ide/ideMain.c | 32 +++++++- apps/dvxbasic/runtime/vm.c | 4 +- apps/dvxbasic/runtime/vm.h | 4 +- apps/dvxhelp/dvxhelp.c | 40 +++++++--- apps/dvxhelp/hlpformat.h | 6 ++ assets/DVX Help Logo.xcf | 3 + assets/DVX Logo.xcf | 3 + assets/DVX Text.xcf | 3 + assets/help.png | 3 + core/dvxApp.c | 67 +++++++++++++++++ core/dvxApp.h | 6 ++ core/dvxDraw.c | 67 +++++++++++++++++ core/dvxDraw.h | 4 + core/sysdoc.dhs | 2 + docs/dvx_system_reference.html | 1 + sdk/include/basic/commdlg.bas | 41 ++++++++++ tools/dvxhlpc.c | 27 ++++++- widgets/image/image.h | 2 + widgets/image/widgetImage.c | 27 ++++++- 22 files changed, 462 insertions(+), 21 deletions(-) create mode 100644 assets/DVX Help Logo.xcf create mode 100644 assets/DVX Logo.xcf create mode 100644 assets/DVX Text.xcf create mode 100644 assets/help.png create mode 100644 sdk/include/basic/commdlg.bas diff --git a/.gitattributes b/.gitattributes index 718e405..7e38b59 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,5 +2,9 @@ *.BMP filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text *.JPG filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.PNG filter=lfs diff=lfs merge=lfs -text +*.xcf filter=lfs diff=lfs merge=lfs -text +*.XCF filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.ZIP filter=lfs diff=lfs merge=lfs -text diff --git a/Makefile b/Makefile index f425afe..7df99e7 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,7 @@ compile-help: apps/dvxhelp/help.dhs $(HLPC) -o bin/apps/kpunch/progman/dvxhelp.hlp \ --html docs/dvx_system_reference.html \ + -i assets \ $(SYSTEM_DHS) \ $$(find widgets -name "*.dhs" ! -path "widgets/wgtsys.dhs" | sort) $(HLPC) -o bin/apps/kpunch/dvxbasic/dvxbasic.hlp \ @@ -105,6 +106,9 @@ deploy-sdk: cp "$$h" $(SDKDIR)/include/widget/"$$wgt"/; \ done; \ done + @# BASIC include files + @mkdir -p $(SDKDIR)/include/basic + @cp sdk/include/basic/*.bas $(SDKDIR)/include/basic/ 2>/dev/null || true @# Samples and readme @cp -r sdk/samples $(SDKDIR)/ @cp sdk/readme.txt $(SDKDIR)/README.TXT diff --git a/apps/dvxbasic/formrt/formrt.c b/apps/dvxbasic/formrt/formrt.c index 54527c0..0c88078 100644 --- a/apps/dvxbasic/formrt/formrt.c +++ b/apps/dvxbasic/formrt/formrt.c @@ -2579,3 +2579,136 @@ static BasValueT zeroValue(void) { memset(&v, 0, sizeof(v)); return v; } + + +// ============================================================ +// Common dialog wrappers for DECLARE LIBRARY "commdlg" +// ============================================================ +// +// Thin wrappers around dvx dialog functions. They retrieve the +// AppContextT from the running form runtime internally, so BASIC +// programs don't need to manage context pointers. Wrappers are +// needed because: (1) dvx functions require AppContextT* as first +// arg, and (2) functions with output buffers (file paths, text) +// need C-side storage since BASIC strings can't be used as +// writable char pointers. + +const char *basFileOpen(const char *title, const char *filter) { + if (!sFormRt) { + return ""; + } + + FileFilterT filters[2]; + filters[0].label = filter; + filters[0].pattern = filter; + filters[1].label = "All Files (*.*)"; + filters[1].pattern = "*.*"; + + static char path[260]; + path[0] = '\0'; + + if (dvxFileDialog(sFormRt->ctx, title, FD_OPEN, NULL, filters, 2, path, sizeof(path))) { + return path; + } + + return ""; +} + + +const char *basFileSave(const char *title, const char *filter) { + if (!sFormRt) { + return ""; + } + + FileFilterT filters[2]; + filters[0].label = filter; + filters[0].pattern = filter; + filters[1].label = "All Files (*.*)"; + filters[1].pattern = "*.*"; + + static char path[260]; + path[0] = '\0'; + + if (dvxFileDialog(sFormRt->ctx, title, FD_SAVE, NULL, filters, 2, path, sizeof(path))) { + return path; + } + + return ""; +} + + +const char *basInputBox2(const char *title, const char *prompt, const char *defaultText) { + if (!sFormRt) { + return ""; + } + + static char buf[512]; + buf[0] = '\0'; + + if (dvxInputBox(sFormRt->ctx, title, prompt, defaultText, buf, sizeof(buf))) { + return buf; + } + + return ""; +} + + +int32_t basChoiceDialog(const char *title, const char *prompt, const char *items, int32_t defaultIdx) { + if (!sFormRt || !items) { + return -1; + } + + // Split pipe-delimited items string into an array + static char itemBuf[1024]; + snprintf(itemBuf, sizeof(itemBuf), "%s", items); + + const char *ptrs[64]; + int32_t count = 0; + char *tok = itemBuf; + + while (*tok && count < 64) { + ptrs[count++] = tok; + char *sep = strchr(tok, '|'); + + if (sep) { + *sep = '\0'; + tok = sep + 1; + } else { + break; + } + } + + if (count == 0) { + return -1; + } + + int32_t chosen = 0; + + if (dvxChoiceDialog(sFormRt->ctx, title, prompt, ptrs, count, defaultIdx, &chosen)) { + return chosen; + } + + return -1; +} + + +int32_t basIntInput(const char *title, const char *prompt, int32_t defaultVal, int32_t minVal, int32_t maxVal) { + if (!sFormRt) { + return defaultVal; + } + + int32_t result = defaultVal; + dvxIntInputBox(sFormRt->ctx, title, prompt, defaultVal, minVal, maxVal, 1, &result); + return result; +} + + +int32_t basPromptSave(const char *title) { + if (!sFormRt) { + return 2; + } + + return dvxPromptSave(sFormRt->ctx, title); +} + + diff --git a/apps/dvxbasic/ide/ideMain.c b/apps/dvxbasic/ide/ideMain.c index faacae3..90aabca 100644 --- a/apps/dvxbasic/ide/ideMain.c +++ b/apps/dvxbasic/ide/ideMain.c @@ -227,7 +227,7 @@ static void printCallback(void *ctx, const char *text, bool newline); static bool inputCallback(void *ctx, const char *prompt, char *buf, int32_t bufSize); static bool doEventsCallback(void *ctx); static void *resolveExternCallback(void *ctx, const char *libName, const char *funcName); -static BasValueT callExternCallback(void *ctx, void *funcPtr, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType); +static BasValueT callExternCallback(void *ctx, void *funcPtr, const char *libName, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType); static void runCached(void); static void runModule(BasModuleT *mod); static void onEditorChange(WidgetT *w); @@ -1328,6 +1328,31 @@ static int32_t localToConcatLine(int32_t editorLine) { // toggleBreakpointLine -- toggle breakpoint on a specific line // ============================================================ +static void clearAllBreakpoints(void) { + arrsetlen(sBreakpoints, 0); + sBreakpointCount = 0; + arrfree(sVmBreakpoints); + sVmBreakpoints = NULL; + updateBreakpointWindow(); +} + + +static void removeBreakpointsForFile(int32_t fileIdx) { + // Remove breakpoints for the given file and adjust indices for + // files above the removed one (since file indices shift down). + for (int32_t i = sBreakpointCount - 1; i >= 0; i--) { + if (sBreakpoints[i].fileIdx == fileIdx) { + arrdel(sBreakpoints, i); + } else if (sBreakpoints[i].fileIdx > fileIdx) { + sBreakpoints[i].fileIdx--; + } + } + + sBreakpointCount = (int32_t)arrlen(sBreakpoints); + updateBreakpointWindow(); +} + + static void toggleBreakpointLine(int32_t editorLine) { int32_t fileIdx = sProject.activeFileIdx; @@ -3357,6 +3382,7 @@ static void closeProject(void) { } freeProcBufs(); + clearAllBreakpoints(); // Close project window prjClose(&sProject); @@ -5357,6 +5383,7 @@ static void handleProjectCmd(int32_t cmd) { } } + removeBreakpointsForFile(rmIdx); prjRemoveFile(&sProject, rmIdx); if (sProject.activeFileIdx == rmIdx) { @@ -5927,8 +5954,9 @@ static void *resolveExternCallback(void *ctx, const char *libName, const char *f // // Supported types: Integer (int16_t passed as int32_t), Long (int32_t), // Single/Double (double), String (const char *), Boolean (int32_t). -static BasValueT callExternCallback(void *ctx, void *funcPtr, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType) { +static BasValueT callExternCallback(void *ctx, void *funcPtr, const char *libName, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType) { (void)ctx; + (void)libName; (void)funcName; // Convert BASIC values to native C values and collect pointers diff --git a/apps/dvxbasic/runtime/vm.c b/apps/dvxbasic/runtime/vm.c index 58e3dd6..615a0c7 100644 --- a/apps/dvxbasic/runtime/vm.c +++ b/apps/dvxbasic/runtime/vm.c @@ -3175,9 +3175,11 @@ BasVmResultE basVmStep(BasVmT *vm) { BasValueT result = basValInteger(0); if (vm->ext.callExtern) { + const char *libName = libNameIdx < (uint16_t)vm->module->constCount + ? vm->module->constants[libNameIdx]->data : ""; const char *funcName = funcNameIdx < (uint16_t)vm->module->constCount ? vm->module->constants[funcNameIdx]->data : ""; - result = vm->ext.callExtern(vm->ext.ctx, funcPtr, funcName, args, argCount, retType); + result = vm->ext.callExtern(vm->ext.ctx, funcPtr, libName, funcName, args, argCount, retType); } for (int32_t i = 0; i < argCount; i++) { diff --git a/apps/dvxbasic/runtime/vm.h b/apps/dvxbasic/runtime/vm.h index ea4360e..2eb0fb3 100644 --- a/apps/dvxbasic/runtime/vm.h +++ b/apps/dvxbasic/runtime/vm.h @@ -187,8 +187,10 @@ typedef void *(*BasResolveExternFnT)(void *ctx, const char *libName, const char // Call a resolved native function. funcPtr is the pointer returned // by resolveExtern. args[0..argc-1] are the BASIC arguments. // retType is the expected return type (BAS_TYPE_*). +// libName is the DECLARE LIBRARY name (e.g. "dvx"); the host may use +// it to inject hidden parameters (e.g. AppContextT for "dvx" library). // Returns the native function's return value as a BasValueT. -typedef BasValueT (*BasCallExternFnT)(void *ctx, void *funcPtr, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType); +typedef BasValueT (*BasCallExternFnT)(void *ctx, void *funcPtr, const char *libName, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType); typedef struct { BasResolveExternFnT resolveExtern; diff --git a/apps/dvxhelp/dvxhelp.c b/apps/dvxhelp/dvxhelp.c index 1ae2be6..31216a3 100644 --- a/apps/dvxhelp/dvxhelp.c +++ b/apps/dvxhelp/dvxhelp.c @@ -971,7 +971,10 @@ static void displayRecord(const HlpRecordHdrT *hdr, const char *payload) { const HlpImageRefT *imgRef = (const HlpImageRefT *)payload; uint32_t absOffset = sHeader.imagePoolOffset + imgRef->imageOffset; - // Read BMP data from file + // Save file position — buildContentWidgets reads records + // sequentially and we must not disturb its position. + long savedPos = ftell(sHlpFile); + uint8_t *bmpData = (uint8_t *)dvxMalloc(imgRef->imageSize); if (bmpData) { @@ -979,19 +982,38 @@ static void displayRecord(const HlpRecordHdrT *hdr, const char *payload) { size_t bytesRead = fread(bmpData, 1, imgRef->imageSize, sHlpFile); if (bytesRead == imgRef->imageSize) { - int32_t imgW = 0; - int32_t imgH = 0; - int32_t imgP = 0; - uint8_t *pixels = dvxLoadImageFromMemory(sAc, bmpData, (int32_t)imgRef->imageSize, &imgW, &imgH, &imgP); + int32_t imgW = 0; + int32_t imgH = 0; + int32_t imgP = 0; + bool hasAlpha = false; + uint32_t keyColor = 0; + uint8_t *pixels = dvxLoadImageAlpha(sAc, bmpData, (int32_t)imgRef->imageSize, &imgW, &imgH, &imgP, &hasAlpha, &keyColor); if (pixels) { - wgtImage(sContentBox, pixels, imgW, imgH, imgP); - // wgtImage takes ownership of the pixel data + WidgetT *parent = sContentBox; + + if (hdr->flags == HLP_IMG_CENTER || hdr->flags == HLP_IMG_RIGHT) { + WidgetT *row = wgtHBox(sContentBox); + + if (row) { + row->align = (hdr->flags == HLP_IMG_CENTER) ? AlignCenterE : AlignEndE; + parent = row; + } + } + + WidgetT *imgWgt = wgtImage(parent, pixels, imgW, imgH, imgP); + + if (imgWgt && hasAlpha) { + wgtImageSetTransparent(imgWgt, true, keyColor); + } } } dvxFree(bmpData); } + + // Restore file position for sequential record reading + fseek(sHlpFile, savedPos, SEEK_SET); } break; @@ -1134,12 +1156,8 @@ static void buildContentWidgets(void) { wgtScrollPaneScrollToTop(sContentScroll); } - // Two invalidate passes: the first does measure+layout which may change - // scrollbar visibility (altering available width). The second re-measures - // at the corrected width so heights match the actual layout. if (sContentScroll) { wgtInvalidate(sContentScroll); - wgtInvalidate(sContentScroll); } } diff --git a/apps/dvxhelp/hlpformat.h b/apps/dvxhelp/hlpformat.h index 3ad4c93..a8b20ab 100644 --- a/apps/dvxhelp/hlpformat.h +++ b/apps/dvxhelp/hlpformat.h @@ -43,6 +43,12 @@ #define HLP_NOTE_TIP 0x01 #define HLP_NOTE_WARNING 0x02 +// Image alignment flags (HlpRecordHdrT.flags when type == HLP_REC_IMAGE) +// 0x00 = left (default, backward compatible with older .hlp files) +#define HLP_IMG_LEFT 0x00 +#define HLP_IMG_CENTER 0x01 +#define HLP_IMG_RIGHT 0x02 + // --------------------------------------------------------------------------- // TOC entry flags diff --git a/assets/DVX Help Logo.xcf b/assets/DVX Help Logo.xcf new file mode 100644 index 0000000..2a539a0 --- /dev/null +++ b/assets/DVX Help Logo.xcf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbdecc2a7945c83ec3948258ceb64ca4d493e1819b5929fb0c7419a7e645b55e +size 11176 diff --git a/assets/DVX Logo.xcf b/assets/DVX Logo.xcf new file mode 100644 index 0000000..459e04d --- /dev/null +++ b/assets/DVX Logo.xcf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f59a35fa49eb940b1e05b15db9420aed9fd6a7e03832516b90fdf1e314e5e981 +size 65028 diff --git a/assets/DVX Text.xcf b/assets/DVX Text.xcf new file mode 100644 index 0000000..2af6ce3 --- /dev/null +++ b/assets/DVX Text.xcf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fafaaf80c4206d18024337b54e0617f0a02ec1984a8230f33f7112b6b9f1a130 +size 24994 diff --git a/assets/help.png b/assets/help.png new file mode 100644 index 0000000..fd695ce --- /dev/null +++ b/assets/help.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:981b735d53aa99a96002a6630db4b7e285150242db070769d1bbf5d5b444cabe +size 3389 diff --git a/core/dvxApp.c b/core/dvxApp.c index 3ed5f55..aeefc28 100644 --- a/core/dvxApp.c +++ b/core/dvxApp.c @@ -4491,6 +4491,73 @@ uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int } +// ============================================================ +// dvxLoadImageAlpha +// ============================================================ + +uint8_t *dvxLoadImageAlpha(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch, bool *outHasAlpha, uint32_t *outKeyColor) { + if (!ctx || !data || dataLen <= 0) { + return NULL; + } + + const DisplayT *d = &ctx->display; + + int imgW; + int imgH; + int channels; + uint8_t *rgba = stbi_load_from_memory(data, dataLen, &imgW, &imgH, &channels, 4); + + if (!rgba) { + return NULL; + } + + int32_t bpp = d->format.bytesPerPixel; + int32_t pitch = imgW * bpp; + uint32_t keyColor = packColor(d, 255, 0, 255); // magenta + bool hasAlpha = false; + uint8_t *buf = (uint8_t *)malloc(pitch * imgH); + + if (!buf) { + stbi_image_free(rgba); + return NULL; + } + + for (int32_t y = 0; y < imgH; y++) { + for (int32_t x = 0; x < imgW; x++) { + const uint8_t *src = rgba + (y * imgW + x) * 4; + uint32_t color; + + if (src[3] < 128) { + color = keyColor; + hasAlpha = true; + } else { + color = packColor(d, src[0], src[1], src[2]); + } + + uint8_t *dst = buf + 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(rgba); + + if (outW) { *outW = imgW; } + if (outH) { *outH = imgH; } + if (outPitch) { *outPitch = pitch; } + if (outHasAlpha) { *outHasAlpha = hasAlpha; } + if (outKeyColor) { *outKeyColor = keyColor; } + + return buf; +} + + // ============================================================ // dvxLoadTheme // ============================================================ diff --git a/core/dvxApp.h b/core/dvxApp.h index 465d29a..d15ac10 100644 --- a/core/dvxApp.h +++ b/core/dvxApp.h @@ -309,6 +309,12 @@ uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, int32_t *outW, i // format as dvxLoadImage. Caller must free the result with dvxFreeImage. uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch); +// Load an image with alpha transparency support. Pixels with alpha < 128 +// are replaced with the key color (packed magenta). If any transparent +// pixels were found, *outKeyColor receives the packed key color and +// *outHasAlpha is set to true. Caller must free with dvxFreeImage. +uint8_t *dvxLoadImageAlpha(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch, bool *outHasAlpha, uint32_t *outKeyColor); + // Free a pixel buffer returned by dvxLoadImage. void dvxFreeImage(uint8_t *data); diff --git a/core/dvxDraw.c b/core/dvxDraw.c index 6bb2a62..4a8bda8 100644 --- a/core/dvxDraw.c +++ b/core/dvxDraw.c @@ -1348,6 +1348,73 @@ void rectCopyGrayscale(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t d } +// ============================================================ +// rectCopyTransparent +// ============================================================ +// +// Per-pixel color-key blit. Pixels matching keyColor are skipped, +// leaving the destination unchanged (background shows through). +// Used for PNG images with alpha transparency converted to key color. + +void rectCopyTransparent(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h, uint32_t keyColor) { + int32_t bpp = ops->bytesPerPixel; + + int32_t origDstX = dstX; + int32_t origDstY = dstY; + + clipRect(d, &dstX, &dstY, &w, &h); + + if (__builtin_expect(w <= 0 || h <= 0, 0)) { + return; + } + + srcX += dstX - origDstX; + srcY += dstY - origDstY; + + for (int32_t row = 0; row < h; row++) { + const uint8_t *src = srcBuf + (srcY + row) * srcPitch + srcX * bpp; + uint8_t *dst = d->backBuf + (dstY + row) * d->pitch + dstX * bpp; + + if (bpp == 1) { + uint8_t key8 = (uint8_t)keyColor; + + for (int32_t col = 0; col < w; col++) { + if (*src != key8) { + *dst = *src; + } + + src++; + dst++; + } + } else if (bpp == 2) { + uint16_t key16 = (uint16_t)keyColor; + + for (int32_t col = 0; col < w; col++) { + uint16_t px = *(const uint16_t *)src; + + if (px != key16) { + *(uint16_t *)dst = px; + } + + src += 2; + dst += 2; + } + } else { + for (int32_t col = 0; col < w; col++) { + uint32_t px = *(const uint32_t *)src; + + if (px != keyColor) { + *(uint32_t *)dst = px; + } + + src += 4; + dst += 4; + } + } + } +} + + // ============================================================ // rectFill // ============================================================ diff --git a/core/dvxDraw.h b/core/dvxDraw.h index 080d43e..47ba1e4 100644 --- a/core/dvxDraw.h +++ b/core/dvxDraw.h @@ -37,6 +37,10 @@ void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, cons // luminance (0.299R + 0.587G + 0.114B) for a disabled/grayed appearance. void rectCopyGrayscale(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h); +// Copy with color-key transparency. Pixels matching keyColor are skipped, +// letting the destination (background) show through. +void rectCopyTransparent(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h, uint32_t keyColor); + // Draw a beveled frame. The bevel is drawn as overlapping horizontal and // vertical spans -- top/left in highlight color, bottom/right in shadow. // The face color fills the interior if non-zero. diff --git a/core/sysdoc.dhs b/core/sysdoc.dhs index d880160..fd21ea1 100644 --- a/core/sysdoc.dhs +++ b/core/sysdoc.dhs @@ -5,4 +5,6 @@ .h1 Welcome! +.image help.png center + DVX (DOS Visual eXecutive) is a graphical user interface environment for DOS, designed for 486-class hardware and above. This help file covers the system architecture, core API, libraries, and widget toolkit. diff --git a/docs/dvx_system_reference.html b/docs/dvx_system_reference.html index 30c6df7..301e569 100644 --- a/docs/dvx_system_reference.html +++ b/docs/dvx_system_reference.html @@ -764,6 +764,7 @@ img { max-width: 100%; }

Welcome!

Welcome!

+

help.png

DVX (DOS Visual eXecutive) is a graphical user interface environment for DOS, designed for 486-class hardware and above. This help file covers the system architecture, core API, libraries, and widget toolkit.

diff --git a/sdk/include/basic/commdlg.bas b/sdk/include/basic/commdlg.bas new file mode 100644 index 0000000..0a53de6 --- /dev/null +++ b/sdk/include/basic/commdlg.bas @@ -0,0 +1,41 @@ +' commdlg.bas -- Common Dialog Library for DVX BASIC +' +' Provides file dialogs, input dialogs, and other common UI dialogs. +' Wrappers handle the AppContextT parameter internally so BASIC +' programs just pass the visible arguments. +' +' Usage: +' '$INCLUDE: 'commdlg.bas' +' +' DIM path AS STRING +' path = basFileOpen("Open Image", "*.bmp") +' IF path <> "" THEN +' ' ... use the file ... +' END IF + +DECLARE LIBRARY "basrt" + ' Show a file Open dialog. Returns selected path, or "" if cancelled. + ' filter$ is a DOS wildcard (e.g. "*.bmp", "*.txt"). + DECLARE FUNCTION basFileOpen(BYVAL title AS STRING, BYVAL filter AS STRING) AS STRING + + ' Show a file Save dialog. Returns selected path, or "" if cancelled. + DECLARE FUNCTION basFileSave(BYVAL title AS STRING, BYVAL filter AS STRING) AS STRING + + ' Show a modal text input box. Returns entered text, or "" if cancelled. + DECLARE FUNCTION basInputBox2(BYVAL title AS STRING, BYVAL prompt AS STRING, BYVAL defaultText AS STRING) AS STRING + + ' Show a choice dialog with a listbox. items$ is pipe-delimited + ' (e.g. "Red|Green|Blue"). Returns chosen index (0-based), or -1. + DECLARE FUNCTION basChoiceDialog(BYVAL title AS STRING, BYVAL prompt AS STRING, BYVAL items AS STRING, BYVAL defaultIdx AS INTEGER) AS INTEGER + + ' Show a modal integer input box with spinner. Returns the entered value. + DECLARE FUNCTION basIntInput(BYVAL title AS STRING, BYVAL prompt AS STRING, BYVAL defaultVal AS INTEGER, BYVAL minVal AS INTEGER, BYVAL maxVal AS INTEGER) AS INTEGER + + ' Show a "Save changes?" prompt with Yes/No/Cancel buttons. + DECLARE FUNCTION basPromptSave(BYVAL title AS STRING) AS INTEGER +END DECLARE + +' Return value constants for basPromptSave +CONST DVX_SAVE_YES = 0 +CONST DVX_SAVE_NO = 1 +CONST DVX_SAVE_CANCEL = 2 diff --git a/tools/dvxhlpc.c b/tools/dvxhlpc.c index b28f9da..a965b94 100644 --- a/tools/dvxhlpc.c +++ b/tools/dvxhlpc.c @@ -533,16 +533,39 @@ static void parseDirective(const char *line, TopicT **curTopic, bool *inList, bo emitError(".image requires a filename"); return; } - // Trim trailing whitespace + + // Parse: .image filename [left|center|right] char imgFile[260]; snprintf(imgFile, sizeof(imgFile), "%s", rest); imgFile[sizeof(imgFile) - 1] = '\0'; + + uint8_t alignFlags = HLP_IMG_LEFT; + + // Split off optional alignment keyword after filename + char *space = strchr(imgFile, ' '); + + if (space) { + *space = '\0'; + char *align = space + 1; + + while (*align == ' ') { align++; } + + if (strcasecmp(align, "center") == 0) { + alignFlags = HLP_IMG_CENTER; + } else if (strcasecmp(align, "right") == 0) { + alignFlags = HLP_IMG_RIGHT; + } + } + + // Trim trailing whitespace from filename int32_t len = strlen(imgFile); + while (len > 0 && isspace(imgFile[len - 1])) { imgFile[--len] = '\0'; } + addImageRef(imgFile); - addRecord(*curTopic, HLP_REC_IMAGE, 0, imgFile, strlen(imgFile)); + addRecord(*curTopic, HLP_REC_IMAGE, alignFlags, imgFile, strlen(imgFile)); } else if (strcmp(directive, "link") == 0) { // .link diff --git a/widgets/image/image.h b/widgets/image/image.h index d695098..85d4530 100644 --- a/widgets/image/image.h +++ b/widgets/image/image.h @@ -9,6 +9,7 @@ typedef struct { WidgetT *(*fromFile)(WidgetT *parent, const char *path); void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch); void (*loadFile)(WidgetT *w, const char *path); + void (*setTransparent)(WidgetT *w, bool hasTransparency, uint32_t keyColor); } ImageApiT; static inline const ImageApiT *dvxImageApi(void) { @@ -21,5 +22,6 @@ static inline const ImageApiT *dvxImageApi(void) { #define wgtImageFromFile(parent, path) dvxImageApi()->fromFile(parent, path) #define wgtImageSetData(w, data, imgW, imgH, pitch) dvxImageApi()->setData(w, data, imgW, imgH, pitch) #define wgtImageLoadFile(w, path) dvxImageApi()->loadFile(w, path) +#define wgtImageSetTransparent(w, has, key) dvxImageApi()->setTransparent(w, has, key) #endif // IMAGE_H diff --git a/widgets/image/widgetImage.c b/widgets/image/widgetImage.c index 2639a1c..994b07b 100644 --- a/widgets/image/widgetImage.c +++ b/widgets/image/widgetImage.c @@ -28,6 +28,8 @@ typedef struct { int32_t imgH; int32_t imgPitch; bool pressed; + bool hasTransparency; + uint32_t keyColor; } ImageDataT; @@ -109,9 +111,15 @@ void widgetImagePaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const Bit } if (w->enabled) { - rectCopy(disp, ops, dx, dy, - d->pixelData, d->imgPitch, - 0, 0, imgW, imgH); + if (d->hasTransparency) { + rectCopyTransparent(disp, ops, dx, dy, + d->pixelData, d->imgPitch, + 0, 0, imgW, imgH, d->keyColor); + } else { + rectCopy(disp, ops, dx, dy, + d->pixelData, d->imgPitch, + 0, 0, imgW, imgH); + } } else { if (!d->grayData) { int32_t bufSize = d->imgPitch * d->imgH; @@ -227,6 +235,15 @@ void wgtImageSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, } +void wgtImageSetTransparent(WidgetT *w, bool hasTransparency, uint32_t keyColor) { + VALIDATE_WIDGET_VOID(w, sTypeId); + ImageDataT *d = (ImageDataT *)w->data; + d->hasTransparency = hasTransparency; + d->keyColor = keyColor; + wgtInvalidatePaint(w); +} + + // ============================================================ // BASIC-facing accessors // ============================================================ @@ -281,11 +298,13 @@ static const struct { WidgetT *(*fromFile)(WidgetT *parent, const char *path); void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch); void (*loadFile)(WidgetT *w, const char *path); + void (*setTransparent)(WidgetT *w, bool hasTransparency, uint32_t keyColor); } sApi = { .create = wgtImage, .fromFile = wgtImageFromFile, .setData = wgtImageSetData, - .loadFile = wgtImageLoadFile + .loadFile = wgtImageLoadFile, + .setTransparent = wgtImageSetTransparent }; static const WgtPropDescT sProps[] = {