Skip to main content

fission_core/ui/widgets/
builder.rs

1use crate::{AppState, BuildCtx, BoxConstraints, Node, NodeId, View, Widget, WidgetNodeId};
2use crate::ui::Container;
3use std::sync::Arc;
4
5/// A closure-based widget that builds a [`Node`] tree from a function.
6///
7/// `Builder` lets you define inline widgets without creating a named struct.
8///
9/// # Example
10///
11/// ```rust,ignore
12/// let widget = Builder::new(|ctx, view| {
13///     Text::new(format!("Count: {}", view.state.count)).into_node()
14/// });
15/// ```
16pub struct Builder<S: AppState> {
17    builder: Arc<dyn Fn(&mut BuildCtx<S>, &View<S>) -> Node + Send + Sync>,
18}
19
20impl<S: AppState> Builder<S> {
21    pub fn new<F>(builder: F) -> Self
22    where
23        F: Fn(&mut BuildCtx<S>, &View<S>) -> Node + Send + Sync + 'static,
24    {
25        Self {
26            builder: Arc::new(builder),
27        }
28    }
29}
30
31impl<S: AppState> Widget<S> for Builder<S> {
32    fn build(&self, ctx: &mut BuildCtx<S>, view: &View<S>) -> Node {
33        (self.builder)(ctx, view)
34    }
35}
36
37/// A closure-based widget that receives its parent's [`BoxConstraints`].
38///
39/// `LayoutBuilder` is the layout-aware counterpart of [`Builder`]. The closure
40/// receives the constraints from the previous frame (or a fallback derived
41/// from the viewport) so it can adapt its output to the available space.
42///
43/// # Example
44///
45/// ```rust,ignore
46/// let widget = LayoutBuilder::new(|ctx, view, constraints| {
47///     let cols = if constraints.max_width > 600.0 { 3 } else { 1 };
48///     build_grid(ctx, view, cols)
49/// })
50/// .flex_grow(1.0);
51/// ```
52pub struct LayoutBuilder<S: AppState> {
53    /// Optional stable identity for constraint look-up across frames.
54    pub id: Option<WidgetNodeId>,
55    /// Flex grow factor.
56    pub flex_grow: f32,
57    /// Flex shrink factor.
58    pub flex_shrink: f32,
59    builder: Arc<dyn Fn(&mut BuildCtx<S>, &View<S>, BoxConstraints) -> Node + Send + Sync>,
60}
61
62impl<S: AppState> LayoutBuilder<S> {
63    pub fn new<F>(builder: F) -> Self
64    where
65        F: Fn(&mut BuildCtx<S>, &View<S>, BoxConstraints) -> Node + Send + Sync + 'static,
66    {
67        Self {
68            id: None,
69            flex_grow: 0.0,
70            flex_shrink: 1.0,
71            builder: Arc::new(builder),
72        }
73    }
74
75    pub fn id(mut self, id: WidgetNodeId) -> Self {
76        self.id = Some(id);
77        self
78    }
79
80    pub fn flex_grow(mut self, grow: f32) -> Self {
81        self.flex_grow = grow;
82        self
83    }
84
85    pub fn flex_shrink(mut self, shrink: f32) -> Self {
86        self.flex_shrink = shrink;
87        self
88    }
89}
90
91impl<S: AppState> Widget<S> for LayoutBuilder<S> {
92    fn build(&self, ctx: &mut BuildCtx<S>, view: &View<S>) -> Node {
93        let viewport = view
94            .layout
95            .map(|layout| layout.viewport_size)
96            .unwrap_or_else(|| view.viewport_size());
97        let mut max_w = viewport.width;
98        let mut max_h = viewport.height;
99        if !max_w.is_finite() || max_w <= 0.0 {
100            max_w = f32::INFINITY;
101        }
102        if !max_h.is_finite() || max_h <= 0.0 {
103            max_h = f32::INFINITY;
104        }
105        let fallback = BoxConstraints::loose(max_w, max_h);
106        let constraints = self
107            .id
108            .and_then(|id| view.get_constraints(id))
109            .unwrap_or(fallback);
110        let child = (self.builder)(ctx, view, constraints);
111        if let Some(id) = self.id {
112            Container::new(child)
113                .id(NodeId::from(id))
114                .flex_grow(self.flex_grow)
115                .flex_shrink(self.flex_shrink)
116                .into_node()
117        } else {
118            child
119        }
120    }
121}