implement splitview window (Cocoa)

Tue, 07 Oct 2025 15:42:18 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 07 Oct 2025 15:42:18 +0200
changeset 811
1391ba7e533f
parent 810
7b5ba65b246f
child 812
29c19fcae088
child 826
e596cfc1ca46

implement splitview window (Cocoa)

make/xcode/toolkit/toolkit.xcodeproj/project.pbxproj file | annotate | diff | comparison | revisions
make/xcode/toolkit/toolkit/main.m file | annotate | diff | comparison | revisions
ui/cocoa/BoxContainer.m file | annotate | diff | comparison | revisions
ui/cocoa/GridLayout.h file | annotate | diff | comparison | revisions
ui/cocoa/GridLayout.m file | annotate | diff | comparison | revisions
ui/cocoa/MainWindow.h file | annotate | diff | comparison | revisions
ui/cocoa/MainWindow.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/window.m file | annotate | diff | comparison | revisions
--- a/make/xcode/toolkit/toolkit.xcodeproj/project.pbxproj	Tue Oct 07 14:59:11 2025 +0200
+++ b/make/xcode/toolkit/toolkit.xcodeproj/project.pbxproj	Tue Oct 07 15:42:18 2025 +0200
@@ -46,6 +46,9 @@
 		ED65815F2CFF4BF200F5402F /* WindowManager.m in Sources */ = {isa = PBXBuildFile; fileRef = ED65815E2CFF4BF200F5402F /* WindowManager.m */; };
 		ED679B0A2E5B266C001D4F71 /* label.m in Sources */ = {isa = PBXBuildFile; fileRef = ED679B092E5B266C001D4F71 /* label.m */; };
 		ED679B0D2E5B2FB0001D4F71 /* UiThread.m in Sources */ = {isa = PBXBuildFile; fileRef = ED679B0C2E5B2FB0001D4F71 /* UiThread.m */; };
+		ED6FB03D2E95466F006C6E8E /* args.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6FB0382E95466F006C6E8E /* args.c */; };
+		ED6FB03E2E95466F006C6E8E /* container.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6FB03A2E95466F006C6E8E /* container.c */; };
+		ED6FB03F2E95466F006C6E8E /* wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = ED6FB03C2E95466F006C6E8E /* wrapper.c */; };
 		ED83C2BF2E8EA49200054B22 /* BoxContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = ED83C2BE2E8EA49200054B22 /* BoxContainer.m */; };
 		ED8687E52D999CF3002F3EC2 /* menu.m in Sources */ = {isa = PBXBuildFile; fileRef = ED8687E42D999CF3002F3EC2 /* menu.m */; };
 		ED99F04A2E5CBD2E00A4CC97 /* widget.m in Sources */ = {isa = PBXBuildFile; fileRef = ED99F0492E5CBD2E00A4CC97 /* widget.m */; };
@@ -156,6 +159,12 @@
 		ED679B092E5B266C001D4F71 /* label.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = label.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/label.m; sourceTree = "<absolute>"; };
 		ED679B0B2E5B2FB0001D4F71 /* UiThread.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = UiThread.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/UiThread.h; sourceTree = "<absolute>"; };
 		ED679B0C2E5B2FB0001D4F71 /* UiThread.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = UiThread.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/UiThread.m; sourceTree = "<absolute>"; };
+		ED6FB0372E95466F006C6E8E /* args.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = args.h; path = /Users/olaf/Projekte/toolkit/ui/common/args.h; sourceTree = "<absolute>"; };
+		ED6FB0382E95466F006C6E8E /* args.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = args.c; path = /Users/olaf/Projekte/toolkit/ui/common/args.c; sourceTree = "<absolute>"; };
+		ED6FB0392E95466F006C6E8E /* container.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = container.h; path = /Users/olaf/Projekte/toolkit/ui/common/container.h; sourceTree = "<absolute>"; };
+		ED6FB03A2E95466F006C6E8E /* container.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = container.c; path = /Users/olaf/Projekte/toolkit/ui/common/container.c; sourceTree = "<absolute>"; };
+		ED6FB03B2E95466F006C6E8E /* wrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wrapper.h; path = /Users/olaf/Projekte/toolkit/ui/common/wrapper.h; sourceTree = "<absolute>"; };
+		ED6FB03C2E95466F006C6E8E /* wrapper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wrapper.c; path = /Users/olaf/Projekte/toolkit/ui/common/wrapper.c; sourceTree = "<absolute>"; };
 		ED83C2BD2E8EA49200054B22 /* BoxContainer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BoxContainer.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/BoxContainer.h; sourceTree = "<absolute>"; };
 		ED83C2BE2E8EA49200054B22 /* BoxContainer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BoxContainer.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/BoxContainer.m; sourceTree = "<absolute>"; };
 		ED8687E32D999CF3002F3EC2 /* menu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = menu.h; path = ../../../ui/cocoa/menu.h; sourceTree = SOURCE_ROOT; };
@@ -216,6 +225,12 @@
 		ED6580D92CFF19DB00F5402F /* common */ = {
 			isa = PBXGroup;
 			children = (
+				ED6FB0372E95466F006C6E8E /* args.h */,
+				ED6FB0382E95466F006C6E8E /* args.c */,
+				ED6FB0392E95466F006C6E8E /* container.h */,
+				ED6FB03A2E95466F006C6E8E /* container.c */,
+				ED6FB03B2E95466F006C6E8E /* wrapper.h */,
+				ED6FB03C2E95466F006C6E8E /* wrapper.c */,
 				ED6580DA2CFF19F900F5402F /* condvar.h */,
 				ED6580DB2CFF19F900F5402F /* condvar.c */,
 				ED6580DC2CFF19F900F5402F /* context.h */,
@@ -440,6 +455,9 @@
 			buildActionMask = 2147483647;
 			files = (
 				ED679B0A2E5B266C001D4F71 /* label.m in Sources */,
+				ED6FB03D2E95466F006C6E8E /* args.c in Sources */,
+				ED6FB03E2E95466F006C6E8E /* container.c in Sources */,
+				ED6FB03F2E95466F006C6E8E /* wrapper.c in Sources */,
 				ED6580EE2CFF19F900F5402F /* context.c in Sources */,
 				ED6580EF2CFF19F900F5402F /* menu.c in Sources */,
 				EDCD22352E59F3B1000612AF /* ListDataSource.m in Sources */,
--- a/make/xcode/toolkit/toolkit/main.m	Tue Oct 07 14:59:11 2025 +0200
+++ b/make/xcode/toolkit/toolkit/main.m	Tue Oct 07 15:42:18 2025 +0200
@@ -63,8 +63,9 @@
     return doc;
 }
 
+static int clickcount = 0;
 static void action_button(UiEvent *event, void *userdata) {
-    printf("button click\n");
+    printf("button click %d\n", clickcount++);
 }
 
 static void toolbar_action(UiEvent *event, void *userdata) {
@@ -94,113 +95,33 @@
     return elm;
 }
 
+
+
 void application_startup(UiEvent *event, void *data) {
-    UiObject *obj = ui_window("My Window", NULL);
+    UiObject *obj = ui_splitview_window("My Window", TRUE);
     //WindowData *wdata = ui_malloc(obj->ctx, sizeof(WindowData));
     //wdata->tbtoggle = ui_int_new(obj->ctx, "tbtoggle");
     //obj->window = wdata;
     MyDocument *doc = create_doc();
     ui_attach_document(obj->ctx, doc);
     
-    /*
+    
     ui_sidebar(obj) {
-        ui_button(obj, .label = "Sidebar Button");
-        ui_textarea(obj, .varname = "todo: fix layout", .fill = TRUE);
-    }
-    */
-    //ui_button(obj, .label = "Button 1");
-    //ui_button(obj, .label = "Button 2");
-    
-    /*
-    ui_grid(obj, .spacing = 0) {
-        ui_button(obj, .label = "HBox Button 1", .hfill = TRUE);
-        ui_button(obj, .label = "HBox Button 2", .hexpand = TRUE, .hfill = TRUE);
-        ui_button(obj, .label = "HBox Button 3", .hfill = TRUE);
-        ui_newline(obj);
-        ui_grid(obj, .spacing = 0, .colspan = 3) {
-            ui_button(obj, .label = "V1 Button 1");
-            ui_newline(obj);
-            ui_textarea(obj, .varname = "text1", .vexpand = TRUE, .vfill = TRUE);
-            ui_newline(obj);
-            ui_button(obj, .label = "V1 Button 3");
+        ui_vbox(obj, .margin = 0, .fill = TRUE) {
+            //ui_button(obj, .label = "Button");
+            ui_textarea(obj, .varname = "text", .fill = TRUE);
         }
-        ui_newline(obj);
-        ui_button(obj, .label = "HBox 2 Button 1", .hfill = TRUE);
-        ui_button(obj, .label = "HBox 2 Button 2", .hfill = TRUE);
-        ui_button(obj, .label = "HBox 2 Button 3");
-    }
-    //*/
-    
-    ui_grid(obj, .fill = TRUE, .margin = 10, .columnspacing = 10, .rowspacing = 10) {
-        ui_button(obj, .label = "Button 1");
-        ui_button(obj, .label = "Button 2");
-        ui_button(obj, .label = "Button 3", .hfill = TRUE, .hexpand = TRUE);
-        ui_newline(obj);
-        
-        ui_button(obj, .label = "Button X2", .colspan = 3, .hfill = TRUE);
-        ui_newline(obj);
-        
-        ui_textarea(obj, .varname = "text", .fill = TRUE, .colspan = 3);
     }
     
-    /*
-    ui_button(obj, .label = "HBox Button 1");
-    ui_button(obj, .label = "HBox Button 2");
-    ui_grid(obj, .fill = TRUE, .columnspacing = 20, .rowspacing = 10, .margin = 50) {
-        ui_hbox(obj, .spacing = 8, .colspan = 1) {
-            ui_button(obj, .label = "HBox Button 1");
-            ui_button(obj, .label = "HBox Button 2");
-            ui_button(obj, .label = "HBox Button 3");
-            ui_button(obj, .label = "HBox Button 4");
-        }
-        ui_newline(obj);
-        
-        ui_spinbox(obj, .width = 300, .varname = "number", .digits = 2, .step = 0.1);
-        ui_button(obj, .label = "spinbox", .hfill = TRUE);
-        ui_newline(obj);
-        ui_textfield(obj, .width = 300);
-        ui_button(obj, .label = "textfield", .hfill = TRUE);
+    
+    ui_left_panel0(obj) {
+        ui_button(obj, .label = "left");
     }
-    ui_button(obj, .label = "HBox Button 3");
-    */
     
-    /*
-    ui_grid(obj, .spacing = 0, .fill = TRUE) {
-        ui_button(obj, .label = "HBox Button 1", .hfill = TRUE);
-        ui_button(obj, .label = "HBox Button 2", .hexpand = TRUE, .hfill = TRUE);
-        ui_button(obj, .label = "HBox Button 3", .hfill = TRUE);
-        ui_newline(obj);
-        ui_grid(obj, .spacing = 0, .fill = TRUE, .colspan = 3) {
-            ui_button(obj, .label = "V1 Button 1");
-            ui_newline(obj);
-            ui_textarea(obj, .varname = "text2", .hfill = TRUE, .vfill = TRUE, .hexpand = TRUE, .vexpand = TRUE);
-        }
+    ui_right_panel0(obj) {
+        ui_button(obj, .label = "right");
     }
-    */
-    
-    //ui_button(obj, .label = "Button 3");
-    /*
-    ui_button(obj, .label = "Button 1", .hexpand = TRUE,  .hfill = TRUE);
-    ui_newline(obj);
-    
-    ui_grid(obj, .margin = 10, .hexpand = TRUE, .hfill = TRUE) {
-        ui_button(obj, .label = "H1");
-        ui_button(obj, .label = "H2 Ext", .hexpand = TRUE, .hfill = TRUE);
-        ui_button(obj, .label = "H3");
-    }
-    ui_newline(obj);
-    
-    ui_grid(obj, .fill = TRUE) {
-        ui_button(obj, .label = "B1");
-        ui_newline(obj);
-        
-    }
-    ui_newline(obj);
-    
-    ui_button(obj, .label = "Button 2", .hexpand = TRUE, .hfill = TRUE);
-    ui_newline(obj);
-    */
-    
+
     
     UiModel *model = ui_model(obj->ctx, UI_STRING, "Column 0", UI_STRING, "Column 1", UI_STRING, "Column 2", -1);
     model->columnsize[1] = -1;
--- a/ui/cocoa/BoxContainer.m	Tue Oct 07 14:59:11 2025 +0200
+++ b/ui/cocoa/BoxContainer.m	Tue Oct 07 15:42:18 2025 +0200
@@ -21,12 +21,11 @@
     } else {
         layout.vexpand = TRUE;
         layout.vfill = TRUE;
-        self.newline = FALSE;
     }
     self.uilayout = layout;
     [super addView:view margin:margin];
     if(_orientation == NSUserInterfaceLayoutOrientationVertical) {
-        self.newline = TRUE;
+        self.container->newline = TRUE;
     }
 }
 
--- a/ui/cocoa/GridLayout.h	Tue Oct 07 14:59:11 2025 +0200
+++ b/ui/cocoa/GridLayout.h	Tue Oct 07 15:42:18 2025 +0200
@@ -56,6 +56,8 @@
 
 @interface GridLayout : NSView<Container>
 
+@property UiContainerX *container;
+
 @property int columnspacing;
 @property int rowspacing;
 @property CxList *children;
--- a/ui/cocoa/GridLayout.m	Tue Oct 07 14:59:11 2025 +0200
+++ b/ui/cocoa/GridLayout.m	Tue Oct 07 15:42:18 2025 +0200
@@ -32,9 +32,8 @@
 
 @implementation GridLayout
 
-@synthesize label=_label;
 @synthesize uilayout=_uilayout;
-@synthesize newline=_newline;
+@synthesize container=_container;
 
 - (GridLayout*)init {
     self = [super init];
@@ -84,10 +83,6 @@
             GridDef *col = &cols[x];
             GridDef *row = &rows[y];
             
-            if(elm->margin.left + elm->margin.right > 0) {
-                printf(""); // break
-            }
-            
             NSSize size = elm->view.intrinsicContentSize;
             NSSize size2 = elm->view.fittingSize;
             if(size.width == NSViewNoIntrinsicMetric) {
@@ -321,10 +316,10 @@
     _preferredSize.width = -1;
     _preferredSize.height = -1;
     
-    if(_newline) {
+    if(self.container->newline) {
         _y++;
         _x = 0;
-        _newline = FALSE;
+        self.container->newline = FALSE;
     }
     
     GridElm elm;
--- a/ui/cocoa/MainWindow.h	Tue Oct 07 14:59:11 2025 +0200
+++ b/ui/cocoa/MainWindow.h	Tue Oct 07 15:42:18 2025 +0200
@@ -32,8 +32,11 @@
 @interface MainWindow : NSWindow
 
 @property (strong) NSView *sidebar;
+@property (strong) NSView *leftPanel;
+@property (strong) NSView *rightPanel;
+@property int topOffset;
 
-- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)sidebar;
+- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)hasSidebar withSplitview:(BOOL)hasSplitview;
 
 @end
 
--- a/ui/cocoa/MainWindow.m	Tue Oct 07 14:59:11 2025 +0200
+++ b/ui/cocoa/MainWindow.m	Tue Oct 07 15:42:18 2025 +0200
@@ -39,7 +39,7 @@
 
 @implementation MainWindow
 
-- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)sidebar {
+- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)hasSidebar withSplitview:(BOOL)hasSplitview{
     NSRect frame = NSMakeRect(300, 200, 600, 500);
     
     self = [self initWithContentRect:frame
@@ -58,12 +58,15 @@
     
     int top = 4;
     NSView *content = self.contentView;
-    if(sidebar) {
+    
+    // A sidebar or splitview window need a NSSplitView
+    NSSplitView *splitview;
+    if(hasSidebar || hasSplitview) {
         self.styleMask |= NSWindowStyleMaskFullSizeContentView;
         self.titleVisibility = NSWindowTitleHidden;
         self.titlebarAppearsTransparent = YES;
         
-        NSSplitView *splitview = [[NSSplitView alloc]init];
+        splitview = [[NSSplitView alloc]init];
         splitview.vertical = YES;
         splitview.dividerStyle = NSSplitViewDividerStyleThin;
         splitview.translatesAutoresizingMaskIntoConstraints = false;
@@ -76,27 +79,44 @@
             [splitview.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor]
         ]];
         
-        _sidebar = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)];
-        [splitview addArrangedSubview:_sidebar];
-        
-        content = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)];
-        [splitview addArrangedSubview:content];
-        
         top = 34;
     }
     
-    // create a vertical stackview as default container
-    BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0];
-    //GridLayout *vbox = [[GridLayout alloc] init];
-    vbox.translatesAutoresizingMaskIntoConstraints = false;
-    [content addSubview:vbox];
-    [NSLayoutConstraint activateConstraints:@[
-        [vbox.topAnchor constraintEqualToAnchor:content.topAnchor constant:top],
-        [vbox.leadingAnchor constraintEqualToAnchor:content.leadingAnchor],
-        [vbox.trailingAnchor constraintEqualToAnchor:content.trailingAnchor],
-        [vbox.bottomAnchor constraintEqualToAnchor:content.bottomAnchor],
-    ]];
-    uic_object_push_container(obj, ui_create_container(obj, vbox));
+    if(hasSidebar) {
+        // add the sidebar
+        _sidebar = [[NSView alloc]initWithFrame:NSMakeRect(0,0,0,0)];
+        _sidebar.translatesAutoresizingMaskIntoConstraints = NO;
+        [splitview addArrangedSubview:_sidebar];
+        [_sidebar.widthAnchor constraintGreaterThanOrEqualToConstant:250].active = YES;
+    }
+    if(hasSplitview) {
+        // add the splitview window left/right panels
+        _leftPanel = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)];
+        [splitview addArrangedSubview:_leftPanel];
+        _rightPanel = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)];
+        [splitview addArrangedSubview:_rightPanel];
+    } else if(hasSidebar) {
+        // sidebar only window: add content view
+        content = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)];
+        [splitview addArrangedSubview:content];
+    }
+    
+    // normal or sidebar-only windows get a container
+    if(!hasSplitview) {
+        // create a vertical stackview as default container
+        BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0];
+        //GridLayout *vbox = [[GridLayout alloc] init];
+        vbox.translatesAutoresizingMaskIntoConstraints = false;
+        [content addSubview:vbox];
+        [NSLayoutConstraint activateConstraints:@[
+            [vbox.topAnchor constraintEqualToAnchor:content.topAnchor constant:top],
+            [vbox.leadingAnchor constraintEqualToAnchor:content.leadingAnchor],
+            [vbox.trailingAnchor constraintEqualToAnchor:content.trailingAnchor],
+            [vbox.bottomAnchor constraintEqualToAnchor:content.bottomAnchor],
+        ]];
+        uic_object_push_container(obj, ui_create_container(obj, vbox));
+    }
+    _topOffset = top;
     
     return self;
 }
@@ -325,3 +345,36 @@
     
     return NULL;
 }
+
+static UIWIDGET splitview_window_add_panel(UiObject *obj, NSView *panel, UiSidebarArgs *args) {
+    MainWindow *window = (__bridge MainWindow*)obj->wobj;
+    BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0];
+    //GridLayout *vbox = [[GridLayout alloc] init];
+    vbox.translatesAutoresizingMaskIntoConstraints = false;
+    [panel addSubview:vbox];
+    [NSLayoutConstraint activateConstraints:@[
+        [vbox.topAnchor constraintEqualToAnchor:panel.topAnchor constant:window.topOffset],
+        [vbox.leadingAnchor constraintEqualToAnchor:panel.leadingAnchor],
+        [vbox.trailingAnchor constraintEqualToAnchor:panel.trailingAnchor],
+        [vbox.bottomAnchor constraintEqualToAnchor:panel.bottomAnchor],
+    ]];
+    uic_object_push_container(obj, ui_create_container(obj, vbox));
+    return (__bridge void*)vbox;
+}
+
+UIWIDGET ui_left_panel_create(UiObject *obj, UiSidebarArgs *args) {
+    MainWindow *window = (__bridge MainWindow*)obj->wobj;
+    if(window.leftPanel == nil) {
+        return NULL;
+    }
+    return splitview_window_add_panel(obj, window.leftPanel, args);
+}
+
+UIWIDGET ui_right_panel_create(UiObject *obj, UiSidebarArgs *args) {
+    MainWindow *window = (__bridge MainWindow*)obj->wobj;
+    if(window.rightPanel == nil) {
+        return NULL;
+    }
+    return splitview_window_add_panel(obj, window.rightPanel, args);
+}
+
--- a/ui/cocoa/button.m	Tue Oct 07 14:59:11 2025 +0200
+++ b/ui/cocoa/button.m	Tue Oct 07 15:42:18 2025 +0200
@@ -33,6 +33,7 @@
 
 UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs *args) {
     NSButton *button = [[NSButton alloc] init];
+    button.translatesAutoresizingMaskIntoConstraints = NO;
     if(args->label) {
         NSString *label = [[NSString alloc] initWithUTF8String:args->label];
         button.title = label;
--- a/ui/cocoa/container.h	Tue Oct 07 14:59:11 2025 +0200
+++ b/ui/cocoa/container.h	Tue Oct 07 15:42:18 2025 +0200
@@ -42,24 +42,6 @@
     UI_LAYOUT_FALSE,
 };
 
-struct UiLayout {
-    UiBool       fill;
-    //UiBool       newline;
-    //char         *label;
-    UiBool       hexpand;
-    UiBool       vexpand;
-    UiBool       hfill;
-    UiBool       vfill;
-    //int          width;
-    int          margin;
-    int          margin_left;
-    int          margin_right;
-    int          margin_top;
-    int          margin_bottom;
-    int          colspan;
-    int          rowspan;
-};
-
 #define UI_INIT_LAYOUT(args) (UiLayout) {\
     .fill = args->fill, \
     .hexpand = args->hexpand, \
@@ -78,8 +60,7 @@
 @protocol Container
 
 @property UiLayout uilayout;
-@property const char *label;
-@property UiBool newline;
+@property UiContainerX *container;
 
 - (void) addView:(NSView*)view margin:(NSEdgeInsets)margin;
 
--- a/ui/cocoa/container.m	Tue Oct 07 14:59:11 2025 +0200
+++ b/ui/cocoa/container.m	Tue Oct 07 15:42:18 2025 +0200
@@ -155,6 +155,7 @@
     ctn->close = 0;
     ctn->prev = NULL;
     ctn->next = NULL;
+    container.container = ctn;
     return ctn;
 }
 
@@ -177,14 +178,3 @@
     [container addView:view margin:margin];
 }
 
-/* ---------------------- public layout functions ----------------------- */
-
-void ui_newline(UiObject *obj) {
-    UiContainerX *ctn = obj->container_end;
-    if(ctn) {
-        id<Container> container = (__bridge id<Container>)ctn->container;
-        container.newline = TRUE;
-    } else {
-        fprintf(stderr, "Error: obj has no container\n");
-    }
-}
--- a/ui/cocoa/window.m	Tue Oct 07 14:59:11 2025 +0200
+++ b/ui/cocoa/window.m	Tue Oct 07 15:42:18 2025 +0200
@@ -42,14 +42,14 @@
 #include <cx/mempool.h>
 
 
-static UiObject* create_window(const char *title, BOOL simple, BOOL sidebar) {
+static UiObject* create_window(const char *title, BOOL simple, BOOL sidebar, BOOL splitview) {
     CxMempool *mp = cxMempoolCreateSimple(256);
     UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject));
     obj->ref = 0;
     
     obj->ctx = uic_context(obj, mp);
     
-    MainWindow *window = [[MainWindow alloc] init:obj withSidebar:sidebar];
+    MainWindow *window = [[MainWindow alloc] init:obj withSidebar:sidebar withSplitview:splitview];
     [[WindowManager sharedWindowManager] addWindow:window];
     window.releasedWhenClosed = false;
     
@@ -64,23 +64,27 @@
 }
 
 UiObject* ui_window(const char *title, void *window_data) {
-    UiObject *obj = create_window(title, FALSE, FALSE);
+    UiObject *obj = create_window(title, FALSE, FALSE, FALSE);
     obj->window = window_data;
     return obj;
 }
 
 UiObject* ui_simple_window(const char *title, void *window_data) {
-    UiObject *obj = create_window(title, TRUE, FALSE);
+    UiObject *obj = create_window(title, TRUE, FALSE, FALSE);
     obj->window = window_data;
     return obj;
 }
 
 UiObject* ui_sidebar_window(const char *title, void *window_data) {
-    UiObject *obj = create_window(title, FALSE, TRUE);
+    UiObject *obj = create_window(title, FALSE, TRUE, FALSE);
     obj->window = window_data;
     return obj;
 }
 
+UiObject* ui_splitview_window(const char *title, UiBool sidebar) {
+    return create_window(title, FALSE, sidebar, TRUE);
+}
+
 /* --------------------------------- File Dialogs --------------------------------- */
 
 void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) {

mercurial