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.
|
||||
* 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 🐛
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ pub struct Area {
|
|||
order: Order,
|
||||
default_pos: Option<Pos2>,
|
||||
new_pos: Option<Pos2>,
|
||||
drag_bounds: Option<Rect>,
|
||||
}
|
||||
|
||||
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<Rect>,
|
||||
}
|
||||
|
||||
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<Rect> {
|
||||
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)
|
||||
|
|
|
@ -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<Rect>,
|
||||
) -> Option<WindowInteraction> {
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue