From bce361548234edf9f6e96635bd4d6cadc3f75e8c Mon Sep 17 00:00:00 2001 From: Extermind Date: Fri, 10 Apr 2026 22:32:46 +0200 Subject: [PATCH 1/2] fix(gizmos): scale child positions on viewport resize + add messages crate When scaling TransformGizmo for viewport size changes, arrow tips and plane handles were not moving proportionally because local translations remained fixed while only GlobalTransform was overridden. Solution manually scales each child's translation vector by viewport_scale when computing final GlobalTransform via gizmo_pos.mul_transform(). Added `bevy_transform_gizmos` crate with `ViewportSize` type and `ViewportResized` message for standardized event-driven viewport management across editor systems. Fixes #253 --- Cargo.toml | 3 +++ bevy_editor_panes/bevy_3d_viewport/Cargo.toml | 1 + bevy_editor_panes/bevy_3d_viewport/src/lib.rs | 10 +++++-- crates/bevy_events/Cargo.toml | 11 ++++++++ crates/bevy_events/src/lib.rs | 16 ++++++++++++ crates/bevy_transform_gizmos/Cargo.toml | 1 + crates/bevy_transform_gizmos/src/lib.rs | 26 ++++++++++++++++++- 7 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 crates/bevy_events/Cargo.toml create mode 100644 crates/bevy_events/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index db72a0e1..a83e8fb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,3 +80,6 @@ bevy_undo = { path = "crates/bevy_undo" } bevy_infinite_grid = { path = "crates/bevy_infinite_grid" } bevy_editor_cam = { path = "crates/bevy_editor_cam" } bevy_clipboard = { path = "crates/bevy_clipboard" } + +# editor events/messages +bevy_events = { path = "crates/bevy_events" } diff --git a/bevy_editor_panes/bevy_3d_viewport/Cargo.toml b/bevy_editor_panes/bevy_3d_viewport/Cargo.toml index 6ad25fb1..f47c7782 100644 --- a/bevy_editor_panes/bevy_3d_viewport/Cargo.toml +++ b/bevy_editor_panes/bevy_3d_viewport/Cargo.toml @@ -11,6 +11,7 @@ bevy_editor_styles.workspace = true bevy_infinite_grid.workspace = true bevy_editor_core.workspace = true bevy_transform_gizmos.workspace = true +bevy_events.workspace = true [lints] workspace = true diff --git a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs index 702df596..1d09c116 100644 --- a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs +++ b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs @@ -21,7 +21,7 @@ use bevy_transform_gizmos::{TransformGizmo, prelude::*}; use view_gizmo::ViewGizmoPlugin; use crate::{selection_box::SelectionBoxPlugin, view_gizmo::view_gizmo_node}; - +use bevy_events::ViewportResized; mod selection_box; mod view_gizmo; @@ -50,6 +50,7 @@ impl Plugin for Viewport3dPanePlugin { } app.add_plugins((DefaultEditorCamPlugins, ViewGizmoPlugin, SelectionBoxPlugin)) + .add_message::() .add_systems(Startup, setup) .add_systems( First, @@ -231,6 +232,7 @@ fn update_render_target_size( bodies: Query<&PaneContentNode>, children_query: Query<&Children>, computed_node_query: Query<&ComputedNode, Changed>, + mut messages: MessageWriter, mut images: ResMut>, ) { for (pane_root, viewport) in &query { @@ -246,7 +248,11 @@ fn update_render_target_size( }; // TODO Convert to physical pixels let content_node_size = computed_node.size(); - + let size_array = [content_node_size.x, content_node_size.y]; + messages.write(ViewportResized { + pane_entity: pane_root, + size: size_array, + }); let camera = camera_query.get_mut(viewport.camera_id).unwrap(); let image_handle = camera.target.as_image().unwrap(); diff --git a/crates/bevy_events/Cargo.toml b/crates/bevy_events/Cargo.toml new file mode 100644 index 00000000..72ff73d6 --- /dev/null +++ b/crates/bevy_events/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bevy_events" +version = "0.1.0" +edition = "2024" + +[dependencies] +bevy.workspace = true +bevy_editor_core.workspace = true + +[lints] +workspace = true diff --git a/crates/bevy_events/src/lib.rs b/crates/bevy_events/src/lib.rs new file mode 100644 index 00000000..35db2516 --- /dev/null +++ b/crates/bevy_events/src/lib.rs @@ -0,0 +1,16 @@ +//! This module contains events and messages used throughout the Bevy editor +use bevy::prelude::Entity; +use bevy::prelude::Message; + +/// Viewport size in pixels as a 2D vector. +pub type ViewportSize = [f32; 2]; + +/// Event emitted when a viewport (pane/editor panel) resizes. +/// Used for dynamic gizmo scaling +#[derive(Message, Clone)] +pub struct ViewportResized { + /// Entity of the pane/viewport that resized (for multi-pane editors). + pub pane_entity: Entity, + /// New viewport size in pixels. + pub size: ViewportSize, +} diff --git a/crates/bevy_transform_gizmos/Cargo.toml b/crates/bevy_transform_gizmos/Cargo.toml index 96dafbee..e6684e7a 100644 --- a/crates/bevy_transform_gizmos/Cargo.toml +++ b/crates/bevy_transform_gizmos/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] bevy.workspace = true bevy_editor_core.workspace = true +bevy_events.workspace = true [lints] workspace = true diff --git a/crates/bevy_transform_gizmos/src/lib.rs b/crates/bevy_transform_gizmos/src/lib.rs index ba7d66e0..53ab6094 100644 --- a/crates/bevy_transform_gizmos/src/lib.rs +++ b/crates/bevy_transform_gizmos/src/lib.rs @@ -15,6 +15,7 @@ use bevy::camera::Projection; use bevy::picking::{backend::ray::RayMap, pointer::PointerId}; use bevy::{prelude::*, transform::TransformSystems}; use bevy_editor_core::selection::EditorSelection; +use bevy_events::ViewportResized; use mesh::{RotationGizmo, ViewTranslateGizmo}; use normalization::*; @@ -103,6 +104,8 @@ pub struct TransformGizmoSettings { pub snap_enabled: bool, /// Current gizmo mode. pub mode: GizmoMode, + /// Current gizmo scale. + pub viewport_scale: f32, } impl Default for TransformGizmoSettings { @@ -115,6 +118,7 @@ impl Default for TransformGizmoSettings { angle_snap: 15.0, // 15 degree angle snapping scale_snap: 0.1, // 0.1 scale increment snapping snap_enabled: true, // Enable snapping by default + viewport_scale: 1.0, mode: GizmoMode::default(), } } @@ -129,6 +133,8 @@ impl Plugin for TransformGizmoPlugin { if !app.is_plugin_added::() { app.add_plugins(MeshPickingPlugin); } + app.add_message::(); + app.init_resource::() .add_plugins(Ui3dNormalizationPlugin) .add_message::() @@ -164,6 +170,7 @@ impl Plugin for TransformGizmoPlugin { (adjust_view_translate_gizmo, gizmo_cam_copy_settings) .chain() .in_set(TransformGizmoSystems::Drag), + transform_gizmo_viewport_handler, ) .chain() .in_set(TransformGizmoSystems::Main) @@ -661,6 +668,7 @@ fn place_gizmo( fn propagate_gizmo_elements( gizmo: Query<(&GlobalTransform, &Children), With>, + settings: Res, mut gizmo_parts_query: Query<(&Transform, &mut GlobalTransform), Without>, ) { if let Ok((gizmo_pos, gizmo_parts)) = gizmo.single() { @@ -669,7 +677,12 @@ fn propagate_gizmo_elements( error!("Malformed transform gizmo"); continue; }; - *g_transform = gizmo_pos.mul_transform(*transform); + let scaled_transform = Transform { + translation: transform.translation * settings.viewport_scale, + rotation: transform.rotation, + scale: Vec3::splat(settings.viewport_scale), + }; + *g_transform = gizmo_pos.mul_transform(scaled_transform); } } } @@ -866,3 +879,14 @@ fn update_gizmo_visibility( } } } + +fn transform_gizmo_viewport_handler( + mut messages: MessageReader, + mut settings: ResMut, +) { + for msg in messages.read() { + let viewport_height = msg.size[1]; + let scale = (viewport_height / 600.0).clamp(0.3, 3.0); + settings.viewport_scale = scale; + } +} From c36d1a7a0fb1223a8bcac77547a0e9ee80e03f72 Mon Sep 17 00:00:00 2001 From: Extermind Date: Sat, 11 Apr 2026 22:08:24 +0200 Subject: [PATCH 2/2] Refactor: move ViewportResized message to bevy_transform_gizmos (per review) --- Cargo.toml | 3 --- bevy_editor_panes/bevy_3d_viewport/Cargo.toml | 1 - bevy_editor_panes/bevy_3d_viewport/src/lib.rs | 3 +-- crates/bevy_transform_gizmos/Cargo.toml | 1 - crates/bevy_transform_gizmos/src/lib.rs | 3 ++- crates/bevy_transform_gizmos/src/messages.rs | 16 ++++++++++++++++ 6 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 crates/bevy_transform_gizmos/src/messages.rs diff --git a/Cargo.toml b/Cargo.toml index a83e8fb6..db72a0e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,3 @@ bevy_undo = { path = "crates/bevy_undo" } bevy_infinite_grid = { path = "crates/bevy_infinite_grid" } bevy_editor_cam = { path = "crates/bevy_editor_cam" } bevy_clipboard = { path = "crates/bevy_clipboard" } - -# editor events/messages -bevy_events = { path = "crates/bevy_events" } diff --git a/bevy_editor_panes/bevy_3d_viewport/Cargo.toml b/bevy_editor_panes/bevy_3d_viewport/Cargo.toml index f47c7782..6ad25fb1 100644 --- a/bevy_editor_panes/bevy_3d_viewport/Cargo.toml +++ b/bevy_editor_panes/bevy_3d_viewport/Cargo.toml @@ -11,7 +11,6 @@ bevy_editor_styles.workspace = true bevy_infinite_grid.workspace = true bevy_editor_core.workspace = true bevy_transform_gizmos.workspace = true -bevy_events.workspace = true [lints] workspace = true diff --git a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs index 1d09c116..b3cad3f1 100644 --- a/bevy_editor_panes/bevy_3d_viewport/src/lib.rs +++ b/bevy_editor_panes/bevy_3d_viewport/src/lib.rs @@ -17,11 +17,10 @@ use bevy_editor_cam::prelude::{DefaultEditorCamPlugins, EditorCam}; use bevy_editor_styles::Theme; use bevy_infinite_grid::{InfiniteGrid, InfiniteGridPlugin, InfiniteGridSettings}; use bevy_pane_layout::prelude::*; -use bevy_transform_gizmos::{TransformGizmo, prelude::*}; +use bevy_transform_gizmos::{TransformGizmo, messages::ViewportResized, prelude::*}; use view_gizmo::ViewGizmoPlugin; use crate::{selection_box::SelectionBoxPlugin, view_gizmo::view_gizmo_node}; -use bevy_events::ViewportResized; mod selection_box; mod view_gizmo; diff --git a/crates/bevy_transform_gizmos/Cargo.toml b/crates/bevy_transform_gizmos/Cargo.toml index e6684e7a..96dafbee 100644 --- a/crates/bevy_transform_gizmos/Cargo.toml +++ b/crates/bevy_transform_gizmos/Cargo.toml @@ -6,7 +6,6 @@ edition = "2024" [dependencies] bevy.workspace = true bevy_editor_core.workspace = true -bevy_events.workspace = true [lints] workspace = true diff --git a/crates/bevy_transform_gizmos/src/lib.rs b/crates/bevy_transform_gizmos/src/lib.rs index 53ab6094..80006d72 100644 --- a/crates/bevy_transform_gizmos/src/lib.rs +++ b/crates/bevy_transform_gizmos/src/lib.rs @@ -15,12 +15,13 @@ use bevy::camera::Projection; use bevy::picking::{backend::ray::RayMap, pointer::PointerId}; use bevy::{prelude::*, transform::TransformSystems}; use bevy_editor_core::selection::EditorSelection; -use bevy_events::ViewportResized; use mesh::{RotationGizmo, ViewTranslateGizmo}; +use messages::ViewportResized; use normalization::*; mod mesh; +pub mod messages; pub mod normalization; /// Crate prelude. diff --git a/crates/bevy_transform_gizmos/src/messages.rs b/crates/bevy_transform_gizmos/src/messages.rs new file mode 100644 index 00000000..5a76478f --- /dev/null +++ b/crates/bevy_transform_gizmos/src/messages.rs @@ -0,0 +1,16 @@ +//! This module contains events/messages used to communicate with the transform gizmo plugin. +use bevy::prelude::Entity; +use bevy::prelude::Message; + +/// Viewport size in pixels as a 2D vector. +pub type ViewportSize = [f32; 2]; + +/// Event emitted when a viewport (pane/editor panel) resizes. +/// Used for dynamic gizmo scaling +#[derive(Message, Clone)] +pub struct ViewportResized { + /// Entity of the pane/viewport that resized (for multi-pane editors). + pub pane_entity: Entity, + /// New viewport size in pixels. + pub size: ViewportSize, +}