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 style: Style,
73 block: Option<Block<'a>>,
74 hscroll: Option<Scroll<'a>>,
75 vscroll: Option<Scroll<'a>>,
76}
77
78#[derive(Debug)]
85pub struct ViewBuffer<'a> {
86 offset: Position,
88 buffer: Buffer,
89
90 widget_area: Rect,
92
93 style: Style,
94 block: Option<Block<'a>>,
95 hscroll: Option<Scroll<'a>>,
96 vscroll: Option<Scroll<'a>>,
97
98 destruct: bool,
99}
100
101#[derive(Debug)]
104pub struct ViewWidget<'a> {
105 offset: Position,
107 buffer: Buffer,
108
109 style: Style,
110 block: Option<Block<'a>>,
111 hscroll: Option<Scroll<'a>>,
112 vscroll: Option<Scroll<'a>>,
113}
114
115#[derive(Debug, Clone)]
117pub struct ViewStyle {
118 pub style: Style,
119 pub block: Option<Block<'static>>,
120 pub scroll: Option<ScrollStyle>,
121 pub non_exhaustive: NonExhaustive,
122}
123
124#[derive(Debug, Default, Clone)]
126pub struct ViewState {
127 pub area: Rect,
130 pub widget_area: Rect,
133
134 pub layout: Rect,
137
138 pub hscroll: ScrollState,
141 pub vscroll: ScrollState,
144
145 pub focus: FocusFlag,
148
149 buffer: Option<Buffer>,
151}
152
153impl<'a> View<'a> {
154 pub fn new() -> Self {
156 Self::default()
157 }
158
159 pub fn layout(mut self, area: Rect) -> Self {
161 self.view_layout = Some(area);
162 self
163 }
164
165 pub fn view_width(mut self, width: u16) -> Self {
167 self.view_width = Some(width);
168 self
169 }
170
171 pub fn view_height(mut self, height: u16) -> Self {
173 self.view_height = Some(height);
174 self
175 }
176 pub fn view_x(mut self, x: u16) -> Self {
178 self.view_x = Some(x);
179 self
180 }
181
182 pub fn view_y(mut self, y: u16) -> Self {
184 self.view_y = Some(y);
185 self
186 }
187
188 pub fn view_size(mut self, view: Size) -> Self {
190 self.view_width = Some(view.width);
191 self.view_height = Some(view.height);
192 self
193 }
194
195 pub fn style(mut self, style: Style) -> Self {
197 self.style = style;
198 self.block = self.block.map(|v| v.style(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: ViewStyle) -> Self {
229 self.style = styles.style;
230 if styles.block.is_some() {
231 self.block = styles.block;
232 }
233 if let Some(styles) = styles.scroll {
234 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
235 self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
236 }
237 self.block = self.block.map(|v| v.style(styles.style));
238 self
239 }
240
241 #[allow(deprecated)]
243 pub fn layout_size(&self, area: Rect, state: &ViewState) -> u16 {
244 self.inner(area, state).width
245 }
246
247 #[allow(deprecated)]
249 pub fn layout_width(&self, area: Rect, state: &ViewState) -> u16 {
250 self.inner(area, state).width
251 }
252
253 #[deprecated(since = "2.3.0", note = "use layout_size instead")]
255 pub fn inner(&self, area: Rect, state: &ViewState) -> Rect {
256 let sa = ScrollArea::new()
257 .block(self.block.as_ref())
258 .h_scroll(self.hscroll.as_ref())
259 .v_scroll(self.vscroll.as_ref());
260 sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
261 }
262
263 pub fn into_buffer(self, area: Rect, state: &mut ViewState) -> ViewBuffer<'a> {
265 state.area = area;
266
267 let sa = ScrollArea::new()
268 .block(self.block.as_ref())
269 .h_scroll(self.hscroll.as_ref())
270 .v_scroll(self.vscroll.as_ref());
271 state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
272
273 state.layout = if let Some(layout) = self.view_layout {
274 layout
275 } else {
276 let mut layout = Rect::new(0, 0, state.widget_area.width, state.widget_area.height);
277 if let Some(x) = self.view_x {
278 layout.x = x;
279 }
280 if let Some(y) = self.view_y {
281 layout.y = y;
282 }
283 if let Some(width) = self.view_width {
284 layout.width = width;
285 }
286 if let Some(height) = self.view_height {
287 layout.height = height;
288 }
289 layout
290 };
291
292 state
293 .hscroll
294 .set_max_offset(state.layout.width.saturating_sub(state.widget_area.width) as usize);
295 state.hscroll.set_page_len(state.widget_area.width as usize);
296 state
297 .vscroll
298 .set_page_len(state.widget_area.height as usize);
299 state
300 .vscroll
301 .set_max_offset(state.layout.height.saturating_sub(state.widget_area.height) as usize);
302
303 let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
306
307 let mut buffer = if let Some(mut buffer) = state.buffer.take() {
309 buffer.reset();
310 buffer.resize(state.layout);
311 buffer
312 } else {
313 Buffer::empty(state.layout)
314 };
315 buffer.set_style(state.layout, self.style);
316
317 ViewBuffer {
318 offset,
319 buffer,
320 widget_area: state.widget_area,
321 style: self.style,
322 block: self.block,
323 hscroll: self.hscroll,
324 vscroll: self.vscroll,
325 destruct: false,
326 }
327 }
328}
329
330impl<'a> Drop for ViewBuffer<'a> {
331 fn drop(&mut self) {
332 if !self.destruct {
333 panic!("ViewBuffer: Must be used. Call finish(..)");
334 }
335 }
336}
337
338impl<'a> ViewBuffer<'a> {
339 #[inline(always)]
341 pub fn render_widget<W>(&mut self, widget: W, area: Rect) -> bool
342 where
343 W: Widget,
344 {
345 if area.intersects(self.buffer.area) {
346 widget.render(area, self.buffer());
348 true
349 } else {
350 false
351 }
352 }
353
354 #[inline(always)]
357 #[allow(deprecated)]
358 pub fn render<W, S>(&mut self, widget: W, area: Rect, state: &mut S) -> bool
359 where
360 W: StatefulWidget<State = S>,
361 S: RelocatableState,
362 {
363 if area.intersects(self.buffer.area) {
364 widget.render(area, self.buffer(), state);
366 state.relocate(self.shift(), self.widget_area);
368 true
369 } else {
370 state.relocate_hidden();
371 false
372 }
373 }
374
375 #[inline(always)]
379 #[allow(deprecated)]
380 pub fn render_popup<W, S>(&mut self, widget: W, area: Rect, state: &mut S) -> bool
381 where
382 W: StatefulWidget<State = S>,
383 S: RelocatableState,
384 {
385 if area.intersects(self.buffer.area) {
386 widget.render(area, self.buffer(), state);
388 state.relocate_popup(self.shift(), self.widget_area);
390 true
391 } else {
392 state.relocate_popup_hidden();
393 false
394 }
395 }
396
397 pub fn layout(&self) -> Rect {
399 self.buffer.area
400 }
401
402 pub fn is_visible_area(&self, area: Rect) -> bool {
404 area.intersects(self.buffer.area)
405 }
406
407 #[deprecated(
409 since = "2.0.0",
410 note = "should not be public. use relocate2() instead."
411 )]
412 pub fn shift(&self) -> (i16, i16) {
413 (
414 self.widget_area.x as i16 - self.offset.x as i16,
415 self.widget_area.y as i16 - self.offset.y as i16,
416 )
417 }
418
419 #[deprecated(
422 since = "2.0.0",
423 note = "wrong api, use is_visible_area() or locate_area2()"
424 )]
425 pub fn locate_area(&self, area: Rect) -> Rect {
426 area
427 }
428
429 pub fn locate_area2(&self, area: Rect) -> Option<Rect> {
431 if area.intersects(self.buffer.area) {
432 Some(area)
433 } else {
434 None
435 }
436 }
437
438 #[deprecated(since = "2.0.0", note = "wrong api, use relocate2() instead")]
444 #[allow(deprecated)]
445 pub fn relocate<S>(&self, state: &mut S)
446 where
447 S: RelocatableState,
448 {
449 state.relocate(self.shift(), self.widget_area);
450 }
451
452 #[allow(deprecated)]
458 pub fn relocate2<S>(&self, area: Rect, state: &mut S)
459 where
460 S: RelocatableState,
461 {
462 if self.is_visible_area(area) {
463 state.relocate(self.shift(), self.widget_area);
464 } else {
465 state.relocate_hidden();
466 }
467 }
468
469 #[deprecated(since = "2.0.0", note = "bad api, use relocate2() instead")]
475 pub fn hidden<S>(&self, state: &mut S)
476 where
477 S: RelocatableState,
478 {
479 state.relocate_hidden();
480 }
481
482 pub fn buffer(&mut self) -> &mut Buffer {
487 &mut self.buffer
488 }
489
490 #[deprecated(since = "2.3.0", note = "use finish() instead")]
494 pub fn into_widget(mut self) -> ViewWidget<'a> {
495 self.destruct = true;
496
497 ViewWidget {
498 block: mem::take(&mut self.block),
499 hscroll: mem::take(&mut self.hscroll),
500 vscroll: mem::take(&mut self.vscroll),
501 offset: self.offset,
502 buffer: mem::take(&mut self.buffer),
503 style: self.style,
504 }
505 }
506
507 pub fn finish(mut self, tgt_buf: &mut Buffer, state: &mut ViewState) {
509 self.destruct = true;
510
511 ScrollArea::new()
512 .style(self.style)
513 .block(self.block.as_ref())
514 .h_scroll(self.hscroll.as_ref())
515 .v_scroll(self.vscroll.as_ref())
516 .render(
517 state.area,
518 tgt_buf,
519 &mut ScrollAreaState::new()
520 .h_scroll(&mut state.hscroll)
521 .v_scroll(&mut state.vscroll),
522 );
523
524 let src_area = self.buffer.area;
525 let tgt_area = state.widget_area;
526 let offset = self.offset;
527
528 let off_x0 = src_area.x.saturating_sub(offset.x);
530 let off_y0 = src_area.y.saturating_sub(offset.y);
531 let cut_x0 = offset.x.saturating_sub(src_area.x);
533 let cut_y0 = offset.y.saturating_sub(src_area.y);
534
535 let len_src = src_area.width.saturating_sub(cut_x0);
537 let len_tgt = tgt_area.width.saturating_sub(off_x0);
538 let len = min(len_src, len_tgt);
539
540 let height_src = src_area.height.saturating_sub(cut_y0);
542 let height_tgt = tgt_area.height.saturating_sub(off_y0);
543 let height = min(height_src, height_tgt);
544
545 for y in 0..height {
559 let src_0 = self
560 .buffer
561 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
562 let tgt_0 = tgt_buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
563
564 let src = &self.buffer.content[src_0..src_0 + len as usize];
565 let tgt = &mut tgt_buf.content[tgt_0..tgt_0 + len as usize];
566 tgt.clone_from_slice(src);
567 }
568
569 state.buffer = Some(mem::take(&mut self.buffer));
571 }
572}
573
574impl StatefulWidget for ViewWidget<'_> {
575 type State = ViewState;
576
577 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
578 if cfg!(debug_assertions) {
579 if area != state.area {
580 panic!(
581 "ViewWidget::render() must be called with the same area as View::into_buffer()."
582 )
583 }
584 }
585 ScrollArea::new()
586 .style(self.style)
587 .block(self.block.as_ref())
588 .h_scroll(self.hscroll.as_ref())
589 .v_scroll(self.vscroll.as_ref())
590 .render(
591 state.area,
592 buf,
593 &mut ScrollAreaState::new()
594 .h_scroll(&mut state.hscroll)
595 .v_scroll(&mut state.vscroll),
596 );
597
598 let src_area = self.buffer.area;
599 let tgt_area = state.widget_area;
600 let offset = self.offset;
601
602 let off_x0 = src_area.x.saturating_sub(offset.x);
604 let off_y0 = src_area.y.saturating_sub(offset.y);
605 let cut_x0 = offset.x.saturating_sub(src_area.x);
607 let cut_y0 = offset.y.saturating_sub(src_area.y);
608
609 let len_src = src_area.width.saturating_sub(cut_x0);
611 let len_tgt = tgt_area.width.saturating_sub(off_x0);
612 let len = min(len_src, len_tgt);
613
614 let height_src = src_area.height.saturating_sub(cut_y0);
616 let height_tgt = tgt_area.height.saturating_sub(off_y0);
617 let height = min(height_src, height_tgt);
618
619 for y in 0..height {
633 let src_0 = self
634 .buffer
635 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
636 let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
637
638 let src = &self.buffer.content[src_0..src_0 + len as usize];
639 let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
640 tgt.clone_from_slice(src);
641 }
642
643 state.buffer = Some(self.buffer);
645 }
646}
647
648impl Default for ViewStyle {
649 fn default() -> Self {
650 Self {
651 style: Default::default(),
652 block: None,
653 scroll: None,
654 non_exhaustive: NonExhaustive,
655 }
656 }
657}
658
659impl HasFocus for ViewState {
660 fn build(&self, builder: &mut FocusBuilder) {
661 builder.leaf_widget(self);
662 }
663
664 fn focus(&self) -> FocusFlag {
665 self.focus.clone()
666 }
667
668 fn area(&self) -> Rect {
669 self.area
670 }
671}
672
673impl ViewState {
674 pub fn new() -> Self {
675 Self::default()
676 }
677
678 pub fn show_area(&mut self, area: Rect) {
680 self.hscroll.scroll_to_pos(area.x as usize);
681 self.vscroll.scroll_to_pos(area.y as usize);
682 }
683}
684
685impl ViewState {
686 pub fn vertical_offset(&self) -> usize {
687 self.vscroll.offset()
688 }
689
690 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
691 let old = self.vscroll.offset();
692 self.vscroll.set_offset(offset);
693 old != self.vscroll.offset()
694 }
695
696 pub fn vertical_page_len(&self) -> usize {
697 self.vscroll.page_len()
698 }
699
700 pub fn horizontal_offset(&self) -> usize {
701 self.hscroll.offset()
702 }
703
704 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
705 let old = self.hscroll.offset();
706 self.hscroll.set_offset(offset);
707 old != self.hscroll.offset()
708 }
709
710 pub fn horizontal_page_len(&self) -> usize {
711 self.hscroll.page_len()
712 }
713
714 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
715 self.hscroll.scroll_to_pos(pos)
716 }
717
718 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
719 self.vscroll.scroll_to_pos(pos)
720 }
721
722 pub fn scroll_up(&mut self, delta: usize) -> bool {
723 self.vscroll.scroll_up(delta)
724 }
725
726 pub fn scroll_down(&mut self, delta: usize) -> bool {
727 self.vscroll.scroll_down(delta)
728 }
729
730 pub fn scroll_left(&mut self, delta: usize) -> bool {
731 self.hscroll.scroll_left(delta)
732 }
733
734 pub fn scroll_right(&mut self, delta: usize) -> bool {
735 self.hscroll.scroll_right(delta)
736 }
737}
738
739impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ViewState {
740 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
741 let r = if self.is_focused() {
742 match event {
743 ct_event!(keycode press Left) => self.scroll_left(self.hscroll.scroll_by()).into(),
744 ct_event!(keycode press Right) => {
745 self.scroll_right(self.hscroll.scroll_by()).into()
746 }
747 ct_event!(keycode press Up) => self.scroll_up(self.vscroll.scroll_by()).into(),
748 ct_event!(keycode press Down) => self.scroll_down(self.vscroll.scroll_by()).into(),
749
750 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
751 ct_event!(keycode press PageDown) => {
752 self.scroll_down(self.vscroll.page_len()).into()
753 }
754 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
755 ct_event!(keycode press End) => {
756 self.vertical_scroll_to(self.vscroll.max_offset()).into()
757 }
758
759 ct_event!(keycode press ALT-PageUp) => {
760 self.scroll_left(self.hscroll.page_len()).into()
761 }
762 ct_event!(keycode press ALT-PageDown) => {
763 self.scroll_right(self.hscroll.page_len()).into()
764 }
765 ct_event!(keycode press ALT-Home) => self.horizontal_scroll_to(0).into(),
766 ct_event!(keycode press ALT-End) => {
767 self.horizontal_scroll_to(self.hscroll.max_offset()).into()
768 }
769 _ => Outcome::Continue,
770 }
771 } else {
772 Outcome::Continue
773 };
774
775 r.or_else(|| self.handle(event, MouseOnly))
776 }
777}
778
779impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ViewState {
780 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
781 let mut sas = ScrollAreaState::new()
782 .area(self.widget_area)
783 .h_scroll(&mut self.hscroll)
784 .v_scroll(&mut self.vscroll);
785 match sas.handle(event, MouseOnly) {
786 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
787 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
788 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
789 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
790 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
791 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
792 r => r.into(),
793 }
794 }
795}
796
797pub fn handle_events(
801 state: &mut ViewState,
802 focus: bool,
803 event: &crossterm::event::Event,
804) -> Outcome {
805 state.focus.set(focus);
806 HandleEvent::handle(state, event, Regular)
807}
808
809pub fn handle_mouse_events(state: &mut ViewState, event: &crossterm::event::Event) -> Outcome {
811 HandleEvent::handle(state, event, MouseOnly)
812}