diff --git a/src/ui/layout/components/file_list.rs b/src/ui/layout/components/file_list.rs
index d1379b3..eb93d4c 100644
--- a/src/ui/layout/components/file_list.rs
+++ b/src/ui/layout/components/file_list.rs
@@ -24,7 +24,7 @@
*/
// locals
-use super::{Component, InputEvent, Msg, Payload, Props, PropsBuilder, Render, States};
+use super::{Component, InputEvent, Msg, Payload, Props, PropsBuilder, Render};
// ext
use crossterm::event::KeyCode;
use tui::{
@@ -97,8 +97,6 @@ impl OwnStates {
}
}
-impl States for OwnStates {}
-
// -- Component
/// ## FileList
diff --git a/src/ui/layout/components/input.rs b/src/ui/layout/components/input.rs
new file mode 100644
index 0000000..68118ce
--- /dev/null
+++ b/src/ui/layout/components/input.rs
@@ -0,0 +1,302 @@
+//! ## Input
+//!
+//! `Input` component renders an input box
+
+/*
+*
+* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
+*
+* This file is part of "TermSCP"
+*
+* TermSCP 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.
+*
+* TermSCP 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 TermSCP. If not, see .
+*
+*/
+
+// locals
+use super::super::props::InputType;
+use super::{Component, InputEvent, Msg, Payload, Props, PropsBuilder, Render};
+// ext
+use crossterm::event::{KeyCode, KeyModifiers};
+use tui::{
+ style::Style,
+ widgets::{Block, BorderType, Borders, Paragraph},
+};
+
+// -- states
+
+/// ## OwnStates
+///
+/// OwnStates contains states for this component
+#[derive(Clone)]
+struct OwnStates {
+ input: Vec, // Current input
+ cursor: usize, // Input position
+}
+
+impl Default for OwnStates {
+ fn default() -> Self {
+ OwnStates {
+ input: Vec::new(),
+ cursor: 0,
+ }
+ }
+}
+
+impl OwnStates {
+ /// ### append
+ ///
+ /// Append, if possible according to input type, the character to the input vec
+ pub fn append(&mut self, ch: char, itype: InputType) {
+ match itype {
+ InputType::Number => {
+ if ch.is_digit(10) {
+ // Must be digit
+ self.input.push(ch);
+ // Increment cursor
+ self.cursor += 1;
+ }
+ }
+ _ => {
+ // No rule
+ self.input.push(ch);
+ // Increment cursor
+ self.cursor += 1;
+ }
+ }
+ }
+
+ /// ### backspace
+ ///
+ /// Delete element at cursor -1; then decrement cursor by 1
+ pub fn backspace(&mut self) {
+ if self.cursor > 0 && self.input.len() > 0 {
+ self.input.remove(self.cursor - 1);
+ // Decrement cursor
+ self.cursor -= 1;
+ }
+ }
+
+ /// ### delete
+ ///
+ /// Delete element at cursor
+ pub fn delete(&mut self) {
+ if self.cursor + 1 < self.input.len() {
+ self.input.remove(self.cursor);
+ }
+ }
+
+ /// ### incr_cursor
+ ///
+ /// Increment cursor value by one if possible
+ pub fn incr_cursor(&mut self) {
+ if self.cursor + 1 < self.input.len() {
+ self.cursor += 1;
+ }
+ }
+
+ /// ### decr_cursor
+ ///
+ /// Decrement cursor value by one if possible
+ pub fn decr_cursor(&mut self) {
+ if self.cursor > 0 {
+ self.cursor -= 1;
+ }
+ }
+
+ /// ### render_value
+ ///
+ /// Get value as string to render
+ pub fn render_value(&self, itype: InputType) -> String {
+ match itype {
+ InputType::Password => (0..self.input.len()).map(|_| '*').collect(),
+ _ => self.get_value(),
+ }
+ }
+
+ /// ### get_value
+ ///
+ /// Get value as string
+ pub fn get_value(&self) -> String {
+ self.input.iter().collect()
+ }
+}
+
+// -- Component
+
+/// ## FileList
+///
+/// File list component
+pub struct Input {
+ props: Props,
+ states: OwnStates,
+}
+
+impl Input {
+ /// ### new
+ ///
+ /// Instantiates a new Input starting from Props
+ /// The method also initializes the component states.
+ pub fn new(props: Props) -> Self {
+ // Initialize states
+ let mut states: OwnStates = OwnStates::default();
+ // Set state value from props
+ if let Some(val) = props.value.as_ref() {
+ for ch in val.chars() {
+ states.append(ch, props.input_type);
+ }
+ }
+ Input { props, states }
+ }
+}
+
+impl Component for Input {
+ /// ### render
+ ///
+ /// Based on the current properties and states, return a Widget instance for the Component
+ /// Returns None if the component is hidden
+ fn render(&self) -> Option {
+ if self.props.visible {
+ let title: String = match self.props.texts.title.as_ref() {
+ Some(t) => t.clone(),
+ None => String::new(),
+ };
+ let p: Paragraph = Paragraph::new(self.states.get_value())
+ .style(match self.props.focus {
+ true => Style::default().fg(self.props.foreground),
+ false => Style::default(),
+ })
+ .block(
+ Block::default()
+ .borders(Borders::ALL)
+ .border_type(BorderType::Rounded)
+ .title(title),
+ );
+ Some(Render {
+ widget: Box::new(p),
+ cursor: self.states.cursor,
+ })
+ } else {
+ None
+ }
+ }
+
+ /// ### update
+ ///
+ /// Update component properties
+ /// Properties should first be retrieved through `get_props` which creates a builder from
+ /// existing properties and then edited before calling update.
+ /// Returns a Msg to the view
+ fn update(&mut self, props: Props) -> Msg {
+ self.props = props;
+ // Don't reset value
+ Msg::None
+ }
+
+ /// ### get_props
+ ///
+ /// Returns a props builder starting from component properties.
+ /// This returns a prop builder in order to make easier to create
+ /// new properties for the element.
+ fn get_props(&self) -> PropsBuilder {
+ PropsBuilder::from_props(&self.props)
+ }
+
+ /// ### on
+ ///
+ /// Handle input event and update internal states.
+ /// Returns a Msg to the view
+ fn on(&mut self, ev: InputEvent) -> Msg {
+ if let InputEvent::Key(key) = ev {
+ match key.code {
+ KeyCode::Backspace => {
+ // Backspace and None
+ self.states.backspace();
+ Msg::None
+ }
+ KeyCode::Delete => {
+ // Delete and None
+ self.states.delete();
+ Msg::None
+ }
+ KeyCode::Enter => Msg::OnSubmit(self.get_value()),
+ KeyCode::Left => {
+ // Move cursor left; msg None
+ self.states.decr_cursor();
+ Msg::None
+ }
+ KeyCode::Right => {
+ // Move cursor right; Msg None
+ self.states.incr_cursor();
+ Msg::None
+ }
+ KeyCode::Char(ch) => {
+ // Check if modifiers is NOT CTRL OR ALT
+ if !key.modifiers.intersects(KeyModifiers::CONTROL)
+ && !key.modifiers.intersects(KeyModifiers::ALT)
+ {
+ // Push char to input
+ self.states.append(ch, self.props.input_type);
+ // Message none
+ Msg::None
+ } else {
+ // Return key
+ Msg::OnKey(key)
+ }
+ }
+ _ => Msg::OnKey(key),
+ }
+ } else {
+ Msg::None
+ }
+ }
+
+ /// ### get_value
+ ///
+ /// Get current value from component
+ /// Returns the value as string or as a number based on the input value
+ fn get_value(&self) -> Payload {
+ match self.props.input_type {
+ InputType::Number => {
+ Payload::Unumber(self.states.get_value().parse::().ok().unwrap_or(0))
+ }
+ _ => Payload::Text(self.states.get_value()),
+ }
+ }
+
+ // -- events
+
+ /// ### should_umount
+ ///
+ /// The component must provide to the supervisor whether it should be umounted (destroyed)
+ /// This makes sense to be called after an `on` or after an `update`, where the states changes.
+ /// This component never umounts
+ fn should_umount(&self) -> bool {
+ false
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+ use crate::ui::layout::props::TextParts;
+
+ use crossterm::event::KeyEvent;
+
+ #[test]
+ fn test_ui_layout_components_input_text() {
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/ui/layout/components/mod.rs b/src/ui/layout/components/mod.rs
index e30eb07..678fa28 100644
--- a/src/ui/layout/components/mod.rs
+++ b/src/ui/layout/components/mod.rs
@@ -24,7 +24,8 @@
*/
// imports
-use super::{Component, InputEvent, Msg, Payload, Props, PropsBuilder, Render, States};
+use super::{Component, InputEvent, Msg, Payload, Props, PropsBuilder, Render};
// exports
pub mod file_list;
+pub mod input;
diff --git a/src/ui/layout/mod.rs b/src/ui/layout/mod.rs
index d317cb4..f2add3b 100644
--- a/src/ui/layout/mod.rs
+++ b/src/ui/layout/mod.rs
@@ -68,14 +68,6 @@ pub struct Render {
pub cursor: usize, // Cursor position
}
-// -- States
-
-/// ## States
-///
-/// States is a trait which defines the behaviours for the states model for the different component.
-/// A state contains internal values for each component.
-pub(crate) trait States {}
-
// -- Component
/// ## Component
diff --git a/src/ui/layout/props.rs b/src/ui/layout/props.rs
index 9d909f7..7d9816d 100644
--- a/src/ui/layout/props.rs
+++ b/src/ui/layout/props.rs
@@ -286,7 +286,7 @@ impl Default for TextParts {
/// ## InputType
///
/// Input type for text inputs
-#[derive(Clone, PartialEq, std::fmt::Debug)]
+#[derive(Clone, Copy, PartialEq, std::fmt::Debug)]
pub enum InputType {
Text,
Number,