Transparent image support. Image alignment in help.

This commit is contained in:
Scott Duensing 2026-04-13 22:04:09 -05:00
parent 3db721fdc0
commit 8abe947b8b
22 changed files with 462 additions and 21 deletions

4
.gitattributes vendored
View file

@ -2,5 +2,9 @@
*.BMP filter=lfs diff=lfs merge=lfs -text *.BMP filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text *.jpg 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
*.ZIP filter=lfs diff=lfs merge=lfs -text *.ZIP filter=lfs diff=lfs merge=lfs -text

View file

@ -66,6 +66,7 @@ compile-help:
apps/dvxhelp/help.dhs apps/dvxhelp/help.dhs
$(HLPC) -o bin/apps/kpunch/progman/dvxhelp.hlp \ $(HLPC) -o bin/apps/kpunch/progman/dvxhelp.hlp \
--html docs/dvx_system_reference.html \ --html docs/dvx_system_reference.html \
-i assets \
$(SYSTEM_DHS) \ $(SYSTEM_DHS) \
$$(find widgets -name "*.dhs" ! -path "widgets/wgtsys.dhs" | sort) $$(find widgets -name "*.dhs" ! -path "widgets/wgtsys.dhs" | sort)
$(HLPC) -o bin/apps/kpunch/dvxbasic/dvxbasic.hlp \ $(HLPC) -o bin/apps/kpunch/dvxbasic/dvxbasic.hlp \
@ -105,6 +106,9 @@ deploy-sdk:
cp "$$h" $(SDKDIR)/include/widget/"$$wgt"/; \ cp "$$h" $(SDKDIR)/include/widget/"$$wgt"/; \
done; \ done; \
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 @# Samples and readme
@cp -r sdk/samples $(SDKDIR)/ @cp -r sdk/samples $(SDKDIR)/
@cp sdk/readme.txt $(SDKDIR)/README.TXT @cp sdk/readme.txt $(SDKDIR)/README.TXT

View file

@ -2579,3 +2579,136 @@ static BasValueT zeroValue(void) {
memset(&v, 0, sizeof(v)); memset(&v, 0, sizeof(v));
return 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);
}

View file

@ -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 inputCallback(void *ctx, const char *prompt, char *buf, int32_t bufSize);
static bool doEventsCallback(void *ctx); static bool doEventsCallback(void *ctx);
static void *resolveExternCallback(void *ctx, const char *libName, const char *funcName); 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 runCached(void);
static void runModule(BasModuleT *mod); static void runModule(BasModuleT *mod);
static void onEditorChange(WidgetT *w); static void onEditorChange(WidgetT *w);
@ -1328,6 +1328,31 @@ static int32_t localToConcatLine(int32_t editorLine) {
// toggleBreakpointLine -- toggle breakpoint on a specific line // 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) { static void toggleBreakpointLine(int32_t editorLine) {
int32_t fileIdx = sProject.activeFileIdx; int32_t fileIdx = sProject.activeFileIdx;
@ -3357,6 +3382,7 @@ static void closeProject(void) {
} }
freeProcBufs(); freeProcBufs();
clearAllBreakpoints();
// Close project window // Close project window
prjClose(&sProject); prjClose(&sProject);
@ -5357,6 +5383,7 @@ static void handleProjectCmd(int32_t cmd) {
} }
} }
removeBreakpointsForFile(rmIdx);
prjRemoveFile(&sProject, rmIdx); prjRemoveFile(&sProject, rmIdx);
if (sProject.activeFileIdx == 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), // Supported types: Integer (int16_t passed as int32_t), Long (int32_t),
// Single/Double (double), String (const char *), Boolean (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)ctx;
(void)libName;
(void)funcName; (void)funcName;
// Convert BASIC values to native C values and collect pointers // Convert BASIC values to native C values and collect pointers

View file

@ -3175,9 +3175,11 @@ BasVmResultE basVmStep(BasVmT *vm) {
BasValueT result = basValInteger(0); BasValueT result = basValInteger(0);
if (vm->ext.callExtern) { 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 const char *funcName = funcNameIdx < (uint16_t)vm->module->constCount
? vm->module->constants[funcNameIdx]->data : ""; ? 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++) { for (int32_t i = 0; i < argCount; i++) {

View file

@ -187,8 +187,10 @@ typedef void *(*BasResolveExternFnT)(void *ctx, const char *libName, const char
// Call a resolved native function. funcPtr is the pointer returned // Call a resolved native function. funcPtr is the pointer returned
// by resolveExtern. args[0..argc-1] are the BASIC arguments. // by resolveExtern. args[0..argc-1] are the BASIC arguments.
// retType is the expected return type (BAS_TYPE_*). // 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. // 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 { typedef struct {
BasResolveExternFnT resolveExtern; BasResolveExternFnT resolveExtern;

View file

@ -971,7 +971,10 @@ static void displayRecord(const HlpRecordHdrT *hdr, const char *payload) {
const HlpImageRefT *imgRef = (const HlpImageRefT *)payload; const HlpImageRefT *imgRef = (const HlpImageRefT *)payload;
uint32_t absOffset = sHeader.imagePoolOffset + imgRef->imageOffset; 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); uint8_t *bmpData = (uint8_t *)dvxMalloc(imgRef->imageSize);
if (bmpData) { if (bmpData) {
@ -979,19 +982,38 @@ static void displayRecord(const HlpRecordHdrT *hdr, const char *payload) {
size_t bytesRead = fread(bmpData, 1, imgRef->imageSize, sHlpFile); size_t bytesRead = fread(bmpData, 1, imgRef->imageSize, sHlpFile);
if (bytesRead == imgRef->imageSize) { if (bytesRead == imgRef->imageSize) {
int32_t imgW = 0; int32_t imgW = 0;
int32_t imgH = 0; int32_t imgH = 0;
int32_t imgP = 0; int32_t imgP = 0;
uint8_t *pixels = dvxLoadImageFromMemory(sAc, bmpData, (int32_t)imgRef->imageSize, &imgW, &imgH, &imgP); bool hasAlpha = false;
uint32_t keyColor = 0;
uint8_t *pixels = dvxLoadImageAlpha(sAc, bmpData, (int32_t)imgRef->imageSize, &imgW, &imgH, &imgP, &hasAlpha, &keyColor);
if (pixels) { if (pixels) {
wgtImage(sContentBox, pixels, imgW, imgH, imgP); WidgetT *parent = sContentBox;
// wgtImage takes ownership of the pixel data
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); dvxFree(bmpData);
} }
// Restore file position for sequential record reading
fseek(sHlpFile, savedPos, SEEK_SET);
} }
break; break;
@ -1134,12 +1156,8 @@ static void buildContentWidgets(void) {
wgtScrollPaneScrollToTop(sContentScroll); 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) { if (sContentScroll) {
wgtInvalidate(sContentScroll); wgtInvalidate(sContentScroll);
wgtInvalidate(sContentScroll);
} }
} }

View file

@ -43,6 +43,12 @@
#define HLP_NOTE_TIP 0x01 #define HLP_NOTE_TIP 0x01
#define HLP_NOTE_WARNING 0x02 #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 // TOC entry flags

BIN
assets/DVX Help Logo.xcf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/DVX Logo.xcf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/DVX Text.xcf (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/help.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -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 // dvxLoadTheme
// ============================================================ // ============================================================

View file

@ -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. // 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); 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. // Free a pixel buffer returned by dvxLoadImage.
void dvxFreeImage(uint8_t *data); void dvxFreeImage(uint8_t *data);

View file

@ -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 // rectFill
// ============================================================ // ============================================================

View file

@ -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. // 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); 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 // Draw a beveled frame. The bevel is drawn as overlapping horizontal and
// vertical spans -- top/left in highlight color, bottom/right in shadow. // vertical spans -- top/left in highlight color, bottom/right in shadow.
// The face color fills the interior if non-zero. // The face color fills the interior if non-zero.

View file

@ -5,4 +5,6 @@
.h1 Welcome! .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. 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.

View file

@ -764,6 +764,7 @@ img { max-width: 100%; }
<div class="topic" id="sys.welcome"> <div class="topic" id="sys.welcome">
<h1>Welcome!</h1> <h1>Welcome!</h1>
<h2>Welcome!</h2> <h2>Welcome!</h2>
<p><img src="data:image/bmp;base64,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" alt="help.png"></p>
<p>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.</p> <p>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.</p>
</div> </div>
<div class="topic" id="arch.overview"> <div class="topic" id="arch.overview">

View file

@ -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

View file

@ -533,16 +533,39 @@ static void parseDirective(const char *line, TopicT **curTopic, bool *inList, bo
emitError(".image requires a filename"); emitError(".image requires a filename");
return; return;
} }
// Trim trailing whitespace
// Parse: .image filename [left|center|right]
char imgFile[260]; char imgFile[260];
snprintf(imgFile, sizeof(imgFile), "%s", rest); snprintf(imgFile, sizeof(imgFile), "%s", rest);
imgFile[sizeof(imgFile) - 1] = '\0'; 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); int32_t len = strlen(imgFile);
while (len > 0 && isspace(imgFile[len - 1])) { while (len > 0 && isspace(imgFile[len - 1])) {
imgFile[--len] = '\0'; imgFile[--len] = '\0';
} }
addImageRef(imgFile); 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) { } else if (strcmp(directive, "link") == 0) {
// .link <topic-id> <display text> // .link <topic-id> <display text>

View file

@ -9,6 +9,7 @@ typedef struct {
WidgetT *(*fromFile)(WidgetT *parent, const char *path); WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch); void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
void (*loadFile)(WidgetT *w, const char *path); void (*loadFile)(WidgetT *w, const char *path);
void (*setTransparent)(WidgetT *w, bool hasTransparency, uint32_t keyColor);
} ImageApiT; } ImageApiT;
static inline const ImageApiT *dvxImageApi(void) { 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 wgtImageFromFile(parent, path) dvxImageApi()->fromFile(parent, path)
#define wgtImageSetData(w, data, imgW, imgH, pitch) dvxImageApi()->setData(w, data, imgW, imgH, pitch) #define wgtImageSetData(w, data, imgW, imgH, pitch) dvxImageApi()->setData(w, data, imgW, imgH, pitch)
#define wgtImageLoadFile(w, path) dvxImageApi()->loadFile(w, path) #define wgtImageLoadFile(w, path) dvxImageApi()->loadFile(w, path)
#define wgtImageSetTransparent(w, has, key) dvxImageApi()->setTransparent(w, has, key)
#endif // IMAGE_H #endif // IMAGE_H

View file

@ -28,6 +28,8 @@ typedef struct {
int32_t imgH; int32_t imgH;
int32_t imgPitch; int32_t imgPitch;
bool pressed; bool pressed;
bool hasTransparency;
uint32_t keyColor;
} ImageDataT; } ImageDataT;
@ -109,9 +111,15 @@ void widgetImagePaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const Bit
} }
if (w->enabled) { if (w->enabled) {
rectCopy(disp, ops, dx, dy, if (d->hasTransparency) {
d->pixelData, d->imgPitch, rectCopyTransparent(disp, ops, dx, dy,
0, 0, imgW, imgH); 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 { } else {
if (!d->grayData) { if (!d->grayData) {
int32_t bufSize = d->imgPitch * d->imgH; 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 // BASIC-facing accessors
// ============================================================ // ============================================================
@ -281,11 +298,13 @@ static const struct {
WidgetT *(*fromFile)(WidgetT *parent, const char *path); WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch); void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
void (*loadFile)(WidgetT *w, const char *path); void (*loadFile)(WidgetT *w, const char *path);
void (*setTransparent)(WidgetT *w, bool hasTransparency, uint32_t keyColor);
} sApi = { } sApi = {
.create = wgtImage, .create = wgtImage,
.fromFile = wgtImageFromFile, .fromFile = wgtImageFromFile,
.setData = wgtImageSetData, .setData = wgtImageSetData,
.loadFile = wgtImageLoadFile .loadFile = wgtImageLoadFile,
.setTransparent = wgtImageSetTransparent
}; };
static const WgtPropDescT sProps[] = { static const WgtPropDescT sProps[] = {