From d2b6b8da59d1e8202caa79c5b9b6999b1fbb7520 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Tue, 17 Mar 2026 12:16:35 +0300 Subject: [PATCH] Fix random tags order and add local randomizer for sequences --- src/dictionary.rs | 89 ++++++++++++++++++++++++++++++------------ src/dictionary_test.rs | 43 ++++++++++++++------ src/main.rs | 10 +++-- src/randomizer.rs | 71 +++++++++++++++++++++++++++++++++ src/selector.rs | 8 +++- 5 files changed, 178 insertions(+), 43 deletions(-) create mode 100644 src/randomizer.rs diff --git a/src/dictionary.rs b/src/dictionary.rs index 9e36580..3770979 100644 --- a/src/dictionary.rs +++ b/src/dictionary.rs @@ -18,7 +18,9 @@ pub struct DictionaryState { dict: Vec, include_map: Vec, tag_map: HashMap, - reverse: bool + reverse: bool, + search: String, + no_typing: bool, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -52,6 +54,8 @@ pub enum DictionaryMessage { Test, ResetTags, SetReverse(bool), + Search(String), + SetTyping(bool), } impl NavigatedPage for DictionaryState { @@ -62,12 +66,16 @@ impl NavigatedPage for DictionaryState { if let Test = message { if self.include_map.iter().any(|x| *x) { let mut words = vec![]; - for i in 0..self.include_map.len(){ + for i in 0..self.include_map.len() { if self.include_map[i] { words.push(self.dict[i].clone()); } } - return Some(Page::DictionaryQuiz(DictionaryQuizState::new(words, self.reverse))) + return Some(Page::DictionaryQuiz(DictionaryQuizState::new( + words, + self.reverse, + self.no_typing, + ))); } } None @@ -95,9 +103,12 @@ impl DictionaryState { dict: list, tag_map: HashMap::new(), reverse: false, + search: "".to_string(), + no_typing: false, }; result.update_tags(); + result } @@ -118,7 +129,6 @@ impl DictionaryState { } DictionaryMessage::Include(i, b) => self.include_map[i] = b, DictionaryMessage::IncludeTag(t, v) => { - println!("Including tag {}", t); self.tag_map.insert(t, v); self.update_words_include() } @@ -131,9 +141,12 @@ impl DictionaryState { let content = serde_json::to_string_pretty(&self.dict.clone()).unwrap(); fs::write(dir, content.clone()) .unwrap_or_else(|e| println!("Can't write file: {}", e)); - println!("{}", content); - }, + } DictionaryMessage::SetReverse(v) => self.reverse = v, + DictionaryMessage::Search(s) => { + self.search = s; + } + DictionaryMessage::SetTyping(b) => self.no_typing = b, _ => {} } @@ -141,20 +154,22 @@ impl DictionaryState { } pub fn view(&self) -> iced::Element<'_, DictionaryMessage> { - container(row![ - iced::widget::column![ - button("Назад").on_press(Back), - self.words_list(), - row![ - button("Добавить слово").on_press(DictionaryMessage::NewWord), - button("Сохранить словарь").on_press(DictionaryMessage::Save), + container( + row![ + iced::widget::column![ + button("Назад").on_press(Back), + self.words_list(), + row![ + button("Добавить слово").on_press(DictionaryMessage::NewWord), + button("Сохранить словарь").on_press(DictionaryMessage::Save), + ] + .spacing(10) ] - .spacing(10) + .spacing(5), + self.filters() ] - .spacing(5) - .padding(10), - self.filters() - ]) + .spacing(10), + ) .padding(10) .into() } @@ -164,6 +179,15 @@ impl DictionaryState { let mut i = 0; for word in &self.dict { + if !self.search.is_empty() { + if word.key.contains(&self.search) == false + && word.value.contains(&self.search) == false + { + i += 1; + continue; + } + } + let mut line = Row::new().width(Length::Fill).align_y(Center); line = line .push( @@ -173,17 +197,20 @@ impl DictionaryState { .push(space().width(10)); line = line.push( - text_input("Ключ", &word.key).size(20) + text_input("Ключ", &word.key) + .size(20) .width(Length::Fill) .on_input(move |string| DictionaryMessage::SetKey(i, string)), ); line = line.push( - text_input("Значение", &word.value).size(20) + text_input("Значение", &word.value) + .size(20) .width(Length::Fill) .on_input(move |string| DictionaryMessage::SetValue(i, string)), ); line = line.push( - text_input("Тэги", &word.tags).size(20) + text_input("Тэги", &word.tags) + .size(20) .width(Length::Fill) .on_input(move |string| DictionaryMessage::SetTags(i, string)), ); @@ -211,13 +238,21 @@ impl DictionaryState { fn filters(&self) -> iced::Element<'_, DictionaryMessage> { iced::widget::column![ + text_input("Поиск", &self.search) + .on_input(DictionaryMessage::Search) + .width(Length::Fill), text!("Всего слов: {}", self.dict.len()), text!( "Выбрано слов: {}", self.include_map.iter().filter(|i| **i).count() ), self.tags_selector(), - toggler(self.reverse).label("Обратный тест").on_toggle(DictionaryMessage::SetReverse), + toggler(self.no_typing) + .label("Без набора") + .on_toggle(DictionaryMessage::SetTyping), + toggler(self.reverse) + .label("Обратный тест") + .on_toggle(DictionaryMessage::SetReverse), button(text!("Тест").center().width(Length::Fill)) .on_press(Test) .width(Length::Fill), @@ -240,7 +275,9 @@ impl DictionaryState { snap: false, }), ); - for tag in &self.tag_map { + let mut sorted_tags = self.tag_map.iter().collect::>(); + sorted_tags.sort(); + for tag in sorted_tags { col = col.push( checkbox(*tag.1) .label(tag.0) @@ -283,8 +320,6 @@ impl DictionaryState { .map(|(t, _)| t.clone()) .collect::>(); - println!("Including include tags: {:?}", include_tags); - if include_tags.is_empty() { self.include_map.iter_mut().for_each(|x| *x = false); return; @@ -292,8 +327,10 @@ impl DictionaryState { for i in 0..self.include_map.len() { let tags = split_with_coma(self.dict[i].tags.clone()); - if tags.iter().any(|t| include_tags.contains(t)) { + if tags.iter().all(|t| include_tags.contains(t)) { self.include_map[i] = true; + } else { + self.include_map[i] = false; } } } diff --git a/src/dictionary_test.rs b/src/dictionary_test.rs index ca070e8..7a4a450 100644 --- a/src/dictionary_test.rs +++ b/src/dictionary_test.rs @@ -20,10 +20,10 @@ pub struct DictionaryQuizState { is_help: bool, reverse: bool, laps: u32, + no_typing: bool, } #[derive(Debug, Clone)] pub enum DictionaryQuizMessage { - Next, Back, AnswerChanged(String), SubmitAnswer, @@ -40,7 +40,11 @@ impl NavigatedPage for DictionaryQuizState { } impl DictionaryQuizState { - pub fn new(words: Vec, reverse: bool) -> DictionaryQuizState { + pub fn new( + words: Vec, + reverse: bool, + no_typing: bool, + ) -> DictionaryQuizState { DictionaryQuizState { words, current_set: Vec::new(), @@ -51,14 +55,12 @@ impl DictionaryQuizState { is_help: false, reverse, laps: 0, + no_typing, } } pub fn update(&mut self, message: DictionaryQuizMessage) -> Task { match message { - DictionaryQuizMessage::Next => { - self.next(); - } DictionaryQuizMessage::Back => {} DictionaryQuizMessage::AnswerChanged(c) => self.answer = c.clone(), DictionaryQuizMessage::SubmitAnswer => self.submit(), @@ -71,7 +73,7 @@ impl DictionaryQuizState { container( iced::widget::column![ self.laps(), - row![ + iced::widget::column![ text!("{}", self.view).size(54), text!( "{}", @@ -80,10 +82,10 @@ impl DictionaryQuizState { } else { String::new() } - ), + ).size(20).align_y(alignment::Vertical::Center), ] - .align_y(alignment::Vertical::Center) - .spacing(20), + .align_x(alignment::Horizontal::Center) + .spacing(5), text_input("Перевод", &self.answer) .size(28) .width(250) @@ -112,14 +114,29 @@ impl DictionaryQuizState { .center_x(Fill) .into() } - fn next(&mut self) {} - fn submit(&mut self) { if self.view == "---" { self.show_next(); return; } + if self.no_typing { + self.no_type_submit(); + } else { + self.default_submit(); + } + } + + fn no_type_submit(&mut self) { + if self.is_help { + self.is_help = false; + self.show_next() + }else { + self.is_help = true; + } + } + + fn default_submit(&mut self) { if !self.is_help { self.score.total += 1; } @@ -182,8 +199,8 @@ impl DictionaryQuizState { col.spacing(10).into() } - fn appeal_button(&self) -> iced::Element<'_, DictionaryQuizMessage> { - if self.is_help { + fn appeal_button(&self) -> Element<'_, DictionaryQuizMessage> { + if self.is_help && self.no_typing == false { return button("Апелляция") .on_press(DictionaryQuizMessage::Appeal) .into(); diff --git a/src/main.rs b/src/main.rs index 38c2bec..a080f42 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,16 +5,18 @@ mod selector; mod writing; mod dictionary; mod dictionary_test; +mod randomizer; use crate::quiz::*; use crate::selector::*; use crate::writing::{WritingMessage, WritingState}; -use crate::Page::{Dictionary, DictionaryQuiz, Quiz, Selector, Writing}; +use crate::Page::{Dictionary, DictionaryQuiz, Quiz, Randomizer, Selector, Writing}; use iced::widget::text; use iced::{Font, Task}; use iced::Element; use crate::dictionary::{DictionaryMessage, DictionaryState}; use crate::dictionary_test::{DictionaryQuizMessage, DictionaryQuizState}; +use crate::randomizer::randomizer::{ RandomizerMessage, RandomizerState}; fn main() -> iced::Result { iced::application(ScreenState::boot, ScreenState::update, ScreenState::view) @@ -28,6 +30,7 @@ pub enum RootMessage { Writing(WritingMessage), Dictionary(DictionaryMessage), DictionaryQuiz(DictionaryQuizMessage), + Randomizer(RandomizerMessage) } enum Page { @@ -36,6 +39,7 @@ enum Page { Writing(WritingState), Dictionary(DictionaryState), DictionaryQuiz(DictionaryQuizState), + Randomizer(RandomizerState), PreviousPage, } @@ -62,12 +66,12 @@ impl ScreenState { (ScreenState::default(), Task::none()) } pub fn update(&mut self, message: RootMessage) -> Task { - state_update!(message, self.stack, Selector, Quiz, Writing, Dictionary, DictionaryQuiz); + state_update!(message, self.stack, Selector, Quiz, Writing, Dictionary, DictionaryQuiz, Randomizer); Task::none() } pub fn view(&self) -> Element<'_, RootMessage> { - view_navigation!(self.stack, Quiz, Selector, Writing, Dictionary, DictionaryQuiz) + view_navigation!(self.stack, Quiz, Selector, Writing, Dictionary, DictionaryQuiz, Randomizer) } } diff --git a/src/randomizer.rs b/src/randomizer.rs new file mode 100644 index 0000000..2ce03d0 --- /dev/null +++ b/src/randomizer.rs @@ -0,0 +1,71 @@ + +pub mod randomizer { + use crate::randomizer::randomizer::RandomizerMessage::{Back, Start}; + use crate::{NavigatedPage, Page, RootMessage}; + use iced::widget::{button, container, text_editor}; + use iced::Task; + use rand::prelude::SliceRandom; + + #[derive(Clone, Debug)] + pub struct RandomizerState { + text: text_editor::Content, + list: Vec + } + + #[derive(Debug, Clone)] + pub enum RandomizerMessage { + Back, + Start, + Edit(text_editor::Action), + } + + impl NavigatedPage for RandomizerState { + fn navigate(&self, message: &RandomizerMessage) -> Option { + if let RandomizerMessage::Back = message { + return Some(Page::PreviousPage); + } + None + } + } + + impl Default for RandomizerState { + fn default() -> Self { + Self::new() + } + } + + impl RandomizerState { + pub fn new() -> RandomizerState { + RandomizerState { text: Default::default(), list: vec![] } + } + + pub fn update(&mut self, message: RandomizerMessage) -> Task { + match message { + RandomizerMessage::Edit(action) => { + self.text.perform(action); + self.list = self.text.text().split("\n").map(|s| s.to_string()).collect(); + }, + RandomizerMessage::Start => { + self.list.shuffle(&mut rand::rng()); + self.text = text_editor::Content::with_text(self.list.join("\n").as_str()); + } + _ => {} + } + Task::none() + } + + pub fn view(&self) -> iced::Element<'_, RandomizerMessage> { + container( + iced::widget::column![ + button("Назад").on_press(Back), + text_editor(&self.text).on_action(RandomizerMessage::Edit + ).width(400).height(400).placeholder("Каждый элемент с новой строки"), + button("Начать").on_press(Start), + ] + .spacing(5), + ) + .padding(10) + .into() + } + } +} diff --git a/src/selector.rs b/src/selector.rs index b5038a2..866f2aa 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -6,6 +6,7 @@ use crate::{NavigatedPage, Page, QuizState, RootMessage}; use iced::widget::*; use iced::{alignment, Element, Task}; use crate::dictionary::DictionaryState; +use crate::randomizer::randomizer::RandomizerState; pub struct SelectorState { pub set: KanaSet, @@ -28,6 +29,7 @@ pub enum SelectorMessage { Check(usize, bool), ChangeMode(bool), ToDictionary, + ToRandomize, } impl NavigatedPage for SelectorState { @@ -45,6 +47,9 @@ impl NavigatedPage for SelectorState { if let SelectorMessage::ToDictionary = message { return Some(Page::Dictionary(DictionaryState::default())) } + if let SelectorMessage::ToRandomize = message { + return Some(Page::Randomizer(RandomizerState::default())) + } None } } @@ -67,7 +72,8 @@ impl SelectorState { container( iced::widget::column![ row![button("Переключить азбуки").on_press(SelectorMessage::Change), - button("Словарь").on_press(SelectorMessage::ToDictionary),].spacing(10), + button("Словарь").on_press(SelectorMessage::ToDictionary), + button("Рандомайзер").on_press(SelectorMessage::ToRandomize)].spacing(10), self.rows_selector(), toggler(self.is_writing) .label("Режим письма")