From 8b182435adb64d1e1d0356eb13a115f754c14837 Mon Sep 17 00:00:00 2001 From: pepengu Date: Fri, 18 Jul 2025 19:56:13 +0300 Subject: [PATCH 1/4] style: confusing naming changed --- flake.lock | 8 ++--- src/lib.rs | 2 +- src/root.rs | 22 +++++--------- .../clients/hyprland/keyboard.rs | 28 ++++++++--------- .../clients/hyprland/mod.rs | 0 src/{processes => services}/clients/mod.rs | 4 +-- src/{processes => services}/mod.rs | 30 +++++++++---------- src/widgets/mod.rs | 2 +- 8 files changed, 45 insertions(+), 51 deletions(-) rename src/{processes => services}/clients/hyprland/keyboard.rs (78%) rename src/{processes => services}/clients/hyprland/mod.rs (100%) rename src/{processes => services}/clients/mod.rs (86%) rename src/{processes => services}/mod.rs (56%) 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/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/root.rs b/src/root.rs index e305daf..bc0f8ef 100644 --- a/src/root.rs +++ b/src/root.rs @@ -39,7 +39,7 @@ use wayland_client::{ use crate::{ config::Config, - processes::{clients, Process, ProcessError, ProcessNew}, + services::{clients, Service, ServiceError, ServiceNew}, util::{ fonts::{self, FontsError}, signals::Signal, @@ -82,7 +82,7 @@ pub struct Root { pointer: Option, widgets: Vec>, - processes: Vec>, + 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,7 +180,6 @@ 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) { @@ -376,7 +371,7 @@ impl Root { pointer: None, widgets: Vec::new(), - processes: Vec::new(), + services: Vec::new(), env: None, }; @@ -444,7 +439,7 @@ impl Root { signals: RefCell::new(HashMap::new()), })); - for process in &mut self.processes { + for process in &mut self.services { process.bind(Rc::clone(self.env.as_ref().unwrap()))?; process.init()?; @@ -526,11 +521,10 @@ impl Root { pub fn create_process(&mut self, f: F, settings: W::Settings) -> Result<()> where - W: ProcessNew + Process + 'static, - F: FnOnce(Option>, W::Settings) -> Result, + W: ServiceNew + Service + '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,7 +533,7 @@ impl Root { return Err(RootError::EnvironmentNotInit.into()); } - for process in &mut self.processes { + for process in &mut self.services { process.run()?; } diff --git a/src/processes/clients/hyprland/keyboard.rs b/src/services/clients/hyprland/keyboard.rs similarity index 78% rename from src/processes/clients/hyprland/keyboard.rs rename to src/services/clients/hyprland/keyboard.rs index 2244edb..f4159ff 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, + services::{clients::KeyboardTrait, ProcessSettings, Service, ServiceError, ServiceNew}, util::signals::Signal, }; -/// 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,22 +42,22 @@ 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)); 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(); @@ -71,9 +71,9 @@ impl Process for Keyboard { 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(); @@ -94,13 +94,13 @@ impl Process for Keyboard { } } -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/processes/mod.rs b/src/services/mod.rs similarity index 56% rename from src/processes/mod.rs rename to src/services/mod.rs index 153b018..5666761 100644 --- a/src/processes/mod.rs +++ b/src/services/mod.rs @@ -22,36 +22,36 @@ pub struct ProcessSettings { pub update_rate: i64, } -/// A **data structure** that can be used as a widget inside a capybar. -pub trait Process { +/// 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<(), ProcessError>; + fn bind(&mut self, env: Rc) -> Result<(), ServiceError>; - /// Prepare `Process` for a first run - fn init(&self) -> Result<(), ProcessError>; + /// Prepare [Service] for a first run + fn init(&self) -> Result<(), ServiceError>; - /// Run the process - fn run(&self) -> Result<(), ProcessError>; + /// Run the [Service] + fn run(&self) -> Result<(), ServiceError>; } -/// A `Process` that can be unifiedly created. +/// A [Service] that can be unifiedly created. /// -/// Implementing this trait allows creating `Process` and binding the environment without +/// Implementing this trait allows creating [Service] and binding the environment without /// intermidiate steps. Simplifies process creation inside of scripts. -pub trait ProcessNew { +pub trait ServiceNew { type Settings; - fn new(env: Option>, settings: Self::Settings) -> Result + 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")] +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 widget \"{0}\": \n \"{1}\"")] + #[error("Custom error occured in service \"{0}\": \n \"{1}\"")] Custom(String, anyhow::Error), } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 662937f..c79ae38 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -11,7 +11,7 @@ use std::{cell::RefCell, rc::Rc}; use serde::Deserialize; use thiserror::Error; -use crate::{processes::ProcessSettings, root::Environment, util::Color}; +use crate::{root::Environment, services::ProcessSettings, util::Color}; use {battery::BatterySettings, clock::ClockSettings, cpu::CPUSettings, text::TextSettings}; From b289e6e5d9266513483bd9589d466f3ad07bb176 Mon Sep 17 00:00:00 2001 From: pepengu Date: Sun, 20 Jul 2025 20:11:30 +0300 Subject: [PATCH 2/4] style: refactor containers --- examples/basic/main.rs | 18 +- examples/toml_config/main.rs | 4 +- src/main.rs | 4 +- src/root.rs | 128 +++++-------- src/services/clients/hyprland/keyboard.rs | 15 +- src/services/mod.rs | 4 +- src/util/signals.rs | 8 +- src/widgets/containers/bar.rs | 174 ++++++++++------- src/widgets/containers/mod.rs | 77 ++------ src/widgets/containers/row.rs | 222 +++++++++++----------- src/widgets/keyboard.rs | 5 +- src/widgets/mod.rs | 35 +++- 12 files changed, 351 insertions(+), 343 deletions(-) diff --git a/examples/basic/main.rs b/examples/basic/main.rs index 690c3c3..acb045f 100644 --- a/examples/basic/main.rs +++ b/examples/basic/main.rs @@ -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 { @@ -104,9 +96,15 @@ fn main() -> Result<(), Box> { }, )?; - 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/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/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/root.rs b/src/root.rs index bc0f8ef..29f3e70 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, - services::{clients, Service, ServiceError, ServiceNew}, + 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,7 +81,7 @@ pub struct Root { keyboard_focus: bool, pointer: Option, - widgets: Vec>, + bar: Option, services: Vec>, env: Option>, } @@ -182,6 +182,7 @@ impl LayerShellHandler for Root { if self.first_configure { self.first_configure = false; + if let Err(a) = self.draw(qh) { println!("{a}"); } @@ -341,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 = @@ -370,7 +375,7 @@ impl Root { keyboard_focus: false, pointer: None, - widgets: Vec::new(), + bar, services: Vec::new(), env: None, }; @@ -379,54 +384,32 @@ impl 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); @@ -439,24 +422,17 @@ impl Root { signals: RefCell::new(HashMap::new()), })); - for process in &mut self.services { - 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().borrow_mut().height as u32); for output in self.output_state().outputs() { let info = self @@ -485,7 +461,8 @@ 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)?; @@ -499,27 +476,7 @@ impl Root { fonts::add_font_by_name(name) } - pub fn add_widget(&mut self, mut widget: W) -> Result<()> - where - W: Widget + 'static, - { - 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<()> + pub fn create_service(&mut self, f: F, settings: W::Settings) -> Result<()> where W: ServiceNew + Service + 'static, F: FnOnce(Option>, W::Settings) -> Result, @@ -533,17 +490,16 @@ impl Root { return Err(RootError::EnvironmentNotInit.into()); } - for process in &mut self.services { - process.run()?; + for service in &mut self.services { + service.run()?; } 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 @@ -560,6 +516,10 @@ impl Root { self.flag = false; Ok(()) } + + pub fn bar(&self) -> &Option { + &self.bar + } } delegate_compositor!(Root); diff --git a/src/services/clients/hyprland/keyboard.rs b/src/services/clients/hyprland/keyboard.rs index f4159ff..6875872 100644 --- a/src/services/clients/hyprland/keyboard.rs +++ b/src/services/clients/hyprland/keyboard.rs @@ -7,7 +7,7 @@ use hyprland::{data::Devices, shared::HyprData}; use crate::{ root::Environment, services::{clients::KeyboardTrait, ProcessSettings, Service, ServiceError, ServiceNew}, - util::signals::Signal, + util::signals::SignalNames, }; /// Service that tracks current keyboard layout @@ -52,6 +52,11 @@ impl Keyboard { 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(()) } @@ -61,12 +66,10 @@ impl Service for Keyboard { } 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(()) } @@ -87,7 +90,7 @@ impl Service 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(()) diff --git a/src/services/mod.rs b/src/services/mod.rs index 5666761..75e93a2 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,4 +1,4 @@ -//! Current module describes all of capybars included processes as well as their common behaviour. +//! 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) @@ -37,7 +37,7 @@ pub trait Service { /// A [Service] that can be unifiedly created. /// /// Implementing this trait allows creating [Service] and binding the environment without -/// intermidiate steps. Simplifies process creation inside of scripts. +/// intermidiate steps. Simplifies service creation inside of scripts. pub trait ServiceNew { type Settings; 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/containers/bar.rs b/src/widgets/containers/bar.rs index 2e30d41..28d38ae 100644 --- a/src/widgets/containers/bar.rs +++ b/src/widgets/containers/bar.rs @@ -5,6 +5,7 @@ use serde::Deserialize; use crate::{ root::Environment, + services::Service, widgets::{Style, Widget, WidgetData, WidgetError, WidgetNew}, }; @@ -47,66 +48,90 @@ 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(()) } -} -impl Widget for Bar { - fn bind( - &mut self, - env: std::rc::Rc, - ) -> anyhow::Result<(), WidgetError> { - 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.env = Some(env); - Ok(()) + pub fn left(&mut self) -> &mut RefCell { + &mut self.left } - fn draw(&self) -> anyhow::Result<(), WidgetError> { - if self.env.is_none() { - return Err(WidgetError::DrawWithNoEnv("Bar".to_string())); - } + 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 data = self.data.borrow(); + 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().borrow_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().borrow_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().borrow_mut(); + + rd.position.0 = data.position.0 + data.width - border.0; + rd.position.1 = data.position.1 + border.0; + + Ok(()) + } + + pub fn draw_border(&self) { let data = self.data.borrow_mut(); let border = match self.settings.style.border { @@ -114,59 +139,62 @@ impl Widget for Bar { 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); - } + 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 mut ld = left.data().borrow_mut(); +impl Widget for Bar { + fn bind( + &mut self, + env: std::rc::Rc, + ) -> anyhow::Result<(), WidgetError> { + self.left.borrow_mut().bind(Rc::clone(&env))?; + self.center.borrow_mut().bind(Rc::clone(&env))?; + self.right.borrow_mut().bind(Rc::clone(&env))?; - ld.position.0 = data.position.0 + border.0; - ld.position.1 = data.position.1 + border.0; + for service in self.services.borrow_mut().iter_mut() { + if let Err(e) = service.bind(Rc::clone(&env)) { + return Err(WidgetError::Custom(e.into())); + } } - left.draw()?; - - let center = self.center.borrow_mut(); - { - let mut cd = center.data().borrow_mut(); + self.env = Some(env); + 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("Bar".to_string())); } - center.draw()?; - let right = self.right.borrow_mut(); - { - let mut rd = right.data().borrow_mut(); + self.draw_border(); - rd.position.0 = data.position.0 + data.width - border.0; - rd.position.1 = data.position.1 + border.0; - } - right.draw()?; + self.align_widgets()?; + self.left.borrow_mut().draw()?; + self.center.borrow_mut().draw()?; + self.right.borrow_mut().draw()?; Ok(()) } @@ -239,6 +267,7 @@ impl WidgetNew for Bar { ..RowSettings::default() }, )?), + services: RefCell::new(Vec::new()), settings, env, @@ -247,15 +276,24 @@ impl WidgetNew for Bar { } impl Container for Bar { - fn align_children(&self) -> anyhow::Result<()> { - todo!(); - } - - fn children(&self) -> &super::WidgetVec { - todo!(); + 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..9b706cc 100644 --- a/src/widgets/containers/row.rs +++ b/src/widgets/containers/row.rs @@ -5,11 +5,12 @@ use thiserror::Error; use crate::{ root::Environment, + services::Service, util::Color, widgets::{Widget, WidgetData, WidgetError, WidgetNew}, }; -use super::{Container, WidgetVec}; +use super::{Container, ContainerSingle}; #[derive(Debug, Clone, Copy)] pub enum Alignment { @@ -43,7 +44,7 @@ pub struct RowSettings { #[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,18 +56,24 @@ pub struct Row { settings: RowSettings, data: RefCell, - children: RefCell, + widgets: RefCell>>, env: Option>, + services: RefCell>>, } impl Widget for Row { 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(()) @@ -74,19 +81,18 @@ impl Widget for Row { 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 { 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().borrow_mut(); 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 + widget_data.margin.3 + border, ); } Ok(()) @@ -97,12 +103,12 @@ impl Widget for Row { return Err(WidgetError::DrawWithNoEnv("Row".to_string())); } - self.align_children()?; + self.align_widgets()?; - let children = self.children.borrow_mut(); + let widgets = self.widgets.borrow_mut(); let mut data = self.data.borrow_mut(); - if children.is_empty() { + if widgets.is_empty() { data.height = self.settings.border.unwrap_or((5, Color::NONE)).0 * 3; } @@ -138,7 +144,7 @@ impl Widget for Row { } } - for widget in children.widgets() { + for widget in widgets.iter() { widget.draw()?; } @@ -151,51 +157,39 @@ impl Widget for Row { } 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(); - } - - pub fn add_child(&mut self, child: Box) -> Result<(), RowError> { - self.children.get_mut().widgets_mut().push(child); - - Ok(()) + self.widgets.get_mut().pop(); } - 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().borrow_mut()) { + res = usize::max(res, widget.height + widget.position.1 + widget.margin.3); } 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 { @@ -203,25 +197,24 @@ impl Row { 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(); + let mut widget = widgets[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; + widget.position.0 = data.position.0 + (data.width - border * 2 - widget.width) / 2; + widget.position.1 = data.position.1 + border + widget.margin.2; } - 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() { + for widget in widgets.iter_mut() { total_width += { - let data = child.data().borrow_mut(); + let data = widget.data().borrow_mut(); data.width + data.margin.0 + data.margin.1 } } @@ -230,48 +223,44 @@ impl Row { 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().borrow_mut(); - child.position.0 = x + child.margin.0; - child.position.1 = data.position.1 + child.margin.2; + widget.position.0 = x + widget.margin.0; + widget.position.1 = data.position.1 + widget.margin.2; - x += child.margin.0 + child.width + child.margin.1 + dist; + x += widget.margin.0 + widget.width + widget.margin.1 + 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().borrow_mut()) { + data.width += widget.margin.0 + widget.width + widget.margin.1 + 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 { @@ -279,14 +268,11 @@ impl Row { 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; + for mut widget in widgets.iter_mut().map(|a| a.data().borrow_mut()) { + widget.position.1 = data.position.1 + widget.margin.2; + widget.position.0 = offset + widget.margin.0; + offset += widget.margin.0 + widget.width + widget.margin.1 + padding; } data.width = offset - padding + border; @@ -294,8 +280,8 @@ impl Row { 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 { @@ -303,19 +289,40 @@ impl Row { None => 0, }; - let children = children.widgets_mut(); - 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; + for mut widget in widgets.iter_mut().map(|a| a.data().borrow_mut()) { + widget.position.1 = data.position.1 + widget.margin.2; + widget.position.0 = offset - widget.width - widget.margin.1; + offset -= widget.margin.0 + widget.width + widget.margin.1 + padding; } data.width = data.position.0 - (offset - padding + border); Ok(()) } + + fn align_widgets(&self) -> Result<()> { + if self.widgets.borrow_mut().is_empty() { + return Ok(()); + } + + 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(()) + } } impl WidgetNew for Row { @@ -328,40 +335,41 @@ 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()), }) } } 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/keyboard.rs b/src/widgets/keyboard.rs index 57070b7..6b1f9a5 100644 --- a/src/widgets/keyboard.rs +++ b/src/widgets/keyboard.rs @@ -5,6 +5,7 @@ use serde::Deserialize; use crate::{ root::Environment, + util::signals::SignalNames, widgets::{text::Text, Widget}, }; @@ -82,7 +83,7 @@ impl Widget for 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(), @@ -92,7 +93,7 @@ impl Widget for Keyboard { let signal_text = Rc::clone(&self.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() diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index c79ae38..c52f188 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -8,10 +8,15 @@ pub mod text; use std::{cell::RefCell, rc::Rc}; +use anyhow::Result; use serde::Deserialize; use thiserror::Error; -use crate::{root::Environment, services::ProcessSettings, util::Color}; +use crate::{ + root::Environment, + services::{ProcessSettings, ServiceNew}, + util::Color, +}; use {battery::BatterySettings, clock::ClockSettings, cpu::CPUSettings, text::TextSettings}; @@ -55,10 +60,10 @@ pub enum WidgetError { #[error("Trying to initialise a widget \"{0}\" not bound to any environment")] InitWithNoEnv(String), - /// 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), @@ -145,3 +150,27 @@ pub enum WidgetsList { CPU(CPUSettings), Keyboard(keyboard::KeyboardSettings, ProcessSettings), } + +impl WidgetsList { + pub fn create_in_container( + &self, + container: &mut impl containers::ContainerSingle, + ) -> Result<(), WidgetError> { + match self { + WidgetsList::Text(settings) => { + container.create_widget(text::Text::new, settings.clone()) + } + WidgetsList::Clock(settings) => { + container.create_widget(clock::Clock::new, settings.clone()) + } + WidgetsList::Battery(settings) => { + container.create_widget(battery::Battery::new, settings.clone()) + } + WidgetsList::CPU(settings) => container.create_widget(cpu::CPU::new, settings.clone()), + WidgetsList::Keyboard(wsettings, psettings) => { + container.create_service(crate::services::clients::Keyboard::new, *psettings)?; + container.create_widget(keyboard::Keyboard::new, wsettings.clone()) + } + } + } +} From 57031b49b0f3d9552d70e08ad368d197b80acbf5 Mon Sep 17 00:00:00 2001 From: pepengu Date: Mon, 4 Aug 2025 20:03:57 +0300 Subject: [PATCH 3/4] feat: widget styling implemented --- Cargo.lock | 2 +- Cargo.toml | 2 +- examples/basic/main.rs | 28 +++- examples/toml_config/config.toml | 34 +++-- src/config/widgets/bar.rs | 8 +- src/root.rs | 27 +++- src/services/mod.rs | 18 ++- src/widgets/battery.rs | 146 +++++++++--------- src/widgets/clock.rs | 85 +++++++++-- src/widgets/containers/bar.rs | 118 +++++++------- src/widgets/containers/row.rs | 205 +++++++++++++++---------- src/widgets/cpu.rs | 159 ++++++++++--------- src/widgets/icon_text.rs | 205 +++++++++++++++++++++++++ src/widgets/keyboard.rs | 161 +++++++++++--------- src/widgets/mod.rs | 253 +++++++++++++++++++++++++++---- src/widgets/text.rs | 63 ++++++-- 16 files changed, 1084 insertions(+), 430 deletions(-) create mode 100644 src/widgets/icon_text.rs 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/examples/basic/main.rs b/examples/basic/main.rs index acb045f..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}; @@ -49,7 +49,7 @@ fn main() -> Result<(), Box> { )?; // Left widgets - bar.create_child_left( + bar.create_widget_left( CPU::new, CPUSettings { update_rate: 1000, @@ -60,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, @@ -79,7 +87,7 @@ fn main() -> Result<(), Box> { )?; // Right widgets - bar.create_child_right( + bar.create_widget_right( Battery::new, BatterySettings { text_settings: TextSettings { @@ -89,9 +97,17 @@ 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() }, )?; diff --git a/examples/toml_config/config.toml b/examples/toml_config/config.toml index f8d02d6..7afcf6a 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,4,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/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/root.rs b/src/root.rs index 29f3e70..3eaa12c 100644 --- a/src/root.rs +++ b/src/root.rs @@ -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), @@ -380,7 +380,7 @@ impl Root { env: None, }; - Ok(bar) + Ok(root) } pub fn apply_config(&mut self, config: Config) -> Result<()> { @@ -432,7 +432,7 @@ impl Root { bar.bind(Rc::clone(self.env.as_ref().unwrap()))?; bar.init()?; - self.height = max(self.height, bar.data().borrow_mut().height as u32); + self.height = max(self.height, bar.data_mut().height as u32); for output in self.output_state().outputs() { let info = self @@ -465,8 +465,8 @@ impl Root { self.init()?; loop { - event_queue.blocking_dispatch(self)?; thread::sleep(Duration::from_millis(100)); + event_queue.blocking_dispatch(self)?; } //Ok(self) @@ -494,6 +494,25 @@ impl Root { 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); diff --git a/src/services/mod.rs b/src/services/mod.rs index 75e93a2..51d39c2 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -5,7 +5,7 @@ pub mod clients; -use std::rc::Rc; +use std::{fmt::Display, rc::Rc}; use serde::Deserialize; use thiserror::Error; @@ -55,3 +55,19 @@ pub enum ServiceError { #[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/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 28d38ae..1b2ee32 100644 --- a/src/widgets/containers/bar.rs +++ b/src/widgets/containers/bar.rs @@ -1,4 +1,7 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, +}; use anyhow::Result; use serde::Deserialize; @@ -6,7 +9,7 @@ use serde::Deserialize; use crate::{ root::Environment, services::Service, - widgets::{Style, Widget, WidgetData, WidgetError, WidgetNew}, + widgets::{Style, Widget, WidgetData, WidgetError, WidgetList, WidgetNew, WidgetStyled}, }; use super::{ @@ -25,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, } @@ -34,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(), } } @@ -104,69 +119,53 @@ impl Bar { } fn align_widgets(&self) -> anyhow::Result<()> { - let data = self.data.borrow(); + 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().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().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().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(()) } +} - pub fn draw_border(&self) { - let data = self.data.borrow_mut(); - - let border = match self.settings.style.border { - Some(a) => (a.0, Some(a.1)), - None => (0, None), - }; +impl Widget for Bar { + fn name(&self) -> WidgetList { + WidgetList::Bar + } - 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); - } - } - } + fn as_styled(&self) -> Option<&dyn WidgetStyled> { + Some(self) + } - 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); - } - } + fn data(&self) -> Ref<'_, WidgetData> { + self.data.borrow() + } - 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 data_mut(&self) -> RefMut<'_, WidgetData> { + self.data.borrow_mut() } -} -impl Widget for Bar { fn bind( &mut self, env: std::rc::Rc, @@ -184,14 +183,28 @@ impl Widget for Bar { Ok(()) } + fn env(&self) -> Option> { + self.env.clone() + } + + fn prepare(&self) -> Result<(), WidgetError> { + self.left.borrow().prepare()?; + self.center.borrow().prepare()?; + self.right.borrow().prepare()?; + + self.align_widgets()?; + self.apply_style()?; + + Ok(()) + } + fn draw(&self) -> anyhow::Result<(), WidgetError> { if self.env.is_none() { - return Err(WidgetError::DrawWithNoEnv("Bar".to_string())); + return Err(WidgetError::DrawWithNoEnv(WidgetList::Bar)); } - self.draw_border(); + self.draw_style()?; - self.align_widgets()?; self.left.borrow_mut().draw()?; self.center.borrow_mut().draw()?; self.right.borrow_mut().draw()?; @@ -206,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)) @@ -225,10 +239,6 @@ impl Widget for Bar { Ok(()) } - - fn data(&self) -> &RefCell { - &self.data - } } impl WidgetNew for Bar { @@ -248,7 +258,7 @@ impl WidgetNew for Bar { env.clone(), RowSettings { alignment: Alignment::GrowthHorizontalRight(settings.padding.0), - ..RowSettings::default() + ..settings.left_settings }, )?), @@ -256,7 +266,7 @@ impl WidgetNew for Bar { env.clone(), RowSettings { alignment: Alignment::GrowthCenteringHorizontalRight(settings.padding.1), - ..RowSettings::default() + ..settings.center_settings }, )?), @@ -264,7 +274,7 @@ impl WidgetNew for Bar { env.clone(), RowSettings { alignment: Alignment::GrowthHorizontalLeft(settings.padding.2), - ..RowSettings::default() + ..settings.right_settings }, )?), services: RefCell::new(Vec::new()), @@ -275,6 +285,12 @@ impl WidgetNew for Bar { } } +impl WidgetStyled for Bar { + fn style(&self) -> &Style { + &self.settings.style + } +} + impl Container for Bar { fn create_service(&mut self, f: F, settings: W::Settings) -> Result<()> where diff --git a/src/widgets/containers/row.rs b/src/widgets/containers/row.rs index 9b706cc..bfcddbe 100644 --- a/src/widgets/containers/row.rs +++ b/src/widgets/containers/row.rs @@ -1,18 +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, ContainerSingle}; -#[derive(Debug, Clone, Copy)] +#[derive(Deserialize, Debug, Clone, Copy)] +#[serde(tag = "type", content = "padding")] pub enum Alignment { CenteringHorizontal, CenteringVertical, @@ -32,14 +37,32 @@ 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)] @@ -59,9 +82,27 @@ pub struct Row { 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)); @@ -79,9 +120,14 @@ impl Widget for Row { Ok(()) } + fn env(&self) -> Option> { + self.env.clone() + } + fn init(&self) -> Result<(), WidgetError> { let mut data = self.data.borrow_mut(); - let border = match self.settings.border { + + let border = match self.settings.style.border { Some(a) => a.0, None => 0, }; @@ -89,71 +135,49 @@ impl Widget for Row { let widgets = self.widgets.borrow(); for widget in widgets.iter() { widget.init()?; - let widget_data = widget.data().borrow_mut(); + let widget_data = widget.data(); data.height = usize::max( data.height, - widget_data.height + widget_data.position.1 + widget_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_widgets()?; + self.apply_style()?; - let widgets = self.widgets.borrow_mut(); - let mut data = self.data.borrow_mut(); + *self.is_ready.borrow_mut() = true; + Ok(()) + } - if widgets.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 widgets.iter() { + self.draw_style()?; + + for widget in self.widgets.borrow_mut().iter() { widget.draw()?; } Ok(()) } - - fn data(&self) -> &RefCell { - &self.data - } } impl Row { @@ -183,8 +207,8 @@ impl Row { } let mut res = 0; - for widget in widgets.iter_mut().map(|a| a.data().borrow_mut()) { - res = usize::max(res, widget.height + widget.position.1 + widget.margin.3); + for widget in widgets.iter_mut().map(|a| a.data()) { + res = usize::max(res, widget.height + widget.position.1); } res } @@ -192,7 +216,7 @@ impl Row { 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, }; @@ -201,10 +225,15 @@ impl Row { if widgets.len() == 1 { { - let mut widget = widgets[0].data().borrow_mut(); - - widget.position.0 = data.position.0 + (data.width - border * 2 - widget.width) / 2; - widget.position.1 = data.position.1 + border + widget.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(&mut widgets) + border; @@ -213,10 +242,7 @@ impl Row { let mut total_width = 0; for widget in widgets.iter_mut() { - total_width += { - let data = widget.data().borrow_mut(); - data.width + data.margin.0 + data.margin.1 - } + total_width += widget.data_mut().width; } if total_width > data.width - 2 * border { @@ -227,12 +253,12 @@ impl Row { let mut x = data.position.0 + border; for widget in widgets.iter_mut() { - let mut widget = widget.data().borrow_mut(); + let mut widget = widget.data_mut(); - widget.position.0 = x + widget.margin.0; - widget.position.1 = data.position.1 + widget.margin.2; + widget.position.0 = x; + widget.position.1 = data.position.1; - x += widget.margin.0 + widget.width + widget.margin.1 + dist; + x += widget.width + dist; } data.height = Row::get_max_height(&mut widgets) + border; @@ -247,8 +273,8 @@ impl Row { data.width = 0; - for widget in widgets.iter_mut().map(|a| a.data().borrow_mut()) { - data.width += widget.margin.0 + widget.width + widget.margin.1 + padding; + for widget in widgets.iter_mut().map(|a| a.data_mut()) { + data.width += widget.width + padding; } data.width -= padding; @@ -263,19 +289,22 @@ impl Row { 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 mut offset = border + data.position.0; - for mut widget in widgets.iter_mut().map(|a| a.data().borrow_mut()) { - widget.position.1 = data.position.1 + widget.margin.2; - widget.position.0 = offset + widget.margin.0; - offset += widget.margin.0 + widget.width + widget.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(()) } @@ -284,25 +313,32 @@ impl Row { 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 mut offset = data.position.0 - border; - for mut widget in widgets.iter_mut().map(|a| a.data().borrow_mut()) { - widget.position.1 = data.position.1 + widget.margin.2; - widget.position.0 = offset - widget.width - widget.margin.1; - offset -= widget.margin.0 + widget.width + widget.margin.1 + padding; + 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.width = data.position.0 - (offset - padding + border); + data.position.0 -= data.width; Ok(()) } 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(()); } @@ -337,10 +373,17 @@ impl WidgetNew for Row { env, 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 create_service(&mut self, f: F, settings: W::Settings) -> Result<()> where 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 6b1f9a5..bbf632f 100644 --- a/src/widgets/keyboard.rs +++ b/src/widgets/keyboard.rs @@ -1,15 +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, - util::signals::SignalNames, - 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)] @@ -32,65 +38,57 @@ 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(&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[&SignalNames::Keyboard].connect(move |data| { @@ -101,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() } } @@ -134,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() }, )?)), @@ -165,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 c52f188..16e7e27 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -3,10 +3,16 @@ 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; @@ -14,7 +20,7 @@ use thiserror::Error; use crate::{ root::Environment, - services::{ProcessSettings, ServiceNew}, + services::{ProcessSettings, ServiceList, ServiceNew}, util::Color, }; @@ -22,17 +28,45 @@ use {battery::BatterySettings, clock::ClockSettings, cpu::CPUSettings, text::Tex /// 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. @@ -54,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 service #[error( "When initialising widget \"{0}\" no coresponding signal was found. 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)] @@ -85,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, } } } @@ -106,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 { @@ -114,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 { @@ -135,42 +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 +#[derive(Debug, Clone)] +pub enum WidgetList { + Text, + IconText, + Clock, + Battery, + CPU, + Keyboard, + + Row, + Bar, + + Custom(String), } -/// All available widgets in capybar and their settings +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 WidgetsList { +impl WidgetsSettingsList { pub fn create_in_container( &self, container: &mut impl containers::ContainerSingle, ) -> Result<(), WidgetError> { match self { - WidgetsList::Text(settings) => { + WidgetsSettingsList::Text(settings) => { container.create_widget(text::Text::new, settings.clone()) } - WidgetsList::Clock(settings) => { + WidgetsSettingsList::Clock(settings) => { container.create_widget(clock::Clock::new, settings.clone()) } - WidgetsList::Battery(settings) => { + WidgetsSettingsList::Battery(settings) => { container.create_widget(battery::Battery::new, settings.clone()) } - WidgetsList::CPU(settings) => container.create_widget(cpu::CPU::new, settings.clone()), - WidgetsList::Keyboard(wsettings, psettings) => { + 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 + } +} From 305719ed3db9d33d14f51545539ddbfee23c73f8 Mon Sep 17 00:00:00 2001 From: pepengu Date: Mon, 4 Aug 2025 21:01:04 +0300 Subject: [PATCH 4/4] docs: README updated --- README.md | 10 +++++++++- examples/toml_config/bar.png | Bin 6403 -> 11001 bytes examples/toml_config/config.toml | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) 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/toml_config/bar.png b/examples/toml_config/bar.png index 89872a92237bd3787e7525444c4a29cb9697ba79..7fbb0faa5b041af83f5f608a4223ad75f814ae59 100644 GIT binary patch literal 11001 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 E06{Lo&;S4c literal 6403 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 diff --git a/examples/toml_config/config.toml b/examples/toml_config/config.toml index 7afcf6a..f625ff8 100644 --- a/examples/toml_config/config.toml +++ b/examples/toml_config/config.toml @@ -15,7 +15,7 @@ [bar.settings.left_settings] margin = [3,0,3,3] [bar.settings.right_settings] - margin = [0,4,3,3] + margin = [0,6,3,3] [[bar.left]] widget = "keyboard"