From 12421d6d5db92a94ae406b045c14c7d839ece57d Mon Sep 17 00:00:00 2001 From: blindfs Date: Tue, 5 May 2026 12:44:41 +0800 Subject: [PATCH 1/5] refactor: more functions to reduce indentation --- src/app_executor.rs | 458 ++++++++++++++++++++++---------------------- 1 file changed, 234 insertions(+), 224 deletions(-) diff --git a/src/app_executor.rs b/src/app_executor.rs index 5a696c5..9364ff2 100644 --- a/src/app_executor.rs +++ b/src/app_executor.rs @@ -981,6 +981,236 @@ impl AppExecutor { } } + async fn perform_filtering(&mut self, key_char: char, mode: FilterMode) { + if key_char == '-' { + self.key_prefix.pop(); + } else if self.key_prefix.len() < self.hint_width as usize { + self.key_prefix.push(key_char); + } + match mode { + FilterMode::OCR => { + self.ocr_res_filtering(); + } + FilterMode::Generic => { + self.filter_by_key().await; + } + FilterMode::WordPicking => { + self.clear_drawing(); + let (matched_words, digits) = self.draw_word_picker(); + self.hint_width = digits; + + if self.key_prefix.len() == digits as usize + && matched_words.len() == 1 + && let Some((idx, text)) = matched_words.first() + { + if self.multi_selection.is_on { + if let Some((idx1, idx2)) = self.multi_selection.set_one_side(*idx) { + let text = self + .word_picker + .as_ref() + .expect("Internal Error: no word picker set yet.") + .select_range(idx1, idx2) + .expect("Internal Error: wrong word picker indexing."); + self.clear_drawing(); + self.update_selected_text_and_show_menu(text.clone()) + } else { + self.key_prefix.clear(); + self.draw_word_picker(); + } + } else { + self.clear_drawing(); + self.update_selected_text_and_show_menu(text.clone()) + } + } + } + } + } + + fn perform_text_action(&mut self, ta: TextAction) { + let Some(ElementOfInterest { + context: Some(text), + .. + }) = self.selected.as_ref() + else { + panic!("Internal Error: No selected text in Mode::TextActionMenu."); + }; + + let text = text.clone(); + + // Clear old menu no matter which action is taken + self.clear_drawing(); + + // TODO: + // 1. URL handling + let keep_drawing = match ta { + TextAction::Copy => { + text_to_clipboard(&text); + self.notify_then_deactivate("Copied to clipboard.", Level::Info); + true + } + TextAction::Dictionary => { + log::trace!("Looking up `{text}` in Apple Dictionary."); + if let Some(attr_string) = get_dictionary_attributed_string( + &text, + &self.config.dictionaries, + &self.config.theme, + ) { + let CGSize { width, height } = self.screen_size; + let (text_size, _) = estimate_frame_for_text(&attr_string, (width, height)); + self.window.draw_attributed_string( + attr_string, + self.screen_size, + text_size, + &self.config.theme, + ); + } else { + self.notify_then_deactivate("No definition found.", Level::Warn); + } + true + } + TextAction::Split => { + let word_picker = WordPicker::new(text); + + self.clear_cache(); + self.word_picker = Some(word_picker); + self.set_mode(Mode::WordPicking); + self.draw_word_picker(); + true + } + TextAction::Editor => { + if let Err(e) = self.open_editor(&text) { + self.notify_then_deactivate( + &format!("Failed to open editor: {e}"), + Level::Error, + ); + true + } else { + false + } + } + TextAction::UserDefined(idx) => { + self.take_external_action(idx, &text); + true + } + }; + + if !keep_drawing { + self.deactivate(); + } + } + + fn perform_scroll_action(&mut self, sa: ScrollAction) { + let Some(ElementOfInterest { + element: Some(element), + role, + frame, + .. + }) = self.selected.as_ref() + else { + panic!("An element is supposed to be selected before entering Mode::Scrolling!") + }; + + if *role == RoleOfInterest::ScrollBar { + let Some(old_val) = element + .value() + .ok() + .and_then(|v| v.downcast::()) + .and_then(|f| f.to_f64()) + else { + self.deactivate(); + return; + }; + + let scroll_unit = self.config.scroll_distance; + match sa { + ScrollAction::DownRight => { + Self::scroll_to_value(element, old_val + scroll_unit); + } + ScrollAction::UpLeft => { + Self::scroll_to_value(element, old_val - scroll_unit); + } + ScrollAction::IncreaseDistance => { + self.config.scroll_distance *= 1.5; + } + ScrollAction::DecreaseDistance => { + self.config.scroll_distance /= 1.5; + } + ScrollAction::Top => { + Self::scroll_to_value(element, 0.0); + self.draw_scrolling_menu(""); + } + ScrollAction::Bottom => { + Self::scroll_to_value(element, 1.0); + self.draw_scrolling_menu(""); + } + } + } else { + let distance = (frame.size().1 * self.config.scroll_distance).max(1.0) as i64; + match sa { + ScrollAction::DownRight => { + Self::simulate_event(&EventType::Wheel { + delta_x: 0, + delta_y: -distance, + }); + } + ScrollAction::UpLeft => { + Self::simulate_event(&EventType::Wheel { + delta_x: 0, + delta_y: distance, + }); + } + ScrollAction::IncreaseDistance => { + self.config.scroll_distance *= 1.5; + } + ScrollAction::DecreaseDistance => { + self.config.scroll_distance /= 1.5; + } + ScrollAction::Top => { + Self::simulate_event(&EventType::Wheel { + delta_x: 0, + delta_y: 999999, + }); + self.draw_scrolling_menu(""); + } + ScrollAction::Bottom => { + Self::simulate_event(&EventType::Wheel { + delta_x: 0, + delta_y: -999999, + }); + self.draw_scrolling_menu(""); + } + } + } + } + + fn handle_file_update(&mut self, pb: PathBuf) { + if pb == self.temp_file + && let Ok(new_text) = std::fs::read_to_string(&self.temp_file) + { + self.update_editing_text(new_text); + } else if pb != self.temp_file { + match GlyphlowConfig::load_config(&pb) { + Ok(mut new_config) => { + self.element_cache.reload_config(&new_config); + let need_warning = !self.config.safe_reload(&mut new_config); + self.config = new_config; + + if need_warning { + self.notify_then_deactivate( + "Restart the app to apply full changes", + Level::Warn, + ); + } else { + self.notify_then_deactivate("Configuration reloaded", Level::Info); + } + } + Err(msg) => { + self.notify_then_deactivate(&msg, Level::Error); + } + }; + } + } + fn update_selected_text(&mut self, new_text: String) { if let Some(ElementOfInterest { context, .. }) = self.selected.as_mut() { *context = Some(new_text); @@ -1061,206 +1291,12 @@ impl AppExecutor { } }, AppSignal::Filter(key_char, mode) => { - if key_char == '-' { - self.key_prefix.pop(); - } else if self.key_prefix.len() < self.hint_width as usize { - self.key_prefix.push(key_char); - } - match mode { - FilterMode::OCR => { - self.ocr_res_filtering(); - } - FilterMode::Generic => { - self.filter_by_key().await; - } - FilterMode::WordPicking => { - self.clear_drawing(); - let (matched_words, digits) = self.draw_word_picker(); - self.hint_width = digits; - - if self.key_prefix.len() == digits as usize - && matched_words.len() == 1 - && let Some((idx, text)) = matched_words.first() - { - if self.multi_selection.is_on { - if let Some((idx1, idx2)) = self.multi_selection.set_one_side(*idx) - { - let text = self - .word_picker - .as_ref() - .expect("Internal Error: no word picker set yet.") - .select_range(idx1, idx2) - .expect("Internal Error: wrong word picker indexing."); - self.clear_drawing(); - self.update_selected_text_and_show_menu(text.clone()) - } else { - self.key_prefix.clear(); - self.draw_word_picker(); - } - } else { - self.clear_drawing(); - self.update_selected_text_and_show_menu(text.clone()) - } - } - } - } + self.perform_filtering(key_char, mode).await; } AppSignal::ScrollAction(sa) => { - let Some(ElementOfInterest { - element: Some(element), - role, - frame, - .. - }) = self.selected.as_ref() - else { - panic!("An element is supposed to be selected before entering Mode::Scrolling!") - }; - - if *role == RoleOfInterest::ScrollBar { - let Some(old_val) = element - .value() - .ok() - .and_then(|v| v.downcast::()) - .and_then(|f| f.to_f64()) - else { - self.deactivate(); - return; - }; - - let scroll_unit = self.config.scroll_distance; - match sa { - ScrollAction::DownRight => { - Self::scroll_to_value(element, old_val + scroll_unit); - } - ScrollAction::UpLeft => { - Self::scroll_to_value(element, old_val - scroll_unit); - } - ScrollAction::IncreaseDistance => { - self.config.scroll_distance *= 1.5; - } - ScrollAction::DecreaseDistance => { - self.config.scroll_distance /= 1.5; - } - ScrollAction::Top => { - Self::scroll_to_value(element, 0.0); - self.draw_scrolling_menu(""); - } - ScrollAction::Bottom => { - Self::scroll_to_value(element, 1.0); - self.draw_scrolling_menu(""); - } - } - } else { - let distance = (frame.size().1 * self.config.scroll_distance).max(1.0) as i64; - match sa { - ScrollAction::DownRight => { - Self::simulate_event(&EventType::Wheel { - delta_x: 0, - delta_y: -distance, - }); - } - ScrollAction::UpLeft => { - Self::simulate_event(&EventType::Wheel { - delta_x: 0, - delta_y: distance, - }); - } - ScrollAction::IncreaseDistance => { - self.config.scroll_distance *= 1.5; - } - ScrollAction::DecreaseDistance => { - self.config.scroll_distance /= 1.5; - } - ScrollAction::Top => { - Self::simulate_event(&EventType::Wheel { - delta_x: 0, - delta_y: 999999, - }); - self.draw_scrolling_menu(""); - } - ScrollAction::Bottom => { - Self::simulate_event(&EventType::Wheel { - delta_x: 0, - delta_y: -999999, - }); - self.draw_scrolling_menu(""); - } - } - } - } - AppSignal::TextAction(ta) => { - let Some(ElementOfInterest { - context: Some(text), - .. - }) = self.selected.as_ref() - else { - panic!("Internal Error: No selected text in Mode::TextActionMenu."); - }; - - let text = text.clone(); - - // Clear old menu no matter which action is taken - self.clear_drawing(); - - // TODO: - // 1. URL handling - let keep_drawing = match ta { - TextAction::Copy => { - text_to_clipboard(&text); - self.notify_then_deactivate("Copied to clipboard.", Level::Info); - true - } - TextAction::Dictionary => { - log::trace!("Looking up `{text}` in Apple Dictionary."); - if let Some(attr_string) = get_dictionary_attributed_string( - &text, - &self.config.dictionaries, - &self.config.theme, - ) { - let CGSize { width, height } = self.screen_size; - let (text_size, _) = - estimate_frame_for_text(&attr_string, (width, height)); - self.window.draw_attributed_string( - attr_string, - self.screen_size, - text_size, - &self.config.theme, - ); - } else { - self.notify_then_deactivate("No definition found.", Level::Warn); - } - true - } - TextAction::Split => { - let word_picker = WordPicker::new(text); - - self.clear_cache(); - self.word_picker = Some(word_picker); - self.set_mode(Mode::WordPicking); - self.draw_word_picker(); - true - } - TextAction::Editor => { - if let Err(e) = self.open_editor(&text) { - self.notify_then_deactivate( - &format!("Failed to open editor: {e}"), - Level::Error, - ); - true - } else { - false - } - } - TextAction::UserDefined(idx) => { - self.take_external_action(idx, &text); - true - } - }; - - if !keep_drawing { - self.deactivate(); - } + self.perform_scroll_action(sa); } + AppSignal::TextAction(ta) => self.perform_text_action(ta), AppSignal::ScreenShot => { self.clear_drawing(); let frame = if let Some(eoi) = self.selected.as_ref() { @@ -1286,33 +1322,7 @@ impl AppExecutor { self.activate(Target::ImageOCR); } } - AppSignal::FileUpdate(pb) => { - if pb == self.temp_file - && let Ok(new_text) = std::fs::read_to_string(&self.temp_file) - { - self.update_editing_text(new_text); - } else if pb != self.temp_file { - match GlyphlowConfig::load_config(&pb) { - Ok(mut new_config) => { - self.element_cache.reload_config(&new_config); - let need_warning = !self.config.safe_reload(&mut new_config); - self.config = new_config; - - if need_warning { - self.notify_then_deactivate( - "Restart the app to apply full changes", - Level::Warn, - ); - } else { - self.notify_then_deactivate("Configuration reloaded", Level::Info); - } - } - Err(msg) => { - self.notify_then_deactivate(&msg, Level::Error); - } - }; - } - } + AppSignal::FileUpdate(pb) => self.handle_file_update(pb), AppSignal::ReadClipboard => { self.clear_drawing(); if let Some(text) = text_from_clipboard() { From 292ae82c552524cd15d32cf5908b453aea534536 Mon Sep 17 00:00:00 2001 From: blindfs Date: Tue, 5 May 2026 12:57:30 +0800 Subject: [PATCH 2/5] feat: backspace to go 1 level up in ui element explorer --- src/app_executor.rs | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/app_executor.rs b/src/app_executor.rs index 9364ff2..86197ad 100644 --- a/src/app_executor.rs +++ b/src/app_executor.rs @@ -489,7 +489,7 @@ impl AppExecutor { /// Activates the app and caches UI elements fn activate(&mut self, target: Target) { - let need_help_msg = target == Target::ChildElement; + let need_help_msg = target == Target::ChildElement && self.selected.is_none(); self.ui_element_traverse_on_activation(target); if !self.element_cache.cache.is_empty() { @@ -983,10 +983,35 @@ impl AppExecutor { async fn perform_filtering(&mut self, key_char: char, mode: FilterMode) { if key_char == '-' { - self.key_prefix.pop(); + if self.key_prefix.is_empty() && self.target == Target::ChildElement { + // Go back 1 level in element explorer + if let Some(parent_element) = self + .selected + .as_ref() + .and_then(|eoi| eoi.element.as_ref()) + .and_then(|ele| ele.parent().ok()) + { + let screen_frame = Frame::from_origion(self.screen_size); + let frame = parent_element + .get_frame() + .and_then(|f| f.intersect(&screen_frame)) + .unwrap_or(screen_frame); + self.selected = Some(ElementOfInterest { + element: Some(parent_element), + context: None, + role: RoleOfInterest::Generic, + frame, + }); + } + self.activate(Target::ChildElement); + return; + } else { + self.key_prefix.pop(); + } } else if self.key_prefix.len() < self.hint_width as usize { self.key_prefix.push(key_char); } + match mode { FilterMode::OCR => { self.ocr_res_filtering(); From 8371e096d1b4ca344340e20838237326325d8010 Mon Sep 17 00:00:00 2001 From: blindfs Date: Tue, 5 May 2026 13:11:25 +0800 Subject: [PATCH 3/5] feat: go back from word picker --- src/app_executor.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/app_executor.rs b/src/app_executor.rs index 86197ad..c4e68e7 100644 --- a/src/app_executor.rs +++ b/src/app_executor.rs @@ -983,7 +983,10 @@ impl AppExecutor { async fn perform_filtering(&mut self, key_char: char, mode: FilterMode) { if key_char == '-' { - if self.key_prefix.is_empty() && self.target == Target::ChildElement { + if self.key_prefix.is_empty() + && mode == FilterMode::Generic + && self.target == Target::ChildElement + { // Go back 1 level in element explorer if let Some(parent_element) = self .selected @@ -1002,8 +1005,23 @@ impl AppExecutor { role: RoleOfInterest::Generic, frame, }); + self.activate(Target::ChildElement); } - self.activate(Target::ChildElement); + return; + } else if self.key_prefix.is_empty() && mode == FilterMode::WordPicking { + self.clear_drawing(); + self.word_picker = None; + self.set_mode(Mode::TextActionMenu); + self.draw_text_action_menu( + &self + .selected + .as_ref() + .and_then(|eoi| eoi.context.clone()) + .expect( + "Internal Error: selected text should be kept during menu refreshing.", + ), + "", + ); return; } else { self.key_prefix.pop(); From ff582eed7cada4b25482edb825effd539353d126 Mon Sep 17 00:00:00 2001 From: blindfs Date: Tue, 5 May 2026 14:08:07 +0800 Subject: [PATCH 4/5] refactor: key_listener build --- src/key_listener.rs | 159 ++++++++++++++++++++++++++++---------------- 1 file changed, 102 insertions(+), 57 deletions(-) diff --git a/src/key_listener.rs b/src/key_listener.rs index 3e95048..8f6d88e 100644 --- a/src/key_listener.rs +++ b/src/key_listener.rs @@ -168,12 +168,18 @@ pub enum Mode { WaitAndDeactivate, } +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +enum MenuType { + Dashboard, + TextAction, + ImageAction, + Scroll, +} + #[derive(Debug)] pub struct KeyListener { - pub text_actions: HashMap, - pub image_actions: HashMap, - pub dashboard_actions: HashMap, - pub scroll_actions: HashMap, + menu_actions: HashMap>, + menu_action_max_key_len: HashMap, sender: Sender, global_key_binding: KeyBinding, } @@ -185,56 +191,88 @@ impl KeyListener { items.into_iter().map(|it| (it.key.to_string(), it.action)) } + fn menu_action_helper( + menu_type: MenuType, + config: &GlyphlowConfig, + ) -> (HashMap, usize) { + let (base_items, need_workflow, need_text_action, editor_signal) = match menu_type { + MenuType::Dashboard => ( + Self::iter_from(DASH_BOARD_MENU_ITEMS).collect::>(), + true, + false, + Some(AppSignal::Activate(Target::Edit)), + ), + MenuType::TextAction => ( + Self::iter_from(TEXT_ACTION_MENU_ITEMS).collect::>(), + true, + true, + Some(AppSignal::TextAction(TextAction::Editor)), + ), + MenuType::ImageAction => ( + Self::iter_from(IMAGE_ACTION_MENU_ITEMS).collect::>(), + true, + false, + None, + ), + MenuType::Scroll => ( + Self::iter_from(SCROLLBAR_MENU_ITEMS).collect::>(), + false, + false, + None, + ), + }; + + let mut items = HashMap::new(); + // Order matters! + if need_workflow { + for (idx, wf) in config.workflows.iter().enumerate() { + items.insert(wf.key.clone(), AppSignal::RunWorkFlow(idx)); + } + } + + if need_text_action { + for (idx, act) in config.text_actions.iter().enumerate() { + items.insert( + act.key.clone(), + AppSignal::TextAction(TextAction::UserDefined(idx)), + ); + } + } + + if let Some(sig) = editor_signal + && let Some(editor) = config.editor.as_ref() + { + items.insert(editor.key.clone(), sig); + } + + for (key, sig) in base_items { + items.insert(key, sig); + } + + let max_key_len = items.keys().map(|k| k.chars().count()).max().unwrap_or(0); + (items, max_key_len) + } + pub fn new(sender: Sender, config: &GlyphlowConfig) -> KeyListener { - let mut text_actions = - // Order matters! - config - .workflows - .iter() - .enumerate() - .map(|(idx, wf)| (wf.key.clone(), AppSignal::RunWorkFlow(idx))) - .chain( - config.text_actions.iter().enumerate().map(|(idx, ca)| { - (ca.key.clone(), AppSignal::TextAction(TextAction::UserDefined(idx))) - }), - ) - .chain(Self::iter_from(TEXT_ACTION_MENU_ITEMS)) - .collect::>(); - - let mut dashboard_actions = config - .workflows - .iter() - .enumerate() - .map(|(idx, wf)| (wf.key.clone(), AppSignal::RunWorkFlow(idx))) - .chain(Self::iter_from(DASH_BOARD_MENU_ITEMS)) - .collect::>(); - - let image_actions = config - .workflows - .iter() - .enumerate() - .map(|(idx, wf)| (wf.key.clone(), AppSignal::RunWorkFlow(idx))) - .chain(Self::iter_from(IMAGE_ACTION_MENU_ITEMS)) - .collect::>(); - - let scroll_actions = Self::iter_from(SCROLLBAR_MENU_ITEMS).collect::>(); - - if let Some(editor_command) = config.editor.as_ref() { - text_actions.insert( - editor_command.key.clone(), - AppSignal::TextAction(TextAction::Editor), - ); - dashboard_actions.insert( - editor_command.key.clone(), - AppSignal::Activate(Target::Edit), - ); + let mut menu_actions = HashMap::new(); + let mut menu_action_max_key_len = HashMap::new(); + + for menu_type in [ + MenuType::Dashboard, + MenuType::TextAction, + MenuType::ImageAction, + MenuType::Scroll, + ] + .into_iter() + { + let (items, max_key_len) = Self::menu_action_helper(menu_type, config); + menu_actions.insert(menu_type, items); + menu_action_max_key_len.insert(menu_type, max_key_len); } KeyListener { - text_actions, - image_actions, - dashboard_actions, - scroll_actions, + menu_actions, + menu_action_max_key_len, sender, global_key_binding: config.global_trigger_key.clone(), } @@ -249,17 +287,22 @@ impl KeyListener { fn menu_helper( &self, key: &Key, - key_signals: &HashMap, + menu_type: MenuType, mut state: MutexGuard<'_, Mode>, key_state: &mut KeyState, ) -> bool { let key_char = key.to_char(); if key_char == '-' { key_state.pop(); - } else { + } else if key_state.prefix.chars().count() < self.menu_action_max_key_len[&menu_type] { key_state.push(key_char); } - if let Some(signal) = key_signals.get(&key_state.prefix) { + + if let Some(signal) = self + .menu_actions + .get(&menu_type) + .and_then(|m| m.get(&key_state.prefix)) + { self.send(signal.clone()); key_state.clear_prefix(); } else if key_char == ' ' { @@ -307,7 +350,7 @@ impl KeyListener { false } } - Mode::DashBoard => self.menu_helper(&key, &self.dashboard_actions, state, key_state), + Mode::DashBoard => self.menu_helper(&key, MenuType::Dashboard, state, key_state), // To act on selected parent node Mode::Filtering if key == Key::Return => { self.send(AppSignal::DashBoard); @@ -323,9 +366,11 @@ impl KeyListener { Mode::WordPicking => self.filter_helper(&key, state, FilterMode::WordPicking), Mode::Filtering => self.filter_helper(&key, state, FilterMode::Generic), Mode::OCRResultFiltering => self.filter_helper(&key, state, FilterMode::OCR), - Mode::TextActionMenu => self.menu_helper(&key, &self.text_actions, state, key_state), - Mode::ImageActionMenu => self.menu_helper(&key, &self.image_actions, state, key_state), - Mode::Scrolling => self.menu_helper(&key, &self.scroll_actions, state, key_state), + Mode::TextActionMenu => self.menu_helper(&key, MenuType::TextAction, state, key_state), + Mode::ImageActionMenu => { + self.menu_helper(&key, MenuType::ImageAction, state, key_state) + } + Mode::Scrolling => self.menu_helper(&key, MenuType::Scroll, state, key_state), Mode::WaitAndDeactivate => { self.send(AppSignal::DeActivate); *state = Mode::Idle; From c3cd4e858af841c0b66b0e667e52ce513191124e Mon Sep 17 00:00:00 2001 From: blindfs Date: Tue, 5 May 2026 15:03:10 +0800 Subject: [PATCH 5/5] feat: go back in multi-selection modes --- src/app_executor.rs | 63 +++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/app_executor.rs b/src/app_executor.rs index c4e68e7..406c16b 100644 --- a/src/app_executor.rs +++ b/src/app_executor.rs @@ -53,6 +53,10 @@ impl MultiSeletionState { self.role = None; } + fn clear_one_side(&mut self) { + self.one_side_idex = None; + } + fn set_one_side(&mut self, other: usize) -> Option<(usize, usize)> { if let Some(one) = self.one_side_idex { Some((one, other)) @@ -981,12 +985,9 @@ impl AppExecutor { } } - async fn perform_filtering(&mut self, key_char: char, mode: FilterMode) { - if key_char == '-' { - if self.key_prefix.is_empty() - && mode == FilterMode::Generic - && self.target == Target::ChildElement - { + async fn go_back_in_filtering(&mut self, mode: FilterMode) { + match mode { + FilterMode::Generic if self.target == Target::ChildElement => { // Go back 1 level in element explorer if let Some(parent_element) = self .selected @@ -1007,21 +1008,43 @@ impl AppExecutor { }); self.activate(Target::ChildElement); } - return; - } else if self.key_prefix.is_empty() && mode == FilterMode::WordPicking { + } + FilterMode::WordPicking => { self.clear_drawing(); - self.word_picker = None; - self.set_mode(Mode::TextActionMenu); - self.draw_text_action_menu( - &self - .selected - .as_ref() - .and_then(|eoi| eoi.context.clone()) - .expect( - "Internal Error: selected text should be kept during menu refreshing.", - ), - "", - ); + if self.multi_selection.is_on { + self.multi_selection.clear_one_side(); + self.draw_word_picker(); + } else { + self.word_picker = None; + self.set_mode(Mode::TextActionMenu); + self.draw_text_action_menu( + &self + .selected + .as_ref() + .and_then(|eoi| eoi.context.clone()) + .expect( + "Internal Error: selected text should be kept during menu refreshing.", + ), + "", + ); + } + } + FilterMode::Generic if self.multi_selection.is_on => { + self.multi_selection.clear_one_side(); + self.filter_by_key().await; + } + FilterMode::OCR if self.multi_selection.is_on => { + self.multi_selection.clear_one_side(); + self.ocr_res_filtering(); + } + _ => (), + } + } + + async fn perform_filtering(&mut self, key_char: char, mode: FilterMode) { + if key_char == '-' { + if self.key_prefix.is_empty() { + self.go_back_in_filtering(mode).await; return; } else { self.key_prefix.pop();