mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-29 09:08:58 +00:00
Replace bevy_mod_picking selection
This commit is contained in:
@@ -83,5 +83,4 @@ features = [
|
|||||||
"backend_raycast",
|
"backend_raycast",
|
||||||
"backend_sprite",
|
"backend_sprite",
|
||||||
"debug",
|
"debug",
|
||||||
"selection",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use bevy::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
input::{self, Action, CameraMotion, InputAction, Lifecycle},
|
input::{self, CameraAction, CameraMotion, Lifecycle, SelectionAction},
|
||||||
interaction::Selection,
|
interaction::Selection,
|
||||||
math::{
|
math::{
|
||||||
Easing, InterpPosition, InterpVelocity, Interpolation, InterpolationSystem, NearlyEq,
|
Easing, InterpPosition, InterpVelocity, Interpolation, InterpolationSystem, NearlyEq,
|
||||||
@@ -71,10 +71,10 @@ impl CameraControllerBundle {
|
|||||||
|
|
||||||
fn camera_control_kinematic(
|
fn camera_control_kinematic(
|
||||||
mut q_window: Query<&mut Window, With<PrimaryWindow>>,
|
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)>,
|
mut q_camera: Query<(&mut CameraController, &mut Transform, &Projection)>,
|
||||||
) {
|
) {
|
||||||
if input.is_empty() {
|
if er_camera.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,30 +82,28 @@ fn camera_control_kinematic(
|
|||||||
let mut any_started = false;
|
let mut any_started = false;
|
||||||
let mut any_stopped = false;
|
let mut any_stopped = false;
|
||||||
|
|
||||||
for action in input.read().filter(|action| {
|
for action in er_camera
|
||||||
!matches!(
|
.read()
|
||||||
action,
|
.filter(|action| !matches!(action, CameraAction(_, CameraMotion::Translate(_))))
|
||||||
InputAction(_, Action::Camera(CameraMotion::Translate(_)))
|
{
|
||||||
)
|
let CameraAction(lifecycle, motion) = action;
|
||||||
}) {
|
let (mut controller, mut xform, proj) = q_camera.single_mut();
|
||||||
if let InputAction(lifecycle, Action::Camera(motion)) = action {
|
let window = q_window.single();
|
||||||
let (mut controller, mut xform, proj) = q_camera.single_mut();
|
|
||||||
let window = q_window.single();
|
|
||||||
|
|
||||||
match lifecycle {
|
match lifecycle {
|
||||||
Lifecycle::Start => any_started = true,
|
Lifecycle::Start => any_started = true,
|
||||||
Lifecycle::Ongoing => any_ongoing = true,
|
Lifecycle::Ongoing => any_ongoing = true,
|
||||||
Lifecycle::Stop => any_stopped = true,
|
Lifecycle::Stop => any_stopped = true,
|
||||||
}
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
use CameraMotion::*;
|
use CameraMotion::*;
|
||||||
match motion {
|
match motion {
|
||||||
Pan(delta) => do_pan(proj, window, *delta, &mut xform, &mut controller),
|
Pan(delta) => do_pan(proj, window, *delta, &mut xform, &mut controller),
|
||||||
Zoom(delta) => do_zoom(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),
|
Orbit(delta) => do_orbit(window, *delta, *lifecycle, &mut xform, &mut controller),
|
||||||
Rotate(delta) => do_rotate(window, *delta, &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(
|
fn process_physical_inputs(
|
||||||
mut cmd: Commands,
|
mut cmd: Commands,
|
||||||
mut input: EventReader<InputAction>,
|
mut er_camera: EventReader<CameraAction>,
|
||||||
q_camera: Query<(Entity, &Velocity), With<CameraController>>,
|
q_camera: Query<(Entity, &Velocity), With<CameraController>>,
|
||||||
mut q_interp: Query<&mut InterpVelocity>,
|
mut q_interp: Query<&mut InterpVelocity>,
|
||||||
) {
|
) {
|
||||||
for action in input.read() {
|
for action in er_camera.read() {
|
||||||
let &InputAction(_, Action::Camera(CameraMotion::Translate(delta))) = action else {
|
let &CameraAction(_, CameraMotion::Translate(delta)) = action else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -156,7 +154,7 @@ fn process_physical_inputs(
|
|||||||
fn process_focus_inputs(
|
fn process_focus_inputs(
|
||||||
mut cmd: Commands,
|
mut cmd: Commands,
|
||||||
r_selection: Option<Res<Selection>>,
|
r_selection: Option<Res<Selection>>,
|
||||||
mut input: EventReader<InputAction>,
|
mut er_selection: EventReader<SelectionAction>,
|
||||||
mut q_camera: Query<(Entity, &CameraController, &mut Velocity)>,
|
mut q_camera: Query<(Entity, &CameraController, &mut Velocity)>,
|
||||||
q_interp_vel: Query<&InterpVelocity>,
|
q_interp_vel: Query<&InterpVelocity>,
|
||||||
mut q_interp_pos: Query<&mut InterpPosition>,
|
mut q_interp_pos: Query<&mut InterpPosition>,
|
||||||
@@ -166,10 +164,10 @@ fn process_focus_inputs(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if input.read().any(|action| {
|
if er_selection.read().any(|action| {
|
||||||
matches!(
|
matches!(
|
||||||
action,
|
action,
|
||||||
InputAction(Lifecycle::Stop, Action::Selection(input::Selection::Focus))
|
SelectionAction(Lifecycle::Stop, input::Selection::Focus)
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
let Ok(target) = q_global_xforms
|
let Ok(target) = q_global_xforms
|
||||||
|
|||||||
+8
-6
@@ -15,7 +15,7 @@ use space_editor::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
anim::SkeletalJoint,
|
anim::SkeletalJoint,
|
||||||
camera_controller::{CameraController, CameraMotionSystem},
|
camera_controller::{CameraController, CameraMotionSystem},
|
||||||
input::{Action, InputAction, Lifecycle},
|
input::{self, EditorAction, Lifecycle},
|
||||||
is_editor, MainCamera,
|
is_editor, MainCamera,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,11 +83,13 @@ impl Plugin for DebugPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_debug_mode(mut input: EventReader<InputAction>, mut r_debug: ResMut<DebugMode>) {
|
fn toggle_debug_mode(mut er_editor: EventReader<EditorAction>, mut r_debug: ResMut<DebugMode>) {
|
||||||
if input
|
if er_editor.read().any(|action| {
|
||||||
.read()
|
matches!(
|
||||||
.any(|action| matches!(action, InputAction(Lifecycle::Stop, Action::ToggleDebug)))
|
action,
|
||||||
{
|
EditorAction(Lifecycle::Stop, input::Editor::ToggleDebug)
|
||||||
|
)
|
||||||
|
}) {
|
||||||
**r_debug = !**r_debug;
|
**r_debug = !**r_debug;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+267
-125
@@ -2,18 +2,30 @@ use bevy::{
|
|||||||
input::mouse::{MouseMotion, MouseWheel},
|
input::mouse::{MouseMotion, MouseWheel},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
utils::HashSet,
|
utils::HashSet,
|
||||||
|
window::PrimaryWindow,
|
||||||
};
|
};
|
||||||
use space_editor::prelude::space_undo::UndoRedo;
|
use space_editor::prelude::space_undo::UndoRedo;
|
||||||
|
|
||||||
use crate::is_editor;
|
use crate::{is_editor, math::NearlyZero};
|
||||||
|
|
||||||
#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
|
#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct StudioInputSystem;
|
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;
|
pub struct StudioInputPlugin;
|
||||||
impl Plugin for StudioInputPlugin {
|
impl Plugin for StudioInputPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
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
|
// Reading each frame of the mouse delta allows
|
||||||
// to get rid of the bug that delta is counted from the last reading,
|
// 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(
|
app.add_systems(
|
||||||
PreUpdate,
|
PreUpdate,
|
||||||
(
|
(translate_dcc_camera_events, translate_fps_camera_events)
|
||||||
translate_selection_events,
|
|
||||||
translate_debug_toggle,
|
|
||||||
translate_dcc_camera_events,
|
|
||||||
translate_fps_camera_events,
|
|
||||||
send_undo_event.run_if(is_editor),
|
|
||||||
)
|
|
||||||
.in_set(StudioInputSystem)
|
.in_set(StudioInputSystem)
|
||||||
|
.in_set(CameraInputSystem)
|
||||||
.after(bevy::input::InputSystem)
|
.after(bevy::input::InputSystem)
|
||||||
.after(read_mouse_delta),
|
.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(
|
app.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(apply_deferred, log_input_actions).after(StudioInputSystem),
|
(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
|
// #[derive(Event, Clone, Copy, Debug)]
|
||||||
/// were fired, with `debug` verbosity.
|
// pub struct InputAction(pub Lifecycle, pub Action);
|
||||||
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(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Lifecycle {
|
pub enum Lifecycle {
|
||||||
Start,
|
Start,
|
||||||
Ongoing,
|
Ongoing,
|
||||||
Stop,
|
Stop,
|
||||||
|
Cancel,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
// #[derive(Clone, Copy, Debug)]
|
||||||
pub enum Action {
|
// pub enum Action {
|
||||||
Camera(CameraMotion),
|
// Camera(CameraMotion),
|
||||||
Selection(Selection),
|
// Selection(Selection),
|
||||||
ToggleDebug,
|
// ToggleDebug,
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
#[derive(Event, Clone, Copy, Debug)]
|
||||||
|
pub struct CameraAction(pub Lifecycle, pub CameraMotion);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum CameraMotion {
|
pub enum CameraMotion {
|
||||||
@@ -128,11 +102,9 @@ pub enum CameraMotion {
|
|||||||
Orbit(Vec2),
|
Orbit(Vec2),
|
||||||
Rotate(Vec2),
|
Rotate(Vec2),
|
||||||
}
|
}
|
||||||
impl From<CameraMotion> for Action {
|
|
||||||
fn from(value: CameraMotion) -> Self {
|
#[derive(Event, Clone, Copy, Debug)]
|
||||||
Action::Camera(value)
|
pub struct SelectionAction(pub Lifecycle, pub Selection);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Selection {
|
pub enum Selection {
|
||||||
@@ -142,41 +114,137 @@ pub enum Selection {
|
|||||||
Focus,
|
Focus,
|
||||||
/// Delete the currently selected entity and its descendants
|
/// Delete the currently selected entity and its descendants
|
||||||
Delete,
|
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 {
|
#[derive(Event, Clone, Copy, Debug)]
|
||||||
Action::Selection(value)
|
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
|
// TODO: Make the bindings of raw device inputs to InputActions configurable
|
||||||
|
|
||||||
fn translate_selection_events(keyboard: Res<Input<KeyCode>>, mut input: EventWriter<InputAction>) {
|
fn translate_selection_events(
|
||||||
if keyboard.just_pressed(KeyCode::Escape) {
|
r_mouse_motion: Res<LastFrameMouseDelta>,
|
||||||
input.send(InputAction(Lifecycle::Start, Selection::Clear.into()));
|
ri_mouse_buttons: Res<Input<MouseButton>>,
|
||||||
} else if keyboard.just_released(KeyCode::Escape) {
|
ri_keyboard: Res<Input<KeyCode>>,
|
||||||
input.send(InputAction(Lifecycle::Stop, Selection::Clear.into()));
|
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) {
|
if ri_keyboard.just_pressed(KeyCode::F) {
|
||||||
input.send(InputAction(Lifecycle::Start, Selection::Focus.into()));
|
ew_actions.send(SelectionAction(Lifecycle::Start, Selection::Focus));
|
||||||
} else if keyboard.just_released(KeyCode::F) {
|
} else if ri_keyboard.just_released(KeyCode::F) {
|
||||||
input.send(InputAction(Lifecycle::Stop, Selection::Focus.into()));
|
ew_actions.send(SelectionAction(Lifecycle::Stop, Selection::Focus));
|
||||||
}
|
}
|
||||||
|
|
||||||
if keyboard.just_pressed(KeyCode::Delete) {
|
if ri_keyboard.just_pressed(KeyCode::Delete) {
|
||||||
input.send(InputAction(Lifecycle::Start, Selection::Delete.into()));
|
ew_actions.send(SelectionAction(Lifecycle::Start, Selection::Delete));
|
||||||
} else if keyboard.just_released(KeyCode::Delete) {
|
} else if ri_keyboard.just_released(KeyCode::Delete) {
|
||||||
input.send(InputAction(Lifecycle::Stop, Selection::Delete.into()));
|
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.pressed(KeyCode::ControlLeft) || keyboard.pressed(KeyCode::ControlRight) {
|
||||||
if keyboard.just_pressed(KeyCode::Grave) {
|
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) {
|
} 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_motion: Res<LastFrameMouseDelta>,
|
||||||
mouse_buttons: Res<Input<MouseButton>>,
|
mouse_buttons: Res<Input<MouseButton>>,
|
||||||
keyboard: Res<Input<KeyCode>>,
|
keyboard: Res<Input<KeyCode>>,
|
||||||
mut input: EventWriter<InputAction>,
|
mut input: EventWriter<CameraAction>,
|
||||||
) {
|
) {
|
||||||
let delta = mouse_motion.get();
|
let delta = mouse_motion.get();
|
||||||
// Camera Pan
|
// Camera Pan
|
||||||
@@ -195,14 +263,11 @@ fn translate_dcc_camera_events(
|
|||||||
} else {
|
} else {
|
||||||
Lifecycle::Ongoing
|
Lifecycle::Ongoing
|
||||||
};
|
};
|
||||||
input.send(InputAction(lifecycle, CameraMotion::Pan(delta).into()));
|
input.send(CameraAction(lifecycle, CameraMotion::Pan(delta)));
|
||||||
}
|
}
|
||||||
// Stop Camera Panning
|
// Stop Camera Panning
|
||||||
else if mouse_buttons.just_released(MouseButton::Middle) {
|
else if mouse_buttons.just_released(MouseButton::Middle) {
|
||||||
input.send(InputAction(
|
input.send(CameraAction(Lifecycle::Stop, CameraMotion::Pan(Vec2::ZERO)));
|
||||||
Lifecycle::Stop,
|
|
||||||
CameraMotion::Pan(Vec2::ZERO).into(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
// Camera Orbit/Zoom
|
// Camera Orbit/Zoom
|
||||||
else if keyboard.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]) {
|
else if keyboard.any_pressed([KeyCode::AltLeft, KeyCode::AltRight]) {
|
||||||
@@ -220,18 +285,18 @@ fn translate_dcc_camera_events(
|
|||||||
CameraMotion::Zoom(delta.y)
|
CameraMotion::Zoom(delta.y)
|
||||||
};
|
};
|
||||||
|
|
||||||
input.send(InputAction(lifecycle, motion.into()));
|
input.send(CameraAction(lifecycle, motion));
|
||||||
}
|
}
|
||||||
// Stop Camera Orbit
|
// Stop Camera Orbit
|
||||||
else if mouse_buttons.just_released(MouseButton::Left) {
|
else if mouse_buttons.just_released(MouseButton::Left) {
|
||||||
input.send(InputAction(
|
input.send(CameraAction(
|
||||||
Lifecycle::Stop,
|
Lifecycle::Stop,
|
||||||
CameraMotion::Orbit(Vec2::ZERO).into(),
|
CameraMotion::Orbit(Vec2::ZERO),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// Stop Camera Zoom
|
// Stop Camera Zoom
|
||||||
else if mouse_buttons.just_released(MouseButton::Right) {
|
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
|
// Stop Camera Orbit & Zoom
|
||||||
@@ -240,14 +305,14 @@ fn translate_dcc_camera_events(
|
|||||||
{
|
{
|
||||||
// Stop Camera Orbit
|
// Stop Camera Orbit
|
||||||
if mouse_buttons.pressed(MouseButton::Left) {
|
if mouse_buttons.pressed(MouseButton::Left) {
|
||||||
input.send(InputAction(
|
input.send(CameraAction(
|
||||||
Lifecycle::Stop,
|
Lifecycle::Stop,
|
||||||
CameraMotion::Orbit(Vec2::ZERO).into(),
|
CameraMotion::Orbit(Vec2::ZERO),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// Stop Camera Zoom
|
// Stop Camera Zoom
|
||||||
else if mouse_buttons.pressed(MouseButton::Right) {
|
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
|
// Camera Rotation
|
||||||
@@ -258,13 +323,13 @@ fn translate_dcc_camera_events(
|
|||||||
Lifecycle::Ongoing
|
Lifecycle::Ongoing
|
||||||
};
|
};
|
||||||
|
|
||||||
input.send(InputAction(lifecycle, CameraMotion::Rotate(delta).into()));
|
input.send(CameraAction(lifecycle, CameraMotion::Rotate(delta)));
|
||||||
}
|
}
|
||||||
// Stop Camera Rotation
|
// Stop Camera Rotation
|
||||||
else if mouse_buttons.just_released(MouseButton::Right) {
|
else if mouse_buttons.just_released(MouseButton::Right) {
|
||||||
input.send(InputAction(
|
input.send(CameraAction(
|
||||||
Lifecycle::Stop,
|
Lifecycle::Stop,
|
||||||
CameraMotion::Rotate(Vec2::ZERO).into(),
|
CameraMotion::Rotate(Vec2::ZERO),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// Alternative Camera Rotation (designed for laptop)
|
// Alternative Camera Rotation (designed for laptop)
|
||||||
@@ -275,13 +340,13 @@ fn translate_dcc_camera_events(
|
|||||||
Lifecycle::Ongoing
|
Lifecycle::Ongoing
|
||||||
};
|
};
|
||||||
|
|
||||||
input.send(InputAction(lifecycle, CameraMotion::Rotate(delta).into()));
|
input.send(CameraAction(lifecycle, CameraMotion::Rotate(delta)));
|
||||||
}
|
}
|
||||||
// Stop Camera Rotation
|
// Stop Camera Rotation
|
||||||
else if keyboard.just_released(KeyCode::ShiftLeft) {
|
else if keyboard.just_released(KeyCode::ShiftLeft) {
|
||||||
input.send(InputAction(
|
input.send(CameraAction(
|
||||||
Lifecycle::Stop,
|
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(
|
fn translate_fps_camera_events(
|
||||||
mut mouse_wheel: EventReader<MouseWheel>,
|
mut mouse_wheel: EventReader<MouseWheel>,
|
||||||
keyboard: Res<Input<KeyCode>>,
|
keyboard: Res<Input<KeyCode>>,
|
||||||
mut input: EventWriter<InputAction>,
|
mut input: EventWriter<CameraAction>,
|
||||||
) {
|
) {
|
||||||
// Camera Translation (FPS-like)
|
// Camera Translation (FPS-like)
|
||||||
use KeyCode::*;
|
use KeyCode::*;
|
||||||
@@ -330,13 +395,13 @@ fn translate_fps_camera_events(
|
|||||||
Lifecycle::Ongoing
|
Lifecycle::Ongoing
|
||||||
};
|
};
|
||||||
|
|
||||||
input.send(InputAction(lifecycle, CameraMotion::Translate(mvmt).into()));
|
input.send(CameraAction(lifecycle, CameraMotion::Translate(mvmt)));
|
||||||
}
|
}
|
||||||
// Stop Translation
|
// Stop Translation
|
||||||
else if keyboard.any_just_released(wasd_keys) {
|
else if keyboard.any_just_released(wasd_keys) {
|
||||||
input.send(InputAction(
|
input.send(CameraAction(
|
||||||
Lifecycle::Stop,
|
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.;
|
// 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) {
|
// if (rem_x.is_nan() || rem_x < 1e-5_f32) && (rem_y.is_nan() || rem_y < 1e-5_f32) {
|
||||||
input.send(InputAction(
|
input.send(CameraAction(Lifecycle::Ongoing, CameraMotion::Zoom(y)));
|
||||||
Lifecycle::Ongoing,
|
|
||||||
CameraMotion::Zoom(y).into(),
|
|
||||||
));
|
|
||||||
// } else {
|
// } else {
|
||||||
// FIXME: I had high hopes for this, but the input translation from
|
// FIXME: I had high hopes for this, but the input translation from
|
||||||
// Bevy / winit is herky-jerky-janky af. It seems to be
|
// 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");
|
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::{
|
use bevy_mod_outline::{
|
||||||
AutoGenerateOutlineNormalsPlugin, OutlineBundle, OutlinePlugin, OutlineVolume,
|
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 space_editor::prelude::space_undo::{self, NewChange, RemovedEntity};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
input::{self, Action, InputAction, Lifecycle},
|
input::{self, Lifecycle, SelectionAction},
|
||||||
scene::{SceneElement, SceneSpawnerSystem, SceneState},
|
scene::{SceneElement, SceneSpawnerSystem, SceneState},
|
||||||
|
MainCamera,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SelectionPlugin;
|
pub struct SelectionPlugin;
|
||||||
@@ -122,29 +124,62 @@ fn tag_scene_elements(
|
|||||||
|
|
||||||
fn update_selection(
|
fn update_selection(
|
||||||
mut cmd: Commands,
|
mut cmd: Commands,
|
||||||
// FIXME: These events aren't aware of our input actions, so we receive a
|
mut er_selection: EventReader<SelectionAction>,
|
||||||
// selection event when LMB is released after orbiting the camera.
|
mut raycast: Raycast,
|
||||||
// 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>,
|
|
||||||
q_parents: Query<&Parent>,
|
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() {
|
for SelectionAction(lifecycle, action) in er_selection.read() {
|
||||||
// Find the nearest Selectable parent of the target entity
|
match (lifecycle, action) {
|
||||||
if let Some(ent) = nearest_selectable_parent(event.target, &q_parents, &q_selectable) {
|
(_, input::Selection::Clear) => {
|
||||||
cmd.insert_resource(Selection(ent));
|
cmd.remove_resource::<Selection>();
|
||||||
} else {
|
}
|
||||||
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(
|
fn process_delete_inputs(
|
||||||
mut cmd: Commands,
|
mut cmd: Commands,
|
||||||
mut er_input: EventReader<InputAction>,
|
mut er_selection: EventReader<SelectionAction>,
|
||||||
mut ew_changes: EventWriter<space_undo::NewChange>,
|
mut ew_changes: EventWriter<space_undo::NewChange>,
|
||||||
r_selection: Res<Selection>,
|
r_selection: Res<Selection>,
|
||||||
) {
|
) {
|
||||||
if er_input.read().any(|action| {
|
if er_selection.read().any(|action| {
|
||||||
matches!(
|
matches!(
|
||||||
action,
|
action,
|
||||||
InputAction(Lifecycle::Stop, Action::Selection(input::Selection::Delete))
|
SelectionAction(Lifecycle::Stop, input::Selection::Delete)
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
let entity = **r_selection;
|
let entity = **r_selection;
|
||||||
|
|||||||
Reference in New Issue
Block a user