1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
// SPDX-FileCopyrightText: Copyright (c) 2026 Mike Li/Mikewolfli/Wei Li(mikewolfli@163.com)
// SPDX-License-Identifier: MIT
macro_rules! impl_menu_widgets {
() => {
#[cfg(not(alloc_frugal))]
fn create_menu_bar(
&self,
parent: ObjectId,
x: i32,
y: i32,
width: u32,
height: u32,
) -> ObjectId {
self.mount_widget_of_kind(WidgetKind::MenuBar, parent, "", x, y, width, height)
}
fn create_menu(
&self,
parent: ObjectId,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> ObjectId {
#[cfg(all(not(alloc_frugal), full_widgets))]
{
// A menu handed to a **bar** is one of that bar's top-level titles, so two
// things must happen and only one of them is widget creation:
//
// 1. The bar has to learn the title. It paints its entries from its own
// `MenuBarEntry` list, so a menu widget alone leaves an empty strip — the
// state the demo documented as "`new_menu` creates no visible entry".
// 2. The menu's popup must be a **window** sibling, not a bar child. A popup
// extends below the bar's own rectangle, and `widget_at` refuses to descend
// into a parent whose bounds reject the point, so a bar-owned popup could
// never be clicked. Mounting it under the bar's window keeps the popup
// reachable; `runtime::open_context_menu` additionally re-appends it as the
// last child when it opens, so creation order cannot bury it.
let parent_kind =
crate::widget::runtime::with_widget(parent, |widget| widget.kind());
let is_bar = parent_kind == Some(crate::widget::WidgetKind::MenuBar);
// Top-level **and nested** menus are overlays: a submenu opens beside its parent
// menu, outside that menu's own rectangle. Mounting it under its invoker would put
// it past the invoker's bounds, where the hit test refuses to descend. Every menu
// therefore hangs off the window; only a bar parent also contributes an entry.
let popup_parent = match parent_kind {
Some(
crate::widget::WidgetKind::MenuBar
| crate::widget::WidgetKind::Menu
| crate::widget::WidgetKind::ContextMenu,
) => crate::widget::runtime::top_level_window_of(parent).unwrap_or(parent),
_ => parent,
};
if is_bar {
// The entry is added before the popup is mounted, so a mount failure still
// leaves the bar describing the menu the caller asked for.
crate::widget::runtime::with_widget_mut(parent, |bar| {
(bar as &mut dyn std::any::Any)
.downcast_mut::<crate::widget::menu_toolbar::menu_bar::MenuBar>()
.map(|bar| bar.add_menu(text))
});
// A bar that is already on screen must repaint to show the new title;
// `add_menu` alone changes the model and requests nothing.
crate::widget::runtime::request_repaint(parent);
}
let id = self.mount_widget_of_kind(
crate::control_backend::custom::MENU_KIND,
popup_parent,
text,
x,
y,
width,
height,
);
if is_bar && id != 0 {
// The bar already shows this title, so the drop-down must not repeat it as
// a heading. A context menu has no label above it and keeps the heading.
let _ = crate::widget::runtime::with_widget_mut(id, |widget| {
(widget as &mut dyn std::any::Any)
.downcast_mut::<crate::widget::menu_toolbar::menu::Menu>()
.map(|menu| menu.set_show_heading(false))
});
}
id
}
#[cfg(all(not(alloc_frugal), not(full_widgets)))]
{
// `menu_toolbar` exists only where the full widget set is compiled. Without it
// there is no bar entry and no heading flag to set, but the popup itself is
// still a widget: mount it so the call does not silently create nothing.
self.mount_widget_of_kind(
crate::control_backend::custom::MENU_KIND,
parent,
text,
x,
y,
width,
height,
)
}
#[cfg(alloc_frugal)]
{
// No widget registry exists in this profile, so neither the bar entry nor the
// popup can be represented. Report the honest failure rather than an id that
// addresses nothing.
let _ = (parent, text, x, y, width, height);
0
}
}
/// Attaches a menu bar to the window that owns it.
///
/// There is no host menu to attach: the menu bar is a self-drawn widget, so
/// "attaching" means making it a child of the window it belongs to. Both ids
/// are checked first — the previous body returned `true` unconditionally,
/// which claimed success for ids that addressed nothing.
fn attach_menu_bar_to_window(&self, window: ObjectId, menu_bar: ObjectId) -> bool {
#[cfg(not(alloc_frugal))]
{
if !crate::widget::runtime::is_mounted(window)
|| !crate::widget::runtime::is_mounted(menu_bar)
{
return false;
}
let attached = crate::widget::runtime::with_widget_mut(menu_bar, |bar| {
bar.set_parent(Some(window))
})
.is_some();
if attached {
crate::widget::runtime::request_repaint(window);
}
return attached;
}
#[cfg(alloc_frugal)]
{
let _ = (window, menu_bar);
false
}
}
/// Adds an entry to a menu and returns the menu's id.
///
/// # Why this returns the menu id, not a new widget id
///
/// A menu entry is a `MenuEntry` value inside `Menu`'s own `items` vector,
/// not a mounted widget — see `widget::menu_toolbar::menu::Menu`. The old
/// implementation minted a fresh id from a counter and stored a text string
/// against it, so the id addressed nothing and the entry was invisible in
/// every respect except that one map. Returning the menu's id keeps the
/// contract honest: the call succeeded, and the thing it acted on is the
/// menu the caller already holds.
///
/// Returns `0` when `parent_menu` is not a mounted `Menu`, rather than
/// silently succeeding.
fn menu_add_item(
&self,
parent_menu: ObjectId,
text: &str,
shortcut: Option<&str>,
) -> ObjectId {
// `widget::runtime` and `widget::menu_toolbar` are compiled out of the
// alloc-frugal profile, so a menu entry cannot be stored there. Report
// that honestly rather than pretending the call succeeded.
#[cfg(alloc_frugal)]
{
let _ = (parent_menu, text, shortcut);
log::warn!(
"custom backend: menu items are unavailable in this profile (no widget \
registry is compiled in)"
);
return 0;
}
#[cfg(all(not(alloc_frugal), full_widgets))]
{
let index = crate::widget::runtime::with_widget_mut(parent_menu, |widget| {
(widget as &mut dyn std::any::Any)
.downcast_mut::<crate::widget::menu_toolbar::menu::Menu>()
.map(|menu| match shortcut {
Some(shortcut) => menu.add_action_with_shortcut(text, shortcut),
None => menu.add_action(text),
})
})
.flatten();
let Some(index) = index else {
log::warn!(
"custom backend: id {parent_menu} is not a mounted menu, so \"{text}\" \
could not be added"
);
return 0;
};
// A menu entry is a row inside the menu, not a widget, so it has no
// id from the widget registry. Handing out one of this backend's
// own — and remembering which row it names — is what lets a
// trigger, or `menu_item_shortcut`, talk about the entry the caller
// just added. The previous `ObjectId::from(added)` returned `1` for
// every item: it addressed no entry at all, and collided with the
// first id the platform hands out.
let mut state = lock(&self.state);
let id = super::super::types::allocate_menu_entry_id(&mut state);
state.menu_entries.insert(id, (parent_menu, index));
let wire_selection = state.wired_menu_signals.insert(parent_menu, ()).is_none();
drop(state);
// # Why the menu's own signal is wired here
//
// `poll_menu_triggered` is documented as the way a host hears a menu choice,
// and on a self-drawn backend nothing fed it: the native backends' hosts push
// into the platform queue, while a `Menu` widget only emits `triggered`. A host
// polling the queue (the code-editor demo does) therefore saw nothing when the
// user chose an entry. One connection per menu maps the emitted index back to
// the entry id this method handed out and queues it, which is the same observable
// contract the native path has.
if wire_selection {
let _ = crate::widget::runtime::with_widget_mut(parent_menu, |widget| {
(widget as &mut dyn std::any::Any)
.downcast_mut::<crate::widget::menu_toolbar::menu::Menu>()
.map(|menu| {
menu.triggered_index.connect_ref(move |chosen: &usize| {
// The global backend is the one production callers poll; a
// backend instance created by a test keeps its own queue and
// is not the one `crate::poll_menu_triggered` drains.
crate::control_backend::get_control_backend()
.note_menu_selection(parent_menu, *chosen);
})
})
});
}
id
}
// `menu_toolbar` is compiled only where the full widget set is, so this
// profile has no `Menu` type to downcast to. Reporting the failure is
// the honest answer and keeps the method total in every profile.
#[cfg(all(not(alloc_frugal), not(full_widgets)))]
{
let _ = (parent_menu, text, shortcut);
log::warn!(
"custom backend: menus are unavailable in this profile (the menu widgets are \
not compiled in)"
);
0
}
}
/// Maps a chosen entry index back to the id `menu_add_item` handed out and queues it
/// for [`Self::poll_menu_triggered`].
fn note_menu_selection(&self, menu: ObjectId, index: usize) {
#[cfg(all(not(alloc_frugal), full_widgets))]
{
let mut state = lock(&self.state);
let entry = state
.menu_entries
.iter()
.find(|(_, (owner, row))| *owner == menu && *row == index)
.map(|(id, _)| *id);
if let Some(entry) = entry {
state.menu_trigger_queue.push_back(entry);
}
}
#[cfg(any(alloc_frugal, not(full_widgets)))]
{
let _ = (menu, index);
}
}
/// Reads a menu entry's accelerator text from the entry it names.
fn menu_item_shortcut(&self, menu_item: ObjectId) -> Option<String> {
#[cfg(all(not(alloc_frugal), full_widgets))]
{
let (menu_id, index) = {
let state = lock(&self.state);
*state.menu_entries.get(&menu_item)?
};
return crate::widget::runtime::with_widget(menu_id, |widget| {
(widget as &dyn std::any::Any)
.downcast_ref::<crate::widget::menu_toolbar::menu::Menu>()
.and_then(|menu| menu.items().get(index))
.map(|entry| entry.shortcut().to_string())
.filter(|shortcut| !shortcut.is_empty())
})
.flatten();
}
#[cfg(any(alloc_frugal, not(full_widgets)))]
{
let _ = menu_item;
None
}
}
#[cfg(not(alloc_frugal))]
fn create_tool_bar(
&self,
parent: ObjectId,
x: i32,
y: i32,
width: u32,
height: u32,
) -> ObjectId {
self.mount_widget_of_kind(WidgetKind::ToolBar, parent, "", x, y, width, height)
}
#[cfg(not(alloc_frugal))]
fn create_status_bar(
&self,
parent: ObjectId,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> ObjectId {
self.mount_widget_of_kind(WidgetKind::StatusBar, parent, text, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
fn create_action(
&self,
parent: ObjectId,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> ObjectId {
self.mount_widget_of_kind(WidgetKind::Action, parent, text, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
fn create_tool_button(
&self,
parent: ObjectId,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> ObjectId {
self.mount_widget_of_kind(WidgetKind::ToolButton, parent, text, x, y, width, height)
}
#[cfg(not(alloc_frugal))]
#[cfg(widgets_unstripped)]
fn create_tool_box(
&self,
parent: ObjectId,
x: i32,
y: i32,
width: u32,
height: u32,
) -> ObjectId {
#[cfg(full_widgets)]
{
self.mount_named_widget("tool_box", parent, "", x, y, width, height)
}
#[cfg(not(full_widgets))]
{
self.mount_widget_of_kind(WidgetKind::Toolbox, parent, "", x, y, width, height)
}
}
#[cfg(not(alloc_frugal))]
fn create_context_menu(
&self,
parent: ObjectId,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> ObjectId {
self.mount_widget_of_kind(WidgetKind::ContextMenu, parent, text, x, y, width, height)
}
fn poll_menu_triggered(&self) -> Option<ObjectId> {
lock(&self.state).menu_trigger_queue.pop_front()
}
fn inject_menu_trigger(&self, menu_item_id: ObjectId) -> bool {
let mut state = lock(&self.state);
state.menu_trigger_queue.push_back(menu_item_id);
true
}
};
}