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}