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