ferroui_base/rendering/composition/server/
server_layer_visual.rs1use super::{
2 CompositionProperty, IServerObject, IServerRenderResourceObserver, IServerVisualContent, ServerCompositionEffectBrush,
3 ServerCompositionVisual, ServerDropShadow, ServerSizeDependantVisual, ServerVisualRenderContext,
4};
5use crate::media::{Color, EffectExtensions, IEffect, ImmutableDropShadowEffect};
6use crate::platform::{IDrawingContextImpl, LtrbRect};
7use crate::rendering::composition::generated::{
8 ServerCompositionContainerVisualProps, ServerLayerVisualHooks, ServerLayerVisualProps,
9};
10use crate::rendering::composition::transport::BatchStreamReader;
11use crate::{Rect, Thickness};
12use std::any::{Any, TypeId};
13use std::rc::Rc;
14use std::time::Duration;
15
16pub(crate) fn observe_visual_resource(
20 visual: &ServerCompositionVisual,
21 resource: Option<Rc<dyn IServerObject>>,
22 observe: bool,
23) {
24 let Some(resource) = resource.and_then(|resource| resource.as_render_resource()) else { return };
25 let Some(this) = visual.weak().upgrade() else { return };
26 let observer: Rc<dyn IServerRenderResourceObserver> = this;
27 if observe {
28 resource.add_observer(&observer);
29 } else {
30 resource.remove_observer(&observer);
31 }
32}
33
34pub fn drop_shadow_effect(shadow: &ServerDropShadow) -> ImmutableDropShadowEffect {
39 drop_shadow_effect_in(shadow, None)
40}
41
42pub fn drop_shadow_effect_in(shadow: &ServerDropShadow, visual: Option<&ServerCompositionVisual>) -> ImmutableDropShadowEffect {
46 let props = shadow.props();
47 let offset = shadow.plane_offset(visual);
48 let color: Color = props.color();
49 ImmutableDropShadowEffect::new(
50 offset.x,
51 offset.y,
52 props.blur_radius().max(0.0) as f64,
53 color,
54 props.opacity().clamp(0.0, 1.0) as f64,
55 )
56}
57
58enum LayerMode {
60 Shadow(ImmutableDropShadowEffect),
63 Layer,
65}
66
67#[derive(Default)]
89pub struct ServerLayerVisual {
90 props: ServerLayerVisualProps,
91}
92
93impl ServerLayerVisual {
94 pub fn new() -> Self {
95 Self::default()
96 }
97
98 pub fn props(&self) -> &ServerLayerVisualProps {
100 &self.props
101 }
102
103 pub fn shadow(&self) -> Option<Rc<ServerDropShadow>> {
105 self.props.shadow().and_then(|shadow| shadow.into_any_rc().downcast::<ServerDropShadow>().ok())
106 }
107
108 pub fn effect_brush(&self) -> Option<Rc<ServerCompositionEffectBrush>> {
111 self.props
112 .effect_brush()
113 .and_then(|brush| brush.into_any_rc().downcast::<ServerCompositionEffectBrush>().ok())
114 }
115
116 fn visible_shadow(&self) -> Option<Rc<ServerDropShadow>> {
118 self.shadow().filter(|shadow| shadow.is_visible())
119 }
120
121 fn rect(visual: &ServerCompositionVisual) -> Rect {
122 let size = visual.size();
123 Rect::new(0.0, 0.0, size.x, size.y)
124 }
125
126 fn layer_mode(&self, visual: &ServerCompositionVisual, canvas: &mut dyn IDrawingContextImpl) -> Option<(LayerMode, Rect)> {
130 let bounds = visual.sub_tree_bounds()?.to_rect();
131 let mode = match self.visible_shadow() {
134 Some(shadow)
135 if shadow.effective_mask().is_none() && canvas.as_drawing_context_impl_with_effects().is_some() =>
136 {
137 LayerMode::Shadow(drop_shadow_effect_in(&shadow, Some(visual)))
138 }
139 _ => LayerMode::Layer,
140 };
141 Some((mode, bounds))
142 }
143}
144
145impl ServerLayerVisualHooks for ServerCompositionVisual {
146 fn base_deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
147 ServerCompositionVisual::base_deserialize_changes_core(self, reader, committed_at);
148 }
149
150 fn on_shadow_changing(&self) {
151 if let Some(content) = self.content_as::<ServerLayerVisual>() {
152 observe_visual_resource(self, content.props.shadow(), false);
153 }
154 }
155
156 fn on_shadow_changed(&self) {
157 if let Some(content) = self.content_as::<ServerLayerVisual>() {
158 observe_visual_resource(self, content.props.shadow(), true);
159 }
160 self.invalidate_content();
161 }
162
163 fn on_effect_brush_changing(&self) {
164 if let Some(content) = self.content_as::<ServerLayerVisual>() {
165 observe_visual_resource(self, content.props.effect_brush(), false);
166 }
167 }
168
169 fn on_effect_brush_changed(&self) {
170 if let Some(content) = self.content_as::<ServerLayerVisual>() {
171 observe_visual_resource(self, content.props.effect_brush(), true);
172 }
173 self.invalidate_content();
174 }
175}
176
177impl IServerVisualContent for ServerLayerVisual {
178 fn deserialize_changes_core(
179 &self,
180 visual: &ServerCompositionVisual,
181 reader: &mut BatchStreamReader<'_>,
182 committed_at: Duration,
183 ) {
184 self.props.deserialize_changes_core(visual, reader, committed_at);
185 }
186
187 fn compute_own_content_bounds(&self, visual: &ServerCompositionVisual) -> Option<LtrbRect> {
190 let shadow = self.visible_shadow()?;
191 let bounds = ServerSizeDependantVisual::compute_own_content_bounds(visual)?;
192 Some(LtrbRect::from_rect(shadow.bounds_in(Some(visual), bounds.to_rect())))
193 }
194
195 fn size_changed(&self, visual: &ServerCompositionVisual) {
196 ServerSizeDependantVisual::size_changed(visual);
197 }
198
199 fn sub_tree_bounds_padding(&self, visual: &ServerCompositionVisual) -> Option<Thickness> {
203 let shadow = self.visible_shadow().map(|shadow| {
204 let effect = drop_shadow_effect_in(&shadow, Some(visual));
205 EffectExtensions::get_effect_output_padding(Some(&effect as &dyn IEffect))
206 });
207 let effect = self.effect_brush().map(|effect| effect.layer_padding());
208 match (shadow, effect) {
209 (None, None) => None,
210 (shadow, effect) => Some(shadow.unwrap_or_default() + effect.unwrap_or_default()),
211 }
212 }
213
214 fn has_effect(&self, visual: &ServerCompositionVisual) -> bool {
217 visual.effect().is_some() || self.visible_shadow().is_some() || self.effect_brush().is_some()
218 }
219
220 fn render_core(
221 &self,
222 visual: &ServerCompositionVisual,
223 context: &mut ServerVisualRenderContext<'_>,
224 _current_transformed_clip: LtrbRect,
225 ) {
226 let canvas = context.canvas();
227 let Some((mode, bounds)) = self.layer_mode(visual, canvas) else { return };
228 match mode {
229 LayerMode::Shadow(effect) => {
230 if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
231 effects.push_effect(Some(bounds), &effect);
232 }
233 }
234 LayerMode::Layer => {
235 if let Some(shadow) = self.visible_shadow() {
236 shadow.render_under_rect_in(canvas, Some(visual), Self::rect(visual));
237 }
238 canvas.push_layer(bounds);
239 }
240 }
241 if let Some(effect) = self.effect_brush() {
244 effect.push_for_layer(canvas, bounds);
245 }
246 }
247
248 fn render_after_children(&self, visual: &ServerCompositionVisual, context: &mut ServerVisualRenderContext<'_>) {
249 let canvas = context.canvas();
250 let Some((mode, bounds)) = self.layer_mode(visual, canvas) else { return };
251 if let Some(effect) = self.effect_brush() {
252 effect.pop_for_layer(canvas, bounds);
253 }
254 match mode {
255 LayerMode::Shadow(_) => {
256 if let Some(effects) = canvas.as_drawing_context_impl_with_effects() {
257 effects.pop_effect();
258 }
259 }
260 LayerMode::Layer => canvas.pop_layer(),
261 }
262 }
263
264 fn dependency_queued_invalidate(&self, visual: &ServerCompositionVisual) {
266 visual.invalidate_content();
267 }
268
269 fn dispose(&self, visual: &ServerCompositionVisual) {
270 observe_visual_resource(visual, self.props.shadow(), false);
271 observe_visual_resource(visual, self.props.effect_brush(), false);
272 }
273
274 fn find_props(&self, type_id: TypeId) -> Option<&dyn Any> {
275 self.props.find_props(type_id)
276 }
277
278 fn get_composition_property(&self, name: &str) -> Option<&'static CompositionProperty> {
279 ServerCompositionContainerVisualProps::get_composition_property(name)
280 }
281
282 fn as_any(&self) -> &dyn Any {
283 self
284 }
285}