use rs_math3d::{Recti, Vec2i};
use crate::atlas::{AtlasHandle, FontId, SlotId};
#[derive(Default, Copy, Clone)]
#[repr(C)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub enum FontRole {
#[default]
Body,
Small,
Title,
Heading,
Mono,
}
impl FontRole {
pub fn atlas_name(self) -> &'static str {
match self {
Self::Body => "body",
Self::Small => "small",
Self::Title => "title",
Self::Heading => "heading",
Self::Mono => "mono",
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FontChoice {
Role(FontRole),
Id(FontId),
}
impl Default for FontChoice {
fn default() -> Self {
Self::Role(FontRole::Body)
}
}
impl From<FontRole> for FontChoice {
fn from(role: FontRole) -> Self {
Self::Role(role)
}
}
impl From<FontId> for FontChoice {
fn from(font: FontId) -> Self {
Self::Id(font)
}
}
pub trait Font {
fn name(&self) -> &str;
fn get_size(&self) -> usize;
fn get_char_size(&self, c: char) -> (usize, usize);
}
#[derive(Copy, Clone)]
pub struct Style {
pub font: FontId,
pub small_font: FontId,
pub title_font: FontId,
pub heading_font: FontId,
pub mono_font: FontId,
pub default_cell_width: i32,
pub padding: i32,
pub spacing: i32,
pub indent: i32,
pub title_height: i32,
pub scrollbar_size: i32,
pub thumb_size: i32,
pub colors: [Color; 14],
}
pub type Real = f32;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct TextureId(pub(crate) u32);
impl TextureId {
pub fn raw(self) -> u32 {
self.0
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Image {
Slot(SlotId),
Texture(TextureId),
}
#[derive(Copy, Clone)]
pub enum ImageSource<'a> {
Raw {
width: i32,
height: i32,
pixels: &'a [u8],
},
#[cfg(any(feature = "builder", feature = "png_source"))]
Png {
bytes: &'a [u8],
},
}
pub(crate) static UNCLIPPED_RECT: Recti = Recti {
x: 0,
y: 0,
width: i32::MAX,
height: i32::MAX,
};
impl Default for Style {
fn default() -> Self {
Self {
font: FontId::default(),
small_font: FontId::default(),
title_font: FontId::default(),
heading_font: FontId::default(),
mono_font: FontId::default(),
default_cell_width: 68,
padding: 5,
spacing: 4,
indent: 24,
title_height: 24,
scrollbar_size: 12,
thumb_size: 8,
colors: [
Color { r: 230, g: 230, b: 230, a: 255 },
Color { r: 25, g: 25, b: 25, a: 255 },
Color { r: 50, g: 50, b: 50, a: 255 },
Color { r: 25, g: 25, b: 25, a: 255 },
Color { r: 240, g: 240, b: 240, a: 255 },
Color { r: 0, g: 0, b: 0, a: 0 },
Color { r: 75, g: 75, b: 75, a: 255 },
Color { r: 95, g: 95, b: 95, a: 255 },
Color { r: 115, g: 115, b: 115, a: 255 },
Color { r: 30, g: 30, b: 30, a: 255 },
Color { r: 35, g: 35, b: 35, a: 255 },
Color { r: 40, g: 40, b: 40, a: 255 },
Color { r: 43, g: 43, b: 43, a: 255 },
Color { r: 30, g: 30, b: 30, a: 255 },
],
}
}
}
impl FontChoice {
pub fn role(role: FontRole) -> Self {
Self::Role(role)
}
pub fn id(font: FontId) -> Self {
Self::Id(font)
}
pub fn resolve(self, style: &Style) -> FontId {
style.resolve_font_choice(self)
}
}
impl Style {
pub fn resolve_font_role(&self, role: FontRole) -> FontId {
match role {
FontRole::Body => self.font,
FontRole::Small => self.small_font,
FontRole::Title => self.title_font,
FontRole::Heading => self.heading_font,
FontRole::Mono => self.mono_font,
}
}
pub fn resolve_font_choice(&self, choice: FontChoice) -> FontId {
match choice {
FontChoice::Role(role) => self.resolve_font_role(role),
FontChoice::Id(font) => font,
}
}
pub fn bind_default_named_fonts(&mut self, atlas: &AtlasHandle) {
let default_font = FontId::default();
if self.font == default_font {
if let Some(font) = atlas.font_id(FontRole::Body.atlas_name()) {
self.font = font;
}
}
if self.small_font == default_font {
self.small_font = atlas.font_id(FontRole::Small.atlas_name()).unwrap_or(self.font);
}
if self.title_font == default_font {
self.title_font = atlas.font_id(FontRole::Title.atlas_name()).unwrap_or(self.font);
}
if self.heading_font == default_font {
self.heading_font = atlas.font_id(FontRole::Heading.atlas_name()).unwrap_or(self.font);
}
if self.mono_font == default_font {
self.mono_font = atlas.font_id(FontRole::Mono.atlas_name()).unwrap_or(self.font);
}
}
pub fn with_named_fonts(mut self, atlas: &AtlasHandle) -> Self {
self.bind_named_fonts(atlas);
self
}
pub fn bind_named_fonts(&mut self, atlas: &AtlasHandle) {
if let Some(font) = atlas.font_id(FontRole::Body.atlas_name()) {
self.font = font;
}
self.small_font = atlas.font_id(FontRole::Small.atlas_name()).unwrap_or(self.font);
self.title_font = atlas.font_id(FontRole::Title.atlas_name()).unwrap_or(self.font);
self.heading_font = atlas.font_id(FontRole::Heading.atlas_name()).unwrap_or(self.font);
self.mono_font = atlas.font_id(FontRole::Mono.atlas_name()).unwrap_or(self.font);
}
}
pub fn vec2(x: i32, y: i32) -> Vec2i {
Vec2i { x, y }
}
pub fn rect(x: i32, y: i32, w: i32, h: i32) -> Recti {
Recti { x, y, width: w, height: h }
}
pub fn color(r: u8, g: u8, b: u8, a: u8) -> Color {
Color { r, g, b, a }
}
pub fn expand_rect(r: Recti, n: i32) -> Recti {
rect(r.x - n, r.y - n, r.width + n * 2, r.height + n * 2)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{AtlasHandle, AtlasSource, CharEntry, FontEntry, SourceFormat};
fn make_test_atlas(fonts: &[(&str, usize)]) -> AtlasHandle {
let pixels = [0xFF, 0xFF, 0xFF, 0xFF];
let entries = [
(
'_',
CharEntry {
offset: Vec2i::new(0, 0),
advance: Vec2i::new(8, 0),
rect: Recti::new(0, 0, 1, 1),
},
),
(
'a',
CharEntry {
offset: Vec2i::new(0, 0),
advance: Vec2i::new(8, 0),
rect: Recti::new(0, 0, 1, 1),
},
),
];
let font_entries: Vec<_> = fonts
.iter()
.map(|(name, size)| {
(
*name,
FontEntry {
line_size: *size,
baseline: *size as i32 - 2,
font_size: *size,
entries: &entries,
},
)
})
.collect();
let source = AtlasSource {
width: 1,
height: 1,
pixels: &pixels,
icons: &[],
fonts: font_entries.as_slice(),
format: SourceFormat::Raw,
slots: &[],
};
AtlasHandle::from(&source)
}
#[test]
fn font_choice_conversions_preserve_selected_font() {
let atlas = make_test_atlas(&[(FontRole::Body.atlas_name(), 12), (FontRole::Heading.atlas_name(), 18)]);
let heading = atlas.font_id(FontRole::Heading.atlas_name()).unwrap();
assert_eq!(FontChoice::from(FontRole::Heading), FontChoice::role(FontRole::Heading));
assert_eq!(FontChoice::from(heading), FontChoice::id(heading));
}
#[test]
fn bind_named_fonts_uses_conventional_role_names() {
let atlas = make_test_atlas(&[
(FontRole::Body.atlas_name(), 12),
(FontRole::Small.atlas_name(), 10),
(FontRole::Title.atlas_name(), 16),
(FontRole::Heading.atlas_name(), 18),
]);
let style = Style::default().with_named_fonts(&atlas);
assert_eq!(style.font, atlas.font_id(FontRole::Body.atlas_name()).unwrap());
assert_eq!(style.small_font, atlas.font_id(FontRole::Small.atlas_name()).unwrap());
assert_eq!(style.title_font, atlas.font_id(FontRole::Title.atlas_name()).unwrap());
assert_eq!(style.heading_font, atlas.font_id(FontRole::Heading.atlas_name()).unwrap());
assert_eq!(style.mono_font, style.font);
}
#[test]
fn bind_default_named_fonts_replaces_unset_font_fields_only() {
let atlas = make_test_atlas(&[
(FontRole::Small.atlas_name(), 10),
(FontRole::Body.atlas_name(), 12),
(FontRole::Title.atlas_name(), 16),
(FontRole::Heading.atlas_name(), 18),
]);
let mut style = Style::default();
style.bind_default_named_fonts(&atlas);
assert_eq!(style.font, atlas.font_id(FontRole::Body.atlas_name()).unwrap());
assert_eq!(style.small_font, atlas.font_id(FontRole::Small.atlas_name()).unwrap());
assert_eq!(style.title_font, atlas.font_id(FontRole::Title.atlas_name()).unwrap());
assert_eq!(style.heading_font, atlas.font_id(FontRole::Heading.atlas_name()).unwrap());
let explicit_title = atlas.font_id(FontRole::Title.atlas_name()).unwrap();
style.font = explicit_title;
style.bind_default_named_fonts(&atlas);
assert_eq!(style.font, explicit_title);
}
}