From ccf548d199df2b7b46a8cc32bf4d3ec81e5ef97d Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Sun, 12 Jun 2022 15:42:50 +0200 Subject: [PATCH] feat: implement `CheckMenuItem` (#13) * feat: implement `CheckMenuItem` * linux --- src/lib.rs | 74 +++++++-- src/platform_impl/linux/accelerator.rs | 4 +- src/platform_impl/linux/mod.rs | 195 ++++++++++++++++++++--- src/platform_impl/macos/accelerator.rs | 4 +- src/platform_impl/macos/menu_item.rs | 6 +- src/platform_impl/macos/mod.rs | 10 +- src/platform_impl/windows/accelerator.rs | 2 +- src/platform_impl/windows/mod.rs | 90 +++++++++-- src/platform_impl/windows/util.rs | 2 +- 9 files changed, 328 insertions(+), 59 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 040e1a0..cc7dd8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,11 +106,11 @@ impl Menu { /// /// ## Platform-specific: /// - /// - **Windows / Linux:** The menu label can containt `&` to indicate which letter should get a generated accelerator. + /// - **Windows / Linux:** The menu label can contain `&` to indicate which letter should get a generated accelerator. /// For example, using `&File` for the File menu would result in the label gets an underline under the `F`, /// and the `&` character is not displayed on menu label. /// Then the menu can be activated by press `Alt+F`. - pub fn add_submenu(&mut self, label: impl AsRef, enabled: bool) -> Submenu { + pub fn add_submenu>(&mut self, label: S, enabled: bool) -> Submenu { Submenu(self.0.add_submenu(label, enabled)) } @@ -232,7 +232,7 @@ impl Menu { } } -/// This is a submenu within another [`Submenu`] or [`Menu`]. +/// This is a Submenu within another [`Submenu`] or [`Menu`]. #[derive(Clone)] pub struct Submenu(platform_impl::Submenu); @@ -243,7 +243,7 @@ impl Submenu { } /// Sets a new label for the submenu. - pub fn set_label(&mut self, label: impl AsRef) { + pub fn set_label>(&mut self, label: S) { self.0.set_label(label) } @@ -261,11 +261,11 @@ impl Submenu { /// /// ## Platform-specific: /// - /// - **Windows / Linux:** The menu label can containt `&` to indicate which letter should get a generated accelerator. + /// - **Windows / Linux:** The menu label can contain `&` to indicate which letter should get a generated accelerator. /// For example, using `&File` for the File menu would result in the label gets an underline under the `F`, /// and the `&` character is not displayed on menu label. /// Then the menu can be activated by press `F` when its parent menu is active. - pub fn add_submenu(&mut self, label: impl AsRef, enabled: bool) -> Submenu { + pub fn add_submenu>(&mut self, label: S, enabled: bool) -> Submenu { Submenu(self.0.add_submenu(label, enabled)) } @@ -273,22 +273,34 @@ impl Submenu { /// /// ## Platform-specific: /// - /// - **Windows / Linux:** The menu item label can containt `&` to indicate which letter should get a generated accelerator. + /// - **Windows / Linux:** The menu item label can contain `&` to indicate which letter should get a generated accelerator. /// For example, using `&Save` for the save menu item would result in the label gets an underline under the `S`, /// and the `&` character is not displayed on menu item label. /// Then the menu item can be activated by press `S` when its parent menu is active. - pub fn add_text_item( + pub fn add_text_item>( &mut self, - label: impl AsRef, + label: S, enabled: bool, accelerator: Option<&str>, ) -> TextMenuItem { TextMenuItem(self.0.add_text_item(label, enabled, accelerator)) } + /// Creates a new [`NativeMenuItem`] within this submenu. pub fn add_native_item(&mut self, item: NativeMenuItem) { self.0.add_native_item(item) } + + /// Creates a new [`CheckMenuItem`] within this submenu. + pub fn add_check_item>( + &mut self, + label: S, + enabled: bool, + checked: bool, + accelerator: Option<&str>, + ) -> CheckMenuItem { + CheckMenuItem(self.0.add_check_item(label, enabled, checked, accelerator)) + } } /// This is a Text menu item within a [`Submenu`]. @@ -302,7 +314,7 @@ impl TextMenuItem { } /// Sets a new label for the menu item. - pub fn set_label(&mut self, label: impl AsRef) { + pub fn set_label>(&mut self, label: S) { self.0.set_label(label) } @@ -322,6 +334,48 @@ impl TextMenuItem { } } +/// This is a Check menu item within a [`Submenu`]. +#[derive(Clone)] +pub struct CheckMenuItem(platform_impl::CheckMenuItem); + +impl CheckMenuItem { + /// Gets the menu item's current label. + pub fn label(&self) -> String { + self.0.label() + } + + /// Sets a new label for the menu item. + pub fn set_label>(&mut self, label: S) { + self.0.set_label(label) + } + + /// Gets the menu item's current state, whether enabled or not. + pub fn enabled(&self) -> bool { + self.0.enabled() + } + + /// Enables or disables the menu item. + pub fn set_enabled(&mut self, enabled: bool) { + self.0.set_enabled(enabled) + } + + /// Gets the menu item's current state, whether checked or not. + pub fn checked(&self) -> bool { + self.0.checked() + } + + /// Enables or disables the menu item. + pub fn set_checked(&mut self, checked: bool) { + self.0.set_checked(checked) + } + + /// Gets the unique id for this menu item. + pub fn id(&self) -> u64 { + self.0.id() + } +} + +/// This is a Native menu item within a [`Submenu`] with a predefined behavior. #[non_exhaustive] #[derive(PartialEq, Eq, Debug, Clone)] pub enum NativeMenuItem { diff --git a/src/platform_impl/linux/accelerator.rs b/src/platform_impl/linux/accelerator.rs index 4890a47..b8898bf 100644 --- a/src/platform_impl/linux/accelerator.rs +++ b/src/platform_impl/linux/accelerator.rs @@ -1,4 +1,4 @@ -pub fn to_gtk_menemenoic(string: impl AsRef) -> String { +pub fn to_gtk_menemenoic>(string: S) -> String { string .as_ref() .replace("&&", "[~~]") @@ -6,7 +6,7 @@ pub fn to_gtk_menemenoic(string: impl AsRef) -> String { .replace("[~~]", "&&") } -pub fn to_gtk_accelerator(accelerator: impl AsRef) -> String { +pub fn to_gtk_accelerator>(accelerator: S) -> String { let accelerator = accelerator.as_ref(); let mut s = accelerator.split("+"); let count = s.clone().count(); diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index d258644..c0ff9cf 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -8,13 +8,14 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; static COUNTER: Counter = Counter::new(); /// Generic shared type describing a menu entry. It can be one of [`MenuEntryType`] -#[derive(Debug)] +#[derive(Debug, Default)] struct MenuEntry { label: String, enabled: bool, - r#type: MenuEntryType, - item_id: Option, + checked: bool, + id: u64, accelerator: Option, + r#type: MenuEntryType, entries: Option>>>, } @@ -26,9 +27,16 @@ enum MenuEntryType { // and push to it every time [`Menu::init_for_gtk_window`] is called. Submenu(Vec<(gtk::MenuItem, gtk::Menu)>), Text(Vec), + Check(Vec), Native(NativeMenuItem), } +impl Default for MenuEntryType { + fn default() -> Self { + Self::Text(Default::default()) + } +} + struct InnerMenu { entries: Vec>>, // NOTE(amrbashir): because gtk doesn't allow using the same [`gtk::MenuBar`] and [`gtk::Box`] @@ -51,7 +59,7 @@ impl Menu { }))) } - pub fn add_submenu(&mut self, label: impl AsRef, enabled: bool) -> Submenu { + pub fn add_submenu>(&mut self, label: S, enabled: bool) -> Submenu { let label = label.as_ref().to_string(); let entry = Rc::new(RefCell::new(MenuEntry { @@ -59,8 +67,7 @@ impl Menu { enabled, entries: Some(Vec::new()), r#type: MenuEntryType::Submenu(Vec::new()), - item_id: Default::default(), - accelerator: None, + ..Default::default() })); let mut inner = self.0.borrow_mut(); @@ -185,7 +192,7 @@ impl Submenu { self.0.borrow().label.clone() } - pub fn set_label(&mut self, label: impl AsRef) { + pub fn set_label>(&mut self, label: S) { let label = label.as_ref().to_string(); let mut entry = self.0.borrow_mut(); if let MenuEntryType::Submenu(native_menus) = &mut entry.r#type { @@ -210,7 +217,7 @@ impl Submenu { } } - pub fn add_submenu(&mut self, label: impl AsRef, enabled: bool) -> Submenu { + pub fn add_submenu>(&mut self, label: S, enabled: bool) -> Submenu { let label = label.as_ref().to_string(); let entry = Rc::new(RefCell::new(MenuEntry { @@ -218,8 +225,7 @@ impl Submenu { enabled, entries: Some(Vec::new()), r#type: MenuEntryType::Submenu(Vec::new()), - item_id: Default::default(), - accelerator: None, + ..Default::default() })); let mut inner = self.0.borrow_mut(); @@ -237,9 +243,9 @@ impl Submenu { Submenu(entry, Rc::clone(&self.1)) } - pub fn add_text_item( + pub fn add_text_item>( &mut self, - label: impl AsRef, + label: S, enabled: bool, accelerator: Option<&str>, ) -> TextMenuItem { @@ -250,9 +256,9 @@ impl Submenu { label: label.clone(), enabled, r#type: MenuEntryType::Text(Vec::new()), - item_id: Some(id), + id, accelerator: accelerator.map(|s| s.to_string()), - entries: None, + ..Default::default() })); let mut inner = self.0.borrow_mut(); @@ -288,14 +294,53 @@ impl Submenu { let entry = Rc::new(RefCell::new(MenuEntry { r#type: MenuEntryType::Native(item), - label: Default::default(), - enabled: Default::default(), - item_id: Default::default(), - accelerator: Default::default(), - entries: Default::default(), + ..Default::default() })); inner.entries.as_mut().unwrap().push(entry); } + + pub fn add_check_item>( + &mut self, + label: S, + enabled: bool, + checked: bool, + accelerator: Option<&str>, + ) -> CheckMenuItem { + let label = label.as_ref().to_string(); + let id = COUNTER.next(); + + let entry = Rc::new(RefCell::new(MenuEntry { + label: label.clone(), + enabled, + checked, + r#type: MenuEntryType::Check(Vec::new()), + id, + accelerator: accelerator.map(|s| s.to_string()), + ..Default::default() + })); + + let mut inner = self.0.borrow_mut(); + + if let MenuEntryType::Submenu(native_menus) = &mut inner.r#type { + for (_, menu) in native_menus { + let item = create_gtk_check_menu_item( + &label, + enabled, + checked, + &accelerator.map(|s| s.to_string()), + id, + &*self.1, + ); + menu.append(&item); + if let MenuEntryType::Check(native_items) = &mut entry.borrow_mut().r#type { + native_items.push(item); + } + } + } + + inner.entries.as_mut().unwrap().push(entry.clone()); + CheckMenuItem(entry) + } } #[derive(Clone)] @@ -306,7 +351,7 @@ impl TextMenuItem { self.0.borrow().label.clone() } - pub fn set_label(&mut self, label: impl AsRef) { + pub fn set_label>(&mut self, label: S) { let label = label.as_ref().to_string(); let mut entry = self.0.borrow_mut(); if let MenuEntryType::Text(native_items) = &mut entry.r#type { @@ -332,7 +377,68 @@ impl TextMenuItem { } pub fn id(&self) -> u64 { - self.0.borrow().item_id.unwrap() + self.0.borrow().id + } +} + +#[derive(Clone)] +pub struct CheckMenuItem(Rc>); + +impl CheckMenuItem { + pub fn label(&self) -> String { + self.0.borrow().label.clone() + } + + pub fn set_label>(&mut self, label: S) { + let label = label.as_ref().to_string(); + let mut entry = self.0.borrow_mut(); + if let MenuEntryType::Text(native_items) = &mut entry.r#type { + for item in native_items { + item.set_label(&to_gtk_menemenoic(&label)); + } + } + entry.label = label; + } + + pub fn enabled(&self) -> bool { + self.0.borrow().enabled + } + + pub fn set_enabled(&mut self, enabled: bool) { + let mut entry = self.0.borrow_mut(); + if let MenuEntryType::Check(native_items) = &mut entry.r#type { + for item in native_items { + item.set_sensitive(enabled); + } + } + entry.enabled = enabled; + } + + pub fn checked(&self) -> bool { + let entry = self.0.borrow(); + let mut checked = entry.checked; + if let MenuEntryType::Check(native_items) = &entry.r#type { + if let Some(item) = native_items.get(0) { + checked = item.is_active(); + } + } + + checked + + } + + pub fn set_checked(&mut self, checked: bool) { + let mut entry = self.0.borrow_mut(); + if let MenuEntryType::Check(native_items) = &mut entry.r#type { + for item in native_items { + item.set_active(checked); + } + } + entry.checked = checked; + } + + pub fn id(&self) -> u64 { + self.0.borrow().id } } @@ -343,7 +449,7 @@ fn add_entries_to_menu>( ) { for entry in entries { let mut entry = entry.borrow_mut(); - let (item, submenu) = match &entry.r#type { + let (item, submenu) = match &mut entry.r#type { MenuEntryType::Submenu(_) => { let (item, submenu) = create_gtk_submenu(&entry.label, entry.enabled); gtk_menu.append(&item); @@ -355,12 +461,24 @@ fn add_entries_to_menu>( &entry.label, entry.enabled, &entry.accelerator, - entry.item_id.unwrap(), + entry.id, accel_group, ); gtk_menu.append(&item); (Some(item), None) } + MenuEntryType::Check(_) => { + let item = create_gtk_check_menu_item( + &entry.label, + entry.enabled, + entry.checked, + &entry.accelerator, + entry.id, + accel_group, + ); + gtk_menu.append(&item); + (Some(item.upcast::()), None) + } MenuEntryType::Native(native_menu_item) => { native_menu_item.add_to_gtk_menu(gtk_menu); (None, None) @@ -373,6 +491,9 @@ fn add_entries_to_menu>( } MenuEntryType::Text(native_items) => { native_items.push(item.unwrap()); + } + MenuEntryType::Check(native_items) => { + native_items.push(item.unwrap().downcast().unwrap()); } MenuEntryType::Native(_) => {} }; @@ -414,6 +535,34 @@ fn create_gtk_text_menu_item( item } +fn create_gtk_check_menu_item( + label: &str, + enabled: bool, + checked: bool, + accelerator: &Option, + id: u64, + accel_group: >k::AccelGroup, +) -> gtk::CheckMenuItem { + let item = gtk::CheckMenuItem::with_mnemonic(&to_gtk_menemenoic(label)); + item.set_sensitive(enabled); + item.set_active(checked); + if let Some(accelerator) = accelerator { + let (key, modifiers) = gtk::accelerator_parse(&to_gtk_accelerator(accelerator)); + item.add_accelerator( + "activate", + accel_group, + key, + modifiers, + gtk::AccelFlags::VISIBLE, + ); + } + item.connect_activate(move |_| { + let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id }); + }); + item.show(); + item +} + impl NativeMenuItem { fn add_to_gtk_menu>(&self, gtk_menu: &M) { match self { diff --git a/src/platform_impl/macos/accelerator.rs b/src/platform_impl/macos/accelerator.rs index 1e2303a..2850815 100644 --- a/src/platform_impl/macos/accelerator.rs +++ b/src/platform_impl/macos/accelerator.rs @@ -2,9 +2,7 @@ use cocoa::appkit::NSEventModifierFlags; /// Mnemonic is deprecated since macOS 10 pub fn remove_mnemonic(string: impl AsRef) -> String { - string - .as_ref() - .replace("&", "") + string.as_ref().replace("&", "") } /// Returns a tuple of (Key, Modifier) diff --git a/src/platform_impl/macos/menu_item.rs b/src/platform_impl/macos/menu_item.rs index baae514..39dd990 100644 --- a/src/platform_impl/macos/menu_item.rs +++ b/src/platform_impl/macos/menu_item.rs @@ -25,8 +25,8 @@ pub struct TextMenuItem { } impl TextMenuItem { - pub fn new( - label: impl AsRef, + pub fn new>( + label: S, enabled: bool, selector: Sel, accelerator: Option<&str>, @@ -52,7 +52,7 @@ impl TextMenuItem { self.label.to_string() } - pub fn set_label(&mut self, label: impl AsRef) { + pub fn set_label>(&mut self, label: S) { unsafe { let title = NSString::alloc(nil).init_str(&remove_mnemonic(&label)); self.ns_menu_item.setTitle_(title); diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 2e3366e..e7ff1c1 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -25,7 +25,7 @@ impl Menu { } } - pub fn add_submenu(&mut self, label: impl AsRef, enabled: bool) -> Submenu { + pub fn add_submenu>(&mut self, label: S, enabled: bool) -> Submenu { let menu = Menu::new(); let menu_item = TextMenuItem::new("", enabled, sel!(fireMenubarAction:), None); @@ -64,7 +64,7 @@ impl Submenu { self.menu_item.label() } - pub fn set_label(&mut self, label: impl AsRef) { + pub fn set_label>(&mut self, label: S) { let label = remove_mnemonic(label); self.menu_item.set_label(&label); unsafe { @@ -81,13 +81,13 @@ impl Submenu { self.menu_item.set_enabled(_enabled) } - pub fn add_submenu(&mut self, label: impl AsRef, enabled: bool) -> Submenu { + pub fn add_submenu>(&mut self, label: S, enabled: bool) -> Submenu { self.menu.add_submenu(label, enabled) } - pub fn add_text_item( + pub fn add_text_item>( &mut self, - label: impl AsRef, + label: S, enabled: bool, accelerator: Option<&str>, ) -> TextMenuItem { diff --git a/src/platform_impl/windows/accelerator.rs b/src/platform_impl/windows/accelerator.rs index 65d29f5..c4703d3 100644 --- a/src/platform_impl/windows/accelerator.rs +++ b/src/platform_impl/windows/accelerator.rs @@ -1,7 +1,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{FALT, FCONTROL, FSHIFT, FVIRTKEY}; /// Returns a tuple of (Key, Modifier, a string representation to be used in menu items) -pub fn parse_accelerator(accelerator: impl AsRef) -> (u16, u32, String) { +pub fn parse_accelerator>(accelerator: S) -> (u16, u32, String) { let accelerator = accelerator.as_ref(); let mut s = accelerator.split("+"); let count = s.clone().count(); diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index df48c73..c3e8bf3 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -16,19 +16,17 @@ use windows_sys::Win32::{ AppendMenuW, CloseWindow, CreateAcceleratorTableW, CreateMenu, DrawMenuBar, EnableMenuItem, GetMenuItemInfoW, MessageBoxW, PostQuitMessage, SetMenu, SetMenuItemInfoW, ShowWindow, ACCEL, HACCEL, HMENU, MB_ICONINFORMATION, MENUITEMINFOW, - MFS_DISABLED, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP, MF_SEPARATOR, MF_STRING, - MIIM_STATE, MIIM_STRING, SW_MINIMIZE, WM_COMMAND, + MFS_CHECKED, MFS_DISABLED, MF_CHECKED, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP, + MF_SEPARATOR, MF_STRING, MF_UNCHECKED, MIIM_STATE, MIIM_STRING, SW_MINIMIZE, + WM_COMMAND, }, }, }; use self::accelerator::parse_accelerator; -const MENU_SUBCLASS_ID: usize = 200; const COUNTER_START: u64 = 1000; static COUNTER: Counter = Counter::new_with_start(COUNTER_START); -const ABOUT_COUNTER_START: u64 = 400; -static ABOUT_COUNTER: Counter = Counter::new_with_start(ABOUT_COUNTER_START); struct InnerMenu { hmenu: HMENU, @@ -48,7 +46,7 @@ impl Menu { }))) } - pub fn add_submenu(&mut self, label: impl AsRef, enabled: bool) -> Submenu { + pub fn add_submenu>(&mut self, label: S, enabled: bool) -> Submenu { let hmenu = unsafe { CreateMenu() }; let mut flags = MF_POPUP; if !enabled { @@ -111,7 +109,10 @@ impl Menu { } } +const ABOUT_COUNTER_START: u64 = 400; +static ABOUT_COUNTER: Counter = Counter::new_with_start(ABOUT_COUNTER_START); static mut ABOUT_MENU_ITEMS: Lazy> = Lazy::new(|| HashMap::new()); +static mut CHECK_MENU_ITEMS: Lazy> = Lazy::new(|| Vec::new()); #[derive(Clone)] pub struct Submenu { @@ -140,7 +141,7 @@ impl Submenu { decode_wide(info.dwTypeData) } - pub fn set_label(&mut self, label: impl AsRef) { + pub fn set_label>(&mut self, label: S) { let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; info.cbSize = std::mem::size_of::() as _; info.fMask = MIIM_STRING; @@ -169,7 +170,7 @@ impl Submenu { }; } - pub fn add_submenu(&mut self, label: impl AsRef, enabled: bool) -> Submenu { + pub fn add_submenu>(&mut self, label: S, enabled: bool) -> Submenu { let hmenu = unsafe { CreateMenu() }; let mut flags = MF_POPUP; if !enabled { @@ -190,9 +191,9 @@ impl Submenu { } } - pub fn add_text_item( + pub fn add_text_item>( &mut self, - label: impl AsRef, + label: S, enabled: bool, accelerator: Option<&str>, ) -> TextMenuItem { @@ -261,6 +262,19 @@ impl Submenu { ) }; } + + pub fn add_check_item>( + &mut self, + label: S, + enabled: bool, + checked: bool, + accelerator: Option<&str>, + ) -> CheckMenuItem { + let mut item = CheckMenuItem(self.add_text_item(label, enabled, accelerator)); + item.set_checked(checked); + unsafe { CHECK_MENU_ITEMS.push(item.clone()) }; + item + } } #[derive(Clone)] @@ -296,7 +310,7 @@ impl TextMenuItem { decode_wide(info.dwTypeData) } - pub fn set_label(&mut self, label: impl AsRef) { + pub fn set_label>(&mut self, label: S) { let mut label = label.as_ref().to_string(); let prev_label = self.label_with_accel(); if let Some(accel_str) = prev_label.split("\t").nth(1) { @@ -337,6 +351,54 @@ impl TextMenuItem { } } +#[derive(Clone)] +pub struct CheckMenuItem(TextMenuItem); + +impl CheckMenuItem { + pub fn label(&self) -> String { + self.0.label() + } + + pub fn set_label>(&mut self, label: S) { + self.0.set_label(label) + } + + pub fn enabled(&self) -> bool { + self.0.enabled() + } + + pub fn set_enabled(&mut self, enabled: bool) { + self.0.set_enabled(enabled) + } + + pub fn checked(&self) -> bool { + let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() }; + info.cbSize = std::mem::size_of::() as _; + info.fMask = MIIM_STATE; + + unsafe { GetMenuItemInfoW(self.0.parent_hmenu, self.0.id as _, false.into(), &mut info) }; + + !((info.fState & MFS_CHECKED) == 0) + } + + pub fn set_checked(&mut self, checked: bool) { + use windows_sys::Win32::UI::WindowsAndMessaging; + unsafe { + WindowsAndMessaging::CheckMenuItem( + self.0.parent_hmenu, + self.0.id as _, + if checked { MF_CHECKED } else { MF_UNCHECKED }, + ) + }; + } + + pub fn id(&self) -> u64 { + self.0.id() + } +} + +const MENU_SUBCLASS_ID: usize = 200; + unsafe extern "system" fn menu_subclass_proc( hwnd: HWND, msg: u32, @@ -351,6 +413,12 @@ unsafe extern "system" fn menu_subclass_proc( // Custom menu items if COUNTER_START <= id && id <= COUNTER.current() { + // Toggle check menu items + // TODO: check the behavior in gtk + if let Some(item) = CHECK_MENU_ITEMS.iter_mut().find(|i| i.id() == id) { + item.set_checked(!item.checked()); + } + let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id }); ret = 0; }; diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index d2782e9..0eab037 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -1,5 +1,5 @@ #[cfg(target_os = "windows")] -pub fn encode_wide(string: impl AsRef) -> Vec { +pub fn encode_wide>(string: S) -> Vec { std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref()) .chain(std::iter::once(0)) .collect()