Menu editor. Focus logic cleanup. Several bugs fixed.
This commit is contained in:
parent
e36d4b9cec
commit
7bc92549f7
31 changed files with 1264 additions and 122 deletions
|
|
@ -31,7 +31,7 @@ COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/s
|
|||
FORMRT_OBJS = $(OBJDIR)/formrt.o
|
||||
|
||||
# IDE app objects
|
||||
IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o
|
||||
IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideMenuEditor.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o
|
||||
APP_OBJS = $(IDE_OBJS) $(FORMRT_OBJS)
|
||||
APP_TARGET = $(APPDIR)/dvxbasic.app
|
||||
|
||||
|
|
@ -97,7 +97,10 @@ $(OBJDIR)/formrt.o: formrt/formrt.c formrt/formrt.h compiler/codegen.h runtime/v
|
|||
$(OBJDIR)/ideDesigner.o: ide/ideDesigner.c ide/ideDesigner.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/ideMain.o: ide/ideMain.c ide/ideDesigner.h ide/ideProject.h ide/ideToolbox.h ide/ideProperties.h compiler/parser.h runtime/vm.h | $(OBJDIR)
|
||||
$(OBJDIR)/ideMenuEditor.o: ide/ideMenuEditor.c ide/ideMenuEditor.h ide/ideDesigner.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/ideMain.o: ide/ideMain.c ide/ideDesigner.h ide/ideMenuEditor.h ide/ideProject.h ide/ideToolbox.h ide/ideProperties.h compiler/parser.h runtime/vm.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/ideProject.o: ide/ideProject.c ide/ideProject.h | $(OBJDIR)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ static BasFormRtT *sFormRt = NULL;
|
|||
// ============================================================
|
||||
|
||||
static BasStringT *basFormRtInputBox(void *ctx, const char *prompt, const char *title, const char *defaultText);
|
||||
static void onFormMenu(WindowT *win, int32_t menuId);
|
||||
static BasValueT callCommonMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc);
|
||||
static BasValueT callListBoxMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc);
|
||||
static WidgetT *createWidget(const char *wgtTypeName, WidgetT *parent);
|
||||
|
|
@ -321,6 +322,7 @@ void basFormRtDestroy(BasFormRtT *rt) {
|
|||
}
|
||||
|
||||
arrfree(form->controls);
|
||||
arrfree(form->menuIdMap);
|
||||
|
||||
if (form->window) {
|
||||
dvxDestroyWindow(rt->ctx, form->window);
|
||||
|
|
@ -684,6 +686,20 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
bool isContainer[MAX_FRM_NESTING];
|
||||
int32_t blockDepth = 0;
|
||||
|
||||
// Temporary menu item accumulation
|
||||
typedef struct {
|
||||
char caption[256];
|
||||
char name[BAS_MAX_CTRL_NAME];
|
||||
int32_t level;
|
||||
bool checked;
|
||||
bool enabled;
|
||||
} TempMenuItemT;
|
||||
|
||||
TempMenuItemT *menuItems = NULL; // stb_ds array
|
||||
TempMenuItemT *curMenuItem = NULL;
|
||||
int32_t menuNestDepth = 0;
|
||||
bool inMenu = false;
|
||||
|
||||
const char *pos = source;
|
||||
const char *end = source + sourceLen;
|
||||
|
||||
|
|
@ -779,6 +795,23 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
if (blockDepth < MAX_FRM_NESTING) {
|
||||
isContainer[blockDepth++] = true;
|
||||
}
|
||||
} else if (strcasecmp(typeName, "Menu") == 0 && form) {
|
||||
TempMenuItemT mi;
|
||||
memset(&mi, 0, sizeof(mi));
|
||||
snprintf(mi.name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
|
||||
mi.level = menuNestDepth;
|
||||
mi.enabled = true;
|
||||
arrput(menuItems, mi);
|
||||
curMenuItem = &menuItems[arrlen(menuItems) - 1];
|
||||
current = NULL;
|
||||
menuNestDepth++;
|
||||
inMenu = true;
|
||||
|
||||
if (blockDepth < MAX_FRM_NESTING) {
|
||||
isContainer[blockDepth++] = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if (form && nestDepth > 0) {
|
||||
// Create the content box on first control if not yet done
|
||||
if (!form->contentBox && form->root) {
|
||||
|
|
@ -858,6 +891,22 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
|
||||
// "End"
|
||||
if (strcasecmp(trimmed, "End") == 0) {
|
||||
if (inMenu) {
|
||||
menuNestDepth--;
|
||||
curMenuItem = NULL;
|
||||
|
||||
if (menuNestDepth <= 0) {
|
||||
menuNestDepth = 0;
|
||||
inMenu = false;
|
||||
}
|
||||
|
||||
if (blockDepth > 0) {
|
||||
blockDepth--;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (blockDepth > 0) {
|
||||
blockDepth--;
|
||||
|
||||
|
|
@ -880,7 +929,23 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
continue;
|
||||
}
|
||||
|
||||
if (current) {
|
||||
if (curMenuItem) {
|
||||
// Menu item properties
|
||||
char *text = value;
|
||||
|
||||
if (text[0] == '"') {
|
||||
text++;
|
||||
int32_t len = (int32_t)strlen(text);
|
||||
|
||||
if (len > 0 && text[len - 1] == '"') {
|
||||
text[len - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
if (strcasecmp(key, "Caption") == 0) { snprintf(curMenuItem->caption, sizeof(curMenuItem->caption), "%s", text); }
|
||||
else if (strcasecmp(key, "Checked") == 0) { curMenuItem->checked = (strcasecmp(text, "True") == 0 || strcasecmp(text, "-1") == 0); }
|
||||
else if (strcasecmp(key, "Enabled") == 0) { curMenuItem->enabled = (strcasecmp(text, "True") == 0 || strcasecmp(text, "-1") == 0 || strcasecmp(text, "False") != 0); }
|
||||
} else if (current) {
|
||||
// Control array index is stored on the struct, not as a widget property
|
||||
if (strcasecmp(key, "Index") == 0) {
|
||||
current->index = atoi(value);
|
||||
|
|
@ -954,6 +1019,62 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
form->contentBox->weight = 100;
|
||||
}
|
||||
|
||||
// Build menu bar from accumulated menu items
|
||||
int32_t menuCount = (int32_t)arrlen(menuItems);
|
||||
|
||||
if (menuCount > 0 && form->window) {
|
||||
MenuBarT *bar = wmAddMenuBar(form->window);
|
||||
|
||||
if (bar) {
|
||||
#define MENU_ID_BASE 10000
|
||||
MenuT *menuStack[16];
|
||||
memset(menuStack, 0, sizeof(menuStack));
|
||||
|
||||
for (int32_t i = 0; i < menuCount; i++) {
|
||||
TempMenuItemT *mi = &menuItems[i];
|
||||
bool isSep = (mi->caption[0] == '-' && (mi->caption[1] == '\0' || mi->caption[1] == '-'));
|
||||
bool isSubParent = (i + 1 < menuCount && menuItems[i + 1].level > mi->level);
|
||||
|
||||
if (mi->level == 0) {
|
||||
// Top-level menu header
|
||||
menuStack[0] = wmAddMenu(bar, mi->caption);
|
||||
} else if (isSep && mi->level > 0 && menuStack[mi->level - 1]) {
|
||||
// Separator
|
||||
wmAddMenuSeparator(menuStack[mi->level - 1]);
|
||||
} else if (isSubParent && mi->level > 0 && menuStack[mi->level - 1]) {
|
||||
// Submenu parent
|
||||
menuStack[mi->level] = wmAddSubMenu(menuStack[mi->level - 1], mi->caption);
|
||||
} else if (mi->level > 0 && menuStack[mi->level - 1]) {
|
||||
// Regular menu item
|
||||
int32_t id = MENU_ID_BASE + i;
|
||||
|
||||
if (mi->checked) {
|
||||
wmAddMenuCheckItem(menuStack[mi->level - 1], mi->caption, id, true);
|
||||
} else {
|
||||
wmAddMenuItem(menuStack[mi->level - 1], mi->caption, id);
|
||||
}
|
||||
|
||||
if (!mi->enabled) {
|
||||
wmMenuItemSetEnabled(bar, id, false);
|
||||
}
|
||||
|
||||
// Store ID-to-name mapping for event dispatch
|
||||
BasMenuIdMapT map;
|
||||
memset(&map, 0, sizeof(map));
|
||||
map.id = id;
|
||||
snprintf(map.name, BAS_MAX_CTRL_NAME, "%s", mi->name);
|
||||
arrput(form->menuIdMap, map);
|
||||
form->menuIdMapCount = (int32_t)arrlen(form->menuIdMap);
|
||||
}
|
||||
}
|
||||
|
||||
form->window->onMenu = onFormMenu;
|
||||
}
|
||||
}
|
||||
|
||||
arrfree(menuItems);
|
||||
menuItems = NULL;
|
||||
|
||||
// Set resizable flag
|
||||
if (form->frmHasResizable) {
|
||||
form->window->resizable = form->frmResizable;
|
||||
|
|
@ -1484,6 +1605,34 @@ static ListBoxItemsT *getListBoxItems(BasControlT *ctrl) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onFormClose
|
||||
// ============================================================
|
||||
// onFormMenu -- dispatch menu clicks as ControlName_Click events
|
||||
// ============================================================
|
||||
|
||||
static void onFormMenu(WindowT *win, int32_t menuId) {
|
||||
if (!sFormRt) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < sFormRt->formCount; i++) {
|
||||
BasFormT *form = &sFormRt->forms[i];
|
||||
|
||||
if (form->window == win) {
|
||||
for (int32_t j = 0; j < form->menuIdMapCount; j++) {
|
||||
if (form->menuIdMap[j].id == menuId) {
|
||||
basFormRtFireEvent(sFormRt, form, form->menuIdMap[j].name, "Click");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onFormClose
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -28,6 +28,15 @@ typedef struct BasControlT BasControlT;
|
|||
|
||||
#define BAS_MAX_CTRL_NAME 32
|
||||
|
||||
// ============================================================
|
||||
// Menu ID to name mapping for event dispatch
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
int32_t id;
|
||||
char name[BAS_MAX_CTRL_NAME];
|
||||
} BasMenuIdMapT;
|
||||
|
||||
// ============================================================
|
||||
// Control instance (a widget on a form)
|
||||
// ============================================================
|
||||
|
|
@ -71,6 +80,9 @@ typedef struct BasFormT {
|
|||
// Per-form variable storage (allocated at load, freed at unload)
|
||||
BasValueT *formVars;
|
||||
int32_t formVarCount;
|
||||
// Menu ID to name mapping (for event dispatch)
|
||||
BasMenuIdMapT *menuIdMap;
|
||||
int32_t menuIdMapCount;
|
||||
} BasFormT;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "ideDesigner.h"
|
||||
#include "dvxDraw.h"
|
||||
#include "dvxVideo.h"
|
||||
#include "dvxWm.h"
|
||||
#include "stb_ds_wrap.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
|
@ -114,6 +115,48 @@ WidgetT *dsgnCreateDesignWidget(const char *vbTypeName, WidgetT *parent) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnBuildPreviewMenuBar
|
||||
// ============================================================
|
||||
|
||||
void dsgnBuildPreviewMenuBar(WindowT *win, const DsgnFormT *form) {
|
||||
if (!win || !form) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t menuCount = (int32_t)arrlen(form->menuItems);
|
||||
|
||||
if (menuCount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MenuBarT *bar = wmAddMenuBar(win);
|
||||
|
||||
if (!bar) {
|
||||
return;
|
||||
}
|
||||
|
||||
MenuT *menuStack[8];
|
||||
memset(menuStack, 0, sizeof(menuStack));
|
||||
|
||||
for (int32_t i = 0; i < menuCount; i++) {
|
||||
const DsgnMenuItemT *mi = &form->menuItems[i];
|
||||
bool isSep = (mi->caption[0] == '-');
|
||||
bool isSubParent = (i + 1 < menuCount && form->menuItems[i + 1].level > mi->level);
|
||||
|
||||
if (mi->level == 0) {
|
||||
menuStack[0] = wmAddMenu(bar, mi->caption);
|
||||
} else if (isSep && mi->level > 0 && mi->level < 8 && menuStack[mi->level - 1]) {
|
||||
wmAddMenuSeparator(menuStack[mi->level - 1]);
|
||||
} else if (isSubParent && mi->level > 0 && mi->level < 8 && menuStack[mi->level - 1]) {
|
||||
menuStack[mi->level] = wmAddSubMenu(menuStack[mi->level - 1], mi->caption);
|
||||
} else if (mi->level > 0 && mi->level < 8 && menuStack[mi->level - 1]) {
|
||||
wmAddMenuItem(menuStack[mi->level - 1], mi->caption, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dsgnAutoName
|
||||
// ============================================================
|
||||
|
|
@ -308,6 +351,7 @@ const char *dsgnDefaultEvent(const char *typeName) {
|
|||
void dsgnFree(DsgnStateT *ds) {
|
||||
if (ds->form) {
|
||||
arrfree(ds->form->controls);
|
||||
arrfree(ds->form->menuItems);
|
||||
free(ds->form->code);
|
||||
free(ds->form);
|
||||
ds->form = NULL;
|
||||
|
|
@ -358,8 +402,11 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
snprintf(form->name, DSGN_MAX_NAME, "Form1");
|
||||
snprintf(form->caption, DSGN_MAX_TEXT, "Form1");
|
||||
|
||||
DsgnControlT *curCtrl = NULL;
|
||||
DsgnControlT *curCtrl = NULL;
|
||||
DsgnMenuItemT *curMenuItem = NULL;
|
||||
bool inForm = false;
|
||||
bool inMenu = false;
|
||||
int32_t menuNestDepth = 0;
|
||||
|
||||
// Parent name stack for nesting (index 0 = form level)
|
||||
char parentStack[8][DSGN_MAX_NAME];
|
||||
|
|
@ -449,6 +496,17 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
inForm = true;
|
||||
nestDepth = 0;
|
||||
curCtrl = NULL;
|
||||
} else if (strcasecmp(typeName, "Menu") == 0 && inForm) {
|
||||
DsgnMenuItemT mi;
|
||||
memset(&mi, 0, sizeof(mi));
|
||||
snprintf(mi.name, DSGN_MAX_NAME, "%s", ctrlName);
|
||||
mi.level = menuNestDepth;
|
||||
mi.enabled = true;
|
||||
arrput(form->menuItems, mi);
|
||||
curMenuItem = &form->menuItems[arrlen(form->menuItems) - 1];
|
||||
curCtrl = NULL; // not a control
|
||||
menuNestDepth++;
|
||||
inMenu = true;
|
||||
} else if (inForm) {
|
||||
DsgnControlT ctrl;
|
||||
memset(&ctrl, 0, sizeof(ctrl));
|
||||
|
|
@ -477,7 +535,15 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
}
|
||||
|
||||
if (strcasecmp(trimmed, "End") == 0) {
|
||||
if (curCtrl) {
|
||||
if (inMenu) {
|
||||
menuNestDepth--;
|
||||
curMenuItem = NULL;
|
||||
|
||||
if (menuNestDepth <= 0) {
|
||||
menuNestDepth = 0;
|
||||
inMenu = false;
|
||||
}
|
||||
} else if (curCtrl) {
|
||||
// If we're closing a container, pop the parent stack
|
||||
if (nestDepth > 0 && strcasecmp(parentStack[nestDepth - 1], curCtrl->name) == 0) {
|
||||
nestDepth--;
|
||||
|
|
@ -552,7 +618,11 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
|
||||
val[vi] = '\0';
|
||||
|
||||
if (curCtrl) {
|
||||
if (curMenuItem) {
|
||||
if (strcasecmp(key, "Caption") == 0) { snprintf(curMenuItem->caption, DSGN_MAX_TEXT, "%s", val); }
|
||||
else if (strcasecmp(key, "Checked") == 0) { curMenuItem->checked = (strcasecmp(val, "True") == 0 || strcasecmp(val, "-1") == 0); }
|
||||
else if (strcasecmp(key, "Enabled") == 0) { curMenuItem->enabled = (strcasecmp(val, "True") == 0 || strcasecmp(val, "-1") == 0); }
|
||||
} else 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, "MinWidth") == 0 ||
|
||||
|
|
@ -1048,6 +1118,71 @@ int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize) {
|
|||
pos += snprintf(buf + pos, bufSize - pos, " Height = %d\n", (int)ds->form->height);
|
||||
}
|
||||
|
||||
// Output menu items as nested Begin Menu blocks
|
||||
{
|
||||
int32_t menuCount = (int32_t)arrlen(ds->form->menuItems);
|
||||
int32_t curLevel = 0;
|
||||
|
||||
for (int32_t i = 0; i < menuCount; i++) {
|
||||
DsgnMenuItemT *mi = &ds->form->menuItems[i];
|
||||
|
||||
// Close blocks back to this item's level
|
||||
while (curLevel > mi->level) {
|
||||
curLevel--;
|
||||
|
||||
for (int32_t p = 0; p < (curLevel + 1) * 4; p++) {
|
||||
buf[pos++] = ' ';
|
||||
}
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, "End\n");
|
||||
}
|
||||
|
||||
// Indent: (level + 1) * 4 spaces (one extra for being inside Form block)
|
||||
for (int32_t p = 0; p < (mi->level + 1) * 4; p++) {
|
||||
buf[pos++] = ' ';
|
||||
}
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, "Begin Menu %s\n", mi->name);
|
||||
|
||||
// Caption
|
||||
for (int32_t p = 0; p < (mi->level + 2) * 4; p++) {
|
||||
buf[pos++] = ' ';
|
||||
}
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, "Caption = \"%s\"\n", mi->caption);
|
||||
|
||||
// Optional properties
|
||||
if (mi->checked) {
|
||||
for (int32_t p = 0; p < (mi->level + 2) * 4; p++) {
|
||||
buf[pos++] = ' ';
|
||||
}
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, "Checked = True\n");
|
||||
}
|
||||
|
||||
if (!mi->enabled) {
|
||||
for (int32_t p = 0; p < (mi->level + 2) * 4; p++) {
|
||||
buf[pos++] = ' ';
|
||||
}
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, "Enabled = False\n");
|
||||
}
|
||||
|
||||
curLevel = mi->level + 1;
|
||||
}
|
||||
|
||||
// Close any remaining open menu blocks
|
||||
while (curLevel > 0) {
|
||||
curLevel--;
|
||||
|
||||
for (int32_t p = 0; p < (curLevel + 1) * 4; p++) {
|
||||
buf[pos++] = ' ';
|
||||
}
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, "End\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Output top-level controls (and recurse into containers)
|
||||
pos = saveControls(ds->form, buf, bufSize, pos, "", 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,18 @@ typedef struct {
|
|||
char value[DSGN_MAX_TEXT];
|
||||
} DsgnPropT;
|
||||
|
||||
// ============================================================
|
||||
// Design-time menu item (flat array with level for tree structure)
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
char caption[DSGN_MAX_TEXT]; // "&File", "-" for separator
|
||||
char name[DSGN_MAX_NAME]; // "mnuFile"
|
||||
int32_t level; // 0 = top-level menu, 1 = item, 2+ = submenu
|
||||
bool checked;
|
||||
bool enabled; // default true
|
||||
} DsgnMenuItemT;
|
||||
|
||||
// ============================================================
|
||||
// Design-time control
|
||||
// ============================================================
|
||||
|
|
@ -72,6 +84,7 @@ typedef struct {
|
|||
bool autoSize; // true = dvxFitWindow, false = use width/height
|
||||
bool resizable; // true = user can resize at runtime
|
||||
DsgnControlT *controls; // stb_ds dynamic array
|
||||
DsgnMenuItemT *menuItems; // stb_ds dynamic array (NULL if no menus)
|
||||
bool dirty;
|
||||
WidgetT *contentBox; // VBox parent for live widgets
|
||||
char *code; // BASIC code section (malloc'd, after End block)
|
||||
|
|
@ -180,6 +193,10 @@ void dsgnFree(DsgnStateT *ds);
|
|||
// Create a live design-time widget for a given VB type name.
|
||||
WidgetT *dsgnCreateDesignWidget(const char *vbTypeName, WidgetT *parent);
|
||||
|
||||
// Build a display-only menu bar on a window from the form's menuItems.
|
||||
// Used in the form designer to preview the menu layout.
|
||||
void dsgnBuildPreviewMenuBar(WindowT *win, const DsgnFormT *form);
|
||||
|
||||
// ============================================================
|
||||
// Code rename support (implemented in ideMain.c)
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
#include "ideDesigner.h"
|
||||
#include "ideProject.h"
|
||||
#include "ideMenuEditor.h"
|
||||
#include "ideToolbox.h"
|
||||
#include "ideProperties.h"
|
||||
|
||||
|
|
@ -92,6 +93,7 @@
|
|||
#define CMD_FIND 141
|
||||
#define CMD_REPLACE 142
|
||||
#define CMD_FIND_NEXT 143
|
||||
#define CMD_MENU_EDITOR 144
|
||||
#define IDE_MAX_IMM 1024
|
||||
#define IDE_DESIGN_W 400
|
||||
#define IDE_DESIGN_H 300
|
||||
|
|
@ -582,6 +584,8 @@ static void buildWindow(void) {
|
|||
wmAddMenuSeparator(viewMenu);
|
||||
wmAddMenuCheckItem(viewMenu, "&Toolbar", CMD_VIEW_TOOLBAR, true);
|
||||
wmAddMenuCheckItem(viewMenu, "&Status Bar", CMD_VIEW_STATUS, true);
|
||||
wmAddMenuSeparator(viewMenu);
|
||||
wmAddMenuItem(viewMenu, "&Menu Editor...\tCtrl+E", CMD_MENU_EDITOR);
|
||||
|
||||
MenuT *winMenu = wmAddMenu(menuBar, "&Window");
|
||||
wmAddMenuItem(winMenu, "&Code Editor", CMD_WIN_CODE);
|
||||
|
|
@ -606,6 +610,7 @@ static void buildWindow(void) {
|
|||
dvxAddAccel(accel, 'F', ACCEL_CTRL, CMD_FIND);
|
||||
dvxAddAccel(accel, 'H', ACCEL_CTRL, CMD_REPLACE);
|
||||
dvxAddAccel(accel, KEY_F3, 0, CMD_FIND_NEXT);
|
||||
dvxAddAccel(accel, 'E', ACCEL_CTRL, CMD_MENU_EDITOR);
|
||||
sWin->accelTable = accel;
|
||||
|
||||
WidgetT *tbRoot = wgtInitWindow(sAc, sWin);
|
||||
|
|
@ -2980,6 +2985,50 @@ static void handleViewCmd(int32_t cmd) {
|
|||
prefsSave(sPrefs);
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_MENU_EDITOR:
|
||||
if (sDesigner.form) {
|
||||
// Snapshot old menu names for rename detection
|
||||
char **oldNames = NULL;
|
||||
int32_t oldCount = (int32_t)arrlen(sDesigner.form->menuItems);
|
||||
|
||||
for (int32_t mi = 0; mi < oldCount; mi++) {
|
||||
arrput(oldNames, strdup(sDesigner.form->menuItems[mi].name));
|
||||
}
|
||||
|
||||
if (mnuEditorDialog(sAc, sDesigner.form)) {
|
||||
sDesigner.form->dirty = true;
|
||||
|
||||
// Detect renames: match by position (items may have been
|
||||
// reordered, but renamed items keep their index)
|
||||
int32_t newCount = (int32_t)arrlen(sDesigner.form->menuItems);
|
||||
int32_t minCount = oldCount < newCount ? oldCount : newCount;
|
||||
|
||||
for (int32_t mi = 0; mi < minCount; mi++) {
|
||||
if (oldNames[mi][0] && sDesigner.form->menuItems[mi].name[0] &&
|
||||
strcasecmp(oldNames[mi], sDesigner.form->menuItems[mi].name) != 0) {
|
||||
ideRenameInCode(oldNames[mi], sDesigner.form->menuItems[mi].name);
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild menu bar preview
|
||||
if (sFormWin) {
|
||||
wmDestroyMenuBar(sFormWin);
|
||||
dsgnBuildPreviewMenuBar(sFormWin, sDesigner.form);
|
||||
dvxInvalidateWindow(sAc, sFormWin);
|
||||
}
|
||||
|
||||
// Rebuild Object dropdown to reflect added/removed/renamed items
|
||||
updateDropdowns();
|
||||
}
|
||||
|
||||
for (int32_t mi = 0; mi < oldCount; mi++) {
|
||||
free(oldNames[mi]);
|
||||
}
|
||||
|
||||
arrfree(oldNames);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3383,10 +3432,27 @@ static void onObjDropdownChange(WidgetT *w) {
|
|||
availEvents = sFormEvents;
|
||||
}
|
||||
|
||||
// Check if this is a menu item (only event is Click)
|
||||
bool isMenuItem = false;
|
||||
|
||||
if (!isForm && sDesigner.form) {
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(sDesigner.form->menuItems); i++) {
|
||||
if (strcasecmp(sDesigner.form->menuItems[i].name, selObj) == 0) {
|
||||
isMenuItem = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMenuItem) {
|
||||
static const char *sMenuEvents[] = { "Click", NULL };
|
||||
availEvents = sMenuEvents;
|
||||
}
|
||||
|
||||
// Get widget-specific events from the interface
|
||||
const WgtIfaceT *iface = NULL;
|
||||
|
||||
if (!isForm && sDesigner.form) {
|
||||
if (!isForm && !isMenuItem && sDesigner.form) {
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(sDesigner.form->controls); i++) {
|
||||
if (strcasecmp(sDesigner.form->controls[i].name, selObj) == 0) {
|
||||
const char *wgtName = wgtFindByBasName(sDesigner.form->controls[i].typeName);
|
||||
|
|
@ -4376,6 +4442,9 @@ static void switchToDesign(void) {
|
|||
sFormWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||
sDesigner.formWin = sFormWin;
|
||||
|
||||
// Build preview menu bar from form's menu items
|
||||
dsgnBuildPreviewMenuBar(sFormWin, sDesigner.form);
|
||||
|
||||
WidgetT *root = wgtInitWindow(sAc, sFormWin);
|
||||
WidgetT *contentBox;
|
||||
|
||||
|
|
@ -4636,6 +4705,8 @@ static void updateProjectMenuState(void) {
|
|||
}
|
||||
|
||||
bool hasProject = (sProject.projectPath[0] != '\0');
|
||||
bool hasFile = (hasProject && sProject.activeFileIdx >= 0);
|
||||
bool hasForm = (hasFile && sProject.files[sProject.activeFileIdx].isForm);
|
||||
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_PRJ_SAVE, hasProject);
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_PRJ_CLOSE, hasProject);
|
||||
|
|
@ -4644,9 +4715,13 @@ static void updateProjectMenuState(void) {
|
|||
wmMenuItemSetEnabled(sWin->menuBar, CMD_SAVE, hasProject);
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_SAVE_ALL, hasProject);
|
||||
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_FIND, hasProject);
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_FIND, hasProject);
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_FIND_NEXT, hasProject);
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_REPLACE, hasProject);
|
||||
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_VIEW_CODE, hasFile);
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_VIEW_DESIGN, hasForm);
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_MENU_EDITOR, hasForm);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -5467,19 +5542,42 @@ static void updateDropdowns(void) {
|
|||
memset(&entry, 0, sizeof(entry));
|
||||
entry.lineNum = lineNum;
|
||||
|
||||
char *underscore = strchr(procName, '_');
|
||||
// Match proc name against known objects: form name,
|
||||
// controls, menu items. Try each as a prefix followed
|
||||
// by "_". This handles names with multiple underscores
|
||||
// correctly (e.g., "cmdOK_Click" matches "cmdOK", not
|
||||
// "This_Is_A_Dumb_Name" matching a control named "This").
|
||||
bool isEvent = false;
|
||||
|
||||
if (underscore) {
|
||||
int32_t objLen = (int32_t)(underscore - procName);
|
||||
if (sDesigner.form) {
|
||||
// Collect all known object names
|
||||
const char *objNames[512];
|
||||
int32_t objNameCount = 0;
|
||||
|
||||
if (objLen > 63) {
|
||||
objLen = 63;
|
||||
objNames[objNameCount++] = sDesigner.form->name;
|
||||
|
||||
for (int32_t ci = 0; ci < (int32_t)arrlen(sDesigner.form->controls) && objNameCount < 511; ci++) {
|
||||
objNames[objNameCount++] = sDesigner.form->controls[ci].name;
|
||||
}
|
||||
|
||||
memcpy(entry.objName, procName, objLen);
|
||||
entry.objName[objLen] = '\0';
|
||||
snprintf(entry.evtName, sizeof(entry.evtName), "%s", underscore + 1);
|
||||
} else {
|
||||
for (int32_t mi = 0; mi < (int32_t)arrlen(sDesigner.form->menuItems) && objNameCount < 511; mi++) {
|
||||
objNames[objNameCount++] = sDesigner.form->menuItems[mi].name;
|
||||
}
|
||||
|
||||
// Try each object name as prefix + "_"
|
||||
for (int32_t oi = 0; oi < objNameCount; oi++) {
|
||||
int32_t nameLen = (int32_t)strlen(objNames[oi]);
|
||||
|
||||
if (nameLen > 0 && strncasecmp(procName, objNames[oi], nameLen) == 0 && procName[nameLen] == '_') {
|
||||
snprintf(entry.objName, sizeof(entry.objName), "%s", objNames[oi]);
|
||||
snprintf(entry.evtName, sizeof(entry.evtName), "%s", procName + nameLen + 1);
|
||||
isEvent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEvent) {
|
||||
snprintf(entry.objName, sizeof(entry.objName), "%s", "(General)");
|
||||
snprintf(entry.evtName, sizeof(entry.evtName), "%s", procName);
|
||||
}
|
||||
|
|
@ -5522,25 +5620,15 @@ static void updateDropdowns(void) {
|
|||
for (int32_t i = 0; i < (int32_t)arrlen(sDesigner.form->controls); i++) {
|
||||
arrput(sObjItems, sDesigner.form->controls[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
// Add any objects from existing procs not already in the list
|
||||
int32_t procCount = (int32_t)arrlen(sProcTable);
|
||||
// Add menu item names (non-separator, non-top-level-header-only)
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(sDesigner.form->menuItems); i++) {
|
||||
DsgnMenuItemT *mi = &sDesigner.form->menuItems[i];
|
||||
|
||||
for (int32_t i = 0; i < procCount; i++) {
|
||||
bool found = false;
|
||||
int32_t objCount = (int32_t)arrlen(sObjItems);
|
||||
|
||||
for (int32_t j = 0; j < objCount; j++) {
|
||||
if (strcasecmp(sObjItems[j], sProcTable[i].objName) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
if (mi->name[0] && mi->caption[0] != '-') {
|
||||
arrput(sObjItems, mi->name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
arrput(sObjItems, sProcTable[i].objName);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort object items alphabetically, keeping (General) first
|
||||
|
|
|
|||
741
apps/dvxbasic/ide/ideMenuEditor.c
Normal file
741
apps/dvxbasic/ide/ideMenuEditor.c
Normal file
|
|
@ -0,0 +1,741 @@
|
|||
// ideMenuEditor.c -- DVX BASIC menu editor dialog
|
||||
//
|
||||
// VB3-style modal dialog for designing form menu bars. Works on
|
||||
// a cloned copy of the menu item array; changes are only applied
|
||||
// when the user clicks OK.
|
||||
|
||||
#include "ideMenuEditor.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxWm.h"
|
||||
#include "widgetBox.h"
|
||||
#include "widgetButton.h"
|
||||
#include "widgetCheckbox.h"
|
||||
#include "widgetLabel.h"
|
||||
#include "widgetListBox.h"
|
||||
#include "widgetTextInput.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
// ============================================================
|
||||
|
||||
#define MAX_MENU_ITEMS 128
|
||||
#define MAX_MENU_LEVEL 5
|
||||
#define ARROW_STR "-> "
|
||||
|
||||
// ============================================================
|
||||
// Dialog state
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
bool done;
|
||||
bool accepted;
|
||||
AppContextT *ctx;
|
||||
DsgnFormT *form;
|
||||
|
||||
// Working copy of menu items
|
||||
DsgnMenuItemT *items; // stb_ds array
|
||||
int32_t selectedIdx;
|
||||
|
||||
bool nameAutoGen; // true = name was auto-generated, update on caption change
|
||||
|
||||
// Widgets
|
||||
WidgetT *captionInput;
|
||||
WidgetT *nameInput;
|
||||
WidgetT *checkedCb;
|
||||
WidgetT *enabledCb;
|
||||
WidgetT *listBox;
|
||||
} MnuEdStateT;
|
||||
|
||||
static MnuEdStateT sMed;
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
static void applyFields(void);
|
||||
static int32_t findSubtreeEnd(int32_t idx);
|
||||
static void loadFields(void);
|
||||
static void onCancel(WidgetT *w);
|
||||
static void onCaptionChange(WidgetT *w);
|
||||
static void onDelete(WidgetT *w);
|
||||
static void onNameChange(WidgetT *w);
|
||||
static void onIndent(WidgetT *w);
|
||||
static void onInsert(WidgetT *w);
|
||||
static void onListClick(WidgetT *w);
|
||||
static void onMoveDown(WidgetT *w);
|
||||
static void onMoveUp(WidgetT *w);
|
||||
static void onNext(WidgetT *w);
|
||||
static void onOk(WidgetT *w);
|
||||
static void onOutdent(WidgetT *w);
|
||||
static void rebuildList(void);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// applyFields -- save current widget values to selected item
|
||||
// ============================================================
|
||||
|
||||
static void applyFields(void) {
|
||||
int32_t count = (int32_t)arrlen(sMed.items);
|
||||
|
||||
if (sMed.selectedIdx < 0 || sMed.selectedIdx >= count) {
|
||||
return;
|
||||
}
|
||||
|
||||
DsgnMenuItemT *mi = &sMed.items[sMed.selectedIdx];
|
||||
const char *cap = wgtGetText(sMed.captionInput);
|
||||
const char *nam = wgtGetText(sMed.nameInput);
|
||||
|
||||
if (cap) {
|
||||
snprintf(mi->caption, DSGN_MAX_TEXT, "%s", cap);
|
||||
}
|
||||
|
||||
if (nam) {
|
||||
snprintf(mi->name, DSGN_MAX_NAME, "%s", nam);
|
||||
}
|
||||
|
||||
// Auto-generate name from caption if name is empty or was auto-generated
|
||||
if ((mi->name[0] == '\0' || sMed.nameAutoGen) && mi->caption[0] != '\0') {
|
||||
char autoName[DSGN_MAX_NAME];
|
||||
|
||||
if (mi->caption[0] == '-') {
|
||||
// Separator: generate mnuSep1, mnuSep2, etc.
|
||||
int32_t sepNum = 1;
|
||||
int32_t itemCount = (int32_t)arrlen(sMed.items);
|
||||
|
||||
for (int32_t i = 0; i < itemCount; i++) {
|
||||
if (i != sMed.selectedIdx && strncasecmp(sMed.items[i].name, "mnuSep", 6) == 0) {
|
||||
int32_t n = atoi(sMed.items[i].name + 6);
|
||||
|
||||
if (n >= sepNum) {
|
||||
sepNum = n + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(autoName, DSGN_MAX_NAME, "mnuSep%d", sepNum);
|
||||
} else {
|
||||
// Normal item: strip & and non-alphanumeric, prefix "mnu"
|
||||
int32_t p = 0;
|
||||
autoName[p++] = 'm';
|
||||
autoName[p++] = 'n';
|
||||
autoName[p++] = 'u';
|
||||
|
||||
for (const char *c = mi->caption; *c && p < DSGN_MAX_NAME - 1; c++) {
|
||||
if (*c == '&') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z') || (*c >= '0' && *c <= '9')) {
|
||||
autoName[p++] = *c;
|
||||
}
|
||||
}
|
||||
|
||||
autoName[p] = '\0';
|
||||
}
|
||||
|
||||
snprintf(mi->name, DSGN_MAX_NAME, "%s", autoName);
|
||||
wgtSetText(sMed.nameInput, mi->name);
|
||||
sMed.nameAutoGen = true;
|
||||
}
|
||||
|
||||
mi->checked = wgtCheckboxIsChecked(sMed.checkedCb);
|
||||
mi->enabled = wgtCheckboxIsChecked(sMed.enabledCb);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// findSubtreeEnd -- index past the last child of items[idx]
|
||||
// ============================================================
|
||||
|
||||
static int32_t findSubtreeEnd(int32_t idx) {
|
||||
int32_t count = (int32_t)arrlen(sMed.items);
|
||||
int32_t level = sMed.items[idx].level;
|
||||
int32_t end = idx + 1;
|
||||
|
||||
while (end < count && sMed.items[end].level > level) {
|
||||
end++;
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// loadFields -- populate widgets from selected item
|
||||
// ============================================================
|
||||
|
||||
static void loadFields(void) {
|
||||
int32_t count = (int32_t)arrlen(sMed.items);
|
||||
|
||||
// Reset auto-gen flag — if the loaded item has an empty name, it's
|
||||
// eligible for auto-generation; if it has a name, the user set it.
|
||||
sMed.nameAutoGen = false;
|
||||
|
||||
if (sMed.selectedIdx < 0 || sMed.selectedIdx >= count) {
|
||||
wgtSetText(sMed.captionInput, "");
|
||||
wgtSetText(sMed.nameInput, "");
|
||||
wgtCheckboxSetChecked(sMed.checkedCb, false);
|
||||
wgtCheckboxSetChecked(sMed.enabledCb, true);
|
||||
sMed.nameAutoGen = true; // new blank item — auto-gen eligible
|
||||
return;
|
||||
}
|
||||
|
||||
DsgnMenuItemT *mi = &sMed.items[sMed.selectedIdx];
|
||||
sMed.nameAutoGen = (mi->name[0] == '\0');
|
||||
wgtSetText(sMed.captionInput, mi->caption);
|
||||
wgtSetText(sMed.nameInput, mi->name);
|
||||
wgtCheckboxSetChecked(sMed.checkedCb, mi->checked);
|
||||
wgtCheckboxSetChecked(sMed.enabledCb, mi->enabled);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onCancel
|
||||
// ============================================================
|
||||
|
||||
static void onCancel(WidgetT *w) {
|
||||
(void)w;
|
||||
sMed.accepted = false;
|
||||
sMed.done = true;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onCaptionChange -- caption field changed, auto-update name
|
||||
// ============================================================
|
||||
|
||||
static void onCaptionChange(WidgetT *w) {
|
||||
(void)w;
|
||||
applyFields();
|
||||
rebuildList();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onNameChange -- user manually edited the name field
|
||||
// ============================================================
|
||||
|
||||
static void onNameChange(WidgetT *w) {
|
||||
(void)w;
|
||||
sMed.nameAutoGen = false; // user took over
|
||||
applyFields();
|
||||
rebuildList();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onDelete
|
||||
// ============================================================
|
||||
|
||||
static void onDelete(WidgetT *w) {
|
||||
(void)w;
|
||||
int32_t count = (int32_t)arrlen(sMed.items);
|
||||
|
||||
if (sMed.selectedIdx < 0 || sMed.selectedIdx >= count) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyFields();
|
||||
|
||||
// Delete selected item and its children
|
||||
int32_t subEnd = findSubtreeEnd(sMed.selectedIdx);
|
||||
|
||||
for (int32_t i = subEnd - 1; i >= sMed.selectedIdx; i--) {
|
||||
arrdel(sMed.items, i);
|
||||
}
|
||||
|
||||
count = (int32_t)arrlen(sMed.items);
|
||||
|
||||
if (sMed.selectedIdx >= count) {
|
||||
sMed.selectedIdx = count - 1;
|
||||
}
|
||||
|
||||
rebuildList();
|
||||
loadFields();
|
||||
wgtSetFocused(sMed.captionInput);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onIndent -- increase level (make child of previous item)
|
||||
// ============================================================
|
||||
|
||||
static void onIndent(WidgetT *w) {
|
||||
(void)w;
|
||||
int32_t count = (int32_t)arrlen(sMed.items);
|
||||
int32_t idx = sMed.selectedIdx;
|
||||
|
||||
if (idx <= 0 || idx >= count) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyFields();
|
||||
|
||||
// Can only indent if previous item is at same or higher level
|
||||
int32_t prevLevel = sMed.items[idx - 1].level;
|
||||
|
||||
if (sMed.items[idx].level > prevLevel) {
|
||||
return; // already deeper than prev
|
||||
}
|
||||
|
||||
if (sMed.items[idx].level >= MAX_MENU_LEVEL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Indent this item and all its children
|
||||
int32_t subEnd = findSubtreeEnd(idx);
|
||||
|
||||
for (int32_t i = idx; i < subEnd; i++) {
|
||||
sMed.items[i].level++;
|
||||
}
|
||||
|
||||
rebuildList();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onInsert
|
||||
// ============================================================
|
||||
|
||||
static void onInsert(WidgetT *w) {
|
||||
(void)w;
|
||||
int32_t count = (int32_t)arrlen(sMed.items);
|
||||
|
||||
if (count >= MAX_MENU_ITEMS) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyFields();
|
||||
|
||||
DsgnMenuItemT mi;
|
||||
memset(&mi, 0, sizeof(mi));
|
||||
mi.enabled = true;
|
||||
|
||||
// Insert after the current item's subtree, at the same level
|
||||
int32_t insertAt;
|
||||
|
||||
if (sMed.selectedIdx >= 0 && sMed.selectedIdx < count) {
|
||||
mi.level = sMed.items[sMed.selectedIdx].level;
|
||||
insertAt = findSubtreeEnd(sMed.selectedIdx);
|
||||
} else {
|
||||
insertAt = count;
|
||||
}
|
||||
|
||||
arrins(sMed.items, insertAt, mi);
|
||||
sMed.selectedIdx = insertAt;
|
||||
rebuildList();
|
||||
loadFields();
|
||||
wgtSetFocused(sMed.captionInput);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onListClick
|
||||
// ============================================================
|
||||
|
||||
static void onListClick(WidgetT *w) {
|
||||
int32_t prev = sMed.selectedIdx;
|
||||
sMed.selectedIdx = wgtListBoxGetSelected(w);
|
||||
|
||||
if (prev != sMed.selectedIdx && prev >= 0 && prev < (int32_t)arrlen(sMed.items)) {
|
||||
// Save fields from previously selected item
|
||||
int32_t saveSel = sMed.selectedIdx;
|
||||
sMed.selectedIdx = prev;
|
||||
applyFields();
|
||||
sMed.selectedIdx = saveSel;
|
||||
}
|
||||
|
||||
loadFields();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onMoveDown
|
||||
// ============================================================
|
||||
|
||||
static void onMoveDown(WidgetT *w) {
|
||||
(void)w;
|
||||
int32_t count = (int32_t)arrlen(sMed.items);
|
||||
int32_t idx = sMed.selectedIdx;
|
||||
|
||||
if (idx < 0 || idx >= count) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyFields();
|
||||
|
||||
int32_t subEnd = findSubtreeEnd(idx);
|
||||
|
||||
if (subEnd >= count) {
|
||||
return; // already at bottom
|
||||
}
|
||||
|
||||
// The item/subtree after us
|
||||
int32_t nextEnd = findSubtreeEnd(subEnd);
|
||||
|
||||
// Rotate: move the next subtree before our subtree
|
||||
// Simple approach: extract our subtree, delete it, insert after next subtree
|
||||
int32_t subSize = subEnd - idx;
|
||||
DsgnMenuItemT *tmp = (DsgnMenuItemT *)malloc(subSize * sizeof(DsgnMenuItemT));
|
||||
memcpy(tmp, &sMed.items[idx], subSize * sizeof(DsgnMenuItemT));
|
||||
|
||||
// Delete our subtree
|
||||
for (int32_t i = subEnd - 1; i >= idx; i--) {
|
||||
arrdel(sMed.items, i);
|
||||
}
|
||||
|
||||
// Insert after what was the next subtree (now shifted)
|
||||
int32_t insertAt = nextEnd - subSize;
|
||||
|
||||
for (int32_t i = 0; i < subSize; i++) {
|
||||
arrins(sMed.items, insertAt + i, tmp[i]);
|
||||
}
|
||||
|
||||
free(tmp);
|
||||
sMed.selectedIdx = insertAt;
|
||||
rebuildList();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onMoveUp
|
||||
// ============================================================
|
||||
|
||||
static void onMoveUp(WidgetT *w) {
|
||||
(void)w;
|
||||
int32_t idx = sMed.selectedIdx;
|
||||
|
||||
if (idx <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyFields();
|
||||
|
||||
// Find the start of the previous subtree at the same or lower level
|
||||
int32_t prevIdx = idx - 1;
|
||||
|
||||
while (prevIdx > 0 && sMed.items[prevIdx].level > sMed.items[idx].level) {
|
||||
prevIdx--;
|
||||
}
|
||||
|
||||
// Move our subtree before the previous item's subtree
|
||||
int32_t subEnd = findSubtreeEnd(idx);
|
||||
int32_t subSize = subEnd - idx;
|
||||
DsgnMenuItemT *tmp = (DsgnMenuItemT *)malloc(subSize * sizeof(DsgnMenuItemT));
|
||||
memcpy(tmp, &sMed.items[idx], subSize * sizeof(DsgnMenuItemT));
|
||||
|
||||
// Delete our subtree
|
||||
for (int32_t i = subEnd - 1; i >= idx; i--) {
|
||||
arrdel(sMed.items, i);
|
||||
}
|
||||
|
||||
// Insert at prevIdx
|
||||
for (int32_t i = 0; i < subSize; i++) {
|
||||
arrins(sMed.items, prevIdx + i, tmp[i]);
|
||||
}
|
||||
|
||||
free(tmp);
|
||||
sMed.selectedIdx = prevIdx;
|
||||
rebuildList();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onNext
|
||||
// ============================================================
|
||||
|
||||
static void onNext(WidgetT *w) {
|
||||
(void)w;
|
||||
int32_t count = (int32_t)arrlen(sMed.items);
|
||||
|
||||
applyFields();
|
||||
|
||||
if (sMed.selectedIdx < count - 1) {
|
||||
sMed.selectedIdx++;
|
||||
} else {
|
||||
// Append new item
|
||||
if (count >= MAX_MENU_ITEMS) {
|
||||
return;
|
||||
}
|
||||
|
||||
DsgnMenuItemT mi;
|
||||
memset(&mi, 0, sizeof(mi));
|
||||
mi.enabled = true;
|
||||
|
||||
if (count > 0) {
|
||||
mi.level = sMed.items[count - 1].level;
|
||||
}
|
||||
|
||||
arrput(sMed.items, mi);
|
||||
sMed.selectedIdx = (int32_t)arrlen(sMed.items) - 1;
|
||||
}
|
||||
|
||||
rebuildList();
|
||||
loadFields();
|
||||
wgtSetFocused(sMed.captionInput);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onOk
|
||||
// ============================================================
|
||||
|
||||
static void onOk(WidgetT *w) {
|
||||
(void)w;
|
||||
applyFields();
|
||||
|
||||
// Strip blank items (no caption and no name)
|
||||
for (int32_t i = (int32_t)arrlen(sMed.items) - 1; i >= 0; i--) {
|
||||
if (sMed.items[i].caption[0] == '\0' && sMed.items[i].name[0] == '\0') {
|
||||
arrdel(sMed.items, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate: check names are non-empty and unique
|
||||
int32_t count = (int32_t)arrlen(sMed.items);
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
if (sMed.items[i].name[0] == '\0') {
|
||||
dvxMessageBox(sMed.ctx, "Menu Editor", "All menu items must have a Name.", MB_OK | MB_ICONERROR);
|
||||
sMed.selectedIdx = i;
|
||||
rebuildList();
|
||||
loadFields();
|
||||
wgtSetFocused(sMed.captionInput);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t j = i + 1; j < count; j++) {
|
||||
if (strcasecmp(sMed.items[i].name, sMed.items[j].name) == 0) {
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof(msg), "Duplicate menu name: %s", sMed.items[i].name);
|
||||
dvxMessageBox(sMed.ctx, "Menu Editor", msg, MB_OK | MB_ICONERROR);
|
||||
sMed.selectedIdx = j;
|
||||
rebuildList();
|
||||
loadFields();
|
||||
wgtSetFocused(sMed.captionInput);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy working items back to form
|
||||
arrfree(sMed.form->menuItems);
|
||||
sMed.form->menuItems = NULL;
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
arrput(sMed.form->menuItems, sMed.items[i]);
|
||||
}
|
||||
|
||||
sMed.accepted = true;
|
||||
sMed.done = true;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onOutdent -- decrease level
|
||||
// ============================================================
|
||||
|
||||
static void onOutdent(WidgetT *w) {
|
||||
(void)w;
|
||||
int32_t count = (int32_t)arrlen(sMed.items);
|
||||
int32_t idx = sMed.selectedIdx;
|
||||
|
||||
if (idx < 0 || idx >= count) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sMed.items[idx].level <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyFields();
|
||||
|
||||
int32_t subEnd = findSubtreeEnd(idx);
|
||||
|
||||
for (int32_t i = idx; i < subEnd; i++) {
|
||||
sMed.items[i].level--;
|
||||
}
|
||||
|
||||
rebuildList();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// rebuildList -- refresh the listbox display
|
||||
// ============================================================
|
||||
|
||||
static void rebuildList(void) {
|
||||
int32_t count = (int32_t)arrlen(sMed.items);
|
||||
|
||||
// Build display strings
|
||||
static const char *sLabels[MAX_MENU_ITEMS];
|
||||
static char sLabelBufs[MAX_MENU_ITEMS][DSGN_MAX_TEXT + 32];
|
||||
|
||||
for (int32_t i = 0; i < count && i < MAX_MENU_ITEMS; i++) {
|
||||
int32_t pos = 0;
|
||||
|
||||
for (int32_t lv = 0; lv < sMed.items[i].level; lv++) {
|
||||
pos += snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "%s", ARROW_STR);
|
||||
}
|
||||
|
||||
const char *cap = sMed.items[i].caption;
|
||||
|
||||
if (cap[0] == '-') {
|
||||
snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "--------");
|
||||
} else if (cap[0]) {
|
||||
snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "%s", cap);
|
||||
} else {
|
||||
snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "(%s)", sMed.items[i].name[0] ? sMed.items[i].name : "untitled");
|
||||
}
|
||||
|
||||
sLabels[i] = sLabelBufs[i];
|
||||
}
|
||||
|
||||
wgtListBoxSetItems(sMed.listBox, sLabels, count < MAX_MENU_ITEMS ? count : MAX_MENU_ITEMS);
|
||||
wgtListBoxSetSelected(sMed.listBox, sMed.selectedIdx);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// mnuEditorDialog
|
||||
// ============================================================
|
||||
|
||||
bool mnuEditorDialog(AppContextT *ctx, DsgnFormT *form) {
|
||||
memset(&sMed, 0, sizeof(sMed));
|
||||
sMed.ctx = ctx;
|
||||
sMed.form = form;
|
||||
sMed.selectedIdx = 0;
|
||||
|
||||
// Clone menu items as working copy
|
||||
int32_t count = (int32_t)arrlen(form->menuItems);
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
arrput(sMed.items, form->menuItems[i]);
|
||||
}
|
||||
|
||||
// If empty, start with one blank item so the user can type immediately
|
||||
if (arrlen(sMed.items) == 0) {
|
||||
DsgnMenuItemT mi;
|
||||
memset(&mi, 0, sizeof(mi));
|
||||
mi.enabled = true;
|
||||
arrput(sMed.items, mi);
|
||||
}
|
||||
|
||||
// Create modal dialog
|
||||
WindowT *win = dvxCreateWindowCentered(ctx, "Menu Editor", 360, 420, false);
|
||||
|
||||
if (!win) {
|
||||
arrfree(sMed.items);
|
||||
return false;
|
||||
}
|
||||
|
||||
win->maxW = win->w;
|
||||
win->maxH = win->h;
|
||||
|
||||
WidgetT *root = wgtInitWindow(ctx, win);
|
||||
root->spacing = wgtPixels(4);
|
||||
|
||||
// Caption row
|
||||
WidgetT *capRow = wgtHBox(root);
|
||||
capRow->spacing = wgtPixels(4);
|
||||
WidgetT *capLbl = wgtLabel(capRow, "Caption:");
|
||||
capLbl->minW = wgtPixels(60);
|
||||
sMed.captionInput = wgtTextInput(capRow, DSGN_MAX_TEXT);
|
||||
sMed.captionInput->weight = 100;
|
||||
sMed.captionInput->onChange = onCaptionChange;
|
||||
|
||||
// Name row
|
||||
WidgetT *namRow = wgtHBox(root);
|
||||
namRow->spacing = wgtPixels(4);
|
||||
WidgetT *namLbl = wgtLabel(namRow, "Name:");
|
||||
namLbl->minW = wgtPixels(60);
|
||||
sMed.nameInput = wgtTextInput(namRow, DSGN_MAX_NAME);
|
||||
sMed.nameInput->weight = 100;
|
||||
sMed.nameInput->onChange = onNameChange;
|
||||
|
||||
// Check row
|
||||
WidgetT *chkRow = wgtHBox(root);
|
||||
chkRow->spacing = wgtPixels(12);
|
||||
sMed.checkedCb = wgtCheckbox(chkRow, "Checked");
|
||||
sMed.enabledCb = wgtCheckbox(chkRow, "Enabled");
|
||||
wgtCheckboxSetChecked(sMed.enabledCb, true);
|
||||
|
||||
// Listbox
|
||||
sMed.listBox = wgtListBox(root);
|
||||
sMed.listBox->weight = 100;
|
||||
sMed.listBox->onClick = onListClick;
|
||||
|
||||
// Arrow buttons
|
||||
WidgetT *arrowRow = wgtHBox(root);
|
||||
arrowRow->spacing = wgtPixels(4);
|
||||
|
||||
WidgetT *btnOut = wgtButton(arrowRow, "<-");
|
||||
btnOut->onClick = onOutdent;
|
||||
btnOut->minW = wgtPixels(32);
|
||||
|
||||
WidgetT *btnIn = wgtButton(arrowRow, "->");
|
||||
btnIn->onClick = onIndent;
|
||||
btnIn->minW = wgtPixels(32);
|
||||
|
||||
WidgetT *btnUp = wgtButton(arrowRow, "Up");
|
||||
btnUp->onClick = onMoveUp;
|
||||
btnUp->minW = wgtPixels(32);
|
||||
|
||||
WidgetT *btnDn = wgtButton(arrowRow, "Dn");
|
||||
btnDn->onClick = onMoveDown;
|
||||
btnDn->minW = wgtPixels(32);
|
||||
|
||||
// Action buttons
|
||||
WidgetT *actRow = wgtHBox(root);
|
||||
actRow->spacing = wgtPixels(4);
|
||||
|
||||
WidgetT *btnNext = wgtButton(actRow, "&Next");
|
||||
btnNext->onClick = onNext;
|
||||
|
||||
WidgetT *btnIns = wgtButton(actRow, "&Insert");
|
||||
btnIns->onClick = onInsert;
|
||||
|
||||
WidgetT *btnDel = wgtButton(actRow, "&Delete");
|
||||
btnDel->onClick = onDelete;
|
||||
|
||||
// OK / Cancel
|
||||
WidgetT *okRow = wgtHBox(root);
|
||||
okRow->spacing = wgtPixels(8);
|
||||
|
||||
WidgetT *btnOk = wgtButton(okRow, "OK");
|
||||
btnOk->onClick = onOk;
|
||||
btnOk->minW = wgtPixels(60);
|
||||
|
||||
WidgetT *btnCancel = wgtButton(okRow, "Cancel");
|
||||
btnCancel->onClick = onCancel;
|
||||
btnCancel->minW = wgtPixels(60);
|
||||
|
||||
// Populate
|
||||
rebuildList();
|
||||
loadFields();
|
||||
wgtSetFocused(sMed.captionInput);
|
||||
|
||||
dvxFitWindow(ctx, win);
|
||||
|
||||
// Modal loop
|
||||
WindowT *prevModal = ctx->modalWindow;
|
||||
ctx->modalWindow = win;
|
||||
|
||||
while (!sMed.done && ctx->running) {
|
||||
dvxUpdate(ctx);
|
||||
}
|
||||
|
||||
ctx->modalWindow = prevModal;
|
||||
dvxDestroyWindow(ctx, win);
|
||||
|
||||
// Cleanup working copy
|
||||
arrfree(sMed.items);
|
||||
sMed.items = NULL;
|
||||
|
||||
return sMed.accepted;
|
||||
}
|
||||
16
apps/dvxbasic/ide/ideMenuEditor.h
Normal file
16
apps/dvxbasic/ide/ideMenuEditor.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// ideMenuEditor.h -- DVX BASIC menu editor dialog
|
||||
//
|
||||
// Modal dialog for designing menu bars on forms. Edits the
|
||||
// DsgnMenuItemT array on a DsgnFormT. Returns true if the
|
||||
// user clicked OK and the menu data was modified.
|
||||
|
||||
#ifndef IDE_MENUEDITOR_H
|
||||
#define IDE_MENUEDITOR_H
|
||||
|
||||
#include "ideDesigner.h"
|
||||
#include "dvxApp.h"
|
||||
|
||||
// Show the modal Menu Editor dialog.
|
||||
bool mnuEditorDialog(AppContextT *ctx, DsgnFormT *form);
|
||||
|
||||
#endif // IDE_MENUEDITOR_H
|
||||
|
|
@ -950,18 +950,14 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
|
|||
WidgetT *next = widgetFindNextFocusable(win->widgetRoot, target);
|
||||
|
||||
if (next) {
|
||||
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||
sFocusedWidget = next;
|
||||
next->focused = true;
|
||||
|
||||
wclsOnAccelActivate(next, win->widgetRoot);
|
||||
|
||||
wgtInvalidate(win->widgetRoot);
|
||||
}
|
||||
} else if (wclsHas(target, WGT_METHOD_ON_ACCEL_ACTIVATE)) {
|
||||
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||
sFocusedWidget = target;
|
||||
target->focused = true;
|
||||
wclsOnAccelActivate(target, win->widgetRoot);
|
||||
wgtInvalidate(win->widgetRoot);
|
||||
}
|
||||
|
|
@ -1818,6 +1814,13 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
|
|||
wmSetFocus(&ctx->stack, &ctx->dirty, hitIdx);
|
||||
}
|
||||
|
||||
// Clear widget focus for non-content clicks (scrollbars, title bar, etc.)
|
||||
// Content clicks clear focus inside widgetOnMouse instead.
|
||||
if (hitPart != HIT_CONTENT && sFocusedWidget) {
|
||||
wgtInvalidatePaint(sFocusedWidget);
|
||||
sFocusedWidget = NULL;
|
||||
}
|
||||
|
||||
switch (hitPart) {
|
||||
case HIT_CONTENT:
|
||||
if (win->onMouse) {
|
||||
|
|
@ -2904,7 +2907,7 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
WidgetT *w = fstack[arrlen(fstack) - 1];
|
||||
arrsetlen(fstack, arrlen(fstack) - 1);
|
||||
|
||||
if (w->focused && widgetIsFocusable(w->type)) {
|
||||
if (w == sFocusedWidget && widgetIsFocusable(w->type)) {
|
||||
current = w;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2938,11 +2941,7 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
|
||||
if (next) {
|
||||
sOpenPopup = NULL;
|
||||
if (sFocusedWidget) {
|
||||
sFocusedWidget->focused = false;
|
||||
}
|
||||
sFocusedWidget = next;
|
||||
next->focused = true;
|
||||
|
||||
// Scroll the widget into view if needed
|
||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
||||
|
|
|
|||
|
|
@ -248,7 +248,6 @@ typedef struct WidgetT {
|
|||
bool visible;
|
||||
bool enabled;
|
||||
bool readOnly;
|
||||
bool focused;
|
||||
bool swallowTab; // Tab key goes to widget, not focus nav
|
||||
char accelKey; // lowercase accelerator character, 0 if none
|
||||
|
||||
|
|
|
|||
25
core/dvxWm.c
25
core/dvxWm.c
|
|
@ -948,6 +948,22 @@ MenuBarT *wmAddMenuBar(WindowT *win) {
|
|||
}
|
||||
|
||||
|
||||
void wmDestroyMenuBar(WindowT *win) {
|
||||
if (!win || !win->menuBar) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < win->menuBar->menuCount; i++) {
|
||||
freeMenuRecursive(&win->menuBar->menus[i]);
|
||||
}
|
||||
|
||||
free(win->menuBar->menus);
|
||||
free(win->menuBar);
|
||||
win->menuBar = NULL;
|
||||
wmUpdateContentRect(win);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wmAddMenuItem
|
||||
// ============================================================
|
||||
|
|
@ -1328,14 +1344,7 @@ void wmDestroyWindow(WindowStackT *stack, WindowT *win) {
|
|||
free(win->contentBuf);
|
||||
}
|
||||
|
||||
if (win->menuBar) {
|
||||
for (int32_t i = 0; i < win->menuBar->menuCount; i++) {
|
||||
freeMenuRecursive(&win->menuBar->menus[i]);
|
||||
}
|
||||
|
||||
free(win->menuBar->menus);
|
||||
free(win->menuBar);
|
||||
}
|
||||
wmDestroyMenuBar(win);
|
||||
|
||||
if (win->vScroll) {
|
||||
free(win->vScroll);
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ int32_t wmReallocContentBuf(WindowT *win, const DisplayT *d);
|
|||
// menu bar per window is supported.
|
||||
MenuBarT *wmAddMenuBar(WindowT *win);
|
||||
|
||||
// Free the menu bar and reclaim the content area.
|
||||
void wmDestroyMenuBar(WindowT *win);
|
||||
|
||||
// Append a dropdown menu to the menu bar. Returns the MenuT to populate
|
||||
// with items. The label supports & accelerator markers (e.g. "&File").
|
||||
MenuT *wmAddMenu(MenuBarT *bar, const char *label);
|
||||
|
|
|
|||
|
|
@ -214,8 +214,6 @@ void widgetClearFocus(WidgetT *root) {
|
|||
return;
|
||||
}
|
||||
|
||||
root->focused = false;
|
||||
|
||||
for (WidgetT *c = root->firstChild; c; c = c->nextSibling) {
|
||||
widgetClearFocus(c);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
// Use cached focus pointer -- O(1) instead of O(n) tree walk
|
||||
WidgetT *focus = sFocusedWidget;
|
||||
|
||||
if (!focus || !focus->focused || focus->window != win) {
|
||||
if (!focus || focus->window != win) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -210,7 +210,7 @@ void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod) {
|
|||
|
||||
WidgetT *focus = sFocusedWidget;
|
||||
|
||||
if (!focus || !focus->focused || focus->window != win) {
|
||||
if (!focus || focus->window != win) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -372,12 +372,12 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
|
|||
WidgetT *prevFocus = sFocusedWidget;
|
||||
|
||||
if (sFocusedWidget) {
|
||||
sFocusedWidget->focused = false;
|
||||
wgtInvalidatePaint(sFocusedWidget);
|
||||
sFocusedWidget = NULL;
|
||||
}
|
||||
|
||||
// Dispatch to the hit widget's mouse handler via vtable. The handler
|
||||
// is responsible for setting hit->focused=true if it wants focus.
|
||||
// is responsible for setting sFocusedWidget if it wants focus.
|
||||
if (hit->enabled) {
|
||||
wclsOnMouse(hit, root, vx, vy);
|
||||
}
|
||||
|
|
@ -453,10 +453,7 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
|
|||
sPrevMouseX = vx;
|
||||
sPrevMouseY = vy;
|
||||
|
||||
// Update the cached focus pointer for O(1) access in widgetOnKey
|
||||
if (hit->focused) {
|
||||
sFocusedWidget = hit;
|
||||
}
|
||||
// sFocusedWidget is now set directly by the widget's mouse handler
|
||||
|
||||
// Fire focus/blur callbacks on transitions
|
||||
if (prevFocus && prevFocus != sFocusedWidget && prevFocus->onBlur) {
|
||||
|
|
@ -543,7 +540,6 @@ void widgetOnPaint(WindowT *win, RectT *dirtyArea) {
|
|||
WidgetT *first = widgetFindNextFocusable(root, NULL);
|
||||
|
||||
if (first) {
|
||||
first->focused = true;
|
||||
sFocusedWidget = first;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -453,11 +453,9 @@ void wgtSetFocused(WidgetT *w) {
|
|||
WidgetT *prev = sFocusedWidget;
|
||||
|
||||
if (prev && prev != w) {
|
||||
prev->focused = false;
|
||||
wgtInvalidatePaint(prev);
|
||||
}
|
||||
|
||||
w->focused = true;
|
||||
sFocusedWidget = w;
|
||||
wgtInvalidatePaint(w);
|
||||
|
||||
|
|
|
|||
|
|
@ -784,7 +784,7 @@ void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
|||
AnsiTermDataT *at = (AnsiTermDataT *)hit->data;
|
||||
AppContextT *actx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &actx->font;
|
||||
hit->focused = true;
|
||||
sFocusedWidget = hit;
|
||||
clearOtherSelections(hit);
|
||||
int32_t cols = at->cols;
|
||||
int32_t rows = at->rows;
|
||||
|
|
@ -898,7 +898,7 @@ void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
if (maxScroll > 0 && thumbRange > 0) { thumbY = trackY + (at->scrollPos * thumbRange) / maxScroll; }
|
||||
drawBevel(d, ops, sbX, thumbY, sbW, thumbH, &btnBevel);
|
||||
}
|
||||
if (w->focused) { drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, colors->contentFg); }
|
||||
if (w == sFocusedWidget) { drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, colors->contentFg); }
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
(void)vx;
|
||||
(void)vy;
|
||||
ButtonDataT *d = (ButtonDataT *)w->data;
|
||||
w->focused = true;
|
||||
sFocusedWidget = w;
|
||||
d->pressed = true;
|
||||
sDragWidget = w;
|
||||
}
|
||||
|
|
@ -157,7 +157,7 @@ void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
|
|||
drawTextAccel(d, ops, font, textX, textY, bd->text, fg, bgFace, true);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
int32_t off = bd->pressed ? BUTTON_PRESS_OFFSET : 0;
|
||||
drawFocusRect(d, ops, w->x + BUTTON_FOCUS_INSET + off, w->y + BUTTON_FOCUS_INSET + off, w->w - BUTTON_FOCUS_INSET * 2, w->h - BUTTON_FOCUS_INSET * 2, fg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
(void)vx;
|
||||
(void)vy;
|
||||
CheckboxDataT *d = (CheckboxDataT *)w->data;
|
||||
w->focused = true;
|
||||
sFocusedWidget = w;
|
||||
d->checked = !d->checked;
|
||||
|
||||
if (w->onChange) {
|
||||
|
|
@ -163,7 +163,7 @@ void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
drawTextAccel(d, ops, font, labelX, labelY, cd->text, fg, bg, false);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
drawFocusRect(d, ops, labelX - 1, labelY - 1, labelW + 2, font->charHeight + 2, fg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
// ============================================================
|
||||
|
||||
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
w->focused = true;
|
||||
sFocusedWidget = w;
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
|
||||
// If popup is open, this click is on a popup item -- select it
|
||||
|
|
@ -300,7 +300,7 @@ void widgetComboBoxPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const
|
|||
len = maxChars;
|
||||
}
|
||||
|
||||
widgetTextEditPaintLine(disp, ops, font, colors, textX, textY, d->buf + off, len, off, d->cursorPos, d->selStart, d->selEnd, fg, bg, w->focused && w->enabled && !d->open, w->x + TEXT_INPUT_PAD, w->x + textAreaW - TEXT_INPUT_PAD);
|
||||
widgetTextEditPaintLine(disp, ops, font, colors, textX, textY, d->buf + off, len, off, d->cursorPos, d->selStart, d->selEnd, fg, bg, w == sFocusedWidget && w->enabled && !d->open, w->x + TEXT_INPUT_PAD, w->x + textAreaW - TEXT_INPUT_PAD);
|
||||
}
|
||||
|
||||
// Drop button
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
(void)vx;
|
||||
w->focused = true;
|
||||
sFocusedWidget = w;
|
||||
DropdownDataT *d = (DropdownDataT *)w->data;
|
||||
|
||||
// If popup is open, this click is on a popup item -- select it
|
||||
|
|
@ -255,7 +255,7 @@ void widgetDropdownPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const
|
|||
int32_t arrowY = w->y + w->h / 2 - 1;
|
||||
widgetDrawDropdownArrow(disp, ops, arrowX, arrowY, arrowFg);
|
||||
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
drawFocusRect(disp, ops, w->x + 3, w->y + 3, textAreaW - 6, w->h - 6, fg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ void widgetImageButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy)
|
|||
(void)vx;
|
||||
(void)vy;
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
w->focused = true;
|
||||
sFocusedWidget = w;
|
||||
d->pressed = true;
|
||||
sDragWidget = w;
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ void widgetImageButtonPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, con
|
|||
0, 0, d->imgW, d->imgH);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
int32_t off = pressed ? 1 : 0;
|
||||
drawFocusRect(disp, ops, w->x + 3 + off, w->y + 3 + off, w->w - 6, w->h - 6, fg);
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
d->sbDragOff = relY - WGT_SB_W - thumbPos;
|
||||
}
|
||||
|
||||
hit->focused = true;
|
||||
sFocusedWidget = hit;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -342,7 +342,7 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
bool ctrl = (ctx->keyModifiers & KEY_MOD_CTRL) != 0;
|
||||
|
||||
d->selectedIdx = idx;
|
||||
hit->focused = true;
|
||||
sFocusedWidget = hit;
|
||||
|
||||
if (multi && d->selBits) {
|
||||
if (ctrl) {
|
||||
|
|
@ -444,7 +444,7 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
drawText(d, ops, font, innerX, iy, lb->items[idx], ifg, ibg, false);
|
||||
|
||||
// Draw cursor indicator in multi-select (dotted focus rect on cursor item)
|
||||
if (multi && idx == lb->selectedIdx && w->focused) {
|
||||
if (multi && idx == lb->selectedIdx && w == sFocusedWidget) {
|
||||
drawFocusRect(d, ops, w->x + LISTBOX_BORDER, iy, contentW, font->charHeight, fg);
|
||||
}
|
||||
}
|
||||
|
|
@ -469,7 +469,7 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
widgetDrawScrollbarV(d, ops, colors, sbX, sbY, innerH, lb->itemCount, visibleRows, lb->scrollPos);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -542,7 +542,7 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
// because scrollbar presence depends on content/widget dimensions which
|
||||
// may have changed since the last paint.
|
||||
void widgetListViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
hit->focused = true;
|
||||
sFocusedWidget = hit;
|
||||
ListViewDataT *lv = (ListViewDataT *)hit->data;
|
||||
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
|
|
@ -1117,7 +1117,7 @@ void widgetListViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
}
|
||||
|
||||
// Draw cursor focus rect in multi-select mode (on top of text)
|
||||
if (multi && dataRow == lv->selectedIdx && w->focused) {
|
||||
if (multi && dataRow == lv->selectedIdx && w == sFocusedWidget) {
|
||||
drawFocusRect(d, ops, baseX, iy, innerW, font->charHeight, fg);
|
||||
}
|
||||
}
|
||||
|
|
@ -1157,7 +1157,7 @@ void widgetListViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
}
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,8 +148,6 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
if (next) {
|
||||
RadioDataT *nd = (RadioDataT *)next->data;
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)next->parent->data;
|
||||
w->focused = false;
|
||||
next->focused = true;
|
||||
sFocusedWidget = next;
|
||||
gd->selectedIdx = nd->index;
|
||||
|
||||
|
|
@ -174,8 +172,6 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
if (prev) {
|
||||
RadioDataT *pd = (RadioDataT *)prev->data;
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)prev->parent->data;
|
||||
w->focused = false;
|
||||
prev->focused = true;
|
||||
sFocusedWidget = prev;
|
||||
gd->selectedIdx = pd->index;
|
||||
|
||||
|
|
@ -198,7 +194,7 @@ void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
w->focused = true;
|
||||
sFocusedWidget = w;
|
||||
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
RadioDataT *rd = (RadioDataT *)w->data;
|
||||
|
|
@ -290,7 +286,7 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
|||
drawTextAccel(d, ops, font, labelX, labelY, rd->text, fg, bg, false);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
drawFocusRect(d, ops, labelX - 1, labelY - 1, labelW + 2, font->charHeight + 2, fg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -609,18 +609,9 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
|||
}
|
||||
|
||||
if (child && child->enabled && wclsHas(child, WGT_METHOD_ON_MOUSE)) {
|
||||
// Clear old focus
|
||||
if (sFocusedWidget && sFocusedWidget != child) {
|
||||
sFocusedWidget->focused = false;
|
||||
}
|
||||
|
||||
wclsOnMouse(child, root, vx, vy);
|
||||
|
||||
if (child->focused) {
|
||||
sFocusedWidget = child;
|
||||
}
|
||||
} else {
|
||||
hit->focused = true;
|
||||
sFocusedWidget = hit;
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(hit);
|
||||
|
|
@ -764,7 +755,7 @@ void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
|
|||
}
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
// The event loop checks sDragSlider on mouse-move to continue the drag.
|
||||
void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
hit->focused = true;
|
||||
sFocusedWidget = hit;
|
||||
SliderDataT *d = (SliderDataT *)hit->data;
|
||||
int32_t range = d->maxValue - d->minValue;
|
||||
|
||||
|
|
@ -259,7 +259,7 @@ void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
|
|||
drawVLine(d, ops, thumbX + SLIDER_THUMB_W / 2, w->y + 3, w->h - 6, tickFg);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
drawFocusRect(d, ops, w->x, w->y, w->w, w->h, fg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
// fixed-width font. Double-click selects all text (select-word doesn't
|
||||
// make sense for numbers), entering edit mode to allow replacement.
|
||||
void widgetSpinnerOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
hit->focused = true;
|
||||
sFocusedWidget = hit;
|
||||
SpinnerDataT *d = (SpinnerDataT *)hit->data;
|
||||
|
||||
int32_t btnX = hit->x + hit->w - SPINNER_BORDER - SPINNER_BTN_W;
|
||||
|
|
@ -424,7 +424,7 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const B
|
|||
len = 0;
|
||||
}
|
||||
|
||||
widgetTextEditPaintLine(disp, ops, font, colors, textX, textY, &d->buf[off], len, off, d->cursorPos, d->selStart, d->selEnd, fg, bg, w->focused, w->x + SPINNER_BORDER, btnX - SPINNER_PAD);
|
||||
widgetTextEditPaintLine(disp, ops, font, colors, textX, textY, &d->buf[off], len, off, d->cursorPos, d->selStart, d->selEnd, fg, bg, w == sFocusedWidget, w->x + SPINNER_BORDER, btnX - SPINNER_PAD);
|
||||
|
||||
// Up button (top half)
|
||||
int32_t btnTopH = w->h / 2;
|
||||
|
|
@ -457,7 +457,7 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const B
|
|||
}
|
||||
|
||||
// Focus rect around entire widget
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
drawFocusRect(disp, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,15 +272,7 @@ void widgetSplitterOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
|||
}
|
||||
|
||||
if (child && child->enabled && wclsHas(child, WGT_METHOD_ON_MOUSE)) {
|
||||
if (sFocusedWidget && sFocusedWidget != child) {
|
||||
sFocusedWidget->focused = false;
|
||||
}
|
||||
|
||||
wclsOnMouse(child, root, vx, vy);
|
||||
|
||||
if (child->focused) {
|
||||
sFocusedWidget = child;
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(hit);
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ void widgetTabControlOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
// on the content area go through normal child hit-testing. Scroll
|
||||
// arrow clicks adjust scrollOffset by 4 character widths at a time.
|
||||
void widgetTabControlOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
hit->focused = true;
|
||||
sFocusedWidget = hit;
|
||||
TabControlDataT *d = (TabControlDataT *)hit->data;
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
|
@ -494,7 +494,7 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
|
|||
|
||||
drawTextAccel(d, ops, font, tabX + TAB_PAD_H, labelY, pd->title, colors->contentFg, tabFace, true);
|
||||
|
||||
if (isActive && w->focused) {
|
||||
if (isActive && w == sFocusedWidget) {
|
||||
drawFocusRect(d, ops, tabX + 3, ty + 3, tw - 6, th - 4, colors->contentFg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1449,7 +1449,7 @@ navigation:
|
|||
// complete.
|
||||
void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||
w->focused = true;
|
||||
sFocusedWidget = w;
|
||||
clearOtherSelections(w);
|
||||
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
|
|
@ -1895,7 +1895,7 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
}
|
||||
|
||||
// Draw cursor (blinks at same rate as terminal cursor)
|
||||
if (w->focused && sCursorBlinkOn) {
|
||||
if (w == sFocusedWidget && sCursorBlinkOn) {
|
||||
int32_t curDrawCol = ta->cursorCol - ta->scrollCol;
|
||||
int32_t curDrawRow = ta->cursorRow - ta->scrollRow;
|
||||
|
||||
|
|
@ -2011,7 +2011,7 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
}
|
||||
|
||||
// Focus rect
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
|
||||
}
|
||||
}
|
||||
|
|
@ -2129,7 +2129,7 @@ void widgetTextInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
// position and registering sDragWidget.
|
||||
void widgetTextInputOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
TextInputDataT *ti = (TextInputDataT *)w->data;
|
||||
w->focused = true;
|
||||
sFocusedWidget = w;
|
||||
clearOtherSelections(w);
|
||||
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
|
|
@ -2187,7 +2187,7 @@ void widgetTextInputPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi
|
|||
memcpy(dispBuf, ti->buf + off, dispLen);
|
||||
}
|
||||
|
||||
widgetTextEditPaintLine(d, ops, font, colors, textX, textY, dispBuf, dispLen, off, ti->cursorPos, ti->selStart, ti->selEnd, fg, bg, w->focused && w->enabled, w->x + TEXT_INPUT_PAD, w->x + w->w - TEXT_INPUT_PAD);
|
||||
widgetTextEditPaintLine(d, ops, font, colors, textX, textY, dispBuf, dispLen, off, ti->cursorPos, ti->selStart, ti->selEnd, fg, bg, w == sFocusedWidget && w->enabled, w->x + TEXT_INPUT_PAD, w->x + w->w - TEXT_INPUT_PAD);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -439,7 +439,7 @@ static void paintTreeItems(WidgetT *parent, DisplayT *d, const BlitOpsT *ops, co
|
|||
drawText(d, ops, font, textX, iy, ti->text, fg, bg, isSelected);
|
||||
|
||||
// Draw focus rectangle around cursor item in multi-select mode
|
||||
if (multi && c == tv->selectedItem && treeView->focused) {
|
||||
if (multi && c == tv->selectedItem && treeView == sFocusedWidget) {
|
||||
uint32_t focusFg = isSelected ? colors->menuHighlightFg : colors->contentFg;
|
||||
drawFocusRect(d, ops, baseX, iy, d->clipW, font->charHeight, focusFg);
|
||||
}
|
||||
|
|
@ -986,7 +986,7 @@ void widgetTreeViewLayout(WidgetT *w, const BitmapFontT *font) {
|
|||
// tracking happens in widgetEvent.c during mouse-move.
|
||||
void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
TreeViewDataT *tv = (TreeViewDataT *)hit->data;
|
||||
hit->focused = true;
|
||||
sFocusedWidget = hit;
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
|
|
@ -1300,7 +1300,7 @@ void widgetTreeViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
}
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
if (w == sFocusedWidget) {
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue