mirror of
				https://github.com/slint-ui/slint.git
				synced 2025-10-30 03:27:22 +00:00 
			
		
		
		
	 e04f028c91
			
		
	
	
		e04f028c91
		
	
	
	
	
		
			
			We had code on the window to reset the default-font-size property to the default from the renderer. For the Qt backend, the PopupWindow being their own Window, this code was activated also for the PopupWindow's hidden default-font-size property, which caused all PopupWindow's font to not follow the default-font-size Fixes #8855
		
			
				
	
	
		
			1079 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			1079 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| // Copyright © SixtyFPS GmbH <info@slint.dev>
 | |
| // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
 | |
| 
 | |
| #![doc = include_str!("README.md")]
 | |
| #![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
 | |
| 
 | |
| #[cfg(any(target_vendor = "apple", skia_backend_vulkan))]
 | |
| use std::cell::OnceCell;
 | |
| use std::cell::{Cell, RefCell};
 | |
| use std::rc::{Rc, Weak};
 | |
| use std::sync::Arc;
 | |
| 
 | |
| use i_slint_core::api::{
 | |
|     GraphicsAPI, PhysicalSize as PhysicalWindowSize, RenderingNotifier, RenderingState,
 | |
|     SetRenderingNotifierError, Window,
 | |
| };
 | |
| use i_slint_core::graphics::euclid::{self, Vector2D};
 | |
| use i_slint_core::graphics::rendering_metrics_collector::RenderingMetricsCollector;
 | |
| use i_slint_core::graphics::RequestedGraphicsAPI;
 | |
| use i_slint_core::graphics::{BorderRadius, FontRequest, SharedPixelBuffer};
 | |
| use i_slint_core::item_rendering::{DirtyRegion, ItemCache, ItemRenderer, PartialRenderingState};
 | |
| use i_slint_core::lengths::{
 | |
|     LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PhysicalPx, ScaleFactor,
 | |
| };
 | |
| use i_slint_core::platform::PlatformError;
 | |
| use i_slint_core::window::{WindowAdapter, WindowInner};
 | |
| use i_slint_core::Brush;
 | |
| 
 | |
| type PhysicalLength = euclid::Length<f32, PhysicalPx>;
 | |
| type PhysicalRect = euclid::Rect<f32, PhysicalPx>;
 | |
| type PhysicalSize = euclid::Size2D<f32, PhysicalPx>;
 | |
| type PhysicalPoint = euclid::Point2D<f32, PhysicalPx>;
 | |
| type PhysicalBorderRadius = BorderRadius<f32, PhysicalPx>;
 | |
| 
 | |
| mod cached_image;
 | |
| mod itemrenderer;
 | |
| mod textlayout;
 | |
| 
 | |
| #[cfg(skia_backend_software)]
 | |
| pub mod software_surface;
 | |
| 
 | |
| #[cfg(target_vendor = "apple")]
 | |
| pub mod metal_surface;
 | |
| 
 | |
| #[cfg(target_family = "windows")]
 | |
| pub mod d3d_surface;
 | |
| 
 | |
| #[cfg(skia_backend_vulkan)]
 | |
| pub mod vulkan_surface;
 | |
| 
 | |
| #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))]
 | |
| pub mod opengl_surface;
 | |
| 
 | |
| use i_slint_core::items::TextWrap;
 | |
| use itemrenderer::to_skia_rect;
 | |
| pub use skia_safe;
 | |
| 
 | |
| cfg_if::cfg_if! {
 | |
|     if #[cfg(skia_backend_vulkan)] {
 | |
|         type DefaultSurface = vulkan_surface::VulkanSurface;
 | |
|     } else if #[cfg(skia_backend_opengl)] {
 | |
|         type DefaultSurface = opengl_surface::OpenGLSurface;
 | |
|     } else if #[cfg(skia_backend_metal)] {
 | |
|         type DefaultSurface = metal_surface::MetalSurface;
 | |
|     } else if #[cfg(skia_backend_software)] {
 | |
|         type DefaultSurface = software_surface::SoftwareSurface;
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn create_default_surface(
 | |
|     context: &SkiaSharedContext,
 | |
|     window_handle: Arc<dyn raw_window_handle::HasWindowHandle>,
 | |
|     display_handle: Arc<dyn raw_window_handle::HasDisplayHandle>,
 | |
|     size: PhysicalWindowSize,
 | |
|     requested_graphics_api: Option<RequestedGraphicsAPI>,
 | |
| ) -> Result<Box<dyn Surface>, PlatformError> {
 | |
|     match DefaultSurface::new(
 | |
|         context,
 | |
|         window_handle.clone(),
 | |
|         display_handle.clone(),
 | |
|         size,
 | |
|         requested_graphics_api,
 | |
|     ) {
 | |
|         Ok(gpu_surface) => Ok(Box::new(gpu_surface) as Box<dyn Surface>),
 | |
|         #[cfg(skia_backend_software)]
 | |
|         Err(err) => {
 | |
|             i_slint_core::debug_log!(
 | |
|                 "Failed to initialize Skia GPU renderer: {} . Falling back to software rendering",
 | |
|                 err
 | |
|             );
 | |
|             software_surface::SoftwareSurface::new(
 | |
|                 context,
 | |
|                 window_handle,
 | |
|                 display_handle,
 | |
|                 size,
 | |
|                 None,
 | |
|             )
 | |
|             .map(|r| Box::new(r) as Box<dyn Surface>)
 | |
|         }
 | |
|         #[cfg(not(skia_backend_software))]
 | |
|         Err(err) => Err(err),
 | |
|     }
 | |
| }
 | |
| 
 | |
| enum DirtyRegionDebugMode {
 | |
|     NoDebug,
 | |
|     Visualize,
 | |
|     Log,
 | |
| }
 | |
| 
 | |
| impl Default for DirtyRegionDebugMode {
 | |
|     fn default() -> Self {
 | |
|         match std::env::var("SLINT_SKIA_PARTIAL_RENDERING").as_deref() {
 | |
|             Ok("visualize") => DirtyRegionDebugMode::Visualize,
 | |
|             Ok("log") => DirtyRegionDebugMode::Log,
 | |
|             _ => DirtyRegionDebugMode::NoDebug,
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn create_partial_renderer_state(
 | |
|     maybe_surface: Option<&dyn Surface>,
 | |
| ) -> Option<PartialRenderingState> {
 | |
|     maybe_surface
 | |
|         .map_or_else(
 | |
|             || std::env::var("SLINT_SKIA_PARTIAL_RENDERING").as_deref().is_ok(),
 | |
|             |surface| surface.use_partial_rendering(),
 | |
|         )
 | |
|         .then(|| PartialRenderingState::default())
 | |
| }
 | |
| 
 | |
| #[derive(Default)]
 | |
| struct SkiaSharedContextInner {
 | |
|     #[cfg(target_vendor = "apple")]
 | |
|     metal_context: OnceCell<metal_surface::SharedMetalContext>,
 | |
|     #[cfg(skia_backend_vulkan)]
 | |
|     vulkan_context: OnceCell<vulkan_surface::SharedVulkanContext>,
 | |
| }
 | |
| 
 | |
| /// This data structure contains data that's intended to be shared across several instances of SkiaRenderer.
 | |
| /// For example, for Vulkan rendering, this shares the Vulkan instance.
 | |
| ///
 | |
| /// Create an instance once and pass clones of it to the difference constructor functions, to ensure most
 | |
| /// efficient resource usage.
 | |
| #[derive(Clone, Default)]
 | |
| pub struct SkiaSharedContext(#[allow(dead_code)] Rc<SkiaSharedContextInner>);
 | |
| 
 | |
| /// Use the SkiaRenderer when implementing a custom Slint platform where you deliver events to
 | |
| /// Slint and want the scene to be rendered using Skia as underlying graphics library.
 | |
| pub struct SkiaRenderer {
 | |
|     maybe_window_adapter: RefCell<Option<Weak<dyn WindowAdapter>>>,
 | |
|     rendering_notifier: RefCell<Option<Box<dyn RenderingNotifier>>>,
 | |
|     image_cache: ItemCache<Option<skia_safe::Image>>,
 | |
|     path_cache: ItemCache<Option<(Vector2D<f32, PhysicalPx>, skia_safe::Path)>>,
 | |
|     rendering_metrics_collector: RefCell<Option<Rc<RenderingMetricsCollector>>>,
 | |
|     rendering_first_time: Cell<bool>,
 | |
|     surface: RefCell<Option<Box<dyn Surface>>>,
 | |
|     surface_factory: fn(
 | |
|         &SkiaSharedContext,
 | |
|         window_handle: Arc<dyn raw_window_handle::HasWindowHandle>,
 | |
|         display_handle: Arc<dyn raw_window_handle::HasDisplayHandle>,
 | |
|         size: PhysicalWindowSize,
 | |
|         requested_graphics_api: Option<RequestedGraphicsAPI>,
 | |
|     ) -> Result<Box<dyn Surface>, PlatformError>,
 | |
|     pre_present_callback: RefCell<Option<Box<dyn FnMut()>>>,
 | |
|     partial_rendering_state: Option<PartialRenderingState>,
 | |
|     dirty_region_debug_mode: DirtyRegionDebugMode,
 | |
|     /// Tracking dirty regions indexed by buffer age - 1. More than 3 back buffers aren't supported, but also unlikely to happen.
 | |
|     dirty_region_history: RefCell<[DirtyRegion; 3]>,
 | |
|     shared_context: SkiaSharedContext,
 | |
| }
 | |
| 
 | |
| impl SkiaRenderer {
 | |
|     pub fn default(context: &SkiaSharedContext) -> Self {
 | |
|         Self {
 | |
|             maybe_window_adapter: Default::default(),
 | |
|             rendering_notifier: Default::default(),
 | |
|             image_cache: Default::default(),
 | |
|             path_cache: Default::default(),
 | |
|             rendering_metrics_collector: Default::default(),
 | |
|             rendering_first_time: Default::default(),
 | |
|             surface: Default::default(),
 | |
|             surface_factory: create_default_surface,
 | |
|             pre_present_callback: Default::default(),
 | |
|             partial_rendering_state: create_partial_renderer_state(None),
 | |
|             dirty_region_debug_mode: Default::default(),
 | |
|             dirty_region_history: Default::default(),
 | |
|             shared_context: context.clone(),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[cfg(skia_backend_software)]
 | |
|     /// Creates a new SkiaRenderer that will always use Skia's software renderer.
 | |
|     pub fn default_software(context: &SkiaSharedContext) -> Self {
 | |
|         Self {
 | |
|             maybe_window_adapter: Default::default(),
 | |
|             rendering_notifier: Default::default(),
 | |
|             image_cache: Default::default(),
 | |
|             path_cache: Default::default(),
 | |
|             rendering_metrics_collector: Default::default(),
 | |
|             rendering_first_time: Default::default(),
 | |
|             surface: Default::default(),
 | |
|             surface_factory: |context,
 | |
|                               window_handle,
 | |
|                               display_handle,
 | |
|                               size,
 | |
|                               requested_graphics_api| {
 | |
|                 software_surface::SoftwareSurface::new(
 | |
|                     context,
 | |
|                     window_handle,
 | |
|                     display_handle,
 | |
|                     size,
 | |
|                     requested_graphics_api,
 | |
|                 )
 | |
|                 .map(|r| Box::new(r) as Box<dyn Surface>)
 | |
|             },
 | |
|             pre_present_callback: Default::default(),
 | |
|             partial_rendering_state: PartialRenderingState::default().into(),
 | |
|             dirty_region_debug_mode: Default::default(),
 | |
|             dirty_region_history: Default::default(),
 | |
|             shared_context: context.clone(),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[cfg(any(not(target_vendor = "apple"), target_os = "macos"))]
 | |
|     /// Creates a new SkiaRenderer that will always use Skia's OpenGL renderer.
 | |
|     pub fn default_opengl(context: &SkiaSharedContext) -> Self {
 | |
|         Self {
 | |
|             maybe_window_adapter: Default::default(),
 | |
|             rendering_notifier: Default::default(),
 | |
|             image_cache: Default::default(),
 | |
|             path_cache: Default::default(),
 | |
|             rendering_metrics_collector: Default::default(),
 | |
|             rendering_first_time: Default::default(),
 | |
|             surface: Default::default(),
 | |
|             surface_factory: |context,
 | |
|                               window_handle,
 | |
|                               display_handle,
 | |
|                               size,
 | |
|                               requested_graphics_api| {
 | |
|                 opengl_surface::OpenGLSurface::new(
 | |
|                     context,
 | |
|                     window_handle,
 | |
|                     display_handle,
 | |
|                     size,
 | |
|                     requested_graphics_api,
 | |
|                 )
 | |
|                 .map(|r| Box::new(r) as Box<dyn Surface>)
 | |
|             },
 | |
|             pre_present_callback: Default::default(),
 | |
|             partial_rendering_state: create_partial_renderer_state(None),
 | |
|             dirty_region_debug_mode: Default::default(),
 | |
|             dirty_region_history: Default::default(),
 | |
|             shared_context: context.clone(),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[cfg(target_vendor = "apple")]
 | |
|     /// Creates a new SkiaRenderer that will always use Skia's Metal renderer.
 | |
|     pub fn default_metal(context: &SkiaSharedContext) -> Self {
 | |
|         Self {
 | |
|             maybe_window_adapter: Default::default(),
 | |
|             rendering_notifier: Default::default(),
 | |
|             image_cache: Default::default(),
 | |
|             path_cache: Default::default(),
 | |
|             rendering_metrics_collector: Default::default(),
 | |
|             rendering_first_time: Default::default(),
 | |
|             surface: Default::default(),
 | |
|             surface_factory: |context,
 | |
|                               window_handle,
 | |
|                               display_handle,
 | |
|                               size,
 | |
|                               requested_graphics_api| {
 | |
|                 metal_surface::MetalSurface::new(
 | |
|                     context,
 | |
|                     window_handle,
 | |
|                     display_handle,
 | |
|                     size,
 | |
|                     requested_graphics_api,
 | |
|                 )
 | |
|                 .map(|r| Box::new(r) as Box<dyn Surface>)
 | |
|             },
 | |
|             pre_present_callback: Default::default(),
 | |
|             partial_rendering_state: create_partial_renderer_state(None),
 | |
|             dirty_region_debug_mode: Default::default(),
 | |
|             dirty_region_history: Default::default(),
 | |
|             shared_context: context.clone(),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[cfg(skia_backend_vulkan)]
 | |
|     /// Creates a new SkiaRenderer that will always use Skia's Vulkan renderer.
 | |
|     pub fn default_vulkan(context: &SkiaSharedContext) -> Self {
 | |
|         Self {
 | |
|             maybe_window_adapter: Default::default(),
 | |
|             rendering_notifier: Default::default(),
 | |
|             image_cache: Default::default(),
 | |
|             path_cache: Default::default(),
 | |
|             rendering_metrics_collector: Default::default(),
 | |
|             rendering_first_time: Default::default(),
 | |
|             surface: Default::default(),
 | |
|             surface_factory: |context,
 | |
|                               window_handle,
 | |
|                               display_handle,
 | |
|                               size,
 | |
|                               requested_graphics_api| {
 | |
|                 vulkan_surface::VulkanSurface::new(
 | |
|                     context,
 | |
|                     window_handle,
 | |
|                     display_handle,
 | |
|                     size,
 | |
|                     requested_graphics_api,
 | |
|                 )
 | |
|                 .map(|r| Box::new(r) as Box<dyn Surface>)
 | |
|             },
 | |
|             pre_present_callback: Default::default(),
 | |
|             partial_rendering_state: create_partial_renderer_state(None),
 | |
|             dirty_region_debug_mode: Default::default(),
 | |
|             dirty_region_history: Default::default(),
 | |
|             shared_context: context.clone(),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[cfg(target_family = "windows")]
 | |
|     /// Creates a new SkiaRenderer that will always use Skia's Direct3D renderer.
 | |
|     pub fn default_direct3d(context: &SkiaSharedContext) -> Self {
 | |
|         Self {
 | |
|             maybe_window_adapter: Default::default(),
 | |
|             rendering_notifier: Default::default(),
 | |
|             image_cache: Default::default(),
 | |
|             path_cache: Default::default(),
 | |
|             rendering_metrics_collector: Default::default(),
 | |
|             rendering_first_time: Default::default(),
 | |
|             surface: Default::default(),
 | |
|             surface_factory: |context,
 | |
|                               window_handle,
 | |
|                               display_handle,
 | |
|                               size,
 | |
|                               requested_graphics_api| {
 | |
|                 d3d_surface::D3DSurface::new(
 | |
|                     context,
 | |
|                     window_handle,
 | |
|                     display_handle,
 | |
|                     size,
 | |
|                     requested_graphics_api,
 | |
|                 )
 | |
|                 .map(|r| Box::new(r) as Box<dyn Surface>)
 | |
|             },
 | |
|             pre_present_callback: Default::default(),
 | |
|             partial_rendering_state: create_partial_renderer_state(None),
 | |
|             dirty_region_debug_mode: Default::default(),
 | |
|             dirty_region_history: Default::default(),
 | |
|             shared_context: context.clone(),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Creates a new renderer is associated with the provided window adapter.
 | |
|     pub fn new(
 | |
|         context: &SkiaSharedContext,
 | |
|         window_handle: Arc<dyn raw_window_handle::HasWindowHandle>,
 | |
|         display_handle: Arc<dyn raw_window_handle::HasDisplayHandle>,
 | |
|         size: PhysicalWindowSize,
 | |
|     ) -> Result<Self, PlatformError> {
 | |
|         Ok(Self::new_with_surface(
 | |
|             context,
 | |
|             create_default_surface(context, window_handle, display_handle, size, None)?,
 | |
|         ))
 | |
|     }
 | |
| 
 | |
|     /// Creates a new renderer with the given surface trait implementation.
 | |
|     pub fn new_with_surface(
 | |
|         context: &SkiaSharedContext,
 | |
|         surface: Box<dyn Surface + 'static>,
 | |
|     ) -> Self {
 | |
|         let partial_rendering_state = create_partial_renderer_state(Some(surface.as_ref())).into();
 | |
|         Self {
 | |
|             maybe_window_adapter: Default::default(),
 | |
|             rendering_notifier: Default::default(),
 | |
|             image_cache: Default::default(),
 | |
|             path_cache: Default::default(),
 | |
|             rendering_metrics_collector: Default::default(),
 | |
|             rendering_first_time: Cell::new(true),
 | |
|             surface: RefCell::new(Some(surface)),
 | |
|             surface_factory: |_, _, _, _, _| {
 | |
|                 Err("Skia renderer constructed with surface does not support dynamic surface re-creation".into())
 | |
|             },
 | |
|             pre_present_callback: Default::default(),
 | |
|             partial_rendering_state,
 | |
|             dirty_region_debug_mode: Default::default(),
 | |
|             dirty_region_history: Default::default(),
 | |
|             shared_context: context.clone(),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Reset the surface to a new surface. (destroy the previously set surface if any)
 | |
|     pub fn set_surface(&self, surface: Box<dyn Surface + 'static>) {
 | |
|         self.image_cache.clear_all();
 | |
|         self.path_cache.clear_all();
 | |
|         self.rendering_first_time.set(true);
 | |
|         *self.surface.borrow_mut() = Some(surface);
 | |
|     }
 | |
| 
 | |
|     fn clear_surface(&self) {
 | |
|         let Some(surface) = self.surface.borrow_mut().take() else {
 | |
|             return;
 | |
|         };
 | |
| 
 | |
|         // If we've rendered a frame before, then we need to invoke the RenderingTearDown notifier.
 | |
|         if !self.rendering_first_time.get() {
 | |
|             if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
 | |
|                 surface
 | |
|                     .with_active_surface(&mut || {
 | |
|                         surface.with_graphics_api(&mut |api| {
 | |
|                             callback.notify(RenderingState::RenderingTeardown, &api)
 | |
|                         })
 | |
|                     })
 | |
|                     .ok();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         drop(surface);
 | |
|     }
 | |
| 
 | |
|     /// Suspends the renderer by freeing all graphics related resources as well as the underlying
 | |
|     /// rendering surface. Call [`Self::set_window_handle()`] to re-associate the renderer with a new
 | |
|     /// window surface for subsequent rendering.
 | |
|     pub fn suspend(&self) -> Result<(), PlatformError> {
 | |
|         self.image_cache.clear_all();
 | |
|         self.path_cache.clear_all();
 | |
|         // Destroy the old surface before allocating the new one, to work around
 | |
|         // the vivante drivers using zwp_linux_explicit_synchronization_v1 and
 | |
|         // trying to create a second synchronization object and that's not allowed.
 | |
|         self.clear_surface();
 | |
|         Ok(())
 | |
|     }
 | |
| 
 | |
|     /// Reset the surface to the window given the window handle
 | |
|     pub fn set_window_handle(
 | |
|         &self,
 | |
|         window_handle: Arc<dyn raw_window_handle::HasWindowHandle>,
 | |
|         display_handle: Arc<dyn raw_window_handle::HasDisplayHandle>,
 | |
|         size: PhysicalWindowSize,
 | |
|         requested_graphics_api: Option<RequestedGraphicsAPI>,
 | |
|     ) -> Result<(), PlatformError> {
 | |
|         // just in case
 | |
|         self.suspend()?;
 | |
|         let surface = (self.surface_factory)(
 | |
|             &self.shared_context,
 | |
|             window_handle,
 | |
|             display_handle,
 | |
|             size,
 | |
|             requested_graphics_api,
 | |
|         )?;
 | |
|         self.set_surface(surface);
 | |
|         Ok(())
 | |
|     }
 | |
| 
 | |
|     /// Render the scene in the previously associated window.
 | |
|     pub fn render(&self) -> Result<(), i_slint_core::platform::PlatformError> {
 | |
|         let window_adapter = self.window_adapter()?;
 | |
|         let size = window_adapter.window().size();
 | |
|         self.internal_render_with_post_callback(0., (0., 0.), size, None)
 | |
|     }
 | |
| 
 | |
|     fn internal_render_with_post_callback(
 | |
|         &self,
 | |
|         rotation_angle_degrees: f32,
 | |
|         translation: (f32, f32),
 | |
|         surface_size: PhysicalWindowSize,
 | |
|         post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
 | |
|     ) -> Result<(), i_slint_core::platform::PlatformError> {
 | |
|         let surface = self.surface.borrow();
 | |
|         let Some(surface) = surface.as_ref() else { return Ok(()) };
 | |
|         if self.rendering_first_time.take() {
 | |
|             *self.rendering_metrics_collector.borrow_mut() =
 | |
|                 RenderingMetricsCollector::new(&format!(
 | |
|                     "Skia renderer (skia backend {}; surface: {} bpp)",
 | |
|                     surface.name(),
 | |
|                     surface.bits_per_pixel()?
 | |
|                 ));
 | |
| 
 | |
|             if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
 | |
|                 surface.with_graphics_api(&mut |api| {
 | |
|                     callback.notify(RenderingState::RenderingSetup, &api)
 | |
|                 })
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         let window_adapter = self.window_adapter()?;
 | |
|         let window = window_adapter.window();
 | |
| 
 | |
|         surface.render(
 | |
|             window,
 | |
|             surface_size,
 | |
|             &|skia_canvas, gr_context, back_buffer_age| {
 | |
|                 self.render_to_canvas(
 | |
|                     skia_canvas,
 | |
|                     rotation_angle_degrees,
 | |
|                     translation,
 | |
|                     gr_context,
 | |
|                     back_buffer_age,
 | |
|                     Some(surface.as_ref()),
 | |
|                     window,
 | |
|                     post_render_cb,
 | |
|                 )
 | |
|             },
 | |
|             &self.pre_present_callback,
 | |
|         )
 | |
|     }
 | |
| 
 | |
|     fn render_to_canvas(
 | |
|         &self,
 | |
|         skia_canvas: &skia_safe::Canvas,
 | |
|         rotation_angle_degrees: f32,
 | |
|         translation: (f32, f32),
 | |
|         gr_context: Option<&mut skia_safe::gpu::DirectContext>,
 | |
|         back_buffer_age: u8,
 | |
|         surface: Option<&dyn Surface>,
 | |
|         window: &i_slint_core::api::Window,
 | |
|         post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
 | |
|     ) -> Option<DirtyRegion> {
 | |
|         skia_canvas.rotate(rotation_angle_degrees, None);
 | |
|         skia_canvas.translate(translation);
 | |
| 
 | |
|         let window_inner = WindowInner::from_pub(window);
 | |
| 
 | |
|         let dirty_region = window_inner
 | |
|             .draw_contents(|components| {
 | |
|                 self.render_components_to_canvas(
 | |
|                     skia_canvas,
 | |
|                     gr_context,
 | |
|                     back_buffer_age,
 | |
|                     surface,
 | |
|                     window,
 | |
|                     post_render_cb,
 | |
|                     components,
 | |
|                 )
 | |
|             })
 | |
|             .unwrap_or_default();
 | |
| 
 | |
|         if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
 | |
|             if let Some(surface) = surface {
 | |
|                 surface.with_graphics_api(&mut |api| {
 | |
|                     callback.notify(RenderingState::AfterRendering, &api)
 | |
|                 })
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         dirty_region
 | |
|     }
 | |
| 
 | |
|     fn render_components_to_canvas(
 | |
|         &self,
 | |
|         skia_canvas: &skia_safe::Canvas,
 | |
|         mut gr_context: Option<&mut skia_safe::gpu::DirectContext>,
 | |
|         back_buffer_age: u8,
 | |
|         surface: Option<&dyn Surface>,
 | |
|         window: &i_slint_core::api::Window,
 | |
|         post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
 | |
|         components: &[(&i_slint_core::item_tree::ItemTreeRc, LogicalPoint)],
 | |
|     ) -> Option<DirtyRegion> {
 | |
|         let window_inner = WindowInner::from_pub(window);
 | |
|         let window_adapter = window_inner.window_adapter();
 | |
| 
 | |
|         let mut box_shadow_cache = Default::default();
 | |
| 
 | |
|         self.image_cache.clear_cache_if_scale_factor_changed(window);
 | |
|         self.path_cache.clear_cache_if_scale_factor_changed(window);
 | |
| 
 | |
|         let mut skia_item_renderer = itemrenderer::SkiaItemRenderer::new(
 | |
|             skia_canvas,
 | |
|             window,
 | |
|             surface,
 | |
|             &self.image_cache,
 | |
|             &self.path_cache,
 | |
|             &mut box_shadow_cache,
 | |
|         );
 | |
| 
 | |
|         let scale_factor = ScaleFactor::new(window_inner.scale_factor());
 | |
|         let logical_window_size = i_slint_core::lengths::logical_size_from_api(
 | |
|             window.size().to_logical(window_inner.scale_factor()),
 | |
|         );
 | |
| 
 | |
|         let mut dirty_region = None;
 | |
| 
 | |
|         {
 | |
|             let mut item_renderer: &mut dyn ItemRenderer = &mut skia_item_renderer;
 | |
|             let mut partial_renderer;
 | |
|             let mut dirty_region_to_visualize = None;
 | |
| 
 | |
|             if let Some(partial_rendering_state) = self.partial_rendering_state() {
 | |
|                 partial_renderer =
 | |
|                     partial_rendering_state.create_partial_renderer(skia_item_renderer);
 | |
| 
 | |
|                 let mut dirty_region_history = self.dirty_region_history.borrow_mut();
 | |
| 
 | |
|                 let buffer_dirty_region = if back_buffer_age > 0
 | |
|                     && back_buffer_age as usize - 1 < dirty_region_history.len()
 | |
|                 {
 | |
|                     // The dirty region is the union of all the previous dirty regions
 | |
|                     Some(
 | |
|                         dirty_region_history[0..back_buffer_age as usize - 1]
 | |
|                             .iter()
 | |
|                             .fold(DirtyRegion::default(), |acc, region| acc.union(region)),
 | |
|                     )
 | |
|                 } else {
 | |
|                     Some(LogicalRect::from_size(logical_window_size).into())
 | |
|                 };
 | |
| 
 | |
|                 let dirty_region_for_this_frame = partial_rendering_state.apply_dirty_region(
 | |
|                     &mut partial_renderer,
 | |
|                     components,
 | |
|                     logical_window_size,
 | |
|                     buffer_dirty_region,
 | |
|                 );
 | |
| 
 | |
|                 let mut clip_path = skia_safe::Path::new();
 | |
| 
 | |
|                 for dirty_rect in partial_renderer.dirty_region.iter() {
 | |
|                     let physical_rect = (dirty_rect * scale_factor).to_rect().round_out();
 | |
|                     clip_path.add_rect(&to_skia_rect(&physical_rect), None);
 | |
|                 }
 | |
| 
 | |
|                 if matches!(self.dirty_region_debug_mode, DirtyRegionDebugMode::Log) {
 | |
|                     let area_to_repaint: f32 =
 | |
|                         partial_renderer.dirty_region.iter().map(|b| b.area()).sum();
 | |
|                     i_slint_core::debug_log!(
 | |
|                         "repainting {:.2}%",
 | |
|                         area_to_repaint * 100. / logical_window_size.area()
 | |
|                     );
 | |
|                 }
 | |
| 
 | |
|                 dirty_region = partial_renderer.dirty_region.clone().into();
 | |
| 
 | |
|                 dirty_region_history.rotate_right(1);
 | |
|                 dirty_region_history[0] = dirty_region_for_this_frame;
 | |
| 
 | |
|                 skia_canvas.clip_path(&clip_path, None, false);
 | |
| 
 | |
|                 if matches!(self.dirty_region_debug_mode, DirtyRegionDebugMode::Visualize) {
 | |
|                     dirty_region_to_visualize = Some(clip_path);
 | |
|                 }
 | |
| 
 | |
|                 item_renderer = &mut partial_renderer;
 | |
|             }
 | |
| 
 | |
|             if let Some(window_item_rc) = window_inner.window_item_rc() {
 | |
|                 let window_item =
 | |
|                     window_item_rc.downcast::<i_slint_core::items::WindowItem>().unwrap();
 | |
|                 match window_item.as_pin_ref().background() {
 | |
|                     Brush::SolidColor(clear_color) => {
 | |
|                         skia_canvas.clear(itemrenderer::to_skia_color(&clear_color));
 | |
|                     }
 | |
|                     _ => {
 | |
|                         // Draws the window background as gradient
 | |
|                         item_renderer.draw_rectangle(
 | |
|                             window_item.as_pin_ref(),
 | |
|                             &window_item_rc,
 | |
|                             i_slint_core::lengths::logical_size_from_api(
 | |
|                                 window.size().to_logical(window_inner.scale_factor()),
 | |
|                             ),
 | |
|                             &window_item.as_pin_ref().cached_rendering_data,
 | |
|                         );
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if let Some(callback) = self.rendering_notifier.borrow_mut().as_mut() {
 | |
|                 // For the BeforeRendering rendering notifier callback it's important that this happens *after* clearing
 | |
|                 // the back buffer, in order to allow the callback to provide its own rendering of the background.
 | |
|                 // Skia's clear() will merely schedule a clear call, so flush right away to make it immediate.
 | |
|                 if let Some(ctx) = gr_context.as_mut() {
 | |
|                     ctx.flush(None);
 | |
|                 }
 | |
| 
 | |
|                 if let Some(surface) = surface {
 | |
|                     surface.with_graphics_api(&mut |api| {
 | |
|                         callback.notify(RenderingState::BeforeRendering, &api)
 | |
|                     })
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             for (component, origin) in components {
 | |
|                 i_slint_core::item_rendering::render_component_items(
 | |
|                     component,
 | |
|                     item_renderer,
 | |
|                     *origin,
 | |
|                     &window_adapter,
 | |
|                 );
 | |
|             }
 | |
| 
 | |
|             if let Some(path) = dirty_region_to_visualize {
 | |
|                 let mut paint = skia_safe::Paint::new(
 | |
|                     &skia_safe::Color4f { a: 0.5, r: 1.0, g: 0., b: 0. },
 | |
|                     None,
 | |
|                 );
 | |
|                 paint.set_style(skia_safe::PaintStyle::Stroke);
 | |
|                 skia_canvas.draw_path(&path, &paint);
 | |
|             }
 | |
| 
 | |
|             if let Some(collector) = &self.rendering_metrics_collector.borrow_mut().as_ref() {
 | |
|                 collector.measure_frame_rendered(item_renderer);
 | |
|                 if collector.refresh_mode()
 | |
|                     == i_slint_core::graphics::rendering_metrics_collector::RefreshMode::FullSpeed
 | |
|                 {
 | |
|                     if let Some(partial_rendering_state) = self.partial_rendering_state() {
 | |
|                         partial_rendering_state.force_screen_refresh();
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if let Some(cb) = post_render_cb.as_ref() {
 | |
|                 cb(item_renderer)
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if let Some(ctx) = gr_context.as_mut() {
 | |
|             ctx.flush(None);
 | |
|         }
 | |
| 
 | |
|         dirty_region
 | |
|     }
 | |
| 
 | |
|     fn window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, PlatformError> {
 | |
|         self.maybe_window_adapter.borrow().as_ref().and_then(|w| w.upgrade()).ok_or_else(|| {
 | |
|             "Renderer must be associated with component before use".to_string().into()
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     /// Sets the specified callback, that's invoked before presenting the rendered buffer to the windowing system.
 | |
|     /// This can be useful to implement frame throttling, i.e. for requesting a frame callback from the wayland compositor.
 | |
|     pub fn set_pre_present_callback(&self, callback: Option<Box<dyn FnMut()>>) {
 | |
|         *self.pre_present_callback.borrow_mut() = callback;
 | |
|     }
 | |
| 
 | |
|     fn partial_rendering_state(&self) -> Option<&PartialRenderingState> {
 | |
|         // We don't know where the application might render to, so disable partial rendering.
 | |
|         if self.rendering_notifier.borrow().is_some() {
 | |
|             None
 | |
|         } else {
 | |
|             self.partial_rendering_state.as_ref()
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl i_slint_core::renderer::RendererSealed for SkiaRenderer {
 | |
|     fn text_size(
 | |
|         &self,
 | |
|         font_request: i_slint_core::graphics::FontRequest,
 | |
|         text: &str,
 | |
|         max_width: Option<LogicalLength>,
 | |
|         scale_factor: ScaleFactor,
 | |
|         _text_wrap: TextWrap, //TODO: Add support for char-wrap
 | |
|     ) -> LogicalSize {
 | |
|         let (layout, _) = textlayout::create_layout(
 | |
|             font_request,
 | |
|             scale_factor,
 | |
|             text,
 | |
|             None,
 | |
|             max_width.map(|w| w * scale_factor),
 | |
|             Default::default(),
 | |
|             Default::default(),
 | |
|             Default::default(),
 | |
|             Default::default(),
 | |
|             Default::default(),
 | |
|             None,
 | |
|         );
 | |
| 
 | |
|         PhysicalSize::new(layout.max_intrinsic_width().ceil(), layout.height().ceil())
 | |
|             / scale_factor
 | |
|     }
 | |
| 
 | |
|     fn font_metrics(
 | |
|         &self,
 | |
|         font_request: i_slint_core::graphics::FontRequest,
 | |
|         scale_factor: ScaleFactor,
 | |
|     ) -> i_slint_core::items::FontMetrics {
 | |
|         textlayout::font_metrics(font_request, scale_factor)
 | |
|     }
 | |
| 
 | |
|     fn text_input_byte_offset_for_position(
 | |
|         &self,
 | |
|         text_input: std::pin::Pin<&i_slint_core::items::TextInput>,
 | |
|         pos: LogicalPoint,
 | |
|         font_request: FontRequest,
 | |
|         scale_factor: ScaleFactor,
 | |
|     ) -> usize {
 | |
|         let max_width = text_input.width() * scale_factor;
 | |
|         let max_height = text_input.height() * scale_factor;
 | |
|         let pos = pos * scale_factor;
 | |
| 
 | |
|         if max_width.get() <= 0. || max_height.get() <= 0. {
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         let visual_representation = text_input.visual_representation(None);
 | |
| 
 | |
|         let (layout, layout_top_left) = textlayout::create_layout(
 | |
|             font_request,
 | |
|             scale_factor,
 | |
|             &visual_representation.text,
 | |
|             None,
 | |
|             Some(max_width),
 | |
|             max_height,
 | |
|             text_input.horizontal_alignment(),
 | |
|             text_input.vertical_alignment(),
 | |
|             text_input.wrap(),
 | |
|             i_slint_core::items::TextOverflow::Clip,
 | |
|             None,
 | |
|         );
 | |
| 
 | |
|         let utf16_index =
 | |
|             layout.get_glyph_position_at_coordinate((pos.x, pos.y - layout_top_left.y)).position;
 | |
|         let mut utf16_count = 0;
 | |
|         let byte_offset = visual_representation
 | |
|             .text
 | |
|             .char_indices()
 | |
|             .find(|(_, x)| {
 | |
|                 let r = utf16_count >= utf16_index;
 | |
|                 utf16_count += x.len_utf16() as i32;
 | |
|                 r
 | |
|             })
 | |
|             .unwrap_or((visual_representation.text.len(), '\0'))
 | |
|             .0;
 | |
| 
 | |
|         visual_representation.map_byte_offset_from_byte_offset_in_visual_text(byte_offset)
 | |
|     }
 | |
| 
 | |
|     fn text_input_cursor_rect_for_byte_offset(
 | |
|         &self,
 | |
|         text_input: std::pin::Pin<&i_slint_core::items::TextInput>,
 | |
|         byte_offset: usize,
 | |
|         font_request: FontRequest,
 | |
|         scale_factor: ScaleFactor,
 | |
|     ) -> LogicalRect {
 | |
|         let max_width = text_input.width() * scale_factor;
 | |
|         let max_height = text_input.height() * scale_factor;
 | |
| 
 | |
|         if max_width.get() <= 0. || max_height.get() <= 0. {
 | |
|             return Default::default();
 | |
|         }
 | |
| 
 | |
|         let string = text_input.text();
 | |
|         let string = string.as_str();
 | |
| 
 | |
|         let (layout, layout_top_left) = textlayout::create_layout(
 | |
|             font_request,
 | |
|             scale_factor,
 | |
|             string,
 | |
|             None,
 | |
|             Some(max_width),
 | |
|             max_height,
 | |
|             text_input.horizontal_alignment(),
 | |
|             text_input.vertical_alignment(),
 | |
|             text_input.wrap(),
 | |
|             i_slint_core::items::TextOverflow::Clip,
 | |
|             None,
 | |
|         );
 | |
| 
 | |
|         let physical_cursor_rect = textlayout::cursor_rect(
 | |
|             string,
 | |
|             byte_offset,
 | |
|             layout,
 | |
|             text_input.text_cursor_width() * scale_factor,
 | |
|             text_input.horizontal_alignment(),
 | |
|         );
 | |
| 
 | |
|         physical_cursor_rect.translate(layout_top_left.to_vector()) / scale_factor
 | |
|     }
 | |
| 
 | |
|     fn register_font_from_memory(
 | |
|         &self,
 | |
|         data: &'static [u8],
 | |
|     ) -> Result<(), Box<dyn std::error::Error>> {
 | |
|         textlayout::register_font_from_memory(data)
 | |
|     }
 | |
| 
 | |
|     fn register_font_from_path(
 | |
|         &self,
 | |
|         path: &std::path::Path,
 | |
|     ) -> Result<(), Box<dyn std::error::Error>> {
 | |
|         textlayout::register_font_from_path(path)
 | |
|     }
 | |
| 
 | |
|     fn set_rendering_notifier(
 | |
|         &self,
 | |
|         callback: Box<dyn RenderingNotifier>,
 | |
|     ) -> std::result::Result<(), SetRenderingNotifierError> {
 | |
|         if !self.surface.borrow().as_ref().map_or(DefaultSurface::supports_graphics_api(), |x| {
 | |
|             x.supports_graphics_api_with_self()
 | |
|         }) {
 | |
|             return Err(SetRenderingNotifierError::Unsupported);
 | |
|         }
 | |
|         let mut notifier = self.rendering_notifier.borrow_mut();
 | |
|         if notifier.replace(callback).is_some() {
 | |
|             Err(SetRenderingNotifierError::AlreadySet)
 | |
|         } else {
 | |
|             Ok(())
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn free_graphics_resources(
 | |
|         &self,
 | |
|         component: i_slint_core::item_tree::ItemTreeRef,
 | |
|         items: &mut dyn Iterator<Item = std::pin::Pin<i_slint_core::items::ItemRef<'_>>>,
 | |
|     ) -> Result<(), i_slint_core::platform::PlatformError> {
 | |
|         self.image_cache.component_destroyed(component);
 | |
|         self.path_cache.component_destroyed(component);
 | |
| 
 | |
|         if let Some(partial_rendering_state) = self.partial_rendering_state() {
 | |
|             partial_rendering_state.free_graphics_resources(items);
 | |
|         }
 | |
| 
 | |
|         Ok(())
 | |
|     }
 | |
| 
 | |
|     fn set_window_adapter(&self, window_adapter: &Rc<dyn WindowAdapter>) {
 | |
|         *self.maybe_window_adapter.borrow_mut() = Some(Rc::downgrade(window_adapter));
 | |
|         self.image_cache.clear_all();
 | |
|         self.path_cache.clear_all();
 | |
| 
 | |
|         if let Some(partial_rendering_state) = self.partial_rendering_state() {
 | |
|             partial_rendering_state.clear_cache();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn resize(&self, size: i_slint_core::api::PhysicalSize) -> Result<(), PlatformError> {
 | |
|         if let Some(surface) = self.surface.borrow().as_ref() {
 | |
|             surface.resize_event(size)
 | |
|         } else {
 | |
|             Ok(())
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Returns an image buffer of what was rendered last by reading the previous front buffer (using glReadPixels).
 | |
|     fn take_snapshot(
 | |
|         &self,
 | |
|     ) -> Result<SharedPixelBuffer<i_slint_core::graphics::Rgba8Pixel>, PlatformError> {
 | |
|         let window_adapter = self.window_adapter()?;
 | |
|         let window = window_adapter.window();
 | |
|         let size = window_adapter.window().size();
 | |
|         let (width, height) = (size.width, size.height);
 | |
|         let mut target_buffer =
 | |
|             SharedPixelBuffer::<i_slint_core::graphics::Rgba8Pixel>::new(width, height);
 | |
| 
 | |
|         let mut surface_borrow = skia_safe::surfaces::wrap_pixels(
 | |
|             &skia_safe::ImageInfo::new(
 | |
|                 (width as i32, height as i32),
 | |
|                 skia_safe::ColorType::RGBA8888,
 | |
|                 skia_safe::AlphaType::Opaque,
 | |
|                 None,
 | |
|             ),
 | |
|             target_buffer.make_mut_bytes(),
 | |
|             None,
 | |
|             None,
 | |
|         )
 | |
|         .ok_or_else(|| "Error wrapping target buffer for rendering into with Skia".to_string())?;
 | |
| 
 | |
|         self.render_to_canvas(surface_borrow.canvas(), 0., (0.0, 0.0), None, 0, None, window, None);
 | |
| 
 | |
|         Ok(target_buffer)
 | |
|     }
 | |
| 
 | |
|     fn mark_dirty_region(&self, region: i_slint_core::item_rendering::DirtyRegion) {
 | |
|         if let Some(partial_rendering_state) = self.partial_rendering_state() {
 | |
|             partial_rendering_state.mark_dirty_region(region);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Drop for SkiaRenderer {
 | |
|     fn drop(&mut self) {
 | |
|         self.clear_surface()
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// This trait represents the interface between the Skia renderer and the underlying rendering surface, such as a window
 | |
| /// with a metal layer, a wayland window with an OpenGL context, etc.
 | |
| pub trait Surface {
 | |
|     /// Creates a new surface with the given window, display, and size.
 | |
|     fn new(
 | |
|         shared_context: &SkiaSharedContext,
 | |
|         window_handle: Arc<dyn raw_window_handle::HasWindowHandle>,
 | |
|         display_handle: Arc<dyn raw_window_handle::HasDisplayHandle>,
 | |
|         size: PhysicalWindowSize,
 | |
|         requested_graphics_api: Option<RequestedGraphicsAPI>,
 | |
|     ) -> Result<Self, PlatformError>
 | |
|     where
 | |
|         Self: Sized;
 | |
|     /// Returns the name of the surface, for diagnostic purposes.
 | |
|     fn name(&self) -> &'static str;
 | |
|     /// Returns true if the surface supports exposing its platform specific API via the GraphicsAPI struct
 | |
|     /// and the `with_graphics_api` function.
 | |
|     fn supports_graphics_api() -> bool
 | |
|     where
 | |
|         Self: Sized,
 | |
|     {
 | |
|         false
 | |
|     }
 | |
| 
 | |
|     fn supports_graphics_api_with_self(&self) -> bool {
 | |
|         false
 | |
|     }
 | |
| 
 | |
|     /// If supported, this invokes the specified callback with access to the platform graphics API.
 | |
|     fn with_graphics_api(&self, _callback: &mut dyn FnMut(GraphicsAPI<'_>)) {}
 | |
|     /// Invokes the callback with the surface active. This has only a meaning for OpenGL rendering, where
 | |
|     /// the implementation must make the GL context current.
 | |
|     fn with_active_surface(
 | |
|         &self,
 | |
|         callback: &mut dyn FnMut(),
 | |
|     ) -> Result<(), i_slint_core::platform::PlatformError> {
 | |
|         callback();
 | |
|         Ok(())
 | |
|     }
 | |
|     /// Prepares the surface for rendering and invokes the provided callback with access to a Skia canvas and
 | |
|     /// rendering context.
 | |
|     fn render(
 | |
|         &self,
 | |
|         window: &Window,
 | |
|         size: PhysicalWindowSize,
 | |
|         render_callback: &dyn Fn(
 | |
|             &skia_safe::Canvas,
 | |
|             Option<&mut skia_safe::gpu::DirectContext>,
 | |
|             u8,
 | |
|         ) -> Option<DirtyRegion>,
 | |
|         pre_present_callback: &RefCell<Option<Box<dyn FnMut()>>>,
 | |
|     ) -> Result<(), i_slint_core::platform::PlatformError>;
 | |
|     /// Called when the surface should be resized.
 | |
|     fn resize_event(
 | |
|         &self,
 | |
|         size: PhysicalWindowSize,
 | |
|     ) -> Result<(), i_slint_core::platform::PlatformError>;
 | |
|     fn bits_per_pixel(&self) -> Result<u8, PlatformError>;
 | |
| 
 | |
|     fn use_partial_rendering(&self) -> bool {
 | |
|         false
 | |
|     }
 | |
| 
 | |
|     fn import_opengl_texture(
 | |
|         &self,
 | |
|         _canvas: &skia_safe::Canvas,
 | |
|         _texture: &i_slint_core::graphics::BorrowedOpenGLTexture,
 | |
|     ) -> Option<skia_safe::Image> {
 | |
|         None
 | |
|     }
 | |
| 
 | |
|     /// Implementations should return self to allow upcasting.
 | |
|     fn as_any(&self) -> &dyn core::any::Any {
 | |
|         &()
 | |
|     }
 | |
| }
 | |
| 
 | |
| pub trait SkiaRendererExt {
 | |
|     fn render_transformed_with_post_callback(
 | |
|         &self,
 | |
|         rotation_angle_degrees: f32,
 | |
|         translation: (f32, f32),
 | |
|         surface_size: PhysicalWindowSize,
 | |
|         post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
 | |
|     ) -> Result<(), i_slint_core::platform::PlatformError>;
 | |
| }
 | |
| 
 | |
| impl SkiaRendererExt for SkiaRenderer {
 | |
|     fn render_transformed_with_post_callback(
 | |
|         &self,
 | |
|         rotation_angle_degrees: f32,
 | |
|         translation: (f32, f32),
 | |
|         surface_size: PhysicalWindowSize,
 | |
|         post_render_cb: Option<&dyn Fn(&mut dyn ItemRenderer)>,
 | |
|     ) -> Result<(), i_slint_core::platform::PlatformError> {
 | |
|         self.internal_render_with_post_callback(
 | |
|             rotation_angle_degrees,
 | |
|             translation,
 | |
|             surface_size,
 | |
|             post_render_cb,
 | |
|         )
 | |
|     }
 | |
| }
 |