Skip to main content

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_core::layout::Rect;
9//! # use ratatui_core::buffer::Buffer;
10//! #
11//! # let l2 = [Rect::ZERO, Rect::ZERO];
12//! # struct State {
13//! #      view: ViewState,
14//! #      first: ParagraphState,
15//! #  }
16//! # let mut state = State {
17//! #     view: Default::default(),
18//! #     first: Default::default(),
19//! # };
20//! # let mut buf = Buffer::default();
21//!
22//! ///
23//! /// Create the view and set the layout area
24//! /// for the buffer.
25//! ///
26//!
27//! let mut view_buf = View::new()
28//!     .layout(Rect::new(0, 0, 400, 400))
29//!     .vscroll(Scroll::new())
30//!     .hscroll(Scroll::new())
31//!     .into_buffer(l2[1], &mut state.view);
32//!
33//! ///
34//! /// Render the widgets to the view buffer.
35//! ///
36//! view_buf.render(
37//!     Paragraph::new("Paragraph\nParagraph\n..."),
38//!     Rect::new(0, 0, 40, 15),
39//!     &mut state.first,
40//! );
41//!
42//! ///
43//! /// Render the finished buffer.
44//! ///
45//! view_buf.finish(&mut buf, &mut state.view);
46//!
47//! ```
48
49use crate::_private::NonExhaustive;
50use crate::event::ScrollOutcome;
51use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Outcome, Regular, ct_event};
52use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
53use rat_reloc::RelocatableState;
54use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
55use ratatui_core::buffer::Buffer;
56use ratatui_core::layout::{Position, Rect, Size};
57use ratatui_core::style::Style;
58use ratatui_core::widgets::{StatefulWidget, Widget};
59use ratatui_crossterm::crossterm::event::Event;
60use ratatui_widgets::block::Block;
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.right().saturating_sub(state.widget_area.width) as usize);
304        state.hscroll.set_page_len(state.widget_area.width as usize);
305
306        state.vscroll.set_max_offset(
307            state
308                .layout
309                .right()
310                .saturating_sub(state.widget_area.height) as usize,
311        );
312        state
313            .vscroll
314            .set_page_len(state.widget_area.height as usize);
315
316        // offset is in layout coordinates.
317        let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
318
319        // resize buffer to fit the layout.
320        let mut buffer = if let Some(mut buffer) = state.buffer.take() {
321            buffer.reset();
322            buffer.resize(state.layout);
323            buffer
324        } else {
325            Buffer::empty(state.layout)
326        };
327        buffer.set_style(state.layout, self.style);
328
329        ViewBuffer {
330            offset,
331            buffer,
332            widget_area: state.widget_area,
333            style: self.style,
334            block: self.block,
335            hscroll: self.hscroll,
336            vscroll: self.vscroll,
337            destruct: false,
338        }
339    }
340}
341
342impl<'a> Drop for ViewBuffer<'a> {
343    fn drop(&mut self) {
344        if !self.destruct {
345            panic!("ViewBuffer: Must be used. Call finish(..)");
346        }
347    }
348}
349
350impl<'a> ViewBuffer<'a> {
351    /// Render a widget to the temp buffer.
352    #[inline(always)]
353    pub fn render_widget<W>(&mut self, widget: W, area: Rect) -> bool
354    where
355        W: Widget,
356    {
357        if area.intersects(self.buffer.area) {
358            // render the actual widget.
359            widget.render(area, self.buffer());
360            true
361        } else {
362            false
363        }
364    }
365
366    /// Render a widget to the temp buffer.
367    /// This expects that the state is a [RelocatableState].
368    #[inline(always)]
369    #[allow(deprecated)]
370    pub fn render<W, S>(&mut self, widget: W, area: Rect, state: &mut S) -> bool
371    where
372        W: StatefulWidget<State = S>,
373        S: RelocatableState,
374    {
375        if area.intersects(self.buffer.area) {
376            // render the actual widget.
377            widget.render(area, self.buffer(), state);
378            // shift and clip the output areas.
379            state.relocate(self.shift(), self.widget_area);
380            true
381        } else {
382            state.relocate_hidden();
383            false
384        }
385    }
386
387    /// Render an additional popup widget for the given main widget.
388    ///
389    /// Doesn't call relocate().
390    #[inline(always)]
391    #[allow(deprecated)]
392    pub fn render_popup<W, S>(&mut self, widget: W, area: Rect, state: &mut S) -> bool
393    where
394        W: StatefulWidget<State = S>,
395        S: RelocatableState,
396    {
397        if area.intersects(self.buffer.area) {
398            // render the actual widget.
399            widget.render(area, self.buffer(), state);
400            // shift and clip the output areas.
401            state.relocate_popup(self.shift(), self.widget_area);
402            true
403        } else {
404            state.relocate_popup_hidden();
405            false
406        }
407    }
408
409    /// Return the buffer layout.
410    pub fn layout(&self) -> Rect {
411        self.buffer.area
412    }
413
414    /// Is this area inside the buffer area.
415    pub fn is_visible_area(&self, area: Rect) -> bool {
416        area.intersects(self.buffer.area)
417    }
418
419    /// Calculate the necessary shift from view to screen.
420    ///
421    /// __Note__
422    ///
423    pub fn shift(&self) -> (i16, i16) {
424        (
425            self.widget_area.x as i16 - self.offset.x as i16,
426            self.widget_area.y as i16 - self.offset.y as i16,
427        )
428    }
429
430    /// Experimental.
431    pub fn clip(&self) -> Rect {
432        self.widget_area
433    }
434
435    /// Does nothing for view.
436    /// Only exists to match [Clipper](crate::clipper::Clipper).
437    #[deprecated(
438        since = "2.0.0",
439        note = "wrong api, use is_visible_area() or locate_area2()"
440    )]
441    pub fn locate_area(&self, area: Rect) -> Rect {
442        area
443    }
444
445    /// Validates that this area is inside the buffer area.
446    pub fn locate_area2(&self, area: Rect) -> Option<Rect> {
447        if area.intersects(self.buffer.area) {
448            Some(area)
449        } else {
450            None
451        }
452    }
453
454    /// After rendering the widget to the buffer it may have
455    /// stored areas in its state. These will be in buffer
456    /// coordinates instead of screen coordinates.
457    ///
458    /// Call this function to correct this after rendering.
459    #[deprecated(since = "2.0.0", note = "wrong api, use relocate2() instead")]
460    #[allow(deprecated)]
461    pub fn relocate<S>(&self, state: &mut S)
462    where
463        S: RelocatableState,
464    {
465        state.relocate(self.shift(), self.clip());
466    }
467
468    /// After rendering the widget to the buffer it may have
469    /// stored areas in its state. These will be in buffer
470    /// coordinates instead of screen coordinates.
471    ///
472    /// Call this function to correct this after rendering.
473    ///
474    /// It needs the render-area of the widget to find out
475    /// if the widget will be display
476    #[allow(deprecated)]
477    pub fn relocate2<S>(&self, area: Rect, state: &mut S)
478    where
479        S: RelocatableState,
480    {
481        if self.is_visible_area(area) {
482            state.relocate(self.shift(), self.widget_area);
483        } else {
484            state.relocate_hidden();
485        }
486    }
487
488    /// If a widget is not rendered because it is out of
489    /// the buffer area, it may still have left over areas
490    /// in its state.
491    ///
492    /// This uses [relocate_hidden](RelocatableState::relocate_hidden) to zero them out.
493    #[deprecated(since = "2.0.0", note = "bad api, use relocate2() instead")]
494    pub fn hidden<S>(&self, state: &mut S)
495    where
496        S: RelocatableState,
497    {
498        state.relocate_hidden();
499    }
500
501    /// Access the temporary buffer.
502    ///
503    /// __Note__
504    /// Use of render_widget is preferred.
505    pub fn buffer(&mut self) -> &mut Buffer {
506        &mut self.buffer
507    }
508
509    /// Rendering the content is finished.
510    ///
511    /// Convert to the output widget that can be rendered in the target area.
512    #[deprecated(since = "2.3.0", note = "use finish() instead")]
513    pub fn into_widget(mut self) -> ViewWidget<'a> {
514        self.destruct = true;
515
516        ViewWidget {
517            block: mem::take(&mut self.block),
518            hscroll: mem::take(&mut self.hscroll),
519            vscroll: mem::take(&mut self.vscroll),
520            offset: self.offset,
521            buffer: mem::take(&mut self.buffer),
522            style: self.style,
523        }
524    }
525
526    /// Render the buffer.
527    pub fn finish(mut self, tgt_buf: &mut Buffer, state: &mut ViewState) {
528        self.destruct = true;
529
530        ScrollArea::new()
531            .style(self.style)
532            .block(self.block.as_ref())
533            .h_scroll(self.hscroll.as_ref())
534            .v_scroll(self.vscroll.as_ref())
535            .render(
536                state.area,
537                tgt_buf,
538                &mut ScrollAreaState::new()
539                    .h_scroll(&mut state.hscroll)
540                    .v_scroll(&mut state.vscroll),
541            );
542
543        let v_src = Rect::new(
544            self.offset.x,
545            self.offset.y,
546            state.widget_area.width,
547            state.widget_area.height,
548        );
549        if !v_src.intersects(self.buffer.area) {
550            return;
551        }
552        let mut src = self.buffer.area.intersection(v_src);
553
554        let mut view = state.widget_area;
555        if src.x > self.offset.x {
556            view.x += src.x - self.offset.x;
557            view.width -= src.x - self.offset.x;
558        }
559        if src.y > self.offset.y {
560            view.y += src.y - self.offset.y;
561            view.height -= src.y - self.offset.y;
562        }
563
564        let width = min(view.width, src.width);
565        let height = min(view.height, src.height);
566
567        src.width = width;
568        view.width = width;
569        src.height = height;
570        view.height = height;
571
572        for y in 0..src.height {
573            let src_0 = self.buffer.index_of(src.x, src.y + y);
574            let src_len = src.width as usize;
575            let view_0 = tgt_buf.index_of(view.x, view.y + y);
576            let view_len = view.width as usize;
577            assert_eq!(src_len, view_len);
578
579            let src = &self.buffer.content[src_0..src_0 + src_len];
580            let tgt = &mut tgt_buf.content[view_0..view_0 + view_len];
581            tgt.clone_from_slice(src);
582        }
583
584        // keep buffer
585        state.buffer = Some(mem::take(&mut self.buffer));
586    }
587}
588
589impl StatefulWidget for ViewWidget<'_> {
590    type State = ViewState;
591
592    fn render(mut self, area: Rect, tgt_buf: &mut Buffer, state: &mut Self::State) {
593        if cfg!(debug_assertions) {
594            if area != state.area {
595                panic!(
596                    "ViewWidget::render() must be called with the same area as View::into_buffer()."
597                )
598            }
599        }
600
601        ScrollArea::new()
602            .style(self.style)
603            .block(self.block.as_ref())
604            .h_scroll(self.hscroll.as_ref())
605            .v_scroll(self.vscroll.as_ref())
606            .render(
607                state.area,
608                tgt_buf,
609                &mut ScrollAreaState::new()
610                    .h_scroll(&mut state.hscroll)
611                    .v_scroll(&mut state.vscroll),
612            );
613
614        let v_src = Rect::new(
615            self.offset.x,
616            self.offset.y,
617            state.widget_area.width,
618            state.widget_area.height,
619        );
620        if !v_src.intersects(self.buffer.area) {
621            return;
622        }
623        let mut src = self.buffer.area.intersection(v_src);
624
625        let mut view = state.widget_area;
626        if src.x > self.offset.x {
627            view.x += src.x - self.offset.x;
628            view.width -= src.x - self.offset.x;
629        }
630        if src.y > self.offset.y {
631            view.y += src.y - self.offset.y;
632            view.height -= src.y - self.offset.y;
633        }
634
635        let width = min(view.width, src.width);
636        let height = min(view.height, src.height);
637
638        src.width = width;
639        view.width = width;
640        src.height = height;
641        view.height = height;
642
643        for y in 0..src.height {
644            let src_0 = self.buffer.index_of(src.x, src.y + y);
645            let src_len = src.width as usize;
646            let view_0 = tgt_buf.index_of(view.x, view.y + y);
647            let view_len = view.width as usize;
648            assert_eq!(src_len, view_len);
649
650            let src = &self.buffer.content[src_0..src_0 + src_len];
651            let tgt = &mut tgt_buf.content[view_0..view_0 + view_len];
652            tgt.clone_from_slice(src);
653        }
654
655        // keep buffer
656        state.buffer = Some(mem::take(&mut self.buffer));
657    }
658}
659
660impl Default for ViewStyle {
661    fn default() -> Self {
662        Self {
663            style: Default::default(),
664            block: Default::default(),
665            border_style: Default::default(),
666            title_style: Default::default(),
667            scroll: Default::default(),
668            non_exhaustive: NonExhaustive,
669        }
670    }
671}
672
673impl HasFocus for ViewState {
674    fn build(&self, builder: &mut FocusBuilder) {
675        builder.leaf_widget(self);
676    }
677
678    fn focus(&self) -> FocusFlag {
679        self.focus.clone()
680    }
681
682    fn area(&self) -> Rect {
683        self.area
684    }
685}
686
687impl RelocatableState for ViewState {
688    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
689        self.area.relocate(shift, clip);
690        self.widget_area.relocate(shift, clip);
691        self.hscroll.relocate(shift, clip);
692        self.vscroll.relocate(shift, clip);
693    }
694}
695
696impl ViewState {
697    pub fn new() -> Self {
698        Self::default()
699    }
700
701    /// Show this rect.
702    pub fn show_area(&mut self, area: Rect) {
703        self.hscroll.scroll_to_pos(area.x as usize);
704        self.vscroll.scroll_to_pos(area.y as usize);
705    }
706}
707
708impl ViewState {
709    pub fn vertical_offset(&self) -> usize {
710        self.vscroll.offset()
711    }
712
713    pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
714        let old = self.vscroll.offset();
715        self.vscroll.set_offset(offset);
716        old != self.vscroll.offset()
717    }
718
719    pub fn vertical_page_len(&self) -> usize {
720        self.vscroll.page_len()
721    }
722
723    pub fn horizontal_offset(&self) -> usize {
724        self.hscroll.offset()
725    }
726
727    pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
728        let old = self.hscroll.offset();
729        self.hscroll.set_offset(offset);
730        old != self.hscroll.offset()
731    }
732
733    pub fn horizontal_page_len(&self) -> usize {
734        self.hscroll.page_len()
735    }
736
737    pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
738        self.hscroll.scroll_to_pos(pos)
739    }
740
741    pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
742        self.vscroll.scroll_to_pos(pos)
743    }
744
745    pub fn scroll_up(&mut self, delta: usize) -> bool {
746        self.vscroll.scroll_up(delta)
747    }
748
749    pub fn scroll_down(&mut self, delta: usize) -> bool {
750        self.vscroll.scroll_down(delta)
751    }
752
753    pub fn scroll_left(&mut self, delta: usize) -> bool {
754        self.hscroll.scroll_left(delta)
755    }
756
757    pub fn scroll_right(&mut self, delta: usize) -> bool {
758        self.hscroll.scroll_right(delta)
759    }
760}
761
762impl HandleEvent<Event, Regular, Outcome> for ViewState {
763    fn handle(&mut self, event: &Event, _qualifier: Regular) -> Outcome {
764        let r = if self.is_focused() {
765            match event {
766                ct_event!(keycode press Left) => self.scroll_left(self.hscroll.scroll_by()).into(),
767                ct_event!(keycode press Right) => {
768                    self.scroll_right(self.hscroll.scroll_by()).into()
769                }
770                ct_event!(keycode press Up) => self.scroll_up(self.vscroll.scroll_by()).into(),
771                ct_event!(keycode press Down) => self.scroll_down(self.vscroll.scroll_by()).into(),
772
773                ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
774                ct_event!(keycode press PageDown) => {
775                    self.scroll_down(self.vscroll.page_len()).into()
776                }
777                ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
778                ct_event!(keycode press End) => {
779                    self.vertical_scroll_to(self.vscroll.max_offset()).into()
780                }
781
782                ct_event!(keycode press ALT-PageUp) => {
783                    self.scroll_left(self.hscroll.page_len()).into()
784                }
785                ct_event!(keycode press ALT-PageDown) => {
786                    self.scroll_right(self.hscroll.page_len()).into()
787                }
788                ct_event!(keycode press ALT-Home) => self.horizontal_scroll_to(0).into(),
789                ct_event!(keycode press ALT-End) => {
790                    self.horizontal_scroll_to(self.hscroll.max_offset()).into()
791                }
792                _ => Outcome::Continue,
793            }
794        } else {
795            Outcome::Continue
796        };
797
798        r.or_else(|| self.handle(event, MouseOnly))
799    }
800}
801
802impl HandleEvent<Event, MouseOnly, Outcome> for ViewState {
803    fn handle(&mut self, event: &Event, _qualifier: MouseOnly) -> Outcome {
804        let mut sas = ScrollAreaState::new()
805            .area(self.widget_area)
806            .h_scroll(&mut self.hscroll)
807            .v_scroll(&mut self.vscroll);
808        match sas.handle(event, MouseOnly) {
809            ScrollOutcome::Up(v) => self.scroll_up(v).into(),
810            ScrollOutcome::Down(v) => self.scroll_down(v).into(),
811            ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
812            ScrollOutcome::Left(v) => self.scroll_left(v).into(),
813            ScrollOutcome::Right(v) => self.scroll_right(v).into(),
814            ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
815            r => r.into(),
816        }
817    }
818}
819
820/// Handle all events.
821/// Text events are only processed if focus is true.
822/// Mouse events are processed if they are in range.
823pub fn handle_events(state: &mut ViewState, focus: bool, event: &Event) -> Outcome {
824    state.focus.set(focus);
825    HandleEvent::handle(state, event, Regular)
826}
827
828/// Handle only mouse-events.
829pub fn handle_mouse_events(state: &mut ViewState, event: &Event) -> Outcome {
830    HandleEvent::handle(state, event, MouseOnly)
831}