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