Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
616 changes: 540 additions & 76 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ strict-assertions = ["iced_renderer/strict-assertions"]
unconditional-rendering = ["iced_winit/unconditional-rendering"]
# Enables support for the `sipper` library
sipper = ["iced_runtime/sipper"]
# Enables tray icon settings
tray-icon = ["iced_core/tray-icon", "iced_runtime/tray-icon", "iced_winit/tray-icon"]

[dependencies]
iced_debug.workspace = true
Expand Down Expand Up @@ -202,6 +204,7 @@ softbuffer = "0.4"
syntect = "5.1"
sysinfo = "0.33"
thiserror = "1.0"
tray-icon = "0.21"
tiny-skia = "0.11"
tokio = "1.0"
tracing = "0.1"
Expand Down
4 changes: 4 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ workspace = true
auto-detect-theme = ["dep:dark-light"]
advanced = []
crisp = []
tray-icon = ["dep:tray-icon"]

[dependencies]
bitflags.workspace = true
Expand All @@ -36,3 +37,6 @@ dark-light.optional = true
serde.workspace = true
serde.optional = true
serde.features = ["derive"]

tray-icon.workspace = true
tray-icon.optional = true
7 changes: 7 additions & 0 deletions core/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use crate::mouse;
use crate::touch;
use crate::window;

#[cfg(feature = "tray-icon")]
use crate::tray_icon;

/// A user interface event.
///
/// _**Note:** This type is largely incomplete! If you need to track
Expand All @@ -27,6 +30,10 @@ pub enum Event {

/// An input method event
InputMethod(input_method::Event),

#[cfg(feature = "tray-icon")]
/// A tray icon event
TrayIcon(tray_icon::Event),
}

/// The status of an [`Event`] after being processed.
Expand Down
3 changes: 3 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pub mod touch;
pub mod widget;
pub mod window;

#[cfg(feature = "tray-icon")]
pub mod tray_icon;

mod angle;
mod background;
mod color;
Expand Down
265 changes: 265 additions & 0 deletions core/src/tray_icon.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
//! Tray icon

mod errors;
mod event;
mod settings;

use std::collections::HashMap;
use std::fmt::{Debug, Formatter};

pub use errors::Error;
pub use event::Event;
pub use settings::*;

/// Wrapper type for tray_icon
#[derive(Clone)]
pub struct TrayIcon {
icon: tray_icon::TrayIcon,
/// Mapping of MenuItem id and tray_icon MenuId
id_map: HashMap<String, String>,
}

impl TrayIcon {
/// Create new TrayIcon from Settings
pub fn new(settings: Settings) -> Result<Self, Error> {
let mut attrs = tray_icon::TrayIconAttributes::default();
if let Some(title) = settings.title {
attrs.title = Some(title.clone());
}
if let Some(icon) = settings.icon {
let icon = icon.try_into()?;
attrs.icon = Some(icon);
}
if let Some(tooltip) = settings.tooltip {
attrs.tooltip = Some(tooltip.clone());
}
let id_map = if let Some(menu_items) = settings.menu_items {
let mut id_map = HashMap::with_capacity(menu_items.len());
let menu = tray_icon::menu::Menu::new();
for menu_item in menu_items {
Self::build_menu_item(&mut id_map, &menu, menu_item)?;
}
attrs.menu = Some(Box::new(menu));
id_map
} else {
HashMap::new()
};
let icon = tray_icon::TrayIcon::new(attrs).map_err(Error::from)?;
let this = Self {
icon: icon,
id_map: id_map,
};

Ok(this)
}

fn build_menu_item(
id_map: &mut HashMap<String, String>,
menu: &impl tray_icon::menu::ContextMenu,
menu_item: MenuItem,
) -> Result<(), Error> {
let menu_id = menu_item.id();
let add_to_menu = |item: &dyn tray_icon::menu::IsMenuItem,
id_map: &mut HashMap<String, String>|
-> Result<(), Error> {
if let Some(menu) = menu.as_menu() {
let _ = id_map.insert(item.id().0.clone(), menu_id);
menu.append(item).map_err(Error::from)
} else if let Some(submenu) = menu.as_submenu() {
let _ = id_map.insert(item.id().0.clone(), menu_id);
submenu.append(item).map_err(Error::from)
} else {
Err(Error::MenuError(
tray_icon::menu::Error::NotAChildOfThisMenu,
))
}
};
match menu_item {
MenuItem::Submenu {
text, menu_items, ..
} => {
let submenu = tray_icon::menu::Submenu::new(text, true);
for sub_menu_item in menu_items {
Self::build_menu_item(id_map, &submenu, sub_menu_item)?;
}
add_to_menu(&submenu, id_map)
}
MenuItem::Predefined {
predefined_type,
alternate_text,
} => {
let p = match predefined_type {
PredefinedMenuItem::Separator => {
tray_icon::menu::PredefinedMenuItem::separator()
}
PredefinedMenuItem::Copy => {
tray_icon::menu::PredefinedMenuItem::copy(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::Cut => {
tray_icon::menu::PredefinedMenuItem::cut(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::Paste => {
tray_icon::menu::PredefinedMenuItem::paste(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::SelectAll => {
tray_icon::menu::PredefinedMenuItem::select_all(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::Undo => {
tray_icon::menu::PredefinedMenuItem::undo(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::Redo => {
tray_icon::menu::PredefinedMenuItem::redo(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::Minimize => {
tray_icon::menu::PredefinedMenuItem::minimize(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::Maximize => {
tray_icon::menu::PredefinedMenuItem::maximize(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::Fullscreen => {
tray_icon::menu::PredefinedMenuItem::fullscreen(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::Hide => {
tray_icon::menu::PredefinedMenuItem::hide(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::HideOthers => {
tray_icon::menu::PredefinedMenuItem::hide_others(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::ShowAll => {
tray_icon::menu::PredefinedMenuItem::show_all(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::CloseWindow => {
tray_icon::menu::PredefinedMenuItem::close_window(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::Quit => {
tray_icon::menu::PredefinedMenuItem::quit(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::About(about_metadata) => {
let a: Option<tray_icon::menu::AboutMetadata> =
if let Some(a) = about_metadata {
let about = a.try_into()?;
Some(about)
} else {
None
};
tray_icon::menu::PredefinedMenuItem::about(
alternate_text.as_deref(),
a,
)
}
PredefinedMenuItem::Services => {
tray_icon::menu::PredefinedMenuItem::services(
alternate_text.as_deref(),
)
}
PredefinedMenuItem::BringAllToFront => {
tray_icon::menu::PredefinedMenuItem::bring_all_to_front(
alternate_text.as_deref(),
)
}
};
add_to_menu(&p, id_map)
}
MenuItem::Text {
text,
enabled,
accelerator,
..
} => {
let a: Option<tray_icon::menu::accelerator::Accelerator> =
if let Some(a) = accelerator {
let a = a.try_into()?;
Some(a)
} else {
None
};
let t = tray_icon::menu::MenuItem::new(text, enabled, a);
add_to_menu(&t, id_map)
}
MenuItem::Check {
text,
enabled,
checked,
accelerator,
..
} => {
let a: Option<tray_icon::menu::accelerator::Accelerator> =
if let Some(a) = accelerator {
let a = a.try_into()?;
Some(a)
} else {
None
};
let c = tray_icon::menu::CheckMenuItem::new(
text, enabled, checked, a,
);
add_to_menu(&c, id_map)
}
MenuItem::Icon {
text,
enabled,
icon,
accelerator,
..
} => {
let i = icon.try_into()?;
let a: Option<tray_icon::menu::accelerator::Accelerator> =
if let Some(a) = accelerator {
let a = a.try_into()?;
Some(a)
} else {
None
};
let c = tray_icon::menu::IconMenuItem::new(
text,
enabled,
Some(i),
a,
);
add_to_menu(&c, id_map)
}
}
}

/// Fetch MenuItem Id for tray_icon MenuId
pub fn id_map(&self) -> HashMap<String, String> {
self.id_map.clone()
}
}

impl Debug for TrayIcon {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TrayIcon")
.field("icon", &self.icon.id())
.field("id_map", &self.id_map)
.finish()
}
}
21 changes: 21 additions & 0 deletions core/src/tray_icon/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! Tray Icon creation errors

/// An error that occurred during Tray Icon creation
#[derive(Debug, thiserror::Error)]
pub enum Error {
///Failed to create icon
#[error("icon could not be parsed")]
BadIcon(#[from] tray_icon::BadIcon),
///Failed to create the tray icon
#[error("tray icon could not be created")]
CreationError(#[from] tray_icon::Error),
///Failed to create the tray icon menu
#[error("tray icon menu could not be created")]
MenuError(#[from] tray_icon::menu::Error),
///Failed to create menu icon
#[error("menu icon could not be parsed")]
BadMenuIcon(#[from] tray_icon::menu::BadIcon),
///Failed to create menu icon
#[error("accelerator could not be parsed")]
BadAccelerator(#[from] tray_icon::menu::AcceleratorParseError),
}
Loading