1use crate::_private::NonExhaustive;
51use crate::event::ScrollOutcome;
52use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Outcome, Regular, ct_event};
53use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
54use rat_reloc::RelocatableState;
55use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
56use ratatui::buffer::Buffer;
57use ratatui::layout::{Position, Rect, Size};
58use ratatui::style::Style;
59use ratatui::widgets::Block;
60use ratatui::widgets::{StatefulWidget, Widget};
61use std::cmp::min;
62use std::mem;
63
64#[derive(Debug, Default, Clone)]
66pub struct View<'a> {
67 view_layout: Option<Rect>,
68 view_x: Option<u16>,
69 view_y: Option<u16>,
70 view_width: Option<u16>,
71 view_height: Option<u16>,
72
73 style: Style,
74 block: Option<Block<'a>>,
75 hscroll: Option<Scroll<'a>>,
76 vscroll: Option<Scroll<'a>>,
77}
78
79#[derive(Debug)]
86pub struct ViewBuffer<'a> {
87 offset: Position,
89 buffer: Buffer,
90
91 widget_area: Rect,
93
94 style: Style,
95 block: Option<Block<'a>>,
96 hscroll: Option<Scroll<'a>>,
97 vscroll: Option<Scroll<'a>>,
98
99 destruct: bool,
100}
101
102#[derive(Debug)]
105pub struct ViewWidget<'a> {
106 offset: Position,
108 buffer: Buffer,
109
110 style: Style,
111 block: Option<Block<'a>>,
112 hscroll: Option<Scroll<'a>>,
113 vscroll: Option<Scroll<'a>>,
114}
115
116#[derive(Debug, Clone)]
118pub struct ViewStyle {
119 pub style: Style,
120 pub block: Option<Block<'static>>,
121 pub border_style: Option<Style>,
122 pub title_style: Option<Style>,
123 pub scroll: Option<ScrollStyle>,
124 pub non_exhaustive: NonExhaustive,
125}
126
127#[derive(Debug, Default, Clone)]
129pub struct ViewState {
130 pub area: Rect,
133 pub widget_area: Rect,
136
137 pub layout: Rect,
140
141 pub hscroll: ScrollState,
144 pub vscroll: ScrollState,
147
148 pub focus: FocusFlag,
151
152 buffer: Option<Buffer>,
154}
155
156impl<'a> View<'a> {
157 pub fn new() -> Self {
159 Self::default()
160 }
161
162 pub fn layout(mut self, area: Rect) -> Self {
164 self.view_layout = Some(area);
165 self
166 }
167
168 pub fn view_width(mut self, width: u16) -> Self {
170 self.view_width = Some(width);
171 self
172 }
173
174 pub fn view_height(mut self, height: u16) -> Self {
176 self.view_height = Some(height);
177 self
178 }
179 pub fn view_x(mut self, x: u16) -> Self {
181 self.view_x = Some(x);
182 self
183 }
184
185 pub fn view_y(mut self, y: u16) -> Self {
187 self.view_y = Some(y);
188 self
189 }
190
191 pub fn view_size(mut self, view: Size) -> Self {
193 self.view_width = Some(view.width);
194 self.view_height = Some(view.height);
195 self
196 }
197
198 pub fn style(mut self, style: Style) -> Self {
200 self.style = style;
201 self.block = self.block.map(|v| v.style(style));
202 self
203 }
204
205 pub fn block(mut self, block: Block<'a>) -> Self {
207 self.block = Some(block);
208 self
209 }
210
211 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
213 self.hscroll = Some(scroll.clone().override_horizontal());
214 self.vscroll = Some(scroll.override_vertical());
215 self
216 }
217
218 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
220 self.hscroll = Some(scroll.override_horizontal());
221 self
222 }
223
224 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
226 self.vscroll = Some(scroll.override_vertical());
227 self
228 }
229
230 pub fn styles(mut self, styles: ViewStyle) -> Self {
232 self.style = styles.style;
233 if styles.block.is_some() {
234 self.block = styles.block;
235 }
236 if let Some(border_style) = styles.border_style {
237 self.block = self.block.map(|v| v.border_style(border_style));
238 }
239 if let Some(title_style) = styles.title_style {
240 self.block = self.block.map(|v| v.title_style(title_style));
241 }
242 self.block = self.block.map(|v| v.style(self.style));
243 if let Some(styles) = styles.scroll {
244 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
245 self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
246 }
247 self
248 }
249
250 #[allow(deprecated)]
252 pub fn layout_size(&self, area: Rect, state: &ViewState) -> u16 {
253 self.inner(area, state).width
254 }
255
256 #[allow(deprecated)]
258 pub fn layout_width(&self, area: Rect, state: &ViewState) -> u16 {
259 self.inner(area, state).width
260 }
261
262 #[deprecated(since = "2.3.0", note = "use layout_size instead")]
264 pub fn inner(&self, area: Rect, state: &ViewState) -> Rect {
265 let sa = ScrollArea::new()
266 .block(self.block.as_ref())
267 .h_scroll(self.hscroll.as_ref())
268 .v_scroll(self.vscroll.as_ref());
269 sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
270 }
271
272 pub fn into_buffer(self, area: Rect, state: &mut ViewState) -> ViewBuffer<'a> {
274 state.area = area;
275
276 let sa = ScrollArea::new()
277 .block(self.block.as_ref())
278 .h_scroll(self.hscroll.as_ref())
279 .v_scroll(self.vscroll.as_ref());
280 state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
281
282 state.layout = if let Some(layout) = self.view_layout {
283 layout
284 } else {
285 let mut layout = Rect::new(0, 0, state.widget_area.width, state.widget_area.height);
286 if let Some(x) = self.view_x {
287 layout.x = x;
288 }
289 if let Some(y) = self.view_y {
290 layout.y = y;
291 }
292 if let Some(width) = self.view_width {
293 layout.width = width;
294 }
295 if let Some(height) = self.view_height {
296 layout.height = height;
297 }
298 layout
299 };
300
301 state
302 .hscroll
303 .set_max_offset(state.layout.width.saturating_sub(state.widget_area.width) as usize);
304 state.hscroll.set_page_len(state.widget_area.width as usize);
305 state
306 .vscroll
307 .set_page_len(state.widget_area.height as usize);
308 state
309 .vscroll
310 .set_max_offset(state.layout.height.saturating_sub(state.widget_area.height) as usize);
311
312 let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
315
316 let mut buffer = if let Some(mut buffer) = state.buffer.take() {
318 buffer.reset();
319 buffer.resize(state.layout);
320 buffer
321 } else {
322 Buffer::empty(state.layout)
323 };
324 buffer.set_style(state.layout, self.style);
325
326 ViewBuffer {
327 offset,
328 buffer,
329 widget_area: state.widget_area,
330 style: self.style,
331 block: self.block,
332 hscroll: self.hscroll,
333 vscroll: self.vscroll,
334 destruct: false,
335 }
336 }
337}
338
339impl<'a> Drop for ViewBuffer<'a> {
340 fn drop(&mut self) {
341 if !self.destruct {
342 panic!("ViewBuffer: Must be used. Call finish(..)");
343 }
344 }
345}
346
347impl<'a> ViewBuffer<'a> {
348 #[inline(always)]
350 pub fn render_widget<W>(&mut self, widget: W, area: Rect) -> bool
351 where
352 W: Widget,
353 {
354 if area.intersects(self.buffer.area) {
355 widget.render(area, self.buffer());
357 true
358 } else {
359 false
360 }
361 }
362
363 #[inline(always)]
366 #[allow(deprecated)]
367 pub fn render<W, S>(&mut self, widget: W, area: Rect, state: &mut S) -> bool
368 where
369 W: StatefulWidget<State = S>,
370 S: RelocatableState,
371 {
372 if area.intersects(self.buffer.area) {
373 widget.render(area, self.buffer(), state);
375 state.relocate(self.shift(), self.widget_area);
377 true
378 } else {
379 state.relocate_hidden();
380 false
381 }
382 }
383
384 #[inline(always)]
388 #[allow(deprecated)]
389 pub fn render_popup<W, S>(&mut self, widget: W, area: Rect, state: &mut S) -> bool
390 where
391 W: StatefulWidget<State = S>,
392 S: RelocatableState,
393 {
394 if area.intersects(self.buffer.area) {
395 widget.render(area, self.buffer(), state);
397 state.relocate_popup(self.shift(), self.widget_area);
399 true
400 } else {
401 state.relocate_popup_hidden();
402 false
403 }
404 }
405
406 pub fn layout(&self) -> Rect {
408 self.buffer.area
409 }
410
411 pub fn is_visible_area(&self, area: Rect) -> bool {
413 area.intersects(self.buffer.area)
414 }
415
416 #[deprecated(
418 since = "2.0.0",
419 note = "should not be public. use relocate2() instead."
420 )]
421 pub fn shift(&self) -> (i16, i16) {
422 (
423 self.widget_area.x as i16 - self.offset.x as i16,
424 self.widget_area.y as i16 - self.offset.y as i16,
425 )
426 }
427
428 #[deprecated(
431 since = "2.0.0",
432 note = "wrong api, use is_visible_area() or locate_area2()"
433 )]
434 pub fn locate_area(&self, area: Rect) -> Rect {
435 area
436 }
437
438 pub fn locate_area2(&self, area: Rect) -> Option<Rect> {
440 if area.intersects(self.buffer.area) {
441 Some(area)
442 } else {
443 None
444 }
445 }
446
447 #[deprecated(since = "2.0.0", note = "wrong api, use relocate2() instead")]
453 #[allow(deprecated)]
454 pub fn relocate<S>(&self, state: &mut S)
455 where
456 S: RelocatableState,
457 {
458 state.relocate(self.shift(), self.widget_area);
459 }
460
461 #[allow(deprecated)]
467 pub fn relocate2<S>(&self, area: Rect, state: &mut S)
468 where
469 S: RelocatableState,
470 {
471 if self.is_visible_area(area) {
472 state.relocate(self.shift(), self.widget_area);
473 } else {
474 state.relocate_hidden();
475 }
476 }
477
478 #[deprecated(since = "2.0.0", note = "bad api, use relocate2() instead")]
484 pub fn hidden<S>(&self, state: &mut S)
485 where
486 S: RelocatableState,
487 {
488 state.relocate_hidden();
489 }
490
491 pub fn buffer(&mut self) -> &mut Buffer {
496 &mut self.buffer
497 }
498
499 #[deprecated(since = "2.3.0", note = "use finish() instead")]
503 pub fn into_widget(mut self) -> ViewWidget<'a> {
504 self.destruct = true;
505
506 ViewWidget {
507 block: mem::take(&mut self.block),
508 hscroll: mem::take(&mut self.hscroll),
509 vscroll: mem::take(&mut self.vscroll),
510 offset: self.offset,
511 buffer: mem::take(&mut self.buffer),
512 style: self.style,
513 }
514 }
515
516 pub fn finish(mut self, tgt_buf: &mut Buffer, state: &mut ViewState) {
518 self.destruct = true;
519
520 ScrollArea::new()
521 .style(self.style)
522 .block(self.block.as_ref())
523 .h_scroll(self.hscroll.as_ref())
524 .v_scroll(self.vscroll.as_ref())
525 .render(
526 state.area,
527 tgt_buf,
528 &mut ScrollAreaState::new()
529 .h_scroll(&mut state.hscroll)
530 .v_scroll(&mut state.vscroll),
531 );
532
533 let src_area = self.buffer.area;
534 let tgt_area = state.widget_area;
535 let offset = self.offset;
536
537 let off_x0 = src_area.x.saturating_sub(offset.x);
539 let off_y0 = src_area.y.saturating_sub(offset.y);
540 let cut_x0 = offset.x.saturating_sub(src_area.x);
542 let cut_y0 = offset.y.saturating_sub(src_area.y);
543
544 let len_src = src_area.width.saturating_sub(cut_x0);
546 let len_tgt = tgt_area.width.saturating_sub(off_x0);
547 let len = min(len_src, len_tgt);
548
549 let height_src = src_area.height.saturating_sub(cut_y0);
551 let height_tgt = tgt_area.height.saturating_sub(off_y0);
552 let height = min(height_src, height_tgt);
553
554 for y in 0..height {
568 let src_0 = self
569 .buffer
570 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
571 let tgt_0 = tgt_buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
572
573 let src = &self.buffer.content[src_0..src_0 + len as usize];
574 let tgt = &mut tgt_buf.content[tgt_0..tgt_0 + len as usize];
575 tgt.clone_from_slice(src);
576 }
577
578 state.buffer = Some(mem::take(&mut self.buffer));
580 }
581}
582
583impl StatefulWidget for ViewWidget<'_> {
584 type State = ViewState;
585
586 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
587 if cfg!(debug_assertions) {
588 if area != state.area {
589 panic!(
590 "ViewWidget::render() must be called with the same area as View::into_buffer()."
591 )
592 }
593 }
594 ScrollArea::new()
595 .style(self.style)
596 .block(self.block.as_ref())
597 .h_scroll(self.hscroll.as_ref())
598 .v_scroll(self.vscroll.as_ref())
599 .render(
600 state.area,
601 buf,
602 &mut ScrollAreaState::new()
603 .h_scroll(&mut state.hscroll)
604 .v_scroll(&mut state.vscroll),
605 );
606
607 let src_area = self.buffer.area;
608 let tgt_area = state.widget_area;
609 let offset = self.offset;
610
611 let off_x0 = src_area.x.saturating_sub(offset.x);
613 let off_y0 = src_area.y.saturating_sub(offset.y);
614 let cut_x0 = offset.x.saturating_sub(src_area.x);
616 let cut_y0 = offset.y.saturating_sub(src_area.y);
617
618 let len_src = src_area.width.saturating_sub(cut_x0);
620 let len_tgt = tgt_area.width.saturating_sub(off_x0);
621 let len = min(len_src, len_tgt);
622
623 let height_src = src_area.height.saturating_sub(cut_y0);
625 let height_tgt = tgt_area.height.saturating_sub(off_y0);
626 let height = min(height_src, height_tgt);
627
628 for y in 0..height {
642 let src_0 = self
643 .buffer
644 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
645 let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
646
647 let src = &self.buffer.content[src_0..src_0 + len as usize];
648 let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
649 tgt.clone_from_slice(src);
650 }
651
652 state.buffer = Some(self.buffer);
654 }
655}
656
657impl Default for ViewStyle {
658 fn default() -> Self {
659 Self {
660 style: Default::default(),
661 block: Default::default(),
662 border_style: Default::default(),
663 title_style: Default::default(),
664 scroll: Default::default(),
665 non_exhaustive: NonExhaustive,
666 }
667 }
668}
669
670impl HasFocus for ViewState {
671 fn build(&self, builder: &mut FocusBuilder) {
672 builder.leaf_widget(self);
673 }
674
675 fn focus(&self) -> FocusFlag {
676 self.focus.clone()
677 }
678
679 fn area(&self) -> Rect {
680 self.area
681 }
682}
683
684impl RelocatableState for ViewState {
685 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
686 self.area.relocate(shift, clip);
687 self.widget_area.relocate(shift, clip);
688 self.hscroll.relocate(shift, clip);
689 self.vscroll.relocate(shift, clip);
690 }
691}
692
693impl ViewState {
694 pub fn new() -> Self {
695 Self::default()
696 }
697
698 pub fn show_area(&mut self, area: Rect) {
700 self.hscroll.scroll_to_pos(area.x as usize);
701 self.vscroll.scroll_to_pos(area.y as usize);
702 }
703}
704
705impl ViewState {
706 pub fn vertical_offset(&self) -> usize {
707 self.vscroll.offset()
708 }
709
710 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
711 let old = self.vscroll.offset();
712 self.vscroll.set_offset(offset);
713 old != self.vscroll.offset()
714 }
715
716 pub fn vertical_page_len(&self) -> usize {
717 self.vscroll.page_len()
718 }
719
720 pub fn horizontal_offset(&self) -> usize {
721 self.hscroll.offset()
722 }
723
724 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
725 let old = self.hscroll.offset();
726 self.hscroll.set_offset(offset);
727 old != self.hscroll.offset()
728 }
729
730 pub fn horizontal_page_len(&self) -> usize {
731 self.hscroll.page_len()
732 }
733
734 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
735 self.hscroll.scroll_to_pos(pos)
736 }
737
738 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
739 self.vscroll.scroll_to_pos(pos)
740 }
741
742 pub fn scroll_up(&mut self, delta: usize) -> bool {
743 self.vscroll.scroll_up(delta)
744 }
745
746 pub fn scroll_down(&mut self, delta: usize) -> bool {
747 self.vscroll.scroll_down(delta)
748 }
749
750 pub fn scroll_left(&mut self, delta: usize) -> bool {
751 self.hscroll.scroll_left(delta)
752 }
753
754 pub fn scroll_right(&mut self, delta: usize) -> bool {
755 self.hscroll.scroll_right(delta)
756 }
757}
758
759impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ViewState {
760 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
761 let r = if self.is_focused() {
762 match event {
763 ct_event!(keycode press Left) => self.scroll_left(self.hscroll.scroll_by()).into(),
764 ct_event!(keycode press Right) => {
765 self.scroll_right(self.hscroll.scroll_by()).into()
766 }
767 ct_event!(keycode press Up) => self.scroll_up(self.vscroll.scroll_by()).into(),
768 ct_event!(keycode press Down) => self.scroll_down(self.vscroll.scroll_by()).into(),
769
770 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
771 ct_event!(keycode press PageDown) => {
772 self.scroll_down(self.vscroll.page_len()).into()
773 }
774 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
775 ct_event!(keycode press End) => {
776 self.vertical_scroll_to(self.vscroll.max_offset()).into()
777 }
778
779 ct_event!(keycode press ALT-PageUp) => {
780 self.scroll_left(self.hscroll.page_len()).into()
781 }
782 ct_event!(keycode press ALT-PageDown) => {
783 self.scroll_right(self.hscroll.page_len()).into()
784 }
785 ct_event!(keycode press ALT-Home) => self.horizontal_scroll_to(0).into(),
786 ct_event!(keycode press ALT-End) => {
787 self.horizontal_scroll_to(self.hscroll.max_offset()).into()
788 }
789 _ => Outcome::Continue,
790 }
791 } else {
792 Outcome::Continue
793 };
794
795 r.or_else(|| self.handle(event, MouseOnly))
796 }
797}
798
799impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ViewState {
800 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
801 let mut sas = ScrollAreaState::new()
802 .area(self.widget_area)
803 .h_scroll(&mut self.hscroll)
804 .v_scroll(&mut self.vscroll);
805 match sas.handle(event, MouseOnly) {
806 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
807 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
808 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
809 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
810 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
811 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
812 r => r.into(),
813 }
814 }
815}
816
817pub fn handle_events(
821 state: &mut ViewState,
822 focus: bool,
823 event: &crossterm::event::Event,
824) -> Outcome {
825 state.focus.set(focus);
826 HandleEvent::handle(state, event, Regular)
827}
828
829pub fn handle_mouse_events(state: &mut ViewState, event: &crossterm::event::Event) -> Outcome {
831 HandleEvent::handle(state, event, MouseOnly)
832}