ferroui_controls/presenters/
items_presenter.rs1use super::panel_container_generator::PanelContainerGenerator;
2use crate::primitives::{as_logical_scrollable, register_logical_scrollable, ILogicalScrollable};
3use crate::templates::ITemplateOf;
4use crate::{Control, ControlImpl, ItemsControl, Panel, VirtualizingPanel};
5use ferroui_base::input::{IScrollable, InputElementImpl, NavigationDirection};
6use ferroui_base::interactivity::InteractiveImpl;
7use ferroui_base::layout::{BringIntoViewRequest, Layoutable, LayoutableImpl};
8use ferroui_base::reactive::{Disposable, IDisposable};
9use ferroui_base::utilities::HandlerList;
10use ferroui_base::{
11 ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObject, FerroObjectImpl, FerroObjectImplExt,
12 FerroPropertyChangedEventArgs, Rect, Ref, Size, StyledElement, StyledElementImpl, StyledProperty, Vector,
13 VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs, WeakRef,
14};
15use std::cell::{Cell, RefCell};
16use std::rc::Rc;
17
18#[repr(C)]
20pub struct ItemsPresenter {
21 base: Control,
22 panel: RefCell<Option<Ref<Panel>>>,
23 items_control: RefCell<Option<WeakRef<ItemsControl>>>,
24 generator: RefCell<Option<Rc<PanelContainerGenerator>>>,
25 logical_scrollable: RefCell<Option<Ref<Panel>>>,
27 logical_scroll_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
28 scroll_invalidated: Rc<HandlerList<dyn Fn()>>,
29 pending_scroll_into_view_index: Cell<i32>,
30}
31
32ferro_class!(ItemsPresenter: Control);
33ferroui_base::ferro_class_info!(ItemsPresenter { new: ItemsPresenter::new });
34ferro_impl_classes!(ItemsPresenter: StyledElementImpl, InteractiveImpl, ControlImpl);
35
36impl FerroObjectImpl for ItemsPresenter {
37 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
38 Self::parent_on_property_changed(this, change);
39
40 if change.property() == StyledElement::templated_parent_property().as_property() {
41 this.reset_state();
42 *this.items_control.borrow_mut() = None;
43
44 let new_value = change.get_new_value::<Option<Ref<FerroObject>>>();
45 if let Some(items_control) = new_value.and_then(|v| v.cast::<ItemsControl>()) {
46 *this.items_control.borrow_mut() = Some(items_control.downgrade());
47 items_control.register_items_presenter(this);
48 }
49 } else if change.property() == Self::items_panel_property().as_property() {
50 this.reset_state();
51 this.invalidate_measure();
52 }
53 }
54}
55
56impl VisualImpl for ItemsPresenter {
57 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
58 Self::parent_on_attached_to_visual_tree(this, e);
59
60 if this.pending_scroll_into_view_index.get() >= 0 {
61 if let Some(root) = this.get_layout_root() {
62 let layout_manager = root.layout_manager();
63 if let Some(layout_manager) = layout_manager.as_bring_into_view_layout_manager() {
64 layout_manager.enqueue_bring_into_view(this.scroll_into_view_request());
65 }
66 }
67 }
68 }
69}
70
71impl LayoutableImpl for ItemsPresenter {
72 fn apply_template(this: &Self) {
73 if this.panel.borrow().is_some() {
74 return;
75 }
76 let Some(items_control) = this.items_control() else { return };
77
78 if let Some(subscription) = this.logical_scroll_subscription.take() {
79 subscription.dispose();
80 }
81
82 let Some(panel) = this.items_panel().build_typed() else { return };
83 *this.panel.borrow_mut() = Some(panel.clone());
84
85 panel.set_templated_parent(this.templated_parent());
86 panel.set_is_items_host(true);
87 this.logical_children().add(panel.clone().upcast());
88 this.visual_children().add(panel.clone().upcast());
89
90 if let Some(v) = panel.downcast_ref::<VirtualizingPanel>() {
91 v.attach(&items_control);
92 } else {
93 this.create_simple_panel_generator();
94 }
95
96 let is_logical_scrollable = as_logical_scrollable(&panel).is_some();
97 *this.logical_scrollable.borrow_mut() = if is_logical_scrollable { Some(panel.clone()) } else { None };
98
99 if let Some(scrollable) = as_logical_scrollable(&panel) {
100 let handlers = Rc::downgrade(&this.scroll_invalidated);
101 let subscription = scrollable.scroll_invalidated(Rc::new(move || {
102 if let Some(handlers) = handlers.upgrade() {
103 for (_, handler) in handlers.snapshot().iter() {
104 handler();
105 }
106 }
107 }));
108 *this.logical_scroll_subscription.borrow_mut() = Some(subscription);
109 }
110 }
111}
112
113impl InputElementImpl for ItemsPresenter {
114 fn as_scrollable(this: &Self) -> Option<&dyn IScrollable> {
115 Some(this)
116 }
117}
118
119impl IScrollable for ItemsPresenter {
120 fn extent(&self) -> Size {
121 self.with_logical_scrollable(|s| s.extent()).unwrap_or_default()
122 }
123
124 fn offset(&self) -> Vector {
125 self.with_logical_scrollable(|s| s.offset()).unwrap_or_default()
126 }
127
128 fn set_offset(&self, value: Vector) {
129 self.with_logical_scrollable(|s| s.set_offset(value));
130 }
131
132 fn viewport(&self) -> Size {
133 self.with_logical_scrollable(|s| s.viewport()).unwrap_or_default()
134 }
135
136 fn can_horizontally_scroll(&self) -> bool {
137 self.with_logical_scrollable(|s| s.can_horizontally_scroll()).unwrap_or(false)
138 }
139
140 fn can_vertically_scroll(&self) -> bool {
141 self.with_logical_scrollable(|s| s.can_vertically_scroll()).unwrap_or(false)
142 }
143}
144
145impl ILogicalScrollable for ItemsPresenter {
146 fn set_can_horizontally_scroll(&self, value: bool) {
147 self.with_logical_scrollable(|s| s.set_can_horizontally_scroll(value));
148 }
149
150 fn set_can_vertically_scroll(&self, value: bool) {
151 self.with_logical_scrollable(|s| s.set_can_vertically_scroll(value));
152 }
153
154 fn is_logical_scroll_enabled(&self) -> bool {
155 self.with_logical_scrollable(|s| s.is_logical_scroll_enabled()).unwrap_or(false)
156 }
157
158 fn scroll_size(&self) -> Size {
159 self.with_logical_scrollable(|s| s.scroll_size()).unwrap_or_default()
160 }
161
162 fn page_scroll_size(&self) -> Size {
163 self.with_logical_scrollable(|s| s.page_scroll_size()).unwrap_or_default()
164 }
165
166 fn scroll_invalidated(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
167 let token = self.scroll_invalidated.add(handler);
168 let handlers = Rc::downgrade(&self.scroll_invalidated);
169 Disposable::create(move || {
170 if let Some(handlers) = handlers.upgrade() {
171 handlers.remove(token);
172 }
173 })
174 }
175
176 fn bring_into_view(&self, target: &Ref<Control>, target_rect: Rect) -> bool {
177 self.with_logical_scrollable(|s| s.bring_into_view(target, target_rect)).unwrap_or(false)
178 }
179
180 fn get_control_in_direction(
181 &self,
182 direction: NavigationDirection,
183 from: Option<&Ref<Control>>,
184 ) -> Option<Ref<Control>> {
185 self.with_logical_scrollable(|s| s.get_control_in_direction(direction, from)).flatten()
186 }
187
188 fn raise_scroll_invalidated(&self) {
189 for (_, handler) in self.scroll_invalidated.snapshot().iter() {
190 handler();
191 }
192 }
193}
194
195struct ScrollIntoViewRequest {
198 presenter: Ref<ItemsPresenter>,
199}
200
201impl BringIntoViewRequest for ScrollIntoViewRequest {
202 fn target(&self) -> Ref<Layoutable> {
203 self.presenter.clone().upcast()
204 }
205
206 fn try_execute(&self) -> bool {
207 self.presenter.try_execute_pending_scroll_into_view()
208 }
209}
210
211ferroui_base::ferro_properties! { impl ItemsPresenter {
212 ferro_property!(
213 pub fn items_panel_property() -> StyledProperty<Rc<dyn ITemplateOf<Option<Ref<Panel>>>>> {
215 ItemsControl::items_panel_property().add_owner::<ItemsPresenter>()
216 }
217 );
218} }
219
220impl ItemsPresenter {
221 fn static_constructor() {
222 register_logical_scrollable::<ItemsPresenter>();
223 }
224
225 pub fn construct() -> Self {
227 Self {
228 base: Control::construct(),
229 panel: RefCell::new(None),
230 items_control: RefCell::new(None),
231 generator: RefCell::new(None),
232 logical_scrollable: RefCell::new(None),
233 logical_scroll_subscription: RefCell::new(None),
234 scroll_invalidated: Rc::new(HandlerList::new()),
235 pending_scroll_into_view_index: Cell::new(-1),
236 }
237 }
238
239 pub fn new() -> Ref<Self> {
240 instantiate(Self::construct())
241 }
242
243 pub fn items_panel(&self) -> Rc<dyn ITemplateOf<Option<Ref<Panel>>>> {
246 self.get_value(Self::items_panel_property())
247 }
248
249 pub fn set_items_panel(&self, value: Rc<dyn ITemplateOf<Option<Ref<Panel>>>>) {
250 self.set_value(Self::items_panel_property(), value)
251 }
252
253 pub fn panel(&self) -> Option<Ref<Panel>> {
255 self.panel.borrow().clone()
256 }
257
258 pub(crate) fn items_control(&self) -> Option<Ref<ItemsControl>> {
260 self.items_control.borrow().as_ref().and_then(WeakRef::upgrade)
261 }
262
263 fn with_logical_scrollable<R>(&self, f: impl FnOnce(&dyn ILogicalScrollable) -> R) -> Option<R> {
264 let panel = self.logical_scrollable.borrow().clone()?;
265 let scrollable = as_logical_scrollable(&panel)?;
266 Some(f(scrollable))
267 }
268
269 fn scroll_into_view_request(&self) -> Rc<dyn BringIntoViewRequest> {
270 Rc::new(ScrollIntoViewRequest { presenter: self.to_ref() })
271 }
272
273 pub(crate) fn scroll_into_view(&self, index: i32) {
274 if index < 0 {
275 return;
276 }
277
278 let layout_manager = self.get_layout_root().map(|root| root.layout_manager());
281 let Some(layout_manager) = layout_manager.as_ref().and_then(|m| m.as_bring_into_view_layout_manager()) else {
282 self.pending_scroll_into_view_index.set(index);
283 return;
284 };
285
286 if !layout_manager.is_in_layout_pass() && self.try_scroll_into_view_now(index) {
288 self.pending_scroll_into_view_index.set(-1);
289 return;
290 }
291
292 self.pending_scroll_into_view_index.set(index);
294 layout_manager.enqueue_bring_into_view(self.scroll_into_view_request());
295 }
296
297 pub(crate) fn cancel_scroll_into_view(&self, index: i32) {
298 if self.pending_scroll_into_view_index.get() == index {
299 self.pending_scroll_into_view_index.set(-1);
300 }
301 }
302
303 fn try_scroll_into_view_now(&self, index: i32) -> bool {
304 if !self.is_effectively_visible() {
305 return false;
306 }
307 let Some(panel) = self.panel() else { return false };
308
309 if let Some(virtualizing_panel) = panel.downcast_ref::<VirtualizingPanel>() {
310 return virtualizing_panel.scroll_into_view(index).is_some();
311 }
312
313 if (index as usize) < panel.children().count() {
314 panel.children().get(index as usize).bring_into_view();
315 return true;
316 }
317
318 false
319 }
320
321 fn try_execute_pending_scroll_into_view(&self) -> bool {
322 let index = self.pending_scroll_into_view_index.get();
323 if index < 0 {
324 return true;
325 }
326
327 let panel_valid = self.panel().is_some_and(|panel| panel.is_measure_valid() && panel.is_arrange_valid());
328 if !panel_valid || !self.is_effectively_visible() {
329 return false;
330 }
331
332 self.pending_scroll_into_view_index.set(-1);
333
334 let _ = self.try_scroll_into_view_now(index);
338
339 self.pending_scroll_into_view_index.get() < 0
343 }
344
345 pub(crate) fn refresh(&self) {
346 let panel = self.panel();
347 if let Some(v) = panel.as_ref().and_then(|p| p.downcast_ref::<VirtualizingPanel>()) {
348 v.refresh();
349 } else {
350 let generator = self.generator.borrow().clone();
351 if let Some(generator) = generator {
352 generator.refresh();
353 }
354 }
355 }
356
357 fn reset_state(&self) {
358 if let Some(generator) = self.generator.take() {
359 generator.dispose();
360 }
361 self.logical_children().clear();
362 self.visual_children().clear();
363 let panel = self.panel.take();
364 if let Some(v) = panel.as_ref().and_then(|p| p.downcast_ref::<VirtualizingPanel>()) {
365 v.detach();
366 }
367 }
368
369 fn create_simple_panel_generator(&self) {
370 debug_assert!(!self.panel().is_some_and(|p| p.is::<VirtualizingPanel>()));
371
372 if self.items_control().is_none() || self.panel().is_none() {
373 return;
374 }
375
376 if let Some(generator) = self.generator.take() {
377 generator.dispose();
378 }
379 let generator = PanelContainerGenerator::new(self);
380 *self.generator.borrow_mut() = Some(generator);
381 }
382
383 pub(crate) fn container_from_index(&self, index: i32) -> Option<Ref<Control>> {
384 let panel = self.panel()?;
385 if let Some(v) = panel.downcast_ref::<VirtualizingPanel>() {
386 return v.container_from_index(index);
387 }
388 if index >= 0 {
389 panel.children().try_get(index as usize)
390 } else {
391 None
392 }
393 }
394
395 pub(crate) fn get_realized_containers(&self) -> Option<Vec<Ref<Control>>> {
396 let panel = self.panel()?;
397 if let Some(v) = panel.downcast_ref::<VirtualizingPanel>() {
398 return v.get_realized_containers();
399 }
400 Some(panel.children().to_vec())
401 }
402
403 pub(crate) fn index_from_container(&self, container: &Ref<Control>) -> i32 {
404 let Some(panel) = self.panel() else { return -1 };
405 if let Some(v) = panel.downcast_ref::<VirtualizingPanel>() {
406 return v.index_from_container(container);
407 }
408 panel.children().index_of(container).map_or(-1, |index| index as i32)
409 }
410}