Thu, 17 Apr 2025 17:51:07 +0200
implement toggle menu items (Cocoa)
| make/xcode/toolkit/toolkit/main.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/menu.h | file | annotate | diff | comparison | revisions | |
| ui/cocoa/menu.m | file | annotate | diff | comparison | revisions | |
| ui/cocoa/window.m | file | annotate | diff | comparison | revisions |
--- a/make/xcode/toolkit/toolkit/main.m Thu Apr 17 11:18:57 2025 +0200 +++ b/make/xcode/toolkit/toolkit/main.m Thu Apr 17 17:51:07 2025 +0200 @@ -54,6 +54,10 @@ } ui_show(obj); + + UiObject *obj2 = ui_window("Window 2", NULL); + ui_textarea(obj2, .fill = UI_ON); + ui_show(obj2); } int main(int argc, char * argv[]) {
--- a/ui/cocoa/MainWindow.h Thu Apr 17 11:18:57 2025 +0200 +++ b/ui/cocoa/MainWindow.h Thu Apr 17 17:51:07 2025 +0200 @@ -31,10 +31,32 @@ @interface MainWindow : NSWindow -@property UiObject *uiobj; +- (MainWindow*)init:(UiObject*)obj; + +@end + + +@interface MainWindowController : NSWindowController<NSMenuItemValidation> -- (MainWindow*)init:(UiObject*)obj; +@property UiObject *uiobj; +@property NSMutableDictionary *checkItemStates; + +- (MainWindowController*)initWithWindow:(UiObject*)obj window:(NSWindow*)window; + +- (void) windowDidLoad; - (void)menuItemAction:(id)sender; +- (BOOL) validateMenuItem:(NSMenuItem *) menuItem; + @end + +@interface MenuCheckItem : NSObject +@property (weak) MainWindowController *mainWindow; +@property UiVar *var; +@property int state; +@end + + +int64_t ui_menu_check_item_get(UiInteger *i); +void ui_menu_check_item_set(UiInteger *i, int64_t value);
--- a/ui/cocoa/MainWindow.m Thu Apr 17 11:18:57 2025 +0200 +++ b/ui/cocoa/MainWindow.m Thu Apr 17 17:51:07 2025 +0200 @@ -33,11 +33,11 @@ #import <objc/runtime.h> #import "EventData.h" +#import "menu.h" @implementation MainWindow - (MainWindow*)init:(UiObject*)obj { - self.uiobj = obj; NSRect frame = NSMakeRect(300, 200, 600, 500); self = [self initWithContentRect:frame @@ -65,6 +65,51 @@ return self; } +@end + + + +@implementation MainWindowController + +- (MainWindowController*)initWithWindow:(UiObject*)obj window:(NSWindow*)window { + self = [super initWithWindow:window]; + _uiobj = obj; + + // bind all stateful menu items (checkbox, radiobuttons, lists) + self.checkItemStates = [[NSMutableDictionary alloc] init]; + NSArray *menuBindItems = ui_get_binding_items(); // returns all items that require binding + for(MenuItem *item in menuBindItems) { + if(item.checkItem) { + // simple check item (ui_menu_toggleitem_create) + UiVar *var = uic_widget_var(obj->ctx, obj->ctx, NULL, item.checkItem->varname, UI_VAR_INTEGER); + // create the state object for this item/window + MenuCheckItem *state = [[MenuCheckItem alloc] init]; + state.mainWindow = self; + state.var = var; + if(var) { + // bind toggle item + UiInteger *i = var->value; + 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 { + state.state = 0; + } + [_checkItemStates setObject:state forKey:item.itemId]; + } else if(item.radioItem) { + // bind radio item + + } + } + + return self; +} + +- (void) windowDidLoad { + [self.window setNextResponder:self]; +} + - (void)menuItemAction:(id)sender { EventData *event = objc_getAssociatedObject(sender, "eventdata"); if(event) { @@ -73,4 +118,57 @@ } } +- (void)menuCheckItemAction:(id)sender { + NSMenuItem *menuItem = sender; + MenuItem *item = objc_getAssociatedObject(sender, "menuitem"); + if(!item || !item.checkItem) { + return; + } + UiMenuCheckItem *it = item.checkItem; + + MenuCheckItem *state = [_checkItemStates objectForKey:item.itemId]; + state.state = state.state == NSControlStateValueOff ? NSControlStateValueOn : NSControlStateValueOff; + menuItem.state = state.state; + + 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); + } +} + +- (BOOL) validateMenuItem:(NSMenuItem *) menuItem { + MenuItem *item = objc_getAssociatedObject(menuItem, "menuitem"); + if(item) { + MenuCheckItem *state = [_checkItemStates objectForKey:item.itemId]; + if(state) { + menuItem.state = state.state; + } else { + menuItem.state = NSControlStateValueOff; + } + } + + return YES; +} + @end + +@implementation MenuCheckItem + +@end + +int64_t ui_menu_check_item_get(UiInteger *i) { + MenuCheckItem *state = (__bridge MenuCheckItem*)i->obj; + i->value = state.state; + return i->value; +} + +void ui_menu_check_item_set(UiInteger *i, int64_t value) { + MenuCheckItem *state = (__bridge MenuCheckItem*)i->obj; + i->value = value; + state.state = (int)value; +}
--- a/ui/cocoa/menu.h Thu Apr 17 11:18:57 2025 +0200 +++ b/ui/cocoa/menu.h Thu Apr 17 17:51:07 2025 +0200 @@ -31,6 +31,18 @@ #import "../common/menu.h" +@interface MenuItem : NSObject + +@property (strong) NSString *itemId; +@property UiMenuCheckItem *checkItem; +@property UiMenuRadioItem *radioItem; +@property ui_callback callback; +@property void *userdata; + +- (MenuItem*)init:(int)itId; + +@end + void ui_menu_init(void); typedef void(*ui_menu_add_f)(NSMenu*, int, UiMenuItemI*); @@ -42,3 +54,5 @@ void add_radioitem_widget(NSMenu *parent, int index, UiMenuItemI *item); void add_checkitemnv_widget(NSMenu *parent, int i, UiMenuItemI *item); void add_menuitem_list_widget(NSMenu *parent, int i, UiMenuItemI *item); + +NSArray* ui_get_binding_items(void);
--- a/ui/cocoa/menu.m Thu Apr 17 11:18:57 2025 +0200 +++ b/ui/cocoa/menu.m Thu Apr 17 17:51:07 2025 +0200 @@ -36,6 +36,22 @@ #import "window.h" #import "EventData.h" +#pragma GCC diagnostic ignored "-Wundeclared-selector" +#pragma clang diagnostic ignored "-Wundeclared-selector" + +// holds all items that need bindings +// value type: MenuItem* +static NSMutableArray *bindingItems; + +@implementation MenuItem + +- (MenuItem*)init:(int)itId { + self.itemId = [[NSString alloc] initWithFormat:@"item%d", itId]; + return self; +} + +@end + static ui_menu_add_f createMenuItem[] = { /* UI_MENU */ add_menu_widget, /* UI_MENU_ITEM */ add_menuitem_widget, @@ -80,15 +96,40 @@ } void add_menuseparator_widget(NSMenu *parent, int i, UiMenuItemI *item) { - + NSMenuItem *menuItem = [NSMenuItem separatorItem]; + [parent addItem:menuItem]; } +static int nItem = 0; + void add_checkitem_widget(NSMenu *parent, int i, UiMenuItemI *item) { + UiMenuCheckItem *it = (UiMenuCheckItem*)item; + NSString *str = [[NSString alloc] initWithUTF8String:it->label]; + NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuCheckItemAction:) keyEquivalent:@""]; + + MenuItem *mItem = [[MenuItem alloc] init:nItem++]; + mItem.callback = it->callback; + mItem.userdata = it->userdata; + mItem.checkItem = it; + + objc_setAssociatedObject(menuItem, "menuitem", mItem, OBJC_ASSOCIATION_RETAIN); + [bindingItems addObject:mItem]; } void add_radioitem_widget(NSMenu *parent, int index, UiMenuItemI *item) { + UiMenuRadioItem *it = (UiMenuRadioItem*)item; + NSString *str = [[NSString alloc] initWithUTF8String:it->label]; + NSMenuItem *menuItem = [parent addItemWithTitle:str action:@selector(menuRadioItemAction:) keyEquivalent:@""]; + + MenuItem *mItem = [[MenuItem alloc] init:nItem++]; + mItem.callback = it->callback; + mItem.userdata = it->userdata; + mItem.radioItem = it; + + objc_setAssociatedObject(menuItem, "menuitem", mItem, OBJC_ASSOCIATION_RETAIN); + [bindingItems addObject:mItem]; } void add_checkitemnv_widget(NSMenu *parent, int i, UiMenuItemI *item) { @@ -101,6 +142,8 @@ void ui_menu_init(void) { + bindingItems = [[NSMutableArray alloc] init]; + UiMenu *menus_begin = uic_get_menu_list(); UiMenu *ls = menus_begin; int index = 1; @@ -117,3 +160,7 @@ index++; } } + +NSArray* ui_get_binding_items(void) { + return bindingItems; +}
--- a/ui/cocoa/window.m Thu Apr 17 11:18:57 2025 +0200 +++ b/ui/cocoa/window.m Thu Apr 17 17:51:07 2025 +0200 @@ -31,6 +31,8 @@ #import "MainWindow.h" #import "WindowManager.h" +#import <objc/runtime.h> + #include "../ui/window.h" #include "../ui/properties.h" #include "../common/context.h" @@ -39,6 +41,7 @@ #include <cx/mempool.h> + static UiObject* create_window(const char *title, BOOL simple) { CxMempool *mp = cxMempoolCreateSimple(256); UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); @@ -52,6 +55,11 @@ obj->wobj = (__bridge void*)window; + MainWindowController *controller = [[MainWindowController alloc] initWithWindow:obj window:window]; + window.windowController = controller; + [window setNextResponder:(NSResponder*)controller]; + objc_setAssociatedObject(window, "windowcontroller", controller, OBJC_ASSOCIATION_RETAIN); + return obj; }