Tons of work on the form designer and widget system to support it.

This commit is contained in:
Scott Duensing 2026-03-29 21:06:29 -05:00
parent 59bc2b5ed3
commit d094205ed0
138 changed files with 3251 additions and 1202 deletions

View file

@ -3,9 +3,9 @@
# Builds the full DVX stack: core library, task switcher,
# bootstrap loader, text help library, widgets, shell, and apps.
.PHONY: all clean core tasks loader texthelp listhelp widgets shell taskmgr serial dvxbasic apps tools
.PHONY: all clean core tasks loader texthelp listhelp widgets shell taskmgr serial apps tools
all: core tasks loader texthelp listhelp widgets shell taskmgr serial dvxbasic apps tools
all: core tasks loader texthelp listhelp tools widgets shell taskmgr serial apps
core:
$(MAKE) -C core
@ -34,9 +34,6 @@ taskmgr: shell
serial: core tasks
$(MAKE) -C serial
dvxbasic: core tasks shell tools
$(MAKE) -C dvxbasic
tools:
$(MAKE) -C tools
@ -53,7 +50,6 @@ clean:
$(MAKE) -C shell clean
$(MAKE) -C taskmgr clean
$(MAKE) -C serial clean
$(MAKE) -C dvxbasic clean
$(MAKE) -C apps clean
$(MAKE) -C tools clean
-rmdir obj 2>/dev/null

View file

@ -13,9 +13,12 @@ DVXRES = ../bin/dvxres
# App definitions: each is a subdir with a single .c file
APPS = progman notepad clock dvxdemo cpanel imgview
.PHONY: all clean $(APPS)
.PHONY: all clean $(APPS) dvxbasic
all: $(APPS)
all: $(APPS) dvxbasic
dvxbasic:
$(MAKE) -C dvxbasic
cpanel: $(BINDIR)/cpanel/cpanel.app
imgview: $(BINDIR)/imgview/imgview.app
@ -105,3 +108,4 @@ clean:
rm -f $(BINDIR)/notepad/notepad.app
rm -f $(BINDIR)/clock/clock.app
rm -f $(BINDIR)/dvxdemo/dvxdemo.app $(addprefix $(BINDIR)/dvxdemo/,$(DVXDEMO_BMPS))
$(MAKE) -C dvxbasic clean

View file

@ -203,10 +203,8 @@ int32_t appMain(DxeAppContextT *ctx) {
sWin->onClose = onClose;
sWin->onPaint = onPaint;
// Initial paint -- dvxInvalidateWindow calls onPaint automatically
dvxInvalidateWindow(ac, sWin);
// Main loop: check if the second has changed, repaint if so, then yield.
// First paint happens automatically on the next dvxUpdate frame.
// tsYield() transfers control back to the shell's task scheduler.
// On a 486, time() resolution is 1 second, so we yield many times per
// second between actual updates -- this keeps CPU usage near zero.

View file

@ -12,12 +12,12 @@
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
CFLAGS = -O2 -Wall -Wextra -Wno-type-limits -Wno-sign-compare -march=i486 -mtune=i586 -I../core -I../core/platform -I../widgets -I../shell -I../tasks -I../core/thirdparty -I.
CFLAGS = -O2 -Wall -Wextra -Wno-type-limits -Wno-sign-compare -march=i486 -mtune=i586 -I../../core -I../../core/platform -I../../widgets -I../../shell -I../../tasks -I../../core/thirdparty -I.
OBJDIR = ../obj/dvxbasic
LIBSDIR = ../bin/libs
APPDIR = ../bin/apps/dvxbasic
DVXRES = ../bin/dvxres
OBJDIR = ../../obj/dvxbasic
LIBSDIR = ../../bin/libs
APPDIR = ../../bin/apps/dvxbasic
DVXRES = ../../bin/dvxres
SAMPLES = samples/hello.bas samples/formtest.bas samples/clickme.bas samples/clickme.frm samples/input.bas
# Runtime library objects (VM + values)
@ -75,7 +75,7 @@ $(RT_TARGET): $(RT_OBJS) | $(LIBSDIR)
-U $(RT_OBJS)
mv $(LIBSDIR)/basrt.dxe $@
$(LIBSDIR)/basrt.dep: ../config/basrt.dep | $(LIBSDIR)
$(LIBSDIR)/basrt.dep: ../../config/basrt.dep | $(LIBSDIR)
sed 's/$$/\r/' $< > $@
# IDE app DXE (compiler linked in, runtime from basrt.lib)

View file

@ -23,7 +23,6 @@
#define DEFAULT_FORM_W 400
#define DEFAULT_FORM_H 300
#define DEFAULT_CREATE_ARG 256 // default maxLen/interval for widget creation
#define MAX_EVENT_NAME_LEN 128
#define MAX_LISTBOX_ITEMS 256
#define MAX_FRM_LINE_LEN 512
@ -479,15 +478,6 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
return NULL;
}
WidgetT *contentBox = wgtVBox(root);
if (!contentBox) {
dvxDestroyWindow(rt->ctx, win);
return NULL;
}
contentBox->weight = 100;
BasFormT *form = &rt->forms[rt->formCount++];
memset(form, 0, sizeof(*form));
snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName);
@ -495,7 +485,7 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
win->onResize = onFormResize;
form->window = win;
form->root = root;
form->contentBox = contentBox;
form->contentBox = NULL; // created lazily after Layout property is known
form->ctx = rt->ctx;
form->vm = rt->vm;
form->module = rt->module;
@ -600,7 +590,8 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
return NULL;
}
parentStack[0] = form->contentBox;
// contentBox is created lazily (see below) after
// form-level properties like Layout are parsed.
nestDepth = 1;
current = NULL;
@ -608,6 +599,17 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
isContainer[blockDepth++] = true;
}
} else if (form && nestDepth > 0) {
// Create the content box on first control if not yet done
if (!form->contentBox && form->root) {
if (form->frmHBox) {
form->contentBox = wgtHBox(form->root);
} else {
form->contentBox = wgtVBox(form->root);
}
form->contentBox->weight = 100;
parentStack[0] = form->contentBox;
}
WidgetT *parent = parentStack[nestDepth - 1];
const char *wgtTypeName = resolveTypeName(typeName);
@ -628,6 +630,7 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
current = &form->controls[form->controlCount++];
memset(current, 0, sizeof(*current));
snprintf(current->name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
snprintf(current->typeName, BAS_MAX_CTRL_NAME, "%s", typeName);
current->widget = widget;
current->form = form;
current->iface = wgtGetIface(wgtTypeName);
@ -641,8 +644,10 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
}
// Track block type for End handling
if (strcasecmp(typeName, "Frame") == 0 && nestDepth < MAX_FRM_NESTING) {
// Frame is a container -- children go inside it
const WgtIfaceT *ctrlIface = wgtGetIface(wgtTypeName);
bool isCtrlContainer = ctrlIface && ctrlIface->isContainer;
if (isCtrlContainer && nestDepth < MAX_FRM_NESTING) {
parentStack[nestDepth++] = widget;
if (blockDepth < MAX_FRM_NESTING) {
@ -700,8 +705,7 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
basFormRtSetProp(rt, current, key, val);
basValRelease(&val);
} else if (nestDepth > 0) {
// Form-level property
if (strcasecmp(key, "Caption") == 0) {
// Form-level property -- strip quotes from string values
char *text = value;
if (text[0] == '"') {
@ -713,14 +717,68 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
}
}
if (strcasecmp(key, "Caption") == 0) {
dvxSetTitle(rt->ctx, form->window, text);
} else if (strcasecmp(key, "Width") == 0) {
form->frmWidth = atoi(value);
} else if (strcasecmp(key, "Height") == 0) {
form->frmHeight = atoi(value);
} else if (strcasecmp(key, "Left") == 0) {
form->frmLeft = atoi(value);
} else if (strcasecmp(key, "Top") == 0) {
form->frmTop = atoi(value);
} else if (strcasecmp(key, "Resizable") == 0) {
form->frmResizable = (strcasecmp(text, "True") == 0);
form->frmHasResizable = true;
} else if (strcasecmp(key, "Centered") == 0) {
form->frmCentered = (strcasecmp(text, "True") == 0);
} else if (strcasecmp(key, "AutoSize") == 0) {
form->frmAutoSize = (strcasecmp(text, "True") == 0);
} else if (strcasecmp(key, "Layout") == 0) {
if (strcasecmp(text, "HBox") == 0) {
form->frmHBox = true;
}
}
}
}
// Force layout recalculation now that all controls and properties are set
// Apply accumulated form-level properties
if (form) {
// Ensure content box exists even if form has no controls
if (!form->contentBox && form->root) {
if (form->frmHBox) {
form->contentBox = wgtHBox(form->root);
} else {
form->contentBox = wgtVBox(form->root);
}
form->contentBox->weight = 100;
}
// Set resizable flag
if (form->frmHasResizable) {
form->window->resizable = form->frmResizable;
}
// Size and position the window
if (form->frmAutoSize) {
dvxFitWindow(rt->ctx, form->window);
} else if (form->frmWidth > 0 && form->frmHeight > 0) {
dvxResizeWindow(rt->ctx, form->window, form->frmWidth, form->frmHeight);
} else {
dvxFitWindow(rt->ctx, form->window);
}
// Position: centered or explicit left/top
if (form->frmCentered) {
int32_t scrW = rt->ctx->display.width;
int32_t scrH = rt->ctx->display.height;
form->window->x = (scrW - form->window->w) / 2;
form->window->y = (scrH - form->window->h) / 2;
} else if (form->frmLeft > 0 || form->frmTop > 0) {
form->window->x = form->frmLeft;
form->window->y = form->frmTop;
}
}
return form;
@ -789,7 +847,6 @@ void basFormRtShowForm(void *ctx, void *formRef, bool modal) {
form->window->visible = true;
dvxFitWindow(rt->ctx, form->window);
dvxInvalidateWindow(rt->ctx, form->window);
if (modal) {
rt->ctx->modalWindow = form->window;
@ -953,65 +1010,50 @@ static WidgetT *createWidget(const char *wgtTypeName, WidgetT *parent) {
return NULL;
}
// All widget APIs have create as the first function pointer.
// The signature varies but the first arg is always parent.
// We handle the common patterns.
// Determine creation signature from the widget interface descriptor.
const WgtIfaceT *iface = wgtGetIface(wgtTypeName);
uint8_t sig = iface ? iface->createSig : WGT_CREATE_PARENT;
typedef WidgetT *(*CreateParentFnT)(WidgetT *);
typedef WidgetT *(*CreateParentTextFnT)(WidgetT *, const char *);
typedef WidgetT *(*CreateParentIntFnT)(WidgetT *, int32_t);
typedef WidgetT *(*CreateParentIntIntFnT)(WidgetT *, int32_t, int32_t);
typedef WidgetT *(*CreateParentIntBoolFnT)(WidgetT *, int32_t, bool);
typedef WidgetT *(*CreateParentIntIntIntFnT)(WidgetT *, int32_t, int32_t, int32_t);
typedef WidgetT *(*CreateParentIntBoolFnT)(WidgetT *, int32_t, bool);
typedef WidgetT *(*CreateParentBoolFnT)(WidgetT *, bool);
// Determine creation pattern by widget type name
if (strcasecmp(wgtTypeName, "button") == 0 ||
strcasecmp(wgtTypeName, "checkbox") == 0 ||
strcasecmp(wgtTypeName, "label") == 0) {
// create(parent, text)
switch (sig) {
case WGT_CREATE_PARENT_TEXT: {
CreateParentTextFnT fn = *(CreateParentTextFnT *)api;
return fn(parent, "");
}
if (strcasecmp(wgtTypeName, "textinput") == 0 ||
strcasecmp(wgtTypeName, "combobox") == 0) {
// create(parent, maxLen)
case WGT_CREATE_PARENT_INT: {
CreateParentIntFnT fn = *(CreateParentIntFnT *)api;
return fn(parent, DEFAULT_CREATE_ARG);
return fn(parent, iface->createArgs[0]);
}
if (strcasecmp(wgtTypeName, "slider") == 0) {
// create(parent, minVal, maxVal)
case WGT_CREATE_PARENT_INT_INT: {
CreateParentIntIntFnT fn = *(CreateParentIntIntFnT *)api;
return fn(parent, 0, 100);
return fn(parent, iface->createArgs[0], iface->createArgs[1]);
}
if (strcasecmp(wgtTypeName, "spinner") == 0) {
// create(parent, minVal, maxVal, step)
case WGT_CREATE_PARENT_INT_INT_INT: {
CreateParentIntIntIntFnT fn = *(CreateParentIntIntIntFnT *)api;
return fn(parent, 0, 100, 1);
return fn(parent, iface->createArgs[0], iface->createArgs[1], iface->createArgs[2]);
}
if (strcasecmp(wgtTypeName, "timer") == 0) {
// create(parent, intervalMs, repeat)
case WGT_CREATE_PARENT_INT_BOOL: {
CreateParentIntBoolFnT fn = *(CreateParentIntBoolFnT *)api;
return fn(parent, 1000, true);
return fn(parent, iface->createArgs[0], (bool)iface->createArgs[1]);
}
if (strcasecmp(wgtTypeName, "box") == 0) {
// box API: first fn is vbox(parent), second is hbox, third is frame
// For BASIC "Frame", use frame (third slot)
typedef struct {
WidgetT *(*vbox)(WidgetT *);
WidgetT *(*hbox)(WidgetT *);
WidgetT *(*frame)(WidgetT *, const char *);
} BoxApiPatternT;
const BoxApiPatternT *boxApi = (const BoxApiPatternT *)api;
return boxApi->frame(parent, "");
case WGT_CREATE_PARENT_BOOL: {
CreateParentBoolFnT fn = *(CreateParentBoolFnT *)api;
return fn(parent, (bool)iface->createArgs[0]);
}
// Default: assume create(parent) with no extra args
case WGT_CREATE_PARENT_DATA:
return NULL;
default: {
CreateParentFnT fn = *(CreateParentFnT *)api;
return fn(parent);
}
}
}
@ -1064,14 +1106,26 @@ static BasValueT getCommonProp(BasControlT *ctrl, const char *propName, bool *ha
return basValLong(ctrl->widget->y);
}
if (strcasecmp(propName, "Width") == 0) {
if (strcasecmp(propName, "Width") == 0 || strcasecmp(propName, "MinWidth") == 0) {
return basValLong(ctrl->widget->w);
}
if (strcasecmp(propName, "Height") == 0) {
if (strcasecmp(propName, "Height") == 0 || strcasecmp(propName, "MinHeight") == 0) {
return basValLong(ctrl->widget->h);
}
if (strcasecmp(propName, "MaxWidth") == 0) {
return basValLong(ctrl->widget->maxW ? (ctrl->widget->maxW & WGT_SIZE_VAL_MASK) : 0);
}
if (strcasecmp(propName, "MaxHeight") == 0) {
return basValLong(ctrl->widget->maxH ? (ctrl->widget->maxH & WGT_SIZE_VAL_MASK) : 0);
}
if (strcasecmp(propName, "Weight") == 0) {
return basValLong(ctrl->widget->weight);
}
if (strcasecmp(propName, "Visible") == 0) {
return basValBool(ctrl->widget->visible);
}
@ -1517,16 +1571,36 @@ static bool setCommonProp(BasControlT *ctrl, const char *propName, BasValueT val
return true;
}
if (strcasecmp(propName, "Width") == 0) {
if (strcasecmp(propName, "Width") == 0 || strcasecmp(propName, "MinWidth") == 0) {
int32_t w = (int32_t)basValToNumber(value);
ctrl->widget->prefW = wgtPixels(w);
ctrl->widget->minW = wgtPixels(w);
wgtInvalidate(ctrl->widget);
return true;
}
if (strcasecmp(propName, "Height") == 0) {
if (strcasecmp(propName, "Height") == 0 || strcasecmp(propName, "MinHeight") == 0) {
int32_t h = (int32_t)basValToNumber(value);
ctrl->widget->prefH = wgtPixels(h);
ctrl->widget->minH = wgtPixels(h);
wgtInvalidate(ctrl->widget);
return true;
}
if (strcasecmp(propName, "MaxWidth") == 0) {
int32_t mw = (int32_t)basValToNumber(value);
ctrl->widget->maxW = mw > 0 ? wgtPixels(mw) : 0;
wgtInvalidate(ctrl->widget);
return true;
}
if (strcasecmp(propName, "MaxHeight") == 0) {
int32_t mh = (int32_t)basValToNumber(value);
ctrl->widget->maxH = mh > 0 ? wgtPixels(mh) : 0;
wgtInvalidate(ctrl->widget);
return true;
}
if (strcasecmp(propName, "Weight") == 0) {
ctrl->widget->weight = (int32_t)basValToNumber(value);
wgtInvalidate(ctrl->widget);
return true;
}

View file

@ -38,6 +38,7 @@ typedef struct BasControlT BasControlT;
typedef struct BasControlT {
char name[BAS_MAX_CTRL_NAME]; // VB control name (e.g. "Command1")
char typeName[BAS_MAX_CTRL_NAME]; // VB type name (e.g. "CommandButton")
WidgetT *widget; // the DVX widget
BasFormT *form; // owning form
const WgtIfaceT *iface; // interface descriptor (from .wgt)
@ -52,12 +53,22 @@ typedef struct BasFormT {
char name[BAS_MAX_CTRL_NAME]; // form name (e.g. "Form1")
WindowT *window; // DVX window
WidgetT *root; // widget root (from wgtInitWindow)
WidgetT *contentBox; // VBox for user controls
WidgetT *contentBox; // VBox/HBox for user controls
AppContextT *ctx; // DVX app context
BasControlT controls[BAS_MAX_CTRLS]; // controls on this form
int32_t controlCount;
BasVmT *vm; // VM for event dispatch
BasModuleT *module; // compiled module (for SUB lookup)
// Form-level properties (accumulated during .frm parsing)
int32_t frmWidth;
int32_t frmHeight;
int32_t frmLeft;
int32_t frmTop;
bool frmResizable;
bool frmHasResizable; // true if Resizable was explicitly set
bool frmCentered;
bool frmAutoSize;
bool frmHBox; // true if Layout = "HBox"
} BasFormT;
// ============================================================

View file

@ -25,53 +25,13 @@
#define DEFAULT_CTRL_W 100
#define DEFAULT_CTRL_H 30
#define MIN_CTRL_SIZE 8
#define DEFAULT_CREATE_ARG 256
// ============================================================
// Tool type name table
// Default event for the Form type (not a widget, so not in iface)
// ============================================================
static const char *sToolTypeNames[TOOL_COUNT] = {
"", // TOOL_POINTER
"CommandButton", // TOOL_BUTTON
"Label", // TOOL_LABEL
"TextBox", // TOOL_TEXTBOX
"CheckBox", // TOOL_CHECKBOX
"OptionButton", // TOOL_OPTION
"Frame", // TOOL_FRAME
"ListBox", // TOOL_LISTBOX
"ComboBox", // TOOL_COMBOBOX
"HScrollBar", // TOOL_HSCROLL
"VScrollBar", // TOOL_VSCROLL
"Timer", // TOOL_TIMER
"PictureBox", // TOOL_PICTURE
"Image" // TOOL_IMAGE
};
// ============================================================
// Default event table
// ============================================================
typedef struct {
const char *typeName;
const char *eventName;
} DefaultEventT;
static const DefaultEventT sDefaultEvents[] = {
{ "CommandButton", "Click" },
{ "Label", "Click" },
{ "TextBox", "Change" },
{ "CheckBox", "Click" },
{ "OptionButton", "Click" },
{ "Frame", "Click" },
{ "ListBox", "Click" },
{ "ComboBox", "Click" },
{ "Timer", "Timer" },
{ "PictureBox", "Click" },
{ "Image", "Click" },
{ "Form", "Load" },
{ NULL, NULL }
};
static const char *FORM_DEFAULT_EVENT = "Load";
// ============================================================
// Prototypes
@ -107,37 +67,50 @@ static WidgetT *createDesignWidget(const char *vbTypeName, WidgetT *parent) {
return NULL;
}
const WgtIfaceT *iface = wgtGetIface(wgtName);
uint8_t sig = iface ? iface->createSig : WGT_CREATE_PARENT;
typedef WidgetT *(*CreateParentFnT)(WidgetT *);
typedef WidgetT *(*CreateParentTextFnT)(WidgetT *, const char *);
typedef WidgetT *(*CreateParentIntFnT)(WidgetT *, int32_t);
typedef WidgetT *(*CreateParentIntIntFnT)(WidgetT *, int32_t, int32_t);
typedef WidgetT *(*CreateParentIntIntIntFnT)(WidgetT *, int32_t, int32_t, int32_t);
typedef WidgetT *(*CreateParentIntBoolFnT)(WidgetT *, int32_t, bool);
typedef WidgetT *(*CreateParentBoolFnT)(WidgetT *, bool);
if (strcasecmp(wgtName, "button") == 0 ||
strcasecmp(wgtName, "checkbox") == 0 ||
strcasecmp(wgtName, "label") == 0) {
switch (sig) {
case WGT_CREATE_PARENT_TEXT: {
CreateParentTextFnT fn = *(CreateParentTextFnT *)api;
return fn(parent, "");
}
if (strcasecmp(wgtName, "textinput") == 0 ||
strcasecmp(wgtName, "combobox") == 0) {
case WGT_CREATE_PARENT_INT: {
CreateParentIntFnT fn = *(CreateParentIntFnT *)api;
return fn(parent, DEFAULT_CREATE_ARG);
return fn(parent, iface->createArgs[0]);
}
if (strcasecmp(wgtName, "timer") == 0) {
case WGT_CREATE_PARENT_INT_INT: {
CreateParentIntIntFnT fn = *(CreateParentIntIntFnT *)api;
return fn(parent, iface->createArgs[0], iface->createArgs[1]);
}
case WGT_CREATE_PARENT_INT_INT_INT: {
CreateParentIntIntIntFnT fn = *(CreateParentIntIntIntFnT *)api;
return fn(parent, iface->createArgs[0], iface->createArgs[1], iface->createArgs[2]);
}
case WGT_CREATE_PARENT_INT_BOOL: {
CreateParentIntBoolFnT fn = *(CreateParentIntBoolFnT *)api;
return fn(parent, 1000, false);
return fn(parent, iface->createArgs[0], (bool)iface->createArgs[1]);
}
if (strcasecmp(wgtName, "listbox") == 0) {
case WGT_CREATE_PARENT_BOOL: {
CreateParentBoolFnT fn = *(CreateParentBoolFnT *)api;
return fn(parent, (bool)iface->createArgs[0]);
}
case WGT_CREATE_PARENT_DATA:
// Image/ImageButton -- cannot auto-create without pixel data
return NULL;
default: {
CreateParentFnT fn = *(CreateParentFnT *)api;
return fn(parent);
}
// Fallback: try simple create(parent)
CreateParentFnT fn = *(CreateParentFnT *)api;
return fn(parent);
}
}
@ -146,13 +119,18 @@ static WidgetT *createDesignWidget(const char *vbTypeName, WidgetT *parent) {
// ============================================================
void dsgnAutoName(const DsgnStateT *ds, const char *typeName, char *buf, int32_t bufSize) {
// Look up the name prefix from the widget interface descriptor.
// Falls back to the type name itself if no prefix is registered.
const char *prefix = typeName;
const char *wgtName = wgtFindByBasName(typeName);
if (strcasecmp(typeName, "CommandButton") == 0) { prefix = "Command"; }
else if (strcasecmp(typeName, "OptionButton") == 0) { prefix = "Option"; }
else if (strcasecmp(typeName, "HScrollBar") == 0) { prefix = "HScroll"; }
else if (strcasecmp(typeName, "VScrollBar") == 0) { prefix = "VScroll"; }
else if (strcasecmp(typeName, "PictureBox") == 0) { prefix = "Picture"; }
if (wgtName) {
const WgtIfaceT *iface = wgtGetIface(wgtName);
if (iface && iface->namePrefix) {
prefix = iface->namePrefix;
}
}
int32_t highest = 0;
int32_t prefixLen = (int32_t)strlen(prefix);
@ -184,14 +162,30 @@ void dsgnCreateWidgets(DsgnStateT *ds, WidgetT *contentBox) {
ds->form->contentBox = contentBox;
int32_t count = (int32_t)arrlen(ds->form->controls);
// Two passes: first create all controls (so containers exist),
// then parent children inside their containers.
// Pass 1: create all widgets as top-level children
for (int32_t i = 0; i < count; i++) {
DsgnControlT *ctrl = &ds->form->controls[i];
if (ctrl->widget) {
continue; // already created
continue;
}
WidgetT *w = createDesignWidget(ctrl->typeName, contentBox);
// Find the parent widget
WidgetT *parent = contentBox;
if (ctrl->parentName[0]) {
for (int32_t j = 0; j < count; j++) {
if (j != i && ds->form->controls[j].widget &&
strcasecmp(ds->form->controls[j].name, ctrl->parentName) == 0) {
parent = ds->form->controls[j].widget;
break;
}
}
}
WidgetT *w = createDesignWidget(ctrl->typeName, parent);
if (!w) {
continue;
@ -207,12 +201,45 @@ void dsgnCreateWidgets(DsgnStateT *ds, WidgetT *contentBox) {
if (caption) { wgtSetText(w, caption); }
if (text) { wgtSetText(w, text); }
// Set size hints for the layout engine
// Set size hints for the layout engine.
// minW/minH set the floor; maxW/maxH cap the size.
if (ctrl->width > 0) {
w->minW = wgtPixels(ctrl->width);
}
if (ctrl->height > 0) {
w->minH = wgtPixels(ctrl->height);
w->prefH = wgtPixels(ctrl->height);
}
if (ctrl->maxWidth > 0) {
w->maxW = wgtPixels(ctrl->maxWidth);
}
if (ctrl->maxHeight > 0) {
w->maxH = wgtPixels(ctrl->maxHeight);
}
w->weight = ctrl->weight;
}
}
// ============================================================
// dsgnIsContainer
// ============================================================
bool dsgnIsContainer(const char *typeName) {
const char *wgtName = wgtFindByBasName(typeName);
if (wgtName) {
const WgtIfaceT *iface = wgtGetIface(wgtName);
if (iface) {
return iface->isContainer;
}
}
return false;
}
@ -221,9 +248,17 @@ void dsgnCreateWidgets(DsgnStateT *ds, WidgetT *contentBox) {
// ============================================================
const char *dsgnDefaultEvent(const char *typeName) {
for (int32_t i = 0; sDefaultEvents[i].typeName; i++) {
if (strcasecmp(typeName, sDefaultEvents[i].typeName) == 0) {
return sDefaultEvents[i].eventName;
if (strcasecmp(typeName, "Form") == 0) {
return FORM_DEFAULT_EVENT;
}
const char *wgtName = wgtFindByBasName(typeName);
if (wgtName) {
const WgtIfaceT *iface = wgtGetIface(wgtName);
if (iface && iface->defaultEvent) {
return iface->defaultEvent;
}
}
@ -251,7 +286,7 @@ void dsgnFree(DsgnStateT *ds) {
void dsgnInit(DsgnStateT *ds, AppContextT *ctx) {
memset(ds, 0, sizeof(*ds));
ds->selectedIdx = -1;
ds->activeTool = TOOL_POINTER;
ds->activeTool[0] = '\0';
ds->mode = DSGN_IDLE;
ds->activeHandle = HANDLE_NONE;
ds->ctx = ctx;
@ -278,12 +313,23 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
form->controls = NULL;
form->width = DEFAULT_FORM_W;
form->height = DEFAULT_FORM_H;
form->left = 0;
form->top = 0;
snprintf(form->layout, DSGN_MAX_NAME, "VBox");
form->centered = true;
form->autoSize = true;
form->resizable = true;
snprintf(form->name, DSGN_MAX_NAME, "Form1");
snprintf(form->caption, DSGN_MAX_TEXT, "Form1");
DsgnControlT *curCtrl = NULL;
bool inForm = false;
// Parent name stack for nesting (index 0 = form level)
char parentStack[8][DSGN_MAX_NAME];
int32_t nestDepth = 0;
parentStack[0][0] = '\0';
const char *pos = source;
const char *end = source + sourceLen;
@ -349,17 +395,29 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
snprintf(form->name, DSGN_MAX_NAME, "%s", ctrlName);
snprintf(form->caption, DSGN_MAX_TEXT, "%s", ctrlName);
inForm = true;
nestDepth = 0;
curCtrl = NULL;
} else if (inForm) {
DsgnControlT ctrl;
memset(&ctrl, 0, sizeof(ctrl));
snprintf(ctrl.name, DSGN_MAX_NAME, "%s", ctrlName);
snprintf(ctrl.typeName, DSGN_MAX_NAME, "%s", typeName);
// Set parent from current nesting
if (nestDepth > 0) {
snprintf(ctrl.parentName, DSGN_MAX_NAME, "%s", parentStack[nestDepth - 1]);
}
ctrl.width = DEFAULT_CTRL_W;
ctrl.height = DEFAULT_CTRL_H;
ctrl.tabIndex = (int32_t)arrlen(form->controls);
arrput(form->controls, ctrl);
curCtrl = &form->controls[arrlen(form->controls) - 1];
// If this is a container, push onto parent stack
if (dsgnIsContainer(typeName) && nestDepth < 7) {
snprintf(parentStack[nestDepth], DSGN_MAX_NAME, "%s", ctrlName);
nestDepth++;
}
}
continue;
@ -367,6 +425,11 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
if (strcasecmp(trimmed, "End") == 0) {
if (curCtrl) {
// If we're closing a container, pop the parent stack
if (nestDepth > 0 && strcasecmp(parentStack[nestDepth - 1], curCtrl->name) == 0) {
nestDepth--;
}
curCtrl = NULL;
} else {
inForm = false;
@ -417,14 +480,25 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
if (curCtrl) {
if (strcasecmp(key, "Left") == 0) { curCtrl->left = atoi(val); }
else if (strcasecmp(key, "Top") == 0) { curCtrl->top = atoi(val); }
else if (strcasecmp(key, "Width") == 0) { curCtrl->width = atoi(val); }
else if (strcasecmp(key, "Height") == 0) { curCtrl->height = atoi(val); }
else if (strcasecmp(key, "TabIndex") == 0) { curCtrl->tabIndex = atoi(val); }
else if (strcasecmp(key, "MinWidth") == 0 ||
strcasecmp(key, "Width") == 0) { curCtrl->width = atoi(val); }
else if (strcasecmp(key, "MinHeight") == 0 ||
strcasecmp(key, "Height") == 0) { curCtrl->height = atoi(val); }
else if (strcasecmp(key, "MaxWidth") == 0) { curCtrl->maxWidth = atoi(val); }
else if (strcasecmp(key, "MaxHeight") == 0) { curCtrl->maxHeight = atoi(val); }
else if (strcasecmp(key, "Weight") == 0) { curCtrl->weight = atoi(val); }
else if (strcasecmp(key, "TabIndex") == 0) { /* ignored -- DVX has no tab order */ }
else { setPropValue(curCtrl, key, val); }
} else {
if (strcasecmp(key, "Caption") == 0) { snprintf(form->caption, DSGN_MAX_TEXT, "%s", val); }
else if (strcasecmp(key, "Width") == 0) { form->width = atoi(val); }
else if (strcasecmp(key, "Height") == 0) { form->height = atoi(val); }
else if (strcasecmp(key, "Layout") == 0) { snprintf(form->layout, DSGN_MAX_NAME, "%.31s", val); }
else if (strcasecmp(key, "AutoSize") == 0) { form->autoSize = (strcasecmp(val, "True") == 0); }
else if (strcasecmp(key, "Resizable") == 0) { form->resizable = (strcasecmp(val, "True") == 0); }
else if (strcasecmp(key, "Centered") == 0) { form->centered = (strcasecmp(val, "True") == 0); }
else if (strcasecmp(key, "Left") == 0) { form->left = atoi(val); }
else if (strcasecmp(key, "Top") == 0) { form->top = atoi(val); }
else if (strcasecmp(key, "Width") == 0) { form->width = atoi(val); form->autoSize = false; }
else if (strcasecmp(key, "Height") == 0) { form->height = atoi(val); form->autoSize = false; }
}
}
}
@ -446,6 +520,12 @@ void dsgnNewForm(DsgnStateT *ds, const char *name) {
form->controls = NULL;
form->width = DEFAULT_FORM_W;
form->height = DEFAULT_FORM_H;
form->left = 0;
form->top = 0;
snprintf(form->layout, DSGN_MAX_NAME, "VBox");
form->centered = true;
form->autoSize = true;
form->resizable = true;
snprintf(form->name, DSGN_MAX_NAME, "%s", name);
snprintf(form->caption, DSGN_MAX_TEXT, "%s", name);
@ -463,11 +543,28 @@ void dsgnOnKey(DsgnStateT *ds, int32_t key) {
return;
}
// Delete key
if (key == 0x153 && ds->selectedIdx >= 0 && ds->selectedIdx < (int32_t)arrlen(ds->form->controls)) {
int32_t count = (int32_t)arrlen(ds->form->controls);
// Delete key -- remove the selected control and any children
if (key == 0x153 && ds->selectedIdx >= 0 && ds->selectedIdx < count) {
const char *delName = ds->form->controls[ds->selectedIdx].name;
// Remove children first (controls whose parentName matches)
for (int32_t i = count - 1; i >= 0; i--) {
if (i != ds->selectedIdx && strcasecmp(ds->form->controls[i].parentName, delName) == 0) {
arrdel(ds->form->controls, i);
if (i < ds->selectedIdx) {
ds->selectedIdx--;
}
}
}
arrdel(ds->form->controls, ds->selectedIdx);
ds->selectedIdx = -1;
ds->form->dirty = true;
rebuildWidgets(ds);
}
}
@ -557,7 +654,7 @@ void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag) {
}
// Pointer tool: select, start resize or reorder
if (ds->activeTool == TOOL_POINTER) {
if (ds->activeTool[0] == '\0') {
// Check grab handles of selected control first
if (ds->selectedIdx >= 0 && ds->selectedIdx < ctrlCount) {
DsgnHandleE handle = hitTestHandles(&ds->form->controls[ds->selectedIdx], x, y);
@ -588,32 +685,58 @@ void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag) {
return;
}
// Non-pointer tool: place a new control (appended to VBox)
const char *typeName = dsgnToolTypeName(ds->activeTool);
// Non-pointer tool: place a new control
const char *typeName = ds->activeTool;
DsgnControlT ctrl;
memset(&ctrl, 0, sizeof(ctrl));
dsgnAutoName(ds, typeName, ctrl.name, DSGN_MAX_NAME);
snprintf(ctrl.typeName, DSGN_MAX_NAME, "%s", typeName);
ctrl.width = DEFAULT_CTRL_W;
ctrl.height = DEFAULT_CTRL_H;
ctrl.tabIndex = ctrlCount;
setPropValue(&ctrl, "Caption", ctrl.name);
// Determine parent: if click is inside a container, nest there
WidgetT *parentWidget = ds->form->contentBox;
for (int32_t i = ctrlCount - 1; i >= 0; i--) {
DsgnControlT *pc = &ds->form->controls[i];
if (pc->widget && dsgnIsContainer(pc->typeName)) {
int32_t wx = pc->widget->x;
int32_t wy = pc->widget->y;
int32_t ww = pc->widget->w;
int32_t wh = pc->widget->h;
if (x >= wx && x < wx + ww && y >= wy && y < wy + wh) {
snprintf(ctrl.parentName, DSGN_MAX_NAME, "%s", pc->name);
parentWidget = pc->widget;
break;
}
}
}
// Create the live widget
if (ds->form->contentBox) {
ctrl.widget = createDesignWidget(typeName, ds->form->contentBox);
if (parentWidget) {
ctrl.widget = createDesignWidget(typeName, parentWidget);
if (ctrl.widget) {
wgtSetName(ctrl.widget, ctrl.name);
wgtSetText(ctrl.widget, ctrl.name);
ctrl.widget->prefW = wgtPixels(ctrl.width);
ctrl.widget->prefH = wgtPixels(ctrl.height);
ctrl.widget->minW = wgtPixels(ctrl.width);
ctrl.widget->minH = wgtPixels(ctrl.height);
}
}
arrput(ds->form->controls, ctrl);
ds->selectedIdx = (int32_t)arrlen(ds->form->controls) - 1;
ds->activeTool = TOOL_POINTER;
// Set text AFTER arrput so pointers into the array element are stable
DsgnControlT *stable = &ds->form->controls[ds->selectedIdx];
if (stable->widget) {
const char *caption = getPropValue(stable, "Caption");
wgtSetName(stable->widget, stable->name);
wgtSetText(stable->widget, caption ? caption : stable->name);
}
ds->activeTool[0] = '\0';
ds->mode = DSGN_IDLE;
ds->form->dirty = true;
}
@ -663,7 +786,7 @@ void dsgnPaintOverlay(DsgnStateT *ds, int32_t winX, int32_t winY) {
DsgnControlT *ctrl = &ds->form->controls[ds->selectedIdx];
if (!ctrl->widget || ctrl->widget->w <= 0 || ctrl->widget->h <= 0) {
if (!ctrl->widget || !ctrl->widget->visible || ctrl->widget->w <= 0 || ctrl->widget->h <= 0) {
return;
}
@ -680,6 +803,7 @@ void dsgnPaintOverlay(DsgnStateT *ds, int32_t winX, int32_t winY) {
const BlitOpsT *ops = &ds->ctx->blitOps;
uint32_t black = packColor(&cd, 0, 0, 0);
uint32_t gray = packColor(&cd, 128, 128, 128);
int32_t cx = ctrl->widget->x;
int32_t cy = ctrl->widget->y;
@ -687,12 +811,14 @@ void dsgnPaintOverlay(DsgnStateT *ds, int32_t winX, int32_t winY) {
int32_t ch = ctrl->widget->h;
int32_t hs = DSGN_HANDLE_SIZE;
// 3 handles: E (right edge), S (bottom edge), SE (corner)
int32_t hx[HANDLE_COUNT] = { cx + cw - hs/2, cx + cw/2 - hs/2, cx + cw - hs/2 };
int32_t hy[HANDLE_COUNT] = { cy + ch/2 - hs/2, cy + ch - hs/2, cy + ch - hs/2 };
// All 8 handles: NW, N, NE, E, SE, S, SW, W
int32_t hx[8] = { cx - hs/2, cx + cw/2 - hs/2, cx + cw - hs/2, cx + cw - hs/2, cx + cw - hs/2, cx + cw/2 - hs/2, cx - hs/2, cx - hs/2 };
int32_t hy[8] = { cy - hs/2, cy - hs/2, cy - hs/2, cy + ch/2 - hs/2, cy + ch - hs/2, cy + ch - hs/2, cy + ch - hs/2, cy + ch/2 - hs/2 };
for (int32_t i = 0; i < HANDLE_COUNT; i++) {
rectFill(&cd, ops, hx[i], hy[i], hs, hs, black);
// E (idx 3), S (idx 5), SE (idx 4) are active (black); rest are inactive (gray)
for (int32_t i = 0; i < 8; i++) {
bool active = (i == 3 || i == 4 || i == 5);
rectFill(&cd, ops, hx[i], hy[i], hs, hs, active ? black : gray);
}
}
@ -701,6 +827,69 @@ void dsgnPaintOverlay(DsgnStateT *ds, int32_t winX, int32_t winY) {
// dsgnSaveFrm
// ============================================================
// Write controls at a given nesting level with the specified parent name.
static int32_t saveControls(const DsgnFormT *form, char *buf, int32_t bufSize, int32_t pos, const char *parentName, int32_t indent) {
int32_t count = (int32_t)arrlen(form->controls);
for (int32_t i = 0; i < count; i++) {
const DsgnControlT *ctrl = &form->controls[i];
// Only output controls whose parent matches
if (parentName[0] == '\0' && ctrl->parentName[0] != '\0') { continue; }
if (parentName[0] != '\0' && strcasecmp(ctrl->parentName, parentName) != 0) { continue; }
// Indent
char pad[32];
int32_t padLen = indent * 4;
if (padLen > 31) { padLen = 31; }
memset(pad, ' ', padLen);
pad[padLen] = '\0';
pos += snprintf(buf + pos, bufSize - pos, "%sBegin %s %s\n", pad, ctrl->typeName, ctrl->name);
const char *caption = getPropValue(ctrl, "Caption");
const char *text = getPropValue(ctrl, "Text");
if (caption) { pos += snprintf(buf + pos, bufSize - pos, "%s Caption = \"%s\"\n", pad, caption); }
if (text) { pos += snprintf(buf + pos, bufSize - pos, "%s Text = \"%s\"\n", pad, text); }
pos += snprintf(buf + pos, bufSize - pos, "%s Left = %d\n", pad, (int)ctrl->left);
pos += snprintf(buf + pos, bufSize - pos, "%s Top = %d\n", pad, (int)ctrl->top);
pos += snprintf(buf + pos, bufSize - pos, "%s MinWidth = %d\n", pad, (int)ctrl->width);
pos += snprintf(buf + pos, bufSize - pos, "%s MinHeight = %d\n", pad, (int)ctrl->height);
if (ctrl->maxWidth > 0) {
pos += snprintf(buf + pos, bufSize - pos, "%s MaxWidth = %d\n", pad, (int)ctrl->maxWidth);
}
if (ctrl->maxHeight > 0) {
pos += snprintf(buf + pos, bufSize - pos, "%s MaxHeight = %d\n", pad, (int)ctrl->maxHeight);
}
if (ctrl->weight > 0) {
pos += snprintf(buf + pos, bufSize - pos, "%s Weight = %d\n", pad, (int)ctrl->weight);
}
for (int32_t j = 0; j < ctrl->propCount; j++) {
if (strcasecmp(ctrl->props[j].name, "Caption") == 0) { continue; }
if (strcasecmp(ctrl->props[j].name, "Text") == 0) { continue; }
pos += snprintf(buf + pos, bufSize - pos, "%s %s = \"%s\"\n", pad, ctrl->props[j].name, ctrl->props[j].value);
}
// Recursively output children of this container
if (dsgnIsContainer(ctrl->typeName)) {
pos = saveControls(form, buf, bufSize, pos, ctrl->name, indent + 1);
}
pos += snprintf(buf + pos, bufSize - pos, "%sEnd\n", pad);
}
return pos;
}
int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize) {
if (!ds->form || !buf || bufSize <= 0) {
return -1;
@ -711,35 +900,23 @@ int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize) {
pos += snprintf(buf + pos, bufSize - pos, "VERSION 1.00\n");
pos += snprintf(buf + pos, bufSize - pos, "Begin Form %s\n", ds->form->name);
pos += snprintf(buf + pos, bufSize - pos, " Caption = \"%s\"\n", ds->form->caption);
pos += snprintf(buf + pos, bufSize - pos, " Layout = %s\n", ds->form->layout);
pos += snprintf(buf + pos, bufSize - pos, " AutoSize = %s\n", ds->form->autoSize ? "True" : "False");
pos += snprintf(buf + pos, bufSize - pos, " Resizable = %s\n", ds->form->resizable ? "True" : "False");
pos += snprintf(buf + pos, bufSize - pos, " Centered = %s\n", ds->form->centered ? "True" : "False");
if (!ds->form->centered) {
pos += snprintf(buf + pos, bufSize - pos, " Left = %d\n", (int)ds->form->left);
pos += snprintf(buf + pos, bufSize - pos, " Top = %d\n", (int)ds->form->top);
}
if (!ds->form->autoSize) {
pos += snprintf(buf + pos, bufSize - pos, " Width = %d\n", (int)ds->form->width);
pos += snprintf(buf + pos, bufSize - pos, " Height = %d\n", (int)ds->form->height);
int32_t count = (int32_t)arrlen(ds->form->controls);
for (int32_t i = 0; i < count; i++) {
const DsgnControlT *ctrl = &ds->form->controls[i];
pos += snprintf(buf + pos, bufSize - pos, " Begin %s %s\n", ctrl->typeName, ctrl->name);
const char *caption = getPropValue(ctrl, "Caption");
const char *text = getPropValue(ctrl, "Text");
if (caption) { pos += snprintf(buf + pos, bufSize - pos, " Caption = \"%s\"\n", caption); }
if (text) { pos += snprintf(buf + pos, bufSize - pos, " Text = \"%s\"\n", text); }
pos += snprintf(buf + pos, bufSize - pos, " Left = %d\n", (int)ctrl->left);
pos += snprintf(buf + pos, bufSize - pos, " Top = %d\n", (int)ctrl->top);
pos += snprintf(buf + pos, bufSize - pos, " Width = %d\n", (int)ctrl->width);
pos += snprintf(buf + pos, bufSize - pos, " Height = %d\n", (int)ctrl->height);
for (int32_t j = 0; j < ctrl->propCount; j++) {
if (strcasecmp(ctrl->props[j].name, "Caption") == 0) { continue; }
if (strcasecmp(ctrl->props[j].name, "Text") == 0) { continue; }
pos += snprintf(buf + pos, bufSize - pos, " %s = \"%s\"\n", ctrl->props[j].name, ctrl->props[j].value);
}
pos += snprintf(buf + pos, bufSize - pos, " End\n");
}
// Output top-level controls (and recurse into containers)
pos = saveControls(ds->form, buf, bufSize, pos, "", 1);
pos += snprintf(buf + pos, bufSize - pos, "End\n");
return pos;
@ -765,17 +942,6 @@ const char *dsgnSelectedName(const DsgnStateT *ds) {
// ============================================================
// dsgnToolTypeName
// ============================================================
const char *dsgnToolTypeName(DsgnToolE tool) {
if (tool >= 0 && tool < TOOL_COUNT) {
return sToolTypeNames[tool];
}
return "";
}
// ============================================================
@ -803,7 +969,7 @@ static int32_t hitTestControl(const DsgnStateT *ds, int32_t x, int32_t y) {
for (int32_t i = count - 1; i >= 0; i--) {
const DsgnControlT *ctrl = &ds->form->controls[i];
if (!ctrl->widget) {
if (!ctrl->widget || !ctrl->widget->visible) {
continue;
}
@ -935,9 +1101,8 @@ static void syncWidgetGeom(DsgnControlT *ctrl) {
return;
}
// Set minW/minH -- the layout engine uses these as the floor
// during the measure pass, so the arrange pass allocates this
// exact size (assuming weight=0).
ctrl->widget->minW = wgtPixels(ctrl->width);
ctrl->widget->minH = wgtPixels(ctrl->height);
ctrl->widget->prefH = wgtPixels(ctrl->height);
ctrl->widget->maxW = ctrl->maxWidth > 0 ? wgtPixels(ctrl->maxWidth) : 0;
ctrl->widget->maxH = ctrl->maxHeight > 0 ? wgtPixels(ctrl->maxHeight) : 0;
}

View file

@ -42,11 +42,14 @@ typedef struct {
typedef struct {
char name[DSGN_MAX_NAME];
char typeName[DSGN_MAX_NAME];
char parentName[DSGN_MAX_NAME]; // empty = top-level (child of form)
int32_t left;
int32_t top;
int32_t width;
int32_t height;
int32_t tabIndex;
int32_t maxWidth; // 0 = no cap (stretch to fill)
int32_t maxHeight; // 0 = no cap (stretch to fill)
int32_t weight; // layout weight (0 = fixed size, >0 = share extra space)
DsgnPropT props[DSGN_MAX_PROPS];
int32_t propCount;
WidgetT *widget; // live widget (created at design time for WYSIWYG)
@ -61,32 +64,25 @@ typedef struct {
char caption[DSGN_MAX_TEXT];
int32_t width;
int32_t height;
int32_t left; // initial X position (0 = default)
int32_t top; // initial Y position (0 = default)
char layout[DSGN_MAX_NAME]; // "VBox" or "HBox" (contentBox type)
bool centered; // true = center on screen, false = use left/top
bool autoSize; // true = dvxFitWindow, false = use width/height
bool resizable; // true = user can resize at runtime
DsgnControlT *controls; // stb_ds dynamic array
bool dirty;
WidgetT *contentBox; // VBox parent for live widgets
} DsgnFormT;
// ============================================================
// Toolbox tool IDs
// Active tool
// ============================================================
//
// Empty string = pointer (select/move) mode.
// Non-empty = basName of the widget type to place.
typedef enum {
TOOL_POINTER = 0,
TOOL_BUTTON,
TOOL_LABEL,
TOOL_TEXTBOX,
TOOL_CHECKBOX,
TOOL_OPTION,
TOOL_FRAME,
TOOL_LISTBOX,
TOOL_COMBOBOX,
TOOL_HSCROLL,
TOOL_VSCROLL,
TOOL_TIMER,
TOOL_PICTURE,
TOOL_IMAGE,
TOOL_COUNT
} DsgnToolE;
#define DSGN_TOOL_NONE "" // pointer mode
// ============================================================
// Grab handle IDs
@ -117,7 +113,7 @@ typedef enum {
typedef struct {
DsgnFormT *form;
int32_t selectedIdx; // -1 = form itself selected
DsgnToolE activeTool;
char activeTool[DSGN_MAX_NAME]; // "" = pointer, else basName to place
DsgnModeE mode;
DsgnHandleE activeHandle;
int32_t dragStartY; // mouse Y at drag start (for reorder)
@ -170,12 +166,12 @@ const char *dsgnSelectedName(const DsgnStateT *ds);
// Get the default event name for a control type.
const char *dsgnDefaultEvent(const char *typeName);
// Get the VB type name for a tool ID.
const char *dsgnToolTypeName(DsgnToolE tool);
// Auto-generate a unique control name for the given type.
void dsgnAutoName(const DsgnStateT *ds, const char *typeName, char *buf, int32_t bufSize);
// Check if a control type is a container (can hold children).
bool dsgnIsContainer(const char *typeName);
// Free designer resources.
void dsgnFree(DsgnStateT *ds);

View file

@ -9,6 +9,7 @@
#include "dvxApp.h"
#include "dvxCursor.h"
#include "dvxPlatform.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "dvxWidgetPlugin.h"
@ -18,8 +19,10 @@
#include "widgetLabel.h"
#include "widgetTextInput.h"
#include "widgetDropdown.h"
#include "widgetButton.h"
#include "widgetSplitter.h"
#include "widgetStatusBar.h"
#include "widgetToolbar.h"
#include "ideDesigner.h"
#include "ideToolbox.h"
@ -43,10 +46,6 @@
// Constants
// ============================================================
#define IDE_WIN_X 10
#define IDE_WIN_Y 10
#define IDE_WIN_W 620
#define IDE_WIN_H 460
#define IDE_MAX_SOURCE 65536
#define IDE_MAX_OUTPUT 32768
#define IDE_STEP_SLICE 10000 // VM steps per slice before yielding to DVX
@ -60,6 +59,13 @@
#define CMD_RUN_NOCMP 105
#define CMD_VIEW_CODE 106
#define CMD_VIEW_DESIGN 107
#define CMD_SAVE 108
#define CMD_WIN_CODE 109
#define CMD_WIN_OUTPUT 110
#define CMD_WIN_IMM 111
#define CMD_WIN_TOOLBOX 112
#define CMD_WIN_PROPS 113
#define CMD_DELETE 114
#define IDE_MAX_IMM 1024
#define IDE_DESIGN_W 400
#define IDE_DESIGN_H 300
@ -73,6 +79,9 @@ static void buildWindow(void);
static void clearOutput(void);
static void compileAndRun(void);
static void loadFile(void);
static void loadFilePath(const char *path);
static void saveFile(void);
static void onTbSave(WidgetT *w);
static void onClose(WindowT *win);
static void onMenu(WindowT *win, int32_t menuId);
static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx);
@ -91,8 +100,17 @@ static void setStatus(const char *text);
static void switchToCode(void);
static void switchToDesign(void);
static int32_t onFormWinCursorQuery(WindowT *win, int32_t x, int32_t y);
static void onFormWinKey(WindowT *win, int32_t key, int32_t mod);
static void onFormWinMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons);
static void onFormWinPaint(WindowT *win, RectT *dirtyArea);
static void onTbRun(WidgetT *w);
static void showCodeWindow(void);
static void showOutputWindow(void);
static void showImmediateWindow(void);
static void onTbStop(WidgetT *w);
static void onTbOpen(WidgetT *w);
static void onTbCode(WidgetT *w);
static void onTbDesign(WidgetT *w);
static void updateDropdowns(void);
// ============================================================
@ -101,13 +119,16 @@ static void updateDropdowns(void);
static DxeAppContextT *sCtx = NULL;
static AppContextT *sAc = NULL;
static WindowT *sWin = NULL;
static WidgetT *sEditor = NULL; // TextArea for source code
static WidgetT *sOutput = NULL; // TextArea for program output
static WidgetT *sImmediate = NULL; // TextArea for immediate window
static WidgetT *sObjDropdown = NULL; // Object dropdown
static WidgetT *sEvtDropdown = NULL; // Event dropdown
static WidgetT *sStatus = NULL; // Status bar label
static WindowT *sWin = NULL; // Main toolbar window
static WindowT *sCodeWin = NULL; // Code editor window
static WindowT *sOutWin = NULL; // Output window
static WindowT *sImmWin = NULL; // Immediate window
static WidgetT *sEditor = NULL;
static WidgetT *sOutput = NULL;
static WidgetT *sImmediate = NULL;
static WidgetT *sObjDropdown = NULL;
static WidgetT *sEvtDropdown = NULL;
static WidgetT *sStatus = NULL;
static BasVmT *sVm = NULL; // VM instance (non-NULL while running)
static BasModuleT *sCachedModule = NULL; // Last compiled module (for Ctrl+F5)
static DsgnStateT sDesigner;
@ -159,6 +180,9 @@ int32_t appMain(DxeAppContextT *ctx) {
sOutputBuf[0] = '\0';
sOutputLen = 0;
// Auto-load clickme.bas for development/testing
loadFilePath("C:\\BIN\\APPS\\DVXBASIC\\CLICKME.BAS");
setStatus("Ready. Open a .BAS file or type code and press Run.");
return 0;
}
@ -168,7 +192,8 @@ int32_t appMain(DxeAppContextT *ctx) {
// ============================================================
static void buildWindow(void) {
sWin = dvxCreateWindow(sAc, "DVX BASIC", IDE_WIN_X, IDE_WIN_Y, IDE_WIN_W, IDE_WIN_H, true);
// ---- Main toolbar window (top of screen) ----
sWin = dvxCreateWindow(sAc, "DVX BASIC", 0, 0, sAc->display.width, 200, false);
if (!sWin) {
return;
@ -181,9 +206,13 @@ static void buildWindow(void) {
MenuBarT *menuBar = wmAddMenuBar(sWin);
MenuT *fileMenu = wmAddMenu(menuBar, "&File");
wmAddMenuItem(fileMenu, "&Open...\tCtrl+O", CMD_OPEN);
wmAddMenuItem(fileMenu, "&Save\tCtrl+S", CMD_SAVE);
wmAddMenuSeparator(fileMenu);
wmAddMenuItem(fileMenu, "E&xit", CMD_EXIT);
MenuT *editMenu = wmAddMenu(menuBar, "&Edit");
wmAddMenuItem(editMenu, "&Delete\tDel", CMD_DELETE);
MenuT *runMenu = wmAddMenu(menuBar, "&Run");
wmAddMenuItem(runMenu, "&Run\tF5", CMD_RUN);
wmAddMenuItem(runMenu, "Run &Without Recompile\tCtrl+F5", CMD_RUN_NOCMP);
@ -195,8 +224,17 @@ static void buildWindow(void) {
wmAddMenuItem(viewMenu, "&Code\tF7", CMD_VIEW_CODE);
wmAddMenuItem(viewMenu, "&Object\tShift+F7", CMD_VIEW_DESIGN);
MenuT *winMenu = wmAddMenu(menuBar, "&Window");
wmAddMenuItem(winMenu, "&Code Editor", CMD_WIN_CODE);
wmAddMenuItem(winMenu, "&Output", CMD_WIN_OUTPUT);
wmAddMenuItem(winMenu, "&Immediate", CMD_WIN_IMM);
wmAddMenuSeparator(winMenu);
wmAddMenuItem(winMenu, "&Toolbox", CMD_WIN_TOOLBOX);
wmAddMenuItem(winMenu, "&Properties", CMD_WIN_PROPS);
AccelTableT *accel = dvxCreateAccelTable();
dvxAddAccel(accel, 'O', ACCEL_CTRL, CMD_OPEN);
dvxAddAccel(accel, 'S', ACCEL_CTRL, CMD_SAVE);
dvxAddAccel(accel, KEY_F5, 0, CMD_RUN);
dvxAddAccel(accel, KEY_F5, ACCEL_CTRL, CMD_RUN_NOCMP);
dvxAddAccel(accel, KEY_F7, 0, CMD_VIEW_CODE);
@ -204,59 +242,41 @@ static void buildWindow(void) {
dvxAddAccel(accel, 0x1B, 0, CMD_STOP);
sWin->accelTable = accel;
// Widget tree
WidgetT *root = wgtInitWindow(sAc, sWin);
WidgetT *tbRoot = wgtInitWindow(sAc, sWin);
WidgetT *tb = wgtToolbar(tbRoot);
// Horizontal splitter: editor on top, output+immediate on bottom
WidgetT *splitter = wgtSplitter(root, false);
splitter->weight = 100;
WidgetT *tbOpen = wgtButton(tb, "Open");
tbOpen->onClick = onTbOpen;
// Top pane: source code editor
WidgetT *editorFrame = wgtFrame(splitter, "Source");
WidgetT *tbSave = wgtButton(tb, "Save");
tbSave->onClick = onTbSave;
// Object/Event dropdown row
WidgetT *dropdownRow = wgtHBox(editorFrame);
dropdownRow->spacing = wgtPixels(4);
WidgetT *tbRun = wgtButton(tb, "Run");
tbRun->onClick = onTbRun;
sObjDropdown = wgtDropdown(dropdownRow);
sObjDropdown->weight = 100;
sObjDropdown->onChange = onObjDropdownChange;
wgtDropdownSetItems(sObjDropdown, NULL, 0);
WidgetT *tbStop = wgtButton(tb, "Stop");
tbStop->onClick = onTbStop;
sEvtDropdown = wgtDropdown(dropdownRow);
sEvtDropdown->weight = 100;
sEvtDropdown->onChange = onEvtDropdownChange;
wgtDropdownSetItems(sEvtDropdown, NULL, 0);
WidgetT *tbCode = wgtButton(tb, "Code");
tbCode->onClick = onTbCode;
sEditor = wgtTextArea(editorFrame, IDE_MAX_SOURCE);
sEditor->weight = 100;
wgtTextAreaSetColorize(sEditor, basicColorize, NULL);
wgtTextAreaSetShowLineNumbers(sEditor, true);
wgtTextAreaSetAutoIndent(sEditor, true);
WidgetT *tbDesign = wgtButton(tb, "Design");
tbDesign->onClick = onTbDesign;
WidgetT *statusBar = wgtStatusBar(tbRoot);
sStatus = wgtLabel(statusBar, "");
sStatus->weight = 100;
// Fit height to content, keeping full screen width
dvxFitWindowH(sAc, sWin);
// Initialize designer (form window created on demand)
dsgnInit(&sDesigner, sAc);
// Bottom pane: output + immediate
WidgetT *bottomPane = wgtVBox(splitter);
WidgetT *outputFrame = wgtFrame(bottomPane, "Output");
outputFrame->weight = 70;
sOutput = wgtTextArea(outputFrame, IDE_MAX_OUTPUT);
sOutput->weight = 100;
sOutput->readOnly = true;
WidgetT *immFrame = wgtFrame(bottomPane, "Immediate");
immFrame->weight = 30;
sImmediate = wgtTextArea(immFrame, IDE_MAX_IMM);
sImmediate->weight = 100;
sImmediate->onChange = onImmediateChange;
// Status bar
WidgetT *statusBar = wgtStatusBar(root);
sStatus = wgtLabel(statusBar, "");
sStatus->weight = 100;
// Create child windows
showCodeWindow();
showOutputWindow();
showImmediateWindow();
}
// ============================================================
@ -635,6 +655,17 @@ static void immPrintCallback(void *ctx, const char *text, bool newline) {
immBuf[curLen] = '\0';
wgtSetText(sImmediate, immBuf);
// Move cursor to end so user can keep typing
int32_t lines = 1;
for (int32_t i = 0; i < curLen; i++) {
if (immBuf[i] == '\n') {
lines++;
}
}
wgtTextAreaGoToLine(sImmediate, lines);
}
}
@ -716,6 +747,44 @@ static bool inputCallback(void *ctx, const char *prompt, char *buf, int32_t bufS
// loadFile
// ============================================================
static void loadFilePath(const char *path) {
FILE *f = fopen(path, "r");
if (!f) {
return;
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size >= IDE_MAX_SOURCE - 1) {
fclose(f);
return;
}
int32_t bytesRead = (int32_t)fread(sSourceBuf, 1, size, f);
fclose(f);
sSourceBuf[bytesRead] = '\0';
wgtSetText(sEditor, sSourceBuf);
snprintf(sFilePath, sizeof(sFilePath), "%s", path);
char title[300];
snprintf(title, sizeof(title), "DVX BASIC - %s", path);
dvxSetTitle(sAc, sWin, title);
if (sFormWin) {
onFormWinClose(sFormWin);
}
dsgnFree(&sDesigner);
updateDropdowns();
setStatus("File loaded.");
}
static void loadFile(void) {
FileFilterT filters[] = {
{ "BASIC Files (*.bas)", "*.bas" },
@ -726,48 +795,72 @@ static void loadFile(void) {
char path[260];
if (dvxFileDialog(sAc, "Open BASIC File", FD_OPEN, NULL, filters, 3, path, sizeof(path))) {
FILE *f = fopen(path, "r");
if (!f) {
dvxMessageBox(sAc, "Error", "Could not open file.", MB_OK | MB_ICONERROR);
return;
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size >= IDE_MAX_SOURCE - 1) {
fclose(f);
dvxMessageBox(sAc, "Error", "File too large.", MB_OK | MB_ICONERROR);
return;
}
int32_t bytesRead = (int32_t)fread(sSourceBuf, 1, size, f);
fclose(f);
sSourceBuf[bytesRead] = '\0';
wgtSetText(sEditor, sSourceBuf);
strncpy(sFilePath, path, sizeof(sFilePath) - 1);
sFilePath[sizeof(sFilePath) - 1] = '\0';
// Update window title
char title[300];
snprintf(title, sizeof(title), "DVX BASIC - %s", path);
dvxSetTitle(sAc, sWin, title);
// Close any open form designer and clear cached form
if (sFormWin) {
onFormWinClose(sFormWin);
}
dsgnFree(&sDesigner);
updateDropdowns();
setStatus("File loaded.");
loadFilePath(path);
}
}
// ============================================================
// saveFile
// ============================================================
static void saveFile(void) {
if (sFilePath[0] == '\0') {
// No file loaded -- use Save As dialog
FileFilterT filters[] = {
{ "BASIC Files (*.bas)", "*.bas" },
{ "All Files (*.*)", "*.*" }
};
if (!dvxFileDialog(sAc, "Save BASIC File", FD_SAVE, NULL, filters, 2, sFilePath, sizeof(sFilePath))) {
return;
}
}
// Save the .bas source
const char *src = wgtGetText(sEditor);
if (src) {
FILE *f = fopen(sFilePath, "w");
if (f) {
fputs(src, f);
fclose(f);
} else {
dvxMessageBox(sAc, "Error", "Could not write file.", MB_OK | MB_ICONERROR);
return;
}
}
// Save the .frm if the designer has form data
if (sDesigner.form && sDesigner.form->dirty) {
char frmPath[260];
snprintf(frmPath, sizeof(frmPath), "%s", sFilePath);
char *dot = strrchr(frmPath, '.');
if (dot) {
strcpy(dot, ".frm");
} else {
strcat(frmPath, ".frm");
}
char frmBuf[IDE_MAX_SOURCE];
int32_t frmLen = dsgnSaveFrm(&sDesigner, frmBuf, sizeof(frmBuf));
if (frmLen > 0) {
FILE *f = fopen(frmPath, "w");
if (f) {
fwrite(frmBuf, 1, frmLen, f);
fclose(f);
sDesigner.form->dirty = false;
}
}
}
setStatus("Saved.");
}
// ============================================================
// loadFrmFiles
// ============================================================
@ -840,7 +933,27 @@ static void loadFrmFiles(BasFormRtT *rt) {
// ============================================================
static void onClose(WindowT *win) {
// Close all child windows
if (sCodeWin && sCodeWin != win) {
dvxDestroyWindow(sAc, sCodeWin);
}
if (sOutWin && sOutWin != win) {
dvxDestroyWindow(sAc, sOutWin);
}
if (sImmWin && sImmWin != win) {
dvxDestroyWindow(sAc, sImmWin);
}
if (sFormWin) {
onFormWinClose(sFormWin);
}
sWin = NULL;
sCodeWin = NULL;
sOutWin = NULL;
sImmWin = NULL;
sEditor = NULL;
sOutput = NULL;
sImmediate = NULL;
@ -853,10 +966,6 @@ static void onClose(WindowT *win) {
sCachedModule = NULL;
}
if (sFormWin) {
onFormWinClose(sFormWin);
}
dsgnFree(&sDesigner);
arrfree(sProcTable);
@ -881,6 +990,10 @@ static void onMenu(WindowT *win, int32_t menuId) {
loadFile();
break;
case CMD_SAVE:
saveFile();
break;
case CMD_RUN:
compileAndRun();
break;
@ -908,6 +1021,44 @@ static void onMenu(WindowT *win, int32_t menuId) {
switchToDesign();
break;
case CMD_WIN_CODE:
showCodeWindow();
break;
case CMD_WIN_OUTPUT:
showOutputWindow();
break;
case CMD_WIN_IMM:
showImmediateWindow();
break;
case CMD_WIN_TOOLBOX:
if (!sToolboxWin) {
sToolboxWin = tbxCreate(sAc, &sDesigner);
}
break;
case CMD_WIN_PROPS:
if (!sPropsWin) {
sPropsWin = prpCreate(sAc, &sDesigner);
}
break;
case CMD_DELETE:
if (sFormWin && sDesigner.selectedIdx >= 0) {
int32_t prevCount = (int32_t)arrlen(sDesigner.form->controls);
dsgnOnKey(&sDesigner, KEY_DELETE);
int32_t newCount = (int32_t)arrlen(sDesigner.form->controls);
if (newCount != prevCount) {
prpRebuildTree(&sDesigner);
prpRefresh(&sDesigner);
dvxInvalidateWindow(sAc, sFormWin);
}
}
break;
case CMD_EXIT:
if (sWin) {
onClose(sWin);
@ -1090,6 +1241,35 @@ static void printCallback(void *ctx, const char *text, bool newline) {
// Handle mouse events on the form designer window. Coordinates
// are relative to the window's client area (content box origin).
// ============================================================
// onFormWinKey
// ============================================================
static void onFormWinKey(WindowT *win, int32_t key, int32_t mod) {
(void)mod;
if (key == KEY_DELETE && sDesigner.selectedIdx >= 0) {
int32_t prevCount = sDesigner.form ? (int32_t)arrlen(sDesigner.form->controls) : 0;
dsgnOnKey(&sDesigner, KEY_DELETE);
int32_t newCount = sDesigner.form ? (int32_t)arrlen(sDesigner.form->controls) : 0;
if (newCount != prevCount) {
prpRebuildTree(&sDesigner);
prpRefresh(&sDesigner);
if (sFormWin) {
dvxInvalidateWindow(sAc, sFormWin);
}
}
return;
}
// Forward unhandled keys to the widget system
widgetOnKey(win, key, mod);
}
// ============================================================
// onFormWinCursorQuery
// ============================================================
@ -1097,7 +1277,16 @@ static void printCallback(void *ctx, const char *text, bool newline) {
static int32_t onFormWinCursorQuery(WindowT *win, int32_t x, int32_t y) {
(void)win;
if (!sDesigner.form || !sDesigner.form->controls) {
if (!sDesigner.form) {
return 0;
}
// Crosshair when placing a new control
if (sDesigner.activeTool[0] != '\0') {
return CURSOR_CROSSHAIR;
}
if (!sDesigner.form->controls) {
return 0;
}
@ -1109,7 +1298,7 @@ static int32_t onFormWinCursorQuery(WindowT *win, int32_t x, int32_t y) {
DsgnControlT *ctrl = &sDesigner.form->controls[sDesigner.selectedIdx];
if (!ctrl->widget || ctrl->widget->w <= 0 || ctrl->widget->h <= 0) {
if (!ctrl->widget || !ctrl->widget->visible || ctrl->widget->w <= 0 || ctrl->widget->h <= 0) {
return 0;
}
@ -1150,8 +1339,19 @@ static void onFormWinMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons)
}
if (isDown) {
int32_t prevCount = sDesigner.form ? (int32_t)arrlen(sDesigner.form->controls) : 0;
bool wasDirty = sDesigner.form ? sDesigner.form->dirty : false;
bool drag = wasDown;
dsgnOnMouse(&sDesigner, x, y, drag);
// Rebuild tree if controls were added, removed, or reordered
int32_t newCount = sDesigner.form ? (int32_t)arrlen(sDesigner.form->controls) : 0;
bool nowDirty = sDesigner.form ? sDesigner.form->dirty : false;
if (newCount != prevCount || (nowDirty && !wasDirty)) {
prpRebuildTree(&sDesigner);
}
prpRefresh(&sDesigner);
if (sFormWin) {
@ -1299,12 +1499,20 @@ static void switchToDesign(void) {
sDesigner.formWin = sFormWin;
WidgetT *root = wgtInitWindow(sAc, sFormWin);
WidgetT *contentBox = wgtVBox(root);
WidgetT *contentBox;
if (sDesigner.form && strcasecmp(sDesigner.form->layout, "HBox") == 0) {
contentBox = wgtHBox(root);
} else {
contentBox = wgtVBox(root);
}
contentBox->weight = 100;
// Override paint and mouse AFTER wgtInitWindow (which sets widgetOnPaint)
sFormWin->onPaint = onFormWinPaint;
sFormWin->onMouse = onFormWinMouse;
sFormWin->onKey = onFormWinKey;
sFormWin->onCursorQuery = onFormWinCursorQuery;
// Create live widgets for each control
@ -1317,8 +1525,14 @@ static void switchToDesign(void) {
dvxSetTitle(sAc, sFormWin, winTitle);
}
// Shrink-wrap the window to its content (matches runtime behavior)
// Size the form window
if (sDesigner.form && sDesigner.form->autoSize) {
dvxFitWindow(sAc, sFormWin);
sDesigner.form->width = sFormWin->w;
sDesigner.form->height = sFormWin->h;
} else if (sDesigner.form) {
dvxResizeWindow(sAc, sFormWin, sDesigner.form->width, sDesigner.form->height);
}
// Create toolbox and properties windows
if (!sToolboxWin) {
@ -1333,6 +1547,129 @@ static void switchToDesign(void) {
}
// ============================================================
// Toolbar button handlers
// ============================================================
static void onTbOpen(WidgetT *w) { (void)w; loadFile(); }
static void onTbSave(WidgetT *w) { (void)w; saveFile(); }
static void onTbRun(WidgetT *w) { (void)w; compileAndRun(); }
static void onTbStop(WidgetT *w) { (void)w; if (sVm) { sVm->running = false; setStatus("Program stopped."); } }
static void onTbCode(WidgetT *w) { (void)w; switchToCode(); }
static void onTbDesign(WidgetT *w) { (void)w; switchToDesign(); }
// ============================================================
// showCodeWindow
// ============================================================
static void showCodeWindow(void) {
if (sCodeWin) {
return; // already open
}
int32_t codeY = sWin ? sWin->y + sWin->h + 2 : 60;
int32_t codeH = sAc->display.height - codeY - 122;
sCodeWin = dvxCreateWindow(sAc, "Code", 0, codeY, sAc->display.width, codeH, true);
if (sCodeWin) {
sCodeWin->onMenu = onMenu;
sCodeWin->accelTable = sWin ? sWin->accelTable : NULL;
WidgetT *codeRoot = wgtInitWindow(sAc, sCodeWin);
WidgetT *dropdownRow = wgtHBox(codeRoot);
dropdownRow->spacing = wgtPixels(4);
sObjDropdown = wgtDropdown(dropdownRow);
sObjDropdown->weight = 100;
sObjDropdown->onChange = onObjDropdownChange;
wgtDropdownSetItems(sObjDropdown, NULL, 0);
sEvtDropdown = wgtDropdown(dropdownRow);
sEvtDropdown->weight = 100;
sEvtDropdown->onChange = onEvtDropdownChange;
wgtDropdownSetItems(sEvtDropdown, NULL, 0);
sEditor = wgtTextArea(codeRoot, IDE_MAX_SOURCE);
sEditor->weight = 100;
wgtTextAreaSetColorize(sEditor, basicColorize, NULL);
wgtTextAreaSetShowLineNumbers(sEditor, true);
wgtTextAreaSetAutoIndent(sEditor, true);
if (sFilePath[0] && sSourceBuf[0]) {
wgtSetText(sEditor, sSourceBuf);
}
updateDropdowns();
}
}
// ============================================================
// showOutputWindow
// ============================================================
static void showOutputWindow(void) {
if (sOutWin) {
return;
}
int32_t outH = 120;
int32_t outY = sAc->display.height - outH;
sOutWin = dvxCreateWindow(sAc, "Output", 0, outY, sAc->display.width / 2, outH, true);
if (sOutWin) {
WidgetT *outRoot = wgtInitWindow(sAc, sOutWin);
sOutput = wgtTextArea(outRoot, IDE_MAX_OUTPUT);
sOutput->weight = 100;
sOutput->readOnly = true;
if (sOutputLen > 0) {
wgtSetText(sOutput, sOutputBuf);
}
}
}
// ============================================================
// showImmediateWindow
// ============================================================
static void showImmediateWindow(void) {
if (sImmWin) {
return;
}
int32_t outH = 120;
int32_t outY = sAc->display.height - outH;
sImmWin = dvxCreateWindow(sAc, "Immediate", sAc->display.width / 2, outY, sAc->display.width / 2, outH, true);
if (sImmWin) {
WidgetT *immRoot = wgtInitWindow(sAc, sImmWin);
if (immRoot) {
sImmediate = wgtTextArea(immRoot, IDE_MAX_IMM);
if (sImmediate) {
sImmediate->weight = 100;
sImmediate->readOnly = false;
sImmediate->onChange = onImmediateChange;
} else {
dvxLog("IDE: failed to create immediate TextArea");
}
} else {
dvxLog("IDE: failed to init immediate window root");
}
}
}
// ============================================================
// setStatus
// ============================================================

View file

@ -0,0 +1,906 @@
// ideProperties.c -- DVX BASIC form designer properties window
//
// A floating window with a TreeView listing all controls on the
// form (for selection and drag-reorder) and a ListView showing
// editable properties of the selected control. Double-click a
// property value to edit it via an InputBox dialog.
#include "ideProperties.h"
#include "dvxDialog.h"
#include "dvxWm.h"
#include "widgetBox.h"
#include "widgetListView.h"
#include "widgetSplitter.h"
#include "widgetTreeView.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
// ============================================================
// Constants
// ============================================================
#define PRP_WIN_W 220
#define PRP_WIN_H 400
// ============================================================
// Module state
// ============================================================
static DsgnStateT *sDs = NULL;
static WindowT *sPrpWin = NULL;
static WidgetT *sTree = NULL;
static WidgetT *sPropList = NULL;
static AppContextT *sPrpCtx = NULL;
static bool sUpdating = false;
static char **sTreeLabels = NULL; // stb_ds array of strdup'd strings
static char **sCellData = NULL; // stb_ds array of strdup'd strings
static int32_t sCellRows = 0;
// ============================================================
// Helpers
// ============================================================
static void freeTreeLabels(void) {
int32_t count = (int32_t)arrlen(sTreeLabels);
for (int32_t i = 0; i < count; i++) {
free(sTreeLabels[i]);
}
arrsetlen(sTreeLabels, 0);
}
static void freeCellData(void) {
int32_t count = (int32_t)arrlen(sCellData);
for (int32_t i = 0; i < count; i++) {
free(sCellData[i]);
}
arrsetlen(sCellData, 0);
sCellRows = 0;
}
static void addPropRow(const char *name, const char *value) {
arrput(sCellData, strdup(name));
arrput(sCellData, strdup(value ? value : ""));
sCellRows++;
}
// ============================================================
// Prototypes
// ============================================================
static void onPrpClose(WindowT *win);
static void onPropDblClick(WidgetT *w);
static void onTreeItemClick(WidgetT *w);
static void onTreeReorder(WidgetT *w);
// ============================================================
// onPrpClose
// ============================================================
static void onPrpClose(WindowT *win) {
(void)win;
}
// ============================================================
// getPropType
// ============================================================
//
// Determine the data type of a property by name. Checks built-in
// properties first, then looks up the widget's interface descriptor.
// Returns WGT_IFACE_STRING, WGT_IFACE_INT, or WGT_IFACE_BOOL.
#define PROP_TYPE_STRING WGT_IFACE_STRING
#define PROP_TYPE_INT WGT_IFACE_INT
#define PROP_TYPE_BOOL WGT_IFACE_BOOL
#define PROP_TYPE_READONLY 255
static uint8_t getPropType(const char *propName, const char *typeName) {
// Read-only properties
if (strcasecmp(propName, "Type") == 0) { return PROP_TYPE_READONLY; }
// Known built-in types
if (strcasecmp(propName, "Name") == 0) { return PROP_TYPE_STRING; }
if (strcasecmp(propName, "Caption") == 0) { return PROP_TYPE_STRING; }
if (strcasecmp(propName, "MinWidth") == 0) { return PROP_TYPE_INT; }
if (strcasecmp(propName, "MinHeight") == 0) { return PROP_TYPE_INT; }
if (strcasecmp(propName, "MaxWidth") == 0) { return PROP_TYPE_INT; }
if (strcasecmp(propName, "MaxHeight") == 0) { return PROP_TYPE_INT; }
if (strcasecmp(propName, "Weight") == 0) { return PROP_TYPE_INT; }
if (strcasecmp(propName, "Left") == 0) { return PROP_TYPE_INT; }
if (strcasecmp(propName, "Top") == 0) { return PROP_TYPE_INT; }
if (strcasecmp(propName, "AutoSize") == 0) { return PROP_TYPE_BOOL; }
if (strcasecmp(propName, "Resizable") == 0) { return PROP_TYPE_BOOL; }
if (strcasecmp(propName, "Centered") == 0) { return PROP_TYPE_BOOL; }
if (strcasecmp(propName, "Visible") == 0) { return PROP_TYPE_BOOL; }
if (strcasecmp(propName, "Enabled") == 0) { return PROP_TYPE_BOOL; }
// Look up in the widget's interface descriptor
if (typeName && typeName[0]) {
const char *wgtName = wgtFindByBasName(typeName);
if (wgtName) {
const WgtIfaceT *iface = wgtGetIface(wgtName);
if (iface) {
for (int32_t i = 0; i < iface->propCount; i++) {
if (strcasecmp(iface->props[i].name, propName) == 0) {
return iface->props[i].type;
}
}
}
}
}
return PROP_TYPE_STRING;
}
// ============================================================
// cascadeToChildren
// ============================================================
//
// Recursively apply Visible or Enabled to all descendants of a
// container control.
static void cascadeToChildren(DsgnStateT *ds, const char *parentName, bool visible, bool enabled) {
int32_t count = (int32_t)arrlen(ds->form->controls);
for (int32_t i = 0; i < count; i++) {
DsgnControlT *child = &ds->form->controls[i];
if (strcasecmp(child->parentName, parentName) != 0) {
continue;
}
if (child->widget) {
wgtSetVisible(child->widget, visible);
wgtSetEnabled(child->widget, enabled);
}
// Recurse into nested containers
if (dsgnIsContainer(child->typeName)) {
cascadeToChildren(ds, child->name, visible, enabled);
}
}
}
// ============================================================
// onPropDblClick
// ============================================================
static void onPropDblClick(WidgetT *w) {
if (!sDs || !sDs->form || !sPropList || !sPrpCtx) {
return;
}
int32_t row = wgtListViewGetSelected(w);
if (row < 0 || row >= sCellRows) {
return;
}
const char *propName = sCellData[row * 2];
const char *curValue = sCellData[row * 2 + 1];
// Layout toggles directly -- no input box needed
if (strcasecmp(propName, "Layout") == 0 && sDs->selectedIdx < 0) {
if (strcasecmp(sDs->form->layout, "VBox") == 0) {
snprintf(sDs->form->layout, DSGN_MAX_NAME, "HBox");
} else {
snprintf(sDs->form->layout, DSGN_MAX_NAME, "VBox");
}
sDs->form->dirty = true;
// Replace the content box with the new layout type
if (sDs->formWin && sDs->formWin->widgetRoot) {
WidgetT *root = sDs->formWin->widgetRoot;
// Remove old content box
root->firstChild = NULL;
root->lastChild = NULL;
// Create new content box
WidgetT *contentBox;
if (strcasecmp(sDs->form->layout, "HBox") == 0) {
contentBox = wgtHBox(root);
} else {
contentBox = wgtVBox(root);
}
contentBox->weight = 100;
// Clear widget pointers and recreate
int32_t cc = (int32_t)arrlen(sDs->form->controls);
for (int32_t ci = 0; ci < cc; ci++) {
sDs->form->controls[ci].widget = NULL;
}
dsgnCreateWidgets(sDs, contentBox);
if (sDs->formWin) {
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
}
}
prpRefresh(sDs);
return;
}
// Get the control's type name for iface lookup
const char *ctrlTypeName = "";
int32_t ctrlCount = (int32_t)arrlen(sDs->form->controls);
if (sDs->selectedIdx >= 0 && sDs->selectedIdx < ctrlCount) {
ctrlTypeName = sDs->form->controls[sDs->selectedIdx].typeName;
}
uint8_t propType = getPropType(propName, ctrlTypeName);
if (propType == PROP_TYPE_READONLY) {
return;
}
char newValue[DSGN_MAX_TEXT];
if (propType == PROP_TYPE_BOOL) {
// Toggle boolean on double-click -- no input box
bool cur = (strcasecmp(curValue, "True") == 0);
snprintf(newValue, sizeof(newValue), "%s", cur ? "False" : "True");
} else if (propType == PROP_TYPE_INT) {
// Spinner dialog for integers
char prompt[128];
snprintf(prompt, sizeof(prompt), "%s:", propName);
int32_t intVal = atoi(curValue);
if (!dvxIntInputBox(sPrpCtx, "Edit Property", prompt, intVal, INT32_MIN, INT32_MAX, 1, &intVal)) {
return;
}
snprintf(newValue, sizeof(newValue), "%d", (int)intVal);
} else {
// Text input for strings
char prompt[128];
snprintf(prompt, sizeof(prompt), "%s:", propName);
snprintf(newValue, sizeof(newValue), "%s", curValue);
if (!dvxInputBox(sPrpCtx, "Edit Property", prompt, curValue, newValue, sizeof(newValue))) {
return;
}
}
int32_t count = (int32_t)arrlen(sDs->form->controls);
if (sDs->selectedIdx >= 0 && sDs->selectedIdx < count) {
DsgnControlT *ctrl = &sDs->form->controls[sDs->selectedIdx];
if (strcasecmp(propName, "Name") == 0) {
snprintf(ctrl->name, DSGN_MAX_NAME, "%.31s", newValue);
if (ctrl->widget) {
wgtSetName(ctrl->widget, ctrl->name);
}
prpRebuildTree(sDs);
} else if (strcasecmp(propName, "MinWidth") == 0) {
ctrl->width = atoi(newValue);
if (ctrl->widget) {
ctrl->widget->minW = wgtPixels(ctrl->width);
}
} else if (strcasecmp(propName, "MinHeight") == 0) {
ctrl->height = atoi(newValue);
if (ctrl->widget) {
ctrl->widget->minH = wgtPixels(ctrl->height);
}
} else if (strcasecmp(propName, "MaxWidth") == 0) {
ctrl->maxWidth = atoi(newValue);
if (ctrl->widget) {
ctrl->widget->maxW = ctrl->maxWidth > 0 ? wgtPixels(ctrl->maxWidth) : 0;
}
} else if (strcasecmp(propName, "MaxHeight") == 0) {
ctrl->maxHeight = atoi(newValue);
if (ctrl->widget) {
ctrl->widget->maxH = ctrl->maxHeight > 0 ? wgtPixels(ctrl->maxHeight) : 0;
}
} else if (strcasecmp(propName, "Weight") == 0) {
ctrl->weight = atoi(newValue);
if (ctrl->widget) {
ctrl->widget->weight = ctrl->weight;
}
} else if (strcasecmp(propName, "Visible") == 0) {
bool val = (strcasecmp(newValue, "True") == 0);
if (ctrl->widget) {
wgtSetVisible(ctrl->widget, val);
}
if (dsgnIsContainer(ctrl->typeName)) {
bool en = ctrl->widget ? ctrl->widget->enabled : true;
cascadeToChildren(sDs, ctrl->name, val, en);
}
} else if (strcasecmp(propName, "Enabled") == 0) {
bool val = (strcasecmp(newValue, "True") == 0);
if (ctrl->widget) {
wgtSetEnabled(ctrl->widget, val);
}
if (dsgnIsContainer(ctrl->typeName)) {
bool vis = ctrl->widget ? ctrl->widget->visible : true;
cascadeToChildren(sDs, ctrl->name, vis, val);
}
} else {
// Try widget iface setter first
bool ifaceHandled = false;
if (ctrl->widget) {
const char *wgtName = wgtFindByBasName(ctrl->typeName);
if (wgtName) {
const WgtIfaceT *iface = wgtGetIface(wgtName);
if (iface) {
for (int32_t i = 0; i < iface->propCount; i++) {
const WgtPropDescT *p = &iface->props[i];
if (strcasecmp(p->name, propName) != 0 || !p->setFn) {
continue;
}
if (p->type == WGT_IFACE_STRING) {
// Store in props for persistence, set from there
bool found = false;
for (int32_t j = 0; j < ctrl->propCount; j++) {
if (strcasecmp(ctrl->props[j].name, propName) == 0) {
snprintf(ctrl->props[j].value, DSGN_MAX_TEXT, "%s", newValue);
((void (*)(WidgetT *, const char *))p->setFn)(ctrl->widget, ctrl->props[j].value);
found = true;
break;
}
}
if (!found && ctrl->propCount < DSGN_MAX_PROPS) {
snprintf(ctrl->props[ctrl->propCount].name, DSGN_MAX_NAME, "%s", propName);
snprintf(ctrl->props[ctrl->propCount].value, DSGN_MAX_TEXT, "%s", newValue);
((void (*)(WidgetT *, const char *))p->setFn)(ctrl->widget, ctrl->props[ctrl->propCount].value);
ctrl->propCount++;
}
} else if (p->type == WGT_IFACE_INT) {
((void (*)(WidgetT *, int32_t))p->setFn)(ctrl->widget, atoi(newValue));
} else if (p->type == WGT_IFACE_BOOL) {
((void (*)(WidgetT *, bool))p->setFn)(ctrl->widget, strcasecmp(newValue, "True") == 0);
}
ifaceHandled = true;
break;
}
}
}
}
if (!ifaceHandled) {
// Custom prop storage
bool found = false;
for (int32_t i = 0; i < ctrl->propCount; i++) {
if (strcasecmp(ctrl->props[i].name, propName) == 0) {
snprintf(ctrl->props[i].value, DSGN_MAX_TEXT, "%s", newValue);
found = true;
break;
}
}
if (!found && ctrl->propCount < DSGN_MAX_PROPS) {
snprintf(ctrl->props[ctrl->propCount].name, DSGN_MAX_NAME, "%s", propName);
snprintf(ctrl->props[ctrl->propCount].value, DSGN_MAX_TEXT, "%s", newValue);
ctrl->propCount++;
}
// Update widget text from the persistent props array
if (ctrl->widget && (strcasecmp(propName, "Caption") == 0 || strcasecmp(propName, "Text") == 0)) {
for (int32_t i = 0; i < ctrl->propCount; i++) {
if (strcasecmp(ctrl->props[i].name, propName) == 0) {
wgtSetText(ctrl->widget, ctrl->props[i].value);
break;
}
}
}
}
}
sDs->form->dirty = true;
if (sDs->formWin) {
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
}
} else {
if (strcasecmp(propName, "Caption") == 0) {
snprintf(sDs->form->caption, DSGN_MAX_TEXT, "%s", newValue);
if (sDs->formWin) {
char winTitle[280];
snprintf(winTitle, sizeof(winTitle), "%s [Design]", sDs->form->caption);
dvxSetTitle(sPrpCtx, sDs->formWin, winTitle);
}
} else if (strcasecmp(propName, "AutoSize") == 0) {
sDs->form->autoSize = (strcasecmp(newValue, "True") == 0);
if (sDs->form->autoSize && sDs->formWin) {
dvxFitWindow(sPrpCtx, sDs->formWin);
sDs->form->width = sDs->formWin->w;
sDs->form->height = sDs->formWin->h;
}
} else if (strcasecmp(propName, "Resizable") == 0) {
sDs->form->resizable = (strcasecmp(newValue, "True") == 0);
if (sDs->formWin) {
sDs->formWin->resizable = sDs->form->resizable;
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
}
} else if (strcasecmp(propName, "Centered") == 0) {
sDs->form->centered = (strcasecmp(newValue, "True") == 0);
} else if (strcasecmp(propName, "Left") == 0) {
sDs->form->left = atoi(newValue);
} else if (strcasecmp(propName, "Top") == 0) {
sDs->form->top = atoi(newValue);
} else if (strcasecmp(propName, "Width") == 0) {
sDs->form->width = atoi(newValue);
sDs->form->autoSize = false;
} else if (strcasecmp(propName, "Height") == 0) {
sDs->form->height = atoi(newValue);
sDs->form->autoSize = false;
}
sDs->form->dirty = true;
// Resize the form designer window
if (sDs->formWin) {
if (sDs->form->autoSize) {
dvxFitWindow(sPrpCtx, sDs->formWin);
sDs->form->width = sDs->formWin->w;
sDs->form->height = sDs->formWin->h;
} else {
dvxResizeWindow(sPrpCtx, sDs->formWin, sDs->form->width, sDs->form->height);
}
}
}
prpRefresh(sDs);
}
// ============================================================
// onTreeItemClick
// ============================================================
static void onTreeItemClick(WidgetT *w) {
(void)w;
if (!sDs || !sDs->form || !sTree || sUpdating) {
return;
}
// Check if it's the form item
WidgetT *formItem = sTree->firstChild;
if (w == formItem) {
sDs->selectedIdx = -1;
if (sDs->formWin) {
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
}
prpRefresh(sDs);
return;
}
// Match by label text against control names
const char *label = (const char *)w->userData;
if (!label) {
return;
}
// Extract name from "Name (Type)"
char clickedName[DSGN_MAX_NAME];
int32_t ni = 0;
while (label[ni] && label[ni] != ' ' && ni < DSGN_MAX_NAME - 1) {
clickedName[ni] = label[ni];
ni++;
}
clickedName[ni] = '\0';
int32_t count = (int32_t)arrlen(sDs->form->controls);
for (int32_t i = 0; i < count; i++) {
if (strcmp(sDs->form->controls[i].name, clickedName) == 0) {
sDs->selectedIdx = i;
if (sDs->formWin) {
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
}
prpRefresh(sDs);
return;
}
}
}
// ============================================================
// onTreeReorder
// ============================================================
// Walk tree items recursively, collecting control names in order.
static void collectTreeOrder(WidgetT *parent, DsgnControlT *srcArr, int32_t srcCount, DsgnControlT **outArr, const char *parentName) {
for (WidgetT *item = parent->firstChild; item; item = item->nextSibling) {
const char *label = (const char *)item->userData;
if (!label) {
continue;
}
char itemName[DSGN_MAX_NAME];
int32_t ni = 0;
while (label[ni] && label[ni] != ' ' && ni < DSGN_MAX_NAME - 1) {
itemName[ni] = label[ni];
ni++;
}
itemName[ni] = '\0';
for (int32_t i = 0; i < srcCount; i++) {
if (strcmp(srcArr[i].name, itemName) == 0) {
DsgnControlT ctrl = srcArr[i];
snprintf(ctrl.parentName, DSGN_MAX_NAME, "%s", parentName);
arrput(*outArr, ctrl);
// Recurse into children (for containers)
if (item->firstChild) {
collectTreeOrder(item, srcArr, srcCount, outArr, itemName);
}
break;
}
}
}
}
static void onTreeReorder(WidgetT *w) {
(void)w;
if (!sDs || !sDs->form || !sTree || sUpdating) {
return;
}
int32_t count = (int32_t)arrlen(sDs->form->controls);
DsgnControlT *newArr = NULL;
WidgetT *formItem = sTree->firstChild;
if (!formItem) {
return;
}
// Collect all controls from the tree in their new order,
// handling nesting (items dragged into containers get parentName updated).
collectTreeOrder(formItem, sDs->form->controls, count, &newArr, "");
// If we lost items (dragged above form), revert
if ((int32_t)arrlen(newArr) != count) {
arrfree(newArr);
prpRebuildTree(sDs);
return;
}
arrfree(sDs->form->controls);
sDs->form->controls = newArr;
sDs->form->dirty = true;
if (sDs->form->contentBox) {
sDs->form->contentBox->firstChild = NULL;
sDs->form->contentBox->lastChild = NULL;
int32_t newCount = (int32_t)arrlen(sDs->form->controls);
for (int32_t i = 0; i < newCount; i++) {
sDs->form->controls[i].widget = NULL;
}
dsgnCreateWidgets(sDs, sDs->form->contentBox);
}
prpRebuildTree(sDs);
if (sDs->formWin) {
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
}
prpRefresh(sDs);
}
// ============================================================
// prpCreate
// ============================================================
WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds) {
sDs = ds;
sPrpCtx = ctx;
int32_t winX = ctx->display.width - PRP_WIN_W - 10;
WindowT *win = dvxCreateWindow(ctx, "Properties", winX, 30, PRP_WIN_W, PRP_WIN_H, true);
if (!win) {
return NULL;
}
win->onClose = onPrpClose;
sPrpWin = win;
WidgetT *root = wgtInitWindow(ctx, win);
// Splitter: tree on top, property list on bottom
WidgetT *splitter = wgtSplitter(root, false);
splitter->weight = 100;
wgtSplitterSetPos(splitter, (PRP_WIN_H - CHROME_TOTAL_TOP - CHROME_TOTAL_BOTTOM) / 2);
// Control tree (top pane)
sTree = wgtTreeView(splitter);
sTree->onChange = onTreeReorder;
wgtTreeViewSetReorderable(sTree, true);
// Property ListView (bottom pane)
sPropList = wgtListView(splitter);
sPropList->onDblClick = onPropDblClick;
static const ListViewColT cols[2] = {
{ "Property", 0, ListViewAlignLeftE },
{ "Value", 0, ListViewAlignLeftE }
};
wgtListViewSetColumns(sPropList, cols, 2);
prpRebuildTree(ds);
prpRefresh(ds);
return win;
}
// ============================================================
// prpDestroy
// ============================================================
void prpDestroy(AppContextT *ctx, WindowT *win) {
freeTreeLabels();
arrfree(sTreeLabels);
sTreeLabels = NULL;
freeCellData();
arrfree(sCellData);
sCellData = NULL;
if (win) {
dvxDestroyWindow(ctx, win);
}
sPrpWin = NULL;
sTree = NULL;
sPropList = NULL;
sDs = NULL;
}
// ============================================================
// prpRebuildTree
// ============================================================
void prpRebuildTree(DsgnStateT *ds) {
if (!sTree || !ds || !ds->form) {
return;
}
sUpdating = true;
freeTreeLabels();
sTree->firstChild = NULL;
sTree->lastChild = NULL;
// Form entry at the top
char *formLabel = strdup(ds->form->name);
arrput(sTreeLabels, formLabel);
WidgetT *formItem = wgtTreeItem(sTree, formLabel);
formItem->userData = formLabel;
formItem->onClick = onTreeItemClick;
wgtTreeItemSetExpanded(formItem, true);
if (ds->selectedIdx < 0) {
wgtTreeItemSetSelected(formItem, true);
}
// Control entries -- nest children under container parents
int32_t count = (int32_t)arrlen(ds->form->controls);
// Temporary array to map control index -> tree item
WidgetT **treeItems = NULL;
for (int32_t i = 0; i < count; i++) {
DsgnControlT *ctrl = &ds->form->controls[i];
char buf[128];
snprintf(buf, sizeof(buf), "%s (%s)", ctrl->name, ctrl->typeName);
char *label = strdup(buf);
arrput(sTreeLabels, label);
// Find the tree parent: form item or a container's tree item
WidgetT *treeParent = formItem;
if (ctrl->parentName[0]) {
for (int32_t j = 0; j < i; j++) {
if (strcasecmp(ds->form->controls[j].name, ctrl->parentName) == 0 && treeItems) {
treeParent = treeItems[j];
break;
}
}
}
WidgetT *item = wgtTreeItem(treeParent, label);
item->userData = label;
item->onClick = onTreeItemClick;
arrput(treeItems, item);
if (dsgnIsContainer(ctrl->typeName)) {
wgtTreeItemSetExpanded(item, true);
}
if (i == ds->selectedIdx) {
wgtTreeItemSetSelected(item, true);
}
}
arrfree(treeItems);
sUpdating = false;
}
// ============================================================
// prpRefresh
// ============================================================
void prpRefresh(DsgnStateT *ds) {
if (!ds || !ds->form) {
return;
}
// Don't rebuild the tree here -- just update selection on existing items.
// prpRebuildTree destroys all items which loses TreeView selection state.
// Update property ListView
if (!sPropList) {
return;
}
freeCellData();
int32_t count = (int32_t)arrlen(ds->form->controls);
if (ds->selectedIdx >= 0 && ds->selectedIdx < count) {
DsgnControlT *ctrl = &ds->form->controls[ds->selectedIdx];
char buf[32];
addPropRow("Name", ctrl->name);
addPropRow("Type", ctrl->typeName);
snprintf(buf, sizeof(buf), "%d", (int)ctrl->width);
addPropRow("MinWidth", buf);
snprintf(buf, sizeof(buf), "%d", (int)ctrl->height);
addPropRow("MinHeight", buf);
snprintf(buf, sizeof(buf), "%d", (int)ctrl->maxWidth);
addPropRow("MaxWidth", buf);
snprintf(buf, sizeof(buf), "%d", (int)ctrl->maxHeight);
addPropRow("MaxHeight", buf);
snprintf(buf, sizeof(buf), "%d", (int)ctrl->weight);
addPropRow("Weight", buf);
addPropRow("Visible", ctrl->widget && ctrl->widget->visible ? "True" : "False");
addPropRow("Enabled", ctrl->widget && ctrl->widget->enabled ? "True" : "False");
for (int32_t i = 0; i < ctrl->propCount; i++) {
addPropRow(ctrl->props[i].name, ctrl->props[i].value);
}
// Widget interface properties (from the .wgt descriptor)
const char *wgtName = wgtFindByBasName(ctrl->typeName);
if (wgtName) {
const WgtIfaceT *iface = wgtGetIface(wgtName);
if (iface && ctrl->widget) {
for (int32_t i = 0; i < iface->propCount; i++) {
const WgtPropDescT *p = &iface->props[i];
// Skip if already shown as a custom prop
bool already = false;
for (int32_t j = 0; j < ctrl->propCount; j++) {
if (strcasecmp(ctrl->props[j].name, p->name) == 0) {
already = true;
break;
}
}
if (already) {
continue;
}
// Read the current value from the widget
if (p->type == WGT_IFACE_STRING && p->getFn) {
const char *s = ((const char *(*)(WidgetT *))p->getFn)(ctrl->widget);
addPropRow(p->name, s ? s : "");
} else if (p->type == WGT_IFACE_INT && p->getFn) {
int32_t v = ((int32_t (*)(const WidgetT *))p->getFn)(ctrl->widget);
snprintf(buf, sizeof(buf), "%d", (int)v);
addPropRow(p->name, buf);
} else if (p->type == WGT_IFACE_BOOL && p->getFn) {
bool v = ((bool (*)(const WidgetT *))p->getFn)(ctrl->widget);
addPropRow(p->name, v ? "True" : "False");
} else {
addPropRow(p->name, "");
}
}
}
}
} else {
char buf[32];
addPropRow("Name", ds->form->name);
addPropRow("Caption", ds->form->caption);
addPropRow("Layout", ds->form->layout);
addPropRow("AutoSize", ds->form->autoSize ? "True" : "False");
addPropRow("Resizable", ds->form->resizable ? "True" : "False");
addPropRow("Centered", ds->form->centered ? "True" : "False");
snprintf(buf, sizeof(buf), "%d", (int)ds->form->left);
addPropRow("Left", buf);
snprintf(buf, sizeof(buf), "%d", (int)ds->form->top);
addPropRow("Top", buf);
snprintf(buf, sizeof(buf), "%d", (int)ds->form->width);
addPropRow("Width", buf);
snprintf(buf, sizeof(buf), "%d", (int)ds->form->height);
addPropRow("Height", buf);
}
wgtListViewSetData(sPropList, (const char **)sCellData, sCellRows);
}

View file

@ -11,7 +11,10 @@ WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds);
// Destroy the properties window.
void prpDestroy(AppContextT *ctx, WindowT *win);
// Refresh the properties list for the currently selected control.
// Rebuild the tree from the controls array (after add/remove/reorder).
void prpRebuildTree(DsgnStateT *ds);
// Refresh selection and properties display (lightweight, no tree rebuild).
void prpRefresh(DsgnStateT *ds);
#endif // IDE_PROPERTIES_H

View file

@ -0,0 +1,213 @@
// ideToolbox.c -- DVX BASIC form designer toolbox window
//
// A small floating window with buttons for each registered widget
// type that has a basName (VB-style name). Built dynamically from
// the widget interface registry.
#include "ideToolbox.h"
#include "dvxApp.h"
#include "dvxResource.h"
#include "dvxWm.h"
#include "widgetBox.h"
#include "widgetButton.h"
#include "widgetImageButton.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
// ============================================================
// Constants
// ============================================================
#define TBX_COLS 4
#define TBX_WIN_W 90
#define TBX_WIN_H 200
// ============================================================
// Per-tool entry
// ============================================================
typedef struct {
char typeName[DSGN_MAX_NAME];
char tooltip[64];
} TbxToolEntryT;
// ============================================================
// Module state
// ============================================================
static DsgnStateT *sDs = NULL;
static TbxToolEntryT *sTbxTools = NULL; // stb_ds dynamic array
// ============================================================
// Callbacks
// ============================================================
static void onToolClick(WidgetT *w) {
if (!sDs) {
return;
}
int32_t toolIdx = (int32_t)(intptr_t)w->userData;
if (toolIdx >= 0 && toolIdx < (int32_t)arrlen(sTbxTools)) {
const char *typeName = sTbxTools[toolIdx].typeName;
// Toggle: clicking the same tool again deselects it
if (strcasecmp(sDs->activeTool, typeName) == 0) {
sDs->activeTool[0] = '\0';
} else {
snprintf(sDs->activeTool, DSGN_MAX_NAME, "%s", typeName);
}
sDs->mode = DSGN_IDLE;
}
}
static void onTbxClose(WindowT *win) {
(void)win;
}
// ============================================================
// tbxCreate
// ============================================================
WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) {
sDs = ds;
arrsetlen(sTbxTools, 0);
WindowT *win = dvxCreateWindow(ctx, "Toolbox", 0, 30, TBX_WIN_W, TBX_WIN_H, false);
if (!win) {
return NULL;
}
win->onClose = onTbxClose;
WidgetT *root = wgtInitWindow(ctx, win);
// Enumerate all registered widget interfaces with a basName
int32_t ifaceCount = wgtIfaceCount();
int32_t col = 0;
WidgetT *row = NULL;
WidgetT **buttons = NULL; // stb_ds array, parallel to sTbxTools
for (int32_t i = 0; i < ifaceCount; i++) {
const char *wgtName = NULL;
const WgtIfaceT *iface = wgtIfaceAt(i, &wgtName);
if (!iface || !iface->basName || !iface->basName[0]) {
continue;
}
// Add tool entry
TbxToolEntryT entry;
memset(&entry, 0, sizeof(entry));
snprintf(entry.typeName, DSGN_MAX_NAME, "%s", iface->basName);
// Load icon data and tooltip from the .wgt file resources
uint8_t *iconData = NULL;
int32_t iconW = 0;
int32_t iconH = 0;
int32_t iconPitch = 0;
const char *wgtPath = wgtIfaceGetPath(wgtName);
int32_t pathIdx = wgtIfaceGetPathIndex(wgtName);
if (wgtPath) {
DvxResHandleT *res = dvxResOpen(wgtPath);
if (res) {
// Build suffixed resource names: "icon16", "icon16-2", "icon16-3", etc.
char iconResName[32];
char nameResName[32];
if (pathIdx <= 1) {
snprintf(iconResName, sizeof(iconResName), "icon16");
snprintf(nameResName, sizeof(nameResName), "name");
} else {
snprintf(iconResName, sizeof(iconResName), "icon16-%d", (int)pathIdx);
snprintf(nameResName, sizeof(nameResName), "name-%d", (int)pathIdx);
}
uint32_t iconSize = 0;
void *iconBuf = dvxResRead(res, iconResName, &iconSize);
if (iconBuf) {
iconData = dvxLoadImageFromMemory(ctx, (const uint8_t *)iconBuf, (int32_t)iconSize, &iconW, &iconH, &iconPitch);
free(iconBuf);
}
uint32_t nameSize = 0;
void *nameBuf = dvxResRead(res, nameResName, &nameSize);
if (nameBuf) {
snprintf(entry.tooltip, sizeof(entry.tooltip), "%s", (const char *)nameBuf);
free(nameBuf);
}
dvxResClose(res);
}
}
if (!entry.tooltip[0]) {
snprintf(entry.tooltip, sizeof(entry.tooltip), "%s", iface->basName);
}
arrput(sTbxTools, entry);
int32_t toolIdx = (int32_t)arrlen(sTbxTools) - 1;
// Start a new row every TBX_COLS buttons
if (col == 0 || !row) {
row = wgtHBox(root);
}
// Create button in the current row
WidgetT *btn = NULL;
if (iconData) {
btn = wgtImageButton(row, iconData, iconW, iconH, iconPitch);
} else {
btn = wgtButton(row, iface->basName);
}
btn->onClick = onToolClick;
btn->userData = (void *)(intptr_t)toolIdx;
arrput(buttons, btn);
col = (col + 1) % TBX_COLS;
// Yield to keep the UI responsive while loading icons
dvxUpdate(ctx);
}
// Set tooltips after array is finalized (arrput can reallocate)
int32_t btnCount = (int32_t)arrlen(buttons);
for (int32_t i = 0; i < btnCount; i++) {
wgtSetTooltip(buttons[i], sTbxTools[i].tooltip);
}
arrfree(buttons);
dvxFitWindow(ctx, win);
return win;
}
// ============================================================
// tbxDestroy
// ============================================================
void tbxDestroy(AppContextT *ctx, WindowT *win) {
if (win) {
dvxDestroyWindow(ctx, win);
}
arrfree(sTbxTools);
sTbxTools = NULL;
sDs = NULL;
}

View file

@ -807,8 +807,6 @@ static void setupControlsWindow(void) {
sbLabel->weight = 100;
wgtSetName(sbLabel, "advStatus");
wgtLabel(sb, "Line 1, Col 1");
wgtInvalidate(root);
}
@ -1126,8 +1124,6 @@ static void setupWidgetDemo(void) {
WidgetT *okBtn = wgtButton(btnRow, "&OK");
okBtn->onClick = onOkClick;
wgtButton(btnRow, "&Cancel");
wgtInvalidate(root);
}

View file

@ -212,9 +212,6 @@ static void loadAndDisplay(const char *path) {
buildScaled(sWin->contentW, sWin->contentH);
dvxSetBusy(sAc, false);
RectT fullRect = {0, 0, sWin->contentW, sWin->contentH};
sWin->onPaint(sWin, &fullRect);
sWin->contentDirty = true;
dvxInvalidateWindow(sAc, sWin);
}

View file

@ -396,6 +396,5 @@ int32_t appMain(DxeAppContextT *ctx) {
sFilePath[0] = '\0';
markClean();
wgtInvalidate(root);
return 0;
}

View file

@ -453,15 +453,21 @@ static void scanAppsDirRecurse(const char *dirPath) {
if (iconBuf) {
entry->iconData = dvxLoadImageFromMemory(sAc, (const uint8_t *)iconBuf, (int32_t)iconSize, &entry->iconW, &entry->iconH, &entry->iconPitch);
if (!entry->iconData) {
dvxLog("Progman: failed to decode icon for %s (%d bytes)", ent->d_name, (int)iconSize);
}
free(iconBuf);
} else {
dvxLog("Progman: no icon32 resource in %s", ent->d_name);
}
uint32_t nameSize = 0;
void *nameBuf = dvxResRead(res, "name", &nameSize);
if (nameBuf) {
strncpy(entry->name, (const char *)nameBuf, SHELL_APP_NAME_MAX - 1);
entry->name[SHELL_APP_NAME_MAX - 1] = '\0';
snprintf(entry->name, SHELL_APP_NAME_MAX, "%s", (const char *)nameBuf);
free(nameBuf);
}
@ -469,14 +475,16 @@ static void scanAppsDirRecurse(const char *dirPath) {
void *descBuf = dvxResRead(res, "description", &descSize);
if (descBuf) {
strncpy(entry->tooltip, (const char *)descBuf, sizeof(entry->tooltip) - 1);
entry->tooltip[sizeof(entry->tooltip) - 1] = '\0';
snprintf(entry->tooltip, sizeof(entry->tooltip), "%s", (const char *)descBuf);
free(descBuf);
}
dvxResClose(res);
} else {
dvxLog("Progman: no resources in %s", ent->d_name);
}
dvxLog("Progman: found %s (%s) icon=%s", entry->name, ent->d_name, entry->iconData ? "yes" : "no");
sAppCount++;
}

View file

@ -55,14 +55,14 @@ CORE_HDRS = dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h dvxWidget.h platfor
$(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h platform/dvxPlatform.h dvxTypes.h dvxPalette.h
$(OBJDIR)/dvxDraw.o: dvxDraw.c dvxDraw.h platform/dvxPlatform.h dvxTypes.h
$(OBJDIR)/dvxComp.o: dvxComp.c dvxComp.h platform/dvxPlatform.h dvxTypes.h
$(OBJDIR)/dvxWm.o: dvxWm.c dvxWm.h dvxTypes.h dvxDraw.h dvxComp.h dvxVideo.h dvxWidget.h thirdparty/stb_image.h
$(OBJDIR)/dvxWm.o: dvxWm.c dvxWm.h dvxTypes.h dvxDraw.h dvxComp.h dvxVideo.h dvxWidget.h platform/dvxPlatform.h thirdparty/stb_image.h
$(OBJDIR)/dvxImage.o: dvxImage.c thirdparty/stb_image.h
$(OBJDIR)/dvxImageWrite.o: dvxImageWrite.c thirdparty/stb_image_write.h
$(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h platform/dvxPlatform.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h
$(OBJDIR)/dvxDialog.o: dvxDialog.c dvxDialog.h platform/dvxPlatform.h dvxApp.h dvxWidget.h dvxWidgetPlugin.h dvxTypes.h dvxDraw.h
$(OBJDIR)/dvxPrefs.o: dvxPrefs.c dvxPrefs.h
WIDGET_DEPS = dvxWidgetPlugin.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
WIDGET_DEPS = dvxWidgetPlugin.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h platform/dvxPlatform.h
$(OBJDIR)/widgetClass.o: widgetClass.c $(WIDGET_DEPS)
$(OBJDIR)/widgetCore.o: widgetCore.c $(WIDGET_DEPS)
$(OBJDIR)/widgetScrollbar.o: widgetScrollbar.c $(WIDGET_DEPS)

View file

@ -3863,26 +3863,9 @@ void dvxDestroyWindow(AppContextT *ctx, WindowT *win) {
// Used after building a dialog's widget tree to size the dialog
// automatically rather than requiring the caller to compute sizes manually.
void dvxFitWindow(AppContextT *ctx, WindowT *win) {
if (!ctx || !win || !win->widgetRoot) {
return;
}
// Measure the widget tree to get minimum content size
widgetCalcMinSizeTree(win->widgetRoot, &ctx->font);
int32_t contentW = win->widgetRoot->calcMinW;
int32_t contentH = win->widgetRoot->calcMinH;
// Compute chrome overhead
int32_t topChrome = CHROME_TOTAL_TOP;
if (win->menuBar) {
topChrome += CHROME_MENU_HEIGHT;
}
int32_t newW = contentW + CHROME_TOTAL_SIDE * 2;
int32_t newH = contentH + topChrome + CHROME_TOTAL_BOTTOM;
// dvxResizeWindow -- resize a window to the given outer dimensions
void dvxResizeWindow(AppContextT *ctx, WindowT *win, int32_t newW, int32_t newH) {
// Dirty old position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
@ -3918,6 +3901,58 @@ void dvxFitWindow(AppContextT *ctx, WindowT *win) {
}
void dvxFitWindow(AppContextT *ctx, WindowT *win) {
if (!ctx || !win || !win->widgetRoot) {
return;
}
widgetCalcMinSizeTree(win->widgetRoot, &ctx->font);
int32_t topChrome = CHROME_TOTAL_TOP;
if (win->menuBar) {
topChrome += CHROME_MENU_HEIGHT;
}
int32_t newW = win->widgetRoot->calcMinW + CHROME_TOTAL_SIDE * 2;
int32_t newH = win->widgetRoot->calcMinH + topChrome + CHROME_TOTAL_BOTTOM;
dvxResizeWindow(ctx, win, newW, newH);
}
void dvxFitWindowW(AppContextT *ctx, WindowT *win) {
if (!ctx || !win || !win->widgetRoot) {
return;
}
widgetCalcMinSizeTree(win->widgetRoot, &ctx->font);
int32_t newW = win->widgetRoot->calcMinW + CHROME_TOTAL_SIDE * 2;
dvxResizeWindow(ctx, win, newW, win->h);
}
void dvxFitWindowH(AppContextT *ctx, WindowT *win) {
if (!ctx || !win || !win->widgetRoot) {
return;
}
widgetCalcMinSizeTree(win->widgetRoot, &ctx->font);
int32_t topChrome = CHROME_TOTAL_TOP;
if (win->menuBar) {
topChrome += CHROME_MENU_HEIGHT;
}
int32_t newH = win->widgetRoot->calcMinH + topChrome + CHROME_TOTAL_BOTTOM;
dvxResizeWindow(ctx, win, win->w, newH);
}
// ============================================================
// dvxFreeAccelTable
// ============================================================
@ -4024,6 +4059,7 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_
platformVideoEnumModes(enumModeCb, ctx);
if (videoInit(&ctx->display, requestedW, requestedH, preferredBpp) != 0) {
dvxLog("App: video init failed for %dx%d@%dbpp", requestedW, requestedH, preferredBpp);
return -1;
}
@ -4831,6 +4867,18 @@ bool dvxUpdate(AppContextT *ctx) {
refreshMinimizedIcons(ctx);
}
// Auto-paint windows that haven't had their first paint yet.
// This fires one frame after creation, giving the app time to
// add all widgets before the first onPaint.
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (win->needsPaint && win->onPaint) {
win->needsPaint = false;
dvxInvalidateWindow(ctx, win);
}
}
if (ctx->dirty.count > 0) {
compositeAndFlush(ctx);
} else if (ctx->idleCallback) {

View file

@ -13,6 +13,7 @@
#ifndef DVX_APP_H
#define DVX_APP_H
#include "dvxCursor.h"
#include "dvxTypes.h"
#include "dvxVideo.h"
#include "dvxDraw.h"
@ -42,7 +43,7 @@ typedef struct AppContextT {
PopupStateT popup;
SysMenuStateT sysMenu;
KbMoveResizeT kbMoveResize;
CursorT cursors[6]; // indexed by CURSOR_xxx
CursorT cursors[CURSOR_COUNT]; // indexed by CURSOR_xxx
int32_t cursorId; // active cursor shape
uint32_t cursorFg; // pre-packed cursor colors
uint32_t cursorBg;
@ -206,6 +207,9 @@ void dvxDestroyWindow(AppContextT *ctx, WindowT *win);
// (plus chrome). Used for dialog boxes and other fixed-layout windows
// where the window should shrink-wrap its content.
void dvxFitWindow(AppContextT *ctx, WindowT *win);
void dvxFitWindowW(AppContextT *ctx, WindowT *win);
void dvxFitWindowH(AppContextT *ctx, WindowT *win);
void dvxResizeWindow(AppContextT *ctx, WindowT *win, int32_t newW, int32_t newH);
// Mark a sub-region of a window's content area as needing repaint. The
// coordinates are relative to the content area, not the screen. The

View file

@ -29,7 +29,8 @@
#define CURSOR_RESIZE_DIAG_NWSE 3 // NW-SE diagonal (top-left / bottom-right)
#define CURSOR_RESIZE_DIAG_NESW 4 // NE-SW diagonal (top-right / bottom-left)
#define CURSOR_BUSY 5 // hourglass (wait)
#define CURSOR_COUNT 6
#define CURSOR_CROSSHAIR 6 // crosshair (+) for placement
#define CURSOR_COUNT 7
// ============================================================
// Standard arrow cursor, 16x16
@ -354,6 +355,50 @@ static const uint16_t cursorBusyXor[16] = {
};
// ============================================================
// Crosshair cursor (+), 16x16
// ============================================================
// Hot spot at center (7, 7)
static const uint16_t cursorCrosshairAnd[16] = {
0xFFFF, // row 0
0xFC3F, // 1111110000111111 row 1 black outline top
0xFC3F, // 1111110000111111 row 2
0xFC3F, // 1111110000111111 row 3
0xFC3F, // 1111110000111111 row 4
0xFC3F, // 1111110000111111 row 5
0x0000, // 0000000000000000 row 6 outline + horizontal bar
0x0000, // 0000000000000000 row 7 horizontal bar center
0x0000, // 0000000000000000 row 8 outline + horizontal bar
0xFC3F, // 1111110000111111 row 9
0xFC3F, // 1111110000111111 row 10
0xFC3F, // 1111110000111111 row 11
0xFC3F, // 1111110000111111 row 12
0xFC3F, // 1111110000111111 row 13 black outline bottom
0xFFFF, // row 14
0xFFFF // row 15
};
static const uint16_t cursorCrosshairXor[16] = {
0x0000, // row 0
0x0180, // 0000000110000000 row 1 white inner
0x0180, // row 2
0x0180, // row 3
0x0180, // row 4
0x0180, // row 5
0x0180, // 0000000110000000 row 6 white inner (black on sides)
0x3FFC, // 0011111111111100 row 7 white horizontal (black edges)
0x0180, // 0000000110000000 row 8 white inner (black on sides)
0x0180, // row 9
0x0180, // row 10
0x0180, // row 11
0x0180, // row 12
0x0180, // row 13 white inner
0x0000, // row 14
0x0000 // row 15
};
// ============================================================
// Cursor table
// ============================================================
@ -365,6 +410,7 @@ static const CursorT dvxCursors[CURSOR_COUNT] = {
{ 16, 16, 7, 7, cursorResizeDiagNWSEAnd, cursorResizeDiagNWSEXor }, // CURSOR_RESIZE_DIAG_NWSE
{ 16, 16, 7, 7, cursorResizeDiagNESWAnd, cursorResizeDiagNESWXor }, // CURSOR_RESIZE_DIAG_NESW
{ 16, 16, 7, 7, cursorBusyAnd, cursorBusyXor }, // CURSOR_BUSY
{ 16, 16, 7, 7, cursorCrosshairAnd, cursorCrosshairXor }, // CURSOR_CROSSHAIR
};
// Legacy alias -- kept for backward compatibility with code that predates

View file

@ -30,6 +30,7 @@
#include "../widgets/widgetLabel.h"
#include "../widgets/widgetListBox.h"
#include "../widgets/widgetSpacer.h"
#include "../widgets/widgetSpinner.h"
#include "../widgets/widgetTextInput.h"
#include <ctype.h>
@ -758,6 +759,120 @@ static void ibOnClose(WindowT *win) {
}
// ============================================================
// dvxIntInputBox
// ============================================================
static struct {
bool done;
bool accepted;
int32_t *outVal;
WidgetT *spinner;
} sIntBox;
static void iibOnOk(WidgetT *w) {
(void)w;
if (sIntBox.spinner && sIntBox.outVal) {
*sIntBox.outVal = wgtSpinnerGetValue(sIntBox.spinner);
}
sIntBox.accepted = true;
sIntBox.done = true;
}
static void iibOnCancel(WidgetT *w) {
(void)w;
sIntBox.accepted = false;
sIntBox.done = true;
}
static void iibOnClose(WindowT *win) {
(void)win;
sIntBox.accepted = false;
sIntBox.done = true;
}
bool dvxIntInputBox(AppContextT *ctx, const char *title, const char *prompt, int32_t defaultVal, int32_t minVal, int32_t maxVal, int32_t step, int32_t *outVal) {
if (!ctx || !outVal) {
return false;
}
int32_t promptH = 0;
if (prompt && prompt[0]) {
int32_t textMaxW = IB_DIALOG_WIDTH - IB_PADDING * 2;
promptH = wordWrapHeight(&ctx->font, prompt, textMaxW);
}
int32_t contentH = IB_PADDING + promptH + IB_PADDING + ctx->font.charHeight + 8 + IB_PADDING + BUTTON_HEIGHT + IB_PADDING;
int32_t contentW = IB_DIALOG_WIDTH;
int32_t winX = (ctx->display.width - contentW) / 2 - CHROME_TOTAL_SIDE;
int32_t winY = (ctx->display.height - contentH) / 2 - CHROME_TOTAL_TOP;
WindowT *win = dvxCreateWindow(ctx, title ? title : "Input",
winX, winY,
contentW + CHROME_TOTAL_SIDE * 2,
contentH + CHROME_TOTAL_TOP + CHROME_TOTAL_BOTTOM,
false);
if (!win) {
return false;
}
win->modal = true;
win->onClose = iibOnClose;
win->maxW = win->w;
win->maxH = win->h;
sIntBox.done = false;
sIntBox.accepted = false;
sIntBox.outVal = outVal;
sIntBox.spinner = NULL;
WidgetT *root = wgtInitWindow(ctx, win);
if (root) {
if (prompt && prompt[0]) {
wgtLabel(root, prompt);
}
WidgetT *spinner = wgtSpinner(root, minVal, maxVal, step);
wgtSpinnerSetValue(spinner, defaultVal);
sIntBox.spinner = spinner;
WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignCenterE;
WidgetT *okBtn = wgtButton(btnRow, "&OK");
okBtn->minW = wgtPixels(BUTTON_WIDTH);
okBtn->minH = wgtPixels(BUTTON_HEIGHT);
okBtn->onClick = iibOnOk;
WidgetT *cancelBtn = wgtButton(btnRow, "&Cancel");
cancelBtn->minW = wgtPixels(BUTTON_WIDTH);
cancelBtn->minH = wgtPixels(BUTTON_HEIGHT);
cancelBtn->onClick = iibOnCancel;
}
dvxFitWindow(ctx, win);
WindowT *prevModal = ctx->modalWindow;
ctx->modalWindow = win;
while (!sIntBox.done && ctx->running) {
dvxUpdate(ctx);
}
ctx->modalWindow = prevModal;
dvxDestroyWindow(ctx, win);
sIntBox.spinner = NULL;
return sIntBox.accepted;
}
// ============================================================
// ibOnOk
// ============================================================
@ -993,6 +1108,7 @@ static void fdLoadDir(void) {
sFd.entryIsDir = (bool *)realloc(sFd.entryIsDir, (idx + 1) * sizeof(bool));
if (!sFd.entryNames || !sFd.entryIsDir) {
dvxLog("Dialog: failed to realloc entry arrays");
break;
}
@ -1021,6 +1137,7 @@ static void fdLoadDir(void) {
int32_t *sortIdx = (int32_t *)malloc(sFd.entryCount * sizeof(int32_t));
if (!sortIdx) {
dvxLog("Dialog: failed to allocate sort index");
return;
}
@ -1034,6 +1151,10 @@ static void fdLoadDir(void) {
char **tmpNames = (char **)malloc(sFd.entryCount * sizeof(char *));
bool *tmpIsDir = (bool *)malloc(sFd.entryCount * sizeof(bool));
if (!tmpNames || !tmpIsDir) {
dvxLog("Dialog: failed to allocate temp sort arrays");
}
if (tmpNames && tmpIsDir) {
for (int32_t i = 0; i < sFd.entryCount; i++) {
tmpNames[i] = sFd.entryNames[sortIdx[i]];
@ -1051,6 +1172,11 @@ static void fdLoadDir(void) {
// Build listItems pointer array for the listbox
sFd.listItems = (const char **)realloc(sFd.listItems, sFd.entryCount * sizeof(const char *));
if (!sFd.listItems) {
dvxLog("Dialog: failed to realloc listItems");
return;
}
for (int32_t i = 0; i < sFd.entryCount; i++) {
sFd.listItems[i] = sFd.entryNames[i];
}

View file

@ -83,4 +83,8 @@ bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const cha
// false if cancelled or closed. defaultText may be NULL.
bool dvxInputBox(AppContextT *ctx, const char *title, const char *prompt, const char *defaultText, char *outBuf, int32_t outBufSize);
// Display a modal integer input box with a spinner (up/down arrows).
// Returns true if the user clicked OK, false if cancelled.
bool dvxIntInputBox(AppContextT *ctx, const char *title, const char *prompt, int32_t defaultVal, int32_t minVal, int32_t maxVal, int32_t step, int32_t *outVal);
#endif // DVX_DIALOG_H

View file

@ -507,6 +507,7 @@ typedef struct WindowT {
bool resizable;
bool modal;
bool contentDirty; // true when contentBuf has changed since last icon refresh
bool needsPaint; // true until first onPaint call (auto-paint on next frame)
int32_t maxW; // maximum width (WM_MAX_FROM_SCREEN = use screen width)
int32_t maxH; // maximum height (WM_MAX_FROM_SCREEN = use screen height)
// Pre-maximize geometry is saved so wmRestore() can put the window

View file

@ -581,6 +581,21 @@ typedef struct {
// Widget descriptors only need to list EXTRA events beyond these.
// Click, DblClick, Change, GotFocus, LostFocus
// Create function signature types for design-time / runtime instantiation.
// The create function is always the first slot in the widget API struct.
typedef enum {
WGT_CREATE_PARENT = 0, // fn(parent)
WGT_CREATE_PARENT_TEXT, // fn(parent, const char *text)
WGT_CREATE_PARENT_INT, // fn(parent, int32_t)
WGT_CREATE_PARENT_INT_INT, // fn(parent, int32_t, int32_t)
WGT_CREATE_PARENT_INT_INT_INT, // fn(parent, int32_t, int32_t, int32_t)
WGT_CREATE_PARENT_INT_BOOL, // fn(parent, int32_t, bool)
WGT_CREATE_PARENT_BOOL, // fn(parent, bool)
WGT_CREATE_PARENT_DATA, // fn(parent, data, w, h, pitch) -- not auto-creatable
} WgtCreateSigE;
#define WGT_MAX_CREATE_ARGS 3
// Widget interface descriptor (registered by each .wgt)
typedef struct {
const char *basName; // VB-style name (e.g. "CommandButton"), or NULL
@ -590,6 +605,11 @@ typedef struct {
int32_t methodCount;
const WgtEventDescT *events; // extra events beyond common set
int32_t eventCount;
uint8_t createSig; // WgtCreateSigE: how to call create fn
int32_t createArgs[WGT_MAX_CREATE_ARGS]; // default numeric args
bool isContainer; // can hold child widgets
const char *defaultEvent; // default event name (e.g. "Click")
const char *namePrefix; // auto-name prefix (NULL = use basName)
} WgtIfaceT;
// Register/retrieve interface descriptors by widget type name.
@ -600,4 +620,16 @@ const WgtIfaceT *wgtGetIface(const char *name);
// Returns NULL if no widget has that basName. Case-insensitive.
const char *wgtFindByBasName(const char *basName);
// Enumerate all registered widget interfaces.
int32_t wgtIfaceCount(void);
const WgtIfaceT *wgtIfaceAt(int32_t idx, const char **outName);
// Get/set the .wgt file path for a registered widget (set by loader).
const char *wgtIfaceGetPath(const char *name);
void wgtIfaceSetPath(const char *name, const char *path);
// Get the 1-based index of this widget within its .wgt file.
// Used to construct suffixed resource names (e.g. "name-2", "icon16-2").
int32_t wgtIfaceGetPathIndex(const char *name);
#endif // DVX_WIDGET_H

View file

@ -34,6 +34,7 @@
#include "dvxDraw.h"
#include "dvxComp.h"
#include "dvxWidget.h"
#include "dvxPlatform.h"
#include "thirdparty/stb_image_wrap.h"
#include <stdlib.h>
@ -847,6 +848,7 @@ ScrollbarT *wmAddHScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
win->hScroll = (ScrollbarT *)malloc(sizeof(ScrollbarT));
if (!win->hScroll) {
dvxLog("WM: failed to allocate horizontal scrollbar");
return NULL;
}
@ -934,6 +936,7 @@ MenuBarT *wmAddMenuBar(WindowT *win) {
win->menuBar = (MenuBarT *)malloc(sizeof(MenuBarT));
if (!win->menuBar) {
dvxLog("WM: failed to allocate menu bar");
return NULL;
}
@ -1076,6 +1079,7 @@ ScrollbarT *wmAddVScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
win->vScroll = (ScrollbarT *)malloc(sizeof(ScrollbarT));
if (!win->vScroll) {
dvxLog("WM: failed to allocate vertical scrollbar");
return NULL;
}
@ -1164,7 +1168,7 @@ WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d, const char *title, int
win->contentBuf = (uint8_t *)malloc(bufSize);
if (!win->contentBuf) {
fprintf(stderr, "WM: Failed to allocate content buffer (%ld bytes)\n", (long)bufSize);
dvxLog("WM: failed to allocate content buffer (%ld bytes)", (long)bufSize);
free(win);
return NULL;
}
@ -1172,6 +1176,8 @@ WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d, const char *title, int
memset(win->contentBuf, 0xFF, bufSize);
}
win->needsPaint = true;
stack->windows[stack->count] = win;
stack->count++;

View file

@ -29,10 +29,16 @@ typedef struct {
static ApiMapEntryT *sApiMap = NULL;
// stb_ds string hashmap: key = widget name, value = interface descriptor
// stb_ds string hashmap: key = widget name, value = iface + path
typedef struct {
const WgtIfaceT *iface;
char path[260];
int32_t pathIndex; // 1-based: 1 = first widget from this file, 2 = second, etc.
} IfaceEntryT;
typedef struct {
char *key;
const WgtIfaceT *value;
IfaceEntryT value;
} IfaceMapEntryT;
static IfaceMapEntryT *sIfaceMap = NULL;
@ -74,8 +80,8 @@ const char *wgtFindByBasName(const char *basName) {
}
for (size_t i = 0; i < shlenu(sIfaceMap); i++) {
if (sIfaceMap[i].value && sIfaceMap[i].value->basName) {
if (strcasecmp(sIfaceMap[i].value->basName, basName) == 0) {
if (sIfaceMap[i].value.iface && sIfaceMap[i].value.iface->basName) {
if (strcasecmp(sIfaceMap[i].value.iface->basName, basName) == 0) {
return sIfaceMap[i].key;
}
}
@ -102,7 +108,97 @@ const WgtIfaceT *wgtGetIface(const char *name) {
return NULL;
}
return sIfaceMap[idx].value;
return sIfaceMap[idx].value.iface;
}
// ============================================================
// wgtIfaceCount / wgtIfaceAt
// ============================================================
//
// Enumerate all registered widget interfaces.
int32_t wgtIfaceCount(void) {
return sIfaceMap ? (int32_t)shlenu(sIfaceMap) : 0;
}
const WgtIfaceT *wgtIfaceAt(int32_t idx, const char **outName) {
if (!sIfaceMap || idx < 0 || idx >= (int32_t)shlenu(sIfaceMap)) {
return NULL;
}
if (outName) {
*outName = sIfaceMap[idx].key;
}
return sIfaceMap[idx].value.iface;
}
// ============================================================
// wgtIfaceGetPath
// ============================================================
const char *wgtIfaceGetPath(const char *name) {
if (!name || !sIfaceMap) {
return NULL;
}
int32_t idx = shgeti(sIfaceMap, name);
if (idx < 0) {
return NULL;
}
return sIfaceMap[idx].value.path[0] ? sIfaceMap[idx].value.path : NULL;
}
// ============================================================
// wgtIfaceGetPathIndex
// ============================================================
int32_t wgtIfaceGetPathIndex(const char *name) {
if (!name || !sIfaceMap) {
return 1;
}
int32_t idx = shgeti(sIfaceMap, name);
if (idx < 0) {
return 1;
}
return sIfaceMap[idx].value.pathIndex > 0 ? sIfaceMap[idx].value.pathIndex : 1;
}
// ============================================================
// wgtIfaceSetPath
// ============================================================
void wgtIfaceSetPath(const char *name, const char *path) {
if (!name || !path || !sIfaceMap) {
return;
}
int32_t idx = shgeti(sIfaceMap, name);
if (idx >= 0) {
snprintf(sIfaceMap[idx].value.path, sizeof(sIfaceMap[idx].value.path), "%s", path);
// Count how many ifaces already have this path to assign a 1-based index
int32_t count = 0;
for (size_t i = 0; i < shlenu(sIfaceMap); i++) {
if (sIfaceMap[i].value.path[0] && strcmp(sIfaceMap[i].value.path, path) == 0) {
count++;
}
}
sIfaceMap[idx].value.pathIndex = count;
}
}
@ -134,7 +230,10 @@ void wgtRegisterIface(const char *name, const WgtIfaceT *iface) {
return;
}
shput(sIfaceMap, name, iface);
IfaceEntryT entry;
memset(&entry, 0, sizeof(entry));
entry.iface = iface;
shput(sIfaceMap, name, entry);
}

View file

@ -19,6 +19,7 @@
// which doesn't map cleanly to an arena pattern.
#include "dvxWidgetPlugin.h"
#include "dvxPlatform.h"
#include "stb_ds_wrap.h"
#include <time.h>
@ -185,6 +186,7 @@ WidgetT *widgetAlloc(WidgetT *parent, int32_t type) {
WidgetT *w = (WidgetT *)malloc(sizeof(WidgetT));
if (!w) {
dvxLog("Widget: failed to allocate widget (type=%d)", type);
return NULL;
}

View file

@ -292,6 +292,22 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
// Assign geometry
int32_t crossSize = availCross;
// Apply preferred size as cross-axis cap (e.g. Width in a VBox).
// When set, the widget won't stretch beyond this on the cross axis.
if (horiz && c->prefH) {
int32_t prefPx = wgtResolveSize(c->prefH, innerH, font->charWidth);
if (crossSize > prefPx) {
crossSize = prefPx;
}
} else if (!horiz && c->prefW) {
int32_t prefPx = wgtResolveSize(c->prefW, innerW, font->charWidth);
if (crossSize > prefPx) {
crossSize = prefPx;
}
}
// Apply max size on cross axis
if (horiz && c->maxH) {
int32_t maxPx = wgtResolveSize(c->maxH, innerH, font->charWidth);
@ -322,21 +338,6 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
c->h = mainSize;
}
// Apply preferred/max on cross axis
if (horiz && c->maxH) {
int32_t maxPx = wgtResolveSize(c->maxH, innerH, font->charWidth);
if (c->h > maxPx) {
c->h = maxPx;
}
} else if (!horiz && c->maxW) {
int32_t maxPx = wgtResolveSize(c->maxW, innerW, font->charWidth);
if (c->w > maxPx) {
c->w = maxPx;
}
}
pos += mainSize + gap;
// Recurse into child containers

View file

@ -12,6 +12,7 @@
// and wgtInvalidatePaint).
#include "dvxWidgetPlugin.h"
#include "dvxPlatform.h"
#include "../widgets/widgetBox.h"
@ -295,6 +296,7 @@ WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
WidgetT *root = dvxBoxApi() ? dvxBoxApi()->vBox(NULL) : NULL;
if (!root) {
dvxLog("Widget: wgtInitWindow failed (%s)", dvxBoxApi() ? "vBox returned NULL" : "dvxBoxApi returned NULL");
return NULL;
}

View file

@ -1,276 +0,0 @@
// ideProperties.c -- DVX BASIC form designer properties window
//
// A floating window with a TreeView listing all controls on the
// form (for selection and drag-reorder) and a read-only TextArea
// showing properties of the selected control.
#include "ideProperties.h"
#include "dvxWm.h"
#include "widgetBox.h"
#include "widgetLabel.h"
#include "widgetTextInput.h"
#include "widgetTreeView.h"
#include "widgetSplitter.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
// ============================================================
// Constants
// ============================================================
#define PRP_WIN_W 200
#define PRP_WIN_H 340
// ============================================================
// Module state
// ============================================================
static DsgnStateT *sDs = NULL;
static WindowT *sPrpWin = NULL;
static WidgetT *sTree = NULL;
static WidgetT *sPropsArea = NULL;
static AppContextT *sPrpCtx = NULL;
static bool sUpdating = false; // prevent feedback loops
// ============================================================
// Prototypes
// ============================================================
static void onPrpClose(WindowT *win);
static void onTreeChange(WidgetT *w);
// ============================================================
// onPrpClose
// ============================================================
static void onPrpClose(WindowT *win) {
(void)win;
}
// ============================================================
// onTreeChange
// ============================================================
//
// Called when the TreeView selection changes or when items are
// reordered by drag. Sync the designer state from the tree.
static void onTreeChange(WidgetT *w) {
(void)w;
if (!sDs || !sDs->form || !sTree || sUpdating) {
return;
}
// Find which tree item is selected and map to control index
int32_t count = (int32_t)arrlen(sDs->form->controls);
int32_t selIdx = -1;
// Iterate tree items (children of sTree) to find the selected one
int32_t idx = 0;
for (WidgetT *item = sTree->firstChild; item; item = item->nextSibling, idx++) {
if (wgtTreeItemIsSelected(item)) {
selIdx = idx;
break;
}
}
// Check if the tree order differs from the design array
// (drag-reorder happened). If so, rebuild the array to match.
bool reordered = false;
idx = 0;
for (WidgetT *item = sTree->firstChild; item; item = item->nextSibling, idx++) {
if (idx >= count) {
break;
}
// Each tree item's userData stores the control name
const char *itemName = (const char *)item->userData;
if (itemName && strcmp(itemName, sDs->form->controls[idx].name) != 0) {
reordered = true;
break;
}
}
if (reordered) {
// Rebuild the controls array to match tree order
DsgnControlT *newArr = NULL;
idx = 0;
for (WidgetT *item = sTree->firstChild; item; item = item->nextSibling) {
const char *itemName = (const char *)item->userData;
if (!itemName) {
continue;
}
// Find this control in the original array
for (int32_t i = 0; i < count; i++) {
if (strcmp(sDs->form->controls[i].name, itemName) == 0) {
arrput(newArr, sDs->form->controls[i]);
break;
}
}
}
// Replace the controls array
arrfree(sDs->form->controls);
sDs->form->controls = newArr;
sDs->form->dirty = true;
// Rebuild live widgets in new order
if (sDs->form->contentBox) {
sDs->form->contentBox->firstChild = NULL;
sDs->form->contentBox->lastChild = NULL;
int32_t newCount = (int32_t)arrlen(sDs->form->controls);
for (int32_t i = 0; i < newCount; i++) {
sDs->form->controls[i].widget = NULL;
}
dsgnCreateWidgets(sDs, sDs->form->contentBox);
}
// Update selection to match tree
count = (int32_t)arrlen(sDs->form->controls);
}
// Update designer selection
if (selIdx != sDs->selectedIdx) {
sDs->selectedIdx = selIdx;
if (sDs->formWin) {
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
}
}
// Refresh properties display
prpRefresh(sDs);
}
// ============================================================
// prpCreate
// ============================================================
WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds) {
sDs = ds;
sPrpCtx = ctx;
int32_t winX = ctx->display.width - PRP_WIN_W - 10;
WindowT *win = dvxCreateWindow(ctx, "Properties", winX, 30, PRP_WIN_W, PRP_WIN_H, false);
if (!win) {
return NULL;
}
win->onClose = onPrpClose;
sPrpWin = win;
WidgetT *root = wgtInitWindow(ctx, win);
// Splitter: tree on top, properties on bottom
WidgetT *splitter = wgtSplitter(root, false);
splitter->weight = 100;
// Control tree (top pane)
sTree = wgtTreeView(splitter);
sTree->onChange = onTreeChange;
wgtTreeViewSetReorderable(sTree, true);
// Properties text (bottom pane)
sPropsArea = wgtTextArea(splitter, 4096);
sPropsArea->readOnly = true;
prpRefresh(ds);
return win;
}
// ============================================================
// prpDestroy
// ============================================================
void prpDestroy(AppContextT *ctx, WindowT *win) {
if (win) {
dvxDestroyWindow(ctx, win);
}
sPrpWin = NULL;
sTree = NULL;
sPropsArea = NULL;
sDs = NULL;
}
// ============================================================
// prpRefresh
// ============================================================
void prpRefresh(DsgnStateT *ds) {
if (!ds || !ds->form) {
return;
}
// Rebuild tree items
if (sTree) {
sUpdating = true;
// Clear existing tree items
sTree->firstChild = NULL;
sTree->lastChild = NULL;
int32_t count = (int32_t)arrlen(ds->form->controls);
for (int32_t i = 0; i < count; i++) {
DsgnControlT *ctrl = &ds->form->controls[i];
char label[128];
snprintf(label, sizeof(label), "%s (%s)", ctrl->name, ctrl->typeName);
WidgetT *item = wgtTreeItem(sTree, label);
item->userData = ctrl->name; // store name for reorder tracking
if (i == ds->selectedIdx) {
wgtTreeItemSetSelected(item, true);
}
}
sUpdating = false;
}
// Update properties text
if (!sPropsArea) {
return;
}
char buf[4096];
int32_t pos = 0;
if (ds->selectedIdx >= 0 && ds->selectedIdx < (int32_t)arrlen(ds->form->controls)) {
DsgnControlT *ctrl = &ds->form->controls[ds->selectedIdx];
pos += snprintf(buf + pos, sizeof(buf) - pos, "Name = %s\n", ctrl->name);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Type = %s\n", ctrl->typeName);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Width = %d\n", (int)ctrl->width);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Height = %d\n", (int)ctrl->height);
pos += snprintf(buf + pos, sizeof(buf) - pos, "TabIndex = %d\n", (int)ctrl->tabIndex);
for (int32_t i = 0; i < ctrl->propCount; i++) {
pos += snprintf(buf + pos, sizeof(buf) - pos, "%-8s = %s\n", ctrl->props[i].name, ctrl->props[i].value);
}
} else {
pos += snprintf(buf + pos, sizeof(buf) - pos, "%s (Form)\n", ds->form->name);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Caption = %s\n", ds->form->caption);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Width = %d\n", (int)ds->form->width);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Height = %d\n", (int)ds->form->height);
}
wgtSetText(sPropsArea, buf);
}

View file

@ -1,110 +0,0 @@
// ideToolbox.c -- DVX BASIC form designer toolbox window
//
// A small floating window with buttons for each control type.
// Clicking a tool sets the designer's activeTool.
#include "ideToolbox.h"
#include "dvxWm.h"
#include "widgetBox.h"
#include "widgetButton.h"
#include <string.h>
// ============================================================
// Constants
// ============================================================
#define TBX_WIN_W 72
#define TBX_WIN_H 320
#define TBX_BTN_H 20
// ============================================================
// Module state
// ============================================================
static DsgnStateT *sDs = NULL;
// ============================================================
// Tool labels (short names for buttons)
// ============================================================
static const char *sToolLabels[TOOL_COUNT] = {
"Pointer",
"Button",
"Label",
"TextBox",
"CheckBox",
"Option",
"Frame",
"ListBox",
"ComboBox",
"HScroll",
"VScroll",
"Timer",
"Picture",
"Image"
};
// ============================================================
// Callbacks
// ============================================================
static void onToolClick(WidgetT *w) {
if (!sDs) {
return;
}
int32_t toolIdx = (int32_t)(intptr_t)w->userData;
if (toolIdx >= 0 && toolIdx < TOOL_COUNT) {
sDs->activeTool = (DsgnToolE)toolIdx;
sDs->mode = DSGN_IDLE;
}
}
static void onTbxClose(WindowT *win) {
// Don't allow closing the toolbox independently
(void)win;
}
// ============================================================
// tbxCreate
// ============================================================
WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) {
sDs = ds;
WindowT *win = dvxCreateWindow(ctx, "Toolbox", 0, 30, TBX_WIN_W, TBX_WIN_H, false);
if (!win) {
return NULL;
}
win->onClose = onTbxClose;
WidgetT *root = wgtInitWindow(ctx, win);
for (int32_t i = 0; i < TOOL_COUNT; i++) {
WidgetT *btn = wgtButton(root, sToolLabels[i]);
btn->onClick = onToolClick;
btn->userData = (void *)(intptr_t)i;
}
dvxFitWindow(ctx, win);
return win;
}
// ============================================================
// tbxDestroy
// ============================================================
void tbxDestroy(AppContextT *ctx, WindowT *win) {
if (win) {
dvxDestroyWindow(ctx, win);
}
sDs = NULL;
}

View file

@ -295,6 +295,32 @@ static void loadInOrder(ModuleT *mods) {
if (regFn) {
regFn();
// Record the .wgt path for any newly registered ifaces
typedef int32_t (*IfaceCountFnT)(void);
typedef const void *(*IfaceAtFnT)(int32_t, const char **);
typedef const char *(*IfaceGetPathFnT)(const char *);
typedef void (*IfaceSetPathFnT)(const char *, const char *);
IfaceCountFnT countFn = (IfaceCountFnT)dlsym(NULL, "_wgtIfaceCount");
IfaceAtFnT atFn = (IfaceAtFnT)dlsym(NULL, "_wgtIfaceAt");
IfaceGetPathFnT getPathFn = (IfaceGetPathFnT)dlsym(NULL, "_wgtIfaceGetPath");
IfaceSetPathFnT setPathFn = (IfaceSetPathFnT)dlsym(NULL, "_wgtIfaceSetPath");
if (countFn && atFn && getPathFn && setPathFn) {
int32_t ic = countFn();
for (int32_t k = 0; k < ic; k++) {
const char *ifaceName = NULL;
atFn(k, &ifaceName);
if (ifaceName && !getPathFn(ifaceName)) {
setPathFn(ifaceName, mods[i].path);
}
}
}
} else if (strstr(mods[i].path, ".wgt") || strstr(mods[i].path, ".WGT")) {
dvxLog(" No _wgtRegister in %s", mods[i].baseName);
}
mods[i].loaded = true;
@ -332,11 +358,14 @@ static void logAndReadDeps(ModuleT *mods) {
readDeps(&mods[i]);
if (arrlen(mods[i].deps) > 0) {
dvxLog(" %s deps:", mods[i].baseName);
char line[512];
int32_t pos = snprintf(line, sizeof(line), " %s deps:", mods[i].baseName);
for (int32_t d = 0; d < arrlen(mods[i].deps); d++) {
dvxLog(" %s", mods[i].deps[d]);
pos += snprintf(line + pos, sizeof(line) - pos, " %s", mods[i].deps[d]);
}
dvxLog("%s", line);
}
}
}

View file

@ -418,6 +418,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
app->dxeCtx = (DxeAppContextT *)calloc(1, sizeof(DxeAppContextT));
if (!app->dxeCtx) {
dvxLog("Shell: failed to allocate app context for %s", app->name);
ctx->currentAppId = 0;
dlclose(handle);
app->state = AppStateFreeE;

View file

@ -388,6 +388,9 @@ int shellMain(int argc, char *argv[]) {
dvxLog("Recovering from crash, killing app %ld", (long)sCtx.currentAppId);
// Clear busy cursor so the fault dialog is interactive
dvxSetBusy(&sCtx, false);
if (sCtx.currentAppId > 0) {
ShellAppT *app = shellGetApp(sCtx.currentAppId);
@ -396,12 +399,10 @@ int shellMain(int argc, char *argv[]) {
snprintf(msg, sizeof(msg), "'%s' has caused a fault and will be terminated.", app->name);
shellForceKillApp(&sCtx, app);
sCtx.currentAppId = 0;
sCtx.currentAppId = 0;
dvxMessageBox(&sCtx, "Application Error", msg, MB_OK | MB_ICONERROR);
}
}
sCtx.currentAppId = 0;
sCtx.currentAppId = 0;
sCrashSignal = 0;
shellDesktopUpdate();

View file

@ -1,118 +1,78 @@
# DVX Widget Modules Makefile for DJGPP cross-compilation
#
# Builds individual .wgt modules from widget source files.
# Each widget lives in its own subdirectory with its .c, .res, and .bmp.
# Each .wgt is a DXE loaded by the loader at startup.
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
CFLAGS = -O2 -Wall -Wextra -Wno-type-limits -Wno-sign-compare -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../core/thirdparty
DVXRES = ../bin/dvxres
OBJDIR = ../obj/widgets
WGTDIR = ../bin/widgets
SRCS = widgetAnsiTerm.c \
widgetBox.c \
widgetButton.c \
widgetCanvas.c \
widgetCheckbox.c \
widgetComboBox.c \
widgetDropdown.c \
widgetImage.c \
widgetImageButton.c \
widgetLabel.c \
widgetListBox.c \
widgetListView.c \
widgetProgressBar.c \
widgetRadio.c \
widgetScrollPane.c \
widgetSeparator.c \
widgetSlider.c \
widgetSpacer.c \
widgetSpinner.c \
widgetSplitter.c \
widgetStatusBar.c \
widgetTabControl.c \
widgetTextInput.c \
widgetTimer.c \
widgetToolbar.c \
widgetTreeView.c
# Widget name -> directory, source, short name for .wgt/.res
# Format: wgtShortName:dirName:srcFile:resFile
WIDGETS = \
box:box:widgetBox:box \
button:button:widgetButton:button \
canvas:canvas:widgetCanvas:canvas \
checkbox:checkbox:widgetCheckbox:checkbox \
combobox:comboBox:widgetComboBox:combobox \
dropdown:dropdown:widgetDropdown:dropdown \
imgbtn:imageButton:widgetImageButton:imgbtn \
image:image:widgetImage:image \
label:label:widgetLabel:label \
listbox:listBox:widgetListBox:listbox \
listview:listView:widgetListView:listview \
progress:progressBar:widgetProgressBar:progress \
radio:radio:widgetRadio:radio \
scrlpane:scrollPane:widgetScrollPane:scrlpane \
separatr:separator:widgetSeparator:separatr \
slider:slider:widgetSlider:slider \
spacer:spacer:widgetSpacer:spacer \
spinner:spinner:widgetSpinner:spinner \
splitter:splitter:widgetSplitter:splitter \
statbar:statusBar:widgetStatusBar:statbar \
tabctrl:tabControl:widgetTabControl:tabctrl \
terminal:ansiTerm:widgetAnsiTerm:terminal \
textinpt:textInput:widgetTextInput:textinpt \
timer:timer:widgetTimer:timer \
toolbar:toolbar:widgetToolbar:toolbar \
treeview:treeView:widgetTreeView:treeview
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
WGT_MODS = $(WGTDIR)/box.wgt \
$(WGTDIR)/button.wgt \
$(WGTDIR)/canvas.wgt \
$(WGTDIR)/checkbox.wgt \
$(WGTDIR)/combobox.wgt \
$(WGTDIR)/dropdown.wgt \
$(WGTDIR)/imgbtn.wgt \
$(WGTDIR)/image.wgt \
$(WGTDIR)/label.wgt \
$(WGTDIR)/listbox.wgt \
$(WGTDIR)/listview.wgt \
$(WGTDIR)/progress.wgt \
$(WGTDIR)/radio.wgt \
$(WGTDIR)/scrlpane.wgt \
$(WGTDIR)/separatr.wgt \
$(WGTDIR)/slider.wgt \
$(WGTDIR)/spacer.wgt \
$(WGTDIR)/spinner.wgt \
$(WGTDIR)/splitter.wgt \
$(WGTDIR)/statbar.wgt \
$(WGTDIR)/tabctrl.wgt \
$(WGTDIR)/terminal.wgt \
$(WGTDIR)/textinpt.wgt \
$(WGTDIR)/timer.wgt \
$(WGTDIR)/toolbar.wgt \
$(WGTDIR)/treeview.wgt
.PHONY: all clean
# Extract lists
WGT_NAMES = $(foreach w,$(WIDGETS),$(word 1,$(subst :, ,$w)))
WGT_MODS = $(WGT_NAMES:%=$(WGTDIR)/%.wgt)
OBJS = $(foreach w,$(WIDGETS),$(OBJDIR)/$(word 3,$(subst :, ,$w)).o)
DEPFILES = textinpt combobox spinner terminal listbox dropdown listview treeview
WGT_DEPS = $(DEPFILES:%=$(WGTDIR)/%.dep)
.PHONY: all clean
all: $(WGT_MODS) $(WGT_DEPS)
$(WGTDIR)/%.dep: ../config/%.dep | $(WGTDIR)
sed 's/$$/\r/' $< > $@
# Generic widget module rule: export only the registration function
$(WGTDIR)/%.wgt: | $(WGTDIR)
$(DXE3GEN) -o $(WGTDIR)/$*.dxe -E _wgtRegister -U $<
mv $(WGTDIR)/$*.dxe $@
# Compile: source is in subdirectory
WIDGET_DEPS_H = ../core/dvxWidgetPlugin.h ../core/dvxWidget.h ../core/dvxTypes.h ../core/dvxApp.h ../core/dvxDraw.h ../core/dvxWm.h ../core/dvxVideo.h ../core/platform/dvxPlatform.h
# Map .wgt name to .o file
$(WGTDIR)/box.wgt: $(OBJDIR)/widgetBox.o
$(WGTDIR)/button.wgt: $(OBJDIR)/widgetButton.o
$(WGTDIR)/canvas.wgt: $(OBJDIR)/widgetCanvas.o
$(WGTDIR)/checkbox.wgt: $(OBJDIR)/widgetCheckbox.o
$(WGTDIR)/combobox.wgt: $(OBJDIR)/widgetComboBox.o
$(WGTDIR)/dropdown.wgt: $(OBJDIR)/widgetDropdown.o
$(WGTDIR)/imgbtn.wgt: $(OBJDIR)/widgetImageButton.o
$(WGTDIR)/image.wgt: $(OBJDIR)/widgetImage.o
$(WGTDIR)/label.wgt: $(OBJDIR)/widgetLabel.o
$(WGTDIR)/listbox.wgt: $(OBJDIR)/widgetListBox.o
$(WGTDIR)/listview.wgt: $(OBJDIR)/widgetListView.o
$(WGTDIR)/progress.wgt: $(OBJDIR)/widgetProgressBar.o
$(WGTDIR)/radio.wgt: $(OBJDIR)/widgetRadio.o
$(WGTDIR)/scrlpane.wgt: $(OBJDIR)/widgetScrollPane.o
$(WGTDIR)/separatr.wgt: $(OBJDIR)/widgetSeparator.o
$(WGTDIR)/slider.wgt: $(OBJDIR)/widgetSlider.o
$(WGTDIR)/spacer.wgt: $(OBJDIR)/widgetSpacer.o
$(WGTDIR)/spinner.wgt: $(OBJDIR)/widgetSpinner.o
$(WGTDIR)/splitter.wgt: $(OBJDIR)/widgetSplitter.o
$(WGTDIR)/statbar.wgt: $(OBJDIR)/widgetStatusBar.o
$(WGTDIR)/tabctrl.wgt: $(OBJDIR)/widgetTabControl.o
$(WGTDIR)/terminal.wgt: $(OBJDIR)/widgetAnsiTerm.o
$(WGTDIR)/textinpt.wgt: $(OBJDIR)/widgetTextInput.o
$(WGTDIR)/timer.wgt: $(OBJDIR)/widgetTimer.o
$(WGTDIR)/toolbar.wgt: $(OBJDIR)/widgetToolbar.o
$(WGTDIR)/treeview.wgt: $(OBJDIR)/widgetTreeView.o
define WIDGET_RULES
$(OBJDIR)/$(word 3,$(subst :, ,$1)).o: $(word 2,$(subst :, ,$1))/$(word 3,$(subst :, ,$1)).c $$(WIDGET_DEPS_H) | $(OBJDIR)
$$(CC) $$(CFLAGS) -c -o $$@ $$<
# Compile
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(WGTDIR)/$(word 1,$(subst :, ,$1)).wgt: $(OBJDIR)/$(word 3,$(subst :, ,$1)).o | $(WGTDIR)
$$(DXE3GEN) -o $(WGTDIR)/$(word 1,$(subst :, ,$1)).dxe -E _wgtRegister -U $$<
mv $(WGTDIR)/$(word 1,$(subst :, ,$1)).dxe $$@
@if [ -f $(word 2,$(subst :, ,$1))/$(word 4,$(subst :, ,$1)).res ]; then \
cd $(word 2,$(subst :, ,$1)) && ../$(DVXRES) build ../$$@ $(word 4,$(subst :, ,$1)).res; \
fi
endef
$(foreach w,$(WIDGETS),$(eval $(call WIDGET_RULES,$w)))
$(OBJDIR):
mkdir -p $(OBJDIR)
@ -120,35 +80,6 @@ $(OBJDIR):
$(WGTDIR):
mkdir -p $(WGTDIR)
# Dependencies
WIDGET_DEPS = ../core/dvxWidgetPlugin.h ../core/dvxWidget.h ../core/dvxTypes.h ../core/dvxApp.h ../core/dvxDraw.h ../core/dvxWm.h ../core/dvxVideo.h
$(OBJDIR)/widgetAnsiTerm.o: widgetAnsiTerm.c $(WIDGET_DEPS)
$(OBJDIR)/widgetBox.o: widgetBox.c $(WIDGET_DEPS)
$(OBJDIR)/widgetButton.o: widgetButton.c $(WIDGET_DEPS)
$(OBJDIR)/widgetCanvas.o: widgetCanvas.c $(WIDGET_DEPS) ../core/thirdparty/stb_image.h ../core/thirdparty/stb_image_write.h
$(OBJDIR)/widgetCheckbox.o: widgetCheckbox.c $(WIDGET_DEPS)
$(OBJDIR)/widgetComboBox.o: widgetComboBox.c $(WIDGET_DEPS)
$(OBJDIR)/widgetDropdown.o: widgetDropdown.c $(WIDGET_DEPS)
$(OBJDIR)/widgetImage.o: widgetImage.c $(WIDGET_DEPS) ../core/thirdparty/stb_image.h
$(OBJDIR)/widgetImageButton.o: widgetImageButton.c $(WIDGET_DEPS)
$(OBJDIR)/widgetLabel.o: widgetLabel.c $(WIDGET_DEPS)
$(OBJDIR)/widgetListBox.o: widgetListBox.c $(WIDGET_DEPS)
$(OBJDIR)/widgetListView.o: widgetListView.c $(WIDGET_DEPS)
$(OBJDIR)/widgetProgressBar.o: widgetProgressBar.c $(WIDGET_DEPS)
$(OBJDIR)/widgetRadio.o: widgetRadio.c $(WIDGET_DEPS)
$(OBJDIR)/widgetScrollPane.o: widgetScrollPane.c $(WIDGET_DEPS)
$(OBJDIR)/widgetSeparator.o: widgetSeparator.c $(WIDGET_DEPS)
$(OBJDIR)/widgetSlider.o: widgetSlider.c $(WIDGET_DEPS)
$(OBJDIR)/widgetSpacer.o: widgetSpacer.c $(WIDGET_DEPS)
$(OBJDIR)/widgetSpinner.o: widgetSpinner.c $(WIDGET_DEPS)
$(OBJDIR)/widgetSplitter.o: widgetSplitter.c $(WIDGET_DEPS)
$(OBJDIR)/widgetStatusBar.o: widgetStatusBar.c $(WIDGET_DEPS)
$(OBJDIR)/widgetTabControl.o: widgetTabControl.c $(WIDGET_DEPS)
$(OBJDIR)/widgetTextInput.o: widgetTextInput.c $(WIDGET_DEPS)
$(OBJDIR)/widgetTimer.o: widgetTimer.c $(WIDGET_DEPS)
$(OBJDIR)/widgetToolbar.o: widgetToolbar.c $(WIDGET_DEPS)
$(OBJDIR)/widgetTreeView.o: widgetTreeView.c $(WIDGET_DEPS)
clean:
rm -f $(OBJS) $(WGT_MODS) $(WGT_DEPS)
-rmdir $(WGTDIR) 2>/dev/null

BIN
widgets/ansiTerm/terminal.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
icon16 icon terminal.bmp
name text "Terminal"
author text "DVX Project"
description text "ANSI terminal emulator widget"
version text "1.0"

View file

@ -1087,7 +1087,9 @@ static const WgtIfaceT sIface = {
.methods = sMethods,
.methodCount = 2,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_INT_INT,
.createArgs = { 80, 25 }
};
void wgtRegister(void) {

BIN
widgets/box/box.bmp (Stored with Git LFS) Normal file

Binary file not shown.

14
widgets/box/box.res Normal file
View file

@ -0,0 +1,14 @@
# box.res -- Resources for VBox, HBox, and Frame widgets
#
# VBox (first registered, uses unsuffixed names)
icon16 icon box.bmp
name text "VBox"
# HBox (second registered)
icon16-2 icon box.bmp
name-2 text "HBox"
# Frame (third registered)
icon16-3 icon box.bmp
name-3 text "Frame"
author text "DVX Project"
description text "VBox, HBox, and Frame container widgets"
version text "1.0"

View file

@ -205,20 +205,66 @@ static const struct {
.frame = wgtFrame
};
static const WgtIfaceT sIface = {
// Separate API structs for each box type so the designer can
// create them independently. Each has create as the first slot.
static const struct { WidgetT *(*create)(WidgetT *); } sVBoxApi = { .create = wgtVBox };
static const struct { WidgetT *(*create)(WidgetT *); } sHBoxApi = { .create = wgtHBox };
static const struct { WidgetT *(*create)(WidgetT *, const char *); } sFrameApi = { .create = wgtFrame };
static const WgtIfaceT sIfaceVBox = {
.basName = "VBox",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0,
.createSig = WGT_CREATE_PARENT,
.isContainer = true,
.defaultEvent = "Click"
};
static const WgtIfaceT sIfaceHBox = {
.basName = "HBox",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0,
.createSig = WGT_CREATE_PARENT,
.isContainer = true,
.defaultEvent = "Click"
};
static const WgtIfaceT sIfaceFrame = {
.basName = "Frame",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_TEXT,
.isContainer = true,
.defaultEvent = "Click"
};
void wgtRegister(void) {
sVBoxTypeId = wgtRegisterClass(&sClassVBox);
sHBoxTypeId = wgtRegisterClass(&sClassHBox);
sFrameTypeId = wgtRegisterClass(&sClassFrame);
// Original combined API (for widgetBox.h compatibility)
wgtRegisterApi("box", &sApi);
wgtRegisterIface("box", &sIface);
// Per-type APIs and ifaces (for designer/toolbox)
wgtRegisterApi("vbox", &sVBoxApi);
wgtRegisterIface("vbox", &sIfaceVBox);
wgtRegisterApi("hbox", &sHBoxApi);
wgtRegisterIface("hbox", &sIfaceHBox);
wgtRegisterApi("frame", &sFrameApi);
wgtRegisterIface("frame", &sIfaceFrame);
}

BIN
widgets/button/button.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
icon16 icon button.bmp
name text "Button"
author text "DVX Project"
description text "Command button widget"
version text "1.0"

View file

@ -268,7 +268,10 @@ static const WgtIfaceT sIface = {
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_TEXT,
.defaultEvent = "Click",
.namePrefix = "Command"
};
void wgtRegister(void) {

BIN
widgets/canvas/canvas.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
icon16 icon canvas.bmp
name text "Canvas"
author text "DVX Project"
description text "Pixel drawing surface"
version text "1.0"

View file

@ -937,7 +937,11 @@ static const WgtIfaceT sIface = {
.methods = sMethods,
.methodCount = 1,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_INT_INT,
.createArgs = { 64, 64 },
.defaultEvent = "Click",
.namePrefix = "Picture"
};
void wgtRegister(void) {

BIN
widgets/checkbox/checkbox.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
icon16 icon checkbox.bmp
name text "CheckBox"
author text "DVX Project"
description text "Check box toggle widget"
version text "1.0"

View file

@ -262,7 +262,9 @@ static const WgtIfaceT sIface = {
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_TEXT,
.defaultEvent = "Click"
};
void wgtRegister(void) {

BIN
widgets/comboBox/combobox.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
icon16 icon combobox.bmp
name text "ComboBox"
author text "DVX Project"
description text "Editable dropdown combo box"
version text "1.0"

View file

@ -548,7 +548,10 @@ static const WgtIfaceT sIface = {
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_INT,
.createArgs = { 256 },
.defaultEvent = "Click"
};
void wgtRegister(void) {

BIN
widgets/dropdown/dropdown.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
icon16 icon dropdown.bmp
name text "DropDown"
author text "DVX Project"
description text "Non-editable dropdown selector"
version text "1.0"

View file

@ -411,7 +411,9 @@ static const WgtIfaceT sIface = {
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT,
.defaultEvent = "Click"
};
void wgtRegister(void) {

BIN
widgets/image/image.bmp (Stored with Git LFS) Normal file

Binary file not shown.

5
widgets/image/image.res Normal file
View file

@ -0,0 +1,5 @@
icon16 icon image.bmp
name text "Image"
author text "DVX Project"
description text "Static image display"
version text "1.0"

View file

@ -261,7 +261,9 @@ static const WgtIfaceT sIface = {
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_DATA,
.defaultEvent = "Click"
};
void wgtRegister(void) {

BIN
widgets/imageButton/imgbtn.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
icon16 icon imgbtn.bmp
name text "ImageButton"
author text "DVX Project"
description text "Button with bitmap icon"
version text "1.0"

View file

@ -335,7 +335,9 @@ static const WgtIfaceT sIface = {
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_DATA,
.defaultEvent = "Click"
};
void wgtRegister(void) {

BIN
widgets/label/label.bmp (Stored with Git LFS) Normal file

Binary file not shown.

5
widgets/label/label.res Normal file
View file

@ -0,0 +1,5 @@
icon16 icon label.bmp
name text "Label"
author text "DVX Project"
description text "Static text label"
version text "1.0"

View file

@ -171,7 +171,9 @@ static const WgtIfaceT sIface = {
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT_TEXT,
.defaultEvent = "Click"
};
void wgtRegister(void) {

BIN
widgets/listBox/listbox.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
icon16 icon listbox.bmp
name text "ListBox"
author text "DVX Project"
description text "Scrollable item list with single or multi-select"
version text "1.0"

View file

@ -877,7 +877,9 @@ static const WgtIfaceT sIface = {
.methods = sMethods,
.methodCount = 6,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT,
.defaultEvent = "Click"
};
void wgtRegister(void) {

BIN
widgets/listView/listview.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
icon16 icon listview.bmp
name text "ListView"
author text "DVX Project"
description text "Multi-column list with sortable headers"
version text "1.0"

View file

@ -855,7 +855,10 @@ void widgetListViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
hit->onChange(hit);
}
// onDblClick is handled by the central dispatcher in widgetEvent.c
// Double-click: fire onDblClick
if (hit->onDblClick && multiClickDetect(vx, vy) >= 2) {
hit->onDblClick(hit);
}
// Initiate drag-reorder if enabled (not from modifier clicks)
if (lv->reorderable && !shift && !ctrl) {
@ -1753,7 +1756,9 @@ static const WgtIfaceT sIface = {
.methods = sMethods,
.methodCount = 6,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT,
.defaultEvent = "Click"
};
void wgtRegister(void) {

BIN
widgets/progressBar/progress.bmp (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
icon16 icon progress.bmp
name text "ProgressBar"
author text "DVX Project"
description text "Progress indicator bar"
version text "1.0"

View file

@ -220,7 +220,8 @@ static const WgtIfaceT sIface = {
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
.eventCount = 0,
.createSig = WGT_CREATE_PARENT
};
void wgtRegister(void) {

BIN
widgets/radio/radio.bmp (Stored with Git LFS) Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more