Sun, 19 Oct 2025 21:20:08 +0200
update toolkit
/* * 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 "MainWindow.h" #import "Container.h" #import "GridLayout.h" #import "BoxContainer.h" #import "../common/object.h" #import "../ui/properties.h" #import <objc/runtime.h> #import "EventData.h" #import "menu.h" #import "Toolbar.h" @implementation MainWindow - (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)hasSidebar withSplitview:(BOOL)hasSplitview{ NSRect frame = NSMakeRect(300, 200, 600, 500); self = [self initWithContentRect:frame styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable backing:NSBackingStoreBuffered defer:false]; if(uic_toolbar_isenabled()) { UiToolbar *toolbar = [[UiToolbar alloc]initWithObject:obj]; [self setToolbar:toolbar]; } int top = 4; NSView *content = self.contentView; // A sidebar or splitview window need a NSSplitView NSSplitView *splitview; if(hasSidebar || hasSplitview) { self.styleMask |= NSWindowStyleMaskFullSizeContentView; self.titleVisibility = NSWindowTitleHidden; self.titlebarAppearsTransparent = YES; splitview = [[NSSplitView alloc]init]; splitview.vertical = YES; splitview.dividerStyle = NSSplitViewDividerStyleThin; splitview.translatesAutoresizingMaskIntoConstraints = false; [self.contentView addSubview:splitview]; [NSLayoutConstraint activateConstraints:@[ [splitview.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:0], [splitview.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], [splitview.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], [splitview.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor] ]]; top = 34; } if(hasSidebar) { // add the sidebar const char *sidebarMaterialProperty = ui_get_property("ui.cocoa.sidebar.usematerial"); BOOL useMaterial = YES; if(sidebarMaterialProperty && (sidebarMaterialProperty[0] == 'f' || sidebarMaterialProperty[0] == 'F')) { useMaterial = NO; } if(useMaterial) { NSVisualEffectView *v = [[NSVisualEffectView alloc] initWithFrame:NSMakeRect(0,0,0,0)]; v.material = NSVisualEffectMaterialSidebar; v.blendingMode = NSVisualEffectBlendingModeBehindWindow; v.state = NSVisualEffectStateActive; _sidebar = v; } else { _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], ]]; UiContainerX *container = ui_create_container(obj, vbox); vbox.container = container; uic_object_push_container(obj, container); } _topOffset = top; return self; } - (BOOL) getIsVisible { return [self isVisible]; } - (void) setVisible:(BOOL)visible { if(visible) { [self makeKeyAndOrderFront:nil]; } else { [self close]; } } @end @implementation MainWindowController - (MainWindowController*)initWithWindow:(UiObject*)obj window:(NSWindow*)window { self = [super initWithWindow:window]; _uiobj = obj; self.checkItemStates = [[NSMutableDictionary alloc] init]; self.radioItems = [[NSMutableDictionary alloc] init]; // bind all stateful menu items (checkbox, radiobuttons, lists) NSArray *menuBindItems = ui_get_binding_items(); // returns all items that require binding for(MenuItem *item in menuBindItems) { if(item.checkItem || item.radioItem) { // simple check item (ui_menu_toggleitem_create) UiVar *var = uic_widget_var(obj->ctx, obj->ctx, NULL, item.checkItem ? item.checkItem->varname : item.radioItem->varname, UI_VAR_INTEGER); // create the state object for this item/window MenuItemState *state = [[MenuItemState alloc] init]; state.mainWindow = self; state.var = var; if(var) { UiInteger *i = var->value; if(item.checkItem) { // bind toggle item state.state = (int)i->value; i->obj = (__bridge void*)state; i->get = ui_menu_check_item_get; i->set = ui_menu_check_item_set; } else { // bind radio item NSMutableArray *rgroup = nil; if(i->obj) { rgroup = (__bridge NSMutableArray*)i->obj; } else { // create a new rgroup array and register it in the window rgroup = [[NSMutableArray alloc] init]; NSString *varname = [[NSString alloc] initWithUTF8String:item.radioItem->varname]; [_radioItems setObject:rgroup forKey:varname]; i->obj = (__bridge void*)rgroup; } i->get = ui_menu_radio_item_get; i->set = ui_menu_radio_item_set; [rgroup addObject:state]; // add this item state to the radio group // i->value can contain a non-zero value, which means a specific radiobutton // should be pre-selected if(i->value == rgroup.count) { state.state = NSControlStateValueOn; } } } else { state.state = 0; } [_checkItemStates setObject:state forKey:item.itemId]; } } return self; } - (void) windowDidLoad { [self.window setNextResponder:self]; } - (void)menuItemAction:(id)sender { EventData *event = objc_getAssociatedObject(sender, "eventdata"); if(event) { if(event.obj) { [event handleEvent:sender]; } else { event.obj = self.uiobj; [event handleEvent:sender]; event.obj = NULL; } } } - (void)menuCheckItemAction:(id)sender { NSMenuItem *menuItem = sender; MenuItem *item = objc_getAssociatedObject(sender, "menuitem"); if(!item || !item.checkItem) { return; } MenuItemState *state = [_checkItemStates objectForKey:item.itemId]; state.state = state.state == NSControlStateValueOff ? NSControlStateValueOn : NSControlStateValueOff; menuItem.state = state.state; UiMenuCheckItem *it = item.checkItem; if(it->callback) { UiEvent event; event.obj = _uiobj; event.window = event.obj->window; event.document = event.obj->ctx->document; event.eventdata = state.var ? state.var->value : NULL; event.intval = state.state; it->callback(&event, it->userdata); } } - (void)menuRadioItemAction:(id)sender { NSMenuItem *menuItem = sender; MenuItem *item = objc_getAssociatedObject(sender, "menuitem"); if(!item || !item.radioItem) { return; } UiMenuRadioItem *it = item.radioItem; if(!it->varname) { return; } MenuItemState *state = [_checkItemStates objectForKey:item.itemId]; // current state of this menu item NSString *varname = [[NSString alloc] initWithUTF8String:it->varname]; NSArray *radioGroup = [_radioItems objectForKey:varname]; if(!radioGroup) { return; } int index = 1; int value = 0; for(MenuItemState *g in radioGroup) { if(g == state) { menuItem.state = NSControlStateValueOn; g.state = NSControlStateValueOn; value = index; } else { menuItem.state = NSControlStateValueOff; g.state = NSControlStateValueOff; } } if(it->callback) { UiEvent event; event.obj = _uiobj; event.window = event.obj->window; event.document = event.obj->ctx->document; event.eventdata = state.var ? state.var->value : NULL; event.intval = value; it->callback(&event, it->userdata); } } - (BOOL) validateMenuItem:(NSMenuItem *) menuItem { MenuItem *item = objc_getAssociatedObject(menuItem, "menuitem"); if(item) { MenuItemState *state = [_checkItemStates objectForKey:item.itemId]; if(state) { menuItem.state = state.state; } else { menuItem.state = NSControlStateValueOff; } } return YES; } @end @implementation MenuItemState @end int64_t ui_menu_check_item_get(UiInteger *i) { MenuItemState *state = (__bridge MenuItemState*)i->obj; i->value = state.state; return i->value; } void ui_menu_check_item_set(UiInteger *i, int64_t value) { MenuItemState *state = (__bridge MenuItemState*)i->obj; i->value = value; state.state = (int)value; } int64_t ui_menu_radio_item_get(UiInteger *i) { NSArray *rgroup = (__bridge NSArray*)i->obj; i->value = 0; int index = 1; for(MenuItemState *state in rgroup) { if(state.state == NSControlStateValueOn) { i->value = index; break; } index++; } return i->value; } void ui_menu_radio_item_set(UiInteger *i, int64_t value) { NSArray *rgroup = (__bridge NSArray*)i->obj; i->value = 0; int index = 1; for(MenuItemState *state in rgroup) { state.state = value == index; index++; } } UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) { MainWindow *window = (__bridge MainWindow*)obj->wobj; if(window.sidebar == nil) { return NULL; } NSView *sidebar = window.sidebar; // create a vertical stackview as default container BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:args->spacing]; vbox.container = ui_create_container(obj, vbox); //GridLayout *vbox = [[GridLayout alloc] init]; vbox.translatesAutoresizingMaskIntoConstraints = false; [sidebar addSubview:vbox]; [NSLayoutConstraint activateConstraints:@[ [vbox.topAnchor constraintEqualToAnchor:sidebar.topAnchor constant:34], [vbox.leadingAnchor constraintEqualToAnchor:sidebar.leadingAnchor], [vbox.trailingAnchor constraintEqualToAnchor:sidebar.trailingAnchor], [vbox.bottomAnchor constraintEqualToAnchor:sidebar.bottomAnchor] ]]; uic_object_push_container(obj, vbox.container); 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.container = ui_create_container(obj, vbox); 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, vbox.container); 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); }