Start to build edit game rules screen
This commit is contained in:
parent
72af5df56f
commit
075b9e33e4
71
rust/Cargo.lock
generated
71
rust/Cargo.lock
generated
@ -467,6 +467,9 @@ dependencies = [
|
|||||||
"env_logger",
|
"env_logger",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
|
"num",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
"sea_battle_backend",
|
"sea_battle_backend",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tui",
|
"tui",
|
||||||
@ -1009,6 +1012,51 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-derive"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
@ -1019,6 +1067,29 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-iter"
|
||||||
|
version = "0.1.43"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -14,3 +14,6 @@ tui = "0.19.0"
|
|||||||
crossterm = "0.25.0"
|
crossterm = "0.25.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
tokio = "1.21.2"
|
tokio = "1.21.2"
|
||||||
|
num = "0.4.0"
|
||||||
|
num-traits = "0.2.15"
|
||||||
|
num-derive = "0.3.3"
|
@ -2,3 +2,4 @@ pub mod cli_args;
|
|||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod ui_screens;
|
pub mod ui_screens;
|
||||||
|
pub mod ui_widgets;
|
||||||
|
@ -12,11 +12,17 @@ use tui::backend::{Backend, CrosstermBackend};
|
|||||||
use tui::Terminal;
|
use tui::Terminal;
|
||||||
|
|
||||||
use cli_player::server::start_server_if_missing;
|
use cli_player::server::start_server_if_missing;
|
||||||
use cli_player::ui_screens::select_play_mode;
|
use cli_player::ui_screens::*;
|
||||||
|
use sea_battle_backend::data::{GameRules, PlayConfiguration};
|
||||||
|
|
||||||
async fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> Result<(), Box<dyn Error>> {
|
async fn run_app<B: Backend>(terminal: &mut Terminal<B>) -> Result<(), Box<dyn Error>> {
|
||||||
let res = select_play_mode::select_play_mode(terminal)?;
|
// Temporary code
|
||||||
println!("selected play mode: {:?}", res);
|
let res = configure_game_rules::configure_play_rules(
|
||||||
|
GameRules::default(),
|
||||||
|
PlayConfiguration::default(),
|
||||||
|
terminal,
|
||||||
|
)?;
|
||||||
|
println!("configured rules: {:?}", res);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
162
rust/cli_player/src/ui_screens/configure_game_rules.rs
Normal file
162
rust/cli_player/src/ui_screens/configure_game_rules.rs
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
extern crate num as num_renamed;
|
||||||
|
|
||||||
|
use std::cmp::max;
|
||||||
|
use std::io;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use crossterm::event;
|
||||||
|
use crossterm::event::{Event, KeyCode};
|
||||||
|
use tui::backend::Backend;
|
||||||
|
use tui::layout::{Constraint, Direction, Layout, Margin};
|
||||||
|
use tui::style::*;
|
||||||
|
use tui::text::Text;
|
||||||
|
use tui::widgets::*;
|
||||||
|
use tui::{Frame, Terminal};
|
||||||
|
|
||||||
|
use sea_battle_backend::data::{GameRules, PlayConfiguration};
|
||||||
|
|
||||||
|
use crate::constants::TICK_RATE;
|
||||||
|
use crate::ui_screens::utils::centered_rect_size;
|
||||||
|
use crate::ui_widgets::button_widget::ButtonWidget;
|
||||||
|
use crate::ui_widgets::checkbox_widget::CheckboxWidget;
|
||||||
|
use crate::ui_widgets::text_editor_widget::TextEditorWidget;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ConfigureGameRulesResult {
|
||||||
|
Ok(GameRules),
|
||||||
|
Canceled,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Eq, PartialEq)]
|
||||||
|
enum EditingField {
|
||||||
|
MapWidth = 0,
|
||||||
|
MapHeight,
|
||||||
|
BoatsList,
|
||||||
|
BoatsCanTouch,
|
||||||
|
PlayerContinueOnHit,
|
||||||
|
Cancel,
|
||||||
|
OK,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GameRulesConfigurationScreen {
|
||||||
|
config: PlayConfiguration,
|
||||||
|
rules: GameRules,
|
||||||
|
curr_field: EditingField,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_play_rules<B: Backend>(
|
||||||
|
rules: GameRules,
|
||||||
|
config: PlayConfiguration,
|
||||||
|
terminal: &mut Terminal<B>,
|
||||||
|
) -> io::Result<ConfigureGameRulesResult> {
|
||||||
|
let mut model = GameRulesConfigurationScreen {
|
||||||
|
config,
|
||||||
|
rules,
|
||||||
|
curr_field: EditingField::OK,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut last_tick = Instant::now();
|
||||||
|
loop {
|
||||||
|
terminal.draw(|f| ui(f, &mut model))?;
|
||||||
|
|
||||||
|
let timeout = TICK_RATE
|
||||||
|
.checked_sub(last_tick.elapsed())
|
||||||
|
.unwrap_or_else(|| Duration::from_secs(0));
|
||||||
|
|
||||||
|
if crossterm::event::poll(timeout)? {
|
||||||
|
let mut cursor_pos = model.curr_field as i32;
|
||||||
|
|
||||||
|
if let Event::Key(key) = event::read()? {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('q') => return Ok(ConfigureGameRulesResult::Canceled),
|
||||||
|
KeyCode::Up => cursor_pos -= 1,
|
||||||
|
KeyCode::Down => cursor_pos += 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply new cursor position
|
||||||
|
cursor_pos = max(0, cursor_pos);
|
||||||
|
if let Some(val) = num_renamed::FromPrimitive::from_u64(cursor_pos as u64) {
|
||||||
|
model.curr_field = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last_tick.elapsed() >= TICK_RATE {
|
||||||
|
last_tick = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui<B: Backend>(f: &mut Frame<B>, model: &mut GameRulesConfigurationScreen) {
|
||||||
|
let area = centered_rect_size(50, 16, f.size());
|
||||||
|
|
||||||
|
let block = Block::default().title("Game rules").borders(Borders::ALL);
|
||||||
|
f.render_widget(block, area);
|
||||||
|
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(3),
|
||||||
|
])
|
||||||
|
.split(area.inner(&Margin {
|
||||||
|
horizontal: 2,
|
||||||
|
vertical: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let editor = TextEditorWidget::new(
|
||||||
|
"Map width",
|
||||||
|
&model.rules.map_width.to_string(),
|
||||||
|
model.curr_field == EditingField::MapWidth,
|
||||||
|
);
|
||||||
|
f.render_widget(editor, chunks[EditingField::MapWidth as usize]);
|
||||||
|
|
||||||
|
let editor = TextEditorWidget::new(
|
||||||
|
"Map height",
|
||||||
|
&model.rules.map_height.to_string(),
|
||||||
|
model.curr_field == EditingField::MapHeight,
|
||||||
|
);
|
||||||
|
f.render_widget(editor, chunks[EditingField::MapHeight as usize]);
|
||||||
|
|
||||||
|
let editor = TextEditorWidget::new(
|
||||||
|
"Boats list",
|
||||||
|
&model
|
||||||
|
.rules
|
||||||
|
.boats_list()
|
||||||
|
.iter()
|
||||||
|
.map(usize::to_string)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("; "),
|
||||||
|
model.curr_field == EditingField::BoatsList,
|
||||||
|
);
|
||||||
|
f.render_widget(editor, chunks[EditingField::BoatsList as usize]);
|
||||||
|
|
||||||
|
let editor = CheckboxWidget::new(
|
||||||
|
"Boats can touch",
|
||||||
|
model.rules.boats_can_touch,
|
||||||
|
model.curr_field == EditingField::BoatsCanTouch,
|
||||||
|
);
|
||||||
|
f.render_widget(editor, chunks[EditingField::BoatsCanTouch as usize]);
|
||||||
|
|
||||||
|
let editor = CheckboxWidget::new(
|
||||||
|
"Player continue on hit",
|
||||||
|
model.rules.player_continue_on_hit,
|
||||||
|
model.curr_field == EditingField::PlayerContinueOnHit,
|
||||||
|
);
|
||||||
|
f.render_widget(editor, chunks[EditingField::PlayerContinueOnHit as usize]);
|
||||||
|
|
||||||
|
let buttons_chunk = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
|
.split(*chunks.last().unwrap());
|
||||||
|
|
||||||
|
let button = ButtonWidget::new("Cancel", model.curr_field == EditingField::Cancel);
|
||||||
|
f.render_widget(button, buttons_chunk[0]);
|
||||||
|
|
||||||
|
let button = ButtonWidget::new("OK", model.curr_field == EditingField::OK);
|
||||||
|
f.render_widget(button, buttons_chunk[1]);
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
|
pub mod configure_game_rules;
|
||||||
pub mod select_play_mode;
|
pub mod select_play_mode;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
@ -31,16 +31,16 @@ pub fn centered_rect_percentage(percent_x: u16, percent_y: u16, r: Rect) -> Rect
|
|||||||
pub fn centered_rect_size(width: u16, height: u16, parent: Rect) -> Rect {
|
pub fn centered_rect_size(width: u16, height: u16, parent: Rect) -> Rect {
|
||||||
if parent.width < width || parent.height < height {
|
if parent.width < width || parent.height < height {
|
||||||
return Rect {
|
return Rect {
|
||||||
x: 0,
|
x: parent.x,
|
||||||
y: 0,
|
y: parent.y,
|
||||||
width: parent.width,
|
width: parent.width,
|
||||||
height: parent.height,
|
height: parent.height,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Rect {
|
Rect {
|
||||||
x: (parent.width - width) / 2,
|
x: parent.x + (parent.width - width) / 2,
|
||||||
y: (parent.height - height) / 2,
|
y: parent.y + (parent.height - height) / 2,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
}
|
}
|
||||||
|
35
rust/cli_player/src/ui_widgets/button_widget.rs
Normal file
35
rust/cli_player/src/ui_widgets/button_widget.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use crate::ui_screens::utils::centered_rect_size;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use tui::buffer::Buffer;
|
||||||
|
use tui::layout::Rect;
|
||||||
|
use tui::style::{Color, Style};
|
||||||
|
use tui::widgets::*;
|
||||||
|
|
||||||
|
pub struct ButtonWidget {
|
||||||
|
is_hovered: bool,
|
||||||
|
label: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonWidget {
|
||||||
|
pub fn new<D: Display>(label: D, is_hovered: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.to_string(),
|
||||||
|
is_hovered,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for ButtonWidget {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let label = format!(" {} ", self.label);
|
||||||
|
|
||||||
|
let area = centered_rect_size(label.len() as u16, 1, area);
|
||||||
|
|
||||||
|
let input = Paragraph::new(label.as_ref()).style(match &self.is_hovered {
|
||||||
|
false => Style::default().bg(Color::DarkGray),
|
||||||
|
true => Style::default().fg(Color::White).bg(Color::Yellow),
|
||||||
|
});
|
||||||
|
|
||||||
|
input.render(area, buf);
|
||||||
|
}
|
||||||
|
}
|
56
rust/cli_player/src/ui_widgets/checkbox_widget.rs
Normal file
56
rust/cli_player/src/ui_widgets/checkbox_widget.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
use tui::buffer::Buffer;
|
||||||
|
use tui::layout::Rect;
|
||||||
|
use tui::style::{Color, Style};
|
||||||
|
use tui::widgets::*;
|
||||||
|
|
||||||
|
pub struct CheckboxWidget {
|
||||||
|
is_editing: bool,
|
||||||
|
checked: bool,
|
||||||
|
label: String,
|
||||||
|
is_radio: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckboxWidget {
|
||||||
|
pub fn new<D: Display>(label: D, checked: bool, is_editing: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
is_editing,
|
||||||
|
checked,
|
||||||
|
label: label.to_string(),
|
||||||
|
is_radio: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_radio(mut self) -> Self {
|
||||||
|
self.is_radio = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for CheckboxWidget {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let paragraph = format!(
|
||||||
|
"{}{}{} {}",
|
||||||
|
match self.is_radio {
|
||||||
|
true => "(",
|
||||||
|
false => "[",
|
||||||
|
},
|
||||||
|
match self.checked {
|
||||||
|
true => "X",
|
||||||
|
false => " ",
|
||||||
|
},
|
||||||
|
match self.is_radio {
|
||||||
|
true => ")",
|
||||||
|
false => "]",
|
||||||
|
},
|
||||||
|
self.label
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = Paragraph::new(paragraph.as_ref()).style(match &self.is_editing {
|
||||||
|
false => Style::default(),
|
||||||
|
true => Style::default().fg(Color::Yellow),
|
||||||
|
});
|
||||||
|
|
||||||
|
input.render(area, buf);
|
||||||
|
}
|
||||||
|
}
|
3
rust/cli_player/src/ui_widgets/mod.rs
Normal file
3
rust/cli_player/src/ui_widgets/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod button_widget;
|
||||||
|
pub mod checkbox_widget;
|
||||||
|
pub mod text_editor_widget;
|
46
rust/cli_player/src/ui_widgets/text_editor_widget.rs
Normal file
46
rust/cli_player/src/ui_widgets/text_editor_widget.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
use tui::buffer::Buffer;
|
||||||
|
use tui::layout::Rect;
|
||||||
|
use tui::style::{Color, Style};
|
||||||
|
use tui::widgets::{Block, Borders, Paragraph, Widget};
|
||||||
|
|
||||||
|
pub enum InputMode {
|
||||||
|
Normal,
|
||||||
|
Editing,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TextEditorWidget {
|
||||||
|
input_mode: InputMode,
|
||||||
|
value: String,
|
||||||
|
label: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextEditorWidget {
|
||||||
|
pub fn new<D: Display>(label: D, value: D, is_editing: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
input_mode: match is_editing {
|
||||||
|
true => InputMode::Editing,
|
||||||
|
false => InputMode::Normal,
|
||||||
|
},
|
||||||
|
value: value.to_string(),
|
||||||
|
label: label.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for TextEditorWidget {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let input = Paragraph::new(self.value.as_ref())
|
||||||
|
.style(match &self.input_mode {
|
||||||
|
InputMode::Normal => Style::default(),
|
||||||
|
InputMode::Editing => Style::default().fg(Color::Yellow),
|
||||||
|
})
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title(self.label.as_ref()),
|
||||||
|
);
|
||||||
|
|
||||||
|
input.render(area, buf);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user