From d0f51811975d637301e5cd1fe8ca8233f99356d8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 3 Jun 2020 21:14:47 +0200 Subject: [PATCH] [window] make resizing a bit smoother --- Cargo.lock | 10 +++--- egui/src/containers/area.rs | 2 +- egui/src/containers/resize.rs | 59 +++++++++++++++++------------------ egui/src/containers/window.rs | 40 +++++++++++------------- egui/src/context.rs | 15 ++++++--- egui/src/memory.rs | 10 +++--- egui/src/style.rs | 11 +++++++ egui/src/widgets.rs | 4 +-- 8 files changed, 81 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b5cf3b7..2f905a5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,7 +348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "egui" -version = "0.1.0" +version = "0.1.1" dependencies = [ "ahash 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "criterion 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -363,7 +363,7 @@ version = "0.1.0" dependencies = [ "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "egui 0.1.0", + "egui 0.1.1", "glium 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", "webbrowser 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -373,7 +373,7 @@ dependencies = [ name = "egui_wasm" version = "0.1.0" dependencies = [ - "egui 0.1.0", + "egui 0.1.1", "js-sys 0.3.39 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", @@ -390,7 +390,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "example_glium" version = "0.1.0" dependencies = [ - "egui 0.1.0", + "egui 0.1.1", "egui_glium 0.1.0", "glium 0.24.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", @@ -401,7 +401,7 @@ dependencies = [ name = "example_wasm" version = "0.1.0" dependencies = [ - "egui 0.1.0", + "egui 0.1.1", "egui_wasm 0.1.0", "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index 64f05dbe..f455d296 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -227,7 +227,7 @@ impl Prepared { fn mouse_pressed_on_area(ctx: &Context, layer: Layer) -> bool { if let Some(mouse_pos) = ctx.input().mouse.pos { - ctx.input().mouse.pressed && ctx.memory().layer_at(mouse_pos) == Some(layer) + ctx.input().mouse.pressed && ctx.layer_at(mouse_pos) == Some(layer) } else { false } diff --git a/egui/src/containers/resize.rs b/egui/src/containers/resize.rs index 390d1042..34f54550 100644 --- a/egui/src/containers/resize.rs +++ b/egui/src/containers/resize.rs @@ -25,9 +25,8 @@ pub struct Resize { /// If false, we are no enabled resizable: bool, - // Will still try to stay within parent ui bounds - min_size: Vec2, - max_size: Vec2, + min_content_size: Vec2, + min_desired_size: Vec2, default_size: Vec2, @@ -40,8 +39,8 @@ impl Default for Resize { Self { id: None, resizable: true, - min_size: Vec2::splat(16.0), - max_size: Vec2::infinity(), + min_content_size: Vec2::splat(16.0), + min_desired_size: vec2(200.0, 400.0), default_size: vec2(280.0, 400.0), // TODO: perferred size for a resizable area (e.g. a window) outline: true, handle_offset: Default::default(), @@ -84,13 +83,15 @@ impl Resize { self } - pub fn min_size(mut self, min_size: impl Into) -> Self { - self.min_size = min_size.into(); + /// Won't shrink to smaller than this + pub fn min_content_size(mut self, min_content_size: impl Into) -> Self { + self.min_content_size = min_content_size.into(); self } - pub fn max_size(mut self, max_size: impl Into) -> Self { - self.max_size = max_size.into(); + /// Won't shrink to smaller than this + pub fn min_desired_size(mut self, min_desired_size: impl Into) -> Self { + self.min_desired_size = min_desired_size.into(); self } @@ -114,17 +115,12 @@ impl Resize { pub fn fixed_size(mut self, size: impl Into) -> Self { let size = size.into(); self.default_size = size; - self.min_size = size; - self.max_size = size; + self.min_content_size = size; + self.min_desired_size = size; self.resizable = false; self } - pub fn as_wide_as_possible(mut self) -> Self { - self.min_size.x = f32::INFINITY; - 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(); @@ -147,12 +143,9 @@ struct Prepared { impl Resize { fn begin(&mut self, ui: &mut Ui) -> Prepared { let id = self.id.unwrap_or_else(|| ui.make_child_id("resize")); - self.min_size = self.min_size.min(ui.available().size()); - self.max_size = self.max_size.min(ui.available().size()); - self.max_size = self.max_size.max(self.min_size); 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); + let default_size = self.default_size.max(self.min_content_size); State { desired_size: default_size, @@ -161,7 +154,7 @@ impl Resize { } }); - state.desired_size = state.desired_size.clamp(self.min_size..=self.max_size); + state.desired_size = state.desired_size.max(self.min_desired_size); let position = ui.available().min; @@ -176,14 +169,8 @@ impl Resize { if corner_interact.active { if let Some(mouse_pos) = ui.input().mouse.pos { - // This is the desired size. We may not be able to achieve it. - 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.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) @@ -193,11 +180,8 @@ impl Resize { if let Some(requested_size) = state.requested_size.take() { 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.desired_size = state.desired_size.clamp(self.min_size..=self.max_size); - state.desired_size = state.desired_size.max(self.min_size); } + state.desired_size = state.desired_size.max(self.min_desired_size); // ------------------------------ @@ -283,6 +267,19 @@ impl Resize { } ui.memory().resize.insert(id, state); + + if ui.ctx().style().debug_resize { + ui.ctx().debug_rect( + Rect::from_min_size(content_ui.top_left(), state.desired_size), + color::GREEN, + "desired_size", + ); + ui.ctx().debug_rect( + Rect::from_min_size(content_ui.top_left(), state.last_content_size), + color::LIGHT_BLUE, + "last_content_size", + ); + } } } diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index e92cde97..2d015320 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -27,7 +27,10 @@ impl<'open> Window<'open> { open: None, area, frame: None, - resize: Resize::default().outline(false), + resize: Resize::default() + .outline(false) + .min_content_size([96.0, 32.0]) + .min_desired_size([96.0, 200.0]), scroll: Some( ScrollArea::default() .always_show_scroll(false) @@ -78,16 +81,6 @@ impl<'open> Window<'open> { self.default_pos(rect.min).default_size(rect.size()) } - pub fn min_size(mut self, min_size: impl Into) -> Self { - self.resize = self.resize.min_size(min_size); - self - } - - pub fn max_size(mut self, max_size: impl Into) -> Self { - self.resize = self.resize.max_size(max_size); - self - } - pub fn fixed_size(mut self, size: impl Into) -> Self { self.resize = self.resize.fixed_size(size); self @@ -165,8 +158,12 @@ impl<'open> Window<'open> { // First interact (move etc) to avoid frame delay: let last_frame_outer_rect = area.state().rect(); let interaction = if possible.movable || possible.resizable { + let title_bar_height = + title_label.font_height(ctx.fonts()) + 1.0 * ctx.style().item_spacing.y; // this could be better + let margins = 2.0 * frame.margin + vec2(0.0, title_bar_height); interact( ctx, + margins, possible, area_layer, area.state_mut(), @@ -295,6 +292,7 @@ impl WindowInteraction { fn interact( ctx: &Context, + margins: Vec2, possible: PossibleInteractions, area_layer: Layer, area_state: &mut area::State, @@ -302,7 +300,6 @@ fn interact( resize_id: Id, rect: Rect, ) -> Option { - let pre_resize = ctx.round_rect_to_pixels(rect); let window_interaction = window_interaction( ctx, possible, @@ -317,12 +314,11 @@ fn interact( area_state.pos = new_rect.min; - 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.desired_size + new_rect.size() - pre_resize.size()); - ctx.memory().resize.insert(resize_id, resize_state); + if window_interaction.is_resize() { + let mut resize_state = ctx.memory().resize.get(&resize_id).cloned().unwrap(); + resize_state.requested_size = Some(new_rect.size() - margins); + ctx.memory().resize.insert(resize_id, resize_state); + } ctx.memory().areas.move_to_top(area_layer); Some(window_interaction) @@ -400,7 +396,7 @@ fn resize_hover( rect: Rect, ) -> Option { if let Some(mouse_pos) = ctx.input().mouse.pos { - if let Some(top_layer) = ctx.memory().layer_at(mouse_pos) { + if let Some(top_layer) = ctx.layer_at(mouse_pos) { if top_layer != area_layer && top_layer.order != Order::Background { return None; // Another window is on top here } @@ -411,8 +407,8 @@ fn resize_hover( return None; } - let side_interact_radius = 5.0; // TODO: from style - let corner_interact_radius = 10.0; // TODO + let side_interact_radius = ctx.style().resize_interact_radius_side; + let corner_interact_radius = ctx.style().resize_interact_radius_corner; if rect.expand(side_interact_radius).contains(mouse_pos) { let (mut left, mut right, mut top, mut bottom) = Default::default(); if possible.resizable { @@ -531,7 +527,7 @@ fn show_title_bar( collapsing: &mut collapsing_header::State, ) -> TitleBar { let title_bar_and_rect = ui.inner_layout(Layout::horizontal(Align::Center), |ui| { - ui.set_desired_height(title_label.font_height(ui)); + ui.set_desired_height(title_label.font_height(ui.fonts())); let item_spacing = ui.style().item_spacing; let button_size = ui.style().start_icon_width; diff --git a/egui/src/context.rs b/egui/src/context.rs index 8508c51c..0896d41d 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -258,10 +258,17 @@ impl Context { } } + // --------------------------------------------------------------------- + + pub fn layer_at(&self, pos: Pos2) -> Option { + let resize_interact_radius_side = self.style().resize_interact_radius_side; + self.memory().layer_at(pos, resize_interact_radius_side) + } + pub fn contains_mouse(&self, layer: Layer, clip_rect: Rect, rect: Rect) -> bool { let rect = rect.intersect(clip_rect); if let Some(mouse_pos) = self.input.mouse.pos { - rect.contains(mouse_pos) && self.memory().layer_at(mouse_pos) == Some(layer) + rect.contains(mouse_pos) && self.layer_at(mouse_pos) == Some(layer) } else { false } @@ -399,7 +406,7 @@ impl Context { ); } - pub fn debug_rect(&self, rect: Rect, name: impl Into) { + pub fn debug_rect(&self, rect: Rect, color: Color, name: impl Into) { let text = format!("{} {:?}", name.into(), rect); let layer = Layer::debug(); self.add_paint_cmd( @@ -407,13 +414,13 @@ impl Context { PaintCmd::Rect { corner_radius: 0.0, fill: None, - outline: Some(LineStyle::new(1.0, color::RED)), + outline: Some(LineStyle::new(2.0, color)), rect, }, ); let align = (Align::Min, Align::Min); let text_style = TextStyle::Monospace; - self.floating_text(layer, rect.min, text, text_style, align, Some(color::RED)); + self.floating_text(layer, rect.min, text, text_style, align, Some(color)); } /// Show some text anywhere on screen. diff --git a/egui/src/memory.rs b/egui/src/memory.rs index 9e8796b8..34b8244f 100644 --- a/egui/src/memory.rs +++ b/egui/src/memory.rs @@ -112,9 +112,8 @@ impl Memory { self.areas.end_frame() } - /// TODO: call once at the start of the frame for the current mouse pos - pub fn layer_at(&self, pos: Pos2) -> Option { - self.areas.layer_at(pos) + pub fn layer_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option { + self.areas.layer_at(pos, resize_interact_radius_side) } } @@ -139,13 +138,14 @@ impl Areas { } } - /// TODO: call once at the start of the frame for the current mouse pos - pub fn layer_at(&self, pos: Pos2) -> Option { + pub fn layer_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option { for layer in self.order.iter().rev() { if self.is_visible(layer) { if let Some(state) = self.areas.get(&layer.id) { if state.interactable { let rect = Rect::from_min_size(state.pos, state.size); + // Allow us to resize by dragging just outside the window: + let rect = rect.expand(resize_interact_radius_side); if rect.contains(pos) { return Some(*layer); } diff --git a/egui/src/style.rs b/egui/src/style.rs index b72d4544..115ec5c0 100644 --- a/egui/src/style.rs +++ b/egui/src/style.rs @@ -25,6 +25,12 @@ pub struct Style { /// The text starts after this many pixels. pub start_icon_width: f32, + /// Mouse must be the close to the side of a window to resize + pub resize_interact_radius_side: f32, + + /// Mouse must be the close to the corner of a window to resize + pub resize_interact_radius_corner: f32, + // ----------------------------------------------- // Purely visual: pub interact: Interact, @@ -60,6 +66,7 @@ pub struct Style { // ----------------------------------------------- // Debug rendering: pub debug_widget_rects: bool, + pub debug_resize: bool, } impl Default for Style { @@ -71,6 +78,8 @@ impl Default for Style { indent: 21.0, clickable_diameter: 22.0, start_icon_width: 14.0, + resize_interact_radius_side: 5.0, + resize_interact_radius_corner: 10.0, interact: Default::default(), text_color: gray(160, 255), line_width: 1.0, @@ -84,6 +93,7 @@ impl Default for Style { menu_bar: MenuBar::default(), clip_rect_margin: 3.0, debug_widget_rects: false, + debug_resize: false, } } } @@ -220,6 +230,7 @@ impl Style { } ui.add(Checkbox::new(&mut self.debug_widget_rects, "Paint debug rectangles around widgets")); + ui.add(Checkbox::new(&mut self.debug_resize, "Debug Resize")); ui.add(Slider::f32(&mut self.item_spacing.x, 0.0..=10.0).text("item_spacing.x").precision(0)); ui.add(Slider::f32(&mut self.item_spacing.y, 0.0..=10.0).text("item_spacing.y").precision(0)); diff --git a/egui/src/widgets.rs b/egui/src/widgets.rs index 01022947..f0a0818a 100644 --- a/egui/src/widgets.rs +++ b/egui/src/widgets.rs @@ -81,8 +81,8 @@ impl Label { } } - pub fn font_height(&self, ui: &Ui) -> f32 { - ui.fonts()[self.text_style].height() + pub fn font_height(&self, fonts: &Fonts) -> f32 { + fonts[self.text_style].height() } // TODO: this should return a LabelLayout which has a paint method.