tui_equalizer/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! An equalizer widget for [Ratatui] with multiple frequency bands.
//!
//! The equalizer is a vertical bar chart where each band represents a frequency range. Each band
//! can display a value from 0.0 to 1.0, where 1.0 is the maximum value.
//!
//! ![Made with VHS](https://vhs.charm.sh/vhs-FiRQkkDAUEnH2BrPbUx5i.gif)
//!
//! This demo can be found in the examples folder in the git repo.
//!
//! ```shell
//! cargo run --example demo
//! ```
//!
//! Inspired by [a comment in the ratatui
//! repo](https://github.com/ratatui/ratatui/issues/1325#issuecomment-2335095486).
//!
//! # Example
//!
//! ```rust
//! use tui_equalizer::{Band, Equalizer};
//!
//! let equalizer = Equalizer {
//!     bands: vec![
//!         Band::from(0.5),
//!         Band::from(0.8),
//!         Band::from(0.3),
//!     ],
//! };
//! equalizer.render(area, buf);
//! ```
//!
//! # License
//!
//! Copyright (c) Josh McKinney
//!
//! This project is licensed under either of:
//!
//! - Apache License, Version 2.0 ([LICENSE-APACHE] or <http://www.apache.org/licenses/LICENSE-2.0>)
//! - MIT license ([LICENSE-MIT] or <http://opensource.org/licenses/MIT>)
//!
//! at your option.
//!
//! [LICENSE-APACHE]: ./LICENSE-APACHE
//! [LICENSE-MIT]: ./LICENSE-MIT
//!
//!
//! [Ratatui]: https://crates.io/crates/ratatui

use std::iter::zip;

use ratatui::{
    buffer::Buffer,
    layout::{Constraint, Layout, Rect},
    style::Color,
    widgets::Widget,
};

/// An equalizer widget with multiple frequency bands.
///
/// The equalizer is a vertical bar chart where each band represents a frequency range.
///
/// # Example
///
/// ```
/// use tui_equalizer::{Band, Equalizer};
///
/// # let area = ratatui::layout::Rect::default();
/// # let mut buf = ratatui::buffer::Buffer::empty(area);
/// let equalizer = Equalizer {
///     bands: vec![
///         Band::from(0.5),
///         Band::from(0.8),
///         Band::from(0.3),
///     ],
/// };
/// equalizer.render(area, buf);
/// ```
#[derive(Debug)]
pub struct Equalizer {
    /// A vector of `Band` structs representing each frequency band.
    pub bands: Vec<Band>,
    pub brightness: f64,
}

/// A struct representing a single frequency band in the equalizer.
#[derive(Debug, Clone)]
pub struct Band {
    /// The normalized value of the band, where the maximum is 1.0.
    pub value: f64,
}

impl From<f64> for Band {
    fn from(value: f64) -> Self {
        Self { value }
    }
}

impl Widget for Equalizer {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let areas = Layout::horizontal(vec![Constraint::Length(2); self.bands.len()]).split(area);
        for (band, area) in zip(self.bands, areas.iter()) {
            band.render(*area, buf, self.brightness);
        }
    }
}

impl Band {
    fn render(self, area: Rect, buf: &mut Buffer, brightness: f64) {
        let value = self.value.clamp(0.0, 1.0);
        let height = (value * area.height as f64) as u16;

        // Calculate the color gradient step
        let color_step = 1.0 / area.height as f64;

        // Iterate over each segment and render it with the corresponding color
        for i in 0..height {
            // Green to Yellow to Red gradient
            let v = i as f64 * color_step;
            let vv = 1.0 - v;
            let br = brightness.clamp(0.0, 1.0) * 255.0;
            let r = if v < 0.5 { v * 2.0 * br } else { br } as u8;
            let g = if v < 0.5 { br } else { vv * 2.0 * br } as u8;
            let b = 0;
            let color = Color::Rgb(r, g, b);
            buf[(area.left(), area.bottom().saturating_sub(i + 1))]
                .set_fg(color)
                .set_symbol(ratatui::symbols::bar::HALF);
        }
    }
}