blob: 1c8eeadfc5451fd89d743bc6941f2e11bd518281 [file] [log] [blame]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use taffy::{
tree::{NodeId, TaffyTree},
AlignContent, AlignItems, Dimension, Display, FlexDirection, FlexWrap, LengthPercentage,
LengthPercentageAuto, Position, Style, TaffyError,
};
trait DumpAsCss {
fn to_css_value(&self) -> String;
}
impl DumpAsCss for LengthPercentageAuto {
fn to_css_value(&self) -> String {
match self {
LengthPercentageAuto::Auto => "auto".to_string(),
LengthPercentageAuto::Length(v) => format!("{}px", v),
LengthPercentageAuto::Percent(v) => format!("{}%", v),
}
}
}
impl DumpAsCss for LengthPercentage {
fn to_css_value(&self) -> String {
match self {
LengthPercentage::Length(x) => format!("{}px", x),
LengthPercentage::Percent(x) => format!("{}%", x),
}
}
}
impl DumpAsCss for Dimension {
fn to_css_value(&self) -> String {
match self {
Dimension::Auto => "auto".to_string(),
Dimension::Length(x) => format!("{}px", x),
Dimension::Percent(x) => format!("{}%", x),
}
}
}
impl DumpAsCss for Option<AlignItems> {
fn to_css_value(&self) -> String {
match self {
None => "normal",
Some(AlignItems::Baseline) => "baseline",
Some(AlignItems::Center) => "center",
Some(AlignItems::End) => "end",
Some(AlignItems::FlexEnd) => "flex-end",
Some(AlignItems::FlexStart) => "flex-start",
Some(AlignItems::Start) => "start",
Some(AlignItems::Stretch) => "stretch",
}
.to_string()
}
}
impl DumpAsCss for Option<AlignContent> {
fn to_css_value(&self) -> String {
match self {
None => "normal",
Some(AlignContent::Center) => "center",
Some(AlignContent::End) => "end",
Some(AlignContent::FlexEnd) => "flex-end",
Some(AlignContent::FlexStart) => "flex-start",
Some(AlignContent::SpaceAround) => "space-around",
Some(AlignContent::SpaceBetween) => "space-between",
Some(AlignContent::SpaceEvenly) => "space-evenly",
Some(AlignContent::Start) => "start",
Some(AlignContent::Stretch) => "stretch",
}
.to_string()
}
}
impl DumpAsCss for FlexDirection {
fn to_css_value(&self) -> String {
match self {
FlexDirection::Column => "column",
FlexDirection::ColumnReverse => "column-reverse",
FlexDirection::Row => "row",
FlexDirection::RowReverse => "row-reverse",
}
.to_string()
}
}
impl DumpAsCss for FlexWrap {
fn to_css_value(&self) -> String {
match self {
FlexWrap::NoWrap => "nowrap",
FlexWrap::Wrap => "wrap",
FlexWrap::WrapReverse => "wrap-reverse",
}
.to_string()
}
}
impl DumpAsCss for taffy::Display {
fn to_css_value(&self) -> String {
match self {
Display::Block => "block",
Display::Grid => "grid",
Display::Flex => "flex",
Display::None => "none",
}
.to_string()
}
}
impl DumpAsCss for taffy::Overflow {
fn to_css_value(&self) -> String {
match self {
taffy::Overflow::Hidden => "hidden",
taffy::Overflow::Scroll => "scroll",
taffy::Overflow::Clip => "clip",
taffy::Overflow::Visible => "visible",
}
.to_string()
}
}
impl DumpAsCss for Position {
fn to_css_value(&self) -> String {
match self {
Position::Absolute => "absolute",
Position::Relative => "relative",
}
.to_string()
}
}
impl DumpAsCss for Style {
fn to_css_value(&self) -> String {
let mut css = Vec::new();
css.push(format!("display: {}", self.display.to_css_value()));
css.push(format!("overflow-x: {}", self.overflow.x.to_css_value()));
css.push(format!("overflow-y: {}", self.overflow.y.to_css_value()));
css.push(format!("position: {}", self.position.to_css_value()));
css.push(format!("top: {}", self.inset.top.to_css_value()));
css.push(format!("left: {}", self.inset.left.to_css_value()));
css.push(format!("bottom: {}", self.inset.bottom.to_css_value()));
css.push(format!("right: {}", self.inset.right.to_css_value()));
css.push(format!("width: {}", self.size.width.to_css_value()));
css.push(format!("height: {}", self.size.height.to_css_value()));
css.push(format!("min-width: {}", self.min_size.width.to_css_value()));
css.push(format!("min-height: {}", self.min_size.height.to_css_value()));
css.push(format!("max-width: {}", self.max_size.width.to_css_value()));
css.push(format!("max-height: {}", self.max_size.height.to_css_value()));
// skip aspect-ratio
css.push(format!("margin-top: {}", self.margin.top.to_css_value()));
css.push(format!("margin-left: {}", self.margin.left.to_css_value()));
css.push(format!("margin-bottom: {}", self.margin.bottom.to_css_value()));
css.push(format!("margin-right: {}", self.margin.right.to_css_value()));
css.push(format!("padding-top: {}", self.padding.top.to_css_value()));
css.push(format!("padding-left: {}", self.padding.left.to_css_value()));
css.push(format!("padding-bottom: {}", self.padding.bottom.to_css_value()));
css.push(format!("padding-right: {}", self.padding.right.to_css_value()));
// skip border (unused by DC)
css.push(format!("align-items: {}", self.align_items.to_css_value()));
css.push(format!("align-self: {}", self.align_self.to_css_value()));
css.push(format!("align-content: {}", self.align_content.to_css_value()));
// skip justify_items, justify_self (CSS grid, unused by DC)
css.push(format!("justify-content: {}", self.justify_content.to_css_value()));
css.push(format!(
"gap: {} {}",
self.gap.height.to_css_value(),
self.gap.width.to_css_value()
));
css.push(format!("flex-direction: {}", self.flex_direction.to_css_value()));
css.push(format!("flex-wrap: {}", self.flex_wrap.to_css_value()));
css.push(format!("flex-basis: {}", self.flex_basis.to_css_value()));
css.push(format!("flex-grow: {}", self.flex_grow));
css.push(format!("flex-shrink: {}", self.flex_shrink));
css.join("; ")
}
}
fn dump_node_as_html<T>(
tree: &TaffyTree<T>,
node_id: NodeId,
depth: String,
) -> Result<String, TaffyError> {
let css = tree.style(node_id)?.to_css_value();
let mut output = Vec::new();
output.push(format!("{}<div style=\"{}\">", depth, css));
for child_id in tree.children(node_id)? {
output.push(dump_node_as_html(tree, child_id, format!("{} ", depth))?);
}
output.push(format!("{}</div>", depth));
Ok(output.join("\n"))
}
pub(crate) fn print_tree_as_html<T>(
tree: &TaffyTree<T>,
root_node_id: NodeId,
print_func: fn(String) -> (),
) {
// We use a simple prelude that outlines boxes in red.
print_func("<html><style>div {{ box-sizing: border-box; border: 1px solid red; }}; body {{ margin: 20px; }}</style><body>".to_string());
print_func(dump_node_as_html(tree, root_node_id, String::new()).unwrap());
print_func("</body></html>".to_string());
}