diff options
Diffstat (limited to 'src/gui')
| -rw-r--r-- | src/gui/decimal_num_box.rs | 173 | ||||
| -rw-r--r-- | src/gui/dimension_indicator.rs | 304 | ||||
| -rw-r--r-- | src/gui/mod.rs | 18 | ||||
| -rw-r--r-- | src/gui/position_indicator.rs | 37 | ||||
| -rw-r--r-- | src/gui/tool_sidebar.rs | 91 |
5 files changed, 0 insertions, 623 deletions
diff --git a/src/gui/decimal_num_box.rs b/src/gui/decimal_num_box.rs deleted file mode 100644 index e9395f7..0000000 --- a/src/gui/decimal_num_box.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! Functions similarly to a text-bux, but only accepts floating point (decimal) numbers -//! -//! Since a lot of functions require the user to input measurements in meters, it is useful to have a -//! singular entity that reads these in an intuitive way. Inputting of such numbers is handled in -//! this module. - -use crate::math::{self, Vec2}; -use nalgebra::RealField; -use num_traits::Pow; -use raylib::drawing::RaylibDraw; -use raylib::ffi::{Color, KeyboardKey}; -use raylib::text; -use raylib::RaylibHandle; -use std::str::FromStr; - -/// The number of decimal places that can be edited and will be shown by a decimal text field. -pub const DECIMAL_PLACES: u16 = 4; - -/// The decimal num box can handle any decimal number, like f32 or f64. Currently has a hard limit -/// of four decimal places, but that may change in the future. -pub struct DecimalNumBox<F: RealField + Pow<u16, Output = F> + FromStr> { - input: String, - last_value: F, - active: bool, -} - -impl<F: RealField + Pow<u16, Output = F> + FromStr> DecimalNumBox<F> { - /// Create a new Number box showing the value specified. Should the value have more then the - /// maximum number of decimal places, it will be rounded. - pub fn new(value: F) -> Self { - let value = math::round_nth_decimal(value, DECIMAL_PLACES); - let input = format!("{:.4}", value); - - Self { - input, - last_value: value, - active: false, - } - } - - /// Get the value entered by the user. If the user has something that cannot be parsed into a - /// decimal value, this differs from the string that is shown and is instead the last value - /// entered by the user that is still a valid decimal number. - pub fn value(&self) -> F { - self.last_value - } - - /// Set the value directly. This may only be done, if the box is currently not active, to protect - /// user input. Returns true if the value could be set, otherwise false. - pub fn set_value(&mut self, value: F) -> bool { - if !self.active { - self.last_value = math::round_nth_decimal(value, DECIMAL_PLACES); - // XXX: Don't use the magical 4 - self.input = format!("{:.4}", self.last_value); - true - } else { - false - } - } - - /// Check if this number box is currently active. Active means, it's capturing keyboard input. - /// If it's not active, it does not attempt to capture any keystrokes. - pub fn active(&self) -> bool { - self.active - } - - /// Set if the box is active (capturing keyboard input and adjusting it's value accordingly) or - /// not. - pub fn set_active(&mut self, active: bool) { - self.active = active - } - - /// Update this decimal box. If it is inactive, this doesn't do anything, but if it is active, it - /// captures the keyboard input, if available. Returns `true`, if the value changed, otherwise - /// `false`. Note that the string that is displayed may change, but the value does not have to. - /// This happens, if the user types something invalid. In this case, `false` is returned as well. - pub fn update(&mut self, rl: &mut RaylibHandle) -> bool { - /* If the box is currently inactive, nothing must be changed, and this function will do - * nothing. - */ - if !self.active { - return false; - } - - // TODO: Check for movement keys. - - // Delete the last character when pressing backspace. - let string_changed = if rl.is_key_pressed(KeyboardKey::KEY_BACKSPACE) { - self.input.pop().is_some() - } - // Check the entered numbers or decimal point. - else if let Some(key) = rl.get_key_pressed() { - match key { - // Add (at most one) decimal point to the input when entering a dot. - KeyboardKey::KEY_PERIOD => { - if !self.input.contains('.') { - self.input.push('.'); - true - } else { - false - } - } - _ => { - if key as u16 >= KeyboardKey::KEY_ZERO as u16 - && key as u16 <= KeyboardKey::KEY_NINE as u16 - { - self.input.push(key as u8 as char); - true - } else { - false - } - } - } - } else { - false - }; - - if string_changed { - // Try to parse the new string. If it doesn't work, keep the old one. - match self.input.parse::<F>() { - Ok(value) => { - let value = math::round_nth_decimal(value, DECIMAL_PLACES); - if value != self.last_value { - self.last_value = value; - true - } else { - false - } - } - Err(_) => false, - } - } else { - false - } - } - - /// Draw the number box at the given position. the `unit` parameter is used to append this text, - /// let's say for instance 'm' for meters to the text drawn to screen. Most of the time, a unit - /// makes sense to show on this number box, otherwise it can be left as an empty string. The unit - /// has no relevance to internal processes and cannot be edited by the user. - pub fn draw(&self, rld: &mut impl RaylibDraw, unit: &str, pos: &Vec2<f64>) { - let text = format!("{}{}", self.input, unit); - let width = text::measure_text(&text, 20); - - // Draw background to highlight this box if it's active. - if self.active { - rld.draw_rectangle_v( - *pos - Vec2::new(5., 5.), - Vec2::new(width as f32 + 10., 20. + 10.), - Color { - r: 120, - g: 120, - b: 120, - a: 180, - }, - ); - } - - // Draw the text of the box. - rld.draw_text( - &text, - pos.x as i32, - pos.y as i32, - 20, - Color { - r: 255, - g: 255, - b: 255, - a: 255, - }, - ) - } -} diff --git a/src/gui/dimension_indicator.rs b/src/gui/dimension_indicator.rs deleted file mode 100644 index 57f5bcc..0000000 --- a/src/gui/dimension_indicator.rs +++ /dev/null @@ -1,304 +0,0 @@ -//! An interface element that shows the size of the selected map items and provides a means to -//! manually change the size of them in a precise manner should need be. - -use crate::colours::DEFAULT_COLOURS; -use crate::map::Map; -use crate::math::{self, Rect, Vec2}; -use crate::transform::Transform; -use nalgebra::{Matrix3, Vector2}; -use raylib::drawing::RaylibDraw; -use raylib::ffi::KeyboardKey; -use raylib::RaylibHandle; - -/// 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. - bounds: Rect<f64>, -} - -impl Default for State { - fn default() -> Self { - Self::Watching - } -} - -impl Default for DimensionIndicator { - fn default() -> Self { - Self { - state: State::default(), - bounds: Rect::new(0., 0., 0., 0.), - } - } -} - -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::default() - } - - /// 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<f64> = Vec2::default(); - let mut max: Vec2<f64> = 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; - } - selection_exists = true; - } - } - - // Set the current selection limits, if any. - self.bounds = 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.bounds.w.to_string(), - dim_y: self.bounds.h.to_string(), - editing_x: true, - }; - } - } - - fn update_ruling(&mut self, map: &mut Map, rl: &mut RaylibHandle) { - // Get the currently edited dimension for processing. - let (edited_dim, editing_x) = match &mut self.state { - State::Watching => panic!("Called ruler update when in watching state"), - State::Ruling { - dim_x, - dim_y, - editing_x, - } => { - if *editing_x { - (dim_x, editing_x) - } else { - (dim_y, editing_x) - } - } - }; - - // Switch the currently edited dimension when pressing tab. - if rl.is_key_pressed(KeyboardKey::KEY_TAB) { - *editing_x = !*editing_x; - return; - } - // Finish editing mode on enter. - if rl.is_key_pressed(KeyboardKey::KEY_ENTER) { - self.state = State::Watching; - return; - } - - // Marker to see if the dimensions will have to be checked for an update. - let mut dimension_changed = false; - // Delete the last character of the dimension on backspace. - if rl.is_key_pressed(KeyboardKey::KEY_BACKSPACE) { - edited_dim.pop(); - dimension_changed = true; - } - /* Capture the current key and try to add it to the string of the current dimension, - * if possible. - */ - else if let Some(key) = rl.get_key_pressed() { - match key { - // Add a decimal point to the dimension if possible. - KeyboardKey::KEY_PERIOD => { - if !edited_dim.contains('.') { - edited_dim.push('.'); - } - // Nothing changed here, since there is an implicit .0 at the end. - } - // Handle the entered key if it is a number to append it to the currently edited dimension. - _ => { - if key as u16 >= KeyboardKey::KEY_ZERO as u16 - && key as u16 <= KeyboardKey::KEY_NINE as u16 - { - edited_dim.push(key as u8 as char); - dimension_changed = true; - } - } - }; - } - - if dimension_changed { - /* Try to parse the dimension from the currently edited string. If it - * is valid, change the dimensions of the currently selected items. If - * not, ignore the change and wait for a valid dimension. - */ - if let Ok(dim) = edited_dim.parse::<f64>() { - let new_bounds = if *editing_x { - Rect::new(self.bounds.x, self.bounds.y, dim, self.bounds.h) - } else { - Rect::new(self.bounds.x, self.bounds.y, self.bounds.h, dim) - }; - - self.set_bounds(map, new_bounds); - } - } - } - - /// Set the selection boundaries to the given bounds. Tries to transform the - /// currently selected items in the map so they fit inside of the new bounding box. - /// - /// # Panics - /// If the `bounds` have a negative value for width or height, the dimensions - /// cannot be set and the function will panic. - pub fn set_bounds(&mut self, map: &mut Map, bounds: Rect<f64>) { - if bounds.w <= 0. || bounds.h <= 0. { - panic!("Cannot set dimensions of elements to zero."); - } - - // If the bounds are the same as before, there is nothing to do. - if self.bounds == bounds { - return; - } - - /* Create a matrix to transform from the current rectangle bounds into the - * new bounds. Internally, this is a three-step process. First, we - * translate the points currently in the bounding box to the origin - * (0, 0) origin vector of the map, then scale and finally move it to the - * origin of the new rectangle. This needs to be applied to all vertices - * of all elements that can be scaled. - */ - let scale = Vector2::new(bounds.w / self.bounds.w, bounds.h / self.bounds.h); - let transform = Matrix3::new_translation(&Vector2::new(-self.bounds.x, -self.bounds.y)) - .append_nonuniform_scaling(&scale) - .append_translation(&Vector2::new(bounds.x, bounds.y)); - - for element in map.elements_mut() { - if element.selected() { - if let Some(transformable) = element.as_non_rigid_mut() { - transformable.apply_matrix(&transform); - } - } - } - - self.bounds = bounds; - } - - /// Draw the dimensions detected on the current selection. - pub fn draw(&self, rld: &mut impl RaylibDraw, transform: &Transform) { - /* Ignore a selection that has no non-null dimensions, since this usually - * indicates that there is nothing to be scaled. - */ - if self.bounds.w == 0. && self.bounds.h == 0. { - return; - } - - let (dim_str_width, dim_str_height) = match &self.state { - State::Watching => (self.bounds.w.to_string(), self.bounds.h.to_string()), - State::Ruling { dim_x, dim_y, .. } => (dim_x.clone(), dim_y.clone()), - }; - - // Draw the horizontal dimension at the bottom and the vertical dimension to the right. - // Use the valid dimensions, but show the edited dimensions in the String (should they differ) - let top_right = Vec2::new(self.bounds.x + self.bounds.w, self.bounds.y); - let bot_left = Vec2::new(self.bounds.x, self.bounds.y + self.bounds.h); - let bot_right = Vec2::new(self.bounds.x + self.bounds.w, self.bounds.y + self.bounds.h); - let dimensions = [ - (bot_left, bot_right, dim_str_width), - (bot_right, top_right, dim_str_height), - ]; - for (start, end, dim_str) in &dimensions { - // 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. */ - // First the two stubs. - rld.draw_line_ex( - start_px - line_normal * 5., - start_px + line_normal * 5., - 2., - DEFAULT_COLOURS.dimension_indicators, - ); - rld.draw_line_ex( - end_px - line_normal * 5., - end_px + line_normal * 5., - 2., - DEFAULT_COLOURS.dimension_indicators, - ); - // Then the actual indicator line. - rld.draw_line_ex(start_px, end_px, 2., DEFAULT_COLOURS.dimension_indicators); - - /* 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", dim_str), - text_pos.x as i32, - text_pos.y as i32, - 20, - DEFAULT_COLOURS.dimension_text, - ); - } - } -} diff --git a/src/gui/mod.rs b/src/gui/mod.rs deleted file mode 100644 index 62173ec..0000000 --- a/src/gui/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! General graphical user interfaces -//! -//! This mod does not contain all graphical content on screen, but all user interfaces that is drawn -//! that is not contained in a different category. This means all interface elements where it does not -//! make sense to bind it to any other part of the program, for instance a tool or type of element. -//! It also does *not* contain anything that does anything that is not triggered by the user, which -//! means everything is called top-down from this module. A function in this module should not be -//! called from any point in the program except the main loop, where the user input is polled. - -pub mod decimal_num_box; -pub mod dimension_indicator; -pub mod position_indicator; -pub mod tool_sidebar; - -pub use self::decimal_num_box::*; -pub use self::dimension_indicator::*; -pub use self::position_indicator::*; -pub use self::tool_sidebar::*; diff --git a/src/gui/position_indicator.rs b/src/gui/position_indicator.rs deleted file mode 100644 index 737a427..0000000 --- a/src/gui/position_indicator.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! The position indicator shows the mouse position on the map -//! -//! The exact position the mouse is currently on is shown unless hidden by the user (TODO). This -//! helps to place things exactly where they should be on the map and let the user know where they -//! are looking and where relative to them other things should be easily at all times. Currently, this -//! is a simple HUD so it doesn't interact with anything in the world, but that may change in the -//! future. - -use crate::colours::DEFAULT_COLOURS; -use crate::math::Vec2; -use crate::snapping::Snapper; -use crate::transform::Transform; -use raylib::drawing::{RaylibDraw, RaylibDrawHandle}; - -/// Function to draw a dot at the mouse position and the coordinates associated with it. -// TODO: Snap this, when the user wants to snap, don't if they don't want to. -pub fn position_indicator_draw( - rld: &mut RaylibDrawHandle, - mouse_pos_px: Vec2<f64>, - transform: &Transform, - snapper: &Snapper, -) { - let mouse_pos_snapped_m = snapper.snap(transform.point_px_to_m(&mouse_pos_px)); - let mouse_pos_snapped_px = transform.point_m_to_px(&mouse_pos_snapped_m); - - rld.draw_circle_v(mouse_pos_snapped_px, 2., DEFAULT_COLOURS.position_indicator); - rld.draw_text( - &format!( - "({:.3}m, {:.3}m)", - mouse_pos_snapped_m.x, mouse_pos_snapped_m.y - ), - mouse_pos_snapped_px.x as i32 - 30, - mouse_pos_snapped_px.y as i32 - 30, - 20, - DEFAULT_COLOURS.position_text, - ); -} diff --git a/src/gui/tool_sidebar.rs b/src/gui/tool_sidebar.rs deleted file mode 100644 index af6af74..0000000 --- a/src/gui/tool_sidebar.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! The sidebar showing all tools available to the user. This toolbar handles changing the active tool -//! based on the mouse input and (TODO!) keyboard inputs. -// TODO: Currently, the keyboard shortcuts for tools are handled by the editor, but a lot speaks for -// them being handled by the ToolSidebar instead. - -use crate::input::Input; -use crate::math::Rect; -use crate::tool::ToolType; -use crate::Editor; -use raylib::core::texture::Texture2D; -use raylib::rgui::RaylibDrawGui; -use raylib::{RaylibHandle, RaylibThread}; -use std::mem; - -/// The file containing textures for all buttons describing the tools. -pub const BUTTON_FILE: &str = "assets/button/tool_buttons.png"; - -/// Sidebar that renders and handles input for the tool activation buttons. -pub struct ToolSidebar { - button_texture: Texture2D, - bindings_id: usize, - panel_rect: Rect<u16>, -} - -impl ToolSidebar { - /// Create a new tool sidebar. There should be only one sidebar per program instance. - pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread, input: &mut Input) -> Self { - let button_texture = rl - .load_texture(rlt, BUTTON_FILE) - .expect("Could not read file containing tool icons."); - - let panel_rect = Self::panel_rect(rl.get_screen_height() as u16); - let bindings_id = input.add_local_handler(panel_rect.clone()); - - Self { - button_texture, - bindings_id, - panel_rect, - } - } - - fn panel_rect(screen_height: u16) -> Rect<u16> { - /* The width is currently hardcoded as 104, which is - * 64 (button-size) + 20 left gap + 20 right gap - */ - Rect::new(0, 0, 104, screen_height) - } - - /// Update the state of the tool sidebar. Due to raylib limitations, this is not where the tools - /// are selected for the editor, which happens in draw. - pub fn update(&mut self, screen_height: u16, input: &mut Input) { - let new_panel_rect = Self::panel_rect(screen_height); - if new_panel_rect != self.panel_rect { - self.panel_rect = new_panel_rect; - input.set_binding_rect(self.bindings_id, self.panel_rect); - } - } - - /// Draw the tool buttons and encasing panel. Because of the way raylib works, this also handles - /// clicking on tool buttons, which may be changed in the future, should a different gui be - /// chosen. - pub fn draw(&self, rld: &mut impl RaylibDrawGui, editor: &mut Editor) { - rld.gui_panel(Rect::new( - self.panel_rect.x as f32, - self.panel_rect.y as f32, - self.panel_rect.w as f32, - self.panel_rect.h as f32, - )); - - // TODO: Update to new input system. Create buttons that integrate. - let mut active = editor.active(); - for i in 0..ToolType::NumTools as usize { - let is_current_active = active as usize == i; - if rld.gui_image_button_ex( - Rect::new(20., i as f32 * 100. + 20., 64., 64.), - None, - &self.button_texture, - Rect::new( - is_current_active as u8 as f32 * 64., - i as f32 * 64., - 64., - 64., - ), - ) { - active = unsafe { mem::transmute(i as u8) }; - } - } - - editor.set_active(active); - } -} |
