mirror of
https://github.com/slint-ui/slint.git
synced 2025-07-24 05:26:29 +00:00

This type is poorly implemented and not documented. Let's remove it for now. It shall remain available in the git history in the event of a resurrection.
732 lines
28 KiB
Rust
732 lines
28 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
|
|
|
//! Passe that compute the layout constraint
|
|
|
|
use lyon_path::geom::euclid::approxeq::ApproxEq;
|
|
|
|
use crate::diagnostics::BuildDiagnostics;
|
|
use crate::diagnostics::Spanned;
|
|
use crate::expression_tree::*;
|
|
use crate::langtype::ElementType;
|
|
use crate::langtype::Type;
|
|
use crate::layout::*;
|
|
use crate::object_tree::*;
|
|
use crate::typeloader::TypeLoader;
|
|
use crate::typeregister::TypeRegister;
|
|
use std::cell::RefCell;
|
|
use std::collections::HashSet;
|
|
use std::rc::Rc;
|
|
|
|
pub async fn lower_layouts(
|
|
component: &Rc<Component>,
|
|
type_loader: &mut TypeLoader,
|
|
diag: &mut BuildDiagnostics,
|
|
) {
|
|
// Ignore import errors
|
|
let mut build_diags_to_ignore = crate::diagnostics::BuildDiagnostics::default();
|
|
let style_metrics = type_loader
|
|
.import_component("std-widgets.slint", "StyleMetrics", &mut build_diags_to_ignore)
|
|
.await;
|
|
|
|
*component.root_constraints.borrow_mut() =
|
|
LayoutConstraints::new(&component.root_element, diag);
|
|
|
|
recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
|
|
let component = elem.borrow().enclosing_component.upgrade().unwrap();
|
|
lower_element_layout(
|
|
&component,
|
|
elem,
|
|
&type_loader.global_type_registry.borrow(),
|
|
&style_metrics,
|
|
diag,
|
|
);
|
|
check_no_layout_properties(elem, diag);
|
|
});
|
|
}
|
|
|
|
fn lower_element_layout(
|
|
component: &Rc<Component>,
|
|
elem: &ElementRc,
|
|
type_register: &TypeRegister,
|
|
style_metrics: &Option<Rc<Component>>,
|
|
diag: &mut BuildDiagnostics,
|
|
) {
|
|
let base_type = if let ElementType::Builtin(base_type) = &elem.borrow().base_type {
|
|
base_type.clone()
|
|
} else {
|
|
return;
|
|
};
|
|
match base_type.name.as_str() {
|
|
"Row" => panic!("Error caught at element lookup time"),
|
|
"GridLayout" => lower_grid_layout(component, elem, diag),
|
|
"HorizontalLayout" => lower_box_layout(elem, diag, Orientation::Horizontal),
|
|
"VerticalLayout" => lower_box_layout(elem, diag, Orientation::Vertical),
|
|
"Dialog" => {
|
|
lower_dialog_layout(elem, style_metrics, diag);
|
|
return; // the Dialog stays in the tree as a Dialog
|
|
}
|
|
_ => return,
|
|
};
|
|
|
|
{
|
|
let mut elem = elem.borrow_mut();
|
|
let elem = &mut *elem;
|
|
let prev_base = std::mem::replace(&mut elem.base_type, type_register.empty_type());
|
|
// Create fake properties for the layout properties
|
|
for p in elem.bindings.keys() {
|
|
if !elem.base_type.lookup_property(p).is_valid()
|
|
&& !elem.property_declarations.contains_key(p)
|
|
{
|
|
let ty = prev_base.lookup_property(p).property_type;
|
|
if ty != Type::Invalid {
|
|
elem.property_declarations.insert(p.into(), ty.into());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn is_layout_element(element: &ElementRc) -> bool {
|
|
matches!(&element.borrow().base_type, ElementType::Builtin(n) if n.name == "GridLayout" || n.name == "HorizontalLayout" || n.name == "VerticalLayout")
|
|
}
|
|
|
|
fn lower_grid_layout(
|
|
component: &Rc<Component>,
|
|
grid_layout_element: &ElementRc,
|
|
diag: &mut BuildDiagnostics,
|
|
) {
|
|
let mut grid = GridLayout {
|
|
elems: Default::default(),
|
|
geometry: LayoutGeometry::new(grid_layout_element),
|
|
dialog_button_roles: None,
|
|
};
|
|
|
|
let layout_cache_prop_h =
|
|
create_new_prop(grid_layout_element, "layout-cache-h", Type::LayoutCache);
|
|
let layout_cache_prop_v =
|
|
create_new_prop(grid_layout_element, "layout-cache-v", Type::LayoutCache);
|
|
let layout_info_prop_h =
|
|
create_new_prop(grid_layout_element, "layoutinfo-h", layout_info_type());
|
|
let layout_info_prop_v =
|
|
create_new_prop(grid_layout_element, "layoutinfo-v", layout_info_type());
|
|
|
|
let mut row = 0;
|
|
let mut col = 0;
|
|
|
|
let layout_children = std::mem::take(&mut grid_layout_element.borrow_mut().children);
|
|
let mut collected_children = Vec::new();
|
|
for layout_child in layout_children {
|
|
let is_row = if let ElementType::Builtin(be) = &layout_child.borrow().base_type {
|
|
be.name == "Row"
|
|
} else {
|
|
false
|
|
};
|
|
if is_row {
|
|
if col > 0 {
|
|
row += 1;
|
|
col = 0;
|
|
}
|
|
let row_children = std::mem::take(&mut layout_child.borrow_mut().children);
|
|
for x in row_children {
|
|
grid.add_element(
|
|
&x,
|
|
(&mut row, &mut col),
|
|
&layout_cache_prop_h,
|
|
&layout_cache_prop_v,
|
|
diag,
|
|
);
|
|
col += 1;
|
|
collected_children.push(x);
|
|
}
|
|
if col > 0 {
|
|
row += 1;
|
|
col = 0;
|
|
}
|
|
component.optimized_elements.borrow_mut().push(layout_child);
|
|
} else {
|
|
grid.add_element(
|
|
&layout_child,
|
|
(&mut row, &mut col),
|
|
&layout_cache_prop_h,
|
|
&layout_cache_prop_v,
|
|
diag,
|
|
);
|
|
col += 1;
|
|
collected_children.push(layout_child);
|
|
}
|
|
}
|
|
grid_layout_element.borrow_mut().children = collected_children;
|
|
let span = grid_layout_element.borrow().to_source_location();
|
|
layout_cache_prop_h.element().borrow_mut().bindings.insert(
|
|
layout_cache_prop_h.name().into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Horizontal),
|
|
span.clone(),
|
|
)
|
|
.into(),
|
|
);
|
|
layout_cache_prop_v.element().borrow_mut().bindings.insert(
|
|
layout_cache_prop_v.name().into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Vertical),
|
|
span.clone(),
|
|
)
|
|
.into(),
|
|
);
|
|
layout_info_prop_h.element().borrow_mut().bindings.insert(
|
|
layout_info_prop_h.name().into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::ComputeLayoutInfo(
|
|
Layout::GridLayout(grid.clone()),
|
|
Orientation::Horizontal,
|
|
),
|
|
span.clone(),
|
|
)
|
|
.into(),
|
|
);
|
|
layout_info_prop_v.element().borrow_mut().bindings.insert(
|
|
layout_info_prop_v.name().into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::ComputeLayoutInfo(Layout::GridLayout(grid), Orientation::Vertical),
|
|
span,
|
|
)
|
|
.into(),
|
|
);
|
|
grid_layout_element.borrow_mut().layout_info_prop =
|
|
Some((layout_info_prop_h, layout_info_prop_v));
|
|
}
|
|
|
|
impl GridLayout {
|
|
fn add_element(
|
|
&mut self,
|
|
item_element: &ElementRc,
|
|
(row, col): (&mut u16, &mut u16),
|
|
layout_cache_prop_h: &NamedReference,
|
|
layout_cache_prop_v: &NamedReference,
|
|
diag: &mut BuildDiagnostics,
|
|
) {
|
|
let mut get_const_value = |name: &str| {
|
|
item_element
|
|
.borrow_mut()
|
|
.bindings
|
|
.remove(name)
|
|
.and_then(|e| eval_const_expr(&e.borrow().expression, name, &*e.borrow(), diag))
|
|
};
|
|
let colspan = get_const_value("colspan").unwrap_or(1);
|
|
let rowspan = get_const_value("rowspan").unwrap_or(1);
|
|
if let Some(r) = get_const_value("row") {
|
|
*row = r;
|
|
*col = 0;
|
|
}
|
|
if let Some(c) = get_const_value("col") {
|
|
*col = c;
|
|
}
|
|
|
|
self.add_element_with_coord(
|
|
item_element,
|
|
(*row, *col),
|
|
(rowspan, colspan),
|
|
layout_cache_prop_h,
|
|
layout_cache_prop_v,
|
|
diag,
|
|
)
|
|
}
|
|
|
|
fn add_element_with_coord(
|
|
&mut self,
|
|
item_element: &ElementRc,
|
|
(row, col): (u16, u16),
|
|
(rowspan, colspan): (u16, u16),
|
|
layout_cache_prop_h: &NamedReference,
|
|
layout_cache_prop_v: &NamedReference,
|
|
diag: &mut BuildDiagnostics,
|
|
) {
|
|
let index = self.elems.len();
|
|
if let Some(layout_item) = create_layout_item(item_element, diag) {
|
|
if layout_item.repeater_index.is_some() {
|
|
diag.push_error(
|
|
"'if' or 'for' expressions are not currently supported in grid layouts"
|
|
.to_string(),
|
|
&*item_element.borrow(),
|
|
);
|
|
return;
|
|
}
|
|
|
|
let e = &layout_item.elem;
|
|
set_prop_from_cache(e, "x", layout_cache_prop_h, index * 2, &None, diag);
|
|
if !layout_item.item.constraints.fixed_width {
|
|
set_prop_from_cache(e, "width", layout_cache_prop_h, index * 2 + 1, &None, diag);
|
|
}
|
|
set_prop_from_cache(e, "y", layout_cache_prop_v, index * 2, &None, diag);
|
|
if !layout_item.item.constraints.fixed_height {
|
|
set_prop_from_cache(e, "height", layout_cache_prop_v, index * 2 + 1, &None, diag);
|
|
}
|
|
|
|
self.elems.push(GridLayoutElement {
|
|
col,
|
|
row,
|
|
colspan,
|
|
rowspan,
|
|
item: layout_item.item,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lower_box_layout(
|
|
layout_element: &ElementRc,
|
|
diag: &mut BuildDiagnostics,
|
|
orientation: Orientation,
|
|
) {
|
|
let mut layout = BoxLayout {
|
|
orientation,
|
|
elems: Default::default(),
|
|
geometry: LayoutGeometry::new(layout_element),
|
|
};
|
|
|
|
let layout_cache_prop = create_new_prop(layout_element, "layout-cache", Type::LayoutCache);
|
|
let layout_info_prop_v = create_new_prop(layout_element, "layoutinfo-v", layout_info_type());
|
|
let layout_info_prop_h = create_new_prop(layout_element, "layoutinfo-h", layout_info_type());
|
|
|
|
let layout_children = std::mem::take(&mut layout_element.borrow_mut().children);
|
|
|
|
let (begin_padding, end_padding) = match orientation {
|
|
Orientation::Horizontal => (&layout.geometry.padding.top, &layout.geometry.padding.bottom),
|
|
Orientation::Vertical => (&layout.geometry.padding.left, &layout.geometry.padding.right),
|
|
};
|
|
let (pos, size, pad, ortho) = match orientation {
|
|
Orientation::Horizontal => ("x", "width", "y", "height"),
|
|
Orientation::Vertical => ("y", "height", "x", "width"),
|
|
};
|
|
let pad_expr = begin_padding.clone().map(Expression::PropertyReference);
|
|
let mut size_expr = Expression::PropertyReference(NamedReference::new(layout_element, ortho));
|
|
if let Some(p) = begin_padding {
|
|
size_expr = Expression::BinaryExpression {
|
|
lhs: Box::new(std::mem::take(&mut size_expr)),
|
|
rhs: Box::new(Expression::PropertyReference(p.clone())),
|
|
op: '-',
|
|
}
|
|
}
|
|
if let Some(p) = end_padding {
|
|
size_expr = Expression::BinaryExpression {
|
|
lhs: Box::new(std::mem::take(&mut size_expr)),
|
|
rhs: Box::new(Expression::PropertyReference(p.clone())),
|
|
op: '-',
|
|
}
|
|
}
|
|
|
|
for layout_child in &layout_children {
|
|
if let Some(item) = create_layout_item(layout_child, diag) {
|
|
let index = layout.elems.len() * 2;
|
|
let rep_idx = &item.repeater_index;
|
|
let (fixed_size, fixed_ortho) = match orientation {
|
|
Orientation::Horizontal => {
|
|
(item.item.constraints.fixed_width, item.item.constraints.fixed_height)
|
|
}
|
|
Orientation::Vertical => {
|
|
(item.item.constraints.fixed_height, item.item.constraints.fixed_width)
|
|
}
|
|
};
|
|
let actual_elem = &item.elem;
|
|
set_prop_from_cache(actual_elem, pos, &layout_cache_prop, index, rep_idx, diag);
|
|
if !fixed_size {
|
|
set_prop_from_cache(
|
|
actual_elem,
|
|
size,
|
|
&layout_cache_prop,
|
|
index + 1,
|
|
rep_idx,
|
|
diag,
|
|
);
|
|
}
|
|
if let Some(pad_expr) = pad_expr.clone() {
|
|
actual_elem.borrow_mut().bindings.insert(pad.into(), RefCell::new(pad_expr.into()));
|
|
}
|
|
if !fixed_ortho {
|
|
actual_elem
|
|
.borrow_mut()
|
|
.bindings
|
|
.insert(ortho.into(), RefCell::new(size_expr.clone().into()));
|
|
}
|
|
layout.elems.push(item.item);
|
|
}
|
|
}
|
|
layout_element.borrow_mut().children = layout_children;
|
|
let span = layout_element.borrow().to_source_location();
|
|
layout_cache_prop.element().borrow_mut().bindings.insert(
|
|
layout_cache_prop.name().into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::SolveLayout(Layout::BoxLayout(layout.clone()), orientation),
|
|
span.clone(),
|
|
)
|
|
.into(),
|
|
);
|
|
layout_info_prop_h.element().borrow_mut().bindings.insert(
|
|
layout_info_prop_h.name().into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::ComputeLayoutInfo(
|
|
Layout::BoxLayout(layout.clone()),
|
|
Orientation::Horizontal,
|
|
),
|
|
span.clone(),
|
|
)
|
|
.into(),
|
|
);
|
|
layout_info_prop_v.element().borrow_mut().bindings.insert(
|
|
layout_info_prop_v.name().into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::ComputeLayoutInfo(Layout::BoxLayout(layout), Orientation::Vertical),
|
|
span,
|
|
)
|
|
.into(),
|
|
);
|
|
layout_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
|
|
}
|
|
|
|
fn lower_dialog_layout(
|
|
dialog_element: &ElementRc,
|
|
style_metrics: &Option<Rc<Component>>,
|
|
diag: &mut BuildDiagnostics,
|
|
) {
|
|
let mut grid = GridLayout {
|
|
elems: Default::default(),
|
|
geometry: LayoutGeometry::new(dialog_element),
|
|
dialog_button_roles: None,
|
|
};
|
|
if let Some(metrics) = style_metrics.as_ref().map(|comp| &comp.root_element) {
|
|
grid.geometry.padding.bottom.get_or_insert(NamedReference::new(metrics, "layout-padding"));
|
|
grid.geometry.padding.top.get_or_insert(NamedReference::new(metrics, "layout-padding"));
|
|
grid.geometry.padding.left.get_or_insert(NamedReference::new(metrics, "layout-padding"));
|
|
grid.geometry.padding.right.get_or_insert(NamedReference::new(metrics, "layout-padding"));
|
|
grid.geometry.spacing.get_or_insert(NamedReference::new(metrics, "layout-spacing"));
|
|
}
|
|
|
|
let layout_cache_prop_h = create_new_prop(dialog_element, "layout-cache-h", Type::LayoutCache);
|
|
let layout_cache_prop_v = create_new_prop(dialog_element, "layout-cache-v", Type::LayoutCache);
|
|
let layout_info_prop_h = create_new_prop(dialog_element, "layoutinfo-h", layout_info_type());
|
|
let layout_info_prop_v = create_new_prop(dialog_element, "layoutinfo-v", layout_info_type());
|
|
|
|
let mut main_widget = None;
|
|
let mut button_roles = vec![];
|
|
let mut seen_buttons = HashSet::new();
|
|
let layout_children = std::mem::take(&mut dialog_element.borrow_mut().children);
|
|
for layout_child in &layout_children {
|
|
let dialog_button_role_binding =
|
|
layout_child.borrow_mut().bindings.remove("dialog-button-role");
|
|
let is_button = if let Some(role_binding) = dialog_button_role_binding {
|
|
let role_binding = role_binding.into_inner();
|
|
if let Expression::EnumerationValue(val) = &role_binding.expression {
|
|
let en = &val.enumeration;
|
|
debug_assert_eq!(en.name, "DialogButtonRole");
|
|
button_roles.push(en.values[val.value].clone());
|
|
if val.value == 0 {
|
|
diag.push_error(
|
|
"The `dialog-button-role` cannot be set explicitly to none".into(),
|
|
&role_binding,
|
|
);
|
|
}
|
|
} else {
|
|
diag.push_error(
|
|
"The `dialog-button-role` property must be known at compile-time".into(),
|
|
&role_binding,
|
|
);
|
|
}
|
|
true
|
|
} else if matches!(&layout_child.borrow().lookup_property("kind").property_type, Type::Enumeration(e) if e.name == "StandardButtonKind")
|
|
{
|
|
// layout_child is a StandardButton
|
|
match layout_child.borrow().bindings.get("kind") {
|
|
None => diag.push_error(
|
|
"The `kind` property of the StandardButton in a Dialog must be set".into(),
|
|
&*layout_child.borrow(),
|
|
),
|
|
Some(binding) => {
|
|
let binding = &*binding.borrow();
|
|
if let Expression::EnumerationValue(val) = &binding.expression {
|
|
let en = &val.enumeration;
|
|
debug_assert_eq!(en.name, "StandardButtonKind");
|
|
let kind = &en.values[val.value];
|
|
let role = match kind.as_str() {
|
|
"ok" => "accept",
|
|
"cancel" => "reject",
|
|
"apply" => "apply",
|
|
"close" => "reject",
|
|
"reset" => "reset",
|
|
"help" => "help",
|
|
"yes" => "accept",
|
|
"no" => "reject",
|
|
"abort" => "reject",
|
|
"retry" => "accept",
|
|
"ignore" => "accept",
|
|
_ => unreachable!(),
|
|
};
|
|
button_roles.push(role.into());
|
|
if !seen_buttons.insert(val.value) {
|
|
diag.push_error("Duplicated `kind`: There are two StandardButton in this Dialog with the same kind".into(), binding);
|
|
} else if Rc::ptr_eq(
|
|
dialog_element,
|
|
&dialog_element
|
|
.borrow()
|
|
.enclosing_component
|
|
.upgrade()
|
|
.unwrap()
|
|
.root_element,
|
|
) {
|
|
let clicked_ty =
|
|
layout_child.borrow().lookup_property("clicked").property_type;
|
|
if matches!(&clicked_ty, Type::Callback { .. })
|
|
&& layout_child.borrow().bindings.get("clicked").map_or(true, |c| {
|
|
matches!(c.borrow().expression, Expression::Invalid)
|
|
})
|
|
{
|
|
dialog_element
|
|
.borrow_mut()
|
|
.property_declarations
|
|
.entry(format!("{}-clicked", kind))
|
|
.or_insert_with(|| PropertyDeclaration {
|
|
property_type: clicked_ty,
|
|
node: None,
|
|
expose_in_public_api: true,
|
|
is_alias: Some(NamedReference::new(
|
|
layout_child,
|
|
"clicked",
|
|
)),
|
|
visibility: PropertyVisibility::InOut,
|
|
pure: None,
|
|
});
|
|
}
|
|
}
|
|
} else {
|
|
diag.push_error(
|
|
"The `kind` property of the StandardButton in a Dialog must be known at compile-time"
|
|
.into(),
|
|
binding,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
|
|
if is_button {
|
|
grid.add_element_with_coord(
|
|
layout_child,
|
|
(1, button_roles.len() as u16),
|
|
(1, 1),
|
|
&layout_cache_prop_h,
|
|
&layout_cache_prop_v,
|
|
diag,
|
|
);
|
|
} else if main_widget.is_some() {
|
|
diag.push_error(
|
|
"A Dialog can have only one child element that is not a StandardButton".into(),
|
|
&*layout_child.borrow(),
|
|
);
|
|
} else {
|
|
main_widget = Some(layout_child.clone())
|
|
}
|
|
}
|
|
dialog_element.borrow_mut().children = layout_children;
|
|
|
|
if let Some(main_widget) = main_widget {
|
|
grid.add_element_with_coord(
|
|
&main_widget,
|
|
(0, 0),
|
|
(1, button_roles.len() as u16 + 1),
|
|
&layout_cache_prop_h,
|
|
&layout_cache_prop_v,
|
|
diag,
|
|
);
|
|
} else {
|
|
diag.push_error(
|
|
"A Dialog must have a single child element that is not StandardButton".into(),
|
|
&*dialog_element.borrow(),
|
|
);
|
|
}
|
|
grid.dialog_button_roles = Some(button_roles);
|
|
|
|
let span = dialog_element.borrow().to_source_location();
|
|
layout_cache_prop_h.element().borrow_mut().bindings.insert(
|
|
layout_cache_prop_h.name().into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Horizontal),
|
|
span.clone(),
|
|
)
|
|
.into(),
|
|
);
|
|
layout_cache_prop_v.element().borrow_mut().bindings.insert(
|
|
layout_cache_prop_v.name().into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Vertical),
|
|
span.clone(),
|
|
)
|
|
.into(),
|
|
);
|
|
layout_info_prop_h.element().borrow_mut().bindings.insert(
|
|
layout_info_prop_h.name().into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::ComputeLayoutInfo(
|
|
Layout::GridLayout(grid.clone()),
|
|
Orientation::Horizontal,
|
|
),
|
|
span.clone(),
|
|
)
|
|
.into(),
|
|
);
|
|
layout_info_prop_v.element().borrow_mut().bindings.insert(
|
|
layout_info_prop_v.name().into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::ComputeLayoutInfo(Layout::GridLayout(grid), Orientation::Vertical),
|
|
span,
|
|
)
|
|
.into(),
|
|
);
|
|
dialog_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
|
|
}
|
|
|
|
struct CreateLayoutItemResult {
|
|
item: LayoutItem,
|
|
elem: ElementRc,
|
|
repeater_index: Option<Expression>,
|
|
}
|
|
|
|
/// Create a LayoutItem for the given `item_element` returns None is the layout is empty
|
|
fn create_layout_item(
|
|
item_element: &ElementRc,
|
|
diag: &mut BuildDiagnostics,
|
|
) -> Option<CreateLayoutItemResult> {
|
|
let fix_explicit_percent = |prop: &str, item: &ElementRc| {
|
|
if !item.borrow().bindings.get(prop).map_or(false, |b| b.borrow().ty() == Type::Percent) {
|
|
return;
|
|
}
|
|
let mut item = item.borrow_mut();
|
|
let b = item.bindings.remove(prop).unwrap();
|
|
item.bindings.insert(format!("min-{}", prop), b.clone());
|
|
item.bindings.insert(format!("max-{}", prop), b);
|
|
item.property_declarations.insert(
|
|
format!("min-{}", prop),
|
|
PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
|
|
);
|
|
item.property_declarations.insert(
|
|
format!("max-{}", prop),
|
|
PropertyDeclaration { property_type: Type::Percent, ..PropertyDeclaration::default() },
|
|
);
|
|
};
|
|
fix_explicit_percent("width", item_element);
|
|
fix_explicit_percent("height", item_element);
|
|
|
|
item_element.borrow_mut().child_of_layout = true;
|
|
let (repeater_index, actual_elem) = if let Some(r) = &item_element.borrow().repeated {
|
|
let rep_comp = item_element.borrow().base_type.as_component().clone();
|
|
fix_explicit_percent("width", &rep_comp.root_element);
|
|
fix_explicit_percent("height", &rep_comp.root_element);
|
|
|
|
*rep_comp.root_constraints.borrow_mut() =
|
|
LayoutConstraints::new(&rep_comp.root_element, diag);
|
|
rep_comp.root_element.borrow_mut().child_of_layout = true;
|
|
(
|
|
Some(if r.is_conditional_element {
|
|
Expression::NumberLiteral(0., Unit::None)
|
|
} else {
|
|
Expression::RepeaterIndexReference { element: Rc::downgrade(item_element) }
|
|
}),
|
|
rep_comp.root_element.clone(),
|
|
)
|
|
} else {
|
|
(None, item_element.clone())
|
|
};
|
|
|
|
let constraints = LayoutConstraints::new(&actual_elem, diag);
|
|
Some(CreateLayoutItemResult {
|
|
item: LayoutItem { element: item_element.clone(), constraints },
|
|
elem: actual_elem,
|
|
repeater_index,
|
|
})
|
|
}
|
|
|
|
fn set_prop_from_cache(
|
|
elem: &ElementRc,
|
|
prop: &str,
|
|
layout_cache_prop: &NamedReference,
|
|
index: usize,
|
|
repeater_index: &Option<Expression>,
|
|
diag: &mut BuildDiagnostics,
|
|
) {
|
|
let old = elem.borrow_mut().bindings.insert(
|
|
prop.into(),
|
|
BindingExpression::new_with_span(
|
|
Expression::LayoutCacheAccess {
|
|
layout_cache_prop: layout_cache_prop.clone(),
|
|
index,
|
|
repeater_index: repeater_index.as_ref().map(|x| Box::new(x.clone())),
|
|
},
|
|
layout_cache_prop.element().borrow().to_source_location(),
|
|
)
|
|
.into(),
|
|
);
|
|
if let Some(old) = old.map(RefCell::into_inner) {
|
|
diag.push_error(
|
|
format!("The property '{}' cannot be set for elements placed in a layout, because the layout is already setting it", prop),
|
|
&old,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn eval_const_expr(
|
|
expression: &Expression,
|
|
name: &str,
|
|
span: &dyn crate::diagnostics::Spanned,
|
|
diag: &mut BuildDiagnostics,
|
|
) -> Option<u16> {
|
|
match expression {
|
|
Expression::NumberLiteral(v, Unit::None) => {
|
|
if *v < 0. || *v > u16::MAX as f64 || !v.trunc().approx_eq(v) {
|
|
diag.push_error(format!("'{}' must be a positive integer", name), span);
|
|
None
|
|
} else {
|
|
Some(*v as u16)
|
|
}
|
|
}
|
|
Expression::Cast { from, .. } => eval_const_expr(from, name, span, diag),
|
|
_ => {
|
|
diag.push_error(format!("'{}' must be an integer literal", name), span);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create a new property based on the name. (it might get a different name if that property exist)
|
|
pub fn create_new_prop(elem: &ElementRc, tentative_name: &str, ty: Type) -> NamedReference {
|
|
let mut e = elem.borrow_mut();
|
|
if !e.lookup_property(tentative_name).is_valid() {
|
|
e.property_declarations.insert(tentative_name.into(), ty.into());
|
|
drop(e);
|
|
NamedReference::new(elem, tentative_name)
|
|
} else {
|
|
let mut counter = 0;
|
|
loop {
|
|
counter += 1;
|
|
let name = format!("{}{}", tentative_name, counter);
|
|
if !e.lookup_property(&name).is_valid() {
|
|
e.property_declarations.insert(name.clone(), ty.into());
|
|
drop(e);
|
|
return NamedReference::new(elem, &name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Checks that there is grid-layout specific properties left
|
|
fn check_no_layout_properties(item: &ElementRc, diag: &mut BuildDiagnostics) {
|
|
for (prop, expr) in item.borrow().bindings.iter() {
|
|
if matches!(prop.as_ref(), "col" | "row" | "colspan" | "rowspan") {
|
|
diag.push_error(format!("{} used outside of a GridLayout", prop), &*expr.borrow());
|
|
}
|
|
if matches!(prop.as_ref(), "dialog-button-role") {
|
|
diag.push_error(format!("{} used outside of a Dialog", prop), &*expr.borrow());
|
|
}
|
|
}
|
|
}
|