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