use super::{
drop_shadow_effect, impl_server_render_resource, paint_composition_brush, IAnimatedServerObject, IServerObject, IServerRenderResource,
ServerCompositionVisual, ServerCompositor, ServerRenderResource,
};
use crate::media::effects::ImmutableBlurEffect;
use crate::media::imaging::BitmapBlendingMode;
use crate::media::{BoxShadow, BoxShadows, Color, RenderOptions};
use crate::platform::{AlphaFormat, IDrawingContextImpl, PixelFormats, SharedBitmapImpl};
use crate::rendering::composition::generated::{ServerDropShadowHooks, ServerDropShadowProps};
use crate::rendering::composition::transport::BatchStreamReader;
use crate::rendering::composition::CompositionDropShadowSourcePolicy;
use crate::{Matrix, PixelSize, Rect, RoundedRect, Vector};
use std::any::{Any, TypeId};
use std::cell::RefCell;
use std::rc::{Rc, Weak};
use std::sync::Arc;
use std::time::Duration;
const COLOR_BITMAP_SIDE: i32 = 4;
pub struct ServerDropShadow {
base: ServerRenderResource,
props: ServerDropShadowProps,
color_bitmap: RefCell<Option<(Color, Arc<SharedBitmapImpl>)>>,
}
impl ServerDropShadow {
pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerDropShadow> {
let shadow = Rc::new_cyclic(|this: &Weak<ServerDropShadow>| ServerDropShadow {
base: ServerRenderResource::new(
compositor,
this.clone() as Weak<dyn IAnimatedServerObject>,
this.clone() as Weak<dyn IServerRenderResource>,
),
props: ServerDropShadowProps::new(),
color_bitmap: RefCell::new(None),
});
shadow.base.object().activate();
shadow
}
pub fn props(&self) -> &ServerDropShadowProps {
&self.props
}
pub fn is_disposed(&self) -> bool {
self.base.is_disposed()
}
pub fn effective_color(&self) -> Color {
let color = self.props.color();
let opacity = self.props.opacity().clamp(0.0, 1.0);
let alpha = (color.a as f32 * opacity).round().clamp(0.0, 255.0) as u8;
Color::from_argb(alpha, color.r, color.g, color.b)
}
pub fn plane_offset(&self, visual: Option<&ServerCompositionVisual>) -> Vector {
let offset = self.props.offset();
let depth = match visual {
Some(visual) => visual.depth_offset(offset.z as f32),
None => Vector::default(),
};
Vector::new(offset.x + depth.x, offset.y + depth.y)
}
pub fn box_shadow_in(&self, visual: Option<&ServerCompositionVisual>) -> BoxShadow {
let offset = self.plane_offset(visual);
BoxShadow {
offset_x: offset.x,
offset_y: offset.y,
blur: self.props.blur_radius().max(0.0) as f64,
spread: 0.0,
color: self.effective_color(),
is_inset: false,
}
}
pub fn box_shadow(&self) -> BoxShadow {
self.box_shadow_in(None)
}
pub fn box_shadows_in(&self, visual: Option<&ServerCompositionVisual>) -> BoxShadows {
let shadow = self.box_shadow_in(visual);
if shadow.color.a == 0 {
return BoxShadows::default();
}
BoxShadows::new(shadow)
}
pub fn box_shadows(&self) -> BoxShadows {
self.box_shadows_in(None)
}
pub fn bounds_in(&self, visual: Option<&ServerCompositionVisual>, rect: Rect) -> Rect {
let shadow = self.box_shadow_in(visual);
rect.union(shadow.transform_bounds(rect))
}
pub fn bounds(&self, rect: Rect) -> Rect {
self.bounds_in(None, rect)
}
pub fn is_visible(&self) -> bool {
self.effective_color().a != 0
}
pub fn inherits_from_visual_content(&self) -> bool {
self.props.source_policy() == CompositionDropShadowSourcePolicy::InheritFromVisualContent
}
pub fn effective_mask(&self) -> Option<Rc<dyn IServerObject>> {
if self.inherits_from_visual_content() {
return None;
}
self.props.mask()
}
pub fn render_under_rect(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
self.render_under_rect_in(canvas, None, rect)
}
pub fn render_with_content(
&self,
canvas: &mut dyn IDrawingContextImpl,
rect: Rect,
paint: impl FnOnce(&mut dyn IDrawingContextImpl),
) -> bool {
if canvas.as_drawing_context_impl_with_effects().is_none() {
return false;
}
let effect = drop_shadow_effect(self);
let bounds = self.bounds(rect);
if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
effects.push_effect(Some(bounds), &effect);
}
paint(&mut *canvas);
if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
effects.pop_effect();
}
true
}
pub fn render_under_rect_in(
&self,
canvas: &mut dyn IDrawingContextImpl,
visual: Option<&ServerCompositionVisual>,
rect: Rect,
) {
if rect.width <= 0.0 || rect.height <= 0.0 || !self.is_visible() {
return;
}
if let Some(mask) = self.effective_mask() {
if self.render_shadow_of(canvas, visual, rect, |canvas| paint_composition_brush(canvas, &mask, rect)) {
return;
}
}
let shadows = self.box_shadows_in(visual);
if shadows.count() > 0 {
canvas.draw_rectangle(None, None, RoundedRect::from_rect(rect), &shadows);
}
}
pub fn render_shadow_of(
&self,
canvas: &mut dyn IDrawingContextImpl,
visual: Option<&ServerCompositionVisual>,
rect: Rect,
paint: impl FnOnce(&mut dyn IDrawingContextImpl),
) -> bool {
if rect.width <= 0.0 || rect.height <= 0.0 {
return true;
}
let color = self.effective_color();
if color.a == 0 {
return true;
}
if canvas.as_drawing_context_impl_with_effects().is_none() {
return false;
}
let Some(bitmap) = self.color_bitmap(color) else { return false };
let offset = self.plane_offset(visual);
let blur = self.props.blur_radius().max(0.0) as f64;
let bounds = self.bounds_in(visual, rect);
let colored = rect.inflate(2.0);
let blurred = blur > 0.0;
if blurred {
if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
effects.push_effect(Some(bounds), &ImmutableBlurEffect::new(blur));
}
}
let transform = canvas.transform();
let moved = offset != Vector::default();
if moved {
canvas.set_transform(Matrix::create_translation(offset.x, offset.y) * transform);
}
canvas.push_layer(colored);
paint(&mut *canvas);
canvas.push_render_options(RenderOptions {
bitmap_blending_mode: BitmapBlendingMode::SourceIn,
..RenderOptions::default()
});
let side = COLOR_BITMAP_SIDE as f64;
canvas.draw_bitmap(&*bitmap, 1.0, Rect::new(1.0, 1.0, side - 2.0, side - 2.0), colored);
canvas.pop_render_options();
canvas.pop_layer();
if moved {
canvas.set_transform(transform);
}
if blurred {
if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
effects.pop_effect();
}
}
true
}
fn color_bitmap(&self, color: Color) -> Option<Arc<SharedBitmapImpl>> {
if let Some((cached, bitmap)) = self.color_bitmap.borrow().as_ref() {
if *cached == color {
return Some(bitmap.clone());
}
}
let compositor = self.base.compositor()?;
let premultiply = |channel: u8| ((channel as u32 * color.a as u32 + 127) / 255) as u8;
let pixel = [premultiply(color.r), premultiply(color.g), premultiply(color.b), color.a];
let pixels: Vec<u8> = pixel.iter().copied().cycle().take((COLOR_BITMAP_SIDE * COLOR_BITMAP_SIDE * 4) as usize).collect();
let size = PixelSize::new(COLOR_BITMAP_SIDE, COLOR_BITMAP_SIDE);
let bitmap = compositor.render_interface().with_platform_render_interface(|render_interface| {
render_interface.load_bitmap_from_pixels(
PixelFormats::RGBA8888,
AlphaFormat::Premul,
&pixels,
size,
Vector::new(96.0, 96.0),
COLOR_BITMAP_SIDE * 4,
)
})?;
*self.color_bitmap.borrow_mut() = Some((color, bitmap.clone()));
Some(bitmap)
}
}
impl_server_render_resource!(
ServerDropShadow,
base,
composition_property: ServerDropShadowProps::get_composition_property
);
impl ServerDropShadowHooks for ServerDropShadow {}
impl IServerObject for ServerDropShadow {
fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
self.props.deserialize_changes_core(self, reader, committed_at);
}
fn values_invalidated(&self) {
self.base.core().invalidated(self);
}
fn dispose(&self) {
self.color_bitmap.borrow_mut().take();
self.base.core().dispose();
}
fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
Some(self)
}
fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
Some(self)
}
fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
self.props.find_props(type_id)
}
fn as_any(&self) -> &dyn Any {
self
}
fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}