use std::sync::Arc;
use crossterm::style::Color;
use crate::{
graphics::block_style,
layout::{
BoxConstraint, Cursor, InlineRun, LayoutBox, LayoutContext,
inner_formatting_context::flow::{LayoutBlockOptions, layout_block},
layoutbox::{
generation::{GeneratedBoxKind, generate_node},
q_quote_pair,
},
},
tdom::node::{Node, NodeArena, NodeId, NodeType},
};
pub fn layout_generic(
arena: &NodeArena,
node_id: NodeId,
containing: BoxConstraint,
cursor: &mut Cursor,
context: LayoutContext,
pre_allocated_rect: Option<crate::layout::Rect>,
) -> Option<LayoutBox> {
let node = &arena[node_id];
if !is_generic_block(&node.node_type) {
return None;
}
Some(layout_block(
arena,
node,
node_id,
containing,
cursor,
LayoutBlockOptions::default(),
context,
pre_allocated_rect,
))
}
fn is_generic_block(node_type: &NodeType) -> bool {
matches!(
node_type,
NodeType::Nav
| NodeType::Main
| NodeType::Section
| NodeType::Article
| NodeType::Figure
| NodeType::Figcaption
| NodeType::Details
| NodeType::Input
| NodeType::Form
| NodeType::Select
| NodeType::Pre
)
}
pub fn layout_inline_blockify(
arena: &NodeArena,
node_id: NodeId,
containing: BoxConstraint,
cursor: &mut Cursor,
context: LayoutContext,
pre_allocated_rect: Option<crate::layout::Rect>,
) -> Option<LayoutBox> {
let generated = generate_node(arena, node_id)?;
if generated.kind != GeneratedBoxKind::Normal || !generated.outer_display.is_inline() {
return None;
}
let pre_allocated_rect = pre_allocated_rect?;
let mut node = arena[node_id].clone();
apply_inline_ua_defaults(&mut node);
let opts = if node.node_type == NodeType::Q {
let (open, close) = q_quote_pair(arena, node_id);
let fg = node.css_attr.get_color();
let bg = node.css_attr.get_bg_color();
let style = block_style(&node.css_attr);
LayoutBlockOptions {
prefix: Some(InlineRun::synthesized_marker(open, style, fg, bg)),
suffix: Some(InlineRun::synthesized_marker(close, style, fg, bg)),
..Default::default()
}
} else {
LayoutBlockOptions::default()
};
Some(layout_block(
arena,
&node,
node_id,
containing,
cursor,
opts,
context,
Some(pre_allocated_rect),
))
}
fn apply_inline_ua_defaults(node: &mut Node) {
for &(prop, value) in node.node_type.inline_ua_declarations() {
if !node.css_attr.is_set(prop) {
node.css_attr.0.insert(Arc::from(prop), Arc::from(value));
}
}
if node.node_type == NodeType::Abbr
&& node.attributes.get("title").is_some()
&& !node.css_attr.is_set("text-decoration")
{
node.css_attr
.0
.insert(Arc::from("text-decoration"), Arc::from("underline"));
}
}
pub fn layout_summary(
arena: &NodeArena,
node_id: NodeId,
containing: BoxConstraint,
cursor: &mut Cursor,
context: LayoutContext,
pre_allocated_rect: Option<crate::layout::Rect>,
) -> Option<LayoutBox> {
let node = &arena[node_id];
if node.node_type != NodeType::Summary {
return None;
}
let prefix = summary_marker(arena, node_id).map(|marker| {
let fg = node.css_attr.get_color();
let bg = node.css_attr.get_bg_color();
let style = block_style(&node.css_attr);
InlineRun::synthesized_marker(marker, style, fg, bg)
});
Some(layout_block(
arena,
node,
node_id,
containing,
cursor,
LayoutBlockOptions {
prefix,
..Default::default()
},
context,
pre_allocated_rect,
))
}
fn summary_marker(arena: &NodeArena, node_id: NodeId) -> Option<String> {
let parent_id = arena[node_id].parent?;
if arena[parent_id].node_type != NodeType::Details {
return None;
}
let open = arena[parent_id].attributes.contains_key("open");
Some(if open { "▾ " } else { "▸ " }.to_string())
}
pub fn layout_progress(
arena: &NodeArena,
node_id: NodeId,
containing: BoxConstraint,
cursor: &mut Cursor,
context: LayoutContext,
pre_allocated_rect: Option<crate::layout::Rect>,
) -> Option<LayoutBox> {
let node = &arena[node_id];
if !matches!(node.node_type, NodeType::Progress | NodeType::Meter) {
return None;
}
let (ratio, color) = progress_ratio(node);
Some(layout_block(
arena,
node,
node_id,
containing,
cursor,
LayoutBlockOptions {
bar_fill: Some(ratio),
bar_color: color,
..Default::default()
},
context,
pre_allocated_rect,
))
}
fn progress_ratio(node: &Node) -> (f32, Option<Color>) {
let attr = |name: &str, default: f32| {
node.attributes
.get(name)
.and_then(|v| v.parse::<f32>().ok())
.unwrap_or(default)
};
let value = attr("value", 0.0);
if node.node_type == NodeType::Meter {
let min = attr("min", 0.0);
let max = attr("max", 1.0);
let ratio = if max > min {
((value - min) / (max - min)).clamp(0.0, 1.0)
} else {
0.0
};
let low = attr("low", min);
let high = attr("high", max);
let optimum = attr("optimum", (min + max) / 2.0);
let mid = (low + high) / 2.0;
let color = if value < low || value > high {
Color::Red
} else if (value >= optimum) == (mid >= optimum) {
Color::Green
} else {
Color::Yellow
};
(ratio, Some(color))
} else {
let max = attr("max", 1.0);
let ratio = if max > 0.0 {
(value / max).clamp(0.0, 1.0)
} else {
0.0
};
let color = node.css_attr.get_accent_color();
(ratio, color)
}
}
pub fn layout_hr(
arena: &NodeArena,
node_id: NodeId,
containing: BoxConstraint,
cursor: &mut Cursor,
context: LayoutContext,
pre_allocated_rect: Option<crate::layout::Rect>,
) -> Option<LayoutBox> {
let node = &arena[node_id];
if node.node_type != NodeType::Hr {
return None;
}
Some(layout_block(
arena,
node,
node_id,
containing,
cursor,
LayoutBlockOptions {
hr_rule: true,
spacing_after: 1,
..Default::default()
},
context,
pre_allocated_rect,
))
}