diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 7a9111f64a8..3c6b711be93 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -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, - 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(); diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index ef0d045de6f..e2654ca5a6c 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -34,7 +34,7 @@ pub struct Resize { id_source: Option, /// 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) -> 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)); // ------------------------------ diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index eaea0f5ac2b..698033cf9c4 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -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) -> 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) -> 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) -> 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), } } diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 996f7a7ec5f..8e09414a6be 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -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 { - 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`]. diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index fcfe0accb42..fb18258eb12 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -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 { - self.areas() - .layer_id_at(pos, resize_interact_radius_side, &self.layer_transforms) + pub fn layer_id_at(&self, pos: Pos2) -> Option { + 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, ) -> Option { 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; } diff --git a/crates/egui_demo_lib/src/demo/about.rs b/crates/egui_demo_lib/src/demo/about.rs index 56e5c902060..f3143e3c7ee 100644 --- a/crates/egui_demo_lib/src/demo/about.rs +++ b/crates/egui_demo_lib/src/demo/about.rs @@ -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); diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index b4adf8fb50c..6786e55a14c 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -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)); } } diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index ac55a4d01e1..d68af17db80 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -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 _; diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index 2c5ceeedfac..0cab00fe62f 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -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 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 diff --git a/crates/emath/src/vec2b.rs b/crates/emath/src/vec2b.rs index dc5eb0ea56b..f241de64ed5 100644 --- a/crates/emath/src/vec2b.rs +++ b/crates/emath/src/vec2b.rs @@ -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 { + let other = other.into(); + Self { + x: self.x && other.x, + y: self.y && other.y, + } + } + + #[inline] + pub fn or(&self, other: impl Into) -> Self { + let other = other.into(); + Self { + x: self.x || other.x, + y: self.y || other.y, + } + } } impl From for Vec2b {