ui/cocoa/container.m

Mon, 10 Nov 2025 21:52:51 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 10 Nov 2025 21:52:51 +0100
changeset 113
dde28a806552
parent 112
c3f2f16fa4b8
permissions
-rw-r--r--

update ucx

/*
 * 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"
#import "GridLayout.h"
#import "BoxContainer.h"
#import "TabView.h"

/* -------------------- public container functions --------------------- */

static UIWIDGET ui_box_create(UiObject *obj, UiContainerArgs *args, NSUserInterfaceLayoutOrientation orientation) {
    BoxContainer *box = [[BoxContainer alloc] init:orientation spacing:args->spacing];
    box.translatesAutoresizingMaskIntoConstraints = false;
    UiContainerX *container = ui_create_container(obj, box);
    
    // add box to the parent
    UiLayout layout = UI_INIT_LAYOUT(args);
    ui_container_add(obj, box, &layout);
    
    // add new box to the obj container chain
    uic_object_push_container(obj, container);
    
    return (__bridge void*)box;
}

UIWIDGET ui_vbox_create(UiObject *obj, UiContainerArgs *args) {
    return ui_box_create(obj, args, NSUserInterfaceLayoutOrientationVertical);
}

UIWIDGET ui_hbox_create(UiObject *obj, UiContainerArgs *args) {
    return ui_box_create(obj, args, NSUserInterfaceLayoutOrientationHorizontal);
}

UIWIDGET ui_grid_create(UiObject *obj, UiContainerArgs *args) {
    GridLayout *grid = [[GridLayout alloc] init];
    grid.translatesAutoresizingMaskIntoConstraints = false;
    grid.columnspacing = args->columnspacing;
    grid.rowspacing = args->rowspacing;
    UiContainerX *container = ui_create_container(obj, grid);
    grid.container = container;
    
    // add box to the parent
    UiLayout layout = UI_INIT_LAYOUT(args);
    ui_container_add(obj, grid, &layout);
    
    // add new box to the obj container chain
    uic_object_push_container(obj, container);
    
    return (__bridge void*)grid;
}

UIWIDGET ui_frame_create(UiObject *obj, UiFrameArgs *args) {
    NSString *title = args->label ? [[NSString alloc]initWithUTF8String:args->label] : nil;
    FrameContainer *frame = [[FrameContainer alloc] init:title];
    UiLayout layout = UI_ARGS2LAYOUT(args);
    ui_container_add(obj, frame, &layout);
    
    // add container to the chain
    UiContainerX *container;
    UiLayout subLayout = {0};
    switch(args->subcontainer) {
        default: {
            // UI_CONTAINER_NO_SUB
            container = ui_create_container(obj, frame);
            break;
        }
        case UI_CONTAINER_VBOX: {
            BoxContainer *box = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationVertical spacing:args->spacing];
            box.translatesAutoresizingMaskIntoConstraints = false;
            [frame addView:box layout:&subLayout];
            container = ui_create_container(obj, box);
            break;
        }
        case UI_CONTAINER_HBOX: {
            BoxContainer *box = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationHorizontal spacing:args->spacing];
            box.translatesAutoresizingMaskIntoConstraints = false;
            [frame addView:box layout:&subLayout];
            container = ui_create_container(obj, box);
            break;
        }
        case UI_CONTAINER_GRID: {
            GridLayout *grid = [[GridLayout alloc] init];
            grid.translatesAutoresizingMaskIntoConstraints = false;
            grid.columnspacing = args->columnspacing;
            grid.rowspacing = args->rowspacing;
            [frame addView:grid layout:&subLayout];
            container = ui_create_container(obj, grid);
            break;
        }
    }
    
    uic_object_push_container(obj, container);
    
    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;
    if(args->subcontainer == UI_CONTAINER_GRID) {
        colspacing = args->columnspacing;
        rowspacing = args->rowspacing;
    }
    ScrollViewContainer *scrollview = [[ScrollViewContainer alloc]init:args->subcontainer columnSpacing:colspacing rowSpacing:rowspacing];
    scrollview.hasVerticalScroller = YES;
    scrollview.scrollerStyle = NSScrollerStyleOverlay;
    scrollview.autohidesScrollers = YES;
    UiLayout layout = UI_ARGS2LAYOUT(args);
    ui_container_add(obj, scrollview, &layout);
    
    UiContainerX *container = ui_create_container(obj, scrollview);
    uic_object_push_container(obj, container);
    
    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];
}


UIWIDGET ui_headerbar_create(UiObject *obj, UiHeaderbarArgs *args) {
    return NULL; // TODO
}

UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs *args) {
    return NULL; // TODO
}

UIWIDGET ui_hsplitpane_create(UiObject *obj, UiSplitPaneArgs *args) {
    return NULL; // TODO
}

UIWIDGET ui_vsplitpane_create(UiObject *obj, UiSplitPaneArgs *args) {
    return NULL; // TODO
}



void ui_container_begin_close(UiObject *obj) {
    UiContainerX *ct = obj->container_end;
    ct->close = 1;
}

int ui_container_finish(UiObject *obj) {
    UiContainerX *ct = obj->container_end;
    if(ct->close) {
        ui_end_new(obj);
        return 0;
    }
    return 1;
}

/* -------------------------- Frame Container -------------------------- */

@implementation FrameContainer

@synthesize container = _container;

- (id)init:(NSString*)title {
    self = [super init];
    self.title = title;
    self.boxType = NSBoxPrimary;
    if(title != nil) {
        self.titlePosition = NSAtTop;
    }
    return self;
}

- (void) addView:(NSView*)view layout:(UiLayout*)layout {
    [self.contentView addSubview:view];
    view.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [view.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:0],
        [view.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor constant:0],
        [view.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor constant:-0],
        [view.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor constant:-0]
    ]];
}

@end


/* -------------------------- Expander Container -------------------------- */

// TODO


/* ------------------------ ScrollView Container ------------------------ */

@implementation ScrollViewContainer

@synthesize container = _container;

- (id)init:(UiSubContainerType)subContainer columnSpacing:(int)columnSpacing rowSpacing:(int)rowSpacing {
    self = [super init];
    
    if(subContainer != UI_CONTAINER_NO_SUB) {
        GridLayout *child;
        switch(subContainer) {
            default:
            case UI_CONTAINER_VBOX: {
                child = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationVertical spacing:columnSpacing];
                break;
            }
            case UI_CONTAINER_HBOX: {
                child = [[BoxContainer alloc]init:NSUserInterfaceLayoutOrientationHorizontal spacing:columnSpacing];
                break;
            }
            case UI_CONTAINER_GRID: {
                child = [[GridLayout alloc]init];
                child.columnspacing = columnSpacing;
                child.rowspacing = rowSpacing;
                break;
            }
        }
        child.translatesAutoresizingMaskIntoConstraints = NO;
        
        self.documentView = child;
        [child.widthAnchor constraintEqualToAnchor:self.contentView.widthAnchor].active = YES;
        
        _child = child;
    }
    
    
    return self;
}

- (void) addView:(NSView*)view layout:(UiLayout*)layout {
    if(_child != nil) {
        _child.container = self.container; // required, otherwise child has no container and can't access the newline property
        view.translatesAutoresizingMaskIntoConstraints = NO;
        [_child addView:view layout:layout];
    } else {
        self.documentView = view;
        [view.widthAnchor constraintEqualToAnchor:self.contentView.widthAnchor].active = YES;
    }
}

@end

/* ------------------------- private functions ------------------------- */

UiContainerX* ui_create_container(UiObject *obj, id<Container> container) {
    UiContainerX *ctn = ui_malloc(obj->ctx, sizeof(UiContainerX));
    ctn->container = (__bridge void*)container;
    ctn->close = 0;
    ctn->prev = NULL;
    ctn->next = NULL;
    container.container = ctn;
    return ctn;
}

void ui_container_add(UiObject *obj, NSView *view, UiLayout *layout) {
    UiContainerX *ctn = obj->container_end;
    id<Container> container = (__bridge id<Container>)ctn->container;
    UiLayout adjustedLayout = *layout;
    if(adjustedLayout.margin > 0) {
        adjustedLayout.margin_left = adjustedLayout.margin;
        adjustedLayout.margin_right = adjustedLayout.margin;
        adjustedLayout.margin_top = adjustedLayout.margin;
        adjustedLayout.margin_bottom = adjustedLayout.margin;
    }
    [container addView:view layout:&adjustedLayout];
}

mercurial