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::{format_smolstr, SmolStr, ToSmolStr};
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)]
280pub struct GridLayoutElement {
281 pub col: u16,
282 pub row: u16,
283 pub colspan: u16,
284 pub rowspan: u16,
285 pub item: LayoutItem,
286}
287
288impl GridLayoutElement {
289 pub fn col_or_row_and_span(&self, orientation: Orientation) -> (u16, u16) {
290 match orientation {
291 Orientation::Horizontal => (self.col, self.colspan),
292 Orientation::Vertical => (self.row, self.rowspan),
293 }
294 }
295}
296
297#[derive(Debug, Clone)]
298pub struct Padding {
299 pub left: Option<NamedReference>,
300 pub right: Option<NamedReference>,
301 pub top: Option<NamedReference>,
302 pub bottom: Option<NamedReference>,
303}
304
305impl Padding {
306 fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
307 if let Some(e) = self.left.as_mut() {
308 visitor(&mut *e)
309 }
310 if let Some(e) = self.right.as_mut() {
311 visitor(&mut *e)
312 }
313 if let Some(e) = self.top.as_mut() {
314 visitor(&mut *e)
315 }
316 if let Some(e) = self.bottom.as_mut() {
317 visitor(&mut *e)
318 }
319 }
320
321 pub fn begin_end(&self, o: Orientation) -> (Option<&NamedReference>, Option<&NamedReference>) {
323 match o {
324 Orientation::Horizontal => (self.left.as_ref(), self.right.as_ref()),
325 Orientation::Vertical => (self.top.as_ref(), self.bottom.as_ref()),
326 }
327 }
328}
329
330#[derive(Debug, Clone)]
331pub struct Spacing {
332 pub horizontal: Option<NamedReference>,
333 pub vertical: Option<NamedReference>,
334}
335
336impl Spacing {
337 fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
338 if let Some(e) = self.horizontal.as_mut() {
339 visitor(&mut *e);
340 }
341 if let Some(e) = self.vertical.as_mut() {
342 visitor(&mut *e);
343 }
344 }
345
346 pub fn orientation(&self, o: Orientation) -> Option<&NamedReference> {
347 match o {
348 Orientation::Horizontal => self.horizontal.as_ref(),
349 Orientation::Vertical => self.vertical.as_ref(),
350 }
351 }
352}
353
354#[derive(Debug, Clone)]
355pub struct LayoutGeometry {
356 pub rect: LayoutRect,
357 pub spacing: Spacing,
358 pub alignment: Option<NamedReference>,
359 pub padding: Padding,
360}
361
362impl LayoutGeometry {
363 pub fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
364 self.rect.visit_named_references(visitor);
365 if let Some(e) = self.alignment.as_mut() {
366 visitor(&mut *e)
367 }
368 self.spacing.visit_named_references(visitor);
369 self.padding.visit_named_references(visitor);
370 }
371
372 pub fn new(layout_element: &ElementRc) -> Self {
373 let spacing = || binding_reference(layout_element, "spacing");
374 init_fake_property(layout_element, "spacing-horizontal", spacing);
375 init_fake_property(layout_element, "spacing-vertical", spacing);
376
377 let alignment = binding_reference(layout_element, "alignment");
378
379 let padding = || binding_reference(layout_element, "padding");
380 init_fake_property(layout_element, "padding-left", padding);
381 init_fake_property(layout_element, "padding-right", padding);
382 init_fake_property(layout_element, "padding-top", padding);
383 init_fake_property(layout_element, "padding-bottom", padding);
384
385 let padding = Padding {
386 left: binding_reference(layout_element, "padding-left").or_else(padding),
387 right: binding_reference(layout_element, "padding-right").or_else(padding),
388 top: binding_reference(layout_element, "padding-top").or_else(padding),
389 bottom: binding_reference(layout_element, "padding-bottom").or_else(padding),
390 };
391
392 let spacing = Spacing {
393 horizontal: binding_reference(layout_element, "spacing-horizontal").or_else(spacing),
394 vertical: binding_reference(layout_element, "spacing-vertical").or_else(spacing),
395 };
396
397 let rect = LayoutRect::install_on_element(layout_element);
398
399 Self { rect, spacing, padding, alignment }
400 }
401}
402
403fn find_binding<R>(
406 element: &ElementRc,
407 name: &str,
408 f: impl FnOnce(&BindingExpression, &Weak<Component>, i32) -> R,
409) -> Option<R> {
410 let mut element = element.clone();
411 let mut depth = 0;
412 loop {
413 if let Some(b) = element.borrow().bindings.get(name) {
414 if b.borrow().has_binding() {
415 return Some(f(&b.borrow(), &element.borrow().enclosing_component, depth));
416 }
417 }
418 let e = match &element.borrow().base_type {
419 ElementType::Component(base) => base.root_element.clone(),
420 _ => return None,
421 };
422 element = e;
423 depth += 1;
424 }
425}
426
427fn binding_reference(element: &ElementRc, name: &'static str) -> Option<NamedReference> {
429 find_binding(element, name, |_, _, _| NamedReference::new(element, SmolStr::new_static(name)))
430}
431
432fn init_fake_property(
433 grid_layout_element: &ElementRc,
434 name: &str,
435 lazy_default: impl Fn() -> Option<NamedReference>,
436) {
437 if grid_layout_element.borrow().property_declarations.contains_key(name)
438 && !grid_layout_element.borrow().bindings.contains_key(name)
439 {
440 if let Some(e) = lazy_default() {
441 if e.name() == name && Rc::ptr_eq(&e.element(), grid_layout_element) {
442 return;
444 }
445 grid_layout_element
446 .borrow_mut()
447 .bindings
448 .insert(name.into(), RefCell::new(Expression::PropertyReference(e).into()));
449 }
450 }
451}
452
453#[derive(Debug, Clone)]
455pub struct GridLayout {
456 pub elems: Vec<GridLayoutElement>,
458
459 pub geometry: LayoutGeometry,
460
461 pub dialog_button_roles: Option<Vec<SmolStr>>,
464}
465
466impl GridLayout {
467 fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
468 for cell in &mut self.elems {
469 cell.item.constraints.visit_named_references(visitor);
470 }
471 self.geometry.visit_named_references(visitor);
472 }
473}
474
475#[derive(Debug, Clone)]
477pub struct BoxLayout {
478 pub orientation: Orientation,
480 pub elems: Vec<LayoutItem>,
481 pub geometry: LayoutGeometry,
482}
483
484impl BoxLayout {
485 fn visit_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) {
486 for cell in &mut self.elems {
487 cell.constraints.visit_named_references(visitor);
488 }
489 self.geometry.visit_named_references(visitor);
490 }
491}
492
493pub fn implicit_layout_info_call(elem: &ElementRc, orientation: Orientation) -> Expression {
495 let mut elem_it = elem.clone();
496 loop {
497 return match &elem_it.clone().borrow().base_type {
498 ElementType::Component(base_comp) => {
499 match base_comp.root_element.borrow().layout_info_prop(orientation) {
500 Some(nr) => {
501 debug_assert!(Rc::ptr_eq(&nr.element(), &base_comp.root_element));
504 Expression::PropertyReference(NamedReference::new(elem, nr.name().clone()))
505 }
506 None => {
507 elem_it = base_comp.root_element.clone();
508 continue;
509 }
510 }
511 }
512 ElementType::Builtin(base_type)
513 if matches!(
514 base_type.name.as_str(),
515 "Rectangle"
516 | "Empty"
517 | "TouchArea"
518 | "FocusScope"
519 | "Opacity"
520 | "Layer"
521 | "BoxShadow"
522 | "Clip"
523 ) =>
524 {
525 Expression::Struct {
528 ty: crate::typeregister::layout_info_type(),
529 values: [("min", 0.), ("max", f32::MAX), ("preferred", 0.)]
530 .iter()
531 .map(|(s, v)| {
532 (SmolStr::new_static(s), Expression::NumberLiteral(*v as _, Unit::Px))
533 })
534 .chain(
535 [("min_percent", 0.), ("max_percent", 100.), ("stretch", 1.)]
536 .iter()
537 .map(|(s, v)| {
538 (
539 SmolStr::new_static(s),
540 Expression::NumberLiteral(*v, Unit::None),
541 )
542 }),
543 )
544 .collect(),
545 }
546 }
547 _ => Expression::FunctionCall {
548 function: BuiltinFunction::ImplicitLayoutInfo(orientation).into(),
549 arguments: vec![Expression::ElementReference(Rc::downgrade(elem))],
550 source_location: None,
551 },
552 };
553 }
554}
555
556pub fn create_new_prop(elem: &ElementRc, tentative_name: SmolStr, ty: Type) -> NamedReference {
558 let mut e = elem.borrow_mut();
559 if !e.lookup_property(&tentative_name).is_valid() {
560 e.property_declarations.insert(tentative_name.clone(), ty.into());
561 drop(e);
562 NamedReference::new(elem, tentative_name)
563 } else {
564 let mut counter = 0;
565 loop {
566 counter += 1;
567 let name = format_smolstr!("{}{}", tentative_name, counter);
568 if !e.lookup_property(&name).is_valid() {
569 e.property_declarations.insert(name.clone(), ty.into());
570 drop(e);
571 return NamedReference::new(elem, name);
572 }
573 }
574 }
575}
576
577pub fn is_layout(base_type: &ElementType) -> bool {
579 match base_type {
580 ElementType::Component(c) => is_layout(&c.root_element.borrow().base_type),
581 ElementType::Builtin(be) => {
582 matches!(be.name.as_str(), "GridLayout" | "HorizontalLayout" | "VerticalLayout")
583 }
584 _ => false,
585 }
586}