feat: improve font handling, add wrapper error type

This commit is contained in:
Rowan 2025-01-26 14:05:37 -06:00
parent 27b7b25814
commit 3e656671d9
2 changed files with 91 additions and 37 deletions

View file

@ -1,13 +1,57 @@
use pango::{prelude::FontMapExt, Font, FontDescription}; use std::fmt::Display;
use pango::{
prelude::{FontExt, FontMapExt},
Font as PangoFont, FontDescription, Language, SCALE,
};
use pangocairo::FontMap; use pangocairo::FontMap;
pub trait FontExt { pub struct Font {
fn load_from_string(value: &str) -> Option<Font> { font: PangoFont,
let fontmap = FontMap::default(); language: Language,
let context = fontmap.create_context(); }
let desc = FontDescription::from_string(value);
fontmap.load_font(&context, &desc) impl Font {
pub fn new(font: PangoFont) -> Self {
Self {
font,
language: Language::default(),
} }
} }
impl FontExt for pango::Font {} pub fn with_language(self, language: Language) -> Self {
Self { language, ..self }
}
pub fn family(&self) -> String {
self.font.describe().family().unwrap().to_string()
}
pub fn size(&self) -> i32 {
self.font.describe().size() / SCALE
}
pub fn height(&self) -> i32 {
self.font.metrics(Some(&self.language)).height() / SCALE
}
pub fn load_from_string(family: &str, size: usize) -> Option<Font> {
let font_str = format!("{family} {size}");
let fontmap = FontMap::default();
let context = fontmap.create_context();
let desc = FontDescription::from_string(&font_str);
fontmap.load_font(&context, &desc).map(Font::new)
}
}
impl Default for Font {
fn default() -> Self {
Font::load_from_string("monospace", 10).unwrap()
}
}
impl Display for Font {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\" {}", self.family(), self.size())
}
}

View file

@ -1,48 +1,58 @@
pub mod color; pub mod color;
pub mod font; pub mod font;
use std::fmt::Display;
use color::ColorPair; use color::ColorPair;
use font::FontExt; use font::Font;
use pango::{prelude::FontExt as _, Font, Language, SCALE};
use wmenu_rs::{Menu as WMenu, MenuError}; use wmenu_rs::{Menu as WMenu, MenuError};
#[derive(Debug)]
pub enum Error {
WMenu(MenuError),
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::WMenu(menu_error) => write!(f, "{menu_error}"),
}
}
}
impl std::error::Error for Error {}
impl From<MenuError> for Error {
fn from(value: MenuError) -> Self {
Self::WMenu(value)
}
}
pub struct Config { pub struct Config {
pub bottom: bool, pub bottom: bool,
pub padding: usize, pub padding: usize,
pub lines: i32, pub lines: i32,
pub font: String, pub font_family: String,
pub font_size: u8, pub font_size: usize,
pub language: Option<String>,
pub normal_color: ColorPair, pub normal_color: ColorPair,
pub prompt_color: ColorPair, pub prompt_color: ColorPair,
pub selection_color: ColorPair, pub selection_color: ColorPair,
} }
impl Config { impl Config {
// TODO: add font struct to handle these methods
fn font_string(&self) -> String {
format!("{} {}", self.font, self.font_size)
}
fn font_height(&self, font: &str) -> i32 {
let font = Font::load_from_string(font).unwrap();
// TODO: let user pick language
let metrics = font.metrics(Some(&Language::default()));
metrics.height() / SCALE
}
// TODO: add ability to override height/line_height
fn apply(self, wmenu: &mut WMenu) { fn apply(self, wmenu: &mut WMenu) {
let font_string = self.font_string(); let font =
let font_height = self.font_height(&font_string); Font::load_from_string(self.font_family.as_str(), self.font_size).unwrap_or_default();
let font_height = font.height();
let line_height = font_height + 2; let line_height = font_height + 2;
let mut height = line_height; let height = if self.lines > 0 {
line_height + line_height * self.lines
// FIXME: ugly } else {
if self.lines > 0 { line_height
height = height + height * self.lines; };
}
wmenu wmenu
.height(height) .height(height)
@ -50,7 +60,7 @@ impl Config {
.padding(self.padding.try_into().unwrap()) .padding(self.padding.try_into().unwrap())
.lines(self.lines.try_into().unwrap()) .lines(self.lines.try_into().unwrap())
.line_height(line_height) .line_height(line_height)
.font(&font_string) .font(&font.to_string())
.normal_bg(self.normal_color.bg.into()) .normal_bg(self.normal_color.bg.into())
.normal_fg(self.normal_color.fg.into()) .normal_fg(self.normal_color.fg.into())
.prompt_bg(self.prompt_color.bg.into()) .prompt_bg(self.prompt_color.bg.into())
@ -66,8 +76,9 @@ impl Default for Config {
bottom: false, bottom: false,
lines: 0, lines: 0,
padding: 16, padding: 16,
font: "monospace".to_string(), font_family: "monospace".to_string(),
font_size: 12, font_size: 12,
language: None,
normal_color: (0x222222ff, 0xbbbbbbffu32).into(), normal_color: (0x222222ff, 0xbbbbbbffu32).into(),
prompt_color: (0x005577ff, 0xeeeeeeffu32).into(), prompt_color: (0x005577ff, 0xeeeeeeffu32).into(),
selection_color: (0x005577ff, 0x227799ffu32).into(), selection_color: (0x005577ff, 0x227799ffu32).into(),
@ -104,9 +115,8 @@ impl Menu {
self self
} }
// TODO: wrap MenuError with new error type pub fn run(&self) -> Result<String, Error> {
pub fn run(&self) -> Result<String, MenuError> { Ok(self.inner().run()?)
self.inner().run()
} }
} }