Add input screen
This commit is contained in:
parent
3e3169dd27
commit
5d4940adc6
@ -23,7 +23,10 @@ async fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> Result<(), Box<dyn E
|
|||||||
rules.boats_can_touch = true;
|
rules.boats_can_touch = true;
|
||||||
let res = set_boats_layout::set_boat_layout(&rules, terminal)?; // select_bot_type::select_bot_type(terminal)?;*/
|
let res = set_boats_layout::set_boat_layout(&rules, terminal)?; // select_bot_type::select_bot_type(terminal)?;*/
|
||||||
/*let res = popup_screen::popup_screen("Hi\nWelcome there", terminal);*/
|
/*let res = popup_screen::popup_screen("Hi\nWelcome there", terminal);*/
|
||||||
let res = confirm_dialog::confirm_dialog("Do you really want to interrupt game ?", terminal)?; // select_bot_type::select_bot_type(terminal)?;
|
// let res = confirm_dialog::confirm_dialog("Do you really want to interrupt game ?", terminal)?; // select_bot_type::select_bot_type(terminal)?;
|
||||||
|
let res = input_screen::InputScreen::new("Whas it your name ?")
|
||||||
|
.set_title("custom title")
|
||||||
|
.show(terminal)?; // select_bot_type::select_bot_type(terminal)?;
|
||||||
Err(io::Error::new(
|
Err(io::Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
format!("result: {:?}", res),
|
format!("result: {:?}", res),
|
||||||
|
171
rust/cli_player/src/ui_screens/input_screen.rs
Normal file
171
rust/cli_player/src/ui_screens/input_screen.rs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
pub mod configure_game_rules;
|
pub mod configure_game_rules;
|
||||||
pub mod confirm_dialog;
|
pub mod confirm_dialog;
|
||||||
|
pub mod input_screen;
|
||||||
pub mod popup_screen;
|
pub mod popup_screen;
|
||||||
pub mod select_bot_type;
|
pub mod select_bot_type;
|
||||||
pub mod select_play_mode;
|
pub mod select_play_mode;
|
||||||
|
@ -12,6 +12,7 @@ pub struct ButtonWidget {
|
|||||||
is_hovered: bool,
|
is_hovered: bool,
|
||||||
label: String,
|
label: String,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
|
min_width: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ButtonWidget {
|
impl ButtonWidget {
|
||||||
@ -20,6 +21,7 @@ impl ButtonWidget {
|
|||||||
label: label.to_string(),
|
label: label.to_string(),
|
||||||
is_hovered,
|
is_hovered,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
min_width: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,11 +29,22 @@ impl ButtonWidget {
|
|||||||
self.disabled = disabled;
|
self.disabled = disabled;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_min_width(mut self, min_width: usize) -> Self {
|
||||||
|
self.min_width = min_width;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for ButtonWidget {
|
impl Widget for ButtonWidget {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let label = format!(" {} ", self.label);
|
let expected_len = (self.label.len() + 2).max(self.min_width);
|
||||||
|
|
||||||
|
let mut label = self.label.clone();
|
||||||
|
while label.len() < expected_len {
|
||||||
|
label.insert(0, ' ');
|
||||||
|
label.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
let area = centered_rect_size(label.len() as u16, 1, &area);
|
let area = centered_rect_size(label.len() as u16, 1, &area);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user