use std::cell::Cell;
use std::rc::Rc;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Family {
#[default]
Neon,
Argon,
Xenon,
Radon,
Krypton,
}
impl Family {
pub const ALL: [Family; 5] = [
Family::Neon,
Family::Argon,
Family::Xenon,
Family::Radon,
Family::Krypton,
];
pub fn label(self) -> &'static str {
match self {
Family::Neon => "Neon",
Family::Argon => "Argon",
Family::Xenon => "Xenon",
Family::Radon => "Radon",
Family::Krypton => "Krypton",
}
}
pub fn name(self) -> &'static str {
match self {
Family::Neon => "monaspace-neon",
Family::Argon => "monaspace-argon",
Family::Xenon => "monaspace-xenon",
Family::Radon => "monaspace-radon",
Family::Krypton => "monaspace-krypton",
}
}
pub fn next(self) -> Self {
let at = Self::ALL.iter().position(|&f| f == self).unwrap_or(0);
Self::ALL[(at + 1) % Self::ALL.len()]
}
fn bytes(self) -> &'static [u8] {
macro_rules! face {
($file:literal) => {
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/fonts/monaspace/",
$file
))
};
}
match self {
Family::Neon => face!("MonaspaceNeon-Regular.otf"),
Family::Argon => face!("MonaspaceArgon-Regular.otf"),
Family::Xenon => face!("MonaspaceXenon-Regular.otf"),
Family::Radon => face!("MonaspaceRadon-Regular.otf"),
Family::Krypton => face!("MonaspaceKrypton-Regular.otf"),
}
}
pub(crate) fn load(self) -> fontdue::Font {
fontdue::Font::from_bytes(self.bytes(), fontdue::FontSettings::default())
.expect("a Monaspace face")
}
}
#[derive(bevy_ecs::prelude::Resource, Default)]
pub struct Font {
family: Family,
}
impl Font {
pub fn family(&self) -> Family {
self.family
}
pub fn set_family(&mut self, family: Family) {
if self.family == family {
return;
}
self.family = family;
log::info!("drawing the UI in Monaspace {}", family.label());
}
pub fn cycle(&mut self) {
self.set_family(self.family().next());
}
}
#[derive(Clone, Default)]
pub(crate) struct Face(Rc<Cell<Family>>);
impl Face {
pub fn set(&self, family: Family) {
self.0.set(family);
}
pub fn get(&self) -> Family {
self.0.get()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_five_faces_load_and_draw_a_capital() {
for family in Family::ALL {
let font = family.load();
let (metrics, raster) = font.rasterize('H', 64.0);
assert!(
metrics.width > 0 && metrics.height > 0,
"{}",
family.label()
);
assert!(
raster.iter().any(|&p| p > 0),
"{} drew nothing",
family.label()
);
}
}
#[test]
fn the_faces_share_an_advance() {
let neon = Family::Neon.load().metrics('M', 100.0).advance_width;
assert!(
(neon - crate::ui::widgets::ADVANCE_PER_EM * 100.0).abs() < 0.5,
"the advance the layout code measures with is the face's: {neon}",
);
for family in Family::ALL {
let font = family.load();
for ch in ['M', 'i', '.', 'W'] {
let advance = font.metrics(ch, 100.0).advance_width;
assert!(
(advance - neon).abs() < 0.1,
"{} advances {advance} for {ch:?}, Neon {neon}",
family.label(),
);
}
}
}
#[test]
fn cycling_goes_round_all_five_and_comes_back() {
let mut font = Font::default();
assert_eq!(font.family(), Family::Neon);
let seen: Vec<Family> = (0..Family::ALL.len())
.map(|_| {
font.cycle();
font.family()
})
.collect();
assert_eq!(seen.last(), Some(&Family::Neon), "back where it started");
let mut sorted = seen.clone();
sorted.sort_by_key(|f| f.label());
sorted.dedup();
assert_eq!(
sorted.len(),
5,
"and through every one on the way: {seen:?}"
);
}
#[test]
fn every_face_has_a_name_of_its_own() {
let mut names: Vec<&str> = Family::ALL.iter().map(|f| f.name()).collect();
names.sort();
names.dedup();
assert_eq!(names.len(), Family::ALL.len());
}
}