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}