Skip to main content

ferroui_native/
tray_icon_impl.rs

1use crate::ferro_native_menu_exporter::FerroNativeMenuExporter;
2use crate::frn_string::to_c_string;
3use crate::helpers::ComResultExt;
4use crate::interop::*;
5use ferroui_base::reactive::IDisposable;
6use ferroui_controls::platform::{INativeMenuExporter, ITrayIconImpl, ITrayIconWithIsTemplateImpl, IWindowIconImpl};
7use ferroui_microcom::ComPtr;
8use std::cell::RefCell;
9use std::ffi::c_void;
10use std::rc::Rc;
11
12/// A status bar (tray) icon.
13pub struct TrayIconImpl {
14    native: RefCell<Option<ComPtr<IFrnTrayIcon>>>,
15    menu_exporter: Rc<FerroNativeMenuExporter>,
16    on_clicked: RefCell<Option<Rc<dyn Fn()>>>,
17}
18
19impl TrayIconImpl {
20    pub(crate) fn new(factory: &ComPtr<IFerroNativeFactory>) -> TrayIconImpl {
21        let native = factory.create_tray_icon().check().expect("the native tray icon");
22
23        TrayIconImpl {
24            menu_exporter: FerroNativeMenuExporter::for_tray_icon(native.clone(), factory),
25            native: RefCell::new(Some(native)),
26            on_clicked: RefCell::new(None),
27        }
28    }
29
30    #[track_caller]
31    fn native(&self) -> ComPtr<IFrnTrayIcon> {
32        match self.native.borrow().clone() {
33            Some(native) => native,
34            None => panic!("Cannot access a disposed object: the native tray icon"),
35        }
36    }
37}
38
39impl IDisposable for TrayIconImpl {
40    fn dispose(&self) {
41        let native = self.native.borrow_mut().take();
42        drop(native);
43    }
44}
45
46impl ITrayIconImpl for TrayIconImpl {
47    fn set_icon(&self, icon: Option<Rc<dyn IWindowIconImpl>>) {
48        match icon {
49            // SAFETY: a null pointer with a zero length clears the icon.
50            None => unsafe { self.native().set_icon(std::ptr::null_mut(), 0) }.check(),
51            Some(icon) => {
52                let mut image_data = Vec::new();
53                if let Err(error) = icon.save(&mut image_data) {
54                    panic!("Unable to save the tray icon: {error}");
55                }
56
57                // SAFETY: the native side copies the image data during the call.
58                unsafe { self.native().set_icon(image_data.as_mut_ptr() as *mut c_void, image_data.len()) }.check();
59            }
60        }
61    }
62
63    fn set_tool_tip_text(&self, text: Option<&str>) {
64        let text = text.map(to_c_string);
65        self.native().set_tool_tip_text(text.as_deref()).check();
66    }
67
68    fn set_is_visible(&self, visible: bool) {
69        self.native().set_is_visible(visible).check();
70    }
71
72    fn menu_exporter(&self) -> Option<Rc<dyn INativeMenuExporter>> {
73        Some(self.menu_exporter.clone())
74    }
75
76    fn on_clicked(&self) -> Option<Rc<dyn Fn()>> {
77        self.on_clicked.borrow().clone()
78    }
79
80    fn set_on_clicked(&self, value: Option<Rc<dyn Fn()>>) {
81        let old = self.on_clicked.replace(value);
82        drop(old);
83    }
84
85    fn as_tray_icon_with_is_template_impl(&self) -> Option<&dyn ITrayIconWithIsTemplateImpl> {
86        Some(self)
87    }
88}
89
90impl ITrayIconWithIsTemplateImpl for TrayIconImpl {
91    fn set_is_template_icon(&self, is_template_icon: bool) {
92        self.native().set_is_template_icon(is_template_icon).check();
93    }
94}