ratatui_kit/components/scroll_view/
scrollbars.rs1use super::ScrollViewState;
2use ratatui::{
3 buffer::Buffer,
4 layout::{Rect, Size},
5 widgets::{Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, StatefulWidgetRef},
6};
7use ratatui_kit_macros::Props;
8
9#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
10pub enum ScrollbarVisibility {
11 #[default]
13 Automatic,
14 Always,
16 Never,
18}
19
20#[derive(Props, Clone, Hash)]
21pub struct ScrollBars<'a> {
22 pub vertical_scrollbar_visibility: ScrollbarVisibility,
23 pub horizontal_scrollbar_visibility: ScrollbarVisibility,
24 pub vertical_scrollbar: Scrollbar<'a>,
25 pub horizontal_scrollbar: Scrollbar<'a>,
26}
27
28impl Default for ScrollBars<'_> {
29 fn default() -> Self {
30 Self {
31 vertical_scrollbar_visibility: ScrollbarVisibility::Automatic,
32 horizontal_scrollbar_visibility: ScrollbarVisibility::Automatic,
33 vertical_scrollbar: Scrollbar::new(ScrollbarOrientation::VerticalRight),
34 horizontal_scrollbar: Scrollbar::new(ScrollbarOrientation::HorizontalBottom),
35 }
36 }
37}
38
39impl ScrollBars<'_> {
40 fn render_visible_area(
41 &self,
42 area: Rect,
43 buf: &mut Buffer,
44 visible_area: Rect,
45 scroll_buffer: &Buffer,
46 ) {
47 for (src_row, dst_row) in visible_area.rows().zip(area.rows()) {
49 for (src_col, dst_col) in src_row.columns().zip(dst_row.columns()) {
50 buf[dst_col] = scroll_buffer[src_col].clone();
51 }
52 }
53 }
54
55 fn render_vertical_scrollbar(
56 &self,
57 area: Rect,
58 buf: &mut Buffer,
59 state: &ScrollViewState,
60 scroll_size: Size,
61 ) {
62 let scrollbar_height = scroll_size.height.saturating_sub(area.height);
63 let mut scrollbar_state =
64 ScrollbarState::new(scrollbar_height as usize).position(state.offset.y as usize);
65
66 self.vertical_scrollbar
67 .clone()
68 .render(area, buf, &mut scrollbar_state);
69 }
70
71 fn render_horizontal_scrollbar(
72 &self,
73 area: Rect,
74 buf: &mut Buffer,
75 state: &ScrollViewState,
76 scroll_size: Size,
77 ) {
78 let scrollbar_width = scroll_size.width.saturating_sub(area.width);
79
80 let mut scrollbar_state =
81 ScrollbarState::new(scrollbar_width as usize).position(state.offset.x as usize);
82 self.horizontal_scrollbar
83 .clone()
84 .render(area, buf, &mut scrollbar_state);
85 }
86
87 pub fn visible_scrollbars(&self, horizontal_space: i32, vertical_space: i32) -> (bool, bool) {
88 type V = ScrollbarVisibility;
89
90 match (
91 self.horizontal_scrollbar_visibility,
92 self.vertical_scrollbar_visibility,
93 ) {
94 (V::Always, V::Always) => (true, true),
96 (V::Never, V::Never) => (false, false),
97 (V::Always, V::Never) => (true, false),
98 (V::Never, V::Always) => (false, true),
99
100 (V::Automatic, V::Never) => (horizontal_space < 0, false),
102 (V::Never, V::Automatic) => (false, vertical_space < 0),
103
104 (V::Always, V::Automatic) => (true, vertical_space <= 0),
108 (V::Automatic, V::Always) => (horizontal_space <= 0, true),
109
110 (V::Automatic, V::Automatic) => {
112 if horizontal_space >= 0 && vertical_space >= 0 {
113 (false, false)
115 } else if horizontal_space < 0 && vertical_space < 0 {
116 (true, true)
118 } else if horizontal_space > 0 && vertical_space < 0 {
119 (false, true)
121 } else if horizontal_space < 0 && vertical_space > 0 {
122 (true, false)
124 } else {
125 (true, true)
127 }
128 }
129 }
130 }
131
132 fn render_scrollbars(
133 &self,
134 area: Rect,
135 buf: &mut Buffer,
136 state: &mut ScrollViewState,
137 scroll_buffer: &Buffer,
138 ) -> Rect {
139 let size: ratatui::prelude::Size = scroll_buffer.area.as_size();
140 let horizontal_space = area.width as i32 - size.width as i32;
145 let vertical_space = area.height as i32 - size.height as i32;
146
147 if horizontal_space > 0 {
149 state.offset.x = 0;
150 }
151 if vertical_space > 0 {
152 state.offset.y = 0;
153 }
154
155 let (show_horizontal, show_vertical) =
156 self.visible_scrollbars(horizontal_space, vertical_space);
157
158 let new_height = if show_horizontal {
159 let width = area.width.saturating_sub(show_vertical as u16);
161 let render_area = Rect { width, ..area };
162 self.render_horizontal_scrollbar(render_area, buf, state, size);
164 area.height.saturating_sub(1)
165 } else {
166 area.height
167 };
168
169 let new_width = if show_vertical {
170 let height = area.height.saturating_sub(show_horizontal as u16);
172 let render_area = Rect { height, ..area };
173 self.render_vertical_scrollbar(render_area, buf, state, size);
175 area.width.saturating_sub(1)
176 } else {
177 area.width
178 };
179
180 Rect::new(state.offset.x, state.offset.y, new_width, new_height)
181 }
182}
183
184impl StatefulWidgetRef for ScrollBars<'_> {
185 type State = (ScrollViewState, Buffer);
186
187 fn render_ref(&self, area: Rect, buf: &mut Buffer, (state, scroll_buffer): &mut Self::State) {
188 let (mut x, mut y) = state.offset.into();
189 let max_x_offset = scroll_buffer
191 .area
192 .width
193 .saturating_sub(area.width.saturating_sub(1));
194 let max_y_offset = scroll_buffer
195 .area
196 .height
197 .saturating_sub(area.height.saturating_sub(1));
198
199 x = x.min(max_x_offset);
200 y = y.min(max_y_offset);
201 state.offset = (x, y).into();
202 state.size = Some(scroll_buffer.area.as_size());
203 state.page_size = Some(area.into());
204 let visible_area = self
205 .render_scrollbars(area, buf, state, scroll_buffer)
206 .intersection(scroll_buffer.area);
207 self.render_visible_area(area, buf, visible_area, scroll_buffer);
208 }
209}