Skip to content

Commit

Permalink
docs: document usage of event loop proxy and use it in examples
Browse files Browse the repository at this point in the history
closes #209
  • Loading branch information
amrbashir committed Nov 28, 2024
1 parent 983795c commit 159832e
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 77 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,36 @@ if let Ok(event) = MenuEvent::receiver().try_recv() {
}
```

### Note for [winit] or [tao] users:

You should use [`TrayIconEvent::set_event_handler`] and forward
the tray icon events to the event loop by using [`EventLoopProxy`]
so that the event loop is awakened on each tray icon event.
Same can be done for menu events using [`MenuEvent::set_event_handler`].

```rust
enum UserEvent {
TrayIconEvent(tray_icon::TrayIconEvent)
MenuEvent(tray_icon::menu::MenuEvent)
}

let event_loop = EventLoop::<UserEvent>::with_user_event().build().unwrap();

let proxy = event_loop.create_proxy();
tray_icon::TrayIconEvent::set_event_handler(Some(move |event| {
proxy.send_event(UserEvent::TrayIconEvent(event));
}));

let proxy = event_loop.create_proxy();
tray_icon::menu::MenuEvent::set_event_handler(Some(move |event| {
proxy.send_event(UserEvent::MenuEvent(event));
}));
```

[`EventLoopProxy`]: https://docs.rs/winit/latest/winit/event_loop/struct.EventLoopProxy.html
[winit]: https://docs.rs/winit
[tao]: https://docs.rs/tao

## License

Apache-2.0/MIT
101 changes: 60 additions & 41 deletions examples/tao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,36 @@

#![allow(unused)]

use tao::event_loop::{ControlFlow, EventLoopBuilder};
use tao::{
event::Event,
event_loop::{ControlFlow, EventLoopBuilder},
};
use tray_icon::{
menu::{AboutMetadata, Menu, MenuEvent, MenuItem, PredefinedMenuItem},
TrayIconBuilder, TrayIconEvent,
};

enum UserEvent {
TrayIconEvent(tray_icon::TrayIconEvent),
MenuEvent(tray_icon::menu::MenuEvent),
}

fn main() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");

let event_loop = EventLoopBuilder::new().build();
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();

// set a tray event handler that forwards the event and wakes up the event loop
let proxy = event_loop.create_proxy();
TrayIconEvent::set_event_handler(Some(move |event| {
proxy.send_event(UserEvent::TrayIconEvent(event));
}));

// set a menu event handler that forwards the event and wakes up the event loop
let proxy = event_loop.create_proxy();
MenuEvent::set_event_handler(Some(move |event| {
proxy.send_event(UserEvent::MenuEvent(event));
}));

let tray_menu = Menu::new();

Expand All @@ -37,49 +57,48 @@ fn main() {
let tray_channel = TrayIconEvent::receiver();

event_loop.run(move |event, _, control_flow| {
// We add delay of 16 ms (60fps) to event_loop to reduce cpu load.
// This can be removed to allow ControlFlow::Poll to poll on each cpu cycle
// Alternatively, you can set ControlFlow::Wait or use TrayIconEvent::set_event_handler,
// see https://github.com/tauri-apps/tray-icon/issues/83#issuecomment-1697773065
*control_flow = ControlFlow::WaitUntil(
std::time::Instant::now() + std::time::Duration::from_millis(16),
);

if let tao::event::Event::NewEvents(tao::event::StartCause::Init) = event {
let icon = load_icon(std::path::Path::new(path));

// We create the icon once the event loop is actually running
// to prevent issues like https://github.com/tauri-apps/tray-icon/issues/90
tray_icon = Some(
TrayIconBuilder::new()
.with_menu(Box::new(tray_menu.clone()))
.with_tooltip("tao - awesome windowing lib")
.with_icon(icon)
.build()
.unwrap(),
);

// We have to request a redraw here to have the icon actually show up.
// Tao only exposes a redraw method on the Window so we use core-foundation directly.
#[cfg(target_os = "macos")]
unsafe {
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};

let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
*control_flow = ControlFlow::Wait;

match event {
Event::NewEvents(tao::event::StartCause::Init) => {
let icon = load_icon(std::path::Path::new(path));

// We create the icon once the event loop is actually running
// to prevent issues like https://github.com/tauri-apps/tray-icon/issues/90
tray_icon = Some(
TrayIconBuilder::new()
.with_menu(Box::new(tray_menu.clone()))
.with_tooltip("tao - awesome windowing lib")
.with_icon(icon)
.build()
.unwrap(),
);

// We have to request a redraw here to have the icon actually show up.
// Tao only exposes a redraw method on the Window so we use core-foundation directly.
#[cfg(target_os = "macos")]
unsafe {
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};

let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}
}

if let Ok(event) = menu_channel.try_recv() {
if event.id == quit_i.id() {
tray_icon.take();
*control_flow = ControlFlow::Exit;
Event::UserEvent(UserEvent::TrayIconEvent(event)) => {
println!("{event:?}");
}

Event::UserEvent(UserEvent::MenuEvent(event)) => {
println!("{event:?}");

if event.id == quit_i.id() {
tray_icon.take();
*control_flow = ControlFlow::Exit;
}
}
println!("{event:?}");
}

if let Ok(event) = tray_channel.try_recv() {
println!("{event:?}");
_ => {}
}
})
}
Expand Down
85 changes: 49 additions & 36 deletions examples/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ use tray_icon::{
menu::{AboutMetadata, Menu, MenuEvent, MenuItem, PredefinedMenuItem},
TrayIconBuilder, TrayIconEvent,
};
use winit::event_loop::{ControlFlow, EventLoopBuilder};
use winit::{
event::Event,
event_loop::{ControlFlow, EventLoopBuilder},
};

enum UserEvent {
TrayIconEvent(tray_icon::TrayIconEvent),
}

fn main() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
Expand All @@ -32,7 +39,15 @@ fn main() {
gtk::main();
});

let event_loop = EventLoopBuilder::new().build().unwrap();
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event()
.build()
.unwrap();

// set a tray event handler that forwards the event and wakes up the event loop
let proxy = event_loop.create_proxy();
TrayIconEvent::set_event_handler(Some(move |event| {
proxy.send_event(UserEvent::TrayIconEvent(event));
}));

#[cfg(not(target_os = "linux"))]
let mut tray_icon = None;
Expand All @@ -41,42 +56,40 @@ fn main() {
let tray_channel = TrayIconEvent::receiver();

event_loop.run(move |event, event_loop| {
// We add delay of 16 ms (60fps) to event_loop to reduce cpu load.
// This can be removed to allow ControlFlow::Poll to poll on each cpu cycle
// Alternatively, you can set ControlFlow::Wait or use TrayIconEvent::set_event_handler,
// see https://github.com/tauri-apps/tray-icon/issues/83#issuecomment-1697773065
event_loop.set_control_flow(ControlFlow::WaitUntil(
std::time::Instant::now() + std::time::Duration::from_millis(16),
));

#[cfg(not(target_os = "linux"))]
if let winit::event::Event::NewEvents(winit::event::StartCause::Init) = event {
let icon = load_icon(std::path::Path::new(path));

// We create the icon once the event loop is actually running
// to prevent issues like https://github.com/tauri-apps/tray-icon/issues/90
tray_icon = Some(
TrayIconBuilder::new()
.with_menu(Box::new(Menu::new()))
.with_tooltip("winit - awesome windowing lib")
.with_icon(icon)
.with_title("x")
.build()
.unwrap(),
);
// We have to request a redraw here to have the icon actually show up.
// Winit only exposes a redraw method on the Window so we use core-foundation directly.
#[cfg(target_os = "macos")]
unsafe {
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};

let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
event_loop.set_control_flow(ControlFlow::Wait);

match event {
#[cfg(not(target_os = "linux"))]
Event::NewEvents(winit::event::StartCause::Init) => {
let icon = load_icon(std::path::Path::new(path));

// We create the icon once the event loop is actually running
// to prevent issues like https://github.com/tauri-apps/tray-icon/issues/90
tray_icon = Some(
TrayIconBuilder::new()
.with_menu(Box::new(Menu::new()))
.with_tooltip("winit - awesome windowing lib")
.with_icon(icon)
.with_title("x")
.build()
.unwrap(),
);
// We have to request a redraw here to have the icon actually show up.
// Winit only exposes a redraw method on the Window so we use core-foundation directly.
#[cfg(target_os = "macos")]
unsafe {
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};

let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}

Event::UserEvent(UserEvent::TrayIconEvent(event)) => {
println!("{event:?}");
}
}

if let Ok(event) = tray_channel.try_recv() {
println!("{event:?}");
_ => {}
}
});
}
Expand Down
31 changes: 31 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,37 @@
//! println!("menu event: {:?}", event);
//! }
//! ```
//!
//! ### Note for [winit] or [tao] users:
//!
//! You should use [`TrayIconEvent::set_event_handler`] and forward
//! the tray icon events to the event loop by using [`EventLoopProxy`]
//! so that the event loop is awakened on each tray icon event.
//! Same can be done for menu events using [`MenuEvent::set_event_handler`].
//!
//! ```no_run
//! # use winit::event_loop::EventLoopBuilder;
//! enum UserEvent {
//! TrayIconEvent(tray_icon::TrayIconEvent),
//! MenuEvent(tray_icon::menu::MenuEvent)
//! }
//!
//! let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build().unwrap();
//!
//! let proxy = event_loop.create_proxy();
//! tray_icon::TrayIconEvent::set_event_handler(Some(move |event| {
//! proxy.send_event(UserEvent::TrayIconEvent(event));
//! }));
//!
//! let proxy = event_loop.create_proxy();
//! tray_icon::menu::MenuEvent::set_event_handler(Some(move |event| {
//! proxy.send_event(UserEvent::MenuEvent(event));
//! }));
//! ```
//!
//! [`EventLoopProxy`]: https://docs.rs/winit/latest/winit/event_loop/struct.EventLoopProxy.html
//! [winit]: https://docs.rs/winit
//! [tao]: https://docs.rs/tao
use std::{
cell::RefCell,
Expand Down

0 comments on commit 159832e

Please sign in to comment.