add sourcelist data source (Cocoa)

Fri, 10 Oct 2025 14:13:52 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 10 Oct 2025 14:13:52 +0200
changeset 828
a952337ae325
parent 827
eae5b817aa47
child 829
0980245646b4

add sourcelist data source (Cocoa)

make/xcode/toolkit/toolkit/main.m file | annotate | diff | comparison | revisions
ui/cocoa/container.h file | annotate | diff | comparison | revisions
ui/cocoa/list.h file | annotate | diff | comparison | revisions
ui/cocoa/list.m file | annotate | diff | comparison | revisions
--- a/make/xcode/toolkit/toolkit/main.m	Fri Oct 10 09:06:06 2025 +0200
+++ b/make/xcode/toolkit/toolkit/main.m	Fri Oct 10 14:13:52 2025 +0200
@@ -39,6 +39,7 @@
     UiDouble *number;
     UiList *list1;
     UiList *list2;
+    UiList *sidebar_list;
 } MyDocument;
 
 MyDocument* create_doc(void) {
@@ -60,6 +61,14 @@
     ui_list_append(doc->list2, "Option 5");
     ui_list_append(doc->list2, "Option 6");
     
+    doc->sidebar_list = ui_list_new(ctx, "sidebar_list");
+    ui_list_append(doc->sidebar_list, "Item 1");
+    ui_list_append(doc->sidebar_list, "Item 2");
+    ui_list_append(doc->sidebar_list, "Item 3");
+    ui_list_append(doc->sidebar_list, "Item 4");
+    ui_list_append(doc->sidebar_list, "Item 5");
+    ui_list_append(doc->sidebar_list, "Item 6");
+    
     return doc;
 }
 
@@ -105,12 +114,11 @@
     MyDocument *doc = create_doc();
     ui_attach_document(obj->ctx, doc);
     
-    
+    UiSubList sublist = {0};
+    sublist.header = "Test";
+    sublist.value = doc->sidebar_list;
     ui_sidebar(obj) {
-        ui_vbox(obj, .margin = 0, .fill = TRUE) {
-            ui_button(obj, .label = "Button");
-            //ui_textarea(obj, .varname = "text", .fill = TRUE);
-        }
+        ui_sourcelist(obj, .fill = TRUE, .sublists = &sublist, .numsublists = 1);
     }
     
     
--- a/ui/cocoa/container.h	Fri Oct 10 09:06:06 2025 +0200
+++ b/ui/cocoa/container.h	Fri Oct 10 14:13:52 2025 +0200
@@ -34,13 +34,7 @@
 #define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
 
 typedef struct UiLayout UiLayout;
-typedef enum UiLayoutBool UiLayoutBool;
 
-enum UiLayoutBool {
-    UI_LAYOUT_UNDEFINED = 0,
-    UI_LAYOUT_TRUE,
-    UI_LAYOUT_FALSE,
-};
 
 #define UI_INIT_LAYOUT(args) (UiLayout) {\
     .fill = args->fill, \
--- a/ui/cocoa/list.h	Fri Oct 10 09:06:06 2025 +0200
+++ b/ui/cocoa/list.h	Fri Oct 10 14:13:52 2025 +0200
@@ -55,3 +55,50 @@
 void ui_dropdown_update(UiList *list, int i);
 UiListSelection ui_dropdown_getselection(UiList *list);
 void ui_dropdown_setselection(UiList *list, UiListSelection selection);
+
+@class UiSourceList;
+
+@interface UiSourceListItem : NSObject
+@property (weak) UiSourceList *sourcelist;
+@property (strong) NSString *label;
+@property (strong) NSString *badge;
+
+@property (strong) NSMutableArray<UiSourceListItem*> *items;
+@property UiVar *var;
+@property UiSubList *sublist;
+
+/*
+ * Initialize a section item
+ */
+- (id)init:(UiSubListItem*)item;
+/*
+ * Initialize a child item
+ */
+- (id)init:(UiSourceList*)sourcelist sublist:(UiSubList*)sublist;
+- (BOOL)isSection;
+- (void)update:(int)row;
+
+@end
+
+
+@interface UiSourceList : NSObject <NSOutlineViewDataSource, NSOutlineViewDelegate>
+
+@property UiObject *obj;
+@property CxList *sublists;
+@property UiVar *dynamic_sublists;
+@property ui_sublist_getvalue_func getvalue;
+@property void *getvaluedata;
+@property ui_callback onactivate;
+@property void *onactivatedata;
+@property ui_callback onbuttonclick;
+@property void *onbuttonclickdata;
+
+@property (strong) NSMutableArray<UiSourceListItem*> *sections;
+
+- (id)init:(UiObject*)obj;
+
+- (void)update:(int)row;
+
+@end
+
+void ui_sourcelist_update(UiList *list, int row);
--- a/ui/cocoa/list.m	Fri Oct 10 09:06:06 2025 +0200
+++ b/ui/cocoa/list.m	Fri Oct 10 14:13:52 2025 +0200
@@ -30,6 +30,11 @@
 #import "ListDelegate.h"
 #import <objc/runtime.h>
 
+#import <inttypes.h>
+#import <limits.h>
+
+#import <cx/array_list.h>
+
 static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
     ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata;
     return getvalue(elm, col);
@@ -354,3 +359,221 @@
         [combobox selectItemAtIndex: -1];
     }
 }
+
+
+/* --------------------------- SourceList --------------------------- */
+
+static void sublist_free(const CxAllocator *a, UiSubList *sl) {
+    cxFree(a, (char*)sl->varname);
+    cxFree(a, (char*)sl->header);
+}
+
+static UiSubList copy_sublist(const CxAllocator *a, UiSubList *sl) {
+    UiSubList new_sl;
+    new_sl.value = sl->value;
+    new_sl.varname = sl->varname ? cx_strdup_a(a, cx_str(sl->varname)).ptr : NULL;
+    new_sl.header = sl->header ? cx_strdup_a(a, cx_str(sl->header)).ptr : NULL;
+    new_sl.separator = sl->separator;
+    new_sl.userdata = sl->userdata;
+    return new_sl;
+}
+
+static CxList* copy_sublists(const CxAllocator *a, UiSourceListArgs *args) {
+    if(args->sublists) {
+        size_t max = args->numsublists;
+        if(max == 0) {
+            max = INT_MAX;
+        }
+        
+        CxList *sublists = cxArrayListCreate(a, NULL, sizeof(UiSubList), args->numsublists);
+        sublists->collection.advanced_destructor = (cx_destructor_func2)sublist_free;
+        
+        for(int i=0;i<max;i++) {
+            UiSubList *sl = &args->sublists[i];
+            if(sl->value == NULL && sl->varname == NULL) {
+                break;
+            }
+            
+            UiSubList new_sl = copy_sublist(a, sl);
+            cxListAdd(sublists, &new_sl);
+        }
+        
+        return sublists;
+    }
+    return NULL;
+}
+
+UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) {
+    // create views
+    NSScrollView *scrollview = [[NSScrollView alloc] init];
+    scrollview.autoresizingMask = NSViewWidthSizable;
+    
+    NSOutlineView *outline = [[NSOutlineView alloc]init];
+    NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"label"];
+    [outline addTableColumn:column];
+    outline.outlineTableColumn = column;
+    outline.headerView = NULL;
+    
+    outline.style = NSTableViewStyleSourceList;
+
+    // Make background transparent so vibrancy shows through
+    outline.backgroundColor = [NSColor clearColor];
+    scrollview.drawsBackground = NO;
+
+    scrollview.documentView = outline;
+    
+    UiLayout layout = UI_ARGS2LAYOUT(args);
+    ui_container_add(obj, scrollview, &layout);
+    
+    // datasource and delegate
+    UiSourceList *data = [[UiSourceList alloc] init:obj];
+    data.sublists = copy_sublists(obj->ctx->allocator, args);
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->dynamic_sublist, args->varname, UI_VAR_LIST);
+    if(var) {
+        UiList *list = var->value;
+        list->obj = (__bridge void*)data;
+        list->update = ui_sourcelist_update;
+    }
+    data.dynamic_sublists = var;
+    data.getvalue = args->getvalue;
+    data.getvaluedata = args->getvaluedata;
+    data.onactivate = args->onactivate;
+    data.onactivatedata = args->onactivatedata;
+    data.onbuttonclick = args->onbuttonclick;
+    data.onactivatedata = args->onbuttonclickdata;
+    [data update:-1];
+ 
+    outline.dataSource = data;
+    //outline.delegate = data;
+    
+    objc_setAssociatedObject(outline, "ui_datasource", data, OBJC_ASSOCIATION_RETAIN);
+    
+    return (__bridge void*)scrollview;
+}
+
+void ui_sourcelist_update(UiList *list, int row) {
+    UiSourceList *sourcelist = (__bridge UiSourceList*)list->obj;
+    [sourcelist update:row];
+}
+
+
+@implementation UiSourceList
+
+- (id)init:(UiObject*)obj {
+    _obj = obj;
+    _sections = [[NSMutableArray alloc] initWithCapacity:16];
+    return self;
+}
+
+- (void)dealloc {
+    cxListFree(_sublists);
+}
+
+- (void)update:(int)row {
+    // TODO: check row
+    
+    [_sections removeAllObjects];
+    
+    CxIterator i = cxListIterator(_sublists);
+    cx_foreach(UiSubList *, sl, i) {
+        UiSourceListItem *section = [[UiSourceListItem alloc] init:self sublist:sl];
+        [section update:-1];
+        [_sections addObject:section];
+    }
+}
+
+// NSOutlineViewDataSource implementation
+
+- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
+    if(item == nil) {
+        return _sections.count;
+    } else {
+        UiSourceListItem *i = item;
+        return i.items.count;
+    }
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
+    UiSourceListItem *i = item;
+    return [i isSection];
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
+    UiSourceListItem *i = item;
+    if(i) {
+        return [i.items objectAtIndex:index];
+    }
+    return [_sections objectAtIndex:index];
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
+    UiSourceListItem *i = item;
+    if([[tableColumn identifier] isEqualToString:@"label"]) {
+        return i.label;
+    }
+    return nil;
+}
+
+- (void)outlineView:(NSOutlineView *)outlineView
+     setObjectValue:(id)object
+     forTableColumn:(NSTableColumn *)tableColumn
+             byItem:(id)item {
+    
+}
+
+@end
+
+@implementation UiSourceListItem
+
+- (id)init:(UiSourceList*)sourcelist sublist:(UiSubList*)sublist {
+    _sourcelist = sourcelist;
+    _sublist = sublist;
+    _items = [[NSMutableArray alloc]initWithCapacity:16];
+    if(sublist->header) {
+        _label = [[NSString alloc]initWithUTF8String:sublist->header];
+    }
+    UiVar *var = uic_widget_var(sourcelist.obj->ctx,
+                                sourcelist.obj->ctx,
+                                sublist->value,
+                                sublist->varname,
+                                UI_VAR_LIST);
+    _var = var;
+    return self;
+}
+
+- (id)init:(UiSubListItem*)item {
+    if(item->label) {
+        _label = [[NSString alloc]initWithUTF8String:item->label];
+    }
+    return self;
+}
+
+- (BOOL)isSection {
+    return _sublist != NULL;
+}
+
+- (void)update:(int)row {
+    [_items removeAllObjects];
+    if(_var == NULL) {
+        return;
+    }
+    UiList *list = _var->value;
+    void *elm = list->first(list);
+    int index = 0;
+    while(elm) {
+        UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL };
+        if(_sourcelist.getvalue) {
+            _sourcelist.getvalue(list, _sublist->userdata, elm, index, &item, _sourcelist.getvaluedata);
+        } else {
+            item.label = strdup(elm);
+        }
+        
+        [_items addObject:[[UiSourceListItem alloc] init:&item]];
+        
+        elm = list->next(list);
+        index++;
+    }
+}
+
+@end
+

mercurial