Skip to main content

i_slint_core/
graphics.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// cSpell: ignore bitmapfont glversion
5#![allow(unsafe_code)]
6#![warn(missing_docs)]
7/*!
8    Graphics Abstractions.
9
10    This module contains the abstractions and convenience types used for rendering.
11*/
12extern crate alloc;
13use crate::Coord;
14use crate::SharedString;
15use crate::api::PlatformError;
16use crate::lengths::LogicalLength;
17use alloc::boxed::Box;
18
19pub use euclid;
20/// 2D Rectangle
21pub type Rect = euclid::default::Rect<Coord>;
22/// 2D Rectangle with integer coordinates
23pub type IntRect = euclid::default::Rect<i32>;
24/// 2D Point
25pub type Point = euclid::default::Point2D<Coord>;
26/// 2D Size
27pub type Size = euclid::default::Size2D<Coord>;
28/// 2D Size in integer coordinates
29pub type IntSize = euclid::default::Size2D<u32>;
30/// 2D Transform
31pub type Transform = euclid::default::Transform2D<Coord>;
32
33pub(crate) mod color;
34pub use color::*;
35
36#[cfg(feature = "shared-fontique")]
37use i_slint_common::sharedfontique::{self, fontique};
38#[cfg(feature = "path")]
39mod path;
40#[cfg(feature = "path")]
41pub use path::*;
42
43mod brush;
44pub use brush::*;
45
46pub(crate) mod image;
47pub use self::image::*;
48
49pub(crate) mod bitmapfont;
50pub use self::bitmapfont::*;
51
52pub mod rendering_metrics_collector;
53
54#[cfg(feature = "box-shadow-cache")]
55pub mod boxshadowcache;
56
57pub mod border_radius;
58pub use border_radius::*;
59
60#[cfg(feature = "wgpu-29")]
61pub mod wgpu_29;
62#[cfg(feature = "wgpu-30")]
63pub mod wgpu_30;
64
65/// Adjusts a rectangle and a border width for drawing the border entirely inside the
66/// rectangle's geometry: renderers stroke a border centered on the path, so the rectangle
67/// is inset by half the border width. If the border width exceeds half of the rectangle's
68/// width, it is clamped so that the border just fills the rectangle.
69pub fn adjust_rect_and_border_for_inner_drawing<U>(
70    rect: &mut euclid::Rect<f32, U>,
71    border_width: &mut euclid::Length<f32, U>,
72) {
73    use crate::lengths::RectLengths;
74    // If the border width exceeds the width, just fill the rectangle.
75    *border_width = border_width.min(rect.width_length() / 2.);
76    // adjust the size so that the border is drawn within the geometry
77    rect.origin += euclid::Size2D::from_lengths(*border_width / 2., *border_width / 2.);
78    rect.size -= euclid::Size2D::from_lengths(*border_width, *border_width);
79}
80
81/// CachedGraphicsData allows the graphics backend to store an arbitrary piece of data associated with
82/// an item, which is typically computed by accessing properties. The dependency_tracker is used to allow
83/// for a lazy computation. Typically, back ends store either compute intensive data or handles that refer to
84/// data that's stored in GPU memory.
85pub struct CachedGraphicsData<T> {
86    /// The backend specific data.
87    pub data: T,
88    /// The property tracker that should be used to evaluate whether the primitive needs to be re-created
89    /// or not.
90    pub dependency_tracker: Option<core::pin::Pin<Box<crate::properties::PropertyTracker>>>,
91}
92
93impl<T> CachedGraphicsData<T> {
94    /// Creates a new TrackingRenderingPrimitive by evaluating the provided update_fn once, storing the returned
95    /// rendering primitive and initializing the dependency tracker.
96    pub fn new(update_fn: impl FnOnce() -> T) -> Self {
97        let dependency_tracker = Box::pin(crate::properties::PropertyTracker::default());
98        let data = dependency_tracker.as_ref().evaluate(update_fn);
99        Self { data, dependency_tracker: Some(dependency_tracker) }
100    }
101}
102
103/// FontRequest collects all the developer-configurable properties for fonts, such as family, weight, etc.
104/// It is submitted as a request to the platform font system (i.e. CoreText on macOS) and in exchange the
105/// backend returns a `Box<dyn Font>`.
106#[derive(Debug, Clone, PartialEq, Default)]
107pub struct FontRequest {
108    /// The name of the font family to be used, such as "Helvetica". An empty family name means the system
109    /// default font family should be used.
110    pub family: Option<SharedString>,
111    /// If the weight is None, the system default font weight should be used.
112    pub weight: Option<i32>,
113    /// If the pixel size is None, the system default font size should be used.
114    pub pixel_size: Option<LogicalLength>,
115    /// The additional spacing (or shrinking if negative) between glyphs. This is usually not submitted to
116    /// the font-subsystem but collected here for API convenience
117    pub letter_spacing: Option<LogicalLength>,
118    /// The line height as a factor applied to the font's natural line height.
119    /// `None` uses the natural line height unchanged (a factor of 1).
120    pub line_height_factor: Option<f32>,
121    /// Whether to select an italic face of the font family.
122    pub italic: bool,
123}
124
125impl FontRequest {
126    /// Returns the configured line height given the font's natural line height
127    /// (in any unit), or `None` when the natural line height applies unchanged.
128    pub fn line_height_for_natural_height(&self, natural_line_height: f32) -> Option<f32> {
129        self.line_height_factor.map(|factor| natural_line_height * factor)
130    }
131}
132
133#[cfg(feature = "shared-fontique")]
134impl FontRequest {
135    /// Attempts to query the fontique font collection for a matching font.
136    pub fn query_fontique(
137        &self,
138        collection: &mut fontique::Collection,
139        source_cache: &mut fontique::SourceCache,
140    ) -> Option<fontique::QueryFont> {
141        let mut query = collection.query(source_cache);
142        query.set_families(
143            self.family
144                .as_ref()
145                .map(|family| fontique::QueryFamily::from(family.as_str()))
146                .into_iter()
147                .chain(
148                    sharedfontique::FALLBACK_FAMILIES
149                        .into_iter()
150                        .map(fontique::QueryFamily::Generic),
151                ),
152        );
153
154        query.set_attributes(fontique::Attributes {
155            weight: self
156                .weight
157                .as_ref()
158                .map(|&weight| fontique::FontWeight::new(weight as f32))
159                .unwrap_or_default(),
160            style: if self.italic {
161                fontique::FontStyle::Italic
162            } else {
163                fontique::FontStyle::Normal
164            },
165            ..Default::default()
166        });
167
168        let mut font = None;
169
170        query.matches_with(|queried_font| {
171            font = Some(queried_font.clone());
172            fontique::QueryStatus::Stop
173        });
174
175        font
176    }
177}
178
179/// Internal enum to specify which version of OpenGL to request
180/// from the windowing system.
181#[derive(Debug, Clone, PartialEq)]
182pub enum RequestedOpenGLVersion {
183    /// OpenGL
184    OpenGL(Option<(u8, u8)>),
185    /// OpenGL ES
186    OpenGLES(Option<(u8, u8)>),
187}
188
189/// Internal enum specify which graphics API should be used, when
190/// the backend selector requests that from a built-in backend.
191#[derive(Debug, Clone)]
192#[allow(clippy::large_enum_variant)]
193pub enum RequestedGraphicsAPI {
194    /// OpenGL (ES)
195    OpenGL(RequestedOpenGLVersion),
196    /// Metal
197    Metal,
198    /// Vulkan
199    Vulkan,
200    /// Direct 3D
201    Direct3D,
202    #[cfg(feature = "unstable-wgpu-29")]
203    /// WGPU 29.x
204    WGPU29(wgpu_29::api::WGPUConfiguration),
205    #[cfg(feature = "unstable-wgpu-30")]
206    /// WGPU 30.x
207    WGPU30(wgpu_30::api::WGPUConfiguration),
208}
209
210impl TryFrom<&RequestedGraphicsAPI> for RequestedOpenGLVersion {
211    type Error = PlatformError;
212
213    fn try_from(requested_graphics_api: &RequestedGraphicsAPI) -> Result<Self, Self::Error> {
214        match requested_graphics_api {
215            RequestedGraphicsAPI::OpenGL(requested_open_glversion) => {
216                Ok(requested_open_glversion.clone())
217            }
218            RequestedGraphicsAPI::Metal => {
219                Err("Metal rendering is not supported with an OpenGL renderer".into())
220            }
221            RequestedGraphicsAPI::Vulkan => {
222                Err("Vulkan rendering is not supported with an OpenGL renderer".into())
223            }
224            RequestedGraphicsAPI::Direct3D => {
225                Err("Direct3D rendering is not supported with an OpenGL renderer".into())
226            }
227            #[cfg(feature = "unstable-wgpu-29")]
228            RequestedGraphicsAPI::WGPU29(..) => {
229                Err("WGPU 29.x rendering is not supported with an OpenGL renderer".into())
230            }
231            #[cfg(feature = "unstable-wgpu-30")]
232            RequestedGraphicsAPI::WGPU30(..) => {
233                Err("WGPU 30.x rendering is not supported with an OpenGL renderer".into())
234            }
235        }
236    }
237}
238
239impl From<RequestedOpenGLVersion> for RequestedGraphicsAPI {
240    fn from(version: RequestedOpenGLVersion) -> Self {
241        Self::OpenGL(version)
242    }
243}
244
245/// Private API exposed to just the renderers to create GraphicsAPI instance with
246/// non-exhaustive enum variant.
247#[cfg(feature = "unstable-wgpu-29")]
248pub fn create_graphics_api_wgpu_29(
249    instance: wgpu_29::wgpu::Instance,
250    device: wgpu_29::wgpu::Device,
251    queue: wgpu_29::wgpu::Queue,
252) -> crate::api::GraphicsAPI<'static> {
253    crate::api::GraphicsAPI::WGPU29 { instance, device, queue }
254}
255
256/// Private API exposed to just the renderers to create GraphicsAPI instance with
257/// non-exhaustive enum variant.
258#[cfg(feature = "unstable-wgpu-30")]
259pub fn create_graphics_api_wgpu_30(
260    instance: wgpu_30::wgpu::Instance,
261    device: wgpu_30::wgpu::Device,
262    queue: wgpu_30::wgpu::Queue,
263) -> crate::api::GraphicsAPI<'static> {
264    crate::api::GraphicsAPI::WGPU30 { instance, device, queue }
265}
266
267/// Internal module for use by cbindgen and the C++ platform API layer.
268#[cfg(feature = "ffi")]
269pub mod ffi {
270    #![allow(unsafe_code)]
271
272    /// Expand Rect so that cbindgen can see it. ( is in fact euclid::default::Rect<f32>)
273    #[cfg(cbindgen)]
274    #[repr(C)]
275    struct Rect {
276        x: f32,
277        y: f32,
278        width: f32,
279        height: f32,
280    }
281
282    /// Expand IntRect so that cbindgen can see it. ( is in fact euclid::default::Rect<i32>)
283    #[cfg(cbindgen)]
284    #[repr(C)]
285    struct IntRect {
286        x: i32,
287        y: i32,
288        width: i32,
289        height: i32,
290    }
291
292    /// Expand Point so that cbindgen can see it. ( is in fact euclid::default::Point2D<f32>)
293    #[cfg(cbindgen)]
294    #[repr(C)]
295    struct Point {
296        x: f32,
297        y: f32,
298    }
299
300    /// Expand Box2D so that cbindgen can see it.
301    #[cfg(cbindgen)]
302    #[repr(C)]
303    struct Box2D<T, U> {
304        min: euclid::Point2D<T>,
305        max: euclid::Point2D<T>,
306        _unit: std::marker::PhantomData<U>,
307    }
308
309    #[cfg(feature = "std")]
310    pub use super::path::ffi::*;
311
312    /// Conversion function used by C++ platform API layer to
313    /// convert the PhysicalSize used in the Rust WindowAdapter API
314    /// to the ffi.
315    pub fn physical_size_from_api(
316        size: crate::api::PhysicalSize,
317    ) -> crate::graphics::euclid::default::Size2D<u32> {
318        size.to_euclid()
319    }
320
321    /// Conversion function used by C++ platform API layer to
322    /// convert the PhysicalPosition used in the Rust WindowAdapter API
323    /// to the ffi.
324    pub fn physical_position_from_api(
325        position: crate::api::PhysicalPosition,
326    ) -> crate::graphics::euclid::default::Point2D<i32> {
327        position.to_euclid()
328    }
329
330    /// Conversion function used by C++ platform API layer to
331    /// convert from the ffi to PhysicalPosition.
332    pub fn physical_position_to_api(
333        position: crate::graphics::euclid::default::Point2D<i32>,
334    ) -> crate::api::PhysicalPosition {
335        crate::api::PhysicalPosition::from_euclid(position)
336    }
337}