ferroui_base/rendering/composition/
element_composition_preview.rs1use super::animations::ICompositionAnimationBase;
2use super::{
3 CompositionBatchTypes, CompositionVisual, Compositor, ICompositionObjectAnimations, ICompositionSurface,
4 LoadedImageSurface,
5};
6use crate::media::imaging::RenderTargetBitmap;
7use crate::platform::IPlatformRenderInterface;
8use crate::reactive::IDisposable;
9use crate::{FerroLocator, LocatorExtensions, PixelSize, Vector, Vector3D, Visual, VisualTreeAttachmentEventArgs, WeakRef};
10use std::cell::RefCell;
11use std::rc::Rc;
12
13crate::ferro_thread_local! {
14 static IMPLICIT: RefCell<Vec<Rc<ImplicitEntry>>> = const { RefCell::new(Vec::new()) };
18}
19
20pub struct ElementComposition;
22
23impl ElementComposition {
24 pub fn get_element_visual(visual: &Visual) -> Option<Rc<CompositionVisual>> {
27 visual.composition_visual().map(|v| (*v).clone())
28 }
29
30 pub fn set_element_child_visual(visual: &Visual, composition_visual: Option<Rc<CompositionVisual>>) {
33 if let (Some(child), Some(own)) = (&composition_visual, visual.composition_visual()) {
34 if !Rc::ptr_eq(child.compositor(), own.compositor()) {
35 panic!("Composition visuals belong to different compositor instances");
36 }
37 }
38
39 if let Some(old_child) = visual.child_composition_visual() {
45 if !composition_visual.as_ref().is_some_and(|child| Rc::ptr_eq(child, &old_child)) {
46 if let Some(own) = visual.composition_visual() {
47 own.children().remove(&old_child);
48 }
49 }
50 }
51
52 visual.set_child_composition_visual(composition_visual);
53 if let Some(source) = visual.presentation_source() {
54 source.renderer().recalculate_children(visual);
55 }
56 }
57
58 pub fn get_element_child_visual(visual: &Visual) -> Option<Rc<CompositionVisual>> {
61 visual.child_composition_visual()
62 }
63
64 pub fn set_implicit_show_animation(visual: &Visual, animation: Option<Rc<dyn ICompositionAnimationBase>>) {
68 ImplicitEntry::update(visual, |entry| *entry.show.borrow_mut() = animation);
69 }
70
71 pub fn get_implicit_show_animation(visual: &Visual) -> Option<Rc<dyn ICompositionAnimationBase>> {
73 ImplicitEntry::find(visual).and_then(|entry| entry.show.borrow().clone())
74 }
75
76 pub fn set_implicit_hide_animation(visual: &Visual, animation: Option<Rc<dyn ICompositionAnimationBase>>) {
85 ImplicitEntry::update(visual, |entry| *entry.hide.borrow_mut() = animation);
86 }
87
88 pub fn get_implicit_hide_animation(visual: &Visual) -> Option<Rc<dyn ICompositionAnimationBase>> {
90 ImplicitEntry::find(visual).and_then(|entry| entry.hide.borrow().clone())
91 }
92
93 pub fn set_is_translation_enabled(visual: &Visual, value: bool) {
97 ImplicitEntry::update(visual, |entry| entry.translation_enabled.set(value));
98 }
99
100 pub fn is_translation_enabled(visual: &Visual) -> bool {
103 ImplicitEntry::find(visual).is_some_and(|entry| entry.translation_enabled.get())
104 }
105}
106
107struct ImplicitEntry {
109 visual: WeakRef<Visual>,
110 show: RefCell<Option<Rc<dyn ICompositionAnimationBase>>>,
111 hide: RefCell<Option<Rc<dyn ICompositionAnimationBase>>>,
112 translation_enabled: std::cell::Cell<bool>,
113 hiding: RefCell<Option<Rc<dyn IDisposable>>>,
116 subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
117}
118
119impl ImplicitEntry {
120 fn find(visual: &Visual) -> Option<Rc<ImplicitEntry>> {
121 IMPLICIT.with(|entries| {
122 entries
123 .borrow()
124 .iter()
125 .find(|entry| entry.visual.upgrade().is_some_and(|v| std::ptr::eq(&*v, visual)))
126 .cloned()
127 })
128 }
129
130 fn update(visual: &Visual, f: impl FnOnce(&ImplicitEntry)) {
133 IMPLICIT.with(|entries| {
134 entries.borrow_mut().retain(|entry| {
135 let alive = entry.visual.upgrade().is_some();
136 if !alive {
137 for subscription in entry.subscriptions.borrow_mut().drain(..) {
138 subscription.dispose();
139 }
140 }
141 alive
142 })
143 });
144 let entry = match Self::find(visual) {
145 Some(entry) => entry,
146 None => {
147 let entry = Rc::new(ImplicitEntry {
148 visual: visual.to_ref().downgrade(),
149 show: RefCell::new(None),
150 hide: RefCell::new(None),
151 translation_enabled: std::cell::Cell::new(false),
152 hiding: RefCell::new(None),
153 subscriptions: RefCell::new(Vec::new()),
154 });
155 entry.subscribe(visual);
156 IMPLICIT.with(|entries| entries.borrow_mut().push(entry.clone()));
157 entry
158 }
159 };
160 f(&entry);
161 }
162
163 fn subscribe(self: &Rc<Self>, visual: &Visual) {
164 let weak = Rc::downgrade(self);
165 let changed = visual.property_changed(move |e| {
166 if e.property() != Visual::is_visible_property().as_property() {
167 return;
168 }
169 let Some(entry) = weak.upgrade() else { return };
170 if e.get_new_value::<bool>() {
171 entry.on_shown();
172 } else {
173 entry.on_hidden();
174 }
175 });
176 let weak = Rc::downgrade(self);
177 let attached = visual.attached_to_visual_tree(move |_| {
178 if let Some(entry) = weak.upgrade() {
179 entry.on_shown();
180 }
181 });
182 let weak = Rc::downgrade(self);
183 let detached = visual.detached_from_visual_tree(move |e| {
184 if let Some(entry) = weak.upgrade() {
185 entry.on_removed(e);
186 }
187 });
188 self.subscriptions.borrow_mut().extend([changed, attached, detached]);
189 }
190
191 fn compositor(visual: &Visual) -> Option<Rc<Compositor>> {
193 visual.presentation_source().and_then(|source| source.renderer().compositor())
194 }
195
196 fn end_hiding(&self, visual: &Visual) {
198 if let Some(subscription) = self.hiding.borrow_mut().take() {
199 subscription.dispose();
200 visual.remove_composition_show_request();
201 }
202 }
203
204 fn on_shown(self: &Rc<Self>) {
207 let Some(visual) = self.visual.upgrade() else { return };
208 self.end_hiding(&visual);
209 if self.show.borrow().is_none() || !visual.is_visible() {
210 return;
211 }
212 let Some(compositor) = Self::compositor(&visual) else { return };
213 let weak = Rc::downgrade(self);
214 compositor.request_composition_update(move || {
215 let Some(entry) = weak.upgrade() else { return };
216 let Some(visual) = entry.visual.upgrade() else { return };
217 let (Some(animation), Some(composition_visual)) = (entry.show.borrow().clone(), visual.composition_visual())
218 else {
219 return;
220 };
221 composition_visual.start_animation_group(&*animation);
222 });
223 }
224
225 fn on_hidden(self: &Rc<Self>) {
228 let Some(visual) = self.visual.upgrade() else { return };
229 let Some(animation) = self.hide.borrow().clone() else { return };
230 let Some(composition_visual) = visual.composition_visual() else { return };
231 self.end_hiding(&visual);
232 let compositor = composition_visual.compositor().clone();
233 visual.add_composition_show_request();
234 let batch = compositor.create_scoped_batch(CompositionBatchTypes::ANIMATION);
235 composition_visual.start_animation_group(&*animation);
236 batch.end();
237 let weak = Rc::downgrade(self);
238 let subscription = batch.completed(move |_| {
239 let Some(entry) = weak.upgrade() else { return };
240 let Some(visual) = entry.visual.upgrade() else { return };
241 entry.end_hiding(&visual);
242 });
243 *self.hiding.borrow_mut() = Some(subscription);
244 }
245
246 fn on_removed(self: &Rc<Self>, e: &VisualTreeAttachmentEventArgs) {
251 let Some(visual) = self.visual.upgrade() else { return };
252 self.end_hiding(&visual);
253 let Some(animation) = self.hide.borrow().clone() else { return };
254 let Some(parent) = e.attachment_point().cloned() else { return };
255 if visual.visual_parent().is_some_and(|own| !std::ptr::eq(&*own, &*parent)) {
256 return;
257 }
258 let (Some(parent_visual), Some(source)) = (parent.composition_visual(), parent.presentation_source()) else {
259 return;
260 };
261 if !visual.is_visible() || FerroLocator::current().get_service::<dyn IPlatformRenderInterface>().is_none() {
262 return;
263 }
264 let bounds = visual.bounds();
265 let scaling = source.render_scaling();
266 let pixel_size = PixelSize::new((bounds.width * scaling).ceil() as i32, (bounds.height * scaling).ceil() as i32);
267 if pixel_size.width <= 0 || pixel_size.height <= 0 {
268 return;
269 }
270 let image = RenderTargetBitmap::with_dpi(pixel_size, Vector::new(96.0 * scaling, 96.0 * scaling));
271 image.render(&visual.to_ref());
272
273 let compositor = parent_visual.compositor().clone();
274 let surface = LoadedImageSurface::from_bitmap(&compositor, &image);
275 let brush = compositor.create_surface_brush_with_surface(surface as Rc<dyn ICompositionSurface>);
276 let sprite = compositor.create_sprite_visual();
277 sprite.set_brush(Some((*brush).clone()));
278 sprite.set_size(Vector::new(bounds.width, bounds.height));
279 sprite.set_offset(Vector3D::new(bounds.x, bounds.y, 0.0));
280 sprite.set_opacity(visual.opacity() as f32);
281 let ghost: Rc<CompositionVisual> = (*sprite).clone();
282 parent.add_composition_unloading_visual(ghost.clone());
283
284 let batch = compositor.create_scoped_batch(CompositionBatchTypes::ANIMATION);
285 ghost.start_animation_group(&*animation);
286 batch.end();
287 let weak_parent = parent.downgrade();
288 let subscription: Rc<RefCell<Option<Rc<dyn IDisposable>>>> = Rc::new(RefCell::new(None));
289 let held = subscription.clone();
290 let completed = batch.completed(move |_| {
291 if let Some(parent) = weak_parent.upgrade() {
292 parent.remove_composition_unloading_visual(&ghost);
293 }
294 if let Some(subscription) = held.borrow_mut().take() {
295 subscription.dispose();
296 }
297 });
298 *subscription.borrow_mut() = Some(completed);
299 }
300}