1use crate::GA3;
36use std::collections::HashMap;
37use std::sync::Arc;
38
39pub trait Component: Send + Sync {
45 fn render(&self, state: &GA3) -> Element;
50
51 fn initial_state(&self) -> GA3 {
55 GA3::zero()
56 }
57
58 fn type_name(&self) -> &'static str {
60 std::any::type_name::<Self>()
61 }
62}
63
64#[derive(Debug, Clone)]
69pub struct Element {
70 pub kind: ElementKind,
72 pub props: Props,
74 pub children: Vec<Element>,
76 pub key: Option<String>,
78}
79
80impl Element {
81 pub fn new(kind: ElementKind) -> Self {
83 Self {
84 kind,
85 props: Props::new(),
86 children: Vec::new(),
87 key: None,
88 }
89 }
90
91 pub fn text(content: impl Into<String>) -> Self {
93 Self::new(ElementKind::Text(content.into()))
94 }
95
96 pub fn tag(name: impl Into<String>) -> Self {
98 Self::new(ElementKind::Tag(name.into()))
99 }
100
101 pub fn fragment(children: Vec<Element>) -> Self {
103 Self {
104 kind: ElementKind::Fragment,
105 props: Props::new(),
106 children,
107 key: None,
108 }
109 }
110
111 pub fn empty() -> Self {
113 Self::new(ElementKind::Empty)
114 }
115
116 pub fn child(mut self, child: Element) -> Self {
118 self.children.push(child);
119 self
120 }
121
122 pub fn children(mut self, children: impl IntoIterator<Item = Element>) -> Self {
124 self.children.extend(children);
125 self
126 }
127
128 pub fn prop(mut self, key: impl Into<String>, value: PropValue) -> Self {
130 self.props.set(key, value);
131 self
132 }
133
134 pub fn attr(self, key: impl Into<String>, value: impl Into<String>) -> Self {
136 self.prop(key, PropValue::String(value.into()))
137 }
138
139 pub fn num(self, key: impl Into<String>, value: f64) -> Self {
141 self.prop(key, PropValue::Number(value))
142 }
143
144 pub fn bool(self, key: impl Into<String>, value: bool) -> Self {
146 self.prop(key, PropValue::Bool(value))
147 }
148
149 pub fn with_key(mut self, key: impl Into<String>) -> Self {
151 self.key = Some(key.into());
152 self
153 }
154
155 pub fn is_empty(&self) -> bool {
157 matches!(self.kind, ElementKind::Empty)
158 }
159
160 pub fn node_count(&self) -> usize {
162 1 + self.children.iter().map(|c| c.node_count()).sum::<usize>()
163 }
164}
165
166#[derive(Debug, Clone, PartialEq)]
168pub enum ElementKind {
169 Tag(String),
171 Text(String),
173 Fragment,
175 ComponentRef(ComponentRef),
177 Empty,
179}
180
181#[derive(Clone)]
183pub struct ComponentRef {
184 pub type_name: String,
186 pub component: Arc<dyn Component>,
188 pub props: Props,
190}
191
192impl std::fmt::Debug for ComponentRef {
193 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 f.debug_struct("ComponentRef")
195 .field("type_name", &self.type_name)
196 .field("props", &self.props)
197 .finish_non_exhaustive()
198 }
199}
200
201impl PartialEq for ComponentRef {
202 fn eq(&self, other: &Self) -> bool {
203 self.type_name == other.type_name
204 }
205}
206
207#[derive(Debug, Clone, Default)]
209pub struct Props {
210 values: HashMap<String, PropValue>,
211}
212
213impl Props {
214 pub fn new() -> Self {
216 Self::default()
217 }
218
219 pub fn set(&mut self, key: impl Into<String>, value: PropValue) {
221 self.values.insert(key.into(), value);
222 }
223
224 pub fn get(&self, key: &str) -> Option<&PropValue> {
226 self.values.get(key)
227 }
228
229 pub fn has(&self, key: &str) -> bool {
231 self.values.contains_key(key)
232 }
233
234 pub fn keys(&self) -> impl Iterator<Item = &String> {
236 self.values.keys()
237 }
238
239 pub fn iter(&self) -> impl Iterator<Item = (&String, &PropValue)> {
241 self.values.iter()
242 }
243
244 pub fn merge(&mut self, other: Props) {
246 for (k, v) in other.values {
247 self.values.insert(k, v);
248 }
249 }
250}
251
252#[derive(Debug, Clone, PartialEq)]
254pub enum PropValue {
255 String(String),
257 Number(f64),
259 Bool(bool),
261 Array(Vec<PropValue>),
263 Object(HashMap<String, PropValue>),
265 Null,
267}
268
269impl PropValue {
270 pub fn as_str(&self) -> Option<&str> {
272 match self {
273 PropValue::String(s) => Some(s),
274 _ => None,
275 }
276 }
277
278 pub fn as_f64(&self) -> Option<f64> {
280 match self {
281 PropValue::Number(n) => Some(*n),
282 _ => None,
283 }
284 }
285
286 pub fn as_bool(&self) -> Option<bool> {
288 match self {
289 PropValue::Bool(b) => Some(*b),
290 _ => None,
291 }
292 }
293}
294
295impl From<&str> for PropValue {
296 fn from(s: &str) -> Self {
297 PropValue::String(s.to_string())
298 }
299}
300
301impl From<String> for PropValue {
302 fn from(s: String) -> Self {
303 PropValue::String(s)
304 }
305}
306
307impl From<f64> for PropValue {
308 fn from(n: f64) -> Self {
309 PropValue::Number(n)
310 }
311}
312
313impl From<i32> for PropValue {
314 fn from(n: i32) -> Self {
315 PropValue::Number(n as f64)
316 }
317}
318
319impl From<bool> for PropValue {
320 fn from(b: bool) -> Self {
321 PropValue::Bool(b)
322 }
323}
324
325pub struct ComposedComponent<A, B>
329where
330 A: Component,
331 B: Component,
332{
333 pub a: A,
335 pub b: B,
337 pub split: StateSplit,
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
343pub enum StateSplit {
344 Shared,
346 ByGrade,
348 ByCoefficient,
350}
351
352impl<A, B> ComposedComponent<A, B>
353where
354 A: Component,
355 B: Component,
356{
357 pub fn new(a: A, b: B) -> Self {
359 Self {
360 a,
361 b,
362 split: StateSplit::Shared,
363 }
364 }
365
366 pub fn with_split(a: A, b: B, split: StateSplit) -> Self {
368 Self { a, b, split }
369 }
370}
371
372impl<A, B> Component for ComposedComponent<A, B>
373where
374 A: Component,
375 B: Component,
376{
377 fn render(&self, state: &GA3) -> Element {
378 let (state_a, state_b) = match self.split {
379 StateSplit::Shared => (state.clone(), state.clone()),
380 StateSplit::ByGrade => {
381 let coeffs = state.as_slice();
384 let a_coeffs = vec![
385 coeffs[0], coeffs[1], coeffs[2], coeffs[3], 0.0, 0.0, 0.0, 0.0,
386 ];
387 let b_coeffs = vec![
388 0.0, 0.0, 0.0, 0.0, coeffs[4], coeffs[5], coeffs[6], coeffs[7],
389 ];
390 (GA3::from_slice(&a_coeffs), GA3::from_slice(&b_coeffs))
391 }
392 StateSplit::ByCoefficient => {
393 let coeffs = state.as_slice();
394 let a_coeffs = vec![
395 coeffs[0], coeffs[1], coeffs[2], coeffs[3], 0.0, 0.0, 0.0, 0.0,
396 ];
397 let b_coeffs = vec![
398 coeffs[4], coeffs[5], coeffs[6], coeffs[7], 0.0, 0.0, 0.0, 0.0,
399 ];
400 (GA3::from_slice(&a_coeffs), GA3::from_slice(&b_coeffs))
401 }
402 };
403
404 let elem_a = self.a.render(&state_a);
405 let elem_b = self.b.render(&state_b);
406
407 Element::fragment(vec![elem_a, elem_b])
408 }
409
410 fn initial_state(&self) -> GA3 {
411 let a_init = self.a.initial_state();
413 let b_init = self.b.initial_state();
414
415 match self.split {
416 StateSplit::Shared => a_init, StateSplit::ByGrade | StateSplit::ByCoefficient => {
418 let a_coeffs = a_init.as_slice();
420 let b_coeffs = b_init.as_slice();
421 GA3::from_slice(&[
422 a_coeffs[0],
423 a_coeffs[1],
424 a_coeffs[2],
425 a_coeffs[3],
426 b_coeffs[0],
427 b_coeffs[1],
428 b_coeffs[2],
429 b_coeffs[3],
430 ])
431 }
432 }
433 }
434}
435
436pub fn compose<A, B>(a: A, b: B) -> ComposedComponent<A, B>
438where
439 A: Component,
440 B: Component,
441{
442 ComposedComponent::new(a, b)
443}
444
445pub struct FnComponent<F>
447where
448 F: Fn(&GA3) -> Element + Send + Sync,
449{
450 render_fn: F,
451 initial: GA3,
452}
453
454impl<F> FnComponent<F>
455where
456 F: Fn(&GA3) -> Element + Send + Sync,
457{
458 pub fn new(render_fn: F) -> Self {
460 Self {
461 render_fn,
462 initial: GA3::zero(),
463 }
464 }
465
466 pub fn with_initial(render_fn: F, initial: GA3) -> Self {
468 Self { render_fn, initial }
469 }
470}
471
472impl<F> Component for FnComponent<F>
473where
474 F: Fn(&GA3) -> Element + Send + Sync,
475{
476 fn render(&self, state: &GA3) -> Element {
477 (self.render_fn)(state)
478 }
479
480 fn initial_state(&self) -> GA3 {
481 self.initial.clone()
482 }
483}
484
485pub fn component<F>(render_fn: F) -> FnComponent<F>
487where
488 F: Fn(&GA3) -> Element + Send + Sync,
489{
490 FnComponent::new(render_fn)
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496
497 #[test]
498 fn test_element_creation() {
499 let elem = Element::tag("div")
500 .attr("class", "container")
501 .child(Element::text("Hello"));
502
503 assert!(matches!(elem.kind, ElementKind::Tag(ref t) if t == "div"));
504 assert_eq!(elem.children.len(), 1);
505 assert!(elem.props.has("class"));
506 }
507
508 #[test]
509 fn test_element_text() {
510 let elem = Element::text("Hello, World!");
511
512 if let ElementKind::Text(content) = &elem.kind {
513 assert_eq!(content, "Hello, World!");
514 } else {
515 panic!("Expected text element");
516 }
517 }
518
519 #[test]
520 fn test_element_fragment() {
521 let frag = Element::fragment(vec![Element::text("A"), Element::text("B")]);
522
523 assert!(matches!(frag.kind, ElementKind::Fragment));
524 assert_eq!(frag.children.len(), 2);
525 }
526
527 #[test]
528 fn test_element_node_count() {
529 let elem = Element::tag("div")
530 .child(Element::tag("span").child(Element::text("Hello")))
531 .child(Element::text("World"));
532
533 assert_eq!(elem.node_count(), 4);
535 }
536
537 #[test]
538 fn test_props() {
539 let mut props = Props::new();
540 props.set("name", PropValue::String("test".into()));
541 props.set("count", PropValue::Number(42.0));
542 props.set("enabled", PropValue::Bool(true));
543
544 assert_eq!(props.get("name").unwrap().as_str(), Some("test"));
545 assert_eq!(props.get("count").unwrap().as_f64(), Some(42.0));
546 assert_eq!(props.get("enabled").unwrap().as_bool(), Some(true));
547 }
548
549 #[test]
550 fn test_fn_component() {
551 let counter = component(|state: &GA3| {
552 let count = state.get(0) as i32;
553 Element::text(format!("Count: {}", count))
554 });
555
556 let elem = counter.render(&GA3::scalar(5.0));
557
558 if let ElementKind::Text(content) = &elem.kind {
559 assert_eq!(content, "Count: 5");
560 } else {
561 panic!("Expected text element");
562 }
563 }
564
565 #[test]
566 fn test_composed_component() {
567 let a = component(|state: &GA3| Element::text(format!("A: {}", state.get(0) as i32)));
568
569 let b = component(|state: &GA3| Element::text(format!("B: {}", state.get(0) as i32)));
570
571 let composed = compose(a, b);
572 let elem = composed.render(&GA3::scalar(10.0));
573
574 assert!(matches!(elem.kind, ElementKind::Fragment));
575 assert_eq!(elem.children.len(), 2);
576 }
577
578 #[test]
579 fn test_prop_value_conversions() {
580 let s: PropValue = "hello".into();
581 assert!(matches!(s, PropValue::String(_)));
582
583 let n: PropValue = 42.0_f64.into();
584 assert!(matches!(n, PropValue::Number(_)));
585
586 let b: PropValue = true.into();
587 assert!(matches!(b, PropValue::Bool(_)));
588
589 let i: PropValue = 123_i32.into();
590 assert!(matches!(i, PropValue::Number(_)));
591 }
592
593 #[test]
594 fn test_element_empty() {
595 let elem = Element::empty();
596 assert!(elem.is_empty());
597 }
598
599 #[test]
600 fn test_element_with_key() {
601 let elem = Element::tag("li").with_key("item-1");
602 assert_eq!(elem.key, Some("item-1".to_string()));
603 }
604
605 #[test]
606 fn test_props_merge() {
607 let mut props1 = Props::new();
608 props1.set("a", PropValue::Number(1.0));
609 props1.set("b", PropValue::Number(2.0));
610
611 let mut props2 = Props::new();
612 props2.set("b", PropValue::Number(3.0));
613 props2.set("c", PropValue::Number(4.0));
614
615 props1.merge(props2);
616
617 assert_eq!(props1.get("a").unwrap().as_f64(), Some(1.0));
618 assert_eq!(props1.get("b").unwrap().as_f64(), Some(3.0)); assert_eq!(props1.get("c").unwrap().as_f64(), Some(4.0));
620 }
621}