Skip to main content

ferroui_controls/
native_menu_export.rs

1use crate::platform::{as_native_menu_exporter_provider, ITopLevelNativeMenuExporter};
2use crate::{NativeMenu, TopLevel};
3use ferroui_base::platform::IOptionalFeatureProvider;
4use ferroui_base::{ferro_properties, AttachedProperty, FerroObject, FerroProperty, Ref};
5use std::cell::Cell;
6use std::rc::Rc;
7
8/// What the native menu support keeps for a top-level.
9struct NativeMenuInfo {
10    changing_is_exported: Cell<bool>,
11    exporter: Option<Rc<dyn ITopLevelNativeMenuExporter>>,
12}
13
14impl NativeMenuInfo {
15    fn new(target: &TopLevel) -> Rc<Self> {
16        let exporter = target.platform_impl().and_then(|platform_impl| {
17            let provider: &dyn IOptionalFeatureProvider = &*platform_impl;
18            provider.try_get::<dyn ITopLevelNativeMenuExporter>()
19        });
20        if let Some(exporter) = &exporter {
21            // Both are held weakly: the exporter belongs to the platform
22            // implementation of the top-level.
23            let target = target.to_ref().downgrade();
24            let weak_exporter = Rc::downgrade(exporter);
25            let _ = exporter.on_is_native_menu_exported_changed(Rc::new(move || {
26                if let (Some(target), Some(exporter)) = (target.upgrade(), weak_exporter.upgrade()) {
27                    NativeMenu::set_is_native_menu_exported(&target, exporter.is_native_menu_exported());
28                }
29            }));
30        }
31        Rc::new(Self { changing_is_exported: Cell::new(false), exporter })
32    }
33}
34
35/// The value of the private attached property that holds a
36/// [`NativeMenuInfo`]; compares by reference.
37#[derive(Clone)]
38struct NativeMenuInfoRef(Rc<NativeMenuInfo>);
39
40impl PartialEq for NativeMenuInfoRef {
41    fn eq(&self, other: &Self) -> bool {
42        Rc::ptr_eq(&self.0, &other.0)
43    }
44}
45
46ferro_properties! {
47    impl NativeMenu, fn register_export_properties {
48        /// Defines the `IsNativeMenuExported` attached property.
49        pub fn is_native_menu_exported_property() -> AttachedProperty<bool> {
50            FerroProperty::register_attached::<NativeMenu, TopLevel, _>("IsNativeMenuExported", false)
51        }
52
53        fn native_menu_info_property() -> AttachedProperty<Option<NativeMenuInfoRef>> {
54            FerroProperty::register_attached::<NativeMenu, TopLevel, _>("___NativeMenuInfo", None)
55        }
56
57        /// Defines the `Menu` attached property.
58        pub fn menu_property() -> AttachedProperty<Option<Ref<NativeMenu>>> {
59            FerroProperty::register_attached::<NativeMenu, FerroObject, _>("Menu", None)
60        }
61    }
62}
63
64impl NativeMenu {
65    /// Whether the native menu of the top-level is exported to the
66    /// platform.
67    pub fn get_is_native_menu_exported(tl: &TopLevel) -> bool {
68        tl.get_value(Self::is_native_menu_exported_property())
69    }
70
71    fn get_info(target: &TopLevel) -> Rc<NativeMenuInfo> {
72        match target.get_value(Self::native_menu_info_property()) {
73            Some(rv) => rv.0,
74            None => {
75                let rv = NativeMenuInfo::new(target);
76                target.set_value(Self::native_menu_info_property(), Some(NativeMenuInfoRef(rv.clone())));
77                Self::set_is_native_menu_exported(
78                    target,
79                    rv.exporter.as_ref().is_some_and(|exporter| exporter.is_native_menu_exported()),
80                );
81                rv
82            }
83        }
84    }
85
86    fn set_is_native_menu_exported(tl: &TopLevel, value: bool) {
87        Self::get_info(tl).changing_is_exported.set(true);
88        tl.set_value(Self::is_native_menu_exported_property(), value);
89    }
90
91    /// Sets the native menu of an object: a top-level, the application or
92    /// an object that provides a native menu exporter.
93    pub fn set_menu(o: &FerroObject, menu: Option<Ref<NativeMenu>>) {
94        o.set_value(Self::menu_property(), menu)
95    }
96
97    /// Gets the native menu of an object.
98    pub fn get_menu(o: &FerroObject) -> Option<Ref<NativeMenu>> {
99        o.get_value(Self::menu_property())
100    }
101
102    /// The part of the static constructor that belongs to this file.
103    pub(crate) fn static_constructor_export() {
104        // This is needed because of the lack of attached direct properties
105        Self::is_native_menu_exported_property().changed().subscribe(|args| {
106            let sender = args.sender().downcast_ref::<TopLevel>().expect("the sender is a top-level");
107            let info = Self::get_info(sender);
108            if !info.changing_is_exported.get() {
109                panic!("IsNativeMenuExported property is read-only");
110            }
111            info.changing_is_exported.set(false);
112        });
113        Self::menu_property().changed().subscribe(|args| {
114            let new_value = args.get_new_value::<Option<Ref<NativeMenu>>>();
115            if let Some(tl) = args.sender().downcast_ref::<TopLevel>() {
116                if let Some(exporter) = &Self::get_info(tl).exporter {
117                    exporter.set_native_menu(new_value);
118                }
119            } else if let Some(provider) = as_native_menu_exporter_provider(args.sender()) {
120                if let Some(exporter) = provider.native_menu_exporter() {
121                    exporter.set_native_menu(new_value);
122                }
123            }
124        });
125    }
126}