kas_theme/
traits.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Theme traits
7
8use kas::draw::{color, DrawIface, DrawSharedImpl, SharedState};
9use kas::event::EventState;
10use kas::theme::{ThemeControl, ThemeDraw, ThemeSize};
11use kas::{autoimpl, TkAction};
12use std::any::Any;
13use std::ops::{Deref, DerefMut};
14
15/// Requirements on theme config (without `config` feature)
16#[cfg(not(feature = "config"))]
17pub trait ThemeConfig: Clone + std::fmt::Debug + 'static {
18    /// Apply startup effects
19    fn apply_startup(&self);
20
21    /// Get raster config
22    fn raster(&self) -> &crate::RasterConfig;
23}
24
25/// Requirements on theme config (with `config` feature)
26#[cfg(feature = "config")]
27pub trait ThemeConfig:
28    Clone + std::fmt::Debug + 'static + for<'a> serde::Deserialize<'a> + serde::Serialize
29{
30    /// Has the config ever been updated?
31    fn is_dirty(&self) -> bool;
32
33    /// Apply startup effects
34    fn apply_startup(&self);
35
36    /// Get raster config
37    fn raster(&self) -> &crate::RasterConfig;
38}
39
40/// A *theme* provides widget sizing and drawing implementations.
41///
42/// The theme is generic over some `DrawIface`.
43///
44/// Objects of this type are copied within each window's data structure. For
45/// large resources (e.g. fonts and icons) consider using external storage.
46pub trait Theme<DS: DrawSharedImpl>: ThemeControl {
47    /// The associated config type
48    type Config: ThemeConfig;
49
50    /// The associated [`Window`] implementation.
51    type Window: Window;
52
53    /// The associated [`ThemeDraw`] implementation.
54    type Draw<'a>: ThemeDraw
55    where
56        DS: 'a,
57        Self: 'a;
58
59    /// Get current configuration
60    fn config(&self) -> std::borrow::Cow<Self::Config>;
61
62    /// Apply/set the passed config
63    fn apply_config(&mut self, config: &Self::Config) -> TkAction;
64
65    /// Theme initialisation
66    ///
67    /// The toolkit must call this method before [`Theme::new_window`]
68    /// to allow initialisation specific to the `DrawIface`.
69    ///
70    /// At a minimum, a theme must load a font to [`kas::text::fonts`].
71    /// The first font loaded (by any theme) becomes the default font.
72    fn init(&mut self, shared: &mut SharedState<DS>);
73
74    /// Construct per-window storage
75    ///
76    /// On "standard" monitors, the `dpi_factor` is 1. High-DPI screens may
77    /// have a factor of 2 or higher. The factor may not be an integer; e.g.
78    /// `9/8 = 1.125` works well with many 1440p screens. It is recommended to
79    /// round dimensions to the nearest integer, and cache the result:
80    /// ```notest
81    /// self.margin = i32::conv_nearest(MARGIN * factor);
82    /// ```
83    ///
84    /// A reference to the draw backend is provided allowing configuration.
85    fn new_window(&self, dpi_factor: f32) -> Self::Window;
86
87    /// Update a window created by [`Theme::new_window`]
88    ///
89    /// This is called when the DPI factor changes or theme dimensions change.
90    fn update_window(&self, window: &mut Self::Window, dpi_factor: f32);
91
92    /// Prepare to draw and construct a [`ThemeDraw`] object
93    ///
94    /// This is called once per window per frame and should do any necessary
95    /// preparation such as loading fonts and textures which are loaded on
96    /// demand.
97    ///
98    /// Drawing via this [`ThemeDraw`] object is restricted to the specified `rect`.
99    ///
100    /// The `window` is guaranteed to be one created by a call to
101    /// [`Theme::new_window`] on `self`.
102    fn draw<'a>(
103        &'a self,
104        draw: DrawIface<'a, DS>,
105        ev: &'a mut EventState,
106        window: &'a mut Self::Window,
107    ) -> Self::Draw<'a>;
108
109    /// Background colour
110    fn clear_color(&self) -> color::Rgba;
111}
112
113/// Per-window storage for the theme
114///
115/// Constructed via [`Theme::new_window`].
116///
117/// The main reason for this separation is to allow proper handling of
118/// multi-window applications across screens with differing DPIs.
119#[autoimpl(for<T: trait> Box<T>)]
120pub trait Window: 'static {
121    /// Construct a [`ThemeSize`] object
122    fn size(&self) -> &dyn ThemeSize;
123
124    fn as_any_mut(&mut self) -> &mut dyn Any;
125}
126
127impl<T: Theme<DS>, DS: DrawSharedImpl> Theme<DS> for Box<T> {
128    type Window = <T as Theme<DS>>::Window;
129    type Config = <T as Theme<DS>>::Config;
130
131    type Draw<'a> = <T as Theme<DS>>::Draw<'a>
132    where
133        T: 'a;
134
135    fn config(&self) -> std::borrow::Cow<Self::Config> {
136        self.deref().config()
137    }
138    fn apply_config(&mut self, config: &Self::Config) -> TkAction {
139        self.deref_mut().apply_config(config)
140    }
141
142    fn init(&mut self, shared: &mut SharedState<DS>) {
143        self.deref_mut().init(shared);
144    }
145
146    fn new_window(&self, dpi_factor: f32) -> Self::Window {
147        self.deref().new_window(dpi_factor)
148    }
149    fn update_window(&self, window: &mut Self::Window, dpi_factor: f32) {
150        self.deref().update_window(window, dpi_factor);
151    }
152
153    fn draw<'a>(
154        &'a self,
155        draw: DrawIface<'a, DS>,
156        ev: &'a mut EventState,
157        window: &'a mut Self::Window,
158    ) -> Self::Draw<'a> {
159        self.deref().draw(draw, ev, window)
160    }
161
162    fn clear_color(&self) -> color::Rgba {
163        self.deref().clear_color()
164    }
165}