feather_ui/layout/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 Fundament Research Institute <https://fundament.institute>
3
4pub mod base;
5pub mod domain_write;
6pub mod fixed;
7pub mod flex;
8pub mod grid;
9pub mod leaf;
10pub mod list;
11pub mod root;
12pub mod text;
13
14use dyn_clone::DynClone;
15use guillotiere::euclid::{Point2D, Vector2D};
16use wide::f32x4;
17
18use crate::color::sRGB32;
19use crate::render::Renderable;
20use crate::render::compositor::CompositorView;
21use crate::{
22    Error, PxDim, PxLimits, PxPoint, PxRect, RelLimits, SourceID, UNSIZED_AXIS, URect, rtree,
23};
24use derive_where::derive_where;
25use std::marker::PhantomData;
26use std::rc::{Rc, Weak};
27use std::sync::Arc;
28
29/// Represents an arbitrary layout node that hasn't been staged yet. The vast majority of
30/// the time, components should simply use the standard [`Node`] implementation of this
31/// trait, which handles most common layout cases. However, some components, like the text
32/// component, have complex layout logic or special cases that [`Node`] can't cover.
33pub trait Layout<Props: ?Sized>: DynClone {
34    fn get_props(&self) -> &Props;
35    fn stage<'a>(
36        &self,
37        area: PxRect,
38        limits: PxLimits,
39        window: &mut crate::component::window::WindowState,
40    ) -> Box<dyn Staged + 'a>;
41}
42
43dyn_clone::clone_trait_object!(<Imposed> Layout<Imposed> where Imposed:?Sized);
44
45impl<U: ?Sized, T> Layout<U> for Box<dyn Layout<T>>
46where
47    for<'a> &'a T: Into<&'a U>,
48{
49    fn get_props(&self) -> &U {
50        use std::ops::Deref;
51        Box::deref(self).get_props().into()
52    }
53
54    fn stage<'a>(
55        &self,
56        area: PxRect,
57        limits: PxLimits,
58        window: &mut crate::component::window::WindowState,
59    ) -> Box<dyn Staged + 'a> {
60        use std::ops::Deref;
61        Box::deref(self).stage(area, limits, window)
62    }
63}
64
65impl<U: ?Sized, T> Layout<U> for &dyn Layout<T>
66where
67    for<'a> &'a T: Into<&'a U>,
68{
69    fn get_props(&self) -> &U {
70        (*self).get_props().into()
71    }
72
73    fn stage<'a>(
74        &self,
75        area: PxRect,
76        limits: PxLimits,
77        window: &mut crate::component::window::WindowState,
78    ) -> Box<dyn Staged + 'a> {
79        (*self).stage(area, limits, window)
80    }
81}
82
83pub trait Desc {
84    type Props: ?Sized;
85    type Child: ?Sized;
86    type Children: Clone;
87
88    /// Resolves a pending layout into a resolved node, which contains a pointer to the R-tree
89    fn stage<'a>(
90        props: &Self::Props,
91        outer_area: PxRect,
92        limits: PxLimits,
93        children: &Self::Children,
94        id: std::sync::Weak<SourceID>,
95        renderable: Option<Rc<dyn Renderable>>,
96        window: &mut crate::component::window::WindowState,
97    ) -> Box<dyn Staged + 'a>;
98}
99
100/// The standard layout node. Expects the layout properties, which must be compatible with the
101/// layout description `D` provided, which also determines the type that contains the children.
102/// A unique ID must be provided, and a renderable is optional - it will be passed to staging
103/// if provided. The layer, if provided will create a new layer operation with the given color
104/// and rotation. This is normally used to do correct transparency.
105///
106/// # Examples
107/// See [`super::component::Component`]
108#[derive_where(Clone)]
109pub struct Node<T, D: Desc + ?Sized> {
110    pub props: Rc<T>,
111    pub id: std::sync::Weak<SourceID>,
112    pub children: D::Children,
113    pub renderable: Option<Rc<dyn Renderable>>,
114    pub layer: Option<(sRGB32, f32)>,
115}
116
117impl<T, D: Desc + ?Sized> Layout<T> for Node<T, D>
118where
119    for<'a> &'a T: Into<&'a D::Props>,
120{
121    fn get_props(&self) -> &T {
122        self.props.as_ref()
123    }
124    fn stage<'a>(
125        &self,
126        area: PxRect,
127        limits: PxLimits,
128        window: &mut crate::component::window::WindowState,
129    ) -> Box<dyn Staged + 'a> {
130        let mut staged = D::stage(
131            self.props.as_ref().into(),
132            area,
133            limits,
134            &self.children,
135            self.id.clone(),
136            self.renderable.as_ref().map(|x| x.clone()),
137            window,
138        );
139        if let Some((color, rotation)) = self.layer {
140            window.driver.shared.create_layer(
141                &window.driver.device,
142                self.id.upgrade().unwrap(),
143                staged.get_area().to_untyped(),
144                None,
145                color,
146                rotation,
147                false,
148            );
149            staged.set_layer(self.id.clone());
150        }
151        staged
152    }
153}
154
155pub trait Staged: DynClone {
156    fn render(
157        &self,
158        parent_pos: PxPoint,
159        driver: &crate::graphics::Driver,
160        compositor: &mut CompositorView<'_>,
161        dependents: &mut Vec<std::sync::Weak<SourceID>>,
162    ) -> Result<(), Error>;
163    fn get_rtree(&self) -> Weak<rtree::Node>;
164    fn get_area(&self) -> PxRect;
165    fn set_layer(&mut self, _id: std::sync::Weak<SourceID>) {
166        panic!("This staged object doesn't support layers!");
167    }
168}
169
170dyn_clone::clone_trait_object!(Staged);
171
172#[derive(Clone)]
173pub(crate) struct Concrete {
174    renderable: Option<Rc<dyn Renderable>>,
175    area: PxRect,
176    rtree: Rc<rtree::Node>,
177    children: im::Vector<Option<Box<dyn Staged>>>,
178    layer: Option<std::sync::Weak<SourceID>>,
179}
180
181impl Concrete {
182    pub fn new(
183        renderable: Option<Rc<dyn Renderable>>,
184        area: PxRect,
185        rtree: Rc<rtree::Node>,
186        children: im::Vector<Option<Box<dyn Staged>>>,
187    ) -> Self {
188        let (unsized_x, unsized_y) = check_unsized_abs(area.bottomright());
189        assert!(
190            !unsized_x && !unsized_y,
191            "concrete area must always be sized!: {area:?}",
192        );
193        Self {
194            renderable,
195            area,
196            rtree,
197            children,
198            layer: None,
199        }
200    }
201
202    fn render_self(
203        &self,
204        parent_pos: PxPoint,
205        driver: &crate::graphics::Driver,
206        compositor: &mut CompositorView<'_>,
207    ) -> Result<(), Error> {
208        if let Some(r) = &self.renderable {
209            r.render((self.area + parent_pos).to_untyped(), driver, compositor)?;
210        }
211        Ok(())
212    }
213
214    fn render_children(
215        &self,
216        parent_pos: PxPoint,
217        driver: &crate::graphics::Driver,
218        compositor: &mut CompositorView<'_>,
219        dependents: &mut Vec<std::sync::Weak<SourceID>>,
220    ) -> Result<(), Error> {
221        for child in (&self.children).into_iter().flatten() {
222            // TODO: If we assign z-indexes to children, ones with negative z-indexes should be rendered before the parent
223            child.render(
224                parent_pos + self.area.topleft().to_vector(),
225                driver,
226                compositor,
227                dependents,
228            )?;
229        }
230        Ok(())
231    }
232}
233
234impl Staged for Concrete {
235    fn render(
236        &self,
237        parent_pos: PxPoint,
238        driver: &crate::graphics::Driver,
239        compositor: &mut CompositorView<'_>,
240        dependents: &mut Vec<std::sync::Weak<SourceID>>,
241    ) -> Result<(), Error> {
242        if let Some(id) = self.layer.as_ref().and_then(|x| x.upgrade()) {
243            let layers = driver.shared.access_layers();
244            let layer = layers.get(&id).expect("Missing layer in render call!");
245            let mut deps = Vec::new();
246            let mut region_uv = None;
247
248            let (mut view, depview) = if layer.target.is_some() {
249                // If this is a "real" layer with a texture target, mark it as a dependency of our parent
250                dependents.push(Arc::downgrade(&id));
251
252                // Acquire a region if we don't have one already. This is done carefully so that the layer can be moved to a different
253                // dependency layer and therefore switched to a different atlas without the user render functions needing to care.
254                let index = match compositor.index {
255                    0 => 1,
256                    1 => 2,
257                    2 => 1,
258                    _ => panic!("Invalid index!"),
259                };
260
261                let mut atlas = driver.layer_atlas[index - 1].write();
262                let region = atlas.cache_region(
263                    &driver.device,
264                    &id,
265                    layer.area.dim().ceil().to_i32(),
266                    None,
267                )?;
268                region_uv = Some(region.uv);
269
270                // Make sure we aren't cached in the opposite atlas
271                driver.layer_atlas[index % 2].write().remove_cache(&id);
272                assert!(compositor.pass < 0b111111);
273
274                let mut v = CompositorView {
275                    index: index as u8,
276                    window: compositor.window,
277                    layer0: compositor.layer0,
278                    layer1: compositor.layer1,
279                    clipstack: compositor.clipstack,
280                    offset: region.uv.min.to_f32() - layer.area.topleft() - parent_pos.to_vector(),
281                    surface_dim: compositor.surface_dim,
282                    pass: compositor.pass + 1,
283                    slice: region.index,
284                };
285
286                v.reserve(driver);
287                // And return a reference to a new dependency vector
288                (v, &mut deps)
289            } else {
290                // Otherwise, we don't create a new compositor view, instead copying our previous one, and passing in
291                // the parent's dependency tracker.
292                (
293                    CompositorView {
294                        index: compositor.index,
295                        window: compositor.window,
296                        layer0: compositor.layer0,
297                        layer1: compositor.layer1,
298                        clipstack: compositor.clipstack,
299                        offset: compositor.offset,
300                        surface_dim: compositor.surface_dim,
301                        pass: compositor.pass,
302                        slice: compositor.slice,
303                    },
304                    dependents,
305                )
306            };
307
308            // Always push a new clipping area, but remember that a layer can only store it's relative area.
309            view.with_clip(layer.area + parent_pos, |refview| {
310                self.render_self(parent_pos, driver, refview)?;
311                self.render_children(parent_pos, driver, refview, depview)
312            })?;
313
314            if let Some(target) = layer.target.as_ref() {
315                // If this was a real layer, now we need to actually assign the result of our dependencies, and
316                // append ourselves to the parent layer. We must be very careful not to use the wrong view here.
317                target.write().dependents = deps;
318                compositor.append_layer(layer, parent_pos, region_uv.unwrap());
319            }
320        } else {
321            self.render_self(parent_pos, driver, compositor)?;
322            self.render_children(parent_pos, driver, compositor, dependents)?;
323        };
324
325        Ok(())
326    }
327
328    fn get_rtree(&self) -> Weak<rtree::Node> {
329        Rc::downgrade(&self.rtree)
330    }
331
332    fn get_area(&self) -> PxRect {
333        self.area
334    }
335
336    fn set_layer(&mut self, id: std::sync::Weak<SourceID>) {
337        self.layer = Some(id)
338    }
339}
340
341#[must_use]
342#[inline]
343pub(crate) fn map_unsized_area(mut area: URect, adjust: PxDim) -> URect {
344    let (unsized_x, unsized_y) = check_unsized(area);
345    let abs = area.abs.v.as_array_mut();
346    let rel = area.rel.v.as_array_mut();
347    // Unsized objects must always have a single anchor point to make sense, so we copy over from topleft.
348    if unsized_x {
349        rel[2] = rel[0];
350        // Fix the bottomright abs area in unsized scenarios, because it was relative to the topleft instead of being independent.
351        abs[2] += abs[0] + adjust.width;
352    }
353    if unsized_y {
354        rel[3] = rel[1];
355        abs[3] += abs[1] + adjust.height;
356    }
357    area
358}
359
360#[must_use]
361#[inline]
362pub(crate) fn nuetralize_unsized(v: PxRect) -> PxRect {
363    let (unsized_x, unsized_y) = check_unsized_abs(v.bottomright());
364    let ltrb = v.v.to_array();
365    PxRect {
366        v: f32x4::new([
367            ltrb[0],
368            ltrb[1],
369            if unsized_x { ltrb[0] } else { ltrb[2] },
370            if unsized_y { ltrb[1] } else { ltrb[3] },
371        ]),
372        _unit: PhantomData,
373    }
374}
375
376#[must_use]
377#[inline]
378pub(crate) fn limit_area(mut v: PxRect, limits: PxLimits) -> PxRect {
379    // We do this by checking clamp(topleft + limit) instead of clamp(bottomright - topleft)
380    // because this avoids floating point precision issues.
381    v.set_bottomright(
382        v.bottomright()
383            .max(v.topleft() + limits.min())
384            .min(v.topleft() + limits.max()),
385    );
386    v
387}
388
389#[must_use]
390#[inline]
391pub(crate) fn limit_dim(v: PxDim, limits: PxLimits) -> PxDim {
392    let (unsized_x, unsized_y) = check_unsized_dim(v);
393    PxDim::new(
394        if unsized_x {
395            v.width
396        } else {
397            v.width.max(limits.min().width).min(limits.max().width)
398        },
399        if unsized_y {
400            v.height
401        } else {
402            v.height.max(limits.min().height).min(limits.max().height)
403        },
404    )
405}
406
407#[must_use]
408#[inline]
409pub(crate) fn eval_dim(area: URect, dim: PxDim) -> PxDim {
410    let (unsized_x, unsized_y) = check_unsized(area);
411    PxDim::new(
412        if unsized_x {
413            area.bottomright().rel().x
414        } else {
415            let top = area.topleft().abs().x + (area.topleft().rel().x * dim.width);
416            let bottom = area.bottomright().abs().x + (area.bottomright().rel().x * dim.width);
417            bottom - top
418        },
419        if unsized_y {
420            area.bottomright().rel().y
421        } else {
422            let top = area.topleft().abs().y + (area.topleft().rel().y * dim.height);
423            let bottom = area.bottomright().abs().y + (area.bottomright().rel().y * dim.height);
424            bottom - top
425        },
426    )
427}
428
429#[must_use]
430#[inline]
431pub(crate) fn apply_limit(dim: PxDim, limits: PxLimits, rlimits: RelLimits) -> PxLimits {
432    let (unsized_x, unsized_y) = check_unsized_dim(dim);
433    let sign = limits.v.sign_bit() | rlimits.v.sign_bit();
434    PxLimits {
435        v: (f32x4::new([
436            if unsized_x {
437                limits.min().width
438            } else {
439                limits.min().width.max(dim.width)
440            },
441            if unsized_y {
442                limits.min().height
443            } else {
444                limits.min().height.max(dim.height)
445            },
446            if unsized_x {
447                limits.max().width
448            } else {
449                limits.max().width.min(dim.width)
450            },
451            if unsized_y {
452                limits.max().height
453            } else {
454                limits.max().height.min(dim.height)
455            },
456        ]) * rlimits.v)
457            .copysign(sign),
458        _unit: PhantomData,
459    }
460}
461
462// Returns true if an axis is unsized, which means it is defined as the size of it's children's maximum extent.
463#[must_use]
464#[inline]
465pub(crate) fn check_unsized(area: URect) -> (bool, bool) {
466    (
467        area.bottomright().rel().x == UNSIZED_AXIS,
468        area.bottomright().rel().y == UNSIZED_AXIS,
469    )
470}
471
472// Returns true if an axis is unsized, which means it is defined as the size of it's children's maximum extent.
473#[must_use]
474#[inline]
475pub(crate) fn check_unsized_abs<U>(bottomright: Point2D<f32, U>) -> (bool, bool) {
476    (bottomright.x == UNSIZED_AXIS, bottomright.y == UNSIZED_AXIS)
477}
478
479// Returns true if an axis is unsized, which means it is defined as the size of it's children's maximum extent.
480#[must_use]
481#[inline]
482pub(crate) fn check_unsized_dim(dim: PxDim) -> (bool, bool) {
483    check_unsized_abs(dim.to_vector().to_point())
484}
485
486pub(crate) fn assert_sized(area: PxRect) {
487    let ltrb = area.v.as_array_ref();
488
489    for v in ltrb {
490        assert_ne!(*v, UNSIZED_AXIS);
491        assert!(v.is_finite());
492    }
493}
494
495#[must_use]
496#[inline]
497pub(crate) fn cap_unsized(area: PxRect) -> PxRect {
498    let ltrb = area.v.to_array();
499    PxRect {
500        v: f32x4::new(ltrb.map(|x| {
501            if x.is_finite() {
502                x
503            } else {
504                crate::UNSIZED_AXIS
505            }
506        })),
507        _unit: PhantomData,
508    }
509}
510
511#[must_use]
512#[inline]
513pub(crate) fn apply_anchor(area: PxRect, outer_area: PxRect, mut anchor: PxPoint) -> PxRect {
514    let (unsized_outer_x, unsized_outer_y) = check_unsized_abs(outer_area.bottomright());
515    if unsized_outer_x {
516        anchor.x = 0.0;
517    }
518    if unsized_outer_y {
519        anchor.y = 0.0;
520    }
521    area - anchor
522}
523
524#[must_use]
525#[inline]
526fn swap_pair<T>(xaxis: bool, v: (T, T)) -> (T, T) {
527    if xaxis { (v.0, v.1) } else { (v.1, v.0) }
528}
529
530trait Swappable<T> {
531    fn swap_axis(self, xaxis: bool) -> (T, T);
532}
533
534impl<T, U> Swappable<T> for Point2D<T, U> {
535    #[inline]
536    fn swap_axis(self, xaxis: bool) -> (T, T) {
537        swap_pair(xaxis, (self.x, self.y))
538    }
539}
540
541impl<T, U> Swappable<T> for guillotiere::euclid::Size2D<T, U> {
542    #[inline]
543    fn swap_axis(self, xaxis: bool) -> (T, T) {
544        swap_pair(xaxis, (self.width, self.height))
545    }
546}
547
548impl<T, U> Swappable<T> for Vector2D<T, U> {
549    #[inline]
550    fn swap_axis(self, xaxis: bool) -> (T, T) {
551        swap_pair(xaxis, (self.x, self.y))
552    }
553}
554
555/// If prev is NAN, always returns zero, which is the correct action for margin edges.
556#[must_use]
557#[inline]
558fn merge_margin(prev: f32, margin: f32) -> f32 {
559    if prev.is_nan() { 0.0 } else { margin.max(prev) }
560}