Add mouse interaction
This commit is contained in:
		| @@ -1,8 +1,9 @@ | ||||
| use std::collections::HashMap; | ||||
| use std::io; | ||||
| use std::time::{Duration, Instant}; | ||||
|  | ||||
| use crossterm::event; | ||||
| use crossterm::event::{Event, KeyCode}; | ||||
| use crossterm::event::{Event, KeyCode, MouseButton, MouseEventKind}; | ||||
| use tui::backend::Backend; | ||||
| use tui::style::*; | ||||
| use tui::{Frame, Terminal}; | ||||
| @@ -19,6 +20,8 @@ struct SetBotsLayoutScreen { | ||||
|     layout: BoatsLayout, | ||||
| } | ||||
|  | ||||
| type CoordinatesMapper = HashMap<Coordinates, Coordinates>; | ||||
|  | ||||
| pub fn set_boat_layout<B: Backend>( | ||||
|     rules: &GameRules, | ||||
|     terminal: &mut Terminal<B>, | ||||
| @@ -29,16 +32,19 @@ pub fn set_boat_layout<B: Backend>( | ||||
|             .expect("Failed to generate initial boats layout"), | ||||
|     }; | ||||
|  | ||||
|     let mut coordinates_mapper = CoordinatesMapper::default(); | ||||
|  | ||||
|     let mut last_tick = Instant::now(); | ||||
|     loop { | ||||
|         terminal.draw(|f| ui(f, &mut model, rules))?; | ||||
|         terminal.draw(|f| coordinates_mapper = ui(f, &mut model, rules))?; | ||||
|  | ||||
|         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()? { | ||||
|             let event = event::read()?; | ||||
|             if let Event::Key(key) = &event { | ||||
|                 match key.code { | ||||
|                     KeyCode::Char('q') => return Ok(ScreenResult::Canceled), | ||||
|                     KeyCode::Enter => { | ||||
| @@ -51,6 +57,22 @@ pub fn set_boat_layout<B: Backend>( | ||||
|                 } | ||||
|  | ||||
|                 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 { | ||||
| @@ -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 { | ||||
|         color: Color::Green, | ||||
|         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, | ||||
|     }; | ||||
|  | ||||
|     let mut coordinates_mapper = HashMap::new(); | ||||
|  | ||||
|     let game_map_widget = GameMapWidget::new(rules) | ||||
|         .set_default_empty_char(' ') | ||||
|         .add_colored_cells(current_boat) | ||||
|         .add_colored_cells(other_boats) | ||||
|         .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( | ||||
|             "n        next boat     \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 area = centered_rect_size(w, h, &f.size()); | ||||
|     f.render_widget(game_map_widget, area); | ||||
|  | ||||
|     coordinates_mapper | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,7 @@ pub struct GameMapWidget<'a> { | ||||
|     colored_cells: Vec<ColoredCells>, | ||||
|     title: Option<String>, | ||||
|     legend: Option<String>, | ||||
|     yield_coordinates: Option<Box<dyn 'a + FnMut(Coordinates, Rect)>>, | ||||
| } | ||||
|  | ||||
| impl<'a> GameMapWidget<'a> { | ||||
| @@ -30,6 +31,7 @@ impl<'a> GameMapWidget<'a> { | ||||
|             colored_cells: vec![], | ||||
|             title: None, | ||||
|             legend: None, | ||||
|             yield_coordinates: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -53,6 +55,14 @@ impl<'a> GameMapWidget<'a> { | ||||
|         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) { | ||||
|         let w = self.rules.map_width 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> { | ||||
|     fn render(self, area: Rect, buf: &mut Buffer) { | ||||
|     fn render(mut self, area: Rect, buf: &mut Buffer) { | ||||
|         let alphabet = PlayConfiguration::default().ordinate_alphabet; | ||||
|  | ||||
|         let symbols = BorderType::line_symbols(BorderType::Plain); | ||||
| @@ -92,12 +102,16 @@ impl<'a> Widget for GameMapWidget<'a> { | ||||
|  | ||||
|         // Paint game grid | ||||
|         for y in 0..(self.rules.map_height + 1) { | ||||
|             // Header (ordinate) | ||||
|             if y < self.rules.map_height { | ||||
|                 buf.get_mut(area.x, start_y + 2 + (y as u16 * 2)) | ||||
|                     .set_char(alphabet.chars().nth(y).unwrap()); | ||||
|             } | ||||
|  | ||||
|             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 { | ||||
|                     buf.set_string( | ||||
|                         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_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) { | ||||
|                     (0, 0) => symbols.top_left, | ||||
|  | ||||
| @@ -142,13 +161,13 @@ impl<'a> Widget for GameMapWidget<'a> { | ||||
|                         .get_mut(o_x + 1, o_y + 1) | ||||
|                         .set_char(self.default_empty_character); | ||||
|  | ||||
|                     if let Some(c) = self | ||||
|                         .colored_cells | ||||
|                         .iter() | ||||
|                         .find(|c| c.cells.contains(&Coordinates::new(x as i32, y as i32))) | ||||
|                     { | ||||
|                     if let Some(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, | ||||
| } | ||||
|  | ||||
| const _BOATS_DIRECTION: [BoatDirection; 4] = [ | ||||
|     BoatDirection::Left, | ||||
|     BoatDirection::Right, | ||||
|     BoatDirection::Up, | ||||
|     BoatDirection::Down, | ||||
| ]; | ||||
| const _BOATS_DIRECTION: [BoatDirection; 2] = [BoatDirection::Right, BoatDirection::Down]; | ||||
|  | ||||
| const _ALL_DIRECTIONS: [BoatDirection; 8] = [ | ||||
|     BoatDirection::Left, | ||||
| @@ -54,8 +49,8 @@ impl BoatDirection { | ||||
|  | ||||
|     pub fn shift_coordinates(&self, coordinates: Coordinates, nth: usize) -> Coordinates { | ||||
|         let shift = match self { | ||||
|             BoatDirection::Left => (1, 0), | ||||
|             BoatDirection::Right => (-1, 0), | ||||
|             BoatDirection::Left => (-1, 0), | ||||
|             BoatDirection::Right => (1, 0), | ||||
|             BoatDirection::Up => (0, -1), | ||||
|             BoatDirection::Down => (0, 1), | ||||
|             BoatDirection::UpLeft => (-1, -1), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user