respo/
node.rs

1//! RespoNode abstraction
2
3pub(crate) mod component;
4pub mod css;
5pub(crate) mod dom_change;
6pub(crate) mod element;
7mod listener;
8
9use std::boxed::Box;
10use std::fmt::Display;
11use std::rc::Rc;
12use std::{collections::HashMap, fmt::Debug};
13
14use cirru_parser::Cirru;
15pub use listener::RespoEvent;
16pub(crate) use listener::{RespoEventMark, RespoListenerFn};
17
18pub use component::RespoComponent;
19pub use element::RespoElement;
20
21use crate::states_tree::{DynEq, RespoStateBranch, RespoUpdateState};
22
23use css::respo_style;
24
25pub(crate) use dom_change::RespoCoord;
26pub(crate) use dom_change::{ChildDomOp, DomChange};
27
28pub use component::effect::{RespoEffect, RespoEffectType};
29pub use css::ConvertRespoCssSize;
30
31/// an `Element` or a `Component`
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum RespoNode<T>
34where
35  T: Debug + Clone,
36{
37  Component(RespoComponent<T>),
38  /// corresponding to DOM elements
39  Element(RespoElement<T>),
40  Referenced(Rc<RespoNode<T>>),
41}
42
43impl<T> From<RespoNode<T>> for Cirru
44where
45  T: Debug + Clone,
46{
47  fn from(value: RespoNode<T>) -> Self {
48    match value {
49      RespoNode::Component(RespoComponent { name, tree, .. }) => {
50        Cirru::List(vec![Cirru::Leaf("::Component".into()), Cirru::from(name.as_ref()), (*tree).into()])
51      }
52      RespoNode::Element(RespoElement { name, children, .. }) => {
53        let mut xs = vec![Cirru::from(name.as_ref())];
54        for (k, child) in children {
55          xs.push(Cirru::List(vec![Cirru::Leaf(k.to_string().into()), child.to_owned().into()]));
56        }
57        Cirru::List(xs)
58      }
59      RespoNode::Referenced(cell) => (*cell).to_owned().into(),
60    }
61  }
62}
63
64impl<T> Display for RespoNode<T>
65where
66  T: Debug + Clone,
67{
68  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69    write!(f, "{:?}", self)
70  }
71}
72
73/// a key for referencing a child node, use a value that can be converted to string
74#[derive(PartialEq, Eq, Debug, Clone)]
75pub struct RespoIndexKey(String);
76
77impl From<usize> for RespoIndexKey {
78  fn from(data: usize) -> Self {
79    Self(data.to_string())
80  }
81}
82
83impl From<&usize> for RespoIndexKey {
84  fn from(data: &usize) -> Self {
85    Self(data.to_string())
86  }
87}
88
89impl From<String> for RespoIndexKey {
90  fn from(s: String) -> Self {
91    Self(s)
92  }
93}
94
95impl From<&String> for RespoIndexKey {
96  fn from(s: &String) -> Self {
97    Self(s.to_owned())
98  }
99}
100
101impl From<&str> for RespoIndexKey {
102  fn from(s: &str) -> Self {
103    Self(s.to_owned())
104  }
105}
106
107impl Display for RespoIndexKey {
108  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109    write!(f, "{}", self.0)
110  }
111}
112
113impl From<RespoIndexKey> for Cirru {
114  fn from(k: RespoIndexKey) -> Cirru {
115    Cirru::from(k.to_string())
116  }
117}
118
119impl<T> RespoNode<T>
120where
121  T: Debug + Clone,
122{
123  /// create an element node
124  pub fn new_tag(name: &str) -> Self {
125    Self::Element(RespoElement {
126      name: name.into(),
127      attributes: HashMap::new(),
128      event: HashMap::new(),
129      style: respo_style(),
130      children: Vec::new(),
131    })
132  }
133  /// create a new component
134  pub fn new_component(name: &str, tree: RespoNode<T>) -> Self {
135    Self::Component(RespoComponent {
136      name: name.into(),
137      effects: Vec::new(),
138      tree: Box::new(tree),
139    })
140  }
141  /// wrap with a `Rc<T>` to enable memory reuse and skipping in diff
142  pub fn rc(&self) -> Self {
143    Self::Referenced(Rc::new(self.to_owned()))
144  }
145}
146
147pub(crate) type StrDict = HashMap<Rc<str>, String>;
148
149pub(crate) fn str_dict_to_cirrus_dict(dict: &StrDict) -> Cirru {
150  let mut xs = vec![];
151  for (k, v) in dict {
152    xs.push(vec![Cirru::from(k.as_ref()), Cirru::from(v)].into());
153  }
154  Cirru::List(xs)
155}
156
157/// dispatch function passed from root of renderer,
158/// call it like `dispatch.run(op)`
159#[derive(Clone)]
160pub struct DispatchFn<T>(Rc<dyn Fn(T) -> Result<(), String>>)
161where
162  T: Debug + Clone;
163
164impl<T> Debug for DispatchFn<T>
165where
166  T: Debug + Clone,
167{
168  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169    f.write_str("[DispatchFn]")
170  }
171}
172
173/// guide for actions to be dispatched
174/// expecially for how you update states
175pub trait RespoAction {
176  type Intent: Debug + Clone + PartialEq + Eq;
177  /// to provide syntax sugar to dispatch.run_state
178  fn build_states_action(cursor: &[Rc<str>], a: Option<RespoStateBranch>) -> Self
179  where
180    Self: Sized,
181  {
182    // val is a backup value from DynEq to Json Value
183    let val = match &a {
184      None => None,
185      Some(v) => v.0.as_ref().backup(),
186    };
187    Self::states_action(RespoUpdateState {
188      cursor: cursor.to_vec(),
189      data: a,
190      backup: val,
191    })
192  }
193
194  /// builder for intent actions
195  fn build_intent_action(op: Self::Intent) -> Self
196  where
197    Self: Sized,
198  {
199    todo!("build_intent_action need to be implemented when intent({:?}) is used ", op)
200  }
201
202  /// a builder for states change
203  fn states_action(a: RespoUpdateState) -> Self;
204
205  /// handle intent seperately since it might contain effects
206  fn detect_intent(&self) -> Option<Self::Intent> {
207    None
208  }
209}
210
211impl<T> DispatchFn<T>
212where
213  T: Debug + Clone + RespoAction,
214{
215  /// dispatch an action
216  pub fn run(&self, op: T) -> Result<(), String> {
217    (self.0)(op)
218  }
219  /// dispatch to update local state
220  pub fn run_state<U>(&self, cursor: &[Rc<str>], data: U) -> Result<(), String>
221  where
222    U: DynEq + ToOwned + Clone + PartialEq + Eq + 'static,
223  {
224    let a = Rc::new(data);
225    (self.0)(T::build_states_action(cursor, Some(RespoStateBranch::new(a))))
226  }
227  /// alias for dispatching intent
228  pub fn run_intent(&self, op: T::Intent) -> Result<(), String> {
229    (self.0)(T::build_intent_action(op))
230  }
231  /// reset state to empty
232  pub fn run_empty_state(&self, cursor: &[Rc<str>]) -> Result<(), String> {
233    (self.0)(T::build_states_action(cursor, None))
234  }
235  pub fn new<U>(f: U) -> Self
236  where
237    U: Fn(T) -> Result<(), String> + 'static,
238  {
239    Self(Rc::new(f))
240  }
241}
242
243/// (internal) function to handle event marks at first phase of event handling
244#[derive(Clone)]
245pub(crate) struct RespoEventMarkFn(Rc<dyn Fn(RespoEventMark) -> Result<(), String>>);
246
247impl Debug for RespoEventMarkFn {
248  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249    f.write_str("[EventMarkFn ...]")
250  }
251}
252
253impl RespoEventMarkFn {
254  pub fn run(&self, e: RespoEventMark) -> Result<(), String> {
255    (self.0)(e)
256  }
257  pub fn new<U>(f: U) -> Self
258  where
259    U: Fn(RespoEventMark) -> Result<(), String> + 'static,
260  {
261    Self(Rc::new(f))
262  }
263}
264
265impl From<Rc<dyn Fn(RespoEventMark) -> Result<(), String>>> for RespoEventMarkFn {
266  fn from(f: Rc<dyn Fn(RespoEventMark) -> Result<(), String>>) -> Self {
267    Self(f)
268  }
269}