More IDE work.
This commit is contained in:
parent
7d74d76985
commit
59bc2b5ed3
7 changed files with 722 additions and 471 deletions
|
|
@ -3259,10 +3259,21 @@ static void updateCursorShape(AppContextT *ctx) {
|
|||
newCursor = CURSOR_RESIZE_V;
|
||||
}
|
||||
} else if (hitIdx >= 0 && hitPart == HIT_CONTENT) {
|
||||
// Hovering over content area -- check for ListView column border
|
||||
WindowT *win = ctx->stack.windows[hitIdx];
|
||||
|
||||
if (win->widgetRoot) {
|
||||
// Window-level cursor query (e.g. form designer handles)
|
||||
if (win->onCursorQuery) {
|
||||
int32_t cx = mx - win->x - win->contentX;
|
||||
int32_t cy = my - win->y - win->contentY;
|
||||
int32_t shape = win->onCursorQuery(win, cx, cy);
|
||||
|
||||
if (shape > 0) {
|
||||
newCursor = shape;
|
||||
}
|
||||
}
|
||||
|
||||
// Widget-level cursor query (e.g. ListView column border)
|
||||
if (newCursor == CURSOR_ARROW && win->widgetRoot) {
|
||||
int32_t cx = mx - win->x - win->contentX;
|
||||
int32_t cy = my - win->y - win->contentY;
|
||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
||||
|
|
|
|||
|
|
@ -555,6 +555,7 @@ typedef struct WindowT {
|
|||
void (*onClose)(struct WindowT *win);
|
||||
void (*onMenu)(struct WindowT *win, int32_t menuId);
|
||||
void (*onScroll)(struct WindowT *win, ScrollbarOrientE orient, int32_t value);
|
||||
int32_t (*onCursorQuery)(struct WindowT *win, int32_t x, int32_t y); // return CURSOR_* or 0 for default
|
||||
} WindowT;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
// ideDesigner.c -- DVX BASIC form designer implementation
|
||||
//
|
||||
// Design surface rendering, hit testing, mouse interaction,
|
||||
// and .frm file I/O. Controls are stored as pure data and
|
||||
// rendered onto a Canvas widget.
|
||||
// WYSIWYG design surface using real DVX widgets positioned
|
||||
// identically to how the form runtime lays them out. Selection
|
||||
// handles and grid dots are drawn in the window's onPaint callback.
|
||||
|
||||
#include "ideDesigner.h"
|
||||
#include "dvxDraw.h"
|
||||
#include "dvxVideo.h"
|
||||
#include "stb_ds_wrap.h"
|
||||
|
||||
|
|
@ -24,6 +25,7 @@
|
|||
#define DEFAULT_CTRL_W 100
|
||||
#define DEFAULT_CTRL_H 30
|
||||
#define MIN_CTRL_SIZE 8
|
||||
#define DEFAULT_CREATE_ARG 256
|
||||
|
||||
// ============================================================
|
||||
// Tool type name table
|
||||
|
|
@ -75,14 +77,68 @@ static const DefaultEventT sDefaultEvents[] = {
|
|||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
static void drawControl(DsgnStateT *ds, const DsgnControlT *ctrl, bool selected);
|
||||
static void drawGrid(DsgnStateT *ds);
|
||||
static void drawHandles(DsgnStateT *ds, const DsgnControlT *ctrl);
|
||||
static WidgetT *createDesignWidget(const char *vbTypeName, WidgetT *parent);
|
||||
static const char *getPropValue(const DsgnControlT *ctrl, const char *name);
|
||||
static DsgnHandleE hitTestHandles(const DsgnControlT *ctrl, int32_t x, int32_t y);
|
||||
static int32_t hitTestControl(const DsgnStateT *ds, int32_t x, int32_t y);
|
||||
static int32_t snapToGrid(int32_t val, int32_t gridSize);
|
||||
static const char *resolveTypeName(const char *typeName);
|
||||
static void setPropValue(DsgnControlT *ctrl, const char *name, const char *value);
|
||||
static void rebuildWidgets(DsgnStateT *ds);
|
||||
static void syncWidgetGeom(DsgnControlT *ctrl);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// createDesignWidget
|
||||
// ============================================================
|
||||
//
|
||||
// Create a real DVX widget for design-time display. Mirrors the
|
||||
// logic in formrt.c createWidget().
|
||||
|
||||
static WidgetT *createDesignWidget(const char *vbTypeName, WidgetT *parent) {
|
||||
const char *wgtName = resolveTypeName(vbTypeName);
|
||||
|
||||
if (!wgtName) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const void *api = wgtGetApi(wgtName);
|
||||
|
||||
if (!api) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef WidgetT *(*CreateParentFnT)(WidgetT *);
|
||||
typedef WidgetT *(*CreateParentTextFnT)(WidgetT *, const char *);
|
||||
typedef WidgetT *(*CreateParentIntFnT)(WidgetT *, int32_t);
|
||||
typedef WidgetT *(*CreateParentIntBoolFnT)(WidgetT *, int32_t, bool);
|
||||
|
||||
if (strcasecmp(wgtName, "button") == 0 ||
|
||||
strcasecmp(wgtName, "checkbox") == 0 ||
|
||||
strcasecmp(wgtName, "label") == 0) {
|
||||
CreateParentTextFnT fn = *(CreateParentTextFnT *)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);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -90,7 +146,6 @@ static void setPropValue(DsgnControlT *ctrl, const char *name, const char *v
|
|||
// ============================================================
|
||||
|
||||
void dsgnAutoName(const DsgnStateT *ds, const char *typeName, char *buf, int32_t bufSize) {
|
||||
// Map VB type to short prefix
|
||||
const char *prefix = typeName;
|
||||
|
||||
if (strcasecmp(typeName, "CommandButton") == 0) { prefix = "Command"; }
|
||||
|
|
@ -117,6 +172,50 @@ void dsgnAutoName(const DsgnStateT *ds, const char *typeName, char *buf, int32_t
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnCreateWidgets
|
||||
// ============================================================
|
||||
|
||||
void dsgnCreateWidgets(DsgnStateT *ds, WidgetT *contentBox) {
|
||||
if (!ds->form || !contentBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
ds->form->contentBox = contentBox;
|
||||
int32_t count = (int32_t)arrlen(ds->form->controls);
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
DsgnControlT *ctrl = &ds->form->controls[i];
|
||||
|
||||
if (ctrl->widget) {
|
||||
continue; // already created
|
||||
}
|
||||
|
||||
WidgetT *w = createDesignWidget(ctrl->typeName, contentBox);
|
||||
|
||||
if (!w) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ctrl->widget = w;
|
||||
wgtSetName(w, ctrl->name);
|
||||
|
||||
// Set Caption/Text
|
||||
const char *caption = getPropValue(ctrl, "Caption");
|
||||
const char *text = getPropValue(ctrl, "Text");
|
||||
|
||||
if (caption) { wgtSetText(w, caption); }
|
||||
if (text) { wgtSetText(w, text); }
|
||||
|
||||
// Set size hints for the layout engine
|
||||
if (ctrl->height > 0) {
|
||||
w->minH = wgtPixels(ctrl->height);
|
||||
w->prefH = wgtPixels(ctrl->height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnDefaultEvent
|
||||
// ============================================================
|
||||
|
|
@ -155,8 +254,6 @@ void dsgnInit(DsgnStateT *ds, AppContextT *ctx) {
|
|||
ds->activeTool = TOOL_POINTER;
|
||||
ds->mode = DSGN_IDLE;
|
||||
ds->activeHandle = HANDLE_NONE;
|
||||
ds->showGrid = true;
|
||||
ds->snapToGrid = true;
|
||||
ds->ctx = ctx;
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +288,6 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
const char *end = source + sourceLen;
|
||||
|
||||
while (pos < end) {
|
||||
// Extract one line
|
||||
const char *lineStart = pos;
|
||||
|
||||
while (pos < end && *pos != '\n' && *pos != '\r') {
|
||||
|
|
@ -212,7 +308,6 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
memcpy(line, lineStart, lineLen);
|
||||
line[lineLen] = '\0';
|
||||
|
||||
// Trim leading whitespace
|
||||
char *trimmed = line;
|
||||
|
||||
while (*trimmed == ' ' || *trimmed == '\t') {
|
||||
|
|
@ -223,7 +318,6 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// VERSION line -- skip
|
||||
if (strncasecmp(trimmed, "VERSION ", 8) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -271,7 +365,6 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// End
|
||||
if (strcasecmp(trimmed, "End") == 0) {
|
||||
if (curCtrl) {
|
||||
curCtrl = NULL;
|
||||
|
|
@ -286,7 +379,6 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
char *eq = strchr(trimmed, '=');
|
||||
|
||||
if (eq && inForm) {
|
||||
// Extract key
|
||||
char key[DSGN_MAX_NAME];
|
||||
char *kend = eq - 1;
|
||||
|
||||
|
|
@ -299,7 +391,6 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
memcpy(key, trimmed, klen);
|
||||
key[klen] = '\0';
|
||||
|
||||
// Extract value (skip leading whitespace and quotes)
|
||||
char *vstart = eq + 1;
|
||||
|
||||
while (*vstart == ' ' || *vstart == '\t') { vstart++; }
|
||||
|
|
@ -318,13 +409,11 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
val[vi++] = *vstart++;
|
||||
}
|
||||
|
||||
// Trim trailing whitespace
|
||||
while (vi > 0 && (val[vi - 1] == ' ' || val[vi - 1] == '\t')) { vi--; }
|
||||
}
|
||||
|
||||
val[vi] = '\0';
|
||||
|
||||
// Assign to form or control
|
||||
if (curCtrl) {
|
||||
if (strcasecmp(key, "Left") == 0) { curCtrl->left = atoi(val); }
|
||||
else if (strcasecmp(key, "Top") == 0) { curCtrl->top = atoi(val); }
|
||||
|
|
@ -340,27 +429,6 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
}
|
||||
}
|
||||
|
||||
// Auto-layout controls that have no position (all at 0,0)
|
||||
int32_t ctrlCount = (int32_t)arrlen(form->controls);
|
||||
bool allAtOrigin = true;
|
||||
|
||||
for (int32_t i = 0; i < ctrlCount; i++) {
|
||||
if (form->controls[i].left != 0 || form->controls[i].top != 0) {
|
||||
allAtOrigin = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allAtOrigin && ctrlCount > 0) {
|
||||
int32_t y = DSGN_GRID_SIZE * 2;
|
||||
|
||||
for (int32_t i = 0; i < ctrlCount; i++) {
|
||||
form->controls[i].left = DSGN_GRID_SIZE * 2;
|
||||
form->controls[i].top = y;
|
||||
y += form->controls[i].height + DSGN_GRID_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
ds->form = form;
|
||||
ds->selectedIdx = -1;
|
||||
return true;
|
||||
|
|
@ -395,12 +463,11 @@ void dsgnOnKey(DsgnStateT *ds, int32_t key) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Delete key -- remove selected control
|
||||
// Delete key
|
||||
if (key == 0x153 && ds->selectedIdx >= 0 && ds->selectedIdx < (int32_t)arrlen(ds->form->controls)) {
|
||||
arrdel(ds->form->controls, ds->selectedIdx);
|
||||
ds->selectedIdx = -1;
|
||||
ds->form->dirty = true;
|
||||
dsgnPaint(ds);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -410,69 +477,29 @@ void dsgnOnKey(DsgnStateT *ds, int32_t key) {
|
|||
// ============================================================
|
||||
|
||||
void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag) {
|
||||
if (!ds->form || !ds->canvas) {
|
||||
if (!ds->form) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t ctrlCount = (int32_t)arrlen(ds->form->controls);
|
||||
|
||||
if (drag) {
|
||||
// Continue ongoing drag operation
|
||||
if (ds->mode == DSGN_MOVING && ds->selectedIdx >= 0 && ds->selectedIdx < ctrlCount) {
|
||||
DsgnControlT *ctrl = &ds->form->controls[ds->selectedIdx];
|
||||
int32_t dx = x - ds->dragStartX;
|
||||
int32_t dy = y - ds->dragStartY;
|
||||
ctrl->left = ds->dragOrigLeft + dx;
|
||||
ctrl->top = ds->dragOrigTop + dy;
|
||||
|
||||
if (ds->snapToGrid) {
|
||||
ctrl->left = snapToGrid(ctrl->left, DSGN_GRID_SIZE);
|
||||
ctrl->top = snapToGrid(ctrl->top, DSGN_GRID_SIZE);
|
||||
}
|
||||
|
||||
ds->form->dirty = true;
|
||||
dsgnPaint(ds);
|
||||
} else if (ds->mode == DSGN_RESIZING && ds->selectedIdx >= 0 && ds->selectedIdx < ctrlCount) {
|
||||
if (ds->mode == DSGN_RESIZING && ds->selectedIdx >= 0 && ds->selectedIdx < ctrlCount) {
|
||||
DsgnControlT *ctrl = &ds->form->controls[ds->selectedIdx];
|
||||
int32_t dx = x - ds->dragStartX;
|
||||
int32_t dy = y - ds->dragStartY;
|
||||
|
||||
// Apply delta based on which handle
|
||||
switch (ds->activeHandle) {
|
||||
case HANDLE_NW:
|
||||
ctrl->left = ds->dragOrigLeft + dx;
|
||||
ctrl->top = ds->dragOrigTop + dy;
|
||||
ctrl->width = ds->dragOrigWidth - dx;
|
||||
ctrl->height = ds->dragOrigHeight - dy;
|
||||
break;
|
||||
case HANDLE_N:
|
||||
ctrl->top = ds->dragOrigTop + dy;
|
||||
ctrl->height = ds->dragOrigHeight - dy;
|
||||
break;
|
||||
case HANDLE_NE:
|
||||
ctrl->top = ds->dragOrigTop + dy;
|
||||
ctrl->width = ds->dragOrigWidth + dx;
|
||||
ctrl->height = ds->dragOrigHeight - dy;
|
||||
break;
|
||||
case HANDLE_E:
|
||||
ctrl->width = ds->dragOrigWidth + dx;
|
||||
break;
|
||||
case HANDLE_SE:
|
||||
ctrl->width = ds->dragOrigWidth + dx;
|
||||
ctrl->height = ds->dragOrigHeight + dy;
|
||||
break;
|
||||
case HANDLE_S:
|
||||
ctrl->height = ds->dragOrigHeight + dy;
|
||||
break;
|
||||
case HANDLE_SW:
|
||||
ctrl->left = ds->dragOrigLeft + dx;
|
||||
ctrl->width = ds->dragOrigWidth - dx;
|
||||
case HANDLE_SE:
|
||||
ctrl->width = ds->dragOrigWidth + dx;
|
||||
ctrl->height = ds->dragOrigHeight + dy;
|
||||
break;
|
||||
case HANDLE_W:
|
||||
ctrl->left = ds->dragOrigLeft + dx;
|
||||
ctrl->width = ds->dragOrigWidth - dx;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -480,100 +507,57 @@ void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag) {
|
|||
if (ctrl->width < MIN_CTRL_SIZE) { ctrl->width = MIN_CTRL_SIZE; }
|
||||
if (ctrl->height < MIN_CTRL_SIZE) { ctrl->height = MIN_CTRL_SIZE; }
|
||||
|
||||
if (ds->snapToGrid) {
|
||||
ctrl->left = snapToGrid(ctrl->left, DSGN_GRID_SIZE);
|
||||
ctrl->top = snapToGrid(ctrl->top, DSGN_GRID_SIZE);
|
||||
ctrl->width = snapToGrid(ctrl->width, DSGN_GRID_SIZE);
|
||||
ctrl->height = snapToGrid(ctrl->height, DSGN_GRID_SIZE);
|
||||
|
||||
if (ctrl->width < MIN_CTRL_SIZE) { ctrl->width = MIN_CTRL_SIZE; }
|
||||
if (ctrl->height < MIN_CTRL_SIZE) { ctrl->height = MIN_CTRL_SIZE; }
|
||||
}
|
||||
|
||||
syncWidgetGeom(ctrl);
|
||||
ds->form->dirty = true;
|
||||
dsgnPaint(ds);
|
||||
} else if (ds->mode == DSGN_DRAWING) {
|
||||
// Rubber-band for new control -- just repaint with preview
|
||||
dsgnPaint(ds);
|
||||
} else if (ds->mode == DSGN_REORDERING && ds->selectedIdx >= 0 && ds->selectedIdx < ctrlCount) {
|
||||
// Determine if we should swap with a neighbor based on drag direction
|
||||
int32_t dy = y - ds->dragStartY;
|
||||
DsgnControlT *ctrl = &ds->form->controls[ds->selectedIdx];
|
||||
|
||||
// Draw rubber-band rectangle
|
||||
int32_t rx = ds->drawX < x ? ds->drawX : x;
|
||||
int32_t ry = ds->drawY < y ? ds->drawY : y;
|
||||
int32_t rw = abs(x - ds->drawX);
|
||||
int32_t rh = abs(y - ds->drawY);
|
||||
if (dy > 0 && ctrl->widget) {
|
||||
// Dragging down -- swap with next control if past its midpoint
|
||||
if (ds->selectedIdx < ctrlCount - 1) {
|
||||
DsgnControlT *next = &ds->form->controls[ds->selectedIdx + 1];
|
||||
|
||||
if (rw > 0 && rh > 0) {
|
||||
uint32_t black = packColor(&ds->ctx->display, 0, 0, 0);
|
||||
wgtCanvasSetPenColor(ds->canvas, black);
|
||||
wgtCanvasDrawRect(ds->canvas, rx, ry, rw, rh);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Mouse click (not drag)
|
||||
|
||||
// If drawing a new control, finalize it
|
||||
if (ds->mode == DSGN_DRAWING) {
|
||||
int32_t rx = ds->drawX < x ? ds->drawX : x;
|
||||
int32_t ry = ds->drawY < y ? ds->drawY : y;
|
||||
int32_t rw = abs(x - ds->drawX);
|
||||
int32_t rh = abs(y - ds->drawY);
|
||||
|
||||
if (rw < MIN_CTRL_SIZE) { rw = DEFAULT_CTRL_W; }
|
||||
if (rh < MIN_CTRL_SIZE) { rh = DEFAULT_CTRL_H; }
|
||||
|
||||
if (ds->snapToGrid) {
|
||||
rx = snapToGrid(rx, DSGN_GRID_SIZE);
|
||||
ry = snapToGrid(ry, DSGN_GRID_SIZE);
|
||||
rw = snapToGrid(rw, DSGN_GRID_SIZE);
|
||||
rh = snapToGrid(rh, DSGN_GRID_SIZE);
|
||||
|
||||
if (rw < MIN_CTRL_SIZE) { rw = MIN_CTRL_SIZE; }
|
||||
if (rh < MIN_CTRL_SIZE) { rh = MIN_CTRL_SIZE; }
|
||||
}
|
||||
|
||||
const char *typeName = dsgnToolTypeName(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.left = rx;
|
||||
ctrl.top = ry;
|
||||
ctrl.width = rw;
|
||||
ctrl.height = rh;
|
||||
ctrl.tabIndex = ctrlCount;
|
||||
|
||||
// Set default Caption/Text
|
||||
setPropValue(&ctrl, "Caption", ctrl.name);
|
||||
|
||||
arrput(ds->form->controls, ctrl);
|
||||
ds->selectedIdx = (int32_t)arrlen(ds->form->controls) - 1;
|
||||
ds->activeTool = TOOL_POINTER;
|
||||
ds->mode = DSGN_IDLE;
|
||||
if (next->widget && y > next->widget->y + next->widget->h / 2) {
|
||||
DsgnControlT tmp = ds->form->controls[ds->selectedIdx];
|
||||
ds->form->controls[ds->selectedIdx] = ds->form->controls[ds->selectedIdx + 1];
|
||||
ds->form->controls[ds->selectedIdx + 1] = tmp;
|
||||
rebuildWidgets(ds);
|
||||
ds->selectedIdx++;
|
||||
ds->dragStartY = y;
|
||||
ds->form->dirty = true;
|
||||
dsgnPaint(ds);
|
||||
}
|
||||
}
|
||||
} else if (dy < 0 && ctrl->widget) {
|
||||
// Dragging up -- swap with previous control if past its midpoint
|
||||
if (ds->selectedIdx > 0) {
|
||||
DsgnControlT *prev = &ds->form->controls[ds->selectedIdx - 1];
|
||||
|
||||
if (prev->widget && y < prev->widget->y + prev->widget->h / 2) {
|
||||
DsgnControlT tmp = ds->form->controls[ds->selectedIdx];
|
||||
ds->form->controls[ds->selectedIdx] = ds->form->controls[ds->selectedIdx - 1];
|
||||
ds->form->controls[ds->selectedIdx - 1] = tmp;
|
||||
rebuildWidgets(ds);
|
||||
ds->selectedIdx--;
|
||||
ds->dragStartY = y;
|
||||
ds->form->dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Release from moving/resizing
|
||||
if (ds->mode == DSGN_MOVING || ds->mode == DSGN_RESIZING) {
|
||||
// Mouse click (not drag) -- end any ongoing operation
|
||||
if (ds->mode == DSGN_REORDERING || ds->mode == DSGN_RESIZING) {
|
||||
ds->mode = DSGN_IDLE;
|
||||
dsgnPaint(ds);
|
||||
return;
|
||||
}
|
||||
|
||||
// Placing mode: start drawing a new control
|
||||
if (ds->activeTool != TOOL_POINTER) {
|
||||
ds->mode = DSGN_DRAWING;
|
||||
ds->drawX = x;
|
||||
ds->drawY = y;
|
||||
return;
|
||||
}
|
||||
|
||||
// Pointer tool: select and start drag
|
||||
|
||||
// Pointer tool: select, start resize or reorder
|
||||
if (ds->activeTool == TOOL_POINTER) {
|
||||
// Check grab handles of selected control first
|
||||
if (ds->selectedIdx >= 0 && ds->selectedIdx < ctrlCount) {
|
||||
DsgnHandleE handle = hitTestHandles(&ds->form->controls[ds->selectedIdx], x, y);
|
||||
|
|
@ -584,59 +568,132 @@ void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag) {
|
|||
ds->activeHandle = handle;
|
||||
ds->dragStartX = x;
|
||||
ds->dragStartY = y;
|
||||
ds->dragOrigLeft = ctrl->left;
|
||||
ds->dragOrigTop = ctrl->top;
|
||||
ds->dragOrigWidth = ctrl->width;
|
||||
ds->dragOrigHeight = ctrl->height;
|
||||
ds->dragOrigWidth = ctrl->widget ? ctrl->widget->w : ctrl->width;
|
||||
ds->dragOrigHeight = ctrl->widget ? ctrl->widget->h : ctrl->height;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Hit test controls
|
||||
// Hit test controls -- click to select, drag to reorder
|
||||
int32_t hit = hitTestControl(ds, x, y);
|
||||
|
||||
if (hit >= 0) {
|
||||
ds->selectedIdx = hit;
|
||||
DsgnControlT *ctrl = &ds->form->controls[hit];
|
||||
ds->mode = DSGN_MOVING;
|
||||
ds->dragStartX = x;
|
||||
ds->mode = DSGN_REORDERING;
|
||||
ds->dragStartY = y;
|
||||
ds->dragOrigLeft = ctrl->left;
|
||||
ds->dragOrigTop = ctrl->top;
|
||||
} else {
|
||||
ds->selectedIdx = -1;
|
||||
}
|
||||
|
||||
dsgnPaint(ds);
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-pointer tool: place a new control (appended to VBox)
|
||||
const char *typeName = dsgnToolTypeName(ds->activeTool);
|
||||
DsgnControlT ctrl;
|
||||
memset(&ctrl, 0, sizeof(ctrl));
|
||||
dsgnAutoName(ds, typeName, ctrl.name, DSGN_MAX_NAME);
|
||||
snprintf(ctrl.typeName, DSGN_MAX_NAME, "%s", typeName);
|
||||
ctrl.width = DEFAULT_CTRL_W;
|
||||
ctrl.height = DEFAULT_CTRL_H;
|
||||
ctrl.tabIndex = ctrlCount;
|
||||
setPropValue(&ctrl, "Caption", ctrl.name);
|
||||
|
||||
// Create the live widget
|
||||
if (ds->form->contentBox) {
|
||||
ctrl.widget = createDesignWidget(typeName, ds->form->contentBox);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
arrput(ds->form->controls, ctrl);
|
||||
ds->selectedIdx = (int32_t)arrlen(ds->form->controls) - 1;
|
||||
ds->activeTool = TOOL_POINTER;
|
||||
ds->mode = DSGN_IDLE;
|
||||
ds->form->dirty = true;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnPaint
|
||||
// ============================================================
|
||||
//
|
||||
// Draw grid dots and selection handles over the live widgets.
|
||||
// Called from the form window's onPaint callback.
|
||||
|
||||
void dsgnPaint(DsgnStateT *ds) {
|
||||
if (!ds->canvas || !ds->form || !ds->ctx) {
|
||||
if (!ds->form || !ds->ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Form background
|
||||
uint32_t formBg = packColor(&ds->ctx->display, 192, 192, 192);
|
||||
wgtCanvasClear(ds->canvas, formBg);
|
||||
// Grid dots and selection handles are drawn directly onto
|
||||
// the window via the display backbuffer. The live widgets
|
||||
// handle their own rendering.
|
||||
|
||||
// Grid dots
|
||||
if (ds->showGrid) {
|
||||
drawGrid(ds);
|
||||
// Nothing to do here for now -- the onPaint hook in ideMain
|
||||
// calls dsgnPaintOverlay with the display pointer.
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnPaintOverlay
|
||||
// ============================================================
|
||||
//
|
||||
// Draw selection handles on the window's painted surface.
|
||||
// Called after widgets have painted, using direct display drawing.
|
||||
|
||||
void dsgnPaintOverlay(DsgnStateT *ds, int32_t winX, int32_t winY) {
|
||||
(void)winX;
|
||||
(void)winY;
|
||||
|
||||
if (!ds->form || !ds->form->controls || !ds->ctx || !ds->formWin || !ds->formWin->contentBuf) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw controls
|
||||
int32_t count = (int32_t)arrlen(ds->form->controls);
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
drawControl(ds, &ds->form->controls[i], i == ds->selectedIdx);
|
||||
if (ds->selectedIdx < 0 || ds->selectedIdx >= count) {
|
||||
return;
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(ds->canvas);
|
||||
DsgnControlT *ctrl = &ds->form->controls[ds->selectedIdx];
|
||||
|
||||
if (!ctrl->widget || ctrl->widget->w <= 0 || ctrl->widget->h <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw into the window's content buffer (same coordinate space as widgets)
|
||||
DisplayT cd = ds->ctx->display;
|
||||
cd.backBuf = ds->formWin->contentBuf;
|
||||
cd.width = ds->formWin->contentW;
|
||||
cd.height = ds->formWin->contentH;
|
||||
cd.pitch = ds->formWin->contentPitch;
|
||||
cd.clipX = 0;
|
||||
cd.clipY = 0;
|
||||
cd.clipW = ds->formWin->contentW;
|
||||
cd.clipH = ds->formWin->contentH;
|
||||
|
||||
const BlitOpsT *ops = &ds->ctx->blitOps;
|
||||
uint32_t black = packColor(&cd, 0, 0, 0);
|
||||
|
||||
int32_t cx = ctrl->widget->x;
|
||||
int32_t cy = ctrl->widget->y;
|
||||
int32_t cw = ctrl->widget->w;
|
||||
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 };
|
||||
|
||||
for (int32_t i = 0; i < HANDLE_COUNT; i++) {
|
||||
rectFill(&cd, ops, hx[i], hy[i], hs, hs, black);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -663,20 +720,17 @@ int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize) {
|
|||
const DsgnControlT *ctrl = &ds->form->controls[i];
|
||||
pos += snprintf(buf + pos, bufSize - pos, " Begin %s %s\n", ctrl->typeName, ctrl->name);
|
||||
|
||||
// Write Caption/Text first if present
|
||||
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); }
|
||||
|
||||
// Geometry
|
||||
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);
|
||||
|
||||
// Other properties (skip Caption, Text, geometry -- already written)
|
||||
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; }
|
||||
|
|
@ -709,13 +763,6 @@ const char *dsgnSelectedName(const DsgnStateT *ds) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnSetCanvas
|
||||
// ============================================================
|
||||
|
||||
void dsgnSetCanvas(DsgnStateT *ds, WidgetT *canvas) {
|
||||
ds->canvas = canvas;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -731,152 +778,6 @@ const char *dsgnToolTypeName(DsgnToolE tool) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawControl
|
||||
// ============================================================
|
||||
|
||||
static void drawControl(DsgnStateT *ds, const DsgnControlT *ctrl, bool selected) {
|
||||
WidgetT *cv = ds->canvas;
|
||||
int32_t x = ctrl->left;
|
||||
int32_t y = ctrl->top;
|
||||
int32_t w = ctrl->width;
|
||||
int32_t h = ctrl->height;
|
||||
|
||||
uint32_t black = packColor(&ds->ctx->display, 0, 0, 0);
|
||||
uint32_t face = packColor(&ds->ctx->display, 192, 192, 192);
|
||||
uint32_t hilight = packColor(&ds->ctx->display, 255, 255, 255);
|
||||
uint32_t shadow = packColor(&ds->ctx->display, 128, 128, 128);
|
||||
uint32_t dkShadow = packColor(&ds->ctx->display, 64, 64, 64);
|
||||
uint32_t contentBg = packColor(&ds->ctx->display, 255, 255, 255);
|
||||
|
||||
// Get display text
|
||||
const char *caption = getPropValue(ctrl, "Caption");
|
||||
const char *text = getPropValue(ctrl, "Text");
|
||||
const char *label = caption ? caption : (text ? text : ctrl->name);
|
||||
|
||||
if (strcasecmp(ctrl->typeName, "CommandButton") == 0) {
|
||||
// Raised button
|
||||
wgtCanvasSetPenColor(cv, face);
|
||||
wgtCanvasFillRect(cv, x, y, w, h);
|
||||
wgtCanvasSetPenColor(cv, hilight);
|
||||
wgtCanvasDrawLine(cv, x, y, x + w - 1, y);
|
||||
wgtCanvasDrawLine(cv, x, y, x, y + h - 1);
|
||||
wgtCanvasSetPenColor(cv, dkShadow);
|
||||
wgtCanvasDrawLine(cv, x + w - 1, y, x + w - 1, y + h - 1);
|
||||
wgtCanvasDrawLine(cv, x, y + h - 1, x + w - 1, y + h - 1);
|
||||
wgtCanvasSetPenColor(cv, shadow);
|
||||
wgtCanvasDrawLine(cv, x + w - 2, y + 1, x + w - 2, y + h - 2);
|
||||
wgtCanvasDrawLine(cv, x + 1, y + h - 2, x + w - 2, y + h - 2);
|
||||
// Center text
|
||||
int32_t tw = (int32_t)strlen(label) * 8;
|
||||
wgtCanvasSetPenColor(cv, black);
|
||||
wgtCanvasDrawText(cv, x + (w - tw) / 2, y + (h - 14) / 2, label);
|
||||
} else if (strcasecmp(ctrl->typeName, "Label") == 0) {
|
||||
// Just text, no border
|
||||
wgtCanvasSetPenColor(cv, black);
|
||||
wgtCanvasDrawText(cv, x, y + (h - 14) / 2, label);
|
||||
} else if (strcasecmp(ctrl->typeName, "TextBox") == 0) {
|
||||
// Sunken text field
|
||||
wgtCanvasSetPenColor(cv, contentBg);
|
||||
wgtCanvasFillRect(cv, x, y, w, h);
|
||||
wgtCanvasSetPenColor(cv, shadow);
|
||||
wgtCanvasDrawLine(cv, x, y, x + w - 1, y);
|
||||
wgtCanvasDrawLine(cv, x, y, x, y + h - 1);
|
||||
wgtCanvasSetPenColor(cv, hilight);
|
||||
wgtCanvasDrawLine(cv, x + w - 1, y, x + w - 1, y + h - 1);
|
||||
wgtCanvasDrawLine(cv, x, y + h - 1, x + w - 1, y + h - 1);
|
||||
wgtCanvasSetPenColor(cv, black);
|
||||
wgtCanvasDrawText(cv, x + 2, y + (h - 14) / 2, text ? text : "");
|
||||
} else if (strcasecmp(ctrl->typeName, "CheckBox") == 0) {
|
||||
// Check box + text
|
||||
wgtCanvasSetPenColor(cv, contentBg);
|
||||
wgtCanvasFillRect(cv, x, y + (h - 12) / 2, 12, 12);
|
||||
wgtCanvasSetPenColor(cv, shadow);
|
||||
wgtCanvasDrawRect(cv, x, y + (h - 12) / 2, 12, 12);
|
||||
wgtCanvasSetPenColor(cv, black);
|
||||
wgtCanvasDrawText(cv, x + 16, y + (h - 14) / 2, label);
|
||||
} else if (strcasecmp(ctrl->typeName, "OptionButton") == 0) {
|
||||
// Radio circle + text
|
||||
wgtCanvasSetPenColor(cv, shadow);
|
||||
wgtCanvasFillCircle(cv, x + 6, y + h / 2, 6);
|
||||
wgtCanvasSetPenColor(cv, contentBg);
|
||||
wgtCanvasFillCircle(cv, x + 6, y + h / 2, 5);
|
||||
wgtCanvasSetPenColor(cv, black);
|
||||
wgtCanvasDrawText(cv, x + 16, y + (h - 14) / 2, label);
|
||||
} else if (strcasecmp(ctrl->typeName, "Frame") == 0) {
|
||||
// Titled border
|
||||
wgtCanvasSetPenColor(cv, shadow);
|
||||
wgtCanvasDrawRect(cv, x, y + 6, w, h - 6);
|
||||
wgtCanvasSetPenColor(cv, hilight);
|
||||
wgtCanvasDrawRect(cv, x + 1, y + 7, w - 2, h - 8);
|
||||
wgtCanvasSetPenColor(cv, face);
|
||||
wgtCanvasFillRect(cv, x + 8, y, (int32_t)strlen(label) * 8 + 4, 14);
|
||||
wgtCanvasSetPenColor(cv, black);
|
||||
wgtCanvasDrawText(cv, x + 10, y, label);
|
||||
} else if (strcasecmp(ctrl->typeName, "Timer") == 0) {
|
||||
// Design-time icon (invisible at runtime)
|
||||
wgtCanvasSetPenColor(cv, shadow);
|
||||
wgtCanvasDrawRect(cv, x, y, w, h);
|
||||
wgtCanvasSetPenColor(cv, black);
|
||||
wgtCanvasDrawText(cv, x + 2, y + 2, "TMR");
|
||||
} else {
|
||||
// Generic: sunken rect with type name
|
||||
wgtCanvasSetPenColor(cv, contentBg);
|
||||
wgtCanvasFillRect(cv, x, y, w, h);
|
||||
wgtCanvasSetPenColor(cv, shadow);
|
||||
wgtCanvasDrawRect(cv, x, y, w, h);
|
||||
wgtCanvasSetPenColor(cv, black);
|
||||
wgtCanvasDrawText(cv, x + 2, y + (h - 14) / 2, label);
|
||||
}
|
||||
|
||||
// Draw grab handles if selected
|
||||
if (selected) {
|
||||
drawHandles(ds, ctrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawGrid
|
||||
// ============================================================
|
||||
|
||||
static void drawGrid(DsgnStateT *ds) {
|
||||
uint32_t dotColor = packColor(&ds->ctx->display, 0, 0, 0);
|
||||
int32_t fw = ds->form->width;
|
||||
int32_t fh = ds->form->height;
|
||||
|
||||
for (int32_t y = 0; y < fh; y += DSGN_GRID_SIZE) {
|
||||
for (int32_t x = 0; x < fw; x += DSGN_GRID_SIZE) {
|
||||
wgtCanvasSetPixel(ds->canvas, x, y, dotColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawHandles
|
||||
// ============================================================
|
||||
|
||||
static void drawHandles(DsgnStateT *ds, const DsgnControlT *ctrl) {
|
||||
int32_t x = ctrl->left;
|
||||
int32_t y = ctrl->top;
|
||||
int32_t w = ctrl->width;
|
||||
int32_t h = ctrl->height;
|
||||
int32_t hs = DSGN_HANDLE_SIZE;
|
||||
|
||||
uint32_t black = packColor(&ds->ctx->display, 0, 0, 0);
|
||||
wgtCanvasSetPenColor(ds->canvas, black);
|
||||
|
||||
// 8 handles: NW, N, NE, E, SE, S, SW, W
|
||||
int32_t hx[HANDLE_COUNT] = { x - hs/2, x + w/2 - hs/2, x + w - hs/2, x + w - hs/2, x + w - hs/2, x + w/2 - hs/2, x - hs/2, x - hs/2 };
|
||||
int32_t hy[HANDLE_COUNT] = { y - hs/2, y - hs/2, y - hs/2, y + h/2 - hs/2, y + h - hs/2, y + h - hs/2, y + h - hs/2, y + h/2 - hs/2 };
|
||||
|
||||
for (int32_t i = 0; i < HANDLE_COUNT; i++) {
|
||||
wgtCanvasFillRect(ds->canvas, hx[i], hy[i], hs, hs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// getPropValue
|
||||
// ============================================================
|
||||
|
|
@ -897,13 +798,22 @@ static const char *getPropValue(const DsgnControlT *ctrl, const char *name) {
|
|||
// ============================================================
|
||||
|
||||
static int32_t hitTestControl(const DsgnStateT *ds, int32_t x, int32_t y) {
|
||||
// Iterate in reverse order (last = topmost)
|
||||
int32_t count = (int32_t)arrlen(ds->form->controls);
|
||||
|
||||
for (int32_t i = count - 1; i >= 0; i--) {
|
||||
const DsgnControlT *ctrl = &ds->form->controls[i];
|
||||
|
||||
if (x >= ctrl->left && x < ctrl->left + ctrl->width && y >= ctrl->top && y < ctrl->top + ctrl->height) {
|
||||
if (!ctrl->widget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use the widget's actual laid-out position
|
||||
int32_t wx = ctrl->widget->x;
|
||||
int32_t wy = ctrl->widget->y;
|
||||
int32_t ww = ctrl->widget->w;
|
||||
int32_t wh = ctrl->widget->h;
|
||||
|
||||
if (x >= wx && x < wx + ww && y >= wy && y < wy + wh) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
|
@ -917,14 +827,19 @@ static int32_t hitTestControl(const DsgnStateT *ds, int32_t x, int32_t y) {
|
|||
// ============================================================
|
||||
|
||||
static DsgnHandleE hitTestHandles(const DsgnControlT *ctrl, int32_t x, int32_t y) {
|
||||
int32_t cx = ctrl->left;
|
||||
int32_t cy = ctrl->top;
|
||||
int32_t cw = ctrl->width;
|
||||
int32_t ch = ctrl->height;
|
||||
if (!ctrl->widget) {
|
||||
return HANDLE_NONE;
|
||||
}
|
||||
|
||||
int32_t cx = ctrl->widget->x;
|
||||
int32_t cy = ctrl->widget->y;
|
||||
int32_t cw = ctrl->widget->w;
|
||||
int32_t ch = ctrl->widget->h;
|
||||
int32_t hs = DSGN_HANDLE_SIZE;
|
||||
|
||||
int32_t hx[HANDLE_COUNT] = { 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[HANDLE_COUNT] = { 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 };
|
||||
// 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 };
|
||||
|
||||
for (int32_t i = 0; i < HANDLE_COUNT; i++) {
|
||||
if (x >= hx[i] && x < hx[i] + hs && y >= hy[i] && y < hy[i] + hs) {
|
||||
|
|
@ -936,12 +851,30 @@ static DsgnHandleE hitTestHandles(const DsgnControlT *ctrl, int32_t x, int32_t y
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// resolveTypeName
|
||||
// ============================================================
|
||||
|
||||
static const char *resolveTypeName(const char *typeName) {
|
||||
const char *wgtName = wgtFindByBasName(typeName);
|
||||
|
||||
if (wgtName) {
|
||||
return wgtName;
|
||||
}
|
||||
|
||||
if (wgtGetApi(typeName)) {
|
||||
return typeName;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// setPropValue
|
||||
// ============================================================
|
||||
|
||||
static void setPropValue(DsgnControlT *ctrl, const char *name, const char *value) {
|
||||
// Update existing
|
||||
for (int32_t i = 0; i < ctrl->propCount; i++) {
|
||||
if (strcasecmp(ctrl->props[i].name, name) == 0) {
|
||||
snprintf(ctrl->props[i].value, DSGN_MAX_TEXT, "%s", value);
|
||||
|
|
@ -949,7 +882,6 @@ static void setPropValue(DsgnControlT *ctrl, const char *name, const char *value
|
|||
}
|
||||
}
|
||||
|
||||
// Add new
|
||||
if (ctrl->propCount < DSGN_MAX_PROPS) {
|
||||
snprintf(ctrl->props[ctrl->propCount].name, DSGN_MAX_NAME, "%s", name);
|
||||
snprintf(ctrl->props[ctrl->propCount].value, DSGN_MAX_TEXT, "%s", value);
|
||||
|
|
@ -958,10 +890,54 @@ static void setPropValue(DsgnControlT *ctrl, const char *name, const char *value
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// snapToGrid
|
||||
// ============================================================
|
||||
|
||||
static int32_t snapToGrid(int32_t val, int32_t gridSize) {
|
||||
return ((val + gridSize / 2) / gridSize) * gridSize;
|
||||
|
||||
// ============================================================
|
||||
// rebuildWidgets
|
||||
// ============================================================
|
||||
//
|
||||
// Destroy all live widgets and recreate them in the current
|
||||
// array order. This is the safest way to reorder since the
|
||||
// widget tree child list matches the creation order.
|
||||
|
||||
static void rebuildWidgets(DsgnStateT *ds) {
|
||||
WidgetT *parent = ds->form ? ds->form->contentBox : NULL;
|
||||
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy all existing widget children
|
||||
// (wgtDestroy not available, so just unlink and let the window own them)
|
||||
parent->firstChild = NULL;
|
||||
parent->lastChild = NULL;
|
||||
|
||||
// Clear widget pointers
|
||||
int32_t count = (int32_t)arrlen(ds->form->controls);
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
ds->form->controls[i].widget = NULL;
|
||||
}
|
||||
|
||||
// Recreate all widgets in current array order
|
||||
dsgnCreateWidgets(ds, parent);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// syncWidgetGeom
|
||||
// ============================================================
|
||||
//
|
||||
// Update the live widget's position and size from the design data.
|
||||
|
||||
static void syncWidgetGeom(DsgnControlT *ctrl) {
|
||||
if (!ctrl->widget) {
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ typedef struct {
|
|||
int32_t tabIndex;
|
||||
DsgnPropT props[DSGN_MAX_PROPS];
|
||||
int32_t propCount;
|
||||
WidgetT *widget; // live widget (created at design time for WYSIWYG)
|
||||
} DsgnControlT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -62,6 +63,7 @@ typedef struct {
|
|||
int32_t height;
|
||||
DsgnControlT *controls; // stb_ds dynamic array
|
||||
bool dirty;
|
||||
WidgetT *contentBox; // VBox parent for live widgets
|
||||
} DsgnFormT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -92,15 +94,10 @@ typedef enum {
|
|||
|
||||
typedef enum {
|
||||
HANDLE_NONE = -1,
|
||||
HANDLE_NW = 0,
|
||||
HANDLE_N,
|
||||
HANDLE_NE,
|
||||
HANDLE_E,
|
||||
HANDLE_SE,
|
||||
HANDLE_S,
|
||||
HANDLE_SW,
|
||||
HANDLE_W,
|
||||
HANDLE_COUNT = 8
|
||||
HANDLE_E = 0, // resize width
|
||||
HANDLE_S, // resize height
|
||||
HANDLE_SE, // resize both
|
||||
HANDLE_COUNT = 3
|
||||
} DsgnHandleE;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -109,9 +106,7 @@ typedef enum {
|
|||
|
||||
typedef enum {
|
||||
DSGN_IDLE,
|
||||
DSGN_PLACING,
|
||||
DSGN_DRAWING,
|
||||
DSGN_MOVING,
|
||||
DSGN_REORDERING, // dragging control up/down in the VBox
|
||||
DSGN_RESIZING
|
||||
} DsgnModeE;
|
||||
|
||||
|
|
@ -125,17 +120,11 @@ typedef struct {
|
|||
DsgnToolE activeTool;
|
||||
DsgnModeE mode;
|
||||
DsgnHandleE activeHandle;
|
||||
int32_t dragStartX;
|
||||
int32_t dragStartY;
|
||||
int32_t dragOrigLeft;
|
||||
int32_t dragOrigTop;
|
||||
int32_t dragOrigWidth;
|
||||
int32_t dragStartY; // mouse Y at drag start (for reorder)
|
||||
int32_t dragOrigWidth; // widget size at resize start
|
||||
int32_t dragOrigHeight;
|
||||
int32_t drawX; // rubber-band start for new control
|
||||
int32_t drawY;
|
||||
bool showGrid;
|
||||
bool snapToGrid;
|
||||
WidgetT *canvas;
|
||||
int32_t dragStartX; // mouse X at resize start
|
||||
WindowT *formWin;
|
||||
AppContextT *ctx;
|
||||
} DsgnStateT;
|
||||
|
||||
|
|
@ -146,9 +135,13 @@ typedef struct {
|
|||
// Initialize designer state.
|
||||
void dsgnInit(DsgnStateT *ds, AppContextT *ctx);
|
||||
|
||||
// Set the canvas widget for rendering.
|
||||
// Set the canvas overlay widget (for grab handles).
|
||||
void dsgnSetCanvas(DsgnStateT *ds, WidgetT *canvas);
|
||||
|
||||
// Create live widgets for all controls in the form.
|
||||
// Call after dsgnLoadFrm or dsgnNewForm, with the form window's contentBox.
|
||||
void dsgnCreateWidgets(DsgnStateT *ds, WidgetT *contentBox);
|
||||
|
||||
// Load a .frm file into the designer.
|
||||
bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen);
|
||||
|
||||
|
|
@ -162,6 +155,9 @@ void dsgnNewForm(DsgnStateT *ds, const char *name);
|
|||
// Repaint the design surface.
|
||||
void dsgnPaint(DsgnStateT *ds);
|
||||
|
||||
// Draw selection handles over the painted window surface.
|
||||
void dsgnPaintOverlay(DsgnStateT *ds, int32_t winX, int32_t winY);
|
||||
|
||||
// Handle mouse click on the design surface.
|
||||
void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,15 +8,16 @@
|
|||
// work on real hardware inside the DVX windowing system.
|
||||
|
||||
#include "dvxApp.h"
|
||||
#include "dvxCursor.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "dvxWm.h"
|
||||
#include "shellApp.h"
|
||||
#include "widgetBox.h"
|
||||
#include "widgetLabel.h"
|
||||
#include "widgetTextInput.h"
|
||||
#include "widgetDropdown.h"
|
||||
#include "widgetCanvas.h"
|
||||
#include "widgetSplitter.h"
|
||||
#include "widgetStatusBar.h"
|
||||
|
||||
|
|
@ -89,7 +90,9 @@ static void onFormWinClose(WindowT *win);
|
|||
static void setStatus(const char *text);
|
||||
static void switchToCode(void);
|
||||
static void switchToDesign(void);
|
||||
static void dsgnMouseCb(WidgetT *w, int32_t cx, int32_t cy, bool drag);
|
||||
static int32_t onFormWinCursorQuery(WindowT *win, int32_t x, int32_t y);
|
||||
static void onFormWinMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons);
|
||||
static void onFormWinPaint(WindowT *win, RectT *dirtyArea);
|
||||
static void updateDropdowns(void);
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -753,6 +756,13 @@ static void loadFile(void) {
|
|||
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.");
|
||||
}
|
||||
|
|
@ -1074,13 +1084,118 @@ static void printCallback(void *ctx, const char *text, bool newline) {
|
|||
}
|
||||
|
||||
// ============================================================
|
||||
// dsgnMouseCb
|
||||
// onFormWinMouse
|
||||
// ============================================================
|
||||
//
|
||||
// Handle mouse events on the form designer window. Coordinates
|
||||
// are relative to the window's client area (content box origin).
|
||||
|
||||
// ============================================================
|
||||
// onFormWinCursorQuery
|
||||
// ============================================================
|
||||
|
||||
static void dsgnMouseCb(WidgetT *w, int32_t cx, int32_t cy, bool drag) {
|
||||
(void)w;
|
||||
dsgnOnMouse(&sDesigner, cx, cy, drag);
|
||||
static int32_t onFormWinCursorQuery(WindowT *win, int32_t x, int32_t y) {
|
||||
(void)win;
|
||||
|
||||
if (!sDesigner.form || !sDesigner.form->controls) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t count = (int32_t)arrlen(sDesigner.form->controls);
|
||||
|
||||
if (sDesigner.selectedIdx < 0 || sDesigner.selectedIdx >= count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
DsgnControlT *ctrl = &sDesigner.form->controls[sDesigner.selectedIdx];
|
||||
|
||||
if (!ctrl->widget || ctrl->widget->w <= 0 || ctrl->widget->h <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t cx = ctrl->widget->x;
|
||||
int32_t cy = ctrl->widget->y;
|
||||
int32_t cw = ctrl->widget->w;
|
||||
int32_t ch = ctrl->widget->h;
|
||||
int32_t hs = DSGN_HANDLE_SIZE;
|
||||
|
||||
// SE handle (check first -- overlaps with E and S)
|
||||
if (x >= cx + cw - hs/2 && x < cx + cw + hs/2 && y >= cy + ch - hs/2 && y < cy + ch + hs/2) {
|
||||
return CURSOR_RESIZE_DIAG_NWSE;
|
||||
}
|
||||
|
||||
// E handle (right edge center)
|
||||
if (x >= cx + cw - hs/2 && x < cx + cw + hs/2 && y >= cy + ch/2 - hs/2 && y < cy + ch/2 + hs/2) {
|
||||
return CURSOR_RESIZE_H;
|
||||
}
|
||||
|
||||
// S handle (bottom center)
|
||||
if (x >= cx + cw/2 - hs/2 && x < cx + cw/2 + hs/2 && y >= cy + ch - hs/2 && y < cy + ch + hs/2) {
|
||||
return CURSOR_RESIZE_V;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void onFormWinMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||
(void)win;
|
||||
static int32_t lastButtons = 0;
|
||||
bool wasDown = (lastButtons & MOUSE_LEFT) != 0;
|
||||
bool isDown = (buttons & MOUSE_LEFT) != 0;
|
||||
|
||||
if (!sDesigner.form || !sFormWin) {
|
||||
lastButtons = buttons;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDown) {
|
||||
bool drag = wasDown;
|
||||
dsgnOnMouse(&sDesigner, x, y, drag);
|
||||
prpRefresh(&sDesigner);
|
||||
|
||||
if (sFormWin) {
|
||||
dvxInvalidateWindow(sAc, sFormWin);
|
||||
}
|
||||
} else if (wasDown) {
|
||||
dsgnOnMouse(&sDesigner, x, y, false);
|
||||
prpRefresh(&sDesigner);
|
||||
|
||||
if (sFormWin) {
|
||||
dvxInvalidateWindow(sAc, sFormWin);
|
||||
}
|
||||
}
|
||||
|
||||
lastButtons = buttons;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onFormWinPaint
|
||||
// ============================================================
|
||||
//
|
||||
// Draw selection handles after widgets have painted.
|
||||
|
||||
static void onFormWinPaint(WindowT *win, RectT *dirtyArea) {
|
||||
if (!win) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Force a full measure + layout + paint cycle.
|
||||
// widgetOnPaint normally skips relayout if root dimensions haven't
|
||||
// changed, but we need it to pick up minH changes from handle drag.
|
||||
if (win->widgetRoot) {
|
||||
widgetCalcMinSizeTree(win->widgetRoot, &sAc->font);
|
||||
win->widgetRoot->w = 0; // force layout pass to re-run
|
||||
}
|
||||
|
||||
widgetOnPaint(win, dirtyArea);
|
||||
|
||||
// Then draw selection handles on top
|
||||
int32_t winX = win->contentX;
|
||||
int32_t winY = win->contentY;
|
||||
|
||||
dsgnPaintOverlay(&sDesigner, winX, winY);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1091,7 +1206,7 @@ static void dsgnMouseCb(WidgetT *w, int32_t cx, int32_t cy, bool drag) {
|
|||
static void onFormWinClose(WindowT *win) {
|
||||
dvxDestroyWindow(sAc, win);
|
||||
sFormWin = NULL;
|
||||
dsgnSetCanvas(&sDesigner, NULL);
|
||||
sDesigner.formWin = NULL;
|
||||
|
||||
if (sToolboxWin) {
|
||||
tbxDestroy(sAc, sToolboxWin);
|
||||
|
|
@ -1168,31 +1283,42 @@ static void switchToDesign(void) {
|
|||
}
|
||||
}
|
||||
|
||||
// Create the form designer window
|
||||
// Create the form designer window (same size as runtime)
|
||||
const char *formName = sDesigner.form ? sDesigner.form->name : "Form1";
|
||||
int32_t formW = sDesigner.form ? sDesigner.form->width : IDE_DESIGN_W;
|
||||
int32_t formH = sDesigner.form ? sDesigner.form->height : IDE_DESIGN_H;
|
||||
|
||||
char title[128];
|
||||
snprintf(title, sizeof(title), "%s [Design]", formName);
|
||||
|
||||
// Position next to the IDE window
|
||||
int32_t winX = IDE_WIN_X;
|
||||
int32_t winY = IDE_WIN_Y;
|
||||
|
||||
sFormWin = dvxCreateWindow(sAc, title, winX, winY, formW + 10, formH + 10, true);
|
||||
sFormWin = dvxCreateWindowCentered(sAc, title, IDE_DESIGN_W, IDE_DESIGN_H, true);
|
||||
|
||||
if (!sFormWin) {
|
||||
return;
|
||||
}
|
||||
|
||||
sFormWin->onClose = onFormWinClose;
|
||||
sDesigner.formWin = sFormWin;
|
||||
|
||||
WidgetT *root = wgtInitWindow(sAc, sFormWin);
|
||||
WidgetT *canvas = wgtCanvas(root, formW, formH);
|
||||
canvas->weight = 100;
|
||||
wgtCanvasSetMouseCallback(canvas, dsgnMouseCb);
|
||||
dsgnSetCanvas(&sDesigner, canvas);
|
||||
WidgetT *contentBox = wgtVBox(root);
|
||||
contentBox->weight = 100;
|
||||
|
||||
// Override paint and mouse AFTER wgtInitWindow (which sets widgetOnPaint)
|
||||
sFormWin->onPaint = onFormWinPaint;
|
||||
sFormWin->onMouse = onFormWinMouse;
|
||||
sFormWin->onCursorQuery = onFormWinCursorQuery;
|
||||
|
||||
// Create live widgets for each control
|
||||
dsgnCreateWidgets(&sDesigner, contentBox);
|
||||
|
||||
// Set form caption as window title
|
||||
if (sDesigner.form && sDesigner.form->caption[0]) {
|
||||
char winTitle[280];
|
||||
snprintf(winTitle, sizeof(winTitle), "%s [Design]", sDesigner.form->caption);
|
||||
dvxSetTitle(sAc, sFormWin, winTitle);
|
||||
}
|
||||
|
||||
// Shrink-wrap the window to its content (matches runtime behavior)
|
||||
dvxFitWindow(sAc, sFormWin);
|
||||
|
||||
// Create toolbox and properties windows
|
||||
if (!sToolboxWin) {
|
||||
|
|
@ -1203,7 +1329,6 @@ static void switchToDesign(void) {
|
|||
sPropsWin = prpCreate(sAc, &sDesigner);
|
||||
}
|
||||
|
||||
dsgnPaint(&sDesigner);
|
||||
setStatus("Design view open.");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
// ideProperties.c -- DVX BASIC form designer properties window
|
||||
//
|
||||
// A floating window with a two-column list showing property names
|
||||
// and values for the currently selected control. Double-clicking
|
||||
// a value opens an input dialog to edit it.
|
||||
// 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 "dvxDialog.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>
|
||||
|
|
@ -21,8 +22,7 @@
|
|||
// ============================================================
|
||||
|
||||
#define PRP_WIN_W 200
|
||||
#define PRP_WIN_H 320
|
||||
#define PRP_MAX_ROWS 64
|
||||
#define PRP_WIN_H 340
|
||||
|
||||
// ============================================================
|
||||
// Module state
|
||||
|
|
@ -30,14 +30,17 @@
|
|||
|
||||
static DsgnStateT *sDs = NULL;
|
||||
static WindowT *sPrpWin = NULL;
|
||||
static WidgetT *sListArea = NULL; // TextArea showing properties as text
|
||||
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
|
||||
|
|
@ -48,6 +51,111 @@ static void onPrpClose(WindowT *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
|
||||
// ============================================================
|
||||
|
|
@ -56,7 +164,6 @@ WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds) {
|
|||
sDs = ds;
|
||||
sPrpCtx = ctx;
|
||||
|
||||
// Position at right edge of screen
|
||||
int32_t winX = ctx->display.width - PRP_WIN_W - 10;
|
||||
WindowT *win = dvxCreateWindow(ctx, "Properties", winX, 30, PRP_WIN_W, PRP_WIN_H, false);
|
||||
|
||||
|
|
@ -69,10 +176,18 @@ WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds) {
|
|||
|
||||
WidgetT *root = wgtInitWindow(ctx, win);
|
||||
|
||||
// Properties displayed as a read-only text area (simple approach)
|
||||
sListArea = wgtTextArea(root, 4096);
|
||||
sListArea->weight = 100;
|
||||
sListArea->readOnly = true;
|
||||
// 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;
|
||||
|
|
@ -89,7 +204,8 @@ void prpDestroy(AppContextT *ctx, WindowT *win) {
|
|||
}
|
||||
|
||||
sPrpWin = NULL;
|
||||
sListArea = NULL;
|
||||
sTree = NULL;
|
||||
sPropsArea = NULL;
|
||||
sDs = NULL;
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +215,38 @@ void prpDestroy(AppContextT *ctx, WindowT *win) {
|
|||
// ============================================================
|
||||
|
||||
void prpRefresh(DsgnStateT *ds) {
|
||||
if (!sListArea || !ds || !ds->form) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -109,11 +256,8 @@ void prpRefresh(DsgnStateT *ds) {
|
|||
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, "%s (%s)\n", ctrl->name, ctrl->typeName);
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "---\n");
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "Name = %s\n", ctrl->name);
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "Left = %d\n", (int)ctrl->left);
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "Top = %d\n", (int)ctrl->top);
|
||||
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);
|
||||
|
|
@ -123,12 +267,10 @@ void prpRefresh(DsgnStateT *ds) {
|
|||
}
|
||||
} else {
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "%s (Form)\n", ds->form->name);
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "---\n");
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "Name = %s\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(sListArea, buf);
|
||||
wgtSetText(sPropsArea, buf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ static void onToolClick(WidgetT *w) {
|
|||
|
||||
if (toolIdx >= 0 && toolIdx < TOOL_COUNT) {
|
||||
sDs->activeTool = (DsgnToolE)toolIdx;
|
||||
sDs->mode = (toolIdx == TOOL_POINTER) ? DSGN_IDLE : DSGN_PLACING;
|
||||
sDs->mode = DSGN_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue