add tabview (Cocoa)

Thu, 16 Oct 2025 10:48:16 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 16 Oct 2025 10:48:16 +0200
changeset 851
367b2bbbc07e
parent 850
3e1c3f4e2ad4
child 852
a04cb4398034

add tabview (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/GridLayout.m file | annotate | diff | comparison | revisions
ui/cocoa/TabView.h file | annotate | diff | comparison | revisions
ui/cocoa/TabView.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/toolkit.m file | annotate | diff | comparison | revisions
ui/cocoa/window.m file | annotate | diff | comparison | revisions
ui/ui/container.h file | annotate | diff | comparison | revisions
--- a/make/xcode/toolkit/toolkit.xcodeproj/project.pbxproj	Wed Oct 15 18:50:52 2025 +0200
+++ b/make/xcode/toolkit/toolkit.xcodeproj/project.pbxproj	Thu Oct 16 10:48:16 2025 +0200
@@ -51,6 +51,7 @@
 		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 */; };
+		ED895DAA2EA0CACC00040078 /* TabView.m in Sources */ = {isa = PBXBuildFile; fileRef = ED895DA92EA0CACC00040078 /* TabView.m */; };
 		ED99F04A2E5CBD2E00A4CC97 /* widget.m in Sources */ = {isa = PBXBuildFile; fileRef = ED99F0492E5CBD2E00A4CC97 /* widget.m */; };
 		EDB452C32E302C65006FB12D /* image.m in Sources */ = {isa = PBXBuildFile; fileRef = EDB452C22E302C65006FB12D /* image.m */; };
 		EDC315A92E9A739300403776 /* json.c in Sources */ = {isa = PBXBuildFile; fileRef = EDC315A62E9A739300403776 /* json.c */; };
@@ -172,6 +173,8 @@
 		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; };
 		ED8687E42D999CF3002F3EC2 /* menu.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = menu.m; path = ../../../ui/cocoa/menu.m; sourceTree = SOURCE_ROOT; };
+		ED895DA82EA0CACC00040078 /* TabView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TabView.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/TabView.h; sourceTree = "<absolute>"; };
+		ED895DA92EA0CACC00040078 /* TabView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = TabView.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/TabView.m; sourceTree = "<absolute>"; };
 		ED99F0482E5CBD2E00A4CC97 /* widget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = widget.h; path = /Users/olaf/Projekte/toolkit/ui/cocoa/widget.h; sourceTree = "<absolute>"; };
 		ED99F0492E5CBD2E00A4CC97 /* widget.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = widget.m; path = /Users/olaf/Projekte/toolkit/ui/cocoa/widget.m; sourceTree = "<absolute>"; };
 		ED99F04B2E5CBE5000A4CC97 /* webview.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = webview.h; path = /Users/olaf/Projekte/toolkit/ui/ui/webview.h; sourceTree = "<absolute>"; };
@@ -322,6 +325,8 @@
 		ED65812E2CFF1A7200F5402F /* cocoa */ = {
 			isa = PBXGroup;
 			children = (
+				ED895DA82EA0CACC00040078 /* TabView.h */,
+				ED895DA92EA0CACC00040078 /* TabView.m */,
 				ED83C2BD2E8EA49200054B22 /* BoxContainer.h */,
 				ED83C2BE2E8EA49200054B22 /* BoxContainer.m */,
 				ED18C9212E76CA5500B64EA5 /* entry.h */,
@@ -511,6 +516,7 @@
 				ED6581462CFF3BCE00F5402F /* GridLayout.m in Sources */,
 				ED6581392CFF287300F5402F /* EventData.m in Sources */,
 				ED65812B2CFF1A3000F5402F /* buffer.c in Sources */,
+				ED895DAA2EA0CACC00040078 /* TabView.m in Sources */,
 				ED65812C2CFF1A3000F5402F /* printf.c in Sources */,
 				ED6580F32CFF19F900F5402F /* object.c in Sources */,
 				ED6580F42CFF19F900F5402F /* toolbar.c in Sources */,
--- a/make/xcode/toolkit/toolkit/main.m	Wed Oct 15 18:50:52 2025 +0200
+++ b/make/xcode/toolkit/toolkit/main.m	Thu Oct 16 10:48:16 2025 +0200
@@ -158,12 +158,17 @@
     }
     
     ui_right_panel0(obj) {
+        ui_tabview(obj, .padding = 20, .spacing = 10, .margin_left = 10, .margin_right = 10, .margin_bottom = 10, .fill = TRUE) {
+            ui_tab(obj, "Tab 1") {
+                ui_button(obj, .label = "Test");
+                ui_textarea(obj, .fill = TRUE);
+            }
+            ui_tab(obj, "Tab 2") {
+                ui_button(obj, .label = "Tab 2 Content");
+            }
+        }
+        
         /*
-        ui_grid(obj, .margin_left = 10, .margin_right = 10, .fill = TRUE) {
-            ui_button(obj, .label = "right", .hexpand = TRUE, .hfill = TRUE);
-        }
-        */
-        
         ui_scrolledwindow(obj, .margin_left = 10, .margin_right = 10, .fill = TRUE, .subcontainer = UI_CONTAINER_NO_SUB) {
             ui_vbox(obj, .margin = 0) {
                 for(int i=0;i<50;i++) {
@@ -173,6 +178,7 @@
                 }
             }
         }
+        */
     }
     
     ui_linkbutton_value_set(doc->link, "unixwork", "https://unixwork.de/");
--- a/ui/cocoa/GridLayout.m	Wed Oct 15 18:50:52 2025 +0200
+++ b/ui/cocoa/GridLayout.m	Thu Oct 16 10:48:16 2025 +0200
@@ -316,7 +316,7 @@
     _preferredSize.width = -1;
     _preferredSize.height = -1;
     
-    if(self.container->newline) {
+    if(self.container != nil && self.container->newline) {
         _y++;
         _x = 0;
         self.container->newline = FALSE;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/TabView.h	Thu Oct 16 10:48:16 2025 +0200
@@ -0,0 +1,55 @@
+/*
+ * 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 "Container.h"
+
+@protocol TabView
+
+- (NSView<Container>*) createTab:(int)index title:(NSString*)title;
+- (void) selectTab:(int)index;
+- (void) removeTab:(int)index;
+- (UiObject*) addTab:(int)index title:(NSString*)title;
+
+@end
+
+@interface UiTopTabView : NSTabView<TabView, Container>
+
+@property UiObject *obj;
+@property UiSubContainerType subcontainer;
+@property int padding;
+@property int spacing;
+@property int columnspacing;
+@property int rowspacing;
+@property ui_callback onchange;
+@property void *onchangedata;
+@property UiVar *var;
+
+- (id)init:(UiObject*)obj args:(UiTabViewArgs*)args;
+
+
+@end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/cocoa/TabView.m	Thu Oct 16 10:48:16 2025 +0200
@@ -0,0 +1,115 @@
+/*
+ * 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 "TabView.h"
+#import "BoxContainer.h"
+#import "GridLayout.h"
+
+@implementation UiTopTabView
+
+@synthesize container = _container;
+
+- (id)init:(UiObject*)obj args:(UiTabViewArgs*)args {
+    self = [super init];
+    _obj = obj;
+    _subcontainer = args->subcontainer;
+    _padding = args->padding;
+    _spacing = args->spacing;
+    _columnspacing = args->columnspacing;
+    _rowspacing = args->rowspacing;
+    _onchange = args->onchange;
+    _onchangedata = args->onchangedata;
+    _var = uic_widget_var(obj->ctx, obj->ctx, args->value, args->varname, UI_VAR_INTEGER);
+    
+    if(args->tabview == UI_TABVIEW_INVISIBLE || args->tabview == UI_TABVIEW_NAVIGATION_SIDE) {
+        self.tabViewType = NSNoTabsNoBorder;
+    }
+    
+    return self;
+}
+
+- (void) addView:(NSView*)view layout:(UiLayout*)layout {
+    // noop
+}
+
+- (NSView<Container>*) createTab:(int)index title:(NSString*)title {
+    NSTabViewItem *item = [[NSTabViewItem alloc]initWithIdentifier:nil];
+    [item setLabel:title];
+    if(index < 0) {
+        [self addTabViewItem:item];
+    } else {
+        [self insertTabViewItem:item atIndex:index];
+    }
+    
+    BoxContainer *content = [[BoxContainer alloc]init];
+    item.view = content;
+    
+    GridLayout *sub;
+    switch(_subcontainer) {
+        default: sub = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:_spacing]; break;
+        case UI_CONTAINER_HBOX: sub = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationHorizontal spacing:_spacing]; break;
+        case UI_CONTAINER_GRID: {
+            sub = [[GridLayout alloc] init];
+            sub.columnspacing = _columnspacing;
+            sub.rowspacing = _rowspacing;
+            break;
+        }
+    }
+    UiLayout layout = {
+        .margin = _padding,
+        .margin_left = _padding, .margin_right = _padding, .margin_top = _padding, .margin_bottom = _padding,
+        .fill = TRUE };
+    [content addView:sub layout:&layout];
+    
+    return sub;
+}
+
+- (void) selectTab:(int)index {
+    [self selectTabViewItemAtIndex:index];
+}
+
+- (void) removeTab:(int)index {
+    NSTabViewItem *item = [self tabViewItemAtIndex:index];
+    if(item != nil) {
+        [self removeTabViewItem:item];
+    }
+}
+
+- (UiObject*) addTab:(int)index title:(NSString*)title {
+    NSView<Container> *sub = [self createTab:index title:title];
+    
+    UiObject *newobj = uic_object_new_toplevel();
+    newobj->widget = (__bridge void*)sub;
+    
+    UiContainerX *container = ui_create_container(newobj, sub);
+    uic_object_push_container(newobj, container);
+    
+    return newobj;
+}
+
+@end
--- a/ui/cocoa/container.h	Wed Oct 15 18:50:52 2025 +0200
+++ b/ui/cocoa/container.h	Thu Oct 16 10:48:16 2025 +0200
@@ -30,8 +30,6 @@
 
 #import "../ui/container.h"
 
-#define ui_lb2bool(b) ((b) == UI_LAYOUT_TRUE ? TRUE : FALSE)
-#define ui_bool2lb(b) ((b) ? UI_LAYOUT_TRUE : UI_LAYOUT_FALSE)
 
 typedef struct UiLayout UiLayout;
 
@@ -74,6 +72,7 @@
 
 @end
 
+
 UiContainerX* ui_create_container(UiObject *obj, id<Container> container);
 
 void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout);
--- a/ui/cocoa/container.m	Wed Oct 15 18:50:52 2025 +0200
+++ b/ui/cocoa/container.m	Thu Oct 16 10:48:16 2025 +0200
@@ -29,6 +29,7 @@
 #import "Container.h"
 #import "GridLayout.h"
 #import "BoxContainer.h"
+#import "TabView.h"
 
 /* -------------------- public container functions --------------------- */
 
@@ -36,7 +37,6 @@
     BoxContainer *box = [[BoxContainer alloc] init:orientation spacing:args->spacing];
     box.translatesAutoresizingMaskIntoConstraints = false;
     UiContainerX *container = ui_create_container(obj, box);
-    box.container = container;
     
     // add box to the parent
     UiLayout layout = UI_INIT_LAYOUT(args);
@@ -119,6 +119,10 @@
     return (__bridge void*)frame;
 }
 
+UIWIDGET ui_expander_create(UiObject *obj, UiFrameArgs *args) {
+    return ui_frame_create(obj, args); // TODO
+}
+
 UIWIDGET ui_scrolledwindow_create(UiObject *obj, UiFrameArgs *args) {
     int colspacing = args->spacing;
     int rowspacing = args->spacing;
@@ -139,6 +143,50 @@
     return (__bridge void*)scrollview;
 }
 
+UIWIDGET ui_tabview_create(UiObject *obj, UiTabViewArgs *args) {
+    NSView<TabView, Container> *tabview;
+    switch(args->tabview) {
+        default: tabview = [[UiTopTabView alloc]init:obj args:args]; break;
+    }
+    
+    UiLayout layout = UI_ARGS2LAYOUT(args);
+    ui_container_add(obj, tabview, &layout);
+    
+    UiContainerX *container = ui_create_container(obj, tabview);
+    uic_object_push_container(obj, container);
+    
+    return (__bridge void*)tabview;
+}
+
+void ui_tab_create(UiObject *obj, const char* title) {
+    UiContainerX *ctn = obj->container_end;
+    id<TabView> tabview = (__bridge id<TabView>)ctn->container;
+    NSString *s = title ? [[NSString alloc]initWithUTF8String:title] : @"";
+    NSView<Container> *sub = [tabview createTab:-1 title:s];
+    
+    UiContainerX *container = ui_create_container(obj, sub);
+    uic_object_push_container(obj, container);
+}
+
+void ui_tabview_select(UIWIDGET tabview, int tab) {
+    id<TabView> tabv = (__bridge id<TabView>)tabview;
+    [tabv selectTab:tab];
+}
+
+void ui_tabview_remove(UIWIDGET tabview, int tab) {
+    id<TabView> tabv = (__bridge id<TabView>)tabview;
+    [tabv removeTab:tab];
+}
+
+UiObject* ui_tabview_add(UIWIDGET tabview, const char *name, int tab_index) {
+    id<TabView> tabv = (__bridge id<TabView>)tabview;
+    NSString *s = name ? [[NSString alloc]initWithUTF8String:name] : @"";
+    return [tabv addTab:tab_index title:s];
+}
+
+
+
+
 void ui_container_begin_close(UiObject *obj) {
     UiContainerX *ct = obj->container_end;
     ct->close = 1;
--- a/ui/cocoa/toolkit.m	Wed Oct 15 18:50:52 2025 +0200
+++ b/ui/cocoa/toolkit.m	Thu Oct 16 10:48:16 2025 +0200
@@ -149,12 +149,17 @@
 void ui_show(UiObject *obj) {
     if(obj->wobj) {
         NSWindow *window = (__bridge NSWindow*)obj->wobj;
+        
+        if(!window.isVisible) {
+            obj->ref++;
+        }
+        
         [window makeKeyAndOrderFront:nil];
     }
 }
 
 void ui_close(UiObject *obj) {
-
+    // TODO: unref, window close, ...
 }
 
 /* ------------------- Job Control / Threadpool functions ------------------- */
--- a/ui/cocoa/window.m	Wed Oct 15 18:50:52 2025 +0200
+++ b/ui/cocoa/window.m	Thu Oct 16 10:48:16 2025 +0200
@@ -38,16 +38,13 @@
 #include "../common/context.h"
 #include "../common/menu.h"
 #include "../common/toolbar.h"
+#include "../common/object.h"
 
 #include <cx/mempool.h>
 
 
 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);
+    UiObject *obj = uic_object_new_toplevel();
     
     MainWindow *window = [[MainWindow alloc] init:obj withSidebar:sidebar withSplitview:splitview];
     [[WindowManager sharedWindowManager] addWindow:window];
--- a/ui/ui/container.h	Wed Oct 15 18:50:52 2025 +0200
+++ b/ui/ui/container.h	Thu Oct 16 10:48:16 2025 +0200
@@ -365,18 +365,6 @@
 
 UIEXPORT void ui_splitpane_set_visible(UIWIDGET splitpane, int child_index, UiBool visible);
 
-// box container layout functions
-UIEXPORT void ui_layout_fill(UiObject *obj, UiBool fill);
-// grid container layout functions
-UIEXPORT void ui_layout_hexpand(UiObject *obj, UiBool expand);
-UIEXPORT void ui_layout_vexpand(UiObject *obj, UiBool expand);
-UIEXPORT void ui_layout_hfill(UiObject *obj, UiBool fill);
-UIEXPORT void ui_layout_vfill(UiObject *obj, UiBool fill);
-UIEXPORT void ui_layout_override_defaults(UiObject *obj, UiBool d);
-UIEXPORT void ui_layout_width(UiObject *obj, int width);
-UIEXPORT void ui_layout_height(UiObject* obj, int width);
-UIEXPORT void ui_layout_colspan(UiObject *obj, int cols);
-UIEXPORT void ui_layout_rowspan(UiObject* obj, int rows);
 UIEXPORT void ui_newline(UiObject *obj);
 
 // TODO

mercurial