1use crate::_private::NonExhaustive;
2use crate::caption::{Caption, CaptionState, CaptionStyle};
3use crate::clipper::ClipperStyle;
4use crate::layout::GenericLayout;
5use rat_event::{ct_event, ConsumedEvent, HandleEvent, MouseOnly, Outcome, Regular};
6use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
7use rat_reloc::RelocatableState;
8use rat_scrolled::event::ScrollOutcome;
9use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState};
10use ratatui::buffer::Buffer;
11use ratatui::layout::{Alignment, Position, Rect, Size};
12use ratatui::style::Style;
13use ratatui::text::Line;
14use ratatui::widgets::Widget;
15use ratatui::widgets::{Block, StatefulWidget};
16use std::borrow::Cow;
17use std::cell::{Ref, RefCell};
18use std::cmp::{max, min};
19use std::hash::Hash;
20use std::marker::PhantomData;
21use std::rc::Rc;
22
23#[derive(Debug)]
26pub struct Clipper<'a, W>
27where
28 W: Eq + Clone + Hash,
29{
30 layout: Option<GenericLayout<W>>,
31 style: Style,
32 block: Option<Block<'a>>,
33 hscroll: Option<Scroll<'a>>,
34 vscroll: Option<Scroll<'a>>,
35 label_style: Option<Style>,
36 label_alignment: Option<Alignment>,
37 caption_style: Option<CaptionStyle>,
38 auto_label: bool,
39}
40
41#[derive(Debug)]
43pub struct ClipperBuffer<'a, W>
44where
45 W: Eq + Clone + Hash,
46{
47 layout: Rc<RefCell<GenericLayout<W>>>,
48 auto_label: bool,
49
50 offset: Position,
52 buffer: Buffer,
53
54 widget_area: Rect,
56
57 style: Style,
58 block: Option<Block<'a>>,
59 hscroll: Option<Scroll<'a>>,
60 vscroll: Option<Scroll<'a>>,
61 label_style: Option<Style>,
62 label_alignment: Option<Alignment>,
63 caption_style: Option<CaptionStyle>,
64}
65
66#[derive(Debug)]
67pub struct ClipperWidget<'a, W>
68where
69 W: Eq + Clone + Hash,
70{
71 offset: Position,
72 buffer: Buffer,
73
74 style: Style,
75 block: Option<Block<'a>>,
76 hscroll: Option<Scroll<'a>>,
77 vscroll: Option<Scroll<'a>>,
78 phantom: PhantomData<W>,
79}
80
81#[derive(Debug)]
82pub struct ClipperState<W>
83where
84 W: Eq + Clone + Hash,
85{
86 pub area: Rect,
89 pub widget_area: Rect,
92
93 pub layout: Rc<RefCell<GenericLayout<W>>>,
96
97 pub hscroll: ScrollState,
100 pub vscroll: ScrollState,
103
104 pub container: FocusFlag,
107
108 buffer: Option<Buffer>,
110
111 pub non_exhaustive: NonExhaustive,
113}
114
115impl<W> Clone for Clipper<'_, W>
116where
117 W: Eq + Clone + Hash,
118{
119 fn clone(&self) -> Self {
120 Self {
121 style: Default::default(),
122 block: self.block.clone(),
123 layout: self.layout.clone(),
124 hscroll: self.hscroll.clone(),
125 vscroll: self.vscroll.clone(),
126 label_style: self.label_style.clone(),
127 label_alignment: self.label_alignment.clone(),
128 caption_style: self.caption_style.clone(),
129 auto_label: self.auto_label,
130 }
131 }
132}
133
134impl<W> Default for Clipper<'_, W>
135where
136 W: Eq + Clone + Hash,
137{
138 fn default() -> Self {
139 Self {
140 style: Default::default(),
141 block: Default::default(),
142 layout: Default::default(),
143 hscroll: Default::default(),
144 vscroll: Default::default(),
145 label_style: Default::default(),
146 label_alignment: Default::default(),
147 caption_style: Default::default(),
148 auto_label: true,
149 }
150 }
151}
152
153impl<'a, W> Clipper<'a, W>
154where
155 W: Eq + Clone + Hash,
156{
157 pub fn new() -> Self {
159 Self::default()
160 }
161
162 pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
165 self.layout = Some(layout);
166 self
167 }
168
169 pub fn style(mut self, style: Style) -> Self {
171 self.style = style;
172 self.block = self.block.map(|v| v.style(style));
173 self
174 }
175
176 pub fn auto_label(mut self, auto: bool) -> Self {
180 self.auto_label = auto;
181 self
182 }
183
184 pub fn label_style(mut self, style: Style) -> Self {
186 self.label_style = Some(style);
187 self
188 }
189
190 pub fn label_alignment(mut self, alignment: Alignment) -> Self {
192 self.label_alignment = Some(alignment);
193 self
194 }
195
196 pub fn caption_style(mut self, style: CaptionStyle) -> Self {
198 self.caption_style = Some(style);
199 self
200 }
201
202 pub fn block(mut self, block: Block<'a>) -> Self {
204 self.block = Some(block);
205 self
206 }
207
208 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
210 self.hscroll = Some(scroll.clone().override_horizontal());
211 self.vscroll = Some(scroll.override_vertical());
212 self
213 }
214
215 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
217 self.hscroll = Some(scroll.override_horizontal());
218 self
219 }
220
221 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
223 self.vscroll = Some(scroll.override_vertical());
224 self
225 }
226
227 pub fn styles(mut self, styles: ClipperStyle) -> Self {
229 self.style = styles.style;
230 if styles.label_style.is_some() {
231 self.label_style = styles.label_style;
232 }
233 if styles.label_alignment.is_some() {
234 self.label_alignment = styles.label_alignment;
235 }
236 if styles.block.is_some() {
237 self.block = styles.block;
238 }
239 if let Some(styles) = styles.scroll {
240 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
241 self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
242 }
243 self.block = self.block.map(|v| v.style(styles.style));
244 self
245 }
246
247 pub fn layout_size(&self, area: Rect, state: &ClipperState<W>) -> Size {
249 let width = self.inner(area, state).width;
250 Size::new(width, u16::MAX)
251 }
252
253 fn inner(&self, area: Rect, state: &ClipperState<W>) -> Rect {
255 let sa = ScrollArea::new()
256 .block(self.block.as_ref())
257 .h_scroll(self.hscroll.as_ref())
258 .v_scroll(self.vscroll.as_ref());
259 sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
260 }
261
262 fn calc_layout(&self, area: Rect, state: &mut ClipperState<W>) -> (Rect, Position) {
263 let layout = state.layout.borrow();
264
265 let view = Rect::new(
266 state.hscroll.offset() as u16,
267 state.vscroll.offset() as u16,
268 area.width,
269 area.height,
270 );
271
272 let mut max_pos = Position::default();
274
275 let mut ext_view: Option<Rect> = None;
278 for idx in 0..layout.widget_len() {
279 let area = layout.widget(idx);
280 let label_area = layout.label(idx);
281
282 if view.intersects(area) || view.intersects(label_area) {
283 if !area.is_empty() {
284 ext_view = ext_view .map(|v| v.union(area))
286 .or(Some(area));
287 }
288 if !label_area.is_empty() {
289 ext_view = ext_view .map(|v| v.union(label_area))
291 .or(Some(label_area));
292 }
293 }
294
295 max_pos.x = max(max_pos.x, area.right());
296 max_pos.y = max(max_pos.y, area.bottom());
297 max_pos.x = max(max_pos.x, label_area.right());
298 max_pos.y = max(max_pos.y, label_area.bottom());
299 }
300 for idx in 0..layout.block_len() {
301 let block_area = layout.block_area(idx);
302 if view.intersects(block_area) {
303 ext_view = ext_view .map(|v| v.union(block_area))
305 .or(Some(block_area));
306 }
307
308 max_pos.x = max(max_pos.x, block_area.right());
309 max_pos.y = max(max_pos.y, block_area.bottom());
310 }
311
312 let ext_view = ext_view.unwrap_or(view);
313
314 (ext_view, max_pos)
315 }
316
317 pub fn into_buffer(mut self, area: Rect, state: &mut ClipperState<W>) -> ClipperBuffer<'a, W> {
319 state.area = area;
320 if let Some(layout) = self.layout.take() {
321 state.layout = Rc::new(RefCell::new(layout));
322 }
323
324 let sa = ScrollArea::new()
325 .block(self.block.as_ref())
326 .h_scroll(self.hscroll.as_ref())
327 .v_scroll(self.vscroll.as_ref());
328 state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
329
330 let (ext_area, max_pos) = self.calc_layout(area, state);
332
333 state
335 .vscroll
336 .set_page_len(state.widget_area.height as usize);
337 state
338 .vscroll
339 .set_max_offset(max_pos.y.saturating_sub(state.widget_area.height) as usize);
340 state.hscroll.set_page_len(state.widget_area.width as usize);
341 state
342 .hscroll
343 .set_max_offset(max_pos.x.saturating_sub(state.widget_area.width) as usize);
344
345 let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
346
347 let buffer_area = ext_area;
349 let mut buffer = if let Some(mut buffer) = state.buffer.take() {
351 buffer.reset();
352 buffer.resize(buffer_area);
353 buffer
354 } else {
355 Buffer::empty(buffer_area)
356 };
357 buffer.set_style(buffer_area, self.style);
358
359 ClipperBuffer {
360 layout: state.layout.clone(),
361 auto_label: self.auto_label,
362 offset,
363 buffer,
364 widget_area: state.widget_area,
365 style: self.style,
366 block: self.block,
367 hscroll: self.hscroll,
368 vscroll: self.vscroll,
369 label_style: self.label_style,
370 label_alignment: self.label_alignment,
371 caption_style: self.caption_style,
372 }
373 }
374}
375
376impl<'a, W> ClipperBuffer<'a, W>
377where
378 W: Eq + Hash + Clone,
379{
380 pub fn is_visible(&self, widget: W) -> bool {
382 let layout = self.layout.borrow();
383 let Some(idx) = layout.try_index_of(widget) else {
384 return false;
385 };
386 let area = layout.widget(idx);
387 self.buffer.area.intersects(area)
388 }
389
390 #[inline(always)]
392 fn render_auto_label(&mut self, idx: usize) -> bool {
393 let layout = self.layout.borrow();
394 let Some(label_area) = self.locate_area(layout.label(idx)) else {
395 return false;
396 };
397 let Some(label_str) = layout.try_label_str(idx) else {
398 return false;
399 };
400
401 let style = self.label_style.unwrap_or_default();
402 let align = self.label_alignment.unwrap_or_default();
403 Line::from(label_str.as_ref())
404 .style(style)
405 .alignment(align)
406 .render(label_area, &mut self.buffer);
407
408 true
409 }
410
411 pub fn render_block(&mut self) {
413 let layout = self.layout.borrow();
414 for (idx, block_area) in layout.block_area_iter().enumerate() {
415 if let Some(block_area) = self.locate_area(*block_area) {
416 if let Some(block) = layout.block(idx) {
417 block.render(block_area, &mut self.buffer);
418 }
419 }
420 }
421 }
422
423 #[inline(always)]
425 pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
426 where
427 FN: FnOnce(&Option<Cow<'static, str>>, Rect, &mut Buffer),
428 {
429 let layout = self.layout.borrow();
430 let Some(idx) = layout.try_index_of(widget) else {
431 return false;
432 };
433 let Some(label_area) = self.locate_area(layout.label(idx)) else {
434 return false;
435 };
436 let label_str = layout.try_label_str(idx);
437 render_fn(label_str, label_area, &mut self.buffer);
438 true
439 }
440
441 #[inline(always)]
443 pub fn render_caption(
444 &mut self,
445 widget: W,
446 link: &FocusFlag,
447 state: &mut CaptionState,
448 ) -> bool {
449 let layout = self.layout.borrow();
450 let Some(idx) = layout.try_index_of(widget) else {
451 return false;
452 };
453 let Some(label_area) = self.locate_area(layout.label(idx)) else {
454 return false;
455 };
456 let Some(label_str) = layout.try_label_str(idx) else {
457 return false;
458 };
459
460 let mut label = Caption::parse(label_str.as_ref()).link(link);
461 if let Some(style) = &self.caption_style {
462 label = label.styles(style.clone())
463 }
464 label.render(label_area, &mut self.buffer, state);
465
466 true
467 }
468
469 #[inline(always)]
471 pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
472 where
473 FN: FnOnce() -> WW,
474 WW: Widget,
475 {
476 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
477 return false;
478 };
479 if self.auto_label {
480 self.render_auto_label(idx);
481 }
482 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
483 return false;
484 };
485 render_fn().render(widget_area, &mut self.buffer);
486 true
487 }
488
489 #[inline(always)]
491 pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
492 where
493 FN: FnOnce() -> WW,
494 WW: StatefulWidget<State = SS>,
495 SS: RelocatableState,
496 {
497 let Some(idx) = self.layout.borrow().try_index_of(widget) else {
498 return false;
499 };
500 if self.auto_label {
501 self.render_auto_label(idx);
502 }
503 let Some(widget_area) = self.locate_area(self.layout.borrow().widget(idx)) else {
504 self.hidden(state);
505 return false;
506 };
507 render_fn().render(widget_area, &mut self.buffer, state);
508 self.relocate(state);
509 true
510 }
511
512 #[inline]
514 pub fn locate_widget(&self, widget: W) -> Option<Rect> {
515 let layout = self.layout.borrow();
516 let Some(idx) = layout.try_index_of(widget) else {
517 return None;
518 };
519 self.locate_area(layout.widget(idx))
520 }
521
522 #[inline]
524 #[allow(clippy::question_mark)]
525 pub fn locate_label(&self, widget: W) -> Option<Rect> {
526 let layout = self.layout.borrow();
527 let Some(idx) = layout.try_index_of(widget) else {
528 return None;
529 };
530 self.locate_area(layout.label(idx))
531 }
532
533 #[inline]
538 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
539 let area = self.buffer.area.intersection(area);
540 if area.is_empty() {
541 None
542 } else {
543 Some(area)
544 }
545 }
546
547 pub fn shift(&self) -> (i16, i16) {
549 (
550 self.widget_area.x as i16 - self.offset.x as i16,
551 self.widget_area.y as i16 - self.offset.y as i16,
552 )
553 }
554
555 pub fn relocate<S>(&self, state: &mut S)
561 where
562 S: RelocatableState,
563 {
564 state.relocate(self.shift(), self.widget_area);
565 }
566
567 pub fn hidden<S>(&self, state: &mut S)
573 where
574 S: RelocatableState,
575 {
576 state.relocate((0, 0), Rect::default())
577 }
578
579 #[inline]
581 pub fn buffer(&mut self) -> &mut Buffer {
582 &mut self.buffer
583 }
584
585 pub fn into_widget(self) -> ClipperWidget<'a, W> {
589 ClipperWidget {
590 block: self.block,
591 hscroll: self.hscroll,
592 vscroll: self.vscroll,
593 offset: self.offset,
594 buffer: self.buffer,
595 phantom: Default::default(),
596 style: self.style,
597 }
598 }
599}
600
601impl<W> StatefulWidget for ClipperWidget<'_, W>
602where
603 W: Eq + Clone + Hash,
604{
605 type State = ClipperState<W>;
606
607 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
608 assert_eq!(area, state.area);
609
610 ScrollArea::new()
611 .style(self.style)
612 .block(self.block.as_ref())
613 .h_scroll(self.hscroll.as_ref())
614 .v_scroll(self.vscroll.as_ref())
615 .render(
616 area,
617 buf,
618 &mut ScrollAreaState::new()
619 .h_scroll(&mut state.hscroll)
620 .v_scroll(&mut state.vscroll),
621 );
622
623 let src_area = self.buffer.area;
624 let tgt_area = state.widget_area;
625 let offset = self.offset;
626
627 let off_x0 = src_area.x.saturating_sub(offset.x);
629 let off_y0 = src_area.y.saturating_sub(offset.y);
630 let cut_x0 = offset.x.saturating_sub(src_area.x);
632 let cut_y0 = offset.y.saturating_sub(src_area.y);
633
634 let len_src = src_area.width.saturating_sub(cut_x0);
636 let len_tgt = tgt_area.width.saturating_sub(off_x0);
637 let len = min(len_src, len_tgt);
638
639 let height_src = src_area.height.saturating_sub(cut_y0);
641 let height_tgt = tgt_area.height.saturating_sub(off_y0);
642 let height = min(height_src, height_tgt);
643
644 for y in 0..height {
658 let src_0 = self
659 .buffer
660 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
661 let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
662
663 let src = &self.buffer.content[src_0..src_0 + len as usize];
664 let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
665 tgt.clone_from_slice(src);
666 }
667
668 state.buffer = Some(self.buffer);
670 }
671}
672
673impl<W> Default for ClipperState<W>
674where
675 W: Eq + Hash + Clone,
676{
677 fn default() -> Self {
678 Self {
679 area: Default::default(),
680 widget_area: Default::default(),
681 layout: Default::default(),
682 hscroll: Default::default(),
683 vscroll: Default::default(),
684 container: Default::default(),
685 buffer: None,
686 non_exhaustive: NonExhaustive,
687 }
688 }
689}
690
691impl<W> Clone for ClipperState<W>
692where
693 W: Eq + Hash + Clone,
694{
695 fn clone(&self) -> Self {
696 Self {
697 area: self.area,
698 widget_area: self.widget_area,
699 layout: self.layout.clone(),
700 hscroll: self.hscroll.clone(),
701 vscroll: self.vscroll.clone(),
702 container: FocusFlag::named(self.container.name()),
703 buffer: None,
704 non_exhaustive: NonExhaustive,
705 }
706 }
707}
708
709impl<W> HasFocus for ClipperState<W>
710where
711 W: Eq + Clone + Hash,
712{
713 fn build(&self, _builder: &mut FocusBuilder) {
714 }
716
717 fn focus(&self) -> FocusFlag {
718 self.container.clone()
719 }
720
721 fn area(&self) -> Rect {
722 self.area
723 }
724}
725
726impl<W> ClipperState<W>
727where
728 W: Eq + Clone + Hash,
729{
730 pub fn new() -> Self {
731 Self::default()
732 }
733
734 pub fn clear(&mut self) {
736 self.layout.borrow_mut().clear();
737 self.hscroll.clear();
738 self.vscroll.clear();
739 }
740
741 pub fn valid_layout(&self, size: Size) -> bool {
743 let layout = self.layout.borrow();
744 !layout.size_changed(size) && !layout.is_empty()
745 }
746
747 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
749 self.layout = Rc::new(RefCell::new(layout));
750 }
751
752 pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
754 self.layout.borrow()
755 }
756
757 pub fn show(&mut self, widget: W) {
759 let layout = self.layout.borrow();
760 let Some(idx) = layout.try_index_of(widget) else {
761 return;
762 };
763 let widget_area = layout.widget(idx);
764 let label_area = layout.label(idx);
765
766 let area = if !widget_area.is_empty() {
767 if !label_area.is_empty() {
768 Some(widget_area.union(label_area))
769 } else {
770 Some(widget_area)
771 }
772 } else {
773 if !label_area.is_empty() {
774 Some(label_area)
775 } else {
776 None
777 }
778 };
779
780 if let Some(area) = area {
781 self.hscroll
782 .scroll_to_range(area.left() as usize..area.right() as usize);
783 self.vscroll
784 .scroll_to_range(area.top() as usize..area.bottom() as usize);
785 }
786 }
787
788 pub fn first(&self) -> Option<W> {
792 let layout = self.layout.borrow();
793
794 let area = Rect::new(
795 self.hscroll.offset() as u16,
796 self.vscroll.offset() as u16,
797 self.widget_area.width,
798 self.widget_area.height,
799 );
800
801 for idx in 0..layout.widget_len() {
802 if layout.widget(idx).intersects(area) {
803 return Some(layout.widget_key(idx).clone());
804 }
805 }
806
807 None
808 }
809}
810
811impl<W> ClipperState<W>
812where
813 W: Eq + Clone + Hash,
814{
815 pub fn vertical_offset(&self) -> usize {
816 self.vscroll.offset()
817 }
818
819 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
820 let old = self.vscroll.offset();
821 self.vscroll.set_offset(offset);
822 old != self.vscroll.offset()
823 }
824
825 pub fn vertical_page_len(&self) -> usize {
826 self.vscroll.page_len()
827 }
828
829 pub fn horizontal_offset(&self) -> usize {
830 self.hscroll.offset()
831 }
832
833 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
834 let old = self.hscroll.offset();
835 self.hscroll.set_offset(offset);
836 old != self.hscroll.offset()
837 }
838
839 pub fn horizontal_page_len(&self) -> usize {
840 self.hscroll.page_len()
841 }
842
843 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
844 self.hscroll.scroll_to_pos(pos)
845 }
846
847 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
848 self.vscroll.scroll_to_pos(pos)
849 }
850
851 pub fn scroll_to(&mut self, widget: W) -> bool {
853 let area = self.layout.borrow().widget_for(widget);
854 let r0 = self
855 .vscroll
856 .scroll_to_range(area.top() as usize..area.bottom() as usize);
857 let r1 = self
858 .hscroll
859 .scroll_to_range(area.left() as usize..area.right() as usize);
860 r0 || r1
861 }
862
863 pub fn scroll_up(&mut self, delta: usize) -> bool {
864 self.vscroll.scroll_up(delta)
865 }
866
867 pub fn scroll_down(&mut self, delta: usize) -> bool {
868 self.vscroll.scroll_down(delta)
869 }
870
871 pub fn scroll_left(&mut self, delta: usize) -> bool {
872 self.hscroll.scroll_left(delta)
873 }
874
875 pub fn scroll_right(&mut self, delta: usize) -> bool {
876 self.hscroll.scroll_right(delta)
877 }
878}
879
880impl<W> HandleEvent<crossterm::event::Event, Regular, Outcome> for ClipperState<W>
881where
882 W: Eq + Clone + Hash,
883{
884 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> Outcome {
885 let r = if self.container.is_focused() {
886 match event {
887 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
888 ct_event!(keycode press PageDown) => {
889 self.scroll_down(self.vscroll.page_len()).into()
890 }
891 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
892 ct_event!(keycode press End) => {
893 self.vertical_scroll_to(self.vscroll.max_offset()).into()
894 }
895 _ => Outcome::Continue,
896 }
897 } else {
898 Outcome::Continue
899 };
900
901 r.or_else(|| self.handle(event, MouseOnly))
902 }
903}
904
905impl<W> HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ClipperState<W>
906where
907 W: Eq + Clone + Hash,
908{
909 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
910 let mut sas = ScrollAreaState::new()
911 .area(self.widget_area)
912 .h_scroll(&mut self.hscroll)
913 .v_scroll(&mut self.vscroll);
914 match sas.handle(event, MouseOnly) {
915 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
916 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
917 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
918 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
919 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
920 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
921 r => r.into(),
922 }
923 }
924}