Skip to main content

ferroui_base/rendering/composition/server/
server_drop_shadow.rs

1use super::{
2    drop_shadow_effect, impl_server_render_resource, paint_composition_brush, IAnimatedServerObject, IServerObject, IServerRenderResource,
3    ServerCompositionVisual, ServerCompositor, ServerRenderResource,
4};
5use crate::media::effects::ImmutableBlurEffect;
6use crate::media::imaging::BitmapBlendingMode;
7use crate::media::{BoxShadow, BoxShadows, Color, RenderOptions};
8use crate::platform::{AlphaFormat, IDrawingContextImpl, PixelFormats, SharedBitmapImpl};
9use crate::rendering::composition::generated::{ServerDropShadowHooks, ServerDropShadowProps};
10use crate::rendering::composition::transport::BatchStreamReader;
11use crate::rendering::composition::CompositionDropShadowSourcePolicy;
12use crate::{Matrix, PixelSize, Rect, RoundedRect, Vector};
13use std::any::{Any, TypeId};
14use std::cell::RefCell;
15use std::rc::{Rc, Weak};
16use std::sync::Arc;
17use std::time::Duration;
18
19/// The side of the bitmap of the colour of a shadow, in pixels. Only its
20/// middle is sampled, so that the filtering of its edges never shows.
21const COLOR_BITMAP_SIDE: i32 = 4;
22
23/// The server-side counterpart of a
24/// [`DropShadow`](crate::rendering::composition::DropShadow).
25///
26/// The shadow of the rectangle of a visual is the box shadow of the drawing
27/// context. The shadow of an alpha (a `Mask` brush, or the content of the
28/// visual with the `InheritFromVisualContent` source policy) is drawn by
29/// [`render_shadow_of`](Self::render_shadow_of) the same way on every backend
30/// that has effects: the source, coloured by the shadow, offset and blurred.
31pub struct ServerDropShadow {
32    base: ServerRenderResource,
33    props: ServerDropShadowProps,
34    /// The bitmap of the colour of the shadow (premultiplied, with the
35    /// opacity), and the colour it was made for.
36    color_bitmap: RefCell<Option<(Color, Arc<SharedBitmapImpl>)>>,
37}
38
39impl ServerDropShadow {
40    /// Creates the shadow, active: the UI-thread shadow activates its server
41    /// object when it is created.
42    pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerDropShadow> {
43        let shadow = Rc::new_cyclic(|this: &Weak<ServerDropShadow>| ServerDropShadow {
44            base: ServerRenderResource::new(
45                compositor,
46                this.clone() as Weak<dyn IAnimatedServerObject>,
47                this.clone() as Weak<dyn IServerRenderResource>,
48            ),
49            props: ServerDropShadowProps::new(),
50            color_bitmap: RefCell::new(None),
51        });
52        shadow.base.object().activate();
53        shadow
54    }
55
56    /// The properties of the shadow.
57    pub fn props(&self) -> &ServerDropShadowProps {
58        &self.props
59    }
60
61    pub fn is_disposed(&self) -> bool {
62        self.base.is_disposed()
63    }
64
65    /// The colour of the shadow with its opacity in the alpha.
66    pub fn effective_color(&self) -> Color {
67        let color = self.props.color();
68        let opacity = self.props.opacity().clamp(0.0, 1.0);
69        let alpha = (color.a as f32 * opacity).round().clamp(0.0, 255.0) as u8;
70        Color::from_argb(alpha, color.r, color.g, color.b)
71    }
72
73    /// The offset of the shadow in the plane of `visual`: the X and Y of
74    /// `Offset`, and its Z as the renderers project that depth for the
75    /// visual ([`ServerCompositionVisual::depth_offset`]); without a visual
76    /// the X and Y alone.
77    pub fn plane_offset(&self, visual: Option<&ServerCompositionVisual>) -> Vector {
78        let offset = self.props.offset();
79        let depth = match visual {
80            Some(visual) => visual.depth_offset(offset.z as f32),
81            None => Vector::default(),
82        };
83        Vector::new(offset.x + depth.x, offset.y + depth.y)
84    }
85
86    /// The shadow of the rectangle of `visual` as a box shadow: offset, blur
87    /// radius, and the colour with the opacity of the shadow in its alpha.
88    pub fn box_shadow_in(&self, visual: Option<&ServerCompositionVisual>) -> BoxShadow {
89        let offset = self.plane_offset(visual);
90        BoxShadow {
91            offset_x: offset.x,
92            offset_y: offset.y,
93            blur: self.props.blur_radius().max(0.0) as f64,
94            spread: 0.0,
95            color: self.effective_color(),
96            is_inset: false,
97        }
98    }
99
100    /// [`box_shadow_in`](Self::box_shadow_in) without a visual.
101    pub fn box_shadow(&self) -> BoxShadow {
102        self.box_shadow_in(None)
103    }
104
105    /// The box shadows the shadow draws under a rectangle of `visual`: empty
106    /// when the shadow is invisible.
107    pub fn box_shadows_in(&self, visual: Option<&ServerCompositionVisual>) -> BoxShadows {
108        let shadow = self.box_shadow_in(visual);
109        if shadow.color.a == 0 {
110            return BoxShadows::default();
111        }
112        BoxShadows::new(shadow)
113    }
114
115    /// [`box_shadows_in`](Self::box_shadows_in) without a visual.
116    pub fn box_shadows(&self) -> BoxShadows {
117        self.box_shadows_in(None)
118    }
119
120    /// The area the shadow of `rect` of `visual` covers, together with
121    /// `rect`: the rectangle moved by the offset and grown by the blur
122    /// radius. The shadow of an alpha inside `rect` stays inside it too.
123    pub fn bounds_in(&self, visual: Option<&ServerCompositionVisual>, rect: Rect) -> Rect {
124        let shadow = self.box_shadow_in(visual);
125        rect.union(shadow.transform_bounds(rect))
126    }
127
128    /// [`bounds_in`](Self::bounds_in) without a visual.
129    pub fn bounds(&self, rect: Rect) -> Rect {
130        self.bounds_in(None, rect)
131    }
132
133    /// Whether the shadow is drawn at all: its colour and opacity leave some
134    /// alpha.
135    pub fn is_visible(&self) -> bool {
136        self.effective_color().a != 0
137    }
138
139    /// Whether the shadow takes the alpha of the content of its visual
140    /// (`SourcePolicy` `InheritFromVisualContent`), which wins over the mask.
141    pub fn inherits_from_visual_content(&self) -> bool {
142        self.props.source_policy() == CompositionDropShadowSourcePolicy::InheritFromVisualContent
143    }
144
145    /// The mask that shapes the shadow: the `Mask` brush, unless the shadow
146    /// takes the alpha of the content of its visual.
147    pub fn effective_mask(&self) -> Option<Rc<dyn IServerObject>> {
148        if self.inherits_from_visual_content() {
149            return None;
150        }
151        self.props.mask()
152    }
153
154    /// Draws the shadow of a sprite or layer visual whose area is `rect`, outside
155    /// the plane of any visual: [`render_under_rect_in`](Self::render_under_rect_in)
156    /// without a visual.
157    pub fn render_under_rect(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
158        self.render_under_rect_in(canvas, None, rect)
159    }
160
161    /// Draws the content of a visual whose area is `rect` (`paint`) with the
162    /// shadow of the alpha of that content under it, through a drop shadow
163    /// effect of the drawing context: the `InheritFromVisualContent` source
164    /// policy. Returns `false`, drawing nothing, when the drawing context has
165    /// no effects; the caller then draws the shadow with
166    /// [`render_under_rect`](Self::render_under_rect) and the content itself.
167    pub fn render_with_content(
168        &self,
169        canvas: &mut dyn IDrawingContextImpl,
170        rect: Rect,
171        paint: impl FnOnce(&mut dyn IDrawingContextImpl),
172    ) -> bool {
173        if canvas.as_drawing_context_impl_with_effects().is_none() {
174            return false;
175        }
176        let effect = drop_shadow_effect(self);
177        let bounds = self.bounds(rect);
178        if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
179            effects.push_effect(Some(bounds), &effect);
180        }
181        paint(&mut *canvas);
182        if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
183            effects.pop_effect();
184        }
185        true
186    }
187
188    /// Draws the shadow of a sprite or layer visual whose area is `rect`,
189    /// without the content of the visual: the shadow of the alpha of the
190    /// mask when there is one ([`render_shadow_of`](Self::render_shadow_of)),
191    /// otherwise the box shadow of the rectangle. Nothing for an invisible
192    /// shadow or an empty rectangle.
193    ///
194    /// Deviation (winui-composition.md): without effects in the drawing
195    /// context, the shadow of a mask is the box shadow of the rectangle.
196    pub fn render_under_rect_in(
197        &self,
198        canvas: &mut dyn IDrawingContextImpl,
199        visual: Option<&ServerCompositionVisual>,
200        rect: Rect,
201    ) {
202        if rect.width <= 0.0 || rect.height <= 0.0 || !self.is_visible() {
203            return;
204        }
205        if let Some(mask) = self.effective_mask() {
206            if self.render_shadow_of(canvas, visual, rect, |canvas| paint_composition_brush(canvas, &mask, rect)) {
207                return;
208            }
209        }
210        let shadows = self.box_shadows_in(visual);
211        if shadows.count() > 0 {
212            canvas.draw_rectangle(None, None, RoundedRect::from_rect(rect), &shadows);
213        }
214    }
215
216    /// Draws the shadow of the alpha of what `paint` draws inside `rect` (a
217    /// mask brush, the brush of a sprite visual, the image of a mask): that
218    /// alpha in the colour of the shadow, moved by its offset and blurred by
219    /// its blur radius, and nothing of the source itself. Returns `false`,
220    /// drawing nothing, when the drawing context has no effects or there is
221    /// no render interface for the bitmap of the colour; the caller then
222    /// draws the box shadow of the rectangle.
223    ///
224    /// Inside a blur effect, the source is painted into a layer at the
225    /// offset of the shadow and a bitmap of the colour of the shadow is drawn
226    /// over it with source-in blending, which keeps the colour where the
227    /// source has alpha, in the proportion of that alpha: the operations
228    /// every drawing context with effects has, so Skia and Vello draw the
229    /// same shadow.
230    pub fn render_shadow_of(
231        &self,
232        canvas: &mut dyn IDrawingContextImpl,
233        visual: Option<&ServerCompositionVisual>,
234        rect: Rect,
235        paint: impl FnOnce(&mut dyn IDrawingContextImpl),
236    ) -> bool {
237        if rect.width <= 0.0 || rect.height <= 0.0 {
238            return true;
239        }
240        let color = self.effective_color();
241        if color.a == 0 {
242            return true;
243        }
244        if canvas.as_drawing_context_impl_with_effects().is_none() {
245            return false;
246        }
247        let Some(bitmap) = self.color_bitmap(color) else { return false };
248
249        let offset = self.plane_offset(visual);
250        let blur = self.props.blur_radius().max(0.0) as f64;
251        let bounds = self.bounds_in(visual, rect);
252        // The source is painted inside `rect`; the colour covers a little
253        // more, so that its own edges are never part of the shadow.
254        let colored = rect.inflate(2.0);
255
256        let blurred = blur > 0.0;
257        if blurred {
258            if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
259                effects.push_effect(Some(bounds), &ImmutableBlurEffect::new(blur));
260            }
261        }
262        let transform = canvas.transform();
263        let moved = offset != Vector::default();
264        if moved {
265            canvas.set_transform(Matrix::create_translation(offset.x, offset.y) * transform);
266        }
267        canvas.push_layer(colored);
268        paint(&mut *canvas);
269        canvas.push_render_options(RenderOptions {
270            bitmap_blending_mode: BitmapBlendingMode::SourceIn,
271            ..RenderOptions::default()
272        });
273        let side = COLOR_BITMAP_SIDE as f64;
274        canvas.draw_bitmap(&*bitmap, 1.0, Rect::new(1.0, 1.0, side - 2.0, side - 2.0), colored);
275        canvas.pop_render_options();
276        canvas.pop_layer();
277        if moved {
278            canvas.set_transform(transform);
279        }
280        if blurred {
281            if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
282                effects.pop_effect();
283            }
284        }
285        true
286    }
287
288    /// The bitmap of `color` (a few pixels of it, premultiplied), made with
289    /// the render interface of the compositor and kept until the colour
290    /// changes; `None` without a render interface.
291    fn color_bitmap(&self, color: Color) -> Option<Arc<SharedBitmapImpl>> {
292        if let Some((cached, bitmap)) = self.color_bitmap.borrow().as_ref() {
293            if *cached == color {
294                return Some(bitmap.clone());
295            }
296        }
297        let compositor = self.base.compositor()?;
298        let premultiply = |channel: u8| ((channel as u32 * color.a as u32 + 127) / 255) as u8;
299        let pixel = [premultiply(color.r), premultiply(color.g), premultiply(color.b), color.a];
300        let pixels: Vec<u8> = pixel.iter().copied().cycle().take((COLOR_BITMAP_SIDE * COLOR_BITMAP_SIDE * 4) as usize).collect();
301        let size = PixelSize::new(COLOR_BITMAP_SIDE, COLOR_BITMAP_SIDE);
302        let bitmap = compositor.render_interface().with_platform_render_interface(|render_interface| {
303            render_interface.load_bitmap_from_pixels(
304                PixelFormats::RGBA8888,
305                AlphaFormat::Premul,
306                &pixels,
307                size,
308                Vector::new(96.0, 96.0),
309                COLOR_BITMAP_SIDE * 4,
310            )
311        })?;
312        *self.color_bitmap.borrow_mut() = Some((color, bitmap.clone()));
313        Some(bitmap)
314    }
315}
316
317impl_server_render_resource!(
318    ServerDropShadow,
319    base,
320    composition_property: ServerDropShadowProps::get_composition_property
321);
322
323impl ServerDropShadowHooks for ServerDropShadow {}
324
325impl IServerObject for ServerDropShadow {
326    fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
327        self.props.deserialize_changes_core(self, reader, committed_at);
328    }
329
330    fn values_invalidated(&self) {
331        self.base.core().invalidated(self);
332    }
333
334    fn dispose(&self) {
335        self.color_bitmap.borrow_mut().take();
336        self.base.core().dispose();
337    }
338
339    fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
340        Some(self)
341    }
342
343    fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
344        Some(self)
345    }
346
347    fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
348        self.props.find_props(type_id)
349    }
350
351    fn as_any(&self) -> &dyn Any {
352        self
353    }
354
355    fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
356        self
357    }
358}