rat_widget/clipper/
clipper.rs

1use crate::_private::NonExhaustive;
2use crate::clipper::ClipperStyle;
3use crate::layout::GenericLayout;
4use rat_event::{ct_event, ConsumedEvent, HandleEvent, MouseOnly, Outcome, Regular};
5use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
6use rat_reloc::RelocatableState;
7use rat_scrolled::event::ScrollOutcome;
8use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState};
9use ratatui::buffer::Buffer;
10use ratatui::layout::{Alignment, Position, Rect, Size};
11use ratatui::prelude::{Style, Widget};
12use ratatui::text::Line;
13use ratatui::widgets::{Block, StatefulWidget};
14use std::borrow::Cow;
15use std::cell::{Ref, RefCell};
16use std::cmp::{max, min};
17use std::hash::Hash;
18use std::marker::PhantomData;
19use std::rc::Rc;
20
21#[derive(Debug)]
22pub struct Clipper<'a, W>
23where
24    W: Eq + Clone + Hash,
25{
26    style: Style,
27    block: Option<Block<'a>>,
28    layout: Option<GenericLayout<W>>,
29    hscroll: Option<Scroll<'a>>,
30    vscroll: Option<Scroll<'a>>,
31    label_style: Option<Style>,
32    label_alignment: Option<Alignment>,
33    phantom: PhantomData<W>,
34}
35
36#[derive(Debug)]
37pub struct ClipperBuffer<'a, W>
38where
39    W: Eq + Clone + Hash,
40{
41    layout: Rc<RefCell<GenericLayout<W>>>,
42
43    // offset from buffer to scroll area
44    offset: Position,
45    buffer: Buffer,
46
47    // inner area that will finally be rendered.
48    widget_area: Rect,
49
50    style: Style,
51    block: Option<Block<'a>>,
52    hscroll: Option<Scroll<'a>>,
53    vscroll: Option<Scroll<'a>>,
54    label_style: Option<Style>,
55    label_alignment: Option<Alignment>,
56}
57
58#[derive(Debug)]
59pub struct ClipperWidget<'a, W>
60where
61    W: Eq + Clone + Hash,
62{
63    offset: Position,
64    buffer: Buffer,
65
66    style: Style,
67    block: Option<Block<'a>>,
68    hscroll: Option<Scroll<'a>>,
69    vscroll: Option<Scroll<'a>>,
70    phantom: PhantomData<W>,
71}
72
73#[derive(Debug)]
74pub struct ClipperState<W>
75where
76    W: Eq + Clone + Hash,
77{
78    // Full area for the widget.
79    /// __read only__ renewed for each render.
80    pub area: Rect,
81    /// Area inside the border.
82    /// __read only__ renewed for each render.
83    pub widget_area: Rect,
84
85    /// Page layout.
86    /// __read only__ renewed for each render.
87    pub layout: Rc<RefCell<GenericLayout<W>>>,
88
89    /// Horizontal scroll
90    /// __read+write__
91    pub hscroll: ScrollState,
92    /// Vertical scroll
93    /// __read+write__
94    pub vscroll: ScrollState,
95
96    /// This widget has no focus of its own, but this flag
97    /// can be used to set a container state.
98    pub container: FocusFlag,
99
100    /// For the buffer to survive render()
101    buffer: Option<Buffer>,
102
103    /// Only construct with `..Default::default()`.
104    pub non_exhaustive: NonExhaustive,
105}
106
107impl<W> Clone for Clipper<'_, W>
108where
109    W: Eq + Clone + Hash,
110{
111    fn clone(&self) -> Self {
112        Self {
113            style: Default::default(),
114            block: self.block.clone(),
115            layout: self.layout.clone(),
116            hscroll: self.hscroll.clone(),
117            vscroll: self.vscroll.clone(),
118            label_style: self.label_style.clone(),
119            label_alignment: self.label_alignment.clone(),
120            phantom: Default::default(),
121        }
122    }
123}
124
125impl<W> Default for Clipper<'_, W>
126where
127    W: Eq + Clone + Hash,
128{
129    fn default() -> Self {
130        Self {
131            style: Default::default(),
132            block: Default::default(),
133            layout: Default::default(),
134            hscroll: Default::default(),
135            vscroll: Default::default(),
136            label_style: Default::default(),
137            label_alignment: Default::default(),
138            phantom: Default::default(),
139        }
140    }
141}
142
143impl<'a, W> Clipper<'a, W>
144where
145    W: Eq + Clone + Hash,
146{
147    /// New Clipper.
148    pub fn new() -> Self {
149        Self::default()
150    }
151
152    /// Set the layout. If no layout is set here the layout is
153    /// taken from the state.
154    pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
155        self.layout = Some(layout);
156        self
157    }
158
159    /// Base style.
160    pub fn style(mut self, style: Style) -> Self {
161        self.style = style;
162        self.block = self.block.map(|v| v.style(style));
163        self
164    }
165
166    /// Widget labels.
167    pub fn label_style(mut self, style: Style) -> Self {
168        self.label_style = Some(style);
169        self
170    }
171
172    /// Widget labels.
173    pub fn label_alignment(mut self, alignment: Alignment) -> Self {
174        self.label_alignment = Some(alignment);
175        self
176    }
177
178    /// Block for border
179    pub fn block(mut self, block: Block<'a>) -> Self {
180        self.block = Some(block);
181        self
182    }
183
184    /// Scroll support.
185    pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
186        self.hscroll = Some(scroll.clone().override_horizontal());
187        self.vscroll = Some(scroll.override_vertical());
188        self
189    }
190
191    /// Horizontal scroll support.
192    pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
193        self.hscroll = Some(scroll.override_horizontal());
194        self
195    }
196
197    /// Vertical scroll support.
198    pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
199        self.vscroll = Some(scroll.override_vertical());
200        self
201    }
202
203    /// Combined style.
204    pub fn styles(mut self, styles: ClipperStyle) -> Self {
205        self.style = styles.style;
206        if styles.label_style.is_some() {
207            self.label_style = styles.label_style;
208        }
209        if styles.label_alignment.is_some() {
210            self.label_alignment = styles.label_alignment;
211        }
212        if styles.block.is_some() {
213            self.block = styles.block;
214        }
215        if let Some(styles) = styles.scroll {
216            self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
217            self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
218        }
219        self.block = self.block.map(|v| v.style(styles.style));
220        self
221    }
222
223    /// Calculate the layout width.
224    pub fn layout_size(&self, area: Rect, state: &ClipperState<W>) -> Size {
225        let width = self.inner(area, state).width;
226        Size::new(width, u16::MAX)
227    }
228
229    /// Calculate the view area.
230    fn inner(&self, area: Rect, state: &ClipperState<W>) -> Rect {
231        let sa = ScrollArea::new()
232            .block(self.block.as_ref())
233            .h_scroll(self.hscroll.as_ref())
234            .v_scroll(self.vscroll.as_ref());
235        sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
236    }
237
238    fn calc_layout(&self, area: Rect, state: &mut ClipperState<W>) -> (Rect, Position) {
239        let layout = state.layout.borrow();
240
241        let view = Rect::new(
242            state.hscroll.offset() as u16,
243            state.vscroll.offset() as u16,
244            area.width,
245            area.height,
246        );
247
248        // maxima for scroll bar max
249        let mut max_pos = Position::default();
250
251        // find the bounding box for the buffer.
252        // convex hull of all visible widgets/labels/blocks.
253        let mut ext_view: Option<Rect> = None;
254        for idx in 0..layout.widget_len() {
255            let area = layout.widget(idx);
256            let label_area = layout.label(idx);
257
258            if view.intersects(area) || view.intersects(label_area) {
259                if !area.is_empty() {
260                    ext_view = ext_view //
261                        .map(|v| v.union(area))
262                        .or(Some(area));
263                }
264                if !label_area.is_empty() {
265                    ext_view = ext_view //
266                        .map(|v| v.union(label_area))
267                        .or(Some(label_area));
268                }
269            }
270
271            max_pos.x = max(max_pos.x, area.right());
272            max_pos.y = max(max_pos.y, area.bottom());
273            max_pos.x = max(max_pos.x, label_area.right());
274            max_pos.y = max(max_pos.y, label_area.bottom());
275        }
276        for idx in 0..layout.block_len() {
277            let block_area = layout.block_area(idx);
278            if view.intersects(block_area) {
279                ext_view = ext_view //
280                    .map(|v| v.union(block_area))
281                    .or(Some(block_area));
282            }
283
284            max_pos.x = max(max_pos.x, block_area.right());
285            max_pos.y = max(max_pos.y, block_area.bottom());
286        }
287
288        let ext_view = ext_view.unwrap_or(view);
289
290        (ext_view, max_pos)
291    }
292
293    /// Calculates the layout and creates a temporary buffer.
294    pub fn into_buffer(mut self, area: Rect, state: &mut ClipperState<W>) -> ClipperBuffer<'a, W> {
295        state.area = area;
296        if let Some(layout) = self.layout.take() {
297            state.layout = Rc::new(RefCell::new(layout));
298        }
299
300        let sa = ScrollArea::new()
301            .block(self.block.as_ref())
302            .h_scroll(self.hscroll.as_ref())
303            .v_scroll(self.vscroll.as_ref());
304        state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
305
306        // run the layout
307        let (ext_area, max_pos) = self.calc_layout(area, state);
308
309        // adjust scroll
310        state
311            .vscroll
312            .set_page_len(state.widget_area.height as usize);
313        state
314            .vscroll
315            .set_max_offset(max_pos.y.saturating_sub(state.widget_area.height) as usize);
316        state.hscroll.set_page_len(state.widget_area.width as usize);
317        state
318            .hscroll
319            .set_max_offset(max_pos.x.saturating_sub(state.widget_area.width) as usize);
320
321        let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
322
323        // resize buffer to fit all visible widgets.
324        let buffer_area = ext_area;
325        // resize buffer to fit the layout.
326        let mut buffer = if let Some(mut buffer) = state.buffer.take() {
327            buffer.reset();
328            buffer.resize(buffer_area);
329            buffer
330        } else {
331            Buffer::empty(buffer_area)
332        };
333        buffer.set_style(buffer_area, self.style);
334
335        ClipperBuffer {
336            layout: state.layout.clone(),
337            offset,
338            buffer,
339            widget_area: state.widget_area,
340            style: self.style,
341            block: self.block,
342            hscroll: self.hscroll,
343            vscroll: self.vscroll,
344            label_style: self.label_style,
345            label_alignment: self.label_alignment,
346        }
347    }
348}
349
350impl<'a, W> ClipperBuffer<'a, W>
351where
352    W: Eq + Hash + Clone,
353{
354    /// Is the widget visible.
355    pub fn is_visible(&self, widget: W) -> bool {
356        let layout = self.layout.borrow();
357        let Some(idx) = layout.try_index_of(widget) else {
358            return false;
359        };
360        let area = layout.widget(idx);
361        self.buffer.area.intersects(area)
362    }
363
364    /// Render the label with the set style and alignment.
365    #[inline(always)]
366    fn render_auto_label(&mut self, idx: usize) -> bool {
367        let layout = self.layout.borrow();
368        let Some(label_area) = self.locate_area(layout.label(idx)) else {
369            return false;
370        };
371        let Some(label_str) = layout.try_label_str(idx) else {
372            return false;
373        };
374
375        let style = self.label_style.unwrap_or_default();
376        let align = self.label_alignment.unwrap_or_default();
377        Line::from(label_str.as_ref())
378            .style(style)
379            .alignment(align)
380            .render(label_area, &mut self.buffer);
381
382        true
383    }
384
385    /// Render the label for the given widget.
386    #[inline(always)]
387    pub fn render_label<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
388    where
389        FN: FnOnce(&Option<Cow<'static, str>>) -> WW,
390        WW: Widget,
391    {
392        let layout = self.layout.borrow();
393        let Some(idx) = layout.try_index_of(widget) else {
394            return false;
395        };
396        let Some(label_area) = self.locate_area(layout.label(idx)) else {
397            return false;
398        };
399        let label_str = layout.try_label_str(idx);
400
401        render_fn(label_str).render(label_area, &mut self.buffer);
402
403        true
404    }
405
406    /// Render a stateless widget and its label.
407    #[inline(always)]
408    pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
409    where
410        FN: FnOnce() -> WW,
411        WW: Widget,
412    {
413        let Some(idx) = self.layout.borrow().try_index_of(widget) else {
414            return false;
415        };
416
417        self.render_auto_label(idx);
418
419        let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
420            return false;
421        };
422        render_fn().render(widget_area, &mut self.buffer);
423
424        true
425    }
426
427    /// Render a stateful widget and its label.
428    #[inline(always)]
429    pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
430    where
431        FN: FnOnce() -> WW,
432        WW: StatefulWidget<State = SS>,
433        SS: RelocatableState,
434    {
435        let Some(idx) = self.layout.borrow().try_index_of(widget) else {
436            return false;
437        };
438
439        self.render_auto_label(idx);
440
441        let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
442            self.hidden(state);
443            return false;
444        };
445        render_fn().render(widget_area, &mut self.buffer, state);
446        self.relocate(state);
447
448        true
449    }
450
451    /// Render all visible blocks.
452    pub fn render_block(&mut self) {
453        let layout = self.layout.borrow();
454        for (idx, block_area) in layout.block_area_iter().enumerate() {
455            if let Some(block_area) = self.locate_area(*block_area) {
456                layout.block(idx).render(block_area, &mut self.buffer);
457            }
458        }
459    }
460
461    /// Get the buffer coordinates for the given widget.
462    #[inline]
463    #[allow(clippy::question_mark)]
464    pub fn locate_widget(&self, widget: W) -> Option<Rect> {
465        let layout = self.layout.borrow();
466        let Some(idx) = layout.try_index_of(widget) else {
467            return None;
468        };
469        self.locate_area(layout.widget(idx))
470    }
471
472    /// Get the buffer coordinates for the label of the given widget.
473    #[inline]
474    #[allow(clippy::question_mark)]
475    pub fn locate_label(&self, widget: W) -> Option<Rect> {
476        let layout = self.layout.borrow();
477        let Some(idx) = layout.try_index_of(widget) else {
478            return None;
479        };
480        self.locate_area(layout.label(idx))
481    }
482
483    /// Relocate the area from layout coordinates to buffer coordinates,
484    /// which is a noop as those are aligned.
485    ///
486    /// But this will return None if the given area is outside the buffer.
487    #[inline]
488    pub fn locate_area(&self, area: Rect) -> Option<Rect> {
489        let area = self.buffer.area.intersection(area);
490        if area.is_empty() {
491            None
492        } else {
493            Some(area)
494        }
495    }
496
497    /// Calculate the necessary shift from layout to screen.
498    pub fn shift(&self) -> (i16, i16) {
499        (
500            self.widget_area.x as i16 - self.offset.x as i16,
501            self.widget_area.y as i16 - self.offset.y as i16,
502        )
503    }
504
505    /// After rendering the widget to the buffer it may have
506    /// stored areas in its state. These will be in buffer
507    /// coordinates instead of screen coordinates.
508    ///
509    /// Call this function to correct this after rendering.
510    pub fn relocate<S>(&self, state: &mut S)
511    where
512        S: RelocatableState,
513    {
514        state.relocate(self.shift(), self.widget_area);
515    }
516
517    /// If a widget is not rendered because it is out of
518    /// the buffer area, it may still have left over areas
519    /// in its state.
520    ///
521    /// This uses the mechanism for [relocate](Self::relocate) to zero them out.
522    pub fn hidden<S>(&self, state: &mut S)
523    where
524        S: RelocatableState,
525    {
526        state.relocate((0, 0), Rect::default())
527    }
528
529    /// Return a reference to the buffer.
530    #[inline]
531    pub fn buffer(&mut self) -> &mut Buffer {
532        &mut self.buffer
533    }
534
535    /// Rendering the content is finished.
536    ///
537    /// Convert to the output widget that can be rendered in the target area.
538    pub fn into_widget(self) -> ClipperWidget<'a, W> {
539        ClipperWidget {
540            block: self.block,
541            hscroll: self.hscroll,
542            vscroll: self.vscroll,
543            offset: self.offset,
544            buffer: self.buffer,
545            phantom: Default::default(),
546            style: self.style,
547        }
548    }
549}
550
551impl<W> StatefulWidget for ClipperWidget<'_, W>
552where
553    W: Eq + Clone + Hash,
554{
555    type State = ClipperState<W>;
556
557    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
558        assert_eq!(area, state.area);
559
560        ScrollArea::new()
561            .style(self.style)
562            .block(self.block.as_ref())
563            .h_scroll(self.hscroll.as_ref())
564            .v_scroll(self.vscroll.as_ref())
565            .render(
566                area,
567                buf,
568                &mut ScrollAreaState::new()
569                    .h_scroll(&mut state.hscroll)
570                    .v_scroll(&mut state.vscroll),
571            );
572
573        let src_area = self.buffer.area;
574        let tgt_area = state.widget_area;
575        let offset = self.offset;
576
577        // extra offset due to buffer starts right of offset.
578        let off_x0 = src_area.x.saturating_sub(offset.x);
579        let off_y0 = src_area.y.saturating_sub(offset.y);
580        // cut source buffer due to start left of offset.
581        let cut_x0 = offset.x.saturating_sub(src_area.x);
582        let cut_y0 = offset.y.saturating_sub(src_area.y);
583
584        // length to copy
585        let len_src = src_area.width.saturating_sub(cut_x0);
586        let len_tgt = tgt_area.width.saturating_sub(off_x0);
587        let len = min(len_src, len_tgt);
588
589        // area height to copy
590        let height_src = src_area.height.saturating_sub(cut_y0);
591        let height_tgt = tgt_area.height.saturating_sub(off_y0);
592        let height = min(height_src, height_tgt);
593
594        // ** slow version **
595        // for y in 0..height {
596        //     for x in 0..len {
597        //         let src_pos = Position::new(src_area.x + cut_x0 + x, src_area.y + cut_y0 + y);
598        //         let src_cell = self.buffer.cell(src_pos).expect("src-cell");
599        //
600        //         let tgt_pos = Position::new(tgt_area.x + off_x0 + x, tgt_area.y + off_y0 + y);
601        //         let tgt_cell = buf.cell_mut(tgt_pos).expect("tgt_cell");
602        //
603        //         *tgt_cell = src_cell.clone();
604        //     }
605        // }
606
607        for y in 0..height {
608            let src_0 = self
609                .buffer
610                .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
611            let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
612
613            let src = &self.buffer.content[src_0..src_0 + len as usize];
614            let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
615            tgt.clone_from_slice(src);
616        }
617
618        // keep buffer
619        state.buffer = Some(self.buffer);
620    }
621}
622
623impl<W> Default for ClipperState<W>
624where
625    W: Eq + Hash + Clone,
626{
627    fn default() -> Self {
628        Self {
629            area: Default::default(),
630            widget_area: Default::default(),
631            layout: Default::default(),
632            hscroll: Default::default(),
633            vscroll: Default::default(),
634            container: Default::default(),
635            buffer: None,
636            non_exhaustive: NonExhaustive,
637        }
638    }
639}
640
641impl<W> Clone for ClipperState<W>
642where
643    W: Eq + Hash + Clone,
644{
645    fn clone(&self) -> Self {
646        Self {
647            area: self.area,
648            widget_area: self.widget_area,
649            layout: self.layout.clone(),
650            hscroll: self.hscroll.clone(),
651            vscroll: self.vscroll.clone(),
652            container: FocusFlag::named(self.container.name()),
653            buffer: None,
654            non_exhaustive: NonExhaustive,
655        }
656    }
657}
658
659impl<W> HasFocus for ClipperState<W>
660where
661    W: Eq + Clone + Hash,
662{
663    fn build(&self, _builder: &mut FocusBuilder) {
664        // not an autonomous widget
665    }
666
667    fn focus(&self) -> FocusFlag {
668        self.container.clone()
669    }
670
671    fn area(&self) -> Rect {
672        self.area
673    }
674}
675
676impl<W> ClipperState<W>
677where
678    W: Eq + Clone + Hash,
679{
680    pub fn new() -> Self {
681        Self::default()
682    }
683
684    /// Clear the layout data and reset any scroll
685    pub fn clear(&mut self) {
686        self.layout.borrow_mut().clear();
687        self.hscroll.clear();
688        self.vscroll.clear();
689    }
690
691    /// Layout needs to change?
692    pub fn valid_layout(&self, size: Size) -> bool {
693        let layout = self.layout.borrow();
694        !layout.size_changed(size) && !layout.is_empty()
695    }
696
697    /// Set the layout.
698    pub fn set_layout(&mut self, layout: GenericLayout<W>) {
699        self.layout = Rc::new(RefCell::new(layout));
700    }
701
702    /// Layout.
703    pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
704        self.layout.borrow()
705    }
706
707    /// Show the area for the given handle.
708    pub fn show(&mut self, widget: W) {
709        let layout = self.layout.borrow();
710        let Some(idx) = layout.try_index_of(widget) else {
711            return;
712        };
713        let widget_area = layout.widget(idx);
714        let label_area = layout.label(idx);
715
716        let area = if !widget_area.is_empty() {
717            if !label_area.is_empty() {
718                Some(widget_area.union(label_area))
719            } else {
720                Some(widget_area)
721            }
722        } else {
723            if !label_area.is_empty() {
724                Some(label_area)
725            } else {
726                None
727            }
728        };
729
730        if let Some(area) = area {
731            self.hscroll
732                .scroll_to_range(area.left() as usize..area.right() as usize);
733            self.vscroll
734                .scroll_to_range(area.top() as usize..area.bottom() as usize);
735        }
736    }
737
738    /// Returns the first visible widget.
739    /// This uses insertion order of the widgets, not
740    /// any graphical ordering.
741    pub fn first(&self) -> Option<W> {
742        let layout = self.layout.borrow();
743
744        let area = Rect::new(
745            self.hscroll.offset() as u16,
746            self.vscroll.offset() as u16,
747            self.widget_area.width,
748            self.widget_area.height,
749        );
750
751        for idx in 0..layout.widget_len() {
752            if layout.widget(idx).intersects(area) {
753                return Some(layout.widget_key(idx).clone());
754            }
755        }
756
757        None
758    }
759}
760
761impl<W> ClipperState<W>
762where
763    W: Eq + Clone + Hash,
764{
765    pub fn vertical_offset(&self) -> usize {
766        self.vscroll.offset()
767    }
768
769    pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
770        let old = self.vscroll.offset();
771        self.vscroll.set_offset(offset);
772        old != self.vscroll.offset()
773    }
774
775    pub fn vertical_page_len(&self) -> usize {
776        self.vscroll.page_len()
777    }
778
779    pub fn horizontal_offset(&self) -> usize {
780        self.hscroll.offset()
781    }
782
783    pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
784        let old = self.hscroll.offset();
785        self.hscroll.set_offset(offset);
786        old != self.hscroll.offset()
787    }
788
789    pub fn horizontal_page_len(&self) -> usize {
790        self.hscroll.page_len()
791    }
792
793    pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
794        self.hscroll.scroll_to_pos(pos)
795    }
796
797    pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
798        self.vscroll.scroll_to_pos(pos)
799    }
800
801    /// Scroll the widget to visible.
802    pub fn scroll_to(&mut self, widget: W) -> bool {
803        let area = self.layout.borrow().widget_for(widget);
804        let r0 = self
805            .vscroll
806            .scroll_to_range(area.top() as usize..area.bottom() as usize);
807        let r1 = self
808            .hscroll
809            .scroll_to_range(area.left() as usize..area.right() as usize);
810        r0 || r1
811    }
812
813    pub fn scroll_up(&mut self, delta: usize) -> bool {
814        self.vscroll.scroll_up(delta)
815    }
816
817    pub fn scroll_down(&mut self, delta: usize) -> bool {
818        self.vscroll.scroll_down(delta)
819    }
820
821    pub fn scroll_left(&mut self, delta: usize) -> bool {
822        self.hscroll.scroll_left(delta)
823    }
824
825    pub fn scroll_right(&mut self, delta: usize) -> bool {
826        self.hscroll.scroll_right(delta)
827    }
828}
829
830impl<W> HandleEvent<crossterm::event::Event, Regular, Outcome> for ClipperState<W>
831where
832    W: Eq + Clone + Hash,
833{
834    fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
835        let r = if self.container.is_focused() {
836            match event {
837                ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
838                ct_event!(keycode press PageDown) => {
839                    self.scroll_down(self.vscroll.page_len()).into()
840                }
841                ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
842                ct_event!(keycode press End) => {
843                    self.vertical_scroll_to(self.vscroll.max_offset()).into()
844                }
845                _ => Outcome::Continue,
846            }
847        } else {
848            Outcome::Continue
849        };
850
851        r.or_else(|| self.handle(event, MouseOnly))
852    }
853}
854
855impl<W> HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ClipperState<W>
856where
857    W: Eq + Clone + Hash,
858{
859    fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
860        let mut sas = ScrollAreaState::new()
861            .area(self.widget_area)
862            .h_scroll(&mut self.hscroll)
863            .v_scroll(&mut self.vscroll);
864        match sas.handle(event, MouseOnly) {
865            ScrollOutcome::Up(v) => self.scroll_up(v).into(),
866            ScrollOutcome::Down(v) => self.scroll_down(v).into(),
867            ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
868            ScrollOutcome::Left(v) => self.scroll_left(v).into(),
869            ScrollOutcome::Right(v) => self.scroll_right(v).into(),
870            ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
871            r => r.into(),
872        }
873    }
874}