use crate::{math::*, util::History};
use std::collections::HashSet;
use crate::data::input::*;
pub use crate::data::input::Key;
const MAX_CLICK_DIST: f32 = 6.0;
const MAX_CLICK_DELAY: f64 = 0.3;
#[derive(Clone, Debug)]
pub struct InputState {
pub raw: RawInput,
pub mouse: CursorState,
pub scroll_delta: Vec2,
pub screen_rect: Rect,
pub pixels_per_point: f32,
pub time: f64,
pub unstable_dt: f32,
pub predicted_dt: f32,
pub modifiers: Modifiers,
pub keys_down: HashSet<Key>,
pub events: Vec<Event>,
}
impl Default for InputState {
fn default() -> Self {
Self {
raw: Default::default(),
mouse: Default::default(),
scroll_delta: Default::default(),
screen_rect: Rect::from_min_size(Default::default(), vec2(10_000.0, 10_000.0)),
pixels_per_point: 1.0,
time: 0.0,
unstable_dt: 1.0 / 6.0,
predicted_dt: 1.0 / 6.0,
modifiers: Default::default(),
keys_down: Default::default(),
events: Default::default(),
}
}
}
#[derive(Clone, Debug)]
pub struct CursorState {
pub down: bool,
pub pressed: bool,
pub released: bool,
pub could_be_click: bool,
pub click: bool,
pub double_click: bool,
pub last_click_time: f64,
pub pos: Option<Pos2>,
pub press_origin: Option<Pos2>,
pub delta: Vec2,
pub velocity: Vec2,
pos_history: History<Pos2>,
}
impl Default for CursorState {
fn default() -> Self {
Self {
down: false,
pressed: false,
released: false,
could_be_click: false,
click: false,
double_click: false,
last_click_time: std::f64::NEG_INFINITY,
pos: None,
press_origin: None,
delta: Vec2::zero(),
velocity: Vec2::zero(),
pos_history: History::new(1000, 0.1),
}
}
}
impl InputState {
#[must_use]
pub fn begin_frame(self, new: RawInput) -> InputState {
#![allow(deprecated)]
let time = new
.time
.unwrap_or_else(|| self.time + new.predicted_dt as f64);
let unstable_dt = (time - self.time) as f32;
let screen_rect = new.screen_rect.unwrap_or_else(|| {
if new.screen_size != Default::default() {
Rect::from_min_size(Default::default(), new.screen_size)
} else {
self.screen_rect
}
});
let mouse = self.mouse.begin_frame(time, &new);
let mut keys_down = self.keys_down;
for event in &new.events {
if let Event::Key { key, pressed, .. } = event {
if *pressed {
keys_down.insert(*key);
} else {
keys_down.remove(key);
}
}
}
InputState {
mouse,
scroll_delta: new.scroll_delta,
screen_rect,
pixels_per_point: new.pixels_per_point.unwrap_or(self.pixels_per_point),
time,
unstable_dt,
predicted_dt: new.predicted_dt,
modifiers: new.modifiers,
keys_down,
events: new.events.clone(),
raw: new,
}
}
pub fn screen_rect(&self) -> Rect {
self.screen_rect
}
pub fn wants_repaint(&self) -> bool {
self.mouse.pressed
|| self.mouse.released
|| self.mouse.delta != Vec2::zero()
|| self.scroll_delta != Vec2::zero()
|| !self.events.is_empty()
}
pub fn key_pressed(&self, desired_key: Key) -> bool {
self.events.iter().any(|event| {
matches!(
event,
Event::Key {
key,
pressed: true,
..
} if *key == desired_key
)
})
}
pub fn key_down(&self, desired_key: Key) -> bool {
self.keys_down.contains(&desired_key)
}
pub fn key_released(&self, desired_key: Key) -> bool {
self.events.iter().any(|event| {
matches!(
event,
Event::Key {
key,
pressed: false,
..
} if *key == desired_key
)
})
}
pub fn pixels_per_point(&self) -> f32 {
self.pixels_per_point
}
pub fn physical_pixel_size(&self) -> f32 {
1.0 / self.pixels_per_point()
}
pub fn aim_radius(&self) -> f32 {
self.physical_pixel_size()
}
}
impl CursorState {
#[must_use]
pub fn begin_frame(mut self, time: f64, new: &RawInput) -> CursorState {
let delta = new
.mouse_pos
.and_then(|new| self.pos.map(|last| new - last))
.unwrap_or_default();
let pressed = !self.down && new.mouse_down;
let released = self.down && !new.mouse_down;
let click = released && self.could_be_click;
let double_click = click && (time - self.last_click_time) < MAX_CLICK_DELAY;
let mut press_origin = self.press_origin;
let mut could_be_click = self.could_be_click;
let mut last_click_time = self.last_click_time;
if click {
last_click_time = time
}
if pressed {
press_origin = new.mouse_pos;
could_be_click = true;
} else if !self.down || self.pos.is_none() {
press_origin = None;
}
if let (Some(press_origin), Some(mouse_pos)) = (new.mouse_pos, press_origin) {
could_be_click &= press_origin.distance(mouse_pos) < MAX_CLICK_DIST;
} else {
could_be_click = false;
}
if pressed {
self.pos_history.clear();
}
if let Some(mouse_pos) = new.mouse_pos {
self.pos_history.add(time, mouse_pos);
} else {
}
self.pos_history.flush(time);
let velocity = if self.pos_history.len() >= 3 && self.pos_history.duration() > 0.01 {
self.pos_history.velocity().unwrap_or_default()
} else {
Vec2::default()
};
CursorState {
down: new.mouse_down && new.mouse_pos.is_some(),
pressed,
released,
could_be_click,
click,
double_click,
last_click_time,
pos: new.mouse_pos,
press_origin,
delta,
velocity,
pos_history: self.pos_history,
}
}
pub fn is_moving(&self) -> bool {
self.velocity != Vec2::zero()
}
}
impl InputState {
pub fn ui(&self, ui: &mut crate::Ui) {
let Self {
raw,
mouse,
scroll_delta,
screen_rect,
pixels_per_point,
time,
unstable_dt,
predicted_dt,
modifiers,
keys_down,
events,
} = self;
ui.style_mut().body_text_style = crate::paint::TextStyle::Monospace;
ui.collapsing("Raw Input", |ui| raw.ui(ui));
crate::containers::CollapsingHeader::new("🖱 Mouse")
.default_open(true)
.show(ui, |ui| {
mouse.ui(ui);
});
ui.label(format!("scroll_delta: {:?} points", scroll_delta));
ui.label(format!("screen_rect: {:?} points", screen_rect));
ui.label(format!(
"{:?} physical pixels for each logical point",
pixels_per_point
));
ui.label(format!("time: {:.3} s", time));
ui.label(format!(
"time since previous frame: {:.1} ms",
1e3 * unstable_dt
));
ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt));
ui.label(format!("modifiers: {:#?}", modifiers));
ui.label(format!("keys_down: {:?}", keys_down));
ui.label(format!("events: {:?}", events))
.on_hover_text("key presses etc");
}
}
impl CursorState {
pub fn ui(&self, ui: &mut crate::Ui) {
let Self {
down,
pressed,
released,
could_be_click,
click,
double_click,
last_click_time,
pos,
press_origin,
delta,
velocity,
pos_history: _,
} = self;
ui.label(format!("down: {}", down));
ui.label(format!("pressed: {}", pressed));
ui.label(format!("released: {}", released));
ui.label(format!("could_be_click: {}", could_be_click));
ui.label(format!("click: {}", click));
ui.label(format!("double_click: {}", double_click));
ui.label(format!("last_click_time: {:.3}", last_click_time));
ui.label(format!("pos: {:?}", pos));
ui.label(format!("press_origin: {:?}", press_origin));
ui.label(format!("delta: {:?}", delta));
ui.label(format!(
"velocity: [{:3.0} {:3.0}] points/sec",
velocity.x, velocity.y
));
}
}