1use std::cell::Cell;
21use std::rc::Rc;
22
23use crate::color::Color;
24use crate::event::{Event, EventResult, MouseButton};
25use crate::geometry::{Point, Rect, Size};
26use crate::draw_ctx::DrawCtx;
27use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
28use crate::widget::Widget;
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum ScrollBarVisibility {
38 AlwaysVisible,
40 VisibleWhenNeeded,
43 AlwaysHidden,
45}
46
47impl Default for ScrollBarVisibility {
48 fn default() -> Self { Self::VisibleWhenNeeded }
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub enum ScrollBarKind { Solid, Floating }
54
55impl Default for ScrollBarKind {
56 fn default() -> Self { Self::Floating }
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum ScrollBarColor {
62 Background,
64 Foreground,
66}
67
68impl Default for ScrollBarColor {
69 fn default() -> Self { Self::Background }
70}
71
72#[derive(Clone, Copy, Debug, PartialEq)]
74pub struct ScrollBarStyle {
75 pub bar_width: f64,
78 pub floating_width: f64,
83 pub handle_min_length: f64,
85 pub outer_margin: f64,
87 pub inner_margin: f64,
89 pub content_margin: f64,
92 pub margin_same: bool,
95 pub kind: ScrollBarKind,
97 pub color: ScrollBarColor,
99 pub fade_strength: f64,
101 pub fade_size: f64,
103}
104
105impl ScrollBarStyle {
106 pub fn bar_width_at(&self, t: f64) -> f64 {
115 if self.kind == ScrollBarKind::Solid {
116 return self.bar_width;
117 }
118 let from = self.floating_width.min(self.bar_width);
119 let t = t.clamp(0.0, 1.0);
120 from + (self.bar_width - from) * t
121 }
122}
123
124impl Default for ScrollBarStyle {
125 fn default() -> Self {
126 Self {
127 bar_width: 15.0,
128 floating_width: 15.0,
129 handle_min_length: 10.0,
130 outer_margin: 5.0,
131 inner_margin: 7.0,
132 content_margin: 5.0,
133 margin_same: true,
134 kind: ScrollBarKind::default(),
135 color: ScrollBarColor::default(),
136 fade_strength: 1.0,
137 fade_size: 45.0,
138 }
139 }
140}
141
142impl ScrollBarStyle {
143 pub fn solid() -> Self {
147 Self {
148 bar_width: 8.0,
149 floating_width: 8.0,
150 handle_min_length: 12.0,
151 outer_margin: 0.0,
152 inner_margin: 4.0,
153 content_margin: 0.0,
154 margin_same: true,
155 kind: ScrollBarKind::Solid,
156 color: ScrollBarColor::Foreground,
157 fade_strength: 0.0,
158 fade_size: 0.0,
159 }
160 }
161 pub fn thin() -> Self {
168 Self {
169 bar_width: 10.0,
170 floating_width: 4.0,
171 handle_min_length: 12.0,
172 outer_margin: 2.0,
173 inner_margin: 2.0,
174 content_margin: 0.0,
175 margin_same: true,
176 kind: ScrollBarKind::Floating,
177 color: ScrollBarColor::Background,
178 fade_strength: 0.0,
179 fade_size: 0.0,
180 }
181 }
182 pub fn floating() -> Self {
185 Self::default()
186 }
187}
188
189std::thread_local! {
197 static CURRENT_SCROLL_STYLE: Cell<ScrollBarStyle> = Cell::new(ScrollBarStyle::default());
198 static CURRENT_SCROLL_VISIBILITY: Cell<ScrollBarVisibility> = Cell::new(ScrollBarVisibility::VisibleWhenNeeded);
199}
200
201pub fn current_scroll_style() -> ScrollBarStyle {
203 CURRENT_SCROLL_STYLE.with(|c| c.get())
204}
205
206pub fn set_scroll_style(s: ScrollBarStyle) {
209 CURRENT_SCROLL_STYLE.with(|c| c.set(s));
210}
211
212pub fn current_scroll_visibility() -> ScrollBarVisibility {
214 CURRENT_SCROLL_VISIBILITY.with(|c| c.get())
215}
216
217pub fn set_scroll_visibility(v: ScrollBarVisibility) {
221 CURRENT_SCROLL_VISIBILITY.with(|c| c.set(v));
222}
223
224fn scale_alpha(c: Color, a: f64) -> Color {
230 Color::rgba(c.r, c.g, c.b, c.a * (a as f32).clamp(0.0, 1.0))
231}
232
233const RIGHT_EDGE_GUARD: f64 = 4.0;
237const BOTTOM_EDGE_GUARD: f64 = 4.0;
239const GRAB_MARGIN: f64 = 6.0;
241
242#[derive(Clone, Copy)]
248struct AxisState {
249 enabled: bool,
250 offset: f64,
251 content: f64,
252 hovered_bar: bool,
253 hovered_thumb: bool,
254 dragging: bool,
255 drag_thumb_offset: f64,
256 hover_anim: crate::animation::Tween,
257 visibility_anim: crate::animation::Tween,
262}
263
264impl Default for AxisState {
265 fn default() -> Self {
266 Self {
267 enabled: false, offset: 0.0, content: 0.0,
268 hovered_bar: false, hovered_thumb: false, dragging: false,
269 drag_thumb_offset: 0.0,
270 hover_anim: crate::animation::Tween::new(0.0, 0.12),
271 visibility_anim: crate::animation::Tween::new(0.0, 0.18),
272 }
273 }
274}
275
276impl AxisState {
277 fn max_scroll(&self, viewport: f64) -> f64 {
278 (self.content - viewport).max(0.0)
279 }
280
281 fn interact(&self) -> bool {
283 self.hovered_bar || self.hovered_thumb || self.dragging
284 }
285}
286
287pub struct ScrollView {
288 bounds: Rect,
289 children: Vec<Box<dyn Widget>>, base: WidgetBase,
291
292 v: AxisState,
293 h: AxisState,
294
295 stick_to_bottom: bool,
298 was_at_bottom: bool,
299
300 bar_visibility: ScrollBarVisibility,
302 visibility_explicit: bool,
307 style: ScrollBarStyle,
308 style_explicit: bool,
312
313 offset_cell: Option<Rc<Cell<f64>>>,
315 max_scroll_cell: Option<Rc<Cell<f64>>>,
316 visibility_cell: Option<Rc<Cell<ScrollBarVisibility>>>,
317 style_cell: Option<Rc<Cell<ScrollBarStyle>>>,
318 viewport_cell: Option<Rc<Cell<Rect>>>,
321}
322
323impl ScrollView {
324 pub fn new(content: Box<dyn Widget>) -> Self {
325 Self {
326 bounds: Rect::default(),
327 children: vec![content],
328 base: WidgetBase::new(),
329 v: AxisState { enabled: true, ..AxisState::default() },
330 h: AxisState::default(),
331 stick_to_bottom: false,
332 was_at_bottom: false,
333 bar_visibility: current_scroll_visibility(),
334 visibility_explicit: false,
335 style: current_scroll_style(),
336 style_explicit: false,
337 offset_cell: None,
338 max_scroll_cell: None,
339 visibility_cell: None,
340 style_cell: None,
341 viewport_cell: None,
342 }
343 }
344
345 pub fn horizontal(mut self, enabled: bool) -> Self {
348 self.h.enabled = enabled; self
349 }
350 pub fn vertical(mut self, enabled: bool) -> Self {
351 self.v.enabled = enabled; self
352 }
353
354 pub fn scroll_offset(&self) -> f64 { self.v.offset }
357
358 pub fn set_scroll_offset(&mut self, offset: f64) {
359 self.v.offset = offset;
360 if let Some(c) = &self.offset_cell { c.set(offset); }
361 }
362
363 pub fn max_scroll_value(&self) -> f64 { self.v.max_scroll(self.bounds.height) }
364
365 pub fn with_offset_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
366 self.offset_cell = Some(cell); self
367 }
368
369 pub fn with_max_scroll_cell(mut self, cell: Rc<Cell<f64>>) -> Self {
370 self.max_scroll_cell = Some(cell); self
371 }
372
373 pub fn with_stick_to_bottom(mut self, stick: bool) -> Self {
374 self.stick_to_bottom = stick; self
375 }
376
377 pub fn with_bar_visibility(mut self, v: ScrollBarVisibility) -> Self {
378 self.bar_visibility = v;
379 self.visibility_explicit = true;
380 self
381 }
382
383 pub fn set_bar_visibility(&mut self, v: ScrollBarVisibility) {
384 self.bar_visibility = v;
385 self.visibility_explicit = true;
386 }
387
388 pub fn with_bar_visibility_cell(mut self, cell: Rc<Cell<ScrollBarVisibility>>) -> Self {
389 self.visibility_cell = Some(cell); self
390 }
391
392 pub fn with_style(mut self, s: ScrollBarStyle) -> Self {
393 self.style = s;
394 self.style_explicit = true;
395 self
396 }
397
398 pub fn with_style_cell(mut self, cell: Rc<Cell<ScrollBarStyle>>) -> Self {
399 self.style_cell = Some(cell); self
400 }
401
402 pub fn with_viewport_cell(mut self, cell: Rc<Cell<Rect>>) -> Self {
404 self.viewport_cell = Some(cell); self
405 }
406
407 fn viewport(&self) -> (f64, f64) {
410 let (reserve_x, reserve_y) = self.bar_reserve();
412 let w = (self.bounds.width - reserve_x).max(0.0);
413 let h = (self.bounds.height - reserve_y).max(0.0);
414 (w, h)
415 }
416
417 fn bar_reserve(&self) -> (f64, f64) {
419 if self.style.kind != ScrollBarKind::Solid {
420 return (0.0, 0.0);
421 }
422 let span = self.style.bar_width
423 + self.style.outer_margin
424 + self.style.inner_margin;
425 let rx = if self.h.enabled && self.h.content > self.bounds.width { 0.0 } else { 0.0 };
426 let need_v = self.v.enabled && self.v.content > self.bounds.height - self.h_bar_thickness();
429 let need_h = self.h.enabled && self.h.content > self.bounds.width - self.v_bar_thickness();
430 let rx = rx + if need_v { span } else { 0.0 };
431 let ry = if need_h { span } else { 0.0 };
432 (rx, ry)
433 }
434
435 fn v_bar_thickness(&self) -> f64 {
438 self.style.bar_width + self.style.outer_margin + self.style.inner_margin
439 }
440 fn h_bar_thickness(&self) -> f64 {
441 self.style.bar_width + self.style.outer_margin + self.style.inner_margin
442 }
443
444 fn v_bar_right(&self) -> f64 {
446 self.bounds.width - RIGHT_EDGE_GUARD - self.style.outer_margin
447 }
448 fn h_bar_bottom(&self) -> f64 {
451 BOTTOM_EDGE_GUARD + self.style.outer_margin
452 }
453
454 fn v_track_range(&self) -> (f64, f64) {
457 let (_, reserve_y) = self.bar_reserve();
458 let lo = self.style.inner_margin + reserve_y;
459 let hi = (self.bounds.height - self.style.inner_margin).max(lo);
460 (lo, hi)
461 }
462
463 fn h_track_range(&self) -> (f64, f64) {
464 let (reserve_x, _) = self.bar_reserve();
465 let lo = self.style.inner_margin;
466 let hi = (self.bounds.width - self.style.inner_margin - reserve_x).max(lo);
467 (lo, hi)
468 }
469
470 fn v_thumb_metrics(&self) -> Option<(f64, f64)> {
472 let (_, vh) = self.viewport();
473 if self.v.content <= vh { return None; }
474 let (lo, hi) = self.v_track_range();
475 let track_h = hi - lo;
476 let ratio = vh / self.v.content;
477 let thumb_h = (track_h * ratio).max(self.style.handle_min_length);
478 let travel = (track_h - thumb_h).max(0.0);
479 let max_s = self.v.max_scroll(vh);
480 let thumb_y = if max_s > 0.0 {
481 lo + travel * (1.0 - self.v.offset / max_s)
482 } else { lo + travel };
483 Some((thumb_y, thumb_h))
484 }
485
486 fn h_thumb_metrics(&self) -> Option<(f64, f64)> {
488 let (vw, _) = self.viewport();
489 if self.h.content <= vw { return None; }
490 let (lo, hi) = self.h_track_range();
491 let track_w = hi - lo;
492 let ratio = vw / self.h.content;
493 let thumb_w = (track_w * ratio).max(self.style.handle_min_length);
494 let travel = (track_w - thumb_w).max(0.0);
495 let max_s = self.h.max_scroll(vw);
496 let thumb_x = if max_s > 0.0 {
497 lo + travel * (self.h.offset / max_s)
498 } else { lo };
499 Some((thumb_x, thumb_w))
500 }
501
502 fn pos_on_v_thumb(&self, pos: Point) -> bool {
503 let bar_right = self.v_bar_right();
504 let bar_left = bar_right - self.style.bar_width;
505 let hit_left = bar_left - GRAB_MARGIN;
506 if pos.x < hit_left || pos.x >= bar_right { return false; }
507 if let Some((ty, th)) = self.v_thumb_metrics() {
508 pos.y >= ty && pos.y <= ty + th
509 } else { false }
510 }
511
512 fn pos_on_h_thumb(&self, pos: Point) -> bool {
513 let bar_bottom = self.h_bar_bottom();
514 let bar_top = bar_bottom + self.style.bar_width;
515 let hit_top = bar_top + GRAB_MARGIN;
516 if pos.y < bar_bottom || pos.y >= hit_top { return false; }
517 if let Some((tx, tw)) = self.h_thumb_metrics() {
518 pos.x >= tx && pos.x <= tx + tw
519 } else { false }
520 }
521
522 fn pos_in_v_hover(&self, pos: Point) -> bool {
523 let bar_right = self.v_bar_right();
524 let bar_left = bar_right - self.style.bar_width - GRAB_MARGIN;
525 pos.x >= bar_left && pos.x < bar_right
526 }
527
528 fn pos_in_h_hover(&self, pos: Point) -> bool {
529 let bar_bottom = self.h_bar_bottom();
530 let bar_top = bar_bottom + self.style.bar_width + GRAB_MARGIN;
531 pos.y >= bar_bottom && pos.y < bar_top
532 }
533
534 fn clamp_offsets(&mut self) {
535 let (vw, vh) = self.viewport();
536 self.v.offset = self.v.offset.clamp(0.0, self.v.max_scroll(vh)).round();
537 self.h.offset = self.h.offset.clamp(0.0, self.h.max_scroll(vw)).round();
538 }
539
540 pub fn with_margin(mut self, m: Insets) -> Self { self.base.margin = m; self }
543 pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
544 pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
545 pub fn with_min_size(mut self, s: Size) -> Self { self.base.min_size = s; self }
546 pub fn with_max_size(mut self, s: Size) -> Self { self.base.max_size = s; self }
547
548 fn should_paint_v(&self) -> bool {
551 let (_, vh) = self.viewport();
552 if self.v.content <= vh { return false; }
553 let floating = self.style.kind == ScrollBarKind::Floating;
554 match self.bar_visibility {
555 ScrollBarVisibility::AlwaysHidden => false,
556 ScrollBarVisibility::AlwaysVisible => true,
557 ScrollBarVisibility::VisibleWhenNeeded =>
561 !floating || self.v.hovered_bar || self.v.dragging,
562 }
563 }
564
565 fn should_paint_h(&self) -> bool {
566 let (vw, _) = self.viewport();
567 if self.h.content <= vw { return false; }
568 let floating = self.style.kind == ScrollBarKind::Floating;
569 match self.bar_visibility {
570 ScrollBarVisibility::AlwaysHidden => false,
571 ScrollBarVisibility::AlwaysVisible => true,
572 ScrollBarVisibility::VisibleWhenNeeded =>
573 !floating || self.h.hovered_bar || self.h.dragging,
574 }
575 }
576}
577
578impl Widget for ScrollView {
579 fn type_name(&self) -> &'static str { "ScrollView" }
580 fn bounds(&self) -> Rect { self.bounds }
581 fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
582 fn children(&self) -> &[Box<dyn Widget>] { &self.children }
583 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
584
585 fn margin(&self) -> Insets { self.base.margin }
586 fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
587 fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
588 fn min_size(&self) -> Size { self.base.min_size }
589 fn max_size(&self) -> Size { self.base.max_size }
590
591 fn hit_test(&self, local_pos: Point) -> bool {
592 if self.v.dragging || self.h.dragging { return true; }
593 let b = self.bounds();
594 local_pos.x >= 0.0 && local_pos.x <= b.width
595 && local_pos.y >= 0.0 && local_pos.y <= b.height
596 }
597
598 fn claims_pointer_exclusively(&self, local_pos: Point) -> bool {
599 if self.v.dragging || self.h.dragging { return true; }
600 let (vw, vh) = self.viewport();
601 if self.v.enabled && self.v.content > vh && self.pos_in_v_hover(local_pos) { return true; }
602 if self.h.enabled && self.h.content > vw && self.pos_in_h_hover(local_pos) { return true; }
603 false
604 }
605
606 fn layout(&mut self, available: Size) -> Size {
607 if let Some(c) = &self.offset_cell { self.v.offset = c.get(); }
609 if let Some(c) = &self.visibility_cell {
610 self.bar_visibility = c.get();
611 } else if !self.visibility_explicit {
612 self.bar_visibility = current_scroll_visibility();
613 }
614 if let Some(c) = &self.style_cell {
615 self.style = c.get();
616 } else if !self.style_explicit {
617 self.style = current_scroll_style();
620 }
621
622 self.bounds = Rect::new(0.0, 0.0, available.width, available.height);
623
624 let (vw_guess, _vh_guess) = self.viewport();
628 let child_in_w = if self.h.enabled { f64::MAX / 2.0 } else { vw_guess };
629 let child_in_h = f64::MAX / 2.0;
630
631 if let Some(child) = self.children.first_mut() {
632 let natural = child.layout(Size::new(child_in_w, child_in_h));
633 self.v.content = natural.height;
634 self.h.content = if self.h.enabled { natural.width } else { vw_guess };
635 }
636
637 let (vw, vh) = self.viewport();
640
641 if self.stick_to_bottom && self.was_at_bottom {
642 self.v.offset = self.v.max_scroll(vh);
643 }
644 self.clamp_offsets();
645 self.was_at_bottom = (self.v.max_scroll(vh) - self.v.offset).abs() < 0.5;
646
647 if let Some(c) = &self.offset_cell { c.set(self.v.offset); }
649 if let Some(c) = &self.max_scroll_cell { c.set(self.v.max_scroll(vh)); }
650 if let Some(c) = &self.viewport_cell {
651 c.set(Rect::new(self.h.offset, self.v.offset, vw, vh));
658 }
659
660 if let Some(child) = self.children.first_mut() {
662 let child_y = vh - self.v.content + self.v.offset;
663 let child_x = -self.h.offset;
664 child.set_bounds(Rect::new(
665 child_x.round(), child_y.round(),
666 if self.h.enabled { self.h.content } else { vw },
667 self.v.content,
668 ));
669 }
670
671 available
672 }
673
674 fn paint(&mut self, _ctx: &mut dyn DrawCtx) {}
675
676 fn clip_children_rect(&self) -> Option<(f64, f64, f64, f64)> {
677 let (vw, vh) = self.viewport();
680 Some((0.0, self.bounds.height - vh, vw, vh))
681 }
682
683 fn paint_overlay(&mut self, ctx: &mut dyn DrawCtx) {
684 let v = ctx.visuals();
685
686 self.v.visibility_anim.set_target(if self.should_paint_v() { 1.0 } else { 0.0 });
693 self.h.visibility_anim.set_target(if self.should_paint_h() { 1.0 } else { 0.0 });
694 let v_alpha = self.v.visibility_anim.tick();
695 let h_alpha = self.h.visibility_anim.tick();
696
697 let paint_v = self.v.enabled && self.v.content > self.viewport().1 && v_alpha > 0.001;
701 let paint_h = self.h.enabled && self.h.content > self.viewport().0 && h_alpha > 0.001;
702
703 let track_color_base = match self.style.color {
704 ScrollBarColor::Background => v.scroll_track,
705 ScrollBarColor::Foreground => Color::rgba(
706 v.accent.r, v.accent.g, v.accent.b, 0.08),
707 };
708 let thumb_idle = match self.style.color {
709 ScrollBarColor::Background => v.scroll_thumb,
710 ScrollBarColor::Foreground => v.accent,
711 };
712
713 if paint_v {
715 if let Some((ty, th)) = self.v_thumb_metrics() {
716 let bar_right = self.v_bar_right();
717 self.v.hover_anim.set_target(if self.v.interact() { 1.0 } else { 0.0 });
718 let t = self.v.hover_anim.tick();
719 let bar_w = self.style.bar_width_at(t);
720 let bar_x = bar_right - bar_w;
721 let r = bar_w * 0.5;
722
723 let (lo, hi) = self.v_track_range();
724 ctx.set_fill_color(scale_alpha(track_color_base, v_alpha));
725 ctx.begin_path();
726 ctx.rounded_rect(bar_x, lo, bar_w, hi - lo, r);
727 ctx.fill();
728
729 let tc = if self.v.dragging {
730 v.scroll_thumb_dragging
731 } else if self.v.hovered_thumb {
732 v.scroll_thumb_hovered
733 } else { thumb_idle };
734 ctx.set_fill_color(scale_alpha(tc, v_alpha));
735 ctx.begin_path();
736 ctx.rounded_rect(bar_x, ty, bar_w, th, r);
737 ctx.fill();
738 }
739 }
740
741 if paint_h {
743 if let Some((tx, tw)) = self.h_thumb_metrics() {
744 let bar_bottom = self.h_bar_bottom();
745 self.h.hover_anim.set_target(if self.h.interact() { 1.0 } else { 0.0 });
746 let t = self.h.hover_anim.tick();
747 let bar_h = self.style.bar_width_at(t);
748 let r = bar_h * 0.5;
749
750 let (lo, hi) = self.h_track_range();
751 ctx.set_fill_color(scale_alpha(track_color_base, h_alpha));
752 ctx.begin_path();
753 ctx.rounded_rect(lo, bar_bottom, hi - lo, bar_h, r);
754 ctx.fill();
755
756 let tc = if self.h.dragging {
757 v.scroll_thumb_dragging
758 } else if self.h.hovered_thumb {
759 v.scroll_thumb_hovered
760 } else { thumb_idle };
761 ctx.set_fill_color(scale_alpha(tc, h_alpha));
762 ctx.begin_path();
763 ctx.rounded_rect(tx, bar_bottom, tw, bar_h, r);
764 ctx.fill();
765 }
766 }
767
768 if self.style.fade_strength > 0.001 && self.style.fade_size > 0.5 {
774 self.paint_fade(ctx);
775 }
776 }
777
778 fn on_event(&mut self, event: &Event) -> EventResult {
779 match event {
780 Event::MouseWheel { delta_y, delta_x, .. } => {
782 let mut consumed = false;
783 if self.v.enabled {
784 self.v.offset = self.v.offset + delta_y * 40.0;
785 consumed = true;
786 }
787 if self.h.enabled {
788 self.h.offset = self.h.offset + delta_x * 40.0;
789 consumed = true;
790 }
791 self.clamp_offsets();
792 let (_, vh) = self.viewport();
793 self.was_at_bottom = (self.v.max_scroll(vh) - self.v.offset).abs() < 0.5;
794 if let Some(c) = &self.offset_cell { c.set(self.v.offset); }
795 if consumed {
796 crate::animation::request_tick();
797 EventResult::Consumed
798 } else {
799 EventResult::Ignored
800 }
801 }
802
803 Event::MouseMove { pos } => {
805 let (vw, vh) = self.viewport();
806 let v_scroll = self.v.enabled && self.v.content > vh;
807 let h_scroll = self.h.enabled && self.h.content > vw;
808 let was_vb = self.v.hovered_bar;
809 let was_vt = self.v.hovered_thumb;
810 let was_hb = self.h.hovered_bar;
811 let was_ht = self.h.hovered_thumb;
812 self.v.hovered_bar = v_scroll && self.pos_in_v_hover(*pos);
813 self.v.hovered_thumb = v_scroll && self.pos_on_v_thumb(*pos);
814 self.h.hovered_bar = h_scroll && self.pos_in_h_hover(*pos);
815 self.h.hovered_thumb = h_scroll && self.pos_on_h_thumb(*pos);
816 if was_vb != self.v.hovered_bar || was_vt != self.v.hovered_thumb
817 || was_hb != self.h.hovered_bar || was_ht != self.h.hovered_thumb
818 {
819 crate::animation::request_tick();
820 }
821
822 if self.v.dragging {
823 if let Some((_, th)) = self.v_thumb_metrics() {
824 let (lo, hi) = self.v_track_range();
825 let travel = (hi - lo - th).max(1.0);
826 let new_ty = (pos.y - self.v.drag_thumb_offset)
827 .clamp(lo, lo + travel);
828 let frac = 1.0 - (new_ty - lo) / travel;
829 self.v.offset = (frac * self.v.max_scroll(vh)).max(0.0);
830 self.clamp_offsets();
831 self.was_at_bottom =
832 (self.v.max_scroll(vh) - self.v.offset).abs() < 0.5;
833 if let Some(c) = &self.offset_cell { c.set(self.v.offset); }
834 }
835 crate::animation::request_tick();
836 return EventResult::Consumed;
837 }
838 if self.h.dragging {
839 if let Some((_, tw)) = self.h_thumb_metrics() {
840 let (lo, hi) = self.h_track_range();
841 let travel = (hi - lo - tw).max(1.0);
842 let new_tx = (pos.x - self.h.drag_thumb_offset)
843 .clamp(lo, lo + travel);
844 let frac = (new_tx - lo) / travel;
845 self.h.offset = (frac * self.h.max_scroll(vw)).max(0.0);
846 self.clamp_offsets();
847 }
848 crate::animation::request_tick();
849 return EventResult::Consumed;
850 }
851 EventResult::Ignored
852 }
853
854 Event::MouseDown { pos, button: MouseButton::Left, .. } => {
856 let (vw, vh) = self.viewport();
857 let v_scroll = self.v.enabled && self.v.content > vh;
858 let h_scroll = self.h.enabled && self.h.content > vw;
859
860 if v_scroll && self.pos_in_v_hover(*pos) {
861 if self.pos_on_v_thumb(*pos) {
862 let ty = self.v_thumb_metrics().map(|(y, _)| y).unwrap_or(0.0);
863 self.v.dragging = true;
864 self.v.drag_thumb_offset = pos.y - ty;
865 } else if let Some((ty, th)) = self.v_thumb_metrics() {
868 let page = (vh - 16.0).max(20.0);
873 if pos.y > ty + th {
874 self.v.offset = (self.v.offset - page).max(0.0);
875 } else if pos.y < ty {
876 self.v.offset = (self.v.offset + page).min(self.v.max_scroll(vh));
877 }
878 self.clamp_offsets();
879 if let Some(c) = &self.offset_cell { c.set(self.v.offset); }
880 crate::animation::request_tick();
882 }
883 return EventResult::Consumed;
884 }
885 if h_scroll && self.pos_in_h_hover(*pos) {
886 if self.pos_on_h_thumb(*pos) {
887 let tx = self.h_thumb_metrics().map(|(x, _)| x).unwrap_or(0.0);
888 self.h.dragging = true;
889 self.h.drag_thumb_offset = pos.x - tx;
890 } else if let Some((tx, tw)) = self.h_thumb_metrics() {
892 let page = (vw - 16.0).max(20.0);
893 if pos.x < tx {
894 self.h.offset = (self.h.offset - page).max(0.0);
895 } else if pos.x > tx + tw {
896 self.h.offset = (self.h.offset + page).min(self.h.max_scroll(vw));
897 }
898 self.clamp_offsets();
899 crate::animation::request_tick();
900 }
901 return EventResult::Consumed;
902 }
903 EventResult::Ignored
904 }
905
906 Event::MouseUp { button: MouseButton::Left, .. } => {
908 let was = self.v.dragging || self.h.dragging;
909 self.v.dragging = false;
910 self.h.dragging = false;
911 if was {
912 crate::animation::request_tick();
913 EventResult::Consumed
914 } else {
915 EventResult::Ignored
916 }
917 }
918
919 _ => EventResult::Ignored,
920 }
921 }
922}
923
924impl ScrollView {
925 fn paint_fade(&self, ctx: &mut dyn DrawCtx) {
930 let v = ctx.visuals();
931 let c = v.panel_fill;
932 let (vw, vh) = self.viewport();
933 let strength = self.style.fade_strength.clamp(0.0, 1.0) as f32;
934 let size = self.style.fade_size.max(0.0);
935 let max_a = strength;
936
937 if self.v.enabled {
939 if self.v.offset > 0.5 {
940 Self::fill_v_gradient(ctx, c, max_a, 0.0, self.bounds.height - size, vw, size, false);
942 }
943 if (self.v.max_scroll(vh) - self.v.offset) > 0.5 {
944 let y_bottom = self.bounds.height - vh;
946 Self::fill_v_gradient(ctx, c, max_a, 0.0, y_bottom, vw, size, true);
947 }
948 }
949 if self.h.enabled {
950 if self.h.offset > 0.5 {
951 Self::fill_h_gradient(ctx, c, max_a, 0.0, self.bounds.height - vh, size, vh, true);
953 }
954 if (self.h.max_scroll(vw) - self.h.offset) > 0.5 {
955 Self::fill_h_gradient(ctx, c, max_a, vw - size, self.bounds.height - vh, size, vh, false);
957 }
958 }
959 }
960
961 fn fill_v_gradient(
967 ctx: &mut dyn DrawCtx,
968 c: Color,
969 max_alpha: f32,
970 x: f64,
971 y: f64,
972 w: f64,
973 h: f64,
974 opaque_at_bottom: bool,
975 ) {
976 const STEPS: usize = 64;
977 let strip_h = h / STEPS as f64;
978 for i in 0..STEPS {
979 let t = (i as f32 + 0.5) / STEPS as f32;
981 let a = if opaque_at_bottom { 1.0 - t } else { t };
982 ctx.set_fill_color(Color::rgba(c.r, c.g, c.b, a * max_alpha));
983 ctx.begin_path();
984 ctx.rect(x, y + i as f64 * strip_h, w, strip_h + 0.5);
985 ctx.fill();
986 }
987 }
988
989 fn fill_h_gradient(
995 ctx: &mut dyn DrawCtx,
996 c: Color,
997 max_alpha: f32,
998 x: f64,
999 y: f64,
1000 w: f64,
1001 h: f64,
1002 opaque_at_left: bool,
1003 ) {
1004 const STEPS: usize = 64;
1005 let strip_w = w / STEPS as f64;
1006 for i in 0..STEPS {
1007 let t = (i as f32 + 0.5) / STEPS as f32;
1008 let a = if opaque_at_left { 1.0 - t } else { t };
1009 ctx.set_fill_color(Color::rgba(c.r, c.g, c.b, a * max_alpha));
1010 ctx.begin_path();
1011 ctx.rect(x + i as f64 * strip_w, y, strip_w + 0.5, h);
1012 ctx.fill();
1013 }
1014 }
1015}