use bevy::input::{
ButtonState,
keyboard::KeyboardInput,
mouse::{MouseButton as BevyMouseButton, MouseButtonInput, MouseScrollUnit, MouseWheel},
touch::{TouchInput, TouchPhase},
};
use bevy::prelude::*;
use bevy::window::{CursorLeft, CursorMoved, PrimaryWindow, WindowFocused, WindowResized};
use noesis_runtime::view::{Key, MouseButton};
use crate::render::NoesisView;
pub mod key_map;
#[derive(Clone, Copy, Debug)]
pub enum NoesisInputEvent {
MouseMove {
x: i32,
y: i32,
},
MouseButton {
down: bool,
x: i32,
y: i32,
button: MouseButton,
},
MouseWheel {
x: i32,
y: i32,
delta: i32,
},
MouseHWheel {
x: i32,
y: i32,
delta: i32,
},
Scroll {
x: i32,
y: i32,
value: f32,
horizontal: bool,
},
TouchDown {
x: i32,
y: i32,
id: u64,
},
TouchMove {
x: i32,
y: i32,
id: u64,
},
TouchUp {
x: i32,
y: i32,
id: u64,
},
KeyDown(Key),
KeyUp(Key),
Char(u32),
Focus(bool),
}
#[derive(Clone, Copy, Debug)]
pub struct TargetedInput {
pub target: Option<Entity>,
pub event: NoesisInputEvent,
}
#[derive(Resource, Clone, Default, Debug)]
pub struct NoesisInputQueue {
pub events: Vec<TargetedInput>,
}
#[derive(Resource, Default, Clone, Copy, Debug)]
pub struct NoesisPointerOverUi {
pub over: bool,
}
impl NoesisInputQueue {
pub fn push(&mut self, ev: NoesisInputEvent) {
self.push_to_opt(None, ev);
}
pub fn push_to(&mut self, target: Entity, ev: NoesisInputEvent) {
self.push_to_opt(Some(target), ev);
}
pub fn push_to_opt(&mut self, target: Option<Entity>, ev: NoesisInputEvent) {
self.events.push(TargetedInput { target, event: ev });
}
pub fn drain(&mut self) -> std::vec::Drain<'_, TargetedInput> {
self.events.drain(..)
}
}
fn to_view_coords(window: &Window, scene: &NoesisView, x: f32, y: f32) -> Option<(i32, i32)> {
let ww = window.width();
let wh = window.height();
if ww <= 0.0 || wh <= 0.0 {
return None;
}
let vw = scene.size.x as f32;
let vh = scene.size.y as f32;
Some(((x * vw / ww) as i32, (y * vh / wh) as i32))
}
#[derive(Resource, Default, Clone, Copy, Debug)]
struct LastPointer {
x: i32,
y: i32,
valid: bool,
target: Option<Entity>,
}
#[allow(clippy::needless_pass_by_value)]
fn forward_cursor_moved(
mut reader: MessageReader<CursorMoved>,
mut queue: ResMut<NoesisInputQueue>,
mut last: ResMut<LastPointer>,
window: Single<(Entity, &Window), With<PrimaryWindow>>,
views: Query<(Entity, &NoesisView)>,
) {
let (primary_window, window) = (window.0, window.1);
let Some((entity, scene)) = views.iter().min_by_key(|(entity, _)| *entity) else {
reader.read(); return;
};
for ev in reader.read() {
if ev.window != primary_window {
continue;
}
if let Some((x, y)) = to_view_coords(window, scene, ev.position.x, ev.position.y) {
last.x = x;
last.y = y;
last.valid = true;
last.target = Some(entity);
queue.push_to(entity, NoesisInputEvent::MouseMove { x, y });
}
}
}
#[allow(clippy::needless_pass_by_value)]
fn forward_cursor_left(
mut reader: MessageReader<CursorLeft>,
mut queue: ResMut<NoesisInputQueue>,
mut last: ResMut<LastPointer>,
primary_window: Single<Entity, With<PrimaryWindow>>,
) {
let primary_window = *primary_window;
let mut left_primary = false;
for ev in reader.read() {
if ev.window == primary_window {
left_primary = true;
}
}
if left_primary {
queue.push_to_opt(last.target, NoesisInputEvent::MouseMove { x: -1, y: -1 });
last.valid = false;
}
}
#[allow(clippy::needless_pass_by_value)]
fn forward_mouse_buttons(
mut reader: MessageReader<MouseButtonInput>,
mut queue: ResMut<NoesisInputQueue>,
last: Res<LastPointer>,
) {
for ev in reader.read() {
let button = match ev.button {
BevyMouseButton::Left => MouseButton::Left,
BevyMouseButton::Right => MouseButton::Right,
BevyMouseButton::Middle => MouseButton::Middle,
BevyMouseButton::Back => MouseButton::XButton1,
BevyMouseButton::Forward => MouseButton::XButton2,
BevyMouseButton::Other(_) => continue,
};
let (x, y) = if last.valid { (last.x, last.y) } else { (0, 0) };
if last.valid {
queue.push_to_opt(last.target, NoesisInputEvent::MouseMove { x, y });
}
queue.push_to_opt(
last.target,
NoesisInputEvent::MouseButton {
down: matches!(ev.state, ButtonState::Pressed),
x,
y,
button,
},
);
}
}
#[allow(clippy::needless_pass_by_value)]
fn forward_mouse_wheel(
mut reader: MessageReader<MouseWheel>,
mut queue: ResMut<NoesisInputQueue>,
last: Res<LastPointer>,
) {
for ev in reader.read() {
let (x, y) = if last.valid { (last.x, last.y) } else { (0, 0) };
let lines_y = match ev.unit {
MouseScrollUnit::Line => ev.y,
MouseScrollUnit::Pixel => ev.y / 40.0,
};
let lines_x = match ev.unit {
MouseScrollUnit::Line => ev.x,
MouseScrollUnit::Pixel => ev.x / 40.0,
};
let wheel_delta = (lines_y * 120.0) as i32;
let hwheel_delta = (lines_x * 120.0) as i32;
if wheel_delta != 0 {
queue.push_to_opt(
last.target,
NoesisInputEvent::MouseWheel {
x,
y,
delta: wheel_delta,
},
);
}
if hwheel_delta != 0 {
queue.push_to_opt(
last.target,
NoesisInputEvent::MouseHWheel {
x,
y,
delta: hwheel_delta,
},
);
}
}
}
#[allow(clippy::needless_pass_by_value)]
fn forward_keyboard(mut reader: MessageReader<KeyboardInput>, mut queue: ResMut<NoesisInputQueue>) {
for ev in reader.read() {
let key = key_map::from_bevy(ev.key_code);
match ev.state {
ButtonState::Pressed => {
if key != Key::None {
queue.push(NoesisInputEvent::KeyDown(key));
}
if let Some(text) = ev.text.as_deref() {
for ch in text.chars() {
queue.push(NoesisInputEvent::Char(ch as u32));
}
}
}
ButtonState::Released => {
if key != Key::None {
queue.push(NoesisInputEvent::KeyUp(key));
}
}
}
}
}
#[allow(clippy::needless_pass_by_value)]
fn forward_touch(
mut reader: MessageReader<TouchInput>,
mut queue: ResMut<NoesisInputQueue>,
window: Single<(Entity, &Window), With<PrimaryWindow>>,
views: Query<(Entity, &NoesisView)>,
) {
let (primary_window, window) = (window.0, window.1);
let Some((entity, scene)) = views.iter().min_by_key(|(entity, _)| *entity) else {
reader.read();
return;
};
for ev in reader.read() {
if ev.window != primary_window {
continue;
}
let Some((x, y)) = to_view_coords(window, scene, ev.position.x, ev.position.y) else {
continue;
};
let id = ev.id;
match ev.phase {
TouchPhase::Started => queue.push_to(entity, NoesisInputEvent::TouchDown { x, y, id }),
TouchPhase::Moved => queue.push_to(entity, NoesisInputEvent::TouchMove { x, y, id }),
TouchPhase::Ended | TouchPhase::Canceled => {
queue.push_to(entity, NoesisInputEvent::TouchUp { x, y, id });
}
}
}
}
#[allow(clippy::needless_pass_by_value)]
fn forward_focus(
mut reader: MessageReader<WindowFocused>,
mut queue: ResMut<NoesisInputQueue>,
primary_window: Single<Entity, With<PrimaryWindow>>,
) {
let primary_window = *primary_window;
for ev in reader.read() {
if ev.window != primary_window {
continue;
}
queue.push(NoesisInputEvent::Focus(ev.focused));
}
}
#[allow(clippy::needless_pass_by_value)]
fn resize_noesis_scene(
mut reader: MessageReader<WindowResized>,
mut views: Query<&mut NoesisView>,
window: Single<(Entity, &Window), With<PrimaryWindow>>,
) {
let (primary_window, window) = (window.0, window.1);
if views.is_empty() {
reader.read();
return;
}
for ev in reader.read() {
if ev.window != primary_window {
continue;
}
let physical = window.physical_size();
if physical.x > 0 && physical.y > 0 {
for mut scene in &mut views {
scene.size = UVec2::new(physical.x, physical.y);
}
}
}
}
pub struct NoesisInputPlugin;
impl Plugin for NoesisInputPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<NoesisInputQueue>()
.init_resource::<LastPointer>()
.init_resource::<NoesisPointerOverUi>()
.add_systems(
PreUpdate,
(
resize_noesis_scene,
forward_cursor_moved,
forward_cursor_left,
forward_mouse_buttons,
forward_mouse_wheel,
forward_keyboard,
forward_touch,
forward_focus,
)
.chain(),
);
}
}