Add mouse interaction
This commit is contained in:
		@@ -1,8 +1,9 @@
 | 
				
			|||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
use std::io;
 | 
					use std::io;
 | 
				
			||||||
use std::time::{Duration, Instant};
 | 
					use std::time::{Duration, Instant};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crossterm::event;
 | 
					use crossterm::event;
 | 
				
			||||||
use crossterm::event::{Event, KeyCode};
 | 
					use crossterm::event::{Event, KeyCode, MouseButton, MouseEventKind};
 | 
				
			||||||
use tui::backend::Backend;
 | 
					use tui::backend::Backend;
 | 
				
			||||||
use tui::style::*;
 | 
					use tui::style::*;
 | 
				
			||||||
use tui::{Frame, Terminal};
 | 
					use tui::{Frame, Terminal};
 | 
				
			||||||
@@ -19,6 +20,8 @@ struct SetBotsLayoutScreen {
 | 
				
			|||||||
    layout: BoatsLayout,
 | 
					    layout: BoatsLayout,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CoordinatesMapper = HashMap<Coordinates, Coordinates>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn set_boat_layout<B: Backend>(
 | 
					pub fn set_boat_layout<B: Backend>(
 | 
				
			||||||
    rules: &GameRules,
 | 
					    rules: &GameRules,
 | 
				
			||||||
    terminal: &mut Terminal<B>,
 | 
					    terminal: &mut Terminal<B>,
 | 
				
			||||||
@@ -29,16 +32,19 @@ pub fn set_boat_layout<B: Backend>(
 | 
				
			|||||||
            .expect("Failed to generate initial boats layout"),
 | 
					            .expect("Failed to generate initial boats layout"),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut coordinates_mapper = CoordinatesMapper::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut last_tick = Instant::now();
 | 
					    let mut last_tick = Instant::now();
 | 
				
			||||||
    loop {
 | 
					    loop {
 | 
				
			||||||
        terminal.draw(|f| ui(f, &mut model, rules))?;
 | 
					        terminal.draw(|f| coordinates_mapper = ui(f, &mut model, rules))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let timeout = TICK_RATE
 | 
					        let timeout = TICK_RATE
 | 
				
			||||||
            .checked_sub(last_tick.elapsed())
 | 
					            .checked_sub(last_tick.elapsed())
 | 
				
			||||||
            .unwrap_or_else(|| Duration::from_secs(0));
 | 
					            .unwrap_or_else(|| Duration::from_secs(0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if event::poll(timeout)? {
 | 
					        if event::poll(timeout)? {
 | 
				
			||||||
            if let Event::Key(key) = event::read()? {
 | 
					            let event = event::read()?;
 | 
				
			||||||
 | 
					            if let Event::Key(key) = &event {
 | 
				
			||||||
                match key.code {
 | 
					                match key.code {
 | 
				
			||||||
                    KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
 | 
					                    KeyCode::Char('q') => return Ok(ScreenResult::Canceled),
 | 
				
			||||||
                    KeyCode::Enter => {
 | 
					                    KeyCode::Enter => {
 | 
				
			||||||
@@ -51,6 +57,22 @@ pub fn set_boat_layout<B: Backend>(
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                model.curr_boat %= model.layout.number_of_boats();
 | 
					                model.curr_boat %= model.layout.number_of_boats();
 | 
				
			||||||
 | 
					            } else if let Event::Mouse(mouse) = event {
 | 
				
			||||||
 | 
					                if MouseEventKind::Up(MouseButton::Left) == mouse.kind {
 | 
				
			||||||
 | 
					                    let src_pos = Coordinates::new(mouse.column, mouse.row);
 | 
				
			||||||
 | 
					                    if let Some(pos) = coordinates_mapper.get(&src_pos) {
 | 
				
			||||||
 | 
					                        match model.layout.find_boat_at_position(*pos).cloned() {
 | 
				
			||||||
 | 
					                            // Change of selected boat
 | 
				
			||||||
 | 
					                            Some(b) if b != model.layout.0[model.curr_boat] => {
 | 
				
			||||||
 | 
					                                model.curr_boat =
 | 
				
			||||||
 | 
					                                    model.layout.0.iter().position(|s| s == &b).unwrap();
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            // Move current boat
 | 
				
			||||||
 | 
					                            _ => model.layout.0[model.curr_boat].start = *pos,
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if last_tick.elapsed() >= TICK_RATE {
 | 
					        if last_tick.elapsed() >= TICK_RATE {
 | 
				
			||||||
@@ -59,7 +81,11 @@ pub fn set_boat_layout<B: Backend>(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn ui<B: Backend>(f: &mut Frame<B>, model: &mut SetBotsLayoutScreen, rules: &GameRules) {
 | 
					fn ui<B: Backend>(
 | 
				
			||||||
 | 
					    f: &mut Frame<B>,
 | 
				
			||||||
 | 
					    model: &mut SetBotsLayoutScreen,
 | 
				
			||||||
 | 
					    rules: &GameRules,
 | 
				
			||||||
 | 
					) -> CoordinatesMapper {
 | 
				
			||||||
    let current_boat = ColoredCells {
 | 
					    let current_boat = ColoredCells {
 | 
				
			||||||
        color: Color::Green,
 | 
					        color: Color::Green,
 | 
				
			||||||
        cells: model.layout.0[model.curr_boat].all_coordinates(),
 | 
					        cells: model.layout.0[model.curr_boat].all_coordinates(),
 | 
				
			||||||
@@ -77,11 +103,20 @@ fn ui<B: Backend>(f: &mut Frame<B>, model: &mut SetBotsLayoutScreen, rules: &Gam
 | 
				
			|||||||
        cells: other_boats_cells,
 | 
					        cells: other_boats_cells,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut coordinates_mapper = HashMap::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let game_map_widget = GameMapWidget::new(rules)
 | 
					    let game_map_widget = GameMapWidget::new(rules)
 | 
				
			||||||
        .set_default_empty_char(' ')
 | 
					        .set_default_empty_char(' ')
 | 
				
			||||||
        .add_colored_cells(current_boat)
 | 
					        .add_colored_cells(current_boat)
 | 
				
			||||||
        .add_colored_cells(other_boats)
 | 
					        .add_colored_cells(other_boats)
 | 
				
			||||||
        .set_title("Choose your boat layout")
 | 
					        .set_title("Choose your boat layout")
 | 
				
			||||||
 | 
					        .set_yield_func(|c, r| {
 | 
				
			||||||
 | 
					            for i in 0..r.width {
 | 
				
			||||||
 | 
					                for j in 0..r.height {
 | 
				
			||||||
 | 
					                    coordinates_mapper.insert(Coordinates::new(r.x + i, r.y + j), c);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
        .set_legend(
 | 
					        .set_legend(
 | 
				
			||||||
            "n        next boat     \n\
 | 
					            "n        next boat     \n\
 | 
				
			||||||
                     r        rotate boat    \n\n\
 | 
					                     r        rotate boat    \n\n\
 | 
				
			||||||
@@ -92,4 +127,6 @@ fn ui<B: Backend>(f: &mut Frame<B>, model: &mut SetBotsLayoutScreen, rules: &Gam
 | 
				
			|||||||
    let (w, h) = game_map_widget.estimated_size();
 | 
					    let (w, h) = game_map_widget.estimated_size();
 | 
				
			||||||
    let area = centered_rect_size(w, h, &f.size());
 | 
					    let area = centered_rect_size(w, h, &f.size());
 | 
				
			||||||
    f.render_widget(game_map_widget, area);
 | 
					    f.render_widget(game_map_widget, area);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    coordinates_mapper
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ pub struct GameMapWidget<'a> {
 | 
				
			|||||||
    colored_cells: Vec<ColoredCells>,
 | 
					    colored_cells: Vec<ColoredCells>,
 | 
				
			||||||
    title: Option<String>,
 | 
					    title: Option<String>,
 | 
				
			||||||
    legend: Option<String>,
 | 
					    legend: Option<String>,
 | 
				
			||||||
 | 
					    yield_coordinates: Option<Box<dyn 'a + FnMut(Coordinates, Rect)>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<'a> GameMapWidget<'a> {
 | 
					impl<'a> GameMapWidget<'a> {
 | 
				
			||||||
@@ -30,6 +31,7 @@ impl<'a> GameMapWidget<'a> {
 | 
				
			|||||||
            colored_cells: vec![],
 | 
					            colored_cells: vec![],
 | 
				
			||||||
            title: None,
 | 
					            title: None,
 | 
				
			||||||
            legend: None,
 | 
					            legend: None,
 | 
				
			||||||
 | 
					            yield_coordinates: None,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,6 +55,14 @@ impl<'a> GameMapWidget<'a> {
 | 
				
			|||||||
        self
 | 
					        self
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn set_yield_func<F>(mut self, func: F) -> Self
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        F: 'a + FnMut(Coordinates, Rect),
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        self.yield_coordinates = Some(Box::new(func));
 | 
				
			||||||
 | 
					        self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn grid_size(&self) -> (u16, u16) {
 | 
					    pub fn grid_size(&self) -> (u16, u16) {
 | 
				
			||||||
        let w = self.rules.map_width as u16 * 2 + 1;
 | 
					        let w = self.rules.map_width as u16 * 2 + 1;
 | 
				
			||||||
        let h = self.rules.map_height as u16 * 2 + 1;
 | 
					        let h = self.rules.map_height as u16 * 2 + 1;
 | 
				
			||||||
@@ -76,7 +86,7 @@ impl<'a> GameMapWidget<'a> {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl<'a> Widget for GameMapWidget<'a> {
 | 
					impl<'a> Widget for GameMapWidget<'a> {
 | 
				
			||||||
    fn render(self, area: Rect, buf: &mut Buffer) {
 | 
					    fn render(mut self, area: Rect, buf: &mut Buffer) {
 | 
				
			||||||
        let alphabet = PlayConfiguration::default().ordinate_alphabet;
 | 
					        let alphabet = PlayConfiguration::default().ordinate_alphabet;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let symbols = BorderType::line_symbols(BorderType::Plain);
 | 
					        let symbols = BorderType::line_symbols(BorderType::Plain);
 | 
				
			||||||
@@ -92,12 +102,16 @@ impl<'a> Widget for GameMapWidget<'a> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Paint game grid
 | 
					        // Paint game grid
 | 
				
			||||||
        for y in 0..(self.rules.map_height + 1) {
 | 
					        for y in 0..(self.rules.map_height + 1) {
 | 
				
			||||||
 | 
					            // Header (ordinate)
 | 
				
			||||||
            if y < self.rules.map_height {
 | 
					            if y < self.rules.map_height {
 | 
				
			||||||
                buf.get_mut(area.x, start_y + 2 + (y as u16 * 2))
 | 
					                buf.get_mut(area.x, start_y + 2 + (y as u16 * 2))
 | 
				
			||||||
                    .set_char(alphabet.chars().nth(y).unwrap());
 | 
					                    .set_char(alphabet.chars().nth(y).unwrap());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for x in 0..(self.rules.map_width + 1) {
 | 
					            for x in 0..(self.rules.map_width + 1) {
 | 
				
			||||||
 | 
					                let coordinates = Coordinates::new(x as i32, y as i32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Header (abscissa)
 | 
				
			||||||
                if x < self.rules.map_width {
 | 
					                if x < self.rules.map_width {
 | 
				
			||||||
                    buf.set_string(
 | 
					                    buf.set_string(
 | 
				
			||||||
                        area.x + 2 + (x as u16 * 2) - (x as u16) / 10,
 | 
					                        area.x + 2 + (x as u16 * 2) - (x as u16) / 10,
 | 
				
			||||||
@@ -110,6 +124,11 @@ impl<'a> Widget for GameMapWidget<'a> {
 | 
				
			|||||||
                let o_x = 1 + area.x + (x as u16 * 2);
 | 
					                let o_x = 1 + area.x + (x as u16 * 2);
 | 
				
			||||||
                let o_y = 1 + start_y + (y as u16 * 2);
 | 
					                let o_y = 1 + start_y + (y as u16 * 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let color = self
 | 
				
			||||||
 | 
					                    .colored_cells
 | 
				
			||||||
 | 
					                    .iter()
 | 
				
			||||||
 | 
					                    .find(|c| c.cells.contains(&coordinates));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                buf.get_mut(o_x, o_y).set_symbol(match (x, y) {
 | 
					                buf.get_mut(o_x, o_y).set_symbol(match (x, y) {
 | 
				
			||||||
                    (0, 0) => symbols.top_left,
 | 
					                    (0, 0) => symbols.top_left,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -142,13 +161,13 @@ impl<'a> Widget for GameMapWidget<'a> {
 | 
				
			|||||||
                        .get_mut(o_x + 1, o_y + 1)
 | 
					                        .get_mut(o_x + 1, o_y + 1)
 | 
				
			||||||
                        .set_char(self.default_empty_character);
 | 
					                        .set_char(self.default_empty_character);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if let Some(c) = self
 | 
					                    if let Some(c) = color {
 | 
				
			||||||
                        .colored_cells
 | 
					 | 
				
			||||||
                        .iter()
 | 
					 | 
				
			||||||
                        .find(|c| c.cells.contains(&Coordinates::new(x as i32, y as i32)))
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        cell.set_bg(c.color);
 | 
					                        cell.set_bg(c.color);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if let Some(f) = self.yield_coordinates.as_mut() {
 | 
				
			||||||
 | 
					                        f(coordinates, Rect::new(o_x + 1, o_y + 1, 1, 1));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,12 +18,7 @@ pub enum BoatDirection {
 | 
				
			|||||||
    BottomRight,
 | 
					    BottomRight,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const _BOATS_DIRECTION: [BoatDirection; 4] = [
 | 
					const _BOATS_DIRECTION: [BoatDirection; 2] = [BoatDirection::Right, BoatDirection::Down];
 | 
				
			||||||
    BoatDirection::Left,
 | 
					 | 
				
			||||||
    BoatDirection::Right,
 | 
					 | 
				
			||||||
    BoatDirection::Up,
 | 
					 | 
				
			||||||
    BoatDirection::Down,
 | 
					 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const _ALL_DIRECTIONS: [BoatDirection; 8] = [
 | 
					const _ALL_DIRECTIONS: [BoatDirection; 8] = [
 | 
				
			||||||
    BoatDirection::Left,
 | 
					    BoatDirection::Left,
 | 
				
			||||||
@@ -54,8 +49,8 @@ impl BoatDirection {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    pub fn shift_coordinates(&self, coordinates: Coordinates, nth: usize) -> Coordinates {
 | 
					    pub fn shift_coordinates(&self, coordinates: Coordinates, nth: usize) -> Coordinates {
 | 
				
			||||||
        let shift = match self {
 | 
					        let shift = match self {
 | 
				
			||||||
            BoatDirection::Left => (1, 0),
 | 
					            BoatDirection::Left => (-1, 0),
 | 
				
			||||||
            BoatDirection::Right => (-1, 0),
 | 
					            BoatDirection::Right => (1, 0),
 | 
				
			||||||
            BoatDirection::Up => (0, -1),
 | 
					            BoatDirection::Up => (0, -1),
 | 
				
			||||||
            BoatDirection::Down => (0, 1),
 | 
					            BoatDirection::Down => (0, 1),
 | 
				
			||||||
            BoatDirection::UpLeft => (-1, -1),
 | 
					            BoatDirection::UpLeft => (-1, -1),
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user