use crate::node_id::NodeId;
use crate::style::{BoxSizing, OffsetValue, Scalar};
use crate::widget_tree::WidgetTree;
use super::common::{get_node_style, resolve_scalar_to_cells};
use super::region::{CarveDir, Region, border_spacing};
pub(crate) fn carve_edge(
tree: &mut WidgetTree,
child: NodeId,
direction: CarveDir,
x0: &mut u16,
y0: &mut u16,
x1: &mut u16,
y1: &mut u16,
viewport: (u16, u16),
) {
let style = get_node_style(tree, child);
let margin = style.effective_margin();
let padding = style.effective_padding();
let (bt, bb, bl, br) = border_spacing(&style);
let border_top = bt as u16;
let border_bottom = bb as u16;
let border_left = bl as u16;
let border_right = br as u16;
let box_sizing = style.box_sizing.unwrap_or(BoxSizing::BorderBox);
let current_w = x1.saturating_sub(*x0);
let current_h = y1.saturating_sub(*y0);
let height_is_explicit = matches!(style.height, Some(ref s) if !matches!(s, Scalar::Auto));
let width_is_explicit = matches!(style.width, Some(ref s) if !matches!(s, Scalar::Auto));
let child_h = match style.height.as_ref() {
None => 1, Some(Scalar::Auto) => {
tree.get(child)
.and_then(|node| node.widget.layout_height())
.and_then(|h| u16::try_from(h).ok())
.unwrap_or(current_h)
}
Some(s) => resolve_scalar_to_cells(s, current_h, viewport.1),
};
let child_w = match style.width.as_ref() {
None => current_w, Some(Scalar::Auto) => {
let intrinsic = tree
.get(child)
.and_then(|node| node.widget.content_width())
.and_then(|w| u16::try_from(w).ok());
if let Some(w) = intrinsic {
w
} else {
let max_w = tree
.get(child)
.map(|node| node.widget.layout_constraints())
.and_then(|c| c.max_width)
.and_then(|w| u16::try_from(w).ok());
max_w.unwrap_or(current_w)
}
}
Some(s) => resolve_scalar_to_cells(s, current_w, viewport.0),
};
let child_w = {
let mut w = child_w;
if let Some(ref s) = style.min_width {
w = w.max(resolve_scalar_to_cells(s, current_w, viewport.0));
}
if let Some(ref s) = style.max_width {
w = w.min(resolve_scalar_to_cells(s, current_w, viewport.0));
}
w
};
let child_h = {
let mut h = child_h;
if let Some(ref s) = style.min_height {
h = h.max(resolve_scalar_to_cells(s, current_h, viewport.1));
}
if let Some(ref s) = style.max_height {
h = h.min(resolve_scalar_to_cells(s, current_h, viewport.1));
}
h
};
let chrome_h = border_top + border_bottom + padding.top + padding.bottom;
let chrome_w = border_left + border_right + padding.left + padding.right;
let outer_h = if box_sizing == BoxSizing::BorderBox && height_is_explicit {
child_h.saturating_add(margin.top + margin.bottom)
} else {
child_h
.saturating_add(chrome_h)
.saturating_add(margin.top + margin.bottom)
};
let outer_w = if box_sizing == BoxSizing::BorderBox && width_is_explicit {
child_w.saturating_add(margin.left + margin.right)
} else {
child_w
.saturating_add(chrome_w)
.saturating_add(margin.left + margin.right)
};
let (layout_x, layout_y, layout_w, layout_h) = match direction {
CarveDir::Top => {
let lx = x0.saturating_add(margin.left);
let ly = y0.saturating_add(margin.top);
let lw = current_w.saturating_sub(margin.left + margin.right);
let lh = outer_h.saturating_sub(margin.top + margin.bottom);
*y0 = y0.saturating_add(outer_h);
(lx, ly, lw, lh)
}
CarveDir::Bottom => {
let lx = x0.saturating_add(margin.left);
let ly = y1.saturating_sub(outer_h).saturating_add(margin.top);
let lw = current_w.saturating_sub(margin.left + margin.right);
let lh = outer_h.saturating_sub(margin.top + margin.bottom);
*y1 = y1.saturating_sub(outer_h);
(lx, ly, lw, lh)
}
CarveDir::Left => {
let lx = x0.saturating_add(margin.left);
let ly = y0.saturating_add(margin.top);
let lw = outer_w.saturating_sub(margin.left + margin.right);
let lh = current_h.saturating_sub(margin.top + margin.bottom);
*x0 = x0.saturating_add(outer_w);
(lx, ly, lw, lh)
}
CarveDir::Right => {
let lx = x1.saturating_sub(outer_w).saturating_add(margin.left);
let ly = y0.saturating_add(margin.top);
let lw = outer_w.saturating_sub(margin.left + margin.right);
let lh = current_h.saturating_sub(margin.top + margin.bottom);
*x1 = x1.saturating_sub(outer_w);
(lx, ly, lw, lh)
}
};
let content_x = layout_x.saturating_add(border_left + padding.left);
let content_y = layout_y.saturating_add(border_top + padding.top);
let content_w = layout_w.saturating_sub(chrome_w);
let content_h = layout_h.saturating_sub(chrome_h);
if let Some(node) = tree.get_mut(child) {
node.layout_rect = Region::new(layout_x, layout_y, layout_w, layout_h).to_rect();
node.content_rect = Region::new(content_x, content_y, content_w, content_h).to_rect();
}
}
pub(crate) fn arrange_split(
tree: &mut WidgetTree,
split_children: &[NodeId],
available: Region,
viewport: (u16, u16),
) -> Region {
let mut x0 = available.x;
let mut y0 = available.y;
let mut x1 = available.x.saturating_add(available.width);
let mut y1 = available.y.saturating_add(available.height);
for &child in split_children {
let style = get_node_style(tree, child);
let split = match style.split {
Some(s) => s,
None => continue,
};
carve_edge(
tree,
child,
CarveDir::from(split),
&mut x0,
&mut y0,
&mut x1,
&mut y1,
viewport,
);
}
Region::new(x0, y0, x1.saturating_sub(x0), y1.saturating_sub(y0))
}
pub(crate) fn layout_absolute(
tree: &mut WidgetTree,
children: &[NodeId],
available: Region,
viewport: (u16, u16),
) {
for &child in children {
let style = get_node_style(tree, child);
let margin = style.effective_margin();
let padding = style.effective_padding();
let (bt, bb, bl, br) = border_spacing(&style);
let box_sizing = style.box_sizing.unwrap_or(BoxSizing::BorderBox);
let chrome_w = bl + br + padding.left + padding.right;
let chrome_h = bt + bb + padding.top + padding.bottom;
let height_is_explicit = style.height.is_some();
let width_is_explicit = style.width.is_some();
let mut layout_w = match style.width.as_ref() {
Some(s) => {
let content_w = resolve_scalar_to_cells(s, available.width, viewport.0);
if box_sizing == BoxSizing::BorderBox && width_is_explicit {
content_w
} else {
content_w.saturating_add(chrome_w)
}
}
None => available.width.saturating_sub(margin.left + margin.right),
};
let mut layout_h = match style.height.as_ref() {
Some(s) => {
let content_h = resolve_scalar_to_cells(s, available.height, viewport.1);
if box_sizing == BoxSizing::BorderBox && height_is_explicit {
content_h
} else {
content_h.saturating_add(chrome_h)
}
}
None => available.height.saturating_sub(margin.top + margin.bottom),
};
if let Some(ref s) = style.min_width {
let min_w = resolve_scalar_to_cells(s, available.width, viewport.0);
let min_w_outer = if box_sizing == BoxSizing::BorderBox {
min_w
} else {
min_w.saturating_add(chrome_w)
};
layout_w = layout_w.max(min_w_outer);
}
if let Some(ref s) = style.max_width {
let max_w = resolve_scalar_to_cells(s, available.width, viewport.0);
let max_w_outer = if box_sizing == BoxSizing::BorderBox {
max_w
} else {
max_w.saturating_add(chrome_w)
};
layout_w = layout_w.min(max_w_outer);
}
if let Some(ref s) = style.min_height {
let min_h = resolve_scalar_to_cells(s, available.height, viewport.1);
let min_h_outer = if box_sizing == BoxSizing::BorderBox {
min_h
} else {
min_h.saturating_add(chrome_h)
};
layout_h = layout_h.max(min_h_outer);
}
if let Some(ref s) = style.max_height {
let max_h = resolve_scalar_to_cells(s, available.height, viewport.1);
let max_h_outer = if box_sizing == BoxSizing::BorderBox {
max_h
} else {
max_h.saturating_add(chrome_h)
};
layout_h = layout_h.min(max_h_outer);
}
let offset = style.offset.unwrap_or_default();
let base_x = available.x.saturating_add(margin.left);
let base_y = available.y.saturating_add(margin.top);
let layout_x = {
let dx = match offset.x {
OffsetValue::Cells(c) => c as i32,
OffsetValue::Percent(p) => (layout_w as f32 * p / 100.0).round() as i32,
};
if dx >= 0 {
base_x.saturating_add(dx as u16)
} else {
base_x.saturating_sub(dx.unsigned_abs() as u16)
}
};
let layout_y = {
let dy = match offset.y {
OffsetValue::Cells(c) => c as i32,
OffsetValue::Percent(p) => (layout_h as f32 * p / 100.0).round() as i32,
};
if dy >= 0 {
base_y.saturating_add(dy as u16)
} else {
base_y.saturating_sub(dy.unsigned_abs() as u16)
}
};
let content_x = layout_x.saturating_add(bl + padding.left);
let content_y = layout_y.saturating_add(bt + padding.top);
let content_w = layout_w.saturating_sub(chrome_w);
let content_h = layout_h.saturating_sub(chrome_h);
if let Some(node) = tree.get_mut(child) {
node.layout_rect = Region::new(layout_x, layout_y, layout_w, layout_h).to_rect();
node.content_rect = Region::new(content_x, content_y, content_w, content_h).to_rect();
}
}
}