Skip to content

Commit

Permalink
A Window can now be resizable in only one direction (#4155)
Browse files Browse the repository at this point in the history
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
#3039 which is no longer needed since
#4026
  • Loading branch information
emilk authored Mar 11, 2024
1 parent a93c6cd commit 00a399b
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 64 deletions.
17 changes: 0 additions & 17 deletions crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -75,7 +71,6 @@ pub struct Area {
pivot: Align2,
anchor: Option<(Align2, Vec2)>,
new_pos: Option<Pos2>,
edges_padded_for_resize: bool,
}

impl Area {
Expand All @@ -93,7 +88,6 @@ impl Area {
new_pos: None,
pivot: Align2::LEFT_TOP,
anchor: None,
edges_padded_for_resize: false,
}
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -279,7 +265,6 @@ impl Area {
anchor,
constrain,
constrain_rect,
edges_padded_for_resize,
} = self;

let layer_id = LayerId::new(order, id);
Expand All @@ -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();
Expand Down
38 changes: 21 additions & 17 deletions crates/egui/src/containers/resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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.
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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));

// ------------------------------
Expand Down
44 changes: 28 additions & 16 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}

Expand All @@ -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
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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),
}
}

Expand Down
5 changes: 1 addition & 4 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,6 @@ impl ContextImpl {
pivot: Align2::LEFT_TOP,
size: screen_rect.size(),
interactable: true,
edges_padded_for_resize: false,
},
);

Expand Down Expand Up @@ -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`].
Expand Down
11 changes: 2 additions & 9 deletions crates/egui/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -921,19 +920,13 @@ 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() {
if self.is_visible(layer) {
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;
}
Expand Down
1 change: 1 addition & 0 deletions crates/egui_demo_lib/src/demo/about.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions crates/egui_demo_lib/src/demo/code_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/egui_demo_lib/src/demo/widget_gallery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _;
Expand Down
12 changes: 12 additions & 0 deletions crates/emath/src/vec2.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand Down Expand Up @@ -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

Expand Down
24 changes: 24 additions & 0 deletions crates/emath/src/vec2b.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 00a399b

Please sign in to comment.