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