1use super::*;
2
3impl Widget for ScrollView {
4 fn type_name(&self) -> &'static str {
5 "ScrollView"
6 }
7 fn bounds(&self) -> Rect {
8 self.bounds
9 }
10 fn set_bounds(&mut self, b: Rect) {
11 self.bounds = b;
12 }
13 fn children(&self) -> &[Box<dyn Widget>] {
14 &self.children
15 }
16 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
17 &mut self.children
18 }
19
20 fn needs_draw(&self) -> bool {
21 if !self.is_visible() {
22 return false;
23 }
24 self.scrollbar_animation_active()
25 || self.painted_style_epoch.get() != current_scroll_style_epoch()
26 || self.children().iter().any(|c| c.needs_draw())
27 }
28
29 fn try_scroll_to_lift(&mut self, amount: f64) -> f64 {
39 if !self.v.enabled || amount.abs() < 0.5 {
40 return 0.0;
41 }
42 let (_, vh) = self.viewport();
43 let max = self.v.max_scroll(vh);
44 let before = self.v.offset;
45 let target = (before + amount).clamp(0.0, max);
46 let applied = target - before;
47 if applied.abs() < 0.5 {
48 return 0.0;
49 }
50 self.v.offset = target;
51 self.publish_offsets();
52 crate::animation::request_draw();
53 applied
54 }
55
56 fn margin(&self) -> Insets {
57 self.base.margin
58 }
59 fn widget_base(&self) -> Option<&WidgetBase> {
60 Some(&self.base)
61 }
62 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
63 Some(&mut self.base)
64 }
65 fn h_anchor(&self) -> HAnchor {
66 self.base.h_anchor
67 }
68 fn v_anchor(&self) -> VAnchor {
69 self.base.v_anchor
70 }
71 fn min_size(&self) -> Size {
72 self.base.min_size
73 }
74 fn max_size(&self) -> Size {
75 self.base.max_size
76 }
77
78 fn measure_min_height(&self, available_w: f64) -> f64 {
90 self.children
91 .first()
92 .map(|c| c.measure_min_height(available_w))
93 .unwrap_or(self.base.min_size.height)
94 }
95
96 fn hit_test(&self, local_pos: Point) -> bool {
97 if self.v.dragging || self.h.dragging || self.middle_dragging {
98 return true;
99 }
100 let b = self.bounds();
101 local_pos.x >= 0.0
102 && local_pos.x <= b.width
103 && local_pos.y >= 0.0
104 && local_pos.y <= b.height
105 }
106
107 fn claims_pointer_exclusively(&self, local_pos: Point) -> bool {
108 if self.v.dragging || self.h.dragging || self.middle_dragging {
109 return true;
110 }
111 let (vw, vh) = self.viewport();
112 if self.v.enabled && self.v.content > vh && self.pos_in_v_hover(local_pos) {
113 return true;
114 }
115 if self.h.enabled && self.h.content > vw && self.pos_in_h_hover(local_pos) {
116 return true;
117 }
118 false
119 }
120
121 fn layout(&mut self, available: Size) -> Size {
122 if let Some(c) = &self.offset_cell {
124 self.v.offset = c.get();
125 }
126 if let Some(c) = &self.h_offset_cell {
127 self.h.offset = c.get();
128 }
129 if let Some(c) = &self.visibility_cell {
130 self.bar_visibility = c.get();
131 } else if !self.visibility_explicit {
132 self.bar_visibility = current_scroll_visibility();
133 }
134 if let Some(c) = &self.style_cell {
135 self.style = c.get();
136 } else if !self.style_explicit {
137 self.style = current_scroll_style();
140 }
141
142 self.bounds = Rect::new(0.0, 0.0, available.width, available.height);
143
144 let (vw_guess, _vh_guess) = self.viewport();
148 let child_in_w = if self.h.enabled {
149 f64::MAX / 2.0
150 } else {
151 vw_guess
152 };
153 let child_in_h = f64::MAX / 2.0;
154
155 if let Some(child) = self.children.first_mut() {
156 let natural = child.layout(Size::new(child_in_w, child_in_h));
157 self.v.content = natural.height;
158 self.h.content = if self.h.enabled {
159 natural.width
160 } else {
161 vw_guess
162 };
163 }
164
165 let (vw, vh) = self.viewport();
168
169 if self.stick_to_bottom && self.was_at_bottom {
170 self.v.offset = self.v.max_scroll(vh);
171 }
172 self.clamp_offsets();
173 self.was_at_bottom = (self.v.max_scroll(vh) - self.v.offset).abs() < 0.5;
174
175 if let Some(c) = &self.offset_cell {
177 c.set(self.v.offset);
178 }
179 if let Some(c) = &self.max_scroll_cell {
180 c.set(self.v.max_scroll(vh));
181 }
182 if let Some(c) = &self.h_offset_cell {
183 c.set(self.h.offset);
184 }
185 if let Some(c) = &self.h_max_scroll_cell {
186 c.set(self.h.max_scroll(vw));
187 }
188 if let Some(c) = &self.viewport_cell {
189 c.set(Rect::new(self.h.offset, self.v.offset, vw, vh));
196 }
197
198 if let Some(child) = self.children.first_mut() {
200 let child_y = vh - self.v.content + self.v.offset;
201 let child_x = -self.h.offset;
202 child.set_bounds(Rect::new(
203 child_x.round(),
204 child_y.round(),
205 if self.h.enabled { self.h.content } else { vw },
206 self.v.content,
207 ));
208 }
209
210 available
211 }
212
213 fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
214
215 fn clip_children_rect(&self) -> Option<(f64, f64, f64, f64)> {
216 let (vw, vh) = self.viewport();
219 Some((0.0, self.bounds.height - vh, vw, vh))
220 }
221
222 fn paint_overlay(&mut self, ctx: &mut dyn DrawCtx) {
223 self.painted_style_epoch.set(current_scroll_style_epoch());
224
225 if self.style.fade_strength > 0.001 && self.style.fade_size > 0.5 {
230 self.paint_fade(ctx);
231 }
232
233 let (_, vh) = self.viewport();
235 let v_geom = self.v_scrollbar_geometry();
236 if let Some(bar) = self
237 .v
238 .prepare_paint(vh, self.style, self.bar_visibility, v_geom)
239 {
240 paint_prepared_scrollbar(ctx, bar);
241 }
242
243 let (vw, _) = self.viewport();
245 let h_geom = self.h_scrollbar_geometry();
246 if let Some(bar) = self
247 .h
248 .prepare_paint(vw, self.style, self.bar_visibility, h_geom)
249 {
250 paint_prepared_scrollbar(ctx, bar);
251 }
252 }
253
254 fn on_event(&mut self, event: &Event) -> EventResult {
255 match event {
256 Event::MouseWheel {
258 delta_y, delta_x, ..
259 } => {
260 let mut consumed = false;
264 if self.v.enabled {
265 self.v.offset = self.v.offset - delta_y * 40.0;
266 consumed = true;
267 }
268 if self.h.enabled {
269 self.h.offset = self.h.offset - delta_x * 40.0;
270 consumed = true;
271 }
272 self.clamp_offsets();
273 let (_, vh) = self.viewport();
274 self.was_at_bottom = (self.v.max_scroll(vh) - self.v.offset).abs() < 0.5;
275 if let Some(c) = &self.offset_cell {
276 c.set(self.v.offset);
277 }
278 if let Some(c) = &self.h_offset_cell {
279 c.set(self.h.offset);
280 }
281 if consumed {
282 crate::animation::request_draw();
283 EventResult::Consumed
284 } else {
285 EventResult::Ignored
286 }
287 }
288
289 Event::MouseMove { pos } => {
291 if self.middle_dragging {
292 let world = crate::widget::current_mouse_world().unwrap_or(*pos);
293 let dx = world.x - self.middle_start_world.x;
294 let dy = world.y - self.middle_start_world.y;
295 if self.h.enabled {
296 self.h.offset = self.middle_start_h_offset - dx;
297 }
298 if self.v.enabled {
299 self.v.offset = self.middle_start_v_offset + dy;
300 }
301 self.clamp_offsets();
302 let (_, vh) = self.viewport();
303 self.was_at_bottom = (self.v.max_scroll(vh) - self.v.offset).abs() < 0.5;
304 self.publish_offsets();
305 crate::animation::request_draw();
306 return EventResult::Consumed;
307 }
308
309 let (vw, vh) = self.viewport();
310 let v_scroll = self.v.enabled && self.v.content > vh;
311 let h_scroll = self.h.enabled && self.h.content > vw;
312 let v_hover_changed =
313 self.v
314 .update_hover(*pos, vh, self.style, self.v_scrollbar_geometry());
315 let h_hover_changed =
316 self.h
317 .update_hover(*pos, vw, self.style, self.h_scrollbar_geometry());
318 if (v_scroll && v_hover_changed) || (h_scroll && h_hover_changed) {
319 crate::animation::request_draw();
320 }
321
322 if self.v.dragging {
323 if self
324 .v
325 .drag_to(*pos, vh, self.style, self.v_scrollbar_geometry())
326 {
327 self.was_at_bottom = (self.v.max_scroll(vh) - self.v.offset).abs() < 0.5;
328 if let Some(c) = &self.offset_cell {
329 c.set(self.v.offset);
330 }
331 crate::animation::request_draw();
332 }
333 return EventResult::Consumed;
334 }
335 if self.h.dragging {
336 if self
337 .h
338 .drag_to(*pos, vw, self.style, self.h_scrollbar_geometry())
339 {
340 if let Some(c) = &self.h_offset_cell {
341 c.set(self.h.offset);
342 }
343 crate::animation::request_draw();
344 }
345 return EventResult::Consumed;
346 }
347 EventResult::Ignored
348 }
349
350 Event::MouseDown {
352 pos,
353 button: MouseButton::Middle,
354 ..
355 } => {
356 let (vw, vh) = self.viewport();
357 if (self.v.enabled && self.v.content > vh)
358 || (self.h.enabled && self.h.content > vw)
359 {
360 self.middle_dragging = true;
361 self.middle_start_world = crate::widget::current_mouse_world().unwrap_or(*pos);
362 self.middle_start_v_offset = self.v.offset;
363 self.middle_start_h_offset = self.h.offset;
364 crate::animation::request_draw();
365 return EventResult::Consumed;
366 }
367 EventResult::Ignored
368 }
369
370 Event::MouseDown {
371 pos,
372 button: MouseButton::Left,
373 ..
374 } => {
375 let (vw, vh) = self.viewport();
376 let v_scroll = self.v.enabled && self.v.content > vh;
377 let h_scroll = self.h.enabled && self.h.content > vw;
378
379 if v_scroll && self.pos_in_v_hover(*pos) {
380 if self
381 .v
382 .begin_drag(*pos, vh, self.style, self.v_scrollbar_geometry())
383 {
384 } else if self
387 .v
388 .page_at(*pos, vh, self.style, self.v_scrollbar_geometry())
389 {
390 if let Some(c) = &self.offset_cell {
391 c.set(self.v.offset);
392 }
393 crate::animation::request_draw();
395 }
396 return EventResult::Consumed;
397 }
398 if h_scroll && self.pos_in_h_hover(*pos) {
399 if self
400 .h
401 .begin_drag(*pos, vw, self.style, self.h_scrollbar_geometry())
402 {
403 } else if self
405 .h
406 .page_at(*pos, vw, self.style, self.h_scrollbar_geometry())
407 {
408 if let Some(c) = &self.h_offset_cell {
409 c.set(self.h.offset);
410 }
411 crate::animation::request_draw();
412 }
413 return EventResult::Consumed;
414 }
415 EventResult::Ignored
416 }
417
418 Event::MouseUp { button, .. } => {
420 let was = self.v.dragging
421 || self.h.dragging
422 || (*button == MouseButton::Middle && self.middle_dragging);
423 self.v.dragging = false;
424 self.h.dragging = false;
425 if *button == MouseButton::Middle {
426 self.middle_dragging = false;
427 }
428 if was {
429 crate::animation::request_draw();
430 EventResult::Consumed
431 } else {
432 EventResult::Ignored
433 }
434 }
435
436 _ => EventResult::Ignored,
437 }
438 }
439
440 fn properties(&self) -> Vec<(&'static str, String)> {
444 let (vw, vh) = self.viewport();
445 vec![
446 ("v_enabled", self.v.enabled.to_string()),
447 ("h_enabled", self.h.enabled.to_string()),
448 ("bar_visibility", format!("{:?}", self.bar_visibility)),
449 ("v_offset", format!("{:.1}", self.v.offset)),
450 ("h_offset", format!("{:.1}", self.h.offset)),
451 ("max_scroll", format!("{:.1}", self.v.max_scroll(vh))),
452 ("h_max_scroll", format!("{:.1}", self.h.max_scroll(vw))),
453 ("v_content", format!("{:.1}", self.v.content)),
454 ("h_content", format!("{:.1}", self.h.content)),
455 ]
456 }
457}
458
459impl ScrollView {
460 fn paint_fade(&self, ctx: &mut dyn DrawCtx) {
465 let v = ctx.visuals();
466 let c = self.fade_color.unwrap_or(v.window_fill);
473 let (vw, vh) = self.viewport();
474 let strength = self.style.fade_strength.clamp(0.0, 1.0) as f32;
475 let size = self.style.fade_size.max(0.0);
476 let max_a = strength;
477
478 if self.v.enabled {
480 if self.v.offset > 0.5 {
481 Self::fill_v_gradient(
483 ctx,
484 c,
485 max_a,
486 0.0,
487 self.bounds.height - size,
488 vw,
489 size,
490 false,
491 );
492 }
493 if (self.v.max_scroll(vh) - self.v.offset) > 0.5 {
494 let y_bottom = self.bounds.height - vh;
496 Self::fill_v_gradient(ctx, c, max_a, 0.0, y_bottom, vw, size, true);
497 }
498 }
499 if self.h.enabled {
500 if self.h.offset > 0.5 {
501 Self::fill_h_gradient(ctx, c, max_a, 0.0, self.bounds.height - vh, size, vh, true);
503 }
504 if (self.h.max_scroll(vw) - self.h.offset) > 0.5 {
505 Self::fill_h_gradient(
507 ctx,
508 c,
509 max_a,
510 vw - size,
511 self.bounds.height - vh,
512 size,
513 vh,
514 false,
515 );
516 }
517 }
518 }
519
520 fn fill_v_gradient(
526 ctx: &mut dyn DrawCtx,
527 c: Color,
528 max_alpha: f32,
529 x: f64,
530 y: f64,
531 w: f64,
532 h: f64,
533 opaque_at_bottom: bool,
534 ) {
535 const STEPS: usize = 64;
536 let strip_h = h / STEPS as f64;
537 for i in 0..STEPS {
538 let t = (i as f32 + 0.5) / STEPS as f32;
540 let a = if opaque_at_bottom { 1.0 - t } else { t };
541 ctx.set_fill_color(Color::rgba(c.r, c.g, c.b, a * max_alpha));
542 ctx.begin_path();
543 ctx.rect(x, y + i as f64 * strip_h, w, strip_h + 0.5);
544 ctx.fill();
545 }
546 }
547
548 fn fill_h_gradient(
554 ctx: &mut dyn DrawCtx,
555 c: Color,
556 max_alpha: f32,
557 x: f64,
558 y: f64,
559 w: f64,
560 h: f64,
561 opaque_at_left: bool,
562 ) {
563 const STEPS: usize = 64;
564 let strip_w = w / STEPS as f64;
565 for i in 0..STEPS {
566 let t = (i as f32 + 0.5) / STEPS as f32;
567 let a = if opaque_at_left { 1.0 - t } else { t };
568 ctx.set_fill_color(Color::rgba(c.r, c.g, c.b, a * max_alpha));
569 ctx.begin_path();
570 ctx.rect(x + i as f64 * strip_w, y, strip_w + 0.5, h);
571 ctx.fill();
572 }
573 }
574}