ferroui_base/rendering/composition/brushes/
server_composition_surface_brush.rs1use crate::media::imaging::BitmapInterpolationMode;
5use crate::media::{BoxShadows, RenderOptions};
6use crate::platform::IDrawingContextImpl;
7use crate::rendering::composition::generated::{
8 ServerCompositionBrushHooks, ServerCompositionBrushProps, ServerCompositionSurfaceBrushHooks,
9 ServerCompositionSurfaceBrushProps,
10};
11use crate::rendering::composition::server::{
12 composition_transform, impl_server_render_resource, IAnimatedServerObject, IServerCompositionSurface,
13 IServerObject, IServerRenderResource, ServerCompositor, ServerRenderResource,
14};
15use crate::rendering::composition::transport::BatchStreamReader;
16use crate::rendering::composition::CompositionStretch;
17use crate::{Matrix, Rect, RoundedRect, Size, Vector};
18use std::any::{Any, TypeId};
19use std::rc::{Rc, Weak};
20use std::time::Duration;
21
22pub struct ServerCompositionSurfaceBrush {
29 this: Weak<ServerCompositionSurfaceBrush>,
30 base: ServerRenderResource,
31 props: ServerCompositionSurfaceBrushProps,
32}
33
34impl ServerCompositionSurfaceBrush {
35 pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerCompositionSurfaceBrush> {
38 let brush = Rc::new_cyclic(|this: &Weak<ServerCompositionSurfaceBrush>| ServerCompositionSurfaceBrush {
39 this: this.clone(),
40 base: ServerRenderResource::new(
41 compositor,
42 this.clone() as Weak<dyn IAnimatedServerObject>,
43 this.clone() as Weak<dyn IServerRenderResource>,
44 ),
45 props: ServerCompositionSurfaceBrushProps::new(),
46 });
47 brush.base.object().activate();
48 brush
49 }
50
51 pub fn props(&self) -> &ServerCompositionSurfaceBrushProps {
53 &self.props
54 }
55
56 pub fn brush_props(&self) -> &ServerCompositionBrushProps {
58 self.props.base()
59 }
60
61 pub fn is_disposed(&self) -> bool {
62 self.base.is_disposed()
63 }
64
65 pub fn surface(&self) -> Option<Rc<dyn IServerCompositionSurface>> {
67 self.props.surface().and_then(|surface| surface.as_surface())
68 }
69
70 fn subscription_key(&self) -> usize {
71 self.this.as_ptr() as *const () as usize
72 }
73
74 fn subscribe(&self) {
78 if let Some(surface) = self.surface() {
79 let weak = self.this.clone();
80 surface.changed().add(
81 self.subscription_key(),
82 Rc::new(move || {
83 if let Some(brush) = weak.upgrade() {
84 brush.base.core().invalidated(&*brush);
85 }
86 }),
87 );
88 }
89 }
90
91 fn unsubscribe(&self) {
92 if let Some(surface) = self.surface() {
93 surface.changed().remove(self.subscription_key());
94 }
95 }
96
97 pub fn content_transform(&self, size: Vector) -> Option<Matrix> {
101 composition_transform(
102 size,
103 self.props.anchor_point(),
104 self.props.center_point(),
105 self.props.transform_matrix(),
106 self.props.scale(),
107 self.props.rotation_angle(),
108 self.props.offset(),
109 )
110 }
111
112 pub fn paint(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
120 let Some(surface) = self.surface() else { return };
121 let Some(natural_size) = surface.natural_size() else { return };
122 let Some(mut content) = surface_brush_content_rect(
123 natural_size,
124 rect,
125 self.props.stretch(),
126 self.props.horizontal_alignment_ratio() as f64,
127 self.props.vertical_alignment_ratio() as f64,
128 ) else {
129 return;
130 };
131 let opacity = self.brush_props().opacity();
132 if opacity <= 0.0 {
133 return;
134 }
135
136 canvas.push_clip(rect);
137
138 let interpolation = self.props.bitmap_interpolation_mode().to_bitmap_interpolation_mode();
142 let push_options = interpolation != BitmapInterpolationMode::LowQuality;
143 if push_options {
144 canvas.push_render_options(RenderOptions {
145 bitmap_interpolation_mode: interpolation,
146 ..RenderOptions::default()
147 });
148 }
149
150 let saved = canvas.transform();
151 let mut world = saved;
152 if let Some(transform) = self.content_transform(Vector::new(rect.width, rect.height)) {
153 let local = Matrix::create_translation(-rect.x, -rect.y)
156 * transform
157 * Matrix::create_translation(rect.x, rect.y);
158 world = local * saved;
159 }
160 if self.props.snap_to_pixels() {
161 let corner = content.top_left().transform(world);
163 let (dx, dy) = (corner.x.round() - corner.x, corner.y.round() - corner.y);
164 if dx != 0.0 || dy != 0.0 {
165 world = world * Matrix::create_translation(dx, dy);
166 }
167 content = Rect::new(content.x, content.y, content.width.round(), content.height.round());
168 }
169 let transformed = world != saved;
170 if transformed {
171 canvas.set_transform(world);
172 }
173 surface.draw(canvas, content, opacity);
174 if transformed {
175 canvas.set_transform(saved);
176 }
177
178 if push_options {
179 canvas.pop_render_options();
180 }
181 canvas.pop_clip();
182 }
183}
184
185impl ServerCompositionSurfaceBrush {
186 pub fn paint_tiled(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
191 let Some(surface) = self.surface() else { return };
192 let Some(natural_size) = surface.natural_size() else { return };
193 let scale = self.props.scale();
194 let (width, height) = (natural_size.width * scale.x.abs(), natural_size.height * scale.y.abs());
195 let opacity = self.brush_props().opacity();
196 if width < 1.0 || height < 1.0 || opacity <= 0.0 || rect.width <= 0.0 || rect.height <= 0.0 {
197 return;
198 }
199 let (columns, rows) = ((rect.width / width).ceil(), (rect.height / height).ceil());
200 if columns * rows > 4096.0 {
201 return;
202 }
203 canvas.push_clip(rect);
204 for row in 0..rows as usize {
205 for column in 0..columns as usize {
206 let tile = Rect::new(rect.x + column as f64 * width, rect.y + row as f64 * height, width, height);
207 surface.draw(canvas, tile, opacity);
208 }
209 }
210 canvas.pop_clip();
211 }
212}
213
214pub fn surface_brush_content_rect(
223 natural_size: Size,
224 area: Rect,
225 stretch: CompositionStretch,
226 horizontal_ratio: f64,
227 vertical_ratio: f64,
228) -> Option<Rect> {
229 if natural_size.width <= 0.0 || natural_size.height <= 0.0 || area.width <= 0.0 || area.height <= 0.0 {
230 return None;
231 }
232 let (width, height) = match stretch {
233 CompositionStretch::None => (natural_size.width, natural_size.height),
234 CompositionStretch::Fill => (area.width, area.height),
235 CompositionStretch::Uniform => {
236 let scale = (area.width / natural_size.width).min(area.height / natural_size.height);
237 (natural_size.width * scale, natural_size.height * scale)
238 }
239 CompositionStretch::UniformToFill => {
240 let scale = (area.width / natural_size.width).max(area.height / natural_size.height);
241 (natural_size.width * scale, natural_size.height * scale)
242 }
243 };
244 Some(Rect::new(
245 area.x + (area.width - width) * horizontal_ratio,
246 area.y + (area.height - height) * vertical_ratio,
247 width,
248 height,
249 ))
250}
251
252pub fn paint_composition_brush(canvas: &mut dyn IDrawingContextImpl, brush: &Rc<dyn IServerObject>, rect: Rect) {
259 if let Some(surface_brush) = brush.as_any().downcast_ref::<ServerCompositionSurfaceBrush>() {
260 surface_brush.paint(canvas, rect);
261 } else if let Some(nine_grid) = brush.as_any().downcast_ref::<super::ServerCompositionNineGridBrush>() {
262 nine_grid.paint(canvas, rect);
263 } else if let Some(mask) = brush.as_any().downcast_ref::<super::ServerCompositionMaskBrush>() {
264 mask.paint(canvas, rect);
265 } else if let Some(effect) = brush.as_any().downcast_ref::<super::ServerCompositionEffectBrush>() {
266 effect.paint(canvas, rect);
267 } else if let Some(backdrop) = brush.as_any().downcast_ref::<super::ServerCompositionBackdropBrush>() {
268 backdrop.paint(canvas, rect);
269 } else if let Some(host_backdrop) = brush.as_any().downcast_ref::<super::ServerCompositionHostBackdropBrush>() {
270 host_backdrop.paint(canvas, rect);
271 } else if let Some(xaml) = brush.as_any().downcast_ref::<super::ServerXamlCompositionBrush>() {
272 xaml.paint(canvas, rect);
273 } else if let Some(platform_brush) = brush.clone().as_brush() {
274 canvas.draw_rectangle(Some(&*platform_brush), None, RoundedRect::from_rect(rect), &BoxShadows::default());
275 }
276}
277
278impl_server_render_resource!(
279 ServerCompositionSurfaceBrush,
280 base,
281 composition_property: ServerCompositionSurfaceBrushProps::get_composition_property
282);
283
284impl ServerCompositionBrushHooks for ServerCompositionSurfaceBrush {}
285
286impl ServerCompositionSurfaceBrushHooks for ServerCompositionSurfaceBrush {
287 fn on_surface_changing(&self) {
288 self.unsubscribe();
289 }
290
291 fn on_surface_changed(&self) {
292 self.subscribe();
293 }
294}
295
296impl IServerObject for ServerCompositionSurfaceBrush {
297 fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
298 self.props.deserialize_changes_core(self, reader, committed_at);
299 }
300
301 fn values_invalidated(&self) {
302 self.base.core().invalidated(self);
303 }
304
305 fn dispose(&self) {
306 self.unsubscribe();
307 self.base.core().dispose();
308 }
309
310 fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
311 Some(self)
312 }
313
314 fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
315 Some(self)
316 }
317
318 fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
319 self.props.find_props(type_id)
320 }
321
322 fn as_any(&self) -> &dyn Any {
323 self
324 }
325
326 fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
327 self
328 }
329}