1use crate::animation::Animatable;
2use crate::media::immutable::ImmutableSolidColorBrush;
3use crate::media::ref_adapter::RefAdapter;
4use crate::media::{Color, IBrush, ITransform, KnownColors};
5use crate::reactive::{Disposable, IDisposable};
6use crate::rendering::composition::drawing::{
7 transform_get_server, CompositorResourceHolder, ICompositionRenderResource,
8};
9use crate::rendering::composition::generated::ServerCompositionSimpleBrushProps;
10use crate::rendering::composition::server::{IServerObject, ServerCompositor, ServerObjectId};
11use crate::rendering::composition::transport::BatchStreamWriter;
12use crate::rendering::composition::{Compositor, ICompositorSerializable};
13use crate::utilities::{FormatError, HandlerList};
14use crate::{
15 ferro_class, ferro_property, FerroObject, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
16 FerroPropertyChangedEventArgs, RelativePoint, StyledProperty, Upcast,
17};
18use std::rc::Rc;
19
20#[repr(C)]
23pub struct Brush {
24 base: Animatable,
25 invalidated: HandlerList<dyn Fn()>,
26 resource: CompositorResourceHolder,
27}
28
29pub type ServerBrushFactory = fn(&Rc<ServerCompositor>) -> Rc<dyn IServerObject>;
31
32ferro_class! {
33 Brush: Animatable, virtuals BrushImpl: FerroObjectImpl {
34 fn factory(this) -> Option<ServerBrushFactory>;
38
39 fn on_referenced_from_compositor(this, c: &Rc<Compositor>);
41
42 fn on_unreferenced_from_compositor(this, c: &Rc<Compositor>);
44
45 fn prepare_serialization(this, c: &Rc<Compositor>);
49
50 fn serialize_changes(this, c: &Compositor, writer: &mut BatchStreamWriter<'_>);
53 }
54}
55crate::ferro_class_info!(Brush { interfaces: [std::rc::Rc<dyn crate::media::IBrush>] });
56
57impl FerroObjectImpl for Brush {
58 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
59 if change.property() == Self::transform_property().as_property()
60 || change.property() == Self::relative_transform_property().as_property()
61 {
62 let (old_value, new_value) = change.get_old_and_new_value::<Option<Rc<dyn ITransform>>>();
63 this.resource.process_property_change_notification(
64 old_value.as_ref().and_then(|t| t.as_composition_render_resource()),
65 new_value.as_ref().and_then(|t| t.as_composition_render_resource()),
66 );
67 }
68
69 this.register_for_serialization();
70 this.raise_invalidated();
71
72 Self::parent_on_property_changed(this, change);
73 }
74}
75
76impl BrushImpl for Brush {
77 fn factory(_this: &Self) -> Option<ServerBrushFactory> {
78 None
79 }
80
81 fn on_referenced_from_compositor(this: &Self, c: &Rc<Compositor>) {
82 if let Some(transform) = this.transform() {
83 if let Some(transform) = transform.as_composition_render_resource() {
84 transform.add_ref_on_compositor(c);
85 }
86 }
87 if let Some(relative_transform) = this.relative_transform() {
88 if let Some(relative_transform) = relative_transform.as_composition_render_resource() {
89 relative_transform.add_ref_on_compositor(c);
90 }
91 }
92 }
93
94 fn on_unreferenced_from_compositor(this: &Self, c: &Rc<Compositor>) {
95 if let Some(transform) = this.transform() {
96 if let Some(transform) = transform.as_composition_render_resource() {
97 transform.release_on_compositor(c);
98 }
99 }
100 if let Some(relative_transform) = this.relative_transform() {
101 if let Some(relative_transform) = relative_transform.as_composition_render_resource() {
102 relative_transform.release_on_compositor(c);
103 }
104 }
105 }
106
107 fn prepare_serialization(_this: &Self, _c: &Rc<Compositor>) {}
108
109 fn serialize_changes(this: &Self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
110 ServerCompositionSimpleBrushProps::serialize_all_changes(
111 writer,
112 this.opacity(),
113 this.transform_origin(),
114 transform_get_server(this.transform().as_ref(), Some(c)),
115 transform_get_server(this.relative_transform().as_ref(), Some(c)),
116 );
117 }
118}
119
120impl ICompositionRenderResource for Brush {
121 fn add_ref_on_compositor(&self, c: &Rc<Compositor>) {
122 let Some(factory) = self.factory() else {
123 panic!("{} is not compatible with composition", self.get_type().name());
124 };
125 let owner = self.as_compositor_serializable();
126 let (_, created) = self
127 .resource
128 .create_or_add_ref(c, Some(owner), |c| c.create_server_object(move |server, _| factory(server)));
129 if created {
130 self.on_referenced_from_compositor(c);
131 }
132 }
133
134 fn release_on_compositor(&self, c: &Rc<Compositor>) {
135 if self.resource.release(c) {
136 self.on_unreferenced_from_compositor(c);
137 }
138 }
139
140 fn get_for_compositor(&self, c: &Compositor) -> ServerObjectId {
141 self.resource.get_for_compositor(c)
142 }
143}
144
145impl ICompositorSerializable for RefAdapter<Brush> {
146 fn try_get_server(&self, c: &Compositor) -> Option<ServerObjectId> {
147 self.0.resource.try_get_for_compositor(c)
148 }
149
150 fn serialization_key(&self) -> *const () {
151 RefAdapter::reference_id(self)
152 }
153
154 fn prepare_serialization(&self, c: &Compositor) {
155 self.0.prepare_serialization(&c.this_handle());
156 }
157
158 fn serialize_changes(&self, c: &Compositor, writer: &mut BatchStreamWriter<'_>) {
159 self.0.serialize_changes(c, writer);
160 }
161}
162
163crate::ferro_properties! { impl Brush {
164 ferro_property!(pub fn opacity_property() -> StyledProperty<f64> {
165 FerroProperty::register::<Brush, _>("Opacity", 1.0)
166 });
167
168 ferro_property!(pub fn transform_property() -> StyledProperty<Option<Rc<dyn ITransform>>> {
169 FerroProperty::register::<Brush, _>("Transform", None)
170 });
171
172 ferro_property!(pub fn transform_origin_property() -> StyledProperty<RelativePoint> {
173 FerroProperty::register::<Brush, _>("TransformOrigin", RelativePoint::default())
174 });
175
176 ferro_property!(pub fn relative_transform_property() -> StyledProperty<Option<Rc<dyn ITransform>>> {
177 FerroProperty::register::<Brush, _>("RelativeTransform", None)
178 });
179} }
180
181impl Brush {
182 pub fn construct() -> Self {
184 Self { base: Animatable::construct(), invalidated: HandlerList::new(), resource: CompositorResourceHolder::new() }
185 }
186
187 pub fn opacity(&self) -> f64 {
189 self.get_value(Self::opacity_property())
190 }
191
192 pub fn set_opacity(&self, value: f64) {
193 self.set_value(Self::opacity_property(), value)
194 }
195
196 pub fn transform(&self) -> Option<Rc<dyn ITransform>> {
198 self.get_value(Self::transform_property())
199 }
200
201 pub fn set_transform(&self, value: Option<Rc<dyn ITransform>>) {
202 self.set_value(Self::transform_property(), value)
203 }
204
205 pub fn transform_origin(&self) -> RelativePoint {
207 self.get_value(Self::transform_origin_property())
208 }
209
210 pub fn set_transform_origin(&self, value: RelativePoint) {
211 self.set_value(Self::transform_origin_property(), value)
212 }
213
214 pub fn relative_transform(&self) -> Option<Rc<dyn ITransform>> {
217 self.get_value(Self::relative_transform_property())
218 }
219
220 pub fn set_relative_transform(&self, value: Option<Rc<dyn ITransform>>) {
221 self.set_value(Self::relative_transform_property(), value)
222 }
223
224 pub fn parse(s: &str) -> Result<Rc<dyn IBrush>, FormatError> {
227 if !s.is_empty() {
228 if let Some(brush) = KnownColors::get_known_brush(s) {
231 return Ok(brush);
232 }
233
234 if let Some(color) = Color::try_parse(s) {
235 return Ok(Rc::new(ImmutableSolidColorBrush::new(color)));
236 }
237 }
238
239 Err(FormatError::from_string(format!("Invalid brush string: '{s}'.")))
240 }
241
242 pub(crate) fn as_compositor_serializable(&self) -> Rc<dyn ICompositorSerializable> {
245 Rc::new(RefAdapter(self.to_ref()))
246 }
247
248 pub(crate) fn register_for_serialization(&self) {
251 if self.resource.is_attached() {
252 let serializable = self.as_compositor_serializable();
253 self.resource.register_for_invalidation_on_all_compositors(&serializable);
254 }
255 }
256
257 pub(crate) fn is_on_compositor(&self, c: &Compositor) -> bool {
259 self.resource.try_get_for_compositor(c).is_some()
260 }
261
262 pub fn invalidated(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
266 let token = self.invalidated.add(Rc::new(handler));
267 let object: &FerroObject = self.upcast();
268 let weak = object.to_weak();
269 Disposable::create(move || {
270 if let Some(this) = weak.upgrade() {
271 if let Some(this) = this.downcast_ref::<Brush>() {
272 this.invalidated.remove(token);
273 }
274 }
275 })
276 }
277
278 pub(crate) fn raise_invalidated(&self) {
280 if self.invalidated.is_empty() {
281 return;
282 }
283 for (_, handler) in self.invalidated.snapshot().iter() {
284 handler();
285 }
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use crate::media::{BrushExtensions, Colors, ISolidColorBrush, SolidColorBrush};
293 use std::cell::Cell;
294
295 fn solid(brush: &Rc<dyn IBrush>) -> &dyn ISolidColorBrush {
296 brush.as_solid_color_brush().expect("solid color brush")
297 }
298
299 #[test]
300 fn parse_parses_rgb_hash_brush() {
301 let result = Brush::parse("#ff8844").unwrap();
302 let color = solid(&result).color();
303 assert_eq!((0xff, 0x88, 0x44, 0xff), (color.r, color.g, color.b, color.a));
304 }
305
306 #[test]
307 fn parse_parses_argb_hash_brush() {
308 let result = Brush::parse("#40ff8844").unwrap();
309 let color = solid(&result).color();
310 assert_eq!((0xff, 0x88, 0x44, 0x40), (color.r, color.g, color.b, color.a));
311 }
312
313 #[test]
314 fn parse_parses_named_brush_lowercase() {
315 let result = Brush::parse("red").unwrap();
316 let color = solid(&result).color();
317 assert_eq!((0xff, 0x00, 0x00, 0xff), (color.r, color.g, color.b, color.a));
318 }
319
320 #[test]
321 fn parse_parses_named_brush_uppercase() {
322 let result = Brush::parse("RED").unwrap();
323 let color = solid(&result).color();
324 assert_eq!((0xff, 0x00, 0x00, 0xff), (color.r, color.g, color.b, color.a));
325 }
326
327 #[test]
328 fn parse_to_string_named_brush_roundtrip() {
329 let brush = Brush::parse("Red").unwrap();
330 let brush = brush.as_any().downcast_ref::<ImmutableSolidColorBrush>().unwrap();
331 assert_eq!("Red", brush.to_string());
332 }
333
334 #[test]
335 fn parse_hex_value_doesnt_accept_too_few_chars() {
336 assert!(Brush::parse("#ff").is_err());
337 }
338
339 #[test]
340 fn parse_hex_value_doesnt_accept_too_many_chars() {
341 assert!(Brush::parse("#ff5555555").is_err());
342 }
343
344 #[test]
345 fn parse_hex_value_doesnt_accept_invalid_number() {
346 assert!(Brush::parse("#ff808g80").is_err());
347 }
348
349 #[test]
350 fn parse_parses_all_color_format_brushes() {
351 for input in [
352 "rgb(255, 128, 64)",
353 "rgba(255, 128, 64, 0.5)",
354 "hsl(120, 100%, 50%)",
355 "hsla(120, 100%, 50%, 0.5)",
356 "hsv(300, 100%, 25%)",
357 "hsva(300, 100%, 25%, 0.75)",
358 "#40ff8844",
359 "Green",
360 ] {
361 let brush = Brush::parse(input).unwrap();
362 let expected = Color::parse(input).unwrap();
364 assert_eq!(Some(expected), brush.as_solid_color_brush().map(|b| b.color()));
365 }
366 }
367
368 #[test]
369 fn parse_rejects_empty_string() {
370 assert!(Brush::parse("").is_err());
371 }
372
373 #[test]
374 fn changing_opacity_raises_invalidated() {
375 let target = SolidColorBrush::new();
376 let raised = Rc::new(Cell::new(false));
377 let r = raised.clone();
378 target.invalidated(move || r.set(true));
379 target.set_opacity(0.5);
380 assert!(raised.get());
381 }
382
383 #[test]
384 fn brush_property_value_accepts_mutable_and_immutable_brushes() {
385 let mutable = SolidColorBrush::with_color(Colors::RED);
386 let a: Rc<dyn IBrush> = mutable.clone().into();
387 let b: Rc<dyn IBrush> = (&mutable).into();
388 assert!(*a == *b);
391 let other: Rc<dyn IBrush> = SolidColorBrush::with_color(Colors::RED).into();
392 assert!(*a != *other);
393
394 let i1: Rc<dyn IBrush> = Rc::new(ImmutableSolidColorBrush::new(Colors::RED));
396 let i2: Rc<dyn IBrush> = Rc::new(ImmutableSolidColorBrush::new(Colors::RED));
397 assert!(*i1 == *i2);
398 assert!(*i1 != *a);
399 assert!(*a != *i1);
400
401 assert_eq!(Some(Colors::RED), a.as_solid_color_brush().map(|b| b.color()));
403 assert!(a.as_gradient_brush().is_none());
404 assert!(a.as_mutable_brush().is_some());
405 assert!(i1.as_mutable_brush().is_none());
406 assert!(a.as_object().unwrap().is::<SolidColorBrush>());
407
408 let immutable = BrushExtensions::to_immutable(&a);
410 assert_eq!(Some(Colors::RED), immutable.as_solid_color_brush().map(|b| b.color()));
411 let same = BrushExtensions::to_immutable(&i1);
412 assert!(same.reference_id() == i1.reference_id());
413
414 let base: crate::Ref<Brush> = mutable.upcast();
416 let c: Rc<dyn IBrush> = base.into();
417 assert!(c.as_solid_color_brush().is_some());
418 assert!(*c == *a);
419 }
420
421 #[test]
422 fn brush_handles_convert_to_the_brush_interface_as_untyped_values() {
423 use crate::data::core::{ValueType, ValueTypes};
424 use crate::{BoxedValue, Ref, TypeInfo};
425
426 let object = SolidColorBrush::TYPE.create_instance().expect("default constructor");
428 let brush = object.cast::<SolidColorBrush>().expect("a solid color brush");
429 brush.set_color(Colors::RED);
430 let boxed: BoxedValue = Rc::new(brush);
431
432 let target = ValueType::of::<Option<Rc<dyn IBrush>>>();
433 assert!(ValueTypes::is_assignable(ValueType::of::<Ref<SolidColorBrush>>(), target));
434 let converted = ValueTypes::try_convert(Some(&boxed), target).flatten().expect("converted");
435 let converted = converted.downcast_ref::<Option<Rc<dyn IBrush>>>().unwrap().clone().unwrap();
436 assert_eq!(solid(&converted).color(), Colors::RED);
437
438 assert!(Brush::TYPE.default_constructor().is_none());
440 assert!(TypeInfo::find_by_name("SolidColorBrush").is_some());
441 }
442}