diff options
Diffstat (limited to 'src/client/gui/decimal_num_box.rs')
| -rw-r--r-- | src/client/gui/decimal_num_box.rs | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/src/client/gui/decimal_num_box.rs b/src/client/gui/decimal_num_box.rs new file mode 100644 index 0000000..e9395f7 --- /dev/null +++ b/src/client/gui/decimal_num_box.rs @@ -0,0 +1,173 @@ +//! 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, + }, + ) + } +} |
