update toolkit, remove getvalue func from model to table/listview args

Sun, 24 Aug 2025 15:24:16 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 24 Aug 2025 15:24:16 +0200
changeset 109
c3dfcb8f0be7
parent 108
77254bd6dccb
child 110
c00e968d018b

update toolkit, remove getvalue func from model to table/listview args

application/Makefile file | annotate | diff | comparison | revisions
application/application.c file | annotate | diff | comparison | revisions
application/settings.c file | annotate | diff | comparison | revisions
application/window.c file | annotate | diff | comparison | revisions
ucx/cx/string.h file | annotate | diff | comparison | revisions
ucx/mempool.c file | annotate | diff | comparison | revisions
ui/cocoa/GridLayout.m file | annotate | diff | comparison | revisions
ui/cocoa/ListDataSource.h file | annotate | diff | comparison | revisions
ui/cocoa/ListDataSource.m file | annotate | diff | comparison | revisions
ui/cocoa/ListDelegate.h file | annotate | diff | comparison | revisions
ui/cocoa/ListDelegate.m file | annotate | diff | comparison | revisions
ui/cocoa/MainWindow.m file | annotate | diff | comparison | revisions
ui/cocoa/Toolbar.h file | annotate | diff | comparison | revisions
ui/cocoa/Toolbar.m file | annotate | diff | comparison | revisions
ui/cocoa/UiJob.h file | annotate | diff | comparison | revisions
ui/cocoa/UiJob.m file | annotate | diff | comparison | revisions
ui/cocoa/UiThread.h file | annotate | diff | comparison | revisions
ui/cocoa/UiThread.m file | annotate | diff | comparison | revisions
ui/cocoa/button.m file | annotate | diff | comparison | revisions
ui/cocoa/container.h file | annotate | diff | comparison | revisions
ui/cocoa/container.m file | annotate | diff | comparison | revisions
ui/cocoa/image.h file | annotate | diff | comparison | revisions
ui/cocoa/image.m file | annotate | diff | comparison | revisions
ui/cocoa/label.h file | annotate | diff | comparison | revisions
ui/cocoa/label.m file | annotate | diff | comparison | revisions
ui/cocoa/list.h file | annotate | diff | comparison | revisions
ui/cocoa/list.m file | annotate | diff | comparison | revisions
ui/cocoa/menu.h file | annotate | diff | comparison | revisions
ui/cocoa/menu.m file | annotate | diff | comparison | revisions
ui/cocoa/objs.mk file | annotate | diff | comparison | revisions
ui/cocoa/text.m file | annotate | diff | comparison | revisions
ui/cocoa/toolkit.h file | annotate | diff | comparison | revisions
ui/cocoa/toolkit.m file | annotate | diff | comparison | revisions
ui/common/args.c file | annotate | diff | comparison | revisions
ui/common/args.h file | annotate | diff | comparison | revisions
ui/common/types.c file | annotate | diff | comparison | revisions
ui/common/wrapper.c file | annotate | diff | comparison | revisions
ui/common/wrapper.h file | annotate | diff | comparison | revisions
ui/gtk/button.c file | annotate | diff | comparison | revisions
ui/gtk/button.h file | annotate | diff | comparison | revisions
ui/gtk/display.c file | annotate | diff | comparison | revisions
ui/gtk/headerbar.c file | annotate | diff | comparison | revisions
ui/gtk/list.c file | annotate | diff | comparison | revisions
ui/gtk/list.h file | annotate | diff | comparison | revisions
ui/gtk/text.c file | annotate | diff | comparison | revisions
ui/gtk/toolkit.c file | annotate | diff | comparison | revisions
ui/gtk/webview.c file | annotate | diff | comparison | revisions
ui/gtk/window.c file | annotate | diff | comparison | revisions
ui/qt/button.cpp file | annotate | diff | comparison | revisions
ui/qt/container.cpp file | annotate | diff | comparison | revisions
ui/qt/list.cpp file | annotate | diff | comparison | revisions
ui/qt/model.cpp file | annotate | diff | comparison | revisions
ui/qt/model.h file | annotate | diff | comparison | revisions
ui/qt/window.cpp file | annotate | diff | comparison | revisions
ui/ui/button.h file | annotate | diff | comparison | revisions
ui/ui/display.h file | annotate | diff | comparison | revisions
ui/ui/toolkit.h file | annotate | diff | comparison | revisions
ui/ui/tree.h file | annotate | diff | comparison | revisions
--- a/application/Makefile	Sun Jul 20 22:04:39 2025 +0200
+++ b/application/Makefile	Sun Aug 24 15:24:16 2025 +0200
@@ -51,7 +51,7 @@
 all: ../build/bin/idav$(APP_EXT)
 
 ../build/bin/idav$(APP_EXT): $(OBJ) $(LIB_UCX) $(LIB_UITK)
-	$(CC) -o ../build/bin/idav$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib -lidav $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) $(LIB_UCX) $(LIB_UITK)
+	$(CC) -o ../build/bin/idav$(APP_EXT) $(OBJ) -L$(BUILD_ROOT)/build/lib $(LIB_UITK) $(LIB_UCX) $(LDFLAGS) $(TK_LDFLAGS) $(DAV_LDFLAGS) -lidav
 
 ../build/application/%.$(OBJ_EXT): %.c
 	$(CC) $(CFLAGS) $(TK_CFLAGS) $(DAV_CFLAGS) -o $@ -c $<
--- a/application/application.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/application/application.c	Sun Aug 24 15:24:16 2025 +0200
@@ -191,7 +191,7 @@
         ui_list_append(app->repos, repo);
     }
     
-    ui_notify(app->repos->observers, NULL);
+    ui_list_update(app->repos);
 }
 
 
--- a/application/settings.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/application/settings.c	Sun Aug 24 15:24:16 2025 +0200
@@ -656,9 +656,9 @@
                         ui_newline(obj);
                         
                         UiModel* model = ui_model(obj->ctx, UI_STRING, "Name", UI_STRING, "URL", UI_STRING, "User", UI_STRING, "Encrypted", -1);
-                        model->getvalue = (ui_getvaluefunc) settings_repolist_getvalue;
                         ui_table(obj,
                                 .model = model,
+                                .getvalue = (ui_getvaluefunc) settings_repolist_getvalue,
                                 .list = wdata->repos,
                                 .multiselection = FALSE,
                                 .onactivate = repolist_activate,
--- a/application/window.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/application/window.c	Sun Aug 24 15:24:16 2025 +0200
@@ -95,10 +95,10 @@
     UiModel* model = ui_model(obj->ctx, UI_ICON_TEXT, "Name", UI_STRING_FREE, "Flags", UI_STRING, "Type", UI_STRING_FREE, "Last Modified", UI_STRING_FREE, "Size", -1);
     model->columnsize[0] = -1;
     model->columnsize[2] = 150;
-    model->getvalue = (ui_getvaluefunc) window_resource_table_getvalue;
     ui_table(obj,
             .fill = TRUE,
             .model = model,
+            .getvalue = (ui_getvaluefunc) window_resource_table_getvalue,
             .onselection = action_list_selection,
             .onactivate = action_list_activate,
             .ondragstart = action_dnd_start,
@@ -255,8 +255,13 @@
                 
                 ui_tab(win, "Properties") {
                     UiModel* model = ui_model(win->ctx, UI_STRING, "Namespace", UI_STRING, "Name", UI_STRING, "Value", -1);
-                    model->getvalue = (ui_getvaluefunc) resourceviewer_proplist_getvalue;
-                    ui_table(win, .fill = TRUE, .model = model, .varname = "properties", .onselection = action_resourceviewer_property_select, .onactivate = action_resourceviewer_property_activate);
+                    ui_table(win,
+                            .fill = TRUE,
+                            .model = model,
+                            .getvalue = (ui_getvaluefunc) resourceviewer_proplist_getvalue,
+                            .varname = "properties",
+                            .onselection = action_resourceviewer_property_select,
+                            .onactivate = action_resourceviewer_property_activate);
                     ui_hbox(win, .margin = 4, .spacing = 4) {
                         ui_button(win, .label = "Add", .onclick = action_resourceviewer_property_add);
                         ui_button(win, .label = "Edit", .onclick = action_resourceviewer_property_edit, .groups = UI_GROUPS(RESOURCEVIEWER_STATE_PROP_SELECTED));
--- a/ucx/cx/string.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ucx/cx/string.h	Sun Aug 24 15:24:16 2025 +0200
@@ -263,6 +263,10 @@
 static inline cxstring cx_strcast(cxstring str) {
     return str;
 }
+cx_attr_nodiscard
+static inline cxstring cx_strcast(const char *str) {
+    return cx_str(str);
+}
 extern "C" {
 #else
 /**
@@ -287,6 +291,17 @@
 }
 
 /**
+ * Internal function, do not use.
+ * @param str
+ * @return
+ * @see cx_strcast()
+ */
+cx_attr_nodiscard
+static inline cxstring cx_strcast_z(const char *str) {
+    return cx_str(str);
+}
+
+/**
 * Casts a mutable string to an immutable string.
 *
 * Does nothing for already immutable strings.
@@ -300,8 +315,9 @@
 */
 #define cx_strcast(str) _Generic((str), \
         cxmutstr: cx_strcast_m, \
-        cxstring: cx_strcast_c) \
-        (str)
+        cxstring: cx_strcast_c, \
+        const char*: cx_strcast_z, \
+        char *: cx_strcast_z) (str)
 #endif
 
 /**
--- a/ucx/mempool.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ucx/mempool.c	Sun Aug 24 15:24:16 2025 +0200
@@ -638,7 +638,7 @@
 
     // transfer all registered memory
     memcpy(&dest->registered[dest->registered_size], source->registered,
-           sizeof(struct cx_mempool_foreign_memory_s) * source->registered_size);
+           sizeof(struct cx_mempool_foreign_memory_s) * source->size);
     dest->registered_size += source->registered_size;
 
     // register the old allocator with the new pool
--- a/ui/cocoa/GridLayout.m	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/cocoa/GridLayout.m	Sun Aug 24 15:24:16 2025 +0200
@@ -293,7 +293,7 @@
     return self.preferredSize;
 }
 
-- (void) addView:(NSView*)view fill:(BOOL)fill {
+- (void) addView:(NSView*)view {
     if(_newline) {
         _y++;
         _x = 0;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/ListDataSource.h	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,42 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#import "toolkit.h"
+#import "../ui/tree.h"
+
+@interface ListDataSource : NSObject <NSTableViewDataSource>
+
+@property NSArray<NSTableColumn*> *columns;
+@property UiVar *var;
+@property ui_getvaluefunc2 getvalue;
+@property void *getvaluedata;
+@property UiModel *model;
+
+- (id) init:(NSArray<NSTableColumn*>*) columns var:(UiVar*)var getvalue:(ui_getvaluefunc2) getvaluefunc getvaluedata:(void*)userdata;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/ListDataSource.m	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,110 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#import "ListDataSource.h"
+
+@implementation ListDataSource
+
+- (id) init:(NSArray<NSTableColumn*>*) columns var:(UiVar*)var getvalue:(ui_getvaluefunc2) getvaluefunc getvaluedata:(void*)userdata {
+    _columns = columns;
+    _var = var;
+    _getvalue = getvaluefunc;
+    _getvaluedata = userdata;
+    return self;
+}
+
+- (NSInteger) numberOfRowsInTableView:(NSTableView *) tableView {
+    if(_var) {
+        UiList *list = _var->value;
+        if(list->count) {
+            return list->count(list);
+        }
+    }
+    return 0;
+}
+
+- (id) tableView:(NSTableView *) tableView
+objectValueForTableColumn:(NSTableColumn *) tableColumn
+             row:(NSInteger) row
+{
+    id ret = nil;
+    UiList *list = _var->value;
+    void *elm = list->get(list, (int)row);
+    if(elm) {
+        // get column index
+        NSUInteger colIndex = [_columns indexOfObject:tableColumn];
+        if(colIndex == NSNotFound) {
+            return nil;
+        }
+        
+        // get UI model type for this column
+        UiModelType type = UI_STRING;
+        UiModel *model = _model;
+        if(model) {
+            if(colIndex >= model->columns) {
+                return nil;
+            }
+            type = model->types[colIndex];
+        }
+        
+        // convert the list element
+        UiBool freeResult = FALSE;
+        void *data = _getvalue(list, elm, (int)row, (int)colIndex, _getvaluedata, &freeResult);
+        
+        switch(type) {
+            case UI_STRING: {
+                ret = [[NSString alloc] initWithUTF8String:data];
+                break;
+            }
+            case UI_STRING_FREE: {
+                ret = [[NSString alloc] initWithUTF8String:data];
+                freeResult = TRUE;
+                break;
+            }
+            case UI_INTEGER: {
+                break;
+            }
+            case UI_ICON: {
+                break;
+            }
+            case UI_ICON_TEXT: {
+                break;
+            }
+            case UI_ICON_TEXT_FREE: {
+                break;
+            }
+        }
+        
+        if(freeResult) {
+            free(data);
+        }
+    }
+    return ret;
+}
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/ListDelegate.h	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,46 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#import "toolkit.h"
+
+@interface ListDelegate : NSObject <NSTableViewDelegate>
+
+@property (weak) NSTableView *tableview;
+@property UiObject    *obj;
+@property ui_callback onselection;
+@property void        *onselectiondata;
+@property ui_callback onactivate;
+@property void        *onactivatedata;
+
+- (id)init:(NSTableView*) tableview obj:(UiObject*)obj;
+
+- (void)activateEvent:(id)sender;
+
+@end
+
+UiListSelection ui_tableview_selection(NSTableView *tableview);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/ListDelegate.m	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,99 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#import "ListDelegate.h"
+
+@implementation ListDelegate
+
+- (id)init:(NSTableView*) tableview obj:(UiObject*)obj {
+    _tableview = tableview;
+    _obj = obj;
+    return self;
+}
+
+- (void)activateEvent:(id)sender {
+    NSTableView *table = sender;
+    if(_onactivate) {
+        int row = (int)table.clickedRow;
+        
+        UiListSelection sel;
+        sel.count = 1;
+        sel.rows = &row;
+        
+        UiEvent  event;
+        event.obj = _obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &sel;
+        event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION;
+        event.intval = row;
+        event.set = ui_get_setop();
+        
+        _onactivate(&event, _onactivatedata);
+    }
+}
+
+- (void) tableViewSelectionDidChange:(NSNotification *) notification {
+    if(_onselection) {
+        UiListSelection sel = ui_tableview_selection(_tableview);
+        
+        UiEvent  event;
+        event.obj = _obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = &sel;
+        event.eventdatatype = UI_EVENT_DATA_LIST_SELECTION;
+        event.intval = 0;
+        event.set = ui_get_setop();
+        
+        _onselection(&event, _onselectiondata);
+    }
+}
+
+@end
+
+UiListSelection ui_tableview_selection(NSTableView *tableview) {
+    NSIndexSet *indexSet = tableview.selectedRowIndexes;
+    NSUInteger count = [indexSet count];
+    
+    if(count == 0) {
+        return (UiListSelection){0, NULL};
+    }
+    
+    int *rows = calloc(count, sizeof(int));
+    
+    __block NSUInteger i = 0;
+    [indexSet enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) {
+        rows[i++] = (int)index;
+    }];
+    
+    UiListSelection sel;
+    sel.count = (int)count;
+    sel.rows = rows;
+    return sel;
+}
--- a/ui/cocoa/MainWindow.m	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/cocoa/MainWindow.m	Sun Aug 24 15:24:16 2025 +0200
@@ -34,6 +34,7 @@
 
 #import "EventData.h"
 #import "menu.h"
+#import "Toolbar.h"
 
 @implementation MainWindow
 
@@ -48,6 +49,12 @@
                              backing:NSBackingStoreBuffered
                                defer:false];
     
+    if(uic_toolbar_isenabled()) {
+        UiToolbar *toolbar = [[UiToolbar alloc]initWithObject:obj];
+        [self setToolbar:toolbar];
+    }
+    
+    
     // create a vertical stackview as default container
     BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0];
     //GridLayout *vbox = [[GridLayout alloc] init];
@@ -134,8 +141,13 @@
 - (void)menuItemAction:(id)sender {
     EventData *event = objc_getAssociatedObject(sender, "eventdata");
     if(event) {
-        event.obj = self.uiobj; // temporary set the event object
-        [event handleEvent:sender];
+        if(event.obj) {
+            [event handleEvent:sender];
+        } else {
+            event.obj = self.uiobj;
+            [event handleEvent:sender];
+            event.obj = NULL;
+        }
     }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/Toolbar.h	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,77 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#import "toolkit.h"
+#import "../common/toolbar.h"
+
+/*
+ * UiToolbarDelegate
+ */
+@interface UiToolbarDelegate : NSObject <NSToolbarDelegate> {
+    NSMutableArray<NSString*> *allowedItems;
+    NSMutableArray<NSString*> *defaultItems;
+}
+
+- (UiToolbarDelegate*) init;
+
+@end
+
+/*
+ * UiToolbar
+ */
+@interface UiToolbar : NSToolbar <NSToolbarDelegate> {
+    NSMutableArray<NSString*> *allowedItems;
+    NSMutableArray<NSString*> *defaultItems;
+}
+
+@property UiObject *obj;
+
+- (UiToolbar*) initWithObject:(UiObject*)object;
+
+@end
+
+
+@interface UiToolbarToggleEventHandler : NSObject
+@property UiObject           *obj;
+@property UiVar              *var;
+@property ui_callback        callback;
+@property void               *userdata;
+
+- (UiToolbarToggleEventHandler*)init;
+- (void)handleEvent:(id)sender;
+
+@end
+
+void ui_toolbar_init(void);
+
+NSToolbarItem* ui_nstoolbaritem_create_item(UiObject *obj, UiToolbarItem *item, NSString *identifier);
+NSToolbarItem* ui_nstoolbaritem_create_toggle(UiObject *obj, UiToolbarToggleItem *item, NSString *identifier);
+NSToolbarItem* ui_nstoolbaritem_create_menu(UiObject *obj, UiToolbarMenuItem *item, NSString *identifier);
+
+int64_t ui_toolbar_seg_toggleitem_get(UiInteger *i);
+void ui_toolbar_seg_toggleitem_set(UiInteger *i, int64_t value);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/Toolbar.m	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,245 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#import "Toolbar.h"
+#import "EventData.h"
+#import "image.h"
+#import "menu.h"
+#import <objc/runtime.h>
+
+#include "../common/toolbar.h"
+
+
+void ui_toolbar_init(void) {
+    
+}
+
+
+
+/* ---------------------      UiToolbar      --------------------- */
+
+@implementation UiToolbar
+
+- (UiToolbar*) initWithObject:(UiObject*)object {
+    self = [super initWithIdentifier:@"UiToolbar"];
+    _obj = object;
+    
+    allowedItems = [[NSMutableArray alloc]initWithCapacity:16];
+    defaultItems = [[NSMutableArray alloc]initWithCapacity:16];
+    
+    CxMap *toolbarItems = uic_get_toolbar_items();
+    CxMapIterator i = cxMapIteratorKeys(toolbarItems);
+    cx_foreach(CxHashKey *, key, i) {
+        NSString *s = [[NSString alloc]initWithBytes:key->data length:key->len encoding:NSUTF8StringEncoding];
+        [allowedItems addObject:s];
+    }
+    [allowedItems addObject: NSToolbarFlexibleSpaceItemIdentifier];
+    [allowedItems addObject: NSToolbarSpaceItemIdentifier];
+    
+    CxList *tbitems[3];
+    tbitems[0] =  uic_get_toolbar_defaults(UI_TOOLBAR_LEFT);
+    tbitems[1] =  uic_get_toolbar_defaults(UI_TOOLBAR_CENTER);
+    tbitems[2] =  uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT);
+    for(int t=0;t<3;t++) {
+        CxIterator iter = cxListIterator(tbitems[t]);
+        cx_foreach(char *, name, iter) {
+            NSString *s = [[NSString alloc] initWithUTF8String:name];
+            [defaultItems addObject:s];
+        }
+    }
+    
+    [self setDelegate:self];
+    [self setAllowsUserCustomization:YES];
+    return self;
+}
+
+// implementation of NSToolbarDelegate methods
+
+- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
+    return allowedItems;
+}
+
+- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
+    return defaultItems;
+}
+
+- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar
+  itemForItemIdentifier:(NSString *)itemIdentifier
+  willBeInsertedIntoToolbar:(BOOL)flag {
+    CxMap *items = uic_get_toolbar_items();
+    UiToolbarItemI *item = cxMapGet(items, itemIdentifier.UTF8String);
+    if(!item) {
+        return nil;
+    }
+    
+    switch(item->type) {
+        default: return nil;
+        case UI_TOOLBAR_ITEM: {
+            return ui_nstoolbaritem_create_item(_obj, (UiToolbarItem*)item, itemIdentifier);
+        }
+        case UI_TOOLBAR_TOGGLEITEM: {
+            return ui_nstoolbaritem_create_toggle(_obj, (UiToolbarToggleItem*)item, itemIdentifier);
+        }
+        case UI_TOOLBAR_MENU: {
+            return ui_nstoolbaritem_create_menu(_obj, (UiToolbarMenuItem*)item, itemIdentifier);
+        }
+    }
+    
+    return nil;
+}
+
+@end
+
+@implementation UiToolbarToggleEventHandler
+
+- (UiToolbarToggleEventHandler*)init {
+    return self;
+}
+
+- (void)handleEvent:(id)sender {
+    if(_callback == nil) {
+        return;
+    }
+    
+    UiEvent event;
+    event.obj = _obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = NULL;
+    event.eventdatatype = 0;
+    event.intval = 0;
+    event.set = ui_get_setop();
+    
+    if([sender isKindOfClass:[NSSegmentedControl class]]) {
+        NSSegmentedControl *seg = (NSSegmentedControl*)sender;
+        event.intval = [seg isSelectedForSegment:0];
+    }
+    
+    _callback(&event, _userdata);
+}
+
+@end
+
+NSToolbarItem* ui_nstoolbaritem_create_item(UiObject *obj, UiToolbarItem *item, NSString *identifier) {
+    NSToolbarItem *button = [[NSToolbarItem alloc] initWithItemIdentifier: identifier];
+    button.bordered = YES;
+    
+    if(item->args.label) {
+        NSString *label = [[NSString alloc] initWithUTF8String:item->args.label];
+        button.paletteLabel = label;
+        button.label = label;
+    }
+    if(item->args.icon) {
+        button.image = ui_cocoa_named_icon(item->args.icon);
+    }
+    
+    if(item->args.onclick) {
+        EventData *event = [[EventData alloc] init:item->args.onclick userdata:item->args.onclickdata];
+        event.obj = obj;
+        button.target = event;
+        button.action = @selector(handleEvent:);
+        objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN);
+    }
+    return button;
+}
+
+NSToolbarItem* ui_nstoolbaritem_create_toggle(UiObject *obj, UiToolbarToggleItem *item, NSString *identifier) {
+    UiToolbarToggleEventHandler *event = [[UiToolbarToggleEventHandler alloc]init];
+    event.obj = obj;
+    event.callback = item->args.onchange;
+    event.userdata = item->args.onchangedata;
+    
+    NSToolbarItem *button = [[NSToolbarItem alloc] initWithItemIdentifier:identifier];
+    if(item->args.label) {
+        NSString *label = [[NSString alloc] initWithUTF8String:item->args.label];
+        button.paletteLabel = label;
+        button.label = label;
+    }
+    objc_setAssociatedObject(button, "eventdata", event, OBJC_ASSOCIATION_RETAIN);
+    
+    NSSegmentedControl *seg;
+    if(!item->args.icon) {
+        NSArray *labels = @[[[NSString alloc] initWithUTF8String:item->args.label]];
+        seg = [NSSegmentedControl segmentedControlWithLabels:labels trackingMode:NSSegmentSwitchTrackingSelectAny target:event action:@selector(handleEvent:)];
+        button.view = seg;
+    } else {
+        NSArray *images = @[ui_cocoa_named_icon(item->args.icon)];
+        seg = [NSSegmentedControl segmentedControlWithImages:images trackingMode:NSSegmentSwitchTrackingSelectAny target:event action:@selector(handleEvent:)];
+    }
+    button.view = seg;
+    
+    UiVar* var = uic_widget_var(obj->ctx, obj->ctx, NULL, item->args.varname, UI_VAR_INTEGER);
+    if(var) {
+        event.var = var;
+        UiInteger *i = var->value;
+        if(i->get) {
+            if(ui_get(i) != 0) {
+                [seg setEnabled:YES forSegment:0];
+            }
+            
+        }
+        i->obj = (__bridge void*)seg;
+        i->get = ui_toolbar_seg_toggleitem_get;
+        i->set = ui_toolbar_seg_toggleitem_set;
+    }
+    
+    return button;
+}
+
+int64_t ui_toolbar_seg_toggleitem_get(UiInteger *i) {
+    NSSegmentedControl *seg = (__bridge NSSegmentedControl*)i->obj;
+    i->value = [seg isSelectedForSegment:0];
+    return i->value;
+}
+
+void ui_toolbar_seg_toggleitem_set(UiInteger *i, int64_t value) {
+    NSSegmentedControl *seg = (__bridge NSSegmentedControl*)i->obj;
+    i->value = value;
+    [seg setSelected:value != 0 forSegment:0];
+}
+
+NSToolbarItem* ui_nstoolbaritem_create_menu(UiObject *obj, UiToolbarMenuItem *item, NSString *identifier) {
+    NSMenuToolbarItem *button = [[NSMenuToolbarItem alloc] initWithItemIdentifier: identifier];
+    button.bordered = YES;
+    
+    if(item->args.label) {
+        NSString *label = [[NSString alloc] initWithUTF8String:item->args.label];
+        button.paletteLabel = label;
+        button.label = label;
+    }
+    if(item->args.icon) {
+        button.image = ui_cocoa_named_icon(item->args.icon);
+    }
+    
+    NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
+    ui_add_menu_items(obj, menu, 0, &item->menu);
+    
+    button.menu = menu;
+    
+    return button;
+}
--- a/ui/cocoa/UiJob.h	Sun Jul 20 22:04:39 2025 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2024 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.
- */
-
-#import "toolkit.h"
-
--- a/ui/cocoa/UiJob.m	Sun Jul 20 22:04:39 2025 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 2024 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.
- */
-
-#import "UiJob.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/UiThread.h	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,45 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 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.
+ */
+
+#import "toolkit.h"
+
+@interface UiThread : NSObject
+
+@property UiObject *obj;
+@property ui_threadfunc job;
+@property void *jobdata;
+@property ui_callback finish_callback;
+@property void *finish_userdata;
+
+- (id)init:(UiObject*)obj jobfunc:(ui_threadfunc)job jobdata:(void*)jobdata;
+
+- (void)start;
+- (void) runJob:(id)n;
+- (void) finish:(id)n;
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/UiThread.m	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,68 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2024 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.
+ */
+
+#import "UiThread.h"
+
+
+@implementation UiThread
+
+- (id) init:(UiObject*)obj jobfunc:(ui_threadfunc)job jobdata:(void*)jobdata {
+    _obj = obj;
+    _job = job;
+    _jobdata = jobdata;
+    return self;
+}
+
+- (void) start {
+    [NSThread detachNewThreadSelector:@selector(runJob:)
+                             toTarget:self
+                           withObject:nil];
+}
+
+
+- (void) runJob:(id)n {
+    int result = _job(_jobdata);
+    if(!result) {
+        [self performSelectorOnMainThread:@selector(finish:)
+                               withObject:nil
+                            waitUntilDone:NO];
+    }
+}
+
+- (void) finish:(id)n {
+    UiEvent event;
+    event.obj = _obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = NULL;
+    event.intval = 0;
+    _finish_callback(&event, _finish_userdata);
+}
+
+@end
+
--- a/ui/cocoa/button.m	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/cocoa/button.m	Sun Aug 24 15:24:16 2025 +0200
@@ -47,7 +47,7 @@
     }
     
     UiLayout layout = UI_INIT_LAYOUT(args);
-    ui_container_add(obj, button, &layout, FALSE);
+    ui_container_add(obj, button, &layout);
     
     return (__bridge void*)button;
 }
@@ -89,7 +89,7 @@
     }
     
     UiLayout layout = UI_INIT_LAYOUT(args);
-    ui_container_add(obj, button, &layout, FALSE);
+    ui_container_add(obj, button, &layout);
     
     return (__bridge void*)button;
 }
@@ -150,7 +150,7 @@
     }
     
     UiLayout layout = UI_INIT_LAYOUT(args);
-    ui_container_add(obj, button, &layout, FALSE);
+    ui_container_add(obj, button, &layout);
     
     return (__bridge void*)button;
 }
@@ -242,7 +242,7 @@
     }
     
     UiLayout layout = UI_INIT_LAYOUT(args);
-    ui_container_add(obj, button, &layout, FALSE);
+    ui_container_add(obj, button, &layout);
     
     return (__bridge void*)button;
 }
--- a/ui/cocoa/container.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/cocoa/container.h	Sun Aug 24 15:24:16 2025 +0200
@@ -71,7 +71,7 @@
 @property const char *label;
 @property UiBool newline;
 
-- (void) addView:(NSView*)view fill:(BOOL)fill;
+- (void) addView:(NSView*)view;
 
 @end
 
@@ -87,4 +87,4 @@
 
 UiContainerX* ui_create_container(UiObject *obj, id<Container> container);
 
-void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout, UiBool fill);
+void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout);
--- a/ui/cocoa/container.m	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/cocoa/container.m	Sun Aug 24 15:24:16 2025 +0200
@@ -57,10 +57,8 @@
     return self;
 }
 
-- (void) addView:(NSView*)view fill:(BOOL)fill {
-    if(_uilayout.fill != UI_LAYOUT_UNDEFINED) {
-        fill = ui_lb2bool(_uilayout.fill);
-    }
+- (void) addView:(NSView*)view {
+    UiBool fill = _uilayout.fill;
     
     [self addArrangedSubview:view];
     
@@ -101,7 +99,7 @@
     
     // add box to the parent
     UiLayout layout = UI_INIT_LAYOUT(args);
-    ui_container_add(obj, box, &layout, TRUE);
+    ui_container_add(obj, box, &layout);
     
     // add new box to the obj container chain
     uic_object_push_container(obj, ui_create_container(obj, box));
@@ -123,7 +121,7 @@
     
     // add box to the parent
     UiLayout layout = UI_INIT_LAYOUT(args);
-    ui_container_add(obj, grid, &layout, TRUE);
+    ui_container_add(obj, grid, &layout);
     
     // add new box to the obj container chain
     uic_object_push_container(obj, ui_create_container(obj, grid));
@@ -157,11 +155,11 @@
     return ctn;
 }
 
-void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout, UiBool fill) {
+void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout) {
     UiContainerX *ctn = obj->container_end;
     id<Container> container = (__bridge id<Container>)ctn->container;
     container.uilayout = *layout;
-    [container addView:view fill:fill];
+    [container addView:view];
 }
 
 /* ---------------------- public layout functions ----------------------- */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/image.h	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,35 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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 "../ui/image.h"
+
+#include "Container.h"
+
+void ui_icon_init(void);
+
+NSImage* ui_cocoa_named_icon(const char *name);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/image.m	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,67 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#import "image.h"
+
+static NSDictionary *standardIconNames;
+
+void ui_icon_init(void) {
+    standardIconNames = @{
+        @"NSImageNameActionTemplate": NSImageNameActionTemplate,
+        @"NSImageNameAddTemplate": NSImageNameAddTemplate,
+        @"NSImageNameAdvanced": NSImageNameAdvanced,
+        @"NSImageNameApplicationIcon": NSImageNameApplicationIcon,
+        @"NSImageNameBluetoothTemplate": NSImageNameBluetoothTemplate,
+        @"NSImageNameBonjour": NSImageNameBonjour,
+        @"NSImageNameBookmarksTemplate": NSImageNameBookmarksTemplate,
+        @"NSImageNameCaution": NSImageNameCaution,
+        // TODO
+        @"NSImageNameRefreshTemplate": NSImageNameRefreshTemplate,
+        @"NSImageNameFolder": NSImageNameFolder,
+        @"NSImageNameGoForwardTemplate": NSImageNameGoForwardTemplate,
+        @"NSImageNameGoBackTemplate": NSImageNameGoBackTemplate
+    };
+}
+
+NSImage* ui_cocoa_named_icon(const char *name) {
+    NSString *imageName = [[NSString alloc] initWithUTF8String:name];
+    NSString *imgName = [standardIconNames objectForKey:imageName];
+    if(imgName) {
+        imageName = imgName;
+    }
+    return [NSImage imageNamed:imageName];
+}
+
+
+void ui_image_ref(UIIMAGE img) {
+    // TODO
+}
+
+void ui_image_unref(UIIMAGE img) {
+    // TODO
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/label.h	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,33 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#import "toolkit.h"
+#import "../ui/display.h"
+
+char* ui_label_get(UiString *s);
+void ui_label_set(UiString *s, const char *str);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/label.m	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,104 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#import "label.h"
+#import "container.h"
+
+#import <string.h>
+
+static UIWIDGET create_label(UiObject *obj, UiLabelArgs *args) {
+    NSTextField *label = [[NSTextField alloc] init];
+    label.editable = NO;
+    label.bezeled = NO;
+    label.drawsBackground = NO;
+    label.selectable = NO;
+    
+    NSTextAlignment alignment;
+    switch(args->align) {
+        case UI_ALIGN_LEFT: alignment = NSTextAlignmentLeft; break;
+        case UI_ALIGN_RIGHT: alignment = NSTextAlignmentRight; break;
+        default: alignment = NSTextAlignmentCenter;
+    }
+    label.alignment = alignment;
+    
+    if(args->label) {
+        NSString *str = [[NSString alloc] initWithUTF8String:args->label];
+        label.stringValue = str;
+    }
+    
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, label, &layout);
+    
+    UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING);
+    if(var) {
+        UiString *s = var->value;
+        s->obj = (__bridge void*)label;
+        s->get = ui_label_get;
+        s->set = ui_label_set;
+        
+        if(s->value.ptr) {
+            label.stringValue = [[NSString alloc]initWithUTF8String:s->value.ptr];
+        }
+    }
+    
+    return (__bridge void*)label;
+}
+
+
+UIWIDGET ui_label_create(UiObject* obj, UiLabelArgs *args) {
+    return create_label(obj, args);
+}
+
+UIWIDGET ui_llabel_create(UiObject* obj, UiLabelArgs *args) {
+    args->align = UI_ALIGN_LEFT;
+    return create_label(obj, args);
+}
+
+UIWIDGET ui_rlabel_create(UiObject* obj, UiLabelArgs *args) {
+    args->align = UI_ALIGN_RIGHT;
+    return create_label(obj, args);
+}
+
+
+char* ui_label_get(UiString *s) {
+    NSTextField *label = (__bridge NSTextField*)s->obj;
+    if(s->value.free) {
+        s->value.free(s->value.ptr);
+    }
+    s->value.ptr = strdup(label.stringValue.UTF8String);
+    return s->value.ptr;
+}
+
+void ui_label_set(UiString *s, const char *str) {
+    NSTextField *label = (__bridge NSTextField*)s->obj;
+    if(s->value.free) {
+        s->value.free(s->value.ptr);
+    }
+    s->value.ptr = NULL;
+    label.stringValue = [[NSString alloc] initWithUTF8String:str];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/list.h	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,57 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#import "toolkit.h"
+#import "Container.h"
+#import "../ui/tree.h"
+
+#import "ListDataSource.h"
+
+@interface UiDropDown : NSObject<NSComboBoxDelegate>
+
+@property UiObject *obj;
+@property ui_callback onactivate;
+@property void *onactivatedata;
+@property ui_callback onselection;
+@property void *onselectiondata;
+@property ui_getvaluefunc2 getvalue;
+@property void *getvaluedata;
+@property UiVar *var;
+@property (weak) NSComboBox *combobox;
+
+- (id)init:(UiObject*)obj;
+
+@end
+
+void ui_tableview_update(UiList *list, int i);
+UiListSelection ui_tableview_getselection(UiList *list);
+void ui_tableview_setselection(UiList *list, UiListSelection selection);
+
+void ui_dropdown_update(UiList *list, int i);
+UiListSelection ui_dropdown_getselection(UiList *list);
+void ui_dropdown_setselection(UiList *list, UiListSelection selection);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/list.m	Sun Aug 24 15:24:16 2025 +0200
@@ -0,0 +1,356 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 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.
+ */
+
+#import "list.h"
+#import "ListDelegate.h"
+#import <objc/runtime.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);
+}
+
+static void* str_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
+    return elm;
+}
+
+/* --------------------------- ListView --------------------------- */
+
+/*
+ * adds a NSTableViewDelegate that handles all events and calls
+ * callbacks specified in the UiListArgs
+ */
+static void add_listdelegate(UiObject *obj, NSTableView *tableview, UiListArgs *args) {
+    ListDelegate *delegate = [[ListDelegate alloc] init:tableview obj:obj];
+    delegate.onactivate = args->onactivate;
+    delegate.onactivatedata = args->onactivatedata;
+    delegate.onselection = args->onselection;
+    delegate.onselectiondata = args->onselectiondata;
+    tableview.delegate = delegate;
+    objc_setAssociatedObject(tableview, "ui_listdelegate", delegate, OBJC_ASSOCIATION_RETAIN);
+    tableview.doubleAction = @selector(activateEvent:);
+    tableview.target = delegate;
+}
+
+static void bind_list_to_tableview(UiList *list, NSTableView *tableview) {
+    list->obj = (__bridge void*)tableview;
+    list->update = ui_tableview_update;
+    list->getselection = ui_tableview_getselection;
+    list->setselection = ui_tableview_setselection;
+}
+
+UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) {
+    NSScrollView *scrollview = [[NSScrollView alloc] init];
+    
+    NSTableView *tableview = [[NSTableView alloc] init];
+    tableview.autoresizingMask = NSViewWidthSizable;
+    tableview.headerView = nil;
+    
+    if(args->multiselection) {
+        tableview.allowsMultipleSelection = YES;
+    }
+    
+    scrollview.documentView = tableview;
+    
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, scrollview, &layout);
+    
+    add_listdelegate(obj, tableview, args);
+    
+    UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
+    if(var) {
+        UiList *list = var->value;
+        bind_list_to_tableview(list, tableview);
+        
+        ui_getvaluefunc2 getvalue = args->getvalue2;
+        void *getvaluedata = args->getvalue2data;
+        if(!getvalue) {
+            if(args->getvalue) {
+                getvalue = getvalue_wrapper;
+                getvaluedata = (void*)args->getvalue;
+            } else {
+                getvalue = str_getvalue; // by default list values are interpreted as strings
+            }
+        }
+        
+        NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:@"column"];
+        [tableview addTableColumn:column];
+        
+        ListDataSource *dataSource = [[ListDataSource alloc] init:tableview.tableColumns var:var getvalue:getvalue getvaluedata:getvaluedata];
+        
+        tableview.dataSource = dataSource;
+        [tableview reloadData];
+        
+        objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN);
+    }
+
+    return (__bridge void*)scrollview;
+}
+
+/* --------------------------- TableView --------------------------- */
+
+UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args) {
+    NSScrollView *scrollview = [[NSScrollView alloc] init];
+    
+    NSTableView *tableview = [[NSTableView alloc] init];
+    tableview.autoresizingMask = NSViewWidthSizable;
+    tableview.columnAutoresizingStyle = NSTableViewSequentialColumnAutoresizingStyle;
+    
+    if(args->multiselection) {
+        tableview.allowsMultipleSelection = YES;
+    }
+    
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, scrollview, &layout);
+    
+    add_listdelegate(obj, tableview, args);
+    
+    // convert model
+    NSMutableArray<NSTableColumn*> *cols = [[NSMutableArray alloc] init];
+    UiModel *model = args->model;
+    if(model) {
+        for(int i=0;i<model->columns;i++) {
+            char *title = model->titles[i];
+            UiModelType type = model->types[i];
+            int width = model->columnsize[i];
+            NSString *identifier = [[NSString alloc] initWithUTF8String:title];
+            NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:identifier];
+            column.title = identifier;
+            column.resizingMask = NSTableColumnUserResizingMask;
+            if(width > 0) {
+                column.width = width;
+            } else if(width < 0) {
+                column.resizingMask = NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask;
+            }
+            if(type >= UI_ICON) {
+                // TODO
+            }
+            [tableview addTableColumn:column];
+            [cols addObject:column];
+        }
+    }
+    
+    UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
+    if(var) {
+        UiList *list = var->value;
+        bind_list_to_tableview(list, tableview);
+        
+        ui_getvaluefunc2 getvalue = args->getvalue2;
+        void *getvaluedata = args->getvalue2data;
+        if(!getvalue) {
+            if(args->getvalue) {
+                getvalue = getvalue_wrapper;
+                getvaluedata = (void*)args->getvalue;
+            } else {
+                fprintf(stderr, "Error: tableview requires getvalue or getvalue2 func\n");
+                return (__bridge void*)scrollview;
+            }
+        }
+        
+        ListDataSource *dataSource = [[ListDataSource alloc] init:cols var:var getvalue:getvalue getvaluedata:getvaluedata];
+        if(model) {
+            dataSource.model = ui_model_copy(obj->ctx, model);
+        }
+        
+        tableview.dataSource = dataSource;
+        [tableview reloadData];
+        
+        objc_setAssociatedObject(tableview, "ui_datasource", dataSource, OBJC_ASSOCIATION_RETAIN);
+    }
+    
+    scrollview.documentView = tableview;
+
+    return (__bridge void*)scrollview;
+}
+
+/* ------ common functions ------ */
+
+void ui_tableview_update(UiList *list, int i) {
+    NSTableView *tableview = (__bridge NSTableView*)list->obj;
+    if(i < 0) {
+        [tableview reloadData];
+    } else {
+        [tableview reloadData]; // TODO: optimize
+    }
+}
+
+UiListSelection ui_tableview_getselection(UiList *list) {
+    NSTableView *tableview = (__bridge NSTableView*)list->obj;
+    return ui_tableview_selection(tableview);
+}
+
+void ui_tableview_setselection(UiList *list, UiListSelection selection) {
+    NSTableView *tableview = (__bridge NSTableView*)list->obj;
+    NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
+    for(int i=0;i<selection.count;i++) {
+        [indexSet addIndex:selection.rows[i]];
+    }
+    [tableview selectRowIndexes:indexSet byExtendingSelection:NO];
+}
+
+
+/* --------------------------- DropDown --------------------------- */
+
+@implementation UiDropDown
+
+- (id)init:(UiObject*)obj {
+    _obj = obj;
+    return self;
+}
+
+- (void) comboBoxSelectionDidChange:(NSNotification *) notification {
+    int index = (int)_combobox.indexOfSelectedItem;
+    
+    void *eventdata = NULL;
+    if(_var) {
+        UiList *list = _var->value;
+        if(index >= 0) {
+            eventdata = list->get(list, index);
+        }
+    } else {
+        NSString *str = _combobox.objectValueOfSelectedItem;
+        if(str) {
+            eventdata = (void*)str.UTF8String;
+        }
+    }
+    
+    UiEvent event;
+    event.obj = _obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = eventdata;
+    event.eventdatatype = UI_EVENT_DATA_LIST_ELM;
+    event.intval = index;
+    
+    if(_onselection) {
+        _onselection(&event, _onselectiondata);
+    }
+    
+    if(_onactivate) {
+        _onactivate(&event, _onactivatedata);
+    }
+}
+
+@end
+
+UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args) {
+    NSComboBox *dropdown = [[NSComboBox alloc] init];
+    dropdown.editable = NO;
+    
+    UiDropDown *uidropdown = [[UiDropDown alloc] init:obj];
+    objc_setAssociatedObject(dropdown, "ui_dropdown", uidropdown, OBJC_ASSOCIATION_RETAIN);
+    uidropdown.onactivate = args->onactivate;
+    uidropdown.onactivatedata = args->onactivatedata;
+    uidropdown.onselection = args->onselection;
+    uidropdown.onselectiondata = args->onselectiondata;
+    uidropdown.combobox = dropdown;
+    
+    if(!args->getvalue2) {
+        if(args->getvalue) {
+            args->getvalue2 = getvalue_wrapper;
+            args->getvalue2data = (void*)args->getvalue;
+        } else {
+            args->getvalue2 = str_getvalue;
+        }
+    }
+    uidropdown.getvalue = args->getvalue2;
+    uidropdown.getvaluedata = args->getvalue2data;
+    
+    UiLayout layout = UI_INIT_LAYOUT(args);
+    ui_container_add(obj, dropdown, &layout);
+    
+    UiVar *var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
+    if(var) {
+        UiList *list = var->value;
+        list->obj = (__bridge void*)dropdown;
+        list->update = ui_dropdown_update;
+        list->getselection = ui_dropdown_getselection;
+        list->setselection = ui_dropdown_setselection;
+        ui_dropdown_update(list, -1);
+    } else {
+        for(int i=0;i<args->static_nelm;i++) {
+            char *str = args->static_elements[i];
+            NSString *item = [[NSString alloc] initWithUTF8String:str];
+            [dropdown addItemWithObjectValue:item];
+        }
+    }
+    
+    uidropdown.var = var;
+    
+    return (__bridge void*)dropdown;
+}
+
+void ui_dropdown_update(UiList *list, int i) {
+    NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
+    UiDropDown *dropdown = objc_getAssociatedObject(combobox, "ui_dropdown");
+    if(dropdown) {
+        [combobox removeAllItems];
+        
+        ui_getvaluefunc2 getvalue = dropdown.getvalue;
+        void *getvaluedata = dropdown.getvaluedata;
+        
+        int index = 0;
+        void *elm = list->first(list);
+        while(elm) {
+            UiBool freeResult = FALSE;
+            char *str = getvalue(list, elm, index, 0, getvaluedata, &freeResult);
+            if(str) {
+                NSString *item = [[NSString alloc] initWithUTF8String:str];
+                [combobox addItemWithObjectValue:item];
+            }
+            if(freeResult) {
+                free(str);
+            }
+            elm = list->next(list);
+            index++;
+        }
+    } else {
+        fprintf(stderr, "Error: obj is not a dropdown\n");
+    }
+}
+
+UiListSelection ui_dropdown_getselection(UiList *list) {
+    UiListSelection sel = { 0, NULL };
+    NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
+    NSInteger index = combobox.indexOfSelectedItem;
+    if(index >= 0) {
+        sel.rows = malloc(sizeof(int));
+        sel.count = 1;
+        sel.rows[0] = (int)index;
+    }
+    return sel;
+}
+
+void ui_dropdown_setselection(UiList *list, UiListSelection selection) {
+    NSComboBox *combobox = (__bridge NSComboBox*)list->obj;
+    if(selection.count > 0) {
+        [combobox selectItemAtIndex:selection.rows[0]];
+    } else {
+        [combobox selectItemAtIndex: -1];
+    }
+}
--- a/ui/cocoa/menu.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/cocoa/menu.h	Sun Aug 24 15:24:16 2025 +0200
@@ -33,26 +33,38 @@
 
 @interface MenuItem : NSObject
 
+@property (weak) NSMenuItem *menuItem;
 @property (strong) NSString *itemId;
-@property UiMenuCheckItem *checkItem;
-@property UiMenuRadioItem *radioItem;
-@property ui_callback     callback;
-@property void            *userdata;
+@property UiMenuCheckItem   *checkItem;
+@property UiMenuRadioItem   *radioItem;
+@property ui_callback       callback;
+@property void              *userdata;
+@property (strong) NSString *varname;
+@property UiObject          *obj;
+@property UiVar             *var;
+@property BOOL              state;
 
 - (MenuItem*)init:(int)itId;
 
+- (void)handleToggleEvent:(id)sender;
+
 @end
 
 void ui_menu_init(void);
 
-typedef void(*ui_menu_add_f)(NSMenu*, int, UiMenuItemI*);
+typedef void(*ui_menu_add_f)(UiObject*, NSMenu*, int, UiMenuItemI*);
 
-void add_menu_widget(NSMenu *parent, int i, UiMenuItemI *item);
-void add_menuitem_widget(NSMenu *parent, int i, UiMenuItemI *item);
-void add_menuseparator_widget(NSMenu *parent, int i, UiMenuItemI *item);
-void add_checkitem_widget(NSMenu *parent, int i, UiMenuItemI *item);
-void add_radioitem_widget(NSMenu *parent, int index, UiMenuItemI *item);
-void add_checkitemnv_widget(NSMenu *parent, int i, UiMenuItemI *item);
-void add_menuitem_list_widget(NSMenu *parent, int i, UiMenuItemI *item);
+void add_menu_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item);
+void add_menuitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item);
+void add_menuseparator_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item);
+void add_checkitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item);
+void add_radioitem_widget(UiObject *obj, NSMenu *parent, int index, UiMenuItemI *item);
+void add_checkitemnv_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item);
+void add_menuitem_list_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item);
+
+void ui_add_menu_items(UiObject *obj, NSMenu *parent, int i, UiMenu *menu);
 
 NSArray* ui_get_binding_items(void);
+
+int64_t ui_menu_toggleitem_get(UiInteger *i);
+void ui_menu_toggleitem_set(UiInteger *i, int64_t value);
--- a/ui/cocoa/menu.m	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/cocoa/menu.m	Sun Aug 24 15:24:16 2025 +0200
@@ -50,6 +50,28 @@
     return self;
 }
 
+- (void)handleToggleEvent:(id)sender {
+    NSMenuItem *item = (NSMenuItem*)sender;
+    if(!_state) {
+        item.state = NSControlStateValueOn;
+    } else {
+        item.state = NSControlStateValueOff;
+    }
+    _state = !_state;
+    
+    if(_callback) {
+        UiEvent event;
+        event.obj = _obj;
+        event.window = event.obj->window;
+        event.document = event.obj->ctx->document;
+        event.eventdata = NULL;
+        event.eventdatatype = 0;
+        event.intval = _state;
+        event.set = ui_get_setop();
+        _callback(&event, _userdata);
+    }
+}
+
 @end
 
 static ui_menu_add_f createMenuItem[] = {
@@ -63,61 +85,84 @@
     /* UI_MENU_SEPARATOR       */ add_menuseparator_widget
 };
 
-static void add_menu_items(NSMenu *parent, int i, UiMenu *menu) {
+void ui_add_menu_items(UiObject *obj, NSMenu *parent, int i, UiMenu *menu) {
     UiMenuItemI *it = menu->items_begin;
     int index = 0;
     while(it) {
-        createMenuItem[it->type](parent, index, it);
+        createMenuItem[it->type](obj, parent, index, it);
         it = it->next;
         index++;
     }
 }
 
-void add_menu_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+void add_menu_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) {
     UiMenu *it = (UiMenu*)item;
     NSString *str = [[NSString alloc] initWithUTF8String:it->label];
     NSMenu *menu = [[NSMenu alloc] initWithTitle: str];
     NSMenuItem *menuItem = [parent addItemWithTitle:str action:nil keyEquivalent:@""];
     [parent setSubmenu:menu forItem:menuItem];
     
-    add_menu_items(menu, i, it);
+    ui_add_menu_items(obj, menu, i, it);
 }
 
-void add_menuitem_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+void add_menuitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) {
     UiMenuItem *it = (UiMenuItem*)item;
     
     NSString *str = [[NSString alloc] initWithUTF8String:it->label];
-    NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuItemAction:) keyEquivalent:@""];
+    NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuItemAction) keyEquivalent:@""];
     
     if(it->callback) {
         EventData *event = [[EventData alloc] init:it->callback userdata:it->userdata];
+        if(obj) {
+            event.obj = obj;
+            menuItem.target = event;
+            menuItem.action = @selector(handleEvent:);
+        }
         objc_setAssociatedObject(menuItem, "eventdata", event, OBJC_ASSOCIATION_RETAIN);
     }
 }
 
-void add_menuseparator_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+void add_menuseparator_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) {
     NSMenuItem *menuItem = [NSMenuItem separatorItem];
     [parent addItem:menuItem];
 }
 
 static int nItem = 0;
 
-void add_checkitem_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+void add_checkitem_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) {
     UiMenuCheckItem *it = (UiMenuCheckItem*)item;
     
     NSString *str = [[NSString alloc] initWithUTF8String:it->label];
     NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuCheckItemAction:) keyEquivalent:@""];
     
     MenuItem *mItem = [[MenuItem alloc] init:nItem++];
+    mItem.menuItem = menuItem;
+    mItem.obj = obj;
     mItem.callback = it->callback;
     mItem.userdata = it->userdata;
     mItem.checkItem = it;
+    if(it->varname) {
+        mItem.varname = [[NSString alloc] initWithUTF8String:it->varname];
+    }
     
     objc_setAssociatedObject(menuItem, "menuitem", mItem, OBJC_ASSOCIATION_RETAIN);
-    [bindingItems addObject:mItem];
+    
+    if(!obj) {
+        [bindingItems addObject:mItem];
+    } else {
+        mItem.var = uic_widget_var(obj->ctx, obj->ctx, NULL, it->varname, UI_VAR_INTEGER);
+        if(mItem.var) {
+            UiInteger *i = mItem.var->value;
+            i->obj = (__bridge void*)mItem;
+            i->get = ui_menu_toggleitem_get;
+            i->set = ui_menu_toggleitem_set;
+        }
+        menuItem.target = mItem;
+        menuItem.action = @selector(handleToggleEvent:);
+    }
 }
 
-void add_radioitem_widget(NSMenu *parent, int index, UiMenuItemI *item) {
+void add_radioitem_widget(UiObject *obj, NSMenu *parent, int index, UiMenuItemI *item) {
     UiMenuRadioItem *it = (UiMenuRadioItem*)item;
     
     NSString *str = [[NSString alloc] initWithUTF8String:it->label];
@@ -132,11 +177,11 @@
     [bindingItems addObject:mItem];
 }
 
-void add_checkitemnv_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+void add_checkitemnv_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) {
     
 }
 
-void add_menuitem_list_widget(NSMenu *parent, int i, UiMenuItemI *item) {
+void add_menuitem_list_widget(UiObject *obj, NSMenu *parent, int i, UiMenuItemI *item) {
     
 }
 
@@ -154,7 +199,7 @@
             NSMenuItem *menuItem = [[NSApp mainMenu] insertItemWithTitle:str action:nil keyEquivalent:@"" atIndex:index];
             [[NSApp mainMenu] setSubmenu:menu forItem:menuItem];
             
-            add_menu_items(menu, 0, ls);
+            ui_add_menu_items(NULL, menu, 0, ls);
         }
         ls = (UiMenu*)ls->item.next;
         index++;
@@ -164,3 +209,20 @@
 NSArray* ui_get_binding_items(void) {
     return bindingItems;
 }
+
+
+int64_t ui_menu_toggleitem_get(UiInteger *i) {
+    MenuItem *item = (__bridge MenuItem*)i->obj;
+    i->value = item.state;
+    return i->value;
+}
+
+void ui_menu_toggleitem_set(UiInteger *i, int64_t value) {
+    MenuItem *item = (__bridge MenuItem*)i->obj;
+    i->value = value;
+    if(value != 0) {
+        item.menuItem.state = NSControlStateValueOn;
+    } else {
+        item.menuItem.state = NSControlStateValueOff;
+    }
+}
--- a/ui/cocoa/objs.mk	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/cocoa/objs.mk	Sun Aug 24 15:24:16 2025 +0200
@@ -41,6 +41,7 @@
 COCOAOBJ += button.o
 COCOAOBJ += text.o
 COCOAOBJ += menu.o
+COCOAOBJ += Toolbar.o
 
 TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%)
 TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m)
--- a/ui/cocoa/text.m	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/cocoa/text.m	Sun Aug 24 15:24:16 2025 +0200
@@ -42,7 +42,7 @@
     scrollview.documentView = textview;
     
     UiLayout layout = UI_INIT_LAYOUT(args);
-    ui_container_add(obj, scrollview, &layout, TRUE);
+    ui_container_add(obj, scrollview, &layout);
     
     
     UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_TEXT);
@@ -85,7 +85,6 @@
     NSTextStorage *textStorage;
     if(text->data1) {
         textStorage = (__bridge NSTextStorage*)text->data1;
-        
     } else {
         textStorage = [[NSTextStorage alloc] init];
     }
@@ -94,43 +93,83 @@
 }
 
 void ui_textarea_set(UiText *text, const char *str) {
-    
+    NSTextView *textview = (__bridge NSTextView*)text->obj;
+    if(text->value.free) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = strdup(str);
+    text->value.free = free;
+    textview.string = [[NSString alloc] initWithUTF8String:str];
 }
 
 char* ui_textarea_get(UiText *text) {
-    return NULL;
+    NSTextView *textview = (__bridge NSTextView*)text->obj;
+    if(text->value.free) {
+        text->value.free(text->value.ptr);
+    }
+    text->value.ptr = strdup(textview.string.UTF8String);
+    text->value.free = free;
+    return text->value.ptr;
 }
 
 char* ui_textarea_getsubstr(UiText *text, int begin, int end) {
-    return NULL;
+    NSTextView *textview = (__bridge NSTextView*)text->obj;
+    NSString *str = textview.string;
+    NSRange range = NSMakeRange(begin, end-begin);
+    NSString *sub = [str substringWithRange:range];
+    return strdup(sub.UTF8String);
 }
 
 void ui_textarea_insert(UiText *text, int pos, char *str) {
-    
+    NSTextView *textview = (__bridge NSTextView*)text->obj;
+    NSString *s = [[NSString alloc] initWithUTF8String:str];
+    NSAttributedString *attributedStr = [[NSAttributedString alloc] initWithString:s];
+    [textview.textStorage insertAttributedString:attributedStr atIndex:pos];
 }
 
 void ui_textarea_setposition(UiText *text, int pos) {
-    
+    NSTextView *textview = (__bridge NSTextView*)text->obj;
+    NSRange range = NSMakeRange(pos, 0);
+    [textview setSelectedRange:range];
 }
 
 int ui_textarea_position(UiText *text) {
-    return 0;
+    NSTextView *textview = (__bridge NSTextView*)text->obj;
+    NSRange range = textview.selectedRange;
+    return (int)range.location;
 }
 
 void ui_textarea_setselection(UiText *text, int begin, int end) {
-    
+    NSTextView *textview = (__bridge NSTextView*)text->obj;
+    NSRange range = NSMakeRange(begin, end-begin);
+    [textview setSelectedRange:range];
 }
 
 void ui_textarea_selection(UiText *text, int *begin, int *end) {
-    
+    NSTextView *textview = (__bridge NSTextView*)text->obj;
+    NSRange range = textview.selectedRange;
+    if(begin) {
+        *begin = (int)range.location;
+    }
+    if(end) {
+        *end = (int)(range.location+range.length);
+    }
 }
 
 int ui_textarea_length(UiText *text) {
-    return 0;
+    NSTextView *textview = (__bridge NSTextView*)text->obj;
+    return (int)textview.string.length;
 }
 
 void ui_textarea_remove(UiText *text, int begin, int end) {
+    NSTextView *textview = (__bridge NSTextView*)text->obj;
     
+    if (begin < 0 || end < begin || end > textview.string.length) {
+        return;
+    }
+    
+    NSRange range = NSMakeRange(begin, end - begin);
+    [[textview textStorage] deleteCharactersInRange:range];
 }
 
 
@@ -150,7 +189,7 @@
     }
     
     UiLayout layout = UI_INIT_LAYOUT(args);
-    ui_container_add(obj, textfield, &layout, FALSE);
+    ui_container_add(obj, textfield, &layout);
     
     UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_STRING);
     if(var) {
--- a/ui/cocoa/toolkit.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/cocoa/toolkit.h	Sun Aug 24 15:24:16 2025 +0200
@@ -31,6 +31,19 @@
 #include "../common/context.h"
 #include "../common/object.h"
 
+@interface UiAppCallback : NSObject {
+    ui_threadfunc callback;
+    void          *userdata;
+}
+
+- (id) initWithCallback:(ui_threadfunc)func userdata:(void*)userdata;
+
+- (void) callMainThread;
+
+- (void) mainThread:(id)n;
+
+@end
+
 void ui_cocoa_onstartup(void);
 void ui_cocoa_onopen(const char *file);
 void ui_cocoa_onexit(void);
--- a/ui/cocoa/toolkit.m	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/cocoa/toolkit.m	Sun Aug 24 15:24:16 2025 +0200
@@ -34,7 +34,10 @@
 #include "../common/toolbar.h"
 #include "../common/threadpool.h"
 
+#import "image.h"
 #import "menu.h"
+#import "Toolbar.h"
+#import "UiThread.h"
 
 #import "AppDelegate.h"
 
@@ -70,6 +73,10 @@
     [NSApplication sharedApplication];
     //[NSBundle loadNibNamed:@"MainMenu" owner:NSApp ];
     //[[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:NSApp topLevelObjects:&topLevelObjects];
+    
+    ui_icon_init();
+    ui_toolbar_init();
+    
 }
 
 const char* ui_appname() {
@@ -154,9 +161,33 @@
 /* ------------------- Job Control / Threadpool functions ------------------- */
 
 void ui_job(UiObject *obj, ui_threadfunc tf, void *td, ui_callback f, void *fd) {
+    UiThread *thread = [[UiThread alloc]init:obj jobfunc:tf jobdata:td];
+    thread.finish_callback = f;
+    thread.finish_userdata = fd;
+    [thread start];
+}
 
+@implementation UiAppCallback
+
+- (id) initWithCallback:(ui_threadfunc)func userdata:(void*)userdata {
+    self->callback = func;
+    self->userdata = userdata;
+    return self;
 }
 
-void ui_call_mainthread(ui_threadfunc tf, void* td) {
+- (void) callMainThread {
+    [self performSelectorOnMainThread:@selector(mainThread:)
+                                   withObject:nil
+                                waitUntilDone:NO];
+}
 
+- (void) mainThread:(id)n {
+    callback(userdata);
 }
+
+@end
+
+void ui_call_mainthread(ui_threadfunc tf, void* td) {
+    UiAppCallback *cb = [[UiAppCallback alloc]initWithCallback:tf userdata:td];
+    [cb callMainThread];
+}
--- a/ui/common/args.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/common/args.c	Sun Aug 24 15:24:16 2025 +0200
@@ -758,6 +758,71 @@
 }
 
 
+/* ------------------------- UiWidgetArgs ----------------------------*/
+
+UiWidgetArgs* ui_widget_args_new(void) {
+    UiWidgetArgs *args = malloc(sizeof(UiWidgetArgs));
+    memset(args, 0, sizeof(UiWidgetArgs));
+    return args;
+}
+
+
+void ui_widget_args_set_fill(UiWidgetArgs *args, UiBool fill) {
+    args->fill = fill ? UI_ON : UI_OFF;
+}
+
+
+void ui_widget_args_set_hexpand(UiWidgetArgs *args, UiBool value) {
+    args->hexpand = value;
+}
+
+
+void ui_widget_args_set_vexpand(UiWidgetArgs *args, UiBool value) {
+    args->vexpand = value;
+}
+
+
+void ui_widget_args_set_hfill(UiWidgetArgs *args, UiBool value) {
+    args->hfill = value;
+}
+
+
+void ui_widget_args_set_vfill(UiWidgetArgs *args, UiBool value) {
+    args->vfill = value;
+}
+
+
+void ui_widget_args_set_override_defaults(UiWidgetArgs *args, UiBool value) {
+    args->override_defaults = value;
+}
+
+
+void ui_widget_args_set_colspan(UiWidgetArgs *args, int colspan) {
+    args->colspan = colspan;
+}
+
+
+void ui_widget_args_set_rowspan(UiWidgetArgs *args, int rowspan) {
+    args->rowspan = rowspan;
+}
+
+
+void ui_widget_args_set_name(UiWidgetArgs *args, const char *name) {
+    args->name = strdup(name);
+}
+
+
+void ui_widget_args_set_style_class(UiWidgetArgs *args, const char *classname) {
+    args->style_class = strdup(classname);
+}
+
+void ui_widget_args_free(UiWidgetArgs *args) {
+    free((void*)args->name);
+    free((void*)args->style_class);
+    free(args);
+}
+
+
 /* ------------------------- UiLabelArgs ----------------------------*/
 
 
@@ -1209,6 +1274,111 @@
     free(args);
 }
 
+/* ------------------------- UiLinkButtonArgs ----------------------------*/
+
+
+UiLinkButtonArgs* ui_linkbutton_args_new(void) {
+    UiLinkButtonArgs *args = malloc(sizeof(UiLinkButtonArgs));
+    memset(args, 0, sizeof(UiLinkButtonArgs));
+    return args;
+}
+
+
+void ui_linkbutton_args_set_fill(UiLinkButtonArgs *args, UiBool fill) {
+    args->fill = fill ? UI_ON : UI_OFF;
+}
+
+
+void ui_linkbutton_args_set_hexpand(UiLinkButtonArgs *args, UiBool value) {
+    args->hexpand = value;
+}
+
+
+void ui_linkbutton_args_set_vexpand(UiLinkButtonArgs *args, UiBool value) {
+    args->vexpand = value;
+}
+
+
+void ui_linkbutton_args_set_hfill(UiLinkButtonArgs *args, UiBool value) {
+    args->hfill = value;
+}
+
+
+void ui_linkbutton_args_set_vfill(UiLinkButtonArgs *args, UiBool value) {
+    args->vfill = value;
+}
+
+
+void ui_linkbutton_args_set_override_defaults(UiLinkButtonArgs *args, UiBool value) {
+    args->override_defaults = value;
+}
+
+
+void ui_linkbutton_args_set_colspan(UiLinkButtonArgs *args, int colspan) {
+    args->colspan = colspan;
+}
+
+
+void ui_linkbutton_args_set_rowspan(UiLinkButtonArgs *args, int rowspan) {
+    args->rowspan = rowspan;
+}
+
+
+void ui_linkbutton_args_set_name(UiLinkButtonArgs *args, const char *name) {
+    args->name = strdup(name);
+}
+
+
+void ui_linkbutton_args_set_style_class(UiLinkButtonArgs *args, const char *classname) {
+    args->style_class = strdup(classname);
+}
+
+void ui_linkbutton_args_set_label(UiLinkButtonArgs *args, const char *label){
+    args->label = strdup(label);
+}
+
+void ui_linkbutton_args_set_uri(UiLinkButtonArgs *args, const char *uri) {
+    args->uri = strdup(uri);
+}
+
+void ui_linkbutton_args_set_onclick(UiLinkButtonArgs *args, ui_callback callback) {
+    args->onclick = callback;
+}
+
+void ui_linkbutton_args_set_onclickdata(UiLinkButtonArgs *args, void *userdata) {
+    args->onclickdata = userdata;
+}
+
+void ui_linkbutton_args_set_nofollow(UiLinkButtonArgs *args, UiBool value) {
+    args->nofollow = value;
+}
+
+void ui_linkbutton_args_set_type(UiLinkButtonArgs *args, UiLinkType type) {
+    args->type = type;
+}
+
+void ui_linkbutton_args_set_varname(UiLinkButtonArgs *args, const char *varname) {
+    args->varname = strdup(varname);
+}
+
+void ui_linkbutton_args_set_value(UiLinkButtonArgs *args, UiString *value) {
+    args->value = value;
+}
+
+void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *groups){
+    // TODO
+}
+
+void ui_linkbutton_args_free(UiLinkButtonArgs *args) {
+    free((void*)args->name);
+    free((void*)args->style_class);
+    free((void*)args->label);
+    free((void*)args->uri);
+    free((void*)args->varname);
+    free((void*)args->groups);
+    free(args);
+}
+
 
 /* ------------------------- UiListArgs ----------------------------*/
 
--- a/ui/common/args.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/common/args.h	Sun Aug 24 15:24:16 2025 +0200
@@ -38,6 +38,7 @@
 #include "../ui/tree.h"
 #include "../ui/text.h"
 #include "../ui/webview.h"
+#include "../ui/widget.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -195,6 +196,19 @@
 UIEXPORT void ui_splitpane_args_set_max_panes(UiSplitPaneArgs *args, int max);
 UIEXPORT void ui_splitpane_args_free(UiSplitPaneArgs *args);
 
+UIEXPORT UiWidgetArgs* ui_widget_args_new(void);
+UIEXPORT void ui_widget_args_set_fill(UiWidgetArgs *args, UiBool fill);
+UIEXPORT void ui_widget_args_set_hexpand(UiWidgetArgs *args, UiBool value);
+UIEXPORT void ui_widget_args_set_vexpand(UiWidgetArgs *args, UiBool value);
+UIEXPORT void ui_widget_args_set_hfill(UiWidgetArgs *args, UiBool value);
+UIEXPORT void ui_widget_args_set_vfill(UiWidgetArgs *args, UiBool value);
+UIEXPORT void ui_widget_args_set_override_defaults(UiWidgetArgs *args, UiBool value);
+UIEXPORT void ui_widget_args_set_colspan(UiWidgetArgs *args, int colspan);
+UIEXPORT void ui_widget_args_set_rowspan(UiWidgetArgs *args, int rowspan);
+UIEXPORT void ui_widget_args_set_name(UiWidgetArgs *args, const char *name);
+UIEXPORT void ui_widget_args_set_style_class(UiWidgetArgs *args, const char *classname);
+UIEXPORT void ui_widget_args_free(UiWidgetArgs *args);
+
 UIEXPORT UiLabelArgs* ui_label_args_new(void);
 UIEXPORT void ui_label_args_set_fill(UiLabelArgs *args, UiBool fill);
 UIEXPORT void ui_label_args_set_hexpand(UiLabelArgs *args, UiBool value);
@@ -288,6 +302,28 @@
 UIEXPORT void ui_toggle_args_set_groups(UiToggleArgs *args, int *groups);
 UIEXPORT void ui_toggle_args_free(UiToggleArgs *args);
 
+UIEXPORT UiLinkButtonArgs* ui_linkbutton_args_new(void);
+UIEXPORT void ui_linkbutton_args_set_fill(UiLinkButtonArgs *args, UiBool fill);
+UIEXPORT void ui_linkbutton_args_set_hexpand(UiLinkButtonArgs *args, UiBool value);
+UIEXPORT void ui_linkbutton_args_set_vexpand(UiLinkButtonArgs *args, UiBool value);
+UIEXPORT void ui_linkbutton_args_set_hfill(UiLinkButtonArgs *args, UiBool value);
+UIEXPORT void ui_linkbutton_args_set_vfill(UiLinkButtonArgs *args, UiBool value);
+UIEXPORT void ui_linkbutton_args_set_override_defaults(UiLinkButtonArgs *args, UiBool value);
+UIEXPORT void ui_linkbutton_args_set_colspan(UiLinkButtonArgs *args, int colspan);
+UIEXPORT void ui_linkbutton_args_set_rowspan(UiLinkButtonArgs *args, int rowspan);
+UIEXPORT void ui_linkbutton_args_set_name(UiLinkButtonArgs *args, const char *name);
+UIEXPORT void ui_linkbutton_args_set_style_class(UiLinkButtonArgs *args, const char *classname);
+UIEXPORT void ui_linkbutton_args_set_varname(UiLinkButtonArgs *args, const char *varname);
+UIEXPORT void ui_linkbutton_args_set_value(UiLinkButtonArgs *args, UiString *value);
+UIEXPORT void ui_linkbutton_args_set_label(UiLinkButtonArgs *args, const char *label);
+UIEXPORT void ui_linkbutton_args_set_uri(UiLinkButtonArgs *args, const char *uri);
+UIEXPORT void ui_linkbutton_args_set_onclick(UiLinkButtonArgs *args, ui_callback callback);
+UIEXPORT void ui_linkbutton_args_set_onclickdata(UiLinkButtonArgs *args, void *userdata);
+UIEXPORT void ui_linkbutton_args_set_nofollow(UiLinkButtonArgs *args, UiBool value);
+UIEXPORT void ui_linkbutton_args_set_type(UiLinkButtonArgs *args, UiLinkType type);
+UIEXPORT void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *groups);
+UIEXPORT void ui_linkbutton_args_free(UiLinkButtonArgs *args);
+
 UIEXPORT UiListArgs* ui_list_args_new(void);
 UIEXPORT void ui_list_args_set_fill(UiListArgs *args, UiBool fill);
 UIEXPORT void ui_list_args_set_hexpand(UiListArgs *args, UiBool value);
--- a/ui/common/types.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/common/types.c	Sun Aug 24 15:24:16 2025 +0200
@@ -165,12 +165,26 @@
     cxListClear(list->data);
 }
 
-UIEXPORT void ui_list_update(UiList *list) {
+void ui_list_update(UiList *list) {
     if(list->update) {
         list->update(list, -1);
     }
 }
 
+void ui_list_update_row(UiList *list, int row) {
+    if(list->update) {
+        list->update(list, row);
+    }
+}
+
+UiListSelection ui_list_get_selection(UiList *list) {
+    if(list->getselection) {
+        return list->getselection(list);
+    } else {
+        return (UiListSelection){0, NULL};
+    }
+}
+
 void ui_list_addobsv(UiList *list, ui_callback f, void *data) {
     list->observers = ui_add_observer(list->observers, f, data);
 }
--- a/ui/common/wrapper.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/common/wrapper.c	Sun Aug 24 15:24:16 2025 +0200
@@ -218,3 +218,38 @@
 void ui_sublist_item_set_eventdata(UiSubListItem *item, void *eventdata) {
     item->eventdata = NULL;
 }
+
+/* ---------------------------- UiListSelection ---------------------------- */
+
+UiListSelection* ui_list_get_selection_allocated(UiList *list) {
+    UiListSelection *sel = malloc(sizeof(UiListSelection));
+    *sel = ui_list_get_selection(list);
+    return sel;
+    
+}
+
+int ui_list_selection_get_count(UiListSelection *sel) {
+    return sel->count;
+}
+
+int* ui_list_selection_get_rows(UiListSelection *sel) {
+    return sel->rows;
+}
+
+void ui_list_selection_free(UiListSelection *sel) {
+    ui_listselection_free(*sel);
+    free(sel);
+}
+
+/* ---------------------------- UiFileList ---------------------------- */
+
+int ui_filelist_count(UiFileList *flist) {
+    return flist->nfiles;
+}
+
+char* ui_filelist_get(UiFileList *flist, int index) {
+    if(index >= 0 && index < flist->nfiles) {
+        return flist->files[index];
+    }
+    return NULL;
+}
--- a/ui/common/wrapper.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/common/wrapper.h	Sun Aug 24 15:24:16 2025 +0200
@@ -74,6 +74,13 @@
 UIEXPORT int ui_event_get_int(UiEvent *event);
 UIEXPORT int ui_event_get_set(UiEvent *event);
 
+UIEXPORT UiListSelection* ui_list_get_selection_allocated(UiList *list);
+UIEXPORT int ui_list_selection_get_count(UiListSelection *sel);
+UIEXPORT int* ui_list_selection_get_rows(UiListSelection *sel);
+UIEXPORT void ui_list_selection_free(UiListSelection *sel);
+
+UIEXPORT int ui_filelist_count(UiFileList *flist);
+UIEXPORT char* ui_filelist_get(UiFileList *flist, int index);
 
 
 #ifdef __cplusplus
--- a/ui/gtk/button.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/gtk/button.c	Sun Aug 24 15:24:16 2025 +0200
@@ -32,6 +32,8 @@
 #include "button.h"
 #include "container.h"
 #include <cx/allocator.h>
+#include <cx/buffer.h>
+#include <cx/json.h>
 #include "../common/context.h"
 #include "../common/object.h"
 
@@ -120,6 +122,14 @@
     event->callback(&e, event->userdata);
 }
 
+void ui_button_set_label(UIWIDGET button, const char *label) {
+    gtk_button_set_label(GTK_BUTTON(button), label);
+}
+
+void ui_button_set_icon(UIWIDGET button, const char *icon) {
+    ui_button_set_icon_name(button, icon);
+}
+
 int64_t ui_toggle_button_get(UiInteger *integer) {
     GtkToggleButton *button = integer->obj;
     integer->value = (int)gtk_toggle_button_get_active(button);
@@ -594,3 +604,280 @@
     value->value = i;
 }
 #endif
+
+
+static void ui_destroy_linkbutton(GtkWidget *widget, UiLinkButton *data) {
+    free(data->link);
+    free(data);
+}
+
+static const char* linkbutton_get_uri(UiLinkButton *link) {
+    if(link->type == UI_LINK_BUTTON) {
+        return link->link;
+    } else {
+        return gtk_link_button_get_uri(GTK_LINK_BUTTON(link->widget));
+    }
+}
+
+static void linkbutton_set_uri(UiLinkButton *link, const char *uri) {
+    if(link->type == UI_LINK_BUTTON) {
+        free(link->link);
+        link->link = uri ? strdup(uri) : NULL;
+    } else {
+        gtk_link_button_set_uri(GTK_LINK_BUTTON(link->widget), uri);
+    }
+}
+
+static gboolean linkbutton_get_visited(UiLinkButton *link) {
+    if(link->type == UI_LINK_BUTTON) {
+        return FALSE;
+    } else {
+        gtk_link_button_get_visited(GTK_LINK_BUTTON(link->widget));
+    }
+}
+
+static void linkbutton_set_visited(UiLinkButton *link, gboolean visited) {
+    if(link->type != UI_LINK_BUTTON) {
+        gtk_link_button_set_visited(GTK_LINK_BUTTON(link->widget), visited);
+    }
+}
+
+/*
+ * Apply linkbutton settings from json. Expects jsonvalue to be a valid
+ * json object.
+ * 
+ * {
+ *     "label": "label text",
+ *     "uri": "http://example.com",
+ *     "visited": true
+ * }
+ * 
+ */
+static void linkbutton_apply_value(UiLinkButton *link, const char *jsonvalue) {
+    CxJson json;
+    cxJsonInit(&json, NULL);
+    cxJsonFill(&json, jsonvalue);
+    
+    CxJsonValue *value;
+    if(cxJsonNext(&json, &value) == CX_JSON_NO_ERROR) {
+        if(cxJsonIsObject(value)) {
+            CxJsonValue *label = cxJsonObjGet(value, "label");
+            CxJsonValue *uri = cxJsonObjGet(value, "uri");
+            CxJsonValue *visited = cxJsonObjGet(value, "visited");
+            if(label) {
+                gtk_button_set_label(GTK_BUTTON(link->widget), cxJsonIsString(label) ? cxJsonAsString(label) : NULL);
+            }
+            if(uri) {
+                linkbutton_set_uri(link, cxJsonIsString(uri) ? cxJsonAsString(uri) : NULL);
+                
+            }
+            if(visited) {
+                linkbutton_set_visited(link, cxJsonIsBool(visited) ? cxJsonAsBool(visited) : FALSE);
+            }
+        }        
+        cxJsonValueFree(value);
+    }
+    cxJsonDestroy(&json);
+}
+
+static char* create_linkbutton_jsonvalue(const char *label, const char *uri, gboolean include_null, gboolean visited, gboolean set_visited) {
+    CxJsonValue *obj = cxJsonCreateObj(NULL);
+    if(label) {
+        cxJsonObjPutString(obj, CX_STR("label"), label);
+    } else if(include_null) {
+        cxJsonObjPutLiteral(obj, CX_STR("label"), CX_JSON_NULL);
+    }
+    
+    if(uri) {
+        cxJsonObjPutString(obj, CX_STR("uri"), uri);
+    } else if(include_null) {
+        cxJsonObjPutLiteral(obj, CX_STR("uri"), CX_JSON_NULL);
+    }
+    
+    if(set_visited) {
+        cxJsonObjPutLiteral(obj, CX_STR("visited"), visited ? CX_JSON_TRUE : CX_JSON_FALSE);
+    }
+    
+    CxJsonWriter writer = cxJsonWriterCompact();
+    CxBuffer buf;
+    cxBufferInit(&buf, NULL, 128, NULL, CX_BUFFER_AUTO_EXTEND);
+    cxJsonWrite(&buf, obj, (cx_write_func)cxBufferWrite, &writer);
+    cxJsonValueFree(obj);
+    cxBufferTerminate(&buf);
+    
+    return buf.space;
+}
+
+static char* linkbutton_get_value(UiLinkButton *link) {
+    const char *label = gtk_button_get_label(GTK_BUTTON(link->widget));
+    const char *uri = linkbutton_get_uri(link);
+    gboolean visited = linkbutton_get_visited(link);
+    return create_linkbutton_jsonvalue(label, uri, TRUE, visited, TRUE);
+}
+
+static void linkbutton_clicked(GtkWidget *widget, UiLinkButton *data) {
+    if(data->onclick) {
+        UiEvent e;
+        e.obj = data->obj;
+        e.document = e.obj->ctx->document;
+        e.window = e.obj->window;
+        e.eventdata = (char*)linkbutton_get_uri(data);
+        e.eventdatatype = UI_EVENT_DATA_STRING;
+        e.intval = 0;
+        e.set = ui_get_setop();
+        data->onclick(&e, data->onclickdata);
+    }
+}
+
+static gboolean linkbutton_activate_link(GtkLinkButton *self, UiLinkButton *data) {
+    linkbutton_clicked(data->widget, data);
+    return data->nofollow;
+}
+
+UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args) {
+    UiLinkButton *data = malloc(sizeof(UiLinkButton));
+    memset(data, 0, sizeof(UiLinkButton));
+    data->obj = obj;
+    data->type = args->type;
+    data->nofollow = args->nofollow;
+    data->onclick = args->onclick;
+    data->onclickdata = args->onclickdata;
+    
+    GtkWidget *button;
+    if(args->type == UI_LINK_BUTTON) {
+        button = gtk_button_new();
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(linkbutton_clicked),
+                data);
+    } else {
+        button = gtk_link_button_new("file:///");
+        g_signal_connect(
+                button,
+                "activate-link",
+                G_CALLBACK(linkbutton_activate_link),
+                data);
+    }
+    gtk_button_set_label(GTK_BUTTON(button), args->label);
+    g_object_set_data(G_OBJECT(button), "ui_linkbutton", data);
+    g_signal_connect(
+            button,
+            "destroy",
+            G_CALLBACK(ui_destroy_linkbutton),
+            data);
+    
+    data->widget = button;
+    
+    UiObject* current = uic_current_obj(obj);
+    UiVar *var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_STRING);
+    if(var) {
+        UiString *str = var->value;
+        char *current_value = ui_get(str);
+        if(current_value) {
+            linkbutton_apply_value(data, current_value);
+        }
+        
+        str->obj = data;
+        str->get = ui_linkbutton_get;
+        str->set = ui_linkbutton_set;
+    }
+    
+    ui_set_name_and_style(button, args->name, args->style_class);
+    ui_set_widget_groups(obj->ctx, button, args->groups);
+    UI_APPLY_LAYOUT2(current, args);
+    current->container->add(current->container, button);
+    
+    return button;
+}
+
+char* ui_linkbutton_get(UiString *s) {
+    UiLinkButton *link = s->obj;
+    if(s->value.free) {
+        s->value.free(s->value.ptr);
+    }
+    s->value.ptr = linkbutton_get_value(link);
+    s->value.free = free;
+    return s->value.ptr;
+}
+
+void ui_linkbutton_set(UiString *s, const char *str) {
+    linkbutton_apply_value(s->obj, str);
+    if(s->value.free) {
+        s->value.free(s->value.ptr);
+    }
+}
+
+
+void ui_linkbutton_value_set(UiString *str, const char *label, const char *uri) {
+    char *value = create_linkbutton_jsonvalue(label, uri, TRUE, FALSE, TRUE);
+    ui_set(str, value);
+    free(value);
+}
+
+void ui_linkbutton_value_set_label(UiString *str, const char *label) {
+    char *value = create_linkbutton_jsonvalue(label, NULL, FALSE, FALSE, TRUE);
+    ui_set(str, value);
+    free(value);
+}
+
+void ui_linkbutton_value_set_uri(UiString *str, const char *uri) {
+    char *value = create_linkbutton_jsonvalue(NULL, uri, FALSE, FALSE, TRUE);
+    ui_set(str, value);
+    free(value);
+}
+
+void ui_linkbutton_value_set_visited(UiString *str, UiBool visited) {
+    char *value = create_linkbutton_jsonvalue(NULL, NULL, FALSE, visited, TRUE);
+    ui_set(str, value);
+    free(value);
+}
+
+
+void ui_linkbutton_set_label(UIWIDGET button, const char *label) {
+    gtk_button_set_label(GTK_BUTTON(button), label);
+}
+
+void ui_linkbutton_set_uri(UIWIDGET button, const char *label) {
+    UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton");
+    if(link) {
+        linkbutton_set_uri(link, label);
+    } else {
+        fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n");
+    }
+}
+
+void ui_linkbutton_set_visited(UIWIDGET button, UiBool visited) {
+    UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton");
+    if(link) {
+        linkbutton_set_visited(link, visited);
+    } else {
+        fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n");
+    }
+}
+
+char* ui_linkbutton_get_label(UIWIDGET button) {
+    const char *label = gtk_button_get_label(GTK_BUTTON(button));
+    return label ? strdup(label) : NULL;
+}
+
+char* ui_linkbutton_get_uri(UIWIDGET button) {
+    UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton");
+    if(link) {
+        const char *uri = linkbutton_get_uri(link);
+        return uri ? strdup(uri) : NULL;
+    } else {
+        fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n");
+    }
+    return NULL;
+}
+
+UiBool ui_linkbutton_get_visited(UIWIDGET button) {
+    UiLinkButton *link = g_object_get_data(G_OBJECT(button), "ui_linkbutton");
+    if(link) {
+        return linkbutton_get_visited(link);
+    } else {
+        fprintf(stderr, "Error: ui_linkbutton_set_label: widget is not a linkbutton\n");
+    }
+    return FALSE;
+}
--- a/ui/gtk/button.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/gtk/button.h	Sun Aug 24 15:24:16 2025 +0200
@@ -37,6 +37,16 @@
 extern "C" {
 #endif
     
+typedef struct UiLinkButton {
+    UiObject *obj;
+    GtkWidget *widget;
+    UiLinkType type;
+    UiBool nofollow;
+    char *link;
+    ui_callback onclick;
+    void *onclickdata;
+} UiLinkButton;
+    
 void ui_button_set_icon_name(GtkWidget *button, const char *icon_name);
 
 typedef void (*ui_toggled_func)(void*, void*);
@@ -89,6 +99,9 @@
 int64_t ui_radiobutton_get(UiInteger *value);
 void ui_radiobutton_set(UiInteger *value, int64_t i);
 
+char* ui_linkbutton_get(UiString *s);
+void ui_linkbutton_set(UiString *s, const char *str);
+
 #ifdef	__cplusplus
 }
 #endif
--- a/ui/gtk/display.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/gtk/display.c	Sun Aug 24 15:24:16 2025 +0200
@@ -101,7 +101,7 @@
         case UI_ALIGN_DEFAULT: break;
         case UI_ALIGN_LEFT: set_alignment(widget, 0, .5); break;
         case UI_ALIGN_RIGHT: set_alignment(widget, 1, .5); break;
-        case UI_ALIGN_CENTER: break; // TODO
+        case UI_ALIGN_CENTER: set_alignment(widget, .5, .5); break;
     }
     
 
--- a/ui/gtk/headerbar.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/gtk/headerbar.c	Sun Aug 24 15:24:16 2025 +0200
@@ -162,9 +162,10 @@
     gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(menubutton), G_MENU_MODEL(menu));
 #else
     GtkWidget *menubutton = gtk_menu_button_new();
-    
-    // TODO
-    
+    GtkWidget *menu = gtk_menu_new();
+    ui_add_menu_items(menu, 0, &item->menu, obj); 
+    gtk_widget_show_all(menu);
+    gtk_menu_button_set_popup(GTK_MENU_BUTTON(menubutton), menu);
     
 #endif
     
--- a/ui/gtk/list.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/gtk/list.c	Sun Aug 24 15:24:16 2025 +0200
@@ -39,21 +39,22 @@
 #include <cx/linked_list.h>
 
 #include "list.h"
+#include "button.h"
 #include "icon.h"
 #include "menu.h"
 #include "dnd.h"
 
 
-void* ui_strmodel_getvalue(void *elm, int column) {
-    return column == 0 ? elm : NULL;
+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);
 }
 
-static void* model_getvalue(UiModel *model, UiList *list, void *elm, int row, int col, UiBool *freeResult) {
-    if(model->getvalue2) {
-        return model->getvalue2(list, elm, row, col, model->getvalue2data, freeResult);
-    } else if(model->getvalue) {
-        return model->getvalue(elm, col);
-    }
+static void* str_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
+    return elm;
+}
+
+static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
     return NULL;
 }
 
@@ -74,6 +75,37 @@
     }
 }
 
+static UiListView* create_listview(UiObject *obj, UiListArgs *args) {
+    UiListView *tableview = malloc(sizeof(UiListView));
+    memset(tableview, 0, sizeof(UiListView));
+    tableview->obj = obj;
+    tableview->model = args->model;
+    tableview->onactivate = args->onactivate;
+    tableview->onactivatedata = args->onactivatedata;
+    tableview->onselection = args->onselection;
+    tableview->onselectiondata = args->onselectiondata;
+    tableview->ondragstart = args->ondragstart;
+    tableview->ondragstartdata = args->ondragstartdata;
+    tableview->ondragcomplete = args->ondragcomplete;
+    tableview->ondragcompletedata = args->ondragcompletedata;
+    tableview->ondrop = args->ondrop;
+    tableview->ondropdata = args->ondropdata;
+    tableview->selection.count = 0;
+    tableview->selection.rows = NULL;
+    
+    if(args->getvalue2) {
+        tableview->getvalue = args->getvalue2;
+        tableview->getvaluedata = args->getvalue2data;
+    } else if(args->getvalue) {
+        tableview->getvalue = getvalue_wrapper;
+        tableview->getvaluedata = (void*)args->getvalue;
+    } else {
+        tableview->getvalue = null_getvalue;
+    }
+    
+    return tableview;
+}
+
 #if GTK_CHECK_VERSION(4, 10, 0)
 
 
@@ -131,16 +163,17 @@
     }
 }
 
-static void column_factory_bind( GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
+static void column_factory_bind(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) {
     UiColData *col = userdata;
     UiList *list = col->listview->var ? col->listview->var->value : NULL;
+    UiListView *listview = col->listview;
     
     ObjWrapper *obj = gtk_list_item_get_item(item);
     UiModel *model = col->listview->model;
     UiModelType type = model->types[col->model_column];
     
     UiBool freevalue = FALSE;
-    void *data = model_getvalue(model, list, obj->data, obj->i, col->data_column, &freevalue);
+    void *data = listview->getvalue(list, obj->data, obj->i, col->data_column, listview->getvaluedata, &freevalue);
     GtkWidget *child = gtk_list_item_get_child(item);
     
     switch(type) {
@@ -172,7 +205,7 @@
             
         }
         case UI_ICON_TEXT_FREE: {
-            void *data2 = model_getvalue(model, list, obj->data, obj->i, col->data_column+1, &freevalue);
+            void *data2 = listview->getvalue(list, obj->data, obj->i, col->data_column+1, listview->getvaluedata, &freevalue);
             if(type == UI_ICON_TEXT_FREE) {
                 freevalue = TRUE;
             }
@@ -199,55 +232,32 @@
         selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(liststore)));
     } else {
         selection_model = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(liststore)));
+        gtk_single_selection_set_can_unselect(GTK_SINGLE_SELECTION(selection_model), TRUE);
+        gtk_single_selection_set_autoselect(GTK_SINGLE_SELECTION(selection_model), FALSE);
     }
     g_signal_connect(selection_model, "selection-changed", G_CALLBACK(ui_listview_selection_changed), listview);
     return selection_model;
 }
 
-static UiListView* create_listview(UiObject *obj, UiListArgs *args) {
-    UiListView *tableview = malloc(sizeof(UiListView));
-    memset(tableview, 0, sizeof(UiListView));
-    tableview->obj = obj;
-    tableview->model = args->model;
-    tableview->onactivate = args->onactivate;
-    tableview->onactivatedata = args->onactivatedata;
-    tableview->onselection = args->onselection;
-    tableview->onselectiondata = args->onselectiondata;
-    tableview->ondragstart = args->ondragstart;
-    tableview->ondragstartdata = args->ondragstartdata;
-    tableview->ondragcomplete = args->ondragcomplete;
-    tableview->ondragcompletedata = args->ondragcompletedata;
-    tableview->ondrop = args->ondrop;
-    tableview->ondropdata = args->ondropdata;
-    tableview->selection.count = 0;
-    tableview->selection.rows = NULL;
-    return tableview;
-}
-
 UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) {
     UiObject* current = uic_current_obj(obj);
     
     // to simplify things and share code with ui_table_create, we also
     // use a UiModel for the listview
     UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
-    if(args->getvalue2) {
-        model->getvalue2 = args->getvalue2;
-        model->getvalue2data = args->getvalue2data;
-    } else if(args->getvalue) {
-        model->getvalue = args->getvalue;
-    } else {
-        model->getvalue = ui_strmodel_getvalue;
-    }
     args->model = model;
     
     GListStore *ls = g_list_store_new(G_TYPE_OBJECT);
     UiListView *listview = create_listview(obj, args);
+    if(!args->getvalue && !args->getvalue2) {
+        listview->getvalue = str_getvalue;
+    }
     
     listview->columns = malloc(sizeof(UiColData));
     listview->columns->listview = listview;
     listview->columns->data_column = 0;
     listview->columns->model_column = 0;
-    
+     
     GtkListItemFactory *factory = gtk_signal_list_item_factory_new();
     g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns);
     g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns);
@@ -280,7 +290,7 @@
         ui_update_liststore(ls, list);
     } else if (args->static_elements && args->static_nelm > 0) {
         listview_copy_static_elements(listview, args->static_elements, args->static_nelm);
-        listview->model->getvalue = ui_strmodel_getvalue; // force strmodel
+        listview->getvalue = str_getvalue; // force string values
         ui_update_liststore_static(ls, listview->elements, listview->nelm);
     }
     
@@ -320,19 +330,15 @@
     // to simplify things and share code with ui_tableview_create, we also
     // use a UiModel for the listview
     UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
-    if(args->getvalue2) {
-        model->getvalue2 = args->getvalue2;
-        model->getvalue2data = args->getvalue2data;
-    } else if(args->getvalue) {
-        model->getvalue = args->getvalue;
-    } else {
-        model->getvalue = ui_strmodel_getvalue;
-    }
     args->model = model;
     
     GListStore *ls = g_list_store_new(G_TYPE_OBJECT);
     UiListView *listview = create_listview(obj, args);
     
+    if(!args->getvalue && !args->getvalue2) {
+        listview->getvalue = str_getvalue;
+    }
+    
     listview->columns = malloc(sizeof(UiColData));
     listview->columns->listview = listview;
     listview->columns->data_column = 0;
@@ -370,7 +376,7 @@
         ui_update_liststore(ls, list);
     } else if (args->static_elements && args->static_nelm > 0) {
         listview_copy_static_elements(listview, args->static_elements, args->static_nelm);
-        listview->model->getvalue = ui_strmodel_getvalue; // force strmodel
+        listview->getvalue = str_getvalue; // force string values
         ui_update_liststore_static(ls, listview->elements, listview->nelm);
     }
     
@@ -626,14 +632,20 @@
         void *value = list->get(list, i);
         if(value) {
             ObjWrapper *obj = obj_wrapper_new(value, i);
+            UiListSelection sel = list->getselection(list);
             // TODO: if index i is selected, the selection is lost
             // is it possible to update the item without removing it?
+            // workaround: save selection and reapply it
             int count = g_list_model_get_n_items(G_LIST_MODEL(view->liststore));
             if(count <= i) {
                 g_list_store_splice(view->liststore, i, 0, (void **)&obj, 1);
             } else {
                 g_list_store_splice(view->liststore, i, 1, (void **)&obj, 1);
             }
+            if(sel.count > 0) {
+                list->setselection(list, sel);
+            }
+            ui_listselection_free(sel);
         }
     }
 }
@@ -695,12 +707,13 @@
 
 #else
 
-static void update_list_row(GtkListStore *store, GtkTreeIter *iter, UiModel *model, UiList *list, void *elm, int row) {
+static void update_list_row(UiListView *listview, GtkListStore *store, GtkTreeIter *iter, UiList *list, void *elm, int row) {
+    UiModel *model = listview->model;
     // set column values
     int c = 0;
     for(int i=0;i<model->columns;i++,c++) {
         UiBool freevalue = FALSE;
-        void *data = model_getvalue(model, list, elm, row, c, &freevalue);
+        void *data = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue);
 
         GValue value = G_VALUE_INIT;
         switch(model->types[i]) {
@@ -765,7 +778,7 @@
                 c++;
                 
                 freevalue = FALSE;
-                char *str = model_getvalue(model, list, elm, row, c, &freevalue);
+                char *str = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue);
                 g_value_init(&value, G_TYPE_STRING);
                 g_value_set_string(&value, str);
                 if(model->types[i] == UI_ICON_TEXT_FREE || freevalue) {
@@ -779,7 +792,8 @@
     }
 }
 
-static GtkListStore* create_list_store(UiList *list, UiModel *model) {
+static GtkListStore* create_list_store(UiListView *listview, UiList *list) {
+    UiModel *model = listview->model;
     int columns = model->columns;
     GType types[2*columns];
     int c = 0;
@@ -807,7 +821,7 @@
             GtkTreeIter iter;
             gtk_list_store_insert (store, &iter, -1);
             
-            update_list_row(store, &iter, model, list, elm, i++);
+            update_list_row(listview, store, &iter, list, elm, i++);
             
             // next row
             elm = list->next(list);
@@ -841,36 +855,29 @@
 #endif
     
     UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
-    if(args->getvalue2) {
-        model->getvalue2 = args->getvalue2;
-        model->getvalue2data = args->getvalue2data;
-    } else if(args->getvalue) {
-        model->getvalue = args->getvalue;
-    } else {
-        model->getvalue = ui_strmodel_getvalue;
+    
+    UiListView *listview = create_listview(obj, args);
+    if(!args->getvalue && !args->getvalue2) {
+        listview->getvalue = str_getvalue;
     }
-    
-    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST);
-    
-    UiList *list = var ? var->value : NULL;
-    GtkListStore *listmodel = create_list_store(list, model);
-    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
-    g_object_unref(listmodel);
-    
-    UiListView *listview = malloc(sizeof(UiListView));
-    memset(listview, 0, sizeof(UiListView));
-    listview->obj = obj;
-    listview->widget = view;
-    listview->var = var;
     listview->model = model;
-    listview->selection.count = 0;
-    listview->selection.rows = NULL;
     g_signal_connect(
                 view,
                 "destroy",
                 G_CALLBACK(ui_listview_destroy),
                 listview);
     
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST);
+    
+    // init listview
+    listview->widget = view;
+    listview->var = var;
+    
+    UiList *list = var ? var->value : NULL;
+    GtkListStore *listmodel = create_list_store(listview, list);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    g_object_unref(listmodel);
+    
     // bind var
     list->update = ui_listview_update;
     list->getselection = ui_listview_getselection;
@@ -921,7 +928,7 @@
     SCROLLEDWINDOW_SET_CHILD(scroll_area, view);
     
     UI_APPLY_LAYOUT2(current, args);
-    current->container->add(current->container, scroll_area, FALSE);
+    current->container->add(current->container, scroll_area);
     
     // ct->current should point to view, not scroll_area, to make it possible
     // to add a context menu
@@ -1006,36 +1013,23 @@
     
     UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST);
     
-    UiList *list = var ? var->value : NULL;
-    GtkListStore *listmodel = create_list_store(list, model);
-    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
-    g_object_unref(listmodel);
-    
     //g_signal_connect(view, "drag-begin", G_CALLBACK(drag_begin), NULL);
     //g_signal_connect(view, "drag-end", G_CALLBACK(drag_end), NULL);
        
     // add TreeView as observer to the UiList to update the TreeView if the
     // data changes
-    UiListView *tableview = malloc(sizeof(UiListView));
-    memset(tableview, 0, sizeof(UiListView));
-    tableview->obj = obj;
-    tableview->widget = view;
-    tableview->var = var;
-    tableview->model = model;
-    tableview->ondragstart = args->ondragstart;
-    tableview->ondragstartdata = args->ondragstartdata;
-    tableview->ondragcomplete = args->ondragcomplete;
-    tableview->ondragcompletedata = args->ondragcompletedata;
-    tableview->ondrop = args->ondrop;
-    tableview->ondropdata = args->ondropdata;
-    tableview->selection.count = 0;
-    tableview->selection.rows = NULL;
+    UiListView *tableview = create_listview(obj, args);
     g_signal_connect(
                 view,
                 "destroy",
                 G_CALLBACK(ui_listview_destroy),
                 tableview);
     
+    UiList *list = var ? var->value : NULL;
+    GtkListStore *listmodel = create_list_store(tableview, list);
+    gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(listmodel));
+    g_object_unref(listmodel);
+    
     // bind var
     list->update = ui_listview_update;
     list->getselection = ui_listview_getselection;
@@ -1098,7 +1092,7 @@
     }
     
     UI_APPLY_LAYOUT2(current, args);
-    current->container->add(current->container, scroll_area, FALSE);
+    current->container->add(current->container, scroll_area);
     
     // ct->current should point to view, not scroll_area, to make it possible
     // to add a context menu
@@ -1112,7 +1106,7 @@
 void ui_listview_update(UiList *list, int i) {
     UiListView *view = list->obj;
     if(i < 0) {
-        GtkListStore *store = create_list_store(list, view->model);
+        GtkListStore *store = create_list_store(view, list);
         gtk_tree_view_set_model(GTK_TREE_VIEW(view->widget), GTK_TREE_MODEL(store));
         g_object_unref(G_OBJECT(store));
     } else {
@@ -1120,7 +1114,7 @@
         GtkTreeModel *store = gtk_tree_view_get_model(GTK_TREE_VIEW(view->widget));
         GtkTreeIter iter;
         if(gtk_tree_model_iter_nth_child(store, &iter, NULL, i)) {
-            update_list_row(GTK_LIST_STORE(store), &iter, view->model, list, elm, i);
+            update_list_row(view, GTK_LIST_STORE(store), &iter, list, elm, i);
         }
     }
 }
@@ -1150,46 +1144,41 @@
 UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args) {
     UiObject* current = uic_current_obj(obj);
     
-    UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1);
-    if(args->getvalue2) {
-        model->getvalue2 = args->getvalue2;
-        model->getvalue2data = args->getvalue2data;
-    } else if(args->getvalue) {
-        model->getvalue = args->getvalue;
-    } else {
-        model->getvalue = ui_strmodel_getvalue;
-    }
+    GtkWidget *combobox = gtk_combo_box_new();
     
-    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST);
-    
-    GtkWidget *combobox = ui_create_combobox(obj, model, var, args->static_elements, args->static_nelm, args->onactivate, args->onactivatedata);
     ui_set_name_and_style(combobox, args->name, args->style_class);
     ui_set_widget_groups(obj->ctx, combobox, args->groups);
     UI_APPLY_LAYOUT2(current, args);
-    current->container->add(current->container, combobox, FALSE);
+    current->container->add(current->container, combobox);
     current->container->current = combobox;
-    return combobox;
-}
-
-GtkWidget* ui_create_combobox(UiObject *obj, UiModel *model, UiVar *var, char **elm, size_t nelm, ui_callback f, void *udata) {
-    GtkWidget *combobox = gtk_combo_box_new();
-       
-    UiListView *uicbox = malloc(sizeof(UiListView));
-    memset(uicbox, 0, sizeof(UiListView));
-    uicbox->obj = obj;
-    uicbox->widget = combobox;
+    
+    UiListView *listview = create_listview(obj, args);
+    listview->widget = combobox;
+    listview->model = ui_model(obj->ctx, UI_STRING, "", -1);
+    g_signal_connect(
+                combobox,
+                "destroy",
+                G_CALLBACK(ui_listview_destroy),
+                listview);
     
+    UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->list, args->varname, UI_VAR_LIST);
     UiList *list = var ? var->value : NULL;
-    GtkListStore *listmodel = create_list_store(list, model);
-    
-    if(!list && elm && nelm > 0) {
-        listview_copy_static_elements(uicbox, elm, nelm);
-        for(int i=0;i<nelm;i++) {
+    GtkListStore *listmodel = create_list_store(listview, list);
+    if(var) {
+        listview->var = var;
+        list->update = ui_combobox_modelupdate;
+        list->getselection = ui_combobox_getselection;
+        list->setselection = ui_combobox_setselection;
+        list->obj = listview;
+        list->update(list, -1);
+    } else if(args->static_nelm > 0) {
+        listview_copy_static_elements(listview, args->static_elements, args->static_nelm);
+        for(int i=0;i<args->static_nelm;i++) {
             GtkTreeIter iter;
             GValue value = G_VALUE_INIT;
             gtk_list_store_insert(listmodel, &iter, -1);
             g_value_init(&value, G_TYPE_STRING);
-            g_value_set_string(&value, uicbox->elements[i]);
+            g_value_set_string(&value, listview->elements[i]);
             gtk_list_store_set_value(listmodel, &iter, 0, &value);
         }
     }
@@ -1199,23 +1188,6 @@
         g_object_unref(listmodel);
     }
     
-    uicbox->var = var;
-    uicbox->model = model;
-    
-    g_signal_connect(
-                combobox,
-                "destroy",
-                G_CALLBACK(ui_combobox_destroy),
-                uicbox);
-    
-    // bind var
-    if(list) {
-        list->update = ui_combobox_modelupdate;
-        list->getselection = ui_combobox_getselection;
-        list->setselection = ui_combobox_setselection;
-        list->obj = uicbox;
-    }
-    
     GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
     gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combobox), renderer, TRUE);
     gtk_cell_layout_set_attributes(
@@ -1227,13 +1199,13 @@
     gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), 0);
     
     // add callback
-    if(f) {
+    if(args->onactivate) {
         UiEventData *event = ui_malloc(obj->ctx, sizeof(UiEventData));
         event->obj = obj;
-        event->userdata = udata;
-        event->callback = f;
+        event->userdata = args->onactivatedata;
+        event->callback = args->onactivate;
         event->value = 0;
-        event->customdata = uicbox;
+        event->customdata = listview;
 
         g_signal_connect(
                 combobox,
@@ -1268,7 +1240,7 @@
 
 void ui_combobox_modelupdate(UiList *list, int i) {
     UiListView *view = list->obj;
-    GtkListStore *store = create_list_store(view->var->value, view->model);
+    GtkListStore *store = create_list_store(view, list);
     gtk_combo_box_set_model(GTK_COMBO_BOX(view->widget), GTK_TREE_MODEL(store));
     g_object_unref(store);
 }
@@ -1709,19 +1681,6 @@
     free(v);
 }
 
-void ui_combobox_destroy(GtkWidget *w, UiListView *v) {
-    if(v->var) {
-        ui_destroy_boundvar(v->obj->ctx, v->var);
-    }
-    if(v->elements) {
-        for(int i=0;i<v->nelm;i++) {
-            free(v->elements[i]);
-        }
-        free(v->elements);
-    }
-    free(v);
-}
-
 
 /* ------------------------------ Source List ------------------------------ */
 
@@ -1801,16 +1760,16 @@
     uisublist.listbox = uilistbox;
     uisublist.userdata = sublist->userdata;
     uisublist.index = cxListSize(sublists);
-
+    uisublist.startpos = 0;
+    cxListAdd(sublists, &uisublist);
+    
     // bind UiList
     UiListBoxSubList *sublist_ptr = cxListAt(uilistbox->sublists, cxListSize(sublists)-1);
-    UiList *list = uisublist.var->value;
-    if(list) {
+    if(uisublist.var && uisublist.var->value) {
+        UiList *list = uisublist.var->value;
         list->obj = sublist_ptr;
         list->update = ui_listbox_list_update;
     }
-
-    cxListAdd(sublists, &uisublist);
 }
 
 UIEXPORT UIWIDGET ui_sourcelist_create(UiObject *obj, UiSourceListArgs *args) {
@@ -1883,6 +1842,11 @@
     g_object_set_data(G_OBJECT(scroll_area), "ui_listbox", uilistbox);
     g_object_set_data(G_OBJECT(listbox), "ui_listbox", uilistbox);
     
+    if(args->contextmenu) {
+        UIMENU menu = ui_contextmenu_create(args->contextmenu, obj, listbox);
+        ui_widget_set_contextmenu(listbox, menu);
+    }
+    
     // signals
     g_signal_connect(
                 listbox,
@@ -1952,11 +1916,37 @@
         }
         
         // reload sublist
+        sublist->startpos = pos;
         ui_listbox_update_sublist(listbox, sublist, pos);
         pos += sublist->numitems;
     }
 }
 
+static void listbox_button_clicked(GtkWidget *widget, UiEventDataExt *data) {
+    UiListBoxSubList *sublist = data->customdata0;
+    
+    UiSubListEventData eventdata;
+    eventdata.list = sublist->var->value;
+    eventdata.sublist_index = sublist->index;
+    eventdata.row_index = data->value0;
+    eventdata.sublist_userdata = sublist->userdata;
+    eventdata.row_data = eventdata.list->get(eventdata.list, eventdata.row_index);
+    eventdata.event_data = data->customdata2;
+    
+    UiEvent event;
+    event.obj = data->obj;
+    event.window = event.obj->window;
+    event.document = event.obj->ctx->document;
+    event.eventdata = &eventdata;
+    event.eventdatatype = UI_EVENT_DATA_SUBLIST;
+    event.intval = data->value0;
+    event.set = ui_get_setop();
+    
+    if(data->callback2) {
+        data->callback2(&event, data->userdata2);
+    }
+}
+
 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) {
@@ -1966,7 +1956,9 @@
     GtkWidget *label = gtk_label_new(item->label);
     gtk_widget_set_halign(label, GTK_ALIGN_START);
     BOX_ADD_EXPAND(hbox, label);
-    // TODO: badge, button
+    if(item->badge) {
+        
+    }
     GtkWidget *row = gtk_list_box_row_new();
     LISTBOX_ROW_SET_CHILD(row, hbox);
     
@@ -1991,6 +1983,34 @@
     
     g_object_set_data(G_OBJECT(row), "ui-listbox-row-eventdata", event);
     
+    // badge
+    if(item->badge) {
+        GtkWidget *badge = gtk_label_new(item->badge);
+        WIDGET_ADD_CSS_CLASS(badge, "ui-badge");
+#if GTK_CHECK_VERSION(4, 0, 0)
+        gtk_widget_set_valign(badge, GTK_ALIGN_CENTER);
+        BOX_ADD(hbox, badge);
+#else
+        GtkWidget *align = gtk_alignment_new(0.5, 0.5, 0, 0);
+        gtk_container_add(GTK_CONTAINER(align), badge);
+        BOX_ADD(hbox, align);
+#endif
+    }
+    // button
+    if(item->button_icon || item->button_label) {
+        GtkWidget *button = gtk_button_new();
+        gtk_button_set_label(GTK_BUTTON(button), item->button_label);
+        ui_button_set_icon_name(button, item->button_icon);
+        WIDGET_ADD_CSS_CLASS(button, "flat");
+        BOX_ADD(hbox, button);
+        g_signal_connect(
+                button,
+                "clicked",
+                G_CALLBACK(listbox_button_clicked),
+                event
+                );
+    }
+    
     return row;
 }
 
@@ -2005,6 +2025,9 @@
     sublist->numitems = 0;
     
     // create items for each UiList element
+    if(!sublist->var) {
+        return;
+    }
     UiList *list = sublist->var->value;
     if(!list) {
         return;
@@ -2012,6 +2035,20 @@
     
     size_t index = 0;
     void *elm = list->first(list);
+    
+    if(!elm && sublist->header) {
+        // empty row for header
+        GtkWidget *row = gtk_list_box_row_new();
+        cxListAdd(sublist->widgets, row);
+        g_object_set_data(G_OBJECT(row), "ui_listbox", listbox);
+        g_object_set_data(G_OBJECT(row), "ui_listbox_sublist", sublist);
+        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);
+        sublist->numitems = 1;
+        return;
+    }
+    
     while(elm) {
         UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL };
         if(listbox->getvalue) {
@@ -2055,6 +2092,14 @@
 
 void ui_listbox_list_update(UiList *list, int i) {
     UiListBoxSubList *sublist = list->obj;
+    ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos);
+    size_t pos = 0;
+    CxIterator it = cxListIterator(sublist->listbox->sublists);
+    cx_foreach(UiListBoxSubList *, ls, it) {
+        ls->startpos = pos;
+        pos += sublist->numitems;
+    }
+    
 }
 
 void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) {
--- a/ui/gtk/list.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/gtk/list.h	Sun Aug 24 15:24:16 2025 +0200
@@ -45,6 +45,8 @@
     GtkWidget         *widget;
     UiVar             *var;
     UiModel           *model;
+    ui_getvaluefunc2  getvalue;
+    void              *getvaluedata;
     char              **elements;
     size_t            nelm;
 #if GTK_CHECK_VERSION(4, 10, 0)
@@ -91,6 +93,7 @@
     UiListBox   *listbox;
     void        *userdata;
     size_t      index;
+    size_t      startpos;
 } UiListBoxSubList;
 
 struct UiListBox {
--- a/ui/gtk/text.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/gtk/text.c	Sun Aug 24 15:24:16 2025 +0200
@@ -1118,7 +1118,7 @@
             pathtf);
     
     UI_APPLY_LAYOUT2(current, args);
-    current->container->add(current->container, eventbox, FALSE);
+    current->container->add(current->container, eventbox);
     
     // hbox as parent for the GtkEntry and GtkButtonBox
     GtkWidget *hbox = ui_gtk_hbox_new(0);
--- a/ui/gtk/toolkit.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/gtk/toolkit.c	Sun Aug 24 15:24:16 2025 +0200
@@ -395,6 +395,15 @@
 "  margin-top: 4px;\n"
 "  margin-bottom: 10px;\n"
 "}\n"
+".ui-badge {\n"
+"  background-color: #e53935;\n"
+"  color: white;\n"
+"  border-radius: 9999px;\n"
+"  padding: 0px 10px 0px 10px;\n"
+"  font-weight: bold;\n"
+"  margin-left: 4px;"
+"  margin-right: 4px;"
+"}\n"
 ;
 
 #elif GTK_MAJOR_VERSION == 3
@@ -428,6 +437,15 @@
 "  margin-top: 4px;\n"
 "  margin-bottom: 10px;\n"
 "}\n"
+".ui-badge {\n"
+"  background-color: #e53935;\n"
+"  color: white;\n"
+"  border-radius: 9999px;\n"
+"  padding: 0px 10px 0px 10px;\n"
+"  font-weight: bold;\n"
+"  margin-left: 4px;"
+"  margin-right: 4px;"
+"}\n"
 ;
 #endif
 
--- a/ui/gtk/webview.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/gtk/webview.c	Sun Aug 24 15:24:16 2025 +0200
@@ -55,7 +55,7 @@
         value->set = ui_webview_set;
         value->obj = data;
         if(value->value && value->type && !strcmp(value->type, UI_WEBVIEW_OBJECT_TYPE)) {
-            // TODO
+            ui_webview_set(value, value->value, UI_WEBVIEW_OBJECT_TYPE);
         }
     }
     
--- a/ui/gtk/window.c	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/gtk/window.c	Sun Aug 24 15:24:16 2025 +0200
@@ -313,7 +313,6 @@
     evt.window = evt.obj->window;
     evt.eventdata = NULL;
     evt.eventdatatype = 0;
-    evt.eventdatatype = 0;
     evt.intval = 0;
     
     if(!strcmp(response, "btn1")) {
@@ -410,7 +409,7 @@
     WINDOW_DESTROY(GTK_WIDGET(self));
 }
 
-void ui_dialog_create(UiObject *parent, UiDialogArgs args) {
+void ui_dialog_create(UiObject *parent, UiDialogArgs *args) {
     GtkDialog *dialog = GTK_DIALOG(gtk_dialog_new());
     gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget));
     gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
--- a/ui/qt/button.cpp	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/qt/button.cpp	Sun Aug 24 15:24:16 2025 +0200
@@ -48,6 +48,16 @@
     return button;
 }
 
+void ui_button_set_label(UIWIDGET button, const char *label) {
+    QString str = QString::fromUtf8(label);
+    QAbstractButton *b = (QAbstractButton*)button;
+    b->setText(str);
+}
+
+void ui_button_set_icon(UIWIDGET button, const char *icon) {
+    // TODO
+}
+
 static void togglebutton_event(UiEvent *event, UiEventWrapper *wrapper) {
     QPushButton *button = (QPushButton*)wrapper->customdata1;
     event->intval = button->isChecked();
--- a/ui/qt/container.cpp	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/qt/container.cpp	Sun Aug 24 15:24:16 2025 +0200
@@ -35,6 +35,7 @@
 #include <QSpacerItem>
 #include <QStackedWidget>
 #include <QLabel>
+#include <QDockWidget>
 
 static void delete_container(UiContainerPrivate *ct) {
     delete ct;
@@ -228,7 +229,7 @@
     }
 }
 
-UIEXPORT UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) {
+UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) {
     UiContainerPrivate *ctn = (UiContainerPrivate*)ui_obj_container(obj);
     UI_APPLY_LAYOUT(ctn->layout, args);
     
@@ -251,6 +252,25 @@
 }
 
 
+/* ---------------------------- UiSidebar ---------------------------- */
+
+UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) {
+    QVariant v = obj->widget->property("ui_sidebar");
+    QDockWidget *dock = (QDockWidget*)v.value<void*>();
+    if(!dock) {
+        fprintf(stderr, "Error: window is not configured for sidebar\n");
+        return nullptr;
+    }
+    
+    QWidget *widget = new QWidget();
+    QBoxLayout *box = new QBoxLayout(QBoxLayout::TopToBottom);
+    widget->setLayout(box);
+    dock->setWidget(widget);
+    
+    ui_container_add(obj, new UiBoxContainer(box));
+    
+    return dock;
+}
 
 /* -------------------- Container Helper Functions -------------------- */
 
--- a/ui/qt/list.cpp	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/qt/list.cpp	Sun Aug 24 15:24:16 2025 +0200
@@ -42,6 +42,10 @@
     return getvalue(elm, col);
 }
 
+static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
+    return NULL;
+}
+
 UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args) {
     UiContainerPrivate *ctn = ui_obj_container(obj);
     UI_APPLY_LAYOUT(ctn->layout, args);
@@ -102,7 +106,17 @@
     
     UiVar* var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
     
-    TableModel *model = new TableModel(obj, view, var, args->model);
+    ui_getvaluefunc2 getvalue = args->getvalue2;
+    void *getvaluedata = args->getvalue2data;
+    if(!getvalue) {
+        if(args->getvalue) {
+            getvalue = getvalue_wrapper;
+            getvaluedata = (void*)args->getvalue;
+        } else {
+            getvalue = null_getvalue;
+        }
+    }
+    TableModel *model = new TableModel(obj, view, var, args->model, getvalue, getvaluedata);
     view->setModel(model);
     
     if(var) {
--- a/ui/qt/model.cpp	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/qt/model.cpp	Sun Aug 24 15:24:16 2025 +0200
@@ -28,14 +28,6 @@
 
 #include "model.h"
 
-static void* model_getvalue(UiModel *model, UiList *list, void *elm, int row, int col, UiBool *freeResult) {
-    if(model->getvalue2) {
-        return model->getvalue2(list, elm, row, col, model->getvalue2data, freeResult);
-    } else if(model->getvalue) {
-        return model->getvalue(elm, col);
-    }
-    return NULL;
-}
 
 ListModel::ListModel(UiObject *obj, QListView *view, UiVar *var, ui_getvaluefunc2 getvalue, void *getvaluedata){
     this->obj = obj;
@@ -116,11 +108,13 @@
 
 
 
-TableModel::TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model){
+TableModel::TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model, ui_getvaluefunc2 getvalue, void *getvaluedata){
     this->obj = obj;
     this->view = view;
     this->var = var;
     this->model = model;
+    this->getvalue = getvalue;
+    this->getvaluedata = getvaluedata;
     this->onactivate = nullptr;
     this->onactivatedata = nullptr;
     this->onselection = nullptr;
@@ -162,7 +156,7 @@
         if(rowData) {
             int col = index.column();
             UiBool freeResult = false;
-            void *value = model_getvalue(model, ls, rowData, index.row(), col, &freeResult);
+            void *value = getvalue(ls, rowData, index.row(), col, getvaluedata, &freeResult);
             if(value) {
                 UiModelType type = model->types[col];
                 switch(type) {
--- a/ui/qt/model.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/qt/model.h	Sun Aug 24 15:24:16 2025 +0200
@@ -45,7 +45,7 @@
     Q_OBJECT
     
     ui_getvaluefunc2 getvalue;
-    void *getvaluedata;
+    void        *getvaluedata;
     ui_callback onactivate;
     void        *onactivatedata;
     ui_callback onselection;
@@ -75,18 +75,20 @@
 class TableModel : public QAbstractListModel {
     Q_OBJECT
     
-    UiModel     *model;
-    ui_callback onactivate;
-    void        *onactivatedata;
-    ui_callback onselection;
-    void        *onselectiondata;
+    UiModel          *model;
+    ui_getvaluefunc2 getvalue;
+    void             *getvaluedata;
+    ui_callback      onactivate;
+    void             *onactivatedata;
+    ui_callback      onselection;
+    void             *onselectiondata;
     
 public:
-    UiObject    *obj;
-    UiVar       *var;
-    QTreeView   *view;
+    UiObject         *obj;
+    UiVar            *var;
+    QTreeView        *view;
     
-    TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model);
+    TableModel(UiObject *obj, QTreeView *view, UiVar *var, UiModel *model, ui_getvaluefunc2 getvalue, void *getvaluedata);
     
     void setActivationCallback(ui_callback f, void *userdata);
     void setSelectionCallback(ui_callback f, void *userdata);
--- a/ui/qt/window.cpp	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/qt/window.cpp	Sun Aug 24 15:24:16 2025 +0200
@@ -38,8 +38,10 @@
 #include <QVBoxLayout>
 #include <QFileDialog>
 #include <QPushButton>
+#include <QDockWidget>
+#include <QMessageBox>
 
-static UiObject* create_window(const char *title, void *window_data, bool simple) {
+static UiObject* create_window(const char *title, void *window_data, bool simple, bool sidebar = false) {
     UiObject *obj = uic_object_new_toplevel();
     obj->window = window_data;
     obj->next = NULL;
@@ -61,19 +63,70 @@
     boxWidget->setLayout(box);
     window->setCentralWidget(boxWidget);
     ui_container_add(obj, new UiBoxContainer(box));
+    if(sidebar) {
+        QDockWidget *dock = new QDockWidget();
+        window->addDockWidget(Qt::LeftDockWidgetArea, dock);
+        window->setProperty("ui_sidebar", QVariant::fromValue((void*)dock));
+    }
     
     obj->widget = window;
     return obj;
 }
 
 UiObject* ui_window(const char *title, void *window_data) {
-    return create_window(title, window_data, FALSE);
+    return create_window(title, window_data, false);
 }
 
 UiObject* ui_simplewindow(char *title, void *window_data) {
-    return create_window(title, window_data, TRUE);
+    return create_window(title, window_data, true);
+}
+
+UiObject *ui_sidebar_window(const char *title, void *window_data) {
+    return create_window(title, window_data, false, true);
 }
 
+void ui_dialog_create(UiObject *parent, UiDialogArgs *args) {
+    if(args->input || args->password) {
+        // TODO: QInputDialog
+    } else {
+        QMessageBox msgBox;
+        if(args->title) {
+            msgBox.setWindowTitle(args->title);
+        }
+        if(args->content) {
+            msgBox.setText(args->content);
+        }
+        QPushButton *btn1;
+        QPushButton *btn2;
+        if(args->button1_label) {
+            btn1 = msgBox.addButton(args->button1_label, QMessageBox::ActionRole);
+        }
+        if(args->button2_label) {
+            btn2 = msgBox.addButton(args->button2_label, QMessageBox::ActionRole);
+        }
+        if(args->closebutton_label) {
+            msgBox.addButton(args->closebutton_label, QMessageBox::DestructiveRole);
+        }
+        
+        msgBox.exec();
+        
+        UiEvent evt;
+        evt.obj = parent;
+        evt.document = evt.obj->ctx->document;
+        evt.window = evt.obj->window;
+        evt.eventdata = NULL;
+        evt.eventdatatype = 0;
+        evt.intval = 0;
+        if(msgBox.clickedButton() == btn1) {
+            evt.intval = 1;
+        } else if(msgBox.clickedButton() == btn2) {
+            evt.intval = 2;
+        }
+        if(args->result) {
+            args->result(&evt, args->resultdata);
+        }
+    }
+}
 
 char* ui_openfiledialog(UiObject *obj) {
     QString fileName = QFileDialog::getOpenFileName(obj->widget);
--- a/ui/ui/button.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/ui/button.h	Sun Aug 24 15:24:16 2025 +0200
@@ -35,6 +35,12 @@
 extern "C" {
 #endif
 
+enum UiLinkType {
+    UI_LINK_TEXT = 0,
+    UI_LINK_BUTTON
+};
+typedef enum UiLinkType UiLinkType;
+    
 typedef struct UiButtonArgs {
     UiBool fill;
     UiBool hexpand;
@@ -81,21 +87,59 @@
     
     const int* groups;
 } UiToggleArgs;
+
+typedef struct UiLinkButtonArgs {
+    UiBool fill;
+    UiBool hexpand;
+    UiBool vexpand;
+    UiBool hfill;
+    UiBool vfill;
+    UiBool override_defaults;
+    int colspan;
+    int rowspan;
+    const char *name;
+    const char *style_class;
+    
+    const char *label;
+    const char *uri;
+    UiString *value;
+    const char *varname;
+    ui_callback onclick;
+    void *onclickdata;
+    UiBool nofollow;
+    UiLinkType type;
+    
+    const int* groups;
+} UiLinkButtonArgs;
  
 #define ui_button(obj, ...) ui_button_create(obj, &(UiButtonArgs){ __VA_ARGS__ } )
 #define ui_togglebutton(obj, ...) ui_togglebutton_create(obj, &(UiToggleArgs){ __VA_ARGS__ } )
 #define ui_checkbox(obj, ...) ui_checkbox_create(obj, &(UiToggleArgs){ __VA_ARGS__ } )
 #define ui_switch(obj, ...) ui_switch_create(obj, &(UiToggleArgs){ __VA_ARGS__ } )
 #define ui_radiobutton(obj, ...) ui_radiobutton_create(obj, &(UiToggleArgs){ __VA_ARGS__ } )
+#define ui_linkbutton(obj, ...) ui_linkbutton_create(obj, &(UiLinkButtonArgs){ __VA_ARGS__ })
 
-UIEXPORT UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args);
-UIEXPORT UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs *args);
-UIEXPORT UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs *args);
-UIEXPORT UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args);
-UIEXPORT UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs *args);
+UIEXPORT UIWIDGET ui_button_create(UiObject *obj, UiButtonArgs *args);
+UIEXPORT UIWIDGET ui_togglebutton_create(UiObject *obj, UiToggleArgs *args);
+UIEXPORT UIWIDGET ui_checkbox_create(UiObject *obj, UiToggleArgs *args);
+UIEXPORT UIWIDGET ui_switch_create(UiObject *obj, UiToggleArgs *args);
+UIEXPORT UIWIDGET ui_radiobutton_create(UiObject *obj, UiToggleArgs *args);
+UIEXPORT UIWIDGET ui_linkbutton_create(UiObject *obj, UiLinkButtonArgs *args);
+
+UIEXPORT void ui_button_set_label(UIWIDGET button, const char *label);
+UIEXPORT void ui_button_set_icon(UIWIDGET button, const char *icon);
 
+UIEXPORT void ui_linkbutton_value_set(UiString *str, const char *label, const char *uri);
+UIEXPORT void ui_linkbutton_value_set_label(UiString *str, const char *label);
+UIEXPORT void ui_linkbutton_value_set_uri(UiString *str, const char *uri);
+UIEXPORT void ui_linkbutton_value_set_visited(UiString *str, UiBool visited);
 
-
+UIEXPORT void ui_linkbutton_set_label(UIWIDGET button, const char *label);
+UIEXPORT void ui_linkbutton_set_uri(UIWIDGET button, const char *label);
+UIEXPORT void ui_linkbutton_set_visited(UIWIDGET button, UiBool visited);
+UIEXPORT char* ui_linkbutton_get_label(UIWIDGET button);
+UIEXPORT char* ui_linkbutton_get_uri(UIWIDGET button);
+UIEXPORT UiBool ui_linkbutton_get_visited(UIWIDGET button);
 
 #ifdef	__cplusplus
 }
--- a/ui/ui/display.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/ui/display.h	Sun Aug 24 15:24:16 2025 +0200
@@ -38,15 +38,7 @@
 #ifdef __cplusplus
 extern "C" {
 #endif
-    
-enum UiAlignment {
-    UI_ALIGN_DEFAULT = 0,
-    UI_ALIGN_LEFT,
-    UI_ALIGN_RIGHT,
-    UI_ALIGN_CENTER
-};
-
-typedef enum UiAlignment UiAlignment;
+   
 
 enum UiLabelStyle {
     UI_LABEL_STYLE_DEFAULT = 0,
--- a/ui/ui/toolkit.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/ui/toolkit.h	Sun Aug 24 15:24:16 2025 +0200
@@ -237,7 +237,16 @@
     UI_DND_ACTION_LINK,
     UI_DND_ACTION_CUSTOM
 } UiDnDAction;
-  
+
+enum UiAlignment {
+    UI_ALIGN_DEFAULT = 0,
+    UI_ALIGN_LEFT,
+    UI_ALIGN_RIGHT,
+    UI_ALIGN_CENTER
+};
+
+typedef enum UiAlignment UiAlignment;
+
 typedef void(*ui_callback)(UiEvent*, void*); /* event, user data */
 
 typedef void*(*ui_getvaluefunc)(void *elm, int col);
@@ -611,6 +620,8 @@
 UIEXPORT void ui_list_remove(UiList *list, int i);
 UIEXPORT void ui_list_clear(UiList *list);
 UIEXPORT void ui_list_update(UiList *list);
+UIEXPORT void ui_list_update_row(UiList *list, int row);
+UIEXPORT UiListSelection ui_list_get_selection(UiList *list);
 UIEXPORT void ui_list_addobsv(UiList *list, ui_callback f, void *data);
 UIEXPORT void ui_list_notify(UiList *list);
 
--- a/ui/ui/tree.h	Sun Jul 20 22:04:39 2025 +0200
+++ b/ui/ui/tree.h	Sun Aug 24 15:24:16 2025 +0200
@@ -76,25 +76,6 @@
      * array of column size hints
      */
     int *columnsize;
-    
-    /*
-     * void*(*ui_getvaluefunc)(void *elm, int col);
-     * 
-     * function for translating model data to view data
-     * first argument is the pointer returned by UiList first|next|get
-     * second argument is the column index
-     * TODO: return
-     */
-    ui_getvaluefunc getvalue;
-    
-    /*
-     * void*(*ui_getvaluefunc2)(UiList *list, void *elm, int row, int col, void *userdata)
-     * 
-     * alternative for getvalue
-     */
-    ui_getvaluefunc2 getvalue2;
-    
-    void *getvalue2data;
 };
 
 struct UiListCallbacks {
@@ -248,12 +229,23 @@
      */
     ui_callback onbuttonclick;
     void        *onbuttonclickdata;
+    
+    UiMenuBuilder *contextmenu;
 };
 
 #define UI_SUBLIST(...) (UiSubList){ __VA_ARGS__ }
 #define UI_SUBLISTS(...) (UiSubList[]){ __VA_ARGS__, (UiSubList){NULL,NULL,NULL,0} }
 
 
+/*
+ * Creates an UiModel, that specifies columns for a table widget.
+ *
+ * For each column a column type (UiModelType) and a title string
+ * (char*) must be specified. The column list must be terminated
+ * with -1.
+ *
+ * UiModel *model = ui_model(ctx, UI_STRING, "Column 1", UI_STRING, "Column 2", -1);
+ */
 UIEXPORT UiModel* ui_model(UiContext *ctx, ...);
 UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model);
 UIEXPORT void ui_model_free(UiContext *ctx, UiModel *mi);

mercurial