diff --git a/Cargo.lock b/Cargo.lock index 2d20f32..b9b1153 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,16 +118,18 @@ dependencies = [ [[package]] name = "capybar" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "battery", "chrono", "fontconfig", "fontdue", + "serde", "smithay-client-toolkit", "sysinfo", "thiserror 2.0.12", + "toml", "wayland-client", "wayland-protocols", ] @@ -301,6 +303,16 @@ dependencies = [ "cc", ] +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -525,6 +537,35 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "shlex" version = "1.3.0" @@ -639,6 +680,47 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tracing" version = "0.1.41" @@ -1103,6 +1185,15 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +dependencies = [ + "memchr", +] + [[package]] name = "xcursor" version = "0.3.8" diff --git a/Cargo.toml b/Cargo.toml index 52b9ff4..2bbee13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "capybar" -version = "0.1.0" +version = "0.1.1" edition = "2021" license = "MIT" @@ -14,6 +14,10 @@ wayland-protocols = "0.32.8" thiserror = "2.0.12" anyhow = "1.0.98" +#Config +toml = "0.8.23" +serde = {version = "1.0.219", features = [ "derive" ] } + ### Widget dependencies #Fonts fontconfig = "0.9.0" diff --git a/examples/basic/main.rs b/examples/basic/main.rs index 62378ca..0a31898 100644 --- a/examples/basic/main.rs +++ b/examples/basic/main.rs @@ -32,7 +32,8 @@ fn main() -> Result<(), Box> { // Fonts can be replaces by your liking. The first font added will be used for normal text, the // second for emoji - capybar.add_font_by_name("mono")?; + //capybar.add_font_by_name("mono")?; + capybar.add_font_by_name("jetbrainsmononerdfont")?; capybar.add_font_by_name("jetbrainsmononerdfont")?; let mut bar = Bar::new( @@ -59,13 +60,7 @@ fn main() -> Result<(), Box> { bar.create_child_left( CPU::new, CPUSettings { - text: TextSettings { - font_color: catpuccin_mocha.font, - size: 25.0, - - ..TextSettings::default() - }, - icon: TextSettings { + text_settings: TextSettings { font_color: catpuccin_mocha.font, size: 25.0, @@ -79,7 +74,6 @@ fn main() -> Result<(), Box> { }, )?; - //Center widgets bar.create_child_center( Clock::new, @@ -91,18 +85,11 @@ fn main() -> Result<(), Box> { }, )?; - // Right widgets bar.create_child_right( Battery::new, BatterySettings { - text: TextSettings { - font_color: catpuccin_mocha.font, - size: 25.0, - - ..TextSettings::default() - }, - icon: TextSettings { + text_settings: TextSettings { font_color: catpuccin_mocha.font, size: 25.0, @@ -116,7 +103,6 @@ fn main() -> Result<(), Box> { }, )?; - capybar.add_widget(bar)?; capybar.init(&mut event_queue)?.run(&mut event_queue)?; diff --git a/examples/toml_config/bar.png b/examples/toml_config/bar.png new file mode 100644 index 0000000..89872a9 Binary files /dev/null and b/examples/toml_config/bar.png differ diff --git a/examples/toml_config/config.toml b/examples/toml_config/config.toml new file mode 100644 index 0000000..5fa651d --- /dev/null +++ b/examples/toml_config/config.toml @@ -0,0 +1,38 @@ +#TOML does not support variables interpolation, so this is just a place to copy values from +[palete] +font = 0xf5e0dcff +background = 0x1e1e2eff +border = 0x74c7ecff + +[preloaded_fonts] +list = ["mono", "jetbrainsmononerdfont"] + +[bar.settings] +width = 1920 +background = 0x1e1e2eff +border = [1, 0x74c7ecff] + +[[bar.left]] + widget = "cpu" + [bar.left.settings] + size = 24 + font_color = 0xf5e0dcff + margin = [10,0,0,0] + update_rate = 1000 + +[[bar.center]] + widget = "clock" + [bar.center.settings] + size = 24 + font_color = 0xf5e0dcff + +[[bar.right]] + widget = "battery" + [bar.right.settings] + size = 24 + font_color = 0xf5e0dcff + margin = [0,10,0,0] + + + + diff --git a/examples/toml_config/main.rs b/examples/toml_config/main.rs new file mode 100644 index 0000000..5fe862d --- /dev/null +++ b/examples/toml_config/main.rs @@ -0,0 +1,17 @@ +use anyhow::Result; +use capybar::{config::Config, Root}; +use wayland_client::{globals::registry_queue_init, Connection}; + +fn main() -> Result<()> { + let config = Config::parse_toml("./examples/toml_config/config.toml".into())?; + + let conn = Connection::connect_to_env()?; + let (globals, mut event_queue) = registry_queue_init(&conn)?; + + let mut capybar = Root::new(&globals, &mut event_queue)?; + capybar.apply_config(config)?; + + capybar.init(&mut event_queue)?.run(&mut event_queue)?; + + Ok(()) +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..b72c7fe --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,32 @@ +pub mod util; +pub mod widgets; + +use anyhow::Result; +use serde::Deserialize; +use std::path::PathBuf; + +use util::font::PreloadedFonts; +use widgets::bar::Bar; + +#[derive(Deserialize, Debug)] +pub struct Config { + pub preloaded_fonts: PreloadedFonts, + + pub bar: Bar, +} + +impl Config { + pub const fn default() -> Self { + Self { + preloaded_fonts: PreloadedFonts::default(), + bar: Bar::default(), + } + } + + pub fn parse_toml(file: PathBuf) -> Result { + let content = std::fs::read_to_string(file)?; + let t: Config = toml::from_str(&content)?; + + Ok(t) + } +} diff --git a/src/config/util/font.rs b/src/config/util/font.rs new file mode 100644 index 0000000..f812f26 --- /dev/null +++ b/src/config/util/font.rs @@ -0,0 +1,84 @@ +use serde::{de::Visitor, Deserialize}; + +use crate::util::{fonts, Color}; + +#[derive(Default, Deserialize, Debug)] +pub struct PreloadedFonts { + pub list: Vec, +} + +impl PreloadedFonts { + pub const fn default() -> Self { + Self { list: Vec::new() } + } +} + +#[derive(Debug, Clone)] +pub struct Font { + pub name: String, +} + +impl<'de> Deserialize<'de> for Font { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct FontVisitor; + + impl<'de> Visitor<'de> for FontVisitor { + type Value = Font; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("Expected font name that can be found using fontconfig") + } + + fn visit_str(self, name: &str) -> Result + where + E: serde::de::Error, + { + match fonts::add_font_by_name(name) { + Ok(_) => Ok(Font { + name: name.to_string(), + }), + Err(e) => Err(E::custom(e.to_string())), + } + } + } + deserializer.deserialize_str(FontVisitor) + } +} + +#[derive(Deserialize, Debug, Clone, Default)] +pub struct FontStyle { + pub name: String, + #[serde(default = "FontStyle::default_text_size")] + pub size: usize, + #[serde(default = "FontStyle::default_text_color")] + pub color: Color, +} + +impl Font { + pub const fn default() -> Self { + Self { + name: String::new(), + } + } +} + +impl FontStyle { + pub const fn default() -> Self { + Self { + name: String::new(), + size: 0, + color: Color::NONE, + } + } + + pub const fn default_text_color() -> Color { + Color::BLACK + } + + pub const fn default_text_size() -> usize { + 12 + } +} diff --git a/src/config/util/mod.rs b/src/config/util/mod.rs new file mode 100644 index 0000000..92116df --- /dev/null +++ b/src/config/util/mod.rs @@ -0,0 +1,3 @@ +pub mod style; + +pub mod font; diff --git a/src/config/util/style.rs b/src/config/util/style.rs new file mode 100644 index 0000000..e4d4abb --- /dev/null +++ b/src/config/util/style.rs @@ -0,0 +1,39 @@ +use serde::Deserialize; + +use crate::util::Color; + +#[derive(Deserialize, Debug, Clone, Copy)] +pub struct Background { + pub enable: bool, + pub color: Color, +} + +impl Background { + pub const fn default() -> Self { + Self { + enable: false, + color: Color::NONE, + } + } +} + +#[derive(Deserialize, Debug, Default, Clone, Copy)] +#[serde(default)] +pub struct Border { + enable: bool, + color: Color, + size: u32, +} + +impl Border { + pub const fn default() -> Self { + Self { + enable: false, + color: Color::NONE, + size: 0, + } + } +} + +#[derive(Deserialize, Debug, Default)] +pub struct StyleConfig {} diff --git a/src/config/widgets/bar.rs b/src/config/widgets/bar.rs new file mode 100644 index 0000000..7028278 --- /dev/null +++ b/src/config/widgets/bar.rs @@ -0,0 +1,26 @@ +use serde::Deserialize; + +use crate::widgets::{containers::bar::BarSettings, WidgetsList}; + +#[derive(Default, Deserialize, Debug)] +pub struct Bar { + #[serde(default)] + pub settings: BarSettings, + #[serde(default)] + pub left: Vec, + #[serde(default)] + pub center: Vec, + #[serde(default)] + pub right: Vec, +} + +impl Bar { + pub const fn default() -> Self { + Self { + settings: BarSettings::default(), + left: Vec::new(), + center: Vec::new(), + right: Vec::new(), + } + } +} diff --git a/src/config/widgets/mod.rs b/src/config/widgets/mod.rs new file mode 100644 index 0000000..46f285c --- /dev/null +++ b/src/config/widgets/mod.rs @@ -0,0 +1 @@ +pub mod bar; diff --git a/src/lib.rs b/src/lib.rs index 42fa059..b193ec6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod util; pub mod widgets; diff --git a/src/root.rs b/src/root.rs index b28980f..e8e1145 100644 --- a/src/root.rs +++ b/src/root.rs @@ -1,12 +1,11 @@ use std::{ + cell::RefCell, cmp::{max, min}, - error::Error, num::NonZeroU32, rc::Rc, }; use anyhow::Result; -use fontdue::Font; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_keyboard, delegate_layer, delegate_output, delegate_pointer, @@ -26,24 +25,37 @@ use smithay_client_toolkit::{ }, WaylandSurface, }, - shm::{slot::SlotPool, Shm, ShmHandler}, + shm::{Shm, ShmHandler}, }; +use thiserror::Error; use wayland_client::{ globals::GlobalList, protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_surface}, Connection, EventQueue, QueueHandle, }; +/// Structure containing things all the widgets in capybar needs access to +pub struct Environment { + pub config: Config, + pub drawer: RefCell, +} + use crate::{ + config::Config, util::{ - font::{Fonts, FontsError}, + fonts::{self, FontsError}, Drawer, }, - widgets::{Widget, WidgetNew}, + widgets::{ + battery::Battery, clock::Clock, containers::bar::Bar, cpu::CPU, text::Text, Widget, + WidgetNew, WidgetsList, + }, }; -pub struct Environment { - pub fonts: Fonts, +#[derive(Error, Debug)] +pub enum RootError { + #[error("Environment is not initialised before drawing")] + EnvironmentNotInit, } pub struct Root { @@ -64,11 +76,8 @@ pub struct Root { keyboard_focus: bool, pointer: Option, - drawer: Option, widgets: Vec>, - fonts: Fonts, - - env: Rc, + env: Option>, } impl CompositorHandler for Root { @@ -336,10 +345,7 @@ impl ProvidesRegistryState for Root { } impl Root { - pub fn new( - globals: &GlobalList, - event_queue: &mut EventQueue, - ) -> Result> { + pub fn new(globals: &GlobalList, event_queue: &mut EventQueue) -> Result { let qh = event_queue.handle(); let compositor = @@ -370,27 +376,64 @@ impl Root { pointer: None, widgets: Vec::new(), - fonts: Fonts::new().unwrap(), - drawer: None, - - env: Rc::new(Environment { - fonts: Fonts::new().unwrap(), - }), + env: None, }; Ok(bar) } - pub fn init( - &mut self, - event_queue: &mut EventQueue, - ) -> Result<&mut Self, Box> { + pub fn apply_config(&mut self, config: Config) -> Result<()> { + let mut bar = Bar::new(None, config.bar.settings)?; + + for widget in config.bar.left { + match widget { + WidgetsList::Text(settings) => bar.create_child_left(Text::new, settings)?, + WidgetsList::Clock(settings) => bar.create_child_left(Clock::new, settings)?, + WidgetsList::Battery(settings) => bar.create_child_left(Battery::new, settings)?, + WidgetsList::CPU(settings) => bar.create_child_left(CPU::new, settings)?, + } + } + + for widget in config.bar.center { + match widget { + WidgetsList::Text(settings) => bar.create_child_center(Text::new, settings)?, + WidgetsList::Clock(settings) => bar.create_child_center(Clock::new, settings)?, + WidgetsList::Battery(settings) => { + bar.create_child_center(Battery::new, settings)? + } + WidgetsList::CPU(settings) => bar.create_child_center(CPU::new, settings)?, + } + } + + for widget in config.bar.right { + match widget { + WidgetsList::Text(settings) => bar.create_child_right(Text::new, settings)?, + WidgetsList::Clock(settings) => bar.create_child_right(Clock::new, settings)?, + WidgetsList::Battery(settings) => bar.create_child_right(Battery::new, settings)?, + WidgetsList::CPU(settings) => bar.create_child_right(CPU::new, settings)?, + } + } + + self.add_widget(bar)?; + Ok(()) + } + + pub fn init(&mut self, event_queue: &mut EventQueue) -> Result<&mut Self> { self.layer.set_anchor(Anchor::TOP); self.layer .set_keyboard_interactivity(KeyboardInteractivity::OnDemand); - self.width = 32; + self.width = 0; self.height = 0; + self.env = Some(Rc::new(Environment { + config: Config::default(), + drawer: RefCell::new(Drawer::new(&mut self.shm, 1, 1)), + })); + + for widget in &mut self.widgets { + widget.bind(Rc::clone(self.env.as_ref().unwrap()))?; + } + for widget in &mut self.widgets { widget.init()?; let data = widget.data().borrow_mut(); @@ -406,7 +449,8 @@ impl Root { let info = self .output_state .info(&output) - .ok_or_else(|| "output has no info".to_owned())?; + .ok_or_else(|| "output has no info".to_owned()) + .unwrap(); if let Some((width, height)) = info.logical_size { self.width = max(self.width, width as u32); @@ -418,14 +462,16 @@ impl Root { self.layer.set_exclusive_zone(self.height as i32); self.layer.commit(); - let pool = SlotPool::new((self.width * self.height * 4) as usize, &self.shm).unwrap(); - - self.drawer = Some(Drawer::new(pool, self.width as i32, self.height as i32)); + self.env.as_ref().unwrap().drawer.borrow_mut().update_sizes( + &mut self.shm, + self.width as i32, + self.height as i32, + ); Ok(self) } - pub fn run(&mut self, event_queue: &mut EventQueue) -> Result<&mut Self, Box> { + pub fn run(&mut self, event_queue: &mut EventQueue) -> Result<&mut Self> { let _ = event_queue.blocking_dispatch(self); loop { @@ -440,22 +486,16 @@ impl Root { } pub fn add_font_by_name(&mut self, name: &'static str) -> Result<(), FontsError> { - self.fonts.add_font_by_name(name)?; - Rc::get_mut(&mut self.env) - .unwrap() - .fonts - .add_font_by_name(name) - } - - pub fn fonts(&self) -> Rc> { - self.fonts.fonts() + fonts::add_font_by_name(name) } pub fn add_widget(&mut self, mut widget: W) -> Result<()> where W: Widget + 'static, { - widget.bind(Rc::clone(&self.env))?; + if let Some(env) = &self.env { + widget.bind(Rc::clone(env))?; + } self.widgets.push(Box::new(widget)); Ok(()) } @@ -465,20 +505,21 @@ impl Root { W: WidgetNew + Widget + 'static, F: FnOnce(Option>, W::Settings) -> Result, { - self.widgets - .push(Box::new(f(Some(Rc::clone(&self.env)), settings)?)); + self.widgets.push(Box::new(f(self.env.clone(), settings)?)); Ok(()) } fn draw(&mut self, qh: &QueueHandle) -> Result<()> { + if self.env.is_none() { + return Err(RootError::EnvironmentNotInit.into()); + } + self.layer .wl_surface() .damage_buffer(0, 0, self.width as i32, self.height as i32); - if let Some(drawer) = &mut self.drawer { - for widget in self.widgets.iter_mut() { - widget.draw(drawer)?; - } + for widget in self.widgets.iter() { + widget.draw()?; } // Request our next frame @@ -486,9 +527,12 @@ impl Root { .wl_surface() .frame(qh, self.layer.wl_surface().clone()); - if let Some(drawer) = &self.drawer { - drawer.commit(self.layer.wl_surface()); - } + self.env + .as_ref() + .unwrap() + .drawer + .borrow_mut() + .commit(self.layer.wl_surface()); self.flag = false; Ok(()) diff --git a/src/util/color.rs b/src/util/color.rs index e0b667b..59b7e31 100644 --- a/src/util/color.rs +++ b/src/util/color.rs @@ -1,6 +1,9 @@ use std::fmt::Display; -#[derive(Clone, Copy, Debug, Default)] +use serde::Deserialize; + +/// Color structure used in capy. Color is stored as an rgba value. +#[derive(Clone, Copy, Debug, Default, Deserialize)] pub struct Color(u32); impl Display for Color { diff --git a/src/util/drawer.rs b/src/util/drawer.rs index 33812fb..a003c3d 100644 --- a/src/util/drawer.rs +++ b/src/util/drawer.rs @@ -2,7 +2,10 @@ use core::fmt; use std::error::Error; use fontdue::{layout::GlyphPosition, Font}; -use smithay_client_toolkit::shm::slot::{Buffer, SlotPool}; +use smithay_client_toolkit::shm::{ + slot::{Buffer, SlotPool}, + Shm, +}; use wayland_client::protocol::{wl_shm, wl_surface::WlSurface}; use crate::widgets::WidgetData; @@ -26,6 +29,7 @@ impl fmt::Display for DrawerError { } } +/// Utility structure used to simplify drawing the widgets. #[derive(Debug)] pub struct Drawer { pool: SlotPool, @@ -36,20 +40,24 @@ pub struct Drawer { } impl Drawer { - pub fn new(mut pool: SlotPool, width: i32, height: i32) -> Self { - let buffer = pool - .create_buffer(width, height, width * 4, wl_shm::Format::Argb8888) - .unwrap() - .0; + pub fn new(shm: &mut Shm, width: i32, height: i32) -> Self { Drawer { - pool, - buffer: Some(buffer), + pool: SlotPool::new((width * height * 4) as usize, shm).unwrap(), + buffer: None, width, height, } } + pub fn update_sizes(&mut self, shm: &mut Shm, width: i32, height: i32) { + self.height = height; + self.width = width; + self.buffer = None; + self.pool = SlotPool::new((width * height * 4) as usize, shm).unwrap(); + } + + /// Commit buffer to a surface pub fn commit(&self, surface: &WlSurface) { if let Some(buffer) = &self.buffer { buffer.attach_to(surface).expect("buffer attach"); @@ -57,6 +65,8 @@ impl Drawer { } } + /// Put a single colored pixel in a relative space. Drawer converts local position in a widget + /// to global buffer position using provided `WidgetData`. pub fn draw_pixel(&mut self, data: &WidgetData, pos: (usize, usize), color: Color) { let buffer = self.buffer.get_or_insert_with(|| { self.pool @@ -97,6 +107,8 @@ impl Drawer { } } + /// Draw a glyph from font. Drawer converts local position in a widget to global buf position + /// using provided `WidgetData`. pub fn draw_glyph( &mut self, data: &WidgetData, diff --git a/src/util/font.rs b/src/util/font.rs deleted file mode 100644 index 07b497e..0000000 --- a/src/util/font.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::rc::Rc; - -use fontconfig::Fontconfig; -use thiserror::Error; - -pub struct Fonts { - fontconfig: Fontconfig, - fonts: Rc>, -} - -#[derive(Error, Debug)] -pub enum FontsError { - #[error(transparent)] - IO(#[from] std::io::Error), - #[error("Font {0} was not found")] - FontNotFound(&'static str), -} - -impl Fonts { - pub fn new() -> Option { - let fc = Fontconfig::new()?; - - Some(Fonts { - fontconfig: fc, - fonts: Rc::new(Vec::new()), - }) - } - - pub fn add_font_by_name(&mut self, name: &'static str) -> Result<(), FontsError> { - let font = match self.fontconfig.find(name, None) { - Some(f) => f, - None => return Err(FontsError::FontNotFound(name)), - }; - - let bytes = match std::fs::read(font.path.as_path()) { - Ok(b) => b, - Err(e) => return Err(FontsError::IO(e)), - }; - - let font = fontdue::Font::from_bytes( - bytes, - fontdue::FontSettings { - ..Default::default() - }, - ) - .unwrap(); - - let a = Rc::get_mut(&mut self.fonts).unwrap(); - a.push(font); - - Ok(()) - } - - pub fn fonts(&self) -> Rc> { - Rc::clone(&self.fonts) - } -} diff --git a/src/util/fonts.rs b/src/util/fonts.rs new file mode 100644 index 0000000..8114cb2 --- /dev/null +++ b/src/util/fonts.rs @@ -0,0 +1,83 @@ +use std::{ + collections::HashMap, + sync::{LazyLock, Mutex, MutexGuard}, +}; + +use anyhow::Result; +use fontconfig::Fontconfig; +use thiserror::Error; + +/// Wrapper for fontconfig and fontdue. Only one exists per capybar instance and shared safly +/// between difrent [crate::root::Root] instance +pub struct FontsMap { + fontconfig: Fontconfig, + + fonts_map: Mutex>, + + fonts_vec: Mutex>, +} + +static FONTS: LazyLock = LazyLock::new(|| FontsMap::new().unwrap()); + +#[derive(Error, Debug)] +pub enum FontsError { + #[error(transparent)] + IO(#[from] std::io::Error), + #[error("Font {0} was not found")] + FontNotFound(String), +} + +impl FontsMap { + fn new() -> Option { + let fc = Fontconfig::new()?; + + Some(FontsMap { + fontconfig: fc, + fonts_map: Mutex::new(HashMap::new()), + fonts_vec: Mutex::new(Vec::new()), + }) + } +} + +pub fn get() -> &'static LazyLock { + &FONTS +} + +/// Fonts map contains map of font name to index in vector +pub fn fonts_map() -> MutexGuard<'static, HashMap> { + FONTS.fonts_map.lock().unwrap() +} + +/// Fonts vector contains all loaded fonts +pub fn fonts_vec() -> MutexGuard<'static, Vec> { + FONTS.fonts_vec.lock().unwrap() +} + +/// Adds font to current FontsMap instance. Font name is case insensitive. Font gets added to fonts +/// vector and map +pub fn add_font_by_name(name: &str) -> Result<(), FontsError> { + let font = match FONTS.fontconfig.find(name, None) { + Some(f) => f, + None => return Err(FontsError::FontNotFound(name.to_string())), + }; + + let bytes = match std::fs::read(font.path.as_path()) { + Ok(b) => b, + Err(e) => return Err(FontsError::IO(e)), + }; + + let font = fontdue::Font::from_bytes( + bytes, + fontdue::FontSettings { + ..Default::default() + }, + ) + .unwrap(); + + let mut fonts_map = FONTS.fonts_map.lock().unwrap(); + let mut fonts_vec = FONTS.fonts_vec.lock().unwrap(); + fonts_map.insert(name.to_string(), fonts_vec.len()); + fonts_vec.push(font); + + Ok(()) +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 188d339..df6aad7 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -4,4 +4,4 @@ pub use color::Color; pub mod drawer; pub use drawer::Drawer; -pub mod font; +pub mod fonts; diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 72ab83b..692e35b 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -2,33 +2,52 @@ use std::{cell::RefCell, ops::Add}; use anyhow::Result; use battery::{Manager, State}; +use serde::Deserialize; use super::{ text::{Text, TextSettings}, - widget::{Widget, WidgetNew}, - Style, WidgetData, WidgetStyled, + Style, Widget, WidgetData, WidgetNew, WidgetStyled, }; +const fn battery_not_charging_default() -> [char; 11] { + ['󰂎', '󰁺', '󰁻', '󰁼', '󰁽', '󰁾', '󰁿', '󰂀', '󰂁', '󰂂', '󰁹'] +} + +const fn battery_charging_default() -> [char; 11] { + ['󰢟', '󰢜', '󰂆', '󰂇', '󰂈', '󰢝', '󰂉', '󰢞', '󰂊', '󰂋', '󰂅'] +} + +/// Settings of a [Battery] widget +#[derive(Debug, Deserialize, Clone)] pub struct BatterySettings { + /// Array of all symbols for percentages of battery when it is not charging. Symbols are changed + /// every 10% including 0%, therefor needs 11 symbols. + #[serde(default = "battery_not_charging_default")] pub battery_not_charging: [char; 11], + + /// Array of all symbols for percentages of battery when it is charging. Symbols are changed + /// every 10% including 0%, therefor needs 11 symbols. + #[serde(default = "battery_charging_default")] pub battery_charging: [char; 11], - pub text: TextSettings, - pub icon: TextSettings, + /// Settings for underlying [Text] widget + #[serde(default, flatten)] + pub text_settings: TextSettings, + #[serde(default, flatten)] pub default_data: WidgetData, + #[serde(default, flatten)] pub style: Style, } impl Default for BatterySettings { fn default() -> Self { Self { - battery_not_charging: ['󰂎', '󰁺', '󰁻', '󰁼', '󰁽', '󰁾', '󰁿', '󰂀', '󰂁', '󰂂', '󰁹'], - battery_charging: ['󰢟', '󰢜', '󰂆', '󰂇', '󰂈', '󰢝', '󰂉', '󰢞', '󰂊', '󰂋', '󰂅'], + battery_not_charging: battery_not_charging_default(), + battery_charging: battery_charging_default(), - text: TextSettings::default(), - icon: TextSettings::default(), + text_settings: TextSettings::default(), default_data: WidgetData::default(), @@ -75,6 +94,7 @@ impl BatteryInfo { } } +/// Widget displaying current battery status. pub struct Battery { manager: Manager, @@ -87,6 +107,7 @@ pub struct Battery { } impl Battery { + /// Get information of current battery status pub fn get_info(&self) -> Option { Some( self.manager @@ -165,7 +186,7 @@ impl Widget for Battery { Ok(()) } - fn draw(&self, drawer: &mut crate::util::Drawer) -> anyhow::Result<()> { + fn draw(&self) -> anyhow::Result<()> { let info = self.get_info(); let mut prev_charge = self.prev_charge.borrow_mut(); @@ -187,7 +208,7 @@ impl Widget for Battery { ) .as_str(), ); - text.change_text(format!("{percentage}").as_str()); + text.change_text(format!("{percentage}%").as_str()); } } None => { @@ -201,8 +222,8 @@ impl Widget for Battery { } self.align()?; - self.percent.borrow_mut().draw(drawer)?; - self.icon.borrow_mut().draw(drawer) + self.percent.borrow_mut().draw()?; + self.icon.borrow_mut().draw() } } @@ -223,8 +244,12 @@ impl WidgetNew for Battery { env.clone(), TextSettings { text: "󰂎".to_string(), + default_data: WidgetData { + margin: (0, 0, 0, 0), + ..WidgetData::default() + }, fontid: 1, - ..settings.text.clone() + ..settings.text_settings.clone() }, )?), percent: RefCell::new(Text::new( @@ -236,7 +261,7 @@ impl WidgetNew for Battery { margin: (5, 0, 2, 0), ..WidgetData::default() }, - ..settings.text.clone() + ..settings.text_settings.clone() }, )?), diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 47e2c99..a90215c 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -2,21 +2,35 @@ use std::{cell::RefCell, rc::Rc}; use anyhow::Result; use chrono::Local; +use serde::Deserialize; use crate::{ root::Environment, - util::{Color, Drawer}, + util::Color, widgets::{text::Text, Widget}, }; use super::{text::TextSettings, WidgetData, WidgetNew}; +fn default_format() -> String { + "%H:%M".to_string() +} + +/// Settings of a [Clock] widget +#[derive(Deserialize, Debug, Clone)] pub struct ClockSettings { + /// Default font size + #[serde(default)] pub size: f32, + + /// Default format strftime format + #[serde(default = "default_format")] pub format: String, + #[serde(default)] pub font_color: Color, + #[serde(default)] pub default_data: WidgetData, } @@ -24,7 +38,7 @@ impl Default for ClockSettings { fn default() -> Self { Self { size: 25.0, - format: "%H:%M:%S".to_string(), + format: default_format(), font_color: Color::BLACK, @@ -33,6 +47,7 @@ impl Default for ClockSettings { } } +/// Widget displaying current time. Supports C's strftime formating. pub struct Clock { text: RefCell, settings: ClockSettings, @@ -41,6 +56,7 @@ pub struct Clock { } impl Clock { + /// Force update current time pub fn update(&self) -> &Self { let mut text = self.text.borrow_mut(); text.change_text(&Local::now().format(&self.settings.format).to_string()); @@ -69,9 +85,9 @@ impl Widget for Clock { Ok(()) } - fn draw(&self, drawer: &mut Drawer) -> Result<()> { + fn draw(&self) -> Result<()> { self.update(); - self.text.borrow_mut().draw(drawer) + self.text.borrow_mut().draw() } fn data(&self) -> &RefCell { diff --git a/src/widgets/containers/bar.rs b/src/widgets/containers/bar.rs index 9a2efb6..55dad65 100644 --- a/src/widgets/containers/bar.rs +++ b/src/widgets/containers/bar.rs @@ -1,28 +1,35 @@ use std::{cell::RefCell, rc::Rc}; use anyhow::Result; +use serde::Deserialize; use crate::{ root::Environment, - widgets::{Style, Widget, WidgetData, WidgetNew}, + widgets::{Style, Widget, WidgetData, WidgetError, WidgetNew}, }; use super::{ - container::Container, row::{Alignment, Row, RowSettings}, + Container, }; -#[derive(Debug, Clone)] +/// Settings of a [Bar] containert +#[derive(Default, Debug, Clone, Deserialize)] pub struct BarSettings { + #[serde(flatten, default)] pub default_data: WidgetData, + /// Distance between widgets in underlying rows. Stored as a tuple of (Distance in left, + /// distance in center, distance in right) + #[serde(default)] pub padding: (usize, usize, usize), + #[serde(flatten)] pub style: Style, } -impl Default for BarSettings { - fn default() -> Self { +impl BarSettings { + pub const fn default() -> Self { Self { default_data: WidgetData::default(), padding: (10, 10, 10), @@ -31,6 +38,7 @@ impl Default for BarSettings { } } +/// Main widget in capybar. Stores 3 alligned [Row] containers. pub struct Bar { settings: BarSettings, data: RefCell, @@ -86,10 +94,16 @@ impl Widget for Bar { fn bind(&mut self, env: std::rc::Rc) -> anyhow::Result<()> { self.left.borrow_mut().bind(Rc::clone(&env))?; self.center.borrow_mut().bind(Rc::clone(&env))?; - self.right.borrow_mut().bind(Rc::clone(&env)) + self.right.borrow_mut().bind(Rc::clone(&env))?; + self.env = Some(env); + Ok(()) } - fn draw(&self, drawer: &mut crate::util::Drawer) -> anyhow::Result<()> { + fn draw(&self) -> anyhow::Result<()> { + if self.env.is_none() { + return Err(WidgetError::DrawWithNoEnv("Bar".to_string()).into()); + } + let data = self.data.borrow_mut(); let border = match self.settings.style.border { @@ -97,63 +111,59 @@ impl Widget for Bar { None => (0, None), }; - if let Some(color) = self.settings.style.background { - for x in border.0..data.width - border.0 { - for y in border.0..data.height - border.0 { - drawer.draw_pixel(&data, (x, y), color); + { + let mut drawer = self.env.as_ref().unwrap().drawer.borrow_mut(); + if let Some(color) = self.settings.style.background { + for x in border.0..data.width - border.0 { + for y in border.0..data.height - border.0 { + drawer.draw_pixel(&data, (x, y), color); + } } } - } - if let Some(color) = border.1 { - for x in 0..border.0 { - for y in 0..data.height { - drawer.draw_pixel(&data, (x, y), color); - drawer.draw_pixel(&data, (data.width - 1 - x, y), color); + if let Some(color) = border.1 { + for x in 0..border.0 { + for y in 0..data.height { + drawer.draw_pixel(&data, (x, y), color); + drawer.draw_pixel(&data, (data.width - 1 - x, y), color); + } } - } - for x in 0..data.width { - for y in 0..border.0 { - drawer.draw_pixel(&data, (x, y), color); - drawer.draw_pixel(&data, (x, data.height - 1 - y), color); + for x in 0..data.width { + for y in 0..border.0 { + drawer.draw_pixel(&data, (x, y), color); + drawer.draw_pixel(&data, (x, data.height - 1 - y), color); + } } } } + let left = self.left.borrow_mut(); { - let left = self.left.borrow_mut(); - { - let mut ld = left.data().borrow_mut(); + let mut ld = left.data().borrow_mut(); - ld.position.0 = data.position.0 + border.0; - ld.position.1 = data.position.1 + border.0; - } - - left.draw(drawer)?; + ld.position.0 = data.position.0 + border.0; + ld.position.1 = data.position.1 + border.0; } + left.draw()?; + let center = self.center.borrow_mut(); { - let center = self.center.borrow_mut(); - { - let mut cd = center.data().borrow_mut(); + let mut cd = center.data().borrow_mut(); - cd.position.0 = data.position.0 + (data.width - cd.width) / 2; - cd.position.1 = data.position.1 + border.0; - } - center.draw(drawer)?; + cd.position.0 = data.position.0 + (data.width - cd.width) / 2; + cd.position.1 = data.position.1 + border.0; } + center.draw()?; + let right = self.right.borrow_mut(); { - let right = self.right.borrow_mut(); - { - let mut rd = right.data().borrow_mut(); + let mut rd = right.data().borrow_mut(); - rd.position.0 = data.position.0 + data.width - border.0; - rd.position.1 = data.position.1 + border.0; - } - right.draw(drawer)?; + rd.position.0 = data.position.0 + data.width - border.0; + rd.position.1 = data.position.1 + border.0; } + right.draw()?; Ok(()) } @@ -238,11 +248,11 @@ impl Container for Bar { todo!(); } - fn children(&self) -> &super::container::WidgetVec { + fn children(&self) -> &super::WidgetVec { todo!(); } - fn children_mut(&mut self) -> &super::container::WidgetVec { + fn children_mut(&mut self) -> &super::WidgetVec { todo!(); } } diff --git a/src/widgets/containers/container.rs b/src/widgets/containers/container.rs deleted file mode 100644 index 876b1d7..0000000 --- a/src/widgets/containers/container.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::borrow::{Borrow, BorrowMut}; - -use anyhow::Result; - -use crate::widgets::Widget; - -pub struct WidgetVec { - pub is_aligned: bool, - pub widgets: Vec>, -} - -impl Default for WidgetVec { - fn default() -> Self { - Self::new() - } -} - -impl WidgetVec { - pub fn new() -> Self { - Self { - is_aligned: false, - widgets: Vec::new(), - } - } - - pub fn is_aligned(&self) -> bool { - self.is_aligned - } - - pub fn is_empty(&self) -> bool { - self.widgets.is_empty() - } - - pub fn widgets(&self) -> &Vec> { - self.borrow() - } - - pub fn widgets_mut(&mut self) -> &mut Vec> { - self.borrow_mut() - } -} - -impl Borrow>> for WidgetVec { - fn borrow(&self) -> &Vec> { - &self.widgets - } -} - -impl BorrowMut>> for WidgetVec { - fn borrow_mut(&mut self) -> &mut Vec> { - self.is_aligned = false; - &mut self.widgets - } -} - -pub trait Container: Widget { - fn align_children(&self) -> Result<()>; - - fn children(&self) -> &WidgetVec; - - fn children_mut(&mut self) -> &WidgetVec; -} diff --git a/src/widgets/containers/mod.rs b/src/widgets/containers/mod.rs index 8bab075..1444f7e 100644 --- a/src/widgets/containers/mod.rs +++ b/src/widgets/containers/mod.rs @@ -1,3 +1,69 @@ pub mod bar; -pub mod container; pub mod row; + +use std::borrow::{Borrow, BorrowMut}; + +use anyhow::Result; + +use crate::widgets::Widget; + +/// Wrapper for [Vec]. Currently is pretty useles though.... +pub struct WidgetVec { + pub is_aligned: bool, + pub widgets: Vec>, +} + +impl Default for WidgetVec { + fn default() -> Self { + Self::new() + } +} + +impl WidgetVec { + pub fn new() -> Self { + Self { + is_aligned: false, + widgets: Vec::new(), + } + } + + pub fn is_aligned(&self) -> bool { + self.is_aligned + } + + pub fn is_empty(&self) -> bool { + self.widgets.is_empty() + } + + pub fn widgets(&self) -> &Vec> { + self.borrow() + } + + pub fn widgets_mut(&mut self) -> &mut Vec> { + self.borrow_mut() + } +} + +impl Borrow>> for WidgetVec { + fn borrow(&self) -> &Vec> { + &self.widgets + } +} + +impl BorrowMut>> for WidgetVec { + fn borrow_mut(&mut self) -> &mut Vec> { + self.is_aligned = false; + &mut self.widgets + } +} + +/// Container is a widget that is responsible for positioning of it's child widgets. It may or may +/// not have any additional logic behind it. +pub trait Container: Widget { + /// Align is called before drawing the container + fn align_children(&self) -> Result<()>; + + fn children(&self) -> &WidgetVec; + + fn children_mut(&mut self) -> &WidgetVec; +} diff --git a/src/widgets/containers/row.rs b/src/widgets/containers/row.rs index 5dbe09d..a282a22 100644 --- a/src/widgets/containers/row.rs +++ b/src/widgets/containers/row.rs @@ -5,11 +5,11 @@ use thiserror::Error; use crate::{ root::Environment, - util::{Color, Drawer}, - widgets::{Widget, WidgetData, WidgetNew}, + util::Color, + widgets::{Widget, WidgetData, WidgetError, WidgetNew}, }; -use super::container::{Container, WidgetVec}; +use super::{Container, WidgetVec}; #[derive(Debug, Clone, Copy)] pub enum Alignment { @@ -31,6 +31,7 @@ impl Default for Alignment { } } +/// Settings of a [Row] container #[derive(Debug, Default, Clone, Copy)] pub struct RowSettings { pub background: Option, @@ -42,13 +43,14 @@ pub struct RowSettings { #[derive(Error, Debug)] pub enum RowError { - #[error("Row is not wide enough to display all of it's child")] + #[error("Row is not wide enough to display all of it's children")] WidthOverflow, #[error("anyhow error: {0}")] Other(#[from] anyhow::Error), } +/// Container that stores widgets in a row. pub struct Row { settings: RowSettings, data: RefCell, @@ -90,7 +92,11 @@ impl Widget for Row { Ok(()) } - fn draw(&self, drawer: &mut Drawer) -> Result<()> { + fn draw(&self) -> Result<()> { + if self.env.is_none() { + return Err(WidgetError::DrawWithNoEnv("Row".to_string()).into()); + } + self.align_children()?; let children = self.children.borrow_mut(); @@ -105,32 +111,35 @@ impl Widget for Row { None => (0, None), }; - if let Some(color) = self.settings.background { - for x in border.0..data.width - border.0 { - for y in border.0..data.height - border.0 { - drawer.draw_pixel(&data, (x, y), color); + { + let mut drawer = self.env.as_ref().unwrap().drawer.borrow_mut(); + if let Some(color) = self.settings.background { + for x in border.0..data.width - border.0 { + for y in border.0..data.height - border.0 { + drawer.draw_pixel(&data, (x, y), color); + } } } - } - if let Some(color) = border.1 { - for x in 0..border.0 { - for y in 0..data.height { - drawer.draw_pixel(&data, (x, y), color); - drawer.draw_pixel(&data, (data.width - 1 - x, y), color); + if let Some(color) = border.1 { + for x in 0..border.0 { + for y in 0..data.height { + drawer.draw_pixel(&data, (x, y), color); + drawer.draw_pixel(&data, (data.width - 1 - x, y), color); + } } - } - for x in 0..data.width { - for y in 0..border.0 { - drawer.draw_pixel(&data, (x, y), color); - drawer.draw_pixel(&data, (x, data.height - 1 - y), color); + for x in 0..data.width { + for y in 0..border.0 { + drawer.draw_pixel(&data, (x, y), color); + drawer.draw_pixel(&data, (x, data.height - 1 - y), color); + } } } } for widget in children.widgets() { - widget.draw(drawer)?; + widget.draw()?; } Ok(()) diff --git a/src/widgets/cpu.rs b/src/widgets/cpu.rs index ab6d319..94dc75b 100644 --- a/src/widgets/cpu.rs +++ b/src/widgets/cpu.rs @@ -1,6 +1,8 @@ use std::cell::RefCell; use anyhow::Result; +use chrono::{DateTime, Local, TimeDelta}; +use serde::Deserialize; use sysinfo::{CpuRefreshKind, RefreshKind, System}; use super::{ @@ -8,16 +10,25 @@ use super::{ Style, Widget, WidgetData, WidgetNew, }; -#[derive(Debug, Default, Clone)] +/// Settings of a [CPU] widget +#[derive(Deserialize, Debug, Default, Clone)] pub struct CPUSettings { + #[serde(default, flatten)] pub default_data: WidgetData, - pub text: TextSettings, - pub icon: TextSettings, + /// Settings for underlying [Text] widget + #[serde(default, flatten)] + pub text_settings: TextSettings, + #[serde(default, flatten)] pub style: Style, + + /// How often to update CPU status in milliseconds + #[serde(default)] + pub update_rate: u32, } +/// Widget displaying current CPU status. pub struct CPU { data: RefCell, @@ -25,6 +36,9 @@ pub struct CPU { percent: RefCell, sys: RefCell, + + last_update: RefCell>, + upadte_rate: TimeDelta, } impl CPU { @@ -77,22 +91,28 @@ impl Widget for CPU { self.align() } - fn draw(&self, drawer: &mut crate::util::Drawer) -> Result<()> { - let info = self.get_info(); + fn draw(&self) -> Result<()> { + let mut last_update = self.last_update.borrow_mut(); - { - let mut text = self.percent.borrow_mut(); - if self.sys.borrow_mut().cpus().is_empty() { - self.icon.borrow_mut().change_text(""); - text.change_text("ERR"); - } else { - text.change_text(format!("{info}%").as_str()); + if Local::now() - *last_update >= self.upadte_rate { + let info = self.get_info(); + + { + let mut text = self.percent.borrow_mut(); + if self.sys.borrow_mut().cpus().is_empty() { + self.icon.borrow_mut().change_text(""); + text.change_text("ERR"); + } else { + text.change_text(format!("{info}%").as_str()); + } } + + self.align()?; + *last_update = Local::now(); } - self.align()?; - self.percent.borrow_mut().draw(drawer)?; - self.icon.borrow_mut().draw(drawer) + self.percent.borrow_mut().draw()?; + self.icon.borrow_mut().draw() } fn data(&self) -> &RefCell { @@ -116,8 +136,12 @@ impl WidgetNew for CPU { env.clone(), TextSettings { text: "".to_string(), + default_data: WidgetData { + margin: (0, 0, 0, 0), + ..WidgetData::default() + }, fontid: 1, - ..settings.text.clone() + ..settings.text_settings.clone() }, )?), percent: RefCell::new(Text::new( @@ -129,13 +153,18 @@ impl WidgetNew for CPU { margin: (5, 0, 2, 0), ..WidgetData::default() }, - ..settings.text.clone() + ..settings.text_settings.clone() }, )?), sys: RefCell::new(System::new_with_specifics( RefreshKind::nothing().with_cpu(CpuRefreshKind::nothing().with_cpu_usage()), )), + + upadte_rate: TimeDelta::milliseconds(settings.update_rate as i64), + last_update: RefCell::new( + chrono::Local::now() - TimeDelta::milliseconds(settings.update_rate as i64), + ), }) } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 4cf8a8a..ad23519 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,9 +1,131 @@ -pub mod widget; -pub use widget::*; - pub mod containers; pub mod battery; pub mod clock; pub mod cpu; pub mod text; + +use std::{cell::RefCell, rc::Rc}; + +use anyhow::Result; +use serde::Deserialize; +use thiserror::Error; + +use crate::{root::Environment, util::Color}; + +use {battery::BatterySettings, clock::ClockSettings, cpu::CPUSettings, text::TextSettings}; + +/// A **data structure** that can be used as a widget inside a capybar. +pub trait Widget { + /// Bind a widget to a new environment. + fn bind(&mut self, env: Rc) -> Result<()>; + + /// Draw an entire widget to a `Drawer` + fn draw(&self) -> Result<()>; + + /// Prepare `Widget` for a first draw + fn init(&self) -> Result<()>; + + /// Return `WidgetData` associated to the widget + fn data(&self) -> &RefCell; +} + +/// A `Widget` that can be unifiedly created. +/// +/// Implementing this trait allows creating `Widget` and binding the environment without +/// intermidiate steps. Simplifies widget creation inside of scripts. +pub trait WidgetNew: Widget { + type Settings; + + fn new(env: Option>, settings: Self::Settings) -> Result + where + Self: Sized; +} + +#[derive(Debug, Error)] +pub enum WidgetError { + #[error("Invalid widget bounds")] + InvalidBounds, + + #[error("Trying to draw a widget \"{0}\" not bound to any environment")] + DrawWithNoEnv(String), +} + +/// Global common data used by `Widget` data structure. +#[derive(Default, Debug, Clone, Copy, Deserialize)] +pub struct WidgetData { + /// Offset of the widget in a global scope. Usually controlled by parent. + #[serde(default)] + pub position: (usize, usize), + + /// Widgth of the widget should be controlled by the widget itself + #[serde(default)] + pub width: usize, + + /// Height of the widget should be controlled by the widget itself + #[serde(default)] + pub height: usize, + + /// Size of an empty space around the widget + #[serde(default)] + pub margin: (usize, usize, usize, usize), +} + +impl WidgetData { + pub const fn default() -> Self { + Self { + position: (0, 0), + width: 0, + height: 0, + margin: (0, 0, 0, 0), + } + } +} + +/// Common style used by `Widget` +#[derive(Default, Debug, Clone, Copy, Deserialize)] +pub struct Style { + pub background: Option, + pub border: Option<(usize, Color)>, +} + +impl Style { + pub const fn default() -> Self { + Self { + background: None, + border: None, + } + } +} + +/// `Widget` that supports common styling. +pub trait WidgetStyled: Widget { + fn style(&self) -> &Style; + + fn style_mut(&mut self) -> &mut Style; + + fn apply_style(&self) -> Result<()> { + let mut data = self.data().borrow_mut(); + let style = self.style(); + + let border = match style.border { + Some(a) => (a.0, Some(a.1)), + None => (0, None), + }; + + data.height += border.0 * 2; + + Ok(()) + } +} + +/// All available widgets in capybar and their settings +#[derive(Deserialize, Debug, Clone)] +#[serde(tag = "widget", content = "settings", rename_all = "snake_case")] +pub enum WidgetsList { + Text(TextSettings), + Clock(ClockSettings), + Battery(BatterySettings), + #[serde(rename = "cpu")] + CPU(CPUSettings), +} diff --git a/src/widgets/text.rs b/src/widgets/text.rs index 14a1286..96a7602 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -3,30 +3,46 @@ use std::{cell::RefCell, rc::Rc}; use anyhow::Result; use fontdue::layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle}; +use serde::Deserialize; + use thiserror::Error; use crate::{ root::Environment, - util::{Color, Drawer}, + util::{fonts, Color}, widgets::Widget, }; -use super::{Style, WidgetData, WidgetNew}; +use super::{Style, WidgetData, WidgetError, WidgetNew}; -#[derive(Debug, Default, Clone)] +/// Settings of a [Text] widget +#[derive(Deserialize, Debug, Clone, Default)] pub struct TextSettings { + #[serde(default, flatten)] pub default_data: WidgetData, + + /// Default text displayed by the widget + #[serde(default)] pub text: String, + #[serde(default)] pub font_color: Color, + + /// Default font size + #[serde(default)] pub size: f32, + + /// Id of font in vector of fonts for current [crate::util::fonts::FontsMap] + #[serde(default)] pub fontid: usize, + #[serde(default)] pub style: Style, } #[derive(Debug, Error)] pub enum TextError {} +/// Basic widget used for drawing text to a screen pub struct Text { layout: Layout, @@ -36,6 +52,7 @@ pub struct Text { } impl Text { + /// Text is not cached as a string and gets consturcted every time. Often usage of the function might be pricy. pub fn get_text(&self) -> String { let mut text = String::new(); @@ -48,9 +65,9 @@ impl Text { pub fn change_text(&mut self, text: &str) { self.layout.clear(); - if let Some(ref mut env) = self.env { + if let Some(ref mut _env) = self.env { self.layout.append( - &env.fonts.fonts(), + &fonts::fonts_vec(), &TextStyle::new(text, self.settings.size, self.settings.fontid), ); } @@ -77,9 +94,9 @@ impl Widget for Text { fn bind(&mut self, env: Rc) -> Result<()> { self.env = Some(env); - let env = self.env.as_mut().unwrap(); + let _env = self.env.as_mut().unwrap(); self.layout.append( - &env.fonts.fonts(), + &fonts::fonts_vec(), &TextStyle::new( &self.settings.text, self.settings.size, @@ -97,11 +114,14 @@ impl Widget for Text { Ok(()) } - fn draw(&self, drawer: &mut Drawer) -> Result<()> { - let env = self.env.as_ref().unwrap(); - let fonts = env.fonts.fonts(); - let font = &fonts[self.settings.fontid]; + fn draw(&self) -> Result<()> { + if self.env.is_none() { + return Err(WidgetError::DrawWithNoEnv("Text".to_string()).into()); + } + + let font = &fonts::fonts_vec()[self.settings.fontid]; let data = &self.data.borrow_mut(); + let mut drawer = self.env.as_ref().unwrap().drawer.borrow_mut(); if let Some(color) = self.settings.style.background { for x in 0..data.width { diff --git a/src/widgets/widget.rs b/src/widgets/widget.rs deleted file mode 100644 index b7cfbe9..0000000 --- a/src/widgets/widget.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::{cell::RefCell, rc::Rc}; - -use anyhow::Result; -use thiserror::Error; - -use crate::{ - root::Environment, - util::{Color, Drawer}, -}; - -pub trait Widget { - fn bind(&mut self, env: Rc) -> Result<()>; - fn draw(&self, drawer: &mut Drawer) -> Result<()>; - fn init(&self) -> Result<()>; - fn data(&self) -> &RefCell; -} - -pub trait WidgetNew: Widget { - type Settings; - - fn new(env: Option>, settings: Self::Settings) -> Result - where - Self: Sized; -} - -#[derive(Debug, Error)] -pub enum WidgetError { - #[error("Invalid widget bounds")] - InvalidBounds, -} - -#[derive(Debug, Default, Clone, Copy)] -pub struct WidgetData { - pub position: (usize, usize), - pub width: usize, - pub height: usize, - pub margin: (usize, usize, usize, usize), -} - -#[derive(Debug, Default, Clone, Copy)] -pub struct Style { - pub background: Option, - pub border: Option<(usize, Color)>, -} - -pub trait WidgetStyled: Widget { - fn style(&self) -> &Style; - - fn style_mut(&mut self) -> &mut Style; - - fn apply_style(&self) -> Result<()> { - let mut data = self.data().borrow_mut(); - let style = self.style(); - - let border = match style.border { - Some(a) => (a.0, Some(a.1)), - None => (0, None), - }; - - data.height += border.0 * 2; - - Ok(()) - } -}