Skip to content

Commit

Permalink
Make colors customizable
Browse files Browse the repository at this point in the history
Primary, secondary, and error colors can be customized. More things to be added in the future (e.g. pane sizing). Also, change default color schema to blue+yellow.

Closes #193
  • Loading branch information
LucasPickering committed May 4, 2024
1 parent bf4c9b8 commit ce56ac9
Show file tree
Hide file tree
Showing 22 changed files with 205 additions and 138 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

- Add option to chain values from response header rather than body ([#184](https://github.com/LucasPickering/slumber/issues/184))
- Add action to save response body to file ([#183](https://github.com/LucasPickering/slumber/issues/183))
- Add `theme` field to the config, to configure colors ([#193](https://github.com/LucasPickering/slumber/issues/193))
- [See docs](https://slumber.lucaspickering.me/book/api/configuration/theme.html) for more info

### Changed

Expand Down
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ nom = "7.1.3"
notify = {version = "^6.1.1", default-features = false, features = ["macos_fsevent"]}
open = "5.1.1"
pretty_assertions = "1.4.0"
ratatui = {version = "^0.26.0", features = ["unstable-rendered-line-info"]}
ratatui = {version = "^0.26.0", features = ["serde", "unstable-rendered-line-info"]}
reqwest = {version = "^0.11.20", default-features = false, features = ["rustls-tls"]}
rmp-serde = "^1.1.2"
rusqlite = {version = "^0.30.0", default-features = false, features = ["bundled", "chrono", "uuid"]}
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
- [Content Type](./api/request_collection/content_type.md)
- [Configuration](./api/configuration/index.md)
- [Input Bindings](./api/configuration/input_bindings.md)
- [Theme](./api/configuration/theme.md)

# Troubleshooting

Expand Down
1 change: 1 addition & 0 deletions docs/src/api/configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ If the root directory doesn't exist yet, you can create it yourself or have Slum
| `preview_templates` | `boolean` | Render template values in the TUI? If false, the raw template will be shown. | `true` |
| `ignore_certificate_hosts` | `string[]` | Hostnames whose TLS certificate errors will be ignored. [More info](../../troubleshooting/tls.md) | `[]` |
| `input_bindings` | `mapping[Action, KeyCombination[]]` | Override default input bindings. [More info](./input_bindings.md) | `{}` |
| `theme` | [`Theme`](./theme.md) | Visual customizations | `{}` |
21 changes: 21 additions & 0 deletions docs/src/api/configuration/theme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Theme

Theming allows you to customize the appearance of the Slumber TUI. To start, [open up your configuration file](../configuration/index.md#location--creation) and add some theme settings:

```yaml
theme:
primary_color: green
secondary_color: blue
```
## Fields
| Field | Type | Description |
| ----------------- | ------- | ---------------------------------- |
| `primary_color` | `Color` | Color of most emphasized content |
| `secondary_color` | `Color` | Color of secondary notable content |
| `error_color` | `Color` | Color representing error messages |

## Color Format

Colors can be specified as names (e.g. "yellow"), RGB codes (e.g. `#ffff00`) or ANSI color indexes. See the [Ratatui docs](https://docs.rs/ratatui/latest/ratatui/style/enum.Color.html#impl-FromStr-for-Color) for more details on color deserialization.
9 changes: 7 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{
tui::input::{Action, InputBinding},
tui::{
input::{Action, InputBinding},
view::Theme,
},
util::{
parse_yaml,
paths::{DataDirectory, FileGuard},
Expand All @@ -25,9 +28,10 @@ pub struct Config {
/// Should templates be rendered inline in the UI, or should we show the
/// raw text?
pub preview_templates: bool,

/// Overrides for default key bindings
pub input_bindings: IndexMap<Action, InputBinding>,
/// Visual configuration for the TUI (e.g. colors)
pub theme: Theme,
}

impl Config {
Expand Down Expand Up @@ -70,6 +74,7 @@ impl Default for Config {
ignore_certificate_hosts: Vec::new(),
preview_templates: true,
input_bindings: IndexMap::default(),
theme: Theme::default(),
}
}
}
2 changes: 1 addition & 1 deletion src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pub mod context;
pub mod input;
pub mod message;
mod util;
mod view;
pub mod view;

use crate::{
collection::{Collection, CollectionFile, ProfileId, RecipeId},
Expand Down
9 changes: 5 additions & 4 deletions src/tui/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
config::Config,
db::CollectionDatabase,
http::HttpEngine,
tui::{input::InputEngine, view::Theme},
tui::{input::InputEngine, view::Styles},
};
use std::sync::OnceLock;

Expand All @@ -22,8 +22,8 @@ static CONTEXT: OnceLock<TuiContext> = OnceLock::new();
pub struct TuiContext {
/// App-level configuration
pub config: Config,
/// Visual theme. Colors!
pub theme: Theme,
/// Visual styles, derived from the theme
pub styles: Styles,
/// Input:action bindings
pub input_engine: InputEngine,
/// For sending HTTP requests
Expand All @@ -36,12 +36,13 @@ pub struct TuiContext {
impl TuiContext {
/// Initialize global context. Should be called only once, during startup.
pub fn init(config: Config, database: CollectionDatabase) {
let styles = Styles::new(&config.theme);
let input_engine = InputEngine::new(config.input_bindings.clone());
let http_engine = HttpEngine::new(&config, database.clone());
CONTEXT
.set(Self {
config,
theme: Theme::default(),
styles,
input_engine,
http_engine,
database,
Expand Down
2 changes: 1 addition & 1 deletion src/tui/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod util;

pub use common::modal::{IntoModal, ModalPriority};
pub use state::RequestState;
pub use theme::Theme;
pub use theme::{Styles, Theme};
pub use util::{Confirm, PreviewPrompter};

use crate::{
Expand Down
2 changes: 1 addition & 1 deletion src/tui/view/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl<'a> Generate for Pane<'a> {
Self: 'this,
{
let (border_type, border_style) =
TuiContext::get().theme.pane.border(self.is_focused);
TuiContext::get().styles.pane.border(self.is_focused);
Block::default()
.borders(Borders::ALL)
.border_type(border_type)
Expand Down
4 changes: 2 additions & 2 deletions src/tui/view/common/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ impl<'a> Generate for Button<'a> {
where
Self: 'this,
{
let theme = &TuiContext::get().theme;
let styles = &TuiContext::get().styles;
Span {
content: self.text.into(),
style: if self.is_focused {
theme.text.highlight
styles.text.highlight
} else {
Default::default()
},
Expand Down
2 changes: 1 addition & 1 deletion src/tui/view/common/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ where

ratatui::widgets::List::new(items)
.block(block)
.highlight_style(TuiContext::get().theme.list.highlight)
.highlight_style(TuiContext::get().styles.list.highlight)
}
}
6 changes: 3 additions & 3 deletions src/tui/view/common/modal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ impl EventHandler for ModalQueue {
impl Draw for ModalQueue {
fn draw(&self, frame: &mut Frame, _: (), area: Rect) {
if let Some(modal) = self.queue.front() {
let theme = &TuiContext::get().theme;
let styles = &TuiContext::get().styles;
let (width, height) = modal.dimensions();

// The child gave us the content dimensions, we need to add one cell
Expand All @@ -150,8 +150,8 @@ impl Draw for ModalQueue {
let block = Block::default()
.title(modal.title())
.borders(Borders::ALL)
.border_style(theme.modal.border)
.border_type(theme.modal.border_type);
.border_style(styles.modal.border)
.border_type(styles.modal.border_type);
let inner_area = block.inner(area);

// Draw the outline of the modal
Expand Down
20 changes: 11 additions & 9 deletions src/tui/view/common/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,30 +75,32 @@ impl<'a, const COLS: usize> Generate for Table<'a, COLS, Row<'a>> {
where
Self: 'this,
{
let theme = &TuiContext::get().theme;
let styles = &TuiContext::get().styles;
let rows = self.rows.into_iter().enumerate().map(|(i, row)| {
// Apply theme styles, but let the row's individual styles override
let base_style = if self.alternate_row_style && i % 2 == 1 {
theme.table.alt
styles.table.alt
} else {
theme.table.text
styles.table.text
};
let row_style = Styled::style(&row);
row.set_style(base_style.patch(row_style))
});
let mut table = ratatui::widgets::Table::new(rows, self.column_widths)
.highlight_style(theme.table.highlight);
.highlight_style(styles.table.highlight);

// Add title
if let Some(title) = self.title {
table = table.block(
Block::default().title(title).title_style(theme.table.title),
Block::default()
.title(title)
.title_style(styles.table.title),
);
}

// Add optional header if given
if let Some(header) = self.header {
table = table.header(Row::new(header).style(theme.table.header));
table = table.header(Row::new(header).style(styles.table.header));
}

table
Expand Down Expand Up @@ -139,7 +141,7 @@ where
where
Self: 'this,
{
let theme = &TuiContext::get().theme;
let styles = &TuiContext::get().styles;
// Include the given cells, then tack on the checkbox for enabled state
Row::new(
iter::once(
Expand All @@ -152,9 +154,9 @@ where
.chain(self.cells.into_iter().map(Cell::from)),
)
.style(if self.enabled {
theme.table.text
styles.table.text
} else {
theme.table.disabled
styles.table.disabled
})
}
}
2 changes: 1 addition & 1 deletion src/tui/view/common/tabs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ where
frame.render_widget(
ratatui::widgets::Tabs::new(T::iter().map(|e| e.to_string()))
.select(self.tabs.selected_index())
.highlight_style(TuiContext::get().theme.tab.highlight),
.highlight_style(TuiContext::get().styles.tab.highlight),
area,
)
}
Expand Down
12 changes: 6 additions & 6 deletions src/tui/view/common/template_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ impl<'a> TextStitcher<'a> {
template: &'a Template,
chunks: &'a [TemplateChunk],
) -> Text<'a> {
let theme = &TuiContext::get().theme;
let styles = &TuiContext::get().styles;

// Each chunk will get its own styling, but we can't just make each
// chunk a Span, because one chunk might have multiple lines. And we
Expand All @@ -122,8 +122,8 @@ impl<'a> TextStitcher<'a> {
let chunk_text = Self::get_chunk_text(template, chunk);
let style = match &chunk {
TemplateChunk::Raw(_) => Style::default(),
TemplateChunk::Rendered { .. } => theme.template_preview.text,
TemplateChunk::Error(_) => theme.template_preview.error,
TemplateChunk::Rendered { .. } => styles.template_preview.text,
TemplateChunk::Error(_) => styles.template_preview.error,
};

stitcher.add_chunk(chunk_text, style);
Expand Down Expand Up @@ -230,11 +230,11 @@ mod tests {
selected_profile: Some(profile_id),
);
let chunks = template.render_chunks(&context).await;
let theme = &TuiContext::get().theme;
let styles = &TuiContext::get().styles;

let text = TextStitcher::stitch_chunks(&template, &chunks);
let rendered_style = theme.template_preview.text;
let error_style = theme.template_preview.error;
let rendered_style = styles.template_preview.text;
let error_style = styles.template_preview.error;
let expected = Text::from(vec![
Line::from("intro"),
Line::from(Span::styled("🧡", rendered_style)),
Expand Down
10 changes: 5 additions & 5 deletions src/tui/view/common/text_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,12 @@ impl EventHandler for TextBox {

impl Draw for TextBox {
fn draw(&self, frame: &mut Frame, _: (), area: Rect) {
let theme = &TuiContext::get().theme;
let styles = &TuiContext::get().styles;

// Hide top secret data
let text: Text = if self.state.text.is_empty() {
Line::from(self.placeholder_text.as_str())
.style(theme.text_box.placeholder)
.style(styles.text_box.placeholder)
.into()
} else if self.sensitive {
Masked::new(&self.state.text, '•').into()
Expand All @@ -230,9 +230,9 @@ impl Draw for TextBox {

// Draw the text
let style = if self.is_valid() {
theme.text_box.text
styles.text_box.text
} else {
theme.text_box.invalid
styles.text_box.invalid
};
frame.render_widget(Paragraph::new(text).style(style), area);

Expand All @@ -246,7 +246,7 @@ impl Draw for TextBox {
};
frame
.buffer_mut()
.set_style(cursor_area, theme.text_box.cursor);
.set_style(cursor_area, styles.text_box.cursor);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/tui/view/common/text_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ where
for<'a> &'a T: Generate<Output<'a> = Text<'a>>,
{
fn draw(&self, frame: &mut Frame, _: (), area: Rect) {
let theme = &TuiContext::get().theme;
let styles = &TuiContext::get().styles;
let text = Paragraph::new(self.text.generate());
// Assume no line wrapping when calculating line count
let text_height = text.line_count(u16::MAX) as u16;
Expand Down Expand Up @@ -146,7 +146,7 @@ where
.collect::<Vec<Line>>(),
)
.alignment(Alignment::Right)
.style(theme.text_window.line_number),
.style(styles.text_window.line_number),
gutter_area,
);

Expand Down
2 changes: 1 addition & 1 deletion src/tui/view/component/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl Draw for HelpFooter {
frame.render_widget(
Paragraph::new(text)
.alignment(Alignment::Right)
.style(tui_context.theme.text.highlight),
.style(tui_context.styles.text.highlight),
area,
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/tui/view/component/recipe_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ impl Draw<RecipeListPaneProps> for RecipeListPane {
.collect_vec();
let list = ratatui::widgets::List::new(items)
.block(pane.generate())
.highlight_style(context.theme.list.highlight);
.highlight_style(context.styles.list.highlight);

frame.render_stateful_widget(
list,
Expand Down
Loading

0 comments on commit ce56ac9

Please sign in to comment.