@akiojin/gwt 9.0.4 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +106 -146
- package/README.md +103 -143
- package/bin/gwt.cjs +1 -1
- package/package.json +5 -5
- package/rustfmt.toml +0 -2
- package/scripts/check-release-flow.sh +2 -8
- package/scripts/postinstall.js +17 -7
- package/scripts/run-local-backend-tests-on-commit.sh +6 -12
- package/scripts/test-all.sh +1 -5
- package/scripts/check-e2e-coverage-threshold.mjs +0 -238
- package/scripts/run-local-e2e-coverage-on-commit.sh +0 -69
- package/scripts/run-local-e2e-on-commit.sh +0 -60
- package/scripts/verify-ci-node-toolchain.sh +0 -76
- package/scripts/voice-eval.sh +0 -48
- package/vendor/ratatui-core/src/backend/test.rs +0 -1077
- package/vendor/ratatui-core/src/backend.rs +0 -405
- package/vendor/ratatui-core/src/buffer/assert.rs +0 -71
- package/vendor/ratatui-core/src/buffer/buffer.rs +0 -1388
- package/vendor/ratatui-core/src/buffer/cell.rs +0 -377
- package/vendor/ratatui-core/src/buffer.rs +0 -9
- package/vendor/ratatui-core/src/layout/alignment.rs +0 -89
- package/vendor/ratatui-core/src/layout/constraint.rs +0 -526
- package/vendor/ratatui-core/src/layout/direction.rs +0 -63
- package/vendor/ratatui-core/src/layout/flex.rs +0 -212
- package/vendor/ratatui-core/src/layout/layout.rs +0 -2838
- package/vendor/ratatui-core/src/layout/margin.rs +0 -79
- package/vendor/ratatui-core/src/layout/offset.rs +0 -66
- package/vendor/ratatui-core/src/layout/position.rs +0 -253
- package/vendor/ratatui-core/src/layout/rect/iter.rs +0 -356
- package/vendor/ratatui-core/src/layout/rect/ops.rs +0 -136
- package/vendor/ratatui-core/src/layout/rect.rs +0 -1114
- package/vendor/ratatui-core/src/layout/size.rs +0 -147
- package/vendor/ratatui-core/src/layout.rs +0 -333
- package/vendor/ratatui-core/src/lib.rs +0 -82
- package/vendor/ratatui-core/src/style/anstyle.rs +0 -348
- package/vendor/ratatui-core/src/style/color.rs +0 -788
- package/vendor/ratatui-core/src/style/palette/material.rs +0 -608
- package/vendor/ratatui-core/src/style/palette/tailwind.rs +0 -653
- package/vendor/ratatui-core/src/style/palette.rs +0 -6
- package/vendor/ratatui-core/src/style/palette_conversion.rs +0 -82
- package/vendor/ratatui-core/src/style/stylize.rs +0 -668
- package/vendor/ratatui-core/src/style.rs +0 -1069
- package/vendor/ratatui-core/src/symbols/bar.rs +0 -51
- package/vendor/ratatui-core/src/symbols/block.rs +0 -51
- package/vendor/ratatui-core/src/symbols/border.rs +0 -709
- package/vendor/ratatui-core/src/symbols/braille.rs +0 -21
- package/vendor/ratatui-core/src/symbols/half_block.rs +0 -3
- package/vendor/ratatui-core/src/symbols/line.rs +0 -259
- package/vendor/ratatui-core/src/symbols/marker.rs +0 -82
- package/vendor/ratatui-core/src/symbols/merge.rs +0 -748
- package/vendor/ratatui-core/src/symbols/pixel.rs +0 -30
- package/vendor/ratatui-core/src/symbols/scrollbar.rs +0 -46
- package/vendor/ratatui-core/src/symbols/shade.rs +0 -5
- package/vendor/ratatui-core/src/symbols.rs +0 -15
- package/vendor/ratatui-core/src/terminal/frame.rs +0 -192
- package/vendor/ratatui-core/src/terminal/terminal.rs +0 -926
- package/vendor/ratatui-core/src/terminal/viewport.rs +0 -58
- package/vendor/ratatui-core/src/terminal.rs +0 -40
- package/vendor/ratatui-core/src/text/grapheme.rs +0 -84
- package/vendor/ratatui-core/src/text/line.rs +0 -1678
- package/vendor/ratatui-core/src/text/masked.rs +0 -149
- package/vendor/ratatui-core/src/text/span.rs +0 -904
- package/vendor/ratatui-core/src/text/text.rs +0 -1434
- package/vendor/ratatui-core/src/text.rs +0 -64
- package/vendor/ratatui-core/src/widgets/stateful_widget.rs +0 -193
- package/vendor/ratatui-core/src/widgets/widget.rs +0 -174
- package/vendor/ratatui-core/src/widgets.rs +0 -9
|
@@ -1,926 +0,0 @@
|
|
|
1
|
-
use crate::backend::{Backend, ClearType};
|
|
2
|
-
use crate::buffer::{Buffer, Cell};
|
|
3
|
-
use crate::layout::{Position, Rect, Size};
|
|
4
|
-
use crate::terminal::{CompletedFrame, Frame, TerminalOptions, Viewport};
|
|
5
|
-
|
|
6
|
-
/// An interface to interact and draw [`Frame`]s on the user's terminal.
|
|
7
|
-
///
|
|
8
|
-
/// This is the main entry point for Ratatui. It is responsible for drawing and maintaining the
|
|
9
|
-
/// state of the buffers, cursor and viewport.
|
|
10
|
-
///
|
|
11
|
-
/// The [`Terminal`] is generic over a [`Backend`] implementation which is used to interface with
|
|
12
|
-
/// the underlying terminal library. The [`Backend`] trait is implemented for three popular Rust
|
|
13
|
-
/// terminal libraries: [Crossterm], [Termion] and [Termwiz]. See the [`backend`] module for more
|
|
14
|
-
/// information.
|
|
15
|
-
///
|
|
16
|
-
/// The `Terminal` struct maintains two buffers: the current and the previous.
|
|
17
|
-
/// When the widgets are drawn, the changes are accumulated in the current buffer.
|
|
18
|
-
/// At the end of each draw pass, the two buffers are compared, and only the changes
|
|
19
|
-
/// between these buffers are written to the terminal, avoiding any redundant operations.
|
|
20
|
-
/// After flushing these changes, the buffers are swapped to prepare for the next draw cycle.
|
|
21
|
-
///
|
|
22
|
-
/// The terminal also has a viewport which is the area of the terminal that is currently visible to
|
|
23
|
-
/// the user. It can be either fullscreen, inline or fixed. See [`Viewport`] for more information.
|
|
24
|
-
///
|
|
25
|
-
/// Applications should detect terminal resizes and call [`Terminal::draw`] to redraw the
|
|
26
|
-
/// application with the new size. This will automatically resize the internal buffers to match the
|
|
27
|
-
/// new size for inline and fullscreen viewports. Fixed viewports are not resized automatically.
|
|
28
|
-
///
|
|
29
|
-
/// # Initialization
|
|
30
|
-
///
|
|
31
|
-
/// For most applications, consider using the convenience functions `ratatui::run()`,
|
|
32
|
-
/// `ratatui::init()`, and `ratatui::restore()` (available since version 0.28.1) along with the
|
|
33
|
-
/// `DefaultTerminal` type alias instead of constructing `Terminal` instances manually. These
|
|
34
|
-
/// functions handle the common setup and teardown tasks automatically. Manual construction
|
|
35
|
-
/// using `Terminal::new()` or `Terminal::with_options()` is still supported for applications
|
|
36
|
-
/// that need fine-grained control over initialization.
|
|
37
|
-
///
|
|
38
|
-
/// # Examples
|
|
39
|
-
///
|
|
40
|
-
/// ## Using convenience functions (recommended for most applications)
|
|
41
|
-
///
|
|
42
|
-
/// ```rust,ignore
|
|
43
|
-
/// // Modern approach using convenience functions
|
|
44
|
-
/// ratatui::run(|terminal| {
|
|
45
|
-
/// terminal.draw(|frame| {
|
|
46
|
-
/// let area = frame.area();
|
|
47
|
-
/// frame.render_widget(Paragraph::new("Hello World!"), area);
|
|
48
|
-
/// })?;
|
|
49
|
-
/// Ok(())
|
|
50
|
-
/// })?;
|
|
51
|
-
/// ```
|
|
52
|
-
///
|
|
53
|
-
/// ## Manual construction (for fine-grained control)
|
|
54
|
-
///
|
|
55
|
-
/// ```rust,ignore
|
|
56
|
-
/// use std::io::stdout;
|
|
57
|
-
///
|
|
58
|
-
/// use ratatui::{backend::CrosstermBackend, widgets::Paragraph, Terminal};
|
|
59
|
-
///
|
|
60
|
-
/// let backend = CrosstermBackend::new(stdout());
|
|
61
|
-
/// let mut terminal = Terminal::new(backend)?;
|
|
62
|
-
/// terminal.draw(|frame| {
|
|
63
|
-
/// let area = frame.area();
|
|
64
|
-
/// frame.render_widget(Paragraph::new("Hello World!"), area);
|
|
65
|
-
/// })?;
|
|
66
|
-
/// # std::io::Result::Ok(())
|
|
67
|
-
/// ```
|
|
68
|
-
///
|
|
69
|
-
/// [Crossterm]: https://crates.io/crates/crossterm
|
|
70
|
-
/// [Termion]: https://crates.io/crates/termion
|
|
71
|
-
/// [Termwiz]: https://crates.io/crates/termwiz
|
|
72
|
-
/// [`backend`]: crate::backend
|
|
73
|
-
/// [`Backend`]: crate::backend::Backend
|
|
74
|
-
/// [`Buffer`]: crate::buffer::Buffer
|
|
75
|
-
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
|
76
|
-
pub struct Terminal<B>
|
|
77
|
-
where
|
|
78
|
-
B: Backend,
|
|
79
|
-
{
|
|
80
|
-
/// The backend used to interface with the terminal
|
|
81
|
-
backend: B,
|
|
82
|
-
/// Holds the results of the current and previous draw calls. The two are compared at the end
|
|
83
|
-
/// of each draw pass to output the necessary updates to the terminal
|
|
84
|
-
buffers: [Buffer; 2],
|
|
85
|
-
/// Index of the current buffer in the previous array
|
|
86
|
-
current: usize,
|
|
87
|
-
/// Whether the cursor is currently hidden
|
|
88
|
-
hidden_cursor: bool,
|
|
89
|
-
/// Viewport
|
|
90
|
-
viewport: Viewport,
|
|
91
|
-
/// Area of the viewport
|
|
92
|
-
viewport_area: Rect,
|
|
93
|
-
/// Last known area of the terminal. Used to detect if the internal buffers have to be resized.
|
|
94
|
-
last_known_area: Rect,
|
|
95
|
-
/// Last known position of the cursor. Used to find the new area when the viewport is inlined
|
|
96
|
-
/// and the terminal resized.
|
|
97
|
-
last_known_cursor_pos: Position,
|
|
98
|
-
/// Number of frames rendered up until current time.
|
|
99
|
-
frame_count: usize,
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/// Options to pass to [`Terminal::with_options`]
|
|
103
|
-
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
|
104
|
-
pub struct Options {
|
|
105
|
-
/// Viewport used to draw to the terminal
|
|
106
|
-
pub viewport: Viewport,
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
impl<B> Drop for Terminal<B>
|
|
110
|
-
where
|
|
111
|
-
B: Backend,
|
|
112
|
-
{
|
|
113
|
-
fn drop(&mut self) {
|
|
114
|
-
// Attempt to restore the cursor state
|
|
115
|
-
if self.hidden_cursor {
|
|
116
|
-
#[allow(unused_variables)]
|
|
117
|
-
if let Err(err) = self.show_cursor() {
|
|
118
|
-
#[cfg(feature = "std")]
|
|
119
|
-
std::eprintln!("Failed to show the cursor: {err}");
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
impl<B> Terminal<B>
|
|
126
|
-
where
|
|
127
|
-
B: Backend,
|
|
128
|
-
{
|
|
129
|
-
/// Creates a new [`Terminal`] with the given [`Backend`] with a full screen viewport.
|
|
130
|
-
///
|
|
131
|
-
/// Note that unlike `ratatui::init`, this does not install a panic hook, so it is recommended
|
|
132
|
-
/// to do that manually when using this function, otherwise any panic messages will be printed
|
|
133
|
-
/// to the alternate screen and the terminal may be left in an unusable state.
|
|
134
|
-
///
|
|
135
|
-
/// See [how to set up panic hooks](https://ratatui.rs/recipes/apps/panic-hooks/) and
|
|
136
|
-
/// [`better-panic` example](https://ratatui.rs/recipes/apps/better-panic/) for more
|
|
137
|
-
/// information.
|
|
138
|
-
///
|
|
139
|
-
/// # Example
|
|
140
|
-
///
|
|
141
|
-
/// ```rust,ignore
|
|
142
|
-
/// use std::io::stdout;
|
|
143
|
-
///
|
|
144
|
-
/// use ratatui::{backend::CrosstermBackend, Terminal};
|
|
145
|
-
///
|
|
146
|
-
/// let backend = CrosstermBackend::new(stdout());
|
|
147
|
-
/// let terminal = Terminal::new(backend)?;
|
|
148
|
-
///
|
|
149
|
-
/// // Optionally set up a panic hook to restore the terminal on panic.
|
|
150
|
-
/// let old_hook = std::panic::take_hook();
|
|
151
|
-
/// std::panic::set_hook(Box::new(move |info| {
|
|
152
|
-
/// ratatui::restore();
|
|
153
|
-
/// old_hook(info);
|
|
154
|
-
/// }));
|
|
155
|
-
/// # std::io::Result::Ok(())
|
|
156
|
-
/// ```
|
|
157
|
-
pub fn new(backend: B) -> Result<Self, B::Error> {
|
|
158
|
-
Self::with_options(
|
|
159
|
-
backend,
|
|
160
|
-
TerminalOptions {
|
|
161
|
-
viewport: Viewport::Fullscreen,
|
|
162
|
-
},
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/// Creates a new [`Terminal`] with the given [`Backend`] and [`TerminalOptions`].
|
|
167
|
-
///
|
|
168
|
-
/// # Example
|
|
169
|
-
///
|
|
170
|
-
/// ```rust,ignore
|
|
171
|
-
/// use std::io::stdout;
|
|
172
|
-
///
|
|
173
|
-
/// use ratatui::{backend::CrosstermBackend, layout::Rect, Terminal, TerminalOptions, Viewport};
|
|
174
|
-
///
|
|
175
|
-
/// let backend = CrosstermBackend::new(stdout());
|
|
176
|
-
/// let viewport = Viewport::Fixed(Rect::new(0, 0, 10, 10));
|
|
177
|
-
/// let terminal = Terminal::with_options(backend, TerminalOptions { viewport })?;
|
|
178
|
-
/// # std::io::Result::Ok(())
|
|
179
|
-
/// ```
|
|
180
|
-
pub fn with_options(mut backend: B, options: TerminalOptions) -> Result<Self, B::Error> {
|
|
181
|
-
let area = match options.viewport {
|
|
182
|
-
Viewport::Fullscreen | Viewport::Inline(_) => backend.size()?.into(),
|
|
183
|
-
Viewport::Fixed(area) => area,
|
|
184
|
-
};
|
|
185
|
-
let (viewport_area, cursor_pos) = match options.viewport {
|
|
186
|
-
Viewport::Fullscreen => (area, Position::ORIGIN),
|
|
187
|
-
Viewport::Inline(height) => {
|
|
188
|
-
compute_inline_size(&mut backend, height, area.as_size(), 0)?
|
|
189
|
-
}
|
|
190
|
-
Viewport::Fixed(area) => (area, area.as_position()),
|
|
191
|
-
};
|
|
192
|
-
Ok(Self {
|
|
193
|
-
backend,
|
|
194
|
-
buffers: [Buffer::empty(viewport_area), Buffer::empty(viewport_area)],
|
|
195
|
-
current: 0,
|
|
196
|
-
hidden_cursor: false,
|
|
197
|
-
viewport: options.viewport,
|
|
198
|
-
viewport_area,
|
|
199
|
-
last_known_area: area,
|
|
200
|
-
last_known_cursor_pos: cursor_pos,
|
|
201
|
-
frame_count: 0,
|
|
202
|
-
})
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/// Get a Frame object which provides a consistent view into the terminal state for rendering.
|
|
206
|
-
///
|
|
207
|
-
/// # Note
|
|
208
|
-
///
|
|
209
|
-
/// This exists to support more advanced use cases. Most cases should be fine using
|
|
210
|
-
/// [`Terminal::draw`].
|
|
211
|
-
///
|
|
212
|
-
/// [`Terminal::get_frame`] should be used when you need direct access to the frame buffer
|
|
213
|
-
/// outside of draw closure, for example:
|
|
214
|
-
///
|
|
215
|
-
/// - Unit testing widgets
|
|
216
|
-
/// - Buffer state inspection
|
|
217
|
-
/// - Cursor manipulation
|
|
218
|
-
/// - Multiple rendering passes/Buffer Manipulation
|
|
219
|
-
/// - Custom frame lifecycle management
|
|
220
|
-
/// - Buffer exporting
|
|
221
|
-
///
|
|
222
|
-
/// # Example
|
|
223
|
-
///
|
|
224
|
-
/// Getting the buffer and asserting on some cells after rendering a widget.
|
|
225
|
-
///
|
|
226
|
-
/// ```rust,ignore
|
|
227
|
-
/// use ratatui::{backend::TestBackend, Terminal};
|
|
228
|
-
/// use ratatui::widgets::Paragraph;
|
|
229
|
-
/// let backend = TestBackend::new(30, 5);
|
|
230
|
-
/// let mut terminal = Terminal::new(backend).unwrap();
|
|
231
|
-
/// {
|
|
232
|
-
/// let mut frame = terminal.get_frame();
|
|
233
|
-
/// frame.render_widget(Paragraph::new("Hello"), frame.area());
|
|
234
|
-
/// }
|
|
235
|
-
/// // When not using `draw`, present the buffer manually:
|
|
236
|
-
/// terminal.flush().unwrap();
|
|
237
|
-
/// terminal.swap_buffers();
|
|
238
|
-
/// terminal.backend_mut().flush().unwrap();
|
|
239
|
-
/// ```
|
|
240
|
-
pub const fn get_frame(&mut self) -> Frame<'_> {
|
|
241
|
-
let count = self.frame_count;
|
|
242
|
-
Frame {
|
|
243
|
-
cursor_position: None,
|
|
244
|
-
viewport_area: self.viewport_area,
|
|
245
|
-
buffer: self.current_buffer_mut(),
|
|
246
|
-
count,
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/// Gets the current buffer as a mutable reference.
|
|
251
|
-
pub const fn current_buffer_mut(&mut self) -> &mut Buffer {
|
|
252
|
-
&mut self.buffers[self.current]
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/// Gets the backend
|
|
256
|
-
pub const fn backend(&self) -> &B {
|
|
257
|
-
&self.backend
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/// Gets the backend as a mutable reference
|
|
261
|
-
pub const fn backend_mut(&mut self) -> &mut B {
|
|
262
|
-
&mut self.backend
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/// Obtains a difference between the previous and the current buffer and passes it to the
|
|
266
|
-
/// current backend for drawing.
|
|
267
|
-
pub fn flush(&mut self) -> Result<(), B::Error> {
|
|
268
|
-
let previous_buffer = &self.buffers[1 - self.current];
|
|
269
|
-
let current_buffer = &self.buffers[self.current];
|
|
270
|
-
let updates = previous_buffer.diff(current_buffer);
|
|
271
|
-
if let Some((col, row, _)) = updates.last() {
|
|
272
|
-
self.last_known_cursor_pos = Position { x: *col, y: *row };
|
|
273
|
-
}
|
|
274
|
-
self.backend.draw(updates.into_iter())
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/// Updates the Terminal so that internal buffers match the requested area.
|
|
278
|
-
///
|
|
279
|
-
/// Requested area will be saved to remain consistent when rendering. This leads to a full clear
|
|
280
|
-
/// of the screen.
|
|
281
|
-
pub fn resize(&mut self, area: Rect) -> Result<(), B::Error> {
|
|
282
|
-
let next_area = match self.viewport {
|
|
283
|
-
Viewport::Inline(height) => {
|
|
284
|
-
let offset_in_previous_viewport = self
|
|
285
|
-
.last_known_cursor_pos
|
|
286
|
-
.y
|
|
287
|
-
.saturating_sub(self.viewport_area.top());
|
|
288
|
-
compute_inline_size(
|
|
289
|
-
&mut self.backend,
|
|
290
|
-
height,
|
|
291
|
-
area.as_size(),
|
|
292
|
-
offset_in_previous_viewport,
|
|
293
|
-
)?
|
|
294
|
-
.0
|
|
295
|
-
}
|
|
296
|
-
Viewport::Fixed(_) | Viewport::Fullscreen => area,
|
|
297
|
-
};
|
|
298
|
-
self.set_viewport_area(next_area);
|
|
299
|
-
self.clear()?;
|
|
300
|
-
|
|
301
|
-
self.last_known_area = area;
|
|
302
|
-
Ok(())
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
fn set_viewport_area(&mut self, area: Rect) {
|
|
306
|
-
self.buffers[self.current].resize(area);
|
|
307
|
-
self.buffers[1 - self.current].resize(area);
|
|
308
|
-
self.viewport_area = area;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/// Queries the backend for size and resizes if it doesn't match the previous size.
|
|
312
|
-
pub fn autoresize(&mut self) -> Result<(), B::Error> {
|
|
313
|
-
// fixed viewports do not get autoresized
|
|
314
|
-
if matches!(self.viewport, Viewport::Fullscreen | Viewport::Inline(_)) {
|
|
315
|
-
let area = self.size()?.into();
|
|
316
|
-
if area != self.last_known_area {
|
|
317
|
-
self.resize(area)?;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
Ok(())
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/// Draws a single frame to the terminal.
|
|
324
|
-
///
|
|
325
|
-
/// Returns a [`CompletedFrame`] if successful, otherwise a [`std::io::Error`].
|
|
326
|
-
///
|
|
327
|
-
/// If the render callback passed to this method can fail, use [`try_draw`] instead.
|
|
328
|
-
///
|
|
329
|
-
/// Applications should call `draw` or [`try_draw`] in a loop to continuously render the
|
|
330
|
-
/// terminal. These methods are the main entry points for drawing to the terminal.
|
|
331
|
-
///
|
|
332
|
-
/// [`try_draw`]: Terminal::try_draw
|
|
333
|
-
///
|
|
334
|
-
/// This method will:
|
|
335
|
-
///
|
|
336
|
-
/// - autoresize the terminal if necessary
|
|
337
|
-
/// - call the render callback, passing it a [`Frame`] reference to render to
|
|
338
|
-
/// - flush the current internal state by copying the current buffer to the backend
|
|
339
|
-
/// - move the cursor to the last known position if it was set during the rendering closure
|
|
340
|
-
/// - return a [`CompletedFrame`] with the current buffer and the area of the terminal
|
|
341
|
-
///
|
|
342
|
-
/// The [`CompletedFrame`] returned by this method can be useful for debugging or testing
|
|
343
|
-
/// purposes, but it is often not used in regular applications.
|
|
344
|
-
///
|
|
345
|
-
/// The render callback should fully render the entire frame when called, including areas that
|
|
346
|
-
/// are unchanged from the previous frame. This is because each frame is compared to the
|
|
347
|
-
/// previous frame to determine what has changed, and only the changes are written to the
|
|
348
|
-
/// terminal. If the render callback does not fully render the frame, the terminal will not be
|
|
349
|
-
/// in a consistent state.
|
|
350
|
-
///
|
|
351
|
-
/// # Examples
|
|
352
|
-
///
|
|
353
|
-
/// ```rust,ignore
|
|
354
|
-
/// # let backend = ratatui::backend::TestBackend::new(10, 10);
|
|
355
|
-
/// # let mut terminal = ratatui::Terminal::new(backend)?;
|
|
356
|
-
/// use ratatui::{layout::Position, widgets::Paragraph};
|
|
357
|
-
///
|
|
358
|
-
/// // with a closure
|
|
359
|
-
/// terminal.draw(|frame| {
|
|
360
|
-
/// let area = frame.area();
|
|
361
|
-
/// frame.render_widget(Paragraph::new("Hello World!"), area);
|
|
362
|
-
/// frame.set_cursor_position(Position { x: 0, y: 0 });
|
|
363
|
-
/// })?;
|
|
364
|
-
///
|
|
365
|
-
/// // or with a function
|
|
366
|
-
/// terminal.draw(render)?;
|
|
367
|
-
///
|
|
368
|
-
/// fn render(frame: &mut ratatui::Frame) {
|
|
369
|
-
/// frame.render_widget(Paragraph::new("Hello World!"), frame.area());
|
|
370
|
-
/// }
|
|
371
|
-
/// # std::io::Result::Ok(())
|
|
372
|
-
/// ```
|
|
373
|
-
pub fn draw<F>(&mut self, render_callback: F) -> Result<CompletedFrame<'_>, B::Error>
|
|
374
|
-
where
|
|
375
|
-
F: FnOnce(&mut Frame),
|
|
376
|
-
{
|
|
377
|
-
self.try_draw(|frame| {
|
|
378
|
-
render_callback(frame);
|
|
379
|
-
Ok::<(), B::Error>(())
|
|
380
|
-
})
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/// Tries to draw a single frame to the terminal.
|
|
384
|
-
///
|
|
385
|
-
/// Returns [`Result::Ok`] containing a [`CompletedFrame`] if successful, otherwise
|
|
386
|
-
/// [`Result::Err`] containing the [`std::io::Error`] that caused the failure.
|
|
387
|
-
///
|
|
388
|
-
/// This is the equivalent of [`Terminal::draw`] but the render callback is a function or
|
|
389
|
-
/// closure that returns a `Result` instead of nothing.
|
|
390
|
-
///
|
|
391
|
-
/// Applications should call `try_draw` or [`draw`] in a loop to continuously render the
|
|
392
|
-
/// terminal. These methods are the main entry points for drawing to the terminal.
|
|
393
|
-
///
|
|
394
|
-
/// [`draw`]: Terminal::draw
|
|
395
|
-
///
|
|
396
|
-
/// This method will:
|
|
397
|
-
///
|
|
398
|
-
/// - autoresize the terminal if necessary
|
|
399
|
-
/// - call the render callback, passing it a [`Frame`] reference to render to
|
|
400
|
-
/// - flush the current internal state by copying the current buffer to the backend
|
|
401
|
-
/// - move the cursor to the last known position if it was set during the rendering closure
|
|
402
|
-
/// - return a [`CompletedFrame`] with the current buffer and the area of the terminal
|
|
403
|
-
///
|
|
404
|
-
/// The render callback passed to `try_draw` can return any [`Result`] with an error type that
|
|
405
|
-
/// can be converted into an [`std::io::Error`] using the [`Into`] trait. This makes it possible
|
|
406
|
-
/// to use the `?` operator to propagate errors that occur during rendering. If the render
|
|
407
|
-
/// callback returns an error, the error will be returned from `try_draw` as an
|
|
408
|
-
/// [`std::io::Error`] and the terminal will not be updated.
|
|
409
|
-
///
|
|
410
|
-
/// The [`CompletedFrame`] returned by this method can be useful for debugging or testing
|
|
411
|
-
/// purposes, but it is often not used in regular applications.
|
|
412
|
-
///
|
|
413
|
-
/// The render callback should fully render the entire frame when called, including areas that
|
|
414
|
-
/// are unchanged from the previous frame. This is because each frame is compared to the
|
|
415
|
-
/// previous frame to determine what has changed, and only the changes are written to the
|
|
416
|
-
/// terminal. If the render function does not fully render the frame, the terminal will not be
|
|
417
|
-
/// in a consistent state.
|
|
418
|
-
///
|
|
419
|
-
/// # Examples
|
|
420
|
-
///
|
|
421
|
-
/// ```ignore
|
|
422
|
-
/// # use ratatui::layout::Position;;
|
|
423
|
-
/// # let backend = ratatui::backend::TestBackend::new(10, 10);
|
|
424
|
-
/// # let mut terminal = ratatui::Terminal::new(backend)?;
|
|
425
|
-
/// use std::io;
|
|
426
|
-
///
|
|
427
|
-
/// use ratatui::widgets::Paragraph;
|
|
428
|
-
///
|
|
429
|
-
/// // with a closure
|
|
430
|
-
/// terminal.try_draw(|frame| {
|
|
431
|
-
/// let value: u8 = "not a number".parse().map_err(io::Error::other)?;
|
|
432
|
-
/// let area = frame.area();
|
|
433
|
-
/// frame.render_widget(Paragraph::new("Hello World!"), area);
|
|
434
|
-
/// frame.set_cursor_position(Position { x: 0, y: 0 });
|
|
435
|
-
/// io::Result::Ok(())
|
|
436
|
-
/// })?;
|
|
437
|
-
///
|
|
438
|
-
/// // or with a function
|
|
439
|
-
/// terminal.try_draw(render)?;
|
|
440
|
-
///
|
|
441
|
-
/// fn render(frame: &mut ratatui::Frame) -> io::Result<()> {
|
|
442
|
-
/// let value: u8 = "not a number".parse().map_err(io::Error::other)?;
|
|
443
|
-
/// frame.render_widget(Paragraph::new("Hello World!"), frame.area());
|
|
444
|
-
/// Ok(())
|
|
445
|
-
/// }
|
|
446
|
-
/// # io::Result::Ok(())
|
|
447
|
-
/// ```
|
|
448
|
-
pub fn try_draw<F, E>(&mut self, render_callback: F) -> Result<CompletedFrame<'_>, B::Error>
|
|
449
|
-
where
|
|
450
|
-
F: FnOnce(&mut Frame) -> Result<(), E>,
|
|
451
|
-
E: Into<B::Error>,
|
|
452
|
-
{
|
|
453
|
-
// Autoresize - otherwise we get glitches if shrinking or potential desync between widgets
|
|
454
|
-
// and the terminal (if growing), which may OOB.
|
|
455
|
-
self.autoresize()?;
|
|
456
|
-
|
|
457
|
-
let mut frame = self.get_frame();
|
|
458
|
-
|
|
459
|
-
render_callback(&mut frame).map_err(Into::into)?;
|
|
460
|
-
|
|
461
|
-
// We can't change the cursor position right away because we have to flush the frame to
|
|
462
|
-
// stdout first. But we also can't keep the frame around, since it holds a &mut to
|
|
463
|
-
// Buffer. Thus, we're taking the important data out of the Frame and dropping it.
|
|
464
|
-
let cursor_position = frame.cursor_position;
|
|
465
|
-
|
|
466
|
-
// Draw to stdout
|
|
467
|
-
self.flush()?;
|
|
468
|
-
|
|
469
|
-
match cursor_position {
|
|
470
|
-
None => self.hide_cursor()?,
|
|
471
|
-
Some(position) => {
|
|
472
|
-
self.show_cursor()?;
|
|
473
|
-
self.set_cursor_position(position)?;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
self.swap_buffers();
|
|
478
|
-
|
|
479
|
-
// Flush
|
|
480
|
-
self.backend.flush()?;
|
|
481
|
-
|
|
482
|
-
let completed_frame = CompletedFrame {
|
|
483
|
-
buffer: &self.buffers[1 - self.current],
|
|
484
|
-
area: self.last_known_area,
|
|
485
|
-
count: self.frame_count,
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
// increment frame count before returning from draw
|
|
489
|
-
self.frame_count = self.frame_count.wrapping_add(1);
|
|
490
|
-
|
|
491
|
-
Ok(completed_frame)
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/// Hides the cursor.
|
|
495
|
-
pub fn hide_cursor(&mut self) -> Result<(), B::Error> {
|
|
496
|
-
self.backend.hide_cursor()?;
|
|
497
|
-
self.hidden_cursor = true;
|
|
498
|
-
Ok(())
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/// Shows the cursor.
|
|
502
|
-
pub fn show_cursor(&mut self) -> Result<(), B::Error> {
|
|
503
|
-
self.backend.show_cursor()?;
|
|
504
|
-
self.hidden_cursor = false;
|
|
505
|
-
Ok(())
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
/// Gets the current cursor position.
|
|
509
|
-
///
|
|
510
|
-
/// This is the position of the cursor after the last draw call and is returned as a tuple of
|
|
511
|
-
/// `(x, y)` coordinates.
|
|
512
|
-
#[deprecated = "use `get_cursor_position()` instead which returns `Result<Position>`"]
|
|
513
|
-
pub fn get_cursor(&mut self) -> Result<(u16, u16), B::Error> {
|
|
514
|
-
let Position { x, y } = self.get_cursor_position()?;
|
|
515
|
-
Ok((x, y))
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/// Sets the cursor position.
|
|
519
|
-
#[deprecated = "use `set_cursor_position((x, y))` instead which takes `impl Into<Position>`"]
|
|
520
|
-
pub fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), B::Error> {
|
|
521
|
-
self.set_cursor_position(Position { x, y })
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/// Gets the current cursor position.
|
|
525
|
-
///
|
|
526
|
-
/// This is the position of the cursor after the last draw call.
|
|
527
|
-
pub fn get_cursor_position(&mut self) -> Result<Position, B::Error> {
|
|
528
|
-
self.backend.get_cursor_position()
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/// Sets the cursor position.
|
|
532
|
-
pub fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<(), B::Error> {
|
|
533
|
-
let position = position.into();
|
|
534
|
-
self.backend.set_cursor_position(position)?;
|
|
535
|
-
self.last_known_cursor_pos = position;
|
|
536
|
-
Ok(())
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/// Clear the terminal and force a full redraw on the next draw call.
|
|
540
|
-
pub fn clear(&mut self) -> Result<(), B::Error> {
|
|
541
|
-
match self.viewport {
|
|
542
|
-
Viewport::Fullscreen => self.backend.clear_region(ClearType::All)?,
|
|
543
|
-
Viewport::Inline(_) => {
|
|
544
|
-
self.backend
|
|
545
|
-
.set_cursor_position(self.viewport_area.as_position())?;
|
|
546
|
-
self.backend.clear_region(ClearType::AfterCursor)?;
|
|
547
|
-
}
|
|
548
|
-
Viewport::Fixed(_) => {
|
|
549
|
-
let area = self.viewport_area;
|
|
550
|
-
for y in area.top()..area.bottom() {
|
|
551
|
-
self.backend.set_cursor_position(Position { x: 0, y })?;
|
|
552
|
-
self.backend.clear_region(ClearType::AfterCursor)?;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
// Reset the back buffer to make sure the next update will redraw everything.
|
|
557
|
-
self.buffers[1 - self.current].reset();
|
|
558
|
-
Ok(())
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/// Clears the inactive buffer and swaps it with the current buffer
|
|
562
|
-
pub fn swap_buffers(&mut self) {
|
|
563
|
-
self.buffers[1 - self.current].reset();
|
|
564
|
-
self.current = 1 - self.current;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
/// Queries the real size of the backend.
|
|
568
|
-
pub fn size(&self) -> Result<Size, B::Error> {
|
|
569
|
-
self.backend.size()
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/// Insert some content before the current inline viewport. This has no effect when the
|
|
573
|
-
/// viewport is not inline.
|
|
574
|
-
///
|
|
575
|
-
/// The `draw_fn` closure will be called to draw into a writable `Buffer` that is `height`
|
|
576
|
-
/// lines tall. The content of that `Buffer` will then be inserted before the viewport.
|
|
577
|
-
///
|
|
578
|
-
/// If the viewport isn't yet at the bottom of the screen, inserted lines will push it towards
|
|
579
|
-
/// the bottom. Once the viewport is at the bottom of the screen, inserted lines will scroll
|
|
580
|
-
/// the area of the screen above the viewport upwards.
|
|
581
|
-
///
|
|
582
|
-
/// Before:
|
|
583
|
-
/// ```ignore
|
|
584
|
-
/// +---------------------+
|
|
585
|
-
/// | pre-existing line 1 |
|
|
586
|
-
/// | pre-existing line 2 |
|
|
587
|
-
/// +---------------------+
|
|
588
|
-
/// | viewport |
|
|
589
|
-
/// +---------------------+
|
|
590
|
-
/// | |
|
|
591
|
-
/// | |
|
|
592
|
-
/// +---------------------+
|
|
593
|
-
/// ```
|
|
594
|
-
///
|
|
595
|
-
/// After inserting 2 lines:
|
|
596
|
-
/// ```ignore
|
|
597
|
-
/// +---------------------+
|
|
598
|
-
/// | pre-existing line 1 |
|
|
599
|
-
/// | pre-existing line 2 |
|
|
600
|
-
/// | inserted line 1 |
|
|
601
|
-
/// | inserted line 2 |
|
|
602
|
-
/// +---------------------+
|
|
603
|
-
/// | viewport |
|
|
604
|
-
/// +---------------------+
|
|
605
|
-
/// +---------------------+
|
|
606
|
-
/// ```
|
|
607
|
-
///
|
|
608
|
-
/// After inserting 2 more lines:
|
|
609
|
-
/// ```ignore
|
|
610
|
-
/// +---------------------+
|
|
611
|
-
/// | pre-existing line 2 |
|
|
612
|
-
/// | inserted line 1 |
|
|
613
|
-
/// | inserted line 2 |
|
|
614
|
-
/// | inserted line 3 |
|
|
615
|
-
/// | inserted line 4 |
|
|
616
|
-
/// +---------------------+
|
|
617
|
-
/// | viewport |
|
|
618
|
-
/// +---------------------+
|
|
619
|
-
/// ```
|
|
620
|
-
///
|
|
621
|
-
/// If more lines are inserted than there is space on the screen, then the top lines will go
|
|
622
|
-
/// directly into the terminal's scrollback buffer. At the limit, if the viewport takes up the
|
|
623
|
-
/// whole screen, all lines will be inserted directly into the scrollback buffer.
|
|
624
|
-
///
|
|
625
|
-
/// # Examples
|
|
626
|
-
///
|
|
627
|
-
/// ## Insert a single line before the current viewport
|
|
628
|
-
///
|
|
629
|
-
/// ```rust,ignore
|
|
630
|
-
/// use ratatui::{
|
|
631
|
-
/// backend::TestBackend,
|
|
632
|
-
/// style::{Color, Style},
|
|
633
|
-
/// text::{Line, Span},
|
|
634
|
-
/// widgets::{Paragraph, Widget},
|
|
635
|
-
/// Terminal,
|
|
636
|
-
/// };
|
|
637
|
-
/// # let backend = TestBackend::new(10, 10);
|
|
638
|
-
/// # let mut terminal = Terminal::new(backend).unwrap();
|
|
639
|
-
/// terminal.insert_before(1, |buf| {
|
|
640
|
-
/// Paragraph::new(Line::from(vec![
|
|
641
|
-
/// Span::raw("This line will be added "),
|
|
642
|
-
/// Span::styled("before", Style::default().fg(Color::Blue)),
|
|
643
|
-
/// Span::raw(" the current viewport"),
|
|
644
|
-
/// ]))
|
|
645
|
-
/// .render(buf.area, buf);
|
|
646
|
-
/// });
|
|
647
|
-
/// ```
|
|
648
|
-
pub fn insert_before<F>(&mut self, height: u16, draw_fn: F) -> Result<(), B::Error>
|
|
649
|
-
where
|
|
650
|
-
F: FnOnce(&mut Buffer),
|
|
651
|
-
{
|
|
652
|
-
match self.viewport {
|
|
653
|
-
#[cfg(feature = "scrolling-regions")]
|
|
654
|
-
Viewport::Inline(_) => self.insert_before_scrolling_regions(height, draw_fn),
|
|
655
|
-
#[cfg(not(feature = "scrolling-regions"))]
|
|
656
|
-
Viewport::Inline(_) => self.insert_before_no_scrolling_regions(height, draw_fn),
|
|
657
|
-
_ => Ok(()),
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
/// Implement `Self::insert_before` using standard backend capabilities.
|
|
662
|
-
#[cfg(not(feature = "scrolling-regions"))]
|
|
663
|
-
fn insert_before_no_scrolling_regions(
|
|
664
|
-
&mut self,
|
|
665
|
-
height: u16,
|
|
666
|
-
draw_fn: impl FnOnce(&mut Buffer),
|
|
667
|
-
) -> Result<(), B::Error> {
|
|
668
|
-
// The approach of this function is to first render all of the lines to insert into a
|
|
669
|
-
// temporary buffer, and then to loop drawing chunks from the buffer to the screen. drawing
|
|
670
|
-
// this buffer onto the screen.
|
|
671
|
-
let area = Rect {
|
|
672
|
-
x: 0,
|
|
673
|
-
y: 0,
|
|
674
|
-
width: self.viewport_area.width,
|
|
675
|
-
height,
|
|
676
|
-
};
|
|
677
|
-
let mut buffer = Buffer::empty(area);
|
|
678
|
-
draw_fn(&mut buffer);
|
|
679
|
-
let mut buffer = buffer.content.as_slice();
|
|
680
|
-
|
|
681
|
-
// Use i32 variables so we don't have worry about overflowed u16s when adding, or about
|
|
682
|
-
// negative results when subtracting.
|
|
683
|
-
let mut drawn_height: i32 = self.viewport_area.top().into();
|
|
684
|
-
let mut buffer_height: i32 = height.into();
|
|
685
|
-
let viewport_height: i32 = self.viewport_area.height.into();
|
|
686
|
-
let screen_height: i32 = self.last_known_area.height.into();
|
|
687
|
-
|
|
688
|
-
// The algorithm here is to loop, drawing large chunks of text (up to a screen-full at a
|
|
689
|
-
// time), until the remainder of the buffer plus the viewport fits on the screen. We choose
|
|
690
|
-
// this loop condition because it guarantees that we can write the remainder of the buffer
|
|
691
|
-
// with just one call to Self::draw_lines().
|
|
692
|
-
while buffer_height + viewport_height > screen_height {
|
|
693
|
-
// We will draw as much of the buffer as possible on this iteration in order to make
|
|
694
|
-
// forward progress. So we have:
|
|
695
|
-
//
|
|
696
|
-
// to_draw = min(buffer_height, screen_height)
|
|
697
|
-
//
|
|
698
|
-
// We may need to scroll the screen up to make room to draw. We choose the minimal
|
|
699
|
-
// possible scroll amount so we don't end up with the viewport sitting in the middle of
|
|
700
|
-
// the screen when this function is done. The amount to scroll by is:
|
|
701
|
-
//
|
|
702
|
-
// scroll_up = max(0, drawn_height + to_draw - screen_height)
|
|
703
|
-
//
|
|
704
|
-
// We want `scroll_up` to be enough so that, after drawing, we have used the whole
|
|
705
|
-
// screen (drawn_height - scroll_up + to_draw = screen_height). However, there might
|
|
706
|
-
// already be enough room on the screen to draw without scrolling (drawn_height +
|
|
707
|
-
// to_draw <= screen_height). In this case, we just don't scroll at all.
|
|
708
|
-
let to_draw = buffer_height.min(screen_height);
|
|
709
|
-
let scroll_up = 0.max(drawn_height + to_draw - screen_height);
|
|
710
|
-
self.scroll_up(scroll_up as u16)?;
|
|
711
|
-
buffer = self.draw_lines((drawn_height - scroll_up) as u16, to_draw as u16, buffer)?;
|
|
712
|
-
drawn_height += to_draw - scroll_up;
|
|
713
|
-
buffer_height -= to_draw;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// There is now enough room on the screen for the remaining buffer plus the viewport,
|
|
717
|
-
// though we may still need to scroll up some of the existing text first. It's possible
|
|
718
|
-
// that by this point we've drained the buffer, but we may still need to scroll up to make
|
|
719
|
-
// room for the viewport.
|
|
720
|
-
//
|
|
721
|
-
// We want to scroll up the exact amount that will leave us completely filling the screen.
|
|
722
|
-
// However, it's possible that the viewport didn't start on the bottom of the screen and
|
|
723
|
-
// the added lines weren't enough to push it all the way to the bottom. We deal with this
|
|
724
|
-
// case by just ensuring that our scroll amount is non-negative.
|
|
725
|
-
//
|
|
726
|
-
// We want:
|
|
727
|
-
// screen_height = drawn_height - scroll_up + buffer_height + viewport_height
|
|
728
|
-
// Or, equivalently:
|
|
729
|
-
// scroll_up = drawn_height + buffer_height + viewport_height - screen_height
|
|
730
|
-
let scroll_up = 0.max(drawn_height + buffer_height + viewport_height - screen_height);
|
|
731
|
-
self.scroll_up(scroll_up as u16)?;
|
|
732
|
-
self.draw_lines(
|
|
733
|
-
(drawn_height - scroll_up) as u16,
|
|
734
|
-
buffer_height as u16,
|
|
735
|
-
buffer,
|
|
736
|
-
)?;
|
|
737
|
-
drawn_height += buffer_height - scroll_up;
|
|
738
|
-
|
|
739
|
-
self.set_viewport_area(Rect {
|
|
740
|
-
y: drawn_height as u16,
|
|
741
|
-
..self.viewport_area
|
|
742
|
-
});
|
|
743
|
-
|
|
744
|
-
// Clear the viewport off the screen. We didn't clear earlier for two reasons. First, it
|
|
745
|
-
// wasn't necessary because the buffer we drew out of isn't sparse, so it overwrote
|
|
746
|
-
// whatever was on the screen. Second, there is a weird bug with tmux where a full screen
|
|
747
|
-
// clear plus immediate scrolling causes some garbage to go into the scrollback.
|
|
748
|
-
self.clear()?;
|
|
749
|
-
|
|
750
|
-
Ok(())
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
/// Implement `Self::insert_before` using scrolling regions.
|
|
754
|
-
///
|
|
755
|
-
/// If a terminal supports scrolling regions, it means that we can define a subset of rows of
|
|
756
|
-
/// the screen, and then tell the terminal to scroll up or down just within that region. The
|
|
757
|
-
/// rows outside of the region are not affected.
|
|
758
|
-
///
|
|
759
|
-
/// This function utilizes this feature to avoid having to redraw the viewport. This is done
|
|
760
|
-
/// either by splitting the screen at the top of the viewport, and then creating a gap by
|
|
761
|
-
/// either scrolling the viewport down, or scrolling the area above it up. The lines to insert
|
|
762
|
-
/// are then drawn into the gap created.
|
|
763
|
-
#[cfg(feature = "scrolling-regions")]
|
|
764
|
-
fn insert_before_scrolling_regions(
|
|
765
|
-
&mut self,
|
|
766
|
-
mut height: u16,
|
|
767
|
-
draw_fn: impl FnOnce(&mut Buffer),
|
|
768
|
-
) -> Result<(), B::Error> {
|
|
769
|
-
// The approach of this function is to first render all of the lines to insert into a
|
|
770
|
-
// temporary buffer, and then to loop drawing chunks from the buffer to the screen. drawing
|
|
771
|
-
// this buffer onto the screen.
|
|
772
|
-
let area = Rect {
|
|
773
|
-
x: 0,
|
|
774
|
-
y: 0,
|
|
775
|
-
width: self.viewport_area.width,
|
|
776
|
-
height,
|
|
777
|
-
};
|
|
778
|
-
let mut buffer = Buffer::empty(area);
|
|
779
|
-
draw_fn(&mut buffer);
|
|
780
|
-
let mut buffer = buffer.content.as_slice();
|
|
781
|
-
|
|
782
|
-
// Handle the special case where the viewport takes up the whole screen.
|
|
783
|
-
if self.viewport_area.height == self.last_known_area.height {
|
|
784
|
-
// "Borrow" the top line of the viewport. Draw over it, then immediately scroll it into
|
|
785
|
-
// scrollback. Do this repeatedly until the whole buffer has been put into scrollback.
|
|
786
|
-
let mut first = true;
|
|
787
|
-
while !buffer.is_empty() {
|
|
788
|
-
buffer = if first {
|
|
789
|
-
self.draw_lines(0, 1, buffer)?
|
|
790
|
-
} else {
|
|
791
|
-
self.draw_lines_over_cleared(0, 1, buffer)?
|
|
792
|
-
};
|
|
793
|
-
first = false;
|
|
794
|
-
self.backend.scroll_region_up(0..1, 1)?;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// Redraw the top line of the viewport.
|
|
798
|
-
let width = self.viewport_area.width as usize;
|
|
799
|
-
let top_line = self.buffers[1 - self.current].content[0..width].to_vec();
|
|
800
|
-
self.draw_lines_over_cleared(0, 1, &top_line)?;
|
|
801
|
-
return Ok(());
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// Handle the case where the viewport isn't yet at the bottom of the screen.
|
|
805
|
-
{
|
|
806
|
-
let viewport_top = self.viewport_area.top();
|
|
807
|
-
let viewport_bottom = self.viewport_area.bottom();
|
|
808
|
-
let screen_bottom = self.last_known_area.bottom();
|
|
809
|
-
if viewport_bottom < screen_bottom {
|
|
810
|
-
let to_draw = height.min(screen_bottom - viewport_bottom);
|
|
811
|
-
self.backend
|
|
812
|
-
.scroll_region_down(viewport_top..viewport_bottom + to_draw, to_draw)?;
|
|
813
|
-
buffer = self.draw_lines_over_cleared(viewport_top, to_draw, buffer)?;
|
|
814
|
-
self.set_viewport_area(Rect {
|
|
815
|
-
y: viewport_top + to_draw,
|
|
816
|
-
..self.viewport_area
|
|
817
|
-
});
|
|
818
|
-
height -= to_draw;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
let viewport_top = self.viewport_area.top();
|
|
823
|
-
while height > 0 {
|
|
824
|
-
let to_draw = height.min(viewport_top);
|
|
825
|
-
self.backend.scroll_region_up(0..viewport_top, to_draw)?;
|
|
826
|
-
buffer = self.draw_lines_over_cleared(viewport_top - to_draw, to_draw, buffer)?;
|
|
827
|
-
height -= to_draw;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
Ok(())
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
/// Draw lines at the given vertical offset. The slice of cells must contain enough cells
|
|
834
|
-
/// for the requested lines. A slice of the unused cells are returned.
|
|
835
|
-
fn draw_lines<'a>(
|
|
836
|
-
&mut self,
|
|
837
|
-
y_offset: u16,
|
|
838
|
-
lines_to_draw: u16,
|
|
839
|
-
cells: &'a [Cell],
|
|
840
|
-
) -> Result<&'a [Cell], B::Error> {
|
|
841
|
-
let width: usize = self.last_known_area.width.into();
|
|
842
|
-
let (to_draw, remainder) = cells.split_at(width * lines_to_draw as usize);
|
|
843
|
-
if lines_to_draw > 0 {
|
|
844
|
-
let iter = to_draw
|
|
845
|
-
.iter()
|
|
846
|
-
.enumerate()
|
|
847
|
-
.map(|(i, c)| ((i % width) as u16, y_offset + (i / width) as u16, c));
|
|
848
|
-
self.backend.draw(iter)?;
|
|
849
|
-
self.backend.flush()?;
|
|
850
|
-
}
|
|
851
|
-
Ok(remainder)
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
/// Draw lines at the given vertical offset, assuming that the lines they are replacing on the
|
|
855
|
-
/// screen are cleared. The slice of cells must contain enough cells for the requested lines. A
|
|
856
|
-
/// slice of the unused cells are returned.
|
|
857
|
-
#[cfg(feature = "scrolling-regions")]
|
|
858
|
-
fn draw_lines_over_cleared<'a>(
|
|
859
|
-
&mut self,
|
|
860
|
-
y_offset: u16,
|
|
861
|
-
lines_to_draw: u16,
|
|
862
|
-
cells: &'a [Cell],
|
|
863
|
-
) -> Result<&'a [Cell], B::Error> {
|
|
864
|
-
let width: usize = self.last_known_area.width.into();
|
|
865
|
-
let (to_draw, remainder) = cells.split_at(width * lines_to_draw as usize);
|
|
866
|
-
if lines_to_draw > 0 {
|
|
867
|
-
let area = Rect::new(0, y_offset, width as u16, y_offset + lines_to_draw);
|
|
868
|
-
let old = Buffer::empty(area);
|
|
869
|
-
let new = Buffer {
|
|
870
|
-
area,
|
|
871
|
-
content: to_draw.to_vec(),
|
|
872
|
-
};
|
|
873
|
-
self.backend.draw(old.diff(&new).into_iter())?;
|
|
874
|
-
self.backend.flush()?;
|
|
875
|
-
}
|
|
876
|
-
Ok(remainder)
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
/// Scroll the whole screen up by the given number of lines.
|
|
880
|
-
#[cfg(not(feature = "scrolling-regions"))]
|
|
881
|
-
fn scroll_up(&mut self, lines_to_scroll: u16) -> Result<(), B::Error> {
|
|
882
|
-
if lines_to_scroll > 0 {
|
|
883
|
-
self.set_cursor_position(Position::new(
|
|
884
|
-
0,
|
|
885
|
-
self.last_known_area.height.saturating_sub(1),
|
|
886
|
-
))?;
|
|
887
|
-
self.backend.append_lines(lines_to_scroll)?;
|
|
888
|
-
}
|
|
889
|
-
Ok(())
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
fn compute_inline_size<B: Backend>(
|
|
894
|
-
backend: &mut B,
|
|
895
|
-
height: u16,
|
|
896
|
-
size: Size,
|
|
897
|
-
offset_in_previous_viewport: u16,
|
|
898
|
-
) -> Result<(Rect, Position), B::Error> {
|
|
899
|
-
let pos = backend.get_cursor_position()?;
|
|
900
|
-
let mut row = pos.y;
|
|
901
|
-
|
|
902
|
-
let max_height = size.height.min(height);
|
|
903
|
-
|
|
904
|
-
let lines_after_cursor = height
|
|
905
|
-
.saturating_sub(offset_in_previous_viewport)
|
|
906
|
-
.saturating_sub(1);
|
|
907
|
-
|
|
908
|
-
backend.append_lines(lines_after_cursor)?;
|
|
909
|
-
|
|
910
|
-
let available_lines = size.height.saturating_sub(row).saturating_sub(1);
|
|
911
|
-
let missing_lines = lines_after_cursor.saturating_sub(available_lines);
|
|
912
|
-
if missing_lines > 0 {
|
|
913
|
-
row = row.saturating_sub(missing_lines);
|
|
914
|
-
}
|
|
915
|
-
row = row.saturating_sub(offset_in_previous_viewport);
|
|
916
|
-
|
|
917
|
-
Ok((
|
|
918
|
-
Rect {
|
|
919
|
-
x: 0,
|
|
920
|
-
y: row,
|
|
921
|
-
width: size.width,
|
|
922
|
-
height: max_height,
|
|
923
|
-
},
|
|
924
|
-
pos,
|
|
925
|
-
))
|
|
926
|
-
}
|