Skip to main content

azul_core/
menu.rs

1//! Menu system for context menus, dropdown menus, and application menus.
2//!
3//! This module provides a cross-platform menu abstraction modeled after the Windows API,
4//! supporting hierarchical menus with separators, icons, keyboard accelerators, and callbacks.
5//!
6//! # Core vs Layout Types
7//!
8//! This module uses `CoreMenuCallback` with `usize` placeholders instead of function pointers
9//! to avoid circular dependencies between `azul-core` and `azul-layout`. The actual function
10//! pointers are stored in `azul-layout` and converted via unsafe code with identical memory
11//! layout.
12
13extern crate alloc;
14
15use alloc::vec::Vec;
16use core::hash::Hash;
17
18use azul_css::AzString;
19
20use crate::{
21    callbacks::{CoreCallback, CoreCallbackType},
22    refany::RefAny,
23    resources::ImageRef,
24    window::{ContextMenuMouseButton, OptionVirtualKeyCodeCombo},
25};
26
27/// Represents a menu (context menu, dropdown menu, or application menu).
28///
29/// A menu consists of a list of items that can be displayed as a popup or
30/// attached to a window's menu bar. Modeled after the Windows API for
31/// cross-platform consistency.
32///
33/// # Fields
34///
35/// * `items` - The menu items to display
36/// * `position` - Where the menu should appear (for popups)
37/// * `context_mouse_btn` - Which mouse button triggers the context menu
38#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
39#[repr(C)]
40pub struct Menu {
41    pub items: MenuItemVec,
42    pub position: MenuPopupPosition,
43    pub context_mouse_btn: ContextMenuMouseButton,
44}
45
46impl_option!(
47    Menu,
48    OptionMenu,
49    copy = false,
50    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
51);
52
53impl Menu {
54    /// Creates a new menu with the given items.
55    ///
56    /// Uses default position (AutoCursor) and right mouse button for context menus.
57    #[must_use]
58    pub fn create(items: MenuItemVec) -> Self {
59        Self {
60            items,
61            position: MenuPopupPosition::AutoCursor,
62            context_mouse_btn: ContextMenuMouseButton::Right,
63        }
64    }
65
66    /// Builder method to set the popup position.
67    #[must_use]
68    pub fn with_position(mut self, position: MenuPopupPosition) -> Self {
69        self.position = position;
70        self
71    }
72
73    /// Computes a 64-bit hash of this menu using the HighwayHash algorithm.
74    ///
75    /// This is used to detect changes in menu structure for caching and optimization.
76    #[must_use]
77    pub fn get_hash(&self) -> u64 {
78        use std::hash::Hasher;
79        let mut hasher = std::hash::DefaultHasher::new();
80        self.hash(&mut hasher);
81        hasher.finish()
82    }
83}
84
85/// Specifies where a popup menu should appear relative to the cursor or clicked element.
86///
87/// This positioning information is ignored for application-level menus (menu bars)
88/// and only applies to context menus and dropdowns.
89#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
90#[repr(C)]
91pub enum MenuPopupPosition {
92    /// Position menu below and to the left of the cursor
93    BottomLeftOfCursor,
94    /// Position menu below and to the right of the cursor
95    BottomRightOfCursor,
96    /// Position menu above and to the left of the cursor
97    TopLeftOfCursor,
98    /// Position menu above and to the right of the cursor
99    TopRightOfCursor,
100    /// Position menu below the rectangle that was clicked
101    BottomOfHitRect,
102    /// Position menu to the left of the rectangle that was clicked
103    LeftOfHitRect,
104    /// Position menu above the rectangle that was clicked
105    TopOfHitRect,
106    /// Position menu to the right of the rectangle that was clicked
107    RightOfHitRect,
108    /// Automatically calculate position based on available screen space near cursor
109    AutoCursor,
110    /// Automatically calculate position based on available screen space near clicked rect
111    AutoHitRect,
112}
113
114impl Default for MenuPopupPosition {
115    fn default() -> Self {
116        Self::AutoCursor
117    }
118}
119
120/// Describes the interactive state of a menu item.
121///
122/// Menu items can be in different states that affect their appearance and behavior:
123///
124/// - Normal items are clickable and render normally
125/// - Greyed items are visually disabled (greyed out) and non-clickable
126/// - Disabled items are non-clickable but retain normal appearance
127#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
128#[repr(C)]
129pub enum MenuItemState {
130    /// Normal menu item (default)
131    Normal,
132    /// Menu item is greyed out and clicking it does nothing
133    Greyed,
134    /// Menu item is disabled, but NOT greyed out
135    Disabled,
136}
137
138/// Represents a single item in a menu.
139///
140/// Menu items can be regular text items with labels and callbacks,
141/// visual separators, or line breaks for horizontal menu layouts.
142#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
143#[repr(C, u8)]
144pub enum MenuItem {
145    /// A regular menu item with a label, optional icon, callback, and sub-items
146    String(StringMenuItem),
147    /// A visual separator line (only rendered in vertical layouts)
148    Separator,
149    /// Forces a line break when the menu is laid out horizontally
150    BreakLine,
151}
152
153impl_option!(
154    MenuItem,
155    OptionMenuItem,
156    copy = false,
157    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
158);
159
160impl_vec!(MenuItem, MenuItemVec, MenuItemVecDestructor, MenuItemVecDestructorType, MenuItemVecSlice, OptionMenuItem);
161impl_vec_clone!(MenuItem, MenuItemVec, MenuItemVecDestructor);
162impl_vec_debug!(MenuItem, MenuItemVec);
163impl_vec_partialeq!(MenuItem, MenuItemVec);
164impl_vec_partialord!(MenuItem, MenuItemVec);
165impl_vec_hash!(MenuItem, MenuItemVec);
166impl_vec_eq!(MenuItem, MenuItemVec);
167impl_vec_ord!(MenuItem, MenuItemVec);
168
169/// A menu item with a text label and optional features.
170///
171/// `StringMenuItem` represents a clickable menu entry that can have:
172///
173/// - A text label
174/// - An optional keyboard accelerator (e.g., Ctrl+C)
175/// - An optional callback function
176/// - An optional icon (checkbox or image)
177/// - A state (normal, greyed, or disabled)
178/// - Child menu items (for sub-menus)
179#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
180#[repr(C)]
181pub struct StringMenuItem {
182    /// Label of the menu
183    /// (ex. "File", "Edit", "View")
184    pub label: AzString,
185    /// Optional accelerator combination
186    /// (ex. "CTRL + X" = [VirtualKeyCode::Ctrl, VirtualKeyCode::X]) for keyboard shortcut
187    pub accelerator: OptionVirtualKeyCodeCombo,
188    /// Optional callback to call
189    pub callback: OptionCoreMenuCallback,
190    /// State (normal, greyed, disabled)
191    pub menu_item_state: MenuItemState,
192    /// Optional icon for the menu entry
193    pub icon: OptionMenuItemIcon,
194    /// Sub-menus of this item (separators and line-breaks can't have sub-menus)
195    pub children: MenuItemVec,
196}
197
198impl StringMenuItem {
199    /// Creates a new menu item with the given label.
200    /// All optional fields default to `None` / `Normal`.
201    #[must_use]
202    pub const fn create(label: AzString) -> Self {
203        StringMenuItem {
204            label,
205            accelerator: OptionVirtualKeyCodeCombo::None,
206            callback: OptionCoreMenuCallback::None,
207            menu_item_state: MenuItemState::Normal,
208            icon: OptionMenuItemIcon::None,
209            children: MenuItemVec::from_const_slice(&[]),
210        }
211    }
212
213    /// Sets the child menu items for this item, creating a sub-menu.
214    #[must_use]
215    pub fn with_children(mut self, children: MenuItemVec) -> Self {
216        self.children = children;
217        self
218    }
219
220    /// Adds a single child menu item to this item.
221    #[must_use]
222    pub fn with_child(mut self, child: MenuItem) -> Self {
223        let mut children = self.children.into_library_owned_vec();
224        children.push(child);
225        self.children = children.into();
226        self
227    }
228
229    /// Attaches a callback function to this menu item.
230    ///
231    /// # Parameters
232    ///
233    /// * `data` - User data passed to the callback
234    /// * `callback` - Function pointer (as usize) to invoke when item is clicked
235    ///
236    /// # Note
237    ///
238    /// This uses `CoreCallbackType` (usize) instead of a real function pointer
239    /// to avoid circular dependencies. The conversion happens in azul-layout.
240    #[must_use]
241    pub fn with_callback<I: Into<CoreCallback>>(mut self, data: RefAny, callback: I) -> Self {
242        self.callback = Some(CoreMenuCallback {
243            refany: data,
244            callback: callback.into(),
245        })
246        .into();
247        self
248    }
249}
250
251/// Optional icon displayed next to a menu item.
252///
253/// Icons can be either:
254/// - A checkbox (checked or unchecked)
255/// - A custom image (typically 16x16 pixels)
256#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
257#[repr(C, u8)]
258pub enum MenuItemIcon {
259    /// Displays a checkbox, with `true` = checked, `false` = unchecked
260    Checkbox(bool),
261    /// Displays a custom image (typically 16x16 format)
262    Image(ImageRef),
263}
264
265impl_option!(
266    MenuItemIcon,
267    OptionMenuItemIcon,
268    copy = false,
269    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
270);
271
272// Core menu callback types (usize-based placeholders)
273//
274// Similar to CoreCallback, these use usize instead of function pointers
275// to avoid circular dependencies. Will be converted to real function
276// pointers in azul-layout.
277//
278// IMPORTANT: Memory layout must be identical to the real callback types!
279// Tests for this are in azul-layout/src/callbacks.rs
280
281/// Menu callback using usize placeholder for function pointer.
282///
283/// This type is used in `azul-core` to represent menu item callbacks without
284/// creating circular dependencies with `azul-layout`. The actual function pointer
285/// is stored as a `usize` and converted via unsafe code in `azul-layout`.
286#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
287#[repr(C)]
288pub struct CoreMenuCallback {
289    /// User data passed to the callback when the menu item is clicked
290    pub refany: RefAny,
291    /// Callback function pointer stored as usize (converted to real fn pointer in azul-layout)
292    pub callback: CoreCallback,
293}
294
295impl_option!(
296    CoreMenuCallback,
297    OptionCoreMenuCallback,
298    copy = false,
299    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
300);