Add optional drag bounds to Area and Window
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
c212d4512e
commit
c1ef81628b
4 changed files with 59 additions and 17 deletions
|
@ -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 🐛
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue