node.rs

  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}