1use 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#[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#[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: Position,
149 buffer: Buffer,
150
151 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#[derive(Debug, Clone)]
166pub struct ClipperStyle {
167 pub style: Style,
168 pub label_style: Option<Style>,
169 pub label_alignment: Option<Alignment>,
170 pub block: Option<Block<'static>>,
171 pub scroll: Option<ScrollStyle>,
172 pub non_exhaustive: NonExhaustive,
173}
174
175impl Default for ClipperStyle {
176 fn default() -> Self {
177 Self {
178 style: Default::default(),
179 label_style: None,
180 label_alignment: None,
181 block: None,
182 scroll: None,
183 non_exhaustive: NonExhaustive,
184 }
185 }
186}
187
188#[derive(Debug)]
190pub struct ClipperState<W = usize>
191where
192 W: Eq + Clone + Hash,
193{
194 pub area: Rect,
197 pub widget_area: Rect,
200
201 pub layout: Rc<RefCell<GenericLayout<W>>>,
204
205 pub hscroll: ScrollState,
208 pub vscroll: ScrollState,
211
212 pub container: FocusFlag,
215
216 buffer: Option<Buffer>,
218
219 pub non_exhaustive: NonExhaustive,
221}
222
223impl<W> Clone for Clipper<'_, W>
224where
225 W: Eq + Clone + Hash,
226{
227 fn clone(&self) -> Self {
228 Self {
229 style: Default::default(),
230 block: self.block.clone(),
231 layout: self.layout.clone(),
232 hscroll: self.hscroll.clone(),
233 vscroll: self.vscroll.clone(),
234 label_style: self.label_style.clone(),
235 label_alignment: self.label_alignment.clone(),
236 manual_label: self.manual_label,
237 buffer_uses_view_size: self.buffer_uses_view_size,
238 }
239 }
240}
241
242impl<W> Default for Clipper<'_, W>
243where
244 W: Eq + Clone + Hash,
245{
246 fn default() -> Self {
247 Self {
248 style: Default::default(),
249 block: Default::default(),
250 layout: Default::default(),
251 hscroll: Default::default(),
252 vscroll: Default::default(),
253 label_style: Default::default(),
254 label_alignment: Default::default(),
255 manual_label: Default::default(),
256 buffer_uses_view_size: Default::default(),
257 }
258 }
259}
260
261impl<'a, W> Clipper<'a, W>
262where
263 W: Eq + Clone + Hash,
264{
265 pub fn new() -> Self {
267 Self::default()
268 }
269
270 pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
273 self.layout = Some(layout);
274 self
275 }
276
277 pub fn style(mut self, style: Style) -> Self {
279 self.style = style;
280 self.block = self.block.map(|v| v.style(style));
281 self
282 }
283
284 pub fn auto_label(mut self, auto: bool) -> Self {
288 self.manual_label = !auto;
289 self
290 }
291
292 pub fn label_style(mut self, style: Style) -> Self {
294 self.label_style = Some(style);
295 self
296 }
297
298 pub fn label_alignment(mut self, alignment: Alignment) -> Self {
300 self.label_alignment = Some(alignment);
301 self
302 }
303
304 pub fn block(mut self, block: Block<'a>) -> Self {
306 self.block = Some(block);
307 self
308 }
309
310 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
312 self.hscroll = Some(scroll.clone().override_horizontal());
313 self.vscroll = Some(scroll.override_vertical());
314 self
315 }
316
317 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
319 self.hscroll = Some(scroll.override_horizontal());
320 self
321 }
322
323 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
325 self.vscroll = Some(scroll.override_vertical());
326 self
327 }
328
329 pub fn styles(mut self, styles: ClipperStyle) -> Self {
331 self.style = styles.style;
332 if styles.label_style.is_some() {
333 self.label_style = styles.label_style;
334 }
335 if styles.label_alignment.is_some() {
336 self.label_alignment = styles.label_alignment;
337 }
338 if styles.block.is_some() {
339 self.block = styles.block;
340 }
341 if let Some(styles) = styles.scroll {
342 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
343 self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
344 }
345 self.block = self.block.map(|v| v.style(styles.style));
346 self
347 }
348
349 pub fn buffer_uses_view_size(mut self) -> Self {
357 self.buffer_uses_view_size = true;
358 self
359 }
360
361 pub fn layout_size(&self, area: Rect, state: &ClipperState<W>) -> Size {
365 let width = self.inner(area, state).width;
366 Size::new(width, u16::MAX)
367 }
368
369 fn inner(&self, area: Rect, state: &ClipperState<W>) -> Rect {
371 let sa = ScrollArea::new()
372 .block(self.block.as_ref())
373 .h_scroll(self.hscroll.as_ref())
374 .v_scroll(self.vscroll.as_ref());
375 sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
376 }
377
378 fn calc_layout(&self, area: Rect, state: &mut ClipperState<W>) -> (Rect, Position) {
379 let layout = state.layout.borrow();
380
381 let view = Rect::new(
382 state.hscroll.offset() as u16,
383 state.vscroll.offset() as u16,
384 area.width,
385 area.height,
386 );
387
388 let mut max_pos = Position::default();
390
391 let mut ext_view: Option<Rect> = None;
394 for idx in 0..layout.widget_len() {
395 let area = layout.widget(idx);
396 let label_area = layout.label(idx);
397
398 if view.intersects(area) || view.intersects(label_area) {
399 if !area.is_empty() {
400 ext_view = ext_view .map(|v| v.union(area))
402 .or(Some(area));
403 }
404 if !label_area.is_empty() {
405 ext_view = ext_view .map(|v| v.union(label_area))
407 .or(Some(label_area));
408 }
409 }
410
411 max_pos.x = max(max_pos.x, area.right());
412 max_pos.y = max(max_pos.y, area.bottom());
413 max_pos.x = max(max_pos.x, label_area.right());
414 max_pos.y = max(max_pos.y, label_area.bottom());
415 }
416 for idx in 0..layout.block_len() {
417 let block_area = layout.block_area(idx);
418 if view.intersects(block_area) {
419 ext_view = ext_view .map(|v| v.union(block_area))
421 .or(Some(block_area));
422 }
423
424 max_pos.x = max(max_pos.x, block_area.right());
425 max_pos.y = max(max_pos.y, block_area.bottom());
426 }
427
428 let ext_view = ext_view.unwrap_or(view);
429
430 (ext_view, max_pos)
431 }
432
433 pub fn into_buffer(mut self, area: Rect, state: &mut ClipperState<W>) -> ClipperBuffer<'a, W> {
435 state.area = area;
436 if let Some(layout) = self.layout.take() {
437 state.layout = Rc::new(RefCell::new(layout));
438 }
439
440 let sa = ScrollArea::new()
441 .block(self.block.as_ref())
442 .h_scroll(self.hscroll.as_ref())
443 .v_scroll(self.vscroll.as_ref());
444 state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
445
446 let (mut ext_area, max_pos) = self.calc_layout(area, state);
448
449 if self.buffer_uses_view_size {
450 ext_area = ext_area.union(area)
451 }
452
453 state
455 .vscroll
456 .set_page_len(state.widget_area.height as usize);
457 state
458 .vscroll
459 .set_max_offset(max_pos.y.saturating_sub(state.widget_area.height) as usize);
460 state.hscroll.set_page_len(state.widget_area.width as usize);
461 state
462 .hscroll
463 .set_max_offset(max_pos.x.saturating_sub(state.widget_area.width) as usize);
464
465 let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
466
467 let buffer_area = ext_area;
469 let mut buffer = if let Some(mut buffer) = state.buffer.take() {
471 buffer.reset();
472 buffer.resize(buffer_area);
473 buffer
474 } else {
475 Buffer::empty(buffer_area)
476 };
477 buffer.set_style(buffer_area, self.style);
478
479 ClipperBuffer {
480 layout: state.layout.clone(),
481 manual_label: self.manual_label,
482 offset,
483 buffer,
484 widget_area: state.widget_area,
485 style: self.style,
486 block: self.block,
487 hscroll: self.hscroll,
488 vscroll: self.vscroll,
489 label_style: self.label_style,
490 label_alignment: self.label_alignment,
491 destruct: false,
492 }
493 }
494}
495
496impl<'a, W> Drop for ClipperBuffer<'a, W>
497where
498 W: Eq + Hash + Clone,
499{
500 fn drop(&mut self) {
501 if !self.destruct {
502 panic!("ClipperBuffer: Must be used. Call finish(..)");
503 }
504 }
505}
506
507impl<'a, W> ClipperBuffer<'a, W>
508where
509 W: Eq + Hash + Clone,
510{
511 pub fn is_visible(&self, widget: W) -> bool {
513 let layout = self.layout.borrow();
514 let Some(idx) = layout.try_index_of(widget) else {
515 return false;
516 };
517 let area = layout.widget(idx);
518 self.buffer.area.intersects(area)
519 }
520
521 #[inline(always)]
523 fn render_auto_label(&mut self, idx: usize) -> bool {
524 let layout = self.layout.borrow();
525 let Some(label_area) = self.locate_area(layout.label(idx)) else {
526 return false;
527 };
528 let Some(label_str) = layout.try_label_str(idx) else {
529 return false;
530 };
531
532 let mut label = Line::from(label_str.as_ref());
533 if let Some(style) = self.label_style {
534 label = label.style(style)
535 };
536 if let Some(align) = self.label_alignment {
537 label = label.alignment(align);
538 }
539 label.render(label_area, &mut self.buffer);
540
541 true
542 }
543
544 fn render_block(&mut self) {
546 let layout = self.layout.borrow();
547 for (idx, block_area) in layout.block_area_iter().enumerate() {
548 if let Some(block_area) = self.locate_area(*block_area) {
549 if let Some(block) = layout.block(idx) {
550 block.render(block_area, &mut self.buffer);
551 }
552 }
553 }
554 }
555
556 #[inline(always)]
561 pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
562 where
563 FN: FnOnce(&Option<Cow<'static, str>>, Rect, &mut Buffer),
564 {
565 let layout = self.layout.borrow();
566 let Some(idx) = layout.try_index_of(widget) else {
567 return false;
568 };
569 let Some(label_area) = self.locate_area(layout.label(idx)) else {
570 return false;
571 };
572 let label_str = layout.try_label_str(idx);
573 render_fn(label_str, label_area, &mut self.buffer);
574 true
575 }
576
577 #[inline(always)]
579 pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
580 where
581 FN: FnOnce() -> WW,
582 WW: Widget,
583 {
584 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
585 return false;
586 };
587 if !self.manual_label {
588 self.render_auto_label(idx);
589 }
590 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
591 return false;
592 };
593 render_fn().render(widget_area, &mut self.buffer);
594 true
595 }
596
597 #[inline(always)]
599 pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
600 where
601 FN: FnOnce() -> WW,
602 WW: StatefulWidget<State = SS>,
603 SS: RelocatableState,
604 {
605 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
606 return false;
607 };
608 if !self.manual_label {
609 self.render_auto_label(idx);
610 }
611 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
612 state.relocate_hidden();
613 return false;
614 };
615 render_fn().render(widget_area, &mut self.buffer, state);
616 state.relocate(self.shift(), self.widget_area);
617 true
618 }
619
620 #[inline(always)]
629 pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
630 where
631 FN: FnOnce() -> (WW, R),
632 WW: StatefulWidget<State = SS>,
633 SS: RelocatableState,
634 {
635 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
636 return None;
637 };
638 if !self.manual_label {
639 self.render_auto_label(idx);
640 }
641 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
642 state.relocate_hidden();
643 return None;
644 };
645 let (widget, remainder) = render_fn();
646 widget.render(widget_area, &mut self.buffer, state);
647 state.relocate(self.shift(), self.widget_area);
648
649 Some(remainder)
650 }
651
652 #[inline(always)]
656 pub fn render_popup<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
657 where
658 FN: FnOnce() -> Option<WW>,
659 WW: StatefulWidget<State = SS>,
660 SS: RelocatableState,
661 {
662 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
663 return false;
664 };
665 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
666 state.relocate_popup_hidden();
667 return false;
668 };
669 let widget = render_fn();
670 if let Some(widget) = widget {
671 widget.render(widget_area, &mut self.buffer, state);
672 state.relocate_popup(self.shift(), self.widget_area);
673 true
674 } else {
675 state.relocate_popup_hidden();
676 false
677 }
678 }
679
680 #[inline]
682 pub fn locate_widget(&self, widget: W) -> Option<Rect> {
683 let layout = self.layout.borrow();
684 let Some(idx) = layout.try_index_of(widget) else {
685 return None;
686 };
687 self.locate_area(layout.widget(idx))
688 }
689
690 #[inline]
692 #[allow(clippy::question_mark)]
693 pub fn locate_label(&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.label(idx))
699 }
700
701 #[inline]
706 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
707 if self.buffer.area.intersects(area) {
708 Some(area)
709 } else {
710 None
711 }
712 }
713
714 fn shift(&self) -> (i16, i16) {
716 (
717 self.widget_area.x as i16 - self.offset.x as i16,
718 self.widget_area.y as i16 - self.offset.y as i16,
719 )
720 }
721
722 pub fn relocate<S>(&self, widget: W, state: &mut S)
739 where
740 S: RelocatableState,
741 {
742 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
743 return;
744 };
745 if self.locate_area(self.layout.borrow().widget(idx)).is_some() {
746 state.relocate(self.shift(), self.widget_area);
747 } else {
748 state.relocate_hidden();
749 };
750 }
751
752 #[inline]
754 pub fn buffer(&mut self) -> &mut Buffer {
755 &mut self.buffer
756 }
757
758 pub fn finish(mut self, tgt_buf: &mut Buffer, state: &mut ClipperState<W>) {
759 self.destruct = true;
760
761 self.render_block();
762
763 ScrollArea::new()
764 .style(self.style)
765 .block(self.block.as_ref())
766 .h_scroll(self.hscroll.as_ref())
767 .v_scroll(self.vscroll.as_ref())
768 .render(
769 state.area,
770 tgt_buf,
771 &mut ScrollAreaState::new()
772 .h_scroll(&mut state.hscroll)
773 .v_scroll(&mut state.vscroll),
774 );
775
776 let src_area = self.buffer.area;
777 let tgt_area = state.widget_area;
778 let offset = self.offset;
779
780 let off_x0 = src_area.x.saturating_sub(offset.x);
782 let off_y0 = src_area.y.saturating_sub(offset.y);
783 let cut_x0 = offset.x.saturating_sub(src_area.x);
785 let cut_y0 = offset.y.saturating_sub(src_area.y);
786
787 let len_src = src_area.width.saturating_sub(cut_x0);
789 let len_tgt = tgt_area.width.saturating_sub(off_x0);
790 let len = min(len_src, len_tgt);
791
792 let height_src = src_area.height.saturating_sub(cut_y0);
794 let height_tgt = tgt_area.height.saturating_sub(off_y0);
795 let height = min(height_src, height_tgt);
796
797 for y in 0..height {
811 let src_0 = self
812 .buffer
813 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
814 let tgt_0 = tgt_buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
815
816 let src = &self.buffer.content[src_0..src_0 + len as usize];
817 let tgt = &mut tgt_buf.content[tgt_0..tgt_0 + len as usize];
818 tgt.clone_from_slice(src);
819 }
820
821 state.buffer = Some(mem::take(&mut self.buffer));
823 }
824}
825
826impl<W> Default for ClipperState<W>
827where
828 W: Eq + Hash + Clone,
829{
830 fn default() -> Self {
831 Self {
832 area: Default::default(),
833 widget_area: Default::default(),
834 layout: Default::default(),
835 hscroll: Default::default(),
836 vscroll: Default::default(),
837 container: Default::default(),
838 buffer: None,
839 non_exhaustive: NonExhaustive,
840 }
841 }
842}
843
844impl<W> Clone for ClipperState<W>
845where
846 W: Eq + Hash + Clone,
847{
848 fn clone(&self) -> Self {
849 Self {
850 area: self.area,
851 widget_area: self.widget_area,
852 layout: self.layout.clone(),
853 hscroll: self.hscroll.clone(),
854 vscroll: self.vscroll.clone(),
855 container: self.container.new_instance(),
856 buffer: None,
857 non_exhaustive: NonExhaustive,
858 }
859 }
860}
861
862impl<W> HasFocus for ClipperState<W>
863where
864 W: Eq + Clone + Hash,
865{
866 fn build(&self, _builder: &mut FocusBuilder) {
867 }
869
870 fn focus(&self) -> FocusFlag {
871 self.container.clone()
872 }
873
874 fn area(&self) -> Rect {
875 self.area
876 }
877}
878
879impl<W> ClipperState<W>
880where
881 W: Eq + Clone + Hash,
882{
883 pub fn new() -> Self {
884 Self::default()
885 }
886
887 pub fn named(name: &str) -> Self {
888 let mut z = Self::default();
889 z.container = z.container.with_name(name);
890 z
891 }
892
893 pub fn clear(&mut self) {
895 self.layout.borrow_mut().clear();
896 self.hscroll.clear();
897 self.vscroll.clear();
898 }
899
900 pub fn valid_layout(&self, size: Size) -> bool {
902 let layout = self.layout.borrow();
903 !layout.size_changed(size) && !layout.is_empty()
904 }
905
906 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
908 self.layout = Rc::new(RefCell::new(layout));
909 }
910
911 pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
913 self.layout.borrow()
914 }
915
916 pub fn show(&mut self, widget: W) -> bool {
918 let layout = self.layout.borrow();
919 let Some(idx) = layout.try_index_of(widget) else {
920 return false;
921 };
922 let widget_area = layout.widget(idx);
923 let label_area = layout.label(idx);
924
925 let area = if !widget_area.is_empty() {
926 if !label_area.is_empty() {
927 Some(widget_area.union(label_area))
928 } else {
929 Some(widget_area)
930 }
931 } else {
932 if !label_area.is_empty() {
933 Some(label_area)
934 } else {
935 None
936 }
937 };
938
939 if let Some(area) = area {
940 let h = self
941 .hscroll
942 .scroll_to_range(area.left() as usize..area.right() as usize);
943 let v = self
944 .vscroll
945 .scroll_to_range(area.top() as usize..area.bottom() as usize);
946 h || v
947 } else {
948 false
949 }
950 }
951
952 pub fn first(&self) -> Option<W> {
956 let layout = self.layout.borrow();
957
958 let area = Rect::new(
959 self.hscroll.offset() as u16,
960 self.vscroll.offset() as u16,
961 self.widget_area.width,
962 self.widget_area.height,
963 );
964
965 for idx in 0..layout.widget_len() {
966 if layout.widget(idx).intersects(area) {
967 return Some(layout.widget_key(idx).clone());
968 }
969 }
970
971 None
972 }
973}
974
975impl<W> ClipperState<W>
976where
977 W: Eq + Clone + Hash,
978{
979 pub fn vertical_offset(&self) -> usize {
980 self.vscroll.offset()
981 }
982
983 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
984 let old = self.vscroll.offset();
985 self.vscroll.set_offset(offset);
986 old != self.vscroll.offset()
987 }
988
989 pub fn vertical_page_len(&self) -> usize {
990 self.vscroll.page_len()
991 }
992
993 pub fn horizontal_offset(&self) -> usize {
994 self.hscroll.offset()
995 }
996
997 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
998 let old = self.hscroll.offset();
999 self.hscroll.set_offset(offset);
1000 old != self.hscroll.offset()
1001 }
1002
1003 pub fn horizontal_page_len(&self) -> usize {
1004 self.hscroll.page_len()
1005 }
1006
1007 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
1008 self.hscroll.scroll_to_pos(pos)
1009 }
1010
1011 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
1012 self.vscroll.scroll_to_pos(pos)
1013 }
1014
1015 pub fn scroll_to(&mut self, widget: W) -> bool {
1017 self.show(widget)
1018 }
1019
1020 pub fn scroll_up(&mut self, delta: usize) -> bool {
1021 self.vscroll.scroll_up(delta)
1022 }
1023
1024 pub fn scroll_down(&mut self, delta: usize) -> bool {
1025 self.vscroll.scroll_down(delta)
1026 }
1027
1028 pub fn scroll_left(&mut self, delta: usize) -> bool {
1029 self.hscroll.scroll_left(delta)
1030 }
1031
1032 pub fn scroll_right(&mut self, delta: usize) -> bool {
1033 self.hscroll.scroll_right(delta)
1034 }
1035}
1036
1037impl ClipperState<usize> {
1038 pub fn focus_first(&self, focus: &Focus) -> bool {
1041 if let Some(w) = self.first() {
1042 focus.by_widget_id(w);
1043 true
1044 } else {
1045 false
1046 }
1047 }
1048
1049 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1055 let Some(focused) = focus.focused() else {
1056 return false;
1057 };
1058 let focused = focused.widget_id();
1059 self.scroll_to(focused)
1060 }
1061}
1062
1063impl ClipperState<FocusFlag> {
1064 pub fn focus_first(&self, focus: &Focus) -> bool {
1066 if let Some(w) = self.first() {
1067 focus.focus(&w);
1068 true
1069 } else {
1070 false
1071 }
1072 }
1073
1074 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1077 let Some(focused) = focus.focused() else {
1078 return false;
1079 };
1080 self.scroll_to(focused)
1081 }
1082}
1083
1084impl<W> HandleEvent<crossterm::event::Event, Regular, Outcome> for ClipperState<W>
1085where
1086 W: Eq + Clone + Hash,
1087{
1088 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
1089 let r = if self.container.is_focused() {
1090 match event {
1091 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
1092 ct_event!(keycode press PageDown) => {
1093 self.scroll_down(self.vscroll.page_len()).into()
1094 }
1095 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
1096 ct_event!(keycode press End) => {
1097 self.vertical_scroll_to(self.vscroll.max_offset()).into()
1098 }
1099 _ => Outcome::Continue,
1100 }
1101 } else {
1102 Outcome::Continue
1103 };
1104
1105 r.or_else(|| self.handle(event, MouseOnly))
1106 }
1107}
1108
1109impl<W> HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ClipperState<W>
1110where
1111 W: Eq + Clone + Hash,
1112{
1113 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
1114 let mut sas = ScrollAreaState::new()
1115 .area(self.widget_area)
1116 .h_scroll(&mut self.hscroll)
1117 .v_scroll(&mut self.vscroll);
1118 match sas.handle(event, MouseOnly) {
1119 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
1120 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
1121 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
1122 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
1123 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
1124 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
1125 r => r.into(),
1126 }
1127 }
1128}
1129
1130pub fn handle_events<W>(
1134 state: &mut ClipperState<W>,
1135 _focus: bool,
1136 event: &crossterm::event::Event,
1137) -> Outcome
1138where
1139 W: Eq + Clone + Hash,
1140{
1141 HandleEvent::handle(state, event, Regular)
1142}
1143
1144pub fn handle_mouse_events<W>(
1146 state: &mut ClipperState<W>,
1147 event: &crossterm::event::Event,
1148) -> Outcome
1149where
1150 W: Eq + Clone + Hash,
1151{
1152 HandleEvent::handle(state, event, MouseOnly)
1153}