From e8b9e706cad2ebfef02007258efffb4906ebe04c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 8 Feb 2023 12:41:36 +0100 Subject: [PATCH] Fix `Window::pivot` causing windows to move around (#2694) * Fix Window::pivot causing windows to move around * Add line to changelog --- CHANGELOG.md | 1 + crates/egui/src/containers/area.rs | 62 ++++++++++++++++++---------- crates/egui/src/containers/window.rs | 11 +++-- crates/egui/src/context.rs | 3 +- 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9daf9a3e8b1..5288f1d39be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG * The `button_padding` style option works closer as expected with image+text buttons now ([#2510](https://github.com/emilk/egui/pull/2510)). * Fixed rendering of `…` (ellipsis). * Menus are now moved to fit on the screen. +* Fix `Window::pivot` causing windows to move around ([#2694](https://github.com/emilk/egui/pull/2694)). ## 0.20.1 - 2022-12-11 - Fix key-repeat diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 84f0ea44632..380c175b323 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -9,8 +9,10 @@ use crate::*; #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) struct State { - /// Last known pos - pub pos: Pos2, + /// Last known pos of the pivot + pub pivot_pos: Pos2, + + pub pivot: Align2, /// Last know size. Used for catching clicks. pub size: Vec2, @@ -21,8 +23,22 @@ pub(crate) struct State { } impl State { + pub fn left_top_pos(&self) -> Pos2 { + pos2( + self.pivot_pos.x - self.pivot.x().to_factor() * self.size.x, + self.pivot_pos.y - self.pivot.y().to_factor() * self.size.y, + ) + } + + pub fn set_left_top_pos(&mut self, pos: Pos2) { + self.pivot_pos = pos2( + pos.x + self.pivot.x().to_factor() * self.size.x, + pos.y + self.pivot.y().to_factor() * self.size.y, + ); + } + pub fn rect(&self) -> Rect { - Rect::from_min_size(self.pos, self.size) + Rect::from_min_size(self.left_top_pos(), self.size) } } @@ -237,21 +253,19 @@ impl Area { ctx.request_repaint(); // if we don't know the previous size we are likely drawing the area in the wrong place } let mut state = state.unwrap_or_else(|| State { - pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)), + pivot_pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)), + pivot, size: Vec2::ZERO, interactable, }); - state.pos = new_pos.unwrap_or(state.pos); + state.pivot_pos = new_pos.unwrap_or(state.pivot_pos); state.interactable = interactable; - if pivot != Align2::LEFT_TOP { - state.pos.x -= pivot.x().to_factor() * state.size.x; - state.pos.y -= pivot.y().to_factor() * state.size.y; - } - if let Some((anchor, offset)) = anchor { let screen = ctx.available_rect(); - state.pos = anchor.align_size_within_rect(state.size, screen).min + offset; + state.set_left_top_pos( + anchor.align_size_within_rect(state.size, screen).left_top() + offset, + ); } // interact right away to prevent frame-delay @@ -278,12 +292,13 @@ impl Area { // Important check - don't try to move e.g. a combobox popup! if movable { if move_response.dragged() { - state.pos += ctx.input(|i| i.pointer.delta()); + state.pivot_pos += ctx.input(|i| i.pointer.delta()); } - state.pos = ctx - .constrain_window_rect_to_area(state.rect(), drag_bounds) - .min; + state.set_left_top_pos( + ctx.constrain_window_rect_to_area(state.rect(), drag_bounds) + .min, + ); } if (move_response.dragged() || move_response.clicked()) @@ -297,12 +312,13 @@ impl Area { move_response }; - state.pos = ctx.round_pos_to_pixels(state.pos); + state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos())); if constrain { - state.pos = ctx - .constrain_window_rect_to_area(state.rect(), drag_bounds) - .min; + state.set_left_top_pos( + ctx.constrain_window_rect_to_area(state.rect(), drag_bounds) + .left_top(), + ); } Prepared { @@ -374,14 +390,16 @@ impl Prepared { }; let max_rect = Rect::from_min_max( - self.state.pos, - bounds.max.at_least(self.state.pos + Vec2::splat(32.0)), + self.state.left_top_pos(), + bounds + .max + .at_least(self.state.left_top_pos() + Vec2::splat(32.0)), ); let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius); - let clip_rect = Rect::from_min_max(self.state.pos, bounds.max) + let clip_rect = Rect::from_min_max(self.state.left_top_pos(), bounds.max) .expand(clip_rect_margin) .intersect(bounds); diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index a236a5e3d98..d5c3f680f33 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -438,9 +438,12 @@ impl<'open> Window<'open> { content_inner }; - area.state_mut().pos = ctx - .constrain_window_rect_to_area(area.state().rect(), area.drag_bounds()) - .min; + { + let pos = ctx + .constrain_window_rect_to_area(area.state().rect(), area.drag_bounds()) + .left_top(); + area.state_mut().set_left_top_pos(pos); + } let full_response = area.end(ctx, area_content_ui); @@ -550,7 +553,7 @@ fn interact( let new_rect = ctx.constrain_window_rect_to_area(new_rect, area.drag_bounds()); // TODO(emilk): add this to a Window state instead as a command "move here next frame" - area.state_mut().pos = new_rect.min; + area.state_mut().set_left_top_pos(new_rect.left_top()); if window_interaction.is_resize() { if let Some(mut state) = resize::State::load(ctx, resize_id) { diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index e4d5de5dc37..8e731f36f75 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -105,7 +105,8 @@ impl ContextImpl { self.memory.areas.set_state( LayerId::background(), containers::area::State { - pos: screen_rect.min, + pivot_pos: screen_rect.left_top(), + pivot: Align2::LEFT_TOP, size: screen_rect.size(), interactable: true, },