1use core::fmt;
25
26use crate::{ActionMap, ActionMapError, Binding, BindingParseError};
27
28#[derive(Debug, Clone, PartialEq)]
30pub struct ActionOverride {
31 pub action: String,
33 pub bindings: Vec<Binding>,
35}
36
37#[derive(Debug, Clone, PartialEq)]
40pub enum OverrideRefusal {
41 UnknownAction(String),
44 BadBinding {
48 action: String,
50 error: BindingParseError,
52 },
53 Refused(ActionMapError),
56}
57
58impl fmt::Display for OverrideRefusal {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 Self::UnknownAction(action) => {
62 write!(f, "no action called `{action}`; its binds were skipped")
63 }
64 Self::BadBinding { action, error } => {
65 write!(f, "`{action}` keeps its defaults: {error}")
66 }
67 Self::Refused(error) => write!(f, "{error}; that action keeps its defaults"),
68 }
69 }
70}
71
72impl std::error::Error for OverrideRefusal {}
73
74impl ActionOverride {
75 pub fn from_text(action: &str, texts: &[String]) -> Result<Self, OverrideRefusal> {
86 let bindings = texts
87 .iter()
88 .map(|text| text.parse::<Binding>())
89 .collect::<Result<Vec<_>, _>>()
90 .map_err(|error| OverrideRefusal::BadBinding {
91 action: action.to_owned(),
92 error,
93 })?;
94 Ok(Self {
95 action: action.to_owned(),
96 bindings,
97 })
98 }
99
100 #[must_use]
102 pub fn texts(&self) -> Vec<String> {
103 self.bindings.iter().map(ToString::to_string).collect()
104 }
105}
106
107impl ActionMap {
108 #[must_use]
114 pub fn overrides(&self) -> Vec<ActionOverride> {
115 self.slots
116 .iter()
117 .filter(|slot| slot.decl.bindings != slot.defaults)
118 .map(|slot| ActionOverride {
119 action: slot.decl.name.clone(),
120 bindings: slot.decl.bindings.clone(),
121 })
122 .collect()
123 }
124
125 #[must_use = "an override that did not apply is reported only here"]
136 pub fn apply_overrides(&mut self, overrides: &[ActionOverride]) -> Vec<ActionMapError> {
137 let mut errors = Vec::new();
138 let mut wanted: Vec<Vec<Binding>> = self
139 .slots
140 .iter()
141 .map(|slot| slot.defaults.clone())
142 .collect();
143 for entry in overrides {
144 match self.name_to_idx.get(&entry.action) {
145 Some(&idx) if entry.bindings.iter().all(Binding::deadzone_is_valid) => {
146 wanted[idx].clone_from(&entry.bindings);
147 }
148 Some(_) => errors.push(ActionMapError::InvalidDeadzone(entry.action.clone())),
149 None => errors.push(ActionMapError::UnknownAction(entry.action.clone())),
150 }
151 }
152 for (idx, bindings) in wanted.into_iter().enumerate() {
153 if self.slots[idx].decl.bindings != bindings {
154 let name = self.slots[idx].decl.name.clone();
155 if let Err(error) = self.rebind(&name, bindings) {
156 errors.push(error);
157 }
158 }
159 }
160 errors
161 }
162
163 #[must_use]
166 pub fn override_text(&self) -> Vec<(String, Vec<String>)> {
167 self.overrides()
168 .into_iter()
169 .map(|entry| {
170 let texts = entry.texts();
171 (entry.action, texts)
172 })
173 .collect()
174 }
175
176 #[must_use = "an entry that did not apply is reported only here"]
184 pub fn apply_override_text<'a>(
185 &mut self,
186 entries: impl IntoIterator<Item = (&'a str, &'a [String])>,
187 ) -> Vec<OverrideRefusal> {
188 let mut refusals = Vec::new();
189 let mut parsed = Vec::new();
190 for (action, texts) in entries {
191 match ActionOverride::from_text(action, texts) {
192 Ok(entry) => parsed.push(entry),
193 Err(refusal) => refusals.push(refusal),
194 }
195 }
196 refusals.extend(
197 self.apply_overrides(&parsed)
198 .into_iter()
199 .map(|error| match error {
200 ActionMapError::UnknownAction(action) => OverrideRefusal::UnknownAction(action),
201 other => OverrideRefusal::Refused(other),
202 }),
203 );
204 refusals
205 }
206
207 #[must_use]
216 pub fn bound_elsewhere(&self, action: &str, binding: &Binding) -> Option<&str> {
217 let context = self.context_of(action)?;
218 self.action_names().find(|other| {
219 *other != action
220 && self.context_of(other) == Some(context)
221 && self
222 .bindings(other)
223 .is_some_and(|bindings| bindings.contains(binding))
224 })
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use crcbl_core::input::KeyCode;
231
232 use super::*;
233 use crate::{ActionDecl, ActionKind, ActionValue, ButtonAction, ButtonState, Trigger};
234
235 fn map() -> ActionMap {
236 let mut map = ActionMap::new();
237 for (name, key) in [
238 ("jump", KeyCode::Space),
239 ("crouch", KeyCode::KeyC),
240 ("use", KeyCode::KeyE),
241 ] {
242 map.declare(ActionDecl {
243 name: name.to_owned(),
244 kind: ActionKind::Button,
245 bindings: vec![Binding::Key(key)],
246 });
247 }
248 map
249 }
250
251 fn one(action: &str, bindings: Vec<Binding>) -> ActionOverride {
252 ActionOverride {
253 action: action.to_owned(),
254 bindings,
255 }
256 }
257
258 #[test]
259 fn a_map_nobody_rebound_has_no_overrides() {
260 assert_eq!(map().overrides(), []);
261 }
262
263 #[test]
264 fn only_the_rebound_actions_are_listed_in_declaration_order() {
265 let mut map = map();
266 map.rebind("use", vec![Binding::Key(KeyCode::KeyF)])
267 .unwrap();
268 map.rebind("jump", vec![Binding::Key(KeyCode::KeyJ)])
269 .unwrap();
270 assert_eq!(
271 map.overrides(),
272 [
273 one("jump", vec![Binding::Key(KeyCode::KeyJ)]),
274 one("use", vec![Binding::Key(KeyCode::KeyF)]),
275 ]
276 );
277 map.rebind("use", vec![Binding::Key(KeyCode::KeyE)])
279 .unwrap();
280 assert_eq!(
281 map.overrides(),
282 [one("jump", vec![Binding::Key(KeyCode::KeyJ)])]
283 );
284 }
285
286 #[test]
287 fn applying_a_maps_overrides_to_a_fresh_map_reproduces_it() {
288 let mut played = map();
289 played
290 .rebind(
291 "crouch",
292 vec![
293 Binding::Key(KeyCode::ControlLeft),
294 Binding::PadTrigger {
295 trigger: Trigger::Left,
296 threshold: 0.25,
297 },
298 ],
299 )
300 .unwrap();
301 let saved = played.overrides();
302
303 let mut fresh = map();
304 assert_eq!(fresh.apply_overrides(&saved), []);
305 assert_eq!(fresh.overrides(), saved);
306 assert_eq!(fresh.bindings("crouch"), played.bindings("crouch"));
307 assert_eq!(
308 fresh.bindings("jump"),
309 Some(&[Binding::Key(KeyCode::Space)][..])
310 );
311 }
312
313 #[test]
314 fn an_action_not_listed_goes_back_to_its_declaration() {
315 let mut map = map();
316 map.rebind("jump", vec![Binding::Key(KeyCode::KeyJ)])
317 .unwrap();
318 assert_eq!(map.apply_overrides(&[]), []);
319 assert_eq!(
320 map.bindings("jump"),
321 Some(&[Binding::Key(KeyCode::Space)][..])
322 );
323 assert_eq!(map.overrides(), []);
324 }
325
326 #[test]
327 fn a_bad_override_is_reported_and_the_rest_still_apply() {
328 let mut map = map();
329 let errors = map.apply_overrides(&[
330 one("retired_action", vec![Binding::Key(KeyCode::KeyR)]),
331 one(
332 "crouch",
333 vec![Binding::PadTrigger {
334 trigger: Trigger::Left,
335 threshold: 1.5,
336 }],
337 ),
338 one("use", vec![Binding::Key(KeyCode::KeyF)]),
339 ]);
340 assert_eq!(
341 errors,
342 [
343 ActionMapError::UnknownAction("retired_action".to_owned()),
344 ActionMapError::InvalidDeadzone("crouch".to_owned()),
345 ]
346 );
347 assert_eq!(
348 map.bindings("crouch"),
349 Some(&[Binding::Key(KeyCode::KeyC)][..])
350 );
351 assert_eq!(
352 map.overrides(),
353 [one("use", vec![Binding::Key(KeyCode::KeyF)])]
354 );
355 }
356
357 #[test]
358 fn an_action_already_as_asked_keeps_its_hold() {
359 let mut map = map();
360 map.key_event(KeyCode::Space, true);
361 map.begin_tick(0.016);
362 assert_eq!(map.apply_overrides(&[]), []);
363 let ActionValue::Button(ButtonAction {
366 state: ButtonState::Held { duration },
367 just_pressed,
368 ..
369 }) = *map.action("jump").unwrap()
370 else {
371 panic!("jump is a held button");
372 };
373 assert!(!just_pressed, "pressed again by a rebind nobody asked for");
374 assert!(duration > 0.0, "the hold restarted: {duration}");
375 }
376
377 #[test]
378 fn overrides_round_trip_through_their_text() {
379 let mut map = map();
380 map.rebind(
381 "use",
382 vec![
383 Binding::Key(KeyCode::KeyF),
384 Binding::PadButton(crate::PadButton::West),
385 ],
386 )
387 .unwrap();
388 let written: Vec<(String, Vec<String>)> = map
389 .overrides()
390 .into_iter()
391 .map(|entry| (entry.action.clone(), entry.texts()))
392 .collect();
393 assert_eq!(
394 written,
395 [(
396 "use".to_owned(),
397 vec!["KeyF".to_owned(), "Pad:West".to_owned()]
398 )]
399 );
400 let read: Vec<ActionOverride> = written
403 .iter()
404 .map(|(action, texts)| ActionOverride::from_text(action, texts).unwrap())
405 .collect();
406 let mut fresh = self::tests::map();
407 assert_eq!(fresh.apply_overrides(&read), []);
408 assert_eq!(fresh.overrides(), map.overrides());
409 }
410
411 #[test]
414 fn one_entry_reads_without_a_map_and_a_bad_text_refuses_it() {
415 let entry =
416 ActionOverride::from_text("anything", &["KeyF".to_owned(), "Pad:West".to_owned()])
417 .unwrap();
418 assert_eq!(entry.action, "anything");
419 assert_eq!(
420 entry.bindings,
421 [
422 Binding::Key(KeyCode::KeyF),
423 Binding::PadButton(crate::PadButton::West)
424 ]
425 );
426 assert_eq!(entry.texts(), ["KeyF", "Pad:West"]);
427 let refused =
428 ActionOverride::from_text("use", &["KeyF".to_owned(), "NoSuchKey".to_owned()]);
429 assert!(
430 matches!(
431 &refused,
432 Err(OverrideRefusal::BadBinding { action, .. }) if action == "use"
433 ),
434 "{refused:?}"
435 );
436 }
437
438 #[test]
441 fn a_button_chord_override_round_trips_and_still_shadows() {
442 use crcbl_core::input::PointerButton;
443
444 let mut map = map();
445 map.declare(ActionDecl {
446 name: "aim".to_owned(),
447 kind: ActionKind::Button,
448 bindings: vec![Binding::MouseButton(PointerButton::Right)],
449 });
450 let chord = Binding::ButtonChord {
451 modifier: crate::Modifier::Alt,
452 button: PointerButton::Right,
453 };
454 map.rebind("use", vec![chord.clone()]).unwrap();
455 let written: Vec<String> = map.overrides()[0]
456 .bindings
457 .iter()
458 .map(ToString::to_string)
459 .collect();
460 assert_eq!(written, ["Alt+Mouse:Right"]);
461
462 let mut fresh = self::tests::map();
463 fresh.declare(ActionDecl {
464 name: "aim".to_owned(),
465 kind: ActionKind::Button,
466 bindings: vec![Binding::MouseButton(PointerButton::Right)],
467 });
468 let read = one("use", written.iter().map(|t| t.parse().unwrap()).collect());
469 assert_eq!(fresh.apply_overrides(&[read]), []);
470 assert_eq!(fresh.bindings("use"), Some(&[chord][..]));
471
472 fresh.key_event(KeyCode::AltLeft, true);
473 fresh.mouse_button(PointerButton::Right, true);
474 assert!(fresh.button_held("use"));
475 assert!(!fresh.button_held("aim"), "the loaded chord did not shadow");
476 }
477
478 fn texts(entries: &[(&str, &[&str])]) -> Vec<(String, Vec<String>)> {
479 entries
480 .iter()
481 .map(|(action, texts)| {
482 (
483 (*action).to_owned(),
484 texts.iter().map(|text| (*text).to_owned()).collect(),
485 )
486 })
487 .collect()
488 }
489
490 fn apply_text(map: &mut ActionMap, entries: &[(String, Vec<String>)]) -> Vec<OverrideRefusal> {
491 map.apply_override_text(
492 entries
493 .iter()
494 .map(|(action, texts)| (action.as_str(), texts.as_slice())),
495 )
496 }
497
498 #[test]
501 fn override_text_round_trips_through_a_fresh_map() {
502 let mut played = map();
503 played
504 .rebind(
505 "use",
506 vec![
507 Binding::Key(KeyCode::KeyF),
508 Binding::PadButton(crate::PadButton::West),
509 ],
510 )
511 .unwrap();
512 played.rebind("crouch", Vec::new()).unwrap();
513 let saved = played.override_text();
514 assert_eq!(
515 saved,
516 texts(&[("crouch", &[]), ("use", &["KeyF", "Pad:West"])])
517 );
518
519 let mut fresh = map();
520 assert_eq!(apply_text(&mut fresh, &saved), []);
521 assert_eq!(fresh.overrides(), played.overrides());
522 }
523
524 #[test]
527 fn an_unknown_action_in_the_text_is_skipped_by_name() {
528 let mut map = map();
529 let refusals = apply_text(
530 &mut map,
531 &texts(&[("glide", &["KeyG"]), ("jump", &["KeyJ"])]),
532 );
533 assert_eq!(
534 refusals,
535 [OverrideRefusal::UnknownAction("glide".to_owned())]
536 );
537 assert!(refusals[0].to_string().contains("glide"));
538 assert_eq!(
539 map.bindings("jump"),
540 Some(&[Binding::Key(KeyCode::KeyJ)][..])
541 );
542 }
543
544 #[test]
547 fn a_bad_key_name_is_refused_by_name_and_its_action_keeps_its_defaults() {
548 let mut map = map();
549 let refusals = apply_text(
550 &mut map,
551 &texts(&[("jump", &["KeyJ", "KeyQwerty"]), ("use", &["KeyF"])]),
552 );
553 let [OverrideRefusal::BadBinding { action, error }] = refusals.as_slice() else {
554 panic!("expected one bad binding, got {refusals:?}");
555 };
556 assert_eq!(action, "jump");
557 assert_eq!(error.text, "KeyQwerty");
558 assert!(
559 refusals[0].to_string().contains("KeyQwerty"),
560 "the refusal does not name the text: {}",
561 refusals[0]
562 );
563 assert_eq!(
564 map.bindings("jump"),
565 Some(&[Binding::Key(KeyCode::Space)][..]),
566 "half a list was applied"
567 );
568 assert_eq!(
569 map.bindings("use"),
570 Some(&[Binding::Key(KeyCode::KeyF)][..])
571 );
572 }
573
574 #[test]
578 fn saved_rebinds_survive_a_new_default_action() {
579 let mut old = map();
580 old.rebind("jump", vec![Binding::Key(KeyCode::KeyJ)])
581 .unwrap();
582 let saved = old.override_text();
583
584 let mut updated = map();
585 updated.declare(ActionDecl {
586 name: "dash".to_owned(),
587 kind: ActionKind::Button,
588 bindings: vec![Binding::Key(KeyCode::ShiftLeft)],
589 });
590 assert_eq!(apply_text(&mut updated, &saved), []);
591 assert_eq!(
592 updated.bindings("dash"),
593 Some(&[Binding::Key(KeyCode::ShiftLeft)][..]),
594 "the new action did not come up on its default"
595 );
596 assert_eq!(
597 updated.bindings("jump"),
598 Some(&[Binding::Key(KeyCode::KeyJ)][..])
599 );
600 assert_eq!(updated.override_text(), saved);
601 }
602
603 #[test]
606 fn a_binding_is_bound_elsewhere_only_in_the_same_context() {
607 let mut map = map();
608 map.declare_in(
609 "vehicle",
610 ActionDecl {
611 name: "horn".to_owned(),
612 kind: ActionKind::Button,
613 bindings: vec![Binding::Key(KeyCode::KeyH)],
614 },
615 );
616 let space = Binding::Key(KeyCode::Space);
617 assert_eq!(map.bound_elsewhere("use", &space), Some("jump"));
618 assert_eq!(map.bound_elsewhere("jump", &space), None, "itself");
619 assert_eq!(map.bound_elsewhere("horn", &space), None, "another context");
620 assert_eq!(
621 map.bound_elsewhere("use", &Binding::Key(KeyCode::KeyH)),
622 None
623 );
624 assert_eq!(
625 map.bound_elsewhere("use", &Binding::Key(KeyCode::KeyQ)),
626 None
627 );
628 }
629}