Skip to content

Commit

Permalink
fix: fix menu mnemonics (#640)
Browse files Browse the repository at this point in the history
  • Loading branch information
amrbashir authored Dec 8, 2022
1 parent ca844a2 commit 86a439e
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changes/fix-mnemonic-on-linux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tao": "patch"
---

On Linux, fix menu item mnemonics.
5 changes: 5 additions & 0 deletions .changes/macos-strip-mnemonics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tao": "patch"
---

On macOS, strip menu mnemonics for consistency with other platforms.
25 changes: 23 additions & 2 deletions src/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ pub struct MenuItemAttributes<'a> {
impl<'a> MenuItemAttributes<'a> {
/// Creates a new custom menu item.
///
/// ## Platform-specific
/// `title` could optionally contain an `&` before a character to assign this character as the mnemonic
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`
///
/// ## Platform-specific:
///
/// - **Linux:** `selected` render a regular item
/// - **macOS**: mnemonics are not supported but single `&` will be rmeoved anyways for consistency with other platforms.
pub fn new(title: &'a str) -> Self {
Self {
id: MenuId::new(title),
Expand Down Expand Up @@ -92,6 +95,10 @@ impl<'a> MenuItemAttributes<'a> {
}

/// Assign default checkbox style.
///
/// Default is `false`
///
/// If `selected` is `false`, renders a regular menu item.
pub fn with_selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
Expand All @@ -117,6 +124,13 @@ impl ContextMenu {
}

/// Add a submenu.
///
/// `title` could optionally contain an `&` before a character to assign this character as the mnemonic
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`
///
/// ## Platform-specific:
///
/// - **macOS**: mnemonics are not supported but single `&` will be rmeoved anyways for consistency with other platforms.
pub fn add_submenu(&mut self, title: &str, enabled: bool, submenu: ContextMenu) {
self
.0
Expand Down Expand Up @@ -158,6 +172,13 @@ impl MenuBar {
}

/// Add a submenu.
///
/// `title` could optionally contain an `&` before a character to assign this character as the mnemonic
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`
///
/// ## Platform-specific:
///
/// - **macOS**: mnemonics are not supported but single `&` will be rmeoved anyways for consistency with other platforms.
pub fn add_submenu(&mut self, title: &str, enabled: bool, submenu: MenuBar) {
self
.0
Expand Down
27 changes: 23 additions & 4 deletions src/platform_impl/linux/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,14 @@ impl MenuItemAttributes {
.gtk_item
.label()
.map(|gstr| gstr.as_str().to_owned())
.unwrap_or("".to_owned())
.map(|label| from_gtk_mnemonic(&label))
.unwrap_or_default()
}
pub fn set_enabled(&mut self, is_enabled: bool) {
self.gtk_item.set_sensitive(is_enabled);
}
pub fn set_title(&mut self, title: &str) {
self.gtk_item.set_label(title);
self.gtk_item.set_label(&to_gtk_mnemonic(title));
}

pub fn set_selected(&mut self, is_selected: bool) {
Expand Down Expand Up @@ -129,12 +130,13 @@ impl Menu {
selected: bool,
menu_type: MenuType,
) -> CustomMenuItem {
let title = to_gtk_mnemonic(&title);
let gtk_item = if selected {
let item = CheckMenuItem::with_label(title);
let item = CheckMenuItem::with_label(&title);
item.set_active(true);
item.upcast::<GtkMenuItem>()
} else {
GtkMenuItem::with_label(title)
GtkMenuItem::with_label(&title)
};
let custom_menu = MenuItemAttributes {
id: menu_id,
Expand Down Expand Up @@ -403,3 +405,20 @@ fn modifiers_to_gdk_modifier_type(modifiers: ModifiersState) -> gdk::ModifierTyp

result
}

pub fn to_gtk_mnemonic<S: AsRef<str>>(string: S) -> String {
string
.as_ref()
.replace("&&", "[~~]")
.replace('&', "_")
.replace("[~~]", "&&")
.replace("[~~]", "&")
}

pub fn from_gtk_mnemonic<S: AsRef<str>>(string: S) -> String {
string
.as_ref()
.replace("__", "[~~]")
.replace('_', "&")
.replace("[~~]", "__")
}
10 changes: 7 additions & 3 deletions src/platform_impl/macos/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ impl MenuItemAttributes {
}
}
pub fn set_title(&mut self, title: &str) {
let title = super::util::strip_mnemonic(title);
unsafe {
let menu_title = NSString::alloc(nil).init_str(title);
let menu_title = NSString::alloc(nil).init_str(&title);
self.1.setTitle_(menu_title);
}
}
Expand Down Expand Up @@ -160,7 +161,8 @@ impl Menu {

pub fn add_submenu(&mut self, title: &str, enabled: bool, submenu: Menu) {
unsafe {
let menu_title = NSString::alloc(nil).init_str(title);
let title = super::util::strip_mnemonic(title);
let menu_title = NSString::alloc(nil).init_str(&title);
let menu_item = NSMenuItem::alloc(nil).autorelease();
let () = msg_send![submenu.menu, setTitle: menu_title];
let () = msg_send![menu_item, setTitle: menu_title];
Expand Down Expand Up @@ -360,14 +362,16 @@ pub(crate) fn make_custom_menu_item(
accelerators: Option<Accelerator>,
menu_type: MenuType,
) -> *mut Object {
let title = super::util::strip_mnemonic(title);

let alloc = make_menu_alloc();
let menu_id = Box::new(Action(Box::new(id.0)));
let ptr = Box::into_raw(menu_id);

unsafe {
(&mut *alloc).set_ivar(BLOCK_PTR, ptr as usize);
let _: () = msg_send![&*alloc, setTarget:&*alloc];
let title = NSString::alloc(nil).init_str(title);
let title = NSString::alloc(nil).init_str(&title);
make_menu_item_from_alloc(alloc, title, selector, accelerators, menu_type)
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/platform_impl/macos/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,14 @@ pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, o
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
window.makeFirstResponder_(view);
}

/// Strips single `&` characters from the string.
///
/// `&` can be escaped as `&&` to prevent stripping, in which case a single `&` will be output.
pub fn strip_mnemonic<S: AsRef<str>>(string: S) -> String {
string
.as_ref()
.replace("&&", "[~~]")
.replace('&', "")
.replace("[~~]", "&")
}

0 comments on commit 86a439e

Please sign in to comment.