1use crate::{
2 color::Color,
3 geometry::{
4 rect::RectF,
5 vector::{vec2f, Vector2F},
6 },
7 json::{json, ToJson},
8 scene,
9 serde_json::Value,
10 AnyElement, Element, LayoutContext, Quad, SceneBuilder, SizeConstraint, View, ViewContext,
11};
12use std::{any::Any, borrow::Cow, f32, ops::Range};
13
14use self::length::Length;
15
16pub struct Node<V: View> {
17 style: NodeStyle,
18 content: Content<V>,
19}
20
21enum Content<V: View> {
22 Children(Vec<AnyElement<V>>),
23 Text(Cow<'static, str>),
24}
25
26impl<V: View> Default for Content<V> {
27 fn default() -> Self {
28 Self::Children(Vec::new())
29 }
30}
31
32pub fn column<V: View>() -> Node<V> {
33 Node::default()
34}
35
36pub fn row<V: View>() -> Node<V> {
37 Node {
38 style: NodeStyle {
39 axis: Axis3d::X,
40 ..Default::default()
41 },
42 content: Default::default(),
43 }
44}
45
46pub fn stack<V: View>() -> Node<V> {
47 Node {
48 style: NodeStyle {
49 axis: Axis3d::Z,
50 ..Default::default()
51 },
52 content: Default::default(),
53 }
54}
55
56impl<V: View> Default for Node<V> {
57 fn default() -> Self {
58 Self {
59 style: Default::default(),
60 content: Default::default(),
61 }
62 }
63}
64
65impl<V: View> Node<V> {
66 pub fn child(mut self, child: impl Element<V>) -> Self {
67 self.content.push(child.into_any());
68 self
69 }
70
71 pub fn children<I, E>(mut self, children: I) -> Self
72 where
73 I: IntoIterator<Item = E>,
74 E: Element<V>,
75 {
76 self.content
77 .extend(children.into_iter().map(|child| child.into_any()));
78 self
79 }
80
81 pub fn width(mut self, width: impl Into<Length>) -> Self {
82 self.style.width = width.into();
83 self
84 }
85
86 pub fn height(mut self, height: impl Into<Length>) -> Self {
87 self.style.height = height.into();
88 self
89 }
90
91 pub fn fill(mut self, fill: impl Into<Fill>) -> Self {
92 self.style.fill = fill.into();
93 self
94 }
95
96 pub fn row(mut self) -> Self {
97 self.style.axis = Axis3d::X;
98 self
99 }
100
101 pub fn stack(mut self) -> Self {
102 self.style.axis = Axis3d::Z;
103 self
104 }
105
106 fn layout_2d_children(
107 &mut self,
108 axis: Axis2d,
109 size: Vector2F,
110 view: &mut V,
111 cx: &mut LayoutContext<V>,
112 ) -> Vector2F {
113 let mut total_flex: Option<f32> = None;
114 let mut total_size = 0.0;
115 let mut cross_axis_max: f32 = 0.0;
116
117 // First pass: Layout non-flex children only
118 for child in &mut self.content {
119 let child_flex = child.metadata::<NodeStyle>().and_then(|style| match axis {
120 Axis2d::X => style.width.flex(),
121 Axis2d::Y => style.height.flex(),
122 });
123
124 if let Some(child_flex) = child_flex {
125 *total_flex.get_or_insert(0.) += child_flex;
126 } else {
127 match axis {
128 Axis2d::X => {
129 let child_constraint =
130 SizeConstraint::new(Vector2F::zero(), vec2f(f32::INFINITY, size.y()));
131 let child_size = child.layout(child_constraint, view, cx);
132 cross_axis_max = cross_axis_max.max(child_size.y());
133 total_size += child_size.x();
134 }
135 Axis2d::Y => {
136 let child_constraint =
137 SizeConstraint::new(Vector2F::zero(), vec2f(size.x(), f32::INFINITY));
138 let child_size = child.layout(child_constraint, view, cx);
139 cross_axis_max = cross_axis_max.max(child_size.x());
140 total_size += child_size.y();
141 }
142 }
143 }
144 }
145
146 let remaining_space = match axis {
147 Axis2d::X => size.x() - total_size,
148 Axis2d::Y => size.y() - total_size,
149 };
150
151 // Second pass: Layout flexible children
152 if let Some(total_flex) = total_flex {
153 if total_flex > 0. {
154 let space_per_flex = remaining_space.max(0.) / total_flex;
155
156 for child in &mut self.content {
157 let child_flex = child.metadata::<NodeStyle>().and_then(|style| match axis {
158 Axis2d::X => style.width.flex(),
159 Axis2d::Y => style.height.flex(),
160 });
161 if let Some(child_flex) = child_flex {
162 let child_max = space_per_flex * child_flex;
163 let mut child_constraint = SizeConstraint::new(Vector2F::zero(), size);
164 match axis {
165 Axis2d::X => {
166 child_constraint.min.set_x(0.0);
167 child_constraint.max.set_x(child_max);
168 }
169 Axis2d::Y => {
170 child_constraint.min.set_y(0.0);
171 child_constraint.max.set_y(child_max);
172 }
173 }
174
175 let child_size = child.layout(child_constraint, view, cx);
176 cross_axis_max = match axis {
177 Axis2d::X => {
178 total_size += child_size.x();
179 cross_axis_max.max(child_size.y())
180 }
181 Axis2d::Y => {
182 total_size += child_size.y();
183 cross_axis_max.max(child_size.x())
184 }
185 };
186 }
187 }
188 }
189 }
190
191 let size = match axis {
192 Axis2d::X => vec2f(total_size, cross_axis_max),
193 Axis2d::Y => vec2f(cross_axis_max, total_size),
194 };
195 size
196 }
197
198 fn paint_2d_children(
199 &mut self,
200 scene: &mut SceneBuilder,
201 axis: Axis2d,
202 bounds: RectF,
203 visible_bounds: RectF,
204 size_of_children: &mut Vector2F,
205 view: &mut V,
206 cx: &mut ViewContext<V>,
207 ) {
208 let parent_size = bounds.size();
209 let mut child_origin = bounds.origin();
210
211 // Align all children together along the primary axis
212 let mut align_horizontally = false;
213 let mut align_vertically = false;
214 match axis {
215 Axis2d::X => align_horizontally = true,
216 Axis2d::Y => align_vertically = true,
217 }
218 align_child(
219 &mut child_origin,
220 parent_size,
221 *size_of_children,
222 self.style.align.0,
223 align_horizontally,
224 align_vertically,
225 );
226
227 for child in &mut self.content {
228 // Align each child along the cross axis
229 align_horizontally = !align_horizontally;
230 align_vertically = !align_vertically;
231 align_child(
232 &mut child_origin,
233 parent_size,
234 child.size(),
235 self.style.align.0,
236 align_horizontally,
237 align_vertically,
238 );
239
240 child.paint(scene, child_origin, visible_bounds, view, cx);
241
242 // Advance along the primary axis by the size of this child
243 match axis {
244 Axis2d::X => child_origin.set_x(child_origin.x() + child.size().x()),
245 Axis2d::Y => child_origin.set_y(child_origin.y() + child.size().y()),
246 }
247 }
248 }
249
250 // fn layout_stacked_children(
251 // &mut self,
252 // constraint: SizeConstraint,
253 // view: &mut V,
254 // cx: &mut LayoutContext<V>,
255 // ) -> Vector2F {
256 // let mut size = Vector2F::zero();
257
258 // for child in &mut self.children {
259 // let child_size = child.layout(constraint, view, cx);
260 // size.set_x(size.x().max(child_size.x()));
261 // size.set_y(size.y().max(child_size.y()));
262 // }
263
264 // size
265 // }
266
267 fn inset_size(&self) -> Vector2F {
268 self.padding_size() + self.border_size() + self.margin_size()
269 }
270
271 fn margin_size(&self) -> Vector2F {
272 vec2f(
273 self.style.margin.left + self.style.margin.right,
274 self.style.margin.top + self.style.margin.bottom,
275 )
276 }
277
278 fn padding_size(&self) -> Vector2F {
279 vec2f(
280 self.style.padding.left + self.style.padding.right,
281 self.style.padding.top + self.style.padding.bottom,
282 )
283 }
284
285 fn border_size(&self) -> Vector2F {
286 let mut x = 0.0;
287 if self.style.border.left {
288 x += self.style.border.width;
289 }
290 if self.style.border.right {
291 x += self.style.border.width;
292 }
293
294 let mut y = 0.0;
295 if self.style.border.top {
296 y += self.style.border.width;
297 }
298 if self.style.border.bottom {
299 y += self.style.border.width;
300 }
301
302 vec2f(x, y)
303 }
304}
305
306impl<V: View> Element<V> for Node<V> {
307 type LayoutState = Vector2F; // Content size
308 type PaintState = ();
309
310 fn layout(
311 &mut self,
312 constraint: SizeConstraint,
313 view: &mut V,
314 cx: &mut LayoutContext<V>,
315 ) -> (Vector2F, Self::LayoutState) {
316 dbg!(constraint.max);
317
318 let mut size = Vector2F::zero();
319 let margin_size = self.margin_size();
320 match self.style.width {
321 Length::Hug => size.set_x(f32::INFINITY),
322 Length::Fixed(width) => size.set_x(width + margin_size.x()),
323 Length::Auto { min, max, .. } => size.set_x(constraint.max.x().max(min).min(max)),
324 }
325 match self.style.height {
326 Length::Hug => size.set_y(f32::INFINITY),
327 Length::Fixed(height) => size.set_y(height + margin_size.y()),
328 Length::Auto { min, max, .. } => size.set_y(constraint.max.y().max(min).min(max)),
329 }
330
331 // Impose horizontal constraints
332 if constraint.min.x().is_finite() {
333 size.set_x(size.x().max(constraint.min.x()));
334 }
335 size.set_x(size.x().min(constraint.max.x()));
336
337 // Impose vertical constraints
338 if constraint.min.y().is_finite() {
339 size.set_y(size.y().max(constraint.min.y()));
340 }
341 size.set_y(size.y().min(constraint.max.y()));
342
343 let inset_size = self.inset_size();
344 let inner_size = size - inset_size;
345 let size_of_children = match self.style.axis {
346 Axis3d::X => self.layout_2d_children(Axis2d::X, inner_size, view, cx),
347 Axis3d::Y => self.layout_2d_children(Axis2d::Y, inner_size, view, cx),
348 Axis3d::Z => todo!(), // self.layout_stacked_children(inner_constraint, view, cx),
349 };
350
351 if matches!(self.style.width, Length::Hug) {
352 size.set_x(size_of_children.x() + inset_size.x());
353 }
354 if matches!(self.style.height, Length::Hug) {
355 size.set_y(size_of_children.y() + inset_size.y());
356 }
357
358 (size, size_of_children)
359 }
360
361 fn paint(
362 &mut self,
363 scene: &mut SceneBuilder,
364 bounds: RectF,
365 visible_bounds: RectF,
366 size_of_children: &mut Vector2F,
367 view: &mut V,
368 cx: &mut ViewContext<V>,
369 ) -> Self::PaintState {
370 let margin = &self.style.margin;
371
372 // Account for margins
373 let content_bounds = RectF::from_points(
374 bounds.origin() + vec2f(margin.left, margin.top),
375 bounds.lower_right() - vec2f(margin.right, margin.bottom),
376 );
377
378 // Paint drop shadow
379 for shadow in &self.style.shadows {
380 scene.push_shadow(scene::Shadow {
381 bounds: content_bounds + shadow.offset,
382 corner_radius: self.style.corner_radius,
383 sigma: shadow.blur,
384 color: shadow.color,
385 });
386 }
387
388 // // Paint cursor style
389 // if let Some(hit_bounds) = content_bounds.intersection(visible_bounds) {
390 // if let Some(style) = self.style.cursor {
391 // scene.push_cursor_region(CursorRegion {
392 // bounds: hit_bounds,
393 // style,
394 // });
395 // }
396 // }
397
398 // Render the background and/or the border (if it not an overlay border).
399 let Fill::Color(fill_color) = self.style.fill;
400 let is_fill_visible = !fill_color.is_fully_transparent();
401 if is_fill_visible || self.style.border.is_visible() {
402 scene.push_quad(Quad {
403 bounds: content_bounds,
404 background: is_fill_visible.then_some(fill_color),
405 border: scene::Border {
406 width: self.style.border.width,
407 color: self.style.border.color,
408 overlay: false,
409 top: self.style.border.top,
410 right: self.style.border.right,
411 bottom: self.style.border.bottom,
412 left: self.style.border.left,
413 },
414 corner_radius: self.style.corner_radius,
415 });
416 }
417
418 if !self.content.is_empty() {
419 // Account for padding first.
420 let padding = &self.style.padding;
421 let padded_bounds = RectF::from_points(
422 content_bounds.origin() + vec2f(padding.left, padding.top),
423 content_bounds.lower_right() - vec2f(padding.right, padding.top),
424 );
425
426 match self.style.axis {
427 Axis3d::X => self.paint_2d_children(
428 scene,
429 Axis2d::X,
430 padded_bounds,
431 visible_bounds,
432 size_of_children,
433 view,
434 cx,
435 ),
436 Axis3d::Y => self.paint_2d_children(
437 scene,
438 Axis2d::Y,
439 padded_bounds,
440 visible_bounds,
441 size_of_children,
442 view,
443 cx,
444 ),
445 Axis3d::Z => todo!(),
446 }
447 }
448 }
449
450 fn rect_for_text_range(
451 &self,
452 range_utf16: Range<usize>,
453 _: RectF,
454 _: RectF,
455 _: &Self::LayoutState,
456 _: &Self::PaintState,
457 view: &V,
458 cx: &ViewContext<V>,
459 ) -> Option<RectF> {
460 self.content
461 .iter()
462 .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
463 }
464
465 fn debug(
466 &self,
467 bounds: RectF,
468 _: &Self::LayoutState,
469 _: &Self::PaintState,
470 view: &V,
471 cx: &ViewContext<V>,
472 ) -> Value {
473 json!({
474 "type": "Node",
475 "bounds": bounds.to_json(),
476 // TODO!
477 // "children": self.content.iter().map(|child| child.debug(view, cx)).collect::<Vec<Value>>()
478 })
479 }
480
481 fn metadata(&self) -> Option<&dyn Any> {
482 Some(&self.style)
483 }
484}
485
486fn align_child(
487 child_origin: &mut Vector2F,
488 parent_size: Vector2F,
489 child_size: Vector2F,
490 alignment: Vector2F,
491 horizontal: bool,
492 vertical: bool,
493) {
494 let parent_center = parent_size / 2.;
495 let parent_target = parent_center + parent_center * alignment;
496 let child_center = child_size / 2.;
497 let child_target = child_center + child_center * alignment;
498
499 if horizontal {
500 child_origin.set_x(child_origin.x() + parent_target.x() - child_target.x())
501 }
502 if vertical {
503 child_origin.set_y(child_origin.y() + parent_target.y() - child_target.y());
504 }
505}
506
507struct Interactive<Style> {
508 default: Style,
509 hovered: Style,
510 active: Style,
511 disabled: Style,
512}
513
514#[derive(Clone, Default)]
515pub struct NodeStyle {
516 axis: Axis3d,
517 wrap: bool,
518 align: Align,
519 overflow_x: Overflow,
520 overflow_y: Overflow,
521 gap_x: Gap,
522 gap_y: Gap,
523
524 width: Length,
525 height: Length,
526 margin: Edges<f32>,
527 padding: Edges<f32>,
528
529 text_color: Option<Color>,
530 font_size: Option<f32>,
531 font_style: Option<FontStyle>,
532 font_weight: Option<FontWeight>,
533
534 opacity: f32,
535 fill: Fill,
536 border: Border,
537 corner_radius: f32, // corner radius matches swift!
538 shadows: Vec<Shadow>,
539}
540
541// Sides?
542#[derive(Clone, Default)]
543struct Edges<T> {
544 top: T,
545 bottom: T,
546 left: T,
547 right: T,
548}
549
550#[derive(Clone, Default)]
551struct CornerRadii {
552 top_left: f32,
553 top_right: f32,
554 bottom_right: f32,
555 bottom_left: f32,
556}
557
558#[derive(Clone)]
559pub enum Fill {
560 Color(Color),
561}
562
563impl From<Color> for Fill {
564 fn from(value: Color) -> Self {
565 Fill::Color(value)
566 }
567}
568
569impl Default for Fill {
570 fn default() -> Self {
571 Fill::Color(Color::default())
572 }
573}
574
575#[derive(Clone, Default)]
576struct Border {
577 color: Color,
578 width: f32,
579 top: bool,
580 bottom: bool,
581 left: bool,
582 right: bool,
583}
584
585impl Border {
586 fn is_visible(&self) -> bool {
587 self.width > 0.
588 && !self.color.is_fully_transparent()
589 && (self.top || self.bottom || self.left || self.right)
590 }
591}
592
593pub mod length {
594 #[derive(Clone, Copy, Default)]
595 pub enum Length {
596 #[default]
597 Hug,
598 Fixed(f32),
599 Auto {
600 flex: f32,
601 min: f32,
602 max: f32,
603 },
604 }
605
606 impl From<f32> for Length {
607 fn from(value: f32) -> Self {
608 Length::Fixed(value)
609 }
610 }
611
612 pub fn auto() -> Length {
613 flex(1.)
614 }
615
616 pub fn flex(flex: f32) -> Length {
617 Length::Auto {
618 flex,
619 min: 0.,
620 max: f32::INFINITY,
621 }
622 }
623
624 pub fn constrained(flex: f32, min: Option<f32>, max: Option<f32>) -> Length {
625 Length::Auto {
626 flex,
627 min: min.unwrap_or(0.),
628 max: max.unwrap_or(f32::INFINITY),
629 }
630 }
631
632 impl Length {
633 pub fn flex(&self) -> Option<f32> {
634 match self {
635 Length::Auto { flex, .. } => Some(*flex),
636 _ => None,
637 }
638 }
639 }
640}
641
642#[derive(Clone)]
643struct Align(Vector2F);
644
645impl Default for Align {
646 fn default() -> Self {
647 Self(vec2f(-1., -1.))
648 }
649}
650
651#[derive(Clone, Copy, Default)]
652enum Axis3d {
653 X,
654 #[default]
655 Y,
656 Z,
657}
658
659impl Axis3d {
660 fn to_2d(self) -> Option<Axis2d> {
661 match self {
662 Axis3d::X => Some(Axis2d::X),
663 Axis3d::Y => Some(Axis2d::Y),
664 Axis3d::Z => None,
665 }
666 }
667}
668
669#[derive(Clone, Copy, Default)]
670enum Axis2d {
671 X,
672 #[default]
673 Y,
674}
675
676#[derive(Clone, Copy, Default)]
677enum Overflow {
678 #[default]
679 Visible,
680 Hidden,
681 Auto,
682}
683
684#[derive(Clone, Copy)]
685enum Gap {
686 Fixed(f32),
687 Around,
688 Between,
689 Even,
690}
691
692impl Default for Gap {
693 fn default() -> Self {
694 Gap::Fixed(0.)
695 }
696}
697
698#[derive(Clone, Copy, Default)]
699struct Shadow {
700 offset: Vector2F,
701 blur: f32,
702 color: Color,
703}
704
705#[derive(Clone, Copy, Default)]
706enum FontStyle {
707 #[default]
708 Normal,
709 Italic,
710 Oblique,
711}
712
713#[derive(Clone, Copy, Default)]
714enum FontWeight {
715 Thin,
716 ExtraLight,
717 Light,
718 #[default]
719 Normal,
720 Medium,
721 Semibold,
722 Bold,
723 ExtraBold,
724 Black,
725}