From 580187bddfd9ab8e233247b30a3a49ffb636f57c Mon Sep 17 00:00:00 2001 From: micielski <73398428+micielski@users.noreply.github.com> Date: Fri, 21 Jun 2024 18:09:45 +0000 Subject: [PATCH] refactor: use ratatui's color serde impl (#26) --- Cargo.lock | 6 +- rm-config/Cargo.toml | 4 +- rm-config/src/color.rs | 239 ------------------ rm-config/src/lib.rs | 10 +- rm-main/src/ui/components/tabs.rs | 3 +- rm-main/src/ui/global_popups/help.rs | 4 +- rm-main/src/ui/tabs/search.rs | 5 +- rm-main/src/ui/tabs/torrents/input_manager.rs | 2 +- rm-main/src/ui/tabs/torrents/mod.rs | 3 +- rm-main/src/ui/tabs/torrents/popups/files.rs | 3 +- rm-main/src/ui/tabs/torrents/popups/stats.rs | 2 +- rm-main/src/ui/tabs/torrents/table_manager.rs | 3 +- 12 files changed, 22 insertions(+), 262 deletions(-) delete mode 100644 rm-config/src/color.rs diff --git a/Cargo.lock b/Cargo.lock index f986c02..da9936a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,9 @@ name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] [[package]] name = "bumpalo" @@ -241,6 +244,7 @@ dependencies = [ "cfg-if", "itoa", "ryu", + "serde", "static_assertions", ] @@ -1037,6 +1041,7 @@ dependencies = [ "itertools", "lru", "paste", + "serde", "stability", "strum", "unicode-segmentation", @@ -1157,7 +1162,6 @@ dependencies = [ "anyhow", "ratatui", "serde", - "serde_json", "toml", "url", "xdg", diff --git a/rm-config/Cargo.toml b/rm-config/Cargo.toml index 62ea201..2d09b31 100644 --- a/rm-config/Cargo.toml +++ b/rm-config/Cargo.toml @@ -13,8 +13,6 @@ toml = "0.8" serde = { version = "1", features = ["derive"] } anyhow = "1" url = "2.5" -ratatui = "0.26" +ratatui = { version = "0.26", features = ["serde"] } -[dev-dependencies] -serde_json = "1" diff --git a/rm-config/src/color.rs b/rm-config/src/color.rs deleted file mode 100644 index b448019..0000000 --- a/rm-config/src/color.rs +++ /dev/null @@ -1,239 +0,0 @@ -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy)] -pub enum Color { - Black, - Red, - Green, - Yellow, - Blue, - Magenta, - Cyan, - Gray, - DarkGray, - LightRed, - LightGreen, - LightYellow, - LightBlue, - LightMagenta, - LightCyan, - White, - Rgb(u8, u8, u8), - Indexed(u8), -} - -impl Color { - #[must_use] - pub const fn as_ratatui(&self) -> ratatui::style::Color { - use ratatui::style::Color as RColor; - use Color::*; - match self { - Red => RColor::Red, - Green => RColor::Green, - Blue => RColor::Blue, - Yellow => RColor::Yellow, - Magenta => RColor::Magenta, - Cyan => RColor::Cyan, - LightRed => RColor::LightRed, - LightGreen => RColor::LightGreen, - LightYellow => RColor::LightYellow, - LightBlue => RColor::LightBlue, - LightMagenta => RColor::LightMagenta, - LightCyan => RColor::LightCyan, - Black => RColor::Black, - Gray => RColor::Gray, - DarkGray => RColor::DarkGray, - White => RColor::White, - Rgb(r, g, b) => RColor::Rgb(*r, *g, *b), - Indexed(u) => RColor::Indexed(*u), - } - } -} - -impl Default for Color { - fn default() -> Self { - Self::LightMagenta - } -} - -pub fn serialize(color: &Color, serializer: S) -> Result { - serializer.serialize_str(&match color { - Color::Red => "Red".to_string(), - Color::Green => "Green".to_string(), - Color::Black => "Black".to_string(), - Color::Yellow => "Yellow".to_string(), - Color::Blue => "Blue".to_string(), - Color::Magenta => "Magenta".to_string(), - Color::Cyan => "Cyan".to_string(), - Color::Gray => "Gray".to_string(), - Color::White => "White".to_string(), - - Color::DarkGray => "DarkGray".to_string(), - Color::LightBlue => "LightBlue".to_string(), - Color::LightCyan => "LightCyan".to_string(), - Color::LightGreen => "LightGreen".to_string(), - Color::LightMagenta => "LightMagenta".to_string(), - Color::LightRed => "LightRed".to_string(), - Color::LightYellow => "LightYellow".to_string(), - Color::Indexed(index) => format!("{:03}", index), - Color::Rgb(r, g, b) => format!("#{:02X}{:02X}{:02X}", r, g, b), - }) -} - -pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { - use serde::de::{Error, Unexpected}; - - let color_string = String::deserialize(deserializer)?; - Ok(match color_string.to_lowercase().as_str() { - "red" => Color::Red, - "green" => Color::Green, - "black" => Color::Black, - "yellow" => Color::Yellow, - "blue" => Color::Blue, - "magenta" => Color::Magenta, - "cyan" => Color::Cyan, - "gray" => Color::Gray, - "white" => Color::White, - - "darkgray" => Color::DarkGray, - "lightblue" => Color::LightBlue, - "lightcyan" => Color::LightCyan, - "lightgreen" => Color::LightGreen, - "lightmagenta" => Color::LightMagenta, - "lightred" => Color::LightRed, - "lightyellow" => Color::LightYellow, - _ => match color_string.len() { - 3 => { - let index = color_string.parse::(); - if let Ok(index) = index { - Color::Indexed(index) - } else { - return Err(Error::invalid_type( - Unexpected::Bytes(color_string.as_bytes()), - &"u8 index color", - )); - } - } - 4 | 7 => { - if !color_string.starts_with('#') { - return Err(Error::invalid_value( - Unexpected::Char(color_string.chars().next().unwrap()), - &"# at the start", - )); - } - - let color_string = color_string.trim_start_matches('#'); - - let (r, g, b); - - match color_string.len() { - 6 => { - r = u8::from_str_radix(&color_string[0..2], 16); - g = u8::from_str_radix(&color_string[2..4], 16); - b = u8::from_str_radix(&color_string[4..6], 16); - } - 3 => { - r = u8::from_str_radix(&color_string[0..1], 16).map(|r| r * 17); - g = u8::from_str_radix(&color_string[1..2], 16).map(|g| g * 17); - b = u8::from_str_radix(&color_string[2..3], 16).map(|b| b * 17); - } - _ => unreachable!("Can't be reached since already checked"), - } - - match (r, g, b) { - (Ok(r), Ok(g), Ok(b)) => Color::Rgb(r, g, b), - (_, _, _) => { - return Err(Error::invalid_value( - Unexpected::Bytes(color_string.as_bytes()), - &"hex color string", - )); - } - } - } - _ => { - return Err(serde::de::Error::invalid_length( - color_string.len(), - &"color string with length 4 or 7", - )) - } - }, - }) -} - -#[cfg(test)] -mod tests { - use serde::{Deserialize, Serialize}; - - use crate::Color; - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Test { - #[serde(with = "super")] - pub c: Color, - } - - #[test] - fn serialize_name() { - let color: Color = Color::LightGreen; - let t = Test { c: color }; - let color_string = serde_json::to_string(&t).unwrap(); - assert_eq!(color_string, r###"{"c":"LightGreen"}"###); - } - - #[test] - fn serialize_index() { - let color: Color = Color::Indexed(123); - let t = Test { c: color }; - let color_string = serde_json::to_string(&t).unwrap(); - assert_eq!(color_string, r###"{"c":"123"}"###); - } - - #[test] - fn serialize_hex() { - let color: Color = Color::Rgb(18, 252, 28); - let t = Test { c: color }; - let color_string = serde_json::to_string(&t).unwrap(); - assert_eq!(color_string, r###"{"c":"#12FC1C"}"###); - } - - #[test] - fn deserialize_name() { - let color: Color = Color::LightGreen; - let color_text = r###"{ "c": "LightGreen" }"###; - let t: Test = serde_json::from_str::(color_text).unwrap(); - assert_eq!(t, Test { c: color }); - } - - #[test] - fn deserialize_hex() { - let color: Color = Color::Rgb(18, 252, 28); - let color_text = r###"{ "c": "#12fc1c" }"###; - let t: Test = serde_json::from_str::(color_text).unwrap(); - assert_eq!(t, Test { c: color }); - } - - #[test] - fn deserialize_short_hex() { - let color: Color = Color::Rgb(255, 255, 170); - let color_text = r###"{ "c": "#FFA" }"###; - let t: Test = serde_json::from_str::(color_text).unwrap(); - assert_eq!(t, Test { c: color }); - } - - #[test] - fn deserialize_hex_and_short_hex() { - let color_text_hex = r###"{ "c": "#FF99CC" }"###; - let color_text_short_hex = r###"{ "c": "#F9C" }"###; - let t_h: Test = serde_json::from_str::(color_text_hex).unwrap(); - let t_sh: Test = serde_json::from_str::(color_text_short_hex).unwrap(); - assert_eq!(t_h, t_sh); - } - - #[test] - fn deserialize_index() { - let color: Color = Color::Indexed(123); - let color_text = r###"{ "c": "123" }"###; - let t: Test = serde_json::from_str::(color_text).unwrap(); - assert_eq!(t, Test { c: color }); - } -} diff --git a/rm-config/src/lib.rs b/rm-config/src/lib.rs index bd566dd..b510760 100644 --- a/rm-config/src/lib.rs +++ b/rm-config/src/lib.rs @@ -1,5 +1,3 @@ -mod color; - use std::{ fs::File, io::{Read, Write}, @@ -8,7 +6,7 @@ use std::{ }; use anyhow::{bail, Context, Result}; -use color::Color; +use ratatui::style::Color; use serde::{Deserialize, Serialize}; use toml::Table; use xdg::BaseDirectories; @@ -23,12 +21,16 @@ pub struct Config { pub struct General { #[serde(default)] pub auto_hide: bool, - #[serde(default, with = "color")] + #[serde(default = "default_accent_color")] pub accent_color: Color, #[serde(default = "default_beginner_mode")] pub beginner_mode: bool, } +fn default_accent_color() -> Color { + Color::LightMagenta +} + fn default_beginner_mode() -> bool { true } diff --git a/rm-main/src/ui/components/tabs.rs b/rm-main/src/ui/components/tabs.rs index af52d16..0a56e85 100644 --- a/rm-main/src/ui/components/tabs.rs +++ b/rm-main/src/ui/components/tabs.rs @@ -44,8 +44,7 @@ impl Component for TabComponent { .flex(Flex::Center) .split(rect)[0]; - let tabs_highlight_style = - Style::default().fg(self.ctx.config.general.accent_color.as_ratatui()); + let tabs_highlight_style = Style::default().fg(self.ctx.config.general.accent_color); let tabs = Tabs::new(self.tabs_list) .style(Style::default().white()) .highlight_style(tabs_highlight_style) diff --git a/rm-main/src/ui/global_popups/help.rs b/rm-main/src/ui/global_popups/help.rs index 28cf734..2b06f52 100644 --- a/rm-main/src/ui/global_popups/help.rs +++ b/rm-main/src/ui/global_popups/help.rs @@ -46,13 +46,13 @@ impl Component for HelpPopup { let popup_rect = centered_rect.inner(&Margin::new(1, 1)); let text_rect = popup_rect.inner(&Margin::new(3, 2)); - let title_style = Style::new().fg(self.ctx.config.general.accent_color.as_ratatui()); + let title_style = Style::new().fg(self.ctx.config.general.accent_color); let block = Block::bordered() .border_set(symbols::border::ROUNDED) .title( Title::from( " [ CLOSE ] " - .fg(self.ctx.config.general.accent_color.as_ratatui()) + .fg(self.ctx.config.general.accent_color) .bold(), ) .alignment(Alignment::Right) diff --git a/rm-main/src/ui/tabs/search.rs b/rm-main/src/ui/tabs/search.rs index cdd1e7f..927207a 100644 --- a/rm-main/src/ui/tabs/search.rs +++ b/rm-main/src/ui/tabs/search.rs @@ -228,7 +228,7 @@ impl Component for SearchTab { if self.search_focus == SearchFocus::Search { Style::default() .underlined() - .fg(self.ctx.config.general.accent_color.as_ratatui()) + .fg(self.ctx.config.general.accent_color) } else { Style::default().underlined().gray() } @@ -264,8 +264,7 @@ impl Component for SearchTab { .ctx .config .general - .accent_color - .as_ratatui()); + .accent_color); let table = Table::new(items, widths) .header(header) .highlight_style(table_higlight_style); diff --git a/rm-main/src/ui/tabs/torrents/input_manager.rs b/rm-main/src/ui/tabs/torrents/input_manager.rs index 8a34f51..93ff5ae 100644 --- a/rm-main/src/ui/tabs/torrents/input_manager.rs +++ b/rm-main/src/ui/tabs/torrents/input_manager.rs @@ -50,7 +50,7 @@ impl Component for InputManager { spans.push(Span::styled( self.prompt.as_str(), - Style::default().fg(self.ctx.config.general.accent_color.as_ratatui()), + Style::default().fg(self.ctx.config.general.accent_color), )); spans.push(Span::raw(self.text())); diff --git a/rm-main/src/ui/tabs/torrents/mod.rs b/rm-main/src/ui/tabs/torrents/mod.rs index 173b0f6..9e83925 100644 --- a/rm-main/src/ui/tabs/torrents/mod.rs +++ b/rm-main/src/ui/tabs/torrents/mod.rs @@ -118,8 +118,7 @@ impl TorrentsTab { .ctx .config .general - .accent_color - .as_ratatui()); + .accent_color); let table_widget = Table::new(torrent_rows, table_manager_lock.widths) .header(Row::new( diff --git a/rm-main/src/ui/tabs/torrents/popups/files.rs b/rm-main/src/ui/tabs/torrents/popups/files.rs index 5455133..5a9a2ef 100644 --- a/rm-main/src/ui/tabs/torrents/popups/files.rs +++ b/rm-main/src/ui/tabs/torrents/popups/files.rs @@ -200,8 +200,7 @@ impl Component for FilesPopup { let info_text_rect = block_rect.inner(&Margin::new(3, 2)); - let highlight_style = - Style::default().fg(self.ctx.config.general.accent_color.as_ratatui()); + let highlight_style = Style::default().fg(self.ctx.config.general.accent_color); let bold_highlight_style = highlight_style.on_black().bold(); let block = Block::bordered() diff --git a/rm-main/src/ui/tabs/torrents/popups/stats.rs b/rm-main/src/ui/tabs/torrents/popups/stats.rs index da54c17..db846f7 100644 --- a/rm-main/src/ui/tabs/torrents/popups/stats.rs +++ b/rm-main/src/ui/tabs/torrents/popups/stats.rs @@ -40,7 +40,7 @@ impl Component for StatisticsPopup { let block_rect = popup_rect.inner(&Margin::new(1, 1)); let text_rect = block_rect.inner(&Margin::new(3, 2)); - let title_style = Style::default().fg(self.ctx.config.general.accent_color.as_ratatui()); + let title_style = Style::default().fg(self.ctx.config.general.accent_color); let block = Block::bordered() .border_type(BorderType::Rounded) .title(Title::from(" Statistics ".set_style(title_style))) diff --git a/rm-main/src/ui/tabs/torrents/table_manager.rs b/rm-main/src/ui/tabs/torrents/table_manager.rs index e3b596d..7fc2aad 100644 --- a/rm-main/src/ui/tabs/torrents/table_manager.rs +++ b/rm-main/src/ui/tabs/torrents/table_manager.rs @@ -88,8 +88,7 @@ impl TableManager { let matcher = SkimMatcherV2::default(); let mut rows = vec![]; - let highlight_style = - Style::default().fg(self.ctx.config.general.accent_color.as_ratatui()); + let highlight_style = Style::default().fg(self.ctx.config.general.accent_color); for torrent in torrents { if let Some((_, indices)) = matcher.fuzzy_indices(&torrent.torrent_name, filter) {