redbear-power: theme system refactor
Move all palette colors into Theme fields so dark/light/high-contrast/nord/gruvbox/solarized-dark are fully usable. Convert NiceEdit and AffinityEditor to regular render_dialog methods that take a theme. Update all render_* helpers to bind let theme = &app.theme and reference theme fields. All 224 tests pass; clippy and fmt clean.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user