From 0c66bb48d7c44fd5a43146ba138f2ab2e422b8e3 Mon Sep 17 00:00:00 2001 From: pepengu Date: Sat, 14 Jun 2025 02:14:33 +0300 Subject: [PATCH 1/4] feat: font system rewritten --- examples/basic/main.rs | 6 ++-- src/lib.rs | 1 + src/root.rs | 27 +++------------ src/util/color.rs | 4 ++- src/util/font.rs | 57 -------------------------------- src/util/fonts.rs | 75 ++++++++++++++++++++++++++++++++++++++++++ src/util/mod.rs | 2 +- src/widgets/battery.rs | 2 +- src/widgets/text.rs | 10 +++--- 9 files changed, 92 insertions(+), 92 deletions(-) delete mode 100644 src/util/font.rs create mode 100644 src/util/fonts.rs diff --git a/examples/basic/main.rs b/examples/basic/main.rs index 62378ca..93fcc58 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( @@ -79,7 +80,6 @@ fn main() -> Result<(), Box> { }, )?; - //Center widgets bar.create_child_center( Clock::new, @@ -91,7 +91,6 @@ fn main() -> Result<(), Box> { }, )?; - // Right widgets bar.create_child_right( Battery::new, @@ -116,7 +115,6 @@ fn main() -> Result<(), Box> { }, )?; - capybar.add_widget(bar)?; capybar.init(&mut event_queue)?.run(&mut event_queue)?; diff --git a/src/lib.rs b/src/lib.rs index 42fa059..5575c97 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod util; pub mod widgets; +pub mod config; pub mod root; pub use root::Root; diff --git a/src/root.rs b/src/root.rs index b28980f..78cee1d 100644 --- a/src/root.rs +++ b/src/root.rs @@ -6,7 +6,6 @@ use std::{ }; use anyhow::Result; -use fontdue::Font; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_keyboard, delegate_layer, delegate_output, delegate_pointer, @@ -34,18 +33,16 @@ use wayland_client::{ Connection, EventQueue, QueueHandle, }; +pub struct Environment {} + use crate::{ util::{ - font::{Fonts, FontsError}, + fonts::{self, FontsError}, Drawer, }, widgets::{Widget, WidgetNew}, }; -pub struct Environment { - pub fonts: Fonts, -} - pub struct Root { flag: bool, @@ -66,8 +63,6 @@ pub struct Root { drawer: Option, widgets: Vec>, - fonts: Fonts, - env: Rc, } @@ -370,12 +365,8 @@ impl Root { pointer: None, widgets: Vec::new(), - fonts: Fonts::new().unwrap(), drawer: None, - - env: Rc::new(Environment { - fonts: Fonts::new().unwrap(), - }), + env: Rc::new(Environment {}), }; Ok(bar) @@ -440,15 +431,7 @@ 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<()> diff --git a/src/util/color.rs b/src/util/color.rs index e0b667b..13b6adb 100644 --- a/src/util/color.rs +++ b/src/util/color.rs @@ -1,6 +1,8 @@ use std::fmt::Display; -#[derive(Clone, Copy, Debug, Default)] +use serde::Deserialize; + +#[derive(Clone, Copy, Debug, Default, Deserialize)] pub struct Color(u32); impl Display for Color { 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..c2b5b35 --- /dev/null +++ b/src/util/fonts.rs @@ -0,0 +1,75 @@ +use std::{ + collections::HashMap, + sync::{LazyLock, Mutex, MutexGuard}, +}; + +use anyhow::Result; +use fontconfig::Fontconfig; +use thiserror::Error; + +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 +} + +pub fn fonts_map() -> MutexGuard<'static, HashMap> { + FONTS.fonts_map.lock().unwrap() +} + +pub fn fonts_vec() -> MutexGuard<'static, Vec> { + FONTS.fonts_vec.lock().unwrap() +} + +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()).into()), + }; + + let bytes = match std::fs::read(font.path.as_path()) { + Ok(b) => b, + Err(e) => return Err(FontsError::IO(e).into()), + }; + + 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..b0c993c 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -187,7 +187,7 @@ impl Widget for Battery { ) .as_str(), ); - text.change_text(format!("{percentage}").as_str()); + text.change_text(format!("{percentage}%").as_str()); } } None => { diff --git a/src/widgets/text.rs b/src/widgets/text.rs index 14a1286..726eafc 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -7,7 +7,7 @@ use thiserror::Error; use crate::{ root::Environment, - util::{Color, Drawer}, + util::{fonts, Color, Drawer}, widgets::Widget, }; @@ -50,7 +50,7 @@ impl Text { self.layout.clear(); 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), ); } @@ -79,7 +79,7 @@ impl Widget for Text { let env = self.env.as_mut().unwrap(); self.layout.append( - &env.fonts.fonts(), + &fonts::fonts_vec(), &TextStyle::new( &self.settings.text, self.settings.size, @@ -98,9 +98,7 @@ impl Widget for Text { } 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]; + let font = &fonts::fonts_vec()[self.settings.fontid]; let data = &self.data.borrow_mut(); if let Some(color) = self.settings.style.background { From 3fcee93d979cbe271c3dce3bc38cb650229ba04d Mon Sep 17 00:00:00 2001 From: pepengu Date: Tue, 17 Jun 2025 01:17:39 +0300 Subject: [PATCH 2/4] feat: bar creation using TOML --- Cargo.lock | 91 +++++++++++++++++++++++++++++++ Cargo.toml | 3 + examples/basic/main.rs | 16 +----- examples/toml_config/config.toml | 37 +++++++++++++ examples/toml_config/config1.toml | 18 ++++++ examples/toml_config/main.rs | 16 ++++++ src/config/mod.rs | 32 +++++++++++ src/config/util/font.rs | 84 ++++++++++++++++++++++++++++ src/config/util/mod.rs | 3 + src/config/util/style.rs | 39 +++++++++++++ src/config/widgets/bar.rs | 26 +++++++++ src/config/widgets/mod.rs | 1 + src/lib.rs | 2 +- src/root.rs | 70 +++++++++++++++++++----- src/util/fonts.rs | 4 +- src/widgets/battery.rs | 33 ++++++++--- src/widgets/clock.rs | 12 +++- src/widgets/containers/bar.rs | 10 +++- src/widgets/cpu.rs | 17 ++++-- src/widgets/text.rs | 14 ++++- src/widgets/widget.rs | 46 +++++++++++++++- 21 files changed, 522 insertions(+), 52 deletions(-) create mode 100644 examples/toml_config/config.toml create mode 100644 examples/toml_config/config1.toml create mode 100644 examples/toml_config/main.rs create mode 100644 src/config/mod.rs create mode 100644 src/config/util/font.rs create mode 100644 src/config/util/mod.rs create mode 100644 src/config/util/style.rs create mode 100644 src/config/widgets/bar.rs create mode 100644 src/config/widgets/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 2d20f32..cae00d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,9 +125,11 @@ dependencies = [ "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..627cdb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,6 @@ chrono = "0.4.41" battery = "0.7.8" #CPU sysinfo = "0.35.1" +toml = "0.8.23" + +serde = {version = "1.0.219", features = [ "derive" ] } diff --git a/examples/basic/main.rs b/examples/basic/main.rs index 93fcc58..f7522f6 100644 --- a/examples/basic/main.rs +++ b/examples/basic/main.rs @@ -60,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_style: TextSettings { font_color: catpuccin_mocha.font, size: 25.0, @@ -95,13 +89,7 @@ fn main() -> Result<(), Box> { bar.create_child_right( Battery::new, BatterySettings { - text: TextSettings { - font_color: catpuccin_mocha.font, - size: 25.0, - - ..TextSettings::default() - }, - icon: TextSettings { + text_style: TextSettings { font_color: catpuccin_mocha.font, size: 25.0, diff --git a/examples/toml_config/config.toml b/examples/toml_config/config.toml new file mode 100644 index 0000000..3a8cebc --- /dev/null +++ b/examples/toml_config/config.toml @@ -0,0 +1,37 @@ +#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] + +[[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/config1.toml b/examples/toml_config/config1.toml new file mode 100644 index 0000000..7df4fc0 --- /dev/null +++ b/examples/toml_config/config1.toml @@ -0,0 +1,18 @@ +[default-style] +background = 0x1e1e2eff +border = 0x74c7ecff +font = 0xf5e0dcff + +[bar] +background.enable = true +background.color = 0x313244ff +border.enable = true + +[battery] +background.enable = true + +[clock] +background.enable = true + +[CPU] +background.enable = true diff --git a/examples/toml_config/main.rs b/examples/toml_config/main.rs new file mode 100644 index 0000000..65dac27 --- /dev/null +++ b/examples/toml_config/main.rs @@ -0,0 +1,16 @@ +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_from_config(&globals, &mut event_queue, 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 5575c97..b193ec6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ +pub mod config; pub mod util; pub mod widgets; -pub mod config; pub mod root; pub use root::Root; diff --git a/src/root.rs b/src/root.rs index 78cee1d..bce07e6 100644 --- a/src/root.rs +++ b/src/root.rs @@ -1,6 +1,5 @@ use std::{ cmp::{max, min}, - error::Error, num::NonZeroU32, rc::Rc, }; @@ -33,14 +32,20 @@ use wayland_client::{ Connection, EventQueue, QueueHandle, }; -pub struct Environment {} +pub struct Environment { + pub config: Config, +} use crate::{ + config::Config, util::{ fonts::{self, FontsError}, Drawer, }, - widgets::{Widget, WidgetNew}, + widgets::{ + battery::Battery, clock::Clock, containers::bar::Bar, cpu::CPU, text::Text, Widget, + WidgetNew, WidgetsList, + }, }; pub struct Root { @@ -331,10 +336,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 = @@ -366,16 +368,57 @@ impl Root { widgets: Vec::new(), drawer: None, - env: Rc::new(Environment {}), + env: Rc::new(Environment { + config: Config::default(), + }), }; Ok(bar) } - pub fn init( - &mut self, + pub fn new_from_config( + globals: &GlobalList, event_queue: &mut EventQueue, - ) -> Result<&mut Self, Box> { + config: Config, + ) -> Result { + let mut root = Root::new(globals, event_queue)?; + + 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)?, + } + } + + root.add_widget(bar)?; + Ok(root) + } + + 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); @@ -397,7 +440,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); @@ -416,7 +460,7 @@ impl Root { 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 { diff --git a/src/util/fonts.rs b/src/util/fonts.rs index c2b5b35..720ff8d 100644 --- a/src/util/fonts.rs +++ b/src/util/fonts.rs @@ -50,12 +50,12 @@ pub fn fonts_vec() -> MutexGuard<'static, Vec> { 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()).into()), + 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).into()), + Err(e) => return Err(FontsError::IO(e)), }; let font = fontdue::Font::from_bytes( diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index b0c993c..f7b9387 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -2,6 +2,7 @@ use std::{cell::RefCell, ops::Add}; use anyhow::Result; use battery::{Manager, State}; +use serde::Deserialize; use super::{ text::{Text, TextSettings}, @@ -9,26 +10,38 @@ use super::{ Style, WidgetData, WidgetStyled, }; +const fn battery_not_charging_default() -> [char; 11] { + ['󰂎', '󰁺', '󰁻', '󰁼', '󰁽', '󰁾', '󰁿', '󰂀', '󰂁', '󰂂', '󰁹'] +} + +const fn battery_charging_default() -> [char; 11] { + ['󰢟', '󰢜', '󰂆', '󰂇', '󰂈', '󰢝', '󰂉', '󰢞', '󰂊', '󰂋', '󰂅'] +} + +#[derive(Debug, Deserialize, Clone)] pub struct BatterySettings { + #[serde(default = "battery_not_charging_default")] pub battery_not_charging: [char; 11], + #[serde(default = "battery_charging_default")] pub battery_charging: [char; 11], - pub text: TextSettings, - pub icon: TextSettings, + #[serde(default, flatten)] + pub text_style: 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_style: TextSettings::default(), default_data: WidgetData::default(), @@ -223,8 +236,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_style.clone() }, )?), percent: RefCell::new(Text::new( @@ -236,7 +253,7 @@ impl WidgetNew for Battery { margin: (5, 0, 2, 0), ..WidgetData::default() }, - ..settings.text.clone() + ..settings.text_style.clone() }, )?), diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 47e2c99..8ad534d 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -2,6 +2,7 @@ use std::{cell::RefCell, rc::Rc}; use anyhow::Result; use chrono::Local; +use serde::Deserialize; use crate::{ root::Environment, @@ -11,12 +12,21 @@ use crate::{ use super::{text::TextSettings, WidgetData, WidgetNew}; +fn default_format() -> String { + "%H:%M".to_string() +} + +#[derive(Deserialize, Debug, Clone)] pub struct ClockSettings { + #[serde(default)] pub size: f32, + #[serde(default = "default_format")] pub format: String, + #[serde(default)] pub font_color: Color, + #[serde(default)] pub default_data: WidgetData, } @@ -24,7 +34,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, diff --git a/src/widgets/containers/bar.rs b/src/widgets/containers/bar.rs index 9a2efb6..5a292d2 100644 --- a/src/widgets/containers/bar.rs +++ b/src/widgets/containers/bar.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, rc::Rc}; use anyhow::Result; +use serde::Deserialize; use crate::{ root::Environment, @@ -12,17 +13,20 @@ use super::{ row::{Alignment, Row, RowSettings}, }; -#[derive(Debug, Clone)] +#[derive(Default, Debug, Clone, Deserialize)] pub struct BarSettings { + #[serde(flatten, default)] pub default_data: WidgetData, + #[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), diff --git a/src/widgets/cpu.rs b/src/widgets/cpu.rs index ab6d319..9216dbf 100644 --- a/src/widgets/cpu.rs +++ b/src/widgets/cpu.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use anyhow::Result; +use serde::Deserialize; use sysinfo::{CpuRefreshKind, RefreshKind, System}; use super::{ @@ -8,13 +9,15 @@ use super::{ Style, Widget, WidgetData, WidgetNew, }; -#[derive(Debug, Default, Clone)] +#[derive(Deserialize, Debug, Default, Clone)] pub struct CPUSettings { + #[serde(default, flatten)] pub default_data: WidgetData, - pub text: TextSettings, - pub icon: TextSettings, + #[serde(default, flatten)] + pub text_style: TextSettings, + #[serde(default, flatten)] pub style: Style, } @@ -116,8 +119,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_style.clone() }, )?), percent: RefCell::new(Text::new( @@ -129,7 +136,7 @@ impl WidgetNew for CPU { margin: (5, 0, 2, 0), ..WidgetData::default() }, - ..settings.text.clone() + ..settings.text_style.clone() }, )?), diff --git a/src/widgets/text.rs b/src/widgets/text.rs index 726eafc..557c4bc 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -3,6 +3,8 @@ use std::{cell::RefCell, rc::Rc}; use anyhow::Result; use fontdue::layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle}; +use serde::Deserialize; + use thiserror::Error; use crate::{ @@ -13,14 +15,20 @@ use crate::{ use super::{Style, WidgetData, WidgetNew}; -#[derive(Debug, Default, Clone)] +#[derive(Deserialize, Debug, Clone, Default)] pub struct TextSettings { + #[serde(default, flatten)] pub default_data: WidgetData, + #[serde(default)] pub text: String, + #[serde(default)] pub font_color: Color, + #[serde(default)] pub size: f32, + #[serde(default)] pub fontid: usize, + #[serde(default)] pub style: Style, } @@ -48,7 +56,7 @@ 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( &fonts::fonts_vec(), &TextStyle::new(text, self.settings.size, self.settings.fontid), @@ -77,7 +85,7 @@ 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( &fonts::fonts_vec(), &TextStyle::new( diff --git a/src/widgets/widget.rs b/src/widgets/widget.rs index b7cfbe9..cc31923 100644 --- a/src/widgets/widget.rs +++ b/src/widgets/widget.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, rc::Rc}; use anyhow::Result; +use serde::Deserialize; use thiserror::Error; use crate::{ @@ -8,6 +9,13 @@ use crate::{ util::{Color, Drawer}, }; +use super::{ + battery::BatterySettings, + clock::ClockSettings, + cpu::CPUSettings, + text::TextSettings, +}; + pub trait Widget { fn bind(&mut self, env: Rc) -> Result<()>; fn draw(&self, drawer: &mut Drawer) -> Result<()>; @@ -29,20 +37,44 @@ pub enum WidgetError { InvalidBounds, } -#[derive(Debug, Default, Clone, Copy)] +#[derive(Default, Debug, Clone, Copy, Deserialize)] pub struct WidgetData { + #[serde(default)] pub position: (usize, usize), + #[serde(default)] pub width: usize, + #[serde(default)] pub height: usize, + #[serde(default)] pub margin: (usize, usize, usize, usize), } -#[derive(Debug, Default, Clone, Copy)] +impl WidgetData { + pub const fn default() -> Self { + Self { + position: (0, 0), + width: 0, + height: 0, + margin: (0, 0, 0, 0), + } + } +} + +#[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, + } + } +} + pub trait WidgetStyled: Widget { fn style(&self) -> &Style; @@ -62,3 +94,13 @@ pub trait WidgetStyled: Widget { Ok(()) } } + +#[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), +} From d51f93aa02107f7830f9470777539de107c3ca73 Mon Sep 17 00:00:00 2001 From: pepengu Date: Fri, 20 Jun 2025 02:30:08 +0300 Subject: [PATCH 3/4] docs: basic comments added --- src/util/color.rs | 1 + src/util/drawer.rs | 6 ++ src/util/fonts.rs | 8 ++ src/widgets/battery.rs | 20 +++-- src/widgets/clock.rs | 6 ++ src/widgets/containers/bar.rs | 10 ++- src/widgets/containers/container.rs | 62 -------------- src/widgets/containers/mod.rs | 68 ++++++++++++++- src/widgets/containers/row.rs | 6 +- src/widgets/cpu.rs | 9 +- src/widgets/mod.rs | 128 +++++++++++++++++++++++++++- src/widgets/text.rs | 9 ++ src/widgets/widget.rs | 106 ----------------------- 13 files changed, 253 insertions(+), 186 deletions(-) delete mode 100644 src/widgets/containers/container.rs diff --git a/src/util/color.rs b/src/util/color.rs index 13b6adb..59b7e31 100644 --- a/src/util/color.rs +++ b/src/util/color.rs @@ -2,6 +2,7 @@ use std::fmt::Display; 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); diff --git a/src/util/drawer.rs b/src/util/drawer.rs index 33812fb..346c629 100644 --- a/src/util/drawer.rs +++ b/src/util/drawer.rs @@ -26,6 +26,7 @@ impl fmt::Display for DrawerError { } } +/// Utility structure used to simplify drawing the widgets. #[derive(Debug)] pub struct Drawer { pool: SlotPool, @@ -50,6 +51,7 @@ impl Drawer { } } + /// 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 +59,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 +101,8 @@ impl Drawer { } } + /// Draw a glyph form dont. 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/fonts.rs b/src/util/fonts.rs index 720ff8d..8114cb2 100644 --- a/src/util/fonts.rs +++ b/src/util/fonts.rs @@ -7,9 +7,13 @@ 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>, } @@ -39,14 +43,18 @@ 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, diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index f7b9387..34acfd6 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -6,8 +6,7 @@ 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] { @@ -18,15 +17,22 @@ 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], + /// Settings for underlying [Text] widget #[serde(default, flatten)] - pub text_style: TextSettings, + pub text_settings: TextSettings, #[serde(default, flatten)] pub default_data: WidgetData, @@ -41,7 +47,7 @@ impl Default for BatterySettings { battery_not_charging: battery_not_charging_default(), battery_charging: battery_charging_default(), - text_style: TextSettings::default(), + text_settings: TextSettings::default(), default_data: WidgetData::default(), @@ -88,6 +94,7 @@ impl BatteryInfo { } } +/// Widget displaying current battery status. pub struct Battery { manager: Manager, @@ -100,6 +107,7 @@ pub struct Battery { } impl Battery { + /// Get information of current battery status pub fn get_info(&self) -> Option { Some( self.manager @@ -241,7 +249,7 @@ impl WidgetNew for Battery { ..WidgetData::default() }, fontid: 1, - ..settings.text_style.clone() + ..settings.text_settings.clone() }, )?), percent: RefCell::new(Text::new( @@ -253,7 +261,7 @@ impl WidgetNew for Battery { margin: (5, 0, 2, 0), ..WidgetData::default() }, - ..settings.text_style.clone() + ..settings.text_settings.clone() }, )?), diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 8ad534d..e42e057 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -16,10 +16,14 @@ 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, @@ -43,6 +47,7 @@ impl Default for ClockSettings { } } +/// Widget displaying current time. Supports C's strftime formating. pub struct Clock { text: RefCell, settings: ClockSettings, @@ -51,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()); diff --git a/src/widgets/containers/bar.rs b/src/widgets/containers/bar.rs index 5a292d2..0d20390 100644 --- a/src/widgets/containers/bar.rs +++ b/src/widgets/containers/bar.rs @@ -9,15 +9,18 @@ use crate::{ }; use super::{ - container::Container, row::{Alignment, Row, RowSettings}, + Container, }; +/// 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), @@ -35,6 +38,7 @@ impl BarSettings { } } +/// Main widget in capybar. Stores 3 alligned [Row] containers. pub struct Bar { settings: BarSettings, data: RefCell, @@ -242,11 +246,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..73413a2 100644 --- a/src/widgets/containers/row.rs +++ b/src/widgets/containers/row.rs @@ -9,7 +9,7 @@ use crate::{ widgets::{Widget, WidgetData, 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, diff --git a/src/widgets/cpu.rs b/src/widgets/cpu.rs index 9216dbf..9d2ccb2 100644 --- a/src/widgets/cpu.rs +++ b/src/widgets/cpu.rs @@ -9,18 +9,21 @@ use super::{ Style, Widget, WidgetData, WidgetNew, }; +/// Settings of a [CPU] widget #[derive(Deserialize, Debug, Default, Clone)] pub struct CPUSettings { #[serde(default, flatten)] pub default_data: WidgetData, + /// Settings for underlying [Text] widget #[serde(default, flatten)] - pub text_style: TextSettings, + pub text_settings: TextSettings, #[serde(default, flatten)] pub style: Style, } +/// Widget displaying current CPU status. pub struct CPU { data: RefCell, @@ -124,7 +127,7 @@ impl WidgetNew for CPU { ..WidgetData::default() }, fontid: 1, - ..settings.text_style.clone() + ..settings.text_settings.clone() }, )?), percent: RefCell::new(Text::new( @@ -136,7 +139,7 @@ impl WidgetNew for CPU { margin: (5, 0, 2, 0), ..WidgetData::default() }, - ..settings.text_style.clone() + ..settings.text_settings.clone() }, )?), diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 4cf8a8a..510bc33 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, Drawer}, +}; + +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, drawer: &mut Drawer) -> 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, +} + +/// 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 557c4bc..b7e48fe 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -15,16 +15,23 @@ use crate::{ use super::{Style, WidgetData, WidgetNew}; +/// 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, @@ -35,6 +42,7 @@ pub struct TextSettings { #[derive(Debug, Error)] pub enum TextError {} +/// Basic widget used for drawing text to a screen pub struct Text { layout: Layout, @@ -44,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(); diff --git a/src/widgets/widget.rs b/src/widgets/widget.rs index cc31923..e69de29 100644 --- a/src/widgets/widget.rs +++ b/src/widgets/widget.rs @@ -1,106 +0,0 @@ -use std::{cell::RefCell, rc::Rc}; - -use anyhow::Result; -use serde::Deserialize; -use thiserror::Error; - -use crate::{ - root::Environment, - util::{Color, Drawer}, -}; - -use super::{ - battery::BatterySettings, - clock::ClockSettings, - cpu::CPUSettings, - text::TextSettings, -}; - -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(Default, Debug, Clone, Copy, Deserialize)] -pub struct WidgetData { - #[serde(default)] - pub position: (usize, usize), - #[serde(default)] - pub width: usize, - #[serde(default)] - pub height: usize, - #[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), - } - } -} - -#[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, - } - } -} - -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(()) - } -} - -#[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), -} From 00f3eb4c3f05f1b08c066ae9f47ba1dc4f837e2e Mon Sep 17 00:00:00 2001 From: pepengu Date: Sat, 5 Jul 2025 11:51:57 +0300 Subject: [PATCH 4/4] style: refactor --- Cargo.lock | 2 +- Cargo.toml | 9 ++-- examples/basic/main.rs | 4 +- examples/toml_config/bar.png | Bin 0 -> 6403 bytes examples/toml_config/config.toml | 1 + examples/toml_config/config1.toml | 18 ------- examples/toml_config/main.rs | 3 +- src/root.rs | 77 +++++++++++++++++----------- src/util/drawer.rs | 24 +++++---- src/widgets/battery.rs | 6 +-- src/widgets/clock.rs | 6 +-- src/widgets/containers/bar.rs | 82 +++++++++++++++--------------- src/widgets/containers/row.rs | 45 +++++++++------- src/widgets/cpu.rs | 43 +++++++++++----- src/widgets/mod.rs | 10 ++-- src/widgets/text.rs | 11 ++-- src/widgets/widget.rs | 0 17 files changed, 191 insertions(+), 150 deletions(-) create mode 100644 examples/toml_config/bar.png delete mode 100644 examples/toml_config/config1.toml delete mode 100644 src/widgets/widget.rs diff --git a/Cargo.lock b/Cargo.lock index cae00d0..b9b1153 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -118,7 +118,7 @@ dependencies = [ [[package]] name = "capybar" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "battery", diff --git a/Cargo.toml b/Cargo.toml index 627cdb7..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" @@ -24,6 +28,3 @@ chrono = "0.4.41" battery = "0.7.8" #CPU sysinfo = "0.35.1" -toml = "0.8.23" - -serde = {version = "1.0.219", features = [ "derive" ] } diff --git a/examples/basic/main.rs b/examples/basic/main.rs index f7522f6..0a31898 100644 --- a/examples/basic/main.rs +++ b/examples/basic/main.rs @@ -60,7 +60,7 @@ fn main() -> Result<(), Box> { bar.create_child_left( CPU::new, CPUSettings { - text_style: TextSettings { + text_settings: TextSettings { font_color: catpuccin_mocha.font, size: 25.0, @@ -89,7 +89,7 @@ fn main() -> Result<(), Box> { bar.create_child_right( Battery::new, BatterySettings { - text_style: TextSettings { + text_settings: TextSettings { font_color: catpuccin_mocha.font, size: 25.0, diff --git a/examples/toml_config/bar.png b/examples/toml_config/bar.png new file mode 100644 index 0000000000000000000000000000000000000000..89872a92237bd3787e7525444c4a29cb9697ba79 GIT binary patch literal 6403 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 literal 0 HcmV?d00001 diff --git a/examples/toml_config/config.toml b/examples/toml_config/config.toml index 3a8cebc..5fa651d 100644 --- a/examples/toml_config/config.toml +++ b/examples/toml_config/config.toml @@ -18,6 +18,7 @@ border = [1, 0x74c7ecff] size = 24 font_color = 0xf5e0dcff margin = [10,0,0,0] + update_rate = 1000 [[bar.center]] widget = "clock" diff --git a/examples/toml_config/config1.toml b/examples/toml_config/config1.toml deleted file mode 100644 index 7df4fc0..0000000 --- a/examples/toml_config/config1.toml +++ /dev/null @@ -1,18 +0,0 @@ -[default-style] -background = 0x1e1e2eff -border = 0x74c7ecff -font = 0xf5e0dcff - -[bar] -background.enable = true -background.color = 0x313244ff -border.enable = true - -[battery] -background.enable = true - -[clock] -background.enable = true - -[CPU] -background.enable = true diff --git a/examples/toml_config/main.rs b/examples/toml_config/main.rs index 65dac27..5fe862d 100644 --- a/examples/toml_config/main.rs +++ b/examples/toml_config/main.rs @@ -8,7 +8,8 @@ fn main() -> Result<()> { let conn = Connection::connect_to_env()?; let (globals, mut event_queue) = registry_queue_init(&conn)?; - let mut capybar = Root::new_from_config(&globals, &mut event_queue, config)?; + let mut capybar = Root::new(&globals, &mut event_queue)?; + capybar.apply_config(config)?; capybar.init(&mut event_queue)?.run(&mut event_queue)?; diff --git a/src/root.rs b/src/root.rs index bce07e6..e8e1145 100644 --- a/src/root.rs +++ b/src/root.rs @@ -1,4 +1,5 @@ use std::{ + cell::RefCell, cmp::{max, min}, num::NonZeroU32, rc::Rc, @@ -24,16 +25,19 @@ 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::{ @@ -48,6 +52,12 @@ use crate::{ }, }; +#[derive(Error, Debug)] +pub enum RootError { + #[error("Environment is not initialised before drawing")] + EnvironmentNotInit, +} + pub struct Root { flag: bool, @@ -66,9 +76,8 @@ pub struct Root { keyboard_focus: bool, pointer: Option, - drawer: Option, widgets: Vec>, - env: Rc, + env: Option>, } impl CompositorHandler for Root { @@ -367,22 +376,13 @@ impl Root { pointer: None, widgets: Vec::new(), - drawer: None, - env: Rc::new(Environment { - config: Config::default(), - }), + env: None, }; Ok(bar) } - pub fn new_from_config( - globals: &GlobalList, - event_queue: &mut EventQueue, - config: Config, - ) -> Result { - let mut root = Root::new(globals, event_queue)?; - + pub fn apply_config(&mut self, config: Config) -> Result<()> { let mut bar = Bar::new(None, config.bar.settings)?; for widget in config.bar.left { @@ -414,17 +414,26 @@ impl Root { } } - root.add_widget(bar)?; - Ok(root) + 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(); @@ -453,9 +462,11 @@ 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) } @@ -482,7 +493,9 @@ impl Root { 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(()) } @@ -492,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 @@ -513,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/drawer.rs b/src/util/drawer.rs index 346c629..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; @@ -37,20 +40,23 @@ 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 { @@ -101,7 +107,7 @@ impl Drawer { } } - /// Draw a glyph form dont. Drawer converts local position in a widget to global buf position + /// 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, diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index 34acfd6..692e35b 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -186,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(); @@ -222,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() } } diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index e42e057..a90215c 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -6,7 +6,7 @@ use serde::Deserialize; use crate::{ root::Environment, - util::{Color, Drawer}, + util::Color, widgets::{text::Text, Widget}, }; @@ -85,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 0d20390..55dad65 100644 --- a/src/widgets/containers/bar.rs +++ b/src/widgets/containers/bar.rs @@ -5,7 +5,7 @@ use serde::Deserialize; use crate::{ root::Environment, - widgets::{Style, Widget, WidgetData, WidgetNew}, + widgets::{Style, Widget, WidgetData, WidgetError, WidgetNew}, }; use super::{ @@ -94,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 { @@ -105,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(()) } diff --git a/src/widgets/containers/row.rs b/src/widgets/containers/row.rs index 73413a2..a282a22 100644 --- a/src/widgets/containers/row.rs +++ b/src/widgets/containers/row.rs @@ -5,8 +5,8 @@ use thiserror::Error; use crate::{ root::Environment, - util::{Color, Drawer}, - widgets::{Widget, WidgetData, WidgetNew}, + util::Color, + widgets::{Widget, WidgetData, WidgetError, WidgetNew}, }; use super::{Container, WidgetVec}; @@ -92,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(); @@ -107,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 9d2ccb2..94dc75b 100644 --- a/src/widgets/cpu.rs +++ b/src/widgets/cpu.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use anyhow::Result; +use chrono::{DateTime, Local, TimeDelta}; use serde::Deserialize; use sysinfo::{CpuRefreshKind, RefreshKind, System}; @@ -21,6 +22,10 @@ pub struct CPUSettings { #[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. @@ -31,6 +36,9 @@ pub struct CPU { percent: RefCell, sys: RefCell, + + last_update: RefCell>, + upadte_rate: TimeDelta, } impl CPU { @@ -83,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(); + + 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()); + { + 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 { @@ -146,6 +160,11 @@ impl WidgetNew for CPU { 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 510bc33..ad23519 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -11,10 +11,7 @@ use anyhow::Result; use serde::Deserialize; use thiserror::Error; -use crate::{ - root::Environment, - util::{Color, Drawer}, -}; +use crate::{root::Environment, util::Color}; use {battery::BatterySettings, clock::ClockSettings, cpu::CPUSettings, text::TextSettings}; @@ -24,7 +21,7 @@ pub trait Widget { fn bind(&mut self, env: Rc) -> Result<()>; /// Draw an entire widget to a `Drawer` - fn draw(&self, drawer: &mut Drawer) -> Result<()>; + fn draw(&self) -> Result<()>; /// Prepare `Widget` for a first draw fn init(&self) -> Result<()>; @@ -49,6 +46,9 @@ pub trait WidgetNew: Widget { 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. diff --git a/src/widgets/text.rs b/src/widgets/text.rs index b7e48fe..96a7602 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -9,11 +9,11 @@ use thiserror::Error; use crate::{ root::Environment, - util::{fonts, Color, Drawer}, + util::{fonts, Color}, widgets::Widget, }; -use super::{Style, WidgetData, WidgetNew}; +use super::{Style, WidgetData, WidgetError, WidgetNew}; /// Settings of a [Text] widget #[derive(Deserialize, Debug, Clone, Default)] @@ -114,9 +114,14 @@ impl Widget for Text { Ok(()) } - fn draw(&self, drawer: &mut Drawer) -> Result<()> { + 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 e69de29..0000000