iced_core/
layout.rs

1//! Position your widgets properly.
2mod limits;
3mod node;
4
5pub mod flex;
6
7pub use limits::Limits;
8pub use node::Node;
9
10use crate::{Length, Padding, Point, Rectangle, Size, Vector};
11
12/// The bounds of a [`Node`] and its children, using absolute coordinates.
13#[derive(Debug, Clone, Copy)]
14pub struct Layout<'a> {
15    /// The virtual offset of the layout.
16    /// May represent the scroll positions in pixels of a scrollable, for example.
17    virtual_offset: Vector,
18    position: Point,
19    node: &'a Node,
20}
21
22impl<'a> Layout<'a> {
23    /// Creates a new [`Layout`] for the given [`Node`] at the origin.
24    pub fn new(node: &'a Node) -> Self {
25        Self::with_offset(Vector::new(0.0, 0.0), node)
26    }
27
28    /// Creates a new [`Layout`] for the given [`Node`] with the provided offset
29    /// from the origin.
30    pub fn with_offset(offset: Vector, node: &'a Node) -> Self {
31        let bounds = node.bounds();
32
33        Self {
34            virtual_offset: Vector::new(0., 0.),
35            position: Point::new(bounds.x, bounds.y) + offset,
36            node,
37        }
38    }
39
40    /// Returns a new layout with the virtual offset
41    pub fn with_virtual_offset(mut self, virtual_offset: Vector) -> Self {
42        self.virtual_offset = virtual_offset;
43        self
44    }
45
46    /// Returns the position of the [`Layout`].
47    pub fn position(&self) -> Point {
48        self.position
49    }
50
51    /// The virtual offset of the layout.
52    /// May represent the scroll positions in pixels of a scrollable, for example.
53    pub fn virtual_offset(&self) -> Vector {
54        self.virtual_offset
55    }
56
57    /// Returns the bounds of the [`Layout`].
58    ///
59    /// The returned [`Rectangle`] describes the position and size of a
60    /// [`Node`].
61    pub fn bounds(&self) -> Rectangle {
62        let bounds = self.node.bounds();
63
64        Rectangle {
65            x: self.position.x,
66            y: self.position.y,
67            width: bounds.width,
68            height: bounds.height,
69        }
70    }
71
72    /// Returns an iterator over the [`Layout`] of the children of a [`Node`].
73    pub fn children(self) -> impl DoubleEndedIterator<Item = Layout<'a>> {
74        self.node.children().iter().map(move |node| {
75            Layout::with_offset(
76                Vector::new(self.position.x, self.position.y),
77                node,
78            )
79        })
80    }
81}
82
83/// Produces a [`Node`] with two children nodes one right next to each other.
84pub fn next_to_each_other(
85    limits: &Limits,
86    spacing: f32,
87    left: impl FnOnce(&Limits) -> Node,
88    right: impl FnOnce(&Limits) -> Node,
89) -> Node {
90    let left_node = left(limits);
91    let left_size = left_node.size();
92
93    let right_limits = limits.shrink(Size::new(left_size.width + spacing, 0.0));
94
95    let right_node = right(&right_limits);
96    let right_size = right_node.size();
97
98    let (left_y, right_y) = if left_size.height > right_size.height {
99        (0.0, (left_size.height - right_size.height) / 2.0)
100    } else {
101        ((right_size.height - left_size.height) / 2.0, 0.0)
102    };
103
104    Node::with_children(
105        Size::new(
106            left_size.width + spacing + right_size.width,
107            left_size.height.max(right_size.height),
108        ),
109        vec![
110            left_node.move_to(Point::new(0.0, left_y)),
111            right_node.move_to(Point::new(left_size.width + spacing, right_y)),
112        ],
113    )
114}
115
116/// Computes the resulting [`Node`] that fits the [`Limits`] given
117/// some width and height requirements and no intrinsic size.
118pub fn atomic(
119    limits: &Limits,
120    width: impl Into<Length>,
121    height: impl Into<Length>,
122) -> Node {
123    let width = width.into();
124    let height = height.into();
125
126    Node::new(limits.resolve(width, height, Size::ZERO))
127}
128
129/// Computes the resulting [`Node`] that fits the [`Limits`] given
130/// some width and height requirements and a closure that produces
131/// the intrinsic [`Size`] inside the given [`Limits`].
132pub fn sized(
133    limits: &Limits,
134    width: impl Into<Length>,
135    height: impl Into<Length>,
136    f: impl FnOnce(&Limits) -> Size,
137) -> Node {
138    let width = width.into();
139    let height = height.into();
140
141    let limits = limits.width(width).height(height);
142    let intrinsic_size = f(&limits);
143
144    Node::new(limits.resolve(width, height, intrinsic_size))
145}
146
147/// Computes the resulting [`Node`] that fits the [`Limits`] given
148/// some width and height requirements and a closure that produces
149/// the content [`Node`] inside the given [`Limits`].
150pub fn contained(
151    limits: &Limits,
152    width: impl Into<Length>,
153    height: impl Into<Length>,
154    f: impl FnOnce(&Limits) -> Node,
155) -> Node {
156    let width = width.into();
157    let height = height.into();
158
159    let limits = limits.width(width).height(height);
160    let content = f(&limits);
161
162    Node::with_children(
163        limits.resolve(width, height, content.size()),
164        vec![content],
165    )
166}
167
168/// Computes the [`Node`] that fits the [`Limits`] given some width, height, and
169/// [`Padding`] requirements and a closure that produces the content [`Node`]
170/// inside the given [`Limits`].
171pub fn padded(
172    limits: &Limits,
173    width: impl Into<Length>,
174    height: impl Into<Length>,
175    padding: impl Into<Padding>,
176    layout: impl FnOnce(&Limits) -> Node,
177) -> Node {
178    positioned(limits, width, height, padding, layout, |content, _| content)
179}
180
181/// Computes a [`padded`] [`Node`] with a positioning step.
182pub fn positioned(
183    limits: &Limits,
184    width: impl Into<Length>,
185    height: impl Into<Length>,
186    padding: impl Into<Padding>,
187    layout: impl FnOnce(&Limits) -> Node,
188    position: impl FnOnce(Node, Size) -> Node,
189) -> Node {
190    let width = width.into();
191    let height = height.into();
192    let padding = padding.into();
193
194    let limits = limits.width(width).height(height);
195    let content = layout(&limits.shrink(padding));
196    let padding = padding.fit(content.size(), limits.max());
197
198    let size = limits
199        .shrink(padding)
200        .resolve(width, height, content.size());
201
202    Node::with_children(
203        size.expand(padding),
204        vec![position(content.move_to((padding.left, padding.top)), size)],
205    )
206}