From 42447da840c354853e6435a6e45a599ba63cd869 Mon Sep 17 00:00:00 2001 From: Mehran KORDI Date: Tue, 19 May 2020 12:26:50 +0200 Subject: [PATCH 01/11] implemented themes. --- CHANGELOG.md | 2 + Cargo.lock | 50 ++++++- Cargo.toml | 4 +- README.md | 13 ++ assets/themes/dark.ron | 12 ++ assets/themes/light.ron | 12 ++ src/app.rs | 44 +++--- src/components/changes.rs | 56 +++----- src/components/commit.rs | 27 ++-- src/components/diff.rs | 65 +++------ src/components/help.rs | 37 +++-- src/components/mod.rs | 14 ++ src/components/msg.rs | 23 ++-- src/components/reset.rs | 21 ++- src/main.rs | 23 +++- src/spinner.rs | 6 +- src/tabs/revlog/mod.rs | 114 ++++++---------- src/tabs/status.rs | 6 +- src/ui/mod.rs | 25 ++-- src/ui/scrolllist.rs | 5 - src/ui/style.rs | 275 ++++++++++++++++++++++++++++++++++++++ 21 files changed, 578 insertions(+), 256 deletions(-) create mode 100644 assets/themes/dark.ron create mode 100644 assets/themes/light.ron create mode 100644 src/ui/style.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 74b67c2152..6ae49493ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- support for color themes and light mode([#28](https://github.com/extrawurst/gitui/issues/28)) ## [0.2.6] - 2020-05-18 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 552fd55026..e778f21390 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,15 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +dependencies = [ + "byteorder", +] + [[package]] name = "base64" version = "0.11.0" @@ -82,6 +91,12 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + [[package]] name = "cassowary" version = "0.3.0" @@ -299,8 +314,10 @@ dependencies = [ "itertools", "log", "rayon-core", + "ron", "scopeguard", "scopetime", + "serde", "simplelog", "tui", ] @@ -654,13 +671,24 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "ron" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5" +dependencies = [ + "base64 0.10.1", + "bitflags", + "serde", +] + [[package]] name = "rust-argon2" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" dependencies = [ - "base64", + "base64 0.11.0", "blake2b_simd", "constant_time_eq", "crossbeam-utils", @@ -685,6 +713,26 @@ dependencies = [ "log", ] +[[package]] +name = "serde" +version = "1.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "signal-hook" version = "0.1.15" diff --git a/Cargo.toml b/Cargo.toml index cf5ffe5371..354cff977b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,10 @@ scopeguard = "1.1" bitflags = "1.2" chrono = "0.4" backtrace = { version = "0.3" } +ron = "0.5.1" scopetime = { path = "./scopetime", version = "0.1" } asyncgit = { path = "./asyncgit", version = "0.2" } +serde = "1.0.110" [features] default=[] @@ -45,6 +47,6 @@ members=[ ] [profile.release] -lto = true +lto = true opt-level = 'z' # Optimize for size. codegen-units = 1 \ No newline at end of file diff --git a/README.md b/README.md index 56f25eba3d..ee3465baec 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,19 @@ this will log to: * `$XDG_CACHE_HOME/gitui/gitui.log` (linux using `XDG`) * `$HOME/.cache/gitui/gitui.log` (linux) +# color theme + +two different color themes are supported for light and dark mode. to change the colors you have to modify files in +[Ron format](https://github.com/ron-rs/ron) located at git config path (same as log paths). the list of valid +colors can be found in [ColorDef](./src/ui/style.rs#ColorDef) struct. note that rgb colors might not be available +on some platforms. + +to enable light mode: +``` +GITUI_LIGHT=true gitui +``` + + # inspiration * https://github.com/jesseduffield/lazygit diff --git a/assets/themes/dark.ron b/assets/themes/dark.ron new file mode 100644 index 0000000000..51afc21cbe --- /dev/null +++ b/assets/themes/dark.ron @@ -0,0 +1,12 @@ +( + selected_tab: Yellow, + command_background: Rgb(0,0,100), + command_disabled: Gray, + diff_line_add: Green, + diff_line_delete: Red, + diff_file_added: LightGreen, + diff_file_removed: LightRed, + diff_file_moved: LightMagenta, + diff_file_modified: Yellow, + table_colors: (Magenta, Blue, Green), +) \ No newline at end of file diff --git a/assets/themes/light.ron b/assets/themes/light.ron new file mode 100644 index 0000000000..bd7703a033 --- /dev/null +++ b/assets/themes/light.ron @@ -0,0 +1,12 @@ +( + selected_tab: Yellow, + command_background: LightBlue, + command_disabled: Gray, + diff_line_add: Green, + diff_line_delete: Red, + diff_file_added: LightGreen, + diff_file_removed: LightRed, + diff_file_moved: LightMagenta, + diff_file_modified: Yellow, + table_colors: (Magenta, Blue, Green), +) \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 28a30d5ff9..cc483fd2d2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,4 @@ +use crate::ui::style::{Mode, Theme}; use crate::{ accessors, components::{ @@ -17,10 +18,11 @@ use itertools::Itertools; use log::trace; use std::borrow::Cow; use strings::commands; +use tui::style::Style; use tui::{ backend::Backend, layout::{Alignment, Constraint, Direction, Layout, Rect}, - style::{Color, Modifier, Style}, + style::Modifier, widgets::{Block, Borders, Paragraph, Tabs, Text}, Frame, }; @@ -37,24 +39,32 @@ pub struct App { revlog: Revlog, status_tab: Status, queue: Queue, + pub theme: Theme, } // public interface impl App { /// - pub fn new(sender: &Sender) -> Self { + pub fn new( + sender: &Sender, + mode: Mode, + ) -> Self { let queue = Queue::default(); + + let theme = Theme::init(mode); + Self { - reset: ResetComponent::new(queue.clone()), - commit: CommitComponent::new(queue.clone()), + reset: ResetComponent::new(queue.clone(), theme), + commit: CommitComponent::new(queue.clone(), theme), do_quit: false, current_commands: Vec::new(), - help: HelpComponent::default(), - msg: MsgComponent::default(), + help: HelpComponent::new(theme), + msg: MsgComponent::new(), tab: 0, - revlog: Revlog::new(&sender), - status_tab: Status::new(&sender, &queue), + revlog: Revlog::new(&sender, theme), + status_tab: Status::new(&sender, &queue, theme), queue, + theme, } } @@ -84,6 +94,7 @@ impl App { f, chunks_main[2], self.current_commands.as_slice(), + self.theme, ); self.draw_popups(f); @@ -320,10 +331,9 @@ impl App { Tabs::default() .block(Block::default().borders(Borders::BOTTOM)) .titles(&[strings::TAB_STATUS, strings::TAB_LOG]) - .style(Style::default().fg(Color::White)) .highlight_style( - Style::default() - .fg(Color::Yellow) + self.theme + .tab(true) .modifier(Modifier::UNDERLINED), ) .divider(strings::TAB_DIVIDER) @@ -336,28 +346,20 @@ impl App { f: &mut Frame, r: Rect, cmds: &[CommandInfo], + theme: Theme, ) { let splitter = Text::Styled( Cow::from(strings::CMD_SPLITTER), Style::default(), ); - let style_enabled = - Style::default().fg(Color::White).bg(Color::Blue); - - let style_disabled = - Style::default().fg(Color::DarkGray).bg(Color::Blue); let texts = cmds .iter() .filter_map(|c| { if c.show_in_quickbar() { Some(Text::Styled( Cow::from(c.text.name), - if c.enabled { - style_enabled - } else { - style_disabled - }, + theme.toolbar(c.enabled), )) } else { None diff --git a/src/components/changes.rs b/src/components/changes.rs index 66418a03c4..6ea16519d2 100644 --- a/src/components/changes.rs +++ b/src/components/changes.rs @@ -3,6 +3,7 @@ use super::{ statustree::{MoveSelection, StatusTree}, CommandBlocking, DrawableComponent, }; +use crate::ui::style::Theme; use crate::{ components::{CommandInfo, Component}, keys, @@ -13,13 +14,7 @@ use asyncgit::{hash, sync, StatusItem, StatusItemType, CWD}; use crossterm::event::Event; use std::{borrow::Cow, convert::From, path::Path}; use strings::commands; -use tui::{ - backend::Backend, - layout::Rect, - style::{Color, Style}, - widgets::Text, - Frame, -}; +use tui::{backend::Backend, layout::Rect, widgets::Text, Frame}; /// pub struct ChangesComponent { @@ -30,6 +25,7 @@ pub struct ChangesComponent { show_selection: bool, is_working_dir: bool, queue: Queue, + theme: Theme, } impl ChangesComponent { @@ -39,6 +35,7 @@ impl ChangesComponent { focus: bool, is_working_dir: bool, queue: Queue, + theme: Theme, ) -> Self { Self { title: title.to_string(), @@ -48,6 +45,7 @@ impl ChangesComponent { show_selection: focus, is_working_dir, queue, + theme, } } @@ -154,9 +152,8 @@ impl ChangesComponent { item: &FileTreeItem, width: u16, selected: bool, + theme: Theme, ) -> Option { - let select_color = Color::Rgb(0, 0, 100); - let indent_str = if item.info.indent == 0 { String::from("") } else { @@ -189,18 +186,14 @@ impl ChangesComponent { format!("{} {}{}", status_char, indent_str, file) }; - let mut style = - Style::default().fg(Self::item_color( - status_item - .status - .unwrap_or(StatusItemType::Modified), - )); + let status = status_item + .status + .unwrap_or(StatusItemType::Modified); - if selected { - style = style.bg(select_color); - } - - Some(Text::Styled(Cow::from(txt), style)) + Some(Text::Styled( + Cow::from(txt), + theme.item(status, selected), + )) } FileTreeItemKind::Path(path_collapsed) => { @@ -222,27 +215,14 @@ impl ChangesComponent { ) }; - let mut style = Style::default(); - - if selected { - style = style.bg(select_color); - } - - Some(Text::Styled(Cow::from(txt), style)) + Some(Text::Styled( + Cow::from(txt), + theme.text(true, selected), + )) } } } - fn item_color(item_type: StatusItemType) -> Color { - match item_type { - StatusItemType::Modified => Color::LightYellow, - StatusItemType::New => Color::LightGreen, - StatusItemType::Deleted => Color::LightRed, - StatusItemType::Renamed => Color::LightMagenta, - _ => Color::White, - } - } - fn item_status_char(item_type: Option) -> char { if let Some(item_type) = item_type { match item_type { @@ -287,6 +267,7 @@ impl DrawableComponent for ChangesComponent { .tree .selection .map_or(false, |e| e == idx), + self.theme, ) }, ); @@ -298,6 +279,7 @@ impl DrawableComponent for ChangesComponent { items, self.tree.selection.map(|idx| idx - selection_offset), self.focused, + self.theme, ); } } diff --git a/src/components/commit.rs b/src/components/commit.rs index 637fa3df2f..2cdd042e01 100644 --- a/src/components/commit.rs +++ b/src/components/commit.rs @@ -2,6 +2,8 @@ use super::{ visibility_blocking, CommandBlocking, CommandInfo, Component, DrawableComponent, }; +use crate::components::dialog_paragraph; +use crate::ui::style::Theme; use crate::{ queue::{InternalEvent, NeedsUpdate, Queue}, strings, ui, @@ -12,11 +14,11 @@ use log::error; use std::borrow::Cow; use strings::commands; use sync::HookResult; +use tui::style::Style; use tui::{ backend::Backend, - layout::{Alignment, Rect}, - style::{Color, Style}, - widgets::{Block, Borders, Clear, Paragraph, Text}, + layout::Rect, + widgets::{Clear, Text}, Frame, }; @@ -24,6 +26,7 @@ pub struct CommitComponent { msg: String, visible: bool, queue: Queue, + theme: Theme, } impl DrawableComponent for CommitComponent { @@ -32,22 +35,19 @@ impl DrawableComponent for CommitComponent { let txt = if self.msg.is_empty() { [Text::Styled( Cow::from(strings::COMMIT_MSG), - Style::default().fg(Color::DarkGray), + self.theme.text(false, false), )] } else { - [Text::Raw(Cow::from(self.msg.clone()))] + [Text::Styled( + Cow::from(self.msg.clone()), + Style::default(), + )] }; let area = ui::centered_rect(60, 20, f.size()); f.render_widget(Clear, area); f.render_widget( - Paragraph::new(txt.iter()) - .block( - Block::default() - .title(strings::COMMIT_TITLE) - .borders(Borders::ALL), - ) - .alignment(Alignment::Left), + dialog_paragraph(strings::COMMIT_TITLE, txt.iter()), area, ); } @@ -112,11 +112,12 @@ impl Component for CommitComponent { impl CommitComponent { /// - pub fn new(queue: Queue) -> Self { + pub fn new(queue: Queue, theme: Theme) -> Self { Self { queue, msg: String::default(), visible: false, + theme, } } diff --git a/src/components/diff.rs b/src/components/diff.rs index 4c62eae4ac..63b389bd4c 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -1,4 +1,5 @@ use super::{CommandBlocking, DrawableComponent, ScrollType}; +use crate::ui::style::Theme; use crate::{ components::{CommandInfo, Component}, keys, @@ -9,10 +10,11 @@ use asyncgit::{hash, DiffLine, DiffLineType, FileDiff}; use crossterm::event::Event; use std::{borrow::Cow, cmp, convert::TryFrom}; use strings::commands; + use tui::{ backend::Backend, layout::{Alignment, Rect}, - style::{Color, Modifier, Style}, + style::{Modifier, Style}, symbols, widgets::{Block, Borders, Paragraph, Text}, Frame, @@ -34,11 +36,12 @@ pub struct DiffComponent { current: Current, selected_hunk: Option, queue: Queue, + theme: Theme, } impl DiffComponent { /// - pub fn new(queue: Queue) -> Self { + pub fn new(queue: Queue, theme: Theme) -> Self { Self { focused: false, queue, @@ -47,6 +50,7 @@ impl DiffComponent { diff: FileDiff::default(), scroll: 0, current_height: 0, + theme, } } /// @@ -171,6 +175,7 @@ impl DiffComponent { selection == line_cursor, hunk_selected, i == hunk_len as usize - 1, + self.theme, ); lines_added += 1; } @@ -191,22 +196,10 @@ impl DiffComponent { selected: bool, selected_hunk: bool, end_of_hunk: bool, + theme: Theme, ) { - let select_color = Color::Rgb(0, 0, 100); - let style_default = Style::default().bg(if selected { - select_color - } else { - Color::Reset - }); - { - let style = Style::default() - .bg(if selected || selected_hunk { - select_color - } else { - Color::Reset - }) - .fg(Color::DarkGray); + let style = theme.text(false, selected || selected_hunk); if end_of_hunk { text.push(Text::Styled( @@ -227,17 +220,6 @@ impl DiffComponent { } } - let style_delete = Style::default() - .fg(Color::Red) - .bg(if selected { select_color } else { Color::Reset }); - let style_add = Style::default() - .fg(Color::Green) - .bg(if selected { select_color } else { Color::Reset }); - let style_header = Style::default() - .fg(Color::White) - .bg(if selected { select_color } else { Color::Reset }) - .modifier(Modifier::BOLD); - let trimmed = line.content.trim_matches(|c| c == '\n' || c == '\r'); @@ -251,16 +233,10 @@ impl DiffComponent { //TODO: allow customize tabsize let content = Cow::from(filled.replace("\t", " ")); - text.push(match line.line_type { - DiffLineType::Delete => { - Text::Styled(content, style_delete) - } - DiffLineType::Add => Text::Styled(content, style_add), - DiffLineType::Header => { - Text::Styled(content, style_header) - } - _ => Text::Styled(content, style_default), - }); + text.push(Text::Styled( + content, + theme.diff_line(line.line_type, selected), + )); } fn hunk_visible( @@ -299,13 +275,6 @@ impl DiffComponent { impl DrawableComponent for DiffComponent { fn draw(&mut self, f: &mut Frame, r: Rect) { self.current_height = r.height.saturating_sub(2); - let mut style_border = Style::default().fg(Color::DarkGray); - let mut style_title = Style::default(); - if self.focused { - style_border = style_border.fg(Color::Gray); - style_title = style_title.modifier(Modifier::BOLD); - } - let title = format!("{}{}", strings::TITLE_DIFF, self.current.path); f.render_widget( @@ -314,8 +283,10 @@ impl DrawableComponent for DiffComponent { Block::default() .title(title.as_str()) .borders(Borders::ALL) - .border_style(style_border) - .title_style(style_title), + .border_style(self.theme.block(self.focused)) + .title_style( + Style::default().modifier(Modifier::BOLD), + ), ) .alignment(Alignment::Left), r, @@ -414,7 +385,6 @@ mod tests { #[test] fn test_lineendings() { let mut text = Vec::new(); - DiffComponent::add_line( &mut text, 10, @@ -425,6 +395,7 @@ mod tests { false, false, false, + Theme::empty(), ); assert_eq!(text.len(), 2); diff --git a/src/components/help.rs b/src/components/help.rs index 43e0305dc4..b9787338da 100644 --- a/src/components/help.rs +++ b/src/components/help.rs @@ -2,26 +2,27 @@ use super::{ visibility_blocking, CommandBlocking, CommandInfo, Component, DrawableComponent, }; +use crate::ui::style::Theme; use crate::{keys, strings, ui, version::Version}; use asyncgit::hash; use crossterm::event::Event; use itertools::Itertools; use std::{borrow::Cow, cmp, convert::TryFrom}; use strings::commands; +use tui::style::{Modifier, Style}; use tui::{ backend::Backend, layout::{Alignment, Constraint, Direction, Layout, Rect}, - style::{Color, Style}, widgets::{Block, Borders, Clear, Paragraph, Text}, Frame, }; /// -#[derive(Default)] pub struct HelpComponent { cmds: Vec, visible: bool, selection: u16, + theme: Theme, } impl DrawableComponent for HelpComponent { @@ -68,10 +69,13 @@ impl DrawableComponent for HelpComponent { f.render_widget( Paragraph::new( - vec![Text::Raw(Cow::from(format!( - "gitui {}", - Version::new(), - )))] + vec![Text::Styled( + Cow::from(format!( + "gitui {}", + Version::new(), + )), + Style::default(), + )] .iter(), ) .alignment(Alignment::Right), @@ -150,6 +154,14 @@ impl Component for HelpComponent { } impl HelpComponent { + pub fn new(theme: Theme) -> Self { + Self { + cmds: vec![], + visible: false, + selection: 0, + theme, + } + } /// pub fn set_cmds(&mut self, cmds: Vec) { self.cmds = cmds @@ -187,7 +199,7 @@ impl HelpComponent { { txt.push(Text::Styled( Cow::from(format!(" {}\n", key)), - Style::default().fg(Color::Black).bg(Color::Gray), + Style::default().modifier(Modifier::REVERSED), )); txt.extend( @@ -216,13 +228,10 @@ impl HelpComponent { ); } - let style = if is_selected { - Style::default().fg(Color::Yellow) - } else { - Style::default() - }; - - Text::Styled(Cow::from(out), style) + Text::Styled( + Cow::from(out), + self.theme.text(true, is_selected), + ) }) .collect::>(), ); diff --git a/src/components/mod.rs b/src/components/mod.rs index e0920d254d..fd1f3235c7 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -18,6 +18,8 @@ pub use filetree::FileTreeItemKind; pub use help::HelpComponent; pub use msg::MsgComponent; pub use reset::ResetComponent; +use tui::layout::Alignment; +use tui::widgets::{Block, Borders, Paragraph, Text}; /// creates accessors for a list of components /// @@ -114,3 +116,15 @@ pub trait Component { /// fn show(&mut self) {} } + +fn dialog_paragraph<'a, 't, T>( + title: &'a str, + content: T, +) -> Paragraph<'a, 't, T> +where + T: Iterator>, +{ + Paragraph::new(content) + .block(Block::default().title(title).borders(Borders::ALL)) + .alignment(Alignment::Left) +} diff --git a/src/components/msg.rs b/src/components/msg.rs index 78847a8935..408c7c277b 100644 --- a/src/components/msg.rs +++ b/src/components/msg.rs @@ -2,18 +2,18 @@ use super::{ visibility_blocking, CommandBlocking, CommandInfo, Component, DrawableComponent, }; +use crate::components::dialog_paragraph; use crate::{keys, strings, ui}; use crossterm::event::Event; use std::borrow::Cow; use strings::commands; use tui::{ backend::Backend, - layout::{Alignment, Rect}, - widgets::{Block, Borders, Clear, Paragraph, Text}, + layout::Rect, + widgets::{Clear, Text}, Frame, }; -#[derive(Default)] pub struct MsgComponent { msg: String, visible: bool, @@ -27,14 +27,8 @@ impl DrawableComponent for MsgComponent { let area = ui::centered_rect_absolute(65, 25, f.size()); f.render_widget(Clear, area); f.render_widget( - Paragraph::new(txt.iter()) - .block( - Block::default() - .title(strings::MSG_TITLE) - .borders(Borders::ALL), - ) - .wrap(true) - .alignment(Alignment::Left), + dialog_paragraph(strings::MSG_TITLE, txt.iter()) + .wrap(true), area, ); } @@ -89,4 +83,11 @@ impl MsgComponent { self.msg = msg.to_string(); self.show(); } + + pub fn new() -> Self { + Self { + msg: "".to_string(), + visible: false, + } + } } diff --git a/src/components/reset.rs b/src/components/reset.rs index e5b481ad47..e755e5bd2d 100644 --- a/src/components/reset.rs +++ b/src/components/reset.rs @@ -7,14 +7,15 @@ use crate::{ strings, ui, }; +use crate::components::dialog_paragraph; +use crate::ui::style::Theme; use crossterm::event::{Event, KeyCode}; use std::borrow::Cow; use strings::commands; use tui::{ backend::Backend, - layout::{Alignment, Rect}, - style::{Color, Style}, - widgets::{Block, Borders, Clear, Paragraph, Text}, + layout::Rect, + widgets::{Clear, Text}, Frame, }; @@ -23,6 +24,7 @@ pub struct ResetComponent { target: Option, visible: bool, queue: Queue, + theme: Theme, } impl DrawableComponent for ResetComponent { @@ -31,19 +33,13 @@ impl DrawableComponent for ResetComponent { let mut txt = Vec::new(); txt.push(Text::Styled( Cow::from(strings::RESET_MSG), - Style::default().fg(Color::Red), + self.theme.text_danger(), )); let area = ui::centered_rect(30, 20, f.size()); f.render_widget(Clear, area); f.render_widget( - Paragraph::new(txt.iter()) - .block( - Block::default() - .title(strings::RESET_TITLE) - .borders(Borders::ALL), - ) - .alignment(Alignment::Left), + dialog_paragraph(strings::RESET_TITLE, txt.iter()), area, ); } @@ -106,11 +102,12 @@ impl Component for ResetComponent { impl ResetComponent { /// - pub fn new(queue: Queue) -> Self { + pub fn new(queue: Queue, theme: Theme) -> Self { Self { target: None, visible: false, queue, + theme, } } /// diff --git a/src/main.rs b/src/main.rs index 356455e746..71a23553f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ mod tabs; mod ui; mod version; +use crate::ui::style::Mode; use crate::{app::App, poll::QueueEvent}; use asyncgit::AsyncNotification; use backtrace::Backtrace; @@ -31,6 +32,7 @@ use scopeguard::defer; use scopetime::scope_time; use simplelog::{Config, LevelFilter, WriteLogger}; use spinner::Spinner; +use std::path::PathBuf; use std::{ env, fs, fs::File, @@ -66,7 +68,12 @@ fn main() -> Result<()> { let (tx_git, rx_git) = unbounded(); - let mut app = App::new(&tx_git); + let mode = match env::var("GITUI_LIGHT") { + Ok(_) => Mode::Light, + _ => Mode::Dark, + }; + + let mut app = App::new(&tx_git, mode); let rx_input = poll::start_polling_thread(); let ticker = tick(TICK_INTERVAL); @@ -75,7 +82,7 @@ fn main() -> Result<()> { app.update(); draw(&mut terminal, &mut app)?; - let mut spinner = Spinner::default(); + let mut spinner = Spinner::new(); loop { let events: Vec = select_event( @@ -175,12 +182,18 @@ fn start_terminal( Ok(terminal) } +#[must_use] +pub fn get_app_config_path() -> PathBuf { + let mut path = dirs::cache_dir().unwrap(); + path.push("gitui"); + fs::create_dir_all(&path).unwrap(); + path +} + fn setup_logging() { if env::var("GITUI_LOGGING").is_ok() { - let mut path = dirs::cache_dir().unwrap(); - path.push("gitui"); + let mut path = get_app_config_path(); path.push("gitui.log"); - fs::create_dir_all(path.parent().unwrap()).unwrap(); let _ = WriteLogger::init( LevelFilter::Trace, diff --git a/src/spinner.rs b/src/spinner.rs index 204b7a74e5..80204c23a9 100644 --- a/src/spinner.rs +++ b/src/spinner.rs @@ -3,8 +3,6 @@ use tui::{backend::Backend, buffer::Cell, Terminal}; static SPINNER_CHARS: &[char] = &['|', '/', '-', '\\']; -/// -#[derive(Default)] pub struct Spinner { idx: usize, } @@ -16,6 +14,10 @@ impl Spinner { self.idx %= SPINNER_CHARS.len(); } + pub fn new() -> Self { + Self { idx: 0 } + } + /// draws or removes spinner char depending on `pending` state pub fn draw( &self, diff --git a/src/tabs/revlog/mod.rs b/src/tabs/revlog/mod.rs index e430c1e1bc..ff6087c5dc 100644 --- a/src/tabs/revlog/mod.rs +++ b/src/tabs/revlog/mod.rs @@ -1,5 +1,6 @@ mod utils; +use crate::ui::style::Theme; use crate::{ components::{ CommandBlocking, CommandInfo, Component, DrawableComponent, @@ -17,34 +18,13 @@ use sync::Tags; use tui::{ backend::Backend, layout::{Alignment, Rect}, - style::{Color, Style}, widgets::{Block, Borders, Paragraph, Text}, Frame, }; use utils::{ItemBatch, LogEntry}; -const COLOR_SELECTION_BG: Color = Color::Blue; - -const STYLE_TAG: Style = Style::new().fg(Color::Yellow); -const STYLE_HASH: Style = Style::new().fg(Color::Magenta); -const STYLE_TIME: Style = Style::new().fg(Color::Blue); -const STYLE_AUTHOR: Style = Style::new().fg(Color::Green); -const STYLE_MSG: Style = Style::new().fg(Color::Reset); - -const STYLE_TAG_SELECTED: Style = - Style::new().fg(Color::Yellow).bg(COLOR_SELECTION_BG); -const STYLE_HASH_SELECTED: Style = - Style::new().fg(Color::Magenta).bg(COLOR_SELECTION_BG); -const STYLE_TIME_SELECTED: Style = - Style::new().fg(Color::White).bg(COLOR_SELECTION_BG); -const STYLE_AUTHOR_SELECTED: Style = - Style::new().fg(Color::Green).bg(COLOR_SELECTION_BG); -const STYLE_MSG_SELECTED: Style = - Style::new().fg(Color::Reset).bg(COLOR_SELECTION_BG); - static ELEMENTS_PER_LINE: usize = 10; static SLICE_SIZE: usize = 1200; - /// pub struct Revlog { selection: usize, @@ -57,11 +37,15 @@ pub struct Revlog { tags: Tags, current_size: (u16, u16), scroll_top: usize, + theme: Theme, } impl Revlog { /// - pub fn new(sender: &Sender) -> Self { + pub fn new( + sender: &Sender, + theme: Theme, + ) -> Self { Self { items: ItemBatch::default(), git_log: AsyncLog::new(sender.clone()), @@ -73,6 +57,7 @@ impl Revlog { tags: Tags::new(), current_size: (0, 0), scroll_top: 0, + theme, } } @@ -171,44 +156,27 @@ impl Revlog { selected: bool, txt: &mut Vec>, tags: Option, + theme: Theme, ) { let count_before = txt.len(); let splitter_txt = Cow::from(" "); - let splitter = if selected { - Text::Styled( - splitter_txt, - Style::new().bg(COLOR_SELECTION_BG), - ) - } else { - Text::Raw(splitter_txt) - }; + let splitter = + Text::Styled(splitter_txt, theme.text(true, selected)); txt.push(Text::Styled( Cow::from(&e.hash[0..7]), - if selected { - STYLE_HASH_SELECTED - } else { - STYLE_HASH - }, + theme.table(0, selected), )); txt.push(splitter.clone()); txt.push(Text::Styled( Cow::from(e.time.as_str()), - if selected { - STYLE_TIME_SELECTED - } else { - STYLE_TIME - }, + theme.table(1, selected), )); txt.push(splitter.clone()); txt.push(Text::Styled( Cow::from(e.author.as_str()), - if selected { - STYLE_AUTHOR_SELECTED - } else { - STYLE_AUTHOR - }, + theme.table(2, selected), )); txt.push(splitter.clone()); txt.push(Text::Styled( @@ -217,20 +185,12 @@ impl Revlog { } else { String::from("") }), - if selected { - STYLE_TAG_SELECTED - } else { - STYLE_TAG - }, + theme.tab(true), )); txt.push(splitter); txt.push(Text::Styled( Cow::from(e.msg.as_str()), - if selected { - STYLE_MSG_SELECTED - } else { - STYLE_MSG - }, + theme.text(true, selected), )); txt.push(Text::Raw(Cow::from("\n"))); @@ -248,7 +208,13 @@ impl Revlog { } else { None }; - Self::add_entry(e, idx == selection, &mut txt, tag); + Self::add_entry( + e, + idx == selection, + &mut txt, + tag, + self.theme, + ); } txt @@ -299,6 +265,24 @@ impl DrawableComponent for Revlog { } impl Component for Revlog { + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + out.push(CommandInfo::new( + commands::SCROLL, + self.visible, + self.visible || force_all, + )); + + if self.visible { + CommandBlocking::Blocking + } else { + CommandBlocking::PassingOn + } + } + fn event(&mut self, ev: Event) -> bool { if self.visible { if let Event::Key(k) = ev { @@ -335,24 +319,6 @@ impl Component for Revlog { false } - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - out.push(CommandInfo::new( - commands::SCROLL, - self.visible, - self.visible || force_all, - )); - - if self.visible { - CommandBlocking::Blocking - } else { - CommandBlocking::PassingOn - } - } - fn is_visible(&self) -> bool { self.visible } diff --git a/src/tabs/status.rs b/src/tabs/status.rs index 03b9c86b17..27b0a2f00b 100644 --- a/src/tabs/status.rs +++ b/src/tabs/status.rs @@ -1,3 +1,4 @@ +use crate::ui::style::Theme; use crate::{ accessors, components::{ @@ -99,6 +100,7 @@ impl Status { pub fn new( sender: &Sender, queue: &Queue, + theme: Theme, ) -> Self { Self { visible: true, @@ -109,14 +111,16 @@ impl Status { true, true, queue.clone(), + theme, ), index: ChangesComponent::new( strings::TITLE_INDEX, false, false, queue.clone(), + theme, ), - diff: DiffComponent::new(queue.clone()), + diff: DiffComponent::new(queue.clone(), theme), git_diff: AsyncDiff::new(sender.clone()), git_status: AsyncStatus::new(sender.clone()), } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 33a614dc81..02ba4332b6 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,10 +1,11 @@ mod scrolllist; - +pub(crate) mod style; +use crate::ui::style::Theme; use scrolllist::ScrollableList; +use tui::style::{Modifier, Style}; use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, - style::{Color, Modifier, Style}, widgets::{Block, Borders, Text}, Frame, }; @@ -76,24 +77,24 @@ pub fn draw_list<'b, B: Backend, L>( items: L, select: Option, selected: bool, + theme: Theme, ) where L: Iterator>, { - let mut style_border = Style::default().fg(Color::DarkGray); - let mut style_title = Style::default(); - if selected { - style_border = style_border.fg(Color::Gray); - style_title = style_title.modifier(Modifier::BOLD); - } + let style = if selected { + Style::default().modifier(Modifier::BOLD) + } else { + Style::default() + }; + let list = ScrollableList::new(items) .block( Block::default() .title(title) .borders(Borders::ALL) - .title_style(style_title) - .border_style(style_border), + .title_style(style) + .border_style(theme.block(selected)), ) - .scroll(select.unwrap_or_default()) - .style(Style::default().fg(Color::White)); + .scroll(select.unwrap_or_default()); f.render_widget(list, r) } diff --git a/src/ui/scrolllist.rs b/src/ui/scrolllist.rs index d301d76f97..36027879cd 100644 --- a/src/ui/scrolllist.rs +++ b/src/ui/scrolllist.rs @@ -38,11 +38,6 @@ where self } - pub fn style(mut self, style: Style) -> Self { - self.style = style; - self - } - pub fn scroll(mut self, index: usize) -> Self { self.scroll = index; self diff --git a/src/ui/style.rs b/src/ui/style.rs new file mode 100644 index 0000000000..92a7a89d5b --- /dev/null +++ b/src/ui/style.rs @@ -0,0 +1,275 @@ +use crate::get_app_config_path; +use asyncgit::{DiffLineType, StatusItemType}; +use ron::de::from_bytes; +use ron::ser::{to_string_pretty, PrettyConfig}; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::{Read, Write}; +use std::path::PathBuf; +use tui::style::{Color, Modifier, Style}; + +#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy)] +pub struct Theme { + selected_tab: ColorDef, + command_background: ColorDef, + command_disabled: ColorDef, + diff_line_add: ColorDef, + diff_line_delete: ColorDef, + diff_file_added: ColorDef, + diff_file_removed: ColorDef, + diff_file_moved: ColorDef, + diff_file_modified: ColorDef, + table_colors: [ColorDef; 3], +} + +#[derive(Copy, Clone, Debug)] +pub enum Mode { + Dark, + Light, +} + +impl Theme { + #[cfg(test)] + pub const fn empty() -> Self { + Self { + selected_tab: ColorDef::Reset, + command_background: ColorDef::Reset, + command_disabled: ColorDef::Reset, + diff_line_add: ColorDef::Reset, + diff_line_delete: ColorDef::Reset, + diff_file_added: ColorDef::Reset, + diff_file_removed: ColorDef::Reset, + diff_file_moved: ColorDef::Reset, + diff_file_modified: ColorDef::Reset, + table_colors: [ + ColorDef::Reset, + ColorDef::Reset, + ColorDef::Reset, + ], + } + } + + pub fn block(&self, focus: bool) -> Style { + if focus { + Style::default() + } else { + Style::default().fg(self.command_disabled.into()) + } + } + + pub fn tab(&self, selected: bool) -> Style { + if selected { + Style::default().fg(self.selected_tab.into()) + } else { + Style::default() + } + } + + pub fn text(&self, enabled: bool, selected: bool) -> Style { + match (enabled, selected) { + (false, _) => { + Style::default().fg(self.command_disabled.into()) + } + (true, false) => Style::default(), + (true, true) => { + Style::default().bg(self.command_background.into()) + } + } + } + + pub fn item(&self, typ: StatusItemType, selected: bool) -> Style { + let style = match typ { + StatusItemType::New => { + Style::default().fg(self.diff_file_added.into()) + } + StatusItemType::Modified => { + Style::default().fg(self.diff_file_modified.into()) + } + StatusItemType::Deleted => { + Style::default().fg(self.diff_file_removed.into()) + } + StatusItemType::Renamed => { + Style::default().fg(self.diff_file_moved.into()) + } + _ => Style::default(), + }; + + self.apply_select(style, selected) + } + + fn apply_select(&self, style: Style, selected: bool) -> Style { + if selected { + style.bg(self.command_background.into()) + } else { + style + } + } + + pub fn diff_line( + &self, + typ: DiffLineType, + selected: bool, + ) -> Style { + let style = match typ { + DiffLineType::Add => { + Style::default().fg(self.diff_line_add.into()) + } + DiffLineType::Delete => { + Style::default().fg(self.diff_line_delete.into()) + } + DiffLineType::Header => { + Style::default().modifier(Modifier::BOLD) + } + _ => Style::default(), + }; + + self.apply_select(style, selected) + } + + pub fn text_danger(&self) -> Style { + Style::default().fg(self.diff_file_removed.into()) + } + + pub fn toolbar(&self, enabled: bool) -> Style { + if enabled { + Style::default().bg(self.command_background.into()) + } else { + Style::default() + .bg(self.command_background.into()) + .fg(self.command_disabled.into()) + } + } + + pub fn table(&self, column: usize, selected: bool) -> Style { + self.apply_select( + Style::default().fg(self.table_colors[column].into()), + selected, + ) + } + + fn save(&self, mode: Mode) -> Result<(), std::io::Error> { + let theme_file = Self::get_theme_file(mode); + let mut file = File::create(theme_file)?; + let data = to_string_pretty(self, PrettyConfig::default()) + .map_err(|_| std::io::Error::from_raw_os_error(100))?; + file.write_all(data.as_bytes())?; + Ok(()) + } + + fn get_theme_file(mode: Mode) -> PathBuf { + let app_home = get_app_config_path(); + match mode { + Mode::Dark => app_home.join("dark.ron"), + Mode::Light => app_home.join("light.ron"), + } + } + + fn read_file( + theme_file: PathBuf, + ) -> Result { + if theme_file.exists() { + let mut f = File::open(theme_file)?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + + Ok(from_bytes(&buffer).map_err(|_| { + std::io::Error::from_raw_os_error(100) + })?) + } else { + Err(std::io::Error::from_raw_os_error(100)) + } + } + + pub fn init(mode: Mode) -> Theme { + if let Ok(x) = Theme::read_file(Theme::get_theme_file(mode)) { + x + } else { + let default_theme: Theme = match &mode { + Mode::Dark => from_bytes(include_bytes!( + "../../assets/themes/dark.ron" + )) + .unwrap_or_default(), + Mode::Light => from_bytes(include_bytes!( + "../../assets/themes/light.ron" + )) + .unwrap_or_default(), + }; + + default_theme.save(mode).unwrap_or_default(); + default_theme + } + } +} + +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] +enum ColorDef { + Reset, + Black, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + Gray, + DarkGray, + LightRed, + LightGreen, + LightYellow, + LightBlue, + LightMagenta, + LightCyan, + White, + Rgb(u8, u8, u8), + Indexed(u8), +} + +impl Default for ColorDef { + fn default() -> Self { + ColorDef::Reset + } +} + +impl From for Color { + fn from(def: ColorDef) -> Self { + match def { + ColorDef::Reset => Color::Reset, + ColorDef::Black => Color::Black, + ColorDef::Red => Color::Red, + ColorDef::Green => Color::Green, + ColorDef::Yellow => Color::Yellow, + ColorDef::Blue => Color::Blue, + ColorDef::Magenta => Color::Magenta, + ColorDef::Cyan => Color::Cyan, + ColorDef::Gray => Color::Gray, + ColorDef::DarkGray => Color::DarkGray, + ColorDef::LightRed => Color::LightRed, + ColorDef::LightGreen => Color::LightGreen, + ColorDef::LightYellow => Color::LightYellow, + ColorDef::LightBlue => Color::LightBlue, + ColorDef::LightMagenta => Color::LightMagenta, + ColorDef::LightCyan => Color::LightCyan, + ColorDef::White => Color::White, + ColorDef::Rgb(a, b, c) => Color::Rgb(a, b, c), + ColorDef::Indexed(x) => Color::Indexed(x), + } + } +} + +#[cfg(test)] +mod tests { + use crate::ui::style::Theme; + use ron::de::from_bytes; + + #[test] + fn verify_theme_files() { + from_bytes::(include_bytes!( + "../../assets/themes/dark.ron" + )) + .unwrap(); + from_bytes::(include_bytes!( + "../../assets/themes/light.ron" + )) + .unwrap(); + } +} From 6b8176f47e349afcb373d03d30dba62652be66b0 Mon Sep 17 00:00:00 2001 From: Mehran KORDI Date: Tue, 19 May 2020 12:37:37 +0200 Subject: [PATCH 02/11] make theme private in App. --- src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index cc483fd2d2..e3f2d68494 100644 --- a/src/app.rs +++ b/src/app.rs @@ -39,7 +39,7 @@ pub struct App { revlog: Revlog, status_tab: Status, queue: Queue, - pub theme: Theme, + theme: Theme, } // public interface From d0d827d1f8c43b1622db27e14c746e3c31b6873f Mon Sep 17 00:00:00 2001 From: Mehran KORDI Date: Tue, 19 May 2020 12:43:42 +0200 Subject: [PATCH 03/11] fixed a minor issue with tag background color when selected. --- src/tabs/revlog/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tabs/revlog/mod.rs b/src/tabs/revlog/mod.rs index ff6087c5dc..4a8f9586e0 100644 --- a/src/tabs/revlog/mod.rs +++ b/src/tabs/revlog/mod.rs @@ -185,7 +185,7 @@ impl Revlog { } else { String::from("") }), - theme.tab(true), + theme.tab(true).bg(theme.text(true, selected).bg), )); txt.push(splitter); txt.push(Text::Styled( From 2b66116dec80b56c247749095487be08c3899b7e Mon Sep 17 00:00:00 2001 From: Mehran KORDI Date: Tue, 19 May 2020 12:45:25 +0200 Subject: [PATCH 04/11] removed spinner::new --- src/main.rs | 2 +- src/spinner.rs | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 71a23553f1..65f01ca956 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,7 +82,7 @@ fn main() -> Result<()> { app.update(); draw(&mut terminal, &mut app)?; - let mut spinner = Spinner::new(); + let mut spinner = Spinner::default(); loop { let events: Vec = select_event( diff --git a/src/spinner.rs b/src/spinner.rs index 80204c23a9..31bdfb7be7 100644 --- a/src/spinner.rs +++ b/src/spinner.rs @@ -2,7 +2,7 @@ use std::io; use tui::{backend::Backend, buffer::Cell, Terminal}; static SPINNER_CHARS: &[char] = &['|', '/', '-', '\\']; - +#[derive(Default)] pub struct Spinner { idx: usize, } @@ -14,10 +14,6 @@ impl Spinner { self.idx %= SPINNER_CHARS.len(); } - pub fn new() -> Self { - Self { idx: 0 } - } - /// draws or removes spinner char depending on `pending` state pub fn draw( &self, From 7e0ed01ff6bfc91381ee1722c81c3dcf8e5b4387 Mon Sep 17 00:00:00 2001 From: Mehran KORDI Date: Tue, 19 May 2020 13:51:33 +0200 Subject: [PATCH 05/11] replaced theme resources with constants. --- assets/themes/dark.ron | 12 ------- assets/themes/light.ron | 12 ------- src/components/diff.rs | 2 +- src/ui/style.rs | 72 ++++++++++++++--------------------------- 4 files changed, 26 insertions(+), 72 deletions(-) delete mode 100644 assets/themes/dark.ron delete mode 100644 assets/themes/light.ron diff --git a/assets/themes/dark.ron b/assets/themes/dark.ron deleted file mode 100644 index 51afc21cbe..0000000000 --- a/assets/themes/dark.ron +++ /dev/null @@ -1,12 +0,0 @@ -( - selected_tab: Yellow, - command_background: Rgb(0,0,100), - command_disabled: Gray, - diff_line_add: Green, - diff_line_delete: Red, - diff_file_added: LightGreen, - diff_file_removed: LightRed, - diff_file_moved: LightMagenta, - diff_file_modified: Yellow, - table_colors: (Magenta, Blue, Green), -) \ No newline at end of file diff --git a/assets/themes/light.ron b/assets/themes/light.ron deleted file mode 100644 index bd7703a033..0000000000 --- a/assets/themes/light.ron +++ /dev/null @@ -1,12 +0,0 @@ -( - selected_tab: Yellow, - command_background: LightBlue, - command_disabled: Gray, - diff_line_add: Green, - diff_line_delete: Red, - diff_file_added: LightGreen, - diff_file_removed: LightRed, - diff_file_moved: LightMagenta, - diff_file_modified: Yellow, - table_colors: (Magenta, Blue, Green), -) \ No newline at end of file diff --git a/src/components/diff.rs b/src/components/diff.rs index 63b389bd4c..cdb04dbbfc 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -395,7 +395,7 @@ mod tests { false, false, false, - Theme::empty(), + crate::ui::style::DARK_THEME, ); assert_eq!(text.len(), 2); diff --git a/src/ui/style.rs b/src/ui/style.rs index 92a7a89d5b..b9f2d57ea8 100644 --- a/src/ui/style.rs +++ b/src/ui/style.rs @@ -22,6 +22,28 @@ pub struct Theme { table_colors: [ColorDef; 3], } +pub const DARK_THEME: Theme = Theme { + selected_tab: ColorDef::Yellow, + command_background: ColorDef::Rgb(0, 0, 100), + command_disabled: ColorDef::Gray, + diff_line_add: ColorDef::Green, + diff_line_delete: ColorDef::Red, + diff_file_added: ColorDef::LightGreen, + diff_file_removed: ColorDef::LightRed, + diff_file_moved: ColorDef::LightMagenta, + diff_file_modified: ColorDef::Yellow, + table_colors: [ + ColorDef::Magenta, + ColorDef::Blue, + ColorDef::Green, + ], +}; + +pub const LIGHT_THEME: Theme = Theme { + command_background: ColorDef::LightBlue, + ..DARK_THEME +}; + #[derive(Copy, Clone, Debug)] pub enum Mode { Dark, @@ -29,26 +51,6 @@ pub enum Mode { } impl Theme { - #[cfg(test)] - pub const fn empty() -> Self { - Self { - selected_tab: ColorDef::Reset, - command_background: ColorDef::Reset, - command_disabled: ColorDef::Reset, - diff_line_add: ColorDef::Reset, - diff_line_delete: ColorDef::Reset, - diff_file_added: ColorDef::Reset, - diff_file_removed: ColorDef::Reset, - diff_file_moved: ColorDef::Reset, - diff_file_modified: ColorDef::Reset, - table_colors: [ - ColorDef::Reset, - ColorDef::Reset, - ColorDef::Reset, - ], - } - } - pub fn block(&self, focus: bool) -> Style { if focus { Style::default() @@ -185,14 +187,8 @@ impl Theme { x } else { let default_theme: Theme = match &mode { - Mode::Dark => from_bytes(include_bytes!( - "../../assets/themes/dark.ron" - )) - .unwrap_or_default(), - Mode::Light => from_bytes(include_bytes!( - "../../assets/themes/light.ron" - )) - .unwrap_or_default(), + Mode::Dark => DARK_THEME, + Mode::Light => LIGHT_THEME, }; default_theme.save(mode).unwrap_or_default(); @@ -202,7 +198,7 @@ impl Theme { } #[derive(Serialize, Deserialize, Debug, Copy, Clone)] -enum ColorDef { +pub enum ColorDef { Reset, Black, Red, @@ -255,21 +251,3 @@ impl From for Color { } } } - -#[cfg(test)] -mod tests { - use crate::ui::style::Theme; - use ron::de::from_bytes; - - #[test] - fn verify_theme_files() { - from_bytes::(include_bytes!( - "../../assets/themes/dark.ron" - )) - .unwrap(); - from_bytes::(include_bytes!( - "../../assets/themes/light.ron" - )) - .unwrap(); - } -} From ab4d2a80ab3f2b3560a50478c87f17e52581ac5c Mon Sep 17 00:00:00 2001 From: Mehran KORDI Date: Tue, 19 May 2020 13:53:53 +0200 Subject: [PATCH 06/11] added a comment. --- src/ui/style.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/style.rs b/src/ui/style.rs index b9f2d57ea8..b6657806c4 100644 --- a/src/ui/style.rs +++ b/src/ui/style.rs @@ -197,6 +197,8 @@ impl Theme { } } +/// we duplicate the Color definition from `tui` crate to implement Serde serialisation +/// this enum can be removed once [tui-#292](https://github.com/fdehau/tui-rs/issues/292) is resolved #[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub enum ColorDef { Reset, From 9d0a0976424468d596634d6ceb79673d557d931a Mon Sep 17 00:00:00 2001 From: Mehran KORDI Date: Tue, 19 May 2020 15:33:56 +0200 Subject: [PATCH 07/11] fixed some colors --- src/components/diff.rs | 6 ++++-- src/ui/mod.rs | 6 +++--- src/ui/style.rs | 11 ++++++----- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/components/diff.rs b/src/components/diff.rs index cdb04dbbfc..5aa9985732 100644 --- a/src/components/diff.rs +++ b/src/components/diff.rs @@ -14,7 +14,7 @@ use strings::commands; use tui::{ backend::Backend, layout::{Alignment, Rect}, - style::{Modifier, Style}, + style::Modifier, symbols, widgets::{Block, Borders, Paragraph, Text}, Frame, @@ -285,7 +285,9 @@ impl DrawableComponent for DiffComponent { .borders(Borders::ALL) .border_style(self.theme.block(self.focused)) .title_style( - Style::default().modifier(Modifier::BOLD), + self.theme + .text(self.focused, false) + .modifier(Modifier::BOLD), ), ) .alignment(Alignment::Left), diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 02ba4332b6..f829b7a187 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -2,7 +2,7 @@ mod scrolllist; pub(crate) mod style; use crate::ui::style::Theme; use scrolllist::ScrollableList; -use tui::style::{Modifier, Style}; +use tui::style::Modifier; use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, @@ -82,9 +82,9 @@ pub fn draw_list<'b, B: Backend, L>( L: Iterator>, { let style = if selected { - Style::default().modifier(Modifier::BOLD) + theme.block(selected).modifier(Modifier::BOLD) } else { - Style::default() + theme.block(selected) }; let list = ScrollableList::new(items) diff --git a/src/ui/style.rs b/src/ui/style.rs index b6657806c4..81a4259588 100644 --- a/src/ui/style.rs +++ b/src/ui/style.rs @@ -11,6 +11,7 @@ use tui::style::{Color, Modifier, Style}; #[derive(Serialize, Deserialize, Debug, Default, Clone, Copy)] pub struct Theme { selected_tab: ColorDef, + command_foreground: ColorDef, command_background: ColorDef, command_disabled: ColorDef, diff_line_add: ColorDef, @@ -24,8 +25,9 @@ pub struct Theme { pub const DARK_THEME: Theme = Theme { selected_tab: ColorDef::Yellow, + command_foreground: ColorDef::White, command_background: ColorDef::Rgb(0, 0, 100), - command_disabled: ColorDef::Gray, + command_disabled: ColorDef::DarkGray, diff_line_add: ColorDef::Green, diff_line_delete: ColorDef::Red, diff_file_added: ColorDef::LightGreen, @@ -134,12 +136,11 @@ impl Theme { pub fn toolbar(&self, enabled: bool) -> Style { if enabled { - Style::default().bg(self.command_background.into()) + Style::default().fg(self.command_foreground.into()) } else { - Style::default() - .bg(self.command_background.into()) - .fg(self.command_disabled.into()) + Style::default().fg(self.command_disabled.into()) } + .bg(self.command_background.into()) } pub fn table(&self, column: usize, selected: bool) -> Style { From 581514cd8f738d3f634af8ee9b07e04b36eb6d5b Mon Sep 17 00:00:00 2001 From: Mehran KORDI Date: Tue, 19 May 2020 18:38:31 +0200 Subject: [PATCH 08/11] removed modes. --- src/app.rs | 9 +++------ src/main.rs | 8 +------- src/ui/style.rs | 35 ++++++++--------------------------- 3 files changed, 12 insertions(+), 40 deletions(-) diff --git a/src/app.rs b/src/app.rs index e3f2d68494..6714c69c5a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,4 @@ -use crate::ui::style::{Mode, Theme}; +use crate::ui::style::Theme; use crate::{ accessors, components::{ @@ -45,13 +45,10 @@ pub struct App { // public interface impl App { /// - pub fn new( - sender: &Sender, - mode: Mode, - ) -> Self { + pub fn new(sender: &Sender) -> Self { let queue = Queue::default(); - let theme = Theme::init(mode); + let theme = Theme::init(); Self { reset: ResetComponent::new(queue.clone(), theme), diff --git a/src/main.rs b/src/main.rs index 65f01ca956..7115ace041 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,6 @@ mod tabs; mod ui; mod version; -use crate::ui::style::Mode; use crate::{app::App, poll::QueueEvent}; use asyncgit::AsyncNotification; use backtrace::Backtrace; @@ -68,12 +67,7 @@ fn main() -> Result<()> { let (tx_git, rx_git) = unbounded(); - let mode = match env::var("GITUI_LIGHT") { - Ok(_) => Mode::Light, - _ => Mode::Dark, - }; - - let mut app = App::new(&tx_git, mode); + let mut app = App::new(&tx_git); let rx_input = poll::start_polling_thread(); let ticker = tick(TICK_INTERVAL); diff --git a/src/ui/style.rs b/src/ui/style.rs index 81a4259588..aaee2ad146 100644 --- a/src/ui/style.rs +++ b/src/ui/style.rs @@ -41,17 +41,6 @@ pub const DARK_THEME: Theme = Theme { ], }; -pub const LIGHT_THEME: Theme = Theme { - command_background: ColorDef::LightBlue, - ..DARK_THEME -}; - -#[derive(Copy, Clone, Debug)] -pub enum Mode { - Dark, - Light, -} - impl Theme { pub fn block(&self, focus: bool) -> Style { if focus { @@ -150,8 +139,8 @@ impl Theme { ) } - fn save(&self, mode: Mode) -> Result<(), std::io::Error> { - let theme_file = Self::get_theme_file(mode); + fn save(&self) -> Result<(), std::io::Error> { + let theme_file = Self::get_theme_file(); let mut file = File::create(theme_file)?; let data = to_string_pretty(self, PrettyConfig::default()) .map_err(|_| std::io::Error::from_raw_os_error(100))?; @@ -159,12 +148,9 @@ impl Theme { Ok(()) } - fn get_theme_file(mode: Mode) -> PathBuf { + fn get_theme_file() -> PathBuf { let app_home = get_app_config_path(); - match mode { - Mode::Dark => app_home.join("dark.ron"), - Mode::Light => app_home.join("light.ron"), - } + app_home.join("theme.ron") } fn read_file( @@ -183,17 +169,12 @@ impl Theme { } } - pub fn init(mode: Mode) -> Theme { - if let Ok(x) = Theme::read_file(Theme::get_theme_file(mode)) { + pub fn init() -> Theme { + if let Ok(x) = Theme::read_file(Theme::get_theme_file()) { x } else { - let default_theme: Theme = match &mode { - Mode::Dark => DARK_THEME, - Mode::Light => LIGHT_THEME, - }; - - default_theme.save(mode).unwrap_or_default(); - default_theme + DARK_THEME.save().unwrap_or_default(); + DARK_THEME } } } From d7130f331bc37accbe5cfdf5cbb30e2a244c11d9 Mon Sep 17 00:00:00 2001 From: Mehran KORDI Date: Tue, 19 May 2020 18:47:03 +0200 Subject: [PATCH 09/11] reverted some useless changes. --- src/app.rs | 2 +- src/components/msg.rs | 8 +------- src/spinner.rs | 2 ++ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/app.rs b/src/app.rs index 6714c69c5a..e1ce02c5d0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -56,7 +56,7 @@ impl App { do_quit: false, current_commands: Vec::new(), help: HelpComponent::new(theme), - msg: MsgComponent::new(), + msg: MsgComponent::default(), tab: 0, revlog: Revlog::new(&sender, theme), status_tab: Status::new(&sender, &queue, theme), diff --git a/src/components/msg.rs b/src/components/msg.rs index 408c7c277b..bf8daacdf5 100644 --- a/src/components/msg.rs +++ b/src/components/msg.rs @@ -14,6 +14,7 @@ use tui::{ Frame, }; +#[derive(Default)] pub struct MsgComponent { msg: String, visible: bool, @@ -83,11 +84,4 @@ impl MsgComponent { self.msg = msg.to_string(); self.show(); } - - pub fn new() -> Self { - Self { - msg: "".to_string(), - visible: false, - } - } } diff --git a/src/spinner.rs b/src/spinner.rs index 31bdfb7be7..204b7a74e5 100644 --- a/src/spinner.rs +++ b/src/spinner.rs @@ -2,6 +2,8 @@ use std::io; use tui::{backend::Backend, buffer::Cell, Terminal}; static SPINNER_CHARS: &[char] = &['|', '/', '-', '\\']; + +/// #[derive(Default)] pub struct Spinner { idx: usize, From 29546002180545d9c007179f28e4758bac017697 Mon Sep 17 00:00:00 2001 From: Mehran KORDI Date: Tue, 19 May 2020 18:49:56 +0200 Subject: [PATCH 10/11] reverted some useless changes. --- src/tabs/revlog/mod.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/tabs/revlog/mod.rs b/src/tabs/revlog/mod.rs index 4a8f9586e0..982f25f955 100644 --- a/src/tabs/revlog/mod.rs +++ b/src/tabs/revlog/mod.rs @@ -265,24 +265,6 @@ impl DrawableComponent for Revlog { } impl Component for Revlog { - fn commands( - &self, - out: &mut Vec, - force_all: bool, - ) -> CommandBlocking { - out.push(CommandInfo::new( - commands::SCROLL, - self.visible, - self.visible || force_all, - )); - - if self.visible { - CommandBlocking::Blocking - } else { - CommandBlocking::PassingOn - } - } - fn event(&mut self, ev: Event) -> bool { if self.visible { if let Event::Key(k) = ev { @@ -319,6 +301,24 @@ impl Component for Revlog { false } + fn commands( + &self, + out: &mut Vec, + force_all: bool, + ) -> CommandBlocking { + out.push(CommandInfo::new( + commands::SCROLL, + self.visible, + self.visible || force_all, + )); + + if self.visible { + CommandBlocking::Blocking + } else { + CommandBlocking::PassingOn + } + } + fn is_visible(&self) -> bool { self.visible } From c083bdd0bf9c167130dbfa9a3da1a1e720498986 Mon Sep 17 00:00:00 2001 From: Mehran KORDI Date: Tue, 19 May 2020 19:52:49 +0200 Subject: [PATCH 11/11] updated doc. --- README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ee3465baec..3abdb6b451 100644 --- a/README.md +++ b/README.md @@ -82,17 +82,11 @@ this will log to: # color theme -two different color themes are supported for light and dark mode. to change the colors you have to modify files in -[Ron format](https://github.com/ron-rs/ron) located at git config path (same as log paths). the list of valid +to change the colors of the program you have to modify `theme.ron` file +[Ron format](https://github.com/ron-rs/ron) located at config path (same as log paths). the list of valid colors can be found in [ColorDef](./src/ui/style.rs#ColorDef) struct. note that rgb colors might not be available on some platforms. -to enable light mode: -``` -GITUI_LIGHT=true gitui -``` - - # inspiration * https://github.com/jesseduffield/lazygit