implement menu itemlist (Win32)

Tue, 27 Jan 2026 13:08:30 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 27 Jan 2026 13:08:30 +0100
changeset 1060
74bed80a0503
parent 1059
226140134594
child 1061
3776fdc30bbe

implement menu itemlist (Win32)

application/main.c file | annotate | diff | comparison | revisions
ui/win32/menu.c file | annotate | diff | comparison | revisions
ui/win32/menu.h file | annotate | diff | comparison | revisions
--- a/application/main.c	Tue Jan 27 12:23:40 2026 +0100
+++ b/application/main.c	Tue Jan 27 13:08:30 2026 +0100
@@ -1307,6 +1307,14 @@
     printf("menu item clicked\n");
 }
 
+static int nitem = 5;
+void action_menu_additem(UiEvent *event, void *userdata) {
+    char buf[32];
+    snprintf(buf, 32, "New Item %d", nitem++);
+    ui_list_append(menu_list, strdup(buf));
+    ui_list_update(menu_list);
+}
+
 int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
 //int main() {
     ui_init("app1", 0, NULL);
@@ -1330,7 +1338,7 @@
         ui_menuitem("Close");
     }
     ui_menu("Test") {
-        ui_menuitem("Item 0");
+        ui_menuitem("Add Item", .onclick = action_menu_additem);
         ui_menuseparator();
         ui_menu_itemlist(.varname = "menulist");
         ui_menuseparator();
--- a/ui/win32/menu.c	Tue Jan 27 12:23:40 2026 +0100
+++ b/ui/win32/menu.c	Tue Jan 27 13:08:30 2026 +0100
@@ -55,7 +55,6 @@
         menu = next && next->type == UI_MENU ? (UiMenu*)next : NULL;
     }
 
-
     return hMenu;
 }
 
@@ -259,10 +258,106 @@
     }
 }
 
+static void menuitem_list_remove_binding(void *obj) {
+    UiActiveMenuItemList *ls = obj;
+    UiList *list = ls->var->value;
+    CxList *bindings = list->obj;
+    if(bindings) {
+        (void)cxListFindRemove(bindings, obj);
+        if(cxListSize(bindings) == 0) {
+            cxListFree(bindings);
+            list->obj = NULL;
+            list->update = NULL;
+        }
+    }
+}
+
 void ui_add_menu_list(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) {
+    UiMenuItemList *il = (UiMenuItemList*)item;
+    const CxAllocator *a = obj->ctx->allocator;
+
+    UiVar *var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST);
+    if (!var) {
+        return;
+    }
+
+    UiActiveMenuItemList *ls = cxMalloc(
+            a,
+            sizeof(UiActiveMenuItemList));
+    ls->object = obj;
+    ls->menu = parent;
+    ls->command_ids = cxArrayListCreate(a, sizeof(uint64_t), 16);
+    ls->index = pos;
+    ls->getvalue = il->getvalue;
+    ls->callback = il->callback;
+    ls->userdata = il->userdata;
+    ls->addseparator = il->addseparator;
+    ls->var = var;
+
+    UiList *list = ls->var->value;
+    list->update = ui_menulist_update;
+    list->getselection = NULL;
+    list->setselection = NULL;
 
+    // It is possible, that the UiVar is from a global shared context,
+    // used by multiple windows. To support this usecase, the list->obj
+    // binding object is a list of all connected UiActiveMenuItemList.
+    CxList *bindings = list->obj;
+    if(!bindings) {
+        bindings = cxLinkedListCreate(ls->var->from_ctx->mp->allocator, CX_STORE_POINTERS);
+        list->obj = bindings;
+    }
+    cxListAdd(bindings, ls);
+
+    // The destruction of the toplevel obj must remove the menulist binding
+    uic_context_add_destructor(obj->ctx, menuitem_list_remove_binding, ls);
+
+    ui_update_menuitem_list(ls);
 }
 
+void ui_menulist_update(UiList *list, int ignored) {
+    CxList *bindings = list->obj;
+    CxIterator i = cxListIterator(bindings);
+    cx_foreach(UiActiveMenuItemList *, ls, i) {
+        ui_update_menuitem_list(ls);
+    }
+}
+
+void ui_update_menuitem_list(UiActiveMenuItemList *list) {
+    UiObject *obj = list->object;
+
+    UiList *ls;
+    if(list->var && list->var->value) {
+        ls = list->var->value;
+    } else {
+        return;
+    }
+
+    CxIterator i = cxListIterator(list->command_ids);
+    cx_foreach(uint64_t *, id, i) {
+        DeleteMenu(list->menu, *id, MF_BYCOMMAND);
+    }
+    cxListClear(list->command_ids); // TODO: we could reuse some of the ids
+
+    ui_getvaluefunc getvalue = list->getvalue;
+    void* elm = ui_list_first(ls);
+
+    int pos = list->index;
+    while(elm) {
+        char *label = (char*) (getvalue ? getvalue(elm, 0) : elm);
+        if (!label) {
+            label = "";
+        }
+
+        uint64_t id = ++obj->ctx->command_id_counter;
+        InsertMenu(list->menu, pos++, MF_STRING, id, label);
+        cxListAdd(list->command_ids, &id);
+
+        elm = ui_list_next(ls);
+    }
+}
+
+
 void ui_add_menu_checklist(HMENU parent, int pos, UiMenuItemI *item, UiObject *obj) {
 
 }
--- a/ui/win32/menu.h	Tue Jan 27 12:23:40 2026 +0100
+++ b/ui/win32/menu.h	Tue Jan 27 13:08:30 2026 +0100
@@ -48,6 +48,19 @@
     UiBool state;
 } UiStateMenuItem;
 
+typedef struct UiActiveMenuItemList UiActiveMenuItemList;
+struct UiActiveMenuItemList {
+    UiObject         *object;
+    HMENU            menu;
+    CxList           *command_ids;
+    int              index;
+    UiVar            *var;
+    ui_getvaluefunc  getvalue;
+    ui_callback      callback;
+    void             *userdata;
+    bool             addseparator;
+};
+
 typedef void(*ui_menu_add_f)(HMENU, int, UiMenuItemI*, UiObject*);
 
 HMENU ui_create_main_menu(UiObject *obj);
@@ -67,6 +80,9 @@
 int64_t ui_radioitem_get(UiInteger *i);
 void ui_radioitem_set(UiInteger *i, int64_t value);
 
+void ui_menulist_update(UiList *list, int ignored);
+void ui_update_menuitem_list(UiActiveMenuItemList *list);
+
 #ifdef __cplusplus
 }
 #endif

mercurial