1use std::cmp::min;
51
52use crate::_private::NonExhaustive;
53use crate::event::ScrollOutcome;
54use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Outcome, Regular, ct_event};
55use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
56use rat_reloc::RelocatableState;
57use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
58use ratatui::buffer::Buffer;
59use ratatui::layout::{Position, Rect, Size};
60use ratatui::style::Style;
61use ratatui::widgets::Block;
62use ratatui::widgets::{StatefulWidget, Widget};
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
99#[derive(Debug)]
101pub struct ViewWidget<'a> {
102 offset: Position,
104 buffer: Buffer,
105
106 style: Style,
107 block: Option<Block<'a>>,
108 hscroll: Option<Scroll<'a>>,
109 vscroll: Option<Scroll<'a>>,
110}
111
112#[derive(Debug)]
114pub struct ViewStyle {
115 pub style: Style,
116 pub block: Option<Block<'static>>,
117 pub scroll: Option<ScrollStyle>,
118 pub non_exhaustive: NonExhaustive,
119}
120
121#[derive(Debug, Default, Clone)]
123pub struct ViewState {
124 pub area: Rect,
127 pub widget_area: Rect,
130
131 pub layout: Rect,
134
135 pub hscroll: ScrollState,
138 pub vscroll: ScrollState,
141
142 pub focus: FocusFlag,
145
146 buffer: Option<Buffer>,
148}
149
150impl<'a> View<'a> {
151 pub fn new() -> Self {
153 Self::default()
154 }
155
156 pub fn layout(mut self, area: Rect) -> Self {
158 self.view_layout = Some(area);
159 self
160 }
161
162 pub fn view_width(mut self, width: u16) -> Self {
164 self.view_width = Some(width);
165 self
166 }
167
168 pub fn view_height(mut self, height: u16) -> Self {
170 self.view_height = Some(height);
171 self
172 }
173 pub fn view_x(mut self, x: u16) -> Self {
175 self.view_x = Some(x);
176 self
177 }
178
179 pub fn view_y(mut self, y: u16) -> Self {
181 self.view_y = Some(y);
182 self
183 }
184
185 pub fn view_size(mut self, view: Size) -> Self {
187 self.view_width = Some(view.width);
188 self.view_height = Some(view.height);
189 self
190 }
191
192 pub fn style(mut self, style: Style) -> Self {
194 self.style = style;
195 self.block = self.block.map(|v| v.style(style));
196 self
197 }
198
199 pub fn block(mut self, block: Block<'a>) -> Self {
201 self.block = Some(block);
202 self
203 }
204
205 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
207 self.hscroll = Some(scroll.clone().override_horizontal());
208 self.vscroll = Some(scroll.override_vertical());
209 self
210 }
211
212 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
214 self.hscroll = Some(scroll.override_horizontal());
215 self
216 }
217
218 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
220 self.vscroll = Some(scroll.override_vertical());
221 self
222 }
223
224 pub fn styles(mut self, styles: ViewStyle) -> Self {
226 self.style = styles.style;
227 if styles.block.is_some() {
228 self.block = styles.block;
229 }
230 if let Some(styles) = styles.scroll {
231 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
232 self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
233 }
234 self.block = self.block.map(|v| v.style(styles.style));
235 self
236 }
237
238 pub fn layout_width(&self, area: Rect, state: &ViewState) -> u16 {
240 self.inner(area, state).width
241 }
242
243 pub fn inner(&self, area: Rect, state: &ViewState) -> Rect {
245 let sa = ScrollArea::new()
246 .block(self.block.as_ref())
247 .h_scroll(self.hscroll.as_ref())
248 .v_scroll(self.vscroll.as_ref());
249 sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
250 }
251
252 pub fn into_buffer(self, area: Rect, state: &mut ViewState) -> ViewBuffer<'a> {
254 state.area = area;
255
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 state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
261
262 state.layout = if let Some(layout) = self.view_layout {
263 layout
264 } else {
265 let mut layout = Rect::new(0, 0, state.widget_area.width, state.widget_area.height);
266 if let Some(x) = self.view_x {
267 layout.x = x;
268 }
269 if let Some(y) = self.view_y {
270 layout.y = y;
271 }
272 if let Some(width) = self.view_width {
273 layout.width = width;
274 }
275 if let Some(height) = self.view_height {
276 layout.height = height;
277 }
278 layout
279 };
280
281 state
282 .hscroll
283 .set_max_offset(state.layout.width.saturating_sub(state.widget_area.width) as usize);
284 state.hscroll.set_page_len(state.widget_area.width as usize);
285 state
286 .vscroll
287 .set_page_len(state.widget_area.height as usize);
288 state
289 .vscroll
290 .set_max_offset(state.layout.height.saturating_sub(state.widget_area.height) as usize);
291
292 let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
295
296 let mut buffer = if let Some(mut buffer) = state.buffer.take() {
298 buffer.reset();
299 buffer.resize(state.layout);
300 buffer
301 } else {
302 Buffer::empty(state.layout)
303 };
304 buffer.set_style(state.layout, self.style);
305
306 ViewBuffer {
307 offset,
308 buffer,
309 widget_area: state.widget_area,
310 style: self.style,
311 block: self.block,
312 hscroll: self.hscroll,
313 vscroll: self.vscroll,
314 }
315 }
316}
317
318impl<'a> ViewBuffer<'a> {
319 #[inline(always)]
321 pub fn render_widget<W>(&mut self, widget: W, area: Rect)
322 where
323 W: Widget,
324 {
325 if area.intersects(self.buffer.area) {
326 widget.render(area, self.buffer());
328 }
329 }
330
331 #[inline(always)]
334 #[allow(deprecated)]
335 pub fn render<W, S>(&mut self, widget: W, area: Rect, state: &mut S)
336 where
337 W: StatefulWidget<State = S>,
338 S: RelocatableState,
339 {
340 if area.intersects(self.buffer.area) {
341 widget.render(area, self.buffer(), state);
343 state.relocate(self.shift(), self.widget_area);
345 } else {
346 state.relocate_hidden();
347 }
348 }
349
350 pub fn layout(&self) -> Rect {
352 self.buffer.area
353 }
354
355 pub fn is_visible_area(&self, area: Rect) -> bool {
357 area.intersects(self.buffer.area)
358 }
359
360 #[deprecated(
362 since = "2.0.0",
363 note = "should not be public. use relocate2() instead."
364 )]
365 pub fn shift(&self) -> (i16, i16) {
366 (
367 self.widget_area.x as i16 - self.offset.x as i16,
368 self.widget_area.y as i16 - self.offset.y as i16,
369 )
370 }
371
372 #[deprecated(
375 since = "2.0.0",
376 note = "wrong api, use is_visible_area() or locate_area2()"
377 )]
378 pub fn locate_area(&self, area: Rect) -> Rect {
379 area
380 }
381
382 #[deprecated(
384 since = "2.0.0",
385 note = "wrong api, use is_visible_area() or locate_area2()"
386 )]
387 pub fn locate_area2(&self, area: Rect) -> Option<Rect> {
388 if area.intersects(self.buffer.area) {
389 Some(area)
390 } else {
391 None
392 }
393 }
394
395 #[deprecated(since = "2.0.0", note = "wrong api, use relocate2() instead")]
401 #[allow(deprecated)]
402 pub fn relocate<S>(&self, state: &mut S)
403 where
404 S: RelocatableState,
405 {
406 state.relocate(self.shift(), self.widget_area);
407 }
408
409 #[deprecated(since = "2.0.0", note = "wrong api, use relocate2() instead")]
418 #[allow(deprecated)]
419 pub fn relocate2<S>(&self, area: Rect, state: &mut S)
420 where
421 S: RelocatableState,
422 {
423 if self.is_visible_area(area) {
424 state.relocate(self.shift(), self.widget_area);
425 } else {
426 state.relocate_hidden();
427 }
428 }
429
430 #[deprecated(since = "2.0.0", note = "bad api, use relocate2() instead")]
436 pub fn hidden<S>(&self, state: &mut S)
437 where
438 S: RelocatableState,
439 {
440 state.relocate_hidden();
441 }
442
443 pub fn buffer(&mut self) -> &mut Buffer {
448 &mut self.buffer
449 }
450
451 pub fn into_widget(self) -> ViewWidget<'a> {
455 ViewWidget {
456 block: self.block,
457 hscroll: self.hscroll,
458 vscroll: self.vscroll,
459 offset: self.offset,
460 buffer: self.buffer,
461 style: self.style,
462 }
463 }
464}
465
466impl StatefulWidget for ViewWidget<'_> {
467 type State = ViewState;
468
469 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
470 if cfg!(debug_assertions) {
471 if area != state.area {
472 panic!(
473 "ViewWidget::render() must be called with the same area as View::into_buffer()."
474 )
475 }
476 }
477 ScrollArea::new()
478 .style(self.style)
479 .block(self.block.as_ref())
480 .h_scroll(self.hscroll.as_ref())
481 .v_scroll(self.vscroll.as_ref())
482 .render(
483 state.area,
484 buf,
485 &mut ScrollAreaState::new()
486 .h_scroll(&mut state.hscroll)
487 .v_scroll(&mut state.vscroll),
488 );
489
490 let src_area = self.buffer.area;
491 let tgt_area = state.widget_area;
492 let offset = self.offset;
493
494 let off_x0 = src_area.x.saturating_sub(offset.x);
496 let off_y0 = src_area.y.saturating_sub(offset.y);
497 let cut_x0 = offset.x.saturating_sub(src_area.x);
499 let cut_y0 = offset.y.saturating_sub(src_area.y);
500
501 let len_src = src_area.width.saturating_sub(cut_x0);
503 let len_tgt = tgt_area.width.saturating_sub(off_x0);
504 let len = min(len_src, len_tgt);
505
506 let height_src = src_area.height.saturating_sub(cut_y0);
508 let height_tgt = tgt_area.height.saturating_sub(off_y0);
509 let height = min(height_src, height_tgt);
510
511 for y in 0..height {
525 let src_0 = self
526 .buffer
527 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
528 let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
529
530 let src = &self.buffer.content[src_0..src_0 + len as usize];
531 let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
532 tgt.clone_from_slice(src);
533 }
534
535 state.buffer = Some(self.buffer);
537 }
538}
539
540impl Default for ViewStyle {
541 fn default() -> Self {
542 Self {
543 style: Default::default(),
544 block: None,
545 scroll: None,
546 non_exhaustive: NonExhaustive,
547 }
548 }
549}
550
551impl HasFocus for ViewState {
552 fn build(&self, builder: &mut FocusBuilder) {
553 builder.leaf_widget(self);
554 }
555
556 fn focus(&self) -> FocusFlag {
557 self.focus.clone()
558 }
559
560 fn area(&self) -> Rect {
561 self.area
562 }
563}
564
565impl ViewState {
566 pub fn new() -> Self {
567 Self::default()
568 }
569
570 pub fn show_area(&mut self, area: Rect) {
572 self.hscroll.scroll_to_pos(area.x as usize);
573 self.vscroll.scroll_to_pos(area.y as usize);
574 }
575}
576
577impl ViewState {
578 pub fn vertical_offset(&self) -> usize {
579 self.vscroll.offset()
580 }
581
582 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
583 let old = self.vscroll.offset();
584 self.vscroll.set_offset(offset);
585 old != self.vscroll.offset()
586 }
587
588 pub fn vertical_page_len(&self) -> usize {
589 self.vscroll.page_len()
590 }
591
592 pub fn horizontal_offset(&self) -> usize {
593 self.hscroll.offset()
594 }
595
596 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
597 let old = self.hscroll.offset();
598 self.hscroll.set_offset(offset);
599 old != self.hscroll.offset()
600 }
601
602 pub fn horizontal_page_len(&self) -> usize {
603 self.hscroll.page_len()
604 }
605
606 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
607 self.hscroll.scroll_to_pos(pos)
608 }
609
610 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
611 self.vscroll.scroll_to_pos(pos)
612 }
613
614 pub fn scroll_up(&mut self, delta: usize) -> bool {
615 self.vscroll.scroll_up(delta)
616 }
617
618 pub fn scroll_down(&mut self, delta: usize) -> bool {
619 self.vscroll.scroll_down(delta)
620 }
621
622 pub fn scroll_left(&mut self, delta: usize) -> bool {
623 self.hscroll.scroll_left(delta)
624 }
625
626 pub fn scroll_right(&mut self, delta: usize) -> bool {
627 self.hscroll.scroll_right(delta)
628 }
629}
630
631impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ViewState {
632 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
633 let r = if self.is_focused() {
634 match event {
635 ct_event!(keycode press Left) => self.scroll_left(self.hscroll.scroll_by()).into(),
636 ct_event!(keycode press Right) => {
637 self.scroll_right(self.hscroll.scroll_by()).into()
638 }
639 ct_event!(keycode press Up) => self.scroll_up(self.vscroll.scroll_by()).into(),
640 ct_event!(keycode press Down) => self.scroll_down(self.vscroll.scroll_by()).into(),
641
642 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
643 ct_event!(keycode press PageDown) => {
644 self.scroll_down(self.vscroll.page_len()).into()
645 }
646 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
647 ct_event!(keycode press End) => {
648 self.vertical_scroll_to(self.vscroll.max_offset()).into()
649 }
650
651 ct_event!(keycode press ALT-PageUp) => {
652 self.scroll_left(self.hscroll.page_len()).into()
653 }
654 ct_event!(keycode press ALT-PageDown) => {
655 self.scroll_right(self.hscroll.page_len()).into()
656 }
657 ct_event!(keycode press ALT-Home) => self.horizontal_scroll_to(0).into(),
658 ct_event!(keycode press ALT-End) => {
659 self.horizontal_scroll_to(self.hscroll.max_offset()).into()
660 }
661 _ => Outcome::Continue,
662 }
663 } else {
664 Outcome::Continue
665 };
666
667 r.or_else(|| self.handle(event, MouseOnly))
668 }
669}
670
671impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ViewState {
672 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
673 let mut sas = ScrollAreaState::new()
674 .area(self.widget_area)
675 .h_scroll(&mut self.hscroll)
676 .v_scroll(&mut self.vscroll);
677 match sas.handle(event, MouseOnly) {
678 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
679 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
680 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
681 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
682 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
683 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
684 r => r.into(),
685 }
686 }
687}
688
689pub fn handle_events(
693 state: &mut ViewState,
694 focus: bool,
695 event: &crossterm::event::Event,
696) -> Outcome {
697 state.focus.set(focus);
698 HandleEvent::handle(state, event, Regular)
699}
700
701pub fn handle_mouse_events(state: &mut ViewState, event: &crossterm::event::Event) -> Outcome {
703 HandleEvent::handle(state, event, MouseOnly)
704}