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 + ); + } +}