Sun, 20 Jul 2025 22:04:39 +0200
update toolkit, adjust UI code
/* * 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 "../common/object.h" #import <objc/runtime.h> #import "EventData.h" #import "menu.h" @implementation MainWindow - (MainWindow*)init:(UiObject*)obj { NSRect frame = NSMakeRect(300, 200, 600, 500); self = [self initWithContentRect:frame styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable backing:NSBackingStoreBuffered defer:false]; // create a vertical stackview as default container BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; //GridLayout *vbox = [[GridLayout alloc] init]; vbox.translatesAutoresizingMaskIntoConstraints = false; [self.contentView addSubview:vbox]; [NSLayoutConstraint activateConstraints:@[ [vbox.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:4], [vbox.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], [vbox.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], [vbox.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor] ]]; uic_object_push_container(obj, ui_create_container(obj, vbox)); return self; } @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) { event.obj = self.uiobj; // temporary set the event object [event handleEvent:sender]; } } - (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++; } }