From 6aca948465e18411e666e465693deb03c97f6fd6 Mon Sep 17 00:00:00 2001 From: ghostly_zsh Date: Thu, 11 Jun 2026 00:27:03 -0500 Subject: [PATCH] start ship editor, added mouse navigation --- Cargo.lock | 9 ++++ crates/ship_editor/Cargo.toml | 25 ++++++++++ crates/ship_editor/src/input.rs | 81 +++++++++++++++++++++++++++++++++ crates/ship_editor/src/main.rs | 32 +++++++++++++ crates/ship_editor/src/ui.rs | 23 ++++++++++ 5 files changed, 170 insertions(+) create mode 100644 crates/ship_editor/Cargo.toml create mode 100644 crates/ship_editor/src/input.rs create mode 100644 crates/ship_editor/src/main.rs create mode 100644 crates/ship_editor/src/ui.rs diff --git a/Cargo.lock b/Cargo.lock index 764a7a06f01324c77a866d7a36dde30d6e44d249..0e41e08d07f0b4bd8937f29cc5e0f3c8954abad6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5448,6 +5448,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "ship_editor" +version = "0.1.0" +dependencies = [ + "bevy", + "ctrlc", + "getrandom 0.4.2", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/crates/ship_editor/Cargo.toml b/crates/ship_editor/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..406d5f772acfcea75f2376f9ecebfce9011b9e34 --- /dev/null +++ b/crates/ship_editor/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "ship_editor" +version = "0.1.0" +edition = "2024" + +[dependencies] +bevy = { workspace = true } +getrandom = { workspace = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +ctrlc = { workspace = true, optional = true } + +[features] +native_dev = [ + "bevy/file_watcher", + "bevy/hotpatching", + "bevy/dynamic_linking", + "native", +] +native = [ + "bevy/x11", + "bevy/wayland", + "dep:ctrlc" +] +wasm = ["getrandom/wasm_js", "bevy/webgl2"] diff --git a/crates/ship_editor/src/input.rs b/crates/ship_editor/src/input.rs new file mode 100644 index 0000000000000000000000000000000000000000..a793d307245aa286b1936cc6f856323e68640b0b --- /dev/null +++ b/crates/ship_editor/src/input.rs @@ -0,0 +1,81 @@ +use bevy::input::mouse::{MouseScrollUnit, MouseWheel}; +use bevy::prelude::*; +use bevy::window::PrimaryWindow; + +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, drag); +} + +#[derive(Resource, Default)] +struct ShipEditorDrag { + is_dragging: bool, + init_cursor_pos: Vec2, + init_camera_pos: Vec2, +} + +fn on_scroll( + mut scroll_events: MessageReader, + mut camera: Single<(&mut Transform, &mut Projection), With>, + window: Single<&Window, With>, +) { + let Some(cursor_pos) = window.cursor_position() else { return }; + let cursor_pos = cursor_pos.with_y(-cursor_pos.y); + + let mut transform = camera.0.clone(); + let Projection::Orthographic(ref mut projection) = *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 *= projection.scale; + projection.scale *= 0.95; + let scaled_rel_pos = rel_pos * 0.95; + transform.translation += scaled_rel_pos.extend(0.0) - rel_pos.extend(0.0); + } else { + let mut rel_pos = vec2(window.width() / 2.0, -window.height() / 2.0) - cursor_pos; + rel_pos *= projection.scale; + projection.scale *= 1.05; + let scaled_rel_pos = rel_pos * 1.05; + transform.translation += scaled_rel_pos.extend(0.0) - rel_pos.extend(0.0); + } + } + } + } + *camera.0 = transform; +} + +fn on_click( + ev: Res>, + mut drag: ResMut, + camera: Single<&Transform, With>, + window: Single<&Window, With>, +) { + 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 drag( + drag: ResMut, + mut camera: Single<(&mut Transform, &Projection), With>, + window: Single<&Window, With>, +) { + if !drag.is_dragging { return } + let Some(cursor) = window.cursor_position() else { return }; + let projection = camera.1.clone(); + let Projection::Orthographic(projection) = projection else { return }; + let transform = &mut camera.0; + + transform.translation = drag.init_camera_pos.extend(0.0) - (cursor.with_y(-cursor.y) - drag.init_cursor_pos).extend(0.0)*projection.scale; +} diff --git a/crates/ship_editor/src/main.rs b/crates/ship_editor/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..b315e2ecfbc5615aa90ca05b5a41a8006d51a262 --- /dev/null +++ b/crates/ship_editor/src/main.rs @@ -0,0 +1,32 @@ +use bevy::{prelude::*}; +use crate::input::input_plugin; +use crate::ui::ui_plugin; + +pub mod ui; +mod input; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_plugins(ui_plugin) + .add_plugins(input_plugin) + .run(); +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands.spawn(( + Camera2d::default(), + Transform::from_xyz(0.0, 0.0, 0.0), + )); + let rectangle = meshes.add(Rectangle::new(50.0, 50.0)); + commands.spawn(( + Mesh2d(rectangle), + MeshMaterial2d(materials.add(Color::linear_rgb(0.0, 0.0, 0.0))), + Transform::from_xyz(0.0, 0.0, 0.0) + )); +} diff --git a/crates/ship_editor/src/ui.rs b/crates/ship_editor/src/ui.rs new file mode 100644 index 0000000000000000000000000000000000000000..c4713be02020165754234243f90247a32fd498eb --- /dev/null +++ b/crates/ship_editor/src/ui.rs @@ -0,0 +1,23 @@ +use bevy::prelude::*; + +pub fn ui_plugin(app: &mut App) { + app.add_systems(Startup, setup_ui); +} + +fn setup_ui(mut commands: Commands) { + commands.spawn(( + Node { + width: Val::Percent(100.), + height: Val::Percent(100.), + ..Default::default() + }, + children![( + Node { + width: Val::Px(32.0), + height: Val::Px(32.0), + ..Default::default() + }, + BackgroundColor(Color::LinearRgba(LinearRgba::rgb(1.0, 1.0, 1.0))), + )], + )); +}