use ratatui::style::Style;
use rust_embed::RustEmbed;
use std::cell::{Ref, RefCell, RefMut};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::{collections::HashMap, fmt::Display, hash::Hash};
use maplit::hashmap;
use ratatui::style::Color;
pub trait ToColor {
fn to_color(self) -> Color;
}
type RgbColor = (u8, u8, u8);
impl ToColor for RgbColor {
fn to_color(self) -> Color {
Color::Rgb(self.0, self.1, self.2)
}
}
#[allow(dead_code)]
pub enum TukaiLayoutColorTypeEnum {
Primary,
Secondary,
Text,
TextReverse,
Background,
Error,
}
#[derive(PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Clone)]
pub enum TukaiLayoutName {
Iced,
Rust,
Anime,
Deadpool,
Wolverine,
Goblin,
}
impl Display for TukaiLayoutName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use TukaiLayoutName::*;
let display_text = match self {
Iced => "π₯Ά Iced",
Rust => "π¦ Rust",
Anime => "πΈ Anime",
Deadpool => "π©Έπ Deadpool",
Wolverine => "πͺπΊ Wolverine",
Goblin => "π³ Goblin",
};
write!(f, "{display_text}")
}
}
pub struct TukaiLayoutColors {
primary: RgbColor,
text: RgbColor,
text_current: RgbColor,
text_current_bg: RgbColor,
background: RgbColor,
error: RgbColor,
}
impl TukaiLayoutColors {
pub fn new(
primary: RgbColor,
text: RgbColor,
text_current: RgbColor,
text_current_bg: RgbColor,
background: RgbColor,
error: RgbColor,
) -> Self {
Self {
primary,
text,
text_current,
text_current_bg,
background,
error,
}
}
}
pub struct TukaiLayout {
layouts: HashMap<TukaiLayoutName, TukaiLayoutColors>,
transitions: HashMap<TukaiLayoutName, TukaiLayoutName>,
active_layout_name: TukaiLayoutName,
}
impl TukaiLayout {
pub fn default() -> Self {
use TukaiLayoutName::*;
let layouts = hashmap! {
Iced => {
TukaiLayoutColors::new(
(108, 181, 230),
(232, 232, 232),
(25, 74, 107),
(200, 200, 200),
(37, 40, 46),
(214, 90, 90),
)
},
Anime => {
TukaiLayoutColors::new(
(152, 117, 201),
(222, 135, 174),
(49, 45, 51),
(222, 170, 146),
(31, 27, 30),
(227, 138, 138),
)
},
Deadpool => {
TukaiLayoutColors::new(
(139, 35, 35),
(210, 210, 210),
(23, 23, 23),
(210, 210, 210),
(33, 29, 29),
(110, 110, 110),
)
},
Wolverine => {
TukaiLayoutColors::new(
(196, 166, 51),
(200, 200, 200),
(23,23,23),
(210, 210, 210),
(10, 14, 18),
(110, 110, 110),
)
},
Rust => {
TukaiLayoutColors::new(
(150, 63, 17),
(255, 178, 137),
(255, 178, 137),
(150, 63, 17),
(24, 8, 2),
(120, 120, 120),
)
},
Goblin => {
TukaiLayoutColors::new(
(82, 140, 25),
(136, 207, 66),
(220, 220, 220),
(39, 61, 17),
(32, 36, 30),
(117, 71, 56),
)
}
};
let transitions = HashMap::from([
(Iced, Anime),
(Anime, Deadpool),
(Deadpool, Wolverine),
(Wolverine, Rust),
(Rust, Goblin),
(Goblin, Iced),
]);
Self {
layouts,
transitions,
active_layout_name: TukaiLayoutName::Iced,
}
}
pub fn get_active_layout_name(&self) -> &TukaiLayoutName {
&self.active_layout_name
}
pub fn active_layout_name(&mut self, active_layout_name: TukaiLayoutName) {
self.active_layout_name = active_layout_name;
}
pub fn switch_to_next_layout(&mut self) -> TukaiLayoutName {
if let Some(next_layout_name) = self.transitions.get(&self.active_layout_name) {
self.active_layout_name = next_layout_name.clone();
};
self.active_layout_name.clone()
}
fn get_layout_colors(&self) -> &TukaiLayoutColors {
self.layouts.get(&self.active_layout_name).unwrap()
}
pub fn get_primary_color(&self) -> Color {
self.get_layout_colors().primary.to_color()
}
pub fn get_text_color(&self) -> Color {
self.get_layout_colors().text.to_color()
}
pub fn get_text_current_color(&self) -> Color {
self.get_layout_colors().text_current.to_color()
}
pub fn get_text_current_bg_color(&self) -> Color {
self.get_layout_colors().text_current_bg.to_color()
}
pub fn get_error_color(&self) -> Color {
self.get_layout_colors().error.to_color()
}
pub fn get_background_color(&self) -> Color {
self.get_layout_colors().background.to_color()
}
}
#[derive(RustEmbed)]
#[folder = "dictionary/"]
struct LanguageDictionary;
pub struct Language {
language_files: Vec<String>,
current_index: usize,
lang_code: String,
words: Vec<String>,
}
impl Language {
pub fn default() -> Self {
Self {
language_files: Vec::new(),
current_index: 0,
lang_code: String::from("en"),
words: Vec::new(),
}
}
pub fn init_lang_code(&mut self) {
let filename = &self.language_files[self.current_index];
let lang_code = Path::new(filename)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
self.lang_code = lang_code;
}
pub fn init(mut self) -> Self {
if let Ok(language_files) = self.load_language_files() {
self.language_files = language_files;
}
if !self.language_files.is_empty()
&& let Ok(words) = self.load_language_words()
{
self.words = words;
}
self
}
pub fn current_index(&mut self, index: usize) {
self.current_index = index;
self.init_lang_code();
}
#[allow(unused)]
pub fn get_current_index(&self) -> &usize {
&self.current_index
}
pub fn switch_language(&mut self) -> usize {
self.current_index += 1;
if self.current_index >= self.language_files.len() {
self.current_index = 0;
}
self.init_lang_code();
self.current_index
}
pub fn load_language_files(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let languages = LanguageDictionary::iter()
.map(|file| file.to_string())
.collect::<Vec<String>>();
Ok(languages)
}
pub fn load_language_words(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let language_file_path = self
.language_files
.get(self.current_index)
.ok_or("Not found a language dictionary file")?;
let file = LanguageDictionary::get(language_file_path).unwrap();
let words = std::str::from_utf8(&file.data)?
.lines()
.flat_map(|line| {
line
.split_whitespace()
.map(String::from)
.collect::<Vec<String>>()
})
.collect::<Vec<String>>();
Ok(words)
}
pub fn get_lang_code(&self) -> &String {
&self.lang_code
}
}
#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Debug, Clone)]
pub enum TypingDuration {
FifteenSec,
ThirtySec,
Minute,
ThreeMinutes,
}
impl Default for TypingDuration {
fn default() -> Self {
Self::Minute
}
}
impl TypingDuration {
pub fn as_seconds(&self) -> usize {
use TypingDuration::*;
match self {
FifteenSec => 15,
ThirtySec => 30,
Minute => 60,
ThreeMinutes => 180,
}
}
}
#[allow(unused)]
pub trait ConfigBuilder<T> {
fn new() -> Self;
fn build(self) -> T;
}
pub struct TukaiConfig {
file_path: PathBuf,
layout: RefCell<TukaiLayout>,
language: RefCell<Language>,
pub has_transparent_bg: bool,
pub typing_duration: TypingDuration,
}
impl TukaiConfig {
pub fn default() -> Self {
Self {
file_path: PathBuf::from("tukai.bin"),
layout: RefCell::new(TukaiLayout::default()),
language: RefCell::new(Language::default().init()),
has_transparent_bg: false,
typing_duration: TypingDuration::default(),
}
}
pub fn get_layout(&self) -> Ref<TukaiLayout> {
self.layout.borrow()
}
pub fn get_language(&self) -> Ref<Language> {
self.language.borrow()
}
pub fn get_layout_mut(&mut self) -> RefMut<TukaiLayout> {
self.layout.borrow_mut()
}
pub fn get_language_mut(&mut self) -> RefMut<Language> {
self.language.borrow_mut()
}
pub fn get_file_path(&self) -> &PathBuf {
&self.file_path
}
pub fn toggle_transparent_bg(&mut self) -> bool {
self.has_transparent_bg = !self.has_transparent_bg;
self.has_transparent_bg
}
pub fn switch_typing_duration(&mut self) -> TypingDuration {
self.typing_duration = match self.typing_duration {
TypingDuration::Minute => TypingDuration::ThreeMinutes,
TypingDuration::ThreeMinutes => TypingDuration::FifteenSec,
TypingDuration::FifteenSec => TypingDuration::ThirtySec,
TypingDuration::ThirtySec => TypingDuration::Minute,
};
self.typing_duration.clone()
}
pub fn get_bg_color(&self) -> Style {
let style = Style::default();
if self.has_transparent_bg {
style
} else {
style.bg(self.get_layout().get_background_color())
}
}
}
pub struct TukaiConfigBuilder {
file_path: Option<PathBuf>,
layout: Option<RefCell<TukaiLayout>>,
language: Option<RefCell<Language>>,
has_transparent_bg: bool,
typing_duration: Option<TypingDuration>,
}
impl TukaiConfigBuilder {
pub fn new() -> Self {
Self {
file_path: None,
layout: None,
language: None,
has_transparent_bg: true,
typing_duration: None,
}
}
#[allow(unused)]
pub fn file_path<P: AsRef<Path>>(mut self, file_path: P) -> Self {
self.file_path = Some(file_path.as_ref().to_path_buf());
self
}
#[allow(unused)]
pub fn layout(mut self, layout: TukaiLayout) -> Self {
self.layout = Some(RefCell::new(layout));
self
}
pub fn build(self) -> TukaiConfig {
let config_default = TukaiConfig::default();
TukaiConfig {
file_path: self.file_path.unwrap_or(config_default.file_path),
layout: self.layout.unwrap_or(config_default.layout),
language: self.language.unwrap_or(config_default.language),
has_transparent_bg: self.has_transparent_bg,
typing_duration: self
.typing_duration
.unwrap_or(config_default.typing_duration),
}
}
}