diff --git a/Cargo.lock b/Cargo.lock index 54d0609..328aa62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,7 +239,7 @@ dependencies = [ [[package]] name = "capybar" -version = "0.2.0" +version = "0.3.0" dependencies = [ "anyhow", "battery", diff --git a/Cargo.toml b/Cargo.toml index 4ce023d..e473837 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "capybar" description = "Wayland native toolbar" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT" repository = "https://github.com/CapyCore/capybar" diff --git a/README.md b/README.md index 6c67fd9..7e20fea 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Simple customizable bar applications that aims to have as little external depend - Custom widgets creation via rust - Pre-built widgets: - Text + - IconText - Clock - Battery - CPU usage @@ -43,7 +44,14 @@ programs.capybar = { ``` ### Others -Currently bar needs to be build manually. To do so clone the repo and write main file. Bulding the bar is done with cargo. The example is located in examples folder. +Capybar can be installed via cargo using +```cmd +cargo install capybar +``` +This will install and setup `capybar` executable. + +### Building from source +Bulding the bar is done with cargo. The example is located in examples folder. ``` cargo build --release ``` diff --git a/examples/basic/main.rs b/examples/basic/main.rs index 690c3c3..fb61642 100644 --- a/examples/basic/main.rs +++ b/examples/basic/main.rs @@ -7,7 +7,7 @@ use capybar::{ containers::bar::{Bar, BarSettings}, cpu::{CPUSettings, CPU}, text::TextSettings, - Style, WidgetData, WidgetNew, + Margin, Style, WidgetData, WidgetNew, }, }; use wayland_client::{globals::registry_queue_init, Connection}; @@ -28,14 +28,6 @@ fn main() -> Result<(), Box> { let conn = Connection::connect_to_env()?; let (globals, mut event_queue) = registry_queue_init(&conn)?; - let mut capybar = Root::new(&globals, &mut event_queue)?; - - // 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("jetbrainsmononerdfont")?; - capybar.add_font_by_name("jetbrainsmononerdfont")?; - let mut bar = Bar::new( None, BarSettings { @@ -57,7 +49,7 @@ fn main() -> Result<(), Box> { )?; // Left widgets - bar.create_child_left( + bar.create_widget_left( CPU::new, CPUSettings { update_rate: 1000, @@ -68,15 +60,23 @@ fn main() -> Result<(), Box> { ..TextSettings::default() }, default_data: WidgetData { - margin: (10, 0, 0, 0), ..WidgetData::default() }, + style: Style { + margin: Margin { + left: 10, + right: 0, + up: 0, + down: 0, + }, + ..Default::default() + }, ..CPUSettings::default() }, )?; //Center widgets - bar.create_child_center( + bar.create_widget_center( Clock::new, ClockSettings { font_color: catpuccin_mocha.font, @@ -87,7 +87,7 @@ fn main() -> Result<(), Box> { )?; // Right widgets - bar.create_child_right( + bar.create_widget_right( Battery::new, BatterySettings { text_settings: TextSettings { @@ -97,16 +97,30 @@ fn main() -> Result<(), Box> { ..TextSettings::default() }, default_data: WidgetData { - margin: (0, 10, 0, 0), ..WidgetData::default() }, + style: Style { + margin: Margin { + left: 0, + right: 10, + up: 0, + down: 0, + }, + ..Default::default() + }, ..BatterySettings::default() }, )?; - capybar.add_widget(bar)?; + let mut capybar = Root::new(&globals, &mut event_queue, Some(bar))?; + + // 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("jetbrainsmononerdfont")?; + capybar.add_font_by_name("jetbrainsmononerdfont")?; - capybar.init(&mut event_queue)?.run(&mut event_queue)?; + capybar.run(&mut event_queue)?; Ok(()) } diff --git a/examples/toml_config/bar.png b/examples/toml_config/bar.png index 89872a9..7fbb0fa 100644 Binary files a/examples/toml_config/bar.png and b/examples/toml_config/bar.png differ diff --git a/examples/toml_config/config.toml b/examples/toml_config/config.toml index f8d02d6..f625ff8 100644 --- a/examples/toml_config/config.toml +++ b/examples/toml_config/config.toml @@ -1,47 +1,51 @@ #TOML does not support variables interpolation, so this is just a place to copy values from [palete] -font = 0xf5e0dcff -background = 0x1e1e2eff -border = 0x74c7ecff + font = 0xf5e0dcff + background = 0x1e1e2eff + border = 0x74c7ecff [preloaded_fonts] -list = ["mono", "jetbrainsmononerdfont"] + list = ["mono", "jetbrainsmononerdfont"] [bar.settings] -width = 1920 -background = 0x1e1e2eff -border = [1, 0x74c7ecff] + width = 1920 + background = 0x1e1e2eff + border = [1, 0x74c7ecff] + padding = [5,5,5] + [bar.settings.left_settings] + margin = [3,0,3,3] + [bar.settings.right_settings] + margin = [0,6,3,3] [[bar.left]] widget = "keyboard" [[bar.left.settings]] size = 24 font_color = 0xf5e0dcff - margin = [10,0,0,0] layout_mappings = {"Russian" = "RU", "English (US)" = "EN"} + border = [1, 0x74c7ecff] [[bar.left.settings]] - update_rate = 0 + update_rate = 100 [[bar.left]] widget = "cpu" [bar.left.settings] size = 24 font_color = 0xf5e0dcff - margin = [10,0,0,0] + border = [1, 0x74c7ecff] [[bar.center]] widget = "clock" [bar.center.settings] size = 24 font_color = 0xf5e0dcff + margin = [0,3,3,0] + border = [1, 0x74c7ecff] [[bar.right]] widget = "battery" [bar.right.settings] size = 24 font_color = 0xf5e0dcff - margin = [0,10,0,0] - - - - + margin = [0,3,3,0] + border = [1, 0x74c7ecff] diff --git a/examples/toml_config/main.rs b/examples/toml_config/main.rs index 6979e10..b762ae1 100644 --- a/examples/toml_config/main.rs +++ b/examples/toml_config/main.rs @@ -8,10 +8,10 @@ fn main() -> Result<()> { let conn = Connection::connect_to_env()?; let (globals, mut event_queue) = registry_queue_init(&conn)?; - let mut capybar = Root::new(&globals, &mut event_queue)?; + let mut capybar = Root::new(&globals, &mut event_queue, None)?; capybar.apply_config(config)?; - capybar.init(&mut event_queue)?.run(&mut event_queue)?; + capybar.run(&mut event_queue)?; Ok(()) } diff --git a/flake.lock b/flake.lock index 66b4127..32dc207 100644 --- a/flake.lock +++ b/flake.lock @@ -2,16 +2,16 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1751274312, - "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", + "lastModified": 1752436162, + "narHash": "sha256-Kt1UIPi7kZqkSc5HVj6UY5YLHHEzPBkgpNUByuyxtlw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", + "rev": "dfcd5b901dbab46c9c6e80b265648481aafb01f8", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixos-24.11", + "ref": "nixos-25.05", "repo": "nixpkgs", "type": "github" } diff --git a/src/config/widgets/bar.rs b/src/config/widgets/bar.rs index 7028278..95a3076 100644 --- a/src/config/widgets/bar.rs +++ b/src/config/widgets/bar.rs @@ -1,17 +1,17 @@ use serde::Deserialize; -use crate::widgets::{containers::bar::BarSettings, WidgetsList}; +use crate::widgets::{containers::bar::BarSettings, WidgetsSettingsList}; #[derive(Default, Deserialize, Debug)] pub struct Bar { #[serde(default)] pub settings: BarSettings, #[serde(default)] - pub left: Vec, + pub left: Vec, #[serde(default)] - pub center: Vec, + pub center: Vec, #[serde(default)] - pub right: Vec, + pub right: Vec, } impl Bar { diff --git a/src/lib.rs b/src/lib.rs index 5ce7486..14c9b4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ pub mod config; -pub mod processes; pub mod root; +pub mod services; pub mod util; pub mod widgets; diff --git a/src/main.rs b/src/main.rs index 5f999ce..da47f7f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -83,10 +83,10 @@ fn main() -> Result<()> { let conn = Connection::connect_to_env()?; let (globals, mut event_queue) = registry_queue_init(&conn)?; - let mut capybar = Root::new(&globals, &mut event_queue)?; + let mut capybar = Root::new(&globals, &mut event_queue, None)?; capybar.apply_config(config)?; - capybar.init(&mut event_queue)?.run(&mut event_queue)?; + capybar.run(&mut event_queue)?; Ok(()) } diff --git a/src/processes/mod.rs b/src/processes/mod.rs deleted file mode 100644 index 153b018..0000000 --- a/src/processes/mod.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Current module describes all of capybars included processes as well as their common behaviour. -//! -//! Process can be treated as a backend component. -//! To communicate with frontend you can use [Signal](crate::util::signals::Signal) - -pub mod clients; - -use std::rc::Rc; - -use serde::Deserialize; -use thiserror::Error; - -use crate::root::Environment; - -fn default_update_rate() -> i64 { - 1000 -} - -#[derive(Debug, Deserialize, Clone, Copy)] -pub struct ProcessSettings { - #[serde(default = "default_update_rate")] - pub update_rate: i64, -} - -/// A **data structure** that can be used as a widget inside a capybar. -pub trait Process { - /// Bind a widget to a new environment. - fn bind(&mut self, env: Rc) -> Result<(), ProcessError>; - - /// Prepare `Process` for a first run - fn init(&self) -> Result<(), ProcessError>; - - /// Run the process - fn run(&self) -> Result<(), ProcessError>; -} - -/// A `Process` that can be unifiedly created. -/// -/// Implementing this trait allows creating `Process` and binding the environment without -/// intermidiate steps. Simplifies process creation inside of scripts. -pub trait ProcessNew { - type Settings; - - fn new(env: Option>, settings: Self::Settings) -> Result - where - Self: Sized; -} - -#[derive(Debug, Error)] -pub enum ProcessError { - /// Argument is a name of a widget - #[error("Trying to run a procces \"{0}\" not bound to any environment")] - RunWithNoEnv(String), - - #[error("Custom error occured in widget \"{0}\": \n \"{1}\"")] - Custom(String, anyhow::Error), -} diff --git a/src/root.rs b/src/root.rs index e305daf..3eaa12c 100644 --- a/src/root.rs +++ b/src/root.rs @@ -8,7 +8,7 @@ use std::{ time::Duration, }; -use anyhow::Result; +use anyhow::{anyhow, Result}; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_keyboard, delegate_layer, delegate_output, delegate_pointer, @@ -39,15 +39,15 @@ use wayland_client::{ use crate::{ config::Config, - processes::{clients, Process, ProcessError, ProcessNew}, + services::{Service, ServiceError, ServiceNew}, util::{ fonts::{self, FontsError}, - signals::Signal, + signals::{Signal, SignalNames}, Drawer, }, widgets::{ - self, battery::Battery, clock::Clock, containers::bar::Bar, cpu::CPU, keyboard::Keyboard, - text::Text, Widget, WidgetError, WidgetNew, WidgetsList, + containers::{bar::Bar, Container}, + Widget, WidgetNew, }, }; @@ -55,7 +55,7 @@ use crate::{ pub struct Environment { pub config: Config, pub drawer: RefCell, - pub signals: RefCell>, + pub signals: RefCell>, } #[derive(Error, Debug)] @@ -81,8 +81,8 @@ pub struct Root { keyboard_focus: bool, pointer: Option, - widgets: Vec>, - processes: Vec>, + bar: Option, + services: Vec>, env: Option>, } @@ -94,7 +94,6 @@ impl CompositorHandler for Root { _surface: &wl_surface::WlSurface, _new_factor: i32, ) { - // Not needed for this example. } fn transform_changed( @@ -104,7 +103,6 @@ impl CompositorHandler for Root { _surface: &wl_surface::WlSurface, _new_transform: wl_output::Transform, ) { - // Not needed for this example. } fn frame( @@ -126,7 +124,6 @@ impl CompositorHandler for Root { _surface: &wl_surface::WlSurface, _output: &wl_output::WlOutput, ) { - // Not needed for this example. } fn surface_leave( @@ -136,7 +133,6 @@ impl CompositorHandler for Root { _surface: &wl_surface::WlSurface, _output: &wl_output::WlOutput, ) { - // Not needed for this example. } } @@ -184,9 +180,9 @@ impl LayerShellHandler for Root { self.width = NonZeroU32::new(configure.new_size.0).map_or(256, NonZeroU32::get); self.height = NonZeroU32::new(configure.new_size.1).map_or(256, NonZeroU32::get); - // Initiate the first draw. if self.first_configure { self.first_configure = false; + if let Err(a) = self.draw(qh) { println!("{a}"); } @@ -346,7 +342,11 @@ 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, + bar: Option, + ) -> Result { let qh = event_queue.handle(); let compositor = @@ -358,7 +358,7 @@ impl Root { let layer = layer_shell.create_layer_surface(&qh, surface, Layer::Top, Some("Bar"), None); - let bar = Root { + let root = Root { flag: true, registry_state: RegistryState::new(globals), @@ -375,63 +375,41 @@ impl Root { keyboard_focus: false, pointer: None, - widgets: Vec::new(), - processes: Vec::new(), + bar, + services: Vec::new(), env: None, }; - Ok(bar) + Ok(root) } pub fn apply_config(&mut self, config: Config) -> Result<()> { + if self.bar.is_some() { + return Err(anyhow!("Config can only be applied once")); + } 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)?, - WidgetsList::Keyboard(wsettings, psettings) => { - self.create_process(clients::Keyboard::new, psettings)?; - bar.create_child_left(Keyboard::new, wsettings)? - } - } + widget.create_in_container(bar.left().get_mut())?; } 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)?, - WidgetsList::Keyboard(wsettings, psettings) => { - self.create_process(clients::Keyboard::new, psettings)?; - bar.create_child_center(widgets::keyboard::Keyboard::new, wsettings)? - } - } + widget.create_in_container(bar.center().get_mut())?; } 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)?, - WidgetsList::Keyboard(wsettings, psettings) => { - self.create_process(clients::Keyboard::new, psettings)?; - bar.create_child_right(widgets::keyboard::Keyboard::new, wsettings)? - } - } + widget.create_in_container(bar.right().get_mut())?; } - self.add_widget(bar)?; + self.bar = Some(bar); Ok(()) } - pub fn init(&mut self, event_queue: &mut EventQueue) -> Result<&mut Self> { + fn init(&mut self) -> Result<&mut Self> { + if self.bar.is_none() { + return Err(anyhow!("Empty bar can not be created")); + } + self.layer.set_anchor(Anchor::TOP); self.layer .set_keyboard_interactivity(KeyboardInteractivity::OnDemand); @@ -444,24 +422,17 @@ impl Root { signals: RefCell::new(HashMap::new()), })); - for process in &mut self.processes { - process.bind(Rc::clone(self.env.as_ref().unwrap()))?; + for service in &mut self.services { + service.bind(Rc::clone(self.env.as_ref().unwrap()))?; - process.init()?; + service.init()?; } - for widget in &mut self.widgets { - widget.bind(Rc::clone(self.env.as_ref().unwrap()))?; + let bar = self.bar.as_mut().unwrap(); + bar.bind(Rc::clone(self.env.as_ref().unwrap()))?; + bar.init()?; - widget.init()?; - let data = widget.data().borrow_mut(); - self.height = max( - self.height, - (data.height + data.position.1).try_into().unwrap(), - ); - } - - event_queue.blocking_dispatch(self)?; + self.height = max(self.height, bar.data_mut().height as u32); for output in self.output_state().outputs() { let info = self @@ -490,11 +461,12 @@ impl Root { } pub fn run(&mut self, event_queue: &mut EventQueue) -> Result<&mut Self> { - let _ = event_queue.blocking_dispatch(self); + event_queue.blocking_dispatch(self)?; + self.init()?; loop { - event_queue.blocking_dispatch(self)?; thread::sleep(Duration::from_millis(100)); + event_queue.blocking_dispatch(self)?; } //Ok(self) @@ -504,33 +476,12 @@ impl Root { fonts::add_font_by_name(name) } - pub fn add_widget(&mut self, mut widget: W) -> Result<()> + pub fn create_service(&mut self, f: F, settings: W::Settings) -> Result<()> where - W: Widget + 'static, + W: ServiceNew + Service + 'static, + F: FnOnce(Option>, W::Settings) -> Result, { - if let Some(env) = &self.env { - widget.bind(Rc::clone(env))?; - } - self.widgets.push(Box::new(widget)); - Ok(()) - } - - pub fn create_widget(&mut self, f: F, settings: W::Settings) -> Result<()> - where - W: WidgetNew + Widget + 'static, - F: FnOnce(Option>, W::Settings) -> Result, - { - self.widgets.push(Box::new(f(self.env.clone(), settings)?)); - Ok(()) - } - - pub fn create_process(&mut self, f: F, settings: W::Settings) -> Result<()> - where - W: ProcessNew + Process + 'static, - F: FnOnce(Option>, W::Settings) -> Result, - { - self.processes - .push(Box::new(f(self.env.clone(), settings)?)); + self.services.push(Box::new(f(self.env.clone(), settings)?)); Ok(()) } @@ -539,17 +490,35 @@ impl Root { return Err(RootError::EnvironmentNotInit.into()); } - for process in &mut self.processes { - process.run()?; + for service in &mut self.services { + service.run()?; + } + + self.bar.as_ref().unwrap().prepare()?; + + { + let bar = self.bar.as_ref().unwrap().data(); + if self.width != bar.width as u32 || self.height != bar.height as u32 { + self.width = bar.width as u32; + self.height = bar.height as u32; + + self.layer.set_size(self.width, self.height); + self.layer.set_exclusive_zone(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, + ); + } } self.layer .wl_surface() .damage_buffer(0, 0, self.width as i32, self.height as i32); - for widget in self.widgets.iter() { - widget.draw()?; - } + self.bar.as_ref().unwrap().run()?; + self.bar.as_ref().unwrap().draw()?; // Request our next frame self.layer @@ -566,6 +535,10 @@ impl Root { self.flag = false; Ok(()) } + + pub fn bar(&self) -> &Option { + &self.bar + } } delegate_compositor!(Root); diff --git a/src/processes/clients/hyprland/keyboard.rs b/src/services/clients/hyprland/keyboard.rs similarity index 68% rename from src/processes/clients/hyprland/keyboard.rs rename to src/services/clients/hyprland/keyboard.rs index 2244edb..6875872 100644 --- a/src/processes/clients/hyprland/keyboard.rs +++ b/src/services/clients/hyprland/keyboard.rs @@ -5,12 +5,12 @@ use chrono::{DateTime, Duration, Local}; use hyprland::{data::Devices, shared::HyprData}; use crate::{ - processes::{clients::KeyboardTrait, Process, ProcessError, ProcessNew, ProcessSettings}, root::Environment, - util::signals::Signal, + services::{clients::KeyboardTrait, ProcessSettings, Service, ServiceError, ServiceNew}, + util::signals::SignalNames, }; -/// Process that tracks current keyboard layout +/// Service that tracks current keyboard layout pub struct Keyboard { settings: ProcessSettings, @@ -21,16 +21,16 @@ pub struct Keyboard { } impl Keyboard { - fn get_main_keyboard() -> Result { + fn get_main_keyboard() -> Result { let devices = Devices::get(); if let Err(err) = devices { - return Err(ProcessError::Custom("Keyboard".to_string(), err.into())); + return Err(ServiceError::Custom("Keyboard".to_string(), err.into())); } let keyboards = devices.unwrap().keyboards; if keyboards.is_empty() { - return Err(ProcessError::Custom( + return Err(ServiceError::Custom( "Keyboard".to_string(), anyhow!("No Keyboard connected"), )); @@ -42,38 +42,41 @@ impl Keyboard { } } - Err(ProcessError::Custom( + Err(ServiceError::Custom( "Keyboard".to_string(), anyhow!("No main keyboard found"), )) } } -impl Process for Keyboard { - fn bind(&mut self, env: std::rc::Rc) -> Result<(), ProcessError> { +impl Service for Keyboard { + fn bind(&mut self, env: std::rc::Rc) -> Result<(), ServiceError> { self.env = Some(Rc::clone(&env)); + env.signals + .borrow_mut() + .entry(SignalNames::Keyboard) + .or_default(); + Ok(()) } - fn init(&self) -> Result<(), ProcessError> { + fn init(&self) -> Result<(), ServiceError> { if self.env.is_none() { - return Err(ProcessError::RunWithNoEnv("Keyboard".to_string())); + return Err(ServiceError::RunWithNoEnv("Keyboard".to_string())); } let mut signals = self.env.as_ref().unwrap().signals.borrow_mut(); - if !signals.contains_key("keyboard") { - signals.insert("keyboard".to_string(), Signal::new()); - } + signals.entry(SignalNames::Keyboard).or_default(); *self.last_layout.borrow_mut() = Keyboard::get_main_keyboard()?.active_keymap; - signals["keyboard"].emit(&self.last_layout.clone()); + signals[&SignalNames::Keyboard].emit(&self.last_layout.clone()); Ok(()) } - fn run(&self) -> Result<(), ProcessError> { + fn run(&self) -> Result<(), ServiceError> { if self.env.is_none() { - return Err(ProcessError::RunWithNoEnv("Keyboard".to_string())); + return Err(ServiceError::RunWithNoEnv("Keyboard".to_string())); } let mut last_update = self.last_update.borrow_mut(); @@ -87,20 +90,20 @@ impl Process for Keyboard { let current_layout = Keyboard::get_main_keyboard()?.active_keymap; if *last_layout != current_layout { *last_layout = current_layout; - signals["keyboard"].emit(&last_layout.clone()); + signals[&SignalNames::Keyboard].emit(&last_layout.clone()); } Ok(()) } } -impl ProcessNew for Keyboard { +impl ServiceNew for Keyboard { type Settings = ProcessSettings; fn new( env: Option>, settings: Self::Settings, - ) -> Result + ) -> Result where Self: Sized, { diff --git a/src/processes/clients/hyprland/mod.rs b/src/services/clients/hyprland/mod.rs similarity index 100% rename from src/processes/clients/hyprland/mod.rs rename to src/services/clients/hyprland/mod.rs diff --git a/src/processes/clients/mod.rs b/src/services/clients/mod.rs similarity index 86% rename from src/processes/clients/mod.rs rename to src/services/clients/mod.rs index d92221b..72fbff2 100644 --- a/src/processes/clients/mod.rs +++ b/src/services/clients/mod.rs @@ -1,14 +1,14 @@ //! Current module describes all of capybars clients. Different compositors handle some stuff //! differently. All of the unique behaviours is described here. -use super::Process; +use super::Service; #[cfg(feature = "hyprland")] pub mod hyprland; #[allow(dead_code)] #[cfg(feature = "keyboard")] -trait KeyboardTrait: Process {} +trait KeyboardTrait: Service {} #[cfg(feature = "keyboard+hyprland")] pub use hyprland::keyboard::Keyboard; diff --git a/src/services/mod.rs b/src/services/mod.rs new file mode 100644 index 0000000..51d39c2 --- /dev/null +++ b/src/services/mod.rs @@ -0,0 +1,73 @@ +//! Current module describes all of capybars included servicees as well as their common behaviour. +//! +//! Process can be treated as a backend component. +//! To communicate with frontend you can use [Signal](crate::util::signals::Signal) + +pub mod clients; + +use std::{fmt::Display, rc::Rc}; + +use serde::Deserialize; +use thiserror::Error; + +use crate::root::Environment; + +fn default_update_rate() -> i64 { + 1000 +} + +#[derive(Debug, Deserialize, Clone, Copy)] +pub struct ProcessSettings { + #[serde(default = "default_update_rate")] + pub update_rate: i64, +} + +/// A **data structure** that can be used as a service inside a capybar. +pub trait Service { + /// Bind a widget to a new environment. + fn bind(&mut self, env: Rc) -> Result<(), ServiceError>; + + /// Prepare [Service] for a first run + fn init(&self) -> Result<(), ServiceError>; + + /// Run the [Service] + fn run(&self) -> Result<(), ServiceError>; +} + +/// A [Service] that can be unifiedly created. +/// +/// Implementing this trait allows creating [Service] and binding the environment without +/// intermidiate steps. Simplifies service creation inside of scripts. +pub trait ServiceNew { + type Settings; + + fn new(env: Option>, settings: Self::Settings) -> Result + where + Self: Sized; +} + +#[derive(Debug, Error)] +pub enum ServiceError { + /// Argument is a name of a service + #[error("Trying to run a service \"{0}\" not bound to any environment")] + RunWithNoEnv(String), + + #[error("Custom error occured in service \"{0}\": \n \"{1}\"")] + Custom(String, anyhow::Error), +} + +/// All available widgets in capybar +#[derive(Debug, Clone)] +pub enum ServiceList { + Keyboard, + Custom(String), +} + +impl Display for ServiceList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Keyboard => write!(f, "Keyboard"), + Self::Custom(name) => write!(f, "{name}"), + } + } +} diff --git a/src/util/signals.rs b/src/util/signals.rs index bf41de7..6bf6ca5 100644 --- a/src/util/signals.rs +++ b/src/util/signals.rs @@ -34,7 +34,7 @@ type Callback = Box; /// let signal = Signal::new(); /// let tracker = Rc::new(RefCell::new(0)); /// -/// // Connect callback that processes i32 values +/// // Connect callback that servicees i32 values /// let track = Rc::clone(&tracker); /// signal.connect(move |data| { /// if let Some(num) = data.downcast_ref::() { @@ -173,3 +173,9 @@ impl Signal { self.with_last_value(|opt| opt.and_then(|any| any.downcast_ref::().cloned())) } } + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub enum SignalNames { + Keyboard, + Custom(String), +} diff --git a/src/widgets/battery.rs b/src/widgets/battery.rs index ca02bb2..059e941 100644 --- a/src/widgets/battery.rs +++ b/src/widgets/battery.rs @@ -1,12 +1,16 @@ -use std::{cell::RefCell, ops::Add}; +use std::{ + cell::{Ref, RefCell, RefMut}, + ops::Add, +}; use anyhow::Result; use battery::{Manager, State}; use serde::Deserialize; use super::{ - text::{Text, TextSettings}, - Style, Widget, WidgetData, WidgetError, WidgetNew, WidgetStyled, + icon_text::{IconText, IconTextSettings}, + text::TextSettings, + Style, Widget, WidgetData, WidgetError, WidgetList, WidgetNew, WidgetStyled, }; const fn battery_not_charging_default() -> [char; 11] { @@ -97,11 +101,11 @@ impl BatteryInfo { /// Widget displaying current battery status. pub struct Battery { manager: Manager, + icon_text: RefCell, - icon: RefCell, - percent: RefCell, settings: BatterySettings, data: RefCell, + is_ready: RefCell, prev_charge: RefCell, } @@ -135,73 +139,83 @@ impl Battery { ), ) } +} - fn align(&self) -> Result<()> { - let icon = self.icon.borrow_mut(); - let mut icon_data = icon.data().borrow_mut(); - let percent = self.percent.borrow_mut(); - let mut percent_data = percent.data().borrow_mut(); - - let data = &mut self.data.borrow_mut(); - - icon_data.position.0 = data.position.0 + icon_data.margin.0; - icon_data.position.1 = data.position.1 + icon_data.margin.2; - percent_data.position.0 = - icon_data.position.0 + icon_data.width + icon_data.margin.1 + percent_data.margin.0; - percent_data.position.1 = data.position.1 + percent_data.margin.2; +impl Widget for Battery { + fn name(&self) -> WidgetList { + WidgetList::Battery + } - data.height = usize::max( - percent_data.position.1 + percent_data.height + percent_data.margin.3, - icon_data.position.1 + icon_data.height + icon_data.margin.3, - ); + fn as_styled(&self) -> Option<&dyn WidgetStyled> { + Some(self) + } - data.width = icon_data.margin.0 - + icon_data.margin.1 - + icon_data.width - + percent_data.margin.0 - + percent_data.margin.1 - + percent_data.width; + fn data(&self) -> Ref<'_, WidgetData> { + self.data.borrow() + } - Ok(()) + fn data_mut(&self) -> RefMut<'_, WidgetData> { + self.data.borrow_mut() } -} -impl Widget for Battery { - fn data(&self) -> &RefCell { - &self.data + fn env(&self) -> Option> { + self.icon_text.borrow().env() } fn bind( &mut self, env: std::rc::Rc, ) -> anyhow::Result<(), WidgetError> { - self.percent.borrow_mut().bind(env.clone())?; - self.icon.borrow_mut().bind(env) + self.icon_text.borrow_mut().bind(env) } fn init(&self) -> Result<(), WidgetError> { - self.icon.borrow_mut().init()?; - self.percent.borrow_mut().init()?; + self.apply_style()?; + + self.icon_text.borrow_mut().change_text("Err"); + self.icon_text + .borrow_mut() + .change_icon(&self.settings.battery_not_charging[0].to_string()); + self.icon_text.borrow().init()?; + + Ok(()) + } + + fn prepare(&self) -> Result<(), WidgetError> { + { + let it = self.icon_text.borrow(); + it.prepare()?; + let mut it_data = it.data_mut(); + let mut self_data = self.data.borrow_mut(); + it_data.position = self_data.position; + self_data.width = it_data.width; + self_data.height = it_data.height; + } self.apply_style()?; - self.align()?; + *self.is_ready.borrow_mut() = true; Ok(()) } fn draw(&self) -> anyhow::Result<(), WidgetError> { + if self.env().is_none() { + return Err(WidgetError::DrawWithNoEnv(WidgetList::Battery)); + } + + self.draw_style()?; + let info = self.get_info(); let mut prev_charge = self.prev_charge.borrow_mut(); { - let mut text = self.percent.borrow_mut(); - let mut icon = self.icon.borrow_mut(); + let mut it = self.icon_text.borrow_mut(); match info { Some(i) => { let percentage: i8 = (i.percentage() * 100.0).round() as i8; if percentage != *prev_charge { - icon.change_text( + it.change_icon( format!( "{}", match i.state { @@ -211,22 +225,29 @@ impl Widget for Battery { ) .as_str(), ); - text.change_text(format!("{percentage}%").as_str()); + it.change_text(format!("{percentage}%").as_str()); } } None => { if *prev_charge != -1 { - self.icon.borrow_mut().change_text(""); - text.change_text("ERR"); + it.change_icon(""); + it.change_text("ERR"); } *prev_charge = -1; } }; } - self.align()?; - self.percent.borrow_mut().draw()?; - self.icon.borrow_mut().draw() + { + let it = self.icon_text.borrow(); + let mut it_data = it.data_mut(); + let mut self_data = self.data.borrow_mut(); + it_data.position = self_data.position; + self_data.width = it_data.width; + self_data.height = it_data.height; + } + + self.icon_text.borrow().draw() } } @@ -248,29 +269,14 @@ impl WidgetNew for Battery { let manager = manager.unwrap(); Ok(Self { manager, + is_ready: RefCell::new(false), - icon: RefCell::new(Text::new( + icon_text: RefCell::new(IconText::new( env.clone(), - TextSettings { - text: "󰂎".to_string(), - default_data: WidgetData { - margin: (0, 0, 0, 0), - ..WidgetData::default() - }, - fontid: 1, - ..settings.text_settings.clone() - }, - )?), - percent: RefCell::new(Text::new( - env, - TextSettings { - text: "Err".to_string(), - - default_data: WidgetData { - margin: (5, 0, 2, 0), - ..WidgetData::default() - }, - ..settings.text_settings.clone() + IconTextSettings { + icon_settings: settings.text_settings.clone(), + text_settings: settings.text_settings.clone(), + ..IconTextSettings::default() }, )?), @@ -285,8 +291,4 @@ impl WidgetStyled for Battery { fn style(&self) -> &Style { &self.settings.style } - - fn style_mut(&mut self) -> &mut Style { - &mut self.settings.style - } } diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index ed5ccc9..49dce37 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -1,4 +1,7 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, +}; use anyhow::Result; use chrono::Local; @@ -10,7 +13,9 @@ use crate::{ widgets::{text::Text, Widget}, }; -use super::{text::TextSettings, WidgetData, WidgetError, WidgetNew}; +use super::{ + text::TextSettings, Margin, Style, WidgetData, WidgetError, WidgetList, WidgetNew, WidgetStyled, +}; fn default_format() -> String { "%H:%M".to_string() @@ -32,6 +37,9 @@ pub struct ClockSettings { #[serde(default)] pub default_data: WidgetData, + + #[serde(default, flatten)] + pub style: Style, } impl Default for ClockSettings { @@ -43,6 +51,8 @@ impl Default for ClockSettings { font_color: Color::BLACK, default_data: WidgetData::default(), + + style: Style::default(), } } } @@ -53,6 +63,7 @@ pub struct Clock { settings: ClockSettings, data: RefCell, + is_ready: RefCell, } impl Clock { @@ -60,39 +71,76 @@ impl Clock { pub fn update(&self) -> &Self { let mut text = self.text.borrow_mut(); text.change_text(&Local::now().format(&self.settings.format).to_string()); - text.data().borrow_mut().position = self.data.borrow_mut().position; + text.data_mut().position = self.data.borrow_mut().position; self } } impl Widget for Clock { + fn name(&self) -> WidgetList { + WidgetList::Clock + } + + fn as_styled(&self) -> Option<&dyn WidgetStyled> { + Some(self) + } + + fn data(&self) -> Ref<'_, WidgetData> { + self.data.borrow() + } + + fn data_mut(&self) -> RefMut<'_, WidgetData> { + self.data.borrow_mut() + } + fn bind(&mut self, env: Rc) -> Result<(), WidgetError> { self.text.borrow_mut().bind(env) } + fn env(&self) -> Option> { + self.text.borrow_mut().env() + } + fn init(&self) -> Result<(), WidgetError> { let text = self.text.borrow_mut(); text.init()?; - let text_data = text.data().borrow_mut(); + let text_data = text.data_mut(); let mut data = self.data.borrow_mut(); - data.width = text_data.width; - data.height = text_data.height; + data.width += text_data.width; + data.height += text_data.height; Ok(()) } + fn prepare(&self) -> Result<(), WidgetError> { + { + let text = self.text.borrow(); + text.prepare()?; + let mut it_data = text.data_mut(); + let mut self_data = self.data.borrow_mut(); + it_data.position = self_data.position; + self_data.width = it_data.width; + self_data.height = it_data.height; + } + + self.apply_style()?; + + *self.is_ready.borrow_mut() = true; + Ok(()) + } + fn draw(&self) -> Result<(), WidgetError> { + if self.env().is_none() { + return Err(WidgetError::DrawWithNoEnv(WidgetList::Clock)); + } + self.draw_style()?; self.update(); self.text.borrow_mut().draw() } - - fn data(&self) -> &RefCell { - &self.data - } } impl WidgetNew for Clock { @@ -114,6 +162,16 @@ impl WidgetNew for Clock { ..WidgetData::default() }, + style: Style { + margin: Margin { + left: 2, + right: 2, + up: 0, + down: 0, + }, + ..Style::default() + }, + ..TextSettings::default() }, )?); @@ -121,6 +179,13 @@ impl WidgetNew for Clock { text, data: RefCell::new(settings.default_data), settings, + is_ready: RefCell::new(false), }) } } + +impl WidgetStyled for Clock { + fn style(&self) -> &Style { + &self.settings.style + } +} diff --git a/src/widgets/containers/bar.rs b/src/widgets/containers/bar.rs index 2e30d41..1b2ee32 100644 --- a/src/widgets/containers/bar.rs +++ b/src/widgets/containers/bar.rs @@ -1,11 +1,15 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, +}; use anyhow::Result; use serde::Deserialize; use crate::{ root::Environment, - widgets::{Style, Widget, WidgetData, WidgetError, WidgetNew}, + services::Service, + widgets::{Style, Widget, WidgetData, WidgetError, WidgetList, WidgetNew, WidgetStyled}, }; use super::{ @@ -24,6 +28,15 @@ pub struct BarSettings { #[serde(default)] pub padding: (usize, usize, usize), + #[serde(default)] + pub left_settings: RowSettings, + + #[serde(default)] + pub center_settings: RowSettings, + + #[serde(default)] + pub right_settings: RowSettings, + #[serde(flatten)] pub style: Style, } @@ -33,6 +46,9 @@ impl BarSettings { Self { default_data: WidgetData::default(), padding: (10, 10, 10), + left_settings: RowSettings::default(), + center_settings: RowSettings::default(), + right_settings: RowSettings::default(), style: Style::default(), } } @@ -47,50 +63,109 @@ pub struct Bar { left: RefCell, center: RefCell, right: RefCell, + services: RefCell>>, } impl Bar { pub fn add_center(&self, widget: Box) -> Result<()> { - self.center.borrow_mut().add_child(widget)?; + self.center.borrow_mut().add_widget(widget); Ok(()) } - pub fn create_child_left(&mut self, f: F, settings: W::Settings) -> Result<()> + pub fn create_widget_left(&mut self, f: F, settings: W::Settings) -> Result<()> where W: WidgetNew + Widget + 'static, F: FnOnce(Option>, W::Settings) -> Result, { self.left .borrow_mut() - .add_child(Box::new(f(self.env.clone(), settings)?))?; + .add_widget(Box::new(f(self.env.clone(), settings)?)); Ok(()) } - pub fn create_child_center(&mut self, f: F, settings: W::Settings) -> Result<()> + pub fn create_widget_center(&mut self, f: F, settings: W::Settings) -> Result<()> where W: WidgetNew + Widget + 'static, F: FnOnce(Option>, W::Settings) -> Result, { self.center .borrow_mut() - .add_child(Box::new(f(self.env.clone(), settings)?))?; + .add_widget(Box::new(f(self.env.clone(), settings)?)); Ok(()) } - pub fn create_child_right(&mut self, f: F, settings: W::Settings) -> Result<()> + pub fn create_widget_right(&mut self, f: F, settings: W::Settings) -> Result<()> where W: WidgetNew + Widget + 'static, F: FnOnce(Option>, W::Settings) -> Result, { self.right .borrow_mut() - .add_child(Box::new(f(self.env.clone(), settings)?))?; + .add_widget(Box::new(f(self.env.clone(), settings)?)); + Ok(()) + } + + pub fn left(&mut self) -> &mut RefCell { + &mut self.left + } + + pub fn center(&mut self) -> &mut RefCell { + &mut self.center + } + + pub fn right(&mut self) -> &mut RefCell { + &mut self.right + } + + fn align_widgets(&self) -> anyhow::Result<()> { + let mut data = self.data.borrow_mut(); + let border = match self.settings.style.border { + Some(a) => (a.0, Some(a.1)), + None => (0, None), + }; + + let left = self.left.borrow_mut(); + let mut ld = left.data_mut(); + + ld.position.0 = data.position.0 + border.0; + ld.position.1 = data.position.1 + border.0; + + let center = self.center.borrow_mut(); + let mut cd = center.data_mut(); + + cd.position.0 = data.position.0 + (data.width - cd.width) / 2; + cd.position.1 = data.position.1 + border.0; + + let right = self.right.borrow_mut(); + let mut rd = right.data_mut(); + + rd.position.0 = data.position.0 + data.width - border.0; + rd.position.1 = data.position.1 + border.0; + + data.height = ld.height.max(cd.height).max(rd.height); + Ok(()) } } impl Widget for Bar { + fn name(&self) -> WidgetList { + WidgetList::Bar + } + + fn as_styled(&self) -> Option<&dyn WidgetStyled> { + Some(self) + } + + fn data(&self) -> Ref<'_, WidgetData> { + self.data.borrow() + } + + fn data_mut(&self) -> RefMut<'_, WidgetData> { + self.data.borrow_mut() + } + fn bind( &mut self, env: std::rc::Rc, @@ -98,75 +173,41 @@ impl Widget for Bar { self.left.borrow_mut().bind(Rc::clone(&env))?; self.center.borrow_mut().bind(Rc::clone(&env))?; self.right.borrow_mut().bind(Rc::clone(&env))?; + + for service in self.services.borrow_mut().iter_mut() { + if let Err(e) = service.bind(Rc::clone(&env)) { + return Err(WidgetError::Custom(e.into())); + } + } self.env = Some(env); Ok(()) } - fn draw(&self) -> anyhow::Result<(), WidgetError> { - if self.env.is_none() { - return Err(WidgetError::DrawWithNoEnv("Bar".to_string())); - } - - let data = self.data.borrow_mut(); - - let border = match self.settings.style.border { - Some(a) => (a.0, Some(a.1)), - None => (0, None), - }; - - { - 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); - } - } - - 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); - } - } - } - } + fn env(&self) -> Option> { + self.env.clone() + } - let left = self.left.borrow_mut(); - { - let mut ld = left.data().borrow_mut(); + fn prepare(&self) -> Result<(), WidgetError> { + self.left.borrow().prepare()?; + self.center.borrow().prepare()?; + self.right.borrow().prepare()?; - ld.position.0 = data.position.0 + border.0; - ld.position.1 = data.position.1 + border.0; - } - left.draw()?; + self.align_widgets()?; + self.apply_style()?; - let center = self.center.borrow_mut(); - { - let mut cd = center.data().borrow_mut(); + Ok(()) + } - cd.position.0 = data.position.0 + (data.width - cd.width) / 2; - cd.position.1 = data.position.1 + border.0; + fn draw(&self) -> anyhow::Result<(), WidgetError> { + if self.env.is_none() { + return Err(WidgetError::DrawWithNoEnv(WidgetList::Bar)); } - center.draw()?; - let right = self.right.borrow_mut(); - { - let mut rd = right.data().borrow_mut(); + self.draw_style()?; - rd.position.0 = data.position.0 + data.width - border.0; - rd.position.1 = data.position.1 + border.0; - } - right.draw()?; + self.left.borrow_mut().draw()?; + self.center.borrow_mut().draw()?; + self.right.borrow_mut().draw()?; Ok(()) } @@ -178,17 +219,18 @@ impl Widget for Bar { left.init()?; center.init()?; right.init()?; + right.data_mut().position.0 = self.data().width; let border = match self.settings.style.border { Some(a) => (a.0, Some(a.1)), None => (0, None), }; - let mut data = self.data.borrow_mut(); + let mut data = self.data_mut(); data.height = *[ - left.data().borrow_mut().height, - center.data().borrow_mut().height, - right.data().borrow_mut().height, + left.data_mut().height, + center.data_mut().height, + right.data_mut().height, ] .iter() .max_by(|a, b| a.cmp(b)) @@ -197,10 +239,6 @@ impl Widget for Bar { Ok(()) } - - fn data(&self) -> &RefCell { - &self.data - } } impl WidgetNew for Bar { @@ -220,7 +258,7 @@ impl WidgetNew for Bar { env.clone(), RowSettings { alignment: Alignment::GrowthHorizontalRight(settings.padding.0), - ..RowSettings::default() + ..settings.left_settings }, )?), @@ -228,7 +266,7 @@ impl WidgetNew for Bar { env.clone(), RowSettings { alignment: Alignment::GrowthCenteringHorizontalRight(settings.padding.1), - ..RowSettings::default() + ..settings.center_settings }, )?), @@ -236,9 +274,10 @@ impl WidgetNew for Bar { env.clone(), RowSettings { alignment: Alignment::GrowthHorizontalLeft(settings.padding.2), - ..RowSettings::default() + ..settings.right_settings }, )?), + services: RefCell::new(Vec::new()), settings, env, @@ -246,16 +285,31 @@ impl WidgetNew for Bar { } } -impl Container for Bar { - fn align_children(&self) -> anyhow::Result<()> { - todo!(); +impl WidgetStyled for Bar { + fn style(&self) -> &Style { + &self.settings.style } +} - fn children(&self) -> &super::WidgetVec { - todo!(); +impl Container for Bar { + fn create_service(&mut self, f: F, settings: W::Settings) -> Result<()> + where + W: crate::services::ServiceNew + crate::services::Service + 'static, + F: FnOnce(Option>, W::Settings) -> Result, + { + self.services + .borrow_mut() + .push(Box::new(f(self.env.clone(), settings)?)); + Ok(()) } - fn children_mut(&mut self) -> &super::WidgetVec { - todo!(); + fn run(&self) -> Result<()> { + for service in self.services.borrow_mut().iter() { + service.run()?; + } + self.left.borrow().run()?; + self.center.borrow().run()?; + self.right.borrow().run()?; + Ok(()) } } diff --git a/src/widgets/containers/mod.rs b/src/widgets/containers/mod.rs index 1444f7e..66fb79a 100644 --- a/src/widgets/containers/mod.rs +++ b/src/widgets/containers/mod.rs @@ -1,69 +1,34 @@ pub mod bar; pub mod row; -use std::borrow::{Borrow, BorrowMut}; +use std::rc::Rc; use anyhow::Result; -use crate::widgets::Widget; +use crate::{ + root::Environment, + services::{Service, ServiceError, ServiceNew}, + 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() - } +use super::{WidgetError, WidgetNew}; - 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 +/// [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 create_service(&mut self, f: F, settings: W::Settings) -> Result<()> + where + W: ServiceNew + Service + 'static, + F: FnOnce(Option>, W::Settings) -> Result; - fn children(&self) -> &WidgetVec; + /// Run all child [Service] objects + fn run(&self) -> Result<()>; +} - fn children_mut(&mut self) -> &WidgetVec; +/// Trait that describes [Container] that has single clear way to add child widget +pub trait ContainerSingle: Container { + fn create_widget(&mut self, f: F, settings: W::Settings) -> Result<(), WidgetError> + where + W: WidgetNew + Widget + 'static, + F: FnOnce(Option>, W::Settings) -> Result; } diff --git a/src/widgets/containers/row.rs b/src/widgets/containers/row.rs index 1578848..bfcddbe 100644 --- a/src/widgets/containers/row.rs +++ b/src/widgets/containers/row.rs @@ -1,17 +1,23 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, +}; use anyhow::Result; +use serde::Deserialize; use thiserror::Error; use crate::{ root::Environment, + services::Service, util::Color, - widgets::{Widget, WidgetData, WidgetError, WidgetNew}, + widgets::{Style, Widget, WidgetData, WidgetError, WidgetList, WidgetNew, WidgetStyled}, }; -use super::{Container, WidgetVec}; +use super::{Container, ContainerSingle}; -#[derive(Debug, Clone, Copy)] +#[derive(Deserialize, Debug, Clone, Copy)] +#[serde(tag = "type", content = "padding")] pub enum Alignment { CenteringHorizontal, CenteringVertical, @@ -31,19 +37,37 @@ impl Default for Alignment { } } +impl Alignment { + pub const fn default() -> Self { + Alignment::GrowthHorizontalRight(10) + } +} + /// Settings of a [Row] container -#[derive(Debug, Default, Clone, Copy)] +#[derive(Default, Deserialize, Debug, Clone, Copy)] pub struct RowSettings { - pub background: Option, - pub border: Option<(usize, Color)>, + #[serde(default)] pub alignment: Alignment, + #[serde(default, flatten)] pub default_data: WidgetData, + #[serde(default, flatten)] + pub style: Style, +} + +impl RowSettings { + pub const fn default() -> RowSettings { + RowSettings { + alignment: Alignment::default(), + default_data: WidgetData::default(), + style: Style::default(), + } + } } #[derive(Error, Debug)] pub enum RowError { - #[error("Row is not wide enough to display all of it's children")] + #[error("Row is not wide enough to display all of it's widgets")] WidthOverflow, #[error("anyhow error: {0}")] @@ -55,264 +79,283 @@ pub struct Row { settings: RowSettings, data: RefCell, - children: RefCell, + widgets: RefCell>>, env: Option>, + services: RefCell>>, + + is_ready: RefCell, } impl Widget for Row { + fn name(&self) -> WidgetList { + WidgetList::Row + } + + fn as_styled(&self) -> Option<&dyn WidgetStyled> { + Some(self) + } + + fn data(&self) -> Ref<'_, WidgetData> { + self.data.borrow() + } + + fn data_mut(&self) -> RefMut<'_, WidgetData> { + self.data.borrow_mut() + } + fn bind(&mut self, env: Rc) -> Result<(), WidgetError> { self.env = Some(Rc::clone(&env)); - let mut children = self.children.borrow_mut(); - let children = children.widgets_mut(); - for child in children { - child.bind(Rc::clone(&env))?; + let mut widgets = self.widgets.borrow_mut(); + for widget in widgets.iter_mut() { + widget.bind(Rc::clone(&env))?; + } + + for service in self.services.borrow_mut().iter_mut() { + if let Err(e) = service.bind(Rc::clone(&env)) { + return Err(WidgetError::Custom(e.into())); + } } Ok(()) } + fn env(&self) -> Option> { + self.env.clone() + } + fn init(&self) -> Result<(), WidgetError> { let mut data = self.data.borrow_mut(); - let mut children = self.children.borrow_mut(); - let border = match self.settings.border { + + let border = match self.settings.style.border { Some(a) => a.0, None => 0, }; - let children = children.widgets_mut(); - for child in children { - child.init()?; - let child_data = child.data().borrow_mut(); + let widgets = self.widgets.borrow(); + for widget in widgets.iter() { + widget.init()?; + let widget_data = widget.data(); data.height = usize::max( data.height, - child_data.height + child_data.position.1 + child_data.margin.3 + border, + widget_data.height + + widget_data.position.1 + + border + + self.settings.style.margin.up + + self.settings.style.margin.down, ); } Ok(()) } - fn draw(&self) -> Result<(), WidgetError> { - if self.env.is_none() { - return Err(WidgetError::DrawWithNoEnv("Row".to_string())); + fn prepare(&self) -> Result<(), WidgetError> { + for widget in self.widgets.borrow_mut().iter() { + widget.prepare()?; } - self.align_children()?; + self.align_widgets()?; + self.apply_style()?; - let children = self.children.borrow_mut(); - let mut data = self.data.borrow_mut(); + *self.is_ready.borrow_mut() = true; + Ok(()) + } - if children.is_empty() { - data.height = self.settings.border.unwrap_or((5, Color::NONE)).0 * 3; + fn draw(&self) -> Result<(), WidgetError> { + if self.env.is_none() { + return Err(WidgetError::DrawWithNoEnv(WidgetList::Row)); } - let border = match self.settings.border { - Some(a) => (a.0, Some(a.1)), - None => (0, None), - }; - - { - 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); - } - } - - 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); - } - } - } + if !*self.is_ready.borrow() { + self.prepare()?; } + *self.is_ready.borrow_mut() = false; - for widget in children.widgets() { + self.draw_style()?; + + for widget in self.widgets.borrow_mut().iter() { widget.draw()?; } Ok(()) } - - fn data(&self) -> &RefCell { - &self.data - } } impl Row { - pub fn children_mut(&mut self) -> &mut Vec> { - self.children.get_mut().widgets_mut() + pub fn widgets_mut(&mut self) -> &mut Vec> { + self.widgets.get_mut() } pub fn len(&self) -> usize { - self.children.borrow().widgets().len() + self.widgets.borrow().len() } pub fn is_empty(&self) -> bool { - self.children.borrow().widgets().is_empty() + self.widgets.borrow().is_empty() } pub fn pop(&mut self) { - self.children.get_mut().widgets_mut().pop(); + self.widgets.get_mut().pop(); } - pub fn add_child(&mut self, child: Box) -> Result<(), RowError> { - self.children.get_mut().widgets_mut().push(child); - - Ok(()) - } - - pub fn create_child(&mut self, f: F, settings: W::Settings) -> Result<()> - where - W: WidgetNew + Widget + 'static, - F: FnOnce(Option>, W::Settings) -> Result, - { - self.add_child(Box::new(f(self.env.clone(), settings)?))?; - Ok(()) + pub fn add_widget(&mut self, widget: Box) { + self.widgets.get_mut().push(widget); } - fn get_max_height(children: &mut Vec>) -> usize { - if children.is_empty() { + fn get_max_height(widgets: &mut Vec>) -> usize { + if widgets.is_empty() { return 0; } let mut res = 0; - for child in children.iter_mut().map(|a| a.data().borrow_mut()) { - res = usize::max(res, child.height + child.position.1 + child.margin.3); + for widget in widgets.iter_mut().map(|a| a.data()) { + res = usize::max(res, widget.height + widget.position.1); } res } - fn align_children_centered_horizontal(&self) -> Result<(), RowError> { - let mut children = self.children.borrow_mut(); + fn align_widgets_centered_horizontal(&self) -> Result<(), RowError> { let mut data = self.data.borrow_mut(); - let border = match self.settings.border { + let border = match self.settings.style.border { Some((i, _)) => i, None => 0, }; - children.is_aligned = true; - let children = children.widgets_mut(); + let mut widgets = self.widgets.borrow_mut(); - if children.len() == 1 { + if widgets.len() == 1 { { - let mut child = children[0].data().borrow_mut(); - - child.position.0 = data.position.0 + (data.width - border * 2 - child.width) / 2; - child.position.1 = data.position.1 + border + child.margin.2; + let mut widget = widgets[0].data_mut(); + + widget.position.0 = data.position.0 + + (data.width - border * 2 - widget.width) / 2 + + self.style().margin.left; + widget.position.1 = data.position.1 + border + self.style().margin.up; + if let Some(styled) = widgets[0].as_styled() { + widget.position.1 += styled.style().margin.up; + } } - data.height = Row::get_max_height(children) + border; + data.height = Row::get_max_height(&mut widgets) + border; return Ok(()); } let mut total_width = 0; - for child in children.iter_mut() { - total_width += { - let data = child.data().borrow_mut(); - data.width + data.margin.0 + data.margin.1 - } + for widget in widgets.iter_mut() { + total_width += widget.data_mut().width; } if total_width > data.width - 2 * border { return Err(RowError::WidthOverflow); } - let dist = (data.width - 2 * border - total_width) / (children.len() - 1); + let dist = (data.width - 2 * border - total_width) / (widgets.len() - 1); let mut x = data.position.0 + border; - for child in children.iter_mut() { - let mut child = child.data().borrow_mut(); + for widget in widgets.iter_mut() { + let mut widget = widget.data_mut(); - child.position.0 = x + child.margin.0; - child.position.1 = data.position.1 + child.margin.2; + widget.position.0 = x; + widget.position.1 = data.position.1; - x += child.margin.0 + child.width + child.margin.1 + dist; + x += widget.width + dist; } - data.height = Row::get_max_height(children) + border; + data.height = Row::get_max_height(&mut widgets) + border; Ok(()) } - fn align_children_growth_ch(&self, padding: usize) -> Result<()> { + fn align_widgets_growth_ch(&self, padding: usize) -> Result<()> { { - let mut children = self.children.borrow_mut(); + let mut widgets = self.widgets.borrow_mut(); let mut data = self.data.borrow_mut(); data.width = 0; - for child in children - .widgets_mut() - .iter_mut() - .map(|a| a.data().borrow_mut()) - { - data.width += child.margin.0 + child.width + child.margin.1 + padding; + for widget in widgets.iter_mut().map(|a| a.data_mut()) { + data.width += widget.width + padding; } data.width -= padding; } - self.align_children_centered_horizontal()?; + self.align_widgets_centered_horizontal()?; Ok(()) } - fn align_children_growth_hr(&self, padding: usize) -> Result<()> { - let mut children = self.children.borrow_mut(); + fn align_widgets_growth_hr(&self, padding: usize) -> Result<()> { + let mut widgets = self.widgets.borrow_mut(); let mut data = self.data.borrow_mut(); - let border = match self.settings.border { + let border = match self.settings.style.border { Some((i, _)) => i, None => 0, }; - children.is_aligned = true; - let children = children.widgets_mut(); - - let mut offset = border + data.position.0; - for mut child in children.iter_mut().map(|a| a.data().borrow_mut()) { - child.position.1 = data.position.1 + child.margin.2; - child.position.0 = offset + child.margin.0; - offset += child.margin.0 + child.width + child.margin.1 + padding; + let mut offset = border + data.position.0 + self.settings.style.margin.left; + data.height = 0; + for mut widget in widgets.iter_mut().map(|a| a.data_mut()) { + widget.position.1 = data.position.1 + self.settings.style.margin.up + border; + widget.position.0 = offset; + offset += widget.width + padding; + data.height = usize::max(data.height, widget.height); } data.width = offset - padding + border; + data.height += self.settings.style.margin.up + self.settings.style.margin.down + 2 * border; Ok(()) } - fn align_children_growth_hl(&self, padding: usize) -> Result<()> { - let mut children = self.children.borrow_mut(); + fn align_widgets_growth_hl(&self, padding: usize) -> Result<()> { + let mut widgets = self.widgets.borrow_mut(); let mut data = self.data.borrow_mut(); - let border = match self.settings.border { + let border = match self.settings.style.border { Some((i, _)) => i, None => 0, }; - let children = children.widgets_mut(); + let mut offset = data.position.0 - border - self.settings.style.margin.right; + data.height = 0; + for mut widget in widgets.iter_mut().map(|a| a.data_mut()) { + widget.position.1 = data.position.1; + widget.position.0 = offset - widget.width; + offset -= widget.width + padding; + data.height = usize::max(data.height, widget.height); + } + data.height += self.settings.style.margin.up + self.settings.style.margin.down + 2 * border; + + data.width = data.position.0 + padding - offset - border; + + data.position.0 -= data.width; + + Ok(()) + } - let mut offset = data.position.0 - border; - for mut child in children.iter_mut().map(|a| a.data().borrow_mut()) { - child.position.1 = data.position.1 + child.margin.2; - child.position.0 = offset - child.width - child.margin.1; - offset -= child.margin.0 + child.width + child.margin.1 + padding; + fn align_widgets(&self) -> Result<()> { + if self.widgets.borrow_mut().is_empty() { + self.data.borrow_mut().height = + self.settings.style.border.unwrap_or((5, Color::NONE)).0 * 3; + return Ok(()); } - data.width = data.position.0 - (offset - padding + border); + match self.settings.alignment { + Alignment::CenteringHorizontal => self.align_widgets_centered_horizontal()?, + Alignment::CenteringVertical => todo!(), + Alignment::GrowthCenteringHorizontalRight(padding) => { + self.align_widgets_growth_ch(padding)? + } + Alignment::GrowthCenteringHorizontalLeft(_) => todo!(), + Alignment::GrowthCenteringVerticalRight(_) => todo!(), + Alignment::GrowthCenteringVerticalLeft(_) => todo!(), + Alignment::GrowthHorizontalRight(padding) => self.align_widgets_growth_hr(padding)?, + Alignment::GrowthHorizontalLeft(padding) => self.align_widgets_growth_hl(padding)?, + Alignment::GrowthVerticalUp(_) => todo!(), + Alignment::GrowthVerticalDown(_) => todo!(), + }; Ok(()) } @@ -328,40 +371,48 @@ impl WidgetNew for Row { data: RefCell::new(settings.default_data), settings, env, - children: RefCell::new(WidgetVec::new()), + widgets: RefCell::new(Vec::new()), + services: RefCell::new(Vec::new()), + is_ready: RefCell::new(false), }) } } +impl WidgetStyled for Row { + fn style(&self) -> &Style { + &self.settings.style + } +} + impl Container for Row { - fn align_children(&self) -> Result<()> { - if self.children.borrow_mut().is_empty() { - return Ok(()); - } + fn create_service(&mut self, f: F, settings: W::Settings) -> Result<()> + where + W: crate::services::ServiceNew + crate::services::Service + 'static, + F: FnOnce(Option>, W::Settings) -> Result, + { + self.services + .borrow_mut() + .push(Box::new(f(self.env.clone(), settings)?)); + Ok(()) + } - match self.settings.alignment { - Alignment::CenteringHorizontal => self.align_children_centered_horizontal()?, - Alignment::CenteringVertical => todo!(), - Alignment::GrowthCenteringHorizontalRight(padding) => { - self.align_children_growth_ch(padding)? - } - Alignment::GrowthCenteringHorizontalLeft(_) => todo!(), - Alignment::GrowthCenteringVerticalRight(_) => todo!(), - Alignment::GrowthCenteringVerticalLeft(_) => todo!(), - Alignment::GrowthHorizontalRight(padding) => self.align_children_growth_hr(padding)?, - Alignment::GrowthHorizontalLeft(padding) => self.align_children_growth_hl(padding)?, - Alignment::GrowthVerticalUp(_) => todo!(), - Alignment::GrowthVerticalDown(_) => todo!(), - }; + fn run(&self) -> Result<()> { + for service in self.services.borrow_mut().iter() { + service.run()?; + } Ok(()) } +} - fn children(&self) -> &WidgetVec { - todo!(); - } +impl ContainerSingle for Row { + fn create_widget(&mut self, f: F, settings: W::Settings) -> Result<(), WidgetError> + where + W: WidgetNew + Widget + 'static, + F: FnOnce(Option>, W::Settings) -> Result, + { + self.add_widget(Box::new(f(self.env.clone(), settings)?)); - fn children_mut(&mut self) -> &WidgetVec { - todo!(); + Ok(()) } } diff --git a/src/widgets/cpu.rs b/src/widgets/cpu.rs index 944d0b0..5b2c0ed 100644 --- a/src/widgets/cpu.rs +++ b/src/widgets/cpu.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::cell::{Ref, RefCell, RefMut}; use anyhow::Result; use chrono::{DateTime, Local, TimeDelta}; @@ -6,8 +6,9 @@ use serde::Deserialize; use sysinfo::{CpuRefreshKind, RefreshKind, System}; use super::{ - text::{Text, TextSettings}, - Style, Widget, WidgetData, WidgetError, WidgetNew, + icon_text::{IconText, IconTextSettings}, + text::TextSettings, + Style, Widget, WidgetData, WidgetError, WidgetList, WidgetNew, WidgetStyled, }; /// Settings of a [CPU] widget @@ -31,9 +32,10 @@ pub struct CPUSettings { /// Widget displaying current CPU status. pub struct CPU { data: RefCell, + settings: CPUSettings, + is_ready: RefCell, - icon: RefCell, - percent: RefCell, + icon_text: RefCell, sys: RefCell, @@ -47,79 +49,97 @@ impl CPU { sys.refresh_cpu_usage(); sys.global_cpu_usage().round() as usize } - - fn align(&self) { - let icon = self.icon.borrow_mut(); - let text = self.percent.borrow_mut(); - - let mut icon_data = icon.data().borrow_mut(); - let mut text_data = text.data().borrow_mut(); - let data = &mut self.data.borrow_mut(); - - icon_data.position.0 = data.position.0 + icon_data.margin.0; - icon_data.position.1 = data.position.1 + icon_data.margin.2; - text_data.position.0 = - icon_data.position.0 + icon_data.width + icon_data.margin.1 + text_data.margin.0; - text_data.position.1 = data.position.1 + text_data.margin.2; - - data.height = usize::max( - text_data.position.1 + text_data.height + text_data.margin.3, - icon_data.position.1 + icon_data.height + icon_data.margin.3, - ); - - data.width = icon_data.margin.0 - + icon_data.margin.1 - + icon_data.width - + text_data.margin.0 - + text_data.margin.1 - + text_data.width; - } } impl Widget for CPU { + fn name(&self) -> WidgetList { + WidgetList::CPU + } + + fn as_styled(&self) -> Option<&dyn WidgetStyled> { + Some(self) + } + + fn data(&self) -> Ref<'_, WidgetData> { + self.data.borrow() + } + + fn data_mut(&self) -> RefMut<'_, WidgetData> { + self.data.borrow_mut() + } + fn bind( &mut self, env: std::rc::Rc, ) -> anyhow::Result<(), WidgetError> { - self.percent.borrow_mut().bind(env.clone())?; - self.icon.borrow_mut().bind(env) + self.icon_text.borrow_mut().bind(env) + } + + fn env(&self) -> Option> { + self.icon_text.borrow().env() } fn init(&self) -> Result<(), WidgetError> { - self.icon.borrow_mut().init()?; - self.percent.borrow_mut().init()?; + self.apply_style()?; - self.align(); + self.icon_text.borrow_mut().change_text("Err"); + self.icon_text.borrow_mut().change_icon(""); + self.icon_text.borrow().init()?; Ok(()) } + fn prepare(&self) -> Result<(), WidgetError> { + { + let it = self.icon_text.borrow(); + it.prepare()?; + let mut it_data = it.data_mut(); + let mut self_data = self.data.borrow_mut(); + it_data.position = self_data.position; + self_data.width = it_data.width; + self_data.height = it_data.height; + } + + self.apply_style()?; + + *self.is_ready.borrow_mut() = true; + Ok(()) + } + fn draw(&self) -> Result<(), WidgetError> { + if self.env().is_none() { + return Err(WidgetError::DrawWithNoEnv(WidgetList::CPU)); + } + + self.draw_style()?; + let mut last_update = self.last_update.borrow_mut(); if Local::now() - *last_update >= self.update_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()); - } + if self.sys.borrow_mut().cpus().is_empty() { + self.icon_text.borrow_mut().change_icon(""); + self.icon_text.borrow_mut().change_text("ERR"); + } else { + self.icon_text + .borrow_mut() + .change_text(format!("{info}%").as_str()); } - self.align(); *last_update = Local::now(); } - self.percent.borrow_mut().draw()?; - self.icon.borrow_mut().draw() - } + { + let it = self.icon_text.borrow(); + let mut it_data = it.data_mut(); + let mut self_data = self.data.borrow_mut(); + it_data.position = self_data.position; + self_data.width = it_data.width; + self_data.height = it_data.height; + } - fn data(&self) -> &RefCell { - &self.data + self.icon_text.borrow().draw() } } @@ -135,28 +155,15 @@ impl WidgetNew for CPU { { Ok(Self { data: RefCell::new(settings.default_data), - icon: RefCell::new(Text::new( + + is_ready: RefCell::new(false), + + icon_text: RefCell::new(IconText::new( env.clone(), - TextSettings { - text: "".to_string(), - default_data: WidgetData { - margin: (0, 0, 0, 0), - ..WidgetData::default() - }, - fontid: 1, - ..settings.text_settings.clone() - }, - )?), - percent: RefCell::new(Text::new( - env, - TextSettings { - text: "Err".to_string(), - - default_data: WidgetData { - margin: (5, 0, 2, 0), - ..WidgetData::default() - }, - ..settings.text_settings.clone() + IconTextSettings { + icon_settings: settings.text_settings.clone(), + text_settings: settings.text_settings.clone(), + ..IconTextSettings::default() }, )?), @@ -168,6 +175,14 @@ impl WidgetNew for CPU { last_update: RefCell::new( chrono::Local::now() - TimeDelta::milliseconds(settings.update_rate as i64), ), + + settings, }) } } + +impl WidgetStyled for CPU { + fn style(&self) -> &Style { + &self.settings.style + } +} diff --git a/src/widgets/icon_text.rs b/src/widgets/icon_text.rs new file mode 100644 index 0000000..a8807a4 --- /dev/null +++ b/src/widgets/icon_text.rs @@ -0,0 +1,205 @@ +use std::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, +}; + +use serde::Deserialize; + +use crate::root::Environment; + +use super::{ + text::{Text, TextSettings}, + Margin, Style, Widget, WidgetData, WidgetError, WidgetList, WidgetNew, WidgetStyled, +}; + +#[derive(Default, Debug, Clone, Deserialize)] +pub struct IconTextSettings { + #[serde(default, flatten)] + pub default_data: WidgetData, + + #[serde(default)] + pub text_settings: TextSettings, + + #[serde(default)] + pub icon_settings: TextSettings, + + #[serde(default)] + pub style: Style, +} + +pub struct IconText { + data: RefCell, + env: Option>, + settings: IconTextSettings, + + icon: Text, + text: Text, + + is_ready: RefCell, +} + +impl IconText { + fn align(&self) { + let mut icon_data = self.icon.data_mut(); + let icon_style = self.icon.style(); + + let mut text_data = self.text.data_mut(); + let text_style = self.text.style(); + let data = &mut self.data.borrow_mut(); + + icon_data.position.0 = data.position.0 + icon_style.margin.left; + icon_data.position.1 = data.position.1 + icon_style.margin.up; + text_data.position.0 = icon_data.position.0 + + icon_data.width + + icon_style.margin.right + + text_style.margin.left; + text_data.position.1 = data.position.1 + text_style.margin.up; + + data.height = usize::max( + text_data.position.1 - data.position.1 + text_data.height + text_style.margin.down, + icon_data.position.1 - data.position.1 + icon_data.height + icon_style.margin.down, + ); + + data.width = icon_style.margin.left + + icon_style.margin.right + + icon_data.width + + text_style.margin.left + + text_style.margin.right + + text_data.width; + } + + pub fn change_text(&mut self, text: &str) { + self.text.change_text(text); + } + + pub fn change_icon(&mut self, text: &str) { + self.icon.change_text(text); + } +} + +impl Widget for IconText { + fn name(&self) -> WidgetList { + WidgetList::IconText + } + + fn as_styled(&self) -> Option<&dyn WidgetStyled> { + Some(self) + } + + fn data(&self) -> Ref<'_, WidgetData> { + self.data.borrow() + } + + fn data_mut(&self) -> RefMut<'_, WidgetData> { + self.data.borrow_mut() + } + + fn bind(&mut self, env: Rc) -> Result<(), WidgetError> { + self.text.bind(env.clone())?; + self.icon.bind(env.clone())?; + self.env = Some(env); + Ok(()) + } + + fn env(&self) -> Option> { + self.env.clone() + } + + fn init(&self) -> Result<(), WidgetError> { + self.icon.init()?; + self.text.init()?; + + Ok(()) + } + + fn prepare(&self) -> Result<(), WidgetError> { + self.text.prepare()?; + self.icon.prepare()?; + + self.align(); + self.apply_style()?; + *self.is_ready.borrow_mut() = true; + Ok(()) + } + + fn draw(&self) -> Result<(), WidgetError> { + if self.env().is_none() { + return Err(WidgetError::DrawWithNoEnv(WidgetList::IconText)); + } + + if !*self.is_ready.borrow() { + self.prepare()?; + } + + self.draw_style()?; + let style = self.style(); + self.icon.data_mut().position += (style.margin.left, style.margin.up); + self.icon.data_mut().position += (style.margin.left, style.margin.up); + + self.text.draw()?; + self.icon.draw() + } +} + +impl WidgetNew for IconText { + type Settings = IconTextSettings; + + fn new(env: Option>, settings: Self::Settings) -> Result + where + Self: Sized, + { + Ok(Self { + data: RefCell::new(settings.default_data), + + icon: Text::new( + env.clone(), + TextSettings { + default_data: WidgetData { + ..WidgetData::default() + }, + style: Style { + margin: Margin { + left: 2, + right: 0, + up: 0, + down: 0, + }, + ..Style::default() + }, + fontid: 1, + ..settings.text_settings.clone() + }, + )?, + + text: Text::new( + env.clone(), + TextSettings { + default_data: WidgetData { + ..WidgetData::default() + }, + style: Style { + margin: Margin { + left: 2, + right: 2, + up: 1, + down: 0, + }, + ..Style::default() + }, + ..settings.text_settings.clone() + }, + )?, + + env, + settings, + + is_ready: RefCell::new(false), + }) + } +} + +impl WidgetStyled for IconText { + fn style(&self) -> &Style { + &self.settings.style + } +} diff --git a/src/widgets/keyboard.rs b/src/widgets/keyboard.rs index 57070b7..bbf632f 100644 --- a/src/widgets/keyboard.rs +++ b/src/widgets/keyboard.rs @@ -1,14 +1,21 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{ + cell::{Ref, RefCell, RefMut}, + collections::HashMap, + rc::Rc, +}; use anyhow::Result; use serde::Deserialize; use crate::{ - root::Environment, - widgets::{text::Text, Widget}, + root::Environment, services::ServiceList, util::signals::SignalNames, widgets::Widget, }; -use super::{text::TextSettings, Style, WidgetData, WidgetError, WidgetNew}; +use super::{ + icon_text::{IconText, IconTextSettings}, + text::TextSettings, + Style, WidgetData, WidgetError, WidgetList, WidgetNew, WidgetStyled, +}; /// Settings of a [Keyboard] widget #[derive(Deserialize, Default, Debug, Clone)] @@ -31,68 +38,60 @@ pub struct KeyboardSettings { /// Widget displaying current keyboard layout. pub struct Keyboard { data: RefCell, + style: Style, + is_ready: RefCell, + layout_mappings: Rc>, - icon: RefCell, - text: Rc>, + icon_text: Rc>, env: Option>, } -impl Keyboard { - fn align(&self) { - let icon = self.icon.borrow_mut(); - let text = self.text.borrow_mut(); - - let mut icon_data = icon.data().borrow_mut(); - let mut text_data = text.data().borrow_mut(); - let data = &mut self.data.borrow_mut(); - - icon_data.position.0 = data.position.0 + icon_data.margin.0; - icon_data.position.1 = data.position.1 + icon_data.margin.2; - text_data.position.0 = - icon_data.position.0 + icon_data.width + icon_data.margin.1 + text_data.margin.0; - text_data.position.1 = data.position.1 + text_data.margin.2; - - data.height = usize::max( - text_data.position.1 + text_data.height + text_data.margin.3, - icon_data.position.1 + icon_data.height + icon_data.margin.3, - ); - - data.width = icon_data.margin.0 - + icon_data.margin.1 - + icon_data.width - + text_data.margin.0 - + text_data.margin.1 - + text_data.width; +impl Widget for Keyboard { + fn name(&self) -> WidgetList { + WidgetList::Keyboard + } + + fn as_styled(&self) -> Option<&dyn WidgetStyled> { + Some(self) + } + + fn data(&self) -> Ref<'_, WidgetData> { + self.data.borrow() + } + + fn data_mut(&self) -> RefMut<'_, WidgetData> { + self.data.borrow_mut() } -} -impl Widget for Keyboard { fn bind(&mut self, env: Rc) -> Result<(), WidgetError> { self.env = Some(env.clone()); - self.text.borrow_mut().bind(env.clone())?; - self.icon.borrow_mut().bind(env) + self.icon_text.borrow_mut().bind(env) + } + + fn env(&self) -> Option> { + self.env.clone() } fn init(&self) -> Result<(), WidgetError> { if self.env.is_none() { - return Err(WidgetError::InitWithNoEnv("Keyboard".to_string())); + return Err(WidgetError::InitWithNoEnv(WidgetList::Keyboard)); } let signals = self.env.as_ref().unwrap().signals.borrow_mut(); - if !signals.contains_key("keyboard") { + if !signals.contains_key(&SignalNames::Keyboard) { return Err(WidgetError::NoCorespondingSignal( - "Keyboard".to_string(), - "Keyboard".to_string(), + WidgetList::Keyboard, + ServiceList::Keyboard, )); } - let signal_text = Rc::clone(&self.text); + let signal_ic = Rc::clone(&self.icon_text); let layout_mappings = Rc::clone(&self.layout_mappings); - signals["keyboard"].connect(move |data| { + signals[&SignalNames::Keyboard].connect(move |data| { if let Some(text) = data.downcast_ref::() { let layout = if layout_mappings.contains_key(text) { layout_mappings.get(text).unwrap() @@ -100,27 +99,53 @@ impl Widget for Keyboard { &text.to_string() }; - signal_text.borrow_mut().change_text(layout); + signal_ic.borrow_mut().change_text(layout); } }); - self.icon.borrow_mut().init()?; - self.text.borrow_mut().init()?; + { + let mut ic = self.icon_text.borrow_mut(); + ic.change_icon("󰌌"); + ic.change_text("ERR"); + ic.init()?; + } - self.align(); + Ok(()) + } + + fn prepare(&self) -> Result<(), WidgetError> { + { + let it = self.icon_text.borrow(); + it.prepare()?; + let mut it_data = it.data_mut(); + let mut self_data = self.data.borrow_mut(); + it_data.position = self_data.position; + self_data.width = it_data.width; + self_data.height = it_data.height; + } + + self.apply_style()?; + *self.is_ready.borrow_mut() = true; Ok(()) } fn draw(&self) -> Result<(), WidgetError> { - self.align(); + if self.env.is_none() { + return Err(WidgetError::DrawWithNoEnv(WidgetList::Keyboard)); + } - self.text.borrow_mut().draw()?; - self.icon.borrow_mut().draw() - } + if !*self.is_ready.borrow() { + self.prepare()?; + } - fn data(&self) -> &RefCell { - &self.data + self.draw_style()?; + + { + let ic_data = self.icon_text.borrow(); + ic_data.data_mut().position = self.data().position; + } + self.icon_text.borrow_mut().draw() } } @@ -133,30 +158,17 @@ impl WidgetNew for Keyboard { { Ok(Keyboard { data: RefCell::new(settings.default_data), + style: settings.style, + is_ready: RefCell::new(false), + layout_mappings: Rc::new(settings.layout_mappings), - icon: RefCell::new(Text::new( + icon_text: Rc::new(RefCell::new(IconText::new( env.clone(), - TextSettings { - text: "󰌌".to_string(), - default_data: WidgetData { - margin: (0, 0, 0, 0), - ..WidgetData::default() - }, - fontid: 1, - ..settings.text_settings.clone() - }, - )?), - text: Rc::new(RefCell::new(Text::new( - env, - TextSettings { - text: String::new(), - - default_data: WidgetData { - margin: (5, 0, 2, 0), - ..WidgetData::default() - }, - ..settings.text_settings.clone() + IconTextSettings { + icon_settings: settings.text_settings.clone(), + text_settings: settings.text_settings.clone(), + ..IconTextSettings::default() }, )?)), @@ -164,3 +176,9 @@ impl WidgetNew for Keyboard { }) } } + +impl WidgetStyled for Keyboard { + fn style(&self) -> &Style { + &self.style + } +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 662937f..16e7e27 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -3,31 +3,70 @@ pub mod containers; pub mod battery; pub mod clock; pub mod cpu; +pub mod icon_text; pub mod keyboard; pub mod text; -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::{Ref, RefMut}, + fmt::Display, + ops::{Add, AddAssign}, + rc::Rc, +}; +use anyhow::Result; use serde::Deserialize; use thiserror::Error; -use crate::{processes::ProcessSettings, root::Environment, util::Color}; +use crate::{ + root::Environment, + services::{ProcessSettings, ServiceList, ServiceNew}, + 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 { + /// Get type of a curent widget + fn name(&self) -> WidgetList; + /// Bind a widget to a new environment. fn bind(&mut self, env: Rc) -> Result<(), WidgetError>; + /// Get environment bound to the widget + fn env(&self) -> Option>; + + /// Prepare current widget for a draw + fn prepare(&self) -> Result<(), WidgetError> { + todo!() + } + /// Draw an entire widget to a current environment's `Drawer` fn draw(&self) -> Result<(), WidgetError>; - /// Prepare `Widget` for a first draw + /// Prepare [Widget] for a first draw fn init(&self) -> Result<(), WidgetError>; - /// Return `WidgetData` associated to the widget - fn data(&self) -> &RefCell; + /// Return [WidgetData] associated to the widget immutably + fn data(&self) -> Ref<'_, WidgetData>; + + /// Return [WidgetData] associated to the widget mutably + fn data_mut(&self) -> RefMut<'_, WidgetData>; + + fn try_data(&self) -> Ref<'_, WidgetData> { + todo!() + } + + fn try_data_mut(&self) -> RefMut<'_, WidgetData> { + todo!() + } + + /// Runtime check if widget is styled + /// Override this function with body `Some(self)` if [WidgetStyled] implemented + fn as_styled(&self) -> Option<&dyn WidgetStyled> { + None + } } /// A `Widget` that can be unifiedly created. @@ -49,29 +88,67 @@ pub enum WidgetError { /// Argument is a name of a widget #[error("Trying to draw a widget \"{0}\" not bound to any environment")] - DrawWithNoEnv(String), + DrawWithNoEnv(WidgetList), /// Argument is a name of a widget #[error("Trying to initialise a widget \"{0}\" not bound to any environment")] - InitWithNoEnv(String), + InitWithNoEnv(WidgetList), - /// Arguments are a name of a widget and a name of coresponding process + /// Arguments are a name of a widget and a name of coresponding service #[error( "When initialising widget \"{0}\" no coresponding signal was found. - Maybe process \"{1}\" was not created?" + Maybe service \"{1}\" was not created?" )] - NoCorespondingSignal(String, String), + NoCorespondingSignal(WidgetList, ServiceList), + + #[error( + "Style initialisation is invalid in widget \"{0}\". Data can not be borrowed mutably." + )] + StyleInitDataBorrowed(WidgetList), #[error(transparent)] Custom(#[from] anyhow::Error), } +#[derive(Default, Debug, Clone, Copy, Deserialize)] +pub struct Position(pub usize, pub usize); + +impl AddAssign for Position { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + self.1 += rhs.1; + } +} + +impl AddAssign<(usize, usize)> for Position { + fn add_assign(&mut self, rhs: (usize, usize)) { + self.0 += rhs.0; + self.1 += rhs.1; + } +} + +impl Add for Position { + type Output = Position; + + fn add(self, rhs: Self) -> Self::Output { + Position(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl Add<(usize, usize)> for Position { + type Output = Position; + + fn add(self, rhs: (usize, usize)) -> Self::Output { + Position(self.0 + rhs.0, self.1 + rhs.1) + } +} + /// 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), + pub position: Position, /// Widgth of the widget should be controlled by the widget itself #[serde(default)] @@ -80,19 +157,33 @@ pub struct WidgetData { /// 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), + position: Position(0, 0), width: 0, height: 0, - margin: (0, 0, 0, 0), + } + } +} + +#[derive(Default, Debug, Clone, Copy, Deserialize)] +pub struct Margin { + pub left: usize, + pub right: usize, + pub up: usize, + pub down: usize, +} + +impl Margin { + pub const fn default() -> Self { + Self { + left: 0, + right: 0, + up: 0, + down: 0, } } } @@ -101,7 +192,13 @@ impl WidgetData { #[derive(Default, Debug, Clone, Copy, Deserialize)] pub struct Style { pub background: Option, + + /// Border of a pixel (border pixel width, color) pub border: Option<(usize, Color)>, + + /// Margin of a widget (Left, Right, Up, Down) + #[serde(default)] + pub margin: Margin, } impl Style { @@ -109,18 +206,28 @@ impl Style { Self { background: None, border: None, + margin: Margin::default(), } } } -/// `Widget` that supports common styling. +/// [Widget] that supports common styling. Already provides helper functions fot initialising and drawing styled widget. pub trait WidgetStyled: Widget { fn style(&self) -> &Style; - fn style_mut(&mut self) -> &mut Style; - + /// Helper function that you should call before alignment, since it changes dimensions + /// + ///
+ /// Before using default function make sure you understand and follow these points:
+ ///
    + ///
  • Borrows [WidgetData] via calling [Widget::data_mut()] then + /// adds border and margins to width and height;
  • + ///
  • Borrows [Style] immutably;
  • + ///
  • Should be called once after every width or height are overwritten. Otherwise width, height and position will be innacurate.
  • + ///
+ ///
fn apply_style(&self) -> Result<(), WidgetError> { - let mut data = self.data().borrow_mut(); + let mut data = self.data_mut(); let style = self.style(); let border = match style.border { @@ -130,18 +237,139 @@ pub trait WidgetStyled: Widget { data.height += border.0 * 2; + data.width += style.margin.left + style.margin.right; + data.height += style.margin.up + style.margin.down; + + Ok(()) + } + + /// Helper function that you should call in the begining of draw function. + ///
+ /// Before using default function make sure you understand and follow these points:
+ ///
    + ///
  • Borrows [WidgetData] via calling [Widget::data_mut()] then + /// adds margins to position;
  • + ///
  • Borrows [Style] immutably;
  • + ///
  • Draws the background and border, therefore should be called every draw before the main + /// logic
  • + ///
+ ///
+ fn draw_style(&self) -> Result<(), WidgetError> { + if self.env().is_none() { + return Err(WidgetError::DrawWithNoEnv(self.name())); + } + + let env = self.env().unwrap(); + let style = self.style(); + let border = style.border.unwrap_or((0, Color::NONE)); + let mut data = self.data_mut(); + + data.position.0 += style.margin.left; + data.position.1 += style.margin.up; + + let mut drawer = env.as_ref().drawer.borrow_mut(); + if let Some(color) = 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 border.1 == Color::NONE { + return Ok(()); + } + + for x in 0..border.0 { + for y in 0..data.height { + drawer.draw_pixel(&data, (x, y), border.1); + drawer.draw_pixel(&data, (data.width - 1 - x, y), border.1); + } + } + + for x in 0..data.width { + for y in 0..border.0 { + drawer.draw_pixel(&data, (x, y), border.1); + drawer.draw_pixel(&data, (x, data.height - 1 - y), border.1); + } + } + Ok(()) } } -/// All available widgets in capybar and their settings +/// All available widgets in capybar +#[derive(Debug, Clone)] +pub enum WidgetList { + Text, + IconText, + Clock, + Battery, + CPU, + Keyboard, + + Row, + Bar, + + Custom(String), +} + +impl Display for WidgetList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Text => write!(f, "Text"), + Self::IconText => write!(f, "Text"), + Self::Clock => write!(f, "Clock"), + Self::Battery => write!(f, "Battery"), + Self::CPU => write!(f, "Cpu"), + Self::Keyboard => write!(f, "Keyboard"), + + Self::Row => write!(f, "Row"), + Self::Bar => write!(f, "Bar"), + + Self::Custom(name) => write!(f, "{name}"), + } + } +} + +/// Enum of [Widget]s with their settings. #[derive(Deserialize, Debug, Clone)] #[serde(tag = "widget", content = "settings", rename_all = "snake_case")] -pub enum WidgetsList { +pub enum WidgetsSettingsList { Text(TextSettings), Clock(ClockSettings), Battery(BatterySettings), #[serde(rename = "cpu")] CPU(CPUSettings), Keyboard(keyboard::KeyboardSettings, ProcessSettings), + Custom(String), +} + +impl WidgetsSettingsList { + pub fn create_in_container( + &self, + container: &mut impl containers::ContainerSingle, + ) -> Result<(), WidgetError> { + match self { + WidgetsSettingsList::Text(settings) => { + container.create_widget(text::Text::new, settings.clone()) + } + WidgetsSettingsList::Clock(settings) => { + container.create_widget(clock::Clock::new, settings.clone()) + } + WidgetsSettingsList::Battery(settings) => { + container.create_widget(battery::Battery::new, settings.clone()) + } + WidgetsSettingsList::CPU(settings) => { + container.create_widget(cpu::CPU::new, settings.clone()) + } + WidgetsSettingsList::Keyboard(wsettings, psettings) => { + container.create_service(crate::services::clients::Keyboard::new, *psettings)?; + container.create_widget(keyboard::Keyboard::new, wsettings.clone()) + } + WidgetsSettingsList::Custom(_) => { + todo!() + } + } + } } diff --git a/src/widgets/text.rs b/src/widgets/text.rs index 474ea90..8a5c109 100644 --- a/src/widgets/text.rs +++ b/src/widgets/text.rs @@ -1,4 +1,7 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, +}; use anyhow::Result; use fontdue::layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle}; @@ -13,7 +16,7 @@ use crate::{ widgets::Widget, }; -use super::{Style, WidgetData, WidgetError, WidgetNew}; +use super::{Style, WidgetData, WidgetError, WidgetList, WidgetNew, WidgetStyled}; /// Settings of a [Text] widget #[derive(Deserialize, Debug, Clone, Default)] @@ -49,6 +52,8 @@ pub struct Text { settings: TextSettings, data: RefCell, env: Option>, + + is_ready: RefCell, } impl Text { @@ -91,6 +96,14 @@ impl Text { } impl Widget for Text { + fn name(&self) -> WidgetList { + WidgetList::Text + } + + fn as_styled(&self) -> Option<&dyn WidgetStyled> { + Some(self) + } + fn bind(&mut self, env: Rc) -> Result<(), WidgetError> { self.env = Some(env); @@ -107,6 +120,10 @@ impl Widget for Text { Ok(()) } + fn env(&self) -> Option> { + self.env.clone() + } + fn init(&self) -> Result<(), WidgetError> { self.update_width(); self.data.borrow_mut().height = self.layout.height() as usize; @@ -114,23 +131,31 @@ impl Widget for Text { Ok(()) } + fn prepare(&self) -> Result<(), WidgetError> { + self.update_width(); + self.apply_style()?; + + *self.is_ready.borrow_mut() = true; + self.data.borrow_mut().height = self.layout.height() as usize; + Ok(()) + } + fn draw(&self) -> Result<(), WidgetError> { if self.env.is_none() { - return Err(WidgetError::DrawWithNoEnv("Text".to_string())); + return Err(WidgetError::DrawWithNoEnv(WidgetList::Text)); + } + + if !*self.is_ready.borrow() { + self.prepare()?; } + *self.is_ready.borrow_mut() = false; + + self.draw_style()?; 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 { - for y in 0..data.height { - drawer.draw_pixel(data, (x, y), color); - } - } - } - for glyph in self.layout.glyphs() { drawer.draw_glyph(data, glyph, font, self.settings.font_color); } @@ -138,8 +163,12 @@ impl Widget for Text { Ok(()) } - fn data(&self) -> &RefCell { - &self.data + fn data(&self) -> Ref<'_, WidgetData> { + self.data.borrow() + } + + fn data_mut(&self) -> RefMut<'_, WidgetData> { + self.data.borrow_mut() } } @@ -166,6 +195,8 @@ impl WidgetNew for Text { data: RefCell::new(settings.default_data), settings, env: None, + + is_ready: RefCell::new(false), }; if let Some(e) = env { @@ -174,3 +205,9 @@ impl WidgetNew for Text { Ok(text) } } + +impl WidgetStyled for Text { + fn style(&self) -> &Style { + &self.settings.style + } +}