This repository has been archived on 2025-03-28. You can view files and clone it, but cannot push or open issues or pull requests.
Pierre Hubert 3c2b96f3a2
All checks were successful
continuous-integration/drone/push Build is passing
Can play against random player
2022-10-16 17:47:41 +02:00

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