diff -r b174e721663e -r 6d0da97105d8 ui/win32/menu.c --- a/ui/win32/menu.c Sat Dec 27 22:47:56 2025 +0100 +++ b/ui/win32/menu.c Thu Jan 08 18:06:04 2026 +0100 @@ -28,6 +28,8 @@ #include "menu.h" +#include + static ui_menu_add_f createMenuItem[] = { /* UI_MENU */ ui_add_menu, /* UI_MENU_ITEM */ ui_add_menu_item, @@ -58,7 +60,6 @@ UiMenu *menu = (UiMenu*)item; HMENU hMenu = CreatePopupMenu(); AppendMenu(parent, MF_POPUP, (UINT_PTR)hMenu, menu->label); - int i = 0; UiMenuItemI *child = menu->items_begin; while (child) { @@ -67,17 +68,192 @@ } } +static void menu_item_clicked(UiObject *obj, uint64_t id, UiMenuItem *item) { + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = 0; + event.set = 0; + if (item->callback) { + item->callback(&event, item->userdata); + } +} + void ui_add_menu_item(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + uint64_t id = ++obj->ctx->command_id_counter; + UiMenuItem *i = (UiMenuItem*)item; - AppendMenu(parent, MF_STRING, 0, i->label); + AppendMenu(parent, MF_STRING, id, i->label); + + UiCommand cmd; + cmd.callback = (ui_command_func)menu_item_clicked; + cmd.userdata = i; + cxMapPut(obj->ctx->command_map, id, &cmd); +} + +static void menu_stateitem_update(UiStateMenuItem *item) { + MENUITEMINFO mi = { 0 }; + mi.cbSize = sizeof(mi); + mi.fMask = MIIM_STATE; + mi.fState = item->state ? MFS_CHECKED : MFS_UNCHECKED; + SetMenuItemInfo(item->menu, item->id, FALSE, &mi); +} + +static void menu_checkitem_clicked(UiObject *obj, uint64_t id, UiStateMenuItem *item) { + item->state = !item->state; + menu_stateitem_update(item); + + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.intval = 0; + event.set = 0; + if (item->onchange) { + item->onchange(&event, item->userdata); + } + + if (item->var) { + UiInteger *i = item->var->value; + ui_notify_evt(i->observers, &event); + } } void ui_add_menu_checkitem(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + uint64_t id = ++obj->ctx->command_id_counter; + UiMenuCheckItem *i = (UiMenuCheckItem*)item; + AppendMenu(parent, MF_STRING, id, i->label); + + // create an UiStateMenuItem with the same lifetime as the UiObject + UiStateMenuItem *sitem = ui_malloc(obj->ctx, sizeof(UiStateMenuItem)); + memset(sitem, 0, sizeof(UiStateMenuItem)); + sitem->obj = obj; + sitem->menu = parent; + sitem->id = id; + sitem->onchange = i->callback; + sitem->userdata = i->userdata; + sitem->var = uic_widget_var(obj->ctx, obj->ctx, NULL, i->varname, UI_VAR_INTEGER); + // bind to var + if (sitem->var) { + UiInteger *v = sitem->var->value; + sitem->state = v->value != 0; + v->obj = sitem; + v->get = ui_checkitem_get; + v->set = ui_checkitem_set; + } + + // register command id + UiCommand cmd; + cmd.callback = (ui_command_func)menu_checkitem_clicked; + cmd.userdata = sitem; + cxMapPut(obj->ctx->command_map, id, &cmd); + + menu_stateitem_update(sitem); +} + +int64_t ui_checkitem_get(UiInteger *i) { + return i->value; +} + +void ui_checkitem_set(UiInteger *i, int64_t value) { + i->value = value; + menu_stateitem_update(i->obj); +} + +static void menu_radioitem_clicked(UiObject *obj, uint64_t id, UiStateMenuItem *item) { + UiInteger *i = item->var->value; // UiVar is always not NULL for radio items + ui_set(i, item->index+1); + + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.eventdata = i; + event.eventdatatype = UI_EVENT_DATA_INTEGER_VALUE; + event.intval = item->state; + event.set = ui_get_setop(); + + if (item->onchange) { + item->onchange(&event, item->userdata); + } + + event.intval = (int)ui_get(i); + ui_notify_evt(i->observers, &event); } void ui_add_menu_radioitem(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) { + uint64_t id = ++obj->ctx->command_id_counter; + UiMenuRadioItem *i = (UiMenuRadioItem*)item; + AppendMenu(parent, MF_STRING, id, i->label); + + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, NULL, i->varname, UI_VAR_INTEGER); + if (!var) { + return; // radio item without var is useless + } + + UiInteger *v = var->value; + CxList *group = v->obj; + if (!group) { + // first radio button in this group + group = cxArrayListCreate(obj->ctx->allocator, sizeof(UiStateMenuItem), 4); + v->obj = group; + v->get = ui_radioitem_get; + v->set = ui_radioitem_set; + } + + UiStateMenuItem sitem = { 0 }; + sitem.obj = obj; + sitem.menu = parent; + sitem.id = id; + sitem.onchange = i->callback; + sitem.userdata = i->userdata; + sitem.var = var; + sitem.index = (int)cxListSize(group); + cxListAdd(group, &sitem); + + if (v->value == sitem.index+1) { + sitem.state = 1; + menu_stateitem_update(&sitem); + } + + + UiStateMenuItem *sitem_ptr = cxListAt(group, sitem.index); + // register command id + UiCommand cmd; + cmd.callback = (ui_command_func)menu_radioitem_clicked; + cmd.userdata = sitem_ptr; + cxMapPut(obj->ctx->command_map, id, &cmd); +} + +int64_t ui_radioitem_get(UiInteger *i) { + return i->value; +} + +void ui_radioitem_set(UiInteger *i, int64_t value) { + CxList *group = i->obj; + // de-select all items + CxIterator it = cxListIterator(group); + cx_foreach(UiStateMenuItem *, item, it) { + if (item->state) { + item->state = FALSE; + menu_stateitem_update(item); + } + } + + if (value > 0) { + UiStateMenuItem *item = cxListAt(group, value-1); + if (item) { + item->state = TRUE; + menu_stateitem_update(item); + } + } } void ui_add_menu_list(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) {