rat_scrolled/
scroll.rs

1use crate::_private::NonExhaustive;
2use crate::event::ScrollOutcome;
3use crate::ScrollbarPolicy;
4use rat_event::util::MouseFlags;
5use rat_event::{ct_event, HandleEvent, MouseOnly};
6use rat_reloc::{relocate_area, RelocatableState};
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    /// Scrollbar policy.
208    pub fn policy(mut self, policy: ScrollbarPolicy) -> Self {
209        self.policy = policy;
210        self
211    }
212
213    /// Scrollbar policy.
214    pub fn get_policy(&self) -> ScrollbarPolicy {
215        self.policy
216    }
217
218    /// Scrollbar orientation.
219    pub fn orientation(mut self, orientation: ScrollbarOrientation) -> Self {
220        if self.orientation != orientation {
221            self.orientation = orientation.clone();
222            self.scrollbar = self.scrollbar.orientation(orientation);
223            self.update_symbols();
224        }
225        self
226    }
227
228    /// Scrollbar orientation.
229    pub fn get_orientation(&self) -> ScrollbarOrientation {
230        self.orientation.clone()
231    }
232
233    /// Ensures a vertical orientation of the scrollbar.
234    ///
235    /// If the orientation is not vertical it will be set to VerticalRight.
236    pub fn override_vertical(mut self) -> Self {
237        let orientation = match self.orientation {
238            ScrollbarOrientation::VerticalRight => ScrollbarOrientation::VerticalRight,
239            ScrollbarOrientation::VerticalLeft => ScrollbarOrientation::VerticalLeft,
240            ScrollbarOrientation::HorizontalBottom => ScrollbarOrientation::VerticalRight,
241            ScrollbarOrientation::HorizontalTop => ScrollbarOrientation::VerticalRight,
242        };
243        if self.orientation != orientation {
244            self.orientation = orientation.clone();
245            self.scrollbar = self.scrollbar.orientation(orientation);
246            self.update_symbols();
247        }
248        self
249    }
250
251    /// Ensures a horizontal orientation of the scrollbar.
252    ///
253    /// If the orientation is not horizontal, it will be set to HorizontalBottom.
254    pub fn override_horizontal(mut self) -> Self {
255        let orientation = match self.orientation {
256            ScrollbarOrientation::VerticalRight => ScrollbarOrientation::HorizontalBottom,
257            ScrollbarOrientation::VerticalLeft => ScrollbarOrientation::HorizontalBottom,
258            ScrollbarOrientation::HorizontalBottom => ScrollbarOrientation::HorizontalBottom,
259            ScrollbarOrientation::HorizontalTop => ScrollbarOrientation::HorizontalTop,
260        };
261        if self.orientation != orientation {
262            self.orientation = orientation.clone();
263            self.scrollbar = self.scrollbar.orientation(orientation);
264            self.update_symbols();
265        }
266        self
267    }
268
269    /// Is this a vertical scrollbar.
270    pub fn is_vertical(&self) -> bool {
271        match self.orientation {
272            ScrollbarOrientation::VerticalRight => true,
273            ScrollbarOrientation::VerticalLeft => true,
274            ScrollbarOrientation::HorizontalBottom => false,
275            ScrollbarOrientation::HorizontalTop => false,
276        }
277    }
278
279    /// Is this a horizontal scrollbar.
280    pub fn is_horizontal(&self) -> bool {
281        match self.orientation {
282            ScrollbarOrientation::VerticalRight => false,
283            ScrollbarOrientation::VerticalLeft => false,
284            ScrollbarOrientation::HorizontalBottom => true,
285            ScrollbarOrientation::HorizontalTop => true,
286        }
287    }
288
289    /// Leave a margin at the start of the scrollbar.
290    pub fn start_margin(mut self, start_margin: u16) -> Self {
291        self.start_margin = start_margin;
292        self
293    }
294
295    /// Margin before the start of the scrollbar.
296    pub fn get_start_margin(&self) -> u16 {
297        self.start_margin
298    }
299
300    /// Leave a margin at the end of the scrollbar.
301    pub fn end_margin(mut self, end_margin: u16) -> Self {
302        self.end_margin = end_margin;
303        self
304    }
305
306    /// Margin after the end of the scrollbar.
307    pub fn get_end_margin(&self) -> u16 {
308        self.end_margin
309    }
310
311    /// Set overscrolling to this value.
312    pub fn overscroll_by(mut self, overscroll: usize) -> Self {
313        self.overscroll_by = Some(overscroll);
314        self
315    }
316
317    /// Set scroll increment.
318    pub fn scroll_by(mut self, scroll: usize) -> Self {
319        self.scroll_by = Some(scroll);
320        self
321    }
322
323    /// Set all styles.
324    pub fn styles(mut self, styles: ScrollStyle) -> Self {
325        if let Some(horizontal) = styles.horizontal {
326            self.hor_symbols = Some(horizontal);
327        }
328        if let Some(vertical) = styles.vertical {
329            self.ver_symbols = Some(vertical);
330        }
331        self.update_symbols();
332
333        if let Some(thumb_style) = styles.thumb_style {
334            self.scrollbar = self.scrollbar.thumb_style(thumb_style);
335        }
336        if let Some(track_style) = styles.track_style {
337            self.scrollbar = self.scrollbar.track_style(track_style);
338        }
339        if let Some(begin_style) = styles.begin_style {
340            self.scrollbar = self.scrollbar.begin_style(begin_style);
341        }
342        if let Some(end_style) = styles.end_style {
343            self.scrollbar = self.scrollbar.end_style(end_style);
344        }
345        if styles.min_style.is_some() {
346            self.min_style = styles.min_style;
347        }
348        self
349    }
350
351    fn update_symbols(&mut self) {
352        if self.is_horizontal() {
353            if let Some(horizontal) = &self.hor_symbols {
354                self.min_symbol = Some(horizontal.min);
355                self.scrollbar = mem::take(&mut self.scrollbar).symbols(horizontal.into());
356            }
357        } else {
358            if let Some(vertical) = &self.ver_symbols {
359                self.min_symbol = Some(vertical.min);
360                self.scrollbar = mem::take(&mut self.scrollbar).symbols(vertical.into());
361            }
362        }
363    }
364
365    /// Symbol for the Scrollbar.
366    pub fn thumb_symbol(mut self, thumb_symbol: &'a str) -> Self {
367        self.scrollbar = self.scrollbar.thumb_symbol(thumb_symbol);
368        self
369    }
370
371    /// Style for the Scrollbar.
372    pub fn thumb_style<S: Into<Style>>(mut self, thumb_style: S) -> Self {
373        self.scrollbar = self.scrollbar.thumb_style(thumb_style);
374        self
375    }
376
377    /// Symbol for the Scrollbar.
378    pub fn track_symbol(mut self, track_symbol: Option<&'a str>) -> Self {
379        self.scrollbar = self.scrollbar.track_symbol(track_symbol);
380        self
381    }
382
383    /// Style for the Scrollbar.
384    pub fn track_style<S: Into<Style>>(mut self, track_style: S) -> Self {
385        self.scrollbar = self.scrollbar.track_style(track_style);
386        self
387    }
388
389    /// Symbol for the Scrollbar.
390    pub fn begin_symbol(mut self, begin_symbol: Option<&'a str>) -> Self {
391        self.scrollbar = self.scrollbar.begin_symbol(begin_symbol);
392        self
393    }
394
395    /// Style for the Scrollbar.
396    pub fn begin_style<S: Into<Style>>(mut self, begin_style: S) -> Self {
397        self.scrollbar = self.scrollbar.begin_style(begin_style);
398        self
399    }
400
401    /// Symbol for the Scrollbar.
402    pub fn end_symbol(mut self, end_symbol: Option<&'a str>) -> Self {
403        self.scrollbar = self.scrollbar.end_symbol(end_symbol);
404        self
405    }
406
407    /// Style for the Scrollbar.
408    pub fn end_style<S: Into<Style>>(mut self, end_style: S) -> Self {
409        self.scrollbar = self.scrollbar.end_style(end_style);
410        self
411    }
412
413    /// Symbol when no Scrollbar is drawn.
414    pub fn min_symbol(mut self, min_symbol: Option<&'a str>) -> Self {
415        self.min_symbol = min_symbol;
416        self
417    }
418
419    /// Style when no Scrollbar is drawn.
420    pub fn min_style<S: Into<Style>>(mut self, min_style: S) -> Self {
421        self.min_style = Some(min_style.into());
422        self
423    }
424
425    /// Set all Scrollbar symbols.
426    pub fn symbols(mut self, symbols: &ScrollSymbols) -> Self {
427        self.min_symbol = Some(symbols.min);
428        self.scrollbar = self.scrollbar.symbols(symbols.into());
429        self
430    }
431
432    /// Set a style for all Scrollbar styles.
433    pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
434        let style = style.into();
435        self.min_style = Some(style);
436        self.scrollbar = self.scrollbar.style(style);
437        self
438    }
439
440    /// Padding needed to embed this Scroll.
441    pub fn padding(&self) -> Padding {
442        match self.orientation {
443            ScrollbarOrientation::VerticalRight => Padding::new(0, 1, 0, 0),
444            ScrollbarOrientation::VerticalLeft => Padding::new(1, 0, 0, 0),
445            ScrollbarOrientation::HorizontalBottom => Padding::new(0, 0, 0, 1),
446            ScrollbarOrientation::HorizontalTop => Padding::new(0, 0, 1, 0),
447        }
448    }
449}
450
451impl<'a> Scroll<'a> {
452    // create the correct scrollbar widget.
453    fn scrollbar(&self) -> Scrollbar<'a> {
454        self.scrollbar.clone()
455    }
456}
457
458impl StatefulWidget for &Scroll<'_> {
459    type State = ScrollState;
460
461    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
462        render_scroll(self, area, buf, state);
463    }
464}
465
466impl StatefulWidget for Scroll<'_> {
467    type State = ScrollState;
468
469    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
470        render_scroll(&self, area, buf, state);
471    }
472}
473
474fn render_scroll(scroll: &Scroll<'_>, area: Rect, buf: &mut Buffer, state: &mut ScrollState) {
475    state.set_orientation(scroll.orientation.clone());
476    if scroll.overscroll_by.is_some() {
477        state.set_overscroll_by(scroll.overscroll_by);
478    }
479    if scroll.scroll_by.is_some() {
480        state.set_scroll_by(scroll.scroll_by);
481    }
482    state.area = area;
483
484    if area.is_empty() {
485        return;
486    }
487
488    if state.max_offset() == 0 {
489        match scroll.policy {
490            ScrollbarPolicy::Always => {
491                scroll.scrollbar().render(
492                    area,
493                    buf,
494                    &mut ScrollbarState::new(1)
495                        .position(state.offset())
496                        .viewport_content_length(state.page_len()),
497                );
498            }
499            ScrollbarPolicy::Minimize => {
500                fill(scroll.min_symbol, scroll.min_style, area, buf);
501            }
502            ScrollbarPolicy::Collapse => {
503                // widget renders
504            }
505        }
506    } else {
507        scroll.scrollbar().render(
508            area,
509            buf,
510            &mut ScrollbarState::new(state.max_offset())
511                .position(state.offset())
512                .viewport_content_length(state.page_len()),
513        );
514    }
515}
516
517fn fill(sym: Option<&'_ str>, style: Option<Style>, area: Rect, buf: &mut Buffer) {
518    let area = buf.area.intersection(area);
519    match (sym, style) {
520        (Some(sym), Some(style)) => {
521            for y in area.top()..area.bottom() {
522                for x in area.left()..area.right() {
523                    if let Some(cell) = buf.cell_mut((x, y)) {
524                        // cell.reset();
525                        cell.set_symbol(sym);
526                        cell.set_style(style);
527                    }
528                }
529            }
530        }
531        (None, Some(style)) => {
532            for y in area.top()..area.bottom() {
533                for x in area.left()..area.right() {
534                    if let Some(cell) = buf.cell_mut((x, y)) {
535                        // cell.reset();
536                        cell.set_style(style);
537                    }
538                }
539            }
540        }
541        (Some(sym), None) => {
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.set_symbol(sym);
546                    }
547                }
548            }
549        }
550        (None, None) => {
551            // noop
552        }
553    }
554}
555
556impl Default for ScrollState {
557    fn default() -> Self {
558        Self {
559            area: Default::default(),
560            orientation: Default::default(),
561            offset: 0,
562            max_offset: 0,
563            page_len: 0,
564            scroll_by: None,
565            overscroll_by: None,
566            mouse: Default::default(),
567            non_exhaustive: NonExhaustive,
568        }
569    }
570}
571
572impl RelocatableState for ScrollState {
573    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
574        self.area = relocate_area(self.area, shift, clip);
575    }
576}
577
578impl ScrollState {
579    pub fn new() -> Self {
580        Self::default()
581    }
582
583    #[inline]
584    pub fn set_orientation(&mut self, orientation: ScrollbarOrientation) {
585        self.orientation = orientation;
586    }
587
588    /// Vertical scroll?
589    #[inline]
590    pub fn is_vertical(&self) -> bool {
591        self.orientation.is_vertical()
592    }
593
594    /// Horizontal scroll?
595    #[inline]
596    pub fn is_horizontal(&self) -> bool {
597        self.orientation.is_horizontal()
598    }
599
600    /// Resets the offset to 0.
601    pub fn clear(&mut self) {
602        self.offset = 0;
603    }
604
605    /// Current vertical offset.
606    #[inline]
607    pub fn offset(&self) -> usize {
608        self.offset
609    }
610
611    /// Change the offset. There is no limitation to the value
612    /// set here. It's therefore possible that this is an
613    /// invalid offset for the widget. The widget must deal
614    /// with this situation.
615    #[inline]
616    pub fn set_offset(&mut self, offset: usize) -> bool {
617        let old = self.offset;
618        self.offset = offset;
619        old != self.offset
620    }
621
622    /// Scroll to make the given pos visible. Adjusts the
623    /// offset just enough to make this happen. Does nothing if
624    /// the position is already visible.
625    #[inline]
626    pub fn scroll_to_pos(&mut self, pos: usize) -> bool {
627        let old = self.offset;
628        if pos >= self.offset + self.page_len {
629            self.offset = pos - self.page_len + 1;
630        } else if pos < self.offset {
631            self.offset = pos;
632        }
633        old != self.offset
634    }
635
636    /// Scroll to make the given range visible.Adjusts the
637    /// offset just enough to make this happen. Does nothing if
638    /// the range is already visible.
639    #[inline]
640    pub fn scroll_to_range(&mut self, range: Range<usize>) -> bool {
641        let old = self.offset;
642        // start of range first
643        if range.start >= self.offset + self.page_len {
644            if range.end - range.start < self.page_len {
645                self.offset = range.end - self.page_len + 1;
646            } else {
647                self.offset = range.start;
648            }
649        } else if range.start < self.offset {
650            self.offset = range.start;
651        } else if range.end >= self.offset + self.page_len {
652            if range.end - range.start < self.page_len {
653                self.offset = range.end - self.page_len + 1;
654            } else {
655                self.offset = range.start;
656            }
657        }
658        old != self.offset
659    }
660
661    /// Scroll up by n.
662    #[inline]
663    pub fn scroll_up(&mut self, n: usize) -> bool {
664        let old = self.offset;
665        self.offset = self.limited_offset(self.offset.saturating_sub(n));
666        old != self.offset
667    }
668
669    /// Scroll down by n.
670    #[inline]
671    pub fn scroll_down(&mut self, n: usize) -> bool {
672        let old = self.offset;
673        self.offset = self.limited_offset(self.offset.saturating_add(n));
674        old != self.offset
675    }
676
677    /// Scroll left by n.
678    #[inline]
679    pub fn scroll_left(&mut self, n: usize) -> bool {
680        self.scroll_up(n)
681    }
682
683    /// Scroll right by n.
684    #[inline]
685    pub fn scroll_right(&mut self, n: usize) -> bool {
686        self.scroll_down(n)
687    }
688
689    /// Calculate the offset limited to max_offset+overscroll_by.
690    #[inline]
691    pub fn limited_offset(&self, offset: usize) -> usize {
692        min(offset, self.max_offset.saturating_add(self.overscroll_by()))
693    }
694
695    /// Maximum offset that is accessible with scrolling.
696    ///
697    /// This is shorter than the length of the content by whatever fills the last page.
698    /// This is the base for the scrollbar content_length.
699    #[inline]
700    pub fn max_offset(&self) -> usize {
701        self.max_offset
702    }
703
704    /// Maximum offset that is accessible with scrolling.
705    ///
706    /// This is shorter than the length of the content by whatever fills the last page.
707    /// This is the base for the scrollbar content_length.
708    #[inline]
709    pub fn set_max_offset(&mut self, max: usize) {
710        self.max_offset = max;
711    }
712
713    /// Page-size at the current offset.
714    #[inline]
715    pub fn page_len(&self) -> usize {
716        self.page_len
717    }
718
719    /// Page-size at the current offset.
720    #[inline]
721    pub fn set_page_len(&mut self, page: usize) {
722        self.page_len = page;
723    }
724
725    /// Suggested scroll per scroll-event.
726    /// Defaults to 1/10 of the page
727    #[inline]
728    pub fn scroll_by(&self) -> usize {
729        if let Some(scroll) = self.scroll_by {
730            max(scroll, 1)
731        } else {
732            max(self.page_len / 10, 1)
733        }
734    }
735
736    /// Suggested scroll per scroll-event.
737    /// Defaults to 1/10 of the page
738    #[inline]
739    pub fn set_scroll_by(&mut self, scroll: Option<usize>) {
740        self.scroll_by = scroll;
741    }
742
743    /// Allowed overscroll
744    #[inline]
745    pub fn overscroll_by(&self) -> usize {
746        self.overscroll_by.unwrap_or_default()
747    }
748
749    /// Allowed overscroll
750    #[inline]
751    pub fn set_overscroll_by(&mut self, overscroll_by: Option<usize>) {
752        self.overscroll_by = overscroll_by;
753    }
754
755    /// Update the state to match adding items.
756    #[inline]
757    pub fn items_added(&mut self, pos: usize, n: usize) {
758        if self.offset >= pos {
759            self.offset += n;
760        }
761        self.max_offset += n;
762    }
763
764    /// Update the state to match removing items.
765    #[inline]
766    pub fn items_removed(&mut self, pos: usize, n: usize) {
767        if self.offset >= pos && self.offset >= n {
768            self.offset -= n;
769        }
770        self.max_offset = self.max_offset.saturating_sub(n);
771    }
772}
773
774impl ScrollState {
775    /// Maps a screen-position to an offset.
776    /// pos - row/column clicked
777    /// base - x/y of the range
778    /// length - width/height of the range.
779    pub fn map_position_index(&self, pos: u16, base: u16, length: u16) -> usize {
780        // correct for the arrows.
781        let pos = pos.saturating_sub(base).saturating_sub(1) as usize;
782        let span = length.saturating_sub(2) as usize;
783
784        if span > 0 {
785            (self.max_offset.saturating_mul(pos)) / span
786        } else {
787            0
788        }
789    }
790}
791
792impl HandleEvent<crossterm::event::Event, MouseOnly, ScrollOutcome> for ScrollState {
793    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> ScrollOutcome {
794        match event {
795            ct_event!(mouse any for m) if self.mouse.drag(self.area, m) => {
796                if self.is_vertical() {
797                    if m.row >= self.area.y {
798                        ScrollOutcome::VPos(self.map_position_index(
799                            m.row,
800                            self.area.y,
801                            self.area.height,
802                        ))
803                    } else {
804                        ScrollOutcome::Unchanged
805                    }
806                } else {
807                    if m.column >= self.area.x {
808                        ScrollOutcome::HPos(self.map_position_index(
809                            m.column,
810                            self.area.x,
811                            self.area.width,
812                        ))
813                    } else {
814                        ScrollOutcome::Unchanged
815                    }
816                }
817            }
818            ct_event!(mouse down Left for col, row) if self.area.contains((*col, *row).into()) => {
819                if self.is_vertical() {
820                    ScrollOutcome::VPos(self.map_position_index(
821                        *row,
822                        self.area.y,
823                        self.area.height,
824                    ))
825                } else {
826                    ScrollOutcome::HPos(self.map_position_index(*col, self.area.x, self.area.width))
827                }
828            }
829            ct_event!(scroll down for col, row)
830                if self.is_vertical() && self.area.contains((*col, *row).into()) =>
831            {
832                ScrollOutcome::Down(self.scroll_by())
833            }
834            ct_event!(scroll up for col, row)
835                if self.is_vertical() && self.area.contains((*col, *row).into()) =>
836            {
837                ScrollOutcome::Up(self.scroll_by())
838            }
839            // right scroll with ALT down. shift doesn't work?
840            ct_event!(scroll ALT down for col, row)
841                if self.is_horizontal() && self.area.contains((*col, *row).into()) =>
842            {
843                ScrollOutcome::Right(self.scroll_by())
844            }
845            // left scroll with ALT up. shift doesn't work?
846            ct_event!(scroll ALT up for col, row)
847                if self.is_horizontal() && self.area.contains((*col, *row).into()) =>
848            {
849                ScrollOutcome::Left(self.scroll_by())
850            }
851            _ => ScrollOutcome::Continue,
852        }
853    }
854}