trnovel 0.9.0

Terminal reader for novel
Documentation
use crate::Result;
use crate::utils::novel_catch_dir;
use ratatui::style::{Color, Style, Stylize};
use serde::{Deserialize, Serialize};
use std::fs::File;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThemeConfig {
    pub colors: ThemeColors,

    pub logo: Style,
    pub highlight: Style,
    pub selected: Style,
    pub empty: Style,
    pub detail_info: Style,

    pub basic: BasicSetting,
    pub loading_modal: BasicSetting,
    pub warning_modal: BasicSetting,
    pub error_modal: BasicSetting,
    pub search: SearchSetting,
    pub novel: NovelSetting,
}

#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
#[serde(rename_all = "camelCase")]
pub struct ThemeColors {
    pub text_color: Color,
    pub primary_color: Color,
    pub warning_color: Color,
    pub error_color: Color,
    pub success_color: Color,
    pub info_color: Color,
}

impl ThemeConfig {
    pub fn save(&self) -> Result<()> {
        let path = novel_catch_dir()?.join("theme.json");
        let file = File::create(path)?;
        serde_json::to_writer_pretty(file, self)?;
        Ok(())
    }

    pub fn from_colors(colors: ThemeColors) -> Self {
        let ThemeColors {
            text_color,
            primary_color,
            warning_color,
            error_color,
            success_color,
            info_color,
        } = colors;

        let basic = BasicSetting {
            text: Style::new().fg(text_color),
            border: Style::new().dim(),
            border_info: Style::new().dim().fg(info_color),
            border_title: Style::new().fg(primary_color).not_dim(),
            ..Default::default()
        };
        Self {
            colors,
            detail_info: Style::new().fg(warning_color).bold(),
            logo: Style::new().fg(success_color),
            selected: Style::new().fg(success_color),
            highlight: Style::new().fg(primary_color),
            empty: Style::new().fg(warning_color).bold(),
            search: SearchSetting {
                success_border: Style::new().fg(success_color).dim(),
                success_border_info: Style::new().fg(success_color).not_dim(),
                error_border: Style::new().fg(error_color).dim(),
                error_border_info: Style::new().fg(error_color).not_dim(),
                placeholder: Style::new().fg(text_color).dark_gray(),
                text: Style::new().fg(text_color),
            },
            loading_modal: BasicSetting {
                border: basic.border.fg(primary_color),
                text: Style::new().fg(primary_color),
                ..Default::default()
            },
            warning_modal: BasicSetting {
                border: basic.border.fg(warning_color),
                text: Style::new().fg(warning_color),
                ..Default::default()
            },
            error_modal: BasicSetting {
                border: basic.border.fg(error_color),
                text: Style::new().fg(error_color),
                ..Default::default()
            },
            novel: NovelSetting {
                content: Style::new().fg(text_color),
                chapter: basic.border_title,
                page: basic.border_info,
                progress: basic.border_info,
                border: basic.border,
                show_title: true,
            },
            basic,
        }
    }
}

impl Default for ThemeConfig {
    fn default() -> Self {
        let colors = ThemeColors {
            text_color: Color::default(),
            primary_color: Color::LightBlue,
            warning_color: Color::LightYellow,
            error_color: Color::LightRed,
            success_color: Color::LightGreen,
            info_color: Color::Gray,
        };
        Self::from_colors(colors)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BasicSetting {
    pub text: Style,
    pub border: Style,
    pub border_title: Style,
    pub border_info: Style,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SearchSetting {
    pub success_border: Style,
    pub success_border_info: Style,
    pub error_border: Style,
    pub error_border_info: Style,
    pub placeholder: Style,
    pub text: Style,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct NovelSetting {
    pub chapter: Style,
    pub page: Style,
    pub content: Style,
    pub progress: Style,
    pub border: Style,
    #[serde(default = "default_show_title")]
    pub show_title: bool,
}

fn default_show_title() -> bool {
    true
}