Skip to main content

ferroui_linuxframebuffer/
framebuffer_toplevel_impl.rs

1//! The one top-level of the platform (the port of
2//! `FramebufferToplevelImpl.cs`): it covers the output and has no handle,
3//! no frame, no cursor and no popups.
4
5use crate::input::{IInputBackend, IScreenInfoProvider};
6use crate::output::IOutputBackend;
7use crate::raw_event_grouping::{IRawEventGrouperDispatchQueue, RawEvent, RawEventGrouper};
8use ferroui_base::input::raw::IRawInputEventArgs;
9use ferroui_base::input::{IInputRoot, IMouseDevice, MouseDevice};
10use ferroui_base::platform::surfaces::IPlatformRenderSurface;
11use ferroui_base::platform::{ICursorImpl, IOptionalFeatureProvider, ISurfaceOrientation, SurfaceOrientation};
12use ferroui_base::reactive::IDisposable;
13use ferroui_base::rendering::composition::Compositor;
14use ferroui_base::{PixelPoint, Point, Rect, Size};
15use ferroui_controls::platform::{IPlatformHandle, IPopupImpl, ITopLevelImpl, PlatformThemeVariant};
16use ferroui_controls::{AcrylicPlatformCompensationLevels, WindowResizeReason, WindowTransparencyLevel};
17use std::any::{Any, TypeId};
18use std::cell::RefCell;
19use std::rc::Rc;
20use std::sync::Arc;
21
22type InputCallback = Rc<dyn Fn(Rc<dyn IRawInputEventArgs>)>;
23
24/// The callbacks of the top-level, which the input queue reaches too.
25#[derive(Default)]
26struct Callbacks {
27    input: RefCell<Option<InputCallback>>,
28    paint: RefCell<Option<Rc<dyn Fn(Rect)>>>,
29    resized: RefCell<Option<Rc<dyn Fn(Size, WindowResizeReason)>>>,
30    scaling_changed: RefCell<Option<Rc<dyn Fn(f64)>>>,
31    transparency_level_changed: RefCell<Option<Rc<dyn Fn(WindowTransparencyLevel)>>>,
32    closed: RefCell<Option<Rc<dyn Fn()>>>,
33    lost_focus: RefCell<Option<Rc<dyn Fn()>>>,
34}
35
36pub struct FramebufferToplevelImpl {
37    output_backend: Arc<dyn IOutputBackend>,
38    input_backend: Rc<dyn IInputBackend>,
39    input_queue: Rc<RawEventGrouper>,
40    compositor: Option<Rc<Compositor>>,
41    input_root: RefCell<Option<Rc<dyn IInputRoot>>>,
42    mouse_device: Rc<MouseDevice>,
43    callbacks: Rc<Callbacks>,
44}
45
46impl FramebufferToplevelImpl {
47    /// Creates the top-level over an output and starts the input backend.
48    /// `queue` is the raw event queue of the platform and `compositor`
49    /// its compositor (statics of the platform in the reference).
50    pub fn new(
51        output_backend: Arc<dyn IOutputBackend>,
52        input_backend: Rc<dyn IInputBackend>,
53        queue: Option<Rc<dyn IRawEventGrouperDispatchQueue>>,
54        compositor: Option<Rc<Compositor>>,
55    ) -> Rc<Self> {
56        let callbacks = Rc::new(Callbacks::default());
57        let input_queue = {
58            let callbacks = callbacks.clone();
59            Rc::new(RawEventGrouper::new(
60                move |grouped_input| {
61                    let input = callbacks.input.borrow().clone();
62                    if let Some(input) = input {
63                        input(grouped_input);
64                    }
65                },
66                queue,
67            ))
68        };
69
70        let this = Rc::new(Self {
71            output_backend,
72            input_backend,
73            input_queue,
74            compositor,
75            input_root: RefCell::new(None),
76            mouse_device: MouseDevice::new(),
77            callbacks,
78        });
79
80        // The backend calls on the UI thread (the reference posts each
81        // event to it here, with the priority `Send`).
82        let input_queue = this.input_queue.clone();
83        let screen: Rc<dyn IScreenInfoProvider> = this.clone();
84        this.input_backend.initialize(screen, Rc::new(move |e: RawEvent| input_queue.handle_event(e)));
85        this
86    }
87
88    pub fn input_root(&self) -> Option<Rc<dyn IInputRoot>> {
89        self.input_root.borrow().clone()
90    }
91
92    pub fn mouse_device(&self) -> Rc<dyn IMouseDevice> {
93        self.mouse_device.clone()
94    }
95
96    pub fn frame_size(&self) -> Option<Size> {
97        None
98    }
99}
100
101impl IScreenInfoProvider for FramebufferToplevelImpl {
102    fn scaled_size(&self) -> Size {
103        self.output_backend.pixel_size().to_size(self.render_scaling())
104    }
105
106    fn as_surface_orientation(&self) -> Option<&dyn ISurfaceOrientation> {
107        Some(self)
108    }
109}
110
111impl ISurfaceOrientation for FramebufferToplevelImpl {
112    fn orientation(&self) -> SurfaceOrientation {
113        match self.output_backend.as_surface_orientation() {
114            Some(surface_orientation) => surface_orientation.orientation(),
115            None => SurfaceOrientation::Rotation0,
116        }
117    }
118}
119
120impl IOptionalFeatureProvider for FramebufferToplevelImpl {
121    fn try_get_feature(&self, _feature_type: TypeId) -> Option<Rc<dyn Any>> {
122        None
123    }
124}
125
126impl IDisposable for FramebufferToplevelImpl {
127    /// # Panics
128    /// Always: the top-level of the platform lives as long as the
129    /// process, as in the reference.
130    fn dispose(&self) {
131        panic!("Specified method is not supported.");
132    }
133}
134
135impl ITopLevelImpl for FramebufferToplevelImpl {
136    fn desktop_scaling(&self) -> f64 {
137        1.0
138    }
139
140    fn handle(&self) -> Option<Rc<dyn IPlatformHandle>> {
141        None
142    }
143
144    fn client_size(&self) -> Size {
145        self.scaled_size()
146    }
147
148    fn render_scaling(&self) -> f64 {
149        self.output_backend.scaling()
150    }
151
152    fn surfaces(&self) -> Vec<Arc<dyn IPlatformRenderSurface>> {
153        vec![self.output_backend.clone().as_render_surface()]
154    }
155
156    fn compositor(&self) -> Option<Rc<Compositor>> {
157        self.compositor.clone()
158    }
159
160    fn input(&self) -> Option<InputCallback> {
161        self.callbacks.input.borrow().clone()
162    }
163
164    fn set_input(&self, value: Option<InputCallback>) {
165        *self.callbacks.input.borrow_mut() = value;
166    }
167
168    fn paint(&self) -> Option<Rc<dyn Fn(Rect)>> {
169        self.callbacks.paint.borrow().clone()
170    }
171
172    fn set_paint(&self, value: Option<Rc<dyn Fn(Rect)>>) {
173        *self.callbacks.paint.borrow_mut() = value;
174    }
175
176    fn resized(&self) -> Option<Rc<dyn Fn(Size, WindowResizeReason)>> {
177        self.callbacks.resized.borrow().clone()
178    }
179
180    fn set_resized(&self, value: Option<Rc<dyn Fn(Size, WindowResizeReason)>>) {
181        *self.callbacks.resized.borrow_mut() = value;
182    }
183
184    fn scaling_changed(&self) -> Option<Rc<dyn Fn(f64)>> {
185        self.callbacks.scaling_changed.borrow().clone()
186    }
187
188    fn set_scaling_changed(&self, value: Option<Rc<dyn Fn(f64)>>) {
189        *self.callbacks.scaling_changed.borrow_mut() = value;
190    }
191
192    fn transparency_level_changed(&self) -> Option<Rc<dyn Fn(WindowTransparencyLevel)>> {
193        self.callbacks.transparency_level_changed.borrow().clone()
194    }
195
196    fn set_transparency_level_changed(&self, value: Option<Rc<dyn Fn(WindowTransparencyLevel)>>) {
197        *self.callbacks.transparency_level_changed.borrow_mut() = value;
198    }
199
200    fn set_input_root(&self, input_root: Rc<dyn IInputRoot>) {
201        *self.input_root.borrow_mut() = Some(input_root.clone());
202        self.input_backend.set_input_root(input_root);
203    }
204
205    fn point_to_client(&self, point: PixelPoint) -> Point {
206        point.to_point(1.0)
207    }
208
209    fn point_to_screen(&self, point: Point) -> PixelPoint {
210        PixelPoint::from_point(point, 1.0)
211    }
212
213    fn set_cursor(&self, _cursor: Option<Rc<dyn ICursorImpl>>) {}
214
215    fn closed(&self) -> Option<Rc<dyn Fn()>> {
216        self.callbacks.closed.borrow().clone()
217    }
218
219    fn set_closed(&self, value: Option<Rc<dyn Fn()>>) {
220        *self.callbacks.closed.borrow_mut() = value;
221    }
222
223    fn lost_focus(&self) -> Option<Rc<dyn Fn()>> {
224        self.callbacks.lost_focus.borrow().clone()
225    }
226
227    fn set_lost_focus(&self, value: Option<Rc<dyn Fn()>>) {
228        *self.callbacks.lost_focus.borrow_mut() = value;
229    }
230
231    fn create_popup(&self) -> Option<Rc<dyn IPopupImpl>> {
232        None
233    }
234
235    fn set_transparency_level_hint(&self, _transparency_levels: &[WindowTransparencyLevel]) {}
236
237    fn transparency_level(&self) -> WindowTransparencyLevel {
238        WindowTransparencyLevel::none()
239    }
240
241    fn acrylic_compensation_levels(&self) -> AcrylicPlatformCompensationLevels {
242        AcrylicPlatformCompensationLevels::new(1.0, 1.0, 1.0)
243    }
244
245    fn set_frame_theme_variant(&self, _theme_variant: Option<PlatformThemeVariant>) {}
246
247    fn as_any(&self) -> &dyn Any {
248        self
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    // Not from the reference, which has no tests for this file.
255    use super::*;
256    use crate::input::null_input::NullInputBackend;
257    use crate::native_unsafe_methods::fb_var_screeninfo;
258    use crate::output::{FbDevOutputOptions, FbdevOutput, FileFbDevice};
259
260    #[test]
261    fn the_client_size_is_the_size_of_the_output_divided_by_its_scaling() {
262        let path = std::env::temp_dir().join(format!("ferroui-linuxfb-toplevel-{}.fb", std::process::id()));
263        let var_info = fb_var_screeninfo { xres: 320, yres: 240, bits_per_pixel: 32, ..Default::default() };
264        let device = FileFbDevice::create(&path.to_string_lossy(), var_info).unwrap();
265        let options = FbDevOutputOptions { scaling: 2.0, ..Default::default() };
266        let output = FbdevOutput::with_device(Box::new(device), options).unwrap();
267        let top_level = FramebufferToplevelImpl::new(output.clone(), Rc::new(NullInputBackend::new()), None, None);
268
269        assert_eq!(top_level.render_scaling(), 2.0);
270        assert_eq!(top_level.client_size(), Size::new(160.0, 120.0));
271        assert_eq!(top_level.scaled_size(), Size::new(160.0, 120.0));
272        assert_eq!(top_level.desktop_scaling(), 1.0);
273        assert_eq!(top_level.orientation(), SurfaceOrientation::Rotation0);
274        assert!(top_level.handle().is_none() && top_level.create_popup().is_none() && top_level.frame_size().is_none());
275        assert_eq!(top_level.surfaces().len(), 1);
276        assert!(top_level.surfaces()[0].as_framebuffer_surface().is_some());
277        assert_eq!(top_level.point_to_client(PixelPoint::new(3, 4)), Point::new(3.0, 4.0));
278        assert_eq!(top_level.point_to_screen(Point::new(3.0, 4.0)), PixelPoint::new(3, 4));
279
280        output.set_scaling(1.0);
281        assert_eq!(top_level.client_size(), Size::new(320.0, 240.0));
282        let _ = std::fs::remove_file(&path);
283    }
284}