Start to build edit game rules screen
This commit is contained in:
		
							
								
								
									
										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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user