1use crate::{parts::DecorationParts, theme};
2use std::collections::BTreeMap;
3use tiny_skia::{Pixmap, PixmapMut, PixmapRef, Point, PremultipliedColorU8};
4
5// These values were generated from a screenshot of an libadwaita window using a script.
6// For more details see: https://github.com/PolyMeilex/sctk-adwaita/pull/43
7pub const SHADOW_SIZE: u32 = 43;
8const SHADOW_PARAMS_ACTIVE: (f32, f32, f32) = (0.206_505_5, 0.104_617_53, -0.000_542_446_2);
9const SHADOW_PARAMS_INACTIVE: (f32, f32, f32) = (0.168_297_29, 0.204_299_8, 0.001_769_798_6);
10
11fn shadow(pixel_dist: f32, scale: u32, active: bool) -> f32 {
12 let (a: f32, b: f32, c: f32) = if active {
13 SHADOW_PARAMS_ACTIVE
14 } else {
15 SHADOW_PARAMS_INACTIVE
16 };
17
18 a * (-b * (pixel_dist / scale as f32)).exp() + c
19}
20
21#[derive(Debug)]
22struct RenderedShadow {
23 side: Pixmap,
24 edges: Pixmap,
25}
26
27impl RenderedShadow {
28 fn new(scale: u32, active: bool) -> RenderedShadow {
29 let shadow_size = SHADOW_SIZE * scale;
30 let corner_radius = theme::CORNER_RADIUS * scale;
31
32 #[allow(clippy::unwrap_used)]
33 let mut side = Pixmap::new(shadow_size, 1).unwrap();
34 for x in 0..side.width() as usize {
35 let alpha = (shadow(x as f32 + 0.5, scale, active) * u8::MAX as f32).round() as u8;
36
37 #[allow(clippy::unwrap_used)]
38 let color = PremultipliedColorU8::from_rgba(0, 0, 0, alpha).unwrap();
39 side.pixels_mut()[x] = color;
40 }
41
42 let edges_size = (corner_radius + shadow_size) * 2;
43 #[allow(clippy::unwrap_used)]
44 let mut edges = Pixmap::new(edges_size, edges_size).unwrap();
45 let edges_middle = Point::from_xy(edges_size as f32 / 2.0, edges_size as f32 / 2.0);
46 for y in 0..edges_size as usize {
47 let y_pos = y as f32 + 0.5;
48 for x in 0..edges_size as usize {
49 let dist = edges_middle.distance(Point::from_xy(x as f32 + 0.5, y_pos))
50 - corner_radius as f32;
51 let alpha = (shadow(dist, scale, active) * u8::MAX as f32).round() as u8;
52
53 #[allow(clippy::unwrap_used)]
54 let color = PremultipliedColorU8::from_rgba(0, 0, 0, alpha).unwrap();
55 edges.pixels_mut()[y * edges_size as usize + x] = color;
56 }
57 }
58
59 RenderedShadow { side, edges }
60 }
61
62 fn side_draw(
63 &self,
64 flipped: bool,
65 rotated: bool,
66 stack: usize,
67 dst_pixmap: &mut PixmapMut,
68 dst_left: usize,
69 dst_top: usize,
70 ) {
71 fn iter_copy<'a>(
72 src: impl Iterator<Item = &'a PremultipliedColorU8>,
73 dst: impl Iterator<Item = &'a mut PremultipliedColorU8>,
74 ) {
75 src.zip(dst).for_each(|(src, dst)| *dst = *src)
76 }
77
78 let dst_width = dst_pixmap.width() as usize;
79 let dst_pixels = dst_pixmap.pixels_mut();
80 match (flipped, rotated) {
81 (false, false) => (0..stack).for_each(|i| {
82 let dst = dst_pixels
83 .iter_mut()
84 .skip((dst_top + i) * dst_width + dst_left);
85 iter_copy(self.side.pixels().iter(), dst);
86 }),
87 (false, true) => (0..stack).for_each(|i| {
88 let dst = dst_pixels
89 .iter_mut()
90 .skip(dst_top * dst_width + dst_left + i)
91 .step_by(dst_width);
92 iter_copy(self.side.pixels().iter(), dst);
93 }),
94 (true, false) => (0..stack).for_each(|i| {
95 let dst = dst_pixels
96 .iter_mut()
97 .skip((dst_top + i) * dst_width + dst_left);
98 iter_copy(self.side.pixels().iter().rev(), dst);
99 }),
100 (true, true) => (0..stack).for_each(|i| {
101 let dst = dst_pixels
102 .iter_mut()
103 .skip(dst_top * dst_width + dst_left + i)
104 .step_by(dst_width);
105 iter_copy(self.side.pixels().iter().rev(), dst);
106 }),
107 }
108 }
109
110 #[allow(clippy::too_many_arguments)]
111 fn edges_draw(
112 &self,
113 src_x_offset: isize,
114 src_y_offset: isize,
115 dst_pixmap: &mut PixmapMut,
116 dst_rect_left: usize,
117 dst_rect_top: usize,
118 dst_rect_width: usize,
119 dst_rect_height: usize,
120 ) {
121 let src_width = self.edges.width() as usize;
122 let src_pixels = self.edges.pixels();
123 let dst_width = dst_pixmap.width() as usize;
124 let dst_pixels = dst_pixmap.pixels_mut();
125 for y in 0..dst_rect_height {
126 let dst_y = dst_rect_top + y;
127 let src_y = y as isize + src_y_offset;
128 if src_y < 0 {
129 continue;
130 }
131
132 let src_y = src_y as usize;
133 for x in 0..dst_rect_width {
134 let dst_x = dst_rect_left + x;
135 let src_x = x as isize + src_x_offset;
136 if src_x < 0 {
137 continue;
138 }
139
140 let src = src_pixels.get(src_y * src_width + src_x as usize);
141 let dst = dst_pixels.get_mut(dst_y * dst_width + dst_x);
142 if let (Some(src), Some(dst)) = (src, dst) {
143 *dst = *src;
144 }
145 }
146 }
147 }
148
149 fn draw(&self, dst_pixmap: &mut PixmapMut, scale: u32, part_idx: usize) {
150 let shadow_size = (SHADOW_SIZE * scale) as usize;
151 let visible_border_size = (theme::VISIBLE_BORDER_SIZE * scale) as usize;
152 let corner_radius = (theme::CORNER_RADIUS * scale) as usize;
153 assert!(corner_radius > visible_border_size);
154
155 let dst_width = dst_pixmap.width() as usize;
156 let dst_height = dst_pixmap.height() as usize;
157 let edges_half = self.edges.width() as usize / 2;
158 match part_idx {
159 DecorationParts::TOP => {
160 let left_edge_width = edges_half;
161 let right_edge_width = edges_half;
162 let side_width = dst_width
163 .saturating_sub(left_edge_width)
164 .saturating_sub(right_edge_width);
165
166 self.edges_draw(
167 0,
168 -(visible_border_size as isize),
169 dst_pixmap,
170 0,
171 0,
172 left_edge_width,
173 dst_height,
174 );
175
176 self.side_draw(
177 true,
178 true,
179 side_width,
180 dst_pixmap,
181 left_edge_width,
182 visible_border_size,
183 );
184
185 self.edges_draw(
186 edges_half as isize,
187 -(visible_border_size as isize),
188 dst_pixmap,
189 left_edge_width + side_width,
190 0,
191 right_edge_width,
192 dst_height,
193 );
194 }
195 DecorationParts::LEFT => {
196 let top_edge_height = corner_radius;
197 let bottom_edge_height = corner_radius - visible_border_size;
198 let side_height = dst_height
199 .saturating_sub(top_edge_height)
200 .saturating_sub(bottom_edge_height);
201
202 self.edges_draw(
203 0,
204 shadow_size as isize,
205 dst_pixmap,
206 0,
207 0,
208 dst_width.saturating_sub(visible_border_size),
209 top_edge_height,
210 );
211
212 self.side_draw(true, false, side_height, dst_pixmap, 0, top_edge_height);
213
214 self.edges_draw(
215 0,
216 edges_half as isize,
217 dst_pixmap,
218 0,
219 top_edge_height + side_height,
220 dst_width.saturating_sub(visible_border_size),
221 bottom_edge_height,
222 );
223 }
224 DecorationParts::RIGHT => {
225 let top_edge_height = corner_radius;
226 let bottom_edge_height = corner_radius - visible_border_size;
227 let side_height = dst_height
228 .saturating_sub(top_edge_height)
229 .saturating_sub(bottom_edge_height);
230
231 self.edges_draw(
232 edges_half as isize + corner_radius as isize,
233 shadow_size as isize,
234 dst_pixmap,
235 visible_border_size,
236 0,
237 dst_width.saturating_sub(visible_border_size),
238 top_edge_height,
239 );
240
241 self.side_draw(
242 false,
243 false,
244 side_height,
245 dst_pixmap,
246 visible_border_size,
247 top_edge_height,
248 );
249
250 self.edges_draw(
251 edges_half as isize + corner_radius as isize,
252 edges_half as isize,
253 dst_pixmap,
254 visible_border_size,
255 top_edge_height + side_height,
256 dst_width.saturating_sub(visible_border_size),
257 bottom_edge_height,
258 );
259 }
260 DecorationParts::BOTTOM => {
261 let left_edge_width = edges_half;
262 let right_edge_width = edges_half;
263 let side_width = dst_width
264 .saturating_sub(left_edge_width)
265 .saturating_sub(right_edge_width);
266
267 self.edges_draw(
268 0,
269 edges_half as isize + (corner_radius - visible_border_size) as isize,
270 dst_pixmap,
271 0,
272 0,
273 left_edge_width,
274 dst_height,
275 );
276
277 self.side_draw(
278 false,
279 true,
280 side_width,
281 dst_pixmap,
282 left_edge_width,
283 visible_border_size,
284 );
285
286 self.edges_draw(
287 edges_half as isize,
288 edges_half as isize + (corner_radius - visible_border_size) as isize,
289 dst_pixmap,
290 left_edge_width + side_width,
291 0,
292 right_edge_width,
293 dst_height,
294 );
295 }
296 DecorationParts::HEADER => {
297 self.edges_draw(
298 shadow_size as isize,
299 shadow_size as isize,
300 dst_pixmap,
301 0,
302 0,
303 corner_radius,
304 corner_radius,
305 );
306
307 self.edges_draw(
308 edges_half as isize,
309 shadow_size as isize,
310 dst_pixmap,
311 dst_width.saturating_sub(corner_radius),
312 0,
313 corner_radius,
314 corner_radius,
315 );
316 }
317 _ => unreachable!(),
318 }
319 }
320}
321
322#[derive(Debug)]
323struct CachedPart {
324 pixmap: Pixmap,
325 scale: u32,
326 active: bool,
327}
328
329impl CachedPart {
330 fn new(
331 dst_pixmap: &PixmapRef,
332 rendered: &RenderedShadow,
333 scale: u32,
334 active: bool,
335 part_idx: usize,
336 ) -> CachedPart {
337 #[allow(clippy::unwrap_used)]
338 let mut pixmap = Pixmap::new(dst_pixmap.width(), dst_pixmap.height()).unwrap();
339 rendered.draw(&mut pixmap.as_mut(), scale, part_idx);
340
341 CachedPart {
342 pixmap,
343 scale,
344 active,
345 }
346 }
347
348 fn matches(&self, dst_pixmap: &PixmapRef, dst_scale: u32, dst_active: bool) -> bool {
349 self.pixmap.width() == dst_pixmap.width()
350 && self.pixmap.height() == dst_pixmap.height()
351 && self.scale == dst_scale
352 && self.active == dst_active
353 }
354
355 fn draw(&self, dst_pixmap: &mut PixmapMut) {
356 let src_data = self.pixmap.data();
357 dst_pixmap.data_mut()[..src_data.len()].copy_from_slice(src_data);
358 }
359}
360
361#[derive(Default, Debug)]
362pub struct Shadow {
363 part_cache: [Option<CachedPart>; 5],
364 // (scale, active) -> RenderedShadow
365 rendered: BTreeMap<(u32, bool), RenderedShadow>,
366}
367
368impl Shadow {
369 pub fn draw(&mut self, pixmap: &mut PixmapMut, scale: u32, active: bool, part_idx: usize) {
370 let cache = &mut self.part_cache[part_idx];
371
372 if let Some(cache_value) = cache {
373 if !cache_value.matches(&pixmap.as_ref(), scale, active) {
374 *cache = None;
375 }
376 }
377
378 if cache.is_none() {
379 let rendered = self
380 .rendered
381 .entry((scale, active))
382 .or_insert_with(|| RenderedShadow::new(scale, active));
383
384 *cache = Some(CachedPart::new(
385 &pixmap.as_ref(),
386 rendered,
387 scale,
388 active,
389 part_idx,
390 ));
391 }
392
393 // We filled the cache above.
394 #[allow(clippy::unwrap_used)]
395 cache.as_ref().unwrap().draw(pixmap);
396 }
397}
398