--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/common/menu.c Sat Jan 04 16:38:48 2025 +0100 @@ -0,0 +1,331 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "menu.h" + +#include <stdarg.h> +#include <string.h> +#include <stdio.h> + +#include <cx/linked_list.h> +#include <cx/array_list.h> + + +static UiMenuBuilder *current_builder; +static UiMenuBuilder global_builder; + +static int menu_item_counter = 0; + +void uic_menu_init(void) { + global_builder.current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + current_builder = &global_builder; +} + +static void add_menu(UiMenu *menu) { + cx_linked_list_add( + (void**)¤t_builder->menus_begin, + (void**)¤t_builder->menus_end, + offsetof(UiMenu, item.prev), + offsetof(UiMenu, item.next), + menu); +} + +static void add_item(UiMenuItemI *item) { + UiMenu *menu = cxListAt(current_builder->current, 0); + cx_linked_list_add( + (void**)&menu->items_begin, + (void**)&menu->items_end, + offsetof(UiMenu, item.prev), + offsetof(UiMenu, item.next), + item); +} + +static void mitem_set_id(UiMenuItemI *item) { + snprintf(item->id, 8, "%x", menu_item_counter++); +} + +static char* nl_strdup(const char* s) { + return s ? strdup(s) : NULL; +} + +int* uic_copy_groups(const int* groups, size_t *ngroups) { + *ngroups = 0; + if (!groups) { + return NULL; + } + + size_t n; + for (n = 0; groups[n] > -1; n++) { } + + if (ngroups > 0) { + int* newarray = calloc(n+1, sizeof(int)); + memcpy(newarray, groups, n * sizeof(int)); + newarray[n] = -1; + *ngroups = n; + return newarray; + } + return NULL; +} + +void ui_menu_create(const char *label) { + // create menu + UiMenu *menu = malloc(sizeof(UiMenu)); + mitem_set_id(&menu->item); + menu->item.prev = NULL; + menu->item.next = NULL; + menu->item.type = UI_MENU; + + menu->label = label; + menu->items_begin = NULL; + menu->items_end = NULL; + menu->parent = NULL; + + menu->end = 0; + + if (cxListSize(current_builder->current) == 0) { + add_menu(menu); + } + else { + add_item((UiMenuItemI*)menu); + } + uic_add_menu_to_stack(menu); +} + +UIEXPORT void ui_menu_end(void) { + cxListRemove(current_builder->current, 0); + if(cxListSize(current_builder->current) == 0) { + current_builder = &global_builder; + } +} + + + +void ui_menuitem_create(UiMenuItemArgs args) { + UiMenuItem* item = malloc(sizeof(UiMenuItem)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_ITEM; + + item->label = nl_strdup(args.label); + item->stockid = nl_strdup(args.stockid); + item->icon = nl_strdup(args.icon); + item->userdata = args.onclickdata; + item->callback = args.onclick; + item->groups = uic_copy_groups(args.groups, &item->ngroups); + + add_item((UiMenuItemI*)item); +} + +void ui_menuseparator() { + UiMenuItemI *item = malloc(sizeof(UiMenuItemI)); + item->id[0] = 0; + item->prev = NULL; + item->next = NULL; + item->type = UI_MENU_SEPARATOR; + + add_item((UiMenuItemI*)item); +} + +void ui_menu_toggleitem_create(UiMenuToggleItemArgs args) { + UiMenuCheckItem *item = malloc(sizeof(UiMenuCheckItem)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_CHECK_ITEM; + + item->label = nl_strdup(args.label); + item->stockid = nl_strdup(args.stockid); + item->icon = nl_strdup(args.icon); + item->varname = nl_strdup(args.varname); + item->userdata = args.onchangedata; + item->callback = args.onchange; + item->groups = uic_copy_groups(args.groups, &item->ngroups); + + add_item((UiMenuItemI*)item); +} + +void ui_menu_radioitem_create(UiMenuToggleItemArgs args) { + UiMenuCheckItem* item = malloc(sizeof(UiMenuCheckItem)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_RADIO_ITEM; + + item->label = nl_strdup(args.label); + item->stockid = nl_strdup(args.stockid); + item->icon = nl_strdup(args.icon); + item->varname = nl_strdup(args.varname); + item->userdata = args.onchangedata; + item->callback = args.onchange; + item->groups = uic_copy_groups(args.groups, &item->ngroups); + + add_item((UiMenuItemI*)item); +} + +void ui_menu_itemlist_create(UiMenuItemListArgs args) { + UiMenuItemList*item = malloc(sizeof(UiMenuItemList)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_ITEM_LIST; + item->getvalue = args.getvalue; + item->callback = args.onselect; + item->userdata = args.onselectdata; + item->varname = nl_strdup(args.varname); + item->addseparator = args.addseparator; + + add_item((UiMenuItemI*)item); +} + +void ui_menu_checkitemlist_create(UiMenuItemListArgs args) { + UiMenuItemList* item = malloc(sizeof(UiMenuItemList)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_CHECKITEM_LIST; + item->callback = args.onselect; + item->userdata = args.onselectdata; + item->varname = nl_strdup(args.varname); + + add_item((UiMenuItemI*)item); +} + +void ui_menu_radioitemlist_create(UiMenuItemListArgs args) { + UiMenuItemList* item = malloc(sizeof(UiMenuItemList)); + mitem_set_id(&item->item); + item->item.prev = NULL; + item->item.next = NULL; + item->item.type = UI_MENU_RADIOITEM_LIST; + item->callback = args.onselect; + item->userdata = args.onselectdata; + item->varname = nl_strdup(args.varname); + + add_item((UiMenuItemI*)item); +} + + +void uic_add_menu_to_stack(UiMenu* menu) { + cxListInsert(current_builder->current, 0, menu); +} + +UiMenu* uic_get_menu_list(void) { + return current_builder->menus_begin; +} + +UIEXPORT void ui_menu_close(void) { + UiMenu* menu = cxListAt(current_builder->current, 0); + menu->end = 1; +} + +UIEXPORT int ui_menu_is_open(void) { + UiMenu* menu = cxListAt(current_builder->current, 0); + if (menu->end) { + ui_menu_end(); + return 0; + } + return 1; +} + + +void ui_contextmenu_builder(UiMenuBuilder **out_builder) { + UiMenuBuilder *builder = malloc(sizeof(UiMenuBuilder)); + builder->menus_begin = NULL; + builder->menus_end = NULL; + builder->current = cxLinkedListCreate(cxDefaultAllocator, NULL, CX_STORE_POINTERS); + current_builder = builder; + *out_builder = builder; + + ui_menu_create(NULL); +} + + + +static void free_menuitem(UiMenuItemI *item) { + switch(item->type) { + default: break; + case UI_MENU: { + UiMenu *menu = (UiMenu*)item; + UiMenuItemI *m = menu->items_begin; + while(m) { + UiMenuItemI *next = m->next; + free_menuitem(m); + m = next; + } + break; + } + case UI_MENU_ITEM: { + UiMenuItem *i = (UiMenuItem*)item; + free(i->groups); + free(i->label); + free(i->stockid); + free(i->icon); + break; + } + case UI_MENU_CHECK_ITEM: { + UiMenuCheckItem *i = (UiMenuCheckItem*)item; + free(i->groups); + free(i->label); + free(i->stockid); + free(i->icon); + free(i->varname); + break; + } + case UI_MENU_RADIO_ITEM: { + UiMenuRadioItem *i = (UiMenuRadioItem*)item; + free(i->groups); + free(i->label); + free(i->stockid); + free(i->icon); + //free(i->varname); + break; + } + case UI_MENU_ITEM_LIST: { + break; + } + case UI_MENU_CHECKITEM_LIST: { + break; + } + case UI_MENU_RADIOITEM_LIST: { + break; + } + } + + free(item); +} + +void ui_menubuilder_free(UiMenuBuilder *builder) { + UiMenuItemI *m = &builder->menus_begin->item; + while(m) { + UiMenuItemI *next = m->next; + free_menuitem(m); + m = next; + } + cxListDestroy(builder->current); + free(builder); +}