mirror of
https://github.com/italicsjenga/muda.git
synced 2025-01-11 04:11:32 +11:00
use an event channel instead of callbacks
This commit is contained in:
parent
b5598554a0
commit
fbef2d8a40
15
Cargo.toml
15
Cargo.toml
|
@ -3,15 +3,20 @@ name = "menu-rs"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[dependencies]
|
||||||
windows-sys = { version = "0.34", features = [
|
crossbeam-channel = "0.5"
|
||||||
|
once_cell = "1.10"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||||
|
version = "0.34"
|
||||||
|
features = [
|
||||||
"Win32_UI_WindowsAndMessaging",
|
"Win32_UI_WindowsAndMessaging",
|
||||||
"Win32_Foundation",
|
"Win32_Foundation",
|
||||||
"Win32_Graphics_Gdi"
|
"Win32_Graphics_Gdi",
|
||||||
] }
|
]
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
parking_lot = "0.12.0"
|
parking_lot = "0.12"
|
||||||
gtk = "0.15"
|
gtk = "0.15"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use menu_rs::Menu;
|
use menu_rs::{menu_event_receiver, Menu};
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use tao::platform::unix::WindowExtUnix;
|
use tao::platform::unix::WindowExtUnix;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
@ -9,12 +9,8 @@ use tao::{
|
||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum UserEvent {
|
|
||||||
MenuEvent(u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let event_loop = EventLoop::<UserEvent>::with_user_event();
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||||
let window2 = WindowBuilder::new().build(&event_loop).unwrap();
|
let window2 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||||
|
@ -23,19 +19,13 @@ fn main() {
|
||||||
let mut file_menu = menu_bar.add_submenu("File", true);
|
let mut file_menu = menu_bar.add_submenu("File", true);
|
||||||
let mut edit_menu = menu_bar.add_submenu("Edit", true);
|
let mut edit_menu = menu_bar.add_submenu("Edit", true);
|
||||||
|
|
||||||
let mut open_item = file_menu.add_text_item("Open", true, |_| {});
|
let mut open_item = file_menu.add_text_item("Open", true);
|
||||||
|
|
||||||
let proxy = event_loop.create_proxy();
|
let mut save_item = file_menu.add_text_item("Save", true);
|
||||||
let mut counter = 0;
|
let _quit_item = file_menu.add_text_item("Quit", true);
|
||||||
let save_item = file_menu.add_text_item("Save", true, move |i| {
|
|
||||||
counter += 1;
|
|
||||||
i.set_label(format!("Save triggered {} times", counter));
|
|
||||||
let _ = proxy.send_event(UserEvent::MenuEvent(i.id()));
|
|
||||||
});
|
|
||||||
let _quit_item = file_menu.add_text_item("Quit", true, |_| {});
|
|
||||||
|
|
||||||
let _copy_item = edit_menu.add_text_item("Copy", true, |_| {});
|
let _copy_item = edit_menu.add_text_item("Copy", true);
|
||||||
let _cut_item = edit_menu.add_text_item("Cut", true, |_| {});
|
let _cut_item = edit_menu.add_text_item("Cut", true);
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
|
@ -48,10 +38,29 @@ fn main() {
|
||||||
menu_bar.init_for_gtk_window(window2.gtk_window());
|
menu_bar.init_for_gtk_window(window2.gtk_window());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let menu_channel = menu_event_receiver();
|
||||||
let mut open_item_disabled = false;
|
let mut open_item_disabled = false;
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
*control_flow = ControlFlow::Wait;
|
*control_flow = ControlFlow::Wait;
|
||||||
|
|
||||||
|
if let Ok(event) = menu_channel.try_recv() {
|
||||||
|
match event.id {
|
||||||
|
_ if event.id == save_item.id() => {
|
||||||
|
println!("Save menu item triggered");
|
||||||
|
|
||||||
|
save_item.set_label(format!("Save triggered {counter} times"));
|
||||||
|
if !open_item_disabled {
|
||||||
|
println!("Open item disabled!");
|
||||||
|
open_item.set_enabled(false);
|
||||||
|
open_item_disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
event: WindowEvent::CloseRequested,
|
event: WindowEvent::CloseRequested,
|
||||||
|
@ -60,20 +69,6 @@ fn main() {
|
||||||
Event::MainEventsCleared => {
|
Event::MainEventsCleared => {
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
Event::UserEvent(e) => match e {
|
|
||||||
UserEvent::MenuEvent(id) => {
|
|
||||||
if id == save_item.id() {
|
|
||||||
println!("Save menu item triggered");
|
|
||||||
|
|
||||||
if !open_item_disabled {
|
|
||||||
println!("Open item disabled!");
|
|
||||||
open_item.set_enabled(false);
|
|
||||||
open_item_disabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use menu_rs::Menu;
|
use menu_rs::{menu_event_receiver, Menu};
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use winit::platform::windows::WindowExtWindows;
|
use winit::platform::windows::WindowExtWindows;
|
||||||
use winit::{
|
use winit::{
|
||||||
|
@ -7,63 +7,61 @@ use winit::{
|
||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum UserEvent {
|
|
||||||
MenuEvent(u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let event_loop = EventLoop::<UserEvent>::with_user_event();
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
let _window = WindowBuilder::new().build(&event_loop).unwrap();
|
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||||
let _window2 = WindowBuilder::new().build(&event_loop).unwrap();
|
let window2 = WindowBuilder::new().build(&event_loop).unwrap();
|
||||||
|
|
||||||
let mut menu_bar = Menu::new();
|
let mut menu_bar = Menu::new();
|
||||||
let mut file_menu = menu_bar.add_submenu("File", true);
|
let mut file_menu = menu_bar.add_submenu("File", true);
|
||||||
let mut edit_menu = menu_bar.add_submenu("Edit", true);
|
let mut edit_menu = menu_bar.add_submenu("Edit", true);
|
||||||
|
|
||||||
let mut open_item = file_menu.add_text_item("Open", true, |_| {});
|
let mut open_item = file_menu.add_text_item("Open", true);
|
||||||
|
|
||||||
let proxy = event_loop.create_proxy();
|
let mut save_item = file_menu.add_text_item("Save", true);
|
||||||
let mut counter = 0;
|
let _quit_item = file_menu.add_text_item("Quit", true);
|
||||||
let save_item = file_menu.add_text_item("Save", true, move |i| {
|
|
||||||
counter += 1;
|
|
||||||
i.set_label(format!("Save triggered {} times", counter));
|
|
||||||
let _ = proxy.send_event(UserEvent::MenuEvent(i.id()));
|
|
||||||
});
|
|
||||||
let _quit_item = file_menu.add_text_item("Quit", true, |_| {});
|
|
||||||
|
|
||||||
let _copy_item = edit_menu.add_text_item("Copy", true, |_| {});
|
let _copy_item = edit_menu.add_text_item("Copy", true);
|
||||||
let _cut_item = edit_menu.add_text_item("Cut", true, |_| {});
|
let _cut_item = edit_menu.add_text_item("Cut", true);
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
menu_bar.init_for_hwnd(_window.hwnd() as _);
|
menu_bar.init_for_hwnd(window.hwnd() as _);
|
||||||
menu_bar.init_for_hwnd(_window2.hwnd() as _);
|
menu_bar.init_for_hwnd(window2.hwnd() as _);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let menu_channel = menu_event_receiver();
|
||||||
let mut open_item_disabled = false;
|
let mut open_item_disabled = false;
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
*control_flow = ControlFlow::Wait;
|
*control_flow = ControlFlow::Wait;
|
||||||
|
|
||||||
|
if let Ok(event) = menu_channel.try_recv() {
|
||||||
|
match event.id {
|
||||||
|
_ if event.id == save_item.id() => {
|
||||||
|
println!("Save menu item triggered");
|
||||||
|
|
||||||
|
save_item.set_label(format!("Save triggered {counter} times"));
|
||||||
|
if !open_item_disabled {
|
||||||
|
println!("Open item disabled!");
|
||||||
|
open_item.set_enabled(false);
|
||||||
|
open_item_disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
event: WindowEvent::CloseRequested,
|
event: WindowEvent::CloseRequested,
|
||||||
..
|
..
|
||||||
} => *control_flow = ControlFlow::Exit,
|
} => *control_flow = ControlFlow::Exit,
|
||||||
|
Event::MainEventsCleared => {
|
||||||
Event::UserEvent(e) => match e {
|
window.request_redraw();
|
||||||
UserEvent::MenuEvent(id) => {
|
}
|
||||||
if id == save_item.id() {
|
|
||||||
println!("Save menu item triggered");
|
|
||||||
|
|
||||||
if !open_item_disabled {
|
|
||||||
println!("Open item disabled!");
|
|
||||||
open_item.set_enabled(false);
|
|
||||||
open_item_disabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
24
src/lib.rs
24
src/lib.rs
|
@ -1,6 +1,21 @@
|
||||||
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
mod platform_impl;
|
mod platform_impl;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
static MENU_CHANNEL: Lazy<(Sender<MenuEvent>, Receiver<MenuEvent>)> = Lazy::new(|| unbounded());
|
||||||
|
|
||||||
|
/// Event channel for receiving menu events.
|
||||||
|
pub fn menu_event_receiver<'a>() -> &'a Receiver<MenuEvent> {
|
||||||
|
&MENU_CHANNEL.1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes a menu event emitted when a menu item is activated
|
||||||
|
pub struct MenuEvent {
|
||||||
|
pub id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Menu(platform_impl::Menu);
|
pub struct Menu(platform_impl::Menu);
|
||||||
|
|
||||||
impl Menu {
|
impl Menu {
|
||||||
|
@ -36,13 +51,8 @@ impl Submenu {
|
||||||
Submenu(self.0.add_submenu(label, enabled))
|
Submenu(self.0.add_submenu(label, enabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_text_item<F: FnMut(&mut TextMenuItem) + 'static>(
|
pub fn add_text_item(&mut self, label: impl AsRef<str>, enabled: bool) -> TextMenuItem {
|
||||||
&mut self,
|
TextMenuItem(self.0.add_text_item(label, enabled))
|
||||||
label: impl AsRef<str>,
|
|
||||||
enabled: bool,
|
|
||||||
f: F,
|
|
||||||
) -> TextMenuItem {
|
|
||||||
TextMenuItem(self.0.add_text_item(label, enabled, f))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
use gtk::{prelude::*, Orientation};
|
use gtk::{prelude::*, Orientation};
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ struct MenuEntry {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
entries: Option<Vec<Arc<Mutex<MenuEntry>>>>,
|
entries: Option<Vec<Arc<Mutex<MenuEntry>>>>,
|
||||||
etype: MenuEntryType,
|
etype: MenuEntryType,
|
||||||
item_handler: Option<Rc<RefCell<dyn FnMut(&mut crate::TextMenuItem) + 'static>>>,
|
|
||||||
item_id: Option<u64>,
|
item_id: Option<u64>,
|
||||||
menu_gtk_items: Option<Arc<Mutex<Vec<(gtk::MenuItem, gtk::Menu)>>>>,
|
menu_gtk_items: Option<Arc<Mutex<Vec<(gtk::MenuItem, gtk::Menu)>>>>,
|
||||||
item_gtk_items: Option<Arc<Mutex<Vec<gtk::MenuItem>>>>,
|
item_gtk_items: Option<Arc<Mutex<Vec<gtk::MenuItem>>>>,
|
||||||
|
@ -46,7 +45,6 @@ impl Menu {
|
||||||
enabled,
|
enabled,
|
||||||
entries: Some(Vec::new()),
|
entries: Some(Vec::new()),
|
||||||
etype: MenuEntryType::Submenu,
|
etype: MenuEntryType::Submenu,
|
||||||
item_handler: None,
|
|
||||||
item_id: None,
|
item_id: None,
|
||||||
menu_gtk_items: Some(gtk_items.clone()),
|
menu_gtk_items: Some(gtk_items.clone()),
|
||||||
item_gtk_items: None,
|
item_gtk_items: None,
|
||||||
|
@ -78,7 +76,6 @@ impl Menu {
|
||||||
|
|
||||||
fn add_entries_to_menu<M: IsA<gtk::MenuShell>>(gtk_menu: &M, entries: &Vec<Arc<Mutex<MenuEntry>>>) {
|
fn add_entries_to_menu<M: IsA<gtk::MenuShell>>(gtk_menu: &M, entries: &Vec<Arc<Mutex<MenuEntry>>>) {
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let entry_clone = entry.clone();
|
|
||||||
let mut entry = entry.lock();
|
let mut entry = entry.lock();
|
||||||
let gtk_item = gtk::MenuItem::with_label(&entry.label);
|
let gtk_item = gtk::MenuItem::with_label(&entry.label);
|
||||||
gtk_menu.append(>k_item);
|
gtk_menu.append(>k_item);
|
||||||
|
@ -94,18 +91,9 @@ fn add_entries_to_menu<M: IsA<gtk::MenuShell>>(gtk_menu: &M, entries: &Vec<Arc<M
|
||||||
.lock()
|
.lock()
|
||||||
.push((gtk_item, gtk_menu));
|
.push((gtk_item, gtk_menu));
|
||||||
} else {
|
} else {
|
||||||
let handler = Rc::clone(&entry.item_handler.as_mut().unwrap());
|
let id = entry.item_id.unwrap_or_default();
|
||||||
let item = TextMenuItem {
|
|
||||||
label: entry.label.clone(),
|
|
||||||
enabled: entry.enabled,
|
|
||||||
id: entry.item_id.unwrap(),
|
|
||||||
entry: entry_clone,
|
|
||||||
gtk_items: entry.item_gtk_items.as_ref().unwrap().clone(),
|
|
||||||
};
|
|
||||||
gtk_item.connect_activate(move |_| {
|
gtk_item.connect_activate(move |_| {
|
||||||
let mut handler = handler.borrow_mut();
|
let _ = crate::MENU_CHANNEL.0.send(crate::MenuEvent { id });
|
||||||
let mut item = crate::TextMenuItem(item.clone());
|
|
||||||
handler(&mut item);
|
|
||||||
});
|
});
|
||||||
entry.item_gtk_items.as_mut().unwrap().lock().push(gtk_item);
|
entry.item_gtk_items.as_mut().unwrap().lock().push(gtk_item);
|
||||||
}
|
}
|
||||||
|
@ -148,7 +136,6 @@ impl Submenu {
|
||||||
enabled,
|
enabled,
|
||||||
entries: Some(Vec::new()),
|
entries: Some(Vec::new()),
|
||||||
etype: MenuEntryType::Submenu,
|
etype: MenuEntryType::Submenu,
|
||||||
item_handler: None,
|
|
||||||
item_id: None,
|
item_id: None,
|
||||||
menu_gtk_items: Some(gtk_items.clone()),
|
menu_gtk_items: Some(gtk_items.clone()),
|
||||||
item_gtk_items: None,
|
item_gtk_items: None,
|
||||||
|
@ -167,12 +154,7 @@ impl Submenu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_text_item<F: FnMut(&mut crate::TextMenuItem) + 'static>(
|
pub fn add_text_item(&mut self, label: impl AsRef<str>, enabled: bool) -> TextMenuItem {
|
||||||
&mut self,
|
|
||||||
label: impl AsRef<str>,
|
|
||||||
enabled: bool,
|
|
||||||
f: F,
|
|
||||||
) -> TextMenuItem {
|
|
||||||
let id = COUNTER.next();
|
let id = COUNTER.next();
|
||||||
let label = label.as_ref().to_string();
|
let label = label.as_ref().to_string();
|
||||||
let gtk_items = Arc::new(Mutex::new(Vec::new()));
|
let gtk_items = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
@ -181,7 +163,6 @@ impl Submenu {
|
||||||
enabled,
|
enabled,
|
||||||
entries: None,
|
entries: None,
|
||||||
etype: MenuEntryType::Text,
|
etype: MenuEntryType::Text,
|
||||||
item_handler: Some(Rc::new(RefCell::new(f))),
|
|
||||||
item_id: Some(id),
|
item_id: Some(id),
|
||||||
menu_gtk_items: None,
|
menu_gtk_items: None,
|
||||||
item_gtk_items: Some(gtk_items.clone()),
|
item_gtk_items: Some(gtk_items.clone()),
|
||||||
|
|
Loading…
Reference in a new issue