1use super::*;
2use crate::animation::AnimationTiming;
3use crate::ffi::{Orientation, Unit};
4use crate::persisted::{store_scroll_offset, try_load_scroll_offset};
5use crate::signal::Subscription;
6use crate::transitions::NodeTransitions;
7use std::cell::{Cell, RefCell};
8use std::rc::{Rc, Weak};
9
10const DEFAULT_SCROLLBAR_GUTTER: f32 = 0.0;
11const OVERFLOW_TOLERANCE: f32 = 0.5;
12
13fn clamp(value: f32, min: f32, max: f32) -> f32 {
14 if value < min {
15 return min;
16 }
17 if value > max {
18 return max;
19 }
20 value
21}
22
23struct ScrollBoxInner {
24 root: FlexBox,
25 scroll_state: ScrollState,
26 viewport: ScrollView,
27 top_row: FlexBox,
28 bottom_row: FlexBox,
29 vertical_gutter: FlexBox,
30 corner: FlexBox,
31 vertical_scrollbar: ScrollBar,
32 horizontal_scrollbar: ScrollBar,
33 vertical_visibility: Cell<ScrollBarVisibility>,
34 horizontal_visibility: Cell<ScrollBarVisibility>,
35 vertical_scroll_enabled: Cell<bool>,
36 horizontal_scroll_enabled: Cell<bool>,
37 scrollbar_gutter: Cell<f32>,
38 persist_scroll: Cell<bool>,
39 persisted_restore_pending: Cell<bool>,
40 subscriptions: RefCell<Vec<Subscription>>,
41}
42
43#[derive(Clone)]
44pub struct ScrollBox {
45 inner: Rc<ScrollBoxInner>,
46}
47
48impl ScrollBox {
49 pub fn new() -> Self {
50 Self::with_parts(ScrollState::new(), ScrollView::new())
51 }
52
53 pub fn with_parts(scroll_state: ScrollState, viewport: ScrollView) -> Self {
54 let root = column();
55 let viewport = viewport
56 .bind_scroll_state(scroll_state.clone())
57 .fill_size()
58 .clone();
59 let vertical_gutter = flex_box();
60 vertical_gutter
61 .width(DEFAULT_SCROLLBAR_GUTTER, Unit::Pixel)
62 .fill_height()
63 .on_pointer_down(|_event| {});
64 let vertical_scrollbar = ScrollBar::new(scroll_state.clone(), Orientation::Vertical);
65 let corner = flex_box();
66 corner
67 .width(0.0, Unit::Pixel)
68 .height(0.0, Unit::Pixel)
69 .on_pointer_down(|_event| {});
70 let horizontal_scrollbar = ScrollBar::new(scroll_state.clone(), Orientation::Horizontal);
71 let top_row = row();
72 top_row
73 .fill_size()
74 .on_pointer_down(|_event| {})
75 .child(&viewport)
76 .child(&vertical_gutter)
77 .child(&vertical_scrollbar.render());
78 let bottom_row = row();
79 bottom_row
80 .fill_width()
81 .on_pointer_down(|_event| {})
82 .child(&horizontal_scrollbar.render())
83 .child(&corner);
84 root.child(&top_row).child(&bottom_row);
85
86 let inner = Rc::new(ScrollBoxInner {
87 root,
88 scroll_state,
89 viewport,
90 top_row,
91 bottom_row,
92 vertical_gutter,
93 corner,
94 vertical_scrollbar,
95 horizontal_scrollbar,
96 vertical_visibility: Cell::new(ScrollBarVisibility::Auto),
97 horizontal_visibility: Cell::new(ScrollBarVisibility::Auto),
98 vertical_scroll_enabled: Cell::new(true),
99 horizontal_scroll_enabled: Cell::new(true),
100 scrollbar_gutter: Cell::new(DEFAULT_SCROLLBAR_GUTTER),
101 persist_scroll: Cell::new(true),
102 persisted_restore_pending: Cell::new(false),
103 subscriptions: RefCell::new(Vec::new()),
104 });
105 let scroll_box = Self { inner };
106 scroll_box.attach_listeners();
107 scroll_box.refresh_chrome();
108 scroll_box
109 }
110
111 pub fn scroll_state(&self) -> ScrollState {
112 self.inner.scroll_state.clone()
113 }
114
115 pub fn viewport(&self) -> ScrollView {
116 self.inner.viewport.clone()
117 }
118
119 pub fn vertical_scrollbar(&self) -> ScrollBar {
120 self.inner.vertical_scrollbar.clone()
121 }
122
123 pub fn horizontal_scrollbar(&self) -> ScrollBar {
124 self.inner.horizontal_scrollbar.clone()
125 }
126
127 pub fn scroll_enabled_x(&self, enabled: bool) -> &Self {
128 self.inner.horizontal_scroll_enabled.set(enabled);
129 self.inner.viewport.scroll_enabled_x(enabled);
130 self.refresh_chrome();
131 self
132 }
133
134 pub fn scroll_enabled_y(&self, enabled: bool) -> &Self {
135 self.inner.vertical_scroll_enabled.set(enabled);
136 self.inner.viewport.scroll_enabled_y(enabled);
137 self.refresh_chrome();
138 self
139 }
140
141 pub fn smooth_scrolling(&self, smooth_scrolling: bool) -> &Self {
142 self.inner.viewport.smooth_scrolling(smooth_scrolling);
143 self
144 }
145
146 pub fn persist_scroll(&self, persist: bool) -> &Self {
147 self.inner.persist_scroll.set(persist);
148 if !persist {
149 self.inner.persisted_restore_pending.set(false);
150 }
151 self
152 }
153
154 pub fn scroll_offset(&self, x: f32, y: f32) -> &Self {
155 self.inner.viewport.scroll_offset(x, y);
156 self
157 }
158
159 pub fn transitions(&self, transitions: Option<NodeTransitions>) -> &Self {
160 self.inner.viewport.transitions(transitions);
161 self
162 }
163
164 pub fn scroll_content_size(&self, width: f32, height: f32) -> &Self {
165 self.inner.viewport.scroll_content_size(width, height);
166 self
167 }
168
169 pub fn scroll_to(&self, x: f32, y: f32) -> &Self {
170 self.inner.viewport.scroll_to(x, y);
171 self
172 }
173
174 pub fn scroll_to_animated(&self, x: f32, y: f32, timing: AnimationTiming) -> &Self {
175 self.inner.viewport.scroll_to_animated(x, y, timing);
176 self
177 }
178
179 pub fn set_runtime_scroll_offset(&self, x: f32, y: f32) {
180 self.inner.viewport.set_runtime_scroll_offset(x, y);
181 }
182
183 pub fn vertical_scrollbar_visibility(&self, mode: ScrollBarVisibility) -> &Self {
184 self.inner.vertical_visibility.set(mode);
185 self.refresh_chrome();
186 self
187 }
188
189 pub fn horizontal_scrollbar_visibility(&self, mode: ScrollBarVisibility) -> &Self {
190 self.inner.horizontal_visibility.set(mode);
191 self.refresh_chrome();
192 self
193 }
194
195 pub fn scrollbar_gutter(&self, value: f32) -> &Self {
196 self.inner.scrollbar_gutter.set(value.max(0.0));
197 self.refresh_chrome();
198 self
199 }
200
201 pub fn scrollbar_style(&self, style: ScrollBarStyle) -> &Self {
202 style.apply_to(&self.inner.vertical_scrollbar);
203 style.apply_to(&self.inner.horizontal_scrollbar);
204 self.refresh_chrome();
205 self
206 }
207
208 fn attach_listeners(&self) {
209 let weak = Rc::downgrade(&self.inner);
210 self.inner
211 .vertical_scrollbar
212 .set_chrome_metric_changed_handler(move || {
213 if let Some(inner) = Weak::upgrade(&weak) {
214 ScrollBox { inner }.refresh_chrome();
215 }
216 });
217 let weak = Rc::downgrade(&self.inner);
218 self.inner
219 .horizontal_scrollbar
220 .set_chrome_metric_changed_handler(move || {
221 if let Some(inner) = Weak::upgrade(&weak) {
222 ScrollBox { inner }.refresh_chrome();
223 }
224 });
225
226 let weak = Rc::downgrade(&self.inner);
227 self.inner
228 .subscriptions
229 .borrow_mut()
230 .push(self.inner.scroll_state.subscribe_offset_x(move || {
231 if let Some(inner) = Weak::upgrade(&weak) {
232 let scroll_box = ScrollBox { inner };
233 scroll_box.store_persisted_scroll_offset_if_needed();
234 scroll_box.try_restore_persisted_scroll_offset();
235 }
236 }));
237 let weak = Rc::downgrade(&self.inner);
238 self.inner
239 .subscriptions
240 .borrow_mut()
241 .push(self.inner.scroll_state.subscribe_offset_y(move || {
242 if let Some(inner) = Weak::upgrade(&weak) {
243 let scroll_box = ScrollBox { inner };
244 scroll_box.store_persisted_scroll_offset_if_needed();
245 scroll_box.try_restore_persisted_scroll_offset();
246 }
247 }));
248 let weak = Rc::downgrade(&self.inner);
249 self.inner.subscriptions.borrow_mut().push(
250 self.inner.scroll_state.subscribe_content_width(move || {
251 if let Some(inner) = Weak::upgrade(&weak) {
252 let scroll_box = ScrollBox { inner };
253 scroll_box.refresh_chrome();
254 scroll_box.try_restore_persisted_scroll_offset();
255 }
256 }),
257 );
258 let weak = Rc::downgrade(&self.inner);
259 self.inner.subscriptions.borrow_mut().push(
260 self.inner.scroll_state.subscribe_content_height(move || {
261 if let Some(inner) = Weak::upgrade(&weak) {
262 let scroll_box = ScrollBox { inner };
263 scroll_box.refresh_chrome();
264 scroll_box.try_restore_persisted_scroll_offset();
265 }
266 }),
267 );
268 let weak = Rc::downgrade(&self.inner);
269 self.inner.subscriptions.borrow_mut().push(
270 self.inner.scroll_state.subscribe_viewport_width(move || {
271 if let Some(inner) = Weak::upgrade(&weak) {
272 let scroll_box = ScrollBox { inner };
273 scroll_box.refresh_chrome();
274 scroll_box.bind_scroll_chrome();
275 scroll_box.try_restore_persisted_scroll_offset();
276 }
277 }),
278 );
279 let weak = Rc::downgrade(&self.inner);
280 self.inner.subscriptions.borrow_mut().push(
281 self.inner.scroll_state.subscribe_viewport_height(move || {
282 if let Some(inner) = Weak::upgrade(&weak) {
283 let scroll_box = ScrollBox { inner };
284 scroll_box.refresh_chrome();
285 scroll_box.bind_scroll_chrome();
286 scroll_box.try_restore_persisted_scroll_offset();
287 }
288 }),
289 );
290 }
291
292 fn bind_scroll_chrome(&self) {
293 let viewport_handle = self.inner.viewport.handle().raw();
294 self.inner
295 .vertical_scrollbar
296 .bind_scroll_handle(viewport_handle);
297 self.inner
298 .horizontal_scrollbar
299 .bind_scroll_handle(viewport_handle);
300 self.inner
301 .root
302 .bind_scroll_proxy_target_handle(viewport_handle);
303 self.inner
304 .top_row
305 .bind_scroll_proxy_target_handle(viewport_handle);
306 self.inner
307 .bottom_row
308 .bind_scroll_proxy_target_handle(viewport_handle);
309 self.inner
310 .vertical_gutter
311 .bind_scroll_proxy_target_handle(viewport_handle);
312 self.inner
313 .corner
314 .bind_scroll_proxy_target_handle(viewport_handle);
315 self.bind_content_scroll_proxy_targets();
316 }
317
318 fn bind_content_scroll_proxy_targets(&self) {
319 let viewport_handle = self.inner.viewport.handle().raw();
320 let children = self.inner.viewport.retained_node_ref().children();
321 for child in children {
322 child.bind_scroll_proxy_target_handle(viewport_handle);
323 }
324 }
325
326 fn refresh_chrome(&self) {
327 let vertical_rail_thickness =
328 self.inner.scrollbar_gutter.get() + self.inner.vertical_scrollbar.thickness();
329 let horizontal_rail_thickness = self.inner.horizontal_scrollbar.thickness();
330 let show_vertical = self.should_show(
331 self.inner.vertical_visibility.get(),
332 self.inner.vertical_scroll_enabled.get(),
333 self.inner.scroll_state.content_height(),
334 self.inner.scroll_state.viewport_height(),
335 );
336 let show_horizontal = self.should_show(
337 self.inner.horizontal_visibility.get(),
338 self.inner.horizontal_scroll_enabled.get(),
339 self.inner.scroll_state.content_width(),
340 self.inner.scroll_state.viewport_width(),
341 );
342 self.inner.vertical_scrollbar.chrome_visible(show_vertical);
343 self.inner
344 .horizontal_scrollbar
345 .chrome_visible(show_horizontal);
346
347 self.inner.vertical_gutter.width(
348 if show_vertical {
349 self.inner.scrollbar_gutter.get()
350 } else {
351 0.0
352 },
353 Unit::Pixel,
354 );
355 self.inner.vertical_scrollbar.render().width(
356 if show_vertical {
357 self.inner.vertical_scrollbar.thickness()
358 } else {
359 0.0
360 },
361 Unit::Pixel,
362 );
363 let horizontal_rail_height = if show_horizontal {
364 horizontal_rail_thickness
365 } else {
366 0.0
367 };
368 self.inner
369 .bottom_row
370 .height(horizontal_rail_height, Unit::Pixel);
371 self.inner.horizontal_scrollbar.render().height(
372 if show_horizontal {
373 self.inner.horizontal_scrollbar.thickness()
374 } else {
375 0.0
376 },
377 Unit::Pixel,
378 );
379 let vertical_rail_width = if show_vertical {
380 vertical_rail_thickness
381 } else {
382 0.0
383 };
384 self.inner.corner.width(vertical_rail_width, Unit::Pixel);
385 self.inner
386 .corner
387 .height(horizontal_rail_height, Unit::Pixel);
388 }
389
390 fn should_show(
391 &self,
392 mode: ScrollBarVisibility,
393 enabled: bool,
394 content_size: f32,
395 viewport_size: f32,
396 ) -> bool {
397 if !enabled || mode == ScrollBarVisibility::Never {
398 return false;
399 }
400 if mode == ScrollBarVisibility::Always {
401 return true;
402 }
403 if viewport_size <= 0.0 || content_size <= 0.0 {
404 return false;
405 }
406 content_size > viewport_size + OVERFLOW_TOLERANCE
407 }
408
409 fn store_persisted_scroll_offset_if_needed(&self) {
410 if !self.inner.persist_scroll.get() {
411 return;
412 }
413 let Some(node_id) = self.inner.root.retained_node_ref().node_id() else {
414 return;
415 };
416 if node_id.is_empty() {
417 return;
418 }
419 store_scroll_offset(
420 &node_id,
421 self.inner.scroll_state.offset_x(),
422 self.inner.scroll_state.offset_y(),
423 );
424 }
425
426 fn try_restore_persisted_scroll_offset(&self) -> bool {
427 if !self.inner.persisted_restore_pending.get() || !self.inner.persist_scroll.get() {
428 return false;
429 }
430 let Some(node_id) = self.inner.root.retained_node_ref().node_id() else {
431 self.inner.persisted_restore_pending.set(false);
432 return false;
433 };
434 if node_id.is_empty() {
435 self.inner.persisted_restore_pending.set(false);
436 return false;
437 }
438 let Some(restored) = try_load_scroll_offset(&node_id) else {
439 self.inner.persisted_restore_pending.set(false);
440 return false;
441 };
442 let max_x = (self.inner.scroll_state.content_width()
443 - self.inner.scroll_state.viewport_width())
444 .max(0.0);
445 let max_y = (self.inner.scroll_state.content_height()
446 - self.inner.scroll_state.viewport_height())
447 .max(0.0);
448 let can_restore_x = !self.inner.horizontal_scroll_enabled.get()
449 || self.inner.scroll_state.viewport_width() > 0.0;
450 let can_restore_y = !self.inner.vertical_scroll_enabled.get()
451 || self.inner.scroll_state.viewport_height() > 0.0;
452 if !can_restore_x || !can_restore_y {
453 return false;
454 }
455 self.inner.persisted_restore_pending.set(false);
456 self.set_runtime_scroll_offset(
457 clamp(restored.x, 0.0, max_x),
458 clamp(restored.y, 0.0, max_y),
459 );
460 true
461 }
462}
463
464impl Default for ScrollBox {
465 fn default() -> Self {
466 Self::new()
467 }
468}
469
470impl Node for ScrollBox {
471 fn retained_node_ref(&self) -> NodeRef {
472 let scroll_box = self.clone();
473 self.inner
474 .root
475 .retained_node_ref()
476 .with_build_callback(move || scroll_box.build())
477 }
478
479 fn build(&self) {
480 self.ensure_handle();
481 self.inner.root.build_self();
482 self.build_children();
483 self.inner
484 .persisted_restore_pending
485 .set(self.inner.persist_scroll.get());
486 self.bind_scroll_chrome();
487 self.refresh_chrome();
488 let _ = self.try_restore_persisted_scroll_offset();
489 }
490
491 fn build_self(&self) {
492 self.inner.root.build_self();
493 }
494}
495
496impl HasFlexBoxRoot for ScrollBox {
497 fn flex_box_root(&self) -> &FlexBox {
498 &self.inner.root
499 }
500
501 fn append_flex_box_surface_child(&self, child: NodeRef) {
502 self.inner
503 .viewport
504 .retained_node_ref()
505 .append_child_ref(&child);
506 self.bind_content_scroll_proxy_targets();
507 }
508}
509
510impl ThemeBindable for ScrollBox {
511 fn theme_binding_node(&self) -> NodeRef {
512 self.inner.root.retained_node_ref()
513 }
514
515 fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
516 let weak = Rc::downgrade(&self.inner);
517 Box::new(move || {
518 Some(ScrollBox {
519 inner: weak.upgrade()?,
520 })
521 })
522 }
523}