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;
|
newCursor = CURSOR_RESIZE_V;
|
||||||
}
|
}
|
||||||
} else if (hitIdx >= 0 && hitPart == HIT_CONTENT) {
|
} else if (hitIdx >= 0 && hitPart == HIT_CONTENT) {
|
||||||
// Hovering over content area -- check for ListView column border
|
|
||||||
WindowT *win = ctx->stack.windows[hitIdx];
|
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 cx = mx - win->x - win->contentX;
|
||||||
int32_t cy = my - win->y - win->contentY;
|
int32_t cy = my - win->y - win->contentY;
|
||||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
||||||
|
|
|
||||||
|
|
@ -555,6 +555,7 @@ typedef struct WindowT {
|
||||||
void (*onClose)(struct WindowT *win);
|
void (*onClose)(struct WindowT *win);
|
||||||
void (*onMenu)(struct WindowT *win, int32_t menuId);
|
void (*onMenu)(struct WindowT *win, int32_t menuId);
|
||||||
void (*onScroll)(struct WindowT *win, ScrollbarOrientE orient, int32_t value);
|
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;
|
} WindowT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -49,6 +49,7 @@ typedef struct {
|
||||||
int32_t tabIndex;
|
int32_t tabIndex;
|
||||||
DsgnPropT props[DSGN_MAX_PROPS];
|
DsgnPropT props[DSGN_MAX_PROPS];
|
||||||
int32_t propCount;
|
int32_t propCount;
|
||||||
|
WidgetT *widget; // live widget (created at design time for WYSIWYG)
|
||||||
} DsgnControlT;
|
} DsgnControlT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -62,6 +63,7 @@ typedef struct {
|
||||||
int32_t height;
|
int32_t height;
|
||||||
DsgnControlT *controls; // stb_ds dynamic array
|
DsgnControlT *controls; // stb_ds dynamic array
|
||||||
bool dirty;
|
bool dirty;
|
||||||
|
WidgetT *contentBox; // VBox parent for live widgets
|
||||||
} DsgnFormT;
|
} DsgnFormT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -92,15 +94,10 @@ typedef enum {
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
HANDLE_NONE = -1,
|
HANDLE_NONE = -1,
|
||||||
HANDLE_NW = 0,
|
HANDLE_E = 0, // resize width
|
||||||
HANDLE_N,
|
HANDLE_S, // resize height
|
||||||
HANDLE_NE,
|
HANDLE_SE, // resize both
|
||||||
HANDLE_E,
|
HANDLE_COUNT = 3
|
||||||
HANDLE_SE,
|
|
||||||
HANDLE_S,
|
|
||||||
HANDLE_SW,
|
|
||||||
HANDLE_W,
|
|
||||||
HANDLE_COUNT = 8
|
|
||||||
} DsgnHandleE;
|
} DsgnHandleE;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -109,9 +106,7 @@ typedef enum {
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
DSGN_IDLE,
|
DSGN_IDLE,
|
||||||
DSGN_PLACING,
|
DSGN_REORDERING, // dragging control up/down in the VBox
|
||||||
DSGN_DRAWING,
|
|
||||||
DSGN_MOVING,
|
|
||||||
DSGN_RESIZING
|
DSGN_RESIZING
|
||||||
} DsgnModeE;
|
} DsgnModeE;
|
||||||
|
|
||||||
|
|
@ -125,17 +120,11 @@ typedef struct {
|
||||||
DsgnToolE activeTool;
|
DsgnToolE activeTool;
|
||||||
DsgnModeE mode;
|
DsgnModeE mode;
|
||||||
DsgnHandleE activeHandle;
|
DsgnHandleE activeHandle;
|
||||||
int32_t dragStartX;
|
int32_t dragStartY; // mouse Y at drag start (for reorder)
|
||||||
int32_t dragStartY;
|
int32_t dragOrigWidth; // widget size at resize start
|
||||||
int32_t dragOrigLeft;
|
|
||||||
int32_t dragOrigTop;
|
|
||||||
int32_t dragOrigWidth;
|
|
||||||
int32_t dragOrigHeight;
|
int32_t dragOrigHeight;
|
||||||
int32_t drawX; // rubber-band start for new control
|
int32_t dragStartX; // mouse X at resize start
|
||||||
int32_t drawY;
|
WindowT *formWin;
|
||||||
bool showGrid;
|
|
||||||
bool snapToGrid;
|
|
||||||
WidgetT *canvas;
|
|
||||||
AppContextT *ctx;
|
AppContextT *ctx;
|
||||||
} DsgnStateT;
|
} DsgnStateT;
|
||||||
|
|
||||||
|
|
@ -146,9 +135,13 @@ typedef struct {
|
||||||
// Initialize designer state.
|
// Initialize designer state.
|
||||||
void dsgnInit(DsgnStateT *ds, AppContextT *ctx);
|
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);
|
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.
|
// Load a .frm file into the designer.
|
||||||
bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen);
|
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.
|
// Repaint the design surface.
|
||||||
void dsgnPaint(DsgnStateT *ds);
|
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.
|
// Handle mouse click on the design surface.
|
||||||
void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag);
|
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.
|
// work on real hardware inside the DVX windowing system.
|
||||||
|
|
||||||
#include "dvxApp.h"
|
#include "dvxApp.h"
|
||||||
|
#include "dvxCursor.h"
|
||||||
#include "dvxDialog.h"
|
#include "dvxDialog.h"
|
||||||
#include "dvxWidget.h"
|
#include "dvxWidget.h"
|
||||||
|
#include "dvxWidgetPlugin.h"
|
||||||
#include "dvxWm.h"
|
#include "dvxWm.h"
|
||||||
#include "shellApp.h"
|
#include "shellApp.h"
|
||||||
#include "widgetBox.h"
|
#include "widgetBox.h"
|
||||||
#include "widgetLabel.h"
|
#include "widgetLabel.h"
|
||||||
#include "widgetTextInput.h"
|
#include "widgetTextInput.h"
|
||||||
#include "widgetDropdown.h"
|
#include "widgetDropdown.h"
|
||||||
#include "widgetCanvas.h"
|
|
||||||
#include "widgetSplitter.h"
|
#include "widgetSplitter.h"
|
||||||
#include "widgetStatusBar.h"
|
#include "widgetStatusBar.h"
|
||||||
|
|
||||||
|
|
@ -89,7 +90,9 @@ static void onFormWinClose(WindowT *win);
|
||||||
static void setStatus(const char *text);
|
static void setStatus(const char *text);
|
||||||
static void switchToCode(void);
|
static void switchToCode(void);
|
||||||
static void switchToDesign(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);
|
static void updateDropdowns(void);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -753,6 +756,13 @@ static void loadFile(void) {
|
||||||
snprintf(title, sizeof(title), "DVX BASIC - %s", path);
|
snprintf(title, sizeof(title), "DVX BASIC - %s", path);
|
||||||
dvxSetTitle(sAc, sWin, title);
|
dvxSetTitle(sAc, sWin, title);
|
||||||
|
|
||||||
|
// Close any open form designer and clear cached form
|
||||||
|
if (sFormWin) {
|
||||||
|
onFormWinClose(sFormWin);
|
||||||
|
}
|
||||||
|
|
||||||
|
dsgnFree(&sDesigner);
|
||||||
|
|
||||||
updateDropdowns();
|
updateDropdowns();
|
||||||
setStatus("File loaded.");
|
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) {
|
static int32_t onFormWinCursorQuery(WindowT *win, int32_t x, int32_t y) {
|
||||||
(void)w;
|
(void)win;
|
||||||
dsgnOnMouse(&sDesigner, cx, cy, drag);
|
|
||||||
prpRefresh(&sDesigner);
|
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) {
|
static void onFormWinClose(WindowT *win) {
|
||||||
dvxDestroyWindow(sAc, win);
|
dvxDestroyWindow(sAc, win);
|
||||||
sFormWin = NULL;
|
sFormWin = NULL;
|
||||||
dsgnSetCanvas(&sDesigner, NULL);
|
sDesigner.formWin = NULL;
|
||||||
|
|
||||||
if (sToolboxWin) {
|
if (sToolboxWin) {
|
||||||
tbxDestroy(sAc, 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";
|
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];
|
char title[128];
|
||||||
snprintf(title, sizeof(title), "%s [Design]", formName);
|
snprintf(title, sizeof(title), "%s [Design]", formName);
|
||||||
|
|
||||||
// Position next to the IDE window
|
sFormWin = dvxCreateWindowCentered(sAc, title, IDE_DESIGN_W, IDE_DESIGN_H, true);
|
||||||
int32_t winX = IDE_WIN_X;
|
|
||||||
int32_t winY = IDE_WIN_Y;
|
|
||||||
|
|
||||||
sFormWin = dvxCreateWindow(sAc, title, winX, winY, formW + 10, formH + 10, true);
|
|
||||||
|
|
||||||
if (!sFormWin) {
|
if (!sFormWin) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sFormWin->onClose = onFormWinClose;
|
sFormWin->onClose = onFormWinClose;
|
||||||
|
sDesigner.formWin = sFormWin;
|
||||||
|
|
||||||
WidgetT *root = wgtInitWindow(sAc, sFormWin);
|
WidgetT *root = wgtInitWindow(sAc, sFormWin);
|
||||||
WidgetT *canvas = wgtCanvas(root, formW, formH);
|
WidgetT *contentBox = wgtVBox(root);
|
||||||
canvas->weight = 100;
|
contentBox->weight = 100;
|
||||||
wgtCanvasSetMouseCallback(canvas, dsgnMouseCb);
|
|
||||||
dsgnSetCanvas(&sDesigner, canvas);
|
// 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
|
// Create toolbox and properties windows
|
||||||
if (!sToolboxWin) {
|
if (!sToolboxWin) {
|
||||||
|
|
@ -1203,7 +1329,6 @@ static void switchToDesign(void) {
|
||||||
sPropsWin = prpCreate(sAc, &sDesigner);
|
sPropsWin = prpCreate(sAc, &sDesigner);
|
||||||
}
|
}
|
||||||
|
|
||||||
dsgnPaint(&sDesigner);
|
|
||||||
setStatus("Design view open.");
|
setStatus("Design view open.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
// ideProperties.c -- DVX BASIC form designer properties window
|
// ideProperties.c -- DVX BASIC form designer properties window
|
||||||
//
|
//
|
||||||
// A floating window with a two-column list showing property names
|
// A floating window with a TreeView listing all controls on the
|
||||||
// and values for the currently selected control. Double-clicking
|
// form (for selection and drag-reorder) and a read-only TextArea
|
||||||
// a value opens an input dialog to edit it.
|
// showing properties of the selected control.
|
||||||
|
|
||||||
#include "ideProperties.h"
|
#include "ideProperties.h"
|
||||||
#include "dvxDialog.h"
|
|
||||||
#include "dvxWm.h"
|
#include "dvxWm.h"
|
||||||
#include "widgetBox.h"
|
#include "widgetBox.h"
|
||||||
#include "widgetLabel.h"
|
#include "widgetLabel.h"
|
||||||
#include "widgetTextInput.h"
|
#include "widgetTextInput.h"
|
||||||
|
#include "widgetTreeView.h"
|
||||||
|
#include "widgetSplitter.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -21,23 +22,25 @@
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
#define PRP_WIN_W 200
|
#define PRP_WIN_W 200
|
||||||
#define PRP_WIN_H 320
|
#define PRP_WIN_H 340
|
||||||
#define PRP_MAX_ROWS 64
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Module state
|
// Module state
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static DsgnStateT *sDs = NULL;
|
static DsgnStateT *sDs = NULL;
|
||||||
static WindowT *sPrpWin = NULL;
|
static WindowT *sPrpWin = NULL;
|
||||||
static WidgetT *sListArea = NULL; // TextArea showing properties as text
|
static WidgetT *sTree = NULL;
|
||||||
static AppContextT *sPrpCtx = NULL;
|
static WidgetT *sPropsArea = NULL;
|
||||||
|
static AppContextT *sPrpCtx = NULL;
|
||||||
|
static bool sUpdating = false; // prevent feedback loops
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Prototypes
|
// Prototypes
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void onPrpClose(WindowT *win);
|
static void onPrpClose(WindowT *win);
|
||||||
|
static void onTreeChange(WidgetT *w);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// onPrpClose
|
// 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
|
// prpCreate
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -56,7 +164,6 @@ WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds) {
|
||||||
sDs = ds;
|
sDs = ds;
|
||||||
sPrpCtx = ctx;
|
sPrpCtx = ctx;
|
||||||
|
|
||||||
// Position at right edge of screen
|
|
||||||
int32_t winX = ctx->display.width - PRP_WIN_W - 10;
|
int32_t winX = ctx->display.width - PRP_WIN_W - 10;
|
||||||
WindowT *win = dvxCreateWindow(ctx, "Properties", winX, 30, PRP_WIN_W, PRP_WIN_H, false);
|
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);
|
WidgetT *root = wgtInitWindow(ctx, win);
|
||||||
|
|
||||||
// Properties displayed as a read-only text area (simple approach)
|
// Splitter: tree on top, properties on bottom
|
||||||
sListArea = wgtTextArea(root, 4096);
|
WidgetT *splitter = wgtSplitter(root, false);
|
||||||
sListArea->weight = 100;
|
splitter->weight = 100;
|
||||||
sListArea->readOnly = true;
|
|
||||||
|
// 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);
|
prpRefresh(ds);
|
||||||
return win;
|
return win;
|
||||||
|
|
@ -88,9 +203,10 @@ void prpDestroy(AppContextT *ctx, WindowT *win) {
|
||||||
dvxDestroyWindow(ctx, win);
|
dvxDestroyWindow(ctx, win);
|
||||||
}
|
}
|
||||||
|
|
||||||
sPrpWin = NULL;
|
sPrpWin = NULL;
|
||||||
sListArea = NULL;
|
sTree = NULL;
|
||||||
sDs = NULL;
|
sPropsArea = NULL;
|
||||||
|
sDs = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -99,7 +215,38 @@ void prpDestroy(AppContextT *ctx, WindowT *win) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void prpRefresh(DsgnStateT *ds) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,11 +256,8 @@ void prpRefresh(DsgnStateT *ds) {
|
||||||
if (ds->selectedIdx >= 0 && ds->selectedIdx < (int32_t)arrlen(ds->form->controls)) {
|
if (ds->selectedIdx >= 0 && ds->selectedIdx < (int32_t)arrlen(ds->form->controls)) {
|
||||||
DsgnControlT *ctrl = &ds->form->controls[ds->selectedIdx];
|
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, "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, "Type = %s\n", ctrl->typeName);
|
||||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "Top = %d\n", (int)ctrl->top);
|
|
||||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "Width = %d\n", (int)ctrl->width);
|
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, "Height = %d\n", (int)ctrl->height);
|
||||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "TabIndex = %d\n", (int)ctrl->tabIndex);
|
pos += snprintf(buf + pos, sizeof(buf) - pos, "TabIndex = %d\n", (int)ctrl->tabIndex);
|
||||||
|
|
@ -123,12 +267,10 @@ void prpRefresh(DsgnStateT *ds) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "%s (Form)\n", ds->form->name);
|
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, "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, "Width = %d\n", (int)ds->form->width);
|
||||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "Height = %d\n", (int)ds->form->height);
|
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) {
|
if (toolIdx >= 0 && toolIdx < TOOL_COUNT) {
|
||||||
sDs->activeTool = (DsgnToolE)toolIdx;
|
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