diff --git a/src/ui/layout/components/input.rs b/src/ui/layout/components/input.rs
index 823815c..05ef56e 100644
--- a/src/ui/layout/components/input.rs
+++ b/src/ui/layout/components/input.rs
@@ -176,7 +176,7 @@ impl Component for Input {
Some(t) => t.clone(),
None => String::new(),
};
- let p: Paragraph = Paragraph::new(self.states.get_value())
+ let p: Paragraph = Paragraph::new(self.states.render_value(self.props.input_type))
.style(match self.states.focus {
true => Style::default().fg(self.props.foreground),
false => Style::default(),
diff --git a/src/ui/layout/components/mod.rs b/src/ui/layout/components/mod.rs
index c80e331..eed68fe 100644
--- a/src/ui/layout/components/mod.rs
+++ b/src/ui/layout/components/mod.rs
@@ -29,5 +29,6 @@ use super::{Component, InputEvent, Msg, Payload, PropValue, Props, PropsBuilder,
// exports
pub mod file_list;
pub mod input;
+pub mod progress_bar;
pub mod radio_group;
pub mod text;
diff --git a/src/ui/layout/components/progress_bar.rs b/src/ui/layout/components/progress_bar.rs
new file mode 100644
index 0000000..323a14e
--- /dev/null
+++ b/src/ui/layout/components/progress_bar.rs
@@ -0,0 +1,191 @@
+//! ## ProgressBar
+//!
+//! `ProgressBar` component renders a progress bar
+
+/*
+*
+* 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::{Component, InputEvent, Msg, Payload, PropValue, Props, PropsBuilder, Render};
+// ext
+use tui::{
+ style::Style,
+ widgets::{Block, Borders, Gauge},
+};
+
+// NOTE: this component doesn't handle any state
+
+// -- component
+
+pub struct ProgressBar {
+ props: Props,
+}
+
+impl ProgressBar {
+ /// ### new
+ ///
+ /// Instantiate a new Progress Bar
+ pub fn new(props: Props) -> Self {
+ ProgressBar { props }
+ }
+}
+
+impl Component for ProgressBar {
+ /// ### 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 {
+ // Make a Span
+ if self.props.visible {
+ let title: String = match self.props.texts.title.as_ref() {
+ Some(t) => t.clone(),
+ None => String::new(),
+ };
+ // Text
+ let label: String = match self.props.texts.rows.as_ref() {
+ Some(rows) => match rows.get(0) {
+ Some(label) => label.content.clone(),
+ None => String::new(),
+ },
+ None => String::new(),
+ };
+ // Get percentage
+ let percentage: f64 = match self.props.value {
+ PropValue::Float(ratio) => ratio,
+ _ => 0.0,
+ };
+ // Make progress bar
+ Some(Render {
+ cursor: 0,
+ widget: Box::new(
+ Gauge::default()
+ .block(Block::default().borders(Borders::ALL).title(title))
+ .gauge_style(
+ Style::default()
+ .fg(self.props.foreground)
+ .bg(self.props.background)
+ .add_modifier(self.props.get_modifiers()),
+ )
+ .label(label)
+ .ratio(percentage),
+ ),
+ })
+ } else {
+ // Invisible
+ 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;
+ // Return None
+ 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(self.props.clone())
+ }
+
+ /// ### on
+ ///
+ /// Handle input event and update internal states.
+ /// Returns a Msg to the view.
+ /// Returns always None, since cannot have any focus
+ fn on(&mut self, _ev: InputEvent) -> Msg {
+ Msg::None
+ }
+
+ /// ### get_value
+ ///
+ /// Get current value from component
+ /// For this component returns always None
+ fn get_value(&self) -> Payload {
+ Payload::None
+ }
+
+ // -- 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.
+ /// Always false for this component
+ fn should_umount(&self) -> bool {
+ false
+ }
+
+ /// ### blur
+ ///
+ /// Blur component; does nothing on this component
+ fn blur(&mut self) {}
+
+ /// ### active
+ ///
+ /// Active component; does nothing on this component
+ fn active(&mut self) {}
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+ use crate::ui::layout::props::{TextParts, TextSpan};
+
+ use crossterm::event::{KeyCode, KeyEvent};
+
+ #[test]
+ fn test_ui_layout_components_progress_bar() {
+ let mut component: ProgressBar = ProgressBar::new(
+ PropsBuilder::default()
+ .with_texts(TextParts::new(
+ Some(String::from("Uploading file...")),
+ Some(vec![TextSpan::from("96.5% - ETA 00:02 (4.2MB/s)")]),
+ ))
+ .build(),
+ );
+ // Focus
+ component.active();
+ component.blur();
+ // Should umount
+ assert_eq!(component.should_umount(), false);
+ // Get value
+ assert_eq!(component.get_value(), Payload::None);
+ // Render
+ assert_eq!(component.render().unwrap().cursor, 0);
+ // Event
+ assert_eq!(
+ component.on(InputEvent::Key(KeyEvent::from(KeyCode::Delete))),
+ Msg::None
+ );
+ }
+}