Skip to main content

ferroui_controls/
experimental_acrylic_border.rs

1use crate::{Border, ControlImpl, Decorator, TopLevel, WindowTransparencyLevel};
2use ferroui_base::input::InputElementImpl;
3use ferroui_base::interactivity::InteractiveImpl;
4use ferroui_base::layout::{LayoutHelper, Layoutable, LayoutableImpl};
5use ferroui_base::media::{ExperimentalAcrylicMaterial, ImmutableExperimentalAcrylicMaterial};
6use ferroui_base::reactive::{IDisposable, ObservableExt};
7use ferroui_base::rendering::composition::{
8    CompositionDrawListVisual, CompositionExperimentalAcrylicVisual, Compositor,
9};
10use ferroui_base::{FerroObject, FerroObjectExtensions, FerroObjectImplExt, FerroPropertyChangedEventArgs};
11use std::cell::RefCell;
12use std::rc::Rc;
13use ferroui_base::{
14    ferro_class, ferro_impl_classes, ferro_property, instantiate, CornerRadius, FerroObjectImpl,
15    FerroProperty, Nullable, Ref, Size, StyledElementImpl, StyledProperty, Visual, VisualImpl, VisualImplExt,
16    VisualTreeAttachmentEventArgs,
17};
18
19/// A control which decorates a child with a border and an acrylic material
20/// background.
21///
22/// The material is drawn by the composition visual of the border, an
23/// acrylic visual that receives the material and the corner radius; the
24/// platform transparency compensation of the material follows the
25/// transparency level of the top level the border is in.
26#[repr(C)]
27pub struct ExperimentalAcrylicBorder {
28    base: Decorator,
29    subscription: RefCell<Option<Rc<dyn IDisposable>>>,
30    material_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
31}
32
33ferro_class!(ExperimentalAcrylicBorder: Decorator);
34ferroui_base::ferro_class_info!(ExperimentalAcrylicBorder { new: ExperimentalAcrylicBorder::new });
35ferro_impl_classes!(ExperimentalAcrylicBorder: StyledElementImpl, InteractiveImpl, InputElementImpl, ControlImpl);
36
37impl FerroObjectImpl for ExperimentalAcrylicBorder {
38    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
39        if change.property() == Self::material_property().as_property() {
40            this.update_material_subscription();
41        }
42        if change.property() == Self::corner_radius_property().as_property() {
43            this.sync_material(this.composition_visual());
44        }
45        Self::parent_on_property_changed(this, change);
46    }
47}
48
49impl VisualImpl for ExperimentalAcrylicBorder {
50    fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
51        Self::parent_on_attached_to_visual_tree(this, e);
52
53        let tl = TopLevel::get_top_level(Some(this));
54        if let Some(tl) = tl {
55            // The subscription lives in the border, which the top level
56            // holds: the handler refers to both weakly.
57            let weak_tl = tl.downgrade();
58            let weak_this = this.to_ref().downgrade();
59            let object: &FerroObject = &tl;
60            let subscription =
61                FerroObjectExtensions::get_observable(object, TopLevel::actual_transparency_level_property())
62                    .subscribe_fn(move |x| {
63                        let (Some(tl), Some(this)) = (weak_tl.upgrade(), weak_this.upgrade()) else {
64                            return;
65                        };
66                        let (Some(platform_impl), Some(material)) = (tl.platform_impl(), this.material()) else {
67                            return;
68                        };
69                        if x == WindowTransparencyLevel::transparent() || x == WindowTransparencyLevel::none() {
70                            material.set_platform_transparency_compensation_level(
71                                platform_impl.acrylic_compensation_levels().transparent_level(),
72                            );
73                        } else if x == WindowTransparencyLevel::blur() {
74                            material.set_platform_transparency_compensation_level(
75                                platform_impl.acrylic_compensation_levels().blur_level(),
76                            );
77                        } else if x == WindowTransparencyLevel::acrylic_blur() {
78                            material.set_platform_transparency_compensation_level(
79                                platform_impl.acrylic_compensation_levels().acrylic_blur_level(),
80                            );
81                        }
82                    });
83            *this.subscription.borrow_mut() = Some(subscription);
84        } else if let Some(material) = this.material() {
85            material.set_platform_transparency_compensation_level(1.0);
86        }
87
88        this.update_material_subscription();
89    }
90
91    fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
92        Self::parent_on_detached_from_visual_tree(this, e);
93        this.update_material_subscription();
94        let subscription = this.subscription.borrow().clone();
95        if let Some(subscription) = subscription {
96            subscription.dispose();
97        }
98    }
99
100    fn create_composition_visual(this: &Self, compositor: &Rc<Compositor>) -> CompositionDrawListVisual {
101        let v = CompositionExperimentalAcrylicVisual::new(compositor, this);
102        this.sync_material(Some(v.as_draw_list_visual().clone()));
103
104        v.as_draw_list_visual().clone()
105    }
106}
107
108impl LayoutableImpl for ExperimentalAcrylicBorder {
109    /// Measures the control.
110    fn measure_override(this: &Self, available_size: Size) -> Size {
111        let child = this.child();
112        LayoutHelper::measure_child(child.as_deref().map(|c| -> &Layoutable { c }), available_size, this.padding())
113    }
114
115    /// Arranges the control's child.
116    fn arrange_override(this: &Self, final_size: Size) -> Size {
117        let child = this.child();
118        LayoutHelper::arrange_child(child.as_deref().map(|c| -> &Layoutable { c }), final_size, this.padding())
119    }
120}
121
122ferroui_base::ferro_properties! { impl ExperimentalAcrylicBorder {
123    ferro_property!(
124        /// Defines the `CornerRadius` property.
125        pub fn corner_radius_property() -> StyledProperty<CornerRadius> {
126            Border::corner_radius_property().add_owner::<ExperimentalAcrylicBorder>()
127        }
128    );
129
130    ferro_property!(
131        /// Defines the `Material` property.
132        pub fn material_property() -> StyledProperty<Option<Ref<ExperimentalAcrylicMaterial>>> {
133            FerroProperty::register::<ExperimentalAcrylicBorder, _>("Material", None)
134        }
135    );
136} }
137
138impl ExperimentalAcrylicBorder {
139    fn static_constructor() {
140        Visual::affects_render::<ExperimentalAcrylicBorder>(&[
141            Self::material_property().as_property(),
142            Self::corner_radius_property().as_property(),
143        ]);
144    }
145
146    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
147    pub fn construct() -> Self {
148        Self {
149            base: Decorator::construct(),
150            subscription: RefCell::new(None),
151            material_subscription: RefCell::new(None),
152        }
153    }
154
155    fn update_material_subscription(&self) {
156        let material_subscription = self.material_subscription.borrow_mut().take();
157        if let Some(material_subscription) = material_subscription {
158            material_subscription.dispose();
159        }
160        let Some(composition_visual) = self.composition_visual() else {
161            return;
162        };
163        let Some(material) = self.material() else {
164            return;
165        };
166        let weak = self.to_ref().downgrade();
167        let material_subscription = material.property_changed(move |_| {
168            if let Some(this) = weak.upgrade() {
169                this.update_material_subscription();
170            }
171        });
172        *self.material_subscription.borrow_mut() = Some(material_subscription);
173        self.sync_material(Some(composition_visual));
174    }
175
176    fn sync_material(&self, visual: Option<CompositionDrawListVisual>) {
177        if let Some(v) = visual.and_then(|visual| CompositionExperimentalAcrylicVisual::from_visual(&visual)) {
178            v.set_corner_radius(self.corner_radius());
179            let material = self
180                .material()
181                .and_then(|material| {
182                    material.to_immutable().as_any().downcast_ref::<ImmutableExperimentalAcrylicMaterial>().copied()
183                })
184                .unwrap_or_default();
185            v.set_material(material);
186        }
187    }
188
189    pub fn new() -> Ref<Self> {
190        instantiate(Self::construct())
191    }
192
193    /// Gets the radius of the border rounded corners.
194    pub fn corner_radius(&self) -> CornerRadius {
195        self.get_value(Self::corner_radius_property())
196    }
197
198    /// Sets the radius of the border rounded corners.
199    pub fn set_corner_radius(&self, value: CornerRadius) {
200        self.set_value(Self::corner_radius_property(), value)
201    }
202
203    /// Gets the material used to paint the background of the border.
204    pub fn material(&self) -> Option<Ref<ExperimentalAcrylicMaterial>> {
205        self.get_value(Self::material_property())
206    }
207
208    /// Sets the material used to paint the background of the border.
209    pub fn set_material(&self, value: impl Into<Nullable<ExperimentalAcrylicMaterial>>) {
210        self.set_value(Self::material_property(), value.into().0)
211    }
212}