More IDE work.

This commit is contained in:
Scott Duensing 2026-03-28 21:14:38 -05:00
parent 7d74d76985
commit 59bc2b5ed3
7 changed files with 722 additions and 471 deletions

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -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);
prpRefresh(&sDesigner);
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;
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 *root = wgtInitWindow(sAc, sFormWin);
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.");
}

View file

@ -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,23 +22,25 @@
// ============================================================
#define PRP_WIN_W 200
#define PRP_WIN_H 320
#define PRP_MAX_ROWS 64
#define PRP_WIN_H 340
// ============================================================
// Module state
// ============================================================
static DsgnStateT *sDs = NULL;
static WindowT *sPrpWin = NULL;
static WidgetT *sListArea = NULL; // TextArea showing properties as text
static AppContextT *sPrpCtx = NULL;
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
@ -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;
@ -88,9 +203,10 @@ void prpDestroy(AppContextT *ctx, WindowT *win) {
dvxDestroyWindow(ctx, win);
}
sPrpWin = NULL;
sListArea = NULL;
sDs = NULL;
sPrpWin = 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);
}

View file

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