use std::collections::HashMap;
use bevy::{ecs::message::MessageWriter, prelude::*};
use super::PressedChars;
pub const MODAL_HINT_TIMEOUT: f32 = 0.150;
#[derive(Clone, Debug)]
pub struct ModalNode<A> {
pub children: HashMap<char, ModalNode<A>>,
pub action: Option<A>,
pub label: Option<&'static str>,
}
impl<A: Clone> ModalNode<A> {
pub fn new() -> Self {
Self::default()
}
pub fn child(mut self, key: char, label: &'static str, node: ModalNode<A>) -> Self {
let mut child = node;
child.label = Some(label);
self.children.insert(key, child);
self
}
pub fn action(action: A) -> Self {
Self {
action: Some(action),
children: HashMap::new(),
label: None,
}
}
pub fn has_children(&self) -> bool {
!self.children.is_empty()
}
pub fn is_terminal(&self) -> bool {
self.action.is_some()
}
}
impl<A> Default for ModalNode<A> {
fn default() -> Self {
Self {
children: HashMap::new(),
action: None,
label: None,
}
}
}
#[derive(Resource)]
pub struct ModalState<A> {
pub root: ModalNode<A>,
pub sequence: Vec<char>,
pub time_since_last_key: f32,
pub hints_visible: bool,
pub active: bool,
pub show_help: bool,
}
impl<A: Clone> ModalState<A> {
pub fn new(root: ModalNode<A>) -> Self {
Self { root, ..Default::default() }
}
pub fn current_node(&self) -> Option<&ModalNode<A>> {
let mut node = &self.root;
for &key in &self.sequence {
node = node.children.get(&key)?;
}
Some(node)
}
pub fn reset(&mut self) {
self.sequence.clear();
self.time_since_last_key = 0.0;
self.hints_visible = false;
self.active = false;
self.show_help = false;
}
pub fn process_key(&mut self, key: char) -> Option<A> {
let (is_terminal, action) = {
let current = self.current_node()?;
let next_node = current.children.get(&key)?;
(next_node.is_terminal(), next_node.action.clone())
};
self.sequence.push(key);
self.time_since_last_key = 0.0;
self.hints_visible = false;
self.active = true;
if is_terminal {
self.reset();
return action;
}
self.try_auto_advance()
}
fn try_auto_advance(&mut self) -> Option<A> {
loop {
let (key, is_terminal, action) = {
let current = self.current_node()?;
if current.children.len() != 1 {
return None;
}
let (&k, child) = current.children.iter().next().unwrap();
(k, child.is_terminal(), child.action.clone())
};
self.sequence.push(key);
if is_terminal {
self.reset();
return action;
}
}
}
pub fn is_valid_key(&self, key: char) -> bool {
if let Some(current) = self.current_node() {
current.children.contains_key(&key)
} else {
self.root.children.contains_key(&key)
}
}
}
impl<A: Clone> Default for ModalState<A> {
fn default() -> Self {
Self {
root: ModalNode::new(),
sequence: Vec::new(),
time_since_last_key: 0.0,
hints_visible: false,
active: false,
show_help: false,
}
}
}
pub fn update_modal_state<A: Clone + Send + Sync + 'static>(
time: Res<Time>,
pressed_chars: Res<PressedChars>,
mut modal_state: ResMut<ModalState<A>>,
mut actions: MessageWriter<ModalActionFired<A>>,
) {
if pressed_chars.logical_keys_just_pressed.contains(&KeyCode::Escape) && (modal_state.active || modal_state.show_help) {
modal_state.reset();
return;
}
if modal_state.show_help && !pressed_chars.just_pressed.is_empty() {
modal_state.reset();
}
if modal_state.active {
modal_state.time_since_last_key += time.delta_secs();
if modal_state.time_since_last_key >= MODAL_HINT_TIMEOUT && !modal_state.hints_visible {
modal_state.hints_visible = true;
}
}
for &key in &pressed_chars.just_pressed {
if modal_state.active {
if modal_state.is_valid_key(key) {
if let Some(action) = modal_state.process_key(key) {
actions.write(ModalActionFired(action));
}
} else {
modal_state.reset();
}
} else if modal_state.root.children.contains_key(&key) {
if let Some(action) = modal_state.process_key(key) {
actions.write(ModalActionFired(action));
}
}
}
}
#[derive(Message)]
pub struct ModalActionFired<A>(pub A);