ferroui_controls/documents/
inline_collection.rs1use 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#[derive(Clone)]
16pub struct InlineCollection(Rc<InlineCollectionData>);
17
18struct InlineCollectionData {
19 list: FerroList<Ref<Inline>>,
20 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 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 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 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 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 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 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(¤t));
156 }
157 _ => self.add(Run::with_text(Some(text))),
158 }
159 }
160
161 pub fn add_control(&self, control: impl IntoRef<Control>) {
164 self.add(InlineUIContainer::with_child(control));
165 }
166
167 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 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}