i_slint_core/
menus.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// for MenuVTable_static
5#![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/// Interface for native menu and menubar
25#[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)]
26#[vtable::vtable]
27#[repr(C)]
28pub struct MenuVTable {
29    /// destructor
30    drop: extern "C" fn(VRefMut<MenuVTable>),
31    /// Return the list of items for the sub menu (or the main menu of parent is None)
32    sub_menu: extern "C" fn(VRef<MenuVTable>, Option<&MenuEntry>, &mut SharedVector<MenuEntry>),
33    /// Handler when the menu entry is activated
34    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]
143/// The implementation of an MenuItem items that does nothing
144pub 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    /// Create a `VBox::<MenuVTable>`` that wraps the [`ItemTreeRc`]
234    ///
235    /// Put the created VBox into the result pointer with std::ptr::write
236    #[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}