1use crate::_private::NonExhaustive;
100use crate::layout::GenericLayout;
101use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Outcome, Regular, ct_event};
102use rat_focus::{Focus, FocusBuilder, FocusFlag, HasFocus};
103use rat_reloc::RelocatableState;
104use rat_scrolled::event::ScrollOutcome;
105use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
106use ratatui::buffer::Buffer;
107use ratatui::layout::{Alignment, Position, Rect, Size};
108use ratatui::style::Style;
109use ratatui::text::Line;
110use ratatui::widgets::Widget;
111use ratatui::widgets::{Block, StatefulWidget};
112use std::borrow::Cow;
113use std::cell::{Ref, RefCell};
114use std::cmp::{max, min};
115use std::hash::Hash;
116use std::marker::PhantomData;
117use std::mem;
118use std::rc::Rc;
119
120#[derive(Debug)]
123pub struct Clipper<'a, W>
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 auto_label: bool,
135}
136
137#[derive(Debug)]
139pub struct ClipperBuffer<'a, W>
140where
141 W: Eq + Clone + Hash,
142{
143 layout: Rc<RefCell<GenericLayout<W>>>,
144 auto_label: bool,
145
146 offset: Position,
148 buffer: Buffer,
149
150 widget_area: Rect,
152
153 style: Style,
154 block: Option<Block<'a>>,
155 hscroll: Option<Scroll<'a>>,
156 vscroll: Option<Scroll<'a>>,
157 label_style: Option<Style>,
158 label_alignment: Option<Alignment>,
159
160 destruct: bool,
161}
162
163#[derive(Debug)]
165pub struct ClipperWidget<'a, W>
166where
167 W: Eq + Clone + Hash,
168{
169 offset: Position,
170 buffer: Buffer,
171
172 style: Style,
173 block: Option<Block<'a>>,
174 hscroll: Option<Scroll<'a>>,
175 vscroll: Option<Scroll<'a>>,
176 phantom: PhantomData<W>,
177}
178
179#[derive(Debug, Clone)]
181pub struct ClipperStyle {
182 pub style: Style,
183 pub label_style: Option<Style>,
184 pub label_alignment: Option<Alignment>,
185 pub block: Option<Block<'static>>,
186 pub scroll: Option<ScrollStyle>,
187 pub non_exhaustive: NonExhaustive,
188}
189
190impl Default for ClipperStyle {
191 fn default() -> Self {
192 Self {
193 style: Default::default(),
194 label_style: None,
195 label_alignment: None,
196 block: None,
197 scroll: None,
198 non_exhaustive: NonExhaustive,
199 }
200 }
201}
202
203#[derive(Debug)]
205pub struct ClipperState<W>
206where
207 W: Eq + Clone + Hash,
208{
209 pub area: Rect,
212 pub widget_area: Rect,
215
216 pub layout: Rc<RefCell<GenericLayout<W>>>,
219
220 pub hscroll: ScrollState,
223 pub vscroll: ScrollState,
226
227 pub container: FocusFlag,
230
231 buffer: Option<Buffer>,
233
234 pub non_exhaustive: NonExhaustive,
236}
237
238impl<W> Clone for Clipper<'_, W>
239where
240 W: Eq + Clone + Hash,
241{
242 fn clone(&self) -> Self {
243 Self {
244 style: Default::default(),
245 block: self.block.clone(),
246 layout: self.layout.clone(),
247 hscroll: self.hscroll.clone(),
248 vscroll: self.vscroll.clone(),
249 label_style: self.label_style.clone(),
250 label_alignment: self.label_alignment.clone(),
251 auto_label: self.auto_label,
252 }
253 }
254}
255
256impl<W> Default for Clipper<'_, W>
257where
258 W: Eq + Clone + Hash,
259{
260 fn default() -> Self {
261 Self {
262 style: Default::default(),
263 block: Default::default(),
264 layout: Default::default(),
265 hscroll: Default::default(),
266 vscroll: Default::default(),
267 label_style: Default::default(),
268 label_alignment: Default::default(),
269 auto_label: true,
270 }
271 }
272}
273
274impl<'a, W> Clipper<'a, W>
275where
276 W: Eq + Clone + Hash,
277{
278 pub fn new() -> Self {
280 Self::default()
281 }
282
283 pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
286 self.layout = Some(layout);
287 self
288 }
289
290 pub fn style(mut self, style: Style) -> Self {
292 self.style = style;
293 self.block = self.block.map(|v| v.style(style));
294 self
295 }
296
297 pub fn auto_label(mut self, auto: bool) -> Self {
301 self.auto_label = auto;
302 self
303 }
304
305 pub fn label_style(mut self, style: Style) -> Self {
307 self.label_style = Some(style);
308 self
309 }
310
311 pub fn label_alignment(mut self, alignment: Alignment) -> Self {
313 self.label_alignment = Some(alignment);
314 self
315 }
316
317 pub fn block(mut self, block: Block<'a>) -> Self {
319 self.block = Some(block);
320 self
321 }
322
323 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
325 self.hscroll = Some(scroll.clone().override_horizontal());
326 self.vscroll = Some(scroll.override_vertical());
327 self
328 }
329
330 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
332 self.hscroll = Some(scroll.override_horizontal());
333 self
334 }
335
336 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
338 self.vscroll = Some(scroll.override_vertical());
339 self
340 }
341
342 pub fn styles(mut self, styles: ClipperStyle) -> Self {
344 self.style = styles.style;
345 if styles.label_style.is_some() {
346 self.label_style = styles.label_style;
347 }
348 if styles.label_alignment.is_some() {
349 self.label_alignment = styles.label_alignment;
350 }
351 if styles.block.is_some() {
352 self.block = styles.block;
353 }
354 if let Some(styles) = styles.scroll {
355 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
356 self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
357 }
358 self.block = self.block.map(|v| v.style(styles.style));
359 self
360 }
361
362 pub fn layout_size(&self, area: Rect, state: &ClipperState<W>) -> Size {
364 let width = self.inner(area, state).width;
365 Size::new(width, u16::MAX)
366 }
367
368 fn inner(&self, area: Rect, state: &ClipperState<W>) -> Rect {
370 let sa = ScrollArea::new()
371 .block(self.block.as_ref())
372 .h_scroll(self.hscroll.as_ref())
373 .v_scroll(self.vscroll.as_ref());
374 sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
375 }
376
377 fn calc_layout(&self, area: Rect, state: &mut ClipperState<W>) -> (Rect, Position) {
378 let layout = state.layout.borrow();
379
380 let view = Rect::new(
381 state.hscroll.offset() as u16,
382 state.vscroll.offset() as u16,
383 area.width,
384 area.height,
385 );
386
387 let mut max_pos = Position::default();
389
390 let mut ext_view: Option<Rect> = None;
393 for idx in 0..layout.widget_len() {
394 let area = layout.widget(idx);
395 let label_area = layout.label(idx);
396
397 if view.intersects(area) || view.intersects(label_area) {
398 if !area.is_empty() {
399 ext_view = ext_view .map(|v| v.union(area))
401 .or(Some(area));
402 }
403 if !label_area.is_empty() {
404 ext_view = ext_view .map(|v| v.union(label_area))
406 .or(Some(label_area));
407 }
408 }
409
410 max_pos.x = max(max_pos.x, area.right());
411 max_pos.y = max(max_pos.y, area.bottom());
412 max_pos.x = max(max_pos.x, label_area.right());
413 max_pos.y = max(max_pos.y, label_area.bottom());
414 }
415 for idx in 0..layout.block_len() {
416 let block_area = layout.block_area(idx);
417 if view.intersects(block_area) {
418 ext_view = ext_view .map(|v| v.union(block_area))
420 .or(Some(block_area));
421 }
422
423 max_pos.x = max(max_pos.x, block_area.right());
424 max_pos.y = max(max_pos.y, block_area.bottom());
425 }
426
427 let ext_view = ext_view.unwrap_or(view);
428
429 (ext_view, max_pos)
430 }
431
432 pub fn into_buffer(mut self, area: Rect, state: &mut ClipperState<W>) -> ClipperBuffer<'a, W> {
434 state.area = area;
435 if let Some(layout) = self.layout.take() {
436 state.layout = Rc::new(RefCell::new(layout));
437 }
438
439 let sa = ScrollArea::new()
440 .block(self.block.as_ref())
441 .h_scroll(self.hscroll.as_ref())
442 .v_scroll(self.vscroll.as_ref());
443 state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
444
445 let (ext_area, max_pos) = self.calc_layout(area, state);
447
448 state
450 .vscroll
451 .set_page_len(state.widget_area.height as usize);
452 state
453 .vscroll
454 .set_max_offset(max_pos.y.saturating_sub(state.widget_area.height) as usize);
455 state.hscroll.set_page_len(state.widget_area.width as usize);
456 state
457 .hscroll
458 .set_max_offset(max_pos.x.saturating_sub(state.widget_area.width) as usize);
459
460 let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
461
462 let buffer_area = ext_area;
464 let mut buffer = if let Some(mut buffer) = state.buffer.take() {
466 buffer.reset();
467 buffer.resize(buffer_area);
468 buffer
469 } else {
470 Buffer::empty(buffer_area)
471 };
472 buffer.set_style(buffer_area, self.style);
473
474 ClipperBuffer {
475 layout: state.layout.clone(),
476 auto_label: self.auto_label,
477 offset,
478 buffer,
479 widget_area: state.widget_area,
480 style: self.style,
481 block: self.block,
482 hscroll: self.hscroll,
483 vscroll: self.vscroll,
484 label_style: self.label_style,
485 label_alignment: self.label_alignment,
486 destruct: false,
487 }
488 }
489}
490
491impl<'a, W> Drop for ClipperBuffer<'a, W>
492where
493 W: Eq + Hash + Clone,
494{
495 fn drop(&mut self) {
496 if !self.destruct {
497 panic!("ClipperBuffer must be used by into_widget()");
498 }
499 }
500}
501
502impl<'a, W> ClipperBuffer<'a, W>
503where
504 W: Eq + Hash + Clone,
505{
506 pub fn is_visible(&self, widget: W) -> bool {
508 let layout = self.layout.borrow();
509 let Some(idx) = layout.try_index_of(widget) else {
510 return false;
511 };
512 let area = layout.widget(idx);
513 self.buffer.area.intersects(area)
514 }
515
516 #[inline(always)]
518 fn render_auto_label(&mut self, idx: usize) -> bool {
519 let layout = self.layout.borrow();
520 let Some(label_area) = self.locate_area(layout.label(idx)) else {
521 return false;
522 };
523 let Some(label_str) = layout.try_label_str(idx) else {
524 return false;
525 };
526
527 let mut label = Line::from(label_str.as_ref());
528 if let Some(style) = self.label_style {
529 label = label.style(style)
530 };
531 if let Some(align) = self.label_alignment {
532 label = label.alignment(align);
533 }
534 label.render(label_area, &mut self.buffer);
535
536 true
537 }
538
539 fn render_block(&mut self) {
541 let layout = self.layout.borrow();
542 for (idx, block_area) in layout.block_area_iter().enumerate() {
543 if let Some(block_area) = self.locate_area(*block_area) {
544 if let Some(block) = layout.block(idx) {
545 block.render(block_area, &mut self.buffer);
546 }
547 }
548 }
549 }
550
551 #[inline(always)]
556 pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
557 where
558 FN: FnOnce(&Option<Cow<'static, str>>, Rect, &mut Buffer),
559 {
560 let layout = self.layout.borrow();
561 let Some(idx) = layout.try_index_of(widget) else {
562 return false;
563 };
564 let Some(label_area) = self.locate_area(layout.label(idx)) else {
565 return false;
566 };
567 let label_str = layout.try_label_str(idx);
568 render_fn(label_str, label_area, &mut self.buffer);
569 true
570 }
571
572 #[inline(always)]
574 pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
575 where
576 FN: FnOnce() -> WW,
577 WW: Widget,
578 {
579 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
580 return false;
581 };
582 if self.auto_label {
583 self.render_auto_label(idx);
584 }
585 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
586 return false;
587 };
588 render_fn().render(widget_area, &mut self.buffer);
589 true
590 }
591
592 #[inline(always)]
594 pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
595 where
596 FN: FnOnce() -> WW,
597 WW: StatefulWidget<State = SS>,
598 SS: RelocatableState,
599 {
600 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
601 return false;
602 };
603 if self.auto_label {
604 self.render_auto_label(idx);
605 }
606 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
607 state.relocate_hidden();
608 return false;
609 };
610 render_fn().render(widget_area, &mut self.buffer, state);
611 state.relocate(self.shift(), self.widget_area);
612 true
613 }
614
615 #[inline]
617 pub fn locate_widget(&self, widget: W) -> Option<Rect> {
618 let layout = self.layout.borrow();
619 let Some(idx) = layout.try_index_of(widget) else {
620 return None;
621 };
622 self.locate_area(layout.widget(idx))
623 }
624
625 #[inline]
627 #[allow(clippy::question_mark)]
628 pub fn locate_label(&self, widget: W) -> Option<Rect> {
629 let layout = self.layout.borrow();
630 let Some(idx) = layout.try_index_of(widget) else {
631 return None;
632 };
633 self.locate_area(layout.label(idx))
634 }
635
636 #[inline]
641 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
642 if self.buffer.area.intersects(area) {
643 Some(area)
644 } else {
645 None
646 }
647 }
648
649 fn shift(&self) -> (i16, i16) {
651 (
652 self.widget_area.x as i16 - self.offset.x as i16,
653 self.widget_area.y as i16 - self.offset.y as i16,
654 )
655 }
656
657 pub fn relocate<S>(&self, widget: W, state: &mut S)
674 where
675 S: RelocatableState,
676 {
677 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
678 return;
679 };
680 if self.locate_area(self.layout.borrow().widget(idx)).is_some() {
681 state.relocate(self.shift(), self.widget_area);
682 } else {
683 state.relocate_hidden();
684 };
685 }
686
687 #[inline]
689 pub fn buffer(&mut self) -> &mut Buffer {
690 &mut self.buffer
691 }
692
693 pub fn finish(mut self, buffer: &mut Buffer, state: &mut ClipperState<W>) {
694 self.render_block();
695 self.destruct = true;
696
697 ClipperWidget {
698 block: self.block.take(),
699 hscroll: self.hscroll.take(),
700 vscroll: self.vscroll.take(),
701 offset: self.offset,
702 buffer: mem::take(&mut self.buffer),
703 phantom: Default::default(),
704 style: self.style,
705 }
706 .render(state.area, buffer, state);
707 }
708}
709
710impl<W> StatefulWidget for ClipperWidget<'_, W>
711where
712 W: Eq + Clone + Hash,
713{
714 type State = ClipperState<W>;
715
716 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
717 assert_eq!(area, state.area);
718
719 ScrollArea::new()
720 .style(self.style)
721 .block(self.block.as_ref())
722 .h_scroll(self.hscroll.as_ref())
723 .v_scroll(self.vscroll.as_ref())
724 .render(
725 area,
726 buf,
727 &mut ScrollAreaState::new()
728 .h_scroll(&mut state.hscroll)
729 .v_scroll(&mut state.vscroll),
730 );
731
732 let src_area = self.buffer.area;
733 let tgt_area = state.widget_area;
734 let offset = self.offset;
735
736 let off_x0 = src_area.x.saturating_sub(offset.x);
738 let off_y0 = src_area.y.saturating_sub(offset.y);
739 let cut_x0 = offset.x.saturating_sub(src_area.x);
741 let cut_y0 = offset.y.saturating_sub(src_area.y);
742
743 let len_src = src_area.width.saturating_sub(cut_x0);
745 let len_tgt = tgt_area.width.saturating_sub(off_x0);
746 let len = min(len_src, len_tgt);
747
748 let height_src = src_area.height.saturating_sub(cut_y0);
750 let height_tgt = tgt_area.height.saturating_sub(off_y0);
751 let height = min(height_src, height_tgt);
752
753 for y in 0..height {
767 let src_0 = self
768 .buffer
769 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
770 let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
771
772 let src = &self.buffer.content[src_0..src_0 + len as usize];
773 let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
774 tgt.clone_from_slice(src);
775 }
776
777 state.buffer = Some(self.buffer);
779 }
780}
781
782impl<W> Default for ClipperState<W>
783where
784 W: Eq + Hash + Clone,
785{
786 fn default() -> Self {
787 Self {
788 area: Default::default(),
789 widget_area: Default::default(),
790 layout: Default::default(),
791 hscroll: Default::default(),
792 vscroll: Default::default(),
793 container: Default::default(),
794 buffer: None,
795 non_exhaustive: NonExhaustive,
796 }
797 }
798}
799
800impl<W> Clone for ClipperState<W>
801where
802 W: Eq + Hash + Clone,
803{
804 fn clone(&self) -> Self {
805 Self {
806 area: self.area,
807 widget_area: self.widget_area,
808 layout: self.layout.clone(),
809 hscroll: self.hscroll.clone(),
810 vscroll: self.vscroll.clone(),
811 container: FocusFlag::named(self.container.name()),
812 buffer: None,
813 non_exhaustive: NonExhaustive,
814 }
815 }
816}
817
818impl<W> HasFocus for ClipperState<W>
819where
820 W: Eq + Clone + Hash,
821{
822 fn build(&self, _builder: &mut FocusBuilder) {
823 }
825
826 fn focus(&self) -> FocusFlag {
827 self.container.clone()
828 }
829
830 fn area(&self) -> Rect {
831 self.area
832 }
833}
834
835impl<W> ClipperState<W>
836where
837 W: Eq + Clone + Hash,
838{
839 pub fn new() -> Self {
840 Self::default()
841 }
842
843 pub fn clear(&mut self) {
845 self.layout.borrow_mut().clear();
846 self.hscroll.clear();
847 self.vscroll.clear();
848 }
849
850 pub fn valid_layout(&self, size: Size) -> bool {
852 let layout = self.layout.borrow();
853 !layout.size_changed(size) && !layout.is_empty()
854 }
855
856 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
858 self.layout = Rc::new(RefCell::new(layout));
859 }
860
861 pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
863 self.layout.borrow()
864 }
865
866 pub fn show(&mut self, widget: W) -> bool {
868 let layout = self.layout.borrow();
869 let Some(idx) = layout.try_index_of(widget) else {
870 return false;
871 };
872 let widget_area = layout.widget(idx);
873 let label_area = layout.label(idx);
874
875 let area = if !widget_area.is_empty() {
876 if !label_area.is_empty() {
877 Some(widget_area.union(label_area))
878 } else {
879 Some(widget_area)
880 }
881 } else {
882 if !label_area.is_empty() {
883 Some(label_area)
884 } else {
885 None
886 }
887 };
888
889 if let Some(area) = area {
890 let h = self
891 .hscroll
892 .scroll_to_range(area.left() as usize..area.right() as usize);
893 let v = self
894 .vscroll
895 .scroll_to_range(area.top() as usize..area.bottom() as usize);
896 h || v
897 } else {
898 false
899 }
900 }
901
902 pub fn first(&self) -> Option<W> {
906 let layout = self.layout.borrow();
907
908 let area = Rect::new(
909 self.hscroll.offset() as u16,
910 self.vscroll.offset() as u16,
911 self.widget_area.width,
912 self.widget_area.height,
913 );
914
915 for idx in 0..layout.widget_len() {
916 if layout.widget(idx).intersects(area) {
917 return Some(layout.widget_key(idx).clone());
918 }
919 }
920
921 None
922 }
923}
924
925impl<W> ClipperState<W>
926where
927 W: Eq + Clone + Hash,
928{
929 pub fn vertical_offset(&self) -> usize {
930 self.vscroll.offset()
931 }
932
933 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
934 let old = self.vscroll.offset();
935 self.vscroll.set_offset(offset);
936 old != self.vscroll.offset()
937 }
938
939 pub fn vertical_page_len(&self) -> usize {
940 self.vscroll.page_len()
941 }
942
943 pub fn horizontal_offset(&self) -> usize {
944 self.hscroll.offset()
945 }
946
947 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
948 let old = self.hscroll.offset();
949 self.hscroll.set_offset(offset);
950 old != self.hscroll.offset()
951 }
952
953 pub fn horizontal_page_len(&self) -> usize {
954 self.hscroll.page_len()
955 }
956
957 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
958 self.hscroll.scroll_to_pos(pos)
959 }
960
961 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
962 self.vscroll.scroll_to_pos(pos)
963 }
964
965 pub fn scroll_to(&mut self, widget: W) -> bool {
967 self.show(widget)
968 }
969
970 pub fn scroll_up(&mut self, delta: usize) -> bool {
971 self.vscroll.scroll_up(delta)
972 }
973
974 pub fn scroll_down(&mut self, delta: usize) -> bool {
975 self.vscroll.scroll_down(delta)
976 }
977
978 pub fn scroll_left(&mut self, delta: usize) -> bool {
979 self.hscroll.scroll_left(delta)
980 }
981
982 pub fn scroll_right(&mut self, delta: usize) -> bool {
983 self.hscroll.scroll_right(delta)
984 }
985}
986
987impl ClipperState<usize> {
988 pub fn focus_first(&self, focus: &Focus) -> bool {
991 if let Some(w) = self.first() {
992 focus.by_widget_id(w);
993 true
994 } else {
995 false
996 }
997 }
998
999 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1003 let Some(focused) = focus.focused() else {
1004 return false;
1005 };
1006 let focused = focused.widget_id();
1007 self.scroll_to(focused)
1008 }
1009}
1010
1011impl ClipperState<FocusFlag> {
1012 pub fn focus_first(&self, focus: &Focus) -> bool {
1014 if let Some(w) = self.first() {
1015 focus.focus(&w);
1016 true
1017 } else {
1018 false
1019 }
1020 }
1021
1022 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1025 let Some(focused) = focus.focused() else {
1026 return false;
1027 };
1028 self.scroll_to(focused)
1029 }
1030}
1031
1032impl<W> HandleEvent<crossterm::event::Event, Regular, Outcome> for ClipperState<W>
1033where
1034 W: Eq + Clone + Hash,
1035{
1036 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
1037 let r = if self.container.is_focused() {
1038 match event {
1039 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
1040 ct_event!(keycode press PageDown) => {
1041 self.scroll_down(self.vscroll.page_len()).into()
1042 }
1043 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
1044 ct_event!(keycode press End) => {
1045 self.vertical_scroll_to(self.vscroll.max_offset()).into()
1046 }
1047 _ => Outcome::Continue,
1048 }
1049 } else {
1050 Outcome::Continue
1051 };
1052
1053 r.or_else(|| self.handle(event, MouseOnly))
1054 }
1055}
1056
1057impl<W> HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ClipperState<W>
1058where
1059 W: Eq + Clone + Hash,
1060{
1061 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
1062 let mut sas = ScrollAreaState::new()
1063 .area(self.widget_area)
1064 .h_scroll(&mut self.hscroll)
1065 .v_scroll(&mut self.vscroll);
1066 match sas.handle(event, MouseOnly) {
1067 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
1068 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
1069 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
1070 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
1071 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
1072 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
1073 r => r.into(),
1074 }
1075 }
1076}