1use std::cmp::{max, min};
51
52use crate::_private::NonExhaustive;
53use crate::event::ScrollOutcome;
54use rat_event::{HandleEvent, MouseOnly, Outcome, Regular};
55use rat_reloc::RelocatableState;
56use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
57use ratatui::buffer::Buffer;
58use ratatui::layout::{Position, Rect, Size};
59use ratatui::prelude::{StatefulWidget, Widget};
60use ratatui::widgets::Block;
61
62#[derive(Debug, Default, Clone)]
64pub struct View<'a> {
65 layout: Rect,
66 view_size: Option<Size>,
67
68 block: Option<Block<'a>>,
69 hscroll: Option<Scroll<'a>>,
70 vscroll: Option<Scroll<'a>>,
71}
72
73#[derive(Debug)]
80pub struct ViewBuffer<'a> {
81 layout: Rect,
83
84 offset: Position,
86 buffer: Buffer,
87
88 widget_area: Rect,
90
91 block: Option<Block<'a>>,
92 hscroll: Option<Scroll<'a>>,
93 vscroll: Option<Scroll<'a>>,
94}
95
96#[derive(Debug)]
98pub struct ViewWidget<'a> {
99 offset: Position,
101 buffer: Buffer,
102
103 block: Option<Block<'a>>,
104 hscroll: Option<Scroll<'a>>,
105 vscroll: Option<Scroll<'a>>,
106}
107
108#[derive(Debug)]
110pub struct ViewStyle {
111 pub block: Option<Block<'static>>,
112 pub scroll: Option<ScrollStyle>,
113
114 pub non_exhaustive: NonExhaustive,
115}
116
117#[derive(Debug, Default, Clone)]
119pub struct ViewState {
120 pub area: Rect,
123 pub widget_area: Rect,
126
127 pub layout: Rect,
130
131 pub hscroll: ScrollState,
134 pub vscroll: ScrollState,
137
138 buffer: Option<Buffer>,
140}
141
142impl<'a> View<'a> {
143 pub fn new() -> Self {
145 Self::default()
146 }
147
148 pub fn layout(mut self, area: Rect) -> Self {
150 self.layout = area;
151 self
152 }
153
154 pub fn view_size(mut self, view: Size) -> Self {
157 self.view_size = Some(view);
158 self
159 }
160
161 pub fn block(mut self, block: Block<'a>) -> Self {
163 self.block = Some(block);
164 self
165 }
166
167 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
169 self.hscroll = Some(scroll.clone().override_horizontal());
170 self.vscroll = Some(scroll.override_vertical());
171 self
172 }
173
174 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
176 self.hscroll = Some(scroll.override_horizontal());
177 self
178 }
179
180 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
182 self.vscroll = Some(scroll.override_vertical());
183 self
184 }
185
186 pub fn styles(mut self, styles: ViewStyle) -> Self {
188 if styles.block.is_some() {
189 self.block = styles.block;
190 }
191 if let Some(styles) = styles.scroll {
192 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
193 self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
194 }
195 self
196 }
197
198 pub fn layout_width(&self, area: Rect, state: &ViewState) -> u16 {
200 self.inner(area, state).width
201 }
202
203 pub fn inner(&self, area: Rect, state: &ViewState) -> Rect {
205 let sa = ScrollArea::new()
206 .block(self.block.as_ref())
207 .h_scroll(self.hscroll.as_ref())
208 .v_scroll(self.vscroll.as_ref());
209 sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
210 }
211
212 pub fn into_buffer(self, area: Rect, state: &mut ViewState) -> ViewBuffer<'a> {
214 state.area = area;
215 state.layout = self.layout;
216
217 let sa = ScrollArea::new()
218 .block(self.block.as_ref())
219 .h_scroll(self.hscroll.as_ref())
220 .v_scroll(self.vscroll.as_ref());
221 state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
222
223 let max_x = if let Some(view_size) = self.view_size {
224 max(state.layout.right(), view_size.width)
225 } else {
226 state.layout.right()
227 };
228 let max_y = if let Some(view_size) = self.view_size {
229 max(state.layout.bottom(), view_size.height)
230 } else {
231 state.layout.bottom()
232 };
233
234 state
235 .hscroll
236 .set_max_offset(max_x.saturating_sub(state.widget_area.width) as usize);
237 state.hscroll.set_page_len(state.widget_area.width as usize);
238 state
239 .vscroll
240 .set_page_len(state.widget_area.height as usize);
241 state
242 .vscroll
243 .set_max_offset(max_y.saturating_sub(state.widget_area.height) as usize);
244
245 let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
248
249 let buffer_area = state.layout;
251 let buffer = if let Some(mut buffer) = state.buffer.take() {
252 buffer.reset();
253 buffer.resize(buffer_area);
254 buffer
255 } else {
256 Buffer::empty(buffer_area)
257 };
258
259 ViewBuffer {
260 layout: self.layout,
261 offset,
262 buffer,
263 widget_area: state.widget_area,
264 block: self.block,
265 hscroll: self.hscroll,
266 vscroll: self.vscroll,
267 }
268 }
269}
270
271impl<'a> ViewBuffer<'a> {
272 #[inline(always)]
274 pub fn render_widget<W>(&mut self, widget: W, area: Rect)
275 where
276 W: Widget,
277 {
278 if area.intersects(self.buffer.area) {
279 widget.render(area, self.buffer());
281 }
282 }
283
284 #[inline(always)]
287 pub fn render_stateful<W, S>(&mut self, widget: W, area: Rect, state: &mut S)
288 where
289 W: StatefulWidget<State = S>,
290 S: RelocatableState,
291 {
292 if area.intersects(self.buffer.area) {
293 widget.render(area, self.buffer(), state);
295 self.relocate(state);
297 } else {
298 self.hidden(state);
299 }
300 }
301
302 pub fn layout(&self) -> Rect {
304 self.layout
305 }
306
307 pub fn is_visible_area(&self, area: Rect) -> bool {
309 area.intersects(self.buffer.area)
310 }
311
312 pub fn shift(&self) -> (i16, i16) {
314 (
315 self.widget_area.x as i16 - self.offset.x as i16,
316 self.widget_area.y as i16 - self.offset.y as i16,
317 )
318 }
319
320 pub fn locate_area(&self, area: Rect) -> Rect {
323 area
324 }
325
326 pub fn relocate<S>(&self, state: &mut S)
332 where
333 S: RelocatableState,
334 {
335 state.relocate(self.shift(), self.widget_area);
336 }
337
338 pub fn hidden<S>(&self, state: &mut S)
344 where
345 S: RelocatableState,
346 {
347 state.relocate((0, 0), Rect::default())
348 }
349
350 pub fn buffer(&mut self) -> &mut Buffer {
355 &mut self.buffer
356 }
357
358 pub fn into_widget(self) -> ViewWidget<'a> {
362 ViewWidget {
363 block: self.block,
364 hscroll: self.hscroll,
365 vscroll: self.vscroll,
366 offset: self.offset,
367 buffer: self.buffer,
368 }
369 }
370}
371
372impl StatefulWidget for ViewWidget<'_> {
373 type State = ViewState;
374
375 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
376 assert_eq!(area, state.area);
377
378 ScrollArea::new()
379 .block(self.block.as_ref())
380 .h_scroll(self.hscroll.as_ref())
381 .v_scroll(self.vscroll.as_ref())
382 .render(
383 area,
384 buf,
385 &mut ScrollAreaState::new()
386 .h_scroll(&mut state.hscroll)
387 .v_scroll(&mut state.vscroll),
388 );
389
390 let src_area = self.buffer.area;
391 let tgt_area = state.widget_area;
392 let offset = self.offset;
393
394 let off_x0 = src_area.x.saturating_sub(offset.x);
396 let off_y0 = src_area.y.saturating_sub(offset.y);
397 let cut_x0 = offset.x.saturating_sub(src_area.x);
399 let cut_y0 = offset.y.saturating_sub(src_area.y);
400
401 let len_src = src_area.width.saturating_sub(cut_x0);
403 let len_tgt = tgt_area.width.saturating_sub(off_x0);
404 let len = min(len_src, len_tgt);
405
406 let height_src = src_area.height.saturating_sub(cut_y0);
408 let height_tgt = tgt_area.height.saturating_sub(off_y0);
409 let height = min(height_src, height_tgt);
410
411 for y in 0..height {
425 let src_0 = self
426 .buffer
427 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
428 let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
429
430 let src = &self.buffer.content[src_0..src_0 + len as usize];
431 let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
432 tgt.clone_from_slice(src);
433 }
434
435 state.buffer = Some(self.buffer);
437 }
438}
439
440impl Default for ViewStyle {
441 fn default() -> Self {
442 Self {
443 block: None,
444 scroll: None,
445 non_exhaustive: NonExhaustive,
446 }
447 }
448}
449
450impl ViewState {
451 pub fn new() -> Self {
452 Self::default()
453 }
454
455 pub fn show_area(&mut self, area: Rect) {
457 self.hscroll.scroll_to_pos(area.x as usize);
458 self.vscroll.scroll_to_pos(area.y as usize);
459 }
460}
461
462impl ViewState {
463 pub fn vertical_offset(&self) -> usize {
464 self.vscroll.offset()
465 }
466
467 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
468 let old = self.vscroll.offset();
469 self.vscroll.set_offset(offset);
470 old != self.vscroll.offset()
471 }
472
473 pub fn vertical_page_len(&self) -> usize {
474 self.vscroll.page_len()
475 }
476
477 pub fn horizontal_offset(&self) -> usize {
478 self.hscroll.offset()
479 }
480
481 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
482 let old = self.hscroll.offset();
483 self.hscroll.set_offset(offset);
484 old != self.hscroll.offset()
485 }
486
487 pub fn horizontal_page_len(&self) -> usize {
488 self.hscroll.page_len()
489 }
490
491 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
492 self.hscroll.scroll_to_pos(pos)
493 }
494
495 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
496 self.vscroll.scroll_to_pos(pos)
497 }
498
499 pub fn scroll_up(&mut self, delta: usize) -> bool {
500 self.vscroll.scroll_up(delta)
501 }
502
503 pub fn scroll_down(&mut self, delta: usize) -> bool {
504 self.vscroll.scroll_down(delta)
505 }
506
507 pub fn scroll_left(&mut self, delta: usize) -> bool {
508 self.hscroll.scroll_left(delta)
509 }
510
511 pub fn scroll_right(&mut self, delta: usize) -> bool {
512 self.hscroll.scroll_right(delta)
513 }
514}
515
516impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ViewState {
517 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
518 self.handle(event, MouseOnly)
519 }
520}
521
522impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ViewState {
523 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
524 let mut sas = ScrollAreaState::new()
525 .area(self.widget_area)
526 .h_scroll(&mut self.hscroll)
527 .v_scroll(&mut self.vscroll);
528 match sas.handle(event, MouseOnly) {
529 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
530 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
531 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
532 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
533 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
534 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
535 r => r.into(),
536 }
537 }
538}