use bevy::input::mouse::{MouseScrollUnit, MouseWheel};
use bevy::input_focus::InputFocus;
use bevy::prelude::*;
use bevy::window::PrimaryWindow;
use crate::client::components::Me;
use crate::shared::attachment::{JointOf, PartInShip, Peer, SnapOf, SnapOfJoint};
use crate::ship_editor::components::{GhostCamera, GhostModule, MainCamera, Part, GHOST_RENDER_LAYER};
use crate::ship_editor::spawn_joints;
use crate::ship_editor::ui::PartEntry;
pub fn input_plugin(app: &mut App) {
app
.insert_resource(ShipEditorDrag::default())
.add_systems(Update, on_scroll)
.add_systems(Update, on_click)
.add_systems(Update, camera_drag)
.add_systems(Update, ghost_drag)
.add_systems(Update, part_button_interaction);
}
#[derive(Resource, Default)]
struct ShipEditorDrag {
is_dragging: bool,
is_holding_module: bool,
init_cursor_pos: Vec2,
init_camera_pos: Vec2,
}
fn on_scroll(
mut scroll_events: MessageReader<MouseWheel>,
mut main_camera: Single<(&mut Transform, &mut Projection), (With<Camera2d>, With<MainCamera>)>,
mut ghost_camera: Single<(&mut Transform, &mut Projection), (With<Camera2d>, With<GhostCamera>, Without<MainCamera>)>,
window: Single<&Window, With<PrimaryWindow>>,
) {
let Some(cursor_pos) = window.cursor_position() else { return };
let cursor_pos = cursor_pos.with_y(-cursor_pos.y);
let mut main_transform = main_camera.0.clone();
let mut ghost_transform = ghost_camera.0.clone();
let Projection::Orthographic(ref mut main_projection) = *main_camera.1 else { return };
let Projection::Orthographic(ref mut ghost_projection) = *ghost_camera.1 else { return };
for ev in scroll_events.read() {
match ev.unit {
MouseScrollUnit::Line | MouseScrollUnit::Pixel => {
if ev.y > 0.0 {
let mut rel_pos = vec2(window.width() / 2.0, -window.height() / 2.0) - cursor_pos;
rel_pos *= main_projection.scale;
main_projection.scale *= 0.95;
ghost_projection.scale = main_projection.scale;
let scaled_rel_pos = rel_pos * 0.95;
main_transform.translation += scaled_rel_pos.extend(0.0) - rel_pos.extend(0.0);
ghost_transform.translation = main_transform.translation;
} else {
let mut rel_pos = vec2(window.width() / 2.0, -window.height() / 2.0) - cursor_pos;
rel_pos *= main_projection.scale;
main_projection.scale *= 1.05;
ghost_projection.scale = main_projection.scale;
let scaled_rel_pos = rel_pos * 1.05;
main_transform.translation += scaled_rel_pos.extend(0.0) - rel_pos.extend(0.0);
ghost_transform.translation = main_transform.translation;
}
}
}
}
*main_camera.0 = main_transform;
*ghost_camera.0 = ghost_transform;
}
fn on_click(
ev: Res<ButtonInput<MouseButton>>,
mut drag: ResMut<ShipEditorDrag>,
camera: Single<&Transform, (With<Camera2d>, With<MainCamera>)>,
window: Single<&Window, With<PrimaryWindow>>,
) {
let Some(cursor_pos) = window.cursor_position() else { return };
if ev.just_pressed(MouseButton::Left) {
drag.is_dragging = true;
drag.init_cursor_pos = cursor_pos.with_y(-cursor_pos.y);
drag.init_camera_pos = camera.translation.truncate();
}
if ev.just_released(MouseButton::Left) {
drag.is_dragging = false;
}
}
fn camera_drag(
drag: ResMut<ShipEditorDrag>,
mut main_camera: Single<(&mut Transform, &Projection), (With<Camera2d>, With<MainCamera>)>,
mut ghost_camera: Single<(&mut Transform, &Projection), (With<Camera2d>, With<GhostCamera>, Without<MainCamera>)>,
window: Single<&Window, With<PrimaryWindow>>,
) {
if !drag.is_dragging || drag.is_holding_module { return }
let Some(cursor) = window.cursor_position() else { return };
let projection = main_camera.1.clone();
let Projection::Orthographic(projection) = projection else { return };
let main_transform = &mut main_camera.0;
let ghost_transform = &mut ghost_camera.0;
main_transform.translation = drag.init_camera_pos.extend(0.0) - (cursor.with_y(-cursor.y) - drag.init_cursor_pos).extend(0.0)*projection.scale;
ghost_transform.translation = main_transform.translation;
}
fn ghost_drag(
drag: Res<ShipEditorDrag>,
window: Single<&Window, With<PrimaryWindow>>,
mut ghost_module: Single<(Entity, &mut Transform), With<GhostModule>>,
mut main_camera: Single<(&Camera, &GlobalTransform), (With<Camera2d>, With<MainCamera>)>,
mut ghost_camera: Single<(&Camera, &GlobalTransform), (With<Camera2d>, With<GhostCamera>)>,
snaps: Query<(&Transform, &SnapOfJoint, &SnapOf, Entity), Without<GhostModule>>,
joints: Query<(&Transform, &JointOf, Entity), Without<GhostModule>>,
parts: Query<&GlobalTransform, (Or<(With<Part>, With<GhostModule>)>)>,
) {
const SNAP_CUTOFF: f32 = 25.0;
if !drag.is_dragging || !drag.is_holding_module { return }
let (ghost_entity, mut ghost_transform) = ghost_module.into_inner();
let Some(cursor) = window.cursor_position() else { return };
let (ghost_camera, ghost_camera_transform) = *ghost_camera;
let (main_camera, main_camera_transform) = *main_camera;
let Ok(ghost_position) = ghost_camera.viewport_to_world_2d(ghost_camera_transform, cursor) else {
error!("Unable to convert cursor position to world space");
return // return because this error happens when something is invalid with the camera
};
let Ok(global_cursor_position) = ghost_camera.viewport_to_world_2d(main_camera_transform, cursor) else {
error!("Unable to convert cursor position to world space");
return
};
let mut closest_snap = f32::INFINITY;
let mut closest_snap_transform = None;
for (snap_local_transform, snap_joint, snap_part, snap_id) in snaps {
if snap_part.0 == ghost_entity { continue }
let Ok(part_transform) = parts.get(snap_part.0) else { continue };
let snap_global_translation = part_transform.mul_transform(*snap_local_transform);
let distance_to_cursor = global_cursor_position.distance(snap_global_translation.translation().xy());
if distance_to_cursor > closest_snap {
continue;
}
if distance_to_cursor > SNAP_CUTOFF {
continue;
}
let Ok((offset, _, _)) = joints.get(snap_joint.0) else {
continue;
};
let global_joint_offset = part_transform.transform_point(offset.translation);
let mut target_transform = Transform {
translation: global_joint_offset,
rotation: ghost_transform.rotation,
scale: Vec3::splat(1.0),
};
// figure out the ghost's best snap to use
let mut closest_peer_snap_distance = f32::INFINITY;
let mut closest_peer_snap = None;
let mut closest_joint_transform = None;
for (our_snap_local_transform, our_snap_joint, our_snap_part, our_snap_id) in &snaps {
if ghost_entity != our_snap_part.0 {
continue;
}
let our_snap_global_translation =
target_transform.mul_transform(*our_snap_local_transform);
let distance = our_snap_global_translation
.translation
.distance(snap_global_translation.translation());
if distance > closest_peer_snap_distance {
continue;
}
let Ok((our_joint, _, _)) = joints.get(our_snap_joint.0) else {
continue;
};
closest_peer_snap_distance = distance;
closest_peer_snap = Some(our_snap_id);
closest_joint_transform = Some(our_joint);
}
target_transform.rotation = part_transform.rotation()
* (offset.rotation * closest_joint_transform.unwrap().rotation.inverse())
* Quat::from_rotation_z(180.0f32.to_radians());
closest_snap = distance_to_cursor;
closest_snap_transform = Some(target_transform);
}
if let Some(transform) = closest_snap_transform {
*ghost_transform = transform;
} else {
ghost_transform.translation = ghost_position.extend(0.0);
}
}
fn part_button_interaction(
mouse_ev: Res<ButtonInput<MouseButton>>,
part_button_query: Query<(Entity, &PartEntry, &Interaction), (With<Button>, Changed<Interaction>)>,
ghost_part_query: Query<Entity, With<GhostModule>>,
mut drag: ResMut<ShipEditorDrag>,
mut input_focus: ResMut<InputFocus>,
mut commands: Commands,
mut ghost_camera: Single<(&Camera, &GlobalTransform), (With<Camera2d>, With<GhostCamera>)>,
asset_server: Res<AssetServer>,
window: Single<&Window, With<PrimaryWindow>>,
) {
let Some(cursor) = window.cursor_position() else { return };
let (ghost_camera, camera_transform) = *ghost_camera;
for (entity, part_entry, interaction) in part_button_query.iter() {
match interaction {
Interaction::None => {
if !mouse_ev.pressed(MouseButton::Left) {
drag.is_holding_module = false;
for ghost_entity in ghost_part_query.iter() {
commands.entity(ghost_entity).despawn();
}
}
}
Interaction::Hovered => {
if !mouse_ev.pressed(MouseButton::Left) {
drag.is_holding_module = false;
for ghost_entity in ghost_part_query.iter() {
commands.entity(ghost_entity).despawn();
}
}
}
Interaction::Pressed => {
input_focus.set(entity);
drag.is_holding_module = true;
debug!("pressed {:?}", part_entry.0.part.name);
let mut sprite = Sprite::from_image(asset_server.load(part_entry.0.part.sprite_disconnected.clone()));
sprite.custom_size = Some(vec2(part_entry.0.physics.width as f32, part_entry.0.physics.height as f32));
sprite.color = Color::srgb(0.7, 0.7, 0.7);
let Ok(ghost_position) = ghost_camera.viewport_to_world_2d(camera_transform, cursor) else {
error!("Unable to convert cursor position to world space");
return // return because this error happens when something is invalid with the camera
};
let entity = commands.spawn((
sprite,
Transform::from_translation(ghost_position.extend(0.0)),
GhostModule,
GHOST_RENDER_LAYER,
));
spawn_joints(&part_entry.0, entity.id(), commands.reborrow());
}
}
}
}