diff -r a080c69125d1 -r 3e91b7aff6a1 ui/gtk/list.c --- a/ui/gtk/list.c Thu Dec 05 20:02:30 2024 +0100 +++ b/ui/gtk/list.c Fri Dec 06 13:11:09 2024 +0100 @@ -36,6 +36,7 @@ #include "container.h" #include +#include #include "list.h" #include "icon.h" @@ -982,3 +983,266 @@ gtk_combo_box_set_active(GTK_COMBO_BOX(combobox->widget), selection.rows[0]); } } + + +/* ------------------------------ Source List ------------------------------ */ + +static void ui_destroy_sourcelist(GtkWidget *w, UiListBox *v) { + cxListDestroy(v->sublists); + free(v); +} + +static void sublist_destroy(UiObject *obj, UiListBoxSubList *sublist) { + free(sublist->header); + ui_destroy_boundvar(obj->ctx, sublist->var); + cxListDestroy(sublist->widgets); +} + +static void listbox_create_header(GtkListBoxRow* row, GtkListBoxRow* before, gpointer user_data) { + // first rows in sublists have the ui_listbox property + UiListBox *listbox = g_object_get_data(G_OBJECT(row), "ui_listbox"); + if(!listbox) { + return; + } + + UiListBoxSubList *sublist = g_object_get_data(G_OBJECT(row), "ui_listbox_sublist"); + if(!sublist) { + return; + } + + if(sublist->separator) { + GtkWidget *separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL); + gtk_list_box_row_set_header(row, separator); + } else if(sublist->header) { + GtkWidget *header = gtk_label_new(sublist->header); + gtk_widget_set_halign(header, GTK_ALIGN_START); + if(row == listbox->first_row) { + WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header-first"); + } else { + WIDGET_ADD_CSS_CLASS(header, "ui-listbox-header"); + } + gtk_list_box_row_set_header(row, header); + } +} + +UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs args) { + UiObject* current = uic_current_obj(obj); + + GtkWidget *listbox = gtk_list_box_new(); + if(!args.style_class) { + WIDGET_ADD_CSS_CLASS(listbox, "navigation-sidebar"); + } + gtk_list_box_set_header_func(GTK_LIST_BOX(listbox), listbox_create_header, NULL, NULL); + GtkWidget *scroll_area = SCROLLEDWINDOW_NEW(); + SCROLLEDWINDOW_SET_CHILD(scroll_area, listbox); + + ui_set_name_and_style(listbox, args.name, args.style_class); + ui_set_widget_groups(obj->ctx, listbox, args.groups); + UI_APPLY_LAYOUT1(current, args); + current->container->add(current->container, scroll_area, TRUE); + + UiListBox *uilistbox = malloc(sizeof(UiListBox)); + uilistbox->obj = obj; + uilistbox->listbox = GTK_LIST_BOX(listbox); + uilistbox->getvalue = args.getvalue; + uilistbox->onactivate = args.onactivate; + uilistbox->onactivatedata = args.onactivatedata; + uilistbox->onbuttonclick = args.onbuttonclick; + uilistbox->onbuttonclickdata = args.onbuttonclickdata; + uilistbox->sublists = cxArrayListCreateSimple(sizeof(UiListBoxSubList), 4); + uilistbox->sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_destroy; + uilistbox->sublists->collection.destructor_data = obj; + uilistbox->first_row = NULL; + + if(args.numsublists == 0 && args.sublists) { + args.numsublists = INT_MAX; + } + for(int i=0;ictx, + current->ctx, + sublist.value, + sublist.varname, + UI_VAR_LIST); + uisublist.numitems = 0; + uisublist.header = sublist.header ? strdup(sublist.header) : NULL; + uisublist.separator = sublist.separator; + uisublist.widgets = cxLinkedListCreateSimple(CX_STORE_POINTERS); + uisublist.listbox = uilistbox; + uisublist.userdata = sublist.userdata; + uisublist.index = i; + + cxListAdd(uilistbox->sublists, &uisublist); + + // bind UiList + UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(uilistbox->sublists)-1); + UiList *list = uisublist.var->value; + if(list) { + list->obj = sublist_ptr; + list->update = ui_listbox_list_update; + } + } + // fill items + ui_listbox_update(uilistbox, 0, cxListSize(uilistbox->sublists)); + + // register uilistbox for both widgets, so it doesn't matter which + // widget is used later + g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox); + g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox); + + // signals + g_signal_connect( + listbox, + "destroy", + G_CALLBACK(ui_destroy_sourcelist), + uilistbox); + + if(args.onactivate) { + g_signal_connect( + listbox, + "row-activated", + G_CALLBACK(ui_listbox_row_activate), + NULL); + } + + return scroll_area; +} + +void ui_listbox_update(UiListBox *listbox, int from, int to) { + CxIterator i = cxListIterator(listbox->sublists); + size_t pos = 0; + cx_foreach(UiListBoxSubList *, sublist, i) { + if(i.index < from) { + pos += sublist->numitems; + continue; + } + if(i.index > to) { + break; + } + + // reload sublist + ui_listbox_update_sublist(listbox, sublist, pos); + pos += sublist->numitems; + } +} + +static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) { + GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); + if(item->icon) { + GtkWidget *icon = ICON_IMAGE(item->icon); + BOX_ADD(hbox, icon); + } + GtkWidget *label = gtk_label_new(item->label); + gtk_widget_set_halign(label, GTK_ALIGN_START); + BOX_ADD_EXPAND(hbox, label); + // TODO: badge, button + GtkWidget *row = gtk_list_box_row_new(); + LISTBOX_ROW_SET_CHILD(row, hbox); + + // signals + UiEventDataExt *event = malloc(sizeof(UiEventDataExt)); + memset(event, 0, sizeof(UiEventDataExt)); + event->obj = listbox->obj; + event->customdata0 = sublist; + event->customdata1 = sublist->var; + event->customdata2 = item->eventdata; + event->callback = listbox->onactivate; + event->userdata = listbox->onactivatedata; + event->callback2 = listbox->onbuttonclick; + event->userdata2 = listbox->onbuttonclickdata; + event->value0 = index; + + g_signal_connect( + row, + "destroy", + G_CALLBACK(ui_destroy_userdata), + event); + + g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event); + + return row; +} + +void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) { + // clear sublist + CxIterator r = cxListIterator(sublist->widgets); + cx_foreach(GtkWidget*, widget, r) { + LISTBOX_REMOVE(listbox->listbox, widget); + } + cxListClear(sublist->widgets); + + sublist->numitems = 0; + + // create items for each UiList element + UiList *list = sublist->var->value; + if(!list) { + return; + } + + size_t index = 0; + void *elm = list->first(list); + while(elm) { + UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; + listbox->getvalue(sublist->userdata, elm, index, &item); + + // create listbox item + GtkWidget *row = create_listbox_row(listbox, sublist, &item, (int)index); + if(index == 0) { + // first row in the sublist, set ui_listbox data to the row + // which is then used by the headerfunc + g_object_set_data(G_OBJECT(row), "ui_listbox", listbox); + g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist); + + if(listbox_insert_index == 0) { + // first row in the GtkListBox + listbox->first_row = GTK_LIST_BOX_ROW(row); + } + } + intptr_t rowindex = listbox_insert_index + index; + g_object_set_data(G_OBJECT(row), "ui_listbox_row_index", (gpointer)rowindex); + gtk_list_box_insert(listbox->listbox, row, listbox_insert_index + index); + cxListAdd(sublist->widgets, row); + + // cleanup + free(item.label); + free(item.icon); + free(item.button_label); + free(item.button_icon); + free(item.badge); + + // next row + elm = list->next(list); + index++; + } + + sublist->numitems = cxListSize(sublist->widgets); +} + +void ui_listbox_list_update(UiList *list, int i) { + UiListBoxSubList *sublist = list->obj; +} + +void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) { + UiEventDataExt *data = g_object_get_data(G_OBJECT(row), "ui-listbox-row-eventdata"); + if(!data) { + return; + } + UiListBoxSubList *sublist = data->customdata0; + + UiEvent event; + event.obj = data->obj; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = data->customdata2; + event.intval = data->value0; + + if(data->callback) { + data->callback(&event, data->userdata); + } +}