ferroui_base/rendering/composition/server/
server_custom_composition_visual.rs1use super::{
2 IServerClockItem, IServerVisualContent, ServerCompositionTarget, ServerCompositionVisual, ServerCompositor,
3 ServerVisualRenderContext,
4};
5use crate::logging::{LogArea, LogEventLevel, Logger};
6use crate::media::ImmediateDrawingContext;
7use crate::platform::LtrbRect;
8use crate::rendering::composition::server::CompositorDrawingContextProxy;
9use crate::rendering::composition::{CompositionCustomVisualHandler, ICompositionCustomVisualHandler};
10use std::any::Any;
11use std::cell::{Cell, RefCell};
12use std::panic::{catch_unwind, AssertUnwindSafe};
13use std::rc::{Rc, Weak};
14
15fn panic_message(payload: &(dyn Any + Send)) -> String {
16 if let Some(message) = payload.downcast_ref::<&str>() {
17 (*message).to_owned()
18 } else if let Some(message) = payload.downcast_ref::<String>() {
19 message.clone()
20 } else {
21 "a panic".to_owned()
22 }
23}
24
25fn guarded(handler: &Rc<dyn ICompositionCustomVisualHandler>, member: &str, f: impl FnOnce()) {
28 if let Err(error) = catch_unwind(AssertUnwindSafe(f)) {
29 if let Some(logger) = Logger::try_get(LogEventLevel::Error, LogArea::VISUAL) {
30 let message = panic_message(&*error);
31 logger.log_with_values(
32 Some(handler as &dyn Any),
33 &format!("Exception in {}.{member} {{Error}}", std::any::type_name_of_val(&**handler)),
34 &[&message],
35 );
36 }
37 }
38}
39
40struct CustomVisualClock {
43 visual: RefCell<Weak<ServerCompositionVisual>>,
44 handler: Rc<dyn ICompositionCustomVisualHandler>,
45 wants_next_animation_frame_after_tick: Cell<bool>,
46 this: Weak<CustomVisualClock>,
47}
48
49impl CustomVisualClock {
50 fn as_item(&self) -> Option<Rc<dyn IServerClockItem>> {
51 self.this.upgrade().map(|this| this as Rc<dyn IServerClockItem>)
52 }
53
54 fn compositor(&self) -> Option<Rc<ServerCompositor>> {
55 self.visual.borrow().upgrade().and_then(|visual| visual.compositor())
56 }
57
58 fn add_to_clock(&self) {
59 if let (Some(compositor), Some(item)) = (self.compositor(), self.as_item()) {
60 compositor.animations().add_to_clock(item);
61 }
62 }
63
64 fn remove_from_clock(&self) {
65 if let (Some(compositor), Some(item)) = (self.compositor(), self.as_item()) {
66 compositor.animations().remove_from_clock(&item);
67 }
68 }
69}
70
71impl IServerClockItem for CustomVisualClock {
72 fn on_tick(&self) {
73 self.wants_next_animation_frame_after_tick.set(false);
74 self.handler.on_animation_frame_update();
75 if !self.wants_next_animation_frame_after_tick.get() {
76 self.remove_from_clock();
77 }
78 }
79}
80
81pub struct ServerCompositionCustomVisual {
85 handler: Rc<dyn ICompositionCustomVisualHandler>,
86 clock: Rc<CustomVisualClock>,
87}
88
89impl ServerCompositionCustomVisual {
90 pub(crate) fn new(handler: Rc<dyn ICompositionCustomVisualHandler>) -> Self {
91 let clock = Rc::new_cyclic(|this: &Weak<CustomVisualClock>| CustomVisualClock {
92 visual: RefCell::new(Weak::new()),
93 handler: handler.clone(),
94 wants_next_animation_frame_after_tick: Cell::new(false),
95 this: this.clone(),
96 });
97 Self { handler, clock }
98 }
99
100 pub(crate) fn attach(visual: &Rc<ServerCompositionVisual>) {
104 let Some(content) = visual.content_as::<ServerCompositionCustomVisual>() else { return };
105 *content.clock.visual.borrow_mut() = Rc::downgrade(visual);
106 content.handler.handler_base().attach(Rc::downgrade(visual));
107 }
108
109 pub fn dispatch_messages(&self, messages: Vec<std::sync::Arc<dyn Any + Send + Sync>>) {
111 for message in messages {
112 guarded(&self.handler, "OnMessage", || self.handler.on_message(message));
113 }
114 }
115
116 pub(crate) fn handler_register_for_next_animation_frame_update(visual: &ServerCompositionVisual) {
117 let Some(content) = visual.content_as::<ServerCompositionCustomVisual>() else { return };
118 content.clock.wants_next_animation_frame_after_tick.set(true);
119 if visual.root().is_some() {
120 content.clock.add_to_clock();
121 }
122 }
123}
124
125impl IServerVisualContent for ServerCompositionCustomVisual {
126 fn compute_own_content_bounds(&self, _visual: &ServerCompositionVisual) -> Option<LtrbRect> {
127 Some(LtrbRect::from_rect(self.handler.get_render_bounds()))
128 }
129
130 fn on_attached_to_root(&self, _visual: &ServerCompositionVisual, _target: &Rc<ServerCompositionTarget>) {
131 if self.clock.wants_next_animation_frame_after_tick.get() {
132 self.clock.add_to_clock();
133 }
134 }
135
136 fn on_detached_from_root(&self, _visual: &ServerCompositionVisual, _target: &Rc<ServerCompositionTarget>) {
137 self.clock.remove_from_clock();
138 }
139
140 fn render_core(
141 &self,
142 _visual: &ServerCompositionVisual,
143 context: &mut ServerVisualRenderContext<'_>,
144 current_transformed_clip: LtrbRect,
145 ) {
146 let canvas = context.canvas();
147 let is_proxy = match canvas.as_any_mut().downcast_mut::<CompositorDrawingContextProxy>() {
148 Some(proxy) => {
149 proxy.set_auto_flush(true);
150 proxy.flush();
151 true
152 }
153 None => false,
154 };
155
156 {
157 let transform = canvas.transform();
158 let mut context = ImmediateDrawingContext::borrowed_with_transform(&mut *canvas, transform);
159 guarded(&self.handler, "OnRender", || {
160 CompositionCustomVisualHandler::render(&*self.handler, &mut context, current_transformed_clip.to_rect())
161 });
162 }
163
164 if is_proxy {
165 if let Some(proxy) = canvas.as_any_mut().downcast_mut::<CompositorDrawingContextProxy>() {
166 proxy.set_auto_flush(false);
167 }
168 }
169 }
170
171 fn as_any(&self) -> &dyn Any {
172 self
173 }
174}