use rdom_core::{Dom, NodeId, NodeType};
use unicode_width::UnicodeWidthStr;
use crate::ext::TuiExt;
use crate::layout::{Direction, Size};
use crate::node::TuiNodeExt;
use crate::render::inline::compute_inline_layout;
use crate::style::ComputedStyle;
use super::ifc::is_ifc_block;
pub(crate) fn intrinsic_size(
dom: &Dom<TuiExt>,
id: NodeId,
direction: Direction,
cross_budget: u16,
) -> u16 {
intrinsic_size_inner(dom, id, direction, cross_budget, IntrinsicMode::BoxSize)
}
pub(super) fn content_min_size(
dom: &Dom<TuiExt>,
id: NodeId,
direction: Direction,
cross_budget: u16,
) -> u16 {
intrinsic_size_inner(dom, id, direction, cross_budget, IntrinsicMode::ContentOnly)
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub(super) enum IntrinsicMode {
BoxSize,
ContentOnly,
}
fn intrinsic_size_inner(
dom: &Dom<TuiExt>,
id: NodeId,
direction: Direction,
cross_budget: u16,
mode: IntrinsicMode,
) -> u16 {
let kind = dom.node(id).node_type();
match kind {
NodeType::Text => intrinsic_text(dom, id, direction),
NodeType::Element | NodeType::Fragment => {
intrinsic_element(dom, id, direction, cross_budget, mode)
}
NodeType::Comment => 0,
}
}
fn intrinsic_text(dom: &Dom<TuiExt>, id: NodeId, direction: Direction) -> u16 {
let text = dom.text_content(id);
match direction {
Direction::Row => {
text.lines()
.map(|line| UnicodeWidthStr::width(line) as u16)
.max()
.unwrap_or(0)
}
Direction::Column => text.lines().count().max(1) as u16,
}
}
fn intrinsic_element(
dom: &Dom<TuiExt>,
id: NodeId,
direction: Direction,
cross_budget: u16,
mode: IntrinsicMode,
) -> u16 {
let computed = dom
.node(id)
.computed()
.cloned()
.unwrap_or_else(ComputedStyle::initial);
if mode == IntrinsicMode::BoxSize {
if direction == Direction::Row
&& let Some(w) = dom.node(id).ext().and_then(|e| e.table_used_width)
{
return w;
}
let declared = match direction {
Direction::Row => &computed.width,
Direction::Column => &computed.height,
};
if let Size::Fixed(n) = declared {
return *n;
}
}
let cb_w_for_pad = cross_budget;
let pad_main = match direction {
Direction::Row => {
computed.padding.left.resolve(cb_w_for_pad)
+ computed.padding.right.resolve(cb_w_for_pad)
}
Direction::Column => {
computed.padding.top.resolve(cb_w_for_pad)
+ computed.padding.bottom.resolve(cb_w_for_pad)
}
};
let border_main = border_main_cost(&computed, direction);
let pseudo_main = match direction {
Direction::Row => pseudo_content_width(dom, id),
Direction::Column => 0,
};
if is_ifc_block(dom, id) {
let content = match direction {
Direction::Row => inline_content_width(dom, id),
Direction::Column => {
let outer_width = match &computed.width {
Size::Fixed(n) => *n,
_ => cross_budget,
};
let row_pad = computed.padding.left.resolve(outer_width)
+ computed.padding.right.resolve(outer_width);
let row_border = border_main_cost(&computed, Direction::Row);
let content_width = outer_width
.saturating_sub(row_pad)
.saturating_sub(row_border);
let layout = compute_inline_layout(dom, id, content_width);
layout.height().max(1)
}
};
return content
.saturating_add(pseudo_main)
.saturating_add(pad_main)
.saturating_add(border_main);
}
use crate::layout::{Display, Position};
let children: Vec<NodeId> = super::element_children_of(dom, id)
.into_iter()
.filter(|&c| {
let c_computed = dom.node(c).ext().and_then(|e| e.computed.as_ref());
match c_computed {
Some(s) => {
s.display != Display::None
&& !matches!(s.position, Position::Absolute | Position::Fixed)
}
None => true,
}
})
.collect();
if children.is_empty() {
if has_non_whitespace_text(dom, id) {
let content = match direction {
Direction::Row => inline_content_width(dom, id),
Direction::Column => {
let outer_width = match &computed.width {
Size::Fixed(n) => *n,
_ => cross_budget,
};
let row_pad = computed.padding.left.resolve(outer_width)
+ computed.padding.right.resolve(outer_width);
let row_border = border_main_cost(&computed, Direction::Row);
let content_width = outer_width
.saturating_sub(row_pad)
.saturating_sub(row_border);
let layout = compute_inline_layout(dom, id, content_width);
layout.height().max(1)
}
};
return content
.saturating_add(pseudo_main)
.saturating_add(pad_main)
.saturating_add(border_main);
}
return pseudo_main
.saturating_add(pad_main)
.saturating_add(border_main);
}
let child_cross_budget = match direction {
Direction::Row => cross_budget.saturating_sub(
(computed.padding.top.resolve(cross_budget)
+ computed.padding.bottom.resolve(cross_budget))
.saturating_add(border_main_cost(&computed, Direction::Column)),
),
Direction::Column => cross_budget.saturating_sub(
(computed.padding.left.resolve(cross_budget)
+ computed.padding.right.resolve(cross_budget))
.saturating_add(border_main_cost(&computed, Direction::Row)),
),
};
let intrinsic_children: u16 = if computed.direction == direction {
let gap_total = computed
.gap
.saturating_mul((children.len() as u16).saturating_sub(1));
let children_main: u16 = children
.iter()
.map(|&c| intrinsic_size(dom, c, direction, child_cross_budget))
.fold(0u16, |acc, n| acc.saturating_add(n));
children_main.saturating_add(gap_total)
} else {
children
.iter()
.map(|&c| intrinsic_size(dom, c, direction, child_cross_budget))
.max()
.unwrap_or(0)
};
intrinsic_children
.saturating_add(pseudo_main)
.saturating_add(pad_main)
.saturating_add(border_main)
}
fn pseudo_content_width(dom: &Dom<TuiExt>, id: NodeId) -> u16 {
let mut acc: u32 = 0;
if let Some(before) = dom.node(id).computed_before()
&& let Some(text) = before.content.as_deref()
{
acc = acc.saturating_add(UnicodeWidthStr::width(text) as u32);
}
if let Some(after) = dom.node(id).computed_after()
&& let Some(text) = after.content.as_deref()
{
acc = acc.saturating_add(UnicodeWidthStr::width(text) as u32);
}
acc.min(u16::MAX as u32) as u16
}
pub(super) fn has_non_whitespace_text(dom: &Dom<TuiExt>, id: NodeId) -> bool {
for child in dom.node(id).child_nodes() {
if child.node_type() == NodeType::Text
&& let Some(text) = child.node_value()
&& !text.chars().all(char::is_whitespace)
{
return true;
}
}
false
}
pub(super) fn border_main_cost(computed: &ComputedStyle, direction: Direction) -> u16 {
let b = computed.border;
match direction {
Direction::Row => b.left.cells() + b.right.cells(),
Direction::Column => b.top.cells() + b.bottom.cells(),
}
}
pub(super) fn inline_content_width(dom: &Dom<TuiExt>, id: NodeId) -> u16 {
fn walk(dom: &Dom<TuiExt>, id: NodeId, acc: &mut u32) {
use crate::layout::{Display, Position};
for child in dom.node(id).child_nodes() {
match child.node_type() {
NodeType::Text => {
let text = child.node_value().unwrap_or("");
*acc = acc.saturating_add(UnicodeWidthStr::width(text) as u32);
}
NodeType::Element => {
let (display, position) = child
.ext()
.and_then(|e| e.computed.as_ref())
.map(|c| (c.display, c.position))
.unwrap_or((Display::Block, Position::Static));
if display == Display::None
|| matches!(position, Position::Absolute | Position::Fixed)
{
continue;
}
walk(dom, child.id(), acc);
}
_ => {}
}
}
}
let mut acc: u32 = 0;
walk(dom, id, &mut acc);
acc.min(u16::MAX as u32) as u16
}