use bevy::{
camera::RenderTarget,
input::{mouse::MouseWheel, touch::TouchPhase},
prelude::*,
window::{CursorOptions, PrimaryWindow, WindowRef},
};
use super::{TrackballCamera, TrackballController};
#[derive(Resource, Clone, Debug, PartialEq, Eq, Default)]
pub struct TrackballViewport {
entity: Option<Entity>,
stolen: usize,
}
impl TrackballViewport {
#[allow(clippy::needless_pass_by_value, clippy::missing_const_for_fn)]
#[must_use]
pub fn stolen(viewport: Res<Self>) -> bool {
viewport.stolen != 0
}
#[must_use]
pub const fn was_stolen(&self) -> bool {
self.entity.is_none()
}
#[allow(clippy::needless_pass_by_value)]
pub const fn set_stolen(&mut self, stolen: Option<usize>) {
if let Some(frames) = stolen {
self.entity = None;
self.stolen = frames;
} else if self.stolen != 0 {
self.stolen -= 1;
}
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
pub(super) fn select<'a>(
viewport: &mut ResMut<Self>,
key_input: &Res<ButtonInput<KeyCode>>,
mouse_input: &Res<ButtonInput<MouseButton>>,
touch_events: &mut MessageReader<TouchInput>,
wheel_events: &MessageReader<MouseWheel>,
primary_windows: &'a mut Query<
(Entity, &mut Window, &mut CursorOptions),
With<PrimaryWindow>,
>,
secondary_windows: &'a mut Query<(&mut Window, &mut CursorOptions), Without<PrimaryWindow>>,
cameras: &'a mut Query<(
Entity,
&Camera,
&RenderTarget,
&TrackballCamera,
&mut TrackballController,
)>,
) -> Option<(
bool,
Entity,
Mut<'a, Window>,
Mut<'a, CursorOptions>,
Entity,
&'a Camera,
&'a TrackballCamera,
Mut<'a, TrackballController>,
)> {
let touch = touch_events
.read()
.filter_map(|touch| (touch.phase == TouchPhase::Started).then_some(touch.position))
.last();
let input = !wheel_events.is_empty()
|| key_input.get_just_pressed().len() != 0
|| mouse_input.get_just_pressed().len() != 0;
let mut new_viewport = viewport.clone();
let mut max_order = 0;
for (group, camera, target, _trackball, _controller) in cameras.iter() {
let RenderTarget::Window(window_ref) = target else {
continue;
};
let window = match window_ref {
WindowRef::Primary => primary_windows
.single()
.ok()
.map(|(_id, window, _cursor_options)| window),
WindowRef::Entity(id) => secondary_windows
.get(*id)
.ok()
.map(|(window, _cursor_options)| window),
};
let Some(window) = window else {
continue;
};
let Some(pos) = touch
.filter(|_pos| window.focused)
.or_else(|| window.cursor_position().filter(|_pos| input))
else {
continue;
};
let Some(Rect { min, max }) = camera.logical_viewport_rect() else {
continue;
};
let contained = (min.x..max.x).contains(&pos.x) && (min.y..max.y).contains(&pos.y);
if contained && camera.order >= max_order {
new_viewport.entity = Some(group);
max_order = camera.order;
}
}
let is_changed = viewport.entity != new_viewport.entity;
if is_changed {
viewport.entity = new_viewport.entity;
}
let camera = viewport
.entity
.and_then(|entity| cameras.get_mut(entity).ok());
let Some((group, camera, target, trackball, controller)) = camera else {
viewport.entity = None;
return None;
};
let RenderTarget::Window(window_ref) = target else {
return None;
};
let (window_id, window, cursor_options) = match window_ref {
WindowRef::Primary => primary_windows.single_mut().ok(),
WindowRef::Entity(id) => secondary_windows
.get_mut(*id)
.ok()
.map(|(window, cursor_options)| (*id, window, cursor_options)),
}?;
Some((
is_changed,
window_id,
window,
cursor_options,
group,
camera,
trackball,
controller,
))
}
}