Feat: lissajous
This commit is contained in:
@@ -5,6 +5,7 @@ mod confirm;
|
||||
mod editor;
|
||||
mod file_browser;
|
||||
mod hint_bar;
|
||||
mod lissajous;
|
||||
mod list_select;
|
||||
mod modal;
|
||||
mod nav_minimap;
|
||||
@@ -26,6 +27,7 @@ pub use confirm::ConfirmModal;
|
||||
pub use editor::{fuzzy_match, CompletionCandidate, Editor};
|
||||
pub use file_browser::FileBrowserModal;
|
||||
pub use hint_bar::hint_line;
|
||||
pub use lissajous::Lissajous;
|
||||
pub use list_select::ListSelect;
|
||||
pub use modal::ModalFrame;
|
||||
pub use nav_minimap::{hit_test_tile, minimap_area, NavMinimap, NavTile};
|
||||
|
||||
103
crates/ratatui/src/lissajous.rs
Normal file
103
crates/ratatui/src/lissajous.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use crate::theme;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::style::Color;
|
||||
use ratatui::widgets::Widget;
|
||||
use std::cell::RefCell;
|
||||
|
||||
thread_local! {
|
||||
static PATTERNS: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
|
||||
}
|
||||
|
||||
pub struct Lissajous<'a> {
|
||||
left: &'a [f32],
|
||||
right: &'a [f32],
|
||||
color: Option<Color>,
|
||||
}
|
||||
|
||||
impl<'a> Lissajous<'a> {
|
||||
pub fn new(left: &'a [f32], right: &'a [f32]) -> Self {
|
||||
Self {
|
||||
left,
|
||||
right,
|
||||
color: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(mut self, c: Color) -> Self {
|
||||
self.color = Some(c);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Lissajous<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
if area.width == 0 || area.height == 0 || self.left.is_empty() || self.right.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let color = self.color.unwrap_or_else(|| theme::get().meter.low);
|
||||
let width = area.width as usize;
|
||||
let height = area.height as usize;
|
||||
let fine_width = width * 2;
|
||||
let fine_height = height * 4;
|
||||
let len = self.left.len().min(self.right.len());
|
||||
|
||||
let peak = self
|
||||
.left
|
||||
.iter()
|
||||
.chain(self.right.iter())
|
||||
.map(|s| s.abs())
|
||||
.fold(0.0f32, f32::max);
|
||||
let gain = if peak > 0.001 { 1.0 / peak } else { 1.0 };
|
||||
|
||||
PATTERNS.with(|p| {
|
||||
let mut patterns = p.borrow_mut();
|
||||
let size = width * height;
|
||||
patterns.clear();
|
||||
patterns.resize(size, 0);
|
||||
|
||||
for i in 0..len {
|
||||
let l = (self.left[i] * gain).clamp(-1.0, 1.0);
|
||||
let r = (self.right[i] * gain).clamp(-1.0, 1.0);
|
||||
|
||||
// X = right channel, Y = left channel (inverted so up = positive)
|
||||
let fine_x = ((r + 1.0) * 0.5 * (fine_width - 1) as f32).round() as usize;
|
||||
let fine_y = ((1.0 - l) * 0.5 * (fine_height - 1) as f32).round() as usize;
|
||||
let fine_x = fine_x.min(fine_width - 1);
|
||||
let fine_y = fine_y.min(fine_height - 1);
|
||||
|
||||
let char_x = fine_x / 2;
|
||||
let char_y = fine_y / 4;
|
||||
let dot_x = fine_x % 2;
|
||||
let dot_y = fine_y % 4;
|
||||
|
||||
let bit = match (dot_x, dot_y) {
|
||||
(0, 0) => 0x01,
|
||||
(0, 1) => 0x02,
|
||||
(0, 2) => 0x04,
|
||||
(0, 3) => 0x40,
|
||||
(1, 0) => 0x08,
|
||||
(1, 1) => 0x10,
|
||||
(1, 2) => 0x20,
|
||||
(1, 3) => 0x80,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
patterns[char_y * width + char_x] |= bit;
|
||||
}
|
||||
|
||||
for cy in 0..height {
|
||||
for cx in 0..width {
|
||||
let pattern = patterns[cy * width + cx];
|
||||
if pattern != 0 {
|
||||
let ch = char::from_u32(0x2800 + pattern as u32).unwrap_or(' ');
|
||||
buf[(area.x + cx as u16, area.y + cy as u16)]
|
||||
.set_char(ch)
|
||||
.set_fg(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user