use std::collections::HashMap;
use std::fmt;
use bitflags::bitflags;
use slotmap::{DefaultKey, SlotMap};
use smallvec::SmallVec;
use crate::taffy::LayoutProps;
pub type NodeId = DefaultKey;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum NodeKind {
Text,
#[default]
Box,
Flex,
Input,
List,
Table,
Tree,
Scroll,
Tab,
Modal,
Code,
Spacer,
Separator,
Custom(u16),
}
impl NodeKind {
pub fn name(&self) -> &'static str {
match self {
Self::Text => "Text",
Self::Box => "Box",
Self::Flex => "Flex",
Self::Input => "Input",
Self::List => "List",
Self::Table => "Table",
Self::Tree => "Tree",
Self::Scroll => "Scroll",
Self::Tab => "Tab",
Self::Modal => "Modal",
Self::Code => "Code",
Self::Spacer => "Spacer",
Self::Separator => "Separator",
Self::Custom(_) => "Custom",
}
}
pub fn is_container(&self) -> bool {
!matches!(self, Self::Text | Self::Spacer | Self::Separator)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TreeError {
NodeNotFound(NodeId),
CycleDetected { node: NodeId, ancestor: NodeId },
InvalidOperation(String),
}
impl fmt::Display for TreeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NodeNotFound(id) => write!(f, "Node not found: {id:?}"),
Self::CycleDetected { node, ancestor } => {
write!(f, "Cycle detected: node {node:?} is ancestor of {ancestor:?}")
}
Self::InvalidOperation(msg) => write!(f, "Invalid operation: {msg}"),
}
}
}
impl std::error::Error for TreeError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Color {
Named(NamedColor),
Indexed(u8),
Rgb {
r: u8,
g: u8,
b: u8,
},
#[default]
Default,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ColorIntent {
Rgb,
Indexed,
Default,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Rgba {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Rgba {
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
pub fn rgb(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b, a: 255 }
}
pub fn from_hex(hex: &str) -> Option<Self> {
let hex = hex.trim_start_matches('#');
match hex.len() {
3 => {
let r = u8::from_str_radix(&hex[0..1], 16).ok()? * 17;
let g = u8::from_str_radix(&hex[1..2], 16).ok()? * 17;
let b = u8::from_str_radix(&hex[2..3], 16).ok()? * 17;
Some(Self::rgb(r, g, b))
}
4 => {
let r = u8::from_str_radix(&hex[0..1], 16).ok()? * 17;
let g = u8::from_str_radix(&hex[1..2], 16).ok()? * 17;
let b = u8::from_str_radix(&hex[2..3], 16).ok()? * 17;
let a = u8::from_str_radix(&hex[3..4], 16).ok()? * 17;
Some(Self::new(r, g, b, a))
}
6 => {
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
Some(Self::rgb(r, g, b))
}
8 => {
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
let a = u8::from_str_radix(&hex[6..8], 16).ok()?;
Some(Self::new(r, g, b, a))
}
_ => None,
}
}
pub fn to_hex(&self) -> String {
if self.a == 255 {
format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
} else {
format!("#{:02X}{:02X}{:02X}{:02X}", self.r, self.g, self.b, self.a)
}
}
pub fn lerp(&self, other: &Self, t: f32) -> Self {
let t = t.clamp(0.0, 1.0);
let inv_t = 1.0 - t;
Self {
r: (self.r as f32 * inv_t + other.r as f32 * t) as u8,
g: (self.g as f32 * inv_t + other.g as f32 * t) as u8,
b: (self.b as f32 * inv_t + other.b as f32 * t) as u8,
a: (self.a as f32 * inv_t + other.a as f32 * t) as u8,
}
}
pub fn blend_over(&self, background: &Self) -> Self {
let alpha = self.a as f32 / 255.0;
let inv_alpha = 1.0 - alpha;
Self {
r: (self.r as f32 * alpha + background.r as f32 * inv_alpha) as u8,
g: (self.g as f32 * alpha + background.g as f32 * inv_alpha) as u8,
b: (self.b as f32 * alpha + background.b as f32 * inv_alpha) as u8,
a: 255,
}
}
}
impl Default for Rgba {
fn default() -> Self {
Self::rgb(0, 0, 0)
}
}
impl From<Rgba> for Color {
fn from(rgba: Rgba) -> Self {
Color::Rgb { r: rgba.r, g: rgba.g, b: rgba.b }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum NamedColor {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
#[default]
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
}
impl Color {
pub fn rgb(r: u8, g: u8, b: u8) -> Self {
Self::Rgb { r, g, b }
}
pub fn intent(&self) -> ColorIntent {
match self {
Self::Named(_) => ColorIntent::Rgb,
Self::Indexed(_) => ColorIntent::Indexed,
Self::Rgb { .. } => ColorIntent::Rgb,
Self::Default => ColorIntent::Default,
}
}
pub fn parse(s: &str) -> Option<Self> {
let s = s.trim();
if s.is_empty() {
return Some(Self::Default);
}
if let Some(rgba) = Rgba::from_hex(s) {
if rgba.a == 0 {
return Some(Self::Default);
}
return Some(rgba.into());
}
let lower = s.to_lowercase();
if lower == "default" || lower == "transparent" || lower == "none" {
return Some(Self::Default);
}
if (lower.starts_with("rgb(") || lower.starts_with("rgba(")) && lower.ends_with(')') {
let content =
if lower.starts_with("rgba(") { &lower[5..lower.len() - 1] } else { &lower[4..lower.len() - 1] };
let parts: Vec<&str> = content.split(',').map(|p| p.trim()).collect();
if parts.len() >= 3 {
let r = parts[0].parse::<u8>().ok();
let g = parts[1].parse::<u8>().ok();
let b = parts[2].parse::<u8>().ok();
if let (Some(r), Some(g), Some(b)) = (r, g, b) {
if parts.len() >= 4
&& let Ok(a) = parts[3].parse::<f32>()
&& a == 0.0
{
return Some(Self::Default);
}
return Some(Self::Rgb { r, g, b });
}
}
}
match lower.as_str() {
"black" => Some(Self::Named(NamedColor::Black)),
"red" => Some(Self::Named(NamedColor::Red)),
"green" => Some(Self::Named(NamedColor::Green)),
"yellow" => Some(Self::Named(NamedColor::Yellow)),
"blue" => Some(Self::Named(NamedColor::Blue)),
"magenta" | "purple" | "fuchsia" => Some(Self::Named(NamedColor::Magenta)),
"cyan" | "teal" | "aqua" => Some(Self::Named(NamedColor::Cyan)),
"white" => Some(Self::Named(NamedColor::White)),
"gray" | "grey" | "dark_gray" | "darkgray" | "darkgrey" | "brightblack" | "bright_black" => {
Some(Self::Named(NamedColor::BrightBlack))
}
"bright_red" | "brightred" | "light_red" | "lightred" => Some(Self::Named(NamedColor::BrightRed)),
"bright_green" | "brightgreen" | "light_green" | "lightgreen" => Some(Self::Named(NamedColor::BrightGreen)),
"bright_yellow" | "brightyellow" | "light_yellow" | "lightyellow" => {
Some(Self::Named(NamedColor::BrightYellow))
}
"bright_blue" | "brightblue" | "light_blue" | "lightblue" => Some(Self::Named(NamedColor::BrightBlue)),
"bright_magenta" | "brightmagenta" | "light_magenta" | "lightmagenta" | "pink" => {
Some(Self::Named(NamedColor::BrightMagenta))
}
"bright_cyan" | "brightcyan" | "light_cyan" | "lightcyan" => Some(Self::Named(NamedColor::BrightCyan)),
"bright_white" | "brightwhite" | "silver" | "light_gray" | "lightgray" | "lightgrey" => {
Some(Self::Named(NamedColor::BrightWhite))
}
"orange" | "darkorange" => Some(Self::Rgb { r: 255, g: 165, b: 0 }),
_ => None,
}
}
pub fn to_rgba(&self, alpha: u8) -> Rgba {
match self {
Self::Named(named) => {
let (r, g, b) = named.to_rgb();
Rgba::new(r, g, b, alpha)
}
Self::Indexed(idx) => {
let (r, g, b) = indexed_to_rgb(*idx);
Rgba::new(r, g, b, alpha)
}
Self::Rgb { r, g, b } => Rgba::new(*r, *g, *b, alpha),
Self::Default => Rgba::new(0, 0, 0, alpha),
}
}
pub fn lerp(&self, other: &Self, t: f32) -> Self {
let c1 = self.to_rgba(255);
let c2 = other.to_rgba(255);
let blended = c1.lerp(&c2, t);
Color::Rgb { r: blended.r, g: blended.g, b: blended.b }
}
}
impl NamedColor {
pub fn ansi_index(&self) -> u8 {
match self {
Self::Black => 0,
Self::Red => 1,
Self::Green => 2,
Self::Yellow => 3,
Self::Blue => 4,
Self::Magenta => 5,
Self::Cyan => 6,
Self::White => 7,
Self::BrightBlack => 8,
Self::BrightRed => 9,
Self::BrightGreen => 10,
Self::BrightYellow => 11,
Self::BrightBlue => 12,
Self::BrightMagenta => 13,
Self::BrightCyan => 14,
Self::BrightWhite => 15,
}
}
pub fn to_rgb(&self) -> (u8, u8, u8) {
match self {
Self::Black => (0, 0, 0),
Self::Red => (170, 0, 0),
Self::Green => (0, 170, 0),
Self::Yellow => (170, 85, 0),
Self::Blue => (0, 0, 170),
Self::Magenta => (170, 0, 170),
Self::Cyan => (0, 170, 170),
Self::White => (170, 170, 170),
Self::BrightBlack => (85, 85, 85),
Self::BrightRed => (255, 85, 85),
Self::BrightGreen => (85, 255, 85),
Self::BrightYellow => (255, 255, 85),
Self::BrightBlue => (85, 85, 255),
Self::BrightMagenta => (255, 85, 255),
Self::BrightCyan => (85, 255, 255),
Self::BrightWhite => (255, 255, 255),
}
}
pub fn from_ansi_index(index: u8) -> Option<Self> {
match index {
0 => Some(Self::Black),
1 => Some(Self::Red),
2 => Some(Self::Green),
3 => Some(Self::Yellow),
4 => Some(Self::Blue),
5 => Some(Self::Magenta),
6 => Some(Self::Cyan),
7 => Some(Self::White),
8 => Some(Self::BrightBlack),
9 => Some(Self::BrightRed),
10 => Some(Self::BrightGreen),
11 => Some(Self::BrightYellow),
12 => Some(Self::BrightBlue),
13 => Some(Self::BrightMagenta),
14 => Some(Self::BrightCyan),
15 => Some(Self::BrightWhite),
_ => None,
}
}
}
#[doc(hidden)]
pub fn indexed_to_rgb(index: u8) -> (u8, u8, u8) {
match index {
0..=15 => NamedColor::from_ansi_index(index).map(|c| c.to_rgb()).unwrap_or((0, 0, 0)),
16..=231 => {
let idx = index - 16;
let r = idx / 36;
let g = (idx % 36) / 6;
let b = idx % 6;
let conv = |v: u8| if v == 0 { 0 } else { 55 + v * 40 };
(conv(r), conv(g), conv(b))
}
232..=255 => {
let gray = 8 + (index - 232) * 10;
(gray, gray, gray)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BorderStyle {
#[default]
None,
Solid,
Dashed,
Dotted,
Double,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Style {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub underline_color: Option<Color>,
pub bold: Option<bool>,
pub italic: Option<bool>,
pub underline: Option<bool>,
pub dim: Option<bool>,
pub strikethrough: Option<bool>,
pub inverse: Option<bool>,
pub hidden: Option<bool>,
pub grid_columns: Option<u16>,
pub grid_rows: Option<u16>,
pub border_style: Option<BorderStyle>,
pub border_color: Option<Color>,
pub border_width: Option<u16>,
pub rounded_corners: Option<bool>,
pub overflow: Option<Overflow>,
pub opacity: Option<u8>,
pub text_align: Option<crate::text::TextAlign>,
}
impl Style {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.fg.is_none()
&& self.bg.is_none()
&& self.underline_color.is_none()
&& self.bold.is_none()
&& self.italic.is_none()
&& self.underline.is_none()
&& self.dim.is_none()
&& self.strikethrough.is_none()
&& self.inverse.is_none()
&& self.hidden.is_none()
&& self.grid_columns.is_none()
&& self.grid_rows.is_none()
&& self.border_style.is_none()
&& self.border_color.is_none()
&& self.border_width.is_none()
&& self.rounded_corners.is_none()
&& self.overflow.is_none()
&& self.opacity.is_none()
&& self.text_align.is_none()
}
pub fn resolve(&self, parent: &Style) -> ResolvedStyle {
ResolvedStyle {
fg: self.fg.or(parent.fg),
bg: self.bg,
underline_color: self.underline_color.or(parent.underline_color),
bold: self.bold.or(parent.bold).unwrap_or(false),
italic: self.italic.or(parent.italic).unwrap_or(false),
underline: self.underline.or(parent.underline).unwrap_or(false),
dim: self.dim.or(parent.dim).unwrap_or(false),
strikethrough: self.strikethrough.or(parent.strikethrough).unwrap_or(false),
inverse: self.inverse.or(parent.inverse).unwrap_or(false),
hidden: self.hidden.or(parent.hidden).unwrap_or(false),
border_style: self.border_style.or(parent.border_style).unwrap_or(BorderStyle::None),
border_color: self.border_color.or(parent.border_color),
border_width: self.border_width.or(parent.border_width).unwrap_or(0),
rounded_corners: self.rounded_corners.or(parent.rounded_corners).unwrap_or(false),
overflow: self.overflow.or(parent.overflow).unwrap_or(Overflow::Visible),
opacity: self.opacity.or(parent.opacity).unwrap_or(255),
text_align: self.text_align.or(parent.text_align).unwrap_or(crate::text::TextAlign::Left),
}
}
pub fn merge(&mut self, other: &Style) {
if other.fg.is_some() {
self.fg = other.fg;
}
if other.bg.is_some() {
self.bg = other.bg;
}
if other.underline_color.is_some() {
self.underline_color = other.underline_color;
}
if other.bold.is_some() {
self.bold = other.bold;
}
if other.italic.is_some() {
self.italic = other.italic;
}
if other.underline.is_some() {
self.underline = other.underline;
}
if other.dim.is_some() {
self.dim = other.dim;
}
if other.strikethrough.is_some() {
self.strikethrough = other.strikethrough;
}
if other.inverse.is_some() {
self.inverse = other.inverse;
}
if other.hidden.is_some() {
self.hidden = other.hidden;
}
if other.grid_columns.is_some() {
self.grid_columns = other.grid_columns;
}
if other.grid_rows.is_some() {
self.grid_rows = other.grid_rows;
}
if other.border_style.is_some() {
self.border_style = other.border_style;
}
if other.border_color.is_some() {
self.border_color = other.border_color;
}
if other.border_width.is_some() {
self.border_width = other.border_width;
}
if other.rounded_corners.is_some() {
self.rounded_corners = other.rounded_corners;
}
if other.overflow.is_some() {
self.overflow = other.overflow;
}
if other.opacity.is_some() {
self.opacity = other.opacity;
}
if other.text_align.is_some() {
self.text_align = other.text_align;
}
}
pub fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
pub fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub fn bold(mut self, bold: bool) -> Self {
self.bold = Some(bold);
self
}
pub fn italic(mut self, italic: bool) -> Self {
self.italic = Some(italic);
self
}
pub fn underline(mut self, underline: bool) -> Self {
self.underline = Some(underline);
self
}
pub fn border(mut self, style: BorderStyle, color: Color) -> Self {
self.border_style = Some(style);
self.border_color = Some(color);
self.border_width = Some(1);
self
}
pub fn border_width(mut self, width: u16) -> Self {
self.border_width = Some(width);
self
}
pub fn rounded(mut self, rounded: bool) -> Self {
self.rounded_corners = Some(rounded);
self
}
pub fn overflow(mut self, overflow: Overflow) -> Self {
self.overflow = Some(overflow);
self
}
pub fn opacity(mut self, opacity: u8) -> Self {
self.opacity = Some(opacity);
self
}
pub fn text_align(mut self, align: crate::text::TextAlign) -> Self {
self.text_align = Some(align);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResolvedStyle {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub underline_color: Option<Color>,
pub bold: bool,
pub italic: bool,
pub underline: bool,
pub dim: bool,
pub strikethrough: bool,
pub inverse: bool,
pub hidden: bool,
pub border_style: BorderStyle,
pub border_color: Option<Color>,
pub border_width: u16,
pub rounded_corners: bool,
pub overflow: Overflow,
pub opacity: u8,
pub text_align: crate::text::TextAlign,
}
impl Default for ResolvedStyle {
fn default() -> Self {
Self {
fg: None,
bg: None,
underline_color: None,
bold: false,
italic: false,
underline: false,
dim: false,
strikethrough: false,
inverse: false,
hidden: false,
border_style: BorderStyle::None,
border_color: None,
border_width: 0,
rounded_corners: false,
overflow: Overflow::Visible,
opacity: 255,
text_align: crate::text::TextAlign::Left,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Visibility {
pub display: Display,
pub opacity: f32,
pub clip: bool,
}
impl Default for Visibility {
fn default() -> Self {
Self { display: Display::Flex, opacity: 1.0, clip: false }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Display {
#[default]
Flex,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Transform {
pub translate_x: i32,
pub translate_y: i32,
pub z_index: i32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Overflow {
#[default]
Visible,
Hidden,
Scroll,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CursorProps {
pub style: CursorStyle,
pub blink: bool,
pub position: Option<Point>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CursorStyle {
#[default]
Block,
Underline,
Bar,
None,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Point {
pub x: u16,
pub y: u16,
}
impl Point {
pub fn new(x: u16, y: u16) -> Self {
Self { x, y }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Size {
pub width: u16,
pub height: u16,
}
impl Size {
pub fn new(width: u16, height: u16) -> Self {
Self { width, height }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
impl Rect {
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
Self { x, y, width, height }
}
pub fn right(&self) -> u16 {
self.x + self.width
}
pub fn bottom(&self) -> u16 {
self.y + self.height
}
pub fn contains(&self, point: Point) -> bool {
point.x >= self.x && point.x < self.right() && point.y >= self.y && point.y < self.bottom()
}
pub fn intersects(&self, other: &Rect) -> bool {
self.x < other.right() && self.right() > other.x && self.y < other.bottom() && self.bottom() > other.y
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Metadata {
pub key: Option<Box<str>>,
pub test_id: Option<Box<str>>,
pub aria_label: Option<Box<str>>,
pub tooltip: Option<Box<str>>,
}
impl Metadata {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Accessibility {
pub role: AriaRole,
pub label: Option<AriaLabel>,
pub description: Option<AriaLabel>,
pub live: AriaLive,
pub hidden: bool,
pub properties: AriaProperties,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct AriaProperties {
pub expanded: Option<bool>,
pub selected: Option<bool>,
pub checked: Option<AriaChecked>,
pub disabled: Option<bool>,
pub pressed: Option<AriaPressed>,
pub current: Option<AriaCurrent>,
pub relevant: Option<AriaRelevant>,
pub atomic: Option<bool>,
pub busy: Option<bool>,
pub level: Option<u32>,
pub value_min: Option<f64>,
pub value_max: Option<f64>,
pub value_now: Option<f64>,
pub value_text: Option<Box<str>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum AriaChecked {
#[default]
False,
True,
Mixed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum AriaPressed {
#[default]
False,
True,
Mixed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum AriaCurrent {
#[default]
False,
Page,
Step,
Location,
Date,
Time,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AriaRelevant {
pub additions: bool,
pub removals: bool,
pub text: bool,
pub all: bool,
}
impl Default for AriaRelevant {
fn default() -> Self {
Self { additions: true, removals: false, text: true, all: false }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AriaLabel(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum AriaRole {
#[default]
Text,
Button,
Input,
Link,
Checkbox,
Radio,
Switch,
Slider,
Tab,
TabPanel,
Menu,
Menuitem,
Menuitemcheckbox,
Menuitemradio,
List,
Listbox,
ListItem,
Option,
Table,
Grid,
TableRow,
TableCell,
Columnheader,
Rowheader,
Tree,
TreeItem,
Treegrid,
Dialog,
Alertdialog,
Alert,
Status,
Log,
Marquee,
Timer,
Progressbar,
Toolbar,
Menubar,
Tablist,
Group,
Region,
Heading,
Form,
Img,
Complementary,
Contentinfo,
Definition,
Directory,
Document,
Feed,
Figure,
Footer,
Header,
Landmark,
Main,
Navigation,
None,
Note,
Presentation,
Search,
Separator,
Custom(u16),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum AriaLive {
#[default]
Off,
Polite,
Assertive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FocusInfo {
pub focusable: bool,
pub tabindex: Option<i32>,
pub focused: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct KeyboardInfo {
pub keybindings: Vec<Keybinding>,
pub roledescription: Option<Box<str>>,
pub describedby: Option<Box<str>>,
pub flowto: Option<Box<str>>,
pub labelledby: Option<Box<str>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Keybinding {
pub key: Box<str>,
pub description: Box<str>,
}
impl Keybinding {
pub fn new(key: impl Into<Box<str>>, description: impl Into<Box<str>>) -> Self {
Self { key: key.into(), description: description.into() }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FocusProps {
pub tab_index: Option<i32>,
pub focusable: bool,
pub focused: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NodeState {
pub scroll_x: i32,
pub scroll_y: i32,
pub content_width: u32,
pub content_height: u32,
pub dirty: bool,
pub layout_dirty: bool,
pub render_dirty: bool,
}
impl Default for NodeState {
fn default() -> Self {
Self {
scroll_x: 0,
scroll_y: 0,
content_width: 0,
content_height: 0,
dirty: true,
layout_dirty: true,
render_dirty: true,
}
}
}
impl NodeState {
pub fn new() -> Self {
Self::default()
}
pub fn mark_dirty(&mut self) {
self.dirty = true;
self.layout_dirty = true;
self.render_dirty = true;
}
pub fn mark_layout_dirty(&mut self) {
self.dirty = true;
self.layout_dirty = true;
self.render_dirty = true;
}
pub fn mark_render_dirty(&mut self) {
self.dirty = true;
self.render_dirty = true;
}
pub fn clear_dirty(&mut self) {
self.dirty = false;
self.layout_dirty = false;
self.render_dirty = false;
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct UpdateFlags: u32 {
const STYLE = 0b0000_0001;
const LAYOUT = 0b0000_0010;
const TEXT = 0b0000_0100;
const CHILDREN = 0b0000_1000;
const VISIBILITY = 0b0001_0000;
const TRANSFORM = 0b0010_0000;
const FOCUS = 0b0100_0000;
const METADATA = 0b1000_0000;
const ALL = 0b1111_1111;
}
}
impl Default for UpdateFlags {
fn default() -> Self {
Self::empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct EventHandlers {
pub has_handlers: bool,
}
pub struct RenderNode {
pub id: NodeId,
pub kind: NodeKind,
pub parent: Option<NodeId>,
pub children: SmallVec<[NodeId; 4]>,
pub style: Style,
pub layout: LayoutProps,
pub text: Option<Box<str>>,
pub visibility: Visibility,
pub transform: Transform,
pub overflow: Overflow,
pub cursor: Option<CursorProps>,
pub text_align: crate::text::TextAlign,
pub text_wrap: bool,
pub focus: FocusProps,
pub events: EventHandlers,
pub state: NodeState,
pub metadata: Option<Box<Metadata>>,
pub accessibility: Option<Box<Accessibility>>,
pub attributes: HashMap<String, String>,
}
impl Default for RenderNode {
fn default() -> Self {
Self {
id: NodeId::default(),
kind: NodeKind::default(),
parent: None,
children: SmallVec::new(),
style: Style::default(),
layout: LayoutProps::default(),
text: None,
visibility: Visibility::default(),
transform: Transform::default(),
overflow: Overflow::default(),
cursor: None,
text_align: crate::text::TextAlign::Left,
text_wrap: false,
focus: FocusProps::default(),
events: EventHandlers::default(),
state: NodeState::default(),
metadata: None,
accessibility: None,
attributes: HashMap::new(),
}
}
}
impl RenderNode {
pub fn new(kind: NodeKind) -> Self {
Self { kind, ..Default::default() }
}
pub fn text(content: impl Into<Box<str>>) -> Self {
Self { kind: NodeKind::Text, text: Some(content.into()), ..Default::default() }
}
pub fn box_node() -> Self {
Self::new(NodeKind::Box)
}
pub fn flex() -> Self {
Self::new(NodeKind::Flex)
}
pub fn has_children(&self) -> bool {
!self.children.is_empty()
}
pub fn child_count(&self) -> usize {
self.children.len()
}
pub fn is_root(&self) -> bool {
self.parent.is_none()
}
pub fn set_text(&mut self, content: impl Into<Box<str>>) {
self.text = Some(content.into());
self.state.mark_render_dirty();
}
pub fn set_style(&mut self, style: Style) {
self.style = style;
self.state.mark_render_dirty();
}
pub fn set_layout(&mut self, layout: LayoutProps) {
self.layout = layout;
self.state.mark_layout_dirty();
}
pub fn focus(&mut self) {
self.focus.focused = true;
self.state.mark_render_dirty();
}
pub fn blur(&mut self) {
self.focus.focused = false;
self.state.mark_render_dirty();
}
pub fn set_focusable(&mut self, focusable: bool) {
self.focus.focusable = focusable;
}
}
pub struct NodeArena {
nodes: SlotMap<NodeId, RenderNode>,
root: NodeId,
generation: u64,
change_count: u64,
}
impl Default for NodeArena {
fn default() -> Self {
Self::new()
}
}
impl NodeArena {
pub fn new() -> Self {
let mut nodes = SlotMap::with_key();
let root = nodes.insert(RenderNode { kind: NodeKind::Box, ..Default::default() });
Self { nodes, root, generation: 0, change_count: 0 }
}
pub fn mark_changed(&mut self) {
self.change_count += 1;
}
pub fn change_count(&self) -> u64 {
self.change_count
}
pub fn insert(&mut self, node: RenderNode) -> NodeId {
let id = self.nodes.insert(node);
self.nodes[id].id = id;
self.generation += 1;
self.mark_changed();
id
}
pub fn get(&self, id: NodeId) -> Option<&RenderNode> {
self.nodes.get(id)
}
pub fn get_mut(&mut self, id: NodeId) -> Option<&mut RenderNode> {
self.nodes.get_mut(id)
}
pub fn remove(&mut self, id: NodeId) -> Option<RenderNode> {
if id == self.root {
return None;
}
self.generation += 1;
self.mark_changed();
self.nodes.remove(id)
}
pub fn contains(&self, id: NodeId) -> bool {
self.nodes.contains_key(id)
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn clear_dirty_flags(&mut self) {
for (_, node) in &mut self.nodes {
node.state.clear_dirty();
}
}
pub fn clear(&mut self) {
self.nodes.clear();
self.root = self.nodes.insert(RenderNode { kind: NodeKind::Box, ..Default::default() });
self.nodes[self.root].id = self.root;
self.generation += 1;
self.mark_changed();
}
pub fn root(&self) -> NodeId {
self.root
}
pub fn generation(&self) -> u64 {
self.generation
}
pub fn iter(&self) -> impl Iterator<Item = (NodeId, &RenderNode)> {
self.nodes.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (NodeId, &mut RenderNode)> {
self.nodes.iter_mut()
}
pub fn children(&self, id: NodeId) -> SmallVec<[NodeId; 4]> {
self.nodes.get(id).map(|n| n.children.clone()).unwrap_or_default()
}
pub fn descendants(&self, id: NodeId) -> Vec<NodeId> {
let mut result = Vec::new();
self.descendants_recursive(id, &mut result);
result
}
fn descendants_recursive(&self, id: NodeId, result: &mut Vec<NodeId>) {
if let Some(node) = self.nodes.get(id) {
for &child in &node.children {
result.push(child);
self.descendants_recursive(child, result);
}
}
}
pub fn ancestors(&self, id: NodeId) -> Vec<NodeId> {
let mut result = Vec::new();
let mut current = id;
while let Some(node) = self.nodes.get(current) {
if let Some(parent) = node.parent {
result.push(parent);
current = parent;
} else {
break;
}
}
result
}
pub fn descendant_count(&self, id: NodeId) -> usize {
let mut count = 0;
if let Some(node) = self.nodes.get(id) {
for &child in &node.children {
count += 1;
count += self.descendant_count(child);
}
}
count
}
pub fn depth(&self, id: NodeId) -> u32 {
let mut depth = 0;
let mut current = id;
while let Some(node) = self.nodes.get(current) {
if let Some(parent) = node.parent {
depth += 1;
current = parent;
} else {
break;
}
}
depth
}
pub fn is_ancestor(&self, ancestor: NodeId, descendant: NodeId) -> bool {
let mut current = descendant;
while let Some(node) = self.nodes.get(current) {
if let Some(parent) = node.parent {
if parent == ancestor {
return true;
}
current = parent;
} else {
break;
}
}
false
}
pub fn append_child(&mut self, parent: NodeId, child: NodeId) -> Result<(), TreeError> {
if !self.contains(parent) {
return Err(TreeError::NodeNotFound(parent));
}
if !self.contains(child) {
return Err(TreeError::NodeNotFound(child));
}
if child == self.root {
return Err(TreeError::InvalidOperation("Cannot append root as child".into()));
}
if self.is_ancestor(child, parent) {
return Err(TreeError::CycleDetected { node: child, ancestor: parent });
}
if let Some(_current_parent) = self.nodes[child].parent {
self.detach(child);
}
self.nodes[child].parent = Some(parent);
self.nodes[parent].children.push(child);
self.generation += 1;
self.mark_changed();
Ok(())
}
pub fn insert_before(&mut self, reference: NodeId, child: NodeId) -> Result<(), TreeError> {
if !self.contains(reference) {
return Err(TreeError::NodeNotFound(reference));
}
if !self.contains(child) {
return Err(TreeError::NodeNotFound(child));
}
if child == self.root {
return Err(TreeError::InvalidOperation("Cannot insert root as child".into()));
}
if self.is_ancestor(child, reference) {
return Err(TreeError::CycleDetected { node: child, ancestor: reference });
}
let parent =
self.nodes[reference].parent.ok_or(TreeError::InvalidOperation("Reference node has no parent".into()))?;
if let Some(_current_parent) = self.nodes[child].parent {
self.detach(child);
}
if let Some(parent_node) = self.nodes.get_mut(parent) {
if let Some(idx) = parent_node.children.iter().position(|&id| id == reference) {
parent_node.children.insert(idx, child);
} else {
return Err(TreeError::InvalidOperation("Reference node not found in parent's children".into()));
}
}
self.nodes[child].parent = Some(parent);
self.generation += 1;
self.mark_changed();
Ok(())
}
pub fn move_node(&mut self, node: NodeId, new_parent: NodeId) -> Result<(), TreeError> {
if !self.contains(node) {
return Err(TreeError::NodeNotFound(node));
}
if !self.contains(new_parent) {
return Err(TreeError::NodeNotFound(new_parent));
}
if node == self.root {
return Err(TreeError::InvalidOperation("Cannot move root".into()));
}
if self.is_ancestor(node, new_parent) {
return Err(TreeError::CycleDetected { node, ancestor: new_parent });
}
self.detach(node);
self.append_child(new_parent, node)
}
pub fn replace_node(&mut self, old: NodeId, new: NodeId) -> Result<(), TreeError> {
if !self.contains(old) {
return Err(TreeError::NodeNotFound(old));
}
if !self.contains(new) {
return Err(TreeError::NodeNotFound(new));
}
if old == self.root {
return Err(TreeError::InvalidOperation("Cannot replace root".into()));
}
if new == self.root {
return Err(TreeError::InvalidOperation("Cannot replace with root".into()));
}
let parent = self.nodes[old].parent.ok_or(TreeError::InvalidOperation("Old node has no parent".into()))?;
let old_children = std::mem::take(&mut self.nodes[old].children);
for &child in &old_children {
self.nodes[child].parent = Some(new);
self.nodes[new].children.push(child);
}
if let Some(parent_node) = self.nodes.get_mut(parent)
&& let Some(idx) = parent_node.children.iter().position(|&id| id == old)
{
parent_node.children[idx] = new;
}
self.nodes[new].parent = Some(parent);
self.nodes.remove(old);
self.generation += 1;
self.mark_changed();
Ok(())
}
pub fn remove_subtree(&mut self, id: NodeId) {
if id == self.root {
let children: SmallVec<[NodeId; 4]> = self.nodes[self.root].children.clone();
for child in children {
self.remove_subtree_recursive(child);
}
self.nodes[self.root].children.clear();
return;
}
self.detach(id);
self.remove_subtree_recursive(id);
self.generation += 1;
self.mark_changed();
}
fn remove_subtree_recursive(&mut self, id: NodeId) {
let children: SmallVec<[NodeId; 4]> = self.nodes.get(id).map(|n| n.children.clone()).unwrap_or_default();
for child in children {
self.remove_subtree_recursive(child);
}
self.nodes.remove(id);
}
pub fn detach(&mut self, id: NodeId) {
if id == self.root {
return;
}
let parent = match self.nodes.get(id).and_then(|n| n.parent) {
Some(p) => p,
None => return,
};
if let Some(parent_node) = self.nodes.get_mut(parent) {
parent_node.children.retain(|c| *c != id);
}
if let Some(node) = self.nodes.get_mut(id) {
node.parent = None;
}
self.generation += 1;
self.mark_changed();
}
pub fn validate(&self) -> Result<(), TreeError> {
let root = self.nodes.get(self.root).ok_or(TreeError::NodeNotFound(self.root))?;
if root.parent.is_some() {
return Err(TreeError::InvalidOperation("Root has parent".into()));
}
for (id, node) in &self.nodes {
if id == self.root {
continue;
}
let parent_id =
node.parent.ok_or(TreeError::InvalidOperation(format!("Non-root node {id:?} has no parent")))?;
if !self.contains(parent_id) {
return Err(TreeError::InvalidOperation(format!(
"Node {id:?} references non-existent parent {parent_id:?}"
)));
}
let parent_node = &self.nodes[parent_id];
if !parent_node.children.contains(&id) {
return Err(TreeError::InvalidOperation(format!(
"Node {id:?} claims parent {parent_id:?} but is not in parent's children"
)));
}
}
for (id, node) in &self.nodes {
for &child in &node.children {
if !self.contains(child) {
return Err(TreeError::InvalidOperation(format!(
"Node {id:?} references non-existent child {child:?}"
)));
}
}
}
Ok(())
}
pub fn print_tree(&self) -> String {
let mut output = String::new();
self.print_node(self.root, &mut output, "", true);
output
}
fn print_node(&self, id: NodeId, output: &mut String, prefix: &str, is_last: bool) {
if let Some(node) = self.nodes.get(id) {
let connector = if is_last { "└── " } else { "├── " };
let kind_name = node.kind.name();
let text_preview = node.text.as_ref().map(|t| format!(" \"{}\"", t)).unwrap_or_default();
output.push_str(&format!("{prefix}{connector}{kind_name}{text_preview}\n"));
let child_prefix = format!("{prefix}{}", if is_last { " " } else { "│ " });
let child_count = node.children.len();
for (i, &child) in node.children.iter().enumerate() {
self.print_node(child, output, &child_prefix, i == child_count - 1);
}
}
}
}
impl fmt::Debug for NodeArena {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NodeArena")
.field("len", &self.len())
.field("generation", &self.generation)
.field("root", &self.root)
.finish()
}
}
pub use Display as VisibilityDisplay;