1use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, ColorU, PropertyBinding};
5use api::units::*;
6use crate::border::{BorderRadiusAu};
7use crate::clip::{ClipItemEntry, ClipItemKey, ClipItemKeyKind, ClipNodeId};
8use crate::intern::{Handle as InternHandle, InternDebug, Internable};
9use crate::prim_store::{InternablePrimitive, PrimKey, PrimTemplate, PrimTemplateCommonData};
10use crate::prim_store::{PrimitiveKind, PrimitiveStore, VectorKey};
11use crate::prim_store::rectangle::RectanglePrim;
12use crate::scene_building::{SceneBuilder, IsVisible};
13use crate::spatial_tree::SpatialNodeIndex;
14use crate::internal_types::LayoutPrimitiveInfo;
15
16pub type BoxShadowKey = PrimKey<BoxShadow>;
17
18impl BoxShadowKey {
19 pub fn new(
20 info: &LayoutPrimitiveInfo,
21 shadow: BoxShadow,
22 ) -> Self {
23 BoxShadowKey {
24 common: info.into(),
25 kind: shadow,
26 }
27 }
28}
29
30impl InternDebug for BoxShadowKey {}
31
32#[cfg_attr(feature = "capture", derive(Serialize))]
33#[cfg_attr(feature = "replay", derive(Deserialize))]
34#[derive(Debug, Clone, MallocSizeOf, Hash, Eq, PartialEq)]
35pub struct BoxShadow {
36 pub color: ColorU,
37 pub blur_radius: Au,
38 pub clip_mode: BoxShadowClipMode,
39 pub shadow_radius: BorderRadiusAu,
40 pub element_radius: BorderRadiusAu,
41 pub box_offset: VectorKey,
44 pub spread_amount: Au,
47}
48
49impl IsVisible for BoxShadow {
50 fn is_visible(&self) -> bool {
51 true
52 }
53}
54
55pub type BoxShadowDataHandle = InternHandle<BoxShadow>;
56
57impl InternablePrimitive for BoxShadow {
58 fn into_key(
59 self,
60 info: &LayoutPrimitiveInfo,
61 ) -> BoxShadowKey {
62 BoxShadowKey::new(info, self)
63 }
64
65 fn make_instance_kind(
66 _key: BoxShadowKey,
67 data_handle: BoxShadowDataHandle,
68 _prim_store: &mut PrimitiveStore,
69 ) -> PrimitiveKind {
70 PrimitiveKind::BoxShadow {
71 data_handle,
72 }
73 }
74}
75
76#[cfg_attr(feature = "capture", derive(Serialize))]
77#[cfg_attr(feature = "replay", derive(Deserialize))]
78#[derive(Debug, MallocSizeOf)]
79pub struct BoxShadowData {
80 pub color: ColorF,
81 pub blur_radius: f32,
82 pub clip_mode: BoxShadowClipMode,
83 pub shadow_radius: BorderRadius,
84 pub element_radius: BorderRadius,
85 pub box_offset: LayoutVector2D,
86 pub spread_amount: f32,
87}
88
89impl From<BoxShadow> for BoxShadowData {
90 fn from(shadow: BoxShadow) -> Self {
91 BoxShadowData {
92 color: shadow.color.into(),
93 blur_radius: shadow.blur_radius.to_f32_px(),
94 clip_mode: shadow.clip_mode,
95 shadow_radius: shadow.shadow_radius.into(),
96 element_radius: shadow.element_radius.into(),
97 box_offset: shadow.box_offset.into(),
98 spread_amount: shadow.spread_amount.to_f32_px(),
99 }
100 }
101}
102
103pub type BoxShadowTemplate = PrimTemplate<BoxShadowData>;
104
105impl Internable for BoxShadow {
106 type Key = BoxShadowKey;
107 type StoreData = BoxShadowTemplate;
108 type InternData = ();
109 const PROFILE_COUNTER: usize = crate::profiler::INTERNED_BOX_SHADOWS;
110}
111
112impl From<BoxShadowKey> for BoxShadowTemplate {
113 fn from(shadow: BoxShadowKey) -> Self {
114 BoxShadowTemplate {
115 common: PrimTemplateCommonData::with_key_common(shadow.common),
116 kind: shadow.kind.into(),
117 }
118 }
119}
120
121pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
123
124pub const MAX_BLUR_RADIUS: f32 = 300.;
127
128#[derive(Debug, Clone, Eq, Hash, MallocSizeOf, PartialEq)]
132#[cfg_attr(feature = "capture", derive(Serialize))]
133#[cfg_attr(feature = "replay", derive(Deserialize))]
134pub struct BoxShadowCacheKey {
135 pub blur_radius_dp: Au,
138 pub clip_mode: BoxShadowClipMode,
139 pub original_alloc_size: DeviceIntSize,
142 pub br_top_left: DeviceIntSize,
143 pub br_top_right: DeviceIntSize,
144 pub br_bottom_right: DeviceIntSize,
145 pub br_bottom_left: DeviceIntSize,
146 pub device_pixel_scale: Au,
147}
148
149impl<'a> SceneBuilder<'a> {
150 pub fn add_box_shadow(
151 &mut self,
152 spatial_node_index: SpatialNodeIndex,
153 clip_node_id: ClipNodeId,
154 prim_info: &LayoutPrimitiveInfo,
155 box_offset: &LayoutVector2D,
156 color: ColorF,
157 mut blur_radius: f32,
158 spread_radius: f32,
159 border_radius: BorderRadius,
160 shadow_radius: BorderRadius,
161 clip_mode: BoxShadowClipMode,
162 ) {
163 if color.a == 0.0 {
164 return;
165 }
166
167 let spread_amount = match clip_mode {
169 BoxShadowClipMode::Outset => spread_radius,
170 BoxShadowClipMode::Inset => -spread_radius,
171 };
172
173 blur_radius = f32::min(blur_radius, MAX_BLUR_RADIUS);
175
176 let shadow_rect = prim_info
179 .rect
180 .translate(*box_offset)
181 .inflate(spread_amount, spread_amount);
182
183 if blur_radius == 0.0 {
186 if box_offset.x == 0.0 && box_offset.y == 0.0 && spread_amount == 0.0 {
188 return;
189 }
190
191 let mut clips = Vec::with_capacity(2);
192 let (final_prim_rect, clip_radius) = match clip_mode {
193 BoxShadowClipMode::Outset => {
194 if shadow_rect.is_empty() {
195 return;
196 }
197
198 clips.push(ClipItemEntry {
200 key: ClipItemKey {
201 kind: ClipItemKeyKind::rounded_rect(
202 border_radius,
203 ClipMode::ClipOut,
204 ),
205 },
206 spatial_node_index,
207 clip_rect: prim_info.rect,
208 });
209
210 (shadow_rect, shadow_radius)
211 }
212 BoxShadowClipMode::Inset => {
213 if !shadow_rect.is_empty() {
214 clips.push(ClipItemEntry {
215 key: ClipItemKey {
216 kind: ClipItemKeyKind::rounded_rect(
217 shadow_radius,
218 ClipMode::ClipOut,
219 ),
220 },
221 spatial_node_index,
222 clip_rect: shadow_rect,
223 });
224 }
225
226 (prim_info.rect, border_radius)
227 }
228 };
229
230 clips.push(ClipItemEntry {
231 key: ClipItemKey {
232 kind: ClipItemKeyKind::rounded_rect(
233 clip_radius,
234 ClipMode::Clip,
235 ),
236 },
237 spatial_node_index,
238 clip_rect: final_prim_rect,
239 });
240
241 self.add_primitive(
242 spatial_node_index,
243 clip_node_id,
244 &LayoutPrimitiveInfo::with_clip_rect(final_prim_rect, prim_info.clip_rect),
245 clips,
246 RectanglePrim {
247 color: PropertyBinding::Value(color.into()),
248 },
249 );
250 } else {
251 let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil();
254
255 let dest_rect = shadow_rect.inflate(blur_offset, blur_offset);
258
259 match clip_mode {
260 BoxShadowClipMode::Outset => {
261 if shadow_rect.is_empty() {
263 return;
264 }
265
266 self.add_nonshadowable_primitive(
268 spatial_node_index,
269 clip_node_id,
270 &LayoutPrimitiveInfo::with_clip_rect(dest_rect, prim_info.clip_rect),
271 vec![],
272 BoxShadow {
273 color: color.into(),
274 blur_radius: Au::from_f32_px(blur_radius),
275 clip_mode,
276 shadow_radius: shadow_radius.into(),
277 element_radius: border_radius.into(),
278 box_offset: (*box_offset).into(),
279 spread_amount: Au::from_f32_px(spread_amount),
280 },
281 );
282 }
283 BoxShadowClipMode::Inset => {
284 if border_radius.is_zero() && shadow_rect
287 .inflate(-blur_radius, -blur_radius)
288 .contains_box(&prim_info.rect)
289 {
290 return;
291 }
292
293 self.add_nonshadowable_primitive(
295 spatial_node_index,
296 clip_node_id,
297 &prim_info.clone(),
298 vec![],
299 BoxShadow {
300 color: color.into(),
301 blur_radius: Au::from_f32_px(blur_radius),
302 clip_mode,
303 shadow_radius: shadow_radius.into(),
304 element_radius: border_radius.into(),
305 box_offset: (*box_offset).into(),
306 spread_amount: Au::from_f32_px(spread_amount),
307 },
308 );
309 }
310 }
311 }
312 }
313}