From 5bac853d9c3eec699788e27cf66211be00e6db72 Mon Sep 17 00:00:00 2001 From: LoganDark Date: Sun, 13 Nov 2022 11:30:39 -0800 Subject: [PATCH 1/5] eframe: Repaint immediately on RepaintAsap, fixes #903 (#2280) * eframe: Repaint immediately on RepaintAsap, fixes #903 This completely eliminates the white flickering seen on Windows when rapidly resizing a window on the glow backend. The reason that happens is because DWM only waits for the resize event to be delivered before displaying the window at its new size. You must repaint synchronously inside that iteration of the event loop or else you get flickering. * Differentiate between RepaintAsap and RepaintNext RepaintNext looks like it is indeed needed in at least one case instead of RepaintAsap. * Use RepaintNext in more situations Starting to understand why this was the behavior. It looks like only a few special cases should be given RepaintAsap, such as the window being resized. All other cases should be RepaintNext, as it can wait. Using RepaintAsap in all situations will cause things like lag when changing a slider by keyboard with a high key repeat rate. * Add explanatory comments I am a total hypocrite for forgetting to add these. * Rename RepaintAsap to RepaintNow There is no notion of "possibility" here like there is when waiting for RedrawEventsCleared. RepaintNow causes an immediate repaint no matter what. * Fix RepaintNow comment "Delays" is ambiguous. --- crates/eframe/src/native/run.rs | 85 ++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 5af24347..ef49cfa0 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -22,7 +22,17 @@ pub use epi::NativeOptions; #[derive(Debug)] enum EventResult { Wait, - RepaintAsap, + /// Causes a synchronous repaint inside the event handler. This should only + /// be used in special situations if the window must be repainted while + /// handling a specific event. This occurs on Windows when handling resizes. + /// + /// `RepaintNow` creates a new frame synchronously, and should therefore + /// only be used for extremely urgent repaints. + RepaintNow, + /// Queues a repaint for once the event loop handles its next redraw. Exists + /// so that multiple input events can be handled in one frame. Does not + /// cause any delay like `RepaintNow`. + RepaintNext, RepaintAt(Instant), Exit, } @@ -103,7 +113,7 @@ fn run_and_return(event_loop: &mut EventLoop, mut winit_app winit::event::Event::UserEvent(RequestRepaintEvent) | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { .. - }) => EventResult::RepaintAsap, + }) => EventResult::RepaintNext, winit::event::Event::WindowEvent { window_id, .. } if winit_app.window().is_none() @@ -119,7 +129,12 @@ fn run_and_return(event_loop: &mut EventLoop, mut winit_app match event_result { EventResult::Wait => {} - EventResult::RepaintAsap => { + EventResult::RepaintNow => { + tracing::trace!("Repaint caused by winit::Event: {:?}", event); + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint(); + } + EventResult::RepaintNext => { tracing::trace!("Repaint caused by winit::Event: {:?}", event); next_repaint_time = Instant::now(); } @@ -188,14 +203,18 @@ fn run_and_exit( winit::event::Event::UserEvent(RequestRepaintEvent) | winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { .. - }) => EventResult::RepaintAsap, + }) => EventResult::RepaintNext, event => winit_app.on_event(event_loop, &event), }; match event_result { EventResult::Wait => {} - EventResult::RepaintAsap => { + EventResult::RepaintNow => { + next_repaint_time = Instant::now() + Duration::from_secs(1_000_000_000); + winit_app.paint(); + } + EventResult::RepaintNext => { next_repaint_time = Instant::now(); } EventResult::RepaintAt(repaint_time) => { @@ -496,7 +515,7 @@ mod glow_integration { let control_flow = if integration.should_close() { EventResult::Exit } else if repaint_after.is_zero() { - EventResult::RepaintAsap + EventResult::RepaintNext } else if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_after) { @@ -538,7 +557,7 @@ mod glow_integration { if self.running.is_none() { self.init_run_state(event_loop); } - EventResult::RepaintAsap + EventResult::RepaintNow } winit::event::Event::Suspended => { #[cfg(target_os = "android")] @@ -560,11 +579,28 @@ mod glow_integration { winit::event::Event::WindowEvent { event, .. } => { if let Some(running) = &mut self.running { + // On Windows, if a window is resized by the user, it should repaint synchronously, inside the + // event handler. + // + // If this is not done, the compositor will assume that the window does not want to redraw, + // and continue ahead. + // + // In eframe's case, that causes the window to rapidly flicker, as it struggles to deliver + // new frames to the compositor in time. + // + // The flickering is technically glutin or glow's fault, but we should be responding properly + // to resizes anyway, as doing so avoids dropping frames. + // + // See: https://github.com/emilk/egui/issues/903 + let mut repaint_asap = false; + match &event { winit::event::WindowEvent::Focused(new_focused) => { self.is_focused = *new_focused; } winit::event::WindowEvent::Resized(physical_size) => { + repaint_asap = true; + // Resize with 0 width and height is used by winit to signal a minimize event on Windows. // See: https://github.com/rust-windowing/winit/issues/208 // This solves an issue where the app would panic when minimizing on Windows. @@ -576,6 +612,7 @@ mod glow_integration { new_inner_size, .. } => { + repaint_asap = true; running.gl_window.resize(**new_inner_size); } winit::event::WindowEvent::CloseRequested @@ -592,7 +629,11 @@ mod glow_integration { if running.integration.should_close() { EventResult::Exit } else if event_response.repaint { - EventResult::RepaintAsap + if repaint_asap { + EventResult::RepaintNow + } else { + EventResult::RepaintNext + } } else { EventResult::Wait } @@ -854,7 +895,7 @@ mod wgpu_integration { let control_flow = if integration.should_close() { EventResult::Exit } else if repaint_after.is_zero() { - EventResult::RepaintAsap + EventResult::RepaintNext } else if let Some(repaint_after_instant) = std::time::Instant::now().checked_add(repaint_after) { @@ -913,7 +954,7 @@ mod wgpu_integration { ); self.init_run_state(event_loop, storage, window); } - EventResult::RepaintAsap + EventResult::RepaintNow } winit::event::Event::Suspended => { #[cfg(target_os = "android")] @@ -923,11 +964,28 @@ mod wgpu_integration { winit::event::Event::WindowEvent { event, .. } => { if let Some(running) = &mut self.running { + // On Windows, if a window is resized by the user, it should repaint synchronously, inside the + // event handler. + // + // If this is not done, the compositor will assume that the window does not want to redraw, + // and continue ahead. + // + // In eframe's case, that causes the window to rapidly flicker, as it struggles to deliver + // new frames to the compositor in time. + // + // The flickering is technically glutin or glow's fault, but we should be responding properly + // to resizes anyway, as doing so avoids dropping frames. + // + // See: https://github.com/emilk/egui/issues/903 + let mut repaint_asap = false; + match &event { winit::event::WindowEvent::Focused(new_focused) => { self.is_focused = *new_focused; } winit::event::WindowEvent::Resized(physical_size) => { + repaint_asap = true; + // Resize with 0 width and height is used by winit to signal a minimize event on Windows. // See: https://github.com/rust-windowing/winit/issues/208 // This solves an issue where the app would panic when minimizing on Windows. @@ -942,6 +1000,7 @@ mod wgpu_integration { new_inner_size, .. } => { + repaint_asap = true; running .painter .on_window_resized(new_inner_size.width, new_inner_size.height); @@ -959,7 +1018,11 @@ mod wgpu_integration { if running.integration.should_close() { EventResult::Exit } else if event_response.repaint { - EventResult::RepaintAsap + if repaint_asap { + EventResult::RepaintNow + } else { + EventResult::RepaintNext + } } else { EventResult::Wait } From f0f41d60e154b927c07c4f0fcfcbb34c81036dee Mon Sep 17 00:00:00 2001 From: LoganDark Date: Sun, 13 Nov 2022 11:30:52 -0800 Subject: [PATCH 2/5] eframe: Don't show window until after initialization (#2279) * Don't show window until after initialization Shortens #1802, but does not completely solve it * format code * Present first frame immediately before showing window This resolves the white flash almost completely, but is a hack. Window visibility should be derived from the AppOutput, and the first frame should not be painted before the event loop has processed initial events. Working on a better implementation. * Integrate window showing with AppOutput This allows an app to keep the window hidden (never shown) by calling Frame.set_visible(false) on the first update. This includes a slightly less nasty hack than the last commit did. Also fixes an accidental cross-contamination of pull requests. * fmt * add comments * add comments * add comments * add comments Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/native/epi_integration.rs | 18 ++++++++++++------ crates/eframe/src/native/run.rs | 9 +++++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 9d9d51e0..a27cb738 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -146,7 +146,7 @@ pub fn handle_app_output( fullscreen, drag_window, window_pos, - visible, + visible: _, // handled in post_present always_on_top, } = app_output; @@ -183,10 +183,6 @@ pub fn handle_app_output( let _ = window.drag_window(); } - if let Some(visible) = visible { - window.set_visible(visible); - } - if let Some(always_on_top) = always_on_top { window.set_always_on_top(always_on_top); } @@ -240,7 +236,10 @@ impl EpiIntegration { native_pixels_per_point: Some(native_pixels_per_point), window_info: read_window_info(window, egui_ctx.pixels_per_point()), }, - output: Default::default(), + output: epi::backend::AppOutput { + visible: Some(true), + ..Default::default() + }, storage, #[cfg(feature = "glow")] gl, @@ -325,6 +324,7 @@ impl EpiIntegration { if app_output.close { self.close = app.on_close_event(); } + self.frame.output.visible = app_output.visible; // this is handled by post_present handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); } @@ -341,6 +341,12 @@ impl EpiIntegration { app.post_rendering(window_size_px, &self.frame); } + pub fn post_present(&mut self, window: &winit::window::Window) { + if let Some(visible) = self.frame.output.visible.take() { + window.set_visible(visible); + } + } + pub fn handle_platform_output( &mut self, window: &winit::window::Window, diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index ef49cfa0..fb1e5125 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -349,8 +349,9 @@ mod glow_integration { }; let window_settings = epi_integration::load_window_settings(storage); - let window_builder = - epi_integration::window_builder(native_options, &window_settings).with_title(title); + let window_builder = epi_integration::window_builder(native_options, &window_settings) + .with_title(title) + .with_visible(false); // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279 let gl_window = unsafe { glutin::ContextBuilder::new() @@ -512,6 +513,8 @@ mod glow_integration { gl_window.swap_buffers().unwrap(); } + integration.post_present(window); + let control_flow = if integration.should_close() { EventResult::Exit } else if repaint_after.is_zero() { @@ -733,6 +736,7 @@ mod wgpu_integration { let window_settings = epi_integration::load_window_settings(storage); epi_integration::window_builder(native_options, &window_settings) .with_title(title) + .with_visible(false) // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279 .build(event_loop) .unwrap() } @@ -891,6 +895,7 @@ mod wgpu_integration { ); integration.post_rendering(app.as_mut(), window); + integration.post_present(window); let control_flow = if integration.should_close() { EventResult::Exit From ef931c406c47c64d71462886692787ebca4b7b04 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 13 Nov 2022 22:17:33 +0100 Subject: [PATCH 3/5] Add Window::pivot and position combo boxes better (#2303) * Paint ComboBox icon differently if opening upwards * Add Area::pivot and Window::pivot * Add Window::contrain * ComboBox: pop up above if it doesn't fit below * Add line to changelog --- CHANGELOG.md | 5 +- crates/egui/src/containers/area.rs | 34 ++++++++-- crates/egui/src/containers/combo_box.rs | 89 ++++++++++++++++++++----- crates/egui/src/containers/popup.rs | 32 +++++++-- crates/egui/src/containers/window.rs | 40 ++++++++--- 5 files changed, 163 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c530657d..dc4785b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,9 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG * Added `Key::Minus` and `Key::Equals` ([#2239](https://github.com/emilk/egui/pull/2239)). * Added `egui::gui_zoom` module with helpers for scaling the whole GUI of an app ([#2239](https://github.com/emilk/egui/pull/2239)). * You can now put one interactive widget on top of another, and only one will get interaction at a time ([#2244](https://github.com/emilk/egui/pull/2244)). -* Add `ui.centered`. -* Added `Area::constrain` which constrains area to the screen bounds. ([#2270](https://github.com/emilk/egui/pull/2270)). +* Added `ui.centered`. +* Added `Area::constrain` and `Window::constrain` which constrains area to the screen bounds. ([#2270](https://github.com/emilk/egui/pull/2270)). +* Added `Area::pivot` and `Window::pivot` which controls what part of the window to position. ([#2303](https://github.com/emilk/egui/pull/2303)). ### Changed 🔧 * Panels always have a separator line, but no stroke on other sides. Their spacing has also changed slightly ([#2261](https://github.com/emilk/egui/pull/2261)). diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 7ce2bab5..072af44e 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -49,6 +49,7 @@ pub struct Area { constrain: bool, order: Order, default_pos: Option, + pivot: Align2, anchor: Option<(Align2, Vec2)>, new_pos: Option, drag_bounds: Option, @@ -65,6 +66,7 @@ impl Area { order: Order::Middle, default_pos: None, new_pos: None, + pivot: Align2::LEFT_TOP, anchor: None, drag_bounds: None, } @@ -122,16 +124,28 @@ impl Area { self } + /// Positions the window and prevents it from being moved + pub fn fixed_pos(mut self, fixed_pos: impl Into) -> Self { + self.new_pos = Some(fixed_pos.into()); + self.movable = false; + self + } + /// Constrains this area to the screen bounds. pub fn constrain(mut self, constrain: bool) -> Self { self.constrain = constrain; self } - /// Positions the window and prevents it from being moved - pub fn fixed_pos(mut self, fixed_pos: impl Into) -> Self { - self.new_pos = Some(fixed_pos.into()); - self.movable = false; + /// Where the "root" of the area is. + /// + /// For instance, if you set this to [`Align2::RIGHT_TOP`] + /// then [`Self::fixed_pos`] will set the position of the right-top + /// corner of the area. + /// + /// Default: [`Align2::LEFT_TOP`]. + pub fn pivot(mut self, pivot: Align2) -> Self { + self.pivot = pivot; self } @@ -208,6 +222,7 @@ impl Area { enabled, default_pos, new_pos, + pivot, anchor, drag_bounds, constrain, @@ -229,9 +244,18 @@ impl Area { state.interactable = interactable; let mut temporarily_invisible = false; + if pivot != Align2::LEFT_TOP { + if is_new { + temporarily_invisible = true; // figure out the size first + } else { + state.pos.x -= pivot.x().to_factor() * state.size.x; + state.pos.y -= pivot.y().to_factor() * state.size.y; + } + } + if let Some((anchor, offset)) = anchor { if is_new { - temporarily_invisible = true; + temporarily_invisible = true; // figure out the size first } else { let screen = ctx.available_rect(); state.pos = anchor.align_size_within_rect(state.size, screen).min + offset; diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index 49b9f8a1..9c33002f 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -1,8 +1,16 @@ -use crate::{style::WidgetVisuals, *}; use epaint::Shape; +use crate::{style::WidgetVisuals, *}; + +/// Indicate wether or not a popup will be shown above or below the box. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum AboveOrBelow { + Above, + Below, +} + /// A function that paints the [`ComboBox`] icon -pub type IconPainter = Box; +pub type IconPainter = Box; /// A drop-down selection menu with a descriptive label. /// @@ -89,6 +97,7 @@ impl ComboBox { /// rect: egui::Rect, /// visuals: &egui::style::WidgetVisuals, /// _is_open: bool, + /// _above_or_below: egui::AboveOrBelow, /// ) { /// let rect = egui::Rect::from_center_size( /// rect.center(), @@ -107,7 +116,10 @@ impl ComboBox { /// .show_ui(ui, |_ui| {}); /// # }); /// ``` - pub fn icon(mut self, icon_fn: impl FnOnce(&Ui, Rect, &WidgetVisuals, bool) + 'static) -> Self { + pub fn icon( + mut self, + icon_fn: impl FnOnce(&Ui, Rect, &WidgetVisuals, bool, AboveOrBelow) + 'static, + ) -> Self { self.icon = Some(Box::new(icon_fn)); self } @@ -213,6 +225,23 @@ fn combo_box_dyn<'c, R>( let popup_id = button_id.with("popup"); let is_popup_open = ui.memory().is_popup_open(popup_id); + + let popup_height = ui + .ctx() + .memory() + .areas + .get(popup_id) + .map_or(100.0, |state| state.size.y); + + let above_or_below = + if ui.next_widget_position().y + ui.spacing().interact_size.y + popup_height + < ui.ctx().input().screen_rect().bottom() + { + AboveOrBelow::Below + } else { + AboveOrBelow::Above + }; + let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| { // We don't want to change width when user selects something new let full_minimum_width = ui.spacing().slider_width; @@ -243,9 +272,15 @@ fn combo_box_dyn<'c, R>( icon_rect.expand(visuals.expansion), visuals, is_popup_open, + above_or_below, ); } else { - paint_default_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals); + paint_default_icon( + ui.painter(), + icon_rect.expand(visuals.expansion), + visuals, + above_or_below, + ); } let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect); @@ -256,12 +291,18 @@ fn combo_box_dyn<'c, R>( if button_response.clicked() { ui.memory().toggle_popup(popup_id); } - let inner = crate::popup::popup_below_widget(ui, popup_id, &button_response, |ui| { - ScrollArea::vertical() - .max_height(ui.spacing().combo_height) - .show(ui, menu_contents) - .inner - }); + let inner = crate::popup::popup_above_or_below_widget( + ui, + popup_id, + &button_response, + above_or_below, + |ui| { + ScrollArea::vertical() + .max_height(ui.spacing().combo_height) + .show(ui, menu_contents) + .inner + }, + ); InnerResponse { inner, @@ -316,13 +357,31 @@ fn button_frame( response } -fn paint_default_icon(painter: &Painter, rect: Rect, visuals: &WidgetVisuals) { +fn paint_default_icon( + painter: &Painter, + rect: Rect, + visuals: &WidgetVisuals, + above_or_below: AboveOrBelow, +) { let rect = Rect::from_center_size( rect.center(), vec2(rect.width() * 0.7, rect.height() * 0.45), ); - painter.add(Shape::closed_line( - vec![rect.left_top(), rect.right_top(), rect.center_bottom()], - visuals.fg_stroke, - )); + + match above_or_below { + AboveOrBelow::Above => { + // Upward pointing triangle + painter.add(Shape::closed_line( + vec![rect.left_bottom(), rect.right_bottom(), rect.center_top()], + visuals.fg_stroke, + )); + } + AboveOrBelow::Below => { + // Downward pointing triangle + painter.add(Shape::closed_line( + vec![rect.left_top(), rect.right_top(), rect.center_bottom()], + visuals.fg_stroke, + )); + } + } } diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 992b6cf9..3ae49479 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -294,7 +294,23 @@ pub fn was_tooltip_open_last_frame(ctx: &Context, tooltip_id: Id) -> bool { false } -/// Shows a popup below another widget. +/// Helper for [`popup_above_or_below_widget`]. +pub fn popup_below_widget( + ui: &Ui, + popup_id: Id, + widget_response: &Response, + add_contents: impl FnOnce(&mut Ui) -> R, +) -> Option { + popup_above_or_below_widget( + ui, + popup_id, + widget_response, + AboveOrBelow::Below, + add_contents, + ) +} + +/// Shows a popup above or below another widget. /// /// Useful for drop-down menus (combo boxes) or suggestion menus under text fields. /// @@ -309,24 +325,32 @@ pub fn was_tooltip_open_last_frame(ctx: &Context, tooltip_id: Id) -> bool { /// if response.clicked() { /// ui.memory().toggle_popup(popup_id); /// } -/// egui::popup::popup_below_widget(ui, popup_id, &response, |ui| { +/// let below = egui::AboveOrBelow::Below; +/// egui::popup::popup_above_or_below_widget(ui, popup_id, &response, below, |ui| { /// ui.set_min_width(200.0); // if you want to control the size /// ui.label("Some more info, or things you can select:"); /// ui.label("…"); /// }); /// # }); /// ``` -pub fn popup_below_widget( +pub fn popup_above_or_below_widget( ui: &Ui, popup_id: Id, widget_response: &Response, + above_or_below: AboveOrBelow, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option { if ui.memory().is_popup_open(popup_id) { + let (pos, pivot) = match above_or_below { + AboveOrBelow::Above => (widget_response.rect.left_top(), Align2::LEFT_BOTTOM), + AboveOrBelow::Below => (widget_response.rect.left_bottom(), Align2::LEFT_TOP), + }; + let inner = Area::new(popup_id) .order(Order::Foreground) .constrain(true) - .fixed_pos(widget_response.rect.left_bottom()) + .fixed_pos(pos) + .pivot(pivot) .show(ui.ctx(), |ui| { // Note: we use a separate clip-rect for this area, so the popup can be outside the parent. // See https://github.com/emilk/egui/issues/825 diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 6182b9f8..9d7032c5 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -122,6 +122,30 @@ impl<'open> Window<'open> { self } + /// Sets the window position and prevents it from being dragged around. + pub fn fixed_pos(mut self, pos: impl Into) -> Self { + self.area = self.area.fixed_pos(pos); + self + } + + /// Constrains this window to the screen bounds. + pub fn constrain(mut self, constrain: bool) -> Self { + self.area = self.area.constrain(constrain); + self + } + + /// Where the "root" of the window is. + /// + /// For instance, if you set this to [`Align2::RIGHT_TOP`] + /// then [`Self::fixed_pos`] will set the position of the right-top + /// corner of the window. + /// + /// Default: [`Align2::LEFT_TOP`]. + pub fn pivot(mut self, pivot: Align2) -> Self { + self.area = self.area.pivot(pivot); + self + } + /// Set anchor and distance. /// /// An anchor of `Align2::RIGHT_TOP` means "put the right-top corner of the window @@ -156,23 +180,17 @@ impl<'open> Window<'open> { self } - /// Set initial position and size of the window. - pub fn default_rect(self, rect: Rect) -> Self { - self.default_pos(rect.min).default_size(rect.size()) - } - - /// Sets the window position and prevents it from being dragged around. - pub fn fixed_pos(mut self, pos: impl Into) -> Self { - self.area = self.area.fixed_pos(pos); - self - } - /// Sets the window size and prevents it from being resized by dragging its edges. pub fn fixed_size(mut self, size: impl Into) -> Self { self.resize = self.resize.fixed_size(size); self } + /// Set initial position and size of the window. + pub fn default_rect(self, rect: Rect) -> Self { + self.default_pos(rect.min).default_size(rect.size()) + } + /// Sets the window pos and size and prevents it from being moved and resized by dragging its edges. pub fn fixed_rect(self, rect: Rect) -> Self { self.fixed_pos(rect.min).fixed_size(rect.size()) From 690dc2d2e8ed2478d5c3e415c7d7f839a7c96503 Mon Sep 17 00:00:00 2001 From: Matt Fellenz Date: Mon, 14 Nov 2022 02:44:46 -0800 Subject: [PATCH 4/5] Add 'none' alias to Sense::hover (#2306) --- crates/egui/src/sense.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/egui/src/sense.rs b/crates/egui/src/sense.rs index 68efde52..06c3a2b0 100644 --- a/crates/egui/src/sense.rs +++ b/crates/egui/src/sense.rs @@ -16,6 +16,7 @@ pub struct Sense { impl Sense { /// Senses no clicks or drags. Only senses mouse hover. + #[doc(alias = "none")] pub fn hover() -> Self { Self { click: false, From 0ba04184d5b1242948bc627bd140238829b8148e Mon Sep 17 00:00:00 2001 From: Arshia Soleimani <9458000+arshiyasolei@users.noreply.github.com> Date: Wed, 16 Nov 2022 02:26:02 -0800 Subject: [PATCH 5/5] Improve mouse selection accuracy (#2304) * updated * Update crates/egui/src/context.rs Co-authored-by: Emil Ernerfeldt Co-authored-by: Emil Ernerfeldt --- crates/egui/src/context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index a4a92447..31455702 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -328,7 +328,7 @@ impl Context { sense: Sense, enabled: bool, ) -> Response { - let gap = 0.5; // Just to make sure we don't accidentally hover two things at once (a small eps should be sufficient). + let gap = 0.1; // Just to make sure we don't accidentally hover two things at once (a small eps should be sufficient). // Make it easier to click things: let interact_rect = rect.expand2(