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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
use crate::button::Button;
use crate::config::{IconToolKeybindings, ToolKeybindings};
use crate::grid::{snap_to_grid, SNAP_SIZE};
use crate::map_data::MapData;
use crate::math::Vec2;
use crate::tool::Tool;
use crate::transform::Transform;
use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle};
use raylib::core::texture::Texture2D;
use raylib::ffi::Color;
use raylib::{RaylibHandle, RaylibThread};
use ron::de::from_reader;
use serde::{Deserialize, Serialize};
use std::fs::{self, File};
pub const ICON_DIR: &str = "assets/icons";
#[derive(Deserialize)]
struct IconFileInfo {
/// The position the icon should be anchored in pixels. This is the Vector it will be moved by
/// relative to the mouse pointer (to the left and up).
anchor: Vec2<f32>,
/// The scale of the icon as expressed in image pixels per real meter.
pixels_per_m: f32,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct IconInfo {
/// The id of the icon is the icons position in the currently loaded icon_data vector.
pub icon_id: usize,
/// The position of the icon on the map, given by the vector in meters.
pub position: Vec2<f32>,
/// Rotation of the icon texture in degrees.
pub rotation: f32,
}
pub struct IconTool {
// TODO: support svg
keybindings: IconToolKeybindings,
/// The icon data, containing the image texture and the info for that image texture like the
/// scale the image actually has.
icon_data: Vec<(Texture2D, IconFileInfo)>,
/// Saves whether the IconTool is the currently active tool or not.
active: bool,
/// The information of the icon that should be placed / is currently being placed, if it
/// exists.
current_icon: IconInfo,
}
impl Default for IconInfo {
fn default() -> Self {
Self {
icon_id: 0,
position: Vec2::new(0., 0.),
rotation: 0.,
}
}
}
impl IconTool {
pub fn new(
rl: &mut RaylibHandle,
rlt: &RaylibThread,
keybindings: IconToolKeybindings,
) -> Self {
/* Read all available icons from the icon directory. SVGs do not need any special scale
* file, but pixel-based file formats require a RON-file declaring what the scale of the
* picture is right beside them.
*/
let mut image_files = Vec::new();
for entry in fs::read_dir(ICON_DIR).expect("Could not open icon directory") {
let entry = entry.expect("Failed to read file from icon directory");
// Ignore the RON-files for now and put the image files into the vec
if entry
.path()
.extension()
.expect("Entry does not have a file extension")
!= "ron"
{
image_files.push(entry);
}
}
// Read the RON-files where it is necessary.
let mut icon_data = Vec::with_capacity(image_files.len());
for file in image_files {
// TODO: Handle svg
let texture = rl
.load_texture(
rlt,
file.path()
.to_str()
.expect("Unable to convert path to string."),
)
.expect("Could not read image file");
let mut file = file.path();
file.set_extension("ron");
let ron = File::open(file).expect("Could not read ron file for icon information.");
let icon_info: IconFileInfo =
from_reader(ron).expect("Could not parse icon info from reader.");
icon_data.push((texture, icon_info));
}
Self {
keybindings,
icon_data,
active: false,
current_icon: IconInfo::default(),
}
}
}
impl Tool for IconTool {
fn activate(&mut self) {
self.active = true;
}
fn deactivate(&mut self) {
self.active = false;
}
fn active_update(&mut self, map: &mut MapData, rl: &RaylibHandle, transform: &Transform) {
// Update the position of the icon that should be drawn to the current mouse position.
let snapped_mouse_pos_m = snap_to_grid(
transform.point_px_to_m(rl.get_mouse_position().into()),
SNAP_SIZE,
);
self.current_icon.position = snapped_mouse_pos_m;
// Unwrap the current icon, since it is now definitely set, as we are in the active update.
if self.keybindings.next.is_pressed(rl) {
self.current_icon.icon_id = (self.current_icon.icon_id + 1) % self.icon_data.len();
}
if self.keybindings.rotate_clockwise.is_pressed(rl) {
self.current_icon.rotation += 45.;
}
// Handle placing the icon on the map
if self.keybindings.place.is_pressed(rl) {
map.icons_mut().push(self.current_icon.clone());
}
}
fn draw(&self, map: &MapData, rld: &mut RaylibDrawHandle, transform: &Transform) {
// Draw all icons that have been placed on the map.
for icon in map.icons() {
let (texture, info) = &self.icon_data[icon.icon_id];
// Round the position to whole pixels to fix rotation problems.
let mut position_px =
transform.point_m_to_px(icon.position - (info.anchor / info.pixels_per_m));
position_px.x = position_px.x as i32 as f32;
position_px.y = position_px.y as i32 as f32;
rld.draw_texture_ex(
texture,
position_px,
icon.rotation,
transform.pixels_per_m() / info.pixels_per_m,
Color {
r: 255,
g: 255,
b: 255,
a: 255,
},
);
}
// Draw the icon that would be placed
if self.active {
let (texture, info) = &self.icon_data[self.current_icon.icon_id];
// Round the position to whole pixels to fix rotation problems.
let mut position_px = transform
.point_m_to_px(self.current_icon.position - (info.anchor / info.pixels_per_m));
position_px.x = position_px.x as i32 as f32;
position_px.y = position_px.y as i32 as f32;
rld.draw_texture_ex(
texture,
position_px,
self.current_icon.rotation,
transform.pixels_per_m() / info.pixels_per_m,
Color {
r: 120,
g: 200,
b: 120,
a: 255,
},
);
}
}
fn activation_key(&self) -> Button {
self.keybindings.activation_key()
}
}
|