respo/
node.rs

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