Start of form designer.
This commit is contained in:
parent
5171753250
commit
7d74d76985
11 changed files with 1697 additions and 4 deletions
|
|
@ -31,7 +31,8 @@ COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/s
|
|||
FORMRT_OBJS = $(OBJDIR)/formrt.o
|
||||
|
||||
# IDE app objects
|
||||
APP_OBJS = $(OBJDIR)/ideMain.o $(FORMRT_OBJS)
|
||||
IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o
|
||||
APP_OBJS = $(IDE_OBJS) $(FORMRT_OBJS)
|
||||
APP_TARGET = $(APPDIR)/dvxbasic.app
|
||||
|
||||
# Native test programs (host gcc, not cross-compiled)
|
||||
|
|
@ -92,7 +93,16 @@ $(OBJDIR)/codegen.o: compiler/codegen.c compiler/codegen.h compiler/symtab.h com
|
|||
$(OBJDIR)/formrt.o: formrt/formrt.c formrt/formrt.h compiler/codegen.h runtime/vm.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/ideMain.o: ide/ideMain.c compiler/parser.h runtime/vm.h | $(OBJDIR)
|
||||
$(OBJDIR)/ideDesigner.o: ide/ideDesigner.c ide/ideDesigner.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/ideMain.o: ide/ideMain.c ide/ideDesigner.h ide/ideToolbox.h ide/ideProperties.h compiler/parser.h runtime/vm.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/ideProperties.o: ide/ideProperties.c ide/ideProperties.h ide/ideDesigner.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/ideToolbox.o: ide/ideToolbox.c ide/ideToolbox.h ide/ideDesigner.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/lexer.o: compiler/lexer.c compiler/lexer.h | $(OBJDIR)
|
||||
|
|
@ -124,5 +134,5 @@ $(BINDIR):
|
|||
mkdir -p $(BINDIR)
|
||||
|
||||
clean:
|
||||
rm -f $(RT_OBJS) $(COMP_OBJS) $(FORMRT_OBJS) $(APP_OBJS) $(RT_TARGET) $(APP_TARGET) $(LIBSDIR)/basrt.dep $(OBJDIR)/basrt_init.o
|
||||
rm -f $(RT_OBJS) $(COMP_OBJS) $(FORMRT_OBJS) $(IDE_OBJS) $(RT_TARGET) $(APP_TARGET) $(LIBSDIR)/basrt.dep $(OBJDIR)/basrt_init.o
|
||||
rm -f $(TEST_COMPILER) $(TEST_VM) $(TEST_LEX) $(TEST_QUICK)
|
||||
|
|
|
|||
967
dvxbasic/ide/ideDesigner.c
Normal file
967
dvxbasic/ide/ideDesigner.c
Normal file
|
|
@ -0,0 +1,967 @@
|
|||
// 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.
|
||||
|
||||
#include "ideDesigner.h"
|
||||
#include "dvxVideo.h"
|
||||
#include "stb_ds_wrap.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
// ============================================================
|
||||
|
||||
#define FRM_LINE_MAX 512
|
||||
#define DEFAULT_FORM_W 400
|
||||
#define DEFAULT_FORM_H 300
|
||||
#define DEFAULT_CTRL_W 100
|
||||
#define DEFAULT_CTRL_H 30
|
||||
#define MIN_CTRL_SIZE 8
|
||||
|
||||
// ============================================================
|
||||
// Tool type name table
|
||||
// ============================================================
|
||||
|
||||
static const char *sToolTypeNames[TOOL_COUNT] = {
|
||||
"", // TOOL_POINTER
|
||||
"CommandButton", // TOOL_BUTTON
|
||||
"Label", // TOOL_LABEL
|
||||
"TextBox", // TOOL_TEXTBOX
|
||||
"CheckBox", // TOOL_CHECKBOX
|
||||
"OptionButton", // TOOL_OPTION
|
||||
"Frame", // TOOL_FRAME
|
||||
"ListBox", // TOOL_LISTBOX
|
||||
"ComboBox", // TOOL_COMBOBOX
|
||||
"HScrollBar", // TOOL_HSCROLL
|
||||
"VScrollBar", // TOOL_VSCROLL
|
||||
"Timer", // TOOL_TIMER
|
||||
"PictureBox", // TOOL_PICTURE
|
||||
"Image" // TOOL_IMAGE
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Default event table
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
const char *typeName;
|
||||
const char *eventName;
|
||||
} DefaultEventT;
|
||||
|
||||
static const DefaultEventT sDefaultEvents[] = {
|
||||
{ "CommandButton", "Click" },
|
||||
{ "Label", "Click" },
|
||||
{ "TextBox", "Change" },
|
||||
{ "CheckBox", "Click" },
|
||||
{ "OptionButton", "Click" },
|
||||
{ "Frame", "Click" },
|
||||
{ "ListBox", "Click" },
|
||||
{ "ComboBox", "Click" },
|
||||
{ "Timer", "Timer" },
|
||||
{ "PictureBox", "Click" },
|
||||
{ "Image", "Click" },
|
||||
{ "Form", "Load" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 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 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 void setPropValue(DsgnControlT *ctrl, const char *name, const char *value);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnAutoName
|
||||
// ============================================================
|
||||
|
||||
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"; }
|
||||
else if (strcasecmp(typeName, "OptionButton") == 0) { prefix = "Option"; }
|
||||
else if (strcasecmp(typeName, "HScrollBar") == 0) { prefix = "HScroll"; }
|
||||
else if (strcasecmp(typeName, "VScrollBar") == 0) { prefix = "VScroll"; }
|
||||
else if (strcasecmp(typeName, "PictureBox") == 0) { prefix = "Picture"; }
|
||||
|
||||
int32_t highest = 0;
|
||||
int32_t prefixLen = (int32_t)strlen(prefix);
|
||||
int32_t count = ds->form ? (int32_t)arrlen(ds->form->controls) : 0;
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
if (strncasecmp(ds->form->controls[i].name, prefix, prefixLen) == 0) {
|
||||
int32_t num = atoi(ds->form->controls[i].name + prefixLen);
|
||||
|
||||
if (num > highest) {
|
||||
highest = num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(buf, bufSize, "%s%d", prefix, (int)(highest + 1));
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnDefaultEvent
|
||||
// ============================================================
|
||||
|
||||
const char *dsgnDefaultEvent(const char *typeName) {
|
||||
for (int32_t i = 0; sDefaultEvents[i].typeName; i++) {
|
||||
if (strcasecmp(typeName, sDefaultEvents[i].typeName) == 0) {
|
||||
return sDefaultEvents[i].eventName;
|
||||
}
|
||||
}
|
||||
|
||||
return "Click";
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnFree
|
||||
// ============================================================
|
||||
|
||||
void dsgnFree(DsgnStateT *ds) {
|
||||
if (ds->form) {
|
||||
arrfree(ds->form->controls);
|
||||
free(ds->form);
|
||||
ds->form = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnInit
|
||||
// ============================================================
|
||||
|
||||
void dsgnInit(DsgnStateT *ds, AppContextT *ctx) {
|
||||
memset(ds, 0, sizeof(*ds));
|
||||
ds->selectedIdx = -1;
|
||||
ds->activeTool = TOOL_POINTER;
|
||||
ds->mode = DSGN_IDLE;
|
||||
ds->activeHandle = HANDLE_NONE;
|
||||
ds->showGrid = true;
|
||||
ds->snapToGrid = true;
|
||||
ds->ctx = ctx;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnLoadFrm
|
||||
// ============================================================
|
||||
|
||||
bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
||||
if (!source || sourceLen <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dsgnFree(ds);
|
||||
|
||||
DsgnFormT *form = (DsgnFormT *)calloc(1, sizeof(DsgnFormT));
|
||||
|
||||
if (!form) {
|
||||
return false;
|
||||
}
|
||||
|
||||
form->controls = NULL;
|
||||
form->width = DEFAULT_FORM_W;
|
||||
form->height = DEFAULT_FORM_H;
|
||||
snprintf(form->name, DSGN_MAX_NAME, "Form1");
|
||||
snprintf(form->caption, DSGN_MAX_TEXT, "Form1");
|
||||
|
||||
DsgnControlT *curCtrl = NULL;
|
||||
bool inForm = false;
|
||||
|
||||
const char *pos = source;
|
||||
const char *end = source + sourceLen;
|
||||
|
||||
while (pos < end) {
|
||||
// Extract one line
|
||||
const char *lineStart = pos;
|
||||
|
||||
while (pos < end && *pos != '\n' && *pos != '\r') {
|
||||
pos++;
|
||||
}
|
||||
|
||||
int32_t lineLen = (int32_t)(pos - lineStart);
|
||||
|
||||
if (pos < end && *pos == '\r') { pos++; }
|
||||
if (pos < end && *pos == '\n') { pos++; }
|
||||
|
||||
char line[FRM_LINE_MAX];
|
||||
|
||||
if (lineLen >= FRM_LINE_MAX) {
|
||||
lineLen = FRM_LINE_MAX - 1;
|
||||
}
|
||||
|
||||
memcpy(line, lineStart, lineLen);
|
||||
line[lineLen] = '\0';
|
||||
|
||||
// Trim leading whitespace
|
||||
char *trimmed = line;
|
||||
|
||||
while (*trimmed == ' ' || *trimmed == '\t') {
|
||||
trimmed++;
|
||||
}
|
||||
|
||||
if (*trimmed == '\0' || *trimmed == '\'') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// VERSION line -- skip
|
||||
if (strncasecmp(trimmed, "VERSION ", 8) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Begin TypeName CtrlName
|
||||
if (strncasecmp(trimmed, "Begin ", 6) == 0) {
|
||||
char *rest = trimmed + 6;
|
||||
char typeName[DSGN_MAX_NAME];
|
||||
char ctrlName[DSGN_MAX_NAME];
|
||||
int32_t ti = 0;
|
||||
|
||||
while (*rest && *rest != ' ' && *rest != '\t' && ti < DSGN_MAX_NAME - 1) {
|
||||
typeName[ti++] = *rest++;
|
||||
}
|
||||
|
||||
typeName[ti] = '\0';
|
||||
|
||||
while (*rest == ' ' || *rest == '\t') { rest++; }
|
||||
|
||||
int32_t ci = 0;
|
||||
|
||||
while (*rest && *rest != ' ' && *rest != '\t' && *rest != '\r' && *rest != '\n' && ci < DSGN_MAX_NAME - 1) {
|
||||
ctrlName[ci++] = *rest++;
|
||||
}
|
||||
|
||||
ctrlName[ci] = '\0';
|
||||
|
||||
if (strcasecmp(typeName, "Form") == 0) {
|
||||
snprintf(form->name, DSGN_MAX_NAME, "%s", ctrlName);
|
||||
snprintf(form->caption, DSGN_MAX_TEXT, "%s", ctrlName);
|
||||
inForm = true;
|
||||
curCtrl = NULL;
|
||||
} else if (inForm) {
|
||||
DsgnControlT ctrl;
|
||||
memset(&ctrl, 0, sizeof(ctrl));
|
||||
snprintf(ctrl.name, DSGN_MAX_NAME, "%s", ctrlName);
|
||||
snprintf(ctrl.typeName, DSGN_MAX_NAME, "%s", typeName);
|
||||
ctrl.width = DEFAULT_CTRL_W;
|
||||
ctrl.height = DEFAULT_CTRL_H;
|
||||
ctrl.tabIndex = (int32_t)arrlen(form->controls);
|
||||
arrput(form->controls, ctrl);
|
||||
curCtrl = &form->controls[arrlen(form->controls) - 1];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// End
|
||||
if (strcasecmp(trimmed, "End") == 0) {
|
||||
if (curCtrl) {
|
||||
curCtrl = NULL;
|
||||
} else {
|
||||
inForm = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Property = Value
|
||||
char *eq = strchr(trimmed, '=');
|
||||
|
||||
if (eq && inForm) {
|
||||
// Extract key
|
||||
char key[DSGN_MAX_NAME];
|
||||
char *kend = eq - 1;
|
||||
|
||||
while (kend > trimmed && (*kend == ' ' || *kend == '\t')) { kend--; }
|
||||
|
||||
int32_t klen = (int32_t)(kend - trimmed + 1);
|
||||
|
||||
if (klen >= DSGN_MAX_NAME) { klen = DSGN_MAX_NAME - 1; }
|
||||
|
||||
memcpy(key, trimmed, klen);
|
||||
key[klen] = '\0';
|
||||
|
||||
// Extract value (skip leading whitespace and quotes)
|
||||
char *vstart = eq + 1;
|
||||
|
||||
while (*vstart == ' ' || *vstart == '\t') { vstart++; }
|
||||
|
||||
char val[DSGN_MAX_TEXT];
|
||||
int32_t vi = 0;
|
||||
|
||||
if (*vstart == '"') {
|
||||
vstart++;
|
||||
|
||||
while (*vstart && *vstart != '"' && vi < DSGN_MAX_TEXT - 1) {
|
||||
val[vi++] = *vstart++;
|
||||
}
|
||||
} else {
|
||||
while (*vstart && *vstart != '\r' && *vstart != '\n' && vi < DSGN_MAX_TEXT - 1) {
|
||||
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); }
|
||||
else if (strcasecmp(key, "Width") == 0) { curCtrl->width = atoi(val); }
|
||||
else if (strcasecmp(key, "Height") == 0) { curCtrl->height = atoi(val); }
|
||||
else if (strcasecmp(key, "TabIndex") == 0) { curCtrl->tabIndex = atoi(val); }
|
||||
else { setPropValue(curCtrl, key, val); }
|
||||
} else {
|
||||
if (strcasecmp(key, "Caption") == 0) { snprintf(form->caption, DSGN_MAX_TEXT, "%s", val); }
|
||||
else if (strcasecmp(key, "Width") == 0) { form->width = atoi(val); }
|
||||
else if (strcasecmp(key, "Height") == 0) { form->height = atoi(val); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnNewForm
|
||||
// ============================================================
|
||||
|
||||
void dsgnNewForm(DsgnStateT *ds, const char *name) {
|
||||
dsgnFree(ds);
|
||||
|
||||
DsgnFormT *form = (DsgnFormT *)calloc(1, sizeof(DsgnFormT));
|
||||
form->controls = NULL;
|
||||
form->width = DEFAULT_FORM_W;
|
||||
form->height = DEFAULT_FORM_H;
|
||||
snprintf(form->name, DSGN_MAX_NAME, "%s", name);
|
||||
snprintf(form->caption, DSGN_MAX_TEXT, "%s", name);
|
||||
|
||||
ds->form = form;
|
||||
ds->selectedIdx = -1;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnOnKey
|
||||
// ============================================================
|
||||
|
||||
void dsgnOnKey(DsgnStateT *ds, int32_t key) {
|
||||
if (!ds->form) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete key -- remove selected control
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnOnMouse
|
||||
// ============================================================
|
||||
|
||||
void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag) {
|
||||
if (!ds->form || !ds->canvas) {
|
||||
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) {
|
||||
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;
|
||||
ctrl->height = ds->dragOrigHeight + dy;
|
||||
break;
|
||||
case HANDLE_W:
|
||||
ctrl->left = ds->dragOrigLeft + dx;
|
||||
ctrl->width = ds->dragOrigWidth - dx;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
|
||||
ds->form->dirty = true;
|
||||
dsgnPaint(ds);
|
||||
} else if (ds->mode == DSGN_DRAWING) {
|
||||
// Rubber-band for new control -- just repaint with preview
|
||||
dsgnPaint(ds);
|
||||
|
||||
// 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 (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;
|
||||
ds->form->dirty = true;
|
||||
dsgnPaint(ds);
|
||||
return;
|
||||
}
|
||||
|
||||
// Release from moving/resizing
|
||||
if (ds->mode == DSGN_MOVING || 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
|
||||
|
||||
// Check grab handles of selected control first
|
||||
if (ds->selectedIdx >= 0 && ds->selectedIdx < ctrlCount) {
|
||||
DsgnHandleE handle = hitTestHandles(&ds->form->controls[ds->selectedIdx], x, y);
|
||||
|
||||
if (handle != HANDLE_NONE) {
|
||||
DsgnControlT *ctrl = &ds->form->controls[ds->selectedIdx];
|
||||
ds->mode = DSGN_RESIZING;
|
||||
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;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Hit test controls
|
||||
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->dragStartY = y;
|
||||
ds->dragOrigLeft = ctrl->left;
|
||||
ds->dragOrigTop = ctrl->top;
|
||||
} else {
|
||||
ds->selectedIdx = -1;
|
||||
}
|
||||
|
||||
dsgnPaint(ds);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnPaint
|
||||
// ============================================================
|
||||
|
||||
void dsgnPaint(DsgnStateT *ds) {
|
||||
if (!ds->canvas || !ds->form || !ds->ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Form background
|
||||
uint32_t formBg = packColor(&ds->ctx->display, 192, 192, 192);
|
||||
wgtCanvasClear(ds->canvas, formBg);
|
||||
|
||||
// Grid dots
|
||||
if (ds->showGrid) {
|
||||
drawGrid(ds);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(ds->canvas);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnSaveFrm
|
||||
// ============================================================
|
||||
|
||||
int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize) {
|
||||
if (!ds->form || !buf || bufSize <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t pos = 0;
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, "VERSION 1.00\n");
|
||||
pos += snprintf(buf + pos, bufSize - pos, "Begin Form %s\n", ds->form->name);
|
||||
pos += snprintf(buf + pos, bufSize - pos, " Caption = \"%s\"\n", ds->form->caption);
|
||||
pos += snprintf(buf + pos, bufSize - pos, " Width = %d\n", (int)ds->form->width);
|
||||
pos += snprintf(buf + pos, bufSize - pos, " Height = %d\n", (int)ds->form->height);
|
||||
|
||||
int32_t count = (int32_t)arrlen(ds->form->controls);
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
const DsgnControlT *ctrl = &ds->form->controls[i];
|
||||
pos += snprintf(buf + pos, bufSize - pos, " Begin %s %s\n", ctrl->typeName, ctrl->name);
|
||||
|
||||
// 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; }
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, " %s = \"%s\"\n", ctrl->props[j].name, ctrl->props[j].value);
|
||||
}
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, " End\n");
|
||||
}
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, "End\n");
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnSelectedName
|
||||
// ============================================================
|
||||
|
||||
const char *dsgnSelectedName(const DsgnStateT *ds) {
|
||||
if (!ds->form) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (ds->selectedIdx >= 0 && ds->selectedIdx < (int32_t)arrlen(ds->form->controls)) {
|
||||
return ds->form->controls[ds->selectedIdx].name;
|
||||
}
|
||||
|
||||
return ds->form->name;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnSetCanvas
|
||||
// ============================================================
|
||||
|
||||
void dsgnSetCanvas(DsgnStateT *ds, WidgetT *canvas) {
|
||||
ds->canvas = canvas;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnToolTypeName
|
||||
// ============================================================
|
||||
|
||||
const char *dsgnToolTypeName(DsgnToolE tool) {
|
||||
if (tool >= 0 && tool < TOOL_COUNT) {
|
||||
return sToolTypeNames[tool];
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 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
|
||||
// ============================================================
|
||||
|
||||
static const char *getPropValue(const DsgnControlT *ctrl, const char *name) {
|
||||
for (int32_t i = 0; i < ctrl->propCount; i++) {
|
||||
if (strcasecmp(ctrl->props[i].name, name) == 0) {
|
||||
return ctrl->props[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// hitTestControl
|
||||
// ============================================================
|
||||
|
||||
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) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// hitTestHandles
|
||||
// ============================================================
|
||||
|
||||
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;
|
||||
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 };
|
||||
|
||||
for (int32_t i = 0; i < HANDLE_COUNT; i++) {
|
||||
if (x >= hx[i] && x < hx[i] + hs && y >= hy[i] && y < hy[i] + hs) {
|
||||
return (DsgnHandleE)i;
|
||||
}
|
||||
}
|
||||
|
||||
return HANDLE_NONE;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 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);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
ctrl->propCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// snapToGrid
|
||||
// ============================================================
|
||||
|
||||
static int32_t snapToGrid(int32_t val, int32_t gridSize) {
|
||||
return ((val + gridSize / 2) / gridSize) * gridSize;
|
||||
}
|
||||
186
dvxbasic/ide/ideDesigner.h
Normal file
186
dvxbasic/ide/ideDesigner.h
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
// ideDesigner.h -- DVX BASIC form designer
|
||||
//
|
||||
// Design-time data model and design surface for visual form editing.
|
||||
// Controls are stored as pure data (not live widgets) and rendered
|
||||
// onto a Canvas widget. The designer reads and writes .frm files.
|
||||
|
||||
#ifndef IDE_DESIGNER_H
|
||||
#define IDE_DESIGNER_H
|
||||
|
||||
#include "dvxApp.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgetCanvas.h"
|
||||
|
||||
#include "stb_ds_wrap.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// ============================================================
|
||||
// Limits
|
||||
// ============================================================
|
||||
|
||||
#define DSGN_MAX_NAME 32
|
||||
#define DSGN_MAX_TEXT 256
|
||||
#define DSGN_MAX_PROPS 32
|
||||
#define DSGN_GRID_SIZE 8
|
||||
#define DSGN_HANDLE_SIZE 6
|
||||
|
||||
// ============================================================
|
||||
// Design-time property (stored as key=value strings)
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
char name[DSGN_MAX_NAME];
|
||||
char value[DSGN_MAX_TEXT];
|
||||
} DsgnPropT;
|
||||
|
||||
// ============================================================
|
||||
// Design-time control
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
char name[DSGN_MAX_NAME];
|
||||
char typeName[DSGN_MAX_NAME];
|
||||
int32_t left;
|
||||
int32_t top;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
int32_t tabIndex;
|
||||
DsgnPropT props[DSGN_MAX_PROPS];
|
||||
int32_t propCount;
|
||||
} DsgnControlT;
|
||||
|
||||
// ============================================================
|
||||
// Design-time form
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
char name[DSGN_MAX_NAME];
|
||||
char caption[DSGN_MAX_TEXT];
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
DsgnControlT *controls; // stb_ds dynamic array
|
||||
bool dirty;
|
||||
} DsgnFormT;
|
||||
|
||||
// ============================================================
|
||||
// Toolbox tool IDs
|
||||
// ============================================================
|
||||
|
||||
typedef enum {
|
||||
TOOL_POINTER = 0,
|
||||
TOOL_BUTTON,
|
||||
TOOL_LABEL,
|
||||
TOOL_TEXTBOX,
|
||||
TOOL_CHECKBOX,
|
||||
TOOL_OPTION,
|
||||
TOOL_FRAME,
|
||||
TOOL_LISTBOX,
|
||||
TOOL_COMBOBOX,
|
||||
TOOL_HSCROLL,
|
||||
TOOL_VSCROLL,
|
||||
TOOL_TIMER,
|
||||
TOOL_PICTURE,
|
||||
TOOL_IMAGE,
|
||||
TOOL_COUNT
|
||||
} DsgnToolE;
|
||||
|
||||
// ============================================================
|
||||
// Grab handle IDs
|
||||
// ============================================================
|
||||
|
||||
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
|
||||
} DsgnHandleE;
|
||||
|
||||
// ============================================================
|
||||
// Interaction mode
|
||||
// ============================================================
|
||||
|
||||
typedef enum {
|
||||
DSGN_IDLE,
|
||||
DSGN_PLACING,
|
||||
DSGN_DRAWING,
|
||||
DSGN_MOVING,
|
||||
DSGN_RESIZING
|
||||
} DsgnModeE;
|
||||
|
||||
// ============================================================
|
||||
// Designer state
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
DsgnFormT *form;
|
||||
int32_t selectedIdx; // -1 = form itself selected
|
||||
DsgnToolE activeTool;
|
||||
DsgnModeE mode;
|
||||
DsgnHandleE activeHandle;
|
||||
int32_t dragStartX;
|
||||
int32_t dragStartY;
|
||||
int32_t dragOrigLeft;
|
||||
int32_t dragOrigTop;
|
||||
int32_t dragOrigWidth;
|
||||
int32_t dragOrigHeight;
|
||||
int32_t drawX; // rubber-band start for new control
|
||||
int32_t drawY;
|
||||
bool showGrid;
|
||||
bool snapToGrid;
|
||||
WidgetT *canvas;
|
||||
AppContextT *ctx;
|
||||
} DsgnStateT;
|
||||
|
||||
// ============================================================
|
||||
// API
|
||||
// ============================================================
|
||||
|
||||
// Initialize designer state.
|
||||
void dsgnInit(DsgnStateT *ds, AppContextT *ctx);
|
||||
|
||||
// Set the canvas widget for rendering.
|
||||
void dsgnSetCanvas(DsgnStateT *ds, WidgetT *canvas);
|
||||
|
||||
// Load a .frm file into the designer.
|
||||
bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen);
|
||||
|
||||
// Save the designer form to .frm text format.
|
||||
// Returns the number of bytes written (excluding null), or -1 on error.
|
||||
int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize);
|
||||
|
||||
// Create a new blank form.
|
||||
void dsgnNewForm(DsgnStateT *ds, const char *name);
|
||||
|
||||
// Repaint the design surface.
|
||||
void dsgnPaint(DsgnStateT *ds);
|
||||
|
||||
// Handle mouse click on the design surface.
|
||||
void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag);
|
||||
|
||||
// Handle key press in design view.
|
||||
void dsgnOnKey(DsgnStateT *ds, int32_t key);
|
||||
|
||||
// Get the name of the selected control (or form name if nothing selected).
|
||||
const char *dsgnSelectedName(const DsgnStateT *ds);
|
||||
|
||||
// Get the default event name for a control type.
|
||||
const char *dsgnDefaultEvent(const char *typeName);
|
||||
|
||||
// Get the VB type name for a tool ID.
|
||||
const char *dsgnToolTypeName(DsgnToolE tool);
|
||||
|
||||
// Auto-generate a unique control name for the given type.
|
||||
void dsgnAutoName(const DsgnStateT *ds, const char *typeName, char *buf, int32_t bufSize);
|
||||
|
||||
// Free designer resources.
|
||||
void dsgnFree(DsgnStateT *ds);
|
||||
|
||||
#endif // IDE_DESIGNER_H
|
||||
|
|
@ -16,9 +16,14 @@
|
|||
#include "widgetLabel.h"
|
||||
#include "widgetTextInput.h"
|
||||
#include "widgetDropdown.h"
|
||||
#include "widgetCanvas.h"
|
||||
#include "widgetSplitter.h"
|
||||
#include "widgetStatusBar.h"
|
||||
|
||||
#include "ideDesigner.h"
|
||||
#include "ideToolbox.h"
|
||||
#include "ideProperties.h"
|
||||
|
||||
#include "../compiler/parser.h"
|
||||
#include "../formrt/formrt.h"
|
||||
#include "../runtime/vm.h"
|
||||
|
|
@ -52,7 +57,11 @@
|
|||
#define CMD_CLEAR 103
|
||||
#define CMD_EXIT 104
|
||||
#define CMD_RUN_NOCMP 105
|
||||
#define CMD_VIEW_CODE 106
|
||||
#define CMD_VIEW_DESIGN 107
|
||||
#define IDE_MAX_IMM 1024
|
||||
#define IDE_DESIGN_W 400
|
||||
#define IDE_DESIGN_H 300
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
|
|
@ -76,7 +85,11 @@ static bool inputCallback(void *ctx, const char *prompt, char *buf, int32_t bufS
|
|||
static bool doEventsCallback(void *ctx);
|
||||
static void runCached(void);
|
||||
static void runModule(BasModuleT *mod);
|
||||
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 void updateDropdowns(void);
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -94,6 +107,10 @@ static WidgetT *sEvtDropdown = NULL; // Event dropdown
|
|||
static WidgetT *sStatus = NULL; // Status bar label
|
||||
static BasVmT *sVm = NULL; // VM instance (non-NULL while running)
|
||||
static BasModuleT *sCachedModule = NULL; // Last compiled module (for Ctrl+F5)
|
||||
static DsgnStateT sDesigner;
|
||||
static WindowT *sFormWin = NULL; // Form designer window (separate)
|
||||
static WindowT *sToolboxWin = NULL;
|
||||
static WindowT *sPropsWin = NULL;
|
||||
|
||||
static char sSourceBuf[IDE_MAX_SOURCE];
|
||||
static char sOutputBuf[IDE_MAX_OUTPUT];
|
||||
|
|
@ -171,10 +188,16 @@ static void buildWindow(void) {
|
|||
wmAddMenuSeparator(runMenu);
|
||||
wmAddMenuItem(runMenu, "&Clear Output", CMD_CLEAR);
|
||||
|
||||
MenuT *viewMenu = wmAddMenu(menuBar, "&View");
|
||||
wmAddMenuItem(viewMenu, "&Code\tF7", CMD_VIEW_CODE);
|
||||
wmAddMenuItem(viewMenu, "&Object\tShift+F7", CMD_VIEW_DESIGN);
|
||||
|
||||
AccelTableT *accel = dvxCreateAccelTable();
|
||||
dvxAddAccel(accel, 'O', ACCEL_CTRL, CMD_OPEN);
|
||||
dvxAddAccel(accel, KEY_F5, 0, CMD_RUN);
|
||||
dvxAddAccel(accel, KEY_F5, ACCEL_CTRL, CMD_RUN_NOCMP);
|
||||
dvxAddAccel(accel, KEY_F7, 0, CMD_VIEW_CODE);
|
||||
dvxAddAccel(accel, KEY_F7, ACCEL_SHIFT, CMD_VIEW_DESIGN);
|
||||
dvxAddAccel(accel, 0x1B, 0, CMD_STOP);
|
||||
sWin->accelTable = accel;
|
||||
|
||||
|
|
@ -208,6 +231,9 @@ static void buildWindow(void) {
|
|||
wgtTextAreaSetShowLineNumbers(sEditor, true);
|
||||
wgtTextAreaSetAutoIndent(sEditor, true);
|
||||
|
||||
// Initialize designer (form window created on demand)
|
||||
dsgnInit(&sDesigner, sAc);
|
||||
|
||||
// Bottom pane: output + immediate
|
||||
WidgetT *bottomPane = wgtVBox(splitter);
|
||||
|
||||
|
|
@ -817,6 +843,12 @@ static void onClose(WindowT *win) {
|
|||
sCachedModule = NULL;
|
||||
}
|
||||
|
||||
if (sFormWin) {
|
||||
onFormWinClose(sFormWin);
|
||||
}
|
||||
|
||||
dsgnFree(&sDesigner);
|
||||
|
||||
arrfree(sProcTable);
|
||||
arrfree(sObjItems);
|
||||
arrfree(sEvtItems);
|
||||
|
|
@ -858,6 +890,14 @@ static void onMenu(WindowT *win, int32_t menuId) {
|
|||
clearOutput();
|
||||
break;
|
||||
|
||||
case CMD_VIEW_CODE:
|
||||
switchToCode();
|
||||
break;
|
||||
|
||||
case CMD_VIEW_DESIGN:
|
||||
switchToDesign();
|
||||
break;
|
||||
|
||||
case CMD_EXIT:
|
||||
if (sWin) {
|
||||
onClose(sWin);
|
||||
|
|
@ -1033,6 +1073,141 @@ static void printCallback(void *ctx, const char *text, bool newline) {
|
|||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// dsgnMouseCb
|
||||
// ============================================================
|
||||
|
||||
static void dsgnMouseCb(WidgetT *w, int32_t cx, int32_t cy, bool drag) {
|
||||
(void)w;
|
||||
dsgnOnMouse(&sDesigner, cx, cy, drag);
|
||||
prpRefresh(&sDesigner);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onFormWinClose
|
||||
// ============================================================
|
||||
|
||||
static void onFormWinClose(WindowT *win) {
|
||||
dvxDestroyWindow(sAc, win);
|
||||
sFormWin = NULL;
|
||||
dsgnSetCanvas(&sDesigner, NULL);
|
||||
|
||||
if (sToolboxWin) {
|
||||
tbxDestroy(sAc, sToolboxWin);
|
||||
sToolboxWin = NULL;
|
||||
}
|
||||
|
||||
if (sPropsWin) {
|
||||
prpDestroy(sAc, sPropsWin);
|
||||
sPropsWin = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// switchToCode
|
||||
// ============================================================
|
||||
|
||||
static void switchToCode(void) {
|
||||
if (sFormWin) {
|
||||
onFormWinClose(sFormWin);
|
||||
}
|
||||
|
||||
setStatus("Code view.");
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// switchToDesign
|
||||
// ============================================================
|
||||
|
||||
static void switchToDesign(void) {
|
||||
// If already open, just bring to front
|
||||
if (sFormWin) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load .frm if we don't have a form yet
|
||||
if (!sDesigner.form) {
|
||||
if (sFilePath[0]) {
|
||||
char frmPath[260];
|
||||
snprintf(frmPath, sizeof(frmPath), "%s", sFilePath);
|
||||
char *dot = strrchr(frmPath, '.');
|
||||
|
||||
if (dot) {
|
||||
strcpy(dot, ".frm");
|
||||
} else {
|
||||
strcat(frmPath, ".frm");
|
||||
}
|
||||
|
||||
FILE *f = fopen(frmPath, "r");
|
||||
|
||||
if (f) {
|
||||
fseek(f, 0, SEEK_END);
|
||||
long size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
if (size > 0 && size < IDE_MAX_SOURCE) {
|
||||
char *buf = (char *)malloc(size + 1);
|
||||
|
||||
if (buf) {
|
||||
int32_t bytesRead = (int32_t)fread(buf, 1, size, f);
|
||||
buf[bytesRead] = '\0';
|
||||
dsgnLoadFrm(&sDesigner, buf, bytesRead);
|
||||
free(buf);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!sDesigner.form) {
|
||||
dsgnNewForm(&sDesigner, "Form1");
|
||||
}
|
||||
}
|
||||
|
||||
// Create the form designer window
|
||||
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);
|
||||
|
||||
if (!sFormWin) {
|
||||
return;
|
||||
}
|
||||
|
||||
sFormWin->onClose = onFormWinClose;
|
||||
|
||||
WidgetT *root = wgtInitWindow(sAc, sFormWin);
|
||||
WidgetT *canvas = wgtCanvas(root, formW, formH);
|
||||
canvas->weight = 100;
|
||||
wgtCanvasSetMouseCallback(canvas, dsgnMouseCb);
|
||||
dsgnSetCanvas(&sDesigner, canvas);
|
||||
|
||||
// Create toolbox and properties windows
|
||||
if (!sToolboxWin) {
|
||||
sToolboxWin = tbxCreate(sAc, &sDesigner);
|
||||
}
|
||||
|
||||
if (!sPropsWin) {
|
||||
sPropsWin = prpCreate(sAc, &sDesigner);
|
||||
}
|
||||
|
||||
dsgnPaint(&sDesigner);
|
||||
setStatus("Design view open.");
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// setStatus
|
||||
// ============================================================
|
||||
|
|
|
|||
134
dvxbasic/ide/ideProperties.c
Normal file
134
dvxbasic/ide/ideProperties.c
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
// 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.
|
||||
|
||||
#include "ideProperties.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxWm.h"
|
||||
#include "widgetBox.h"
|
||||
#include "widgetLabel.h"
|
||||
#include "widgetTextInput.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
// ============================================================
|
||||
|
||||
#define PRP_WIN_W 200
|
||||
#define PRP_WIN_H 320
|
||||
#define PRP_MAX_ROWS 64
|
||||
|
||||
// ============================================================
|
||||
// Module state
|
||||
// ============================================================
|
||||
|
||||
static DsgnStateT *sDs = NULL;
|
||||
static WindowT *sPrpWin = NULL;
|
||||
static WidgetT *sListArea = NULL; // TextArea showing properties as text
|
||||
static AppContextT *sPrpCtx = NULL;
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
static void onPrpClose(WindowT *win);
|
||||
|
||||
// ============================================================
|
||||
// onPrpClose
|
||||
// ============================================================
|
||||
|
||||
static void onPrpClose(WindowT *win) {
|
||||
(void)win;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prpCreate
|
||||
// ============================================================
|
||||
|
||||
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);
|
||||
|
||||
if (!win) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
win->onClose = onPrpClose;
|
||||
sPrpWin = win;
|
||||
|
||||
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;
|
||||
|
||||
prpRefresh(ds);
|
||||
return win;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prpDestroy
|
||||
// ============================================================
|
||||
|
||||
void prpDestroy(AppContextT *ctx, WindowT *win) {
|
||||
if (win) {
|
||||
dvxDestroyWindow(ctx, win);
|
||||
}
|
||||
|
||||
sPrpWin = NULL;
|
||||
sListArea = NULL;
|
||||
sDs = NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// prpRefresh
|
||||
// ============================================================
|
||||
|
||||
void prpRefresh(DsgnStateT *ds) {
|
||||
if (!sListArea || !ds || !ds->form) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[4096];
|
||||
int32_t pos = 0;
|
||||
|
||||
if (ds->selectedIdx >= 0 && ds->selectedIdx < (int32_t)arrlen(ds->form->controls)) {
|
||||
DsgnControlT *ctrl = &ds->form->controls[ds->selectedIdx];
|
||||
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "%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, "Width = %d\n", (int)ctrl->width);
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "Height = %d\n", (int)ctrl->height);
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "TabIndex = %d\n", (int)ctrl->tabIndex);
|
||||
|
||||
for (int32_t i = 0; i < ctrl->propCount; i++) {
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "%-8s = %s\n", ctrl->props[i].name, ctrl->props[i].value);
|
||||
}
|
||||
} else {
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "%s (Form)\n", ds->form->name);
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, "---\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);
|
||||
}
|
||||
17
dvxbasic/ide/ideProperties.h
Normal file
17
dvxbasic/ide/ideProperties.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// ideProperties.h -- DVX BASIC form designer properties window
|
||||
|
||||
#ifndef IDE_PROPERTIES_H
|
||||
#define IDE_PROPERTIES_H
|
||||
|
||||
#include "ideDesigner.h"
|
||||
|
||||
// Create the properties floating window.
|
||||
WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds);
|
||||
|
||||
// Destroy the properties window.
|
||||
void prpDestroy(AppContextT *ctx, WindowT *win);
|
||||
|
||||
// Refresh the properties list for the currently selected control.
|
||||
void prpRefresh(DsgnStateT *ds);
|
||||
|
||||
#endif // IDE_PROPERTIES_H
|
||||
110
dvxbasic/ide/ideToolbox.c
Normal file
110
dvxbasic/ide/ideToolbox.c
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
// ideToolbox.c -- DVX BASIC form designer toolbox window
|
||||
//
|
||||
// A small floating window with buttons for each control type.
|
||||
// Clicking a tool sets the designer's activeTool.
|
||||
|
||||
#include "ideToolbox.h"
|
||||
#include "dvxWm.h"
|
||||
#include "widgetBox.h"
|
||||
#include "widgetButton.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
// ============================================================
|
||||
|
||||
#define TBX_WIN_W 72
|
||||
#define TBX_WIN_H 320
|
||||
#define TBX_BTN_H 20
|
||||
|
||||
// ============================================================
|
||||
// Module state
|
||||
// ============================================================
|
||||
|
||||
static DsgnStateT *sDs = NULL;
|
||||
|
||||
// ============================================================
|
||||
// Tool labels (short names for buttons)
|
||||
// ============================================================
|
||||
|
||||
static const char *sToolLabels[TOOL_COUNT] = {
|
||||
"Pointer",
|
||||
"Button",
|
||||
"Label",
|
||||
"TextBox",
|
||||
"CheckBox",
|
||||
"Option",
|
||||
"Frame",
|
||||
"ListBox",
|
||||
"ComboBox",
|
||||
"HScroll",
|
||||
"VScroll",
|
||||
"Timer",
|
||||
"Picture",
|
||||
"Image"
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Callbacks
|
||||
// ============================================================
|
||||
|
||||
static void onToolClick(WidgetT *w) {
|
||||
if (!sDs) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t toolIdx = (int32_t)(intptr_t)w->userData;
|
||||
|
||||
if (toolIdx >= 0 && toolIdx < TOOL_COUNT) {
|
||||
sDs->activeTool = (DsgnToolE)toolIdx;
|
||||
sDs->mode = (toolIdx == TOOL_POINTER) ? DSGN_IDLE : DSGN_PLACING;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void onTbxClose(WindowT *win) {
|
||||
// Don't allow closing the toolbox independently
|
||||
(void)win;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// tbxCreate
|
||||
// ============================================================
|
||||
|
||||
WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) {
|
||||
sDs = ds;
|
||||
|
||||
WindowT *win = dvxCreateWindow(ctx, "Toolbox", 0, 30, TBX_WIN_W, TBX_WIN_H, false);
|
||||
|
||||
if (!win) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
win->onClose = onTbxClose;
|
||||
|
||||
WidgetT *root = wgtInitWindow(ctx, win);
|
||||
|
||||
for (int32_t i = 0; i < TOOL_COUNT; i++) {
|
||||
WidgetT *btn = wgtButton(root, sToolLabels[i]);
|
||||
btn->onClick = onToolClick;
|
||||
btn->userData = (void *)(intptr_t)i;
|
||||
}
|
||||
|
||||
dvxFitWindow(ctx, win);
|
||||
return win;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// tbxDestroy
|
||||
// ============================================================
|
||||
|
||||
void tbxDestroy(AppContextT *ctx, WindowT *win) {
|
||||
if (win) {
|
||||
dvxDestroyWindow(ctx, win);
|
||||
}
|
||||
|
||||
sDs = NULL;
|
||||
}
|
||||
14
dvxbasic/ide/ideToolbox.h
Normal file
14
dvxbasic/ide/ideToolbox.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// ideToolbox.h -- DVX BASIC form designer toolbox window
|
||||
|
||||
#ifndef IDE_TOOLBOX_H
|
||||
#define IDE_TOOLBOX_H
|
||||
|
||||
#include "ideDesigner.h"
|
||||
|
||||
// Create the toolbox floating window. Returns the WindowT pointer.
|
||||
WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds);
|
||||
|
||||
// Destroy the toolbox window.
|
||||
void tbxDestroy(AppContextT *ctx, WindowT *win);
|
||||
|
||||
#endif // IDE_TOOLBOX_H
|
||||
|
|
@ -352,6 +352,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
|||
if (!handle) {
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "Failed to load %s:\n%s", baseName(path), dlerror());
|
||||
dvxLog("DXE load failed: %s", msg);
|
||||
dvxSetBusy(ctx, false);
|
||||
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
||||
|
||||
|
|
@ -368,6 +369,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
|||
if (!desc) {
|
||||
char msg[256];
|
||||
snprintf(msg, sizeof(msg), "%s: missing appDescriptor", baseName(path));
|
||||
dvxLog("DXE symbol error: %s", msg);
|
||||
dvxSetBusy(ctx, false);
|
||||
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
||||
dlclose(handle);
|
||||
|
|
|
|||
|
|
@ -815,6 +815,80 @@ void wgtCanvasSetPenSize(WidgetT *w, int32_t size) {
|
|||
}
|
||||
|
||||
|
||||
void wgtCanvasDrawText(WidgetT *w, int32_t x, int32_t y, const char *text) {
|
||||
if (!w || w->type != sTypeId || !text) {
|
||||
return;
|
||||
}
|
||||
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
if (!cd->pixelData) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppContextT *ctx = wgtGetContext(w);
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
int32_t cw = font->charWidth;
|
||||
int32_t ch = font->charHeight;
|
||||
int32_t bpp = cd->canvasBpp;
|
||||
|
||||
for (int32_t ci = 0; text[ci]; ci++) {
|
||||
int32_t cx = x + ci * cw;
|
||||
|
||||
if (cx + cw <= 0 || cx >= cd->canvasW || y + ch <= 0 || y >= cd->canvasH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t idx = (uint8_t)text[ci] - font->firstChar;
|
||||
|
||||
if (idx < 0 || idx >= font->numChars) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint8_t *glyph = font->glyphData + idx * ch;
|
||||
|
||||
for (int32_t row = 0; row < ch; row++) {
|
||||
int32_t py = y + row;
|
||||
|
||||
if (py < 0 || py >= cd->canvasH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t bits = glyph[row];
|
||||
|
||||
if (bits == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int32_t col = 0; col < cw; col++) {
|
||||
int32_t px = cx + col;
|
||||
|
||||
if (px < 0 || px >= cd->canvasW) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bits & (0x80 >> col)) {
|
||||
if (bpp == 2) {
|
||||
*(uint16_t *)(cd->pixelData + py * cd->canvasPitch + px * 2) = (uint16_t)cd->penColor;
|
||||
} else if (bpp == 4) {
|
||||
*(uint32_t *)(cd->pixelData + py * cd->canvasPitch + px * 4) = cd->penColor;
|
||||
} else {
|
||||
cd->pixelData[py * cd->canvasPitch + px] = (uint8_t)cd->penColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
|
@ -834,6 +908,7 @@ static const struct {
|
|||
void (*fillCircle)(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
|
||||
void (*setPixel)(WidgetT *w, int32_t x, int32_t y, uint32_t color);
|
||||
uint32_t (*getPixel)(const WidgetT *w, int32_t x, int32_t y);
|
||||
void (*drawText)(WidgetT *w, int32_t x, int32_t y, const char *text);
|
||||
} sApi = {
|
||||
.create = wgtCanvas,
|
||||
.clear = wgtCanvasClear,
|
||||
|
|
@ -847,7 +922,8 @@ static const struct {
|
|||
.fillRect = wgtCanvasFillRect,
|
||||
.fillCircle = wgtCanvasFillCircle,
|
||||
.setPixel = wgtCanvasSetPixel,
|
||||
.getPixel = wgtCanvasGetPixel
|
||||
.getPixel = wgtCanvasGetPixel,
|
||||
.drawText = wgtCanvasDrawText
|
||||
};
|
||||
|
||||
static const WgtMethodDescT sMethods[] = {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ typedef struct {
|
|||
void (*fillCircle)(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
|
||||
void (*setPixel)(WidgetT *w, int32_t x, int32_t y, uint32_t color);
|
||||
uint32_t (*getPixel)(const WidgetT *w, int32_t x, int32_t y);
|
||||
void (*drawText)(WidgetT *w, int32_t x, int32_t y, const char *text);
|
||||
} CanvasApiT;
|
||||
|
||||
static inline const CanvasApiT *dvxCanvasApi(void) {
|
||||
|
|
@ -39,5 +40,6 @@ static inline const CanvasApiT *dvxCanvasApi(void) {
|
|||
#define wgtCanvasFillCircle(w, cx, cy, radius) dvxCanvasApi()->fillCircle(w, cx, cy, radius)
|
||||
#define wgtCanvasSetPixel(w, x, y, color) dvxCanvasApi()->setPixel(w, x, y, color)
|
||||
#define wgtCanvasGetPixel(w, x, y) dvxCanvasApi()->getPixel(w, x, y)
|
||||
#define wgtCanvasDrawText(w, x, y, text) dvxCanvasApi()->drawText(w, x, y, text)
|
||||
|
||||
#endif // WIDGET_CANVAS_H
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue