mirror of
https://github.com/ratatui/ratatui.git
synced 2025-10-02 15:25:54 +00:00

Code which previously called `buf.get(x, y)` or `buf.get_mut(x, y)` should now use index operators, or be transitioned to `buff.cell()` or `buf.cell_mut()` for safe access that avoids panics by returning `Option<&Cell>` and `Option<&mut Cell>`. The new methods accept `Into<Position>` instead of `x` and `y` coordinates, which makes them more ergonomic to use. ```rust let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10)); let cell = buf[(0, 0)]; let cell = buf[Position::new(0, 0)]; let symbol = buf.cell((0, 0)).map(|cell| cell.symbol()); let symbol = buf.cell(Position::new(0, 0)).map(|cell| cell.symbol()); buf[(0, 0)].set_symbol("🐀"); buf[Position::new(0, 0)].set_symbol("🐀"); buf.cell_mut((0, 0)).map(|cell| cell.set_symbol("🐀")); buf.cell_mut(Position::new(0, 0)).map(|cell| cell.set_symbol("🐀")); ``` The existing `get()` and `get_mut()` methods are marked as deprecated. These are fairly widely used and we will leave these methods around on the buffer for a longer time than our normal deprecation approach (2 major release) Addresses part of: https://github.com/ratatui-org/ratatui/issues/1011 --------- Co-authored-by: EdJoPaTo <rfc-conform-git-commit-email@funny-long-domain-label-everyone-hates-as-it-is-too-long.edjopato.de>
143 lines
5.5 KiB
Rust
143 lines
5.5 KiB
Rust
use rand::Rng;
|
|
use rand_chacha::rand_core::SeedableRng;
|
|
use ratatui::{
|
|
buffer::Buffer,
|
|
layout::{Flex, Layout, Rect},
|
|
style::{Color, Style},
|
|
text::Text,
|
|
widgets::Widget,
|
|
Frame,
|
|
};
|
|
|
|
/// delay the start of the animation so it doesn't start immediately
|
|
const DELAY: usize = 120;
|
|
/// higher means more pixels per frame are modified in the animation
|
|
const DRIP_SPEED: usize = 500;
|
|
/// delay the start of the text animation so it doesn't start immediately after the initial delay
|
|
const TEXT_DELAY: usize = 180;
|
|
|
|
/// Destroy mode activated by pressing `d`
|
|
pub fn destroy(frame: &mut Frame<'_>) {
|
|
let frame_count = frame.count().saturating_sub(DELAY);
|
|
if frame_count == 0 {
|
|
return;
|
|
}
|
|
|
|
let area = frame.area();
|
|
let buf = frame.buffer_mut();
|
|
|
|
drip(frame_count, area, buf);
|
|
text(frame_count, area, buf);
|
|
}
|
|
|
|
/// Move a bunch of random pixels down one row.
|
|
///
|
|
/// Each pick some random pixels and move them each down one row. This is a very inefficient way to
|
|
/// do this, but it works well enough for this demo.
|
|
#[allow(
|
|
clippy::cast_possible_truncation,
|
|
clippy::cast_precision_loss,
|
|
clippy::cast_sign_loss
|
|
)]
|
|
fn drip(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
|
// a seeded rng as we have to move the same random pixels each frame
|
|
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(10);
|
|
let ramp_frames = 450;
|
|
let fractional_speed = frame_count as f64 / f64::from(ramp_frames);
|
|
let variable_speed = DRIP_SPEED as f64 * fractional_speed * fractional_speed * fractional_speed;
|
|
let pixel_count = (frame_count as f64 * variable_speed).floor() as usize;
|
|
for _ in 0..pixel_count {
|
|
let src_x = rng.gen_range(0..area.width);
|
|
let src_y = rng.gen_range(1..area.height - 2);
|
|
let src = buf[(src_x, src_y)].clone();
|
|
// 1% of the time, move a blank or pixel (10:1) to the top line of the screen
|
|
if rng.gen_ratio(1, 100) {
|
|
let dest_x = rng
|
|
.gen_range(src_x.saturating_sub(5)..src_x.saturating_add(5))
|
|
.clamp(area.left(), area.right() - 1);
|
|
let dest_y = area.top() + 1;
|
|
|
|
let dest = &mut buf[(dest_x, dest_y)];
|
|
// copy the cell to the new location about 1/10 of the time blank out the cell the rest
|
|
// of the time. This has the effect of gradually removing the pixels from the screen.
|
|
if rng.gen_ratio(1, 10) {
|
|
*dest = src;
|
|
} else {
|
|
dest.reset();
|
|
}
|
|
} else {
|
|
// move the pixel down one row
|
|
let dest_x = src_x;
|
|
let dest_y = src_y.saturating_add(1).min(area.bottom() - 2);
|
|
// copy the cell to the new location
|
|
buf[(dest_x, dest_y)] = src;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// draw some text fading in and out from black to red and back
|
|
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
|
|
fn text(frame_count: usize, area: Rect, buf: &mut Buffer) {
|
|
let sub_frame = frame_count.saturating_sub(TEXT_DELAY);
|
|
if sub_frame == 0 {
|
|
return;
|
|
}
|
|
|
|
let logo = indoc::indoc! {"
|
|
██████ ████ ██████ ████ ██████ ██ ██ ██
|
|
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
██████ ████████ ██ ████████ ██ ██ ██ ██
|
|
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
██ ██ ██ ██ ██ ██ ██ ██ ████ ██
|
|
"};
|
|
let logo_text = Text::styled(logo, Color::Rgb(255, 255, 255));
|
|
let area = centered_rect(area, logo_text.width() as u16, logo_text.height() as u16);
|
|
|
|
let mask_buf = &mut Buffer::empty(area);
|
|
logo_text.render(area, mask_buf);
|
|
|
|
let percentage = (sub_frame as f64 / 480.0).clamp(0.0, 1.0);
|
|
|
|
for row in area.rows() {
|
|
for col in row.columns() {
|
|
let cell = &mut buf[(col.x, col.y)];
|
|
let mask_cell = &mut mask_buf[(col.x, col.y)];
|
|
cell.set_symbol(mask_cell.symbol());
|
|
|
|
// blend the mask cell color with the cell color
|
|
let cell_color = cell.style().bg.unwrap_or(Color::Rgb(0, 0, 0));
|
|
let mask_color = mask_cell.style().fg.unwrap_or(Color::Rgb(255, 0, 0));
|
|
|
|
let color = blend(mask_color, cell_color, percentage);
|
|
cell.set_style(Style::new().fg(color));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn blend(mask_color: Color, cell_color: Color, percentage: f64) -> Color {
|
|
let Color::Rgb(mask_red, mask_green, mask_blue) = mask_color else {
|
|
return mask_color;
|
|
};
|
|
let Color::Rgb(cell_red, cell_green, cell_blue) = cell_color else {
|
|
return mask_color;
|
|
};
|
|
|
|
let remain = 1.0 - percentage;
|
|
|
|
let red = f64::from(mask_red).mul_add(percentage, f64::from(cell_red) * remain);
|
|
let green = f64::from(mask_green).mul_add(percentage, f64::from(cell_green) * remain);
|
|
let blue = f64::from(mask_blue).mul_add(percentage, f64::from(cell_blue) * remain);
|
|
|
|
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
|
Color::Rgb(red as u8, green as u8, blue as u8)
|
|
}
|
|
|
|
/// a centered rect of the given size
|
|
fn centered_rect(area: Rect, width: u16, height: u16) -> Rect {
|
|
let horizontal = Layout::horizontal([width]).flex(Flex::Center);
|
|
let vertical = Layout::vertical([height]).flex(Flex::Center);
|
|
let [area] = vertical.areas(area);
|
|
let [area] = horizontal.areas(area);
|
|
area
|
|
}
|