tui_equalizer/lib.rs
1//! An equalizer widget for [Ratatui] with multiple frequency bands.
2//!
3//! The equalizer is a vertical bar chart where each band represents a frequency range. Each band
4//! can display a value from 0.0 to 1.0, where 1.0 is the maximum value.
5//!
6//! 
7//!
8//! This demo can be found in the examples folder in the git repo.
9//!
10//! ```shell
11//! cargo run --example demo
12//! ```
13//!
14//! Inspired by [a comment in the ratatui
15//! repo](https://github.com/ratatui/ratatui/issues/1325#issuecomment-2335095486).
16//!
17//! # Example
18//!
19//! ```rust
20//! # use ratatui::{widgets::Widget, layout::Rect, buffer::Buffer};
21//! # let area = Rect::default();
22//! # let mut buf = Buffer::empty(area);
23//! use tui_equalizer::{Band, Equalizer};
24//!
25//! let equalizer = Equalizer {
26//! bands: vec![Band::from(0.5), Band::from(0.8), Band::from(0.3)],
27//! brightness: 1.0,
28//! };
29//! equalizer.render(area, &mut buf);
30//! ```
31//!
32//! # License
33//!
34//! Copyright (c) Josh McKinney
35//!
36//! This project is licensed under either of:
37//!
38//! - Apache License, Version 2.0 ([LICENSE-APACHE] or <http://www.apache.org/licenses/LICENSE-2.0>)
39//! - MIT license ([LICENSE-MIT] or <http://opensource.org/licenses/MIT>)
40//!
41//! at your option.
42//!
43//! [LICENSE-APACHE]: https://github.com/ratatui/tui-widgets/blob/main/LICENSE-APACHE
44//! [LICENSE-MIT]: https://github.com/ratatui/tui-widgets/blob/main/LICENSE-MIT
45//!
46//! [Ratatui]: https://crates.io/crates/ratatui
47
48use std::iter::zip;
49
50use ratatui_core::buffer::Buffer;
51use ratatui_core::layout::{Constraint, Layout, Rect};
52use ratatui_core::style::Color;
53use ratatui_core::symbols;
54use ratatui_core::widgets::Widget;
55
56/// An equalizer widget with multiple frequency bands.
57///
58/// The equalizer is a vertical bar chart where each band represents a frequency range.
59///
60/// # Example
61///
62/// ```
63/// # use ratatui::widgets::Widget;
64/// # let area = ratatui::layout::Rect::default();
65/// # let mut buf = ratatui::buffer::Buffer::empty(area);
66/// use tui_equalizer::{Band, Equalizer};
67///
68/// let equalizer = Equalizer {
69/// bands: vec![Band::from(0.5), Band::from(0.8), Band::from(0.3)],
70/// brightness: 1.0,
71/// };
72/// equalizer.render(area, &mut buf);
73/// ```
74#[derive(Debug, Clone)]
75pub struct Equalizer {
76 /// A vector of `Band` structs representing each frequency band.
77 pub bands: Vec<Band>,
78 pub brightness: f64,
79}
80
81/// A struct representing a single frequency band in the equalizer.
82#[derive(Debug, Clone)]
83pub struct Band {
84 /// The normalized value of the band, where the maximum is 1.0.
85 pub value: f64,
86}
87
88impl From<f64> for Band {
89 fn from(value: f64) -> Self {
90 Self { value }
91 }
92}
93
94impl Widget for Equalizer {
95 fn render(self, area: Rect, buf: &mut Buffer) {
96 (&self).render(area, buf);
97 }
98}
99
100impl Widget for &Equalizer {
101 fn render(self, area: Rect, buf: &mut Buffer) {
102 let areas = Layout::horizontal(vec![Constraint::Length(2); self.bands.len()]).split(area);
103 for (band, area) in zip(&self.bands, areas.iter()) {
104 band.render(*area, buf, self.brightness);
105 }
106 }
107}
108
109impl Band {
110 fn render(&self, area: Rect, buf: &mut Buffer, brightness: f64) {
111 let value = self.value.clamp(0.0, 1.0);
112 let height = (value * area.height as f64) as u16;
113
114 // Calculate the color gradient step
115 let color_step = 1.0 / area.height as f64;
116
117 // Iterate over each segment and render it with the corresponding color
118 for i in 0..height {
119 // Green to Yellow to Red gradient
120 let v = i as f64 * color_step;
121 let vv = 1.0 - v;
122 let br = brightness.clamp(0.0, 1.0) * 255.0;
123 let r = if v < 0.5 { v * 2.0 * br } else { br } as u8;
124 let g = if v < 0.5 { br } else { vv * 2.0 * br } as u8;
125 let b = 0;
126 let color = Color::Rgb(r, g, b);
127 buf[(area.left(), area.bottom().saturating_sub(i + 1))]
128 .set_fg(color)
129 .set_symbol(symbols::bar::HALF);
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn render_by_reference_matches_owned_render() {
140 let equalizer = Equalizer {
141 bands: vec![Band::from(0.25), Band::from(0.5), Band::from(1.0)],
142 brightness: 1.0,
143 };
144 let mut owned_buf = Buffer::empty(Rect::new(0, 0, 6, 4));
145 let mut borrowed_buf = Buffer::empty(Rect::new(0, 0, 6, 4));
146
147 equalizer.clone().render(owned_buf.area, &mut owned_buf);
148 (&equalizer).render(borrowed_buf.area, &mut borrowed_buf);
149
150 assert_eq!(borrowed_buf, owned_buf);
151 }
152}