use cosmic_text::FontSystem;
use taffy::prelude::{TaffyGridLine, TaffyGridSpan};
use taffy::{
AlignItems, AvailableSpace, Display, FlexDirection, GridPlacement, JustifyContent,
LengthPercentage, Line,
};
use crate::geometry::{Point, Rect};
use crate::style::Font;
use crate::text::TextSystem;
use crate::tree::{Node, NodeId, NodeKind, Tree, add_damage, icon_rect};
use crate::widgets::{Align, Justify};
#[derive(Clone, Copy, PartialEq, Eq)]
enum Parent {
Flex,
Stack,
Window,
}
impl Tree {
pub(crate) fn layout(&mut self, text: &mut TextSystem) {
let root = self.root;
self.sync(root, Parent::Flex, Font::DEFAULT, text);
let fonts = text.font_system();
if self.popup.is_some() {
self.size = self.natural_size(fonts);
let mut style = self.taffy.style(root).cloned().unwrap_or_default();
style.size = definite(self.size);
let _ = self.taffy.set_style(root, style);
}
let available = taffy::Size {
width: AvailableSpace::Definite(self.size.width),
height: AvailableSpace::Definite(self.size.height),
};
let measured =
self
.taffy
.compute_layout_with_measure(root, available, |inputs, _id, node, style| {
taffy::compute_leaf_layout(
inputs,
style,
|_, _| 0.0,
|known, available| measure(node, fonts, known, available),
)
});
if let Err(error) = measured {
tracing::error!(%error, "taffy failed to lay out a window");
return;
}
self.place(root, Point::default(), fonts);
self.needs_layout = false;
self.pointer_after_layout();
}
fn natural_size(&mut self, fonts: &mut FontSystem) -> crate::Size {
let root = self.root;
let mut style = self.taffy.style(root).cloned().unwrap_or_default();
style.size = taffy::Size::auto();
let _ = self.taffy.set_style(root, style);
let unlimited = taffy::Size {
width: AvailableSpace::MaxContent,
height: AvailableSpace::MaxContent,
};
let measured =
self
.taffy
.compute_layout_with_measure(root, unlimited, |inputs, _id, node, style| {
taffy::compute_leaf_layout(
inputs,
style,
|_, _| 0.0,
|known, available| measure(node, fonts, known, available),
)
});
match (measured, self.taffy.layout(root)) {
(Ok(()), Ok(layout)) => crate::Size::new(
layout.size.width.ceil().max(1.0),
layout.size.height.ceil().max(1.0),
),
(Err(error), _) => {
tracing::error!(%error, "taffy failed to measure a popup");
self.size
}
(_, Err(error)) => {
tracing::error!(%error, "taffy has no layout for a popup's root");
self.size
}
}
}
fn sync(&mut self, id: NodeId, parent: Parent, font: Font, text: &mut TextSystem) {
let size = self.size;
let Some(node) = self.taffy.get_node_context_mut(id) else {
return;
};
let font = node.effective_style().font.unwrap_or(font);
let mut reshaped = false;
if let NodeKind::Text(text_node) = &mut node.kind {
reshaped = text_node.prepare(font, text);
}
let changed = node.changed;
let style = taffy_style(node, parent, size);
let child_parent = match node.kind {
NodeKind::Root => Parent::Window,
NodeKind::Stack => Parent::Stack,
_ => Parent::Flex,
};
let same_style = self.taffy.style(id).is_ok_and(|current| *current == style);
if !same_style {
let _ = self.taffy.set_style(id, style);
} else if reshaped || changed {
let _ = self.taffy.mark_dirty(id);
}
for child in self.taffy.children(id).unwrap_or_default() {
self.sync(child, child_parent, font, text);
}
}
fn place(&mut self, id: NodeId, origin: Point, fonts: &mut FontSystem) -> Rect {
let Ok(&layout) = self.taffy.layout(id) else {
return Rect::default();
};
let rect = Rect::new(
origin.x + layout.location.x,
origin.y + layout.location.y,
layout.size.width,
layout.size.height,
);
let inset_left = layout.padding.left + layout.border.left;
let inset_top = layout.padding.top + layout.border.top;
let content = Rect::new(
rect.x + inset_left,
rect.y + inset_top,
(layout.content_box_width()).max(0.0),
(layout.content_box_height()).max(0.0),
);
let mut children = Rect::default();
for child in self.taffy.children(id).unwrap_or_default() {
children = children.union(self.place(child, Point::new(rect.x, rect.y), fonts));
}
let Some(node) = self.taffy.get_node_context_mut(id) else {
return rect.union(children);
};
let mut painted = rect;
match &mut node.kind {
NodeKind::Text(text) => {
text.finish(content, fonts);
painted = painted.union(text.ink_box());
}
NodeKind::Icon(icon) => painted = painted.union(icon_rect(content, icon.size)),
_ => {}
}
let drawn = if node.draws() {
painted
} else {
Rect::default()
};
match node.rect {
None => add_damage(&mut self.damage, drawn),
Some(_) if node.painted != painted || node.changed => {
add_damage(&mut self.damage, node.drawn);
add_damage(&mut self.damage, drawn);
}
Some(_) => {}
}
node.drawn = drawn;
let bounds = painted.union(children);
node.rect = Some(rect);
node.content = content;
node.painted = painted;
node.bounds = bounds;
node.changed = false;
bounds
}
}
fn measure(
node: Option<&mut Node>,
fonts: &mut FontSystem,
known: taffy::Size<Option<f32>>,
available: taffy::Size<AvailableSpace>,
) -> taffy::Size<f32> {
match node.map(|node| &mut node.kind) {
Some(NodeKind::Text(text)) => text.measure(fonts, known, available),
Some(NodeKind::Icon(icon)) => {
let side = icon.size as f32;
taffy::Size {
width: known.width.unwrap_or(side),
height: known.height.unwrap_or(side),
}
}
_ => taffy::Size {
width: known.width.unwrap_or(0.0),
height: known.height.unwrap_or(0.0),
},
}
}
fn taffy_style(node: &Node, parent: Parent, size: crate::Size) -> taffy::Style {
let padding = node.padding();
let vertical = LengthPercentage::length(padding.vertical as f32);
let horizontal = LengthPercentage::length(padding.horizontal as f32);
let mut style = taffy::Style {
padding: taffy::Rect {
left: horizontal,
right: horizontal,
top: vertical,
bottom: vertical,
},
..taffy::Style::default()
};
match &node.kind {
NodeKind::Root => {
style.display = Display::Grid;
style.size = definite(size);
style.grid_template_columns = vec![taffy::prelude::fr(1.0)];
style.grid_template_rows = vec![taffy::prelude::fr(1.0)];
}
NodeKind::Flex(flex) => {
style.flex_direction = if flex.column {
FlexDirection::Column
} else {
FlexDirection::Row
};
if flex.grow {
style.flex_grow = 1.0;
}
let gap = LengthPercentage::length(flex.gap as f32);
style.gap = taffy::Size {
width: gap,
height: gap,
};
style.align_items = match flex.align {
Align::Start => AlignItems::START,
Align::Center => AlignItems::CENTER,
Align::End => AlignItems::END,
Align::Stretch => AlignItems::STRETCH,
};
style.justify_content = match flex.justify {
Justify::Start => JustifyContent::START,
Justify::Center => JustifyContent::CENTER,
Justify::End => JustifyContent::END,
Justify::SpaceBetween => JustifyContent::SPACE_BETWEEN,
Justify::SpaceAround => JustifyContent::SPACE_AROUND,
Justify::SpaceEvenly => JustifyContent::SPACE_EVENLY,
};
}
NodeKind::Stack => style.display = Display::Grid,
NodeKind::Spacer => style.flex_grow = 1.0,
NodeKind::Button(_) => {
style.align_items = AlignItems::CENTER;
style.justify_content = JustifyContent::CENTER;
}
NodeKind::Text(_) | NodeKind::Icon(_) => {}
}
if parent != Parent::Flex {
let cell = Line {
start: GridPlacement::from_line_index(1),
end: GridPlacement::from_span(1),
};
style.grid_row = cell.clone();
style.grid_column = cell;
}
if parent == Parent::Window {
style.min_size = taffy::Size {
width: taffy::LengthPercentageAuto::length(0.0),
height: taffy::LengthPercentageAuto::length(0.0),
};
}
style
}
fn definite(size: crate::Size) -> taffy::Size<taffy::Dimension> {
taffy::Size {
width: taffy::Dimension::length(size.width),
height: taffy::Dimension::length(size.height),
}
}