Add optional drag bounds to Area and Window

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
aakamenov 2021-03-07 22:42:16 +03:00 committed by GitHub
parent c212d4512e
commit c1ef81628b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 59 additions and 17 deletions

View file

@ -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. * Use arrow keys to adjust sliders and `DragValue`s.
* egui will now output events when widgets gain keyboard focus. * egui will now output events when widgets gain keyboard focus.
* This can be hooked up to a screen reader to aid the visually impaired * 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 🐛 ### Fixed 🐛

View file

@ -49,6 +49,7 @@ pub struct Area {
order: Order, order: Order,
default_pos: Option<Pos2>, default_pos: Option<Pos2>,
new_pos: Option<Pos2>, new_pos: Option<Pos2>,
drag_bounds: Option<Rect>,
} }
impl Area { impl Area {
@ -61,6 +62,7 @@ impl Area {
order: Order::Middle, order: Order::Middle,
default_pos: None, default_pos: None,
new_pos: None, new_pos: None,
drag_bounds: None,
} }
} }
@ -130,6 +132,12 @@ impl Area {
self.new_pos = Some(current_pos); self.new_pos = Some(current_pos);
self 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 { pub(crate) struct Prepared {
@ -137,6 +145,7 @@ pub(crate) struct Prepared {
state: State, state: State,
movable: bool, movable: bool,
enabled: bool, enabled: bool,
drag_bounds: Option<Rect>,
} }
impl Area { impl Area {
@ -149,6 +158,7 @@ impl Area {
enabled, enabled,
default_pos, default_pos,
new_pos, new_pos,
drag_bounds,
} = self; } = self;
let layer_id = LayerId::new(order, id); let layer_id = LayerId::new(order, id);
@ -167,6 +177,7 @@ impl Area {
state, state,
movable, movable,
enabled, enabled,
drag_bounds,
} }
} }
@ -215,13 +226,19 @@ impl Prepared {
&mut self.state &mut self.state
} }
pub(crate) fn drag_bounds(&self) -> Option<Rect> {
self.drag_bounds
}
pub(crate) fn content_ui(&self, ctx: &CtxRef) -> Ui { pub(crate) fn content_ui(&self, ctx: &CtxRef) -> Ui {
let max_rect = Rect::from_min_size(self.state.pos, Vec2::INFINITY); let max_rect = Rect::from_min_size(self.state.pos, Vec2::INFINITY);
let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky 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 let mut clip_rect = max_rect
.expand(ctx.style().visuals.clip_rect_margin) .expand(ctx.style().visuals.clip_rect_margin)
.expand(shadow_radius) .expand(shadow_radius)
.intersect(ctx.input().screen_rect); .intersect(bounds);
// Windows are constrained to central area, // Windows are constrained to central area,
// (except in rare cases where they don't fit). // (except in rare cases where they don't fit).
@ -240,6 +257,7 @@ impl Prepared {
clip_rect, clip_rect,
); );
ui.set_enabled(self.enabled); ui.set_enabled(self.enabled);
ui ui
} }
@ -250,6 +268,7 @@ impl Prepared {
mut state, mut state,
movable, movable,
enabled, enabled,
drag_bounds,
} = self; } = self;
state.size = content_ui.min_rect().size(); state.size = content_ui.min_rect().size();
@ -275,7 +294,11 @@ impl Prepared {
state.pos += ctx.input().pointer.delta(); 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()) if (move_response.dragged() || move_response.clicked())
|| pointer_pressed_on_area(ctx, layer_id) || pointer_pressed_on_area(ctx, layer_id)

View file

@ -204,6 +204,12 @@ impl<'open> Window<'open> {
} }
self 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> { impl<'open> Window<'open> {
@ -275,6 +281,7 @@ impl<'open> Window<'open> {
0.0 0.0
}; };
let margins = 2.0 * frame.margin + vec2(0.0, title_bar_height); let margins = 2.0 * frame.margin + vec2(0.0, title_bar_height);
let bounds = area.drag_bounds();
interact( interact(
window_interaction, window_interaction,
@ -283,6 +290,7 @@ impl<'open> Window<'open> {
area_layer_id, area_layer_id,
area.state_mut(), area.state_mut(),
resize_id, resize_id,
bounds,
) )
}) })
} else { } else {
@ -435,10 +443,16 @@ fn interact(
area_layer_id: LayerId, area_layer_id: LayerId,
area_state: &mut area::State, area_state: &mut area::State,
resize_id: Id, resize_id: Id,
drag_bounds: Option<Rect>,
) -> Option<WindowInteraction> { ) -> Option<WindowInteraction> {
let new_rect = move_and_resize_window(ctx, &window_interaction)?; let new_rect = move_and_resize_window(ctx, &window_interaction)?;
let new_rect = ctx.round_rect_to_pixels(new_rect); 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" // TODO: add this to a Window state instead as a command "move here next frame"
area_state.pos = new_rect.min; area_state.pos = new_rect.min;

View file

@ -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. /// so it fits within the screen.
pub(crate) fn constrain_window_rect(&self, window: Rect) -> Rect { 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. // Allow overlapping side bars.
// This is important for small screens, e.g. mobiles running the web demo. // This is important for small screens, e.g. mobiles running the web demo.
screen.max.x = self.input().screen_rect().max.x; area.max.x = self.input().screen_rect().max.x;
screen.min.x = self.input().screen_rect().min.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: // Allow overlapping top/bottom bars:
screen.max.y = self.input().screen_rect().max.y; area.max.y = self.input().screen_rect().max.y;
screen.min.y = self.input().screen_rect().min.y; area.min.y = self.input().screen_rect().min.y;
} }
let mut pos = window.min; let mut pos = window.min;
// Constrain to screen, unless window is too large to fit: // Constrain to screen, unless window is too large to fit:
let margin_x = (window.width() - screen.width()).at_least(0.0); let margin_x = (window.width() - area.width()).at_least(0.0);
let margin_y = (window.height() - screen.height()).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_most(area.right() + margin_x - window.width()); // move left if needed
pos.x = pos.x.at_least(screen.left() - margin_x); // move right if needed pos.x = pos.x.at_least(area.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_most(area.bottom() + margin_y - window.height()); // move right if needed
pos.y = pos.y.at_least(screen.top() - margin_y); // move down if needed pos.y = pos.y.at_least(area.top() - margin_y); // move down if needed
pos = self.round_pos_to_pixels(pos); pos = self.round_pos_to_pixels(pos);