[WIP] Daemon & CLI
This commit is contained in:
parent
56aad3a023
commit
8b158108bb
100 changed files with 6519 additions and 1596 deletions
163
iota-cli/src/theme/config.rs
Normal file
163
iota-cli/src/theme/config.rs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
use super::ThemeName;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct UiConfig {
|
||||
#[serde(default)]
|
||||
pub theme: ThemeName,
|
||||
/// Whether opening the interactive UI should launch a locally installed daemon.
|
||||
#[serde(default)]
|
||||
pub daemon_start_policy: DaemonStartPolicy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum DaemonStartPolicy {
|
||||
#[default]
|
||||
Ask,
|
||||
WithUi,
|
||||
}
|
||||
impl Serialize for DaemonStartPolicy {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
Self::Ask => serializer.serialize_str("ask"),
|
||||
Self::WithUi => serializer.serialize_str("with_ui"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for DaemonStartPolicy {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum Compat {
|
||||
Policy(String),
|
||||
Legacy(bool),
|
||||
}
|
||||
match Compat::deserialize(deserializer)? {
|
||||
Compat::Policy(v) if v == "with_ui" || v == "WithUi" => Ok(Self::WithUi),
|
||||
Compat::Policy(_) => Ok(Self::Ask),
|
||||
Compat::Legacy(true) => Ok(Self::WithUi),
|
||||
Compat::Legacy(false) => Ok(Self::Ask),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl UiConfig {
|
||||
pub fn path() -> PathBuf {
|
||||
iota_paths::config_dir().join("ui.yaml")
|
||||
}
|
||||
pub fn load() -> Result<Self, io::Error> {
|
||||
Self::load_from(&Self::path())
|
||||
}
|
||||
|
||||
fn load_from(path: &Path) -> Result<Self, io::Error> {
|
||||
if !path.exists() {
|
||||
return Ok(Self::default());
|
||||
}
|
||||
serde_yaml::from_str(&fs::read_to_string(path)?).map_err(io::Error::other)
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<(), io::Error> {
|
||||
let path = Self::path();
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let yaml = serde_yaml::to_string(self).map_err(io::Error::other)?;
|
||||
fs::write(path, yaml)
|
||||
}
|
||||
|
||||
pub fn resolve_theme(override_theme: Option<ThemeName>) -> ThemeName {
|
||||
Self::resolve_theme_from(
|
||||
override_theme,
|
||||
std::env::var("IOTA_THEME").ok().as_deref(),
|
||||
&Self::path(),
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_theme_from(
|
||||
override_theme: Option<ThemeName>,
|
||||
environment_theme: Option<&str>,
|
||||
config_path: &Path,
|
||||
) -> ThemeName {
|
||||
if let Some(theme) = override_theme {
|
||||
return theme;
|
||||
}
|
||||
if let Some(value) = environment_theme {
|
||||
match ThemeName::from_str(value) {
|
||||
Ok(theme) => return theme,
|
||||
Err(error) => {
|
||||
eprintln!("Invalid IOTA_THEME value: {error}; checking UI configuration.");
|
||||
}
|
||||
}
|
||||
}
|
||||
match Self::load_from(config_path) {
|
||||
Ok(config) => config.theme,
|
||||
Err(error) => {
|
||||
eprintln!(
|
||||
"Could not read UI configuration {}: {error}; using ansi.",
|
||||
config_path.display()
|
||||
);
|
||||
ThemeName::Ansi
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn config_path(name: &str) -> PathBuf {
|
||||
std::env::temp_dir().join(format!("iota-ui-config-{}-{name}.yaml", std::process::id()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_line_override_has_highest_precedence() {
|
||||
let path = config_path("override");
|
||||
fs::write(&path, "theme: surface\n").unwrap();
|
||||
let resolved =
|
||||
UiConfig::resolve_theme_from(Some(ThemeName::Binary), Some("monospace"), &path);
|
||||
fs::remove_file(path).unwrap();
|
||||
assert_eq!(resolved, ThemeName::Binary);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn environment_precedes_stored_configuration() {
|
||||
let path = config_path("environment");
|
||||
fs::write(&path, "theme: surface\n").unwrap();
|
||||
let resolved = UiConfig::resolve_theme_from(None, Some("monospace"), &path);
|
||||
fs::remove_file(path).unwrap();
|
||||
assert_eq!(resolved, ThemeName::Monospace);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stored_configuration_precedes_default() {
|
||||
let path = config_path("stored");
|
||||
fs::write(&path, "theme: surface\n").unwrap();
|
||||
let resolved = UiConfig::resolve_theme_from(None, None, &path);
|
||||
fs::remove_file(path).unwrap();
|
||||
assert_eq!(resolved, ThemeName::Surface);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_stored_configuration_falls_back_to_ansi() {
|
||||
let path = config_path("invalid");
|
||||
fs::write(&path, "theme: ultraviolet\n").unwrap();
|
||||
let resolved = UiConfig::resolve_theme_from(None, None, &path);
|
||||
fs::remove_file(path).unwrap();
|
||||
assert_eq!(resolved, ThemeName::Ansi);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_configuration_falls_back_to_ansi() {
|
||||
let path = config_path("missing");
|
||||
let _ = fs::remove_file(&path);
|
||||
assert_eq!(
|
||||
UiConfig::resolve_theme_from(None, None, &path),
|
||||
ThemeName::Ansi
|
||||
);
|
||||
}
|
||||
}
|
||||
12
iota-cli/src/theme/mod.rs
Normal file
12
iota-cli/src/theme/mod.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
mod config;
|
||||
mod model;
|
||||
mod name;
|
||||
mod presets;
|
||||
|
||||
pub use config::{DaemonStartPolicy, UiConfig};
|
||||
pub use model::*;
|
||||
pub use name::ThemeName;
|
||||
|
||||
pub fn resolve(name: ThemeName) -> ResolvedTheme {
|
||||
presets::resolve(name)
|
||||
}
|
||||
149
iota-cli/src/theme/model.rs
Normal file
149
iota-cli/src/theme/model.rs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
use super::ThemeName;
|
||||
use ratatui::style::Style;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TextStyles {
|
||||
pub normal: Style,
|
||||
pub muted: Style,
|
||||
pub heading: Style,
|
||||
pub link: Style,
|
||||
pub code: Style,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StatusStyles {
|
||||
pub info: Style,
|
||||
pub success: Style,
|
||||
pub warning: Style,
|
||||
pub error: Style,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BorderStyles {
|
||||
pub normal: Style,
|
||||
pub focused: Style,
|
||||
pub disabled: Style,
|
||||
pub title: Style,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ChoiceItemStyle {
|
||||
pub marker: Style,
|
||||
pub label: Style,
|
||||
pub description: Style,
|
||||
pub prefix: &'static str,
|
||||
pub suffix: &'static str,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ChoiceStyles {
|
||||
pub normal: ChoiceItemStyle,
|
||||
pub focused: ChoiceItemStyle,
|
||||
pub selected: ChoiceItemStyle,
|
||||
pub focused_selected: ChoiceItemStyle,
|
||||
pub disabled: ChoiceItemStyle,
|
||||
pub focused_disabled: ChoiceItemStyle,
|
||||
pub selected_disabled: ChoiceItemStyle,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ButtonStyles {
|
||||
pub primary: Style,
|
||||
pub primary_focused: Style,
|
||||
pub neutral: Style,
|
||||
pub neutral_focused: Style,
|
||||
pub cancel: Style,
|
||||
pub cancel_focused: Style,
|
||||
pub destructive: Style,
|
||||
pub disabled: Style,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MarkerSet {
|
||||
pub checkbox_unselected: &'static str,
|
||||
pub checkbox_selected: &'static str,
|
||||
pub radio_unselected: &'static str,
|
||||
pub radio_selected: &'static str,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CursorPresentation {
|
||||
StyledCell(Style),
|
||||
Character { glyph: &'static str, style: Style },
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConsoleStyles {
|
||||
pub text: Style,
|
||||
pub prefix: Style,
|
||||
pub hint: Style,
|
||||
pub error: Style,
|
||||
pub confirmation: Style,
|
||||
pub cursor: CursorPresentation,
|
||||
pub border: Style,
|
||||
pub focused_border: Style,
|
||||
pub title: Style,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GraphStyles {
|
||||
pub ram: ratatui::style::Color,
|
||||
pub cpu: ratatui::style::Color,
|
||||
pub ping: ratatui::style::Color,
|
||||
pub text: Style,
|
||||
pub border: Style,
|
||||
pub focused_border: Style,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LogStyles {
|
||||
pub call: Style,
|
||||
pub client: Style,
|
||||
pub iota: Style,
|
||||
pub omikron: Style,
|
||||
pub omega: Style,
|
||||
pub command: Style,
|
||||
pub other: Style,
|
||||
pub text: Style,
|
||||
pub error: Style,
|
||||
pub timestamp: Style,
|
||||
pub border: Style,
|
||||
pub focused_border: Style,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MarkdownStyles {
|
||||
pub normal: Style,
|
||||
pub muted: Style,
|
||||
pub heading: Style,
|
||||
pub link: Style,
|
||||
pub code: Style,
|
||||
pub table_header: Style,
|
||||
pub table_text: Style,
|
||||
pub divider: Style,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct TextSemantics {
|
||||
pub bold: bool,
|
||||
pub underline: bool,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResolvedTheme {
|
||||
pub name: ThemeName,
|
||||
pub text: TextStyles,
|
||||
pub status: StatusStyles,
|
||||
pub choices: ChoiceStyles,
|
||||
pub buttons: ButtonStyles,
|
||||
pub borders: BorderStyles,
|
||||
pub console: ConsoleStyles,
|
||||
pub graphs: GraphStyles,
|
||||
pub logs: LogStyles,
|
||||
pub markdown: MarkdownStyles,
|
||||
pub markers: MarkerSet,
|
||||
}
|
||||
|
||||
impl ResolvedTheme {
|
||||
pub fn apply_text_semantics(&self, base: Style, semantics: TextSemantics) -> Style {
|
||||
use ratatui::style::Modifier;
|
||||
if matches!(self.name, ThemeName::Monospace) {
|
||||
return base;
|
||||
}
|
||||
let mut style = base;
|
||||
if semantics.bold {
|
||||
style = style.add_modifier(Modifier::BOLD);
|
||||
}
|
||||
if semantics.underline {
|
||||
style = style.add_modifier(Modifier::UNDERLINED);
|
||||
}
|
||||
style
|
||||
}
|
||||
}
|
||||
47
iota-cli/src/theme/name.rs
Normal file
47
iota-cli/src/theme/name.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ThemeName {
|
||||
Monospace,
|
||||
Binary,
|
||||
#[default]
|
||||
Ansi,
|
||||
Surface,
|
||||
}
|
||||
|
||||
impl ThemeName {
|
||||
pub const ALL: [Self; 4] = [Self::Monospace, Self::Binary, Self::Ansi, Self::Surface];
|
||||
|
||||
pub fn supported_names() -> &'static str {
|
||||
"monospace, binary, ansi, surface"
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ThemeName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Monospace => "monospace",
|
||||
Self::Binary => "binary",
|
||||
Self::Ansi => "ansi",
|
||||
Self::Surface => "surface",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ThemeName {
|
||||
type Err = String;
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
match value.to_ascii_lowercase().as_str() {
|
||||
"monospace" => Ok(Self::Monospace),
|
||||
"binary" => Ok(Self::Binary),
|
||||
"ansi" => Ok(Self::Ansi),
|
||||
"surface" => Ok(Self::Surface),
|
||||
_ => Err(format!(
|
||||
"unknown theme `{value}`; supported themes: {}",
|
||||
Self::supported_names()
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
301
iota-cli/src/theme/presets.rs
Normal file
301
iota-cli/src/theme/presets.rs
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
use super::{
|
||||
BorderStyles, ButtonStyles, ChoiceItemStyle, ChoiceStyles, ConsoleStyles, CursorPresentation,
|
||||
GraphStyles, LogStyles, MarkdownStyles, MarkerSet, ResolvedTheme, StatusStyles, TextStyles,
|
||||
ThemeName,
|
||||
};
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
|
||||
fn marker() -> MarkerSet {
|
||||
MarkerSet {
|
||||
checkbox_unselected: "[ ]",
|
||||
checkbox_selected: "[x]",
|
||||
radio_unselected: "( )",
|
||||
radio_selected: "(x)",
|
||||
}
|
||||
}
|
||||
fn choice(
|
||||
marker: Style,
|
||||
label: Style,
|
||||
prefix: &'static str,
|
||||
suffix: &'static str,
|
||||
) -> ChoiceItemStyle {
|
||||
ChoiceItemStyle {
|
||||
marker,
|
||||
label,
|
||||
description: label,
|
||||
prefix,
|
||||
suffix,
|
||||
}
|
||||
}
|
||||
fn base(
|
||||
name: ThemeName,
|
||||
normal: Style,
|
||||
muted: Style,
|
||||
focused: Style,
|
||||
selected: Style,
|
||||
disabled: Style,
|
||||
status: StatusStyles,
|
||||
buttons: ButtonStyles,
|
||||
) -> ResolvedTheme {
|
||||
let error = status.error;
|
||||
let (prefix, suffix) = if matches!(name, ThemeName::Monospace | ThemeName::Binary) {
|
||||
("> ", " <")
|
||||
} else {
|
||||
("", "")
|
||||
};
|
||||
ResolvedTheme {
|
||||
name,
|
||||
text: TextStyles {
|
||||
normal,
|
||||
muted,
|
||||
heading: normal,
|
||||
link: focused,
|
||||
code: normal,
|
||||
},
|
||||
status,
|
||||
choices: ChoiceStyles {
|
||||
normal: choice(normal, normal, "", ""),
|
||||
focused: choice(focused, focused, prefix, suffix),
|
||||
selected: choice(selected, selected, "", ""),
|
||||
focused_selected: choice(
|
||||
selected.patch(focused),
|
||||
selected.patch(focused),
|
||||
prefix,
|
||||
suffix,
|
||||
),
|
||||
disabled: choice(disabled, disabled, "", ""),
|
||||
focused_disabled: choice(disabled, error, prefix, suffix),
|
||||
selected_disabled: choice(disabled, disabled, "", ""),
|
||||
},
|
||||
buttons,
|
||||
borders: BorderStyles {
|
||||
normal,
|
||||
focused,
|
||||
disabled,
|
||||
title: normal,
|
||||
},
|
||||
console: ConsoleStyles {
|
||||
text: normal,
|
||||
prefix: muted,
|
||||
hint: muted,
|
||||
error,
|
||||
confirmation: focused,
|
||||
cursor: CursorPresentation::StyledCell(focused),
|
||||
border: normal,
|
||||
focused_border: focused,
|
||||
title: normal,
|
||||
},
|
||||
graphs: GraphStyles {
|
||||
ram: Color::Reset,
|
||||
cpu: Color::Reset,
|
||||
ping: Color::Reset,
|
||||
text: normal,
|
||||
border: normal,
|
||||
focused_border: focused,
|
||||
},
|
||||
logs: LogStyles {
|
||||
call: normal,
|
||||
client: normal,
|
||||
iota: normal,
|
||||
omikron: normal,
|
||||
omega: normal,
|
||||
command: normal,
|
||||
other: normal,
|
||||
text: normal,
|
||||
error,
|
||||
timestamp: muted,
|
||||
border: normal,
|
||||
focused_border: focused,
|
||||
},
|
||||
markdown: MarkdownStyles {
|
||||
normal,
|
||||
muted,
|
||||
heading: focused,
|
||||
link: focused,
|
||||
code: focused,
|
||||
table_header: focused,
|
||||
table_text: normal,
|
||||
divider: muted,
|
||||
},
|
||||
markers: marker(),
|
||||
}
|
||||
}
|
||||
pub fn resolve(name: ThemeName) -> ResolvedTheme {
|
||||
let plain = Style::default();
|
||||
match name {
|
||||
ThemeName::Monospace => {
|
||||
let mut theme = base(
|
||||
name,
|
||||
plain,
|
||||
plain,
|
||||
plain,
|
||||
plain,
|
||||
plain,
|
||||
StatusStyles {
|
||||
info: plain,
|
||||
success: plain,
|
||||
warning: plain,
|
||||
error: plain,
|
||||
},
|
||||
ButtonStyles {
|
||||
primary: plain,
|
||||
primary_focused: plain,
|
||||
neutral: plain,
|
||||
neutral_focused: plain,
|
||||
cancel: plain,
|
||||
cancel_focused: plain,
|
||||
destructive: plain,
|
||||
disabled: plain,
|
||||
},
|
||||
);
|
||||
theme.console.cursor = CursorPresentation::Character {
|
||||
glyph: "▌",
|
||||
style: plain,
|
||||
};
|
||||
theme.graphs = GraphStyles {
|
||||
ram: Color::Reset,
|
||||
cpu: Color::Reset,
|
||||
ping: Color::Reset,
|
||||
text: plain,
|
||||
border: plain,
|
||||
focused_border: plain,
|
||||
};
|
||||
theme
|
||||
}
|
||||
ThemeName::Binary => {
|
||||
let reversed = plain.add_modifier(Modifier::REVERSED);
|
||||
base(
|
||||
name,
|
||||
plain,
|
||||
plain,
|
||||
plain,
|
||||
reversed,
|
||||
plain,
|
||||
StatusStyles {
|
||||
info: plain,
|
||||
success: plain,
|
||||
warning: plain,
|
||||
error: plain,
|
||||
},
|
||||
ButtonStyles {
|
||||
primary: plain,
|
||||
primary_focused: reversed,
|
||||
neutral: plain,
|
||||
neutral_focused: reversed,
|
||||
cancel: plain,
|
||||
cancel_focused: reversed,
|
||||
destructive: plain,
|
||||
disabled: plain,
|
||||
},
|
||||
)
|
||||
}
|
||||
ThemeName::Ansi => {
|
||||
let yellow = plain.fg(Color::Yellow).add_modifier(Modifier::BOLD);
|
||||
let mut theme = base(
|
||||
name,
|
||||
plain,
|
||||
plain.fg(Color::DarkGray),
|
||||
yellow,
|
||||
plain,
|
||||
plain.fg(Color::DarkGray),
|
||||
StatusStyles {
|
||||
info: plain,
|
||||
success: plain.fg(Color::Green),
|
||||
warning: plain.fg(Color::Yellow),
|
||||
error: plain.fg(Color::Red),
|
||||
},
|
||||
ButtonStyles {
|
||||
primary: plain.fg(Color::Green),
|
||||
primary_focused: plain
|
||||
.fg(Color::Black)
|
||||
.bg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
neutral: plain,
|
||||
neutral_focused: yellow,
|
||||
cancel: plain.fg(Color::Red),
|
||||
cancel_focused: plain
|
||||
.fg(Color::Black)
|
||||
.bg(Color::Red)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
destructive: plain.fg(Color::Red),
|
||||
disabled: plain.fg(Color::DarkGray),
|
||||
},
|
||||
);
|
||||
theme.console = ConsoleStyles {
|
||||
text: plain.fg(Color::White),
|
||||
prefix: plain.fg(Color::DarkGray),
|
||||
hint: plain.fg(Color::DarkGray),
|
||||
error: plain.fg(Color::Red),
|
||||
confirmation: plain.fg(Color::Yellow),
|
||||
cursor: CursorPresentation::StyledCell(plain.fg(Color::White).bg(Color::DarkGray)),
|
||||
border: plain,
|
||||
focused_border: plain.fg(Color::Yellow),
|
||||
title: plain.fg(Color::White),
|
||||
};
|
||||
theme.graphs = GraphStyles {
|
||||
ram: Color::Blue,
|
||||
cpu: Color::Red,
|
||||
ping: Color::Green,
|
||||
text: plain,
|
||||
border: plain,
|
||||
focused_border: plain.fg(Color::Yellow),
|
||||
};
|
||||
theme.logs = LogStyles {
|
||||
call: plain.fg(Color::Magenta),
|
||||
client: plain.fg(Color::Green),
|
||||
iota: plain.fg(Color::Yellow),
|
||||
omikron: plain.fg(Color::Blue),
|
||||
omega: plain.fg(Color::Cyan),
|
||||
command: plain.fg(Color::LightGreen),
|
||||
other: plain.fg(Color::LightCyan),
|
||||
text: plain.fg(Color::White),
|
||||
error: plain.fg(Color::Red),
|
||||
timestamp: plain.fg(Color::DarkGray),
|
||||
border: plain,
|
||||
focused_border: plain.fg(Color::Yellow),
|
||||
};
|
||||
theme.markdown = MarkdownStyles {
|
||||
normal: plain,
|
||||
muted: plain.fg(Color::DarkGray),
|
||||
heading: plain.fg(Color::Cyan),
|
||||
link: plain.fg(Color::Cyan),
|
||||
code: plain.fg(Color::Yellow),
|
||||
table_header: plain.fg(Color::Cyan),
|
||||
table_text: plain.fg(Color::Green),
|
||||
divider: plain.fg(Color::DarkGray),
|
||||
};
|
||||
theme
|
||||
}
|
||||
ThemeName::Surface => {
|
||||
let focus = plain.fg(Color::Black).bg(Color::Yellow);
|
||||
let selected = plain.fg(Color::Black).bg(Color::Cyan);
|
||||
let mut theme = base(
|
||||
name,
|
||||
plain,
|
||||
plain.fg(Color::DarkGray),
|
||||
focus,
|
||||
selected,
|
||||
plain.fg(Color::DarkGray),
|
||||
StatusStyles {
|
||||
info: plain,
|
||||
success: plain.fg(Color::Green),
|
||||
warning: plain.fg(Color::Yellow),
|
||||
error: plain.fg(Color::Red),
|
||||
},
|
||||
ButtonStyles {
|
||||
primary: plain.fg(Color::Black).bg(Color::Green),
|
||||
primary_focused: focus,
|
||||
neutral: plain,
|
||||
neutral_focused: focus,
|
||||
cancel: plain.fg(Color::Black).bg(Color::Red),
|
||||
cancel_focused: focus,
|
||||
destructive: plain.fg(Color::Black).bg(Color::Red),
|
||||
disabled: plain.fg(Color::DarkGray),
|
||||
},
|
||||
);
|
||||
theme.console.cursor =
|
||||
CursorPresentation::StyledCell(plain.fg(Color::Black).bg(Color::Yellow));
|
||||
theme
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue