1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
// 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 configuration
use super::{ColorsSrgb, TextClass, ThemeConfig};
use crate::text::fonts::{fonts, AddMode, FontSelector};
use crate::Action;
use std::collections::BTreeMap;
use std::time::Duration;
/// Event handling configuration
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Config {
#[cfg_attr(feature = "serde", serde(skip))]
dirty: bool,
/// Standard font size, in units of pixels-per-Em
#[cfg_attr(feature = "serde", serde(default = "defaults::font_size"))]
font_size: f32,
/// The colour scheme to use
#[cfg_attr(feature = "serde", serde(default))]
active_scheme: String,
/// All colour schemes
/// TODO: possibly we should not save default schemes and merge when
/// loading (perhaps via a `PartialConfig` type).
#[cfg_attr(feature = "serde", serde(default = "defaults::color_schemes"))]
color_schemes: BTreeMap<String, ColorsSrgb>,
/// Font aliases, used when searching for a font family matching the key.
///
/// Example:
/// ```yaml
/// font_aliases:
/// sans-serif:
/// mode: Prepend
/// list:
/// - noto sans
/// ```
///
/// Fonts are named by *family*. Several standard families exist, e.g.
/// "serif", "sans-serif", "monospace"; these resolve to a list
/// of aliases (e.g. "Noto Sans", "DejaVu Sans", "Arial"), each of which may
/// have further aliases.
///
/// In the above example, "noto sans" is inserted at the top of the alias
/// list for "sans-serif".
///
/// Supported modes: `Prepend`, `Append`, `Replace`.
#[cfg_attr(feature = "serde", serde(default))]
font_aliases: BTreeMap<String, FontAliases>,
/// Standard fonts
#[cfg_attr(feature = "serde", serde(default))]
fonts: BTreeMap<TextClass, FontSelector<'static>>,
/// Text cursor blink rate: delay between switching states
#[cfg_attr(feature = "serde", serde(default = "defaults::cursor_blink_rate_ms"))]
cursor_blink_rate_ms: u32,
/// Transition duration used in animations
#[cfg_attr(feature = "serde", serde(default = "defaults::transition_fade_ms"))]
transition_fade_ms: u32,
/// Text glyph rastering settings
#[cfg_attr(feature = "serde", serde(default))]
raster: RasterConfig,
}
impl Default for Config {
fn default() -> Self {
Config {
dirty: false,
font_size: defaults::font_size(),
active_scheme: Default::default(),
color_schemes: defaults::color_schemes(),
font_aliases: Default::default(),
fonts: defaults::fonts(),
cursor_blink_rate_ms: defaults::cursor_blink_rate_ms(),
transition_fade_ms: defaults::transition_fade_ms(),
raster: Default::default(),
}
}
}
/// Font raster settings
///
/// These are not used by the theme, but passed through to the rendering
/// backend.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RasterConfig {
//// Raster mode/engine (backend dependent)
#[cfg_attr(feature = "serde", serde(default))]
pub mode: u8,
/// Scale multiplier for fixed-precision
///
/// This should be an integer `n >= 1`, e.g. `n = 4` provides four sub-pixel
/// steps of precision. It is also required that `n * h < (1 << 24)` where
/// `h` is the text height in pixels.
#[cfg_attr(feature = "serde", serde(default = "defaults::scale_steps"))]
pub scale_steps: u8,
/// Subpixel positioning threshold
///
/// Text with height `h` less than this threshold will use sub-pixel
/// positioning, which should make letter spacing more accurate for small
/// fonts (though exact behaviour depends on the font; it may be worse).
/// This may make rendering worse by breaking pixel alignment.
///
/// Note: this feature may not be available, depending on the backend and
/// the mode.
///
/// See also sub-pixel positioning steps.
#[cfg_attr(feature = "serde", serde(default = "defaults::subpixel_threshold"))]
pub subpixel_threshold: u8,
/// Subpixel steps
///
/// The number of sub-pixel positioning steps to use. 1 is the minimum and
/// equivalent to no sub-pixel positioning. 16 is the maximum.
///
/// Note that since this applies to horizontal and vertical positioning, the
/// maximum number of rastered glyphs is multiplied by the square of this
/// value, though this maxmimum may not be reached in practice. Since this
/// feature is usually only used for small fonts this likely acceptable.
#[cfg_attr(feature = "serde", serde(default = "defaults::subpixel_steps"))]
pub subpixel_steps: u8,
}
impl Default for RasterConfig {
fn default() -> Self {
RasterConfig {
mode: 0,
scale_steps: defaults::scale_steps(),
subpixel_threshold: defaults::subpixel_threshold(),
subpixel_steps: defaults::subpixel_steps(),
}
}
}
/// Getters
impl Config {
/// Standard font size
///
/// Units: logical (unscaled) pixels per Em.
///
/// To convert to Points, multiply by three quarters.
#[inline]
pub fn font_size(&self) -> f32 {
self.font_size
}
/// Active colour scheme (name)
///
/// An empty string will resolve the default colour scheme.
#[inline]
pub fn active_scheme(&self) -> &str {
&self.active_scheme
}
/// Iterate over all colour schemes
#[inline]
pub fn color_schemes_iter(&self) -> impl Iterator<Item = (&str, &ColorsSrgb)> {
self.color_schemes.iter().map(|(s, t)| (s.as_str(), t))
}
/// Get a colour scheme by name
#[inline]
pub fn get_color_scheme(&self, name: &str) -> Option<ColorsSrgb> {
self.color_schemes.get(name).cloned()
}
/// Get the active colour scheme
///
/// Even this one isn't guaranteed to exist.
#[inline]
pub fn get_active_scheme(&self) -> Option<ColorsSrgb> {
self.color_schemes.get(&self.active_scheme).cloned()
}
/// Get an iterator over font mappings
#[inline]
pub fn iter_fonts(&self) -> impl Iterator<Item = (&TextClass, &FontSelector<'static>)> {
self.fonts.iter()
}
/// Get the cursor blink rate (delay)
#[inline]
pub fn cursor_blink_rate(&self) -> Duration {
Duration::from_millis(self.cursor_blink_rate_ms as u64)
}
/// Get the fade duration used in transition animations
#[inline]
pub fn transition_fade_duration(&self) -> Duration {
Duration::from_millis(self.transition_fade_ms as u64)
}
}
/// Setters
impl Config {
/// Set font size
pub fn set_font_size(&mut self, pt_size: f32) {
self.dirty = true;
self.font_size = pt_size;
}
/// Set colour scheme
pub fn set_active_scheme(&mut self, scheme: impl ToString) {
self.dirty = true;
self.active_scheme = scheme.to_string();
}
}
/// Other functions
impl Config {
/// Currently this is just "set". Later, maybe some type of merge.
#[allow(clippy::float_cmp)]
pub fn apply_config(&mut self, other: &Config) -> Action {
let action = if self.font_size != other.font_size {
Action::RESIZE | Action::THEME_UPDATE
} else if self != other {
Action::REDRAW
} else {
Action::empty()
};
*self = other.clone();
action
}
}
impl ThemeConfig for Config {
#[cfg(feature = "serde")]
#[inline]
fn is_dirty(&self) -> bool {
self.dirty
}
/// Apply config effects which only happen on startup
fn apply_startup(&self) {
if !self.font_aliases.is_empty() {
fonts().update_db(|db| {
for (family, aliases) in self.font_aliases.iter() {
db.add_aliases(
family.to_string().into(),
aliases.list.iter().map(|s| s.to_string().into()),
aliases.mode,
);
}
});
}
}
/// Get raster config
#[inline]
fn raster(&self) -> &RasterConfig {
&self.raster
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FontAliases {
#[cfg_attr(feature = "serde", serde(default = "defaults::add_mode"))]
mode: AddMode,
list: Vec<String>,
}
mod defaults {
use super::*;
#[cfg(feature = "serde")]
pub fn add_mode() -> AddMode {
AddMode::Prepend
}
pub fn font_size() -> f32 {
14.0
}
pub fn color_schemes() -> BTreeMap<String, ColorsSrgb> {
let mut schemes = BTreeMap::new();
schemes.insert("light".to_string(), ColorsSrgb::light());
schemes.insert("dark".to_string(), ColorsSrgb::dark());
schemes.insert("blue".to_string(), ColorsSrgb::blue());
schemes
}
pub fn fonts() -> BTreeMap<TextClass, FontSelector<'static>> {
let mut selector = FontSelector::new();
selector.set_families(vec!["serif".into()]);
let list = [
(TextClass::Edit(false), selector.clone()),
(TextClass::Edit(true), selector),
];
list.iter().cloned().collect()
}
pub fn cursor_blink_rate_ms() -> u32 {
600
}
pub fn transition_fade_ms() -> u32 {
150
}
pub fn scale_steps() -> u8 {
4
}
pub fn subpixel_threshold() -> u8 {
0
}
pub fn subpixel_steps() -> u8 {
5
}
}