1#![allow(unsafe_code)]
6
7use crate::graphics::Image;
8use crate::input::{InternalKeyEvent, Keys};
9use crate::item_rendering::CachedRenderingData;
10use crate::item_tree::{ItemTreeRc, ItemWeak, VisitChildrenResult};
11use crate::items::{ItemRc, ItemRef, MenuEntry, VoidArg};
12use crate::properties::PropertyTracker;
13#[cfg(feature = "rtti")]
14use crate::rtti::*;
15use crate::string::ToSharedString;
16use crate::window::WindowAdapter;
17use crate::{Callback, Property, SharedString, SharedVector};
18use alloc::boxed::Box;
19use alloc::collections::BTreeMap;
20use alloc::rc::Rc;
21use core::cell::{Cell, RefCell};
22use core::pin::Pin;
23use i_slint_core_macros::SlintElement;
24use vtable::{VRef, VRefMut};
25
26#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
28#[vtable::vtable]
29#[repr(C)]
30pub struct MenuVTable {
31 sub_menu: extern "C" fn(VRef<MenuVTable>, Option<&MenuEntry>, &mut SharedVector<MenuEntry>),
33 activate: extern "C" fn(VRef<MenuVTable>, &MenuEntry),
35 visible: extern "C" fn(VRef<MenuVTable>) -> bool,
37 drop_in_place: extern "C" fn(VRefMut<MenuVTable>) -> Layout,
39 dealloc: extern "C" fn(&MenuVTable, ptr: *mut u8, layout: Layout),
41}
42
43struct ShadowTreeNode {
44 item: ItemWeak,
45 children: SharedVector<MenuEntry>,
46}
47
48pub struct MenuFromItemTree {
49 item_tree: ItemTreeRc,
50 item_cache: RefCell<BTreeMap<SharedString, ShadowTreeNode>>,
51 root: RefCell<SharedVector<MenuEntry>>,
52 next_id: Cell<usize>,
53 tracker: Pin<Box<PropertyTracker>>,
54 condition: Option<Pin<Box<Property<bool>>>>,
55 visible: Option<Pin<Box<Property<bool>>>>,
56}
57
58impl MenuFromItemTree {
59 pub fn new(item_tree: ItemTreeRc) -> Self {
60 Self::new_internal(item_tree, None, None)
61 }
62
63 pub fn new_with_condition_and_visible(
64 item_tree: ItemTreeRc,
65 condition: impl Fn() -> bool + 'static,
66 visible: impl Fn() -> bool + 'static,
67 ) -> Self {
68 fn make_prop(
69 f: impl Fn() -> bool + 'static,
70 name: &'static str,
71 ) -> Option<Pin<Box<Property<bool>>>> {
72 let prop = Box::pin(Property::new_named(true, name));
73 prop.as_ref().set_binding(f);
74 Some(prop)
75 }
76 let condition = make_prop(condition, "MenuFromItemTree::condition");
77 let visible = make_prop(visible, "MenuFromItemTree::visible");
78 Self::new_internal(item_tree, condition, visible)
79 }
80
81 fn new_internal(
82 item_tree: ItemTreeRc,
83 condition: Option<Pin<Box<Property<bool>>>>,
84 visible: Option<Pin<Box<Property<bool>>>>,
85 ) -> Self {
86 Self {
87 item_tree,
88 item_cache: Default::default(),
89 root: Default::default(),
90 tracker: Box::pin(PropertyTracker::default()),
91 next_id: 0.into(),
92 condition,
93 visible,
94 }
95 }
96
97 fn update_shadow_tree(&self) {
98 self.tracker.as_ref().evaluate_if_dirty(|| {
99 self.item_cache.replace(Default::default());
100 if let Some(condition) = &self.condition
101 && !condition.as_ref().get()
102 {
103 self.root.replace(SharedVector::default());
104 return;
105 }
106 crate::item_tree::ensure_item_tree_instantiated(&self.item_tree);
107 self.root.replace(
108 self.update_shadow_tree_recursive(&ItemRc::new_root(self.item_tree.clone())),
109 );
110 });
111 }
112
113 fn update_shadow_tree_recursive(&self, parent: &ItemRc) -> SharedVector<MenuEntry> {
114 let mut result = SharedVector::default();
115 let mut last_is_separator = false;
116
117 let mut actual_visitor =
118 |item_tree: &ItemTreeRc, index: u32, item_pin: Pin<ItemRef>| -> VisitChildrenResult {
119 if let Some(menu_item) = ItemRef::downcast_pin::<MenuItem>(item_pin) {
120 let title = menu_item.title();
121 let is_separator =
122 title.as_str() == i_slint_common::MENU_SEPARATOR_PLACEHOLDER_TITLE;
123 if is_separator && (last_is_separator || result.is_empty()) {
124 return VisitChildrenResult::CONTINUE;
125 }
126 last_is_separator = is_separator;
127 let next_id = self.next_id.get();
128 self.next_id.set(next_id + 1);
129 let id = next_id.to_shared_string();
130 let item = ItemRc::new(item_tree.clone(), index);
131 let children = self.update_shadow_tree_recursive(&item);
132 let has_sub_menu = !children.is_empty();
133 let enabled = menu_item.enabled();
134 let checkable = menu_item.checkable();
135 let checked = menu_item.checked();
136 let icon = menu_item.icon();
137 let shortcut = menu_item.shortcut();
138 self.item_cache.borrow_mut().insert(
139 id.clone(),
140 ShadowTreeNode { item: ItemRc::downgrade(&item), children },
141 );
142 result.push(MenuEntry {
143 title,
144 id,
145 has_sub_menu,
146 is_separator,
147 enabled,
148 checkable,
149 checked,
150 icon,
151 shortcut,
152 });
153 }
154 VisitChildrenResult::CONTINUE
155 };
156 vtable::new_vref!(let mut actual_visitor : VRefMut<crate::item_tree::ItemVisitorVTable> for crate::item_tree::ItemVisitor = &mut actual_visitor);
157
158 vtable::VRc::borrow_pin(parent.item_tree()).as_ref().visit_children_item(
159 parent.index() as isize,
160 crate::item_tree::TraversalOrder::BackToFront,
161 actual_visitor,
162 );
163 if last_is_separator {
164 result.pop();
165 }
166 result
167 }
168}
169
170impl Menu for MenuFromItemTree {
171 fn sub_menu(&self, parent: Option<&MenuEntry>, result: &mut SharedVector<MenuEntry>) {
172 self.update_shadow_tree();
173 match parent {
174 Some(parent) => {
175 if let Some(r) = self.item_cache.borrow().get(parent.id.as_str()) {
176 *result = r.children.clone();
177 }
178 }
179 None => {
180 *result = self.root.borrow().clone();
181 }
182 }
183 }
184
185 fn activate(&self, entry: &MenuEntry) {
186 if let Some(menu_item) =
187 self.item_cache.borrow().get(entry.id.as_str()).and_then(|e| e.item.upgrade())
188 && let Some(menu_item) = menu_item.downcast::<MenuItem>()
189 {
190 if menu_item.as_pin_ref().checkable() {
191 menu_item.checked.set(!menu_item.as_pin_ref().checked());
192 }
193
194 menu_item.activated.call(&());
195 }
196 }
197
198 fn visible(&self) -> bool {
199 self.visible.as_ref().is_none_or(|v| v.as_ref().get())
200 }
201}
202
203MenuVTable_static!(static MENU_FROM_ITEM_TREE_VT for MenuFromItemTree);
204
205#[repr(C)]
206#[derive(const_field_offset::FieldOffsets, Default, SlintElement)]
207#[pin]
208pub struct MenuItem {
210 pub cached_rendering_data: CachedRenderingData,
211 pub title: Property<SharedString>,
212 pub activated: Callback<VoidArg>,
213 pub enabled: Property<bool>,
214 pub checkable: Property<bool>,
215 pub checked: Property<bool>,
216 pub icon: Property<Image>,
217 pub shortcut: Property<Keys>,
218}
219
220impl crate::items::Item for MenuItem {
221 fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
222
223 fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
224
225 fn layout_info(
226 self: Pin<&Self>,
227 _orientation: crate::items::Orientation,
228 _cross_axis_constraint: crate::Coord,
229 _window_adapter: &Rc<dyn WindowAdapter>,
230 _self_rc: &ItemRc,
231 ) -> crate::layout::LayoutInfo {
232 Default::default()
233 }
234
235 fn input_event_filter_before_children(
236 self: Pin<&Self>,
237 _: &crate::input::MouseEvent,
238 _window_adapter: &Rc<dyn WindowAdapter>,
239 _self_rc: &ItemRc,
240 _: &mut crate::items::MouseCursor,
241 ) -> crate::input::InputEventFilterResult {
242 Default::default()
243 }
244
245 fn input_event(
246 self: Pin<&Self>,
247 _: &crate::input::MouseEvent,
248 _window_adapter: &Rc<dyn WindowAdapter>,
249 _self_rc: &ItemRc,
250 _: &mut crate::items::MouseCursor,
251 ) -> crate::input::InputEventResult {
252 Default::default()
253 }
254
255 fn capture_key_event(
256 self: Pin<&Self>,
257 _: &InternalKeyEvent,
258 _window_adapter: &Rc<dyn WindowAdapter>,
259 _self_rc: &ItemRc,
260 ) -> crate::input::KeyEventResult {
261 crate::input::KeyEventResult::EventIgnored
262 }
263
264 fn key_event(
265 self: Pin<&Self>,
266 _: &InternalKeyEvent,
267 _window_adapter: &Rc<dyn WindowAdapter>,
268 _self_rc: &ItemRc,
269 ) -> crate::input::KeyEventResult {
270 Default::default()
271 }
272
273 fn focus_event(
274 self: Pin<&Self>,
275 _: &crate::input::FocusEvent,
276 _window_adapter: &Rc<dyn WindowAdapter>,
277 _self_rc: &ItemRc,
278 ) -> crate::input::FocusEventResult {
279 Default::default()
280 }
281
282 fn render(
283 self: Pin<&Self>,
284 _backend: &mut &mut dyn crate::item_rendering::ItemRenderer,
285 _self_rc: &ItemRc,
286 _size: crate::lengths::LogicalSize,
287 ) -> crate::items::RenderingResult {
288 Default::default()
289 }
290
291 fn bounding_rect(
292 self: Pin<&Self>,
293 _window_adapter: &Rc<dyn WindowAdapter>,
294 _self_rc: &ItemRc,
295 geometry: crate::lengths::LogicalRect,
296 ) -> crate::lengths::LogicalRect {
297 geometry
298 }
299
300 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
301 false
302 }
303}
304
305impl crate::items::ItemConsts for MenuItem {
306 const cached_rendering_data_offset: const_field_offset::FieldOffset<
307 MenuItem,
308 CachedRenderingData,
309 > = MenuItem::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
310}
311
312#[cfg(feature = "ffi")]
313pub mod ffi {
314 use super::*;
315
316 #[unsafe(no_mangle)]
320 pub unsafe extern "C" fn slint_menus_create_wrapper(
321 menu_tree: &ItemTreeRc,
322 result: *mut vtable::VRc<MenuVTable>,
323 condition: Option<extern "C" fn(menu_tree: &ItemTreeRc) -> bool>,
324 visible: Option<extern "C" fn(menu_tree: &ItemTreeRc) -> bool>,
325 ) {
326 let menu = match (condition, visible) {
327 (None, None) => MenuFromItemTree::new(menu_tree.clone()),
328 (condition, visible) => {
329 let menu_weak = ItemTreeRc::downgrade(menu_tree);
330 let condition = move || {
331 menu_weak
332 .upgrade()
333 .map(|menu_rc| condition.map(|x| x(&menu_rc)).unwrap_or(true))
334 .unwrap_or(false)
335 };
336
337 let menu_weak = ItemTreeRc::downgrade(menu_tree);
338 let visible = move || {
339 menu_weak
340 .upgrade()
341 .map(|menu_rc| visible.map(|x| x(&menu_rc)).unwrap_or(true))
342 .unwrap_or(false)
343 };
344
345 MenuFromItemTree::new_with_condition_and_visible(
346 menu_tree.clone(),
347 condition,
348 visible,
349 )
350 }
351 };
352
353 let vrc = vtable::VRc::into_dyn(vtable::VRc::new(menu));
354 unsafe { core::ptr::write(result, vrc) };
355 }
356}