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}