From 347fdb97d632b14c8e18b56583c5b55776e460c2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 May 2020 17:51:01 +0200 Subject: [PATCH] [window] fix a bunch of resize-releated bugs --- egui/README.md | 2 +- egui/src/containers/menu.rs | 2 +- egui/src/containers/resize.rs | 158 +++++++++-------------------- egui/src/containers/scroll_area.rs | 23 +++-- egui/src/containers/window.rs | 11 +- egui/src/examples/app.rs | 21 +--- egui/src/widgets.rs | 19 ++-- 7 files changed, 85 insertions(+), 151 deletions(-) diff --git a/egui/README.md b/egui/README.md index 6209490e..6ff29fa7 100644 --- a/egui/README.md +++ b/egui/README.md @@ -16,7 +16,7 @@ This is the core library crate Egui. It is fully platform independent without an * [ ] Windows should open from `UI`s and be boxed by parent ui. * Then we could open the example app inside a window in the example app, recursively. * [x] Resize any side and corner on windows - * [ ] Fix autoshrink + * [x] Fix autoshrink * [ ] Scroll areas * [x] Vertical scrolling * [ ] Horizontal scrolling diff --git a/egui/src/containers/menu.rs b/egui/src/containers/menu.rs index 39ae4c4e..393fd72b 100644 --- a/egui/src/containers/menu.rs +++ b/egui/src/containers/menu.rs @@ -80,7 +80,7 @@ fn menu_impl<'c>( .fixed_pos(button_interact.rect.left_bottom()); let frame = Frame::menu(ui.style()); - let resize = Resize::default().auto_sized(); + let resize = Resize::default().auto_sized().outline(false); let menu_interact = area.show(ui.ctx(), |ui| { frame.show(ui, |ui| { diff --git a/egui/src/containers/resize.rs b/egui/src/containers/resize.rs index d4299ce3..6212e529 100644 --- a/egui/src/containers/resize.rs +++ b/egui/src/containers/resize.rs @@ -5,13 +5,19 @@ use crate::*; #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "with_serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) struct State { - pub(crate) size: Vec2, + /// This is the size that the user has picked by dragging the resize handles. + /// This may be smaller and/or larger than the actual size. + /// For instance, the user may have tried to shrink too much (not fitting the contents). + /// Or the user requested a large area, but the content don't need that much space. + pub(crate) desired_size: Vec2, + + /// Actual size of content last frame + last_content_size: Vec2, /// Externally requested size (e.g. by Window) for the next frame pub(crate) requested_size: Option, } -// TODO: auto-shink/grow should be part of another container! #[derive(Clone, Copy, Debug)] pub struct Resize { id: Option, @@ -25,14 +31,6 @@ pub struct Resize { default_size: Vec2, - // If true, won't allow you to make window so big that it creates spacing - auto_shrink_width: bool, - auto_shrink_height: bool, - - // If true, won't allow you to resize smaller than that everything fits. - expand_width_to_fit_content: bool, - expand_height_to_fit_content: bool, - outline: bool, handle_offset: Vec2, } @@ -44,11 +42,7 @@ impl Default for Resize { resizable: true, min_size: Vec2::splat(16.0), max_size: Vec2::infinity(), - default_size: vec2(f32::INFINITY, 200.0), // TODO - auto_shrink_width: false, - auto_shrink_height: false, - expand_width_to_fit_content: true, - expand_height_to_fit_content: true, + default_size: vec2(200.0, 400.0), // TODO: default height for a resizable area (e.g. a window) outline: true, handle_offset: Default::default(), } @@ -98,22 +92,14 @@ impl Resize { self.resizable } - /// Not resizable, just takes the size of its contents. + /// Not manually resizable, just takes the size of its contents. pub fn auto_sized(self) -> Self { self.default_size(Vec2::splat(f32::INFINITY)) .resizable(false) - .auto_shrink_width(true) - .auto_expand_width(true) - .auto_shrink_height(true) - .auto_expand_height(true) } pub fn fixed_size(mut self, size: impl Into) -> Self { let size = size.into(); - self.auto_shrink_width = false; - self.auto_shrink_height = false; - self.expand_width_to_fit_content = false; - self.expand_height_to_fit_content = false; self.default_size = size; self.min_size = size; self.max_size = size; @@ -126,38 +112,6 @@ impl Resize { self } - /// true: prevent from resizing to smaller than contents. - /// false: allow shrinking to smaller than contents. - pub fn auto_expand(mut self, auto_expand: bool) -> Self { - self.expand_width_to_fit_content = auto_expand; - self.expand_height_to_fit_content = auto_expand; - self - } - - /// true: prevent from resizing to smaller than contents. - /// false: allow shrinking to smaller than contents. - pub fn auto_expand_width(mut self, auto_expand: bool) -> Self { - self.expand_width_to_fit_content = auto_expand; - self - } - - /// true: prevent from resizing to smaller than contents. - /// false: allow shrinking to smaller than contents. - pub fn auto_expand_height(mut self, auto_expand: bool) -> Self { - self.expand_height_to_fit_content = auto_expand; - self - } - - pub fn auto_shrink_width(mut self, auto_shrink_width: bool) -> Self { - self.auto_shrink_width = auto_shrink_width; - self - } - - pub fn auto_shrink_height(mut self, auto_shrink_height: bool) -> Self { - self.auto_shrink_height = auto_shrink_height; - self - } - /// Offset the position of the resize handle by this much pub fn handle_offset(mut self, handle_offset: impl Into) -> Self { self.handle_offset = handle_offset.into(); @@ -173,7 +127,6 @@ impl Resize { struct Prepared { id: Id, state: State, - is_new: bool, corner_interact: Option, content_ui: Ui, } @@ -185,22 +138,17 @@ impl Resize { self.max_size = self.max_size.min(ui.available().size()); self.max_size = self.max_size.max(self.min_size); - let (is_new, mut state) = match ui.memory().resize.get(&id) { - Some(state) => (false, *state), - None => { - let default_size = self.default_size.clamp(self.min_size..=self.max_size); - ( - true, - State { - size: default_size, - requested_size: None, - }, - ) - } - }; + let mut state = ui.memory().resize.get(&id).cloned().unwrap_or_else(|| { + let default_size = self.default_size.clamp(self.min_size..=self.max_size); - state.size = state.size.clamp(self.min_size..=self.max_size); - let last_frame_size = state.size; + State { + desired_size: default_size, + last_content_size: vec2(0.0, 0.0), + requested_size: None, + } + }); + + state.desired_size = state.desired_size.clamp(self.min_size..=self.max_size); let position = ui.available().min; @@ -208,7 +156,7 @@ impl Resize { // Resize-corner: let corner_size = Vec2::splat(16.0); // TODO: style let corner_rect = Rect::from_min_size( - position + state.size + self.handle_offset - corner_size, + position + state.desired_size + self.handle_offset - corner_size, corner_size, ); let corner_interact = ui.interact(corner_rect, id.with("corner"), Sense::drag()); @@ -217,12 +165,12 @@ impl Resize { if let Some(mouse_pos) = ui.input().mouse.pos { // This is the desired size. We may not be able to achieve it. - state.size = mouse_pos - position + 0.5 * corner_interact.rect.size() + state.desired_size = mouse_pos - position + 0.5 * corner_interact.rect.size() - self.handle_offset; // We don't clamp to max size, because we want to be able to push against outer bounds. // For instance, if we are inside a bigger Resize region, we want to expand that. - // state.size = state.size.clamp(self.min_size..=self.max_size); - state.size = state.size.max(self.min_size); + // state.desired_size = state.desired_size.clamp(self.min_size..=self.max_size); + state.desired_size = state.desired_size.max(self.min_size); } } Some(corner_interact) @@ -231,30 +179,29 @@ impl Resize { }; if let Some(requested_size) = state.requested_size.take() { - state.size = requested_size; + state.desired_size = requested_size; // We don't clamp to max size, because we want to be able to push against outer bounds. // For instance, if we are inside a bigger Resize region, we want to expand that. - // state.size = state.size.clamp(self.min_size..=self.max_size); - state.size = state.size.max(self.min_size); + // state.desired_size = state.desired_size.clamp(self.min_size..=self.max_size); + state.desired_size = state.desired_size.max(self.min_size); } // ------------------------------ - let inner_rect = Rect::from_min_size(position, state.size); + let inner_rect = Rect::from_min_size(position, state.desired_size); - let mut content_clip_rect = ui - .clip_rect() - .intersect(inner_rect.expand(ui.style().clip_rect_margin)); + let mut content_clip_rect = inner_rect.expand(ui.style().clip_rect_margin); // If we pull the resize handle to shrink, we want to TRY to shink it. // After laying out the contents, we might be much bigger. // In those cases we don't want the clip_rect to be smaller, because // then we will clip the contents of the region even thought the result gets larger. This is simply ugly! - // So we use the memory of last_frame_size to make the clip rect large enough. - content_clip_rect.max = content_clip_rect - .max - .max(content_clip_rect.min + last_frame_size) - .min(ui.clip_rect().max); // Respect parent region + // So we use the memory of last_content_size to make the clip rect large enough. + content_clip_rect.max = content_clip_rect.max.max( + inner_rect.min + state.last_content_size + Vec2::splat(ui.style().clip_rect_margin), + ); + + content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); // Respect parent region let mut content_ui = ui.child_ui(inner_rect); content_ui.set_clip_rect(content_clip_rect); @@ -262,7 +209,6 @@ impl Resize { Prepared { id, state, - is_new, corner_interact, content_ui, } @@ -279,39 +225,33 @@ impl Resize { let Prepared { id, mut state, - is_new, corner_interact, content_ui, } = prepared; - let desired_size = content_ui.bounding_size(); - let desired_size = desired_size.ceil(); // Avoid rounding errors in math + state.last_content_size = content_ui.bounding_size(); + state.last_content_size = state.last_content_size.ceil(); // Avoid rounding errors in math // ------------------------------ - if self.auto_shrink_width { - state.size.x = state.size.x.min(desired_size.x); - } - if self.auto_shrink_height { - state.size.y = state.size.y.min(desired_size.y); - } - if self.expand_width_to_fit_content || is_new { - state.size.x = state.size.x.max(desired_size.x); - } - if self.expand_height_to_fit_content || is_new { - state.size.y = state.size.y.max(desired_size.y); - } + if self.outline || self.resizable { + // We show how large we are, + // so we must follow the contents: - state.size = state.size.max(self.min_size); - // state.size = state.size.clamp(self.min_size..=self.max_size); - state.size = state.size.round(); // TODO: round to pixels + state.desired_size = state.desired_size.max(state.last_content_size); + state.desired_size = ui.round_vec_to_pixels(state.desired_size); - ui.allocate_space(state.size); + // We are as large as we look + ui.allocate_space(state.desired_size); + } else { + // Probably a window. + ui.allocate_space(state.last_content_size); + } // ------------------------------ if self.outline && corner_interact.is_some() { - let rect = Rect::from_min_size(content_ui.top_left(), state.size); + let rect = Rect::from_min_size(content_ui.top_left(), state.desired_size); let rect = rect.expand(2.0); // breathing room for content ui.add_paint_cmd(paint::PaintCmd::Rect { rect, diff --git a/egui/src/containers/scroll_area.rs b/egui/src/containers/scroll_area.rs index 20866c18..f307632b 100644 --- a/egui/src/containers/scroll_area.rs +++ b/egui/src/containers/scroll_area.rs @@ -72,7 +72,7 @@ impl ScrollArea { .cloned() .unwrap_or_default(); - // content: size of contents (generally large) + // content: size of contents (generally large; that's why we want scroll bars) // outer: size of scroll area including scroll bar(s) // inner: excluding scroll bar(s). The area we clip the contents to. @@ -154,7 +154,7 @@ impl Prepared { } } - // TODO: check that nothing else is being inteacted with + // TODO: check that nothing else is being interacted with if ui.contains_mouse(outer_rect) { state.offset.y -= ui.input().scroll_delta.y; } @@ -209,10 +209,17 @@ impl Prepared { state.offset.y = state.offset.y.min(content_size.y - inner_rect.height()); // Avoid frame-delay by calculating a new handle rect: - let handle_rect = Rect::from_min_max( + let mut handle_rect = Rect::from_min_max( pos2(left, from_content(state.offset.y)), pos2(right, from_content(state.offset.y + inner_rect.height())), ); + let min_handle_height = (2.0 * corner_radius).max(8.0); + if handle_rect.size().y < min_handle_height { + handle_rect = Rect::from_center_size( + handle_rect.center(), + vec2(handle_rect.size().x, min_handle_height), + ); + } let style = ui.style(); let handle_fill = style.interact(&interact).fill; @@ -233,12 +240,10 @@ impl Prepared { }); } - // let size = content_size.min(inner_rect.size()); - // let size = vec2( - // content_size.x, // ignore inner_rect, i.e. try to expand horizontally if necessary - // content_size.y.min(inner_rect.size().y), // respect vertical height. - // ); - let size = outer_rect.size(); + let size = vec2( + outer_rect.size().x, + outer_rect.size().y.min(content_size.y), // shrink if content is so small that we don't need scroll bars + ); ui.allocate_space(size); state.offset.y = state.offset.y.min(content_size.y - inner_rect.height()); diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index aa827248..60c95b76 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -27,13 +27,7 @@ impl<'open> Window<'open> { open: None, area, frame: None, - resize: Resize::default() - .auto_expand_height(false) - .auto_expand_width(true) - .auto_shrink_height(false) - .auto_shrink_width(true) - .handle_offset(Vec2::splat(4.0)) - .outline(false), + resize: Resize::default().outline(false), scroll: Some( ScrollArea::default() .always_show_scroll(false) @@ -326,7 +320,8 @@ fn interact( let mut resize_state = ctx.memory().resize.get(&resize_id).cloned().unwrap(); // resize_state.size += new_rect.size() - pre_resize.size(); // resize_state.size = new_rect.size() - some margin; - resize_state.requested_size = Some(resize_state.size + new_rect.size() - pre_resize.size()); + resize_state.requested_size = + Some(resize_state.desired_size + new_rect.size() - pre_resize.size()); ctx.memory().resize.insert(resize_id, resize_state); ctx.memory().areas.move_to_top(area_layer); diff --git a/egui/src/examples/app.rs b/egui/src/examples/app.rs index 274a167f..68a84fc9 100644 --- a/egui/src/examples/app.rs +++ b/egui/src/examples/app.rs @@ -50,32 +50,25 @@ impl ExampleApp { Window::new("Examples") .open(&mut open_windows.examples) - .default_pos([32.0, 100.0]) - .default_size([430.0, 600.0]) .show(ctx, |ui| { example_window.ui(ui); }); Window::new("Settings") .open(&mut open_windows.settings) - .default_pos([500.0, 100.0]) - .default_size([350.0, 400.0]) .show(ctx, |ui| { ctx.settings_ui(ui); }); Window::new("Inspection") .open(&mut open_windows.inspection) - .default_pos([500.0, 400.0]) - .default_size([400.0, 300.0]) .show(ctx, |ui| { ctx.inspection_ui(ui); }); Window::new("Memory") .open(&mut open_windows.memory) - .default_pos([700.0, 350.0]) - .auto_sized() + .resizable(false) .show(ctx, |ui| { ctx.memory_ui(ui); }); @@ -244,14 +237,10 @@ impl ExampleWindow { CollapsingHeader::new("Resize") .default_open(false) .show(ui, |ui| { - Resize::default() - .default_height(200.0) - // .as_wide_as_possible() - .auto_shrink_height(false) - .show(ui, |ui| { - ui.add(label!("This ui can be resized!")); - ui.add(label!("Just pull the handle on the bottom right")); - }); + Resize::default().default_height(200.0).show(ui, |ui| { + ui.add(label!("This ui can be resized!")); + ui.add(label!("Just pull the handle on the bottom right")); + }); }); ui.collapsing("Name clash example", |ui| { diff --git a/egui/src/widgets.rs b/egui/src/widgets.rs index 267617fc..01022947 100644 --- a/egui/src/widgets.rs +++ b/egui/src/widgets.rs @@ -412,7 +412,7 @@ impl Widget for RadioButton { pub struct Separator { line_width: Option, - min_spacing: f32, + spacing: f32, extra: f32, color: Color, } @@ -421,7 +421,7 @@ impl Separator { pub fn new() -> Self { Self { line_width: None, - min_spacing: 6.0, + spacing: 6.0, extra: 0.0, color: color::WHITE, } @@ -432,8 +432,9 @@ impl Separator { self } - pub fn min_spacing(mut self, min_spacing: f32) -> Self { - self.min_spacing = min_spacing; + /// How much space we take up. The line is painted in the middle of this. + pub fn spacing(mut self, spacing: f32) -> Self { + self.spacing = spacing; self } @@ -453,7 +454,7 @@ impl Widget for Separator { fn ui(self, ui: &mut Ui) -> InteractInfo { let Separator { line_width, - min_spacing, + spacing, extra, color, } = self; @@ -462,9 +463,13 @@ impl Widget for Separator { let available_space = ui.available_finite().size(); + // TODO: only allocate `spacing`, but not our full width/height + // as that would make the false impression that we *need* all that space, + // wich would prevent regions from autoshrinking + let (points, rect) = match ui.layout().dir() { Direction::Horizontal => { - let rect = ui.allocate_space(vec2(min_spacing, available_space.y)); + let rect = ui.allocate_space(vec2(spacing, available_space.y)); ( [ pos2(rect.center().x, rect.top() - extra), @@ -474,7 +479,7 @@ impl Widget for Separator { ) } Direction::Vertical => { - let rect = ui.allocate_space(vec2(available_space.x, min_spacing)); + let rect = ui.allocate_space(vec2(available_space.x, spacing)); ( [ pos2(rect.left() - extra, rect.center().y),