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