diff --git a/CHANGELOG.md b/CHANGELOG.md index e5313e8..91f4273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.9] - 2025-10-09 + +### Added +- **Two New Usage Segments**: Enhanced API usage monitoring with dedicated segments + - **Usage5Hour Segment**: Displays 5-hour usage percentage with reset time (e.g., `24% -> 11am`) + - **Usage7Day Segment**: Displays 7-day usage percentage with full reset datetime (e.g., `12% -> Oct 9, 5am`) + - Both segments share efficient API cache to minimize redundant calls + - Dynamic circle icons that change based on utilization level + - Automatic UTC to local timezone conversion for reset times + - Disabled by default (opt-in via config or TUI) + +### Changed +- **API Cache Structure**: Updated to store both 5-hour and 7-day reset times separately + - Cache now includes `five_hour_resets_at` and `seven_day_resets_at` fields + - Backward compatible with graceful handling of old cache files +- **Segment ID Enum**: Added `Usage5Hour` and `Usage7Day` variants +- **All Theme Files**: Updated all 9 built-in themes to include new usage segments +- **TUI Components**: Enhanced UI components with new segment name mappings + +### Technical Details +- New time formatting functions: `format_5hour_reset_time()` and `format_7day_reset_time()` +- Public API for `UsageSegment::load_usage_cache()` to enable cache sharing +- Added mock preview data for new segments in TUI configurator +- All UI match statements updated to handle new segment IDs + ## [1.0.4] - 2025-08-28 ### Added diff --git a/Cargo.lock b/Cargo.lock index 55cf6b5..74ad9da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,7 +160,7 @@ dependencies = [ [[package]] name = "ccometixline" -version = "1.0.8" +version = "1.0.9" dependencies = [ "ansi-to-tui", "ansi_term", diff --git a/Cargo.toml b/Cargo.toml index 5157ab7..6042a29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ccometixline" -version = "1.0.8" +version = "1.0.9" edition = "2021" description = "CCometixLine (ccline) - High-performance Claude Code StatusLine tool written in Rust" authors = ["Haleclipse"] diff --git a/README.md b/README.md index 91512b7..9476fd7 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,43 @@ New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.claude\ccline" copy target\release\ccometixline.exe "$env:USERPROFILE\.claude\ccline\ccline.exe" ``` +### Running a Forked Version + +If you're running a forked version with the latest changes, you'll need to rebuild and reinstall after pulling updates: + +```bash +# Navigate to your forked repository +cd /path/to/your/CCometixLine + +# Pull latest changes +git pull + +# Rebuild the binary +cargo build --release + +# Install to local directory (recommended for testing) +# Linux/macOS +cp target/release/ccometixline ~/.claude/ccline/ccline + +# Or install to system location (if using Homebrew path) +# macOS +cp target/release/ccometixline /opt/homebrew/bin/ccline + +# Or install to system location +# Linux +sudo cp target/release/ccometixline /usr/local/bin/ccline + +# Verify version +ccline --version +``` + +**After updating the binary**, if you've added new segments or themes: +1. Remove old theme cache: `rm -rf ~/.claude/ccline/themes` +2. Reinitialize if needed: `ccline --init` +3. Use TUI configurator to enable new segments: `ccline -c` + +**Note**: The binary name in the repository is `ccometixline`, but it's renamed to `ccline` for convenience. + ## Usage ### Configuration Management @@ -244,6 +281,32 @@ Shows simplified Claude model names: Token usage percentage based on transcript analysis with context limit tracking. +### Usage Segments + +Three usage tracking segments are available for monitoring Claude API usage: + +**Usage (Original)** - Shows combined usage info: +- Displays 5-hour usage percentage +- Shows 7-day reset date in compact format +- Format: `24% · 10-7-2` (24% used, resets Oct 7 at 2am) + +**Usage (5-hour)** - Focused 5-hour window: +- Shows 5-hour usage percentage with reset time +- Format: `24% → 11am` +- Ideal for monitoring short-term API limits + +**Usage (7-day)** - Weekly usage tracking: +- Shows 7-day usage percentage with full reset datetime +- Format: `12% → Oct 9:5am` +- Perfect for tracking weekly quota + +All usage segments: +- Share the same API call and cache (efficient) +- Use dynamic circle icons that change with utilization level +- Are disabled by default (enable via config or TUI) +- Auto-convert reset times from UTC to local timezone +- **Support threshold-based warning colors** (see below) + ## Configuration CCometixLine supports full configuration via TOML files and interactive TUI: @@ -261,7 +324,45 @@ All segments are configurable with: - Color customization - Format options -Supported segments: Directory, Git, Model, Usage, Time, Cost, OutputStyle +Supported segments: Directory, Git, Model, ContextWindow, Usage, Usage5Hour, Usage7Day, Cost, Session, OutputStyle, Update + +### Threshold-Based Warning Colors + +Usage segments (Usage5Hour and Usage7Day) support dynamic color changes based on utilization thresholds. This allows you to get visual warnings when your API usage approaches limits. + +**Configuration example:** + +```toml +[[segments]] +id = "usage_5hour" +enabled = true + +[segments.colors] +# Default colors (used when under warning threshold) +icon.c16 = 14 # Cyan +text.c16 = 14 + +[segments.options] +warning_threshold = 60 # Turn yellow at 60% usage +critical_threshold = 80 # Turn red at 80% usage +warning_color.c16 = 11 # Yellow (16-color palette) +critical_color.c16 = 9 # Red (16-color palette) +``` + +**How it works:** +- **< 60%**: Uses default segment colors (cyan) +- **≥ 60%**: Text changes to warning color (yellow) +- **≥ 80%**: Text changes to critical color (red) + +You can customize thresholds and colors for each usage segment independently. The colors can be specified using: +- `c16`: 16-color ANSI palette (0-15) +- `c256`: 256-color palette (0-255) +- RGB values (e.g., `{r = 255, g = 165, b = 0}`) + +**Common color codes:** +- Yellow: `c256 = 226` or `c16 = 11` +- Red: `c256 = 196` or `c16 = 9` +- Orange: `c256 = 208` or `c256 = 214` ## Requirements diff --git a/src/config/types.rs b/src/config/types.rs index e5a78dc..364ba64 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -69,6 +69,8 @@ pub enum SegmentId { Git, ContextWindow, Usage, + Usage5Hour, + Usage7Day, Cost, Session, OutputStyle, diff --git a/src/core/segments/color_utils.rs b/src/core/segments/color_utils.rs new file mode 100644 index 0000000..f0e810f --- /dev/null +++ b/src/core/segments/color_utils.rs @@ -0,0 +1,51 @@ +use crate::config::AnsiColor; +use ratatui::style::Color; + +/// Serializes an AnsiColor to a JSON string for metadata storage +pub fn serialize_ansi_color_to_json(color: &AnsiColor) -> String { + match color { + AnsiColor::Color256 { c256 } => { + serde_json::json!({"c256": c256}).to_string() + } + AnsiColor::Color16 { c16 } => { + serde_json::json!({"c16": c16}).to_string() + } + AnsiColor::Rgb { r, g, b } => { + serde_json::json!({"r": r, "g": g, "b": b}).to_string() + } + } +} + +/// Converts a 16-color ANSI code to a ratatui Color +/// This is used throughout the TUI for consistent color rendering +pub fn c16_to_ratatui_color(c16: u8) -> Color { + match c16 { + 0 => Color::Black, + 1 => Color::Red, + 2 => Color::Green, + 3 => Color::Yellow, + 4 => Color::Blue, + 5 => Color::Magenta, + 6 => Color::Cyan, + 7 => Color::White, + 8 => Color::DarkGray, + 9 => Color::LightRed, + 10 => Color::LightGreen, + 11 => Color::LightYellow, + 12 => Color::LightBlue, + 13 => Color::LightMagenta, + 14 => Color::LightCyan, + 15 => Color::Gray, + _ => Color::White, // Default fallback + } +} + +/// Converts an AnsiColor to a ratatui Color +/// Handles all three color formats: c16, c256, and RGB +pub fn ansi_color_to_ratatui(color: &AnsiColor) -> Color { + match color { + AnsiColor::Color16 { c16 } => c16_to_ratatui_color(*c16), + AnsiColor::Color256 { c256 } => Color::Indexed(*c256), + AnsiColor::Rgb { r, g, b } => Color::Rgb(*r, *g, *b), + } +} diff --git a/src/core/segments/mod.rs b/src/core/segments/mod.rs index ff036a9..1ce72e7 100644 --- a/src/core/segments/mod.rs +++ b/src/core/segments/mod.rs @@ -1,3 +1,4 @@ +pub mod color_utils; pub mod context_window; pub mod cost; pub mod directory; @@ -7,6 +8,8 @@ pub mod output_style; pub mod session; pub mod update; pub mod usage; +pub mod usage_5hour; +pub mod usage_7day; use crate::config::{InputData, SegmentId}; use std::collections::HashMap; @@ -34,3 +37,5 @@ pub use output_style::OutputStyleSegment; pub use session::SessionSegment; pub use update::UpdateSegment; pub use usage::UsageSegment; +pub use usage_5hour::Usage5HourSegment; +pub use usage_7day::Usage7DaySegment; diff --git a/src/core/segments/usage.rs b/src/core/segments/usage.rs index 51a00d4..ac54e12 100644 --- a/src/core/segments/usage.rs +++ b/src/core/segments/usage.rs @@ -18,11 +18,12 @@ struct UsagePeriod { } #[derive(Debug, Serialize, Deserialize)] -struct ApiUsageCache { - five_hour_utilization: f64, - seven_day_utilization: f64, - resets_at: Option, - cached_at: String, +pub struct ApiUsageCache { + pub five_hour_utilization: f64, + pub seven_day_utilization: f64, + pub five_hour_resets_at: Option, + pub seven_day_resets_at: Option, + pub cached_at: String, } #[derive(Default)] @@ -33,7 +34,7 @@ impl UsageSegment { Self } - fn get_circle_icon(utilization: f64) -> String { + pub fn get_circle_icon(utilization: f64) -> String { let percent = (utilization * 100.0) as u8; match percent { 0..=12 => "\u{f0a9e}".to_string(), // circle_slice_1 @@ -65,7 +66,72 @@ impl UsageSegment { "?".to_string() } - fn get_cache_path() -> Option { + /// Format 5-hour reset time as "11am" or "5pm" + pub fn format_5hour_reset_time(reset_time_str: Option<&str>) -> String { + if let Some(time_str) = reset_time_str { + if let Ok(dt) = DateTime::parse_from_rfc3339(time_str) { + let mut local_dt = dt.with_timezone(&Local); + // Round up if more than 45 minutes past the hour + if local_dt.minute() > 45 { + local_dt = local_dt + Duration::hours(1); + } + let hour = local_dt.hour(); + let (hour_12, period) = if hour == 0 { + (12, "am") + } else if hour < 12 { + (hour, "am") + } else if hour == 12 { + (12, "pm") + } else { + (hour - 12, "pm") + }; + return format!("{}{}", hour_12, period); + } + } + "?".to_string() + } + + /// Format 7-day reset time as "Oct 9, 5am" + pub fn format_7day_reset_time(reset_time_str: Option<&str>) -> String { + if let Some(time_str) = reset_time_str { + if let Ok(dt) = DateTime::parse_from_rfc3339(time_str) { + let mut local_dt = dt.with_timezone(&Local); + // Round up if more than 45 minutes past the hour + if local_dt.minute() > 45 { + local_dt = local_dt + Duration::hours(1); + } + let month_name = match local_dt.month() { + 1 => "Jan", + 2 => "Feb", + 3 => "Mar", + 4 => "Apr", + 5 => "May", + 6 => "Jun", + 7 => "Jul", + 8 => "Aug", + 9 => "Sep", + 10 => "Oct", + 11 => "Nov", + 12 => "Dec", + _ => "?", + }; + let hour = local_dt.hour(); + let (hour_12, period) = if hour == 0 { + (12, "am") + } else if hour < 12 { + (hour, "am") + } else if hour == 12 { + (12, "pm") + } else { + (hour - 12, "pm") + }; + return format!("{} {}:{}{}", month_name, local_dt.day(), hour_12, period); + } + } + "?".to_string() + } + + pub fn get_cache_path() -> Option { let home = dirs::home_dir()?; Some( home.join(".claude") @@ -84,6 +150,17 @@ impl UsageSegment { serde_json::from_str(&content).ok() } + /// Public helper to load cache for other usage segments + pub fn load_usage_cache() -> Option { + let cache_path = Self::get_cache_path()?; + if !cache_path.exists() { + return None; + } + + let content = std::fs::read_to_string(&cache_path).ok()?; + serde_json::from_str(&content).ok() + } + fn save_cache(&self, cache: &ApiUsageCache) { if let Some(cache_path) = Self::get_cache_path() { if let Some(parent) = cache_path.parent() { @@ -214,7 +291,7 @@ impl Segment for UsageSegment { ( cache.five_hour_utilization, cache.seven_day_utilization, - cache.resets_at, + cache.seven_day_resets_at, ) } else { match self.fetch_api_usage(api_base_url, &token, timeout) { @@ -222,7 +299,8 @@ impl Segment for UsageSegment { let cache = ApiUsageCache { five_hour_utilization: response.five_hour.utilization, seven_day_utilization: response.seven_day.utilization, - resets_at: response.seven_day.resets_at.clone(), + five_hour_resets_at: response.five_hour.resets_at.clone(), + seven_day_resets_at: response.seven_day.resets_at.clone(), cached_at: Utc::now().to_rfc3339(), }; self.save_cache(&cache); @@ -237,7 +315,7 @@ impl Segment for UsageSegment { ( cache.five_hour_utilization, cache.seven_day_utilization, - cache.resets_at, + cache.seven_day_resets_at, ) } else { return None; diff --git a/src/core/segments/usage_5hour.rs b/src/core/segments/usage_5hour.rs new file mode 100644 index 0000000..25c23c7 --- /dev/null +++ b/src/core/segments/usage_5hour.rs @@ -0,0 +1,147 @@ +use super::{color_utils, Segment, SegmentData}; +use crate::config::{AnsiColor, Config, InputData, SegmentId}; +use crate::core::segments::usage::UsageSegment; +use std::collections::HashMap; + +#[derive(Default)] +pub struct Usage5HourSegment; + +impl Usage5HourSegment { + pub fn new() -> Self { + Self + } + + fn get_color_for_utilization(&self, utilization: f64) -> Option { + // Load config to get threshold settings + let config = Config::load().ok()?; + let segment_config = config.segments.iter().find(|s| s.id == SegmentId::Usage5Hour)?; + + // Get threshold values from options + let warning_threshold = segment_config + .options + .get("warning_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(60) as f64; + + let critical_threshold = segment_config + .options + .get("critical_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(80) as f64; + + // Determine which color to use based on utilization + if utilization >= critical_threshold { + // Critical threshold exceeded - use critical color + segment_config + .options + .get("critical_color") + .and_then(|v| { + if let Some(c256) = v.get("c256").and_then(|c| c.as_u64()) { + Some(AnsiColor::Color256 { c256: c256 as u8 }) + } else if let Some(c16) = v.get("c16").and_then(|c| c.as_u64()) { + Some(AnsiColor::Color16 { c16: c16 as u8 }) + } else { + None + } + }) + } else if utilization >= warning_threshold { + // Warning threshold exceeded - use warning color + segment_config + .options + .get("warning_color") + .and_then(|v| { + if let Some(c256) = v.get("c256").and_then(|c| c.as_u64()) { + Some(AnsiColor::Color256 { c256: c256 as u8 }) + } else if let Some(c16) = v.get("c16").and_then(|c| c.as_u64()) { + Some(AnsiColor::Color16 { c16: c16 as u8 }) + } else { + None + } + }) + } else { + // Below warning threshold - use default color + None + } + } + + fn should_be_bold(&self, utilization: f64) -> Option { + // Load config to get threshold settings + let config = Config::load().ok()?; + let segment_config = config.segments.iter().find(|s| s.id == SegmentId::Usage5Hour)?; + + // Get threshold values from options + let warning_threshold = segment_config + .options + .get("warning_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(60) as f64; + + let critical_threshold = segment_config + .options + .get("critical_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(80) as f64; + + // Determine if text should be bold based on utilization + if utilization >= critical_threshold { + // Critical threshold - check critical_bold option + segment_config + .options + .get("critical_bold") + .and_then(|v| v.as_bool()) + } else if utilization >= warning_threshold { + // Warning threshold - check warning_bold option + segment_config + .options + .get("warning_bold") + .and_then(|v| v.as_bool()) + } else { + // Below warning threshold - no bold override + None + } + } +} + +impl Segment for Usage5HourSegment { + fn collect(&self, _input: &InputData) -> Option { + // Load the shared cache created by UsageSegment + let cache = UsageSegment::load_usage_cache()?; + + // Note: five_hour_utilization is a percentage (0-100) from the API + let five_hour_util = cache.five_hour_utilization; + let reset_time = UsageSegment::format_5hour_reset_time(cache.five_hour_resets_at.as_deref()); + + // Convert percentage (0-100) to normalized value (0-1) for get_circle_icon + let dynamic_icon = UsageSegment::get_circle_icon(five_hour_util / 100.0); + + let five_hour_percent = five_hour_util.round() as u8; + let primary = format!("{}%", five_hour_percent); + let secondary = format!("→ {}", reset_time); + + let mut metadata = HashMap::new(); + metadata.insert("dynamic_icon".to_string(), dynamic_icon); + metadata.insert("five_hour_utilization".to_string(), five_hour_util.to_string()); + + // Check if we need to apply threshold-based color override + if let Some(color) = self.get_color_for_utilization(five_hour_util) { + // Serialize the color to JSON for metadata using shared helper + let color_json = color_utils::serialize_ansi_color_to_json(&color); + metadata.insert("text_color_override".to_string(), color_json); + } + + // Check if we need to apply threshold-based bold override + if let Some(should_bold) = self.should_be_bold(five_hour_util) { + metadata.insert("text_bold_override".to_string(), should_bold.to_string()); + } + + Some(SegmentData { + primary, + secondary, + metadata, + }) + } + + fn id(&self) -> SegmentId { + SegmentId::Usage5Hour + } +} diff --git a/src/core/segments/usage_7day.rs b/src/core/segments/usage_7day.rs new file mode 100644 index 0000000..6dbbb00 --- /dev/null +++ b/src/core/segments/usage_7day.rs @@ -0,0 +1,147 @@ +use super::{color_utils, Segment, SegmentData}; +use crate::config::{AnsiColor, Config, InputData, SegmentId}; +use crate::core::segments::usage::UsageSegment; +use std::collections::HashMap; + +#[derive(Default)] +pub struct Usage7DaySegment; + +impl Usage7DaySegment { + pub fn new() -> Self { + Self + } + + fn get_color_for_utilization(&self, utilization: f64) -> Option { + // Load config to get threshold settings + let config = Config::load().ok()?; + let segment_config = config.segments.iter().find(|s| s.id == SegmentId::Usage7Day)?; + + // Get threshold values from options + let warning_threshold = segment_config + .options + .get("warning_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(60) as f64; + + let critical_threshold = segment_config + .options + .get("critical_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(80) as f64; + + // Determine which color to use based on utilization + if utilization >= critical_threshold { + // Critical threshold exceeded - use critical color + segment_config + .options + .get("critical_color") + .and_then(|v| { + if let Some(c256) = v.get("c256").and_then(|c| c.as_u64()) { + Some(AnsiColor::Color256 { c256: c256 as u8 }) + } else if let Some(c16) = v.get("c16").and_then(|c| c.as_u64()) { + Some(AnsiColor::Color16 { c16: c16 as u8 }) + } else { + None + } + }) + } else if utilization >= warning_threshold { + // Warning threshold exceeded - use warning color + segment_config + .options + .get("warning_color") + .and_then(|v| { + if let Some(c256) = v.get("c256").and_then(|c| c.as_u64()) { + Some(AnsiColor::Color256 { c256: c256 as u8 }) + } else if let Some(c16) = v.get("c16").and_then(|c| c.as_u64()) { + Some(AnsiColor::Color16 { c16: c16 as u8 }) + } else { + None + } + }) + } else { + // Below warning threshold - use default color + None + } + } + + fn should_be_bold(&self, utilization: f64) -> Option { + // Load config to get threshold settings + let config = Config::load().ok()?; + let segment_config = config.segments.iter().find(|s| s.id == SegmentId::Usage7Day)?; + + // Get threshold values from options + let warning_threshold = segment_config + .options + .get("warning_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(60) as f64; + + let critical_threshold = segment_config + .options + .get("critical_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(80) as f64; + + // Determine if text should be bold based on utilization + if utilization >= critical_threshold { + // Critical threshold - check critical_bold option + segment_config + .options + .get("critical_bold") + .and_then(|v| v.as_bool()) + } else if utilization >= warning_threshold { + // Warning threshold - check warning_bold option + segment_config + .options + .get("warning_bold") + .and_then(|v| v.as_bool()) + } else { + // Below warning threshold - no bold override + None + } + } +} + +impl Segment for Usage7DaySegment { + fn collect(&self, _input: &InputData) -> Option { + // Load the shared cache created by UsageSegment + let cache = UsageSegment::load_usage_cache()?; + + // Note: seven_day_utilization is a percentage (0-100) from the API + let seven_day_util = cache.seven_day_utilization; + let reset_time = UsageSegment::format_7day_reset_time(cache.seven_day_resets_at.as_deref()); + + // Convert percentage (0-100) to normalized value (0-1) for get_circle_icon + let dynamic_icon = UsageSegment::get_circle_icon(seven_day_util / 100.0); + + let seven_day_percent = seven_day_util.round() as u8; + let primary = format!("{}%", seven_day_percent); + let secondary = format!("→ {}", reset_time); + + let mut metadata = HashMap::new(); + metadata.insert("dynamic_icon".to_string(), dynamic_icon); + metadata.insert("seven_day_utilization".to_string(), seven_day_util.to_string()); + + // Check if we need to apply threshold-based color override + if let Some(color) = self.get_color_for_utilization(seven_day_util) { + // Serialize the color to JSON for metadata using shared helper + let color_json = color_utils::serialize_ansi_color_to_json(&color); + metadata.insert("text_color_override".to_string(), color_json); + } + + // Check if we need to apply threshold-based bold override + if let Some(should_bold) = self.should_be_bold(seven_day_util) { + metadata.insert("text_bold_override".to_string(), should_bold.to_string()); + } + + Some(SegmentData { + primary, + secondary, + metadata, + }) + } + + fn id(&self) -> SegmentId { + SegmentId::Usage7Day + } +} diff --git a/src/core/statusline.rs b/src/core/statusline.rs index bd4e581..a3c195c 100644 --- a/src/core/statusline.rs +++ b/src/core/statusline.rs @@ -221,6 +221,37 @@ impl StatusLineGenerator { self.get_icon(config) }; + // Check for text color override in metadata + let text_color = if let Some(color_override_json) = data.metadata.get("text_color_override") { + // Parse the color override from JSON string + if let Ok(color_val) = serde_json::from_str::(color_override_json) { + if let Some(c256) = color_val.get("c256").and_then(|v| v.as_u64()) { + Some(AnsiColor::Color256 { c256: c256 as u8 }) + } else if let Some(c16) = color_val.get("c16").and_then(|v| v.as_u64()) { + Some(AnsiColor::Color16 { c16: c16 as u8 }) + } else if let (Some(r), Some(g), Some(b)) = ( + color_val.get("r").and_then(|v| v.as_u64()), + color_val.get("g").and_then(|v| v.as_u64()), + color_val.get("b").and_then(|v| v.as_u64()), + ) { + Some(AnsiColor::Rgb { r: r as u8, g: g as u8, b: b as u8 }) + } else { + config.colors.text.clone() + } + } else { + config.colors.text.clone() + } + } else { + config.colors.text.clone() + }; + + // Check for text bold override in metadata + let text_bold = if let Some(bold_override) = data.metadata.get("text_bold_override") { + bold_override.parse::().unwrap_or(config.styles.text_bold) + } else { + config.styles.text_bold + }; + // Apply background color to the entire segment if set if let Some(bg_color) = &config.colors.background { let bg_code = self.apply_background_color(bg_color); @@ -236,8 +267,8 @@ impl StatusLineGenerator { let text_styled = self .apply_style( &data.primary, - config.colors.text.as_ref(), - config.styles.text_bold, + text_color.as_ref(), + text_bold, ) .replace("\x1b[0m", ""); @@ -247,8 +278,8 @@ impl StatusLineGenerator { let secondary_styled = self .apply_style( &data.secondary, - config.colors.text.as_ref(), - config.styles.text_bold, + text_color.as_ref(), + text_bold, ) .replace("\x1b[0m", ""); segment_content.push_str(&format!("{} ", secondary_styled)); @@ -261,8 +292,8 @@ impl StatusLineGenerator { let icon_colored = self.apply_color(&icon, config.colors.icon.as_ref()); let text_styled = self.apply_style( &data.primary, - config.colors.text.as_ref(), - config.styles.text_bold, + text_color.as_ref(), + text_bold, ); let mut segment = format!("{} {}", icon_colored, text_styled); @@ -272,8 +303,8 @@ impl StatusLineGenerator { " {}", self.apply_style( &data.secondary, - config.colors.text.as_ref(), - config.styles.text_bold + text_color.as_ref(), + text_bold ) )); } @@ -489,6 +520,14 @@ pub fn collect_all_segments( let segment = UsageSegment::new(); segment.collect(input) } + crate::config::SegmentId::Usage5Hour => { + let segment = Usage5HourSegment::new(); + segment.collect(input) + } + crate::config::SegmentId::Usage7Day => { + let segment = Usage7DaySegment::new(); + segment.collect(input) + } crate::config::SegmentId::Cost => { let segment = CostSegment::new(); segment.collect(input) diff --git a/src/ui/app.rs b/src/ui/app.rs index 0cca753..36aec14 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1,4 +1,5 @@ use crate::config::{Config, SegmentId, StyleMode}; +use crate::core::segments::color_utils; use crate::ui::components::{ color_picker::{ColorPickerComponent, NavDirection}, help::HelpComponent, @@ -462,7 +463,17 @@ impl App { self.selected_segment = new_selection; } Panel::Settings => { - let field_count = 7; // Enabled, Icon, IconColor, TextColor, TextStyle, BackgroundColor, Options + // Check if current segment is a usage segment to determine field count + let is_usage_segment = self.config.segments.get(self.selected_segment) + .map(|s| matches!(s.id, crate::config::SegmentId::Usage5Hour | crate::config::SegmentId::Usage7Day)) + .unwrap_or(false); + + let field_count = if is_usage_segment { + 13 // Enabled, Icon, IconColor, TextColor, BackgroundColor, TextStyle, WarningThreshold, CriticalThreshold, WarningColor, CriticalColor, WarningBold, CriticalBold, Options + } else { + 7 // Enabled, Icon, IconColor, TextColor, BackgroundColor, TextStyle, Options + }; + let current_field = match self.selected_field { FieldSelection::Enabled => 0i32, FieldSelection::Icon => 1, @@ -470,7 +481,13 @@ impl App { FieldSelection::TextColor => 3, FieldSelection::BackgroundColor => 4, FieldSelection::TextStyle => 5, - FieldSelection::Options => 6, + FieldSelection::WarningThreshold => 6, + FieldSelection::CriticalThreshold => 7, + FieldSelection::WarningColor => 8, + FieldSelection::CriticalColor => 9, + FieldSelection::WarningBold => 10, + FieldSelection::CriticalBold => 11, + FieldSelection::Options => 12, }; let new_field = (current_field + delta).clamp(0, field_count - 1) as usize; self.selected_field = match new_field { @@ -480,7 +497,14 @@ impl App { 3 => FieldSelection::TextColor, 4 => FieldSelection::BackgroundColor, 5 => FieldSelection::TextStyle, - 6 => FieldSelection::Options, + 6 if is_usage_segment => FieldSelection::WarningThreshold, + 7 if is_usage_segment => FieldSelection::CriticalThreshold, + 8 if is_usage_segment => FieldSelection::WarningColor, + 9 if is_usage_segment => FieldSelection::CriticalColor, + 10 if is_usage_segment => FieldSelection::WarningBold, + 11 if is_usage_segment => FieldSelection::CriticalBold, + 12 if is_usage_segment => FieldSelection::Options, + 6 => FieldSelection::Options, // For non-usage segments _ => FieldSelection::Enabled, }; } @@ -499,6 +523,8 @@ impl App { SegmentId::Git => "Git", SegmentId::ContextWindow => "Context Window", SegmentId::Usage => "Usage", + SegmentId::Usage5Hour => "Usage (5-hour)", + SegmentId::Usage7Day => "Usage (7-day)", SegmentId::Cost => "Cost", SegmentId::Session => "Session", SegmentId::OutputStyle => "Output Style", @@ -526,6 +552,8 @@ impl App { SegmentId::Git => "Git", SegmentId::ContextWindow => "Context Window", SegmentId::Usage => "Usage", + SegmentId::Usage5Hour => "Usage (5-hour)", + SegmentId::Usage7Day => "Usage (7-day)", SegmentId::Cost => "Cost", SegmentId::Session => "Session", SegmentId::OutputStyle => "Output Style", @@ -543,7 +571,9 @@ impl App { FieldSelection::Icon => self.open_icon_selector(), FieldSelection::IconColor | FieldSelection::TextColor - | FieldSelection::BackgroundColor => self.open_color_picker(), + | FieldSelection::BackgroundColor + | FieldSelection::WarningColor + | FieldSelection::CriticalColor => self.open_color_picker(), FieldSelection::TextStyle => { // Toggle text bold style if let Some(segment) = self.config.segments.get_mut(self.selected_segment) { @@ -559,6 +589,92 @@ impl App { self.preview.update_preview(&self.config); } } + FieldSelection::WarningThreshold => { + // Cycle through common warning thresholds + if let Some(segment) = self.config.segments.get_mut(self.selected_segment) { + let current = segment + .options + .get("warning_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(60); + let new_value = match current { + x if x < 50 => 50, + 50 => 60, + 60 => 70, + 70 => 80, + _ => 40, + }; + segment.options.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(new_value.into()), + ); + self.status_message = Some(format!("Warning threshold set to {}%", new_value)); + self.preview.update_preview(&self.config); + } + } + FieldSelection::CriticalThreshold => { + // Cycle through common critical thresholds + if let Some(segment) = self.config.segments.get_mut(self.selected_segment) { + let current = segment + .options + .get("critical_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(80); + let new_value = match current { + x if x < 70 => 70, + 70 => 80, + 80 => 90, + 90 => 95, + _ => 60, + }; + segment.options.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(new_value.into()), + ); + self.status_message = Some(format!("Critical threshold set to {}%", new_value)); + self.preview.update_preview(&self.config); + } + } + FieldSelection::WarningBold => { + // Toggle warning bold option + if let Some(segment) = self.config.segments.get_mut(self.selected_segment) { + let current = segment + .options + .get("warning_bold") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + let new_value = !current; + segment.options.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(new_value), + ); + self.status_message = Some(format!( + "Warning bold {}", + if new_value { "enabled" } else { "disabled" } + )); + self.preview.update_preview(&self.config); + } + } + FieldSelection::CriticalBold => { + // Toggle critical bold option + if let Some(segment) = self.config.segments.get_mut(self.selected_segment) { + let current = segment + .options + .get("critical_bold") + .and_then(|v| v.as_bool()) + .unwrap_or(true); + let new_value = !current; + segment.options.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(new_value), + ); + self.status_message = Some(format!( + "Critical bold {}", + if new_value { "enabled" } else { "disabled" } + )); + self.preview.update_preview(&self.config); + } + } FieldSelection::Options => { // TODO: Implement options editor self.status_message = @@ -580,7 +696,9 @@ impl App { if self.selected_panel == Panel::Settings && (self.selected_field == FieldSelection::IconColor || self.selected_field == FieldSelection::TextColor - || self.selected_field == FieldSelection::BackgroundColor) + || self.selected_field == FieldSelection::BackgroundColor + || self.selected_field == FieldSelection::WarningColor + || self.selected_field == FieldSelection::CriticalColor) { self.color_picker.open(); } @@ -598,6 +716,16 @@ impl App { FieldSelection::IconColor => segment.colors.icon = Some(color), FieldSelection::TextColor => segment.colors.text = Some(color), FieldSelection::BackgroundColor => segment.colors.background = Some(color), + FieldSelection::WarningColor => { + // Store warning color in options using shared helper + let color_json: serde_json::Value = serde_json::from_str(&color_utils::serialize_ansi_color_to_json(&color)).unwrap(); + segment.options.insert("warning_color".to_string(), color_json); + } + FieldSelection::CriticalColor => { + // Store critical color in options using shared helper + let color_json: serde_json::Value = serde_json::from_str(&color_utils::serialize_ansi_color_to_json(&color)).unwrap(); + segment.options.insert("critical_color".to_string(), color_json); + } _ => {} } self.preview.update_preview(&self.config); diff --git a/src/ui/components/preview.rs b/src/ui/components/preview.rs index 53c5d66..8251b6e 100644 --- a/src/ui/components/preview.rs +++ b/src/ui/components/preview.rs @@ -81,6 +81,40 @@ impl PreviewComponent { &self.preview_cache } + /// Get threshold-based color override for usage segments + fn get_threshold_color(&self, segment_config: &crate::config::SegmentConfig, utilization: f64) -> Option { + // Get threshold values from options + let warning_threshold = segment_config + .options + .get("warning_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(60) as f64; + + let critical_threshold = segment_config + .options + .get("critical_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(80) as f64; + + // Determine which color to use based on utilization + if utilization >= critical_threshold { + // Critical threshold exceeded - use critical color + segment_config + .options + .get("critical_color") + .map(|v| v.to_string()) + } else if utilization >= warning_threshold { + // Warning threshold exceeded - use warning color + segment_config + .options + .get("warning_color") + .map(|v| v.to_string()) + } else { + // Below warning threshold - no override + None + } + } + /// Generate mock segments data for preview display /// This creates perfect preview data without depending on real environment fn generate_mock_segments_data( @@ -141,6 +175,42 @@ impl PreviewComponent { secondary: "· 10-7-2".to_string(), metadata: HashMap::new(), }, + SegmentId::Usage5Hour => { + // Use mock utilization that demonstrates warning threshold (65% > default 60%) + let utilization = 65.0; + let mut metadata = HashMap::new(); + metadata.insert("dynamic_icon".to_string(), "\u{f0aa3}".to_string()); // circle_slice_6 + metadata.insert("five_hour_utilization".to_string(), utilization.to_string()); + + // Apply threshold-based color override + if let Some(color_override) = self.get_threshold_color(segment_config, utilization) { + metadata.insert("text_color_override".to_string(), color_override); + } + + SegmentData { + primary: "65%".to_string(), + secondary: "→ 11am".to_string(), + metadata, + } + }, + SegmentId::Usage7Day => { + // Use mock utilization that demonstrates critical threshold (85% > default 80%) + let utilization = 85.0; + let mut metadata = HashMap::new(); + metadata.insert("dynamic_icon".to_string(), "\u{f0aa4}".to_string()); // circle_slice_7 + metadata.insert("seven_day_utilization".to_string(), utilization.to_string()); + + // Apply threshold-based color override + if let Some(color_override) = self.get_threshold_color(segment_config, utilization) { + metadata.insert("text_color_override".to_string(), color_override); + } + + SegmentData { + primary: "85%".to_string(), + secondary: "→ Oct 9:5am".to_string(), + metadata, + } + }, SegmentId::Cost => SegmentData { primary: "$0.02".to_string(), secondary: "".to_string(), diff --git a/src/ui/components/segment_list.rs b/src/ui/components/segment_list.rs index 832834b..3716631 100644 --- a/src/ui/components/segment_list.rs +++ b/src/ui/components/segment_list.rs @@ -22,6 +22,13 @@ pub enum FieldSelection { BackgroundColor, TextStyle, Options, + // Threshold fields (only shown for usage segments) + WarningThreshold, + CriticalThreshold, + WarningColor, + CriticalColor, + WarningBold, + CriticalBold, } #[derive(Default)] @@ -53,6 +60,8 @@ impl SegmentListComponent { SegmentId::Git => "Git", SegmentId::ContextWindow => "Context Window", SegmentId::Usage => "Usage", + SegmentId::Usage5Hour => "Usage (5-hour)", + SegmentId::Usage7Day => "Usage (7-day)", SegmentId::Cost => "Cost", SegmentId::Session => "Session", SegmentId::OutputStyle => "Output Style", diff --git a/src/ui/components/settings.rs b/src/ui/components/settings.rs index aa65acb..e071ba3 100644 --- a/src/ui/components/settings.rs +++ b/src/ui/components/settings.rs @@ -1,5 +1,6 @@ use super::segment_list::{FieldSelection, Panel}; use crate::config::{Config, SegmentId, StyleMode}; +use crate::core::segments::color_utils; use ratatui::{ layout::Rect, style::{Color, Style}, @@ -32,6 +33,8 @@ impl SettingsComponent { SegmentId::Git => "Git", SegmentId::ContextWindow => "Context Window", SegmentId::Usage => "Usage", + SegmentId::Usage5Hour => "Usage (5-hour)", + SegmentId::Usage7Day => "Usage (7-day)", SegmentId::Cost => "Cost", SegmentId::Session => "Session", SegmentId::OutputStyle => "Output Style", @@ -41,55 +44,15 @@ impl SettingsComponent { StyleMode::Plain => &segment.icon.plain, StyleMode::NerdFont | StyleMode::Powerline => &segment.icon.nerd_font, }; - // Convert AnsiColor to ratatui Color - let icon_ratatui_color = match &segment.colors.icon { - Some(crate::config::AnsiColor::Color16 { c16 }) => match c16 { - 0 => Color::Black, - 1 => Color::Red, - 2 => Color::Green, - 3 => Color::Yellow, - 4 => Color::Blue, - 5 => Color::Magenta, - 6 => Color::Cyan, - 7 => Color::White, - 8 => Color::DarkGray, - 9 => Color::LightRed, - 10 => Color::LightGreen, - 11 => Color::LightYellow, - 12 => Color::LightBlue, - 13 => Color::LightMagenta, - 14 => Color::LightCyan, - 15 => Color::Gray, - _ => Color::White, - }, - Some(crate::config::AnsiColor::Color256 { c256 }) => Color::Indexed(*c256), - Some(crate::config::AnsiColor::Rgb { r, g, b }) => Color::Rgb(*r, *g, *b), - None => Color::White, - }; - let text_ratatui_color = match &segment.colors.text { - Some(crate::config::AnsiColor::Color16 { c16 }) => match c16 { - 0 => Color::Black, - 1 => Color::Red, - 2 => Color::Green, - 3 => Color::Yellow, - 4 => Color::Blue, - 5 => Color::Magenta, - 6 => Color::Cyan, - 7 => Color::White, - 8 => Color::DarkGray, - 9 => Color::LightRed, - 10 => Color::LightGreen, - 11 => Color::LightYellow, - 12 => Color::LightBlue, - 13 => Color::LightMagenta, - 14 => Color::LightCyan, - 15 => Color::Gray, - _ => Color::White, - }, - Some(crate::config::AnsiColor::Color256 { c256 }) => Color::Indexed(*c256), - Some(crate::config::AnsiColor::Rgb { r, g, b }) => Color::Rgb(*r, *g, *b), - None => Color::White, - }; + // Convert AnsiColor to ratatui Color using shared helper + let icon_ratatui_color = segment.colors.icon + .as_ref() + .map(|c| color_utils::ansi_color_to_ratatui(c)) + .unwrap_or(Color::White); + let text_ratatui_color = segment.colors.text + .as_ref() + .map(|c| color_utils::ansi_color_to_ratatui(c)) + .unwrap_or(Color::White); let icon_color_desc = match &segment.colors.icon { Some(crate::config::AnsiColor::Color16 { c16 }) => match c16 { 0 => "Black".to_string(), @@ -142,30 +105,10 @@ impl SettingsComponent { } None => "Default".to_string(), }; - let background_ratatui_color = match &segment.colors.background { - Some(crate::config::AnsiColor::Color16 { c16 }) => match c16 { - 0 => Color::Black, - 1 => Color::Red, - 2 => Color::Green, - 3 => Color::Yellow, - 4 => Color::Blue, - 5 => Color::Magenta, - 6 => Color::Cyan, - 7 => Color::White, - 8 => Color::DarkGray, - 9 => Color::LightRed, - 10 => Color::LightGreen, - 11 => Color::LightYellow, - 12 => Color::LightBlue, - 13 => Color::LightMagenta, - 14 => Color::LightCyan, - 15 => Color::Gray, - _ => Color::White, - }, - Some(crate::config::AnsiColor::Color256 { c256 }) => Color::Indexed(*c256), - Some(crate::config::AnsiColor::Rgb { r, g, b }) => Color::Rgb(*r, *g, *b), - None => Color::White, - }; + let background_ratatui_color = segment.colors.background + .as_ref() + .map(|c| color_utils::ansi_color_to_ratatui(c)) + .unwrap_or(Color::White); let background_color_desc = match &segment.colors.background { Some(crate::config::AnsiColor::Color16 { c16 }) => match c16 { 0 => "Black".to_string(), @@ -208,7 +151,14 @@ impl SettingsComponent { spans.extend(content); Line::from(spans) }; - let lines = vec![ + + // Check if this is a usage segment to show threshold fields + let is_usage_segment = matches!( + segment.id, + SegmentId::Usage5Hour | SegmentId::Usage7Day + ); + + let mut lines = vec![ Line::from(format!("{} Segment", segment_name)), create_field_line( FieldSelection::Enabled, @@ -266,14 +216,119 @@ impl SettingsComponent { } ))], ), - create_field_line( - FieldSelection::Options, - vec![Span::raw(format!( - "└─ Options: {} items", - segment.options.len() - ))], - ), ]; + + // Add threshold fields for usage segments + if is_usage_segment { + let warning_threshold = segment + .options + .get("warning_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(60); + let critical_threshold = segment + .options + .get("critical_threshold") + .and_then(|v| v.as_u64()) + .unwrap_or(80); + + // Get warning color description and ratatui color using shared helper + let (warning_color_desc, warning_ratatui_color) = if let Some(color_val) = segment.options.get("warning_color") { + if let Some(c256) = color_val.get("c256").and_then(|v| v.as_u64()) { + (format!("256:{}", c256), Some(Color::Indexed(c256 as u8))) + } else if let Some(c16) = color_val.get("c16").and_then(|v| v.as_u64()) { + let color = color_utils::c16_to_ratatui_color(c16 as u8); + (format!("16:{}", c16), Some(color)) + } else { + ("Not set".to_string(), None) + } + } else { + ("Not set".to_string(), None) + }; + + // Get critical color description and ratatui color using shared helper + let (critical_color_desc, critical_ratatui_color) = if let Some(color_val) = segment.options.get("critical_color") { + if let Some(c256) = color_val.get("c256").and_then(|v| v.as_u64()) { + (format!("256:{}", c256), Some(Color::Indexed(c256 as u8))) + } else if let Some(c16) = color_val.get("c16").and_then(|v| v.as_u64()) { + let color = color_utils::c16_to_ratatui_color(c16 as u8); + (format!("16:{}", c16), Some(color)) + } else { + ("Not set".to_string(), None) + } + } else { + ("Not set".to_string(), None) + }; + + // Get bold settings + let warning_bold = segment + .options + .get("warning_bold") + .and_then(|v| v.as_bool()) + .unwrap_or(false); + let critical_bold = segment + .options + .get("critical_bold") + .and_then(|v| v.as_bool()) + .unwrap_or(true); + + lines.extend(vec![ + create_field_line( + FieldSelection::WarningThreshold, + vec![Span::raw(format!("├─ Warning Threshold: {}%", warning_threshold))], + ), + create_field_line( + FieldSelection::CriticalThreshold, + vec![Span::raw(format!("├─ Critical Threshold: {}%", critical_threshold))], + ), + create_field_line( + FieldSelection::WarningColor, + { + let mut spans = vec![Span::raw(format!("├─ Warning Color: {} ", warning_color_desc))]; + if let Some(color) = warning_ratatui_color { + spans.push(Span::styled("██".to_string(), Style::default().fg(color))); + } else { + spans.push(Span::styled("--".to_string(), Style::default().fg(Color::DarkGray))); + } + spans + }, + ), + create_field_line( + FieldSelection::CriticalColor, + { + let mut spans = vec![Span::raw(format!("├─ Critical Color: {} ", critical_color_desc))]; + if let Some(color) = critical_ratatui_color { + spans.push(Span::styled("██".to_string(), Style::default().fg(color))); + } else { + spans.push(Span::styled("--".to_string(), Style::default().fg(Color::DarkGray))); + } + spans + }, + ), + create_field_line( + FieldSelection::WarningBold, + vec![Span::raw(format!( + "├─ Warning Bold: {}", + if warning_bold { "[✓]" } else { "[ ]" } + ))], + ), + create_field_line( + FieldSelection::CriticalBold, + vec![Span::raw(format!( + "├─ Critical Bold: {}", + if critical_bold { "[✓]" } else { "[ ]" } + ))], + ), + ]); + } + + // Add Options field (always last) + lines.push(create_field_line( + FieldSelection::Options, + vec![Span::raw(format!( + "└─ Options: {} items", + segment.options.len() + ))], + )); let text = Text::from(lines); let settings_block = Block::default() .borders(Borders::ALL) diff --git a/src/ui/themes/presets.rs b/src/ui/themes/presets.rs index 0a51dab..0bf424e 100644 --- a/src/ui/themes/presets.rs +++ b/src/ui/themes/presets.rs @@ -134,6 +134,8 @@ impl ThemePresets { theme_cometix::git_segment(), theme_cometix::context_window_segment(), theme_cometix::usage_segment(), + theme_cometix::usage_5hour_segment(), + theme_cometix::usage_7day_segment(), theme_cometix::cost_segment(), theme_cometix::session_segment(), theme_cometix::output_style_segment(), @@ -154,6 +156,8 @@ impl ThemePresets { theme_default::git_segment(), theme_default::context_window_segment(), theme_default::usage_segment(), + theme_default::usage_5hour_segment(), + theme_default::usage_7day_segment(), theme_default::cost_segment(), theme_default::session_segment(), theme_default::output_style_segment(), @@ -174,6 +178,8 @@ impl ThemePresets { theme_minimal::git_segment(), theme_minimal::context_window_segment(), theme_minimal::usage_segment(), + theme_minimal::usage_5hour_segment(), + theme_minimal::usage_7day_segment(), theme_minimal::cost_segment(), theme_minimal::session_segment(), theme_minimal::output_style_segment(), @@ -194,6 +200,8 @@ impl ThemePresets { theme_gruvbox::git_segment(), theme_gruvbox::context_window_segment(), theme_gruvbox::usage_segment(), + theme_gruvbox::usage_5hour_segment(), + theme_gruvbox::usage_7day_segment(), theme_gruvbox::cost_segment(), theme_gruvbox::session_segment(), theme_gruvbox::output_style_segment(), @@ -214,6 +222,8 @@ impl ThemePresets { theme_nord::git_segment(), theme_nord::context_window_segment(), theme_nord::usage_segment(), + theme_nord::usage_5hour_segment(), + theme_nord::usage_7day_segment(), theme_nord::cost_segment(), theme_nord::session_segment(), theme_nord::output_style_segment(), @@ -234,6 +244,8 @@ impl ThemePresets { theme_powerline_dark::git_segment(), theme_powerline_dark::context_window_segment(), theme_powerline_dark::usage_segment(), + theme_powerline_dark::usage_5hour_segment(), + theme_powerline_dark::usage_7day_segment(), theme_powerline_dark::cost_segment(), theme_powerline_dark::session_segment(), theme_powerline_dark::output_style_segment(), @@ -254,6 +266,8 @@ impl ThemePresets { theme_powerline_light::git_segment(), theme_powerline_light::context_window_segment(), theme_powerline_light::usage_segment(), + theme_powerline_light::usage_5hour_segment(), + theme_powerline_light::usage_7day_segment(), theme_powerline_light::cost_segment(), theme_powerline_light::session_segment(), theme_powerline_light::output_style_segment(), @@ -274,6 +288,8 @@ impl ThemePresets { theme_powerline_rose_pine::git_segment(), theme_powerline_rose_pine::context_window_segment(), theme_powerline_rose_pine::usage_segment(), + theme_powerline_rose_pine::usage_5hour_segment(), + theme_powerline_rose_pine::usage_7day_segment(), theme_powerline_rose_pine::cost_segment(), theme_powerline_rose_pine::session_segment(), theme_powerline_rose_pine::output_style_segment(), @@ -294,6 +310,8 @@ impl ThemePresets { theme_powerline_tokyo_night::git_segment(), theme_powerline_tokyo_night::context_window_segment(), theme_powerline_tokyo_night::usage_segment(), + theme_powerline_tokyo_night::usage_5hour_segment(), + theme_powerline_tokyo_night::usage_7day_segment(), theme_powerline_tokyo_night::cost_segment(), theme_powerline_tokyo_night::session_segment(), theme_powerline_tokyo_night::output_style_segment(), diff --git a/src/ui/themes/theme_cometix.rs b/src/ui/themes/theme_cometix.rs index dfcd1e6..16ffb1e 100644 --- a/src/ui/themes/theme_cometix.rs +++ b/src/ui/themes/theme_cometix.rs @@ -162,3 +162,93 @@ pub fn usage_segment() -> SegmentConfig { }, } } + +pub fn usage_5hour_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage5Hour, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color16 { c16: 14 }), + text: Some(AnsiColor::Color16 { c16: 14 }), + background: None, + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} + +pub fn usage_7day_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage7Day, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color16 { c16: 12 }), + text: Some(AnsiColor::Color16 { c16: 12 }), + background: None, + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} diff --git a/src/ui/themes/theme_default.rs b/src/ui/themes/theme_default.rs index 20b21ca..8777a01 100644 --- a/src/ui/themes/theme_default.rs +++ b/src/ui/themes/theme_default.rs @@ -109,6 +109,96 @@ pub fn usage_segment() -> SegmentConfig { } } +pub fn usage_5hour_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage5Hour, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), // circle_slice_1 + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color16 { c16: 14 }), // Cyan + text: Some(AnsiColor::Color16 { c16: 14 }), + background: None, + }, + styles: TextStyleConfig::default(), + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} + +pub fn usage_7day_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage7Day, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), // circle_slice_1 + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color16 { c16: 12 }), // Light Blue (to differentiate from 5hour) + text: Some(AnsiColor::Color16 { c16: 12 }), + background: None, + }, + styles: TextStyleConfig::default(), + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} + pub fn cost_segment() -> SegmentConfig { SegmentConfig { id: SegmentId::Cost, diff --git a/src/ui/themes/theme_gruvbox.rs b/src/ui/themes/theme_gruvbox.rs index 6b071e1..704ebd7 100644 --- a/src/ui/themes/theme_gruvbox.rs +++ b/src/ui/themes/theme_gruvbox.rs @@ -162,3 +162,99 @@ pub fn usage_segment() -> SegmentConfig { }, } } + +pub fn usage_5hour_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage5Hour, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color16 { c16: 14 }), + text: Some(AnsiColor::Color16 { c16: 14 }), + background: None, + }, + styles: TextStyleConfig::default(), + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + // Yellow for warning (16-color palette) + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + // Red for critical (16-color palette) + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + // Bold text for warnings + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} + +pub fn usage_7day_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage7Day, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color16 { c16: 12 }), // Light Blue + text: Some(AnsiColor::Color16 { c16: 12 }), + background: None, + }, + styles: TextStyleConfig::default(), + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + // Yellow for warning (16-color palette) + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + // Red for critical (16-color palette) + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + // Bold text for warnings + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} diff --git a/src/ui/themes/theme_minimal.rs b/src/ui/themes/theme_minimal.rs index 0c1cdd6..2c42539 100644 --- a/src/ui/themes/theme_minimal.rs +++ b/src/ui/themes/theme_minimal.rs @@ -162,3 +162,93 @@ pub fn usage_segment() -> SegmentConfig { }, } } + +pub fn usage_5hour_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage5Hour, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color16 { c16: 14 }), + text: Some(AnsiColor::Color16 { c16: 14 }), + background: None, + }, + styles: TextStyleConfig::default(), + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} + +pub fn usage_7day_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage7Day, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color16 { c16: 11 }), + text: Some(AnsiColor::Color16 { c16: 11 }), + background: None, + }, + styles: TextStyleConfig::default(), + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} diff --git a/src/ui/themes/theme_nord.rs b/src/ui/themes/theme_nord.rs index 4811aab..26691dc 100644 --- a/src/ui/themes/theme_nord.rs +++ b/src/ui/themes/theme_nord.rs @@ -246,3 +246,93 @@ pub fn usage_segment() -> SegmentConfig { }, } } + +pub fn usage_5hour_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage5Hour, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color256 { c256: 110 }), + text: Some(AnsiColor::Color256 { c256: 110 }), + background: Some(AnsiColor::Color256 { c256: 59 }), + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} + +pub fn usage_7day_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage7Day, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color256 { c256: 187 }), + text: Some(AnsiColor::Color256 { c256: 187 }), + background: Some(AnsiColor::Color256 { c256: 59 }), + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} diff --git a/src/ui/themes/theme_powerline_dark.rs b/src/ui/themes/theme_powerline_dark.rs index d003e07..e191862 100644 --- a/src/ui/themes/theme_powerline_dark.rs +++ b/src/ui/themes/theme_powerline_dark.rs @@ -246,3 +246,93 @@ pub fn usage_segment() -> SegmentConfig { }, } } + +pub fn usage_5hour_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage5Hour, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color256 { c256: 16 }), + text: Some(AnsiColor::Color256 { c256: 16 }), + background: Some(AnsiColor::Color256 { c256: 68 }), + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} + +pub fn usage_7day_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage7Day, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color256 { c256: 16 }), + text: Some(AnsiColor::Color256 { c256: 16 }), + background: Some(AnsiColor::Color256 { c256: 144 }), + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} diff --git a/src/ui/themes/theme_powerline_light.rs b/src/ui/themes/theme_powerline_light.rs index c747340..11fc2da 100644 --- a/src/ui/themes/theme_powerline_light.rs +++ b/src/ui/themes/theme_powerline_light.rs @@ -238,3 +238,93 @@ pub fn usage_segment() -> SegmentConfig { }, } } + +pub fn usage_5hour_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage5Hour, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color256 { c256: 16 }), + text: Some(AnsiColor::Color256 { c256: 16 }), + background: Some(AnsiColor::Color256 { c256: 153 }), + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} + +pub fn usage_7day_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage7Day, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color256 { c256: 16 }), + text: Some(AnsiColor::Color256 { c256: 16 }), + background: Some(AnsiColor::Color256 { c256: 185 }), + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} diff --git a/src/ui/themes/theme_powerline_rose_pine.rs b/src/ui/themes/theme_powerline_rose_pine.rs index f0519b2..84be0b7 100644 --- a/src/ui/themes/theme_powerline_rose_pine.rs +++ b/src/ui/themes/theme_powerline_rose_pine.rs @@ -246,3 +246,93 @@ pub fn usage_segment() -> SegmentConfig { }, } } + +pub fn usage_5hour_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage5Hour, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color256 { c256: 253 }), + text: Some(AnsiColor::Color256 { c256: 253 }), + background: Some(AnsiColor::Color256 { c256: 31 }), + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} + +pub fn usage_7day_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage7Day, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color256 { c256: 253 }), + text: Some(AnsiColor::Color256 { c256: 253 }), + background: Some(AnsiColor::Color256 { c256: 156 }), + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} diff --git a/src/ui/themes/theme_powerline_tokyo_night.rs b/src/ui/themes/theme_powerline_tokyo_night.rs index c7e449b..7e8e558 100644 --- a/src/ui/themes/theme_powerline_tokyo_night.rs +++ b/src/ui/themes/theme_powerline_tokyo_night.rs @@ -246,3 +246,93 @@ pub fn usage_segment() -> SegmentConfig { }, } } + +pub fn usage_5hour_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage5Hour, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color256 { c256: 189 }), + text: Some(AnsiColor::Color256 { c256: 189 }), + background: Some(AnsiColor::Color256 { c256: 61 }), + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +} + +pub fn usage_7day_segment() -> SegmentConfig { + SegmentConfig { + id: SegmentId::Usage7Day, + enabled: false, + icon: IconConfig { + plain: "📊".to_string(), + nerd_font: "\u{f0a9e}".to_string(), + }, + colors: ColorConfig { + icon: Some(AnsiColor::Color256 { c256: 189 }), + text: Some(AnsiColor::Color256 { c256: 189 }), + background: Some(AnsiColor::Color256 { c256: 140 }), + }, + styles: TextStyleConfig { text_bold: true }, + options: { + let mut opts = HashMap::new(); + opts.insert( + "warning_threshold".to_string(), + serde_json::Value::Number(60.into()), + ); + opts.insert( + "critical_threshold".to_string(), + serde_json::Value::Number(80.into()), + ); + opts.insert( + "warning_color".to_string(), + serde_json::json!({"c16": 11}), + ); + opts.insert( + "critical_color".to_string(), + serde_json::json!({"c16": 9}), + ); + opts.insert( + "warning_bold".to_string(), + serde_json::Value::Bool(false), + ); + opts.insert( + "critical_bold".to_string(), + serde_json::Value::Bool(true), + ); + opts + }, + } +}