From ddf9d267fc700f274d9f19f38f185db0f9954dcc Mon Sep 17 00:00:00 2001 From: Grayden <38144548+graydenshand@users.noreply.github.com> Date: Tue, 25 Mar 2025 05:26:07 -0400 Subject: [PATCH] Fix in `Scene`: make `scene_rect` full size on reset (#5801) * [x] I have followed the instructions in the PR template # Overview This is a small change that supports draggable elements inside a `Scene`. When a Scene is initialized with a `Rect::Zero`, following the [example in the demo](https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/demo/scene.rs#L15), it will [automatically be reset to the `inner_rect` of the UI](https://github.com/emilk/egui/blob/master/crates/egui/src/containers/scene.rs#L120-L123). This centers the scene on the inner-rect contents, however the resulting `scene_rect` doesn't fill the entire `outer_rect`. This probably isn't an issue for most users of `Scene`. However, I want to support draggable elements on a `Scene`, and to do that I need to map the pointer-position in the window to the scene_rect position. As is, the example of draggable elements on Scene works after the user has modified the scene rect in some way (zoom or pan), when `scene_rect` is set to `to_global.inverse() * outer_rect` ([here](https://github.com/emilk/egui/blob/master/crates/egui/src/containers/scene.rs#L114-L118)). Before a user modifies the scene rect, the pointer-position cannot be reliably mapped to the scene_rect, since the scene_rect doesn't span the entire window. This PR just forces that translation to always run after the scene_rect is reset to `inner_rect`. The practical result is that the scene_rect will now always span the full outer_rect. # Example Here's a small app that demonstrates the functionality I'm trying to support. I'm new to Egui so there may be better patterns for what I'm trying to do, but if you run this against `main` and this branch you'll notice the difference. ```rs use eframe::egui::*; /// Map coordinates from the src rect to the target rect fn map_to_rect(position: Pos2, src_rect: Rect, dest_rect: Rect) -> Pos2 { let x = (position.x - src_rect.min.x) / (src_rect.max.x - src_rect.min.x) * (dest_rect.max.x - dest_rect.min.x) + dest_rect.min.x; let y = (position.y - src_rect.min.y) / (src_rect.max.y - src_rect.min.y) * (dest_rect.max.y - dest_rect.min.y) + dest_rect.min.y; Pos2::new(x, y) } pub fn draggable_scene_element( ui: &mut Ui, id: Id, position: &mut Rect, scene_rect: Rect, container_rect: Rect, ) -> Response { let is_being_dragged = ui.ctx().is_being_dragged(id); if is_being_dragged { let r = ui.put(*position, |ui: &mut Ui| ui.label("Draggable")); if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() { let pointer_pos = map_to_rect(pointer_pos, container_rect, scene_rect); let delta = pointer_pos.to_vec2() - position.center().to_vec2(); *position = position.translate(delta); }; r } else { let r = ui.put(*position, |ui: &mut Ui| ui.label("Draggable")); ui .interact(position.clone(), id, Sense::drag()) .on_hover_cursor(CursorIcon::Grab); r } } struct MyApp { scene_rect: Rect, position: Rect, } impl MyApp { fn new() -> Self { Self { scene_rect: Rect::ZERO, position: Rect::from_min_size(Pos2::new(-50., -50.), Vec2::new(100., 100.)), } } } impl eframe::App for MyApp { fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { CentralPanel::default().show(ctx, |ui| { let scene_rect = self.scene_rect.clone(); let container_rect = ui.min_rect(); Scene::default().show(ui, &mut self.scene_rect, |ui| { ui.put( Rect::from_min_size(Pos2::new(100., 200.), Vec2::new(100., 100.)), |ui: &mut Ui| ui.label("static element"), ); ui.put(self.position, |ui: &mut Ui| { draggable_scene_element( ui, Id::from("demo"), &mut self.position, scene_rect, container_rect, ) }); }); }); } } ``` # Summary I need a way to map pointer coordinates to scene coordinates, in order to support draggable elements in a scene. This patch makes that easier by ensuring the scene_rect will always be the full size of the outer_rect. If you have a better way to accomplish what I'm after, I'm happy to close this. Thanks! --- crates/egui/src/containers/scene.rs | 4 +++- crates/egui_demo_lib/tests/snapshots/demos/Scene.png | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/containers/scene.rs b/crates/egui/src/containers/scene.rs index 6a9fc104..fff4136f 100644 --- a/crates/egui/src/containers/scene.rs +++ b/crates/egui/src/containers/scene.rs @@ -119,7 +119,9 @@ impl Scene { if !scene_rect_was_good { // Auto-reset if the transformation goes bad somehow (or started bad). - *scene_rect = inner_rect; + // Recalculates transform based on inner_rect, resulting in a rect that's the full size of outer_rect but centered on inner_rect. + let to_global = fit_to_rect_in_scene(outer_rect, inner_rect, self.zoom_range); + *scene_rect = to_global.inverse() * outer_rect; } ret diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png index 0f67cc6a..a2c2e7ba 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Scene.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Scene.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16ee44708adbe6e0ac3ce58617a5d63fb3bde357c07611815376518950e056b0 -size 34763 +oid sha256:b4bf35ad4ce01122de5bc0830018044fd70f116938293fbeb72a2278de0bbb22 +size 35068