Skip to main content

ferroui_base/rendering/composition/generated/
spot_light.rs

1// @generated by scripts/generate_composition.py — do not edit
2
3//! `SpotLight`.
4//!
5//! Defined by the FerroUI overlay (scripts/composition-schema-winui.xml).
6
7use bitflags::bitflags;
8use crate::Vector3D;
9use crate::media::Color;
10use crate::rendering::composition::ICompositionObject;
11use crate::rendering::composition::animations::AnimationInstanceFactory;
12use crate::rendering::composition::animations::ICompositionAnimation;
13use crate::rendering::composition::expressions::ExpressionVariant;
14use crate::rendering::composition::server::AsServerRenderResource;
15use crate::rendering::composition::server::CompositionProperty;
16use crate::rendering::composition::server::CompositionPropertyOf;
17use crate::rendering::composition::server::IServerObject;
18use crate::rendering::composition::server::ServerPropertyValue;
19use crate::rendering::composition::server::ServerValueChange;
20use crate::rendering::composition::server::props_of;
21use crate::rendering::composition::server::read_server_object;
22use crate::rendering::composition::transport::BatchStreamReader;
23use crate::rendering::composition::transport::BatchStreamWriter;
24use crate::rendering::composition::transport::BatchValue;
25use crate::rendering::composition::transport::BatchValueReader;
26use std::any::Any;
27use std::any::TypeId;
28use std::cell::Cell;
29use std::cell::RefCell;
30use std::rc::Rc;
31use std::sync::OnceLock;
32use std::time::Duration;
33use super::CompositionLightHooks;
34use super::CompositionLightProps;
35use super::ServerCompositionLightHooks;
36use super::ServerCompositionLightProps;
37
38bitflags! {
39    /// The fields of `SpotLight` that changed since its last serialization.
40    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
41    pub struct SpotLightChangedFields: u32 {
42        const CONSTANT_ATTENUATION = 1;
43        const CONSTANT_ATTENUATION_ANIMATED = 2;
44        const LINEAR_ATTENUATION = 4;
45        const LINEAR_ATTENUATION_ANIMATED = 8;
46        const QUADRATIC_ATTENUATION = 16;
47        const QUADRATIC_ATTENUATION_ANIMATED = 32;
48        const COORDINATE_SPACE = 64;
49        const DIRECTION = 128;
50        const DIRECTION_ANIMATED = 256;
51        const INNER_CONE_ANGLE = 512;
52        const INNER_CONE_ANGLE_ANIMATED = 1024;
53        const INNER_CONE_COLOR = 2048;
54        const INNER_CONE_COLOR_ANIMATED = 4096;
55        const INNER_CONE_INTENSITY = 8192;
56        const INNER_CONE_INTENSITY_ANIMATED = 16384;
57        const OUTER_CONE_ANGLE = 32768;
58        const OUTER_CONE_ANGLE_ANIMATED = 65536;
59        const OUTER_CONE_COLOR = 131072;
60        const OUTER_CONE_COLOR_ANIMATED = 262144;
61        const OUTER_CONE_INTENSITY = 524288;
62        const OUTER_CONE_INTENSITY_ANIMATED = 1048576;
63        const OFFSET = 2097152;
64        const OFFSET_ANIMATED = 4194304;
65        const MIN_ATTENUATION_CUTOFF = 8388608;
66        const MIN_ATTENUATION_CUTOFF_ANIMATED = 16777216;
67        const MAX_ATTENUATION_CUTOFF = 33554432;
68        const MAX_ATTENUATION_CUTOFF_ANIMATED = 67108864;
69    }
70}
71
72impl BatchValue for SpotLightChangedFields {
73    #[inline]
74    fn write_to(&self, out: &mut Vec<u8>) {
75        self.bits().write_to(out);
76    }
77
78    #[inline]
79    fn read_from(input: &mut BatchValueReader<'_>) -> Self {
80        SpotLightChangedFields::from_bits_retain(u32::read_from(input))
81    }
82}
83
84/// The partial methods of `SpotLight`: implemented by the class that embeds
85/// [`SpotLightProps`]. Every hook defaults to doing nothing.
86pub trait SpotLightHooks: CompositionLightHooks {
87    fn initialize_defaults_extra(&self) {}
88
89    fn validate_constant_attenuation_change(&self, _old_value: f32, _new_value: f32) {}
90    fn on_constant_attenuation_changed(&self) {}
91    fn on_constant_attenuation_changing(&self) {}
92
93    fn validate_linear_attenuation_change(&self, _old_value: f32, _new_value: f32) {}
94    fn on_linear_attenuation_changed(&self) {}
95    fn on_linear_attenuation_changing(&self) {}
96
97    fn validate_quadratic_attenuation_change(&self, _old_value: f32, _new_value: f32) {}
98    fn on_quadratic_attenuation_changed(&self) {}
99    fn on_quadratic_attenuation_changing(&self) {}
100
101    fn validate_coordinate_space_change(&self, _old_value: &Option<Rc<dyn ICompositionObject>>, _new_value: &Option<Rc<dyn ICompositionObject>>) {}
102    fn on_coordinate_space_changed(&self) {}
103    fn on_coordinate_space_changing(&self) {}
104
105    fn validate_direction_change(&self, _old_value: Vector3D, _new_value: Vector3D) {}
106    fn on_direction_changed(&self) {}
107    fn on_direction_changing(&self) {}
108
109    fn validate_inner_cone_angle_change(&self, _old_value: f32, _new_value: f32) {}
110    fn on_inner_cone_angle_changed(&self) {}
111    fn on_inner_cone_angle_changing(&self) {}
112
113    fn validate_inner_cone_color_change(&self, _old_value: Color, _new_value: Color) {}
114    fn on_inner_cone_color_changed(&self) {}
115    fn on_inner_cone_color_changing(&self) {}
116
117    fn validate_inner_cone_intensity_change(&self, _old_value: f32, _new_value: f32) {}
118    fn on_inner_cone_intensity_changed(&self) {}
119    fn on_inner_cone_intensity_changing(&self) {}
120
121    fn validate_outer_cone_angle_change(&self, _old_value: f32, _new_value: f32) {}
122    fn on_outer_cone_angle_changed(&self) {}
123    fn on_outer_cone_angle_changing(&self) {}
124
125    fn validate_outer_cone_color_change(&self, _old_value: Color, _new_value: Color) {}
126    fn on_outer_cone_color_changed(&self) {}
127    fn on_outer_cone_color_changing(&self) {}
128
129    fn validate_outer_cone_intensity_change(&self, _old_value: f32, _new_value: f32) {}
130    fn on_outer_cone_intensity_changed(&self) {}
131    fn on_outer_cone_intensity_changing(&self) {}
132
133    fn validate_offset_change(&self, _old_value: Vector3D, _new_value: Vector3D) {}
134    fn on_offset_changed(&self) {}
135    fn on_offset_changing(&self) {}
136
137    fn validate_min_attenuation_cutoff_change(&self, _old_value: f32, _new_value: f32) {}
138    fn on_min_attenuation_cutoff_changed(&self) {}
139    fn on_min_attenuation_cutoff_changing(&self) {}
140
141    fn validate_max_attenuation_cutoff_change(&self, _old_value: f32, _new_value: f32) {}
142    fn on_max_attenuation_cutoff_changed(&self) {}
143    fn on_max_attenuation_cutoff_changing(&self) {}
144}
145
146/// The UI-thread properties of `SpotLight`.
147pub struct SpotLightProps {
148    base: CompositionLightProps,
149    changed_fields_of_spot_light: Cell<SpotLightChangedFields>,
150    constant_attenuation: Cell<f32>,
151    linear_attenuation: Cell<f32>,
152    quadratic_attenuation: Cell<f32>,
153    coordinate_space: RefCell<Option<Rc<dyn ICompositionObject>>>,
154    direction: Cell<Vector3D>,
155    inner_cone_angle: Cell<f32>,
156    inner_cone_color: Cell<Color>,
157    inner_cone_intensity: Cell<f32>,
158    outer_cone_angle: Cell<f32>,
159    outer_cone_color: Cell<Color>,
160    outer_cone_intensity: Cell<f32>,
161    offset: Cell<Vector3D>,
162    min_attenuation_cutoff: Cell<f32>,
163    max_attenuation_cutoff: Cell<f32>,
164}
165
166impl Default for SpotLightProps {
167    fn default() -> Self {
168        Self::new()
169    }
170}
171
172impl SpotLightProps {
173    /// Creates the properties with every field at the default of its type.
174    /// The defaults of the schema are applied by
175    /// [`initialize_defaults`](Self::initialize_defaults).
176    pub fn new() -> Self {
177        Self {
178            base: CompositionLightProps::new(),
179            changed_fields_of_spot_light: Cell::new(SpotLightChangedFields::empty()),
180            constant_attenuation: Cell::new(Default::default()),
181            linear_attenuation: Cell::new(Default::default()),
182            quadratic_attenuation: Cell::new(Default::default()),
183            coordinate_space: RefCell::new(None),
184            direction: Cell::new(Default::default()),
185            inner_cone_angle: Cell::new(Default::default()),
186            inner_cone_color: Cell::new(Default::default()),
187            inner_cone_intensity: Cell::new(Default::default()),
188            outer_cone_angle: Cell::new(Default::default()),
189            outer_cone_color: Cell::new(Default::default()),
190            outer_cone_intensity: Cell::new(Default::default()),
191            offset: Cell::new(Default::default()),
192            min_attenuation_cutoff: Cell::new(Default::default()),
193            max_attenuation_cutoff: Cell::new(Default::default()),
194        }
195    }
196
197    /// The properties of the base class.
198    pub fn base(&self) -> &CompositionLightProps {
199        &self.base
200    }
201
202    /// The `InitializeDefaults` calls of the constructor chain: the base
203    /// classes first, then this class.
204    pub fn initialize_defaults(&self, host: &dyn SpotLightHooks) {
205        self.base.initialize_defaults(host);
206        self.initialize_own_defaults(host);
207    }
208
209    /// `InitializeDefaults` of this class alone.
210    pub fn initialize_own_defaults(&self, host: &dyn SpotLightHooks) {
211        SpotLightHooks::initialize_defaults_extra(host);
212        self.set_constant_attenuation(host, 1.0);
213        self.set_direction(host, Vector3D::new(0.0, 0.0, -1.0));
214        self.set_inner_cone_color(host, crate::media::Colors::WHITE);
215        self.set_inner_cone_intensity(host, 1.0);
216        self.set_outer_cone_angle(host, std::f32::consts::FRAC_PI_4);
217        self.set_outer_cone_color(host, crate::media::Colors::WHITE);
218        self.set_outer_cone_intensity(host, 1.0);
219        self.set_max_attenuation_cutoff(host, f32::MAX);
220    }
221
222    /// `ConstantAttenuation` (animated, default `1.0`, FerroUI overlay).
223    pub fn constant_attenuation(&self) -> f32 {
224        self.constant_attenuation.get()
225    }
226
227    /// Sets `ConstantAttenuation` (animated, default `1.0`, FerroUI overlay).
228    pub fn set_constant_attenuation(&self, host: &dyn SpotLightHooks, value: f32) {
229        let mut changed = false;
230        let old_value = self.constant_attenuation.get();
231        if old_value.differs(&value) {
232            SpotLightHooks::validate_constant_attenuation_change(host, old_value, value);
233            SpotLightHooks::on_constant_attenuation_changing(host);
234            changed = true;
235            // Update the backing value
236            self.constant_attenuation.set(value);
237            // Register object for serialization in the next batch
238            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::CONSTANT_ATTENUATION);
239            host.register_for_serialization();
240            // Reset previous animation if any
241            host.pending_animations().remove(ServerSpotLightProps::id_of_constant_attenuation_property().base());
242            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::CONSTANT_ATTENUATION_ANIMATED);
243            // Check for implicit animations
244            if let Some(animation) = host.implicit_animation("ConstantAttenuation") {
245                // Animation affects only current property
246                if let Some(a) = animation.as_composition_animation() {
247                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::CONSTANT_ATTENUATION_ANIMATED);
248                    host.pending_animations().set(
249                        ServerSpotLightProps::id_of_constant_attenuation_property().base(),
250                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
251                    );
252                }
253                // Animation is triggered by the current field, but does not necessary affects it
254                host.start_animation_group(&animation, "ConstantAttenuation", ExpressionVariant::create(value));
255            }
256        }
257        self.constant_attenuation.set(value);
258        if changed {
259            SpotLightHooks::on_constant_attenuation_changed(host);
260        }
261    }
262
263    /// `LinearAttenuation` (animated, FerroUI overlay).
264    pub fn linear_attenuation(&self) -> f32 {
265        self.linear_attenuation.get()
266    }
267
268    /// Sets `LinearAttenuation` (animated, FerroUI overlay).
269    pub fn set_linear_attenuation(&self, host: &dyn SpotLightHooks, value: f32) {
270        let mut changed = false;
271        let old_value = self.linear_attenuation.get();
272        if old_value.differs(&value) {
273            SpotLightHooks::validate_linear_attenuation_change(host, old_value, value);
274            SpotLightHooks::on_linear_attenuation_changing(host);
275            changed = true;
276            // Update the backing value
277            self.linear_attenuation.set(value);
278            // Register object for serialization in the next batch
279            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::LINEAR_ATTENUATION);
280            host.register_for_serialization();
281            // Reset previous animation if any
282            host.pending_animations().remove(ServerSpotLightProps::id_of_linear_attenuation_property().base());
283            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::LINEAR_ATTENUATION_ANIMATED);
284            // Check for implicit animations
285            if let Some(animation) = host.implicit_animation("LinearAttenuation") {
286                // Animation affects only current property
287                if let Some(a) = animation.as_composition_animation() {
288                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::LINEAR_ATTENUATION_ANIMATED);
289                    host.pending_animations().set(
290                        ServerSpotLightProps::id_of_linear_attenuation_property().base(),
291                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
292                    );
293                }
294                // Animation is triggered by the current field, but does not necessary affects it
295                host.start_animation_group(&animation, "LinearAttenuation", ExpressionVariant::create(value));
296            }
297        }
298        self.linear_attenuation.set(value);
299        if changed {
300            SpotLightHooks::on_linear_attenuation_changed(host);
301        }
302    }
303
304    /// `QuadraticAttenuation` (animated, FerroUI overlay).
305    pub fn quadratic_attenuation(&self) -> f32 {
306        self.quadratic_attenuation.get()
307    }
308
309    /// Sets `QuadraticAttenuation` (animated, FerroUI overlay).
310    pub fn set_quadratic_attenuation(&self, host: &dyn SpotLightHooks, value: f32) {
311        let mut changed = false;
312        let old_value = self.quadratic_attenuation.get();
313        if old_value.differs(&value) {
314            SpotLightHooks::validate_quadratic_attenuation_change(host, old_value, value);
315            SpotLightHooks::on_quadratic_attenuation_changing(host);
316            changed = true;
317            // Update the backing value
318            self.quadratic_attenuation.set(value);
319            // Register object for serialization in the next batch
320            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::QUADRATIC_ATTENUATION);
321            host.register_for_serialization();
322            // Reset previous animation if any
323            host.pending_animations().remove(ServerSpotLightProps::id_of_quadratic_attenuation_property().base());
324            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::QUADRATIC_ATTENUATION_ANIMATED);
325            // Check for implicit animations
326            if let Some(animation) = host.implicit_animation("QuadraticAttenuation") {
327                // Animation affects only current property
328                if let Some(a) = animation.as_composition_animation() {
329                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::QUADRATIC_ATTENUATION_ANIMATED);
330                    host.pending_animations().set(
331                        ServerSpotLightProps::id_of_quadratic_attenuation_property().base(),
332                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
333                    );
334                }
335                // Animation is triggered by the current field, but does not necessary affects it
336                host.start_animation_group(&animation, "QuadraticAttenuation", ExpressionVariant::create(value));
337            }
338        }
339        self.quadratic_attenuation.set(value);
340        if changed {
341            SpotLightHooks::on_quadratic_attenuation_changed(host);
342        }
343    }
344
345    /// `CoordinateSpace` (FerroUI overlay).
346    pub fn coordinate_space(&self) -> Option<Rc<dyn ICompositionObject>> {
347        self.coordinate_space.borrow().clone()
348    }
349
350    /// Sets `CoordinateSpace` (FerroUI overlay).
351    pub fn set_coordinate_space(&self, host: &dyn SpotLightHooks, value: Option<Rc<dyn ICompositionObject>>) {
352        let mut changed = false;
353        let old_value = self.coordinate_space.borrow().clone();
354        if old_value.differs(&value) {
355            SpotLightHooks::validate_coordinate_space_change(host, &old_value, &value);
356            SpotLightHooks::on_coordinate_space_changing(host);
357            changed = true;
358            // Update the backing value
359            *self.coordinate_space.borrow_mut() = value.clone();
360            // Register object for serialization in the next batch
361            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::COORDINATE_SPACE);
362            host.register_for_serialization();
363        }
364        *self.coordinate_space.borrow_mut() = value;
365        if changed {
366            SpotLightHooks::on_coordinate_space_changed(host);
367        }
368    }
369
370    /// `Direction` (animated, default `Vector3D::new(0.0, 0.0, -1.0)`, FerroUI overlay).
371    pub fn direction(&self) -> Vector3D {
372        self.direction.get()
373    }
374
375    /// Sets `Direction` (animated, default `Vector3D::new(0.0, 0.0, -1.0)`, FerroUI overlay).
376    pub fn set_direction(&self, host: &dyn SpotLightHooks, value: Vector3D) {
377        let mut changed = false;
378        let old_value = self.direction.get();
379        if old_value.differs(&value) {
380            SpotLightHooks::validate_direction_change(host, old_value, value);
381            SpotLightHooks::on_direction_changing(host);
382            changed = true;
383            // Update the backing value
384            self.direction.set(value);
385            // Register object for serialization in the next batch
386            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::DIRECTION);
387            host.register_for_serialization();
388            // Reset previous animation if any
389            host.pending_animations().remove(ServerSpotLightProps::id_of_direction_property().base());
390            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::DIRECTION_ANIMATED);
391            // Check for implicit animations
392            if let Some(animation) = host.implicit_animation("Direction") {
393                // Animation affects only current property
394                if let Some(a) = animation.as_composition_animation() {
395                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::DIRECTION_ANIMATED);
396                    host.pending_animations().set(
397                        ServerSpotLightProps::id_of_direction_property().base(),
398                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
399                    );
400                }
401                // Animation is triggered by the current field, but does not necessary affects it
402                host.start_animation_group(&animation, "Direction", ExpressionVariant::create(value));
403            }
404        }
405        self.direction.set(value);
406        if changed {
407            SpotLightHooks::on_direction_changed(host);
408        }
409    }
410
411    /// `InnerConeAngle` (animated, FerroUI overlay).
412    pub fn inner_cone_angle(&self) -> f32 {
413        self.inner_cone_angle.get()
414    }
415
416    /// Sets `InnerConeAngle` (animated, FerroUI overlay).
417    pub fn set_inner_cone_angle(&self, host: &dyn SpotLightHooks, value: f32) {
418        let mut changed = false;
419        let old_value = self.inner_cone_angle.get();
420        if old_value.differs(&value) {
421            SpotLightHooks::validate_inner_cone_angle_change(host, old_value, value);
422            SpotLightHooks::on_inner_cone_angle_changing(host);
423            changed = true;
424            // Update the backing value
425            self.inner_cone_angle.set(value);
426            // Register object for serialization in the next batch
427            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::INNER_CONE_ANGLE);
428            host.register_for_serialization();
429            // Reset previous animation if any
430            host.pending_animations().remove(ServerSpotLightProps::id_of_inner_cone_angle_property().base());
431            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::INNER_CONE_ANGLE_ANIMATED);
432            // Check for implicit animations
433            if let Some(animation) = host.implicit_animation("InnerConeAngle") {
434                // Animation affects only current property
435                if let Some(a) = animation.as_composition_animation() {
436                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::INNER_CONE_ANGLE_ANIMATED);
437                    host.pending_animations().set(
438                        ServerSpotLightProps::id_of_inner_cone_angle_property().base(),
439                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
440                    );
441                }
442                // Animation is triggered by the current field, but does not necessary affects it
443                host.start_animation_group(&animation, "InnerConeAngle", ExpressionVariant::create(value));
444            }
445        }
446        self.inner_cone_angle.set(value);
447        if changed {
448            SpotLightHooks::on_inner_cone_angle_changed(host);
449        }
450    }
451
452    /// `InnerConeColor` (animated, default `crate::media::Colors::WHITE`, FerroUI overlay).
453    pub fn inner_cone_color(&self) -> Color {
454        self.inner_cone_color.get()
455    }
456
457    /// Sets `InnerConeColor` (animated, default `crate::media::Colors::WHITE`, FerroUI overlay).
458    pub fn set_inner_cone_color(&self, host: &dyn SpotLightHooks, value: Color) {
459        let mut changed = false;
460        let old_value = self.inner_cone_color.get();
461        if old_value.differs(&value) {
462            SpotLightHooks::validate_inner_cone_color_change(host, old_value, value);
463            SpotLightHooks::on_inner_cone_color_changing(host);
464            changed = true;
465            // Update the backing value
466            self.inner_cone_color.set(value);
467            // Register object for serialization in the next batch
468            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::INNER_CONE_COLOR);
469            host.register_for_serialization();
470            // Reset previous animation if any
471            host.pending_animations().remove(ServerSpotLightProps::id_of_inner_cone_color_property().base());
472            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::INNER_CONE_COLOR_ANIMATED);
473            // Check for implicit animations
474            if let Some(animation) = host.implicit_animation("InnerConeColor") {
475                // Animation affects only current property
476                if let Some(a) = animation.as_composition_animation() {
477                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::INNER_CONE_COLOR_ANIMATED);
478                    host.pending_animations().set(
479                        ServerSpotLightProps::id_of_inner_cone_color_property().base(),
480                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
481                    );
482                }
483                // Animation is triggered by the current field, but does not necessary affects it
484                host.start_animation_group(&animation, "InnerConeColor", ExpressionVariant::create(value));
485            }
486        }
487        self.inner_cone_color.set(value);
488        if changed {
489            SpotLightHooks::on_inner_cone_color_changed(host);
490        }
491    }
492
493    /// `InnerConeIntensity` (animated, default `1.0`, FerroUI overlay).
494    pub fn inner_cone_intensity(&self) -> f32 {
495        self.inner_cone_intensity.get()
496    }
497
498    /// Sets `InnerConeIntensity` (animated, default `1.0`, FerroUI overlay).
499    pub fn set_inner_cone_intensity(&self, host: &dyn SpotLightHooks, value: f32) {
500        let mut changed = false;
501        let old_value = self.inner_cone_intensity.get();
502        if old_value.differs(&value) {
503            SpotLightHooks::validate_inner_cone_intensity_change(host, old_value, value);
504            SpotLightHooks::on_inner_cone_intensity_changing(host);
505            changed = true;
506            // Update the backing value
507            self.inner_cone_intensity.set(value);
508            // Register object for serialization in the next batch
509            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::INNER_CONE_INTENSITY);
510            host.register_for_serialization();
511            // Reset previous animation if any
512            host.pending_animations().remove(ServerSpotLightProps::id_of_inner_cone_intensity_property().base());
513            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::INNER_CONE_INTENSITY_ANIMATED);
514            // Check for implicit animations
515            if let Some(animation) = host.implicit_animation("InnerConeIntensity") {
516                // Animation affects only current property
517                if let Some(a) = animation.as_composition_animation() {
518                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::INNER_CONE_INTENSITY_ANIMATED);
519                    host.pending_animations().set(
520                        ServerSpotLightProps::id_of_inner_cone_intensity_property().base(),
521                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
522                    );
523                }
524                // Animation is triggered by the current field, but does not necessary affects it
525                host.start_animation_group(&animation, "InnerConeIntensity", ExpressionVariant::create(value));
526            }
527        }
528        self.inner_cone_intensity.set(value);
529        if changed {
530            SpotLightHooks::on_inner_cone_intensity_changed(host);
531        }
532    }
533
534    /// `OuterConeAngle` (animated, default `std::f32::consts::FRAC_PI_4`, FerroUI overlay).
535    pub fn outer_cone_angle(&self) -> f32 {
536        self.outer_cone_angle.get()
537    }
538
539    /// Sets `OuterConeAngle` (animated, default `std::f32::consts::FRAC_PI_4`, FerroUI overlay).
540    pub fn set_outer_cone_angle(&self, host: &dyn SpotLightHooks, value: f32) {
541        let mut changed = false;
542        let old_value = self.outer_cone_angle.get();
543        if old_value.differs(&value) {
544            SpotLightHooks::validate_outer_cone_angle_change(host, old_value, value);
545            SpotLightHooks::on_outer_cone_angle_changing(host);
546            changed = true;
547            // Update the backing value
548            self.outer_cone_angle.set(value);
549            // Register object for serialization in the next batch
550            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OUTER_CONE_ANGLE);
551            host.register_for_serialization();
552            // Reset previous animation if any
553            host.pending_animations().remove(ServerSpotLightProps::id_of_outer_cone_angle_property().base());
554            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::OUTER_CONE_ANGLE_ANIMATED);
555            // Check for implicit animations
556            if let Some(animation) = host.implicit_animation("OuterConeAngle") {
557                // Animation affects only current property
558                if let Some(a) = animation.as_composition_animation() {
559                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OUTER_CONE_ANGLE_ANIMATED);
560                    host.pending_animations().set(
561                        ServerSpotLightProps::id_of_outer_cone_angle_property().base(),
562                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
563                    );
564                }
565                // Animation is triggered by the current field, but does not necessary affects it
566                host.start_animation_group(&animation, "OuterConeAngle", ExpressionVariant::create(value));
567            }
568        }
569        self.outer_cone_angle.set(value);
570        if changed {
571            SpotLightHooks::on_outer_cone_angle_changed(host);
572        }
573    }
574
575    /// `OuterConeColor` (animated, default `crate::media::Colors::WHITE`, FerroUI overlay).
576    pub fn outer_cone_color(&self) -> Color {
577        self.outer_cone_color.get()
578    }
579
580    /// Sets `OuterConeColor` (animated, default `crate::media::Colors::WHITE`, FerroUI overlay).
581    pub fn set_outer_cone_color(&self, host: &dyn SpotLightHooks, value: Color) {
582        let mut changed = false;
583        let old_value = self.outer_cone_color.get();
584        if old_value.differs(&value) {
585            SpotLightHooks::validate_outer_cone_color_change(host, old_value, value);
586            SpotLightHooks::on_outer_cone_color_changing(host);
587            changed = true;
588            // Update the backing value
589            self.outer_cone_color.set(value);
590            // Register object for serialization in the next batch
591            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OUTER_CONE_COLOR);
592            host.register_for_serialization();
593            // Reset previous animation if any
594            host.pending_animations().remove(ServerSpotLightProps::id_of_outer_cone_color_property().base());
595            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::OUTER_CONE_COLOR_ANIMATED);
596            // Check for implicit animations
597            if let Some(animation) = host.implicit_animation("OuterConeColor") {
598                // Animation affects only current property
599                if let Some(a) = animation.as_composition_animation() {
600                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OUTER_CONE_COLOR_ANIMATED);
601                    host.pending_animations().set(
602                        ServerSpotLightProps::id_of_outer_cone_color_property().base(),
603                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
604                    );
605                }
606                // Animation is triggered by the current field, but does not necessary affects it
607                host.start_animation_group(&animation, "OuterConeColor", ExpressionVariant::create(value));
608            }
609        }
610        self.outer_cone_color.set(value);
611        if changed {
612            SpotLightHooks::on_outer_cone_color_changed(host);
613        }
614    }
615
616    /// `OuterConeIntensity` (animated, default `1.0`, FerroUI overlay).
617    pub fn outer_cone_intensity(&self) -> f32 {
618        self.outer_cone_intensity.get()
619    }
620
621    /// Sets `OuterConeIntensity` (animated, default `1.0`, FerroUI overlay).
622    pub fn set_outer_cone_intensity(&self, host: &dyn SpotLightHooks, value: f32) {
623        let mut changed = false;
624        let old_value = self.outer_cone_intensity.get();
625        if old_value.differs(&value) {
626            SpotLightHooks::validate_outer_cone_intensity_change(host, old_value, value);
627            SpotLightHooks::on_outer_cone_intensity_changing(host);
628            changed = true;
629            // Update the backing value
630            self.outer_cone_intensity.set(value);
631            // Register object for serialization in the next batch
632            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OUTER_CONE_INTENSITY);
633            host.register_for_serialization();
634            // Reset previous animation if any
635            host.pending_animations().remove(ServerSpotLightProps::id_of_outer_cone_intensity_property().base());
636            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::OUTER_CONE_INTENSITY_ANIMATED);
637            // Check for implicit animations
638            if let Some(animation) = host.implicit_animation("OuterConeIntensity") {
639                // Animation affects only current property
640                if let Some(a) = animation.as_composition_animation() {
641                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OUTER_CONE_INTENSITY_ANIMATED);
642                    host.pending_animations().set(
643                        ServerSpotLightProps::id_of_outer_cone_intensity_property().base(),
644                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
645                    );
646                }
647                // Animation is triggered by the current field, but does not necessary affects it
648                host.start_animation_group(&animation, "OuterConeIntensity", ExpressionVariant::create(value));
649            }
650        }
651        self.outer_cone_intensity.set(value);
652        if changed {
653            SpotLightHooks::on_outer_cone_intensity_changed(host);
654        }
655    }
656
657    /// `Offset` (animated, FerroUI overlay).
658    pub fn offset(&self) -> Vector3D {
659        self.offset.get()
660    }
661
662    /// Sets `Offset` (animated, FerroUI overlay).
663    pub fn set_offset(&self, host: &dyn SpotLightHooks, value: Vector3D) {
664        let mut changed = false;
665        let old_value = self.offset.get();
666        if old_value.differs(&value) {
667            SpotLightHooks::validate_offset_change(host, old_value, value);
668            SpotLightHooks::on_offset_changing(host);
669            changed = true;
670            // Update the backing value
671            self.offset.set(value);
672            // Register object for serialization in the next batch
673            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OFFSET);
674            host.register_for_serialization();
675            // Reset previous animation if any
676            host.pending_animations().remove(ServerSpotLightProps::id_of_offset_property().base());
677            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::OFFSET_ANIMATED);
678            // Check for implicit animations
679            if let Some(animation) = host.implicit_animation("Offset") {
680                // Animation affects only current property
681                if let Some(a) = animation.as_composition_animation() {
682                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OFFSET_ANIMATED);
683                    host.pending_animations().set(
684                        ServerSpotLightProps::id_of_offset_property().base(),
685                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
686                    );
687                }
688                // Animation is triggered by the current field, but does not necessary affects it
689                host.start_animation_group(&animation, "Offset", ExpressionVariant::create(value));
690            }
691        }
692        self.offset.set(value);
693        if changed {
694            SpotLightHooks::on_offset_changed(host);
695        }
696    }
697
698    /// `MinAttenuationCutoff` (animated, FerroUI overlay).
699    pub fn min_attenuation_cutoff(&self) -> f32 {
700        self.min_attenuation_cutoff.get()
701    }
702
703    /// Sets `MinAttenuationCutoff` (animated, FerroUI overlay).
704    pub fn set_min_attenuation_cutoff(&self, host: &dyn SpotLightHooks, value: f32) {
705        let mut changed = false;
706        let old_value = self.min_attenuation_cutoff.get();
707        if old_value.differs(&value) {
708            SpotLightHooks::validate_min_attenuation_cutoff_change(host, old_value, value);
709            SpotLightHooks::on_min_attenuation_cutoff_changing(host);
710            changed = true;
711            // Update the backing value
712            self.min_attenuation_cutoff.set(value);
713            // Register object for serialization in the next batch
714            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::MIN_ATTENUATION_CUTOFF);
715            host.register_for_serialization();
716            // Reset previous animation if any
717            host.pending_animations().remove(ServerSpotLightProps::id_of_min_attenuation_cutoff_property().base());
718            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::MIN_ATTENUATION_CUTOFF_ANIMATED);
719            // Check for implicit animations
720            if let Some(animation) = host.implicit_animation("MinAttenuationCutoff") {
721                // Animation affects only current property
722                if let Some(a) = animation.as_composition_animation() {
723                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::MIN_ATTENUATION_CUTOFF_ANIMATED);
724                    host.pending_animations().set(
725                        ServerSpotLightProps::id_of_min_attenuation_cutoff_property().base(),
726                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
727                    );
728                }
729                // Animation is triggered by the current field, but does not necessary affects it
730                host.start_animation_group(&animation, "MinAttenuationCutoff", ExpressionVariant::create(value));
731            }
732        }
733        self.min_attenuation_cutoff.set(value);
734        if changed {
735            SpotLightHooks::on_min_attenuation_cutoff_changed(host);
736        }
737    }
738
739    /// `MaxAttenuationCutoff` (animated, default `f32::MAX`, FerroUI overlay).
740    pub fn max_attenuation_cutoff(&self) -> f32 {
741        self.max_attenuation_cutoff.get()
742    }
743
744    /// Sets `MaxAttenuationCutoff` (animated, default `f32::MAX`, FerroUI overlay).
745    pub fn set_max_attenuation_cutoff(&self, host: &dyn SpotLightHooks, value: f32) {
746        let mut changed = false;
747        let old_value = self.max_attenuation_cutoff.get();
748        if old_value.differs(&value) {
749            SpotLightHooks::validate_max_attenuation_cutoff_change(host, old_value, value);
750            SpotLightHooks::on_max_attenuation_cutoff_changing(host);
751            changed = true;
752            // Update the backing value
753            self.max_attenuation_cutoff.set(value);
754            // Register object for serialization in the next batch
755            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::MAX_ATTENUATION_CUTOFF);
756            host.register_for_serialization();
757            // Reset previous animation if any
758            host.pending_animations().remove(ServerSpotLightProps::id_of_max_attenuation_cutoff_property().base());
759            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() & !SpotLightChangedFields::MAX_ATTENUATION_CUTOFF_ANIMATED);
760            // Check for implicit animations
761            if let Some(animation) = host.implicit_animation("MaxAttenuationCutoff") {
762                // Animation affects only current property
763                if let Some(a) = animation.as_composition_animation() {
764                    self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::MAX_ATTENUATION_CUTOFF_ANIMATED);
765                    host.pending_animations().set(
766                        ServerSpotLightProps::id_of_max_attenuation_cutoff_property().base(),
767                        a.create_instance(host.server(), Some(ExpressionVariant::create(value))),
768                    );
769                }
770                // Animation is triggered by the current field, but does not necessary affects it
771                host.start_animation_group(&animation, "MaxAttenuationCutoff", ExpressionVariant::create(value));
772            }
773        }
774        self.max_attenuation_cutoff.set(value);
775        if changed {
776            SpotLightHooks::on_max_attenuation_cutoff_changed(host);
777        }
778    }
779
780    /// Connects an animation with the named property. Returns `false` when
781    /// neither this class nor its generated bases have an animated property
782    /// of that name: the caller continues with its hand-written base.
783    pub fn start_animation(
784        &self,
785        host: &dyn SpotLightHooks,
786        property_name: &str,
787        animation: &dyn ICompositionAnimation,
788        final_value: Option<ExpressionVariant>,
789    ) -> bool {
790        if property_name == "ConstantAttenuation" {
791            let server = animation.create_instance(host.server(), final_value);
792            host.pending_animations().set(ServerSpotLightProps::id_of_constant_attenuation_property().base(), server);
793            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::CONSTANT_ATTENUATION_ANIMATED);
794            host.register_for_serialization();
795            return true;
796        }
797        if property_name == "LinearAttenuation" {
798            let server = animation.create_instance(host.server(), final_value);
799            host.pending_animations().set(ServerSpotLightProps::id_of_linear_attenuation_property().base(), server);
800            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::LINEAR_ATTENUATION_ANIMATED);
801            host.register_for_serialization();
802            return true;
803        }
804        if property_name == "QuadraticAttenuation" {
805            let server = animation.create_instance(host.server(), final_value);
806            host.pending_animations().set(ServerSpotLightProps::id_of_quadratic_attenuation_property().base(), server);
807            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::QUADRATIC_ATTENUATION_ANIMATED);
808            host.register_for_serialization();
809            return true;
810        }
811        if property_name == "Direction" {
812            let server = animation.create_instance(host.server(), final_value);
813            host.pending_animations().set(ServerSpotLightProps::id_of_direction_property().base(), server);
814            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::DIRECTION_ANIMATED);
815            host.register_for_serialization();
816            return true;
817        }
818        if property_name == "InnerConeAngle" {
819            let server = animation.create_instance(host.server(), final_value);
820            host.pending_animations().set(ServerSpotLightProps::id_of_inner_cone_angle_property().base(), server);
821            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::INNER_CONE_ANGLE_ANIMATED);
822            host.register_for_serialization();
823            return true;
824        }
825        if property_name == "InnerConeColor" {
826            let server = animation.create_instance(host.server(), final_value);
827            host.pending_animations().set(ServerSpotLightProps::id_of_inner_cone_color_property().base(), server);
828            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::INNER_CONE_COLOR_ANIMATED);
829            host.register_for_serialization();
830            return true;
831        }
832        if property_name == "InnerConeIntensity" {
833            let server = animation.create_instance(host.server(), final_value);
834            host.pending_animations().set(ServerSpotLightProps::id_of_inner_cone_intensity_property().base(), server);
835            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::INNER_CONE_INTENSITY_ANIMATED);
836            host.register_for_serialization();
837            return true;
838        }
839        if property_name == "OuterConeAngle" {
840            let server = animation.create_instance(host.server(), final_value);
841            host.pending_animations().set(ServerSpotLightProps::id_of_outer_cone_angle_property().base(), server);
842            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OUTER_CONE_ANGLE_ANIMATED);
843            host.register_for_serialization();
844            return true;
845        }
846        if property_name == "OuterConeColor" {
847            let server = animation.create_instance(host.server(), final_value);
848            host.pending_animations().set(ServerSpotLightProps::id_of_outer_cone_color_property().base(), server);
849            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OUTER_CONE_COLOR_ANIMATED);
850            host.register_for_serialization();
851            return true;
852        }
853        if property_name == "OuterConeIntensity" {
854            let server = animation.create_instance(host.server(), final_value);
855            host.pending_animations().set(ServerSpotLightProps::id_of_outer_cone_intensity_property().base(), server);
856            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OUTER_CONE_INTENSITY_ANIMATED);
857            host.register_for_serialization();
858            return true;
859        }
860        if property_name == "Offset" {
861            let server = animation.create_instance(host.server(), final_value);
862            host.pending_animations().set(ServerSpotLightProps::id_of_offset_property().base(), server);
863            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::OFFSET_ANIMATED);
864            host.register_for_serialization();
865            return true;
866        }
867        if property_name == "MinAttenuationCutoff" {
868            let server = animation.create_instance(host.server(), final_value);
869            host.pending_animations().set(ServerSpotLightProps::id_of_min_attenuation_cutoff_property().base(), server);
870            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::MIN_ATTENUATION_CUTOFF_ANIMATED);
871            host.register_for_serialization();
872            return true;
873        }
874        if property_name == "MaxAttenuationCutoff" {
875            let server = animation.create_instance(host.server(), final_value);
876            host.pending_animations().set(ServerSpotLightProps::id_of_max_attenuation_cutoff_property().base(), server);
877            self.changed_fields_of_spot_light.set(self.changed_fields_of_spot_light.get() | SpotLightChangedFields::MAX_ATTENUATION_CUTOFF_ANIMATED);
878            host.register_for_serialization();
879            return true;
880        }
881        false
882    }
883
884    /// Writes the changes of the object: the base class first, then the
885    /// changed-fields mask and the changed members of this class.
886    pub fn serialize_changes_core(&self, host: &dyn SpotLightHooks, writer: &mut BatchStreamWriter<'_>) {
887        self.base.serialize_changes_core(host, writer);
888        let changed = self.changed_fields_of_spot_light.get();
889        writer.write(changed);
890        if changed.contains(SpotLightChangedFields::CONSTANT_ATTENUATION_ANIMATED) {
891            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_constant_attenuation_property().base()));
892        } else if changed.contains(SpotLightChangedFields::CONSTANT_ATTENUATION) {
893            writer.write(self.constant_attenuation.get());
894        }
895        if changed.contains(SpotLightChangedFields::LINEAR_ATTENUATION_ANIMATED) {
896            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_linear_attenuation_property().base()));
897        } else if changed.contains(SpotLightChangedFields::LINEAR_ATTENUATION) {
898            writer.write(self.linear_attenuation.get());
899        }
900        if changed.contains(SpotLightChangedFields::QUADRATIC_ATTENUATION_ANIMATED) {
901            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_quadratic_attenuation_property().base()));
902        } else if changed.contains(SpotLightChangedFields::QUADRATIC_ATTENUATION) {
903            writer.write(self.quadratic_attenuation.get());
904        }
905        if changed.contains(SpotLightChangedFields::COORDINATE_SPACE) {
906            writer.write_server_object(self.coordinate_space.borrow().clone().map(|o| o.server()));
907        }
908        if changed.contains(SpotLightChangedFields::DIRECTION_ANIMATED) {
909            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_direction_property().base()));
910        } else if changed.contains(SpotLightChangedFields::DIRECTION) {
911            writer.write(self.direction.get());
912        }
913        if changed.contains(SpotLightChangedFields::INNER_CONE_ANGLE_ANIMATED) {
914            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_inner_cone_angle_property().base()));
915        } else if changed.contains(SpotLightChangedFields::INNER_CONE_ANGLE) {
916            writer.write(self.inner_cone_angle.get());
917        }
918        if changed.contains(SpotLightChangedFields::INNER_CONE_COLOR_ANIMATED) {
919            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_inner_cone_color_property().base()));
920        } else if changed.contains(SpotLightChangedFields::INNER_CONE_COLOR) {
921            writer.write(self.inner_cone_color.get());
922        }
923        if changed.contains(SpotLightChangedFields::INNER_CONE_INTENSITY_ANIMATED) {
924            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_inner_cone_intensity_property().base()));
925        } else if changed.contains(SpotLightChangedFields::INNER_CONE_INTENSITY) {
926            writer.write(self.inner_cone_intensity.get());
927        }
928        if changed.contains(SpotLightChangedFields::OUTER_CONE_ANGLE_ANIMATED) {
929            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_outer_cone_angle_property().base()));
930        } else if changed.contains(SpotLightChangedFields::OUTER_CONE_ANGLE) {
931            writer.write(self.outer_cone_angle.get());
932        }
933        if changed.contains(SpotLightChangedFields::OUTER_CONE_COLOR_ANIMATED) {
934            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_outer_cone_color_property().base()));
935        } else if changed.contains(SpotLightChangedFields::OUTER_CONE_COLOR) {
936            writer.write(self.outer_cone_color.get());
937        }
938        if changed.contains(SpotLightChangedFields::OUTER_CONE_INTENSITY_ANIMATED) {
939            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_outer_cone_intensity_property().base()));
940        } else if changed.contains(SpotLightChangedFields::OUTER_CONE_INTENSITY) {
941            writer.write(self.outer_cone_intensity.get());
942        }
943        if changed.contains(SpotLightChangedFields::OFFSET_ANIMATED) {
944            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_offset_property().base()));
945        } else if changed.contains(SpotLightChangedFields::OFFSET) {
946            writer.write(self.offset.get());
947        }
948        if changed.contains(SpotLightChangedFields::MIN_ATTENUATION_CUTOFF_ANIMATED) {
949            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_min_attenuation_cutoff_property().base()));
950        } else if changed.contains(SpotLightChangedFields::MIN_ATTENUATION_CUTOFF) {
951            writer.write(self.min_attenuation_cutoff.get());
952        }
953        if changed.contains(SpotLightChangedFields::MAX_ATTENUATION_CUTOFF_ANIMATED) {
954            writer.write_value(host.pending_animations().get_and_remove(ServerSpotLightProps::id_of_max_attenuation_cutoff_property().base()));
955        } else if changed.contains(SpotLightChangedFields::MAX_ATTENUATION_CUTOFF) {
956            writer.write(self.max_attenuation_cutoff.get());
957        }
958        self.changed_fields_of_spot_light.set(SpotLightChangedFields::empty());
959    }
960}
961
962/// The partial methods of `ServerSpotLight`: implemented by the class that embeds
963/// [`ServerSpotLightProps`]. Every hook defaults to doing nothing.
964pub trait ServerSpotLightHooks: ServerCompositionLightHooks {
965    fn deserialize_changes_extra(&self, _reader: &mut BatchStreamReader<'_>) {}
966    fn on_fields_deserialized(&self, _changed: SpotLightChangedFields) {}
967
968    fn on_coordinate_space_changed(&self) {}
969    fn on_coordinate_space_changing(&self) {}
970}
971
972/// The server-side properties of `ServerSpotLight`.
973pub struct ServerSpotLightProps {
974    base: ServerCompositionLightProps,
975    constant_attenuation: Cell<f32>,
976    linear_attenuation: Cell<f32>,
977    quadratic_attenuation: Cell<f32>,
978    coordinate_space: RefCell<Option<Rc<dyn IServerObject>>>,
979    direction: Cell<Vector3D>,
980    inner_cone_angle: Cell<f32>,
981    inner_cone_color: Cell<Color>,
982    inner_cone_intensity: Cell<f32>,
983    outer_cone_angle: Cell<f32>,
984    outer_cone_color: Cell<Color>,
985    outer_cone_intensity: Cell<f32>,
986    offset: Cell<Vector3D>,
987    min_attenuation_cutoff: Cell<f32>,
988    max_attenuation_cutoff: Cell<f32>,
989}
990
991impl Default for ServerSpotLightProps {
992    fn default() -> Self {
993        Self::new()
994    }
995}
996
997impl ServerSpotLightProps {
998    /// Creates the properties with every field at the default of its type; the
999    /// values of the UI thread arrive with the first batch.
1000    pub fn new() -> Self {
1001        Self {
1002            base: ServerCompositionLightProps::new(),
1003            constant_attenuation: Cell::new(Default::default()),
1004            linear_attenuation: Cell::new(Default::default()),
1005            quadratic_attenuation: Cell::new(Default::default()),
1006            coordinate_space: RefCell::new(None),
1007            direction: Cell::new(Default::default()),
1008            inner_cone_angle: Cell::new(Default::default()),
1009            inner_cone_color: Cell::new(Default::default()),
1010            inner_cone_intensity: Cell::new(Default::default()),
1011            outer_cone_angle: Cell::new(Default::default()),
1012            outer_cone_color: Cell::new(Default::default()),
1013            outer_cone_intensity: Cell::new(Default::default()),
1014            offset: Cell::new(Default::default()),
1015            min_attenuation_cutoff: Cell::new(Default::default()),
1016            max_attenuation_cutoff: Cell::new(Default::default()),
1017        }
1018    }
1019
1020    /// The properties of the base class.
1021    pub fn base(&self) -> &ServerCompositionLightProps {
1022        &self.base
1023    }
1024
1025    /// This block or the block of a base class, by type: what
1026    /// `IServerObject::get_props` of the embedding class returns.
1027    pub fn find_props(&self, type_id: TypeId) -> Option<&dyn Any> {
1028        if type_id == TypeId::of::<Self>() {
1029            return Some(self);
1030        }
1031        self.base.find_props(type_id)
1032    }
1033
1034    /// The composition property of `ConstantAttenuation`.
1035    pub fn id_of_constant_attenuation_property() -> &'static CompositionPropertyOf<f32> {
1036        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
1037        PROPERTY.get_or_init(|| {
1038            CompositionProperty::register::<ServerSpotLightProps, f32>(
1039                "ConstantAttenuation",
1040                "ServerSpotLight",
1041                |obj| props_of::<ServerSpotLightProps>(obj).constant_attenuation.get(),
1042                |obj, v| props_of::<ServerSpotLightProps>(obj).constant_attenuation.set(v),
1043                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).constant_attenuation.get())),
1044                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).constant_attenuation.set(v.cast_or_default::<f32>())),
1045            )
1046        })
1047    }
1048
1049    /// `ConstantAttenuation` (animated).
1050    pub fn constant_attenuation(&self) -> f32 {
1051        self.constant_attenuation.get()
1052    }
1053
1054    pub fn set_constant_attenuation(&self, host: &dyn ServerSpotLightHooks, value: f32) {
1055        self.constant_attenuation.set(value);
1056        host.remove_animation_for_property(Self::id_of_constant_attenuation_property().base());
1057    }
1058
1059    /// The composition property of `LinearAttenuation`.
1060    pub fn id_of_linear_attenuation_property() -> &'static CompositionPropertyOf<f32> {
1061        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
1062        PROPERTY.get_or_init(|| {
1063            CompositionProperty::register::<ServerSpotLightProps, f32>(
1064                "LinearAttenuation",
1065                "ServerSpotLight",
1066                |obj| props_of::<ServerSpotLightProps>(obj).linear_attenuation.get(),
1067                |obj, v| props_of::<ServerSpotLightProps>(obj).linear_attenuation.set(v),
1068                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).linear_attenuation.get())),
1069                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).linear_attenuation.set(v.cast_or_default::<f32>())),
1070            )
1071        })
1072    }
1073
1074    /// `LinearAttenuation` (animated).
1075    pub fn linear_attenuation(&self) -> f32 {
1076        self.linear_attenuation.get()
1077    }
1078
1079    pub fn set_linear_attenuation(&self, host: &dyn ServerSpotLightHooks, value: f32) {
1080        self.linear_attenuation.set(value);
1081        host.remove_animation_for_property(Self::id_of_linear_attenuation_property().base());
1082    }
1083
1084    /// The composition property of `QuadraticAttenuation`.
1085    pub fn id_of_quadratic_attenuation_property() -> &'static CompositionPropertyOf<f32> {
1086        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
1087        PROPERTY.get_or_init(|| {
1088            CompositionProperty::register::<ServerSpotLightProps, f32>(
1089                "QuadraticAttenuation",
1090                "ServerSpotLight",
1091                |obj| props_of::<ServerSpotLightProps>(obj).quadratic_attenuation.get(),
1092                |obj, v| props_of::<ServerSpotLightProps>(obj).quadratic_attenuation.set(v),
1093                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).quadratic_attenuation.get())),
1094                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).quadratic_attenuation.set(v.cast_or_default::<f32>())),
1095            )
1096        })
1097    }
1098
1099    /// `QuadraticAttenuation` (animated).
1100    pub fn quadratic_attenuation(&self) -> f32 {
1101        self.quadratic_attenuation.get()
1102    }
1103
1104    pub fn set_quadratic_attenuation(&self, host: &dyn ServerSpotLightHooks, value: f32) {
1105        self.quadratic_attenuation.set(value);
1106        host.remove_animation_for_property(Self::id_of_quadratic_attenuation_property().base());
1107    }
1108
1109    /// The composition property of `CoordinateSpace`.
1110    pub fn id_of_coordinate_space_property() -> &'static CompositionPropertyOf<Option<Rc<dyn IServerObject>>> {
1111        static PROPERTY: OnceLock<CompositionPropertyOf<Option<Rc<dyn IServerObject>>>> = OnceLock::new();
1112        PROPERTY.get_or_init(|| {
1113            CompositionProperty::register::<ServerSpotLightProps, Option<Rc<dyn IServerObject>>>(
1114                "CoordinateSpace",
1115                "ServerSpotLight",
1116                |obj| props_of::<ServerSpotLightProps>(obj).coordinate_space.borrow().clone(),
1117                |obj, v| *props_of::<ServerSpotLightProps>(obj).coordinate_space.borrow_mut() = v,
1118                None,
1119                None,
1120            )
1121        })
1122    }
1123
1124    /// `CoordinateSpace`.
1125    pub fn coordinate_space(&self) -> Option<Rc<dyn IServerObject>> {
1126        self.coordinate_space.borrow().clone()
1127    }
1128
1129    pub fn set_coordinate_space(&self, host: &dyn ServerSpotLightHooks, value: Option<Rc<dyn IServerObject>>) {
1130        let old_value = self.coordinate_space.borrow().clone();
1131        let mut changed = false;
1132        if old_value.differs(&value) {
1133            ServerSpotLightHooks::on_coordinate_space_changing(host);
1134            changed = true;
1135        }
1136        host.set_value(
1137            Self::id_of_coordinate_space_property().base(),
1138            ServerValueChange {
1139                equal: old_value.default_equals(&value),
1140                old_resource: old_value.as_server_render_resource(),
1141                new_resource: value.as_server_render_resource(),
1142                assign: &mut || *self.coordinate_space.borrow_mut() = value.clone(),
1143            },
1144        );
1145        if changed {
1146            ServerSpotLightHooks::on_coordinate_space_changed(host);
1147        }
1148    }
1149
1150    /// The composition property of `Direction`.
1151    pub fn id_of_direction_property() -> &'static CompositionPropertyOf<Vector3D> {
1152        static PROPERTY: OnceLock<CompositionPropertyOf<Vector3D>> = OnceLock::new();
1153        PROPERTY.get_or_init(|| {
1154            CompositionProperty::register::<ServerSpotLightProps, Vector3D>(
1155                "Direction",
1156                "ServerSpotLight",
1157                |obj| props_of::<ServerSpotLightProps>(obj).direction.get(),
1158                |obj, v| props_of::<ServerSpotLightProps>(obj).direction.set(v),
1159                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).direction.get())),
1160                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).direction.set(v.cast_or_default::<Vector3D>())),
1161            )
1162        })
1163    }
1164
1165    /// `Direction` (animated).
1166    pub fn direction(&self) -> Vector3D {
1167        self.direction.get()
1168    }
1169
1170    pub fn set_direction(&self, host: &dyn ServerSpotLightHooks, value: Vector3D) {
1171        self.direction.set(value);
1172        host.remove_animation_for_property(Self::id_of_direction_property().base());
1173    }
1174
1175    /// The composition property of `InnerConeAngle`.
1176    pub fn id_of_inner_cone_angle_property() -> &'static CompositionPropertyOf<f32> {
1177        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
1178        PROPERTY.get_or_init(|| {
1179            CompositionProperty::register::<ServerSpotLightProps, f32>(
1180                "InnerConeAngle",
1181                "ServerSpotLight",
1182                |obj| props_of::<ServerSpotLightProps>(obj).inner_cone_angle.get(),
1183                |obj, v| props_of::<ServerSpotLightProps>(obj).inner_cone_angle.set(v),
1184                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).inner_cone_angle.get())),
1185                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).inner_cone_angle.set(v.cast_or_default::<f32>())),
1186            )
1187        })
1188    }
1189
1190    /// `InnerConeAngle` (animated).
1191    pub fn inner_cone_angle(&self) -> f32 {
1192        self.inner_cone_angle.get()
1193    }
1194
1195    pub fn set_inner_cone_angle(&self, host: &dyn ServerSpotLightHooks, value: f32) {
1196        self.inner_cone_angle.set(value);
1197        host.remove_animation_for_property(Self::id_of_inner_cone_angle_property().base());
1198    }
1199
1200    /// The composition property of `InnerConeColor`.
1201    pub fn id_of_inner_cone_color_property() -> &'static CompositionPropertyOf<Color> {
1202        static PROPERTY: OnceLock<CompositionPropertyOf<Color>> = OnceLock::new();
1203        PROPERTY.get_or_init(|| {
1204            CompositionProperty::register::<ServerSpotLightProps, Color>(
1205                "InnerConeColor",
1206                "ServerSpotLight",
1207                |obj| props_of::<ServerSpotLightProps>(obj).inner_cone_color.get(),
1208                |obj, v| props_of::<ServerSpotLightProps>(obj).inner_cone_color.set(v),
1209                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).inner_cone_color.get())),
1210                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).inner_cone_color.set(v.cast_or_default::<Color>())),
1211            )
1212        })
1213    }
1214
1215    /// `InnerConeColor` (animated).
1216    pub fn inner_cone_color(&self) -> Color {
1217        self.inner_cone_color.get()
1218    }
1219
1220    pub fn set_inner_cone_color(&self, host: &dyn ServerSpotLightHooks, value: Color) {
1221        self.inner_cone_color.set(value);
1222        host.remove_animation_for_property(Self::id_of_inner_cone_color_property().base());
1223    }
1224
1225    /// The composition property of `InnerConeIntensity`.
1226    pub fn id_of_inner_cone_intensity_property() -> &'static CompositionPropertyOf<f32> {
1227        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
1228        PROPERTY.get_or_init(|| {
1229            CompositionProperty::register::<ServerSpotLightProps, f32>(
1230                "InnerConeIntensity",
1231                "ServerSpotLight",
1232                |obj| props_of::<ServerSpotLightProps>(obj).inner_cone_intensity.get(),
1233                |obj, v| props_of::<ServerSpotLightProps>(obj).inner_cone_intensity.set(v),
1234                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).inner_cone_intensity.get())),
1235                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).inner_cone_intensity.set(v.cast_or_default::<f32>())),
1236            )
1237        })
1238    }
1239
1240    /// `InnerConeIntensity` (animated).
1241    pub fn inner_cone_intensity(&self) -> f32 {
1242        self.inner_cone_intensity.get()
1243    }
1244
1245    pub fn set_inner_cone_intensity(&self, host: &dyn ServerSpotLightHooks, value: f32) {
1246        self.inner_cone_intensity.set(value);
1247        host.remove_animation_for_property(Self::id_of_inner_cone_intensity_property().base());
1248    }
1249
1250    /// The composition property of `OuterConeAngle`.
1251    pub fn id_of_outer_cone_angle_property() -> &'static CompositionPropertyOf<f32> {
1252        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
1253        PROPERTY.get_or_init(|| {
1254            CompositionProperty::register::<ServerSpotLightProps, f32>(
1255                "OuterConeAngle",
1256                "ServerSpotLight",
1257                |obj| props_of::<ServerSpotLightProps>(obj).outer_cone_angle.get(),
1258                |obj, v| props_of::<ServerSpotLightProps>(obj).outer_cone_angle.set(v),
1259                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).outer_cone_angle.get())),
1260                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).outer_cone_angle.set(v.cast_or_default::<f32>())),
1261            )
1262        })
1263    }
1264
1265    /// `OuterConeAngle` (animated).
1266    pub fn outer_cone_angle(&self) -> f32 {
1267        self.outer_cone_angle.get()
1268    }
1269
1270    pub fn set_outer_cone_angle(&self, host: &dyn ServerSpotLightHooks, value: f32) {
1271        self.outer_cone_angle.set(value);
1272        host.remove_animation_for_property(Self::id_of_outer_cone_angle_property().base());
1273    }
1274
1275    /// The composition property of `OuterConeColor`.
1276    pub fn id_of_outer_cone_color_property() -> &'static CompositionPropertyOf<Color> {
1277        static PROPERTY: OnceLock<CompositionPropertyOf<Color>> = OnceLock::new();
1278        PROPERTY.get_or_init(|| {
1279            CompositionProperty::register::<ServerSpotLightProps, Color>(
1280                "OuterConeColor",
1281                "ServerSpotLight",
1282                |obj| props_of::<ServerSpotLightProps>(obj).outer_cone_color.get(),
1283                |obj, v| props_of::<ServerSpotLightProps>(obj).outer_cone_color.set(v),
1284                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).outer_cone_color.get())),
1285                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).outer_cone_color.set(v.cast_or_default::<Color>())),
1286            )
1287        })
1288    }
1289
1290    /// `OuterConeColor` (animated).
1291    pub fn outer_cone_color(&self) -> Color {
1292        self.outer_cone_color.get()
1293    }
1294
1295    pub fn set_outer_cone_color(&self, host: &dyn ServerSpotLightHooks, value: Color) {
1296        self.outer_cone_color.set(value);
1297        host.remove_animation_for_property(Self::id_of_outer_cone_color_property().base());
1298    }
1299
1300    /// The composition property of `OuterConeIntensity`.
1301    pub fn id_of_outer_cone_intensity_property() -> &'static CompositionPropertyOf<f32> {
1302        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
1303        PROPERTY.get_or_init(|| {
1304            CompositionProperty::register::<ServerSpotLightProps, f32>(
1305                "OuterConeIntensity",
1306                "ServerSpotLight",
1307                |obj| props_of::<ServerSpotLightProps>(obj).outer_cone_intensity.get(),
1308                |obj, v| props_of::<ServerSpotLightProps>(obj).outer_cone_intensity.set(v),
1309                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).outer_cone_intensity.get())),
1310                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).outer_cone_intensity.set(v.cast_or_default::<f32>())),
1311            )
1312        })
1313    }
1314
1315    /// `OuterConeIntensity` (animated).
1316    pub fn outer_cone_intensity(&self) -> f32 {
1317        self.outer_cone_intensity.get()
1318    }
1319
1320    pub fn set_outer_cone_intensity(&self, host: &dyn ServerSpotLightHooks, value: f32) {
1321        self.outer_cone_intensity.set(value);
1322        host.remove_animation_for_property(Self::id_of_outer_cone_intensity_property().base());
1323    }
1324
1325    /// The composition property of `Offset`.
1326    pub fn id_of_offset_property() -> &'static CompositionPropertyOf<Vector3D> {
1327        static PROPERTY: OnceLock<CompositionPropertyOf<Vector3D>> = OnceLock::new();
1328        PROPERTY.get_or_init(|| {
1329            CompositionProperty::register::<ServerSpotLightProps, Vector3D>(
1330                "Offset",
1331                "ServerSpotLight",
1332                |obj| props_of::<ServerSpotLightProps>(obj).offset.get(),
1333                |obj, v| props_of::<ServerSpotLightProps>(obj).offset.set(v),
1334                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).offset.get())),
1335                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).offset.set(v.cast_or_default::<Vector3D>())),
1336            )
1337        })
1338    }
1339
1340    /// `Offset` (animated).
1341    pub fn offset(&self) -> Vector3D {
1342        self.offset.get()
1343    }
1344
1345    pub fn set_offset(&self, host: &dyn ServerSpotLightHooks, value: Vector3D) {
1346        self.offset.set(value);
1347        host.remove_animation_for_property(Self::id_of_offset_property().base());
1348    }
1349
1350    /// The composition property of `MinAttenuationCutoff`.
1351    pub fn id_of_min_attenuation_cutoff_property() -> &'static CompositionPropertyOf<f32> {
1352        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
1353        PROPERTY.get_or_init(|| {
1354            CompositionProperty::register::<ServerSpotLightProps, f32>(
1355                "MinAttenuationCutoff",
1356                "ServerSpotLight",
1357                |obj| props_of::<ServerSpotLightProps>(obj).min_attenuation_cutoff.get(),
1358                |obj, v| props_of::<ServerSpotLightProps>(obj).min_attenuation_cutoff.set(v),
1359                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).min_attenuation_cutoff.get())),
1360                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).min_attenuation_cutoff.set(v.cast_or_default::<f32>())),
1361            )
1362        })
1363    }
1364
1365    /// `MinAttenuationCutoff` (animated).
1366    pub fn min_attenuation_cutoff(&self) -> f32 {
1367        self.min_attenuation_cutoff.get()
1368    }
1369
1370    pub fn set_min_attenuation_cutoff(&self, host: &dyn ServerSpotLightHooks, value: f32) {
1371        self.min_attenuation_cutoff.set(value);
1372        host.remove_animation_for_property(Self::id_of_min_attenuation_cutoff_property().base());
1373    }
1374
1375    /// The composition property of `MaxAttenuationCutoff`.
1376    pub fn id_of_max_attenuation_cutoff_property() -> &'static CompositionPropertyOf<f32> {
1377        static PROPERTY: OnceLock<CompositionPropertyOf<f32>> = OnceLock::new();
1378        PROPERTY.get_or_init(|| {
1379            CompositionProperty::register::<ServerSpotLightProps, f32>(
1380                "MaxAttenuationCutoff",
1381                "ServerSpotLight",
1382                |obj| props_of::<ServerSpotLightProps>(obj).max_attenuation_cutoff.get(),
1383                |obj, v| props_of::<ServerSpotLightProps>(obj).max_attenuation_cutoff.set(v),
1384                Some(|obj| ExpressionVariant::create(props_of::<ServerSpotLightProps>(obj).max_attenuation_cutoff.get())),
1385                Some(|obj, v: ExpressionVariant| props_of::<ServerSpotLightProps>(obj).max_attenuation_cutoff.set(v.cast_or_default::<f32>())),
1386            )
1387        })
1388    }
1389
1390    /// `MaxAttenuationCutoff` (animated).
1391    pub fn max_attenuation_cutoff(&self) -> f32 {
1392        self.max_attenuation_cutoff.get()
1393    }
1394
1395    pub fn set_max_attenuation_cutoff(&self, host: &dyn ServerSpotLightHooks, value: f32) {
1396        self.max_attenuation_cutoff.set(value);
1397        host.remove_animation_for_property(Self::id_of_max_attenuation_cutoff_property().base());
1398    }
1399
1400    /// Reads the changes of the object: the base class first, then the
1401    /// changed-fields mask and the changed members of this class.
1402    pub fn deserialize_changes_core(
1403        &self,
1404        host: &dyn ServerSpotLightHooks,
1405        reader: &mut BatchStreamReader<'_>,
1406        committed_at: Duration,
1407    ) {
1408        let compositor = host.server_compositor();
1409        self.base.deserialize_changes_core(host, reader, committed_at);
1410        ServerSpotLightHooks::deserialize_changes_extra(host, reader);
1411        let changed = reader.read::<SpotLightChangedFields>();
1412        if changed.contains(SpotLightChangedFields::CONSTANT_ATTENUATION_ANIMATED) {
1413            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1414                panic!("a batch starts an animation of `ConstantAttenuation` without an animation instance");
1415            };
1416            host.set_animated_value(
1417                Self::id_of_constant_attenuation_property().base(),
1418                ExpressionVariant::create(self.constant_attenuation.get()),
1419                committed_at,
1420                animation,
1421            );
1422        } else if changed.contains(SpotLightChangedFields::CONSTANT_ATTENUATION) {
1423            let value = reader.read::<f32>();
1424            self.set_constant_attenuation(host, value);
1425        }
1426        if changed.contains(SpotLightChangedFields::LINEAR_ATTENUATION_ANIMATED) {
1427            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1428                panic!("a batch starts an animation of `LinearAttenuation` without an animation instance");
1429            };
1430            host.set_animated_value(
1431                Self::id_of_linear_attenuation_property().base(),
1432                ExpressionVariant::create(self.linear_attenuation.get()),
1433                committed_at,
1434                animation,
1435            );
1436        } else if changed.contains(SpotLightChangedFields::LINEAR_ATTENUATION) {
1437            let value = reader.read::<f32>();
1438            self.set_linear_attenuation(host, value);
1439        }
1440        if changed.contains(SpotLightChangedFields::QUADRATIC_ATTENUATION_ANIMATED) {
1441            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1442                panic!("a batch starts an animation of `QuadraticAttenuation` without an animation instance");
1443            };
1444            host.set_animated_value(
1445                Self::id_of_quadratic_attenuation_property().base(),
1446                ExpressionVariant::create(self.quadratic_attenuation.get()),
1447                committed_at,
1448                animation,
1449            );
1450        } else if changed.contains(SpotLightChangedFields::QUADRATIC_ATTENUATION) {
1451            let value = reader.read::<f32>();
1452            self.set_quadratic_attenuation(host, value);
1453        }
1454        if changed.contains(SpotLightChangedFields::COORDINATE_SPACE) {
1455            let value = read_server_object(reader, compositor.as_ref());
1456            self.set_coordinate_space(host, value);
1457        }
1458        if changed.contains(SpotLightChangedFields::DIRECTION_ANIMATED) {
1459            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1460                panic!("a batch starts an animation of `Direction` without an animation instance");
1461            };
1462            host.set_animated_value(
1463                Self::id_of_direction_property().base(),
1464                ExpressionVariant::create(self.direction.get()),
1465                committed_at,
1466                animation,
1467            );
1468        } else if changed.contains(SpotLightChangedFields::DIRECTION) {
1469            let value = reader.read::<Vector3D>();
1470            self.set_direction(host, value);
1471        }
1472        if changed.contains(SpotLightChangedFields::INNER_CONE_ANGLE_ANIMATED) {
1473            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1474                panic!("a batch starts an animation of `InnerConeAngle` without an animation instance");
1475            };
1476            host.set_animated_value(
1477                Self::id_of_inner_cone_angle_property().base(),
1478                ExpressionVariant::create(self.inner_cone_angle.get()),
1479                committed_at,
1480                animation,
1481            );
1482        } else if changed.contains(SpotLightChangedFields::INNER_CONE_ANGLE) {
1483            let value = reader.read::<f32>();
1484            self.set_inner_cone_angle(host, value);
1485        }
1486        if changed.contains(SpotLightChangedFields::INNER_CONE_COLOR_ANIMATED) {
1487            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1488                panic!("a batch starts an animation of `InnerConeColor` without an animation instance");
1489            };
1490            host.set_animated_value(
1491                Self::id_of_inner_cone_color_property().base(),
1492                ExpressionVariant::create(self.inner_cone_color.get()),
1493                committed_at,
1494                animation,
1495            );
1496        } else if changed.contains(SpotLightChangedFields::INNER_CONE_COLOR) {
1497            let value = reader.read::<Color>();
1498            self.set_inner_cone_color(host, value);
1499        }
1500        if changed.contains(SpotLightChangedFields::INNER_CONE_INTENSITY_ANIMATED) {
1501            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1502                panic!("a batch starts an animation of `InnerConeIntensity` without an animation instance");
1503            };
1504            host.set_animated_value(
1505                Self::id_of_inner_cone_intensity_property().base(),
1506                ExpressionVariant::create(self.inner_cone_intensity.get()),
1507                committed_at,
1508                animation,
1509            );
1510        } else if changed.contains(SpotLightChangedFields::INNER_CONE_INTENSITY) {
1511            let value = reader.read::<f32>();
1512            self.set_inner_cone_intensity(host, value);
1513        }
1514        if changed.contains(SpotLightChangedFields::OUTER_CONE_ANGLE_ANIMATED) {
1515            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1516                panic!("a batch starts an animation of `OuterConeAngle` without an animation instance");
1517            };
1518            host.set_animated_value(
1519                Self::id_of_outer_cone_angle_property().base(),
1520                ExpressionVariant::create(self.outer_cone_angle.get()),
1521                committed_at,
1522                animation,
1523            );
1524        } else if changed.contains(SpotLightChangedFields::OUTER_CONE_ANGLE) {
1525            let value = reader.read::<f32>();
1526            self.set_outer_cone_angle(host, value);
1527        }
1528        if changed.contains(SpotLightChangedFields::OUTER_CONE_COLOR_ANIMATED) {
1529            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1530                panic!("a batch starts an animation of `OuterConeColor` without an animation instance");
1531            };
1532            host.set_animated_value(
1533                Self::id_of_outer_cone_color_property().base(),
1534                ExpressionVariant::create(self.outer_cone_color.get()),
1535                committed_at,
1536                animation,
1537            );
1538        } else if changed.contains(SpotLightChangedFields::OUTER_CONE_COLOR) {
1539            let value = reader.read::<Color>();
1540            self.set_outer_cone_color(host, value);
1541        }
1542        if changed.contains(SpotLightChangedFields::OUTER_CONE_INTENSITY_ANIMATED) {
1543            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1544                panic!("a batch starts an animation of `OuterConeIntensity` without an animation instance");
1545            };
1546            host.set_animated_value(
1547                Self::id_of_outer_cone_intensity_property().base(),
1548                ExpressionVariant::create(self.outer_cone_intensity.get()),
1549                committed_at,
1550                animation,
1551            );
1552        } else if changed.contains(SpotLightChangedFields::OUTER_CONE_INTENSITY) {
1553            let value = reader.read::<f32>();
1554            self.set_outer_cone_intensity(host, value);
1555        }
1556        if changed.contains(SpotLightChangedFields::OFFSET_ANIMATED) {
1557            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1558                panic!("a batch starts an animation of `Offset` without an animation instance");
1559            };
1560            host.set_animated_value(
1561                Self::id_of_offset_property().base(),
1562                ExpressionVariant::create(self.offset.get()),
1563                committed_at,
1564                animation,
1565            );
1566        } else if changed.contains(SpotLightChangedFields::OFFSET) {
1567            let value = reader.read::<Vector3D>();
1568            self.set_offset(host, value);
1569        }
1570        if changed.contains(SpotLightChangedFields::MIN_ATTENUATION_CUTOFF_ANIMATED) {
1571            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1572                panic!("a batch starts an animation of `MinAttenuationCutoff` without an animation instance");
1573            };
1574            host.set_animated_value(
1575                Self::id_of_min_attenuation_cutoff_property().base(),
1576                ExpressionVariant::create(self.min_attenuation_cutoff.get()),
1577                committed_at,
1578                animation,
1579            );
1580        } else if changed.contains(SpotLightChangedFields::MIN_ATTENUATION_CUTOFF) {
1581            let value = reader.read::<f32>();
1582            self.set_min_attenuation_cutoff(host, value);
1583        }
1584        if changed.contains(SpotLightChangedFields::MAX_ATTENUATION_CUTOFF_ANIMATED) {
1585            let Some(animation) = reader.read_value::<AnimationInstanceFactory>().map(AnimationInstanceFactory::create) else {
1586                panic!("a batch starts an animation of `MaxAttenuationCutoff` without an animation instance");
1587            };
1588            host.set_animated_value(
1589                Self::id_of_max_attenuation_cutoff_property().base(),
1590                ExpressionVariant::create(self.max_attenuation_cutoff.get()),
1591                committed_at,
1592                animation,
1593            );
1594        } else if changed.contains(SpotLightChangedFields::MAX_ATTENUATION_CUTOFF) {
1595            let value = reader.read::<f32>();
1596            self.set_max_attenuation_cutoff(host, value);
1597        }
1598        ServerSpotLightHooks::on_fields_deserialized(host, changed);
1599    }
1600
1601    /// The composition property with the given name, among the properties
1602    /// of this class and of its generated bases that have a variant form.
1603    pub fn get_composition_property(name: &str) -> Option<&'static CompositionProperty> {
1604        if name == "ConstantAttenuation" {
1605            return Some(Self::id_of_constant_attenuation_property().base());
1606        }
1607        if name == "LinearAttenuation" {
1608            return Some(Self::id_of_linear_attenuation_property().base());
1609        }
1610        if name == "QuadraticAttenuation" {
1611            return Some(Self::id_of_quadratic_attenuation_property().base());
1612        }
1613        if name == "Direction" {
1614            return Some(Self::id_of_direction_property().base());
1615        }
1616        if name == "InnerConeAngle" {
1617            return Some(Self::id_of_inner_cone_angle_property().base());
1618        }
1619        if name == "InnerConeColor" {
1620            return Some(Self::id_of_inner_cone_color_property().base());
1621        }
1622        if name == "InnerConeIntensity" {
1623            return Some(Self::id_of_inner_cone_intensity_property().base());
1624        }
1625        if name == "OuterConeAngle" {
1626            return Some(Self::id_of_outer_cone_angle_property().base());
1627        }
1628        if name == "OuterConeColor" {
1629            return Some(Self::id_of_outer_cone_color_property().base());
1630        }
1631        if name == "OuterConeIntensity" {
1632            return Some(Self::id_of_outer_cone_intensity_property().base());
1633        }
1634        if name == "Offset" {
1635            return Some(Self::id_of_offset_property().base());
1636        }
1637        if name == "MinAttenuationCutoff" {
1638            return Some(Self::id_of_min_attenuation_cutoff_property().base());
1639        }
1640        if name == "MaxAttenuationCutoff" {
1641            return Some(Self::id_of_max_attenuation_cutoff_property().base());
1642        }
1643        ServerCompositionLightProps::get_composition_property(name)
1644    }
1645}