use crate::math::{self, Vec2, Rect}; use crate::transform::Transform; use raylib::drawing::RaylibDraw; use raylib::RaylibHandle; use raylib::ffi::{KeyboardKey, Color}; use crate::map::Map; use std::mem; /// A state the [DimensionIndicator] is currently in. This determines the behaviour of it and what /// inputs it might be waiting for. enum State { /// In this state, the indicator is not trying to read any keyboard input, but will instead watch /// for any changes to the dimensions from a different source, updating its display. Watching, /// In this state, the indicator will capture keyboard input and attempt to set the dimensions /// according to whatever was entered. If the dimensions cannot be set, the indicator will use /// the last valid dimensions. Ruling { dim_x: String, dim_y: String, editing_x: bool } } /// Used to render the horizontal and vertical dimensions of whatever is selected on the map and, if /// the user so desires edit them directly by entering values into it. pub struct DimensionIndicator { /// The [State] the dimension indicator is currently in. state: State, /// The last dimensions that were valid. dimensions: Rect } impl Default for State { fn default() -> Self { Self::Watching } } impl DimensionIndicator { /// Create a new dimension indicator. While it is possible to have multiple instances, this is /// not generally recommended, since they will need to be managed carefully or otherwise steal /// keystrokes from each other. pub fn new() -> Self { Self { state: State::default(), dimensions: Rect::new(0., 0., 0., 0.); } } /// Update whatever is selected on the map according to the dimension indicator rules and rulers. pub fn update(&mut self, map: &mut Map, rl: &mut RaylibHandle) { match &self.state { &State::Watching => self.update_watching(map, rl), &State::Ruling{ .. } => self.update_ruling(map, rl), }; } fn update_watching(&mut self, map: &Map, rl: &RaylibHandle) { let mut min: Vec2 = Vec2::default(); let mut max: Vec2 = Vec2::default(); /* Try to find selected items. If no items exist, the dimension indicator is set to its * default, otherwise it is adjusted to the size of the combined selection. */ let mut selection_exists = false; for e in map.elements() { if e.selected() { let element_bounds = e.bounding_rect(); if selection_exists { // Adjust the currently detected selection size. min.x = math::partial_min(min.x, element_bounds.x); min.y = math::partial_min(min.y, element_bounds.y); max.x = math::partial_max(max.x, element_bounds.x + element_bounds.w); max.y = math::partial_max(max.y, element_bounds.y + element_bounds.h); } else { // No selection size detected yet. Set now. min.x = element_bounds.x; min.y = element_bounds.y; max.x = element_bounds.x + element_bounds.w; max.y = element_bounds.y + element_bounds.h; } } } // Set the current selection limits, if any. self.dimensions = if selection_exists { Rect::bounding_rect(min, max) } else { Rect::new(0., 0., 0., 0.) }; // Check if the user wants to change into editing mode, which the user can only do if there // is a selection to begin with. if selection_exists && rl.is_key_pressed(KeyboardKey::KEY_TAB) { self.state = State::Ruling { dim_x: self.dimensions.w.to_string(), dim_y: self.dimensions.h.to_string(), editing_x: true }; } } fn update_ruling(&mut self, map: &mut Map, rl: &mut RaylibHandle) { /* Capture the current key press and interpret it, if it exists. Otherwise, there is nothing * to update. */ let key = match rl.get_key_pressed() { Some(key) => key, None => return, }; } pub fn draw(&self, rld: &mut impl RaylibDraw, transform: &Transform) { // Draw all the dimension lines. for (start, end) in &self.length_lines { // Don't draw anything if the length is zero. if start == end { continue; } /* Get the vector that is perpendicular and points right/down from the line, assuming * the lines prefer left as start over right and bottom over top. */ let line_normal = { // Start with the direction of the line vector. let dir = *start - *end; // Calculate perpendicular vec and normalise. dir.rotated_90_clockwise() / dir.length() }; // To not have the line directly in the rect, move start and end outside a bit. let start_px = transform.point_m_to_px(start) + line_normal * 10.; let end_px = transform.point_m_to_px(end) + line_normal * 10.; /* Draw the indicator line, with stubs at both ends. */ let line_colour = Color { r: 200, g: 200, b: 200, a: 255, }; // First the two stubs. rld.draw_line_ex( start_px - line_normal * 5., start_px + line_normal * 5., 2., line_colour, ); rld.draw_line_ex( end_px - line_normal * 5., end_px + line_normal * 5., 2., line_colour, ); // Then the actual indicator line. rld.draw_line_ex(start_px, end_px, 2., line_colour); /* Draw the indicator text showing how long this line is in meters. * It should be placed in the middle of the line, but not into the line directly, so it * will be moved out by the normal. */ let text_pos = transform.point_m_to_px(&((*end + *start) / 2.)) + line_normal * 20.; rld.draw_text( &format!("{}m", &(*end - *start).length()), text_pos.x as i32, text_pos.y as i32, 20, line_colour, ); } } }