From 026b8bb265dd2110f90b1bb7d623fb6c5ee63963 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 09:40:20 +0900 Subject: [PATCH 01/24] Define ColorTheme --- src/color.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/color.rs diff --git a/src/color.rs b/src/color.rs new file mode 100644 index 0000000..1f7263a --- /dev/null +++ b/src/color.rs @@ -0,0 +1,38 @@ +use ratatui::style::Color; + +#[derive(Debug, Clone)] +pub struct ColorTheme { + pub text: Color, + pub selected: Color, + pub selected_text: Color, + pub disabled: Color, + pub match_text: Color, + pub link: Color, + pub short_help: Color, + pub info_status: Color, + pub success_status: Color, + pub warn_status: Color, + pub error_status: Color, + pub line_number: Color, + pub divider: Color, +} + +impl Default for ColorTheme { + fn default() -> Self { + Self { + text: Color::Reset, + selected: Color::Cyan, + selected_text: Color::Black, + disabled: Color::DarkGray, + match_text: Color::Red, + link: Color::Blue, + short_help: Color::DarkGray, + info_status: Color::Blue, + success_status: Color::Green, + warn_status: Color::Yellow, + error_status: Color::Red, + line_number: Color::DarkGray, + divider: Color::DarkGray, + } + } +} From 6a18f013bf8821244fa16162854a170edb6b1631 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 09:41:19 +0900 Subject: [PATCH 02/24] Fix App to have ColorTheme --- src/app.rs | 5 ++++- src/main.rs | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/app.rs b/src/app.rs index 149a478..ebb5c86 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,6 +3,7 @@ use tokio::spawn; use crate::{ client::Client, + color::ColorTheme, config::Config, error::{AppError, Result}, event::{ @@ -57,17 +58,19 @@ pub struct App { app_objects: AppObjects, client: Option>, config: Config, + pub theme: ColorTheme, tx: Sender, } impl App { - pub fn new(config: Config, tx: Sender, width: usize, height: usize) -> App { + pub fn new(config: Config, theme: ColorTheme, tx: Sender, width: usize, height: usize) -> App { App { app_view_state: AppViewState::new(width, height), app_objects: AppObjects::default(), page_stack: PageStack::new(tx.clone()), client: None, config, + theme, tx, } } diff --git a/src/main.rs b/src/main.rs index e821b8b..78ac64f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod app; mod cache; mod client; +mod color; mod config; mod constant; mod error; @@ -24,6 +25,7 @@ use tracing_subscriber::fmt::time::ChronoLocal; use crate::app::App; use crate::client::Client; +use crate::color::ColorTheme; use crate::config::Config; /// STU - S3 Terminal UI @@ -55,11 +57,12 @@ struct Args { async fn main() -> anyhow::Result<()> { let args = Args::parse(); let config = Config::load()?; + let theme = ColorTheme::default(); initialize_debug_log(&args, &config)?; let mut terminal = ratatui::try_init()?; - let ret = run(&mut terminal, args, config).await; + let ret = run(&mut terminal, args, config, theme).await; ratatui::try_restore()?; @@ -70,12 +73,13 @@ async fn run( terminal: &mut Terminal, args: Args, config: Config, + theme: ColorTheme, ) -> anyhow::Result<()> { let (tx, rx) = event::new(); let (width, height) = get_frame_size(terminal); let default_region_fallback = config.default_region.clone(); - let mut app = App::new(config, tx.clone(), width, height); + let mut app = App::new(config, theme, tx.clone(), width, height); spawn(async move { let client = Client::new( From 58054e3c7c2241a2147a4ff9f7086e308098a782 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 09:51:03 +0900 Subject: [PATCH 03/24] Fix render --- src/ui/render.rs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/ui/render.rs b/src/ui/render.rs index 23275e6..6c69793 100644 --- a/src/ui/render.rs +++ b/src/ui/render.rs @@ -1,6 +1,6 @@ use ratatui::{ layout::{Alignment, Constraint, Layout, Rect}, - style::{Color, Modifier, Stylize}, + style::{Modifier, Stylize}, text::Line, widgets::{Block, BorderType, Padding, Paragraph}, Frame, @@ -8,18 +8,13 @@ use ratatui::{ use crate::{ app::{App, Notification}, + color::ColorTheme, pages::page::Page, ui::common::calc_centered_dialog_rect, util, widget::{Dialog, Header}, }; -const SHORT_HELP_COLOR: Color = Color::DarkGray; -const INFO_STATUS_COLOR: Color = Color::Blue; -const SUCCESS_STATUS_COLOR: Color = Color::Green; -const WARN_STATUS_COLOR: Color = Color::Yellow; -const ERROR_STATUS_COLOR: Color = Color::Red; - pub fn render(f: &mut Frame, app: &mut App) { let chunks = Layout::vertical([ Constraint::Length(header_height(app)), @@ -63,19 +58,19 @@ fn render_content(f: &mut Frame, area: Rect, app: &mut App) { fn render_footer(f: &mut Frame, area: Rect, app: &App) { match &app.app_view_state.notification { Notification::Info(msg) => { - let msg = build_info_status(msg); + let msg = build_info_status(msg, &app.theme); f.render_widget(msg, area); } Notification::Success(msg) => { - let msg = build_success_status(msg); + let msg = build_success_status(msg, &app.theme); f.render_widget(msg, area); } Notification::Warn(msg) => { - let msg = build_warn_status(msg); + let msg = build_warn_status(msg, &app.theme); f.render_widget(msg, area); } Notification::Error(msg) => { - let msg = build_error_status(msg); + let msg = build_error_status(msg, &app.theme); f.render_widget(msg, area); } Notification::None => { @@ -125,7 +120,7 @@ fn build_short_help(app: &App, width: u16) -> Paragraph { let pad = Padding::horizontal(2); let max_width = (width - pad.left - pad.right) as usize; let help = build_short_help_string(&helps, max_width); - Paragraph::new(help.fg(SHORT_HELP_COLOR)).block(Block::default().padding(pad)) + Paragraph::new(help.fg(app.theme.short_help)).block(Block::default().padding(pad)) } fn build_short_help_string(helps: &[(String, usize)], max_width: usize) -> String { @@ -134,24 +129,24 @@ fn build_short_help_string(helps: &[(String, usize)], max_width: usize) -> Strin ss.join(delimiter) } -fn build_info_status(msg: &str) -> Paragraph { - Paragraph::new(msg.fg(INFO_STATUS_COLOR)) +fn build_info_status<'a>(msg: &'a str, theme: &'a ColorTheme) -> Paragraph<'a> { + Paragraph::new(msg.fg(theme.info_status)) .block(Block::default().padding(Padding::horizontal(2))) } -fn build_success_status(msg: &str) -> Paragraph { - Paragraph::new(msg.add_modifier(Modifier::BOLD).fg(SUCCESS_STATUS_COLOR)) +fn build_success_status<'a>(msg: &'a str, theme: &'a ColorTheme) -> Paragraph<'a> { + Paragraph::new(msg.add_modifier(Modifier::BOLD).fg(theme.success_status)) .block(Block::default().padding(Padding::horizontal(2))) } -fn build_warn_status(msg: &str) -> Paragraph { - Paragraph::new(msg.add_modifier(Modifier::BOLD).fg(WARN_STATUS_COLOR)) +fn build_warn_status<'a>(msg: &'a str, theme: &'a ColorTheme) -> Paragraph<'a> { + Paragraph::new(msg.add_modifier(Modifier::BOLD).fg(theme.warn_status)) .block(Block::default().padding(Padding::horizontal(2))) } -fn build_error_status(err: &str) -> Paragraph { +fn build_error_status<'a>(err: &'a str, theme: &'a ColorTheme) -> Paragraph<'a> { let err = format!("ERROR: {}", err); - Paragraph::new(err.add_modifier(Modifier::BOLD).fg(ERROR_STATUS_COLOR)) + Paragraph::new(err.add_modifier(Modifier::BOLD).fg(theme.error_status)) .block(Block::default().padding(Padding::horizontal(2))) } From bc61d69612afcbce65b52ea39319836d7771d995 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 09:51:35 +0900 Subject: [PATCH 04/24] Fix BucketListPage --- src/app.rs | 7 ++++-- src/pages/bucket_list.rs | 52 ++++++++++++++++++++++++---------------- src/pages/page.rs | 5 ++-- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/app.rs b/src/app.rs index ebb5c86..8b91fc7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -98,8 +98,11 @@ impl App { Ok(CompleteInitializeResult { buckets }) => { self.app_objects.set_bucket_items(buckets); - let bucket_list_page = - Page::of_bucket_list(self.app_objects.get_bucket_items(), self.tx.clone()); + let bucket_list_page = Page::of_bucket_list( + self.app_objects.get_bucket_items(), + self.theme.clone(), + self.tx.clone(), + ); self.page_stack.pop(); // remove initializing page self.page_stack.push(bucket_list_page); } diff --git a/src/pages/bucket_list.rs b/src/pages/bucket_list.rs index e1ef771..5029123 100644 --- a/src/pages/bucket_list.rs +++ b/src/pages/bucket_list.rs @@ -3,13 +3,14 @@ use std::cmp::Ordering; use ratatui::{ crossterm::event::{KeyCode, KeyEvent}, layout::Rect, - style::{Color, Style, Stylize}, + style::{Style, Stylize}, text::Line, widgets::ListItem, Frame, }; use crate::{ + color::ColorTheme, event::{AppEventType, Sender}, key_code, key_code_char, object::{BucketItem, ObjectKey}, @@ -21,10 +22,6 @@ use crate::{ }, }; -const SELECTED_COLOR: Color = Color::Cyan; -const SELECTED_ITEM_TEXT_COLOR: Color = Color::Black; -const HIGHLIGHTED_ITEM_TEXT_COLOR: Color = Color::Red; - #[derive(Debug)] pub struct BucketListPage { bucket_items: Vec, @@ -35,6 +32,8 @@ pub struct BucketListPage { list_state: ScrollListState, filter_input_state: InputDialogState, sort_dialog_state: BucketListSortDialogState, + + theme: ColorTheme, tx: Sender, } @@ -47,7 +46,7 @@ enum ViewState { } impl BucketListPage { - pub fn new(bucket_items: Vec, tx: Sender) -> Self { + pub fn new(bucket_items: Vec, theme: ColorTheme, tx: Sender) -> Self { let items_len = bucket_items.len(); let view_indices = (0..items_len).collect(); Self { @@ -57,6 +56,7 @@ impl BucketListPage { list_state: ScrollListState::new(items_len), filter_input_state: InputDialogState::default(), sort_dialog_state: BucketListSortDialogState::default(), + theme, tx, } } @@ -175,6 +175,7 @@ impl BucketListPage { &self.bucket_items, &self.view_indices, self.filter_input_state.input(), + &self.theme, offset, selected, area, @@ -457,6 +458,7 @@ fn build_list_items<'a>( current_items: &'a [BucketItem], view_indices: &'a [usize], filter: &'a str, + theme: &'a ColorTheme, offset: usize, selected: usize, area: Rect, @@ -470,12 +472,17 @@ fn build_list_items<'a>( .enumerate() .map(|(idx, item)| { let selected = idx + offset == selected; - build_list_item(&item.name, selected, filter) + build_list_item(&item.name, selected, filter, theme) }) .collect() } -fn build_list_item<'a>(name: &'a str, selected: bool, filter: &'a str) -> ListItem<'a> { +fn build_list_item<'a>( + name: &'a str, + selected: bool, + filter: &'a str, + theme: &'a ColorTheme, +) -> ListItem<'a> { let line = if filter.is_empty() { Line::from(vec![" ".into(), name.into(), " ".into()]) } else { @@ -483,16 +490,14 @@ fn build_list_item<'a>(name: &'a str, selected: bool, filter: &'a str) -> ListIt Line::from(vec![ " ".into(), before.into(), - highlighted.fg(HIGHLIGHTED_ITEM_TEXT_COLOR), + highlighted.fg(theme.match_text), after.into(), " ".into(), ]) }; let style = if selected { - Style::default() - .bg(SELECTED_COLOR) - .fg(SELECTED_ITEM_TEXT_COLOR) + Style::default().bg(theme.selected).fg(theme.selected_text) } else { Style::default() }; @@ -504,10 +509,11 @@ mod tests { use crate::{event, set_cells}; use super::*; - use ratatui::{backend::TestBackend, buffer::Buffer, Terminal}; + use ratatui::{backend::TestBackend, buffer::Buffer, style::Color, Terminal}; #[test] fn test_render_without_scroll() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -516,7 +522,7 @@ mod tests { .into_iter() .map(bucket_item) .collect(); - let mut page = BucketListPage::new(items, tx); + let mut page = BucketListPage::new(items, theme, tx); let area = Rect::new(0, 0, 30, 10); page.render(f, area); })?; @@ -545,6 +551,7 @@ mod tests { #[test] fn test_render_with_scroll() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -552,7 +559,7 @@ mod tests { let items = (0..16) .map(|i| bucket_item(&format!("bucket{}", i + 1))) .collect(); - let mut page = BucketListPage::new(items, tx); + let mut page = BucketListPage::new(items, theme, tx); let area = Rect::new(0, 0, 30, 10); page.render(f, area); })?; @@ -582,6 +589,7 @@ mod tests { #[test] fn test_render_filter_items() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -589,7 +597,7 @@ mod tests { .into_iter() .map(bucket_item) .collect(); - let mut page = BucketListPage::new(items, tx); + let mut page = BucketListPage::new(items, theme, tx); let area = Rect::new(0, 0, 30, 10); page.handle_key(KeyEvent::from(KeyCode::Char('/'))); @@ -658,6 +666,7 @@ mod tests { #[test] fn test_render_sort_items() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -665,7 +674,7 @@ mod tests { .into_iter() .map(bucket_item) .collect(); - let mut page = BucketListPage::new(items, tx); + let mut page = BucketListPage::new(items, theme, tx); let area = Rect::new(0, 0, 30, 10); page.handle_key(KeyEvent::from(KeyCode::Char('o'))); @@ -703,13 +712,14 @@ mod tests { #[test] fn test_filter_items() { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let items = ["foo", "bar", "baz", "qux", "foobar"] .into_iter() .map(bucket_item) .collect(); - let mut page = BucketListPage::new(items, tx); + let mut page = BucketListPage::new(items, theme, tx); page.handle_key(KeyEvent::from(KeyCode::Char('/'))); page.handle_key(KeyEvent::from(KeyCode::Char('b'))); @@ -737,13 +747,14 @@ mod tests { #[test] fn test_sort_items() { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let items = ["foo", "bar", "baz", "qux", "foobar"] .into_iter() .map(bucket_item) .collect(); - let mut page = BucketListPage::new(items, tx); + let mut page = BucketListPage::new(items, theme, tx); page.handle_key(KeyEvent::from(KeyCode::Char('o'))); @@ -768,13 +779,14 @@ mod tests { #[test] fn test_filter_and_sort_items() { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let items = ["foo", "bar", "baz", "qux", "foobar"] .into_iter() .map(bucket_item) .collect(); - let mut page = BucketListPage::new(items, tx); + let mut page = BucketListPage::new(items, theme, tx); page.handle_key(KeyEvent::from(KeyCode::Char('/'))); page.handle_key(KeyEvent::from(KeyCode::Char('b'))); diff --git a/src/pages/page.rs b/src/pages/page.rs index 3dbc3ac..d565ee0 100644 --- a/src/pages/page.rs +++ b/src/pages/page.rs @@ -1,4 +1,5 @@ use crate::{ + color::ColorTheme, config::{PreviewConfig, UiConfig}, event::Sender, object::{BucketItem, FileDetail, ObjectItem, ObjectKey, RawObject}, @@ -25,8 +26,8 @@ impl Page { Self::Initializing(Box::new(InitializingPage::new(tx))) } - pub fn of_bucket_list(bucket_items: Vec, tx: Sender) -> Self { - Self::BucketList(Box::new(BucketListPage::new(bucket_items, tx))) + pub fn of_bucket_list(bucket_items: Vec, theme: ColorTheme, tx: Sender) -> Self { + Self::BucketList(Box::new(BucketListPage::new(bucket_items, theme, tx))) } pub fn of_object_list( From edf5959e274302de577846afed1ef89ea75cff0d Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 09:57:50 +0900 Subject: [PATCH 05/24] Fix ObjectListPage --- src/app.rs | 3 +++ src/pages/object_list.rs | 58 +++++++++++++++++++++++++++------------- src/pages/page.rs | 2 ++ 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/app.rs b/src/app.rs index 8b91fc7..9291f40 100644 --- a/src/app.rs +++ b/src/app.rs @@ -151,6 +151,7 @@ impl App { current_object_items, object_key, self.config.ui.clone(), + self.theme.clone(), self.tx.clone(), ); self.page_stack.push(object_list_page); @@ -200,6 +201,7 @@ impl App { current_object_items, object_key, self.config.ui.clone(), + self.theme.clone(), self.tx.clone(), ); self.page_stack.push(new_object_list_page); @@ -267,6 +269,7 @@ impl App { items, current_object_key, self.config.ui.clone(), + self.theme.clone(), self.tx.clone(), ); self.page_stack.push(object_list_page); diff --git a/src/pages/object_list.rs b/src/pages/object_list.rs index 5a787e1..95d3a2e 100644 --- a/src/pages/object_list.rs +++ b/src/pages/object_list.rs @@ -4,13 +4,14 @@ use chrono::{DateTime, Local}; use ratatui::{ crossterm::event::{KeyCode, KeyEvent}, layout::Rect, - style::{Color, Style, Stylize}, + style::{Style, Stylize}, text::Line, widgets::ListItem, Frame, }; use crate::{ + color::ColorTheme, config::UiConfig, event::{AppEventType, Sender}, key_code, key_code_char, @@ -25,10 +26,6 @@ use crate::{ }, }; -const SELECTED_COLOR: Color = Color::Cyan; -const SELECTED_ITEM_TEXT_COLOR: Color = Color::Black; -const HIGHLIGHTED_ITEM_TEXT_COLOR: Color = Color::Red; - #[derive(Debug)] pub struct ObjectListPage { object_items: Vec, @@ -42,6 +39,7 @@ pub struct ObjectListPage { sort_dialog_state: ObjectListSortDialogState, ui_config: UiConfig, + theme: ColorTheme, tx: Sender, } @@ -58,6 +56,7 @@ impl ObjectListPage { object_items: Vec, object_key: ObjectKey, ui_config: UiConfig, + theme: ColorTheme, tx: Sender, ) -> Self { let items_len = object_items.len(); @@ -71,6 +70,7 @@ impl ObjectListPage { filter_input_state: InputDialogState::default(), sort_dialog_state: ObjectListSortDialogState::default(), ui_config, + theme, tx, } } @@ -199,6 +199,7 @@ impl ObjectListPage { selected, area, &self.ui_config, + &self.theme, ); let list = ScrollList::new(list_items); @@ -514,6 +515,7 @@ impl ObjectListPage { } } +#[allow(clippy::too_many_arguments)] fn build_list_items<'a>( current_items: &'a [ObjectItem], view_indices: &'a [usize], @@ -522,6 +524,7 @@ fn build_list_items<'a>( selected: usize, area: Rect, ui_config: &UiConfig, + theme: &ColorTheme, ) -> Vec> { let show_item_count = (area.height as usize) - 2 /* border */; view_indices @@ -530,7 +533,16 @@ fn build_list_items<'a>( .skip(offset) .take(show_item_count) .enumerate() - .map(|(idx, item)| build_list_item(item, idx + offset == selected, filter, area, ui_config)) + .map(|(idx, item)| { + build_list_item( + item, + idx + offset == selected, + filter, + area, + ui_config, + theme, + ) + }) .collect() } @@ -540,9 +552,10 @@ fn build_list_item<'a>( filter: &'a str, area: Rect, ui_config: &UiConfig, + theme: &ColorTheme, ) -> ListItem<'a> { let line = match item { - ObjectItem::Dir { name, .. } => build_object_dir_line(name, filter), + ObjectItem::Dir { name, .. } => build_object_dir_line(name, filter, theme), ObjectItem::File { name, size_byte, @@ -555,20 +568,19 @@ fn build_list_item<'a>( filter, area.width, ui_config, + theme, ), }; let style = if selected { - Style::default() - .bg(SELECTED_COLOR) - .fg(SELECTED_ITEM_TEXT_COLOR) + Style::default().bg(theme.selected).fg(theme.selected_text) } else { Style::default() }; ListItem::new(line).style(style) } -fn build_object_dir_line<'a>(name: &'a str, filter: &'a str) -> Line<'a> { +fn build_object_dir_line<'a>(name: &'a str, filter: &'a str, theme: &ColorTheme) -> Line<'a> { if filter.is_empty() { Line::from(vec![" ".into(), name.bold(), "/".bold(), " ".into()]) } else { @@ -576,7 +588,7 @@ fn build_object_dir_line<'a>(name: &'a str, filter: &'a str) -> Line<'a> { Line::from(vec![ " ".into(), before.bold(), - highlighted.fg(HIGHLIGHTED_ITEM_TEXT_COLOR).bold(), + highlighted.fg(theme.match_text).bold(), after.bold(), "/".bold(), " ".into(), @@ -591,6 +603,7 @@ fn build_object_file_line<'a>( filter: &'a str, width: u16, ui_config: &UiConfig, + theme: &ColorTheme, ) -> Line<'a> { let size = format_size_byte(size_byte); let date = format_datetime(last_modified, &ui_config.object_list.date_format); @@ -617,7 +630,7 @@ fn build_object_file_line<'a>( Line::from(vec![ " ".into(), before.into(), - highlighted.fg(HIGHLIGHTED_ITEM_TEXT_COLOR), + highlighted.fg(theme.match_text), after.into(), " ".into(), date.into(), @@ -634,10 +647,16 @@ mod tests { use super::*; use chrono::NaiveDateTime; - use ratatui::{backend::TestBackend, buffer::Buffer, style::Modifier, Terminal}; + use ratatui::{ + backend::TestBackend, + buffer::Buffer, + style::{Color, Modifier}, + Terminal, + }; #[test] fn test_render_without_scroll() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -653,7 +672,7 @@ mod tests { object_path: vec!["path".to_string(), "to".to_string()], }; let ui_config = UiConfig::default(); - let mut page = ObjectListPage::new(items, object_key, ui_config, tx); + let mut page = ObjectListPage::new(items, object_key, ui_config, theme, tx); let area = Rect::new(0, 0, 60, 10); page.render(f, area); })?; @@ -685,6 +704,7 @@ mod tests { #[test] fn test_render_with_scroll() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -697,7 +717,7 @@ mod tests { object_path: vec!["path".to_string(), "to".to_string()], }; let ui_config = UiConfig::default(); - let mut page = ObjectListPage::new(items, object_key, ui_config, tx); + let mut page = ObjectListPage::new(items, object_key, ui_config, theme, tx); let area = Rect::new(0, 0, 60, 10); page.render(f, area); })?; @@ -727,6 +747,7 @@ mod tests { #[test] fn test_render_with_config() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -744,7 +765,7 @@ mod tests { let mut ui_config = UiConfig::default(); ui_config.object_list.date_format = "%Y/%m/%d".to_string(); ui_config.object_list.date_width = 10; - let mut page = ObjectListPage::new(items, object_key, ui_config, tx); + let mut page = ObjectListPage::new(items, object_key, ui_config, theme, tx); let area = Rect::new(0, 0, 60, 10); page.render(f, area); })?; @@ -776,6 +797,7 @@ mod tests { #[test] fn test_sort_items() { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let items = vec![ object_dir_item("rid"), @@ -789,7 +811,7 @@ mod tests { object_path: vec!["path".to_string(), "to".to_string()], }; let ui_config = UiConfig::default(); - let mut page = ObjectListPage::new(items, object_key, ui_config, tx); + let mut page = ObjectListPage::new(items, object_key, ui_config, theme, tx); page.handle_key(KeyEvent::from(KeyCode::Char('o'))); page.handle_key(KeyEvent::from(KeyCode::Char('j'))); // select NameAsc diff --git a/src/pages/page.rs b/src/pages/page.rs index d565ee0..127a653 100644 --- a/src/pages/page.rs +++ b/src/pages/page.rs @@ -34,12 +34,14 @@ impl Page { object_items: Vec, object_key: ObjectKey, ui_config: UiConfig, + theme: ColorTheme, tx: Sender, ) -> Self { Self::ObjectList(Box::new(ObjectListPage::new( object_items, object_key, ui_config, + theme, tx, ))) } From f258d6cb493be1418bc497efe60cdaabdc228730 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 10:07:36 +0900 Subject: [PATCH 06/24] Fix ObjectDetailPage --- src/app.rs | 2 + src/pages/object_detail.rs | 75 +++++++++++++++++++++++++------------- src/pages/page.rs | 2 + src/widget/bar.rs | 14 +------ 4 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/app.rs b/src/app.rs index 9291f40..d5c98a4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -185,6 +185,7 @@ impl App { current_object_key, object_list_page.list_state(), self.config.ui.clone(), + self.theme.clone(), self.tx.clone(), ); self.page_stack.push(object_detail_page); @@ -338,6 +339,7 @@ impl App { map_key, object_page.list_state(), self.config.ui.clone(), + self.theme.clone(), self.tx.clone(), ); self.page_stack.push(object_detail_page); diff --git a/src/pages/object_detail.rs b/src/pages/object_detail.rs index b6ac372..02d698f 100644 --- a/src/pages/object_detail.rs +++ b/src/pages/object_detail.rs @@ -9,6 +9,7 @@ use ratatui::{ }; use crate::{ + color::ColorTheme, config::UiConfig, event::{AppEventType, Sender}, key_code, key_code_char, @@ -21,10 +22,6 @@ use crate::{ }, }; -const SELECTED_COLOR: Color = Color::Cyan; -const SELECTED_ITEM_TEXT_COLOR: Color = Color::Black; -const SELECTED_DISABLED_COLOR: Color = Color::DarkGray; - #[derive(Debug)] pub struct ObjectDetailPage { file_detail: FileDetail, @@ -38,6 +35,7 @@ pub struct ObjectDetailPage { list_state: ScrollListState, ui_config: UiConfig, + theme: ColorTheme, tx: Sender, } @@ -70,6 +68,7 @@ impl ObjectDetailPage { object_key: ObjectKey, list_state: ScrollListState, ui_config: UiConfig, + theme: ColorTheme, tx: Sender, ) -> Self { let detail_tab_state = DetailTabState::new(&file_detail, &ui_config); @@ -82,6 +81,7 @@ impl ObjectDetailPage { object_items, list_state, ui_config, + theme, tx, } } @@ -187,8 +187,13 @@ impl ObjectDetailPage { let offset = self.list_state.offset; let selected = self.list_state.selected; - let list_items = - build_list_items_from_object_items(&self.object_items, offset, selected, chunks[0]); + let list_items = build_list_items_from_object_items( + &self.object_items, + offset, + selected, + chunks[0], + &self.theme, + ); let list = ScrollList::new(list_items); f.render_stateful_widget(list, chunks[0], &mut self.list_state); @@ -200,7 +205,7 @@ impl ObjectDetailPage { .margin(1) .split(chunks[1]); - let tabs = build_tabs(&self.tab); + let tabs = build_tabs(&self.tab, &self.theme); f.render_widget(tabs, chunks[0]); match self.tab { @@ -209,7 +214,7 @@ impl ObjectDetailPage { f.render_stateful_widget(detail, chunks[1], state); } Tab::Version(ref mut state) => { - let version = VersionTab::default(); + let version = VersionTab::new(self.theme.selected); f.render_stateful_widget(version, chunks[1], state); } } @@ -402,29 +407,33 @@ impl ObjectDetailPage { } } -fn build_list_items_from_object_items( - current_items: &[ObjectItem], +fn build_list_items_from_object_items<'a>( + current_items: &'a [ObjectItem], offset: usize, selected: usize, area: Rect, -) -> Vec { + theme: &ColorTheme, +) -> Vec> { let show_item_count = (area.height as usize) - 2 /* border */; current_items .iter() .skip(offset) .take(show_item_count) .enumerate() - .map(|(idx, item)| build_list_item_from_object_item(idx, item, offset, selected, area)) + .map(|(idx, item)| { + build_list_item_from_object_item(idx, item, offset, selected, area, theme) + }) .collect() } -fn build_list_item_from_object_item( +fn build_list_item_from_object_item<'a>( idx: usize, - item: &ObjectItem, + item: &'a ObjectItem, offset: usize, selected: usize, area: Rect, -) -> ListItem { + theme: &ColorTheme, +) -> ListItem<'a> { let content = match item { ObjectItem::Dir { name, .. } => { let content = format_dir_item(name, area.width); @@ -438,11 +447,7 @@ fn build_list_item_from_object_item( } }; if idx + offset == selected { - ListItem::new(content).style( - Style::default() - .bg(SELECTED_DISABLED_COLOR) - .fg(SELECTED_ITEM_TEXT_COLOR), - ) + ListItem::new(content).style(Style::default().bg(theme.disabled).fg(theme.selected_text)) } else { ListItem::new(content) } @@ -459,14 +464,14 @@ fn format_file_item(name: &str, width: u16) -> String { format!(" {: Tabs<'static> { +fn build_tabs(tab: &Tab, theme: &ColorTheme) -> Tabs<'static> { let tabs = vec!["Detail", "Version"]; Tabs::new(tabs) .select(tab.val()) .highlight_style( Style::default() .add_modifier(Modifier::BOLD) - .fg(SELECTED_COLOR), + .fg(theme.selected), ) .block(Block::default().borders(Borders::BOTTOM)) } @@ -631,8 +636,16 @@ impl VersionTabState { } } -#[derive(Debug, Default)] -struct VersionTab {} +#[derive(Debug)] +struct VersionTab { + selected_color: Color, +} + +impl VersionTab { + fn new(selected_color: Color) -> Self { + Self { selected_color } + } +} impl StatefulWidget for VersionTab { type State = VersionTabState; @@ -674,7 +687,7 @@ impl StatefulWidget for VersionTab { .padding(Padding::left(1)), ); if i == state.selected { - let bar = Bar::default().color(SELECTED_COLOR); + let bar = Bar::new(self.selected_color); bar.render(chunks[0], buf); } version_paragraph.render(chunks[1], buf); @@ -706,6 +719,7 @@ mod tests { #[test] fn test_render_detail_tab() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -719,6 +733,7 @@ mod tests { object_key, ScrollListState::new(items_len), ui_config, + theme, tx, ); let area = Rect::new(0, 0, 60, 20); @@ -772,6 +787,7 @@ mod tests { #[test] fn test_render_detail_tab_with_config() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -786,6 +802,7 @@ mod tests { object_key, ScrollListState::new(items_len), ui_config, + theme, tx, ); let area = Rect::new(0, 0, 60, 20); @@ -839,6 +856,7 @@ mod tests { #[test] fn test_render_version_tab() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -852,6 +870,7 @@ mod tests { object_key, ScrollListState::new(items_len), ui_config, + theme, tx, ); page.set_versions(file_versions); @@ -907,6 +926,7 @@ mod tests { #[test] fn test_render_version_tab_with_config() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -921,6 +941,7 @@ mod tests { object_key, ScrollListState::new(items_len), ui_config, + theme, tx, ); page.set_versions(file_versions); @@ -976,6 +997,7 @@ mod tests { #[test] fn test_render_save_dialog_detail_tab() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -989,6 +1011,7 @@ mod tests { object_key, ScrollListState::new(items_len), ui_config, + theme, tx, ); page.open_save_dialog(); @@ -1041,6 +1064,7 @@ mod tests { #[test] fn test_render_copy_detail_dialog_detail_tab() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -1054,6 +1078,7 @@ mod tests { object_key, ScrollListState::new(items_len), ui_config, + theme, tx, ); page.open_copy_detail_dialog(); diff --git a/src/pages/page.rs b/src/pages/page.rs index 127a653..26f7621 100644 --- a/src/pages/page.rs +++ b/src/pages/page.rs @@ -52,6 +52,7 @@ impl Page { object_key: ObjectKey, list_state: ScrollListState, ui_config: UiConfig, + theme: ColorTheme, tx: Sender, ) -> Self { Self::ObjectDetail(Box::new(ObjectDetailPage::new( @@ -60,6 +61,7 @@ impl Page { object_key, list_state, ui_config, + theme, tx, ))) } diff --git a/src/widget/bar.rs b/src/widget/bar.rs index 2245a33..338b28e 100644 --- a/src/widget/bar.rs +++ b/src/widget/bar.rs @@ -11,19 +11,9 @@ pub struct Bar { color: Color, } -impl Default for Bar { - fn default() -> Self { - Self { - char: "┃", - color: Color::Reset, - } - } -} - impl Bar { - pub fn color(mut self, color: Color) -> Self { - self.color = color; - self + pub fn new(color: Color) -> Self { + Self { char: "┃", color } } } From 0f49f71a732fac0d05fec40968a93c61061c3be7 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 10:10:51 +0900 Subject: [PATCH 07/24] Fix HelpPage --- src/app.rs | 2 +- src/pages/help.rs | 32 ++++++++++++++++++++++++-------- src/pages/page.rs | 4 ++-- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/app.rs b/src/app.rs index d5c98a4..89e9625 100644 --- a/src/app.rs +++ b/src/app.rs @@ -414,7 +414,7 @@ impl App { Page::ObjectDetail(page) => page.helps(), Page::ObjectPreview(page) => page.helps(), }; - let help_page = Page::of_help(helps, self.tx.clone()); + let help_page = Page::of_help(helps, self.theme.clone(), self.tx.clone()); self.page_stack.push(help_page); } diff --git a/src/pages/help.rs b/src/pages/help.rs index dd75bfc..4aec52b 100644 --- a/src/pages/help.rs +++ b/src/pages/help.rs @@ -9,6 +9,7 @@ use ratatui::{ }; use crate::{ + color::ColorTheme, constant::{APP_DESCRIPTION, APP_HOMEPAGE, APP_NAME, APP_VERSION}, event::{AppEventType, Sender}, key_code, key_code_char, @@ -17,18 +18,17 @@ use crate::{ widget::Divider, }; -const LINK_TEXT_COLOR: Color = Color::Blue; - #[derive(Debug)] pub struct HelpPage { helps: Vec, + theme: ColorTheme, tx: Sender, } impl HelpPage { - pub fn new(helps: Vec, tx: Sender) -> Self { - Self { helps, tx } + pub fn new(helps: Vec, theme: ColorTheme, tx: Sender) -> Self { + Self { helps, theme, tx } } pub fn handle_key(&mut self, key: KeyEvent) { @@ -57,7 +57,13 @@ impl HelpPage { ]) .split(content_area); - let about = About::new(APP_NAME, APP_DESCRIPTION, APP_VERSION, APP_HOMEPAGE); + let about = About::new( + APP_NAME, + APP_DESCRIPTION, + APP_VERSION, + APP_HOMEPAGE, + self.theme.link, + ); let divider = Divider::default(); let help = Help::new(&self.helps); @@ -79,15 +85,24 @@ struct About<'a> { description: &'a str, version: &'a str, homepage: &'a str, + + link_color: Color, } impl<'a> About<'a> { - fn new(name: &'a str, description: &'a str, version: &'a str, homepage: &'a str) -> Self { + fn new( + name: &'a str, + description: &'a str, + version: &'a str, + homepage: &'a str, + link_color: Color, + ) -> Self { Self { name, description, version, homepage, + link_color, } } } @@ -97,7 +112,7 @@ impl Widget for About<'_> { let lines = vec![ Line::from(format!("{} - {}", self.name, self.description)), Line::from(format!("Version: {}", self.version)), - Line::from(self.homepage.fg(LINK_TEXT_COLOR)), + Line::from(self.homepage.fg(self.link_color)), ]; let content = with_empty_lines(lines); let paragraph = Paragraph::new(content).block( @@ -167,6 +182,7 @@ mod tests { #[test] fn test_render() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -180,7 +196,7 @@ mod tests { .iter() .map(|s| s.to_string()) .collect(); - let mut page = HelpPage::new(helps, tx); + let mut page = HelpPage::new(helps, theme, tx); let area = Rect::new(0, 0, 70, 20); page.render(f, area); })?; diff --git a/src/pages/page.rs b/src/pages/page.rs index 26f7621..318437c 100644 --- a/src/pages/page.rs +++ b/src/pages/page.rs @@ -86,8 +86,8 @@ impl Page { ))) } - pub fn of_help(helps: Vec, tx: Sender) -> Self { - Self::Help(Box::new(HelpPage::new(helps, tx))) + pub fn of_help(helps: Vec, theme: ColorTheme, tx: Sender) -> Self { + Self::Help(Box::new(HelpPage::new(helps, theme, tx))) } pub fn as_bucket_list(&self) -> &BucketListPage { From a88133e6dbd529aea828dd6edaa2a4ad5f4319a8 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 10:13:12 +0900 Subject: [PATCH 08/24] Fix Divider --- src/pages/help.rs | 2 +- src/pages/object_detail.rs | 12 ++++++++---- src/widget/divider.rs | 11 +++-------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/pages/help.rs b/src/pages/help.rs index 4aec52b..acbb7d0 100644 --- a/src/pages/help.rs +++ b/src/pages/help.rs @@ -64,7 +64,7 @@ impl HelpPage { APP_HOMEPAGE, self.theme.link, ); - let divider = Divider::default(); + let divider = Divider::new(self.theme.divider); let help = Help::new(&self.helps); f.render_widget(block, area); diff --git a/src/pages/object_detail.rs b/src/pages/object_detail.rs index 02d698f..f1e3e4f 100644 --- a/src/pages/object_detail.rs +++ b/src/pages/object_detail.rs @@ -214,7 +214,7 @@ impl ObjectDetailPage { f.render_stateful_widget(detail, chunks[1], state); } Tab::Version(ref mut state) => { - let version = VersionTab::new(self.theme.selected); + let version = VersionTab::new(self.theme.selected, self.theme.divider); f.render_stateful_widget(version, chunks[1], state); } } @@ -639,11 +639,15 @@ impl VersionTabState { #[derive(Debug)] struct VersionTab { selected_color: Color, + divider_color: Color, } impl VersionTab { - fn new(selected_color: Color) -> Self { - Self { selected_color } + fn new(selected_color: Color, divider_color: Color) -> Self { + Self { + selected_color, + divider_color, + } } } @@ -675,7 +679,7 @@ impl StatefulWidget for VersionTab { .split(area); area = chunks[2]; - let divider = Divider::default(); + let divider = Divider::new(self.divider_color); divider.render(chunks[1], buf); let chunks = diff --git a/src/widget/divider.rs b/src/widget/divider.rs index e11e210..854a68b 100644 --- a/src/widget/divider.rs +++ b/src/widget/divider.rs @@ -6,20 +6,15 @@ use ratatui::{ widgets::Widget, }; -const DIVIDER_COLOR: Color = Color::DarkGray; - #[derive(Debug)] pub struct Divider { char: &'static str, color: Color, } -impl Default for Divider { - fn default() -> Self { - Self { - char: "─", - color: DIVIDER_COLOR, - } +impl Divider { + pub fn new(color: Color) -> Self { + Self { char: "─", color } } } From 59439bed2ea4824ed7d008b0cc17711a7ffa737b Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 15:59:55 +0900 Subject: [PATCH 09/24] Fix ScrollLines --- src/app.rs | 1 + src/pages/object_preview.rs | 12 ++++++++++++ src/pages/page.rs | 3 +++ src/widget/scroll_lines.rs | 25 ++++++++++++++++++------- src/widget/text_preview.rs | 11 ++++++++++- 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/app.rs b/src/app.rs index 89e9625..0450602 100644 --- a/src/app.rs +++ b/src/app.rs @@ -535,6 +535,7 @@ impl App { path, current_object_key, self.config.preview.clone(), + self.theme.clone(), self.tx.clone(), ); self.page_stack.push(object_preview_page); diff --git a/src/pages/object_preview.rs b/src/pages/object_preview.rs index 53aef6d..93a44d7 100644 --- a/src/pages/object_preview.rs +++ b/src/pages/object_preview.rs @@ -5,6 +5,7 @@ use ratatui::{ }; use crate::{ + color::ColorTheme, config::PreviewConfig, event::{AppEventType, Sender}, key_code, key_code_char, @@ -28,6 +29,7 @@ pub struct ObjectPreviewPage { view_state: ViewState, + theme: ColorTheme, tx: Sender, } @@ -45,6 +47,7 @@ enum ViewState { } impl ObjectPreviewPage { + #[allow(clippy::too_many_arguments)] pub fn new( file_detail: FileDetail, file_version_id: Option, @@ -52,6 +55,7 @@ impl ObjectPreviewPage { path: String, object_key: ObjectKey, preview_config: PreviewConfig, + theme: ColorTheme, tx: Sender, ) -> Self { let preview_type = if infer::is_image(&object.bytes) { @@ -77,6 +81,7 @@ impl ObjectPreviewPage { path, object_key, view_state: ViewState::Default, + theme, tx, } } @@ -176,6 +181,7 @@ impl ObjectPreviewPage { let preview = TextPreview::new( self.file_detail.name.as_str(), self.file_version_id.as_deref(), + self.theme.line_number, ); f.render_stateful_widget(preview, area, state); } @@ -322,6 +328,7 @@ mod tests { #[test] fn test_render_without_scroll() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -347,6 +354,7 @@ mod tests { file_path, object_key, preview_config, + theme, tx, ); let area = Rect::new(0, 0, 30, 10); @@ -377,6 +385,7 @@ mod tests { #[test] fn test_render_with_scroll() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -397,6 +406,7 @@ mod tests { file_path, object_key, preview_config, + theme, tx, ); let area = Rect::new(0, 0, 30, 10); @@ -427,6 +437,7 @@ mod tests { #[test] fn test_render_save_dialog_without_scroll() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; @@ -452,6 +463,7 @@ mod tests { file_path, object_key, preview_config, + theme, tx, ); page.open_save_dialog(); diff --git a/src/pages/page.rs b/src/pages/page.rs index 318437c..5a01da4 100644 --- a/src/pages/page.rs +++ b/src/pages/page.rs @@ -66,6 +66,7 @@ impl Page { ))) } + #[allow(clippy::too_many_arguments)] pub fn of_object_preview( file_detail: FileDetail, file_version_id: Option, @@ -73,6 +74,7 @@ impl Page { path: String, object_key: ObjectKey, preview_config: PreviewConfig, + theme: ColorTheme, tx: Sender, ) -> Self { Self::ObjectPreview(Box::new(ObjectPreviewPage::new( @@ -82,6 +84,7 @@ impl Page { path, object_key, preview_config, + theme, tx, ))) } diff --git a/src/widget/scroll_lines.rs b/src/widget/scroll_lines.rs index 65b09b9..bb965be 100644 --- a/src/widget/scroll_lines.rs +++ b/src/widget/scroll_lines.rs @@ -8,8 +8,6 @@ use ratatui::{ use crate::util::digits; -const PREVIEW_LINE_NUMBER_COLOR: Color = Color::DarkGray; - #[derive(Debug, Default)] enum ScrollEvent { #[default] @@ -113,6 +111,7 @@ impl ScrollLinesState { #[derive(Debug, Default)] pub struct ScrollLines { block: Option>, + line_number_color: Color, } impl ScrollLines { @@ -120,6 +119,11 @@ impl ScrollLines { self.block = Some(block); self } + + pub fn line_number_color(mut self, color: Color) -> Self { + self.line_number_color = color; + self + } } impl StatefulWidget for ScrollLines { @@ -144,8 +148,12 @@ impl StatefulWidget for ScrollLines { // handle scroll events and update the state handle_scroll_events(state, text_area_width, show_lines_count); - let line_numbers_paragraph = - build_line_numbers_paragraph(state, text_area_width, show_lines_count); + let line_numbers_paragraph = build_line_numbers_paragraph( + state, + text_area_width, + show_lines_count, + self.line_number_color, + ); let lines_paragraph = build_lines_paragraph(state, show_lines_count); self.block.render(area, buf); @@ -158,6 +166,7 @@ fn build_line_numbers_paragraph( state: &ScrollLinesState, text_area_width: usize, show_lines_count: usize, + line_number_color: Color, ) -> Paragraph { // may not be correct because the wrap of the text is calculated separately... let line_heights = wrapped_line_width_iter( @@ -175,7 +184,7 @@ fn build_line_numbers_paragraph( vec![Line::raw("")] } else { let line_number = format!("{:>width$}", line, width = state.max_digits); - let number_line: Line = line_number.fg(PREVIEW_LINE_NUMBER_COLOR).into(); + let number_line: Line = line_number.fg(line_number_color).into(); let empty_lines = (0..(line_height - 1)).map(|_| Line::raw("")); std::iter::once(number_line).chain(empty_lines).collect() } @@ -706,14 +715,16 @@ mod tests { } fn render_scroll_lines(state: &mut ScrollLinesState) -> Buffer { - let scroll_lines = ScrollLines::default().block(Block::bordered().title("TITLE")); + let scroll_lines = ScrollLines::default() + .block(Block::bordered().title("TITLE")) + .line_number_color(Color::DarkGray); let mut buf = Buffer::empty(Rect::new(0, 0, 20, 5 + 2)); scroll_lines.render(buf.area, &mut buf, state); buf } fn render_scroll_lines_no_block(state: &mut ScrollLinesState) -> Buffer { - let scroll_lines = ScrollLines::default(); + let scroll_lines = ScrollLines::default().line_number_color(Color::DarkGray); let mut buf = Buffer::empty(Rect::new(0, 0, 20, 5 + 2)); scroll_lines.render(buf.area, &mut buf, state); buf diff --git a/src/widget/text_preview.rs b/src/widget/text_preview.rs index 83aa2ba..e3f6d69 100644 --- a/src/widget/text_preview.rs +++ b/src/widget/text_preview.rs @@ -3,6 +3,7 @@ use once_cell::sync::Lazy; use ratatui::{ buffer::Buffer, layout::Rect, + style::Color, text::Line, widgets::{Block, StatefulWidget}, }; @@ -103,13 +104,20 @@ fn build_highlighted_lines( pub struct TextPreview<'a> { file_name: &'a str, file_version_id: Option<&'a str>, + + line_number_color: Color, } impl<'a> TextPreview<'a> { - pub fn new(file_name: &'a str, file_version_id: Option<&'a str>) -> Self { + pub fn new( + file_name: &'a str, + file_version_id: Option<&'a str>, + line_number_color: Color, + ) -> Self { Self { file_name, file_version_id, + line_number_color, } } } @@ -129,6 +137,7 @@ impl StatefulWidget for TextPreview<'_> { }; ScrollLines::default() .block(Block::bordered().title(title)) + .line_number_color(self.line_number_color) .render(area, buf, &mut state.scroll_lines_state); } } From c529be8754c7d2f373e2e06096abffcc2f781f42 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 18:17:35 +0900 Subject: [PATCH 10/24] Fix CopyDetailDialog --- src/pages/object_detail.rs | 4 +-- src/widget/bar.rs | 14 ++++++-- src/widget/copy_detail_dialog.rs | 62 ++++++++++++++++++++++++-------- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/pages/object_detail.rs b/src/pages/object_detail.rs index f1e3e4f..2b238cb 100644 --- a/src/pages/object_detail.rs +++ b/src/pages/object_detail.rs @@ -228,7 +228,7 @@ impl ObjectDetailPage { } if let ViewState::CopyDetailDialog(state) = &mut self.view_state { - let copy_detail_dialog = CopyDetailDialog::default(); + let copy_detail_dialog = CopyDetailDialog::default().theme(&self.theme); f.render_stateful_widget(copy_detail_dialog, area, state); } } @@ -691,7 +691,7 @@ impl StatefulWidget for VersionTab { .padding(Padding::left(1)), ); if i == state.selected { - let bar = Bar::new(self.selected_color); + let bar = Bar::default().color(self.selected_color); bar.render(chunks[0], buf); } version_paragraph.render(chunks[1], buf); diff --git a/src/widget/bar.rs b/src/widget/bar.rs index 338b28e..1b762e2 100644 --- a/src/widget/bar.rs +++ b/src/widget/bar.rs @@ -11,9 +11,19 @@ pub struct Bar { color: Color, } +impl Default for Bar { + fn default() -> Self { + Self { + char: "┃", + color: Color::default(), + } + } +} + impl Bar { - pub fn new(color: Color) -> Self { - Self { char: "┃", color } + pub fn color(mut self, color: Color) -> Self { + self.color = color; + self } } diff --git a/src/widget/copy_detail_dialog.rs b/src/widget/copy_detail_dialog.rs index fee91b5..be23ed5 100644 --- a/src/widget/copy_detail_dialog.rs +++ b/src/widget/copy_detail_dialog.rs @@ -10,13 +10,12 @@ use ratatui::{ }; use crate::{ + color::ColorTheme, object::{BucketItem, FileDetail, ObjectItem}, ui::common::calc_centered_dialog_rect, widget::Dialog, }; -const SELECTED_COLOR: Color = Color::Cyan; - #[derive(Default)] #[zero_indexed_enum] enum BucketListItemType { @@ -218,7 +217,33 @@ impl CopyDetailDialogState { } #[derive(Debug, Default)] -pub struct CopyDetailDialog {} +struct CopyDetailDialogColor { + block: Color, + text: Color, + selected: Color, +} + +impl CopyDetailDialogColor { + fn new(theme: &ColorTheme) -> Self { + Self { + block: theme.text, + text: theme.text, + selected: theme.selected, + } + } +} + +#[derive(Debug, Default)] +pub struct CopyDetailDialog { + color: CopyDetailDialogColor, +} + +impl CopyDetailDialog { + pub fn theme(mut self, theme: &ColorTheme) -> Self { + self.color = CopyDetailDialogColor::new(theme); + self + } +} impl StatefulWidget for CopyDetailDialog { type State = CopyDetailDialogState; @@ -229,7 +254,7 @@ impl StatefulWidget for CopyDetailDialog { .name_and_value_vec() .into_iter() .enumerate() - .map(|(i, (name, value))| build_list_item(i, selected, (name, value))) + .map(|(i, (name, value))| self.build_list_item(i, selected, (name, value))) .collect(); let dialog_width = (area.width - 4).min(80); @@ -241,6 +266,7 @@ impl StatefulWidget for CopyDetailDialog { Block::bordered() .border_type(BorderType::Rounded) .title(title) + .fg(self.color.block) .padding(Padding::horizontal(1)), ); let dialog = Dialog::new(Box::new(list)); @@ -248,15 +274,22 @@ impl StatefulWidget for CopyDetailDialog { } } -fn build_list_item<'a>(i: usize, selected: usize, (name, value): (String, String)) -> ListItem<'a> { - let item = ListItem::new(vec![ - Line::from(format!("{}:", name).add_modifier(Modifier::BOLD)), - Line::from(format!(" {}", value)), - ]); - if i == selected { - item.fg(SELECTED_COLOR) - } else { - item +impl CopyDetailDialog { + fn build_list_item<'a>( + &self, + i: usize, + selected: usize, + (name, value): (String, String), + ) -> ListItem<'a> { + let item = ListItem::new(vec![ + Line::from(format!("{}:", name).add_modifier(Modifier::BOLD)), + Line::from(format!(" {}", value)), + ]); + if i == selected { + item.fg(self.color.selected) + } else { + item.fg(self.color.text) + } } } @@ -271,8 +304,9 @@ mod tests { #[test] fn test_render_copy_detail_dialog() { let file_detail = file_detail(); + let theme = ColorTheme::default(); let mut state = CopyDetailDialogState::object_detail(file_detail); - let copy_detail_dialog = CopyDetailDialog::default(); + let copy_detail_dialog = CopyDetailDialog::default().theme(&theme); let mut buf = Buffer::empty(Rect::new(0, 0, 40, 20)); copy_detail_dialog.render(buf.area, &mut buf, &mut state); From 70ad2ed57aaa9da6b2b5d0ae1ecb97351465230b Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 18:19:40 +0900 Subject: [PATCH 11/24] Fix Divider --- src/pages/help.rs | 2 +- src/pages/object_detail.rs | 2 +- src/widget/divider.rs | 14 ++++++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/pages/help.rs b/src/pages/help.rs index acbb7d0..965c95b 100644 --- a/src/pages/help.rs +++ b/src/pages/help.rs @@ -64,7 +64,7 @@ impl HelpPage { APP_HOMEPAGE, self.theme.link, ); - let divider = Divider::new(self.theme.divider); + let divider = Divider::default().color(self.theme.divider); let help = Help::new(&self.helps); f.render_widget(block, area); diff --git a/src/pages/object_detail.rs b/src/pages/object_detail.rs index 2b238cb..cd7bfd8 100644 --- a/src/pages/object_detail.rs +++ b/src/pages/object_detail.rs @@ -679,7 +679,7 @@ impl StatefulWidget for VersionTab { .split(area); area = chunks[2]; - let divider = Divider::new(self.divider_color); + let divider = Divider::default().color(self.divider_color); divider.render(chunks[1], buf); let chunks = diff --git a/src/widget/divider.rs b/src/widget/divider.rs index 854a68b..c3fd7d5 100644 --- a/src/widget/divider.rs +++ b/src/widget/divider.rs @@ -12,9 +12,19 @@ pub struct Divider { color: Color, } +impl Default for Divider { + fn default() -> Self { + Self { + char: "─", + color: Color::default(), + } + } +} + impl Divider { - pub fn new(color: Color) -> Self { - Self { char: "─", color } + pub fn color(mut self, color: Color) -> Self { + self.color = color; + self } } From c95a5d3e7f98a501e7efb4a3c4deb32ea4412ba3 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 18:28:19 +0900 Subject: [PATCH 12/24] Fix Header --- src/ui/render.rs | 2 +- src/widget/header.rs | 51 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/ui/render.rs b/src/ui/render.rs index 6c69793..dc992cd 100644 --- a/src/ui/render.rs +++ b/src/ui/render.rs @@ -105,7 +105,7 @@ fn build_header(app: &App) -> Header { _ => unreachable!(), }) .collect(); - Header::new(breadcrumb) + Header::new(breadcrumb).theme(&app.theme) } fn build_short_help(app: &App, width: u16) -> Paragraph { diff --git a/src/widget/header.rs b/src/widget/header.rs index bb03f50..cd66987 100644 --- a/src/widget/header.rs +++ b/src/widget/header.rs @@ -1,18 +1,44 @@ use ratatui::{ buffer::Buffer, layout::{Margin, Rect}, + style::{Color, Stylize}, widgets::{Block, Padding, Paragraph, Widget}, }; -use crate::{constant::APP_NAME, util::prune_strings_to_fit_width}; +use crate::{color::ColorTheme, constant::APP_NAME, util::prune_strings_to_fit_width}; +#[derive(Debug, Default)] +struct HeaderColor { + block: Color, + text: Color, +} + +impl HeaderColor { + fn new(theme: &ColorTheme) -> HeaderColor { + HeaderColor { + block: theme.text, + text: theme.text, + } + } +} + +#[derive(Debug, Default)] pub struct Header { breadcrumb: Vec, + color: HeaderColor, } impl Header { pub fn new(breadcrumb: Vec) -> Header { - Header { breadcrumb } + Header { + breadcrumb, + ..Default::default() + } + } + + pub fn theme(mut self, theme: &ColorTheme) -> Self { + self.color = HeaderColor::new(theme); + self } } @@ -31,10 +57,16 @@ impl Header { let pad = Padding::horizontal(1); let max_width = (inner_area.width - pad.left - pad.right) as usize; - let current_key_str = self.build_current_key_str(max_width); + let block_color = self.color.block; + let text_color = self.color.text; + let current_key_str = self.build_current_key_str(max_width).fg(text_color); - let paragraph = - Paragraph::new(current_key_str).block(Block::bordered().title(APP_NAME).padding(pad)); + let paragraph = Paragraph::new(current_key_str).block( + Block::bordered() + .title(APP_NAME) + .fg(block_color) + .padding(pad), + ); paragraph.render(area, buf); } @@ -73,11 +105,12 @@ mod tests { #[test] fn test_render_header() { + let theme = ColorTheme::default(); let breadcrumb = ["bucket", "key01", "key02", "key03"] .into_iter() .map(|s| s.to_string()) .collect(); - let header = Header::new(breadcrumb); + let header = Header::new(breadcrumb).theme(&theme); let mut buf = Buffer::empty(Rect::new(0, 0, 30 + 4, 3)); header.render(buf.area, &mut buf); @@ -92,11 +125,12 @@ mod tests { #[test] fn test_render_header_with_ellipsis() { + let theme = ColorTheme::default(); let breadcrumb = ["bucket", "key01", "key02a", "key03"] .into_iter() .map(|s| s.to_string()) .collect(); - let header = Header::new(breadcrumb); + let header = Header::new(breadcrumb).theme(&theme); let mut buf = Buffer::empty(Rect::new(0, 0, 30 + 4, 3)); header.render(buf.area, &mut buf); @@ -111,7 +145,8 @@ mod tests { #[test] fn test_render_header_empty() { - let header = Header::new(vec![]); + let theme = ColorTheme::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); From 339dc56f4023af9a92f298814c99d1df2cf7961e Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Fri, 27 Sep 2024 18:33:22 +0900 Subject: [PATCH 13/24] Fix InputDialog --- src/pages/bucket_list.rs | 5 ++++- src/pages/object_detail.rs | 5 ++++- src/pages/object_list.rs | 5 ++++- src/pages/object_preview.rs | 5 ++++- src/widget/input_dialog.rs | 36 ++++++++++++++++++++++++++++++++---- 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/pages/bucket_list.rs b/src/pages/bucket_list.rs index 5029123..e1847cb 100644 --- a/src/pages/bucket_list.rs +++ b/src/pages/bucket_list.rs @@ -185,7 +185,10 @@ impl BucketListPage { 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); + let filter_dialog = InputDialog::default() + .title("Filter") + .max_width(30) + .theme(&self.theme); f.render_stateful_widget(filter_dialog, area, &mut self.filter_input_state); let (cursor_x, cursor_y) = self.filter_input_state.cursor(); diff --git a/src/pages/object_detail.rs b/src/pages/object_detail.rs index cd7bfd8..9734f19 100644 --- a/src/pages/object_detail.rs +++ b/src/pages/object_detail.rs @@ -220,7 +220,10 @@ impl ObjectDetailPage { } if let ViewState::SaveDialog(state) = &mut self.view_state { - let save_dialog = InputDialog::default().title("Save As").max_width(40); + let save_dialog = InputDialog::default() + .title("Save As") + .max_width(40) + .theme(&self.theme); f.render_stateful_widget(save_dialog, area, state); let (cursor_x, cursor_y) = state.cursor(); diff --git a/src/pages/object_list.rs b/src/pages/object_list.rs index 95d3a2e..bdc4d87 100644 --- a/src/pages/object_list.rs +++ b/src/pages/object_list.rs @@ -206,7 +206,10 @@ impl ObjectListPage { 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); + let filter_dialog = InputDialog::default() + .title("Filter") + .max_width(30) + .theme(&self.theme); f.render_stateful_widget(filter_dialog, area, &mut self.filter_input_state); let (cursor_x, cursor_y) = self.filter_input_state.cursor(); diff --git a/src/pages/object_preview.rs b/src/pages/object_preview.rs index 93a44d7..11093ba 100644 --- a/src/pages/object_preview.rs +++ b/src/pages/object_preview.rs @@ -195,7 +195,10 @@ impl ObjectPreviewPage { } if let ViewState::SaveDialog(state) = &mut self.view_state { - let save_dialog = InputDialog::default().title("Save As").max_width(40); + let save_dialog = InputDialog::default() + .title("Save As") + .max_width(40) + .theme(&self.theme); f.render_stateful_widget(save_dialog, area, state); let (cursor_x, cursor_y) = state.cursor(); diff --git a/src/widget/input_dialog.rs b/src/widget/input_dialog.rs index 06ddbeb..1a1c5b6 100644 --- a/src/widget/input_dialog.rs +++ b/src/widget/input_dialog.rs @@ -2,11 +2,12 @@ use ratatui::{ buffer::Buffer, crossterm::event::KeyEvent, layout::Rect, + style::{Color, Stylize}, widgets::{block::Title, Block, BorderType, Padding, Paragraph, StatefulWidget, WidgetRef}, }; use tui_input::{backend::crossterm::EventHandler, Input}; -use crate::{ui::common::calc_centered_dialog_rect, widget::Dialog}; +use crate::{color::ColorTheme, ui::common::calc_centered_dialog_rect, widget::Dialog}; #[derive(Debug, Default)] pub struct InputDialogState { @@ -33,10 +34,26 @@ impl InputDialogState { } } +#[derive(Debug, Default)] +struct InputDialogColor { + block: Color, + text: Color, +} + +impl InputDialogColor { + fn new(theme: &ColorTheme) -> InputDialogColor { + InputDialogColor { + block: theme.text, + text: theme.text, + } + } +} + #[derive(Debug, Default)] pub struct InputDialog { title: &'static str, max_width: Option, + color: InputDialogColor, } impl InputDialog { @@ -49,6 +66,11 @@ impl InputDialog { self.max_width = Some(max_width); self } + + pub fn theme(mut self, theme: &ColorTheme) -> Self { + self.color = InputDialogColor::new(theme); + self + } } impl StatefulWidget for InputDialog { @@ -68,10 +90,11 @@ impl StatefulWidget for InputDialog { let input_view: &str = &state.input.value()[input_start_index..]; let title = Title::from(self.title); - let dialog_content = Paragraph::new(input_view).block( + let dialog_content = Paragraph::new(input_view.fg(self.color.text)).block( Block::bordered() .border_type(BorderType::Rounded) .title(title) + .fg(self.color.block) .padding(Padding::horizontal(1)), ); let dialog = Dialog::new(Box::new(dialog_content)); @@ -92,8 +115,9 @@ mod tests { #[test] fn test_render_input_dialog() { + let theme = ColorTheme::default(); let mut state = InputDialogState::default(); - let save_dialog = InputDialog::default(); + let save_dialog = InputDialog::default().theme(&theme); for c in "abc".chars() { state.handle_key_event(KeyEvent::from(KeyCode::Char(c))); @@ -122,8 +146,12 @@ mod tests { #[test] fn test_render_input_dialog_with_params() { + let theme = ColorTheme::default(); let mut state = InputDialogState::default(); - let save_dialog = InputDialog::default().title("xyz").max_width(20); + let save_dialog = InputDialog::default() + .title("xyz") + .max_width(20) + .theme(&theme); for c in "abc".chars() { state.handle_key_event(KeyEvent::from(KeyCode::Char(c))); From 396c3259a31f96424f1529b2602e9614bb688a60 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 28 Sep 2024 07:37:04 +0900 Subject: [PATCH 14/24] Fix Scroll --- src/pages/bucket_list.rs | 2 +- src/pages/object_detail.rs | 2 +- src/pages/object_list.rs | 2 +- src/widget/scroll.rs | 11 +++++++++-- src/widget/scroll_list.rs | 40 ++++++++++++++++++++++++++++++++------ 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/pages/bucket_list.rs b/src/pages/bucket_list.rs index e1847cb..6d6808d 100644 --- a/src/pages/bucket_list.rs +++ b/src/pages/bucket_list.rs @@ -181,7 +181,7 @@ impl BucketListPage { area, ); - let list = ScrollList::new(list_items); + let list = ScrollList::new(list_items).theme(&self.theme); f.render_stateful_widget(list, area, &mut self.list_state); if let ViewState::FilterDialog = self.view_state { diff --git a/src/pages/object_detail.rs b/src/pages/object_detail.rs index 9734f19..0e859f9 100644 --- a/src/pages/object_detail.rs +++ b/src/pages/object_detail.rs @@ -195,7 +195,7 @@ impl ObjectDetailPage { &self.theme, ); - let list = ScrollList::new(list_items); + let list = ScrollList::new(list_items).theme(&self.theme); f.render_stateful_widget(list, chunks[0], &mut self.list_state); let block = Block::bordered(); diff --git a/src/pages/object_list.rs b/src/pages/object_list.rs index bdc4d87..2cd2b30 100644 --- a/src/pages/object_list.rs +++ b/src/pages/object_list.rs @@ -202,7 +202,7 @@ impl ObjectListPage { &self.theme, ); - let list = ScrollList::new(list_items); + let list = ScrollList::new(list_items).theme(&self.theme); f.render_stateful_widget(list, area, &mut self.list_state); if let ViewState::FilterDialog = self.view_state { diff --git a/src/widget/scroll.rs b/src/widget/scroll.rs index c3d0142..6869cce 100644 --- a/src/widget/scroll.rs +++ b/src/widget/scroll.rs @@ -1,10 +1,11 @@ -use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; +use ratatui::{buffer::Buffer, layout::Rect, style::Color, widgets::Widget}; // implemented independently to calculate based on offset position pub struct ScrollBar { lines_len: usize, offset: usize, bar_char: char, + color: Color, } impl ScrollBar { @@ -13,8 +14,14 @@ impl ScrollBar { lines_len, offset, bar_char: '│', // use '┃' or '║' instead...? + color: Color::default(), } } + + pub fn color(mut self, color: Color) -> ScrollBar { + self.color = color; + self + } } impl Widget for ScrollBar { @@ -31,7 +38,7 @@ impl ScrollBar { let x = area.x; for h in 0..scrollbar_height { let y = scrollbar_top + h; - buf[(x, y)].set_char(self.bar_char); + buf[(x, y)].set_char(self.bar_char).set_fg(self.color); } } diff --git a/src/widget/scroll_list.rs b/src/widget/scroll_list.rs index a8010b0..e54315d 100644 --- a/src/widget/scroll_list.rs +++ b/src/widget/scroll_list.rs @@ -1,10 +1,11 @@ use ratatui::{ buffer::Buffer, layout::{Alignment, Margin, Rect}, + style::{Color, Stylize}, widgets::{Block, List, ListItem, Padding, StatefulWidget, Widget}, }; -use crate::util::digits; +use crate::{color::ColorTheme, util::digits}; use crate::widget::ScrollBar; @@ -111,14 +112,38 @@ impl ScrollListState { } } +#[derive(Debug, Default)] +struct ScrollListColor { + block: Color, + bar: Color, +} + +impl ScrollListColor { + fn new(theme: &ColorTheme) -> ScrollListColor { + ScrollListColor { + block: theme.text, + bar: theme.text, + } + } +} + #[derive(Debug)] pub struct ScrollList<'a> { items: Vec>, + color: ScrollListColor, } impl ScrollList<'_> { pub fn new(items: Vec) -> ScrollList { - ScrollList { items } + ScrollList { + items, + color: Default::default(), + } + } + + pub fn theme(mut self, theme: &ColorTheme) -> Self { + self.color = ScrollListColor::new(theme); + self } } @@ -133,7 +158,8 @@ impl StatefulWidget for ScrollList<'_> { Block::bordered() .title(title) .title_alignment(Alignment::Right) - .padding(Padding::horizontal(1)), + .padding(Padding::horizontal(1)) + .fg(self.color.block), ); Widget::render(list, area, buf); @@ -141,7 +167,7 @@ impl StatefulWidget for ScrollList<'_> { let scrollbar_area = Rect::new(area.right(), area.top(), 1, area.height); if state.total > (scrollbar_area.height as usize) { - let scroll_bar = ScrollBar::new(state.total, state.offset); + let scroll_bar = ScrollBar::new(state.total, state.offset).color(self.color.bar); Widget::render(scroll_bar, scrollbar_area, buf); } } @@ -164,11 +190,12 @@ mod tests { #[test] fn test_render_scroll_list_without_scroll() { + let theme = ColorTheme::default(); let mut state = ScrollListState::new(5); let items: Vec = (1..=5) .map(|i| ListItem::new(vec![Line::from(format!("Item {}", i))])) .collect(); - let scroll_list = ScrollList::new(items); + let scroll_list = ScrollList::new(items).theme(&theme); let mut buf = Buffer::empty(Rect::new(0, 0, 20, 12)); scroll_list.render(buf.area, &mut buf, &mut state); @@ -270,7 +297,8 @@ mod tests { .skip(state.offset) .take(show_item_count as usize) .collect(); - let scroll_list = ScrollList::new(items); + let theme = ColorTheme::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); buf From 9a6e37fc11a2f28ab2e6d891032825f1fccaac62 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 28 Sep 2024 07:57:47 +0900 Subject: [PATCH 15/24] Fix ScrollLines --- src/pages/object_detail.rs | 47 +++++++++++++++++++++++++++---------- src/pages/object_preview.rs | 2 +- src/widget/scroll_lines.rs | 44 +++++++++++++++++++++++++--------- src/widget/text_preview.rs | 10 ++++---- 4 files changed, 73 insertions(+), 30 deletions(-) diff --git a/src/pages/object_detail.rs b/src/pages/object_detail.rs index 0e859f9..d4f5846 100644 --- a/src/pages/object_detail.rs +++ b/src/pages/object_detail.rs @@ -210,11 +210,11 @@ impl ObjectDetailPage { match self.tab { Tab::Detail(ref mut state) => { - let detail = DetailTab::default(); + let detail = DetailTab::new(&self.theme); f.render_stateful_widget(detail, chunks[1], state); } Tab::Version(ref mut state) => { - let version = VersionTab::new(self.theme.selected, self.theme.divider); + let version = VersionTab::new(&self.theme); f.render_stateful_widget(version, chunks[1], state); } } @@ -522,14 +522,22 @@ impl DetailTabState { } } -#[derive(Debug, Default)] -struct DetailTab {} +#[derive(Debug)] +struct DetailTab<'a> { + theme: &'a ColorTheme, +} -impl StatefulWidget for DetailTab { +impl<'a> DetailTab<'a> { + fn new(theme: &'a ColorTheme) -> Self { + Self { theme } + } +} + +impl StatefulWidget for DetailTab<'_> { type State = DetailTabState; fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { - let scroll_lines = ScrollLines::default(); + let scroll_lines = ScrollLines::default().theme(self.theme); StatefulWidget::render(scroll_lines, area, buf, &mut state.scroll_lines_state); } } @@ -639,17 +647,30 @@ impl VersionTabState { } } +#[derive(Debug, Default)] +struct VersionTabColor { + selected: Color, + divider: Color, +} + +impl VersionTabColor { + fn new(theme: &ColorTheme) -> Self { + Self { + selected: theme.selected, + divider: theme.divider, + } + } +} + #[derive(Debug)] struct VersionTab { - selected_color: Color, - divider_color: Color, + color: VersionTabColor, } impl VersionTab { - fn new(selected_color: Color, divider_color: Color) -> Self { + fn new(theme: &ColorTheme) -> Self { Self { - selected_color, - divider_color, + color: VersionTabColor::new(theme), } } } @@ -682,7 +703,7 @@ impl StatefulWidget for VersionTab { .split(area); area = chunks[2]; - let divider = Divider::default().color(self.divider_color); + let divider = Divider::default().color(self.color.divider); divider.render(chunks[1], buf); let chunks = @@ -694,7 +715,7 @@ impl StatefulWidget for VersionTab { .padding(Padding::left(1)), ); if i == state.selected { - let bar = Bar::default().color(self.selected_color); + let bar = Bar::default().color(self.color.selected); bar.render(chunks[0], buf); } version_paragraph.render(chunks[1], buf); diff --git a/src/pages/object_preview.rs b/src/pages/object_preview.rs index 11093ba..78abd95 100644 --- a/src/pages/object_preview.rs +++ b/src/pages/object_preview.rs @@ -181,7 +181,7 @@ impl ObjectPreviewPage { let preview = TextPreview::new( self.file_detail.name.as_str(), self.file_version_id.as_deref(), - self.theme.line_number, + &self.theme, ); f.render_stateful_widget(preview, area, state); } diff --git a/src/widget/scroll_lines.rs b/src/widget/scroll_lines.rs index bb965be..a2c36a3 100644 --- a/src/widget/scroll_lines.rs +++ b/src/widget/scroll_lines.rs @@ -6,7 +6,7 @@ use ratatui::{ widgets::{block::BlockExt, Block, Borders, Padding, Paragraph, StatefulWidget, Widget, Wrap}, }; -use crate::util::digits; +use crate::{color::ColorTheme, util::digits}; #[derive(Debug, Default)] enum ScrollEvent { @@ -107,11 +107,26 @@ impl ScrollLinesState { } } +#[derive(Debug, Default)] +struct ScrollLinesColor { + block: Color, + line_number: Color, +} + +impl ScrollLinesColor { + fn new(theme: &ColorTheme) -> Self { + Self { + block: theme.text, + line_number: theme.line_number, + } + } +} + // fixme: bad implementation for highlighting and displaying the number of lines :( #[derive(Debug, Default)] pub struct ScrollLines { block: Option>, - line_number_color: Color, + color: ScrollLinesColor, } impl ScrollLines { @@ -120,8 +135,8 @@ impl ScrollLines { self } - pub fn line_number_color(mut self, color: Color) -> Self { - self.line_number_color = color; + pub fn theme(mut self, theme: &ColorTheme) -> Self { + self.color = ScrollLinesColor::new(theme); self } } @@ -152,11 +167,11 @@ impl StatefulWidget for ScrollLines { state, text_area_width, show_lines_count, - self.line_number_color, + self.color.line_number, ); - let lines_paragraph = build_lines_paragraph(state, show_lines_count); + let lines_paragraph = build_lines_paragraph(state, show_lines_count, self.color.block); - self.block.render(area, buf); + self.block.map(|b| b.fg(self.color.block)).render(area, buf); line_numbers_paragraph.render(chunks[0], buf); lines_paragraph.render(chunks[1], buf); } @@ -199,7 +214,11 @@ fn build_line_numbers_paragraph( ) } -fn build_lines_paragraph(state: &ScrollLinesState, show_lines_count: usize) -> Paragraph { +fn build_lines_paragraph( + state: &ScrollLinesState, + show_lines_count: usize, + block_color: Color, +) -> Paragraph { let lines_content: Vec = state .lines .iter() @@ -211,7 +230,8 @@ fn build_lines_paragraph(state: &ScrollLinesState, show_lines_count: usize) -> P let lines_paragraph = Paragraph::new(lines_content).block( Block::default() .borders(Borders::NONE) - .padding(Padding::horizontal(1)), + .padding(Padding::horizontal(1)) + .fg(block_color), ); if state.options.wrap { @@ -715,16 +735,18 @@ mod tests { } fn render_scroll_lines(state: &mut ScrollLinesState) -> Buffer { + let theme = ColorTheme::default(); let scroll_lines = ScrollLines::default() .block(Block::bordered().title("TITLE")) - .line_number_color(Color::DarkGray); + .theme(&theme); let mut buf = Buffer::empty(Rect::new(0, 0, 20, 5 + 2)); scroll_lines.render(buf.area, &mut buf, state); buf } fn render_scroll_lines_no_block(state: &mut ScrollLinesState) -> Buffer { - let scroll_lines = ScrollLines::default().line_number_color(Color::DarkGray); + let theme = ColorTheme::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); buf diff --git a/src/widget/text_preview.rs b/src/widget/text_preview.rs index e3f6d69..08b2a17 100644 --- a/src/widget/text_preview.rs +++ b/src/widget/text_preview.rs @@ -3,7 +3,6 @@ use once_cell::sync::Lazy; use ratatui::{ buffer::Buffer, layout::Rect, - style::Color, text::Line, widgets::{Block, StatefulWidget}, }; @@ -15,6 +14,7 @@ use syntect::{ }; use crate::{ + color::ColorTheme, object::{FileDetail, RawObject}, ui::common::format_version, util::extension_from_file_name, @@ -105,19 +105,19 @@ pub struct TextPreview<'a> { file_name: &'a str, file_version_id: Option<&'a str>, - line_number_color: Color, + theme: &'a ColorTheme, } impl<'a> TextPreview<'a> { pub fn new( file_name: &'a str, file_version_id: Option<&'a str>, - line_number_color: Color, + theme: &'a ColorTheme, ) -> Self { Self { file_name, file_version_id, - line_number_color, + theme, } } } @@ -137,7 +137,7 @@ impl StatefulWidget for TextPreview<'_> { }; ScrollLines::default() .block(Block::bordered().title(title)) - .line_number_color(self.line_number_color) + .theme(self.theme) .render(area, buf, &mut state.scroll_lines_state); } } From a07d7a26da9538ec5267ecc3ba12b3fd5640bede Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 28 Sep 2024 10:46:04 +0900 Subject: [PATCH 16/24] Fix InitializingPage --- src/app.rs | 2 +- src/pages/initializing.rs | 12 ++++++++---- src/pages/page.rs | 8 ++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/app.rs b/src/app.rs index 0450602..f8d903e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -67,7 +67,7 @@ impl App { App { app_view_state: AppViewState::new(width, height), app_objects: AppObjects::default(), - page_stack: PageStack::new(tx.clone()), + page_stack: PageStack::new(theme.clone(), tx.clone()), client: None, config, theme, diff --git a/src/pages/initializing.rs b/src/pages/initializing.rs index 5fe247c..ee55197 100644 --- a/src/pages/initializing.rs +++ b/src/pages/initializing.rs @@ -1,11 +1,13 @@ use ratatui::{ crossterm::event::{KeyCode, KeyEvent}, layout::Rect, + style::Stylize, widgets::Block, Frame, }; use crate::{ + color::ColorTheme, event::{AppEventType, Sender}, key_code, pages::util::build_short_helps, @@ -13,12 +15,13 @@ use crate::{ #[derive(Debug)] pub struct InitializingPage { + theme: ColorTheme, tx: Sender, } impl InitializingPage { - pub fn new(tx: Sender) -> Self { - Self { tx } + pub fn new(theme: ColorTheme, tx: Sender) -> Self { + Self { theme, tx } } pub fn handle_key(&mut self, key: KeyEvent) { @@ -28,7 +31,7 @@ impl InitializingPage { } pub fn render(&mut self, f: &mut Frame, area: Rect) { - let content = Block::bordered(); + let content = Block::bordered().fg(self.theme.text); f.render_widget(content, area); } @@ -47,11 +50,12 @@ mod tests { #[test] fn test_render() -> std::io::Result<()> { + let theme = ColorTheme::default(); let (tx, _) = event::new(); let mut terminal = setup_terminal()?; terminal.draw(|f| { - let mut page = InitializingPage::new(tx); + let mut page = InitializingPage::new(theme, tx); let area = Rect::new(0, 0, 30, 10); page.render(f, area); })?; diff --git a/src/pages/page.rs b/src/pages/page.rs index 5a01da4..8f66080 100644 --- a/src/pages/page.rs +++ b/src/pages/page.rs @@ -22,8 +22,8 @@ pub enum Page { } impl Page { - pub fn of_initializing(tx: Sender) -> Self { - Self::Initializing(Box::new(InitializingPage::new(tx))) + pub fn of_initializing(theme: ColorTheme, tx: Sender) -> Self { + Self::Initializing(Box::new(InitializingPage::new(theme, tx))) } pub fn of_bucket_list(bucket_items: Vec, theme: ColorTheme, tx: Sender) -> Self { @@ -142,9 +142,9 @@ pub struct PageStack { } impl PageStack { - pub fn new(tx: Sender) -> PageStack { + pub fn new(theme: ColorTheme, tx: Sender) -> PageStack { PageStack { - stack: vec![Page::of_initializing(tx)], + stack: vec![Page::of_initializing(theme, tx)], } } From ef92da59755b5e35a50c924598e3a0b083d4a151 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 28 Sep 2024 10:47:06 +0900 Subject: [PATCH 17/24] Fix HelpPage --- src/pages/help.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/help.rs b/src/pages/help.rs index 965c95b..bc7b317 100644 --- a/src/pages/help.rs +++ b/src/pages/help.rs @@ -46,7 +46,8 @@ impl HelpPage { pub fn render(&mut self, f: &mut Frame, area: Rect) { let block = Block::bordered() .padding(Padding::horizontal(1)) - .title(APP_NAME); + .title(APP_NAME) + .fg(self.theme.text); let content_area = block.inner(area); From 102547fbab8317d7c50f4c5383f862da28482164 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 28 Sep 2024 10:49:16 +0900 Subject: [PATCH 18/24] Fix CopyDetailDialog --- src/pages/bucket_list.rs | 2 +- src/pages/object_list.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/bucket_list.rs b/src/pages/bucket_list.rs index 6d6808d..b17e788 100644 --- a/src/pages/bucket_list.rs +++ b/src/pages/bucket_list.rs @@ -201,7 +201,7 @@ impl BucketListPage { } if let ViewState::CopyDetailDialog(state) = &mut self.view_state { - let copy_detail_dialog = CopyDetailDialog::default(); + let copy_detail_dialog = CopyDetailDialog::default().theme(&self.theme); f.render_stateful_widget(copy_detail_dialog, area, state); } } diff --git a/src/pages/object_list.rs b/src/pages/object_list.rs index 2cd2b30..8beb5b7 100644 --- a/src/pages/object_list.rs +++ b/src/pages/object_list.rs @@ -222,7 +222,7 @@ impl ObjectListPage { } if let ViewState::CopyDetailDialog(state) = &mut self.view_state { - let copy_detail_dialog = CopyDetailDialog::default(); + let copy_detail_dialog = CopyDetailDialog::default().theme(&self.theme); f.render_stateful_widget(copy_detail_dialog, area, state); } } From 4bcbdb93b4196bc157a9c14c55deec886806e4dd Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 28 Sep 2024 13:00:41 +0900 Subject: [PATCH 19/24] Fix SortListDialog --- src/pages/bucket_list.rs | 2 +- src/pages/object_list.rs | 2 +- src/widget/sort_list_dialog.rs | 65 +++++++++++++++++++++++++++------- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/pages/bucket_list.rs b/src/pages/bucket_list.rs index b17e788..f0ca1eb 100644 --- a/src/pages/bucket_list.rs +++ b/src/pages/bucket_list.rs @@ -196,7 +196,7 @@ impl BucketListPage { } if let ViewState::SortDialog = self.view_state { - let sort_dialog = BucketListSortDialog::new(self.sort_dialog_state); + let sort_dialog = BucketListSortDialog::new(self.sort_dialog_state).theme(&self.theme); f.render_widget(sort_dialog, area); } diff --git a/src/pages/object_list.rs b/src/pages/object_list.rs index 8beb5b7..a47c804 100644 --- a/src/pages/object_list.rs +++ b/src/pages/object_list.rs @@ -217,7 +217,7 @@ impl ObjectListPage { } if let ViewState::SortDialog = self.view_state { - let sort_dialog = ObjectListSortDialog::new(self.sort_dialog_state); + let sort_dialog = ObjectListSortDialog::new(self.sort_dialog_state).theme(&self.theme); f.render_widget(sort_dialog, area); } diff --git a/src/widget/sort_list_dialog.rs b/src/widget/sort_list_dialog.rs index 3754e40..bf7e06f 100644 --- a/src/widget/sort_list_dialog.rs +++ b/src/widget/sort_list_dialog.rs @@ -7,9 +7,7 @@ use ratatui::{ widgets::{block::Title, Block, BorderType, List, ListItem, Padding, Widget, WidgetRef}, }; -use crate::{ui::common::calc_centered_dialog_rect, widget::Dialog}; - -const SELECTED_COLOR: Color = Color::Cyan; +use crate::{color::ColorTheme, ui::common::calc_centered_dialog_rect, widget::Dialog}; #[derive(Default)] #[zero_indexed_enum] @@ -56,6 +54,7 @@ impl BucketListSortDialogState { pub struct BucketListSortDialog { state: BucketListSortDialogState, labels: Vec<&'static str>, + color: ListSortDialogColor, } impl BucketListSortDialog { @@ -64,13 +63,22 @@ impl BucketListSortDialog { .iter() .map(|sort_type| sort_type.str()) .collect(); - Self { state, labels } + Self { + state, + labels, + color: ListSortDialogColor::default(), + } + } + + pub fn theme(mut self, theme: &ColorTheme) -> Self { + self.color = ListSortDialogColor::new(theme); + self } } impl Widget for BucketListSortDialog { fn render(self, area: Rect, buf: &mut Buffer) { - let dialog = ListSortDialog::new(self.state.selected.val(), self.labels); + let dialog = ListSortDialog::new(self.state.selected.val(), self.labels, self.color); dialog.render(area, buf); } } @@ -128,6 +136,7 @@ impl ObjectListSortDialogState { pub struct ObjectListSortDialog { state: ObjectListSortDialogState, labels: Vec<&'static str>, + color: ListSortDialogColor, } impl ObjectListSortDialog { @@ -136,25 +145,56 @@ impl ObjectListSortDialog { .iter() .map(|sort_type| sort_type.str()) .collect(); - Self { state, labels } + Self { + state, + labels, + color: ListSortDialogColor::default(), + } + } + + pub fn theme(mut self, theme: &ColorTheme) -> Self { + self.color = ListSortDialogColor::new(theme); + self } } impl Widget for ObjectListSortDialog { fn render(self, area: Rect, buf: &mut Buffer) { - let dialog = ListSortDialog::new(self.state.selected.val(), self.labels); + let dialog = ListSortDialog::new(self.state.selected.val(), self.labels, self.color); dialog.render(area, buf); } } +#[derive(Debug, Default)] +struct ListSortDialogColor { + block: Color, + text: Color, + selected: Color, +} + +impl ListSortDialogColor { + fn new(theme: &ColorTheme) -> ListSortDialogColor { + ListSortDialogColor { + block: theme.text, + text: theme.text, + selected: theme.selected, + } + } +} + struct ListSortDialog { selected: usize, labels: Vec<&'static str>, + color: ListSortDialogColor, } impl ListSortDialog { - pub fn new(selected: usize, labels: Vec<&'static str>) -> Self { - Self { selected, labels } + fn new(selected: usize, labels: Vec<&'static str>, color: ListSortDialogColor) -> Self { + Self { + selected, + labels, + color, + } } } @@ -167,9 +207,9 @@ impl Widget for ListSortDialog { .map(|(i, label)| { let item = ListItem::new(Line::raw(*label)); if i == self.selected { - item.fg(SELECTED_COLOR) + item.fg(self.color.selected) } else { - item + item.fg(self.color.text) } }) .collect(); @@ -183,7 +223,8 @@ impl Widget for ListSortDialog { Block::bordered() .border_type(BorderType::Rounded) .title(title) - .padding(Padding::horizontal(1)), + .padding(Padding::horizontal(1)) + .fg(self.color.block), ); let dialog = Dialog::new(Box::new(list)); dialog.render_ref(area, buf); From df0ef1c6e4d1b197e3756c83a4d3d2101fa74bde Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 28 Sep 2024 13:03:26 +0900 Subject: [PATCH 20/24] Fix ObjectDetailPage --- src/pages/object_detail.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/object_detail.rs b/src/pages/object_detail.rs index d4f5846..1de4566 100644 --- a/src/pages/object_detail.rs +++ b/src/pages/object_detail.rs @@ -198,7 +198,7 @@ impl ObjectDetailPage { let list = ScrollList::new(list_items).theme(&self.theme); f.render_stateful_widget(list, chunks[0], &mut self.list_state); - let block = Block::bordered(); + let block = Block::bordered().fg(self.theme.text); f.render_widget(block, chunks[1]); let chunks = Layout::vertical([Constraint::Length(2), Constraint::Min(0)]) From ed36e7e30f3bc63aef963f08cfc6e84dc2cf9a78 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 28 Sep 2024 13:06:08 +0900 Subject: [PATCH 21/24] Fix loading dialog --- src/ui/render.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ui/render.rs b/src/ui/render.rs index dc992cd..0d2d14c 100644 --- a/src/ui/render.rs +++ b/src/ui/render.rs @@ -82,7 +82,7 @@ fn render_footer(f: &mut Frame, area: Rect, app: &App) { fn render_loading_dialog(f: &mut Frame, app: &App) { if app.app_view_state.is_loading { - let loading = build_loading_dialog("Loading..."); + let loading = build_loading_dialog("Loading...", &app.theme); let area = calc_centered_dialog_rect(f.area(), 30, 5); let dialog = Dialog::new(Box::new(loading)); f.render_widget_ref(dialog, area); @@ -150,11 +150,12 @@ fn build_error_status<'a>(err: &'a str, theme: &'a ColorTheme) -> Paragraph<'a> .block(Block::default().padding(Padding::horizontal(2))) } -fn build_loading_dialog(msg: &str) -> Paragraph { +fn build_loading_dialog<'a>(msg: &'a str, theme: &'a ColorTheme) -> Paragraph<'a> { let text = Line::from(msg.add_modifier(Modifier::BOLD)); Paragraph::new(text).alignment(Alignment::Center).block( Block::bordered() .border_type(BorderType::Rounded) - .padding(Padding::vertical(1)), + .padding(Padding::vertical(1)) + .fg(theme.text), ) } From e0161d39ede5dc65e7625aedd2ef1355a6ff9e59 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 28 Sep 2024 14:25:32 +0900 Subject: [PATCH 22/24] Add bg --- src/color.rs | 2 ++ src/ui/render.rs | 8 +++++++- src/widget/copy_detail_dialog.rs | 5 ++++- src/widget/dialog.rs | 12 ++++++++---- src/widget/input_dialog.rs | 5 ++++- src/widget/sort_list_dialog.rs | 5 ++++- 6 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/color.rs b/src/color.rs index 1f7263a..b3c8d93 100644 --- a/src/color.rs +++ b/src/color.rs @@ -2,6 +2,7 @@ use ratatui::style::Color; #[derive(Debug, Clone)] pub struct ColorTheme { + pub bg: Color, pub text: Color, pub selected: Color, pub selected_text: Color, @@ -20,6 +21,7 @@ pub struct ColorTheme { impl Default for ColorTheme { fn default() -> Self { Self { + bg: Color::Reset, text: Color::Reset, selected: Color::Cyan, selected_text: Color::Black, diff --git a/src/ui/render.rs b/src/ui/render.rs index 0d2d14c..cfc0c2b 100644 --- a/src/ui/render.rs +++ b/src/ui/render.rs @@ -23,6 +23,7 @@ pub fn render(f: &mut Frame, app: &mut App) { ]) .split(f.area()); + render_background(f, f.area(), app); render_header(f, chunks[0], app); render_content(f, chunks[1], app); render_footer(f, chunks[2], app); @@ -36,6 +37,11 @@ fn header_height(app: &App) -> u16 { } } +fn render_background(f: &mut Frame, area: Rect, app: &App) { + let block = Block::default().bg(app.theme.bg); + f.render_widget(block, area); +} + fn render_header(f: &mut Frame, area: Rect, app: &App) { if area.is_empty() { return; @@ -84,7 +90,7 @@ fn render_loading_dialog(f: &mut Frame, app: &App) { if app.app_view_state.is_loading { let loading = build_loading_dialog("Loading...", &app.theme); let area = calc_centered_dialog_rect(f.area(), 30, 5); - let dialog = Dialog::new(Box::new(loading)); + let dialog = Dialog::new(Box::new(loading), app.theme.bg); f.render_widget_ref(dialog, area); } } diff --git a/src/widget/copy_detail_dialog.rs b/src/widget/copy_detail_dialog.rs index be23ed5..879d98e 100644 --- a/src/widget/copy_detail_dialog.rs +++ b/src/widget/copy_detail_dialog.rs @@ -218,6 +218,7 @@ impl CopyDetailDialogState { #[derive(Debug, Default)] struct CopyDetailDialogColor { + bg: Color, block: Color, text: Color, selected: Color, @@ -226,6 +227,7 @@ struct CopyDetailDialogColor { impl CopyDetailDialogColor { fn new(theme: &ColorTheme) -> Self { Self { + bg: theme.bg, block: theme.text, text: theme.text, selected: theme.selected, @@ -266,10 +268,11 @@ impl StatefulWidget for CopyDetailDialog { Block::bordered() .border_type(BorderType::Rounded) .title(title) + .bg(self.color.bg) .fg(self.color.block) .padding(Padding::horizontal(1)), ); - let dialog = Dialog::new(Box::new(list)); + let dialog = Dialog::new(Box::new(list), self.color.bg); dialog.render_ref(area, buf); } } diff --git a/src/widget/dialog.rs b/src/widget/dialog.rs index 9dee7c2..7030c16 100644 --- a/src/widget/dialog.rs +++ b/src/widget/dialog.rs @@ -1,16 +1,18 @@ use ratatui::{ buffer::Buffer, layout::{Margin, Rect}, - widgets::{Clear, Widget, WidgetRef}, + style::{Color, Stylize}, + widgets::{Block, Clear, Widget, WidgetRef}, }; pub struct Dialog<'a> { content: Box, + bg: Color, } impl<'a> Dialog<'a> { - pub fn new(content: Box) -> Dialog<'a> { - Dialog { content } + pub fn new(content: Box, bg: Color) -> Dialog<'a> { + Dialog { content, bg } } } @@ -22,7 +24,9 @@ impl<'a> WidgetRef for Dialog<'a> { impl<'a> Dialog<'a> { fn render_dialog(&self, area: Rect, buf: &mut Buffer) { - Clear.render(outer_rect(area, &Margin::new(1, 0)), buf); + let outer = outer_rect(area, &Margin::new(1, 0)); + Clear.render(outer, buf); + Block::default().bg(self.bg).render(outer, buf); self.content.render_ref(area, buf); } } diff --git a/src/widget/input_dialog.rs b/src/widget/input_dialog.rs index 1a1c5b6..6b612d6 100644 --- a/src/widget/input_dialog.rs +++ b/src/widget/input_dialog.rs @@ -36,6 +36,7 @@ impl InputDialogState { #[derive(Debug, Default)] struct InputDialogColor { + bg: Color, block: Color, text: Color, } @@ -43,6 +44,7 @@ struct InputDialogColor { impl InputDialogColor { fn new(theme: &ColorTheme) -> InputDialogColor { InputDialogColor { + bg: theme.bg, block: theme.text, text: theme.text, } @@ -94,10 +96,11 @@ impl StatefulWidget for InputDialog { Block::bordered() .border_type(BorderType::Rounded) .title(title) + .bg(self.color.bg) .fg(self.color.block) .padding(Padding::horizontal(1)), ); - let dialog = Dialog::new(Box::new(dialog_content)); + let dialog = Dialog::new(Box::new(dialog_content), self.color.bg); dialog.render_ref(dialog_area, buf); // update cursor position diff --git a/src/widget/sort_list_dialog.rs b/src/widget/sort_list_dialog.rs index bf7e06f..675fe34 100644 --- a/src/widget/sort_list_dialog.rs +++ b/src/widget/sort_list_dialog.rs @@ -167,6 +167,7 @@ impl Widget for ObjectListSortDialog { #[derive(Debug, Default)] struct ListSortDialogColor { + bg: Color, block: Color, text: Color, selected: Color, @@ -175,6 +176,7 @@ struct ListSortDialogColor { impl ListSortDialogColor { fn new(theme: &ColorTheme) -> ListSortDialogColor { ListSortDialogColor { + bg: theme.bg, block: theme.text, text: theme.text, selected: theme.selected, @@ -224,9 +226,10 @@ impl Widget for ListSortDialog { .border_type(BorderType::Rounded) .title(title) .padding(Padding::horizontal(1)) + .bg(self.color.bg) .fg(self.color.block), ); - let dialog = Dialog::new(Box::new(list)); + let dialog = Dialog::new(Box::new(list), self.color.bg); dialog.render_ref(area, buf); } } From 92766013fabaf7c942a64e9aa70156b694f0ffa3 Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 28 Sep 2024 14:48:39 +0900 Subject: [PATCH 23/24] Rename text --- src/color.rs | 4 ++-- src/pages/help.rs | 2 +- src/pages/initializing.rs | 2 +- src/pages/object_detail.rs | 2 +- src/ui/render.rs | 2 +- src/widget/copy_detail_dialog.rs | 4 ++-- src/widget/header.rs | 4 ++-- src/widget/input_dialog.rs | 4 ++-- src/widget/scroll_lines.rs | 2 +- src/widget/scroll_list.rs | 4 ++-- src/widget/sort_list_dialog.rs | 4 ++-- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/color.rs b/src/color.rs index b3c8d93..5a4da4f 100644 --- a/src/color.rs +++ b/src/color.rs @@ -3,7 +3,7 @@ use ratatui::style::Color; #[derive(Debug, Clone)] pub struct ColorTheme { pub bg: Color, - pub text: Color, + pub fg: Color, pub selected: Color, pub selected_text: Color, pub disabled: Color, @@ -22,7 +22,7 @@ impl Default for ColorTheme { fn default() -> Self { Self { bg: Color::Reset, - text: Color::Reset, + fg: Color::Reset, selected: Color::Cyan, selected_text: Color::Black, disabled: Color::DarkGray, diff --git a/src/pages/help.rs b/src/pages/help.rs index bc7b317..abc5742 100644 --- a/src/pages/help.rs +++ b/src/pages/help.rs @@ -47,7 +47,7 @@ impl HelpPage { let block = Block::bordered() .padding(Padding::horizontal(1)) .title(APP_NAME) - .fg(self.theme.text); + .fg(self.theme.fg); let content_area = block.inner(area); diff --git a/src/pages/initializing.rs b/src/pages/initializing.rs index ee55197..4fbba4a 100644 --- a/src/pages/initializing.rs +++ b/src/pages/initializing.rs @@ -31,7 +31,7 @@ impl InitializingPage { } pub fn render(&mut self, f: &mut Frame, area: Rect) { - let content = Block::bordered().fg(self.theme.text); + let content = Block::bordered().fg(self.theme.fg); f.render_widget(content, area); } diff --git a/src/pages/object_detail.rs b/src/pages/object_detail.rs index 1de4566..efb377a 100644 --- a/src/pages/object_detail.rs +++ b/src/pages/object_detail.rs @@ -198,7 +198,7 @@ impl ObjectDetailPage { let list = ScrollList::new(list_items).theme(&self.theme); f.render_stateful_widget(list, chunks[0], &mut self.list_state); - let block = Block::bordered().fg(self.theme.text); + let block = Block::bordered().fg(self.theme.fg); f.render_widget(block, chunks[1]); let chunks = Layout::vertical([Constraint::Length(2), Constraint::Min(0)]) diff --git a/src/ui/render.rs b/src/ui/render.rs index cfc0c2b..6cb04db 100644 --- a/src/ui/render.rs +++ b/src/ui/render.rs @@ -162,6 +162,6 @@ fn build_loading_dialog<'a>(msg: &'a str, theme: &'a ColorTheme) -> Paragraph<'a Block::bordered() .border_type(BorderType::Rounded) .padding(Padding::vertical(1)) - .fg(theme.text), + .fg(theme.fg), ) } diff --git a/src/widget/copy_detail_dialog.rs b/src/widget/copy_detail_dialog.rs index 879d98e..cb66bd4 100644 --- a/src/widget/copy_detail_dialog.rs +++ b/src/widget/copy_detail_dialog.rs @@ -228,8 +228,8 @@ impl CopyDetailDialogColor { fn new(theme: &ColorTheme) -> Self { Self { bg: theme.bg, - block: theme.text, - text: theme.text, + block: theme.fg, + text: theme.fg, selected: theme.selected, } } diff --git a/src/widget/header.rs b/src/widget/header.rs index cd66987..51ef8ad 100644 --- a/src/widget/header.rs +++ b/src/widget/header.rs @@ -16,8 +16,8 @@ struct HeaderColor { impl HeaderColor { fn new(theme: &ColorTheme) -> HeaderColor { HeaderColor { - block: theme.text, - text: theme.text, + block: theme.fg, + text: theme.fg, } } } diff --git a/src/widget/input_dialog.rs b/src/widget/input_dialog.rs index 6b612d6..ca1bf15 100644 --- a/src/widget/input_dialog.rs +++ b/src/widget/input_dialog.rs @@ -45,8 +45,8 @@ impl InputDialogColor { fn new(theme: &ColorTheme) -> InputDialogColor { InputDialogColor { bg: theme.bg, - block: theme.text, - text: theme.text, + block: theme.fg, + text: theme.fg, } } } diff --git a/src/widget/scroll_lines.rs b/src/widget/scroll_lines.rs index a2c36a3..70ddffe 100644 --- a/src/widget/scroll_lines.rs +++ b/src/widget/scroll_lines.rs @@ -116,7 +116,7 @@ struct ScrollLinesColor { impl ScrollLinesColor { fn new(theme: &ColorTheme) -> Self { Self { - block: theme.text, + block: theme.fg, line_number: theme.line_number, } } diff --git a/src/widget/scroll_list.rs b/src/widget/scroll_list.rs index e54315d..07aa06a 100644 --- a/src/widget/scroll_list.rs +++ b/src/widget/scroll_list.rs @@ -121,8 +121,8 @@ struct ScrollListColor { impl ScrollListColor { fn new(theme: &ColorTheme) -> ScrollListColor { ScrollListColor { - block: theme.text, - bar: theme.text, + block: theme.fg, + bar: theme.fg, } } } diff --git a/src/widget/sort_list_dialog.rs b/src/widget/sort_list_dialog.rs index 675fe34..9a2c982 100644 --- a/src/widget/sort_list_dialog.rs +++ b/src/widget/sort_list_dialog.rs @@ -177,8 +177,8 @@ impl ListSortDialogColor { fn new(theme: &ColorTheme) -> ListSortDialogColor { ListSortDialogColor { bg: theme.bg, - block: theme.text, - text: theme.text, + block: theme.fg, + text: theme.fg, selected: theme.selected, } } From ef086961b6a97b5d4c290d710c40e549425a3aae Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sat, 28 Sep 2024 14:58:40 +0900 Subject: [PATCH 24/24] Organize --- src/color.rs | 62 ++++++++++++++++++++------------ src/pages/bucket_list.rs | 6 ++-- src/pages/object_detail.rs | 10 ++++-- src/pages/object_list.rs | 8 +++-- src/ui/render.rs | 10 +++--- src/widget/copy_detail_dialog.rs | 2 +- src/widget/scroll_lines.rs | 2 +- src/widget/sort_list_dialog.rs | 2 +- 8 files changed, 64 insertions(+), 38 deletions(-) diff --git a/src/color.rs b/src/color.rs index 5a4da4f..784dd7d 100644 --- a/src/color.rs +++ b/src/color.rs @@ -4,18 +4,27 @@ use ratatui::style::Color; pub struct ColorTheme { pub bg: Color, pub fg: Color, - pub selected: Color, - pub selected_text: Color, - pub disabled: Color, - pub match_text: Color, - pub link: Color, - pub short_help: Color, - pub info_status: Color, - pub success_status: Color, - pub warn_status: Color, - pub error_status: Color, - pub line_number: Color, + pub divider: Color, + pub link: Color, + + pub list_selected_bg: Color, + pub list_selected_fg: Color, + pub list_selected_inactive_bg: Color, + pub list_selected_inactive_fg: Color, + pub list_filter_match: Color, + + pub detail_selected: Color, + + pub dialog_selected: Color, + + pub preview_line_number: Color, + + pub status_help: Color, + pub status_info: Color, + pub status_success: Color, + pub status_warn: Color, + pub status_error: Color, } impl Default for ColorTheme { @@ -23,18 +32,27 @@ impl Default for ColorTheme { Self { bg: Color::Reset, fg: Color::Reset, - selected: Color::Cyan, - selected_text: Color::Black, - disabled: Color::DarkGray, - match_text: Color::Red, - link: Color::Blue, - short_help: Color::DarkGray, - info_status: Color::Blue, - success_status: Color::Green, - warn_status: Color::Yellow, - error_status: Color::Red, - line_number: Color::DarkGray, + divider: Color::DarkGray, + link: Color::Blue, + + 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, + + detail_selected: Color::Cyan, + + dialog_selected: Color::Cyan, + + preview_line_number: Color::DarkGray, + + status_help: Color::DarkGray, + status_info: Color::Blue, + status_success: Color::Green, + status_warn: Color::Yellow, + status_error: Color::Red, } } } diff --git a/src/pages/bucket_list.rs b/src/pages/bucket_list.rs index f0ca1eb..1fcda6f 100644 --- a/src/pages/bucket_list.rs +++ b/src/pages/bucket_list.rs @@ -493,14 +493,16 @@ fn build_list_item<'a>( Line::from(vec![ " ".into(), before.into(), - highlighted.fg(theme.match_text), + highlighted.fg(theme.list_filter_match), after.into(), " ".into(), ]) }; let style = if selected { - Style::default().bg(theme.selected).fg(theme.selected_text) + Style::default() + .bg(theme.list_selected_bg) + .fg(theme.list_selected_fg) } else { Style::default() }; diff --git a/src/pages/object_detail.rs b/src/pages/object_detail.rs index efb377a..1444273 100644 --- a/src/pages/object_detail.rs +++ b/src/pages/object_detail.rs @@ -450,7 +450,11 @@ fn build_list_item_from_object_item<'a>( } }; if idx + offset == selected { - ListItem::new(content).style(Style::default().bg(theme.disabled).fg(theme.selected_text)) + ListItem::new(content).style( + Style::default() + .bg(theme.list_selected_inactive_bg) + .fg(theme.list_selected_inactive_fg), + ) } else { ListItem::new(content) } @@ -474,7 +478,7 @@ fn build_tabs(tab: &Tab, theme: &ColorTheme) -> Tabs<'static> { .highlight_style( Style::default() .add_modifier(Modifier::BOLD) - .fg(theme.selected), + .fg(theme.detail_selected), ) .block(Block::default().borders(Borders::BOTTOM)) } @@ -656,7 +660,7 @@ struct VersionTabColor { impl VersionTabColor { fn new(theme: &ColorTheme) -> Self { Self { - selected: theme.selected, + selected: theme.detail_selected, divider: theme.divider, } } diff --git a/src/pages/object_list.rs b/src/pages/object_list.rs index a47c804..54f239b 100644 --- a/src/pages/object_list.rs +++ b/src/pages/object_list.rs @@ -576,7 +576,9 @@ fn build_list_item<'a>( }; let style = if selected { - Style::default().bg(theme.selected).fg(theme.selected_text) + Style::default() + .bg(theme.list_selected_bg) + .fg(theme.list_selected_fg) } else { Style::default() }; @@ -591,7 +593,7 @@ fn build_object_dir_line<'a>(name: &'a str, filter: &'a str, theme: &ColorTheme) Line::from(vec![ " ".into(), before.bold(), - highlighted.fg(theme.match_text).bold(), + highlighted.fg(theme.list_filter_match).bold(), after.bold(), "/".bold(), " ".into(), @@ -633,7 +635,7 @@ fn build_object_file_line<'a>( Line::from(vec![ " ".into(), before.into(), - highlighted.fg(theme.match_text), + highlighted.fg(theme.list_filter_match), after.into(), " ".into(), date.into(), diff --git a/src/ui/render.rs b/src/ui/render.rs index 6cb04db..334bdd5 100644 --- a/src/ui/render.rs +++ b/src/ui/render.rs @@ -126,7 +126,7 @@ fn build_short_help(app: &App, width: u16) -> Paragraph { let pad = Padding::horizontal(2); let max_width = (width - pad.left - pad.right) as usize; let help = build_short_help_string(&helps, max_width); - Paragraph::new(help.fg(app.theme.short_help)).block(Block::default().padding(pad)) + Paragraph::new(help.fg(app.theme.status_help)).block(Block::default().padding(pad)) } fn build_short_help_string(helps: &[(String, usize)], max_width: usize) -> String { @@ -136,23 +136,23 @@ fn build_short_help_string(helps: &[(String, usize)], max_width: usize) -> Strin } fn build_info_status<'a>(msg: &'a str, theme: &'a ColorTheme) -> Paragraph<'a> { - Paragraph::new(msg.fg(theme.info_status)) + Paragraph::new(msg.fg(theme.status_info)) .block(Block::default().padding(Padding::horizontal(2))) } fn build_success_status<'a>(msg: &'a str, theme: &'a ColorTheme) -> Paragraph<'a> { - Paragraph::new(msg.add_modifier(Modifier::BOLD).fg(theme.success_status)) + Paragraph::new(msg.add_modifier(Modifier::BOLD).fg(theme.status_success)) .block(Block::default().padding(Padding::horizontal(2))) } fn build_warn_status<'a>(msg: &'a str, theme: &'a ColorTheme) -> Paragraph<'a> { - Paragraph::new(msg.add_modifier(Modifier::BOLD).fg(theme.warn_status)) + Paragraph::new(msg.add_modifier(Modifier::BOLD).fg(theme.status_warn)) .block(Block::default().padding(Padding::horizontal(2))) } fn build_error_status<'a>(err: &'a str, theme: &'a ColorTheme) -> Paragraph<'a> { let err = format!("ERROR: {}", err); - Paragraph::new(err.add_modifier(Modifier::BOLD).fg(theme.error_status)) + Paragraph::new(err.add_modifier(Modifier::BOLD).fg(theme.status_error)) .block(Block::default().padding(Padding::horizontal(2))) } diff --git a/src/widget/copy_detail_dialog.rs b/src/widget/copy_detail_dialog.rs index cb66bd4..3c1851f 100644 --- a/src/widget/copy_detail_dialog.rs +++ b/src/widget/copy_detail_dialog.rs @@ -230,7 +230,7 @@ impl CopyDetailDialogColor { bg: theme.bg, block: theme.fg, text: theme.fg, - selected: theme.selected, + selected: theme.dialog_selected, } } } diff --git a/src/widget/scroll_lines.rs b/src/widget/scroll_lines.rs index 70ddffe..8299244 100644 --- a/src/widget/scroll_lines.rs +++ b/src/widget/scroll_lines.rs @@ -117,7 +117,7 @@ impl ScrollLinesColor { fn new(theme: &ColorTheme) -> Self { Self { block: theme.fg, - line_number: theme.line_number, + line_number: theme.preview_line_number, } } } diff --git a/src/widget/sort_list_dialog.rs b/src/widget/sort_list_dialog.rs index 9a2c982..47f1a62 100644 --- a/src/widget/sort_list_dialog.rs +++ b/src/widget/sort_list_dialog.rs @@ -179,7 +179,7 @@ impl ListSortDialogColor { bg: theme.bg, block: theme.fg, text: theme.fg, - selected: theme.selected, + selected: theme.dialog_selected, } } }