Have non-resizable Areas ignore mouse input just beyond their bounds (#3039)
As described in #576, the method `Memory::layer_id_at` expands the hit-testing region for every `Area` slightly. This is necessary so that, when the user clicks to resize a `Window`, the mouse input isn't routed to a different `Window`. For non-resizable `Area`s (such as dropdown menus, context menus, date pickers, and any non-resizable `Window`), this causes them to be surrounded by a "dead zone" where any underlying widgets can't be hovered or clicked. The effect is particularly noticeable in menu bars. This commit adds a persisted `edges_padded_for_resize` property to `Area`, which is `true` when the `Area` belongs to a resizable `Window` and `false` for all other `Area`s. The hit-testing region is only expanded when this property is `true`. --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
1777bb7789
commit
5432f91a4d
|
|
@ -20,6 +20,10 @@ pub(crate) struct State {
|
||||||
/// If false, clicks goes straight through to what is behind us.
|
/// If false, clicks goes straight through to what is behind us.
|
||||||
/// Good for tooltips etc.
|
/// Good for tooltips etc.
|
||||||
pub interactable: bool,
|
pub interactable: bool,
|
||||||
|
|
||||||
|
/// When `true`, this `Area` belongs to a resizable window, so it needs to
|
||||||
|
/// receive mouse input which occurs a short distance beyond its bounding rect.
|
||||||
|
pub edges_padded_for_resize: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
|
@ -71,6 +75,7 @@ pub struct Area {
|
||||||
pivot: Align2,
|
pivot: Align2,
|
||||||
anchor: Option<(Align2, Vec2)>,
|
anchor: Option<(Align2, Vec2)>,
|
||||||
new_pos: Option<Pos2>,
|
new_pos: Option<Pos2>,
|
||||||
|
edges_padded_for_resize: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Area {
|
impl Area {
|
||||||
|
|
@ -87,6 +92,7 @@ impl Area {
|
||||||
new_pos: None,
|
new_pos: None,
|
||||||
pivot: Align2::LEFT_TOP,
|
pivot: Align2::LEFT_TOP,
|
||||||
anchor: None,
|
anchor: None,
|
||||||
|
edges_padded_for_resize: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,6 +223,14 @@ impl Area {
|
||||||
Align2::LEFT_TOP
|
Align2::LEFT_TOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When `true`, this `Area` belongs to a resizable window, so it needs to
|
||||||
|
/// receive mouse input which occurs a short distance beyond its bounding rect.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn edges_padded_for_resize(mut self, edges_padded_for_resize: bool) -> Self {
|
||||||
|
self.edges_padded_for_resize = edges_padded_for_resize;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Prepared {
|
pub(crate) struct Prepared {
|
||||||
|
|
@ -261,6 +275,7 @@ impl Area {
|
||||||
anchor,
|
anchor,
|
||||||
constrain,
|
constrain,
|
||||||
constrain_rect,
|
constrain_rect,
|
||||||
|
edges_padded_for_resize,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let layer_id = LayerId::new(order, id);
|
let layer_id = LayerId::new(order, id);
|
||||||
|
|
@ -281,9 +296,11 @@ impl Area {
|
||||||
pivot,
|
pivot,
|
||||||
size: Vec2::ZERO,
|
size: Vec2::ZERO,
|
||||||
interactable,
|
interactable,
|
||||||
|
edges_padded_for_resize,
|
||||||
});
|
});
|
||||||
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos);
|
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos);
|
||||||
state.interactable = interactable;
|
state.interactable = interactable;
|
||||||
|
state.edges_padded_for_resize = edges_padded_for_resize;
|
||||||
|
|
||||||
if let Some((anchor, offset)) = anchor {
|
if let Some((anchor, offset)) = anchor {
|
||||||
let screen = ctx.available_rect();
|
let screen = ctx.available_rect();
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,9 @@ impl<'open> Window<'open> {
|
||||||
/// If you need a changing title, you must call `window.id(…)` with a fixed id.
|
/// If you need a changing title, you must call `window.id(…)` with a fixed id.
|
||||||
pub fn new(title: impl Into<WidgetText>) -> Self {
|
pub fn new(title: impl Into<WidgetText>) -> Self {
|
||||||
let title = title.into().fallback_text_style(TextStyle::Heading);
|
let title = title.into().fallback_text_style(TextStyle::Heading);
|
||||||
let area = Area::new(Id::new(title.text())).constrain(true);
|
let area = Area::new(Id::new(title.text()))
|
||||||
|
.constrain(true)
|
||||||
|
.edges_padded_for_resize(true);
|
||||||
Self {
|
Self {
|
||||||
title,
|
title,
|
||||||
open: None,
|
open: None,
|
||||||
|
|
@ -117,6 +119,9 @@ impl<'open> Window<'open> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self {
|
pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self {
|
||||||
self.resize = mutate(self.resize);
|
self.resize = mutate(self.resize);
|
||||||
|
self.area = self
|
||||||
|
.area
|
||||||
|
.edges_padded_for_resize(self.resize.is_resizable());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,6 +278,7 @@ impl<'open> Window<'open> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
|
pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
|
||||||
self.resize = self.resize.fixed_size(size);
|
self.resize = self.resize.fixed_size(size);
|
||||||
|
self.area = self.area.edges_padded_for_resize(false);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,6 +300,7 @@ impl<'open> Window<'open> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn resizable(mut self, resizable: bool) -> Self {
|
pub fn resizable(mut self, resizable: bool) -> Self {
|
||||||
self.resize = self.resize.resizable(resizable);
|
self.resize = self.resize.resizable(resizable);
|
||||||
|
self.area = self.area.edges_padded_for_resize(resizable);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,6 +326,7 @@ impl<'open> Window<'open> {
|
||||||
pub fn auto_sized(mut self) -> Self {
|
pub fn auto_sized(mut self) -> Self {
|
||||||
self.resize = self.resize.auto_sized();
|
self.resize = self.resize.auto_sized();
|
||||||
self.scroll = ScrollArea::neither();
|
self.scroll = ScrollArea::neither();
|
||||||
|
self.area = self.area.edges_padded_for_resize(false);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -317,6 +317,7 @@ impl ContextImpl {
|
||||||
pivot: Align2::LEFT_TOP,
|
pivot: Align2::LEFT_TOP,
|
||||||
size: screen_rect.size(),
|
size: screen_rect.size(),
|
||||||
interactable: true,
|
interactable: true,
|
||||||
|
edges_padded_for_resize: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -862,8 +862,11 @@ impl Areas {
|
||||||
if let Some(state) = self.areas.get(&layer.id) {
|
if let Some(state) = self.areas.get(&layer.id) {
|
||||||
let mut rect = state.rect();
|
let mut rect = state.rect();
|
||||||
if state.interactable {
|
if state.interactable {
|
||||||
// Allow us to resize by dragging just outside the window:
|
if state.edges_padded_for_resize {
|
||||||
rect = rect.expand(resize_interact_radius_side);
|
// Allow us to resize by dragging just outside the window:
|
||||||
|
rect = rect.expand(resize_interact_radius_side);
|
||||||
|
}
|
||||||
|
|
||||||
if rect.contains(pos) {
|
if rect.contains(pos) {
|
||||||
return Some(*layer);
|
return Some(*layer);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue