mod container;
mod image;
mod text;
use ::image::RgbaImage;
pub use container::*;
pub use image::*;
pub use text::*;
use serde::Deserialize;
use taffy::{AvailableSpace, Layout, Point, Size};
use zeno::Fill;
use crate::{
Result,
layout::{
Viewport,
inline::InlineContentKind,
style::{
Affine, BackgroundClip, BackgroundImage, BlendMode, CssValue, InheritedStyle, Length, Sides,
Style,
},
},
rendering::{
BackgroundTile, BorderProperties, Canvas, RenderContext, SizedShadow,
collect_background_layers, rasterize_layers,
},
resources::task::FetchTaskCollection,
};
macro_rules! impl_node_enum {
($name:ident, $($variant:ident => $variant_type:ty),*) => {
impl $crate::layout::node::Node<$name> for $name {
fn take_children(&mut self) -> Option<Box<[$name]>> {
match self {
$( $name::$variant(inner) => inner.take_children(), )*
}
}
fn children_ref(&self) -> Option<&[$name]> {
match self {
$( $name::$variant(inner) => inner.children_ref(), )*
}
}
fn create_inherited_style(&mut self, parent: &$crate::layout::style::InheritedStyle, viewport: $crate::layout::Viewport) -> $crate::layout::style::InheritedStyle {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::create_inherited_style(inner, parent, viewport), )*
}
}
fn inline_content(&self) -> Option<$crate::layout::inline::InlineContentKind<'_>> {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::inline_content(inner), )*
}
}
fn measure(
&self,
context: &$crate::rendering::RenderContext,
available_space: $crate::taffy::Size<$crate::taffy::AvailableSpace>,
known_dimensions: $crate::taffy::Size<Option<f32>>,
style: &taffy::Style,
) -> $crate::taffy::Size<f32> {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::measure(inner, context, available_space, known_dimensions, style), )*
}
}
fn draw_content(&self, context: &$crate::rendering::RenderContext, canvas: &mut $crate::rendering::Canvas, layout: $crate::taffy::Layout) -> $crate::Result<()> {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::draw_content(inner, context, canvas, layout), )*
}
}
fn draw_border(&self, context: &$crate::rendering::RenderContext, canvas: &mut $crate::rendering::Canvas, layout: $crate::taffy::Layout) -> $crate::Result<()> {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::draw_border(inner, context, canvas, layout), )*
}
}
fn draw_outline(&self, context: &$crate::rendering::RenderContext, canvas: &mut $crate::rendering::Canvas, layout: $crate::taffy::Layout) -> $crate::Result<()> {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::draw_outline(inner, context, canvas, layout), )*
}
}
fn draw_outset_box_shadow(&self, context: &$crate::rendering::RenderContext, canvas: &mut $crate::rendering::Canvas, layout: $crate::taffy::Layout) -> $crate::Result<()> {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::draw_outset_box_shadow(inner, context, canvas, layout), )*
}
}
fn draw_inset_box_shadow(&self, context: &$crate::rendering::RenderContext, canvas: &mut $crate::rendering::Canvas, layout: $crate::taffy::Layout) -> $crate::Result<()> {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::draw_inset_box_shadow(inner, context, canvas, layout), )*
}
}
fn get_style(&self) -> Option<&Style> {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::get_style(inner), )*
}
}
fn collect_fetch_tasks(&self, collection: &mut FetchTaskCollection) {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::collect_fetch_tasks(inner, collection), )*
}
}
fn collect_style_fetch_tasks(&self, collection: &mut FetchTaskCollection) {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::collect_style_fetch_tasks(inner, collection), )*
}
}
fn draw_background(&self, context: &$crate::rendering::RenderContext, canvas: &mut $crate::rendering::Canvas, layout: $crate::taffy::Layout) -> $crate::Result<()> {
match self {
$( $name::$variant(inner) => <_ as $crate::layout::node::Node<$name>>::draw_background(inner, context, canvas, layout), )*
}
}
}
$(
impl From<$variant_type> for $name {
fn from(inner: $variant_type) -> Self {
$name::$variant(inner)
}
}
)*
};
}
pub trait Node<N: Node<N>>: Send + Sync + Clone {
fn children_ref(&self) -> Option<&[N]> {
None
}
fn collect_fetch_tasks(&self, collection: &mut FetchTaskCollection) {
let Some(children) = self.children_ref() else {
return;
};
for child in children {
child.collect_fetch_tasks(collection);
}
}
fn get_style(&self) -> Option<&Style>;
fn collect_style_fetch_tasks(&self, collection: &mut FetchTaskCollection) {
if let Some(style) = self.get_style() {
if let CssValue::Value(Some(images)) = &style.background_image {
collection.insert_many(images.iter().filter_map(|image| {
if let BackgroundImage::Url(url) = image {
Some(url.clone())
} else {
None
}
}))
};
if let CssValue::Value(background) = &style.background {
collection.insert_many(background.iter().filter_map(|background| {
if let BackgroundImage::Url(url) = &background.image {
Some(url.clone())
} else {
None
}
}));
};
if let CssValue::Value(Some(images)) = &style.mask_image {
collection.insert_many(images.iter().filter_map(|image| {
if let BackgroundImage::Url(url) = image {
Some(url.clone())
} else {
None
}
}));
};
if let CssValue::Value(mask) = &style.mask {
collection.insert_many(mask.iter().filter_map(|background| {
if let BackgroundImage::Url(url) = &background.image {
Some(url.clone())
} else {
None
}
}));
};
};
let Some(children) = self.children_ref() else {
return;
};
for child in children {
child.collect_fetch_tasks(collection);
}
}
fn take_children(&mut self) -> Option<Box<[N]>> {
None
}
fn create_inherited_style(
&mut self,
_parent: &InheritedStyle,
viewport: Viewport,
) -> InheritedStyle;
fn inline_content(&self) -> Option<InlineContentKind<'_>> {
None
}
fn measure(
&self,
_context: &RenderContext,
_available_space: Size<AvailableSpace>,
_known_dimensions: Size<Option<f32>>,
_style: &taffy::Style,
) -> Size<f32> {
Size::ZERO
}
fn draw_outset_box_shadow(
&self,
context: &RenderContext,
canvas: &mut Canvas,
layout: Layout,
) -> Result<()> {
let Some(box_shadow) = context.style.box_shadow.as_ref() else {
return Ok(());
};
let element_border_radius = BorderProperties::from_context(context, layout.size, layout.border);
for shadow in box_shadow.iter() {
if shadow.inset {
continue;
}
let mut paths = Vec::new();
let mut element_paths = Vec::new();
let mut border_radius = element_border_radius;
let resolved_spread_radius = shadow
.spread_radius
.to_px(&context.sizing, layout.size.width);
border_radius.expand_by(Sides([resolved_spread_radius; 4]).into());
let shadow =
SizedShadow::from_box_shadow(*shadow, &context.sizing, context.current_color, layout.size);
let spread_size = Size {
width: (layout.size.width + 2.0 * resolved_spread_radius).max(0.0),
height: (layout.size.height + 2.0 * resolved_spread_radius).max(0.0),
};
border_radius.append_mask_commands(
&mut paths,
spread_size,
Point {
x: -resolved_spread_radius,
y: -resolved_spread_radius,
},
);
element_border_radius.append_mask_commands(&mut element_paths, layout.size, Point::ZERO);
shadow.draw_outset(
canvas,
&paths,
context.transform,
Fill::NonZero.into(),
Some(&element_paths),
)?;
}
Ok(())
}
fn draw_inset_box_shadow(
&self,
context: &RenderContext,
canvas: &mut Canvas,
layout: Layout,
) -> Result<()> {
if let Some(box_shadow) = context.style.box_shadow.as_ref() {
let border_radius = BorderProperties::from_context(context, layout.size, layout.border);
for shadow in box_shadow.iter() {
if !shadow.inset {
continue;
}
let shadow = SizedShadow::from_box_shadow(
*shadow,
&context.sizing,
context.current_color,
layout.size,
);
shadow.draw_inset(context.transform, border_radius, canvas, layout)?;
}
}
Ok(())
}
fn draw_background(
&self,
context: &RenderContext,
canvas: &mut Canvas,
layout: Layout,
) -> Result<()> {
let mut border_radius = BorderProperties::from_context(context, layout.size, layout.border);
match context.style.background_clip {
BackgroundClip::BorderBox => {
let tiles = collect_background_layers(context, layout.size, &mut canvas.buffer_pool)?;
for tile in tiles {
for y in &tile.ys {
for x in &tile.xs {
canvas.overlay_image(
&tile.tile,
border_radius,
context.transform * Affine::translation(*x as f32, *y as f32),
context.style.image_rendering,
tile.blend_mode,
);
}
}
}
}
BackgroundClip::PaddingBox => {
border_radius.inset_by_border_width();
let layers = collect_background_layers(context, layout.size, &mut canvas.buffer_pool)?;
if let Some(tile) = rasterize_layers(
layers,
Size {
width: (layout.size.width - layout.border.left - layout.border.right) as u32,
height: (layout.size.height - layout.border.top - layout.border.bottom) as u32,
},
context,
border_radius,
Affine::translation(-layout.border.left, -layout.border.top),
&mut canvas.mask_memory,
&mut canvas.buffer_pool,
)? {
canvas.overlay_image(
&tile,
BorderProperties::default(),
context.transform * Affine::translation(layout.border.left, layout.border.top),
context.style.image_rendering,
BlendMode::Normal,
);
if let BackgroundTile::Image(image) = tile {
canvas.buffer_pool.release_image(image);
}
}
}
BackgroundClip::ContentBox => {
border_radius.inset_by_border_width();
border_radius.expand_by(layout.padding.map(|size| -size));
let layers = collect_background_layers(context, layout.size, &mut canvas.buffer_pool)?;
if let Some(tile) = rasterize_layers(
layers,
layout.content_box_size().map(|x| x as u32),
context,
border_radius,
Affine::translation(
-layout.padding.left - layout.border.left,
-layout.padding.top - layout.border.top,
),
&mut canvas.mask_memory,
&mut canvas.buffer_pool,
)? {
canvas.overlay_image(
&tile,
BorderProperties::default(),
context.transform
* Affine::translation(
layout.padding.left + layout.border.left,
layout.padding.top + layout.border.top,
),
context.style.image_rendering,
BlendMode::Normal,
);
if let BackgroundTile::Image(image) = tile {
canvas.buffer_pool.release_image(image);
}
}
}
_ => {}
}
Ok(())
}
fn draw_content(
&self,
_context: &RenderContext,
_canvas: &mut Canvas,
_layout: Layout,
) -> Result<()> {
Ok(())
}
fn draw_border(
&self,
context: &RenderContext,
canvas: &mut Canvas,
layout: Layout,
) -> Result<()> {
let clip_image = if context.style.background_clip == BackgroundClip::BorderArea {
rasterize_layers(
collect_background_layers(context, layout.size, &mut canvas.buffer_pool)?,
layout.size.map(|x| x as u32),
context,
BorderProperties::default(),
Affine::IDENTITY,
&mut canvas.mask_memory,
&mut canvas.buffer_pool,
)?
} else {
None
};
BorderProperties::from_context(context, layout.size, layout.border).draw(
canvas,
layout.size,
context.transform,
clip_image.as_ref(),
);
if let Some(BackgroundTile::Image(image)) = clip_image {
canvas.buffer_pool.release_image(image);
}
Ok(())
}
fn draw_outline(
&self,
context: &RenderContext,
canvas: &mut Canvas,
layout: Layout,
) -> Result<()> {
let width = context
.style
.outline_width
.unwrap_or(context.style.outline.width)
.to_px(&context.sizing, layout.size.width)
.max(0.0);
let offset = context
.style
.outline_offset
.unwrap_or(Length::zero())
.to_px(&context.sizing, layout.size.width);
let mut border = BorderProperties {
width: Sides([width; 4]).into(),
color: context
.style
.outline_color
.unwrap_or(context.style.outline.color)
.resolve(context.current_color),
style: context
.style
.outline_style
.unwrap_or(context.style.outline.style),
image_rendering: context.style.image_rendering,
radius: BorderProperties::resolve_radius_part(context, layout.size),
};
border.expand_by(Sides([offset + width; 4]).into());
let transform = Affine::translation(-offset - width, -offset - width) * context.transform;
let size = layout.size.map(|x| x + (offset + width) * 2.0);
border.draw::<RgbaImage>(canvas, size, transform, None);
Ok(())
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum NodeKind {
Container(ContainerNode<NodeKind>),
Image(ImageNode),
Text(TextNode),
}
impl_node_enum!(
NodeKind,
Container => ContainerNode<NodeKind>,
Image => ImageNode,
Text => TextNode
);