A `Window` can now be resizable in only one direction (#4155)

For instance: `Window::new(…).resizable([true, false])` is a window that
is only resizable in the horizontal direction.

This PR also removes a hack added in
https://github.com/emilk/egui/pull/3039 which is no longer needed since
https://github.com/emilk/egui/pull/4026
This commit is contained in:
Emil Ernerfeldt 2024-03-11 09:29:48 +01:00 committed by GitHub
parent a93c6cd5d2
commit 00a399b2f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 91 additions and 64 deletions

View File

@ -20,10 +20,6 @@ pub(crate) struct State {
/// If false, clicks goes straight through to what is behind us.
/// Good for tooltips etc.
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 {
@ -75,7 +71,6 @@ pub struct Area {
pivot: Align2,
anchor: Option<(Align2, Vec2)>,
new_pos: Option<Pos2>,
edges_padded_for_resize: bool,
}
impl Area {
@ -93,7 +88,6 @@ impl Area {
new_pos: None,
pivot: Align2::LEFT_TOP,
anchor: None,
edges_padded_for_resize: false,
}
}
@ -227,14 +221,6 @@ impl Area {
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 {
@ -279,7 +265,6 @@ impl Area {
anchor,
constrain,
constrain_rect,
edges_padded_for_resize,
} = self;
let layer_id = LayerId::new(order, id);
@ -300,11 +285,9 @@ impl Area {
pivot,
size: Vec2::ZERO,
interactable,
edges_padded_for_resize,
});
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos);
state.interactable = interactable;
state.edges_padded_for_resize = edges_padded_for_resize;
if let Some((anchor, offset)) = anchor {
let screen = ctx.available_rect();

View File

@ -34,7 +34,7 @@ pub struct Resize {
id_source: Option<Id>,
/// If false, we are no enabled
resizable: bool,
resizable: Vec2b,
pub(crate) min_size: Vec2,
pub(crate) max_size: Vec2,
@ -49,7 +49,7 @@ impl Default for Resize {
Self {
id: None,
id_source: None,
resizable: true,
resizable: Vec2b::TRUE,
min_size: Vec2::splat(16.0),
max_size: Vec2::splat(f32::INFINITY),
default_size: vec2(320.0, 128.0), // TODO(emilk): preferred size of [`Resize`] area.
@ -152,12 +152,13 @@ impl Resize {
///
/// Default is `true`.
#[inline]
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
pub fn resizable(mut self, resizable: impl Into<Vec2b>) -> Self {
self.resizable = resizable.into();
self
}
pub fn is_resizable(&self) -> bool {
#[inline]
pub fn is_resizable(&self) -> Vec2b {
self.resizable
}
@ -175,7 +176,7 @@ impl Resize {
self.default_size = size;
self.min_size = size;
self.max_size = size;
self.resizable = false;
self.resizable = Vec2b::FALSE;
self
}
@ -226,7 +227,7 @@ impl Resize {
let mut user_requested_size = state.requested_size.take();
let corner_id = self.resizable.then(|| id.with("__resize_corner"));
let corner_id = self.resizable.any().then(|| id.with("__resize_corner"));
if let Some(corner_id) = corner_id {
if let Some(corner_response) = ui.ctx().read_response(corner_id) {
@ -299,18 +300,21 @@ impl Resize {
// ------------------------------
let size = if self.with_stroke || self.resizable {
// We show how large we are,
// so we must follow the contents:
let mut size = state.last_content_size;
for d in 0..2 {
if self.with_stroke || self.resizable[d] {
// We show how large we are,
// so we must follow the contents:
state.desired_size = state.desired_size.max(state.last_content_size);
state.desired_size[d] = state.desired_size[d].max(state.last_content_size[d]);
// We are as large as we look
state.desired_size
} else {
// Probably a window.
state.last_content_size
};
// We are as large as we look
size[d] = state.desired_size[d];
} else {
// Probably a window.
size[d] = state.last_content_size[d];
}
}
ui.advance_cursor_after_rect(Rect::from_min_size(content_ui.min_rect().min, size));
// ------------------------------

View File

@ -48,9 +48,7 @@ impl<'open> Window<'open> {
/// If you need a changing title, you must call `window.id(…)` with a fixed id.
pub fn new(title: impl Into<WidgetText>) -> Self {
let title = title.into().fallback_text_style(TextStyle::Heading);
let area = Area::new(Id::new(title.text()))
.constrain(true)
.edges_padded_for_resize(true);
let area = Area::new(Id::new(title.text())).constrain(true);
Self {
title,
open: None,
@ -119,9 +117,6 @@ impl<'open> Window<'open> {
#[inline]
pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self {
self.resize = mutate(self.resize);
self.area = self
.area
.edges_padded_for_resize(self.resize.is_resizable());
self
}
@ -278,7 +273,6 @@ impl<'open> Window<'open> {
#[inline]
pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
self.resize = self.resize.fixed_size(size);
self.area = self.area.edges_padded_for_resize(false);
self
}
@ -296,11 +290,15 @@ impl<'open> Window<'open> {
///
/// Note that even if you set this to `false` the window may still auto-resize.
///
/// You can set the window to only be resizable in one direction by using
/// e.g. `[true, false]` as the argument,
/// making the window only resizable in the x-direction.
///
/// Default is `true`.
#[inline]
pub fn resizable(mut self, resizable: bool) -> Self {
pub fn resizable(mut self, resizable: impl Into<Vec2b>) -> Self {
let resizable = resizable.into();
self.resize = self.resize.resizable(resizable);
self.area = self.area.edges_padded_for_resize(resizable);
self
}
@ -326,7 +324,6 @@ impl<'open> Window<'open> {
pub fn auto_sized(mut self) -> Self {
self.resize = self.resize.auto_sized();
self.scroll = ScrollArea::neither();
self.area = self.area.edges_padded_for_resize(false);
self
}
@ -589,7 +586,20 @@ fn paint_resize_corner(
} else if possible.resize_right && possible.resize_top {
(Align2::RIGHT_TOP, rounding.ne)
} else {
return;
// We're not in two directions, but it is still nice to tell the user
// we're resizable by painting the resize corner in the expected place
// (i.e. for windows only resizable in one direction):
if possible.resize_right || possible.resize_bottom {
(Align2::RIGHT_BOTTOM, rounding.se)
} else if possible.resize_left || possible.resize_bottom {
(Align2::LEFT_BOTTOM, rounding.sw)
} else if possible.resize_left || possible.resize_top {
(Align2::LEFT_TOP, rounding.nw)
} else if possible.resize_right || possible.resize_top {
(Align2::RIGHT_TOP, rounding.ne)
} else {
return;
}
};
// Adjust the corner offset to accommodate the stroke width and window rounding
@ -621,13 +631,15 @@ struct PossibleInteractions {
impl PossibleInteractions {
fn new(area: &Area, resize: &Resize, is_collapsed: bool) -> Self {
let movable = area.is_enabled() && area.is_movable();
let resizable = area.is_enabled() && resize.is_resizable() && !is_collapsed;
let resizable = resize
.is_resizable()
.and(area.is_enabled() && !is_collapsed);
let pivot = area.get_pivot();
Self {
resize_left: resizable && (movable || pivot.x() != Align::LEFT),
resize_right: resizable && (movable || pivot.x() != Align::RIGHT),
resize_top: resizable && (movable || pivot.y() != Align::TOP),
resize_bottom: resizable && (movable || pivot.y() != Align::BOTTOM),
resize_left: resizable.x && (movable || pivot.x() != Align::LEFT),
resize_right: resizable.x && (movable || pivot.x() != Align::RIGHT),
resize_top: resizable.y && (movable || pivot.y() != Align::TOP),
resize_bottom: resizable.y && (movable || pivot.y() != Align::BOTTOM),
}
}

View File

@ -496,7 +496,6 @@ impl ContextImpl {
pivot: Align2::LEFT_TOP,
size: screen_rect.size(),
interactable: true,
edges_padded_for_resize: false,
},
);
@ -2331,9 +2330,7 @@ impl Context {
/// Top-most layer at the given position.
pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
self.memory(|mem| {
mem.layer_id_at(pos, mem.options.style.interaction.resize_grab_radius_side)
})
self.memory(|mem| mem.layer_id_at(pos))
}
/// Moves the given area to the top in its [`Order`].

View File

@ -646,9 +646,8 @@ impl Memory {
}
/// Top-most layer at the given position.
pub fn layer_id_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<LayerId> {
self.areas()
.layer_id_at(pos, resize_interact_radius_side, &self.layer_transforms)
pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
self.areas().layer_id_at(pos, &self.layer_transforms)
}
/// An iterator over all layers. Back-to-front. Top is last.
@ -921,7 +920,6 @@ impl Areas {
pub fn layer_id_at(
&self,
pos: Pos2,
resize_interact_radius_side: f32,
layer_transforms: &HashMap<LayerId, TSTransform>,
) -> Option<LayerId> {
for layer in self.order.iter().rev() {
@ -929,11 +927,6 @@ impl Areas {
if let Some(state) = self.areas.get(&layer.id) {
let mut rect = state.rect();
if state.interactable {
if state.edges_padded_for_resize {
// Allow us to resize by dragging just outside the window:
rect = rect.expand(resize_interact_radius_side);
}
if let Some(transform) = layer_transforms.get(layer) {
rect = *transform * rect;
}

View File

@ -13,6 +13,7 @@ impl super::Demo for About {
.default_width(320.0)
.default_height(480.0)
.open(open)
.resizable([true, false])
.show(ctx, |ui| {
use super::View as _;
self.ui(ui);

View File

@ -75,6 +75,7 @@ impl super::Demo for CodeExample {
.default_size([800.0, 400.0])
.vscroll(false)
.hscroll(true)
.resizable([true, false])
.show(ctx, |ui| self.ui(ui));
}
}

View File

@ -50,7 +50,7 @@ impl super::Demo for WidgetGallery {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(true)
.resizable([true, false])
.default_width(280.0)
.show(ctx, |ui| {
use super::View as _;

View File

@ -1,5 +1,7 @@
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use crate::Vec2b;
/// A vector has a direction and length.
/// A [`Vec2`] is often used to represent a size.
///
@ -86,6 +88,16 @@ impl From<&Vec2> for (f32, f32) {
}
}
impl From<Vec2b> for Vec2 {
#[inline(always)]
fn from(v: Vec2b) -> Self {
Self {
x: v.x as i32 as f32,
y: v.y as i32 as f32,
}
}
}
// ----------------------------------------------------------------------------
// Mint compatibility and convenience conversions

View File

@ -19,6 +19,30 @@ impl Vec2b {
pub fn any(&self) -> bool {
self.x || self.y
}
/// Are both `x` and `y` true?
#[inline]
pub fn all(&self) -> bool {
self.x && self.y
}
#[inline]
pub fn and(&self, other: impl Into<Self>) -> Self {
let other = other.into();
Self {
x: self.x && other.x,
y: self.y && other.y,
}
}
#[inline]
pub fn or(&self, other: impl Into<Self>) -> Self {
let other = other.into();
Self {
x: self.x || other.x,
y: self.y || other.y,
}
}
}
impl From<bool> for Vec2b {