//! ## Text //! //! `Text` component renders a simple readonly no event associated text /* * * 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, Props, PropsBuilder, Render}; // ext use tui::{ style::Style, text::{Span, Spans, Text as TuiText}, widgets::Paragraph, }; // NOTE: this component doesn't handle any state // -- component pub struct Text { props: Props, } impl Text { /// ### new /// /// Instantiate a new Text component pub fn new(props: Props) -> Self { Text { props } } } impl Component for Text { /// ### 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 spans: Vec = match self.props.texts.rows.as_ref() { None => Vec::new(), Some(rows) => rows .iter() .map(|x| { Span::styled( x.content.clone(), Style::default() .add_modifier(x.get_modifiers()) .fg(x.fg) .bg(x.bg), ) }) .collect(), }; // Make text let mut text: TuiText = TuiText::from(Spans::from(spans)); // Apply style text.patch_style( Style::default() .add_modifier(self.props.get_modifiers()) .fg(self.props.foreground) .bg(self.props.background), ); Some(Render { widget: Box::new(Paragraph::new(text)), cursor: 0, }) } 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, TextSpanBuilder}; use crossterm::event::{KeyCode, KeyEvent}; use tui::style::Color; #[test] fn test_ui_layout_components_text() { let mut component: Text = Text::new( PropsBuilder::default() .with_texts(TextParts::new( None, Some(vec![ TextSpan::from("Press "), TextSpanBuilder::new("") .with_foreground(Color::Cyan) .bold() .build(), TextSpan::from(" to quit"), ]), )) .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 ); } }