#![windows_subsystem = "windows"] mod data_provider; mod dictionary; mod dictionary_test; mod lang; mod quiz; mod randomizer; mod repetition; mod repetitions; mod selector; mod sync; mod word; mod writing; mod history; use crate::data_provider::card_sets::load_sets; use crate::data_provider::settings::get_setting; use crate::data_provider::words::{ load_word_groups, load_words}; use crate::dictionary::{app_data_dir, DictionaryMessage, DictionaryState}; use crate::dictionary_test::{DictionaryQuizMessage, DictionaryQuizState}; use crate::lang::{WordData, WordGroup}; use crate::quiz::*; use crate::randomizer::randomizer::{RandomizerMessage, RandomizerState}; use crate::repetition::{RepetitionMessage, RepetitionState}; use crate::repetitions::{CardSetSettings, RepetitionsMessage, RepetitionsState}; use crate::selector::*; use crate::sync::{SyncMessage, SyncState}; use crate::word::{WordMessage, WordState}; use crate::writing::{WritingMessage, WritingState}; use crate::Page::{Dictionary, DictionaryQuiz, History, Quiz, Randomizer, Repetition, Repetitions, Selector, Sync, Word, Writing}; use crate::RootMessage::Keyboard; use iced::keyboard::Event; use iced::widget::text; use iced::{keyboard, Element, Program, Subscription, Theme}; use iced::{Font, Task}; use rusqlite::Connection; use std::sync::{Arc, Mutex}; use crate::data_provider::sqlite::create_db; use crate::history::{HistoryMessage, HistoryState}; const DEFAULT_SPACING: f32 = 10.0; const USER_FONT: Font = Font::with_name("Noto Sans JP"); fn main() -> iced::Result { iced::application(ScreenState::boot, ScreenState::update, ScreenState::view) .subscription(subscription) .title("Kana learn app") .settings(iced::Settings{ default_text_size: iced::Pixels(16.0), ..iced::Settings::default() }) .font(include_bytes!("../ibmplex.ttf")) .font(include_bytes!("../noto.ttf")) .default_font(USER_FONT) .scale_factor(|x| 1.5) .theme(Theme::GruvboxDark) .run() } fn subscription(_state: &ScreenState) -> Subscription { keyboard::listen().map(|e| Keyboard(e)) } #[derive(Clone)] pub enum RootMessage { Selector(SelectorMessage), Quiz(QuizMessage), Writing(WritingMessage), Dictionary(DictionaryMessage), DictionaryQuiz(DictionaryQuizMessage), Randomizer(RandomizerMessage), Repetitions(RepetitionsMessage), Repetition(RepetitionMessage), Word(WordMessage), Sync(SyncMessage), History(HistoryMessage), Keyboard(Event), } pub enum Page { Selector(SelectorState), Quiz(QuizState), Writing(WritingState), Dictionary(DictionaryState), DictionaryQuiz(DictionaryQuizState), Randomizer(RandomizerState), Repetitions(RepetitionsState), Repetition(RepetitionState), Word(WordState), Sync(SyncState), History(HistoryState), PreviousPage, } pub struct ScreenState { stack: Vec, } pub struct AppState { pub dictionary: Vec, pub card_sets: Vec, pub word_groups: Vec, pub connection: Connection, pub sync_data: AppSettings, } impl AppState { pub fn new() -> Self { let path = app_data_dir(); let db_file = path.join("data.db"); let connection = Connection::open(db_file).unwrap(); connection.execute("PRAGMA foreign_keys = ON;", []).unwrap(); Self{ dictionary: vec![], card_sets: vec![], word_groups: vec![], connection, sync_data: AppSettings { key: None }, } } } impl Default for ScreenState { fn default() -> Self { let mut state = AppState::new(); fill_state(&mut state); let state = Arc::new(Mutex::new(state)); ScreenState { stack: vec![Selector(SelectorState::new(state.clone()))], } } } fn fill_state(state: &mut AppState) { let list = load_words(&state.connection); let sets = load_sets(&state.connection); let groups = load_word_groups(&state.connection); let setting = load_settings(&state.connection); state.dictionary = list; state.card_sets = sets; state.word_groups = groups; state.sync_data = setting } fn load_settings(connection: &Connection) -> AppSettings { let key = get_setting("SYNC_KEY".to_string(), connection); AppSettings { key } } impl ScreenState { pub fn boot() -> (ScreenState, Task) { create_db(); (ScreenState::default(), Task::none()) } pub fn update(&mut self, message: RootMessage) -> Task { if let Keyboard(e) = message { return self.handle_keyboard(e); } state_update!( message, self.stack, Selector, Quiz, Writing, Dictionary, DictionaryQuiz, Randomizer, Repetitions, Repetition, Word, Sync, History ); Task::none() } pub fn view(&self) -> Element<'_, RootMessage> { view_navigation!( self.stack, Quiz, Selector, Writing, Dictionary, DictionaryQuiz, Randomizer, Repetitions, Repetition, Word, Sync, History ) } fn handle_keyboard(&mut self, message: Event) -> Task { let page = self.stack.last_mut().unwrap(); match page { Repetition(page) => page.press(&message), _ => Task::none(), } } } pub struct AppSettings { key: Option, } #[macro_export] macro_rules! view_navigation { ($stack:expr, $($e:ident), *) => { match &$stack.last().unwrap() { $( $e(s) => s.view().map(RootMessage::$e), )* _ => text!("").into(), } } } #[macro_export] macro_rules! state_update { ($message:expr, $stack:expr, $($e:ident), *) => { match $message { $( RootMessage::$e(msg) => { if let $e(s) = $stack.last_mut().unwrap() { message_navigation!(msg, $stack, s) } } )* _ => {} } } } #[macro_export] macro_rules! message_navigation { ($msg:expr, $stack:expr, $state:expr) => { if let Some(new_page) = $state.navigate(&$msg) { if let Page::PreviousPage = new_page { $stack.pop(); return Task::none(); } $stack.push(new_page); } else { return $state.update($msg); } }; } trait NavigatedPage { fn navigate(&self, message: &T) -> Option; } pub trait KeyPressedPage { fn press(&mut self, message: &Event) -> Task; }