diff --git a/src/font.rs b/src/font.rs index 5c24922..02354cd 100644 --- a/src/font.rs +++ b/src/font.rs @@ -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; -pub trait FontExt { - fn load_from_string(value: &str) -> Option { +pub struct Font { + font: PangoFont, + language: Language, +} + +impl Font { + pub fn new(font: PangoFont) -> Self { + Self { + font, + language: Language::default(), + } + } + + 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 { + let font_str = format!("{family} {size}"); let fontmap = FontMap::default(); let context = fontmap.create_context(); - let desc = FontDescription::from_string(value); - fontmap.load_font(&context, &desc) + let desc = FontDescription::from_string(&font_str); + fontmap.load_font(&context, &desc).map(Font::new) } } -impl FontExt for pango::Font {} +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()) + } +} diff --git a/src/lib.rs b/src/lib.rs index a990987..618ef25 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,48 +1,58 @@ pub mod color; pub mod font; +use std::fmt::Display; + use color::ColorPair; -use font::FontExt; -use pango::{prelude::FontExt as _, Font, Language, SCALE}; +use font::Font; 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 for Error { + fn from(value: MenuError) -> Self { + Self::WMenu(value) + } +} + pub struct Config { pub bottom: bool, pub padding: usize, pub lines: i32, - pub font: String, - pub font_size: u8, + pub font_family: String, + pub font_size: usize, + pub language: Option, pub normal_color: ColorPair, pub prompt_color: ColorPair, pub selection_color: ColorPair, } 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) { - let font_string = self.font_string(); - let font_height = self.font_height(&font_string); + let font = + 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 mut height = line_height; - - // FIXME: ugly - if self.lines > 0 { - height = height + height * self.lines; - } + let height = if self.lines > 0 { + line_height + line_height * self.lines + } else { + line_height + }; wmenu .height(height) @@ -50,7 +60,7 @@ impl Config { .padding(self.padding.try_into().unwrap()) .lines(self.lines.try_into().unwrap()) .line_height(line_height) - .font(&font_string) + .font(&font.to_string()) .normal_bg(self.normal_color.bg.into()) .normal_fg(self.normal_color.fg.into()) .prompt_bg(self.prompt_color.bg.into()) @@ -66,8 +76,9 @@ impl Default for Config { bottom: false, lines: 0, padding: 16, - font: "monospace".to_string(), + font_family: "monospace".to_string(), font_size: 12, + language: None, normal_color: (0x222222ff, 0xbbbbbbffu32).into(), prompt_color: (0x005577ff, 0xeeeeeeffu32).into(), selection_color: (0x005577ff, 0x227799ffu32).into(), @@ -104,9 +115,8 @@ impl Menu { self } - // TODO: wrap MenuError with new error type - pub fn run(&self) -> Result { - self.inner().run() + pub fn run(&self) -> Result { + Ok(self.inner().run()?) } }