use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_input::prelude::*;
use bevy_time::prelude::*;
use bevy_window::WindowFocused;
pub struct DebouncedInputPlugin {
debounce_secs: f32,
}
#[derive(Message)]
pub struct DebouncedInputEvent {
pub triggered_by_focus_loss: bool,
}
#[derive(Resource)]
struct DebouncedInputTimer {
timer: Timer,
has_pending_input: bool,
}
impl DebouncedInputPlugin {
pub fn new(debounce_secs: f32) -> Self {
Self { debounce_secs }
}
}
impl DebouncedInputEvent {
fn from_timer() -> Self {
Self {
triggered_by_focus_loss: false,
}
}
fn from_focus_loss() -> Self {
Self {
triggered_by_focus_loss: true,
}
}
}
impl DebouncedInputTimer {
fn new(secs: f32) -> Self {
Self {
timer: Timer::from_seconds(secs, TimerMode::Once),
has_pending_input: false,
}
}
}
impl Default for DebouncedInputEvent {
fn default() -> Self {
Self::from_timer()
}
}
impl Plugin for DebouncedInputPlugin {
fn build(&self, app: &mut App) {
app.add_message::<DebouncedInputEvent>()
.insert_resource(DebouncedInputTimer::new(self.debounce_secs))
.add_systems(
Update,
(
detect_input_changes,
handle_debounce_timer,
handle_window_focus,
)
.chain(),
);
}
}
fn detect_input_changes(
mut timer: ResMut<DebouncedInputTimer>,
keyboard: Res<ButtonInput<KeyCode>>,
mouse: Res<ButtonInput<MouseButton>>,
mut touch: MessageReader<TouchInput>,
) {
let has_input =
keyboard.get_pressed().len() > 0 || mouse.get_pressed().len() > 0 || touch.read().len() > 0;
if has_input {
timer.timer.reset();
timer.has_pending_input = true;
}
}
fn handle_debounce_timer(
time: Res<Time>,
mut timer: ResMut<DebouncedInputTimer>,
mut msgs: MessageWriter<DebouncedInputEvent>,
) {
if timer.has_pending_input {
timer.timer.tick(time.delta());
if timer.timer.just_finished() {
msgs.write(DebouncedInputEvent::from_timer());
timer.has_pending_input = false;
}
}
}
fn handle_window_focus(
mut window_focused: MessageReader<WindowFocused>,
mut timer: ResMut<DebouncedInputTimer>,
mut msgs: MessageWriter<DebouncedInputEvent>,
) {
for msg in window_focused.read() {
if !msg.focused && timer.has_pending_input {
msgs.write(DebouncedInputEvent::from_focus_loss());
timer.has_pending_input = false;
timer.timer.reset();
}
}
}