1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
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,
},
)
}
}
|