mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-27 12:29:41 +00:00
256 lines
9.3 KiB
Rust
256 lines
9.3 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
|
|
|
//! Passe lower the `MenuBar` and `ContextMenu` as well as all their contents
|
|
//!
|
|
//! Must be done before inlining and many other passes because the lowered code must
|
|
//! be further inlined as it may expends to native widget that needs inlining
|
|
|
|
use crate::diagnostics::{BuildDiagnostics, Spanned};
|
|
use crate::expression_tree::{BuiltinFunction, Expression, NamedReference};
|
|
use crate::langtype::{ElementType, Type};
|
|
use crate::object_tree::*;
|
|
use core::cell::RefCell;
|
|
use smol_str::{format_smolstr, SmolStr};
|
|
|
|
struct UsefulMenuComponents {
|
|
menubar_impl: ElementType,
|
|
vertical_layout: ElementType,
|
|
empty: ElementType,
|
|
}
|
|
|
|
pub async fn lower_menus(
|
|
doc: &mut Document,
|
|
type_loader: &mut crate::typeloader::TypeLoader,
|
|
diag: &mut BuildDiagnostics,
|
|
) {
|
|
// Ignore import errors
|
|
let mut build_diags_to_ignore = BuildDiagnostics::default();
|
|
|
|
let menubar_impl = type_loader
|
|
.import_component("std-widgets.slint", "MenuBarImpl", &mut build_diags_to_ignore)
|
|
.await
|
|
.expect("MenuBarImpl should be in std-widgets.slint");
|
|
let useful_menu_component = UsefulMenuComponents {
|
|
menubar_impl: menubar_impl.clone().into(),
|
|
vertical_layout: type_loader
|
|
.global_type_registry
|
|
.borrow()
|
|
.lookup_builtin_element("VerticalLayout")
|
|
.expect("VerticalLayout is a builtin type"),
|
|
empty: type_loader.global_type_registry.borrow().empty_type(),
|
|
};
|
|
|
|
let mut has_menu = false;
|
|
|
|
doc.visit_all_used_components(|component| {
|
|
recurse_elem_including_sub_components_no_borrow(component, &(), &mut |elem, _| {
|
|
if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "Window") {
|
|
has_menu |= process_window(elem, &useful_menu_component, diag);
|
|
}
|
|
if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "ContextMenu") {
|
|
has_menu |= process_context_menu(elem, &useful_menu_component, diag);
|
|
}
|
|
})
|
|
});
|
|
|
|
if has_menu {
|
|
let popup_menu_impl = type_loader
|
|
.import_component("std-widgets.slint", "PopupMenuImpl", &mut build_diags_to_ignore)
|
|
.await
|
|
.expect("PopupMenuImpl should be in std-widgets.slint");
|
|
{
|
|
let mut root = popup_menu_impl.root_element.borrow_mut();
|
|
|
|
for prop in ["entries", "sub-menu", "activated"] {
|
|
match root.property_declarations.get_mut(prop) {
|
|
Some(d) => d.expose_in_public_api = true,
|
|
None => diag.push_error(format!("PopupMenuImpl doesn't have {prop}"), &*root),
|
|
}
|
|
}
|
|
root.property_analysis.borrow_mut().entry("entries".into()).or_default().is_set = true;
|
|
}
|
|
|
|
recurse_elem_including_sub_components_no_borrow(&popup_menu_impl, &(), &mut |elem, _| {
|
|
if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "ContextMenu") {
|
|
process_context_menu(elem, &useful_menu_component, diag);
|
|
}
|
|
});
|
|
recurse_elem_including_sub_components_no_borrow(&menubar_impl, &(), &mut |elem, _| {
|
|
if matches!(&elem.borrow().builtin_type(), Some(b) if b.name == "ContextMenu") {
|
|
process_context_menu(elem, &useful_menu_component, diag);
|
|
}
|
|
});
|
|
doc.popup_menu_impl = popup_menu_impl.into();
|
|
}
|
|
}
|
|
|
|
fn process_context_menu(
|
|
_context_menu_elem: &ElementRc,
|
|
_useful_menu_components: &UsefulMenuComponents,
|
|
_diag: &mut BuildDiagnostics,
|
|
) -> bool {
|
|
// TODO:
|
|
true
|
|
}
|
|
|
|
fn process_window(
|
|
win: &ElementRc,
|
|
components: &UsefulMenuComponents,
|
|
diag: &mut BuildDiagnostics,
|
|
) -> bool {
|
|
/* if matches!(&elem.borrow_mut().base_type, ElementType::Builtin(_)) {
|
|
// That's the TabWidget re-exported from the style, it doesn't need to be processed
|
|
return;
|
|
}*/
|
|
|
|
let mut window = win.borrow_mut();
|
|
let mut menu_bar = None;
|
|
window.children.retain(|x| {
|
|
if matches!(&x.borrow().base_type, ElementType::Builtin(b) if b.name == "MenuBar") {
|
|
if menu_bar.is_some() {
|
|
diag.push_error("Only one MenuBar is allowed in a Window".into(), &*x.borrow());
|
|
} else {
|
|
menu_bar = Some(x.clone());
|
|
}
|
|
false
|
|
} else {
|
|
true
|
|
}
|
|
});
|
|
|
|
let Some(menu_bar) = menu_bar else {
|
|
return false;
|
|
};
|
|
if menu_bar.borrow().repeated.is_some() {
|
|
diag.push_error(
|
|
"MenuBar cannot be in a conditional or repeated element".into(),
|
|
&*menu_bar.borrow(),
|
|
);
|
|
}
|
|
assert!(
|
|
menu_bar.borrow().children.is_empty() || diag.has_errors(),
|
|
"MenuBar element can't have children"
|
|
);
|
|
|
|
let menubar_impl = Element {
|
|
id: format_smolstr!("{}-menulayout", window.id),
|
|
base_type: components.menubar_impl.clone(),
|
|
enclosing_component: window.enclosing_component.clone(),
|
|
repeated: Some(crate::object_tree::RepeatedElementInfo {
|
|
model: Expression::UnaryOp {
|
|
op: '!',
|
|
sub: Expression::FunctionCall {
|
|
function: Expression::BuiltinFunctionReference(
|
|
BuiltinFunction::SupportsNativeMenuBar,
|
|
None,
|
|
)
|
|
.into(),
|
|
arguments: vec![],
|
|
source_location: None,
|
|
}
|
|
.into(),
|
|
},
|
|
model_data_id: SmolStr::default(),
|
|
index_id: SmolStr::default(),
|
|
is_conditional_element: true,
|
|
is_listview: None,
|
|
}),
|
|
..Default::default()
|
|
}
|
|
.make_rc();
|
|
|
|
// Create a child that contains all the children of the window but the menubar
|
|
let child = Element {
|
|
id: format_smolstr!("{}-child", window.id),
|
|
base_type: components.empty.clone(),
|
|
enclosing_component: window.enclosing_component.clone(),
|
|
children: std::mem::take(&mut window.children),
|
|
..Default::default()
|
|
}
|
|
.make_rc();
|
|
|
|
const HEIGHT: &str = "height";
|
|
let child_height = NamedReference::new(&child, SmolStr::new_static(HEIGHT));
|
|
|
|
const ENTRIES: &str = "entries";
|
|
const SUB_MENU: &str = "sub-menu";
|
|
const ACTIVATE: &str = "activated";
|
|
|
|
let source_location = Some(menu_bar.borrow().to_source_location());
|
|
|
|
for prop in [ENTRIES, SUB_MENU, ACTIVATE] {
|
|
// materialize the properties and callbacks
|
|
let ty = components.menubar_impl.lookup_property(prop).property_type;
|
|
assert_ne!(ty, Type::Invalid, "Can't lookup type for {prop}");
|
|
let nr = NamedReference::new(&menu_bar, SmolStr::new_static(prop));
|
|
let forward_expr = if let Type::Callback(cb) = &ty {
|
|
Expression::FunctionCall {
|
|
function: Expression::CallbackReference(nr, None).into(),
|
|
arguments: cb
|
|
.args
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, ty)| Expression::FunctionParameterReference {
|
|
index,
|
|
ty: ty.clone(),
|
|
})
|
|
.collect(),
|
|
source_location: source_location.clone(),
|
|
}
|
|
} else {
|
|
Expression::PropertyReference(nr)
|
|
};
|
|
menubar_impl.borrow_mut().bindings.insert(prop.into(), RefCell::new(forward_expr.into()));
|
|
let old = menu_bar
|
|
.borrow_mut()
|
|
.property_declarations
|
|
.insert(prop.into(), PropertyDeclaration { property_type: ty, ..Default::default() });
|
|
assert!(old.is_none(), "{prop} already exists");
|
|
}
|
|
|
|
// Transform the MenuBar in a layout
|
|
menu_bar.borrow_mut().base_type = components.vertical_layout.clone();
|
|
menu_bar.borrow_mut().children = vec![menubar_impl, child];
|
|
|
|
let setup_menubar = Expression::FunctionCall {
|
|
function: Expression::BuiltinFunctionReference(
|
|
BuiltinFunction::SetupNativeMenuBar,
|
|
source_location.clone(),
|
|
)
|
|
.into(),
|
|
arguments: vec![
|
|
Expression::PropertyReference(NamedReference::new(
|
|
&menu_bar,
|
|
SmolStr::new_static(ENTRIES),
|
|
)),
|
|
Expression::CallbackReference(
|
|
NamedReference::new(&menu_bar, SmolStr::new_static(SUB_MENU)),
|
|
None,
|
|
),
|
|
Expression::CallbackReference(
|
|
NamedReference::new(&menu_bar, SmolStr::new_static(ACTIVATE)),
|
|
None,
|
|
),
|
|
],
|
|
source_location: source_location.clone(),
|
|
};
|
|
|
|
window.children.push(menu_bar);
|
|
let component = window.enclosing_component.upgrade().unwrap();
|
|
drop(window);
|
|
|
|
// Rename every access to `root.height` into `child.height`
|
|
let win_height = NamedReference::new(win, SmolStr::new_static(HEIGHT));
|
|
crate::object_tree::visit_all_named_references(&component, &mut |nr| {
|
|
if nr == &win_height {
|
|
*nr = child_height.clone()
|
|
}
|
|
});
|
|
// except for the actual geometry
|
|
win.borrow_mut().geometry_props.as_mut().unwrap().height = win_height;
|
|
|
|
component.init_code.borrow_mut().constructor_code.push(setup_menubar.into());
|
|
|
|
true
|
|
}
|