ferroui_controls/pull_to_refresh/
refresh_container.rs1use super::{
2 RefreshInfoProvider, RefreshRequestedEventArgs, RefreshVisualizer, ScrollViewerIRefreshInfoProviderAdapter,
3};
4use crate::primitives::{TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt};
5use crate::{ContentControl, ContentControlImpl, ControlImpl, Grid};
6use ferroui_base::data::BindingPriority;
7use ferroui_base::input::{InputElementImpl, PullDirection};
8use ferroui_base::interactivity::{Interactive, InteractiveImpl, RoutedEvent, RoutedEventHandlerToken, RoutingStrategies};
9use ferroui_base::layout::{HorizontalAlignment, LayoutableImpl, VerticalAlignment};
10use ferroui_base::reactive::{IDisposable, ObservableExt};
11use ferroui_base::{
12 ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, DirectProperty, FerroObject,
13 FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Rect, Ref,
14 Size, StyledElementImpl, StyledProperty, Visual, VisualImpl,
15};
16use std::cell::{Cell, RefCell};
17use std::rc::Rc;
18
19#[repr(C)]
22pub struct RefreshContainer {
23 base: ContentControl,
24 has_default_refresh_info_provider_adapter: Cell<bool>,
25 refresh_info_provider_adapter: RefCell<Option<Rc<ScrollViewerIRefreshInfoProviderAdapter>>>,
26 refresh_info_provider: RefCell<Option<Ref<RefreshInfoProvider>>>,
27 visualizer_size_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
28 visualizer_presenter: RefCell<Option<Ref<Grid>>>,
29 refresh_visualizer: RefCell<Option<Ref<RefreshVisualizer>>>,
30 visualizer_refresh_requested: Cell<Option<RoutedEventHandlerToken>>,
32 has_default_refresh_visualizer: Cell<bool>,
33}
34
35ferro_class!(RefreshContainer: ContentControl);
36ferroui_base::ferro_class_info!(RefreshContainer { new: RefreshContainer::new });
37ferro_impl_classes!(
38 RefreshContainer: StyledElementImpl,
39 VisualImpl,
40 LayoutableImpl,
41 InteractiveImpl,
42 InputElementImpl,
43 ControlImpl,
44 ContentControlImpl
45);
46
47struct DecrementCountGuard<'a>(&'a RefreshRequestedEventArgs);
50
51impl Drop for DecrementCountGuard<'_> {
52 fn drop(&mut self) {
53 self.0.decrement_count();
54 }
55}
56
57impl FerroObjectImpl for RefreshContainer {
58 fn constructed(this: &Self) {
59 Self::parent_constructed(this);
60
61 this.has_default_refresh_info_provider_adapter.set(true);
62 let adapter = ScrollViewerIRefreshInfoProviderAdapter::new(this.pull_direction(), this.is_mouse_enabled());
63 drop(this.refresh_info_provider_adapter.replace(Some(adapter.clone())));
64 this.raise_refresh_info_provider_adapter_changed(Some(adapter));
65 }
66
67 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
68 Self::parent_on_property_changed(this, change);
69
70 if change.property() == Self::refresh_info_provider_adapter_property().as_property() {
71 let refresh_visualizer = this.refresh_visualizer.borrow().clone();
72 if let Some(refresh_visualizer) = refresh_visualizer {
73 let refresh_info_provider = this.refresh_info_provider.borrow().clone();
74 if let Some(refresh_info_provider) = refresh_info_provider {
75 refresh_visualizer.set_refresh_info_provider(Some(refresh_info_provider));
76 } else if let Some(adapter) = this.refresh_info_provider_adapter() {
77 let refresh_info_provider =
78 adapter.adapt_from_tree(this, Some(refresh_visualizer.bounds().size()));
79 drop(this.refresh_info_provider.replace(refresh_info_provider.clone()));
80
81 if let Some(refresh_info_provider) = refresh_info_provider {
82 refresh_visualizer.set_refresh_info_provider(Some(refresh_info_provider));
83 if let Some(adapter) = this.refresh_info_provider_adapter() {
84 adapter.set_animations(&refresh_visualizer);
85 }
86 }
87 }
88 }
89 } else if change.property() == Self::visualizer_property().as_property() {
90 let refresh_visualizer = this.refresh_visualizer.borrow().clone();
91
92 let visualizer_presenter = this.visualizer_presenter.borrow().clone();
93 if let Some(visualizer_presenter) = visualizer_presenter {
94 visualizer_presenter.children().clear();
95 if let Some(refresh_visualizer) = &refresh_visualizer {
96 visualizer_presenter.children().add(refresh_visualizer.clone());
97 }
98 }
99
100 if let Some(refresh_visualizer) = refresh_visualizer {
101 let weak = this.to_ref().downgrade();
102 this.visualizer_refresh_requested.set(Some(refresh_visualizer.refresh_requested(move |_, e| {
103 if let Some(this) = weak.upgrade() {
104 this.visualizer_refresh_requested(e);
105 }
106 })));
107
108 let weak = this.to_ref().downgrade();
109 let object: &FerroObject = &refresh_visualizer;
110 let subscription =
111 FerroObjectExtensions::get_observable(object, Visual::bounds_property())
112 .subscribe_fn(move |bounds| {
113 if let Some(this) = weak.upgrade() {
114 this.on_visualizer_size_changed(bounds);
115 }
116 });
117 drop(this.visualizer_size_subscription.replace(Some(subscription)));
118 }
119 } else if change.property() == Self::pull_direction_property().as_property() {
120 this.on_pull_direction_changed();
121 } else if change.property() == Self::is_mouse_enabled_property().as_property() {
122 this.on_is_mouse_enabled_changed();
123 }
124 }
125}
126
127impl TemplatedControlImpl for RefreshContainer {
128 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
129 Self::parent_on_apply_template(this, e);
130
131 let visualizer_presenter = e.name_scope().find_as::<Grid>("PART_RefreshVisualizerPresenter");
132 drop(this.visualizer_presenter.replace(visualizer_presenter));
133
134 let refresh_visualizer = this.refresh_visualizer.borrow().clone();
135 match refresh_visualizer {
136 None => {
137 this.has_default_refresh_visualizer.set(true);
138 this.set_visualizer(Some(RefreshVisualizer::new()));
139 }
140 Some(refresh_visualizer) => {
141 this.has_default_refresh_visualizer.set(false);
142 this.raise_property_changed(
143 Self::visualizer_property().as_property(),
144 Some(&None::<Ref<RefreshVisualizer>>),
145 &Some(refresh_visualizer),
146 BindingPriority::LocalValue,
147 true,
148 );
149 }
150 }
151
152 this.on_pull_direction_changed();
153 }
154}
155
156ferroui_base::ferro_properties! { impl RefreshContainer {
157 ferro_property!(
158 pub(crate) fn refresh_info_provider_adapter_property()
159 -> DirectProperty<RefreshContainer, Option<Rc<ScrollViewerIRefreshInfoProviderAdapter>>> {
160 FerroProperty::register_direct::<RefreshContainer, _>(
161 "RefreshInfoProviderAdapter",
162 |s| s.refresh_info_provider_adapter(),
163 Some(|s, o| s.set_refresh_info_provider_adapter(o)),
164 None,
165 )
166 }
167 );
168
169 ferro_property!(
170 pub fn visualizer_property() -> DirectProperty<RefreshContainer, Option<Ref<RefreshVisualizer>>> {
172 FerroProperty::register_direct::<RefreshContainer, _>(
173 "Visualizer",
174 |s| s.visualizer(),
175 Some(|s, o| s.set_visualizer(o)),
176 None,
177 )
178 }
179 );
180
181 ferro_property!(
182 pub fn pull_direction_property() -> StyledProperty<PullDirection> {
184 FerroProperty::register::<RefreshContainer, _>("PullDirection", PullDirection::TopToBottom)
185 }
186 );
187
188 ferro_property!(
189 pub fn is_mouse_enabled_property() -> StyledProperty<bool> {
194 FerroProperty::register::<RefreshContainer, _>("IsMouseEnabled", false)
195 }
196 );
197} }
198
199impl RefreshContainer {
200 pub(crate) const DEFAULT_PULL_DIMENSION_SIZE: i32 = 100;
201
202 ferro_routed_event!(
203 pub fn refresh_requested_event() -> RoutedEvent<RefreshRequestedEventArgs> {
205 RoutedEvent::register::<RefreshContainer, _>("RefreshRequested", RoutingStrategies::BUBBLE)
206 }
207 );
208
209 pub fn construct() -> Self {
211 Self {
212 base: ContentControl::construct(),
213 has_default_refresh_info_provider_adapter: Cell::new(false),
214 refresh_info_provider_adapter: RefCell::new(None),
215 refresh_info_provider: RefCell::new(None),
216 visualizer_size_subscription: RefCell::new(None),
217 visualizer_presenter: RefCell::new(None),
218 refresh_visualizer: RefCell::new(None),
219 visualizer_refresh_requested: Cell::new(None),
220 has_default_refresh_visualizer: Cell::new(false),
221 }
222 }
223
224 pub fn new() -> Ref<Self> {
225 instantiate(Self::construct())
226 }
227
228 pub(crate) fn refresh_info_provider_adapter(&self) -> Option<Rc<ScrollViewerIRefreshInfoProviderAdapter>> {
229 self.refresh_info_provider_adapter.borrow().clone()
230 }
231
232 pub(crate) fn set_refresh_info_provider_adapter(&self, value: Option<Rc<ScrollViewerIRefreshInfoProviderAdapter>>) {
233 self.has_default_refresh_info_provider_adapter.set(false);
234 self.set_and_raise(Self::refresh_info_provider_adapter_property(), &self.refresh_info_provider_adapter, value);
235 }
236
237 pub fn is_mouse_enabled(&self) -> bool {
241 self.get_value(Self::is_mouse_enabled_property())
242 }
243
244 pub fn set_is_mouse_enabled(&self, value: bool) {
245 self.set_value(Self::is_mouse_enabled_property(), value)
246 }
247
248 pub fn visualizer(&self) -> Option<Ref<RefreshVisualizer>> {
250 self.refresh_visualizer.borrow().clone()
251 }
252
253 pub fn set_visualizer(&self, value: Option<Ref<RefreshVisualizer>>) {
254 let refresh_visualizer = self.refresh_visualizer.borrow().clone();
255 if let Some(refresh_visualizer) = refresh_visualizer {
256 let subscription = self.visualizer_size_subscription.borrow().clone();
257 if let Some(subscription) = subscription {
258 subscription.dispose();
259 }
260 if let Some(token) = self.visualizer_refresh_requested.take() {
261 refresh_visualizer.remove_handler(RefreshVisualizer::refresh_requested_event(), token);
262 }
263 }
264
265 self.set_and_raise(Self::visualizer_property(), &self.refresh_visualizer, value);
266 }
267
268 pub fn pull_direction(&self) -> PullDirection {
271 self.get_value(Self::pull_direction_property())
272 }
273
274 pub fn set_pull_direction(&self, value: PullDirection) {
275 self.set_value(Self::pull_direction_property(), value)
276 }
277
278 pub fn refresh_requested(
280 &self,
281 handler: impl Fn(&Interactive, &RefreshRequestedEventArgs) + 'static,
282 ) -> RoutedEventHandlerToken {
283 self.add_handler(Self::refresh_requested_event(), handler)
284 }
285
286 fn raise_refresh_info_provider_adapter_changed(&self, adapter: Option<Rc<ScrollViewerIRefreshInfoProviderAdapter>>) {
289 self.raise_property_changed(
290 Self::refresh_info_provider_adapter_property().as_property(),
291 Some(&None::<Rc<ScrollViewerIRefreshInfoProviderAdapter>>),
292 &adapter,
293 BindingPriority::LocalValue,
294 true,
295 );
296 }
297
298 fn on_visualizer_size_changed(&self, obj: Rect) {
299 if self.has_default_refresh_info_provider_adapter.get() {
300 if let Some(adapter) = self.refresh_info_provider_adapter() {
301 adapter.update_visualizer_size(Some(obj.size()));
302 self.raise_refresh_info_provider_adapter_changed(Some(adapter));
303 }
304 }
305 }
306
307 fn visualizer_refresh_requested(&self, e: &RefreshRequestedEventArgs) {
308 let ev = RefreshRequestedEventArgs::with_deferral(e.get_deferral(), Some(Self::refresh_requested_event()));
315 let _guard = DecrementCountGuard(&ev);
316 self.raise_event(&ev);
317 }
318
319 fn on_pull_direction_changed(&self) {
320 let visualizer_presenter = self.visualizer_presenter.borrow().clone();
321 let refresh_visualizer = self.refresh_visualizer.borrow().clone();
322 let (Some(visualizer_presenter), Some(refresh_visualizer)) = (visualizer_presenter, refresh_visualizer) else {
323 return;
324 };
325
326 let has_default_refresh_visualizer = self.has_default_refresh_visualizer.get();
327 let default_pull_dimension_size = f64::from(Self::DEFAULT_PULL_DIMENSION_SIZE);
328 let pull_direction = self.pull_direction();
329
330 match pull_direction {
331 PullDirection::TopToBottom => {
332 visualizer_presenter.set_vertical_alignment(VerticalAlignment::Top);
333 visualizer_presenter.set_horizontal_alignment(HorizontalAlignment::Stretch);
334 if has_default_refresh_visualizer {
335 refresh_visualizer.set_pull_direction(PullDirection::TopToBottom);
336 refresh_visualizer.set_height(default_pull_dimension_size);
337 refresh_visualizer.set_width(f64::NAN);
338 }
339 }
340 PullDirection::BottomToTop => {
341 visualizer_presenter.set_vertical_alignment(VerticalAlignment::Bottom);
342 visualizer_presenter.set_horizontal_alignment(HorizontalAlignment::Stretch);
343 if has_default_refresh_visualizer {
344 refresh_visualizer.set_pull_direction(PullDirection::BottomToTop);
345 refresh_visualizer.set_height(default_pull_dimension_size);
346 refresh_visualizer.set_width(f64::NAN);
347 }
348 }
349 PullDirection::LeftToRight => {
350 visualizer_presenter.set_vertical_alignment(VerticalAlignment::Stretch);
351 visualizer_presenter.set_horizontal_alignment(HorizontalAlignment::Left);
352 if has_default_refresh_visualizer {
353 refresh_visualizer.set_pull_direction(PullDirection::LeftToRight);
354 refresh_visualizer.set_width(default_pull_dimension_size);
355 refresh_visualizer.set_height(f64::NAN);
356 }
357 }
358 PullDirection::RightToLeft => {
359 visualizer_presenter.set_vertical_alignment(VerticalAlignment::Stretch);
360 visualizer_presenter.set_horizontal_alignment(HorizontalAlignment::Right);
361 if has_default_refresh_visualizer {
362 refresh_visualizer.set_pull_direction(PullDirection::RightToLeft);
363 refresh_visualizer.set_width(default_pull_dimension_size);
364 refresh_visualizer.set_height(f64::NAN);
365 }
366 }
367 }
368
369 if self.has_default_refresh_info_provider_adapter.get() {
370 let adapter = self.refresh_info_provider_adapter();
371
372 if has_default_refresh_visualizer {
373 let size = Size::new(refresh_visualizer.width(), refresh_visualizer.height());
374 if let Some(adapter) = &adapter {
375 adapter.update_visualizer_size(Some(size));
376 }
377 }
378
379 if let Some(adapter) = &adapter {
380 adapter.update_pull_direction(pull_direction);
381 }
382 self.raise_refresh_info_provider_adapter_changed(adapter);
383 }
384 }
385
386 fn on_is_mouse_enabled_changed(&self) {
387 if let Some(adapter) = self.refresh_info_provider_adapter() {
388 adapter.update_is_mouse_enabled(self.is_mouse_enabled());
389 }
390 }
391
392 pub fn request_refresh(&self) {
394 let refresh_visualizer = self.refresh_visualizer.borrow().clone();
395 if let Some(refresh_visualizer) = refresh_visualizer {
396 refresh_visualizer.request_refresh();
397 }
398 }
399}