Input modes
This commit is contained in:
parent
5a0e712237
commit
38e7fa7664
|
@ -39,7 +39,7 @@ use tui::{
|
||||||
layout::{Constraint, Direction, Layout},
|
layout::{Constraint, Direction, Layout},
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
text::{Span, Spans, Text},
|
text::{Span, Spans, Text},
|
||||||
widgets::{Block, Borders, List, ListItem, Paragraph, Tabs},
|
widgets::{Block, Borders, Paragraph, Tabs},
|
||||||
};
|
};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
@ -55,6 +55,16 @@ enum InputField {
|
||||||
Password,
|
Password,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ### InputMode
|
||||||
|
///
|
||||||
|
/// InputMode describes the current input mode
|
||||||
|
/// Each input mode handle the input events in a different way
|
||||||
|
#[derive(std::cmp::PartialEq)]
|
||||||
|
enum InputMode {
|
||||||
|
Text,
|
||||||
|
Popup,
|
||||||
|
}
|
||||||
|
|
||||||
/// ### ScpProtocol
|
/// ### ScpProtocol
|
||||||
///
|
///
|
||||||
/// ScpProtocol describes the communication protocol selected by the user to communicate with the remote
|
/// ScpProtocol describes the communication protocol selected by the user to communicate with the remote
|
||||||
|
@ -76,6 +86,7 @@ pub struct AuthActivity {
|
||||||
pub submit: bool, // becomes true after user has submitted fields
|
pub submit: bool, // becomes true after user has submitted fields
|
||||||
pub quit: bool, // Becomes true if user has pressed esc
|
pub quit: bool, // Becomes true if user has pressed esc
|
||||||
selected_field: InputField,
|
selected_field: InputField,
|
||||||
|
input_mode: InputMode,
|
||||||
popup_message: Option<String>,
|
popup_message: Option<String>,
|
||||||
password_placeholder: String,
|
password_placeholder: String,
|
||||||
}
|
}
|
||||||
|
@ -94,11 +105,172 @@ impl AuthActivity {
|
||||||
submit: false,
|
submit: false,
|
||||||
quit: false,
|
quit: false,
|
||||||
selected_field: InputField::Address,
|
selected_field: InputField::Address,
|
||||||
|
input_mode: InputMode::Text,
|
||||||
popup_message: None,
|
popup_message: None,
|
||||||
password_placeholder: String::new(),
|
password_placeholder: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ### set_input_mode
|
||||||
|
///
|
||||||
|
/// Update input mode based on current parameters
|
||||||
|
fn select_input_mode(&mut self) -> InputMode {
|
||||||
|
if self.popup_message.is_some() {
|
||||||
|
return InputMode::Popup;
|
||||||
|
}
|
||||||
|
// Default to text
|
||||||
|
InputMode::Text
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### handle_input_event
|
||||||
|
///
|
||||||
|
/// Handle input event, based on current input mode
|
||||||
|
fn handle_input_event(&mut self, ev: &InputEvent) {
|
||||||
|
match self.input_mode {
|
||||||
|
InputMode::Text => self.handle_input_event_mode_text(ev),
|
||||||
|
InputMode::Popup => self.handle_input_event_mode_popup(ev),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### handle_input_event_mode_text
|
||||||
|
///
|
||||||
|
/// Handler for input event when in textmode
|
||||||
|
fn handle_input_event_mode_text(&mut self, ev: &InputEvent) {
|
||||||
|
match ev {
|
||||||
|
InputEvent::Key(key) => {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Esc => {
|
||||||
|
self.quit = true;
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
// Handle submit
|
||||||
|
// Check form
|
||||||
|
// Check address
|
||||||
|
if self.address.len() == 0 {
|
||||||
|
self.popup_message = Some(String::from("Invalid address"));
|
||||||
|
}
|
||||||
|
// Check port
|
||||||
|
// Convert port to number
|
||||||
|
match self.port.parse::<usize>() {
|
||||||
|
Ok(val) => {
|
||||||
|
if val > 65535 {
|
||||||
|
self.popup_message = Some(String::from(
|
||||||
|
"Specified port must be in range 0-65535",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
self.popup_message =
|
||||||
|
Some(String::from("Specified port is not a number"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check username
|
||||||
|
if self.username.len() == 0 {
|
||||||
|
self.popup_message = Some(String::from("Invalid username"));
|
||||||
|
}
|
||||||
|
// Everything OK, set enter
|
||||||
|
self.submit = true;
|
||||||
|
self.popup_message =
|
||||||
|
Some(format!("Connecting to {}:{}...", self.address, self.port));
|
||||||
|
}
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
// Pop last char
|
||||||
|
match self.selected_field {
|
||||||
|
InputField::Address => {
|
||||||
|
let _ = self.address.pop();
|
||||||
|
}
|
||||||
|
InputField::Password => {
|
||||||
|
let _ = self.password.pop();
|
||||||
|
}
|
||||||
|
InputField::Username => {
|
||||||
|
let _ = self.username.pop();
|
||||||
|
}
|
||||||
|
InputField::Port => {
|
||||||
|
let _ = self.port.pop();
|
||||||
|
}
|
||||||
|
_ => { /* Nothing to do */ }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
KeyCode::Up => {
|
||||||
|
// Move item up
|
||||||
|
self.selected_field = match self.selected_field {
|
||||||
|
InputField::Address => InputField::Address, // End of list
|
||||||
|
InputField::Port => InputField::Address,
|
||||||
|
InputField::Protocol => InputField::Port,
|
||||||
|
InputField::Username => InputField::Protocol,
|
||||||
|
InputField::Password => InputField::Username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Down => {
|
||||||
|
// Move item down
|
||||||
|
self.selected_field = match self.selected_field {
|
||||||
|
InputField::Address => InputField::Port,
|
||||||
|
InputField::Port => InputField::Protocol,
|
||||||
|
InputField::Protocol => InputField::Username,
|
||||||
|
InputField::Username => InputField::Password,
|
||||||
|
InputField::Password => InputField::Password, // End of list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Char(ch) => {
|
||||||
|
match self.selected_field {
|
||||||
|
InputField::Address => self.address.push(ch),
|
||||||
|
InputField::Password => self.password.push(ch),
|
||||||
|
InputField::Username => self.username.push(ch),
|
||||||
|
InputField::Port => {
|
||||||
|
// Value must be numeric
|
||||||
|
if ch.is_numeric() {
|
||||||
|
self.port.push(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { /* Nothing to do */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Left => {
|
||||||
|
// If current field is Protocol handle event... (move element left)
|
||||||
|
if self.selected_field == InputField::Protocol {
|
||||||
|
self.protocol = match self.protocol {
|
||||||
|
ScpProtocol::Sftp => ScpProtocol::Sftp,
|
||||||
|
ScpProtocol::Ftp => ScpProtocol::Sftp, // End of list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyCode::Right => {
|
||||||
|
// If current field is Protocol handle event... ( move element right )
|
||||||
|
if self.selected_field == InputField::Protocol {
|
||||||
|
self.protocol = match self.protocol {
|
||||||
|
ScpProtocol::Sftp => ScpProtocol::Ftp,
|
||||||
|
ScpProtocol::Ftp => ScpProtocol::Ftp, // End of list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { /* Nothing to do */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { /* Nothing to do */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ### handle_input_event_mode_text
|
||||||
|
///
|
||||||
|
/// Handler for input event when in popup mode
|
||||||
|
fn handle_input_event_mode_popup(&mut self, ev: &InputEvent) {
|
||||||
|
// Only enter (+ ESC) should be allowed here
|
||||||
|
match ev {
|
||||||
|
InputEvent::Key(key) => {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Esc => {
|
||||||
|
self.quit = true;
|
||||||
|
}
|
||||||
|
KeyCode::Enter => {
|
||||||
|
self.popup_message = None; // Hide popup
|
||||||
|
}
|
||||||
|
_ => { /* Nothing to do */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => { /* Nothing to do */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ### draw_remote_address
|
/// ### draw_remote_address
|
||||||
///
|
///
|
||||||
/// Draw remote address block
|
/// Draw remote address block
|
||||||
|
@ -177,7 +349,7 @@ impl AuthActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ### draw_header
|
/// ### draw_header
|
||||||
///
|
///
|
||||||
/// Draw header
|
/// Draw header
|
||||||
fn draw_header(&self) -> Paragraph {
|
fn draw_header(&self) -> Paragraph {
|
||||||
Paragraph::new(" _____ ____ ____ ____ \n|_ _|__ _ __ _ __ ___ / ___| / ___| _ \\ \n | |/ _ \\ '__| '_ ` _ \\\\___ \\| | | |_) |\n | | __/ | | | | | | |___) | |___| __/ \n |_|\\___|_| |_| |_| |_|____/ \\____|_| \n")
|
Paragraph::new(" _____ ____ ____ ____ \n|_ _|__ _ __ _ __ ___ / ___| / ___| _ \\ \n | |/ _ \\ '__| '_ ` _ \\\\___ \\| | | |_) |\n | | __/ | | | | | | |___) | |___| __/ \n |_|\\___|_| |_| |_| |_|____/ \\____|_| \n")
|
||||||
|
@ -228,132 +400,16 @@ impl Activity for AuthActivity {
|
||||||
if let Ok(input_events) = context.input_hnd.fetch_events() {
|
if let Ok(input_events) = context.input_hnd.fetch_events() {
|
||||||
// Iterate over input events
|
// Iterate over input events
|
||||||
for event in input_events.iter() {
|
for event in input_events.iter() {
|
||||||
match event {
|
self.handle_input_event(event);
|
||||||
InputEvent::Key(key) => {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Esc => {
|
|
||||||
self.quit = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
// Handle submit
|
|
||||||
// Check form
|
|
||||||
// Check address
|
|
||||||
if self.address.len() == 0 {
|
|
||||||
self.popup_message = Some(String::from("Invalid address"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Check port
|
|
||||||
// Convert port to number
|
|
||||||
match self.port.parse::<usize>() {
|
|
||||||
Ok(val) => {
|
|
||||||
if val > 65535 {
|
|
||||||
self.popup_message = Some(String::from(
|
|
||||||
"Specified port must be in range 0-65535",
|
|
||||||
));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
self.popup_message =
|
|
||||||
Some(String::from("Specified port is not a number"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check username
|
|
||||||
if self.username.len() == 0 {
|
|
||||||
self.popup_message = Some(String::from("Invalid username"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Everything OK, set enter
|
|
||||||
self.submit = true;
|
|
||||||
self.popup_message = Some(format!(
|
|
||||||
"Connecting to {}:{}...",
|
|
||||||
self.address, self.port
|
|
||||||
));
|
|
||||||
}
|
|
||||||
KeyCode::Backspace => {
|
|
||||||
// Pop last char
|
|
||||||
match self.selected_field {
|
|
||||||
InputField::Address => {
|
|
||||||
let _ = self.address.pop();
|
|
||||||
}
|
|
||||||
InputField::Password => {
|
|
||||||
let _ = self.password.pop();
|
|
||||||
}
|
|
||||||
InputField::Username => {
|
|
||||||
let _ = self.username.pop();
|
|
||||||
}
|
|
||||||
InputField::Port => {
|
|
||||||
let _ = self.port.pop();
|
|
||||||
}
|
|
||||||
_ => { /* Nothing to do */ }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
KeyCode::Up => {
|
|
||||||
// Move item up
|
|
||||||
self.selected_field = match self.selected_field {
|
|
||||||
InputField::Address => InputField::Address, // End of list
|
|
||||||
InputField::Port => InputField::Address,
|
|
||||||
InputField::Protocol => InputField::Port,
|
|
||||||
InputField::Username => InputField::Protocol,
|
|
||||||
InputField::Password => InputField::Username,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Down => {
|
|
||||||
// Move item down
|
|
||||||
self.selected_field = match self.selected_field {
|
|
||||||
InputField::Address => InputField::Port,
|
|
||||||
InputField::Port => InputField::Protocol,
|
|
||||||
InputField::Protocol => InputField::Username,
|
|
||||||
InputField::Username => InputField::Password,
|
|
||||||
InputField::Password => InputField::Password, // End of list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char(ch) => {
|
|
||||||
match self.selected_field {
|
|
||||||
InputField::Address => self.address.push(ch),
|
|
||||||
InputField::Password => self.password.push(ch),
|
|
||||||
InputField::Username => self.username.push(ch),
|
|
||||||
InputField::Port => {
|
|
||||||
// Value must be numeric
|
|
||||||
if ch.is_numeric() {
|
|
||||||
self.port.push(ch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => { /* Nothing to do */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Left => {
|
|
||||||
// If current field is Protocol handle event... (move element left)
|
|
||||||
if self.selected_field == InputField::Protocol {
|
|
||||||
self.protocol = match self.protocol {
|
|
||||||
ScpProtocol::Sftp => ScpProtocol::Sftp,
|
|
||||||
ScpProtocol::Ftp => ScpProtocol::Sftp, // End of list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Right => {
|
|
||||||
// If current field is Protocol handle event... ( move element right )
|
|
||||||
if self.selected_field == InputField::Protocol {
|
|
||||||
self.protocol = match self.protocol {
|
|
||||||
ScpProtocol::Sftp => ScpProtocol::Ftp,
|
|
||||||
ScpProtocol::Ftp => ScpProtocol::Ftp, // End of list
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => { /* Nothing to do */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => { /* Nothing to do */ }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Determine input mode
|
||||||
|
self.input_mode = self.select_input_mode();
|
||||||
// draw interface
|
// draw interface
|
||||||
let _ = context.terminal.draw(|f| {
|
let _ = context.terminal.draw(|f| {
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.margin(5)
|
.margin(2)
|
||||||
.constraints(
|
.constraints(
|
||||||
[
|
[
|
||||||
Constraint::Length(5),
|
Constraint::Length(5),
|
||||||
|
@ -384,10 +440,9 @@ impl Activity for AuthActivity {
|
||||||
chunks[1].x + self.address.width() as u16 + 1,
|
chunks[1].x + self.address.width() as u16 + 1,
|
||||||
chunks[1].y + 1,
|
chunks[1].y + 1,
|
||||||
),
|
),
|
||||||
InputField::Port => f.set_cursor(
|
InputField::Port => {
|
||||||
chunks[2].x + self.port.width() as u16 + 1,
|
f.set_cursor(chunks[2].x + self.port.width() as u16 + 1, chunks[2].y + 1)
|
||||||
chunks[2].y + 1
|
}
|
||||||
),
|
|
||||||
InputField::Username => f.set_cursor(
|
InputField::Username => f.set_cursor(
|
||||||
chunks[4].x + self.username.width() as u16 + 1,
|
chunks[4].x + self.username.width() as u16 + 1,
|
||||||
chunks[4].y + 1,
|
chunks[4].y + 1,
|
||||||
|
|
Loading…
Reference in a new issue