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