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