use embedded_graphics::{
draw_target::DrawTarget,
geometry::{Point, Size},
pixelcolor::PixelColor,
primitives::Rectangle,
};
use crate::{
ColumnStyle, DisplayTarget, RowStyle, StorageView, Style, TextStyle, Theme,
common::{NodeIndex, SizeExt as _, TextRange, to_i32},
layout::{BoxLayout, Constraints, Layout},
style::BoxStyle,
};
#[derive(Clone, Copy)]
enum Axis {
Horizontal,
Vertical,
}
pub(crate) struct FrameTree<'frame, C>
where
C: PixelColor,
{
pub root: Option<NodeIndex>,
pub storage: StorageView<'frame, C>,
}
pub(crate) struct Node<C>
where
C: PixelColor,
{
pub kind: NodeKind<C>,
pub layout: Layout,
pub child: Option<NodeIndex>,
pub sibling: Option<NodeIndex>,
}
impl<C: PixelColor> Node<C> {
pub(crate) const fn box_style(&self) -> BoxStyle {
self.kind.box_style()
}
pub(crate) fn draw<D>(&self, layout: &BoxLayout, target: &mut D) -> Result<(), D::Error>
where
D: DrawTarget<Color = C>,
{
self.kind.draw(layout, target)
}
pub(crate) const fn set_sibling(&mut self, sibling: NodeIndex) {
self.sibling = Some(sibling);
}
}
impl<'frame, C> FrameTree<'frame, C>
where
C: PixelColor,
{
pub(crate) const fn new(storage: StorageView<'frame, C>) -> Self {
Self { root: None, storage }
}
#[allow(unused)]
pub(crate) fn len(&self) -> usize {
self.storage.nodes.len()
}
#[inline]
pub(crate) fn node(&self, index: NodeIndex) -> &Node<C> {
&self.storage.nodes[index]
}
#[inline]
pub(crate) fn node_mut(&mut self, index: NodeIndex) -> &mut Node<C> {
&mut self.storage.nodes[index]
}
pub(crate) fn resolve(&mut self, root: NodeIndex, constraints: Constraints) {
self.root = Some(root);
self.layout_node(root, constraints);
}
pub(crate) fn draw<D>(&mut self, target: &mut D, theme: &Theme<C>) -> Result<(), D::Error>
where
D: DisplayTarget<Color = C>,
{
let Some(root) = self.root else { return target.clear(theme.background) };
let viewport = target.bounding_box();
if self.needs_clear(viewport.size) {
if target.try_begin(viewport, theme.background) {
self.draw_subtree(root, Point::zero(), target, theme)?;
return target.flush();
}
target.clear(theme.background)?;
}
self.draw_adaptive(root, Point::zero(), theme.background, viewport, target, theme)
}
pub(crate) fn needs_clear(&self, viewport: Size) -> bool {
let Some(root) = self.root else {
return true;
};
let root = self.node(root);
if !root.kind.has_background() {
return true;
}
let root_bounds = root.layout.resolve(Point::zero()).border;
let viewport = Rectangle::new(Point::zero(), viewport);
root_bounds.intersection(&viewport) != viewport
}
fn draw_adaptive<D>(
&mut self,
index: NodeIndex,
parent_origin: Point,
inherited_background: C,
viewport: Rectangle,
target: &mut D,
theme: &Theme<C>,
) -> Result<(), D::Error>
where
D: DisplayTarget<Color = C>,
{
let layout = self.node(index).layout.resolve(parent_origin);
let bounds = layout.border.intersection(&viewport);
if bounds.size != Size::zero() && target.try_begin(bounds, inherited_background) {
self.draw_subtree(index, parent_origin, target, theme)?;
target.flush()?;
return Ok(());
}
self.draw_node(index, &layout, target, theme)?;
let child_background = self.node(index).kind.background().unwrap_or(inherited_background);
let mut child = self.node(index).child;
while let Some(index) = child {
self.draw_adaptive(
index,
layout.content.top_left,
child_background,
viewport,
target,
theme,
)?;
child = self.node(index).sibling;
}
Ok(())
}
fn draw_node<D>(
&self,
index: NodeIndex,
layout: &BoxLayout,
target: &mut D,
theme: &Theme<C>,
) -> Result<(), D::Error>
where
D: DrawTarget<Color = C>,
{
let node = self.node(index);
if let NodeKind::Text(text) = &node.kind {
let content = text.content(self.storage.text);
text.draw(content, layout, target, theme)
} else {
node.draw(layout, target)
}
}
fn draw_subtree<D>(
&mut self,
index: NodeIndex,
parent_origin: Point,
target: &mut D,
theme: &Theme<C>,
) -> Result<(), D::Error>
where
D: DisplayTarget<Color = C>,
{
let layout = self.node(index).layout.resolve(parent_origin);
self.draw_node(index, &layout, target, theme)?;
let mut child = self.node(index).child;
while let Some(index) = child {
self.draw_subtree(index, layout.content.top_left, target, theme)?;
child = self.node(index).sibling;
}
Ok(())
}
fn layout_node(&mut self, index: NodeIndex, constraints: Constraints) {
let box_style = self.node(index).box_style();
let border_constraints = box_style.border_constraints(constraints);
let content_constraints = box_style.content_constraints(constraints);
enum ContentLayout {
Container(Axis),
Leaf(Size),
}
let content_layout = match &self.node(index).kind {
NodeKind::Column(_) => ContentLayout::Container(Axis::Vertical),
NodeKind::Row(_) => ContentLayout::Container(Axis::Horizontal),
NodeKind::Text(text) => ContentLayout::Leaf(content_constraints.constrain(text.size)),
};
let intrinsic_content_size = match content_layout {
ContentLayout::Container(axis) => {
self.layout_children(index, content_constraints, axis)
}
ContentLayout::Leaf(size) => size,
};
let content_insets = box_style.content_insets();
let content_inset_size = content_insets.total_size();
let border_size = {
let natural_border_size = intrinsic_content_size.inflate(content_inset_size);
let desired_border_size = box_style.size.map_or(natural_border_size, |size| {
Size::new(
size.width.max(content_inset_size.width),
size.height.max(content_inset_size.height),
)
});
border_constraints.constrain(desired_border_size)
};
let outer_size = border_size.inflate(box_style.margin.total_size());
let content_size = border_size.deflate(content_inset_size);
let content_offset = box_style.margin.saturating_add(content_insets);
self.node_mut(index).layout = Layout {
offset: Point::new(0, 0),
border_offset: Point::new(to_i32(box_style.margin.left), to_i32(box_style.margin.top)),
content_offset: Point::new(to_i32(content_offset.left), to_i32(content_offset.top)),
outer_size,
border_size,
content_size,
};
}
fn layout_children(&mut self, index: NodeIndex, constraints: Constraints, axis: Axis) -> Size {
let mut main_size: u32 = 0;
let mut cross_size: u32 = 0;
let mut child = self.node(index).child;
while let Some(child_idx) = child {
self.layout_node(child_idx, constraints);
let size = self.node(child_idx).layout.outer_size;
let main_offset = i32::try_from(main_size).unwrap_or(i32::MAX);
let offset = match axis {
Axis::Horizontal => Point::new(main_offset, 0),
Axis::Vertical => Point::new(0, main_offset),
};
self.node_mut(child_idx).layout.set_offset(offset);
let (child_main_size, child_cross_size) = match axis {
Axis::Horizontal => (size.width, size.height),
Axis::Vertical => (size.height, size.width),
};
main_size = main_size.saturating_add(child_main_size);
cross_size = cross_size.max(child_cross_size);
child = self.node(child_idx).sibling;
}
let intrinsic_size = match axis {
Axis::Horizontal => Size::new(main_size, cross_size),
Axis::Vertical => Size::new(cross_size, main_size),
};
constraints.constrain(intrinsic_size)
}
}
pub(crate) enum NodeKind<C> {
Row(Style<RowStyle, C>),
Column(Style<ColumnStyle, C>),
Text(TextNode<C>),
}
impl<C: PixelColor> NodeKind<C> {
pub(crate) const fn box_style(&self) -> BoxStyle {
match self {
Self::Row(style) => style.box_style(),
Self::Column(style) => style.box_style(),
Self::Text(text) => text.style.box_style(),
}
}
pub(crate) const fn background(&self) -> Option<C> {
match self {
Self::Row(style) => style.background,
Self::Column(style) => style.background,
Self::Text(text) => text.style.background,
}
}
}
pub(crate) struct TextNode<C> {
pub(crate) range: TextRange,
pub(crate) size: Size,
pub(crate) style: Style<TextStyle<C>, C>,
}
impl<C: PixelColor> TextNode<C> {
pub(crate) fn content<'a>(&self, storage: &'a [u8]) -> &'a str {
let end = self.range.offset + self.range.len;
unsafe { core::str::from_utf8_unchecked(&storage[self.range.offset..end]) }
}
}