1use std::cmp::{max, 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 layout: Rect,
68 view_size: Option<Size>,
69 style: Style,
70 block: Option<Block<'a>>,
71 hscroll: Option<Scroll<'a>>,
72 vscroll: Option<Scroll<'a>>,
73}
74
75#[derive(Debug)]
82pub struct ViewBuffer<'a> {
83 layout: Rect,
85
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.layout = area;
159 self
160 }
161
162 pub fn view_size(mut self, view: Size) -> Self {
165 self.view_size = Some(view);
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 block(mut self, block: Block<'a>) -> Self {
178 self.block = Some(block);
179 self
180 }
181
182 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
184 self.hscroll = Some(scroll.clone().override_horizontal());
185 self.vscroll = Some(scroll.override_vertical());
186 self
187 }
188
189 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
191 self.hscroll = Some(scroll.override_horizontal());
192 self
193 }
194
195 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
197 self.vscroll = Some(scroll.override_vertical());
198 self
199 }
200
201 pub fn styles(mut self, styles: ViewStyle) -> Self {
203 self.style = styles.style;
204 if styles.block.is_some() {
205 self.block = styles.block;
206 }
207 if let Some(styles) = styles.scroll {
208 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
209 self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
210 }
211 self.block = self.block.map(|v| v.style(styles.style));
212 self
213 }
214
215 pub fn layout_width(&self, area: Rect, state: &ViewState) -> u16 {
217 self.inner(area, state).width
218 }
219
220 pub fn inner(&self, area: Rect, state: &ViewState) -> Rect {
222 let sa = ScrollArea::new()
223 .block(self.block.as_ref())
224 .h_scroll(self.hscroll.as_ref())
225 .v_scroll(self.vscroll.as_ref());
226 sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
227 }
228
229 pub fn into_buffer(self, area: Rect, state: &mut ViewState) -> ViewBuffer<'a> {
231 state.area = area;
232 state.layout = self.layout;
233
234 let sa = ScrollArea::new()
235 .block(self.block.as_ref())
236 .h_scroll(self.hscroll.as_ref())
237 .v_scroll(self.vscroll.as_ref());
238 state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
239
240 let max_x = if let Some(view_size) = self.view_size {
241 max(state.layout.right(), view_size.width)
242 } else {
243 state.layout.right()
244 };
245 let max_y = if let Some(view_size) = self.view_size {
246 max(state.layout.bottom(), view_size.height)
247 } else {
248 state.layout.bottom()
249 };
250
251 state
252 .hscroll
253 .set_max_offset(max_x.saturating_sub(state.widget_area.width) as usize);
254 state.hscroll.set_page_len(state.widget_area.width as usize);
255 state
256 .vscroll
257 .set_page_len(state.widget_area.height as usize);
258 state
259 .vscroll
260 .set_max_offset(max_y.saturating_sub(state.widget_area.height) as usize);
261
262 let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
265
266 let buffer_area = state.layout;
268 let mut buffer = if let Some(mut buffer) = state.buffer.take() {
269 buffer.reset();
270 buffer.resize(buffer_area);
271 buffer
272 } else {
273 Buffer::empty(buffer_area)
274 };
275 buffer.set_style(buffer_area, self.style);
276
277 ViewBuffer {
278 layout: self.layout,
279 offset,
280 buffer,
281 widget_area: state.widget_area,
282 style: self.style,
283 block: self.block,
284 hscroll: self.hscroll,
285 vscroll: self.vscroll,
286 }
287 }
288}
289
290impl<'a> ViewBuffer<'a> {
291 #[inline(always)]
293 pub fn render_widget<W>(&mut self, widget: W, area: Rect)
294 where
295 W: Widget,
296 {
297 if area.intersects(self.buffer.area) {
298 widget.render(area, self.buffer());
300 }
301 }
302
303 #[inline(always)]
306 #[allow(deprecated)]
307 pub fn render<W, S>(&mut self, widget: W, area: Rect, state: &mut S)
308 where
309 W: StatefulWidget<State = S>,
310 S: RelocatableState,
311 {
312 if area.intersects(self.buffer.area) {
313 widget.render(area, self.buffer(), state);
315 state.relocate(self.shift(), self.widget_area);
317 } else {
318 state.relocate_hidden();
319 }
320 }
321
322 pub fn layout(&self) -> Rect {
324 self.layout
325 }
326
327 pub fn is_visible_area(&self, area: Rect) -> bool {
329 area.intersects(self.buffer.area)
330 }
331
332 #[deprecated(
334 since = "2.0.0",
335 note = "should not be public. use relocate2() instead."
336 )]
337 pub fn shift(&self) -> (i16, i16) {
338 (
339 self.widget_area.x as i16 - self.offset.x as i16,
340 self.widget_area.y as i16 - self.offset.y as i16,
341 )
342 }
343
344 #[deprecated(
347 since = "2.0.0",
348 note = "wrong api, use is_visible_area() or locate_area2()"
349 )]
350 pub fn locate_area(&self, area: Rect) -> Rect {
351 area
352 }
353
354 #[deprecated(
356 since = "2.0.0",
357 note = "wrong api, use is_visible_area() or locate_area2()"
358 )]
359 pub fn locate_area2(&self, area: Rect) -> Option<Rect> {
360 if area.intersects(self.buffer.area) {
361 Some(area)
362 } else {
363 None
364 }
365 }
366
367 #[deprecated(since = "2.0.0", note = "wrong api, use relocate2() instead")]
373 #[allow(deprecated)]
374 pub fn relocate<S>(&self, state: &mut S)
375 where
376 S: RelocatableState,
377 {
378 state.relocate(self.shift(), self.widget_area);
379 }
380
381 #[deprecated(since = "2.0.0", note = "wrong api, use relocate2() instead")]
390 #[allow(deprecated)]
391 pub fn relocate2<S>(&self, area: Rect, state: &mut S)
392 where
393 S: RelocatableState,
394 {
395 if self.is_visible_area(area) {
396 state.relocate(self.shift(), self.widget_area);
397 } else {
398 state.relocate_hidden();
399 }
400 }
401
402 #[deprecated(since = "2.0.0", note = "bad api, use relocate2() instead")]
408 pub fn hidden<S>(&self, state: &mut S)
409 where
410 S: RelocatableState,
411 {
412 state.relocate_hidden();
413 }
414
415 pub fn buffer(&mut self) -> &mut Buffer {
420 &mut self.buffer
421 }
422
423 pub fn into_widget(self) -> ViewWidget<'a> {
427 ViewWidget {
428 block: self.block,
429 hscroll: self.hscroll,
430 vscroll: self.vscroll,
431 offset: self.offset,
432 buffer: self.buffer,
433 style: self.style,
434 }
435 }
436}
437
438impl StatefulWidget for ViewWidget<'_> {
439 type State = ViewState;
440
441 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
442 assert_eq!(area, state.area);
443
444 ScrollArea::new()
445 .style(self.style)
446 .block(self.block.as_ref())
447 .h_scroll(self.hscroll.as_ref())
448 .v_scroll(self.vscroll.as_ref())
449 .render(
450 area,
451 buf,
452 &mut ScrollAreaState::new()
453 .h_scroll(&mut state.hscroll)
454 .v_scroll(&mut state.vscroll),
455 );
456
457 let src_area = self.buffer.area;
458 let tgt_area = state.widget_area;
459 let offset = self.offset;
460
461 let off_x0 = src_area.x.saturating_sub(offset.x);
463 let off_y0 = src_area.y.saturating_sub(offset.y);
464 let cut_x0 = offset.x.saturating_sub(src_area.x);
466 let cut_y0 = offset.y.saturating_sub(src_area.y);
467
468 let len_src = src_area.width.saturating_sub(cut_x0);
470 let len_tgt = tgt_area.width.saturating_sub(off_x0);
471 let len = min(len_src, len_tgt);
472
473 let height_src = src_area.height.saturating_sub(cut_y0);
475 let height_tgt = tgt_area.height.saturating_sub(off_y0);
476 let height = min(height_src, height_tgt);
477
478 for y in 0..height {
492 let src_0 = self
493 .buffer
494 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
495 let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
496
497 let src = &self.buffer.content[src_0..src_0 + len as usize];
498 let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
499 tgt.clone_from_slice(src);
500 }
501
502 state.buffer = Some(self.buffer);
504 }
505}
506
507impl Default for ViewStyle {
508 fn default() -> Self {
509 Self {
510 style: Default::default(),
511 block: None,
512 scroll: None,
513 non_exhaustive: NonExhaustive,
514 }
515 }
516}
517
518impl HasFocus for ViewState {
519 fn build(&self, builder: &mut FocusBuilder) {
520 builder.leaf_widget(self);
521 }
522
523 fn focus(&self) -> FocusFlag {
524 self.focus.clone()
525 }
526
527 fn area(&self) -> Rect {
528 self.area
529 }
530}
531
532impl ViewState {
533 pub fn new() -> Self {
534 Self::default()
535 }
536
537 pub fn show_area(&mut self, area: Rect) {
539 self.hscroll.scroll_to_pos(area.x as usize);
540 self.vscroll.scroll_to_pos(area.y as usize);
541 }
542}
543
544impl ViewState {
545 pub fn vertical_offset(&self) -> usize {
546 self.vscroll.offset()
547 }
548
549 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
550 let old = self.vscroll.offset();
551 self.vscroll.set_offset(offset);
552 old != self.vscroll.offset()
553 }
554
555 pub fn vertical_page_len(&self) -> usize {
556 self.vscroll.page_len()
557 }
558
559 pub fn horizontal_offset(&self) -> usize {
560 self.hscroll.offset()
561 }
562
563 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
564 let old = self.hscroll.offset();
565 self.hscroll.set_offset(offset);
566 old != self.hscroll.offset()
567 }
568
569 pub fn horizontal_page_len(&self) -> usize {
570 self.hscroll.page_len()
571 }
572
573 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
574 self.hscroll.scroll_to_pos(pos)
575 }
576
577 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
578 self.vscroll.scroll_to_pos(pos)
579 }
580
581 pub fn scroll_up(&mut self, delta: usize) -> bool {
582 self.vscroll.scroll_up(delta)
583 }
584
585 pub fn scroll_down(&mut self, delta: usize) -> bool {
586 self.vscroll.scroll_down(delta)
587 }
588
589 pub fn scroll_left(&mut self, delta: usize) -> bool {
590 self.hscroll.scroll_left(delta)
591 }
592
593 pub fn scroll_right(&mut self, delta: usize) -> bool {
594 self.hscroll.scroll_right(delta)
595 }
596}
597
598impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ViewState {
599 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
600 let r = if self.is_focused() {
601 match event {
602 ct_event!(keycode press Left) => self.scroll_left(self.hscroll.scroll_by()).into(),
603 ct_event!(keycode press Right) => {
604 self.scroll_right(self.hscroll.scroll_by()).into()
605 }
606 ct_event!(keycode press Up) => self.scroll_up(self.vscroll.scroll_by()).into(),
607 ct_event!(keycode press Down) => self.scroll_down(self.vscroll.scroll_by()).into(),
608
609 ct_event!(keycode press PageUp) => self.scroll_up(self.vscroll.page_len()).into(),
610 ct_event!(keycode press PageDown) => {
611 self.scroll_down(self.vscroll.page_len()).into()
612 }
613 ct_event!(keycode press Home) => self.vertical_scroll_to(0).into(),
614 ct_event!(keycode press End) => {
615 self.vertical_scroll_to(self.vscroll.max_offset()).into()
616 }
617
618 ct_event!(keycode press ALT-PageUp) => {
619 self.scroll_left(self.hscroll.page_len()).into()
620 }
621 ct_event!(keycode press ALT-PageDown) => {
622 self.scroll_right(self.hscroll.page_len()).into()
623 }
624 ct_event!(keycode press ALT-Home) => self.horizontal_scroll_to(0).into(),
625 ct_event!(keycode press ALT-End) => {
626 self.horizontal_scroll_to(self.hscroll.max_offset()).into()
627 }
628 _ => Outcome::Continue,
629 }
630 } else {
631 Outcome::Continue
632 };
633
634 r.or_else(|| self.handle(event, MouseOnly))
635 }
636}
637
638impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ViewState {
639 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
640 let mut sas = ScrollAreaState::new()
641 .area(self.widget_area)
642 .h_scroll(&mut self.hscroll)
643 .v_scroll(&mut self.vscroll);
644 match sas.handle(event, MouseOnly) {
645 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
646 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
647 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
648 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
649 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
650 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
651 r => r.into(),
652 }
653 }
654}
655
656pub fn handle_events(
660 state: &mut ViewState,
661 focus: bool,
662 event: &crossterm::event::Event,
663) -> Outcome {
664 state.focus.set(focus);
665 HandleEvent::handle(state, event, Regular)
666}
667
668pub fn handle_mouse_events(state: &mut ViewState, event: &crossterm::event::Event) -> Outcome {
670 HandleEvent::handle(state, event, MouseOnly)
671}