1use chargrid_render::*;
2#[cfg(feature = "serialize")]
3use serde::{Deserialize, Serialize};
4
5#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
6#[derive(Debug, Clone, Copy)]
7pub struct VerticalScrollBarStyle {
8 pub style: Style,
9 pub character: char,
10 pub left_padding: u32,
11}
12
13#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
14#[derive(Debug, Clone, Copy)]
15pub struct VerticalScrollLimits {
16 last_rendered_inner_height: u32,
17 last_rendered_outer_height: u32,
18}
19
20#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
21#[derive(Debug, Clone, Copy)]
22pub struct VerticalScrollState {
23 scroll_position: u32,
24}
25
26pub struct VerticalScrollView<'s, 'l, V> {
27 pub view: V,
28 pub scroll_bar_style: &'s VerticalScrollBarStyle,
29 pub limits: &'l mut VerticalScrollLimits,
30 pub state: VerticalScrollState,
31}
32
33impl VerticalScrollBarStyle {
34 pub fn new() -> Self {
35 Self {
36 style: Style::new(),
37 character: '█',
38 left_padding: 1,
39 }
40 }
41}
42
43impl Default for VerticalScrollBarStyle {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49impl VerticalScrollLimits {
50 pub fn new() -> Self {
51 Self {
52 last_rendered_inner_height: 0,
53 last_rendered_outer_height: 0,
54 }
55 }
56 pub fn max_scroll_position(self) -> u32 {
57 self.last_rendered_inner_height
58 .saturating_sub(self.last_rendered_outer_height)
59 }
60}
61
62impl Default for VerticalScrollLimits {
63 fn default() -> Self {
64 Self::new()
65 }
66}
67
68impl VerticalScrollState {
69 pub fn new() -> Self {
70 Self { scroll_position: 0 }
71 }
72 pub fn scroll_to(&mut self, scroll_position: u32, limits: VerticalScrollLimits) {
73 self.scroll_position = scroll_position.min(limits.max_scroll_position());
74 }
75 pub fn scroll_up_lines(&mut self, num_lines: u32, limits: VerticalScrollLimits) {
76 let _ = limits;
77 self.scroll_position = self.scroll_position.saturating_sub(num_lines);
78 }
79 pub fn scroll_down_lines(&mut self, num_lines: u32, limits: VerticalScrollLimits) {
80 let scroll_position = self.scroll_position;
81 self.scroll_to(scroll_position + num_lines, limits)
82 }
83 pub fn scroll_lines(&mut self, num_lines: i32, limits: VerticalScrollLimits) {
84 if num_lines < 0 {
85 self.scroll_up_lines((-num_lines) as u32, limits);
86 } else {
87 self.scroll_down_lines(num_lines as u32, limits);
88 }
89 }
90 pub fn scroll_up_line(&mut self, limits: VerticalScrollLimits) {
91 self.scroll_up_lines(1, limits);
92 }
93 pub fn scroll_down_line(&mut self, limits: VerticalScrollLimits) {
94 self.scroll_down_lines(1, limits);
95 }
96 pub fn scroll_up_page(&mut self, limits: VerticalScrollLimits) {
97 self.scroll_up_lines(limits.last_rendered_outer_height as u32, limits);
98 }
99 pub fn scroll_down_page(&mut self, limits: VerticalScrollLimits) {
100 self.scroll_down_lines(limits.last_rendered_outer_height as u32, limits);
101 }
102 pub fn scroll_to_top(&mut self, limits: VerticalScrollLimits) {
103 let _ = limits;
104 self.scroll_position = 0;
105 }
106 pub fn scroll_to_bottom(&mut self, limits: VerticalScrollLimits) {
107 self.scroll_position = limits.max_scroll_position();
108 }
109 pub fn scroll_position(self) -> u32 {
110 self.scroll_position
111 }
112}
113
114impl Default for VerticalScrollState {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120fn render_scroll_bar<F: Frame, C: ColModify>(
121 scroll_bar_style: &VerticalScrollBarStyle,
122 state: VerticalScrollState,
123 limits: VerticalScrollLimits,
124 context: ViewContext<C>,
125 frame: &mut F,
126) {
127 if limits.last_rendered_inner_height > limits.last_rendered_outer_height {
128 let view_cell = ViewCell {
129 style: scroll_bar_style.style,
130 character: Some(scroll_bar_style.character),
131 };
132 let bar_x = context.size.width() as i32 - 1;
133 let bar_height = (limits.last_rendered_outer_height * limits.last_rendered_outer_height)
134 / limits.last_rendered_inner_height;
135 let bar_top = ((limits.last_rendered_outer_height - bar_height)
136 * state.scroll_position as u32)
137 / limits.max_scroll_position() as u32;
138 for y in 0..bar_height {
139 let bar_y = (y + bar_top) as i32;
140 let coord = Coord::new(bar_x, bar_y);
141 frame.set_cell_relative(coord, 0, view_cell, context);
142 }
143 }
144}
145
146struct PartialFrame<'a, F> {
147 offset: Coord,
148 max_y: i32,
149 frame: &'a mut F,
150}
151
152impl<'a, F> Frame for PartialFrame<'a, F>
153where
154 F: Frame,
155{
156 fn set_cell_relative<C: ColModify>(
157 &mut self,
158 relative_coord: Coord,
159 relative_depth: i8,
160 relative_cell: ViewCell,
161 context: ViewContext<C>,
162 ) {
163 let adjusted_relative_coord = relative_coord - self.offset;
164 self.max_y = self.max_y.max((relative_coord + context.offset).y);
165 if adjusted_relative_coord.is_valid(context.size) {
166 let absolute_coord = adjusted_relative_coord + context.offset;
167 let absolute_depth = relative_depth + context.depth;
168 let absolute_cell = ViewCell {
169 style: Style {
170 foreground: context
171 .col_modify
172 .foreground(relative_cell.style.foreground),
173 background: context
174 .col_modify
175 .background(relative_cell.style.background),
176 ..relative_cell.style
177 },
178 ..relative_cell
179 };
180 self.set_cell_absolute(absolute_coord, absolute_depth, absolute_cell);
181 }
182 }
183
184 fn set_cell_absolute(
185 &mut self,
186 absolute_coord: Coord,
187 absolute_depth: i8,
188 absolute_cell: ViewCell,
189 ) {
190 self.frame
191 .set_cell_absolute(absolute_coord, absolute_depth, absolute_cell);
192 }
193
194 fn blend_cell_background_relative<C: ColModify, B: Blend>(
195 &mut self,
196 relative_coord: Coord,
197 relative_depth: i8,
198 rgb24: Rgb24,
199 alpha: u8,
200 blend: B,
201 context: ViewContext<C>,
202 ) {
203 let adjusted_relative_coord = relative_coord - self.offset;
204 self.max_y = self.max_y.max((relative_coord + context.offset).y);
205 if adjusted_relative_coord.is_valid(context.size) {
206 let absolute_coord = adjusted_relative_coord + context.offset;
207 let absolute_depth = relative_depth + context.depth;
208 if let Some(modified_rgb24) = context.col_modify.background(Some(rgb24)) {
209 self.blend_cell_background_absolute(
210 absolute_coord,
211 absolute_depth,
212 modified_rgb24,
213 alpha,
214 blend,
215 );
216 }
217 }
218 }
219
220 fn blend_cell_background_absolute<B: Blend>(
221 &mut self,
222 absolute_coord: Coord,
223 absolute_depth: i8,
224 rgb24: Rgb24,
225 alpha: u8,
226 blend: B,
227 ) {
228 self.frame.blend_cell_background_absolute(
229 absolute_coord,
230 absolute_depth,
231 rgb24,
232 alpha,
233 blend,
234 );
235 }
236}
237
238impl<'s, 'l, V, T> View<T> for VerticalScrollView<'s, 'l, V>
239where
240 V: View<T>,
241{
242 fn view<F: Frame, C: ColModify>(&mut self, data: T, context: ViewContext<C>, frame: &mut F) {
243 let mut partial_frame = PartialFrame {
244 offset: Coord::new(0, self.state.scroll_position as i32),
245 max_y: 0,
246 frame,
247 };
248 self.view.view(
249 data,
250 context.constrain_size_by(Size::new(1 + self.scroll_bar_style.left_padding, 0)),
251 &mut partial_frame,
252 );
253 self.limits.last_rendered_inner_height =
254 (partial_frame.max_y - context.offset.y).max(0) as u32 + 1;
255 self.limits.last_rendered_outer_height = context.size.height();
256 render_scroll_bar(
257 self.scroll_bar_style,
258 self.state,
259 *self.limits,
260 context,
261 frame,
262 );
263 }
264}