rat_widget/
clipper.rs

1//!
2//!
3//! Alternative View widget that renders only visible widgets.
4//!
5//! It uses a [GenericLayout] to find the visible widgets.
6//! It only uses a Buffer big enough to render these.
7//! They may be only partially visible of course.
8//!
9//! This helps with rendering speed and allows rendering more
10//! than u16::MAX lines.
11//!
12//! It works in several phases:
13//!
14//! ```rust no_run
15//!     # use rat_widget::clipper::{Clipper, ClipperState};
16//!     # use rat_widget::checkbox::{Checkbox, CheckboxState};
17//!     # use ratatui_core::layout::Rect;
18//!     # use ratatui_core::buffer::Buffer;
19//!     # use rat_focus::{FocusFlag, HasFocus};
20//!     # use rat_widget::layout::GenericLayout;
21//!     #
22//!     # let l2 = [Rect::ZERO, Rect::ZERO];
23//!     # struct State {
24//!     #      check_states: Vec<CheckboxState>,
25//!     #      clipper: ClipperState<FocusFlag>
26//!     #  }
27//!     # let mut state = State {
28//!     #      clipper: Default::default(),
29//!     #      check_states: Vec::default()
30//!     #  };
31//!     # let mut buf = Buffer::default();
32//!
33//!     /// Create the layout. The layout can be stored long-term
34//!     /// and needs to be rebuilt only if your widget layout changes.
35//!
36//!     let clipper = Clipper::new();
37//!     let layout_size = clipper.layout_size(l2[1], &mut state.clipper);
38//!
39//!     if !state.clipper.valid_layout(layout_size) {
40//!         let mut cl = GenericLayout::new();
41//!         for i in 0..100 {
42//!             cl.add(state.check_states[i].focus(),
43//!                 Rect::new(10, i as u16 *11, 15, 10),
44//!                 None,
45//!                 Rect::default()
46//!             );
47//!         }
48//!         state.clipper.set_layout(cl);
49//!     }
50//!
51//!     /// The given area plus the current scroll offset define the
52//!     /// view area. With the view area a temporary buffer is created
53//!     /// that is big enough to fit all widgets that are at least
54//!     /// partially visible.
55//!
56//!     let mut clip_buf = clipper
57//!         .into_buffer(l2[1], &mut state.clipper);
58//!
59//!     ///
60//!     /// The widgets are rendered to that buffer.
61//!     ///
62//!     for i in 0..100 {
63//!         // refer by handle
64//!         clip_buf.render(
65//!             state.check_states[i].focus(),
66//!             || {
67//!                 Checkbox::new()
68//!                 .text(format!("{:?}", i))
69//!             },
70//!             &mut state.check_states[i],
71//!         );
72//!     }
73//!
74//!     ///
75//!     /// The last step clips and copies the buffer to the frame buffer.
76//!     ///
77//!
78//!     clip_buf.finish(&mut buf, &mut state.clipper);
79//!
80//! ```
81//!
82//! __StatefulWidget__
83//!
84//! For this to work with StatefulWidgets they must cooperate
85//! by implementing the [RelocatableState]
86//! trait. With this trait the widget can clip/hide all areas that
87//! it stores in its state.
88//!
89//! __Form__
90//!
91//! There is an alternative to scrolling through long lists of widgets.
92//! With [Form](crate::form::Form) you can split the layout into pages.
93//! This avoids clipped widgets and allows the extra feature to stretch
94//! some widgets to fill the available vertical space.
95//!
96//! __See__
97//!
98//! [example](https://github.com/thscharler/rat-widget/blob/master/examples/clipper1.rs)
99//!
100
101use crate::_private::NonExhaustive;
102use crate::layout::GenericLayout;
103use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Outcome, Regular, ct_event};
104use rat_focus::{Focus, FocusBuilder, FocusFlag, HasFocus};
105use rat_reloc::RelocatableState;
106use rat_scrolled::event::ScrollOutcome;
107use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
108use ratatui_core::buffer::Buffer;
109use ratatui_core::layout::{Alignment, Position, Rect, Size};
110use ratatui_core::style::Style;
111use ratatui_core::text::Line;
112use ratatui_core::widgets::StatefulWidget;
113use ratatui_core::widgets::Widget;
114use ratatui_crossterm::crossterm::event::Event;
115use ratatui_widgets::block::Block;
116use std::borrow::Cow;
117use std::cell::{Ref, RefCell};
118use std::cmp::{max, min};
119use std::hash::Hash;
120use std::mem;
121use std::rc::Rc;
122
123/// This widget allows rendering to a temporary buffer and clips
124/// it to size for the final rendering.
125#[derive(Debug)]
126pub struct Clipper<'a, W = usize>
127where
128    W: Eq + Clone + Hash,
129{
130    layout: Option<GenericLayout<W>>,
131    style: Style,
132    block: Option<Block<'a>>,
133    hscroll: Option<Scroll<'a>>,
134    vscroll: Option<Scroll<'a>>,
135    label_style: Option<Style>,
136    label_alignment: Option<Alignment>,
137    manual_label: bool,
138    buffer_uses_view_size: bool,
139}
140
141/// Second stage: render widgets to the temporary buffer.
142#[derive(Debug)]
143pub struct ClipperBuffer<'a, W>
144where
145    W: Eq + Clone + Hash,
146{
147    layout: Rc<RefCell<GenericLayout<W>>>,
148    manual_label: bool,
149
150    // offset from buffer to scroll area
151    offset: Position,
152    buffer: Buffer,
153
154    // inner area that will finally be rendered.
155    widget_area: Rect,
156
157    style: Style,
158    block: Option<Block<'a>>,
159    hscroll: Option<Scroll<'a>>,
160    vscroll: Option<Scroll<'a>>,
161    label_style: Option<Style>,
162    label_alignment: Option<Alignment>,
163
164    destruct: bool,
165}
166
167/// Clipper styles.
168#[derive(Debug, Clone)]
169pub struct ClipperStyle {
170    pub style: Style,
171    pub block: Option<Block<'static>>,
172    pub border_style: Option<Style>,
173    pub title_style: Option<Style>,
174    pub scroll: Option<ScrollStyle>,
175    pub label_style: Option<Style>,
176    pub label_alignment: Option<Alignment>,
177    pub non_exhaustive: NonExhaustive,
178}
179
180impl Default for ClipperStyle {
181    fn default() -> Self {
182        Self {
183            style: Default::default(),
184            block: Default::default(),
185            border_style: Default::default(),
186            title_style: Default::default(),
187            scroll: Default::default(),
188            label_style: Default::default(),
189            label_alignment: Default::default(),
190            non_exhaustive: NonExhaustive,
191        }
192    }
193}
194
195/// Widget state.
196#[derive(Debug)]
197pub struct ClipperState<W = usize>
198where
199    W: Eq + Clone + Hash,
200{
201    // Full area for the widget.
202    /// __read only__ renewed for each render.
203    pub area: Rect,
204    /// Area inside the border.
205    /// __read only__ renewed for each render.
206    pub widget_area: Rect,
207
208    /// Page layout.
209    /// __read only__ renewed for each render.
210    pub layout: Rc<RefCell<GenericLayout<W>>>,
211
212    /// Horizontal scroll
213    /// __read+write__
214    pub hscroll: ScrollState,
215    /// Vertical scroll
216    /// __read+write__
217    pub vscroll: ScrollState,
218
219    /// This widget has no focus of its own, but this flag
220    /// can be used to set a container state.
221    pub container: FocusFlag,
222
223    /// For the buffer to survive render()
224    buffer: Option<Buffer>,
225
226    /// Only construct with `..Default::default()`.
227    pub non_exhaustive: NonExhaustive,
228}
229
230impl<W> Clone for Clipper<'_, W>
231where
232    W: Eq + Clone + Hash,
233{
234    fn clone(&self) -> Self {
235        Self {
236            style: Default::default(),
237            block: self.block.clone(),
238            layout: self.layout.clone(),
239            hscroll: self.hscroll.clone(),
240            vscroll: self.vscroll.clone(),
241            label_style: self.label_style.clone(),
242            label_alignment: self.label_alignment.clone(),
243            manual_label: self.manual_label,
244            buffer_uses_view_size: self.buffer_uses_view_size,
245        }
246    }
247}
248
249impl<W> Default for Clipper<'_, W>
250where
251    W: Eq + Clone + Hash,
252{
253    fn default() -> Self {
254        Self {
255            style: Default::default(),
256            block: Default::default(),
257            layout: Default::default(),
258            hscroll: Default::default(),
259            vscroll: Default::default(),
260            label_style: Default::default(),
261            label_alignment: Default::default(),
262            manual_label: Default::default(),
263            buffer_uses_view_size: Default::default(),
264        }
265    }
266}
267
268impl<'a, W> Clipper<'a, W>
269where
270    W: Eq + Clone + Hash,
271{
272    /// New Clipper.
273    pub fn new() -> Self {
274        Self::default()
275    }
276
277    /// Set the layout. If no layout is set here the layout is
278    /// taken from the state.
279    pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
280        self.layout = Some(layout);
281        self
282    }
283
284    /// Base style.
285    pub fn style(mut self, style: Style) -> Self {
286        self.style = style;
287        self.block = self.block.map(|v| v.style(style));
288        self
289    }
290
291    /// Render the label automatically when rendering the widget.
292    ///
293    /// Default: true
294    pub fn auto_label(mut self, auto: bool) -> Self {
295        self.manual_label = !auto;
296        self
297    }
298
299    /// Widget labels.
300    pub fn label_style(mut self, style: Style) -> Self {
301        self.label_style = Some(style);
302        self
303    }
304
305    /// Widget labels.
306    pub fn label_alignment(mut self, alignment: Alignment) -> Self {
307        self.label_alignment = Some(alignment);
308        self
309    }
310
311    /// Block for border
312    pub fn block(mut self, block: Block<'a>) -> Self {
313        self.block = Some(block);
314        self
315    }
316
317    /// Scroll support.
318    pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
319        self.hscroll = Some(scroll.clone().override_horizontal());
320        self.vscroll = Some(scroll.override_vertical());
321        self
322    }
323
324    /// Horizontal scroll support.
325    pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
326        self.hscroll = Some(scroll.override_horizontal());
327        self
328    }
329
330    /// Vertical scroll support.
331    pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
332        self.vscroll = Some(scroll.override_vertical());
333        self
334    }
335
336    /// Combined style.
337    pub fn styles(mut self, styles: ClipperStyle) -> Self {
338        self.style = styles.style;
339        if styles.block.is_some() {
340            self.block = styles.block;
341        }
342        if let Some(border_style) = styles.border_style {
343            self.block = self.block.map(|v| v.border_style(border_style));
344        }
345        if let Some(title_style) = styles.title_style {
346            self.block = self.block.map(|v| v.title_style(title_style));
347        }
348        self.block = self.block.map(|v| v.style(self.style));
349        if styles.label_style.is_some() {
350            self.label_style = styles.label_style;
351        }
352        if styles.label_alignment.is_some() {
353            self.label_alignment = styles.label_alignment;
354        }
355        if let Some(styles) = styles.scroll {
356            self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
357            self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
358        }
359        self.block = self.block.map(|v| v.style(styles.style));
360        self
361    }
362
363    /// By default, the buffer is sized according to the maximum extend
364    /// of the visible widgets.
365    ///
366    /// This may not be enough, when you use popups.
367    ///
368    /// This flag indicates, that the buffer should be at least the size
369    /// of the rendered view area.
370    pub fn buffer_uses_view_size(mut self) -> Self {
371        self.buffer_uses_view_size = true;
372        self
373    }
374
375    /// Calculate the layout size.
376    /// Returns the size of the inner area that is available
377    /// for drawing widgets.
378    pub fn layout_size(&self, area: Rect, state: &ClipperState<W>) -> Size {
379        let width = self.inner(area, state).width;
380        Size::new(width, u16::MAX)
381    }
382
383    /// Calculate the view area.
384    fn inner(&self, area: Rect, state: &ClipperState<W>) -> Rect {
385        let sa = ScrollArea::new()
386            .block(self.block.as_ref())
387            .h_scroll(self.hscroll.as_ref())
388            .v_scroll(self.vscroll.as_ref());
389        sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
390    }
391
392    fn calc_layout(&self, area: Rect, state: &mut ClipperState<W>) -> (Rect, Position) {
393        let layout = state.layout.borrow();
394
395        let view = Rect::new(
396            state.hscroll.offset() as u16,
397            state.vscroll.offset() as u16,
398            area.width,
399            area.height,
400        );
401
402        // maxima for scroll bar max
403        let mut max_pos = Position::default();
404
405        // find the bounding box for the buffer.
406        // convex hull of all visible widgets/labels/blocks.
407        let mut ext_view: Option<Rect> = None;
408        for idx in 0..layout.widget_len() {
409            let area = layout.widget(idx);
410            let label_area = layout.label(idx);
411
412            if view.intersects(area) || view.intersects(label_area) {
413                if !area.is_empty() {
414                    ext_view = ext_view //
415                        .map(|v| v.union(area))
416                        .or(Some(area));
417                }
418                if !label_area.is_empty() {
419                    ext_view = ext_view //
420                        .map(|v| v.union(label_area))
421                        .or(Some(label_area));
422                }
423            }
424
425            max_pos.x = max(max_pos.x, area.right());
426            max_pos.y = max(max_pos.y, area.bottom());
427            max_pos.x = max(max_pos.x, label_area.right());
428            max_pos.y = max(max_pos.y, label_area.bottom());
429        }
430        for idx in 0..layout.block_len() {
431            let block_area = layout.block_area(idx);
432            if view.intersects(block_area) {
433                ext_view = ext_view //
434                    .map(|v| v.union(block_area))
435                    .or(Some(block_area));
436            }
437
438            max_pos.x = max(max_pos.x, block_area.right());
439            max_pos.y = max(max_pos.y, block_area.bottom());
440        }
441
442        let ext_view = ext_view.unwrap_or(view);
443
444        (ext_view, max_pos)
445    }
446
447    /// Calculates the layout and creates a temporary buffer.
448    pub fn into_buffer(mut self, area: Rect, state: &mut ClipperState<W>) -> ClipperBuffer<'a, W> {
449        state.area = area;
450        if let Some(layout) = self.layout.take() {
451            state.layout = Rc::new(RefCell::new(layout));
452        }
453
454        let sa = ScrollArea::new()
455            .block(self.block.as_ref())
456            .h_scroll(self.hscroll.as_ref())
457            .v_scroll(self.vscroll.as_ref());
458        state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
459
460        // run the layout
461        let (mut ext_area, max_pos) = self.calc_layout(area, state);
462
463        if self.buffer_uses_view_size {
464            ext_area = ext_area.union(area)
465        }
466
467        // adjust scroll
468        state
469            .vscroll
470            .set_page_len(state.widget_area.height as usize);
471        state
472            .vscroll
473            .set_max_offset(max_pos.y.saturating_sub(state.widget_area.height) as usize);
474        state.hscroll.set_page_len(state.widget_area.width as usize);
475        state
476            .hscroll
477            .set_max_offset(max_pos.x.saturating_sub(state.widget_area.width) as usize);
478
479        let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
480
481        // resize buffer to fit all visible widgets.
482        let buffer_area = ext_area;
483        // resize buffer to fit the layout.
484        let mut buffer = if let Some(mut buffer) = state.buffer.take() {
485            buffer.reset();
486            buffer.resize(buffer_area);
487            buffer
488        } else {
489            Buffer::empty(buffer_area)
490        };
491        buffer.set_style(buffer_area, self.style);
492
493        ClipperBuffer {
494            layout: state.layout.clone(),
495            manual_label: self.manual_label,
496            offset,
497            buffer,
498            widget_area: state.widget_area,
499            style: self.style,
500            block: self.block,
501            hscroll: self.hscroll,
502            vscroll: self.vscroll,
503            label_style: self.label_style,
504            label_alignment: self.label_alignment,
505            destruct: false,
506        }
507    }
508}
509
510impl<'a, W> Drop for ClipperBuffer<'a, W>
511where
512    W: Eq + Hash + Clone,
513{
514    fn drop(&mut self) {
515        if !self.destruct {
516            panic!("ClipperBuffer: Must be used. Call finish(..)");
517        }
518    }
519}
520
521impl<'a, W> ClipperBuffer<'a, W>
522where
523    W: Eq + Hash + Clone,
524{
525    /// Is the widget visible.
526    pub fn is_visible(&self, widget: W) -> bool {
527        let layout = self.layout.borrow();
528        let Some(idx) = layout.try_index_of(widget) else {
529            return false;
530        };
531        let area = layout.widget(idx);
532        self.buffer.area.intersects(area)
533    }
534
535    /// Render the label with the set style and alignment.
536    #[inline(always)]
537    fn render_auto_label(&mut self, idx: usize) -> bool {
538        let layout = self.layout.borrow();
539        let Some(label_area) = self.locate_area(layout.label(idx)) else {
540            return false;
541        };
542        let Some(label_str) = layout.try_label_str(idx) else {
543            return false;
544        };
545
546        let mut label = Line::from(label_str.as_ref());
547        if let Some(style) = self.label_style {
548            label = label.style(style)
549        };
550        if let Some(align) = self.label_alignment {
551            label = label.alignment(align);
552        }
553        label.render(label_area, &mut self.buffer);
554
555        true
556    }
557
558    /// Render all visible blocks.
559    fn render_block(&mut self) {
560        let layout = self.layout.borrow();
561        for (idx, block_area) in layout.block_area_iter().enumerate() {
562            if let Some(block_area) = self.locate_area(*block_area) {
563                if let Some(block) = layout.block(idx) {
564                    block.render(block_area, &mut self.buffer);
565                }
566            }
567        }
568    }
569
570    // q: why is this a render instead of returning a Widget??
571    // a: lifetime of `&Option<Cow<'static, str>>`. Can't use
572    //    this as part of a return value.
573    /// Render the label for the given widget.
574    #[inline(always)]
575    pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
576    where
577        FN: FnOnce(&Option<Cow<'static, str>>, Rect, &mut Buffer),
578    {
579        let layout = self.layout.borrow();
580        let Some(idx) = layout.try_index_of(widget) else {
581            return false;
582        };
583        let Some(label_area) = self.locate_area(layout.label(idx)) else {
584            return false;
585        };
586        let label_str = layout.try_label_str(idx);
587        render_fn(label_str, label_area, &mut self.buffer);
588        true
589    }
590
591    /// Render a stateless widget and its label.
592    #[inline(always)]
593    pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
594    where
595        FN: FnOnce() -> WW,
596        WW: Widget,
597    {
598        let Some(idx) = self.layout.borrow().try_index_of(widget) else {
599            return false;
600        };
601        if !self.manual_label {
602            self.render_auto_label(idx);
603        }
604        let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
605            return false;
606        };
607        render_fn().render(widget_area, &mut self.buffer);
608        true
609    }
610
611    /// Render a stateful widget and its label.
612    #[inline(always)]
613    pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
614    where
615        FN: FnOnce() -> WW,
616        WW: StatefulWidget<State = SS>,
617        SS: RelocatableState,
618    {
619        let Some(idx) = self.layout.borrow().try_index_of(widget) else {
620            return false;
621        };
622        if !self.manual_label {
623            self.render_auto_label(idx);
624        }
625        let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
626            state.relocate_hidden();
627            return false;
628        };
629        render_fn().render(widget_area, &mut self.buffer, state);
630        state.relocate(self.shift(), self.widget_area);
631        true
632    }
633
634    /// Render a stateful widget and its label.
635    ///
636    /// This expects a pair of StatefulWidgets, of which the first
637    /// will be rendered and the second will be returned.
638    ///
639    /// This is for rendering widgets that split in two parts,
640    /// the main widget and a popup. The popup must be rendered later
641    /// to be 'above' all other widgets. Use [render_pop] for this.
642    #[inline(always)]
643    pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
644    where
645        FN: FnOnce() -> (WW, R),
646        WW: StatefulWidget<State = SS>,
647        SS: RelocatableState,
648    {
649        let Some(idx) = self.layout.borrow().try_index_of(widget) else {
650            return None;
651        };
652        if !self.manual_label {
653            self.render_auto_label(idx);
654        }
655        let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
656            state.relocate_hidden();
657            return None;
658        };
659        let (widget, remainder) = render_fn();
660        widget.render(widget_area, &mut self.buffer, state);
661        state.relocate(self.shift(), self.widget_area);
662
663        Some(remainder)
664    }
665
666    /// Render an additional popup widget for the given main widget.
667    ///
668    /// Doesn't call relocate() at all.
669    #[inline(always)]
670    pub fn render_popup<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
671    where
672        FN: FnOnce() -> Option<WW>,
673        WW: StatefulWidget<State = SS>,
674        SS: RelocatableState,
675    {
676        let Some(idx) = self.layout.borrow().try_index_of(widget) else {
677            return false;
678        };
679        let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
680            state.relocate_popup_hidden();
681            return false;
682        };
683        let widget = render_fn();
684        if let Some(widget) = widget {
685            widget.render(widget_area, &mut self.buffer, state);
686            state.relocate_popup(self.shift(), self.widget_area);
687            true
688        } else {
689            state.relocate_popup_hidden();
690            false
691        }
692    }
693
694    /// Get the buffer coordinates for the given widget.
695    #[inline]
696    pub fn locate_widget(&self, widget: W) -> Option<Rect> {
697        let layout = self.layout.borrow();
698        let Some(idx) = layout.try_index_of(widget) else {
699            return None;
700        };
701        self.locate_area(layout.widget(idx))
702    }
703
704    /// Get the buffer coordinates for the label of the given widget.
705    #[inline]
706    #[allow(clippy::question_mark)]
707    pub fn locate_label(&self, widget: W) -> Option<Rect> {
708        let layout = self.layout.borrow();
709        let Some(idx) = layout.try_index_of(widget) else {
710            return None;
711        };
712        self.locate_area(layout.label(idx))
713    }
714
715    /// Relocate the area from layout coordinates to buffer coordinates,
716    /// which is a noop as those are aligned.
717    ///
718    /// But this will return None if the given area is outside the buffer.
719    #[inline]
720    pub fn locate_area(&self, area: Rect) -> Option<Rect> {
721        if self.buffer.area.intersects(area) {
722            Some(area)
723        } else {
724            None
725        }
726    }
727
728    /// Calculate the necessary shift from layout to screen.
729    fn shift(&self) -> (i16, i16) {
730        (
731            self.widget_area.x as i16 - self.offset.x as i16,
732            self.widget_area.y as i16 - self.offset.y as i16,
733        )
734    }
735
736    /// After rendering the widget to the buffer it may have
737    /// stored areas in its state. These will be in buffer
738    /// coordinates instead of screen coordinates.
739    ///
740    /// Call this function to correct this after rendering.
741    ///
742    /// Note:
743    ///
744    /// This is only necessary if you do some manual rendering
745    /// of stateful widgets. If you use [render] this will
746    /// happen automatically
747    ///
748    /// Parameter:
749    ///
750    /// widget: The visibility of this widget will determine
751    /// if the areas in state are shifted or hidden altogether.
752    pub fn relocate<S>(&self, widget: W, state: &mut S)
753    where
754        S: RelocatableState,
755    {
756        let Some(idx) = self.layout.borrow().try_index_of(widget) else {
757            return;
758        };
759        if self.locate_area(self.layout.borrow().widget(idx)).is_some() {
760            state.relocate(self.shift(), self.widget_area);
761        } else {
762            state.relocate_hidden();
763        };
764    }
765
766    /// Return a reference to the buffer.
767    #[inline]
768    pub fn buffer(&mut self) -> &mut Buffer {
769        &mut self.buffer
770    }
771
772    pub fn finish(mut self, tgt_buf: &mut Buffer, state: &mut ClipperState<W>) {
773        self.destruct = true;
774
775        self.render_block();
776
777        ScrollArea::new()
778            .style(self.style)
779            .block(self.block.as_ref())
780            .h_scroll(self.hscroll.as_ref())
781            .v_scroll(self.vscroll.as_ref())
782            .render(
783                state.area,
784                tgt_buf,
785                &mut ScrollAreaState::new()
786                    .h_scroll(&mut state.hscroll)
787                    .v_scroll(&mut state.vscroll),
788            );
789
790        let src_area = self.buffer.area;
791        let tgt_area = state.widget_area;
792        let offset = self.offset;
793
794        // extra offset due to buffer starts right of offset.
795        let off_x0 = src_area.x.saturating_sub(offset.x);
796        let off_y0 = src_area.y.saturating_sub(offset.y);
797        // cut source buffer due to start left of offset.
798        let cut_x0 = offset.x.saturating_sub(src_area.x);
799        let cut_y0 = offset.y.saturating_sub(src_area.y);
800
801        // length to copy
802        let len_src = src_area.width.saturating_sub(cut_x0);
803        let len_tgt = tgt_area.width.saturating_sub(off_x0);
804        let len = min(len_src, len_tgt);
805
806        // area height to copy
807        let height_src = src_area.height.saturating_sub(cut_y0);
808        let height_tgt = tgt_area.height.saturating_sub(off_y0);
809        let height = min(height_src, height_tgt);
810
811        // ** slow version **
812        // for y in 0..height {
813        //     for x in 0..len {
814        //         let src_pos = Position::new(src_area.x + cut_x0 + x, src_area.y + cut_y0 + y);
815        //         let src_cell = self.buffer.cell(src_pos).expect("src-cell");
816        //
817        //         let tgt_pos = Position::new(tgt_area.x + off_x0 + x, tgt_area.y + off_y0 + y);
818        //         let tgt_cell = buf.cell_mut(tgt_pos).expect("tgt_cell");
819        //
820        //         *tgt_cell = src_cell.clone();
821        //     }
822        // }
823
824        for y in 0..height {
825            let src_0 = self
826                .buffer
827                .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
828
829            if tgt_buf
830                .area
831                .contains(Position::new(tgt_area.x + off_x0, tgt_area.y + off_y0 + y))
832            {
833                let tgt_0 = tgt_buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
834
835                let src = &self.buffer.content[src_0..src_0 + len as usize];
836                let tgt = &mut tgt_buf.content[tgt_0..tgt_0 + len as usize];
837                tgt.clone_from_slice(src);
838            }
839        }
840
841        // keep buffer
842        state.buffer = Some(mem::take(&mut self.buffer));
843    }
844}
845
846impl<W> Default for ClipperState<W>
847where
848    W: Eq + Hash + Clone,
849{
850    fn default() -> Self {
851        Self {
852            area: Default::default(),
853            widget_area: Default::default(),
854            layout: Default::default(),
855            hscroll: Default::default(),
856            vscroll: Default::default(),
857            container: Default::default(),
858            buffer: None,
859            non_exhaustive: NonExhaustive,
860        }
861    }
862}
863
864impl<W> Clone for ClipperState<W>
865where
866    W: Eq + Hash + Clone,
867{
868    fn clone(&self) -> Self {
869        Self {
870            area: self.area,
871            widget_area: self.widget_area,
872            layout: self.layout.clone(),
873            hscroll: self.hscroll.clone(),
874            vscroll: self.vscroll.clone(),
875            container: self.container.new_instance(),
876            buffer: None,
877            non_exhaustive: NonExhaustive,
878        }
879    }
880}
881
882impl<W> HasFocus for ClipperState<W>
883where
884    W: Eq + Clone + Hash,
885{
886    fn build(&self, _builder: &mut FocusBuilder) {
887        // not an autonomous widget
888    }
889
890    fn focus(&self) -> FocusFlag {
891        self.container.clone()
892    }
893
894    fn area(&self) -> Rect {
895        self.area
896    }
897}
898
899impl<W> RelocatableState for ClipperState<W>
900where
901    W: Eq + Clone + Hash,
902{
903    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
904        self.area.relocate(shift, clip);
905        self.widget_area.relocate(shift, clip);
906        self.hscroll.relocate(shift, clip);
907        self.vscroll.relocate(shift, clip);
908    }
909}
910
911impl<W> ClipperState<W>
912where
913    W: Eq + Clone + Hash,
914{
915    pub fn new() -> Self {
916        Self::default()
917    }
918
919    pub fn named(name: &str) -> Self {
920        let mut z = Self::default();
921        z.container = z.container.with_name(name);
922        z
923    }
924
925    /// Clear the layout data and reset any scroll
926    pub fn clear(&mut self) {
927        self.layout.borrow_mut().clear();
928        self.hscroll.clear();
929        self.vscroll.clear();
930    }
931
932    /// Layout needs to change?
933    pub fn valid_layout(&self, size: Size) -> bool {
934        let layout = self.layout.borrow();
935        !layout.size_changed(size) && !layout.is_empty()
936    }
937
938    /// Set the layout.
939    pub fn set_layout(&mut self, layout: GenericLayout<W>) {
940        self.layout = Rc::new(RefCell::new(layout));
941    }
942
943    /// Layout.
944    pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
945        self.layout.borrow()
946    }
947
948    /// Scroll to the given widget.
949    pub fn show(&mut self, widget: W) -> bool {
950        let layout = self.layout.borrow();
951        let Some(idx) = layout.try_index_of(widget) else {
952            return false;
953        };
954        let widget_area = layout.widget(idx);
955        let label_area = layout.label(idx);
956
957        let area = if !widget_area.is_empty() {
958            if !label_area.is_empty() {
959                Some(widget_area.union(label_area))
960            } else {
961                Some(widget_area)
962            }
963        } else {
964            if !label_area.is_empty() {
965                Some(label_area)
966            } else {
967                None
968            }
969        };
970
971        if let Some(area) = area {
972            let h = self
973                .hscroll
974                .scroll_to_range(area.left() as usize..area.right() as usize);
975            let v = self
976                .vscroll
977                .scroll_to_range(area.top() as usize..area.bottom() as usize);
978            h || v
979        } else {
980            false
981        }
982    }
983
984    /// Returns the first visible widget.
985    /// This uses insertion order of the widgets, not
986    /// any graphical ordering.
987    pub fn first(&self) -> Option<W> {
988        let layout = self.layout.borrow();
989
990        let area = Rect::new(
991            self.hscroll.offset() as u16,
992            self.vscroll.offset() as u16,
993            self.widget_area.width,
994            self.widget_area.height,
995        );
996
997        for idx in 0..layout.widget_len() {
998            if layout.widget(idx).intersects(area) {
999                return Some(layout.widget_key(idx).clone());
1000            }
1001        }
1002
1003        None
1004    }
1005}
1006
1007impl<W> ClipperState<W>
1008where
1009    W: Eq + Clone + Hash,
1010{
1011    pub fn vertical_offset(&self) -> usize {
1012        self.vscroll.offset()
1013    }
1014
1015    pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
1016        let old = self.vscroll.offset();
1017        self.vscroll.set_offset(offset);
1018        old != self.vscroll.offset()
1019    }
1020
1021    pub fn vertical_page_len(&self) -> usize {
1022        self.vscroll.page_len()
1023    }
1024
1025    pub fn horizontal_offset(&self) -> usize {
1026        self.hscroll.offset()
1027    }
1028
1029    pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
1030        let old = self.hscroll.offset();
1031        self.hscroll.set_offset(offset);
1032        old != self.hscroll.offset()
1033    }
1034
1035    pub fn horizontal_page_len(&self) -> usize {
1036        self.hscroll.page_len()
1037    }
1038
1039    pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
1040        self.hscroll.scroll_to_pos(pos)
1041    }
1042
1043    pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
1044        self.vscroll.scroll_to_pos(pos)
1045    }
1046
1047    /// Scroll the widget to visible.
1048    pub fn scroll_to(&mut self, widget: W) -> bool {
1049        self.show(widget)
1050    }
1051
1052    pub fn scroll_up(&mut self, delta: usize) -> bool {
1053        self.vscroll.scroll_up(delta)
1054    }
1055
1056    pub fn scroll_down(&mut self, delta: usize) -> bool {
1057        self.vscroll.scroll_down(delta)
1058    }
1059
1060    pub fn scroll_left(&mut self, delta: usize) -> bool {
1061        self.hscroll.scroll_left(delta)
1062    }
1063
1064    pub fn scroll_right(&mut self, delta: usize) -> bool {
1065        self.hscroll.scroll_right(delta)
1066    }
1067}
1068
1069impl ClipperState<usize> {
1070    /// Focus the first widget on the active page.
1071    /// This assumes the usize-key is a widget id.
1072    pub fn focus_first(&self, focus: &Focus) -> bool {
1073        if let Some(w) = self.first() {
1074            focus.by_widget_id(w);
1075            true
1076        } else {
1077            false
1078        }
1079    }
1080
1081    /// Show the page with the focused widget.
1082    /// This assumes the usize-key is a widget id.
1083    /// Does nothing if none of the widgets has the focus.
1084    ///
1085    /// You must use the boolean to trigger a repaint.
1086    pub fn show_focused(&mut self, focus: &Focus) -> bool {
1087        let Some(focused) = focus.focused() else {
1088            return false;
1089        };
1090        let focused = focused.widget_id();
1091        self.scroll_to(focused)
1092    }
1093}
1094
1095impl ClipperState<FocusFlag> {
1096    /// Focus the first widget on the active page.
1097    pub fn focus_first(&self, focus: &Focus) -> bool {
1098        if let Some(w) = self.first() {
1099            focus.focus(&w);
1100            true
1101        } else {
1102            false
1103        }
1104    }
1105
1106    /// Show the page with the focused widget.
1107    /// Does nothing if none of the widgets has the focus.
1108    pub fn show_focused(&mut self, focus: &Focus) -> bool {
1109        let Some(focused) = focus.focused() else {
1110            return false;
1111        };
1112        self.scroll_to(focused)
1113    }
1114}
1115
1116impl<W> HandleEvent<Event, Regular, Outcome> for ClipperState<W>
1117where
1118    W: Eq + Clone + Hash,
1119{
1120    fn handle(&mut self, event: &Event, _keymap: Regular) -> Outcome {
1121        let r = if self.container.is_focused() {
1122            match event {
1123                ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
1124                ct_event!(keycode press PageDown) => {
1125                    self.scroll_down(self.vscroll.page_len()).into()
1126                }
1127                ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
1128                ct_event!(keycode press End) => {
1129                    self.vertical_scroll_to(self.vscroll.max_offset()).into()
1130                }
1131                _ => Outcome::Continue,
1132            }
1133        } else {
1134            Outcome::Continue
1135        };
1136
1137        r.or_else(|| self.handle(event, MouseOnly))
1138    }
1139}
1140
1141impl<W> HandleEvent<Event, MouseOnly, Outcome> for ClipperState<W>
1142where
1143    W: Eq + Clone + Hash,
1144{
1145    fn handle(&mut self, event: &Event, _keymap: MouseOnly) -> Outcome {
1146        let mut sas = ScrollAreaState::new()
1147            .area(self.widget_area)
1148            .h_scroll(&mut self.hscroll)
1149            .v_scroll(&mut self.vscroll);
1150        match sas.handle(event, MouseOnly) {
1151            ScrollOutcome::Up(v) => self.scroll_up(v).into(),
1152            ScrollOutcome::Down(v) => self.scroll_down(v).into(),
1153            ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
1154            ScrollOutcome::Left(v) => self.scroll_left(v).into(),
1155            ScrollOutcome::Right(v) => self.scroll_right(v).into(),
1156            ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
1157            r => r.into(),
1158        }
1159    }
1160}
1161
1162/// Handle all events.
1163/// Text events are only processed if focus is true.
1164/// Mouse events are processed if they are in range.
1165pub fn handle_events<W>(state: &mut ClipperState<W>, _focus: bool, event: &Event) -> Outcome
1166where
1167    W: Eq + Clone + Hash,
1168{
1169    HandleEvent::handle(state, event, Regular)
1170}
1171
1172/// Handle only mouse-events.
1173pub fn handle_mouse_events<W>(state: &mut ClipperState<W>, event: &Event) -> Outcome
1174where
1175    W: Eq + Clone + Hash,
1176{
1177    HandleEvent::handle(state, event, MouseOnly)
1178}