All checks were successful
continuous-integration/drone/push Build is passing
178 lines
5.5 KiB
Rust
178 lines
5.5 KiB
Rust
use std::fmt::Display;
|
|
use std::io;
|
|
use std::time::{Duration, Instant};
|
|
use tui::style::*;
|
|
|
|
use crossterm::event;
|
|
use crossterm::event::{Event, KeyCode};
|
|
use tui::backend::Backend;
|
|
use tui::layout::*;
|
|
use tui::widgets::*;
|
|
use tui::{Frame, Terminal};
|
|
|
|
use crate::constants::*;
|
|
use crate::ui_screens::utils::*;
|
|
use crate::ui_screens::ScreenResult;
|
|
use crate::ui_widgets::button_widget::ButtonWidget;
|
|
use crate::ui_widgets::text_editor_widget::TextEditorWidget;
|
|
|
|
pub struct InputScreen<'a> {
|
|
title: &'a str,
|
|
msg: &'a str,
|
|
input_label: &'a str,
|
|
value: String,
|
|
can_cancel: bool,
|
|
is_cancel_hovered: bool,
|
|
value_required: bool,
|
|
min_len: usize,
|
|
max_len: usize,
|
|
has_already_been_edited: bool,
|
|
}
|
|
|
|
impl<'a> InputScreen<'a> {
|
|
pub fn new(msg: &'a str) -> Self {
|
|
Self {
|
|
title: "Input",
|
|
msg,
|
|
input_label: "",
|
|
value: "".to_string(),
|
|
can_cancel: true,
|
|
is_cancel_hovered: false,
|
|
value_required: true,
|
|
min_len: 1,
|
|
max_len: 10,
|
|
has_already_been_edited: false,
|
|
}
|
|
}
|
|
|
|
pub fn set_title(mut self, title: &'a str) -> Self {
|
|
self.title = title;
|
|
self
|
|
}
|
|
|
|
pub fn set_value<D: Display>(mut self, value: D) -> Self {
|
|
self.value = value.to_string();
|
|
self
|
|
}
|
|
|
|
/// Get error contained in input
|
|
fn error(&self) -> Option<&'static str> {
|
|
if self.value.len() > self.max_len {
|
|
Some("Input is too large!")
|
|
} else if self.value.is_empty() && !self.value_required {
|
|
None
|
|
} else if self.value.len() < self.min_len {
|
|
Some("Input is too small!")
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn show<B: Backend>(
|
|
mut self,
|
|
terminal: &mut Terminal<B>,
|
|
) -> io::Result<ScreenResult<String>> {
|
|
let mut last_tick = Instant::now();
|
|
loop {
|
|
terminal.draw(|f| self.ui(f))?;
|
|
|
|
let timeout = TICK_RATE
|
|
.checked_sub(last_tick.elapsed())
|
|
.unwrap_or_else(|| Duration::from_secs(0));
|
|
|
|
if event::poll(timeout)? {
|
|
if let Event::Key(key) = event::read()? {
|
|
match key.code {
|
|
KeyCode::Esc => return Ok(ScreenResult::Canceled),
|
|
KeyCode::Tab if self.can_cancel => {
|
|
self.is_cancel_hovered = !self.is_cancel_hovered;
|
|
}
|
|
KeyCode::Enter => {
|
|
if self.is_cancel_hovered {
|
|
return Ok(ScreenResult::Canceled);
|
|
} else if self.error().is_none() {
|
|
return Ok(ScreenResult::Ok(self.value));
|
|
}
|
|
}
|
|
KeyCode::Backspace => {
|
|
self.value.pop();
|
|
}
|
|
KeyCode::Char(c) => {
|
|
if self.value.len() < self.max_len {
|
|
self.has_already_been_edited = true;
|
|
self.value.push(c);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
if last_tick.elapsed() >= TICK_RATE {
|
|
last_tick = Instant::now();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ui<B: Backend>(&mut self, f: &mut Frame<B>) {
|
|
let area = centered_rect_size(
|
|
(self.msg.len() + 4).max(self.max_len + 4) as u16,
|
|
7,
|
|
&f.size(),
|
|
);
|
|
|
|
let error = self.error();
|
|
|
|
let block = Block::default().borders(Borders::ALL).title(self.title);
|
|
f.render_widget(block, area);
|
|
|
|
// Create two chunks with equal horizontal screen space
|
|
let chunks = Layout::default()
|
|
.direction(Direction::Vertical)
|
|
.constraints(
|
|
[
|
|
Constraint::Length(1),
|
|
Constraint::Length(3),
|
|
Constraint::Length(3),
|
|
]
|
|
.as_ref(),
|
|
)
|
|
.split(area.inner(&Margin {
|
|
horizontal: 2,
|
|
vertical: 1,
|
|
}));
|
|
|
|
let paragraph = Paragraph::new(self.msg);
|
|
f.render_widget(paragraph, chunks[0]);
|
|
|
|
let input_widget =
|
|
TextEditorWidget::new(self.input_label, &self.value, !self.is_cancel_hovered);
|
|
f.render_widget(input_widget, chunks[1]);
|
|
|
|
let buttons_area = Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
|
.split(*chunks.last().unwrap());
|
|
|
|
let cancel_button = ButtonWidget::new("Cancel", self.is_cancel_hovered)
|
|
.set_disabled(!self.can_cancel)
|
|
.set_min_width(8);
|
|
f.render_widget(cancel_button, buttons_area[0]);
|
|
|
|
let ok_button = ButtonWidget::new("OK", !self.is_cancel_hovered)
|
|
.set_min_width(8)
|
|
.set_disabled(error.is_some());
|
|
f.render_widget(ok_button, buttons_area[1]);
|
|
|
|
// Render error (if any)
|
|
if let (Some(e), true) = (error, self.has_already_been_edited) {
|
|
let target_area = centered_text(
|
|
e,
|
|
&Rect::new(f.size().x, area.bottom() + 2, f.size().width, 1),
|
|
);
|
|
|
|
let paragraph = Paragraph::new(e).style(Style::default().fg(Color::Red));
|
|
f.render_widget(paragraph, target_area)
|
|
}
|
|
}
|
|
}
|