Add `Area::constrain_to` and `Window::constrain_to` (#3396)
* Rename `drag_bounds` to `constrain_to` and allow turning off constraining for `Window`s * Constrain DatePicker popup to screen * Fix typo
This commit is contained in:
parent
1911248ade
commit
1b18f8e266
|
|
@ -65,12 +65,12 @@ pub struct Area {
|
||||||
interactable: bool,
|
interactable: bool,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
constrain: bool,
|
constrain: bool,
|
||||||
|
constrain_rect: Option<Rect>,
|
||||||
order: Order,
|
order: Order,
|
||||||
default_pos: Option<Pos2>,
|
default_pos: Option<Pos2>,
|
||||||
pivot: Align2,
|
pivot: Align2,
|
||||||
anchor: Option<(Align2, Vec2)>,
|
anchor: Option<(Align2, Vec2)>,
|
||||||
new_pos: Option<Pos2>,
|
new_pos: Option<Pos2>,
|
||||||
drag_bounds: Option<Rect>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Area {
|
impl Area {
|
||||||
|
|
@ -80,13 +80,13 @@ impl Area {
|
||||||
movable: true,
|
movable: true,
|
||||||
interactable: true,
|
interactable: true,
|
||||||
constrain: false,
|
constrain: false,
|
||||||
|
constrain_rect: None,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
order: Order::Middle,
|
order: Order::Middle,
|
||||||
default_pos: None,
|
default_pos: None,
|
||||||
new_pos: None,
|
new_pos: None,
|
||||||
pivot: Align2::LEFT_TOP,
|
pivot: Align2::LEFT_TOP,
|
||||||
anchor: None,
|
anchor: None,
|
||||||
drag_bounds: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,6 +155,21 @@ impl Area {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constraint the movement of the window to the given rectangle.
|
||||||
|
///
|
||||||
|
/// For instance: `.constrain_to(ctx.screen_rect())`.
|
||||||
|
pub fn constrain_to(mut self, constrain_rect: Rect) -> Self {
|
||||||
|
self.constrain = true;
|
||||||
|
self.constrain_rect = Some(constrain_rect);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Use `constrain_to` instead"]
|
||||||
|
pub fn drag_bounds(mut self, constrain_rect: Rect) -> Self {
|
||||||
|
self.constrain_rect = Some(constrain_rect);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Where the "root" of the area is.
|
/// Where the "root" of the area is.
|
||||||
///
|
///
|
||||||
/// For instance, if you set this to [`Align2::RIGHT_TOP`]
|
/// For instance, if you set this to [`Align2::RIGHT_TOP`]
|
||||||
|
|
@ -189,12 +204,6 @@ impl Area {
|
||||||
self.movable(false)
|
self.movable(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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) fn get_pivot(&self) -> Align2 {
|
pub(crate) fn get_pivot(&self) -> Align2 {
|
||||||
if let Some((pivot, _)) = self.anchor {
|
if let Some((pivot, _)) = self.anchor {
|
||||||
pivot
|
pivot
|
||||||
|
|
@ -209,7 +218,8 @@ pub(crate) struct Prepared {
|
||||||
state: State,
|
state: State,
|
||||||
move_response: Response,
|
move_response: Response,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
drag_bounds: Option<Rect>,
|
constrain: bool,
|
||||||
|
constrain_rect: Option<Rect>,
|
||||||
|
|
||||||
/// We always make windows invisible the first frame to hide "first-frame-jitters".
|
/// We always make windows invisible the first frame to hide "first-frame-jitters".
|
||||||
///
|
///
|
||||||
|
|
@ -243,8 +253,8 @@ impl Area {
|
||||||
new_pos,
|
new_pos,
|
||||||
pivot,
|
pivot,
|
||||||
anchor,
|
anchor,
|
||||||
drag_bounds,
|
|
||||||
constrain,
|
constrain,
|
||||||
|
constrain_rect,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let layer_id = LayerId::new(order, id);
|
let layer_id = LayerId::new(order, id);
|
||||||
|
|
@ -308,7 +318,7 @@ impl Area {
|
||||||
|
|
||||||
if constrain {
|
if constrain {
|
||||||
state.set_left_top_pos(
|
state.set_left_top_pos(
|
||||||
ctx.constrain_window_rect_to_area(state.rect(), drag_bounds)
|
ctx.constrain_window_rect_to_area(state.rect(), constrain_rect)
|
||||||
.min,
|
.min,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -323,7 +333,8 @@ impl Area {
|
||||||
state,
|
state,
|
||||||
move_response,
|
move_response,
|
||||||
enabled,
|
enabled,
|
||||||
drag_bounds,
|
constrain,
|
||||||
|
constrain_rect,
|
||||||
temporarily_invisible: is_new,
|
temporarily_invisible: is_new,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -366,15 +377,19 @@ impl Prepared {
|
||||||
&mut self.state
|
&mut self.state
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn drag_bounds(&self) -> Option<Rect> {
|
pub(crate) fn constrain(&self) -> bool {
|
||||||
self.drag_bounds
|
self.constrain
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn constrain_rect(&self) -> Option<Rect> {
|
||||||
|
self.constrain_rect
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn content_ui(&self, ctx: &Context) -> Ui {
|
pub(crate) fn content_ui(&self, ctx: &Context) -> Ui {
|
||||||
let screen_rect = ctx.screen_rect();
|
let screen_rect = ctx.screen_rect();
|
||||||
|
|
||||||
let bounds = if let Some(bounds) = self.drag_bounds {
|
let constrain_rect = if let Some(constrain_rect) = self.constrain_rect {
|
||||||
bounds.intersect(screen_rect) // protect against infinite bounds
|
constrain_rect.intersect(screen_rect) // protect against infinite bounds
|
||||||
} else {
|
} else {
|
||||||
let central_area = ctx.available_rect();
|
let central_area = ctx.available_rect();
|
||||||
|
|
||||||
|
|
@ -388,7 +403,7 @@ impl Prepared {
|
||||||
|
|
||||||
let max_rect = Rect::from_min_max(
|
let max_rect = Rect::from_min_max(
|
||||||
self.state.left_top_pos(),
|
self.state.left_top_pos(),
|
||||||
bounds
|
constrain_rect
|
||||||
.max
|
.max
|
||||||
.at_least(self.state.left_top_pos() + Vec2::splat(32.0)),
|
.at_least(self.state.left_top_pos() + Vec2::splat(32.0)),
|
||||||
);
|
);
|
||||||
|
|
@ -396,9 +411,9 @@ impl Prepared {
|
||||||
let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky
|
let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky
|
||||||
let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius);
|
let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius);
|
||||||
|
|
||||||
let clip_rect = Rect::from_min_max(self.state.left_top_pos(), bounds.max)
|
let clip_rect = Rect::from_min_max(self.state.left_top_pos(), constrain_rect.max)
|
||||||
.expand(clip_rect_margin)
|
.expand(clip_rect_margin)
|
||||||
.intersect(bounds);
|
.intersect(constrain_rect);
|
||||||
|
|
||||||
let mut ui = Ui::new(
|
let mut ui = Ui::new(
|
||||||
ctx.clone(),
|
ctx.clone(),
|
||||||
|
|
@ -419,7 +434,8 @@ impl Prepared {
|
||||||
mut state,
|
mut state,
|
||||||
move_response,
|
move_response,
|
||||||
enabled: _,
|
enabled: _,
|
||||||
drag_bounds: _,
|
constrain: _,
|
||||||
|
constrain_rect: _,
|
||||||
temporarily_invisible: _,
|
temporarily_invisible: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -260,9 +260,8 @@ fn show_tooltip_area_dyn<'c, R>(
|
||||||
Area::new(area_id)
|
Area::new(area_id)
|
||||||
.order(Order::Tooltip)
|
.order(Order::Tooltip)
|
||||||
.fixed_pos(window_pos)
|
.fixed_pos(window_pos)
|
||||||
.constrain(true)
|
.constrain_to(ctx.screen_rect())
|
||||||
.interactable(false)
|
.interactable(false)
|
||||||
.drag_bounds(ctx.screen_rect())
|
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
Frame::popup(&ctx.style())
|
Frame::popup(&ctx.style())
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ 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()));
|
let area = Area::new(Id::new(title.text())).constrain(true);
|
||||||
Self {
|
Self {
|
||||||
title,
|
title,
|
||||||
open: None,
|
open: None,
|
||||||
|
|
@ -146,11 +146,31 @@ impl<'open> Window<'open> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constrains this window to the screen bounds.
|
/// Constrains this window to the screen bounds.
|
||||||
|
///
|
||||||
|
/// To change the area to constrain to, use [`Self::constraint_to`].
|
||||||
|
///
|
||||||
|
/// Default: `true`.
|
||||||
pub fn constrain(mut self, constrain: bool) -> Self {
|
pub fn constrain(mut self, constrain: bool) -> Self {
|
||||||
self.area = self.area.constrain(constrain);
|
self.area = self.area.constrain(constrain);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constraint the movement of the window to the given rectangle.
|
||||||
|
///
|
||||||
|
/// For instance: `.constrain_to(ctx.screen_rect())`.
|
||||||
|
pub fn constraint_to(mut self, constrain_rect: Rect) -> Self {
|
||||||
|
self.area = self.area.constrain_to(constrain_rect);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Use `constrain_to` instead"]
|
||||||
|
pub fn drag_bounds(mut self, constrain_rect: Rect) -> Self {
|
||||||
|
#![allow(deprecated)]
|
||||||
|
|
||||||
|
self.area = self.area.drag_bounds(constrain_rect);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Where the "root" of the window is.
|
/// Where the "root" of the window is.
|
||||||
///
|
///
|
||||||
/// For instance, if you set this to [`Align2::RIGHT_TOP`]
|
/// For instance, if you set this to [`Align2::RIGHT_TOP`]
|
||||||
|
|
@ -276,12 +296,6 @@ impl<'open> Window<'open> {
|
||||||
self.scroll = self.scroll.drag_to_scroll(drag_to_scroll);
|
self.scroll = self.scroll.drag_to_scroll(drag_to_scroll);
|
||||||
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> {
|
||||||
|
|
@ -452,13 +466,6 @@ impl<'open> Window<'open> {
|
||||||
content_inner
|
content_inner
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
|
||||||
let pos = ctx
|
|
||||||
.constrain_window_rect_to_area(area.state().rect(), area.drag_bounds())
|
|
||||||
.left_top();
|
|
||||||
area.state_mut().set_left_top_pos(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
let full_response = area.end(ctx, area_content_ui);
|
let full_response = area.end(ctx, area_content_ui);
|
||||||
|
|
||||||
let inner_response = InnerResponse {
|
let inner_response = InnerResponse {
|
||||||
|
|
@ -562,9 +569,11 @@ fn interact(
|
||||||
resize_id: Id,
|
resize_id: Id,
|
||||||
) -> 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 mut new_rect = ctx.round_rect_to_pixels(new_rect);
|
||||||
|
|
||||||
let new_rect = ctx.constrain_window_rect_to_area(new_rect, area.drag_bounds());
|
if area.constrain() {
|
||||||
|
new_rect = ctx.constrain_window_rect_to_area(new_rect, area.constrain_rect());
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(emilk): add this to a Window state instead as a command "move here next frame"
|
// TODO(emilk): add this to a Window state instead as a command "move here next frame"
|
||||||
area.state_mut().set_left_top_pos(new_rect.left_top());
|
area.state_mut().set_left_top_pos(new_rect.left_top());
|
||||||
|
|
|
||||||
|
|
@ -146,10 +146,9 @@ pub(crate) fn menu_ui<'c, R>(
|
||||||
|
|
||||||
let area = Area::new(menu_id)
|
let area = Area::new(menu_id)
|
||||||
.order(Order::Foreground)
|
.order(Order::Foreground)
|
||||||
.constrain(true)
|
|
||||||
.fixed_pos(pos)
|
.fixed_pos(pos)
|
||||||
.interactable(true)
|
.constrain_to(ctx.screen_rect())
|
||||||
.drag_bounds(ctx.screen_rect());
|
.interactable(true);
|
||||||
|
|
||||||
area.show(ctx, |ui| {
|
area.show(ctx, |ui| {
|
||||||
set_menu_style(ui.style_mut());
|
set_menu_style(ui.style_mut());
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ pub struct WindowOptions {
|
||||||
closable: bool,
|
closable: bool,
|
||||||
collapsible: bool,
|
collapsible: bool,
|
||||||
resizable: bool,
|
resizable: bool,
|
||||||
|
constrain: bool,
|
||||||
scroll2: [bool; 2],
|
scroll2: [bool; 2],
|
||||||
disabled_time: f64,
|
disabled_time: f64,
|
||||||
|
|
||||||
|
|
@ -22,6 +23,7 @@ impl Default for WindowOptions {
|
||||||
closable: true,
|
closable: true,
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
constrain: true,
|
||||||
scroll2: [true; 2],
|
scroll2: [true; 2],
|
||||||
disabled_time: f64::NEG_INFINITY,
|
disabled_time: f64::NEG_INFINITY,
|
||||||
anchored: false,
|
anchored: false,
|
||||||
|
|
@ -43,6 +45,7 @@ impl super::Demo for WindowOptions {
|
||||||
closable,
|
closable,
|
||||||
collapsible,
|
collapsible,
|
||||||
resizable,
|
resizable,
|
||||||
|
constrain,
|
||||||
scroll2,
|
scroll2,
|
||||||
disabled_time,
|
disabled_time,
|
||||||
anchored,
|
anchored,
|
||||||
|
|
@ -59,6 +62,7 @@ impl super::Demo for WindowOptions {
|
||||||
let mut window = egui::Window::new(title)
|
let mut window = egui::Window::new(title)
|
||||||
.id(egui::Id::new("demo_window_options")) // required since we change the title
|
.id(egui::Id::new("demo_window_options")) // required since we change the title
|
||||||
.resizable(resizable)
|
.resizable(resizable)
|
||||||
|
.constrain(constrain)
|
||||||
.collapsible(collapsible)
|
.collapsible(collapsible)
|
||||||
.title_bar(title_bar)
|
.title_bar(title_bar)
|
||||||
.scroll2(scroll2)
|
.scroll2(scroll2)
|
||||||
|
|
@ -81,6 +85,7 @@ impl super::View for WindowOptions {
|
||||||
closable,
|
closable,
|
||||||
collapsible,
|
collapsible,
|
||||||
resizable,
|
resizable,
|
||||||
|
constrain,
|
||||||
scroll2,
|
scroll2,
|
||||||
disabled_time: _,
|
disabled_time: _,
|
||||||
anchored,
|
anchored,
|
||||||
|
|
@ -99,6 +104,8 @@ impl super::View for WindowOptions {
|
||||||
ui.checkbox(closable, "closable");
|
ui.checkbox(closable, "closable");
|
||||||
ui.checkbox(collapsible, "collapsible");
|
ui.checkbox(collapsible, "collapsible");
|
||||||
ui.checkbox(resizable, "resizable");
|
ui.checkbox(resizable, "resizable");
|
||||||
|
ui.checkbox(constrain, "constrain")
|
||||||
|
.on_hover_text("Constrain window to the screen");
|
||||||
ui.checkbox(&mut scroll2[0], "hscroll");
|
ui.checkbox(&mut scroll2[0], "hscroll");
|
||||||
ui.checkbox(&mut scroll2[1], "vscroll");
|
ui.checkbox(&mut scroll2[1], "vscroll");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ impl<'a> Widget for DatePickerButton<'a> {
|
||||||
} = Area::new(ui.make_persistent_id(self.id_source))
|
} = Area::new(ui.make_persistent_id(self.id_source))
|
||||||
.order(Order::Foreground)
|
.order(Order::Foreground)
|
||||||
.fixed_pos(pos)
|
.fixed_pos(pos)
|
||||||
|
.constrain_to(ui.ctx().screen_rect())
|
||||||
.show(ui.ctx(), |ui| {
|
.show(ui.ctx(), |ui| {
|
||||||
let frame = Frame::popup(ui.style());
|
let frame = Frame::popup(ui.style());
|
||||||
frame
|
frame
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue