From c1ef81628b0d35c404295f943e634f178c56b202 Mon Sep 17 00:00:00 2001 From: aakamenov Date: Sun, 7 Mar 2021 22:42:16 +0300 Subject: [PATCH] Add optional drag bounds to Area and Window Co-authored-by: Emil Ernerfeldt --- CHANGELOG.md | 1 + egui/src/containers/area.rs | 27 +++++++++++++++++++++++++-- egui/src/containers/window.rs | 16 +++++++++++++++- egui/src/context.rs | 32 ++++++++++++++++++-------------- 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2210c93f..08a9d2db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Use arrow keys to adjust sliders and `DragValue`s. * egui will now output events when widgets gain keyboard focus. * This can be hooked up to a screen reader to aid the visually impaired +* Add the option to restrict the dragging bounds of `Window` and `Area` to a specified area using `drag_bounds(rect)`. ### Fixed 🐛 diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index f8c7b1bb..8fa6300e 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -49,6 +49,7 @@ pub struct Area { order: Order, default_pos: Option, new_pos: Option, + drag_bounds: Option, } impl Area { @@ -61,6 +62,7 @@ impl Area { order: Order::Middle, default_pos: None, new_pos: None, + drag_bounds: None, } } @@ -130,6 +132,12 @@ impl Area { self.new_pos = Some(current_pos); self } + + /// 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) struct Prepared { @@ -137,6 +145,7 @@ pub(crate) struct Prepared { state: State, movable: bool, enabled: bool, + drag_bounds: Option, } impl Area { @@ -149,6 +158,7 @@ impl Area { enabled, default_pos, new_pos, + drag_bounds, } = self; let layer_id = LayerId::new(order, id); @@ -167,6 +177,7 @@ impl Area { state, movable, enabled, + drag_bounds, } } @@ -215,13 +226,19 @@ impl Prepared { &mut self.state } + pub(crate) fn drag_bounds(&self) -> Option { + self.drag_bounds + } + pub(crate) fn content_ui(&self, ctx: &CtxRef) -> Ui { let max_rect = Rect::from_min_size(self.state.pos, Vec2::INFINITY); let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky + let bounds = self.drag_bounds.unwrap_or_else(|| ctx.input().screen_rect); + let mut clip_rect = max_rect .expand(ctx.style().visuals.clip_rect_margin) .expand(shadow_radius) - .intersect(ctx.input().screen_rect); + .intersect(bounds); // Windows are constrained to central area, // (except in rare cases where they don't fit). @@ -240,6 +257,7 @@ impl Prepared { clip_rect, ); ui.set_enabled(self.enabled); + ui } @@ -250,6 +268,7 @@ impl Prepared { mut state, movable, enabled, + drag_bounds, } = self; state.size = content_ui.min_rect().size(); @@ -275,7 +294,11 @@ impl Prepared { state.pos += ctx.input().pointer.delta(); } - state.pos = ctx.constrain_window_rect(state.rect()).min; + if let Some(bounds) = drag_bounds { + state.pos = ctx.constrain_window_rect_to_area(state.rect(), bounds).min; + } else { + state.pos = ctx.constrain_window_rect(state.rect()).min; + } if (move_response.dragged() || move_response.clicked()) || pointer_pressed_on_area(ctx, layer_id) diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index 50b15cd4..32374814 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -204,6 +204,12 @@ impl<'open> Window<'open> { } 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> { @@ -275,6 +281,7 @@ impl<'open> Window<'open> { 0.0 }; let margins = 2.0 * frame.margin + vec2(0.0, title_bar_height); + let bounds = area.drag_bounds(); interact( window_interaction, @@ -283,6 +290,7 @@ impl<'open> Window<'open> { area_layer_id, area.state_mut(), resize_id, + bounds, ) }) } else { @@ -435,10 +443,16 @@ fn interact( area_layer_id: LayerId, area_state: &mut area::State, resize_id: Id, + drag_bounds: Option, ) -> Option { let new_rect = move_and_resize_window(ctx, &window_interaction)?; let new_rect = ctx.round_rect_to_pixels(new_rect); - let new_rect = ctx.constrain_window_rect(new_rect); + + let new_rect = if let Some(bounds) = drag_bounds { + ctx.constrain_window_rect_to_area(new_rect, bounds) + } else { + ctx.constrain_window_rect(new_rect) + }; // TODO: add this to a Window state instead as a command "move here next frame" area_state.pos = new_rect.min; diff --git a/egui/src/context.rs b/egui/src/context.rs index 789b29d2..3796211a 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -476,33 +476,37 @@ impl Context { // --------------------------------------------------------------------- - /// Constraint the position of a window/area + /// Constrain the position of a window/area /// so it fits within the screen. pub(crate) fn constrain_window_rect(&self, window: Rect) -> Rect { - let mut screen = self.available_rect(); + self.constrain_window_rect_to_area(window, self.available_rect()) + } - if window.width() > screen.width() { + /// Constrain the position of a window/area + /// so it fits within the provided boundary. + pub(crate) fn constrain_window_rect_to_area(&self, window: Rect, mut area: Rect) -> Rect { + if window.width() > area.width() { // Allow overlapping side bars. // This is important for small screens, e.g. mobiles running the web demo. - screen.max.x = self.input().screen_rect().max.x; - screen.min.x = self.input().screen_rect().min.x; + area.max.x = self.input().screen_rect().max.x; + area.min.x = self.input().screen_rect().min.x; } - if window.height() > screen.height() { + if window.height() > area.height() { // Allow overlapping top/bottom bars: - screen.max.y = self.input().screen_rect().max.y; - screen.min.y = self.input().screen_rect().min.y; + area.max.y = self.input().screen_rect().max.y; + area.min.y = self.input().screen_rect().min.y; } let mut pos = window.min; // Constrain to screen, unless window is too large to fit: - let margin_x = (window.width() - screen.width()).at_least(0.0); - let margin_y = (window.height() - screen.height()).at_least(0.0); + let margin_x = (window.width() - area.width()).at_least(0.0); + let margin_y = (window.height() - area.height()).at_least(0.0); - pos.x = pos.x.at_most(screen.right() + margin_x - window.width()); // move left if needed - pos.x = pos.x.at_least(screen.left() - margin_x); // move right if needed - pos.y = pos.y.at_most(screen.bottom() + margin_y - window.height()); // move right if needed - pos.y = pos.y.at_least(screen.top() - margin_y); // move down if needed + pos.x = pos.x.at_most(area.right() + margin_x - window.width()); // move left if needed + pos.x = pos.x.at_least(area.left() - margin_x); // move right if needed + pos.y = pos.y.at_most(area.bottom() + margin_y - window.height()); // move right if needed + pos.y = pos.y.at_least(area.top() - margin_y); // move down if needed pos = self.round_pos_to_pixels(pos);