rat_scrolled/
scroll.rs

1use crate::_private::NonExhaustive;
2use crate::ScrollbarPolicy;
3use crate::event::ScrollOutcome;
4use rat_event::util::MouseFlags;
5use rat_event::{HandleEvent, MouseOnly, ct_event};
6use rat_reloc::{RelocatableState, relocate_area};
7use ratatui::buffer::Buffer;
8use ratatui::layout::Rect;
9use ratatui::style::Style;
10use ratatui::symbols;
11use ratatui::widgets::{Padding, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget};
12use std::cmp::{max, min};
13use std::mem;
14use std::ops::Range;
15
16/// Scroll widget.
17///
18/// This is not a widget by itself, rather it is meant to be used
19/// analogous to Block. A widget that supports scrolling accepts
20/// one or two of these Scroll indicators.
21#[derive(Debug, Default, Clone)]
22pub struct Scroll<'a> {
23    policy: ScrollbarPolicy,
24    orientation: ScrollbarOrientation,
25
26    start_margin: u16,
27    end_margin: u16,
28    overscroll_by: Option<usize>,
29    scroll_by: Option<usize>,
30
31    scrollbar: Scrollbar<'a>,
32    min_style: Option<Style>,
33    min_symbol: Option<&'a str>,
34    hor_symbols: Option<ScrollSymbols>,
35    ver_symbols: Option<ScrollSymbols>,
36}
37
38/// Scroll state.
39///
40/// The current visible page is represented as the pair (offset, page_len).
41///
42/// The limit for scrolling is given as max_offset, which is the maximum offset
43/// where a full page can still be displayed.
44///
45/// __Note__
46///
47/// that the total length of the widgets data is NOT max_offset + page_len.
48/// The page_len can be different for every offset selected. Only
49/// if the offset is set to max_offset and after the next round of rendering
50/// len == max_offset + page_len will hold true.
51///
52/// __Note__
53///
54/// In terms of ScrollbarState,
55/// - offset is position,
56/// - page_len is viewport_content_length and
57/// - max_offset is content_length.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct ScrollState {
60    /// Area of the Scrollbar.
61    /// __readonly__. renewed for each render.
62    pub area: Rect,
63    /// Vertical/Horizontal scroll?
64    /// __readonly__. renewed for each render.
65    pub orientation: ScrollbarOrientation,
66
67    /// Current offset.
68    /// __read+write__
69    pub offset: usize,
70    /// Length of the current displayed page. This value can be
71    /// used for page-up/page-down handling.
72    /// __read+write__
73    pub page_len: usize,
74    /// Maximum offset that is accessible with scrolling.
75    ///
76    /// This offset is calculated as `item_count - last_page_items`. Both
77    /// are abstract values and can denote items or columns/rows as
78    /// the widget sees fit.
79    /// __read+write__
80    pub max_offset: usize,
81
82    /// How many items are scrolled per scroll event.
83    /// When not set it defaults to 1/10 of the page_len, which gives a
84    /// decent median between scroll speed and disorientation.
85    /// __read+write__
86    pub scroll_by: Option<usize>,
87    /// By how much can the max_offset be exceeded.
88    /// This allows displaying some empty space at the end of the
89    /// content, which can be more intuitive for some widgets.
90    /// __read+write__
91    pub overscroll_by: Option<usize>,
92
93    /// Mouse support.
94    /// __read+write__
95    pub mouse: MouseFlags,
96
97    pub non_exhaustive: NonExhaustive,
98}
99
100/// Collected styles for the Scroll.
101#[derive(Debug, Clone)]
102pub struct ScrollStyle {
103    pub thumb_style: Option<Style>,
104    pub track_style: Option<Style>,
105    pub begin_style: Option<Style>,
106    pub end_style: Option<Style>,
107    pub min_style: Option<Style>,
108
109    pub horizontal: Option<ScrollSymbols>,
110    pub vertical: Option<ScrollSymbols>,
111
112    pub non_exhaustive: NonExhaustive,
113}
114
115/// Scrollbar Symbol Set
116/// ```text
117/// <--▮------->
118/// ^  ^   ^   ^
119/// │  │   │   └ end
120/// │  │   └──── track
121/// │  └──────── thumb
122/// └─────────── begin
123/// ```
124///
125/// or if there is no scrollbar
126///
127/// ```text
128/// ............
129/// ^
130/// │
131/// └─── min
132/// ```
133///
134///
135#[derive(Debug, Clone, Copy)]
136pub struct ScrollSymbols {
137    pub track: &'static str,
138    pub thumb: &'static str,
139    pub begin: &'static str,
140    pub end: &'static str,
141    pub min: &'static str,
142}
143
144pub const SCROLLBAR_DOUBLE_VERTICAL: ScrollSymbols = ScrollSymbols {
145    track: symbols::line::DOUBLE_VERTICAL,
146    thumb: symbols::block::FULL,
147    begin: "▲",
148    end: "▼",
149    min: symbols::line::DOUBLE_VERTICAL,
150};
151
152pub const SCROLLBAR_DOUBLE_HORIZONTAL: ScrollSymbols = ScrollSymbols {
153    track: symbols::line::DOUBLE_HORIZONTAL,
154    thumb: symbols::block::FULL,
155    begin: "◄",
156    end: "►",
157    min: symbols::line::DOUBLE_HORIZONTAL,
158};
159
160pub const SCROLLBAR_VERTICAL: ScrollSymbols = ScrollSymbols {
161    track: symbols::line::VERTICAL,
162    thumb: symbols::block::FULL,
163    begin: "↑",
164    end: "↓",
165    min: symbols::line::VERTICAL,
166};
167
168pub const SCROLLBAR_HORIZONTAL: ScrollSymbols = ScrollSymbols {
169    track: symbols::line::HORIZONTAL,
170    thumb: symbols::block::FULL,
171    begin: "←",
172    end: "→",
173    min: symbols::line::HORIZONTAL,
174};
175
176impl From<&ScrollSymbols> for symbols::scrollbar::Set {
177    fn from(value: &ScrollSymbols) -> Self {
178        symbols::scrollbar::Set {
179            track: value.track,
180            thumb: value.thumb,
181            begin: value.begin,
182            end: value.end,
183        }
184    }
185}
186
187impl Default for ScrollStyle {
188    fn default() -> Self {
189        Self {
190            thumb_style: None,
191            track_style: None,
192            begin_style: None,
193            end_style: None,
194            min_style: None,
195            horizontal: None,
196            vertical: None,
197            non_exhaustive: NonExhaustive,
198        }
199    }
200}
201
202impl<'a> Scroll<'a> {
203    pub fn new() -> Self {
204        Self::default()
205    }
206
207    /// Horizontal, bottom scrollbar.
208    pub fn horizontal() -> Self {
209        Self::default().orientation(ScrollbarOrientation::HorizontalBottom)
210    }
211
212    /// Vertical, right scrollbar.
213    pub fn vertical() -> Self {
214        Self::default().orientation(ScrollbarOrientation::VerticalRight)
215    }
216
217    /// Scrollbar policy.
218    pub fn policy(mut self, policy: ScrollbarPolicy) -> Self {
219        self.policy = policy;
220        self
221    }
222
223    /// Scrollbar policy.
224    pub fn get_policy(&self) -> ScrollbarPolicy {
225        self.policy
226    }
227
228    /// Scrollbar orientation.
229    pub fn orientation(mut self, orientation: ScrollbarOrientation) -> Self {
230        if self.orientation != orientation {
231            self.orientation = orientation.clone();
232            self.scrollbar = self.scrollbar.orientation(orientation);
233            self.update_symbols();
234        }
235        self
236    }
237
238    /// Scrollbar orientation.
239    pub fn get_orientation(&self) -> ScrollbarOrientation {
240        self.orientation.clone()
241    }
242
243    /// Ensures a vertical orientation of the scrollbar.
244    ///
245    /// If the orientation is not vertical it will be set to VerticalRight.
246    pub fn override_vertical(mut self) -> Self {
247        let orientation = match self.orientation {
248            ScrollbarOrientation::VerticalRight => ScrollbarOrientation::VerticalRight,
249            ScrollbarOrientation::VerticalLeft => ScrollbarOrientation::VerticalLeft,
250            ScrollbarOrientation::HorizontalBottom => ScrollbarOrientation::VerticalRight,
251            ScrollbarOrientation::HorizontalTop => ScrollbarOrientation::VerticalRight,
252        };
253        if self.orientation != orientation {
254            self.orientation = orientation.clone();
255            self.scrollbar = self.scrollbar.orientation(orientation);
256            self.update_symbols();
257        }
258        self
259    }
260
261    /// Ensures a horizontal orientation of the scrollbar.
262    ///
263    /// If the orientation is not horizontal, it will be set to HorizontalBottom.
264    pub fn override_horizontal(mut self) -> Self {
265        let orientation = match self.orientation {
266            ScrollbarOrientation::VerticalRight => ScrollbarOrientation::HorizontalBottom,
267            ScrollbarOrientation::VerticalLeft => ScrollbarOrientation::HorizontalBottom,
268            ScrollbarOrientation::HorizontalBottom => ScrollbarOrientation::HorizontalBottom,
269            ScrollbarOrientation::HorizontalTop => ScrollbarOrientation::HorizontalTop,
270        };
271        if self.orientation != orientation {
272            self.orientation = orientation.clone();
273            self.scrollbar = self.scrollbar.orientation(orientation);
274            self.update_symbols();
275        }
276        self
277    }
278
279    /// Is this a vertical scrollbar.
280    pub fn is_vertical(&self) -> bool {
281        match self.orientation {
282            ScrollbarOrientation::VerticalRight => true,
283            ScrollbarOrientation::VerticalLeft => true,
284            ScrollbarOrientation::HorizontalBottom => false,
285            ScrollbarOrientation::HorizontalTop => false,
286        }
287    }
288
289    /// Is this a horizontal scrollbar.
290    pub fn is_horizontal(&self) -> bool {
291        match self.orientation {
292            ScrollbarOrientation::VerticalRight => false,
293            ScrollbarOrientation::VerticalLeft => false,
294            ScrollbarOrientation::HorizontalBottom => true,
295            ScrollbarOrientation::HorizontalTop => true,
296        }
297    }
298
299    /// Leave a margin at the start of the scrollbar.
300    pub fn start_margin(mut self, start_margin: u16) -> Self {
301        self.start_margin = start_margin;
302        self
303    }
304
305    /// Margin before the start of the scrollbar.
306    pub fn get_start_margin(&self) -> u16 {
307        self.start_margin
308    }
309
310    /// Leave a margin at the end of the scrollbar.
311    pub fn end_margin(mut self, end_margin: u16) -> Self {
312        self.end_margin = end_margin;
313        self
314    }
315
316    /// Margin after the end of the scrollbar.
317    pub fn get_end_margin(&self) -> u16 {
318        self.end_margin
319    }
320
321    /// Set overscrolling to this value.
322    pub fn overscroll_by(mut self, overscroll: usize) -> Self {
323        self.overscroll_by = Some(overscroll);
324        self
325    }
326
327    /// Set scroll increment.
328    pub fn scroll_by(mut self, scroll: usize) -> Self {
329        self.scroll_by = Some(scroll);
330        self
331    }
332
333    /// Set all styles.
334    pub fn styles(mut self, styles: ScrollStyle) -> Self {
335        if let Some(horizontal) = styles.horizontal {
336            self.hor_symbols = Some(horizontal);
337        }
338        if let Some(vertical) = styles.vertical {
339            self.ver_symbols = Some(vertical);
340        }
341        self.update_symbols();
342
343        if let Some(thumb_style) = styles.thumb_style {
344            self.scrollbar = self.scrollbar.thumb_style(thumb_style);
345        }
346        if let Some(track_style) = styles.track_style {
347            self.scrollbar = self.scrollbar.track_style(track_style);
348        }
349        if let Some(begin_style) = styles.begin_style {
350            self.scrollbar = self.scrollbar.begin_style(begin_style);
351        }
352        if let Some(end_style) = styles.end_style {
353            self.scrollbar = self.scrollbar.end_style(end_style);
354        }
355        if styles.min_style.is_some() {
356            self.min_style = styles.min_style;
357        }
358        self
359    }
360
361    fn update_symbols(&mut self) {
362        if self.is_horizontal() {
363            if let Some(horizontal) = &self.hor_symbols {
364                self.min_symbol = Some(horizontal.min);
365                self.scrollbar = mem::take(&mut self.scrollbar).symbols(horizontal.into());
366            }
367        } else {
368            if let Some(vertical) = &self.ver_symbols {
369                self.min_symbol = Some(vertical.min);
370                self.scrollbar = mem::take(&mut self.scrollbar).symbols(vertical.into());
371            }
372        }
373    }
374
375    /// Symbol for the Scrollbar.
376    pub fn thumb_symbol(mut self, thumb_symbol: &'a str) -> Self {
377        self.scrollbar = self.scrollbar.thumb_symbol(thumb_symbol);
378        self
379    }
380
381    /// Style for the Scrollbar.
382    pub fn thumb_style<S: Into<Style>>(mut self, thumb_style: S) -> Self {
383        self.scrollbar = self.scrollbar.thumb_style(thumb_style);
384        self
385    }
386
387    /// Symbol for the Scrollbar.
388    pub fn track_symbol(mut self, track_symbol: Option<&'a str>) -> Self {
389        self.scrollbar = self.scrollbar.track_symbol(track_symbol);
390        self
391    }
392
393    /// Style for the Scrollbar.
394    pub fn track_style<S: Into<Style>>(mut self, track_style: S) -> Self {
395        self.scrollbar = self.scrollbar.track_style(track_style);
396        self
397    }
398
399    /// Symbol for the Scrollbar.
400    pub fn begin_symbol(mut self, begin_symbol: Option<&'a str>) -> Self {
401        self.scrollbar = self.scrollbar.begin_symbol(begin_symbol);
402        self
403    }
404
405    /// Style for the Scrollbar.
406    pub fn begin_style<S: Into<Style>>(mut self, begin_style: S) -> Self {
407        self.scrollbar = self.scrollbar.begin_style(begin_style);
408        self
409    }
410
411    /// Symbol for the Scrollbar.
412    pub fn end_symbol(mut self, end_symbol: Option<&'a str>) -> Self {
413        self.scrollbar = self.scrollbar.end_symbol(end_symbol);
414        self
415    }
416
417    /// Style for the Scrollbar.
418    pub fn end_style<S: Into<Style>>(mut self, end_style: S) -> Self {
419        self.scrollbar = self.scrollbar.end_style(end_style);
420        self
421    }
422
423    /// Symbol when no Scrollbar is drawn.
424    pub fn min_symbol(mut self, min_symbol: Option<&'a str>) -> Self {
425        self.min_symbol = min_symbol;
426        self
427    }
428
429    /// Style when no Scrollbar is drawn.
430    pub fn min_style<S: Into<Style>>(mut self, min_style: S) -> Self {
431        self.min_style = Some(min_style.into());
432        self
433    }
434
435    /// Set all Scrollbar symbols.
436    pub fn symbols(mut self, symbols: &ScrollSymbols) -> Self {
437        self.min_symbol = Some(symbols.min);
438        self.scrollbar = self.scrollbar.symbols(symbols.into());
439        self
440    }
441
442    /// Set a style for all Scrollbar styles.
443    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
444        let style = style.into();
445        self.min_style = Some(style);
446        self.scrollbar = self.scrollbar.style(style);
447        self
448    }
449
450    /// Padding needed to embed this Scroll.
451    pub fn padding(&self) -> Padding {
452        match self.orientation {
453            ScrollbarOrientation::VerticalRight => Padding::new(0, 1, 0, 0),
454            ScrollbarOrientation::VerticalLeft => Padding::new(1, 0, 0, 0),
455            ScrollbarOrientation::HorizontalBottom => Padding::new(0, 0, 0, 1),
456            ScrollbarOrientation::HorizontalTop => Padding::new(0, 0, 1, 0),
457        }
458    }
459}
460
461impl<'a> Scroll<'a> {
462    // create the correct scrollbar widget.
463    fn scrollbar(&self) -> Scrollbar<'a> {
464        self.scrollbar.clone()
465    }
466}
467
468impl StatefulWidget for &Scroll<'_> {
469    type State = ScrollState;
470
471    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
472        render_scroll(self, area, buf, state);
473    }
474}
475
476impl StatefulWidget for Scroll<'_> {
477    type State = ScrollState;
478
479    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
480        render_scroll(&self, area, buf, state);
481    }
482}
483
484fn render_scroll(scroll: &Scroll<'_>, area: Rect, buf: &mut Buffer, state: &mut ScrollState) {
485    state.set_orientation(scroll.orientation.clone());
486    if scroll.overscroll_by.is_some() {
487        state.set_overscroll_by(scroll.overscroll_by);
488    }
489    if scroll.scroll_by.is_some() {
490        state.set_scroll_by(scroll.scroll_by);
491    }
492    state.area = area;
493
494    if area.is_empty() {
495        return;
496    }
497
498    if state.max_offset() == 0 {
499        match scroll.policy {
500            ScrollbarPolicy::Always => {
501                scroll.scrollbar().render(
502                    area,
503                    buf,
504                    &mut ScrollbarState::new(1)
505                        .position(state.offset())
506                        .viewport_content_length(state.page_len()),
507                );
508            }
509            ScrollbarPolicy::Minimize => {
510                fill(scroll.min_symbol, scroll.min_style, area, buf);
511            }
512            ScrollbarPolicy::Collapse => {
513                // widget renders
514            }
515        }
516    } else {
517        scroll.scrollbar().render(
518            area,
519            buf,
520            &mut ScrollbarState::new(state.max_offset())
521                .position(state.offset())
522                .viewport_content_length(state.page_len()),
523        );
524    }
525}
526
527fn fill(sym: Option<&'_ str>, style: Option<Style>, area: Rect, buf: &mut Buffer) {
528    let area = buf.area.intersection(area);
529    match (sym, style) {
530        (Some(sym), Some(style)) => {
531            for y in area.top()..area.bottom() {
532                for x in area.left()..area.right() {
533                    if let Some(cell) = buf.cell_mut((x, y)) {
534                        // cell.reset();
535                        cell.set_symbol(sym);
536                        cell.set_style(style);
537                    }
538                }
539            }
540        }
541        (None, Some(style)) => {
542            for y in area.top()..area.bottom() {
543                for x in area.left()..area.right() {
544                    if let Some(cell) = buf.cell_mut((x, y)) {
545                        // cell.reset();
546                        cell.set_style(style);
547                    }
548                }
549            }
550        }
551        (Some(sym), None) => {
552            for y in area.top()..area.bottom() {
553                for x in area.left()..area.right() {
554                    if let Some(cell) = buf.cell_mut((x, y)) {
555                        cell.set_symbol(sym);
556                    }
557                }
558            }
559        }
560        (None, None) => {
561            // noop
562        }
563    }
564}
565
566impl Default for ScrollState {
567    fn default() -> Self {
568        Self {
569            area: Default::default(),
570            orientation: Default::default(),
571            offset: 0,
572            max_offset: 0,
573            page_len: 0,
574            scroll_by: None,
575            overscroll_by: None,
576            mouse: Default::default(),
577            non_exhaustive: NonExhaustive,
578        }
579    }
580}
581
582impl RelocatableState for ScrollState {
583    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
584        self.area = relocate_area(self.area, shift, clip);
585    }
586}
587
588impl ScrollState {
589    pub fn new() -> Self {
590        Self::default()
591    }
592
593    #[inline]
594    pub fn set_orientation(&mut self, orientation: ScrollbarOrientation) {
595        self.orientation = orientation;
596    }
597
598    /// Vertical scroll?
599    #[inline]
600    pub fn is_vertical(&self) -> bool {
601        self.orientation.is_vertical()
602    }
603
604    /// Horizontal scroll?
605    #[inline]
606    pub fn is_horizontal(&self) -> bool {
607        self.orientation.is_horizontal()
608    }
609
610    /// Resets the offset to 0.
611    pub fn clear(&mut self) {
612        self.offset = 0;
613    }
614
615    /// Current vertical offset.
616    #[inline]
617    pub fn offset(&self) -> usize {
618        self.offset
619    }
620
621    /// Change the offset. There is no limitation to the value
622    /// set here. It's therefore possible that this is an
623    /// invalid offset for the widget. The widget must deal
624    /// with this situation.
625    #[inline]
626    pub fn set_offset(&mut self, offset: usize) -> bool {
627        let old = self.offset;
628        self.offset = offset;
629        old != self.offset
630    }
631
632    /// Scroll to make the given pos visible. Adjusts the
633    /// offset just enough to make this happen. Does nothing if
634    /// the position is already visible.
635    #[inline]
636    pub fn scroll_to_pos(&mut self, pos: usize) -> bool {
637        let old = self.offset;
638        if pos >= self.offset + self.page_len {
639            self.offset = pos - self.page_len + 1;
640        } else if pos < self.offset {
641            self.offset = pos;
642        }
643        old != self.offset
644    }
645
646    /// Scroll to make the given range visible.Adjusts the
647    /// offset just enough to make this happen. Does nothing if
648    /// the range is already visible.
649    #[inline]
650    pub fn scroll_to_range(&mut self, range: Range<usize>) -> bool {
651        let old = self.offset;
652        // start of range first
653        if range.start >= self.offset + self.page_len {
654            if range.end - range.start < self.page_len {
655                self.offset = range.end - self.page_len;
656            } else {
657                self.offset = range.start;
658            }
659        } else if range.start < self.offset {
660            self.offset = range.start;
661        } else if range.end >= self.offset + self.page_len {
662            if range.end - range.start < self.page_len {
663                self.offset = range.end - self.page_len;
664            } else {
665                self.offset = range.start;
666            }
667        }
668        old != self.offset
669    }
670
671    /// Scroll up by n.
672    #[inline]
673    pub fn scroll_up(&mut self, n: usize) -> bool {
674        let old = self.offset;
675        self.offset = self.limited_offset(self.offset.saturating_sub(n));
676        old != self.offset
677    }
678
679    /// Scroll down by n.
680    #[inline]
681    pub fn scroll_down(&mut self, n: usize) -> bool {
682        let old = self.offset;
683        self.offset = self.limited_offset(self.offset.saturating_add(n));
684        old != self.offset
685    }
686
687    /// Scroll left by n.
688    #[inline]
689    pub fn scroll_left(&mut self, n: usize) -> bool {
690        self.scroll_up(n)
691    }
692
693    /// Scroll right by n.
694    #[inline]
695    pub fn scroll_right(&mut self, n: usize) -> bool {
696        self.scroll_down(n)
697    }
698
699    /// Calculate the offset limited to max_offset+overscroll_by.
700    #[inline]
701    pub fn limited_offset(&self, offset: usize) -> usize {
702        min(offset, self.max_offset.saturating_add(self.overscroll_by()))
703    }
704
705    /// Maximum offset that is accessible with scrolling.
706    ///
707    /// This is shorter than the length of the content by whatever fills the last page.
708    /// This is the base for the scrollbar content_length.
709    #[inline]
710    pub fn max_offset(&self) -> usize {
711        self.max_offset
712    }
713
714    /// Maximum offset that is accessible with scrolling.
715    ///
716    /// This is shorter than the length of the content by whatever fills the last page.
717    /// This is the base for the scrollbar content_length.
718    #[inline]
719    pub fn set_max_offset(&mut self, max: usize) {
720        self.max_offset = max;
721    }
722
723    /// Page-size at the current offset.
724    #[inline]
725    pub fn page_len(&self) -> usize {
726        self.page_len
727    }
728
729    /// Page-size at the current offset.
730    #[inline]
731    pub fn set_page_len(&mut self, page: usize) {
732        self.page_len = page;
733    }
734
735    /// Suggested scroll per scroll-event.
736    /// Defaults to 1/10 of the page
737    #[inline]
738    pub fn scroll_by(&self) -> usize {
739        if let Some(scroll) = self.scroll_by {
740            max(scroll, 1)
741        } else {
742            max(self.page_len / 10, 1)
743        }
744    }
745
746    /// Suggested scroll per scroll-event.
747    /// Defaults to 1/10 of the page
748    #[inline]
749    pub fn set_scroll_by(&mut self, scroll: Option<usize>) {
750        self.scroll_by = scroll;
751    }
752
753    /// Allowed overscroll
754    #[inline]
755    pub fn overscroll_by(&self) -> usize {
756        self.overscroll_by.unwrap_or_default()
757    }
758
759    /// Allowed overscroll
760    #[inline]
761    pub fn set_overscroll_by(&mut self, overscroll_by: Option<usize>) {
762        self.overscroll_by = overscroll_by;
763    }
764
765    /// Update the state to match adding items.
766    #[inline]
767    pub fn items_added(&mut self, pos: usize, n: usize) {
768        if self.offset >= pos {
769            self.offset += n;
770        }
771        self.max_offset += n;
772    }
773
774    /// Update the state to match removing items.
775    #[inline]
776    pub fn items_removed(&mut self, pos: usize, n: usize) {
777        if self.offset >= pos && self.offset >= n {
778            self.offset -= n;
779        }
780        self.max_offset = self.max_offset.saturating_sub(n);
781    }
782}
783
784impl ScrollState {
785    /// Maps a screen-position to an offset.
786    /// pos - row/column clicked
787    /// base - x/y of the range
788    /// length - width/height of the range.
789    pub fn map_position_index(&self, pos: u16, base: u16, length: u16) -> usize {
790        // correct for the arrows.
791        let pos = pos.saturating_sub(base).saturating_sub(1) as usize;
792        let span = length.saturating_sub(2) as usize;
793
794        if span > 0 {
795            (self.max_offset.saturating_mul(pos)) / span
796        } else {
797            0
798        }
799    }
800}
801
802impl HandleEvent<crossterm::event::Event, MouseOnly, ScrollOutcome> for ScrollState {
803    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> ScrollOutcome {
804        match event {
805            ct_event!(mouse any for m) if self.mouse.drag(self.area, m) => {
806                if self.is_vertical() {
807                    if m.row >= self.area.y {
808                        ScrollOutcome::VPos(self.map_position_index(
809                            m.row,
810                            self.area.y,
811                            self.area.height,
812                        ))
813                    } else {
814                        ScrollOutcome::Unchanged
815                    }
816                } else {
817                    if m.column >= self.area.x {
818                        ScrollOutcome::HPos(self.map_position_index(
819                            m.column,
820                            self.area.x,
821                            self.area.width,
822                        ))
823                    } else {
824                        ScrollOutcome::Unchanged
825                    }
826                }
827            }
828            ct_event!(mouse down Left for col, row) if self.area.contains((*col, *row).into()) => {
829                if self.is_vertical() {
830                    ScrollOutcome::VPos(self.map_position_index(
831                        *row,
832                        self.area.y,
833                        self.area.height,
834                    ))
835                } else {
836                    ScrollOutcome::HPos(self.map_position_index(*col, self.area.x, self.area.width))
837                }
838            }
839            ct_event!(scroll down for col, row)
840                if self.is_vertical() && self.area.contains((*col, *row).into()) =>
841            {
842                ScrollOutcome::Down(self.scroll_by())
843            }
844            ct_event!(scroll up for col, row)
845                if self.is_vertical() && self.area.contains((*col, *row).into()) =>
846            {
847                ScrollOutcome::Up(self.scroll_by())
848            }
849            // right scroll with ALT down. shift doesn't work?
850            ct_event!(scroll ALT down for col, row)
851                if self.is_horizontal() && self.area.contains((*col, *row).into()) =>
852            {
853                ScrollOutcome::Right(self.scroll_by())
854            }
855            // left scroll with ALT up. shift doesn't work?
856            ct_event!(scroll ALT up for col, row)
857                if self.is_horizontal() && self.area.contains((*col, *row).into()) =>
858            {
859                ScrollOutcome::Left(self.scroll_by())
860            }
861            _ => ScrollOutcome::Continue,
862        }
863    }
864}