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            let tgt_0 = tgt_buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
826
827            let src = &self.buffer.content[src_0..src_0 + len as usize];
828            let tgt = &mut tgt_buf.content[tgt_0..tgt_0 + len as usize];
829            tgt.clone_from_slice(src);
830        }
831
832        // keep buffer
833        state.buffer = Some(mem::take(&mut self.buffer));
834    }
835}
836
837impl<W> Default for ClipperState<W>
838where
839    W: Eq + Hash + Clone,
840{
841    fn default() -> Self {
842        Self {
843            area: Default::default(),
844            widget_area: Default::default(),
845            layout: Default::default(),
846            hscroll: Default::default(),
847            vscroll: Default::default(),
848            container: Default::default(),
849            buffer: None,
850            non_exhaustive: NonExhaustive,
851        }
852    }
853}
854
855impl<W> Clone for ClipperState<W>
856where
857    W: Eq + Hash + Clone,
858{
859    fn clone(&self) -> Self {
860        Self {
861            area: self.area,
862            widget_area: self.widget_area,
863            layout: self.layout.clone(),
864            hscroll: self.hscroll.clone(),
865            vscroll: self.vscroll.clone(),
866            container: self.container.new_instance(),
867            buffer: None,
868            non_exhaustive: NonExhaustive,
869        }
870    }
871}
872
873impl<W> HasFocus for ClipperState<W>
874where
875    W: Eq + Clone + Hash,
876{
877    fn build(&self, _builder: &mut FocusBuilder) {
878        // not an autonomous widget
879    }
880
881    fn focus(&self) -> FocusFlag {
882        self.container.clone()
883    }
884
885    fn area(&self) -> Rect {
886        self.area
887    }
888}
889
890impl<W> ClipperState<W>
891where
892    W: Eq + Clone + Hash,
893{
894    pub fn new() -> Self {
895        Self::default()
896    }
897
898    pub fn named(name: &str) -> Self {
899        let mut z = Self::default();
900        z.container = z.container.with_name(name);
901        z
902    }
903
904    /// Clear the layout data and reset any scroll
905    pub fn clear(&mut self) {
906        self.layout.borrow_mut().clear();
907        self.hscroll.clear();
908        self.vscroll.clear();
909    }
910
911    /// Layout needs to change?
912    pub fn valid_layout(&self, size: Size) -> bool {
913        let layout = self.layout.borrow();
914        !layout.size_changed(size) && !layout.is_empty()
915    }
916
917    /// Set the layout.
918    pub fn set_layout(&mut self, layout: GenericLayout<W>) {
919        self.layout = Rc::new(RefCell::new(layout));
920    }
921
922    /// Layout.
923    pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
924        self.layout.borrow()
925    }
926
927    /// Scroll to the given widget.
928    pub fn show(&mut self, widget: W) -> bool {
929        let layout = self.layout.borrow();
930        let Some(idx) = layout.try_index_of(widget) else {
931            return false;
932        };
933        let widget_area = layout.widget(idx);
934        let label_area = layout.label(idx);
935
936        let area = if !widget_area.is_empty() {
937            if !label_area.is_empty() {
938                Some(widget_area.union(label_area))
939            } else {
940                Some(widget_area)
941            }
942        } else {
943            if !label_area.is_empty() {
944                Some(label_area)
945            } else {
946                None
947            }
948        };
949
950        if let Some(area) = area {
951            let h = self
952                .hscroll
953                .scroll_to_range(area.left() as usize..area.right() as usize);
954            let v = self
955                .vscroll
956                .scroll_to_range(area.top() as usize..area.bottom() as usize);
957            h || v
958        } else {
959            false
960        }
961    }
962
963    /// Returns the first visible widget.
964    /// This uses insertion order of the widgets, not
965    /// any graphical ordering.
966    pub fn first(&self) -> Option<W> {
967        let layout = self.layout.borrow();
968
969        let area = Rect::new(
970            self.hscroll.offset() as u16,
971            self.vscroll.offset() as u16,
972            self.widget_area.width,
973            self.widget_area.height,
974        );
975
976        for idx in 0..layout.widget_len() {
977            if layout.widget(idx).intersects(area) {
978                return Some(layout.widget_key(idx).clone());
979            }
980        }
981
982        None
983    }
984}
985
986impl<W> ClipperState<W>
987where
988    W: Eq + Clone + Hash,
989{
990    pub fn vertical_offset(&self) -> usize {
991        self.vscroll.offset()
992    }
993
994    pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
995        let old = self.vscroll.offset();
996        self.vscroll.set_offset(offset);
997        old != self.vscroll.offset()
998    }
999
1000    pub fn vertical_page_len(&self) -> usize {
1001        self.vscroll.page_len()
1002    }
1003
1004    pub fn horizontal_offset(&self) -> usize {
1005        self.hscroll.offset()
1006    }
1007
1008    pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
1009        let old = self.hscroll.offset();
1010        self.hscroll.set_offset(offset);
1011        old != self.hscroll.offset()
1012    }
1013
1014    pub fn horizontal_page_len(&self) -> usize {
1015        self.hscroll.page_len()
1016    }
1017
1018    pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
1019        self.hscroll.scroll_to_pos(pos)
1020    }
1021
1022    pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
1023        self.vscroll.scroll_to_pos(pos)
1024    }
1025
1026    /// Scroll the widget to visible.
1027    pub fn scroll_to(&mut self, widget: W) -> bool {
1028        self.show(widget)
1029    }
1030
1031    pub fn scroll_up(&mut self, delta: usize) -> bool {
1032        self.vscroll.scroll_up(delta)
1033    }
1034
1035    pub fn scroll_down(&mut self, delta: usize) -> bool {
1036        self.vscroll.scroll_down(delta)
1037    }
1038
1039    pub fn scroll_left(&mut self, delta: usize) -> bool {
1040        self.hscroll.scroll_left(delta)
1041    }
1042
1043    pub fn scroll_right(&mut self, delta: usize) -> bool {
1044        self.hscroll.scroll_right(delta)
1045    }
1046}
1047
1048impl ClipperState<usize> {
1049    /// Focus the first widget on the active page.
1050    /// This assumes the usize-key is a widget id.
1051    pub fn focus_first(&self, focus: &Focus) -> bool {
1052        if let Some(w) = self.first() {
1053            focus.by_widget_id(w);
1054            true
1055        } else {
1056            false
1057        }
1058    }
1059
1060    /// Show the page with the focused widget.
1061    /// This assumes the usize-key is a widget id.
1062    /// Does nothing if none of the widgets has the focus.
1063    ///
1064    /// You must use the boolean to trigger a repaint.
1065    pub fn show_focused(&mut self, focus: &Focus) -> bool {
1066        let Some(focused) = focus.focused() else {
1067            return false;
1068        };
1069        let focused = focused.widget_id();
1070        self.scroll_to(focused)
1071    }
1072}
1073
1074impl ClipperState<FocusFlag> {
1075    /// Focus the first widget on the active page.
1076    pub fn focus_first(&self, focus: &Focus) -> bool {
1077        if let Some(w) = self.first() {
1078            focus.focus(&w);
1079            true
1080        } else {
1081            false
1082        }
1083    }
1084
1085    /// Show the page with the focused widget.
1086    /// Does nothing if none of the widgets has the focus.
1087    pub fn show_focused(&mut self, focus: &Focus) -> bool {
1088        let Some(focused) = focus.focused() else {
1089            return false;
1090        };
1091        self.scroll_to(focused)
1092    }
1093}
1094
1095impl<W> HandleEvent<crossterm::event::Event, Regular, Outcome> for ClipperState<W>
1096where
1097    W: Eq + Clone + Hash,
1098{
1099    fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
1100        let r = if self.container.is_focused() {
1101            match event {
1102                ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
1103                ct_event!(keycode press PageDown) => {
1104                    self.scroll_down(self.vscroll.page_len()).into()
1105                }
1106                ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
1107                ct_event!(keycode press End) => {
1108                    self.vertical_scroll_to(self.vscroll.max_offset()).into()
1109                }
1110                _ => Outcome::Continue,
1111            }
1112        } else {
1113            Outcome::Continue
1114        };
1115
1116        r.or_else(|| self.handle(event, MouseOnly))
1117    }
1118}
1119
1120impl<W> HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ClipperState<W>
1121where
1122    W: Eq + Clone + Hash,
1123{
1124    fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
1125        let mut sas = ScrollAreaState::new()
1126            .area(self.widget_area)
1127            .h_scroll(&mut self.hscroll)
1128            .v_scroll(&mut self.vscroll);
1129        match sas.handle(event, MouseOnly) {
1130            ScrollOutcome::Up(v) => self.scroll_up(v).into(),
1131            ScrollOutcome::Down(v) => self.scroll_down(v).into(),
1132            ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
1133            ScrollOutcome::Left(v) => self.scroll_left(v).into(),
1134            ScrollOutcome::Right(v) => self.scroll_right(v).into(),
1135            ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
1136            r => r.into(),
1137        }
1138    }
1139}
1140
1141/// Handle all events.
1142/// Text events are only processed if focus is true.
1143/// Mouse events are processed if they are in range.
1144pub fn handle_events<W>(
1145    state: &mut ClipperState<W>,
1146    _focus: bool,
1147    event: &crossterm::event::Event,
1148) -> Outcome
1149where
1150    W: Eq + Clone + Hash,
1151{
1152    HandleEvent::handle(state, event, Regular)
1153}
1154
1155/// Handle only mouse-events.
1156pub fn handle_mouse_events<W>(
1157    state: &mut ClipperState<W>,
1158    event: &crossterm::event::Event,
1159) -> Outcome
1160where
1161    W: Eq + Clone + Hash,
1162{
1163    HandleEvent::handle(state, event, MouseOnly)
1164}