//! 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 + FromStr> { input: String, last_value: F, active: bool, } impl + FromStr> DecimalNumBox { /// 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::() { 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) { 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, }, ) } }