1use crate::{
2 AbsoluteLength, App, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style, Window,
3 point, size,
4};
5use collections::{FxHashMap, FxHashSet};
6use stacksafe::{StackSafe, stacksafe};
7use std::{fmt::Debug, ops::Range};
8use taffy::{
9 TaffyTree, TraversePartialTree as _,
10 geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize},
11 style::AvailableSpace as TaffyAvailableSpace,
12 tree::NodeId,
13};
14
15type NodeMeasureFn = StackSafe<
16 Box<
17 dyn FnMut(
18 Size<Option<Pixels>>,
19 Size<AvailableSpace>,
20 &mut Window,
21 &mut App,
22 ) -> Size<Pixels>,
23 >,
24>;
25
26struct NodeContext {
27 measure: NodeMeasureFn,
28}
29pub struct TaffyLayoutEngine {
30 taffy: TaffyTree<NodeContext>,
31 absolute_layout_bounds: FxHashMap<LayoutId, Bounds<Pixels>>,
32 computed_layouts: FxHashSet<LayoutId>,
33 layout_bounds_scratch_space: Vec<LayoutId>,
34}
35
36const EXPECT_MESSAGE: &str = "we should avoid taffy layout errors by construction if possible";
37
38impl TaffyLayoutEngine {
39 pub fn new() -> Self {
40 let mut taffy = TaffyTree::new();
41 taffy.enable_rounding();
42 TaffyLayoutEngine {
43 taffy,
44 absolute_layout_bounds: FxHashMap::default(),
45 computed_layouts: FxHashSet::default(),
46 layout_bounds_scratch_space: Vec::new(),
47 }
48 }
49
50 pub fn clear(&mut self) {
51 self.taffy.clear();
52 self.absolute_layout_bounds.clear();
53 self.computed_layouts.clear();
54 }
55
56 pub fn request_layout(
57 &mut self,
58 style: Style,
59 rem_size: Pixels,
60 scale_factor: f32,
61 children: &[LayoutId],
62 ) -> LayoutId {
63 let taffy_style = style.to_taffy(rem_size, scale_factor);
64
65 if children.is_empty() {
66 self.taffy
67 .new_leaf(taffy_style)
68 .expect(EXPECT_MESSAGE)
69 .into()
70 } else {
71 self.taffy
72 // This is safe because LayoutId is repr(transparent) to taffy::tree::NodeId.
73 .new_with_children(taffy_style, LayoutId::to_taffy_slice(children))
74 .expect(EXPECT_MESSAGE)
75 .into()
76 }
77 }
78
79 pub fn request_measured_layout(
80 &mut self,
81 style: Style,
82 rem_size: Pixels,
83 scale_factor: f32,
84 measure: impl FnMut(
85 Size<Option<Pixels>>,
86 Size<AvailableSpace>,
87 &mut Window,
88 &mut App,
89 ) -> Size<Pixels>
90 + 'static,
91 ) -> LayoutId {
92 let taffy_style = style.to_taffy(rem_size, scale_factor);
93
94 self.taffy
95 .new_leaf_with_context(
96 taffy_style,
97 NodeContext {
98 measure: StackSafe::new(Box::new(measure)),
99 },
100 )
101 .expect(EXPECT_MESSAGE)
102 .into()
103 }
104
105 // Used to understand performance
106 #[allow(dead_code)]
107 fn count_all_children(&self, parent: LayoutId) -> anyhow::Result<u32> {
108 let mut count = 0;
109
110 for child in self.taffy.children(parent.0)? {
111 // Count this child.
112 count += 1;
113
114 // Count all of this child's children.
115 count += self.count_all_children(LayoutId(child))?
116 }
117
118 Ok(count)
119 }
120
121 // Used to understand performance
122 #[allow(dead_code)]
123 fn max_depth(&self, depth: u32, parent: LayoutId) -> anyhow::Result<u32> {
124 println!(
125 "{parent:?} at depth {depth} has {} children",
126 self.taffy.child_count(parent.0)
127 );
128
129 let mut max_child_depth = 0;
130
131 for child in self.taffy.children(parent.0)? {
132 max_child_depth = std::cmp::max(max_child_depth, self.max_depth(0, LayoutId(child))?);
133 }
134
135 Ok(depth + 1 + max_child_depth)
136 }
137
138 // Used to understand performance
139 #[allow(dead_code)]
140 fn get_edges(&self, parent: LayoutId) -> anyhow::Result<Vec<(LayoutId, LayoutId)>> {
141 let mut edges = Vec::new();
142
143 for child in self.taffy.children(parent.0)? {
144 edges.push((parent, LayoutId(child)));
145
146 edges.extend(self.get_edges(LayoutId(child))?);
147 }
148
149 Ok(edges)
150 }
151
152 #[stacksafe]
153 pub fn compute_layout(
154 &mut self,
155 id: LayoutId,
156 available_space: Size<AvailableSpace>,
157 window: &mut Window,
158 cx: &mut App,
159 ) {
160 // Leaving this here until we have a better instrumentation approach.
161 // println!("Laying out {} children", self.count_all_children(id)?);
162 // println!("Max layout depth: {}", self.max_depth(0, id)?);
163
164 // Output the edges (branches) of the tree in Mermaid format for visualization.
165 // println!("Edges:");
166 // for (a, b) in self.get_edges(id)? {
167 // println!("N{} --> N{}", u64::from(a), u64::from(b));
168 // }
169 //
170
171 if !self.computed_layouts.insert(id) {
172 let mut stack = &mut self.layout_bounds_scratch_space;
173 stack.push(id);
174 while let Some(id) = stack.pop() {
175 self.absolute_layout_bounds.remove(&id);
176 stack.extend(
177 self.taffy
178 .children(id.into())
179 .expect(EXPECT_MESSAGE)
180 .into_iter()
181 .map(LayoutId::from),
182 );
183 }
184 }
185
186 let scale_factor = window.scale_factor();
187
188 let transform = |v: AvailableSpace| match v {
189 AvailableSpace::Definite(pixels) => {
190 AvailableSpace::Definite(Pixels(pixels.0 * scale_factor))
191 }
192 AvailableSpace::MinContent => AvailableSpace::MinContent,
193 AvailableSpace::MaxContent => AvailableSpace::MaxContent,
194 };
195 let available_space = size(
196 transform(available_space.width),
197 transform(available_space.height),
198 );
199
200 self.taffy
201 .compute_layout_with_measure(
202 id.into(),
203 available_space.into(),
204 |known_dimensions, available_space, _id, node_context, _style| {
205 let Some(node_context) = node_context else {
206 return taffy::geometry::Size::default();
207 };
208
209 let known_dimensions = Size {
210 width: known_dimensions.width.map(|e| Pixels(e / scale_factor)),
211 height: known_dimensions.height.map(|e| Pixels(e / scale_factor)),
212 };
213
214 let available_space: Size<AvailableSpace> = available_space.into();
215 let untransform = |ev: AvailableSpace| match ev {
216 AvailableSpace::Definite(pixels) => {
217 AvailableSpace::Definite(Pixels(pixels.0 / scale_factor))
218 }
219 AvailableSpace::MinContent => AvailableSpace::MinContent,
220 AvailableSpace::MaxContent => AvailableSpace::MaxContent,
221 };
222 let available_space = size(
223 untransform(available_space.width),
224 untransform(available_space.height),
225 );
226
227 let a: Size<Pixels> =
228 (node_context.measure)(known_dimensions, available_space, window, cx);
229 size(a.width.0 * scale_factor, a.height.0 * scale_factor).into()
230 },
231 )
232 .expect(EXPECT_MESSAGE);
233 }
234
235 pub fn layout_bounds(&mut self, id: LayoutId, scale_factor: f32) -> Bounds<Pixels> {
236 if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
237 return layout;
238 }
239
240 let layout = self.taffy.layout(id.into()).expect(EXPECT_MESSAGE);
241 let mut bounds = Bounds {
242 origin: point(
243 Pixels(layout.location.x / scale_factor),
244 Pixels(layout.location.y / scale_factor),
245 ),
246 size: size(
247 Pixels(layout.size.width / scale_factor),
248 Pixels(layout.size.height / scale_factor),
249 ),
250 };
251
252 if let Some(parent_id) = self.taffy.parent(id.0) {
253 let parent_bounds = self.layout_bounds(parent_id.into(), scale_factor);
254 bounds.origin += parent_bounds.origin;
255 }
256 self.absolute_layout_bounds.insert(id, bounds);
257
258 bounds
259 }
260}
261
262/// A unique identifier for a layout node, generated when requesting a layout from Taffy
263#[derive(Copy, Clone, Eq, PartialEq, Debug)]
264#[repr(transparent)]
265pub struct LayoutId(NodeId);
266
267impl LayoutId {
268 fn to_taffy_slice(node_ids: &[Self]) -> &[taffy::NodeId] {
269 // SAFETY: LayoutId is repr(transparent) to taffy::tree::NodeId.
270 unsafe { std::mem::transmute::<&[LayoutId], &[taffy::NodeId]>(node_ids) }
271 }
272}
273
274impl std::hash::Hash for LayoutId {
275 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
276 u64::from(self.0).hash(state);
277 }
278}
279
280impl From<NodeId> for LayoutId {
281 fn from(node_id: NodeId) -> Self {
282 Self(node_id)
283 }
284}
285
286impl From<LayoutId> for NodeId {
287 fn from(layout_id: LayoutId) -> NodeId {
288 layout_id.0
289 }
290}
291
292trait ToTaffy<Output> {
293 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> Output;
294}
295
296impl ToTaffy<taffy::style::Style> for Style {
297 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Style {
298 use taffy::style_helpers::{fr, length, minmax, repeat};
299
300 fn to_grid_line(
301 placement: &Range<crate::GridPlacement>,
302 ) -> taffy::Line<taffy::GridPlacement> {
303 taffy::Line {
304 start: placement.start.into(),
305 end: placement.end.into(),
306 }
307 }
308
309 fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
310 unit: &Option<u16>,
311 ) -> Vec<taffy::GridTemplateComponent<T>> {
312 // grid-template-columns: repeat(<number>, minmax(0, 1fr));
313 unit.map(|count| vec![repeat(count, vec![minmax(length(0.0), fr(1.0))])])
314 .unwrap_or_default()
315 }
316
317 taffy::style::Style {
318 display: self.display.into(),
319 overflow: self.overflow.into(),
320 scrollbar_width: self.scrollbar_width.to_taffy(rem_size, scale_factor),
321 position: self.position.into(),
322 inset: self.inset.to_taffy(rem_size, scale_factor),
323 size: self.size.to_taffy(rem_size, scale_factor),
324 min_size: self.min_size.to_taffy(rem_size, scale_factor),
325 max_size: self.max_size.to_taffy(rem_size, scale_factor),
326 aspect_ratio: self.aspect_ratio,
327 margin: self.margin.to_taffy(rem_size, scale_factor),
328 padding: self.padding.to_taffy(rem_size, scale_factor),
329 border: self.border_widths.to_taffy(rem_size, scale_factor),
330 align_items: self.align_items.map(|x| x.into()),
331 align_self: self.align_self.map(|x| x.into()),
332 align_content: self.align_content.map(|x| x.into()),
333 justify_content: self.justify_content.map(|x| x.into()),
334 gap: self.gap.to_taffy(rem_size, scale_factor),
335 flex_direction: self.flex_direction.into(),
336 flex_wrap: self.flex_wrap.into(),
337 flex_basis: self.flex_basis.to_taffy(rem_size, scale_factor),
338 flex_grow: self.flex_grow,
339 flex_shrink: self.flex_shrink,
340 grid_template_rows: to_grid_repeat(&self.grid_rows),
341 grid_template_columns: to_grid_repeat(&self.grid_cols),
342 grid_row: self
343 .grid_location
344 .as_ref()
345 .map(|location| to_grid_line(&location.row))
346 .unwrap_or_default(),
347 grid_column: self
348 .grid_location
349 .as_ref()
350 .map(|location| to_grid_line(&location.column))
351 .unwrap_or_default(),
352 ..Default::default()
353 }
354 }
355}
356
357impl ToTaffy<f32> for AbsoluteLength {
358 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> f32 {
359 match self {
360 AbsoluteLength::Pixels(pixels) => {
361 let pixels: f32 = pixels.into();
362 pixels * scale_factor
363 }
364 AbsoluteLength::Rems(rems) => {
365 let pixels: f32 = (*rems * rem_size).into();
366 pixels * scale_factor
367 }
368 }
369 }
370}
371
372impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
373 fn to_taffy(
374 &self,
375 rem_size: Pixels,
376 scale_factor: f32,
377 ) -> taffy::prelude::LengthPercentageAuto {
378 match self {
379 Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
380 Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
381 }
382 }
383}
384
385impl ToTaffy<taffy::style::Dimension> for Length {
386 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::prelude::Dimension {
387 match self {
388 Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
389 Length::Auto => taffy::prelude::Dimension::auto(),
390 }
391 }
392}
393
394impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
395 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
396 match self {
397 DefiniteLength::Absolute(length) => match length {
398 AbsoluteLength::Pixels(pixels) => {
399 let pixels: f32 = pixels.into();
400 taffy::style::LengthPercentage::length(pixels * scale_factor)
401 }
402 AbsoluteLength::Rems(rems) => {
403 let pixels: f32 = (*rems * rem_size).into();
404 taffy::style::LengthPercentage::length(pixels * scale_factor)
405 }
406 },
407 DefiniteLength::Fraction(fraction) => {
408 taffy::style::LengthPercentage::percent(*fraction)
409 }
410 }
411 }
412}
413
414impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
415 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
416 match self {
417 DefiniteLength::Absolute(length) => match length {
418 AbsoluteLength::Pixels(pixels) => {
419 let pixels: f32 = pixels.into();
420 taffy::style::LengthPercentageAuto::length(pixels * scale_factor)
421 }
422 AbsoluteLength::Rems(rems) => {
423 let pixels: f32 = (*rems * rem_size).into();
424 taffy::style::LengthPercentageAuto::length(pixels * scale_factor)
425 }
426 },
427 DefiniteLength::Fraction(fraction) => {
428 taffy::style::LengthPercentageAuto::percent(*fraction)
429 }
430 }
431 }
432}
433
434impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
435 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
436 match self {
437 DefiniteLength::Absolute(length) => match length {
438 AbsoluteLength::Pixels(pixels) => {
439 let pixels: f32 = pixels.into();
440 taffy::style::Dimension::length(pixels * scale_factor)
441 }
442 AbsoluteLength::Rems(rems) => {
443 taffy::style::Dimension::length((*rems * rem_size * scale_factor).into())
444 }
445 },
446 DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
447 }
448 }
449}
450
451impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
452 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
453 match self {
454 AbsoluteLength::Pixels(pixels) => {
455 let pixels: f32 = pixels.into();
456 taffy::style::LengthPercentage::length(pixels * scale_factor)
457 }
458 AbsoluteLength::Rems(rems) => {
459 let pixels: f32 = (*rems * rem_size).into();
460 taffy::style::LengthPercentage::length(pixels * scale_factor)
461 }
462 }
463 }
464}
465
466impl<T, T2> From<TaffyPoint<T>> for Point<T2>
467where
468 T: Into<T2>,
469 T2: Clone + Debug + Default + PartialEq,
470{
471 fn from(point: TaffyPoint<T>) -> Point<T2> {
472 Point {
473 x: point.x.into(),
474 y: point.y.into(),
475 }
476 }
477}
478
479impl<T, T2> From<Point<T>> for TaffyPoint<T2>
480where
481 T: Into<T2> + Clone + Debug + Default + PartialEq,
482{
483 fn from(val: Point<T>) -> Self {
484 TaffyPoint {
485 x: val.x.into(),
486 y: val.y.into(),
487 }
488 }
489}
490
491impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
492where
493 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
494{
495 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffySize<U> {
496 TaffySize {
497 width: self.width.to_taffy(rem_size, scale_factor),
498 height: self.height.to_taffy(rem_size, scale_factor),
499 }
500 }
501}
502
503impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
504where
505 T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
506{
507 fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffyRect<U> {
508 TaffyRect {
509 top: self.top.to_taffy(rem_size, scale_factor),
510 right: self.right.to_taffy(rem_size, scale_factor),
511 bottom: self.bottom.to_taffy(rem_size, scale_factor),
512 left: self.left.to_taffy(rem_size, scale_factor),
513 }
514 }
515}
516
517impl<T, U> From<TaffySize<T>> for Size<U>
518where
519 T: Into<U>,
520 U: Clone + Debug + Default + PartialEq,
521{
522 fn from(taffy_size: TaffySize<T>) -> Self {
523 Size {
524 width: taffy_size.width.into(),
525 height: taffy_size.height.into(),
526 }
527 }
528}
529
530impl<T, U> From<Size<T>> for TaffySize<U>
531where
532 T: Into<U> + Clone + Debug + Default + PartialEq,
533{
534 fn from(size: Size<T>) -> Self {
535 TaffySize {
536 width: size.width.into(),
537 height: size.height.into(),
538 }
539 }
540}
541
542/// The space available for an element to be laid out in
543#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
544pub enum AvailableSpace {
545 /// The amount of space available is the specified number of pixels
546 Definite(Pixels),
547 /// The amount of space available is indefinite and the node should be laid out under a min-content constraint
548 #[default]
549 MinContent,
550 /// The amount of space available is indefinite and the node should be laid out under a max-content constraint
551 MaxContent,
552}
553
554impl AvailableSpace {
555 /// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`.
556 ///
557 /// This function is useful when you want to create a `Size` with the minimum content constraints
558 /// for both dimensions.
559 ///
560 /// # Examples
561 ///
562 /// ```
563 /// use gpui::AvailableSpace;
564 /// let min_content_size = AvailableSpace::min_size();
565 /// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
566 /// assert_eq!(min_content_size.height, AvailableSpace::MinContent);
567 /// ```
568 pub const fn min_size() -> Size<Self> {
569 Size {
570 width: Self::MinContent,
571 height: Self::MinContent,
572 }
573 }
574}
575
576impl From<AvailableSpace> for TaffyAvailableSpace {
577 fn from(space: AvailableSpace) -> TaffyAvailableSpace {
578 match space {
579 AvailableSpace::Definite(Pixels(value)) => TaffyAvailableSpace::Definite(value),
580 AvailableSpace::MinContent => TaffyAvailableSpace::MinContent,
581 AvailableSpace::MaxContent => TaffyAvailableSpace::MaxContent,
582 }
583 }
584}
585
586impl From<TaffyAvailableSpace> for AvailableSpace {
587 fn from(space: TaffyAvailableSpace) -> AvailableSpace {
588 match space {
589 TaffyAvailableSpace::Definite(value) => AvailableSpace::Definite(Pixels(value)),
590 TaffyAvailableSpace::MinContent => AvailableSpace::MinContent,
591 TaffyAvailableSpace::MaxContent => AvailableSpace::MaxContent,
592 }
593 }
594}
595
596impl From<Pixels> for AvailableSpace {
597 fn from(pixels: Pixels) -> Self {
598 AvailableSpace::Definite(pixels)
599 }
600}
601
602impl From<Size<Pixels>> for Size<AvailableSpace> {
603 fn from(size: Size<Pixels>) -> Self {
604 Size {
605 width: AvailableSpace::Definite(size.width),
606 height: AvailableSpace::Definite(size.height),
607 }
608 }
609}