i_slint_compiler/passes/
flickable.rs1use crate::expression_tree::{BindingExpression, Expression, MinMaxOp, NamedReference};
14use crate::langtype::{ElementType, NativeClass, Type};
15use crate::layout::is_layout;
16use crate::object_tree::{Component, Element, ElementRc};
17use crate::typeregister::TypeRegister;
18use core::cell::RefCell;
19use smol_str::{SmolStr, format_smolstr};
20use std::rc::Rc;
21
22pub fn is_flickable_element(element: &ElementRc) -> bool {
23 matches!(&element.borrow().base_type, ElementType::Builtin(n) if n.name == "Flickable")
24}
25
26pub fn handle_flickable(root_component: &Rc<Component>, tr: &TypeRegister) {
27 let mut native_empty = tr.empty_type().as_builtin().native_class.clone();
28 while let Some(p) = native_empty.parent.clone() {
29 native_empty = p;
30 }
31
32 crate::object_tree::recurse_elem_including_sub_components(
33 root_component,
34 &(),
35 &mut |elem: &ElementRc, _| {
36 if !is_flickable_element(elem) {
37 return;
38 }
39
40 fixup_geometry(elem);
41 create_viewport_element(elem, &native_empty);
42 },
43 )
44}
45
46fn create_viewport_element(flickable: &ElementRc, native_empty: &Rc<NativeClass>) {
47 let children = std::mem::take(&mut flickable.borrow_mut().children);
48 let is_listview = children
49 .iter()
50 .any(|c| c.borrow().repeated.as_ref().is_some_and(|r| r.is_listview.is_some()));
51
52 if is_listview {
53 for c in &children {
57 if c.borrow().repeated.is_none() {
58 continue;
60 }
61 let ElementType::Component(base) = c.borrow().base_type.clone() else { continue };
62 let inner_elem = &base.root_element;
63 let new_y = crate::layout::create_new_prop(
64 inner_elem,
65 SmolStr::new_static("actual-y"),
66 Type::LogicalLength,
67 );
68 new_y.mark_as_set();
69 inner_elem.borrow_mut().bindings.insert(
70 "y".into(),
71 RefCell::new(
72 Expression::BinaryExpression {
73 lhs: Expression::PropertyReference(new_y.clone()).into(),
74 rhs: Expression::PropertyReference(NamedReference::new(
75 flickable,
76 SmolStr::new_static("viewport-y"),
77 ))
78 .into(),
79 op: '-',
80 }
81 .into(),
82 ),
83 );
84 inner_elem.borrow_mut().geometry_props.as_mut().unwrap().y = new_y;
85 }
86 }
87
88 let viewport = Element::make_rc(Element {
89 id: format_smolstr!("{}-viewport", flickable.borrow().id),
90 base_type: ElementType::Native(native_empty.clone()),
91 children,
92 enclosing_component: flickable.borrow().enclosing_component.clone(),
93 is_flickable_viewport: true,
94 ..Element::default()
95 });
96 let element_type = flickable.borrow().base_type.clone();
97 for prop in element_type.as_builtin().properties.keys() {
98 if let Some(vp_prop) = prop.strip_prefix("viewport-") {
100 if is_listview && matches!(vp_prop, "y" | "height") {
101 continue;
103 }
104 viewport.borrow_mut().bindings.insert(
105 vp_prop.into(),
106 BindingExpression::new_two_way(NamedReference::new(flickable, prop.clone()).into())
107 .into(),
108 );
109 }
110 }
111 viewport
112 .borrow()
113 .property_analysis
114 .borrow_mut()
115 .entry("y".into())
116 .or_default()
117 .is_set_externally = true;
118 viewport
119 .borrow()
120 .property_analysis
121 .borrow_mut()
122 .entry("x".into())
123 .or_default()
124 .is_set_externally = true;
125
126 let enclosing_component = flickable.borrow().enclosing_component.upgrade().unwrap();
127 if let Some(insertion_point) = &mut *enclosing_component.child_insertion_point.borrow_mut()
128 && std::rc::Rc::ptr_eq(&insertion_point.parent, flickable)
129 {
130 insertion_point.parent = viewport.clone()
131 }
132
133 flickable.borrow_mut().children.push(viewport);
134}
135
136fn fixup_geometry(flickable_elem: &ElementRc) {
137 let forward_minmax_of = |prop: &'static str, op: MinMaxOp| {
138 set_binding_if_not_explicit(flickable_elem, prop, || {
139 flickable_elem
140 .borrow()
141 .children
142 .iter()
143 .filter(|x| is_layout(&x.borrow().base_type))
144 .filter(|x| x.borrow().repeated.is_none())
146 .map(|x| {
147 Expression::PropertyReference(NamedReference::new(x, SmolStr::new_static(prop)))
148 })
149 .reduce(|lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, op))
150 })
151 };
152
153 if !flickable_elem.borrow().bindings.contains_key("height") {
154 forward_minmax_of("max-height", MinMaxOp::Min);
155 forward_minmax_of("preferred-height", MinMaxOp::Min);
156 }
157 if !flickable_elem.borrow().bindings.contains_key("width") {
158 forward_minmax_of("max-width", MinMaxOp::Min);
159 forward_minmax_of("preferred-width", MinMaxOp::Min);
160 }
161 set_binding_if_not_explicit(flickable_elem, "viewport-width", || {
162 Some(
163 flickable_elem
164 .borrow()
165 .children
166 .iter()
167 .filter(|x| is_layout(&x.borrow().base_type))
168 .filter(|x| x.borrow().repeated.is_none())
170 .map(|x| {
171 Expression::PropertyReference(NamedReference::new(
172 x,
173 SmolStr::new_static("min-width"),
174 ))
175 })
176 .fold(
177 Expression::PropertyReference(NamedReference::new(
178 flickable_elem,
179 SmolStr::new_static("width"),
180 )),
181 |lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max),
182 ),
183 )
184 });
185 set_binding_if_not_explicit(flickable_elem, "viewport-height", || {
186 Some(
187 flickable_elem
188 .borrow()
189 .children
190 .iter()
191 .filter(|x| is_layout(&x.borrow().base_type))
192 .filter(|x| x.borrow().repeated.is_none())
194 .map(|x| {
195 Expression::PropertyReference(NamedReference::new(
196 x,
197 SmolStr::new_static("min-height"),
198 ))
199 })
200 .fold(
201 Expression::PropertyReference(NamedReference::new(
202 flickable_elem,
203 SmolStr::new_static("height"),
204 )),
205 |lhs, rhs| crate::builtin_macros::min_max_expression(lhs, rhs, MinMaxOp::Max),
206 ),
207 )
208 });
209}
210
211fn set_binding_if_not_explicit(
214 elem: &ElementRc,
215 property: &str,
216 expression: impl FnOnce() -> Option<Expression>,
217) {
218 if elem.borrow().bindings.get(property).is_none_or(|b| !b.borrow().has_binding())
220 && let Some(e) = expression()
221 {
222 elem.borrow_mut().set_binding_if_not_set(property.into(), || e);
223 }
224}