Checkboxes and radio buttons in menus.
This commit is contained in:
parent
6f8aeda7b2
commit
7476367ace
6 changed files with 188 additions and 14 deletions
102
dvx/dvxApp.c
102
dvx/dvxApp.c
|
|
@ -15,6 +15,7 @@
|
|||
#define DBLCLICK_THRESHOLD (CLOCKS_PER_SEC / 2)
|
||||
#define ICON_REFRESH_INTERVAL 8
|
||||
#define KB_MOVE_STEP 8
|
||||
#define MENU_CHECK_WIDTH 14
|
||||
#define SUBMENU_ARROW_WIDTH 12
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -22,6 +23,7 @@
|
|||
// ============================================================
|
||||
|
||||
static void calcPopupSize(const AppContextT *ctx, const MenuT *menu, int32_t *pw, int32_t *ph);
|
||||
static void clickMenuCheckRadio(MenuT *menu, int32_t itemIdx);
|
||||
static void closeAllPopups(AppContextT *ctx);
|
||||
static void closePopupLevel(AppContextT *ctx);
|
||||
static void closeSysMenu(AppContextT *ctx);
|
||||
|
|
@ -74,6 +76,7 @@ static const char sAltScanToAscii[256] = {
|
|||
static void calcPopupSize(const AppContextT *ctx, const MenuT *menu, int32_t *pw, int32_t *ph) {
|
||||
int32_t maxW = 0;
|
||||
bool hasSub = false;
|
||||
bool hasCheck = false;
|
||||
|
||||
for (int32_t k = 0; k < menu->itemCount; k++) {
|
||||
int32_t itemW = textWidthAccel(&ctx->font, menu->items[k].label);
|
||||
|
|
@ -85,13 +88,50 @@ static void calcPopupSize(const AppContextT *ctx, const MenuT *menu, int32_t *pw
|
|||
if (menu->items[k].subMenu) {
|
||||
hasSub = true;
|
||||
}
|
||||
|
||||
if (menu->items[k].type == MenuItemCheckE || menu->items[k].type == MenuItemRadioE) {
|
||||
hasCheck = true;
|
||||
}
|
||||
}
|
||||
|
||||
*pw = maxW + CHROME_TITLE_PAD * 2 + 8 + (hasSub ? SUBMENU_ARROW_WIDTH : 0);
|
||||
*pw = maxW + CHROME_TITLE_PAD * 2 + 8 + (hasSub ? SUBMENU_ARROW_WIDTH : 0) + (hasCheck ? MENU_CHECK_WIDTH : 0);
|
||||
*ph = menu->itemCount * ctx->font.charHeight + 4;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// clickMenuCheckRadio — toggle check or select radio on click
|
||||
// ============================================================
|
||||
|
||||
static void clickMenuCheckRadio(MenuT *menu, int32_t itemIdx) {
|
||||
MenuItemT *item = &menu->items[itemIdx];
|
||||
|
||||
if (item->type == MenuItemCheckE) {
|
||||
item->checked = !item->checked;
|
||||
} else if (item->type == MenuItemRadioE) {
|
||||
// Uncheck all radio items in the same group (consecutive radio items)
|
||||
// Search backward to find group start
|
||||
int32_t groupStart = itemIdx;
|
||||
|
||||
while (groupStart > 0 && menu->items[groupStart - 1].type == MenuItemRadioE) {
|
||||
groupStart--;
|
||||
}
|
||||
|
||||
// Search forward to find group end
|
||||
int32_t groupEnd = itemIdx;
|
||||
|
||||
while (groupEnd < menu->itemCount - 1 && menu->items[groupEnd + 1].type == MenuItemRadioE) {
|
||||
groupEnd++;
|
||||
}
|
||||
|
||||
// Uncheck all in group, check the clicked one
|
||||
for (int32_t i = groupStart; i <= groupEnd; i++) {
|
||||
menu->items[i].checked = (i == itemIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// closeAllPopups — dirty all popup levels and deactivate
|
||||
// ============================================================
|
||||
|
|
@ -587,6 +627,11 @@ static void dispatchEvents(AppContextT *ctx) {
|
|||
// Clicking a submenu item opens it (already open from hover, but ensure)
|
||||
openSubMenu(ctx);
|
||||
} else if (item->enabled && !item->separator) {
|
||||
// Toggle check/radio state before closing
|
||||
if (item->type == MenuItemCheckE || item->type == MenuItemRadioE) {
|
||||
clickMenuCheckRadio(ctx->popup.menu, itemIdx);
|
||||
}
|
||||
|
||||
// Close popup BEFORE calling onMenu (onMenu may run a nested event loop)
|
||||
int32_t menuId = item->id;
|
||||
WindowT *win = findWindowById(ctx, ctx->popup.windowId);
|
||||
|
|
@ -738,6 +783,18 @@ static void drawPopupLevel(AppContextT *ctx, DisplayT *d, const BlitOpsT *ops, c
|
|||
return;
|
||||
}
|
||||
|
||||
// Detect if menu has check/radio items (for left margin)
|
||||
bool hasCheck = false;
|
||||
|
||||
for (int32_t k = 0; k < menu->itemCount; k++) {
|
||||
if (menu->items[k].type == MenuItemCheckE || menu->items[k].type == MenuItemRadioE) {
|
||||
hasCheck = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t checkMargin = hasCheck ? MENU_CHECK_WIDTH : 0;
|
||||
|
||||
// Draw popup background
|
||||
BevelStyleT popBevel;
|
||||
popBevel.highlight = ctx->colors.windowHighlight;
|
||||
|
|
@ -767,7 +824,30 @@ static void drawPopupLevel(AppContextT *ctx, DisplayT *d, const BlitOpsT *ops, c
|
|||
}
|
||||
|
||||
rectFill(d, ops, px + 2, itemY, pw - 4, ctx->font.charHeight, bg);
|
||||
drawTextAccel(d, ops, &ctx->font, px + CHROME_TITLE_PAD + 2, itemY, item->label, fg, bg, true);
|
||||
drawTextAccel(d, ops, &ctx->font, px + CHROME_TITLE_PAD + 2 + checkMargin, itemY, item->label, fg, bg, true);
|
||||
|
||||
// Draw check/radio indicator
|
||||
if (item->checked) {
|
||||
int32_t cy = itemY + ctx->font.charHeight / 2;
|
||||
int32_t cx = px + 2 + MENU_CHECK_WIDTH / 2;
|
||||
|
||||
if (item->type == MenuItemCheckE) {
|
||||
// Checkmark: small tick shape
|
||||
drawVLine(d, ops, cx - 2, cy - 1, 2, fg);
|
||||
drawVLine(d, ops, cx - 1, cy, 2, fg);
|
||||
drawVLine(d, ops, cx, cy + 1, 2, fg);
|
||||
drawVLine(d, ops, cx + 1, cy, 2, fg);
|
||||
drawVLine(d, ops, cx + 2, cy - 1, 2, fg);
|
||||
drawVLine(d, ops, cx + 3, cy - 2, 2, fg);
|
||||
} else if (item->type == MenuItemRadioE) {
|
||||
// Filled circle bullet (5x5)
|
||||
drawHLine(d, ops, cx - 1, cy - 2, 3, fg);
|
||||
drawHLine(d, ops, cx - 2, cy - 1, 5, fg);
|
||||
drawHLine(d, ops, cx - 2, cy, 5, fg);
|
||||
drawHLine(d, ops, cx - 2, cy + 1, 5, fg);
|
||||
drawHLine(d, ops, cx - 1, cy + 2, 3, fg);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw submenu arrow indicator
|
||||
if (item->subMenu) {
|
||||
|
|
@ -971,6 +1051,24 @@ void dvxInvalidateWindow(AppContextT *ctx, WindowT *win) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dvxMaximizeWindow
|
||||
// ============================================================
|
||||
|
||||
void dvxMaximizeWindow(AppContextT *ctx, WindowT *win) {
|
||||
wmMaximize(&ctx->stack, &ctx->dirty, &ctx->display, win);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dvxMinimizeWindow
|
||||
// ============================================================
|
||||
|
||||
void dvxMinimizeWindow(AppContextT *ctx, WindowT *win) {
|
||||
wmMinimize(&ctx->stack, &ctx->dirty, win);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dvxQuit
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -74,6 +74,12 @@ void dvxInvalidateRect(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int
|
|||
// Invalidate entire window content
|
||||
void dvxInvalidateWindow(AppContextT *ctx, WindowT *win);
|
||||
|
||||
// Minimize a window (show as icon at bottom of screen)
|
||||
void dvxMinimizeWindow(AppContextT *ctx, WindowT *win);
|
||||
|
||||
// Maximize a window (expand to fill screen or maxW/maxH)
|
||||
void dvxMaximizeWindow(AppContextT *ctx, WindowT *win);
|
||||
|
||||
// Request exit from main loop
|
||||
void dvxQuit(AppContextT *ctx);
|
||||
|
||||
|
|
|
|||
|
|
@ -155,14 +155,21 @@ typedef struct {
|
|||
// Forward declaration for submenu pointers
|
||||
typedef struct MenuT MenuT;
|
||||
|
||||
typedef enum {
|
||||
MenuItemNormalE,
|
||||
MenuItemCheckE,
|
||||
MenuItemRadioE,
|
||||
} MenuItemTypeE;
|
||||
|
||||
typedef struct {
|
||||
char label[MAX_MENU_LABEL];
|
||||
int32_t id; // application-defined command ID
|
||||
bool separator; // true = this is a separator line, not a clickable item
|
||||
bool enabled;
|
||||
bool checked;
|
||||
char accelKey; // lowercase accelerator character, 0 if none
|
||||
MenuT *subMenu; // child menu for cascading submenus (NULL if leaf item)
|
||||
char label[MAX_MENU_LABEL];
|
||||
int32_t id; // application-defined command ID
|
||||
MenuItemTypeE type; // normal, checkbox, or radio
|
||||
bool separator; // true = this is a separator line, not a clickable item
|
||||
bool enabled;
|
||||
bool checked;
|
||||
char accelKey; // lowercase accelerator character, 0 if none
|
||||
MenuT *subMenu; // child menu for cascading submenus (NULL if leaf item)
|
||||
} MenuItemT;
|
||||
|
||||
struct MenuT {
|
||||
|
|
|
|||
44
dvx/dvxWm.c
44
dvx/dvxWm.c
|
|
@ -645,6 +645,50 @@ void wmAddMenuItem(MenuT *menu, const char *label, int32_t id) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wmAddMenuCheckItem
|
||||
// ============================================================
|
||||
|
||||
void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked) {
|
||||
if (menu->itemCount >= MAX_MENU_ITEMS) {
|
||||
return;
|
||||
}
|
||||
|
||||
MenuItemT *item = &menu->items[menu->itemCount];
|
||||
memset(item, 0, sizeof(*item));
|
||||
strncpy(item->label, label, MAX_MENU_LABEL - 1);
|
||||
item->label[MAX_MENU_LABEL - 1] = '\0';
|
||||
item->id = id;
|
||||
item->type = MenuItemCheckE;
|
||||
item->enabled = true;
|
||||
item->checked = checked;
|
||||
item->accelKey = accelParse(label);
|
||||
menu->itemCount++;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wmAddMenuRadioItem
|
||||
// ============================================================
|
||||
|
||||
void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked) {
|
||||
if (menu->itemCount >= MAX_MENU_ITEMS) {
|
||||
return;
|
||||
}
|
||||
|
||||
MenuItemT *item = &menu->items[menu->itemCount];
|
||||
memset(item, 0, sizeof(*item));
|
||||
strncpy(item->label, label, MAX_MENU_LABEL - 1);
|
||||
item->label[MAX_MENU_LABEL - 1] = '\0';
|
||||
item->id = id;
|
||||
item->type = MenuItemRadioE;
|
||||
item->enabled = true;
|
||||
item->checked = checked;
|
||||
item->accelKey = accelParse(label);
|
||||
menu->itemCount++;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wmAddMenuSeparator
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ MenuT *wmAddMenu(MenuBarT *bar, const char *label);
|
|||
// Add an item to a menu
|
||||
void wmAddMenuItem(MenuT *menu, const char *label, int32_t id);
|
||||
|
||||
// Add a checkbox item to a menu
|
||||
void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked);
|
||||
|
||||
// Add a radio item to a menu (radio group = consecutive radio items)
|
||||
void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked);
|
||||
|
||||
// Add a separator to a menu
|
||||
void wmAddMenuSeparator(MenuT *menu);
|
||||
|
||||
|
|
|
|||
|
|
@ -22,11 +22,16 @@
|
|||
#define CMD_EDIT_CUT 200
|
||||
#define CMD_EDIT_COPY 201
|
||||
#define CMD_EDIT_PASTE 202
|
||||
#define CMD_VIEW_TERM 300
|
||||
#define CMD_VIEW_CTRL 301
|
||||
#define CMD_VIEW_ZOOM_IN 302
|
||||
#define CMD_VIEW_ZOOM_OUT 303
|
||||
#define CMD_VIEW_ZOOM_FIT 304
|
||||
#define CMD_VIEW_TERM 300
|
||||
#define CMD_VIEW_CTRL 301
|
||||
#define CMD_VIEW_ZOOM_IN 302
|
||||
#define CMD_VIEW_ZOOM_OUT 303
|
||||
#define CMD_VIEW_ZOOM_FIT 304
|
||||
#define CMD_VIEW_TOOLBAR 305
|
||||
#define CMD_VIEW_STATUSBAR 306
|
||||
#define CMD_VIEW_SIZE_SMALL 307
|
||||
#define CMD_VIEW_SIZE_MED 308
|
||||
#define CMD_VIEW_SIZE_LARGE 309
|
||||
#define CMD_HELP_ABOUT 400
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -594,6 +599,13 @@ static void setupMainWindow(AppContextT *ctx) {
|
|||
wmAddMenuItem(viewMenu, "&Terminal", CMD_VIEW_TERM);
|
||||
wmAddMenuItem(viewMenu, "&Controls", CMD_VIEW_CTRL);
|
||||
wmAddMenuSeparator(viewMenu);
|
||||
wmAddMenuCheckItem(viewMenu, "Tool&bar", CMD_VIEW_TOOLBAR, true);
|
||||
wmAddMenuCheckItem(viewMenu, "&Status Bar", CMD_VIEW_STATUSBAR, true);
|
||||
wmAddMenuSeparator(viewMenu);
|
||||
wmAddMenuRadioItem(viewMenu, "S&mall", CMD_VIEW_SIZE_SMALL, false);
|
||||
wmAddMenuRadioItem(viewMenu, "Me&dium", CMD_VIEW_SIZE_MED, true);
|
||||
wmAddMenuRadioItem(viewMenu, "&Large", CMD_VIEW_SIZE_LARGE, false);
|
||||
wmAddMenuSeparator(viewMenu);
|
||||
|
||||
MenuT *zoomMenu = wmAddSubMenu(viewMenu, "&Zoom");
|
||||
|
||||
|
|
@ -727,6 +739,7 @@ static void setupTerminalWindow(AppContextT *ctx) {
|
|||
|
||||
dvxFitWindow(ctx, win);
|
||||
wgtInvalidate(root);
|
||||
dvxMinimizeWindow(ctx, win);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue