From f4bbee0a2ed9f2e6fa7cfc9d87f64eea550764d0 Mon Sep 17 00:00:00 2001 From: Csaba Date: Sun, 16 Mar 2025 21:54:30 +0100 Subject: [PATCH] feat(bar): auto select/hide widget based on value This commit adds new settings to some widgets that allows to auto select/hide them based on their current values. The cpu/memory/network/storage widgets get a setting that auto selects the widget if the current value/percentage is over a value. The battery widget gets a setting that auto selects the widget if the current percentage is under a value. The storage widget gets a setting that auto hides the disk widget if the percentage is under a value. Also added 2 new settings (auto_select_fill and auto_select_text) to the theme, in order to select the fill and text colors of an auto selected widget. (Easter egg: the network icons change if the value is over the limit) PR: #1353 --- komorebi-bar/src/bar.rs | 149 ++++++---- komorebi-bar/src/config.rs | 12 + komorebi-bar/src/main.rs | 4 + komorebi-bar/src/render.rs | 15 ++ komorebi-bar/src/selected_frame.rs | 31 ++- komorebi-bar/src/widgets/battery.rs | 57 ++-- komorebi-bar/src/widgets/cpu.rs | 39 ++- komorebi-bar/src/widgets/memory.rs | 42 ++- komorebi-bar/src/widgets/network.rs | 340 +++++++++++++++++------ komorebi-bar/src/widgets/storage.rs | 64 +++-- schema.bar.json | 404 +++++++++++++++++++++++++--- 11 files changed, 915 insertions(+), 242 deletions(-) diff --git a/komorebi-bar/src/bar.rs b/komorebi-bar/src/bar.rs index e639ead7..ecf62a84 100644 --- a/komorebi-bar/src/bar.rs +++ b/komorebi-bar/src/bar.rs @@ -14,6 +14,8 @@ use crate::widgets::komorebi::KomorebiNotificationState; use crate::widgets::widget::BarWidget; use crate::widgets::widget::WidgetConfig; use crate::KomorebiEvent; +use crate::AUTO_SELECT_FILL_COLOUR; +use crate::AUTO_SELECT_TEXT_COLOUR; use crate::BAR_HEIGHT; use crate::DEFAULT_PADDING; use crate::MAX_LABEL_WIDTH; @@ -43,6 +45,7 @@ use eframe::egui::Vec2; use eframe::egui::Visuals; use font_loader::system_fonts; use font_loader::system_fonts::FontPropertyBuilder; +use komorebi_client::Colour; use komorebi_client::KomorebiTheme; use komorebi_client::MonitorNotification; use komorebi_client::NotificationEvent; @@ -88,71 +91,82 @@ pub fn apply_theme( grouping: Option, render_config: Rc>, ) { - match theme { + let (auto_select_fill, auto_select_text) = match theme { KomobarTheme::Catppuccin { name: catppuccin, accent: catppuccin_value, - } => match catppuccin { - Catppuccin::Frappe => { - catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE); - let catppuccin_value = catppuccin_value.unwrap_or_default(); - let accent = catppuccin_value.color32(catppuccin.as_theme()); + auto_select_fill: catppuccin_auto_select_fill, + auto_select_text: catppuccin_auto_select_text, + } => { + match catppuccin { + Catppuccin::Frappe => { + catppuccin_egui::set_theme(ctx, catppuccin_egui::FRAPPE); + let catppuccin_value = catppuccin_value.unwrap_or_default(); + let accent = catppuccin_value.color32(catppuccin.as_theme()); - ctx.style_mut(|style| { - style.visuals.selection.stroke.color = accent; - style.visuals.widgets.hovered.fg_stroke.color = accent; - style.visuals.widgets.active.fg_stroke.color = accent; - style.visuals.override_text_color = None; - }); + ctx.style_mut(|style| { + style.visuals.selection.stroke.color = accent; + style.visuals.widgets.hovered.fg_stroke.color = accent; + style.visuals.widgets.active.fg_stroke.color = accent; + style.visuals.override_text_color = None; + }); - bg_color.replace(catppuccin_egui::FRAPPE.base); + bg_color.replace(catppuccin_egui::FRAPPE.base); + } + Catppuccin::Latte => { + catppuccin_egui::set_theme(ctx, catppuccin_egui::LATTE); + let catppuccin_value = catppuccin_value.unwrap_or_default(); + let accent = catppuccin_value.color32(catppuccin.as_theme()); + + ctx.style_mut(|style| { + style.visuals.selection.stroke.color = accent; + style.visuals.widgets.hovered.fg_stroke.color = accent; + style.visuals.widgets.active.fg_stroke.color = accent; + style.visuals.override_text_color = None; + }); + + bg_color.replace(catppuccin_egui::LATTE.base); + } + Catppuccin::Macchiato => { + catppuccin_egui::set_theme(ctx, catppuccin_egui::MACCHIATO); + let catppuccin_value = catppuccin_value.unwrap_or_default(); + let accent = catppuccin_value.color32(catppuccin.as_theme()); + + ctx.style_mut(|style| { + style.visuals.selection.stroke.color = accent; + style.visuals.widgets.hovered.fg_stroke.color = accent; + style.visuals.widgets.active.fg_stroke.color = accent; + style.visuals.override_text_color = None; + }); + + bg_color.replace(catppuccin_egui::MACCHIATO.base); + } + Catppuccin::Mocha => { + catppuccin_egui::set_theme(ctx, catppuccin_egui::MOCHA); + let catppuccin_value = catppuccin_value.unwrap_or_default(); + let accent = catppuccin_value.color32(catppuccin.as_theme()); + + ctx.style_mut(|style| { + style.visuals.selection.stroke.color = accent; + style.visuals.widgets.hovered.fg_stroke.color = accent; + style.visuals.widgets.active.fg_stroke.color = accent; + style.visuals.override_text_color = None; + }); + + bg_color.replace(catppuccin_egui::MOCHA.base); + } } - Catppuccin::Latte => { - catppuccin_egui::set_theme(ctx, catppuccin_egui::LATTE); - let catppuccin_value = catppuccin_value.unwrap_or_default(); - let accent = catppuccin_value.color32(catppuccin.as_theme()); - ctx.style_mut(|style| { - style.visuals.selection.stroke.color = accent; - style.visuals.widgets.hovered.fg_stroke.color = accent; - style.visuals.widgets.active.fg_stroke.color = accent; - style.visuals.override_text_color = None; - }); - - bg_color.replace(catppuccin_egui::LATTE.base); - } - Catppuccin::Macchiato => { - catppuccin_egui::set_theme(ctx, catppuccin_egui::MACCHIATO); - let catppuccin_value = catppuccin_value.unwrap_or_default(); - let accent = catppuccin_value.color32(catppuccin.as_theme()); - - ctx.style_mut(|style| { - style.visuals.selection.stroke.color = accent; - style.visuals.widgets.hovered.fg_stroke.color = accent; - style.visuals.widgets.active.fg_stroke.color = accent; - style.visuals.override_text_color = None; - }); - - bg_color.replace(catppuccin_egui::MACCHIATO.base); - } - Catppuccin::Mocha => { - catppuccin_egui::set_theme(ctx, catppuccin_egui::MOCHA); - let catppuccin_value = catppuccin_value.unwrap_or_default(); - let accent = catppuccin_value.color32(catppuccin.as_theme()); - - ctx.style_mut(|style| { - style.visuals.selection.stroke.color = accent; - style.visuals.widgets.hovered.fg_stroke.color = accent; - style.visuals.widgets.active.fg_stroke.color = accent; - style.visuals.override_text_color = None; - }); - - bg_color.replace(catppuccin_egui::MOCHA.base); - } - }, + ( + catppuccin_auto_select_fill.map(|c| c.color32(catppuccin.as_theme())), + catppuccin_auto_select_text.map(|c| c.color32(catppuccin.as_theme())), + ) + } KomobarTheme::Base16 { name: base16, accent: base16_value, + auto_select_fill: base16_auto_select_fill, + auto_select_text: base16_auto_select_text, } => { ctx.set_style(base16.style()); let base16_value = base16_value.unwrap_or_default(); @@ -165,15 +179,22 @@ pub fn apply_theme( }); bg_color.replace(base16.background()); + + ( + base16_auto_select_fill.map(|c| c.color32(Base16Wrapper::Base16(base16))), + base16_auto_select_text.map(|c| c.color32(Base16Wrapper::Base16(base16))), + ) } KomobarTheme::Custom { colours, accent: base16_value, + auto_select_fill: base16_auto_select_fill, + auto_select_text: base16_auto_select_text, } => { let background = colours.background(); ctx.set_style(colours.style()); let base16_value = base16_value.unwrap_or_default(); - let accent = base16_value.color32(Base16Wrapper::Custom(colours)); + let accent = base16_value.color32(Base16Wrapper::Custom(colours.clone())); ctx.style_mut(|style| { style.visuals.selection.stroke.color = accent; @@ -182,8 +203,22 @@ pub fn apply_theme( }); bg_color.replace(background); + + ( + base16_auto_select_fill.map(|c| c.color32(Base16Wrapper::Custom(colours.clone()))), + base16_auto_select_text.map(|c| c.color32(Base16Wrapper::Custom(colours.clone()))), + ) } - } + }; + + AUTO_SELECT_FILL_COLOUR.store( + auto_select_fill.map_or(0, |c| Colour::from(c).into()), + Ordering::SeqCst, + ); + AUTO_SELECT_TEXT_COLOUR.store( + auto_select_text.map_or(0, |c| Colour::from(c).into()), + Ordering::SeqCst, + ); // Apply transparency_alpha let theme_color = *bg_color.borrow(); diff --git a/komorebi-bar/src/config.rs b/komorebi-bar/src/config.rs index b362a9f1..e5ca29e2 100644 --- a/komorebi-bar/src/config.rs +++ b/komorebi-bar/src/config.rs @@ -382,18 +382,24 @@ pub enum KomobarTheme { /// Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin) name: komorebi_themes::Catppuccin, accent: Option, + auto_select_fill: Option, + auto_select_text: Option, }, /// A theme from base16-egui-themes Base16 { /// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/) name: komorebi_themes::Base16, accent: Option, + auto_select_fill: Option, + auto_select_text: Option, }, /// A custom Base16 theme Custom { /// Colours of the custom Base16 theme palette colours: Box, accent: Option, + auto_select_fill: Option, + auto_select_text: Option, }, } @@ -405,12 +411,16 @@ impl From for KomobarTheme { } => Self::Catppuccin { name, accent: bar_accent, + auto_select_fill: None, + auto_select_text: None, }, KomorebiTheme::Base16 { name, bar_accent, .. } => Self::Base16 { name, accent: bar_accent, + auto_select_fill: None, + auto_select_text: None, }, KomorebiTheme::Custom { colours, @@ -419,6 +429,8 @@ impl From for KomobarTheme { } => Self::Custom { colours, accent: bar_accent, + auto_select_fill: None, + auto_select_text: None, }, } } diff --git a/komorebi-bar/src/main.rs b/komorebi-bar/src/main.rs index 0f17495f..6a2bcf1c 100644 --- a/komorebi-bar/src/main.rs +++ b/komorebi-bar/src/main.rs @@ -23,6 +23,7 @@ use std::io::BufReader; use std::io::Read; use std::path::PathBuf; use std::sync::atomic::AtomicI32; +use std::sync::atomic::AtomicU32; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use std::sync::LazyLock; @@ -47,6 +48,9 @@ pub static MONITOR_INDEX: AtomicUsize = AtomicUsize::new(0); pub static BAR_HEIGHT: f32 = 50.0; pub static DEFAULT_PADDING: f32 = 10.0; +pub static AUTO_SELECT_FILL_COLOUR: AtomicU32 = AtomicU32::new(0); +pub static AUTO_SELECT_TEXT_COLOUR: AtomicU32 = AtomicU32::new(0); + pub static ICON_CACHE: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); diff --git a/komorebi-bar/src/render.rs b/komorebi-bar/src/render.rs index ae323e93..dda15336 100644 --- a/komorebi-bar/src/render.rs +++ b/komorebi-bar/src/render.rs @@ -1,6 +1,8 @@ use crate::bar::Alignment; use crate::config::KomobarConfig; use crate::config::MonitorConfigOrIndex; +use crate::AUTO_SELECT_FILL_COLOUR; +use crate::AUTO_SELECT_TEXT_COLOUR; use eframe::egui::Color32; use eframe::egui::Context; use eframe::egui::CornerRadius; @@ -11,8 +13,11 @@ use eframe::egui::Margin; use eframe::egui::Shadow; use eframe::egui::TextStyle; use eframe::egui::Ui; +use komorebi_client::Colour; +use komorebi_client::Rgb; use serde::Deserialize; use serde::Serialize; +use std::num::NonZeroU32; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -55,6 +60,10 @@ pub struct RenderConfig { pub icon_font_id: FontId, /// Show all icons on the workspace section of the Komorebi widget pub show_all_icons: bool, + /// Background color of the selected frame + pub auto_select_fill: Option, + /// Text color of the selected frame + pub auto_select_text: Option, } pub trait RenderExt { @@ -108,6 +117,10 @@ impl RenderExt for &KomobarConfig { text_font_id, icon_font_id, show_all_icons, + auto_select_fill: NonZeroU32::new(AUTO_SELECT_FILL_COLOUR.load(Ordering::SeqCst)) + .map(|c| Colour::Rgb(Rgb::from(c.get())).into()), + auto_select_text: NonZeroU32::new(AUTO_SELECT_TEXT_COLOUR.load(Ordering::SeqCst)) + .map(|c| Colour::Rgb(Rgb::from(c.get())).into()), } } } @@ -133,6 +146,8 @@ impl RenderConfig { text_font_id: FontId::default(), icon_font_id: FontId::default(), show_all_icons: false, + auto_select_fill: None, + auto_select_text: None, } } diff --git a/komorebi-bar/src/selected_frame.rs b/komorebi-bar/src/selected_frame.rs index 359564e4..6a9347e1 100644 --- a/komorebi-bar/src/selected_frame.rs +++ b/komorebi-bar/src/selected_frame.rs @@ -10,15 +10,29 @@ use eframe::egui::Ui; /// Same as SelectableLabel, but supports all content pub struct SelectableFrame { selected: bool, + selected_fill: Option, } impl SelectableFrame { pub fn new(selected: bool) -> Self { - Self { selected } + Self { + selected, + selected_fill: None, + } + } + + pub fn new_auto(selected: bool, selected_fill: Option) -> Self { + Self { + selected, + selected_fill, + } } pub fn show(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Response { - let Self { selected } = self; + let Self { + selected, + selected_fill, + } = self; Frame::NONE .show(ui, |ui| { @@ -32,7 +46,16 @@ impl SelectableFrame { ); // since the stroke is drawn inside the frame, we always reserve space for it - if response.hovered() || response.highlighted() || response.has_focus() { + if selected && response.hovered() { + let visuals = ui.style().interact_selectable(&response, selected); + + Frame::NONE + .stroke(Stroke::new(1.0, visuals.bg_stroke.color)) + .corner_radius(visuals.corner_radius) + .fill(selected_fill.unwrap_or(visuals.bg_fill)) + .inner_margin(inner_margin) + .show(ui, add_contents); + } else if response.hovered() || response.highlighted() || response.has_focus() { let visuals = ui.style().interact_selectable(&response, selected); Frame::NONE @@ -47,7 +70,7 @@ impl SelectableFrame { Frame::NONE .stroke(Stroke::new(1.0, visuals.bg_fill)) .corner_radius(visuals.corner_radius) - .fill(visuals.bg_fill) + .fill(selected_fill.unwrap_or(visuals.bg_fill)) .inner_margin(inner_margin) .show(ui, add_contents); } else { diff --git a/komorebi-bar/src/widgets/battery.rs b/komorebi-bar/src/widgets/battery.rs index 84a74751..50e31f06 100644 --- a/komorebi-bar/src/widgets/battery.rs +++ b/komorebi-bar/src/widgets/battery.rs @@ -28,6 +28,8 @@ pub struct BatteryConfig { pub data_refresh_interval: Option, /// Display label prefix pub label_prefix: Option, + /// Select when the current percentage is under this value [[1-100]] + pub auto_select_under: Option, } impl From for Battery { @@ -38,9 +40,10 @@ impl From for Battery { enable: value.enable, hide_on_full_charge: value.hide_on_full_charge.unwrap_or(false), manager: Manager::new().unwrap(), - last_state: String::new(), + last_state: None, data_refresh_interval, label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon), + auto_select_under: value.auto_select_under.map(|u| u.clamp(1, 100)), state: BatteryState::Discharging, last_updated: Instant::now() .checked_sub(Duration::from_secs(data_refresh_interval)) @@ -54,6 +57,12 @@ pub enum BatteryState { Discharging, } +#[derive(Clone, Debug)] +struct BatteryOutput { + label: String, + selected: bool, +} + pub struct Battery { pub enable: bool, hide_on_full_charge: bool, @@ -61,24 +70,25 @@ pub struct Battery { pub state: BatteryState, data_refresh_interval: u64, label_prefix: LabelPrefix, - last_state: String, + auto_select_under: Option, + last_state: Option, last_updated: Instant, } impl Battery { - fn output(&mut self) -> String { + fn output(&mut self) -> Option { let mut output = self.last_state.clone(); let now = Instant::now(); if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) { - output.clear(); + output = None; if let Ok(mut batteries) = self.manager.batteries() { if let Some(Ok(first)) = batteries.nth(0) { - let percentage = first.state_of_charge().get::(); + let percentage = first.state_of_charge().get::() as u8; - if percentage == 100.0 && self.hide_on_full_charge { - output = String::new() + if percentage == 100 && self.hide_on_full_charge { + output = None } else { match first.state() { State::Charging => self.state = BatteryState::Charging, @@ -86,12 +96,19 @@ impl Battery { _ => {} } - output = match self.label_prefix { - LabelPrefix::Text | LabelPrefix::IconAndText => { - format!("BAT: {percentage:.0}%") - } - LabelPrefix::None | LabelPrefix::Icon => format!("{percentage:.0}%"), - } + let selected = self.auto_select_under.is_some_and(|u| percentage <= u); + + output = Some(BatteryOutput { + label: match self.label_prefix { + LabelPrefix::Text | LabelPrefix::IconAndText => { + format!("BAT: {percentage}%") + } + LabelPrefix::None | LabelPrefix::Icon => { + format!("{percentage}%") + } + }, + selected, + }) } } } @@ -108,35 +125,39 @@ impl BarWidget for Battery { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) { if self.enable { let output = self.output(); - if !output.is_empty() { + if let Some(output) = output { let emoji = match self.state { BatteryState::Charging => egui_phosphor::regular::BATTERY_CHARGING, BatteryState::Discharging => egui_phosphor::regular::BATTERY_FULL, }; + let auto_text_color = config.auto_select_text.filter(|_| output.selected); + let mut layout_job = LayoutJob::simple( match self.label_prefix { LabelPrefix::Icon | LabelPrefix::IconAndText => emoji.to_string(), LabelPrefix::None | LabelPrefix::Text => String::new(), }, config.icon_font_id.clone(), - ctx.style().visuals.selection.stroke.color, + auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color), 100.0, ); layout_job.append( - &output, + &output.label, 10.0, TextFormat { font_id: config.text_font_id.clone(), - color: ctx.style().visuals.text_color(), + color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()), valign: Align::Center, ..Default::default() }, ); + let auto_focus_fill = config.auto_select_fill; + config.apply_on_widget(false, ui, |ui| { - if SelectableFrame::new(false) + if SelectableFrame::new_auto(output.selected, auto_focus_fill) .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false))) .clicked() { diff --git a/komorebi-bar/src/widgets/cpu.rs b/komorebi-bar/src/widgets/cpu.rs index b3160819..c7bca328 100644 --- a/komorebi-bar/src/widgets/cpu.rs +++ b/komorebi-bar/src/widgets/cpu.rs @@ -25,6 +25,8 @@ pub struct CpuConfig { pub data_refresh_interval: Option, /// Display label prefix pub label_prefix: Option, + /// Select when the current percentage is over this value [[1-100]] + pub auto_select_over: Option, } impl From for Cpu { @@ -38,6 +40,7 @@ impl From for Cpu { ), data_refresh_interval, label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText), + auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)), last_updated: Instant::now() .checked_sub(Duration::from_secs(data_refresh_interval)) .unwrap(), @@ -45,26 +48,38 @@ impl From for Cpu { } } +#[derive(Clone, Debug)] +struct CpuOutput { + label: String, + selected: bool, +} + pub struct Cpu { pub enable: bool, system: System, data_refresh_interval: u64, label_prefix: LabelPrefix, + auto_select_over: Option, last_updated: Instant, } impl Cpu { - fn output(&mut self) -> String { + fn output(&mut self) -> CpuOutput { let now = Instant::now(); if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) { self.system.refresh_cpu_usage(); self.last_updated = now; } - let used = self.system.global_cpu_usage(); - match self.label_prefix { - LabelPrefix::Text | LabelPrefix::IconAndText => format!("CPU: {:.0}%", used), - LabelPrefix::None | LabelPrefix::Icon => format!("{:.0}%", used), + let used = self.system.global_cpu_usage() as u8; + let selected = self.auto_select_over.is_some_and(|o| used >= o); + + CpuOutput { + label: match self.label_prefix { + LabelPrefix::Text | LabelPrefix::IconAndText => format!("CPU: {}%", used), + LabelPrefix::None | LabelPrefix::Icon => format!("{}%", used), + }, + selected, } } } @@ -73,7 +88,9 @@ impl BarWidget for Cpu { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) { if self.enable { let output = self.output(); - if !output.is_empty() { + if !output.label.is_empty() { + let auto_text_color = config.auto_select_text.filter(|_| output.selected); + let mut layout_job = LayoutJob::simple( match self.label_prefix { LabelPrefix::Icon | LabelPrefix::IconAndText => { @@ -82,23 +99,25 @@ impl BarWidget for Cpu { LabelPrefix::None | LabelPrefix::Text => String::new(), }, config.icon_font_id.clone(), - ctx.style().visuals.selection.stroke.color, + auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color), 100.0, ); layout_job.append( - &output, + &output.label, 10.0, TextFormat { font_id: config.text_font_id.clone(), - color: ctx.style().visuals.text_color(), + color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()), valign: Align::Center, ..Default::default() }, ); + let auto_focus_fill = config.auto_select_fill; + config.apply_on_widget(false, ui, |ui| { - if SelectableFrame::new(false) + if SelectableFrame::new_auto(output.selected, auto_focus_fill) .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false))) .clicked() { diff --git a/komorebi-bar/src/widgets/memory.rs b/komorebi-bar/src/widgets/memory.rs index 7466f696..62d42132 100644 --- a/komorebi-bar/src/widgets/memory.rs +++ b/komorebi-bar/src/widgets/memory.rs @@ -25,6 +25,8 @@ pub struct MemoryConfig { pub data_refresh_interval: Option, /// Display label prefix pub label_prefix: Option, + /// Select when the current percentage is over this value [[1-100]] + pub auto_select_over: Option, } impl From for Memory { @@ -38,6 +40,7 @@ impl From for Memory { ), data_refresh_interval, label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText), + auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)), last_updated: Instant::now() .checked_sub(Duration::from_secs(data_refresh_interval)) .unwrap(), @@ -45,16 +48,23 @@ impl From for Memory { } } +#[derive(Clone, Debug)] +struct MemoryOutput { + label: String, + selected: bool, +} + pub struct Memory { pub enable: bool, system: System, data_refresh_interval: u64, label_prefix: LabelPrefix, + auto_select_over: Option, last_updated: Instant, } impl Memory { - fn output(&mut self) -> String { + fn output(&mut self) -> MemoryOutput { let now = Instant::now(); if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) { self.system.refresh_memory(); @@ -63,11 +73,17 @@ impl Memory { let used = self.system.used_memory(); let total = self.system.total_memory(); - match self.label_prefix { - LabelPrefix::Text | LabelPrefix::IconAndText => { - format!("RAM: {}%", (used * 100) / total) - } - LabelPrefix::None | LabelPrefix::Icon => format!("{}%", (used * 100) / total), + let usage = ((used * 100) / total) as u8; + let selected = self.auto_select_over.is_some_and(|o| usage >= o); + + MemoryOutput { + label: match self.label_prefix { + LabelPrefix::Text | LabelPrefix::IconAndText => { + format!("RAM: {}%", usage) + } + LabelPrefix::None | LabelPrefix::Icon => format!("{}%", usage), + }, + selected, } } } @@ -76,7 +92,9 @@ impl BarWidget for Memory { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) { if self.enable { let output = self.output(); - if !output.is_empty() { + if !output.label.is_empty() { + let auto_text_color = config.auto_select_text.filter(|_| output.selected); + let mut layout_job = LayoutJob::simple( match self.label_prefix { LabelPrefix::Icon | LabelPrefix::IconAndText => { @@ -85,23 +103,25 @@ impl BarWidget for Memory { LabelPrefix::None | LabelPrefix::Text => String::new(), }, config.icon_font_id.clone(), - ctx.style().visuals.selection.stroke.color, + auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color), 100.0, ); layout_job.append( - &output, + &output.label, 10.0, TextFormat { font_id: config.text_font_id.clone(), - color: ctx.style().visuals.text_color(), + color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()), valign: Align::Center, ..Default::default() }, ); + let auto_focus_fill = config.auto_select_fill; + config.apply_on_widget(false, ui, |ui| { - if SelectableFrame::new(false) + if SelectableFrame::new_auto(output.selected, auto_focus_fill) .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false))) .clicked() { diff --git a/komorebi-bar/src/widgets/network.rs b/komorebi-bar/src/widgets/network.rs index b7132cd2..6f98c64e 100644 --- a/komorebi-bar/src/widgets/network.rs +++ b/komorebi-bar/src/widgets/network.rs @@ -1,9 +1,11 @@ +use crate::bar::Alignment; use crate::config::LabelPrefix; use crate::render::RenderConfig; use crate::selected_frame::SelectableFrame; use crate::widgets::widget::BarWidget; use eframe::egui::text::LayoutJob; use eframe::egui::Align; +use eframe::egui::Color32; use eframe::egui::Context; use eframe::egui::Label; use eframe::egui::TextFormat; @@ -22,18 +24,36 @@ use sysinfo::Networks; pub struct NetworkConfig { /// Enable the Network widget pub enable: bool, - /// Show total data transmitted - pub show_total_data_transmitted: bool, - /// Show network activity - pub show_network_activity: bool, + /// Show total received and transmitted activity + #[serde(alias = "show_total_data_transmitted")] + pub show_total_activity: bool, + /// Show received and transmitted activity + #[serde(alias = "show_network_activity")] + pub show_activity: bool, /// Show default interface pub show_default_interface: Option, - /// Characters to reserve for network activity data - pub network_activity_fill_characters: Option, + /// Characters to reserve for received and transmitted activity + #[serde(alias = "network_activity_fill_characters")] + pub activity_left_padding: Option, /// Data refresh interval (default: 10 seconds) pub data_refresh_interval: Option, /// Display label prefix pub label_prefix: Option, + /// Select when the value is over a limit (1MiB is 1048576 bytes (1024*1024)) + pub auto_select: Option, +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct NetworkSelectConfig { + /// Select the total received data when it's over this value + pub total_received_over: Option, + /// Select the total transmitted data when it's over this value + pub total_transmitted_over: Option, + /// Select the received data when it's over this value + pub received_over: Option, + /// Select the transmitted data when it's over this value + pub transmitted_over: Option, } impl From for Network { @@ -42,16 +62,15 @@ impl From for Network { Self { enable: value.enable, - show_total_activity: value.show_total_data_transmitted, - show_activity: value.show_network_activity, + show_total_activity: value.show_total_activity, + show_activity: value.show_activity, show_default_interface: value.show_default_interface.unwrap_or(true), networks_network_activity: Networks::new_with_refreshed_list(), default_interface: String::new(), data_refresh_interval, label_prefix: value.label_prefix.unwrap_or(LabelPrefix::Icon), - network_activity_fill_characters: value - .network_activity_fill_characters - .unwrap_or_default(), + auto_select: value.auto_select, + activity_left_padding: value.activity_left_padding.unwrap_or_default(), last_state_total_activity: vec![], last_state_activity: vec![], last_updated_network_activity: Instant::now() @@ -69,11 +88,12 @@ pub struct Network { networks_network_activity: Networks, data_refresh_interval: u64, label_prefix: LabelPrefix, + auto_select: Option, default_interface: String, last_state_total_activity: Vec, last_state_activity: Vec, last_updated_network_activity: Instant, - network_activity_fill_characters: usize, + activity_left_padding: usize, } impl Network { @@ -105,24 +125,32 @@ impl Network { for (interface_name, data) in &self.networks_network_activity { if friendly_name.eq(interface_name) { if self.show_activity { + let received = Self::to_pretty_bytes( + data.received(), + self.data_refresh_interval, + ); + let transmitted = Self::to_pretty_bytes( + data.transmitted(), + self.data_refresh_interval, + ); + activity.push(NetworkReading::new( NetworkReadingFormat::Speed, - Self::to_pretty_bytes( - data.received(), - self.data_refresh_interval, - ), - Self::to_pretty_bytes( - data.transmitted(), - self.data_refresh_interval, - ), + ReadingValue::from(received), + ReadingValue::from(transmitted), )); } if self.show_total_activity { + let total_received = + Self::to_pretty_bytes(data.total_received(), 1); + let total_transmitted = + Self::to_pretty_bytes(data.total_transmitted(), 1); + total_activity.push(NetworkReading::new( NetworkReadingFormat::Total, - Self::to_pretty_bytes(data.total_received(), 1), - Self::to_pretty_bytes(data.total_transmitted(), 1), + ReadingValue::from(total_received), + ReadingValue::from(total_transmitted), )) } } @@ -138,105 +166,121 @@ impl Network { (activity, total_activity) } - fn reading_to_label( + fn reading_to_labels( &self, + select_received: bool, + select_transmitted: bool, ctx: &Context, - reading: NetworkReading, + reading: &NetworkReading, config: RenderConfig, - ) -> Label { + ) -> (Label, Label) { let (text_down, text_up) = match self.label_prefix { LabelPrefix::None | LabelPrefix::Icon => match reading.format { NetworkReadingFormat::Speed => ( format!( "{: >width$}/s ", - reading.received_text, - width = self.network_activity_fill_characters + reading.received.pretty, + width = self.activity_left_padding ), format!( "{: >width$}/s", - reading.transmitted_text, - width = self.network_activity_fill_characters + reading.transmitted.pretty, + width = self.activity_left_padding ), ), NetworkReadingFormat::Total => ( - format!("{} ", reading.received_text), - reading.transmitted_text, + format!("{} ", reading.received.pretty), + reading.transmitted.pretty.clone(), ), }, LabelPrefix::Text | LabelPrefix::IconAndText => match reading.format { NetworkReadingFormat::Speed => ( format!( "DOWN: {: >width$}/s ", - reading.received_text, - width = self.network_activity_fill_characters + reading.received.pretty, + width = self.activity_left_padding ), format!( "UP: {: >width$}/s", - reading.transmitted_text, - width = self.network_activity_fill_characters + reading.transmitted.pretty, + width = self.activity_left_padding ), ), NetworkReadingFormat::Total => ( - format!("\u{2211}DOWN: {}/s ", reading.received_text), - format!("\u{2211}UP: {}/s", reading.transmitted_text), + format!("\u{2211}DOWN: {}/s ", reading.received.pretty), + format!("\u{2211}UP: {}/s", reading.transmitted.pretty), ), }, }; - let icon_format = TextFormat::simple( - config.icon_font_id.clone(), - ctx.style().visuals.selection.stroke.color, - ); - let text_format = TextFormat { - font_id: config.text_font_id.clone(), - color: ctx.style().visuals.text_color(), - valign: Align::Center, - ..Default::default() - }; + let auto_text_color_received = config.auto_select_text.filter(|_| select_received); + let auto_text_color_transmitted = config.auto_select_text.filter(|_| select_transmitted); // icon - let mut layout_job = LayoutJob::simple( + let mut layout_job_down = LayoutJob::simple( match self.label_prefix { LabelPrefix::Icon | LabelPrefix::IconAndText => { - egui_phosphor::regular::ARROW_FAT_DOWN.to_string() + if select_received { + egui_phosphor::regular::ARROW_FAT_LINES_DOWN.to_string() + } else { + egui_phosphor::regular::ARROW_FAT_DOWN.to_string() + } } LabelPrefix::None | LabelPrefix::Text => String::new(), }, - icon_format.font_id.clone(), - icon_format.color, + config.icon_font_id.clone(), + auto_text_color_received.unwrap_or(ctx.style().visuals.selection.stroke.color), 100.0, ); // text - layout_job.append( + layout_job_down.append( &text_down, ctx.style().spacing.item_spacing.x, - text_format.clone(), + TextFormat { + font_id: config.text_font_id.clone(), + color: auto_text_color_received.unwrap_or(ctx.style().visuals.text_color()), + valign: Align::Center, + ..Default::default() + }, ); // icon - layout_job.append( - &match self.label_prefix { + let mut layout_job_up = LayoutJob::simple( + match self.label_prefix { LabelPrefix::Icon | LabelPrefix::IconAndText => { - egui_phosphor::regular::ARROW_FAT_UP.to_string() + if select_transmitted { + egui_phosphor::regular::ARROW_FAT_LINES_UP.to_string() + } else { + egui_phosphor::regular::ARROW_FAT_UP.to_string() + } } LabelPrefix::None | LabelPrefix::Text => String::new(), }, - 0.0, - icon_format.clone(), + config.icon_font_id.clone(), + auto_text_color_transmitted.unwrap_or(ctx.style().visuals.selection.stroke.color), + 100.0, ); // text - layout_job.append( + layout_job_up.append( &text_up, ctx.style().spacing.item_spacing.x, - text_format.clone(), + TextFormat { + font_id: config.text_font_id.clone(), + color: auto_text_color_transmitted.unwrap_or(ctx.style().visuals.text_color()), + valign: Align::Center, + ..Default::default() + }, ); - Label::new(layout_job).selectable(false) + ( + Label::new(layout_job_down).selectable(false), + Label::new(layout_job_up).selectable(false), + ) } - fn to_pretty_bytes(input_in_bytes: u64, timespan_in_s: u64) -> String { + fn to_pretty_bytes(input_in_bytes: u64, timespan_in_s: u64) -> (u64, String) { let input = input_in_bytes as f32 / timespan_in_s as f32; let mut magnitude = input.log(1024f32) as u32; @@ -248,10 +292,30 @@ impl Network { let base: Option = num::FromPrimitive::from_u32(magnitude); let result = input / ((1u64) << (magnitude * 10)) as f32; - match base { - Some(DataUnit::B) => format!("{result:.1} B"), - Some(unit) => format!("{result:.1} {unit}iB"), - None => String::from("Unknown data unit"), + ( + input as u64, + match base { + Some(DataUnit::B) => format!("{result:.1} B"), + Some(unit) => format!("{result:.1} {unit}iB"), + None => String::from("Unknown data unit"), + }, + ) + } + + fn show_frame( + &self, + selected: bool, + auto_focus_fill: Option, + ui: &mut Ui, + add_contents: impl FnOnce(&mut Ui) -> R, + ) { + if SelectableFrame::new_auto(selected, auto_focus_fill) + .show(ui, add_contents) + .clicked() + { + if let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn() { + eprintln!("{}", error); + } } } } @@ -259,6 +323,8 @@ impl Network { impl BarWidget for Network { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) { if self.enable { + let is_reversed = matches!(config.alignment, Some(Alignment::Right)); + // widget spacing: make sure to use the same config to call the apply_on_widget function let mut render_config = config.clone(); @@ -266,17 +332,102 @@ impl BarWidget for Network { let (activity, total_activity) = self.network_activity(); if self.show_total_activity { - for reading in total_activity { - render_config.apply_on_widget(true, ui, |ui| { - ui.add(self.reading_to_label(ctx, reading, config.clone())); + for reading in &total_activity { + render_config.apply_on_widget(false, ui, |ui| { + let select_received = self.auto_select.is_some_and(|f| { + f.total_received_over + .is_some_and(|o| reading.received.value > o) + }); + let select_transmitted = self.auto_select.is_some_and(|f| { + f.total_transmitted_over + .is_some_and(|o| reading.transmitted.value > o) + }); + + let labels = self.reading_to_labels( + select_received, + select_transmitted, + ctx, + reading, + config.clone(), + ); + + if is_reversed { + self.show_frame( + select_transmitted, + config.auto_select_fill, + ui, + |ui| ui.add(labels.1), + ); + self.show_frame( + select_received, + config.auto_select_fill, + ui, + |ui| ui.add(labels.0), + ); + } else { + self.show_frame( + select_received, + config.auto_select_fill, + ui, + |ui| ui.add(labels.0), + ); + self.show_frame( + select_transmitted, + config.auto_select_fill, + ui, + |ui| ui.add(labels.1), + ); + } }); } } if self.show_activity { - for reading in activity { - render_config.apply_on_widget(true, ui, |ui| { - ui.add(self.reading_to_label(ctx, reading, config.clone())); + for reading in &activity { + render_config.apply_on_widget(false, ui, |ui| { + let select_received = self.auto_select.is_some_and(|f| { + f.received_over.is_some_and(|o| reading.received.value > o) + }); + let select_transmitted = self.auto_select.is_some_and(|f| { + f.transmitted_over + .is_some_and(|o| reading.transmitted.value > o) + }); + + let labels = self.reading_to_labels( + select_received, + select_transmitted, + ctx, + reading, + config.clone(), + ); + + if is_reversed { + self.show_frame( + select_transmitted, + config.auto_select_fill, + ui, + |ui| ui.add(labels.1), + ); + self.show_frame( + select_received, + config.auto_select_fill, + ui, + |ui| ui.add(labels.0), + ); + } else { + self.show_frame( + select_received, + config.auto_select_fill, + ui, + |ui| ui.add(labels.0), + ); + self.show_frame( + select_transmitted, + config.auto_select_fill, + ui, + |ui| ui.add(labels.1), + ); + } }); } } @@ -314,15 +465,9 @@ impl BarWidget for Network { ); render_config.apply_on_widget(false, ui, |ui| { - if SelectableFrame::new(false) - .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false))) - .clicked() - { - if let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn() - { - eprintln!("{}", error) - } - } + self.show_frame(false, None, ui, |ui| { + ui.add(Label::new(layout_job).selectable(false)) + }); }); } } @@ -339,19 +484,38 @@ enum NetworkReadingFormat { Total = 1, } +#[derive(Clone)] +struct ReadingValue { + value: u64, + pretty: String, +} + +impl From<(u64, String)> for ReadingValue { + fn from(value: (u64, String)) -> Self { + Self { + value: value.0, + pretty: value.1, + } + } +} + #[derive(Clone)] struct NetworkReading { - pub format: NetworkReadingFormat, - pub received_text: String, - pub transmitted_text: String, + format: NetworkReadingFormat, + received: ReadingValue, + transmitted: ReadingValue, } impl NetworkReading { - pub fn new(format: NetworkReadingFormat, received: String, transmitted: String) -> Self { - NetworkReading { + fn new( + format: NetworkReadingFormat, + received: ReadingValue, + transmitted: ReadingValue, + ) -> Self { + Self { format, - received_text: received, - transmitted_text: transmitted, + received, + transmitted, } } } diff --git a/komorebi-bar/src/widgets/storage.rs b/komorebi-bar/src/widgets/storage.rs index a455061d..fbeb7730 100644 --- a/komorebi-bar/src/widgets/storage.rs +++ b/komorebi-bar/src/widgets/storage.rs @@ -1,3 +1,4 @@ +use crate::bar::Alignment; use crate::config::LabelPrefix; use crate::render::RenderConfig; use crate::selected_frame::SelectableFrame; @@ -24,6 +25,10 @@ pub struct StorageConfig { pub data_refresh_interval: Option, /// Display label prefix pub label_prefix: Option, + /// Select when the current percentage is over this value [[1-100]] + pub auto_select_over: Option, + /// Hide when the current percentage is under this value [[1-100]] + pub auto_hide_under: Option, } impl From for Storage { @@ -33,21 +38,30 @@ impl From for Storage { disks: Disks::new_with_refreshed_list(), data_refresh_interval: value.data_refresh_interval.unwrap_or(10), label_prefix: value.label_prefix.unwrap_or(LabelPrefix::IconAndText), + auto_select_over: value.auto_select_over.map(|o| o.clamp(1, 100)), + auto_hide_under: value.auto_hide_under.map(|o| o.clamp(1, 100)), last_updated: Instant::now(), } } } +struct StorageDisk { + label: String, + selected: bool, +} + pub struct Storage { pub enable: bool, disks: Disks, data_refresh_interval: u64, label_prefix: LabelPrefix, + auto_select_over: Option, + auto_hide_under: Option, last_updated: Instant, } impl Storage { - fn output(&mut self) -> Vec { + fn output(&mut self) -> Vec { let now = Instant::now(); if now.duration_since(self.last_updated) > Duration::from_secs(self.data_refresh_interval) { self.disks.refresh(true); @@ -61,17 +75,26 @@ impl Storage { let total = disk.total_space(); let available = disk.available_space(); let used = total - available; + let percentage = ((used * 100) / total) as u8; - disks.push(match self.label_prefix { - LabelPrefix::Text | LabelPrefix::IconAndText => { - format!("{} {}%", mount.to_string_lossy(), (used * 100) / total) - } - LabelPrefix::None | LabelPrefix::Icon => format!("{}%", (used * 100) / total), - }) + let hide = self.auto_hide_under.is_some_and(|u| percentage <= u); + + if !hide { + let selected = self.auto_select_over.is_some_and(|o| percentage >= o); + + disks.push(StorageDisk { + label: match self.label_prefix { + LabelPrefix::Text | LabelPrefix::IconAndText => { + format!("{} {}%", mount.to_string_lossy(), percentage) + } + LabelPrefix::None | LabelPrefix::Icon => format!("{}%", percentage), + }, + selected, + }) + } } - disks.sort(); - disks.reverse(); + disks.sort_by(|a, b| a.label.cmp(&b.label)); disks } @@ -80,7 +103,16 @@ impl Storage { impl BarWidget for Storage { fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) { if self.enable { - for output in self.output() { + let mut output = self.output(); + let is_reversed = matches!(config.alignment, Some(Alignment::Right)); + + if is_reversed { + output.reverse(); + } + + for output in output { + let auto_text_color = config.auto_select_text.filter(|_| output.selected); + let mut layout_job = LayoutJob::simple( match self.label_prefix { LabelPrefix::Icon | LabelPrefix::IconAndText => { @@ -89,23 +121,25 @@ impl BarWidget for Storage { LabelPrefix::None | LabelPrefix::Text => String::new(), }, config.icon_font_id.clone(), - ctx.style().visuals.selection.stroke.color, + auto_text_color.unwrap_or(ctx.style().visuals.selection.stroke.color), 100.0, ); layout_job.append( - &output, + &output.label, 10.0, TextFormat { font_id: config.text_font_id.clone(), - color: ctx.style().visuals.text_color(), + color: auto_text_color.unwrap_or(ctx.style().visuals.text_color()), valign: Align::Center, ..Default::default() }, ); + let auto_focus_fill = config.auto_select_fill; + config.apply_on_widget(false, ui, |ui| { - if SelectableFrame::new(false) + if SelectableFrame::new_auto(output.selected, auto_focus_fill) .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false))) .clicked() { @@ -113,7 +147,7 @@ impl BarWidget for Storage { .args([ "/C", "explorer.exe", - output.split(' ').collect::>()[0], + output.label.split(' ').collect::>()[0], ]) .spawn() { diff --git a/schema.bar.json b/schema.bar.json index 37bfcc99..07d9e68b 100644 --- a/schema.bar.json +++ b/schema.bar.json @@ -26,6 +26,12 @@ "enable" ], "properties": { + "auto_select_under": { + "description": "Select when the current percentage is under this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -90,6 +96,12 @@ "enable" ], "properties": { + "auto_select_over": { + "description": "Select when the current percentage is over this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -752,6 +764,12 @@ "enable" ], "properties": { + "auto_select_over": { + "description": "Select when the current percentage is over this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -810,10 +828,46 @@ "type": "object", "required": [ "enable", - "show_network_activity", - "show_total_data_transmitted" + "show_activity", + "show_total_activity" ], "properties": { + "activity_left_padding": { + "description": "Characters to reserve for received and transmitted activity", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "auto_select": { + "description": "Select when the value is over a limit (1MiB is 1048576 bytes (1024*1024))", + "type": "object", + "properties": { + "received_over": { + "description": "Select the received data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_received_over": { + "description": "Select the total received data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_transmitted_over": { + "description": "Select the total transmitted data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "transmitted_over": { + "description": "Select the transmitted data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -857,22 +911,16 @@ } ] }, - "network_activity_fill_characters": { - "description": "Characters to reserve for network activity data", - "type": "integer", - "format": "uint", - "minimum": 0.0 + "show_activity": { + "description": "Show received and transmitted activity", + "type": "boolean" }, "show_default_interface": { "description": "Show default interface", "type": "boolean" }, - "show_network_activity": { - "description": "Show network activity", - "type": "boolean" - }, - "show_total_data_transmitted": { - "description": "Show total data transmitted", + "show_total_activity": { + "description": "Show total received and transmitted activity", "type": "boolean" } } @@ -892,6 +940,18 @@ "enable" ], "properties": { + "auto_hide_under": { + "description": "Hide when the current percentage is under this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "auto_select_over": { + "description": "Select when the current percentage is over this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -1493,6 +1553,12 @@ "enable" ], "properties": { + "auto_select_under": { + "description": "Select when the current percentage is under this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -1557,6 +1623,12 @@ "enable" ], "properties": { + "auto_select_over": { + "description": "Select when the current percentage is over this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -2219,6 +2291,12 @@ "enable" ], "properties": { + "auto_select_over": { + "description": "Select when the current percentage is over this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -2277,10 +2355,46 @@ "type": "object", "required": [ "enable", - "show_network_activity", - "show_total_data_transmitted" + "show_activity", + "show_total_activity" ], "properties": { + "activity_left_padding": { + "description": "Characters to reserve for received and transmitted activity", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "auto_select": { + "description": "Select when the value is over a limit (1MiB is 1048576 bytes (1024*1024))", + "type": "object", + "properties": { + "received_over": { + "description": "Select the received data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_received_over": { + "description": "Select the total received data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_transmitted_over": { + "description": "Select the total transmitted data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "transmitted_over": { + "description": "Select the transmitted data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -2324,22 +2438,16 @@ } ] }, - "network_activity_fill_characters": { - "description": "Characters to reserve for network activity data", - "type": "integer", - "format": "uint", - "minimum": 0.0 + "show_activity": { + "description": "Show received and transmitted activity", + "type": "boolean" }, "show_default_interface": { "description": "Show default interface", "type": "boolean" }, - "show_network_activity": { - "description": "Show network activity", - "type": "boolean" - }, - "show_total_data_transmitted": { - "description": "Show total data transmitted", + "show_total_activity": { + "description": "Show total received and transmitted activity", "type": "boolean" } } @@ -2359,6 +2467,18 @@ "enable" ], "properties": { + "auto_hide_under": { + "description": "Hide when the current percentage is under this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "auto_select_over": { + "description": "Select when the current percentage is over this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -2893,6 +3013,12 @@ "enable" ], "properties": { + "auto_select_under": { + "description": "Select when the current percentage is under this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -2957,6 +3083,12 @@ "enable" ], "properties": { + "auto_select_over": { + "description": "Select when the current percentage is over this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -3619,6 +3751,12 @@ "enable" ], "properties": { + "auto_select_over": { + "description": "Select when the current percentage is over this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -3677,10 +3815,46 @@ "type": "object", "required": [ "enable", - "show_network_activity", - "show_total_data_transmitted" + "show_activity", + "show_total_activity" ], "properties": { + "activity_left_padding": { + "description": "Characters to reserve for received and transmitted activity", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "auto_select": { + "description": "Select when the value is over a limit (1MiB is 1048576 bytes (1024*1024))", + "type": "object", + "properties": { + "received_over": { + "description": "Select the received data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_received_over": { + "description": "Select the total received data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "total_transmitted_over": { + "description": "Select the total transmitted data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "transmitted_over": { + "description": "Select the transmitted data when it's over this value", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -3724,22 +3898,16 @@ } ] }, - "network_activity_fill_characters": { - "description": "Characters to reserve for network activity data", - "type": "integer", - "format": "uint", - "minimum": 0.0 + "show_activity": { + "description": "Show received and transmitted activity", + "type": "boolean" }, "show_default_interface": { "description": "Show default interface", "type": "boolean" }, - "show_network_activity": { - "description": "Show network activity", - "type": "boolean" - }, - "show_total_data_transmitted": { - "description": "Show total data transmitted", + "show_total_activity": { + "description": "Show total received and transmitted activity", "type": "boolean" } } @@ -3759,6 +3927,18 @@ "enable" ], "properties": { + "auto_hide_under": { + "description": "Hide when the current percentage is under this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "auto_select_over": { + "description": "Select when the current percentage is over this value [[1-100]]", + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "data_refresh_interval": { "description": "Data refresh interval (default: 10 seconds)", "type": "integer", @@ -4035,6 +4215,68 @@ "Crust" ] }, + "auto_select_fill": { + "type": "string", + "enum": [ + "Rosewater", + "Flamingo", + "Pink", + "Mauve", + "Red", + "Maroon", + "Peach", + "Yellow", + "Green", + "Teal", + "Sky", + "Sapphire", + "Blue", + "Lavender", + "Text", + "Subtext1", + "Subtext0", + "Overlay2", + "Overlay1", + "Overlay0", + "Surface2", + "Surface1", + "Surface0", + "Base", + "Mantle", + "Crust" + ] + }, + "auto_select_text": { + "type": "string", + "enum": [ + "Rosewater", + "Flamingo", + "Pink", + "Mauve", + "Red", + "Maroon", + "Peach", + "Yellow", + "Green", + "Teal", + "Sky", + "Sapphire", + "Blue", + "Lavender", + "Text", + "Subtext1", + "Subtext0", + "Overlay2", + "Overlay1", + "Overlay0", + "Surface2", + "Surface1", + "Surface0", + "Base", + "Mantle", + "Crust" + ] + }, "name": { "description": "Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin)", "type": "string", @@ -4082,6 +4324,48 @@ "Base0F" ] }, + "auto_select_fill": { + "type": "string", + "enum": [ + "Base00", + "Base01", + "Base02", + "Base03", + "Base04", + "Base05", + "Base06", + "Base07", + "Base08", + "Base09", + "Base0A", + "Base0B", + "Base0C", + "Base0D", + "Base0E", + "Base0F" + ] + }, + "auto_select_text": { + "type": "string", + "enum": [ + "Base00", + "Base01", + "Base02", + "Base03", + "Base04", + "Base05", + "Base06", + "Base07", + "Base08", + "Base09", + "Base0A", + "Base0B", + "Base0C", + "Base0D", + "Base0E", + "Base0F" + ] + }, "name": { "description": "Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)", "type": "string", @@ -4394,6 +4678,48 @@ "Base0F" ] }, + "auto_select_fill": { + "type": "string", + "enum": [ + "Base00", + "Base01", + "Base02", + "Base03", + "Base04", + "Base05", + "Base06", + "Base07", + "Base08", + "Base09", + "Base0A", + "Base0B", + "Base0C", + "Base0D", + "Base0E", + "Base0F" + ] + }, + "auto_select_text": { + "type": "string", + "enum": [ + "Base00", + "Base01", + "Base02", + "Base03", + "Base04", + "Base05", + "Base06", + "Base07", + "Base08", + "Base09", + "Base0A", + "Base0B", + "Base0C", + "Base0D", + "Base0E", + "Base0F" + ] + }, "colours": { "description": "Colours of the custom Base16 theme palette", "type": "object",