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::style::Style;
60use ratatui::widgets::Block;
61use ratatui::widgets::{StatefulWidget, Widget};
62
63#[derive(Debug, Default, Clone)]
65pub struct View<'a> {
66 layout: Rect,
67 view_size: Option<Size>,
68 style: Style,
69 block: Option<Block<'a>>,
70 hscroll: Option<Scroll<'a>>,
71 vscroll: Option<Scroll<'a>>,
72}
73
74#[derive(Debug)]
81pub struct ViewBuffer<'a> {
82 layout: Rect,
84
85 offset: Position,
87 buffer: Buffer,
88
89 widget_area: Rect,
91
92 style: Style,
93 block: Option<Block<'a>>,
94 hscroll: Option<Scroll<'a>>,
95 vscroll: Option<Scroll<'a>>,
96}
97
98#[derive(Debug)]
100pub struct ViewWidget<'a> {
101 offset: Position,
103 buffer: Buffer,
104
105 style: Style,
106 block: Option<Block<'a>>,
107 hscroll: Option<Scroll<'a>>,
108 vscroll: Option<Scroll<'a>>,
109}
110
111#[derive(Debug)]
113pub struct ViewStyle {
114 pub style: Style,
115 pub block: Option<Block<'static>>,
116 pub scroll: Option<ScrollStyle>,
117 pub non_exhaustive: NonExhaustive,
118}
119
120#[derive(Debug, Default, Clone)]
122pub struct ViewState {
123 pub area: Rect,
126 pub widget_area: Rect,
129
130 pub layout: Rect,
133
134 pub hscroll: ScrollState,
137 pub vscroll: ScrollState,
140
141 buffer: Option<Buffer>,
143}
144
145impl<'a> View<'a> {
146 pub fn new() -> Self {
148 Self::default()
149 }
150
151 pub fn layout(mut self, area: Rect) -> Self {
153 self.layout = area;
154 self
155 }
156
157 pub fn view_size(mut self, view: Size) -> Self {
160 self.view_size = Some(view);
161 self
162 }
163
164 pub fn style(mut self, style: Style) -> Self {
166 self.style = style;
167 self.block = self.block.map(|v| v.style(style));
168 self
169 }
170
171 pub fn block(mut self, block: Block<'a>) -> Self {
173 self.block = Some(block);
174 self
175 }
176
177 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
179 self.hscroll = Some(scroll.clone().override_horizontal());
180 self.vscroll = Some(scroll.override_vertical());
181 self
182 }
183
184 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
186 self.hscroll = Some(scroll.override_horizontal());
187 self
188 }
189
190 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
192 self.vscroll = Some(scroll.override_vertical());
193 self
194 }
195
196 pub fn styles(mut self, styles: ViewStyle) -> Self {
198 self.style = styles.style;
199 if styles.block.is_some() {
200 self.block = styles.block;
201 }
202 if let Some(styles) = styles.scroll {
203 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
204 self.vscroll = self.vscroll.map(|v| v.styles(styles.clone()));
205 }
206 self.block = self.block.map(|v| v.style(styles.style));
207 self
208 }
209
210 pub fn layout_width(&self, area: Rect, state: &ViewState) -> u16 {
212 self.inner(area, state).width
213 }
214
215 pub fn inner(&self, area: Rect, state: &ViewState) -> Rect {
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 sa.inner(area, Some(&state.hscroll), Some(&state.vscroll))
222 }
223
224 pub fn into_buffer(self, area: Rect, state: &mut ViewState) -> ViewBuffer<'a> {
226 state.area = area;
227 state.layout = self.layout;
228
229 let sa = ScrollArea::new()
230 .block(self.block.as_ref())
231 .h_scroll(self.hscroll.as_ref())
232 .v_scroll(self.vscroll.as_ref());
233 state.widget_area = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
234
235 let max_x = if let Some(view_size) = self.view_size {
236 max(state.layout.right(), view_size.width)
237 } else {
238 state.layout.right()
239 };
240 let max_y = if let Some(view_size) = self.view_size {
241 max(state.layout.bottom(), view_size.height)
242 } else {
243 state.layout.bottom()
244 };
245
246 state
247 .hscroll
248 .set_max_offset(max_x.saturating_sub(state.widget_area.width) as usize);
249 state.hscroll.set_page_len(state.widget_area.width as usize);
250 state
251 .vscroll
252 .set_page_len(state.widget_area.height as usize);
253 state
254 .vscroll
255 .set_max_offset(max_y.saturating_sub(state.widget_area.height) as usize);
256
257 let offset = Position::new(state.hscroll.offset as u16, state.vscroll.offset as u16);
260
261 let buffer_area = state.layout;
263 let mut buffer = if let Some(mut buffer) = state.buffer.take() {
264 buffer.reset();
265 buffer.resize(buffer_area);
266 buffer
267 } else {
268 Buffer::empty(buffer_area)
269 };
270 buffer.set_style(buffer_area, self.style);
271
272 ViewBuffer {
273 layout: self.layout,
274 offset,
275 buffer,
276 widget_area: state.widget_area,
277 style: self.style,
278 block: self.block,
279 hscroll: self.hscroll,
280 vscroll: self.vscroll,
281 }
282 }
283}
284
285impl<'a> ViewBuffer<'a> {
286 #[inline(always)]
288 pub fn render_widget<W>(&mut self, widget: W, area: Rect)
289 where
290 W: Widget,
291 {
292 if area.intersects(self.buffer.area) {
293 widget.render(area, self.buffer());
295 }
296 }
297
298 #[inline(always)]
301 pub fn render_stateful<W, S>(&mut self, widget: W, area: Rect, state: &mut S)
302 where
303 W: StatefulWidget<State = S>,
304 S: RelocatableState,
305 {
306 if area.intersects(self.buffer.area) {
307 widget.render(area, self.buffer(), state);
309 self.relocate(state);
311 } else {
312 self.hidden(state);
313 }
314 }
315
316 pub fn layout(&self) -> Rect {
318 self.layout
319 }
320
321 pub fn is_visible_area(&self, area: Rect) -> bool {
323 area.intersects(self.buffer.area)
324 }
325
326 pub fn shift(&self) -> (i16, i16) {
328 (
329 self.widget_area.x as i16 - self.offset.x as i16,
330 self.widget_area.y as i16 - self.offset.y as i16,
331 )
332 }
333
334 pub fn locate_area(&self, area: Rect) -> Rect {
337 area
338 }
339
340 pub fn relocate<S>(&self, state: &mut S)
346 where
347 S: RelocatableState,
348 {
349 state.relocate(self.shift(), self.widget_area);
350 }
351
352 pub fn hidden<S>(&self, state: &mut S)
358 where
359 S: RelocatableState,
360 {
361 state.relocate((0, 0), Rect::default())
362 }
363
364 pub fn buffer(&mut self) -> &mut Buffer {
369 &mut self.buffer
370 }
371
372 pub fn into_widget(self) -> ViewWidget<'a> {
376 ViewWidget {
377 block: self.block,
378 hscroll: self.hscroll,
379 vscroll: self.vscroll,
380 offset: self.offset,
381 buffer: self.buffer,
382 style: self.style,
383 }
384 }
385}
386
387impl StatefulWidget for ViewWidget<'_> {
388 type State = ViewState;
389
390 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
391 assert_eq!(area, state.area);
392
393 ScrollArea::new()
394 .style(self.style)
395 .block(self.block.as_ref())
396 .h_scroll(self.hscroll.as_ref())
397 .v_scroll(self.vscroll.as_ref())
398 .render(
399 area,
400 buf,
401 &mut ScrollAreaState::new()
402 .h_scroll(&mut state.hscroll)
403 .v_scroll(&mut state.vscroll),
404 );
405
406 let src_area = self.buffer.area;
407 let tgt_area = state.widget_area;
408 let offset = self.offset;
409
410 let off_x0 = src_area.x.saturating_sub(offset.x);
412 let off_y0 = src_area.y.saturating_sub(offset.y);
413 let cut_x0 = offset.x.saturating_sub(src_area.x);
415 let cut_y0 = offset.y.saturating_sub(src_area.y);
416
417 let len_src = src_area.width.saturating_sub(cut_x0);
419 let len_tgt = tgt_area.width.saturating_sub(off_x0);
420 let len = min(len_src, len_tgt);
421
422 let height_src = src_area.height.saturating_sub(cut_y0);
424 let height_tgt = tgt_area.height.saturating_sub(off_y0);
425 let height = min(height_src, height_tgt);
426
427 for y in 0..height {
441 let src_0 = self
442 .buffer
443 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
444 let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
445
446 let src = &self.buffer.content[src_0..src_0 + len as usize];
447 let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
448 tgt.clone_from_slice(src);
449 }
450
451 state.buffer = Some(self.buffer);
453 }
454}
455
456impl Default for ViewStyle {
457 fn default() -> Self {
458 Self {
459 style: Default::default(),
460 block: None,
461 scroll: None,
462 non_exhaustive: NonExhaustive,
463 }
464 }
465}
466
467impl ViewState {
468 pub fn new() -> Self {
469 Self::default()
470 }
471
472 pub fn show_area(&mut self, area: Rect) {
474 self.hscroll.scroll_to_pos(area.x as usize);
475 self.vscroll.scroll_to_pos(area.y as usize);
476 }
477}
478
479impl ViewState {
480 pub fn vertical_offset(&self) -> usize {
481 self.vscroll.offset()
482 }
483
484 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
485 let old = self.vscroll.offset();
486 self.vscroll.set_offset(offset);
487 old != self.vscroll.offset()
488 }
489
490 pub fn vertical_page_len(&self) -> usize {
491 self.vscroll.page_len()
492 }
493
494 pub fn horizontal_offset(&self) -> usize {
495 self.hscroll.offset()
496 }
497
498 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
499 let old = self.hscroll.offset();
500 self.hscroll.set_offset(offset);
501 old != self.hscroll.offset()
502 }
503
504 pub fn horizontal_page_len(&self) -> usize {
505 self.hscroll.page_len()
506 }
507
508 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
509 self.hscroll.scroll_to_pos(pos)
510 }
511
512 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
513 self.vscroll.scroll_to_pos(pos)
514 }
515
516 pub fn scroll_up(&mut self, delta: usize) -> bool {
517 self.vscroll.scroll_up(delta)
518 }
519
520 pub fn scroll_down(&mut self, delta: usize) -> bool {
521 self.vscroll.scroll_down(delta)
522 }
523
524 pub fn scroll_left(&mut self, delta: usize) -> bool {
525 self.hscroll.scroll_left(delta)
526 }
527
528 pub fn scroll_right(&mut self, delta: usize) -> bool {
529 self.hscroll.scroll_right(delta)
530 }
531}
532
533impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ViewState {
534 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
535 self.handle(event, MouseOnly)
536 }
537}
538
539impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ViewState {
540 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
541 let mut sas = ScrollAreaState::new()
542 .area(self.widget_area)
543 .h_scroll(&mut self.hscroll)
544 .v_scroll(&mut self.vscroll);
545 match sas.handle(event, MouseOnly) {
546 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
547 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
548 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
549 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
550 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
551 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
552 r => r.into(),
553 }
554 }
555}