Skip to main content

i_slint_core/
renderer.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 alloc::boxed::Box;
5use alloc::rc::Rc;
6use core::pin::Pin;
7
8use crate::api::PlatformError;
9#[cfg(feature = "std")]
10use crate::graphics::{Rgba8Pixel, SharedPixelBuffer};
11use crate::item_tree::ItemTreeRef;
12use crate::items::{ItemRc, TextWrap};
13use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize, ScaleFactor};
14use crate::window::WindowAdapter;
15
16/// Result of a single rendering attempt. WGPU-backed renderers can report `Occluded`,
17/// `Timeout` or `Skipped` instead of rendering a frame; in those cases the caller should
18/// re-arm a redraw rather than wait for the next external event.
19#[must_use]
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum DrawOutcome {
22    Success,
23    Occluded,
24    Timeout,
25    /// The renderer wasn't ready to draw yet (e.g. the WGPU surface is still being set up
26    /// asynchronously). No frame was produced; the caller should re-arm a redraw.
27    Skipped,
28}
29
30/// The content widths of a text, as used for its layout constraints.
31#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct ContentWidths {
33    /// The width of the widest chunk that cannot be broken up: the longest word for
34    /// word-wrap. A wrapping text cannot be laid out narrower than this without breaking
35    /// words apart.
36    pub min: LogicalLength,
37    /// The width the text takes without wrapping.
38    pub max: LogicalLength,
39}
40
41/// This trait represents a Renderer that can render a slint scene.
42///
43/// This trait is [sealed](https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed),
44/// meaning that you are not expected to implement this trait
45/// yourself, but you should use the provided ones from Slint.
46pub trait Renderer: RendererSealed {}
47impl<T: RendererSealed> Renderer for T {}
48
49/// Implementation details behind [`Renderer`], but since this
50/// trait is not exported in the public API, it is not possible for the
51/// users to re-implement these functions.
52pub trait RendererSealed {
53    /// The cache used by the default implementations of the text related trait functions,
54    /// which lay text out through [`crate::textlayout::sharedparley`]. Renderers using
55    /// those default implementations return their cache here; with the default `None`,
56    /// text layouts are computed without caching.
57    #[cfg(feature = "shared-parley")]
58    fn text_layout_cache(&self) -> Option<&crate::textlayout::sharedparley::TextLayoutCache> {
59        None
60    }
61
62    /// Returns the size of the given text in logical pixels.
63    /// When set, `max_width` means that one need to wrap the text, so it does not go further than that,
64    /// using the wrapping type passed by `text_wrap`.
65    #[cfg(not(feature = "shared-parley"))]
66    fn text_size(
67        &self,
68        text_item: Pin<&dyn crate::item_rendering::RenderString>,
69        item_rc: &crate::item_tree::ItemRc,
70        max_width: Option<LogicalLength>,
71        text_wrap: TextWrap,
72    ) -> LogicalSize;
73
74    /// Returns the size of the given text in logical pixels.
75    /// When set, `max_width` means that one need to wrap the text, so it does not go further than that,
76    /// using the wrapping type passed by `text_wrap`.
77    ///
78    /// The default implementation uses the shared parley text layout with [`Self::text_layout_cache`].
79    #[cfg(feature = "shared-parley")]
80    fn text_size(
81        &self,
82        text_item: Pin<&dyn crate::item_rendering::RenderString>,
83        item_rc: &crate::item_tree::ItemRc,
84        max_width: Option<LogicalLength>,
85        text_wrap: TextWrap,
86    ) -> LogicalSize {
87        crate::textlayout::sharedparley::text_size(
88            self,
89            text_item,
90            item_rc,
91            max_width,
92            text_wrap,
93            self.text_layout_cache(),
94        )
95        .unwrap_or_default()
96    }
97
98    /// Returns the content widths of the text, or None if the renderer can't measure them,
99    /// in which case the caller falls back to `text_size` without a lower bound.
100    ///
101    /// These are intrinsic to the text and don't depend on its `wrap` mode: `min` is the
102    /// longest word, `max` is the unwrapped width.
103    fn text_content_widths(
104        &self,
105        text_item: Pin<&dyn crate::item_rendering::RenderString>,
106        item_rc: &crate::item_tree::ItemRc,
107    ) -> Option<ContentWidths> {
108        #[cfg(feature = "shared-parley")]
109        {
110            crate::textlayout::sharedparley::text_content_widths(
111                self,
112                text_item,
113                item_rc,
114                self.text_layout_cache(),
115            )
116        }
117        #[cfg(not(feature = "shared-parley"))]
118        {
119            let _ = (text_item, item_rc);
120            None
121        }
122    }
123
124    /// Returns the size of the individual character in logical pixels.
125    #[cfg(not(feature = "shared-parley"))]
126    fn char_size(
127        &self,
128        text_item: Pin<&dyn crate::item_rendering::HasFont>,
129        item_rc: &crate::item_tree::ItemRc,
130        ch: char,
131    ) -> LogicalSize;
132
133    /// Returns the size of the individual character in logical pixels.
134    ///
135    /// The default implementation measures the character with the shared parley layout.
136    #[cfg(feature = "shared-parley")]
137    fn char_size(
138        &self,
139        text_item: Pin<&dyn crate::item_rendering::HasFont>,
140        item_rc: &crate::item_tree::ItemRc,
141        ch: char,
142    ) -> LogicalSize {
143        self.slint_context()
144            .and_then(|ctx| {
145                let mut font_ctx = ctx.font_context().borrow_mut();
146                crate::textlayout::sharedparley::char_size(&mut font_ctx, text_item, item_rc, ch)
147            })
148            .unwrap_or_default()
149    }
150
151    /// Returns the metrics of the given font.
152    #[cfg(not(feature = "shared-parley"))]
153    fn font_metrics(&self, font_request: crate::graphics::FontRequest)
154    -> crate::items::FontMetrics;
155
156    /// Returns the metrics of the given font.
157    ///
158    /// The default implementation queries the font through the shared fontique collection.
159    #[cfg(feature = "shared-parley")]
160    fn font_metrics(
161        &self,
162        font_request: crate::graphics::FontRequest,
163    ) -> crate::items::FontMetrics {
164        self.slint_context()
165            .map(|ctx| {
166                let mut font_ctx = ctx.font_context().borrow_mut();
167                crate::textlayout::sharedparley::font_metrics(&mut font_ctx, font_request)
168            })
169            .unwrap_or_default()
170    }
171
172    /// The height of one line of text: what a shaped single-line layout reports, without
173    /// shaping. `None` means the caller must measure through [`Self::text_size`].
174    fn text_line_height(
175        &self,
176        font_request: crate::graphics::FontRequest,
177    ) -> Option<LogicalLength> {
178        #[cfg(feature = "shared-parley")]
179        {
180            let ctx = self.slint_context()?;
181            let mut font_ctx = ctx.font_context().borrow_mut();
182            crate::textlayout::sharedparley::text_line_height(&mut font_ctx, &font_request)
183        }
184        #[cfg(not(feature = "shared-parley"))]
185        {
186            let _ = font_request;
187            None
188        }
189    }
190
191    /// Returns the (UTF-8) byte offset in the text property that refers to the character that contributed to
192    /// the glyph cluster that's visually nearest to the given coordinate. This is used for hit-testing,
193    /// for example when receiving a mouse click into a text field. Then this function returns the "cursor"
194    /// position. The affinity says which visual position was hit (differs only at a soft break).
195    #[cfg(not(feature = "shared-parley"))]
196    fn text_input_byte_offset_for_position(
197        &self,
198        text_input: Pin<&crate::items::TextInput>,
199        item_rc: &ItemRc,
200        pos: LogicalPoint,
201    ) -> (usize, crate::items::TextCursorAffinity);
202
203    /// Returns the (UTF-8) byte offset in the text property that refers to the character that contributed to
204    /// the glyph cluster that's visually nearest to the given coordinate. This is used for hit-testing,
205    /// for example when receiving a mouse click into a text field. Then this function returns the "cursor"
206    /// position. The affinity says which visual position was hit (differs only at a soft break).
207    ///
208    /// The default implementation uses the shared parley text layout with [`Self::text_layout_cache`].
209    #[cfg(feature = "shared-parley")]
210    fn text_input_byte_offset_for_position(
211        &self,
212        text_input: Pin<&crate::items::TextInput>,
213        item_rc: &ItemRc,
214        pos: LogicalPoint,
215    ) -> (usize, crate::items::TextCursorAffinity) {
216        crate::textlayout::sharedparley::text_input_byte_offset_for_position(
217            self,
218            text_input,
219            item_rc,
220            pos,
221            self.text_layout_cache(),
222        )
223    }
224
225    /// That's the opposite of [`Self::text_input_byte_offset_for_position`]
226    /// It takes a (UTF-8) byte offset in the text property, and returns a Rectangle
227    /// left to the char. It is one logical pixel wide and ends at the baseline.
228    /// An offset at a soft line break has one rectangle per line; `affinity` picks between them.
229    #[cfg(not(feature = "shared-parley"))]
230    fn text_input_cursor_rect_for_byte_offset(
231        &self,
232        text_input: Pin<&crate::items::TextInput>,
233        item_rc: &ItemRc,
234        byte_offset: usize,
235        affinity: crate::items::TextCursorAffinity,
236    ) -> LogicalRect;
237
238    /// That's the opposite of [`Self::text_input_byte_offset_for_position`]
239    /// It takes a (UTF-8) byte offset in the text property, and returns a Rectangle
240    /// left to the char. It is one logical pixel wide and ends at the baseline.
241    /// An offset at a soft line break has one rectangle per line; `affinity` picks between them.
242    ///
243    /// The default implementation uses the shared parley text layout with [`Self::text_layout_cache`].
244    #[cfg(feature = "shared-parley")]
245    fn text_input_cursor_rect_for_byte_offset(
246        &self,
247        text_input: Pin<&crate::items::TextInput>,
248        item_rc: &ItemRc,
249        byte_offset: usize,
250        affinity: crate::items::TextCursorAffinity,
251    ) -> LogicalRect {
252        crate::textlayout::sharedparley::text_input_cursor_rect_for_byte_offset(
253            self,
254            text_input,
255            item_rc,
256            byte_offset,
257            affinity,
258            self.text_layout_cache(),
259        )
260    }
261
262    /// Whether this renderer lays `text_input`'s text out through parley.
263    #[cfg(feature = "shared-parley")]
264    fn text_input_has_parley_layout(
265        &self,
266        _text_input: Pin<&crate::items::TextInput>,
267        _item_rc: &ItemRc,
268    ) -> bool {
269        true
270    }
271
272    /// Clear the caches for the items that are being removed
273    fn free_graphics_resources(
274        &self,
275        _component: ItemTreeRef,
276        _items: &mut dyn Iterator<Item = Pin<crate::items::ItemRef<'_>>>,
277    ) -> Result<(), crate::platform::PlatformError> {
278        Ok(())
279    }
280
281    /// Mark a given region as dirty regardless whether the items actually are dirty.
282    ///
283    /// Example: when a PopupWindow disappears, the region under the popup needs to be redrawn
284    fn mark_dirty_region(&self, _region: crate::partial_renderer::DirtyRegion) {}
285
286    #[cfg(all(feature = "std", not(feature = "shared-parley")))] // FIXME: just because of the Error
287    /// This function can be used to register a custom TrueType font with Slint,
288    /// for use with the `font-family` property. The provided slice must be a valid TrueType
289    /// font.
290    fn register_font_from_memory(
291        &self,
292        _data: &'static [u8],
293    ) -> Result<(), Box<dyn std::error::Error>> {
294        Err("This renderer does not support registering custom fonts.".into())
295    }
296
297    #[cfg(all(feature = "std", feature = "shared-parley"))]
298    /// This function can be used to register a custom TrueType font with Slint,
299    /// for use with the `font-family` property. The provided slice must be a valid TrueType
300    /// font.
301    ///
302    /// The default implementation registers the font with the shared fontique collection.
303    fn register_font_from_memory(
304        &self,
305        data: &'static [u8],
306    ) -> Result<(), Box<dyn std::error::Error>> {
307        let ctx = self.slint_context().ok_or("slint platform not initialized")?;
308        ctx.font_context().borrow_mut().register_static_font(data);
309        Ok(())
310    }
311
312    #[cfg(all(feature = "std", not(feature = "shared-parley")))]
313    /// This function can be used to register a custom TrueType font with Slint,
314    /// for use with the `font-family` property. The provided path must refer to a valid TrueType
315    /// font.
316    fn register_font_from_path(
317        &self,
318        _path: &std::path::Path,
319    ) -> Result<(), Box<dyn std::error::Error>> {
320        Err("This renderer does not support registering custom fonts.".into())
321    }
322
323    #[cfg(all(feature = "std", feature = "shared-parley"))]
324    /// This function can be used to register a custom TrueType font with Slint,
325    /// for use with the `font-family` property. The provided path must refer to a valid TrueType
326    /// font.
327    ///
328    /// The default implementation registers the font with the shared fontique collection.
329    fn register_font_from_path(
330        &self,
331        path: &std::path::Path,
332    ) -> Result<(), Box<dyn std::error::Error>> {
333        let requested_path = path.canonicalize().unwrap_or_else(|_| path.into());
334        let contents = std::fs::read(requested_path)?;
335        let ctx = self.slint_context().ok_or("slint platform not initialized")?;
336        ctx.font_context().borrow_mut().collection.register_fonts(contents.into(), None);
337        Ok(())
338    }
339
340    fn register_bitmap_font(&self, _font_data: &'static crate::graphics::BitmapFont) {
341        crate::debug_log!(
342            "Internal error: The current renderer cannot load fonts build with the `EmbedForSoftwareRenderer` option. Please use the software Renderer, or disable that option when building your slint files"
343        );
344    }
345
346    /// This function is called through the public API to register a callback that the backend needs to invoke during
347    /// different phases of rendering.
348    fn set_rendering_notifier(
349        &self,
350        _callback: Box<dyn crate::api::RenderingNotifier>,
351    ) -> Result<(), crate::api::SetRenderingNotifierError> {
352        Err(crate::api::SetRenderingNotifierError::Unsupported)
353    }
354
355    fn set_window_adapter(&self, _window_adapter: &Rc<dyn WindowAdapter>);
356
357    fn window_adapter(&self) -> Option<Rc<dyn WindowAdapter>>;
358
359    fn scale_factor(&self) -> Option<ScaleFactor> {
360        self.window_adapter()
361            .map(|window_adapter| ScaleFactor::new(window_adapter.window().scale_factor()))
362    }
363
364    #[cfg(feature = "shared-parley")]
365    fn slint_context(&self) -> Option<crate::SlintContext> {
366        self.window_adapter()
367            .map(|wa| crate::window::WindowInner::from_pub(wa.window()).context().clone())
368    }
369
370    fn resize(&self, _size: crate::api::PhysicalSize) -> Result<(), PlatformError> {
371        Ok(())
372    }
373
374    /// Re-implement this function to support Window::take_snapshot(), i.e. return
375    /// the contents of the window in an image buffer.
376    #[cfg(feature = "std")]
377    fn take_snapshot(&self) -> Result<SharedPixelBuffer<Rgba8Pixel>, PlatformError> {
378        Err("WindowAdapter::take_snapshot is not implemented by the platform".into())
379    }
380
381    /// Whether the renderer supports transformations such as rotations and scaling or not.
382    fn supports_transformations(&self) -> bool;
383}