-
Notifications
You must be signed in to change notification settings - Fork 860
Add an interface for creating system trays #11234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 30 commits
e609d95
4c31394
7c35e6c
4a593aa
3c31a7f
1bdbc3f
4f9328d
78f4e4b
b580236
18d816a
1e7f58b
11b0720
e680743
98a5b82
b88cc1a
a9e2d33
4c9441b
f25cacc
5874d3c
72bbde8
8486653
8017a2e
b8fb662
5bea4eb
c61a027
2f6fd28
a066df6
36a7258
f98c448
3b6102b
d038250
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -320,6 +320,7 @@ fn gen_corelib( | |
| "Layer", | ||
| "ContextMenu", | ||
| "MenuItem", | ||
| "SystemTray", | ||
| ]; | ||
|
|
||
| config.export.include = [ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # Copyright © SixtyFPS GmbH <info@slint.dev> | ||
| # SPDX-License-Identifier: MIT | ||
|
|
||
| [package] | ||
| name = "system_tray" | ||
| version = "1.16.0" | ||
| authors = ["Slint Developers <info@slint.dev>"] | ||
| edition.workspace = true | ||
| publish = false | ||
| license = "MIT" | ||
|
|
||
| [[bin]] | ||
| path = "main.rs" | ||
| name = "system_tray" | ||
|
|
||
| [dependencies] | ||
| slint = { path = "../../api/rs/slint", default-features = false, features = ["renderer-femtovg", "backend-winit-x11", "compat-1-2"] } | ||
| i-slint-core = { workspace = true, features = ["system-tray"] } | ||
|
|
||
| [build-dependencies] | ||
| slint-build = { path = "../../api/rs/build" } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| // Copyright © SixtyFPS GmbH <info@slint.dev> | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| fn main() { | ||
| println!("cargo:rustc-env=SLINT_ENABLE_EXPERIMENTAL_FEATURES=1"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| // Copyright © SixtyFPS GmbH <info@slint.dev> | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| slint::slint! { | ||
| export component ExampleTray inherits SystemTray { | ||
| icon: @image-url("favicon-white.png"); | ||
| } | ||
| } | ||
|
|
||
| fn main() { | ||
| let _tray = ExampleTray::new().unwrap(); | ||
| slint::run_event_loop().unwrap(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,7 @@ pub use crate::menus::MenuItem; | |
| use crate::rtti::*; | ||
| use crate::window::{WindowAdapter, WindowAdapterRc, WindowInner}; | ||
| use crate::{Callback, Coord, Property, SharedString}; | ||
| use alloc::boxed::Box; | ||
| use alloc::rc::Rc; | ||
| use const_field_offset::FieldOffsets; | ||
| use core::cell::Cell; | ||
|
|
@@ -1774,6 +1775,168 @@ declare_item_vtable! { | |
| fn slint_get_BoxShadowVTable() -> BoxShadowVTable for BoxShadow | ||
| } | ||
|
|
||
| #[repr(C)] | ||
| /// Wraps the internal data structure for the SystemTray | ||
| pub struct SystemTrayDataBox(core::ptr::NonNull<SystemTrayData>); | ||
|
|
||
| impl Default for SystemTrayDataBox { | ||
| fn default() -> Self { | ||
| SystemTrayDataBox(Box::leak(Box::<SystemTrayData>::default()).into()) | ||
| } | ||
| } | ||
| impl Drop for SystemTrayDataBox { | ||
| fn drop(&mut self) { | ||
| // Safety: the self.0 was constructed from a Box::leak in SystemTrayDataBox::default | ||
| drop(unsafe { Box::from_raw(self.0.as_ptr()) }); | ||
| } | ||
| } | ||
|
|
||
| impl core::ops::Deref for SystemTrayDataBox { | ||
| type Target = SystemTrayData; | ||
| fn deref(&self) -> &Self::Target { | ||
| // Safety: initialized in SystemTrayDataBox::default | ||
| unsafe { self.0.as_ref() } | ||
| } | ||
| } | ||
|
|
||
| #[derive(Default)] | ||
| pub struct SystemTrayData { | ||
| #[cfg(feature = "system-tray")] | ||
| inner: std::cell::OnceCell<crate::system_tray::SystemTray>, | ||
| #[cfg_attr(not(feature = "system-tray"), allow(unused))] | ||
| change_tracker: crate::properties::ChangeTracker, | ||
| } | ||
|
|
||
| #[repr(C)] | ||
| #[derive(FieldOffsets, Default, SlintElement)] | ||
| #[pin] | ||
| pub struct SystemTray { | ||
| pub icon: Property<crate::graphics::Image>, | ||
| pub title: Property<SharedString>, | ||
| pub cached_rendering_data: CachedRenderingData, | ||
| data: SystemTrayDataBox, | ||
| } | ||
|
|
||
| impl Item for SystemTray { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we really need the SystemTray to be an
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would the alternative be? |
||
| #[cfg_attr(not(feature = "system-tray"), allow(unused))] | ||
| fn init(self: Pin<&Self>, self_rc: &ItemRc) { | ||
| #[cfg(feature = "system-tray")] | ||
| self.data.change_tracker.init_delayed( | ||
| self_rc.downgrade(), | ||
| |_| true, | ||
| |self_weak, has_icon| { | ||
| let Some(tray_rc) = self_weak.upgrade() else { | ||
| return; | ||
| }; | ||
| let Some(tray) = tray_rc.downcast::<SystemTray>() else { | ||
| return; | ||
| }; | ||
| if !*has_icon { | ||
| return; | ||
| } | ||
| let tray = tray.as_pin_ref(); | ||
| let system_tray = | ||
| match crate::system_tray::SystemTray::new(crate::system_tray::Params { | ||
| icon: &tray.icon(), | ||
| title: &tray.title(), | ||
| }) { | ||
| Ok(system_tray) => system_tray, | ||
| Err(err) => panic!("{}", err), | ||
| }; | ||
|
|
||
| let _ = tray.data.inner.set(system_tray); | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| fn layout_info( | ||
| self: Pin<&Self>, | ||
| _orientation: Orientation, | ||
| _window_adapter: &Rc<dyn WindowAdapter>, | ||
| _self_rc: &ItemRc, | ||
| ) -> LayoutInfo { | ||
| LayoutInfo::default() | ||
| } | ||
|
|
||
| fn input_event_filter_before_children( | ||
| self: Pin<&Self>, | ||
| _: &MouseEvent, | ||
| _window_adapter: &Rc<dyn WindowAdapter>, | ||
| _self_rc: &ItemRc, | ||
| _: &mut MouseCursor, | ||
| ) -> InputEventFilterResult { | ||
| InputEventFilterResult::ForwardAndIgnore | ||
| } | ||
|
|
||
| fn input_event( | ||
| self: Pin<&Self>, | ||
| _: &MouseEvent, | ||
| _window_adapter: &Rc<dyn WindowAdapter>, | ||
| _self_rc: &ItemRc, | ||
| _: &mut MouseCursor, | ||
| ) -> InputEventResult { | ||
| InputEventResult::EventIgnored | ||
| } | ||
|
|
||
| fn capture_key_event( | ||
| self: Pin<&Self>, | ||
| _: &InternalKeyEvent, | ||
| _window_adapter: &Rc<dyn WindowAdapter>, | ||
| _self_rc: &ItemRc, | ||
| ) -> KeyEventResult { | ||
| KeyEventResult::EventIgnored | ||
| } | ||
|
|
||
| fn key_event( | ||
| self: Pin<&Self>, | ||
| _: &InternalKeyEvent, | ||
| _window_adapter: &Rc<dyn WindowAdapter>, | ||
| _self_rc: &ItemRc, | ||
| ) -> KeyEventResult { | ||
| KeyEventResult::EventIgnored | ||
| } | ||
|
|
||
| fn focus_event( | ||
| self: Pin<&Self>, | ||
| _: &FocusEvent, | ||
| _window_adapter: &Rc<dyn WindowAdapter>, | ||
| _self_rc: &ItemRc, | ||
| ) -> FocusEventResult { | ||
| FocusEventResult::FocusIgnored | ||
| } | ||
|
|
||
| fn render( | ||
| self: Pin<&Self>, | ||
| _backend: &mut ItemRendererRef, | ||
| _self_rc: &ItemRc, | ||
| _size: LogicalSize, | ||
| ) -> RenderingResult { | ||
| RenderingResult::ContinueRenderingChildren | ||
| } | ||
|
|
||
| fn bounding_rect( | ||
| self: core::pin::Pin<&Self>, | ||
| _window_adapter: &Rc<dyn WindowAdapter>, | ||
| _self_rc: &ItemRc, | ||
| geometry: LogicalRect, | ||
| ) -> LogicalRect { | ||
| geometry | ||
| } | ||
|
|
||
| fn clips_children(self: core::pin::Pin<&Self>) -> bool { | ||
| false | ||
| } | ||
| } | ||
|
|
||
| impl ItemConsts for SystemTray { | ||
| const cached_rendering_data_offset: const_field_offset::FieldOffset<Self, CachedRenderingData> = | ||
| Self::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection(); | ||
| } | ||
|
|
||
| declare_item_vtable! { | ||
| fn slint_get_SystemTrayVTable() -> SystemTrayVTable for SystemTray | ||
| } | ||
|
|
||
| declare_item_vtable! { | ||
| fn slint_get_ComponentContainerVTable() -> ComponentContainerVTable for ComponentContainer | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make it as experimental so it is only available when using
SLINT_ENABLE_EXPERIMENTAL_FEATURES=1?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Where does that leave the example? Should I remove it now?