diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index e2134510e83..52c02b18ba0 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -65,12 +65,12 @@ pub struct Area { interactable: bool, enabled: bool, constrain: bool, + constrain_rect: Option, order: Order, default_pos: Option, pivot: Align2, anchor: Option<(Align2, Vec2)>, new_pos: Option, - drag_bounds: Option, } impl Area { @@ -80,13 +80,13 @@ impl Area { movable: true, interactable: true, constrain: false, + constrain_rect: None, enabled: true, order: Order::Middle, default_pos: None, new_pos: None, pivot: Align2::LEFT_TOP, anchor: None, - drag_bounds: None, } } @@ -155,6 +155,21 @@ impl Area { self } + /// Constraint the movement of the window to the given rectangle. + /// + /// For instance: `.constrain_to(ctx.screen_rect())`. + pub fn constrain_to(mut self, constrain_rect: Rect) -> Self { + self.constrain = true; + self.constrain_rect = Some(constrain_rect); + self + } + + #[deprecated = "Use `constrain_to` instead"] + pub fn drag_bounds(mut self, constrain_rect: Rect) -> Self { + self.constrain_rect = Some(constrain_rect); + self + } + /// Where the "root" of the area is. /// /// For instance, if you set this to [`Align2::RIGHT_TOP`] @@ -189,12 +204,6 @@ impl Area { self.movable(false) } - /// Constrain the area up to which the window can be dragged. - pub fn drag_bounds(mut self, bounds: Rect) -> Self { - self.drag_bounds = Some(bounds); - self - } - pub(crate) fn get_pivot(&self) -> Align2 { if let Some((pivot, _)) = self.anchor { pivot @@ -209,7 +218,8 @@ pub(crate) struct Prepared { state: State, move_response: Response, enabled: bool, - drag_bounds: Option, + constrain: bool, + constrain_rect: Option, /// We always make windows invisible the first frame to hide "first-frame-jitters". /// @@ -243,8 +253,8 @@ impl Area { new_pos, pivot, anchor, - drag_bounds, constrain, + constrain_rect, } = self; let layer_id = LayerId::new(order, id); @@ -308,7 +318,7 @@ impl Area { if constrain { state.set_left_top_pos( - ctx.constrain_window_rect_to_area(state.rect(), drag_bounds) + ctx.constrain_window_rect_to_area(state.rect(), constrain_rect) .min, ); } @@ -323,7 +333,8 @@ impl Area { state, move_response, enabled, - drag_bounds, + constrain, + constrain_rect, temporarily_invisible: is_new, } } @@ -366,15 +377,19 @@ impl Prepared { &mut self.state } - pub(crate) fn drag_bounds(&self) -> Option { - self.drag_bounds + pub(crate) fn constrain(&self) -> bool { + self.constrain + } + + pub(crate) fn constrain_rect(&self) -> Option { + self.constrain_rect } pub(crate) fn content_ui(&self, ctx: &Context) -> Ui { let screen_rect = ctx.screen_rect(); - let bounds = if let Some(bounds) = self.drag_bounds { - bounds.intersect(screen_rect) // protect against infinite bounds + let constrain_rect = if let Some(constrain_rect) = self.constrain_rect { + constrain_rect.intersect(screen_rect) // protect against infinite bounds } else { let central_area = ctx.available_rect(); @@ -388,7 +403,7 @@ impl Prepared { let max_rect = Rect::from_min_max( self.state.left_top_pos(), - bounds + constrain_rect .max .at_least(self.state.left_top_pos() + Vec2::splat(32.0)), ); @@ -396,9 +411,9 @@ impl Prepared { 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.left_top_pos(), bounds.max) + let clip_rect = Rect::from_min_max(self.state.left_top_pos(), constrain_rect.max) .expand(clip_rect_margin) - .intersect(bounds); + .intersect(constrain_rect); let mut ui = Ui::new( ctx.clone(), @@ -419,7 +434,8 @@ impl Prepared { mut state, move_response, enabled: _, - drag_bounds: _, + constrain: _, + constrain_rect: _, temporarily_invisible: _, } = self; diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index e4ad1b1305f..78be080ba47 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -260,9 +260,8 @@ fn show_tooltip_area_dyn<'c, R>( Area::new(area_id) .order(Order::Tooltip) .fixed_pos(window_pos) - .constrain(true) + .constrain_to(ctx.screen_rect()) .interactable(false) - .drag_bounds(ctx.screen_rect()) .show(ctx, |ui| { Frame::popup(&ctx.style()) .show(ui, |ui| { diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index ca4f6190d24..aa3fad11872 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -43,7 +43,7 @@ impl<'open> Window<'open> { /// If you need a changing title, you must call `window.id(…)` with a fixed id. pub fn new(title: impl Into) -> Self { let title = title.into().fallback_text_style(TextStyle::Heading); - let area = Area::new(Id::new(title.text())); + let area = Area::new(Id::new(title.text())).constrain(true); Self { title, open: None, @@ -146,11 +146,31 @@ impl<'open> Window<'open> { } /// Constrains this window to the screen bounds. + /// + /// To change the area to constrain to, use [`Self::constraint_to`]. + /// + /// Default: `true`. pub fn constrain(mut self, constrain: bool) -> Self { self.area = self.area.constrain(constrain); self } + /// Constraint the movement of the window to the given rectangle. + /// + /// For instance: `.constrain_to(ctx.screen_rect())`. + pub fn constraint_to(mut self, constrain_rect: Rect) -> Self { + self.area = self.area.constrain_to(constrain_rect); + self + } + + #[deprecated = "Use `constrain_to` instead"] + pub fn drag_bounds(mut self, constrain_rect: Rect) -> Self { + #![allow(deprecated)] + + self.area = self.area.drag_bounds(constrain_rect); + self + } + /// Where the "root" of the window is. /// /// For instance, if you set this to [`Align2::RIGHT_TOP`] @@ -276,12 +296,6 @@ impl<'open> Window<'open> { self.scroll = self.scroll.drag_to_scroll(drag_to_scroll); self } - - /// Constrain the area up to which the window can be dragged. - pub fn drag_bounds(mut self, bounds: Rect) -> Self { - self.area = self.area.drag_bounds(bounds); - self - } } impl<'open> Window<'open> { @@ -452,13 +466,6 @@ impl<'open> Window<'open> { content_inner }; - { - 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); let inner_response = InnerResponse { @@ -562,9 +569,11 @@ fn interact( resize_id: Id, ) -> Option { let new_rect = move_and_resize_window(ctx, &window_interaction)?; - let new_rect = ctx.round_rect_to_pixels(new_rect); + let mut new_rect = ctx.round_rect_to_pixels(new_rect); - let new_rect = ctx.constrain_window_rect_to_area(new_rect, area.drag_bounds()); + if area.constrain() { + new_rect = ctx.constrain_window_rect_to_area(new_rect, area.constrain_rect()); + } // TODO(emilk): add this to a Window state instead as a command "move here next frame" area.state_mut().set_left_top_pos(new_rect.left_top()); diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index ab92f063df7..588d0b40511 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -146,10 +146,9 @@ pub(crate) fn menu_ui<'c, R>( let area = Area::new(menu_id) .order(Order::Foreground) - .constrain(true) .fixed_pos(pos) - .interactable(true) - .drag_bounds(ctx.screen_rect()); + .constrain_to(ctx.screen_rect()) + .interactable(true); area.show(ctx, |ui| { set_menu_style(ui.style_mut()); diff --git a/crates/egui_demo_lib/src/demo/window_options.rs b/crates/egui_demo_lib/src/demo/window_options.rs index ed142d08997..37b08183a52 100644 --- a/crates/egui_demo_lib/src/demo/window_options.rs +++ b/crates/egui_demo_lib/src/demo/window_options.rs @@ -6,6 +6,7 @@ pub struct WindowOptions { closable: bool, collapsible: bool, resizable: bool, + constrain: bool, scroll2: [bool; 2], disabled_time: f64, @@ -22,6 +23,7 @@ impl Default for WindowOptions { closable: true, collapsible: true, resizable: true, + constrain: true, scroll2: [true; 2], disabled_time: f64::NEG_INFINITY, anchored: false, @@ -43,6 +45,7 @@ impl super::Demo for WindowOptions { closable, collapsible, resizable, + constrain, scroll2, disabled_time, anchored, @@ -59,6 +62,7 @@ impl super::Demo for WindowOptions { let mut window = egui::Window::new(title) .id(egui::Id::new("demo_window_options")) // required since we change the title .resizable(resizable) + .constrain(constrain) .collapsible(collapsible) .title_bar(title_bar) .scroll2(scroll2) @@ -81,6 +85,7 @@ impl super::View for WindowOptions { closable, collapsible, resizable, + constrain, scroll2, disabled_time: _, anchored, @@ -99,6 +104,8 @@ impl super::View for WindowOptions { ui.checkbox(closable, "closable"); ui.checkbox(collapsible, "collapsible"); ui.checkbox(resizable, "resizable"); + ui.checkbox(constrain, "constrain") + .on_hover_text("Contrain window to the screen"); ui.checkbox(&mut scroll2[0], "hscroll"); ui.checkbox(&mut scroll2[1], "vscroll"); });