diff --git a/local/recipes/system/redbear-power/source/src/affinity.rs b/local/recipes/system/redbear-power/source/src/affinity.rs index 654af8d692..e994b457aa 100644 --- a/local/recipes/system/redbear-power/source/src/affinity.rs +++ b/local/recipes/system/redbear-power/source/src/affinity.rs @@ -5,7 +5,7 @@ use ratatui::{ widgets::{Block, Borders, Clear, Widget}, }; -use crate::theme; +use crate::theme::Theme; pub struct AffinityEditor { pub open: bool, @@ -123,8 +123,8 @@ impl AffinityEditor { } } -impl Widget for &mut AffinityEditor { - fn render(self, area: Rect, buf: &mut ratatui::buffer::Buffer) { +impl AffinityEditor { + pub fn render_dialog(&mut self, area: Rect, buf: &mut ratatui::buffer::Buffer, theme: &Theme) { Clear.render(area, buf); let cols = self.grid_cols(); let rows = self.num_cpus.div_ceil(cols); @@ -136,7 +136,7 @@ impl Widget for &mut AffinityEditor { let block = Block::default() .borders(Borders::ALL) - .border_style(theme::BORDER_FOCUSED) + .border_style(theme.border_focused) .title(format!(" Affinity: {} (PID {}) ", self.comm, self.pid)); let inner = block.inner(darea); block.render(darea, buf); @@ -161,9 +161,9 @@ impl Widget for &mut AffinityEditor { ratatui::style::Modifier::BOLD | ratatui::style::Modifier::REVERSED, ) } else if is_on { - theme::VALUE + theme.value } else { - theme::VALUE_OFF + theme.value_off }; let marker = if is_on { "+" } else { "-" }; spans.push(Span::styled(format!("{marker}{label} "), style)); @@ -179,16 +179,16 @@ impl Widget for &mut AffinityEditor { Err(e) => format!("Error: {e}"), }; let style = if result.is_ok() { - theme::STATUS_OK + theme.status_ok } else { - theme::STATUS_ERR + theme.status_err }; let para = ratatui::widgets::Paragraph::new(Line::from(Span::styled(msg, style))); para.render(footer_area, buf); } else { let hint = "Space:toggle Enter:apply Esc:cancel Arrows:move"; let para = - ratatui::widgets::Paragraph::new(Line::from(Span::styled(hint, theme::VALUE_OFF))); + ratatui::widgets::Paragraph::new(Line::from(Span::styled(hint, theme.value_off))); para.render(footer_area, buf); } } diff --git a/local/recipes/system/redbear-power/source/src/main.rs b/local/recipes/system/redbear-power/source/src/main.rs index 86f01dfa80..ad9fea57a9 100644 --- a/local/recipes/system/redbear-power/source/src/main.rs +++ b/local/recipes/system/redbear-power/source/src/main.rs @@ -602,11 +602,14 @@ fn main() -> io::Result<()> { } f.render_widget(&app.kill_dialog, full); if app.nice_edit.open { - f.render_widget(&app.nice_edit, full); + app.nice_edit + .render_dialog(full, f.buffer_mut(), &app.theme); } if app.affinity_editor.open { - f.render_widget(&mut app.affinity_editor, full); + app.affinity_editor + .render_dialog(full, f.buffer_mut(), &app.theme); } + if app.show_open_files && let Some(files) = &app.open_files_result { diff --git a/local/recipes/system/redbear-power/source/src/nice_edit.rs b/local/recipes/system/redbear-power/source/src/nice_edit.rs index dfbbe4c40a..ac3793280d 100644 --- a/local/recipes/system/redbear-power/source/src/nice_edit.rs +++ b/local/recipes/system/redbear-power/source/src/nice_edit.rs @@ -5,7 +5,7 @@ use ratatui::{ widgets::{Block, Borders, Clear, Paragraph, Widget}, }; -use crate::theme; +use crate::theme::Theme; const MIN_NICE: i32 = -20; const MAX_NICE: i32 = 19; @@ -55,8 +55,8 @@ impl NiceEdit { } } -impl Widget for &NiceEdit { - fn render(self, area: Rect, buf: &mut ratatui::buffer::Buffer) { +impl NiceEdit { + pub fn render_dialog(&self, area: Rect, buf: &mut ratatui::buffer::Buffer, theme: &Theme) { let width = 48u16.min(area.width); let height = 9u16.min(area.height); let x = area.x + (area.width - width) / 2; @@ -87,7 +87,7 @@ impl Widget for &NiceEdit { let info = Paragraph::new(vec![ Line::from(Span::styled( format!(" PID {} {}", self.pid, self.comm), - theme::VALUE, + theme.value, )), Line::from(""), ]); @@ -109,11 +109,11 @@ impl Widget for &NiceEdit { Span::styled( bar, if self.value < 0 { - Style::default().fg(theme::STATUS_OK.fg.unwrap_or_default()) + Style::default().fg(theme.status_ok.fg.unwrap_or_default()) } else if self.value > 0 { - Style::default().fg(theme::STATUS_WARN.fg.unwrap_or_default()) + Style::default().fg(theme.status_warn.fg.unwrap_or_default()) } else { - theme::VALUE + theme.value }, ), Span::raw("]"), @@ -128,14 +128,14 @@ impl Widget for &NiceEdit { match res { Ok(()) => Line::from(Span::styled( " Applied \u{2014} press Esc to close", - theme::STATUS_OK, + theme.status_ok, )), - Err(e) => Line::from(Span::styled(format!(" Error: {}", e), theme::STATUS_ERR)), + Err(e) => Line::from(Span::styled(format!(" Error: {}", e), theme.status_err)), } } else { Line::from(Span::styled( " \u{2191}\u{2193}/+- adjust, Enter=apply, Esc=close", - theme::VALUE_OFF, + theme.value_off, )) }; Widget::render(Paragraph::new(msg), msg_area, buf); diff --git a/local/recipes/system/redbear-power/source/src/render.rs b/local/recipes/system/redbear-power/source/src/render.rs index 4629b746cc..6686412269 100644 --- a/local/recipes/system/redbear-power/source/src/render.rs +++ b/local/recipes/system/redbear-power/source/src/render.rs @@ -126,6 +126,7 @@ pub fn panel_border<'a>(focused: bool, title: &'a str, theme: &Theme) -> Block<' /// Build a pulsing full-width PROCHOT alert bar, or `None` if no CPU /// has PROCHOT asserted. pub fn render_prochot_alert(app: &App, frame: &Frame) -> Option> { + let theme = &app.theme; if !app.cpus.iter().any(|c| c.prochot) { return None; } @@ -143,25 +144,27 @@ pub fn render_prochot_alert(app: &App, frame: &Frame) -> Option(app: &'a App) -> Option> { - use crate::theme; + let theme = &app.theme; let msg = app.active_toast()?; + Some( Paragraph::new(Line::from(vec![ - ratatui::text::Span::styled(" \u{25CF} ", theme::STATUS_OK), - ratatui::text::Span::styled(msg, theme::VALUE), + ratatui::text::Span::styled(" \u{25CF} ", theme.status_ok), + ratatui::text::Span::styled(msg, theme.value), ])) - .style(theme::VALUE) + .style(theme.value) .block(Block::default().borders(Borders::ALL).title(" Notice ")) .alignment(ratatui::layout::Alignment::Left), ) } pub fn render_header<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { + let theme = &app.theme; let pkg_temp = app .cpus .iter() @@ -171,114 +174,114 @@ pub fn render_header<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { .unwrap_or_else(|| "n/a".into()); let lines = vec![ Line::from(vec![ - "Vendor: ".set_style(theme::LABEL), + "Vendor: ".set_style(theme.label), format!("{} ", app.cpu_vendor).into(), - "Model: ".set_style(theme::LABEL), + "Model: ".set_style(theme.label), app.cpu_model.as_str().into(), ]), Line::from(vec![ - "Cores: ".set_style(theme::LABEL), + "Cores: ".set_style(theme.label), format!("{} ", app.cpus.len()).into(), - "Governor: ".set_style(theme::LABEL), + "Governor: ".set_style(theme.label), app.cpufreq .active .as_str() .set_style(app.theme.header_governor), " ".into(), - "Throttle: ".set_style(theme::LABEL), + "Throttle: ".set_style(theme.label), match app.throttle { - ThrottleMode::Auto => "AUTO".set_style(theme::HEADER_THROTTLE_AUTO), - ThrottleMode::User => "USER".set_style(theme::HEADER_THROTTLE_USER), - ThrottleMode::ForcedMin => "FORCED MIN".set_style(theme::HEADER_THROTTLE_FORCED), + ThrottleMode::Auto => "AUTO".set_style(theme.header_throttle_auto), + ThrottleMode::User => "USER".set_style(theme.header_throttle_user), + ThrottleMode::ForcedMin => "FORCED MIN".set_style(theme.header_throttle_forced), }, ]), Line::from(vec![ - "Pkg: ".set_style(theme::LABEL), + "Pkg: ".set_style(theme.label), pkg_temp.set_style(Style::default().fg(theme::temp_color( app.cpus.iter().filter_map(|c| c.temp_c).max(), ))), " ".into(), - "PkgW: ".set_style(theme::LABEL), + "PkgW: ".set_style(theme.label), app.pkg_power_w .map(|w| format!("{w:.1} W")) .unwrap_or_else(|| app.rapl_status.clone()) .set_style(if app.pkg_power_w.is_some() { - theme::VALUE + theme.value } else { - theme::VALUE_OFF + theme.value_off }), " ".into(), - "P-state source: ".set_style(theme::LABEL), + "P-state source: ".set_style(theme.label), app.pss_source.as_str().into(), ]), Line::from(vec![ - "SIMD: ".set_style(theme::LABEL), - app.simd.as_str().set_style(theme::VALUE), + "SIMD: ".set_style(theme.label), + app.simd.as_str().set_style(theme.value), " ".into(), - "Cache: ".set_style(theme::LABEL), - app.cache_summary.as_str().set_style(theme::VALUE), + "Cache: ".set_style(theme.label), + app.cache_summary.as_str().set_style(theme.value), ]), Line::from(vec![ - "Sources: ".set_style(theme::LABEL), - "MSR=".set_style(theme::VALUE_OFF), + "Sources: ".set_style(theme.label), + "MSR=".set_style(theme.value_off), if app.msr_available { - "ok".set_style(theme::VALUE_OK) + "ok".set_style(theme.value_ok) } else { - "no".set_style(theme::STATUS_ERR) + "no".set_style(theme.status_err) }, - " PSS=".set_style(theme::VALUE_OFF), + " PSS=".set_style(theme.value_off), if app.pss_available { - "ok".set_style(theme::VALUE_OK) + "ok".set_style(theme.value_ok) } else { - "no".set_style(theme::STATUS_ERR) + "no".set_style(theme.status_err) }, - " load=".set_style(theme::VALUE_OFF), + " load=".set_style(theme.value_off), if app.load_available { - "ok".set_style(theme::VALUE_OK) + "ok".set_style(theme.value_ok) } else { - "no".set_style(theme::STATUS_ERR) + "no".set_style(theme.status_err) }, - " gov=".set_style(theme::VALUE_OFF), + " gov=".set_style(theme.value_off), if app.governor_available { - "ok".set_style(theme::VALUE_OK) + "ok".set_style(theme.value_ok) } else { - "no".set_style(theme::STATUS_ERR) + "no".set_style(theme.status_err) }, - " hwmon=".set_style(theme::VALUE_OFF), + " hwmon=".set_style(theme.value_off), if app.hwmon_available { - "ok".set_style(theme::VALUE_OK) + "ok".set_style(theme.value_ok) } else { - "no".set_style(theme::STATUS_ERR) + "no".set_style(theme.status_err) }, - " dmi=".set_style(theme::VALUE_OFF), + " dmi=".set_style(theme.value_off), if !app.dmi.is_empty() { - "ok".set_style(theme::VALUE_OK) + "ok".set_style(theme.value_ok) } else { - "no".set_style(theme::STATUS_ERR) + "no".set_style(theme.status_err) }, ]), Line::from(vec![ - "Hybrid: ".set_style(theme::LABEL), + "Hybrid: ".set_style(theme.label), if app.hybrid_summary.is_empty() { - "non-hybrid".set_style(theme::VALUE_OFF) + "non-hybrid".set_style(theme.value_off) } else { - app.hybrid_summary.as_str().set_style(theme::VALUE_HOT) + app.hybrid_summary.as_str().set_style(theme.value_hot) }, ]), Line::from(vec![ - "Daemons: ".set_style(theme::LABEL), - "cpufreqd=".set_style(theme::VALUE_OFF), + "Daemons: ".set_style(theme.label), + "cpufreqd=".set_style(theme.value_off), if app.cpufreqd_available { - "up".set_style(theme::VALUE_OK) + "up".set_style(theme.value_ok) } else { - "DOWN".set_style(theme::STATUS_ERR) + "DOWN".set_style(theme.status_err) }, " ".into(), - "thermald=".set_style(theme::VALUE_OFF), + "thermald=".set_style(theme.value_off), if app.thermald_available { - "up".set_style(theme::VALUE_OK) + "up".set_style(theme.value_ok) } else { - "DOWN".set_style(theme::STATUS_ERR) + "DOWN".set_style(theme.status_err) }, ]), ]; @@ -309,6 +312,7 @@ fn mem_bar_line<'a>( value_kib: u64, total_kib: u64, color: Style, + theme: &Theme, ) -> Line<'a> { let bar_width: usize = 20; let filled = ((percent.clamp(0.0, 100.0) / 100.0) * bar_width as f64) as usize; @@ -321,15 +325,15 @@ fn mem_bar_line<'a>( } // Threshold gradient: green <50%, yellow 50-75%, red >75%. let threshold_color = if percent < 50.0 { - theme::VALUE_OK + theme.value_ok } else if percent < 75.0 { - theme::STATUS_WARN + theme.status_warn } else { - theme::VALUE_HOT + theme.value_hot }; // Size-based tint: apply Modifiers (BOLD at 50%, BOLD|REVERSED at 90%+) // for visual emphasis at high pressure — tlc size_tint pattern. - let mut percent_style = theme::VALUE; + let mut percent_style = theme.value; if percent >= 90.0 { percent_style = percent_style.add_modifier(ratatui::style::Modifier::BOLD); } @@ -337,7 +341,7 @@ fn mem_bar_line<'a>( percent_style = percent_style.add_modifier(ratatui::style::Modifier::REVERSED); } Line::from(vec![ - label.set_style(theme::LABEL), + label.set_style(theme.label), format!("[{}] ", bar).set_style(if filled > 0 { threshold_color } else { color }), format!("{:5.1}% ", percent).set_style(percent_style), format!( @@ -345,7 +349,7 @@ fn mem_bar_line<'a>( crate::render::format_kib(value_kib), crate::render::format_kib(total_kib) ) - .set_style(theme::VALUE_OFF), + .set_style(theme.value_off), ]) } @@ -353,6 +357,7 @@ fn mem_bar_line<'a>( /// with the active tab highlighted. Hotkeys `1`/`2`/`3`/`4` switch /// directly; `T` cycles through them in order. pub fn render_tab_bar<'a>(app: &'a App) -> Tabs<'a> { + let theme = &app.theme; let proc_count = app.processes.processes.len(); let titles: Vec> = [ TabId::PerCpu, @@ -388,14 +393,15 @@ pub fn render_tab_bar<'a>(app: &'a App) -> Tabs<'a> { }; Tabs::new(titles) .select(selected) - .style(theme::BORDER_DIM) - .highlight_style(theme::LABEL_BOLD) + .style(theme.border_dim) + .highlight_style(theme.label_bold) .divider(" │ ") } /// Render the System tab (memory/uptime/etc). Uses `/proc/meminfo` on /// Linux and `/scheme/sys/mem` on Redox if present. pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { + let theme = &app.theme; let mut lines: Vec> = Vec::new(); let n = app.cpus.len(); let avg_freq: f64 = if n > 0 { @@ -417,76 +423,76 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { .map(|w| w as f64 / 1000.0) .sum(); lines.push(Line::from(vec![ - "Cores: ".set_style(theme::LABEL), - format!("{n}").set_style(theme::VALUE), - " AvgFreq: ".set_style(theme::LABEL), - format!("{avg_freq:.0} MHz").set_style(theme::VALUE), - " MaxTemp: ".set_style(theme::LABEL), - max_temp.set_style(theme::VALUE), - " TotalPkg: ".set_style(theme::LABEL), - format!("{total_pkgw:.1} W").set_style(theme::VALUE), - " Load: ".set_style(theme::LABEL), + "Cores: ".set_style(theme.label), + format!("{n}").set_style(theme.value), + " AvgFreq: ".set_style(theme.label), + format!("{avg_freq:.0} MHz").set_style(theme.value), + " MaxTemp: ".set_style(theme.label), + max_temp.set_style(theme.value), + " TotalPkg: ".set_style(theme.label), + format!("{total_pkgw:.1} W").set_style(theme.value), + " Load: ".set_style(theme.label), match app.load_avg { - Some((a, b, c)) => format!("{a:.2} {b:.2} {c:.2}").set_style(theme::VALUE), - None => "n/a".set_style(theme::VALUE_OFF), + Some((a, b, c)) => format!("{a:.2} {b:.2} {c:.2}").set_style(theme.value), + None => "n/a".set_style(theme.value_off), }, ])); let any_prochot = app.cpus.iter().any(|c| c.prochot); let any_critical = app.cpus.iter().any(|c| c.critical); let any_pl = app.cpus.iter().any(|c| c.power_limit); lines.push(Line::from(vec![ - "Aggregate flags: ".set_style(theme::LABEL), + "Aggregate flags: ".set_style(theme.label), if any_prochot { - "PROCHOT ".set_style(theme::PROCHOT_FLAG) + "PROCHOT ".set_style(theme.prochot_flag) } else { - "PROCHOT ".set_style(theme::VALUE_OFF) + "PROCHOT ".set_style(theme.value_off) }, if any_critical { - "CRIT ".set_style(theme::VALUE_HOT) + "CRIT ".set_style(theme.value_hot) } else { - "CRIT ".set_style(theme::VALUE_OFF) + "CRIT ".set_style(theme.value_off) }, if any_pl { - "PL ".set_style(theme::POWER_LIMIT_FLAG) + "PL ".set_style(theme.power_limit_flag) } else { - "PL ".set_style(theme::VALUE_OFF) + "PL ".set_style(theme.value_off) }, ])); { let s = &app.sched_stats; let mut parts: Vec = Vec::new(); - parts.push("Sched: ".set_style(theme::LABEL)); + parts.push("Sched: ".set_style(theme.label)); let mut wrote = false; if let Some(v) = s.context_switches { - parts.push(format!("switches={v} ").set_style(theme::VALUE)); + parts.push(format!("switches={v} ").set_style(theme.value)); wrote = true; } if let Some(v) = s.contexts_created { - parts.push(format!("created={v} ").set_style(theme::VALUE)); + parts.push(format!("created={v} ").set_style(theme.value)); wrote = true; } if let Some(v) = s.contexts_running { - parts.push(format!("running={v} ").set_style(theme::VALUE)); + parts.push(format!("running={v} ").set_style(theme.value)); wrote = true; } if let Some(v) = s.contexts_blocked { - parts.push(format!("blocked={v} ").set_style(theme::VALUE)); + parts.push(format!("blocked={v} ").set_style(theme.value)); wrote = true; } if let Some(v) = s.total_irqs { - parts.push(format!("IRQs={v} ").set_style(theme::VALUE)); + parts.push(format!("IRQs={v} ").set_style(theme.value)); wrote = true; } if !s.per_cpu_steals.is_empty() { let total_steals: u64 = s.per_cpu_steals.iter().sum(); - parts.push(format!("steals={total_steals} ").set_style(theme::VALUE)); + parts.push(format!("steals={total_steals} ").set_style(theme.value)); wrote = true; } if !s.per_cpu_queue_depth.is_empty() { let total_qd: u64 = s.per_cpu_queue_depth.iter().sum(); let avg_qd = total_qd / s.per_cpu_queue_depth.len() as u64; - parts.push(format!("avg_qd={avg_qd}").set_style(theme::VALUE)); + parts.push(format!("avg_qd={avg_qd}").set_style(theme.value)); wrote = true; } if wrote { @@ -500,18 +506,18 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { || app.dram_power_w.is_some() { let mut parts: Vec = Vec::new(); - parts.push("Power: ".set_style(theme::LABEL)); + parts.push("Power: ".set_style(theme.label)); if let Some(w) = app.pkg_power_w { - parts.push(format!("Pkg {w:.1}W ").set_style(theme::VALUE)); + parts.push(format!("Pkg {w:.1}W ").set_style(theme.value)); } if let Some(w) = app.pp0_power_w { - parts.push(format!("Core {w:.1}W ").set_style(theme::VALUE)); + parts.push(format!("Core {w:.1}W ").set_style(theme.value)); } if let Some(w) = app.pp1_power_w { - parts.push(format!("Uncore {w:.1}W ").set_style(theme::VALUE)); + parts.push(format!("Uncore {w:.1}W ").set_style(theme.value)); } if let Some(w) = app.dram_power_w { - parts.push(format!("DRAM {w:.1}W").set_style(theme::VALUE)); + parts.push(format!("DRAM {w:.1}W").set_style(theme.value)); } lines.push(Line::from(parts)); } @@ -519,26 +525,26 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { // OS identity (matches cpu-x System tab) if app.os_info.available { lines.push(Line::from(vec![ - "OS: ".set_style(theme::LABEL), + "OS: ".set_style(theme.label), if app.os_info.name.is_empty() { - "(unknown)".set_style(theme::VALUE_OFF) + "(unknown)".set_style(theme.value_off) } else { - app.os_info.name.as_str().set_style(theme::VALUE) + app.os_info.name.as_str().set_style(theme.value) }, - " Kernel: ".set_style(theme::LABEL), + " Kernel: ".set_style(theme.label), if app.os_info.kernel.is_empty() { - "(unknown)".set_style(theme::VALUE_OFF) + "(unknown)".set_style(theme.value_off) } else { - app.os_info.kernel.as_str().set_style(theme::VALUE) + app.os_info.kernel.as_str().set_style(theme.value) }, - " Host: ".set_style(theme::LABEL), + " Host: ".set_style(theme.label), if app.os_info.hostname.is_empty() { - "(unknown)".set_style(theme::VALUE_OFF) + "(unknown)".set_style(theme.value_off) } else { - app.os_info.hostname.as_str().set_style(theme::VALUE) + app.os_info.hostname.as_str().set_style(theme.value) }, - " Up: ".set_style(theme::LABEL), - crate::meminfo::format_uptime(app.os_info.uptime_secs).set_style(theme::VALUE_HOT), + " Up: ".set_style(theme.label), + crate::meminfo::format_uptime(app.os_info.uptime_secs).set_style(theme.value_hot), ])); } @@ -547,41 +553,45 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { if app.meminfo.available { let mi = &app.meminfo; lines.push(Line::from(vec![ - "Mem: ".set_style(theme::LABEL_BOLD), + "Mem: ".set_style(theme.label_bold), format!( "{} used / {} total", crate::render::format_kib(mi.used_kib), crate::render::format_kib(mi.total_kib) ) - .set_style(theme::VALUE), + .set_style(theme.value), ])); lines.push(mem_bar_line( "Used: ", mi.percent_used(), mi.used_kib, mi.total_kib, - theme::VALUE_HOT, + theme.value_hot, + theme, )); lines.push(mem_bar_line( "Buffers: ", mi.percent_buffers(), mi.buffers_kib, mi.total_kib, - theme::VALUE, + theme.value, + theme, )); lines.push(mem_bar_line( "Cached: ", mi.percent_cached(), mi.cached_kib, mi.total_kib, - theme::VALUE, + theme.value, + theme, )); lines.push(mem_bar_line( "Free: ", mi.percent_free(), mi.free_kib, mi.total_kib, - theme::VALUE_OK, + theme.value_ok, + theme, )); if mi.swap_total_kib > 0 { lines.push(mem_bar_line( @@ -589,7 +599,8 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { mi.percent_swap(), mi.swap_used_kib, mi.swap_total_kib, - theme::STATUS_WARN, + theme.status_warn, + theme, )); } } @@ -597,26 +608,26 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { let cs = &app.collector_stats; if cs.thread_count > 1 { lines.push(Line::from(vec![ - "Collector: ".set_style(theme::LABEL), - format!("{} threads", cs.thread_count).set_style(theme::VALUE), - " pinned: ".set_style(theme::LABEL), - format!("{}/{}", cs.pinned_count, cs.barrier_size).set_style(theme::VALUE), - " barrier: ".set_style(theme::LABEL), - format!("{}", cs.barrier_size).set_style(theme::VALUE), - " last: ".set_style(theme::LABEL), - format!("{:.2} ms", cs.elapsed_us as f64 / 1000.0).set_style(theme::VALUE), + "Collector: ".set_style(theme.label), + format!("{} threads", cs.thread_count).set_style(theme.value), + " pinned: ".set_style(theme.label), + format!("{}/{}", cs.pinned_count, cs.barrier_size).set_style(theme.value), + " barrier: ".set_style(theme.label), + format!("{}", cs.barrier_size).set_style(theme.value), + " last: ".set_style(theme.label), + format!("{:.2} ms", cs.elapsed_us as f64 / 1000.0).set_style(theme.value), ])); } else { lines.push(Line::from(vec![ - "Collector: ".set_style(theme::LABEL), - "single-threaded".set_style(theme::VALUE_OFF), + "Collector: ".set_style(theme.label), + "single-threaded".set_style(theme.value_off), ])); } lines.push(Line::from(vec![if app.bench_line.is_empty() { - "(idle)".set_style(theme::VALUE_OFF) + "(idle)".set_style(theme.value_off) } else { - app.bench_line.as_str().set_style(theme::VALUE) + app.bench_line.as_str().set_style(theme.value) }])); Paragraph::new(lines) .block(panel_border(focused, " System ", &app.theme)) @@ -625,6 +636,7 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { /// Render the Info tab (static CPU identification details). pub fn render_info_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { + let theme = &app.theme; let family = app.cpuid_info.family; let model = app.cpuid_info.model_id; let stepping = app.cpuid_info.stepping; @@ -664,7 +676,7 @@ pub fn render_info_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { " L1d: {} KB, {}-way, {}B line", c.size_kb, c.associativity, c.line_bytes ) - .set_style(theme::VALUE), + .set_style(theme.value), )); } if let Some(c) = caches.l1i { @@ -673,7 +685,7 @@ pub fn render_info_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { " L1i: {} KB, {}-way, {}B line", c.size_kb, c.associativity, c.line_bytes ) - .set_style(theme::VALUE), + .set_style(theme.value), )); } if let Some(c) = caches.l2 { @@ -682,7 +694,7 @@ pub fn render_info_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { " L2: {} KB, {}-way, {}B line", c.size_kb, c.associativity, c.line_bytes ) - .set_style(theme::VALUE), + .set_style(theme.value), )); } if let Some(c) = caches.l3 { @@ -696,24 +708,24 @@ pub fn render_info_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { " L3: {size}, {}-way, {}B line", c.associativity, c.line_bytes ) - .set_style(theme::VALUE), + .set_style(theme.value), )); } let mut lines = vec![ Line::from(vec![ - "Vendor: ".set_style(theme::LABEL), - app.cpu_vendor.as_str().set_style(theme::VALUE), - " Model: ".set_style(theme::LABEL), - app.cpu_model.as_str().set_style(theme::VALUE), + "Vendor: ".set_style(theme.label), + app.cpu_vendor.as_str().set_style(theme.value), + " Model: ".set_style(theme.label), + app.cpu_model.as_str().set_style(theme.value), ]), Line::from( format!("Family {family:#x}, Model {model:#x}, Stepping {stepping:#x}") - .set_style(theme::VALUE), + .set_style(theme.value), ), - Line::from(format!("Flags: {flag_str}").set_style(theme::VALUE)), + Line::from(format!("Flags: {flag_str}").set_style(theme.value)), ]; if !cache_lines.is_empty() { - lines.push(Line::from("Caches:".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("Caches:".set_style(theme.label_bold))); lines.extend(cache_lines); } if app.cpuid_info.hybrid.is_hybrid { @@ -723,7 +735,7 @@ pub fn render_info_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { app.hybrid_summary.as_str() }; lines.push(Line::from( - format!("Hybrid: {summary}").set_style(theme::VALUE_HOT), + format!("Hybrid: {summary}").set_style(theme.value_hot), )); } Paragraph::new(lines) @@ -733,6 +745,7 @@ pub fn render_info_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { #[allow(clippy::vec_init_then_push)] pub fn render_motherboard_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { + let theme = &app.theme; let dmi = &app.dmi; let empty_msg = if dmi.is_empty() { "(no DMI data — /sys/class/dmi/id not readable)" @@ -740,91 +753,91 @@ pub fn render_motherboard_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a "" }; let mut lines: Vec> = Vec::new(); - lines.push(Line::from("System".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("System".set_style(theme.label_bold))); lines.push(Line::from(vec![ - " Manufacturer: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.sys_vendor).set_style(theme::VALUE), + " Manufacturer: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.sys_vendor).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Product: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.product_name).set_style(theme::VALUE), + " Product: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.product_name).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Family: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.product_family).set_style(theme::VALUE), + " Family: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.product_family).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Version: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.product_version).set_style(theme::VALUE), + " Version: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.product_version).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Serial: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.product_serial).set_style(theme::VALUE), + " Serial: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.product_serial).set_style(theme.value), ])); lines.push(Line::from(vec![ - " UUID: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.product_uuid).set_style(theme::VALUE), + " UUID: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.product_uuid).set_style(theme.value), ])); lines.push(Line::from("")); - lines.push(Line::from("Board".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("Board".set_style(theme.label_bold))); lines.push(Line::from(vec![ - " Manufacturer: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.board_vendor).set_style(theme::VALUE), + " Manufacturer: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.board_vendor).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Name: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.board_name).set_style(theme::VALUE), + " Name: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.board_name).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Version: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.board_version).set_style(theme::VALUE), + " Version: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.board_version).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Serial: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.board_serial).set_style(theme::VALUE), + " Serial: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.board_serial).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Asset Tag: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.board_asset_tag).set_style(theme::VALUE), + " Asset Tag: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.board_asset_tag).set_style(theme.value), ])); lines.push(Line::from("")); - lines.push(Line::from("BIOS".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("BIOS".set_style(theme.label_bold))); lines.push(Line::from(vec![ - " Vendor: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.bios_vendor).set_style(theme::VALUE), + " Vendor: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.bios_vendor).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Version: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.bios_version).set_style(theme::VALUE), + " Version: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.bios_version).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Date: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.bios_date).set_style(theme::VALUE), + " Date: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.bios_date).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Release: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.bios_release).set_style(theme::VALUE), + " Release: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.bios_release).set_style(theme.value), ])); lines.push(Line::from("")); - lines.push(Line::from("Chassis".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("Chassis".set_style(theme.label_bold))); lines.push(Line::from(vec![ - " Vendor: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.chassis_vendor).set_style(theme::VALUE), + " Vendor: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.chassis_vendor).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Type: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.chassis_type).set_style(theme::VALUE), + " Type: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.chassis_type).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Version: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.chassis_version).set_style(theme::VALUE), + " Version: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.chassis_version).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Asset Tag: ".set_style(theme::LABEL), - crate::dmi::DmiInfo::display(&dmi.chassis_asset_tag).set_style(theme::VALUE), + " Asset Tag: ".set_style(theme.label), + crate::dmi::DmiInfo::display(&dmi.chassis_asset_tag).set_style(theme.value), ])); if !empty_msg.is_empty() { - lines.push(Line::from(empty_msg.set_style(theme::VALUE_WARM))); + lines.push(Line::from(empty_msg.set_style(theme.value_warm))); } Paragraph::new(lines) .block(panel_border(focused, " Motherboard ", &app.theme)) @@ -832,88 +845,89 @@ pub fn render_motherboard_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a } pub fn render_battery_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { + let theme = &app.theme; let bat = &app.battery; if !bat.available { return Paragraph::new(Line::from( "(no battery detected — /sys/class/power_supply/BAT* not present)" - .set_style(theme::VALUE_WARM), + .set_style(theme.value_warm), )) .block(panel_border(focused, " Battery ", &app.theme)) .wrap(Wrap { trim: true }); } let mut lines: Vec> = Vec::new(); - lines.push(Line::from("Identity".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("Identity".set_style(theme.label_bold))); lines.push(Line::from(vec![ - " Manufacturer: ".set_style(theme::LABEL), - crate::battery::BatteryInfo::display(&bat.manufacturer).set_style(theme::VALUE), + " Manufacturer: ".set_style(theme.label), + crate::battery::BatteryInfo::display(&bat.manufacturer).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Model: ".set_style(theme::LABEL), - crate::battery::BatteryInfo::display(&bat.model_name).set_style(theme::VALUE), + " Model: ".set_style(theme.label), + crate::battery::BatteryInfo::display(&bat.model_name).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Technology: ".set_style(theme::LABEL), - crate::battery::BatteryInfo::display(&bat.technology).set_style(theme::VALUE), + " Technology: ".set_style(theme.label), + crate::battery::BatteryInfo::display(&bat.technology).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Serial: ".set_style(theme::LABEL), - crate::battery::BatteryInfo::display(&bat.serial_number).set_style(theme::VALUE), + " Serial: ".set_style(theme.label), + crate::battery::BatteryInfo::display(&bat.serial_number).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Cycles: ".set_style(theme::LABEL), - crate::battery::BatteryInfo::display_u32(&bat.cycle_count).set_style(theme::VALUE), + " Cycles: ".set_style(theme.label), + crate::battery::BatteryInfo::display_u32(&bat.cycle_count).set_style(theme.value), ])); lines.push(Line::from("")); - lines.push(Line::from("State".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("State".set_style(theme.label_bold))); lines.push(Line::from(vec![ - " Status: ".set_style(theme::LABEL), - crate::battery::BatteryInfo::display(&bat.status).set_style(theme::VALUE), + " Status: ".set_style(theme.label), + crate::battery::BatteryInfo::display(&bat.status).set_style(theme.value), ])); lines.push(Line::from(vec![ - " Capacity: ".set_style(theme::LABEL), - crate::battery::BatteryInfo::display_u32(&bat.capacity_percent).set_style(theme::VALUE), - "%".set_style(theme::VALUE), + " Capacity: ".set_style(theme.label), + crate::battery::BatteryInfo::display_u32(&bat.capacity_percent).set_style(theme.value), + "%".set_style(theme.value), ])); lines.push(Line::from(vec![ - " Energy: ".set_style(theme::LABEL), - crate::battery::BatteryInfo::display_f64(&bat.energy_now_wh).set_style(theme::VALUE), - " Wh".set_style(theme::VALUE), - " / ".set_style(theme::VALUE), - crate::battery::BatteryInfo::display_f64(&bat.energy_full_wh).set_style(theme::VALUE), - " Wh".set_style(theme::VALUE), + " Energy: ".set_style(theme.label), + crate::battery::BatteryInfo::display_f64(&bat.energy_now_wh).set_style(theme.value), + " Wh".set_style(theme.value), + " / ".set_style(theme.value), + crate::battery::BatteryInfo::display_f64(&bat.energy_full_wh).set_style(theme.value), + " Wh".set_style(theme.value), ])); lines.push(Line::from(vec![ - " Health: ".set_style(theme::LABEL), - crate::battery::BatteryInfo::display_u32(&bat.health_percent()).set_style(theme::VALUE), - "%".set_style(theme::VALUE), - " (current charge / full charge)".set_style(theme::VALUE_OFF), + " Health: ".set_style(theme.label), + crate::battery::BatteryInfo::display_u32(&bat.health_percent()).set_style(theme.value), + "%".set_style(theme.value), + " (current charge / full charge)".set_style(theme.value_off), ])); lines.push(Line::from("")); - lines.push(Line::from("Power".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("Power".set_style(theme.label_bold))); lines.push(Line::from(vec![ - " Power: ".set_style(theme::LABEL), - crate::battery::BatteryInfo::display_f64(&bat.power_now_w).set_style(theme::VALUE), - " W".set_style(theme::VALUE), + " Power: ".set_style(theme.label), + crate::battery::BatteryInfo::display_f64(&bat.power_now_w).set_style(theme.value), + " W".set_style(theme.value), ])); lines.push(Line::from(vec![ - " Voltage: ".set_style(theme::LABEL), - crate::battery::BatteryInfo::display_f64(&bat.voltage_now_v).set_style(theme::VALUE), - " V".set_style(theme::VALUE), + " Voltage: ".set_style(theme.label), + crate::battery::BatteryInfo::display_f64(&bat.voltage_now_v).set_style(theme.value), + " V".set_style(theme.value), ])); lines.push(Line::from(vec![ - " Time to empty: ".set_style(theme::LABEL), + " Time to empty: ".set_style(theme.label), if let Some(s) = bat.time_to_empty_s { - crate::battery::BatteryInfo::format_duration(s).set_style(theme::VALUE) + crate::battery::BatteryInfo::format_duration(s).set_style(theme.value) } else { - "?".set_style(theme::VALUE_OFF) + "?".set_style(theme.value_off) }, ])); lines.push(Line::from(vec![ - " Time to full: ".set_style(theme::LABEL), + " Time to full: ".set_style(theme.label), if let Some(s) = bat.time_to_full_s { - crate::battery::BatteryInfo::format_duration(s).set_style(theme::VALUE) + crate::battery::BatteryInfo::format_duration(s).set_style(theme.value) } else { - "?".set_style(theme::VALUE_OFF) + "?".set_style(theme.value_off) }, ])); Paragraph::new(lines) @@ -922,10 +936,11 @@ pub fn render_battery_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { } pub fn render_sensor_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { + let theme = &app.theme; let sensors = &app.sensors; if sensors.is_empty() { return Paragraph::new(Line::from( - "(no sensors detected — /sys/class/hwmon/ not readable)".set_style(theme::VALUE_WARM), + "(no sensors detected — /sys/class/hwmon/ not readable)".set_style(theme.value_warm), )) .block(panel_border(focused, " Sensors ", &app.theme)) .wrap(Wrap { trim: true }); @@ -937,19 +952,19 @@ pub fn render_sensor_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { sensors.chips.len(), sensors.total_readings() ) - .set_style(theme::LABEL_BOLD), + .set_style(theme.label_bold), )); lines.push(Line::from("")); for chip in &sensors.chips { lines.push(Line::from( - format!("▸ {}", chip.name).set_style(theme::LABEL_BOLD), + format!("▸ {}", chip.name).set_style(theme.label_bold), )); for reading in &chip.readings { let label_str = reading.label.as_deref().unwrap_or(reading.kind.name()); lines.push(Line::from(vec![ " ".into(), - format!("{:<12}", label_str).set_style(theme::LABEL), - format!("{:>14}", reading.display_value).set_style(theme::VALUE), + format!("{:<12}", label_str).set_style(theme.label), + format!("{:>14}", reading.display_value).set_style(theme.value), ])); } lines.push(Line::from("")); @@ -960,83 +975,84 @@ pub fn render_sensor_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { } pub fn render_network_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { + let theme = &app.theme; let net = &app.net; if net.is_empty() { return Paragraph::new(Line::from( "(no network interfaces detected — /sys/class/net/ not readable)" - .set_style(theme::VALUE_WARM), + .set_style(theme.value_warm), )) .block(panel_border(focused, " Network ", &app.theme)) .wrap(Wrap { trim: true }); } let mut lines: Vec> = Vec::new(); lines.push(Line::from( - format!("Detected {} interface(s):", net.count()).set_style(theme::LABEL_BOLD), + format!("Detected {} interface(s):", net.count()).set_style(theme.label_bold), )); lines.push(Line::from("")); for iface in &net.interfaces { lines.push(Line::from( - format!("▸ {}", iface.name).set_style(theme::LABEL_BOLD), + format!("▸ {}", iface.name).set_style(theme.label_bold), )); lines.push(Line::from(vec![ - " State: ".set_style(theme::LABEL), + " State: ".set_style(theme.label), iface .operstate .as_deref() .unwrap_or("?") - .set_style(theme::VALUE), + .set_style(theme.value), ])); if let Some(mac) = &iface.mac_address && !mac.is_empty() && mac != "00:00:00:00:00:00" { lines.push(Line::from(vec![ - " MAC: ".set_style(theme::LABEL), - mac.clone().set_style(theme::VALUE), + " MAC: ".set_style(theme.label), + mac.clone().set_style(theme.value), ])); } if let Some(mtu) = iface.mtu { lines.push(Line::from(vec![ - " MTU: ".set_style(theme::LABEL), - mtu.to_string().set_style(theme::VALUE), + " MTU: ".set_style(theme.label), + mtu.to_string().set_style(theme.value), ])); } if let Some(speed) = iface.speed_mbps && speed > 0 { lines.push(Line::from(vec![ - " Speed: ".set_style(theme::LABEL), - format!("{} Mbps", speed).set_style(theme::VALUE), + " Speed: ".set_style(theme.label), + format!("{} Mbps", speed).set_style(theme.value), ])); } lines.push(Line::from(vec![ - " RX bytes: ".set_style(theme::LABEL), - crate::network::NetInterface::format_bytes(iface.rx_bytes).set_style(theme::VALUE), + " RX bytes: ".set_style(theme.label), + crate::network::NetInterface::format_bytes(iface.rx_bytes).set_style(theme.value), format!( " ({} packets, {} err, {} drop, {:.1} KiB/s)", iface.rx_packets, iface.rx_errors, iface.rx_dropped, iface.rx_kbps ) - .set_style(theme::VALUE_OFF), + .set_style(theme.value_off), ])); lines.push(Line::from(vec![ - " TX bytes: ".set_style(theme::LABEL), - crate::network::NetInterface::format_bytes(iface.tx_bytes).set_style(theme::VALUE), + " TX bytes: ".set_style(theme.label), + crate::network::NetInterface::format_bytes(iface.tx_bytes).set_style(theme.value), format!( " ({} packets, {} err, {} drop, {:.1} KiB/s)", iface.tx_packets, iface.tx_errors, iface.tx_dropped, iface.tx_kbps ) - .set_style(theme::VALUE_OFF), + .set_style(theme.value_off), ])); if !iface.ipv4_addrs.is_empty() { - lines.push(Line::from(" IPv4:".set_style(theme::LABEL))); + lines.push(Line::from(" IPv4:".set_style(theme.label))); for addr in &iface.ipv4_addrs { - lines.push(Line::from(format!(" {}", addr).set_style(theme::VALUE))); + lines.push(Line::from(format!(" {}", addr).set_style(theme.value))); } } if !iface.ipv6_addrs.is_empty() { - lines.push(Line::from(" IPv6:".set_style(theme::LABEL))); + lines.push(Line::from(" IPv6:".set_style(theme.label))); for addr in &iface.ipv6_addrs { - lines.push(Line::from(format!(" {}", addr).set_style(theme::VALUE))); + lines.push(Line::from(format!(" {}", addr).set_style(theme.value))); } } lines.push(Line::from("")); @@ -1047,17 +1063,18 @@ pub fn render_network_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { } pub fn render_storage_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { + let theme = &app.theme; let storage = &app.storage; if storage.is_empty() { return Paragraph::new(Line::from( - "(no storage devices detected — /sys/block/ not readable)".set_style(theme::VALUE_WARM), + "(no storage devices detected — /sys/block/ not readable)".set_style(theme.value_warm), )) .block(panel_border(focused, " Storage ", &app.theme)) .wrap(Wrap { trim: true }); } let mut lines: Vec> = Vec::new(); lines.push(Line::from( - format!("Detected {} disk(s):", storage.count()).set_style(theme::LABEL_BOLD), + format!("Detected {} disk(s):", storage.count()).set_style(theme.label_bold), )); lines.push(Line::from("")); for disk in &storage.disks { @@ -1076,7 +1093,7 @@ pub fn render_storage_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { }; lines.push(Line::from( format!("▸ {} ({}){}", disk.name, disk.kind_label(), smart_badge) - .set_style(theme::LABEL_BOLD), + .set_style(theme.label_bold), )); // v1.38: per-disk I/O throughput history sparkline. // 12 samples, normalized 0..=255 against the disk's @@ -1091,62 +1108,62 @@ pub fn render_storage_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { }) .unwrap_or_else(|| " ".repeat(crate::app::PROCESS_IO_HISTORY_LEN)); lines.push(Line::from( - format!(" Throughput: {}", disk_sparkline).set_style(theme::VALUE), + format!(" Throughput: {}", disk_sparkline).set_style(theme.value), )); if let Some(model) = &disk.model { lines.push(Line::from(vec![ - " Model: ".set_style(theme::LABEL), - model.clone().set_style(theme::VALUE), + " Model: ".set_style(theme.label), + model.clone().set_style(theme.value), ])); } if let Some(vendor) = &disk.vendor { let trimmed = vendor.trim(); if !trimmed.is_empty() { lines.push(Line::from(vec![ - " Vendor: ".set_style(theme::LABEL), - trimmed.set_style(theme::VALUE), + " Vendor: ".set_style(theme.label), + trimmed.set_style(theme.value), ])); } } lines.push(Line::from(vec![ - " Size: ".set_style(theme::LABEL), - crate::storage::DiskInfo::format_size(disk.size_bytes).set_style(theme::VALUE), + " Size: ".set_style(theme.label), + crate::storage::DiskInfo::format_size(disk.size_bytes).set_style(theme.value), ])); if let Some(sched) = &disk.scheduler { let sched_trimmed: String = sched.chars().take(60).collect(); lines.push(Line::from(vec![ - " Scheduler:".set_style(theme::LABEL), - format!(" {}", sched_trimmed).set_style(theme::VALUE), + " Scheduler:".set_style(theme.label), + format!(" {}", sched_trimmed).set_style(theme.value), ])); } if let Some(qd) = disk.queue_depth { lines.push(Line::from(vec![ - " Queue: ".set_style(theme::LABEL), - format!("{} requests", qd).set_style(theme::VALUE), + " Queue: ".set_style(theme.label), + format!("{} requests", qd).set_style(theme.value), ])); } lines.push(Line::from(vec![ - " Read: ".set_style(theme::LABEL), - crate::storage::DiskInfo::format_size(disk.stats.read_bytes).set_style(theme::VALUE), + " Read: ".set_style(theme.label), + crate::storage::DiskInfo::format_size(disk.stats.read_bytes).set_style(theme.value), format!( " ({} I/Os, {:.1} KiB/s)", disk.stats.reads_completed, disk.stats.read_kbps ) - .set_style(theme::VALUE_OFF), + .set_style(theme.value_off), ])); lines.push(Line::from(vec![ - " Written: ".set_style(theme::LABEL), - crate::storage::DiskInfo::format_size(disk.stats.write_bytes).set_style(theme::VALUE), + " Written: ".set_style(theme.label), + crate::storage::DiskInfo::format_size(disk.stats.write_bytes).set_style(theme.value), format!( " ({} I/Os, {:.1} KiB/s)", disk.stats.writes_completed, disk.stats.write_kbps ) - .set_style(theme::VALUE_OFF), + .set_style(theme.value_off), ])); if !disk.partitions.is_empty() { lines.push(Line::from(vec![ - " Parts: ".set_style(theme::LABEL), - disk.partitions.join(", ").set_style(theme::VALUE), + " Parts: ".set_style(theme.label), + disk.partitions.join(", ").set_style(theme.value), ])); } lines.push(Line::from("")); @@ -1157,10 +1174,11 @@ pub fn render_storage_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { } pub fn render_process_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { + let theme = &app.theme; let proc = &app.processes; if proc.is_empty() { return Paragraph::new(Line::from( - "(no processes detected — /proc/ not readable)".set_style(theme::VALUE_WARM), + "(no processes detected — /proc/ not readable)".set_style(theme.value_warm), )) .block(panel_border(focused, " Process ", &app.theme)) .wrap(Wrap { trim: true }); @@ -1199,7 +1217,7 @@ pub fn render_process_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { if app.process_tree { "; view: tree" } else { "" }, if app.process_narrow { "; view: narrow" } else { "" }, filter_indicator, - ).set_style(theme::LABEL_BOLD))); + ).set_style(theme.label_bold))); lines.push(Line::from("")); // The MEM column header swaps between RSS and VSZ depending on // the active sort. Default and most modes use RSS (resident @@ -1224,7 +1242,7 @@ pub fn render_process_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { mem_header ) }; - lines.push(Line::from(vec![header_str.set_style(theme::LABEL)])); + lines.push(Line::from(vec![header_str.set_style(theme.label)])); let filter_lower = app.process_filter.to_lowercase(); let mut visible_index: usize = 0; for p in &proc.processes { @@ -1335,34 +1353,34 @@ pub fn render_process_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> { let row_style = if is_cursor { app.theme.cursor_highlight } else { - theme::VALUE + theme.value }; let cpu_str = format!("{:<6}", format!("{:.1}", p.cpu_pct)); let cpu_style = if p.cpu_pct >= 90.0 { - theme::VALUE_HOT_LIGHT + theme.value_hot_light } else if p.cpu_pct >= 50.0 { - theme::VALUE_HOT + theme.value_hot } else if p.cpu_pct >= 10.0 { - theme::VALUE_WARM + theme.value_warm } else { - theme::VALUE + theme.value }; let pre1a = format!(" {}{:<7} ", prefix, p.pid); let state_style = match p.state { - 'R' => theme::VALUE_OK, - 'D' => theme::VALUE_HOT, - 'Z' => theme::VALUE_OFF, - 'T' => theme::VALUE_WARM, - _ => theme::VALUE, + 'R' => theme.value_ok, + 'D' => theme.value_hot, + 'Z' => theme.value_off, + 'T' => theme.value_warm, + _ => theme.value, }; let pre1b = format!(" {:<5} {:<4} ", p.sched_policy, p.priority); let nice_str = format!("{:<3}", p.nice); let nice_style = if p.nice < 0 { - theme::VALUE_OK + theme.value_ok } else if p.nice > 0 { - theme::VALUE_WARM + theme.value_warm } else { - theme::VALUE + theme.value }; let pre2 = format!(" {:<3} ", p.num_threads); let post = format!( @@ -1589,15 +1607,15 @@ pub fn render_pid_detail( let mut lines: Vec = Vec::new(); lines.push(Line::from( - format!("═══ PID {} Detail (press any key to close) ═══", pid).set_style(theme::LABEL_BOLD), + format!("═══ PID {} Detail (press any key to close) ═══", pid).set_style(theme.label_bold), )); lines.push(Line::from("")); - lines.push(Line::from("[Identity]".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("[Identity]".set_style(theme.label_bold))); lines.push(Line::from( - format!(" Name: {}", opt_str(&s.name)).set_style(theme::VALUE), + format!(" Name: {}", opt_str(&s.name)).set_style(theme.value), )); lines.push(Line::from( - format!(" State: {}", opt_str(&s.state)).set_style(theme::VALUE), + format!(" State: {}", opt_str(&s.state)).set_style(theme.value), )); lines.push(Line::from( format!( @@ -1606,10 +1624,10 @@ pub fn render_pid_detail( opt(&s.ppid), opt(&s.pgrp) ) - .set_style(theme::VALUE), + .set_style(theme.value), )); lines.push(Line::from( - format!(" Threads: {}", opt(&s.threads)).set_style(theme::VALUE), + format!(" Threads: {}", opt(&s.threads)).set_style(theme.value), )); lines.push(Line::from( format!( @@ -1621,7 +1639,7 @@ pub fn render_pid_detail( opt(&s.gid_effective), opt(&s.gid_saved) ) - .set_style(theme::VALUE), + .set_style(theme.value), )); // v1.38: cmdline (htop parity). Long cmdlines are // truncated to fit the popup; the full string is available @@ -1637,25 +1655,25 @@ pub fn render_pid_detail( cmdline_trunc } ) - .set_style(theme::VALUE), + .set_style(theme.value), )); let io_pri = detail .io_priority .map(|v| v.to_string()) .unwrap_or_else(|| "?".to_string()); lines.push(Line::from( - format!(" IO priority: {}", io_pri).set_style(theme::VALUE), + format!(" IO priority: {}", io_pri).set_style(theme.value), )); // v1.39: environ (htop F7 parity). Render the first // 8 vars as KEY=VALUE; the full list is unbounded so we // truncate for the popup. Sorted by key in `read_environ` // for a stable, predictable list. lines.push(Line::from("")); - lines.push(Line::from("[environ]".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("[environ]".set_style(theme.label_bold))); match &detail.environ { Some(vars) if !vars.is_empty() => { lines.push(Line::from( - format!(" ({} variables)", vars.len()).set_style(theme::VALUE), + format!(" ({} variables)", vars.len()).set_style(theme.value), )); for (k, v) in vars.iter().take(8) { let v_trunc: String = v.chars().take(80).collect(); @@ -1669,31 +1687,31 @@ pub fn render_pid_detail( v_trunc } ) - .set_style(theme::VALUE), + .set_style(theme.value), )); } if vars.len() > 8 { lines.push(Line::from( - format!(" ... and {} more", vars.len() - 8).set_style(theme::VALUE), + format!(" ... and {} more", vars.len() - 8).set_style(theme.value), )); } } Some(_) => { - lines.push(Line::from(" (empty)").set_style(theme::VALUE)); + lines.push(Line::from(" (empty)").set_style(theme.value)); } None => { - lines.push(Line::from(" (unavailable)").set_style(theme::VALUE)); + lines.push(Line::from(" (unavailable)").set_style(theme.value)); } } lines.push(Line::from("")); - lines.push(Line::from("[Memory]".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("[Memory]".set_style(theme.label_bold))); lines.push(Line::from( format!( " VmPeak: {} VmRSS: {}", opt_kb(&s.vm_peak_kb), opt_kb(&s.vm_rss_kb) ) - .set_style(theme::VALUE), + .set_style(theme.value), )); lines.push(Line::from( format!( @@ -1701,7 +1719,7 @@ pub fn render_pid_detail( opt_kb(&s.vm_size_kb), opt_kb(&s.vm_hwm_kb) ) - .set_style(theme::VALUE), + .set_style(theme.value), )); lines.push(Line::from( format!( @@ -1710,7 +1728,7 @@ pub fn render_pid_detail( opt_kb(&s.vm_stk_kb), opt_kb(&s.vm_exe_kb) ) - .set_style(theme::VALUE), + .set_style(theme.value), )); lines.push(Line::from( format!( @@ -1719,10 +1737,10 @@ pub fn render_pid_detail( opt_kb(&s.vm_pte_kb), opt_kb(&s.vm_swap_kb) ) - .set_style(theme::VALUE), + .set_style(theme.value), )); lines.push(Line::from("")); - lines.push(Line::from("[smaps_rollup]".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("[smaps_rollup]".set_style(theme.label_bold))); lines.push(Line::from( format!( " Rss: {} Pss: {} Swapped: {}", @@ -1730,7 +1748,7 @@ pub fn render_pid_detail( opt_kb(&sm.pss_kb), opt_kb(&sm.swapped_kb) ) - .set_style(theme::VALUE), + .set_style(theme.value), )); lines.push(Line::from( format!( @@ -1738,17 +1756,17 @@ pub fn render_pid_detail( opt_kb(&sm.private_clean_kb), opt_kb(&sm.private_dirty_kb) ) - .set_style(theme::VALUE), + .set_style(theme.value), )); lines.push(Line::from("")); - lines.push(Line::from("[io]".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("[io]".set_style(theme.label_bold))); lines.push(Line::from( format!( " rchar: {} wchar: {}", opt(&i.rchar), opt(&i.wchar) ) - .set_style(theme::VALUE), + .set_style(theme.value), )); lines.push(Line::from( format!( @@ -1756,7 +1774,7 @@ pub fn render_pid_detail( opt(&i.read_bytes), opt(&i.write_bytes) ) - .set_style(theme::VALUE), + .set_style(theme.value), )); lines.push(Line::from( format!( @@ -1764,11 +1782,11 @@ pub fn render_pid_detail( opt(&i.syscr), opt(&i.syscw) ) - .set_style(theme::VALUE), + .set_style(theme.value), )); lines.push(Line::from( format!(" cancelled_write_bytes: {}", opt(&i.cancelled_write_bytes)) - .set_style(theme::VALUE), + .set_style(theme.value), )); // v1.41: per-thread IO aggregated across all threads. // Read directly here (not via PidDetail) because the @@ -1778,7 +1796,7 @@ pub fn render_pid_detail( let (thread_read, thread_write) = crate::process::read_thread_io_for_pid(pid); lines.push(Line::from("")); lines.push(Line::from( - "[thread_io] (sum across /proc/[pid]/task/*/io)".set_style(theme::LABEL_BOLD), + "[thread_io] (sum across /proc/[pid]/task/*/io)".set_style(theme.label_bold), )); lines.push(Line::from( format!( @@ -1786,7 +1804,7 @@ pub fn render_pid_detail( opt_kb(&thread_read), opt_kb(&thread_write), ) - .set_style(theme::VALUE), + .set_style(theme.value), )); // v1.42: CPU affinity (full list, expanded form). // Re-read on popup open so the value is current. @@ -1796,14 +1814,14 @@ pub fn render_pid_detail( // (>32 CPUs) we truncate to "first 8 + N more". let affinity = crate::process::read_cpu_affinity_for_pid(pid); lines.push(Line::from("")); - lines.push(Line::from("[cpu_affinity]".set_style(theme::LABEL_BOLD))); + lines.push(Line::from("[cpu_affinity]".set_style(theme.label_bold))); match &affinity { None => { - lines.push(Line::from(" (unavailable)".set_style(theme::VALUE))); + lines.push(Line::from(" (unavailable)".set_style(theme.value))); } Some(ids) if ids.is_empty() => { lines.push(Line::from( - " (empty — process pinned to no CPUs!)".set_style(theme::VALUE), + " (empty — process pinned to no CPUs!)".set_style(theme.value), )); } Some(ids) => { @@ -1815,7 +1833,7 @@ pub fn render_pid_detail( ids.len(), if ids.len() == 1 { "" } else { "s" }, ) - .set_style(theme::VALUE), + .set_style(theme.value), )); // Show the expanded Vec. Truncate for // large machines to keep the popup @@ -1834,7 +1852,7 @@ pub fn render_pid_detail( expanded.push_str(&format!(", ... ({} more)", ids.len() - 8)); } lines.push(Line::from( - format!(" expanded: [{}]", expanded).set_style(theme::VALUE), + format!(" expanded: [{}]", expanded).set_style(theme.value), )); } } @@ -1853,15 +1871,15 @@ pub fn render_cpu_table<'a>( theme: &Theme, ) -> Table<'a> { let header = Row::new(vec![ - "CPU".set_style(theme::LABEL), - "Freq/MHz".set_style(theme::LABEL), - "PkgW".set_style(theme::LABEL), - "Temp°C bar".set_style(theme::LABEL), - "P-state".set_style(theme::LABEL), - "State".set_style(theme::LABEL), - "Flags".set_style(theme::LABEL), - "Load % (30s)".set_style(theme::LABEL), - "IRQs".set_style(theme::LABEL), + "CPU".set_style(theme.label), + "Freq/MHz".set_style(theme.label), + "PkgW".set_style(theme.label), + "Temp°C bar".set_style(theme.label), + "P-state".set_style(theme.label), + "State".set_style(theme.label), + "Flags".set_style(theme.label), + "Load % (30s)".set_style(theme.label), + "IRQs".set_style(theme.label), ]) .height(1); let rows: Vec = cpus @@ -1874,16 +1892,16 @@ pub fn render_cpu_table<'a>( if max_f > min_f && freq_mhz > 0 { let pct = (freq_mhz - min_f) as f64 * 100.0 / (max_f - min_f) as f64; match pct as u32 { - p if p >= 85 => theme::VALUE_HOT, - p if p >= 50 => theme::VALUE_WARM, + p if p >= 85 => theme.value_hot, + p if p >= 50 => theme.value_warm, p if p <= 15 => Style::new().dark_gray(), - _ => theme::VALUE, + _ => theme.value, } } else { - theme::VALUE + theme.value } } else { - theme::VALUE + theme.value }; let freq = if cpu.freq_khz > 0 { format!("{}", freq_mhz) @@ -1907,7 +1925,7 @@ pub fn render_cpu_table<'a>( bar.set_style(Style::new().fg(color)), ])) } - None => Cell::from("n/a".set_style(theme::VALUE_OFF)), + None => Cell::from("n/a".set_style(theme.value_off)), }; let pstate = cpu .current_idx @@ -1924,7 +1942,7 @@ pub fn render_cpu_table<'a>( flags.push('L'); } let flags_cell: Cell = if flags.is_empty() { - Cell::from("-".set_style(theme::NO_FLAG)) + Cell::from("-".set_style(theme.no_flag)) } else { let color = theme::flags_color(cpu.critical, cpu.prochot, cpu.power_limit); Cell::from(flags.clone().set_style(Style::new().fg(color))) @@ -1944,7 +1962,7 @@ pub fn render_cpu_table<'a>( Row::new(vec![ Cell::from( format!("{}{}", cpuid::core_type_label(cpu.core_type), cpu.id) - .set_style(theme::VALUE), + .set_style(theme.value), ), Cell::from(freq.set_style(freq_style)), Cell::from( @@ -1957,14 +1975,14 @@ pub fn render_cpu_table<'a>( _ => "n/a".into(), }) .set_style(if pkg_power_w.is_some() { - theme::VALUE + theme.value } else { - theme::VALUE_OFF + theme.value_off }), ), temp_cell, - Cell::from(pstate.set_style(theme::VALUE)), - Cell::from(cpu.state_label().set_style(theme::VALUE)), + Cell::from(pstate.set_style(theme.value)), + Cell::from(cpu.state_label().set_style(theme.value)), flags_cell, Cell::from(Line::from(vec![ spark_text.set_style(Style::new().fg(lcolor)), @@ -1977,9 +1995,9 @@ pub fn render_cpu_table<'a>( .map(|v| format!("{v}")) .unwrap_or_else(|| "—".into()) .set_style(if irq_val.is_some() { - theme::VALUE + theme.value } else { - theme::VALUE_OFF + theme.value_off }), ) }, @@ -1990,7 +2008,7 @@ pub fn render_cpu_table<'a>( if let Some(expanded_id) = expanded_cpu && let Some(cpu) = cpus.iter().find(|c| c.id == expanded_id) { - let sub_style = theme::LABEL; + let sub_style = theme.label; let active_style = Style::new().yellow().bold(); for (idx, pstate) in cpu.pstates.iter().enumerate() { let is_current = cpu.current_idx == Some(idx); @@ -2032,7 +2050,7 @@ pub fn render_cpu_table<'a>( .set_style(if hwp.enabled { Style::new().green() } else { - theme::VALUE_OFF + theme.value_off }), "".set_style(s), "".set_style(s), @@ -2081,6 +2099,7 @@ pub fn render_cpu_table<'a>( } pub fn render_keybar<'a>(app: &'a App) -> Paragraph<'a> { + let theme = &app.theme; let gov = format!(" g:{}", app.cpufreq.active.as_str()); let interval = format!(" [/]:{}ms", app.poll_ms); let tab_hints: &str = if app.current_tab == crate::app::TabId::Process { @@ -2102,7 +2121,7 @@ pub fn render_keybar<'a>(app: &'a App) -> Paragraph<'a> { } let line = parts.join(""); Paragraph::new(line) - .style(theme::VALUE_OFF) + .style(theme.value_off) .block(Block::default().borders(Borders::NONE)) } @@ -2252,7 +2271,7 @@ pub fn render_help<'a>(help_scroll: usize, theme: &Theme) -> Paragraph<'a> { let visible: String = lines[start..].join("\n"); let pct = (start * 100).checked_div(total).unwrap_or(0); Paragraph::new(visible) - .style(theme::VALUE) + .style(theme.value) .wrap(Wrap { trim: true }) .scroll((help_scroll as u16, 0)) .block( @@ -2260,7 +2279,7 @@ pub fn render_help<'a>(help_scroll: usize, theme: &Theme) -> Paragraph<'a> { .borders(Borders::ALL) .title(Line::styled( format!(" Help ({}%, j/k to scroll) ", pct), - theme::LABEL_BOLD, + theme.label_bold, )) .border_style(theme.border_focused), ) @@ -2318,6 +2337,7 @@ pub fn buffer_to_string(buf: &ratatui::buffer::Buffer) -> String { pub fn render_once(app: &App) -> io::Result<()> { print!("{}", snapshot(app, 140, 50)); + // Also dump the System panel as a second snapshot for verification. eprintln!("--- System panel (verifies v1.4 memory + OS info) ---"); let sys_para = render_system_panel(app, false); diff --git a/local/recipes/system/redbear-power/source/src/theme.rs b/local/recipes/system/redbear-power/source/src/theme.rs index 56148a19ac..38fa69a664 100644 --- a/local/recipes/system/redbear-power/source/src/theme.rs +++ b/local/recipes/system/redbear-power/source/src/theme.rs @@ -1,56 +1,43 @@ //! Centralized color and style palette for redbear-power. //! -//! Every visible color and recurring style lives here as a `const`, -//! so changing a color is a one-line edit and tests can snapshot -//! against stable references. The constants use the ratatui 0.30 -//! `Stylize` shorthand (`Style::new().red().bold()`) which is -//! stable across all builds. -//! -//! v1.44: Added `Theme` struct with Dark / Light / HighContrast -//! presets for border styles and key highlights. The `const` values -//! below are the Dark theme defaults. The active `Theme` is stored -//! in `App` and selectable via `--theme` / config `[theme].mode`. +//! Every visible color and recurring style lives in the `Theme` struct, +//! so each preset (Dark, Light, HighContrast, Nord, Gruvbox, SolarizedDark) +//! can define its own palette. The active `Theme` is stored in `App` and +//! selectable via `--theme` / config `[theme].mode`. #[allow(unused_imports)] use ratatui::style::{Color, Style, Stylize}; -pub const LABEL: Style = Style::new().cyan(); -pub const LABEL_BOLD: Style = Style::new().cyan().bold(); - -pub const VALUE: Style = Style::new(); -pub const VALUE_OFF: Style = Style::new().dark_gray(); -pub const VALUE_OK: Style = Style::new().green(); -pub const VALUE_WARM: Style = Style::new().yellow(); -pub const VALUE_HOT: Style = Style::new().red().bold(); -pub const VALUE_HOT_LIGHT: Style = Style::new().light_red(); - -pub const BORDER_FOCUSED: Style = Style::new().yellow().bold(); -pub const BORDER_DIM: Style = Style::new().dark_gray(); - -pub const HEADER_GOVERNOR: Style = Style::new().magenta().bold(); -pub const HEADER_THROTTLE_AUTO: Style = Style::new().green(); -pub const HEADER_THROTTLE_USER: Style = Style::new().blue(); -pub const HEADER_THROTTLE_FORCED: Style = Style::new().red().bold(); - -pub const STATUS_OK: Style = Style::new().green().bold(); -pub const STATUS_WARN: Style = Style::new().yellow().bold(); -pub const STATUS_ERR: Style = Style::new().red().bold(); - -pub const PROCHOT_PULSE: Style = Style::new().red().bold(); -pub const PROCHOT_FLAG: Style = Style::new().light_red(); -pub const POWER_LIMIT_FLAG: Style = Style::new().yellow(); -pub const NO_FLAG: Style = Style::new().dark_gray(); - -pub const CURSOR: Style = Style::new().bold(); - -/// Active theme with overridable border/header/cursor styles. -/// Three built-in presets: Dark (default), Light, HighContrast. +/// Active theme palette. #[derive(Clone, Debug)] pub struct Theme { pub border_focused: Style, pub border_dim: Style, pub header_governor: Style, pub cursor_highlight: Style, + + pub label: Style, + pub label_bold: Style, + + pub value: Style, + pub value_off: Style, + pub value_ok: Style, + pub value_warm: Style, + pub value_hot: Style, + pub value_hot_light: Style, + + pub header_throttle_auto: Style, + pub header_throttle_user: Style, + pub header_throttle_forced: Style, + + pub status_ok: Style, + pub status_warn: Style, + pub status_err: Style, + + pub prochot_pulse: Style, + pub prochot_flag: Style, + pub power_limit_flag: Style, + pub no_flag: Style, } impl Theme { @@ -67,10 +54,28 @@ impl Theme { pub fn dark() -> Self { Self { - border_focused: BORDER_FOCUSED, - border_dim: BORDER_DIM, - header_governor: HEADER_GOVERNOR, - cursor_highlight: CURSOR, + border_focused: Style::new().yellow().bold(), + border_dim: Style::new().dark_gray(), + header_governor: Style::new().magenta().bold(), + cursor_highlight: Style::new().bold(), + label: Style::new().cyan(), + label_bold: Style::new().cyan().bold(), + value: Style::new(), + value_off: Style::new().dark_gray(), + value_ok: Style::new().green(), + value_warm: Style::new().yellow(), + value_hot: Style::new().red().bold(), + value_hot_light: Style::new().light_red(), + header_throttle_auto: Style::new().green(), + header_throttle_user: Style::new().blue(), + header_throttle_forced: Style::new().red().bold(), + status_ok: Style::new().green().bold(), + status_warn: Style::new().yellow().bold(), + status_err: Style::new().red().bold(), + prochot_pulse: Style::new().red().bold(), + prochot_flag: Style::new().light_red(), + power_limit_flag: Style::new().yellow(), + no_flag: Style::new().dark_gray(), } } @@ -80,6 +85,24 @@ impl Theme { border_dim: Style::new().gray(), header_governor: Style::new().blue().bold(), cursor_highlight: Style::new().bold(), + label: Style::new().cyan(), + label_bold: Style::new().cyan().bold(), + value: Style::new().black(), + value_off: Style::new().gray(), + value_ok: Style::new().green(), + value_warm: Style::new().yellow(), + value_hot: Style::new().red().bold(), + value_hot_light: Style::new().red(), + header_throttle_auto: Style::new().green(), + header_throttle_user: Style::new().blue(), + header_throttle_forced: Style::new().red().bold(), + status_ok: Style::new().green().bold(), + status_warn: Style::new().yellow().bold(), + status_err: Style::new().red().bold(), + prochot_pulse: Style::new().red().bold(), + prochot_flag: Style::new().red(), + power_limit_flag: Style::new().yellow(), + no_flag: Style::new().gray(), } } @@ -89,6 +112,24 @@ impl Theme { border_dim: Style::new().white(), header_governor: Style::new().white().bold(), cursor_highlight: Style::new().white().on_black().bold(), + label: Style::new().white().bold(), + label_bold: Style::new().white().bold(), + value: Style::new().white(), + value_off: Style::new().white(), + value_ok: Style::new().green().bold(), + value_warm: Style::new().yellow().bold(), + value_hot: Style::new().red().bold(), + value_hot_light: Style::new().red().bold(), + header_throttle_auto: Style::new().green().bold(), + header_throttle_user: Style::new().cyan().bold(), + header_throttle_forced: Style::new().red().bold(), + status_ok: Style::new().green().bold(), + status_warn: Style::new().yellow().bold(), + status_err: Style::new().red().bold(), + prochot_pulse: Style::new().red().bold(), + prochot_flag: Style::new().red().bold(), + power_limit_flag: Style::new().yellow().bold(), + no_flag: Style::new().white(), } } @@ -98,6 +139,24 @@ impl Theme { border_dim: Style::new().dark_gray(), header_governor: Style::new().light_blue().bold(), cursor_highlight: Style::new().bold(), + label: Style::new().light_cyan(), + label_bold: Style::new().light_cyan().bold(), + value: Style::new().white(), + value_off: Style::new().dark_gray(), + value_ok: Style::new().light_green(), + value_warm: Style::new().yellow(), + value_hot: Style::new().light_red().bold(), + value_hot_light: Style::new().light_red(), + header_throttle_auto: Style::new().light_green(), + header_throttle_user: Style::new().light_blue(), + header_throttle_forced: Style::new().light_red().bold(), + status_ok: Style::new().light_green().bold(), + status_warn: Style::new().yellow().bold(), + status_err: Style::new().light_red().bold(), + prochot_pulse: Style::new().light_red().bold(), + prochot_flag: Style::new().light_red(), + power_limit_flag: Style::new().yellow(), + no_flag: Style::new().dark_gray(), } } @@ -107,6 +166,24 @@ impl Theme { border_dim: Style::new().dark_gray(), header_governor: Style::new().yellow().bold(), cursor_highlight: Style::new().bold(), + label: Style::new().light_blue(), + label_bold: Style::new().light_blue().bold(), + value: Style::new().white(), + value_off: Style::new().dark_gray(), + value_ok: Style::new().green(), + value_warm: Style::new().yellow(), + value_hot: Style::new().red().bold(), + value_hot_light: Style::new().red(), + header_throttle_auto: Style::new().green(), + header_throttle_user: Style::new().blue(), + header_throttle_forced: Style::new().red().bold(), + status_ok: Style::new().green().bold(), + status_warn: Style::new().yellow().bold(), + status_err: Style::new().red().bold(), + prochot_pulse: Style::new().red().bold(), + prochot_flag: Style::new().red(), + power_limit_flag: Style::new().yellow(), + no_flag: Style::new().dark_gray(), } } @@ -116,6 +193,24 @@ impl Theme { border_dim: Style::new().dark_gray(), header_governor: Style::new().magenta().bold(), cursor_highlight: Style::new().bold(), + label: Style::new().cyan(), + label_bold: Style::new().cyan().bold(), + value: Style::new().white(), + value_off: Style::new().dark_gray(), + value_ok: Style::new().green(), + value_warm: Style::new().yellow(), + value_hot: Style::new().red().bold(), + value_hot_light: Style::new().light_red(), + header_throttle_auto: Style::new().green(), + header_throttle_user: Style::new().blue(), + header_throttle_forced: Style::new().red().bold(), + status_ok: Style::new().green().bold(), + status_warn: Style::new().yellow().bold(), + status_err: Style::new().red().bold(), + prochot_pulse: Style::new().red().bold(), + prochot_flag: Style::new().light_red(), + power_limit_flag: Style::new().yellow(), + no_flag: Style::new().dark_gray(), } } } @@ -161,3 +256,26 @@ pub fn flags_color(prochot: bool, critical: bool, power_limit: bool) -> Color { Color::Green } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn named_dispatches_to_dark_by_default() { + let t = Theme::named("unknown"); + assert_eq!(t.label, Theme::dark().label); + } + + #[test] + fn light_has_dark_foreground() { + let t = Theme::light(); + assert_eq!(t.value, Style::new().black()); + } + + #[test] + fn nord_label_is_not_dark_default() { + let t = Theme::nord(); + assert_ne!(t.label, Theme::dark().label); + } +}