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