Tons of work on the form designer and widget system to support it.
This commit is contained in:
parent
59bc2b5ed3
commit
d094205ed0
138 changed files with 3251 additions and 1202 deletions
8
Makefile
8
Makefile
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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,14 +590,26 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
return NULL;
|
||||
}
|
||||
|
||||
parentStack[0] = form->contentBox;
|
||||
nestDepth = 1;
|
||||
current = NULL;
|
||||
// contentBox is created lazily (see below) after
|
||||
// form-level properties like Layout are parsed.
|
||||
nestDepth = 1;
|
||||
current = NULL;
|
||||
|
||||
if (blockDepth < MAX_FRM_NESTING) {
|
||||
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,27 +705,80 @@ 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) {
|
||||
char *text = value;
|
||||
// Form-level property -- strip quotes from string values
|
||||
char *text = value;
|
||||
|
||||
if (text[0] == '"') {
|
||||
text++;
|
||||
int32_t len = (int32_t)strlen(text);
|
||||
if (text[0] == '"') {
|
||||
text++;
|
||||
int32_t len = (int32_t)strlen(text);
|
||||
|
||||
if (len > 0 && text[len - 1] == '"') {
|
||||
text[len - 1] = '\0';
|
||||
}
|
||||
if (len > 0 && text[len - 1] == '"') {
|
||||
text[len - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
dvxFitWindow(rt->ctx, form->window);
|
||||
// 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)
|
||||
CreateParentTextFnT fn = *(CreateParentTextFnT *)api;
|
||||
return fn(parent, "");
|
||||
switch (sig) {
|
||||
case WGT_CREATE_PARENT_TEXT: {
|
||||
CreateParentTextFnT fn = *(CreateParentTextFnT *)api;
|
||||
return fn(parent, "");
|
||||
}
|
||||
case WGT_CREATE_PARENT_INT: {
|
||||
CreateParentIntFnT fn = *(CreateParentIntFnT *)api;
|
||||
return fn(parent, iface->createArgs[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, iface->createArgs[0], (bool)iface->createArgs[1]);
|
||||
}
|
||||
case WGT_CREATE_PARENT_BOOL: {
|
||||
CreateParentBoolFnT fn = *(CreateParentBoolFnT *)api;
|
||||
return fn(parent, (bool)iface->createArgs[0]);
|
||||
}
|
||||
case WGT_CREATE_PARENT_DATA:
|
||||
return NULL;
|
||||
default: {
|
||||
CreateParentFnT fn = *(CreateParentFnT *)api;
|
||||
return fn(parent);
|
||||
}
|
||||
}
|
||||
|
||||
if (strcasecmp(wgtTypeName, "textinput") == 0 ||
|
||||
strcasecmp(wgtTypeName, "combobox") == 0) {
|
||||
// create(parent, maxLen)
|
||||
CreateParentIntFnT fn = *(CreateParentIntFnT *)api;
|
||||
return fn(parent, DEFAULT_CREATE_ARG);
|
||||
}
|
||||
|
||||
if (strcasecmp(wgtTypeName, "slider") == 0) {
|
||||
// create(parent, minVal, maxVal)
|
||||
CreateParentIntIntFnT fn = *(CreateParentIntIntFnT *)api;
|
||||
return fn(parent, 0, 100);
|
||||
}
|
||||
|
||||
if (strcasecmp(wgtTypeName, "spinner") == 0) {
|
||||
// create(parent, minVal, maxVal, step)
|
||||
CreateParentIntIntIntFnT fn = *(CreateParentIntIntIntFnT *)api;
|
||||
return fn(parent, 0, 100, 1);
|
||||
}
|
||||
|
||||
if (strcasecmp(wgtTypeName, "timer") == 0) {
|
||||
// create(parent, intervalMs, repeat)
|
||||
CreateParentIntBoolFnT fn = *(CreateParentIntBoolFnT *)api;
|
||||
return fn(parent, 1000, true);
|
||||
}
|
||||
|
||||
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, "");
|
||||
}
|
||||
|
||||
// Default: assume create(parent) with no extra args
|
||||
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;
|
||||
}
|
||||
|
|
@ -37,11 +37,12 @@ typedef struct BasControlT BasControlT;
|
|||
#define BAS_MAX_TEXT_BUF 256
|
||||
|
||||
typedef struct BasControlT {
|
||||
char name[BAS_MAX_CTRL_NAME]; // VB control name (e.g. "Command1")
|
||||
WidgetT *widget; // the DVX widget
|
||||
BasFormT *form; // owning form
|
||||
const WgtIfaceT *iface; // interface descriptor (from .wgt)
|
||||
char textBuf[BAS_MAX_TEXT_BUF]; // persistent text for Caption/Text
|
||||
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)
|
||||
char textBuf[BAS_MAX_TEXT_BUF]; // persistent text for Caption/Text
|
||||
} BasControlT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -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;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -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) {
|
||||
CreateParentTextFnT fn = *(CreateParentTextFnT *)api;
|
||||
return fn(parent, "");
|
||||
switch (sig) {
|
||||
case WGT_CREATE_PARENT_TEXT: {
|
||||
CreateParentTextFnT fn = *(CreateParentTextFnT *)api;
|
||||
return fn(parent, "");
|
||||
}
|
||||
case WGT_CREATE_PARENT_INT: {
|
||||
CreateParentIntFnT fn = *(CreateParentIntFnT *)api;
|
||||
return fn(parent, iface->createArgs[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, iface->createArgs[0], (bool)iface->createArgs[1]);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (strcasecmp(wgtName, "textinput") == 0 ||
|
||||
strcasecmp(wgtName, "combobox") == 0) {
|
||||
CreateParentIntFnT fn = *(CreateParentIntFnT *)api;
|
||||
return fn(parent, DEFAULT_CREATE_ARG);
|
||||
}
|
||||
|
||||
if (strcasecmp(wgtName, "timer") == 0) {
|
||||
CreateParentIntBoolFnT fn = *(CreateParentIntBoolFnT *)api;
|
||||
return fn(parent, 1000, false);
|
||||
}
|
||||
|
||||
if (strcasecmp(wgtName, "listbox") == 0) {
|
||||
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) {
|
||||
const char *prefix = typeName;
|
||||
// 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);
|
||||
w->minH = 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -250,9 +285,9 @@ void dsgnFree(DsgnStateT *ds) {
|
|||
|
||||
void dsgnInit(DsgnStateT *ds, AppContextT *ctx) {
|
||||
memset(ds, 0, sizeof(*ds));
|
||||
ds->selectedIdx = -1;
|
||||
ds->activeTool = TOOL_POINTER;
|
||||
ds->mode = DSGN_IDLE;
|
||||
ds->selectedIdx = -1;
|
||||
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;
|
||||
|
||||
|
|
@ -348,18 +394,30 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
if (strcasecmp(typeName, "Form") == 0) {
|
||||
snprintf(form->name, DSGN_MAX_NAME, "%s", ctrlName);
|
||||
snprintf(form->caption, DSGN_MAX_TEXT, "%s", ctrlName);
|
||||
inForm = true;
|
||||
curCtrl = NULL;
|
||||
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;
|
||||
|
|
@ -415,16 +478,27 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
val[vi] = '\0';
|
||||
|
||||
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); }
|
||||
if (strcasecmp(key, "Left") == 0) { curCtrl->left = atoi(val); }
|
||||
else if (strcasecmp(key, "Top") == 0) { curCtrl->top = 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); }
|
||||
if (strcasecmp(key, "Caption") == 0) { snprintf(form->caption, DSGN_MAX_TEXT, "%s", 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -443,9 +517,15 @@ void dsgnNewForm(DsgnStateT *ds, const char *name) {
|
|||
dsgnFree(ds);
|
||||
|
||||
DsgnFormT *form = (DsgnFormT *)calloc(1, sizeof(DsgnFormT));
|
||||
form->controls = NULL;
|
||||
form->width = DEFAULT_FORM_W;
|
||||
form->height = DEFAULT_FORM_H;
|
||||
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;
|
||||
ctrl.width = DEFAULT_CTRL_W;
|
||||
ctrl.height = DEFAULT_CTRL_H;
|
||||
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;
|
||||
|
|
@ -710,37 +899,25 @@ 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, " Width = %d\n", (int)ds->form->width);
|
||||
pos += snprintf(buf + pos, bufSize - pos, " Height = %d\n", (int)ds->form->height);
|
||||
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");
|
||||
|
||||
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");
|
||||
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);
|
||||
}
|
||||
|
||||
// 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->minH = wgtPixels(ctrl->height);
|
||||
ctrl->widget->prefH = wgtPixels(ctrl->height);
|
||||
ctrl->widget->minW = wgtPixels(ctrl->width);
|
||||
ctrl->widget->minH = wgtPixels(ctrl->height);
|
||||
ctrl->widget->maxW = ctrl->maxWidth > 0 ? wgtPixels(ctrl->maxWidth) : 0;
|
||||
ctrl->widget->maxH = ctrl->maxHeight > 0 ? wgtPixels(ctrl->maxHeight) : 0;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
@ -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) {
|
||||
bool drag = wasDown;
|
||||
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) {
|
||||
|
|
@ -1298,13 +1498,21 @@ static void switchToDesign(void) {
|
|||
sFormWin->onClose = onFormWinClose;
|
||||
sDesigner.formWin = sFormWin;
|
||||
|
||||
WidgetT *root = wgtInitWindow(sAc, sFormWin);
|
||||
WidgetT *contentBox = wgtVBox(root);
|
||||
contentBox->weight = 100;
|
||||
WidgetT *root = wgtInitWindow(sAc, sFormWin);
|
||||
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)
|
||||
dvxFitWindow(sAc, sFormWin);
|
||||
// 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
|
||||
// ============================================================
|
||||
906
apps/dvxbasic/ide/ideProperties.c
Normal file
906
apps/dvxbasic/ide/ideProperties.c
Normal 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);
|
||||
}
|
||||
|
|
@ -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
|
||||
213
apps/dvxbasic/ide/ideToolbox.c
Normal file
213
apps/dvxbasic/ide/ideToolbox.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -396,6 +396,5 @@ int32_t appMain(DxeAppContextT *ctx) {
|
|||
sFilePath[0] = '\0';
|
||||
markClean();
|
||||
|
||||
wgtInvalidate(root);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
126
core/dvxDialog.c
126
core/dvxDialog.c
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -581,15 +581,35 @@ 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
|
||||
const WgtPropDescT *props; // type-specific properties
|
||||
const char *basName; // VB-style name (e.g. "CommandButton"), or NULL
|
||||
const WgtPropDescT *props; // type-specific properties
|
||||
int32_t propCount;
|
||||
const WgtMethodDescT *methods; // type-specific methods
|
||||
const WgtMethodDescT *methods; // type-specific methods
|
||||
int32_t methodCount;
|
||||
const WgtEventDescT *events; // extra events beyond common set
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
char *key;
|
||||
const WgtIfaceT *value;
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
171
widgets/Makefile
171
widgets/Makefile
|
|
@ -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
BIN
widgets/ansiTerm/terminal.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/ansiTerm/terminal.res
Normal file
5
widgets/ansiTerm/terminal.res
Normal 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"
|
||||
|
|
@ -1081,13 +1081,15 @@ static const WgtMethodDescT sMethods[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "Terminal",
|
||||
.props = sProps,
|
||||
.propCount = 3,
|
||||
.methods = sMethods,
|
||||
.methodCount = 2,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "Terminal",
|
||||
.props = sProps,
|
||||
.propCount = 3,
|
||||
.methods = sMethods,
|
||||
.methodCount = 2,
|
||||
.events = NULL,
|
||||
.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
BIN
widgets/box/box.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
14
widgets/box/box.res
Normal file
14
widgets/box/box.res
Normal 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"
|
||||
|
|
@ -205,20 +205,66 @@ static const struct {
|
|||
.frame = wgtFrame
|
||||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "Frame",
|
||||
.props = NULL,
|
||||
.propCount = 0,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
// 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,
|
||||
.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
BIN
widgets/button/button.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/button/button.res
Normal file
5
widgets/button/button.res
Normal 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"
|
||||
|
|
@ -262,13 +262,16 @@ static const struct {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "CommandButton",
|
||||
.props = NULL,
|
||||
.propCount = 0,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "CommandButton",
|
||||
.props = NULL,
|
||||
.propCount = 0,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.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
BIN
widgets/canvas/canvas.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/canvas/canvas.res
Normal file
5
widgets/canvas/canvas.res
Normal 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"
|
||||
|
|
@ -931,13 +931,17 @@ static const WgtMethodDescT sMethods[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "PictureBox",
|
||||
.props = NULL,
|
||||
.propCount = 0,
|
||||
.methods = sMethods,
|
||||
.methodCount = 1,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "PictureBox",
|
||||
.props = NULL,
|
||||
.propCount = 0,
|
||||
.methods = sMethods,
|
||||
.methodCount = 1,
|
||||
.events = NULL,
|
||||
.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
BIN
widgets/checkbox/checkbox.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/checkbox/checkbox.res
Normal file
5
widgets/checkbox/checkbox.res
Normal 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"
|
||||
|
|
@ -256,13 +256,15 @@ static const WgtPropDescT sProps[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "CheckBox",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "CheckBox",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT_TEXT,
|
||||
.defaultEvent = "Click"
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
BIN
widgets/comboBox/combobox.bmp
(Stored with Git LFS)
Normal file
BIN
widgets/comboBox/combobox.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/comboBox/combobox.res
Normal file
5
widgets/comboBox/combobox.res
Normal 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"
|
||||
|
|
@ -542,13 +542,16 @@ static const WgtPropDescT sProps[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "ComboBox",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "ComboBox",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.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
BIN
widgets/dropdown/dropdown.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/dropdown/dropdown.res
Normal file
5
widgets/dropdown/dropdown.res
Normal 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"
|
||||
|
|
@ -405,13 +405,15 @@ static const WgtPropDescT sProps[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "DropDown",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "DropDown",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT,
|
||||
.defaultEvent = "Click"
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
BIN
widgets/image/image.bmp
(Stored with Git LFS)
Normal file
BIN
widgets/image/image.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/image/image.res
Normal file
5
widgets/image/image.res
Normal 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"
|
||||
|
|
@ -255,13 +255,15 @@ static const WgtPropDescT sProps[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "Image",
|
||||
.props = sProps,
|
||||
.propCount = 3,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "Image",
|
||||
.props = sProps,
|
||||
.propCount = 3,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT_DATA,
|
||||
.defaultEvent = "Click"
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
BIN
widgets/imageButton/imgbtn.bmp
(Stored with Git LFS)
Normal file
BIN
widgets/imageButton/imgbtn.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/imageButton/imgbtn.res
Normal file
5
widgets/imageButton/imgbtn.res
Normal 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"
|
||||
|
|
@ -329,13 +329,15 @@ static const WgtPropDescT sImgBtnProps[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "ImageButton",
|
||||
.props = sImgBtnProps,
|
||||
.propCount = 3,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "ImageButton",
|
||||
.props = sImgBtnProps,
|
||||
.propCount = 3,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT_DATA,
|
||||
.defaultEvent = "Click"
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
BIN
widgets/label/label.bmp
(Stored with Git LFS)
Normal file
BIN
widgets/label/label.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/label/label.res
Normal file
5
widgets/label/label.res
Normal 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"
|
||||
|
|
@ -165,13 +165,15 @@ static const WgtPropDescT sProps[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "Label",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "Label",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT_TEXT,
|
||||
.defaultEvent = "Click"
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
BIN
widgets/listBox/listbox.bmp
(Stored with Git LFS)
Normal file
BIN
widgets/listBox/listbox.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/listBox/listbox.res
Normal file
5
widgets/listBox/listbox.res
Normal 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"
|
||||
|
|
@ -871,13 +871,15 @@ static const WgtMethodDescT sMethods[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "ListBox",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = sMethods,
|
||||
.methodCount = 6,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "ListBox",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = sMethods,
|
||||
.methodCount = 6,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT,
|
||||
.defaultEvent = "Click"
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
BIN
widgets/listView/listview.bmp
(Stored with Git LFS)
Normal file
BIN
widgets/listView/listview.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/listView/listview.res
Normal file
5
widgets/listView/listview.res
Normal 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"
|
||||
|
|
@ -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) {
|
||||
|
|
@ -1747,13 +1750,15 @@ static const WgtMethodDescT sMethods[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "ListView",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = sMethods,
|
||||
.methodCount = 6,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "ListView",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = sMethods,
|
||||
.methodCount = 6,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT,
|
||||
.defaultEvent = "Click"
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
BIN
widgets/progressBar/progress.bmp
(Stored with Git LFS)
Normal file
BIN
widgets/progressBar/progress.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/progressBar/progress.res
Normal file
5
widgets/progressBar/progress.res
Normal 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"
|
||||
|
|
@ -214,13 +214,14 @@ static const WgtPropDescT sProps[] = {
|
|||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "ProgressBar",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0
|
||||
.basName = "ProgressBar",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
BIN
widgets/radio/radio.bmp
(Stored with Git LFS)
Normal file
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
Loading…
Add table
Reference in a new issue