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 #[deprecated(since = "1.2.0", note = "use render() instead")]
302 pub fn render_stateful<W, S>(&mut self, widget: W, area: Rect, state: &mut S)
303 where
304 W: StatefulWidget<State = S>,
305 S: RelocatableState,
306 {
307 self.render(widget, area, state);
308 }
309
310 #[inline(always)]
313 pub fn render<W, S>(&mut self, widget: W, area: Rect, state: &mut S)
314 where
315 W: StatefulWidget<State = S>,
316 S: RelocatableState,
317 {
318 if area.intersects(self.buffer.area) {
319 widget.render(area, self.buffer(), state);
321 self.relocate(state);
323 } else {
324 self.hidden(state);
325 }
326 }
327
328 pub fn layout(&self) -> Rect {
330 self.layout
331 }
332
333 pub fn is_visible_area(&self, area: Rect) -> bool {
335 area.intersects(self.buffer.area)
336 }
337
338 pub fn shift(&self) -> (i16, i16) {
340 (
341 self.widget_area.x as i16 - self.offset.x as i16,
342 self.widget_area.y as i16 - self.offset.y as i16,
343 )
344 }
345
346 pub fn locate_area(&self, area: Rect) -> Rect {
349 area
350 }
351
352 pub fn relocate<S>(&self, state: &mut S)
358 where
359 S: RelocatableState,
360 {
361 state.relocate(self.shift(), self.widget_area);
362 }
363
364 pub fn hidden<S>(&self, state: &mut S)
370 where
371 S: RelocatableState,
372 {
373 state.relocate((0, 0), Rect::default())
374 }
375
376 pub fn buffer(&mut self) -> &mut Buffer {
381 &mut self.buffer
382 }
383
384 pub fn into_widget(self) -> ViewWidget<'a> {
388 ViewWidget {
389 block: self.block,
390 hscroll: self.hscroll,
391 vscroll: self.vscroll,
392 offset: self.offset,
393 buffer: self.buffer,
394 style: self.style,
395 }
396 }
397}
398
399impl StatefulWidget for ViewWidget<'_> {
400 type State = ViewState;
401
402 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
403 assert_eq!(area, state.area);
404
405 ScrollArea::new()
406 .style(self.style)
407 .block(self.block.as_ref())
408 .h_scroll(self.hscroll.as_ref())
409 .v_scroll(self.vscroll.as_ref())
410 .render(
411 area,
412 buf,
413 &mut ScrollAreaState::new()
414 .h_scroll(&mut state.hscroll)
415 .v_scroll(&mut state.vscroll),
416 );
417
418 let src_area = self.buffer.area;
419 let tgt_area = state.widget_area;
420 let offset = self.offset;
421
422 let off_x0 = src_area.x.saturating_sub(offset.x);
424 let off_y0 = src_area.y.saturating_sub(offset.y);
425 let cut_x0 = offset.x.saturating_sub(src_area.x);
427 let cut_y0 = offset.y.saturating_sub(src_area.y);
428
429 let len_src = src_area.width.saturating_sub(cut_x0);
431 let len_tgt = tgt_area.width.saturating_sub(off_x0);
432 let len = min(len_src, len_tgt);
433
434 let height_src = src_area.height.saturating_sub(cut_y0);
436 let height_tgt = tgt_area.height.saturating_sub(off_y0);
437 let height = min(height_src, height_tgt);
438
439 for y in 0..height {
453 let src_0 = self
454 .buffer
455 .index_of(src_area.x + cut_x0, src_area.y + cut_y0 + y);
456 let tgt_0 = buf.index_of(tgt_area.x + off_x0, tgt_area.y + off_y0 + y);
457
458 let src = &self.buffer.content[src_0..src_0 + len as usize];
459 let tgt = &mut buf.content[tgt_0..tgt_0 + len as usize];
460 tgt.clone_from_slice(src);
461 }
462
463 state.buffer = Some(self.buffer);
465 }
466}
467
468impl Default for ViewStyle {
469 fn default() -> Self {
470 Self {
471 style: Default::default(),
472 block: None,
473 scroll: None,
474 non_exhaustive: NonExhaustive,
475 }
476 }
477}
478
479impl ViewState {
480 pub fn new() -> Self {
481 Self::default()
482 }
483
484 pub fn show_area(&mut self, area: Rect) {
486 self.hscroll.scroll_to_pos(area.x as usize);
487 self.vscroll.scroll_to_pos(area.y as usize);
488 }
489}
490
491impl ViewState {
492 pub fn vertical_offset(&self) -> usize {
493 self.vscroll.offset()
494 }
495
496 pub fn set_vertical_offset(&mut self, offset: usize) -> bool {
497 let old = self.vscroll.offset();
498 self.vscroll.set_offset(offset);
499 old != self.vscroll.offset()
500 }
501
502 pub fn vertical_page_len(&self) -> usize {
503 self.vscroll.page_len()
504 }
505
506 pub fn horizontal_offset(&self) -> usize {
507 self.hscroll.offset()
508 }
509
510 pub fn set_horizontal_offset(&mut self, offset: usize) -> bool {
511 let old = self.hscroll.offset();
512 self.hscroll.set_offset(offset);
513 old != self.hscroll.offset()
514 }
515
516 pub fn horizontal_page_len(&self) -> usize {
517 self.hscroll.page_len()
518 }
519
520 pub fn horizontal_scroll_to(&mut self, pos: usize) -> bool {
521 self.hscroll.scroll_to_pos(pos)
522 }
523
524 pub fn vertical_scroll_to(&mut self, pos: usize) -> bool {
525 self.vscroll.scroll_to_pos(pos)
526 }
527
528 pub fn scroll_up(&mut self, delta: usize) -> bool {
529 self.vscroll.scroll_up(delta)
530 }
531
532 pub fn scroll_down(&mut self, delta: usize) -> bool {
533 self.vscroll.scroll_down(delta)
534 }
535
536 pub fn scroll_left(&mut self, delta: usize) -> bool {
537 self.hscroll.scroll_left(delta)
538 }
539
540 pub fn scroll_right(&mut self, delta: usize) -> bool {
541 self.hscroll.scroll_right(delta)
542 }
543}
544
545impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ViewState {
546 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
547 self.handle(event, MouseOnly)
548 }
549}
550
551impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ViewState {
552 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> Outcome {
553 let mut sas = ScrollAreaState::new()
554 .area(self.widget_area)
555 .h_scroll(&mut self.hscroll)
556 .v_scroll(&mut self.vscroll);
557 match sas.handle(event, MouseOnly) {
558 ScrollOutcome::Up(v) => self.scroll_up(v).into(),
559 ScrollOutcome::Down(v) => self.scroll_down(v).into(),
560 ScrollOutcome::VPos(v) => self.set_vertical_offset(v).into(),
561 ScrollOutcome::Left(v) => self.scroll_left(v).into(),
562 ScrollOutcome::Right(v) => self.scroll_right(v).into(),
563 ScrollOutcome::HPos(v) => self.set_horizontal_offset(v).into(),
564 r => r.into(),
565 }
566 }
567}