1#![allow(clippy::needless_range_loop)]
2
3use crate::*;
4
5#[derive(Clone, Copy, Debug)]
6#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
7struct ScrollTarget {
8 animation_time_span: (f64, f64),
9 target_offset: f32,
10}
11
12#[derive(Clone, Copy, Debug)]
13#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
14#[cfg_attr(feature = "serde", serde(default))]
15pub struct State {
16 pub offset: Vec2,
18
19 offset_target: [Option<ScrollTarget>; 2],
21
22 show_scroll: Vec2b,
24
25 content_is_too_large: Vec2b,
27
28 scroll_bar_interaction: Vec2b,
30
31 #[cfg_attr(feature = "serde", serde(skip))]
33 vel: Vec2,
34
35 scroll_start_offset_from_top_left: [Option<f32>; 2],
37
38 scroll_stuck_to_end: Vec2b,
42
43 interact_rect: Option<Rect>,
45}
46
47impl Default for State {
48 fn default() -> Self {
49 Self {
50 offset: Vec2::ZERO,
51 offset_target: Default::default(),
52 show_scroll: Vec2b::FALSE,
53 content_is_too_large: Vec2b::FALSE,
54 scroll_bar_interaction: Vec2b::FALSE,
55 vel: Vec2::ZERO,
56 scroll_start_offset_from_top_left: [None; 2],
57 scroll_stuck_to_end: Vec2b::TRUE,
58 interact_rect: None,
59 }
60 }
61}
62
63impl State {
64 pub fn load(ctx: &Context, id: Id) -> Option<Self> {
65 ctx.data_mut(|d| d.get_persisted(id))
66 }
67
68 pub fn store(self, ctx: &Context, id: Id) {
69 ctx.data_mut(|d| d.insert_persisted(id, self));
70 }
71
72 pub fn velocity(&self) -> Vec2 {
74 self.vel
75 }
76}
77
78pub struct ScrollAreaOutput<R> {
79 pub inner: R,
81
82 pub id: Id,
84
85 pub state: State,
87
88 pub content_size: Vec2,
91
92 pub inner_rect: Rect,
94}
95
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
99pub enum ScrollBarVisibility {
100 AlwaysHidden,
106
107 VisibleWhenNeeded,
112
113 AlwaysVisible,
116}
117
118impl Default for ScrollBarVisibility {
119 #[inline]
120 fn default() -> Self {
121 Self::VisibleWhenNeeded
122 }
123}
124
125impl ScrollBarVisibility {
126 pub const ALL: [Self; 3] = [
127 Self::AlwaysHidden,
128 Self::VisibleWhenNeeded,
129 Self::AlwaysVisible,
130 ];
131}
132
133#[derive(Clone, Debug)]
162#[must_use = "You should call .show()"]
163pub struct ScrollArea {
164 scroll_enabled: Vec2b,
166
167 auto_shrink: Vec2b,
168 max_size: Vec2,
169 min_scrolled_size: Vec2,
170 scroll_bar_visibility: ScrollBarVisibility,
171 id_source: Option<Id>,
172 offset_x: Option<f32>,
173 offset_y: Option<f32>,
174
175 scrolling_enabled: bool,
177 drag_to_scroll: bool,
178
179 stick_to_end: Vec2b,
183
184 animated: bool,
186}
187
188impl ScrollArea {
189 #[inline]
191 pub fn horizontal() -> Self {
192 Self::new([true, false])
193 }
194
195 #[inline]
197 pub fn vertical() -> Self {
198 Self::new([false, true])
199 }
200
201 #[inline]
203 pub fn both() -> Self {
204 Self::new([true, true])
205 }
206
207 #[inline]
210 pub fn neither() -> Self {
211 Self::new([false, false])
212 }
213
214 pub fn new(scroll_enabled: impl Into<Vec2b>) -> Self {
217 Self {
218 scroll_enabled: scroll_enabled.into(),
219 auto_shrink: Vec2b::TRUE,
220 max_size: Vec2::INFINITY,
221 min_scrolled_size: Vec2::splat(64.0),
222 scroll_bar_visibility: Default::default(),
223 id_source: None,
224 offset_x: None,
225 offset_y: None,
226 scrolling_enabled: true,
227 drag_to_scroll: true,
228 stick_to_end: Vec2b::FALSE,
229 animated: true,
230 }
231 }
232
233 #[inline]
239 pub fn max_width(mut self, max_width: f32) -> Self {
240 self.max_size.x = max_width;
241 self
242 }
243
244 #[inline]
250 pub fn max_height(mut self, max_height: f32) -> Self {
251 self.max_size.y = max_height;
252 self
253 }
254
255 #[inline]
262 pub fn min_scrolled_width(mut self, min_scrolled_width: f32) -> Self {
263 self.min_scrolled_size.x = min_scrolled_width;
264 self
265 }
266
267 #[inline]
274 pub fn min_scrolled_height(mut self, min_scrolled_height: f32) -> Self {
275 self.min_scrolled_size.y = min_scrolled_height;
276 self
277 }
278
279 #[inline]
283 pub fn scroll_bar_visibility(mut self, scroll_bar_visibility: ScrollBarVisibility) -> Self {
284 self.scroll_bar_visibility = scroll_bar_visibility;
285 self
286 }
287
288 #[inline]
290 pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self {
291 self.id_source = Some(Id::new(id_source));
292 self
293 }
294
295 #[inline]
303 pub fn scroll_offset(mut self, offset: Vec2) -> Self {
304 self.offset_x = Some(offset.x);
305 self.offset_y = Some(offset.y);
306 self
307 }
308
309 #[inline]
316 pub fn vertical_scroll_offset(mut self, offset: f32) -> Self {
317 self.offset_y = Some(offset);
318 self
319 }
320
321 #[inline]
328 pub fn horizontal_scroll_offset(mut self, offset: f32) -> Self {
329 self.offset_x = Some(offset);
330 self
331 }
332
333 #[inline]
335 pub fn hscroll(mut self, hscroll: bool) -> Self {
336 self.scroll_enabled[0] = hscroll;
337 self
338 }
339
340 #[inline]
342 pub fn vscroll(mut self, vscroll: bool) -> Self {
343 self.scroll_enabled[1] = vscroll;
344 self
345 }
346
347 #[inline]
351 pub fn scroll(mut self, scroll_enabled: impl Into<Vec2b>) -> Self {
352 self.scroll_enabled = scroll_enabled.into();
353 self
354 }
355
356 #[deprecated = "Renamed to `scroll`"]
358 #[inline]
359 pub fn scroll2(mut self, scroll_enabled: impl Into<Vec2b>) -> Self {
360 self.scroll_enabled = scroll_enabled.into();
361 self
362 }
363
364 #[inline]
374 pub fn enable_scrolling(mut self, enable: bool) -> Self {
375 self.scrolling_enabled = enable;
376 self
377 }
378
379 #[inline]
387 pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self {
388 self.drag_to_scroll = drag_to_scroll;
389 self
390 }
391
392 #[inline]
399 pub fn auto_shrink(mut self, auto_shrink: impl Into<Vec2b>) -> Self {
400 self.auto_shrink = auto_shrink.into();
401 self
402 }
403
404 #[inline]
408 pub fn animated(mut self, animated: bool) -> Self {
409 self.animated = animated;
410 self
411 }
412
413 pub(crate) fn is_any_scroll_enabled(&self) -> bool {
415 self.scroll_enabled[0] || self.scroll_enabled[1]
416 }
417
418 #[inline]
425 pub fn stick_to_right(mut self, stick: bool) -> Self {
426 self.stick_to_end[0] = stick;
427 self
428 }
429
430 #[inline]
437 pub fn stick_to_bottom(mut self, stick: bool) -> Self {
438 self.stick_to_end[1] = stick;
439 self
440 }
441}
442
443struct Prepared {
444 id: Id,
445 state: State,
446
447 auto_shrink: Vec2b,
448
449 scroll_enabled: Vec2b,
451
452 show_bars_factor: Vec2,
454
455 current_bar_use: Vec2,
465
466 scroll_bar_visibility: ScrollBarVisibility,
467
468 inner_rect: Rect,
470
471 content_ui: Ui,
472
473 viewport: Rect,
476
477 scrolling_enabled: bool,
478 stick_to_end: Vec2b,
479 animated: bool,
480}
481
482impl ScrollArea {
483 fn begin(self, ui: &mut Ui) -> Prepared {
484 let Self {
485 scroll_enabled,
486 auto_shrink,
487 max_size,
488 min_scrolled_size,
489 scroll_bar_visibility,
490 id_source,
491 offset_x,
492 offset_y,
493 scrolling_enabled,
494 drag_to_scroll,
495 stick_to_end,
496 animated,
497 } = self;
498
499 let ctx = ui.ctx().clone();
500 let scrolling_enabled = scrolling_enabled && ui.is_enabled();
501
502 let id_source = id_source.unwrap_or_else(|| Id::new("scroll_area"));
503 let id = ui.make_persistent_id(id_source);
504 ctx.check_for_id_clash(
505 id,
506 Rect::from_min_size(ui.available_rect_before_wrap().min, Vec2::ZERO),
507 "ScrollArea",
508 );
509 let mut state = State::load(&ctx, id).unwrap_or_default();
510
511 state.offset.x = offset_x.unwrap_or(state.offset.x);
512 state.offset.y = offset_y.unwrap_or(state.offset.y);
513
514 let show_bars: Vec2b = match scroll_bar_visibility {
515 ScrollBarVisibility::AlwaysHidden => Vec2b::FALSE,
516 ScrollBarVisibility::VisibleWhenNeeded => state.show_scroll,
517 ScrollBarVisibility::AlwaysVisible => scroll_enabled,
518 };
519
520 let show_bars_factor = Vec2::new(
521 ctx.animate_bool_responsive(id.with("h"), show_bars[0]),
522 ctx.animate_bool_responsive(id.with("v"), show_bars[1]),
523 );
524
525 let current_bar_use = show_bars_factor.yx() * ui.spacing().scroll.allocated_width();
526
527 let available_outer = ui.available_rect_before_wrap();
528
529 let outer_size = available_outer.size().at_most(max_size);
530
531 let inner_size = {
532 let mut inner_size = outer_size - current_bar_use;
533
534 for d in 0..2 {
539 if scroll_enabled[d] {
540 inner_size[d] = inner_size[d].max(min_scrolled_size[d]);
541 }
542 }
543 inner_size
544 };
545
546 let inner_rect = Rect::from_min_size(available_outer.min, inner_size);
547
548 let mut content_max_size = inner_size;
549
550 if true {
551 } else {
554 for d in 0..2 {
556 if scroll_enabled[d] {
557 content_max_size[d] = f32::INFINITY;
558 }
559 }
560 }
561
562 let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size);
563 let mut content_ui = ui.child_ui(
564 content_max_rect,
565 *ui.layout(),
566 Some(UiStackInfo::new(UiKind::ScrollArea)),
567 );
568
569 {
570 let clip_rect_margin = ui.visuals().clip_rect_margin;
572 let mut content_clip_rect = ui.clip_rect();
573 for d in 0..2 {
574 if scroll_enabled[d] {
575 if state.content_is_too_large[d] {
576 content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin;
577 content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin;
578 }
579 } else {
580 content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d];
582 }
583 }
584 content_clip_rect = content_clip_rect.intersect(ui.clip_rect());
586 content_ui.set_clip_rect(content_clip_rect);
587 }
588
589 let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size);
590 let dt = ui.input(|i| i.stable_dt).at_most(0.1);
591
592 if (scrolling_enabled && drag_to_scroll)
593 && (state.content_is_too_large[0] || state.content_is_too_large[1])
594 {
595 let content_response_option = state
599 .interact_rect
600 .map(|rect| ui.interact(rect, id.with("area"), Sense::drag()));
601
602 if content_response_option.map(|response| response.dragged()) == Some(true) {
603 for d in 0..2 {
604 if scroll_enabled[d] {
605 ui.input(|input| {
606 state.offset[d] -= input.pointer.delta()[d];
607 state.vel[d] = input.pointer.velocity()[d];
608 });
609 state.scroll_stuck_to_end[d] = false;
610 state.offset_target[d] = None;
611 } else {
612 state.vel[d] = 0.0;
613 }
614 }
615 } else {
616 for d in 0..2 {
617 let stop_speed = 20.0; let friction_coeff = 1000.0; let friction = friction_coeff * dt;
622 if friction > state.vel[d].abs() || state.vel[d].abs() < stop_speed {
623 state.vel[d] = 0.0;
624 } else {
625 state.vel[d] -= friction * state.vel[d].signum();
626 state.offset[d] -= state.vel[d] * dt;
629 ctx.request_repaint();
630 }
631 }
632 }
633 }
634
635 for d in 0..2 {
638 if let Some(scroll_target) = state.offset_target[d] {
639 state.vel[d] = 0.0;
640
641 if (state.offset[d] - scroll_target.target_offset).abs() < 1.0 {
642 state.offset[d] = scroll_target.target_offset;
644 state.offset_target[d] = None;
645 } else {
646 let t = emath::interpolation_factor(
648 scroll_target.animation_time_span,
649 ui.input(|i| i.time),
650 dt,
651 emath::ease_in_ease_out,
652 );
653 if t < 1.0 {
654 state.offset[d] =
655 emath::lerp(state.offset[d]..=scroll_target.target_offset, t);
656 ctx.request_repaint();
657 } else {
658 state.offset[d] = scroll_target.target_offset;
660 state.offset_target[d] = None;
661 }
662 }
663 }
664 }
665
666 Prepared {
667 id,
668 state,
669 auto_shrink,
670 scroll_enabled,
671 show_bars_factor,
672 current_bar_use,
673 scroll_bar_visibility,
674 inner_rect,
675 content_ui,
676 viewport,
677 scrolling_enabled,
678 stick_to_end,
679 animated,
680 }
681 }
682
683 pub fn show<R>(
687 self,
688 ui: &mut Ui,
689 add_contents: impl FnOnce(&mut Ui) -> R,
690 ) -> ScrollAreaOutput<R> {
691 self.show_viewport_dyn(ui, Box::new(|ui, _viewport| add_contents(ui)))
692 }
693
694 pub fn show_rows<R>(
711 self,
712 ui: &mut Ui,
713 row_height_sans_spacing: f32,
714 total_rows: usize,
715 add_contents: impl FnOnce(&mut Ui, std::ops::Range<usize>) -> R,
716 ) -> ScrollAreaOutput<R> {
717 let spacing = ui.spacing().item_spacing;
718 let row_height_with_spacing = row_height_sans_spacing + spacing.y;
719 self.show_viewport(ui, |ui, viewport| {
720 ui.set_height((row_height_with_spacing * total_rows as f32 - spacing.y).at_least(0.0));
721
722 let mut min_row = (viewport.min.y / row_height_with_spacing).floor() as usize;
723 let mut max_row = (viewport.max.y / row_height_with_spacing).ceil() as usize + 1;
724 if max_row > total_rows {
725 let diff = max_row.saturating_sub(min_row);
726 max_row = total_rows;
727 min_row = total_rows.saturating_sub(diff);
728 }
729
730 let y_min = ui.max_rect().top() + min_row as f32 * row_height_with_spacing;
731 let y_max = ui.max_rect().top() + max_row as f32 * row_height_with_spacing;
732
733 let rect = Rect::from_x_y_ranges(ui.max_rect().x_range(), y_min..=y_max);
734
735 ui.allocate_ui_at_rect(rect, |viewport_ui| {
736 viewport_ui.skip_ahead_auto_ids(min_row); add_contents(viewport_ui, min_row..max_row)
738 })
739 .inner
740 })
741 }
742
743 pub fn show_viewport<R>(
748 self,
749 ui: &mut Ui,
750 add_contents: impl FnOnce(&mut Ui, Rect) -> R,
751 ) -> ScrollAreaOutput<R> {
752 self.show_viewport_dyn(ui, Box::new(add_contents))
753 }
754
755 fn show_viewport_dyn<'c, R>(
756 self,
757 ui: &mut Ui,
758 add_contents: Box<dyn FnOnce(&mut Ui, Rect) -> R + 'c>,
759 ) -> ScrollAreaOutput<R> {
760 let mut prepared = self.begin(ui);
761 let id = prepared.id;
762 let inner_rect = prepared.inner_rect;
763 let inner = add_contents(&mut prepared.content_ui, prepared.viewport);
764 let (content_size, state) = prepared.end(ui);
765 ScrollAreaOutput {
766 inner,
767 id,
768 state,
769 content_size,
770 inner_rect,
771 }
772 }
773}
774
775impl Prepared {
776 fn end(self, ui: &mut Ui) -> (Vec2, State) {
778 let Self {
779 id,
780 mut state,
781 inner_rect,
782 auto_shrink,
783 scroll_enabled,
784 mut show_bars_factor,
785 current_bar_use,
786 scroll_bar_visibility,
787 content_ui,
788 viewport: _,
789 scrolling_enabled,
790 stick_to_end,
791 animated,
792 } = self;
793
794 let content_size = content_ui.min_size();
795
796 let scroll_delta = content_ui
797 .ctx()
798 .frame_state_mut(|state| std::mem::take(&mut state.scroll_delta));
799
800 for d in 0..2 {
801 let mut delta = -scroll_delta[d];
803
804 let scroll_target = content_ui
807 .ctx()
808 .frame_state_mut(|state| state.scroll_target[d].take());
809
810 if scroll_enabled[d] {
811 delta += if let Some((target_range, align)) = scroll_target {
812 let min = content_ui.min_rect().min[d];
813 let clip_rect = content_ui.clip_rect();
814 let visible_range = min..=min + clip_rect.size()[d];
815 let (start, end) = (target_range.min, target_range.max);
816 let clip_start = clip_rect.min[d];
817 let clip_end = clip_rect.max[d];
818 let mut spacing = ui.spacing().item_spacing[d];
819
820 if let Some(align) = align {
821 let center_factor = align.to_factor();
822
823 let offset =
824 lerp(target_range, center_factor) - lerp(visible_range, center_factor);
825
826 spacing *= remap(center_factor, 0.0..=1.0, -1.0..=1.0);
828
829 offset + spacing - state.offset[d]
830 } else if start < clip_start && end < clip_end {
831 -(clip_start - start + spacing).min(clip_end - end - spacing)
832 } else if end > clip_end && start > clip_start {
833 (end - clip_end + spacing).min(start - clip_start - spacing)
834 } else {
835 0.0
837 }
838 } else {
839 0.0
840 };
841
842 if delta != 0.0 {
843 let target_offset = state.offset[d] + delta;
844
845 if !animated {
846 state.offset[d] = target_offset;
847 } else if let Some(animation) = &mut state.offset_target[d] {
848 animation.target_offset = target_offset;
851 } else {
852 let now = ui.input(|i| i.time);
855 let points_per_second = 1000.0;
856 let animation_duration = (delta.abs() / points_per_second).clamp(0.1, 0.3);
857 state.offset_target[d] = Some(ScrollTarget {
858 animation_time_span: (now, now + animation_duration as f64),
859 target_offset,
860 });
861 }
862 ui.ctx().request_repaint();
863 }
864 }
865 }
866
867 let inner_rect = {
868 let mut inner_size = inner_rect.size();
870
871 for d in 0..2 {
872 inner_size[d] = match (scroll_enabled[d], auto_shrink[d]) {
873 (true, true) => inner_size[d].min(content_size[d]), (true, false) => inner_size[d], (false, true) => content_size[d], (false, false) => inner_size[d].max(content_size[d]), };
878 }
879
880 Rect::from_min_size(inner_rect.min, inner_size)
881 };
882
883 let outer_rect = Rect::from_min_size(inner_rect.min, inner_rect.size() + current_bar_use);
884
885 let content_is_too_large = Vec2b::new(
886 scroll_enabled[0] && inner_rect.width() < content_size.x,
887 scroll_enabled[1] && inner_rect.height() < content_size.y,
888 );
889
890 let max_offset = content_size - inner_rect.size();
891 let is_hovering_outer_rect = ui.rect_contains_pointer(outer_rect);
892 if scrolling_enabled && is_hovering_outer_rect {
893 let always_scroll_enabled_direction = ui.style().always_scroll_the_only_direction
894 && scroll_enabled[0] != scroll_enabled[1];
895 for d in 0..2 {
896 if scroll_enabled[d] {
897 let scroll_delta = ui.ctx().input_mut(|input| {
898 if always_scroll_enabled_direction {
899 input.smooth_scroll_delta[0] + input.smooth_scroll_delta[1]
901 } else {
902 input.smooth_scroll_delta[d]
903 }
904 });
905
906 let scrolling_up = state.offset[d] > 0.0 && scroll_delta > 0.0;
907 let scrolling_down = state.offset[d] < max_offset[d] && scroll_delta < 0.0;
908
909 if scrolling_up || scrolling_down {
910 state.offset[d] -= scroll_delta;
911
912 ui.ctx().input_mut(|input| {
914 if always_scroll_enabled_direction {
915 input.smooth_scroll_delta[0] = 0.0;
916 input.smooth_scroll_delta[1] = 0.0;
917 } else {
918 input.smooth_scroll_delta[d] = 0.0;
919 }
920 });
921
922 state.scroll_stuck_to_end[d] = false;
923 state.offset_target[d] = None;
924 }
925 }
926 }
927 }
928
929 let show_scroll_this_frame = match scroll_bar_visibility {
930 ScrollBarVisibility::AlwaysHidden => Vec2b::FALSE,
931 ScrollBarVisibility::VisibleWhenNeeded => content_is_too_large,
932 ScrollBarVisibility::AlwaysVisible => scroll_enabled,
933 };
934
935 if show_scroll_this_frame[0] && show_bars_factor.x <= 0.0 {
937 show_bars_factor.x = ui.ctx().animate_bool_responsive(id.with("h"), true);
938 }
939 if show_scroll_this_frame[1] && show_bars_factor.y <= 0.0 {
940 show_bars_factor.y = ui.ctx().animate_bool_responsive(id.with("v"), true);
941 }
942
943 let scroll_style = ui.spacing().scroll;
944
945 for d in 0..2 {
947 if stick_to_end[d] && state.scroll_stuck_to_end[d] {
949 state.offset[d] = content_size[d] - inner_rect.size()[d];
950 }
951
952 let show_factor = show_bars_factor[d];
953 if show_factor == 0.0 {
954 state.scroll_bar_interaction[d] = false;
955 continue;
956 }
957
958 let main_range = Rangef::new(inner_rect.min[d], inner_rect.max[d]);
961
962 let inner_margin = show_factor * scroll_style.bar_inner_margin;
964 let outer_margin = show_factor * scroll_style.bar_outer_margin;
965
966 let mut cross = if scroll_style.floating {
969 let max_bar_rect = if d == 0 {
972 outer_rect.with_min_y(outer_rect.max.y - outer_margin - scroll_style.bar_width)
973 } else {
974 outer_rect.with_min_x(outer_rect.max.x - outer_margin - scroll_style.bar_width)
975 };
976
977 let is_hovering_bar_area = is_hovering_outer_rect
978 && ui.rect_contains_pointer(max_bar_rect)
979 || state.scroll_bar_interaction[d];
980
981 let is_hovering_bar_area_t = ui
982 .ctx()
983 .animate_bool_responsive(id.with((d, "bar_hover")), is_hovering_bar_area);
984
985 let width = show_factor
986 * lerp(
987 scroll_style.floating_width..=scroll_style.bar_width,
988 is_hovering_bar_area_t,
989 );
990
991 let max_cross = outer_rect.max[1 - d] - outer_margin;
992 let min_cross = max_cross - width;
993 Rangef::new(min_cross, max_cross)
994 } else {
995 let min_cross = inner_rect.max[1 - d] + inner_margin;
996 let max_cross = outer_rect.max[1 - d] - outer_margin;
997 Rangef::new(min_cross, max_cross)
998 };
999
1000 if ui.clip_rect().max[1 - d] < cross.max + outer_margin {
1001 let width = cross.max - cross.min;
1011 cross.max = ui.clip_rect().max[1 - d] - outer_margin;
1012 cross.min = cross.max - width;
1013 }
1014
1015 let outer_scroll_rect = if d == 0 {
1016 Rect::from_min_max(
1017 pos2(inner_rect.left(), cross.min),
1018 pos2(inner_rect.right(), cross.max),
1019 )
1020 } else {
1021 Rect::from_min_max(
1022 pos2(cross.min, inner_rect.top()),
1023 pos2(cross.max, inner_rect.bottom()),
1024 )
1025 };
1026
1027 let from_content = |content| remap_clamp(content, 0.0..=content_size[d], main_range);
1028
1029 let handle_rect = if d == 0 {
1030 Rect::from_min_max(
1031 pos2(from_content(state.offset.x), cross.min),
1032 pos2(from_content(state.offset.x + inner_rect.width()), cross.max),
1033 )
1034 } else {
1035 Rect::from_min_max(
1036 pos2(cross.min, from_content(state.offset.y)),
1037 pos2(
1038 cross.max,
1039 from_content(state.offset.y + inner_rect.height()),
1040 ),
1041 )
1042 };
1043
1044 let interact_id = id.with(d);
1045 let sense = if self.scrolling_enabled {
1046 Sense::click_and_drag()
1047 } else {
1048 Sense::hover()
1049 };
1050 let response = ui.interact(outer_scroll_rect, interact_id, sense);
1051
1052 state.scroll_bar_interaction[d] = response.hovered() || response.dragged();
1053
1054 if let Some(pointer_pos) = response.interact_pointer_pos() {
1055 let scroll_start_offset_from_top_left = state.scroll_start_offset_from_top_left[d]
1056 .get_or_insert_with(|| {
1057 if handle_rect.contains(pointer_pos) {
1058 pointer_pos[d] - handle_rect.min[d]
1059 } else {
1060 let handle_top_pos_at_bottom = main_range.max - handle_rect.size()[d];
1061 let new_handle_top_pos = (pointer_pos[d] - handle_rect.size()[d] / 2.0)
1063 .clamp(main_range.min, handle_top_pos_at_bottom);
1064 pointer_pos[d] - new_handle_top_pos
1065 }
1066 });
1067
1068 let new_handle_top = pointer_pos[d] - *scroll_start_offset_from_top_left;
1069 state.offset[d] = remap(new_handle_top, main_range, 0.0..=content_size[d]);
1070
1071 state.scroll_stuck_to_end[d] = false;
1073 state.offset_target[d] = None;
1074 } else {
1075 state.scroll_start_offset_from_top_left[d] = None;
1076 }
1077
1078 let unbounded_offset = state.offset[d];
1079 state.offset[d] = state.offset[d].max(0.0);
1080 state.offset[d] = state.offset[d].min(max_offset[d]);
1081
1082 if state.offset[d] != unbounded_offset {
1083 state.vel[d] = 0.0;
1084 }
1085
1086 if ui.is_rect_visible(outer_scroll_rect) {
1087 let mut handle_rect = if d == 0 {
1089 Rect::from_min_max(
1090 pos2(from_content(state.offset.x), cross.min),
1091 pos2(from_content(state.offset.x + inner_rect.width()), cross.max),
1092 )
1093 } else {
1094 Rect::from_min_max(
1095 pos2(cross.min, from_content(state.offset.y)),
1096 pos2(
1097 cross.max,
1098 from_content(state.offset.y + inner_rect.height()),
1099 ),
1100 )
1101 };
1102 let min_handle_size = scroll_style.handle_min_length;
1103 if handle_rect.size()[d] < min_handle_size {
1104 handle_rect = Rect::from_center_size(
1105 handle_rect.center(),
1106 if d == 0 {
1107 vec2(min_handle_size, handle_rect.size().y)
1108 } else {
1109 vec2(handle_rect.size().x, min_handle_size)
1110 },
1111 );
1112 }
1113
1114 let visuals = if scrolling_enabled {
1115 let is_hovering_handle = response.hovered()
1118 && ui.input(|i| {
1119 i.pointer
1120 .latest_pos()
1121 .map_or(false, |p| handle_rect.contains(p))
1122 });
1123 let visuals = ui.visuals();
1124 if response.is_pointer_button_down_on() {
1125 &visuals.widgets.active
1126 } else if is_hovering_handle {
1127 &visuals.widgets.hovered
1128 } else {
1129 &visuals.widgets.inactive
1130 }
1131 } else {
1132 &ui.visuals().widgets.inactive
1133 };
1134
1135 let handle_opacity = if scroll_style.floating {
1136 if response.hovered() || response.dragged() {
1137 scroll_style.interact_handle_opacity
1138 } else {
1139 let is_hovering_outer_rect_t = ui.ctx().animate_bool_responsive(
1140 id.with((d, "is_hovering_outer_rect")),
1141 is_hovering_outer_rect,
1142 );
1143 lerp(
1144 scroll_style.dormant_handle_opacity
1145 ..=scroll_style.active_handle_opacity,
1146 is_hovering_outer_rect_t,
1147 )
1148 }
1149 } else {
1150 1.0
1151 };
1152
1153 let background_opacity = if scroll_style.floating {
1154 if response.hovered() || response.dragged() {
1155 scroll_style.interact_background_opacity
1156 } else if is_hovering_outer_rect {
1157 scroll_style.active_background_opacity
1158 } else {
1159 scroll_style.dormant_background_opacity
1160 }
1161 } else {
1162 1.0
1163 };
1164
1165 let handle_color = if scroll_style.foreground_color {
1166 visuals.fg_stroke.color
1167 } else {
1168 visuals.bg_fill
1169 };
1170
1171 ui.painter().add(epaint::Shape::rect_filled(
1173 outer_scroll_rect,
1174 visuals.rounding,
1175 ui.visuals()
1176 .extreme_bg_color
1177 .gamma_multiply(background_opacity),
1178 ));
1179
1180 ui.painter().add(epaint::Shape::rect_filled(
1182 handle_rect,
1183 visuals.rounding,
1184 handle_color.gamma_multiply(handle_opacity),
1185 ));
1186 }
1187 }
1188
1189 ui.advance_cursor_after_rect(outer_rect);
1190
1191 if show_scroll_this_frame != state.show_scroll {
1192 ui.ctx().request_repaint();
1193 }
1194
1195 let available_offset = content_size - inner_rect.size();
1196 state.offset = state.offset.min(available_offset);
1197 state.offset = state.offset.max(Vec2::ZERO);
1198
1199 state.scroll_stuck_to_end = Vec2b::new(
1205 (state.offset[0] == available_offset[0])
1206 || (self.stick_to_end[0] && available_offset[0] < 0.0),
1207 (state.offset[1] == available_offset[1])
1208 || (self.stick_to_end[1] && available_offset[1] < 0.0),
1209 );
1210
1211 state.show_scroll = show_scroll_this_frame;
1212 state.content_is_too_large = content_is_too_large;
1213 state.interact_rect = Some(inner_rect);
1214
1215 state.store(ui.ctx(), id);
1216
1217 (content_size, state)
1218 }
1219}