ferroui_base/rendering/composition/brushes/
server_composition_nine_grid_brush.rs1use super::{paint_composition_brush, ServerCompositionSurfaceBrush};
5use crate::media::imaging::BitmapBlendingMode;
6use crate::media::{BoxShadows, RenderOptions};
7use crate::platform::IDrawingContextImpl;
8use crate::rendering::composition::generated::{
9 ServerCompositionBrushHooks, ServerCompositionBrushProps, ServerCompositionMaskBrushHooks,
10 ServerCompositionMaskBrushProps, ServerCompositionNineGridBrushHooks, ServerCompositionNineGridBrushProps,
11};
12use crate::rendering::composition::server::{
13 impl_server_render_resource, IAnimatedServerObject, IServerObject, IServerRenderResource, ServerCompositor,
14 ServerRenderResource,
15};
16use crate::rendering::composition::transport::BatchStreamReader;
17use crate::{Rect, RoundedRect};
18use std::any::{Any, TypeId};
19use std::cell::Cell;
20use std::rc::{Rc, Weak};
21use std::time::Duration;
22
23pub type NineGridEdges = [f64; 4];
25
26pub const NINE_GRID_CENTER: usize = 4;
29
30fn nine_grid_lines(start: f64, length: f64, first: f64, last: f64) -> [f64; 4] {
34 let (mut first, mut last) = (first.max(0.0), last.max(0.0));
35 let length = length.max(0.0);
36 if first + last > length && first + last > 0.0 {
37 let scale = length / (first + last);
38 first *= scale;
39 last *= scale;
40 }
41 [start, start + first, start + length - last, start + length]
42}
43
44pub fn nine_grid_rects(source: Rect, dest: Rect, insets: NineGridEdges, scales: NineGridEdges) -> [(Rect, Rect); 9] {
50 let [left, top, right, bottom] = insets;
51 let [left_scale, top_scale, right_scale, bottom_scale] = scales;
52 let source_x = nine_grid_lines(source.x, source.width, left, right);
53 let source_y = nine_grid_lines(source.y, source.height, top, bottom);
54 let dest_x = nine_grid_lines(dest.x, dest.width, left * left_scale, right * right_scale);
55 let dest_y = nine_grid_lines(dest.y, dest.height, top * top_scale, bottom * bottom_scale);
56 let cell = |x: &[f64; 4], y: &[f64; 4], column: usize, row: usize| {
57 Rect::new(x[column], y[row], x[column + 1] - x[column], y[row + 1] - y[row])
58 };
59 std::array::from_fn(|index| {
60 let (row, column) = (index / 3, index % 3);
61 (cell(&source_x, &source_y, column, row), cell(&dest_x, &dest_y, column, row))
62 })
63}
64
65fn is_empty(rect: &Rect) -> bool {
66 rect.width <= 0.0 || rect.height <= 0.0
67}
68
69pub struct ServerCompositionNineGridBrush {
73 base: ServerRenderResource,
74 props: ServerCompositionNineGridBrushProps,
75 painting: Cell<bool>,
78}
79
80impl ServerCompositionNineGridBrush {
81 pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerCompositionNineGridBrush> {
84 let brush = Rc::new_cyclic(|this: &Weak<ServerCompositionNineGridBrush>| ServerCompositionNineGridBrush {
85 base: ServerRenderResource::new(
86 compositor,
87 this.clone() as Weak<dyn IAnimatedServerObject>,
88 this.clone() as Weak<dyn IServerRenderResource>,
89 ),
90 props: ServerCompositionNineGridBrushProps::new(),
91 painting: Cell::new(false),
92 });
93 brush.base.object().activate();
94 brush
95 }
96
97 pub fn props(&self) -> &ServerCompositionNineGridBrushProps {
99 &self.props
100 }
101
102 pub fn brush_props(&self) -> &ServerCompositionBrushProps {
104 self.props.base()
105 }
106
107 pub fn is_disposed(&self) -> bool {
108 self.base.is_disposed()
109 }
110
111 pub fn insets(&self) -> NineGridEdges {
113 [
114 self.props.left_inset() as f64,
115 self.props.top_inset() as f64,
116 self.props.right_inset() as f64,
117 self.props.bottom_inset() as f64,
118 ]
119 }
120
121 pub fn inset_scales(&self) -> NineGridEdges {
123 [
124 self.props.left_inset_scale() as f64,
125 self.props.top_inset_scale() as f64,
126 self.props.right_inset_scale() as f64,
127 self.props.bottom_inset_scale() as f64,
128 ]
129 }
130
131 pub fn paint(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
138 let Some(source) = self.props.source() else { return };
139 let opacity = self.brush_props().opacity();
140 if opacity <= 0.0 || is_empty(&rect) || self.painting.replace(true) {
141 return;
142 }
143 if opacity != 1.0 {
144 canvas.push_opacity(opacity, Some(rect));
145 }
146 let hollow = self.props.is_center_hollow();
147 let insets = self.insets();
148 let scales = self.inset_scales();
149
150 if let Some(surface_brush) = source.as_any().downcast_ref::<ServerCompositionSurfaceBrush>() {
151 let surface = surface_brush.surface();
152 let natural_size = surface.as_ref().and_then(|surface| surface.natural_size());
153 let surface_opacity = surface_brush.brush_props().opacity();
154 if let (Some(surface), Some(natural_size)) = (surface, natural_size) {
155 let natural = Rect::new(0.0, 0.0, natural_size.width, natural_size.height);
156 for (index, (from, to)) in nine_grid_rects(natural, rect, insets, scales).iter().enumerate() {
157 if (hollow && index == NINE_GRID_CENTER) || is_empty(from) || is_empty(to) {
158 continue;
159 }
160 let scale_x = to.width / from.width;
163 let scale_y = to.height / from.height;
164 let whole = Rect::new(
165 to.x - from.x * scale_x,
166 to.y - from.y * scale_y,
167 natural.width * scale_x,
168 natural.height * scale_y,
169 );
170 canvas.push_clip(*to);
171 surface.draw(canvas, whole, surface_opacity);
172 canvas.pop_clip();
173 }
174 }
175 } else {
176 let solid = source.clone().as_brush().filter(|brush| brush.as_solid_color_brush().is_some());
177 let cells = nine_grid_rects(rect, rect, insets, scales);
178 if let Some(solid) = solid {
179 for (index, (_, to)) in cells.iter().enumerate() {
180 if (hollow && index == NINE_GRID_CENTER) || is_empty(to) {
181 continue;
182 }
183 canvas.draw_rectangle(Some(&*solid), None, RoundedRect::from_rect(*to), &BoxShadows::default());
184 }
185 } else if !hollow {
186 paint_composition_brush(canvas, &source, rect);
187 } else {
188 for (index, (_, to)) in cells.iter().enumerate() {
189 if index == NINE_GRID_CENTER || is_empty(to) {
190 continue;
191 }
192 canvas.push_clip(*to);
193 paint_composition_brush(canvas, &source, rect);
194 canvas.pop_clip();
195 }
196 }
197 }
198
199 if opacity != 1.0 {
200 canvas.pop_opacity();
201 }
202 self.painting.set(false);
203 }
204}
205
206impl_server_render_resource!(
207 ServerCompositionNineGridBrush,
208 base,
209 composition_property: ServerCompositionNineGridBrushProps::get_composition_property
210);
211
212impl ServerCompositionBrushHooks for ServerCompositionNineGridBrush {}
213
214impl ServerCompositionNineGridBrushHooks for ServerCompositionNineGridBrush {}
215
216impl IServerObject for ServerCompositionNineGridBrush {
217 fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
218 self.props.deserialize_changes_core(self, reader, committed_at);
219 }
220
221 fn values_invalidated(&self) {
222 self.base.core().invalidated(self);
223 }
224
225 fn dispose(&self) {
226 self.base.core().dispose();
227 }
228
229 fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
230 Some(self)
231 }
232
233 fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
234 Some(self)
235 }
236
237 fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
238 self.props.find_props(type_id)
239 }
240
241 fn as_any(&self) -> &dyn Any {
242 self
243 }
244
245 fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
246 self
247 }
248}
249
250pub struct ServerCompositionMaskBrush {
254 base: ServerRenderResource,
255 props: ServerCompositionMaskBrushProps,
256 painting: Cell<bool>,
259}
260
261impl ServerCompositionMaskBrush {
262 pub fn new(compositor: &Rc<ServerCompositor>) -> Rc<ServerCompositionMaskBrush> {
265 let brush = Rc::new_cyclic(|this: &Weak<ServerCompositionMaskBrush>| ServerCompositionMaskBrush {
266 base: ServerRenderResource::new(
267 compositor,
268 this.clone() as Weak<dyn IAnimatedServerObject>,
269 this.clone() as Weak<dyn IServerRenderResource>,
270 ),
271 props: ServerCompositionMaskBrushProps::new(),
272 painting: Cell::new(false),
273 });
274 brush.base.object().activate();
275 brush
276 }
277
278 pub fn props(&self) -> &ServerCompositionMaskBrushProps {
280 &self.props
281 }
282
283 pub fn brush_props(&self) -> &ServerCompositionBrushProps {
285 self.props.base()
286 }
287
288 pub fn is_disposed(&self) -> bool {
289 self.base.is_disposed()
290 }
291
292 pub fn paint(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
297 let Some(source) = self.props.source() else { return };
298 let opacity = self.brush_props().opacity();
299 if opacity <= 0.0 || is_empty(&rect) || self.painting.replace(true) {
300 return;
301 }
302 if opacity != 1.0 {
303 canvas.push_opacity(opacity, Some(rect));
304 }
305 match self.props.mask() {
306 None => paint_composition_brush(canvas, &source, rect),
307 Some(mask) => match mask.clone().as_brush() {
308 Some(mask_brush) => {
309 canvas.push_opacity_mask(&*mask_brush, rect);
310 paint_composition_brush(canvas, &source, rect);
311 canvas.pop_opacity_mask();
312 }
313 None => {
314 canvas.push_layer(rect);
317 paint_composition_brush(canvas, &source, rect);
318 canvas.push_render_options(RenderOptions {
319 bitmap_blending_mode: BitmapBlendingMode::DestinationIn,
320 ..RenderOptions::default()
321 });
322 paint_composition_brush(canvas, &mask, rect);
323 canvas.pop_render_options();
324 canvas.pop_layer();
325 }
326 },
327 }
328 if opacity != 1.0 {
329 canvas.pop_opacity();
330 }
331 self.painting.set(false);
332 }
333}
334
335impl_server_render_resource!(
336 ServerCompositionMaskBrush,
337 base,
338 composition_property: ServerCompositionMaskBrushProps::get_composition_property
339);
340
341impl ServerCompositionBrushHooks for ServerCompositionMaskBrush {}
342
343impl ServerCompositionMaskBrushHooks for ServerCompositionMaskBrush {}
344
345impl IServerObject for ServerCompositionMaskBrush {
346 fn deserialize_changes_core(&self, reader: &mut BatchStreamReader<'_>, committed_at: Duration) {
347 self.props.deserialize_changes_core(self, reader, committed_at);
348 }
349
350 fn values_invalidated(&self) {
351 self.base.core().invalidated(self);
352 }
353
354 fn dispose(&self) {
355 self.base.core().dispose();
356 }
357
358 fn as_render_resource(self: Rc<Self>) -> Option<Rc<dyn IServerRenderResource>> {
359 Some(self)
360 }
361
362 fn as_animated(self: Rc<Self>) -> Option<Rc<dyn IAnimatedServerObject>> {
363 Some(self)
364 }
365
366 fn get_props(&self, type_id: TypeId) -> Option<&dyn Any> {
367 self.props.find_props(type_id)
368 }
369
370 fn as_any(&self) -> &dyn Any {
371 self
372 }
373
374 fn into_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
375 self
376 }
377}