Another project
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(ui): taffy measure engine

Lewis: May this revision serve well! <lu5a@proton.me>

+626
+626
crates/bone-ui/src/layout/engine.rs
··· 1 + use core::num::NonZeroU16; 2 + 3 + use taffy::geometry::{Point as TaffyPoint, Rect as TaffyRect, Size as TaffySize}; 4 + use taffy::prelude::{ 5 + AlignItems, AvailableSpace, Dimension, Display, FlexDirection, GridPlacement, 6 + GridTemplateComponent, JustifyContent, Layout as TaffyLayout, Line as TaffyLine, NodeId, Style, 7 + TaffyAuto, TaffyTree, auto, fr, length, 8 + }; 9 + use taffy::style::Overflow; 10 + 11 + use super::axis::{Axis, CrossAxisAlign, MainAxisJustify}; 12 + use super::dock::{DockNode, DockState, PanelId, SplitFraction}; 13 + use super::geometry::{EdgeInsets, LayoutPos, LayoutPx, LayoutRect, LayoutSize}; 14 + use super::primitives::{DockPanel, GridChild, Layout}; 15 + use super::retained::{RetainedLayout, ScrollOffset}; 16 + use super::scroll::{ScrollAxes, clamp_scroll}; 17 + use super::track::{FlexWeight, GridLine, GridLineRef, GridTrack, TrackName, TrackSize}; 18 + use crate::theme::Spacing; 19 + use crate::widget_id::WidgetId; 20 + 21 + #[derive(Debug, thiserror::Error)] 22 + pub enum LayoutError { 23 + #[error("dock host references panel {0} that is not provided")] 24 + MissingDockPanel(PanelId), 25 + #[error("dock tab node has no panels")] 26 + EmptyDockTabs, 27 + #[error("grid span references unknown track name {0}")] 28 + UnknownTrackName(String), 29 + #[error("grid declares track name {0} more than once")] 30 + DuplicateTrackName(String), 31 + #[error("grid column span resolves to non-increasing lines")] 32 + GridColumnSpanNotIncreasing, 33 + #[error("grid row span resolves to non-increasing lines")] 34 + GridRowSpanNotIncreasing, 35 + #[error("dock host does not yet render floating surfaces")] 36 + UnsupportedFloatingSurfaces, 37 + #[error("taffy: {0}")] 38 + Taffy(#[from] taffy::TaffyError), 39 + } 40 + 41 + #[derive(Clone, Debug, PartialEq)] 42 + pub enum NodeKind { 43 + Pass, 44 + Leaf(WidgetId), 45 + ScrollRegion { 46 + id: WidgetId, 47 + axes: ScrollAxes, 48 + offset: ScrollOffset, 49 + }, 50 + Splitter { 51 + id: WidgetId, 52 + axis: Axis, 53 + fraction: SplitFraction, 54 + }, 55 + DockHost { 56 + id: WidgetId, 57 + }, 58 + DockSplit { 59 + axis: Axis, 60 + fraction: SplitFraction, 61 + }, 62 + DockTabStrip { 63 + active: PanelId, 64 + tabs: Vec<PanelId>, 65 + }, 66 + DockPanel { 67 + id: PanelId, 68 + }, 69 + } 70 + 71 + #[derive(Clone, Debug)] 72 + pub struct SolvedNode { 73 + pub kind: NodeKind, 74 + pub rect: LayoutRect, 75 + pub content_size: LayoutSize, 76 + pub children: Vec<SolvedIndex>, 77 + } 78 + 79 + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] 80 + pub struct SolvedIndex(usize); 81 + 82 + #[derive(Clone, Debug)] 83 + pub struct SolvedLayout { 84 + pub nodes: Vec<SolvedNode>, 85 + pub root: SolvedIndex, 86 + } 87 + 88 + impl SolvedLayout { 89 + #[must_use] 90 + pub fn root_node(&self) -> &SolvedNode { 91 + &self.nodes[self.root.0] 92 + } 93 + 94 + #[must_use] 95 + pub fn node(&self, index: SolvedIndex) -> &SolvedNode { 96 + &self.nodes[index.0] 97 + } 98 + } 99 + 100 + pub fn measure( 101 + layout: &Layout, 102 + available: LayoutSize, 103 + retained: &RetainedLayout, 104 + ) -> Result<SolvedLayout, LayoutError> { 105 + let mut engine = Engine::new(); 106 + let root_taffy = engine.lower(layout, retained)?; 107 + let mut root_style = engine.style_of(root_taffy)?; 108 + root_style.size = TaffySize { 109 + width: length(available.width.value()), 110 + height: length(available.height.value()), 111 + }; 112 + engine.tree.set_style(root_taffy, root_style)?; 113 + engine.tree.compute_layout( 114 + root_taffy, 115 + TaffySize { 116 + width: AvailableSpace::Definite(available.width.value()), 117 + height: AvailableSpace::Definite(available.height.value()), 118 + }, 119 + )?; 120 + let root = engine.collect(root_taffy, LayoutPos::ORIGIN)?; 121 + Ok(SolvedLayout { 122 + nodes: engine.solved, 123 + root, 124 + }) 125 + } 126 + 127 + struct Engine { 128 + tree: TaffyTree<NodeKind>, 129 + solved: Vec<SolvedNode>, 130 + } 131 + 132 + impl Engine { 133 + fn new() -> Self { 134 + Self { 135 + tree: TaffyTree::new(), 136 + solved: Vec::new(), 137 + } 138 + } 139 + 140 + fn style_of(&self, node: NodeId) -> Result<Style, LayoutError> { 141 + Ok(self.tree.style(node)?.clone()) 142 + } 143 + 144 + fn apply_split_grow( 145 + &mut self, 146 + a: NodeId, 147 + b: NodeId, 148 + fraction: SplitFraction, 149 + ) -> Result<(), LayoutError> { 150 + let pa = fraction.value(); 151 + let pb = 1.0 - pa; 152 + let mut sa = self.style_of(a)?; 153 + let mut sb = self.style_of(b)?; 154 + sa.flex_grow = pa; 155 + sa.flex_shrink = 0.0; 156 + sa.flex_basis = length(0.0); 157 + sb.flex_grow = pb; 158 + sb.flex_shrink = 0.0; 159 + sb.flex_basis = length(0.0); 160 + self.tree.set_style(a, sa)?; 161 + self.tree.set_style(b, sb)?; 162 + Ok(()) 163 + } 164 + } 165 + 166 + const fn flex_direction_for(axis: Axis) -> FlexDirection { 167 + match axis { 168 + Axis::Horizontal => FlexDirection::Row, 169 + Axis::Vertical => FlexDirection::Column, 170 + } 171 + } 172 + 173 + fn fill_along_main(style: Style) -> Style { 174 + Style { 175 + flex_grow: 1.0, 176 + flex_shrink: 1.0, 177 + flex_basis: length(0.0), 178 + ..style 179 + } 180 + } 181 + 182 + fn flex_stretch_style(axis: Axis) -> Style { 183 + fill_along_main(Style { 184 + display: Display::Flex, 185 + flex_direction: flex_direction_for(axis), 186 + align_items: Some(AlignItems::Stretch), 187 + ..Style::DEFAULT 188 + }) 189 + } 190 + 191 + impl Engine { 192 + fn lower(&mut self, layout: &Layout, retained: &RetainedLayout) -> Result<NodeId, LayoutError> { 193 + match layout { 194 + Layout::Row { 195 + gap, 196 + justify, 197 + cross, 198 + children, 199 + } => self.flex(Axis::Horizontal, *gap, *justify, *cross, children, retained), 200 + Layout::Column { 201 + gap, 202 + justify, 203 + cross, 204 + children, 205 + } => self.flex(Axis::Vertical, *gap, *justify, *cross, children, retained), 206 + Layout::Stack { children } => self.stack(children, retained), 207 + Layout::Grid { 208 + columns, 209 + rows, 210 + column_gap, 211 + row_gap, 212 + children, 213 + } => self.grid(columns, rows, *column_gap, *row_gap, children, retained), 214 + Layout::Inset { padding, child } => self.inset(*padding, child, retained), 215 + Layout::Spacer { weight } => self.spacer(*weight), 216 + Layout::Gap { size } => self.gap(*size), 217 + Layout::ScrollRegion { id, axes, child } => { 218 + self.scroll_region(*id, *axes, child, retained) 219 + } 220 + Layout::Splitter { 221 + id, 222 + axis, 223 + default_fraction, 224 + a, 225 + b, 226 + } => self.splitter(*id, *axis, *default_fraction, a, b, retained), 227 + Layout::DockHost { 228 + id, 229 + state, 230 + panels, 231 + tab_strip_height, 232 + } => self.dock_host(*id, state, panels, *tab_strip_height, retained), 233 + Layout::Leaf { id } => { 234 + self.leaf_node(NodeKind::Leaf(*id), fill_along_main(Style::DEFAULT)) 235 + } 236 + } 237 + } 238 + 239 + fn leaf_node(&mut self, kind: NodeKind, style: Style) -> Result<NodeId, LayoutError> { 240 + Ok(self.tree.new_leaf_with_context(style, kind)?) 241 + } 242 + 243 + fn parent( 244 + &mut self, 245 + kind: NodeKind, 246 + style: Style, 247 + children: &[NodeId], 248 + ) -> Result<NodeId, LayoutError> { 249 + let id = self.tree.new_with_children(style, children)?; 250 + self.tree.set_node_context(id, Some(kind))?; 251 + Ok(id) 252 + } 253 + 254 + fn flex( 255 + &mut self, 256 + axis: Axis, 257 + gap: Spacing, 258 + justify: MainAxisJustify, 259 + cross: CrossAxisAlign, 260 + children: &[Layout], 261 + retained: &RetainedLayout, 262 + ) -> Result<NodeId, LayoutError> { 263 + let kids = children 264 + .iter() 265 + .map(|c| self.lower(c, retained)) 266 + .collect::<Result<Vec<_>, _>>()?; 267 + let style = fill_along_main(Style { 268 + display: Display::Flex, 269 + flex_direction: flex_direction_for(axis), 270 + gap: TaffySize { 271 + width: length(gap.value_px()), 272 + height: length(gap.value_px()), 273 + }, 274 + justify_content: Some(match justify { 275 + MainAxisJustify::Start => JustifyContent::FlexStart, 276 + MainAxisJustify::Center => JustifyContent::Center, 277 + MainAxisJustify::End => JustifyContent::FlexEnd, 278 + MainAxisJustify::SpaceBetween => JustifyContent::SpaceBetween, 279 + MainAxisJustify::SpaceAround => JustifyContent::SpaceAround, 280 + }), 281 + align_items: Some(match cross { 282 + CrossAxisAlign::Start => AlignItems::FlexStart, 283 + CrossAxisAlign::Center => AlignItems::Center, 284 + CrossAxisAlign::End => AlignItems::FlexEnd, 285 + CrossAxisAlign::Stretch => AlignItems::Stretch, 286 + }), 287 + ..Style::DEFAULT 288 + }); 289 + self.parent(NodeKind::Pass, style, &kids) 290 + } 291 + 292 + fn stack( 293 + &mut self, 294 + children: &[Layout], 295 + retained: &RetainedLayout, 296 + ) -> Result<NodeId, LayoutError> { 297 + let kids = children 298 + .iter() 299 + .map(|c| self.lower(c, retained)) 300 + .collect::<Result<Vec<_>, _>>()?; 301 + let cell = TaffyLine { 302 + start: GridPlacement::Line(1.into()), 303 + end: GridPlacement::Line(2.into()), 304 + }; 305 + let style = fill_along_main(Style { 306 + display: Display::Grid, 307 + grid_template_columns: vec![fr::<f32, GridTemplateComponent<String>>(1.0)], 308 + grid_template_rows: vec![fr::<f32, GridTemplateComponent<String>>(1.0)], 309 + justify_items: Some(AlignItems::Stretch), 310 + align_items: Some(AlignItems::Stretch), 311 + ..Style::DEFAULT 312 + }); 313 + kids.iter() 314 + .try_fold((), |(), kid| -> Result<(), LayoutError> { 315 + let mut s = self.style_of(*kid)?; 316 + s.grid_row = cell.clone(); 317 + s.grid_column = cell.clone(); 318 + self.tree.set_style(*kid, s)?; 319 + Ok(()) 320 + })?; 321 + self.parent(NodeKind::Pass, style, &kids) 322 + } 323 + 324 + fn grid( 325 + &mut self, 326 + columns: &[GridTrack], 327 + rows: &[GridTrack], 328 + column_gap: Spacing, 329 + row_gap: Spacing, 330 + children: &[GridChild], 331 + retained: &RetainedLayout, 332 + ) -> Result<NodeId, LayoutError> { 333 + let kids = children 334 + .iter() 335 + .map(|gc| { 336 + let id = self.lower(&gc.child, retained)?; 337 + let mut style = self.style_of(id)?; 338 + let col_start = resolve_line(&gc.span.column_start, columns)?; 339 + let col_end = resolve_line(&gc.span.column_end, columns)?; 340 + if col_end.get() <= col_start.get() { 341 + return Err(LayoutError::GridColumnSpanNotIncreasing); 342 + } 343 + let row_start = resolve_line(&gc.span.row_start, rows)?; 344 + let row_end = resolve_line(&gc.span.row_end, rows)?; 345 + if row_end.get() <= row_start.get() { 346 + return Err(LayoutError::GridRowSpanNotIncreasing); 347 + } 348 + style.grid_column = TaffyLine { 349 + start: GridPlacement::Line(col_start.taffy_index().into()), 350 + end: GridPlacement::Line(col_end.taffy_index().into()), 351 + }; 352 + style.grid_row = TaffyLine { 353 + start: GridPlacement::Line(row_start.taffy_index().into()), 354 + end: GridPlacement::Line(row_end.taffy_index().into()), 355 + }; 356 + self.tree.set_style(id, style)?; 357 + Ok::<_, LayoutError>(id) 358 + }) 359 + .collect::<Result<Vec<_>, _>>()?; 360 + let style = fill_along_main(Style { 361 + display: Display::Grid, 362 + grid_template_columns: columns.iter().map(track_to_taffy).collect(), 363 + grid_template_rows: rows.iter().map(track_to_taffy).collect(), 364 + gap: TaffySize { 365 + width: length(column_gap.value_px()), 366 + height: length(row_gap.value_px()), 367 + }, 368 + ..Style::DEFAULT 369 + }); 370 + self.parent(NodeKind::Pass, style, &kids) 371 + } 372 + 373 + fn inset( 374 + &mut self, 375 + padding: EdgeInsets, 376 + child: &Layout, 377 + retained: &RetainedLayout, 378 + ) -> Result<NodeId, LayoutError> { 379 + let kid = self.lower(child, retained)?; 380 + let style = Style { 381 + padding: TaffyRect { 382 + left: length(padding.left.value_px()), 383 + right: length(padding.right.value_px()), 384 + top: length(padding.top.value_px()), 385 + bottom: length(padding.bottom.value_px()), 386 + }, 387 + ..flex_stretch_style(Axis::Vertical) 388 + }; 389 + self.parent(NodeKind::Pass, style, &[kid]) 390 + } 391 + 392 + fn spacer(&mut self, weight: FlexWeight) -> Result<NodeId, LayoutError> { 393 + let style = Style { 394 + flex_grow: weight.as_f32(), 395 + flex_shrink: 0.0, 396 + flex_basis: length(0.0), 397 + ..Style::DEFAULT 398 + }; 399 + self.leaf_node(NodeKind::Pass, style) 400 + } 401 + 402 + fn gap(&mut self, size: Spacing) -> Result<NodeId, LayoutError> { 403 + let style = Style { 404 + flex_grow: 0.0, 405 + flex_shrink: 0.0, 406 + flex_basis: length(size.value_px()), 407 + ..Style::DEFAULT 408 + }; 409 + self.leaf_node(NodeKind::Pass, style) 410 + } 411 + 412 + fn scroll_region( 413 + &mut self, 414 + id: WidgetId, 415 + axes: ScrollAxes, 416 + child: &Layout, 417 + retained: &RetainedLayout, 418 + ) -> Result<NodeId, LayoutError> { 419 + let kid = self.lower(child, retained)?; 420 + let style = Style { 421 + overflow: TaffyPoint { 422 + x: scroll_overflow(axes.allows_horizontal()), 423 + y: scroll_overflow(axes.allows_vertical()), 424 + }, 425 + ..flex_stretch_style(Axis::Vertical) 426 + }; 427 + let offset = retained.scroll_for(id); 428 + self.parent(NodeKind::ScrollRegion { id, axes, offset }, style, &[kid]) 429 + } 430 + 431 + fn splitter( 432 + &mut self, 433 + id: WidgetId, 434 + axis: Axis, 435 + default_fraction: SplitFraction, 436 + a: &Layout, 437 + b: &Layout, 438 + retained: &RetainedLayout, 439 + ) -> Result<NodeId, LayoutError> { 440 + let fraction = retained.split_for(id, default_fraction); 441 + let a_id = self.lower(a, retained)?; 442 + let b_id = self.lower(b, retained)?; 443 + self.apply_split_grow(a_id, b_id, fraction)?; 444 + self.parent( 445 + NodeKind::Splitter { id, axis, fraction }, 446 + flex_stretch_style(axis), 447 + &[a_id, b_id], 448 + ) 449 + } 450 + 451 + fn dock_host( 452 + &mut self, 453 + id: WidgetId, 454 + state: &DockState, 455 + panels: &[DockPanel], 456 + tab_strip_height: Spacing, 457 + retained: &RetainedLayout, 458 + ) -> Result<NodeId, LayoutError> { 459 + if !state.floating.is_empty() { 460 + return Err(LayoutError::UnsupportedFloatingSurfaces); 461 + } 462 + let main = self.dock_node(&state.main, panels, tab_strip_height, retained)?; 463 + self.parent( 464 + NodeKind::DockHost { id }, 465 + flex_stretch_style(Axis::Vertical), 466 + &[main], 467 + ) 468 + } 469 + 470 + fn dock_node( 471 + &mut self, 472 + node: &DockNode, 473 + panels: &[DockPanel], 474 + tab_strip_height: Spacing, 475 + retained: &RetainedLayout, 476 + ) -> Result<NodeId, LayoutError> { 477 + match node { 478 + DockNode::Split { 479 + axis, 480 + fraction, 481 + a, 482 + b, 483 + } => { 484 + let a_id = self.dock_node(a, panels, tab_strip_height, retained)?; 485 + let b_id = self.dock_node(b, panels, tab_strip_height, retained)?; 486 + self.apply_split_grow(a_id, b_id, *fraction)?; 487 + self.parent( 488 + NodeKind::DockSplit { 489 + axis: *axis, 490 + fraction: *fraction, 491 + }, 492 + flex_stretch_style(*axis), 493 + &[a_id, b_id], 494 + ) 495 + } 496 + DockNode::Tabs { tabs, active } => { 497 + let active_id = tabs 498 + .get(usize::from(active.get())) 499 + .copied() 500 + .or_else(|| tabs.first().copied()) 501 + .ok_or(LayoutError::EmptyDockTabs)?; 502 + let panel = panels 503 + .iter() 504 + .find(|p| p.id == active_id) 505 + .ok_or(LayoutError::MissingDockPanel(active_id))?; 506 + let body = self.lower(&panel.child, retained)?; 507 + let body_panel = self.parent( 508 + NodeKind::DockPanel { id: active_id }, 509 + flex_stretch_style(Axis::Vertical), 510 + &[body], 511 + )?; 512 + if tabs.len() <= 1 { 513 + return Ok(body_panel); 514 + } 515 + let strip = self.leaf_node( 516 + NodeKind::DockTabStrip { 517 + active: active_id, 518 + tabs: tabs.clone(), 519 + }, 520 + Style { 521 + size: TaffySize { 522 + width: Dimension::AUTO, 523 + height: length(tab_strip_height.value_px()), 524 + }, 525 + ..Style::DEFAULT 526 + }, 527 + )?; 528 + self.parent( 529 + NodeKind::Pass, 530 + flex_stretch_style(Axis::Vertical), 531 + &[strip, body_panel], 532 + ) 533 + } 534 + } 535 + } 536 + 537 + fn collect( 538 + &mut self, 539 + node: NodeId, 540 + parent_origin: LayoutPos, 541 + ) -> Result<SolvedIndex, LayoutError> { 542 + let layout: TaffyLayout = *self.tree.layout(node)?; 543 + let origin = LayoutPos::new( 544 + LayoutPx::saturating(parent_origin.x.value() + layout.location.x), 545 + LayoutPx::saturating(parent_origin.y.value() + layout.location.y), 546 + ); 547 + let size = LayoutSize::new( 548 + LayoutPx::saturating_nonneg(layout.size.width), 549 + LayoutPx::saturating_nonneg(layout.size.height), 550 + ); 551 + let rect = LayoutRect::new(origin, size); 552 + let content_size = LayoutSize::new( 553 + LayoutPx::saturating_nonneg(layout.content_size.width.max(layout.size.width)), 554 + LayoutPx::saturating_nonneg(layout.content_size.height.max(layout.size.height)), 555 + ); 556 + let kind = match self 557 + .tree 558 + .get_node_context(node) 559 + .cloned() 560 + .unwrap_or(NodeKind::Pass) 561 + { 562 + NodeKind::ScrollRegion { id, axes, offset } => NodeKind::ScrollRegion { 563 + id, 564 + axes, 565 + offset: clamp_scroll(offset, size, content_size, axes), 566 + }, 567 + other => other, 568 + }; 569 + let child_ids: Vec<NodeId> = self.tree.children(node)?; 570 + let children = child_ids 571 + .into_iter() 572 + .map(|c| self.collect(c, origin)) 573 + .collect::<Result<Vec<_>, _>>()?; 574 + let solved = SolvedNode { 575 + kind, 576 + rect, 577 + content_size, 578 + children, 579 + }; 580 + let idx = SolvedIndex(self.solved.len()); 581 + self.solved.push(solved); 582 + Ok(idx) 583 + } 584 + } 585 + 586 + fn track_to_taffy(track: &GridTrack) -> GridTemplateComponent<String> { 587 + match track.size { 588 + TrackSize::Fixed(spacing) => length(spacing.value_px()), 589 + TrackSize::Auto => auto(), 590 + TrackSize::Flex(weight) => fr(weight.as_f32()), 591 + } 592 + } 593 + 594 + const fn scroll_overflow(allowed: bool) -> Overflow { 595 + if allowed { 596 + Overflow::Scroll 597 + } else { 598 + Overflow::Hidden 599 + } 600 + } 601 + 602 + fn resolve_line(line: &GridLineRef, tracks: &[GridTrack]) -> Result<GridLine, LayoutError> { 603 + match line { 604 + GridLineRef::Line(l) => Ok(*l), 605 + GridLineRef::Name(name) => track_line(name, tracks), 606 + } 607 + } 608 + 609 + fn track_line(name: &TrackName, tracks: &[GridTrack]) -> Result<GridLine, LayoutError> { 610 + let mut iter = tracks 611 + .iter() 612 + .enumerate() 613 + .filter(|(_, t)| t.name.as_ref() == Some(name)); 614 + let (position, _) = iter 615 + .next() 616 + .ok_or_else(|| LayoutError::UnknownTrackName(name.as_str().to_owned()))?; 617 + if iter.next().is_some() { 618 + return Err(LayoutError::DuplicateTrackName(name.as_str().to_owned())); 619 + } 620 + u16::try_from(position) 621 + .ok() 622 + .and_then(|n| n.checked_add(1)) 623 + .and_then(NonZeroU16::new) 624 + .map(GridLine::new) 625 + .ok_or_else(|| LayoutError::UnknownTrackName(name.as_str().to_owned())) 626 + }