rat_widget/
view.rs

1//! A view allows scrolling of on or more widgets without builtin
2//! support for scrolling.
3//!
4//! ```rust
5//! # use rat_scrolled::Scroll;
6//! use rat_widget::paragraph::{Paragraph, ParagraphState};
7//! # use rat_widget::view::{View, ViewState};
8//! # use ratatui::prelude::*;
9//! #
10//! # let l2 = [Rect::ZERO, Rect::ZERO];
11//! # struct State {
12//! #      view: ViewState,
13//! #      first: ParagraphState,
14//! #  }
15//! # let mut state = State {
16//! #     view: Default::default(),
17//! #     first: Default::default(),
18//! # };
19//! # let mut buf = Buffer::default();
20//!
21//! ///
22//! /// Create the view and set the layout area
23//! /// for the buffer.
24//! ///
25//!
26//! let mut view_buf = View::new()
27//!     .layout(Rect::new(0, 0, 400, 400))
28//!     .vscroll(Scroll::new())
29//!     .hscroll(Scroll::new())
30//!     .into_buffer(l2[1], &mut state.view);
31//!
32//! ///
33//! /// Render the widgets to the view buffer.
34//! ///
35//! view_buf.render(
36//!     Paragraph::new("Paragraph\nParagraph\n..."),
37//!     Rect::new(0, 0, 40, 15),
38//!     &mut state.first,
39//! );
40//!
41//! ///
42//! /// Render the finished buffer.
43//! ///
44//! view_buf
45//!     .into_widget()
46//!     .render(l2[1], &mut buf, &mut state.view);
47//!
48//! ```
49
50use crate::_private::NonExhaustive;
51use crate::event::ScrollOutcome;
52use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Outcome, Regular, ct_event};
53use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
54use rat_reloc::RelocatableState;
55use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
56use ratatui::buffer::Buffer;
57use ratatui::layout::{Position, Rect, Size};
58use ratatui::style::Style;
59use ratatui::widgets::Block;
60use ratatui::widgets::{StatefulWidget, Widget};
61use std::cmp::min;
62use std::mem;
63
64/// Configure the view.
65#[derive(Debug, Default, Clone)]
66pub struct View<'a> {
67    view_layout: Option<Rect>,
68    view_x: Option<u16>,
69    view_y: Option<u16>,
70    view_width: Option<u16>,
71    view_height: Option<u16>,
72
73    style: Style,
74    block: Option<Block<'a>>,
75    hscroll: Option<Scroll<'a>>,
76    vscroll: Option<Scroll<'a>>,
77}
78
79/// Render to the temp buffer.
80///
81/// * It maps your widget area from layout coordinates
82///   to screen coordinates before rendering.
83/// * It helps with cleanup of the widget state if your
84///   widget is currently invisible.
85#[derive(Debug)]
86pub struct ViewBuffer<'a> {
87    // Scroll offset into the view.
88    offset: Position,
89    buffer: Buffer,
90
91    // inner area that will finally be rendered.
92    widget_area: Rect,
93
94    style: Style,
95    block: Option<Block<'a>>,
96    hscroll: Option<Scroll<'a>>,
97    vscroll: Option<Scroll<'a>>,
98
99    destruct: bool,
100}
101
102/// Clips and copies the temp buffer to the frame buffer.
103// todo: deprecate
104#[derive(Debug)]
105pub struct ViewWidget<'a> {
106    // Scroll offset into the view.
107    offset: Position,
108    buffer: Buffer,
109
110    style: Style,
111    block: Option<Block<'a>>,
112    hscroll: Option<Scroll<'a>>,
113    vscroll: Option<Scroll<'a>>,
114}
115
116/// All styles for a view.
117#[derive(Debug, Clone)]
118pub struct ViewStyle {
119    pub style: Style,
120    pub block: Option<Block<'static>>,
121    pub border_style: Option<Style>,
122    pub title_style: Option<Style>,
123    pub scroll: Option<ScrollStyle>,
124    pub non_exhaustive: NonExhaustive,
125}
126
127/// View state.
128#[derive(Debug, Default, Clone)]
129pub struct ViewState {
130    /// Full area for the widget.
131    /// __read only__ renewed for each render.
132    pub area: Rect,
133    /// Area inside the border.
134    /// __read only__ renewed for each render.
135    pub widget_area: Rect,
136
137    /// The layout of the temp buffer uses.
138    /// __read only__ renewed for each render.
139    pub layout: Rect,
140
141    /// Horizontal scroll
142    /// __read+write__
143    pub hscroll: ScrollState,
144    /// Vertical scroll
145    /// __read+write__
146    pub vscroll: ScrollState,
147
148    /// Current focus state.
149    /// __read+write__
150    pub focus: FocusFlag,
151
152    /// For the buffer to survive render()
153    buffer: Option<Buffer>,
154}
155
156impl<'a> View<'a> {
157    /// New View.
158    pub fn new() -> Self {
159        Self::default()
160    }
161
162    /// Size of the view buffer.
163    pub fn layout(mut self, area: Rect) -> Self {
164        self.view_layout = Some(area);
165        self
166    }
167
168    /// Width of the view buffer.
169    pub fn view_width(mut self, width: u16) -> Self {
170        self.view_width = Some(width);
171        self
172    }
173
174    /// Width of the view buffer.
175    pub fn view_height(mut self, height: u16) -> Self {
176        self.view_height = Some(height);
177        self
178    }
179    /// Start position of the view buffer.
180    pub fn view_x(mut self, x: u16) -> Self {
181        self.view_x = Some(x);
182        self
183    }
184
185    /// Start position of the view buffer.
186    pub fn view_y(mut self, y: u16) -> Self {
187        self.view_y = Some(y);
188        self
189    }
190
191    /// Size of the view buffer.
192    pub fn view_size(mut self, view: Size) -> Self {
193        self.view_width = Some(view.width);
194        self.view_height = Some(view.height);
195        self
196    }
197
198    /// Base style.
199    pub fn style(mut self, style: Style) -> Self {
200        self.style = style;
201        self.block = self.block.map(|v| v.style(style));
202        self
203    }
204
205    /// Block for border
206    pub fn block(mut self, block: Block<'a>) -> Self {
207        self.block = Some(block);
208        self
209    }
210
211    /// Scroll support.
212    pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
213        self.hscroll = Some(scroll.clone().override_horizontal());
214        self.vscroll = Some(scroll.override_vertical());
215        self
216    }
217
218    /// Horizontal scroll support.
219    pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
220        self.hscroll = Some(scroll.override_horizontal());
221        self
222    }
223
224    /// Vertical scroll support.
225    pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
226        self.vscroll = Some(scroll.override_vertical());
227        self
228    }
229
230    /// Combined style.
231    pub fn styles(mut self, styles: ViewStyle) -> Self {
232        self.style = styles.style;
233        if styles.block.is_some() {
234            self.block = styles.block;
235        }
236        if let Some(border_style) = styles.border_style {
237            self.block = self.block.map(|v| v.border_style(border_style));
238        }
239        if let Some(title_style) = styles.title_style {
240            self.block = self.block.map(|v| v.title_style(title_style));
241        }
242        self.block = self.block.map(|v| v.style(self.style));
243        if let Some(styles) = styles.scroll {
244            self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
245            self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
246        }
247        self
248    }
249
250    /// Calculate the layout width.
251    #[allow(deprecated)]
252    pub fn layout_size(&self, area: Rect, state: &ViewState) -> u16 {
253        self.inner(area, state).width
254    }
255
256    /// Calculate the layout width.
257    #[allow(deprecated)]
258    pub fn layout_width(&self, area: Rect, state: &ViewState) -> u16 {
259        self.inner(area, state).width
260    }
261
262    /// Calculate the view area.
263    #[deprecated(since = "2.3.0", note = "use layout_size instead")]
264    pub fn inner(&self, area: Rect, state: &ViewState) -> Rect {
265        let sa = ScrollArea::new()
266            .block(self.block.as_ref())
267            .h_scroll(self.hscroll.as_ref())
268            .v_scroll(self.vscroll.as_ref());
269        sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
270    }
271
272    /// Calculates the layout and creates a temporary buffer.
273    pub fn into_buffer(self, area: Rect, state: &mut ViewState) -> ViewBuffer<'a> {
274        state.area = area;
275
276        let sa = ScrollArea::new()
277            .block(self.block.as_ref())
278            .h_scroll(self.hscroll.as_ref())
279            .v_scroll(self.vscroll.as_ref());
280        state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
281
282        state.layout = if let Some(layout) = self.view_layout {
283            layout
284        } else {
285            let mut layout = Rect::new(0, 0, state.widget_area.width, state.widget_area.height);
286            if let Some(x) = self.view_x {
287                layout.x = x;
288            }
289            if let Some(y) = self.view_y {
290                layout.y = y;
291            }
292            if let Some(width) = self.view_width {
293                layout.width = width;
294            }
295            if let Some(height) = self.view_height {
296                layout.height = height;
297            }
298            layout
299        };
300
301        state
302            .hscroll
303            .set_max_offset(state.layout.width.saturating_sub(state.widget_area.width) as usize);
304        state.hscroll.set_page_len(state.widget_area.width as usize);
305        state
306            .vscroll
307            .set_page_len(state.widget_area.height as usize);
308        state
309            .vscroll
310            .set_max_offset(state.layout.height.saturating_sub(state.widget_area.height) as usize);
311
312        // offset is in layout coordinates.
313        // internal buffer starts at (view.x,view.y)
314        let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
315
316        // resize buffer to fit the layout.
317        let mut buffer = if let Some(mut buffer) = state.buffer.take() {
318            buffer.reset();
319            buffer.resize(state.layout);
320            buffer
321        } else {
322            Buffer::empty(state.layout)
323        };
324        buffer.set_style(state.layout, self.style);
325
326        ViewBuffer {
327            offset,
328            buffer,
329            widget_area: state.widget_area,
330            style: self.style,
331            block: self.block,
332            hscroll: self.hscroll,
333            vscroll: self.vscroll,
334            destruct: false,
335        }
336    }
337}
338
339impl<'a> Drop for ViewBuffer<'a> {
340    fn drop(&mut self) {
341        if !self.destruct {
342            panic!("ViewBuffer: Must be used. Call finish(..)");
343        }
344    }
345}
346
347impl<'a> ViewBuffer<'a> {
348    /// Render a widget to the temp buffer.
349    #[inline(always)]
350    pub fn render_widget<W>(&mut self, widget: W, area: Rect) -> bool
351    where
352        W: Widget,
353    {
354        if area.intersects(self.buffer.area) {
355            // render the actual widget.
356            widget.render(area, self.buffer());
357            true
358        } else {
359            false
360        }
361    }
362
363    /// Render a widget to the temp buffer.
364    /// This expects that the state is a [RelocatableState].
365    #[inline(always)]
366    #[allow(deprecated)]
367    pub fn render<W, S>(&mut self, widget: W, area: Rect, state: &mut S) -> bool
368    where
369        W: StatefulWidget<State = S>,
370        S: RelocatableState,
371    {
372        if area.intersects(self.buffer.area) {
373            // render the actual widget.
374            widget.render(area, self.buffer(), state);
375            // shift and clip the output areas.
376            state.relocate(self.shift(), self.widget_area);
377            true
378        } else {
379            state.relocate_hidden();
380            false
381        }
382    }
383
384    /// Render an additional popup widget for the given main widget.
385    ///
386    /// Doesn't call relocate().
387    #[inline(always)]
388    #[allow(deprecated)]
389    pub fn render_popup<W, S>(&mut self, widget: W, area: Rect, state: &mut S) -> bool
390    where
391        W: StatefulWidget<State = S>,
392        S: RelocatableState,
393    {
394        if area.intersects(self.buffer.area) {
395            // render the actual widget.
396            widget.render(area, self.buffer(), state);
397            // shift and clip the output areas.
398            state.relocate_popup(self.shift(), self.widget_area);
399            true
400        } else {
401            state.relocate_popup_hidden();
402            false
403        }
404    }
405
406    /// Return the buffer layout.
407    pub fn layout(&self) -> Rect {
408        self.buffer.area
409    }
410
411    /// Is this area inside the buffer area.
412    pub fn is_visible_area(&self, area: Rect) -> bool {
413        area.intersects(self.buffer.area)
414    }
415
416    /// Calculate the necessary shift from view to screen.
417    #[deprecated(
418        since = "2.0.0",
419        note = "should not be public. use relocate2() instead."
420    )]
421    pub fn shift(&self) -> (i16, i16) {
422        (
423            self.widget_area.x as i16 - self.offset.x as i16,
424            self.widget_area.y as i16 - self.offset.y as i16,
425        )
426    }
427
428    /// Does nothing for view.
429    /// Only exists to match [Clipper](crate::clipper::Clipper).
430    #[deprecated(
431        since = "2.0.0",
432        note = "wrong api, use is_visible_area() or locate_area2()"
433    )]
434    pub fn locate_area(&self, area: Rect) -> Rect {
435        area
436    }
437
438    /// Validates that this area is inside the buffer area.
439    pub fn locate_area2(&self, area: Rect) -> Option<Rect> {
440        if area.intersects(self.buffer.area) {
441            Some(area)
442        } else {
443            None
444        }
445    }
446
447    /// After rendering the widget to the buffer it may have
448    /// stored areas in its state. These will be in buffer
449    /// coordinates instead of screen coordinates.
450    ///
451    /// Call this function to correct this after rendering.
452    #[deprecated(since = "2.0.0", note = "wrong api, use relocate2() instead")]
453    #[allow(deprecated)]
454    pub fn relocate<S>(&self, state: &mut S)
455    where
456        S: RelocatableState,
457    {
458        state.relocate(self.shift(), self.widget_area);
459    }
460
461    /// After rendering the widget to the buffer it may have
462    /// stored areas in its state. These will be in buffer
463    /// coordinates instead of screen coordinates.
464    ///
465    /// Call this function to correct this after rendering.
466    #[allow(deprecated)]
467    pub fn relocate2<S>(&self, area: Rect, state: &mut S)
468    where
469        S: RelocatableState,
470    {
471        if self.is_visible_area(area) {
472            state.relocate(self.shift(), self.widget_area);
473        } else {
474            state.relocate_hidden();
475        }
476    }
477
478    /// If a widget is not rendered because it is out of
479    /// the buffer area, it may still have left over areas
480    /// in its state.
481    ///
482    /// This uses [relocate_hidden](RelocatableState::relocate_hidden) to zero them out.
483    #[deprecated(since = "2.0.0", note = "bad api, use relocate2() instead")]
484    pub fn hidden<S>(&self, state: &mut S)
485    where
486        S: RelocatableState,
487    {
488        state.relocate_hidden();
489    }
490
491    /// Access the temporary buffer.
492    ///
493    /// __Note__
494    /// Use of render_widget is preferred.
495    pub fn buffer(&mut self) -> &mut Buffer {
496        &mut self.buffer
497    }
498
499    /// Rendering the content is finished.
500    ///
501    /// Convert to the output widget that can be rendered in the target area.
502    #[deprecated(since = "2.3.0", note = "use finish() instead")]
503    pub fn into_widget(mut self) -> ViewWidget<'a> {
504        self.destruct = true;
505
506        ViewWidget {
507            block: mem::take(&mut self.block),
508            hscroll: mem::take(&mut self.hscroll),
509            vscroll: mem::take(&mut self.vscroll),
510            offset: self.offset,
511            buffer: mem::take(&mut self.buffer),
512            style: self.style,
513        }
514    }
515
516    /// Render the buffer.
517    pub fn finish(mut self, tgt_buf: &mut Buffer, state: &mut ViewState) {
518        self.destruct = true;
519
520        ScrollArea::new()
521            .style(self.style)
522            .block(self.block.as_ref())
523            .h_scroll(self.hscroll.as_ref())
524            .v_scroll(self.vscroll.as_ref())
525            .render(
526                state.area,
527                tgt_buf,
528                &mut ScrollAreaState::new()
529                    .h_scroll(&mut state.hscroll)
530                    .v_scroll(&mut state.vscroll),
531            );
532
533        let src_area = self.buffer.area;
534        let tgt_area = state.widget_area;
535        let offset = self.offset;
536
537        // extra offset due to buffer starts right of offset.
538        let off_x0 = src_area.x.saturating_sub(offset.x);
539        let off_y0 = src_area.y.saturating_sub(offset.y);
540        // cut source buffer due to start left of offset.
541        let cut_x0 = offset.x.saturating_sub(src_area.x);
542        let cut_y0 = offset.y.saturating_sub(src_area.y);
543
544        // length to copy
545        let len_src = src_area.width.saturating_sub(cut_x0);
546        let len_tgt = tgt_area.width.saturating_sub(off_x0);
547        let len = min(len_src, len_tgt);
548
549        // area height to copy
550        let height_src = src_area.height.saturating_sub(cut_y0);
551        let height_tgt = tgt_area.height.saturating_sub(off_y0);
552        let height = min(height_src, height_tgt);
553
554        // ** slow version **
555        // for y in 0..height {
556        //     for x in 0..len {
557        //         let src_pos = Position::new(src_area.x + cut_x0 + x, src_area.y + cut_y0 + y);
558        //         let src_cell = self.buffer.cell(src_pos).expect("src-cell");
559        //
560        //         let tgt_pos = Position::new(tgt_area.x + off_x0 + x, tgt_area.y + off_y0 + y);
561        //         let tgt_cell = buf.cell_mut(tgt_pos).expect("tgt_cell");
562        //
563        //         *tgt_cell = src_cell.clone();
564        //     }
565        // }
566
567        for y in 0..height {
568            let src_0 = self
569                .buffer
570                .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
571            let tgt_0 = tgt_buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
572
573            let src = &self.buffer.content[src_0..src_0 + len as usize];
574            let tgt = &mut tgt_buf.content[tgt_0..tgt_0 + len as usize];
575            tgt.clone_from_slice(src);
576        }
577
578        // keep buffer
579        state.buffer = Some(mem::take(&mut self.buffer));
580    }
581}
582
583impl StatefulWidget for ViewWidget<'_> {
584    type State = ViewState;
585
586    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
587        if cfg!(debug_assertions) {
588            if area != state.area {
589                panic!(
590                    "ViewWidget::render() must be called with the same area as View::into_buffer()."
591                )
592            }
593        }
594        ScrollArea::new()
595            .style(self.style)
596            .block(self.block.as_ref())
597            .h_scroll(self.hscroll.as_ref())
598            .v_scroll(self.vscroll.as_ref())
599            .render(
600                state.area,
601                buf,
602                &mut ScrollAreaState::new()
603                    .h_scroll(&mut state.hscroll)
604                    .v_scroll(&mut state.vscroll),
605            );
606
607        let src_area = self.buffer.area;
608        let tgt_area = state.widget_area;
609        let offset = self.offset;
610
611        // extra offset due to buffer starts right of offset.
612        let off_x0 = src_area.x.saturating_sub(offset.x);
613        let off_y0 = src_area.y.saturating_sub(offset.y);
614        // cut source buffer due to start left of offset.
615        let cut_x0 = offset.x.saturating_sub(src_area.x);
616        let cut_y0 = offset.y.saturating_sub(src_area.y);
617
618        // length to copy
619        let len_src = src_area.width.saturating_sub(cut_x0);
620        let len_tgt = tgt_area.width.saturating_sub(off_x0);
621        let len = min(len_src, len_tgt);
622
623        // area height to copy
624        let height_src = src_area.height.saturating_sub(cut_y0);
625        let height_tgt = tgt_area.height.saturating_sub(off_y0);
626        let height = min(height_src, height_tgt);
627
628        // ** slow version **
629        // for y in 0..height {
630        //     for x in 0..len {
631        //         let src_pos = Position::new(src_area.x + cut_x0 + x, src_area.y + cut_y0 + y);
632        //         let src_cell = self.buffer.cell(src_pos).expect("src-cell");
633        //
634        //         let tgt_pos = Position::new(tgt_area.x + off_x0 + x, tgt_area.y + off_y0 + y);
635        //         let tgt_cell = buf.cell_mut(tgt_pos).expect("tgt_cell");
636        //
637        //         *tgt_cell = src_cell.clone();
638        //     }
639        // }
640
641        for y in 0..height {
642            let src_0 = self
643                .buffer
644                .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
645            let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
646
647            let src = &self.buffer.content[src_0..src_0 + len as usize];
648            let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
649            tgt.clone_from_slice(src);
650        }
651
652        // keep buffer
653        state.buffer = Some(self.buffer);
654    }
655}
656
657impl Default for ViewStyle {
658    fn default() -> Self {
659        Self {
660            style: Default::default(),
661            block: Default::default(),
662            border_style: Default::default(),
663            title_style: Default::default(),
664            scroll: Default::default(),
665            non_exhaustive: NonExhaustive,
666        }
667    }
668}
669
670impl HasFocus for ViewState {
671    fn build(&self, builder: &mut FocusBuilder) {
672        builder.leaf_widget(self);
673    }
674
675    fn focus(&self) -> FocusFlag {
676        self.focus.clone()
677    }
678
679    fn area(&self) -> Rect {
680        self.area
681    }
682}
683
684impl RelocatableState for ViewState {
685    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
686        self.area.relocate(shift, clip);
687        self.widget_area.relocate(shift, clip);
688        self.hscroll.relocate(shift, clip);
689        self.vscroll.relocate(shift, clip);
690    }
691}
692
693impl ViewState {
694    pub fn new() -> Self {
695        Self::default()
696    }
697
698    /// Show this rect.
699    pub fn show_area(&mut self, area: Rect) {
700        self.hscroll.scroll_to_pos(area.x as usize);
701        self.vscroll.scroll_to_pos(area.y as usize);
702    }
703}
704
705impl ViewState {
706    pub fn vertical_offset(&self) -> usize {
707        self.vscroll.offset()
708    }
709
710    pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
711        let old = self.vscroll.offset();
712        self.vscroll.set_offset(offset);
713        old != self.vscroll.offset()
714    }
715
716    pub fn vertical_page_len(&self) -> usize {
717        self.vscroll.page_len()
718    }
719
720    pub fn horizontal_offset(&self) -> usize {
721        self.hscroll.offset()
722    }
723
724    pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
725        let old = self.hscroll.offset();
726        self.hscroll.set_offset(offset);
727        old != self.hscroll.offset()
728    }
729
730    pub fn horizontal_page_len(&self) -> usize {
731        self.hscroll.page_len()
732    }
733
734    pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
735        self.hscroll.scroll_to_pos(pos)
736    }
737
738    pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
739        self.vscroll.scroll_to_pos(pos)
740    }
741
742    pub fn scroll_up(&mut self, delta: usize) -> bool {
743        self.vscroll.scroll_up(delta)
744    }
745
746    pub fn scroll_down(&mut self, delta: usize) -> bool {
747        self.vscroll.scroll_down(delta)
748    }
749
750    pub fn scroll_left(&mut self, delta: usize) -> bool {
751        self.hscroll.scroll_left(delta)
752    }
753
754    pub fn scroll_right(&mut self, delta: usize) -> bool {
755        self.hscroll.scroll_right(delta)
756    }
757}
758
759impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ViewState {
760    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
761        let r = if self.is_focused() {
762            match event {
763                ct_event!(keycode press Left) => self.scroll_left(self.hscroll.scroll_by()).into(),
764                ct_event!(keycode press Right) => {
765                    self.scroll_right(self.hscroll.scroll_by()).into()
766                }
767                ct_event!(keycode press Up) => self.scroll_up(self.vscroll.scroll_by()).into(),
768                ct_event!(keycode press Down) => self.scroll_down(self.vscroll.scroll_by()).into(),
769
770                ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
771                ct_event!(keycode press PageDown) => {
772                    self.scroll_down(self.vscroll.page_len()).into()
773                }
774                ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
775                ct_event!(keycode press End) => {
776                    self.vertical_scroll_to(self.vscroll.max_offset()).into()
777                }
778
779                ct_event!(keycode press ALT-PageUp) => {
780                    self.scroll_left(self.hscroll.page_len()).into()
781                }
782                ct_event!(keycode press ALT-PageDown) => {
783                    self.scroll_right(self.hscroll.page_len()).into()
784                }
785                ct_event!(keycode press ALT-Home) => self.horizontal_scroll_to(0).into(),
786                ct_event!(keycode press ALT-End) => {
787                    self.horizontal_scroll_to(self.hscroll.max_offset()).into()
788                }
789                _ => Outcome::Continue,
790            }
791        } else {
792            Outcome::Continue
793        };
794
795        r.or_else(|| self.handle(event, MouseOnly))
796    }
797}
798
799impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ViewState {
800    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
801        let mut sas = ScrollAreaState::new()
802            .area(self.widget_area)
803            .h_scroll(&mut self.hscroll)
804            .v_scroll(&mut self.vscroll);
805        match sas.handle(event, MouseOnly) {
806            ScrollOutcome::Up(v) => self.scroll_up(v).into(),
807            ScrollOutcome::Down(v) => self.scroll_down(v).into(),
808            ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
809            ScrollOutcome::Left(v) => self.scroll_left(v).into(),
810            ScrollOutcome::Right(v) => self.scroll_right(v).into(),
811            ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
812            r => r.into(),
813        }
814    }
815}
816
817/// Handle all events.
818/// Text events are only processed if focus is true.
819/// Mouse events are processed if they are in range.
820pub fn handle_events(
821    state: &mut ViewState,
822    focus: bool,
823    event: &crossterm::event::Event,
824) -> Outcome {
825    state.focus.set(focus);
826    HandleEvent::handle(state, event, Regular)
827}
828
829/// Handle only mouse-events.
830pub fn handle_mouse_events(state: &mut ViewState, event: &crossterm::event::Event) -> Outcome {
831    HandleEvent::handle(state, event, MouseOnly)
832}