1use core::fmt::Display;
8
9use mirage_engine::prelude::*;
10
11const READING_MARGIN: f32 = 4.0;
14
15const VALUE_COLUMN_WIDTH: f32 = 70.0;
19
20#[derive(InputButtonAction, Clone, Copy, PartialEq)]
23enum ButtonAction {
24 South,
25 East,
26 West,
27 North,
28 LeftBumper,
29 RightBumper,
30 LeftTrigger,
31 RightTrigger,
32 Select,
33 Start,
34 Guide,
35 LeftStickPress,
36 RightStickPress,
37 DPadUp,
38 DPadDown,
39 DPadLeft,
40 DPadRight,
41 MouseLeft,
42 MouseRight,
43 MouseMiddle,
44}
45
46impl InputButtonAction for ButtonAction {
47 fn bindings(&self) -> Vec<ButtonBinding> {
48 match self {
49 Self::South => vec![Pad::South.into(), Key::Space.into()],
50 Self::East => vec![Pad::East.into(), Key::Escape.into()],
51 Self::West => vec![Pad::West.into(), Key::X.into()],
52 Self::North => vec![Pad::North.into(), Key::N.into()],
53 Self::LeftBumper => vec![Pad::LeftBumper.into(), Key::Q.into()],
54 Self::RightBumper => vec![Pad::RightBumper.into(), Key::E.into()],
55 Self::LeftTrigger => vec![Pad::LeftTrigger.into(), Key::BracketLeft.into()],
56 Self::RightTrigger => vec![Pad::RightTrigger.into(), Key::BracketRight.into()],
57 Self::Select => vec![Pad::Select.into(), Key::Tab.into()],
58 Self::Start => vec![Pad::Start.into(), Key::Enter.into()],
59 Self::Guide => vec![Pad::Guide.into(), Key::Backspace.into()],
60 Self::LeftStickPress => vec![Pad::LeftStick.into(), Key::Digit1.into()],
61 Self::RightStickPress => vec![Pad::RightStick.into(), Key::Digit2.into()],
62 Self::DPadUp => vec![Pad::DPadUp.into(), Key::T.into()],
63 Self::DPadDown => vec![Pad::DPadDown.into(), Key::G.into()],
64 Self::DPadLeft => vec![Pad::DPadLeft.into(), Key::F.into()],
65 Self::DPadRight => vec![Pad::DPadRight.into(), Key::H.into()],
66 Self::MouseLeft => vec![MouseButton::Left.into()],
67 Self::MouseRight => vec![MouseButton::Right.into()],
68 Self::MouseMiddle => vec![MouseButton::Middle.into()],
69 }
70 }
71}
72
73#[derive(InputAxisAction, Clone, Copy, PartialEq)]
76enum AxisAction {
77 LeftX,
78 LeftY,
79 RightX,
80 RightY,
81 LeftTrigger,
82 RightTrigger,
83 PointerSideways,
84 PointerUp,
85 WheelUp,
86 WheelSideways,
87}
88
89impl InputAxisAction for AxisAction {
90 fn bindings(&self) -> Vec<AxisBinding> {
91 match self {
92 Self::LeftX => vec![
93 AxisBinding::pad(PadAxis::LeftX),
94 ButtonAxis {
95 negative: Key::A,
96 positive: Key::D,
97 }
98 .into(),
99 ],
100 Self::LeftY => vec![
101 AxisBinding::pad(PadAxis::LeftY),
102 ButtonAxis {
103 negative: Key::S,
104 positive: Key::W,
105 }
106 .into(),
107 ],
108 Self::RightX => vec![
109 AxisBinding::pad(PadAxis::RightX),
110 ButtonAxis {
111 negative: Key::J,
112 positive: Key::L,
113 }
114 .into(),
115 ],
116 Self::RightY => vec![
117 AxisBinding::pad(PadAxis::RightY),
118 ButtonAxis {
119 negative: Key::K,
120 positive: Key::I,
121 }
122 .into(),
123 ],
124 Self::LeftTrigger => vec![
125 AxisBinding::pad(PadAxis::LeftTrigger),
126 ButtonAxis {
127 negative: Key::Minus,
128 positive: Key::BracketLeft,
129 }
130 .into(),
131 ],
132 Self::RightTrigger => vec![
133 AxisBinding::pad(PadAxis::RightTrigger),
134 ButtonAxis {
135 negative: Key::Equal,
136 positive: Key::BracketRight,
137 }
138 .into(),
139 ],
140 Self::PointerSideways => vec![AxisBinding::pointer_delta(PointerDelta::Sideways)],
141 Self::PointerUp => vec![AxisBinding::pointer_delta(PointerDelta::Up)],
142 Self::WheelUp => vec![AxisBinding::wheel(WheelDelta::Up)],
143 Self::WheelSideways => vec![AxisBinding::wheel(WheelDelta::Sideways)],
144 }
145 }
146}
147
148#[derive(InputAxis2Action, Clone, Copy, PartialEq)]
151enum Axis2Action {
152 LeftStick,
153 RightStick,
154 Pointer,
155 Wheel,
156}
157
158impl InputAxis2Action for Axis2Action {
159 fn bindings(&self) -> Vec<Axis2Binding> {
160 match self {
161 Self::LeftStick => vec![
162 Axis2Binding::stick(Stick::Left),
163 ButtonAxis2 {
164 left: Key::A,
165 right: Key::D,
166 down: Key::S,
167 up: Key::W,
168 }
169 .into(),
170 ],
171 Self::RightStick => vec![
172 Axis2Binding::stick(Stick::Right),
173 ButtonAxis2 {
174 left: Key::J,
175 right: Key::L,
176 down: Key::K,
177 up: Key::I,
178 }
179 .into(),
180 ],
181 Self::Pointer => vec![Axis2Binding::pointer()],
182 Self::Wheel => vec![Axis2Binding::wheel()],
183 }
184 }
185}
186
187struct Controls;
188
189impl InputActions for Controls {
190 type Button = ButtonAction;
191 type Axis = AxisAction;
192 type Axis2 = Axis2Action;
193}
194
195#[derive(Clone, Copy, PartialEq)]
197enum Control {
198 Button(ButtonAction),
199 Axis(AxisAction),
200 Axis2(Axis2Action),
201}
202
203struct InputLab {
207 listening: Option<Control>,
209 last_button: Option<ButtonBinding>,
211 last_axis: Option<AxisBinding>,
213 last_axis2: Option<Axis2Binding>,
215}
216
217impl InputLab {
218 fn init(_ctx: &mut InitContext<'_, Self>) -> Result<Self, Error> {
219 Ok(Self {
220 listening: None,
221 last_button: None,
222 last_axis: None,
223 last_axis2: None,
224 })
225 }
226}
227
228impl Game for InputLab {
229 type Meshes = NoMeshes;
230 type Sounds = NoSounds;
231 type InputActions = Controls;
232 type SurfaceStyles = NoSurfaceStyles;
233 type Skyboxes = NoSkyboxes;
234 type PostEffects = NoPostEffects;
235
236 fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
237
238 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
239 let buttons: Vec<_> = ButtonAction::all()
242 .into_iter()
243 .map(|action| {
244 (
245 action,
246 bindings_text(ctx.bindings(action)),
247 ctx.down(action),
248 ctx.pressed(action),
249 ctx.released(action),
250 ctx.clicks(action),
251 )
252 })
253 .collect();
254 let axes: Vec<_> = AxisAction::all()
255 .into_iter()
256 .map(|action| {
257 (
258 action,
259 bindings_text(ctx.bindings(action)),
260 ctx.axis(action),
261 )
262 })
263 .collect();
264 let axes2: Vec<_> = Axis2Action::all()
265 .into_iter()
266 .map(|action| {
267 (
268 action,
269 bindings_text(ctx.bindings(action)),
270 ctx.axis2(action),
271 )
272 })
273 .collect();
274
275 let capturing = !ctx.ui_wants_keyboard();
276 let actuated_button = capturing.then(|| ctx.actuated_button()).flatten();
277 let actuated_axis = capturing.then(|| ctx.actuated_axis()).flatten();
278 let actuated_axis2 = capturing.then(|| ctx.actuated_axis2()).flatten();
279 if actuated_button.is_some() {
280 self.last_button = actuated_button;
281 }
282 if actuated_axis.is_some() {
283 self.last_axis = actuated_axis;
284 }
285 if actuated_axis2.is_some() {
286 self.last_axis2 = actuated_axis2;
287 }
288 let pointer = ctx.pointer();
289 let mut edits = RowEdits {
290 listening: self.listening,
291 start_listening: None,
292 cancel: false,
293 reset: None,
294 };
295
296 ctx.ui(|ui| {
297 egui::CentralPanel::default().show(ui, |ui| {
298 ui.spacing_mut().item_spacing = egui::vec2(6.0, 2.0);
299 ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
300 ui.label("rebinds persist across runs");
301 ui.label(format!(
302 "last captured: button {}, pad axis {}, pad stick {}",
303 text_of(self.last_button),
304 text_of(self.last_axis),
305 text_of(self.last_axis2),
306 ));
307 ui.label(format!("pointer {:.0}, {:.0}", pointer.x, pointer.y));
308 ui.separator();
309
310 ui.horizontal(|ui| {
311 ui.vertical(|ui| {
312 ui.heading("buttons");
313 egui::Grid::new("buttons-grid")
314 .num_columns(5)
315 .spacing([6.0, 2.0])
316 .show(ui, |ui| {
317 for (action, bindings, down, pressed, released, clicks) in &buttons
318 {
319 let control = Control::Button(*action);
320 ui.label(action.name());
321 ui.label(bindings);
322 ui.horizontal(|ui| {
323 mark(ui, "down", *down);
324 mark(ui, "pressed", *pressed);
325 mark(ui, "released", *released);
326 ui.label(format!("clicks {clicks}"));
327 });
328 rebind_cell(ui, control, &mut edits);
329 reset_cell(ui, control, &mut edits);
330 ui.end_row();
331 }
332 });
333 });
334
335 ui.separator();
336
337 ui.vertical(|ui| {
338 egui::Grid::new("axes-grid")
339 .num_columns(5)
340 .spacing([6.0, 2.0])
341 .show(ui, |ui| {
342 ui.heading("axes");
343 ui.end_row();
344 for (action, bindings, value) in &axes {
345 let control = Control::Axis(*action);
346 ui.label(action.name());
347 ui.label(bindings);
348 axis_bar(ui, *value);
349 rebind_cell(ui, control, &mut edits);
350 reset_cell(ui, control, &mut edits);
351 ui.end_row();
352 }
353
354 ui.heading("vectors");
355 ui.end_row();
356 for (action, bindings, value) in &axes2 {
357 let control = Control::Axis2(*action);
358 ui.label(action.name());
359 ui.label(bindings);
360 axis2_dot(ui, *value);
361 rebind_cell(ui, control, &mut edits);
362 reset_cell(ui, control, &mut edits);
363 ui.end_row();
364 }
365 });
366 });
367 });
368 });
369 });
370
371 if edits.cancel {
372 self.listening = None;
373 }
374 if let Some(control) = edits.start_listening {
375 self.listening = Some(control);
376 }
377 if let Some(control) = edits.reset {
378 match control {
379 Control::Button(action) => ctx.rebind(action, action.bindings()),
380 Control::Axis(action) => ctx.rebind(action, action.bindings()),
381 Control::Axis2(action) => ctx.rebind(action, action.bindings()),
382 }
383 }
384 match (
385 self.listening,
386 actuated_button,
387 actuated_axis,
388 actuated_axis2,
389 ) {
390 (Some(Control::Button(action)), Some(binding), _, _) => {
391 ctx.rebind(action, vec![binding]);
392 self.listening = None;
393 }
394 (Some(Control::Axis(action)), _, Some(binding), _) => {
395 ctx.rebind(action, vec![binding]);
396 self.listening = None;
397 }
398 (Some(Control::Axis2(action)), _, _, Some(binding)) => {
399 ctx.rebind(action, vec![binding]);
400 self.listening = None;
401 }
402 _ => {}
403 }
404 }
405}
406
407struct RowEdits {
411 listening: Option<Control>,
412 start_listening: Option<Control>,
413 cancel: bool,
414 reset: Option<Control>,
415}
416
417fn rebind_cell(ui: &mut egui::Ui, control: Control, edits: &mut RowEdits) {
419 if edits.listening == Some(control) {
420 ui.horizontal(|ui| {
421 ui.label("listening");
422 if ui.button("cancel").clicked() {
423 edits.cancel = true;
424 }
425 });
426 } else if ui.button("rebind").clicked() {
427 edits.start_listening = Some(control);
428 }
429}
430
431fn reset_cell(ui: &mut egui::Ui, control: Control, edits: &mut RowEdits) {
433 if ui.button("reset").clicked() {
434 edits.reset = Some(control);
435 }
436}
437
438fn mark(ui: &mut egui::Ui, label: &str, active: bool) {
441 ui.colored_label(highlight_color(active), label);
442}
443
444fn highlight_color(active: bool) -> egui::Color32 {
447 match active {
448 true => egui::Color32::from_rgb(90, 200, 120),
449 false => egui::Color32::from_gray(90),
450 }
451}
452
453fn axis_bar(ui: &mut egui::Ui, value: f32) {
456 let size = egui::vec2(VALUE_COLUMN_WIDTH, 12.0);
457 let (rect, _response) = ui.allocate_exact_size(size, egui::Sense::hover());
458 let painter = ui.painter();
459 painter.rect_filled(rect, 2.0, egui::Color32::from_gray(35));
460
461 let mid = rect.left() + rect.width() * 0.5;
462 let reach = rect.width() * 0.5 - READING_MARGIN;
463 let end = mid + value.clamp(-1.0, 1.0) * reach;
464 let fill = egui::Rect::from_min_max(
465 egui::pos2(mid.min(end), rect.top()),
466 egui::pos2(mid.max(end), rect.bottom()),
467 );
468 painter.rect_filled(fill, 2.0, highlight_color(value != 0.0));
469 painter.vline(
470 mid,
471 egui::Rangef::new(rect.top() + 3.0, rect.bottom() - 3.0),
472 egui::Stroke::new(1.0, egui::Color32::from_gray(160)),
473 );
474 painter.rect_stroke(
475 rect,
476 2.0,
477 egui::Stroke::new(1.0, egui::Color32::from_gray(120)),
478 egui::StrokeKind::Inside,
479 );
480
481 ui.label(format!("{value:.2}"));
482}
483
484fn axis2_dot(ui: &mut egui::Ui, value: Vec2) {
487 let square_side = 36.0;
488 let radius = 4.0;
489 let (column, _response) = ui.allocate_exact_size(
490 egui::vec2(VALUE_COLUMN_WIDTH, square_side),
491 egui::Sense::hover(),
492 );
493 let rect = egui::Rect::from_center_size(column.center(), egui::Vec2::splat(square_side));
494 let painter = ui.painter();
495 painter.rect_filled(rect, 2.0, egui::Color32::from_gray(35));
496
497 let shown = value.clamp_length_max(1.0);
498 let reach = rect.width() * 0.5 - READING_MARGIN - radius;
499 let point = rect.center() + egui::vec2(shown.x, -shown.y) * reach;
500 painter.circle_filled(point, radius, highlight_color(value != Vec2::ZERO));
501 painter.rect_stroke(
502 rect,
503 2.0,
504 egui::Stroke::new(1.0, egui::Color32::from_gray(120)),
505 egui::StrokeKind::Inside,
506 );
507
508 ui.label(format!("{:.2}, {:.2}", value.x, value.y));
509}
510
511fn bindings_text<B: Display>(bindings: Vec<B>) -> String {
514 bindings
515 .iter()
516 .map(ToString::to_string)
517 .collect::<Vec<_>>()
518 .join(", ")
519}
520
521fn text_of<B: Display>(binding: Option<B>) -> String {
524 match binding {
525 Some(binding) => binding.to_string(),
526 None => "none".to_string(),
527 }
528}
529
530fn main() {
531 run(
532 Config::new("Mirage: every control as an action"),
533 InputLab::init,
534 );
535}