ferroui_linuxframebuffer/
framebuffer_toplevel_impl.rs1use 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#[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 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 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 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 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}