Replace bevy_mod_picking selection

This commit is contained in:
Danny McGee
2024-04-01 12:56:53 -04:00
parent 46351e53d3
commit e39843690d
5 changed files with 364 additions and 188 deletions
-1
View File
@@ -83,5 +83,4 @@ features = [
"backend_raycast",
"backend_sprite",
"debug",
"selection",
]
+29 -31
View File
@@ -6,7 +6,7 @@ use bevy::{
};
use crate::{
input::{self, Action, CameraMotion, InputAction, Lifecycle},
input::{self, CameraAction, CameraMotion, Lifecycle, SelectionAction},
interaction::Selection,
math::{
Easing, InterpPosition, InterpVelocity, Interpolation, InterpolationSystem, NearlyEq,
@@ -71,10 +71,10 @@ impl CameraControllerBundle {
fn camera_control_kinematic(
mut q_window: Query<&mut Window, With<PrimaryWindow>>,
mut input: EventReader<InputAction>,
mut er_camera: EventReader<CameraAction>,
mut q_camera: Query<(&mut CameraController, &mut Transform, &Projection)>,
) {
if input.is_empty() {
if er_camera.is_empty() {
return;
}
@@ -82,30 +82,28 @@ fn camera_control_kinematic(
let mut any_started = false;
let mut any_stopped = false;
for action in input.read().filter(|action| {
!matches!(
action,
InputAction(_, Action::Camera(CameraMotion::Translate(_)))
)
}) {
if let InputAction(lifecycle, Action::Camera(motion)) = action {
let (mut controller, mut xform, proj) = q_camera.single_mut();
let window = q_window.single();
for action in er_camera
.read()
.filter(|action| !matches!(action, CameraAction(_, CameraMotion::Translate(_))))
{
let CameraAction(lifecycle, motion) = action;
let (mut controller, mut xform, proj) = q_camera.single_mut();
let window = q_window.single();
match lifecycle {
Lifecycle::Start => any_started = true,
Lifecycle::Ongoing => any_ongoing = true,
Lifecycle::Stop => any_stopped = true,
}
match lifecycle {
Lifecycle::Start => any_started = true,
Lifecycle::Ongoing => any_ongoing = true,
Lifecycle::Stop => any_stopped = true,
_ => {}
}
use CameraMotion::*;
match motion {
Pan(delta) => do_pan(proj, window, *delta, &mut xform, &mut controller),
Zoom(delta) => do_zoom(window, *delta, &mut xform, &mut controller),
Orbit(delta) => do_orbit(window, *delta, *lifecycle, &mut xform, &mut controller),
Rotate(delta) => do_rotate(window, *delta, &mut xform, &mut controller),
_ => {}
}
use CameraMotion::*;
match motion {
Pan(delta) => do_pan(proj, window, *delta, &mut xform, &mut controller),
Zoom(delta) => do_zoom(window, *delta, &mut xform, &mut controller),
Orbit(delta) => do_orbit(window, *delta, *lifecycle, &mut xform, &mut controller),
Rotate(delta) => do_rotate(window, *delta, &mut xform, &mut controller),
_ => {}
}
}
@@ -118,12 +116,12 @@ fn camera_control_kinematic(
fn process_physical_inputs(
mut cmd: Commands,
mut input: EventReader<InputAction>,
mut er_camera: EventReader<CameraAction>,
q_camera: Query<(Entity, &Velocity), With<CameraController>>,
mut q_interp: Query<&mut InterpVelocity>,
) {
for action in input.read() {
let &InputAction(_, Action::Camera(CameraMotion::Translate(delta))) = action else {
for action in er_camera.read() {
let &CameraAction(_, CameraMotion::Translate(delta)) = action else {
continue;
};
@@ -156,7 +154,7 @@ fn process_physical_inputs(
fn process_focus_inputs(
mut cmd: Commands,
r_selection: Option<Res<Selection>>,
mut input: EventReader<InputAction>,
mut er_selection: EventReader<SelectionAction>,
mut q_camera: Query<(Entity, &CameraController, &mut Velocity)>,
q_interp_vel: Query<&InterpVelocity>,
mut q_interp_pos: Query<&mut InterpPosition>,
@@ -166,10 +164,10 @@ fn process_focus_inputs(
return;
};
if input.read().any(|action| {
if er_selection.read().any(|action| {
matches!(
action,
InputAction(Lifecycle::Stop, Action::Selection(input::Selection::Focus))
SelectionAction(Lifecycle::Stop, input::Selection::Focus)
)
}) {
let Ok(target) = q_global_xforms
+8 -6
View File
@@ -15,7 +15,7 @@ use space_editor::{
use crate::{
anim::SkeletalJoint,
camera_controller::{CameraController, CameraMotionSystem},
input::{Action, InputAction, Lifecycle},
input::{self, EditorAction, Lifecycle},
is_editor, MainCamera,
};
@@ -83,11 +83,13 @@ impl Plugin for DebugPlugin {
}
}
fn toggle_debug_mode(mut input: EventReader<InputAction>, mut r_debug: ResMut<DebugMode>) {
if input
.read()
.any(|action| matches!(action, InputAction(Lifecycle::Stop, Action::ToggleDebug)))
{
fn toggle_debug_mode(mut er_editor: EventReader<EditorAction>, mut r_debug: ResMut<DebugMode>) {
if er_editor.read().any(|action| {
matches!(
action,
EditorAction(Lifecycle::Stop, input::Editor::ToggleDebug)
)
}) {
**r_debug = !**r_debug;
}
}
+267 -125
View File
@@ -2,18 +2,30 @@ use bevy::{
input::mouse::{MouseMotion, MouseWheel},
prelude::*,
utils::HashSet,
window::PrimaryWindow,
};
use space_editor::prelude::space_undo::UndoRedo;
use crate::is_editor;
use crate::{is_editor, math::NearlyZero};
#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
pub struct StudioInputSystem;
#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
pub struct CameraInputSystem;
#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
pub struct EditorInputSystem;
#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
pub struct SelectionInputSystem;
pub struct StudioInputPlugin;
impl Plugin for StudioInputPlugin {
fn build(&self, app: &mut App) {
app.add_event::<InputAction>();
app.add_event::<CameraAction>()
.add_event::<SelectionAction>()
.add_event::<EditorAction>();
// Reading each frame of the mouse delta allows
// to get rid of the bug that delta is counted from the last reading,
@@ -28,18 +40,32 @@ impl Plugin for StudioInputPlugin {
app.add_systems(
PreUpdate,
(
translate_selection_events,
translate_debug_toggle,
translate_dcc_camera_events,
translate_fps_camera_events,
send_undo_event.run_if(is_editor),
)
(translate_dcc_camera_events, translate_fps_camera_events)
.in_set(StudioInputSystem)
.in_set(CameraInputSystem)
.after(bevy::input::InputSystem)
.after(read_mouse_delta),
);
app.add_systems(
PreUpdate,
(translate_debug_toggle, send_undo_event.run_if(is_editor))
.in_set(StudioInputSystem)
.in_set(EditorInputSystem)
.after(bevy::input::InputSystem)
.after(read_mouse_delta),
);
app.add_systems(
PreUpdate,
(apply_deferred, translate_selection_events)
.in_set(StudioInputSystem)
.in_set(SelectionInputSystem)
.after(bevy::input::InputSystem)
.after(CameraInputSystem)
.after(read_mouse_delta),
);
app.add_systems(
PostUpdate,
(apply_deferred, log_input_actions).after(StudioInputSystem),
@@ -47,78 +73,26 @@ impl Plugin for StudioInputPlugin {
}
}
/// Logs all `InputAction`s emitted, along with the input state at the time they
/// were fired, with `debug` verbosity.
fn log_input_actions(
ri_keyboard: Res<Input<KeyCode>>,
ri_mouse_buttons: Res<Input<MouseButton>>,
mut er_input_actions: EventReader<InputAction>,
) {
if er_input_actions.is_empty() {
return;
}
let lmb = if ri_mouse_buttons.pressed(MouseButton::Left) {
'X'
} else {
'-'
};
let mmb = if ri_mouse_buttons.pressed(MouseButton::Middle) {
'X'
} else {
'-'
};
let rmb = if ri_mouse_buttons.pressed(MouseButton::Right) {
'X'
} else {
'-'
};
use KeyCode::*;
let mod_keys = [AltLeft, AltRight, ShiftLeft, ControlLeft, ControlRight]
.into_iter()
.collect::<HashSet<_>>();
let action_keys = [W, A, S, D, E, Q, Escape, Grave, Delete, F]
.into_iter()
.collect::<HashSet<_>>();
let keys_pressed = ri_keyboard.get_pressed().copied().collect::<HashSet<_>>();
let keys_display = keys_pressed
.intersection(&mod_keys)
.map(|key| format!("{key:?}"))
.chain(
keys_pressed
.intersection(&action_keys)
.map(|key| format!("{key:?}")),
)
.collect::<Vec<_>>()
.join("+");
for action in er_input_actions.read() {
debug!(
"Mouse: [{lmb}{mmb}{rmb}] \
Keyboard: [{keys_display}] \
Action: [{action:.2?}]"
);
}
}
#[derive(Event, Clone, Copy, Debug)]
pub struct InputAction(pub Lifecycle, pub Action);
// #[derive(Event, Clone, Copy, Debug)]
// pub struct InputAction(pub Lifecycle, pub Action);
#[derive(Clone, Copy, Debug)]
pub enum Lifecycle {
Start,
Ongoing,
Stop,
Cancel,
}
#[derive(Clone, Copy, Debug)]
pub enum Action {
Camera(CameraMotion),
Selection(Selection),
ToggleDebug,
}
// #[derive(Clone, Copy, Debug)]
// pub enum Action {
// Camera(CameraMotion),
// Selection(Selection),
// ToggleDebug,
// }
#[derive(Event, Clone, Copy, Debug)]
pub struct CameraAction(pub Lifecycle, pub CameraMotion);
#[derive(Clone, Copy, Debug)]
pub enum CameraMotion {
@@ -128,11 +102,9 @@ pub enum CameraMotion {
Orbit(Vec2),
Rotate(Vec2),
}
impl From<CameraMotion> for Action {
fn from(value: CameraMotion) -> Self {
Action::Camera(value)
}
}
#[derive(Event, Clone, Copy, Debug)]
pub struct SelectionAction(pub Lifecycle, pub Selection);
#[derive(Clone, Copy, Debug)]
pub enum Selection {
@@ -142,41 +114,137 @@ pub enum Selection {
Focus,
/// Delete the currently selected entity and its descendants
Delete,
/// Select the single target under the cursor position
Single(Vec2),
/// Add or remove the target under the cursor position to the current
/// selection
Multi(Vec2),
/// Select all targets that overlap the marquee selection
Marquee(Rect),
}
impl From<Selection> for Action {
fn from(value: Selection) -> Self {
Action::Selection(value)
}
#[derive(Event, Clone, Copy, Debug)]
pub struct EditorAction(pub Lifecycle, pub Editor);
#[derive(Clone, Copy, Debug)]
pub enum Editor {
ToggleDebug,
}
// TODO: Make the bindings of raw device inputs to InputActions configurable
fn translate_selection_events(keyboard: Res<Input<KeyCode>>, mut input: EventWriter<InputAction>) {
if keyboard.just_pressed(KeyCode::Escape) {
input.send(InputAction(Lifecycle::Start, Selection::Clear.into()));
} else if keyboard.just_released(KeyCode::Escape) {
input.send(InputAction(Lifecycle::Stop, Selection::Clear.into()));
fn translate_selection_events(
r_mouse_motion: Res<LastFrameMouseDelta>,
ri_mouse_buttons: Res<Input<MouseButton>>,
ri_keyboard: Res<Input<KeyCode>>,
mut l_pointer_drag_start: Local<Option<Vec2>>,
mut l_drag_dist2: Local<f32>,
q_window: Query<&Window, With<PrimaryWindow>>,
mut er_camera_actions: EventReader<CameraAction>,
mut ew_actions: EventWriter<SelectionAction>,
) {
// Keyboard actions -- clear, focus, etc.
if ri_keyboard.just_pressed(KeyCode::Escape) {
ew_actions.send(SelectionAction(Lifecycle::Start, Selection::Clear));
} else if ri_keyboard.just_released(KeyCode::Escape) {
ew_actions.send(SelectionAction(Lifecycle::Stop, Selection::Clear));
}
if keyboard.just_pressed(KeyCode::F) {
input.send(InputAction(Lifecycle::Start, Selection::Focus.into()));
} else if keyboard.just_released(KeyCode::F) {
input.send(InputAction(Lifecycle::Stop, Selection::Focus.into()));
if ri_keyboard.just_pressed(KeyCode::F) {
ew_actions.send(SelectionAction(Lifecycle::Start, Selection::Focus));
} else if ri_keyboard.just_released(KeyCode::F) {
ew_actions.send(SelectionAction(Lifecycle::Stop, Selection::Focus));
}
if keyboard.just_pressed(KeyCode::Delete) {
input.send(InputAction(Lifecycle::Start, Selection::Delete.into()));
} else if keyboard.just_released(KeyCode::Delete) {
input.send(InputAction(Lifecycle::Stop, Selection::Delete.into()));
if ri_keyboard.just_pressed(KeyCode::Delete) {
ew_actions.send(SelectionAction(Lifecycle::Start, Selection::Delete));
} else if ri_keyboard.just_released(KeyCode::Delete) {
ew_actions.send(SelectionAction(Lifecycle::Stop, Selection::Delete));
}
// Mouse-based actions
if !er_camera_actions.is_empty() {
// We don't want to emit any mouse-triggered selection actions if
// we've already emitted some kind of camera motion this frame. This
// avoids UX jank where releasing the LMB at the end of e.g.
// orbiting the camera causes the object under the cursor to become
// selected as an unintended side-effect.
er_camera_actions.clear();
return;
}
let Some(mouse_pos) = q_window
.get_single()
.ok()
.and_then(|window| window.cursor_position())
else {
return;
};
if ri_mouse_buttons.just_pressed(MouseButton::Left) {
*l_drag_dist2 = 0.;
*l_pointer_drag_start = Some(mouse_pos);
ew_actions.send(SelectionAction(
Lifecycle::Start,
Selection::Marquee(Rect::from_corners(mouse_pos, mouse_pos)),
));
} else if ri_mouse_buttons.pressed(MouseButton::Left) {
*l_drag_dist2 += r_mouse_motion.0.length_squared();
let Some(drag_start) = *l_pointer_drag_start else {
return;
};
ew_actions.send(SelectionAction(
Lifecycle::Ongoing,
Selection::Marquee(Rect::from_corners(drag_start, mouse_pos)),
));
} else if ri_mouse_buttons.just_released(MouseButton::Left) {
*l_drag_dist2 += r_mouse_motion.0.length_squared();
let Some(drag_start) = l_pointer_drag_start.take() else {
return;
};
// If the total drag distance is nearly zero, treat this as a single-click
if l_drag_dist2.nearly_zero_within(1e-3) {
ew_actions.send(SelectionAction(
Lifecycle::Cancel,
Selection::Marquee(Rect::from_corners(drag_start, mouse_pos)),
));
if ri_keyboard.any_pressed([
KeyCode::ShiftLeft,
KeyCode::ShiftRight,
KeyCode::ControlLeft,
KeyCode::ControlRight,
]) {
ew_actions.send(SelectionAction(
Lifecycle::Stop,
Selection::Multi(mouse_pos),
));
} else {
ew_actions.send(SelectionAction(
Lifecycle::Stop,
Selection::Single(mouse_pos),
));
}
} else {
ew_actions.send(SelectionAction(
Lifecycle::Stop,
Selection::Marquee(Rect::from_corners(drag_start, mouse_pos)),
));
}
}
}
fn translate_debug_toggle(keyboard: Res<Input<KeyCode>>, mut input: EventWriter<InputAction>) {
fn translate_debug_toggle(keyboard: Res<Input<KeyCode>>, mut input: EventWriter<EditorAction>) {
if keyboard.pressed(KeyCode::ControlLeft) || keyboard.pressed(KeyCode::ControlRight) {
if keyboard.just_pressed(KeyCode::Grave) {
input.send(InputAction(Lifecycle::Start, Action::ToggleDebug));
input.send(EditorAction(Lifecycle::Start, Editor::ToggleDebug));
} else if keyboard.just_released(KeyCode::Grave) {
input.send(InputAction(Lifecycle::Stop, Action::ToggleDebug));
input.send(EditorAction(Lifecycle::Stop, Editor::ToggleDebug));
}
}
}
@@ -185,7 +253,7 @@ fn translate_dcc_camera_events(
mouse_motion: Res<LastFrameMouseDelta>,
mouse_buttons: Res<Input<MouseButton>>,
keyboard: Res<Input<KeyCode>>,
mut input: EventWriter<InputAction>,
mut input: EventWriter<CameraAction>,
) {
let delta = mouse_motion.get();
// Camera Pan
@@ -195,14 +263,11 @@ fn translate_dcc_camera_events(
} else {
Lifecycle::Ongoing
};
input.send(InputAction(lifecycle, CameraMotion::Pan(delta).into()));
input.send(CameraAction(lifecycle, CameraMotion::Pan(delta)));
}
// Stop Camera Panning
else if mouse_buttons.just_released(MouseButton::Middle) {
input.send(InputAction(
Lifecycle::Stop,
CameraMotion::Pan(Vec2::ZERO).into(),
));
input.send(CameraAction(Lifecycle::Stop, CameraMotion::Pan(Vec2::ZERO)));
}
// Camera Orbit/Zoom
else if keyboard.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]) {
@@ -220,18 +285,18 @@ fn translate_dcc_camera_events(
CameraMotion::Zoom(delta.y)
};
input.send(InputAction(lifecycle, motion.into()));
input.send(CameraAction(lifecycle, motion));
}
// Stop Camera Orbit
else if mouse_buttons.just_released(MouseButton::Left) {
input.send(InputAction(
input.send(CameraAction(
Lifecycle::Stop,
CameraMotion::Orbit(Vec2::ZERO).into(),
CameraMotion::Orbit(Vec2::ZERO),
));
}
// Stop Camera Zoom
else if mouse_buttons.just_released(MouseButton::Right) {
input.send(InputAction(Lifecycle::Stop, CameraMotion::Zoom(0.).into()));
input.send(CameraAction(Lifecycle::Stop, CameraMotion::Zoom(0.)));
}
}
// Stop Camera Orbit & Zoom
@@ -240,14 +305,14 @@ fn translate_dcc_camera_events(
{
// Stop Camera Orbit
if mouse_buttons.pressed(MouseButton::Left) {
input.send(InputAction(
input.send(CameraAction(
Lifecycle::Stop,
CameraMotion::Orbit(Vec2::ZERO).into(),
CameraMotion::Orbit(Vec2::ZERO),
));
}
// Stop Camera Zoom
else if mouse_buttons.pressed(MouseButton::Right) {
input.send(InputAction(Lifecycle::Stop, CameraMotion::Zoom(0.).into()));
input.send(CameraAction(Lifecycle::Stop, CameraMotion::Zoom(0.)));
}
}
// Camera Rotation
@@ -258,13 +323,13 @@ fn translate_dcc_camera_events(
Lifecycle::Ongoing
};
input.send(InputAction(lifecycle, CameraMotion::Rotate(delta).into()));
input.send(CameraAction(lifecycle, CameraMotion::Rotate(delta)));
}
// Stop Camera Rotation
else if mouse_buttons.just_released(MouseButton::Right) {
input.send(InputAction(
input.send(CameraAction(
Lifecycle::Stop,
CameraMotion::Rotate(Vec2::ZERO).into(),
CameraMotion::Rotate(Vec2::ZERO),
));
}
// Alternative Camera Rotation (designed for laptop)
@@ -275,13 +340,13 @@ fn translate_dcc_camera_events(
Lifecycle::Ongoing
};
input.send(InputAction(lifecycle, CameraMotion::Rotate(delta).into()));
input.send(CameraAction(lifecycle, CameraMotion::Rotate(delta)));
}
// Stop Camera Rotation
else if keyboard.just_released(KeyCode::ShiftLeft) {
input.send(InputAction(
input.send(CameraAction(
Lifecycle::Stop,
CameraMotion::Rotate(Vec2::ZERO).into(),
CameraMotion::Rotate(Vec2::ZERO),
));
}
}
@@ -289,7 +354,7 @@ fn translate_dcc_camera_events(
fn translate_fps_camera_events(
mut mouse_wheel: EventReader<MouseWheel>,
keyboard: Res<Input<KeyCode>>,
mut input: EventWriter<InputAction>,
mut input: EventWriter<CameraAction>,
) {
// Camera Translation (FPS-like)
use KeyCode::*;
@@ -330,13 +395,13 @@ fn translate_fps_camera_events(
Lifecycle::Ongoing
};
input.send(InputAction(lifecycle, CameraMotion::Translate(mvmt).into()));
input.send(CameraAction(lifecycle, CameraMotion::Translate(mvmt)));
}
// Stop Translation
else if keyboard.any_just_released(wasd_keys) {
input.send(InputAction(
input.send(CameraAction(
Lifecycle::Stop,
CameraMotion::Translate(Vec3::ZERO).into(),
CameraMotion::Translate(Vec3::ZERO),
));
}
@@ -347,10 +412,7 @@ fn translate_fps_camera_events(
// let rem_y = (100. / y).abs() % 1.;
// if (rem_x.is_nan() || rem_x < 1e-5_f32) && (rem_y.is_nan() || rem_y < 1e-5_f32) {
input.send(InputAction(
Lifecycle::Ongoing,
CameraMotion::Zoom(y).into(),
));
input.send(CameraAction(Lifecycle::Ongoing, CameraMotion::Zoom(y)));
// } else {
// FIXME: I had high hopes for this, but the input translation from
// Bevy / winit is herky-jerky-janky af. It seems to be
@@ -411,3 +473,83 @@ fn send_undo_event(mut events: EventWriter<UndoRedo>, keyboard: Res<Input<KeyCod
info!("Redo event sent");
}
}
/// Logs all `InputAction`s emitted, along with the input state at the time they
/// were fired, with `debug` verbosity.
fn log_input_actions(
ri_keyboard: Res<Input<KeyCode>>,
ri_mouse_buttons: Res<Input<MouseButton>>,
mut er_camera_actions: EventReader<CameraAction>,
mut er_selection_actions: EventReader<SelectionAction>,
mut er_editor_actions: EventReader<EditorAction>,
) {
#[derive(Clone, Copy, Debug)]
enum InputAction {
Camera(CameraAction),
Selection(SelectionAction),
Editor(EditorAction),
}
let input_actions = er_camera_actions
.read()
.map(|&action| InputAction::Camera(action))
.chain(
er_selection_actions
.read()
.map(|&action| InputAction::Selection(action)),
)
.chain(
er_editor_actions
.read()
.map(|&action| InputAction::Editor(action)),
)
.collect::<Vec<_>>();
if input_actions.is_empty() {
return;
}
let lmb = if ri_mouse_buttons.pressed(MouseButton::Left) {
'X'
} else {
'-'
};
let mmb = if ri_mouse_buttons.pressed(MouseButton::Middle) {
'X'
} else {
'-'
};
let rmb = if ri_mouse_buttons.pressed(MouseButton::Right) {
'X'
} else {
'-'
};
use KeyCode::*;
let mod_keys = [AltLeft, AltRight, ShiftLeft, ControlLeft, ControlRight]
.into_iter()
.collect::<HashSet<_>>();
let action_keys = [W, A, S, D, E, Q, Escape, Grave, Delete, F]
.into_iter()
.collect::<HashSet<_>>();
let keys_pressed = ri_keyboard.get_pressed().copied().collect::<HashSet<_>>();
let keys_display = keys_pressed
.intersection(&mod_keys)
.map(|key| format!("{key:?}"))
.chain(
keys_pressed
.intersection(&action_keys)
.map(|key| format!("{key:?}")),
)
.collect::<Vec<_>>()
.join("+");
for action in input_actions {
info!(
"Mouse: [{lmb}{mmb}{rmb}] \
Keyboard: [{keys_display}] \
Action: [{action:.2?}]"
);
}
}
+60 -25
View File
@@ -4,12 +4,14 @@ use bevy::{prelude::*, render::mesh::skinning::SkinnedMesh};
use bevy_mod_outline::{
AutoGenerateOutlineNormalsPlugin, OutlineBundle, OutlinePlugin, OutlineVolume,
};
use bevy_mod_picking::{prelude::Pointer, selection::Select, PickableBundle};
use bevy_mod_picking::PickableBundle;
use bevy_mod_raycast::prelude::{Raycast, RaycastSettings};
use space_editor::prelude::space_undo::{self, NewChange, RemovedEntity};
use crate::{
input::{self, Action, InputAction, Lifecycle},
input::{self, Lifecycle, SelectionAction},
scene::{SceneElement, SceneSpawnerSystem, SceneState},
MainCamera,
};
pub struct SelectionPlugin;
@@ -122,29 +124,62 @@ fn tag_scene_elements(
fn update_selection(
mut cmd: Commands,
// FIXME: These events aren't aware of our input actions, so we receive a
// selection event when LMB is released after orbiting the camera.
// Should drop bevy_mod_picking's Selection plugin/feature and just
// use our own conditional click listener.
mut ev_select: EventReader<Pointer<Select>>,
mut ev_input: EventReader<InputAction>,
mut er_selection: EventReader<SelectionAction>,
mut raycast: Raycast,
q_parents: Query<&Parent>,
q_selectable: Query<&Selectable>,
q_selectables: Query<&Selectable>,
q_pickables: Query<&bevy_mod_picking::prelude::Pickable>,
q_camera: Query<(&Camera, &GlobalTransform), With<MainCamera>>,
) {
if let Some(event) = ev_select.read().last() {
// Find the nearest Selectable parent of the target entity
if let Some(ent) = nearest_selectable_parent(event.target, &q_parents, &q_selectable) {
cmd.insert_resource(Selection(ent));
} else {
cmd.remove_resource::<Selection>();
for SelectionAction(lifecycle, action) in er_selection.read() {
match (lifecycle, action) {
(_, input::Selection::Clear) => {
cmd.remove_resource::<Selection>();
}
(_, input::Selection::Single(mouse_pos)) | (_, input::Selection::Multi(mouse_pos)) => {
let is_multi = matches!(action, input::Selection::Multi(_));
// Raycast to find the pickable entity under the cursor
let (cam, cam_xform) = q_camera.single();
let ray = cam.viewport_to_world(cam_xform, *mouse_pos).unwrap();
let hits = raycast.cast_ray(
ray.into(),
&RaycastSettings {
filter: &|ent: Entity| q_pickables.contains(ent),
..default()
},
);
if hits.is_empty() && !is_multi {
cmd.remove_resource::<Selection>();
continue;
}
let (ent, _) = &hits[0];
if let Some(ent) = nearest_selectable_parent(*ent, &q_parents, &q_selectables) {
cmd.insert_resource(Selection(ent));
} else {
cmd.remove_resource::<Selection>();
}
}
(Lifecycle::Ongoing, input::Selection::Marquee(Rect { min, max })) => {
// TODO
// let center = (*max - *min) / 2.;
// let width = max.x - min.x;
// let height = max.y - min.y;
// gizmos.rect_2d(
// center,
// 0.,
// vec2(width, height),
// Color::rgba(1., 1., 0., 0.4),
// );
}
(Lifecycle::Stop, input::Selection::Marquee(Rect { min, max })) => {
// TODO
}
_ => {}
}
} else if ev_input.read().any(|action| {
matches!(
action,
InputAction(Lifecycle::Stop, Action::Selection(input::Selection::Clear))
)
}) {
cmd.remove_resource::<Selection>();
}
}
@@ -171,14 +206,14 @@ pub struct MarkedForDeletion;
fn process_delete_inputs(
mut cmd: Commands,
mut er_input: EventReader<InputAction>,
mut er_selection: EventReader<SelectionAction>,
mut ew_changes: EventWriter<space_undo::NewChange>,
r_selection: Res<Selection>,
) {
if er_input.read().any(|action| {
if er_selection.read().any(|action| {
matches!(
action,
InputAction(Lifecycle::Stop, Action::Selection(input::Selection::Delete))
SelectionAction(Lifecycle::Stop, input::Selection::Delete)
)
}) {
let entity = **r_selection;