feat(core): wire up match selection GUI
This commit is contained in:
parent
e361bdb9c2
commit
5a66594532
|
@ -63,6 +63,14 @@ impl<'a> super::render::MatchProvider<'a> for MatchCache<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> super::ui::selector::MatchProvider<'a> for MatchCache<'a> {
|
||||||
|
fn get_matches(&self, ids: &[i32]) -> Vec<&'a Match> {
|
||||||
|
ids.iter().flat_map(|id| {
|
||||||
|
self.cache.get(&id).map(|m| *m)
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> MatchInfoProvider for MatchCache<'a> {
|
impl<'a> MatchInfoProvider for MatchCache<'a> {
|
||||||
fn get_force_mode(&self, match_id: i32) -> Option<crate::engine::event::effect::TextInjectMode> {
|
fn get_force_mode(&self, match_id: i32) -> Option<crate::engine::event::effect::TextInjectMode> {
|
||||||
let m = self.cache.get(&match_id)?;
|
let m = self.cache.get(&match_id)?;
|
||||||
|
|
|
@ -55,7 +55,8 @@ pub fn initialize_and_spawn(
|
||||||
let match_cache = super::engine::match_cache::MatchCache::load(&*config_store, &*match_store);
|
let match_cache = super::engine::match_cache::MatchCache::load(&*config_store, &*match_store);
|
||||||
|
|
||||||
let modulo_manager = crate::gui::modulo::manager::ModuloManager::new();
|
let modulo_manager = crate::gui::modulo::manager::ModuloManager::new();
|
||||||
let modulo_form_ui = crate::gui::modulo::form::ModuloFormUI::new(&modulo_manager, icon_paths.form_icon);
|
let modulo_form_ui = crate::gui::modulo::form::ModuloFormUI::new(&modulo_manager, &icon_paths.form_icon);
|
||||||
|
let modulo_search_ui = crate::gui::modulo::search::ModuloSearchUI::new(&modulo_manager, &icon_paths.search_icon);
|
||||||
|
|
||||||
let (detect_source, modifier_state_store, sequencer) =
|
let (detect_source, modifier_state_store, sequencer) =
|
||||||
super::engine::source::init_and_spawn().expect("failed to initialize detector module");
|
super::engine::source::init_and_spawn().expect("failed to initialize detector module");
|
||||||
|
@ -74,7 +75,7 @@ pub fn initialize_and_spawn(
|
||||||
let matchers: Vec<
|
let matchers: Vec<
|
||||||
&dyn crate::engine::process::Matcher<super::engine::matcher::MatcherState>,
|
&dyn crate::engine::process::Matcher<super::engine::matcher::MatcherState>,
|
||||||
> = vec![&rolling_matcher, ®ex_matcher];
|
> = vec![&rolling_matcher, ®ex_matcher];
|
||||||
let selector = MatchSelectorAdapter::new();
|
let selector = MatchSelectorAdapter::new(&modulo_search_ui, &match_cache);
|
||||||
let multiplexer = super::engine::multiplex::MultiplexAdapter::new(&match_cache);
|
let multiplexer = super::engine::multiplex::MultiplexAdapter::new(&match_cache);
|
||||||
|
|
||||||
let injector = espanso_inject::get_injector(Default::default())
|
let injector = espanso_inject::get_injector(Default::default())
|
||||||
|
|
|
@ -17,21 +17,69 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::engine::process::MatchSelector;
|
use espanso_config::matches::{Match};
|
||||||
|
use log::error;
|
||||||
|
|
||||||
pub struct MatchSelectorAdapter {
|
use crate::{
|
||||||
// TODO: pass Modulo search UI manager
|
engine::process::MatchSelector,
|
||||||
|
gui::{SearchItem, SearchUI},
|
||||||
|
};
|
||||||
|
|
||||||
|
const MAX_LABEL_LEN: usize = 100;
|
||||||
|
|
||||||
|
pub trait MatchProvider<'a> {
|
||||||
|
fn get_matches(&self, ids: &[i32]) -> Vec<&'a Match>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MatchSelectorAdapter {
|
pub struct MatchSelectorAdapter<'a> {
|
||||||
pub fn new() -> Self {
|
search_ui: &'a dyn SearchUI,
|
||||||
Self {}
|
match_provider: &'a dyn MatchProvider<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MatchSelectorAdapter<'a> {
|
||||||
|
pub fn new(search_ui: &'a dyn SearchUI, match_provider: &'a dyn MatchProvider<'a>) -> Self {
|
||||||
|
Self {
|
||||||
|
search_ui,
|
||||||
|
match_provider,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MatchSelector for MatchSelectorAdapter {
|
impl<'a> MatchSelector for MatchSelectorAdapter<'a> {
|
||||||
fn select(&self, matches_ids: &[i32]) -> Option<i32> {
|
fn select(&self, matches_ids: &[i32]) -> Option<i32> {
|
||||||
// TODO: replace with actual selection
|
let matches = self.match_provider.get_matches(&matches_ids);
|
||||||
Some(*matches_ids.first().unwrap())
|
let search_items: Vec<SearchItem> = matches
|
||||||
|
.iter()
|
||||||
|
.map(|m| {
|
||||||
|
let label = m.description();
|
||||||
|
let clipped_label = &label[..std::cmp::min(label.len(), MAX_LABEL_LEN)];
|
||||||
|
|
||||||
|
SearchItem {
|
||||||
|
id: m.id.to_string(),
|
||||||
|
label: clipped_label.to_string(),
|
||||||
|
tag: m.cause_description().map(String::from),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
match self.search_ui.show(&search_items) {
|
||||||
|
Ok(Some(selected_id)) => match selected_id.parse::<i32>() {
|
||||||
|
Ok(id) => Some(id),
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
"match selector received an invalid id from SearchUI: {}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(None) => None,
|
||||||
|
Err(err) => {
|
||||||
|
error!("SearchUI reported an error: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: test
|
|
@ -33,6 +33,7 @@ const WINDOWS_RED_ICO_BINARY: &[u8] = include_bytes!("../../../res/windows/espan
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct IconPaths {
|
pub struct IconPaths {
|
||||||
pub form_icon: Option<PathBuf>,
|
pub form_icon: Option<PathBuf>,
|
||||||
|
pub search_icon: Option<PathBuf>,
|
||||||
|
|
||||||
pub tray_icon_normal: Option<PathBuf>,
|
pub tray_icon_normal: Option<PathBuf>,
|
||||||
pub tray_icon_disabled: Option<PathBuf>,
|
pub tray_icon_disabled: Option<PathBuf>,
|
||||||
|
@ -45,6 +46,7 @@ pub struct IconPaths {
|
||||||
pub fn load_icon_paths(runtime_dir: &Path) -> Result<IconPaths> {
|
pub fn load_icon_paths(runtime_dir: &Path) -> Result<IconPaths> {
|
||||||
Ok(IconPaths {
|
Ok(IconPaths {
|
||||||
form_icon: Some(extract_icon(WINDOWS_ICO_BINARY, &runtime_dir.join("form.ico"))?),
|
form_icon: Some(extract_icon(WINDOWS_ICO_BINARY, &runtime_dir.join("form.ico"))?),
|
||||||
|
search_icon: Some(extract_icon(ICON_BINARY, &runtime_dir.join("search.png"))?),
|
||||||
tray_icon_normal: Some(extract_icon(WINDOWS_ICO_BINARY, &runtime_dir.join("normal.ico"))?),
|
tray_icon_normal: Some(extract_icon(WINDOWS_ICO_BINARY, &runtime_dir.join("normal.ico"))?),
|
||||||
tray_icon_disabled: Some(extract_icon(WINDOWS_RED_ICO_BINARY, &runtime_dir.join("disabled.ico"))?),
|
tray_icon_disabled: Some(extract_icon(WINDOWS_RED_ICO_BINARY, &runtime_dir.join("disabled.ico"))?),
|
||||||
logo: Some(extract_icon(ICON_BINARY, &runtime_dir.join("icon.png"))?),
|
logo: Some(extract_icon(ICON_BINARY, &runtime_dir.join("icon.png"))?),
|
||||||
|
@ -56,6 +58,7 @@ pub fn load_icon_paths(runtime_dir: &Path) -> Result<IconPaths> {
|
||||||
pub fn load_icon_paths(runtime_dir: &Path) -> Result<IconPaths> {
|
pub fn load_icon_paths(runtime_dir: &Path) -> Result<IconPaths> {
|
||||||
Ok(IconPaths {
|
Ok(IconPaths {
|
||||||
logo: Some(extract_icon(ICON_BINARY, &runtime_dir.join("icon.png"))?),
|
logo: Some(extract_icon(ICON_BINARY, &runtime_dir.join("icon.png"))?),
|
||||||
|
search_icon: Some(extract_icon(ICON_BINARY, &runtime_dir.join("search.png"))?),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ use anyhow::Result;
|
||||||
pub mod modulo;
|
pub mod modulo;
|
||||||
|
|
||||||
pub trait SearchUI {
|
pub trait SearchUI {
|
||||||
fn show(&self, items: &SearchItem) -> Result<Option<String>>;
|
fn show(&self, items: &[SearchItem]) -> Result<Option<String>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -31,10 +31,10 @@ pub struct ModuloFormUI<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ModuloFormUI<'a> {
|
impl<'a> ModuloFormUI<'a> {
|
||||||
pub fn new(manager: &'a ModuloManager, icon_path: Option<PathBuf>) -> Self {
|
pub fn new(manager: &'a ModuloManager, icon_path: &Option<PathBuf>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
manager,
|
manager,
|
||||||
icon_path: icon_path.map(|path| path.to_string_lossy().to_string()),
|
icon_path: icon_path.as_ref().map(|path| path.to_string_lossy().to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,5 +19,4 @@
|
||||||
|
|
||||||
pub mod form;
|
pub mod form;
|
||||||
pub mod manager;
|
pub mod manager;
|
||||||
|
pub mod search;
|
||||||
// TODO: implement FormUI and SearchUI traits using ModuloManager
|
|
91
espanso/src/gui/modulo/search.rs
Normal file
91
espanso/src/gui/modulo/search.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* This file is part of espanso.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 Federico Terzi
|
||||||
|
*
|
||||||
|
* espanso is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* espanso is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::gui::{SearchItem, SearchUI};
|
||||||
|
|
||||||
|
use super::manager::ModuloManager;
|
||||||
|
|
||||||
|
pub struct ModuloSearchUI<'a> {
|
||||||
|
manager: &'a ModuloManager,
|
||||||
|
icon_path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ModuloSearchUI<'a> {
|
||||||
|
pub fn new(manager: &'a ModuloManager, icon_path: &Option<PathBuf>) -> Self {
|
||||||
|
Self {
|
||||||
|
manager,
|
||||||
|
icon_path: icon_path.as_ref().map(|path| path.to_string_lossy().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SearchUI for ModuloSearchUI<'a> {
|
||||||
|
fn show(&self, items: &[SearchItem]) -> anyhow::Result<Option<String>> {
|
||||||
|
let modulo_config = ModuloSearchConfig {
|
||||||
|
icon: self.icon_path.as_deref(),
|
||||||
|
title: "espanso",
|
||||||
|
items: convert_items(&items),
|
||||||
|
};
|
||||||
|
|
||||||
|
let json_config = serde_json::to_string(&modulo_config)?;
|
||||||
|
let output = self
|
||||||
|
.manager
|
||||||
|
.invoke(&["search", "-j", "-i", "-"], &json_config)?;
|
||||||
|
let json: Result<HashMap<String, Value>, _> = serde_json::from_str(&output);
|
||||||
|
match json {
|
||||||
|
Ok(json) => {
|
||||||
|
if let Some(Value::String(selected_id)) = json.get("selected") {
|
||||||
|
return Ok(Some(selected_id.clone()));
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
return Err(error.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ModuloSearchConfig<'a> {
|
||||||
|
icon: Option<&'a str>,
|
||||||
|
title: &'a str,
|
||||||
|
items: Vec<ModuloSearchItemConfig<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ModuloSearchItemConfig<'a> {
|
||||||
|
id: &'a str,
|
||||||
|
label: &'a str,
|
||||||
|
trigger: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: test
|
||||||
|
fn convert_items<'a>(items: &'a [SearchItem]) -> Vec<ModuloSearchItemConfig<'a>> {
|
||||||
|
items.iter().map(|item| ModuloSearchItemConfig {
|
||||||
|
id: &item.id,
|
||||||
|
label: &item.label,
|
||||||
|
trigger: item.tag.as_deref(),
|
||||||
|
}).collect()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user