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 #[deprecated(since = "1.2.0", note = "happens automatically")]
539 pub fn render_block(&mut self) {
540 let layout = self.layout.borrow();
541 for (idx, block_area) in layout.block_area_iter().enumerate() {
542 if let Some(block_area) = self.locate_area(*block_area) {
543 if let Some(block) = layout.block(idx) {
544 block.render(block_area, &mut self.buffer);
545 }
546 }
547 }
548 }
549
550 #[inline(always)]
552 pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
553 where
554 FN: FnOnce(&Option<Cow<'static, str>>, Rect, &mut Buffer),
555 {
556 let layout = self.layout.borrow();
557 let Some(idx) = layout.try_index_of(widget) else {
558 return false;
559 };
560 let Some(label_area) = self.locate_area(layout.label(idx)) else {
561 return false;
562 };
563 let label_str = layout.try_label_str(idx);
564 render_fn(label_str, label_area, &mut self.buffer);
565 true
566 }
567
568 #[inline(always)]
570 pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
571 where
572 FN: FnOnce() -> WW,
573 WW: Widget,
574 {
575 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
576 return false;
577 };
578 if self.auto_label {
579 self.render_auto_label(idx);
580 }
581 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
582 return false;
583 };
584 render_fn().render(widget_area, &mut self.buffer);
585 true
586 }
587
588 #[inline(always)]
590 #[allow(deprecated)]
591 pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
592 where
593 FN: FnOnce() -> WW,
594 WW: StatefulWidget<State = SS>,
595 SS: RelocatableState,
596 {
597 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
598 return false;
599 };
600 if self.auto_label {
601 self.render_auto_label(idx);
602 }
603 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
604 self.hidden(state);
605 return false;
606 };
607 render_fn().render(widget_area, &mut self.buffer, state);
608 self.relocate(state);
609 true
610 }
611
612 #[inline]
614 pub fn locate_widget(&self, widget: W) -> Option<Rect> {
615 let layout = self.layout.borrow();
616 let Some(idx) = layout.try_index_of(widget) else {
617 return None;
618 };
619 self.locate_area(layout.widget(idx))
620 }
621
622 #[inline]
624 #[allow(clippy::question_mark)]
625 pub fn locate_label(&self, widget: W) -> Option<Rect> {
626 let layout = self.layout.borrow();
627 let Some(idx) = layout.try_index_of(widget) else {
628 return None;
629 };
630 self.locate_area(layout.label(idx))
631 }
632
633 #[inline]
638 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
639 let area = self.buffer.area.intersection(area);
640 if area.is_empty() { None } else { Some(area) }
641 }
642
643 #[deprecated(since = "1.2.0", note = "will be made private")]
645 pub fn shift(&self) -> (i16, i16) {
646 (
647 self.widget_area.x as i16 - self.offset.x as i16,
648 self.widget_area.y as i16 - self.offset.y as i16,
649 )
650 }
651
652 #[deprecated(since = "1.2.0", note = "will be made private")]
658 #[allow(deprecated)]
659 pub fn relocate<S>(&self, state: &mut S)
660 where
661 S: RelocatableState,
662 {
663 state.relocate(self.shift(), self.widget_area);
664 }
665
666 #[allow(deprecated)]
672 #[deprecated(since = "1.2.0", note = "will be made private")]
673 pub fn hidden<S>(&self, state: &mut S)
674 where
675 S: RelocatableState,
676 {
677 state.relocate((0, 0), Rect::default())
678 }
679
680 #[inline]
682 pub fn buffer(&mut self) -> &mut Buffer {
683 &mut self.buffer
684 }
685
686 #[allow(deprecated)]
687 pub fn finish(mut self, buffer: &mut Buffer, state: &mut ClipperState<W>) {
688 self.render_block();
689 self.destruct = true;
690
691 ClipperWidget {
692 block: self.block.take(),
693 hscroll: self.hscroll.take(),
694 vscroll: self.vscroll.take(),
695 offset: self.offset,
696 buffer: mem::take(&mut self.buffer),
697 phantom: Default::default(),
698 style: self.style,
699 }
700 .render(state.area, buffer, state);
701 }
702
703 #[allow(deprecated)]
707 #[deprecated(since = "1.2.0", note = "use finish() instead")]
708 pub fn into_widget(mut self) -> ClipperWidget<'a, W> {
709 self.render_block();
710 self.destruct = true;
711
712 ClipperWidget {
713 block: self.block.take(),
714 hscroll: self.hscroll.take(),
715 vscroll: self.vscroll.take(),
716 offset: self.offset,
717 buffer: mem::take(&mut self.buffer),
718 phantom: Default::default(),
719 style: self.style,
720 }
721 }
722}
723
724impl<W> StatefulWidget for ClipperWidget<'_, W>
725where
726 W: Eq + Clone + Hash,
727{
728 type State = ClipperState<W>;
729
730 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
731 assert_eq!(area, state.area);
732
733 ScrollArea::new()
734 .style(self.style)
735 .block(self.block.as_ref())
736 .h_scroll(self.hscroll.as_ref())
737 .v_scroll(self.vscroll.as_ref())
738 .render(
739 area,
740 buf,
741 &mut ScrollAreaState::new()
742 .h_scroll(&mut state.hscroll)
743 .v_scroll(&mut state.vscroll),
744 );
745
746 let src_area = self.buffer.area;
747 let tgt_area = state.widget_area;
748 let offset = self.offset;
749
750 let off_x0 = src_area.x.saturating_sub(offset.x);
752 let off_y0 = src_area.y.saturating_sub(offset.y);
753 let cut_x0 = offset.x.saturating_sub(src_area.x);
755 let cut_y0 = offset.y.saturating_sub(src_area.y);
756
757 let len_src = src_area.width.saturating_sub(cut_x0);
759 let len_tgt = tgt_area.width.saturating_sub(off_x0);
760 let len = min(len_src, len_tgt);
761
762 let height_src = src_area.height.saturating_sub(cut_y0);
764 let height_tgt = tgt_area.height.saturating_sub(off_y0);
765 let height = min(height_src, height_tgt);
766
767 for y in 0..height {
781 let src_0 = self
782 .buffer
783 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
784 let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
785
786 let src = &self.buffer.content[src_0..src_0 + len as usize];
787 let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
788 tgt.clone_from_slice(src);
789 }
790
791 state.buffer = Some(self.buffer);
793 }
794}
795
796impl<W> Default for ClipperState<W>
797where
798 W: Eq + Hash + Clone,
799{
800 fn default() -> Self {
801 Self {
802 area: Default::default(),
803 widget_area: Default::default(),
804 layout: Default::default(),
805 hscroll: Default::default(),
806 vscroll: Default::default(),
807 container: Default::default(),
808 buffer: None,
809 non_exhaustive: NonExhaustive,
810 }
811 }
812}
813
814impl<W> Clone for ClipperState<W>
815where
816 W: Eq + Hash + Clone,
817{
818 fn clone(&self) -> Self {
819 Self {
820 area: self.area,
821 widget_area: self.widget_area,
822 layout: self.layout.clone(),
823 hscroll: self.hscroll.clone(),
824 vscroll: self.vscroll.clone(),
825 container: FocusFlag::named(self.container.name()),
826 buffer: None,
827 non_exhaustive: NonExhaustive,
828 }
829 }
830}
831
832impl<W> HasFocus for ClipperState<W>
833where
834 W: Eq + Clone + Hash,
835{
836 fn build(&self, _builder: &mut FocusBuilder) {
837 }
839
840 fn focus(&self) -> FocusFlag {
841 self.container.clone()
842 }
843
844 fn area(&self) -> Rect {
845 self.area
846 }
847}
848
849impl<W> ClipperState<W>
850where
851 W: Eq + Clone + Hash,
852{
853 pub fn new() -> Self {
854 Self::default()
855 }
856
857 pub fn clear(&mut self) {
859 self.layout.borrow_mut().clear();
860 self.hscroll.clear();
861 self.vscroll.clear();
862 }
863
864 pub fn valid_layout(&self, size: Size) -> bool {
866 let layout = self.layout.borrow();
867 !layout.size_changed(size) && !layout.is_empty()
868 }
869
870 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
872 self.layout = Rc::new(RefCell::new(layout));
873 }
874
875 pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
877 self.layout.borrow()
878 }
879
880 pub fn show(&mut self, widget: W) -> bool {
882 let layout = self.layout.borrow();
883 let Some(idx) = layout.try_index_of(widget) else {
884 return false;
885 };
886 let widget_area = layout.widget(idx);
887 let label_area = layout.label(idx);
888
889 let area = if !widget_area.is_empty() {
890 if !label_area.is_empty() {
891 Some(widget_area.union(label_area))
892 } else {
893 Some(widget_area)
894 }
895 } else {
896 if !label_area.is_empty() {
897 Some(label_area)
898 } else {
899 None
900 }
901 };
902
903 if let Some(area) = area {
904 let h = self
905 .hscroll
906 .scroll_to_range(area.left() as usize..area.right() as usize);
907 let v = self
908 .vscroll
909 .scroll_to_range(area.top() as usize..area.bottom() as usize);
910 h || v
911 } else {
912 false
913 }
914 }
915
916 pub fn first(&self) -> Option<W> {
920 let layout = self.layout.borrow();
921
922 let area = Rect::new(
923 self.hscroll.offset() as u16,
924 self.vscroll.offset() as u16,
925 self.widget_area.width,
926 self.widget_area.height,
927 );
928
929 for idx in 0..layout.widget_len() {
930 if layout.widget(idx).intersects(area) {
931 return Some(layout.widget_key(idx).clone());
932 }
933 }
934
935 None
936 }
937}
938
939impl<W> ClipperState<W>
940where
941 W: Eq + Clone + Hash,
942{
943 pub fn vertical_offset(&self) -> usize {
944 self.vscroll.offset()
945 }
946
947 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
948 let old = self.vscroll.offset();
949 self.vscroll.set_offset(offset);
950 old != self.vscroll.offset()
951 }
952
953 pub fn vertical_page_len(&self) -> usize {
954 self.vscroll.page_len()
955 }
956
957 pub fn horizontal_offset(&self) -> usize {
958 self.hscroll.offset()
959 }
960
961 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
962 let old = self.hscroll.offset();
963 self.hscroll.set_offset(offset);
964 old != self.hscroll.offset()
965 }
966
967 pub fn horizontal_page_len(&self) -> usize {
968 self.hscroll.page_len()
969 }
970
971 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
972 self.hscroll.scroll_to_pos(pos)
973 }
974
975 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
976 self.vscroll.scroll_to_pos(pos)
977 }
978
979 pub fn scroll_to(&mut self, widget: W) -> bool {
981 self.show(widget)
982 }
983
984 pub fn scroll_up(&mut self, delta: usize) -> bool {
985 self.vscroll.scroll_up(delta)
986 }
987
988 pub fn scroll_down(&mut self, delta: usize) -> bool {
989 self.vscroll.scroll_down(delta)
990 }
991
992 pub fn scroll_left(&mut self, delta: usize) -> bool {
993 self.hscroll.scroll_left(delta)
994 }
995
996 pub fn scroll_right(&mut self, delta: usize) -> bool {
997 self.hscroll.scroll_right(delta)
998 }
999}
1000
1001impl ClipperState<usize> {
1002 pub fn focus_first(&self, focus: &Focus) -> bool {
1005 if let Some(w) = self.first() {
1006 focus.by_widget_id(w);
1007 true
1008 } else {
1009 false
1010 }
1011 }
1012
1013 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1017 let Some(focused) = focus.focused() else {
1018 return false;
1019 };
1020 let focused = focused.widget_id();
1021 self.scroll_to(focused)
1022 }
1023}
1024
1025impl ClipperState<FocusFlag> {
1026 pub fn focus_first(&self, focus: &Focus) -> bool {
1028 if let Some(w) = self.first() {
1029 focus.focus(&w);
1030 true
1031 } else {
1032 false
1033 }
1034 }
1035
1036 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1039 let Some(focused) = focus.focused() else {
1040 return false;
1041 };
1042 self.scroll_to(focused)
1043 }
1044}
1045
1046impl<W> HandleEvent<crossterm::event::Event, Regular, Outcome> for ClipperState<W>
1047where
1048 W: Eq + Clone + Hash,
1049{
1050 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
1051 let r = if self.container.is_focused() {
1052 match event {
1053 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
1054 ct_event!(keycode press PageDown) => {
1055 self.scroll_down(self.vscroll.page_len()).into()
1056 }
1057 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
1058 ct_event!(keycode press End) => {
1059 self.vertical_scroll_to(self.vscroll.max_offset()).into()
1060 }
1061 _ => Outcome::Continue,
1062 }
1063 } else {
1064 Outcome::Continue
1065 };
1066
1067 r.or_else(|| self.handle(event, MouseOnly))
1068 }
1069}
1070
1071impl<W> HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ClipperState<W>
1072where
1073 W: Eq + Clone + Hash,
1074{
1075 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
1076 let mut sas = ScrollAreaState::new()
1077 .area(self.widget_area)
1078 .h_scroll(&mut self.hscroll)
1079 .v_scroll(&mut self.vscroll);
1080 match sas.handle(event, MouseOnly) {
1081 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
1082 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
1083 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
1084 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
1085 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
1086 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
1087 r => r.into(),
1088 }
1089 }
1090}