1use ratatui::buffer::Buffer;
2use ratatui::layout::Rect;
3use ratatui::style::Style;
4use ratatui::widgets::Widget;
5
6pub struct DimOverlay {
7 pub style: Style,
8}
9
10impl Widget for DimOverlay {
11 fn render(self, area: Rect, buf: &mut Buffer) {
12 let dim_bg = self.style.bg;
13 for y in area.top()..area.bottom() {
14 for x in area.left()..area.right() {
15 if let Some(cell) = buf.cell_mut((x, y)) {
16 let mut s = cell.style();
17 if let Some(bg) = dim_bg {
18 if s.bg.is_none_or(|c| c == ratatui::style::Color::Reset) {
21 s.bg = Some(bg);
22 } else {
23 s.bg = s.bg.map(|c| dim_color(c, DIM_FACTOR));
24 }
25 }
26 s.fg = s.fg.map(|c| dim_color(c, DIM_FACTOR));
27 cell.set_style(s);
28 }
29 }
30 }
31 }
32}
33
34const DIM_FACTOR: f64 = 0.45;
36
37fn dim_color(c: ratatui::style::Color, factor: f64) -> ratatui::style::Color {
38 match c {
39 ratatui::style::Color::Rgb(r, g, b) => ratatui::style::Color::Rgb(
40 (r as f64 * factor) as u8,
41 (g as f64 * factor) as u8,
42 (b as f64 * factor) as u8,
43 ),
44 _ => c,
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use ratatui::style::{Color, Modifier};
52
53 fn style_fg(fg: Color) -> Style {
54 Style {
55 fg: Some(fg),
56 bg: Some(Color::Reset),
57 add_modifier: Modifier::empty(),
58 sub_modifier: Modifier::empty(),
59 underline_color: None,
60 }
61 }
62
63 fn style_fg_bg(fg: Color, bg: Color) -> Style {
64 Style {
65 fg: Some(fg),
66 bg: Some(bg),
67 add_modifier: Modifier::empty(),
68 sub_modifier: Modifier::empty(),
69 underline_color: None,
70 }
71 }
72
73 #[test]
74 fn dim_overlay_applies_overlay_bg_to_reset_cells() {
75 let area = Rect::new(0, 0, 3, 1);
76 let mut buf = Buffer::with_lines(vec!["abc"]);
77
78 DimOverlay {
79 style: Style::default().bg(Color::Rgb(10, 20, 30)),
80 }
81 .render(area, &mut buf);
82
83 let cell = buf.cell((0, 0)).unwrap();
85 assert_eq!(cell.style().fg, Some(Color::Reset)); assert_eq!(cell.style().bg, Some(Color::Rgb(10, 20, 30))); }
88
89 #[test]
90 fn dim_overlay_dims_existing_rgb_fg_and_bg() {
91 let area = Rect::new(0, 0, 3, 1);
92 let mut buf = Buffer::with_lines(vec!["abc"]);
93 buf[(0u16, 0u16)].set_style(style_fg_bg(
94 Color::Rgb(200, 100, 50),
95 Color::Rgb(50, 100, 200),
96 ));
97
98 DimOverlay {
99 style: Style::default().bg(Color::Rgb(10, 20, 30)),
100 }
101 .render(area, &mut buf);
102
103 let cell = buf.cell((0, 0)).unwrap();
104 assert_eq!(cell.style().fg, Some(Color::Rgb(90, 45, 22)));
105 assert_eq!(cell.style().bg, Some(Color::Rgb(22, 45, 90)));
106 }
107
108 #[test]
109 fn dim_overlay_outside_area_untouched() {
110 let area = Rect::new(0, 0, 2, 1);
111 let mut buf = Buffer::with_lines(vec!["abc"]);
112 buf[(0u16, 0u16)].set_style(style_fg(Color::Rgb(200, 100, 50)));
113 buf[(2u16, 0u16)].set_style(style_fg(Color::Rgb(200, 100, 50)));
114
115 DimOverlay {
116 style: Style::default().bg(Color::Rgb(10, 20, 30)),
117 }
118 .render(area, &mut buf);
119
120 assert_eq!(
121 buf.cell((0, 0)).unwrap().style().fg,
122 Some(Color::Rgb(90, 45, 22))
123 );
124 assert_eq!(
125 buf.cell((2, 0)).unwrap().style().fg,
126 Some(Color::Rgb(200, 100, 50))
127 );
128 }
129
130 #[test]
131 fn dim_overlay_empty_area_no_panic() {
132 let area = Rect::new(0, 0, 0, 0);
133 let mut buf = Buffer::with_lines(vec!["abc"]);
134 DimOverlay {
135 style: Style::default().bg(Color::Rgb(10, 20, 30)),
136 }
137 .render(area, &mut buf);
138 }
139
140 #[test]
141 fn dim_overlay_no_overlay_bg_only_dims_fg() {
142 let area = Rect::new(0, 0, 3, 1);
143 let mut buf = Buffer::with_lines(vec!["abc"]);
144 buf[(0u16, 0u16)].set_style(style_fg(Color::Rgb(200, 100, 50)));
145
146 DimOverlay {
147 style: Style::default(), }
149 .render(area, &mut buf);
150
151 let cell = buf.cell((0, 0)).unwrap();
152 assert_eq!(cell.style().fg, Some(Color::Rgb(90, 45, 22)));
153 assert_eq!(cell.style().bg, Some(Color::Reset));
154 }
155}