Skip to main content

ferroui_base/rendering/composition/server/
server_custom_composition_visual.rs

1use 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
25/// Runs a member of the handler; a panic is logged as upstream logs the
26/// exception of a handler.
27fn 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
40/// The clock item of a custom visual: the server compositor holds clock
41/// items by handle, and the content of a visual is not one.
42struct 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
81/// The content of the server-side counterpart of a
82/// [`CompositionCustomVisual`](crate::rendering::composition::CompositionCustomVisual):
83/// a container visual that draws through its handler.
84pub 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    /// Attaches the handler to the visual that has this content (upstream
101    /// does it in the constructor of the visual): done when the visual is
102    /// created.
103    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    /// Dispatches the messages the UI thread sent to the handler.
110    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}