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);