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> {
|
||||
fn get_force_mode(&self, match_id: i32) -> Option<crate::engine::event::effect::TextInjectMode> {
|
||||
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 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) =
|
||||
super::engine::source::init_and_spawn().expect("failed to initialize detector module");
|
||||
|
@ -74,7 +75,7 @@ pub fn initialize_and_spawn(
|
|||
let matchers: Vec<
|
||||
&dyn crate::engine::process::Matcher<super::engine::matcher::MatcherState>,
|
||||
> = 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 injector = espanso_inject::get_injector(Default::default())
|
||||
|
|
|
@ -17,21 +17,69 @@
|
|||
* 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 {
|
||||
// TODO: pass Modulo search UI manager
|
||||
use crate::{
|
||||
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 fn new() -> Self {
|
||||
Self {}
|
||||
pub struct MatchSelectorAdapter<'a> {
|
||||
search_ui: &'a dyn SearchUI,
|
||||
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> {
|
||||
// TODO: replace with actual selection
|
||||
Some(*matches_ids.first().unwrap())
|
||||
let matches = self.match_provider.get_matches(&matches_ids);
|
||||
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)]
|
||||
pub struct IconPaths {
|
||||
pub form_icon: Option<PathBuf>,
|
||||
pub search_icon: Option<PathBuf>,
|
||||
|
||||
pub tray_icon_normal: 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> {
|
||||
Ok(IconPaths {
|
||||
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_disabled: Some(extract_icon(WINDOWS_RED_ICO_BINARY, &runtime_dir.join("disabled.ico"))?),
|
||||
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> {
|
||||
Ok(IconPaths {
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ use anyhow::Result;
|
|||
pub mod modulo;
|
||||
|
||||
pub trait SearchUI {
|
||||
fn show(&self, items: &SearchItem) -> Result<Option<String>>;
|
||||
fn show(&self, items: &[SearchItem]) -> Result<Option<String>>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -31,10 +31,10 @@ pub struct 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 {
|
||||
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 manager;
|
||||
|
||||
// TODO: implement FormUI and SearchUI traits using ModuloManager
|
||||
pub mod search;
|
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