aboutsummaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/decimal_num_box.rs173
-rw-r--r--src/gui/dimension_indicator.rs304
-rw-r--r--src/gui/mod.rs18
-rw-r--r--src/gui/position_indicator.rs37
-rw-r--r--src/gui/tool_sidebar.rs91
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);
- }
-}