i_slint_backend_linuxkms/
fullscreenwindowadapter.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! This module contains the window adapter implementation to communicate between Slint and Vulkan + libinput
5
6use std::cell::Cell;
7use std::pin::Pin;
8use std::rc::Rc;
9
10use i_slint_core::api::{LogicalPosition, PhysicalSize as PhysicalWindowSize};
11use i_slint_core::graphics::{euclid, Image};
12use i_slint_core::item_rendering::ItemRenderer;
13use i_slint_core::lengths::LogicalRect;
14use i_slint_core::platform::WindowEvent;
15use i_slint_core::slice::Slice;
16use i_slint_core::Property;
17use i_slint_core::{platform::PlatformError, window::WindowAdapter};
18
19use crate::display::RenderingRotation;
20
21pub trait FullscreenRenderer {
22    fn as_core_renderer(&self) -> &dyn i_slint_core::renderer::Renderer;
23    fn render_and_present(
24        &self,
25        rotation: RenderingRotation,
26        draw_mouse_cursor_callback: &dyn Fn(&mut dyn ItemRenderer),
27    ) -> Result<(), PlatformError>;
28    fn size(&self) -> PhysicalWindowSize;
29}
30
31pub struct FullscreenWindowAdapter {
32    window: i_slint_core::api::Window,
33    renderer: Box<dyn FullscreenRenderer>,
34    redraw_requested: Cell<bool>,
35    rotation: RenderingRotation,
36}
37
38impl WindowAdapter for FullscreenWindowAdapter {
39    fn window(&self) -> &i_slint_core::api::Window {
40        &self.window
41    }
42
43    fn size(&self) -> i_slint_core::api::PhysicalSize {
44        self.rotation.screen_size_to_rotated_window_size(self.renderer.size())
45    }
46
47    fn renderer(&self) -> &dyn i_slint_core::renderer::Renderer {
48        self.renderer.as_core_renderer()
49    }
50
51    fn request_redraw(&self) {
52        self.redraw_requested.set(true)
53    }
54
55    fn set_visible(&self, visible: bool) -> Result<(), PlatformError> {
56        if visible {
57            if let Some(scale_factor) =
58                std::env::var("SLINT_SCALE_FACTOR").ok().and_then(|sf| sf.parse().ok())
59            {
60                self.window.try_dispatch_event(WindowEvent::ScaleFactorChanged { scale_factor })?;
61            }
62        }
63        Ok(())
64    }
65}
66
67impl FullscreenWindowAdapter {
68    pub fn new(
69        renderer: Box<dyn FullscreenRenderer>,
70        rotation: RenderingRotation,
71    ) -> Result<Rc<Self>, PlatformError> {
72        let size = renderer.size();
73        let rotation_degrees = rotation.degrees();
74        eprintln!(
75            "Rendering at {}x{}{}",
76            size.width,
77            size.height,
78            if rotation_degrees != 0. {
79                format!(" with {} rotation_degrees rotation", rotation_degrees)
80            } else {
81                String::new()
82            }
83        );
84        Ok(Rc::<FullscreenWindowAdapter>::new_cyclic(|self_weak| FullscreenWindowAdapter {
85            window: i_slint_core::api::Window::new(self_weak.clone()),
86            renderer,
87            redraw_requested: Cell::new(true),
88            rotation,
89        }))
90    }
91
92    pub fn render_if_needed(
93        self: Rc<Self>,
94        mouse_position: Pin<&Property<Option<LogicalPosition>>>,
95    ) -> Result<(), PlatformError> {
96        if self.redraw_requested.replace(false) {
97            self.renderer.render_and_present(self.rotation, &|item_renderer| {
98                if let Some(mouse_position) = mouse_position.get() {
99                    let cursor_image = mouse_cursor_image();
100                    item_renderer.save_state();
101                    item_renderer.translate(
102                        i_slint_core::lengths::logical_point_from_api(mouse_position).to_vector(),
103                    );
104                    item_renderer.draw_image_direct(mouse_cursor_image());
105                    item_renderer.restore_state();
106                    let cursor_rect = LogicalRect::new(
107                        euclid::point2(mouse_position.x, mouse_position.y),
108                        euclid::Size2D::from_untyped(cursor_image.size().cast()),
109                    );
110                    self.renderer.as_core_renderer().mark_dirty_region(cursor_rect.into());
111                }
112            })?;
113            // Check once after rendering if we have running animations and
114            // remember that to trigger a redraw after the frame is on the screen.
115            // Timers might have been updated if the event loop is woken up
116            // due to other reasons, which would also reset has_active_animations.
117            if self.window.has_active_animations() {
118                let self_weak = Rc::downgrade(&self);
119                i_slint_core::timers::Timer::single_shot(
120                    std::time::Duration::default(),
121                    move || {
122                        let Some(this) = self_weak.upgrade() else {
123                            return;
124                        };
125                        this.request_redraw();
126                    },
127                )
128            }
129        }
130        Ok(())
131    }
132}
133
134fn mouse_cursor_image() -> Image {
135    let mouse_pointer_svg = i_slint_core::graphics::load_image_from_embedded_data(
136        Slice::from_slice(include_bytes!("mouse-pointer.svg")),
137        Slice::from_slice(b"svg"),
138    );
139    let mouse_pointer_inner: &i_slint_core::graphics::ImageInner = (&mouse_pointer_svg).into();
140    match mouse_pointer_inner {
141        i_slint_core::ImageInner::Svg(svg) => {
142            let pixels = svg.render(None).unwrap();
143            let cache_key = svg.cache_key();
144            let mouse_pointer_pixel_image = i_slint_core::graphics::ImageInner::EmbeddedImage {
145                cache_key: cache_key.clone(),
146                buffer: pixels,
147            };
148            i_slint_core::graphics::cache::replace_cached_image(
149                cache_key,
150                mouse_pointer_pixel_image.clone(),
151            );
152
153            mouse_pointer_pixel_image.into()
154        }
155        cached_image @ _ => cached_image.clone().into(),
156    }
157}