//! In-window Command line interface. Used for operations that are just easier than with GUI. //! //! Sometimes it is nice to have a GUI, for instance when a selection has to be made, things have to //! be moved etc., however for operations like saving/loading and exporting, no such thing has to be //! done and the GUI is really just slowing you down (at least in my opinion). For these operations, //! it is much better to simply have a command do that specific thing. It is also much easier to //! implement a new command, so features can be tested more quickly. For some things, there should //! still be a GUI option. With the example of saving/loading, it is much easier to find some hidden //! folder in a GUI, so that is definitely a consideration for the future. pub mod cmd; pub use self::cmd::*; use crate::client::colours::DEFAULT_COLOURS; use crate::client::input::{Button, Input}; use crate::client::Editor; use crate::math::Vec2; use raylib::drawing::{RaylibDraw, RaylibDrawHandle}; use std::sync::mpsc::Receiver; /// The command line interface. Should be created only once per program instance. pub struct CLI { text: String, char_pipe: Option>, } impl CLI { /// Create a CLI for this instance pub fn new(input: &mut Input) -> Self { input.add_global(Button::Text(':').into()); Self { text: String::new(), char_pipe: None, } } /// Activates the CLI, which will now capture keyboard input and execute commands accordingly. pub fn try_activate(&mut self, input: &mut Input) { if !self.active() { self.char_pipe = input.try_capture_keyboard(); if self.char_pipe.is_some() { self.text = ":".to_owned(); } } } /// Deactivate the command line. pub fn deactivate(&mut self) { // Hang up, the input handler will get the message. self.char_pipe = None; } /// Checks if the CLI is currently active. This means input to other things should be ignored. pub fn active(&self) -> bool { self.char_pipe.is_some() } fn perform_command(&mut self, editor: &mut Editor) { match cmd::parse_command(&self.text[1..]) { Ok(cmd) => match cmd.process(editor) { Ok(res) => self.text = format!("SUCCESS: {}", res), Err(err) => self.text = format!("ERROR: {}", err), }, Err(err) => self.text = format!("SYNTAX ERROR: {}", err), } } /// Handle input for the command line and perform any commands the user may want to run. pub fn update(&mut self, editor: &mut Editor, input: &mut Input) { /* Check if the CLI is currently active. If not and it should not be activated according to * keyboard input, there is nothing to do. */ if !self.active() { if input.poll_global(&Button::Text(':').into()) { self.try_activate(input); if !self.active() { return; } } else { return; } } let rx = self .char_pipe .as_mut() .expect("No character pipe eventhough CLI should be active"); if let Ok(c) = rx.try_recv() { match c { '\x1B' => self.text.clear(), // Escape '\x7f' => { self.text.pop(); } // Backspace '\n' => { self.perform_command(editor); self.deactivate(); } c => self.text.push(c), } } // When the text is empty, there is also no command marker, so set as inactive and leave. if self.text.is_empty() { self.deactivate() } } /// Draw the command line at the bottom of the window. pub fn draw(&self, rld: &mut RaylibDrawHandle) { let pos = Vec2::new(150., rld.get_screen_height() as f32 - 25.); rld.draw_rectangle_v( pos, Vec2::new(rld.get_screen_width() as f32 - pos.x, 25.), DEFAULT_COLOURS.cli_background, ); rld.draw_text( &self.text, 155, rld.get_screen_height() - 22, 20, DEFAULT_COLOURS.cli_foreground, ); } }