chargrid_decorator/
vertical_scroll.rs

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}