Skip to main content

i_slint_renderer_software/
minimal_software_window.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
4use super::{RepaintBufferType, SoftwareRenderer};
5use alloc::rc::{Rc, Weak};
6use core::cell::Cell;
7use i_slint_core::api::Window;
8use i_slint_core::platform::Renderer;
9use i_slint_core::window::WindowAdapter;
10
11/// This is a minimal adapter for a Window that doesn't have any other feature than rendering
12/// using the software renderer.
13pub struct MinimalSoftwareWindow {
14    window: Window,
15    renderer: SoftwareRenderer,
16    needs_redraw: Cell<bool>,
17    size: Cell<i_slint_core::api::PhysicalSize>,
18}
19
20impl MinimalSoftwareWindow {
21    /// Instantiate a new MinimalWindowAdaptor
22    ///
23    /// The `repaint_buffer_type` parameter specify what kind of buffer are passed to the [`SoftwareRenderer`]
24    pub fn new(repaint_buffer_type: RepaintBufferType) -> Rc<Self> {
25        Rc::new_cyclic(|w: &Weak<Self>| Self {
26            window: Window::new(w.clone()),
27            renderer: SoftwareRenderer::new_with_repaint_buffer_type(repaint_buffer_type),
28            needs_redraw: Default::default(),
29            size: Default::default(),
30        })
31    }
32    /// If the window needs to be redrawn, the callback will be called with the
33    /// [renderer](SoftwareRenderer) that should be used to do the drawing.
34    ///
35    /// [`SoftwareRenderer::render()`] or [`SoftwareRenderer::render_by_line()`] should be called
36    /// in that callback.
37    ///
38    /// Return true if something was redrawn.
39    pub fn draw_if_needed(&self, render_callback: impl FnOnce(&SoftwareRenderer)) -> bool {
40        if self.needs_redraw.replace(false)
41            || self.renderer.rendering_metrics_collector.as_ref().is_some_and(|m| m.refresh_mode() == i_slint_core::graphics::rendering_metrics_collector::RefreshMode::FullSpeed)
42        {
43            render_callback(&self.renderer);
44            true
45        } else {
46            false
47        }
48    }
49
50    #[cfg(feature = "experimental")]
51    /// If the window needs to be redrawn, the callback will be called with the
52    /// [renderer](SoftwareRenderer) that should be used to do the drawing.
53    ///
54    /// [`SoftwareRenderer::render()`] or [`SoftwareRenderer::render_by_line()`] should be called
55    /// in that callback.
56    ///
57    /// Return true if something was redrawn.
58    pub async fn draw_async_if_needed(
59        &self,
60        render_callback: impl AsyncFnOnce(&SoftwareRenderer),
61    ) -> bool {
62        if self.needs_redraw.replace(false) || self.renderer.rendering_metrics_collector.is_some() {
63            render_callback(&self.renderer).await;
64            true
65        } else {
66            false
67        }
68    }
69
70    #[doc(hidden)]
71    /// Forward to the window through Deref
72    /// (Before 1.1, WindowAdapter didn't have set_size, so the one from Deref was used.
73    /// But in Slint 1.1, if one had imported the WindowAdapter trait, the other one would be found)
74    pub fn set_size(&self, size: impl Into<i_slint_core::api::WindowSize>) {
75        self.window.set_size(size);
76    }
77}
78
79impl WindowAdapter for MinimalSoftwareWindow {
80    fn window(&self) -> &Window {
81        &self.window
82    }
83
84    fn renderer(&self) -> &dyn Renderer {
85        &self.renderer
86    }
87
88    fn size(&self) -> i_slint_core::api::PhysicalSize {
89        self.size.get()
90    }
91    fn set_size(&self, size: i_slint_core::api::WindowSize) {
92        let sf = self.window.scale_factor();
93        self.size.set(size.to_physical(sf));
94        let logical_size = size.to_logical(sf);
95        self.window
96            .dispatch_event(i_slint_core::platform::WindowEvent::Resized { size: logical_size });
97    }
98
99    fn request_redraw(&self) {
100        self.needs_redraw.set(true);
101    }
102}
103
104impl core::ops::Deref for MinimalSoftwareWindow {
105    type Target = Window;
106    fn deref(&self) -> &Self::Target {
107        &self.window
108    }
109}
110
111#[test]
112fn test_empty_window() {
113    // Test that when creating an empty window without a component, we don't panic when render() is called.
114    // This isn't typically done intentionally, but for example if we receive a paint event in Qt before a component
115    // is set, this may happen. Concretely as per #2799 this could happen with popups where the call to
116    // QWidget::show() with egl delivers an immediate paint event, before we've had a chance to call set_component.
117    // Let's emulate this scenario here using public platform API.
118
119    let msw = MinimalSoftwareWindow::new(RepaintBufferType::NewBuffer);
120    msw.window().request_redraw();
121    let mut region = None;
122    let render_called = msw.draw_if_needed(|renderer| {
123        let mut buffer = i_slint_core::graphics::SharedPixelBuffer::<
124            i_slint_core::graphics::Rgb8Pixel,
125        >::new(100, 100);
126        let stride = buffer.width() as usize;
127        region = Some(renderer.render(buffer.make_mut_slice(), stride));
128    });
129    assert!(render_called);
130    let region = region.unwrap();
131    assert_eq!(region.bounding_box_size(), i_slint_core::api::PhysicalSize::default());
132    assert_eq!(region.bounding_box_origin(), i_slint_core::api::PhysicalPosition::default());
133}