ratatui_kit/components/scroll_view/
scrollbars.rs1use super::ScrollViewState;
17use ratatui::{
18 buffer::Buffer,
19 layout::{Rect, Size},
20 widgets::{Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget},
21};
22use ratatui_kit_macros::Props;
23
24#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
25pub enum ScrollbarVisibility {
27 #[default]
29 Automatic,
30 Always,
32 Never,
34}
35
36#[derive(Props, Clone, Hash)]
37pub struct ScrollBars<'a> {
39 pub vertical_scrollbar_visibility: ScrollbarVisibility,
41 pub horizontal_scrollbar_visibility: ScrollbarVisibility,
43 pub vertical_scrollbar: Scrollbar<'a>,
45 pub horizontal_scrollbar: Scrollbar<'a>,
47}
48
49impl Default for ScrollBars<'_> {
50 fn default() -> Self {
51 Self {
52 vertical_scrollbar_visibility: ScrollbarVisibility::Automatic,
53 horizontal_scrollbar_visibility: ScrollbarVisibility::Automatic,
54 vertical_scrollbar: Scrollbar::new(ScrollbarOrientation::VerticalRight),
55 horizontal_scrollbar: Scrollbar::new(ScrollbarOrientation::HorizontalBottom),
56 }
57 }
58}
59
60impl ScrollBars<'_> {
61 fn render_visible_area(
62 &self,
63 area: Rect,
64 buf: &mut Buffer,
65 visible_area: Rect,
66 scroll_buffer: &Buffer,
67 ) {
68 for (src_row, dst_row) in visible_area.rows().zip(area.rows()) {
70 for (src_col, dst_col) in src_row.columns().zip(dst_row.columns()) {
71 buf[dst_col] = scroll_buffer[src_col].clone();
72 }
73 }
74 }
75
76 fn render_vertical_scrollbar(
77 &self,
78 area: Rect,
79 buf: &mut Buffer,
80 state: &ScrollViewState,
81 scroll_size: Size,
82 ) {
83 let scrollbar_height = scroll_size.height.saturating_sub(area.height);
84 let mut scrollbar_state =
85 ScrollbarState::new(scrollbar_height as usize).position(state.offset.y as usize);
86
87 self.vertical_scrollbar
88 .clone()
89 .render(area, buf, &mut scrollbar_state);
90 }
91
92 fn render_horizontal_scrollbar(
93 &self,
94 area: Rect,
95 buf: &mut Buffer,
96 state: &ScrollViewState,
97 scroll_size: Size,
98 ) {
99 let scrollbar_width = scroll_size.width.saturating_sub(area.width);
100
101 let mut scrollbar_state =
102 ScrollbarState::new(scrollbar_width as usize).position(state.offset.x as usize);
103 self.horizontal_scrollbar
104 .clone()
105 .render(area, buf, &mut scrollbar_state);
106 }
107
108 pub fn visible_scrollbars(&self, horizontal_space: i32, vertical_space: i32) -> (bool, bool) {
109 type V = ScrollbarVisibility;
110
111 match (
112 self.horizontal_scrollbar_visibility,
113 self.vertical_scrollbar_visibility,
114 ) {
115 (V::Always, V::Always) => (true, true),
117 (V::Never, V::Never) => (false, false),
118 (V::Always, V::Never) => (true, false),
119 (V::Never, V::Always) => (false, true),
120
121 (V::Automatic, V::Never) => (horizontal_space < 0, false),
123 (V::Never, V::Automatic) => (false, vertical_space < 0),
124
125 (V::Always, V::Automatic) => (true, vertical_space <= 0),
129 (V::Automatic, V::Always) => (horizontal_space <= 0, true),
130
131 (V::Automatic, V::Automatic) => {
133 if horizontal_space >= 0 && vertical_space >= 0 {
134 (false, false)
136 } else if horizontal_space < 0 && vertical_space < 0 {
137 (true, true)
139 } else if horizontal_space > 0 && vertical_space < 0 {
140 (false, true)
142 } else if horizontal_space < 0 && vertical_space > 0 {
143 (true, false)
145 } else {
146 (true, true)
148 }
149 }
150 }
151 }
152
153 fn render_scrollbars(
154 &self,
155 area: Rect,
156 buf: &mut Buffer,
157 state: &mut ScrollViewState,
158 scroll_buffer: &Buffer,
159 ) -> Rect {
160 let size: ratatui::prelude::Size = scroll_buffer.area.as_size();
161 let horizontal_space = area.width as i32 - size.width as i32;
166 let vertical_space = area.height as i32 - size.height as i32;
167
168 if horizontal_space > 0 {
170 state.offset.x = 0;
171 }
172 if vertical_space > 0 {
173 state.offset.y = 0;
174 }
175
176 let (show_horizontal, show_vertical) =
177 self.visible_scrollbars(horizontal_space, vertical_space);
178
179 let new_height = if show_horizontal {
180 let width = area.width.saturating_sub(show_vertical as u16);
182 let render_area = Rect { width, ..area };
183 self.render_horizontal_scrollbar(render_area, buf, state, size);
185 area.height.saturating_sub(1)
186 } else {
187 area.height
188 };
189
190 let new_width = if show_vertical {
191 let height = area.height.saturating_sub(show_horizontal as u16);
193 let render_area = Rect { height, ..area };
194 self.render_vertical_scrollbar(render_area, buf, state, size);
196 area.width.saturating_sub(1)
197 } else {
198 area.width
199 };
200
201 Rect::new(state.offset.x, state.offset.y, new_width, new_height)
202 }
203
204 pub fn render_ref(
205 &self,
206 area: Rect,
207 buf: &mut Buffer,
208 state: &mut ScrollViewState,
209 scroll_buffer: &Buffer,
210 ) {
211 let (mut x, mut y) = state.offset.into();
212 let max_x_offset = scroll_buffer.area.width.saturating_sub(area.width);
214 let max_y_offset = scroll_buffer.area.height.saturating_sub(area.height);
215
216 x = x.min(max_x_offset);
217 y = y.min(max_y_offset);
218 state.offset = (x, y).into();
219 state.size = Some(scroll_buffer.area.as_size());
220 state.page_size = Some(area.into());
221 let visible_area = self
222 .render_scrollbars(area, buf, state, scroll_buffer)
223 .intersection(scroll_buffer.area);
224 self.render_visible_area(area, buf, visible_area, scroll_buffer);
225 }
226}