tui_vfx_shadow/renderers/
cls_gradient.rs1use tui_vfx_types::{Cell, Color, Grid, Rect};
15
16use crate::types::ShadowConfig;
17
18pub struct GradientRenderer;
24
25impl GradientRenderer {
26 pub fn render_with_colors<G: Grid>(
46 grid: &mut G,
47 element_rect: Rect,
48 config: &ShadowConfig,
49 colors: &[Color],
50 progress: f64,
51 ) {
52 if colors.is_empty() || progress <= 0.0 {
53 return;
54 }
55
56 let layers = colors.len();
57
58 let rect_x = element_rect.x as i32;
60 let rect_y = element_rect.y as i32;
61 let rect_w = element_rect.width as i32;
62 let rect_h = element_rect.height as i32;
63
64 let ox = config.offset_x as i32;
65 let oy = config.offset_y as i32;
66 let edges = config.edges;
67
68 for (layer_idx, color) in colors.iter().enumerate().rev() {
71 let layer_mult = (layers - layer_idx) as i32;
73 let layer_ox = ox * layer_mult;
74 let layer_oy = oy * layer_mult;
75
76 let layer_color = if progress < 1.0 {
78 let alpha = (color.a as f64 * progress).round() as u8;
79 color.with_alpha(alpha)
80 } else {
81 *color
82 };
83
84 Self::render_layer(
86 grid,
87 rect_x,
88 rect_y,
89 rect_w,
90 rect_h,
91 layer_ox,
92 layer_oy,
93 edges,
94 layer_color,
95 );
96 }
97 }
98
99 pub fn render<G: Grid>(
111 grid: &mut G,
112 element_rect: Rect,
113 config: &ShadowConfig,
114 layers: u8,
115 progress: f64,
116 ) {
117 let base_color = config.color_at_progress(progress);
118 if base_color.a == 0 {
119 return;
120 }
121
122 let layers = layers.clamp(1, 4) as usize;
123
124 let rect_x = element_rect.x as i32;
126 let rect_y = element_rect.y as i32;
127 let rect_w = element_rect.width as i32;
128 let rect_h = element_rect.height as i32;
129
130 let ox = config.offset_x as i32;
131 let oy = config.offset_y as i32;
132 let edges = config.edges;
133
134 for layer in (0..layers).rev() {
136 let layer_mult = (layer + 1) as i32;
138 let layer_ox = ox * layer_mult;
139 let layer_oy = oy * layer_mult;
140
141 let intensity = 1.0 - (layer as f32 / layers as f32);
143 let layer_alpha = (base_color.a as f32 * intensity).round() as u8;
144 let layer_color = base_color.with_alpha(layer_alpha);
145
146 Self::render_layer(
148 grid,
149 rect_x,
150 rect_y,
151 rect_w,
152 rect_h,
153 layer_ox,
154 layer_oy,
155 edges,
156 layer_color,
157 );
158 }
159 }
160
161 #[allow(clippy::too_many_arguments)]
163 fn render_layer<G: Grid>(
164 grid: &mut G,
165 rect_x: i32,
166 rect_y: i32,
167 rect_w: i32,
168 rect_h: i32,
169 ox: i32,
170 oy: i32,
171 edges: crate::types::ShadowEdges,
172 color: Color,
173 ) {
174 if color.a == 0 {
175 return;
176 }
177
178 let cell = Cell::new(' ').with_bg(color).with_mod_alpha(Some(255));
179
180 if edges.has_right() && ox > 0 {
182 let start_x = (rect_x + rect_w).max(0) as usize;
183 let end_x = (rect_x + rect_w + ox).max(0) as usize;
184 let start_y = (rect_y + oy.max(0) + 1).max(0) as usize;
187 let end_y = (rect_y + rect_h + oy.min(0)).max(0) as usize;
188
189 Self::fill_region(
190 grid,
191 start_x,
192 start_y,
193 end_x.saturating_sub(start_x),
194 end_y.saturating_sub(start_y),
195 cell,
196 );
197 }
198
199 if edges.has_bottom() && oy > 0 {
201 let start_x = (rect_x + ox.max(0) + 1).max(0) as usize;
204 let end_x = (rect_x + rect_w + ox.min(0)).max(0) as usize;
205 let start_y = (rect_y + rect_h).max(0) as usize;
206 let end_y = (rect_y + rect_h + oy).max(0) as usize;
207
208 Self::fill_region(
209 grid,
210 start_x,
211 start_y,
212 end_x.saturating_sub(start_x),
213 end_y.saturating_sub(start_y),
214 cell,
215 );
216 }
217
218 if edges.has_left() && ox < 0 {
220 let start_x = (rect_x + ox).max(0) as usize;
221 let end_x = rect_x.max(0) as usize;
222 let start_y = (rect_y + oy.max(0)).max(0) as usize;
223 let end_y = (rect_y + rect_h + oy.min(0)).max(0) as usize;
224
225 Self::fill_region(
226 grid,
227 start_x,
228 start_y,
229 end_x.saturating_sub(start_x),
230 end_y.saturating_sub(start_y),
231 cell,
232 );
233 }
234
235 if edges.has_top() && oy < 0 {
237 let start_x = (rect_x + ox.max(0)).max(0) as usize;
238 let end_x = (rect_x + rect_w + ox.min(0)).max(0) as usize;
239 let start_y = (rect_y + oy).max(0) as usize;
240 let end_y = rect_y.max(0) as usize;
241
242 Self::fill_region(
243 grid,
244 start_x,
245 start_y,
246 end_x.saturating_sub(start_x),
247 end_y.saturating_sub(start_y),
248 cell,
249 );
250 }
251
252 if edges.has_right() && edges.has_bottom() && ox > 0 && oy > 0 {
254 let start_x = (rect_x + rect_w).max(0) as usize;
255 let start_y = (rect_y + rect_h).max(0) as usize;
256
257 Self::fill_region(grid, start_x, start_y, ox as usize, oy as usize, cell);
258 }
259
260 if edges.has_left() && edges.has_top() && ox < 0 && oy < 0 {
262 let start_x = (rect_x + ox).max(0) as usize;
263 let start_y = (rect_y + oy).max(0) as usize;
264
265 Self::fill_region(grid, start_x, start_y, (-ox) as usize, (-oy) as usize, cell);
266 }
267 }
268
269 fn fill_region<G: Grid>(grid: &mut G, x: usize, y: usize, w: usize, h: usize, cell: Cell) {
271 for dy in 0..h {
272 for dx in 0..w {
273 let px = x + dx;
274 let py = y + dy;
275 if grid.in_bounds(px, py) {
276 grid.set(px, py, cell);
277 }
278 }
279 }
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::types::ShadowEdges;
287 use tui_vfx_types::OwnedGrid;
288
289 #[test]
290 fn test_render_single_layer() {
291 let mut grid = OwnedGrid::new(30, 15);
292 let rect = Rect::new(5, 2, 10, 6);
293 let config = ShadowConfig::new(Color::BLACK.with_alpha(200))
294 .with_offset(1, 1)
295 .with_edges(ShadowEdges::BOTTOM_RIGHT);
296
297 GradientRenderer::render(&mut grid, rect, &config, 1, 1.0);
298
299 let cell = grid.get(15, 4).unwrap();
301 assert_ne!(cell.bg, Color::TRANSPARENT);
302 }
303
304 #[test]
305 fn test_render_multiple_layers() {
306 let mut grid = OwnedGrid::new(30, 15);
307 let rect = Rect::new(5, 2, 10, 6);
308 let config = ShadowConfig::new(Color::BLACK.with_alpha(200))
309 .with_offset(1, 1)
310 .with_edges(ShadowEdges::BOTTOM_RIGHT);
311
312 GradientRenderer::render(&mut grid, rect, &config, 3, 1.0);
313
314 let outer_cell = grid.get(17, 9).unwrap(); let inner_cell = grid.get(15, 8).unwrap(); assert_ne!(outer_cell.bg, Color::TRANSPARENT);
320 assert_ne!(inner_cell.bg, Color::TRANSPARENT);
321 }
322
323 #[test]
324 fn test_zero_progress_renders_nothing() {
325 let mut grid = OwnedGrid::new(30, 15);
326 let rect = Rect::new(5, 2, 10, 6);
327 let config = ShadowConfig::new(Color::BLACK);
328
329 GradientRenderer::render(&mut grid, rect, &config, 3, 0.0);
330
331 for y in 0..15 {
333 for x in 0..30 {
334 let cell = grid.get(x, y).unwrap();
335 assert_eq!(cell.bg, Color::TRANSPARENT);
336 }
337 }
338 }
339}
340
341