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