diff --git a/Cargo.lock b/Cargo.lock index bf791c5..a8d57fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -786,6 +786,9 @@ name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] [[package]] name = "bitstream-io" @@ -1051,6 +1054,7 @@ dependencies = [ "itoa", "rustversion", "ryu", + "serde", "static_assertions", ] @@ -1177,6 +1181,23 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.10.0", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix 0.38.34", + "serde", + "signal-hook", + "signal-hook-mio", + "winapi", +] + [[package]] name = "crossterm" version = "0.29.0" @@ -1191,6 +1212,7 @@ dependencies = [ "mio", "parking_lot", "rustix 1.1.2", + "serde", "signal-hook", "signal-hook-mio", "winapi", @@ -2759,6 +2781,12 @@ dependencies = [ "libc", ] +[[package]] +name = "numtoa" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" + [[package]] name = "objc2" version = "0.6.0" @@ -3358,8 +3386,10 @@ dependencies = [ "ratatui-core", "ratatui-crossterm", "ratatui-macros", + "ratatui-termion", "ratatui-termwiz", "ratatui-widgets", + "serde", ] [[package]] @@ -3375,6 +3405,7 @@ dependencies = [ "itertools 0.14.0", "kasuari", "lru", + "serde", "strum", "thiserror 2.0.17", "unicode-segmentation", @@ -3389,7 +3420,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" dependencies = [ "cfg-if", - "crossterm", + "crossterm 0.28.1", + "crossterm 0.29.0", "instability", "ratatui-core", ] @@ -3420,6 +3452,17 @@ dependencies = [ "ratatui-widgets", ] +[[package]] +name = "ratatui-termion" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cade85a8591fbc911e147951422f0d6fd40f4948b271b6216c7dc01838996f8" +dependencies = [ + "instability", + "ratatui-core", + "termion", +] + [[package]] name = "ratatui-termwiz" version = "0.1.0" @@ -3443,6 +3486,7 @@ dependencies = [ "itertools 0.14.0", "line-clipping", "ratatui-core", + "serde", "strum", "time", "unicode-segmentation", @@ -4145,7 +4189,7 @@ dependencies = [ "chrono", "clap", "console", - "crossterm", + "crossterm 0.29.0", "encoding_rs", "futures", "humansize", @@ -4263,6 +4307,17 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "termion" +version = "4.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44138a9ae08f0f502f24104d82517ef4da7330c35acd638f1f29d3cd5475ecb" +dependencies = [ + "libc", + "numtoa", + "serde", +] + [[package]] name = "termios" version = "0.3.3" @@ -4297,6 +4352,7 @@ dependencies = [ "pest", "pest_derive", "phf", + "serde", "sha2", "signal-hook", "siphasher", @@ -4773,6 +4829,7 @@ dependencies = [ "atomic", "getrandom 0.3.3", "js-sys", + "serde_core", "wasm-bindgen", ] @@ -4988,6 +5045,7 @@ checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" dependencies = [ "getrandom 0.3.3", "mac_address", + "serde", "sha2", "thiserror 1.0.61", "uuid", @@ -5002,6 +5060,7 @@ dependencies = [ "csscolorparser", "deltae", "lazy_static", + "serde", "wezterm-dynamic", ] diff --git a/Cargo.toml b/Cargo.toml index 5b73a14..965c316 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ itsuki = "0.2.1" laurier = "0.3.0" once_cell = "1.21.3" open = "5.3.3" -ratatui = "0.30.0" +ratatui = { version = "0.30.0", features = ["serde"] } ratatui-image = { version = "10.0.4", default-features = false, features = [ "crossterm", "image-defaults", diff --git a/docs/src/configurations/config-file-format.md b/docs/src/configurations/config-file-format.md index 2352d0d..871ef83 100644 --- a/docs/src/configurations/config-file-format.md +++ b/docs/src/configurations/config-file-format.md @@ -23,6 +23,27 @@ date_format = "%Y-%m-%d %H:%M:%S" [ui.help] max_help_width = 100 +[ui.theme] +bg = "reset" +fg = "reset" +divider = "dark_gray" +link = "blue" +list_selected_bg = "#FFD166" +list_selected_fg = "black" +list_selected_inactive_bg = "dark_gray" +list_selected_inactive_fg = "black" +list_filter_match = "red" +detail_selected = "cyan" +dialog_selected = "cyan" +preview_line_number = "dark_gray" +help_key_fg = "yellow" +status_help = "dark_gray" +status_info = "blue" +status_success = "green" +status_warn = "yellow" +status_error = "red" +object_dir_bold = true + [preview] highlight = false highlight_theme = "base16-ocean.dark" @@ -119,6 +140,169 @@ The maximum width of the keybindings display area in the help. - type: `usize` - default: `100` +### `ui.theme.bg` + +The default background color used across the UI. + +- type: `string` +- default: `reset` + +### `ui.theme.fg` + +The default foreground color used across the UI. + +- type: `string` +- default: `reset` + +### `ui.theme.divider` + +The color of dividers and separators. + +- type: `string` +- default: `dark_gray` + +### `ui.theme.link` + +The color used for links in the help view. + +- type: `string` +- default: `blue` + +### `ui.theme.list_selected_bg` + +The background color of the selected row in bucket and object lists. + +- type: `string` +- default: `cyan` + +Theme colors are deserialized by Ratatui's `Color` serde support. +Supported examples include named colors such as `cyan`, `dark_gray`, `bright_white`, +hex colors such as `#FFD166`, and indexed colors such as `42`. + +### `ui.theme.list_selected_fg` + +The foreground color of the selected row in bucket and object lists. + +- type: `string` +- default: `black` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.list_selected_inactive_bg` + +The background color of the selected row when the list is inactive. + +- type: `string` +- default: `dark_gray` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.list_selected_inactive_fg` + +The foreground color of the selected row when the list is inactive. + +- type: `string` +- default: `black` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.list_filter_match` + +The color used to highlight matched filter text in lists. + +- type: `string` +- default: `red` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.detail_selected` + +The color used for the selected tab and selection accents in the object detail page. + +- type: `string` +- default: `cyan` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.dialog_selected` + +The color used to highlight the selected choice in dialogs. + +- type: `string` +- default: `cyan` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.preview_line_number` + +The color of line numbers in the text preview. + +- type: `string` +- default: `dark_gray` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.help_key_fg` + +The foreground color of key labels in the help view. + +- type: `string` +- default: `yellow` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.status_help` + +The color used for help/status hint messages. + +- type: `string` +- default: `dark_gray` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.status_info` + +The color used for informational status messages. + +- type: `string` +- default: `blue` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.status_success` + +The color used for success status messages. + +- type: `string` +- default: `green` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.status_warn` + +The color used for warning status messages. + +- type: `string` +- default: `yellow` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.status_error` + +The color used for error status messages. + +- type: `string` +- default: `red` + +Supports the same color formats as `ui.theme.list_selected_bg`. + +### `ui.theme.object_dir_bold` + +Whether directory names should be rendered in bold. + +- type: `bool` +- default: `true` + ### `preview.highlight` Whether syntax highlighting is enabled in the object preview. diff --git a/src/app.rs b/src/app.rs index 04d4b83..9695c32 100644 --- a/src/app.rs +++ b/src/app.rs @@ -15,7 +15,7 @@ use tokio::spawn; use crate::{ client::Client, - color::ColorTheme, + color::Theme, config::Config, environment::Environment, error::{AppError, Result}, @@ -46,12 +46,15 @@ pub enum Notification { pub struct AppContext { pub config: Config, pub env: Environment, - pub theme: ColorTheme, } impl AppContext { - pub fn new(config: Config, env: Environment, theme: ColorTheme) -> AppContext { - AppContext { config, env, theme } + pub fn new(config: Config, env: Environment) -> AppContext { + AppContext { config, env } + } + + pub fn theme(&self) -> &Theme { + &self.config.ui.theme } } @@ -898,13 +901,13 @@ impl App { } fn render_background(&self, f: &mut Frame, area: Rect) { - let block = Block::default().bg(self.ctx.theme.bg); + let block = Block::default().bg(self.ctx.theme().bg); f.render_widget(block, area); } fn render_header(&self, f: &mut Frame, area: Rect) { if !area.is_empty() { - let header = Header::new(self.page_stack.breadcrumb()).theme(&self.ctx.theme); + let header = Header::new(self.page_stack.breadcrumb()).theme(self.ctx.theme()); f.render_widget(header, area); } } @@ -923,13 +926,13 @@ impl App { StatusType::Help(self.page_stack.current_page().short_helps(&self.mapper)) } }; - let status = Status::new(status_type).theme(&self.ctx.theme); + let status = Status::new(status_type).theme(self.ctx.theme()); f.render_widget(status, area); } fn render_loading_dialog(&self, f: &mut Frame) { if self.loading() { - let dialog = LoadingDialog::default().theme(&self.ctx.theme); + let dialog = LoadingDialog::default().theme(self.ctx.theme()); f.render_widget(dialog, f.area()); } } diff --git a/src/color.rs b/src/color.rs index 1845c6a..1bf9ecc 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,62 +1,112 @@ -use ratatui::style::Color; - -#[derive(Debug, Clone)] -pub struct ColorTheme { +use ratatui::style::{Color, Modifier, Style}; +use serde::Deserialize; +use smart_default::SmartDefault; +use umbra::optional; + +#[optional(derives = [Deserialize], visibility = pub)] +#[derive(Debug, Clone, SmartDefault)] +pub struct Theme { + #[default(Color::Reset)] pub bg: Color, + #[default(Color::Reset)] pub fg: Color, + #[default(Color::DarkGray)] pub divider: Color, + #[default(Color::Blue)] pub link: Color, + #[default(Color::Cyan)] pub list_selected_bg: Color, + #[default(Color::Black)] pub list_selected_fg: Color, + #[default(Color::DarkGray)] pub list_selected_inactive_bg: Color, + #[default(Color::Black)] pub list_selected_inactive_fg: Color, + #[default(Color::Red)] pub list_filter_match: Color, + #[default(Color::Cyan)] pub detail_selected: Color, + #[default(Color::Cyan)] pub dialog_selected: Color, + #[default(Color::DarkGray)] pub preview_line_number: Color, + #[default(Color::Yellow)] pub help_key_fg: Color, + #[default(Color::DarkGray)] pub status_help: Color, + #[default(Color::Blue)] pub status_info: Color, + #[default(Color::Green)] pub status_success: Color, + #[default(Color::Yellow)] pub status_warn: Color, + #[default(Color::Red)] pub status_error: Color, + #[default = true] + pub object_dir_bold: bool, } -impl Default for ColorTheme { - fn default() -> Self { - Self { - bg: Color::Reset, - fg: Color::Reset, +impl Theme { + pub fn list_item_style(&self, selected: bool, active: bool) -> Style { + if !selected { + return Style::default(); + } - divider: Color::DarkGray, - link: Color::Blue, + if active { + self.list_selected_style() + } else { + self.list_selected_inactive_style() + } + } - list_selected_bg: Color::Cyan, - list_selected_fg: Color::Black, - list_selected_inactive_bg: Color::DarkGray, - list_selected_inactive_fg: Color::Black, - list_filter_match: Color::Red, + pub fn list_selected_style(&self) -> Style { + list_style(self.list_selected_bg, self.list_selected_fg) + } - detail_selected: Color::Cyan, + pub fn list_selected_inactive_style(&self) -> Style { + list_style( + self.list_selected_inactive_bg, + self.list_selected_inactive_fg, + ) + } - dialog_selected: Color::Cyan, + pub fn object_dir_style(&self) -> Style { + let mut style = Style::default(); + if self.object_dir_bold { + style = style.add_modifier(Modifier::BOLD); + } + style + } +} + +fn list_style(bg: Color, fg: Color) -> Style { + Style::default().bg(bg).fg(fg) +} - preview_line_number: Color::DarkGray, +#[cfg(test)] +mod tests { + use super::*; - help_key_fg: Color::Yellow, + #[test] + fn list_item_style_falls_back_to_default_for_unselected_items() { + let theme = Theme::default(); + assert_eq!(theme.list_item_style(false, true), Style::default()); + assert_eq!(theme.list_item_style(false, false), Style::default()); + } - status_help: Color::DarkGray, - status_info: Color::Blue, - status_success: Color::Green, - status_warn: Color::Yellow, - status_error: Color::Red, - } + #[test] + fn object_dir_style_adds_bold_when_enabled() { + let theme = Theme::default(); + assert_eq!( + theme.object_dir_style(), + Style::default().add_modifier(Modifier::BOLD) + ); } } diff --git a/src/config.rs b/src/config.rs index 5156161..37d6d7f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,6 +8,8 @@ use serde::Deserialize; use smart_default::SmartDefault; use umbra::optional; +use crate::color::{OptionalTheme, Theme}; + const STU_ROOT_DIR_ENV_VAR: &str = "STU_ROOT_DIR"; const APP_BASE_DIR: &str = ".stu"; @@ -45,6 +47,8 @@ pub struct UiConfig { pub object_detail: UiObjectDetailConfig, #[nested] pub help: UiHelpConfig, + #[nested] + pub theme: Theme, } #[optional(derives = [Deserialize])] @@ -208,3 +212,61 @@ impl Config { } } } + +#[cfg(test)] +mod tests { + use ratatui::style::Color; + + use super::*; + + fn parse_config(input: &str) -> Config { + let config: OptionalConfig = toml::from_str(input).unwrap(); + config.into() + } + + #[test] + fn theme_defaults_when_omitted() { + let config = parse_config(""); + + assert_eq!(config.ui.theme.bg, Color::Reset); + assert_eq!(config.ui.theme.link, Color::Blue); + assert_eq!(config.ui.theme.list_selected_bg, Color::Cyan); + assert!(config.ui.theme.object_dir_bold); + } + + #[test] + fn theme_accepts_ratatui_color_formats() { + let config = parse_config( + r##" +[ui.theme] +bg = "reset" +list_selected_bg = "#FFD166" +list_selected_fg = "42" +status_error = "bright-white" +object_dir_bold = false +"##, + ); + + assert_eq!(config.ui.theme.bg, Color::Reset); + assert_eq!( + config.ui.theme.list_selected_bg, + Color::Rgb(0xFF, 0xD1, 0x66) + ); + assert_eq!(config.ui.theme.list_selected_fg, Color::Indexed(42)); + assert_eq!(config.ui.theme.status_error, Color::White); + assert!(!config.ui.theme.object_dir_bold); + assert_eq!(config.ui.theme.dialog_selected, Color::Cyan); + } + + #[test] + fn invalid_theme_color_is_rejected() { + let result = toml::from_str::( + r#" +[ui.theme] +list_selected_bg = "not-a-color" +"#, + ); + + assert!(result.is_err()); + } +} diff --git a/src/main.rs b/src/main.rs index 41f9e12..8ba43a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,6 @@ use tracing_subscriber::fmt::time::ChronoLocal; use crate::{ app::{App, AppContext}, client::Client, - color::ColorTheme, config::Config, environment::Environment, keys::UserEventMapper, @@ -97,8 +96,7 @@ async fn main() -> anyhow::Result<()> { let config = Config::load()?; let mapper = UserEventMapper::load()?; let env = Environment::new(config.preview.image, args.fix_dynamic_values_for_test); - let theme = ColorTheme::default(); - let ctx = AppContext::new(config, env, theme); + let ctx = AppContext::new(config, env); initialize_debug_log(&args)?; diff --git a/src/pages/bucket_list.rs b/src/pages/bucket_list.rs index 2c781db..db90063 100644 --- a/src/pages/bucket_list.rs +++ b/src/pages/bucket_list.rs @@ -12,7 +12,7 @@ use ratatui::{ use crate::{ app::AppContext, - color::ColorTheme, + color::Theme, event::{AppEventType, Sender}, format::format_size_byte, handle_user_events, handle_user_events_with_default, @@ -230,20 +230,20 @@ impl BucketListPage { &self.bucket_items, &self.view_indices, self.filter_input_state.input(), - &self.ctx.theme, + self.ctx.theme(), offset, selected, area, ); - let list = ScrollList::new(list_items).theme(&self.ctx.theme); + let list = ScrollList::new(list_items).theme(self.ctx.theme()); f.render_stateful_widget(list, area, &mut self.list_state); if let ViewState::FilterDialog = self.view_state { let filter_dialog = InputDialog::default() .title("Filter") .max_width(30) - .theme(&self.ctx.theme); + .theme(self.ctx.theme()); f.render_stateful_widget(filter_dialog, area, &mut self.filter_input_state); let (cursor_x, cursor_y) = self.filter_input_state.cursor(); @@ -252,18 +252,18 @@ impl BucketListPage { if let ViewState::SortDialog = self.view_state { let sort_dialog = - BucketListSortDialog::new(self.sort_dialog_state).theme(&self.ctx.theme); + BucketListSortDialog::new(self.sort_dialog_state).theme(self.ctx.theme()); f.render_widget(sort_dialog, area); } if let ViewState::CopyDetailDialog(state) = &mut self.view_state { - let copy_detail_dialog = CopyDetailDialog::default().theme(&self.ctx.theme); + let copy_detail_dialog = CopyDetailDialog::default().theme(self.ctx.theme()); f.render_stateful_widget(copy_detail_dialog, area, state); } if let ViewState::DownloadConfirmDialog(objs, state, _) = &mut self.view_state { - let message_lines = build_download_confirm_message_lines(objs, &self.ctx.theme); - let download_confirm_dialog = ConfirmDialog::new(message_lines).theme(&self.ctx.theme); + let message_lines = build_download_confirm_message_lines(objs, self.ctx.theme()); + let download_confirm_dialog = ConfirmDialog::new(message_lines).theme(self.ctx.theme()); f.render_stateful_widget(download_confirm_dialog, area, state); } @@ -271,7 +271,7 @@ impl BucketListPage { let save_dialog = InputDialog::default() .title("Save As") .max_width(40) - .theme(&self.ctx.theme); + .theme(self.ctx.theme()); f.render_stateful_widget(save_dialog, area, state); let (cursor_x, cursor_y) = state.cursor(); @@ -364,7 +364,7 @@ impl BucketListPage { ] } }; - build_help_spans(helps, mapper, self.ctx.theme.help_key_fg) + build_help_spans(helps, mapper, self.ctx.theme().help_key_fg) } pub fn short_helps(&self, mapper: &UserEventMapper) -> Vec { @@ -675,7 +675,7 @@ fn build_list_items<'a>( current_items: &'a [BucketItem], view_indices: &'a [usize], filter: &'a str, - theme: &'a ColorTheme, + theme: &'a Theme, offset: usize, selected: usize, area: Rect, @@ -699,7 +699,7 @@ fn build_list_item<'a>( selected: bool, filter: &'a str, width: u16, - theme: &'a ColorTheme, + theme: &'a Theme, ) -> ListItem<'a> { let name_w = (width as usize) - 5 /* border + pad + scroll */; let w = console::measure_text_width(name); @@ -727,19 +727,12 @@ fn build_list_item<'a>( Line::from(spans) }; - let style = if selected { - Style::default() - .bg(theme.list_selected_bg) - .fg(theme.list_selected_fg) - } else { - Style::default() - }; - ListItem::new(line).style(style) + ListItem::new(line).style(theme.list_item_style(selected, true)) } fn build_download_confirm_message_lines<'a>( objs: &[DownloadObjectInfo], - theme: &ColorTheme, + theme: &Theme, ) -> Vec> { let total_size = format_size_byte(objs.iter().map(|obj| obj.size_byte).sum()); let total_count = objs.len(); diff --git a/src/pages/help.rs b/src/pages/help.rs index 6ba9f5f..b015a92 100644 --- a/src/pages/help.rs +++ b/src/pages/help.rs @@ -47,7 +47,7 @@ impl HelpPage { pub fn render(&mut self, f: &mut Frame, area: Rect) { let block = Block::bordered() .padding(Padding::horizontal(1)) - .fg(self.ctx.theme.fg); + .fg(self.ctx.theme().fg); let content_area = block.inner(area); @@ -63,9 +63,9 @@ impl HelpPage { APP_DESCRIPTION, APP_VERSION, APP_REPOSITORY_URL, - self.ctx.theme.link, + self.ctx.theme().link, ); - let divider = Divider::default().color(self.ctx.theme.divider); + let divider = Divider::default().color(self.ctx.theme().divider); let help = Help::new(&self.helps, self.ctx.config.ui.help.max_help_width); f.render_widget(block, area); diff --git a/src/pages/initializing.rs b/src/pages/initializing.rs index 9d99965..7bf523a 100644 --- a/src/pages/initializing.rs +++ b/src/pages/initializing.rs @@ -23,7 +23,7 @@ impl InitializingPage { pub fn handle_key(&mut self, _user_events: Vec, _key_event: KeyEvent) {} pub fn render(&mut self, f: &mut Frame, area: Rect) { - let content = Block::bordered().fg(self.ctx.theme.fg); + let content = Block::bordered().fg(self.ctx.theme().fg); f.render_widget(content, area); } diff --git a/src/pages/object_detail.rs b/src/pages/object_detail.rs index 6ffc987..0f12e5e 100644 --- a/src/pages/object_detail.rs +++ b/src/pages/object_detail.rs @@ -12,7 +12,7 @@ use ratatui::{ use crate::{ app::AppContext, - color::ColorTheme, + color::Theme, config::UiConfig, environment::Environment, event::{AppEventType, Sender}, @@ -202,29 +202,29 @@ impl ObjectDetailPage { offset, selected, chunks[0], - &self.ctx.theme, + self.ctx.theme(), ); - let list = ScrollList::new(list_items).theme(&self.ctx.theme); + let list = ScrollList::new(list_items).theme(self.ctx.theme()); f.render_stateful_widget(list, chunks[0], &mut self.list_state); - let block = Block::bordered().fg(self.ctx.theme.fg); + let block = Block::bordered().fg(self.ctx.theme().fg); f.render_widget(block, chunks[1]); let chunks = Layout::vertical([Constraint::Length(2), Constraint::Min(0)]) .margin(1) .split(chunks[1]); - let tabs = build_tabs(&self.tab, &self.ctx.theme); + let tabs = build_tabs(&self.tab, self.ctx.theme()); f.render_widget(tabs, chunks[0]); match self.tab { Tab::Detail(ref mut state) => { - let detail = DetailTab::new(&self.ctx.theme); + let detail = DetailTab::new(self.ctx.theme()); f.render_stateful_widget(detail, chunks[1], state); } Tab::Version(ref mut state) => { - let version = VersionTab::new(&self.ctx.theme); + let version = VersionTab::new(self.ctx.theme()); f.render_stateful_widget(version, chunks[1], state); } } @@ -233,7 +233,7 @@ impl ObjectDetailPage { let save_dialog = InputDialog::default() .title("Save As") .max_width(40) - .theme(&self.ctx.theme); + .theme(self.ctx.theme()); f.render_stateful_widget(save_dialog, area, state); let (cursor_x, cursor_y) = state.cursor(); @@ -241,7 +241,7 @@ impl ObjectDetailPage { } if let ViewState::CopyDetailDialog(state) = &mut self.view_state { - let copy_detail_dialog = CopyDetailDialog::default().theme(&self.ctx.theme); + let copy_detail_dialog = CopyDetailDialog::default().theme(self.ctx.theme()); f.render_stateful_widget(copy_detail_dialog, area, state); } } @@ -300,7 +300,7 @@ impl ObjectDetailPage { ] }, }; - build_help_spans(helps, mapper, self.ctx.theme.help_key_fg) + build_help_spans(helps, mapper, self.ctx.theme().help_key_fg) } pub fn short_helps(&self, mapper: &UserEventMapper) -> Vec { @@ -483,7 +483,7 @@ fn build_list_items_from_object_items<'a>( offset: usize, selected: usize, area: Rect, - theme: &ColorTheme, + theme: &Theme, ) -> Vec> { let show_item_count = (area.height as usize) - 2 /* border */; current_items @@ -503,12 +503,12 @@ fn build_list_item_from_object_item<'a>( offset: usize, selected: usize, area: Rect, - theme: &ColorTheme, + theme: &Theme, ) -> ListItem<'a> { let content = match item { ObjectItem::Dir { name, .. } => { let content = format_dir_item(name, area.width); - let style = Style::default().add_modifier(Modifier::BOLD); + let style = theme.object_dir_style(); Span::styled(content, style) } ObjectItem::File { name, .. } => { @@ -517,15 +517,7 @@ fn build_list_item_from_object_item<'a>( Span::styled(content, style) } }; - if idx + offset == selected { - ListItem::new(content).style( - Style::default() - .bg(theme.list_selected_inactive_bg) - .fg(theme.list_selected_inactive_fg), - ) - } else { - ListItem::new(content) - } + ListItem::new(content).style(theme.list_item_style(idx + offset == selected, false)) } fn format_dir_item(name: &str, width: u16) -> String { @@ -539,7 +531,7 @@ fn format_file_item(name: &str, width: u16) -> String { format!(" {name: Tabs<'static> { +fn build_tabs(tab: &Tab, theme: &Theme) -> Tabs<'static> { let tabs = vec!["Detail", "Version"]; Tabs::new(tabs) .select(tab.val()) @@ -604,11 +596,11 @@ impl DetailTabState { #[derive(Debug)] struct DetailTab<'a> { - theme: &'a ColorTheme, + theme: &'a Theme, } impl<'a> DetailTab<'a> { - fn new(theme: &'a ColorTheme) -> Self { + fn new(theme: &'a Theme) -> Self { Self { theme } } } @@ -737,7 +729,7 @@ struct VersionTabColor { } impl VersionTabColor { - fn new(theme: &ColorTheme) -> Self { + fn new(theme: &Theme) -> Self { Self { selected: theme.detail_selected, divider: theme.divider, @@ -751,7 +743,7 @@ struct VersionTab { } impl VersionTab { - fn new(theme: &ColorTheme) -> Self { + fn new(theme: &Theme) -> Self { Self { color: VersionTabColor::new(theme), } diff --git a/src/pages/object_list.rs b/src/pages/object_list.rs index e85cea2..dbcd494 100644 --- a/src/pages/object_list.rs +++ b/src/pages/object_list.rs @@ -6,14 +6,14 @@ use ratatui::{ crossterm::event::KeyEvent, layout::Rect, style::{Style, Stylize}, - text::Line, + text::{Line, Span}, widgets::ListItem, Frame, }; use crate::{ app::AppContext, - color::ColorTheme, + color::Theme, config::UiConfig, environment::Environment, event::{AppEventType, Sender}, @@ -251,17 +251,17 @@ impl ObjectListPage { area, &self.ctx.config.ui, &self.ctx.env, - &self.ctx.theme, + self.ctx.theme(), ); - let list = ScrollList::new(list_items).theme(&self.ctx.theme); + let list = ScrollList::new(list_items).theme(self.ctx.theme()); f.render_stateful_widget(list, area, &mut self.list_state); if let ViewState::FilterDialog = self.view_state { let filter_dialog = InputDialog::default() .title("Filter") .max_width(30) - .theme(&self.ctx.theme); + .theme(self.ctx.theme()); f.render_stateful_widget(filter_dialog, area, &mut self.filter_input_state); let (cursor_x, cursor_y) = self.filter_input_state.cursor(); @@ -270,18 +270,18 @@ impl ObjectListPage { if let ViewState::SortDialog = self.view_state { let sort_dialog = - ObjectListSortDialog::new(self.sort_dialog_state).theme(&self.ctx.theme); + ObjectListSortDialog::new(self.sort_dialog_state).theme(self.ctx.theme()); f.render_widget(sort_dialog, area); } if let ViewState::CopyDetailDialog(state) = &mut self.view_state { - let copy_detail_dialog = CopyDetailDialog::default().theme(&self.ctx.theme); + let copy_detail_dialog = CopyDetailDialog::default().theme(self.ctx.theme()); f.render_stateful_widget(copy_detail_dialog, area, state); } if let ViewState::DownloadConfirmDialog(objs, state, _) = &mut self.view_state { - let message_lines = build_download_confirm_message_lines(objs, &self.ctx.theme); - let download_confirm_dialog = ConfirmDialog::new(message_lines).theme(&self.ctx.theme); + let message_lines = build_download_confirm_message_lines(objs, self.ctx.theme()); + let download_confirm_dialog = ConfirmDialog::new(message_lines).theme(self.ctx.theme()); f.render_stateful_widget(download_confirm_dialog, area, state); } @@ -289,7 +289,7 @@ impl ObjectListPage { let save_dialog = InputDialog::default() .title("Save As") .max_width(40) - .theme(&self.ctx.theme); + .theme(self.ctx.theme()); f.render_stateful_widget(save_dialog, area, state); let (cursor_x, cursor_y) = state.cursor(); @@ -386,7 +386,7 @@ impl ObjectListPage { ] } }; - build_help_spans(helps, mapper, self.ctx.theme.help_key_fg) + build_help_spans(helps, mapper, self.ctx.theme().help_key_fg) } pub fn short_helps(&self, mapper: &UserEventMapper) -> Vec { @@ -774,7 +774,7 @@ fn build_list_items<'a>( area: Rect, ui_config: &UiConfig, env: &Environment, - theme: &ColorTheme, + theme: &Theme, ) -> Vec> { let show_item_count = (area.height as usize) - 2 /* border */; view_indices @@ -804,7 +804,7 @@ fn build_list_item<'a>( area: Rect, ui_config: &UiConfig, env: &Environment, - theme: &ColorTheme, + theme: &Theme, ) -> ListItem<'a> { let line = match item { ObjectItem::Dir { name, .. } => build_object_dir_line(name, filter, area.width, theme), @@ -825,21 +825,14 @@ fn build_list_item<'a>( ), }; - let style = if selected { - Style::default() - .bg(theme.list_selected_bg) - .fg(theme.list_selected_fg) - } else { - Style::default() - }; - ListItem::new(line).style(style) + ListItem::new(line).style(theme.list_item_style(selected, true)) } fn build_object_dir_line<'a>( name: &'a str, filter: &'a str, width: u16, - theme: &ColorTheme, + theme: &Theme, ) -> Line<'a> { let name = format!("{name}/"); let name_w = (width as usize) - 2 /* spaces */ - 4 /* border + pad */ - 1 /* slash */; @@ -851,17 +844,20 @@ fn build_object_dir_line<'a>( }; if filter.is_empty() { - Line::from(vec![" ".into(), pad_name.bold(), " ".into()]) + let name_span = Span::styled(pad_name, theme.object_dir_style()); + Line::from(vec![" ".into(), name_span, " ".into()]) } else { let i = name.find(filter).unwrap(); let mut hm = highlight_matched_text(vec![pad_name.into()]); if w > name_w { hm = hm.ellipsis(ELLIPSIS); } + let not_matched_style = theme.object_dir_style(); + let matched_style = not_matched_style.fg(theme.list_filter_match); let mut spans = hm .matched_range(i, i + filter.len()) - .not_matched_style(Style::default().bold()) - .matched_style(Style::default().fg(theme.list_filter_match).bold()) + .not_matched_style(not_matched_style) + .matched_style(matched_style) .into_spans(); spans.insert(0, " ".into()); spans.push(" ".into()); @@ -877,7 +873,7 @@ fn build_object_file_line<'a>( width: u16, ui_config: &UiConfig, env: &Environment, - theme: &ColorTheme, + theme: &Theme, ) -> Line<'a> { let size = format_size_byte(size_byte); let date = format_datetime( @@ -931,7 +927,7 @@ fn build_object_file_line<'a>( fn build_download_confirm_message_lines<'a>( objs: &[DownloadObjectInfo], - theme: &ColorTheme, + theme: &Theme, ) -> Vec> { let total_size = format_size_byte(objs.iter().map(|obj| obj.size_byte).sum()); let total_count = objs.len(); @@ -1098,6 +1094,54 @@ mod tests { Ok(()) } + #[tokio::test] + async fn test_render_with_theme_config() -> Result<(), core::convert::Infallible> { + let tx = sender(); + let mut terminal = setup_terminal()?; + + terminal.draw(|f| { + let items = vec![ + object_dir_item("dir1"), + object_dir_item("dir2"), + object_file_item("file1", 1024 + 10, "2024-01-02 13:01:02"), + object_file_item("file2", 1024 * 999, "2023-12-31 09:00:00"), + ]; + let object_key = ObjectKey { + bucket_name: "test-bucket".to_string(), + object_path: vec!["path".to_string(), "to".to_string()], + }; + let mut ctx = AppContext::default(); + ctx.config.ui.theme.list_selected_bg = Color::LightMagenta; + ctx.config.ui.theme.list_selected_fg = Color::Yellow; + ctx.config.ui.theme.object_dir_bold = false; + let mut page = ObjectListPage::new(items, object_key, Rc::new(ctx), tx); + let area = Rect::new(0, 0, 60, 10); + page.render(f, area); + })?; + + #[rustfmt::skip] + let mut expected = Buffer::with_lines([ + "┌─────────────────────────────────────────────────── 1 / 4 ┐", + "│ dir1/ │", + "│ dir2/ │", + "│ file1 2024-01-02 13:01:02 1.01 KiB │", + "│ file2 2023-12-31 09:00:00 999 KiB │", + "│ │", + "│ │", + "│ │", + "│ │", + "└──────────────────────────────────────────────────────────┘", + ]); + set_cells! { expected => + // selected item + (2..58, [1]) => bg: Color::LightMagenta, fg: Color::Yellow, + } + + terminal.backend().assert_buffer(&expected); + + Ok(()) + } + #[tokio::test] async fn test_sort_items() { let ctx = Rc::default(); diff --git a/src/pages/object_preview.rs b/src/pages/object_preview.rs index 9f15103..67a0721 100644 --- a/src/pages/object_preview.rs +++ b/src/pages/object_preview.rs @@ -218,7 +218,7 @@ impl ObjectPreviewPage { self.file_detail.name.as_str(), self.file_version_id.as_deref(), &self.ctx.env, - &self.ctx.theme, + self.ctx.theme(), ); f.render_stateful_widget(preview, area, state); } @@ -236,7 +236,7 @@ impl ObjectPreviewPage { let save_dialog = InputDialog::default() .title("Save As") .max_width(40) - .theme(&self.ctx.theme); + .theme(self.ctx.theme()); f.render_stateful_widget(save_dialog, area, state); let (cursor_x, cursor_y) = state.cursor(); @@ -245,7 +245,7 @@ impl ObjectPreviewPage { if let ViewState::EncodingDialog = &mut self.view_state { let encoding_dialog = - EncodingDialog::new(&self.encoding_dialog_state).theme(&self.ctx.theme); + EncodingDialog::new(&self.encoding_dialog_state).theme(self.ctx.theme()); f.render_widget(encoding_dialog, area); } } @@ -299,7 +299,7 @@ impl ObjectPreviewPage { ] }, }; - build_help_spans(helps, mapper, self.ctx.theme.help_key_fg) + build_help_spans(helps, mapper, self.ctx.theme().help_key_fg) } pub fn short_helps(&self, mapper: &UserEventMapper) -> Vec { diff --git a/src/widget/confirm_dialog.rs b/src/widget/confirm_dialog.rs index 7770e37..ca784c6 100644 --- a/src/widget/confirm_dialog.rs +++ b/src/widget/confirm_dialog.rs @@ -9,7 +9,7 @@ use ratatui::{ }; use crate::{ - color::ColorTheme, + color::Theme, widget::{Dialog, Divider}, }; @@ -46,7 +46,7 @@ struct ConfirmDialogColor { } impl ConfirmDialogColor { - fn new(theme: &ColorTheme) -> ConfirmDialogColor { + fn new(theme: &Theme) -> ConfirmDialogColor { ConfirmDialogColor { bg: theme.bg, block: theme.fg, @@ -72,7 +72,7 @@ impl<'a> ConfirmDialog<'a> { } } - pub fn theme(mut self, theme: &ColorTheme) -> Self { + pub fn theme(mut self, theme: &Theme) -> Self { self.color = ConfirmDialogColor::new(theme); self } diff --git a/src/widget/copy_detail_dialog.rs b/src/widget/copy_detail_dialog.rs index b41b413..8f0a192 100644 --- a/src/widget/copy_detail_dialog.rs +++ b/src/widget/copy_detail_dialog.rs @@ -9,7 +9,7 @@ use ratatui::{ }; use crate::{ - color::ColorTheme, + color::Theme, object::{BucketItem, FileDetail, FileVersion, ObjectItem}, widget::Dialog, }; @@ -281,7 +281,7 @@ struct CopyDetailDialogColor { } impl CopyDetailDialogColor { - fn new(theme: &ColorTheme) -> Self { + fn new(theme: &Theme) -> Self { Self { bg: theme.bg, block: theme.fg, @@ -297,7 +297,7 @@ pub struct CopyDetailDialog { } impl CopyDetailDialog { - pub fn theme(mut self, theme: &ColorTheme) -> Self { + pub fn theme(mut self, theme: &Theme) -> Self { self.color = CopyDetailDialogColor::new(theme); self } @@ -364,7 +364,7 @@ mod tests { #[test] fn test_render_copy_detail_dialog() { let file_detail = file_detail(); - let theme = ColorTheme::default(); + let theme = Theme::default(); let mut state = CopyDetailDialogState::object_detail(file_detail); let copy_detail_dialog = CopyDetailDialog::default().theme(&theme); diff --git a/src/widget/header.rs b/src/widget/header.rs index f547d07..32c1976 100644 --- a/src/widget/header.rs +++ b/src/widget/header.rs @@ -5,7 +5,7 @@ use ratatui::{ widgets::{Block, Padding, Paragraph, Widget}, }; -use crate::{color::ColorTheme, util::prune_strings_to_fit_width}; +use crate::{color::Theme, util::prune_strings_to_fit_width}; #[derive(Debug, Default)] struct HeaderColor { @@ -14,7 +14,7 @@ struct HeaderColor { } impl HeaderColor { - fn new(theme: &ColorTheme) -> HeaderColor { + fn new(theme: &Theme) -> HeaderColor { HeaderColor { block: theme.fg, text: theme.fg, @@ -36,7 +36,7 @@ impl Header { } } - pub fn theme(mut self, theme: &ColorTheme) -> Self { + pub fn theme(mut self, theme: &Theme) -> Self { self.color = HeaderColor::new(theme); self } @@ -101,7 +101,7 @@ mod tests { #[test] fn test_render_header() { - let theme = ColorTheme::default(); + let theme = Theme::default(); let breadcrumb = ["bucket", "key01", "key02", "key03"] .into_iter() .map(|s| s.to_string()) @@ -121,7 +121,7 @@ mod tests { #[test] fn test_render_header_with_ellipsis() { - let theme = ColorTheme::default(); + let theme = Theme::default(); let breadcrumb = ["bucket", "key01", "key02a", "key03"] .into_iter() .map(|s| s.to_string()) @@ -141,7 +141,7 @@ mod tests { #[test] fn test_render_header_empty() { - let theme = ColorTheme::default(); + let theme = Theme::default(); let header = Header::new(vec![]).theme(&theme); let mut buf = Buffer::empty(Rect::new(0, 0, 30 + 4, 3)); header.render(buf.area, &mut buf); diff --git a/src/widget/input_dialog.rs b/src/widget/input_dialog.rs index aa75f1d..e3cc182 100644 --- a/src/widget/input_dialog.rs +++ b/src/widget/input_dialog.rs @@ -8,7 +8,7 @@ use ratatui::{ }; use tui_input::{backend::crossterm::EventHandler, Input}; -use crate::{color::ColorTheme, widget::Dialog}; +use crate::{color::Theme, widget::Dialog}; #[derive(Debug, Default)] pub struct InputDialogState { @@ -59,7 +59,7 @@ struct InputDialogColor { } impl InputDialogColor { - fn new(theme: &ColorTheme) -> InputDialogColor { + fn new(theme: &Theme) -> InputDialogColor { InputDialogColor { bg: theme.bg, block: theme.fg, @@ -86,7 +86,7 @@ impl InputDialog { self } - pub fn theme(mut self, theme: &ColorTheme) -> Self { + pub fn theme(mut self, theme: &Theme) -> Self { self.color = InputDialogColor::new(theme); self } @@ -136,7 +136,7 @@ mod tests { #[test] fn test_render_input_dialog() { - let theme = ColorTheme::default(); + let theme = Theme::default(); let mut state = InputDialogState::default(); let save_dialog = InputDialog::default().theme(&theme); @@ -167,7 +167,7 @@ mod tests { #[test] fn test_render_input_dialog_with_params() { - let theme = ColorTheme::default(); + let theme = Theme::default(); let mut state = InputDialogState::default(); let save_dialog = InputDialog::default() .title("xyz") @@ -200,7 +200,7 @@ mod tests { #[test] fn test_render_input_dialog_with_default_input() { - let theme = ColorTheme::default(); + let theme = Theme::default(); let mut state = InputDialogState::new("xyz".into()); let save_dialog = InputDialog::default().theme(&theme); diff --git a/src/widget/loading_dialog.rs b/src/widget/loading_dialog.rs index ae727c9..c1915f6 100644 --- a/src/widget/loading_dialog.rs +++ b/src/widget/loading_dialog.rs @@ -7,7 +7,7 @@ use ratatui::{ widgets::{Block, BorderType, Padding, Paragraph, Widget}, }; -use crate::{color::ColorTheme, widget::Dialog}; +use crate::{color::Theme, widget::Dialog}; #[derive(Debug, Default)] struct LoadingDialogColor { @@ -17,7 +17,7 @@ struct LoadingDialogColor { } impl LoadingDialogColor { - fn new(theme: &ColorTheme) -> Self { + fn new(theme: &Theme) -> Self { LoadingDialogColor { bg: theme.bg, block: theme.fg, @@ -32,7 +32,7 @@ pub struct LoadingDialog { } impl LoadingDialog { - pub fn theme(mut self, theme: &ColorTheme) -> Self { + pub fn theme(mut self, theme: &Theme) -> Self { self.color = LoadingDialogColor::new(theme); self } diff --git a/src/widget/scroll_lines.rs b/src/widget/scroll_lines.rs index 53fbcac..39fc222 100644 --- a/src/widget/scroll_lines.rs +++ b/src/widget/scroll_lines.rs @@ -6,7 +6,7 @@ use ratatui::{ widgets::{Block, BlockExt, Borders, Padding, Paragraph, StatefulWidget, Widget, Wrap}, }; -use crate::{color::ColorTheme, util::digits}; +use crate::{color::Theme, util::digits}; #[derive(Debug, Default)] enum ScrollEvent { @@ -118,7 +118,7 @@ struct ScrollLinesColor { } impl ScrollLinesColor { - fn new(theme: &ColorTheme) -> Self { + fn new(theme: &Theme) -> Self { Self { block: theme.fg, line_number: theme.preview_line_number, @@ -139,7 +139,7 @@ impl ScrollLines { self } - pub fn theme(mut self, theme: &ColorTheme) -> Self { + pub fn theme(mut self, theme: &Theme) -> Self { self.color = ScrollLinesColor::new(theme); self } @@ -739,7 +739,7 @@ mod tests { } fn render_scroll_lines(state: &mut ScrollLinesState) -> Buffer { - let theme = ColorTheme::default(); + let theme = Theme::default(); let scroll_lines = ScrollLines::default() .block(Block::bordered().title("TITLE")) .theme(&theme); @@ -749,7 +749,7 @@ mod tests { } fn render_scroll_lines_no_block(state: &mut ScrollLinesState) -> Buffer { - let theme = ColorTheme::default(); + let theme = Theme::default(); let scroll_lines = ScrollLines::default().theme(&theme); let mut buf = Buffer::empty(Rect::new(0, 0, 20, 5 + 2)); scroll_lines.render(buf.area, &mut buf, state); diff --git a/src/widget/scroll_list.rs b/src/widget/scroll_list.rs index 363160c..65f8d7a 100644 --- a/src/widget/scroll_list.rs +++ b/src/widget/scroll_list.rs @@ -5,7 +5,7 @@ use ratatui::{ widgets::{Block, List, ListItem, Padding, StatefulWidget, Widget}, }; -use crate::{color::ColorTheme, util::digits}; +use crate::{color::Theme, util::digits}; use crate::widget::ScrollBar; @@ -119,7 +119,7 @@ struct ScrollListColor { } impl ScrollListColor { - fn new(theme: &ColorTheme) -> ScrollListColor { + fn new(theme: &Theme) -> ScrollListColor { ScrollListColor { block: theme.fg, bar: theme.fg, @@ -141,7 +141,7 @@ impl ScrollList<'_> { } } - pub fn theme(mut self, theme: &ColorTheme) -> Self { + pub fn theme(mut self, theme: &Theme) -> Self { self.color = ScrollListColor::new(theme); self } @@ -190,7 +190,7 @@ mod tests { #[test] fn test_render_scroll_list_without_scroll() { - let theme = ColorTheme::default(); + let theme = Theme::default(); let mut state = ScrollListState::new(5); let items: Vec = (1..=5) .map(|i| ListItem::new(vec![Line::from(format!("Item {i}"))])) @@ -297,7 +297,7 @@ mod tests { .skip(state.offset) .take(show_item_count as usize) .collect(); - let theme = ColorTheme::default(); + let theme = Theme::default(); let scroll_list = ScrollList::new(items).theme(&theme); let mut buf = Buffer::empty(Rect::new(0, 0, 20, show_item_count + 2)); scroll_list.render(buf.area, &mut buf, state); diff --git a/src/widget/sort_list_dialog.rs b/src/widget/sort_list_dialog.rs index c3c2cf1..3fb2707 100644 --- a/src/widget/sort_list_dialog.rs +++ b/src/widget/sort_list_dialog.rs @@ -8,7 +8,7 @@ use ratatui::{ widgets::{Block, BorderType, List, ListItem, Padding, Widget}, }; -use crate::{color::ColorTheme, config, widget::Dialog}; +use crate::{color::Theme, config, widget::Dialog}; #[zero_indexed_enum] pub enum BucketListSortType { @@ -87,7 +87,7 @@ impl BucketListSortDialog { } } - pub fn theme(mut self, theme: &ColorTheme) -> Self { + pub fn theme(mut self, theme: &Theme) -> Self { self.color = ListSortDialogColor::new(theme); self } @@ -189,7 +189,7 @@ impl ObjectListSortDialog { } } - pub fn theme(mut self, theme: &ColorTheme) -> Self { + pub fn theme(mut self, theme: &Theme) -> Self { self.color = ListSortDialogColor::new(theme); self } @@ -211,7 +211,7 @@ struct ListSortDialogColor { } impl ListSortDialogColor { - fn new(theme: &ColorTheme) -> ListSortDialogColor { + fn new(theme: &Theme) -> ListSortDialogColor { ListSortDialogColor { bg: theme.bg, block: theme.fg, diff --git a/src/widget/status.rs b/src/widget/status.rs index 3ac4c7b..c6a0501 100644 --- a/src/widget/status.rs +++ b/src/widget/status.rs @@ -7,7 +7,7 @@ use ratatui::{ }; use crate::{ - color::ColorTheme, + color::Theme, help::{prune_spans_to_fit_width, SpansWithPriority}, }; @@ -30,7 +30,7 @@ struct StatusColor { } impl StatusColor { - fn new(theme: &ColorTheme) -> Self { + fn new(theme: &Theme) -> Self { StatusColor { help: theme.status_help, info: theme.status_info, @@ -55,7 +55,7 @@ impl Status { } } - pub fn theme(mut self, theme: &ColorTheme) -> Self { + pub fn theme(mut self, theme: &Theme) -> Self { self.color = StatusColor::new(theme); self } diff --git a/src/widget/text_preview.rs b/src/widget/text_preview.rs index 2c3e05f..5ea7048 100644 --- a/src/widget/text_preview.rs +++ b/src/widget/text_preview.rs @@ -20,7 +20,7 @@ use syntect::{ }; use crate::{ - color::ColorTheme, + color::Theme, config::Config, environment::Environment, format::format_version, @@ -306,7 +306,7 @@ struct EncodingDialogColor { } impl EncodingDialogColor { - fn new(theme: &ColorTheme) -> Self { + fn new(theme: &Theme) -> Self { Self { bg: theme.bg, block: theme.fg, @@ -332,7 +332,7 @@ impl<'a> EncodingDialog<'a> { } } - pub fn theme(mut self, theme: &ColorTheme) -> Self { + pub fn theme(mut self, theme: &Theme) -> Self { self.color = EncodingDialogColor::new(theme); self } @@ -501,7 +501,7 @@ pub struct TextPreview<'a> { file_version_id: Option<&'a str>, env: &'a Environment, - theme: &'a ColorTheme, + theme: &'a Theme, } impl<'a> TextPreview<'a> { @@ -509,7 +509,7 @@ impl<'a> TextPreview<'a> { file_name: &'a str, file_version_id: Option<&'a str>, env: &'a Environment, - theme: &'a ColorTheme, + theme: &'a Theme, ) -> Self { Self { file_name,