1#![allow(unsafe_code)]
6
7use crate::item_rendering::CachedRenderingData;
8use crate::item_tree::{ItemTreeRc, ItemWeak, VisitChildrenResult};
9use crate::items::{ItemRc, ItemRef, MenuEntry, VoidArg};
10use crate::properties::PropertyTracker;
11#[cfg(feature = "rtti")]
12use crate::rtti::*;
13use crate::string::ToSharedString;
14use crate::window::WindowAdapter;
15use crate::{Callback, Property, SharedString, SharedVector};
16use alloc::boxed::Box;
17use alloc::collections::BTreeMap;
18use alloc::rc::Rc;
19use core::cell::{Cell, RefCell};
20use core::pin::Pin;
21use i_slint_core_macros::SlintElement;
22use vtable::{VRef, VRefMut};
23
24#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
26#[vtable::vtable]
27#[repr(C)]
28pub struct MenuVTable {
29 drop: extern "C" fn(VRefMut<MenuVTable>),
31 sub_menu: extern "C" fn(VRef<MenuVTable>, Option<&MenuEntry>, &mut SharedVector<MenuEntry>),
33 activate: extern "C" fn(VRef<MenuVTable>, &MenuEntry),
35}
36
37struct ShadowTreeNode {
38 item: ItemWeak,
39 children: SharedVector<MenuEntry>,
40}
41
42pub struct MenuFromItemTree {
43 item_tree: ItemTreeRc,
44 item_cache: RefCell<BTreeMap<SharedString, ShadowTreeNode>>,
45 root: RefCell<SharedVector<MenuEntry>>,
46 next_id: Cell<usize>,
47 tracker: Pin<Box<PropertyTracker>>,
48}
49
50impl MenuFromItemTree {
51 pub fn new(item_tree: ItemTreeRc) -> Self {
52 Self {
53 item_tree,
54 item_cache: Default::default(),
55 root: Default::default(),
56 tracker: Box::pin(PropertyTracker::default()),
57 next_id: 0.into(),
58 }
59 }
60
61 fn update_shadow_tree(&self) {
62 self.tracker.as_ref().evaluate_if_dirty(|| {
63 self.item_cache.replace(Default::default());
64 self.root
65 .replace(self.update_shadow_tree_recursive(&ItemRc::new(self.item_tree.clone(), 0)))
66 });
67 }
68
69 fn update_shadow_tree_recursive(&self, parent: &ItemRc) -> SharedVector<MenuEntry> {
70 let mut result = SharedVector::default();
71 let mut last_is_separator = false;
72
73 let mut actual_visitor =
74 |item_tree: &ItemTreeRc, index: u32, item_pin: Pin<ItemRef>| -> VisitChildrenResult {
75 if let Some(menu_item) = ItemRef::downcast_pin::<MenuItem>(item_pin) {
76 let title = menu_item.title();
77 let is_separator =
78 title.as_str() == i_slint_common::MENU_SEPARATOR_PLACEHOLDER_TITLE;
79 if is_separator && (last_is_separator || result.is_empty()) {
80 return VisitChildrenResult::CONTINUE;
81 }
82 last_is_separator = is_separator;
83 let next_id = self.next_id.get();
84 self.next_id.set(next_id + 1);
85 let id = next_id.to_shared_string();
86 let item = ItemRc::new(item_tree.clone(), index);
87 let children = self.update_shadow_tree_recursive(&item);
88 let has_sub_menu = !children.is_empty();
89 let enabled = menu_item.enabled();
90 self.item_cache.borrow_mut().insert(
91 id.clone(),
92 ShadowTreeNode { item: ItemRc::downgrade(&item), children },
93 );
94 result.push(MenuEntry { title, id, has_sub_menu, is_separator, enabled });
95 }
96 VisitChildrenResult::CONTINUE
97 };
98 vtable::new_vref!(let mut actual_visitor : VRefMut<crate::item_tree::ItemVisitorVTable> for crate::item_tree::ItemVisitor = &mut actual_visitor);
99
100 vtable::VRc::borrow_pin(parent.item_tree()).as_ref().visit_children_item(
101 parent.index() as isize,
102 crate::item_tree::TraversalOrder::BackToFront,
103 actual_visitor,
104 );
105 if last_is_separator {
106 result.pop();
107 }
108 result
109 }
110}
111
112impl Menu for MenuFromItemTree {
113 fn sub_menu(&self, parent: Option<&MenuEntry>, result: &mut SharedVector<MenuEntry>) {
114 self.update_shadow_tree();
115 match parent {
116 Some(parent) => {
117 if let Some(r) = self.item_cache.borrow().get(parent.id.as_str()) {
118 *result = r.children.clone();
119 }
120 }
121 None => {
122 *result = self.root.borrow().clone();
123 }
124 }
125 }
126
127 fn activate(&self, entry: &MenuEntry) {
128 if let Some(menu_item) =
129 self.item_cache.borrow().get(entry.id.as_str()).and_then(|e| e.item.upgrade())
130 {
131 if let Some(menu_item) = menu_item.downcast::<MenuItem>() {
132 menu_item.activated.call(&());
133 }
134 }
135 }
136}
137
138MenuVTable_static!(static MENU_FROM_ITEM_TREE_VT for MenuFromItemTree);
139
140#[repr(C)]
141#[derive(const_field_offset::FieldOffsets, Default, SlintElement)]
142#[pin]
143pub struct MenuItem {
145 pub cached_rendering_data: CachedRenderingData,
146 pub title: Property<SharedString>,
147 pub activated: Callback<VoidArg>,
148 pub enabled: Property<bool>,
149}
150
151impl crate::items::Item for MenuItem {
152 fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
153
154 fn layout_info(
155 self: Pin<&Self>,
156 _orientation: crate::items::Orientation,
157 _window_adapter: &Rc<dyn WindowAdapter>,
158 _self_rc: &ItemRc,
159 ) -> crate::layout::LayoutInfo {
160 Default::default()
161 }
162
163 fn input_event_filter_before_children(
164 self: Pin<&Self>,
165 _: crate::input::MouseEvent,
166 _window_adapter: &Rc<dyn WindowAdapter>,
167 _self_rc: &ItemRc,
168 ) -> crate::input::InputEventFilterResult {
169 Default::default()
170 }
171
172 fn input_event(
173 self: Pin<&Self>,
174 _: crate::input::MouseEvent,
175 _window_adapter: &Rc<dyn WindowAdapter>,
176 _self_rc: &ItemRc,
177 ) -> crate::input::InputEventResult {
178 Default::default()
179 }
180
181 fn key_event(
182 self: Pin<&Self>,
183 _: &crate::input::KeyEvent,
184 _window_adapter: &Rc<dyn WindowAdapter>,
185 _self_rc: &ItemRc,
186 ) -> crate::input::KeyEventResult {
187 Default::default()
188 }
189
190 fn focus_event(
191 self: Pin<&Self>,
192 _: &crate::input::FocusEvent,
193 _window_adapter: &Rc<dyn WindowAdapter>,
194 _self_rc: &ItemRc,
195 ) -> crate::input::FocusEventResult {
196 Default::default()
197 }
198
199 fn render(
200 self: Pin<&Self>,
201 _backend: &mut &mut dyn crate::item_rendering::ItemRenderer,
202 _self_rc: &ItemRc,
203 _size: crate::lengths::LogicalSize,
204 ) -> crate::items::RenderingResult {
205 Default::default()
206 }
207
208 fn bounding_rect(
209 self: Pin<&Self>,
210 _window_adapter: &Rc<dyn WindowAdapter>,
211 _self_rc: &ItemRc,
212 geometry: crate::lengths::LogicalRect,
213 ) -> crate::lengths::LogicalRect {
214 geometry
215 }
216
217 fn clips_children(self: core::pin::Pin<&Self>) -> bool {
218 false
219 }
220}
221
222impl crate::items::ItemConsts for MenuItem {
223 const cached_rendering_data_offset: const_field_offset::FieldOffset<
224 MenuItem,
225 CachedRenderingData,
226 > = MenuItem::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
227}
228
229#[cfg(feature = "ffi")]
230pub mod ffi {
231 use super::*;
232
233 #[unsafe(no_mangle)]
237 pub unsafe extern "C" fn slint_menus_create_wrapper(
238 menu_tree: &ItemTreeRc,
239 result: *mut vtable::VBox<MenuVTable>,
240 ) {
241 let b = vtable::VBox::<MenuVTable>::new(MenuFromItemTree::new(menu_tree.clone()));
242 core::ptr::write(result, b);
243 }
244}