1use gizmo_core::input::Input;
7use mlua::prelude::*;
8
9pub fn register_input_api(lua: &Lua) -> Result<(), LuaError> {
11 crate::api_table::register_protected(lua, "input", |input_table| {
12
13 input_table.raw_set("_keys", lua.create_table()?)?;
15 input_table.raw_set("_just_keys", lua.create_table()?)?;
16 input_table.raw_set("_mouse_x", 0.0f32)?;
17 input_table.raw_set("_mouse_y", 0.0f32)?;
18 input_table.raw_set("_mouse_dx", 0.0f32)?;
19 input_table.raw_set("_mouse_dy", 0.0f32)?;
20 input_table.raw_set("_mouse_left", false)?;
21 input_table.raw_set("_mouse_right", false)?;
22 input_table.raw_set("_mouse_middle", false)?;
23
24 let key_map = lua.create_table()?;
31 for (name, code) in gizmo_core::input::NAMED_KEYS {
32 key_map.set(*name, *code)?;
33 }
34 input_table.raw_set("_key_map", key_map)?;
35
36 lua.load(
38 r#"
39 function input.is_pressed(key_name)
40 local code = input._key_map[string.lower(key_name)]
41 if code and input._keys[code] then
42 return true
43 end
44 return false
45 end
46
47 function input.is_just_pressed(key_name)
48 local code = input._key_map[string.lower(key_name)]
49 if code and input._just_keys[code] then
50 return true
51 end
52 return false
53 end
54
55 function input.mouse_position()
56 return { x = input._mouse_x, y = input._mouse_y }
57 end
58
59 function input.mouse_delta()
60 return { x = input._mouse_dx, y = input._mouse_dy }
61 end
62
63 function input.is_mouse_pressed(button)
64 if button == "left" then return input._mouse_left
65 elseif button == "right" then return input._mouse_right
66 elseif button == "middle" then return input._mouse_middle
67 end
68 return false
69 end
70 "#,
71 )
72 .exec()
73 })
74}
75
76#[tracing::instrument(skip_all, name = "script_input_read")]
78pub fn update_input_api(lua: &Lua, input: &Input) -> Result<(), LuaError> {
79 let input_table = crate::api_table::raw(lua, "input")?;
82
83 let keys = lua.create_table()?;
85 let just_keys = lua.create_table()?;
86
87 for code in 0..256u32 {
89 if input.is_key_pressed(code) {
90 keys.set(code, true)?;
91 }
92 if input.is_key_just_pressed(code) {
93 just_keys.set(code, true)?;
94 }
95 }
96
97 input_table.raw_set("_keys", keys)?;
98 input_table.raw_set("_just_keys", just_keys)?;
99
100 let (mx, my) = input.mouse_position();
101 input_table.raw_set("_mouse_x", mx)?;
102 input_table.raw_set("_mouse_y", my)?;
103
104 let (dx, dy) = input.mouse_delta();
105 input_table.raw_set("_mouse_dx", dx)?;
106 input_table.raw_set("_mouse_dy", dy)?;
107
108 input_table.raw_set(
109 "_mouse_left",
110 input.is_mouse_button_pressed(gizmo_core::input::mouse::LEFT),
111 )?;
112 input_table.raw_set(
113 "_mouse_right",
114 input.is_mouse_button_pressed(gizmo_core::input::mouse::RIGHT),
115 )?;
116 input_table.raw_set(
117 "_mouse_middle",
118 input.is_mouse_button_pressed(gizmo_core::input::mouse::MIDDLE),
119 )?;
120
121 Ok(())
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use gizmo_core::input::code_from_name;
128
129 fn press(lua: &Lua, held: &[&str], just: &[&str]) {
139 let table = |names: &[&str]| {
140 let t = lua.create_table().unwrap();
141 for n in names {
142 t.raw_set(code_from_name(n).expect("known key"), true).unwrap();
143 }
144 t
145 };
146 let input_table = crate::api_table::raw(lua, "input").unwrap();
147 input_table.raw_set("_keys", table(held)).unwrap();
148 input_table.raw_set("_just_keys", table(just)).unwrap();
149 }
150
151 #[test]
153 fn n_and_w_keys_do_not_collide() {
154 let lua = Lua::new();
155 register_input_api(&lua).unwrap();
156
157 press(&lua, &["w"], &[]);
158 lua.load(
159 r#"
160 assert(input.is_pressed("w") == true, "w basili olmali")
161 assert(input.is_pressed("n") == false, "n basili OLMAMALI (w ile cakisma)")
162 "#,
163 )
164 .exec()
165 .unwrap();
166
167 press(&lua, &["n"], &[]);
168 lua.load(
169 r#"
170 assert(input.is_pressed("n") == true, "n kendi keycode'unda basili olmali")
171 assert(input.is_pressed("w") == false, "w basili OLMAMALI")
172 "#,
173 )
174 .exec()
175 .unwrap();
176 }
177
178 #[test]
185 fn the_arrow_keys_are_not_swapped() {
186 let lua = Lua::new();
187 register_input_api(&lua).unwrap();
188
189 press(&lua, &["down"], &[]);
190 lua.load(
191 r#"
192 assert(input.is_pressed("down") == true, "asagi basili")
193 assert(input.is_pressed("right") == false, "sag basili DEGIL")
194 assert(input.is_pressed("up") == false and input.is_pressed("left") == false)
195 "#,
196 )
197 .exec()
198 .unwrap();
199
200 press(&lua, &["right"], &[]);
201 lua.load(
202 r#"
203 assert(input.is_pressed("right") == true, "sag basili")
204 assert(input.is_pressed("down") == false, "asagi basili DEGIL")
205 "#,
206 )
207 .exec()
208 .unwrap();
209 }
210
211 #[test]
214 fn is_just_pressed_is_independent_from_held() {
215 let lua = Lua::new();
216 register_input_api(&lua).unwrap();
217
218 press(&lua, &["space"], &[]);
219 lua.load(
220 r#"
221 assert(input.is_pressed("space") == true, "space surekli basili")
222 assert(input.is_just_pressed("space") == false, "space bu frame basilmadi")
223 "#,
224 )
225 .exec()
226 .unwrap();
227
228 press(&lua, &["space"], &["space"]);
229 lua.load(r#"assert(input.is_just_pressed("space") == true, "space bu frame basildi")"#)
230 .exec()
231 .unwrap();
232 }
233
234 #[test]
237 fn key_name_casing_unknown_and_digits() {
238 let lua = Lua::new();
239 register_input_api(&lua).unwrap();
240
241 press(&lua, &["w", "1"], &[]);
242 lua.load(
243 r#"
244 assert(input.is_pressed("W") == true, "buyuk harf W basili sayilmali")
245 assert(input.is_pressed("w") == true, "kucuk harf w basili")
246 assert(input.is_pressed("1") == true, "rakam tusu 1")
247 assert(input.is_pressed("bilinmeyen_tus") == false, "haritada olmayan ad false")
248 assert(input.is_pressed("2") == false, "basilmayan rakam false")
249 "#,
250 )
251 .exec()
252 .unwrap();
253 }
254
255 #[test]
258 fn mouse_helpers_read_snapshot() {
259 let lua = Lua::new();
260 register_input_api(&lua).unwrap();
261 let t = crate::api_table::raw(&lua, "input").unwrap();
262 for (k, v) in [("_mouse_x", 120.0f32), ("_mouse_y", 45.0), ("_mouse_dx", -3.0), ("_mouse_dy", 7.0)] {
263 t.raw_set(k, v).unwrap();
264 }
265 for (k, v) in [("_mouse_left", true), ("_mouse_right", false), ("_mouse_middle", true)] {
266 t.raw_set(k, v).unwrap();
267 }
268 lua.load(
269 r#"
270 local p = input.mouse_position()
271 assert(p.x == 120.0 and p.y == 45.0, "pozisyon")
272 local d = input.mouse_delta()
273 assert(d.x == -3.0 and d.y == 7.0, "delta")
274 assert(input.is_mouse_pressed("left") == true, "sol basılı")
275 assert(input.is_mouse_pressed("right") == false, "sağ basılı değil")
276 assert(input.is_mouse_pressed("middle") == true, "orta basılı")
277 assert(input.is_mouse_pressed("side") == false, "bilinmeyen düğme false")
278 "#,
279 )
280 .exec()
281 .unwrap();
282 }
283
284 #[test]
287 fn update_input_api_mirrors_real_input() {
288 use gizmo_core::input::{mouse, Input};
289
290 let lua = Lua::new();
291 register_input_api(&lua).unwrap();
292
293 let mut input = Input::default();
294 input.on_key_pressed(code_from_name("w").unwrap());
295 input.set_mouse_position(200.0, 100.0);
296 input.on_mouse_delta(5.0, -2.0);
297 input.on_mouse_button_pressed(mouse::RIGHT);
298
299 update_input_api(&lua, &input).unwrap();
300
301 lua.load(
302 r#"
303 assert(input.is_pressed("w") == true, "w World'den aktarılmalı")
304 assert(input.is_just_pressed("w") == true, "w bu frame basıldı")
305 local p = input.mouse_position()
306 assert(p.x == 200.0 and p.y == 100.0, "fare konumu aktarılmalı")
307 local d = input.mouse_delta()
308 assert(math.abs(d.x - 5.0) < 1e-5 and math.abs(d.y + 2.0) < 1e-5, "fare delta")
309 assert(input.is_mouse_pressed("right") == true, "sağ tık aktarılmalı")
310 assert(input.is_mouse_pressed("left") == false, "sol tık basılı değil")
311 "#,
312 )
313 .exec()
314 .unwrap();
315 }
316}