use crate::color; use crate::config::BarStyle; use crate::section::{RenderContext, SectionOutput}; /// Render context bar at a given bar_width. Called both at initial render /// and during flex expansion (with wider bar_width). pub fn render_at_width(ctx: &RenderContext, bar_width: u16) -> Option { let pct = ctx .input .context_window .as_ref()? .used_percentage .unwrap_or(0.0); let pct_int = pct.round() as u16; let filled = (u32::from(pct_int) * u32::from(bar_width) / 100) as usize; let empty = bar_width as usize - filled; let cfg = &ctx.config.sections.context_bar; let thresh = &cfg.thresholds; // Determine characters based on bar_style let (fill_ch, empty_ch) = match cfg.bar_style { BarStyle::Block => { let f = cfg.fill_char.as_deref().unwrap_or("\u{2588}"); let e = cfg.empty_char.as_deref().unwrap_or("\u{2591}"); (f, e) } BarStyle::Classic => { let f = cfg.fill_char.as_deref().unwrap_or("="); let e = cfg.empty_char.as_deref().unwrap_or("-"); (f, e) } }; // Build raw string (always plain, no ANSI) let bar_raw = fill_ch.repeat(filled) + &empty_ch.repeat(empty); let raw = format!("[{bar_raw}] {pct_int}%"); let ansi = if ctx.color_enabled { if cfg.gradient && matches!(cfg.bar_style, BarStyle::Block) { // Per-character gradient coloring render_gradient_bar(filled, empty, bar_width, fill_ch, empty_ch, pct_int) } else { // Single threshold color for entire bar let color_code = threshold_color(pct, thresh); format!("{color_code}[{bar_raw}] {pct_int}%{}", color::RESET) } } else { raw.clone() }; Some(SectionOutput { raw, ansi }) } /// Build a gradient-colored context bar. Each filled char gets a color /// from a green→yellow→red gradient based on its position. fn render_gradient_bar( filled: usize, empty: usize, bar_width: u16, fill_ch: &str, empty_ch: &str, pct_int: u16, ) -> String { let grad = color::make_gradient(&["#50fa7b", "#f1fa8c", "#ff5555"]); let bw = bar_width as f32; let mut result = String::with_capacity(filled * 20 + empty * 10 + 20); result.push('['); for i in 0..filled { let t = if bw > 1.0 { i as f32 / (bw - 1.0) } else { 0.0 }; result.push_str(&color::sample_fg(&grad, t)); result.push_str(fill_ch); } if empty > 0 { result.push_str(color::DIM); for _ in 0..empty { result.push_str(empty_ch); } } result.push_str(color::RESET); result.push_str(&format!("] {pct_int}%")); result } pub fn render(ctx: &RenderContext) -> Option { if !ctx.config.sections.context_bar.base.enabled { return None; } render_at_width(ctx, ctx.config.sections.context_bar.bar_width) } fn threshold_color(pct: f64, thresh: &crate::config::Thresholds) -> String { if pct >= thresh.critical { format!("{}{}", color::RED, color::BOLD) } else if pct >= thresh.danger { color::RED.to_string() } else if pct >= thresh.warn { color::YELLOW.to_string() } else { color::GREEN.to_string() } }