mirror of
https://github.com/italicsjenga/muda.git
synced 2025-01-10 03:41:30 +11:00
feat: implement CheckMenuItem
(#13)
* feat: implement `CheckMenuItem` * linux
This commit is contained in:
parent
01e7a2a848
commit
ccf548d199
74
src/lib.rs
74
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<str>, enabled: bool) -> Submenu {
|
||||
pub fn add_submenu<S: AsRef<str>>(&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<str>) {
|
||||
pub fn set_label<S: AsRef<str>>(&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<str>, enabled: bool) -> Submenu {
|
||||
pub fn add_submenu<S: AsRef<str>>(&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<S: AsRef<str>>(
|
||||
&mut self,
|
||||
label: impl AsRef<str>,
|
||||
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<S: AsRef<str>>(
|
||||
&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<str>) {
|
||||
pub fn set_label<S: AsRef<str>>(&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<S: AsRef<str>>(&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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
pub fn to_gtk_menemenoic(string: impl AsRef<str>) -> String {
|
||||
pub fn to_gtk_menemenoic<S: AsRef<str>>(string: S) -> String {
|
||||
string
|
||||
.as_ref()
|
||||
.replace("&&", "[~~]")
|
||||
|
@ -6,7 +6,7 @@ pub fn to_gtk_menemenoic(string: impl AsRef<str>) -> String {
|
|||
.replace("[~~]", "&&")
|
||||
}
|
||||
|
||||
pub fn to_gtk_accelerator(accelerator: impl AsRef<str>) -> String {
|
||||
pub fn to_gtk_accelerator<S: AsRef<str>>(accelerator: S) -> String {
|
||||
let accelerator = accelerator.as_ref();
|
||||
let mut s = accelerator.split("+");
|
||||
let count = s.clone().count();
|
||||
|
|
|
@ -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<u64>,
|
||||
checked: bool,
|
||||
id: u64,
|
||||
accelerator: Option<String>,
|
||||
r#type: MenuEntryType,
|
||||
entries: Option<Vec<Rc<RefCell<MenuEntry>>>>,
|
||||
}
|
||||
|
||||
|
@ -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<gtk::MenuItem>),
|
||||
Check(Vec<gtk::CheckMenuItem>),
|
||||
Native(NativeMenuItem),
|
||||
}
|
||||
|
||||
impl Default for MenuEntryType {
|
||||
fn default() -> Self {
|
||||
Self::Text(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
struct InnerMenu {
|
||||
entries: Vec<Rc<RefCell<MenuEntry>>>,
|
||||
// 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<str>, enabled: bool) -> Submenu {
|
||||
pub fn add_submenu<S: AsRef<str>>(&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<str>) {
|
||||
pub fn set_label<S: AsRef<str>>(&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<str>, enabled: bool) -> Submenu {
|
||||
pub fn add_submenu<S: AsRef<str>>(&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<S: AsRef<str>>(
|
||||
&mut self,
|
||||
label: impl AsRef<str>,
|
||||
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<S: AsRef<str>>(
|
||||
&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<str>) {
|
||||
pub fn set_label<S: AsRef<str>>(&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<RefCell<MenuEntry>>);
|
||||
|
||||
impl CheckMenuItem {
|
||||
pub fn label(&self) -> String {
|
||||
self.0.borrow().label.clone()
|
||||
}
|
||||
|
||||
pub fn set_label<S: AsRef<str>>(&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<M: IsA<gtk::MenuShell>>(
|
|||
) {
|
||||
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<M: IsA<gtk::MenuShell>>(
|
|||
&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::<gtk::MenuItem>()), 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<M: IsA<gtk::MenuShell>>(
|
|||
}
|
||||
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<String>,
|
||||
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<M: IsA<gtk::MenuShell>>(&self, gtk_menu: &M) {
|
||||
match self {
|
||||
|
|
|
@ -2,9 +2,7 @@ use cocoa::appkit::NSEventModifierFlags;
|
|||
|
||||
/// Mnemonic is deprecated since macOS 10
|
||||
pub fn remove_mnemonic(string: impl AsRef<str>) -> String {
|
||||
string
|
||||
.as_ref()
|
||||
.replace("&", "")
|
||||
string.as_ref().replace("&", "")
|
||||
}
|
||||
|
||||
/// Returns a tuple of (Key, Modifier)
|
||||
|
|
|
@ -25,8 +25,8 @@ pub struct TextMenuItem {
|
|||
}
|
||||
|
||||
impl TextMenuItem {
|
||||
pub fn new(
|
||||
label: impl AsRef<str>,
|
||||
pub fn new<S: AsRef<str>>(
|
||||
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<str>) {
|
||||
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
|
||||
unsafe {
|
||||
let title = NSString::alloc(nil).init_str(&remove_mnemonic(&label));
|
||||
self.ns_menu_item.setTitle_(title);
|
||||
|
|
|
@ -25,7 +25,7 @@ impl Menu {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
|
||||
pub fn add_submenu<S: AsRef<str>>(&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<str>) {
|
||||
pub fn set_label<S: AsRef<str>>(&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<str>, enabled: bool) -> Submenu {
|
||||
pub fn add_submenu<S: AsRef<str>>(&mut self, label: S, enabled: bool) -> Submenu {
|
||||
self.menu.add_submenu(label, enabled)
|
||||
}
|
||||
|
||||
pub fn add_text_item(
|
||||
pub fn add_text_item<S: AsRef<str>>(
|
||||
&mut self,
|
||||
label: impl AsRef<str>,
|
||||
label: S,
|
||||
enabled: bool,
|
||||
accelerator: Option<&str>,
|
||||
) -> TextMenuItem {
|
||||
|
|
|
@ -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<str>) -> (u16, u32, String) {
|
||||
pub fn parse_accelerator<S: AsRef<str>>(accelerator: S) -> (u16, u32, String) {
|
||||
let accelerator = accelerator.as_ref();
|
||||
let mut s = accelerator.split("+");
|
||||
let count = s.clone().count();
|
||||
|
|
|
@ -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<str>, enabled: bool) -> Submenu {
|
||||
pub fn add_submenu<S: AsRef<str>>(&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<HashMap<u64, NativeMenuItem>> = Lazy::new(|| HashMap::new());
|
||||
static mut CHECK_MENU_ITEMS: Lazy<Vec<CheckMenuItem>> = 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<str>) {
|
||||
pub fn set_label<S: AsRef<str>>(&mut self, label: S) {
|
||||
let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() };
|
||||
info.cbSize = std::mem::size_of::<MENUITEMINFOW>() as _;
|
||||
info.fMask = MIIM_STRING;
|
||||
|
@ -169,7 +170,7 @@ impl Submenu {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn add_submenu(&mut self, label: impl AsRef<str>, enabled: bool) -> Submenu {
|
||||
pub fn add_submenu<S: AsRef<str>>(&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<S: AsRef<str>>(
|
||||
&mut self,
|
||||
label: impl AsRef<str>,
|
||||
label: S,
|
||||
enabled: bool,
|
||||
accelerator: Option<&str>,
|
||||
) -> TextMenuItem {
|
||||
|
@ -261,6 +262,19 @@ impl Submenu {
|
|||
)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn add_check_item<S: AsRef<str>>(
|
||||
&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<str>) {
|
||||
pub fn set_label<S: AsRef<str>>(&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<S: AsRef<str>>(&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::<MENUITEMINFOW>() 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;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#[cfg(target_os = "windows")]
|
||||
pub fn encode_wide(string: impl AsRef<std::ffi::OsStr>) -> Vec<u16> {
|
||||
pub fn encode_wide<S: AsRef<std::ffi::OsStr>>(string: S) -> Vec<u16> {
|
||||
std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref())
|
||||
.chain(std::iter::once(0))
|
||||
.collect()
|
||||
|
|
Loading…
Reference in a new issue