mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-02 14:51:15 +00:00

Last commit uncovered some bug in which the second phase of inlining tries to inline children in the wrong place (This usually inlines zero children as it was already done in the first phase, but we still assert that the location is right)
169 lines
6.5 KiB
Rust
169 lines
6.5 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
|
|
|
|
//! Flickable pass
|
|
//!
|
|
//! The Flickable element is special in the sense that it has a viewport
|
|
//! which is not exposed. This passes create the viewport and fixes all property access
|
|
//!
|
|
//! It will also initialize proper geometry
|
|
//! This pass must be called before the materialize_fake_properties as it going to be generate
|
|
//! binding reference to fake properties
|
|
|
|
use crate::expression_tree::{BindingExpression, Expression, MinMaxOp, NamedReference};
|
|
use crate::langtype::{ElementType, NativeClass};
|
|
use crate::object_tree::{Component, Element, ElementRc};
|
|
use crate::typeregister::TypeRegister;
|
|
use std::rc::Rc;
|
|
|
|
pub fn is_flickable_element(element: &ElementRc) -> bool {
|
|
matches!(&element.borrow().base_type, ElementType::Builtin(n) if n.name == "Flickable")
|
|
}
|
|
|
|
pub fn handle_flickable(root_component: &Rc<Component>, tr: &TypeRegister) {
|
|
let mut native_empty = tr.empty_type().as_builtin().native_class.clone();
|
|
while let Some(p) = native_empty.parent.clone() {
|
|
native_empty = p;
|
|
}
|
|
|
|
crate::object_tree::recurse_elem_including_sub_components(
|
|
root_component,
|
|
&(),
|
|
&mut |elem: &ElementRc, _| {
|
|
if !is_flickable_element(elem) {
|
|
return;
|
|
}
|
|
|
|
fixup_geometry(elem);
|
|
create_viewport_element(elem, &native_empty);
|
|
},
|
|
)
|
|
}
|
|
|
|
fn create_viewport_element(flickable: &ElementRc, native_empty: &Rc<NativeClass>) {
|
|
let children = std::mem::take(&mut flickable.borrow_mut().children);
|
|
let viewport = Element::make_rc(Element {
|
|
id: format!("{}-viewport", flickable.borrow().id),
|
|
base_type: ElementType::Native(native_empty.clone()),
|
|
children,
|
|
enclosing_component: flickable.borrow().enclosing_component.clone(),
|
|
is_flickable_viewport: true,
|
|
..Element::default()
|
|
});
|
|
let element_type = flickable.borrow().base_type.clone();
|
|
for prop in element_type.as_builtin().properties.keys() {
|
|
if let Some(vp_prop) = prop.strip_prefix("viewport-") {
|
|
// bind the viewport's property to the flickable property, such as: `width <=> parent.viewport-width`
|
|
viewport.borrow_mut().bindings.insert(
|
|
vp_prop.to_owned(),
|
|
BindingExpression::new_two_way(NamedReference::new(flickable, prop)).into(),
|
|
);
|
|
}
|
|
}
|
|
viewport
|
|
.borrow()
|
|
.property_analysis
|
|
.borrow_mut()
|
|
.entry("y".into())
|
|
.or_default()
|
|
.is_set_externally = true;
|
|
viewport
|
|
.borrow()
|
|
.property_analysis
|
|
.borrow_mut()
|
|
.entry("x".into())
|
|
.or_default()
|
|
.is_set_externally = true;
|
|
|
|
let enclosing_component = flickable.borrow().enclosing_component.upgrade().unwrap();
|
|
if let Some((insertion_point, _, _)) =
|
|
&mut *enclosing_component.child_insertion_point.borrow_mut()
|
|
{
|
|
if std::rc::Rc::ptr_eq(insertion_point, flickable) {
|
|
*insertion_point = viewport.clone()
|
|
}
|
|
}
|
|
|
|
flickable.borrow_mut().children.push(viewport);
|
|
}
|
|
|
|
fn fixup_geometry(flickable_elem: &ElementRc) {
|
|
let forward_minmax_of = |prop: &str, op: MinMaxOp| {
|
|
set_binding_if_not_explicit(flickable_elem, prop, || {
|
|
flickable_elem
|
|
.borrow()
|
|
.children
|
|
.iter()
|
|
.filter(|x| is_layout(&x.borrow().base_type))
|
|
// FIXME: we should ideally add runtime code to merge layout info of all elements that are repeated (#407)
|
|
.filter(|x| x.borrow().repeated.is_none())
|
|
.map(|x| Expression::PropertyReference(NamedReference::new(x, prop)))
|
|
.reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, op))
|
|
})
|
|
};
|
|
|
|
if !flickable_elem.borrow().bindings.contains_key("height") {
|
|
forward_minmax_of("max-height", MinMaxOp::Min);
|
|
forward_minmax_of("preferred-height", MinMaxOp::Min);
|
|
}
|
|
if !flickable_elem.borrow().bindings.contains_key("width") {
|
|
forward_minmax_of("max-width", MinMaxOp::Min);
|
|
forward_minmax_of("preferred-width", MinMaxOp::Min);
|
|
}
|
|
set_binding_if_not_explicit(flickable_elem, "viewport-width", || {
|
|
Some(
|
|
flickable_elem
|
|
.borrow()
|
|
.children
|
|
.iter()
|
|
.filter(|x| is_layout(&x.borrow().base_type))
|
|
// FIXME: (#407)
|
|
.filter(|x| x.borrow().repeated.is_none())
|
|
.map(|x| Expression::PropertyReference(NamedReference::new(x, "min-width")))
|
|
.fold(
|
|
Expression::PropertyReference(NamedReference::new(flickable_elem, "width")),
|
|
|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max),
|
|
),
|
|
)
|
|
});
|
|
set_binding_if_not_explicit(flickable_elem, "viewport-height", || {
|
|
Some(
|
|
flickable_elem
|
|
.borrow()
|
|
.children
|
|
.iter()
|
|
.filter(|x| is_layout(&x.borrow().base_type))
|
|
// FIXME: (#407)
|
|
.filter(|x| x.borrow().repeated.is_none())
|
|
.map(|x| Expression::PropertyReference(NamedReference::new(x, "min-height")))
|
|
.fold(
|
|
Expression::PropertyReference(NamedReference::new(flickable_elem, "height")),
|
|
|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max),
|
|
),
|
|
)
|
|
});
|
|
}
|
|
|
|
/// Return true if this type is a layout that has constraints
|
|
fn is_layout(base_type: &ElementType) -> bool {
|
|
if let ElementType::Builtin(be) = base_type {
|
|
matches!(be.name.as_str(), "GridLayout" | "HorizontalLayout" | "VerticalLayout")
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Set the property binding on the given element to the given expression (computed lazily).
|
|
/// The parameter to the lazily calculation is the element's children
|
|
fn set_binding_if_not_explicit(
|
|
elem: &ElementRc,
|
|
property: &str,
|
|
expression: impl FnOnce() -> Option<Expression>,
|
|
) {
|
|
// we can't use `set_binding_if_not_set` directly because `expression()` may borrow `elem`
|
|
if elem.borrow().bindings.get(property).map_or(true, |b| !b.borrow().has_binding()) {
|
|
if let Some(e) = expression() {
|
|
elem.borrow_mut().set_binding_if_not_set(property.into(), || e);
|
|
}
|
|
}
|
|
}
|