diff --git a/Cargo.lock b/Cargo.lock index 9c927e2..0688985 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,3 +1,11 @@ +[[package]] +name = "aho-corasick" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "android_glue" version = "0.2.3" @@ -379,6 +387,16 @@ dependencies = [ "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "memchr" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "memmap" version = "0.6.2" @@ -715,6 +733,26 @@ dependencies = [ "redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "remove_dir_all" version = "0.5.1" @@ -843,6 +881,7 @@ dependencies = [ "conrod 0.61.1 (registry+https://github.com/rust-lang/crates.io-index)", "directories 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "find_folder 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", @@ -938,6 +977,19 @@ dependencies = [ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ucd-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-segmentation" version = "1.2.1" @@ -961,11 +1013,21 @@ dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "utf8-ranges" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "void" version = "1.0.2" @@ -1085,6 +1147,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] +"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e" "checksum android_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08abcc3b4e9339e33a3d0a5ed15d84a687350c05689d825e0f6655eef9e76a94" @@ -1133,6 +1196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +"checksum memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "db4c41318937f6e76648f42826b1d9ade5c09cafb5aef7e351240a70f39206e9" "checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff" "checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" @@ -1172,6 +1236,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3" "checksum redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "679da7508e9a6390aeaf7fbd02a800fdc64b73fe2204dd2c8ae66d22d9d5ad5d" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f" +"checksum regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4e47a2ed29da7a9e1960e1639e7a982e6edc6d49be308a3b02daf511504a16d1" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" @@ -1197,11 +1263,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7e91405c14320e5c79b3d148e1c86f40749a36e490642202a31689cb1a3452b2" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum wayland-client 0.20.12 (registry+https://github.com/rust-lang/crates.io-index)" = "e7516a23419a55bd2e6d466c75a6a52c85718e5013660603289c2b8bee794b12" "checksum wayland-commons 0.20.12 (registry+https://github.com/rust-lang/crates.io-index)" = "d8609d59b95bf198bae4f3b064d55a712f2d529eec6aac98cc1f6e9cc911d47a" diff --git a/Cargo.toml b/Cargo.toml index 64d28fe..af0191c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ serde_json = "1.0" find_folder = "0.3.0" directories = "1.0" structopt = "0.2" +regex = "1" diff --git a/README.md b/README.md index ba33d71..f3b7553 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Edit `config.json` in your configuration path to change spaceruns behaviour. ## Cool & shiny things to implement -* [ ] Auto-generate a form by placeholders in the command (e.g. `${'My Field Name': default val}`) +* [x] Auto-generate a form by placeholders in the command (e.g. `${'My Field Name': default val}`) * [ ] Context sensitive commands * [ ] Prev focused window. I could have added a `"class": "^chromium"` to the commands config. This option will only show if chromium was focused previous to spacerun being opened. @@ -65,10 +65,11 @@ Edit `config.json` in your configuration path to change spaceruns behaviour. (Also solvable by using a CLI interface (not yet implemented), but not as cool?) * [ ] Server mode, instance is constantly running in background so the JSON won't need to be parsed each time the window should be displayed. -* [ ] Breadcrumbs, showing the path you went down. +* [x] Breadcrumbs, showing the path you went down. * [ ] Show name and description of the current tree as a title / subtitle of the window * [ ] Radial menu instead of list as option (Because radial menus are awesome!) * [ ] Unicode / emoticons / ligatures / FontAwesome support +* [ ] dmenu parameter mode (similar to rofi) * [ ] More key-value pairs for command leafs! * [ ] "description" to find / understand your nodes & commands, even after a long night. * [ ] "clip" copying a string to clipboard. diff --git a/config.json b/config.json index dc7dc80..9f02c8a 100644 --- a/config.json +++ b/config.json @@ -29,6 +29,11 @@ "shortcut": "f", "name": "firefox", "cmd": "firefox" + }, + { + "shortcut": "h", + "name": "Example templating form", + "cmd": "echo {{text}}; echo \"===========\n\"; {{postcmd}}" } ] } diff --git a/src/commands.rs b/src/commands.rs index e98c464..b6910b1 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,3 +1,9 @@ +use std::collections::HashMap; +use std::fmt::{self, Display}; +use std::str::FromStr; + +use regex::{Regex, RegexBuilder}; +use serde::de; use serde_derive::Deserialize; use crate::bindings::Shortcut; @@ -6,7 +12,7 @@ use crate::bindings::Shortcut; pub struct CommandNode { pub shortcut: Shortcut, pub name: String, - pub cmd: Option, + pub cmd: Option, pub children: Vec, } @@ -14,7 +20,7 @@ pub struct CommandNode { pub struct CommandLeaf { pub shortcut: Shortcut, pub name: String, - pub cmd: String, + pub cmd: CommandTask, } #[derive(Debug, Clone, Deserialize)] @@ -24,6 +30,107 @@ pub enum Command { Leaf(CommandLeaf), } +#[derive(Copy, Clone)] +pub struct CommandTaskParseError; +impl Display for CommandTaskParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Failed to parse a command task") + } +} +impl fmt::Debug for CommandTaskParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Failed to parse a command task") + } +} + +/** + * The executable part of a command. + */ +#[derive(Debug, Clone)] +pub struct CommandTask { + pub base: String, + pub variables: Vec, +} + +impl Display for CommandTask { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.base) + } +} + +#[derive(Debug)] +struct CommandTaskReplaceValuesError; +impl std::error::Error for CommandTaskReplaceValuesError {} +impl fmt::Display for CommandTaskReplaceValuesError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Could not replace the placeholders in the CommandTask string" + ) + } +} + +impl FromStr for CommandTask { + type Err = CommandTaskParseError; + + fn from_str(value: &str) -> Result { + let template_regex = Regex::new(r"\{\{(.+?)\}\}").unwrap(); + + let variables = template_regex + .captures_iter(value) + .map(|template_data| { + CommandTaskVariable { + name: template_data[1].into(), + // TODO Add default value parsing + default_value: None, + } + }) + .collect(); + + Ok(CommandTask { + base: value.into(), + variables: variables, + }) + } +} + +impl CommandTask { + pub fn to_executable_string( + &self, + variables: &HashMap, + ) -> Result> { + let mut output = self.base.clone(); + for task_variable in &self.variables { + let value = variables + .get(&task_variable.name) + .ok_or(CommandTaskReplaceValuesError)?; + let match_string = format!("\\{{\\{{{}\\}}\\}}", task_variable.name); + let re = RegexBuilder::new(&match_string).build()?; + output = re.replace_all(&output, value.as_str()).to_string(); + } + Ok(output) + } +} + +impl<'de> de::Deserialize<'de> for CommandTask { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + FromStr::from_str(&s).map_err(de::Error::custom) + } +} + +/** + * A variable value in a command task, to be filled in e.g. by a form + */ +#[derive(Debug, Clone, Deserialize)] +pub struct CommandTaskVariable { + pub name: String, + pub default_value: Option, +} + /** * Easily displayable command */ @@ -55,7 +162,7 @@ impl From for CommandDisplay { fn from(node: CommandLeaf) -> Self { CommandDisplay { shortcut: node.shortcut, - name: node.name, + name: node.name.to_string(), } } } diff --git a/src/main.rs b/src/main.rs index 42e37a5..d008a4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,17 @@ +use std::process::Command as CliCommand; + use conrod::backend::glium::glium::glutin::os::unix::WindowBuilderExt; use conrod::backend::glium::glium::{self, Surface}; use conrod::backend::glium::Renderer; use structopt::StructOpt; use crate::bindings::Shortcut; +use crate::commands::Command; use crate::event_loop::EventLoop; -use crate::state::State; -use crate::view::SpacerunEvent::{CloseApplication, FocusLost, PrevLevelCommand, SelectCommand}; +use crate::state::{init_variables_form_input, State}; +use crate::view::SpacerunEvent::{ + CloseApplication, ExecuteCommandTask, FocusLost, PrevLevelCommand, SelectCommand, +}; use crate::view::{ handle_event, rendered_elements_height, set_ui, update_initial_window_state, update_window_and_window_state, Ids, @@ -99,6 +104,10 @@ fn main() { state .selection_path .push(state.selected_command.clone().into()); + if let Command::Leaf(command_leaf) = &state.selected_command { + let x = &command_leaf.cmd.variables; + init_variables_form_input(&mut state.form_command_task_variables, x); + } } Some(PrevLevelCommand) => { if state.selection_path.pop().is_some() { @@ -120,10 +129,60 @@ fn main() { .set_cursor_position((0, 0).into()) .unwrap(); } + Some(ExecuteCommandTask(cmd, variables)) => { + let cmd_string = cmd.to_executable_string(&variables); + match cmd_string { + Ok(cmd_string) => { + CliCommand::new("sh") + .arg("-c") + .arg(cmd_string) + .spawn() + .expect("process failed to execute"); + break 'main; + }, + Err(error) => eprintln!("Error replacing cmd task vars! {}", error), + } + } Some(CloseApplication) => break 'main, None => (), } } + // FIXME (PE) To use conrods own events or not? + // all its examples use the glium backend events, not the conrod ones for custom event + // handling in user code. + // + // ui.global_input().events().for_each(|event| { + // use conrod::event::Text; + // use conrod::event::{Event, Ui}; + // match event { + // Event::Ui(x) => { + // match x { + // Ui::Text(widget_id, text_value) => { + // if let Some(widget_id) = widget_id { + // println!("Text Evnet | Input WidgetId {:?} ; {:?}", widget_id, x); + // println!("{:?}", ids + // .command_variables_form_inputs + // .iter().any(|input_widget_id| { + // println!("{:?}", input_widget_id); + // input_widget_id[widget_id] + // } )); + // println!("Text Evnet | Input WidgetId {:?} ; {:?}", widget_id, x); + // if state.form_command_task_variables.len() > 0 + // && ids + // .command_variables_form_inputs + // .iter().any(|input_widget_id| input_widget_id == widget_id) + // { + // println!("Input widget event: ||| {:?}", text_value); + // } + // // Some text input on some widget + // } + // } + // _ => {} + // } + // } + // _ => {} + // } + // }); render( &mut state, &mut ui, @@ -143,7 +202,7 @@ fn render( display: &glium::Display, image_map: &conrod::image::Map, ) { - set_ui(ui.set_widgets(), &state, &state.selected_command, ids); + set_ui(ui.set_widgets(), state, ids); // Render the `Ui` and then display it on the screen. if let Some(primitives) = ui.draw_if_changed() { diff --git a/src/state.rs b/src/state.rs index 55a98a3..e5a0321 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,8 @@ use conrod::glium::glutin::dpi::{LogicalPosition, LogicalSize}; -use crate::commands::{Command, CommandDisplay}; +use std::collections::HashMap; + +use crate::commands::{Command, CommandDisplay, CommandTaskVariable}; use crate::config::SpacerunConfig; use crate::Options; @@ -13,6 +15,7 @@ pub struct State { pub config: SpacerunConfig, pub selected_command: Command, pub selection_path: Vec, + pub form_command_task_variables: HashMap, pub options: Options, } @@ -23,6 +26,7 @@ impl State { window_position: (0, 0).into(), selected_command: select_initial_command(&config, &options), selection_path: vec![], + form_command_task_variables: HashMap::new(), config, options, }; @@ -30,6 +34,21 @@ impl State { } } +pub fn init_variables_form_input( + form_command_task_variables: &mut HashMap, + variables: &[CommandTaskVariable], +) { + *form_command_task_variables = variables + .iter() + .map(|variable| { + ( + variable.name.clone(), + variable.default_value.clone().unwrap_or("".to_string()), + ) + }) + .collect(); +} + fn select_initial_command(config: &SpacerunConfig, options: &Options) -> Command { let mut command = &config.commands; diff --git a/src/view.rs b/src/view.rs index de22063..9a2d5a9 100644 --- a/src/view.rs +++ b/src/view.rs @@ -1,13 +1,17 @@ -use std::process::Command as CliCommand; +// use std::collections::HashMap; +use std::collections::HashMap; use conrod::backend::glium::glium; use conrod::backend::glium::glium::backend::glutin::glutin::Event; use conrod::backend::glium::glium::backend::glutin::Display; +// use conrod::event::Input; +// use conrod::input::Button; +// use conrod::input::Key; use conrod::Ui; use conrod::{color, widget_ids}; use crate::bindings::Shortcut; -use crate::commands::Command; +use crate::commands::{Command, CommandTask}; use crate::state::State; use crate::window_position::WindowPosition; @@ -16,18 +20,25 @@ widget_ids! { canvas, head_canvas, head_breadcrumbs, - list_canvas, + main_canvas, + command_list, + command_list_item_canvas[], command_list_item_shortcut_canvas[], command_list_item_name_canvas[], command_list_item_shortcut_widget[], command_list_item_name_widget[], + + command_form, + command_variables_form_input_canvas[], + command_variables_form_inputs[], } } pub enum SpacerunEvent { SelectCommand(Command), + ExecuteCommandTask(CommandTask, HashMap), PrevLevelCommand, FocusLost, CloseApplication, @@ -35,26 +46,76 @@ pub enum SpacerunEvent { static DEFAULT_FONT_SIZE: u32 = 14; +// pub fn handle_event(event: &Input, state: &State) -> Option { +// match event { +// // conrod::event::Widget::Text(text) => println!("TEXT EVETN {:?}", text), +// Input::Press(Button::Keyboard(Key::Escape)) +// glium::glutin::Event::WindowEvent { event, .. } => match event { +// // Break from the loop upon `Escape`. +// glium::glutin::WindowEvent::CloseRequested +// | glium::glutin::WindowEvent::KeyboardInput { +// input: +// glium::glutin::KeyboardInput { +// virtual_keycode: Some(glium::glutin::VirtualKeyCode::Escape), +// .. +// }, +// .. +// } => return Some(SpacerunEvent::CloseApplication), +// glium::glutin::WindowEvent::Focused(false) => return Some(SpacerunEvent::FocusLost), +// glium::glutin::WindowEvent::KeyboardInput { input, .. } => { +// if let Some(virtual_keycode) = input.virtual_keycode { +// if input.state == glium::glutin::ElementState::Pressed { +// if virtual_keycode == glium::glutin::VirtualKeyCode::Back { +// return Some(SpacerunEvent::PrevLevelCommand); +// } +// let pressed_shortcut = Shortcut { +// modifiers: input.modifiers, +// key_code: virtual_keycode.into(), +// }; +// let found_child = state +// .selected_command +// .find_child_for_shortcut(&pressed_shortcut); +// if let Some(found_child) = found_child { +// return select_command(&found_child); +// } +// } +// } +// } +// _ => (), +// }, +// _ => (), +// } +// None +// } + pub fn handle_event(event: &Event, state: &State) -> Option { match event { + // conrod::event::Widget::Text(text) => println!("TEXT EVETN {:?}", text), glium::glutin::Event::WindowEvent { event, .. } => match event { // Break from the loop upon `Escape`. - glium::glutin::WindowEvent::CloseRequested - | glium::glutin::WindowEvent::KeyboardInput { - input: - glium::glutin::KeyboardInput { - virtual_keycode: Some(glium::glutin::VirtualKeyCode::Escape), - .. - }, - .. - } => return Some(SpacerunEvent::CloseApplication), + glium::glutin::WindowEvent::CloseRequested => { + return Some(SpacerunEvent::CloseApplication); + } glium::glutin::WindowEvent::Focused(false) => return Some(SpacerunEvent::FocusLost), glium::glutin::WindowEvent::KeyboardInput { input, .. } => { if let Some(virtual_keycode) = input.virtual_keycode { if input.state == glium::glutin::ElementState::Pressed { - if virtual_keycode == glium::glutin::VirtualKeyCode::Back { - return Some(SpacerunEvent::PrevLevelCommand); + if virtual_keycode == glium::glutin::VirtualKeyCode::Escape { + if state.selection_path.len() == 0 { + return Some(SpacerunEvent::CloseApplication); + } else { + return Some(SpacerunEvent::PrevLevelCommand); + } + } + if virtual_keycode == glium::glutin::VirtualKeyCode::Return { + if let Command::Leaf(leaf) = &state.selected_command { + return Some(SpacerunEvent::ExecuteCommandTask( + leaf.cmd.clone(), + state.form_command_task_variables.clone(), + )); + } } + let pressed_shortcut = Shortcut { modifiers: input.modifiers, key_code: virtual_keycode.into(), @@ -63,7 +124,7 @@ pub fn handle_event(event: &Event, state: &State) -> Option { .selected_command .find_child_for_shortcut(&pressed_shortcut); if let Some(found_child) = found_child { - return select_command(&found_child); + return select_command(&found_child, &state.form_command_task_variables); } } } @@ -75,16 +136,18 @@ pub fn handle_event(event: &Event, state: &State) -> Option { None } -fn select_command(command: & Command) -> Option { +fn select_command(command: &Command, variables: &HashMap) -> Option { match command { command @ Command::Node(_) => return Some(SpacerunEvent::SelectCommand(command.clone())), Command::Leaf(child_leaf) => { - CliCommand::new("sh") - .arg("-c") - .arg(&child_leaf.cmd) - .spawn() - .expect("process failed to execute"); - return Some(SpacerunEvent::CloseApplication); + if child_leaf.cmd.variables.len() != 0 { + return Some(SpacerunEvent::SelectCommand(command.clone())); + } else { + return Some(SpacerunEvent::ExecuteCommandTask( + child_leaf.cmd.clone(), + variables.clone(), + )); + } } } } @@ -100,12 +163,17 @@ fn select_command(command: & Command) -> Option { * TODO (LinuCC) We probably need a max height? Same as window height? */ pub fn rendered_elements_height(ui: &Ui, ids: &Ids) -> Option { + let mut height = 0.0; + if let Some(head_render_rect) = ui.kids_bounding_box(ids.head_canvas) { + height += head_render_rect.h(); + } if let Some(list_render_rect) = ui.kids_bounding_box(ids.command_list) { - if let Some(head_render_rect) = ui.kids_bounding_box(ids.head_canvas) { - return Some(list_render_rect.h() + head_render_rect.h()); - } + height += list_render_rect.h(); } - None + if let Some(list_render_rect) = ui.kids_bounding_box(ids.command_form) { + height += list_render_rect.h(); + } + return Some(height); } pub fn update_initial_window_state(ui: &mut Ui, state: &mut State, ids: &mut Ids) { @@ -114,8 +182,8 @@ pub fn update_initial_window_state(ui: &mut Ui, state: &mut State, ids: &mut Ids // FIXME LinuCC For some reason `ui.kids_bounding_box()` accesses the // `ui.prev_updated_widgets`, which only exists after generating the Ui // a second time. - set_ui(ui.set_widgets(), &state, &state.selected_command, ids); - set_ui(ui.set_widgets(), &state, &state.selected_command, ids); + set_ui(ui.set_widgets(), state, ids); + set_ui(ui.set_widgets(), state, ids); if let Some(height) = rendered_elements_height(ui, ids) { state.window_dimensions.height = height; @@ -158,27 +226,9 @@ pub fn update_window_and_window_state( } // Declare the `WidgetId`s and instantiate the widgets. -pub fn set_ui(ref mut ui: conrod::UiCell, state: &State, command: &Command, ids: &mut Ids) { +pub fn set_ui(ref mut ui: conrod::UiCell, state: &mut State, ids: &mut Ids) { use conrod::{widget, Colorable, Positionable, Sizeable, Widget}; - - let displayed_leafs = command.displayable_children(); - - // Make sure we have enough Ids for the displayed items - if displayed_leafs.len() != ids.command_list_item_canvas.len() { - ids.command_list_item_canvas - .resize(displayed_leafs.len(), &mut ui.widget_id_generator()); - ids.command_list_item_shortcut_canvas - .resize(displayed_leafs.len(), &mut ui.widget_id_generator()); - ids.command_list_item_name_canvas - .resize(displayed_leafs.len(), &mut ui.widget_id_generator()); - ids.command_list_item_shortcut_widget - .resize(displayed_leafs.len(), &mut ui.widget_id_generator()); - ids.command_list_item_name_widget - .resize(displayed_leafs.len(), &mut ui.widget_id_generator()); - } - - let child_canvas = [ ( ids.head_canvas, @@ -187,11 +237,7 @@ pub fn set_ui(ref mut ui: conrod::UiCell, state: &State, command: &Command, ids: .pad_left(10.0) .color(color::ORANGE), ), - ( - ids.list_canvas, - widget::Canvas::new() - .color(color::BLUE), - ), + (ids.main_canvas, widget::Canvas::new().color(color::BLUE)), ]; // let canvas = widget::Canvas::new() widget::Canvas::new() @@ -199,9 +245,12 @@ pub fn set_ui(ref mut ui: conrod::UiCell, state: &State, command: &Command, ids: .flow_down(&child_canvas) .set(ids.canvas, ui); - let breadcrumb_text = state.selection_path.iter().fold("Root".into(), |acc, selection| { - format!("{} > {}", acc, selection.name) - }); + let breadcrumb_text = state + .selection_path + .iter() + .fold("Root".into(), |acc, selection| { + format!("{} > {}", acc, selection.name) + }); widget::Text::new(&breadcrumb_text) .mid_left_of(ids.head_canvas) .color(color::WHITE) @@ -209,6 +258,30 @@ pub fn set_ui(ref mut ui: conrod::UiCell, state: &State, command: &Command, ids: .font_size(state.config.font_size.unwrap_or(DEFAULT_FONT_SIZE)) .set(ids.head_breadcrumbs, ui); + match state.selected_command { + Command::Node(_) => set_command_list(ui, state, ids), + Command::Leaf(_) => set_command_form(ui, state, ids), + } +} + +fn set_command_list(ui: &mut conrod::UiCell, state: &State, ids: &mut Ids) { + use conrod::{widget, Colorable, Positionable, Sizeable, Widget}; + + let displayed_leafs = state.selected_command.displayable_children(); + + // Make sure we have enough Ids for the displayed items + + let mut resize_id_list = |id_list: &mut conrod::widget::id::List| { + if displayed_leafs.len() != id_list.len() { + id_list.resize(displayed_leafs.len(), &mut ui.widget_id_generator()); + } + }; + + resize_id_list(&mut ids.command_list_item_canvas); + resize_id_list(&mut ids.command_list_item_shortcut_canvas); + resize_id_list(&mut ids.command_list_item_name_canvas); + resize_id_list(&mut ids.command_list_item_shortcut_widget); + resize_id_list(&mut ids.command_list_item_name_widget); // Generate list displaying the commands let (mut items, scrollbar) = widget::List::flow_down(displayed_leafs.len()) @@ -216,9 +289,9 @@ pub fn set_ui(ref mut ui: conrod::UiCell, state: &State, command: &Command, ids: item_height_by_font_size(state.config.font_size.unwrap_or(DEFAULT_FONT_SIZE)).into(), ) .scrollbar_on_top() - .mid_top_of(ids.list_canvas) - .w_of(ids.list_canvas) - .h_of(ids.list_canvas) + .mid_top_of(ids.main_canvas) + .w_of(ids.main_canvas) + .h_of(ids.main_canvas) .set(ids.command_list, ui); // Generate each command item @@ -249,6 +322,12 @@ pub fn set_ui(ref mut ui: conrod::UiCell, state: &State, command: &Command, ids: .font_size(state.config.font_size.unwrap_or(DEFAULT_FONT_SIZE)) .set(ids.command_list_item_shortcut_widget[i], ui); + // widget::TextBox::new(&displayed_leafs[i].name) + // .mid_left_of(ids.command_list_item_name_canvas[i]) + // .color(color::WHITE) + // .font_size(state.config.font_size.unwrap_or(DEFAULT_FONT_SIZE)) + // .w_of(ids.command_list_item_name_canvas[i]) + // .set(ids.command_list_item_name_widget[i], ui); widget::Text::new(&displayed_leafs[i].name) .mid_left_of(ids.command_list_item_name_canvas[i]) .color(color::WHITE) @@ -261,6 +340,88 @@ pub fn set_ui(ref mut ui: conrod::UiCell, state: &State, command: &Command, ids: } } +fn set_command_form(ui: &mut conrod::UiCell, state: &mut State, ids: &mut Ids) { + use conrod::{widget, Colorable, Positionable, Sizeable, Widget}; + + if let Command::Leaf(command) = &state.selected_command { + let variables = &command.cmd.variables; + + + let mut resize_id_list = |id_list: &mut conrod::widget::id::List| { + if variables.len() != id_list.len() { + id_list.resize(variables.len(), &mut ui.widget_id_generator()); + } + }; + + resize_id_list(&mut ids.command_variables_form_input_canvas); + resize_id_list(&mut ids.command_variables_form_inputs); + resize_id_list(&mut ids.command_list_item_shortcut_widget); + resize_id_list(&mut ids.command_list_item_shortcut_canvas); + + // Generate list displaying the inputs + let (mut items, scrollbar) = widget::List::flow_down(variables.len()) + .item_size( + item_height_by_font_size(state.config.font_size.unwrap_or(DEFAULT_FONT_SIZE)) + .into(), + ) + .scrollbar_on_top() + .mid_top_of(ids.main_canvas) + .w_of(ids.main_canvas) + .h_of(ids.main_canvas) + .set(ids.command_form, ui); + + while let Some(item) = items.next(ui) { + let i = item.i; + let name = &variables[i].name; + let value = state.form_command_task_variables.get_mut(name); + + if let Some(value) = value { + let text_container_canvas = widget::Canvas::new().pad(5.0); + let child_canvas = [ + ( + ids.command_list_item_shortcut_canvas[i], + text_container_canvas + .clone() + .length_weight(0.2) + .color(color::ORANGE), + ), + ( + ids.command_variables_form_input_canvas[i], + text_container_canvas.color(color::CHARCOAL), + ), + ]; + let canvas = widget::Canvas::new().flow_right(&child_canvas); + + item.set(canvas, ui); + + widget::Text::new(name) + .middle_of(ids.command_list_item_shortcut_canvas[i]) + .color(color::WHITE) + .font_size(state.config.font_size.unwrap_or(DEFAULT_FONT_SIZE)) + .set(ids.command_list_item_shortcut_widget[i], ui); + + for event in widget::TextBox::new(value) + .mid_left_of(ids.command_variables_form_input_canvas[i]) + .color(color::DARK_GRAY) + .font_size(state.config.font_size.unwrap_or(DEFAULT_FONT_SIZE)) + .wh_of(ids.command_variables_form_input_canvas[i]) + .set(ids.command_variables_form_inputs[i], ui) + { + if let conrod::widget::text_box::Event::Update(s) = event { + *value = s.clone(); + } + } + } + } + + if let Some(s) = scrollbar { + s.set(ui) + } + } else { + return; + } +} + /// Calculate the items height by the given font size fn item_height_by_font_size(font_size: u32) -> u32 { font_size + 20