use std::cmp; use chrono::prelude::*; use hyprland::data::Workspaces; use hyprland::event_listener; use hyprland::shared::{HyprData, WorkspaceType}; use iced::font::Weight; use iced::widget::{container, horizontal_space, row, text, Text}; use iced::{ time, Background, Border, Color, Element, Event, Font, Length, Shadow, Subscription, Task, Theme, Vector, }; use iced_layershell::build_pattern::{application, MainSettings}; use iced_layershell::reexport::{Anchor, KeyboardInteractivity}; use iced_layershell::settings::LayerShellSettings; use iced_layershell::to_layer_message; use iced_runtime::futures::event; use sysinfo::{Disks, System}; use tracing::level_filters::LevelFilter; use tracing::{debug, error}; use tracing_subscriber::EnvFilter; fn main() -> iced_layershell::Result { let timer = tracing_subscriber::fmt::time::ChronoLocal::new("%Y-%m-%d_%I:%M:%S%.6f %P".to_owned()); let filter = EnvFilter::builder() .with_default_directive(LevelFilter::WARN.into()) .parse_lossy("shelly=debug"); tracing_subscriber::FmtSubscriber::builder() .pretty() .with_line_number(true) .with_level(true) .with_target(true) .with_env_filter(filter) .with_target(true) .with_timer(timer) .init(); debug!("Starting shelly"); let mut font = Font::with_name("VictorMono Nerd Font"); font.weight = Weight::Bold; let settings = MainSettings { layer_settings: LayerShellSettings { size: Some((1400, 60)), anchor: Anchor::Bottom | Anchor::Left | Anchor::Right, margin: (0, 0, 0, 0), exclusive_zone: 60, keyboard_interactivity: KeyboardInteractivity::None, ..Default::default() }, default_font: font, // antialiasing: true, ..Default::default() }; application(Panel::namespace, Panel::update, Panel::view) .subscription(Panel::subscription) .subscription(|_| { time::every(time::Duration::from_millis(1000)).map(|_| Message::Time(Local::now())) }) .settings(settings) .style(Panel::style) .run_with(Panel::new) } #[derive(Debug)] struct Panel { time: String, cpu: String, memory: String, battery: String, disk: String, system: System, disks: Disks, workspaces: Workspaces, active_workspace: i32, apps: Vec, } #[to_layer_message] #[derive(Debug, Clone)] enum Message { Launch(usize), Time(DateTime), IcedEvent(Event), WorkspaceChange(WorkspaceType), } impl Panel { fn new() -> (Self, Task) { let workspaces = Workspaces::get().map_err(|e| { error!("Couldn't get hyprland info: {}", e); e }); ( Self { system: System::new_all(), disks: Disks::new_with_refreshed_list(), workspaces: workspaces.unwrap(), active_workspace: 1, time: String::new(), cpu: String::new(), memory: String::new(), battery: String::new(), disk: String::new(), apps: vec![String::new()], }, Task::none(), ) } fn namespace(&self) -> String { String::from("panel") } fn update(&mut self, message: Message) -> Task { match message { Message::Launch(index) => {} Message::Time(instant) => { self.system.refresh_memory(); self.system.refresh_cpu_usage(); // disk.refresh(); // let used_space = disk.available_space() - disk.total_space(); let disk = self.disks.first_mut(); if let Some(disk) = disk { self.disk = format!( "󰋊 {}", convert((disk.total_space() - disk.available_space()) as f64) ); }; // let used_space = 393; // self.disk = format!("󰋊 {}", used_space); self.cpu = format!(" {}%", self.system.global_cpu_usage().round()); let memory = ((self.system.used_memory() as f64 / self.system.total_memory() as f64) .fract() * 100.0) .trunc(); self.memory = format!(" {}%", memory); self.time = instant.format("%a %b %d, %I:%M %p").to_string(); } Message::AnchorChange(anchor) => todo!(), Message::AnchorSizeChange(anchor, _) => todo!(), Message::LayerChange(layer) => todo!(), Message::MarginChange(_) => todo!(), Message::SizeChange(_) => todo!(), Message::VirtualKeyboardPressed { time, key } => todo!(), Message::IcedEvent(event) => { debug!(?event) } _ => unreachable!(), Message::WorkspaceChange(workspace_type) => todo!(), } Task::none() } fn subscription(&self) -> Subscription { event::listen().map(Message::IcedEvent) } // fn hyprland_subscription(&self) -> Subscription { // let mut event_listener = event_listener::EventListener::new(); // event_listener.add_workspace_changed_handler(|x| Message::WorkspaceChange(x)); // Subscription::run(|| event_listener.start_listener()) // } fn view(&self) -> Element { let workspaces: Vec> = self .workspaces .iter() .map(|w| { text!("{}", { match w.id { 1 => "".to_owned(), 2 => "".to_owned(), 3 => "󰈙".to_owned(), 4 => "󰭹".to_owned(), 5 => "".to_owned(), 6 => "".to_owned(), 7 => "󰕧".to_owned(), 8 => "󰭹".to_owned(), 9 => "".to_owned(), _ => w.name.clone(), } }) .color({ if w.id == self.active_workspace { Color::parse("#ff6ac1").unwrap() } else { Color::parse("#57c7ff").unwrap() } }) .into() }) .collect(); let workspaces = row(workspaces).spacing(20).padding([0, 5]); let disk = text!("{}", self.disk).color(Color::parse("#ff9f43").unwrap()); let mem = text!("{}", self.memory).color(Color::parse("#f3f99d").unwrap()); let cpu = text!("{}", self.cpu).color(Color::parse("#57c7ff").unwrap()); let clock = text!("{}", self.time); let clock = clock.color(Color::parse("#5af78e").unwrap()); let row = row!( container(workspaces), container(horizontal_space()), container(clock), container(row!(horizontal_space(), disk, cpu, mem).spacing(5)) ) .spacing(5) .padding([0, 20]); let mut shadow = Shadow::default(); shadow.color = Color::BLACK; shadow.offset = Vector::new(4.5, 4.5); shadow.blur_radius = 18.5; container( container(row) .style(move |t: &iced::Theme| { container::rounded_box(t) .border(Border::default().rounded(20)) .background( Background::Color(Color::parse("#282a36").unwrap()).scale_alpha(0.95), ) .shadow(shadow) }) .center(Length::Fill), ) .padding([15, 20]) .center(Length::Fill) .style(move |t: &iced::Theme| { container::rounded_box(t) .border(Border::default().rounded(20)) .background(Background::Color(Color::TRANSPARENT)) }) .into() } fn style(&self, theme: &iced::Theme) -> iced_layershell::Appearance { use iced_layershell::Appearance; Appearance { background_color: Color::TRANSPARENT, text_color: theme.palette().text, } } } pub fn convert(num: f64) -> String { let negative = if num.is_sign_positive() { "" } else { "-" }; let num = num.abs(); let units = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; if num < 1_f64 { return format!("{}{} {}", negative, num, "B"); } let delimiter = 1000_f64; let exponent = cmp::min( (num.ln() / delimiter.ln()).floor() as i32, (units.len() - 1) as i32, ); let pretty_bytes = format!("{:.2}", num / delimiter.powi(exponent)) .parse::() .unwrap() * 1_f64; let unit = units[exponent as usize]; format!("{}{} {}", negative, pretty_bytes, unit) }