kas-theme 0.10.1

KAS GUI / theme support
Documentation
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License in the LICENSE-APACHE file or at:
//     https://www.apache.org/licenses/LICENSE-2.0

//! Theme traits

use kas::draw::{color, DrawHandle, DrawIface, DrawSharedImpl, SharedState, SizeHandle, ThemeApi};
use kas::TkAction;
use std::any::Any;
use std::ops::{Deref, DerefMut};

/// Requirements on theme config (without `config` feature)
#[cfg(not(feature = "config"))]
pub trait ThemeConfig: Clone + std::fmt::Debug + 'static {
    /// Apply startup effects
    fn apply_startup(&self);

    /// Get raster config
    fn raster(&self) -> &crate::RasterConfig;
}

/// Requirements on theme config (with `config` feature)
#[cfg(feature = "config")]
pub trait ThemeConfig:
    Clone + std::fmt::Debug + 'static + for<'a> serde::Deserialize<'a> + serde::Serialize
{
    /// Has the config ever been updated?
    fn is_dirty(&self) -> bool;

    /// Apply startup effects
    fn apply_startup(&self);

    /// Get raster config
    fn raster(&self) -> &crate::RasterConfig;
}

/// A *theme* provides widget sizing and drawing implementations.
///
/// The theme is generic over some `DrawIface`.
///
/// Objects of this type are copied within each window's data structure. For
/// large resources (e.g. fonts and icons) consider using external storage.
pub trait Theme<DS: DrawSharedImpl>: ThemeApi {
    /// The associated config type
    type Config: ThemeConfig;

    /// The associated [`Window`] implementation.
    type Window: Window;

    /// The associated [`DrawHandle`] implementation.
    #[cfg(not(feature = "gat"))]
    type DrawHandle: DrawHandle;
    #[cfg(feature = "gat")]
    type DrawHandle<'a>: DrawHandle;

    /// Get current config
    fn config(&self) -> std::borrow::Cow<Self::Config>;

    /// Apply/set the passed config
    fn apply_config(&mut self, config: &Self::Config) -> TkAction;

    /// Theme initialisation
    ///
    /// The toolkit must call this method before [`Theme::new_window`]
    /// to allow initialisation specific to the `DrawIface`.
    ///
    /// At a minimum, a theme must load a font to [`kas::text::fonts`].
    /// The first font loaded (by any theme) becomes the default font.
    fn init(&mut self, shared: &mut SharedState<DS>);

    /// Construct per-window storage
    ///
    /// On "standard" monitors, the `dpi_factor` is 1. High-DPI screens may
    /// have a factor of 2 or higher. The factor may not be an integer; e.g.
    /// `9/8 = 1.125` works well with many 1440p screens. It is recommended to
    /// round dimensions to the nearest integer, and cache the result:
    /// ```notest
    /// self.margin = i32::conv_nearest(MARGIN * factor);
    /// ```
    ///
    /// A reference to the draw backend is provided allowing configuration.
    fn new_window(&self, dpi_factor: f32) -> Self::Window;

    /// Update a window created by [`Theme::new_window`]
    ///
    /// This is called when the DPI factor changes or theme dimensions change.
    fn update_window(&self, window: &mut Self::Window, dpi_factor: f32);

    /// Prepare to draw and construct a [`DrawHandle`] object
    ///
    /// This is called once per window per frame and should do any necessary
    /// preparation such as loading fonts and textures which are loaded on
    /// demand.
    ///
    /// Drawing via this [`DrawHandle`] is restricted to the specified `rect`.
    ///
    /// The `window` is guaranteed to be one created by a call to
    /// [`Theme::new_window`] on `self`, and the `draw` reference is guaranteed
    /// to be identical to the one passed to [`Theme::new_window`].
    ///
    /// # Safety
    ///
    /// (This section only applies when not using the `gat` feature.)
    ///
    /// All references passed into the method must outlive the returned object.
    #[cfg(not(feature = "gat"))]
    unsafe fn draw_handle(
        &self,
        draw: DrawIface<DS>,
        window: &mut Self::Window,
    ) -> Self::DrawHandle;
    #[cfg(feature = "gat")]
    fn draw_handle<'a>(
        &'a self,
        draw: DrawIface<'a, DS>,
        window: &'a mut Self::Window,
    ) -> Self::DrawHandle<'a>;

    /// Background colour
    fn clear_color(&self) -> color::Rgba;
}

/// Per-window storage for the theme
///
/// Constructed via [`Theme::new_window`].
///
/// The main reason for this separation is to allow proper handling of
/// multi-window applications across screens with differing DPIs.
pub trait Window: 'static {
    /// Construct a [`SizeHandle`] object
    fn size_handle(&self) -> &dyn SizeHandle;

    fn as_any_mut(&mut self) -> &mut dyn Any;
}

impl<T: Theme<DS>, DS: DrawSharedImpl> Theme<DS> for Box<T> {
    type Window = <T as Theme<DS>>::Window;
    type Config = <T as Theme<DS>>::Config;

    #[cfg(not(feature = "gat"))]
    type DrawHandle = <T as Theme<DS>>::DrawHandle;
    #[cfg(feature = "gat")]
    type DrawHandle<'a> = <T as Theme<DS>>::DrawHandle<'a>;

    fn config(&self) -> std::borrow::Cow<Self::Config> {
        self.deref().config()
    }
    fn apply_config(&mut self, config: &Self::Config) -> TkAction {
        self.deref_mut().apply_config(config)
    }

    fn init(&mut self, shared: &mut SharedState<DS>) {
        self.deref_mut().init(shared);
    }

    fn new_window(&self, dpi_factor: f32) -> Self::Window {
        self.deref().new_window(dpi_factor)
    }
    fn update_window(&self, window: &mut Self::Window, dpi_factor: f32) {
        self.deref().update_window(window, dpi_factor);
    }

    #[cfg(not(feature = "gat"))]
    unsafe fn draw_handle(
        &self,
        draw: DrawIface<DS>,
        window: &mut Self::Window,
    ) -> Self::DrawHandle {
        self.deref().draw_handle(draw, window)
    }
    #[cfg(feature = "gat")]
    fn draw_handle<'a>(
        &'a self,
        draw: DrawIface<'a, DS>,
        window: &'a mut Self::Window,
    ) -> Self::DrawHandle<'a> {
        self.deref().draw_handle(draw, window)
    }

    fn clear_color(&self) -> color::Rgba {
        self.deref().clear_color()
    }
}

impl<W: Window> Window for Box<W> {
    fn size_handle(&self) -> &dyn SizeHandle {
        self.deref().size_handle()
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self.deref_mut().as_any_mut()
    }
}