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
826 if tgt_buf
827 .area
828 .contains(Position::new(tgt_area.x + off_x0, tgt_area.y + off_y0 + y))
829 {
830 let tgt_0 = tgt_buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
831
832 let src = &self.buffer.content[src_0..src_0 + len as usize];
833 let tgt = &mut tgt_buf.content[tgt_0..tgt_0 + len as usize];
834 tgt.clone_from_slice(src);
835 }
836 }
837
838 state.buffer = Some(mem::take(&mut self.buffer));
840 }
841}
842
843impl<W> Default for ClipperState<W>
844where
845 W: Eq + Hash + Clone,
846{
847 fn default() -> Self {
848 Self {
849 area: Default::default(),
850 widget_area: Default::default(),
851 layout: Default::default(),
852 hscroll: Default::default(),
853 vscroll: Default::default(),
854 container: Default::default(),
855 buffer: None,
856 non_exhaustive: NonExhaustive,
857 }
858 }
859}
860
861impl<W> Clone for ClipperState<W>
862where
863 W: Eq + Hash + Clone,
864{
865 fn clone(&self) -> Self {
866 Self {
867 area: self.area,
868 widget_area: self.widget_area,
869 layout: self.layout.clone(),
870 hscroll: self.hscroll.clone(),
871 vscroll: self.vscroll.clone(),
872 container: self.container.new_instance(),
873 buffer: None,
874 non_exhaustive: NonExhaustive,
875 }
876 }
877}
878
879impl<W> HasFocus for ClipperState<W>
880where
881 W: Eq + Clone + Hash,
882{
883 fn build(&self, _builder: &mut FocusBuilder) {
884 }
886
887 fn focus(&self) -> FocusFlag {
888 self.container.clone()
889 }
890
891 fn area(&self) -> Rect {
892 self.area
893 }
894}
895
896impl<W> RelocatableState for ClipperState<W>
897where
898 W: Eq + Clone + Hash,
899{
900 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
901 self.area.relocate(shift, clip);
902 self.widget_area.relocate(shift, clip);
903 self.hscroll.relocate(shift, clip);
904 self.vscroll.relocate(shift, clip);
905 }
906}
907
908impl<W> ClipperState<W>
909where
910 W: Eq + Clone + Hash,
911{
912 pub fn new() -> Self {
913 Self::default()
914 }
915
916 pub fn named(name: &str) -> Self {
917 let mut z = Self::default();
918 z.container = z.container.with_name(name);
919 z
920 }
921
922 pub fn clear(&mut self) {
924 self.layout.borrow_mut().clear();
925 self.hscroll.clear();
926 self.vscroll.clear();
927 }
928
929 pub fn valid_layout(&self, size: Size) -> bool {
931 let layout = self.layout.borrow();
932 !layout.size_changed(size) && !layout.is_empty()
933 }
934
935 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
937 self.layout = Rc::new(RefCell::new(layout));
938 }
939
940 pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
942 self.layout.borrow()
943 }
944
945 pub fn show(&mut self, widget: W) -> bool {
947 let layout = self.layout.borrow();
948 let Some(idx) = layout.try_index_of(widget) else {
949 return false;
950 };
951 let widget_area = layout.widget(idx);
952 let label_area = layout.label(idx);
953
954 let area = if !widget_area.is_empty() {
955 if !label_area.is_empty() {
956 Some(widget_area.union(label_area))
957 } else {
958 Some(widget_area)
959 }
960 } else {
961 if !label_area.is_empty() {
962 Some(label_area)
963 } else {
964 None
965 }
966 };
967
968 if let Some(area) = area {
969 let h = self
970 .hscroll
971 .scroll_to_range(area.left() as usize..area.right() as usize);
972 let v = self
973 .vscroll
974 .scroll_to_range(area.top() as usize..area.bottom() as usize);
975 h || v
976 } else {
977 false
978 }
979 }
980
981 pub fn first(&self) -> Option<W> {
985 let layout = self.layout.borrow();
986
987 let area = Rect::new(
988 self.hscroll.offset() as u16,
989 self.vscroll.offset() as u16,
990 self.widget_area.width,
991 self.widget_area.height,
992 );
993
994 for idx in 0..layout.widget_len() {
995 if layout.widget(idx).intersects(area) {
996 return Some(layout.widget_key(idx).clone());
997 }
998 }
999
1000 None
1001 }
1002}
1003
1004impl<W> ClipperState<W>
1005where
1006 W: Eq + Clone + Hash,
1007{
1008 pub fn vertical_offset(&self) -> usize {
1009 self.vscroll.offset()
1010 }
1011
1012 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
1013 let old = self.vscroll.offset();
1014 self.vscroll.set_offset(offset);
1015 old != self.vscroll.offset()
1016 }
1017
1018 pub fn vertical_page_len(&self) -> usize {
1019 self.vscroll.page_len()
1020 }
1021
1022 pub fn horizontal_offset(&self) -> usize {
1023 self.hscroll.offset()
1024 }
1025
1026 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
1027 let old = self.hscroll.offset();
1028 self.hscroll.set_offset(offset);
1029 old != self.hscroll.offset()
1030 }
1031
1032 pub fn horizontal_page_len(&self) -> usize {
1033 self.hscroll.page_len()
1034 }
1035
1036 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
1037 self.hscroll.scroll_to_pos(pos)
1038 }
1039
1040 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
1041 self.vscroll.scroll_to_pos(pos)
1042 }
1043
1044 pub fn scroll_to(&mut self, widget: W) -> bool {
1046 self.show(widget)
1047 }
1048
1049 pub fn scroll_up(&mut self, delta: usize) -> bool {
1050 self.vscroll.scroll_up(delta)
1051 }
1052
1053 pub fn scroll_down(&mut self, delta: usize) -> bool {
1054 self.vscroll.scroll_down(delta)
1055 }
1056
1057 pub fn scroll_left(&mut self, delta: usize) -> bool {
1058 self.hscroll.scroll_left(delta)
1059 }
1060
1061 pub fn scroll_right(&mut self, delta: usize) -> bool {
1062 self.hscroll.scroll_right(delta)
1063 }
1064}
1065
1066impl ClipperState<usize> {
1067 pub fn focus_first(&self, focus: &Focus) -> bool {
1070 if let Some(w) = self.first() {
1071 focus.by_widget_id(w);
1072 true
1073 } else {
1074 false
1075 }
1076 }
1077
1078 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1084 let Some(focused) = focus.focused() else {
1085 return false;
1086 };
1087 let focused = focused.widget_id();
1088 self.scroll_to(focused)
1089 }
1090}
1091
1092impl ClipperState<FocusFlag> {
1093 pub fn focus_first(&self, focus: &Focus) -> bool {
1095 if let Some(w) = self.first() {
1096 focus.focus(&w);
1097 true
1098 } else {
1099 false
1100 }
1101 }
1102
1103 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1106 let Some(focused) = focus.focused() else {
1107 return false;
1108 };
1109 self.scroll_to(focused)
1110 }
1111}
1112
1113impl<W> HandleEvent<crossterm::event::Event, Regular, Outcome> for ClipperState<W>
1114where
1115 W: Eq + Clone + Hash,
1116{
1117 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
1118 let r = if self.container.is_focused() {
1119 match event {
1120 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
1121 ct_event!(keycode press PageDown) => {
1122 self.scroll_down(self.vscroll.page_len()).into()
1123 }
1124 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
1125 ct_event!(keycode press End) => {
1126 self.vertical_scroll_to(self.vscroll.max_offset()).into()
1127 }
1128 _ => Outcome::Continue,
1129 }
1130 } else {
1131 Outcome::Continue
1132 };
1133
1134 r.or_else(|| self.handle(event, MouseOnly))
1135 }
1136}
1137
1138impl<W> HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ClipperState<W>
1139where
1140 W: Eq + Clone + Hash,
1141{
1142 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
1143 let mut sas = ScrollAreaState::new()
1144 .area(self.widget_area)
1145 .h_scroll(&mut self.hscroll)
1146 .v_scroll(&mut self.vscroll);
1147 match sas.handle(event, MouseOnly) {
1148 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
1149 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
1150 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
1151 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
1152 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
1153 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
1154 r => r.into(),
1155 }
1156 }
1157}
1158
1159pub fn handle_events<W>(
1163 state: &mut ClipperState<W>,
1164 _focus: bool,
1165 event: &crossterm::event::Event,
1166) -> Outcome
1167where
1168 W: Eq + Clone + Hash,
1169{
1170 HandleEvent::handle(state, event, Regular)
1171}
1172
1173pub fn handle_mouse_events<W>(
1175 state: &mut ClipperState<W>,
1176 event: &crossterm::event::Event,
1177) -> Outcome
1178where
1179 W: Eq + Clone + Hash,
1180{
1181 HandleEvent::handle(state, event, MouseOnly)
1182}