From 8fe4f3b8f4572068ee83ea90bb0f64a4271fda36 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Fri, 11 Sep 2026 00:41:36 +0300 Subject: [PATCH] New selected field movement --- src/data/repositories.rs | 4 ++ src/navigation.rs | 1 + src/pages/dictionary.rs | 134 +++++++++++++++++++++++++++++++++------ 3 files changed, 120 insertions(+), 19 deletions(-) diff --git a/src/data/repositories.rs b/src/data/repositories.rs index 95553bf..6f6a39e 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -215,6 +215,10 @@ impl, D: DbLayer> Qu pub fn len(&self) -> usize { self.cache.len() } + + pub fn get(&self, index: usize) -> Option> { + self.cache.get(index).cloned() + } } impl, D: DbLayer> Index diff --git a/src/navigation.rs b/src/navigation.rs index be23c81..6db1a31 100644 --- a/src/navigation.rs +++ b/src/navigation.rs @@ -244,6 +244,7 @@ impl ScreenState { if let Some(page) = self.stack.last_mut() { return match page { Repetition(page) => page.press(&message), + Dictionary(page) => page.press(&message), _ => Task::none(), }; } diff --git a/src/pages/dictionary.rs b/src/pages/dictionary.rs index 1e96357..eec6217 100644 --- a/src/pages/dictionary.rs +++ b/src/pages/dictionary.rs @@ -6,7 +6,7 @@ use crate::data_provider::word_groups::{WordGroupDataLayer, WordGroupRepositoryI use crate::data_provider::words::WordDataLayer; use crate::data_provider::words::WordRepositoryInner; use crate::navigation::Page::{Import, Word}; -use crate::navigation::{NavigatedPage, Page, RootMessage}; +use crate::navigation::{KeyPressedPage, NavigatedPage, Page, RootMessage}; use crate::pages::dictionary::DictionaryMessage::*; use crate::pages::dictionary_quiz::DictionaryQuizState; use crate::pages::import::ImportState; @@ -27,11 +27,17 @@ use iced::widget::text_input::default; 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 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, @@ -46,8 +52,7 @@ pub struct DictionaryState { auto_save_queue: HashMap, DateTime>, displayed_words_query: Query, groups_query: Query, - selected_cell: Option<(EntityHandle, BasicWordFields)>, - hovered_cell: Option<(EntityHandle, BasicWordFields)>, + selected_cell: Option<(EntityHandle, BasicWordFields, usize)>, } #[derive(Clone)] @@ -73,8 +78,17 @@ pub enum DictionaryMessage { ChangeDirection, TrySave(EntityHandle), GotoImport, - SelectField(EntityHandle, BasicWordFields), + SelectField(EntityHandle, BasicWordFields, usize), EditField(String), + ChangeSelectedField(SelectionChangeDirection), +} + +#[derive(Clone, PartialEq, Eq, Hash, Copy)] +pub enum SelectionChangeDirection { + Next, + Previous, + Forward, + Backward, } #[derive(Clone, PartialEq, Eq, Hash, Copy)] @@ -84,6 +98,8 @@ pub enum BasicWordFields { Tags, } +const FIELDS: &[BasicWordFields] = &[Key, Value, Tags]; + impl NavigatedPage for DictionaryState { fn navigate(&mut self, message: &DictionaryMessage) -> Option { if let Back = message { @@ -281,9 +297,9 @@ impl NavigatedPage for DictionaryState { } } GotoImport => {} - SelectField(e, f) => { + SelectField(e, f, index) => { info!("Editing word {}", e.id); - self.selected_cell = Some((e, f)); + self.selected_cell = Some((e, f, index)); return focus(iced::widget::Id::new("editor")); } EditField(value) => { @@ -307,6 +323,51 @@ impl NavigatedPage for DictionaryState { return self.launch_auto_save_offset(selected.0); } } + ChangeSelectedField(change) => { + return if let Some(selected) = self.selected_cell.clone() { + 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) }; + if new_index < self.displayed_words_query.len() { + return SelectField(self.displayed_words_query.get(new_index).unwrap(), selected.1, new_index); + } + + SelectField(selected.0, selected.1, selected.2) + }; + + 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; + if index < 0 { + index = (FIELDS.len() - 1) as i8; + } + if index >= FIELDS.len() as i8 { + index = 0; + } + SelectField(selected.0, FIELDS[index as usize], selected.2) + }; + + Task::done(RootMessage::Dictionary(match change { + Next => { + update_vertical(true) + } + Previous => { + update_vertical(false) + } + Forward => { + update_horizontal(true) + } + Backward => { + update_horizontal(false) + } + })) + } else { + Task::none() + } + } } Task::none() @@ -375,7 +436,6 @@ impl DictionaryState { displayed_words_query: words_query, groups_query, selected_cell: None, - hovered_cell: None, }; result.init(); @@ -435,7 +495,6 @@ impl DictionaryState { } let mut selection = None; - let mut hovered = None; if let Some(cell) = self.selected_cell.as_ref() { if cell.0 == *word_handle { @@ -443,12 +502,6 @@ impl DictionaryState { } } - if let Some(cell) = self.hovered_cell.as_ref() { - if cell.0 == *word_handle { - hovered = Some(cell.1); - } - } - let word_line_data = WordLineState { is_included: self.included.contains(word_handle), key: word.key.clone().into(), @@ -456,7 +509,7 @@ impl DictionaryState { tags: word.tags.clone().into(), handle: *word_handle, selection, - hover: hovered, + number: i, }; let lazy_line = lazy(word_line_data, Self::word_line); @@ -499,7 +552,7 @@ impl DictionaryState { &data.key, data.selection.is_some() && data.selection.unwrap() == BasicWordFields::Key, )) - .on_press(SelectField(data.handle, BasicWordFields::Key)) + .on_press(SelectField(data.handle, BasicWordFields::Key, data.number)), ); line = line.push( hard_mouse_area(field( @@ -507,7 +560,11 @@ impl DictionaryState { &data.value, data.selection.is_some() && data.selection.unwrap() == BasicWordFields::Value, )) - .on_press(SelectField(data.handle, BasicWordFields::Value)) + .on_press(SelectField( + data.handle, + BasicWordFields::Value, + data.number, + )), ); line = line.push( @@ -516,7 +573,7 @@ impl DictionaryState { &data.tags, data.selection.is_some() && data.selection.unwrap() == BasicWordFields::Tags, )) - .on_press(SelectField(data.handle, BasicWordFields::Tags)) + .on_press(SelectField(data.handle, BasicWordFields::Tags, data.number)), ); let line_button = || { @@ -715,6 +772,45 @@ impl DictionaryState { } } +impl KeyPressedPage for DictionaryState { + fn press(&mut self, message: &Event) -> Task { + if let Event::KeyPressed { + key: _, + modified_key: modifier, + physical_key: pk, + location: _, + modifiers: _, + 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::Tab => { + if let Named(keyboard::key::Named::Alt) = modifier { + Task::done(RootMessage::Dictionary(ChangeSelectedField( + SelectionChangeDirection::Backward, + ))) + } else { + Task::done(RootMessage::Dictionary(ChangeSelectedField( + SelectionChangeDirection::Forward, + ))) + } + } + _ => Task::none(), + }; + } + + Task::none() + } +} + pub fn split_with_coma(ts: &str) -> Vec { ts.split(',') .map(|ts| ts.to_lowercase().trim().to_string()) @@ -730,5 +826,5 @@ struct WordLineState { tags: Box, handle: EntityHandle, selection: Option, - hover: Option, + number: usize, }