1use crate::diagnostics::BuildDiagnostics;
7use crate::expression_tree::*;
8use crate::langtype::{PropertyLookupResult, Type};
9use crate::object_tree::{Component, ElementRc};
10
11use std::cell::RefCell;
12use std::rc::{Rc, Weak};
13
14#[derive(Clone, Debug, Copy, Eq, PartialEq)]
15pub enum Orientation {
16 Horizontal,
17 Vertical,
18}
19
20#[derive(Clone, Debug, derive_more::From)]
21pub enum Layout {
22 GridLayout(GridLayout),
23 PathLayout(PathLayout),
24 BoxLayout(BoxLayout),
25}
26
27impl Layout {
28 pub fn rect(&self) -> &LayoutRect {
29 match self {
30 Layout::GridLayout(g) => &g.geometry.rect,
31 Layout::BoxLayout(g) => &g.geometry.rect,
32 Layout::PathLayout(p) => &p.rect,
33 }
34 }
35 pub fn rect_mut(&mut self) -> &mut LayoutRect {
36 match self {
37 Layout::GridLayout(g) => &mut g.geometry.rect,
38 Layout::BoxLayout(g) => &mut g.geometry.rect,
39 Layout::PathLayout(p) => &mut p.rect,
40 }
41 }
42 pub fn geometry(&self) -> Option<&LayoutGeometry> {
43 match self {
44 Layout::GridLayout(l) => Some(&l.geometry),
45 Layout::BoxLayout(l) => Some(&l.geometry),
46 Layout::PathLayout(_) => None,
47 }
48 }
49}
50
51impl Layout {
52 pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
54 match self {
55 Layout::GridLayout(grid) => grid.visit_named_references(visitor),
56 Layout::BoxLayout(l) => l.visit_named_references(visitor),
57 Layout::PathLayout(path) => path.visit_named_references(visitor),
58 }
59 }
60}
61
62#[derive(Debug, Default, Clone)]
64pub struct LayoutItem {
65 pub element: ElementRc,
66 pub constraints: LayoutConstraints,
67}
68
69impl LayoutItem {
70 pub fn rect(&self) -> LayoutRect {
71 let p = |unresolved_name: &str| {
72 let PropertyLookupResult { resolved_name, property_type } =
73 self.element.borrow().lookup_property(unresolved_name);
74 if property_type == Type::LogicalLength {
75 Some(NamedReference::new(&self.element, resolved_name.as_ref()))
76 } else {
77 None
78 }
79 };
80 LayoutRect {
81 x_reference: p("x"),
82 y_reference: p("y"),
83 width_reference: if !self.constraints.fixed_width { p("width") } else { None },
84 height_reference: if !self.constraints.fixed_height { p("height") } else { None },
85 }
86 }
87}
88
89#[derive(Debug, Clone, Default)]
90pub struct LayoutRect {
91 pub width_reference: Option<NamedReference>,
92 pub height_reference: Option<NamedReference>,
93 pub x_reference: Option<NamedReference>,
94 pub y_reference: Option<NamedReference>,
95}
96
97impl LayoutRect {
98 pub fn install_on_element(element: &ElementRc) -> Self {
99 let install_prop = |name: &str| Some(NamedReference::new(element, name));
100
101 Self {
102 x_reference: install_prop("x"),
103 y_reference: install_prop("y"),
104 width_reference: install_prop("width"),
105 height_reference: install_prop("height"),
106 }
107 }
108
109 fn visit_named_references(&mut self, mut visitor: &mut impl FnMut(&mut NamedReference)) {
110 self.width_reference.as_mut().map(&mut visitor);
111 self.height_reference.as_mut().map(&mut visitor);
112 self.x_reference.as_mut().map(&mut visitor);
113 self.y_reference.as_mut().map(&mut visitor);
114 }
115
116 pub fn size_reference(&self, orientation: Orientation) -> Option<&NamedReference> {
117 match orientation {
118 Orientation::Horizontal => self.width_reference.as_ref(),
119 Orientation::Vertical => self.height_reference.as_ref(),
120 }
121 }
122}
123
124#[derive(Debug, Default, Clone)]
125pub struct LayoutConstraints {
126 pub min_width: Option<NamedReference>,
127 pub max_width: Option<NamedReference>,
128 pub min_height: Option<NamedReference>,
129 pub max_height: Option<NamedReference>,
130 pub preferred_width: Option<NamedReference>,
131 pub preferred_height: Option<NamedReference>,
132 pub horizontal_stretch: Option<NamedReference>,
133 pub vertical_stretch: Option<NamedReference>,
134 pub fixed_width: bool,
135 pub fixed_height: bool,
136}
137
138impl LayoutConstraints {
139 pub fn new(element: &ElementRc, diag: &mut BuildDiagnostics) -> Self {
140 let mut constraints = Self {
141 min_width: binding_reference(element, "min-width"),
142 max_width: binding_reference(element, "max-width"),
143 min_height: binding_reference(element, "min-height"),
144 max_height: binding_reference(element, "max-height"),
145 preferred_width: binding_reference(element, "preferred-width"),
146 preferred_height: binding_reference(element, "preferred-height"),
147 horizontal_stretch: binding_reference(element, "horizontal-stretch"),
148 vertical_stretch: binding_reference(element, "vertical-stretch"),
149 fixed_width: false,
150 fixed_height: false,
151 };
152 let mut apply_size_constraint =
153 |prop,
154 binding: &BindingExpression,
155 enclosing1: &Weak<Component>,
156 depth,
157 op: &mut Option<NamedReference>| {
158 if let Some(other_prop) = op {
159 find_binding(
160 &other_prop.element(),
161 other_prop.name(),
162 |old, enclosing2, d2| {
163 if Weak::ptr_eq(enclosing1, enclosing2)
164 && old.priority.saturating_add(d2)
165 <= binding.priority.saturating_add(depth)
166 {
167 diag.push_error(
168 format!(
169 "Cannot specify both '{}' and '{}'",
170 prop,
171 other_prop.name()
172 ),
173 binding,
174 )
175 }
176 },
177 );
178 }
179 *op = Some(NamedReference::new(element, prop))
180 };
181 find_binding(element, "height", |s, enclosing, depth| {
182 constraints.fixed_height = true;
183 apply_size_constraint("height", s, enclosing, depth, &mut constraints.min_height);
184 apply_size_constraint("height", s, enclosing, depth, &mut constraints.max_height);
185 });
186 find_binding(element, "width", |s, enclosing, depth| {
187 if s.expression.ty() == Type::Percent {
188 apply_size_constraint("width", s, enclosing, depth, &mut constraints.min_width);
189 } else {
190 constraints.fixed_width = true;
191 apply_size_constraint("width", s, enclosing, depth, &mut constraints.min_width);
192 apply_size_constraint("width", s, enclosing, depth, &mut constraints.max_width);
193 }
194 });
195
196 constraints
197 }
198
199 pub fn has_explicit_restrictions(&self) -> bool {
200 self.min_width.is_some()
201 || self.max_width.is_some()
202 || self.min_height.is_some()
203 || self.max_width.is_some()
204 || self.max_height.is_some()
205 || self.preferred_height.is_some()
206 || self.preferred_width.is_some()
207 || self.horizontal_stretch.is_some()
208 || self.vertical_stretch.is_some()
209 }
210
211 pub fn for_each_restrictions<'a>(
213 &'a self,
214 orientation: Orientation,
215 ) -> impl Iterator<Item = (&NamedReference, &'static str)> {
216 let (min, max, preferred, stretch) = match orientation {
217 Orientation::Horizontal => {
218 (&self.min_width, &self.max_width, &self.preferred_width, &self.horizontal_stretch)
219 }
220 Orientation::Vertical => {
221 (&self.min_height, &self.max_height, &self.preferred_height, &self.vertical_stretch)
222 }
223 };
224 std::iter::empty()
225 .chain(min.as_ref().map(|x| {
226 if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
227 (x, "min")
228 } else {
229 (x, "min_percent")
230 }
231 }))
232 .chain(max.as_ref().map(|x| {
233 if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
234 (x, "max")
235 } else {
236 (x, "max_percent")
237 }
238 }))
239 .chain(preferred.as_ref().map(|x| (x, "preferred")))
240 .chain(stretch.as_ref().map(|x| (x, "stretch")))
241 }
242
243 pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
244 if let Some(e) = self.max_width.as_mut() {
245 visitor(&mut *e);
246 }
247 if let Some(e) = self.min_width.as_mut() {
248 visitor(&mut *e);
249 }
250 if let Some(e) = self.max_height.as_mut() {
251 visitor(&mut *e);
252 }
253 if let Some(e) = self.min_height.as_mut() {
254 visitor(&mut *e);
255 }
256 if let Some(e) = self.preferred_width.as_mut() {
257 visitor(&mut *e);
258 }
259 if let Some(e) = self.preferred_height.as_mut() {
260 visitor(&mut *e);
261 }
262 if let Some(e) = self.horizontal_stretch.as_mut() {
263 visitor(&mut *e);
264 }
265 if let Some(e) = self.vertical_stretch.as_mut() {
266 visitor(&mut *e);
267 }
268 }
269}
270
271#[derive(Debug, Clone)]
273pub struct GridLayoutElement {
274 pub col: u16,
275 pub row: u16,
276 pub colspan: u16,
277 pub rowspan: u16,
278 pub item: LayoutItem,
279}
280
281impl GridLayoutElement {
282 pub fn col_or_row_and_span(&self, orientation: Orientation) -> (u16, u16) {
283 match orientation {
284 Orientation::Horizontal => (self.col, self.colspan),
285 Orientation::Vertical => (self.row, self.rowspan),
286 }
287 }
288}
289
290#[derive(Debug, Clone)]
291pub struct Padding {
292 pub left: Option<NamedReference>,
293 pub right: Option<NamedReference>,
294 pub top: Option<NamedReference>,
295 pub bottom: Option<NamedReference>,
296}
297
298impl Padding {
299 fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
300 if let Some(e) = self.left.as_mut() {
301 visitor(&mut *e)
302 }
303 if let Some(e) = self.right.as_mut() {
304 visitor(&mut *e)
305 }
306 if let Some(e) = self.top.as_mut() {
307 visitor(&mut *e)
308 }
309 if let Some(e) = self.bottom.as_mut() {
310 visitor(&mut *e)
311 }
312 }
313
314 pub fn begin_end(&self, o: Orientation) -> (Option<&NamedReference>, Option<&NamedReference>) {
316 match o {
317 Orientation::Horizontal => (self.left.as_ref(), self.right.as_ref()),
318 Orientation::Vertical => (self.top.as_ref(), self.bottom.as_ref()),
319 }
320 }
321}
322
323#[derive(Debug, Clone)]
324pub struct LayoutGeometry {
325 pub rect: LayoutRect,
326 pub spacing: Option<NamedReference>,
327 pub alignment: Option<NamedReference>,
328 pub padding: Padding,
329}
330
331impl LayoutGeometry {
332 pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
333 self.rect.visit_named_references(visitor);
334 if let Some(e) = self.spacing.as_mut() {
335 visitor(&mut *e)
336 }
337 if let Some(e) = self.alignment.as_mut() {
338 visitor(&mut *e)
339 }
340 self.padding.visit_named_references(visitor);
341 }
342
343 pub fn new(layout_element: &ElementRc) -> Self {
344 let spacing = binding_reference(layout_element, "spacing");
345 let alignment = binding_reference(layout_element, "alignment");
346
347 let padding = || binding_reference(layout_element, "padding");
348 init_fake_property(layout_element, "padding-left", padding);
349 init_fake_property(layout_element, "padding-right", padding);
350 init_fake_property(layout_element, "padding-top", padding);
351 init_fake_property(layout_element, "padding-bottom", padding);
352
353 let padding = Padding {
354 left: binding_reference(layout_element, "padding-left").or_else(padding),
355 right: binding_reference(layout_element, "padding-right").or_else(padding),
356 top: binding_reference(layout_element, "padding-top").or_else(padding),
357 bottom: binding_reference(layout_element, "padding-bottom").or_else(padding),
358 };
359
360 let rect = LayoutRect::install_on_element(layout_element);
361
362 Self { rect, spacing, padding, alignment }
363 }
364}
365
366fn find_binding<R>(
369 element: &ElementRc,
370 name: &str,
371 f: impl FnOnce(&BindingExpression, &Weak<Component>, i32) -> R,
372) -> Option<R> {
373 let mut element = element.clone();
374 let mut depth = 0;
375 loop {
376 if let Some(b) = element.borrow().bindings.get(name) {
377 return Some(f(&b.borrow(), &element.borrow().enclosing_component, depth));
378 }
379 let e = match &element.borrow().base_type {
380 Type::Component(base) => base.root_element.clone(),
381 _ => return None,
382 };
383 element = e;
384 depth += 1;
385 }
386}
387
388fn binding_reference(element: &ElementRc, name: &str) -> Option<NamedReference> {
390 find_binding(element, name, |_, _, _| NamedReference::new(element, name))
391}
392
393fn init_fake_property(
394 grid_layout_element: &ElementRc,
395 name: &str,
396 lazy_default: impl Fn() -> Option<NamedReference>,
397) {
398 if grid_layout_element.borrow().property_declarations.contains_key(name)
399 && !grid_layout_element.borrow().bindings.contains_key(name)
400 {
401 if let Some(e) = lazy_default() {
402 if e.name() == name && Rc::ptr_eq(&e.element(), grid_layout_element) {
403 return;
405 }
406 grid_layout_element
407 .borrow_mut()
408 .bindings
409 .insert(name.to_owned(), RefCell::new(Expression::PropertyReference(e).into()));
410 }
411 }
412}
413
414#[derive(Debug, Clone)]
416pub struct GridLayout {
417 pub elems: Vec<GridLayoutElement>,
419
420 pub geometry: LayoutGeometry,
421
422 pub dialog_button_roles: Option<Vec<String>>,
425}
426
427impl GridLayout {
428 fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
429 for cell in &mut self.elems {
430 cell.item.constraints.visit_named_references(visitor);
431 }
432 self.geometry.visit_named_references(visitor);
433 }
434}
435
436#[derive(Debug, Clone)]
438pub struct BoxLayout {
439 pub orientation: Orientation,
441 pub elems: Vec<LayoutItem>,
442 pub geometry: LayoutGeometry,
443}
444
445impl BoxLayout {
446 fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
447 for cell in &mut self.elems {
448 cell.constraints.visit_named_references(visitor);
449 }
450 self.geometry.visit_named_references(visitor);
451 }
452}
453
454#[derive(Debug, Clone)]
456pub struct PathLayout {
457 pub path: Path,
458 pub elements: Vec<ElementRc>,
459 pub rect: LayoutRect,
460 pub offset_reference: Option<NamedReference>,
461}
462
463impl PathLayout {
464 fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
465 self.rect.visit_named_references(visitor);
466 self.offset_reference.as_mut().map(visitor);
467 }
468}
469
470pub fn layout_info_type() -> Type {
472 Type::Struct {
473 fields: ["min", "max", "preferred"]
474 .iter()
475 .map(|s| (s.to_string(), Type::LogicalLength))
476 .chain(
477 ["min_percent", "max_percent", "stretch"]
478 .iter()
479 .map(|s| (s.to_string(), Type::Float32)),
480 )
481 .collect(),
482 name: Some("LayoutInfo".into()),
483 node: None,
484 }
485}
486
487pub fn implicit_layout_info_call(elem: &ElementRc, orientation: Orientation) -> Expression {
489 let mut elem_it = elem.clone();
490 loop {
491 return match &elem_it.clone().borrow().base_type {
492 Type::Component(base_comp) => {
493 match base_comp.root_element.borrow().layout_info_prop(orientation) {
494 Some(nr) => {
495 debug_assert!(Rc::ptr_eq(&nr.element(), &base_comp.root_element));
498 Expression::PropertyReference(NamedReference::new(elem, nr.name()))
499 }
500 None => {
501 elem_it = base_comp.root_element.clone();
502 continue;
503 }
504 }
505 }
506 Type::Builtin(base_type) if base_type.name == "Rectangle" => {
507 Expression::Struct {
510 ty: layout_info_type(),
511 values: [("min", 0.), ("max", f32::MAX), ("preferred", 0.)]
512 .iter()
513 .map(|(s, v)| (s.to_string(), Expression::NumberLiteral(*v as _, Unit::Px)))
514 .chain(
515 [("min_percent", 0.), ("max_percent", 100.), ("stretch", 1.)]
516 .iter()
517 .map(|(s, v)| {
518 (s.to_string(), Expression::NumberLiteral(*v, Unit::None))
519 }),
520 )
521 .collect(),
522 }
523 }
524 _ => Expression::FunctionCall {
525 function: Box::new(Expression::BuiltinFunctionReference(
526 BuiltinFunction::ImplicitLayoutInfo(orientation),
527 None,
528 )),
529 arguments: vec![Expression::ElementReference(Rc::downgrade(elem))],
530 source_location: None,
531 },
532 };
533 }
534}