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. /// 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 {
@ -75,7 +71,6 @@ 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 {
@ -93,7 +88,6 @@ 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,
} }
} }
@ -227,14 +221,6 @@ 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 {
@ -279,7 +265,6 @@ 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);
@ -300,11 +285,9 @@ 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();

View File

@ -34,7 +34,7 @@ pub struct Resize {
id_source: Option<Id>, id_source: Option<Id>,
/// If false, we are no enabled /// If false, we are no enabled
resizable: bool, resizable: Vec2b,
pub(crate) min_size: Vec2, pub(crate) min_size: Vec2,
pub(crate) max_size: Vec2, pub(crate) max_size: Vec2,
@ -49,7 +49,7 @@ impl Default for Resize {
Self { Self {
id: None, id: None,
id_source: None, id_source: None,
resizable: true, resizable: Vec2b::TRUE,
min_size: Vec2::splat(16.0), min_size: Vec2::splat(16.0),
max_size: Vec2::splat(f32::INFINITY), max_size: Vec2::splat(f32::INFINITY),
default_size: vec2(320.0, 128.0), // TODO(emilk): preferred size of [`Resize`] area. default_size: vec2(320.0, 128.0), // TODO(emilk): preferred size of [`Resize`] area.
@ -152,12 +152,13 @@ impl Resize {
/// ///
/// Default is `true`. /// Default is `true`.
#[inline] #[inline]
pub fn resizable(mut self, resizable: bool) -> Self { pub fn resizable(mut self, resizable: impl Into<Vec2b>) -> Self {
self.resizable = resizable; self.resizable = resizable.into();
self self
} }
pub fn is_resizable(&self) -> bool { #[inline]
pub fn is_resizable(&self) -> Vec2b {
self.resizable self.resizable
} }
@ -175,7 +176,7 @@ impl Resize {
self.default_size = size; self.default_size = size;
self.min_size = size; self.min_size = size;
self.max_size = size; self.max_size = size;
self.resizable = false; self.resizable = Vec2b::FALSE;
self self
} }
@ -226,7 +227,7 @@ impl Resize {
let mut user_requested_size = state.requested_size.take(); 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_id) = corner_id {
if let Some(corner_response) = ui.ctx().read_response(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 { let mut size = state.last_content_size;
// We show how large we are, for d in 0..2 {
// so we must follow the contents: 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 // We are as large as we look
state.desired_size size[d] = state.desired_size[d];
} else { } else {
// Probably a window. // Probably a window.
state.last_content_size size[d] = state.last_content_size[d];
}; }
}
ui.advance_cursor_after_rect(Rect::from_min_size(content_ui.min_rect().min, size)); 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. /// 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);
.constrain(true)
.edges_padded_for_resize(true);
Self { Self {
title, title,
open: None, open: None,
@ -119,9 +117,6 @@ 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
} }
@ -278,7 +273,6 @@ 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
} }
@ -296,11 +290,15 @@ impl<'open> Window<'open> {
/// ///
/// Note that even if you set this to `false` the window may still auto-resize. /// 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`. /// Default is `true`.
#[inline] #[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.resize = self.resize.resizable(resizable);
self.area = self.area.edges_padded_for_resize(resizable);
self self
} }
@ -326,7 +324,6 @@ 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
} }
@ -589,7 +586,20 @@ fn paint_resize_corner(
} else if possible.resize_right && possible.resize_top { } else if possible.resize_right && possible.resize_top {
(Align2::RIGHT_TOP, rounding.ne) (Align2::RIGHT_TOP, rounding.ne)
} else { } 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 // Adjust the corner offset to accommodate the stroke width and window rounding
@ -621,13 +631,15 @@ struct PossibleInteractions {
impl PossibleInteractions { impl PossibleInteractions {
fn new(area: &Area, resize: &Resize, is_collapsed: bool) -> Self { fn new(area: &Area, resize: &Resize, is_collapsed: bool) -> Self {
let movable = area.is_enabled() && area.is_movable(); 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(); let pivot = area.get_pivot();
Self { Self {
resize_left: resizable && (movable || pivot.x() != Align::LEFT), resize_left: resizable.x && (movable || pivot.x() != Align::LEFT),
resize_right: resizable && (movable || pivot.x() != Align::RIGHT), resize_right: resizable.x && (movable || pivot.x() != Align::RIGHT),
resize_top: resizable && (movable || pivot.y() != Align::TOP), resize_top: resizable.y && (movable || pivot.y() != Align::TOP),
resize_bottom: resizable && (movable || pivot.y() != Align::BOTTOM), resize_bottom: resizable.y && (movable || pivot.y() != Align::BOTTOM),
} }
} }

View File

@ -496,7 +496,6 @@ 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,
}, },
); );
@ -2331,9 +2330,7 @@ impl Context {
/// Top-most layer at the given position. /// Top-most layer at the given position.
pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> { pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
self.memory(|mem| { self.memory(|mem| mem.layer_id_at(pos))
mem.layer_id_at(pos, mem.options.style.interaction.resize_grab_radius_side)
})
} }
/// Moves the given area to the top in its [`Order`]. /// 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. /// Top-most layer at the given position.
pub fn layer_id_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<LayerId> { pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
self.areas() self.areas().layer_id_at(pos, &self.layer_transforms)
.layer_id_at(pos, resize_interact_radius_side, &self.layer_transforms)
} }
/// An iterator over all layers. Back-to-front. Top is last. /// An iterator over all layers. Back-to-front. Top is last.
@ -921,7 +920,6 @@ impl Areas {
pub fn layer_id_at( pub fn layer_id_at(
&self, &self,
pos: Pos2, pos: Pos2,
resize_interact_radius_side: f32,
layer_transforms: &HashMap<LayerId, TSTransform>, layer_transforms: &HashMap<LayerId, TSTransform>,
) -> Option<LayerId> { ) -> Option<LayerId> {
for layer in self.order.iter().rev() { for layer in self.order.iter().rev() {
@ -929,11 +927,6 @@ 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 {
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) { if let Some(transform) = layer_transforms.get(layer) {
rect = *transform * rect; rect = *transform * rect;
} }

View File

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

View File

@ -75,6 +75,7 @@ impl super::Demo for CodeExample {
.default_size([800.0, 400.0]) .default_size([800.0, 400.0])
.vscroll(false) .vscroll(false)
.hscroll(true) .hscroll(true)
.resizable([true, false])
.show(ctx, |ui| self.ui(ui)); .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) { fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name()) egui::Window::new(self.name())
.open(open) .open(open)
.resizable(true) .resizable([true, false])
.default_width(280.0) .default_width(280.0)
.show(ctx, |ui| { .show(ctx, |ui| {
use super::View as _; use super::View as _;

View File

@ -1,5 +1,7 @@
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use crate::Vec2b;
/// A vector has a direction and length. /// A vector has a direction and length.
/// A [`Vec2`] is often used to represent a size. /// 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 // Mint compatibility and convenience conversions

View File

@ -19,6 +19,30 @@ impl Vec2b {
pub fn any(&self) -> bool { pub fn any(&self) -> bool {
self.x || self.y 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 { impl From<bool> for Vec2b {