1use crate::_private::NonExhaustive;
50use crate::event::ScrollOutcome;
51use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Outcome, Regular, ct_event};
52use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
53use rat_reloc::RelocatableState;
54use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
55use ratatui_core::buffer::Buffer;
56use ratatui_core::layout::{Position, Rect, Size};
57use ratatui_core::style::Style;
58use ratatui_core::widgets::{StatefulWidget, Widget};
59use ratatui_crossterm::crossterm::event::Event;
60use ratatui_widgets::block::Block;
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.right().saturating_sub(state.widget_area.width) as usize);
304 state.hscroll.set_page_len(state.widget_area.width as usize);
305
306 state.vscroll.set_max_offset(
307 state
308 .layout
309 .right()
310 .saturating_sub(state.widget_area.height) as usize,
311 );
312 state
313 .vscroll
314 .set_page_len(state.widget_area.height as usize);
315
316 let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
318
319 let mut buffer = if let Some(mut buffer) = state.buffer.take() {
321 buffer.reset();
322 buffer.resize(state.layout);
323 buffer
324 } else {
325 Buffer::empty(state.layout)
326 };
327 buffer.set_style(state.layout, self.style);
328
329 ViewBuffer {
330 offset,
331 buffer,
332 widget_area: state.widget_area,
333 style: self.style,
334 block: self.block,
335 hscroll: self.hscroll,
336 vscroll: self.vscroll,
337 destruct: false,
338 }
339 }
340}
341
342impl<'a> Drop for ViewBuffer<'a> {
343 fn drop(&mut self) {
344 if !self.destruct {
345 panic!("ViewBuffer: Must be used. Call finish(..)");
346 }
347 }
348}
349
350impl<'a> ViewBuffer<'a> {
351 #[inline(always)]
353 pub fn render_widget<W>(&mut self, widget: W, area: Rect) -> bool
354 where
355 W: Widget,
356 {
357 if area.intersects(self.buffer.area) {
358 widget.render(area, self.buffer());
360 true
361 } else {
362 false
363 }
364 }
365
366 #[inline(always)]
369 #[allow(deprecated)]
370 pub fn render<W, S>(&mut self, widget: W, area: Rect, state: &mut S) -> bool
371 where
372 W: StatefulWidget<State = S>,
373 S: RelocatableState,
374 {
375 if area.intersects(self.buffer.area) {
376 widget.render(area, self.buffer(), state);
378 state.relocate(self.shift(), self.widget_area);
380 true
381 } else {
382 state.relocate_hidden();
383 false
384 }
385 }
386
387 #[inline(always)]
391 #[allow(deprecated)]
392 pub fn render_popup<W, S>(&mut self, widget: W, area: Rect, state: &mut S) -> bool
393 where
394 W: StatefulWidget<State = S>,
395 S: RelocatableState,
396 {
397 if area.intersects(self.buffer.area) {
398 widget.render(area, self.buffer(), state);
400 state.relocate_popup(self.shift(), self.widget_area);
402 true
403 } else {
404 state.relocate_popup_hidden();
405 false
406 }
407 }
408
409 pub fn layout(&self) -> Rect {
411 self.buffer.area
412 }
413
414 pub fn is_visible_area(&self, area: Rect) -> bool {
416 area.intersects(self.buffer.area)
417 }
418
419 pub fn shift(&self) -> (i16, i16) {
424 (
425 self.widget_area.x as i16 - self.offset.x as i16,
426 self.widget_area.y as i16 - self.offset.y as i16,
427 )
428 }
429
430 pub fn clip(&self) -> Rect {
432 self.widget_area
433 }
434
435 #[deprecated(
438 since = "2.0.0",
439 note = "wrong api, use is_visible_area() or locate_area2()"
440 )]
441 pub fn locate_area(&self, area: Rect) -> Rect {
442 area
443 }
444
445 pub fn locate_area2(&self, area: Rect) -> Option<Rect> {
447 if area.intersects(self.buffer.area) {
448 Some(area)
449 } else {
450 None
451 }
452 }
453
454 #[deprecated(since = "2.0.0", note = "wrong api, use relocate2() instead")]
460 #[allow(deprecated)]
461 pub fn relocate<S>(&self, state: &mut S)
462 where
463 S: RelocatableState,
464 {
465 state.relocate(self.shift(), self.clip());
466 }
467
468 #[allow(deprecated)]
477 pub fn relocate2<S>(&self, area: Rect, state: &mut S)
478 where
479 S: RelocatableState,
480 {
481 if self.is_visible_area(area) {
482 state.relocate(self.shift(), self.widget_area);
483 } else {
484 state.relocate_hidden();
485 }
486 }
487
488 #[deprecated(since = "2.0.0", note = "bad api, use relocate2() instead")]
494 pub fn hidden<S>(&self, state: &mut S)
495 where
496 S: RelocatableState,
497 {
498 state.relocate_hidden();
499 }
500
501 pub fn buffer(&mut self) -> &mut Buffer {
506 &mut self.buffer
507 }
508
509 #[deprecated(since = "2.3.0", note = "use finish() instead")]
513 pub fn into_widget(mut self) -> ViewWidget<'a> {
514 self.destruct = true;
515
516 ViewWidget {
517 block: mem::take(&mut self.block),
518 hscroll: mem::take(&mut self.hscroll),
519 vscroll: mem::take(&mut self.vscroll),
520 offset: self.offset,
521 buffer: mem::take(&mut self.buffer),
522 style: self.style,
523 }
524 }
525
526 pub fn finish(mut self, tgt_buf: &mut Buffer, state: &mut ViewState) {
528 self.destruct = true;
529
530 ScrollArea::new()
531 .style(self.style)
532 .block(self.block.as_ref())
533 .h_scroll(self.hscroll.as_ref())
534 .v_scroll(self.vscroll.as_ref())
535 .render(
536 state.area,
537 tgt_buf,
538 &mut ScrollAreaState::new()
539 .h_scroll(&mut state.hscroll)
540 .v_scroll(&mut state.vscroll),
541 );
542
543 let v_src = Rect::new(
544 self.offset.x,
545 self.offset.y,
546 state.widget_area.width,
547 state.widget_area.height,
548 );
549 if !v_src.intersects(self.buffer.area) {
550 return;
551 }
552 let mut src = self.buffer.area.intersection(v_src);
553
554 let mut view = state.widget_area;
555 if src.x > self.offset.x {
556 view.x += src.x - self.offset.x;
557 view.width -= src.x - self.offset.x;
558 }
559 if src.y > self.offset.y {
560 view.y += src.y - self.offset.y;
561 view.height -= src.y - self.offset.y;
562 }
563
564 let width = min(view.width, src.width);
565 let height = min(view.height, src.height);
566
567 src.width = width;
568 view.width = width;
569 src.height = height;
570 view.height = height;
571
572 for y in 0..src.height {
573 let src_0 = self.buffer.index_of(src.x, src.y + y);
574 let src_len = src.width as usize;
575 let view_0 = tgt_buf.index_of(view.x, view.y + y);
576 let view_len = view.width as usize;
577 assert_eq!(src_len, view_len);
578
579 let src = &self.buffer.content[src_0..src_0 + src_len];
580 let tgt = &mut tgt_buf.content[view_0..view_0 + view_len];
581 tgt.clone_from_slice(src);
582 }
583
584 state.buffer = Some(mem::take(&mut self.buffer));
586 }
587}
588
589impl StatefulWidget for ViewWidget<'_> {
590 type State = ViewState;
591
592 fn render(mut self, area: Rect, tgt_buf: &mut Buffer, state: &mut Self::State) {
593 if cfg!(debug_assertions) {
594 if area != state.area {
595 panic!(
596 "ViewWidget::render() must be called with the same area as View::into_buffer()."
597 )
598 }
599 }
600
601 ScrollArea::new()
602 .style(self.style)
603 .block(self.block.as_ref())
604 .h_scroll(self.hscroll.as_ref())
605 .v_scroll(self.vscroll.as_ref())
606 .render(
607 state.area,
608 tgt_buf,
609 &mut ScrollAreaState::new()
610 .h_scroll(&mut state.hscroll)
611 .v_scroll(&mut state.vscroll),
612 );
613
614 let v_src = Rect::new(
615 self.offset.x,
616 self.offset.y,
617 state.widget_area.width,
618 state.widget_area.height,
619 );
620 if !v_src.intersects(self.buffer.area) {
621 return;
622 }
623 let mut src = self.buffer.area.intersection(v_src);
624
625 let mut view = state.widget_area;
626 if src.x > self.offset.x {
627 view.x += src.x - self.offset.x;
628 view.width -= src.x - self.offset.x;
629 }
630 if src.y > self.offset.y {
631 view.y += src.y - self.offset.y;
632 view.height -= src.y - self.offset.y;
633 }
634
635 let width = min(view.width, src.width);
636 let height = min(view.height, src.height);
637
638 src.width = width;
639 view.width = width;
640 src.height = height;
641 view.height = height;
642
643 for y in 0..src.height {
644 let src_0 = self.buffer.index_of(src.x, src.y + y);
645 let src_len = src.width as usize;
646 let view_0 = tgt_buf.index_of(view.x, view.y + y);
647 let view_len = view.width as usize;
648 assert_eq!(src_len, view_len);
649
650 let src = &self.buffer.content[src_0..src_0 + src_len];
651 let tgt = &mut tgt_buf.content[view_0..view_0 + view_len];
652 tgt.clone_from_slice(src);
653 }
654
655 state.buffer = Some(mem::take(&mut self.buffer));
657 }
658}
659
660impl Default for ViewStyle {
661 fn default() -> Self {
662 Self {
663 style: Default::default(),
664 block: Default::default(),
665 border_style: Default::default(),
666 title_style: Default::default(),
667 scroll: Default::default(),
668 non_exhaustive: NonExhaustive,
669 }
670 }
671}
672
673impl HasFocus for ViewState {
674 fn build(&self, builder: &mut FocusBuilder) {
675 builder.leaf_widget(self);
676 }
677
678 fn focus(&self) -> FocusFlag {
679 self.focus.clone()
680 }
681
682 fn area(&self) -> Rect {
683 self.area
684 }
685}
686
687impl RelocatableState for ViewState {
688 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
689 self.area.relocate(shift, clip);
690 self.widget_area.relocate(shift, clip);
691 self.hscroll.relocate(shift, clip);
692 self.vscroll.relocate(shift, clip);
693 }
694}
695
696impl ViewState {
697 pub fn new() -> Self {
698 Self::default()
699 }
700
701 pub fn show_area(&mut self, area: Rect) {
703 self.hscroll.scroll_to_pos(area.x as usize);
704 self.vscroll.scroll_to_pos(area.y as usize);
705 }
706}
707
708impl ViewState {
709 pub fn vertical_offset(&self) -> usize {
710 self.vscroll.offset()
711 }
712
713 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
714 let old = self.vscroll.offset();
715 self.vscroll.set_offset(offset);
716 old != self.vscroll.offset()
717 }
718
719 pub fn vertical_page_len(&self) -> usize {
720 self.vscroll.page_len()
721 }
722
723 pub fn horizontal_offset(&self) -> usize {
724 self.hscroll.offset()
725 }
726
727 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
728 let old = self.hscroll.offset();
729 self.hscroll.set_offset(offset);
730 old != self.hscroll.offset()
731 }
732
733 pub fn horizontal_page_len(&self) -> usize {
734 self.hscroll.page_len()
735 }
736
737 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
738 self.hscroll.scroll_to_pos(pos)
739 }
740
741 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
742 self.vscroll.scroll_to_pos(pos)
743 }
744
745 pub fn scroll_up(&mut self, delta: usize) -> bool {
746 self.vscroll.scroll_up(delta)
747 }
748
749 pub fn scroll_down(&mut self, delta: usize) -> bool {
750 self.vscroll.scroll_down(delta)
751 }
752
753 pub fn scroll_left(&mut self, delta: usize) -> bool {
754 self.hscroll.scroll_left(delta)
755 }
756
757 pub fn scroll_right(&mut self, delta: usize) -> bool {
758 self.hscroll.scroll_right(delta)
759 }
760}
761
762impl HandleEvent<Event, Regular, Outcome> for ViewState {
763 fn handle(&mut self, event: &Event, _qualifier: Regular) -> Outcome {
764 let r = if self.is_focused() {
765 match event {
766 ct_event!(keycode press Left) => self.scroll_left(self.hscroll.scroll_by()).into(),
767 ct_event!(keycode press Right) => {
768 self.scroll_right(self.hscroll.scroll_by()).into()
769 }
770 ct_event!(keycode press Up) => self.scroll_up(self.vscroll.scroll_by()).into(),
771 ct_event!(keycode press Down) => self.scroll_down(self.vscroll.scroll_by()).into(),
772
773 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
774 ct_event!(keycode press PageDown) => {
775 self.scroll_down(self.vscroll.page_len()).into()
776 }
777 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
778 ct_event!(keycode press End) => {
779 self.vertical_scroll_to(self.vscroll.max_offset()).into()
780 }
781
782 ct_event!(keycode press ALT-PageUp) => {
783 self.scroll_left(self.hscroll.page_len()).into()
784 }
785 ct_event!(keycode press ALT-PageDown) => {
786 self.scroll_right(self.hscroll.page_len()).into()
787 }
788 ct_event!(keycode press ALT-Home) => self.horizontal_scroll_to(0).into(),
789 ct_event!(keycode press ALT-End) => {
790 self.horizontal_scroll_to(self.hscroll.max_offset()).into()
791 }
792 _ => Outcome::Continue,
793 }
794 } else {
795 Outcome::Continue
796 };
797
798 r.or_else(|| self.handle(event, MouseOnly))
799 }
800}
801
802impl HandleEvent<Event, MouseOnly, Outcome> for ViewState {
803 fn handle(&mut self, event: &Event, _qualifier: MouseOnly) -> Outcome {
804 let mut sas = ScrollAreaState::new()
805 .area(self.widget_area)
806 .h_scroll(&mut self.hscroll)
807 .v_scroll(&mut self.vscroll);
808 match sas.handle(event, MouseOnly) {
809 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
810 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
811 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
812 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
813 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
814 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
815 r => r.into(),
816 }
817 }
818}
819
820pub fn handle_events(state: &mut ViewState, focus: bool, event: &Event) -> Outcome {
824 state.focus.set(focus);
825 HandleEvent::handle(state, event, Regular)
826}
827
828pub fn handle_mouse_events(state: &mut ViewState, event: &Event) -> Outcome {
830 HandleEvent::handle(state, event, MouseOnly)
831}