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%; }
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.