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