feat: config file, cloes #47

Enable use of a config file, with custom keymap and custom colours
This commit is contained in:
Jack Wills
2025-02-16 12:53:54 +00:00
parent 4539d8ad07
commit 8fd95b7fd1
37 changed files with 8725 additions and 3879 deletions
+433
View File
@@ -0,0 +1,433 @@
use ratatui::style::Color;
/// The macro accepts a list of struct names with key names
/// Returns a struct where every key name is an Option<String>, with the correct derived attributes
macro_rules! optional_config_struct {
($($struct_name:ident, $($key_name:ident),*);*) => {
$(
#[derive(Debug, serde::Deserialize, Clone, PartialEq, Eq)]
struct $struct_name {
$(
$key_name: Option<String>,
)*
}
)*
};
}
/// The macro accepts a list of struct names with key names
macro_rules! config_struct {
($($struct_name:ident, $($key_name:ident),*);*) => {
$(
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub struct $struct_name {
$(
pub $key_name: Color,
)*
}
)*
};
}
impl AppColors {
fn map_color(color_str: Option<&str>, setter: &mut Color) {
color_str.map(|i| i.parse::<Color>().map(|i| *setter = i));
}
}
impl From<Option<ConfigColors>> for AppColors {
#[allow(clippy::too_many_lines)]
fn from(value: Option<ConfigColors>) -> Self {
let mut app_colors = Self::new();
if let Some(config_colors) = value {
// Heading bar
if let Some(hb) = config_colors.headers_bar {
Self::map_color(
hb.background.as_deref(),
&mut app_colors.headers_bar.background,
);
Self::map_color(
hb.loading_spinner.as_deref(),
&mut app_colors.headers_bar.loading_spinner,
);
Self::map_color(hb.text.as_deref(), &mut app_colors.headers_bar.text);
Self::map_color(
hb.text_selected.as_deref(),
&mut app_colors.headers_bar.text_selected,
);
}
// Seletable panel borders
if let Some(b) = config_colors.borders {
Self::map_color(b.selected.as_deref(), &mut app_colors.borders.selected);
Self::map_color(b.unselected.as_deref(), &mut app_colors.borders.unselected);
}
// Error Popup
if let Some(ep) = config_colors.popup_error {
Self::map_color(
ep.background.as_deref(),
&mut app_colors.popup_error.background,
);
Self::map_color(ep.text.as_deref(), &mut app_colors.popup_error.text);
}
// Help Popup
if let Some(hp) = config_colors.popup_help {
Self::map_color(
hp.background.as_deref(),
&mut app_colors.popup_help.background,
);
Self::map_color(hp.text.as_deref(), &mut app_colors.popup_help.text);
Self::map_color(
hp.text_highlight.as_deref(),
&mut app_colors.popup_help.text_highlight,
);
}
// Info Popup
if let Some(ip) = config_colors.popup_info {
Self::map_color(
ip.background.as_deref(),
&mut app_colors.popup_info.background,
);
Self::map_color(ip.text.as_deref(), &mut app_colors.popup_info.text);
}
// Delete Popup
if let Some(dp) = config_colors.popup_delete {
Self::map_color(
dp.background.as_deref(),
&mut app_colors.popup_delete.background,
);
Self::map_color(dp.text.as_deref(), &mut app_colors.popup_delete.text);
Self::map_color(
dp.text_highlight.as_deref(),
&mut app_colors.popup_delete.text_highlight,
);
}
// Chart Cpu
if let Some(cc) = config_colors.chart_cpu {
Self::map_color(
cc.background.as_deref(),
&mut app_colors.chart_cpu.background,
);
Self::map_color(cc.border.as_deref(), &mut app_colors.chart_cpu.border);
Self::map_color(cc.max.as_deref(), &mut app_colors.chart_cpu.max);
Self::map_color(cc.points.as_deref(), &mut app_colors.chart_cpu.points);
Self::map_color(cc.title.as_deref(), &mut app_colors.chart_cpu.title);
Self::map_color(cc.y_axis.as_deref(), &mut app_colors.chart_cpu.y_axis);
}
// Chart Memory
if let Some(cm) = config_colors.chart_memory {
Self::map_color(
cm.background.as_deref(),
&mut app_colors.chart_memory.background,
);
Self::map_color(cm.border.as_deref(), &mut app_colors.chart_memory.border);
Self::map_color(cm.max.as_deref(), &mut app_colors.chart_memory.max);
Self::map_color(cm.points.as_deref(), &mut app_colors.chart_memory.points);
Self::map_color(cm.title.as_deref(), &mut app_colors.chart_memory.title);
Self::map_color(cm.y_axis.as_deref(), &mut app_colors.chart_memory.y_axis);
}
// Chart ports
if let Some(cp) = config_colors.chart_ports {
Self::map_color(
cp.background.as_deref(),
&mut app_colors.chart_ports.background,
);
Self::map_color(cp.border.as_deref(), &mut app_colors.chart_ports.border);
Self::map_color(cp.headings.as_deref(), &mut app_colors.chart_ports.headings);
Self::map_color(cp.text.as_deref(), &mut app_colors.chart_ports.text);
Self::map_color(cp.title.as_deref(), &mut app_colors.chart_ports.title);
}
// Containers
if let Some(c) = config_colors.containers {
Self::map_color(
c.background.as_deref(),
&mut app_colors.containers.background,
);
Self::map_color(c.icon.as_deref(), &mut app_colors.containers.icon);
Self::map_color(c.text.as_deref(), &mut app_colors.containers.text);
Self::map_color(c.text_rx.as_deref(), &mut app_colors.containers.text_rx);
Self::map_color(c.text_tx.as_deref(), &mut app_colors.containers.text_tx);
}
// Commands
if let Some(cc) = config_colors.commands {
Self::map_color(
cc.background.as_deref(),
&mut app_colors.commands.background,
);
Self::map_color(cc.pause.as_deref(), &mut app_colors.commands.pause);
Self::map_color(cc.restart.as_deref(), &mut app_colors.commands.restart);
Self::map_color(cc.stop.as_deref(), &mut app_colors.commands.stop);
Self::map_color(cc.delete.as_deref(), &mut app_colors.commands.start);
Self::map_color(cc.resume.as_deref(), &mut app_colors.commands.resume);
Self::map_color(cc.start.as_deref(), &mut app_colors.commands.start);
}
// Container State
if let Some(cs) = config_colors.container_state {
Self::map_color(cs.dead.as_deref(), &mut app_colors.container_state.dead);
Self::map_color(cs.exited.as_deref(), &mut app_colors.container_state.exited);
Self::map_color(cs.paused.as_deref(), &mut app_colors.container_state.paused);
Self::map_color(
cs.removing.as_deref(),
&mut app_colors.container_state.removing,
);
Self::map_color(
cs.restarting.as_deref(),
&mut app_colors.container_state.restarting,
);
Self::map_color(
cs.running_healthy.as_deref(),
&mut app_colors.container_state.running_healthy,
);
Self::map_color(
cs.running_unhealthy.as_deref(),
&mut app_colors.container_state.running_unhealthy,
);
Self::map_color(
cs.unknown.as_deref(),
&mut app_colors.container_state.unknown,
);
}
}
app_colors
}
}
const ORANGE: Color = Color::Rgb(255, 178, 36);
optional_config_struct!(
ConfigBackgroundText, background, text;
ConfigBackgroundTextHighlight, background, text, text_highlight;
ConfigBorders, selected, unselected;
ConfigChartCpu, background, border, order, title, max, points,y_axis;
ConfigChartMemory, background, border, title, max, points, y_axis;
ConfigChartPorts, background, border, title, headings, text;
ConfigCommands, background, pause, restart, stop, delete, resume, start;
ConfigContainers, background, icon, text, text_rx, text_tx;
ConfigContainerState, background, dead, exited, paused, removing, restarting, running_healthy, running_unhealthy, unknown;
ConfigHeadersBar, background, loading_spinner, text, text_selected
);
config_struct!(
Borders, selected, unselected;
ChartCpu, background, border, title, max, points, y_axis;
ChartMemory, background, border, title, max, points, y_axis;
ChartPorts, background, border, title, headings, text;
Commands, background, pause, restart, stop, delete, resume, start;
Containers, background, icon, text, text_rx, text_tx;
ContainerState, dead, exited, paused, removing, restarting, running_healthy, running_unhealthy, unknown;
HeadersBar, background, text_selected, loading_spinner, text;
PopupDelete, background, text, text_highlight;
PopupError, background, text;
PopupHelp, background, text, text_highlight;
PopupInfo, background, text
);
#[derive(Debug, serde::Deserialize, Clone, PartialEq, Eq)]
pub struct ConfigColors {
borders: Option<ConfigBorders>,
chart_cpu: Option<ConfigChartCpu>,
chart_memory: Option<ConfigChartMemory>,
chart_ports: Option<ConfigChartPorts>,
commands: Option<ConfigCommands>,
container_state: Option<ConfigContainerState>,
containers: Option<ConfigContainers>,
headers_bar: Option<ConfigHeadersBar>,
popup_delete: Option<ConfigBackgroundTextHighlight>,
popup_error: Option<ConfigBackgroundText>,
popup_help: Option<ConfigBackgroundTextHighlight>,
popup_info: Option<ConfigBackgroundText>,
}
/// Default colours for the header bar
impl HeadersBar {
const fn new() -> Self {
Self {
background: Color::Magenta,
loading_spinner: Color::White,
text: Color::Black,
text_selected: Color::Gray,
}
}
}
/// Default colours for the borders
impl Borders {
const fn new() -> Self {
Self {
selected: Color::LightCyan,
unselected: Color::Gray,
}
}
}
/// Default colours for the delete popup
impl Commands {
const fn new() -> Self {
Self {
background: Color::Reset,
pause: Color::Yellow,
restart: Color::Magenta,
stop: Color::Red,
delete: Color::Gray,
resume: Color::Blue,
start: Color::Green,
}
}
}
/// Default colours for the help popup
impl ChartCpu {
const fn new() -> Self {
Self {
background: Color::Reset,
border: Color::White,
title: Color::Green,
max: ORANGE,
points: Color::Magenta,
y_axis: Color::White,
}
}
}
/// Default colours for the help popup
impl ChartMemory {
const fn new() -> Self {
Self {
background: Color::Reset,
border: Color::White,
title: Color::Green,
max: ORANGE,
points: Color::Cyan,
y_axis: Color::White,
}
}
}
/// Default colours for the help popup
impl ChartPorts {
const fn new() -> Self {
Self {
background: Color::Reset,
border: Color::White,
title: Color::Green,
headings: Color::Yellow,
text: Color::White,
}
}
}
/// Default colours for the help popup
impl Containers {
const fn new() -> Self {
Self {
background: Color::Reset,
icon: Color::White,
text: Color::Blue,
text_rx: Color::Rgb(255, 233, 193),
text_tx: Color::Rgb(205, 140, 140),
}
}
}
/// Default colours for the help popup
impl ContainerState {
const fn new() -> Self {
Self {
paused: Color::Yellow,
removing: Color::LightRed,
restarting: Color::LightGreen,
running_healthy: Color::Green,
running_unhealthy: ORANGE,
dead: Color::Red,
exited: Color::Red,
unknown: Color::Red,
}
}
}
/// Default colours for the Error popup
impl PopupError {
const fn new() -> Self {
Self {
background: Color::Red,
text: Color::White,
}
}
}
/// Default colours for the info popup
impl PopupInfo {
const fn new() -> Self {
Self {
background: Color::Blue,
text: Color::White,
}
}
}
/// Default colours for the help popup
impl PopupHelp {
const fn new() -> Self {
Self {
background: Color::Magenta,
text: Color::Black,
text_highlight: Color::White,
}
}
}
/// Default colours for the delete popup
impl PopupDelete {
const fn new() -> Self {
Self {
background: Color::White,
text: Color::Black,
text_highlight: Color::Red,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Copy)]
pub struct AppColors {
pub borders: Borders,
pub chart_cpu: ChartCpu,
pub chart_memory: ChartMemory,
pub chart_ports: ChartPorts,
pub commands: Commands,
pub container_state: ContainerState,
pub containers: Containers,
pub headers_bar: HeadersBar,
pub popup_delete: PopupDelete,
pub popup_error: PopupError,
pub popup_help: PopupHelp,
pub popup_info: PopupInfo,
}
impl AppColors {
pub const fn new() -> Self {
Self {
borders: Borders::new(),
chart_cpu: ChartCpu::new(),
chart_memory: ChartMemory::new(),
chart_ports: ChartPorts::new(),
commands: Commands::new(),
container_state: ContainerState::new(),
containers: Containers::new(),
headers_bar: HeadersBar::new(),
popup_delete: PopupDelete::new(),
popup_error: PopupError::new(),
popup_help: PopupHelp::new(),
popup_info: PopupInfo::new(),
}
}
}