ferroui_base/media/
xaml_light.rs1use crate::collections::{FerroList, NotifyCollectionChangedEventArgs};
7use crate::media::{IBrush, XamlCompositionBrushBase};
8use crate::reactive::{Disposable, IDisposable};
9use crate::rendering::composition::{CompositionLight, CompositionVisual, DistantLight, PointLight, SpotLight};
10use crate::{ferro_class, FerroObject, FerroObjectImpl, Ref, Visual, WeakRef};
11use std::cell::RefCell;
12use std::rc::Rc;
13
14#[derive(Default)]
15struct XamlLightState {
16 composition_light: Option<Rc<CompositionLight>>,
18 host: Option<WeakRef<Visual>>,
20}
21
22#[repr(C)]
39pub struct XamlLight {
40 base: FerroObject,
41 state: RefCell<XamlLightState>,
42}
43
44ferro_class! {
45 XamlLight: FerroObject, virtuals XamlLightImpl: FerroObjectImpl {
46 fn get_id(this) -> String;
48
49 fn on_connected(this, new_element: &Visual);
52
53 fn on_disconnected(this, old_element: &Visual);
56 }
57}
58crate::ferro_class_info!(XamlLight {
59 markup: {
60 namespace: "FerroUI.Media",
61 },
62});
63
64crate::ferro_impl_classes!(XamlLight: FerroObjectImpl);
65
66impl XamlLightImpl for XamlLight {
67 fn get_id(_this: &Self) -> String {
68 String::new()
69 }
70
71 fn on_connected(_this: &Self, _new_element: &Visual) {}
72
73 fn on_disconnected(_this: &Self, _old_element: &Visual) {}
74}
75
76impl XamlLight {
77 pub fn construct() -> Self {
79 Self { base: FerroObject::construct(), state: RefCell::new(XamlLightState::default()) }
80 }
81
82 pub fn id(&self) -> String {
84 self.get_id()
85 }
86
87 pub fn composition_light(&self) -> Option<Rc<CompositionLight>> {
89 self.state.borrow().composition_light.clone()
90 }
91
92 pub fn set_composition_light(&self, light: Option<Rc<CompositionLight>>) {
97 let old = {
98 let mut state = self.state.borrow_mut();
99 let same = match (&state.composition_light, &light) {
100 (Some(a), Some(b)) => Rc::ptr_eq(a, b),
101 (None, None) => true,
102 _ => false,
103 };
104 if same {
105 return;
106 }
107 std::mem::replace(&mut state.composition_light, light)
108 };
109 if let Some(old) = old {
110 unbind(&old);
111 }
112 self.bind();
113 }
114
115 pub fn host(&self) -> Option<Ref<Visual>> {
117 self.state.borrow().host.as_ref().and_then(WeakRef::upgrade)
118 }
119
120 pub fn is_connected(&self) -> bool {
122 self.host().is_some()
123 }
124
125 fn connect(&self, host: &Visual) {
126 if self.host().is_some_and(|existing| std::ptr::eq::<Visual>(&*existing, host)) {
127 return;
128 }
129 if let Some(previous) = self.host() {
130 self.disconnect(&previous);
131 }
132 self.state.borrow_mut().host = Some(host.to_ref().downgrade());
133 self.on_connected(host);
134 self.bind();
135 }
136
137 fn disconnect(&self, host: &Visual) {
138 if !self.host().is_some_and(|existing| std::ptr::eq::<Visual>(&*existing, host)) {
139 return;
140 }
141 if let Some(light) = self.composition_light() {
142 unbind(&light);
143 }
144 self.on_disconnected(host);
145 self.state.borrow_mut().host = None;
146 }
147
148 fn bind(&self) {
152 let (Some(light), Some(host)) = (self.composition_light(), self.host()) else { return };
153 let Some(scope) = host.composition_visual().map(|visual| (*visual).clone()) else { return };
154 let id = self.id();
155 light.set_xaml_binding(Some(id.clone()), Some(scope.clone()));
156 set_coordinate_space(&light, Some(scope));
157 let mut stack = vec![host.clone()];
158 while let Some(visual) = stack.pop() {
159 if visual.light_target_ids().iter().any(|existing| *existing == id) {
160 if let Some(target) = visual.composition_visual() {
161 light.targets().add((*target).clone());
162 }
163 }
164 stack.extend(visual.visual_children().to_vec());
165 }
166 }
167
168 pub fn add_target_element(id: &str, element: &Visual) {
172 if element.add_light_target_id(id) {
173 offer_to_lights(element);
174 }
175 }
176
177 pub fn remove_target_element(id: &str, element: &Visual) {
179 if !element.remove_light_target_id(id) {
180 return;
181 }
182 let Some(target) = element.composition_visual().map(|visual| (*visual).clone()) else { return };
183 for light in lights_above(element) {
184 if light.id() == id {
185 if let Some(composition_light) = light.composition_light() {
186 composition_light.targets().remove(&target);
187 }
188 }
189 }
190 }
191
192 pub fn add_target_brush(id: &str, brush: &Rc<dyn IBrush>) {
197 if let Some(brush) = XamlCompositionBrushBase::of(&**brush) {
198 brush.add_light_target_id(id);
199 }
200 }
201
202 pub fn remove_target_brush(id: &str, brush: &Rc<dyn IBrush>) {
204 if let Some(brush) = XamlCompositionBrushBase::of(&**brush) {
205 brush.remove_light_target_id(id);
206 }
207 }
208}
209
210fn set_coordinate_space(light: &Rc<CompositionLight>, space: Option<Rc<CompositionVisual>>) {
211 if let Some(spot) = SpotLight::from_light(light) {
212 spot.set_coordinate_space(space);
213 } else if let Some(point) = PointLight::from_light(light) {
214 point.set_coordinate_space(space);
215 } else if let Some(distant) = DistantLight::from_light(light) {
216 distant.set_coordinate_space(space);
217 }
218}
219
220fn unbind(light: &Rc<CompositionLight>) {
221 light.set_xaml_binding(None, None);
222 set_coordinate_space(light, None);
223 light.targets().remove_all();
224}
225
226fn lights_above(element: &Visual) -> Vec<Ref<XamlLight>> {
228 let mut lights = Vec::new();
229 let mut current = Some(element.to_ref());
230 while let Some(visual) = current {
231 if visual.has_lights() {
232 lights.extend(visual.lights().to_vec().into_iter().filter(|light| light.is_connected()));
233 }
234 current = visual.visual_parent();
235 }
236 lights
237}
238
239fn offer_to_lights(element: &Visual) {
242 let Some(target) = element.composition_visual().map(|visual| (*visual).clone()) else { return };
243 let ids = element.light_target_ids();
244 for light in lights_above(element) {
245 if ids.iter().any(|id| *id == light.id()) {
246 if let Some(composition_light) = light.composition_light() {
247 composition_light.targets().add(target.clone());
248 }
249 }
250 }
251}
252
253pub(crate) fn track_lights(element: &Visual, lights: &FerroList<Ref<XamlLight>>) -> Vec<Rc<dyn IDisposable>> {
256 let connected: Rc<RefCell<Vec<Ref<XamlLight>>>> = Rc::new(RefCell::new(Vec::new()));
257 let sync = {
258 let weak = element.to_ref().downgrade();
259 let lights = lights.clone();
260 let connected = connected.clone();
261 Rc::new(move |attached: Option<bool>| {
264 let Some(element) = weak.upgrade() else { return };
265 let attached = attached.unwrap_or_else(|| element.is_attached_to_visual_tree());
266 let wanted = if attached { lights.to_vec() } else { Vec::new() };
267 let previous = std::mem::take(&mut *connected.borrow_mut());
268 for light in previous.iter().filter(|light| !wanted.contains(light)) {
269 light.disconnect(&element);
270 }
271 for light in &wanted {
272 light.connect(&element);
273 }
274 *connected.borrow_mut() = wanted;
275 })
276 };
277 let token = {
278 let sync = sync.clone();
279 lights.add_collection_changed(Rc::new(move |_: &NotifyCollectionChangedEventArgs<'_, Ref<XamlLight>>| sync(None)))
280 };
281 let collection = {
282 let lights = lights.clone();
283 Disposable::create(move || {
284 lights.remove_collection_changed(token);
285 })
286 };
287 let attached = {
288 let sync = sync.clone();
289 element.attached_to_visual_tree(move |_| sync(Some(true)))
290 };
291 let detached = {
292 let sync = sync.clone();
293 element.detached_from_visual_tree(move |_| sync(Some(false)))
294 };
295 sync(None);
296 vec![collection, attached, detached]
297}
298
299pub(crate) fn track_light_target(element: &Visual) -> Rc<dyn IDisposable> {
302 let weak = element.to_ref().downgrade();
303 element.attached_to_visual_tree(move |_| {
304 if let Some(element) = weak.upgrade() {
305 offer_to_lights(&element);
306 }
307 })
308}