Skip to content

Commit efcff40

Browse files
authored
Add a MenuBar::visible property (#11272)
Add a visible property to the menu bar. If a menu bar is not visible, it still accepts shortcuts for its Menu items, but it does not take up any space anymore. This allows recreating behavior similar to Firefox, where pressing, ALT will hide or show the menu bar conditionally.
1 parent afc9750 commit efcff40

11 files changed

Lines changed: 238 additions & 75 deletions

File tree

api/cpp/include/slint.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,10 +289,11 @@ union MaybeUninitialized {
289289

290290
inline vtable::VRc<cbindgen_private::MenuVTable>
291291
create_menu_wrapper(const ItemTreeRc &menu_item_tree,
292-
bool (*condition)(const ItemTreeRc *menu_tree) = nullptr)
292+
bool (*condition)(const ItemTreeRc *menu_tree) = nullptr,
293+
bool (*visible)(const ItemTreeRc *menu_tree) = nullptr)
293294
{
294295
MaybeUninitialized<vtable::VRc<cbindgen_private::MenuVTable>> maybe;
295-
cbindgen_private::slint_menus_create_wrapper(&menu_item_tree, &maybe.value, condition);
296+
cbindgen_private::slint_menus_create_wrapper(&menu_item_tree, &maybe.value, condition, visible);
296297
return maybe.take();
297298
}
298299

internal/backends/winit/muda.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,9 @@ impl MudaAdapter {
215215
if let Some(menu_tree) = menu_tree {
216216
let mut build_menu = || {
217217
let mut menu_entries = Default::default();
218-
vtable::VRc::borrow(menu_tree).sub_menu(None, &mut menu_entries);
218+
if vtable::VRc::borrow(&menu_tree).visible() {
219+
vtable::VRc::borrow(&menu_tree).sub_menu(None, &mut menu_entries);
220+
}
219221

220222
if menu_entries.is_empty() && muda_type == MudaType::Menubar {
221223
self.menu = None;

internal/compiler/builtins.slint

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,9 @@ component Menu {
13951395
/// ```
13961396
/// \skip_children
13971397
component MenuBar {
1398+
/// Whether this menu bar should be visible. If the menu bar is not visible, the menu bar will not take up any space but shortcuts will still function.
1399+
/// \default true
1400+
in property <bool> visible: true;
13981401
//-is_non_item_type
13991402
//-disallow_global_types_as_child_elements
14001403
Menu { }

internal/compiler/generator/cpp.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4657,7 +4657,7 @@ fn compile_builtin_function_call(
46574657
}
46584658
BuiltinFunction::SetupMenuBar => {
46594659
let window = access_window_field(ctx);
4660-
let [llr::Expression::PropertyReference(entries_r), llr::Expression::PropertyReference(sub_menu_r), llr::Expression::PropertyReference(activated_r), llr::Expression::NumberLiteral(tree_index), llr::Expression::BoolLiteral(no_native), rest @ ..] = arguments
4660+
let [llr::Expression::PropertyReference(entries_r), llr::Expression::PropertyReference(sub_menu_r), llr::Expression::PropertyReference(activated_r), llr::Expression::NumberLiteral(tree_index), llr::Expression::BoolLiteral(no_native), condition, visible, ..] = arguments
46614661
else {
46624662
panic!("internal error: incorrect argument count to SetupMenuBar")
46634663
};
@@ -4676,21 +4676,22 @@ fn compile_builtin_function_call(
46764676
slint::private_api::setup_popup_menu_from_menu_item_tree(menu_wrapper, {access_entries}, {access_sub_menu}, {access_activated});
46774677
}}")
46784678
} else {
4679-
let condition = if let [condition] = &rest {
4680-
let condition = compile_expression(condition, ctx);
4679+
let compile_prop = |prop_expr: &llr::Expression| {
4680+
let binding = compile_expression(prop_expr, ctx);
46814681
format!(r"[](auto menu_tree) {{
46824682
auto self_mapped = reinterpret_cast<const {item_tree_id} *>(menu_tree->operator->())->parent.lock();
46834683
[[maybe_unused]] auto self = &**self_mapped;
4684-
return {condition};
4684+
return {binding};
46854685
}}")
4686-
} else {
4687-
"nullptr".to_string()
46884686
};
46894687

4688+
let condition = compile_prop(condition);
4689+
let visible = compile_prop(visible);
4690+
46904691
format!(r"{{
46914692
auto item_tree = {item_tree_id}::create(self);
46924693
auto item_tree_dyn = item_tree.into_dyn();
4693-
auto menu_wrapper = slint::private_api::create_menu_wrapper(item_tree_dyn, {condition});
4694+
auto menu_wrapper = slint::private_api::create_menu_wrapper(item_tree_dyn, {condition}, {visible});
46944695
slint::private_api::slint_windowrc_setup_menu_bar_shortcuts(&{window}.handle(), &menu_wrapper);
46954696
if ({window}.supports_native_menu_bar()) {{
46964697
slint::cbindgen_private::slint_windowrc_setup_native_menu_bar(&{window}.handle(), &menu_wrapper);

internal/compiler/generator/rust.rs

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4073,7 +4073,9 @@ fn compile_builtin_function_call(
40734073
Expression::PropertyReference(activated_r),
40744074
Expression::NumberLiteral(tree_index),
40754075
Expression::BoolLiteral(no_native),
4076-
rest @ ..,
4076+
condition,
4077+
visible,
4078+
..,
40774079
] = arguments
40784080
else {
40794081
panic!("internal error: incorrect arguments to SetupMenuBar")
@@ -4090,28 +4092,33 @@ fn compile_builtin_function_call(
40904092
let access_sub_menu = access_member(sub_menu_r, ctx).unwrap();
40914093
let access_activated = access_member(activated_r, ctx).unwrap();
40924094

4093-
let native_impl = if *no_native {
4094-
quote!(let menu_item_tree = sp::MenuFromItemTree::new(sp::VRc::into_dyn(menu_item_tree_instance));)
4095-
} else {
4096-
let menu_from_item_tree = if let Some(condition) = &rest.first() {
4097-
let binding = compile_expression(condition, ctx);
4098-
quote!(sp::MenuFromItemTree::new_with_condition(sp::VRc::into_dyn(menu_item_tree_instance), {
4099-
let self_weak = _self.self_weak.get().unwrap().clone();
4100-
move || {
4101-
let Some(self_rc) = self_weak.upgrade() else { return false };
4102-
let _self = self_rc.as_pin_ref();
4103-
#binding
4104-
}
4105-
}))
4095+
let compile_prop = |prop_expr: &Expression| {
4096+
let binding = compile_expression(prop_expr, ctx);
4097+
quote!({
4098+
let self_weak = _self.self_weak.get().unwrap().clone();
4099+
move || {
4100+
let Some(self_rc) = self_weak.upgrade() else { return false };
4101+
let _self = self_rc.as_pin_ref();
4102+
#binding
4103+
}
4104+
})
4105+
};
4106+
4107+
let condition_tokens = compile_prop(condition);
4108+
let visible_tokens = compile_prop(visible);
4109+
4110+
let native_impl = {
4111+
let menu_from_item_tree = quote!(sp::VRc::new(sp::MenuFromItemTree::new_with_condition_and_visible(sp::VRc::into_dyn(menu_item_tree_instance), #condition_tokens, #visible_tokens)));
4112+
if *no_native {
4113+
quote!(let menu_item_tree = #menu_from_item_tree;)
41064114
} else {
4107-
quote!(sp::MenuFromItemTree::new(sp::VRc::into_dyn(menu_item_tree_instance)))
4108-
};
4109-
quote! {
4110-
let menu_item_tree = sp::VRc::new(#menu_from_item_tree);
4111-
if sp::WindowInner::from_pub(#window_adapter_tokens.window()).supports_native_menu_bar() {
4112-
let menu_item_tree_dyn = sp::VRc::into_dyn(sp::VRc::clone(&menu_item_tree));
4113-
sp::WindowInner::from_pub(#window_adapter_tokens.window()).setup_menubar(menu_item_tree_dyn);
4114-
} else
4115+
quote! {
4116+
let menu_item_tree = #menu_from_item_tree;
4117+
if sp::WindowInner::from_pub(#window_adapter_tokens.window()).supports_native_menu_bar() {
4118+
let menu_item_tree_dyn = sp::VRc::into_dyn(sp::VRc::clone(&menu_item_tree));
4119+
sp::WindowInner::from_pub(#window_adapter_tokens.window()).setup_menubar(menu_item_tree_dyn);
4120+
} else
4121+
}
41154122
}
41164123
};
41174124

@@ -4160,25 +4167,27 @@ fn compile_builtin_function_call(
41604167
let system_tray_rc = access_item_rc(system_tray_ref, ctx);
41614168

41624169
// `if cond : Menu { ... }` lowers the condition into a closure that
4163-
// gates the menu's shadow tree. `MenuFromItemTree::new_with_condition`
4164-
// re-evaluates it through a property-tracked binding.
4165-
let menu_from_item_tree = if let Some(condition) = rest.first() {
4170+
// gates the menu's shadow tree.
4171+
let condition_tokens = if let Some(condition) = rest.first() {
41664172
let binding = compile_expression(condition, ctx);
4167-
quote!(sp::MenuFromItemTree::new_with_condition(
4168-
sp::VRc::into_dyn(menu_item_tree_instance),
4169-
{
4170-
let self_weak = _self.self_weak.get().unwrap().clone();
4171-
move || {
4172-
let Some(self_rc) = self_weak.upgrade() else { return false };
4173-
let _self = self_rc.as_pin_ref();
4174-
#binding
4175-
}
4176-
},
4177-
))
4173+
quote!({
4174+
let self_weak = _self.self_weak.get().unwrap().clone();
4175+
move || {
4176+
let Some(self_rc) = self_weak.upgrade() else { return false };
4177+
let _self = self_rc.as_pin_ref();
4178+
#binding
4179+
}
4180+
})
41784181
} else {
4179-
quote!(sp::MenuFromItemTree::new(sp::VRc::into_dyn(menu_item_tree_instance)))
4182+
quote!(|| true)
41804183
};
41814184

4185+
let menu_from_item_tree = quote!(sp::MenuFromItemTree::new_with_condition_and_visible(
4186+
sp::VRc::into_dyn(menu_item_tree_instance),
4187+
#condition_tokens,
4188+
|| true
4189+
));
4190+
41824191
quote!({
41834192
let menu_item_tree_instance = #item_tree_id::new(_self.self_weak.get().unwrap().clone()).unwrap();
41844193
let menu_vrc = sp::VRc::into_dyn(sp::VRc::new(#menu_from_item_tree));

internal/compiler/passes/lower_menus.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,15 @@ fn process_window(
558558
}
559559
}
560560

561+
// Transfer the visible binding from MenuBar to MenuBarImpl
562+
let visible_binding = menu_bar.borrow_mut().bindings.remove("visible");
563+
if let Some(visible_binding) = &visible_binding {
564+
menubar_impl
565+
.borrow_mut()
566+
.bindings
567+
.insert(SmolStr::new_static("menubar-visible"), visible_binding.clone());
568+
}
569+
561570
// Transform the MenuBar in a layout
562571
menu_bar.borrow_mut().base_type = components.vertical_layout.clone();
563572
menu_bar.borrow_mut().children = vec![menubar_impl, child];
@@ -602,6 +611,14 @@ fn process_window(
602611

603612
if let Some(condition) = original_cond {
604613
arguments.push(condition);
614+
} else {
615+
arguments.push(Expression::BoolLiteral(true));
616+
}
617+
618+
if let Some(visible_binding) = visible_binding {
619+
arguments.push(visible_binding.borrow().expression.clone());
620+
} else {
621+
arguments.push(Expression::BoolLiteral(true));
605622
}
606623

607624
let setup_menubar = Expression::FunctionCall {

internal/compiler/widgets/common/menus.slint

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,11 @@ export component MenuBarImpl {
152152
callback sub-menu(menu-entry: MenuEntry) -> [MenuEntry];
153153

154154
property <[MenuEntry]> entries;
155+
property <bool> menubar-visible: true;
155156

156157
preferred-width: 100%;
157-
height: base.min-height;
158+
height: menubar-visible ? base.min-height : 0px;
159+
visible: menubar-visible;
158160

159161
private property <int> last-open-entry-index : -1;
160162

internal/core/menus.rs

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub struct MenuVTable {
3232
sub_menu: extern "C" fn(VRef<MenuVTable>, Option<&MenuEntry>, &mut SharedVector<MenuEntry>),
3333
/// Handler when the menu entry is activated
3434
activate: extern "C" fn(VRef<MenuVTable>, &MenuEntry),
35+
/// Handler to return whether the menu is visible or not
36+
visible: extern "C" fn(VRef<MenuVTable>) -> bool,
3537
/// drop_in_place handler
3638
drop_in_place: extern "C" fn(VRefMut<MenuVTable>) -> Layout,
3739
/// dealloc handler
@@ -50,34 +52,45 @@ pub struct MenuFromItemTree {
5052
next_id: Cell<usize>,
5153
tracker: Pin<Box<PropertyTracker>>,
5254
condition: Option<Pin<Box<Property<bool>>>>,
55+
visible: Option<Pin<Box<Property<bool>>>>,
5356
}
5457

5558
impl MenuFromItemTree {
5659
pub fn new(item_tree: ItemTreeRc) -> Self {
57-
Self {
58-
item_tree,
59-
item_cache: Default::default(),
60-
root: Default::default(),
61-
tracker: Box::pin(PropertyTracker::default()),
62-
next_id: 0.into(),
63-
condition: None,
64-
}
60+
Self::new_internal(item_tree, None, None)
6561
}
6662

67-
pub fn new_with_condition(
63+
pub fn new_with_condition_and_visible(
6864
item_tree: ItemTreeRc,
6965
condition: impl Fn() -> bool + 'static,
66+
visible: impl Fn() -> bool + 'static,
7067
) -> Self {
71-
let cond_prop = Box::pin(Property::new_named(true, "MenuFromItemTree::condition"));
72-
cond_prop.as_ref().set_binding(condition);
68+
fn make_prop(
69+
f: impl Fn() -> bool + 'static,
70+
name: &'static str,
71+
) -> Option<Pin<Box<Property<bool>>>> {
72+
let prop = Box::pin(Property::new_named(true, name));
73+
prop.as_ref().set_binding(f);
74+
Some(prop)
75+
}
76+
let condition = make_prop(condition, "MenuFromItemTree::condition");
77+
let visible = make_prop(visible, "MenuFromItemTree::visible");
78+
Self::new_internal(item_tree, condition, visible)
79+
}
7380

81+
fn new_internal(
82+
item_tree: ItemTreeRc,
83+
condition: Option<Pin<Box<Property<bool>>>>,
84+
visible: Option<Pin<Box<Property<bool>>>>,
85+
) -> Self {
7486
Self {
7587
item_tree,
7688
item_cache: Default::default(),
7789
root: Default::default(),
7890
tracker: Box::pin(PropertyTracker::default()),
7991
next_id: 0.into(),
80-
condition: Some(cond_prop),
92+
condition,
93+
visible,
8194
}
8295
}
8396

@@ -181,6 +194,10 @@ impl Menu for MenuFromItemTree {
181194
menu_item.activated.call(&());
182195
}
183196
}
197+
198+
fn visible(&self) -> bool {
199+
self.visible.as_ref().is_none_or(|v| v.as_ref().get())
200+
}
184201
}
185202

186203
MenuVTable_static!(static MENU_FROM_ITEM_TREE_VT for MenuFromItemTree);
@@ -304,16 +321,33 @@ pub mod ffi {
304321
menu_tree: &ItemTreeRc,
305322
result: *mut vtable::VRc<MenuVTable>,
306323
condition: Option<extern "C" fn(menu_tree: &ItemTreeRc) -> bool>,
324+
visible: Option<extern "C" fn(menu_tree: &ItemTreeRc) -> bool>,
307325
) {
308-
let menu = match condition {
309-
Some(condition) => {
326+
let menu = match (condition, visible) {
327+
(None, None) => MenuFromItemTree::new(menu_tree.clone()),
328+
(condition, visible) => {
329+
let menu_weak = ItemTreeRc::downgrade(menu_tree);
330+
let condition = move || {
331+
menu_weak
332+
.upgrade()
333+
.map(|menu_rc| condition.map(|x| x(&menu_rc)).unwrap_or(true))
334+
.unwrap_or(false)
335+
};
336+
310337
let menu_weak = ItemTreeRc::downgrade(menu_tree);
311-
MenuFromItemTree::new_with_condition(menu_tree.clone(), move || {
312-
let Some(menu_rc) = menu_weak.upgrade() else { return false };
313-
condition(&menu_rc)
314-
})
338+
let visible = move || {
339+
menu_weak
340+
.upgrade()
341+
.map(|menu_rc| visible.map(|x| x(&menu_rc)).unwrap_or(true))
342+
.unwrap_or(false)
343+
};
344+
345+
MenuFromItemTree::new_with_condition_and_visible(
346+
menu_tree.clone(),
347+
condition,
348+
visible,
349+
)
315350
}
316-
None => MenuFromItemTree::new(menu_tree.clone()),
317351
};
318352

319353
let vrc = vtable::VRc::into_dyn(vtable::VRc::new(menu));

internal/interpreter/dynamic_item_tree.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2858,6 +2858,7 @@ pub fn make_menu_item_tree(
28582858
menu_item_tree: &Rc<object_tree::Component>,
28592859
enclosing_component: &InstanceRef,
28602860
condition: Option<&Expression>,
2861+
visible: Option<&Expression>,
28612862
) -> vtable::VRc<i_slint_core::menus::MenuVTable, MenuFromItemTree> {
28622863
generativity::make_guard!(guard);
28632864
let mit_compiled = generate_item_tree(
@@ -2879,13 +2880,26 @@ pub fn make_menu_item_tree(
28792880
);
28802881
mit_inst.run_setup_code();
28812882
let item_tree = vtable::VRc::into_dyn(mit_inst);
2882-
let menu = match condition {
2883-
Some(condition) => {
2884-
let binding =
2885-
make_binding_eval_closure(condition.clone(), enclosing_component_weak.clone());
2886-
MenuFromItemTree::new_with_condition(item_tree, move || binding().try_into().unwrap())
2883+
let condition = condition.map(|condition| {
2884+
let binding =
2885+
make_binding_eval_closure(condition.clone(), enclosing_component_weak.clone());
2886+
move || binding().try_into().unwrap()
2887+
});
2888+
let visible = visible.map(|visible| {
2889+
let binding = make_binding_eval_closure(visible.clone(), enclosing_component_weak.clone());
2890+
move || binding().try_into().unwrap()
2891+
});
2892+
let menu = match (condition, visible) {
2893+
(None, None) => MenuFromItemTree::new(item_tree),
2894+
(None, Some(visible)) => {
2895+
MenuFromItemTree::new_with_condition_and_visible(item_tree, || true, visible)
2896+
}
2897+
(Some(condition), None) => {
2898+
MenuFromItemTree::new_with_condition_and_visible(item_tree, condition, || true)
2899+
}
2900+
(Some(condition), Some(visible)) => {
2901+
MenuFromItemTree::new_with_condition_and_visible(item_tree, condition, visible)
28872902
}
2888-
None => MenuFromItemTree::new(item_tree),
28892903
};
28902904
vtable::VRc::new(menu)
28912905
}

0 commit comments

Comments
 (0)