tui_textarea/scroll.rs
1use crate::widget::Viewport;
2#[cfg(feature = "serde")]
3use serde::{Deserialize, Serialize};
4
5/// Specify how to scroll the textarea.
6///
7/// This type is marked as `#[non_exhaustive]` since more variations may be supported in the future. Note that the cursor will
8/// not move until it goes out the viewport. See also: [`TextArea::scroll`]
9///
10/// [`TextArea::scroll`]: https://docs.rs/tui-textarea/latest/tui_textarea/struct.TextArea.html#method.scroll
11#[non_exhaustive]
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14pub enum Scrolling {
15 /// Scroll the textarea by rows (vertically) and columns (horizontally). Passing positive scroll amounts to `rows` and `cols`
16 /// scolls it to down and right. Negative integers means the opposite directions. `(i16, i16)` pair can be converted into
17 /// `Scrolling::Delta` where 1st element means rows and 2nd means columns.
18 ///
19 /// ```
20 /// # use ratatui::buffer::Buffer;
21 /// # use ratatui::layout::Rect;
22 /// # use ratatui::widgets::Widget as _;
23 /// use tui_textarea::{TextArea, Scrolling};
24 ///
25 /// // Let's say terminal height is 8.
26 ///
27 /// // Create textarea with 20 lines "0", "1", "2", "3", ...
28 /// let mut textarea: TextArea = (0..20).into_iter().map(|i| i.to_string()).collect();
29 /// # // Call `render` at least once to populate terminal size
30 /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 };
31 /// # let mut b = Buffer::empty(r.clone());
32 /// # textarea.render(r, &mut b);
33 ///
34 /// // Scroll down by 2 lines.
35 /// textarea.scroll(Scrolling::Delta{rows: 2, cols: 0});
36 /// assert_eq!(textarea.cursor(), (2, 0));
37 ///
38 /// // (1, 0) is converted into Scrolling::Delta{rows: 1, cols: 0}
39 /// textarea.scroll((1, 0));
40 /// assert_eq!(textarea.cursor(), (3, 0));
41 /// ```
42 Delta { rows: i16, cols: i16 },
43 /// Scroll down the textarea by one page.
44 ///
45 /// ```
46 /// # use ratatui::buffer::Buffer;
47 /// # use ratatui::layout::Rect;
48 /// # use ratatui::widgets::Widget as _;
49 /// use tui_textarea::{TextArea, Scrolling};
50 ///
51 /// // Let's say terminal height is 8.
52 ///
53 /// // Create textarea with 20 lines "0", "1", "2", "3", ...
54 /// let mut textarea: TextArea = (0..20).into_iter().map(|i| i.to_string()).collect();
55 /// # // Call `render` at least once to populate terminal size
56 /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 };
57 /// # let mut b = Buffer::empty(r.clone());
58 /// # textarea.render(r, &mut b);
59 ///
60 /// // Scroll down by one page (8 lines)
61 /// textarea.scroll(Scrolling::PageDown);
62 /// assert_eq!(textarea.cursor(), (8, 0));
63 /// textarea.scroll(Scrolling::PageDown);
64 /// assert_eq!(textarea.cursor(), (16, 0));
65 /// textarea.scroll(Scrolling::PageDown);
66 /// assert_eq!(textarea.cursor(), (19, 0)); // Reached bottom of the textarea
67 /// ```
68 PageDown,
69 /// Scroll up the textarea by one page.
70 ///
71 /// ```
72 /// # use ratatui::buffer::Buffer;
73 /// # use ratatui::layout::Rect;
74 /// # use ratatui::widgets::Widget as _;
75 /// use tui_textarea::{TextArea, Scrolling, CursorMove};
76 ///
77 /// // Let's say terminal height is 8.
78 ///
79 /// // Create textarea with 20 lines "0", "1", "2", "3", ...
80 /// let mut textarea: TextArea = (0..20).into_iter().map(|i| i.to_string()).collect();
81 /// # // Call `render` at least once to populate terminal size
82 /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 };
83 /// # let mut b = Buffer::empty(r.clone());
84 /// # textarea.render(r.clone(), &mut b);
85 ///
86 /// // Go to the last line at first
87 /// textarea.move_cursor(CursorMove::Bottom);
88 /// assert_eq!(textarea.cursor(), (19, 0));
89 /// # // Call `render` to populate terminal size
90 /// # textarea.render(r.clone(), &mut b);
91 ///
92 /// // Scroll up by one page (8 lines)
93 /// textarea.scroll(Scrolling::PageUp);
94 /// assert_eq!(textarea.cursor(), (11, 0));
95 /// textarea.scroll(Scrolling::PageUp);
96 /// assert_eq!(textarea.cursor(), (7, 0)); // Reached top of the textarea
97 /// ```
98 PageUp,
99 /// Scroll down the textarea by half of the page.
100 ///
101 /// ```
102 /// # use ratatui::buffer::Buffer;
103 /// # use ratatui::layout::Rect;
104 /// # use ratatui::widgets::Widget as _;
105 /// use tui_textarea::{TextArea, Scrolling};
106 ///
107 /// // Let's say terminal height is 8.
108 ///
109 /// // Create textarea with 10 lines "0", "1", "2", "3", ...
110 /// let mut textarea: TextArea = (0..10).into_iter().map(|i| i.to_string()).collect();
111 /// # // Call `render` at least once to populate terminal size
112 /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 };
113 /// # let mut b = Buffer::empty(r.clone());
114 /// # textarea.render(r, &mut b);
115 ///
116 /// // Scroll down by half-page (4 lines)
117 /// textarea.scroll(Scrolling::HalfPageDown);
118 /// assert_eq!(textarea.cursor(), (4, 0));
119 /// textarea.scroll(Scrolling::HalfPageDown);
120 /// assert_eq!(textarea.cursor(), (8, 0));
121 /// textarea.scroll(Scrolling::HalfPageDown);
122 /// assert_eq!(textarea.cursor(), (9, 0)); // Reached bottom of the textarea
123 /// ```
124 HalfPageDown,
125 /// Scroll up the textarea by half of the page.
126 ///
127 /// ```
128 /// # use ratatui::buffer::Buffer;
129 /// # use ratatui::layout::Rect;
130 /// # use ratatui::widgets::Widget as _;
131 /// use tui_textarea::{TextArea, Scrolling, CursorMove};
132 ///
133 /// // Let's say terminal height is 8.
134 ///
135 /// // Create textarea with 20 lines "0", "1", "2", "3", ...
136 /// let mut textarea: TextArea = (0..20).into_iter().map(|i| i.to_string()).collect();
137 /// # // Call `render` at least once to populate terminal size
138 /// # let r = Rect { x: 0, y: 0, width: 24, height: 8 };
139 /// # let mut b = Buffer::empty(r.clone());
140 /// # textarea.render(r.clone(), &mut b);
141 ///
142 /// // Go to the last line at first
143 /// textarea.move_cursor(CursorMove::Bottom);
144 /// assert_eq!(textarea.cursor(), (19, 0));
145 /// # // Call `render` to populate terminal size
146 /// # textarea.render(r.clone(), &mut b);
147 ///
148 /// // Scroll up by half-page (4 lines)
149 /// textarea.scroll(Scrolling::HalfPageUp);
150 /// assert_eq!(textarea.cursor(), (15, 0));
151 /// textarea.scroll(Scrolling::HalfPageUp);
152 /// assert_eq!(textarea.cursor(), (11, 0));
153 /// ```
154 HalfPageUp,
155}
156
157impl Scrolling {
158 pub(crate) fn scroll(self, viewport: &mut Viewport) {
159 let (rows, cols) = match self {
160 Self::Delta { rows, cols } => (rows, cols),
161 Self::PageDown => {
162 let (_, _, _, height) = viewport.rect();
163 (height as i16, 0)
164 }
165 Self::PageUp => {
166 let (_, _, _, height) = viewport.rect();
167 (-(height as i16), 0)
168 }
169 Self::HalfPageDown => {
170 let (_, _, _, height) = viewport.rect();
171 ((height as i16) / 2, 0)
172 }
173 Self::HalfPageUp => {
174 let (_, _, _, height) = viewport.rect();
175 (-(height as i16) / 2, 0)
176 }
177 };
178 viewport.scroll(rows, cols);
179 }
180}
181
182impl From<(i16, i16)> for Scrolling {
183 fn from((rows, cols): (i16, i16)) -> Self {
184 Self::Delta { rows, cols }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 // Separate tests for tui-rs support
193 #[test]
194 fn delta() {
195 use crate::ratatui::buffer::Buffer;
196 use crate::ratatui::layout::Rect;
197 use crate::ratatui::widgets::Widget as _;
198 use crate::TextArea;
199
200 let mut textarea: TextArea = (0..20).map(|i| i.to_string()).collect();
201 let r = Rect {
202 x: 0,
203 y: 0,
204 width: 24,
205 height: 8,
206 };
207 let mut b = Buffer::empty(r);
208 textarea.render(r, &mut b);
209
210 textarea.scroll(Scrolling::Delta { rows: 2, cols: 0 });
211 assert_eq!(textarea.cursor(), (2, 0));
212
213 textarea.scroll((1, 0));
214 assert_eq!(textarea.cursor(), (3, 0));
215 }
216}