1use std::collections::{HashMap, HashSet};
4
5use taffy::{
6 AlignItems, AlignSelf, AvailableSpace, Dimension, Display, FlexDirection, JustifyContent,
7 LayoutInput, LayoutOutput, LengthPercentage, LengthPercentageAuto, NodeId, Overflow, Rect,
8 Style, TaffyTree, TraversePartialTree, compute_leaf_layout, style_helpers::length,
9};
10
11use crate::geometry::{Frame, Point, Size};
12use crate::style::{Align, Axis, Justify, StackStyle};
13
14const LIVE_NODE: &str = "layout nodes are only used until they are removed";
15
16type TaffySize = taffy::Size<f32>;
17
18const TOP_EDGE_TOLERANCE: f32 = 0.5;
20
21pub(crate) enum Placed<'a, W> {
23 Frame(&'a W, Frame),
25 ScrollContent(&'a W, Size),
27}
28
29pub(crate) struct Layout<W> {
32 tree: TaffyTree<TaffySize>,
33 widgets: HashMap<NodeId, W>,
34 surfaces: HashSet<NodeId>,
36 scrolls: HashMap<NodeId, W>,
38 overlays: HashSet<NodeId>,
40}
41
42impl<W> Layout<W> {
43 pub(crate) fn new() -> Self {
44 Self {
45 tree: TaffyTree::new(),
46 widgets: HashMap::new(),
47 surfaces: HashSet::new(),
48 scrolls: HashMap::new(),
49 overlays: HashSet::new(),
50 }
51 }
52
53 pub(crate) fn add_root(&mut self) -> NodeId {
54 self.tree
55 .new_leaf(root_style(Some(Size::ZERO), 0.0))
56 .expect(LIVE_NODE)
57 }
58
59 pub(crate) fn add_widget(&mut self, widget: W, natural: Size) -> NodeId {
60 self.add_leaf(widget, natural, Style::default())
61 }
62
63 pub(crate) fn add_divider(&mut self, widget: W, natural: Size) -> NodeId {
65 let style = Style {
66 align_self: Some(AlignSelf::STRETCH),
67 flex_shrink: 0.0,
68 ..Style::default()
69 };
70 self.add_leaf(widget, natural, style)
71 }
72
73 pub(crate) fn add_overlay(&mut self, widget: W, natural: Size) -> NodeId {
77 let style = Style {
78 display: Display::None,
79 ..Style::default()
80 };
81 let node = self.add_leaf(widget, natural, style);
82 self.overlays.insert(node);
83 node
84 }
85
86 pub(crate) fn add_spacer(&mut self) -> NodeId {
88 let style = Style {
89 flex_grow: 1.0,
90 ..Style::default()
91 };
92 self.tree.new_leaf(style).expect(LIVE_NODE)
93 }
94
95 pub(crate) fn add_stack(&mut self, style: &StackStyle, surface: Option<W>) -> NodeId {
98 let node = self.tree.new_leaf(stack_style(style)).expect(LIVE_NODE);
99 if let Some(surface) = surface {
100 self.widgets.insert(node, surface);
101 self.surfaces.insert(node);
102 }
103 node
104 }
105
106 pub(crate) fn add_scroll(
111 &mut self,
112 style: &StackStyle,
113 surface: Option<W>,
114 scroll: W,
115 bar_width: f32,
116 ) -> NodeId {
117 let node = self.add_stack(style, surface);
118 let style = Style {
119 overflow: taffy::Point {
120 x: Overflow::Visible,
121 y: Overflow::Scroll,
122 },
123 scrollbar_width: bar_width,
124 flex_grow: 1.0,
125 flex_basis: Dimension::length(0.0),
126 min_size: taffy::Size {
127 width: LengthPercentageAuto::auto(),
128 height: LengthPercentageAuto::length(0.0),
129 },
130 ..stack_style(style)
131 };
132 self.tree.set_style(node, style).expect(LIVE_NODE);
133 self.scrolls.insert(node, scroll);
134 node
135 }
136
137 pub(crate) fn set_scroll_bar_width(&mut self, root: NodeId, width: f32) {
140 if self.scrolls.contains_key(&root) {
141 let mut style = self.tree.style(root).expect(LIVE_NODE).clone();
142 style.scrollbar_width = width;
143 self.tree.set_style(root, style).expect(LIVE_NODE);
144 }
145 let children: Vec<NodeId> = self.tree.child_ids(root).collect();
146 for child in children {
147 self.set_scroll_bar_width(child, width);
148 }
149 }
150
151 pub(crate) fn scroll_around(&self, node: NodeId) -> Option<&W> {
154 let mut current = Some(node);
155 while let Some(node) = current {
156 if let Some(scroll) = self.scrolls.get(&node) {
157 return Some(scroll);
158 }
159 current = self.tree.parent(node);
160 }
161 None
162 }
163
164 fn add_leaf(&mut self, widget: W, natural: Size, style: Style) -> NodeId {
165 let node = self
166 .tree
167 .new_leaf_with_context(style, to_taffy(natural))
168 .expect(LIVE_NODE);
169 self.widgets.insert(node, widget);
170 node
171 }
172
173 pub(crate) fn insert(&mut self, parent: NodeId, index: usize, child: NodeId) {
174 self.tree
175 .insert_child_at_index(parent, index, child)
176 .expect(LIVE_NODE);
177 }
178
179 pub(crate) fn set_content(&mut self, root: NodeId, content: NodeId) -> Option<NodeId> {
182 let previous = self.tree.child_ids(root).next();
183 let mut style = self.tree.style(content).expect(LIVE_NODE).clone();
184 style.flex_grow = 1.0;
185 self.tree.set_style(content, style).expect(LIVE_NODE);
186 self.tree.set_children(root, &[content]).expect(LIVE_NODE);
187 previous.filter(|&previous| previous != content)
188 }
189
190 pub(crate) fn remove(&mut self, node: NodeId) -> Vec<W> {
192 let mut removed = Vec::new();
193 self.remove_into(node, &mut removed);
194 removed
195 }
196
197 fn remove_into(&mut self, node: NodeId, removed: &mut Vec<W>) {
198 let children: Vec<NodeId> = self.tree.child_ids(node).collect();
199 for child in children {
200 self.remove_into(child, removed);
201 }
202 self.tree.remove(node).expect(LIVE_NODE);
203 self.surfaces.remove(&node);
204 self.overlays.remove(&node);
205 removed.extend(self.widgets.remove(&node));
206 removed.extend(self.scrolls.remove(&node));
207 }
208
209 pub(crate) fn widget(&self, node: NodeId) -> Option<&W> {
210 self.widgets.get(&node)
211 }
212
213 pub(crate) fn set_natural(&mut self, node: NodeId, natural: Size) -> bool {
215 let natural = to_taffy(natural);
216 let current = self
217 .tree
218 .get_node_context_mut(node)
219 .expect("only widget nodes have a natural size");
220 if *current == natural {
221 return false;
222 }
223 *current = natural;
224 self.tree.mark_dirty(node).expect(LIVE_NODE);
225 true
226 }
227
228 pub(crate) fn min_width(&mut self, root: NodeId, title_bar_inset: f32) -> f32 {
230 self.tree
231 .set_style(root, root_style(None, title_bar_inset))
232 .expect(LIVE_NODE);
233 let min_content = taffy::Size {
234 width: AvailableSpace::MinContent,
235 height: AvailableSpace::MinContent,
236 };
237 self.tree
238 .compute_layout_with_measure(root, min_content, measure_leaf)
239 .expect(LIVE_NODE);
240 self.tree.layout(root).expect(LIVE_NODE).size.width
241 }
242
243 pub(crate) fn layout_window(
247 &mut self,
248 root: NodeId,
249 viewport: Size,
250 title_bar_inset: f32,
251 mut apply: impl FnMut(Placed<'_, W>),
252 ) {
253 self.tree
254 .set_style(root, root_style(Some(viewport), title_bar_inset))
255 .expect(LIVE_NODE);
256 let available = taffy::Size {
257 width: AvailableSpace::Definite(viewport.width),
258 height: AvailableSpace::Definite(viewport.height),
259 };
260 self.tree
261 .compute_layout_with_measure(root, available, measure_leaf)
262 .expect(LIVE_NODE);
263 self.place(root, Point::default(), Some(title_bar_inset), &mut apply);
264 }
265
266 fn place(
270 &self,
271 node: NodeId,
272 parent_origin: Point,
273 title_bar_inset: Option<f32>,
274 apply: &mut impl FnMut(Placed<'_, W>),
275 ) {
276 let layout = self.tree.layout(node).expect(LIVE_NODE);
277 let origin = Point {
278 x: parent_origin.x + layout.location.x,
279 y: parent_origin.y + layout.location.y,
280 };
281 let frame = Frame {
282 origin,
283 size: Size::new(layout.size.width, layout.size.height),
284 };
285 if let Some(widget) = self.widgets.get(&node) {
286 let reaches_title_bar = self.surfaces.contains(&node)
287 && title_bar_inset
288 .is_some_and(|inset| (origin.y - inset).abs() < TOP_EDGE_TOLERANCE);
289 let frame = if reaches_title_bar {
290 under_title_bar(frame)
291 } else {
292 frame
293 };
294 apply(Placed::Frame(widget, frame));
295 }
296 let (content, title_bar_inset) = match self.scrolls.get(&node) {
297 Some(scroll) => {
298 apply(Placed::Frame(scroll, frame));
299 let size = Size::new(
300 layout.size.width - layout.scrollbar_size.width,
301 layout.size.height + layout.scroll_height(),
302 );
303 apply(Placed::ScrollContent(scroll, size));
304 let content = Frame {
305 origin: Point::default(),
306 size,
307 };
308 (content, None)
309 }
310 None => (frame, title_bar_inset),
311 };
312 for child in self.tree.child_ids(node) {
313 match self
314 .widgets
315 .get(&child)
316 .filter(|_| self.overlays.contains(&child))
317 {
318 Some(overlay) => apply(Placed::Frame(overlay, content)),
319 None => self.place(child, content.origin, title_bar_inset, apply),
320 }
321 }
322 }
323}
324
325fn under_title_bar(frame: Frame) -> Frame {
327 Frame {
328 origin: Point {
329 x: frame.origin.x,
330 y: 0.0,
331 },
332 size: Size::new(frame.size.width, frame.size.height + frame.origin.y),
333 }
334}
335
336fn measure_leaf(
337 inputs: LayoutInput,
338 _node: NodeId,
339 natural: Option<&mut TaffySize>,
340 style: &Style,
341) -> LayoutOutput {
342 let natural = natural.map_or(TaffySize::ZERO, |natural| *natural);
343 compute_leaf_layout(
344 inputs,
345 style,
346 |_, _| 0.0,
347 |known, _available| known.unwrap_or(natural),
348 )
349}
350
351fn root_style(viewport: Option<Size>, title_bar_inset: f32) -> Style {
354 Style {
355 display: Display::Flex,
356 flex_direction: FlexDirection::Column,
357 size: viewport.map_or(taffy::Size::auto(), |viewport| taffy::Size {
358 width: length(viewport.width),
359 height: length(viewport.height),
360 }),
361 padding: Rect {
362 top: LengthPercentage::length(title_bar_inset),
363 ..Rect::zero()
364 },
365 ..Style::default()
366 }
367}
368
369fn stack_style(stack: &StackStyle) -> Style {
370 Style {
371 display: Display::Flex,
372 flex_direction: match stack.axis {
373 Axis::Horizontal => FlexDirection::Row,
374 Axis::Vertical => FlexDirection::Column,
375 },
376 gap: taffy::Size::length(stack.spacing),
377 padding: Rect::length(stack.padding),
378 align_items: match stack.align {
379 Align::Stretch => AlignItems::STRETCH,
380 Align::Start => AlignItems::START,
381 Align::Center => AlignItems::CENTER,
382 Align::End => AlignItems::END,
383 },
384 justify_content: match stack.justify {
385 Justify::Start => JustifyContent::START,
386 Justify::Center => JustifyContent::CENTER,
387 Justify::End => JustifyContent::END,
388 Justify::SpaceBetween => JustifyContent::SPACE_BETWEEN,
389 },
390 flex_grow: if stack.grow { 1.0 } else { 0.0 },
391 size: taffy::Size {
392 width: stack_width(stack),
393 height: Dimension::auto(),
394 },
395 max_size: taffy::Size {
396 width: stack
397 .width_limit()
398 .map_or(LengthPercentageAuto::auto(), LengthPercentageAuto::length),
399 height: LengthPercentageAuto::auto(),
400 },
401 ..Style::default()
402 }
403}
404
405fn stack_width(stack: &StackStyle) -> Dimension {
408 match (stack.fixed_width(), stack.width_limit()) {
409 (Some(width), _) => Dimension::length(width),
410 (None, Some(_)) => Dimension::percent(1.0),
411 (None, None) => Dimension::auto(),
412 }
413}
414
415fn to_taffy(size: Size) -> TaffySize {
416 TaffySize {
417 width: size.width,
418 height: size.height,
419 }
420}