Skip to main content

ferroui_base/rendering/composition/
composition_draw_list_visual.rs

1use super::drawing::CompositionRenderData;
2use super::generated::{
3    CompositionContainerVisualProps, CompositionExperimentalAcrylicVisualHooks,
4    CompositionExperimentalAcrylicVisualProps,
5};
6use super::server::{IServerVisualContent, ServerCompositionDrawListVisual, ServerCompositionExperimentalAcrylicVisual};
7use super::transport::{BatchStreamWriter, IRegisterForSerialization};
8use super::visual::CompositionVisualKind;
9use super::{CompositionVisual, Compositor};
10use crate::media::{Geometry, IntersectionResult};
11use crate::{Point, Ref, Visual, WeakRef};
12use std::cell::{Cell, RefCell};
13use std::ops::Deref;
14use std::rc::Rc;
15
16/// What a class derived from the draw list visual outside of this module
17/// (the custom composition visual of a control) adds to its UI-thread side:
18/// the serialization of its own changes.
19pub trait ICompositionDrawListVisualExtension: 'static {
20    /// `SerializeChangesCore` of the derived class: called after the draw
21    /// list visual has written its own changes. What is written here is read
22    /// by the server content of the visual, after the changes of the draw
23    /// list visual.
24    fn serialize_changes_core(&self, writer: &mut BatchStreamWriter<'_>);
25}
26
27/// What a draw list visual adds to a visual: the visual of the tree it
28/// renders, and what that visual drew.
29pub(crate) struct DrawListData {
30    props: CompositionContainerVisualProps,
31    /// `None` for a draw list visual of no visual of the tree, which only
32    /// the benchmarks create (`composition::testing`), as upstream's
33    /// benchmark passes `null` for the visual.
34    visual: Option<WeakRef<Visual>>,
35    draw_list_changed: Cell<bool>,
36    draw_list: RefCell<Option<Rc<CompositionRenderData>>>,
37    /// The properties of the acrylic visual, when the visual is one.
38    pub(super) acrylic: Option<CompositionExperimentalAcrylicVisualProps>,
39    /// The part of a derived class defined outside of this module.
40    extension: Option<Rc<dyn ICompositionDrawListVisualExtension>>,
41}
42
43impl DrawListData {
44    pub(super) fn props(&self) -> &CompositionContainerVisualProps {
45        &self.props
46    }
47
48    pub(super) fn initialize_defaults(&self, host: &CompositionVisual) {
49        self.props.initialize_defaults(host);
50        if let Some(acrylic) = &self.acrylic {
51            acrylic.initialize_own_defaults(host);
52        }
53    }
54
55    fn serialize_draw_list(&self, host: &CompositionVisual, writer: &mut BatchStreamWriter<'_>) {
56        writer.write(u8::from(self.draw_list_changed.get()));
57        if self.draw_list_changed.get() {
58            writer.write_server_object(self.draw_list.borrow().as_ref().map(|draw_list| draw_list.server()));
59            self.draw_list_changed.set(false);
60        }
61        self.props.serialize_changes_core(host, writer);
62    }
63
64    pub(super) fn serialize_changes_core(&self, host: &CompositionVisual, writer: &mut BatchStreamWriter<'_>) {
65        match &self.acrylic {
66            // The acrylic properties follow those of the draw list visual,
67            // which the generated code writes through the base hook.
68            Some(acrylic) => acrylic.serialize_changes_core(host, writer),
69            None => {
70                self.serialize_draw_list(host, writer);
71                if let Some(extension) = &self.extension {
72                    extension.serialize_changes_core(writer);
73                }
74            }
75        }
76    }
77
78    pub(super) fn hit_test(&self, pt: Point) -> bool {
79        let custom = self.visual.as_ref().and_then(WeakRef::upgrade).and_then(|visual| visual.custom_hit_test(pt));
80        let draw_list = self.draw_list.borrow().clone();
81        if draw_list.is_none() && custom.is_none() {
82            return false;
83        }
84        if let Some(custom) = custom {
85            return custom;
86        }
87        draw_list.is_some_and(|draw_list| draw_list.hit_test(pt))
88    }
89
90    pub(super) fn hit_test_geometry(&self, geometry: &Ref<Geometry>) -> IntersectionResult {
91        let custom =
92            self.visual.as_ref().and_then(WeakRef::upgrade).and_then(|visual| visual.custom_hit_test_geometry(geometry));
93        let draw_list = self.draw_list.borrow().clone();
94        if draw_list.is_none() && custom.is_none() {
95            return IntersectionResult::Empty;
96        }
97        if let Some(custom) = custom {
98            return custom;
99        }
100        draw_list.map_or(IntersectionResult::Empty, |draw_list| draw_list.hit_test_geometry(geometry))
101    }
102}
103
104impl CompositionExperimentalAcrylicVisualHooks for CompositionVisual {
105    fn base_serialize_changes_core(&self, writer: &mut BatchStreamWriter<'_>) {
106        if let CompositionVisualKind::DrawList(data) = &self.kind {
107            data.serialize_draw_list(self, writer);
108        }
109    }
110}
111
112/// The composition visual of a visual of the visual tree: draws what the
113/// visual rendered.
114#[derive(Clone)]
115pub struct CompositionDrawListVisual(Rc<CompositionVisual>);
116
117impl Deref for CompositionDrawListVisual {
118    type Target = Rc<CompositionVisual>;
119
120    fn deref(&self) -> &Rc<CompositionVisual> {
121        &self.0
122    }
123}
124
125impl CompositionDrawListVisual {
126    /// Creates the composition visual of `visual`.
127    pub fn new(compositor: &Rc<Compositor>, visual: &Visual) -> CompositionDrawListVisual {
128        Self::create(compositor, visual, false)
129    }
130
131    /// Creates the composition visual of `visual` for a class derived from
132    /// the draw list visual: `server_content` creates the server-side
133    /// counterpart (which wraps a `ServerCompositionDrawListVisual` and
134    /// forwards to it), `extension` serializes what the derived class adds.
135    pub fn with_extension(
136        compositor: &Rc<Compositor>,
137        visual: &Visual,
138        server_content: impl FnOnce() -> Box<dyn IServerVisualContent> + Send + 'static,
139        extension: Rc<dyn ICompositionDrawListVisualExtension>,
140    ) -> CompositionDrawListVisual {
141        Self::create_core(compositor, Some(visual), false, Some(extension), server_content)
142    }
143
144    pub(super) fn create(compositor: &Rc<Compositor>, visual: &Visual, acrylic: bool) -> CompositionDrawListVisual {
145        Self::create_core(compositor, Some(visual), acrylic, None, move || {
146            if acrylic {
147                Box::new(ServerCompositionExperimentalAcrylicVisual::new())
148            } else {
149                Box::new(ServerCompositionDrawListVisual::new())
150            }
151        })
152    }
153
154    /// `new CompositionDrawListVisual(compositor, new
155    /// ServerCompositionDrawListVisual(compositor.Server, null), null)`: a
156    /// draw list visual of no visual of the tree, which draws its draw list
157    /// and has no custom hit test. For the benchmarks
158    /// (`composition::testing`).
159    #[cfg(any(test, feature = "testing"))]
160    pub(crate) fn without_visual(compositor: &Rc<Compositor>) -> CompositionDrawListVisual {
161        Self::create_core(compositor, None, false, None, || Box::new(ServerCompositionDrawListVisual::new()))
162    }
163
164    fn create_core(
165        compositor: &Rc<Compositor>,
166        visual: Option<&Visual>,
167        acrylic: bool,
168        extension: Option<Rc<dyn ICompositionDrawListVisualExtension>>,
169        server_content: impl FnOnce() -> Box<dyn IServerVisualContent> + Send + 'static,
170    ) -> CompositionDrawListVisual {
171        let data = DrawListData {
172            props: CompositionContainerVisualProps::new(),
173            visual: visual.map(|visual| visual.to_ref().downgrade()),
174            draw_list_changed: Cell::new(false),
175            draw_list: RefCell::new(None),
176            acrylic: acrylic.then(CompositionExperimentalAcrylicVisualProps::new),
177            extension,
178        };
179        let result = CompositionVisual::create(compositor, CompositionVisualKind::DrawList(data), server_content);
180        // A visual with a custom hit test decides for itself whether it is
181        // hit, whatever its bounds are.
182        let has_custom_hit_test = visual.is_some_and(|visual| visual.custom_hit_test(Point::default()).is_some());
183        result.custom_hit_test_count_in_sub_tree.set(i32::from(has_custom_hit_test));
184        CompositionDrawListVisual(result)
185    }
186
187    /// The handle of `visual` as a draw list visual, if it is one.
188    pub fn from_visual(visual: &Rc<CompositionVisual>) -> Option<CompositionDrawListVisual> {
189        matches!(visual.kind, CompositionVisualKind::DrawList(_)).then(|| CompositionDrawListVisual(visual.clone()))
190    }
191
192    fn data(&self) -> &DrawListData {
193        match &self.0.kind {
194            CompositionVisualKind::DrawList(data) => data,
195            _ => unreachable!("a draw list visual handle wraps a draw list visual"),
196        }
197    }
198
199    /// The visual of the visual tree this composition visual renders, if
200    /// it is alive.
201    pub fn visual(&self) -> Option<Ref<Visual>> {
202        self.data().visual.as_ref().and_then(WeakRef::upgrade)
203    }
204
205    pub fn draw_list(&self) -> Option<Rc<CompositionRenderData>> {
206        self.data().draw_list.borrow().clone()
207    }
208
209    pub fn set_draw_list(&self, value: Option<Rc<CompositionRenderData>>) {
210        let data = self.data();
211        // Nothing to do
212        if value.is_none() && data.draw_list.borrow().is_none() {
213            return;
214        }
215
216        let old = data.draw_list.replace(value);
217        if let Some(old) = old {
218            old.dispose();
219        }
220        data.draw_list_changed.set(true);
221        self.0.register_for_serialization();
222    }
223}