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

            
13
extern crate alloc;
14

            
15
use alloc::vec::Vec;
16
use core::hash::Hash;
17

            
18
use azul_css::AzString;
19

            
20
use 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)]
40
pub struct Menu {
41
    pub items: MenuItemVec,
42
    pub position: MenuPopupPosition,
43
    pub context_mouse_btn: ContextMenuMouseButton,
44
}
45

            
46
impl_option!(
47
    Menu,
48
    OptionMenu,
49
    copy = false,
50
    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
51
);
52

            
53
impl 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 core::hash::Hasher;
79
        let mut hasher = crate::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)]
91
pub 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

            
114
impl 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)]
129
pub 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)]
144
pub 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

            
153
impl_option!(
154
    MenuItem,
155
    OptionMenuItem,
156
    copy = false,
157
    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
158
);
159

            
160
impl_vec!(MenuItem, MenuItemVec, MenuItemVecDestructor, MenuItemVecDestructorType, MenuItemVecSlice, OptionMenuItem);
161
impl_vec_clone!(MenuItem, MenuItemVec, MenuItemVecDestructor);
162
impl_vec_debug!(MenuItem, MenuItemVec);
163
impl_vec_partialeq!(MenuItem, MenuItemVec);
164
impl_vec_partialord!(MenuItem, MenuItemVec);
165
impl_vec_hash!(MenuItem, MenuItemVec);
166
impl_vec_eq!(MenuItem, MenuItemVec);
167
impl_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)]
181
pub 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

            
198
impl 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)]
258
pub 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

            
265
impl_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)]
288
pub 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

            
295
impl_option!(
296
    CoreMenuCallback,
297
    OptionCoreMenuCallback,
298
    copy = false,
299
    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
300
);