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 style = self.label_style.unwrap_or_default();
528 let align = self.label_alignment.unwrap_or_default();
529 Line::from(label_str.as_ref())
530 .style(style)
531 .alignment(align)
532 .render(label_area, &mut self.buffer);
533
534 true
535 }
536
537 fn render_block(&mut self) {
539 let layout = self.layout.borrow();
540 for (idx, block_area) in layout.block_area_iter().enumerate() {
541 if let Some(block_area) = self.locate_area(*block_area) {
542 if let Some(block) = layout.block(idx) {
543 block.render(block_area, &mut self.buffer);
544 }
545 }
546 }
547 }
548
549 #[inline(always)]
551 pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
552 where
553 FN: FnOnce(&Option<Cow<'static, str>>, Rect, &mut Buffer),
554 {
555 let layout = self.layout.borrow();
556 let Some(idx) = layout.try_index_of(widget) else {
557 return false;
558 };
559 let Some(label_area) = self.locate_area(layout.label(idx)) else {
560 return false;
561 };
562 let label_str = layout.try_label_str(idx);
563 render_fn(label_str, label_area, &mut self.buffer);
564 true
565 }
566
567 #[inline(always)]
569 pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
570 where
571 FN: FnOnce() -> WW,
572 WW: Widget,
573 {
574 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
575 return false;
576 };
577 if self.auto_label {
578 self.render_auto_label(idx);
579 }
580 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
581 return false;
582 };
583 render_fn().render(widget_area, &mut self.buffer);
584 true
585 }
586
587 #[inline(always)]
589 pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
590 where
591 FN: FnOnce() -> WW,
592 WW: StatefulWidget<State = SS>,
593 SS: RelocatableState,
594 {
595 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
596 return false;
597 };
598 if self.auto_label {
599 self.render_auto_label(idx);
600 }
601 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
602 self.hidden(state);
603 return false;
604 };
605 render_fn().render(widget_area, &mut self.buffer, state);
606 self.relocate(state);
607 true
608 }
609
610 #[inline]
612 pub fn locate_widget(&self, widget: W) -> Option<Rect> {
613 let layout = self.layout.borrow();
614 let Some(idx) = layout.try_index_of(widget) else {
615 return None;
616 };
617 self.locate_area(layout.widget(idx))
618 }
619
620 #[inline]
622 #[allow(clippy::question_mark)]
623 pub fn locate_label(&self, widget: W) -> Option<Rect> {
624 let layout = self.layout.borrow();
625 let Some(idx) = layout.try_index_of(widget) else {
626 return None;
627 };
628 self.locate_area(layout.label(idx))
629 }
630
631 #[inline]
636 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
637 let area = self.buffer.area.intersection(area);
638 if area.is_empty() { None } else { Some(area) }
639 }
640
641 fn shift(&self) -> (i16, i16) {
643 (
644 self.widget_area.x as i16 - self.offset.x as i16,
645 self.widget_area.y as i16 - self.offset.y as i16,
646 )
647 }
648
649 fn relocate<S>(&self, state: &mut S)
655 where
656 S: RelocatableState,
657 {
658 state.relocate(self.shift(), self.widget_area);
659 }
660
661 fn hidden<S>(&self, state: &mut S)
667 where
668 S: RelocatableState,
669 {
670 state.relocate((0, 0), Rect::default())
671 }
672
673 #[inline]
675 pub fn buffer(&mut self) -> &mut Buffer {
676 &mut self.buffer
677 }
678
679 pub fn finish(mut self, buffer: &mut Buffer, state: &mut ClipperState<W>) {
680 self.render_block();
681 self.destruct = true;
682
683 ClipperWidget {
684 block: self.block.take(),
685 hscroll: self.hscroll.take(),
686 vscroll: self.vscroll.take(),
687 offset: self.offset,
688 buffer: mem::take(&mut self.buffer),
689 phantom: Default::default(),
690 style: self.style,
691 }
692 .render(state.area, buffer, state);
693 }
694}
695
696impl<W> StatefulWidget for ClipperWidget<'_, W>
697where
698 W: Eq + Clone + Hash,
699{
700 type State = ClipperState<W>;
701
702 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
703 assert_eq!(area, state.area);
704
705 ScrollArea::new()
706 .style(self.style)
707 .block(self.block.as_ref())
708 .h_scroll(self.hscroll.as_ref())
709 .v_scroll(self.vscroll.as_ref())
710 .render(
711 area,
712 buf,
713 &mut ScrollAreaState::new()
714 .h_scroll(&mut state.hscroll)
715 .v_scroll(&mut state.vscroll),
716 );
717
718 let src_area = self.buffer.area;
719 let tgt_area = state.widget_area;
720 let offset = self.offset;
721
722 let off_x0 = src_area.x.saturating_sub(offset.x);
724 let off_y0 = src_area.y.saturating_sub(offset.y);
725 let cut_x0 = offset.x.saturating_sub(src_area.x);
727 let cut_y0 = offset.y.saturating_sub(src_area.y);
728
729 let len_src = src_area.width.saturating_sub(cut_x0);
731 let len_tgt = tgt_area.width.saturating_sub(off_x0);
732 let len = min(len_src, len_tgt);
733
734 let height_src = src_area.height.saturating_sub(cut_y0);
736 let height_tgt = tgt_area.height.saturating_sub(off_y0);
737 let height = min(height_src, height_tgt);
738
739 for y in 0..height {
753 let src_0 = self
754 .buffer
755 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
756 let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
757
758 let src = &self.buffer.content[src_0..src_0 + len as usize];
759 let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
760 tgt.clone_from_slice(src);
761 }
762
763 state.buffer = Some(self.buffer);
765 }
766}
767
768impl<W> Default for ClipperState<W>
769where
770 W: Eq + Hash + Clone,
771{
772 fn default() -> Self {
773 Self {
774 area: Default::default(),
775 widget_area: Default::default(),
776 layout: Default::default(),
777 hscroll: Default::default(),
778 vscroll: Default::default(),
779 container: Default::default(),
780 buffer: None,
781 non_exhaustive: NonExhaustive,
782 }
783 }
784}
785
786impl<W> Clone for ClipperState<W>
787where
788 W: Eq + Hash + Clone,
789{
790 fn clone(&self) -> Self {
791 Self {
792 area: self.area,
793 widget_area: self.widget_area,
794 layout: self.layout.clone(),
795 hscroll: self.hscroll.clone(),
796 vscroll: self.vscroll.clone(),
797 container: FocusFlag::named(self.container.name()),
798 buffer: None,
799 non_exhaustive: NonExhaustive,
800 }
801 }
802}
803
804impl<W> HasFocus for ClipperState<W>
805where
806 W: Eq + Clone + Hash,
807{
808 fn build(&self, _builder: &mut FocusBuilder) {
809 }
811
812 fn focus(&self) -> FocusFlag {
813 self.container.clone()
814 }
815
816 fn area(&self) -> Rect {
817 self.area
818 }
819}
820
821impl<W> ClipperState<W>
822where
823 W: Eq + Clone + Hash,
824{
825 pub fn new() -> Self {
826 Self::default()
827 }
828
829 pub fn clear(&mut self) {
831 self.layout.borrow_mut().clear();
832 self.hscroll.clear();
833 self.vscroll.clear();
834 }
835
836 pub fn valid_layout(&self, size: Size) -> bool {
838 let layout = self.layout.borrow();
839 !layout.size_changed(size) && !layout.is_empty()
840 }
841
842 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
844 self.layout = Rc::new(RefCell::new(layout));
845 }
846
847 pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
849 self.layout.borrow()
850 }
851
852 pub fn show(&mut self, widget: W) -> bool {
854 let layout = self.layout.borrow();
855 let Some(idx) = layout.try_index_of(widget) else {
856 return false;
857 };
858 let widget_area = layout.widget(idx);
859 let label_area = layout.label(idx);
860
861 let area = if !widget_area.is_empty() {
862 if !label_area.is_empty() {
863 Some(widget_area.union(label_area))
864 } else {
865 Some(widget_area)
866 }
867 } else {
868 if !label_area.is_empty() {
869 Some(label_area)
870 } else {
871 None
872 }
873 };
874
875 if let Some(area) = area {
876 let h = self
877 .hscroll
878 .scroll_to_range(area.left() as usize..area.right() as usize);
879 let v = self
880 .vscroll
881 .scroll_to_range(area.top() as usize..area.bottom() as usize);
882 h || v
883 } else {
884 false
885 }
886 }
887
888 pub fn first(&self) -> Option<W> {
892 let layout = self.layout.borrow();
893
894 let area = Rect::new(
895 self.hscroll.offset() as u16,
896 self.vscroll.offset() as u16,
897 self.widget_area.width,
898 self.widget_area.height,
899 );
900
901 for idx in 0..layout.widget_len() {
902 if layout.widget(idx).intersects(area) {
903 return Some(layout.widget_key(idx).clone());
904 }
905 }
906
907 None
908 }
909}
910
911impl<W> ClipperState<W>
912where
913 W: Eq + Clone + Hash,
914{
915 pub fn vertical_offset(&self) -> usize {
916 self.vscroll.offset()
917 }
918
919 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
920 let old = self.vscroll.offset();
921 self.vscroll.set_offset(offset);
922 old != self.vscroll.offset()
923 }
924
925 pub fn vertical_page_len(&self) -> usize {
926 self.vscroll.page_len()
927 }
928
929 pub fn horizontal_offset(&self) -> usize {
930 self.hscroll.offset()
931 }
932
933 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
934 let old = self.hscroll.offset();
935 self.hscroll.set_offset(offset);
936 old != self.hscroll.offset()
937 }
938
939 pub fn horizontal_page_len(&self) -> usize {
940 self.hscroll.page_len()
941 }
942
943 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
944 self.hscroll.scroll_to_pos(pos)
945 }
946
947 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
948 self.vscroll.scroll_to_pos(pos)
949 }
950
951 pub fn scroll_to(&mut self, widget: W) -> bool {
953 self.show(widget)
954 }
955
956 pub fn scroll_up(&mut self, delta: usize) -> bool {
957 self.vscroll.scroll_up(delta)
958 }
959
960 pub fn scroll_down(&mut self, delta: usize) -> bool {
961 self.vscroll.scroll_down(delta)
962 }
963
964 pub fn scroll_left(&mut self, delta: usize) -> bool {
965 self.hscroll.scroll_left(delta)
966 }
967
968 pub fn scroll_right(&mut self, delta: usize) -> bool {
969 self.hscroll.scroll_right(delta)
970 }
971}
972
973impl ClipperState<usize> {
974 pub fn focus_first(&self, focus: &Focus) -> bool {
977 if let Some(w) = self.first() {
978 focus.by_widget_id(w);
979 true
980 } else {
981 false
982 }
983 }
984
985 pub fn show_focused(&mut self, focus: &Focus) -> bool {
989 let Some(focused) = focus.focused() else {
990 return false;
991 };
992 let focused = focused.widget_id();
993 self.scroll_to(focused)
994 }
995}
996
997impl ClipperState<FocusFlag> {
998 pub fn focus_first(&self, focus: &Focus) -> bool {
1000 if let Some(w) = self.first() {
1001 focus.focus(&w);
1002 true
1003 } else {
1004 false
1005 }
1006 }
1007
1008 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1011 let Some(focused) = focus.focused() else {
1012 return false;
1013 };
1014 self.scroll_to(focused)
1015 }
1016}
1017
1018impl<W> HandleEvent<crossterm::event::Event, Regular, Outcome> for ClipperState<W>
1019where
1020 W: Eq + Clone + Hash,
1021{
1022 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
1023 let r = if self.container.is_focused() {
1024 match event {
1025 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
1026 ct_event!(keycode press PageDown) => {
1027 self.scroll_down(self.vscroll.page_len()).into()
1028 }
1029 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
1030 ct_event!(keycode press End) => {
1031 self.vertical_scroll_to(self.vscroll.max_offset()).into()
1032 }
1033 _ => Outcome::Continue,
1034 }
1035 } else {
1036 Outcome::Continue
1037 };
1038
1039 r.or_else(|| self.handle(event, MouseOnly))
1040 }
1041}
1042
1043impl<W> HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ClipperState<W>
1044where
1045 W: Eq + Clone + Hash,
1046{
1047 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
1048 let mut sas = ScrollAreaState::new()
1049 .area(self.widget_area)
1050 .h_scroll(&mut self.hscroll)
1051 .v_scroll(&mut self.vscroll);
1052 match sas.handle(event, MouseOnly) {
1053 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
1054 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
1055 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
1056 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
1057 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
1058 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
1059 r => r.into(),
1060 }
1061 }
1062}