Skip to main content

ferroui_controls/documents/
inline_collection.rs

1use super::{IInlineHost, Inline, InlineUIContainer, Run};
2use crate::Control;
3use ferroui_base::collections::{FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs, ResetBehavior};
4use ferroui_base::reactive::{Disposable, IDisposable};
5use ferroui_base::utilities::HandlerList;
6use ferroui_base::{IntoRef, Ref, StyledElement, WeakRef};
7use std::cell::RefCell;
8use std::ops::Deref;
9use std::rc::{Rc, Weak};
10
11/// A collection of [`Inline`]s.
12///
13/// The value is a shared handle: clones refer to the same collection, and
14/// handles compare by identity.
15#[derive(Clone)]
16pub struct InlineCollection(Rc<InlineCollectionData>);
17
18struct InlineCollectionData {
19    list: FerroList<Ref<Inline>>,
20    /// The element whose logical children mirror the collection.
21    logical_children: RefCell<Option<WeakRef<StyledElement>>>,
22    inline_host: RefCell<Option<Rc<dyn IInlineHost>>>,
23    invalidated: Rc<HandlerList<dyn Fn()>>,
24}
25
26impl PartialEq for InlineCollection {
27    fn eq(&self, other: &Self) -> bool {
28        Rc::ptr_eq(&self.0, &other.0)
29    }
30}
31
32impl std::fmt::Debug for InlineCollection {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        write!(f, "InlineCollection({})", self.0.list.count())
35    }
36}
37
38impl Deref for InlineCollection {
39    type Target = FerroList<Ref<Inline>>;
40
41    fn deref(&self) -> &Self::Target {
42        &self.0.list
43    }
44}
45
46impl InlineCollection {
47    /// Initializes a new instance of the `InlineCollection` class.
48    pub fn new() -> Self {
49        let list = FerroList::new();
50        list.set_reset_behavior(ResetBehavior::Remove);
51
52        let collection = Self(Rc::new(InlineCollectionData {
53            list,
54            logical_children: RefCell::new(None),
55            inline_host: RefCell::new(None),
56            invalidated: Rc::new(HandlerList::new()),
57        }));
58
59        let weak: Weak<InlineCollectionData> = Rc::downgrade(&collection.0);
60        collection.0.list.add_collection_changed(Rc::new(
61            move |e: &NotifyCollectionChangedEventArgs<'_, Ref<Inline>>| {
62                let Some(this) = weak.upgrade().map(InlineCollection) else { return };
63
64                match e.action {
65                    NotifyCollectionChangedAction::Add => {
66                        for inline in e.new_items {
67                            this.on_add(inline);
68                        }
69                    }
70                    NotifyCollectionChangedAction::Remove => {
71                        for inline in e.old_items {
72                            this.on_remove(inline);
73                        }
74                    }
75                    NotifyCollectionChangedAction::Replace => {
76                        for inline in e.old_items {
77                            this.on_remove(inline);
78                        }
79                        for inline in e.new_items {
80                            this.on_add(inline);
81                        }
82                    }
83                    NotifyCollectionChangedAction::Move => {}
84                    NotifyCollectionChangedAction::Reset => panic!("Resetting the collection is not supported."),
85                }
86            },
87        ));
88
89        collection
90    }
91
92    /// The element whose logical children mirror the collection.
93    pub(crate) fn logical_children(&self) -> Option<Ref<StyledElement>> {
94        self.0.logical_children.borrow().as_ref().and_then(WeakRef::upgrade)
95    }
96
97    pub(crate) fn set_logical_children(&self, value: Option<&StyledElement>) {
98        let old_value = self.logical_children();
99        let new_value = value.map(StyledElement::to_ref);
100
101        *self.0.logical_children.borrow_mut() = new_value.as_ref().map(Ref::downgrade);
102
103        self.on_parent_changed(old_value.as_ref(), new_value.as_ref());
104    }
105
106    /// The host of the inlines of the collection.
107    pub(crate) fn inline_host(&self) -> Option<Rc<dyn IInlineHost>> {
108        self.0.inline_host.borrow().clone()
109    }
110
111    pub(crate) fn set_inline_host(&self, value: Option<Rc<dyn IInlineHost>>) {
112        *self.0.inline_host.borrow_mut() = value.clone();
113
114        self.on_inline_host_changed(value);
115    }
116
117    /// Gets the text contained in this collection: the text of every inline,
118    /// or `None` when the collection is empty.
119    pub fn text(&self) -> Option<String> {
120        if self.0.list.count() == 0 {
121            return None;
122        }
123
124        let mut builder = String::new();
125
126        for inline in self.0.list.snapshot().iter() {
127            inline.append_text(&mut builder);
128        }
129
130        Some(builder)
131    }
132
133    /// Adds an inline (of any class) to the collection.
134    ///
135    /// The text of a hosting text block becomes the first run of the
136    /// collection.
137    pub fn add(&self, inline: impl IntoRef<Inline>) {
138        if let Some(text_block) = self.inline_host().and_then(|host| host.as_text_block()) {
139            if let Some(text) = text_block.text().filter(|text| !text.is_empty()) {
140                self.0.list.add(Run::with_text(Some(&text)).upcast());
141
142                text_block.clear_text_internal();
143            }
144        }
145
146        self.0.list.add(inline.into_ref());
147    }
148
149    /// Adds a text inline to this collection.
150    pub fn add_text(&self, text: &str) {
151        match self.inline_host().and_then(|host| host.as_text_block()) {
152            Some(text_block) if !text_block.has_complex_content() => {
153                let mut current = text_block.text().unwrap_or_default();
154                current.push_str(text);
155                text_block.set_text(Some(&current));
156            }
157            _ => self.add(Run::with_text(Some(text))),
158        }
159    }
160
161    /// Adds a control wrapped inside an `InlineUIContainer` to this
162    /// collection.
163    pub fn add_control(&self, control: impl IntoRef<Control>) {
164        self.add(InlineUIContainer::with_child(control));
165    }
166
167    /// Raised when an inline in the collection changes.
168    pub fn invalidated(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
169        let token = self.0.invalidated.add(Rc::new(handler));
170        let handlers = Rc::downgrade(&self.0.invalidated);
171        Disposable::create(move || {
172            if let Some(handlers) = handlers.upgrade() {
173                handlers.remove(token);
174            }
175        })
176    }
177
178    /// Invalidates this collection.
179    fn invalidate(&self) {
180        if let Some(host) = self.inline_host() {
181            host.invalidate();
182        }
183
184        for (_, handler) in self.0.invalidated.snapshot().iter() {
185            handler();
186        }
187    }
188
189    fn on_parent_changed(&self, old_value: Option<&Ref<StyledElement>>, new_value: Option<&Ref<StyledElement>>) {
190        if old_value != new_value {
191            for child in self.0.list.snapshot().iter() {
192                let child: Ref<StyledElement> = child.clone().upcast();
193
194                if let Some(old_value) = old_value {
195                    old_value.logical_children().remove(&child);
196                }
197
198                if let Some(new_value) = new_value {
199                    new_value.logical_children().add(child);
200                }
201            }
202        }
203
204        self.invalidate();
205    }
206
207    fn on_inline_host_changed(&self, new_value: Option<Rc<dyn IInlineHost>>) {
208        for child in self.0.list.snapshot().iter() {
209            child.set_inline_host(new_value.clone());
210        }
211
212        self.invalidate();
213    }
214
215    fn on_add(&self, inline: &Ref<Inline>) {
216        inline.set_inline_host(self.inline_host());
217
218        if let Some(logical_children) = self.logical_children() {
219            logical_children.logical_children().add(inline.clone().upcast());
220        }
221
222        self.invalidate();
223    }
224
225    fn on_remove(&self, inline: &Ref<Inline>) {
226        if let Some(logical_children) = self.logical_children() {
227            logical_children.logical_children().remove(&inline.clone().upcast());
228        }
229
230        inline.set_inline_host(None);
231
232        self.invalidate();
233    }
234}