1use crate::color::{Color, Palette};
14use crate::grid::Grid;
15
16#[derive(Clone, Debug)]
18pub struct Gradient {
19 stops: Vec<Color>,
20 direction: GradientDirection,
21}
22
23#[derive(Clone, Copy, Debug)]
25pub enum GradientDirection {
26 Vertical,
28 Horizontal,
30 Diagonal,
32}
33
34impl Gradient {
35 pub fn new(stops: Vec<Color>, direction: GradientDirection) -> Self {
37 Self { stops, direction }
38 }
39
40 pub fn vertical(palette: Palette) -> Self {
42 Self::new(palette.colors().to_vec(), GradientDirection::Vertical)
43 }
44
45 pub fn horizontal(palette: Palette) -> Self {
47 Self::new(palette.colors().to_vec(), GradientDirection::Horizontal)
48 }
49
50 pub fn diagonal(palette: Palette) -> Self {
52 Self::new(palette.colors().to_vec(), GradientDirection::Diagonal)
53 }
54
55 pub fn apply(&self, grid: &mut Grid) {
57 if self.stops.is_empty() {
58 return;
59 }
60
61 let height = grid.height().max(1);
62 let width = grid.width().max(1);
63
64 for r in 0..height {
65 for c in 0..width {
66 let t = match self.direction {
67 GradientDirection::Vertical => {
68 if height <= 1 {
69 0.0
70 } else {
71 r as f32 / (height - 1) as f32
72 }
73 }
74 GradientDirection::Horizontal => {
75 if width <= 1 {
76 0.0
77 } else {
78 c as f32 / (width - 1) as f32
79 }
80 }
81 GradientDirection::Diagonal => {
82 if width + height <= 2 {
83 0.0
84 } else {
85 (r + c) as f32 / (width + height - 2) as f32
86 }
87 }
88 };
89
90 if let Some(cell) = grid.cell_mut(r, c)
91 && cell.visible
92 {
93 cell.fg = Some(color_at(&self.stops, t));
94 }
95 }
96 }
97 }
98}
99
100fn color_at(stops: &[Color], t: f32) -> Color {
101 if stops.len() == 1 {
102 return stops[0];
103 }
104
105 let t = t.clamp(0.0, 1.0);
106 let max_index = stops.len() - 1;
107 let scaled = t * max_index as f32;
108 let idx = scaled.floor() as usize;
109 let next = idx.min(max_index - 1) + 1;
110 let local_t = scaled - idx as f32;
111
112 stops[idx].lerp(stops[next], local_t)
113}