ferroui_controls/
native_menu.rs1use crate::i_native_menu_exporter_events_impl_bridge::INativeMenuExporterEventsImplBridge;
2use crate::utils::debug_display::debug_type_name;
3use crate::{NativeMenuItem, NativeMenuItemBase};
4use ferroui_base::collections::{FerroList, NotifyCollectionChangedEventArgs, ResetBehavior};
5use ferroui_base::reactive::{Disposable, IDisposable};
6use ferroui_base::utilities::HandlerList;
7use ferroui_base::{
8 ferro_class, ferro_class_info, ferro_properties, instantiate, DirectProperty, FerroObject,
9 FerroObjectImpl, FerroObjectImplExt, FerroProperty, IntoRef, Ref, WeakRef,
10};
11use std::cell::RefCell;
12use std::rc::Rc;
13
14#[repr(C)]
17pub struct NativeMenu {
18 base: FerroObject,
19 items: FerroList<Ref<NativeMenuItemBase>>,
20 parent: RefCell<Option<WeakRef<NativeMenuItem>>>,
23 needs_update: HandlerList<dyn Fn(&NativeMenu)>,
24 opening: HandlerList<dyn Fn(&NativeMenu)>,
25 closed: HandlerList<dyn Fn(&NativeMenu)>,
26}
27
28ferro_class!(NativeMenu: FerroObject);
29ferro_class_info!(NativeMenu { new: NativeMenu::new });
30
31impl FerroObjectImpl for NativeMenu {
32 fn constructed(this: &Self) {
33 Self::parent_constructed(this);
34
35 let weak = this.to_ref().downgrade();
36 this.items.set_validate(Some(Rc::new({
37 let weak = weak.clone();
38 move |item: &Ref<NativeMenuItemBase>| Self::validate(&weak, item)
39 })));
40 this.items.add_collection_changed(Rc::new(
41 move |e: &NotifyCollectionChangedEventArgs<'_, Ref<NativeMenuItemBase>>| Self::items_changed(&weak, e),
42 ));
43 }
44}
45
46struct NativeMenuHandle(Ref<NativeMenu>);
48
49impl INativeMenuExporterEventsImplBridge for NativeMenuHandle {
50 fn raise_needs_update(&self) {
51 for (_, handler) in self.0.needs_update.snapshot().iter() {
52 handler(&self.0);
53 }
54 }
55
56 fn raise_opening(&self) {
57 for (_, handler) in self.0.opening.snapshot().iter() {
58 handler(&self.0);
59 }
60 }
61
62 fn raise_closed(&self) {
63 for (_, handler) in self.0.closed.snapshot().iter() {
64 handler(&self.0);
65 }
66 }
67}
68
69ferro_properties! {
70 impl NativeMenu, also [NativeMenu::register_export_properties] {
71 pub fn parent_property() -> DirectProperty<NativeMenu, Option<Ref<NativeMenuItem>>> {
73 FerroProperty::register_direct::<NativeMenu, _>("Parent", |o| o.parent(), None, None)
74 }
75 }
76}
77
78impl NativeMenu {
79 fn static_constructor() {
80 Self::static_constructor_export();
81 }
82
83 pub fn construct() -> Self {
85 let items = FerroList::new();
86 items.set_reset_behavior(ResetBehavior::Remove);
87 Self {
88 base: FerroObject::construct(),
89 items,
90 parent: RefCell::new(None),
91 needs_update: HandlerList::new(),
92 opening: HandlerList::new(),
93 closed: HandlerList::new(),
94 }
95 }
96
97 pub fn new() -> Ref<Self> {
98 instantiate(Self::construct())
99 }
100
101 pub fn items(&self) -> FerroList<Ref<NativeMenuItemBase>> {
103 self.items.clone()
104 }
105
106 pub fn needs_update(&self, handler: impl Fn(&NativeMenu) + 'static) -> Rc<dyn IDisposable> {
111 self.subscribe(|menu| &menu.needs_update, handler)
112 }
113
114 pub fn opening(&self, handler: impl Fn(&NativeMenu) + 'static) -> Rc<dyn IDisposable> {
119 self.subscribe(|menu| &menu.opening, handler)
120 }
121
122 pub fn closed(&self, handler: impl Fn(&NativeMenu) + 'static) -> Rc<dyn IDisposable> {
127 self.subscribe(|menu| &menu.closed, handler)
128 }
129
130 fn subscribe(
131 &self,
132 event: fn(&NativeMenu) -> &HandlerList<dyn Fn(&NativeMenu)>,
133 handler: impl Fn(&NativeMenu) + 'static,
134 ) -> Rc<dyn IDisposable> {
135 let token = event(self).add(Rc::new(handler));
136 let weak = self.to_ref().downgrade();
137 Disposable::create(move || {
138 if let Some(this) = weak.upgrade() {
139 event(&this).remove(token);
140 }
141 })
142 }
143
144 fn validate(this: &WeakRef<NativeMenu>, item: &Ref<NativeMenuItemBase>) {
145 if let Some(parent) = item.parent() {
146 panic!(
147 "The menu item {} already has a parent {} while trying to add it as a child of {}.",
148 debug_display(item),
149 debug_display(&parent),
150 this.upgrade().map_or_else(|| NativeMenu::TYPE.name().to_string(), |this| debug_display(&this)),
151 );
152 }
153 }
154
155 fn items_changed(this: &WeakRef<NativeMenu>, e: &NotifyCollectionChangedEventArgs<'_, Ref<NativeMenuItemBase>>) {
156 for i in e.old_items {
157 i.set_parent(None);
158 }
159 for i in e.new_items {
160 i.set_parent(this.upgrade());
161 }
162 }
163
164 pub fn parent(&self) -> Option<Ref<NativeMenuItem>> {
166 let parent = self.parent.borrow().clone();
167 parent.and_then(|parent| parent.upgrade())
168 }
169
170 pub(crate) fn set_parent(&self, value: Option<Ref<NativeMenuItem>>) {
171 let old = self.parent();
172 if old == value {
173 return;
174 }
175 *self.parent.borrow_mut() = value.as_ref().map(|value| value.downgrade());
176 self.raise_direct_property_changed(Self::parent_property(), &old, &value);
177 }
178
179 pub fn to_exporter_events_bridge(&self) -> Rc<dyn INativeMenuExporterEventsImplBridge> {
182 Rc::new(NativeMenuHandle(self.to_ref()))
183 }
184
185 pub fn add(&self, item: impl IntoRef<NativeMenuItemBase>) {
187 self.items.add(item.into_ref());
188 }
189
190 pub fn iter(&self) -> std::vec::IntoIter<Ref<NativeMenuItemBase>> {
192 self.items.to_vec().into_iter()
193 }
194}
195
196impl IntoIterator for &NativeMenu {
197 type Item = Ref<NativeMenuItemBase>;
198 type IntoIter = std::vec::IntoIter<Ref<NativeMenuItemBase>>;
199
200 fn into_iter(self) -> Self::IntoIter {
201 self.iter()
202 }
203}
204
205pub(crate) fn debug_display(object: &FerroObject) -> String {
208 let mut builder = String::new();
209 match object.downcast_ref::<NativeMenuItem>() {
210 Some(item) => item.build_debug_display(&mut builder, true),
211 None => builder.push_str(&debug_type_name(object)),
212 }
213 builder
214}