use std::any::TypeId;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use super::Style;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct NodeKey {
pub user_key: Option<u64>,
pub type_id: TypeId,
pub index: usize,
}
impl NodeKey {
pub fn with_key(key: &(impl Hash + ?Sized), type_id: TypeId, index: usize) -> Self {
let mut hasher = DefaultHasher::new();
key.hash(&mut hasher);
Self {
user_key: Some(hasher.finish()),
type_id,
index,
}
}
pub fn new(type_id: TypeId, index: usize) -> Self {
Self {
user_key: None,
type_id,
index,
}
}
pub fn root() -> Self {
Self {
user_key: None,
type_id: TypeId::of::<()>(),
index: 0,
}
}
pub fn matches(&self, other: &NodeKey) -> bool {
match (self.user_key, other.user_key) {
(Some(a), Some(b)) => a == b && self.type_id == other.type_id,
(None, None) => self.type_id == other.type_id && self.index == other.index,
_ => false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum VNodeType {
Root,
Box,
Text(String),
Component(TypeId),
}
impl VNodeType {
pub fn type_id(&self) -> TypeId {
match self {
VNodeType::Root => TypeId::of::<RootMarker>(),
VNodeType::Box => TypeId::of::<BoxMarker>(),
VNodeType::Text(_) => TypeId::of::<TextMarker>(),
VNodeType::Component(id) => *id,
}
}
}
struct RootMarker;
struct BoxMarker;
struct TextMarker;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Props {
pub style: Style,
pub key: Option<String>,
pub scroll_offset_x: Option<u16>,
pub scroll_offset_y: Option<u16>,
}
impl Props {
pub fn new() -> Self {
Self::default()
}
pub fn with_style(style: Style) -> Self {
Self {
style,
..Default::default()
}
}
pub fn key(mut self, key: impl Into<String>) -> Self {
self.key = Some(key.into());
self
}
pub fn scroll(mut self, x: Option<u16>, y: Option<u16>) -> Self {
self.scroll_offset_x = x;
self.scroll_offset_y = y;
self
}
pub fn to_taffy(&self) -> taffy::Style {
self.style.to_taffy()
}
pub fn has_changed(&self, other: &Props) -> bool {
self != other
}
}
#[derive(Debug, Clone)]
pub struct VNode {
pub key: NodeKey,
pub node_type: VNodeType,
pub props: Props,
pub children: Vec<VNode>,
}
impl VNode {
pub fn new(node_type: VNodeType, props: Props) -> Self {
let type_id = node_type.type_id();
Self {
key: NodeKey::new(type_id, 0),
node_type,
props,
children: Vec::new(),
}
}
pub fn root() -> Self {
Self {
key: NodeKey::root(),
node_type: VNodeType::Root,
props: Props::new(),
children: Vec::new(),
}
}
pub fn box_node() -> Self {
Self::new(VNodeType::Box, Props::new())
}
pub fn text(content: impl Into<String>) -> Self {
Self::new(VNodeType::Text(content.into()), Props::new())
}
pub fn component<C: 'static>() -> Self {
Self::new(VNodeType::Component(TypeId::of::<C>()), Props::new())
}
pub fn with_key(mut self, key: &(impl Hash + ?Sized)) -> Self {
let type_id = self.node_type.type_id();
self.key = NodeKey::with_key(key, type_id, self.key.index);
self
}
pub fn with_index(mut self, index: usize) -> Self {
self.key.index = index;
self
}
pub fn with_props(mut self, props: Props) -> Self {
self.props = props;
self
}
pub fn with_style(mut self, style: Style) -> Self {
self.props.style = style;
self
}
pub fn child(mut self, child: VNode) -> Self {
let index = self.children.len();
self.children.push(child.with_index(index));
self
}
pub fn children(mut self, children: impl IntoIterator<Item = VNode>) -> Self {
for child in children {
self = self.child(child);
}
self
}
pub fn get_text(&self) -> Option<&str> {
match &self.node_type {
VNodeType::Text(s) => Some(s),
_ => None,
}
}
pub fn is_text(&self) -> bool {
matches!(self.node_type, VNodeType::Text(_))
}
pub fn is_box(&self) -> bool {
matches!(self.node_type, VNodeType::Box)
}
pub fn is_component(&self) -> bool {
matches!(self.node_type, VNodeType::Component(_))
}
pub fn is_root(&self) -> bool {
matches!(self.node_type, VNodeType::Root)
}
pub fn node_count(&self) -> usize {
1 + self.children.iter().map(|c| c.node_count()).sum::<usize>()
}
pub fn find_by_key(&self, key: &NodeKey) -> Option<&VNode> {
if self.key == *key {
return Some(self);
}
for child in &self.children {
if let Some(found) = child.find_by_key(key) {
return Some(found);
}
}
None
}
pub fn find_by_key_mut(&mut self, key: &NodeKey) -> Option<&mut VNode> {
if self.key == *key {
return Some(self);
}
for child in &mut self.children {
if let Some(found) = child.find_by_key_mut(key) {
return Some(found);
}
}
None
}
}
impl PartialEq for VNode {
fn eq(&self, other: &Self) -> bool {
self.key == other.key
&& self.node_type == other.node_type
&& self.props == other.props
&& self.children == other.children
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_key_creation() {
let key1 = NodeKey::new(TypeId::of::<i32>(), 0);
let key2 = NodeKey::new(TypeId::of::<i32>(), 0);
assert_eq!(key1, key2);
let key3 = NodeKey::new(TypeId::of::<i32>(), 1);
assert_ne!(key1, key3);
}
#[test]
fn test_node_key_with_user_key() {
let key1 = NodeKey::with_key("item-1", TypeId::of::<i32>(), 0);
let key2 = NodeKey::with_key("item-1", TypeId::of::<i32>(), 5);
assert!(key1.matches(&key2));
let key3 = NodeKey::with_key("item-2", TypeId::of::<i32>(), 0);
assert!(!key1.matches(&key3));
}
#[test]
fn test_node_key_matches() {
let key1 = NodeKey::new(TypeId::of::<i32>(), 0);
let key2 = NodeKey::new(TypeId::of::<i32>(), 0);
assert!(key1.matches(&key2));
let key3 = NodeKey::new(TypeId::of::<String>(), 0);
assert!(!key1.matches(&key3));
}
#[test]
fn test_vnode_creation() {
let node = VNode::box_node();
assert!(node.is_box());
assert!(node.children.is_empty());
}
#[test]
fn test_vnode_text() {
let node = VNode::text("Hello");
assert!(node.is_text());
assert_eq!(node.get_text(), Some("Hello"));
}
#[test]
fn test_vnode_children() {
let node = VNode::box_node()
.child(VNode::text("A"))
.child(VNode::text("B"))
.child(VNode::text("C"));
assert_eq!(node.children.len(), 3);
assert_eq!(node.children[0].key.index, 0);
assert_eq!(node.children[1].key.index, 1);
assert_eq!(node.children[2].key.index, 2);
}
#[test]
fn test_vnode_with_key() {
let node = VNode::box_node().with_key("my-key");
assert!(node.key.user_key.is_some());
}
#[test]
fn test_vnode_node_count() {
let node = VNode::box_node().child(VNode::text("A")).child(
VNode::box_node()
.child(VNode::text("B"))
.child(VNode::text("C")),
);
assert_eq!(node.node_count(), 5);
}
#[test]
fn test_vnode_find_by_key() {
let _target_key = NodeKey::with_key(&"target", TypeId::of::<TextMarker>(), 0);
let node = VNode::box_node()
.child(VNode::box_node().child(VNode::text("Found").with_key("target")));
assert_eq!(node.node_count(), 3);
}
#[test]
fn test_props_has_changed() {
let props1 = Props::new();
let props2 = Props::new();
assert!(!props1.has_changed(&props2));
let props3 = Props::new().key("different");
assert!(props1.has_changed(&props3));
}
#[test]
fn test_vnode_equality() {
let node1 = VNode::text("Hello");
let node2 = VNode::text("Hello");
assert_eq!(node1.node_type, node2.node_type);
}
#[test]
fn test_vnode_component() {
struct MyComponent;
let node = VNode::component::<MyComponent>();
assert!(node.is_component());
if let VNodeType::Component(type_id) = node.node_type {
assert_eq!(type_id, TypeId::of::<MyComponent>());
} else {
panic!("Expected Component type");
}
}
}