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
174
175
176
177
|
use crate::math::{self, Vec2, Rect};
use crate::transform::Transform;
use raylib::drawing::RaylibDraw;
use raylib::RaylibHandle;
use raylib::ffi::{KeyboardKey, Color};
use crate::map::Map;
use std::mem;
/// 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.
dimensions: Rect<f64>
}
impl Default for State {
fn default() -> Self {
Self::Watching
}
}
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 {
state: State::default(),
dimensions: Rect::new(0., 0., 0., 0.);
}
}
/// 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;
}
}
}
// Set the current selection limits, if any.
self.dimensions = 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.dimensions.w.to_string(),
dim_y: self.dimensions.h.to_string(),
editing_x: true
};
}
}
fn update_ruling(&mut self, map: &mut Map, rl: &mut RaylibHandle) {
/* Capture the current key press and interpret it, if it exists. Otherwise, there is nothing
* to update.
*/
let key = match rl.get_key_pressed() {
Some(key) => key,
None => return,
};
}
pub fn draw(&self, rld: &mut impl RaylibDraw, transform: &Transform) {
// Draw all the dimension lines.
for (start, end) in &self.length_lines {
// 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. */
let line_colour = Color {
r: 200,
g: 200,
b: 200,
a: 255,
};
// First the two stubs.
rld.draw_line_ex(
start_px - line_normal * 5.,
start_px + line_normal * 5.,
2.,
line_colour,
);
rld.draw_line_ex(
end_px - line_normal * 5.,
end_px + line_normal * 5.,
2.,
line_colour,
);
// Then the actual indicator line.
rld.draw_line_ex(start_px, end_px, 2., line_colour);
/* 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", &(*end - *start).length()),
text_pos.x as i32,
text_pos.y as i32,
20,
line_colour,
);
}
}
}
|