use std::collections::VecDeque;
use crate::taffy::{FlexDirection, FlexWrap, LayoutProps};
use crate::tree::{Color, NodeArena, NodeId, NodeKind, RenderNode, Style};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ScreenMode {
#[default]
AlternateScreen,
MainScreen,
SplitFooter { height: u16 },
}
impl ScreenMode {
pub fn name(&self) -> &'static str {
match self {
Self::AlternateScreen => "alternate-screen",
Self::MainScreen => "main-screen",
Self::SplitFooter { .. } => "split-footer",
}
}
pub fn render_offset(&self, terminal_height: u16) -> u16 {
match self {
Self::SplitFooter { height } => terminal_height.saturating_sub(*height),
_ => 0,
}
}
pub fn viewport_height(&self, terminal_height: u16) -> u16 {
match self {
Self::SplitFooter { height } => terminal_height.saturating_sub(*height),
_ => terminal_height,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Command {
CreateNode { id: NodeId, kind: NodeKind },
RemoveNode { id: NodeId },
AppendChild { parent: NodeId, child: NodeId },
InsertBefore { reference: NodeId, child: NodeId },
MoveNode { node: NodeId, new_parent: NodeId },
ReplaceNode { old: NodeId, new: NodeId },
DetachNode { id: NodeId },
SetStyle { id: NodeId, style: Style },
SetForeground { id: NodeId, color: Color },
SetBackground { id: NodeId, color: Color },
SetBold { id: NodeId, value: bool },
SetItalic { id: NodeId, value: bool },
SetUnderline { id: NodeId, value: bool },
SetStrikethrough { id: NodeId, value: bool },
SetDim { id: NodeId, value: bool },
SetInverse { id: NodeId, value: bool },
SetHidden { id: NodeId, value: bool },
SetLayout { id: NodeId, layout: LayoutProps },
SetFlexDirection { id: NodeId, direction: FlexDirection },
SetFlexWrap { id: NodeId, value: FlexWrap },
SetJustifyContent { id: NodeId, value: crate::taffy::JustifyContent },
SetAlignItems { id: NodeId, value: crate::taffy::AlignItems },
SetAlignSelf { id: NodeId, value: crate::taffy::AlignSelf },
SetWidth { id: NodeId, value: crate::taffy::Sizing },
SetHeight { id: NodeId, value: crate::taffy::Sizing },
SetMinWidth { id: NodeId, value: crate::taffy::Sizing },
SetMinHeight { id: NodeId, value: crate::taffy::Sizing },
SetMaxWidth { id: NodeId, value: crate::taffy::Sizing },
SetMaxHeight { id: NodeId, value: crate::taffy::Sizing },
SetPadding { id: NodeId, value: crate::taffy::RectValues },
SetMargin { id: NodeId, value: crate::taffy::RectValues },
SetGap { id: NodeId, value: crate::taffy::Gap },
SetFlexGrow { id: NodeId, value: f32 },
SetFlexShrink { id: NodeId, value: f32 },
SetFlexBasis { id: NodeId, value: crate::taffy::Sizing },
SetPosition { id: NodeId, value: crate::taffy::Position },
SetInset { id: NodeId, value: crate::taffy::RectValues },
SetText { id: NodeId, text: String },
SetAttribute { id: NodeId, key: String, value: String },
RemoveAttribute { id: NodeId, key: String },
SetDisplay { id: NodeId, value: crate::tree::VisibilityDisplay },
SetOpacity { id: NodeId, value: f32 },
SetClip { id: NodeId, value: bool },
SetTranslateX { id: NodeId, value: i32 },
SetTranslateY { id: NodeId, value: i32 },
SetZIndex { id: NodeId, value: i32 },
SetOverflow { id: NodeId, value: crate::tree::Overflow },
FocusNode { id: NodeId },
BlurNode { id: NodeId },
SetTabIndex { id: NodeId, value: i32 },
BeginFrame { frame_id: u64 },
CommitFrame { frame_id: u64 },
Invalidate { id: NodeId },
SetScreenMode { mode: ScreenMode },
Shutdown,
}
impl Command {
pub fn target(&self) -> Option<NodeId> {
match self {
Self::CreateNode { id, .. } => Some(*id),
Self::RemoveNode { id } => Some(*id),
Self::AppendChild { parent, .. } => Some(*parent),
Self::InsertBefore { reference, .. } => Some(*reference),
Self::MoveNode { node, .. } => Some(*node),
Self::ReplaceNode { old, .. } => Some(*old),
Self::DetachNode { id } => Some(*id),
Self::SetStyle { id, .. } => Some(*id),
Self::SetForeground { id, .. } => Some(*id),
Self::SetBackground { id, .. } => Some(*id),
Self::SetBold { id, .. } => Some(*id),
Self::SetItalic { id, .. } => Some(*id),
Self::SetUnderline { id, .. } => Some(*id),
Self::SetStrikethrough { id, .. } => Some(*id),
Self::SetDim { id, .. } => Some(*id),
Self::SetInverse { id, .. } => Some(*id),
Self::SetHidden { id, .. } => Some(*id),
Self::SetLayout { id, .. } => Some(*id),
Self::SetFlexDirection { id, .. } => Some(*id),
Self::SetFlexWrap { id, .. } => Some(*id),
Self::SetJustifyContent { id, .. } => Some(*id),
Self::SetAlignItems { id, .. } => Some(*id),
Self::SetAlignSelf { id, .. } => Some(*id),
Self::SetWidth { id, .. } => Some(*id),
Self::SetHeight { id, .. } => Some(*id),
Self::SetMinWidth { id, .. } => Some(*id),
Self::SetMinHeight { id, .. } => Some(*id),
Self::SetMaxWidth { id, .. } => Some(*id),
Self::SetMaxHeight { id, .. } => Some(*id),
Self::SetPadding { id, .. } => Some(*id),
Self::SetMargin { id, .. } => Some(*id),
Self::SetGap { id, .. } => Some(*id),
Self::SetFlexGrow { id, .. } => Some(*id),
Self::SetFlexShrink { id, .. } => Some(*id),
Self::SetFlexBasis { id, .. } => Some(*id),
Self::SetPosition { id, .. } => Some(*id),
Self::SetInset { id, .. } => Some(*id),
Self::SetText { id, .. } => Some(*id),
Self::SetAttribute { id, .. } => Some(*id),
Self::RemoveAttribute { id, .. } => Some(*id),
Self::SetDisplay { id, .. } => Some(*id),
Self::SetOpacity { id, .. } => Some(*id),
Self::SetClip { id, .. } => Some(*id),
Self::SetTranslateX { id, .. } => Some(*id),
Self::SetTranslateY { id, .. } => Some(*id),
Self::SetZIndex { id, .. } => Some(*id),
Self::SetOverflow { id, .. } => Some(*id),
Self::FocusNode { id } => Some(*id),
Self::BlurNode { id } => Some(*id),
Self::SetTabIndex { id, .. } => Some(*id),
Self::Invalidate { id } => Some(*id),
Self::BeginFrame { .. } => None,
Self::CommitFrame { .. } => None,
Self::SetScreenMode { .. } => None,
Self::Shutdown => None,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::CreateNode { .. } => "CreateNode",
Self::RemoveNode { .. } => "RemoveNode",
Self::AppendChild { .. } => "AppendChild",
Self::InsertBefore { .. } => "InsertBefore",
Self::MoveNode { .. } => "MoveNode",
Self::ReplaceNode { .. } => "ReplaceNode",
Self::DetachNode { .. } => "DetachNode",
Self::SetStyle { .. } => "SetStyle",
Self::SetForeground { .. } => "SetForeground",
Self::SetBackground { .. } => "SetBackground",
Self::SetBold { .. } => "SetBold",
Self::SetItalic { .. } => "SetItalic",
Self::SetUnderline { .. } => "SetUnderline",
Self::SetStrikethrough { .. } => "SetStrikethrough",
Self::SetDim { .. } => "SetDim",
Self::SetInverse { .. } => "SetInverse",
Self::SetHidden { .. } => "SetHidden",
Self::SetLayout { .. } => "SetLayout",
Self::SetFlexDirection { .. } => "SetFlexDirection",
Self::SetFlexWrap { .. } => "SetFlexWrap",
Self::SetJustifyContent { .. } => "SetJustifyContent",
Self::SetAlignItems { .. } => "SetAlignItems",
Self::SetAlignSelf { .. } => "SetAlignSelf",
Self::SetWidth { .. } => "SetWidth",
Self::SetHeight { .. } => "SetHeight",
Self::SetMinWidth { .. } => "SetMinWidth",
Self::SetMinHeight { .. } => "SetMinHeight",
Self::SetMaxWidth { .. } => "SetMaxWidth",
Self::SetMaxHeight { .. } => "SetMaxHeight",
Self::SetPadding { .. } => "SetPadding",
Self::SetMargin { .. } => "SetMargin",
Self::SetGap { .. } => "SetGap",
Self::SetFlexGrow { .. } => "SetFlexGrow",
Self::SetFlexShrink { .. } => "SetFlexShrink",
Self::SetFlexBasis { .. } => "SetFlexBasis",
Self::SetPosition { .. } => "SetPosition",
Self::SetInset { .. } => "SetInset",
Self::SetText { .. } => "SetText",
Self::SetAttribute { .. } => "SetAttribute",
Self::RemoveAttribute { .. } => "RemoveAttribute",
Self::SetDisplay { .. } => "SetDisplay",
Self::SetOpacity { .. } => "SetOpacity",
Self::SetClip { .. } => "SetClip",
Self::SetTranslateX { .. } => "SetTranslateX",
Self::SetTranslateY { .. } => "SetTranslateY",
Self::SetZIndex { .. } => "SetZIndex",
Self::SetOverflow { .. } => "SetOverflow",
Self::FocusNode { .. } => "FocusNode",
Self::BlurNode { .. } => "BlurNode",
Self::SetTabIndex { .. } => "SetTabIndex",
Self::BeginFrame { .. } => "BeginFrame",
Self::CommitFrame { .. } => "CommitFrame",
Self::Invalidate { .. } => "Invalidate",
Self::SetScreenMode { .. } => "SetScreenMode",
Self::Shutdown => "Shutdown",
}
}
pub fn is_create(&self) -> bool {
matches!(self, Self::CreateNode { .. })
}
pub fn is_remove(&self) -> bool {
matches!(self, Self::RemoveNode { .. })
}
pub fn is_tree_mutation(&self) -> bool {
matches!(
self,
Self::CreateNode { .. }
| Self::RemoveNode { .. }
| Self::AppendChild { .. }
| Self::InsertBefore { .. }
| Self::MoveNode { .. }
| Self::ReplaceNode { .. }
| Self::DetachNode { .. }
)
}
pub fn is_frame_command(&self) -> bool {
matches!(self, Self::BeginFrame { .. } | Self::CommitFrame { .. })
}
}
impl std::fmt::Display for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CreateNode { id, kind } => write!(f, "CreateNode({id:?}, {kind:?})"),
Self::RemoveNode { id } => write!(f, "RemoveNode({id:?})"),
Self::AppendChild { parent, child } => {
write!(f, "AppendChild({parent:?}, {child:?})")
}
Self::InsertBefore { reference, child } => {
write!(f, "InsertBefore({reference:?}, {child:?})")
}
Self::MoveNode { node, new_parent } => {
write!(f, "MoveNode({node:?}, {new_parent:?})")
}
Self::ReplaceNode { old, new } => write!(f, "ReplaceNode({old:?}, {new:?})"),
Self::DetachNode { id } => write!(f, "DetachNode({id:?})"),
Self::SetStyle { id, style } => write!(f, "SetStyle({id:?}, {style:?})"),
Self::SetText { id, text } => write!(f, "SetText({id:?}, \"{}\")", text),
Self::SetForeground { id, color } => write!(f, "SetForeground({id:?}, {color:?})"),
Self::SetBackground { id, color } => write!(f, "SetBackground({id:?}, {color:?})"),
Self::SetBold { id, value } => write!(f, "SetBold({id:?}, {value})"),
Self::SetItalic { id, value } => write!(f, "SetItalic({id:?}, {value})"),
Self::SetUnderline { id, value } => write!(f, "SetUnderline({id:?}, {value})"),
Self::SetStrikethrough { id, value } => {
write!(f, "SetStrikethrough({id:?}, {value})")
}
Self::SetDim { id, value } => write!(f, "SetDim({id:?}, {value})"),
Self::SetInverse { id, value } => write!(f, "SetInverse({id:?}, {value})"),
Self::SetHidden { id, value } => write!(f, "SetHidden({id:?}, {value})"),
Self::SetLayout { id, layout } => write!(f, "SetLayout({id:?}, {layout:?})"),
Self::SetFlexDirection { id, direction } => {
write!(f, "SetFlexDirection({id:?}, {direction:?})")
}
Self::SetFlexWrap { id, value } => {
write!(f, "SetFlexWrap({id:?}, {value:?})")
}
Self::SetJustifyContent { id, value } => {
write!(f, "SetJustifyContent({id:?}, {value:?})")
}
Self::SetAlignItems { id, value } => write!(f, "SetAlignItems({id:?}, {value:?})"),
Self::SetAlignSelf { id, value } => write!(f, "SetAlignSelf({id:?}, {value:?})"),
Self::SetWidth { id, value } => write!(f, "SetWidth({id:?}, {value:?})"),
Self::SetHeight { id, value } => write!(f, "SetHeight({id:?}, {value:?})"),
Self::SetMinWidth { id, value } => write!(f, "SetMinWidth({id:?}, {value:?})"),
Self::SetMinHeight { id, value } => write!(f, "SetMinHeight({id:?}, {value:?})"),
Self::SetMaxWidth { id, value } => write!(f, "SetMaxWidth({id:?}, {value:?})"),
Self::SetMaxHeight { id, value } => write!(f, "SetMaxHeight({id:?}, {value:?})"),
Self::SetPadding { id, value } => write!(f, "SetPadding({id:?}, {value:?})"),
Self::SetMargin { id, value } => write!(f, "SetMargin({id:?}, {value:?})"),
Self::SetGap { id, value } => write!(f, "SetGap({id:?}, {value:?})"),
Self::SetFlexGrow { id, value } => write!(f, "SetFlexGrow({id:?}, {value})"),
Self::SetFlexShrink { id, value } => write!(f, "SetFlexShrink({id:?}, {value})"),
Self::SetFlexBasis { id, value } => write!(f, "SetFlexBasis({id:?}, {value:?})"),
Self::SetPosition { id, value } => write!(f, "SetPosition({id:?}, {value:?})"),
Self::SetInset { id, value } => write!(f, "SetInset({id:?}, {value:?})"),
Self::SetAttribute { id, key, value } => {
write!(f, "SetAttribute({id:?}, \"{key}\", \"{value}\")")
}
Self::RemoveAttribute { id, key } => {
write!(f, "RemoveAttribute({id:?}, \"{key}\")")
}
Self::SetDisplay { id, value } => write!(f, "SetDisplay({id:?}, {value:?})"),
Self::SetOpacity { id, value } => write!(f, "SetOpacity({id:?}, {value})"),
Self::SetClip { id, value } => write!(f, "SetClip({id:?}, {value})"),
Self::SetTranslateX { id, value } => write!(f, "SetTranslateX({id:?}, {value})"),
Self::SetTranslateY { id, value } => write!(f, "SetTranslateY({id:?}, {value})"),
Self::SetZIndex { id, value } => write!(f, "SetZIndex({id:?}, {value})"),
Self::SetOverflow { id, value } => write!(f, "SetOverflow({id:?}, {value:?})"),
Self::FocusNode { id } => write!(f, "FocusNode({id:?})"),
Self::BlurNode { id } => write!(f, "BlurNode({id:?})"),
Self::SetTabIndex { id, value } => write!(f, "SetTabIndex({id:?}, {value})"),
Self::BeginFrame { frame_id } => write!(f, "BeginFrame({frame_id})"),
Self::CommitFrame { frame_id } => write!(f, "CommitFrame({frame_id})"),
Self::Invalidate { id } => write!(f, "Invalidate({id:?})"),
Self::SetScreenMode { mode } => write!(f, "SetScreenMode({:?})", mode),
Self::Shutdown => write!(f, "Shutdown"),
}
}
}
#[derive(Debug, Clone)]
pub struct CommandEntry {
pub name: String,
pub data: String,
pub undoable: bool,
pub timestamp: u64,
}
impl CommandEntry {
pub fn new(name: impl Into<String>, data: impl Into<String>, undoable: bool) -> Self {
Self { name: name.into(), data: data.into(), undoable, timestamp: 0 }
}
pub fn with_timestamp(mut self, ts: u64) -> Self {
self.timestamp = ts;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RegistryResult {
Success,
Failure(String),
NotFound,
}
#[derive(Debug)]
pub struct CommandRegistry {
history: VecDeque<CommandEntry>,
undo_stack: Vec<CommandEntry>,
redo_stack: Vec<CommandEntry>,
max_history: usize,
max_undo: usize,
}
impl Default for CommandRegistry {
fn default() -> Self {
Self::new()
}
}
impl CommandRegistry {
pub fn new() -> Self {
Self {
history: VecDeque::new(),
undo_stack: Vec::new(),
redo_stack: Vec::new(),
max_history: 1000,
max_undo: 100,
}
}
pub fn with_max_history(mut self, max: usize) -> Self {
self.max_history = max;
self
}
pub fn with_max_undo(mut self, max: usize) -> Self {
self.max_undo = max;
self
}
pub fn execute(&mut self, entry: CommandEntry) -> RegistryResult {
let undoable = entry.undoable;
self.history.push_back(entry.clone());
while self.history.len() > self.max_history {
self.history.pop_front();
}
if undoable {
self.undo_stack.push(entry);
if self.undo_stack.len() > self.max_undo {
self.undo_stack.remove(0);
}
self.redo_stack.clear();
}
RegistryResult::Success
}
pub fn undo(&mut self) -> Option<CommandEntry> {
let entry = self.undo_stack.pop()?;
self.redo_stack.push(entry.clone());
Some(entry)
}
pub fn redo(&mut self) -> Option<CommandEntry> {
let entry = self.redo_stack.pop()?;
self.undo_stack.push(entry.clone());
Some(entry)
}
pub fn can_undo(&self) -> bool {
!self.undo_stack.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn history(&self) -> impl Iterator<Item = &CommandEntry> {
self.history.iter()
}
pub fn history_len(&self) -> usize {
self.history.len()
}
pub fn undo_depth(&self) -> usize {
self.undo_stack.len()
}
pub fn redo_depth(&self) -> usize {
self.redo_stack.len()
}
pub fn clear(&mut self) {
self.history.clear();
self.undo_stack.clear();
self.redo_stack.clear();
}
pub fn find(&self, name: &str) -> Vec<&CommandEntry> {
self.history.iter().filter(|e| e.name == name).collect()
}
}
pub struct CommandBuffer {
commands: Vec<Command>,
capacity: usize,
}
impl Default for CommandBuffer {
fn default() -> Self {
Self::new()
}
}
impl CommandBuffer {
pub fn new() -> Self {
Self::with_capacity(64)
}
pub fn with_capacity(capacity: usize) -> Self {
Self { commands: Vec::with_capacity(capacity), capacity }
}
pub fn push(&mut self, cmd: Command) {
self.commands.push(cmd);
}
pub fn drain(&mut self) -> Vec<Command> {
std::mem::take(&mut self.commands)
}
pub fn peek(&self) -> &[Command] {
&self.commands
}
pub fn clear(&mut self) {
self.commands.clear();
}
pub fn len(&self) -> usize {
self.commands.len()
}
pub fn is_empty(&self) -> bool {
self.commands.is_empty()
}
pub fn estimated_size(&self) -> usize {
self.commands.len() * 128
}
pub fn reserve(&mut self, additional: usize) {
self.commands.reserve(additional);
}
pub fn capacity(&self) -> usize {
self.capacity
}
}
impl From<Vec<Command>> for CommandBuffer {
fn from(commands: Vec<Command>) -> Self {
let capacity = commands.len();
Self { commands, capacity }
}
}
impl IntoIterator for CommandBuffer {
type Item = Command;
type IntoIter = std::vec::IntoIter<Command>;
fn into_iter(self) -> Self::IntoIter {
self.commands.into_iter()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommandError {
NodeNotFound(NodeId),
CycleDetected { node: NodeId, ancestor: NodeId },
InvalidOperation(String),
InvalidState(String),
StaleReference(NodeId),
}
impl std::fmt::Display for CommandError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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}"),
Self::InvalidState(msg) => write!(f, "Invalid state: {msg}"),
Self::StaleReference(id) => write!(f, "Stale reference: {id:?}"),
}
}
}
impl std::error::Error for CommandError {}
impl From<crate::tree::TreeError> for CommandError {
fn from(err: crate::tree::TreeError) -> Self {
match err {
crate::tree::TreeError::NodeNotFound(id) => Self::NodeNotFound(id),
crate::tree::TreeError::CycleDetected { node, ancestor } => Self::CycleDetected { node, ancestor },
crate::tree::TreeError::InvalidOperation(msg) => Self::InvalidOperation(msg),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommandWarning {
NodeSkipped(NodeId),
NoEffect(String),
Redundant(String),
Deprecated(String),
}
impl std::fmt::Display for CommandWarning {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NodeSkipped(id) => write!(f, "Node skipped: {id:?}"),
Self::NoEffect(msg) => write!(f, "No effect: {msg}"),
Self::Redundant(msg) => write!(f, "Redundant: {msg}"),
Self::Deprecated(msg) => write!(f, "Deprecated: {msg}"),
}
}
}
impl std::error::Error for CommandWarning {}
#[derive(Debug, Clone, Default)]
pub struct CommandResult {
pub processed: usize,
pub failed: usize,
pub errors: Vec<CommandError>,
pub warnings: Vec<CommandWarning>,
}
impl CommandResult {
pub fn new() -> Self {
Self::default()
}
pub fn success() -> Self {
Self { processed: 1, ..Default::default() }
}
pub fn error(err: CommandError) -> Self {
Self { failed: 1, errors: vec![err], ..Default::default() }
}
pub fn push_success(&mut self) {
self.processed += 1;
}
pub fn push_error(&mut self, err: CommandError) {
self.failed += 1;
self.errors.push(err);
}
pub fn push_warning(&mut self, warn: CommandWarning) {
self.warnings.push(warn);
}
pub fn merge(&mut self, other: CommandResult) {
self.processed += other.processed;
self.failed += other.failed;
self.errors.extend(other.errors);
self.warnings.extend(other.warnings);
}
pub fn is_success(&self) -> bool {
self.failed == 0
}
pub fn has_errors(&self) -> bool {
self.failed > 0
}
pub fn total(&self) -> usize {
self.processed + self.failed
}
}
impl std::fmt::Display for CommandResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"CommandResult(processed={}, failed={}, warnings={})",
self.processed,
self.failed,
self.warnings.len()
)
}
}
pub struct CommandProcessor {
arena: NodeArena,
frame_id: u64,
}
impl Default for CommandProcessor {
fn default() -> Self {
Self::new()
}
}
impl CommandProcessor {
pub fn new() -> Self {
Self { arena: NodeArena::new(), frame_id: 0 }
}
pub fn with_arena(arena: NodeArena) -> Self {
Self { arena, frame_id: 0 }
}
pub fn arena(&self) -> &NodeArena {
&self.arena
}
pub fn arena_mut(&mut self) -> &mut NodeArena {
&mut self.arena
}
pub fn frame_id(&self) -> u64 {
self.frame_id
}
pub fn process_batch(&mut self, commands: Vec<Command>) -> CommandResult {
let mut result = CommandResult::new();
for cmd in commands {
match self.process_single(cmd) {
Ok(()) => result.push_success(),
Err(err) => result.push_error(err),
}
}
result
}
pub fn process_single(&mut self, cmd: Command) -> Result<(), CommandError> {
match cmd {
Command::CreateNode { id: _, kind } => {
let node = RenderNode::new(kind);
let _created_id = self.arena.insert(node);
Ok(())
}
Command::RemoveNode { id } => {
self.arena.remove_subtree(id);
Ok(())
}
Command::AppendChild { parent, child } => {
self.arena.append_child(parent, child)?;
Ok(())
}
Command::InsertBefore { reference, child } => {
self.arena.insert_before(reference, child)?;
Ok(())
}
Command::MoveNode { node, new_parent } => {
self.arena.move_node(node, new_parent)?;
Ok(())
}
Command::ReplaceNode { old, new } => {
self.arena.replace_node(old, new)?;
Ok(())
}
Command::DetachNode { id } => {
self.arena.detach(id);
Ok(())
}
Command::SetStyle { id, style } => {
let node = self.get_node_mut(id)?;
node.style.merge(&style);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetForeground { id, color } => {
let node = self.get_node_mut(id)?;
node.style.fg = Some(color);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetBackground { id, color } => {
let node = self.get_node_mut(id)?;
node.style.bg = Some(color);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetBold { id, value } => {
let node = self.get_node_mut(id)?;
node.style.bold = Some(value);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetItalic { id, value } => {
let node = self.get_node_mut(id)?;
node.style.italic = Some(value);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetUnderline { id, value } => {
let node = self.get_node_mut(id)?;
node.style.underline = Some(value);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetStrikethrough { id, value } => {
let node = self.get_node_mut(id)?;
node.style.strikethrough = Some(value);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetDim { id, value } => {
let node = self.get_node_mut(id)?;
node.style.dim = Some(value);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetInverse { id, value } => {
let node = self.get_node_mut(id)?;
node.style.inverse = Some(value);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetHidden { id, value } => {
let node = self.get_node_mut(id)?;
node.style.hidden = Some(value);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetLayout { id, layout } => {
let node = self.get_node_mut(id)?;
node.layout = layout;
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetFlexDirection { id, direction } => {
let node = self.get_node_mut(id)?;
node.layout.direction = direction;
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetFlexWrap { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.flex_wrap = value;
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetJustifyContent { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.justify = value;
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetAlignItems { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.align = value;
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetAlignSelf { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.align_self = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetWidth { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.width = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetHeight { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.height = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetMinWidth { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.min_width = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetMinHeight { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.min_height = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetMaxWidth { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.max_width = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetMaxHeight { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.max_height = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetPadding { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.padding = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetMargin { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.margin = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetGap { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.gap = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetFlexGrow { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.flex_grow = value;
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetFlexShrink { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.flex_shrink = value;
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetFlexBasis { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.flex_basis = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetPosition { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.position = value;
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetInset { id, value } => {
let node = self.get_node_mut(id)?;
node.layout.inset = Some(value);
node.state.mark_layout_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetText { id, text } => {
let node = self.get_node_mut(id)?;
node.set_text(text);
self.arena.mark_changed();
Ok(())
}
Command::SetAttribute { id, key, value } => {
let node = self.get_node_mut(id)?;
node.attributes.insert(key, value);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::RemoveAttribute { id, key } => {
let node = self.get_node_mut(id)?;
node.attributes.remove(&key);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetDisplay { id, value } => {
let node = self.get_node_mut(id)?;
node.visibility.display = value;
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetOpacity { id, value } => {
let node = self.get_node_mut(id)?;
node.visibility.opacity = value;
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetClip { id, value } => {
let node = self.get_node_mut(id)?;
node.visibility.clip = value;
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetTranslateX { id, value } => {
let node = self.get_node_mut(id)?;
node.transform.translate_x = value;
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetTranslateY { id, value } => {
let node = self.get_node_mut(id)?;
node.transform.translate_y = value;
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetZIndex { id, value } => {
let node = self.get_node_mut(id)?;
node.transform.z_index = value;
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetOverflow { id, value } => {
let node = self.get_node_mut(id)?;
node.overflow = value;
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::FocusNode { id } => {
let node = self.get_node_mut(id)?;
node.focus();
self.arena.mark_changed();
Ok(())
}
Command::BlurNode { id } => {
let node = self.get_node_mut(id)?;
node.blur();
self.arena.mark_changed();
Ok(())
}
Command::SetTabIndex { id, value } => {
let node = self.get_node_mut(id)?;
node.focus.tab_index = Some(value);
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::BeginFrame { frame_id } => {
self.frame_id = frame_id;
Ok(())
}
Command::CommitFrame { frame_id: _ } => Ok(()),
Command::Invalidate { id } => {
let node = self.get_node_mut(id)?;
node.state.mark_dirty();
self.arena.mark_changed();
Ok(())
}
Command::SetScreenMode { mode: _ } => {
self.arena.mark_changed();
Ok(())
}
Command::Shutdown => {
self.arena.clear();
Ok(())
}
}
}
fn get_node_mut(&mut self, id: NodeId) -> Result<&mut RenderNode, CommandError> {
self.arena.get_mut(id).ok_or(CommandError::NodeNotFound(id))
}
pub fn get_node(&self, id: NodeId) -> Option<&RenderNode> {
self.arena.get(id)
}
pub fn validate(&self) -> Result<(), CommandError> {
self.arena.validate().map_err(CommandError::from)
}
pub fn print_tree(&self) -> String {
self.arena.print_tree()
}
pub fn node_count(&self) -> usize {
self.arena.len()
}
}
impl std::fmt::Debug for CommandProcessor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CommandProcessor")
.field("node_count", &self.node_count())
.field("frame_id", &self.frame_id)
.finish()
}
}