2026-02-10 17:25:39 +03:00
|
|
|
#![windows_subsystem = "windows"]
|
2026-05-05 12:02:59 +03:00
|
|
|
mod data_provider;
|
2026-04-09 22:45:31 +03:00
|
|
|
mod dictionary;
|
|
|
|
|
mod dictionary_test;
|
2026-02-10 11:28:12 +03:00
|
|
|
mod lang;
|
2026-02-10 12:12:16 +03:00
|
|
|
mod quiz;
|
2026-04-09 22:45:31 +03:00
|
|
|
mod randomizer;
|
|
|
|
|
mod repetition;
|
|
|
|
|
mod repetitions;
|
2026-02-10 12:12:16 +03:00
|
|
|
mod selector;
|
2026-04-17 12:38:05 +03:00
|
|
|
mod word;
|
2026-05-05 12:02:59 +03:00
|
|
|
mod writing;
|
2026-04-17 12:38:05 +03:00
|
|
|
|
2026-04-13 22:25:58 +03:00
|
|
|
use crate::data_provider::card_sets::load_sets;
|
2026-05-05 12:02:59 +03:00
|
|
|
use crate::data_provider::words::{create_db, load_word_groups, load_words};
|
2026-04-13 22:25:58 +03:00
|
|
|
use crate::dictionary::{app_data_dir, DictionaryMessage, DictionaryState};
|
2026-04-09 22:45:31 +03:00
|
|
|
use crate::dictionary_test::{DictionaryQuizMessage, DictionaryQuizState};
|
2026-05-05 12:02:59 +03:00
|
|
|
use crate::lang::{WordData, WordGroup};
|
2026-02-10 12:12:16 +03:00
|
|
|
use crate::quiz::*;
|
2026-04-09 22:45:31 +03:00
|
|
|
use crate::randomizer::randomizer::{RandomizerMessage, RandomizerState};
|
|
|
|
|
use crate::repetition::{RepetitionMessage, RepetitionState};
|
2026-04-13 22:25:58 +03:00
|
|
|
use crate::repetitions::{CardSetSettings, RepetitionsMessage, RepetitionsState};
|
2026-02-10 12:12:16 +03:00
|
|
|
use crate::selector::*;
|
2026-04-17 12:38:05 +03:00
|
|
|
use crate::word::{WordMessage, WordState};
|
2026-02-10 17:30:43 +03:00
|
|
|
use crate::writing::{WritingMessage, WritingState};
|
2026-04-09 22:45:31 +03:00
|
|
|
use crate::Page::{
|
2026-05-05 12:02:59 +03:00
|
|
|
Dictionary, DictionaryQuiz, Quiz, Randomizer, Repetition, Repetitions, Selector, Word, Writing,
|
2026-04-09 22:45:31 +03:00
|
|
|
};
|
2026-04-17 12:38:05 +03:00
|
|
|
use crate::RootMessage::Keyboard;
|
|
|
|
|
use iced::keyboard::Event;
|
2026-02-10 12:12:16 +03:00
|
|
|
use iced::widget::text;
|
2026-04-15 07:55:03 +03:00
|
|
|
use iced::{keyboard, Element, Subscription};
|
2026-04-09 22:45:31 +03:00
|
|
|
use iced::{Font, Task};
|
2026-04-13 22:25:58 +03:00
|
|
|
use rusqlite::Connection;
|
2026-04-09 22:45:31 +03:00
|
|
|
use std::sync::{Arc, Mutex};
|
2026-02-10 11:28:12 +03:00
|
|
|
|
2026-04-16 09:56:57 +03:00
|
|
|
const DEFAULT_SPACING: f32 = 10.0;
|
|
|
|
|
|
2026-02-10 11:28:12 +03:00
|
|
|
fn main() -> iced::Result {
|
2026-05-05 12:02:59 +03:00
|
|
|
iced::application(ScreenState::boot, ScreenState::update, ScreenState::view)
|
|
|
|
|
.subscription(subscription)
|
2026-04-09 22:45:31 +03:00
|
|
|
.title("Kana learn app")
|
|
|
|
|
.font(include_bytes!("../noto.ttf"))
|
|
|
|
|
.default_font(Font::with_name("Noto Sans JP"))
|
2026-05-05 12:02:59 +03:00
|
|
|
.run()
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 15:19:46 +03:00
|
|
|
fn subscription(_state: &ScreenState) -> Subscription<RootMessage> {
|
|
|
|
|
keyboard::listen().map(|e| Keyboard(e))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
2026-02-10 11:28:12 +03:00
|
|
|
pub enum RootMessage {
|
|
|
|
|
Selector(SelectorMessage),
|
|
|
|
|
Quiz(QuizMessage),
|
2026-02-10 17:04:48 +03:00
|
|
|
Writing(WritingMessage),
|
2026-02-22 11:07:57 +03:00
|
|
|
Dictionary(DictionaryMessage),
|
|
|
|
|
DictionaryQuiz(DictionaryQuizMessage),
|
2026-04-09 22:45:31 +03:00
|
|
|
Randomizer(RandomizerMessage),
|
|
|
|
|
Repetitions(RepetitionsMessage),
|
|
|
|
|
Repetition(RepetitionMessage),
|
2026-04-17 12:38:05 +03:00
|
|
|
Word(WordMessage),
|
|
|
|
|
Keyboard(Event),
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Page {
|
|
|
|
|
Selector(SelectorState),
|
|
|
|
|
Quiz(QuizState),
|
2026-02-10 17:04:48 +03:00
|
|
|
Writing(WritingState),
|
2026-02-22 11:07:57 +03:00
|
|
|
Dictionary(DictionaryState),
|
|
|
|
|
DictionaryQuiz(DictionaryQuizState),
|
2026-03-17 12:16:35 +03:00
|
|
|
Randomizer(RandomizerState),
|
2026-04-09 22:45:31 +03:00
|
|
|
Repetitions(RepetitionsState),
|
|
|
|
|
Repetition(RepetitionState),
|
2026-04-17 12:38:05 +03:00
|
|
|
Word(WordState),
|
2026-02-11 16:19:23 +03:00
|
|
|
PreviousPage,
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct ScreenState {
|
|
|
|
|
stack: Vec<Page>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 22:45:31 +03:00
|
|
|
pub struct AppState {
|
2026-05-05 12:02:59 +03:00
|
|
|
pub dictionary: Vec<WordData>,
|
2026-04-13 22:25:58 +03:00
|
|
|
pub card_sets: Vec<CardSetSettings>,
|
2026-05-05 12:02:59 +03:00
|
|
|
pub word_groups: Vec<WordGroup>,
|
|
|
|
|
pub connection: Connection,
|
2026-04-09 22:45:31 +03:00
|
|
|
}
|
|
|
|
|
|
2026-02-10 11:28:12 +03:00
|
|
|
impl Default for ScreenState {
|
|
|
|
|
fn default() -> Self {
|
2026-04-13 22:25:58 +03:00
|
|
|
let path = app_data_dir();
|
|
|
|
|
let db_file = path.join("data.db");
|
|
|
|
|
let connection = Connection::open(db_file).unwrap();
|
2026-04-14 16:24:45 +03:00
|
|
|
connection.execute("PRAGMA foreign_keys = ON;", []).unwrap();
|
2026-05-05 12:02:59 +03:00
|
|
|
let list = load_words(&connection);
|
|
|
|
|
let sets = load_sets(&connection);
|
|
|
|
|
let groups = load_word_groups(&connection);
|
|
|
|
|
|
|
|
|
|
let state = Arc::new(Mutex::new(AppState {
|
|
|
|
|
dictionary: list,
|
|
|
|
|
card_sets: sets,
|
|
|
|
|
connection,
|
|
|
|
|
word_groups: groups,
|
|
|
|
|
}));
|
2026-02-10 11:28:12 +03:00
|
|
|
ScreenState {
|
2026-04-09 22:45:31 +03:00
|
|
|
stack: vec![Selector(SelectorState::new(state.clone()))],
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ScreenState {
|
2026-04-09 22:45:31 +03:00
|
|
|
pub fn boot() -> (ScreenState, Task<RootMessage>) {
|
2026-04-13 22:25:58 +03:00
|
|
|
create_db();
|
2026-02-11 15:53:53 +03:00
|
|
|
(ScreenState::default(), Task::none())
|
|
|
|
|
}
|
2026-04-09 22:45:31 +03:00
|
|
|
pub fn update(&mut self, message: RootMessage) -> Task<RootMessage> {
|
2026-04-14 15:19:46 +03:00
|
|
|
if let Keyboard(e) = message {
|
|
|
|
|
return self.handle_keyboard(e);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 22:45:31 +03:00
|
|
|
state_update!(
|
|
|
|
|
message,
|
|
|
|
|
self.stack,
|
|
|
|
|
Selector,
|
|
|
|
|
Quiz,
|
|
|
|
|
Writing,
|
|
|
|
|
Dictionary,
|
|
|
|
|
DictionaryQuiz,
|
|
|
|
|
Randomizer,
|
|
|
|
|
Repetitions,
|
2026-04-17 12:38:05 +03:00
|
|
|
Repetition,
|
|
|
|
|
Word
|
2026-04-09 22:45:31 +03:00
|
|
|
);
|
2026-02-22 11:07:57 +03:00
|
|
|
Task::none()
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
|
|
|
|
pub fn view(&self) -> Element<'_, RootMessage> {
|
2026-04-09 22:45:31 +03:00
|
|
|
view_navigation!(
|
|
|
|
|
self.stack,
|
|
|
|
|
Quiz,
|
|
|
|
|
Selector,
|
|
|
|
|
Writing,
|
|
|
|
|
Dictionary,
|
|
|
|
|
DictionaryQuiz,
|
|
|
|
|
Randomizer,
|
|
|
|
|
Repetitions,
|
2026-04-17 12:38:05 +03:00
|
|
|
Repetition,
|
|
|
|
|
Word
|
2026-04-09 22:45:31 +03:00
|
|
|
)
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
2026-04-14 15:19:46 +03:00
|
|
|
|
|
|
|
|
fn handle_keyboard(&mut self, message: Event) -> Task<RootMessage> {
|
|
|
|
|
let page = self.stack.last_mut().unwrap();
|
2026-04-20 10:13:39 +03:00
|
|
|
return match page {
|
2026-04-14 15:19:46 +03:00
|
|
|
Repetition(page) => page.press(&message),
|
2026-04-20 10:13:39 +03:00
|
|
|
_ => Task::none(),
|
2026-05-05 12:02:59 +03:00
|
|
|
};
|
2026-04-14 15:19:46 +03:00
|
|
|
}
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
|
|
|
|
|
2026-02-10 12:12:16 +03:00
|
|
|
#[macro_export]
|
|
|
|
|
macro_rules! view_navigation {
|
|
|
|
|
($stack:expr, $($e:ident), *) => {
|
|
|
|
|
match &$stack.last().unwrap() {
|
|
|
|
|
$(
|
|
|
|
|
$e(s) => s.view().map(RootMessage::$e),
|
|
|
|
|
)*
|
|
|
|
|
_ => text!("").into(),
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-10 12:12:16 +03:00
|
|
|
#[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)
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-10 12:12:16 +03:00
|
|
|
)*
|
2026-04-14 15:19:46 +03:00
|
|
|
_ => {}
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-10 12:12:16 +03:00
|
|
|
#[macro_export]
|
|
|
|
|
macro_rules! message_navigation {
|
|
|
|
|
($msg:expr, $stack:expr, $state:expr) => {
|
|
|
|
|
if let Some(new_page) = $state.navigate(&$msg) {
|
2026-02-11 16:19:23 +03:00
|
|
|
if let Page::PreviousPage = new_page {
|
2026-02-10 12:12:16 +03:00
|
|
|
$stack.pop();
|
2026-02-22 11:07:57 +03:00
|
|
|
return Task::none();
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
2026-02-10 12:12:16 +03:00
|
|
|
$stack.push(new_page);
|
|
|
|
|
} else {
|
2026-02-22 11:07:57 +03:00
|
|
|
return $state.update($msg);
|
2026-02-10 11:28:12 +03:00
|
|
|
}
|
2026-02-10 12:12:16 +03:00
|
|
|
};
|
|
|
|
|
}
|
2026-02-10 11:28:12 +03:00
|
|
|
|
2026-02-10 12:12:16 +03:00
|
|
|
trait NavigatedPage<T> {
|
|
|
|
|
fn navigate(&self, message: &T) -> Option<Page>;
|
2026-04-09 22:45:31 +03:00
|
|
|
}
|
2026-04-14 15:19:46 +03:00
|
|
|
|
|
|
|
|
pub trait KeyPressedPage {
|
2026-04-20 10:13:39 +03:00
|
|
|
fn press(&mut self, message: &keyboard::Event) -> Task<RootMessage>;
|
2026-04-17 12:38:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*pub fn danger_button(x: &Theme, status: Status) -> Style{
|
|
|
|
|
Style {
|
|
|
|
|
background: Some(Color(x.palette().danger)),
|
|
|
|
|
text_color: x.palette().text,
|
|
|
|
|
border: Border::default().rounded(2),
|
|
|
|
|
shadow: Default::default(),
|
|
|
|
|
snap: false,
|
|
|
|
|
}
|
2026-05-05 12:02:59 +03:00
|
|
|
}*/
|