diff options
author | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2022-03-22 11:26:36 +0200 |
---|---|---|
committer | bruvzg <7645683+bruvzg@users.noreply.github.com> | 2022-03-22 12:38:14 +0200 |
commit | 595995a5a7d5b0862cce1d198b8abf823e0f0c34 (patch) | |
tree | f0ea928a3c44bebcb72d0907832392385a710f9e /platform | |
parent | 50f3154d55e9a77fff18ce6592e2ce9bc5ba292d (diff) |
[macOS] Add missing global menu features.
Diffstat (limited to 'platform')
-rw-r--r-- | platform/osx/display_server_osx.h | 39 | ||||
-rw-r--r-- | platform/osx/display_server_osx.mm | 535 | ||||
-rw-r--r-- | platform/osx/godot_menu_item.h | 11 |
3 files changed, 556 insertions, 29 deletions
diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index cc9ac162ea..d9a0d2ce17 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -186,6 +186,7 @@ private: void _process_key_events(); void _update_keyboard_layouts(); static void _keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info); + NSImage *_convert_to_nsimg(Ref<Image> &p_image) const; static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil); @@ -217,24 +218,46 @@ public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; - virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()) override; - virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag = Variant()) override; - virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) override; - virtual void global_menu_add_separator(const String &p_menu_root) override; + virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override; + virtual void global_menu_add_separator(const String &p_menu_root, int p_index = -1) override; + + virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const override; + virtual int global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const override; virtual bool global_menu_is_item_checked(const String &p_menu_root, int p_idx) const override; virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const override; - virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) override; - virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) override; - virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) override; - virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) override; + virtual bool global_menu_is_item_radio_checkable(const String &p_menu_root, int p_idx) const override; + virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) const override; + virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) const override; + virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) const override; + virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override; + virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const override; + virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const override; + virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const override; + virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override; + virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override; + virtual Ref<Texture2D> global_menu_get_item_icon(const String &p_menu_root, int p_idx) const override; virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) override; virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override; + virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override; virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override; virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override; virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override; virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override; + virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) override; + virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) override; + virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) override; + virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override; + virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override; + virtual void global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon) override; virtual int global_menu_get_item_count(const String &p_menu_root) const override; diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 89ca6e50ec..9e32cab5ed 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -91,6 +91,7 @@ NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) { // Submenu. if (!submenu.has(p_menu_root)) { NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; + [n_menu setAutoenablesItems:NO]; submenu[p_menu_root] = n_menu; } menu = submenu[p_menu_root]; @@ -472,6 +473,40 @@ void DisplayServerOSX::_keyboard_layout_changed(CFNotificationCenterRef center, } } +NSImage *DisplayServerOSX::_convert_to_nsimg(Ref<Image> &p_image) const { + p_image->convert(Image::FORMAT_RGBA8); + NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:NULL + pixelsWide:p_image->get_width() + pixelsHigh:p_image->get_height() + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:int(p_image->get_width()) * 4 + bitsPerPixel:32]; + ERR_FAIL_COND_V(imgrep == nil, nil); + uint8_t *pixels = [imgrep bitmapData]; + + int len = p_image->get_width() * p_image->get_height(); + const uint8_t *r = p_image->get_data().ptr(); + + /* Premultiply the alpha channel */ + for (int i = 0; i < len; i++) { + uint8_t alpha = r[i * 4 + 3]; + pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255); + pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255); + pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255); + pixels[i * 4 + 3] = alpha; + } + + NSImage *nsimg = [[NSImage alloc] initWithSize:NSMakeSize(p_image->get_width(), p_image->get_height())]; + ERR_FAIL_COND_V(nsimg == nil, nil); + [nsimg addRepresentation:imgrep]; + return nsimg; +} + NSCursor *DisplayServerOSX::_cursor_from_selector(SEL p_selector, SEL p_fallback) { if ([NSCursor respondsToSelector:p_selector]) { id object = [NSCursor performSelector:p_selector]; @@ -498,7 +533,14 @@ void DisplayServerOSX::menu_callback(id p_sender) { GodotMenuItem *value = [p_sender representedObject]; if (value) { - if (value->checkable) { + if (value->max_states > 0) { + value->state++; + if (value->state >= value->max_states) { + value->state = 0; + } + } + + if (value->checkable_type == CHECKABLE_TYPE_CHECK_BOX) { if ([p_sender state] == NSControlStateValueOff) { [p_sender setState:NSControlStateValueOn]; } else { @@ -671,35 +713,195 @@ String DisplayServerOSX::get_name() const { return "OSX"; } -void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) { +void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_NONE; + obj->max_states = 0; + obj->state = 0; + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; + obj->max_states = 0; + obj->state = 0; + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_NONE; + obj->max_states = 0; + obj->state = 0; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; + obj->max_states = 0; + obj->state = 0; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = p_callback; + obj->meta = p_tag; + obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; + obj->max_states = 0; + obj->state = 0; + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; + [menu_item setRepresentedObject:obj]; + } +} + +void DisplayServerOSX::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; + String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; - obj->checkable = false; + obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; + obj->max_states = 0; + obj->state = 0; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } } -void DisplayServerOSX::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) { +void DisplayServerOSX::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; + String keycode = KeyMappingOSX::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; - obj->checkable = true; + obj->checkable_type = CHECKABLE_TYPE_NONE; + obj->max_states = p_max_states; + obj->state = p_default_state; + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } } -void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu) { +void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) { _THREAD_SAFE_METHOD_ NSMenu *menu = _get_menu_root(p_menu_root); @@ -713,18 +915,58 @@ void DisplayServerOSX::global_menu_add_submenu_item(const String &p_menu_root, c ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!"); return; } - NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""]; + NSMenuItem *menu_item; + if (p_index != -1) { + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index]; + } else { + menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""]; + } + [sub_menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]]; [menu setSubmenu:sub_menu forItem:menu_item]; } } -void DisplayServerOSX::global_menu_add_separator(const String &p_menu_root) { +void DisplayServerOSX::global_menu_add_separator(const String &p_menu_root, int p_index) { _THREAD_SAFE_METHOD_ NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - [menu addItem:[NSMenuItem separatorItem]]; + if (p_index != -1) { + [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index]; + } else { + [menu addItem:[NSMenuItem separatorItem]]; + } + } +} + +int DisplayServerOSX::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; } + + return -1; +} + +int DisplayServerOSX::global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + for (NSInteger i = 0; i < [menu numberOfItems]; i++) { + const NSMenuItem *menu_item = [menu itemAtIndex:i]; + if (menu_item) { + const GodotMenuItem *obj = [menu_item representedObject]; + if (obj && obj->meta == p_tag) { + return i; + } + } + } + } + + return -1; } bool DisplayServerOSX::global_menu_is_item_checked(const String &p_menu_root, int p_idx) const { @@ -749,14 +991,30 @@ bool DisplayServerOSX::global_menu_is_item_checkable(const String &p_menu_root, if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; if (obj) { - return obj->checkable; + return obj->checkable_type == CHECKABLE_TYPE_CHECK_BOX; + } + } + } + return false; +} + +bool DisplayServerOSX::global_menu_is_item_radio_checkable(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->checkable_type == CHECKABLE_TYPE_RADIO_BUTTON; } } } return false; } -Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_root, int p_idx) { +Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_root, int p_idx) const { _THREAD_SAFE_METHOD_ const NSMenu *menu = _get_menu_root(p_menu_root); @@ -772,7 +1030,7 @@ Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_ro return Callable(); } -Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, int p_idx) { +Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, int p_idx) const { _THREAD_SAFE_METHOD_ const NSMenu *menu = _get_menu_root(p_menu_root); @@ -788,22 +1046,20 @@ Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, in return Variant(); } -String DisplayServerOSX::global_menu_get_item_text(const String &p_menu_root, int p_idx) { +String DisplayServerOSX::global_menu_get_item_text(const String &p_menu_root, int p_idx) const { _THREAD_SAFE_METHOD_ const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - String ret; - ret.parse_utf8([[menu_item title] UTF8String]); - return ret; + return String::utf8([[menu_item title] UTF8String]); } } return String(); } -String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, int p_idx) { +String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const { _THREAD_SAFE_METHOD_ const NSMenu *menu = _get_menu_root(p_menu_root); @@ -823,6 +1079,116 @@ String DisplayServerOSX::global_menu_get_item_submenu(const String &p_menu_root, return String(); } +Key DisplayServerOSX::global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + String ret = String::utf8([[menu_item keyEquivalent] UTF8String]); + Key keycode = find_keycode(ret); + NSUInteger mask = [menu_item keyEquivalentModifierMask]; + if (mask & NSEventModifierFlagControl) { + keycode |= KeyModifierMask::CTRL; + } + if (mask & NSEventModifierFlagOption) { + keycode |= KeyModifierMask::ALT; + } + if (mask & NSEventModifierFlagShift) { + keycode |= KeyModifierMask::SHIFT; + } + if (mask & NSEventModifierFlagCommand) { + keycode |= KeyModifierMask::META; + } + if (mask & NSEventModifierFlagNumericPad) { + keycode |= KeyModifierMask::KPAD; + } + return keycode; + } + } + return Key::NONE; +} + +bool DisplayServerOSX::global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + return ![menu_item isEnabled]; + } + } + return false; +} + +String DisplayServerOSX::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + return String::utf8([[menu_item toolTip] UTF8String]); + } + } + return String(); +} + +int DisplayServerOSX::global_menu_get_item_state(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->state; + } + } + } + return 0; +} + +int DisplayServerOSX::global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->max_states; + } + } + } + return 0; +} + +Ref<Texture2D> DisplayServerOSX::global_menu_get_item_icon(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + if (obj->img.is_valid()) { + Ref<ImageTexture> txt; + txt.instantiate(); + txt->create_from_image(obj->img); + return txt; + } + } + } + } + return Ref<Texture2D>(); +} + void DisplayServerOSX::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) { _THREAD_SAFE_METHOD_ @@ -853,7 +1219,23 @@ void DisplayServerOSX::global_menu_set_item_checkable(const String &p_menu_root, NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; - obj->checkable = p_checkable; + obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE; + } + } +} + +void DisplayServerOSX::global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE; } } } @@ -929,6 +1311,116 @@ void DisplayServerOSX::global_menu_set_item_submenu(const String &p_menu_root, i } } +void DisplayServerOSX::global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + [menu_item setKeyEquivalentModifierMask:KeyMappingOSX::keycode_get_native_mask(p_keycode)]; + String keycode = KeyMappingOSX::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK); + [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + } + } +} + +void DisplayServerOSX::global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + [menu_item setEnabled:(!p_disabled)]; + } + } +} + +void DisplayServerOSX::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + [menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]]; + } + } +} + +void DisplayServerOSX::global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + obj->state = p_state; + } + } + } +} + +void DisplayServerOSX::global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + obj->max_states = p_max_states; + } + } + } +} + +void DisplayServerOSX::global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. + return; + } + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (p_icon.is_valid()) { + obj->img = p_icon->get_image(); + obj->img = obj->img->duplicate(); + if (obj->img->is_compressed()) { + obj->img->decompress(); + } + obj->img->resize(16, 16, Image::INTERPOLATE_LANCZOS); + [menu_item setImage:_convert_to_nsimg(obj->img)]; + } else { + obj->img = Ref<Image>(); + [menu_item setImage:nil]; + } + } + } +} + int DisplayServerOSX::global_menu_get_item_count(const String &p_menu_root) const { _THREAD_SAFE_METHOD_ @@ -2628,11 +3120,13 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode // Setup Dock menu. dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"]; + [dock_menu setAutoenablesItems:NO]; // Setup Apple menu. apple_menu = [[NSMenu alloc] initWithTitle:@""]; title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""]; + [apple_menu setAutoenablesItems:NO]; [apple_menu addItem:[NSMenuItem separatorItem]]; @@ -2660,6 +3154,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode NSMenu *main_menu = [NSApp mainMenu]; menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; [main_menu setSubmenu:apple_menu forItem:menu_item]; + [main_menu setAutoenablesItems:NO]; //!!!!!!!!!!!!!!!!!!!!!!!!!! //TODO - do Vulkan and OpenGL support checks, driver selection and fallback diff --git a/platform/osx/godot_menu_item.h b/platform/osx/godot_menu_item.h index 50c4709c18..2c12897f10 100644 --- a/platform/osx/godot_menu_item.h +++ b/platform/osx/godot_menu_item.h @@ -36,12 +36,21 @@ #import <AppKit/AppKit.h> #import <Foundation/Foundation.h> +enum GlobalMenuCheckType { + CHECKABLE_TYPE_NONE, + CHECKABLE_TYPE_CHECK_BOX, + CHECKABLE_TYPE_RADIO_BUTTON, +}; + @interface GodotMenuItem : NSObject { @public Callable callback; Variant meta; int id; - bool checkable; + GlobalMenuCheckType checkable_type; + int max_states; + int state; + Ref<Image> img; } @end |