use cosmic_text::FontSystem;
use taffy::prelude::{TaffyGridLine, TaffyGridSpan};
use taffy::{
AlignItems, AvailableSpace, Dimension, Display, FlexDirection, GridPlacement, JustifyContent,
LengthPercentage, Line, Overflow,
};
use crate::geometry::{Affine, Point, Rect, through};
use crate::style::Font;
use crate::text::TextSystem;
use crate::text_input::DEFAULT_WIDTH;
use crate::tree::{Node, NodeId, NodeKind, Tree, add_damage, centered, icon_rect};
use crate::widgets::{Align, Justify};
#[derive(Clone, Copy, PartialEq, Eq)]
enum Parent {
Flex,
Stack,
Window,
Scroll,
}
impl Tree {
pub(crate) fn layout(&mut self, text: &mut TextSystem) {
let root = self.root;
if self.needs_place && !self.needs_layout {
self.place(root, Point::default(), &mut Vec::new(), text.font_system());
self.needs_place = false;
return;
}
self.sync(root, Parent::Flex, Font::DEFAULT, text);
let fonts = text.font_system();
if self.popup.is_some() {
let wanted = self.natural_size(fonts);
self.size = wanted;
if let Some(popup) = self.popup.as_mut() {
popup.wanted = wanted;
if let Some(limit) = popup.limit {
self.size = crate::Size::new(
wanted.width.min(limit.width),
wanted.height.min(limit.height),
);
}
}
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(), &mut Vec::new(), fonts);
self.needs_layout = false;
self.needs_place = 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;
match &mut node.kind {
NodeKind::Text(text_node) => reshaped = text_node.prepare(font, text),
NodeKind::Input(input) => reshaped = input.text.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,
NodeKind::Scroll(_) => Parent::Scroll,
_ => 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,
chain: &mut Vec<Affine>,
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 origin = Point::new(rect.x, rect.y);
if let Some(NodeKind::Scroll(scroll)) = self.node(id).map(|node| &node.kind) {
let bottom = self
.taffy
.children(id)
.unwrap_or_default()
.into_iter()
.filter_map(|child| self.taffy.layout(child).ok())
.map(|child| {
child.location.y + child.size.height + layout.padding.bottom + layout.border.bottom
})
.fold(0.0, f32::max);
let most = (bottom - layout.size.height).max(0.0);
let shown = if scroll.offset.is_nan() {
0.0
} else {
scroll.offset.clamp(0.0, most)
};
origin.y -= shown;
if let Some(NodeKind::Scroll(scroll)) = self.node_mut(id).map(|node| &mut node.kind) {
scroll.shown = shown;
scroll.most = most;
}
}
let Some(node) = self.node_mut(id) else {
return rect;
};
let transform = node.shown_style().transform(rect);
node.transform = transform;
chain.extend(transform);
let mut children = Rect::default();
for child in self.taffy.children(id).unwrap_or_default() {
children = children.union(self.place(child, origin, chain, fonts));
}
let bounds = self.finish_place(id, rect, content, children, chain, fonts);
if transform.is_some() {
chain.pop();
}
bounds
}
fn finish_place(
&mut self,
id: NodeId,
rect: Rect,
content: Rect,
children: Rect,
chain: &[Affine],
fonts: &mut FontSystem,
) -> Rect {
let Some(node) = self.taffy.get_node_context_mut(id) else {
return rect.union(children);
};
let mut painted = rect;
let fading = node.fade.as_ref().map(|fade| fade.from);
for style in [Some(node.effective_style()), fading].into_iter().flatten() {
if let Some(shadow) = style.shadow {
painted = painted.union(shadow.reach(rect));
}
}
match &mut node.kind {
NodeKind::Text(text) => {
text.finish(content, fonts);
painted = painted.union(text.ink_box());
}
NodeKind::Input(input) => {
input.text.finish(content, fonts);
input.place(content);
}
NodeKind::Icon(icon) => painted = painted.union(icon_rect(content, icon.size)),
NodeKind::Image(image) => {
painted = painted.union(centered(content, image.width, image.height));
}
_ => {}
}
let window_painted = through(chain, painted);
let drawn = if node.draws() {
window_painted
} else {
Rect::default()
};
match node.rect {
None => add_damage(&mut self.damage, drawn),
Some(_) if node.window_painted != window_painted || node.changed => {
add_damage(&mut self.damage, node.drawn);
add_damage(&mut self.damage, drawn);
}
Some(_) => {}
}
node.drawn = drawn;
let clips = node.clips();
let children = if clips {
children.intersect(rect)
} else {
children
};
let inside = painted.union(children);
let window_bounds = through(chain, inside);
if (clips || node.clips)
&& node.rect.is_some()
&& (node.changed || node.window_bounds != window_bounds)
{
add_damage(&mut self.damage, node.window_bounds);
add_damage(&mut self.damage, window_bounds);
}
let bounds = match node.transform {
Some(transform) => through(&[transform], inside),
None => inside,
};
node.clips = clips;
node.rect = Some(rect);
node.content = content;
node.painted = painted;
node.bounds = bounds;
node.inner_bounds = inside;
node.window_bounds = window_bounds;
node.window_painted = window_painted;
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::Input(input)) => {
let line = input.text.measure(fonts, known, available);
taffy::Size {
width: known.width.unwrap_or(DEFAULT_WIDTH),
height: line.height,
}
}
Some(NodeKind::Image(image)) => taffy::Size {
width: known.width.unwrap_or(image.width as f32),
height: known.height.unwrap_or(image.height as f32),
},
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 side = |pixels: u32| LengthPercentage::length(pixels as f32);
let mut style = taffy::Style {
padding: taffy::Rect {
left: side(padding.left),
right: side(padding.right),
top: side(padding.top),
bottom: side(padding.bottom),
},
..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::Scroll(_) => {
style.flex_direction = FlexDirection::Column;
style.overflow = taffy::Point {
x: Overflow::Visible,
y: Overflow::Scroll,
};
style.scrollbar_width = 0.0;
}
NodeKind::Spacer => style.flex_grow = 1.0,
NodeKind::Button(_) => {
style.align_items = AlignItems::CENTER;
style.justify_content = JustifyContent::CENTER;
}
NodeKind::Text(_)
| NodeKind::Icon(_)
| NodeKind::Image(_)
| NodeKind::Capture(_)
| NodeKind::Input(_) => {}
}
let own = node.effective_style();
if !matches!(node.kind, NodeKind::Root) {
let length = |value: Option<f32>| match value {
Some(value) if value.is_finite() => Dimension::length(value.max(0.0)),
_ => Dimension::auto(),
};
style.size = taffy::Size {
width: length(own.width),
height: length(own.height),
};
}
if parent == Parent::Scroll {
style.flex_shrink = 0.0;
}
if matches!(parent, Parent::Stack | Parent::Window) {
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),
}
}