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    pub fn padding(&self) -> Padding {
441        match self.orientation {
442            ScrollbarOrientation::VerticalRight => Padding::new(0, 1, 0, 0),
443            ScrollbarOrientation::VerticalLeft => Padding::new(1, 0, 0, 0),
444            ScrollbarOrientation::HorizontalBottom => Padding::new(0, 0, 0, 1),
445            ScrollbarOrientation::HorizontalTop => Padding::new(0, 0, 1, 0),
446        }
447    }
448}
449
450impl<'a> Scroll<'a> {
451    // create the correct scrollbar widget.
452    fn scrollbar(&self) -> Scrollbar<'a> {
453        self.scrollbar.clone()
454    }
455}
456
457impl StatefulWidget for &Scroll<'_> {
458    type State = ScrollState;
459
460    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
461        render_scroll(self, area, buf, state);
462    }
463}
464
465impl StatefulWidget for Scroll<'_> {
466    type State = ScrollState;
467
468    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
469        render_scroll(&self, area, buf, state);
470    }
471}
472
473fn render_scroll(scroll: &Scroll<'_>, area: Rect, buf: &mut Buffer, state: &mut ScrollState) {
474    state.set_orientation(scroll.orientation.clone());
475    if scroll.overscroll_by.is_some() {
476        state.set_overscroll_by(scroll.overscroll_by);
477    }
478    if scroll.scroll_by.is_some() {
479        state.set_scroll_by(scroll.scroll_by);
480    }
481    state.area = area;
482
483    if area.is_empty() {
484        return;
485    }
486
487    if state.max_offset() == 0 {
488        match scroll.policy {
489            ScrollbarPolicy::Always => {
490                scroll.scrollbar().render(
491                    area,
492                    buf,
493                    &mut ScrollbarState::new(state.max_offset())
494                        .position(state.offset())
495                        .viewport_content_length(state.page_len()),
496                );
497            }
498            ScrollbarPolicy::Minimize => {
499                fill(scroll.min_symbol, scroll.min_style, area, buf);
500            }
501            ScrollbarPolicy::Collapse => {
502                // widget renders
503            }
504        }
505    } else {
506        scroll.scrollbar().render(
507            area,
508            buf,
509            &mut ScrollbarState::new(state.max_offset())
510                .position(state.offset())
511                .viewport_content_length(state.page_len()),
512        );
513    }
514}
515
516fn fill(sym: Option<&'_ str>, style: Option<Style>, area: Rect, buf: &mut Buffer) {
517    let area = buf.area.intersection(area);
518    match (sym, style) {
519        (Some(sym), Some(style)) => {
520            for y in area.top()..area.bottom() {
521                for x in area.left()..area.right() {
522                    if let Some(cell) = buf.cell_mut((x, y)) {
523                        // cell.reset();
524                        cell.set_symbol(sym);
525                        cell.set_style(style);
526                    }
527                }
528            }
529        }
530        (None, 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_style(style);
536                    }
537                }
538            }
539        }
540        (Some(sym), None) => {
541            for y in area.top()..area.bottom() {
542                for x in area.left()..area.right() {
543                    if let Some(cell) = buf.cell_mut((x, y)) {
544                        cell.set_symbol(sym);
545                    }
546                }
547            }
548        }
549        (None, None) => {
550            // noop
551        }
552    }
553}
554
555impl Default for ScrollState {
556    fn default() -> Self {
557        Self {
558            area: Default::default(),
559            orientation: Default::default(),
560            offset: 0,
561            max_offset: 0,
562            page_len: 0,
563            scroll_by: None,
564            overscroll_by: None,
565            mouse: Default::default(),
566            non_exhaustive: NonExhaustive,
567        }
568    }
569}
570
571impl RelocatableState for ScrollState {
572    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
573        self.area = relocate_area(self.area, shift, clip);
574    }
575}
576
577impl ScrollState {
578    pub fn new() -> Self {
579        Self::default()
580    }
581
582    #[inline]
583    pub fn set_orientation(&mut self, orientation: ScrollbarOrientation) {
584        self.orientation = orientation;
585    }
586
587    /// Vertical scroll?
588    #[inline]
589    pub fn is_vertical(&self) -> bool {
590        self.orientation.is_vertical()
591    }
592
593    /// Horizontal scroll?
594    #[inline]
595    pub fn is_horizontal(&self) -> bool {
596        self.orientation.is_horizontal()
597    }
598
599    /// Resets the offset to 0.
600    pub fn clear(&mut self) {
601        self.offset = 0;
602    }
603
604    /// Current vertical offset.
605    #[inline]
606    pub fn offset(&self) -> usize {
607        self.offset
608    }
609
610    /// Change the offset. There is no limitation to the value
611    /// set here. It's therefore possible that this is an
612    /// invalid offset for the widget. The widget must deal
613    /// with this situation.
614    #[inline]
615    pub fn set_offset(&mut self, offset: usize) -> bool {
616        let old = self.offset;
617        self.offset = offset;
618        old != self.offset
619    }
620
621    /// Scroll to make the given pos visible. Adjusts the
622    /// offset just enough to make this happen. Does nothing if
623    /// the position is already visible.
624    #[inline]
625    pub fn scroll_to_pos(&mut self, pos: usize) -> bool {
626        let old = self.offset;
627        if pos >= self.offset + self.page_len {
628            self.offset = pos - self.page_len + 1;
629        } else if pos < self.offset {
630            self.offset = pos;
631        }
632        old != self.offset
633    }
634
635    /// Scroll to make the given range visible.Adjusts the
636    /// offset just enough to make this happen. Does nothing if
637    /// the range is already visible.
638    #[inline]
639    pub fn scroll_to_range(&mut self, range: Range<usize>) -> bool {
640        let old = self.offset;
641        // start of range first
642        if range.start >= self.offset + self.page_len {
643            if range.end - range.start < self.page_len {
644                self.offset = range.end - self.page_len + 1;
645            } else {
646                self.offset = range.start;
647            }
648        } else if range.start < self.offset {
649            self.offset = range.start;
650        } else if range.end >= self.offset + self.page_len {
651            if range.end - range.start < self.page_len {
652                self.offset = range.end - self.page_len + 1;
653            } else {
654                self.offset = range.start;
655            }
656        }
657        old != self.offset
658    }
659
660    /// Scroll up by n.
661    #[inline]
662    pub fn scroll_up(&mut self, n: usize) -> bool {
663        let old = self.offset;
664        self.offset = self.limited_offset(self.offset.saturating_sub(n));
665        old != self.offset
666    }
667
668    /// Scroll down by n.
669    #[inline]
670    pub fn scroll_down(&mut self, n: usize) -> bool {
671        let old = self.offset;
672        self.offset = self.limited_offset(self.offset.saturating_add(n));
673        old != self.offset
674    }
675
676    /// Scroll left by n.
677    #[inline]
678    pub fn scroll_left(&mut self, n: usize) -> bool {
679        self.scroll_up(n)
680    }
681
682    /// Scroll right by n.
683    #[inline]
684    pub fn scroll_right(&mut self, n: usize) -> bool {
685        self.scroll_down(n)
686    }
687
688    /// Calculate the offset limited to max_offset+overscroll_by.
689    #[inline]
690    pub fn limited_offset(&self, offset: usize) -> usize {
691        min(offset, self.max_offset.saturating_add(self.overscroll_by()))
692    }
693
694    /// Maximum offset that is accessible with scrolling.
695    ///
696    /// This is shorter than the length of the content by whatever fills the last page.
697    /// This is the base for the scrollbar content_length.
698    #[inline]
699    pub fn max_offset(&self) -> usize {
700        self.max_offset
701    }
702
703    /// Maximum offset that is accessible with scrolling.
704    ///
705    /// This is shorter than the length of the content by whatever fills the last page.
706    /// This is the base for the scrollbar content_length.
707    #[inline]
708    pub fn set_max_offset(&mut self, max: usize) {
709        self.max_offset = max;
710    }
711
712    /// Page-size at the current offset.
713    #[inline]
714    pub fn page_len(&self) -> usize {
715        self.page_len
716    }
717
718    /// Page-size at the current offset.
719    #[inline]
720    pub fn set_page_len(&mut self, page: usize) {
721        self.page_len = page;
722    }
723
724    /// Suggested scroll per scroll-event.
725    /// Defaults to 1/10 of the page
726    #[inline]
727    pub fn scroll_by(&self) -> usize {
728        if let Some(scroll) = self.scroll_by {
729            max(scroll, 1)
730        } else {
731            max(self.page_len / 10, 1)
732        }
733    }
734
735    /// Suggested scroll per scroll-event.
736    /// Defaults to 1/10 of the page
737    #[inline]
738    pub fn set_scroll_by(&mut self, scroll: Option<usize>) {
739        self.scroll_by = scroll;
740    }
741
742    /// Allowed overscroll
743    #[inline]
744    pub fn overscroll_by(&self) -> usize {
745        self.overscroll_by.unwrap_or_default()
746    }
747
748    /// Allowed overscroll
749    #[inline]
750    pub fn set_overscroll_by(&mut self, overscroll_by: Option<usize>) {
751        self.overscroll_by = overscroll_by;
752    }
753
754    /// Update the state to match adding items.
755    #[inline]
756    pub fn items_added(&mut self, pos: usize, n: usize) {
757        if self.offset >= pos {
758            self.offset += n;
759        }
760        self.max_offset += n;
761    }
762
763    /// Update the state to match removing items.
764    #[inline]
765    pub fn items_removed(&mut self, pos: usize, n: usize) {
766        if self.offset >= pos && self.offset >= n {
767            self.offset -= n;
768        }
769        self.max_offset = self.max_offset.saturating_sub(n);
770    }
771}
772
773impl ScrollState {
774    /// Maps a screen-position to an offset.
775    /// pos - row/column clicked
776    /// base - x/y of the range
777    /// length - width/height of the range.
778    pub fn map_position_index(&self, pos: u16, base: u16, length: u16) -> usize {
779        // correct for the arrows.
780        let pos = pos.saturating_sub(base).saturating_sub(1) as usize;
781        let span = length.saturating_sub(2) as usize;
782
783        if span > 0 {
784            (self.max_offset.saturating_mul(pos)) / span
785        } else {
786            0
787        }
788    }
789}
790
791impl HandleEvent<crossterm::event::Event, MouseOnly, ScrollOutcome> for ScrollState {
792    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> ScrollOutcome {
793        match event {
794            ct_event!(mouse any for m) if self.mouse.drag(self.area, m) => {
795                if self.is_vertical() {
796                    if m.row >= self.area.y {
797                        ScrollOutcome::VPos(self.map_position_index(
798                            m.row,
799                            self.area.y,
800                            self.area.height,
801                        ))
802                    } else {
803                        ScrollOutcome::Unchanged
804                    }
805                } else {
806                    if m.column >= self.area.x {
807                        ScrollOutcome::HPos(self.map_position_index(
808                            m.column,
809                            self.area.x,
810                            self.area.width,
811                        ))
812                    } else {
813                        ScrollOutcome::Unchanged
814                    }
815                }
816            }
817            ct_event!(mouse down Left for col, row) if self.area.contains((*col, *row).into()) => {
818                if self.is_vertical() {
819                    ScrollOutcome::VPos(self.map_position_index(
820                        *row,
821                        self.area.y,
822                        self.area.height,
823                    ))
824                } else {
825                    ScrollOutcome::HPos(self.map_position_index(*col, self.area.x, self.area.width))
826                }
827            }
828            ct_event!(scroll down for col, row)
829                if self.is_vertical() && self.area.contains((*col, *row).into()) =>
830            {
831                ScrollOutcome::Down(self.scroll_by())
832            }
833            ct_event!(scroll up for col, row)
834                if self.is_vertical() && self.area.contains((*col, *row).into()) =>
835            {
836                ScrollOutcome::Up(self.scroll_by())
837            }
838            // right scroll with ALT down. shift doesn't work?
839            ct_event!(scroll ALT down for col, row)
840                if self.is_horizontal() && self.area.contains((*col, *row).into()) =>
841            {
842                ScrollOutcome::Right(self.scroll_by())
843            }
844            // left scroll with ALT up. shift doesn't work?
845            ct_event!(scroll ALT up for col, row)
846                if self.is_horizontal() && self.area.contains((*col, *row).into()) =>
847            {
848                ScrollOutcome::Left(self.scroll_by())
849            }
850            _ => ScrollOutcome::Continue,
851        }
852    }
853}