M Cargo.lock => Cargo.lock +9 -0
@@ 5449,6 5449,15 @@ dependencies = [
]
[[package]]
+name = "ship_editor"
+version = "0.1.0"
+dependencies = [
+ "bevy",
+ "ctrlc",
+ "getrandom 0.4.2",
+]
+
+[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
A crates/ship_editor/Cargo.toml => crates/ship_editor/Cargo.toml +25 -0
@@ 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"]
A crates/ship_editor/src/input.rs => crates/ship_editor/src/input.rs +81 -0
@@ 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<MouseWheel>,
+ mut camera: Single<(&mut Transform, &mut Projection), With<Camera2d>>,
+ 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 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<ButtonInput<MouseButton>>,
+ mut drag: ResMut<ShipEditorDrag>,
+ camera: Single<&Transform, With<Camera2d>>,
+ 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 drag(
+ drag: ResMut<ShipEditorDrag>,
+ mut camera: Single<(&mut Transform, &Projection), With<Camera2d>>,
+ window: Single<&Window, With<PrimaryWindow>>,
+) {
+ 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;
+}
A crates/ship_editor/src/main.rs => crates/ship_editor/src/main.rs +32 -0
@@ 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<Assets<Mesh>>,
+ mut materials: ResMut<Assets<ColorMaterial>>,
+) {
+ 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)
+ ));
+}
A crates/ship_editor/src/ui.rs => crates/ship_editor/src/ui.rs +23 -0
@@ 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))),
+ )],
+ ));
+}