From adb6388ee2acbace81b8add3cebb10eab6482096 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Fri, 11 Sep 2026 11:47:04 +0300 Subject: [PATCH] Prepare for extended word edit --- src/navigation.rs | 6 -- src/pages/dictionary.rs | 219 ++++++++++++++++++++++------------------ src/pages/word.rs | 23 ++--- 3 files changed, 126 insertions(+), 122 deletions(-) diff --git a/src/navigation.rs b/src/navigation.rs index 6db1a31..05c9600 100644 --- a/src/navigation.rs +++ b/src/navigation.rs @@ -21,7 +21,6 @@ use crate::pages::repetition_settings::{RepetitionSettingsMessage, RepetitionSet use crate::pages::repetitions::{RepetitionsMessage, RepetitionsState}; use crate::pages::selector::{SelectorMessage, SelectorState}; use crate::pages::sync::{SyncMessage, SyncState}; -use crate::pages::word::{WordMessage, WordState}; use crate::pages::writing::{WritingMessage, WritingState}; use crate::state_navigate_handle; use crate::state_update; @@ -56,7 +55,6 @@ pub enum RootMessage { Randomizer(RandomizerMessage), Repetitions(RepetitionsMessage), Repetition(RepetitionMessage), - Word(WordMessage), Sync(SyncMessage), History(HistoryMessage), RepetitionSettings(RepetitionSettingsMessage), @@ -78,7 +76,6 @@ pub enum Page { Randomizer(RandomizerState), Repetitions(RepetitionsState), Repetition(RepetitionState), - Word(WordState), Sync(SyncState), History(HistoryState), RepetitionSettings(RepetitionSettingsState), @@ -182,7 +179,6 @@ impl ScreenState { Randomizer, Repetitions, Repetition, - Word, Sync, History, RepetitionSettings, @@ -206,7 +202,6 @@ impl ScreenState { Randomizer, Repetitions, Repetition, - Word, Sync, History, RepetitionSettings, @@ -227,7 +222,6 @@ impl ScreenState { Randomizer, Repetitions, Repetition, - Word, Sync, History, RepetitionSettings, diff --git a/src/pages/dictionary.rs b/src/pages/dictionary.rs index eec6217..e096178 100644 --- a/src/pages/dictionary.rs +++ b/src/pages/dictionary.rs @@ -1,16 +1,18 @@ use crate::app::AppState; -use crate::data::Id; use crate::data::entities::{EntityHandle, WordData, WordGroup}; use crate::data::repositories::{Query, Repository}; +use crate::data::Id; use crate::data_provider::word_groups::{WordGroupDataLayer, WordGroupRepositoryInner}; use crate::data_provider::words::WordDataLayer; use crate::data_provider::words::WordRepositoryInner; -use crate::navigation::Page::{Import, Word}; +use crate::navigation::Page::Import; use crate::navigation::{KeyPressedPage, NavigatedPage, Page, RootMessage}; +use crate::pages::dictionary::BasicWordFields::*; use crate::pages::dictionary::DictionaryMessage::*; +use crate::pages::dictionary::SelectionChangeDirection::*; use crate::pages::dictionary_quiz::DictionaryQuizState; use crate::pages::import::ImportState; -use crate::pages::word::WordState; +use crate::pages::word::{WordEditor, WordMessage}; use crate::push_error_task; use crate::styling::*; use crate::widgets::hard_mouse_area::hard_mouse_area; @@ -22,22 +24,20 @@ use iced::widget::button::Style; use iced::widget::button::{danger, text}; use iced::widget::operation::focus; use iced::widget::space::horizontal; -use iced::widget::text_input::Status::Active; use iced::widget::text_input::default; +use iced::widget::text_input::Status::Active; use iced::widget::*; use iced::{Border, Color, Shadow, Task}; -use iced_core::Length::Fill; use iced_core::keyboard; -use iced_core::keyboard::Event; -use iced_core::keyboard::Key::Named; use iced_core::keyboard::key::Physical::Code; +use iced_core::keyboard::{Event, Modifiers}; +use iced_core::keyboard::Key::Named; +use iced_core::Length::{Fill, FillPortion}; use log::info; use rand::random_range; use std::ops::Add; use std::sync::Arc; use std::time::{Duration, Instant}; -use crate::pages::dictionary::BasicWordFields::*; -use crate::pages::dictionary::SelectionChangeDirection::*; pub struct DictionaryState { state: AppState, @@ -53,6 +53,7 @@ pub struct DictionaryState { displayed_words_query: Query, groups_query: Query, selected_cell: Option<(EntityHandle, BasicWordFields, usize)>, + word_editor: Option, } #[derive(Clone)] @@ -61,7 +62,8 @@ pub enum DictionaryMessage { SetTags(EntityHandle, String), SetKey(EntityHandle, String), SetValue(EntityHandle, String), - WordAction(EntityHandle), + ExtendedEdit(EntityHandle), + DeleteWord(EntityHandle), NewWord, Include(EntityHandle, bool), IncludeTag(String, bool), @@ -81,6 +83,7 @@ pub enum DictionaryMessage { SelectField(EntityHandle, BasicWordFields, usize), EditField(String), ChangeSelectedField(SelectionChangeDirection), + EditorAction(WordMessage), } #[derive(Clone, PartialEq, Eq, Hash, Copy)] @@ -122,11 +125,7 @@ impl NavigatedPage for DictionaryState { self.state.clone(), ))); } - if let WordAction(handle) = message - && handle.real() - { - return Some(Word(WordState::new(*handle, self.state.clone()))); - } + if let GotoImport = message { return Some(Import(ImportState::new(self.state.clone()))); } @@ -186,14 +185,8 @@ impl NavigatedPage for DictionaryState { return self.launch_auto_save_offset(i); } - WordAction(i) => { - let mut dictionary = self.state.dictionary.write(); - - if let Err(err) = dictionary.delete(i) { - return push_error_task!(err); - } - - self.displayed_words_query.load(&dictionary); + ExtendedEdit(i) => { + self.word_editor = Some(WordEditor::new(i, self.state.clone())); } Include(i, b) => { if b { @@ -308,13 +301,13 @@ impl NavigatedPage for DictionaryState { let mut dictionary = self.state.dictionary.write(); let word = dictionary.get_mut_static(selected.0).unwrap(); match selected.1 { - BasicWordFields::Key => { + Key => { word.key = value; } - BasicWordFields::Value => { + Value => { word.value = value; } - BasicWordFields::Tags => { + Tags => { word.key = value; } } @@ -324,14 +317,22 @@ impl NavigatedPage for DictionaryState { } } ChangeSelectedField(change) => { - return if let Some(selected) = self.selected_cell.clone() { + return if let Some(selected) = self.selected_cell { let update_vertical = move |mut dir: bool| { if self.reverse_list { dir = !dir; } - let new_index = if dir { selected.2.saturating_add(1) } else { selected.2.saturating_sub(1) }; + let new_index = if dir { + selected.2.saturating_add(1) + } else { + selected.2.saturating_sub(1) + }; if new_index < self.displayed_words_query.len() { - return SelectField(self.displayed_words_query.get(new_index).unwrap(), selected.1, new_index); + return SelectField( + self.displayed_words_query.get(new_index).unwrap(), + selected.1, + new_index, + ); } SelectField(selected.0, selected.1, selected.2) @@ -340,7 +341,8 @@ impl NavigatedPage for DictionaryState { let update_horizontal = move |dir: bool| { let current = selected.1; let delta = if dir { 1 } else { -1 }; - let mut index = FIELDS.iter().position(|&f| f == current).unwrap() as i8 + delta; + let mut index = + FIELDS.iter().position(|&f| f == current).unwrap() as i8 + delta; if index < 0 { index = (FIELDS.len() - 1) as i8; } @@ -351,22 +353,26 @@ impl NavigatedPage for DictionaryState { }; Task::done(RootMessage::Dictionary(match change { - Next => { - update_vertical(true) - } - Previous => { - update_vertical(false) - } - Forward => { - update_horizontal(true) - } - Backward => { - update_horizontal(false) - } + Next => update_vertical(true), + Previous => update_vertical(false), + Forward => update_horizontal(true), + Backward => update_horizontal(false), })) } else { Task::none() + }; + } + DeleteWord(i) => { + let mut dictionary = self.state.dictionary.write(); + + if let Err(err) = dictionary.delete(i) { + return push_error_task!(err); } + + self.displayed_words_query.load(&dictionary); + } + EditorAction(action) => { + self.word_editor.as_mut().unwrap().update(action); } } @@ -379,37 +385,15 @@ impl NavigatedPage for DictionaryState { iced::widget::column![ self.groups_panel(), row![horizontal().width(8), self.words_list(),], - { - let mut display = "".to_string(); - if let Some(selected) = self.selected_cell.as_ref() { - let mut dictionary = self.state.dictionary.write(); - let word = dictionary.get_mut_static(selected.0).unwrap(); - - match selected.1 { - BasicWordFields::Key => { - display = word.key.clone(); - } - BasicWordFields::Value => { - display = word.value.clone(); - } - BasicWordFields::Tags => { - display = word.tags.clone(); - } - } - } - text_input("", display.as_str()) - .width(Fill) - .id(iced::widget::Id::new("editor")) - .on_input(EditField) - }, + self.selection_editor(), row![ self.add_word_button(), horizontal().width(Fill), button("Импорт").style(text).on_press(GotoImport), ] ] - .spacing(5), - self.filters(), + .spacing(5).width(FillPortion(4)), + self.right_panel(), ] .spacing(DEFAULT_SPACING) .into(), @@ -436,6 +420,7 @@ impl DictionaryState { displayed_words_query: words_query, groups_query, selected_cell: None, + word_editor: None, }; result.init(); @@ -496,10 +481,10 @@ impl DictionaryState { let mut selection = None; - if let Some(cell) = self.selected_cell.as_ref() { - if cell.0 == *word_handle { - selection = Some(cell.1); - } + if let Some(cell) = self.selected_cell.as_ref() + && cell.0 == *word_handle + { + selection = Some(cell.1); } let word_line_data = WordLineState { @@ -540,7 +525,7 @@ impl DictionaryState { default_style.border.radius = 0.0.into(); if selected { default_style.border.color = x.extended_palette().secondary.base.color; - default_style.border.width = 3.0_f32.into(); + default_style.border.width = 3.0_f32; } default_style }) @@ -550,36 +535,32 @@ impl DictionaryState { hard_mouse_area(field( "Слово", &data.key, - data.selection.is_some() && data.selection.unwrap() == BasicWordFields::Key, + data.selection.is_some() && data.selection.unwrap() == Key, )) - .on_press(SelectField(data.handle, BasicWordFields::Key, data.number)), + .on_press(SelectField(data.handle, Key, data.number)), ); line = line.push( hard_mouse_area(field( "Перевод", &data.value, - data.selection.is_some() && data.selection.unwrap() == BasicWordFields::Value, + data.selection.is_some() && data.selection.unwrap() == Value, )) - .on_press(SelectField( - data.handle, - BasicWordFields::Value, - data.number, - )), + .on_press(SelectField(data.handle, Value, data.number)), ); line = line.push( hard_mouse_area(field( "Теги", &data.tags, - data.selection.is_some() && data.selection.unwrap() == BasicWordFields::Tags, + data.selection.is_some() && data.selection.unwrap() == Tags, )) - .on_press(SelectField(data.handle, BasicWordFields::Tags, data.number)), + .on_press(SelectField(data.handle, Tags, data.number)), ); let line_button = || { if !data.handle.real() { return button("-") - .on_press(WordAction(data.handle)) + .on_press(ExtendedEdit(data.handle)) .style(|_x: &Theme, _status: button::Status| Style { background: None, text_color: Color::BLACK, @@ -591,13 +572,21 @@ impl DictionaryState { } button("") - .on_press(WordAction(data.handle)) + .on_press(ExtendedEdit(data.handle)) .style(button::primary) .width(15) }; line.push(line_button()).push(space().width(10)) } + fn right_panel(&self) -> iced::Element<'_, DictionaryMessage> { + if let Some(editor) = &self.word_editor { + container(editor.view().map(EditorAction)).width(FillPortion(3)).into() + } else { + self.filters() + } + } + fn filters(&self) -> iced::Element<'_, DictionaryMessage> { let dict = &self.state.dictionary.read(); @@ -619,7 +608,7 @@ impl DictionaryState { .on_press(GotoTest) .width(Fill), ] - .width(250) + .width(FillPortion(1)) .spacing(DEFAULT_SPACING) .into() } @@ -719,6 +708,31 @@ impl DictionaryState { info!("Time {}", time.elapsed().as_micros()); } + fn selection_editor(&self) -> iced::Element<'_, DictionaryMessage> { + let mut display = "".to_string(); + if let Some(selected) = self.selected_cell.as_ref() { + let mut dictionary = self.state.dictionary.write(); + let word = dictionary.get_mut_static(selected.0).unwrap(); + + match selected.1 { + Key => { + display = word.key.clone(); + } + Value => { + display = word.value.clone(); + } + Tags => { + display = word.tags.clone(); + } + } + } + text_input("", display.as_str()) + .width(Fill) + .id(iced::widget::Id::new("editor")) + .on_input(EditField) + .into() + } + fn groups_panel(&self) -> iced::Element<'_, DictionaryMessage> { let mut row = Row::new(); row = row.push(button("+").style(text).on_press(CreateGroup)); @@ -779,30 +793,37 @@ impl KeyPressedPage for DictionaryState { modified_key: modifier, physical_key: pk, location: _, - modifiers: _, + modifiers: mods, text: _, repeat: _, } = message && let Code(code) = pk { return match code { - keyboard::key::Code::ArrowDown => Task::done(RootMessage::Dictionary( - ChangeSelectedField(SelectionChangeDirection::Next), - )), - keyboard::key::Code::ArrowUp => Task::done(RootMessage::Dictionary( - ChangeSelectedField(SelectionChangeDirection::Previous), - )), + keyboard::key::Code::ArrowDown => { + Task::done(RootMessage::Dictionary(ChangeSelectedField(Next))) + } + keyboard::key::Code::ArrowUp => { + Task::done(RootMessage::Dictionary(ChangeSelectedField(Previous))) + } keyboard::key::Code::Tab => { - if let Named(keyboard::key::Named::Alt) = modifier { - Task::done(RootMessage::Dictionary(ChangeSelectedField( - SelectionChangeDirection::Backward, - ))) + return if *mods == Modifiers::CTRL && let Some(selected) = self.selected_cell { + Task::done(RootMessage::Dictionary(ExtendedEdit(selected.0))) + } else if let Named(keyboard::key::Named::Alt) = modifier { + Task::done(RootMessage::Dictionary(ChangeSelectedField(Backward))) } else { - Task::done(RootMessage::Dictionary(ChangeSelectedField( - SelectionChangeDirection::Forward, - ))) + Task::done(RootMessage::Dictionary(ChangeSelectedField(Forward))) } } + keyboard::key::Code::Escape => { + if self.word_editor.is_some() { + self.word_editor = None; + }else if self.selected_cell.is_some() { + self.selected_cell = None; + } + + Task::none() + } _ => Task::none(), }; } diff --git a/src/pages/word.rs b/src/pages/word.rs index 6232f66..239ec1a 100644 --- a/src/pages/word.rs +++ b/src/pages/word.rs @@ -8,22 +8,13 @@ use iced::widget::button::danger; use iced::widget::{button, column, row, space, text, text_input}; use iced::{Element, Fill, Task}; -pub struct WordState { +pub struct WordEditor { state: AppState, handle: EntityHandle, } -impl NavigatedPage for WordState { - fn navigate(&mut self, message: &WordMessage) -> Option { - if let Back = message { - Some(PreviousPage) - } else { - None - } - } - - fn navigated(&mut self) {} - fn update(&mut self, message: WordMessage) -> Task { +impl WordEditor { + pub(crate) fn update(&mut self, message: WordMessage) { // match message { // Back => {} // Save => { @@ -60,10 +51,9 @@ impl NavigatedPage for WordState { // self.word.additional.remove(key.as_str()); // } // } - Task::none() } - fn view(&self) -> Element<'_, WordMessage> { + pub(crate) fn view(&self) -> Element<'_, WordMessage> { // let mut fast_add = row![]; // if !self.word.additional.contains_key("reading") { // fast_add = fast_add.push( @@ -129,13 +119,13 @@ impl NavigatedPage for WordState { } } -impl WordState { +impl WordEditor { pub fn new(handle: EntityHandle, state: AppState) -> Self { Self { state, handle } } } -impl WordState { +impl WordEditor { fn get_view_for_more(&self, value: (&String, &String)) -> Element<'_, WordMessage> { match value.0.as_str() { "reading" => self.reading_field(value.1), @@ -180,7 +170,6 @@ impl WordState { #[derive(Debug, Clone)] pub enum WordMessage { Save, - Back, Delete, SetTags(String), SetKey(String),