Skip to main content

lgui_core/core/scene/
shadow.rs

1use super::{Color, UiRect, UiScale};
2
3/// A drop shadow of an element's composited alpha, including its descendants.
4/// Distances are logical pixels; blur is the Gaussian standard deviation.
5/// Positive spread dilates the alpha mask; negative spread erodes it.
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7pub struct ShadowStyle {
8    pub color: Color,
9    pub alpha: u8,
10    offset_x_millis: i32,
11    offset_y_millis: i32,
12    blur_millis: i32,
13    spread_millis: i32,
14}
15
16impl ShadowStyle {
17    pub const fn new(color: Color) -> Self {
18        Self {
19            color,
20            alpha: 64,
21            offset_x_millis: 0,
22            offset_y_millis: 4000,
23            blur_millis: 6000,
24            spread_millis: 0,
25        }
26    }
27
28    pub const fn alpha(mut self, alpha: u8) -> Self {
29        self.alpha = alpha;
30        self
31    }
32
33    pub fn offset(mut self, x: f32, y: f32) -> Self {
34        self.offset_x_millis = distance(x);
35        self.offset_y_millis = distance(y);
36        self
37    }
38
39    pub fn blur(mut self, sigma: f32) -> Self {
40        self.blur_millis = distance(sigma).max(0);
41        self
42    }
43
44    pub fn spread(mut self, spread: f32) -> Self {
45        self.spread_millis = distance(spread);
46        self
47    }
48
49    pub fn offset_x(self) -> f32 {
50        self.offset_x_millis as f32 / 1000.0
51    }
52    pub fn offset_y(self) -> f32 {
53        self.offset_y_millis as f32 / 1000.0
54    }
55    pub fn blur_sigma(self) -> f32 {
56        self.blur_millis as f32 / 1000.0
57    }
58    pub fn spread_radius(self) -> f32 {
59        self.spread_millis as f32 / 1000.0
60    }
61
62    pub fn paint_bounds(self, content: UiRect) -> UiRect {
63        if self.alpha == 0 {
64            return content;
65        }
66        // Four sigma covers the Gaussian kernel support; reserve another sampling pixel.
67        let outset = (self.blur_sigma() * 4.0).ceil() + self.spread_radius().max(0.0).ceil() + 1.0;
68        let padded = content.inflate(outset, outset);
69        padded.union(padded.translate(self.offset_x(), self.offset_y()))
70    }
71
72    pub(crate) fn project_to_physical(self, scale: UiScale) -> Self {
73        self.offset(
74            scale.physical_ui_value(self.offset_x()),
75            scale.physical_ui_value(self.offset_y()),
76        )
77        .blur(scale.physical_ui_value(self.blur_sigma()))
78        .spread(scale.physical_ui_value(self.spread_radius()))
79    }
80}
81
82impl Default for ShadowStyle {
83    fn default() -> Self {
84        Self::new(Color::BLACK)
85    }
86}
87
88fn distance(value: f32) -> i32 {
89    if value.is_finite() {
90        (value.clamp(-4096.0, 4096.0) * 1000.0).round() as i32
91    } else {
92        0
93    }
94}