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;
|
||||
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 = 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(
|
||||
ErrorKind::Other,
|
||||
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 confirm_dialog;
|
||||
pub mod input_screen;
|
||||
pub mod popup_screen;
|
||||
pub mod select_bot_type;
|
||||
pub mod select_play_mode;
|
||||
|
@ -12,6 +12,7 @@ pub struct ButtonWidget {
|
||||
is_hovered: bool,
|
||||
label: String,
|
||||
disabled: bool,
|
||||
min_width: usize,
|
||||
}
|
||||
|
||||
impl ButtonWidget {
|
||||
@ -20,6 +21,7 @@ impl ButtonWidget {
|
||||
label: label.to_string(),
|
||||
is_hovered,
|
||||
disabled: false,
|
||||
min_width: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,11 +29,22 @@ impl ButtonWidget {
|
||||
self.disabled = disabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_min_width(mut self, min_width: usize) -> Self {
|
||||
self.min_width = min_width;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for ButtonWidget {
|
||||
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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user