use std::fmt::{Display, Formatter};
use bevy::log::{error, info, warn};
use serde::{Deserialize, Serialize};
use crate::input_action::InputKind;
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct InputConfigReport {
problems: Vec<InputConfigReportItem>,
}
impl InputConfigReport {
#[must_use]
pub fn is_empty(&self) -> bool {
self.problems.is_empty()
}
pub fn dump_to_log(&self) {
for problem in &self.problems {
match problem.severity {
Severity::Info => {
info!("{}", problem.problem.print());
}
Severity::Warning => {
warn!("{}", problem.problem.print());
}
Severity::Error => {
error!("{}", problem.problem.print());
}
}
}
}
#[allow(unused)]
pub(crate) fn info(&mut self, problem: InputConfigProblem) {
self.problems.push(InputConfigReportItem {
severity: Severity::Info,
problem,
});
}
pub(crate) fn warning(&mut self, problem: InputConfigProblem) {
self.problems.push(InputConfigReportItem {
severity: Severity::Warning,
problem,
});
}
pub(crate) fn error(&mut self, problem: InputConfigProblem) {
self.problems.push(InputConfigReportItem {
severity: Severity::Error,
problem,
});
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct ActionLocation {
pub group_id: String,
pub action_id: String,
pub index: usize,
}
impl Display for ActionLocation {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}::{}#{}", self.group_id, self.action_id, self.index)
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct InputConfigReportItem {
pub severity: Severity,
pub problem: InputConfigProblem,
}
#[derive(
Debug, Default, Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord,
)]
pub enum Severity {
Info,
#[default]
Warning,
Error,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
pub enum InputConfigProblem {
UnknownGroup {
group_id: String,
options: Vec<String>,
},
UnknownAction {
group_id: String,
action_id: String,
options: Vec<String>,
},
ActionWrongKind {
loc: ActionLocation,
wrong_kind: InputKind,
right_kind: InputKind,
},
RootBindingIsDummy {
loc: ActionLocation,
},
ConvolutedDummy {
loc: ActionLocation,
is_now: String,
},
ChordContainsDuplicates {
loc: ActionLocation,
},
SequenceEmpty {
loc: ActionLocation,
},
SequenceOnlyContainsOneElement {
loc: ActionLocation,
},
SequenceUnrealisticTiming {
loc: ActionLocation,
actual_millis: usize,
},
}
impl InputConfigProblem {
#[must_use]
pub fn print(&self) -> String {
match self {
InputConfigProblem::UnknownGroup {
group_id: name,
options: known,
} => {
format!("Unknown group '{name}'. Must be one of: {known:?}.\n\
\tYou can register this InputAction enum by calling app.register_input_action::<{name}>()")
}
InputConfigProblem::UnknownAction {
group_id,
action_id,
options: known,
} => {
format!("Unknown action '{group_id}::{action_id}'. Must be one of: {known:?}")
}
InputConfigProblem::ActionWrongKind {
loc,
wrong_kind,
right_kind,
} => {
format!(
"Binding {loc} is of the wrong kind.\n\
\tIs a '{wrong_kind:?}': {}\n\
\tShould be a '{right_kind:?}': {}\n\
\tAn example of a valid {right_kind:?} binding is: {:?}",
wrong_kind.explain(),
right_kind.explain(),
right_kind.example()
)
}
InputConfigProblem::RootBindingIsDummy { loc } => {
format!("Binding {loc} is a dummy and will never do anything useful. You can safely remove it.")
}
InputConfigProblem::ConvolutedDummy { loc, is_now } => {
format!(
"Binding {loc} contains a dummy section that will never activate.\n\
\t`{is_now}` is redundant can be replaced with `Dummy`."
)
}
InputConfigProblem::ChordContainsDuplicates { loc } => {
format!("Binding {loc} contains a chord with duplicate entries.\n\
\tA chord is a group of inputs that must be activated at the same time; for example, Ctrl-S to save a document.\n\
\tA chord's children should be unique. Duplicates don't do anything and will be ignored.")
}
InputConfigProblem::SequenceEmpty { loc } => {
format!("Binding {loc} contains an empty sequence.\n\
\tA sequence is a series of inputs that must be triggered one after another, with a maximum delay between individual inputs. For example: entering a cheat code.\n\
\tAn empty sequence will never activate, and can be replaced with `Dummy` for greater readability.")
}
InputConfigProblem::SequenceOnlyContainsOneElement { loc } => {
format!("Binding {loc} contains a sequence with only one element.\n\
\tA sequence is a series of inputs that must be triggered one after another, with a maximum delay between individual inputs. For example: entering a cheat code.\n\
\tA sequence with only one element may be replaced with `WhenPressed(_)` for greater readability.", )
}
InputConfigProblem::SequenceUnrealisticTiming { loc, actual_millis } => {
format!("Binding {loc} contains a sequence with a maximum delay of {actual_millis} milliseconds.\n\
\tA sequence is a series of inputs that must be triggered one after another, with a maximum delay between individual inputs. For example: entering a cheat code.\n\
\tThe maximum delay (currently {actual_millis}ms) is the maximum amount of time between any two inputs in the sequence.\n\
\tThis seems unrealistically low and may never activate. Did you perhaps mean {actual_millis} seconds? If so, change to `{actual_millis}000`.")
}
}
}
}