// 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_attr(slint_nightly_test, feature(non_exhaustive_omitted_patterns_lint))]
#![cfg_attr(slint_nightly_test, warn(non_exhaustive_omitted_patterns))]

#[cfg(any(target_vendor = "apple", skia_backend_vulkan))]
use std::cell::OnceCell;
use std::cell::{Cell, RefCell};
use std::pin::Pin;
use std::rc::{Rc, Weak};
use std::sync::Arc;

use i_slint_common::sharedfontique;
use i_slint_core::Brush;
use i_slint_core::api::{
    GraphicsAPI, PhysicalSize as PhysicalWindowSize, RenderingNotifier, RenderingState,
    SetRenderingNotifierError, Window,
};
use i_slint_core::graphics::RequestedGraphicsAPI;
use i_slint_core::graphics::euclid::{self, Vector2D};
use i_slint_core::graphics::rendering_metrics_collector::RenderingMetricsCollector;
use i_slint_core::graphics::{BorderRadius, SharedPixelBuffer};
use i_slint_core::item_rendering::{ItemCache, ItemRenderer};
use i_slint_core::item_tree::ItemTreeWeak;
use i_slint_core::lengths::{
    LogicalLength, LogicalPoint, LogicalRect, LogicalSize, PhysicalPx, ScaleFactor,
};
use i_slint_core::partial_renderer::{DirtyRegion, PartialRenderingState};
use i_slint_core::platform::PlatformError;
use i_slint_core::textlayout::sharedparley;
use i_slint_core::window::{WindowAdapter, WindowInner};

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 font_cache;
mod itemrenderer;

#[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;

#[cfg(feature = "unstable-wgpu-26")]
mod wgpu_26_surface;
#[cfg(feature = "unstable-wgpu-27")]
mod wgpu_27_surface;

use i_slint_core::items::{ItemRc, 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 + Sync + Send>,
    display_handle: Arc<dyn raw_window_handle::HasDisplayHandle + Sync + Send>,
    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>>,
    layer_cache: ItemCache<Option<(Vector2D<f32, PhysicalPx>, 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 + Send + Sync>,
        display_handle: Arc<dyn raw_window_handle::HasDisplayHandle + Send + Sync>,
        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(),
            layer_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(),
            layer_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(),
            layer_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(),
            layer_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(),
            layer_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(),
            layer_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(),
        }
    }

    #[cfg(feature = "unstable-wgpu-26")]
    /// Creates a new SkiaRenderer that will always use Skia's Vulkan renderer.
    pub fn default_wgpu_26(context: &SkiaSharedContext) -> Self {
        Self {
            maybe_window_adapter: Default::default(),
            rendering_notifier: Default::default(),
            image_cache: Default::default(),
            layer_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| {
                wgpu_26_surface::WGPUSurface::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(feature = "unstable-wgpu-27")]
    /// Creates a new SkiaRenderer that will always use Skia's Vulkan renderer.
    pub fn default_wgpu_27(context: &SkiaSharedContext) -> Self {
        Self {
            maybe_window_adapter: Default::default(),
            rendering_notifier: Default::default(),
            image_cache: Default::default(),
            layer_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| {
                wgpu_27_surface::WGPUSurface::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 + Send + Sync>,
        display_handle: Arc<dyn raw_window_handle::HasDisplayHandle + Send + Sync>,
        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()));
        Self {
            maybe_window_adapter: Default::default(),
            rendering_notifier: Default::default(),
            image_cache: Default::default(),
            layer_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 + Send + Sync>,
        display_handle: Arc<dyn raw_window_handle::HasDisplayHandle + Send + Sync>,
        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: &[(ItemTreeWeak, 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.layer_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();
                if let Brush::SolidColor(clear_color) = window_item.as_pin_ref().background() {
                    skia_canvas.clear(itemrenderer::to_skia_color(&clear_color));
                } else {
                    // 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 {
                if let Some(component) = ItemTreeWeak::upgrade(component) {
                    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, Default::default());
                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,
        text_item: Pin<&dyn i_slint_core::item_rendering::RenderString>,
        item_rc: &ItemRc,
        max_width: Option<LogicalLength>,
        text_wrap: TextWrap,
    ) -> LogicalSize {
        sharedparley::text_size(self, text_item, item_rc, max_width, text_wrap)
    }

    fn char_size(
        &self,
        text_item: Pin<&dyn i_slint_core::item_rendering::HasFont>,
        item_rc: &i_slint_core::item_tree::ItemRc,
        ch: char,
    ) -> LogicalSize {
        sharedparley::char_size(text_item, item_rc, ch).unwrap_or_default()
    }

    fn font_metrics(
        &self,
        font_request: i_slint_core::graphics::FontRequest,
    ) -> i_slint_core::items::FontMetrics {
        sharedparley::font_metrics(font_request)
    }

    fn text_input_byte_offset_for_position(
        &self,
        text_input: std::pin::Pin<&i_slint_core::items::TextInput>,
        item_rc: &i_slint_core::item_tree::ItemRc,
        pos: LogicalPoint,
    ) -> usize {
        sharedparley::text_input_byte_offset_for_position(self, text_input, item_rc, pos)
    }

    fn text_input_cursor_rect_for_byte_offset(
        &self,
        text_input: std::pin::Pin<&i_slint_core::items::TextInput>,
        item_rc: &i_slint_core::item_tree::ItemRc,
        byte_offset: usize,
    ) -> LogicalRect {
        sharedparley::text_input_cursor_rect_for_byte_offset(self, text_input, item_rc, byte_offset)
    }

    fn register_font_from_memory(
        &self,
        data: &'static [u8],
    ) -> Result<(), Box<dyn std::error::Error>> {
        sharedfontique::get_collection().register_fonts(data.to_vec().into(), None);
        Ok(())
    }

    fn register_font_from_path(
        &self,
        path: &std::path::Path,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let requested_path = path.canonicalize().unwrap_or_else(|_| path.into());
        let contents = std::fs::read(requested_path)?;
        sharedfontique::get_collection().register_fonts(contents.into(), None);
        Ok(())
    }

    fn set_rendering_notifier(
        &self,
        callback: Box<dyn RenderingNotifier>,
    ) -> std::result::Result<(), SetRenderingNotifierError> {
        let mut notifier = self.rendering_notifier.borrow_mut();
        if notifier.replace(callback).is_some() {
            Err(SetRenderingNotifierError::AlreadySet)
        } else {
            Ok(())
        }
    }

    fn default_font_size(&self) -> LogicalLength {
        sharedparley::DEFAULT_FONT_SIZE
    }

    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 window_adapter(&self) -> Option<Rc<dyn WindowAdapter>> {
        self.maybe_window_adapter
            .borrow()
            .as_ref()
            .and_then(|window_adapter| window_adapter.upgrade())
    }

    fn resize(&self, size: i_slint_core::api::PhysicalSize) -> Result<(), PlatformError> {
        if size.width == 0 || size.height == 0 {
            return Ok(());
        }

        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: DirtyRegion) {
        if let Some(partial_rendering_state) = self.partial_rendering_state() {
            partial_rendering_state.mark_dirty_region(region);
        }
    }

    fn supports_transformations(&self) -> bool {
        true
    }
}

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 + Sync + Send>,
        display_handle: Arc<dyn raw_window_handle::HasDisplayHandle + Sync + Send>,
        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;

    /// 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
    }

    #[cfg(any(feature = "unstable-wgpu-26", feature = "unstable-wgpu-27"))]
    fn import_wgpu_texture(
        &self,
        _canvas: &skia_safe::Canvas,
        _texture: &i_slint_core::graphics::WGPUTexture,
    ) -> 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,
        )
    }
}
