1use crate::diagnostics::{BuildDiagnostics, DiagnosticLevel, Spanned};
7use crate::expression_tree::*;
8use crate::langtype::{ElementType, PropertyLookupResult, Type};
9use crate::object_tree::{Component, ElementRc};
10
11use smol_str::{SmolStr, ToSmolStr, format_smolstr};
12
13use std::cell::RefCell;
14use std::rc::{Rc, Weak};
15
16#[derive(Clone, Debug, Copy, Eq, PartialEq)]
17pub enum Orientation {
18 Horizontal,
19 Vertical,
20}
21
22#[derive(Clone, Debug, derive_more::From)]
23pub enum Layout {
24 GridLayout(GridLayout),
25 BoxLayout(BoxLayout),
26}
27
28impl Layout {
29 pub fn rect(&self) -> &LayoutRect {
30 match self {
31 Layout::GridLayout(g) => &g.geometry.rect,
32 Layout::BoxLayout(g) => &g.geometry.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 }
40 }
41 pub fn geometry(&self) -> &LayoutGeometry {
42 match self {
43 Layout::GridLayout(l) => &l.geometry,
44 Layout::BoxLayout(l) => &l.geometry,
45 }
46 }
47}
48
49impl Layout {
50 pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
52 match self {
53 Layout::GridLayout(grid) => grid.visit_named_references(visitor),
54 Layout::BoxLayout(l) => l.visit_named_references(visitor),
55 }
56 }
57}
58
59#[derive(Debug, Default, Clone)]
61pub struct LayoutItem {
62 pub element: ElementRc,
63 pub constraints: LayoutConstraints,
64}
65
66impl LayoutItem {
67 pub fn rect(&self) -> LayoutRect {
68 let p = |unresolved_name: &str| {
69 let PropertyLookupResult { resolved_name, property_type, .. } =
70 self.element.borrow().lookup_property(unresolved_name);
71 if property_type == Type::LogicalLength {
72 Some(NamedReference::new(&self.element, resolved_name.to_smolstr()))
73 } else {
74 None
75 }
76 };
77 LayoutRect {
78 x_reference: p("x"),
79 y_reference: p("y"),
80 width_reference: if !self.constraints.fixed_width { p("width") } else { None },
81 height_reference: if !self.constraints.fixed_height { p("height") } else { None },
82 }
83 }
84}
85
86#[derive(Debug, Clone, Default)]
87pub struct LayoutRect {
88 pub width_reference: Option<NamedReference>,
89 pub height_reference: Option<NamedReference>,
90 pub x_reference: Option<NamedReference>,
91 pub y_reference: Option<NamedReference>,
92}
93
94impl LayoutRect {
95 pub fn install_on_element(element: &ElementRc) -> Self {
96 let install_prop =
97 |name: &'static str| Some(NamedReference::new(element, SmolStr::new_static(name)));
98
99 Self {
100 x_reference: install_prop("x"),
101 y_reference: install_prop("y"),
102 width_reference: install_prop("width"),
103 height_reference: install_prop("height"),
104 }
105 }
106
107 fn visit_named_references(&mut self, mut visitor: &mut impl FnMut(&mut NamedReference)) {
108 self.width_reference.as_mut().map(&mut visitor);
109 self.height_reference.as_mut().map(&mut visitor);
110 self.x_reference.as_mut().map(&mut visitor);
111 self.y_reference.as_mut().map(&mut visitor);
112 }
113
114 pub fn size_reference(&self, orientation: Orientation) -> Option<&NamedReference> {
115 match orientation {
116 Orientation::Horizontal => self.width_reference.as_ref(),
117 Orientation::Vertical => self.height_reference.as_ref(),
118 }
119 }
120}
121
122#[derive(Debug, Default, Clone)]
123pub struct LayoutConstraints {
124 pub min_width: Option<NamedReference>,
125 pub max_width: Option<NamedReference>,
126 pub min_height: Option<NamedReference>,
127 pub max_height: Option<NamedReference>,
128 pub preferred_width: Option<NamedReference>,
129 pub preferred_height: Option<NamedReference>,
130 pub horizontal_stretch: Option<NamedReference>,
131 pub vertical_stretch: Option<NamedReference>,
132 pub fixed_width: bool,
133 pub fixed_height: bool,
134}
135
136impl LayoutConstraints {
137 pub fn new(element: &ElementRc, diag: &mut BuildDiagnostics, level: DiagnosticLevel) -> Self {
142 let mut constraints = Self {
143 min_width: binding_reference(element, "min-width"),
144 max_width: binding_reference(element, "max-width"),
145 min_height: binding_reference(element, "min-height"),
146 max_height: binding_reference(element, "max-height"),
147 preferred_width: binding_reference(element, "preferred-width"),
148 preferred_height: binding_reference(element, "preferred-height"),
149 horizontal_stretch: binding_reference(element, "horizontal-stretch"),
150 vertical_stretch: binding_reference(element, "vertical-stretch"),
151 fixed_width: false,
152 fixed_height: false,
153 };
154 let mut apply_size_constraint =
155 |prop: &'static str,
156 binding: &BindingExpression,
157 enclosing1: &Weak<Component>,
158 depth,
159 op: &mut Option<NamedReference>| {
160 if let Some(other_prop) = op {
161 find_binding(
162 &other_prop.element(),
163 other_prop.name(),
164 |old, enclosing2, d2| {
165 if Weak::ptr_eq(enclosing1, enclosing2)
166 && old.priority.saturating_add(d2)
167 <= binding.priority.saturating_add(depth)
168 {
169 diag.push_diagnostic_with_span(
170 format!(
171 "Cannot specify both '{prop}' and '{}'",
172 other_prop.name()
173 ),
174 binding.to_source_location(),
175 level,
176 );
177 }
178 },
179 );
180 }
181 *op = Some(NamedReference::new(element, SmolStr::new_static(prop)))
182 };
183 find_binding(element, "height", |s, enclosing, depth| {
184 constraints.fixed_height = true;
185 apply_size_constraint("height", s, enclosing, depth, &mut constraints.min_height);
186 apply_size_constraint("height", s, enclosing, depth, &mut constraints.max_height);
187 });
188 find_binding(element, "width", |s, enclosing, depth| {
189 constraints.fixed_width = true;
190 if s.expression.ty() == Type::Percent {
191 apply_size_constraint("width", s, enclosing, depth, &mut constraints.min_width);
192 } else {
193 apply_size_constraint("width", s, enclosing, depth, &mut constraints.min_width);
194 apply_size_constraint("width", s, enclosing, depth, &mut constraints.max_width);
195 }
196 });
197
198 constraints
199 }
200
201 pub fn has_explicit_restrictions(&self, orientation: Orientation) -> bool {
202 match orientation {
203 Orientation::Horizontal => {
204 self.min_width.is_some()
205 || self.max_width.is_some()
206 || self.preferred_width.is_some()
207 || self.horizontal_stretch.is_some()
208 }
209 Orientation::Vertical => {
210 self.min_height.is_some()
211 || self.max_height.is_some()
212 || self.preferred_height.is_some()
213 || self.vertical_stretch.is_some()
214 }
215 }
216 }
217
218 pub fn for_each_restrictions(
220 &self,
221 orientation: Orientation,
222 ) -> impl Iterator<Item = (&NamedReference, &'static str)> {
223 let (min, max, preferred, stretch) = match orientation {
224 Orientation::Horizontal => {
225 (&self.min_width, &self.max_width, &self.preferred_width, &self.horizontal_stretch)
226 }
227 Orientation::Vertical => {
228 (&self.min_height, &self.max_height, &self.preferred_height, &self.vertical_stretch)
229 }
230 };
231 std::iter::empty()
232 .chain(min.as_ref().map(|x| {
233 if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
234 (x, "min")
235 } else {
236 (x, "min_percent")
237 }
238 }))
239 .chain(max.as_ref().map(|x| {
240 if Expression::PropertyReference(x.clone()).ty() != Type::Percent {
241 (x, "max")
242 } else {
243 (x, "max_percent")
244 }
245 }))
246 .chain(preferred.as_ref().map(|x| (x, "preferred")))
247 .chain(stretch.as_ref().map(|x| (x, "stretch")))
248 }
249
250 pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
251 if let Some(e) = self.max_width.as_mut() {
252 visitor(&mut *e);
253 }
254 if let Some(e) = self.min_width.as_mut() {
255 visitor(&mut *e);
256 }
257 if let Some(e) = self.max_height.as_mut() {
258 visitor(&mut *e);
259 }
260 if let Some(e) = self.min_height.as_mut() {
261 visitor(&mut *e);
262 }
263 if let Some(e) = self.preferred_width.as_mut() {
264 visitor(&mut *e);
265 }
266 if let Some(e) = self.preferred_height.as_mut() {
267 visitor(&mut *e);
268 }
269 if let Some(e) = self.horizontal_stretch.as_mut() {
270 visitor(&mut *e);
271 }
272 if let Some(e) = self.vertical_stretch.as_mut() {
273 visitor(&mut *e);
274 }
275 }
276}
277
278#[derive(Debug, Clone)]
279pub enum RowColExpr {
280 Named(NamedReference),
281 Literal(u16),
282 Auto,
283}
284
285#[derive(Debug, Clone)]
286pub struct GridLayoutCell {
287 pub new_row: bool,
288 pub col_expr: RowColExpr,
289 pub row_expr: RowColExpr,
290 pub colspan_expr: RowColExpr,
291 pub rowspan_expr: RowColExpr,
292 pub child_items: Option<Vec<LayoutItem>>, }
294
295impl GridLayoutCell {
296 pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
297 if let RowColExpr::Named(ref mut e) = self.col_expr {
298 visitor(e);
299 }
300 if let RowColExpr::Named(ref mut e) = self.row_expr {
301 visitor(e);
302 }
303 if let RowColExpr::Named(ref mut e) = self.colspan_expr {
304 visitor(e);
305 }
306 if let RowColExpr::Named(ref mut e) = self.rowspan_expr {
307 visitor(e);
308 }
309 if let Some(children) = &mut self.child_items {
310 for child in children {
311 child.constraints.visit_named_references(visitor);
312 }
313 }
314 }
315}
316
317#[derive(Debug, Clone)]
319pub struct GridLayoutElement {
320 pub cell: Rc<RefCell<GridLayoutCell>>,
322 pub item: LayoutItem,
323}
324
325impl GridLayoutElement {
326 pub fn span(&self, orientation: Orientation) -> RowColExpr {
327 let cell = self.cell.borrow();
328 match orientation {
329 Orientation::Horizontal => cell.colspan_expr.clone(),
330 Orientation::Vertical => cell.rowspan_expr.clone(),
331 }
332 }
333}
334
335#[derive(Debug, Clone)]
336pub struct Padding {
337 pub left: Option<NamedReference>,
338 pub right: Option<NamedReference>,
339 pub top: Option<NamedReference>,
340 pub bottom: Option<NamedReference>,
341}
342
343impl Padding {
344 fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
345 if let Some(e) = self.left.as_mut() {
346 visitor(&mut *e)
347 }
348 if let Some(e) = self.right.as_mut() {
349 visitor(&mut *e)
350 }
351 if let Some(e) = self.top.as_mut() {
352 visitor(&mut *e)
353 }
354 if let Some(e) = self.bottom.as_mut() {
355 visitor(&mut *e)
356 }
357 }
358
359 pub fn begin_end(&self, o: Orientation) -> (Option<&NamedReference>, Option<&NamedReference>) {
361 match o {
362 Orientation::Horizontal => (self.left.as_ref(), self.right.as_ref()),
363 Orientation::Vertical => (self.top.as_ref(), self.bottom.as_ref()),
364 }
365 }
366}
367
368#[derive(Debug, Clone)]
369pub struct Spacing {
370 pub horizontal: Option<NamedReference>,
371 pub vertical: Option<NamedReference>,
372}
373
374impl Spacing {
375 fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
376 if let Some(e) = self.horizontal.as_mut() {
377 visitor(&mut *e);
378 }
379 if let Some(e) = self.vertical.as_mut() {
380 visitor(&mut *e);
381 }
382 }
383
384 pub fn orientation(&self, o: Orientation) -> Option<&NamedReference> {
385 match o {
386 Orientation::Horizontal => self.horizontal.as_ref(),
387 Orientation::Vertical => self.vertical.as_ref(),
388 }
389 }
390}
391
392#[derive(Debug, Clone)]
393pub struct LayoutGeometry {
394 pub rect: LayoutRect,
395 pub spacing: Spacing,
396 pub alignment: Option<NamedReference>,
397 pub padding: Padding,
398}
399
400impl LayoutGeometry {
401 pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
402 self.rect.visit_named_references(visitor);
403 if let Some(e) = self.alignment.as_mut() {
404 visitor(&mut *e)
405 }
406 self.spacing.visit_named_references(visitor);
407 self.padding.visit_named_references(visitor);
408 }
409
410 pub fn new(layout_element: &ElementRc) -> Self {
411 let spacing = || binding_reference(layout_element, "spacing");
412 init_fake_property(layout_element, "spacing-horizontal", spacing);
413 init_fake_property(layout_element, "spacing-vertical", spacing);
414
415 let alignment = binding_reference(layout_element, "alignment");
416
417 let padding = || binding_reference(layout_element, "padding");
418 init_fake_property(layout_element, "padding-left", padding);
419 init_fake_property(layout_element, "padding-right", padding);
420 init_fake_property(layout_element, "padding-top", padding);
421 init_fake_property(layout_element, "padding-bottom", padding);
422
423 let padding = Padding {
424 left: binding_reference(layout_element, "padding-left").or_else(padding),
425 right: binding_reference(layout_element, "padding-right").or_else(padding),
426 top: binding_reference(layout_element, "padding-top").or_else(padding),
427 bottom: binding_reference(layout_element, "padding-bottom").or_else(padding),
428 };
429
430 let spacing = Spacing {
431 horizontal: binding_reference(layout_element, "spacing-horizontal").or_else(spacing),
432 vertical: binding_reference(layout_element, "spacing-vertical").or_else(spacing),
433 };
434
435 let rect = LayoutRect::install_on_element(layout_element);
436
437 Self { rect, spacing, padding, alignment }
438 }
439}
440
441fn find_binding<R>(
444 element: &ElementRc,
445 name: &str,
446 f: impl FnOnce(&BindingExpression, &Weak<Component>, i32) -> R,
447) -> Option<R> {
448 let mut element = element.clone();
449 let mut depth = 0;
450 loop {
451 if let Some(b) = element.borrow().bindings.get(name)
452 && b.borrow().has_binding()
453 {
454 return Some(f(&b.borrow(), &element.borrow().enclosing_component, depth));
455 }
456 let e = match &element.borrow().base_type {
457 ElementType::Component(base) => base.root_element.clone(),
458 _ => return None,
459 };
460 element = e;
461 depth += 1;
462 }
463}
464
465pub fn binding_reference(element: &ElementRc, name: &'static str) -> Option<NamedReference> {
467 find_binding(element, name, |_, _, _| NamedReference::new(element, SmolStr::new_static(name)))
468}
469
470fn init_fake_property(
471 grid_layout_element: &ElementRc,
472 name: &str,
473 lazy_default: impl Fn() -> Option<NamedReference>,
474) {
475 if grid_layout_element.borrow().property_declarations.contains_key(name)
476 && !grid_layout_element.borrow().bindings.contains_key(name)
477 && let Some(e) = lazy_default()
478 {
479 if e.name() == name && Rc::ptr_eq(&e.element(), grid_layout_element) {
480 return;
482 }
483 grid_layout_element
484 .borrow_mut()
485 .bindings
486 .insert(name.into(), RefCell::new(Expression::PropertyReference(e).into()));
487 }
488}
489
490#[derive(Debug, Clone)]
492pub struct GridLayout {
493 pub elems: Vec<GridLayoutElement>,
495
496 pub geometry: LayoutGeometry,
497
498 pub dialog_button_roles: Option<Vec<SmolStr>>,
501
502 pub uses_auto: bool,
504}
505
506impl GridLayout {
507 pub fn visit_rowcol_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
508 for elem in &mut self.elems {
509 let mut cell = elem.cell.borrow_mut();
510 if let RowColExpr::Named(ref mut e) = cell.col_expr {
511 visitor(e);
512 }
513 if let RowColExpr::Named(ref mut e) = cell.row_expr {
514 visitor(e);
515 }
516 if let RowColExpr::Named(ref mut e) = cell.colspan_expr {
517 visitor(e);
518 }
519 if let RowColExpr::Named(ref mut e) = cell.rowspan_expr {
520 visitor(e);
521 }
522 }
523 }
524
525 pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
526 self.visit_rowcol_named_references(visitor);
527 for layout_elem in &mut self.elems {
528 layout_elem.item.constraints.visit_named_references(visitor);
529 if let Some(child_items) = &mut layout_elem.cell.borrow_mut().child_items {
530 for child in child_items {
531 child.constraints.visit_named_references(visitor);
532 }
533 }
534 }
535 self.geometry.visit_named_references(visitor);
536 }
537}
538
539#[derive(Debug, Clone)]
541pub struct BoxLayout {
542 pub orientation: Orientation,
544 pub elems: Vec<LayoutItem>,
545 pub geometry: LayoutGeometry,
546}
547
548impl BoxLayout {
549 fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
550 for cell in &mut self.elems {
551 cell.constraints.visit_named_references(visitor);
552 }
553 self.geometry.visit_named_references(visitor);
554 }
555}
556
557pub fn implicit_layout_info_call(elem: &ElementRc, orientation: Orientation) -> Expression {
559 let mut elem_it = elem.clone();
560 loop {
561 return match &elem_it.clone().borrow().base_type {
562 ElementType::Component(base_comp) => {
563 match base_comp.root_element.borrow().layout_info_prop(orientation) {
564 Some(nr) => {
565 debug_assert!(Rc::ptr_eq(&nr.element(), &base_comp.root_element));
568 Expression::PropertyReference(NamedReference::new(elem, nr.name().clone()))
569 }
570 None => {
571 elem_it = base_comp.root_element.clone();
572 continue;
573 }
574 }
575 }
576 ElementType::Builtin(base_type)
577 if matches!(
578 base_type.name.as_str(),
579 "Rectangle"
580 | "Empty"
581 | "TouchArea"
582 | "FocusScope"
583 | "Opacity"
584 | "Layer"
585 | "BoxShadow"
586 | "Clip"
587 ) =>
588 {
589 Expression::Struct {
592 ty: crate::typeregister::layout_info_type(),
593 values: [("min", 0.), ("max", f32::MAX), ("preferred", 0.)]
594 .iter()
595 .map(|(s, v)| {
596 (SmolStr::new_static(s), Expression::NumberLiteral(*v as _, Unit::Px))
597 })
598 .chain(
599 [("min_percent", 0.), ("max_percent", 100.), ("stretch", 1.)]
600 .iter()
601 .map(|(s, v)| {
602 (
603 SmolStr::new_static(s),
604 Expression::NumberLiteral(*v, Unit::None),
605 )
606 }),
607 )
608 .collect(),
609 }
610 }
611 _ => Expression::FunctionCall {
612 function: BuiltinFunction::ImplicitLayoutInfo(orientation).into(),
613 arguments: vec![Expression::ElementReference(Rc::downgrade(elem))],
614 source_location: None,
615 },
616 };
617 }
618}
619
620pub fn create_new_prop(elem: &ElementRc, tentative_name: SmolStr, ty: Type) -> NamedReference {
622 let mut e = elem.borrow_mut();
623 if !e.lookup_property(&tentative_name).is_valid() {
624 e.property_declarations.insert(tentative_name.clone(), ty.into());
625 drop(e);
626 NamedReference::new(elem, tentative_name)
627 } else {
628 let mut counter = 0;
629 loop {
630 counter += 1;
631 let name = format_smolstr!("{}{}", tentative_name, counter);
632 if !e.lookup_property(&name).is_valid() {
633 e.property_declarations.insert(name.clone(), ty.into());
634 drop(e);
635 return NamedReference::new(elem, name);
636 }
637 }
638 }
639}
640
641pub fn is_layout(base_type: &ElementType) -> bool {
643 match base_type {
644 ElementType::Component(c) => is_layout(&c.root_element.borrow().base_type),
645 ElementType::Builtin(be) => {
646 matches!(be.name.as_str(), "GridLayout" | "HorizontalLayout" | "VerticalLayout")
647 }
648 _ => false,
649 }
650}