1use crate::reactive::{effect, on_cleanup, untrack, Owner};
14use ferroui_base::data::BindingBase;
15use ferroui_base::reactive::IDisposable;
16use ferroui_base::{BoxedValue, FerroProperty, Ref};
17use ferroui_base::data::BindingPriority;
18use ferroui_controls::presenters::ContentPresenter;
19use ferroui_controls::{Control, ContentControl, Decorator, ItemsControl, Panel, TextBlock};
20use std::any::Any;
21use std::cell::RefCell;
22use std::collections::HashMap;
23use std::fmt;
24use std::hash::Hash;
25use std::rc::Rc;
26
27#[derive(Clone, PartialEq, Eq, Hash, Debug)]
30pub enum Key {
31 Int(i64),
33 Str(Rc<str>),
35}
36
37impl From<i64> for Key {
38 fn from(value: i64) -> Self {
39 Key::Int(value)
40 }
41}
42
43impl From<i32> for Key {
44 fn from(value: i32) -> Self {
45 Key::Int(i64::from(value))
46 }
47}
48
49impl From<u32> for Key {
50 fn from(value: u32) -> Self {
51 Key::Int(i64::from(value))
52 }
53}
54
55impl From<usize> for Key {
56 fn from(value: usize) -> Self {
57 Key::Int(value as i64)
58 }
59}
60
61impl From<&str> for Key {
62 fn from(value: &str) -> Self {
63 Key::Str(Rc::from(value))
64 }
65}
66
67impl From<String> for Key {
68 fn from(value: String) -> Self {
69 Key::Str(Rc::from(value))
70 }
71}
72
73#[derive(Clone)]
75pub enum PropValue {
76 Value(BoxedValue),
78 Dynamic(Rc<dyn Fn() -> BoxedValue>),
81 TwoWay {
84 get: Rc<dyn Fn() -> BoxedValue>,
86 set: Rc<dyn Fn(&dyn Any)>,
88 },
89 Element(Rc<RefCell<Option<Element>>>),
92 Binding(Rc<dyn BindingBase>),
95}
96
97impl PartialEq for PropValue {
98 fn eq(&self, other: &Self) -> bool {
101 match (self, other) {
102 (PropValue::Value(a), PropValue::Value(b)) => a == b,
103 _ => false,
104 }
105 }
106}
107
108impl fmt::Debug for PropValue {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 match self {
111 PropValue::Value(_) => f.write_str("Value"),
112 PropValue::Dynamic(_) => f.write_str("Dynamic"),
113 PropValue::TwoWay { .. } => f.write_str("TwoWay"),
114 PropValue::Element(_) => f.write_str("Element"),
115 PropValue::Binding(_) => f.write_str("Binding"),
116 }
117 }
118}
119
120#[derive(Clone, Debug, PartialEq)]
122pub struct PropEntry {
123 pub property: &'static FerroProperty,
125 pub value: PropValue,
127}
128
129pub type Attach = Rc<dyn Fn(&Ref<Control>)>;
132
133#[derive(Clone, Copy)]
135pub struct ElementClass {
136 pub name: &'static str,
138 pub type_id: fn() -> std::any::TypeId,
140 pub create: fn() -> Ref<Control>,
142}
143
144impl PartialEq for ElementClass {
145 fn eq(&self, other: &Self) -> bool {
146 (self.type_id)() == (other.type_id)()
147 }
148}
149
150impl fmt::Debug for ElementClass {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 f.write_str(self.name)
153 }
154}
155
156#[derive(Clone)]
158pub struct ControlElement {
159 pub class: ElementClass,
161 pub key: Option<Key>,
163 pub name: Option<String>,
165 pub classes: Vec<String>,
167 pub props: Vec<PropEntry>,
169 pub handlers: Vec<Attach>,
171 pub children: Vec<Element>,
174 pub on_mount: Vec<Attach>,
176}
177
178impl fmt::Debug for ControlElement {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 f.debug_struct("ControlElement")
181 .field("class", &self.class)
182 .field("key", &self.key)
183 .field("name", &self.name)
184 .field("classes", &self.classes)
185 .field("props", &self.props)
186 .field("children", &self.children)
187 .finish()
188 }
189}
190
191impl ControlElement {
192 pub fn new(class: ElementClass) -> Self {
194 Self {
195 class,
196 key: None,
197 name: None,
198 classes: Vec::new(),
199 props: Vec::new(),
200 handlers: Vec::new(),
201 children: Vec::new(),
202 on_mount: Vec::new(),
203 }
204 }
205}
206
207#[derive(Clone)]
211pub struct DynamicChildren {
212 pub(crate) render: Rc<dyn Fn() -> Vec<(Option<Key>, Option<Rc<dyn AnyValueEq>>, Rc<dyn Fn() -> Element>)>>,
213}
214
215pub trait AnyValueEq: Any {
218 fn item_equals(&self, other: &dyn AnyValueEq) -> bool;
220 fn item_as_any(&self) -> &dyn Any;
222}
223
224impl<T: PartialEq + 'static> AnyValueEq for T {
225 fn item_equals(&self, other: &dyn AnyValueEq) -> bool {
226 other.item_as_any().downcast_ref::<T>().is_some_and(|other| other == self)
227 }
228
229 fn item_as_any(&self) -> &dyn Any {
230 self
231 }
232}
233
234#[derive(Clone, Default)]
238pub enum Element {
239 #[default]
241 Empty,
242 Control(Box<ControlElement>),
244 Text(Rc<str>),
247 Fragment(Vec<Element>),
249 Dynamic(DynamicChildren),
251 Existing(Ref<Control>),
254}
255
256impl fmt::Debug for Element {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 match self {
259 Element::Empty => f.write_str("Empty"),
260 Element::Control(control) => control.fmt(f),
261 Element::Text(text) => write!(f, "Text({text:?})"),
262 Element::Fragment(items) => f.debug_tuple("Fragment").field(items).finish(),
263 Element::Dynamic(_) => f.write_str("Dynamic"),
264 Element::Existing(_) => f.write_str("Existing"),
265 }
266 }
267}
268
269pub trait IntoElement {
271 fn into_element(self) -> Element;
273}
274
275impl IntoElement for Element {
276 fn into_element(self) -> Element {
277 self
278 }
279}
280
281impl<T: ferroui_base::ObjectType + ferroui_base::Upcast<Control>> IntoElement for Ref<T> {
282 fn into_element(self) -> Element {
283 Element::Existing(self.upcast())
284 }
285}
286
287impl<T: IntoElement> IntoElement for Option<T> {
288 fn into_element(self) -> Element {
289 self.map_or(Element::Empty, IntoElement::into_element)
290 }
291}
292
293impl<T: IntoElement> IntoElement for Vec<T> {
294 fn into_element(self) -> Element {
295 Element::Fragment(self.into_iter().map(IntoElement::into_element).collect())
296 }
297}
298
299pub trait IntoChild {
302 fn into_child(self) -> Element;
304}
305
306impl<T: IntoElement> IntoChild for T {
307 fn into_child(self) -> Element {
308 self.into_element()
309 }
310}
311
312impl IntoChild for &str {
313 fn into_child(self) -> Element {
314 Element::Text(Rc::from(self))
315 }
316}
317
318impl IntoChild for String {
319 fn into_child(self) -> Element {
320 Element::Text(Rc::from(self))
321 }
322}
323
324pub trait IntoChildren {
326 fn into_children(self) -> Vec<Element>;
328}
329
330impl<T: IntoChild> IntoChildren for Vec<T> {
331 fn into_children(self) -> Vec<Element> {
332 self.into_iter().map(IntoChild::into_child).collect()
333 }
334}
335
336impl<T: IntoChild, const N: usize> IntoChildren for [T; N] {
337 fn into_children(self) -> Vec<Element> {
338 self.into_iter().map(IntoChild::into_child).collect()
339 }
340}
341
342macro_rules! tuple_children {
343 ($($name:ident),+) => {
344 impl<$($name: IntoChild),+> IntoChildren for ($($name,)+) {
345 #[allow(non_snake_case)]
346 fn into_children(self) -> Vec<Element> {
347 let ($($name,)+) = self;
348 vec![$($name.into_child()),+]
349 }
350 }
351 };
352}
353
354tuple_children!(A);
355tuple_children!(A, B);
356tuple_children!(A, B, C);
357tuple_children!(A, B, C, D);
358tuple_children!(A, B, C, D, E);
359tuple_children!(A, B, C, D, E, F);
360tuple_children!(A, B, C, D, E, F, G);
361tuple_children!(A, B, C, D, E, F, G, H);
362tuple_children!(A, B, C, D, E, F, G, H, I);
363tuple_children!(A, B, C, D, E, F, G, H, I, J);
364tuple_children!(A, B, C, D, E, F, G, H, I, J, K);
365tuple_children!(A, B, C, D, E, F, G, H, I, J, K, L);
366
367enum Host {
369 Panel(Ref<Panel>),
371 Items(Ref<ItemsControl>),
373 Content(Ref<Control>, &'static FerroProperty),
375 Decorator(Ref<Decorator>),
377 Text(Ref<TextBlock>),
379}
380
381#[derive(Clone)]
383enum Placed {
384 Control(Ref<Control>),
385 Value(BoxedValue),
386}
387
388impl Placed {
389 fn boxed(&self) -> BoxedValue {
390 match self {
391 Placed::Control(control) => Control::boxed(control),
392 Placed::Value(value) => value.clone(),
393 }
394 }
395}
396
397impl Host {
398 fn of(control: &Ref<Control>) -> Option<Host> {
399 if let Some(panel) = control.cast::<Panel>() {
400 return Some(Host::Panel(panel));
401 }
402 if let Some(items) = control.cast::<ItemsControl>() {
403 return Some(Host::Items(items));
404 }
405 if control.cast::<ContentControl>().is_some() {
406 return Some(Host::Content(control.clone(), ContentControl::content_property().as_property()));
407 }
408 if control.cast::<ContentPresenter>().is_some() {
409 return Some(Host::Content(control.clone(), ContentPresenter::content_property().as_property()));
410 }
411 if let Some(decorator) = control.cast::<Decorator>() {
412 return Some(Host::Decorator(decorator));
413 }
414 if let Some(text) = control.cast::<TextBlock>() {
415 return Some(Host::Text(text));
416 }
417 None
418 }
419
420 fn is_single(&self) -> bool {
421 !matches!(self, Host::Panel(_) | Host::Items(_))
422 }
423
424 fn takes_text(&self) -> bool {
427 matches!(self, Host::Content(..) | Host::Items(_) | Host::Text(_))
428 }
429
430 fn splice(&self, start: usize, removed: usize, items: &[Placed]) {
432 match self {
433 Host::Panel(panel) => {
434 let children = panel.children();
435 for _ in 0..removed {
436 children.remove_at(start);
437 }
438 for (offset, item) in items.iter().enumerate() {
439 match item {
440 Placed::Control(control) => children.insert(start + offset, control.clone()),
441 Placed::Value(_) => panic!("a panel holds controls; text is not a child of a panel (use a TextBlock)"),
442 }
443 }
444 }
445 Host::Items(items_control) => {
446 let collection = items_control.items();
447 for _ in 0..removed {
448 collection.remove_at(start);
449 }
450 for (offset, item) in items.iter().enumerate() {
451 collection.insert(start + offset, Some(item.boxed()));
452 }
453 }
454 Host::Content(control, property) => {
455 assert!(start + items.len() <= 1, "a content holds one child");
456 let value: Option<BoxedValue> = items.first().map(Placed::boxed);
457 control.set_value_untyped(property, &value, BindingPriority::LocalValue);
458 }
459 Host::Decorator(decorator) => {
460 assert!(start + items.len() <= 1, "a decorator holds one child");
461 match items.first() {
462 Some(Placed::Control(control)) => decorator.set_child(control.clone()),
463 Some(Placed::Value(_)) => panic!("a decorator holds a control; text is not its child (use a TextBlock)"),
464 None => decorator.set_child(None::<Ref<Control>>),
465 }
466 }
467 Host::Text(text_block) => {
468 let text: String = items
469 .iter()
470 .map(|item| match item {
471 Placed::Value(value) => value.downcast_ref::<String>().cloned().unwrap_or_default(),
472 Placed::Control(_) => panic!("a TextBlock holds text, not controls"),
473 })
474 .collect();
475 text_block.set_text(Some(text.as_str()));
476 }
477 }
478 }
479}
480
481enum Slot {
484 Fixed(Placed),
485 Dynamic(Rc<RefCell<Vec<Placed>>>),
486}
487
488impl Element {
489 pub fn mount(self) -> Ref<Control> {
500 match self {
501 Element::Control(element) => mount_control(*element),
502 Element::Existing(control) => control,
503 Element::Text(text) => {
504 let text_block = TextBlock::new();
505 text_block.set_text(Some(&*text));
506 text_block.upcast()
507 }
508 Element::Fragment(items) => {
509 let panel = ferroui_controls::StackPanel::new();
510 place_children(&panel.clone().upcast(), items);
511 panel.upcast()
512 }
513 Element::Empty | Element::Dynamic(_) => panic!("an empty element or a dynamic list has no control of its own"),
514 }
515 }
516
517 pub fn mount_root(self) -> (Ref<Control>, Owner) {
521 let owner = Owner::new_root();
522 let control = owner.with(|| self.mount());
523 (control, owner)
524 }
525
526 pub fn key(&self) -> Option<&Key> {
528 match self {
529 Element::Control(element) => element.key.as_ref(),
530 _ => None,
531 }
532 }
533}
534
535fn mount_control(element: ControlElement) -> Ref<Control> {
536 let control = (element.class.create)();
537 control.begin_init();
542 if let Some(name) = element.name {
543 control.set_name(Some(name));
544 }
545 if !element.classes.is_empty() {
546 let classes = control.classes();
547 for class in &element.classes {
548 classes.add(class);
549 }
550 }
551 for entry in element.props {
552 apply_prop(&control, entry.property, entry.value);
553 }
554 for attach in &element.handlers {
555 attach(&control);
556 }
557 place_children(&control, element.children);
558 control.end_init();
559 for attach in &element.on_mount {
560 attach(&control);
561 }
562 control
563}
564
565fn apply_prop(control: &Ref<Control>, property: &'static FerroProperty, value: PropValue) {
566 match value {
567 PropValue::Value(value) => {
568 set_untyped(control, property, value);
569 }
570 PropValue::Dynamic(compute) => {
571 let weak = control.downgrade();
572 effect(move || {
573 let value = compute();
574 if let Some(control) = weak.upgrade() {
575 untrack(|| set_untyped(&control, property, value));
576 }
577 });
578 }
579 PropValue::TwoWay { get, set } => {
580 let weak = control.downgrade();
581 effect(move || {
582 let value = get();
583 if let Some(control) = weak.upgrade() {
584 untrack(|| set_untyped(&control, property, value));
585 }
586 });
587 let subscription = control.property_changed(move |e| {
588 if std::ptr::eq(e.property(), property) {
589 set(e.new_value());
590 }
591 });
592 on_cleanup(move || subscription.dispose());
593 }
594 PropValue::Element(element) => {
595 let element = element.borrow_mut().take().unwrap_or_default();
596 let value: Option<BoxedValue> = match element {
597 Element::Empty => None,
598 Element::Text(text) => Some(Rc::new(text.to_string())),
599 element => Some(Control::boxed(&element.mount())),
600 };
601 control.set_value_untyped(property, &value, BindingPriority::LocalValue);
602 }
603 PropValue::Binding(binding) => {
604 control.bind_binding(property, &*binding);
605 }
606 }
607}
608
609fn set_untyped(control: &Ref<Control>, property: &'static FerroProperty, value: BoxedValue) {
611 let inner: &dyn ferroui_base::AnyValue = &*value;
614 let value: &dyn Any = inner.as_any();
615 control.set_value_untyped(property, value, BindingPriority::LocalValue);
616}
617
618fn place_children(control: &Ref<Control>, children: Vec<Element>) {
620 let children: Vec<Element> = flatten(children);
621 if children.is_empty() {
622 return;
623 }
624 let host = Host::of(control).unwrap_or_else(|| {
625 panic!("{} has no content to place children into", control.get_type().full_name())
626 });
627 let host = Rc::new(host);
628 let slots: Rc<RefCell<Vec<Slot>>> = Rc::new(RefCell::new(Vec::new()));
629 let mut fixed = Vec::new();
630 for child in children {
631 match child {
632 Element::Dynamic(dynamic) => {
633 let items = Rc::new(RefCell::new(Vec::new()));
634 slots.borrow_mut().push(Slot::Dynamic(items.clone()));
635 mount_dynamic(host.clone(), slots.clone(), items, dynamic);
636 }
637 child => {
638 let placed = place(child, host.takes_text());
639 fixed.push(placed.clone());
640 slots.borrow_mut().push(Slot::Fixed(placed));
641 }
642 }
643 }
644 if host.is_single() && slots.borrow().len() > 1 {
645 panic!("{} holds one child; it was given {}", control.get_type().full_name(), slots.borrow().len());
646 }
647 let all: Vec<Placed> = slots
650 .borrow()
651 .iter()
652 .flat_map(|slot| match slot {
653 Slot::Fixed(placed) => vec![placed.clone()],
654 Slot::Dynamic(items) => items.borrow().clone(),
655 })
656 .collect();
657 if !all.is_empty() || !fixed.is_empty() {
658 host.splice(0, 0, &all);
659 }
660}
661
662fn flatten(children: Vec<Element>) -> Vec<Element> {
663 let mut flat = Vec::with_capacity(children.len());
664 for child in children {
665 match child {
666 Element::Fragment(items) => flat.extend(flatten(items)),
667 Element::Empty => {}
668 child => flat.push(child),
669 }
670 }
671 flat
672}
673
674fn place(child: Element, takes_text: bool) -> Placed {
677 match child {
678 Element::Text(text) if takes_text => Placed::Value(Rc::new(text.to_string())),
679 child => Placed::Control(child.mount()),
680 }
681}
682
683struct MountedItem {
685 value: Option<Rc<dyn AnyValueEq>>,
686 placed: Vec<Placed>,
687 owner: Owner,
688}
689
690fn mount_dynamic(host: Rc<Host>, slots: Rc<RefCell<Vec<Slot>>>, items: Rc<RefCell<Vec<Placed>>>, dynamic: DynamicChildren) {
691 let takes_text = host.takes_text();
692 let mounted: Rc<RefCell<HashMap<Key, MountedItem>>> = Rc::new(RefCell::new(HashMap::new()));
693 let unkeyed: Rc<RefCell<Vec<Owner>>> = Rc::new(RefCell::new(Vec::new()));
694 let first_run = Rc::new(std::cell::Cell::new(true));
695 {
696 let mounted = mounted.clone();
697 let unkeyed = unkeyed.clone();
698 on_cleanup(move || {
699 for (_, item) in mounted.borrow_mut().drain() {
700 item.owner.dispose();
701 }
702 for owner in unkeyed.borrow_mut().drain(..) {
703 owner.dispose();
704 }
705 });
706 }
707 let parent = Owner::current();
708 effect(move || {
709 let entries = (dynamic.render)();
710 untrack(|| {
711 let make = |render: &Rc<dyn Fn() -> Element>| -> (Vec<Placed>, Owner) {
712 let owner = parent.map_or_else(Owner::new_root, |parent| parent.with(Owner::new));
713 let placed = owner.with(|| {
714 flatten(vec![render()])
715 .into_iter()
716 .map(|element| match element {
717 Element::Dynamic(_) => panic!("a dynamic list gives elements, not other dynamic lists"),
718 element => place(element, takes_text),
719 })
720 .collect()
721 });
722 (placed, owner)
723 };
724 for owner in unkeyed.borrow_mut().drain(..) {
725 owner.dispose();
726 }
727 let mut previous = std::mem::take(&mut *mounted.borrow_mut());
728 let mut next_mounted = HashMap::new();
729 let mut next = Vec::with_capacity(entries.len());
730 for (key, value, render) in &entries {
731 match key {
732 Some(key) => {
733 let reuse = previous.remove(key).filter(|item| match (&item.value, value) {
734 (Some(old), Some(new)) => old.item_equals(&**new),
735 _ => true,
736 });
737 let item = match reuse {
738 Some(item) => item,
739 None => {
740 if let Some(stale) = previous.remove(key) {
741 stale.owner.dispose();
742 }
743 let (placed, owner) = make(render);
744 MountedItem { value: value.clone(), placed, owner }
745 }
746 };
747 next.extend(item.placed.iter().cloned());
748 next_mounted.insert(key.clone(), item);
749 }
750 None => {
751 let (placed, owner) = make(render);
752 unkeyed.borrow_mut().push(owner);
753 next.extend(placed);
754 }
755 }
756 }
757 for (_, item) in previous {
758 item.owner.dispose();
759 }
760 *mounted.borrow_mut() = next_mounted;
761
762 let old_len = items.borrow().len();
763 *items.borrow_mut() = next.clone();
764 if first_run.replace(false) {
765 return;
768 }
769 let start = slot_start(&slots, &items);
770 host.splice(start, old_len, &next);
771 });
772 });
773}
774
775fn slot_start(slots: &Rc<RefCell<Vec<Slot>>>, items: &Rc<RefCell<Vec<Placed>>>) -> usize {
778 let mut start = 0;
779 for slot in slots.borrow().iter() {
780 match slot {
781 Slot::Fixed(_) => start += 1,
782 Slot::Dynamic(other) => {
783 if Rc::ptr_eq(other, items) {
784 return start;
785 }
786 start += other.borrow().len();
787 }
788 }
789 }
790 start
791}
792
793#[allow(dead_code)]
795pub(crate) fn dispose_with_owner(subscription: Rc<dyn IDisposable>) {
796 on_cleanup(move || subscription.dispose());
797}
798
799pub fn with_key(element: Element, key: Key) -> Element {
802 match element {
803 Element::Control(mut control) => {
804 control.key = Some(key);
805 Element::Control(control)
806 }
807 element => element,
808 }
809}