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)]
643 pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
644 where
645 FN: FnOnce() -> (WW, R),
646 WW: StatefulWidget<State = SS>,
647 SS: RelocatableState,
648 {
649 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
650 return None;
651 };
652 if !self.manual_label {
653 self.render_auto_label(idx);
654 }
655 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
656 state.relocate_hidden();
657 return None;
658 };
659 let (widget, remainder) = render_fn();
660 widget.render(widget_area, &mut self.buffer, state);
661 state.relocate(self.shift(), self.widget_area);
662
663 Some(remainder)
664 }
665
666 #[inline(always)]
670 pub fn render_popup<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
671 where
672 FN: FnOnce() -> Option<WW>,
673 WW: StatefulWidget<State = SS>,
674 SS: RelocatableState,
675 {
676 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
677 return false;
678 };
679 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
680 state.relocate_popup_hidden();
681 return false;
682 };
683 let widget = render_fn();
684 if let Some(widget) = widget {
685 widget.render(widget_area, &mut self.buffer, state);
686 state.relocate_popup(self.shift(), self.widget_area);
687 true
688 } else {
689 state.relocate_popup_hidden();
690 false
691 }
692 }
693
694 #[inline]
696 pub fn locate_widget(&self, widget: W) -> Option<Rect> {
697 let layout = self.layout.borrow();
698 let Some(idx) = layout.try_index_of(widget) else {
699 return None;
700 };
701 self.locate_area(layout.widget(idx))
702 }
703
704 #[inline]
706 #[allow(clippy::question_mark)]
707 pub fn locate_label(&self, widget: W) -> Option<Rect> {
708 let layout = self.layout.borrow();
709 let Some(idx) = layout.try_index_of(widget) else {
710 return None;
711 };
712 self.locate_area(layout.label(idx))
713 }
714
715 #[inline]
720 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
721 if self.buffer.area.intersects(area) {
722 Some(area)
723 } else {
724 None
725 }
726 }
727
728 fn shift(&self) -> (i16, i16) {
730 (
731 self.widget_area.x as i16 - self.offset.x as i16,
732 self.widget_area.y as i16 - self.offset.y as i16,
733 )
734 }
735
736 pub fn relocate<S>(&self, widget: W, state: &mut S)
753 where
754 S: RelocatableState,
755 {
756 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
757 return;
758 };
759 if self.locate_area(self.layout.borrow().widget(idx)).is_some() {
760 state.relocate(self.shift(), self.widget_area);
761 } else {
762 state.relocate_hidden();
763 };
764 }
765
766 #[inline]
768 pub fn buffer(&mut self) -> &mut Buffer {
769 &mut self.buffer
770 }
771
772 pub fn finish(mut self, tgt_buf: &mut Buffer, state: &mut ClipperState<W>) {
773 self.destruct = true;
774
775 self.render_block();
776
777 ScrollArea::new()
778 .style(self.style)
779 .block(self.block.as_ref())
780 .h_scroll(self.hscroll.as_ref())
781 .v_scroll(self.vscroll.as_ref())
782 .render(
783 state.area,
784 tgt_buf,
785 &mut ScrollAreaState::new()
786 .h_scroll(&mut state.hscroll)
787 .v_scroll(&mut state.vscroll),
788 );
789
790 let src_area = self.buffer.area;
791 let tgt_area = state.widget_area;
792 let offset = self.offset;
793
794 let off_x0 = src_area.x.saturating_sub(offset.x);
796 let off_y0 = src_area.y.saturating_sub(offset.y);
797 let cut_x0 = offset.x.saturating_sub(src_area.x);
799 let cut_y0 = offset.y.saturating_sub(src_area.y);
800
801 let len_src = src_area.width.saturating_sub(cut_x0);
803 let len_tgt = tgt_area.width.saturating_sub(off_x0);
804 let len = min(len_src, len_tgt);
805
806 let height_src = src_area.height.saturating_sub(cut_y0);
808 let height_tgt = tgt_area.height.saturating_sub(off_y0);
809 let height = min(height_src, height_tgt);
810
811 for y in 0..height {
825 let src_0 = self
826 .buffer
827 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
828
829 if tgt_buf
830 .area
831 .contains(Position::new(tgt_area.x + off_x0, tgt_area.y + off_y0 + y))
832 {
833 let tgt_0 = tgt_buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
834
835 let src = &self.buffer.content[src_0..src_0 + len as usize];
836 let tgt = &mut tgt_buf.content[tgt_0..tgt_0 + len as usize];
837 tgt.clone_from_slice(src);
838 }
839 }
840
841 state.buffer = Some(mem::take(&mut self.buffer));
843 }
844}
845
846impl<W> Default for ClipperState<W>
847where
848 W: Eq + Hash + Clone,
849{
850 fn default() -> Self {
851 Self {
852 area: Default::default(),
853 widget_area: Default::default(),
854 layout: Default::default(),
855 hscroll: Default::default(),
856 vscroll: Default::default(),
857 container: Default::default(),
858 buffer: None,
859 non_exhaustive: NonExhaustive,
860 }
861 }
862}
863
864impl<W> Clone for ClipperState<W>
865where
866 W: Eq + Hash + Clone,
867{
868 fn clone(&self) -> Self {
869 Self {
870 area: self.area,
871 widget_area: self.widget_area,
872 layout: self.layout.clone(),
873 hscroll: self.hscroll.clone(),
874 vscroll: self.vscroll.clone(),
875 container: self.container.new_instance(),
876 buffer: None,
877 non_exhaustive: NonExhaustive,
878 }
879 }
880}
881
882impl<W> HasFocus for ClipperState<W>
883where
884 W: Eq + Clone + Hash,
885{
886 fn build(&self, _builder: &mut FocusBuilder) {
887 }
889
890 fn focus(&self) -> FocusFlag {
891 self.container.clone()
892 }
893
894 fn area(&self) -> Rect {
895 self.area
896 }
897}
898
899impl<W> RelocatableState for ClipperState<W>
900where
901 W: Eq + Clone + Hash,
902{
903 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
904 self.area.relocate(shift, clip);
905 self.widget_area.relocate(shift, clip);
906 self.hscroll.relocate(shift, clip);
907 self.vscroll.relocate(shift, clip);
908 }
909}
910
911impl<W> ClipperState<W>
912where
913 W: Eq + Clone + Hash,
914{
915 pub fn new() -> Self {
916 Self::default()
917 }
918
919 pub fn named(name: &str) -> Self {
920 let mut z = Self::default();
921 z.container = z.container.with_name(name);
922 z
923 }
924
925 pub fn clear(&mut self) {
927 self.layout.borrow_mut().clear();
928 self.hscroll.clear();
929 self.vscroll.clear();
930 }
931
932 pub fn valid_layout(&self, size: Size) -> bool {
934 let layout = self.layout.borrow();
935 !layout.size_changed(size) && !layout.is_empty()
936 }
937
938 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
940 self.layout = Rc::new(RefCell::new(layout));
941 }
942
943 pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
945 self.layout.borrow()
946 }
947
948 pub fn show(&mut self, widget: W) -> bool {
950 let layout = self.layout.borrow();
951 let Some(idx) = layout.try_index_of(widget) else {
952 return false;
953 };
954 let widget_area = layout.widget(idx);
955 let label_area = layout.label(idx);
956
957 let area = if !widget_area.is_empty() {
958 if !label_area.is_empty() {
959 Some(widget_area.union(label_area))
960 } else {
961 Some(widget_area)
962 }
963 } else {
964 if !label_area.is_empty() {
965 Some(label_area)
966 } else {
967 None
968 }
969 };
970
971 if let Some(area) = area {
972 let h = self
973 .hscroll
974 .scroll_to_range(area.left() as usize..area.right() as usize);
975 let v = self
976 .vscroll
977 .scroll_to_range(area.top() as usize..area.bottom() as usize);
978 h || v
979 } else {
980 false
981 }
982 }
983
984 pub fn first(&self) -> Option<W> {
988 let layout = self.layout.borrow();
989
990 let area = Rect::new(
991 self.hscroll.offset() as u16,
992 self.vscroll.offset() as u16,
993 self.widget_area.width,
994 self.widget_area.height,
995 );
996
997 for idx in 0..layout.widget_len() {
998 if layout.widget(idx).intersects(area) {
999 return Some(layout.widget_key(idx).clone());
1000 }
1001 }
1002
1003 None
1004 }
1005}
1006
1007impl<W> ClipperState<W>
1008where
1009 W: Eq + Clone + Hash,
1010{
1011 pub fn vertical_offset(&self) -> usize {
1012 self.vscroll.offset()
1013 }
1014
1015 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
1016 let old = self.vscroll.offset();
1017 self.vscroll.set_offset(offset);
1018 old != self.vscroll.offset()
1019 }
1020
1021 pub fn vertical_page_len(&self) -> usize {
1022 self.vscroll.page_len()
1023 }
1024
1025 pub fn horizontal_offset(&self) -> usize {
1026 self.hscroll.offset()
1027 }
1028
1029 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
1030 let old = self.hscroll.offset();
1031 self.hscroll.set_offset(offset);
1032 old != self.hscroll.offset()
1033 }
1034
1035 pub fn horizontal_page_len(&self) -> usize {
1036 self.hscroll.page_len()
1037 }
1038
1039 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
1040 self.hscroll.scroll_to_pos(pos)
1041 }
1042
1043 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
1044 self.vscroll.scroll_to_pos(pos)
1045 }
1046
1047 pub fn scroll_to(&mut self, widget: W) -> bool {
1049 self.show(widget)
1050 }
1051
1052 pub fn scroll_up(&mut self, delta: usize) -> bool {
1053 self.vscroll.scroll_up(delta)
1054 }
1055
1056 pub fn scroll_down(&mut self, delta: usize) -> bool {
1057 self.vscroll.scroll_down(delta)
1058 }
1059
1060 pub fn scroll_left(&mut self, delta: usize) -> bool {
1061 self.hscroll.scroll_left(delta)
1062 }
1063
1064 pub fn scroll_right(&mut self, delta: usize) -> bool {
1065 self.hscroll.scroll_right(delta)
1066 }
1067}
1068
1069impl ClipperState<usize> {
1070 pub fn focus_first(&self, focus: &Focus) -> bool {
1073 if let Some(w) = self.first() {
1074 focus.by_widget_id(w);
1075 true
1076 } else {
1077 false
1078 }
1079 }
1080
1081 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1087 let Some(focused) = focus.focused() else {
1088 return false;
1089 };
1090 let focused = focused.widget_id();
1091 self.scroll_to(focused)
1092 }
1093}
1094
1095impl ClipperState<FocusFlag> {
1096 pub fn focus_first(&self, focus: &Focus) -> bool {
1098 if let Some(w) = self.first() {
1099 focus.focus(&w);
1100 true
1101 } else {
1102 false
1103 }
1104 }
1105
1106 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1109 let Some(focused) = focus.focused() else {
1110 return false;
1111 };
1112 self.scroll_to(focused)
1113 }
1114}
1115
1116impl<W> HandleEvent<Event, Regular, Outcome> for ClipperState<W>
1117where
1118 W: Eq + Clone + Hash,
1119{
1120 fn handle(&mut self, event: &Event, _keymap: Regular) -> Outcome {
1121 let r = if self.container.is_focused() {
1122 match event {
1123 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
1124 ct_event!(keycode press PageDown) => {
1125 self.scroll_down(self.vscroll.page_len()).into()
1126 }
1127 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
1128 ct_event!(keycode press End) => {
1129 self.vertical_scroll_to(self.vscroll.max_offset()).into()
1130 }
1131 _ => Outcome::Continue,
1132 }
1133 } else {
1134 Outcome::Continue
1135 };
1136
1137 r.or_else(|| self.handle(event, MouseOnly))
1138 }
1139}
1140
1141impl<W> HandleEvent<Event, MouseOnly, Outcome> for ClipperState<W>
1142where
1143 W: Eq + Clone + Hash,
1144{
1145 fn handle(&mut self, event: &Event, _keymap: MouseOnly) -> Outcome {
1146 let mut sas = ScrollAreaState::new()
1147 .area(self.widget_area)
1148 .h_scroll(&mut self.hscroll)
1149 .v_scroll(&mut self.vscroll);
1150 match sas.handle(event, MouseOnly) {
1151 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
1152 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
1153 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
1154 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
1155 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
1156 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
1157 r => r.into(),
1158 }
1159 }
1160}
1161
1162pub fn handle_events<W>(state: &mut ClipperState<W>, _focus: bool, event: &Event) -> Outcome
1166where
1167 W: Eq + Clone + Hash,
1168{
1169 HandleEvent::handle(state, event, Regular)
1170}
1171
1172pub fn handle_mouse_events<W>(state: &mut ClipperState<W>, event: &Event) -> Outcome
1174where
1175 W: Eq + Clone + Hash,
1176{
1177 HandleEvent::handle(state, event, MouseOnly)
1178}