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:
|
/// ## 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`,
|
/// 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.
|
/// and the `&` character is not displayed on menu label.
|
||||||
/// Then the menu can be activated by press `Alt+F`.
|
/// 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))
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct Submenu(platform_impl::Submenu);
|
pub struct Submenu(platform_impl::Submenu);
|
||||||
|
|
||||||
|
@ -243,7 +243,7 @@ impl Submenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a new label for the 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)
|
self.0.set_label(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,11 +261,11 @@ impl Submenu {
|
||||||
///
|
///
|
||||||
/// ## Platform-specific:
|
/// ## 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`,
|
/// 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.
|
/// and the `&` character is not displayed on menu label.
|
||||||
/// Then the menu can be activated by press `F` when its parent menu is active.
|
/// 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))
|
Submenu(self.0.add_submenu(label, enabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,22 +273,34 @@ impl Submenu {
|
||||||
///
|
///
|
||||||
/// ## Platform-specific:
|
/// ## 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`,
|
/// 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.
|
/// 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.
|
/// 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,
|
&mut self,
|
||||||
label: impl AsRef<str>,
|
label: S,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
accelerator: Option<&str>,
|
accelerator: Option<&str>,
|
||||||
) -> TextMenuItem {
|
) -> TextMenuItem {
|
||||||
TextMenuItem(self.0.add_text_item(label, enabled, accelerator))
|
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) {
|
pub fn add_native_item(&mut self, item: NativeMenuItem) {
|
||||||
self.0.add_native_item(item)
|
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`].
|
/// This is a Text menu item within a [`Submenu`].
|
||||||
|
@ -302,7 +314,7 @@ impl TextMenuItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a new label for the menu item.
|
/// 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)
|
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]
|
#[non_exhaustive]
|
||||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||||
pub enum NativeMenuItem {
|
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
|
string
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.replace("&&", "[~~]")
|
.replace("&&", "[~~]")
|
||||||
|
@ -6,7 +6,7 @@ pub fn to_gtk_menemenoic(string: impl AsRef<str>) -> String {
|
||||||
.replace("[~~]", "&&")
|
.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 accelerator = accelerator.as_ref();
|
||||||
let mut s = accelerator.split("+");
|
let mut s = accelerator.split("+");
|
||||||
let count = s.clone().count();
|
let count = s.clone().count();
|
||||||
|
|
|
@ -8,13 +8,14 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
static COUNTER: Counter = Counter::new();
|
static COUNTER: Counter = Counter::new();
|
||||||
|
|
||||||
/// Generic shared type describing a menu entry. It can be one of [`MenuEntryType`]
|
/// Generic shared type describing a menu entry. It can be one of [`MenuEntryType`]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
struct MenuEntry {
|
struct MenuEntry {
|
||||||
label: String,
|
label: String,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
r#type: MenuEntryType,
|
checked: bool,
|
||||||
item_id: Option<u64>,
|
id: u64,
|
||||||
accelerator: Option<String>,
|
accelerator: Option<String>,
|
||||||
|
r#type: MenuEntryType,
|
||||||
entries: Option<Vec<Rc<RefCell<MenuEntry>>>>,
|
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.
|
// and push to it every time [`Menu::init_for_gtk_window`] is called.
|
||||||
Submenu(Vec<(gtk::MenuItem, gtk::Menu)>),
|
Submenu(Vec<(gtk::MenuItem, gtk::Menu)>),
|
||||||
Text(Vec<gtk::MenuItem>),
|
Text(Vec<gtk::MenuItem>),
|
||||||
|
Check(Vec<gtk::CheckMenuItem>),
|
||||||
Native(NativeMenuItem),
|
Native(NativeMenuItem),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for MenuEntryType {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Text(Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct InnerMenu {
|
struct InnerMenu {
|
||||||
entries: Vec<Rc<RefCell<MenuEntry>>>,
|
entries: Vec<Rc<RefCell<MenuEntry>>>,
|
||||||
// NOTE(amrbashir): because gtk doesn't allow using the same [`gtk::MenuBar`] and [`gtk::Box`]
|
// 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 label = label.as_ref().to_string();
|
||||||
|
|
||||||
let entry = Rc::new(RefCell::new(MenuEntry {
|
let entry = Rc::new(RefCell::new(MenuEntry {
|
||||||
|
@ -59,8 +67,7 @@ impl Menu {
|
||||||
enabled,
|
enabled,
|
||||||
entries: Some(Vec::new()),
|
entries: Some(Vec::new()),
|
||||||
r#type: MenuEntryType::Submenu(Vec::new()),
|
r#type: MenuEntryType::Submenu(Vec::new()),
|
||||||
item_id: Default::default(),
|
..Default::default()
|
||||||
accelerator: None,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let mut inner = self.0.borrow_mut();
|
let mut inner = self.0.borrow_mut();
|
||||||
|
@ -185,7 +192,7 @@ impl Submenu {
|
||||||
self.0.borrow().label.clone()
|
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 label = label.as_ref().to_string();
|
||||||
let mut entry = self.0.borrow_mut();
|
let mut entry = self.0.borrow_mut();
|
||||||
if let MenuEntryType::Submenu(native_menus) = &mut entry.r#type {
|
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 label = label.as_ref().to_string();
|
||||||
|
|
||||||
let entry = Rc::new(RefCell::new(MenuEntry {
|
let entry = Rc::new(RefCell::new(MenuEntry {
|
||||||
|
@ -218,8 +225,7 @@ impl Submenu {
|
||||||
enabled,
|
enabled,
|
||||||
entries: Some(Vec::new()),
|
entries: Some(Vec::new()),
|
||||||
r#type: MenuEntryType::Submenu(Vec::new()),
|
r#type: MenuEntryType::Submenu(Vec::new()),
|
||||||
item_id: Default::default(),
|
..Default::default()
|
||||||
accelerator: None,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let mut inner = self.0.borrow_mut();
|
let mut inner = self.0.borrow_mut();
|
||||||
|
@ -237,9 +243,9 @@ impl Submenu {
|
||||||
Submenu(entry, Rc::clone(&self.1))
|
Submenu(entry, Rc::clone(&self.1))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_text_item(
|
pub fn add_text_item<S: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
label: impl AsRef<str>,
|
label: S,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
accelerator: Option<&str>,
|
accelerator: Option<&str>,
|
||||||
) -> TextMenuItem {
|
) -> TextMenuItem {
|
||||||
|
@ -250,9 +256,9 @@ impl Submenu {
|
||||||
label: label.clone(),
|
label: label.clone(),
|
||||||
enabled,
|
enabled,
|
||||||
r#type: MenuEntryType::Text(Vec::new()),
|
r#type: MenuEntryType::Text(Vec::new()),
|
||||||
item_id: Some(id),
|
id,
|
||||||
accelerator: accelerator.map(|s| s.to_string()),
|
accelerator: accelerator.map(|s| s.to_string()),
|
||||||
entries: None,
|
..Default::default()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let mut inner = self.0.borrow_mut();
|
let mut inner = self.0.borrow_mut();
|
||||||
|
@ -288,14 +294,53 @@ impl Submenu {
|
||||||
|
|
||||||
let entry = Rc::new(RefCell::new(MenuEntry {
|
let entry = Rc::new(RefCell::new(MenuEntry {
|
||||||
r#type: MenuEntryType::Native(item),
|
r#type: MenuEntryType::Native(item),
|
||||||
label: Default::default(),
|
..Default::default()
|
||||||
enabled: Default::default(),
|
|
||||||
item_id: Default::default(),
|
|
||||||
accelerator: Default::default(),
|
|
||||||
entries: Default::default(),
|
|
||||||
}));
|
}));
|
||||||
inner.entries.as_mut().unwrap().push(entry);
|
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)]
|
#[derive(Clone)]
|
||||||
|
@ -306,7 +351,7 @@ impl TextMenuItem {
|
||||||
self.0.borrow().label.clone()
|
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 label = label.as_ref().to_string();
|
||||||
let mut entry = self.0.borrow_mut();
|
let mut entry = self.0.borrow_mut();
|
||||||
if let MenuEntryType::Text(native_items) = &mut entry.r#type {
|
if let MenuEntryType::Text(native_items) = &mut entry.r#type {
|
||||||
|
@ -332,7 +377,68 @@ impl TextMenuItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> u64 {
|
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 {
|
for entry in entries {
|
||||||
let mut entry = entry.borrow_mut();
|
let mut entry = entry.borrow_mut();
|
||||||
let (item, submenu) = match &entry.r#type {
|
let (item, submenu) = match &mut entry.r#type {
|
||||||
MenuEntryType::Submenu(_) => {
|
MenuEntryType::Submenu(_) => {
|
||||||
let (item, submenu) = create_gtk_submenu(&entry.label, entry.enabled);
|
let (item, submenu) = create_gtk_submenu(&entry.label, entry.enabled);
|
||||||
gtk_menu.append(&item);
|
gtk_menu.append(&item);
|
||||||
|
@ -355,12 +461,24 @@ fn add_entries_to_menu<M: IsA<gtk::MenuShell>>(
|
||||||
&entry.label,
|
&entry.label,
|
||||||
entry.enabled,
|
entry.enabled,
|
||||||
&entry.accelerator,
|
&entry.accelerator,
|
||||||
entry.item_id.unwrap(),
|
entry.id,
|
||||||
accel_group,
|
accel_group,
|
||||||
);
|
);
|
||||||
gtk_menu.append(&item);
|
gtk_menu.append(&item);
|
||||||
(Some(item), None)
|
(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) => {
|
MenuEntryType::Native(native_menu_item) => {
|
||||||
native_menu_item.add_to_gtk_menu(gtk_menu);
|
native_menu_item.add_to_gtk_menu(gtk_menu);
|
||||||
(None, None)
|
(None, None)
|
||||||
|
@ -373,6 +491,9 @@ fn add_entries_to_menu<M: IsA<gtk::MenuShell>>(
|
||||||
}
|
}
|
||||||
MenuEntryType::Text(native_items) => {
|
MenuEntryType::Text(native_items) => {
|
||||||
native_items.push(item.unwrap());
|
native_items.push(item.unwrap());
|
||||||
|
}
|
||||||
|
MenuEntryType::Check(native_items) => {
|
||||||
|
native_items.push(item.unwrap().downcast().unwrap());
|
||||||
}
|
}
|
||||||
MenuEntryType::Native(_) => {}
|
MenuEntryType::Native(_) => {}
|
||||||
};
|
};
|
||||||
|
@ -414,6 +535,34 @@ fn create_gtk_text_menu_item(
|
||||||
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 {
|
impl NativeMenuItem {
|
||||||
fn add_to_gtk_menu<M: IsA<gtk::MenuShell>>(&self, gtk_menu: &M) {
|
fn add_to_gtk_menu<M: IsA<gtk::MenuShell>>(&self, gtk_menu: &M) {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -2,9 +2,7 @@ use cocoa::appkit::NSEventModifierFlags;
|
||||||
|
|
||||||
/// Mnemonic is deprecated since macOS 10
|
/// Mnemonic is deprecated since macOS 10
|
||||||
pub fn remove_mnemonic(string: impl AsRef<str>) -> String {
|
pub fn remove_mnemonic(string: impl AsRef<str>) -> String {
|
||||||
string
|
string.as_ref().replace("&", "")
|
||||||
.as_ref()
|
|
||||||
.replace("&", "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a tuple of (Key, Modifier)
|
/// Returns a tuple of (Key, Modifier)
|
||||||
|
|
|
@ -25,8 +25,8 @@ pub struct TextMenuItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextMenuItem {
|
impl TextMenuItem {
|
||||||
pub fn new(
|
pub fn new<S: AsRef<str>>(
|
||||||
label: impl AsRef<str>,
|
label: S,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
selector: Sel,
|
selector: Sel,
|
||||||
accelerator: Option<&str>,
|
accelerator: Option<&str>,
|
||||||
|
@ -52,7 +52,7 @@ impl TextMenuItem {
|
||||||
self.label.to_string()
|
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 {
|
unsafe {
|
||||||
let title = NSString::alloc(nil).init_str(&remove_mnemonic(&label));
|
let title = NSString::alloc(nil).init_str(&remove_mnemonic(&label));
|
||||||
self.ns_menu_item.setTitle_(title);
|
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 = Menu::new();
|
||||||
let menu_item = TextMenuItem::new("", enabled, sel!(fireMenubarAction:), None);
|
let menu_item = TextMenuItem::new("", enabled, sel!(fireMenubarAction:), None);
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ impl Submenu {
|
||||||
self.menu_item.label()
|
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);
|
let label = remove_mnemonic(label);
|
||||||
self.menu_item.set_label(&label);
|
self.menu_item.set_label(&label);
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -81,13 +81,13 @@ impl Submenu {
|
||||||
self.menu_item.set_enabled(_enabled)
|
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)
|
self.menu.add_submenu(label, enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_text_item(
|
pub fn add_text_item<S: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
label: impl AsRef<str>,
|
label: S,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
accelerator: Option<&str>,
|
accelerator: Option<&str>,
|
||||||
) -> TextMenuItem {
|
) -> TextMenuItem {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use windows_sys::Win32::UI::WindowsAndMessaging::{FALT, FCONTROL, FSHIFT, FVIRTKEY};
|
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)
|
/// 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 accelerator = accelerator.as_ref();
|
||||||
let mut s = accelerator.split("+");
|
let mut s = accelerator.split("+");
|
||||||
let count = s.clone().count();
|
let count = s.clone().count();
|
||||||
|
|
|
@ -16,19 +16,17 @@ use windows_sys::Win32::{
|
||||||
AppendMenuW, CloseWindow, CreateAcceleratorTableW, CreateMenu, DrawMenuBar,
|
AppendMenuW, CloseWindow, CreateAcceleratorTableW, CreateMenu, DrawMenuBar,
|
||||||
EnableMenuItem, GetMenuItemInfoW, MessageBoxW, PostQuitMessage, SetMenu,
|
EnableMenuItem, GetMenuItemInfoW, MessageBoxW, PostQuitMessage, SetMenu,
|
||||||
SetMenuItemInfoW, ShowWindow, ACCEL, HACCEL, HMENU, MB_ICONINFORMATION, MENUITEMINFOW,
|
SetMenuItemInfoW, ShowWindow, ACCEL, HACCEL, HMENU, MB_ICONINFORMATION, MENUITEMINFOW,
|
||||||
MFS_DISABLED, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP, MF_SEPARATOR, MF_STRING,
|
MFS_CHECKED, MFS_DISABLED, MF_CHECKED, MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_POPUP,
|
||||||
MIIM_STATE, MIIM_STRING, SW_MINIMIZE, WM_COMMAND,
|
MF_SEPARATOR, MF_STRING, MF_UNCHECKED, MIIM_STATE, MIIM_STRING, SW_MINIMIZE,
|
||||||
|
WM_COMMAND,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::accelerator::parse_accelerator;
|
use self::accelerator::parse_accelerator;
|
||||||
|
|
||||||
const MENU_SUBCLASS_ID: usize = 200;
|
|
||||||
const COUNTER_START: u64 = 1000;
|
const COUNTER_START: u64 = 1000;
|
||||||
static COUNTER: Counter = Counter::new_with_start(COUNTER_START);
|
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 {
|
struct InnerMenu {
|
||||||
hmenu: HMENU,
|
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 hmenu = unsafe { CreateMenu() };
|
||||||
let mut flags = MF_POPUP;
|
let mut flags = MF_POPUP;
|
||||||
if !enabled {
|
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 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)]
|
#[derive(Clone)]
|
||||||
pub struct Submenu {
|
pub struct Submenu {
|
||||||
|
@ -140,7 +141,7 @@ impl Submenu {
|
||||||
decode_wide(info.dwTypeData)
|
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() };
|
let mut info: MENUITEMINFOW = unsafe { std::mem::zeroed() };
|
||||||
info.cbSize = std::mem::size_of::<MENUITEMINFOW>() as _;
|
info.cbSize = std::mem::size_of::<MENUITEMINFOW>() as _;
|
||||||
info.fMask = MIIM_STRING;
|
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 hmenu = unsafe { CreateMenu() };
|
||||||
let mut flags = MF_POPUP;
|
let mut flags = MF_POPUP;
|
||||||
if !enabled {
|
if !enabled {
|
||||||
|
@ -190,9 +191,9 @@ impl Submenu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_text_item(
|
pub fn add_text_item<S: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
label: impl AsRef<str>,
|
label: S,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
accelerator: Option<&str>,
|
accelerator: Option<&str>,
|
||||||
) -> TextMenuItem {
|
) -> 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)]
|
#[derive(Clone)]
|
||||||
|
@ -296,7 +310,7 @@ impl TextMenuItem {
|
||||||
decode_wide(info.dwTypeData)
|
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 mut label = label.as_ref().to_string();
|
||||||
let prev_label = self.label_with_accel();
|
let prev_label = self.label_with_accel();
|
||||||
if let Some(accel_str) = prev_label.split("\t").nth(1) {
|
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(
|
unsafe extern "system" fn menu_subclass_proc(
|
||||||
hwnd: HWND,
|
hwnd: HWND,
|
||||||
msg: u32,
|
msg: u32,
|
||||||
|
@ -351,6 +413,12 @@ unsafe extern "system" fn menu_subclass_proc(
|
||||||
|
|
||||||
// Custom menu items
|
// Custom menu items
|
||||||
if COUNTER_START <= id && id <= COUNTER.current() {
|
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 });
|
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id });
|
||||||
ret = 0;
|
ret = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#[cfg(target_os = "windows")]
|
#[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())
|
std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref())
|
||||||
.chain(std::iter::once(0))
|
.chain(std::iter::once(0))
|
||||||
.collect()
|
.collect()
|
||||||
|
|
Loading…
Reference in a new issue