mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 01:18:21 +00:00
parent
e73cdb67bc
commit
4ec1a4e94b
18 changed files with 470 additions and 231 deletions
|
|
@ -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
|
||||
|
|
|
|||
50
Cargo.lock
generated
50
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -80,6 +80,13 @@ this will log to:
|
|||
* `$XDG_CACHE_HOME/gitui/gitui.log` (linux using `XDG`)
|
||||
* `$HOME/.cache/gitui/gitui.log` (linux)
|
||||
|
||||
# color theme
|
||||
|
||||
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.
|
||||
|
||||
# inspiration
|
||||
|
||||
* https://github.com/jesseduffield/lazygit
|
||||
|
|
|
|||
37
src/app.rs
37
src/app.rs
|
|
@ -1,3 +1,4 @@
|
|||
use crate::ui::style::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,6 +39,7 @@ pub struct App {
|
|||
revlog: Revlog,
|
||||
status_tab: Status,
|
||||
queue: Queue,
|
||||
theme: Theme,
|
||||
}
|
||||
|
||||
// public interface
|
||||
|
|
@ -44,17 +47,21 @@ impl App {
|
|||
///
|
||||
pub fn new(sender: &Sender<AsyncNotification>) -> Self {
|
||||
let queue = Queue::default();
|
||||
|
||||
let theme = Theme::init();
|
||||
|
||||
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(),
|
||||
help: HelpComponent::new(theme),
|
||||
msg: MsgComponent::default(),
|
||||
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 +91,7 @@ impl App {
|
|||
f,
|
||||
chunks_main[2],
|
||||
self.current_commands.as_slice(),
|
||||
self.theme,
|
||||
);
|
||||
|
||||
self.draw_popups(f);
|
||||
|
|
@ -320,10 +328,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 +343,20 @@ impl App {
|
|||
f: &mut Frame<B>,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<Text> {
|
||||
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<StatusItemType>) -> 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
symbols,
|
||||
widgets::{Block, Borders, Paragraph, Text},
|
||||
Frame,
|
||||
|
|
@ -34,11 +36,12 @@ pub struct DiffComponent {
|
|||
current: Current,
|
||||
selected_hunk: Option<u16>,
|
||||
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<B: Backend>(&mut self, f: &mut Frame<B>, 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,12 @@ 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(
|
||||
self.theme
|
||||
.text(self.focused, false)
|
||||
.modifier(Modifier::BOLD),
|
||||
),
|
||||
)
|
||||
.alignment(Alignment::Left),
|
||||
r,
|
||||
|
|
@ -414,7 +387,6 @@ mod tests {
|
|||
#[test]
|
||||
fn test_lineendings() {
|
||||
let mut text = Vec::new();
|
||||
|
||||
DiffComponent::add_line(
|
||||
&mut text,
|
||||
10,
|
||||
|
|
@ -425,6 +397,7 @@ mod tests {
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
crate::ui::style::DARK_THEME,
|
||||
);
|
||||
|
||||
assert_eq!(text.len(), 2);
|
||||
|
|
|
|||
|
|
@ -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<CommandInfo>,
|
||||
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<CommandInfo>) {
|
||||
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::<Vec<_>>(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<Item = &'t Text<'t>>,
|
||||
{
|
||||
Paragraph::new(content)
|
||||
.block(Block::default().title(title).borders(Borders::ALL))
|
||||
.alignment(Alignment::Left)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@ 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,
|
||||
};
|
||||
|
||||
|
|
@ -27,14 +28,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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ResetItem>,
|
||||
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,
|
||||
}
|
||||
}
|
||||
///
|
||||
|
|
|
|||
13
src/main.rs
13
src/main.rs
|
|
@ -31,6 +31,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,
|
||||
|
|
@ -175,12 +176,18 @@ fn start_terminal<W: Write>(
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -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<AsyncNotification>) -> Self {
|
||||
pub fn new(
|
||||
sender: &Sender<AsyncNotification>,
|
||||
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<Text<'a>>,
|
||||
tags: Option<String>,
|
||||
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).bg(theme.text(true, selected).bg),
|
||||
));
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::ui::style::Theme;
|
||||
use crate::{
|
||||
accessors,
|
||||
components::{
|
||||
|
|
@ -99,6 +100,7 @@ impl Status {
|
|||
pub fn new(
|
||||
sender: &Sender<AsyncNotification>,
|
||||
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()),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
mod scrolllist;
|
||||
|
||||
pub(crate) mod style;
|
||||
use crate::ui::style::Theme;
|
||||
use scrolllist::ScrollableList;
|
||||
use tui::style::Modifier;
|
||||
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<usize>,
|
||||
selected: bool,
|
||||
theme: Theme,
|
||||
) where
|
||||
L: Iterator<Item = Text<'b>>,
|
||||
{
|
||||
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 {
|
||||
theme.block(selected).modifier(Modifier::BOLD)
|
||||
} else {
|
||||
theme.block(selected)
|
||||
};
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
237
src/ui/style.rs
Normal file
237
src/ui/style.rs
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
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_foreground: 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],
|
||||
}
|
||||
|
||||
pub const DARK_THEME: Theme = Theme {
|
||||
selected_tab: ColorDef::Yellow,
|
||||
command_foreground: ColorDef::White,
|
||||
command_background: ColorDef::Rgb(0, 0, 100),
|
||||
command_disabled: ColorDef::DarkGray,
|
||||
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,
|
||||
],
|
||||
};
|
||||
|
||||
impl Theme {
|
||||
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().fg(self.command_foreground.into())
|
||||
} else {
|
||||
Style::default().fg(self.command_disabled.into())
|
||||
}
|
||||
.bg(self.command_background.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) -> 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))?;
|
||||
file.write_all(data.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_theme_file() -> PathBuf {
|
||||
let app_home = get_app_config_path();
|
||||
app_home.join("theme.ron")
|
||||
}
|
||||
|
||||
fn read_file(
|
||||
theme_file: PathBuf,
|
||||
) -> Result<Theme, std::io::Error> {
|
||||
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() -> Theme {
|
||||
if let Ok(x) = Theme::read_file(Theme::get_theme_file()) {
|
||||
x
|
||||
} else {
|
||||
DARK_THEME.save().unwrap_or_default();
|
||||
DARK_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,
|
||||
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<ColorDef> 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue