gm-tui 0.0.1

TUI-based Ethereum wallet
Documentation
use ratatui::prelude::Color;
use ratatui::style::Modifier;
use ratatui::style::Style;
use ratatui::widgets::BorderType;
use std::fmt::Formatter;
use strum::EnumIter;
use strum::IntoEnumIterator;

#[derive(Default, Debug, EnumIter)]
pub enum ThemeName {
    #[default]
    Monochrome,
    MonochromeModern,
    Dark,
    DarkModern,
}

impl std::fmt::Display for ThemeName {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{self:?}")
    }
}

impl std::str::FromStr for ThemeName {
    type Err = crate::Error;

    fn from_str(theme_name: &str) -> crate::Result<Self> {
        match theme_name {
            "Monochrome" => Ok(Self::Monochrome),
            "MonochromeModern" => Ok(Self::MonochromeModern),
            "Dark" => Ok(Self::Dark),
            "DarkModern" => Ok(Self::DarkModern),
            _ => Err(crate::Error::InternalError(format!(
                "Unknown theme name: {theme_name}"
            ))),
        }
    }
}

impl ThemeName {
    pub fn list() -> Vec<String> {
        Self::iter().map(|theme| theme.to_string()).collect()
    }
}

#[derive(Clone)]
pub struct Theme {
    pub text: Option<Color>,
    pub bg: Option<Color>,
    pub reversed: bool,
    pub select_focus: Option<Color>,
    pub popup_reversed: bool,
    pub popup_bg: Option<Color>,
    pub error_popup_bg: Option<Color>,
    pub border_type: BorderType,
}

impl From<&Theme> for Style {
    fn from(theme: &Theme) -> Self {
        let mut style = Style::default();
        if let Some(text_color) = theme.text {
            style = style.fg(text_color);
        }
        if let Some(bg_color) = theme.bg {
            style = style.bg(bg_color);
        }
        if theme.reversed {
            style = style.add_modifier(Modifier::REVERSED);
        } else {
            style = style.remove_modifier(Modifier::REVERSED);
        }
        style
    }
}

impl From<&mut Theme> for Style {
    fn from(theme: &mut Theme) -> Self {
        let theme = &*theme;
        theme.into()
    }
}

impl From<&Theme> for BorderType {
    fn from(val: &Theme) -> Self {
        val.border_type
    }
}

impl Theme {
    pub fn new(theme_name: ThemeName) -> Theme {
        match theme_name {
            ThemeName::Monochrome => Theme {
                text: None,
                bg: None,
                select_focus: None,
                reversed: false,
                popup_reversed: true,
                popup_bg: None,
                error_popup_bg: None,
                border_type: BorderType::Plain,
            },
            ThemeName::MonochromeModern => Theme {
                text: None,
                bg: None,
                select_focus: None,
                reversed: false,
                popup_reversed: true,
                popup_bg: None,
                error_popup_bg: None,
                border_type: BorderType::Rounded,
            },
            ThemeName::Dark => Theme {
                text: Some(Color::White),
                bg: Some(Color::Black),
                select_focus: None,
                reversed: false,
                popup_reversed: false,
                popup_bg: Some(Color::Blue),
                error_popup_bg: Some(Color::Red),
                border_type: BorderType::Plain,
            },
            ThemeName::DarkModern => Theme {
                text: Some(Color::White),
                bg: Some(Color::Black),
                select_focus: None,
                reversed: false,
                popup_reversed: false,
                popup_bg: Some(Color::Blue),
                error_popup_bg: Some(Color::Red),
                border_type: BorderType::Rounded,
            },
        }
    }

    pub fn button_focused(&self) -> Style {
        if self.reversed {
            Style::default()
                .add_modifier(Modifier::BOLD)
                .remove_modifier(Modifier::REVERSED)
        } else {
            Style::default().add_modifier(Modifier::BOLD | Modifier::REVERSED)
        }
    }

    pub fn select(&self) -> Style {
        if let Some(select_focus) = self.select_focus {
            Style::default()
                .bg(select_focus)
                .add_modifier(Modifier::BOLD)
        } else {
            Style::default().add_modifier(Modifier::BOLD | Modifier::REVERSED)
        }
    }

    pub fn select_popup(&self) -> Style {
        if let Some(select_focus) = self.select_focus {
            Style::default()
                .bg(select_focus)
                .add_modifier(Modifier::BOLD)
        } else {
            Style::default()
                .add_modifier(Modifier::BOLD)
                .remove_modifier(Modifier::REVERSED)
        }
    }

    pub fn popup(&self) -> Theme {
        Theme {
            bg: self.popup_bg,
            reversed: self.popup_reversed,
            ..self.clone()
        }
    }

    pub fn error_popup(&self) -> Theme {
        let s = self.popup();
        Theme {
            bg: s.error_popup_bg,
            ..s.clone()
        }
    }
}