mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-16 00:03:29 +00:00
Replace bevy_mod_picking selection
This commit is contained in:
@@ -83,5 +83,4 @@ features = [
|
||||
"backend_raycast",
|
||||
"backend_sprite",
|
||||
"debug",
|
||||
"selection",
|
||||
]
|
||||
|
||||
@@ -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
@@ -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
@@ -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?}]"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user