Skip to main content

denise_arrange/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3#![forbid(unsafe_code)]
4
5extern crate alloc;
6
7use alloc::vec::Vec;
8
9use denise::Rect;
10use denise_ui::{NodeId, Offer, Ui};
11
12/// Which way a container lays its children out.
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14pub enum Flow {
15    /// Left to right. The main axis is the width.
16    #[default]
17    Row,
18    /// Top to bottom. The main axis is the height.
19    Column,
20    /// All on top of each other: every child gets the whole content box.
21    ///
22    /// Deliberately not called a *stack*, because
23    /// [`Ui::set_stack`](denise_ui::Ui::set_stack) already means top-to-bottom
24    /// and two meanings of one word in one workspace is a trap.
25    Layer,
26}
27
28impl Flow {
29    /// The main-axis extent of a rectangle under this flow.
30    const fn main(self, rect: Rect) -> i32 {
31        match self {
32            Self::Row => rect.width,
33            Self::Column | Self::Layer => rect.height,
34        }
35    }
36}
37
38/// How much of the main axis a child takes.
39///
40/// The cross axis is not a choice: a child fills it. See the crate docs for why
41/// that is the whole of the alignment story.
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum Sizing {
44    /// This many pixels, whatever else happens.
45    Fixed(i32),
46    /// A share of what is left once the fixed and hugging children have taken
47    /// theirs, in proportion to the weights of the other flex children.
48    ///
49    /// A weight of `0` takes nothing, which is a way to park a child without
50    /// removing it.
51    Flex(u16),
52    /// Whatever the child says it wants to be, through
53    /// [`Widget::measure`](denise_ui::Widget::measure).
54    ///
55    /// A node with no opinion — a panel, an image — hugs to nothing, which is
56    /// visible immediately rather than silently wrong.
57    Hug,
58}
59
60/// A container in an [`Arrange`], to add children to.
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub struct Group(usize);
63
64/// What one slot in the arena is.
65#[derive(Clone, Copy, Debug)]
66enum Kind {
67    /// A node of the tree.
68    Node(NodeId),
69    /// A container, by its index into `groups`.
70    Group(usize),
71}
72
73#[derive(Clone, Copy, Debug)]
74struct Child {
75    kind: Kind,
76    sizing: Sizing,
77}
78
79#[derive(Clone, Debug)]
80struct Container {
81    flow: Flow,
82    padding: i32,
83    gap: i32,
84    children: Vec<Child>,
85    /// The node whose rectangle this container occupies, if it has one. `None`
86    /// for the root, which is given a rectangle by the caller.
87    node: Option<NodeId>,
88}
89
90/// An arrangement: containers, their children, and how each child is sized.
91///
92/// Built once, applied as often as you like. Applying computes rectangles and
93/// writes them with [`Ui::set_layout`] — exactly what an application doing its
94/// own arithmetic would call, which is what keeps this a layer *over* the tree.
95///
96/// ```
97/// # use denise::{Rect, Size, theme};
98/// # use denise_ui::{Ui, Void, widgets::{Button, Label, Panel}};
99/// # use denise_arrange::{Arrange, Flow, Sizing};
100/// let mut ui: Ui<Void> = Ui::new(Size::new(400, 200), theme::DARK);
101/// let root = ui.root();
102/// let bar = ui.add(root, Panel::default(), Rect::new(0, 0, 400, 44)).unwrap();
103/// let title = ui.add(bar, Label::new("Settings"), Rect::ZERO).unwrap();
104/// let spacer = ui.add(bar, Panel::default(), Rect::ZERO).unwrap();
105/// let save = ui.add(bar, Button::<Void>::inert("Save"), Rect::ZERO).unwrap();
106///
107/// let mut arrange = Arrange::new(Flow::Row);
108/// let row = arrange.root();
109/// arrange.set_padding(row, 8);
110/// arrange.set_gap(row, 8);
111/// arrange.node(row, title, Sizing::Hug);      // as wide as its text
112/// arrange.node(row, spacer, Sizing::Flex(1)); // everything left over
113/// arrange.node(row, save, Sizing::Hug);       // as wide as its label
114///
115/// arrange.apply(&mut ui, Rect::new(0, 0, 400, 44));
116///
117/// // The three fill the row between the paddings, in order, with the gaps.
118/// let title_rect = ui.layout(title).unwrap();
119/// let save_rect = ui.layout(save).unwrap();
120/// assert_eq!(title_rect.x, 8, "the padding");
121/// assert_eq!(save_rect.right(), 400 - 8, "flush against the far padding");
122/// assert_eq!(title_rect.height, 44 - 8 * 2, "children fill the cross axis");
123/// ```
124#[derive(Clone, Debug)]
125pub struct Arrange {
126    groups: Vec<Container>,
127}
128
129impl Arrange {
130    /// A new arrangement whose root container flows this way.
131    #[must_use]
132    pub fn new(flow: Flow) -> Self {
133        Self {
134            groups: alloc::vec![Container {
135                flow,
136                padding: 0,
137                gap: 0,
138                children: Vec::new(),
139                node: None,
140            }],
141        }
142    }
143
144    /// The root container. Its rectangle is the one given to [`Arrange::apply`].
145    #[must_use]
146    pub const fn root(&self) -> Group {
147        Group(0)
148    }
149
150    /// Space inside a container, on every side.
151    pub fn set_padding(&mut self, group: Group, padding: i32) {
152        if let Some(container) = self.groups.get_mut(group.0) {
153            container.padding = padding.max(0);
154        }
155    }
156
157    /// Space between a container's children. Not before the first or after the
158    /// last — that is what padding is for.
159    pub fn set_gap(&mut self, group: Group, gap: i32) {
160        if let Some(container) = self.groups.get_mut(group.0) {
161            container.gap = gap.max(0);
162        }
163    }
164
165    /// Adds a node of the tree as a child.
166    pub fn node(&mut self, parent: Group, id: NodeId, sizing: Sizing) {
167        self.push(parent, Kind::Node(id), sizing);
168    }
169
170    /// Adds a nested container as a child, sized like any other.
171    ///
172    /// Give it `node` when a real node of the tree is the container — a
173    /// [`Panel`](denise_ui::widgets::Panel) holding a row of buttons — and that
174    /// node gets the container's rectangle. Give it `None` for a grouping that
175    /// exists only in this arrangement.
176    pub fn group(
177        &mut self,
178        parent: Group,
179        flow: Flow,
180        sizing: Sizing,
181        node: Option<NodeId>,
182    ) -> Group {
183        let index = self.groups.len();
184        self.groups.push(Container {
185            flow,
186            padding: 0,
187            gap: 0,
188            children: Vec::new(),
189            node,
190        });
191        self.push(parent, Kind::Group(index), sizing);
192        Group(index)
193    }
194
195    fn push(&mut self, parent: Group, kind: Kind, sizing: Sizing) {
196        if let Some(container) = self.groups.get_mut(parent.0) {
197            container.children.push(Child { kind, sizing });
198        }
199    }
200
201    /// Computes every rectangle and writes it with [`Ui::set_layout`].
202    ///
203    /// `within` is the root container's rectangle, in the coordinates the root's
204    /// children are placed in — which for a child of node `p` is `p`'s content
205    /// box with its origin at zero, the same space
206    /// [`Ui::set_layout`] already takes.
207    ///
208    /// Measuring happens here, so calling this again after content changed picks
209    /// the change up. Nothing caches, and nothing runs unless you call it.
210    pub fn apply<M>(&self, ui: &mut Ui<M>, within: Rect) {
211        self.lay_out(ui, 0, within);
212    }
213
214    /// Places one container's children inside `box_of`, and recurses.
215    fn lay_out<M>(&self, ui: &mut Ui<M>, index: usize, box_of: Rect) {
216        let Some(container) = self.groups.get(index) else {
217            return;
218        };
219        let pad = container.padding;
220        let content = Rect::from_edges(
221            box_of.x + pad,
222            box_of.y + pad,
223            (box_of.right() - pad).max(box_of.x + pad),
224            (box_of.bottom() - pad).max(box_of.y + pad),
225        );
226
227        if container.flow == Flow::Layer {
228            for child in &container.children {
229                self.place(ui, child, content);
230            }
231            return;
232        }
233
234        // Pass one: what each child takes of the main axis, before the leftover
235        // is shared out. A flex child takes nothing yet.
236        let cross = match container.flow {
237            Flow::Row => Offer::tall(content.height),
238            Flow::Column | Flow::Layer => Offer::wide(content.width),
239        };
240        let mut taken: Vec<i32> = Vec::with_capacity(container.children.len());
241        let mut weights: u32 = 0;
242        for child in &container.children {
243            let extent = match child.sizing {
244                Sizing::Fixed(n) => n.max(0),
245                Sizing::Flex(weight) => {
246                    weights += u32::from(weight);
247                    0
248                }
249                Sizing::Hug => self.hug(ui, child, container.flow, cross),
250            };
251            taken.push(extent);
252        }
253
254        let gaps = container
255            .gap
256            .saturating_mul((container.children.len().max(1) - 1) as i32);
257        let used: i32 = taken.iter().copied().sum::<i32>().saturating_add(gaps);
258        let spare = (container.flow.main(content) - used).max(0);
259
260        // Pass two: share the leftover among the flex children and place them.
261        // The last flex child takes the rounding, so the row ends flush against
262        // the padding rather than a pixel or two short of it.
263        let mut handed = 0;
264        let mut remaining = weights;
265        for (child, extent) in container.children.iter().zip(&mut taken) {
266            if let Sizing::Flex(weight) = child.sizing {
267                let weight = u32::from(weight);
268                *extent = if weight == 0 || weights == 0 {
269                    0
270                } else if weight == remaining {
271                    spare - handed
272                } else {
273                    let share = (i64::from(spare) * i64::from(weight) / i64::from(weights)) as i32;
274                    handed += share;
275                    share
276                };
277                remaining -= weight;
278            }
279        }
280
281        let mut at = match container.flow {
282            Flow::Row => content.x,
283            Flow::Column | Flow::Layer => content.y,
284        };
285        for (child, extent) in container.children.iter().zip(&taken) {
286            let rect = match container.flow {
287                Flow::Row => Rect::new(at, content.y, *extent, content.height),
288                Flow::Column | Flow::Layer => Rect::new(content.x, at, content.width, *extent),
289            };
290            self.place(ui, child, rect);
291            at = at.saturating_add(*extent).saturating_add(container.gap);
292        }
293    }
294
295    /// Writes one child's rectangle, and lays a container's own children out.
296    fn place<M>(&self, ui: &mut Ui<M>, child: &Child, rect: Rect) {
297        match child.kind {
298            Kind::Node(id) => ui.set_layout(id, rect),
299            Kind::Group(index) => {
300                if let Some(node) = self.groups.get(index).and_then(|c| c.node) {
301                    ui.set_layout(node, rect);
302                    // A container that *is* a node places its children inside
303                    // that node, so their coordinates start at its origin.
304                    self.lay_out(ui, index, Rect::new(0, 0, rect.width, rect.height));
305                } else {
306                    self.lay_out(ui, index, rect);
307                }
308            }
309        }
310    }
311
312    /// What a hugging child wants along the main axis.
313    fn hug<M>(&self, ui: &mut Ui<M>, child: &Child, flow: Flow, cross: Offer) -> i32 {
314        match child.kind {
315            Kind::Node(id) => {
316                let wanted = ui.measure(id, cross);
317                match flow {
318                    Flow::Row => wanted.width,
319                    Flow::Column | Flow::Layer => wanted.height,
320                }
321                .unwrap_or(0)
322                .max(0)
323            }
324            // A container hugs to the sum of what its own children want. A flex
325            // child inside one contributes nothing, because "a share of what is
326            // left" has no answer when nothing has been left yet.
327            Kind::Group(index) => self.natural(ui, index, flow, cross),
328        }
329    }
330
331    /// A container's own preferred extent along `flow`.
332    fn natural<M>(&self, ui: &mut Ui<M>, index: usize, flow: Flow, cross: Offer) -> i32 {
333        let Some(container) = self.groups.get(index) else {
334            return 0;
335        };
336        let pad = container.padding.saturating_mul(2);
337        let inner_cross = match (container.flow, cross) {
338            (Flow::Row, Offer { height, .. }) => Offer {
339                width: None,
340                height: height.map(|h| (h - pad).max(0)),
341            },
342            (_, Offer { width, .. }) => Offer {
343                width: width.map(|w| (w - pad).max(0)),
344                height: None,
345            },
346        };
347
348        let mut total = 0i32;
349        let mut widest = 0i32;
350        for child in &container.children {
351            let extent = match child.sizing {
352                Sizing::Fixed(n) => n.max(0),
353                Sizing::Flex(_) => 0,
354                Sizing::Hug => self.hug(ui, child, container.flow, inner_cross),
355            };
356            total = total.saturating_add(extent);
357            widest = widest.max(extent);
358        }
359        let gaps = container
360            .gap
361            .saturating_mul((container.children.len().max(1) - 1) as i32);
362
363        // Along its own flow the extents add up; across it, or for a layer, the
364        // largest child is the answer.
365        let along = match container.flow {
366            Flow::Layer => widest,
367            _ => total.saturating_add(gaps),
368        };
369        if container.flow == flow || container.flow == Flow::Layer {
370            along.saturating_add(pad)
371        } else {
372            widest.saturating_add(pad)
373        }
374    }
375}