From 947b5813d7def12be84e3c2f3cf4af13a1eba5b3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Mar 2024 10:13:49 +0100 Subject: [PATCH 0001/1202] Fix visual glitch on the right side of highly rounded rectangles (#4244) * Part of https://github.com/emilk/egui/issues/4238 When one side of a rectangle is all rounding we need to take care not to produce duplicated vertices in the rectangle path generator. The old code only handled three sides, but forgot the last side (the right side). The new code handles the right side, and also handles the other sides more robustly (with a floating point eps) and efficiently (in a single pass). The glitch was most notable in shadows with a high blur width. Examples of the glitch: Screenshot 2024-03-26 at 20 15 38 Screenshot 2024-03-27 at 09 48 48 Screenshot 2024-03-27 at 09 49 21 --- crates/epaint/src/tessellator.rs | 36 +++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 49d5ab8a33c1..f626b9f3094d 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -520,7 +520,7 @@ pub mod path { let min = rect.min; let max = rect.max; - let r = clamp_radius(rounding, rect); + let r = clamp_rounding(rounding, rect); if r == Rounding::ZERO { let min = rect.min; @@ -531,11 +531,33 @@ pub mod path { path.push(pos2(max.x, max.y)); // right bottom path.push(pos2(min.x, max.y)); // left bottom } else { - add_circle_quadrant(path, pos2(max.x - r.se, max.y - r.se), r.se, 0.0); - add_circle_quadrant(path, pos2(min.x + r.sw, max.y - r.sw), r.sw, 1.0); - add_circle_quadrant(path, pos2(min.x + r.nw, min.y + r.nw), r.nw, 2.0); - add_circle_quadrant(path, pos2(max.x - r.ne, min.y + r.ne), r.ne, 3.0); - path.dedup(); // We get duplicates for thin rectangles, producing visual artifats + // We need to avoid duplicated vertices, because that leads to visual artifacts later. + // Duplicated vertices can happen when one side is all rounding, with no straight edge between. + let eps = f32::EPSILON * rect.size().max_elem(); + + add_circle_quadrant(path, pos2(max.x - r.se, max.y - r.se), r.se, 0.0); // south east + + if rect.width() <= r.se + r.sw + eps { + path.pop(); // avoid duplicated vertex + } + + add_circle_quadrant(path, pos2(min.x + r.sw, max.y - r.sw), r.sw, 1.0); // south west + + if rect.height() <= r.sw + r.nw + eps { + path.pop(); // avoid duplicated vertex + } + + add_circle_quadrant(path, pos2(min.x + r.nw, min.y + r.nw), r.nw, 2.0); // north west + + if rect.width() <= r.nw + r.ne + eps { + path.pop(); // avoid duplicated vertex + } + + add_circle_quadrant(path, pos2(max.x - r.ne, min.y + r.ne), r.ne, 3.0); // north east + + if rect.height() <= r.ne + r.se + eps { + path.pop(); // avoid duplicated vertex + } } } @@ -589,7 +611,7 @@ pub mod path { } // Ensures the radius of each corner is within a valid range - fn clamp_radius(rounding: Rounding, rect: Rect) -> Rounding { + fn clamp_rounding(rounding: Rounding, rect: Rect) -> Rounding { let half_width = rect.width() * 0.5; let half_height = rect.height() * 0.5; let max_cr = half_width.min(half_height); From a15e6c21220e593932569558a31de9403e3791d0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Mar 2024 11:22:38 +0100 Subject: [PATCH 0002/1202] Prevent visual glitch when shadow blur width is very high (#4245) * Closes https://github.com/emilk/egui/issues/4238 The comment in the code explains it well, but the short of it is this: we can't handle a shadow blur width larger than the shadow rectangle, so we need to clamp the blur. This means smaller things will cast shadows with a smaller blur width, but that's better than having visual glitches. --- crates/epaint/src/shadow.rs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/epaint/src/shadow.rs b/crates/epaint/src/shadow.rs index 71ce76c94fec..8b73c07e0da8 100644 --- a/crates/epaint/src/shadow.rs +++ b/crates/epaint/src/shadow.rs @@ -1,3 +1,5 @@ +use emath::NumExt as _; + use super::*; /// The color and fuzziness of a fuzzy shape. @@ -16,7 +18,7 @@ pub struct Shadow { /// The width of the blur, i.e. the width of the fuzzy penumbra. /// - /// A value of 0.0 means no blur. + /// A value of 0.0 means a sharp shadow. pub blur: f32, /// Expand the shadow in all directions by this much. @@ -47,12 +49,26 @@ impl Shadow { color, } = *self; - let rect = rect.translate(offset); + let rect = rect.translate(offset).expand(spread); + + // We simulate a blurry shadow by tessellating a solid rectangle using a very large feathering. + // Feathering is usually used to make the edges of a shape softer for anti-aliasing. + // The tessellator can't handle blurring/feathering larger than the smallest side of the rect. + // Thats because the tessellator approximate very thin rectangles as line segments, + // and these line segments don't have rounded corners. + // When the feathering is small (the size of a pixel), this is usually fine, + // but here we have a huge feathering to simulate blur, + // so we need to avoid this optimization in the tessellator, + // which is also why we add this rather big epsilon: + let eps = 0.1; + let blur = blur.at_most(rect.size().min_elem() - eps).at_least(0.0); + + // TODO(emilk): if blur <= 0, return a simple `Shape::Rect` instead of using the tessellator let rounding_expansion = spread.abs() + 0.5 * blur; - let rounding = rounding.into() + Rounding::from(rounding_expansion); + let rounding = rounding.into() + Rounding::same(rounding_expansion); - let rect = RectShape::filled(rect.expand(spread), rounding, color); + let rect = RectShape::filled(rect, rounding, color); let pixels_per_point = 1.0; // doesn't matter here let font_tex_size = [1; 2]; // unused since we are not tessellating text. let mut tessellator = Tessellator::new( From bc5ce7781906a2227bcf367292c500f8e7713117 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Mar 2024 16:14:17 +0100 Subject: [PATCH 0003/1202] Fix `InputState::any_touches` and add `InputState::has_touch_screen` (#4247) Add `InputState::has_touch_screen` to query if there ever has been any touches (which is what `any_touches` used to return). --- crates/egui/src/input_state.rs | 5 +++++ crates/egui/src/input_state/touch_state.rs | 5 +++++ crates/egui/src/widgets/label.rs | 2 +- crates/egui/src/widgets/text_edit/builder.rs | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index b2284d45c2e1..a9c1b3143838 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -485,6 +485,11 @@ impl InputState { /// True if there currently are any fingers touching egui. pub fn any_touches(&self) -> bool { + self.touch_states.values().any(|t| t.any_touches()) + } + + /// True if we have ever received a touch event. + pub fn has_touch_screen(&self) -> bool { !self.touch_states.is_empty() } diff --git a/crates/egui/src/input_state/touch_state.rs b/crates/egui/src/input_state/touch_state.rs index 2a77a4d371b9..43053f821e59 100644 --- a/crates/egui/src/input_state/touch_state.rs +++ b/crates/egui/src/input_state/touch_state.rs @@ -177,6 +177,11 @@ impl TouchState { } } + /// Are there currently any fingers touching the surface? + pub fn any_touches(&self) -> bool { + !self.active_touches.is_empty() + } + pub fn info(&self) -> Option { self.gesture_state.as_ref().map(|state| { // state.previous can be `None` when the number of simultaneous touches has just diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index f5bb7ac425aa..10cefc5fb6a3 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -128,7 +128,7 @@ impl Label { // dragging select text, or scroll the enclosing [`ScrollArea`] (if any)? // Since currently copying selected text in not supported on `eframe` web, // we prioritize touch-scrolling: - let allow_drag_to_select = ui.input(|i| !i.any_touches()); + let allow_drag_to_select = ui.input(|i| !i.has_touch_screen()); let mut select_sense = if allow_drag_to_select { Sense::click_and_drag() diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index e57453890826..799774096ffa 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -527,7 +527,7 @@ impl<'t> TextEdit<'t> { // Since currently copying selected text in not supported on `eframe` web, // we prioritize touch-scrolling: let allow_drag_to_select = - ui.input(|i| !i.any_touches()) || ui.memory(|mem| mem.has_focus(id)); + ui.input(|i| !i.has_touch_screen()) || ui.memory(|mem| mem.has_focus(id)); let sense = if interactive { if allow_drag_to_select { From 3c029a45aca5257fc59640b1df8015f5893de559 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Mar 2024 16:14:22 +0100 Subject: [PATCH 0004/1202] Fix `Context::repaint_causes` returning no causes (#4248) It would return the causes for repainting again collected this frame, instead of the cause for repainting the current frame. * Part of https://github.com/emilk/egui/issues/3931 --- crates/egui/src/context.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 258c39bfdcf6..376c41d94d43 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -252,7 +252,7 @@ struct ViewportState { } /// What called [`Context::request_repaint`]? -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct RepaintCause { /// What file had the call that requested the repaint? pub file: &'static str, @@ -261,6 +261,12 @@ pub struct RepaintCause { pub line: u32, } +impl std::fmt::Debug for RepaintCause { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.file, self.line) + } +} + impl RepaintCause { /// Capture the file and line number of the call site. #[allow(clippy::new_without_default)] @@ -1465,7 +1471,7 @@ impl Context { self.read(|ctx| { ctx.viewports .get(&ctx.viewport_id()) - .map(|v| v.repaint.causes.clone()) + .map(|v| v.repaint.prev_causes.clone()) }) .unwrap_or_default() } From 570e7cf71bbba6376138b20df857496adc886a64 Mon Sep 17 00:00:00 2001 From: lomekragow <51994883+Chaojimengnan@users.noreply.github.com> Date: Wed, 27 Mar 2024 23:35:25 +0800 Subject: [PATCH 0005/1202] Add `register_native_texture` in `eframe::Frame` (#4246) * Closes https://github.com/emilk/egui/issues/4243 --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/epi.rs | 14 ++++++++++++++ crates/eframe/src/native/epi_integration.rs | 5 +++++ crates/eframe/src/native/glow_integration.rs | 6 +++++- crates/eframe/src/native/wgpu_integration.rs | 2 ++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 5ee29e457fe0..e7b53691cda9 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -614,6 +614,11 @@ pub struct Frame { #[cfg(feature = "glow")] pub(crate) gl: Option>, + /// Used to convert user custom [`glow::Texture`] to [`egui::TextureId`] + #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] + pub(crate) glow_register_native_texture: + Option egui::TextureId>>, + /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. #[cfg(feature = "wgpu")] pub(crate) wgpu_render_state: Option, @@ -690,6 +695,15 @@ impl Frame { self.gl.as_ref() } + /// Register your own [`glow::Texture`], + /// and then you can use the returned [`egui::TextureId`] to render your texture with [`egui`]. + /// + /// This function will take the ownership of your [`glow::Texture`], so please do not delete your [`glow::Texture`] after registering. + #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] + pub fn register_native_glow_texture(&mut self, native: glow::Texture) -> egui::TextureId { + self.glow_register_native_texture.as_mut().unwrap()(native) + } + /// The underlying WGPU render state. /// /// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`]. diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index f27d011202e6..fbf7b6dc078e 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -152,6 +152,9 @@ impl EpiIntegration { native_options: &crate::NativeOptions, storage: Option>, #[cfg(feature = "glow")] gl: Option>, + #[cfg(feature = "glow")] glow_register_native_texture: Option< + Box egui::TextureId>, + >, #[cfg(feature = "wgpu")] wgpu_render_state: Option, ) -> Self { let frame = epi::Frame { @@ -162,6 +165,8 @@ impl EpiIntegration { storage, #[cfg(feature = "glow")] gl, + #[cfg(feature = "glow")] + glow_register_native_texture, #[cfg(feature = "wgpu")] wgpu_render_state, raw_display_handle: window.display_handle().map(|h| h.as_raw()), diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 8b1f16ec8078..8dd0f05f6e21 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -217,6 +217,7 @@ impl GlowWinitApp { let system_theme = winit_integration::system_theme(&glutin.window(ViewportId::ROOT), &self.native_options); + let painter = Rc::new(RefCell::new(painter)); let integration = EpiIntegration::new( egui_ctx, @@ -226,6 +227,10 @@ impl GlowWinitApp { &self.native_options, storage, Some(gl.clone()), + Some(Box::new({ + let painter = painter.clone(); + move |native| painter.borrow_mut().register_native_texture(native) + })), #[cfg(feature = "wgpu")] None, ); @@ -302,7 +307,6 @@ impl GlowWinitApp { }; let glutin = Rc::new(RefCell::new(glutin)); - let painter = Rc::new(RefCell::new(painter)); { // Create weak pointers so that we don't keep diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 61bf157c007f..c17bba27afa7 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -215,6 +215,8 @@ impl WgpuWinitApp { storage, #[cfg(feature = "glow")] None, + #[cfg(feature = "glow")] + None, wgpu_render_state.clone(), ); From 9fa8aa7e30e3d2cdf67dd4d9ab023ea3eed4c225 Mon Sep 17 00:00:00 2001 From: Nicolas PASCAL Date: Wed, 27 Mar 2024 16:35:36 +0100 Subject: [PATCH 0006/1202] `Plot::Items:allow_hover` give possibility to masked the interaction on hovered item (#2558) This is particularly interesting if you want to authorize a single hover tooltip on an item in the event of an interaction. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui_plot/src/items/mod.rs | 134 ++++++++++++++++++++++++++++++ crates/egui_plot/src/lib.rs | 15 ++-- 2 files changed, 143 insertions(+), 6 deletions(-) diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 3df3b798dd35..78f4560c6d35 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -45,6 +45,9 @@ pub trait PlotItem { fn highlighted(&self) -> bool; + /// Can the user hover this is item? + fn allow_hover(&self) -> bool; + fn geometry(&self) -> PlotGeometry<'_>; fn bounds(&self) -> PlotBounds; @@ -121,6 +124,7 @@ pub struct HLine { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) style: LineStyle, id: Option, } @@ -132,6 +136,7 @@ impl HLine { stroke: Stroke::new(1.0, Color32::TRANSPARENT), name: String::default(), highlight: false, + allow_hover: true, style: LineStyle::Solid, id: None, } @@ -144,6 +149,13 @@ impl HLine { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a stroke. #[inline] pub fn stroke(mut self, stroke: impl Into) -> Self { @@ -233,6 +245,10 @@ impl PlotItem for HLine { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -256,6 +272,7 @@ pub struct VLine { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) style: LineStyle, id: Option, } @@ -267,6 +284,7 @@ impl VLine { stroke: Stroke::new(1.0, Color32::TRANSPARENT), name: String::default(), highlight: false, + allow_hover: true, style: LineStyle::Solid, id: None, } @@ -279,6 +297,13 @@ impl VLine { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a stroke. #[inline] pub fn stroke(mut self, stroke: impl Into) -> Self { @@ -368,6 +393,10 @@ impl PlotItem for VLine { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -390,6 +419,7 @@ pub struct Line { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) fill: Option, pub(super) style: LineStyle, id: Option, @@ -402,6 +432,7 @@ impl Line { stroke: Stroke::new(1.5, Color32::TRANSPARENT), // Note: a stroke of 1.0 (or less) can look bad on low-dpi-screens name: Default::default(), highlight: false, + allow_hover: true, fill: None, style: LineStyle::Solid, id: None, @@ -415,6 +446,13 @@ impl Line { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a stroke. #[inline] pub fn stroke(mut self, stroke: impl Into) -> Self { @@ -558,6 +596,10 @@ impl PlotItem for Line { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.series.points()) } @@ -577,6 +619,7 @@ pub struct Polygon { pub(super) stroke: Stroke, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) fill_color: Option, pub(super) style: LineStyle, id: Option, @@ -589,6 +632,7 @@ impl Polygon { stroke: Stroke::new(1.0, Color32::TRANSPARENT), name: Default::default(), highlight: false, + allow_hover: true, fill_color: None, style: LineStyle::Solid, id: None, @@ -603,6 +647,13 @@ impl Polygon { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a custom stroke. #[inline] pub fn stroke(mut self, stroke: impl Into) -> Self { @@ -697,6 +748,10 @@ impl PlotItem for Polygon { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.series.points()) } @@ -717,6 +772,7 @@ pub struct Text { pub(super) position: PlotPoint, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) color: Color32, pub(super) anchor: Align2, id: Option, @@ -729,6 +785,7 @@ impl Text { position, name: Default::default(), highlight: false, + allow_hover: true, color: Color32::TRANSPARENT, anchor: Align2::CENTER_CENTER, id: None, @@ -742,6 +799,13 @@ impl Text { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Text color. #[inline] pub fn color(mut self, color: impl Into) -> Self { @@ -822,6 +886,10 @@ impl PlotItem for Text { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -856,6 +924,8 @@ pub struct Points { pub(super) highlight: bool, + pub(super) allow_hover: bool, + pub(super) stems: Option, id: Option, } @@ -870,6 +940,7 @@ impl Points { radius: 1.0, name: Default::default(), highlight: false, + allow_hover: true, stems: None, id: None, } @@ -889,6 +960,13 @@ impl Points { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Set the marker's color. #[inline] pub fn color(mut self, color: impl Into) -> Self { @@ -1087,6 +1165,10 @@ impl PlotItem for Points { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.series.points()) } @@ -1108,6 +1190,7 @@ pub struct Arrows { pub(super) color: Color32, pub(super) name: String, pub(super) highlight: bool, + pub(super) allow_hover: bool, id: Option, } @@ -1120,6 +1203,7 @@ impl Arrows { color: Color32::TRANSPARENT, name: Default::default(), highlight: false, + allow_hover: true, id: None, } } @@ -1131,6 +1215,13 @@ impl Arrows { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Set the length of the arrow tips #[inline] pub fn tip_length(mut self, tip_length: f32) -> Self { @@ -1232,6 +1323,10 @@ impl PlotItem for Arrows { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Points(self.origins.points()) } @@ -1256,6 +1351,7 @@ pub struct PlotImage { pub(super) bg_fill: Color32, pub(super) tint: Color32, pub(super) highlight: bool, + pub(super) allow_hover: bool, pub(super) name: String, id: Option, } @@ -1271,6 +1367,7 @@ impl PlotImage { position: center_position, name: Default::default(), highlight: false, + allow_hover: true, texture_id: texture_id.into(), uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), size: size.into(), @@ -1288,6 +1385,13 @@ impl PlotImage { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right. #[inline] pub fn uv(mut self, uv: impl Into) -> Self { @@ -1407,6 +1511,10 @@ impl PlotItem for PlotImage { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::None } @@ -1443,6 +1551,7 @@ pub struct BarChart { pub(super) element_formatter: Option String>>, highlight: bool, + allow_hover: bool, id: Option, } @@ -1455,6 +1564,7 @@ impl BarChart { name: String::new(), element_formatter: None, highlight: false, + allow_hover: true, id: None, } } @@ -1523,6 +1633,13 @@ impl BarChart { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a custom way to format an element. /// Can be used to display a set number of decimals or custom labels. #[inline] @@ -1591,6 +1708,10 @@ impl PlotItem for BarChart { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Rects } @@ -1636,6 +1757,7 @@ pub struct BoxPlot { pub(super) element_formatter: Option String>>, highlight: bool, + allow_hover: bool, id: Option, } @@ -1648,6 +1770,7 @@ impl BoxPlot { name: String::new(), element_formatter: None, highlight: false, + allow_hover: true, id: None, } } @@ -1709,6 +1832,13 @@ impl BoxPlot { self } + /// Allowed hovering this item in the plot. Default: `true`. + #[inline] + pub fn allow_hover(mut self, hovering: bool) -> Self { + self.allow_hover = hovering; + self + } + /// Add a custom way to format an element. /// Can be used to display a set number of decimals or custom labels. #[inline] @@ -1752,6 +1882,10 @@ impl PlotItem for BoxPlot { self.highlight } + fn allow_hover(&self) -> bool { + self.allow_hover + } + fn geometry(&self) -> PlotGeometry<'_> { PlotGeometry::Rects } diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index e9962c843d24..26567117652d 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -1649,12 +1649,15 @@ impl PreparedPlot { let interact_radius_sq = (16.0_f32).powi(2); - let candidates = items.iter().filter_map(|item| { - let item = &**item; - let closest = item.find_closest(pointer, transform); - - Some(item).zip(closest) - }); + let candidates = items + .iter() + .filter(|entry| entry.allow_hover()) + .filter_map(|item| { + let item = &**item; + let closest = item.find_closest(pointer, transform); + + Some(item).zip(closest) + }); let closest = candidates .min_by_key(|(_, elem)| elem.dist_sq.ord()) From 58a27882b0d6d86168dde9c952d2b9b5025a3c68 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 27 Mar 2024 16:39:06 +0100 Subject: [PATCH 0007/1202] Fix touch-and-hold to open context menu (#4249) This was broken in cases where the ui wasn't waking up, i.e. when nothing else was happening. --- crates/egui/src/input_state.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index a9c1b3143838..9c6103e0a546 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -326,6 +326,10 @@ impl InputState { self.pointer.wants_repaint() || self.unprocessed_scroll_delta.abs().max_elem() > 0.2 || !self.events.is_empty() + + // We need to wake up and check for press-and-hold for the context menu. + // TODO(emilk): wake up after `MAX_CLICK_DURATION` instead of every frame. + || (self.any_touches() && !self.pointer.is_decidedly_dragging()) } /// Count presses of a key. If non-zero, the presses are consumed, so that this will only return non-zero once. From e183655aac76b9f76bdb4c82786dfacf53c0c09d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 28 Mar 2024 10:09:21 +0100 Subject: [PATCH 0008/1202] Don't apply a clip rect to the contents of an `Area` or `Window` (#4258) The edges were rather arbitrarily chosen anyway, and I'm not sure who it was supposed to help. --- crates/egui/src/containers/area.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 0c73ddf9fc5d..5cd0f01dfce1 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -384,7 +384,7 @@ impl Area { let layer_id = LayerId::new(self.order, self.id); let area_rect = ctx.memory(|mem| mem.areas().get(self.id).map(|area| area.rect())); if let Some(area_rect) = area_rect { - let clip_rect = ctx.available_rect(); + let clip_rect = Rect::EVERYTHING; let painter = Painter::new(ctx.clone(), layer_id, clip_rect); // shrinkage: looks kinda a bad on its own @@ -437,12 +437,7 @@ impl Prepared { .at_least(self.state.left_top_pos() + Vec2::splat(32.0)), ); - let shadow_radius = ctx.style().visuals.window_shadow.margin().sum().max_elem(); // hacky - let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius); - - let clip_rect = Rect::from_min_max(self.state.left_top_pos(), constrain_rect.max) - .expand(clip_rect_margin) - .intersect(constrain_rect); + let clip_rect = constrain_rect; // Don't paint outside our bounds let mut ui = Ui::new( ctx.clone(), From 3dba73e63eb145ac3a6e5baffeefb79292133c62 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 28 Mar 2024 10:09:28 +0100 Subject: [PATCH 0009/1202] Improve the UI for changing the egui theme (#4257) I added a new demo - a `Frame` editor: ![frame-editor](https://github.com/emilk/egui/assets/1148717/d0bec169-c211-45a3-9f53-5059fb8fc224) This whole menu is now just a a bit nicer to use: Screenshot 2024-03-28 at 09 49 16 --- crates/egui/src/containers/frame.rs | 1 + crates/egui/src/context.rs | 2 +- crates/egui/src/introspection.rs | 44 +- crates/egui/src/lib.rs | 2 +- crates/egui/src/memory.rs | 8 +- crates/egui/src/style.rs | 705 ++++++++++++------ crates/egui/src/widgets/mod.rs | 67 +- .../egui_demo_app/src/apps/fractal_clock.rs | 2 +- crates/egui_demo_app/src/wrap_app.rs | 8 +- .../src/demo/demo_app_windows.rs | 1 + crates/egui_demo_lib/src/demo/frame_demo.rs | 73 ++ .../src/demo/misc_demo_window.rs | 2 +- crates/egui_demo_lib/src/demo/mod.rs | 1 + crates/egui_demo_lib/src/demo/paint_bezier.rs | 28 +- crates/egui_demo_lib/src/demo/painting.rs | 3 +- crates/egui_demo_lib/src/demo/plot_demo.rs | 2 +- crates/egui_demo_lib/src/demo/scrolling.rs | 2 +- crates/egui_demo_lib/src/demo/sliders.rs | 2 +- crates/egui_demo_lib/src/demo/strip_demo.rs | 2 +- crates/egui_demo_lib/src/demo/table_demo.rs | 2 +- crates/egui_demo_lib/src/demo/tests.rs | 4 +- .../egui_demo_lib/src/demo/window_options.rs | 2 +- .../src/easy_mark/easy_mark_editor.rs | 2 +- 23 files changed, 633 insertions(+), 332 deletions(-) create mode 100644 crates/egui_demo_lib/src/demo/frame_demo.rs diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 92113f4dd499..2847fdfd1c53 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -52,6 +52,7 @@ use epaint::*; /// Note that you cannot change the margins after calling `begin`. #[doc(alias = "border")] #[derive(Clone, Copy, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[must_use = "You should call .show()"] pub struct Frame { /// Margin within the painted frame. diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 376c41d94d43..222f8ab0575e 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1567,7 +1567,7 @@ impl Context { /// The [`Style`] used by all new windows, panels etc. /// - /// You can also change this using [`Self::style_mut]` + /// You can also change this using [`Self::style_mut`] /// /// You can use [`Ui::style_mut`] to change the style of a single [`Ui`]. pub fn set_style(&self, style: impl Into>) { diff --git a/crates/egui/src/introspection.rs b/crates/egui/src/introspection.rs index 9c738153873a..f71e82194be7 100644 --- a/crates/egui/src/introspection.rs +++ b/crates/egui/src/introspection.rs @@ -150,23 +150,33 @@ impl Widget for &mut epaint::TessellationOptions { validate_meshes, } = self; - ui.checkbox(feathering, "Feathering (antialias)") - .on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain."); - let feathering_slider = crate::Slider::new(feathering_size_in_pixels, 0.0..=10.0) - .smallest_positive(0.1) - .logarithmic(true) - .text("Feathering size in pixels"); - ui.add_enabled(*feathering, feathering_slider); + ui.horizontal(|ui| { + ui.checkbox(feathering, "Feathering (antialias)") + .on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain."); + + if *feathering { + ui.add(crate::DragValue::new(feathering_size_in_pixels).clamp_range(0.0..=10.0).speed(0.1).suffix(" px")); + } + }); ui.checkbox(prerasterized_discs, "Speed up filled circles with pre-rasterization"); - ui.add( - crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0) - .logarithmic(true) - .show_value(true) - .text("Spline Tolerance"), - ); - ui.collapsing("debug", |ui| { + ui.horizontal(|ui| { + ui.label("Spline tolerance"); + let speed = 0.01 * *bezier_tolerance; + ui.add( + crate::DragValue::new(bezier_tolerance).clamp_range(0.0001..=10.0) + .speed(speed) + ); + }); + + ui.add_enabled(epaint::HAS_RAYON, crate::Checkbox::new(parallel_tessellation, "Parallelize tessellation") + ).on_hover_text("Only available if epaint was compiled with the rayon feature") + .on_disabled_hover_text("epaint was not compiled with the rayon feature"); + + ui.checkbox(validate_meshes, "Validate meshes").on_hover_text("Check that incoming meshes are valid, i.e. that all indices are in range, etc."); + + ui.collapsing("Debug", |ui| { ui.checkbox( coarse_tessellation_culling, "Do coarse culling in the tessellator", @@ -178,12 +188,6 @@ impl Widget for &mut epaint::TessellationOptions { ui.checkbox(debug_paint_clip_rects, "Paint clip rectangles"); ui.checkbox(debug_paint_text_rects, "Paint text bounds"); }); - - ui.add_enabled(epaint::HAS_RAYON, crate::Checkbox::new(parallel_tessellation, "Parallelize tessellation") - ).on_hover_text("Only available if epaint was compiled with the rayon feature") - .on_disabled_hover_text("epaint was not compiled with the rayon feature"); - - ui.checkbox(validate_meshes, "Validate meshes").on_hover_text("Check that incoming meshes are valid, i.e. that all indices are in range, etc."); }) .response } diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index e64668ec8076..b8f0c8fa3d29 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -431,7 +431,7 @@ pub use epaint::{ text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta}, ClippedPrimitive, ColorImage, FontImage, ImageData, Margin, Mesh, PaintCallback, - PaintCallbackInfo, Rounding, Shape, Stroke, TextureHandle, TextureId, + PaintCallbackInfo, Rounding, Shadow, Shape, Stroke, TextureHandle, TextureId, }; pub mod text { diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index dc611787fffb..b84308283724 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -278,13 +278,15 @@ impl Options { }); CollapsingHeader::new("✒ Painting") - .default_open(true) + .default_open(false) .show(ui, |ui| { tessellation_options.ui(ui); - ui.vertical_centered(|ui| crate::reset_button(ui, tessellation_options)); + ui.vertical_centered(|ui| { + crate::reset_button(ui, tessellation_options, "Reset paint settings"); + }); }); - ui.vertical_centered(|ui| crate::reset_button(ui, self)); + ui.vertical_centered(|ui| crate::reset_button(ui, self, "Reset all")); } } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 5fc3b39ecf3a..1523345f9b8c 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -7,8 +7,8 @@ use std::collections::BTreeMap; use epaint::{Rounding, Shadow, Stroke}; use crate::{ - ecolor::*, emath::*, ComboBox, CursorIcon, FontFamily, FontId, Margin, Response, RichText, - WidgetText, + ecolor::*, emath::*, ComboBox, CursorIcon, FontFamily, FontId, Grid, Margin, Response, + RichText, WidgetText, }; // ---------------------------------------------------------------------------- @@ -1305,19 +1305,21 @@ impl Style { visuals.light_dark_radio_buttons(ui); crate::Grid::new("_options").show(ui, |ui| { - ui.label("Override font id:"); - ui.horizontal(|ui| { - ui.radio_value(override_font_id, None, "None"); - if ui.radio(override_font_id.is_some(), "override").clicked() { - *override_font_id = Some(FontId::default()); - } + ui.label("Override font id"); + ui.vertical(|ui| { + ui.horizontal(|ui| { + ui.radio_value(override_font_id, None, "None"); + if ui.radio(override_font_id.is_some(), "override").clicked() { + *override_font_id = Some(FontId::default()); + } + }); if let Some(override_font_id) = override_font_id { crate::introspection::font_id_ui(ui, override_font_id); } }); ui.end_row(); - ui.label("Override text style:"); + ui.label("Override text style"); crate::ComboBox::from_id_source("Override text style") .selected_text(match override_text_style { None => "None".to_owned(), @@ -1334,7 +1336,7 @@ impl Style { }); ui.end_row(); - ui.label("Text style of DragValue:"); + ui.label("Text style of DragValue"); crate::ComboBox::from_id_source("drag_value_text_style") .selected_text(drag_value_text_style.to_string()) .show_ui(ui, |ui| { @@ -1347,10 +1349,11 @@ impl Style { }); ui.end_row(); - ui.label("Animation duration:"); + ui.label("Animation duration"); ui.add( - Slider::new(animation_time, 0.0..=1.0) - .clamp_to_range(true) + DragValue::new(animation_time) + .clamp_range(0.0..=1.0) + .speed(0.02) .suffix(" s"), ); ui.end_row(); @@ -1376,7 +1379,7 @@ impl Style { "If scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift", ); - ui.vertical_centered(|ui| reset_button(ui, self)); + ui.vertical_centered(|ui| reset_button(ui, self, "Reset style")); } } @@ -1389,7 +1392,7 @@ fn text_styles_ui(ui: &mut Ui, text_styles: &mut BTreeMap) -> ui.end_row(); } }); - crate::reset_button_with(ui, text_styles, default_text_styles()); + crate::reset_button_with(ui, text_styles, "Reset text styles", default_text_styles()); }) .response } @@ -1418,72 +1421,85 @@ impl Spacing { scroll, } = self; - ui.add(slider_vec2(item_spacing, 0.0..=20.0, "Item spacing")); + Grid::new("spacing") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Item spacing"); + ui.add(two_drag_values(item_spacing, 0.0..=20.0)); + ui.end_row(); - margin_ui(ui, "Window margin:", window_margin); - margin_ui(ui, "Menu margin:", menu_margin); + ui.label("Window margin"); + ui.add(window_margin); + ui.end_row(); - ui.add(slider_vec2(button_padding, 0.0..=20.0, "Button padding")); - ui.add(slider_vec2(interact_size, 4.0..=60.0, "Interact size")) - .on_hover_text("Minimum size of an interactive widget"); - ui.horizontal(|ui| { - ui.add(DragValue::new(indent).clamp_range(0.0..=100.0)); - ui.label("Indent"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(slider_width).clamp_range(0.0..=1000.0)); - ui.label("Slider width"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(slider_rail_height).clamp_range(0.0..=50.0)); - ui.label("Slider rail height"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(combo_width).clamp_range(0.0..=1000.0)); - ui.label("ComboBox width"); - }); - ui.horizontal(|ui| { - ui.add(DragValue::new(text_edit_width).clamp_range(0.0..=1000.0)); - ui.label("TextEdit width"); - }); + ui.label("Menu margin"); + ui.add(menu_margin); + ui.end_row(); - ui.collapsing("Scroll Area", |ui| { - scroll.ui(ui); - }); + ui.label("Button padding"); + ui.add(two_drag_values(button_padding, 0.0..=20.0)); + ui.end_row(); - ui.horizontal(|ui| { - ui.label("Checkboxes etc:"); - ui.add( - DragValue::new(icon_width) - .prefix("outer icon width:") - .clamp_range(0.0..=60.0), - ); - ui.add( - DragValue::new(icon_width_inner) - .prefix("inner icon width:") - .clamp_range(0.0..=60.0), - ); - ui.add( - DragValue::new(icon_spacing) - .prefix("spacing:") - .clamp_range(0.0..=10.0), - ); - }); + ui.label("Interact size") + .on_hover_text("Minimum size of an interactive widget"); + ui.add(two_drag_values(interact_size, 4.0..=60.0)); + ui.end_row(); - ui.horizontal(|ui| { - ui.add(DragValue::new(tooltip_width).clamp_range(0.0..=1000.0)); - ui.label("Tooltip wrap width"); - }); + ui.label("Indent"); + ui.add(DragValue::new(indent).clamp_range(0.0..=100.0)); + ui.end_row(); - ui.horizontal(|ui| { - ui.add(DragValue::new(menu_width).clamp_range(0.0..=1000.0)); - ui.label("Default width of a menu"); - }); + ui.label("Slider width"); + ui.add(DragValue::new(slider_width).clamp_range(0.0..=1000.0)); + ui.end_row(); - ui.horizontal(|ui| { - ui.add(DragValue::new(menu_spacing).clamp_range(0.0..=10.0)); - ui.label("Horizontal spacing between menus"); - }); + ui.label("Slider rail height"); + ui.add(DragValue::new(slider_rail_height).clamp_range(0.0..=50.0)); + ui.end_row(); + + ui.label("ComboBox width"); + ui.add(DragValue::new(combo_width).clamp_range(0.0..=1000.0)); + ui.end_row(); + + ui.label("TextEdit width"); + ui.add(DragValue::new(text_edit_width).clamp_range(0.0..=1000.0)); + ui.end_row(); + + ui.label("Tooltip wrap width"); + ui.add(DragValue::new(tooltip_width).clamp_range(0.0..=1000.0)); + ui.end_row(); + + ui.label("Default menu width"); + ui.add(DragValue::new(menu_width).clamp_range(0.0..=1000.0)); + ui.end_row(); + + ui.label("Menu spacing") + .on_hover_text("Horizontal spacing between menus"); + ui.add(DragValue::new(menu_spacing).clamp_range(0.0..=10.0)); + ui.end_row(); + + ui.label("Checkboxes etc"); + ui.vertical(|ui| { + ui.add( + DragValue::new(icon_width) + .prefix("outer icon width:") + .clamp_range(0.0..=60.0), + ); + ui.add( + DragValue::new(icon_width_inner) + .prefix("inner icon width:") + .clamp_range(0.0..=60.0), + ); + ui.add( + DragValue::new(icon_spacing) + .prefix("spacing:") + .clamp_range(0.0..=10.0), + ); + }); + ui.end_row(); + }); ui.checkbox( indent_ends_with_horizontal_line, @@ -1495,57 +1511,12 @@ impl Spacing { ui.add(DragValue::new(combo_height).clamp_range(0.0..=1000.0)); }); - ui.vertical_centered(|ui| reset_button(ui, self)); - } -} - -fn margin_ui(ui: &mut Ui, text: &str, margin: &mut Margin) { - let margin_range = 0.0..=20.0; - - ui.horizontal(|ui| { - ui.label(text); - - let mut same = margin.is_same(); - ui.checkbox(&mut same, "Same"); - - if same { - let mut value = margin.left; - ui.add(DragValue::new(&mut value).clamp_range(margin_range.clone())); - *margin = Margin::same(value); - } else { - if margin.is_same() { - // HACK: prevent collapse: - margin.right = margin.left + 1.0; - margin.bottom = margin.left + 2.0; - margin.top = margin.left + 3.0; - } + ui.collapsing("Scroll Area", |ui| { + scroll.ui(ui); + }); - ui.add( - DragValue::new(&mut margin.left) - .clamp_range(margin_range.clone()) - .prefix("L: "), - ) - .on_hover_text("Left margin"); - ui.add( - DragValue::new(&mut margin.right) - .clamp_range(margin_range.clone()) - .prefix("R: "), - ) - .on_hover_text("Right margin"); - ui.add( - DragValue::new(&mut margin.top) - .clamp_range(margin_range.clone()) - .prefix("T: "), - ) - .on_hover_text("Top margin"); - ui.add( - DragValue::new(&mut margin.bottom) - .clamp_range(margin_range) - .prefix("B: "), - ) - .on_hover_text("Bottom margin"); - } - }); + ui.vertical_centered(|ui| reset_button(ui, self, "Reset spacing")); + } } impl Interaction { @@ -1559,21 +1530,42 @@ impl Interaction { selectable_labels, multi_widget_text_select, } = self; - ui.add(Slider::new(interact_radius, 0.0..=20.0).text("interact_radius")) - .on_hover_text("Interact with the closest widget within this radius."); - ui.add(Slider::new(resize_grab_radius_side, 0.0..=20.0).text("resize_grab_radius_side")); - ui.add( - Slider::new(resize_grab_radius_corner, 0.0..=20.0).text("resize_grab_radius_corner"), - ); + + ui.spacing_mut().item_spacing = vec2(12.0, 8.0); + + Grid::new("interaction") + .num_columns(2) + .striped(true) + .show(ui, |ui| { + ui.label("interact_radius") + .on_hover_text("Interact with the closest widget within this radius."); + ui.add(DragValue::new(interact_radius).clamp_range(0.0..=20.0)); + ui.end_row(); + + ui.label("resize_grab_radius_side").on_hover_text("Radius of the interactive area of the side of a window during drag-to-resize"); + ui.add(DragValue::new(resize_grab_radius_side).clamp_range(0.0..=20.0)); + ui.end_row(); + + ui.label("resize_grab_radius_corner").on_hover_text("Radius of the interactive area of the corner of a window during drag-to-resize."); + ui.add(DragValue::new(resize_grab_radius_corner).clamp_range(0.0..=20.0)); + ui.end_row(); + + ui.label("Tooltip delay").on_hover_text( + "Delay in seconds before showing tooltips after the mouse stops moving", + ); + ui.add( + DragValue::new(tooltip_delay) + .clamp_range(0.0..=1.0) + .speed(0.05) + .suffix(" s"), + ); + ui.end_row(); + }); + ui.checkbox( show_tooltips_only_when_still, "Only show tooltips if mouse is still", ); - ui.add( - Slider::new(tooltip_delay, 0.0..=1.0) - .suffix(" s") - .text("tooltip_delay"), - ); ui.horizontal(|ui| { ui.checkbox(selectable_labels, "Selectable text in labels"); @@ -1582,7 +1574,7 @@ impl Interaction { } }); - ui.vertical_centered(|ui| reset_button(ui, self)); + ui.vertical_centered(|ui| reset_button(ui, self, "Reset interaction settings")); } } @@ -1627,8 +1619,16 @@ impl Selection { pub fn ui(&mut self, ui: &mut crate::Ui) { let Self { bg_fill, stroke } = self; ui.label("Selectable labels"); - ui_color(ui, bg_fill, "background fill"); - stroke_ui(ui, stroke, "stroke"); + + Grid::new("selectiom").num_columns(2).show(ui, |ui| { + ui.label("Background fill"); + ui.color_edit_button_srgba(bg_fill); + ui.end_row(); + + ui.label("Stroke"); + ui.add(stroke); + ui.end_row(); + }); } } @@ -1642,17 +1642,39 @@ impl WidgetVisuals { fg_stroke, expansion, } = self; - ui_color(ui, weak_bg_fill, "optional background fill") - .on_hover_text("For buttons, combo-boxes, etc"); - ui_color(ui, mandatory_bg_fill, "mandatory background fill") - .on_hover_text("For checkboxes, sliders, etc"); - stroke_ui(ui, bg_stroke, "background stroke"); - rounding_ui(ui, rounding); + Grid::new("widget") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Optional background fill") + .on_hover_text("For buttons, combo-boxes, etc"); + ui.color_edit_button_srgba(weak_bg_fill); + ui.end_row(); + + ui.label("Mandatory background fill") + .on_hover_text("For checkboxes, sliders, etc"); + ui.color_edit_button_srgba(mandatory_bg_fill); + ui.end_row(); + + ui.label("Background stroke"); + ui.add(bg_stroke); + ui.end_row(); - stroke_ui(ui, fg_stroke, "foreground stroke (text)"); - ui.add(Slider::new(expansion, -5.0..=5.0).text("expansion")) - .on_hover_text("make shapes this much larger"); + ui.label("Rounding"); + ui.add(rounding); + ui.end_row(); + + ui.label("Foreground stroke (text)"); + ui.add(fg_stroke); + ui.end_row(); + + ui.label("Expansion") + .on_hover_text("make shapes this much larger"); + ui.add(DragValue::new(expansion).speed(0.1)); + ui.end_row(); + }); } } @@ -1745,79 +1767,123 @@ impl Visuals { }); ui.collapsing("Window", |ui| { - ui_color(ui, window_fill, "Fill"); - stroke_ui(ui, window_stroke, "Outline"); - rounding_ui(ui, window_rounding); - shadow_ui(ui, window_shadow, "Shadow"); + Grid::new("window") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Fill"); + ui.color_edit_button_srgba(window_fill); + ui.end_row(); + + ui.label("Stroke"); + ui.add(window_stroke); + ui.end_row(); + + ui.label("Rounding"); + ui.add(window_rounding); + ui.end_row(); + + ui.label("Shadow"); + ui.add(window_shadow); + ui.end_row(); + }); + ui.checkbox(window_highlight_topmost, "Highlight topmost Window"); }); ui.collapsing("Menus and popups", |ui| { - rounding_ui(ui, menu_rounding); - shadow_ui(ui, popup_shadow, "Shadow"); + Grid::new("menus_and_popups") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Rounding"); + ui.add(menu_rounding); + ui.end_row(); + + ui.label("Shadow"); + ui.add(popup_shadow); + ui.end_row(); + }); }); ui.collapsing("Widgets", |ui| widgets.ui(ui)); ui.collapsing("Selection", |ui| selection.ui(ui)); - ui.horizontal(|ui| { - ui_color( - ui, - &mut widgets.noninteractive.fg_stroke.color, - "Text color", + ui.collapsing("Other colors", |ui| { + ui.horizontal(|ui| { + ui_color( + ui, + &mut widgets.noninteractive.fg_stroke.color, + "Text color", + ); + ui_color(ui, warn_fg_color, RichText::new("Warnings")); + ui_color(ui, error_fg_color, RichText::new("Errors")); + }); + + ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui( + |ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("For monospaced inlined text "); + ui.code("like this"); + ui.label("."); + }); + }, ); - ui_color(ui, warn_fg_color, RichText::new("Warnings")); - ui_color(ui, error_fg_color, RichText::new("Errors")); - }); - ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui(|ui| { + ui_color(ui, hyperlink_color, "hyperlink_color"); + ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 0.0; - ui.label("For monospaced inlined text "); - ui.code("like this"); - ui.label("."); + ui.label("Text cursor"); + ui.add(text_cursor); }); }); - ui_color(ui, hyperlink_color, "hyperlink_color"); - stroke_ui(ui, text_cursor, "Text Cursor"); + ui.collapsing("Misc", |ui| { + ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); + ui.checkbox(text_cursor_preview, "Preview text cursor on hover"); + ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin")); - ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); - ui.checkbox(text_cursor_preview, "Preview text cursor on hover"); - ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin")); - - ui.checkbox(button_frame, "Button has a frame"); - ui.checkbox(collapsing_header_frame, "Collapsing header has a frame"); - ui.checkbox( - indent_has_left_vline, - "Paint a vertical line to the left of indented regions", - ); + ui.checkbox(button_frame, "Button has a frame"); + ui.checkbox(collapsing_header_frame, "Collapsing header has a frame"); + ui.checkbox( + indent_has_left_vline, + "Paint a vertical line to the left of indented regions", + ); - ui.checkbox(striped, "By default, add stripes to grids and tables?"); + ui.checkbox(striped, "Default stripes on grids and tables"); - ui.checkbox(slider_trailing_fill, "Add trailing color to sliders"); + ui.checkbox(slider_trailing_fill, "Add trailing color to sliders"); - handle_shape.ui(ui); + handle_shape.ui(ui); - ComboBox::from_label("Interact Cursor") - .selected_text(format!("{interact_cursor:?}")) - .show_ui(ui, |ui| { - ui.selectable_value(interact_cursor, None, "None"); + ComboBox::from_label("Interact cursor") + .selected_text( + interact_cursor.map_or_else(|| "-".to_owned(), |cursor| format!("{cursor:?}")), + ) + .show_ui(ui, |ui| { + ui.selectable_value(interact_cursor, None, "-"); - for icon in CursorIcon::ALL { - ui.selectable_value(interact_cursor, Some(icon), format!("{icon:?}")); - } - }); + for cursor in CursorIcon::ALL { + ui.selectable_value(interact_cursor, Some(cursor), format!("{cursor:?}")) + .on_hover_cursor(cursor); + } + }) + .response + .on_hover_text("Use this cursor when hovering buttons etc"); - ui.checkbox(image_loading_spinners, "Image loading spinners") - .on_hover_text("Show a spinner when an Image is loading"); + ui.checkbox(image_loading_spinners, "Image loading spinners") + .on_hover_text("Show a spinner when an Image is loading"); - ui.horizontal(|ui| { - ui.label("Color picker type:"); - numeric_color_space.toggle_button_ui(ui); + ui.horizontal(|ui| { + ui.label("Color picker type"); + numeric_color_space.toggle_button_ui(ui); + }); }); - ui.vertical_centered(|ui| reset_button(ui, self)); + ui.vertical_centered(|ui| reset_button(ui, self, "Reset visuals")); } } @@ -1862,16 +1928,12 @@ impl DebugOptions { ui.checkbox(show_widget_hits, "Show widgets under mouse pointer"); - ui.vertical_centered(|ui| reset_button(ui, self)); + ui.vertical_centered(|ui| reset_button(ui, self, "Reset debug options")); } } -// TODO(emilk): improve and standardize `slider_vec2` -fn slider_vec2<'a>( - value: &'a mut Vec2, - range: std::ops::RangeInclusive, - text: &'a str, -) -> impl Widget + 'a { +// TODO(emilk): improve and standardize +fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive) -> impl Widget + '_ { move |ui: &mut crate::Ui| { ui.horizontal(|ui| { ui.add( @@ -1884,7 +1946,6 @@ fn slider_vec2<'a>( .clamp_range(range.clone()) .prefix("y: "), ); - ui.label(text); }) .response } @@ -1898,36 +1959,10 @@ fn ui_color(ui: &mut Ui, srgba: &mut Color32, label: impl Into) -> R .response } -fn rounding_ui(ui: &mut Ui, rounding: &mut Rounding) { - const MAX: f32 = 20.0; - let mut same = rounding.is_same(); - ui.group(|ui| { - ui.horizontal(|ui| { - ui.label("Rounding: "); - ui.radio_value(&mut same, true, "Same"); - ui.radio_value(&mut same, false, "Separate"); - }); - - if same { - let mut cr = rounding.nw; - ui.add(Slider::new(&mut cr, 0.0..=MAX)); - *rounding = Rounding::same(cr); - } else { - ui.add(Slider::new(&mut rounding.nw, 0.0..=MAX).text("North-West")); - ui.add(Slider::new(&mut rounding.ne, 0.0..=MAX).text("North-East")); - ui.add(Slider::new(&mut rounding.sw, 0.0..=MAX).text("South-West")); - ui.add(Slider::new(&mut rounding.se, 0.0..=MAX).text("South-East")); - if rounding.is_same() { - rounding.se *= 1.00001; - } - } - }); -} - impl HandleShape { pub fn ui(&mut self, ui: &mut Ui) { - ui.label("Widget handle shape"); ui.horizontal(|ui| { + ui.label("Slider handle"); ui.radio_value(self, Self::Circle, "Circle"); if ui .radio(matches!(self, Self::Rect { .. }), "Rectangle") @@ -1983,3 +2018,213 @@ impl std::fmt::Display for NumericColorSpace { } } } + +impl Widget for &mut Margin { + fn ui(self, ui: &mut Ui) -> Response { + let mut same = self.is_same(); + + let response = if same { + ui.horizontal(|ui| { + ui.checkbox(&mut same, "same"); + + let mut value = self.left; + ui.add(DragValue::new(&mut value)); + *self = Margin::same(value); + }) + .response + } else { + ui.vertical(|ui| { + ui.checkbox(&mut same, "same"); + + crate::Grid::new("margin").num_columns(2).show(ui, |ui| { + ui.label("Left"); + ui.add(DragValue::new(&mut self.left)); + ui.end_row(); + + ui.label("Right"); + ui.add(DragValue::new(&mut self.right)); + ui.end_row(); + + ui.label("Top"); + ui.add(DragValue::new(&mut self.top)); + ui.end_row(); + + ui.label("Bottom"); + ui.add(DragValue::new(&mut self.bottom)); + ui.end_row(); + }); + }) + .response + }; + + // Apply the checkbox: + if same { + *self = Margin::same((self.left + self.right + self.top + self.bottom) / 4.0); + } else if self.is_same() { + self.right *= 1.00001; // prevent collapsing into sameness + } + + response + } +} + +impl Widget for &mut Rounding { + fn ui(self, ui: &mut Ui) -> Response { + let mut same = self.is_same(); + + let response = if same { + ui.horizontal(|ui| { + ui.checkbox(&mut same, "same"); + + let mut cr = self.nw; + ui.add(DragValue::new(&mut cr).clamp_range(0.0..=f32::INFINITY)); + *self = Rounding::same(cr); + }) + .response + } else { + ui.vertical(|ui| { + ui.checkbox(&mut same, "same"); + + crate::Grid::new("rounding").num_columns(2).show(ui, |ui| { + ui.label("NW"); + ui.add(DragValue::new(&mut self.nw).clamp_range(0.0..=f32::INFINITY)); + ui.end_row(); + + ui.label("NE"); + ui.add(DragValue::new(&mut self.ne).clamp_range(0.0..=f32::INFINITY)); + ui.end_row(); + + ui.label("SW"); + ui.add(DragValue::new(&mut self.sw).clamp_range(0.0..=f32::INFINITY)); + ui.end_row(); + + ui.label("SE"); + ui.add(DragValue::new(&mut self.se).clamp_range(0.0..=f32::INFINITY)); + ui.end_row(); + }); + }) + .response + }; + + // Apply the checkbox: + if same { + *self = Rounding::same((self.nw + self.ne + self.sw + self.se) / 4.0); + } else if self.is_same() { + self.se *= 1.00001; // prevent collapsing into sameness + } + + response + } +} + +impl Widget for &mut Shadow { + fn ui(self, ui: &mut Ui) -> Response { + let epaint::Shadow { + offset, + blur, + spread, + color, + } = self; + + ui.vertical(|ui| { + crate::Grid::new("shadow_ui").show(ui, |ui| { + ui.add( + DragValue::new(&mut offset.x) + .speed(1.0) + .clamp_range(-100.0..=100.0) + .prefix("x: "), + ); + ui.add( + DragValue::new(&mut offset.y) + .speed(1.0) + .clamp_range(-100.0..=100.0) + .prefix("y: "), + ); + ui.end_row(); + + ui.add( + DragValue::new(blur) + .speed(1.0) + .clamp_range(0.0..=100.0) + .prefix("blur: "), + ); + + ui.add( + DragValue::new(spread) + .speed(1.0) + .clamp_range(0.0..=100.0) + .prefix("spread: "), + ); + }); + ui.color_edit_button_srgba(color); + }) + .response + } +} + +impl Widget for &mut Stroke { + fn ui(self, ui: &mut Ui) -> Response { + let Stroke { width, color } = self; + + ui.horizontal(|ui| { + ui.add( + DragValue::new(width) + .speed(0.1) + .clamp_range(0.0..=f32::INFINITY), + ) + .on_hover_text("Width"); + ui.color_edit_button_srgba(color); + + // stroke preview: + let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size); + let left = stroke_rect.left_center(); + let right = stroke_rect.right_center(); + ui.painter().line_segment([left, right], (*width, *color)); + }) + .response + } +} + +impl Widget for &mut crate::Frame { + fn ui(self, ui: &mut Ui) -> Response { + let crate::Frame { + inner_margin, + outer_margin, + rounding, + shadow, + fill, + stroke, + } = self; + + crate::Grid::new("frame") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Inner margin"); + ui.add(inner_margin); + ui.end_row(); + + ui.label("Outer margin"); + ui.add(outer_margin); + ui.end_row(); + + ui.label("Rounding"); + ui.add(rounding); + ui.end_row(); + + ui.label("Shadow"); + ui.add(shadow); + ui.end_row(); + + ui.label("Fill"); + ui.color_edit_button_srgba(fill); + ui.end_row(); + + ui.label("Stroke"); + ui.add(stroke); + ui.end_row(); + }) + .response + } +} diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index 77ee8692796e..6b3fb72e0c58 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -92,15 +92,19 @@ pub trait WidgetWithState { /// Show a button to reset a value to its default. /// The button is only enabled if the value does not already have its original value. -pub fn reset_button(ui: &mut Ui, value: &mut T) { - reset_button_with(ui, value, T::default()); +/// +/// The `text` could be something like "Reset foo". +pub fn reset_button(ui: &mut Ui, value: &mut T, text: &str) { + reset_button_with(ui, value, text, T::default()); } /// Show a button to reset a value to its default. /// The button is only enabled if the value does not already have its original value. -pub fn reset_button_with(ui: &mut Ui, value: &mut T, reset_value: T) { +/// +/// The `text` could be something like "Reset foo". +pub fn reset_button_with(ui: &mut Ui, value: &mut T, text: &str, reset_value: T) { if ui - .add_enabled(*value != reset_value, Button::new("Reset")) + .add_enabled(*value != reset_value, Button::new(text)) .clicked() { *value = reset_value; @@ -109,62 +113,11 @@ pub fn reset_button_with(ui: &mut Ui, value: &mut T, reset_value: // ---------------------------------------------------------------------------- +#[deprecated = "Use `ui.add(&mut stroke)` instead"] pub fn stroke_ui(ui: &mut crate::Ui, stroke: &mut epaint::Stroke, text: &str) { - let epaint::Stroke { width, color } = stroke; ui.horizontal(|ui| { - ui.add(DragValue::new(width).speed(0.1).clamp_range(0.0..=5.0)) - .on_hover_text("Width"); - ui.color_edit_button_srgba(color); ui.label(text); - - // stroke preview: - let (_id, stroke_rect) = ui.allocate_space(ui.spacing().interact_size); - let left = stroke_rect.left_center(); - let right = stroke_rect.right_center(); - ui.painter().line_segment([left, right], (*width, *color)); - }); -} - -pub(crate) fn shadow_ui(ui: &mut Ui, shadow: &mut epaint::Shadow, text: &str) { - let epaint::Shadow { - offset, - blur, - spread, - color, - } = shadow; - - ui.label(text); - ui.indent(text, |ui| { - crate::Grid::new("shadow_ui").show(ui, |ui| { - ui.add( - DragValue::new(&mut offset.x) - .speed(1.0) - .clamp_range(-100.0..=100.0) - .prefix("x: "), - ); - ui.add( - DragValue::new(&mut offset.y) - .speed(1.0) - .clamp_range(-100.0..=100.0) - .prefix("y: "), - ); - ui.end_row(); - - ui.add( - DragValue::new(blur) - .speed(1.0) - .clamp_range(0.0..=100.0) - .prefix("Blur:"), - ); - - ui.add( - DragValue::new(spread) - .speed(1.0) - .clamp_range(0.0..=100.0) - .prefix("Spread:"), - ); - }); - ui.color_edit_button_srgba(color); + ui.add(stroke); }); } diff --git a/crates/egui_demo_app/src/apps/fractal_clock.rs b/crates/egui_demo_app/src/apps/fractal_clock.rs index 6afe6f0459e2..1a6f4f55ebb5 100644 --- a/crates/egui_demo_app/src/apps/fractal_clock.rs +++ b/crates/egui_demo_app/src/apps/fractal_clock.rs @@ -79,7 +79,7 @@ impl FractalClock { ui.add(Slider::new(&mut self.luminance_factor, 0.0..=1.0).text("luminance factor")); ui.add(Slider::new(&mut self.width_factor, 0.0..=1.0).text("width factor")); - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.hyperlink_to( "Inspired by a screensaver by Rob Mayoff", diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index e87814cb4013..db3ee385d9be 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -245,7 +245,13 @@ impl eframe::App for WrapApp { } fn clear_color(&self, visuals: &egui::Visuals) -> [f32; 4] { - visuals.panel_fill.to_normalized_gamma_f32() + // Give the area behind the floating windows a different color, because it looks better: + let color = egui::lerp( + egui::Rgba::from(visuals.panel_fill)..=egui::Rgba::from(visuals.extreme_bg_color), + 0.5, + ); + let color = egui::Color32::from(color); + color.to_normalized_gamma_f32() } fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index de1d96cddf4a..505e938d0d82 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -29,6 +29,7 @@ impl Default for Demos { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), diff --git a/crates/egui_demo_lib/src/demo/frame_demo.rs b/crates/egui_demo_lib/src/demo/frame_demo.rs new file mode 100644 index 000000000000..b4013c93bce9 --- /dev/null +++ b/crates/egui_demo_lib/src/demo/frame_demo.rs @@ -0,0 +1,73 @@ +/// Shows off a table with dynamic layout +#[derive(PartialEq)] +pub struct FrameDemo { + frame: egui::Frame, +} + +impl Default for FrameDemo { + fn default() -> Self { + Self { + frame: egui::Frame { + inner_margin: 12.0.into(), + outer_margin: 24.0.into(), + rounding: 14.0.into(), + shadow: egui::Shadow { + offset: [8.0, 12.0].into(), + blur: 16.0, + spread: 0.0, + color: egui::Color32::from_black_alpha(180), + }, + fill: egui::Color32::from_rgba_unmultiplied(97, 0, 255, 128), + stroke: egui::Stroke::new(1.0, egui::Color32::GRAY), + }, + } + } +} + +impl super::Demo for FrameDemo { + fn name(&self) -> &'static str { + "▣ Frame" + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + egui::Window::new(self.name()) + .open(open) + .resizable(false) + .show(ctx, |ui| { + use super::View as _; + self.ui(ui); + }); + } +} + +impl super::View for FrameDemo { + fn ui(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.vertical(|ui| { + ui.add(&mut self.frame); + + ui.add_space(8.0); + ui.set_max_width(ui.min_size().x); + ui.vertical_centered(|ui| egui::reset_button(ui, self, "Reset")); + }); + + ui.separator(); + + ui.vertical(|ui| { + // We want to paint a background around the outer margin of the demonstration frame, so we use another frame around it: + egui::Frame::default() + .stroke(ui.visuals().widgets.noninteractive.bg_stroke) + .rounding(ui.visuals().widgets.noninteractive.rounding) + .show(ui, |ui| { + self.frame.show(ui, |ui| { + ui.label(egui::RichText::new("Content").color(egui::Color32::WHITE)); + }); + }); + }); + }); + + ui.set_max_width(ui.min_size().x); + ui.separator(); + ui.vertical_centered(|ui| ui.add(crate::egui_github_link_file!())); + } +} diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index b4b28511b62d..f3c7a8dcfec2 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -328,7 +328,7 @@ impl Default for ColorWidgets { impl ColorWidgets { fn ui(&mut self, ui: &mut Ui) { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.label("egui lets you edit colors stored as either sRGBA or linear RGBA and with or without premultiplied alpha"); diff --git a/crates/egui_demo_lib/src/demo/mod.rs b/crates/egui_demo_lib/src/demo/mod.rs index d54498bbcf9a..8a911c9d68b2 100644 --- a/crates/egui_demo_lib/src/demo/mod.rs +++ b/crates/egui_demo_lib/src/demo/mod.rs @@ -13,6 +13,7 @@ pub mod demo_app_windows; pub mod drag_and_drop; pub mod extra_viewport; pub mod font_book; +pub mod frame_demo; pub mod highlighting; pub mod layout_test; pub mod misc_demo_window; diff --git a/crates/egui_demo_lib/src/demo/paint_bezier.rs b/crates/egui_demo_lib/src/demo/paint_bezier.rs index ad507de50a43..99d477b44ae1 100644 --- a/crates/egui_demo_lib/src/demo/paint_bezier.rs +++ b/crates/egui_demo_lib/src/demo/paint_bezier.rs @@ -43,13 +43,27 @@ impl Default for PaintBezier { impl PaintBezier { pub fn ui_control(&mut self, ui: &mut egui::Ui) { ui.collapsing("Colors", |ui| { - ui.horizontal(|ui| { - ui.label("Fill color:"); - ui.color_edit_button_srgba(&mut self.fill); - }); - egui::stroke_ui(ui, &mut self.stroke, "Curve Stroke"); - egui::stroke_ui(ui, &mut self.aux_stroke, "Auxiliary Stroke"); - egui::stroke_ui(ui, &mut self.bounding_box_stroke, "Bounding Box Stroke"); + Grid::new("colors") + .num_columns(2) + .spacing([12.0, 8.0]) + .striped(true) + .show(ui, |ui| { + ui.label("Fill color"); + ui.color_edit_button_srgba(&mut self.fill); + ui.end_row(); + + ui.label("Curve Stroke"); + ui.add(&mut self.stroke); + ui.end_row(); + + ui.label("Auxiliary Stroke"); + ui.add(&mut self.aux_stroke); + ui.end_row(); + + ui.label("Bounding Box Stroke"); + ui.add(&mut self.bounding_box_stroke); + ui.end_row(); + }); }); ui.collapsing("Global tessellation options", |ui| { diff --git a/crates/egui_demo_lib/src/demo/painting.rs b/crates/egui_demo_lib/src/demo/painting.rs index 57e5f114da48..d95e85346533 100644 --- a/crates/egui_demo_lib/src/demo/painting.rs +++ b/crates/egui_demo_lib/src/demo/painting.rs @@ -20,7 +20,8 @@ impl Default for Painting { impl Painting { pub fn ui_control(&mut self, ui: &mut egui::Ui) -> egui::Response { ui.horizontal(|ui| { - egui::stroke_ui(ui, &mut self.stroke, "Stroke"); + ui.label("Stroke:"); + ui.add(&mut self.stroke); ui.separator(); if ui.button("Clear Painting").clicked() { self.lines.clear(); diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 0f8b8edcc13e..12e45aa0f098 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -62,7 +62,7 @@ impl super::Demo for PlotDemo { impl super::View for PlotDemo { fn ui(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.collapsing("Instructions", |ui| { ui.label("Pan by dragging, or scroll (+ shift = horizontal)."); ui.label("Box zooming: Right click to zoom in and zoom out using a selection."); diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index 0530f2fee99e..9c490d23d8a7 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -336,7 +336,7 @@ impl super::View for ScrollTo { ui.separator(); ui.vertical_centered(|ui| { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.add(crate::egui_github_link_file!()); }); } diff --git a/crates/egui_demo_lib/src/demo/sliders.rs b/crates/egui_demo_lib/src/demo/sliders.rs index d2b065403c50..4142c7af0e1c 100644 --- a/crates/egui_demo_lib/src/demo/sliders.rs +++ b/crates/egui_demo_lib/src/demo/sliders.rs @@ -198,7 +198,7 @@ impl super::View for Sliders { ui.add_space(8.0); ui.vertical_centered(|ui| { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.add(crate::egui_github_link_file!()); }); } diff --git a/crates/egui_demo_lib/src/demo/strip_demo.rs b/crates/egui_demo_lib/src/demo/strip_demo.rs index defeac8275a6..091d4f01dacd 100644 --- a/crates/egui_demo_lib/src/demo/strip_demo.rs +++ b/crates/egui_demo_lib/src/demo/strip_demo.rs @@ -8,7 +8,7 @@ pub struct StripDemo {} impl super::Demo for StripDemo { fn name(&self) -> &'static str { - "▣ Strip Demo" + "▣ Strip" } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index f15d2df9e865..4cf0449fb6f9 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -40,7 +40,7 @@ impl Default for TableDemo { impl super::Demo for TableDemo { fn name(&self) -> &'static str { - "☰ Table Demo" + "☰ Table" } fn show(&mut self, ctx: &egui::Context, open: &mut bool) { diff --git a/crates/egui_demo_lib/src/demo/tests.rs b/crates/egui_demo_lib/src/demo/tests.rs index 8d45de1e15b6..a29e37fe0bca 100644 --- a/crates/egui_demo_lib/src/demo/tests.rs +++ b/crates/egui_demo_lib/src/demo/tests.rs @@ -131,7 +131,7 @@ impl super::Demo for ManualLayoutTest { impl super::View for ManualLayoutTest { fn ui(&mut self, ui: &mut egui::Ui) { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); let Self { widget_offset, @@ -298,7 +298,7 @@ impl super::View for TableTest { }); ui.vertical_centered(|ui| { - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.add(crate::egui_github_link_file!()); }); } diff --git a/crates/egui_demo_lib/src/demo/window_options.rs b/crates/egui_demo_lib/src/demo/window_options.rs index baa6eac652e8..54c77988e750 100644 --- a/crates/egui_demo_lib/src/demo/window_options.rs +++ b/crates/egui_demo_lib/src/demo/window_options.rs @@ -146,7 +146,7 @@ impl super::View for WindowOptions { if ui.button("Disable for 2 seconds").clicked() { self.disabled_time = ui.input(|i| i.time); } - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.add(crate::egui_github_link_file!()); }); } diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs index 2a44d78f3a7c..0b0bb9608a3b 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs @@ -48,7 +48,7 @@ impl EasyMarkEditor { let _ = ui.button("Hotkeys").on_hover_ui(nested_hotkeys_ui); ui.checkbox(&mut self.show_rendered, "Show rendered"); ui.checkbox(&mut self.highlight_editor, "Highlight editor"); - egui::reset_button(ui, self); + egui::reset_button(ui, self, "Reset"); ui.end_row(); }); ui.separator(); From c4f16af721be7060662cc76f2bd7793e3b656217 Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Thu, 28 Mar 2024 10:43:28 +0100 Subject: [PATCH 0010/1202] Prevent plot from resetting one axis while zooming/dragging the other (#4252) * Closes --- crates/egui_plot/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 26567117652d..6056f46bd4e7 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -1021,7 +1021,7 @@ impl Plot { delta.y = 0.0; } mem.transform.translate_bounds(delta); - mem.auto_bounds = !allow_drag; + mem.auto_bounds = mem.auto_bounds.and(!allow_drag); } // Zooming @@ -1092,7 +1092,7 @@ impl Plot { } if zoom_factor != Vec2::splat(1.0) { mem.transform.zoom(zoom_factor, hover_pos); - mem.auto_bounds = !allow_zoom; + mem.auto_bounds = mem.auto_bounds.and(!allow_zoom); } } if allow_scroll.any() { From 60da4b4f65531bb78736c8f6471b55cc8febb1b1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 10:59:24 +0100 Subject: [PATCH 0011/1202] Web: repaint if the `#hash` in the URL changes (#4261) Fixes a problem in egui.rs where the back-button would not work to switch between the top-level tabs --- crates/eframe/src/web/events.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 30775733e583..c6737f6e770f 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -242,6 +242,7 @@ pub(crate) fn install_window_events(runner_ref: &WebRunner) -> Result<(), JsValu runner_ref.add_event_listener(&window, "hashchange", |_: web_sys::Event, runner| { // `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here runner.frame.info.web_info.location.hash = location_hash(); + runner.needs_repaint.repaint_asap(); // tell the user about the new hash })?; Ok(()) From 7cc98bd38e1d34ca9a2fbd306e92ba881857e3f4 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann <45795270+justusdieckmann@users.noreply.github.com> Date: Fri, 29 Mar 2024 11:55:49 +0100 Subject: [PATCH 0012/1202] Add web support for `zoom_factor` (#4260) Before, when setting the `zoom_factor`, the website was already enlarged, but the zoom was ignored when calculating the logical window size and mouse position, causing an offset between the actual cursor and selected elements. That is addressed here --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/web/app_runner.rs | 6 ++++-- crates/eframe/src/web/events.rs | 24 ++++++++++++++++++------ crates/eframe/src/web/input.rs | 22 +++++++++++++++------- crates/eframe/src/web/mod.rs | 4 ++-- crates/egui/src/memory.rs | 6 ++++++ 5 files changed, 45 insertions(+), 17 deletions(-) diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 31f6e79c01c8..620fe86fd624 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -60,7 +60,9 @@ impl AppRunner { super::storage::load_memory(&egui_ctx); egui_ctx.options_mut(|o| { - // On web, the browser controls the zoom factor: + // On web by default egui follows the zoom factor of the browser, + // and lets the browser handle the zoom shortscuts. + // A user can still zoom egui separately by calling [`egui::Context::set_zoom_factor`]. o.zoom_with_keyboard = false; o.zoom_factor = 1.0; }); @@ -183,7 +185,7 @@ impl AppRunner { /// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`]. pub fn logic(&mut self) { super::resize_canvas_to_screen_size(self.canvas(), self.web_options.max_size_points); - let canvas_size = super::canvas_size_in_points(self.canvas()); + let canvas_size = super::canvas_size_in_points(self.canvas(), self.egui_ctx()); let raw_input = self.input.new_frame(canvas_size); let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index c6737f6e770f..224ae3ac0db6 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -297,7 +297,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.modifiers = modifiers; if let Some(button) = button_from_mouse_event(&event) { - let pos = pos_from_mouse_event(runner.canvas(), &event); + let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); let modifiers = runner.input.raw.modifiers; runner.input.raw.events.push(egui::Event::PointerButton { pos, @@ -324,7 +324,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu |event: web_sys::MouseEvent, runner| { let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.modifiers = modifiers; - let pos = pos_from_mouse_event(runner.canvas(), &event); + let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); runner.input.raw.events.push(egui::Event::PointerMoved(pos)); runner.needs_repaint.repaint_asap(); event.stop_propagation(); @@ -336,7 +336,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu let modifiers = modifiers_from_mouse_event(&event); runner.input.raw.modifiers = modifiers; if let Some(button) = button_from_mouse_event(&event) { - let pos = pos_from_mouse_event(runner.canvas(), &event); + let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); let modifiers = runner.input.raw.modifiers; runner.input.raw.events.push(egui::Event::PointerButton { pos, @@ -374,7 +374,12 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu "touchstart", |event: web_sys::TouchEvent, runner| { let mut latest_touch_pos_id = runner.input.latest_touch_pos_id; - let pos = pos_from_touch_event(runner.canvas(), &event, &mut latest_touch_pos_id); + let pos = pos_from_touch_event( + runner.canvas(), + &event, + &mut latest_touch_pos_id, + runner.egui_ctx(), + ); runner.input.latest_touch_pos_id = latest_touch_pos_id; runner.input.latest_touch_pos = Some(pos); let modifiers = runner.input.raw.modifiers; @@ -397,7 +402,12 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu "touchmove", |event: web_sys::TouchEvent, runner| { let mut latest_touch_pos_id = runner.input.latest_touch_pos_id; - let pos = pos_from_touch_event(runner.canvas(), &event, &mut latest_touch_pos_id); + let pos = pos_from_touch_event( + runner.canvas(), + &event, + &mut latest_touch_pos_id, + runner.egui_ctx(), + ); runner.input.latest_touch_pos_id = latest_touch_pos_id; runner.input.latest_touch_pos = Some(pos); runner.input.raw.events.push(egui::Event::PointerMoved(pos)); @@ -460,7 +470,9 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu }); let scroll_multiplier = match unit { - egui::MouseWheelUnit::Page => canvas_size_in_points(runner.canvas()).y, + egui::MouseWheelUnit::Page => { + canvas_size_in_points(runner.canvas(), runner.egui_ctx()).y + } egui::MouseWheelUnit::Line => { #[allow(clippy::let_and_return)] let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in winit. diff --git a/crates/eframe/src/web/input.rs b/crates/eframe/src/web/input.rs index 4ed0227739a1..361e8031d407 100644 --- a/crates/eframe/src/web/input.rs +++ b/crates/eframe/src/web/input.rs @@ -3,11 +3,13 @@ use super::{canvas_origin, AppRunner}; pub fn pos_from_mouse_event( canvas: &web_sys::HtmlCanvasElement, event: &web_sys::MouseEvent, + ctx: &egui::Context, ) -> egui::Pos2 { let rect = canvas.get_bounding_client_rect(); + let zoom_factor = ctx.zoom_factor(); egui::Pos2 { - x: event.client_x() as f32 - rect.left() as f32, - y: event.client_y() as f32 - rect.top() as f32, + x: (event.client_x() as f32 - rect.left() as f32) / zoom_factor, + y: (event.client_y() as f32 - rect.top() as f32) / zoom_factor, } } @@ -32,6 +34,7 @@ pub fn pos_from_touch_event( canvas: &web_sys::HtmlCanvasElement, event: &web_sys::TouchEvent, touch_id_for_pos: &mut Option, + egui_ctx: &egui::Context, ) -> egui::Pos2 { let touch_for_pos = if let Some(touch_id_for_pos) = touch_id_for_pos { // search for the touch we previously used for the position @@ -49,14 +52,19 @@ pub fn pos_from_touch_event( .or_else(|| event.touches().get(0)) .map_or(Default::default(), |touch| { *touch_id_for_pos = Some(egui::TouchId::from(touch.identifier())); - pos_from_touch(canvas_origin(canvas), &touch) + pos_from_touch(canvas_origin(canvas), &touch, egui_ctx) }) } -fn pos_from_touch(canvas_origin: egui::Pos2, touch: &web_sys::Touch) -> egui::Pos2 { +fn pos_from_touch( + canvas_origin: egui::Pos2, + touch: &web_sys::Touch, + egui_ctx: &egui::Context, +) -> egui::Pos2 { + let zoom_factor = egui_ctx.zoom_factor(); egui::Pos2 { - x: touch.page_x() as f32 - canvas_origin.x, - y: touch.page_y() as f32 - canvas_origin.y, + x: (touch.page_x() as f32 - canvas_origin.x) / zoom_factor, + y: (touch.page_y() as f32 - canvas_origin.y) / zoom_factor, } } @@ -68,7 +76,7 @@ pub fn push_touches(runner: &mut AppRunner, phase: egui::TouchPhase, event: &web device_id: egui::TouchDeviceId(0), id: egui::TouchId::from(touch.identifier()), phase, - pos: pos_from_touch(canvas_origin, &touch), + pos: pos_from_touch(canvas_origin, &touch, runner.egui_ctx()), force: Some(touch.force()), }); } diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index d88e94229fd0..a322a530aa67 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -116,8 +116,8 @@ fn canvas_origin(canvas: &web_sys::HtmlCanvasElement) -> egui::Pos2 { egui::pos2(rect.left() as f32, rect.top() as f32) } -fn canvas_size_in_points(canvas: &web_sys::HtmlCanvasElement) -> egui::Vec2 { - let pixels_per_point = native_pixels_per_point(); +fn canvas_size_in_points(canvas: &web_sys::HtmlCanvasElement, ctx: &egui::Context) -> egui::Vec2 { + let pixels_per_point = ctx.pixels_per_point(); egui::vec2( canvas.width() as f32 / pixels_per_point, canvas.height() as f32 / pixels_per_point, diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index b84308283724..7d61acd965f4 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -185,6 +185,12 @@ pub struct Options { /// presses Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser. /// /// This is `true` by default. + /// + /// On the web-backend of `eframe` this is set to false by default, + /// so that the zoom shortcuts are handled exclusively by the browser, + /// which will change the `native_pixels_per_point` (`devicePixelRatio`). + /// You can still zoom egui independently by calling [`crate::Context::set_zoom_factor`], + /// which will be applied on top of the browsers global zoom. #[cfg_attr(feature = "serde", serde(skip))] pub zoom_with_keyboard: bool, From 946bc888db2a19dc0545480f517b96bab3ee9f9f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 12:15:03 +0100 Subject: [PATCH 0013/1202] Hide shortcut text on zoom buttons if `zoom_with_keyboard` is false (#4262) --- crates/egui/src/gui_zoom.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/gui_zoom.rs b/crates/egui/src/gui_zoom.rs index c4b5deb5ed60..bde60f4321b7 100644 --- a/crates/egui/src/gui_zoom.rs +++ b/crates/egui/src/gui_zoom.rs @@ -70,10 +70,20 @@ pub fn zoom_out(ctx: &Context) { /// /// This is meant to be called from within a menu (See [`Ui::menu_button`]). pub fn zoom_menu_buttons(ui: &mut Ui) { + fn button(ctx: &Context, text: &str, shortcut: &KeyboardShortcut) -> Button<'static> { + let btn = Button::new(text); + let zoom_with_keyboard = ctx.options(|o| o.zoom_with_keyboard); + if zoom_with_keyboard { + btn.shortcut_text(ctx.format_shortcut(shortcut)) + } else { + btn + } + } + if ui .add_enabled( ui.ctx().zoom_factor() < MAX_ZOOM_FACTOR, - Button::new("Zoom In").shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_IN)), + button(ui.ctx(), "Zoom In", &kb_shortcuts::ZOOM_IN), ) .clicked() { @@ -84,8 +94,7 @@ pub fn zoom_menu_buttons(ui: &mut Ui) { if ui .add_enabled( ui.ctx().zoom_factor() > MIN_ZOOM_FACTOR, - Button::new("Zoom Out") - .shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_OUT)), + button(ui.ctx(), "Zoom Out", &kb_shortcuts::ZOOM_OUT), ) .clicked() { @@ -96,8 +105,7 @@ pub fn zoom_menu_buttons(ui: &mut Ui) { if ui .add_enabled( ui.ctx().zoom_factor() != 1.0, - Button::new("Reset Zoom") - .shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_RESET)), + button(ui.ctx(), "Reset Zoom", &kb_shortcuts::ZOOM_RESET), ) .clicked() { From dfbe118ea47576cc0400899649850193515ae630 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 13:12:26 +0100 Subject: [PATCH 0014/1202] Release 0.27.1 (#4264) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## egui changelog ### 🐛 Fixed * Fix visual glitch on the right side of highly rounded rectangles [#4244](https://github.com/emilk/egui/pull/4244) * Prevent visual glitch when shadow blur width is very high [#4245](https://github.com/emilk/egui/pull/4245) * Fix `InputState::any_touches` and add `InputState::has_touch_screen` [#4247](https://github.com/emilk/egui/pull/4247) * Fix `Context::repaint_causes` returning no causes [#4248](https://github.com/emilk/egui/pull/4248) * Fix touch-and-hold to open context menu [#4249](https://github.com/emilk/egui/pull/4249) * Hide shortcut text on zoom buttons if `zoom_with_keyboard` is false [#4262](https://github.com/emilk/egui/pull/4262) ### 🔧 Changed * Don't apply a clip rect to the contents of an `Area` or `Window` [#4258](https://github.com/emilk/egui/pull/4258) ## eframe changelog * Web: repaint if the `#hash` in the URL changes [#4261](https://github.com/emilk/egui/pull/4261) * Add web support for `zoom_factor` [#4260](https://github.com/emilk/egui/pull/4260) (thanks [@justusdieckmann](https://github.com/justusdieckmann)!) --------- Co-authored-by: Justus Dieckmann <45795270+justusdieckmann@users.noreply.github.com> --- CHANGELOG.md | 13 ++++++++ Cargo.lock | 24 +++++++-------- Cargo.toml | 24 +++++++-------- crates/ecolor/CHANGELOG.md | 4 +++ crates/eframe/CHANGELOG.md | 5 ++++ crates/egui-wgpu/CHANGELOG.md | 4 +++ crates/egui-winit/CHANGELOG.md | 4 +++ crates/egui_extras/CHANGELOG.md | 4 +++ crates/egui_glow/CHANGELOG.md | 4 +++ crates/egui_plot/CHANGELOG.md | 4 +++ crates/epaint/CHANGELOG.md | 5 ++++ scripts/generate_changelog.py | 53 ++++++++++++++++++++++----------- 12 files changed, 107 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 128eba907ebc..764b01eeeb8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +### 🐛 Fixed +* Fix visual glitch on the right side of highly rounded rectangles [#4244](https://github.com/emilk/egui/pull/4244) +* Prevent visual glitch when shadow blur width is very high [#4245](https://github.com/emilk/egui/pull/4245) +* Fix `InputState::any_touches` and add `InputState::has_touch_screen` [#4247](https://github.com/emilk/egui/pull/4247) +* Fix `Context::repaint_causes` returning no causes [#4248](https://github.com/emilk/egui/pull/4248) +* Fix touch-and-hold to open context menu [#4249](https://github.com/emilk/egui/pull/4249) +* Hide shortcut text on zoom buttons if `zoom_with_keyboard` is false [#4262](https://github.com/emilk/egui/pull/4262) + +### 🔧 Changed +* Don't apply a clip rect to the contents of an `Area` or `Window` [#4258](https://github.com/emilk/egui/pull/4258) + + ## 0.27.0 - 2024-03-26 - Nicer menus and new hit test logic The hit test logic (what is the user clicking on?) has been completely rewritten, and should now be much more accurate and helpful. The hit test and interaction logic is run at the start of the frame, using the widgets rects from the previous frame, but the latest mouse coordinates. diff --git a/Cargo.lock b/Cargo.lock index 2a517c7059f6..0a72704b8096 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1187,7 +1187,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "ecolor" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "cint", @@ -1198,7 +1198,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "cocoa", @@ -1236,7 +1236,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.27.0" +version = "0.27.1" dependencies = [ "accesskit", "ahash", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "document-features", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.27.0" +version = "0.27.1" dependencies = [ "accesskit_winit", "arboard", @@ -1287,7 +1287,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "chrono", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.27.0" +version = "0.27.1" dependencies = [ "chrono", "criterion", @@ -1327,7 +1327,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.27.0" +version = "0.27.1" dependencies = [ "chrono", "document-features", @@ -1345,7 +1345,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "document-features", @@ -1365,7 +1365,7 @@ dependencies = [ [[package]] name = "egui_plot" -version = "0.27.0" +version = "0.27.1" dependencies = [ "document-features", "egui", @@ -1394,7 +1394,7 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "emath" -version = "0.27.0" +version = "0.27.1" dependencies = [ "bytemuck", "document-features", @@ -1469,7 +1469,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.27.0" +version = "0.27.1" dependencies = [ "ab_glyph", "ahash", diff --git a/Cargo.toml b/Cargo.toml index 431bffcfcc93..bde03d7c8fb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ edition = "2021" license = "MIT OR Apache-2.0" rust-version = "1.72" -version = "0.27.0" +version = "0.27.1" [profile.release] @@ -48,17 +48,17 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.27.0", path = "crates/emath", default-features = false } -ecolor = { version = "0.27.0", path = "crates/ecolor", default-features = false } -epaint = { version = "0.27.0", path = "crates/epaint", default-features = false } -egui = { version = "0.27.0", path = "crates/egui", default-features = false } -egui_plot = { version = "0.27.0", path = "crates/egui_plot", default-features = false } -egui-winit = { version = "0.27.0", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.27.0", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.27.0", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.27.0", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.27.0", path = "crates/egui_glow", default-features = false } -eframe = { version = "0.27.0", path = "crates/eframe", default-features = false } +emath = { version = "0.27.1", path = "crates/emath", default-features = false } +ecolor = { version = "0.27.1", path = "crates/ecolor", default-features = false } +epaint = { version = "0.27.1", path = "crates/epaint", default-features = false } +egui = { version = "0.27.1", path = "crates/egui", default-features = false } +egui_plot = { version = "0.27.1", path = "crates/egui_plot", default-features = false } +egui-winit = { version = "0.27.1", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.27.1", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.27.1", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.27.1", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.27.1", path = "crates/egui_glow", default-features = false } +eframe = { version = "0.27.1", path = "crates/eframe", default-features = false } #TODO(emilk): make more things workspace dependencies ahash = { version = "0.8.6", default-features = false, features = [ diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index d21513d6c15e..493d125e7f14 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Nothing new + + ## 0.27.0 - 2024-03-26 * Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 3f3c3d689ffa..e110500cb53d 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,11 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Web: repaint if the `#hash` in the URL changes [#4261](https://github.com/emilk/egui/pull/4261) +* Add web support for `zoom_factor` [#4260](https://github.com/emilk/egui/pull/4260) (thanks [@justusdieckmann](https://github.com/justusdieckmann)!) + + ## 0.27.0 - 2024-03-26 * Update to document-features 0.2.8 [#4003](https://github.com/emilk/egui/pull/4003) * Added `App::raw_input_hook` allows for the manipulation or filtering of raw input events [#4008](https://github.com/emilk/egui/pull/4008) (thanks [@varphone](https://github.com/varphone)!) diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 92da1e975a0c..9dea60d1e0a7 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Nothing new + + ## 0.27.0 - 2024-03-26 * Improve panic message in egui-wgpu when failing to create buffers [#3986](https://github.com/emilk/egui/pull/3986) diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index ff85f8b5dcbf..52dd56e8fddc 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Nothing new + + ## 0.27.0 - 2024-03-26 * Update memoffset to 0.9.0, arboard to 3.3.1, and remove egui_glow's needless dependency on pure_glow's deps [#4036](https://github.com/emilk/egui/pull/4036) (thanks [@Nopey](https://github.com/Nopey)!) * Don't clear modifier state on focus change [#4157](https://github.com/emilk/egui/pull/4157) (thanks [@ming08108](https://github.com/ming08108)!) diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index fd498f3773aa..0980768aee2c 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Nothing new + + ## 0.27.0 - 2024-03-26 * Add scroll bar visibility option to `Table` widget [#3981](https://github.com/emilk/egui/pull/3981) (thanks [@richardhozak](https://github.com/richardhozak)!) * Update `ehttp` to 0.5 [#4055](https://github.com/emilk/egui/pull/4055) diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 970fb87a2b23..f39d8a199c57 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Nothing new + + ## 0.27.0 - 2024-03-26 * Add `sense` option to `Plot` [#4052](https://github.com/emilk/egui/pull/4052) (thanks [@AmesingFlank](https://github.com/AmesingFlank)!) * Plot widget - allow disabling scroll for x and y separately [#4051](https://github.com/emilk/egui/pull/4051) (thanks [@YgorSouza](https://github.com/YgorSouza)!) diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index 4e222f85563e..b5fc33f67316 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -5,6 +5,11 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.1 - 2024-03-29 +* Fix visual glitch on the right side of highly rounded rectangles [#4244](https://github.com/emilk/egui/pull/4244) +* Prevent visual glitch when shadow blur width is very high [#4245](https://github.com/emilk/egui/pull/4245) + + ## 0.27.0 - 2024-03-26 * Add `ColorImage::from_gray_iter` [#3536](https://github.com/emilk/egui/pull/3536) (thanks [@wangxiaochuTHU](https://github.com/wangxiaochuTHU)!) * Convenience const fn for `Margin`, `Rounding` and `Shadow` [#4080](https://github.com/emilk/egui/pull/4080) (thanks [@0Qwel](https://github.com/0Qwel)!) diff --git a/scripts/generate_changelog.py b/scripts/generate_changelog.py index 17b44f49be5c..6b0fc71eb85b 100755 --- a/scripts/generate_changelog.py +++ b/scripts/generate_changelog.py @@ -115,18 +115,22 @@ def print_section(crate: str, items: List[str]) -> None: print() +def changelog_filepath(crate: str) -> str: + scripts_dirpath = os.path.dirname(os.path.realpath(__file__)) + if crate == "egui": + file_path = f"{scripts_dirpath}/../CHANGELOG.md" + else: + file_path = f"{scripts_dirpath}/../crates/{crate}/CHANGELOG.md" + return os.path.normpath(file_path) + + def add_to_changelog_file(crate: str, items: List[str], version: str) -> None: insert_text = f"\n## {version} - {date.today()}\n" for item in items: insert_text += f"* {item}\n" insert_text += "\n" - scripts_dirpath = os.path.dirname(os.path.realpath(__file__)) - if crate == "egui": - file_path = f"{scripts_dirpath}/../CHANGELOG.md" - else: - file_path = f"{scripts_dirpath}/../crates/{crate}/CHANGELOG.md" - file_path = os.path.normpath(file_path) + file_path = changelog_filepath(crate) with open(file_path, 'r') as file: content = file.read() @@ -151,6 +155,28 @@ def main() -> None: print("ERROR: --version is required when --write is used") sys.exit(1) + crate_names = [ + "ecolor", + "eframe", + "egui_extras", + "egui_plot", + "egui_glow", + "egui-wgpu", + "egui-winit", + "egui", + "epaint", + ] + + # We read all existing changelogs to remove duplicate entries. + # For instance: the PRs that were part of 0.27.2 would also show up in the diff for `0.27.0..HEAD` + # when its time for a 0.28 release. We can't do `0.27.2..HEAD` because we would miss PRs that were + # merged before in `0.27.0..0.27.2` that were not cherry-picked into `0.27.2`. + all_changelogs = "" + for crate in crate_names: + file_path = changelog_filepath(crate) + with open(file_path, 'r') as file: + all_changelogs += file.read() + repo = Repo(".") commits = list(repo.iter_commits(args.commit_range)) commits.reverse() # Most recent last @@ -167,17 +193,6 @@ def main() -> None: ignore_labels = ["CI", "dependencies"] - crate_names = [ - "ecolor", - "eframe", - "egui_extras", - "egui_plot", - "egui_glow", - "egui-wgpu", - "egui-winit", - "egui", - "epaint", - ] sections = {} unsorted_prs = [] unsorted_commits = [] @@ -193,6 +208,10 @@ def main() -> None: summary = f"{title} [{hexsha[:7]}](https://github.com/{OWNER}/{REPO}/commit/{hexsha})" unsorted_commits.append(summary) else: + if f"[#{pr_number}]" in all_changelogs: + print(f"Ignoring PR that is already in the changelog: #{pr_number}") + continue + # We prefer the PR title if available title = pr_info.pr_title if pr_info else title labels = pr_info.labels if pr_info else [] From 0a428f0887c93f44545ec280778b295e7aa577c8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 15:58:22 +0100 Subject: [PATCH 0015/1202] Improve docs of `ui.collapsing` See https://github.com/emilk/egui/issues/4265 --- crates/egui/src/ui.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 56db9e97f60d..9ff05a267e87 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1806,6 +1806,9 @@ impl Ui { } /// A [`CollapsingHeader`] that starts out collapsed. + /// + /// The name must be unique within the current parent, + /// or you need to use [`CollapsingHeader::id_source`]. pub fn collapsing( &mut self, heading: impl Into, From 73dbfd689b6d1cf67bec170f3690a1c16d44e368 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 15:58:37 +0100 Subject: [PATCH 0016/1202] Don't wrap the text in the `Frame` demo --- crates/egui_demo_lib/src/demo/frame_demo.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/egui_demo_lib/src/demo/frame_demo.rs b/crates/egui_demo_lib/src/demo/frame_demo.rs index b4013c93bce9..0027a98d9dae 100644 --- a/crates/egui_demo_lib/src/demo/frame_demo.rs +++ b/crates/egui_demo_lib/src/demo/frame_demo.rs @@ -60,6 +60,7 @@ impl super::View for FrameDemo { .rounding(ui.visuals().widgets.noninteractive.rounding) .show(ui, |ui| { self.frame.show(ui, |ui| { + ui.style_mut().wrap = Some(false); ui.label(egui::RichText::new("Content").color(egui::Color32::WHITE)); }); }); From a541e021aa6f972d1eb6006505e02ba1c2923a3d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 29 Mar 2024 20:29:42 +0100 Subject: [PATCH 0017/1202] Add `RectShape::blur_width` to implement shadows (#4267) This is mostly a refactor, but has some performance benefits: * We (re)use the same tessellator as for everything else, leading to less allocations * We cull shapes before rendering them Adding `RectShape::blur_width` means it can also be used for other effects, such as glow. --- crates/egui/src/containers/frame.rs | 5 ++-- crates/egui/src/widgets/image.rs | 1 + crates/egui/src/widgets/slider.rs | 10 ++----- crates/epaint/src/shadow.rs | 42 +++------------------------- crates/epaint/src/shape.rs | 26 ++++++++++++++++- crates/epaint/src/shape_transform.rs | 1 + crates/epaint/src/tessellator.rs | 28 ++++++++++++++++++- 7 files changed, 62 insertions(+), 51 deletions(-) diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 2847fdfd1c53..6d7ba6e6274b 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -291,9 +291,8 @@ impl Frame { if shadow == Default::default() { frame_shape } else { - let shadow = shadow.tessellate(outer_rect, rounding); - let shadow = Shape::Mesh(shadow); - Shape::Vec(vec![shadow, frame_shape]) + let shadow = shadow.as_shape(outer_rect, rounding); + Shape::Vec(vec![Shape::from(shadow), frame_shape]) } } } diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 90176018cb7e..945c497dd4e9 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -762,6 +762,7 @@ pub fn paint_texture_at( rounding: options.rounding, fill: options.tint, stroke: Stroke::NONE, + blur_width: 0.0, fill_texture_id: texture.id, uv: options.uv, }); diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index c5c60b926108..a428fb7dc4da 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -739,14 +739,8 @@ impl<'a> Slider<'a> { }; let v = v + Vec2::splat(visuals.expansion); let rect = Rect::from_center_size(center, 2.0 * v); - ui.painter().add(epaint::RectShape { - fill: visuals.bg_fill, - stroke: visuals.fg_stroke, - rect, - rounding: visuals.rounding, - fill_texture_id: Default::default(), - uv: Rect::ZERO, - }); + ui.painter() + .rect(rect, visuals.rounding, visuals.bg_fill, visuals.fg_stroke); } } } diff --git a/crates/epaint/src/shadow.rs b/crates/epaint/src/shadow.rs index 8b73c07e0da8..fc7de267b9aa 100644 --- a/crates/epaint/src/shadow.rs +++ b/crates/epaint/src/shadow.rs @@ -1,5 +1,3 @@ -use emath::NumExt as _; - use super::*; /// The color and fuzziness of a fuzzy shape. @@ -37,11 +35,10 @@ impl Shadow { color: Color32::TRANSPARENT, }; - pub fn tessellate(&self, rect: Rect, rounding: impl Into) -> Mesh { + /// The argument is the rectangle of the shadow caster. + pub fn as_shape(&self, rect: Rect, rounding: impl Into) -> RectShape { // tessellator.clip_rect = clip_rect; // TODO(emilk): culling - use crate::tessellator::*; - let Self { offset, blur, @@ -50,40 +47,9 @@ impl Shadow { } = *self; let rect = rect.translate(offset).expand(spread); + let rounding = rounding.into() + Rounding::same(spread.abs()); - // We simulate a blurry shadow by tessellating a solid rectangle using a very large feathering. - // Feathering is usually used to make the edges of a shape softer for anti-aliasing. - // The tessellator can't handle blurring/feathering larger than the smallest side of the rect. - // Thats because the tessellator approximate very thin rectangles as line segments, - // and these line segments don't have rounded corners. - // When the feathering is small (the size of a pixel), this is usually fine, - // but here we have a huge feathering to simulate blur, - // so we need to avoid this optimization in the tessellator, - // which is also why we add this rather big epsilon: - let eps = 0.1; - let blur = blur.at_most(rect.size().min_elem() - eps).at_least(0.0); - - // TODO(emilk): if blur <= 0, return a simple `Shape::Rect` instead of using the tessellator - - let rounding_expansion = spread.abs() + 0.5 * blur; - let rounding = rounding.into() + Rounding::same(rounding_expansion); - - let rect = RectShape::filled(rect, rounding, color); - let pixels_per_point = 1.0; // doesn't matter here - let font_tex_size = [1; 2]; // unused since we are not tessellating text. - let mut tessellator = Tessellator::new( - pixels_per_point, - TessellationOptions { - feathering: true, - feathering_size_in_pixels: blur * pixels_per_point, - ..Default::default() - }, - font_tex_size, - vec![], - ); - let mut mesh = Mesh::default(); - tessellator.tessellate_rect(&rect, &mut mesh); - mesh + RectShape::filled(rect, rounding, color).with_blur_width(blur) } /// How much larger than the parent rect are we in each direction? diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 3c6f1703d172..7922a92d6d55 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -668,6 +668,14 @@ pub struct RectShape { /// The thickness and color of the outline. pub stroke: Stroke, + /// If larger than zero, the edges of the rectangle + /// (for both fill and stroke) will be blurred. + /// + /// This can be used to produce shadows and glow effects. + /// + /// The blur is currently implemented using a simple linear blur in sRGBA gamma space. + pub blur_width: f32, + /// If the rect should be filled with a texture, which one? /// /// The texture is multiplied with [`Self::fill`]. @@ -695,6 +703,7 @@ impl RectShape { rounding: rounding.into(), fill: fill_color.into(), stroke: stroke.into(), + blur_width: 0.0, fill_texture_id: Default::default(), uv: Rect::ZERO, } @@ -711,6 +720,7 @@ impl RectShape { rounding: rounding.into(), fill: fill_color.into(), stroke: Default::default(), + blur_width: 0.0, fill_texture_id: Default::default(), uv: Rect::ZERO, } @@ -723,18 +733,32 @@ impl RectShape { rounding: rounding.into(), fill: Default::default(), stroke: stroke.into(), + blur_width: 0.0, fill_texture_id: Default::default(), uv: Rect::ZERO, } } + /// If larger than zero, the edges of the rectangle + /// (for both fill and stroke) will be blurred. + /// + /// This can be used to produce shadows and glow effects. + /// + /// The blur is currently implemented using a simple linear blur in `sRGBA` gamma space. + #[inline] + pub fn with_blur_width(mut self, blur_width: f32) -> Self { + self.blur_width = blur_width; + self + } + /// The visual bounding rectangle (includes stroke width) #[inline] pub fn visual_bounding_rect(&self) -> Rect { if self.fill == Color32::TRANSPARENT && self.stroke.is_empty() { Rect::NOTHING } else { - self.rect.expand(self.stroke.width / 2.0) + self.rect + .expand((self.stroke.width + self.blur_width) / 2.0) } } } diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index 7f4d335845f2..8ff65d2a0454 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -37,6 +37,7 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { rounding: _, fill, stroke, + blur_width: _, fill_texture_id: _, uv: _, }) diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index f626b9f3094d..f5b3d9e3cb14 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1503,9 +1503,10 @@ impl Tessellator { pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) { let RectShape { mut rect, - rounding, + mut rounding, fill, stroke, + mut blur_width, fill_texture_id, uv, } = *rect; @@ -1524,6 +1525,29 @@ impl Tessellator { rect.min = rect.min.at_least(pos2(-1e7, -1e7)); rect.max = rect.max.at_most(pos2(1e7, 1e7)); + let old_feathering = self.feathering; + + if old_feathering < blur_width { + // We accomplish the blur by using a larger-than-normal feathering. + // Feathering is usually used to make the edges of a shape softer for anti-aliasing. + + // The tessellator can't handle blurring/feathering larger than the smallest side of the rect. + // Thats because the tessellator approximate very thin rectangles as line segments, + // and these line segments don't have rounded corners. + // When the feathering is small (the size of a pixel), this is usually fine, + // but here we have a huge feathering to simulate blur, + // so we need to avoid this optimization in the tessellator, + // which is also why we add this rather big epsilon: + let eps = 0.1; + blur_width = blur_width + .at_most(rect.size().min_elem() - eps) + .at_least(0.0); + + rounding += Rounding::same(0.5 * blur_width); + + self.feathering = self.feathering.max(blur_width); + } + if rect.width() < self.feathering { // Very thin - approximate by a vertical line-segment: let line = [rect.center_top(), rect.center_bottom()]; @@ -1566,6 +1590,8 @@ impl Tessellator { path.stroke_closed(self.feathering, stroke, out); } + + self.feathering = old_feathering; // restore } /// Tessellate a single [`TextShape`] into a [`Mesh`]. From 810135c5eb0189759a9d58d5e60590271634987a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 10:15:54 +0100 Subject: [PATCH 0018/1202] Fix incorrect `Response::interact_rect` for `Area/Window` (#4273) It was wrong for any `Area` or `Window` that was being moved or whose position was constrained --- crates/egui/src/containers/area.rs | 3 ++- crates/egui/src/response.rs | 15 +++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 5cd0f01dfce1..857569da3ac2 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -355,7 +355,8 @@ impl Area { state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos())); // Update responsbe with posisbly moved/constrained rect: - move_response = move_response.with_new_rect(state.rect()); + move_response.rect = state.rect(); + move_response.interact_rect = state.rect(); Prepared { layer_id, diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 32c07ed7b1a8..51a53121e1b7 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -38,9 +38,6 @@ pub struct Response { /// /// This is sometimes smaller than [`Self::rect`] because of clipping /// (e.g. when inside a scroll area). - /// - /// The interact rect may also be slightly larger than the widget rect, - /// because egui adds half if the item spacing to make the interact rect easier to hit. pub interact_rect: Rect, /// The senses (click and/or drag) that the widget was interested in (if any). @@ -59,7 +56,7 @@ pub struct Response { pub enabled: bool, // OUT: - /// The pointer is hovering above this widget. + /// The pointer is above this widget with no other blocking it. #[doc(hidden)] pub contains_pointer: bool, @@ -200,7 +197,10 @@ impl Response { self.clicked && self.ctx.input(|i| i.pointer.button_triple_clicked(button)) } - /// `true` if there was a click *outside* this widget this frame. + /// `true` if there was a click *outside* the rect of this widget. + /// + /// Clicks on widgets contained in this one counts as clicks inside this widget, + /// so that clicking a button in an area will not be considered as clicking "elsewhere" from the area. pub fn clicked_elsewhere(&self) -> bool { // We do not use self.clicked(), because we want to catch all clicks within our frame, // even if we aren't clickable (or even enabled). @@ -243,14 +243,13 @@ impl Response { self.hovered } - /// Returns true if the pointer is contained by the response rect. + /// Returns true if the pointer is contained by the response rect, and no other widget is covering it. /// /// In contrast to [`Self::hovered`], this can be `true` even if some other widget is being dragged. /// This means it is useful for styling things like drag-and-drop targets. /// `contains_pointer` can also be `true` for disabled widgets. /// - /// This is slightly different from [`Ui::rect_contains_pointer`] and [`Context::rect_contains_pointer`], - /// The rectangle used here is slightly larger, by half of the current item spacing. + /// This is slightly different from [`Ui::rect_contains_pointer`] and [`Context::rect_contains_pointer`], in that /// [`Self::contains_pointer`] also checks that no other widget is covering this response rectangle. #[inline(always)] pub fn contains_pointer(&self) -> bool { From a7c5eb47a8a34195121f6a68de18751da0e1a9a3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 10:22:58 +0100 Subject: [PATCH 0019/1202] Fix bug in determining wether to remove focus from a widget (#4272) * Closes https://github.com/emilk/egui/issues/4206 --- crates/egui/src/context.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 222f8ab0575e..74a71da21419 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1113,8 +1113,6 @@ impl Context { changed: false, }; - let clicked_elsewhere = res.clicked_elsewhere(); - self.write(|ctx| { let viewport = ctx.viewports.entry(ctx.viewport_id()).or_default(); @@ -1157,15 +1155,22 @@ impl Context { } let clicked = Some(id) == viewport.interact_widgets.clicked; + let mut any_press = false; for pointer_event in &input.pointer.pointer_events { - if let PointerEvent::Released { click, .. } = pointer_event { - if enabled && sense.click && clicked && click.is_some() { - res.clicked = true; + match pointer_event { + PointerEvent::Moved(_) => {} + PointerEvent::Pressed { .. } => { + any_press = true; } + PointerEvent::Released { click, .. } => { + if enabled && sense.click && clicked && click.is_some() { + res.clicked = true; + } - res.is_pointer_button_down_on = false; - res.dragged = false; + res.is_pointer_button_down_on = false; + res.dragged = false; + } } } @@ -1188,14 +1193,10 @@ impl Context { res.hovered = false; } - if clicked_elsewhere && memory.has_focus(id) { + let pointer_pressed_elsewhere = any_press && !res.hovered; + if pointer_pressed_elsewhere && memory.has_focus(id) { memory.surrender_focus(id); } - - if res.dragged() && !memory.has_focus(id) { - // e.g.: remove focus from a widget when you drag something else - memory.stop_text_input(); - } }); res From 8da0e8cc770cfd89e57009be0c85cbbb19b3975d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 10:35:55 +0100 Subject: [PATCH 0020/1202] Fix: `Response::clicked_elsewhere` takes clip rect into account (#4274) `clicked_elsewhere` now checks against the clipped `interact_rect` of the widget instead of the full `rect`. In practice this shouldn't change much since the function is mostly used for windows and areas, which aren't clipped. --- crates/egui/src/response.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 51a53121e1b7..6946e157f5a9 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -209,14 +209,10 @@ impl Response { let pointer = &i.pointer; if pointer.any_click() { - // We detect clicks/hover on a "interact_rect" that is slightly larger than - // self.rect. See Context::interact. - // This means we can be hovered and clicked even though `!self.rect.contains(pos)` is true, - // hence the extra complexity here. - if self.contains_pointer() { + if self.contains_pointer || self.hovered { false } else if let Some(pos) = pointer.interact_pos() { - !self.rect.contains(pos) + !self.interact_rect.contains(pos) } else { false // clicked without a pointer, weird } From fbb4a040ac94fc83e64d75bbe6e4c61327d26787 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 10:54:32 +0100 Subject: [PATCH 0021/1202] Change the resize cursor when you reach the resize limit (#4275) For panels and `DragValue`: if you cannot resize more in one direction, reflect that in the choice of mouse cursor ![resize-cursor-2](https://github.com/emilk/egui/assets/1148717/f95176d3-eab9-48cf-b7bd-3182312551d9) --- crates/egui/src/containers/panel.rs | 40 ++++++++++++++++++--------- crates/egui/src/containers/window.rs | 1 + crates/egui/src/widgets/drag_value.rs | 12 ++++++-- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 11e69b8289cb..27be59996d42 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -228,8 +228,8 @@ impl SidePanel { let available_rect = ui.available_rect_before_wrap(); let mut panel_rect = available_rect; + let mut width = default_width; { - let mut width = default_width; if let Some(state) = PanelState::load(ui.ctx(), id) { width = state.rect.width(); } @@ -249,9 +249,8 @@ impl SidePanel { if is_resizing { if let Some(pointer) = resize_response.interact_pointer_pos() { - let width = (pointer.x - side.side_x(panel_rect)).abs(); - let width = - clamp_to_range(width, width_range).at_most(available_rect.width()); + width = (pointer.x - side.side_x(panel_rect)).abs(); + width = clamp_to_range(width, width_range).at_most(available_rect.width()); side.set_rect_width(&mut panel_rect, width); } } @@ -296,7 +295,14 @@ impl SidePanel { } if resize_hover || is_resizing { - ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal); + let cursor_icon = if width <= width_range.min { + CursorIcon::ResizeEast + } else if width < width_range.max { + CursorIcon::ResizeHorizontal + } else { + CursorIcon::ResizeWest + }; + ui.ctx().set_cursor_icon(cursor_icon); } PanelState { rect }.store(ui.ctx(), id); @@ -684,12 +690,13 @@ impl TopBottomPanel { let available_rect = ui.available_rect_before_wrap(); let mut panel_rect = available_rect; + + let mut height = if let Some(state) = PanelState::load(ui.ctx(), id) { + state.rect.height() + } else { + default_height.unwrap_or_else(|| ui.style().spacing.interact_size.y) + }; { - let mut height = if let Some(state) = PanelState::load(ui.ctx(), id) { - state.rect.height() - } else { - default_height.unwrap_or_else(|| ui.style().spacing.interact_size.y) - }; height = clamp_to_range(height, height_range).at_most(available_rect.height()); side.set_rect_height(&mut panel_rect, height); ui.ctx() @@ -707,8 +714,8 @@ impl TopBottomPanel { if is_resizing { if let Some(pointer) = resize_response.interact_pointer_pos() { - let height = (pointer.y - side.side_y(panel_rect)).abs(); - let height = + height = (pointer.y - side.side_y(panel_rect)).abs(); + height = clamp_to_range(height, height_range).at_most(available_rect.height()); side.set_rect_height(&mut panel_rect, height); } @@ -755,7 +762,14 @@ impl TopBottomPanel { } if resize_hover || is_resizing { - ui.ctx().set_cursor_icon(CursorIcon::ResizeVertical); + let cursor_icon = if height <= height_range.min { + CursorIcon::ResizeSouth + } else if height < height_range.max { + CursorIcon::ResizeVertical + } else { + CursorIcon::ResizeNorth + }; + ui.ctx().set_cursor_icon(cursor_icon); } PanelState { rect }.store(ui.ctx(), id); diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index af6f6b16c312..619675ca8a55 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -697,6 +697,7 @@ impl ResizeInteraction { let top = self.top.any(); let bottom = self.bottom.any(); + // TODO(emilk): use one-sided cursors for when we reached the min/max size. if (left && top) || (right && bottom) { ctx.set_cursor_icon(CursorIcon::ResizeNwSe); } else if (right && top) || (left && bottom) { diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index f0875ba30553..8a7f19028b66 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -525,8 +525,16 @@ impl<'a> Widget for DragValue<'a> { .sense(Sense::click_and_drag()) .min_size(ui.spacing().interact_size); // TODO(emilk): find some more generic solution to `min_size` + let cursor_icon = if value <= *clamp_range.start() { + CursorIcon::ResizeEast + } else if value < *clamp_range.end() { + CursorIcon::ResizeHorizontal + } else { + CursorIcon::ResizeWest + }; + let response = ui.add(button); - let mut response = response.on_hover_cursor(CursorIcon::ResizeHorizontal); + let mut response = response.on_hover_cursor(cursor_icon); if ui.style().explanation_tooltips { response = response.on_hover_text(format!( @@ -552,7 +560,7 @@ impl<'a> Widget for DragValue<'a> { ))); state.store(ui.ctx(), response.id); } else if response.dragged() { - ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal); + ui.ctx().set_cursor_icon(cursor_icon); let mdelta = response.drag_delta(); let delta_points = mdelta.x - mdelta.y; // Increase to the right and up From 32888e0f83aa3ecf287f8052db43d8ed38f3f5b6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 13:33:51 +0100 Subject: [PATCH 0022/1202] Make `TextEdit` and atomic widget (#4276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This means only one space allocation, which should allow putting it in more types of layouts (right-to-left, centered, adjusted, …). It also just makes the code simpler --- crates/egui/src/widgets/text_edit/builder.rs | 56 ++++++++------------ 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 799774096ffa..a8f60d1a5df0 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -389,29 +389,20 @@ impl<'t> TextEdit<'t> { pub fn show(self, ui: &mut Ui) -> TextEditOutput { let is_mutable = self.text.is_mutable(); let frame = self.frame; - let interactive = self.interactive; let where_to_put_background = ui.painter().add(Shape::Noop); let margin = self.margin; - let available = ui.available_rect_before_wrap(); - let max_rect = margin.shrink_rect(available); - let mut content_ui = ui.child_ui(max_rect, *ui.layout()); + let mut output = self.show_content(ui); - let mut output = self.show_content(&mut content_ui); - - let id = output.response.id; - let frame_rect = margin.expand_rect(output.response.rect); - ui.allocate_space(frame_rect.size()); - if interactive { - output.response |= ui.interact(frame_rect, id, Sense::click()); - } - if output.response.clicked() && !output.response.lost_focus() { - ui.memory_mut(|mem| mem.request_focus(output.response.id)); - } + // TODO(emilk): return full outer_rect in `TextEditOutput`. + // Can't do it now because this fix is ging into a patch release. + let outer_rect = output.response.rect; + let inner_rect = margin.shrink_rect(outer_rect); + output.response.rect = inner_rect; if frame { let visuals = ui.style().interact(&output.response); - let frame_rect = frame_rect.expand(visuals.expansion); + let frame_rect = outer_rect.expand(visuals.expansion); let shape = if is_mutable { if output.response.has_focus() { epaint::RectShape::new( @@ -478,7 +469,7 @@ impl<'t> TextEdit<'t> { let font_id = font_selection.resolve(ui.style()); let row_height = ui.fonts(|f| f.row_height(&font_id)); const MIN_WIDTH: f32 = 24.0; // Never make a [`TextEdit`] more narrow than this. - let available_width = ui.available_width().at_least(MIN_WIDTH); + let available_width = (ui.available_width() - margin.sum().x).at_least(MIN_WIDTH); let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width); let wrap_width = if ui.layout().horizontal_justify() { available_width @@ -507,11 +498,10 @@ impl<'t> TextEdit<'t> { galley.size().x.max(wrap_width) }; let desired_height = (desired_height_rows.at_least(1) as f32) * row_height; - let at_least = min_size - margin.sum(); - let desired_size = - vec2(desired_width, galley.size().y.max(desired_height)).at_least(at_least); - - let (auto_id, rect) = ui.allocate_space(desired_size); + let desired_inner_size = vec2(desired_width, galley.size().y.max(desired_height)); + let desired_outer_size = (desired_inner_size + margin.sum()).at_least(min_size); + let (auto_id, outer_rect) = ui.allocate_space(desired_outer_size); + let rect = margin.shrink_rect(outer_rect); // inner rect (excluding frame/margin). let id = id.unwrap_or_else(|| { if let Some(id_source) = id_source { @@ -538,7 +528,7 @@ impl<'t> TextEdit<'t> { } else { Sense::hover() }; - let mut response = ui.interact(rect, id, sense); + let mut response = ui.interact(outer_rect, id, sense); let text_clip_rect = rect; let painter = ui.painter_at(text_clip_rect.expand(1.0)); // expand to avoid clipping cursor @@ -552,7 +542,7 @@ impl<'t> TextEdit<'t> { let singleline_offset = vec2(state.singleline_offset, 0.0); let cursor_at_pointer = - galley.cursor_from_pos(pointer_pos - response.rect.min + singleline_offset); + galley.cursor_from_pos(pointer_pos - rect.min + singleline_offset); if ui.visuals().text_cursor_preview && response.hovered() @@ -560,7 +550,7 @@ impl<'t> TextEdit<'t> { { // preview: let cursor_rect = - cursor_rect(response.rect.min, &galley, &cursor_at_pointer, row_height); + cursor_rect(rect.min, &galley, &cursor_at_pointer, row_height); paint_cursor(&painter, ui.visuals(), cursor_rect); } @@ -617,10 +607,10 @@ impl<'t> TextEdit<'t> { } let mut galley_pos = align - .align_size_within_rect(galley.size(), response.rect) - .intersect(response.rect) // limit pos to the response rect area + .align_size_within_rect(galley.size(), rect) + .intersect(rect) // limit pos to the response rect area .min; - let align_offset = response.rect.left() - galley_pos.x; + let align_offset = rect.left() - galley_pos.x; // Visual clipping for singleline text editor with text larger than width if clip_text && align_offset == 0.0 { @@ -630,18 +620,18 @@ impl<'t> TextEdit<'t> { }; let mut offset_x = state.singleline_offset; - let visible_range = offset_x..=offset_x + desired_size.x; + let visible_range = offset_x..=offset_x + desired_inner_size.x; if !visible_range.contains(&cursor_pos) { if cursor_pos < *visible_range.start() { offset_x = cursor_pos; } else { - offset_x = cursor_pos - desired_size.x; + offset_x = cursor_pos - desired_inner_size.x; } } offset_x = offset_x - .at_most(galley.size().x - desired_size.x) + .at_most(galley.size().x - desired_inner_size.x) .at_least(0.0); state.singleline_offset = offset_x; @@ -664,11 +654,11 @@ impl<'t> TextEdit<'t> { if text.as_str().is_empty() && !hint_text.is_empty() { let hint_text_color = ui.visuals().weak_text_color(); let galley = if multiline { - hint_text.into_galley(ui, Some(true), desired_size.x, font_id) + hint_text.into_galley(ui, Some(true), desired_inner_size.x, font_id) } else { hint_text.into_galley(ui, Some(false), f32::INFINITY, font_id) }; - painter.galley(response.rect.min, galley, hint_text_color); + painter.galley(rect.min, galley, hint_text_color); } if ui.memory(|mem| mem.has_focus(id)) { From a9a756e8f3e827c5f2d53d35029d580ef05e286c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 14:03:41 +0100 Subject: [PATCH 0023/1202] Overload operators for `Rect + Margin`, `Rect - Margin` etc (#4277) It's more ergonomic --- crates/egui/src/containers/frame.rs | 9 +- crates/egui/src/widgets/text_edit/builder.rs | 4 +- crates/epaint/src/margin.rs | 108 ++++++++++++++----- 3 files changed, 89 insertions(+), 32 deletions(-) diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 6d7ba6e6274b..b982cfd0b030 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -240,7 +240,7 @@ impl Frame { let where_to_put_background = ui.painter().add(Shape::Noop); let outer_rect_bounds = ui.available_rect_before_wrap(); - let mut inner_rect = (self.inner_margin + self.outer_margin).shrink_rect(outer_rect_bounds); + let mut inner_rect = outer_rect_bounds - self.outer_margin - self.inner_margin; // Make sure we don't shrink to the negative: inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x); @@ -299,7 +299,7 @@ impl Frame { impl Prepared { fn content_with_margin(&self) -> Rect { - (self.frame.inner_margin + self.frame.outer_margin).expand_rect(self.content_ui.min_rect()) + self.content_ui.min_rect() + self.frame.inner_margin + self.frame.outer_margin } /// Allocate the the space that was used by [`Self::content_ui`]. @@ -315,10 +315,7 @@ impl Prepared { /// /// This can be called before or after [`Self::allocate_space`]. pub fn paint(&self, ui: &Ui) { - let paint_rect = self - .frame - .inner_margin - .expand_rect(self.content_ui.min_rect()); + let paint_rect = self.content_ui.min_rect() + self.frame.inner_margin; if ui.is_rect_visible(paint_rect) { let shape = self.frame.paint(paint_rect); diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index a8f60d1a5df0..e320a1ae8439 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -397,7 +397,7 @@ impl<'t> TextEdit<'t> { // TODO(emilk): return full outer_rect in `TextEditOutput`. // Can't do it now because this fix is ging into a patch release. let outer_rect = output.response.rect; - let inner_rect = margin.shrink_rect(outer_rect); + let inner_rect = outer_rect - margin; output.response.rect = inner_rect; if frame { @@ -501,7 +501,7 @@ impl<'t> TextEdit<'t> { let desired_inner_size = vec2(desired_width, galley.size().y.max(desired_height)); let desired_outer_size = (desired_inner_size + margin.sum()).at_least(min_size); let (auto_id, outer_rect) = ui.allocate_space(desired_outer_size); - let rect = margin.shrink_rect(outer_rect); // inner rect (excluding frame/margin). + let rect = outer_rect - margin; // inner rect (excluding frame/margin). let id = id.unwrap_or_else(|| { if let Some(id_source) = id_source { diff --git a/crates/epaint/src/margin.rs b/crates/epaint/src/margin.rs index 815ff8351636..e2ade58f9c25 100644 --- a/crates/epaint/src/margin.rs +++ b/crates/epaint/src/margin.rs @@ -2,6 +2,8 @@ use emath::{vec2, Rect, Vec2}; /// A value for all four sides of a rectangle, /// often used to express padding or spacing. +/// +/// Can be added and subtracted to/from [`Rect`]s. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Margin { @@ -19,6 +21,8 @@ impl Margin { bottom: 0.0, }; + /// The same margin on every side. + #[doc(alias = "symmetric")] #[inline] pub const fn same(margin: f32) -> Self { Self { @@ -56,16 +60,20 @@ impl Margin { vec2(self.right, self.bottom) } + /// Are the margin on every side the same? + #[doc(alias = "symmetric")] #[inline] pub fn is_same(&self) -> bool { self.left == self.right && self.left == self.top && self.left == self.bottom } + #[deprecated = "Use `rect + margin` instead"] #[inline] pub fn expand_rect(&self, rect: Rect) -> Rect { Rect::from_min_max(rect.min - self.left_top(), rect.max + self.right_bottom()) } + #[deprecated = "Use `rect - margin` instead"] #[inline] pub fn shrink_rect(&self, rect: Rect) -> Rect { Rect::from_min_max(rect.min + self.left_top(), rect.max - self.right_bottom()) @@ -86,6 +94,7 @@ impl From for Margin { } } +/// `Margin + Margin` impl std::ops::Add for Margin { type Output = Self; @@ -100,6 +109,7 @@ impl std::ops::Add for Margin { } } +/// `Margin + f32` impl std::ops::Add for Margin { type Output = Self; @@ -114,6 +124,7 @@ impl std::ops::Add for Margin { } } +/// `Margind += f32` impl std::ops::AddAssign for Margin { #[inline] fn add_assign(&mut self, v: f32) { @@ -124,30 +135,7 @@ impl std::ops::AddAssign for Margin { } } -impl std::ops::Div for Margin { - type Output = Self; - - #[inline] - fn div(self, v: f32) -> Self { - Self { - left: self.left / v, - right: self.right / v, - top: self.top / v, - bottom: self.bottom / v, - } - } -} - -impl std::ops::DivAssign for Margin { - #[inline] - fn div_assign(&mut self, v: f32) { - self.left /= v; - self.right /= v; - self.top /= v; - self.bottom /= v; - } -} - +/// `Margin * f32` impl std::ops::Mul for Margin { type Output = Self; @@ -162,6 +150,7 @@ impl std::ops::Mul for Margin { } } +/// `Margin *= f32` impl std::ops::MulAssign for Margin { #[inline] fn mul_assign(&mut self, v: f32) { @@ -172,6 +161,33 @@ impl std::ops::MulAssign for Margin { } } +/// `Margin / f32` +impl std::ops::Div for Margin { + type Output = Self; + + #[inline] + fn div(self, v: f32) -> Self { + Self { + left: self.left / v, + right: self.right / v, + top: self.top / v, + bottom: self.bottom / v, + } + } +} + +/// `Margin /= f32` +impl std::ops::DivAssign for Margin { + #[inline] + fn div_assign(&mut self, v: f32) { + self.left /= v; + self.right /= v; + self.top /= v; + self.bottom /= v; + } +} + +/// `Margin - Margin` impl std::ops::Sub for Margin { type Output = Self; @@ -186,6 +202,7 @@ impl std::ops::Sub for Margin { } } +/// `Margin - f32` impl std::ops::Sub for Margin { type Output = Self; @@ -200,6 +217,7 @@ impl std::ops::Sub for Margin { } } +/// `Margin -= f32` impl std::ops::SubAssign for Margin { #[inline] fn sub_assign(&mut self, v: f32) { @@ -209,3 +227,45 @@ impl std::ops::SubAssign for Margin { self.bottom -= v; } } + +/// `Rect + Margin` +impl std::ops::Add for Rect { + type Output = Self; + + #[inline] + fn add(self, margin: Margin) -> Self { + Self::from_min_max( + self.min - margin.left_top(), + self.max + margin.right_bottom(), + ) + } +} + +/// `Rect += Margin` +impl std::ops::AddAssign for Rect { + #[inline] + fn add_assign(&mut self, margin: Margin) { + *self = *self + margin; + } +} + +/// `Rect - Margin` +impl std::ops::Sub for Rect { + type Output = Self; + + #[inline] + fn sub(self, margin: Margin) -> Self { + Self::from_min_max( + self.min + margin.left_top(), + self.max - margin.right_bottom(), + ) + } +} + +/// `Rect -= Margin` +impl std::ops::SubAssign for Rect { + #[inline] + fn sub_assign(&mut self, margin: Margin) { + *self = *self - margin; + } +} From 727732298326988c033d2a1be7ae93161a84a4cc Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 14:28:12 +0100 Subject: [PATCH 0024/1202] Break out Checkbox, RadioButton and ImageButton to their own files (#4278) Just a refactor --- crates/egui/src/widgets/button.rs | 376 ------------------------ crates/egui/src/widgets/checkbox.rs | 136 +++++++++ crates/egui/src/widgets/image_button.rs | 130 ++++++++ crates/egui/src/widgets/mod.rs | 30 +- crates/egui/src/widgets/radio_button.rs | 107 +++++++ 5 files changed, 392 insertions(+), 387 deletions(-) create mode 100644 crates/egui/src/widgets/checkbox.rs create mode 100644 crates/egui/src/widgets/image_button.rs create mode 100644 crates/egui/src/widgets/radio_button.rs diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 75da2a9c716a..76a621250455 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -336,379 +336,3 @@ impl Widget for Button<'_> { response } } - -// ---------------------------------------------------------------------------- - -// TODO(emilk): allow checkbox without a text label -/// Boolean on/off control with text label. -/// -/// Usually you'd use [`Ui::checkbox`] instead. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// # let mut my_bool = true; -/// // These are equivalent: -/// ui.checkbox(&mut my_bool, "Checked"); -/// ui.add(egui::Checkbox::new(&mut my_bool, "Checked")); -/// # }); -/// ``` -#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] -pub struct Checkbox<'a> { - checked: &'a mut bool, - text: WidgetText, - indeterminate: bool, -} - -impl<'a> Checkbox<'a> { - pub fn new(checked: &'a mut bool, text: impl Into) -> Self { - Checkbox { - checked, - text: text.into(), - indeterminate: false, - } - } - - pub fn without_text(checked: &'a mut bool) -> Self { - Self::new(checked, WidgetText::default()) - } - - /// Display an indeterminate state (neither checked nor unchecked) - /// - /// This only affects the checkbox's appearance. It will still toggle its boolean value when - /// clicked. - #[inline] - pub fn indeterminate(mut self, indeterminate: bool) -> Self { - self.indeterminate = indeterminate; - self - } -} - -impl<'a> Widget for Checkbox<'a> { - fn ui(self, ui: &mut Ui) -> Response { - let Checkbox { - checked, - text, - indeterminate, - } = self; - - let spacing = &ui.spacing(); - let icon_width = spacing.icon_width; - let icon_spacing = spacing.icon_spacing; - - let (galley, mut desired_size) = if text.is_empty() { - (None, vec2(icon_width, 0.0)) - } else { - let total_extra = vec2(icon_width + icon_spacing, 0.0); - - let wrap_width = ui.available_width() - total_extra.x; - let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button); - - let mut desired_size = total_extra + galley.size(); - desired_size = desired_size.at_least(spacing.interact_size); - - (Some(galley), desired_size) - }; - - desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); - desired_size.y = desired_size.y.max(icon_width); - let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click()); - - if response.clicked() { - *checked = !*checked; - response.mark_changed(); - } - response.widget_info(|| { - if indeterminate { - WidgetInfo::labeled( - WidgetType::Checkbox, - galley.as_ref().map_or("", |x| x.text()), - ) - } else { - WidgetInfo::selected( - WidgetType::Checkbox, - *checked, - galley.as_ref().map_or("", |x| x.text()), - ) - } - }); - - if ui.is_rect_visible(rect) { - // let visuals = ui.style().interact_selectable(&response, *checked); // too colorful - let visuals = ui.style().interact(&response); - let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); - ui.painter().add(epaint::RectShape::new( - big_icon_rect.expand(visuals.expansion), - visuals.rounding, - visuals.bg_fill, - visuals.bg_stroke, - )); - - if indeterminate { - // Horizontal line: - ui.painter().add(Shape::hline( - small_icon_rect.x_range(), - small_icon_rect.center().y, - visuals.fg_stroke, - )); - } else if *checked { - // Check mark: - ui.painter().add(Shape::line( - vec![ - pos2(small_icon_rect.left(), small_icon_rect.center().y), - pos2(small_icon_rect.center().x, small_icon_rect.bottom()), - pos2(small_icon_rect.right(), small_icon_rect.top()), - ], - visuals.fg_stroke, - )); - } - if let Some(galley) = galley { - let text_pos = pos2( - rect.min.x + icon_width + icon_spacing, - rect.center().y - 0.5 * galley.size().y, - ); - ui.painter().galley(text_pos, galley, visuals.text_color()); - } - } - - response - } -} - -// ---------------------------------------------------------------------------- - -/// One out of several alternatives, either selected or not. -/// -/// Usually you'd use [`Ui::radio_value`] or [`Ui::radio`] instead. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// #[derive(PartialEq)] -/// enum Enum { First, Second, Third } -/// let mut my_enum = Enum::First; -/// -/// ui.radio_value(&mut my_enum, Enum::First, "First"); -/// -/// // is equivalent to: -/// -/// if ui.add(egui::RadioButton::new(my_enum == Enum::First, "First")).clicked() { -/// my_enum = Enum::First -/// } -/// # }); -/// ``` -#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] -pub struct RadioButton { - checked: bool, - text: WidgetText, -} - -impl RadioButton { - pub fn new(checked: bool, text: impl Into) -> Self { - Self { - checked, - text: text.into(), - } - } -} - -impl Widget for RadioButton { - fn ui(self, ui: &mut Ui) -> Response { - let Self { checked, text } = self; - - let spacing = &ui.spacing(); - let icon_width = spacing.icon_width; - let icon_spacing = spacing.icon_spacing; - - let (galley, mut desired_size) = if text.is_empty() { - (None, vec2(icon_width, 0.0)) - } else { - let total_extra = vec2(icon_width + icon_spacing, 0.0); - - let wrap_width = ui.available_width() - total_extra.x; - let text = text.into_galley(ui, None, wrap_width, TextStyle::Button); - - let mut desired_size = total_extra + text.size(); - desired_size = desired_size.at_least(spacing.interact_size); - - (Some(text), desired_size) - }; - - desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); - desired_size.y = desired_size.y.max(icon_width); - let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click()); - - response.widget_info(|| { - WidgetInfo::selected( - WidgetType::RadioButton, - checked, - galley.as_ref().map_or("", |x| x.text()), - ) - }); - - if ui.is_rect_visible(rect) { - // let visuals = ui.style().interact_selectable(&response, checked); // too colorful - let visuals = ui.style().interact(&response); - - let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); - - let painter = ui.painter(); - - painter.add(epaint::CircleShape { - center: big_icon_rect.center(), - radius: big_icon_rect.width() / 2.0 + visuals.expansion, - fill: visuals.bg_fill, - stroke: visuals.bg_stroke, - }); - - if checked { - painter.add(epaint::CircleShape { - center: small_icon_rect.center(), - radius: small_icon_rect.width() / 3.0, - fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill - // fill: ui.visuals().selection.stroke.color, // too much color - stroke: Default::default(), - }); - } - - if let Some(galley) = galley { - let text_pos = pos2( - rect.min.x + icon_width + icon_spacing, - rect.center().y - 0.5 * galley.size().y, - ); - ui.painter().galley(text_pos, galley, visuals.text_color()); - } - } - - response - } -} - -// ---------------------------------------------------------------------------- - -/// A clickable image within a frame. -#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] -#[derive(Clone, Debug)] -pub struct ImageButton<'a> { - image: Image<'a>, - sense: Sense, - frame: bool, - selected: bool, -} - -impl<'a> ImageButton<'a> { - pub fn new(image: impl Into>) -> Self { - Self { - image: image.into(), - sense: Sense::click(), - frame: true, - selected: false, - } - } - - /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right. - #[inline] - pub fn uv(mut self, uv: impl Into) -> Self { - self.image = self.image.uv(uv); - self - } - - /// Multiply image color with this. Default is WHITE (no tint). - #[inline] - pub fn tint(mut self, tint: impl Into) -> Self { - self.image = self.image.tint(tint); - self - } - - /// If `true`, mark this button as "selected". - #[inline] - pub fn selected(mut self, selected: bool) -> Self { - self.selected = selected; - self - } - - /// Turn off the frame - #[inline] - pub fn frame(mut self, frame: bool) -> Self { - self.frame = frame; - self - } - - /// By default, buttons senses clicks. - /// Change this to a drag-button with `Sense::drag()`. - #[inline] - pub fn sense(mut self, sense: Sense) -> Self { - self.sense = sense; - self - } - - /// Set rounding for the `ImageButton`. - /// If the underlying image already has rounding, this - /// will override that value. - #[inline] - pub fn rounding(mut self, rounding: impl Into) -> Self { - self.image = self.image.rounding(rounding.into()); - self - } -} - -impl<'a> Widget for ImageButton<'a> { - fn ui(self, ui: &mut Ui) -> Response { - let padding = if self.frame { - // so we can see that it is a button: - Vec2::splat(ui.spacing().button_padding.x) - } else { - Vec2::ZERO - }; - - let available_size_for_image = ui.available_size() - 2.0 * padding; - let tlr = self.image.load_for_size(ui.ctx(), available_size_for_image); - let original_image_size = tlr.as_ref().ok().and_then(|t| t.size()); - let image_size = self - .image - .calc_size(available_size_for_image, original_image_size); - - let padded_size = image_size + 2.0 * padding; - let (rect, response) = ui.allocate_exact_size(padded_size, self.sense); - response.widget_info(|| WidgetInfo::new(WidgetType::ImageButton)); - - if ui.is_rect_visible(rect) { - let (expansion, rounding, fill, stroke) = if self.selected { - let selection = ui.visuals().selection; - ( - Vec2::ZERO, - self.image.image_options().rounding, - selection.bg_fill, - selection.stroke, - ) - } else if self.frame { - let visuals = ui.style().interact(&response); - let expansion = Vec2::splat(visuals.expansion); - ( - expansion, - self.image.image_options().rounding, - visuals.weak_bg_fill, - visuals.bg_stroke, - ) - } else { - Default::default() - }; - - // Draw frame background (for transparent images): - ui.painter() - .rect_filled(rect.expand2(expansion), rounding, fill); - - let image_rect = ui - .layout() - .align_size_within_rect(image_size, rect.shrink2(padding)); - // let image_rect = image_rect.expand2(expansion); // can make it blurry, so let's not - let image_options = self.image.image_options().clone(); - - widgets::image::paint_texture_load_result(ui, &tlr, image_rect, None, &image_options); - - // Draw frame outline: - ui.painter() - .rect_stroke(rect.expand2(expansion), rounding, stroke); - } - - widgets::image::texture_load_result_response(self.image.source(), &tlr, response) - } -} diff --git a/crates/egui/src/widgets/checkbox.rs b/crates/egui/src/widgets/checkbox.rs new file mode 100644 index 000000000000..e416a6e06811 --- /dev/null +++ b/crates/egui/src/widgets/checkbox.rs @@ -0,0 +1,136 @@ +use crate::*; + +// TODO(emilk): allow checkbox without a text label +/// Boolean on/off control with text label. +/// +/// Usually you'd use [`Ui::checkbox`] instead. +/// +/// ``` +/// # egui::__run_test_ui(|ui| { +/// # let mut my_bool = true; +/// // These are equivalent: +/// ui.checkbox(&mut my_bool, "Checked"); +/// ui.add(egui::Checkbox::new(&mut my_bool, "Checked")); +/// # }); +/// ``` +#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] +pub struct Checkbox<'a> { + checked: &'a mut bool, + text: WidgetText, + indeterminate: bool, +} + +impl<'a> Checkbox<'a> { + pub fn new(checked: &'a mut bool, text: impl Into) -> Self { + Checkbox { + checked, + text: text.into(), + indeterminate: false, + } + } + + pub fn without_text(checked: &'a mut bool) -> Self { + Self::new(checked, WidgetText::default()) + } + + /// Display an indeterminate state (neither checked nor unchecked) + /// + /// This only affects the checkbox's appearance. It will still toggle its boolean value when + /// clicked. + #[inline] + pub fn indeterminate(mut self, indeterminate: bool) -> Self { + self.indeterminate = indeterminate; + self + } +} + +impl<'a> Widget for Checkbox<'a> { + fn ui(self, ui: &mut Ui) -> Response { + let Checkbox { + checked, + text, + indeterminate, + } = self; + + let spacing = &ui.spacing(); + let icon_width = spacing.icon_width; + let icon_spacing = spacing.icon_spacing; + + let (galley, mut desired_size) = if text.is_empty() { + (None, vec2(icon_width, 0.0)) + } else { + let total_extra = vec2(icon_width + icon_spacing, 0.0); + + let wrap_width = ui.available_width() - total_extra.x; + let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button); + + let mut desired_size = total_extra + galley.size(); + desired_size = desired_size.at_least(spacing.interact_size); + + (Some(galley), desired_size) + }; + + desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); + desired_size.y = desired_size.y.max(icon_width); + let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click()); + + if response.clicked() { + *checked = !*checked; + response.mark_changed(); + } + response.widget_info(|| { + if indeterminate { + WidgetInfo::labeled( + WidgetType::Checkbox, + galley.as_ref().map_or("", |x| x.text()), + ) + } else { + WidgetInfo::selected( + WidgetType::Checkbox, + *checked, + galley.as_ref().map_or("", |x| x.text()), + ) + } + }); + + if ui.is_rect_visible(rect) { + // let visuals = ui.style().interact_selectable(&response, *checked); // too colorful + let visuals = ui.style().interact(&response); + let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); + ui.painter().add(epaint::RectShape::new( + big_icon_rect.expand(visuals.expansion), + visuals.rounding, + visuals.bg_fill, + visuals.bg_stroke, + )); + + if indeterminate { + // Horizontal line: + ui.painter().add(Shape::hline( + small_icon_rect.x_range(), + small_icon_rect.center().y, + visuals.fg_stroke, + )); + } else if *checked { + // Check mark: + ui.painter().add(Shape::line( + vec![ + pos2(small_icon_rect.left(), small_icon_rect.center().y), + pos2(small_icon_rect.center().x, small_icon_rect.bottom()), + pos2(small_icon_rect.right(), small_icon_rect.top()), + ], + visuals.fg_stroke, + )); + } + if let Some(galley) = galley { + let text_pos = pos2( + rect.min.x + icon_width + icon_spacing, + rect.center().y - 0.5 * galley.size().y, + ); + ui.painter().galley(text_pos, galley, visuals.text_color()); + } + } + + response + } +} diff --git a/crates/egui/src/widgets/image_button.rs b/crates/egui/src/widgets/image_button.rs new file mode 100644 index 000000000000..65ef30728074 --- /dev/null +++ b/crates/egui/src/widgets/image_button.rs @@ -0,0 +1,130 @@ +use crate::*; + +/// A clickable image within a frame. +#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] +#[derive(Clone, Debug)] +pub struct ImageButton<'a> { + image: Image<'a>, + sense: Sense, + frame: bool, + selected: bool, +} + +impl<'a> ImageButton<'a> { + pub fn new(image: impl Into>) -> Self { + Self { + image: image.into(), + sense: Sense::click(), + frame: true, + selected: false, + } + } + + /// Select UV range. Default is (0,0) in top-left, (1,1) bottom right. + #[inline] + pub fn uv(mut self, uv: impl Into) -> Self { + self.image = self.image.uv(uv); + self + } + + /// Multiply image color with this. Default is WHITE (no tint). + #[inline] + pub fn tint(mut self, tint: impl Into) -> Self { + self.image = self.image.tint(tint); + self + } + + /// If `true`, mark this button as "selected". + #[inline] + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + + /// Turn off the frame + #[inline] + pub fn frame(mut self, frame: bool) -> Self { + self.frame = frame; + self + } + + /// By default, buttons senses clicks. + /// Change this to a drag-button with `Sense::drag()`. + #[inline] + pub fn sense(mut self, sense: Sense) -> Self { + self.sense = sense; + self + } + + /// Set rounding for the `ImageButton`. + /// If the underlying image already has rounding, this + /// will override that value. + #[inline] + pub fn rounding(mut self, rounding: impl Into) -> Self { + self.image = self.image.rounding(rounding.into()); + self + } +} + +impl<'a> Widget for ImageButton<'a> { + fn ui(self, ui: &mut Ui) -> Response { + let padding = if self.frame { + // so we can see that it is a button: + Vec2::splat(ui.spacing().button_padding.x) + } else { + Vec2::ZERO + }; + + let available_size_for_image = ui.available_size() - 2.0 * padding; + let tlr = self.image.load_for_size(ui.ctx(), available_size_for_image); + let original_image_size = tlr.as_ref().ok().and_then(|t| t.size()); + let image_size = self + .image + .calc_size(available_size_for_image, original_image_size); + + let padded_size = image_size + 2.0 * padding; + let (rect, response) = ui.allocate_exact_size(padded_size, self.sense); + response.widget_info(|| WidgetInfo::new(WidgetType::ImageButton)); + + if ui.is_rect_visible(rect) { + let (expansion, rounding, fill, stroke) = if self.selected { + let selection = ui.visuals().selection; + ( + Vec2::ZERO, + self.image.image_options().rounding, + selection.bg_fill, + selection.stroke, + ) + } else if self.frame { + let visuals = ui.style().interact(&response); + let expansion = Vec2::splat(visuals.expansion); + ( + expansion, + self.image.image_options().rounding, + visuals.weak_bg_fill, + visuals.bg_stroke, + ) + } else { + Default::default() + }; + + // Draw frame background (for transparent images): + ui.painter() + .rect_filled(rect.expand2(expansion), rounding, fill); + + let image_rect = ui + .layout() + .align_size_within_rect(image_size, rect.shrink2(padding)); + // let image_rect = image_rect.expand2(expansion); // can make it blurry, so let's not + let image_options = self.image.image_options().clone(); + + widgets::image::paint_texture_load_result(ui, &tlr, image_rect, None, &image_options); + + // Draw frame outline: + ui.painter() + .rect_stroke(rect.expand2(expansion), rounding, stroke); + } + + widgets::image::texture_load_result_response(self.image.source(), &tlr, response) + } +} diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index 6b3fb72e0c58..0ab9273c7a52 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -7,29 +7,37 @@ use crate::*; mod button; +mod checkbox; pub mod color_picker; pub(crate) mod drag_value; mod hyperlink; mod image; +mod image_button; mod label; mod progress_bar; +mod radio_button; mod selected_label; mod separator; mod slider; mod spinner; pub mod text_edit; -pub use button::*; -pub use drag_value::DragValue; -pub use hyperlink::*; -pub use image::{paint_texture_at, Image, ImageFit, ImageOptions, ImageSize, ImageSource}; -pub use label::*; -pub use progress_bar::ProgressBar; -pub use selected_label::SelectableLabel; -pub use separator::Separator; -pub use slider::*; -pub use spinner::*; -pub use text_edit::{TextBuffer, TextEdit}; +pub use self::{ + button::Button, + checkbox::Checkbox, + drag_value::DragValue, + hyperlink::{Hyperlink, Link}, + image::{paint_texture_at, Image, ImageFit, ImageOptions, ImageSize, ImageSource}, + image_button::ImageButton, + label::Label, + progress_bar::ProgressBar, + radio_button::RadioButton, + selected_label::SelectableLabel, + separator::Separator, + slider::{Slider, SliderOrientation}, + spinner::Spinner, + text_edit::{TextBuffer, TextEdit}, +}; // ---------------------------------------------------------------------------- diff --git a/crates/egui/src/widgets/radio_button.rs b/crates/egui/src/widgets/radio_button.rs new file mode 100644 index 000000000000..ccbe785f6ab6 --- /dev/null +++ b/crates/egui/src/widgets/radio_button.rs @@ -0,0 +1,107 @@ +use crate::*; + +/// One out of several alternatives, either selected or not. +/// +/// Usually you'd use [`Ui::radio_value`] or [`Ui::radio`] instead. +/// +/// ``` +/// # egui::__run_test_ui(|ui| { +/// #[derive(PartialEq)] +/// enum Enum { First, Second, Third } +/// let mut my_enum = Enum::First; +/// +/// ui.radio_value(&mut my_enum, Enum::First, "First"); +/// +/// // is equivalent to: +/// +/// if ui.add(egui::RadioButton::new(my_enum == Enum::First, "First")).clicked() { +/// my_enum = Enum::First +/// } +/// # }); +/// ``` +#[must_use = "You should put this widget in an ui with `ui.add(widget);`"] +pub struct RadioButton { + checked: bool, + text: WidgetText, +} + +impl RadioButton { + pub fn new(checked: bool, text: impl Into) -> Self { + Self { + checked, + text: text.into(), + } + } +} + +impl Widget for RadioButton { + fn ui(self, ui: &mut Ui) -> Response { + let Self { checked, text } = self; + + let spacing = &ui.spacing(); + let icon_width = spacing.icon_width; + let icon_spacing = spacing.icon_spacing; + + let (galley, mut desired_size) = if text.is_empty() { + (None, vec2(icon_width, 0.0)) + } else { + let total_extra = vec2(icon_width + icon_spacing, 0.0); + + let wrap_width = ui.available_width() - total_extra.x; + let text = text.into_galley(ui, None, wrap_width, TextStyle::Button); + + let mut desired_size = total_extra + text.size(); + desired_size = desired_size.at_least(spacing.interact_size); + + (Some(text), desired_size) + }; + + desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); + desired_size.y = desired_size.y.max(icon_width); + let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click()); + + response.widget_info(|| { + WidgetInfo::selected( + WidgetType::RadioButton, + checked, + galley.as_ref().map_or("", |x| x.text()), + ) + }); + + if ui.is_rect_visible(rect) { + // let visuals = ui.style().interact_selectable(&response, checked); // too colorful + let visuals = ui.style().interact(&response); + + let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); + + let painter = ui.painter(); + + painter.add(epaint::CircleShape { + center: big_icon_rect.center(), + radius: big_icon_rect.width() / 2.0 + visuals.expansion, + fill: visuals.bg_fill, + stroke: visuals.bg_stroke, + }); + + if checked { + painter.add(epaint::CircleShape { + center: small_icon_rect.center(), + radius: small_icon_rect.width() / 3.0, + fill: visuals.fg_stroke.color, // Intentional to use stroke and not fill + // fill: ui.visuals().selection.stroke.color, // too much color + stroke: Default::default(), + }); + } + + if let Some(galley) = galley { + let text_pos = pos2( + rect.min.x + icon_width + icon_spacing, + rect.center().y - 0.5 * galley.size().y, + ); + ui.painter().galley(text_pos, galley, visuals.text_color()); + } + } + + response + } +} From d3c6895443052232109a332c43925796128f4abe Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 16:22:16 +0100 Subject: [PATCH 0025/1202] eframe: Correctly identify if browser tab has focus (#4280) `input.focus` was often wrong on web --- crates/eframe/src/web/backend.rs | 4 ++- crates/eframe/src/web/events.rs | 49 +++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/crates/eframe/src/web/backend.rs b/crates/eframe/src/web/backend.rs index 041fe42028a2..74853abe9fb1 100644 --- a/crates/eframe/src/web/backend.rs +++ b/crates/eframe/src/web/backend.rs @@ -36,8 +36,10 @@ impl WebInput { raw_input } + /// On alt-tab and similar. pub fn on_web_page_focus_change(&mut self, focused: bool) { - self.raw.modifiers = egui::Modifiers::default(); + // log::debug!("on_web_page_focus_change: {focused}"); + self.raw.modifiers = egui::Modifiers::default(); // Avoid sticky modifier keys on alt-tab: self.raw.focused = focused; self.raw.events.push(egui::Event::WindowFocused(focused)); self.latest_touch_pos = None; diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 224ae3ac0db6..f7234d76fad9 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -50,24 +50,21 @@ fn paint_if_needed(runner: &mut AppRunner) { pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsValue> { let document = web_sys::window().unwrap().document().unwrap(); - { - // Avoid sticky modifier keys on alt-tab: - for event_name in ["blur", "focus"] { - let closure = move |_event: web_sys::MouseEvent, runner: &mut AppRunner| { - let has_focus = event_name == "focus"; - - if !has_focus { - // We lost focus - good idea to save - runner.save(); - } + for event_name in ["blur", "focus"] { + let closure = move |_event: web_sys::MouseEvent, runner: &mut AppRunner| { + // log::debug!("{event_name:?}"); + let has_focus = event_name == "focus"; + + if !has_focus { + // We lost focus - good idea to save + runner.save(); + } - runner.input.on_web_page_focus_change(has_focus); - runner.egui_ctx().request_repaint(); - // log::debug!("{event_name:?}"); - }; + runner.input.on_web_page_focus_change(has_focus); + runner.egui_ctx().request_repaint(); + }; - runner_ref.add_event_listener(&document, event_name, closure)?; - } + runner_ref.add_event_listener(&document, event_name, closure)?; } runner_ref.add_event_listener( @@ -228,13 +225,31 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa pub(crate) fn install_window_events(runner_ref: &WebRunner) -> Result<(), JsValue> { let window = web_sys::window().unwrap(); + for event_name in ["blur", "focus"] { + let closure = move |_event: web_sys::MouseEvent, runner: &mut AppRunner| { + // log::debug!("{event_name:?}"); + let has_focus = event_name == "focus"; + + if !has_focus { + // We lost focus - good idea to save + runner.save(); + } + + runner.input.on_web_page_focus_change(has_focus); + runner.egui_ctx().request_repaint(); + }; + + runner_ref.add_event_listener(&window, event_name, closure)?; + } + // Save-on-close runner_ref.add_event_listener(&window, "onbeforeunload", |_: web_sys::Event, runner| { runner.save(); })?; for event_name in &["load", "pagehide", "pageshow", "resize"] { - runner_ref.add_event_listener(&window, event_name, |_: web_sys::Event, runner| { + runner_ref.add_event_listener(&window, event_name, move |_: web_sys::Event, runner| { + // log::debug!("{event_name:?}"); runner.needs_repaint.repaint_asap(); })?; } From 3b147c066b006431fe57a5259b5a9f6c2453b475 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 16:22:28 +0100 Subject: [PATCH 0026/1202] Implement blinking text cursor in `TextEdit` (#4279) On by default. Can be set with `style.text_cursor.blink`. * Closes https://github.com/emilk/egui/pull/4121 --- crates/egui/src/style.rs | 102 ++++++++++++++++--- crates/egui/src/text_selection/visuals.rs | 36 ++++++- crates/egui/src/widgets/text_edit/builder.rs | 44 +++++--- crates/egui/src/widgets/text_edit/state.rs | 5 + 4 files changed, 155 insertions(+), 32 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 1523345f9b8c..df6ae55c4c5b 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -647,6 +647,39 @@ pub struct Interaction { pub multi_widget_text_select: bool, } +/// Look and feel of the text cursor. +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct TextCursorStyle { + /// The color and width of the text cursor + pub stroke: Stroke, + + /// Show where the text cursor would be if you clicked? + pub preview: bool, + + /// Should the cursor blink? + pub blink: bool, + + /// When blinking, this is how long the cursor is visible. + pub on_duration: f32, + + /// When blinking, this is how long the cursor is invisible. + pub off_duration: f32, +} + +impl Default for TextCursorStyle { + fn default() -> Self { + Self { + stroke: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), // Dark mode + preview: false, + blink: true, + on_duration: 0.5, + off_duration: 0.5, + } + } +} + /// Controls the visual style (colors etc) of egui. /// /// You can change the visuals of a [`Ui`] with [`Ui::visuals_mut`] @@ -722,11 +755,8 @@ pub struct Visuals { pub resize_corner_size: f32, - /// The color and width of the text cursor - pub text_cursor: Stroke, - - /// show where the text cursor would be if you clicked - pub text_cursor_preview: bool, + /// How the text cursor acts. + pub text_cursor: TextCursorStyle, /// Allow child widgets to be just on the border and still have a stroke with some thickness pub clip_rect_margin: f32, @@ -1094,8 +1124,7 @@ impl Visuals { resize_corner_size: 12.0, - text_cursor: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), - text_cursor_preview: false, + text_cursor: Default::default(), clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion button_frame: true, @@ -1146,7 +1175,10 @@ impl Visuals { color: Color32::from_black_alpha(25), }, - text_cursor: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)), + text_cursor: TextCursorStyle { + stroke: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)), + ..Default::default() + }, ..Self::dark() } @@ -1737,8 +1769,9 @@ impl Visuals { popup_shadow, resize_corner_size, + text_cursor, - text_cursor_preview, + clip_rect_margin, button_frame, collapsing_header_frame, @@ -1834,16 +1867,14 @@ impl Visuals { ); ui_color(ui, hyperlink_color, "hyperlink_color"); + }); - ui.horizontal(|ui| { - ui.label("Text cursor"); - ui.add(text_cursor); - }); + ui.collapsing("Text cursor", |ui| { + text_cursor.ui(ui); }); ui.collapsing("Misc", |ui| { ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); - ui.checkbox(text_cursor_preview, "Preview text cursor on hover"); ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin")); ui.checkbox(button_frame, "Button has a frame"); @@ -1887,6 +1918,49 @@ impl Visuals { } } +impl TextCursorStyle { + fn ui(&mut self, ui: &mut Ui) { + let Self { + stroke, + preview, + blink, + on_duration, + off_duration, + } = self; + + ui.horizontal(|ui| { + ui.label("Stroke"); + ui.add(stroke); + }); + + ui.checkbox(preview, "Preview text cursor on hover"); + + ui.checkbox(blink, "Blink"); + + if *blink { + Grid::new("cursor_blink").show(ui, |ui| { + ui.label("On time"); + ui.add( + DragValue::new(on_duration) + .speed(0.1) + .clamp_range(0.0..=2.0) + .suffix(" s"), + ); + ui.end_row(); + + ui.label("Off time"); + ui.add( + DragValue::new(off_duration) + .speed(0.1) + .clamp_range(0.0..=2.0) + .suffix(" s"), + ); + ui.end_row(); + }); + } + } +} + #[cfg(debug_assertions)] impl DebugOptions { pub fn ui(&mut self, ui: &mut crate::Ui) { diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index ca85e59ef661..4fc8af0abd46 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -51,8 +51,10 @@ pub fn paint_text_selection( } /// Paint one end of the selection, e.g. the primary cursor. -pub fn paint_cursor(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) { - let stroke = visuals.text_cursor; +/// +/// This will never blink. +pub fn paint_cursor_end(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) { + let stroke = visuals.text_cursor.stroke; let top = cursor_rect.center_top(); let bottom = cursor_rect.center_bottom(); @@ -73,3 +75,33 @@ pub fn paint_cursor(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) { ); } } + +/// Paint one end of the selection, e.g. the primary cursor, with blinking (if enabled). +pub fn paint_text_cursor( + ui: &mut Ui, + painter: &Painter, + primary_cursor_rect: Rect, + time_since_last_edit: f64, +) { + if ui.visuals().text_cursor.blink { + let on_duration = ui.visuals().text_cursor.on_duration; + let off_duration = ui.visuals().text_cursor.off_duration; + let total_duration = on_duration + off_duration; + + let time_in_cycle = (time_since_last_edit % (total_duration as f64)) as f32; + + let wake_in = if time_in_cycle < on_duration { + // Cursor is visible + paint_cursor_end(painter, ui.visuals(), primary_cursor_rect); + on_duration - time_in_cycle + } else { + // Cursor is not visible + total_duration - time_in_cycle + }; + + ui.ctx() + .request_repaint_after(std::time::Duration::from_secs_f32(wake_in)); + } else { + paint_cursor_end(painter, ui.visuals(), primary_cursor_rect); + } +} diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index e320a1ae8439..e0cf2d012f17 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -6,9 +6,7 @@ use crate::{ os::OperatingSystem, output::OutputEvent, text_selection::{ - text_cursor_state::cursor_rect, - visuals::{paint_cursor, paint_text_selection}, - CCursorRange, CursorRange, + text_cursor_state::cursor_rect, visuals::paint_text_selection, CCursorRange, CursorRange, }, *, }; @@ -544,14 +542,14 @@ impl<'t> TextEdit<'t> { let cursor_at_pointer = galley.cursor_from_pos(pointer_pos - rect.min + singleline_offset); - if ui.visuals().text_cursor_preview + if ui.visuals().text_cursor.preview && response.hovered() && ui.input(|i| i.pointer.is_moving()) { - // preview: + // text cursor preview: let cursor_rect = cursor_rect(rect.min, &galley, &cursor_at_pointer, row_height); - paint_cursor(&painter, ui.visuals(), cursor_rect); + text_selection::visuals::paint_cursor_end(&painter, ui.visuals(), cursor_rect); } let is_being_dragged = ui.ctx().is_being_dragged(response.id); @@ -683,18 +681,32 @@ impl<'t> TextEdit<'t> { ui.scroll_to_rect(primary_cursor_rect, None); } - if text.is_mutable() { - paint_cursor(&painter, ui.visuals(), primary_cursor_rect); + if text.is_mutable() && interactive { + let now = ui.ctx().input(|i| i.time); + if response.changed || selection_changed { + state.last_edit_time = now; + } - if interactive { - // For IME, so only set it when text is editable and visible! - ui.ctx().output_mut(|o| { - o.ime = Some(crate::output::IMEOutput { - rect, - cursor_rect: primary_cursor_rect, - }); - }); + // Only show (and blink) cursor if the egui viewport has focus. + // This is for two reasons: + // * Don't give the impression that the user can type into a window without focus + // * Don't repaint the ui because of a blinking cursor in an app that is not in focus + if ui.ctx().input(|i| i.focused) { + text_selection::visuals::paint_text_cursor( + ui, + &painter, + primary_cursor_rect, + now - state.last_edit_time, + ); } + + // For IME, so only set it when text is editable and visible! + ui.ctx().output_mut(|o| { + o.ime = Some(crate::output::IMEOutput { + rect, + cursor_rect: primary_cursor_rect, + }); + }); } } } diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index 8901fd56f27f..d0334da3213a 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -51,6 +51,11 @@ pub struct TextEditState { // Visual offset when editing singleline text bigger than the width. #[cfg_attr(feature = "serde", serde(skip))] pub(crate) singleline_offset: f32, + + /// When did the user last press a key? + /// Used to pause the cursor animation when typing. + #[cfg_attr(feature = "serde", serde(skip))] + pub(crate) last_edit_time: f64, } impl TextEditState { From 33221bd4dda712159accf97efef220ea73b13634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sat, 30 Mar 2024 12:14:58 -0400 Subject: [PATCH 0027/1202] Fix continuous repaint on Wayland when TextEdit is focused or IME output is not None (#4269) * Closes #4254 Changes egui-winit so that it calls `window.set_ime_cursor_area` when the IME rect changes or the user interacts with the application instead of calling it every time the app is rendered. This works around a winit bug that causes the app to continuously repaint under certain circumstances on Wayland. Tested on Wayland and on X11 using the text edit in the egui_demo_app - no changes in IME functionality as far as I can tell. Untested on non-Linux platforms. --------- Co-authored-by: Emil Ernerfeldt --- crates/egui-winit/src/lib.rs | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 41db04952e5f..3d8e9184fdcd 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -99,6 +99,7 @@ pub struct State { accesskit: Option, allow_ime: bool, + ime_rect_px: Option, } impl State { @@ -139,6 +140,7 @@ impl State { accesskit: None, allow_ime: false, + ime_rect_px: None, }; slf.egui_input @@ -817,19 +819,26 @@ impl State { } if let Some(ime) = ime { - let rect = ime.rect; let pixels_per_point = pixels_per_point(&self.egui_ctx, window); - crate::profile_scope!("set_ime_cursor_area"); - window.set_ime_cursor_area( - winit::dpi::PhysicalPosition { - x: pixels_per_point * rect.min.x, - y: pixels_per_point * rect.min.y, - }, - winit::dpi::PhysicalSize { - width: pixels_per_point * rect.width(), - height: pixels_per_point * rect.height(), - }, - ); + let ime_rect_px = pixels_per_point * ime.rect; + if self.ime_rect_px != Some(ime_rect_px) + || self.egui_ctx.input(|i| !i.events.is_empty()) + { + self.ime_rect_px = Some(ime_rect_px); + crate::profile_scope!("set_ime_cursor_area"); + window.set_ime_cursor_area( + winit::dpi::PhysicalPosition { + x: ime_rect_px.min.x, + y: ime_rect_px.min.y, + }, + winit::dpi::PhysicalSize { + width: ime_rect_px.width(), + height: ime_rect_px.height(), + }, + ); + } + } else { + self.ime_rect_px = None; } #[cfg(feature = "accesskit")] From 1354c3e19a6d3b1b43fbe02c987d100fdcf6ec98 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 17:51:44 +0100 Subject: [PATCH 0028/1202] Make the code example demo narrow enough to fit on mobile (#4281) I think it is a good example, and so I want to open it by default, but for that it needs to work on mobile. Hopefully this doesn't make it too cryptic image --- crates/egui_demo_app/src/backend_panel.rs | 1 + crates/egui_demo_lib/src/demo/code_example.rs | 106 ++++++++++-------- .../src/demo/demo_app_windows.rs | 9 ++ 3 files changed, 71 insertions(+), 45 deletions(-) diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 512255871347..2176d57a8b61 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -305,6 +305,7 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) { Some(egui::vec2(375.0, 667.0)), "📱 iPhone SE 2nd Gen", ); + ui.selectable_value(&mut size, Some(egui::vec2(393.0, 852.0)), "📱 iPhone 15"); ui.selectable_value( &mut size, Some(egui::vec2(1280.0, 720.0)), diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index 6786e55a14c1..8e109ca4da01 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -15,78 +15,64 @@ impl Default for CodeExample { impl CodeExample { fn samples_in_grid(&mut self, ui: &mut egui::Ui) { - show_code(ui, r#"ui.heading("Code samples");"#); - ui.heading("Code samples"); + // Note: we keep the code narrow so that the example fits on a mobile screen. + + let Self { name, age } = self; // for brevity later on + + show_code(ui, r#"ui.heading("Example");"#); + ui.heading("Example"); ui.end_row(); show_code( ui, r#" - // Putting things on the same line using ui.horizontal: ui.horizontal(|ui| { - ui.label("Your name: "); - ui.text_edit_singleline(&mut self.name); + ui.label("Name"); + ui.text_edit_singleline(name); });"#, ); // Putting things on the same line using ui.horizontal: ui.horizontal(|ui| { - ui.label("Your name: "); - ui.text_edit_singleline(&mut self.name); + ui.label("Name"); + ui.text_edit_singleline(name); }); ui.end_row(); show_code( ui, - r#"ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));"#, + r#" + ui.add( + egui::DragValue::new(age) + .clamp_range(0..=120) + .suffix(" years"), + );"#, + ); + ui.add( + egui::DragValue::new(age) + .clamp_range(0..=120) + .suffix(" years"), ); - ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age")); ui.end_row(); show_code( ui, r#" if ui.button("Increment").clicked() { - self.age += 1; + *age += 1; }"#, ); if ui.button("Increment").clicked() { - self.age += 1; + *age += 1; } ui.end_row(); - show_code( - ui, - r#"ui.label(format!("Hello '{}', age {}", self.name, self.age));"#, - ); - ui.label(format!("Hello '{}', age {}", self.name, self.age)); + show_code(ui, r#"ui.label(format!("{name} is {age}"));"#); + ui.label(format!("{name} is {age}")); ui.end_row(); } -} - -impl super::Demo for CodeExample { - fn name(&self) -> &'static str { - "🖮 Code Example" - } - - fn show(&mut self, ctx: &egui::Context, open: &mut bool) { - use super::View; - egui::Window::new(self.name()) - .open(open) - .default_size([800.0, 400.0]) - .vscroll(false) - .hscroll(true) - .resizable([true, false]) - .show(ctx, |ui| self.ui(ui)); - } -} - -impl super::View for CodeExample { - fn ui(&mut self, ui: &mut egui::Ui) { - ui.vertical_centered(|ui| { - ui.add(crate::egui_github_link_file!()); - }); - crate::rust_view_ui( + fn code(&mut self, ui: &mut egui::Ui) { + show_code( ui, r" pub struct CodeExample { @@ -96,8 +82,8 @@ pub struct CodeExample { impl CodeExample { fn ui(&mut self, ui: &mut egui::Ui) { -" - .trim(), + // Saves us from writing `&mut self.name` etc + let Self { name, age } = self;", ); ui.horizontal(|ui| { @@ -109,14 +95,38 @@ impl CodeExample { egui::Grid::new("code_samples") .striped(true) .num_columns(2) - .min_col_width(16.0) - .spacing([16.0, 8.0]) .show(ui, |ui| { self.samples_in_grid(ui); }); }); crate::rust_view_ui(ui, " }\n}"); + } +} + +impl super::Demo for CodeExample { + fn name(&self) -> &'static str { + "🖮 Code Example" + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + use super::View; + egui::Window::new(self.name()) + .open(open) + .min_width(375.0) + .default_size([390.0, 500.0]) + .scroll2(false) + .resizable([true, false]) + .show(ctx, |ui| self.ui(ui)); + } +} + +impl super::View for CodeExample { + fn ui(&mut self, ui: &mut egui::Ui) { + ui.scope(|ui| { + ui.spacing_mut().item_spacing = egui::vec2(8.0, 8.0); + self.code(ui); + }); ui.separator(); @@ -129,6 +139,12 @@ impl CodeExample { theme.ui(ui); theme.store_in_memory(ui.ctx()); }); + + ui.separator(); + + ui.vertical_centered(|ui| { + ui.add(crate::egui_github_link_file!()); + }); } } diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 505e938d0d82..0910df4ef566 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -52,6 +52,15 @@ impl Default for Demos { impl Demos { pub fn from_demos(demos: Vec>) -> Self { let mut open = BTreeSet::new(); + + // Explains egui very well + open.insert( + super::code_example::CodeExample::default() + .name() + .to_owned(), + ); + + // Shows off the features open.insert( super::widget_gallery::WidgetGallery::default() .name() From 549b2432284d88cac7493b776e9437ede7f8efbc Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 18:00:43 +0100 Subject: [PATCH 0029/1202] Rename `fn scroll2` to `fn scroll` (#4282) --- crates/egui/src/containers/scroll_area.rs | 10 ++++++++++ crates/egui/src/containers/window.rs | 12 +++++++++++- crates/egui_demo_lib/src/demo/code_example.rs | 2 +- crates/egui_demo_lib/src/demo/tests.rs | 2 +- crates/egui_demo_lib/src/demo/window_options.rs | 2 +- 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index c35cca342251..9c05068f60f6 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -337,6 +337,16 @@ impl ScrollArea { } /// Turn on/off scrolling on the horizontal/vertical axes. + /// + /// You can pass in `false`, `true`, `[false, true]` etc. + #[inline] + pub fn scroll(mut self, scroll_enabled: impl Into) -> Self { + self.scroll_enabled = scroll_enabled.into(); + self + } + + /// Turn on/off scrolling on the horizontal/vertical axes. + #[deprecated = "Renamed to `scroll`"] #[inline] pub fn scroll2(mut self, scroll_enabled: impl Into) -> Self { self.scroll_enabled = scroll_enabled.into(); diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 619675ca8a55..2b266dbd8ffa 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -328,9 +328,19 @@ impl<'open> Window<'open> { } /// Enable/disable horizontal/vertical scrolling. `false` by default. + /// + /// You can pass in `false`, `true`, `[false, true]` etc. + #[inline] + pub fn scroll(mut self, scroll: impl Into) -> Self { + self.scroll = self.scroll.scroll(scroll); + self + } + + /// Enable/disable horizontal/vertical scrolling. `false` by default. + #[deprecated = "Renamed to `scroll`"] #[inline] pub fn scroll2(mut self, scroll: impl Into) -> Self { - self.scroll = self.scroll.scroll2(scroll); + self.scroll = self.scroll.scroll(scroll); self } diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index 8e109ca4da01..2b09b557be35 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -115,7 +115,7 @@ impl super::Demo for CodeExample { .open(open) .min_width(375.0) .default_size([390.0, 500.0]) - .scroll2(false) + .scroll(false) .resizable([true, false]) .show(ctx, |ui| self.ui(ui)); } diff --git a/crates/egui_demo_lib/src/demo/tests.rs b/crates/egui_demo_lib/src/demo/tests.rs index a29e37fe0bca..44e355d0e8e3 100644 --- a/crates/egui_demo_lib/src/demo/tests.rs +++ b/crates/egui_demo_lib/src/demo/tests.rs @@ -372,7 +372,7 @@ impl super::Demo for InputTest { .default_width(800.0) .open(open) .resizable(true) - .scroll2(false) + .scroll(false) .show(ctx, |ui| { use super::View as _; self.ui(ui); diff --git a/crates/egui_demo_lib/src/demo/window_options.rs b/crates/egui_demo_lib/src/demo/window_options.rs index 54c77988e750..7d4241dd38ef 100644 --- a/crates/egui_demo_lib/src/demo/window_options.rs +++ b/crates/egui_demo_lib/src/demo/window_options.rs @@ -67,7 +67,7 @@ impl super::Demo for WindowOptions { .constrain(constrain) .collapsible(collapsible) .title_bar(title_bar) - .scroll2(scroll2) + .scroll(scroll2) .enabled(enabled); if closable { window = window.open(open); From e03ea2e17d73b3597a8dae8c4d833d40b1413a0c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 18:38:59 +0100 Subject: [PATCH 0030/1202] eframe: Early-out from context switching the `glow` backend (#4284) * Closes https://github.com/emilk/egui/issues/4173 --- crates/eframe/src/native/glow_integration.rs | 31 +++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 8dd0f05f6e21..ba0b4d8fbd23 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -836,6 +836,13 @@ fn change_gl_context( ) { crate::profile_function!(); + if let Some(current_gl_context) = current_gl_context { + crate::profile_scope!("is_current"); + if gl_surface.is_current(current_gl_context) { + return; // Early-out to save a lot of time. + } + } + let not_current = { crate::profile_scope!("make_not_current"); current_gl_context @@ -844,6 +851,7 @@ fn change_gl_context( .make_not_current() .unwrap() }; + crate::profile_scope!("make_current"); *current_gl_context = Some(not_current.make_current(gl_surface).unwrap()); } @@ -1178,15 +1186,7 @@ impl GlutinWindowContext { if let Some(viewport) = self.viewports.get(&viewport_id) { if let Some(gl_surface) = &viewport.gl_surface { - self.current_gl_context = Some( - self.current_gl_context - .take() - .unwrap() - .make_not_current() - .unwrap() - .make_current(gl_surface) - .unwrap(), - ); + change_gl_context(&mut self.current_gl_context, gl_surface); gl_surface.resize( self.current_gl_context .as_ref() @@ -1436,18 +1436,7 @@ fn render_immediate_viewport( let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); - { - crate::profile_function!("context-switch"); - *current_gl_context = Some( - current_gl_context - .take() - .unwrap() - .make_not_current() - .unwrap() - .make_current(gl_surface) - .unwrap(), - ); - } + change_gl_context(current_gl_context, gl_surface); let current_gl_context = current_gl_context.as_ref().unwrap(); From ab720ce900161180b3a4ee5d1eb5cc3867d9cca2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 18:39:05 +0100 Subject: [PATCH 0031/1202] Change `Frame::multiply_with_opacity` to multiply in gamma space (#4283) This produces a more perceptually even change when used in animations, like when fading out a closed window --- crates/ecolor/src/color32.rs | 2 +- crates/egui/src/containers/frame.rs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index 0b60176f5cd9..a2f062948194 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -212,7 +212,7 @@ impl Color32 { /// Multiply with 0.5 to make color half as opaque in linear space. /// /// This is using linear space, which is not perceptually even. - /// You may want to use [`Self::gamma_multiply`] instead. + /// You likely want to use [`Self::gamma_multiply`] instead. #[inline] pub fn linear_multiply(self, factor: f32) -> Self { crate::ecolor_assert!(0.0 <= factor && factor <= 1.0); diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index b982cfd0b030..82ef47770a74 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -196,11 +196,15 @@ impl Frame { self } + /// Opacity multiplier in gamma space. + /// + /// For instance, multiplying with `0.5` + /// will make the frame half transparent. #[inline] pub fn multiply_with_opacity(mut self, opacity: f32) -> Self { - self.fill = self.fill.linear_multiply(opacity); - self.stroke.color = self.stroke.color.linear_multiply(opacity); - self.shadow.color = self.shadow.color.linear_multiply(opacity); + self.fill = self.fill.gamma_multiply(opacity); + self.stroke.color = self.stroke.color.gamma_multiply(opacity); + self.shadow.color = self.shadow.color.gamma_multiply(opacity); self } } From 2ee9d30d6e9879460d771c480a64a393f29e5241 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 19:12:54 +0100 Subject: [PATCH 0032/1202] Make it easier to tweak text colors in settings --- crates/egui/src/style.rs | 72 ++++++++++++++----------- crates/egui/src/widgets/color_picker.rs | 2 +- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index df6ae55c4c5b..9f9aac0534a1 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -1799,6 +1799,37 @@ impl Visuals { .on_hover_text("Background of plots and paintings"); }); + ui.collapsing("Text color", |ui| { + ui_text_color(ui, &mut widgets.noninteractive.fg_stroke.color, "Label"); + ui_text_color( + ui, + &mut widgets.inactive.fg_stroke.color, + "Unhovered button", + ); + ui_text_color(ui, &mut widgets.hovered.fg_stroke.color, "Hovered button"); + ui_text_color(ui, &mut widgets.active.fg_stroke.color, "Clicked button"); + + ui_text_color(ui, warn_fg_color, RichText::new("Warnings")); + ui_text_color(ui, error_fg_color, RichText::new("Errors")); + + ui_text_color(ui, hyperlink_color, "hyperlink_color"); + + ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui( + |ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("For monospaced inlined text "); + ui.code("like this"); + ui.label("."); + }); + }, + ); + }); + + ui.collapsing("Text cursor", |ui| { + text_cursor.ui(ui); + }); + ui.collapsing("Window", |ui| { Grid::new("window") .num_columns(2) @@ -1844,35 +1875,6 @@ impl Visuals { ui.collapsing("Widgets", |ui| widgets.ui(ui)); ui.collapsing("Selection", |ui| selection.ui(ui)); - ui.collapsing("Other colors", |ui| { - ui.horizontal(|ui| { - ui_color( - ui, - &mut widgets.noninteractive.fg_stroke.color, - "Text color", - ); - ui_color(ui, warn_fg_color, RichText::new("Warnings")); - ui_color(ui, error_fg_color, RichText::new("Errors")); - }); - - ui_color(ui, code_bg_color, RichText::new("Code background").code()).on_hover_ui( - |ui| { - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 0.0; - ui.label("For monospaced inlined text "); - ui.code("like this"); - ui.label("."); - }); - }, - ); - - ui_color(ui, hyperlink_color, "hyperlink_color"); - }); - - ui.collapsing("Text cursor", |ui| { - text_cursor.ui(ui); - }); - ui.collapsing("Misc", |ui| { ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); ui.add(Slider::new(clip_rect_margin, 0.0..=20.0).text("clip_rect_margin")); @@ -2025,14 +2027,22 @@ fn two_drag_values(value: &mut Vec2, range: std::ops::RangeInclusive) -> im } } -fn ui_color(ui: &mut Ui, srgba: &mut Color32, label: impl Into) -> Response { +fn ui_color(ui: &mut Ui, color: &mut Color32, label: impl Into) -> Response { ui.horizontal(|ui| { - ui.color_edit_button_srgba(srgba); + ui.color_edit_button_srgba(color); ui.label(label); }) .response } +fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into) -> Response { + ui.horizontal(|ui| { + ui.color_edit_button_srgba(color); + ui.label(label.into().color(*color)); + }) + .response +} + impl HandleShape { pub fn ui(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index 8426adee9682..6935de4536d5 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -93,7 +93,7 @@ fn color_button(ui: &mut Ui, color: Color32, open: bool) -> Response { show_color_at(ui.painter(), color, rect); - let rounding = visuals.rounding.at_most(2.0); + let rounding = visuals.rounding.at_most(2.0); // Can't do more rounding because the background grid doesn't do any rounding ui.painter() .rect_stroke(rect, rounding, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border } From 5a0a1e96e05d8429f98bc9741a1036484e6a29b2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 30 Mar 2024 19:33:19 +0100 Subject: [PATCH 0033/1202] Remove a bunch of `unwrap()` (#4285) The fewer unwraps, the fewer panics --- crates/eframe/src/native/glow_integration.rs | 40 ++++++++++++-------- crates/eframe/src/native/wgpu_integration.rs | 20 +++++----- crates/egui_glow/examples/pure_glow.rs | 9 +++-- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index ba0b4d8fbd23..65e2094b8865 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -7,7 +7,7 @@ #![allow(clippy::arc_with_non_send_sync)] // glow::Context was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e -use std::{cell::RefCell, rc::Rc, sync::Arc, time::Instant}; +use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant}; use glutin::{ config::GlConfig, @@ -22,9 +22,9 @@ use winit::{ }; use egui::{ - epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, NumExt as _, - ViewportBuilder, ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, - ViewportInfo, ViewportOutput, + epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, ViewportBuilder, + ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, + ViewportOutput, }; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; @@ -257,7 +257,7 @@ impl GlowWinitApp { #[cfg(feature = "accesskit")] { let event_loop_proxy = self.repaint_proxy.lock().clone(); - let viewport = glutin.viewports.get_mut(&ViewportId::ROOT).unwrap(); + let viewport = glutin.viewports.get_mut(&ViewportId::ROOT).unwrap(); // we always have a root if let Viewport { window: Some(window), egui_winit: Some(egui_winit), @@ -548,13 +548,17 @@ impl GlowWinitRunning { let (raw_input, viewport_ui_cb) = { let mut glutin = self.glutin.borrow_mut(); let egui_ctx = glutin.egui_ctx.clone(); - let viewport = glutin.viewports.get_mut(&viewport_id).unwrap(); + let Some(viewport) = glutin.viewports.get_mut(&viewport_id) else { + return EventResult::Wait; + }; let Some(window) = viewport.window.as_ref() else { return EventResult::Wait; }; egui_winit::update_viewport_info(&mut viewport.info, &egui_ctx, window); - let egui_winit = viewport.egui_winit.as_mut().unwrap(); + let Some(egui_winit) = viewport.egui_winit.as_mut() else { + return EventResult::Wait; + }; let mut raw_input = egui_winit.take_egui_input(window); let viewport_ui_cb = viewport.viewport_ui_cb.clone(); @@ -587,8 +591,12 @@ impl GlowWinitRunning { .. } = &mut *glutin; let viewport = &viewports[&viewport_id]; - let window = viewport.window.as_ref().unwrap(); - let gl_surface = viewport.gl_surface.as_ref().unwrap(); + let Some(window) = viewport.window.as_ref() else { + return EventResult::Wait; + }; + let Some(gl_surface) = viewport.gl_surface.as_ref() else { + return EventResult::Wait; + }; let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); @@ -638,7 +646,9 @@ impl GlowWinitRunning { .. } = &mut *glutin; - let viewport = viewports.get_mut(&viewport_id).unwrap(); + let Some(viewport) = viewports.get_mut(&viewport_id) else { + return EventResult::Wait; + }; viewport.info.events.clear(); // they should have been processed let window = viewport.window.clone().unwrap(); let gl_surface = viewport.gl_surface.as_ref().unwrap(); @@ -877,7 +887,7 @@ impl GlutinWindowContext { crate::HardwareAcceleration::Off => Some(false), }; let swap_interval = if native_options.vsync { - glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap()) + glutin::surface::SwapInterval::Wait(NonZeroU32::MIN) } else { glutin::surface::SwapInterval::DontWait }; @@ -1101,8 +1111,8 @@ impl GlutinWindowContext { // surface attributes let (width_px, height_px): (u32, u32) = window.inner_size().into(); - let width_px = std::num::NonZeroU32::new(width_px.at_least(1)).unwrap(); - let height_px = std::num::NonZeroU32::new(height_px.at_least(1)).unwrap(); + let width_px = NonZeroU32::new(width_px).unwrap_or(NonZeroU32::MIN); + let height_px = NonZeroU32::new(height_px).unwrap_or(NonZeroU32::MIN); let surface_attributes = { use rwh_05::HasRawWindowHandle as _; // glutin stuck on old version of raw-window-handle glutin::surface::SurfaceAttributesBuilder::::new() @@ -1181,8 +1191,8 @@ impl GlutinWindowContext { } fn resize(&mut self, viewport_id: ViewportId, physical_size: winit::dpi::PhysicalSize) { - let width_px = std::num::NonZeroU32::new(physical_size.width.at_least(1)).unwrap(); - let height_px = std::num::NonZeroU32::new(physical_size.height.at_least(1)).unwrap(); + let width_px = NonZeroU32::new(physical_size.width).unwrap_or(NonZeroU32::MIN); + let height_px = NonZeroU32::new(physical_size.height).unwrap_or(NonZeroU32::MIN); if let Some(viewport) = self.viewports.get(&viewport_id) { if let Some(gl_surface) = &viewport.gl_surface { diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index c17bba27afa7..dd4debd8b7a0 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -5,7 +5,7 @@ //! There is a bunch of improvements we could do, //! like removing a bunch of `unwraps`. -use std::{cell::RefCell, rc::Rc, sync::Arc, time::Instant}; +use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant}; use parking_lot::Mutex; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; @@ -437,13 +437,12 @@ impl WinitApp for WgpuWinitApp { self.init_run_state(egui_ctx, event_loop, storage, window, builder)? }; - EventResult::RepaintNow( - running.shared.borrow().viewports[&ViewportId::ROOT] - .window - .as_ref() - .unwrap() - .id(), - ) + let viewport = &running.shared.borrow().viewports[&ViewportId::ROOT]; + if let Some(window) = &viewport.window { + EventResult::RepaintNow(window.id()) + } else { + EventResult::Wait + } } winit::event::Event::Suspended => { @@ -615,7 +614,9 @@ impl WgpuWinitRunning { } } - let egui_winit = egui_winit.as_mut().unwrap(); + let Some(egui_winit) = egui_winit.as_mut() else { + return EventResult::Wait; + }; let mut raw_input = egui_winit.take_egui_input(window); integration.pre_update(); @@ -775,7 +776,6 @@ impl WgpuWinitRunning { // See: https://github.com/rust-windowing/winit/issues/208 // This solves an issue where the app would panic when minimizing on Windows. if let Some(viewport_id) = viewport_id { - use std::num::NonZeroU32; if let (Some(width), Some(height)) = ( NonZeroU32::new(physical_size.width), NonZeroU32::new(physical_size.height), diff --git a/crates/egui_glow/examples/pure_glow.rs b/crates/egui_glow/examples/pure_glow.rs index eab04af1c5fb..3da382af6ec2 100644 --- a/crates/egui_glow/examples/pure_glow.rs +++ b/crates/egui_glow/examples/pure_glow.rs @@ -4,6 +4,8 @@ #![allow(unsafe_code)] #![allow(clippy::arc_with_non_send_sync)] // glow::Context was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e +use std::num::NonZeroU32; + use egui_winit::winit; /// The majority of `GlutinWindowContext` is taken from `eframe` @@ -19,7 +21,6 @@ impl GlutinWindowContext { // preferably add android support at the same time. #[allow(unsafe_code)] unsafe fn new(event_loop: &winit::event_loop::EventLoopWindowTarget) -> Self { - use egui::NumExt; use glutin::context::NotCurrentGlContext; use glutin::display::GetGlDisplay; use glutin::display::GlDisplay; @@ -87,8 +88,8 @@ impl GlutinWindowContext { .expect("failed to finalize glutin window") }); let (width, height): (u32, u32) = window.inner_size().into(); - let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap(); - let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap(); + let width = NonZeroU32::new(width).unwrap_or(NonZeroU32::MIN); + let height = NonZeroU32::new(height).unwrap_or(NonZeroU32::MIN); let surface_attributes = glutin::surface::SurfaceAttributesBuilder::::new() .build(window.raw_window_handle(), width, height); @@ -107,7 +108,7 @@ impl GlutinWindowContext { gl_surface .set_swap_interval( &gl_context, - glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap()), + glutin::surface::SwapInterval::Wait(NonZeroU32::MIN), ) .unwrap(); From 21835c31760f0c3e03d0d3db1f44b00ecb955734 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sun, 31 Mar 2024 04:09:28 +0900 Subject: [PATCH 0034/1202] Fix `ViewportCommand::InnerSize` not resizing viewport on Wayland (#4211) --- crates/eframe/src/native/glow_integration.rs | 93 ++++++------ crates/eframe/src/native/wgpu_integration.rs | 149 +++++++++++-------- crates/egui-winit/src/lib.rs | 116 ++++++++------- 3 files changed, 198 insertions(+), 160 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 65e2094b8865..823e0b0201a4 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -23,8 +23,7 @@ use winit::{ use egui::{ epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, ViewportBuilder, - ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, - ViewportOutput, + ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportInfo, ViewportOutput, }; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; @@ -103,6 +102,7 @@ struct Viewport { ids: ViewportIdPair, class: ViewportClass, builder: ViewportBuilder, + deferred_commands: Vec, info: ViewportInfo, screenshot_requested: bool, @@ -554,7 +554,7 @@ impl GlowWinitRunning { let Some(window) = viewport.window.as_ref() else { return EventResult::Wait; }; - egui_winit::update_viewport_info(&mut viewport.info, &egui_ctx, window); + egui_winit::update_viewport_info(&mut viewport.info, &egui_ctx, window, false); let Some(egui_winit) = viewport.egui_winit.as_mut() else { return EventResult::Wait; @@ -640,6 +640,8 @@ impl GlowWinitRunning { viewport_output, } = full_output; + glutin.remove_viewports_not_in(&viewport_output); + let GlutinWindowContext { viewports, current_gl_context, @@ -649,6 +651,7 @@ impl GlowWinitRunning { let Some(viewport) = viewports.get_mut(&viewport_id) else { return EventResult::Wait; }; + viewport.info.events.clear(); // they should have been processed let window = viewport.window.clone().unwrap(); let gl_surface = viewport.gl_surface.as_ref().unwrap(); @@ -715,7 +718,7 @@ impl GlowWinitRunning { } } - glutin.handle_viewport_output(event_loop, &integration.egui_ctx, viewport_output); + glutin.handle_viewport_output(event_loop, &integration.egui_ctx, &viewport_output); integration.report_frame_time(frame_timer.total_time_sec()); // don't count auto-save time as part of regular frame time @@ -998,8 +1001,7 @@ impl GlutinWindowContext { if let Some(window) = &window { viewport_from_window.insert(window.id(), ViewportId::ROOT); window_from_viewport.insert(ViewportId::ROOT, window.id()); - info.minimized = window.is_minimized(); - info.maximized = Some(window.is_maximized()); + egui_winit::update_viewport_info(&mut info, egui_ctx, window, true); } let mut viewports = ViewportIdMap::default(); @@ -1009,6 +1011,7 @@ impl GlutinWindowContext { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, builder: viewport_builder, + deferred_commands: vec![], info, screenshot_requested: false, viewport_ui_cb: None, @@ -1090,8 +1093,8 @@ impl GlutinWindowContext { &window, &viewport.builder, ); - viewport.info.minimized = window.is_minimized(); - viewport.info.maximized = Some(window.is_maximized()); + + egui_winit::update_viewport_info(&mut viewport.info, &self.egui_ctx, &window, true); viewport.window.insert(Arc::new(window)) }; @@ -1212,16 +1215,27 @@ impl GlutinWindowContext { self.gl_config.display().get_proc_address(addr) } + pub(crate) fn remove_viewports_not_in( + &mut self, + viewport_output: &ViewportIdMap, + ) { + // GC old viewports + self.viewports + .retain(|id, _| viewport_output.contains_key(id)); + self.viewport_from_window + .retain(|_, id| viewport_output.contains_key(id)); + self.window_from_viewport + .retain(|id, _| viewport_output.contains_key(id)); + } + fn handle_viewport_output( &mut self, event_loop: &EventLoopWindowTarget, egui_ctx: &egui::Context, - viewport_output: ViewportIdMap, + viewport_output: &ViewportIdMap, ) { crate::profile_function!(); - let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect(); - for ( viewport_id, ViewportOutput { @@ -1229,58 +1243,60 @@ impl GlutinWindowContext { class, builder, viewport_ui_cb, - commands, + mut commands, repaint_delay: _, // ignored - we listened to the repaint callback instead }, - ) in viewport_output + ) in viewport_output.clone() { let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent); let viewport = initialize_or_update_viewport( - egui_ctx, &mut self.viewports, ids, class, builder, viewport_ui_cb, - self.focused_viewport, ); if let Some(window) = &viewport.window { + let old_inner_size = window.inner_size(); + let is_viewport_focused = self.focused_viewport == Some(viewport_id); + viewport.deferred_commands.append(&mut commands); + egui_winit::process_viewport_commands( egui_ctx, &mut viewport.info, - commands, + std::mem::take(&mut viewport.deferred_commands), window, is_viewport_focused, &mut viewport.screenshot_requested, ); + + // For Wayland : https://github.com/emilk/egui/issues/4196 + if cfg!(target_os = "linux") { + let new_inner_size = window.inner_size(); + if new_inner_size != old_inner_size { + self.resize(viewport_id, new_inner_size); + } + } } } // Create windows for any new viewports: self.initialize_all_windows(event_loop); - // GC old viewports - self.viewports - .retain(|id, _| active_viewports_ids.contains(id)); - self.viewport_from_window - .retain(|_, id| active_viewports_ids.contains(id)); - self.window_from_viewport - .retain(|id, _| active_viewports_ids.contains(id)); + self.remove_viewports_not_in(viewport_output); } } -fn initialize_or_update_viewport<'vp>( - egu_ctx: &egui::Context, - viewports: &'vp mut ViewportIdMap, +fn initialize_or_update_viewport( + viewports: &mut ViewportIdMap, ids: ViewportIdPair, class: ViewportClass, mut builder: ViewportBuilder, viewport_ui_cb: Option>, - focused_viewport: Option, -) -> &'vp mut Viewport { +) -> &mut Viewport { crate::profile_function!(); if builder.icon.is_none() { @@ -1298,6 +1314,7 @@ fn initialize_or_update_viewport<'vp>( ids, class, builder, + deferred_commands: vec![], info: Default::default(), screenshot_requested: false, viewport_ui_cb, @@ -1315,7 +1332,7 @@ fn initialize_or_update_viewport<'vp>( viewport.class = class; viewport.viewport_ui_cb = viewport_ui_cb; - let (delta_commands, recreate) = viewport.builder.patch(builder); + let (mut delta_commands, recreate) = viewport.builder.patch(builder); if recreate { log::debug!( @@ -1325,18 +1342,10 @@ fn initialize_or_update_viewport<'vp>( ); viewport.window = None; viewport.egui_winit = None; - } else if let Some(window) = &viewport.window { - let is_viewport_focused = focused_viewport == Some(ids.this); - egui_winit::process_viewport_commands( - egu_ctx, - &mut viewport.info, - delta_commands, - window, - is_viewport_focused, - &mut viewport.screenshot_requested, - ); } + viewport.deferred_commands.append(&mut delta_commands); + entry.into_mut() } } @@ -1366,13 +1375,11 @@ fn render_immediate_viewport( let mut glutin = glutin.borrow_mut(); initialize_or_update_viewport( - egui_ctx, &mut glutin.viewports, ids, ViewportClass::Immediate, builder, None, - None, ); if let Err(err) = glutin.initialize_window(viewport_id, event_loop) { @@ -1392,7 +1399,7 @@ fn render_immediate_viewport( let (Some(egui_winit), Some(window)) = (&mut viewport.egui_winit, &viewport.window) else { return; }; - egui_winit::update_viewport_info(&mut viewport.info, egui_ctx, window); + egui_winit::update_viewport_info(&mut viewport.info, egui_ctx, window, false); let mut raw_input = egui_winit.take_egui_input(window); raw_input.viewports = glutin @@ -1480,7 +1487,7 @@ fn render_immediate_viewport( egui_winit.handle_platform_output(window, platform_output); - glutin.handle_viewport_output(event_loop, egui_ctx, viewport_output); + glutin.handle_viewport_output(event_loop, egui_ctx, &viewport_output); } #[cfg(feature = "__screenshot")] diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index dd4debd8b7a0..9e0022489e7b 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -76,6 +76,7 @@ pub struct Viewport { ids: ViewportIdPair, class: ViewportClass, builder: ViewportBuilder, + deferred_commands: Vec, info: ViewportInfo, screenshot_requested: bool, @@ -154,13 +155,11 @@ impl WgpuWinitApp { } = &mut *running.shared.borrow_mut(); initialize_or_update_viewport( - egui_ctx, viewports, ViewportIdPair::ROOT, ViewportClass::Root, self.native_options.viewport.clone(), None, - None, ) .initialize_window(event_loop, egui_ctx, viewport_from_window, painter); } @@ -278,6 +277,9 @@ impl WgpuWinitApp { let mut viewport_from_window = HashMap::default(); viewport_from_window.insert(window.id(), ViewportId::ROOT); + let mut info = ViewportInfo::default(); + egui_winit::update_viewport_info(&mut info, &egui_ctx, &window, true); + let mut viewports = Viewports::default(); viewports.insert( ViewportId::ROOT, @@ -285,11 +287,8 @@ impl WgpuWinitApp { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, builder, - info: ViewportInfo { - minimized: window.is_minimized(), - maximized: Some(window.is_maximized()), - ..Default::default() - }, + deferred_commands: vec![], + info, screenshot_requested: false, viewport_ui_cb: None, window: Some(window), @@ -603,7 +602,7 @@ impl WgpuWinitRunning { let Some(window) = window else { return EventResult::Wait; }; - egui_winit::update_viewport_info(info, &integration.egui_ctx, window); + egui_winit::update_viewport_info(info, &integration.egui_ctx, window, false); { crate::profile_scope!("set_window"); @@ -638,7 +637,7 @@ impl WgpuWinitRunning { // ------------------------------------------------------------ - let mut shared = shared.borrow_mut(); + let mut shared_mut = shared.borrow_mut(); let SharedState { egui_ctx, @@ -646,7 +645,17 @@ impl WgpuWinitRunning { painter, viewport_from_window, focused_viewport, - } = &mut *shared; + } = &mut *shared_mut; + + let FullOutput { + platform_output, + textures_delta, + shapes, + pixels_per_point, + viewport_output, + } = full_output; + + remove_viewports_not_in(viewports, painter, viewport_from_window, &viewport_output); let Some(viewport) = viewports.get_mut(&viewport_id) else { return EventResult::Wait; @@ -663,14 +672,6 @@ impl WgpuWinitRunning { return EventResult::Wait; }; - let FullOutput { - platform_output, - textures_delta, - shapes, - pixels_per_point, - viewport_output, - } = full_output; - egui_winit.handle_platform_output(window, platform_output); let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point); @@ -700,8 +701,10 @@ impl WgpuWinitRunning { handle_viewport_output( &integration.egui_ctx, - viewport_output, + &viewport_output, viewports, + painter, + viewport_from_window, *focused_viewport, ); @@ -876,9 +879,7 @@ impl Viewport { painter.max_texture_side(), )); - self.info.minimized = window.is_minimized(); - self.info.maximized = Some(window.is_maximized()); - + egui_winit::update_viewport_info(&mut self.info, egui_ctx, &window, true); self.window = Some(window); } Err(err) => { @@ -933,15 +934,8 @@ fn render_immediate_viewport( .. } = &mut *shared.borrow_mut(); - let viewport = initialize_or_update_viewport( - egui_ctx, - viewports, - ids, - ViewportClass::Immediate, - builder, - None, - None, - ); + let viewport = + initialize_or_update_viewport(viewports, ids, ViewportClass::Immediate, builder, None); if viewport.window.is_none() { viewport.initialize_window(event_loop, egui_ctx, viewport_from_window, painter); } @@ -949,7 +943,7 @@ fn render_immediate_viewport( let (Some(window), Some(egui_winit)) = (&viewport.window, &mut viewport.egui_winit) else { return; }; - egui_winit::update_viewport_info(&mut viewport.info, egui_ctx, window); + egui_winit::update_viewport_info(&mut viewport.info, egui_ctx, window, false); let mut input = egui_winit.take_egui_input(window); input.viewports = viewports @@ -978,13 +972,14 @@ fn render_immediate_viewport( // ------------------------------------------ - let mut shared = shared.borrow_mut(); + let mut shared_mut = shared.borrow_mut(); let SharedState { viewports, painter, + viewport_from_window, focused_viewport, .. - } = &mut *shared; + } = &mut *shared_mut; let Some(viewport) = viewports.get_mut(&ids.this) else { return; @@ -1016,14 +1011,37 @@ fn render_immediate_viewport( egui_winit.handle_platform_output(window, platform_output); - handle_viewport_output(&egui_ctx, viewport_output, viewports, *focused_viewport); + handle_viewport_output( + &egui_ctx, + &viewport_output, + viewports, + painter, + viewport_from_window, + *focused_viewport, + ); +} + +pub(crate) fn remove_viewports_not_in( + viewports: &mut ViewportIdMap, + painter: &mut egui_wgpu::winit::Painter, + viewport_from_window: &mut HashMap, + viewport_output: &ViewportIdMap, +) { + let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect(); + + // Prune dead viewports: + viewports.retain(|id, _| active_viewports_ids.contains(id)); + viewport_from_window.retain(|_, id| active_viewports_ids.contains(id)); + painter.gc_viewports(&active_viewports_ids); } /// Add new viewports, and update existing ones: fn handle_viewport_output( egui_ctx: &egui::Context, - viewport_output: ViewportIdMap, + viewport_output: &ViewportIdMap, viewports: &mut ViewportIdMap, + painter: &mut egui_wgpu::winit::Painter, + viewport_from_window: &mut HashMap, focused_viewport: Option, ) { for ( @@ -1033,46 +1051,56 @@ fn handle_viewport_output( class, builder, viewport_ui_cb, - commands, + mut commands, repaint_delay: _, // ignored - we listened to the repaint callback instead }, - ) in viewport_output + ) in viewport_output.clone() { let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent); - let viewport = initialize_or_update_viewport( - egui_ctx, - viewports, - ids, - class, - builder, - viewport_ui_cb, - focused_viewport, - ); + let viewport = + initialize_or_update_viewport(viewports, ids, class, builder, viewport_ui_cb); if let Some(window) = viewport.window.as_ref() { + let old_inner_size = window.inner_size(); + let is_viewport_focused = focused_viewport == Some(viewport_id); + viewport.deferred_commands.append(&mut commands); + egui_winit::process_viewport_commands( egui_ctx, &mut viewport.info, - commands, + std::mem::take(&mut viewport.deferred_commands), window, is_viewport_focused, &mut viewport.screenshot_requested, ); + + // For Wayland : https://github.com/emilk/egui/issues/4196 + if cfg!(target_os = "linux") { + let new_inner_size = window.inner_size(); + if new_inner_size != old_inner_size { + if let (Some(width), Some(height)) = ( + NonZeroU32::new(new_inner_size.width), + NonZeroU32::new(new_inner_size.height), + ) { + painter.on_window_resized(viewport_id, width, height); + } + } + } } } + + remove_viewports_not_in(viewports, painter, viewport_from_window, viewport_output); } -fn initialize_or_update_viewport<'vp>( - egui_ctx: &egui::Context, - viewports: &'vp mut Viewports, +fn initialize_or_update_viewport( + viewports: &mut Viewports, ids: ViewportIdPair, class: ViewportClass, mut builder: ViewportBuilder, viewport_ui_cb: Option>, - focused_viewport: Option, -) -> &'vp mut Viewport { +) -> &mut Viewport { crate::profile_function!(); if builder.icon.is_none() { @@ -1090,6 +1118,7 @@ fn initialize_or_update_viewport<'vp>( ids, class, builder, + deferred_commands: vec![], info: Default::default(), screenshot_requested: false, viewport_ui_cb, @@ -1106,7 +1135,7 @@ fn initialize_or_update_viewport<'vp>( viewport.ids.parent = ids.parent; viewport.viewport_ui_cb = viewport_ui_cb; - let (delta_commands, recreate) = viewport.builder.patch(builder); + let (mut delta_commands, recreate) = viewport.builder.patch(builder); if recreate { log::debug!( @@ -1116,18 +1145,10 @@ fn initialize_or_update_viewport<'vp>( ); viewport.window = None; viewport.egui_winit = None; - } else if let Some(window) = &viewport.window { - let is_viewport_focused = focused_viewport == Some(ids.this); - egui_winit::process_viewport_commands( - egui_ctx, - &mut viewport.info, - delta_commands, - window, - is_viewport_focused, - &mut viewport.screenshot_requested, - ); } + viewport.deferred_commands.append(&mut delta_commands); + entry.into_mut() } } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 3d8e9184fdcd..5abfa0f68986 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -874,70 +874,62 @@ impl State { } } +pub fn inner_rect_in_points(window: &Window, pixels_per_point: f32) -> Option { + let inner_pos_px = window.inner_position().ok()?; + let inner_pos_px = egui::pos2(inner_pos_px.x as f32, inner_pos_px.y as f32); + + let inner_size_px = window.inner_size(); + let inner_size_px = egui::vec2(inner_size_px.width as f32, inner_size_px.height as f32); + + let inner_rect_px = egui::Rect::from_min_size(inner_pos_px, inner_size_px); + + Some(inner_rect_px / pixels_per_point) +} + +pub fn outer_rect_in_points(window: &Window, pixels_per_point: f32) -> Option { + let outer_pos_px = window.outer_position().ok()?; + let outer_pos_px = egui::pos2(outer_pos_px.x as f32, outer_pos_px.y as f32); + + let outer_size_px = window.outer_size(); + let outer_size_px = egui::vec2(outer_size_px.width as f32, outer_size_px.height as f32); + + let outer_rect_px = egui::Rect::from_min_size(outer_pos_px, outer_size_px); + + Some(outer_rect_px / pixels_per_point) +} + /// Update the given viewport info with the current state of the window. /// /// Call before [`State::take_egui_input`]. +/// +/// If this is called right after window creation, `is_init` should be `true`, otherwise `false`. pub fn update_viewport_info( viewport_info: &mut ViewportInfo, egui_ctx: &egui::Context, window: &Window, + is_init: bool, ) { crate::profile_function!(); let pixels_per_point = pixels_per_point(egui_ctx, window); let has_a_position = match window.is_minimized() { - None | Some(true) => false, - Some(false) => true, - }; - - let inner_pos_px = if has_a_position { - window - .inner_position() - .map(|pos| Pos2::new(pos.x as f32, pos.y as f32)) - .ok() - } else { - None - }; - - let outer_pos_px = if has_a_position { - window - .outer_position() - .map(|pos| Pos2::new(pos.x as f32, pos.y as f32)) - .ok() - } else { - None - }; - - let inner_size_px = if has_a_position { - let size = window.inner_size(); - Some(Vec2::new(size.width as f32, size.height as f32)) - } else { - None - }; - - let outer_size_px = if has_a_position { - let size = window.outer_size(); - Some(Vec2::new(size.width as f32, size.height as f32)) - } else { - None + Some(true) => false, + Some(false) | None => true, }; - let inner_rect_px = if let (Some(pos), Some(size)) = (inner_pos_px, inner_size_px) { - Some(Rect::from_min_size(pos, size)) + let inner_rect = if has_a_position { + inner_rect_in_points(window, pixels_per_point) } else { None }; - let outer_rect_px = if let (Some(pos), Some(size)) = (outer_pos_px, outer_size_px) { - Some(Rect::from_min_size(pos, size)) + let outer_rect = if has_a_position { + outer_rect_in_points(window, pixels_per_point) } else { None }; - let inner_rect = inner_rect_px.map(|r| r / pixels_per_point); - let outer_rect = outer_rect_px.map(|r| r / pixels_per_point); - let monitor_size = { crate::profile_scope!("monitor_size"); if let Some(monitor) = window.current_monitor() { @@ -948,21 +940,23 @@ pub fn update_viewport_info( } }; - viewport_info.focused = Some(window.has_focus()); - viewport_info.fullscreen = Some(window.fullscreen().is_some()); - viewport_info.inner_rect = inner_rect; - viewport_info.monitor_size = monitor_size; + viewport_info.title = Some(window.title()); viewport_info.native_pixels_per_point = Some(window.scale_factor() as f32); + + viewport_info.monitor_size = monitor_size; + viewport_info.inner_rect = inner_rect; viewport_info.outer_rect = outer_rect; - viewport_info.title = Some(window.title()); - if cfg!(target_os = "windows") { - // It's tempting to do this, but it leads to a deadlock on Mac when running + if is_init || !cfg!(target_os = "macos") { + // Asking for minimized/maximized state at runtime leads to a deadlock on Mac when running // `cargo run -p custom_window_frame`. // See https://github.com/emilk/egui/issues/3494 viewport_info.maximized = Some(window.is_maximized()); viewport_info.minimized = Some(window.is_minimized().unwrap_or(false)); } + + viewport_info.fullscreen = Some(window.fullscreen().is_some()); + viewport_info.focused = Some(window.has_focus()); } fn open_url_in_browser(_url: &str) { @@ -1319,11 +1313,27 @@ fn process_viewport_command( ViewportCommand::InnerSize(size) => { let width_px = pixels_per_point * size.x.max(1.0); let height_px = pixels_per_point * size.y.max(1.0); - if window - .request_inner_size(PhysicalSize::new(width_px, height_px)) - .is_some() - { - log::debug!("ViewportCommand::InnerSize ignored by winit"); + let requested_size = PhysicalSize::new(width_px, height_px); + if let Some(_returned_inner_size) = window.request_inner_size(requested_size) { + // On platforms where the size is entirely controlled by the user the + // applied size will be returned immediately, resize event in such case + // may not be generated. + // e.g. Linux + + // On platforms where resizing is disallowed by the windowing system, the current + // inner size is returned immediately, and the user one is ignored. + // e.g. Android, iOS, … + + // However, comparing the results is prone to numerical errors + // because the linux backend converts physical to logical and back again. + // So let's just assume it worked: + + info.inner_rect = inner_rect_in_points(window, pixels_per_point); + info.outer_rect = outer_rect_in_points(window, pixels_per_point); + } else { + // e.g. macOS, Windows + // The request went to the display system, + // and the actual size will be delivered later with the [`WindowEvent::Resized`]. } } ViewportCommand::BeginResize(direction) => { From bb06befef1389eedb1502e416dfd5508db585748 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 31 Mar 2024 20:06:25 +0200 Subject: [PATCH 0035/1202] Consider all non-interactie widgets under the mouse pointer hovered (#4291) At least all those above any interactive widget. * Closes https://github.com/emilk/egui/issues/4286 I feel there is still more thinking to be done about what is considered `hovered` and how it relates to `contains_pointer`, but this PR at least fixes tooltips for uninteractive widgets --- crates/egui/src/interaction.rs | 43 +++++++++++++++++++++++++--------- crates/egui/src/widget_rect.rs | 5 ++++ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 8ee3f259a434..8a7b2d0948c5 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -242,18 +242,39 @@ pub(crate) fn interact( .chain(&long_touched) .copied() .collect() - } else if hits.click.is_some() || hits.drag.is_some() { - // We are hovering over an interactive widget or two. - hits.click.iter().chain(&hits.drag).map(|w| w.id).collect() } else { - // Whatever is topmost is what we are hovering. - // TODO(emilk): consider handle hovering over multiple top-most widgets? - // TODO(emilk): allow hovering close widgets? - hits.contains_pointer - .last() - .map(|w| w.id) - .into_iter() - .collect() + // We may be hovering a an interactive widget or two. + // We must also consider the case where non-interactive widgets + // are _on top_ of an interactive widget. + // For instance: a label in a draggable window. + // In that case we want to hover _both_ widgets, + // otherwise we won't see tooltips for the label. + // + // Because of how `Ui` work, we will often allocate the `Ui` rect + // _after_ adding the children in it (once we know the size it will occopy) + // so we will also have a lot of such `Ui` widgets rects covering almost any widget. + // + // So: we want to hover _all_ widgets above the interactive widget (if any), + // but none below it (an interactive widget stops the hover search). + // + // To know when to stop we need to first know the order of the widgets, + // which luckily we have in the `WidgetRects`. + + let order = |id| widgets.order(id).map(|(_layer, order)| order); // we ignore the layer, since all widgets at this point is in the same layer + + let click_order = hits.click.and_then(|w| order(w.id)).unwrap_or(0); + let drag_order = hits.drag.and_then(|w| order(w.id)).unwrap_or(0); + let top_interactive_order = click_order.max(drag_order); + + let mut hovered: IdSet = hits.click.iter().chain(&hits.drag).map(|w| w.id).collect(); + + for w in &hits.contains_pointer { + if top_interactive_order <= order(w.id).unwrap_or(0) { + hovered.insert(w.id); + } + } + + hovered }; InteractionSnapshot { diff --git a/crates/egui/src/widget_rect.rs b/crates/egui/src/widget_rect.rs index ab95447e1fd2..bf393fe6bd69 100644 --- a/crates/egui/src/widget_rect.rs +++ b/crates/egui/src/widget_rect.rs @@ -72,6 +72,11 @@ impl WidgetRects { self.by_id.get(&id).map(|(_, w)| w) } + /// In which layer, and in which order in that layer? + pub fn order(&self, id: Id) -> Option<(LayerId, usize)> { + self.by_id.get(&id).map(|(idx, w)| (w.layer_id, *idx)) + } + #[inline] pub fn contains(&self, id: Id) -> bool { self.by_id.contains_key(&id) From aa2f87e0ff004ec0bec439ea16ca0c9235eff84f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 31 Mar 2024 20:20:46 +0200 Subject: [PATCH 0036/1202] Allow zoom/pan a plot as long as it contains the mouse cursor (#4292) This is a fix meant mostly for Rerun, where we sometiems paint a vertical time-line over the plot (which is interactive). Before this PR you couldn't zoom or pan the plot while hovering that line, which was really annoying. --- crates/egui_plot/src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 6056f46bd4e7..bae9038378a2 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -1076,8 +1076,13 @@ impl Plot { } } - let hover_pos = response.hover_pos(); - if let Some(hover_pos) = hover_pos { + // Note: we catch zoom/pan if the response contains the pointer, even if it isn't hovered. + // For instance: The user is painting another interactive widget on top of the plot + // but they still want to be able to pan/zoom the plot. + if let (true, Some(hover_pos)) = ( + response.contains_pointer, + ui.input(|i| i.pointer.hover_pos()), + ) { if allow_zoom.any() { let mut zoom_factor = if data_aspect.is_some() { Vec2::splat(ui.input(|i| i.zoom_delta())) From 95b62ce144a28cbb50f10a83f071cc26c8011e6e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 31 Mar 2024 20:32:05 +0200 Subject: [PATCH 0037/1202] Show `WidgetInfo` for each widget if `debug.show_interactive_widgets` This was useful during debugging --- crates/egui/src/context.rs | 48 +++++++++++++++++++++++++++++----- crates/egui/src/response.rs | 8 ++++++ crates/egui/src/widget_rect.rs | 36 ++++++++++++++++++++++--- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 74a71da21419..aa9849523be8 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1202,6 +1202,19 @@ impl Context { res } + /// This is called by [`Response::widget_info`], but can also be called directly. + /// + /// With some debug flags it will store the widget info in [`WidgetRects`] for later display. + #[inline] + pub fn register_widget_info(&self, id: Id, make_info: impl Fn() -> crate::WidgetInfo) { + #[cfg(debug_assertions)] + self.write(|ctx| { + if ctx.memory.options.style.debug.show_interactive_widgets { + ctx.viewport().widgets_this_frame.set_info(id, make_info()); + } + }); + } + /// Get a full-screen painter for a new or existing layer pub fn layer_painter(&self, layer_id: LayerId) -> Painter { let screen_rect = self.screen_rect(); @@ -1807,6 +1820,7 @@ impl Context { self.write(|ctx| ctx.end_frame()) } + /// Called at the end of the frame. #[cfg(debug_assertions)] fn debug_painting(&self) { let paint_widget = |widget: &WidgetRect, text: &str, color: Color32| { @@ -1839,8 +1853,8 @@ impl Context { } else if rect.sense.drag { (Color32::from_rgb(0, 0, 0x88), "drag") } else { - continue; - // (Color32::from_rgb(0, 0, 0x88), "hover") + // unreachable since we only show interactive + (Color32::from_rgb(0, 0, 0x88), "hover") }; painter.debug_rect(rect.interact_rect, color, text); } @@ -1860,10 +1874,32 @@ impl Context { hovered, } = interact_widgets; - if false { - for widget in contains_pointer { - paint_widget_id(widget, "contains_pointer", Color32::BLUE); + if true { + for &id in &contains_pointer { + paint_widget_id(id, "contains_pointer", Color32::BLUE); + } + + let widget_rects = self.write(|w| w.viewport().widgets_this_frame.clone()); + + let mut contains_pointer: Vec = contains_pointer.iter().copied().collect(); + contains_pointer.sort_by_key(|&id| { + widget_rects + .order(id) + .map(|(layer_id, order_in_layer)| (layer_id.order, order_in_layer)) + }); + + let mut debug_text = "Widgets in order:\n".to_owned(); + for id in contains_pointer { + let mut widget_text = format!("{id:?}"); + if let Some(rect) = widget_rects.get(id) { + widget_text += &format!(" {:?} {:?}", rect.rect, rect.sense); + } + if let Some(info) = widget_rects.info(id) { + widget_text += &format!(" {info:?}"); + } + debug_text += &format!("{widget_text}\n"); } + self.debug_text(debug_text); } if true { for widget in hovered { @@ -1887,7 +1923,7 @@ impl Context { drag, } = hits; - if false { + if true { for widget in &contains_pointer { paint_widget(widget, "contains_pointer", Color32::BLUE); } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 6946e157f5a9..18598f81a668 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -745,6 +745,7 @@ impl Response { /// Call after interacting and potential calls to [`Self::mark_changed`]. pub fn widget_info(&self, make_info: impl Fn() -> crate::WidgetInfo) { use crate::output::OutputEvent; + let event = if self.clicked() { Some(OutputEvent::Clicked(make_info())) } else if self.double_clicked() { @@ -758,6 +759,7 @@ impl Response { } else { None }; + if let Some(event) = event { self.output_event(event); } else { @@ -765,6 +767,8 @@ impl Response { self.ctx.accesskit_node_builder(self.id, |builder| { self.fill_accesskit_node_from_widget_info(builder, make_info()); }); + + self.ctx.register_widget_info(self.id, make_info); } } @@ -773,6 +777,10 @@ impl Response { self.ctx.accesskit_node_builder(self.id, |builder| { self.fill_accesskit_node_from_widget_info(builder, event.widget_info().clone()); }); + + self.ctx + .register_widget_info(self.id, || event.widget_info().clone()); + self.ctx.output_mut(|o| o.events.push(event)); } diff --git a/crates/egui/src/widget_rect.rs b/crates/egui/src/widget_rect.rs index bf393fe6bd69..7c502f139277 100644 --- a/crates/egui/src/widget_rect.rs +++ b/crates/egui/src/widget_rect.rs @@ -46,13 +46,25 @@ pub struct WidgetRect { /// /// All [`Ui`]s have a [`WidgetRects`], but whether or not their rects are correct /// depends on if [`Ui::interact_bg`] was ever called. -#[derive(Default, Clone, PartialEq, Eq)] +#[derive(Default, Clone)] pub struct WidgetRects { /// All widgets, in painting order. by_layer: HashMap>, /// All widgets, by id, and their order in their respective layer by_id: IdMap<(usize, WidgetRect)>, + + /// Info about some widgets. + /// + /// Only filled in if the widget is interacted with, + /// or if this is a debug build. + infos: IdMap, +} + +impl PartialEq for WidgetRects { + fn eq(&self, other: &Self) -> bool { + self.by_layer == other.by_layer + } } impl WidgetRects { @@ -90,18 +102,28 @@ impl WidgetRects { /// Clear the contents while retaining allocated memory. pub fn clear(&mut self) { - let Self { by_layer, by_id } = self; + let Self { + by_layer, + by_id, + infos, + } = self; for rects in by_layer.values_mut() { rects.clear(); } by_id.clear(); + + infos.clear(); } /// Insert the given widget rect in the given layer. pub fn insert(&mut self, layer_id: LayerId, widget_rect: WidgetRect) { - let Self { by_layer, by_id } = self; + let Self { + by_layer, + by_id, + infos: _, + } = self; let layer_widgets = by_layer.entry(layer_id).or_default(); @@ -134,4 +156,12 @@ impl WidgetRects { } } } + + pub fn set_info(&mut self, id: Id, info: WidgetInfo) { + self.infos.insert(id, info); + } + + pub fn info(&self, id: Id) -> Option<&WidgetInfo> { + self.infos.get(&id) + } } From a97134d66c30f876118e9eefef7686ee8645af27 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 31 Mar 2024 20:33:02 +0200 Subject: [PATCH 0038/1202] Improve `Debug` format of `Sense`, `WidgetInfo` and `Id` --- crates/egui/src/data/output.rs | 5 ++++- crates/egui/src/id.rs | 2 +- crates/egui/src/sense.rs | 24 +++++++++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index d1db5fc2a41c..97794f4d13e9 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -504,7 +504,10 @@ impl std::fmt::Debug for WidgetInfo { let mut s = f.debug_struct("WidgetInfo"); s.field("typ", typ); - s.field("enabled", enabled); + + if !enabled { + s.field("enabled", enabled); + } if let Some(label) = label { s.field("label", label); diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 1cd96faec5f5..0465c32d76c8 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -85,7 +85,7 @@ impl Id { impl std::fmt::Debug for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:016X}", self.0) + write!(f, "{:04X}", self.value() as u16) } } diff --git a/crates/egui/src/sense.rs b/crates/egui/src/sense.rs index ca216896aee9..1b6394b2cc3e 100644 --- a/crates/egui/src/sense.rs +++ b/crates/egui/src/sense.rs @@ -1,5 +1,5 @@ /// What sort of interaction is a widget sensitive to? -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] // #[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct Sense { /// Buttons, sliders, windows, … @@ -15,6 +15,28 @@ pub struct Sense { pub focusable: bool, } +impl std::fmt::Debug for Sense { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + click, + drag, + focusable, + } = self; + + write!(f, "Sense {{")?; + if *click { + write!(f, " click")?; + } + if *drag { + write!(f, " drag")?; + } + if *focusable { + write!(f, " focusable")?; + } + write!(f, " }}") + } +} + impl Sense { /// Senses no clicks or drags. Only senses mouse hover. #[doc(alias = "none")] From 3ee4890b9433e8038de4cb498a3a9d09eb0d0c4b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 31 Mar 2024 20:41:44 +0200 Subject: [PATCH 0039/1202] Remove warning in release build --- crates/egui/src/context.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index aa9849523be8..1ee0b364136c 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1213,6 +1213,11 @@ impl Context { ctx.viewport().widgets_this_frame.set_info(id, make_info()); } }); + + #[cfg(not(debug_assertions))] + { + _ = (self, id, make_info); + } } /// Get a full-screen painter for a new or existing layer From e99bd00dec0b40ee660149459996f2ec15cc2a7a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 1 Apr 2024 12:14:44 +0200 Subject: [PATCH 0040/1202] Only avoid glow context switching on Windows (#4296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …since it is reportedly broken on Windows * Early-out added in https://github.com/emilk/egui/pull/4284 * Re-opens https://github.com/emilk/egui/issues/4173 😭 * Closes https://github.com/emilk/egui/issues/4289 * Closes https://github.com/emilk/egui/pull/4290 --- crates/eframe/src/native/glow_integration.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 823e0b0201a4..84b3fd94528c 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -849,10 +849,17 @@ fn change_gl_context( ) { crate::profile_function!(); - if let Some(current_gl_context) = current_gl_context { - crate::profile_scope!("is_current"); - if gl_surface.is_current(current_gl_context) { - return; // Early-out to save a lot of time. + if !cfg!(target_os = "windows") { + // According to https://github.com/emilk/egui/issues/4289 + // we cannot do this early-out on Windows. + // TODO(emilk): optimize context switching on Windows too. + // See https://github.com/emilk/egui/issues/4173 + + if let Some(current_gl_context) = current_gl_context { + crate::profile_scope!("is_current"); + if gl_surface.is_current(current_gl_context) { + return; // Early-out to save a lot of time. + } } } From 48ecf01e11e9bacb798dc03c7fc43927c276340b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 1 Apr 2024 13:08:52 +0200 Subject: [PATCH 0041/1202] Rename "Color test" to "Rendering test", and restructure it slightly (#4298) Put the pixel-alignment test first, and hide the color test under a collapsing header. Screenshot 2024-04-01 at 13 01 43 --- crates/egui_demo_app/src/wrap_app.rs | 19 +++-- crates/egui_demo_lib/src/lib.rs | 4 +- .../src/{color_test.rs => rendering_test.rs} | 69 ++++++++++++------- 3 files changed, 60 insertions(+), 32 deletions(-) rename crates/egui_demo_lib/src/{color_test.rs => rendering_test.rs} (93%) diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index db3ee385d9be..98e74d0deea8 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -79,15 +79,22 @@ impl eframe::App for ColorTestApp { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] enum Anchor { Demo, + EasyMarkEditor, + #[cfg(feature = "http")] Http, + #[cfg(feature = "image_viewer")] ImageViewer, + Clock, + #[cfg(any(feature = "glow", feature = "wgpu"))] Custom3d, - Colors, + + /// Rendering test + Rendering, } impl Anchor { @@ -101,7 +108,7 @@ impl Anchor { Self::Clock, #[cfg(any(feature = "glow", feature = "wgpu"))] Self::Custom3d, - Self::Colors, + Self::Rendering, ] } } @@ -147,7 +154,7 @@ pub struct State { #[cfg(feature = "image_viewer")] image_viewer: crate::apps::ImageViewer, clock: FractalClockApp, - color_test: ColorTestApp, + rendering_test: ColorTestApp, selected_anchor: Anchor, backend_panel: super::backend_panel::BackendPanel, @@ -229,9 +236,9 @@ impl WrapApp { } vec.push(( - "🎨 Color test", - Anchor::Colors, - &mut self.state.color_test as &mut dyn eframe::App, + "🎨 Rendering test", + Anchor::Rendering, + &mut self.state.rendering_test as &mut dyn eframe::App, )); vec.into_iter() diff --git a/crates/egui_demo_lib/src/lib.rs b/crates/egui_demo_lib/src/lib.rs index cf31ee8d43b3..7dba93b4117d 100644 --- a/crates/egui_demo_lib/src/lib.rs +++ b/crates/egui_demo_lib/src/lib.rs @@ -13,12 +13,12 @@ #![cfg_attr(feature = "puffin", deny(unsafe_code))] #![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] -mod color_test; mod demo; pub mod easy_mark; +mod rendering_test; -pub use color_test::ColorTest; pub use demo::{DemoWindows, WidgetGallery}; +pub use rendering_test::ColorTest; /// View some Rust code with syntax highlighting and selection. pub(crate) fn rust_view_ui(ui: &mut egui::Ui, code: &str) { diff --git a/crates/egui_demo_lib/src/color_test.rs b/crates/egui_demo_lib/src/rendering_test.rs similarity index 93% rename from crates/egui_demo_lib/src/color_test.rs rename to crates/egui_demo_lib/src/rendering_test.rs index ebbb3076ffcb..2aa6194eea1f 100644 --- a/crates/egui_demo_lib/src/color_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -31,17 +31,43 @@ impl Default for ColorTest { impl ColorTest { pub fn ui(&mut self, ui: &mut Ui) { - ui.set_max_width(680.0); - ui.vertical_centered(|ui| { ui.add(crate::egui_github_link_file!()); }); ui.horizontal_wrapped(|ui|{ - ui.label("This is made to test that the egui painter backend is set up correctly."); + ui.label("This is made to test that the egui rendering backend is set up correctly."); ui.add(egui::Label::new("❓").sense(egui::Sense::click())) .on_hover_text("The texture sampling should be sRGB-aware, and every other color operation should be done in gamma-space (sRGB). All colors should use pre-multiplied alpha"); }); + + ui.separator(); + + pixel_test(ui); + + ui.separator(); + + ui.collapsing("Color test", |ui| { + self.color_test(ui); + }); + + ui.separator(); + + ui.heading("Text rendering"); + + text_on_bg(ui, Color32::from_gray(200), Color32::from_gray(230)); // gray on gray + text_on_bg(ui, Color32::from_gray(140), Color32::from_gray(28)); // dark mode normal text + + // Matches Mac Font book (useful for testing): + text_on_bg(ui, Color32::from_gray(39), Color32::from_gray(255)); + text_on_bg(ui, Color32::from_gray(220), Color32::from_gray(30)); + + ui.separator(); + + blending_and_feathering_test(ui); + } + + fn color_test(&mut self, ui: &mut Ui) { ui.label("If the rendering is done right, all groups of gradients will look uniform."); ui.horizontal(|ui| { @@ -134,24 +160,6 @@ impl ColorTest { ui.label("Linear interpolation (texture sampling):"); self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Linear); - - ui.separator(); - - pixel_test(ui); - - ui.separator(); - ui.label("Testing text rendering:"); - - text_on_bg(ui, Color32::from_gray(200), Color32::from_gray(230)); // gray on gray - text_on_bg(ui, Color32::from_gray(140), Color32::from_gray(28)); // dark mode normal text - - // Matches Mac Font book (useful for testing): - text_on_bg(ui, Color32::from_gray(39), Color32::from_gray(255)); - text_on_bg(ui, Color32::from_gray(220), Color32::from_gray(30)); - - ui.separator(); - - blending_and_feathering_test(ui); } fn show_gradients( @@ -385,8 +393,17 @@ impl TextureManager { } } -fn pixel_test(ui: &mut Ui) { - ui.label("Each subsequent square should be one physical pixel larger than the previous. They should be exactly one physical pixel apart. They should be perfectly aligned to the pixel grid."); +/// A visual test that the rendering is correctly aligned on the physical pixel grid. +/// +/// Requires eyes and a magnifying glass to verify. +pub fn pixel_test(ui: &mut Ui) { + ui.heading("Pixel alignment test"); + ui.label("The first square should be exactly one physical pixel big."); + ui.label("They should be exactly one physical pixel apart."); + ui.label("Each subsequent square should be one physical pixel larger than the previous."); + ui.label("They should be perfectly aligned to the physical pixel grid."); + ui.label("If these squares are blurry, everything will be blurry, including text."); + ui.label("You might need a magnifying glass to check this test."); let color = if ui.style().visuals.dark_mode { egui::Color32::WHITE @@ -395,7 +412,7 @@ fn pixel_test(ui: &mut Ui) { }; let pixels_per_point = ui.ctx().pixels_per_point(); - let num_squares: u32 = 8; + let num_squares = (pixels_per_point * 10.0).round().max(10.0) as u32; let size_pixels = vec2( ((num_squares + 1) * (num_squares + 2) / 2) as f32, num_squares as f32, @@ -422,6 +439,10 @@ fn pixel_test(ui: &mut Ui) { } fn blending_and_feathering_test(ui: &mut Ui) { + ui.label("The left side shows how lines of different widths look."); + ui.label("The right side tests text rendering at different opacities and sizes."); + ui.label("The top and bottom images should look symmetrical in their intensities."); + let size = vec2(512.0, 512.0); let (response, painter) = ui.allocate_painter(size, Sense::hover()); let rect = response.rect; From 0a40b16bd410aa9ac882c4c2ffe2cb50acdcd0ff Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 1 Apr 2024 15:22:47 +0200 Subject: [PATCH 0042/1202] Fix blurry rendering in some browsers (#4299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Closes https://github.com/emilk/egui/issues/4241 I would love some more testers of this. I'm not sure if we really need the round-to-even code, but I'm hesitant to out-right revert https://github.com/emilk/egui/pull/151 when I cannot reproduce its problem. Keeping it seems quite safe though. --- # Testing Checkout the branch and run: * `./scripts/start_server.sh` * `./scripts/build_demo_web.sh` and then open `http://localhost:8888/index.html#Rendering` * `./scripts/build_demo_web.sh --wgpu` and then open `http://localhost:8888/index.html#Rendering` Check the "Rendering test" that the squares in the pixel alignment test are perfectly sharp, like this: Screenshot 2024-04-01 at 13 27 20 If it looks something like this, something is WRONG: Screenshot 2024-04-01 at 13 29 07 Please try it on different zoom levels in different browsers, and if possible on different monitors with different native dpi scaling. Report back the results! ### Mac I have tested on a high-DPI Mac: * Chromium (Brave): ✅ Can reproduce problem on `master`, and it's now fixed * Firefox: ✅ Can reproduce problem on `master`, and it's now fixed * Safari: ❌ Can't get it to work; giving up for now --- crates/eframe/src/web/mod.rs | 51 ++++++++++++++++-------------------- web_demo/index.html | 7 ++++- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index a322a530aa67..566c9b03c8d6 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -130,51 +130,46 @@ fn resize_canvas_to_screen_size( ) -> Option<()> { let parent = canvas.parent_element()?; + // In this function we use "pixel" to mean physical pixel, + // and "point" to mean "logical CSS pixel". + let pixels_per_point = native_pixels_per_point(); + // Prefer the client width and height so that if the parent // element is resized that the egui canvas resizes appropriately. - let width = parent.client_width(); - let height = parent.client_height(); - - let canvas_real_size = Vec2 { - x: width as f32, - y: height as f32, + let parent_size_points = Vec2 { + x: parent.client_width() as f32, + y: parent.client_height() as f32, }; - if width <= 0 || height <= 0 { - log::error!("egui canvas parent size is {}x{}. Try adding `html, body {{ height: 100%; width: 100% }}` to your CSS!", width, height); + if parent_size_points.x <= 0.0 || parent_size_points.y <= 0.0 { + log::error!("The parent element of the egui canvas is {}x{}. Try adding `html, body {{ height: 100%; width: 100% }}` to your CSS!", parent_size_points.x, parent_size_points.y); } - let pixels_per_point = native_pixels_per_point(); + // We take great care here to ensure the rendered canvas aligns + // perfectly to the physical pixel grid, lest we get blurry text. + // At the time of writing, we get pixel perfection on Chromium and Firefox on Mac, + // but Desktop Safari will be blurry on most zoom levels. + // See https://github.com/emilk/egui/issues/4241 for more. - let max_size_pixels = pixels_per_point * max_size_points; + let canvas_size_pixels = pixels_per_point * parent_size_points.min(max_size_points); - let canvas_size_pixels = pixels_per_point * canvas_real_size; - let canvas_size_pixels = canvas_size_pixels.min(max_size_pixels); - let canvas_size_points = canvas_size_pixels / pixels_per_point; - - // Make sure that the height and width are always even numbers. + // Make sure that the size is always an even number of pixels, // otherwise, the page renders blurry on some platforms. // See https://github.com/emilk/egui/issues/103 - fn round_to_even(v: f32) -> f32 { - (v / 2.0).round() * 2.0 - } + let canvas_size_pixels = (canvas_size_pixels / 2.0).round() * 2.0; + + let canvas_size_points = canvas_size_pixels / pixels_per_point; canvas .style() - .set_property( - "width", - &format!("{}px", round_to_even(canvas_size_points.x)), - ) + .set_property("width", &format!("{}px", canvas_size_points.x)) .ok()?; canvas .style() - .set_property( - "height", - &format!("{}px", round_to_even(canvas_size_points.y)), - ) + .set_property("height", &format!("{}px", canvas_size_points.y)) .ok()?; - canvas.set_width(round_to_even(canvas_size_pixels.x) as u32); - canvas.set_height(round_to_even(canvas_size_pixels.y) as u32); + canvas.set_width(canvas_size_pixels.x as u32); + canvas.set_height(canvas_size_pixels.y as u32); Some(()) } diff --git a/web_demo/index.html b/web_demo/index.html index afe4cc646e18..142355b48f83 100644 --- a/web_demo/index.html +++ b/web_demo/index.html @@ -37,7 +37,12 @@ width: 100%; } - /* Position canvas in center-top: */ + /* Position canvas in center-top. + This is rather arbitrarily chosen. + In particular, it seems like both Chromium and Firefox will still align + the canvas on the physical pixel grid, which is required to get + pixel-perfect (non-blurry) rendering in egui. + See https://github.com/emilk/egui/issues/4241 for more */ canvas { margin-right: auto; margin-left: auto; From 058f4753b0cec2f86fd800d6ca5544206bf694ec Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 2 Apr 2024 09:55:13 +0200 Subject: [PATCH 0043/1202] Fix typos and false positives found by new version of 'typos' --- .typos.toml | 2 ++ crates/eframe/src/native/glow_integration.rs | 4 ++-- crates/egui/src/context.rs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.typos.toml b/.typos.toml index 6d8564951781..de51a691cc06 100644 --- a/.typos.toml +++ b/.typos.toml @@ -3,7 +3,9 @@ # run: typos [default.extend-words] +ime = "ime" # Input Method Editor nknown = "nknown" # part of @55nknown username +ro = "ro" # read-only, also part of the username @Phen-Ro [files] extend-exclude = ["web_demo/egui_demo_app.js"] # auto-generated diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 84b3fd94528c..7c9eada1ccc1 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -714,7 +714,7 @@ impl GlowWinitRunning { #[cfg(feature = "__screenshot")] if integration.egui_ctx.frame_nr() == 2 { if let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO") { - save_screeshot_and_exit(&path, &painter, screen_size_in_pixels); + save_screenshot_and_exit(&path, &painter, screen_size_in_pixels); } } @@ -1498,7 +1498,7 @@ fn render_immediate_viewport( } #[cfg(feature = "__screenshot")] -fn save_screeshot_and_exit( +fn save_screenshot_and_exit( path: &str, painter: &egui_glow::Painter, screen_size_in_pixels: [u32; 2], diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 1ee0b364136c..aa8a0b13e32c 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2818,7 +2818,7 @@ impl Context { /// The `Context` lock is held while the given closure is called! /// /// Returns `None` if acesskit is off. - // TODO(emilk): consider making both RO and RW versions + // TODO(emilk): consider making both read-only and read-write versions #[cfg(feature = "accesskit")] pub fn accesskit_node_builder( &self, From 4bc7e6624507347fff453972f171ff4ec049f215 Mon Sep 17 00:00:00 2001 From: Alexander Parlett Date: Tue, 2 Apr 2024 09:33:14 +0100 Subject: [PATCH 0044/1202] Support order on windows (#4301) Adds support for the ordering of windows passing through to the underlying area. Needed for a specific usecase of adding debug tools using windows with the bevy integration. --- crates/egui/src/containers/window.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 2b266dbd8ffa..0f336743bd85 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -104,6 +104,13 @@ impl<'open> Window<'open> { self } + /// `order(Order::Foreground)` for a Window that should always be on top + #[inline] + pub fn order(mut self, order: Order) -> Self { + self.area = self.area.order(order); + self + } + /// Usage: `Window::new(…).mutate(|w| w.resize = w.resize.auto_expand_width(true))` // TODO(emilk): I'm not sure this is a good interface for this. #[inline] From 36ebce163a74af40f6216cba2d68aa4c728443eb Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 2 Apr 2024 15:37:44 +0200 Subject: [PATCH 0045/1202] egui_plots: Fix the same plot tick label being painted multiple times (#4307) Usually this isn't visible (the same label being painted on top of itself), but it will be visible if the user has a custom formatter (e.g. `y_axis_formatter`) that choses a different format based on `GridMark:step_size` (e.g. using fewer decimals for thicker ticks). --- crates/egui_plot/src/lib.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index bae9038378a2..a5adf1c11c13 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -13,7 +13,7 @@ mod memory; mod plot_ui; mod transform; -use std::{ops::RangeInclusive, sync::Arc}; +use std::{cmp::Ordering, ops::RangeInclusive, sync::Arc}; use egui::ahash::HashMap; use egui::*; @@ -1717,9 +1717,31 @@ fn generate_marks(step_sizes: [f64; 3], bounds: (f64, f64)) -> Vec { fill_marks_between(&mut steps, step_sizes[0], bounds); fill_marks_between(&mut steps, step_sizes[1], bounds); fill_marks_between(&mut steps, step_sizes[2], bounds); + + // Remove duplicates: + // This can happen because we have overlapping steps, e.g.: + // step_size[0] = 10 => [-10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120] + // step_size[1] = 100 => [ 0, 100 ] + // step_size[2] = 1000 => [ 0 ] + + steps.sort_by(|a, b| match cmp_f64(a.value, b.value) { + // Keep the largest step size when we dedup later + Ordering::Equal => cmp_f64(b.step_size, a.step_size), + + ord => ord, + }); + steps.dedup_by(|a, b| a.value == b.value); + steps } +fn cmp_f64(a: f64, b: f64) -> Ordering { + match a.partial_cmp(&b) { + Some(ord) => ord, + None => a.is_nan().cmp(&b.is_nan()), + } +} + /// Fill in all values between [min, max] which are a multiple of `step_size` fn fill_marks_between(out: &mut Vec, step_size: f64, (min, max): (f64, f64)) { debug_assert!(max > min); From de9e0adf17601d31cde3a74a7e7b4ca7ec03d166 Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Tue, 2 Apr 2024 17:53:54 +0200 Subject: [PATCH 0046/1202] Allow disabling animations on a ScrollArea (#4309) Adds a flag to ScrollArea that disables animations for the scroll_to_* functions --- crates/egui/src/containers/scroll_area.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 9c05068f60f6..c448269fb0cf 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -176,6 +176,9 @@ pub struct ScrollArea { /// end position until user manually changes position. It will become true /// again once scroll handle makes contact with end. stick_to_end: Vec2b, + + /// If false, `scroll_to_*` functions will not be animated + animated: bool, } impl ScrollArea { @@ -219,6 +222,7 @@ impl ScrollArea { scrolling_enabled: true, drag_to_scroll: true, stick_to_end: Vec2b::FALSE, + animated: true, } } @@ -393,6 +397,15 @@ impl ScrollArea { self } + /// Should the scroll area animate `scroll_to_*` functions? + /// + /// Default: `true`. + #[inline] + pub fn animated(mut self, animated: bool) -> Self { + self.animated = animated; + self + } + /// Is any scrolling enabled? pub(crate) fn is_any_scroll_enabled(&self) -> bool { self.scroll_enabled[0] || self.scroll_enabled[1] @@ -459,6 +472,7 @@ struct Prepared { scrolling_enabled: bool, stick_to_end: Vec2b, + animated: bool, } impl ScrollArea { @@ -475,6 +489,7 @@ impl ScrollArea { scrolling_enabled, drag_to_scroll, stick_to_end, + animated, } = self; let ctx = ui.ctx().clone(); @@ -650,6 +665,7 @@ impl ScrollArea { viewport, scrolling_enabled, stick_to_end, + animated, } } @@ -761,6 +777,7 @@ impl Prepared { viewport: _, scrolling_enabled, stick_to_end, + animated, } = self; let content_size = content_ui.min_size(); @@ -804,7 +821,9 @@ impl Prepared { if delta != 0.0 { let target_offset = state.offset[d] + delta; - if let Some(animation) = &mut state.offset_target[d] { + if !animated { + state.offset[d] = target_offset; + } else if let Some(animation) = &mut state.offset_target[d] { // For instance: the user is continuously calling `ui.scroll_to_cursor`, // so we don't want to reset the animation, but perhaps update the target: animation.target_offset = target_offset; From 15b0ef3259e208e7d0b7ec24dfb9eb5da58de77f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 2 Apr 2024 18:13:37 +0200 Subject: [PATCH 0047/1202] Release 0.27.2 - Fix blurry web rendering --- CHANGELOG.md | 11 +++++++++++ Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 24 ++++++++++++------------ crates/ecolor/CHANGELOG.md | 4 ++++ crates/eframe/CHANGELOG.md | 10 ++++++++++ crates/egui-wgpu/CHANGELOG.md | 4 ++++ crates/egui-winit/CHANGELOG.md | 4 ++++ crates/egui_extras/CHANGELOG.md | 4 ++++ crates/egui_glow/CHANGELOG.md | 4 ++++ crates/egui_plot/CHANGELOG.md | 6 ++++++ crates/epaint/CHANGELOG.md | 4 ++++ 11 files changed, 75 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 764b01eeeb8e..35cab321fcda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +### 🐛 Fixed +* Fix tooltips for non-interactive widgets [#4291](https://github.com/emilk/egui/pull/4291) +* Fix problem clicking the edge of a `TextEdit` [#4272](https://github.com/emilk/egui/pull/4272) +* Fix: `Response::clicked_elsewhere` takes clip rect into account [#4274](https://github.com/emilk/egui/pull/4274) +* Fix incorrect `Response::interact_rect` for `Area/Window` [#4273](https://github.com/emilk/egui/pull/4273) + +### ⭐ Added +* Allow disabling animations on a `ScrollArea` [#4309](https://github.com/emilk/egui/pull/4309) (thanks [@lucasmerlin](https://github.com/lucasmerlin)!) + + ## 0.27.1 - 2024-03-29 ### 🐛 Fixed * Fix visual glitch on the right side of highly rounded rectangles [#4244](https://github.com/emilk/egui/pull/4244) diff --git a/Cargo.lock b/Cargo.lock index 0a72704b8096..c6d8727188a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1187,7 +1187,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "ecolor" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "cint", @@ -1198,7 +1198,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "cocoa", @@ -1236,7 +1236,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.27.1" +version = "0.27.2" dependencies = [ "accesskit", "ahash", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "document-features", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.27.1" +version = "0.27.2" dependencies = [ "accesskit_winit", "arboard", @@ -1287,7 +1287,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "chrono", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.27.1" +version = "0.27.2" dependencies = [ "chrono", "criterion", @@ -1327,7 +1327,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.27.1" +version = "0.27.2" dependencies = [ "chrono", "document-features", @@ -1345,7 +1345,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "document-features", @@ -1365,7 +1365,7 @@ dependencies = [ [[package]] name = "egui_plot" -version = "0.27.1" +version = "0.27.2" dependencies = [ "document-features", "egui", @@ -1394,7 +1394,7 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "emath" -version = "0.27.1" +version = "0.27.2" dependencies = [ "bytemuck", "document-features", @@ -1469,7 +1469,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.27.1" +version = "0.27.2" dependencies = [ "ab_glyph", "ahash", diff --git a/Cargo.toml b/Cargo.toml index bde03d7c8fb4..598eae08cb1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ edition = "2021" license = "MIT OR Apache-2.0" rust-version = "1.72" -version = "0.27.1" +version = "0.27.2" [profile.release] @@ -48,17 +48,17 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.27.1", path = "crates/emath", default-features = false } -ecolor = { version = "0.27.1", path = "crates/ecolor", default-features = false } -epaint = { version = "0.27.1", path = "crates/epaint", default-features = false } -egui = { version = "0.27.1", path = "crates/egui", default-features = false } -egui_plot = { version = "0.27.1", path = "crates/egui_plot", default-features = false } -egui-winit = { version = "0.27.1", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.27.1", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.27.1", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.27.1", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.27.1", path = "crates/egui_glow", default-features = false } -eframe = { version = "0.27.1", path = "crates/eframe", default-features = false } +emath = { version = "0.27.2", path = "crates/emath", default-features = false } +ecolor = { version = "0.27.2", path = "crates/ecolor", default-features = false } +epaint = { version = "0.27.2", path = "crates/epaint", default-features = false } +egui = { version = "0.27.2", path = "crates/egui", default-features = false } +egui_plot = { version = "0.27.2", path = "crates/egui_plot", default-features = false } +egui-winit = { version = "0.27.2", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.27.2", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.27.2", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.27.2", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.27.2", path = "crates/egui_glow", default-features = false } +eframe = { version = "0.27.2", path = "crates/eframe", default-features = false } #TODO(emilk): make more things workspace dependencies ahash = { version = "0.8.6", default-features = false, features = [ diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index 493d125e7f14..3316a4089444 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Nothing new + + ## 0.27.1 - 2024-03-29 * Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index e110500cb53d..dfea5671a5d2 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,16 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +#### Desktop/Native +* Fix continuous repaint on Wayland when TextEdit is focused or IME output is set [#4269](https://github.com/emilk/egui/pull/4269) (thanks [@white-axe](https://github.com/white-axe)!) +* Remove a bunch of `unwrap()` [#4285](https://github.com/emilk/egui/pull/4285) + +#### Web +* Fix blurry rendering in some browsers [#4299](https://github.com/emilk/egui/pull/4299) +* Correctly identify if the browser tab has focus [#4280](https://github.com/emilk/egui/pull/4280) + + ## 0.27.1 - 2024-03-29 * Web: repaint if the `#hash` in the URL changes [#4261](https://github.com/emilk/egui/pull/4261) * Add web support for `zoom_factor` [#4260](https://github.com/emilk/egui/pull/4260) (thanks [@justusdieckmann](https://github.com/justusdieckmann)!) diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 9dea60d1e0a7..48aa5cdb454e 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Nothing new + + ## 0.27.1 - 2024-03-29 * Nothing new diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index 52dd56e8fddc..20662752609b 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Fix continuous repaint on Wayland when TextEdit is focused or IME output is set [#4269](https://github.com/emilk/egui/pull/4269) (thanks [@white-axe](https://github.com/white-axe)!) + + ## 0.27.1 - 2024-03-29 * Nothing new diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index 0980768aee2c..cc00e7e23240 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Nothing new + + ## 0.27.1 - 2024-03-29 * Nothing new diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index f39d8a199c57..627b1c2ebdb0 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Allow zoom/pan a plot as long as it contains the mouse cursor [#4292](https://github.com/emilk/egui/pull/4292) +* Prevent plot from resetting one axis while zooming/dragging the other [#4252](https://github.com/emilk/egui/pull/4252) (thanks [@YgorSouza](https://github.com/YgorSouza)!) +* egui_plot: Fix the same plot tick label being painted multiple times [#4307](https://github.com/emilk/egui/pull/4307) + + ## 0.27.1 - 2024-03-29 * Nothing new diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index b5fc33f67316..a4f935a38833 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.27.2 - 2024-04-02 +* Nothing new + + ## 0.27.1 - 2024-03-29 * Fix visual glitch on the right side of highly rounded rectangles [#4244](https://github.com/emilk/egui/pull/4244) * Prevent visual glitch when shadow blur width is very high [#4245](https://github.com/emilk/egui/pull/4245) From 2342788973120552c012f028bfda576fea87738b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustav=20S=C3=B6rn=C3=A4s?= Date: Wed, 3 Apr 2024 10:02:29 +0200 Subject: [PATCH 0048/1202] Fix wrong replacement function in deprecation notice of `drag_released*` (#4314) Quick thing I noticed while updating a crate to egui 0.27. --- crates/egui/src/response.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 18598f81a668..1bee1b298608 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -361,13 +361,13 @@ impl Response { /// The widget was being dragged, but now it has been released. #[inline] - #[deprecated = "Renamed 'dragged_stopped'"] + #[deprecated = "Renamed 'drag_stopped'"] pub fn drag_released(&self) -> bool { self.drag_stopped } /// The widget was being dragged by the button, but now it has been released. - #[deprecated = "Renamed 'dragged_stopped_by'"] + #[deprecated = "Renamed 'drag_stopped_by'"] pub fn drag_released_by(&self, button: PointerButton) -> bool { self.drag_stopped_by(button) } From 78d95f430b09e3040d3f383fde8fea2ae1592afb Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Fri, 5 Apr 2024 01:31:33 -0400 Subject: [PATCH 0049/1202] Consider layer transform when positioning text agent (#4319) When positioning the text agent, the layer transform was not being considered. This not only caused issues with IME input positioning but also layout shifts if the text agent was off-screen. Before ![Screenshot 2024-04-04 at 13 33 11@2x](https://github.com/emilk/egui/assets/1410520/5d88a358-67bd-478c-95c9-d76f84d57c39) After ![Screenshot 2024-04-04 at 13 31 52@2x](https://github.com/emilk/egui/assets/1410520/55f068c7-56fe-4ba4-8455-7d0f613e8a52) --- crates/egui/src/widgets/text_edit/builder.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index e0cf2d012f17..f022e860daf5 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -700,11 +700,15 @@ impl<'t> TextEdit<'t> { ); } - // For IME, so only set it when text is editable and visible! + // Set IME output (in screen coords) when text is editable and visible + let transform = ui + .memory(|m| m.layer_transforms.get(&ui.layer_id()).cloned()) + .unwrap_or_default(); + ui.ctx().output_mut(|o| { o.ime = Some(crate::output::IMEOutput { - rect, - cursor_rect: primary_cursor_rect, + rect: transform * rect, + cursor_rect: transform * primary_cursor_rect, }); }); } From 79fbd17b338c22e09a44517407ecb7490b3ee69d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 18 Apr 2024 16:08:23 +0200 Subject: [PATCH 0050/1202] Add link to layouting tracking issue --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2f4d5d19a833..924d509bd80b 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,8 @@ You can also call the layout code twice (once to get the size, once to do the in For "atomic" widgets (e.g. a button) `egui` knows the size before showing it, so centering buttons, labels etc is possible in `egui` without any special workarounds. +See [this issue](https://github.com/emilk/egui/issues/4378) for more. + #### CPU usage Since an immediate mode GUI does a full layout each frame, the layout code needs to be quick. If you have a very complex GUI this can tax the CPU. In particular, having a very large UI in a scroll area (with very long scrollback) can be slow, as the content needs to be laid out each frame. From c630a8de894b28bf1774f798b1b1fe90a759ee42 Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Sun, 21 Apr 2024 04:58:40 -0400 Subject: [PATCH 0051/1202] Fix incorrect line breaks (#4377) While breaking a paragraph, it was possible to lose line break candidates that could've been used on the next line, causing egui to unnecessarily overrun `wrap.max_width`. This PR fixes it so that we don't forget about those candidates. Before: Note that the window can't resize to the requested width because the text is not wrapping. https://github.com/emilk/egui/assets/1410520/6430a334-2995-4b40-bc34-8f01923f9f95 After: https://github.com/emilk/egui/assets/1410520/225fa4cd-cbbb-4a7e-9580-7f1814c05ee7 --------- Co-authored-by: Emil Ernerfeldt --- crates/epaint/src/text/text_layout.rs | 31 ++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 322026da3c51..c9956aac1976 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -296,7 +296,7 @@ fn line_break(paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, e // Start a new row: row_start_idx = last_kept_index + 1; row_start_x = paragraph.glyphs[row_start_idx].pos.x; - row_break_candidates = Default::default(); + row_break_candidates.forget_before_idx(row_start_idx); } else { // Found no place to break, so we have to overrun wrap_width. } @@ -943,6 +943,35 @@ impl RowBreakCandidates { .or(self.any) } } + + fn forget_before_idx(&mut self, index: usize) { + let Self { + space, + cjk, + pre_cjk, + dash, + punctuation, + any, + } = self; + if space.map_or(false, |s| s < index) { + *space = None; + } + if cjk.map_or(false, |s| s < index) { + *cjk = None; + } + if pre_cjk.map_or(false, |s| s < index) { + *pre_cjk = None; + } + if dash.map_or(false, |s| s < index) { + *dash = None; + } + if punctuation.map_or(false, |s| s < index) { + *punctuation = None; + } + if any.map_or(false, |s| s < index) { + *any = None; + } + } } #[inline] From 89d7f9f9d3237b5d02d6bce87f6804d969b7e8f1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 21 Apr 2024 11:05:44 +0200 Subject: [PATCH 0052/1202] Update cargo-deny and some dependencies (#4386) * Closes https://github.com/emilk/egui/issues/4382 --- Cargo.lock | 120 ++++++++++++++++++++++++++++++++++++++++------------- deny.toml | 55 ++++++++++++++++-------- 2 files changed, 129 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6d8727188a7..112c7d78ef12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2286,12 +2286,9 @@ dependencies = [ [[package]] name = "line-wrap" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", -] +checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" [[package]] name = "linked-hash-map" @@ -2798,9 +2795,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" +checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" dependencies = [ "base64", "indexmap", @@ -3158,17 +3155,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.20" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", + "getrandom", "libc", - "once_cell", "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -3230,9 +3227,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" dependencies = [ "log", "ring", @@ -3242,9 +3239,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.6" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", "untrusted", @@ -3256,12 +3253,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "same-file" version = "1.0.6" @@ -3305,9 +3296,9 @@ dependencies = [ [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", "untrusted", @@ -3507,9 +3498,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spirv" @@ -3898,9 +3889,9 @@ checksum = "446c96c6dd42604779487f0a981060717156648c1706aa1f464677f03c6cc059" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" @@ -4444,6 +4435,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -4474,6 +4474,22 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -4486,6 +4502,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -4498,6 +4520,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -4510,6 +4538,18 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -4522,6 +4562,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -4534,6 +4580,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -4546,6 +4598,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -4558,6 +4616,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + [[package]] name = "winit" version = "0.29.10" diff --git a/deny.toml b/deny.toml index 17c47772833e..ebe372744dd7 100644 --- a/deny.toml +++ b/deny.toml @@ -1,7 +1,17 @@ -# https://embarkstudios.github.io/cargo-deny/ +# Copied from https://github.com/rerun-io/rerun_template +# +# https://github.com/EmbarkStudios/cargo-deny +# +# cargo-deny checks our dependency tree for copy-left licenses, +# duplicate dependencies, and rustsec advisories (https://rustsec.org/advisories). +# +# Install: `cargo install cargo-deny` +# Check: `cargo deny check`. + # Note: running just `cargo deny check` without a `--target` can result in # false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324 +[graph] targets = [ { triple = "aarch64-apple-darwin" }, { triple = "i686-pc-windows-gnu" }, @@ -15,26 +25,29 @@ targets = [ { triple = "x86_64-unknown-linux-musl" }, { triple = "x86_64-unknown-redox" }, ] +all-features = true + [advisories] -vulnerability = "deny" -unmaintained = "warn" -yanked = "deny" +version = 2 ignore = [ - "RUSTSEC-2020-0071", # https://rustsec.org/advisories/RUSTSEC-2020-0071 - chrono/time: Potential segfault in the time crate + "RUSTSEC-2024-0320", # unmaintaines yaml-rust pulled in by syntect + { name = "async-process" }, # yanked crated pulled in by old accesskit ] + [bans] multiple-versions = "deny" -wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed +wildcards = "deny" deny = [ - { name = "cmake" }, # Lord no - { name = "openssl-sys" }, # prefer rustls - { name = "openssl" }, # prefer rustls + { name = "cmake", reason = "It has hurt me too much" }, + { name = "openssl-sys", reason = "Use rustls" }, + { name = "openssl", reason = "Use rustls" }, ] skip = [ { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … + { name = "event-listener" }, # TODO(emilk): rustls pulls in two versions of this 😭 { name = "libloading" }, # wgpu-hal itself depends on 0.8 while some of its dependencies, like ash and d3d12, depend on 0.7 { name = "memoffset" }, # tiny dependency { name = "quick-xml" }, # old version via wayland-scanner @@ -42,8 +55,8 @@ skip = [ { name = "spin" }, # old version via ring through rusttls and other libraries, newer for wgpu. { name = "time" }, # old version pulled in by unmaintianed crate 'chrono' { name = "windows" }, # old version via accesskit_windows - { name = "x11rb" }, # old version via arboard { name = "x11rb-protocol" }, # old version via arboard + { name = "x11rb" }, # old version via arboard ] skip-tree = [ { name = "criterion" }, # dev-dependency @@ -56,10 +69,9 @@ skip-tree = [ [licenses] -unlicensed = "deny" -allow-osi-fsf-free = "neither" -confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text -copyleft = "deny" +version = 2 +private = { ignore = true } +confidence-threshold = 0.93 # We want really high confidence when inferring licenses from text allow = [ "Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) @@ -67,15 +79,17 @@ allow = [ "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) "BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained "CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/ - "ISC", # https://tldrlegal.com/license/-isc-license - "LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321 + "ISC", # https://www.tldrlegal.com/license/isc-license + "LicenseRef-UFL-1.0", # no official SPDX, see https://github.com/emilk/egui/issues/2321 + "MIT-0", # https://choosealicense.com/licenses/mit-0/ "MIT", # https://tldrlegal.com/license/mit-license - "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11 + "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux. "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html - "OpenSSL", # https://www.openssl.org/source/license.html + "OpenSSL", # https://www.openssl.org/source/license.html - used on Linux "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) ] +exceptions = [] [[licenses.clarify]] name = "webpki" @@ -86,3 +100,8 @@ license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] name = "ring" expression = "MIT AND ISC AND OpenSSL" license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] + + +[sources] +unknown-registry = "deny" +unknown-git = "deny" From 26c97a19a414f7b2060ead74b734ba71724b292c Mon Sep 17 00:00:00 2001 From: Narcha <42248344+Narcha@users.noreply.github.com> Date: Sun, 21 Apr 2024 11:05:53 +0200 Subject: [PATCH 0053/1202] Expose `ClosestElem` and `PlotConfig` (#4380) These two items are needed to implement the `PlotItem` trait (which is already public) when using `PlotGeometry::Rects`. Specifically, they are used in the return type of `PlotItem::find_closest` and as arguments to `PlotItem::on_hover`, which need to be implemented when using `PlotGeometry::Rects`. --- crates/egui_plot/src/items/mod.rs | 7 ++++--- crates/egui_plot/src/lib.rs | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 78f4560c6d35..ddf09b98504f 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -9,11 +9,12 @@ use crate::*; use super::{Cursor, LabelFormatter, PlotBounds, PlotTransform}; use rect_elem::*; -use values::ClosestElem; pub use bar::Bar; pub use box_elem::{BoxElem, BoxSpread}; -pub use values::{LineStyle, MarkerShape, Orientation, PlotGeometry, PlotPoint, PlotPoints}; +pub use values::{ + ClosestElem, LineStyle, MarkerShape, Orientation, PlotGeometry, PlotPoint, PlotPoints, +}; mod bar; mod box_elem; @@ -45,7 +46,7 @@ pub trait PlotItem { fn highlighted(&self) -> bool; - /// Can the user hover this is item? + /// Can the user hover this item? fn allow_hover(&self) -> bool; fn geometry(&self) -> PlotGeometry<'_>; diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index a5adf1c11c13..9de1f7ce705a 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -22,9 +22,9 @@ use epaint::{util::FloatOrd, Hsva}; pub use crate::{ axis::{Axis, AxisHints, HPlacement, Placement, VPlacement}, items::{ - Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape, - Orientation, PlotGeometry, PlotImage, PlotItem, PlotPoint, PlotPoints, Points, Polygon, - Text, VLine, + Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, ClosestElem, HLine, Line, LineStyle, + MarkerShape, Orientation, PlotConfig, PlotGeometry, PlotImage, PlotItem, PlotPoint, + PlotPoints, Points, Polygon, Text, VLine, }, legend::{Corner, Legend}, memory::PlotMemory, From fe454573dbcc812302009d5e8ba97d7553d8c8a2 Mon Sep 17 00:00:00 2001 From: dataphract <86984145+dataphract@users.noreply.github.com> Date: Sun, 21 Apr 2024 04:07:55 -0500 Subject: [PATCH 0054/1202] Fix `hex_color!` macro by re-exporting `color_hex` crate from `ecolor` (#4372) The `hex_color!` macro currently makes an unqualified reference to the `color_hex` crate, which users may not have in their `Cargo.toml`. This PR re-exports `color_hex` from the root of `ecolor` and changes the macro to use the re-exported path. * Closes https://github.com/emilk/egui/issues/2644 --- crates/ecolor/src/hex_color_macro.rs | 2 +- crates/ecolor/src/lib.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/ecolor/src/hex_color_macro.rs b/crates/ecolor/src/hex_color_macro.rs index 16f8cc2b8d8c..a0a0729fdd15 100644 --- a/crates/ecolor/src/hex_color_macro.rs +++ b/crates/ecolor/src/hex_color_macro.rs @@ -13,7 +13,7 @@ #[macro_export] macro_rules! hex_color { ($s:literal) => {{ - let array = color_hex::color_from_hex!($s); + let array = $crate::color_hex::color_from_hex!($s); if array.len() == 3 { $crate::Color32::from_rgb(array[0], array[1], array[2]) } else { diff --git a/crates/ecolor/src/lib.rs b/crates/ecolor/src/lib.rs index e3c077edd502..a7072d8e89a3 100644 --- a/crates/ecolor/src/lib.rs +++ b/crates/ecolor/src/lib.rs @@ -24,6 +24,9 @@ pub use hsva::*; #[cfg(feature = "color-hex")] mod hex_color_macro; +#[cfg(feature = "color-hex")] +#[doc(hidden)] +pub use color_hex; mod rgba; pub use rgba::*; From 5f9c17c85549833b13a55401a8de4295da475282 Mon Sep 17 00:00:00 2001 From: Varphone Wong Date: Sun, 21 Apr 2024 17:36:18 +0800 Subject: [PATCH 0055/1202] `egui`: Change `Ui::allocate_painter` to inherit properties from `Ui` (#4343) The painter, allocated by `Ui::allocate_painter`, doesn't inherit properties from the `Ui` object, leading to improper widget rendering in `egui`. Specifically, if the `Ui` object is disabled, the corresponding painter should also be disabled. --- crates/egui/src/ui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 9ff05a267e87..d160f5d404d0 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1001,7 +1001,7 @@ impl Ui { pub fn allocate_painter(&mut self, desired_size: Vec2, sense: Sense) -> (Response, Painter) { let response = self.allocate_response(desired_size, sense); let clip_rect = self.clip_rect().intersect(response.rect); // Make sure we don't paint out of bounds - let painter = Painter::new(self.ctx().clone(), self.layer_id(), clip_rect); + let painter = self.painter().with_clip_rect(clip_rect); (response, painter) } From 690c3ba883efe6e3890f59f4ea40a25e7a9e963b Mon Sep 17 00:00:00 2001 From: Alexander Parlett Date: Sun, 21 Apr 2024 10:44:44 +0100 Subject: [PATCH 0056/1202] Use parent `Ui`s style for popups (#4325) * Closes --------- Co-authored-by: Alex Parlett --- .gitignore | 1 + crates/egui/src/containers/popup.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index c43b95594e4d..7db0b9d06fb9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /.*.json /.vscode /media/* +.idea/ diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 99165149f1d6..9643a8eee31c 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -333,13 +333,13 @@ pub fn popup_below_widget( /// # }); /// ``` pub fn popup_above_or_below_widget( - ui: &Ui, + parent_ui: &Ui, popup_id: Id, widget_response: &Response, above_or_below: AboveOrBelow, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option { - if ui.memory(|mem| mem.is_popup_open(popup_id)) { + if parent_ui.memory(|mem| mem.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), @@ -350,8 +350,8 @@ pub fn popup_above_or_below_widget( .constrain(true) .fixed_pos(pos) .pivot(pivot) - .show(ui.ctx(), |ui| { - let frame = Frame::popup(ui.style()); + .show(parent_ui.ctx(), |ui| { + let frame = Frame::popup(parent_ui.style()); let frame_margin = frame.total_margin(); frame .show(ui, |ui| { @@ -365,8 +365,8 @@ pub fn popup_above_or_below_widget( }) .inner; - if ui.input(|i| i.key_pressed(Key::Escape)) || widget_response.clicked_elsewhere() { - ui.memory_mut(|mem| mem.close_popup()); + if parent_ui.input(|i| i.key_pressed(Key::Escape)) || widget_response.clicked_elsewhere() { + parent_ui.memory_mut(|mem| mem.close_popup()); } Some(inner) } else { From d2c426924039fbd8b73be51dea01a5db0911238c Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sun, 21 Apr 2024 18:49:14 +0900 Subject: [PATCH 0057/1202] Fix : take `rounding` into account when using `Slider::trailing_fill` (#4308) Handles `rounding` when doing trailing_fill on the rail of `Slider`. We can see this by setting the rounding to around 8.0 ``` ui.visuals_mut().widgets.inactive.rounding = Rounding::same(8.0); ``` Before : There is a little bit of blue painted on the left end. ![20240404-2](https://github.com/emilk/egui/assets/127506429/aa70104c-0733-41c6-8e78-c3e69eb45204) After : Fix ![20240404-3](https://github.com/emilk/egui/assets/127506429/c2452fcb-48fd-4b2a-9f1a-02a3bf763ed1) --- crates/egui/src/widgets/slider.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index a428fb7dc4da..f303464a4feb 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -686,12 +686,10 @@ impl<'a> Slider<'a> { let rail_radius = (spacing.slider_rail_height / 2.0).at_least(0.0); let rail_rect = self.rail_rect(rect, rail_radius); + let rounding = widget_visuals.inactive.rounding; - ui.painter().rect_filled( - rail_rect, - widget_visuals.inactive.rounding, - widget_visuals.inactive.bg_fill, - ); + ui.painter() + .rect_filled(rail_rect, rounding, widget_visuals.inactive.bg_fill); let position_1d = self.position_from_value(value, position_range); let center = self.marker_center(position_1d, &rail_rect); @@ -707,13 +705,17 @@ impl<'a> Slider<'a> { // The trailing rect has to be drawn differently depending on the orientation. match self.orientation { - SliderOrientation::Vertical => trailing_rail_rect.min.y = center.y, - SliderOrientation::Horizontal => trailing_rail_rect.max.x = center.x, + SliderOrientation::Horizontal => { + trailing_rail_rect.max.x = center.x + rounding.nw; + } + SliderOrientation::Vertical => { + trailing_rail_rect.min.y = center.y - rounding.se; + } }; ui.painter().rect_filled( trailing_rail_rect, - widget_visuals.inactive.rounding, + rounding, ui.visuals().selection.bg_fill, ); } From d68c8d70aa1dd5c44bd1a83a9f6aa8247e7c5c3b Mon Sep 17 00:00:00 2001 From: valadaptive <79560998+valadaptive@users.noreply.github.com> Date: Sun, 21 Apr 2024 06:23:59 -0400 Subject: [PATCH 0058/1202] Add a way to specify Undoer settings and construct Undoers more easily (#4357) * Closes https://github.com/emilk/egui/issues/4356 - Add `Undoer::new()` - This is necessary to construct an `Undoer` whose `State` parameter doesn't implement `Default`. - Add `Undoer::with_settings(...)` - This is necessary to actually pass settings into the `Undoer`. Without this, API consumers could construct their own `Settings` but not actually do anything with it. I've refrained from adding any kind of builder API for `Settings` because there are only three options and I don't want to duplicate or move all the documentation onto the builder methods. --- crates/egui/src/util/undoer.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/util/undoer.rs b/crates/egui/src/util/undoer.rs index de6d27161715..d5f004f86ae5 100644 --- a/crates/egui/src/util/undoer.rs +++ b/crates/egui/src/util/undoer.rs @@ -47,7 +47,7 @@ impl Default for Settings { /// /// Rule 1) will make sure an undo point is not created until you _stop_ dragging that slider. /// Rule 2) will make sure that you will get some undo points even if you are constantly changing the state. -#[derive(Clone, Default)] +#[derive(Clone)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Undoer { settings: Settings, @@ -77,6 +77,21 @@ impl std::fmt::Debug for Undoer { } } +impl Default for Undoer +where + State: Clone + PartialEq, +{ + #[inline] + fn default() -> Self { + Self { + settings: Settings::default(), + undos: VecDeque::new(), + redos: Vec::new(), + flux: None, + } + } +} + /// Represents how the current state is changing #[derive(Clone)] struct Flux { @@ -89,6 +104,14 @@ impl Undoer where State: Clone + PartialEq, { + /// Create a new [`Undoer`] with the given [`Settings`]. + pub fn with_settings(settings: Settings) -> Self { + Self { + settings, + ..Default::default() + } + } + /// Do we have an undo point different from the given state? pub fn has_undo(&self, current_state: &State) -> bool { match self.undos.len() { From 46b241eb94f6bc9234c20d69159c5b4c5f784add Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:26:16 +0200 Subject: [PATCH 0059/1202] Add `xtask` crate (#4293) Replaces only the cargo_deny.sh script for now. Can be expanded over time to replace the other shell and python scripts, so only Rust is needed to work with the repository. Closes Closes --------- Co-authored-by: Emil Ernerfeldt --- .cargo/config.toml | 3 +++ Cargo.lock | 4 +++ Cargo.toml | 2 ++ scripts/cargo_deny.sh | 20 +-------------- xtask/Cargo.toml | 9 +++++++ xtask/README.md | 12 +++++++++ xtask/src/deny.rs | 60 +++++++++++++++++++++++++++++++++++++++++++ xtask/src/main.rs | 40 +++++++++++++++++++++++++++++ xtask/src/utils.rs | 45 ++++++++++++++++++++++++++++++++ 9 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 xtask/Cargo.toml create mode 100644 xtask/README.md create mode 100644 xtask/src/deny.rs create mode 100644 xtask/src/main.rs create mode 100644 xtask/src/utils.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index be614b002368..b18b6ce735d9 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,3 +4,6 @@ # we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93 [target.wasm32-unknown-unknown] rustflags = ["--cfg=web_sys_unstable_apis"] + +[alias] +xtask = "run --quiet --package xtask --" diff --git a/Cargo.lock b/Cargo.lock index 112c7d78ef12..8698db26e7a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4762,6 +4762,10 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "xtask" +version = "0.27.2" + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 598eae08cb1d..425eb8de2085 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ members = [ "crates/epaint", "examples/*", + + "xtask", ] [workspace.package] diff --git a/scripts/cargo_deny.sh b/scripts/cargo_deny.sh index 560b8efd1bbc..bf811e852041 100755 --- a/scripts/cargo_deny.sh +++ b/scripts/cargo_deny.sh @@ -1,21 +1,3 @@ #!/usr/bin/env bash -set -eu -script_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) -cd "$script_path/.." -set -x - -cargo install --quiet cargo-deny - -cargo deny --all-features --log-level error --target aarch64-apple-darwin check -cargo deny --all-features --log-level error --target aarch64-linux-android check -cargo deny --all-features --log-level error --target i686-pc-windows-gnu check -cargo deny --all-features --log-level error --target i686-pc-windows-msvc check -cargo deny --all-features --log-level error --target i686-unknown-linux-gnu check -cargo deny --all-features --log-level error --target wasm32-unknown-unknown check -cargo deny --all-features --log-level error --target x86_64-apple-darwin check -cargo deny --all-features --log-level error --target x86_64-pc-windows-gnu check -cargo deny --all-features --log-level error --target x86_64-pc-windows-msvc check -cargo deny --all-features --log-level error --target x86_64-unknown-linux-gnu check -cargo deny --all-features --log-level error --target x86_64-unknown-linux-musl check -cargo deny --all-features --log-level error --target x86_64-unknown-redox check +cargo xtask deny diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 000000000000..ba5b93820892 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "xtask" +edition.workspace = true +license.workspace = true +rust-version.workspace = true +version.workspace = true +publish = false + +[dependencies] diff --git a/xtask/README.md b/xtask/README.md new file mode 100644 index 000000000000..bb5e92777627 --- /dev/null +++ b/xtask/README.md @@ -0,0 +1,12 @@ +## xtask - Task automation + +This crate is meant to automate common tasks on the repository. It serves as a +replacement for shell scripts that is more portable across host operating +systems (namely Windows) and hopefully also easier to work with for +contributors who are already familiar with Rust (and not necessarily with shell +scripting). + +The executable can be invoked via the subcommand `cargo xtask`, thanks to an +alias defined in `.cargo/config.toml`. + +For more information, see . diff --git a/xtask/src/deny.rs b/xtask/src/deny.rs new file mode 100644 index 000000000000..5c24949bd439 --- /dev/null +++ b/xtask/src/deny.rs @@ -0,0 +1,60 @@ +//! Run `cargo deny` +//! +//! Also installs the subcommand if it is not already installed. + +use std::process::Command; + +use super::DynError; + +pub fn deny(args: &[&str]) -> Result<(), DynError> { + if !args.is_empty() { + return Err(format!("Invalid arguments: {args:?}").into()); + } + install_cargo_deny()?; + let targets = [ + "aarch64-apple-darwin", + "aarch64-linux-android", + "i686-pc-windows-gnu", + "i686-pc-windows-msvc", + "i686-unknown-linux-gnu", + "wasm32-unknown-unknown", + "x86_64-apple-darwin", + "x86_64-pc-windows-gnu", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "x86_64-unknown-redox", + ]; + for target in targets { + let mut cmd = Command::new("cargo"); + cmd.args([ + "deny", + "--all-features", + "--log-level", + "error", + "--target", + target, + "check", + ]); + super::utils::print_cmd(&cmd); + let status = cmd.status()?; + if !status.success() { + return Err(status.to_string().into()); + } + } + Ok(()) +} + +fn install_cargo_deny() -> Result<(), DynError> { + let already_installed = Command::new("cargo") + .args(["deny", "--version"]) + .output() + .is_ok_and(|out| out.status.success()); + if already_installed { + return Ok(()); + } + let mut cmd = Command::new("cargo"); + cmd.args(["+stable", "install", "--quiet", "--locked", "cargo-deny"]); + let reason = "install cargo-deny"; + super::utils::ask_to_run(cmd, true, reason) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 000000000000..b194629203a6 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,40 @@ +#![allow(clippy::print_stdout)] +#![allow(clippy::print_stderr)] +#![allow(clippy::exit)] + +mod deny; +pub(crate) mod utils; + +type DynError = Box; + +fn main() { + if let Err(e) = try_main() { + eprintln!("{e}"); + std::process::exit(-1); + } +} + +fn try_main() -> Result<(), DynError> { + let arg_strings: Vec<_> = std::env::args().skip(1).collect(); + let args: Vec<_> = arg_strings.iter().map(String::as_str).collect(); + + match args.as_slice() { + &[] | &["-h"] | &["--help"] => print_help(), + &["deny", ..] => deny::deny(&args[1..])?, + c => Err(format!("Invalid arguments {c:?}"))?, + } + Ok(()) +} + +fn print_help() { + let help = " + xtask help + + Subcommands + deny: Run cargo-deny for all targets + + Options + -h, --help: print help and exit + "; + println!("{help}"); +} diff --git a/xtask/src/utils.rs b/xtask/src/utils.rs new file mode 100644 index 000000000000..760c27f8f147 --- /dev/null +++ b/xtask/src/utils.rs @@ -0,0 +1,45 @@ +use std::{ + env, + io::{self, Write as _}, + process::Command, +}; + +use super::DynError; + +/// Print the command and its arguments as if the user had typed them +pub fn print_cmd(cmd: &Command) { + print!("{} ", cmd.get_program().to_string_lossy()); + for arg in cmd.get_args() { + print!("{} ", arg.to_string_lossy()); + } + println!(); +} + +/// Prompt user before running a command +/// +/// Adapted from [miri](https://github.com/rust-lang/miri/blob/dba35d2be72f4b78343d1a0f0b4737306f310672/cargo-miri/src/util.rs#L181-L204) +pub fn ask_to_run(mut cmd: Command, ask: bool, reason: &str) -> Result<(), DynError> { + // Disable interactive prompts in CI (GitHub Actions, Travis, AppVeyor, etc). + // Azure doesn't set `CI` though (nothing to see here, just Microsoft being Microsoft), + // so we also check their `TF_BUILD`. + let is_ci = env::var_os("CI").is_some() || env::var_os("TF_BUILD").is_some(); + if ask && !is_ci { + let mut buf = String::new(); + print!("The script is going to run: \n\n`{cmd:?}`\n\n To {reason}.\nProceed? [Y/n] ",); + io::stdout().flush().unwrap(); + io::stdin().read_line(&mut buf).unwrap(); + match buf.trim().to_lowercase().as_ref() { + "" | "y" | "yes" => {} + "n" | "no" => return Err("Aborting as per your request".into()), + a => return Err(format!("Invalid answer `{a}`").into()), + }; + } else { + eprintln!("Running `{cmd:?}` to {reason}."); + } + + let status = cmd.status()?; + if !status.success() { + return Err(format!("failed to {reason}: {status}").into()); + } + Ok(()) +} From 87b294534e4339fe7fe55f23b2b04df2e13f3f0e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sun, 21 Apr 2024 20:36:32 +0200 Subject: [PATCH 0060/1202] Add `emath::OrderedFloat` (moved from `epaint::util::OrderedFloat`) (#4389) It makes much more sense in `emath` --- Cargo.lock | 1 + crates/ecolor/src/rgba.rs | 1 + crates/egui/Cargo.toml | 1 + crates/egui/src/load.rs | 20 ++++----- crates/egui/src/widgets/image.rs | 8 ++-- crates/egui_plot/src/items/mod.rs | 2 +- crates/egui_plot/src/lib.rs | 3 +- crates/emath/src/lib.rs | 4 +- .../src/util => emath/src}/ordered_float.rs | 41 +++++++++++-------- crates/epaint/src/lib.rs | 26 ------------ crates/epaint/src/stroke.rs | 2 +- crates/epaint/src/text/fonts.rs | 23 ++--------- crates/epaint/src/text/text_layout_types.rs | 10 ++--- crates/epaint/src/util/mod.rs | 5 +-- 14 files changed, 57 insertions(+), 90 deletions(-) rename crates/{epaint/src/util => emath/src}/ordered_float.rs (79%) diff --git a/Cargo.lock b/Cargo.lock index 8698db26e7a2..d64d54397f14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1242,6 +1242,7 @@ dependencies = [ "ahash", "backtrace", "document-features", + "emath", "epaint", "log", "nohash-hasher", diff --git a/crates/ecolor/src/rgba.rs b/crates/ecolor/src/rgba.rs index 2a61d311ee47..36cb33bd5992 100644 --- a/crates/ecolor/src/rgba.rs +++ b/crates/ecolor/src/rgba.rs @@ -26,6 +26,7 @@ impl std::ops::IndexMut for Rgba { } } +/// Deterministically hash an `f32`, treating all NANs as equal, and ignoring the sign of zero. #[inline] pub(crate) fn f32_hash(state: &mut H, f: f32) { if f == 0.0 { diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 347dbeda7cba..87b25a7ea4b7 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -81,6 +81,7 @@ unity = ["epaint/unity"] [dependencies] +emath = { workspace = true, default-features = false } epaint = { workspace = true, default-features = false } ahash.workspace = true diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index 4d527de63be4..71d9d086f562 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -55,23 +55,21 @@ mod bytes_loader; mod texture_loader; -use std::borrow::Cow; -use std::fmt::Debug; -use std::ops::Deref; -use std::{fmt::Display, sync::Arc}; +use std::{ + borrow::Cow, + fmt::{Debug, Display}, + ops::Deref, + sync::Arc, +}; use ahash::HashMap; -use epaint::mutex::Mutex; -use epaint::util::FloatOrd; -use epaint::util::OrderedFloat; -use epaint::TextureHandle; -use epaint::{textures::TextureOptions, ColorImage, TextureId, Vec2}; +use emath::{Float, OrderedFloat}; +use epaint::{mutex::Mutex, textures::TextureOptions, ColorImage, TextureHandle, TextureId, Vec2}; use crate::Context; -pub use self::bytes_loader::DefaultBytesLoader; -pub use self::texture_loader::DefaultTextureLoader; +pub use self::{bytes_loader::DefaultBytesLoader, texture_loader::DefaultTextureLoader}; /// Represents a failed attempt at loading an image. #[derive(Clone, Debug)] diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 945c497dd4e9..15b9dcddb2ee 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -1,12 +1,12 @@ use std::borrow::Cow; -use crate::load::TextureLoadResult; +use emath::{Float as _, Rot2}; +use epaint::RectShape; + use crate::{ - load::{Bytes, SizeHint, SizedTexture, TexturePoll}, + load::{Bytes, SizeHint, SizedTexture, TextureLoadResult, TexturePoll}, *, }; -use emath::Rot2; -use epaint::{util::FloatOrd, RectShape}; /// A widget which displays an image. /// diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index ddf09b98504f..56098b366998 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -3,7 +3,7 @@ use std::ops::RangeInclusive; -use epaint::{emath::Rot2, util::FloatOrd, Mesh}; +use epaint::{emath::Rot2, Mesh}; use crate::*; diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 9de1f7ce705a..2e31f8312f53 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -17,7 +17,8 @@ use std::{cmp::Ordering, ops::RangeInclusive, sync::Arc}; use egui::ahash::HashMap; use egui::*; -use epaint::{util::FloatOrd, Hsva}; +use emath::Float as _; +use epaint::Hsva; pub use crate::{ axis::{Axis, AxisHints, HPlacement, Placement, VPlacement}, diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 911fb52a044f..d3e2f5e0313f 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -30,6 +30,7 @@ use std::ops::{Add, Div, Mul, RangeInclusive, Sub}; pub mod align; mod history; mod numeric; +mod ordered_float; mod pos2; mod range; mod rect; @@ -40,10 +41,11 @@ mod ts_transform; mod vec2; mod vec2b; -pub use { +pub use self::{ align::{Align, Align2}, history::History, numeric::*, + ordered_float::*, pos2::*, range::Rangef, rect::*, diff --git a/crates/epaint/src/util/ordered_float.rs b/crates/emath/src/ordered_float.rs similarity index 79% rename from crates/epaint/src/util/ordered_float.rs rename to crates/emath/src/ordered_float.rs index 72f06a20a397..950ec16ac2c7 100644 --- a/crates/epaint/src/util/ordered_float.rs +++ b/crates/emath/src/ordered_float.rs @@ -7,9 +7,12 @@ use std::hash::{Hash, Hasher}; /// Wraps a floating-point value to add total order and hash. /// Possible types for `T` are `f32` and `f64`. /// -/// See also [`FloatOrd`]. +/// All NaNs are considered equal to each other. +/// The size of zero is ignored. +/// +/// See also [`Float`]. #[derive(Clone, Copy)] -pub struct OrderedFloat(T); +pub struct OrderedFloat(pub T); impl OrderedFloat { #[inline] @@ -68,44 +71,34 @@ impl From for OrderedFloat { /// /// Example with `f64`: /// ``` -/// use epaint::util::FloatOrd; +/// use emath::Float as _; /// /// let array = [1.0, 2.5, 2.0]; /// let max = array.iter().max_by_key(|val| val.ord()); /// /// assert_eq!(max, Some(&2.5)); /// ``` -pub trait FloatOrd { +pub trait Float: PartialOrd + PartialEq + private::FloatImpl { /// Type to provide total order, useful as key in sorted contexts. fn ord(self) -> OrderedFloat where Self: Sized; } -impl FloatOrd for f32 { +impl Float for f32 { #[inline] fn ord(self) -> OrderedFloat { OrderedFloat(self) } } -impl FloatOrd for f64 { +impl Float for f64 { #[inline] fn ord(self) -> OrderedFloat { OrderedFloat(self) } } -// ---------------------------------------------------------------------------- - -/// Internal abstraction over floating point types -#[doc(hidden)] -pub trait Float: PartialOrd + PartialEq + private::FloatImpl {} - -impl Float for f32 {} - -impl Float for f64 {} - // Keep this trait in private module, to avoid exposing its methods as extensions in user code mod private { use super::*; @@ -124,7 +117,13 @@ mod private { #[inline] fn hash(&self, state: &mut H) { - crate::f32_hash(state, *self); + if *self == 0.0 { + state.write_u8(0); + } else if self.is_nan() { + state.write_u8(1); + } else { + self.to_bits().hash(state); + } } } @@ -136,7 +135,13 @@ mod private { #[inline] fn hash(&self, state: &mut H) { - crate::f64_hash(state, *self); + if *self == 0.0 { + state.write_u8(0); + } else if self.is_nan() { + state.write_u8(1); + } else { + self.to_bits().hash(state); + } } } } diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 182984846796..f7ee04cb75d4 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -152,32 +152,6 @@ macro_rules! epaint_assert { } } -// ---------------------------------------------------------------------------- - -#[inline(always)] -pub(crate) fn f32_hash(state: &mut H, f: f32) { - if f == 0.0 { - state.write_u8(0); - } else if f.is_nan() { - state.write_u8(1); - } else { - use std::hash::Hash; - f.to_bits().hash(state); - } -} - -#[inline(always)] -pub(crate) fn f64_hash(state: &mut H, f: f64) { - if f == 0.0 { - state.write_u8(0); - } else if f.is_nan() { - state.write_u8(1); - } else { - use std::hash::Hash; - f.to_bits().hash(state); - } -} - // --------------------------------------------------------------------------- /// Was epaint compiled with the `rayon` feature? diff --git a/crates/epaint/src/stroke.rs b/crates/epaint/src/stroke.rs index 15ddd231f1b8..9dafef31ddcf 100644 --- a/crates/epaint/src/stroke.rs +++ b/crates/epaint/src/stroke.rs @@ -48,7 +48,7 @@ impl std::hash::Hash for Stroke { #[inline(always)] fn hash(&self, state: &mut H) { let Self { width, color } = *self; - crate::f32_hash(state, width); + emath::OrderedFloat(width).hash(state); color.hash(state); } } diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 557d71733c82..394b898291e6 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -8,7 +8,7 @@ use crate::{ }, TextureAtlas, }; -use emath::NumExt as _; +use emath::{NumExt as _, OrderedFloat}; // ---------------------------------------------------------------------------- @@ -56,7 +56,7 @@ impl std::hash::Hash for FontId { #[inline(always)] fn hash(&self, state: &mut H) { let Self { size, family } = self; - crate::f32_hash(state, *size); + emath::OrderedFloat(*size).hash(state); family.hash(state); } } @@ -567,21 +567,6 @@ impl FontsAndCache { // ---------------------------------------------------------------------------- -#[derive(Clone, Copy, Debug, PartialEq)] -struct HashableF32(f32); - -#[allow(clippy::derived_hash_with_manual_eq)] -impl std::hash::Hash for HashableF32 { - #[inline(always)] - fn hash(&self, state: &mut H) { - crate::f32_hash(state, self.0); - } -} - -impl Eq for HashableF32 {} - -// ---------------------------------------------------------------------------- - /// The collection of fonts used by `epaint`. /// /// Required in order to paint text. @@ -591,7 +576,7 @@ pub struct FontsImpl { definitions: FontDefinitions, atlas: Arc>, font_impl_cache: FontImplCache, - sized_family: ahash::HashMap<(HashableF32, FontFamily), Font>, + sized_family: ahash::HashMap<(OrderedFloat, FontFamily), Font>, } impl FontsImpl { @@ -641,7 +626,7 @@ impl FontsImpl { let FontId { size, family } = font_id; self.sized_family - .entry((HashableF32(*size), family.clone())) + .entry((OrderedFloat(*size), family.clone())) .or_insert_with(|| { let fonts = &self.definitions.families.get(family); let fonts = fonts diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 70317f771f6d..5e4a56d9502e 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -185,7 +185,7 @@ impl std::hash::Hash for LayoutJob { text.hash(state); sections.hash(state); wrap.hash(state); - crate::f32_hash(state, *first_row_min_height); + emath::OrderedFloat(*first_row_min_height).hash(state); break_on_newline.hash(state); halign.hash(state); justify.hash(state); @@ -214,7 +214,7 @@ impl std::hash::Hash for LayoutSection { byte_range, format, } = self; - crate::f32_hash(state, *leading_space); + OrderedFloat(*leading_space).hash(state); byte_range.hash(state); format.hash(state); } @@ -293,9 +293,9 @@ impl std::hash::Hash for TextFormat { valign, } = self; font_id.hash(state); - crate::f32_hash(state, *extra_letter_spacing); + emath::OrderedFloat(*extra_letter_spacing).hash(state); if let Some(line_height) = *line_height { - crate::f32_hash(state, line_height); + emath::OrderedFloat(line_height).hash(state); } color.hash(state); background.hash(state); @@ -375,7 +375,7 @@ impl std::hash::Hash for TextWrapping { break_anywhere, overflow_character, } = self; - crate::f32_hash(state, *max_width); + emath::OrderedFloat(*max_width).hash(state); max_rows.hash(state); break_anywhere.hash(state); overflow_character.hash(state); diff --git a/crates/epaint/src/util/mod.rs b/crates/epaint/src/util/mod.rs index 2aa70ee041e6..383b945671c6 100644 --- a/crates/epaint/src/util/mod.rs +++ b/crates/epaint/src/util/mod.rs @@ -1,6 +1,5 @@ -mod ordered_float; - -pub use ordered_float::*; +#[deprecated = "Use emath::OrderedFloat instead"] +pub use emath::OrderedFloat; /// Hash the given value with a predictable hasher. #[inline] From a2f1ca31a085be1974d50102a82137a18b734483 Mon Sep 17 00:00:00 2001 From: bu5hm4nn Date: Mon, 22 Apr 2024 01:06:33 -0600 Subject: [PATCH 0061/1202] Add `ViewportCommand::RequestCut`, `RequestCopy` and `RequestPaste` to trigger Clipboard actions (#4035) ### Motivation We want to offer our users a context menu with `Cut`, `Copy` and `Paste` actions. `Paste` is not possible out of the box. ### Changes This PR adds `ViewportCommand::RequestCut`, `ViewportCommand::RequestCopy` and `ViewportCommand::RequestPaste`. They are routed and handled after the example of `ViewportCommand::Screenshot` and result in the same code being executed as when the user uses `CTRL+V` style keyboard commands. ### Reasoning In our last release we used an instance of `egui_winit::clipboard::Clipboard` in order to get the `Paste` functionality. However Linux users on Wayland complained about broken clipboard interaction (https://github.com/mikedilger/gossip/issues/617). After a while of digging I could not find the issue although I have found references to problems with multiple clipboards per handle before (https://gitlab.gnome.org/GNOME/mutter/-/issues/1250) but I compared mutter with weston and the problem occured on both. So to solve this I set out to extend egui to access the clipboard instance already present in egui_winit. Since there was no trivial way to reach the instance of `egui_winit::State` I felt the best approach was to follow the logic of the new `ViewportCommand::Screenshot`. ### Variations It could make sense to make the introduced `enum ActionRequested` a part of crates/egui/src/viewport.rs and to then wrap them into one single `ViewportCommand::ActionRequest(ActionRequested)`. ### Example ```Rust let mut text = String::new(); let response = ui.text_edit_singleline(&mut text); if ui.button("Paste").clicked() { response.request_focus(); ui.ctx().send_viewport_cmd(ViewportCommand::RequestPaste); } ``` --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/native/glow_integration.rs | 55 ++++++++++++++------ crates/eframe/src/native/wgpu_integration.rs | 45 +++++++++++++--- crates/egui-winit/src/lib.rs | 28 ++++++++-- crates/egui/src/viewport.rs | 15 ++++++ crates/egui_glow/src/winit.rs | 10 ++-- 5 files changed, 119 insertions(+), 34 deletions(-) diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 7c9eada1ccc1..880b906c2b89 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -9,6 +9,7 @@ use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant}; +use egui_winit::ActionRequested; use glutin::{ config::GlConfig, context::NotCurrentGlContext, @@ -22,8 +23,9 @@ use winit::{ }; use egui::{ - epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, ViewportBuilder, - ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportInfo, ViewportOutput, + ahash::HashSet, epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, + ViewportBuilder, ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportInfo, + ViewportOutput, }; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; @@ -104,7 +106,7 @@ struct Viewport { builder: ViewportBuilder, deferred_commands: Vec, info: ViewportInfo, - screenshot_requested: bool, + actions_requested: HashSet, /// The user-callback that shows the ui. /// None for immediate viewports. @@ -682,17 +684,38 @@ impl GlowWinitRunning { ); { - let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested); - if screenshot_requested { - let screenshot = painter.read_screen_rgba(screen_size_in_pixels); - egui_winit - .egui_input_mut() - .events - .push(egui::Event::Screenshot { - viewport_id, - image: screenshot.into(), - }); + for action in viewport.actions_requested.drain() { + match action { + ActionRequested::Screenshot => { + let screenshot = painter.read_screen_rgba(screen_size_in_pixels); + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Screenshot { + viewport_id, + image: screenshot.into(), + }); + } + ActionRequested::Cut => { + egui_winit.egui_input_mut().events.push(egui::Event::Cut); + } + ActionRequested::Copy => { + egui_winit.egui_input_mut().events.push(egui::Event::Copy); + } + ActionRequested::Paste => { + if let Some(contents) = egui_winit.clipboard_text() { + let contents = contents.replace("\r\n", "\n"); + if !contents.is_empty() { + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Paste(contents)); + } + } + } + } } + integration.post_rendering(&window); } @@ -1020,7 +1043,7 @@ impl GlutinWindowContext { builder: viewport_builder, deferred_commands: vec![], info, - screenshot_requested: false, + actions_requested: Default::default(), viewport_ui_cb: None, gl_surface: None, window: window.map(Arc::new), @@ -1277,7 +1300,7 @@ impl GlutinWindowContext { std::mem::take(&mut viewport.deferred_commands), window, is_viewport_focused, - &mut viewport.screenshot_requested, + &mut viewport.actions_requested, ); // For Wayland : https://github.com/emilk/egui/issues/4196 @@ -1323,7 +1346,7 @@ fn initialize_or_update_viewport( builder, deferred_commands: vec![], info: Default::default(), - screenshot_requested: false, + actions_requested: Default::default(), viewport_ui_cb, window: None, egui_winit: None, diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 9e0022489e7b..1287ce8a01f5 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -7,6 +7,7 @@ use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant}; +use egui_winit::ActionRequested; use parking_lot::Mutex; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; use winit::{ @@ -15,9 +16,9 @@ use winit::{ }; use egui::{ - ahash::HashMap, DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportBuilder, - ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, - ViewportOutput, + ahash::{HashMap, HashSet, HashSetExt}, + DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportBuilder, ViewportClass, + ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput, }; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; @@ -78,7 +79,7 @@ pub struct Viewport { builder: ViewportBuilder, deferred_commands: Vec, info: ViewportInfo, - screenshot_requested: bool, + actions_requested: HashSet, /// `None` for sync viewports. viewport_ui_cb: Option>, @@ -289,7 +290,7 @@ impl WgpuWinitApp { builder, deferred_commands: vec![], info, - screenshot_requested: false, + actions_requested: Default::default(), viewport_ui_cb: None, window: Some(window), egui_winit: Some(egui_winit), @@ -676,7 +677,10 @@ impl WgpuWinitRunning { let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point); - let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested); + let screenshot_requested = viewport + .actions_requested + .take(&ActionRequested::Screenshot) + .is_some(); let (vsync_secs, screenshot) = painter.paint_and_update_textures( viewport_id, pixels_per_point, @@ -695,6 +699,31 @@ impl WgpuWinitRunning { }); } + for action in viewport.actions_requested.drain() { + match action { + ActionRequested::Screenshot => { + // already handled above + } + ActionRequested::Cut => { + egui_winit.egui_input_mut().events.push(egui::Event::Cut); + } + ActionRequested::Copy => { + egui_winit.egui_input_mut().events.push(egui::Event::Copy); + } + ActionRequested::Paste => { + if let Some(contents) = egui_winit.clipboard_text() { + let contents = contents.replace("\r\n", "\n"); + if !contents.is_empty() { + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Paste(contents)); + } + } + } + } + } + integration.post_rendering(window); let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect(); @@ -1073,7 +1102,7 @@ fn handle_viewport_output( std::mem::take(&mut viewport.deferred_commands), window, is_viewport_focused, - &mut viewport.screenshot_requested, + &mut viewport.actions_requested, ); // For Wayland : https://github.com/emilk/egui/issues/4196 @@ -1120,7 +1149,7 @@ fn initialize_or_update_viewport( builder, deferred_commands: vec![], info: Default::default(), - screenshot_requested: false, + actions_requested: HashSet::new(), viewport_ui_cb, window: None, egui_winit: None, diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 5abfa0f68986..1bba39b3cc63 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -14,7 +14,9 @@ pub use accesskit_winit; pub use egui; #[cfg(feature = "accesskit")] use egui::accesskit; -use egui::{Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo}; +use egui::{ + ahash::HashSet, Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo, +}; pub use winit; pub mod clipboard; @@ -1254,6 +1256,13 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option, window: &Window, is_viewport_focused: bool, - screenshot_requested: &mut bool, + actions_requested: &mut HashSet, ) { for command in commands { process_viewport_command( @@ -1270,7 +1279,7 @@ pub fn process_viewport_commands( command, info, is_viewport_focused, - screenshot_requested, + actions_requested, ); } } @@ -1281,7 +1290,7 @@ fn process_viewport_command( command: ViewportCommand, info: &mut ViewportInfo, is_viewport_focused: bool, - screenshot_requested: &mut bool, + actions_requested: &mut HashSet, ) { crate::profile_function!(); @@ -1478,7 +1487,16 @@ fn process_viewport_command( } } ViewportCommand::Screenshot => { - *screenshot_requested = true; + actions_requested.insert(ActionRequested::Screenshot); + } + ViewportCommand::RequestCut => { + actions_requested.insert(ActionRequested::Cut); + } + ViewportCommand::RequestCopy => { + actions_requested.insert(ActionRequested::Copy); + } + ViewportCommand::RequestPaste => { + actions_requested.insert(ActionRequested::Paste); } } } diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index aed8d35165ef..ad7332877c03 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -1038,6 +1038,21 @@ pub enum ViewportCommand { /// /// The results are returned in `crate::Event::Screenshot`. Screenshot, + + /// Request cut of the current selection + /// + /// This is equivalent to the system keyboard shortcut for cut (e.g. CTRL + X). + RequestCut, + + /// Request a copy of the current selection. + /// + /// This is equivalent to the system keyboard shortcut for copy (e.g. CTRL + C). + RequestCopy, + + /// Request a paste from the clipboard to the current focused TextEdit if any. + /// + /// This is equivalent to the system keyboard shortcut for paste (e.g. CTRL + V). + RequestPaste, } impl ViewportCommand { diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index 5d87d000416d..0c407ccb75de 100644 --- a/crates/egui_glow/src/winit.rs +++ b/crates/egui_glow/src/winit.rs @@ -1,7 +1,7 @@ pub use egui_winit; pub use egui_winit::EventResponse; -use egui::{ViewportId, ViewportOutput}; +use egui::{ahash::HashSet, ViewportId, ViewportOutput}; use egui_winit::winit; use crate::shader_version::ShaderVersion; @@ -79,17 +79,17 @@ impl EguiGlow { log::warn!("Multiple viewports not yet supported by EguiGlow"); } for (_, ViewportOutput { commands, .. }) in viewport_output { - let mut screenshot_requested = false; + let mut actions_requested: HashSet = Default::default(); egui_winit::process_viewport_commands( &self.egui_ctx, &mut self.viewport_info, commands, window, true, - &mut screenshot_requested, + &mut actions_requested, ); - if screenshot_requested { - log::warn!("Screenshot not yet supported by EguiGlow"); + for action in actions_requested { + log::warn!("{:?} not yet supported by EguiGlow", action); } } From 587bc2034a38d9de89dafa353734e4e4776931de Mon Sep 17 00:00:00 2001 From: zhatuokun <62168376+zhatuokun@users.noreply.github.com> Date: Mon, 22 Apr 2024 15:22:54 +0800 Subject: [PATCH 0062/1202] Fix `Panel` incorrect size (#4351) * Closes * Closes Previously, `SidePanel`/`TopBottomPanel` didn't include `inner_margin` when setting the min width/height of `Frame`. As a result, when your `Panel` has `inner_margin`, its size doesn't work as expected. Before ![before](https://github.com/emilk/egui/assets/62168376/3bd20c0a-6625-4786-9f7d-85449a0e436c) After ![after](https://github.com/emilk/egui/assets/62168376/a2e41ed4-4f20-484c-8b54-d2ce9cc71d95) --- crates/egui/src/containers/panel.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 27be59996d42..ee41af879392 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -150,7 +150,7 @@ impl SidePanel { self } - /// The initial wrapping width of the [`SidePanel`]. + /// The initial wrapping width of the [`SidePanel`], including margins. #[inline] pub fn default_width(mut self, default_width: f32) -> Self { self.default_width = default_width; @@ -161,21 +161,21 @@ impl SidePanel { self } - /// Minimum width of the panel. + /// Minimum width of the panel, including margins. #[inline] pub fn min_width(mut self, min_width: f32) -> Self { self.width_range = Rangef::new(min_width, self.width_range.max.at_least(min_width)); self } - /// Maximum width of the panel. + /// Maximum width of the panel, including margins. #[inline] pub fn max_width(mut self, max_width: f32) -> Self { self.width_range = Rangef::new(self.width_range.min.at_most(max_width), max_width); self } - /// The allowable width range for the panel. + /// The allowable width range for the panel, including margins. #[inline] pub fn width_range(mut self, width_range: impl Into) -> Self { let width_range = width_range.into(); @@ -184,7 +184,7 @@ impl SidePanel { self } - /// Enforce this exact width. + /// Enforce this exact width, including margins. #[inline] pub fn exact_width(mut self, width: f32) -> Self { self.default_width = width; @@ -262,7 +262,9 @@ impl SidePanel { let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height - ui.set_min_width(width_range.min); + ui.set_min_width( + width_range.min - (frame.inner_margin.left + frame.inner_margin.right), + ); add_contents(ui) }); @@ -609,7 +611,7 @@ impl TopBottomPanel { self } - /// The initial height of the [`TopBottomPanel`]. + /// The initial height of the [`TopBottomPanel`], including margins. /// Defaults to [`style::Spacing::interact_size`].y. #[inline] pub fn default_height(mut self, default_height: f32) -> Self { @@ -621,21 +623,21 @@ impl TopBottomPanel { self } - /// Minimum height of the panel. + /// Minimum height of the panel, including margins. #[inline] pub fn min_height(mut self, min_height: f32) -> Self { self.height_range = Rangef::new(min_height, self.height_range.max.at_least(min_height)); self } - /// Maximum height of the panel. + /// Maximum height of the panel, including margins. #[inline] pub fn max_height(mut self, max_height: f32) -> Self { self.height_range = Rangef::new(self.height_range.min.at_most(max_height), max_height); self } - /// The allowable height range for the panel. + /// The allowable height range for the panel, including margins. #[inline] pub fn height_range(mut self, height_range: impl Into) -> Self { let height_range = height_range.into(); @@ -646,7 +648,7 @@ impl TopBottomPanel { self } - /// Enforce this exact height. + /// Enforce this exact height, including margins. #[inline] pub fn exact_height(mut self, height: f32) -> Self { self.default_height = Some(height); @@ -728,7 +730,9 @@ impl TopBottomPanel { let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width - ui.set_min_height(height_range.min); + ui.set_min_height( + height_range.min - (frame.inner_margin.top + frame.inner_margin.bottom), + ); add_contents(ui) }); From 436c671331a80ac55120423f26fab069f4508770 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:43:07 +0900 Subject: [PATCH 0063/1202] Improve IME support with new `Event::Ime` (#4358) * Closes #4354 Fix: can't repeat input chinese words AND For Windows : ImeEnable ImeDisable --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/web/text_agent.rs | 13 ++-- crates/egui-winit/src/lib.rs | 45 ++++++++---- crates/egui/src/data/input.rs | 29 +++++--- crates/egui/src/widgets/text_edit/builder.rs | 72 +++++++++++--------- crates/egui/src/widgets/text_edit/state.rs | 2 +- 5 files changed, 99 insertions(+), 62 deletions(-) diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index a879f99d0082..c61b70935965 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -68,7 +68,8 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> { is_composing.set(true); input_clone.set_value(""); - runner.input.raw.events.push(egui::Event::CompositionStart); + let egui_event = egui::Event::Ime(egui::ImeEvent::Enabled); + runner.input.raw.events.push(egui_event); runner.needs_repaint.repaint_asap(); } })?; @@ -77,8 +78,9 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> { &input, "compositionupdate", move |event: web_sys::CompositionEvent, runner: &mut AppRunner| { - if let Some(event) = event.data().map(egui::Event::CompositionUpdate) { - runner.input.raw.events.push(event); + if let Some(text) = event.data() { + let egui_event = egui::Event::Ime(egui::ImeEvent::Preedit(text)); + runner.input.raw.events.push(egui_event); runner.needs_repaint.repaint_asap(); } }, @@ -91,8 +93,9 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> { is_composing.set(false); input_clone.set_value(""); - if let Some(event) = event.data().map(egui::Event::CompositionEnd) { - runner.input.raw.events.push(event); + if let Some(text) = event.data() { + let egui_event = egui::Event::Ime(egui::ImeEvent::Commit(text)); + runner.input.raw.events.push(egui_event); runner.needs_repaint.repaint_asap(); } } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 1bba39b3cc63..81840e951a72 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -95,7 +95,7 @@ pub struct State { pointer_touch_id: Option, /// track ime state - input_method_editor_started: bool, + has_sent_ime_enabled: bool, #[cfg(feature = "accesskit")] accesskit: Option, @@ -136,7 +136,7 @@ impl State { simulate_touch_screen: false, pointer_touch_id: None, - input_method_editor_started: false, + has_sent_ime_enabled: false, #[cfg(feature = "accesskit")] accesskit: None, @@ -342,23 +342,39 @@ impl State { // We use input_method_editor_started to manually insert CompositionStart // between Commits. match ime { - winit::event::Ime::Enabled | winit::event::Ime::Disabled => (), - winit::event::Ime::Commit(text) => { - self.input_method_editor_started = false; + winit::event::Ime::Enabled => { self.egui_input .events - .push(egui::Event::CompositionEnd(text.clone())); + .push(egui::Event::Ime(egui::ImeEvent::Enabled)); + self.has_sent_ime_enabled = true; } - winit::event::Ime::Preedit(text, Some(_)) => { - if !self.input_method_editor_started { - self.input_method_editor_started = true; - self.egui_input.events.push(egui::Event::CompositionStart); + winit::event::Ime::Preedit(_, None) => {} + winit::event::Ime::Preedit(text, Some(_cursor)) => { + if !self.has_sent_ime_enabled { + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Enabled)); + self.has_sent_ime_enabled = true; } self.egui_input .events - .push(egui::Event::CompositionUpdate(text.clone())); + .push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone()))); + } + winit::event::Ime::Commit(text) => { + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone()))); + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Disabled)); + self.has_sent_ime_enabled = false; + } + winit::event::Ime::Disabled => { + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Disabled)); + self.has_sent_ime_enabled = false; } - winit::event::Ime::Preedit(_, None) => {} }; EventResponse { @@ -601,7 +617,8 @@ impl State { }); // If we're not yet translating a touch or we're translating this very // touch … - if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap() == touch.id { + if self.pointer_touch_id.is_none() || self.pointer_touch_id.unwrap_or_default() == touch.id + { // … emit PointerButton resp. PointerMoved events to emulate mouse match touch.phase { winit::event::TouchPhase::Started => { @@ -1531,7 +1548,7 @@ pub fn create_winit_window_builder( // We set sizes and positions in egui:s own ui points, which depends on the egui // zoom_factor and the native pixels per point, so we need to know that here. // We don't know what monitor the window will appear on though, but - // we'll try to fix that after the window is created in the vall to `apply_viewport_builder_to_window`. + // we'll try to fix that after the window is created in the call to `apply_viewport_builder_to_window`. let native_pixels_per_point = event_loop .primary_monitor() .or_else(|| event_loop.available_monitors().next()) diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index f61b9312f899..8217f4f5a4d9 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -445,14 +445,8 @@ pub enum Event { /// * `zoom > 1`: pinch spread Zoom(f32), - /// IME composition start. - CompositionStart, - - /// A new IME candidate is being suggested. - CompositionUpdate(String), - - /// IME composition ended with this final result. - CompositionEnd(String), + /// IME Event + Ime(ImeEvent), /// On touch screens, report this *in addition to* /// [`Self::PointerMoved`], [`Self::PointerButton`], [`Self::PointerGone`] @@ -507,6 +501,25 @@ pub enum Event { }, } +/// IME event. +/// +/// See +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum ImeEvent { + /// Notifies when the IME was enabled. + Enabled, + + /// A new IME candidate is being suggested. + Preedit(String), + + /// IME composition ended with this final result. + Commit(String), + + /// Notifies when the IME was disabled. + Disabled, +} + /// Mouse button (or similar for touch input) #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index f022e860daf5..c0b8532d6d95 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -950,47 +950,51 @@ fn events( .. } => check_for_mutating_key_press(os, &mut cursor_range, text, galley, modifiers, *key), - Event::CompositionStart => { - state.has_ime = true; - None - } - - Event::CompositionUpdate(text_mark) => { - // empty prediction can be produced when user press backspace - // or escape during ime. We should clear current text. - if text_mark != "\n" && text_mark != "\r" && state.has_ime { - let mut ccursor = text.delete_selected(&cursor_range); - let start_cursor = ccursor; - if !text_mark.is_empty() { - text.insert_text_at(&mut ccursor, text_mark, char_limit); - } + Event::Ime(ime_event) => match ime_event { + ImeEvent::Enabled => { + state.ime_enabled = true; state.ime_cursor_range = cursor_range; - Some(CCursorRange::two(start_cursor, ccursor)) - } else { None } - } - - Event::CompositionEnd(prediction) => { - // CompositionEnd only characters may be typed into TextEdit without trigger CompositionStart first, - // so do not check `state.has_ime = true` in the following statement. - if prediction != "\n" && prediction != "\r" { - state.has_ime = false; - let mut ccursor; - if !prediction.is_empty() - && cursor_range.secondary.ccursor.index - == state.ime_cursor_range.secondary.ccursor.index - { - ccursor = text.delete_selected(&cursor_range); - text.insert_text_at(&mut ccursor, prediction, char_limit); + ImeEvent::Preedit(text_mark) => { + if text_mark == "\n" || text_mark == "\r" { + None + } else { + // Empty prediction can be produced when user press backspace + // or escape during IME, so we clear current text. + let mut ccursor = text.delete_selected(&cursor_range); + let start_cursor = ccursor; + if !text_mark.is_empty() { + text.insert_text_at(&mut ccursor, text_mark, char_limit); + } + state.ime_cursor_range = cursor_range; + Some(CCursorRange::two(start_cursor, ccursor)) + } + } + ImeEvent::Commit(prediction) => { + if prediction == "\n" || prediction == "\r" { + None } else { - ccursor = cursor_range.primary.ccursor; + state.ime_enabled = false; + + if !prediction.is_empty() + && cursor_range.secondary.ccursor.index + == state.ime_cursor_range.secondary.ccursor.index + { + let mut ccursor = text.delete_selected(&cursor_range); + text.insert_text_at(&mut ccursor, prediction, char_limit); + Some(CCursorRange::one(ccursor)) + } else { + let ccursor = cursor_range.primary.ccursor; + Some(CCursorRange::one(ccursor)) + } } - Some(CCursorRange::one(ccursor)) - } else { + } + ImeEvent::Disabled => { + state.ime_enabled = false; None } - } + }, _ => None, }; diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index d0334da3213a..ef4a8909a737 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -42,7 +42,7 @@ pub struct TextEditState { // If IME candidate window is shown on this text edit. #[cfg_attr(feature = "serde", serde(skip))] - pub(crate) has_ime: bool, + pub(crate) ime_enabled: bool, // cursor range for IME candidate. #[cfg_attr(feature = "serde", serde(skip))] From ff8cfc2aa029b73d400a406f11f6923040da07e6 Mon Sep 17 00:00:00 2001 From: lopo <59609929+lopo12123@users.noreply.github.com> Date: Mon, 22 Apr 2024 18:44:21 +0800 Subject: [PATCH 0064/1202] Allow users to create viewports larger than monitor on Windows & macOS (#4337) Added clamp_size_to_monitor_size field on ViewportBuilder, which means whether clamp the window's size to monitor's size. (default to `true`) * Closes https://github.com/emilk/egui/issues/3389 ### simple example ```rust pub struct MyApp {} impl MyApp { pub fn new() -> MyApp { MyApp {} } } impl eframe::App for MyApp { fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) { egui::CentralPanel::default() .frame(Frame::none().fill(Color32::DARK_GRAY)) .show(ctx, |ui| { if ctx.input(|i| i.key_pressed(Key::Escape)) { ctx.send_viewport_cmd(ViewportCommand::Close); } }); } } pub fn main() { let option = eframe::NativeOptions { viewport: ViewportBuilder::default() .with_position([10.0, 10.0]) .with_inner_size([3000.0, 2000.0]) .with_clamp_size_to_monitor_size(false), ..Default::default() }; eframe::run_native( "a large window app", option, Box::new(|ctx| Box::new(MyApp::new())), ).unwrap(); } ``` It works on my windows (with 3 monitors), but I don't have a test environment for macos --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/native/epi_integration.rs | 23 +++++++++++++++------ crates/egui-winit/src/lib.rs | 1 + crates/egui/src/viewport.rs | 22 ++++++++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index fbf7b6dc078e..b09f0f0e0c79 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -20,14 +20,23 @@ pub fn viewport_builder( let mut viewport_builder = native_options.viewport.clone(); + let clamp_size_to_monitor_size = viewport_builder.clamp_size_to_monitor_size.unwrap_or( + // On some Linux systems, a window size larger than the monitor causes crashes + cfg!(target_os = "linux"), + ); + // Always use the default window size / position on iOS. Trying to restore the previous position // causes the window to be shown too small. #[cfg(not(target_os = "ios"))] let inner_size_points = if let Some(mut window_settings) = window_settings { // Restore pos/size from previous session - window_settings - .clamp_size_to_sane_values(largest_monitor_point_size(egui_zoom_factor, event_loop)); + if clamp_size_to_monitor_size { + window_settings.clamp_size_to_sane_values(largest_monitor_point_size( + egui_zoom_factor, + event_loop, + )); + } window_settings.clamp_position_to_monitors(egui_zoom_factor, event_loop); viewport_builder = window_settings.initialize_viewport_builder(viewport_builder); @@ -37,10 +46,12 @@ pub fn viewport_builder( viewport_builder = viewport_builder.with_position(pos); } - if let Some(initial_window_size) = viewport_builder.inner_size { - let initial_window_size = initial_window_size - .at_most(largest_monitor_point_size(egui_zoom_factor, event_loop)); - viewport_builder = viewport_builder.with_inner_size(initial_window_size); + if clamp_size_to_monitor_size { + if let Some(initial_window_size) = viewport_builder.inner_size { + let initial_window_size = initial_window_size + .at_most(largest_monitor_point_size(egui_zoom_factor, event_loop)); + viewport_builder = viewport_builder.with_inner_size(initial_window_size); + } } viewport_builder.inner_size diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 81840e951a72..313cc58906bc 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1598,6 +1598,7 @@ pub fn create_winit_window_builder( window_type: _window_type, mouse_passthrough: _, // handled in `apply_viewport_builder_to_window` + clamp_size_to_monitor_size: _, // Handled in `viewport_builder` in `epi_integration.rs` } = viewport_builder; let mut window_builder = winit::window::WindowBuilder::new() diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index ad7332877c03..090e93b3fe5c 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -275,6 +275,11 @@ pub struct ViewportBuilder { pub min_inner_size: Option, pub max_inner_size: Option, + /// Whether clamp the window's size to monitor's size. The default is `true` on linux, otherwise it is `false`. + /// + /// Note: On some Linux systems, a window size larger than the monitor causes crashes + pub clamp_size_to_monitor_size: Option, + pub fullscreen: Option, pub maximized: Option, pub resizable: Option, @@ -493,6 +498,15 @@ impl ViewportBuilder { self } + /// Sets whether clamp the window's size to monitor's size. The default is `true` on linux, otherwise it is `false`. + /// + /// Note: On some Linux systems, a window size larger than the monitor causes crashes + #[inline] + pub fn with_clamp_size_to_monitor_size(mut self, value: bool) -> Self { + self.clamp_size_to_monitor_size = Some(value); + self + } + /// Does not work on X11. #[inline] pub fn with_close_button(mut self, value: bool) -> Self { @@ -606,6 +620,7 @@ impl ViewportBuilder { inner_size: new_inner_size, min_inner_size: new_min_inner_size, max_inner_size: new_max_inner_size, + clamp_size_to_monitor_size: new_clamp_size_to_monitor_size, fullscreen: new_fullscreen, maximized: new_maximized, resizable: new_resizable, @@ -740,6 +755,13 @@ impl ViewportBuilder { let mut recreate_window = false; + if new_clamp_size_to_monitor_size.is_some() + && self.clamp_size_to_monitor_size != new_clamp_size_to_monitor_size + { + self.clamp_size_to_monitor_size = new_clamp_size_to_monitor_size; + recreate_window = true; + } + if new_active.is_some() && self.active != new_active { self.active = new_active; recreate_window = true; From 2ce82cce2128a260daacf622e955a43a2ed67999 Mon Sep 17 00:00:00 2001 From: Joe Sorensen Date: Mon, 22 Apr 2024 10:35:09 -0600 Subject: [PATCH 0065/1202] Added ability to define colors at UV coordinates along a path (#4353) I had to make a couple types not Copy because closures, but it should'nt be a massive deal. I tried my best to make the API change as non breaking as possible. Anywhere a PathStroke is used, you can just use a normal Stroke instead. As mentioned above, the bezier paths couldn't be copy anymore, but IMO that's a minor caveat. --------- Co-authored-by: Emil Ernerfeldt --- crates/ecolor/Cargo.toml | 1 - crates/egui/src/painter.rs | 16 +- crates/egui_demo_lib/Cargo.toml | 2 +- .../egui_demo_lib/src/demo/dancing_strings.rs | 28 ++- crates/epaint/benches/benchmark.rs | 160 +++++++++++++- crates/epaint/src/bezier.rs | 28 +-- crates/epaint/src/color.rs | 48 +++++ crates/epaint/src/lib.rs | 4 +- crates/epaint/src/shape.rs | 26 ++- crates/epaint/src/shape_transform.rs | 69 ++++-- crates/epaint/src/stroke.rs | 67 ++++++ crates/epaint/src/tessellator.rs | 201 ++++++++++++++---- crates/epaint/src/text/text_layout.rs | 4 +- 13 files changed, 550 insertions(+), 104 deletions(-) create mode 100644 crates/epaint/src/color.rs diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index 4cdbea9124b9..611b9a4dec98 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -30,7 +30,6 @@ extra_debug_asserts = [] ## Always enable additional checks. extra_asserts = [] - [dependencies] #! ### Optional dependencies diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 788319dc9366..0935587cc7b1 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -7,7 +7,7 @@ use crate::{ }; use epaint::{ text::{Fonts, Galley, LayoutJob}, - CircleShape, ClippedShape, RectShape, Rounding, Shape, Stroke, + CircleShape, ClippedShape, PathStroke, RectShape, Rounding, Shape, Stroke, }; /// Helper to paint shapes and text to a specific region on a specific layer. @@ -280,7 +280,7 @@ impl Painter { /// # Paint different primitives impl Painter { /// Paints a line from the first point to the second. - pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into) -> ShapeIdx { + pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into) -> ShapeIdx { self.add(Shape::LineSegment { points, stroke: stroke.into(), @@ -288,13 +288,13 @@ impl Painter { } /// Paints a horizontal line. - pub fn hline(&self, x: impl Into, y: f32, stroke: impl Into) -> ShapeIdx { - self.add(Shape::hline(x, y, stroke)) + pub fn hline(&self, x: impl Into, y: f32, stroke: impl Into) -> ShapeIdx { + self.add(Shape::hline(x, y, stroke.into())) } /// Paints a vertical line. - pub fn vline(&self, x: f32, y: impl Into, stroke: impl Into) -> ShapeIdx { - self.add(Shape::vline(x, y, stroke)) + pub fn vline(&self, x: f32, y: impl Into, stroke: impl Into) -> ShapeIdx { + self.add(Shape::vline(x, y, stroke.into())) } pub fn circle( @@ -513,7 +513,7 @@ impl Painter { } fn tint_shape_towards(shape: &mut Shape, target: Color32) { - epaint::shape_transform::adjust_colors(shape, &|color| { + epaint::shape_transform::adjust_colors(shape, move |color| { if *color != Color32::PLACEHOLDER { *color = crate::ecolor::tint_color_towards(*color, target); } @@ -521,7 +521,7 @@ fn tint_shape_towards(shape: &mut Shape, target: Color32) { } fn multiply_opacity(shape: &mut Shape, opacity: f32) { - epaint::shape_transform::adjust_colors(shape, &|color| { + epaint::shape_transform::adjust_colors(shape, move |color| { if *color != Color32::PLACEHOLDER { *color = color.gamma_multiply(opacity); } diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index a08a88fe8d00..8c5a703b5d65 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -38,7 +38,7 @@ syntect = ["egui_extras/syntect"] [dependencies] -egui = { workspace = true, default-features = false } +egui = { workspace = true, default-features = false, features = ["color-hex"] } egui_extras = { workspace = true, features = ["default"] } egui_plot = { workspace = true, features = ["default"] } diff --git a/crates/egui_demo_lib/src/demo/dancing_strings.rs b/crates/egui_demo_lib/src/demo/dancing_strings.rs index 3beb323e75d0..a2b560ee723b 100644 --- a/crates/egui_demo_lib/src/demo/dancing_strings.rs +++ b/crates/egui_demo_lib/src/demo/dancing_strings.rs @@ -1,9 +1,11 @@ -use egui::{containers::*, *}; +use egui::{containers::*, epaint::PathStroke, *}; #[derive(Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] -pub struct DancingStrings {} +pub struct DancingStrings { + colors: bool, +} impl super::Demo for DancingStrings { fn name(&self) -> &'static str { @@ -28,6 +30,9 @@ impl super::View for DancingStrings { Color32::from_black_alpha(240) }; + ui.checkbox(&mut self.colors, "Colored") + .on_hover_text("Demonstrates how a path can have varying color across its length."); + Frame::canvas(ui.style()).show(ui, |ui| { ui.ctx().request_repaint(); let time = ui.input(|i| i.time); @@ -55,7 +60,24 @@ impl super::View for DancingStrings { .collect(); let thickness = 10.0 / mode as f32; - shapes.push(epaint::Shape::line(points, Stroke::new(thickness, color))); + shapes.push(epaint::Shape::line( + points, + if self.colors { + PathStroke::new_uv(thickness, move |rect, p| { + let t = remap(p.x, rect.x_range(), -1.0..=1.0).abs(); + let center_color = hex_color!("#5BCEFA"); + let outer_color = hex_color!("#F5A9B8"); + + Color32::from_rgb( + lerp(center_color.r() as f32..=outer_color.r() as f32, t) as u8, + lerp(center_color.g() as f32..=outer_color.g() as f32, t) as u8, + lerp(center_color.b() as f32..=outer_color.b() as f32, t) as u8, + ) + }) + } else { + PathStroke::new(thickness, color) + }, + )); } ui.painter().extend(shapes); diff --git a/crates/epaint/benches/benchmark.rs b/crates/epaint/benches/benchmark.rs index 709adbfae9cf..6323137fa50e 100644 --- a/crates/epaint/benches/benchmark.rs +++ b/crates/epaint/benches/benchmark.rs @@ -1,6 +1,6 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use epaint::*; +use epaint::{tessellator::Path, *}; fn single_dashed_lines(c: &mut Criterion) { c.bench_function("single_dashed_lines", move |b| { @@ -72,10 +72,166 @@ fn tessellate_circles(c: &mut Criterion) { }); } +fn thick_line_solid(c: &mut Criterion) { + c.bench_function("thick_solid_line", move |b| { + let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)]; + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed(1.5, &Stroke::new(2.0, Color32::RED).into(), &mut mesh); + + black_box(mesh); + }); + }); +} + +fn thick_large_line_solid(c: &mut Criterion) { + c.bench_function("thick_large_solid_line", move |b| { + let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::>(); + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed(1.5, &Stroke::new(2.0, Color32::RED).into(), &mut mesh); + + black_box(mesh); + }); + }); +} + +fn thin_line_solid(c: &mut Criterion) { + c.bench_function("thin_solid_line", move |b| { + let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)]; + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed(1.5, &Stroke::new(0.5, Color32::RED).into(), &mut mesh); + + black_box(mesh); + }); + }); +} + +fn thin_large_line_solid(c: &mut Criterion) { + c.bench_function("thin_large_solid_line", move |b| { + let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::>(); + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed(1.5, &Stroke::new(0.5, Color32::RED).into(), &mut mesh); + + black_box(mesh); + }); + }); +} + +fn thick_line_uv(c: &mut Criterion) { + c.bench_function("thick_uv_line", move |b| { + let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)]; + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed( + 1.5, + &PathStroke::new_uv(2.0, |_, p| { + black_box(p * 2.0); + Color32::RED + }), + &mut mesh, + ); + + black_box(mesh); + }); + }); +} + +fn thick_large_line_uv(c: &mut Criterion) { + c.bench_function("thick_large_uv_line", move |b| { + let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::>(); + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed( + 1.5, + &PathStroke::new_uv(2.0, |_, p| { + black_box(p * 2.0); + Color32::RED + }), + &mut mesh, + ); + + black_box(mesh); + }); + }); +} + +fn thin_line_uv(c: &mut Criterion) { + c.bench_function("thin_uv_line", move |b| { + let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)]; + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed( + 1.5, + &PathStroke::new_uv(2.0, |_, p| { + black_box(p * 2.0); + Color32::RED + }), + &mut mesh, + ); + + black_box(mesh); + }); + }); +} + +fn thin_large_line_uv(c: &mut Criterion) { + c.bench_function("thin_large_uv_line", move |b| { + let line = (0..1000).map(|i| pos2(i as f32, 10.0)).collect::>(); + let mut path = Path::default(); + path.add_open_points(&line); + + b.iter(|| { + let mut mesh = Mesh::default(); + path.stroke_closed( + 1.5, + &PathStroke::new_uv(2.0, |_, p| { + black_box(p * 2.0); + Color32::RED + }), + &mut mesh, + ); + + black_box(mesh); + }); + }); +} + criterion_group!( benches, single_dashed_lines, many_dashed_lines, - tessellate_circles + tessellate_circles, + thick_line_solid, + thick_large_line_solid, + thin_line_solid, + thin_large_line_solid, + thick_line_uv, + thick_large_line_uv, + thin_line_uv, + thin_large_line_uv ); criterion_main!(benches); diff --git a/crates/epaint/src/bezier.rs b/crates/epaint/src/bezier.rs index 3da99f33b655..4ad9a28228fe 100644 --- a/crates/epaint/src/bezier.rs +++ b/crates/epaint/src/bezier.rs @@ -3,7 +3,7 @@ use std::ops::Range; -use crate::{shape::Shape, Color32, PathShape, Stroke}; +use crate::{shape::Shape, Color32, PathShape, PathStroke}; use emath::*; // ---------------------------------------------------------------------------- @@ -11,7 +11,7 @@ use emath::*; /// A cubic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve). /// /// See also [`QuadraticBezierShape`]. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct CubicBezierShape { /// The first point is the starting point and the last one is the ending point of the curve. @@ -20,7 +20,7 @@ pub struct CubicBezierShape { pub closed: bool, pub fill: Color32, - pub stroke: Stroke, + pub stroke: PathStroke, } impl CubicBezierShape { @@ -32,7 +32,7 @@ impl CubicBezierShape { points: [Pos2; 4], closed: bool, fill: Color32, - stroke: impl Into, + stroke: impl Into, ) -> Self { Self { points, @@ -52,7 +52,7 @@ impl CubicBezierShape { points, closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), } } @@ -69,7 +69,7 @@ impl CubicBezierShape { points, closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), }; pathshapes.push(pathshape); } @@ -156,7 +156,7 @@ impl CubicBezierShape { points: [d_from, d_ctrl, d_to], closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), }; let delta_t = t_range.end - t_range.start; let q_start = q.sample(t_range.start); @@ -168,7 +168,7 @@ impl CubicBezierShape { points: [from, ctrl1, ctrl2, to], closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), } } @@ -375,7 +375,7 @@ impl From for Shape { /// A quadratic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve). /// /// See also [`CubicBezierShape`]. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct QuadraticBezierShape { /// The first point is the starting point and the last one is the ending point of the curve. @@ -384,7 +384,7 @@ pub struct QuadraticBezierShape { pub closed: bool, pub fill: Color32, - pub stroke: Stroke, + pub stroke: PathStroke, } impl QuadraticBezierShape { @@ -397,7 +397,7 @@ impl QuadraticBezierShape { points: [Pos2; 3], closed: bool, fill: Color32, - stroke: impl Into, + stroke: impl Into, ) -> Self { Self { points, @@ -417,7 +417,7 @@ impl QuadraticBezierShape { points, closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), } } @@ -429,7 +429,7 @@ impl QuadraticBezierShape { points, closed: self.closed, fill: self.fill, - stroke: self.stroke, + stroke: self.stroke.clone(), } } @@ -688,7 +688,7 @@ fn single_curve_approximation(curve: &CubicBezierShape) -> QuadraticBezierShape points: [curve.points[0], c, curve.points[3]], closed: curve.closed, fill: curve.fill, - stroke: curve.stroke, + stroke: curve.stroke.clone(), } } diff --git a/crates/epaint/src/color.rs b/crates/epaint/src/color.rs new file mode 100644 index 000000000000..54106c10d3f9 --- /dev/null +++ b/crates/epaint/src/color.rs @@ -0,0 +1,48 @@ +use std::{fmt::Debug, sync::Arc}; + +use ecolor::Color32; +use emath::{Pos2, Rect}; + +/// How paths will be colored. +#[derive(Clone)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum ColorMode { + /// The entire path is one solid color, this is the default. + Solid(Color32), + + /// Provide a callback which takes in the path's bounding box and a position and converts it to a color. + /// When used with a path, the bounding box will have a margin of [`TessellationOptions::feathering_size_in_pixels`](`crate::tessellator::TessellationOptions::feathering_size_in_pixels`) + /// + /// **This cannot be serialized** + #[cfg_attr(feature = "serde", serde(skip))] + UV(Arc Color32 + Send + Sync>), +} + +impl Default for ColorMode { + fn default() -> Self { + Self::Solid(Color32::TRANSPARENT) + } +} + +impl Debug for ColorMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Solid(arg0) => f.debug_tuple("Solid").field(arg0).finish(), + Self::UV(_arg0) => f.debug_tuple("UV").field(&"").finish(), + } + } +} + +impl PartialEq for ColorMode { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Solid(l0), Self::Solid(r0)) => l0 == r0, + (Self::UV(_l0), Self::UV(_r0)) => false, + _ => false, + } + } +} + +impl ColorMode { + pub const TRANSPARENT: Self = Self::Solid(Color32::TRANSPARENT); +} diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index f7ee04cb75d4..c8c47bdf9b2a 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -26,6 +26,7 @@ #![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] mod bezier; +pub mod color; pub mod image; mod margin; mod mesh; @@ -44,6 +45,7 @@ pub mod util; pub use self::{ bezier::{CubicBezierShape, QuadraticBezierShape}, + color::ColorMode, image::{ColorImage, FontImage, ImageData, ImageDelta}, margin::Margin, mesh::{Mesh, Mesh16, Vertex}, @@ -53,7 +55,7 @@ pub use self::{ Rounding, Shape, TextShape, }, stats::PaintStats, - stroke::Stroke, + stroke::{PathStroke, Stroke}, tessellator::{TessellationOptions, Tessellator}, text::{FontFamily, FontId, Fonts, Galley}, texture_atlas::TextureAtlas, diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 7922a92d6d55..336c90873165 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -3,6 +3,7 @@ use std::{any::Any, sync::Arc}; use crate::{ + stroke::PathStroke, text::{FontId, Fonts, Galley}, Color32, Mesh, Stroke, TextureId, }; @@ -34,7 +35,10 @@ pub enum Shape { Ellipse(EllipseShape), /// A line between two points. - LineSegment { points: [Pos2; 2], stroke: Stroke }, + LineSegment { + points: [Pos2; 2], + stroke: PathStroke, + }, /// A series of lines between points. /// The path can have a stroke and/or fill (if closed). @@ -88,7 +92,7 @@ impl Shape { /// A line between two points. /// More efficient than calling [`Self::line`]. #[inline] - pub fn line_segment(points: [Pos2; 2], stroke: impl Into) -> Self { + pub fn line_segment(points: [Pos2; 2], stroke: impl Into) -> Self { Self::LineSegment { points, stroke: stroke.into(), @@ -96,7 +100,7 @@ impl Shape { } /// A horizontal line. - pub fn hline(x: impl Into, y: f32, stroke: impl Into) -> Self { + pub fn hline(x: impl Into, y: f32, stroke: impl Into) -> Self { let x = x.into(); Self::LineSegment { points: [pos2(x.min, y), pos2(x.max, y)], @@ -105,7 +109,7 @@ impl Shape { } /// A vertical line. - pub fn vline(x: f32, y: impl Into, stroke: impl Into) -> Self { + pub fn vline(x: f32, y: impl Into, stroke: impl Into) -> Self { let y = y.into(); Self::LineSegment { points: [pos2(x, y.min), pos2(x, y.max)], @@ -117,13 +121,13 @@ impl Shape { /// /// Use [`Self::line_segment`] instead if your line only connects two points. #[inline] - pub fn line(points: Vec, stroke: impl Into) -> Self { + pub fn line(points: Vec, stroke: impl Into) -> Self { Self::Path(PathShape::line(points, stroke)) } /// A line that closes back to the start point again. #[inline] - pub fn closed_line(points: Vec, stroke: impl Into) -> Self { + pub fn closed_line(points: Vec, stroke: impl Into) -> Self { Self::Path(PathShape::closed_line(points, stroke)) } @@ -224,7 +228,7 @@ impl Shape { pub fn convex_polygon( points: Vec, fill: impl Into, - stroke: impl Into, + stroke: impl Into, ) -> Self { Self::Path(PathShape::convex_polygon(points, fill, stroke)) } @@ -586,7 +590,7 @@ pub struct PathShape { pub fill: Color32, /// Color and thickness of the line. - pub stroke: Stroke, + pub stroke: PathStroke, // TODO(emilk): Add texture support either by supplying uv for each point, // or by some transform from points to uv (e.g. a callback or a linear transform matrix). } @@ -596,7 +600,7 @@ impl PathShape { /// /// Use [`Shape::line_segment`] instead if your line only connects two points. #[inline] - pub fn line(points: Vec, stroke: impl Into) -> Self { + pub fn line(points: Vec, stroke: impl Into) -> Self { Self { points, closed: false, @@ -607,7 +611,7 @@ impl PathShape { /// A line that closes back to the start point again. #[inline] - pub fn closed_line(points: Vec, stroke: impl Into) -> Self { + pub fn closed_line(points: Vec, stroke: impl Into) -> Self { Self { points, closed: true, @@ -623,7 +627,7 @@ impl PathShape { pub fn convex_polygon( points: Vec, fill: impl Into, - stroke: impl Into, + stroke: impl Into, ) -> Self { Self { points, diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index 8ff65d2a0454..d0ab91536d1f 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -1,7 +1,12 @@ +use std::sync::Arc; + use crate::*; /// Remember to handle [`Color32::PLACEHOLDER`] specially! -pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { +pub fn adjust_colors( + shape: &mut Shape, + adjust_color: impl Fn(&mut Color32) + Send + Sync + Copy + 'static, +) { #![allow(clippy::match_same_arms)] match shape { Shape::Noop => {} @@ -10,8 +15,48 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { adjust_colors(shape, adjust_color); } } - Shape::LineSegment { stroke, points: _ } => { - adjust_color(&mut stroke.color); + Shape::LineSegment { stroke, points: _ } => match &stroke.color { + color::ColorMode::Solid(mut col) => adjust_color(&mut col), + color::ColorMode::UV(callback) => { + let callback = callback.clone(); + stroke.color = color::ColorMode::UV(Arc::new(Box::new(move |rect, pos| { + let mut col = callback(rect, pos); + adjust_color(&mut col); + col + }))); + } + }, + + Shape::Path(PathShape { + points: _, + closed: _, + fill, + stroke, + }) + | Shape::QuadraticBezier(QuadraticBezierShape { + points: _, + closed: _, + fill, + stroke, + }) + | Shape::CubicBezier(CubicBezierShape { + points: _, + closed: _, + fill, + stroke, + }) => { + adjust_color(fill); + match &stroke.color { + color::ColorMode::Solid(mut col) => adjust_color(&mut col), + color::ColorMode::UV(callback) => { + let callback = callback.clone(); + stroke.color = color::ColorMode::UV(Arc::new(Box::new(move |rect, pos| { + let mut col = callback(rect, pos); + adjust_color(&mut col); + col + }))); + } + } } Shape::Circle(CircleShape { @@ -26,12 +71,6 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { fill, stroke, }) - | Shape::Path(PathShape { - points: _, - closed: _, - fill, - stroke, - }) | Shape::Rect(RectShape { rect: _, rounding: _, @@ -40,18 +79,6 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { blur_width: _, fill_texture_id: _, uv: _, - }) - | Shape::QuadraticBezier(QuadraticBezierShape { - points: _, - closed: _, - fill, - stroke, - }) - | Shape::CubicBezier(CubicBezierShape { - points: _, - closed: _, - fill, - stroke, }) => { adjust_color(fill); adjust_color(&mut stroke.color); diff --git a/crates/epaint/src/stroke.rs b/crates/epaint/src/stroke.rs index 9dafef31ddcf..36ecac253d31 100644 --- a/crates/epaint/src/stroke.rs +++ b/crates/epaint/src/stroke.rs @@ -1,5 +1,7 @@ #![allow(clippy::derived_hash_with_manual_eq)] // We need to impl Hash for f32, but we don't implement Eq, which is fine +use std::{fmt::Debug, sync::Arc}; + use super::*; /// Describes the width and color of a line. @@ -52,3 +54,68 @@ impl std::hash::Hash for Stroke { color.hash(state); } } + +/// Describes the width and color of paths. The color can either be solid or provided by a callback. For more information, see [`ColorMode`] +/// +/// The default stroke is the same as [`Stroke::NONE`]. +#[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct PathStroke { + pub width: f32, + pub color: ColorMode, +} + +impl PathStroke { + /// Same as [`PathStroke::default`]. + pub const NONE: Self = Self { + width: 0.0, + color: ColorMode::TRANSPARENT, + }; + + #[inline] + pub fn new(width: impl Into, color: impl Into) -> Self { + Self { + width: width.into(), + color: ColorMode::Solid(color.into()), + } + } + + /// Create a new `PathStroke` with a UV function + /// + /// The bounding box passed to the callback will have a margin of [`TessellationOptions::feathering_size_in_pixels`](`crate::tessellator::TessellationOptions::feathering_size_in_pixels`) + #[inline] + pub fn new_uv( + width: impl Into, + callback: impl Fn(Rect, Pos2) -> Color32 + Send + Sync + 'static, + ) -> Self { + Self { + width: width.into(), + color: ColorMode::UV(Arc::new(callback)), + } + } + + /// True if width is zero or color is solid and transparent + #[inline] + pub fn is_empty(&self) -> bool { + self.width <= 0.0 || self.color == ColorMode::TRANSPARENT + } +} + +impl From<(f32, Color)> for PathStroke +where + Color: Into, +{ + #[inline(always)] + fn from((width, color): (f32, Color)) -> Self { + Self::new(width, color) + } +} + +impl From for PathStroke { + fn from(value: Stroke) -> Self { + Self { + width: value.width, + color: ColorMode::Solid(value.color), + } + } +} diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index f5b3d9e3cb14..c7dc31b75ab5 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -9,6 +9,9 @@ use crate::texture_atlas::PreparedDisc; use crate::*; use emath::*; +use self::color::ColorMode; +use self::stroke::PathStroke; + // ---------------------------------------------------------------------------- #[allow(clippy::approx_constant)] @@ -471,16 +474,22 @@ impl Path { } /// Open-ended. - pub fn stroke_open(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) { + pub fn stroke_open(&self, feathering: f32, stroke: &PathStroke, out: &mut Mesh) { stroke_path(feathering, &self.0, PathType::Open, stroke, out); } /// A closed path (returning to the first point). - pub fn stroke_closed(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) { + pub fn stroke_closed(&self, feathering: f32, stroke: &PathStroke, out: &mut Mesh) { stroke_path(feathering, &self.0, PathType::Closed, stroke, out); } - pub fn stroke(&self, feathering: f32, path_type: PathType, stroke: Stroke, out: &mut Mesh) { + pub fn stroke( + &self, + feathering: f32, + path_type: PathType, + stroke: &PathStroke, + out: &mut Mesh, + ) { stroke_path(feathering, &self.0, path_type, stroke, out); } @@ -864,19 +873,28 @@ fn stroke_path( feathering: f32, path: &[PathPoint], path_type: PathType, - stroke: Stroke, + stroke: &PathStroke, out: &mut Mesh, ) { let n = path.len() as u32; - if stroke.width <= 0.0 || stroke.color == Color32::TRANSPARENT || n < 2 { + if stroke.width <= 0.0 || stroke.color == ColorMode::TRANSPARENT || n < 2 { return; } let idx = out.vertices.len() as u32; + // expand the bounding box to include the thickness of the path + let bbox = Rect::from_points(&path.iter().map(|p| p.pos).collect::>()) + .expand((stroke.width / 2.0) + feathering); + + let get_color = |col: &ColorMode, pos: Pos2| match col { + ColorMode::Solid(col) => *col, + ColorMode::UV(fun) => fun(bbox, pos), + }; + if feathering > 0.0 { - let color_inner = stroke.color; + let color_inner = &stroke.color; let color_outer = Color32::TRANSPARENT; let thin_line = stroke.width <= feathering; @@ -889,9 +907,11 @@ fn stroke_path( */ // Fade out as it gets thinner: - let color_inner = mul_color(color_inner, stroke.width / feathering); - if color_inner == Color32::TRANSPARENT { - return; + if let ColorMode::Solid(col) = color_inner { + let color_inner = mul_color(*col, stroke.width / feathering); + if color_inner == Color32::TRANSPARENT { + return; + } } out.reserve_triangles(4 * n as usize); @@ -904,7 +924,10 @@ fn stroke_path( let p = p1.pos; let n = p1.normal; out.colored_vertex(p + n * feathering, color_outer); - out.colored_vertex(p, color_inner); + out.colored_vertex( + p, + mul_color(get_color(color_inner, p), stroke.width / feathering), + ); out.colored_vertex(p - n * feathering, color_outer); if connect_with_previous { @@ -943,8 +966,14 @@ fn stroke_path( let p = p1.pos; let n = p1.normal; out.colored_vertex(p + n * outer_rad, color_outer); - out.colored_vertex(p + n * inner_rad, color_inner); - out.colored_vertex(p - n * inner_rad, color_inner); + out.colored_vertex( + p + n * inner_rad, + get_color(color_inner, p + n * inner_rad), + ); + out.colored_vertex( + p - n * inner_rad, + get_color(color_inner, p - n * inner_rad), + ); out.colored_vertex(p - n * outer_rad, color_outer); out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0); @@ -983,8 +1012,14 @@ fn stroke_path( let n = end.normal; let back_extrude = n.rot90() * feathering; out.colored_vertex(p + n * outer_rad + back_extrude, color_outer); - out.colored_vertex(p + n * inner_rad, color_inner); - out.colored_vertex(p - n * inner_rad, color_inner); + out.colored_vertex( + p + n * inner_rad, + get_color(color_inner, p + n * inner_rad), + ); + out.colored_vertex( + p - n * inner_rad, + get_color(color_inner, p - n * inner_rad), + ); out.colored_vertex(p - n * outer_rad + back_extrude, color_outer); out.add_triangle(idx + 0, idx + 1, idx + 2); @@ -997,8 +1032,14 @@ fn stroke_path( let p = point.pos; let n = point.normal; out.colored_vertex(p + n * outer_rad, color_outer); - out.colored_vertex(p + n * inner_rad, color_inner); - out.colored_vertex(p - n * inner_rad, color_inner); + out.colored_vertex( + p + n * inner_rad, + get_color(color_inner, p + n * inner_rad), + ); + out.colored_vertex( + p - n * inner_rad, + get_color(color_inner, p - n * inner_rad), + ); out.colored_vertex(p - n * outer_rad, color_outer); out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0); @@ -1020,8 +1061,14 @@ fn stroke_path( let n = end.normal; let back_extrude = -n.rot90() * feathering; out.colored_vertex(p + n * outer_rad + back_extrude, color_outer); - out.colored_vertex(p + n * inner_rad, color_inner); - out.colored_vertex(p - n * inner_rad, color_inner); + out.colored_vertex( + p + n * inner_rad, + get_color(color_inner, p + n * inner_rad), + ); + out.colored_vertex( + p - n * inner_rad, + get_color(color_inner, p - n * inner_rad), + ); out.colored_vertex(p - n * outer_rad + back_extrude, color_outer); out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0); @@ -1067,19 +1114,39 @@ fn stroke_path( if thin_line { // Fade out thin lines rather than making them thinner let radius = feathering / 2.0; - let color = mul_color(stroke.color, stroke.width / feathering); - if color == Color32::TRANSPARENT { - return; + if let ColorMode::Solid(color) = stroke.color { + let color = mul_color(color, stroke.width / feathering); + if color == Color32::TRANSPARENT { + return; + } } for p in path { - out.colored_vertex(p.pos + radius * p.normal, color); - out.colored_vertex(p.pos - radius * p.normal, color); + out.colored_vertex( + p.pos + radius * p.normal, + mul_color( + get_color(&stroke.color, p.pos + radius * p.normal), + stroke.width / feathering, + ), + ); + out.colored_vertex( + p.pos - radius * p.normal, + mul_color( + get_color(&stroke.color, p.pos - radius * p.normal), + stroke.width / feathering, + ), + ); } } else { let radius = stroke.width / 2.0; for p in path { - out.colored_vertex(p.pos + radius * p.normal, stroke.color); - out.colored_vertex(p.pos - radius * p.normal, stroke.color); + out.colored_vertex( + p.pos + radius * p.normal, + get_color(&stroke.color, p.pos + radius * p.normal), + ); + out.colored_vertex( + p.pos - radius * p.normal, + get_color(&stroke.color, p.pos - radius * p.normal), + ); } } } @@ -1275,9 +1342,9 @@ impl Tessellator { self.tessellate_text(&text_shape, out); } Shape::QuadraticBezier(quadratic_shape) => { - self.tessellate_quadratic_bezier(quadratic_shape, out); + self.tessellate_quadratic_bezier(&quadratic_shape, out); } - Shape::CubicBezier(cubic_shape) => self.tessellate_cubic_bezier(cubic_shape, out), + Shape::CubicBezier(cubic_shape) => self.tessellate_cubic_bezier(&cubic_shape, out), Shape::Callback(_) => { panic!("Shape::Callback passed to Tessellator"); } @@ -1337,7 +1404,7 @@ impl Tessellator { self.scratchpad_path.add_circle(center, radius); self.scratchpad_path.fill(self.feathering, fill, out); self.scratchpad_path - .stroke_closed(self.feathering, stroke, out); + .stroke_closed(self.feathering, &stroke.into(), out); } /// Tessellate a single [`EllipseShape`] into a [`Mesh`]. @@ -1404,7 +1471,7 @@ impl Tessellator { self.scratchpad_path.add_line_loop(&points); self.scratchpad_path.fill(self.feathering, fill, out); self.scratchpad_path - .stroke_closed(self.feathering, stroke, out); + .stroke_closed(self.feathering, &stroke.into(), out); } /// Tessellate a single [`Mesh`] into a [`Mesh`]. @@ -1430,7 +1497,13 @@ impl Tessellator { /// /// * `shape`: the mesh to tessellate. /// * `out`: triangles are appended to this. - pub fn tessellate_line(&mut self, points: [Pos2; 2], stroke: Stroke, out: &mut Mesh) { + pub fn tessellate_line( + &mut self, + points: [Pos2; 2], + stroke: impl Into, + out: &mut Mesh, + ) { + let stroke = stroke.into(); if stroke.is_empty() { return; } @@ -1446,7 +1519,7 @@ impl Tessellator { self.scratchpad_path.clear(); self.scratchpad_path.add_line_segment(points); self.scratchpad_path - .stroke_open(self.feathering, stroke, out); + .stroke_open(self.feathering, &stroke, out); } /// Tessellate a single [`PathShape`] into a [`Mesh`]. @@ -1493,7 +1566,7 @@ impl Tessellator { PathType::Open }; self.scratchpad_path - .stroke(self.feathering, typ, *stroke, out); + .stroke(self.feathering, typ, stroke, out); } /// Tessellate a single [`Rect`] into a [`Mesh`]. @@ -1588,7 +1661,7 @@ impl Tessellator { path.fill(self.feathering, fill, out); } - path.stroke_closed(self.feathering, stroke, out); + path.stroke_closed(self.feathering, &stroke.into(), out); } self.feathering = old_feathering; // restore @@ -1707,8 +1780,11 @@ impl Tessellator { self.scratchpad_path.clear(); self.scratchpad_path .add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]); - self.scratchpad_path - .stroke_open(self.feathering, *underline, out); + self.scratchpad_path.stroke_open( + self.feathering, + &PathStroke::from(*underline), + out, + ); } } } @@ -1719,7 +1795,7 @@ impl Tessellator { /// * `out`: triangles are appended to this. pub fn tessellate_quadratic_bezier( &mut self, - quadratic_shape: QuadraticBezierShape, + quadratic_shape: &QuadraticBezierShape, out: &mut Mesh, ) { let options = &self.options; @@ -1737,7 +1813,7 @@ impl Tessellator { &points, quadratic_shape.fill, quadratic_shape.closed, - quadratic_shape.stroke, + &quadratic_shape.stroke, out, ); } @@ -1746,7 +1822,7 @@ impl Tessellator { /// /// * `cubic_shape`: the shape to tessellate. /// * `out`: triangles are appended to this. - pub fn tessellate_cubic_bezier(&mut self, cubic_shape: CubicBezierShape, out: &mut Mesh) { + pub fn tessellate_cubic_bezier(&mut self, cubic_shape: &CubicBezierShape, out: &mut Mesh) { let options = &self.options; let clip_rect = self.clip_rect; if options.coarse_tessellation_culling @@ -1763,7 +1839,7 @@ impl Tessellator { &points, cubic_shape.fill, cubic_shape.closed, - cubic_shape.stroke, + &cubic_shape.stroke, out, ); } @@ -1774,7 +1850,7 @@ impl Tessellator { points: &[Pos2], fill: Color32, closed: bool, - stroke: Stroke, + stroke: &PathStroke, out: &mut Mesh, ) { if points.len() < 2 { @@ -1985,3 +2061,48 @@ fn test_tessellator() { assert_eq!(primitives.len(), 2); } + +#[test] +fn path_bounding_box() { + use crate::*; + + for i in 1..=100 { + let width = i as f32; + + let rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(10.0, 10.0)); + let expected_rect = rect.expand((width / 2.0) + 1.5); + + let mut mesh = Mesh::default(); + + let mut path = Path::default(); + path.add_open_points(&[ + pos2(0.0, 0.0), + pos2(2.0, 0.0), + pos2(5.0, 5.0), + pos2(0.0, 5.0), + pos2(0.0, 7.0), + pos2(10.0, 10.0), + ]); + + path.stroke( + 1.5, + PathType::Closed, + &PathStroke::new_uv(width, move |r, p| { + assert_eq!(r, expected_rect); + // see https://github.com/emilk/egui/pull/4353#discussion_r1573879940 for why .contains() isn't used here. + // TL;DR rounding errors. + assert!( + r.distance_to_pos(p) <= 0.55, + "passed rect {r:?} didn't contain point {p:?} (distance: {})", + r.distance_to_pos(p) + ); + assert!( + expected_rect.distance_to_pos(p) <= 0.55, + "expected rect {expected_rect:?} didn't contain point {p:?}" + ); + Color32::WHITE + }), + &mut mesh, + ); + } +} diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index c9956aac1976..276f3f9e2a3e 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use emath::*; -use crate::{text::font::Font, Color32, Mesh, Stroke, Vertex}; +use crate::{stroke::PathStroke, text::font::Font, Color32, Mesh, Stroke, Vertex}; use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals}; @@ -853,7 +853,7 @@ fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke, let mut path = crate::tessellator::Path::default(); // TODO(emilk): reuse this to avoid re-allocations. path.add_line_segment([start, stop]); let feathering = 1.0 / point_scale.pixels_per_point(); - path.stroke_open(feathering, stroke, mesh); + path.stroke_open(feathering, &PathStroke::from(stroke), mesh); } else { // Thin lines often lost, so this is a bad idea From 2c590636b5422c010cbb3f909c8eb4777a6e2f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=96R=C3=96K=20Attila?= Date: Tue, 23 Apr 2024 08:03:36 +0200 Subject: [PATCH 0066/1202] `egui-winit`: Update `webbrowser` to `v1.0.0` (#4394) No significant changes, mostly marks API stability: https://github.com/amodm/webbrowser-rs/releases/tag/v1.0.0 --- Cargo.lock | 4 ++-- crates/egui-winit/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d64d54397f14..a9214151c0ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4205,9 +4205,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.11" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2c79b77f525a2d670cb40619d7d9c673d09e0666f72c591ebd7861f84a87e57" +checksum = "60b6f804e41d0852e16d2eaee61c7e4f7d3e8ffdb7b8ed85886aeb0791fe9fcd" dependencies = [ "core-foundation", "home", diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index fdff0fcdcbf7..a09829699fc1 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -71,7 +71,7 @@ document-features = { workspace = true, optional = true } puffin = { workspace = true, optional = true } serde = { version = "1.0", optional = true, features = ["derive"] } -webbrowser = { version = "0.8.3", optional = true } +webbrowser = { version = "1.0.0", optional = true } [target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies] smithay-clipboard = { version = "0.7.0", optional = true } From 14194f5d3a5d3c10a09822d3234d2e03e737a0db Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 23 Apr 2024 17:35:12 +0200 Subject: [PATCH 0067/1202] eframe: Use `objc2` and its framework crates (#4395) These are a replacement to the `objc` and `cocoa` crates. This PR prevents: - An extra copy when creating `NSData` - A memory leak when creating `NSImage` - A memory leak when creating `NSString` And is generally a readability improvement. Note that we define `NSApp` manually for now, the implementation in `objc2-app-kit` is currently suboptimal and wouldn't allow you to check whether the NSApplication has been created or not. Related: https://github.com/emilk/egui/issues/4219, this should nicely coincide with the Winit `0.30` release. --------- Co-authored-by: Emil Ernerfeldt --- Cargo.lock | 101 +++++++++++++++++---------- crates/eframe/Cargo.toml | 15 +++- crates/eframe/src/native/app_icon.rs | 43 +++++------- deny.toml | 3 +- 4 files changed, 99 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9214151c0ab..1afbd85fbd05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -548,7 +548,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dd7cf50912cddc06dc5ea7c08c5e81c1b2c842a70d19def1848d54c586fed92" dependencies = [ - "objc-sys 0.3.1", + "objc-sys 0.3.3", ] [[package]] @@ -571,6 +571,15 @@ dependencies = [ "objc2 0.4.1", ] +[[package]] +name = "block2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ff7d91d3c1d568065b06c899777d1e48dcf76103a672a0adbc238a7f247f1e" +dependencies = [ + "objc2 0.5.1", +] + [[package]] name = "blocking" version = "1.4.0" @@ -795,36 +804,6 @@ dependencies = [ "error-code", ] -[[package]] -name = "cocoa" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1201,7 +1180,6 @@ name = "eframe" version = "0.27.2" dependencies = [ "bytemuck", - "cocoa", "directories-next", "document-features", "egui", @@ -1214,7 +1192,9 @@ dependencies = [ "image", "js-sys", "log", - "objc", + "objc2 0.5.1", + "objc2-app-kit", + "objc2-foundation", "parking_lot", "percent-encoding", "pollster", @@ -2599,9 +2579,9 @@ checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" [[package]] name = "objc-sys" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e1d07c6eab1ce8b6382b8e3c7246fe117ff3f8b34be065f5ebace6749fe845" +checksum = "da284c198fb9b7b0603f8635185e85fbd5b64ee154b1ed406d489077de2d6d60" [[package]] name = "objc2" @@ -2620,10 +2600,43 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ - "objc-sys 0.3.1", + "objc-sys 0.3.3", "objc2-encode 3.0.0", ] +[[package]] +name = "objc2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b25e1034d0e636cd84707ccdaa9f81243d399196b8a773946dcffec0401659" +dependencies = [ + "objc-sys 0.3.3", + "objc2-encode 4.0.1", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb79768a710a9a1798848179edb186d1af7e8a8679f369e4b8d201dd2a034047" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", + "objc2-core-data", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e092bc42eaf30a08844e6a076938c60751225ec81431ab89f5d1ccd9f958d6c" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", + "objc2-foundation", +] + [[package]] name = "objc2-encode" version = "2.0.0-pre.2" @@ -2639,6 +2652,22 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" +[[package]] +name = "objc2-encode" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88658da63e4cc2c8adb1262902cd6af51094df0488b760d6fd27194269c0950a" + +[[package]] +name = "objc2-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfaefe14254871ea16c7d88968c0ff14ba554712a20d76421eec52f0a7fb8904" +dependencies = [ + "block2 0.5.0", + "objc2 0.5.1", +] + [[package]] name = "objc_exception" version = "0.1.2" diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index da1aea6badfb..5b2651c9ad4e 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -178,8 +178,19 @@ wgpu = { workspace = true, optional = true, features = [ # mac: [target.'cfg(any(target_os = "macos"))'.dependencies] -cocoa = "0.25.0" -objc = "0.2.7" +objc2 = "0.5.1" +objc2-foundation = { version = "0.2.0", features = [ + "block2", + "NSData", + "NSString", +] } +objc2-app-kit = { version = "0.2.0", features = [ + "NSApplication", + "NSImage", + "NSMenu", + "NSMenuItem", + "NSResponder", +] } # windows: [target.'cfg(any(target_os = "windows"))'.dependencies] diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index f169420863c9..4986549cc1ba 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -203,12 +203,9 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS use crate::icon_data::IconDataExt as _; crate::profile_function!(); - use cocoa::{ - appkit::{NSApp, NSApplication, NSImage, NSMenu, NSWindow}, - base::{id, nil}, - foundation::{NSData, NSString}, - }; - use objc::{msg_send, sel, sel_impl}; + use objc2::ClassType; + use objc2_app_kit::{NSApplication, NSImage}; + use objc2_foundation::{NSData, NSString}; let png_bytes = if let Some(icon_data) = icon_data { match icon_data.to_png_bytes() { @@ -222,38 +219,36 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS None }; - // SAFETY: Accessing raw data from icon in a read-only manner. Icon data is static! + // TODO(madsmtm): Move this into `objc2-app-kit` + extern "C" { + static NSApp: Option<&'static NSApplication>; + } + unsafe { - let app = NSApp(); - if app.is_null() { + let app = if let Some(app) = NSApp { + app + } else { log::debug!("NSApp is null"); return AppIconStatus::NotSetIgnored; - } + }; if let Some(png_bytes) = png_bytes { - let data = NSData::dataWithBytes_length_( - nil, - png_bytes.as_ptr().cast::(), - png_bytes.len() as u64, - ); + let data = NSData::from_vec(png_bytes); log::trace!("NSImage::initWithData…"); - let app_icon = NSImage::initWithData_(NSImage::alloc(nil), data); + let app_icon = NSImage::initWithData(NSImage::alloc(), &data); crate::profile_scope!("setApplicationIconImage_"); log::trace!("setApplicationIconImage…"); - app.setApplicationIconImage_(app_icon); + app.setApplicationIconImage(app_icon.as_deref()); } // Change the title in the top bar - for python processes this would be again "python" otherwise. - let main_menu = app.mainMenu(); - if !main_menu.is_null() { - let item = main_menu.itemAtIndex_(0); - if !item.is_null() { - let app_menu: id = msg_send![item, submenu]; - if !app_menu.is_null() { + if let Some(main_menu) = app.mainMenu() { + if let Some(item) = main_menu.itemAtIndex(0) { + if let Some(app_menu) = item.submenu() { crate::profile_scope!("setTitle_"); - app_menu.setTitle_(NSString::alloc(nil).init_str(title)); + app_menu.setTitle(&NSString::from_str(title)); } } } diff --git a/deny.toml b/deny.toml index ebe372744dd7..02a9cdb04a3f 100644 --- a/deny.toml +++ b/deny.toml @@ -47,6 +47,7 @@ deny = [ skip = [ { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … + { name = "block2" }, # old version via glutin->icrate { name = "event-listener" }, # TODO(emilk): rustls pulls in two versions of this 😭 { name = "libloading" }, # wgpu-hal itself depends on 0.8 while some of its dependencies, like ash and d3d12, depend on 0.7 { name = "memoffset" }, # tiny dependency @@ -61,7 +62,7 @@ skip = [ skip-tree = [ { name = "criterion" }, # dev-dependency { name = "fastrand" }, # old version via accesskit_unix - { name = "foreign-types" }, # small crate. Old version via cocoa and core-graphics (winit). + { name = "foreign-types" }, # small crate. Old version via core-graphics (winit). { name = "objc2" }, # old version via accesskit_macos { name = "polling" }, # old version via accesskit_unix { name = "rfd" }, # example dependency From bfe1858e0bdd3ee50a9d594fe9351476ede44241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20Sch=C3=B6n?= Date: Thu, 25 Apr 2024 15:43:24 +0200 Subject: [PATCH 0068/1202] eframe: update ViewportBuilder.with_icon() documentation (#4408) `None` is no longer correct. To disable the egui icon one has to pass `IconData::default()` now. --- crates/egui/src/viewport.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 090e93b3fe5c..e1e3ebf0e82e 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -389,7 +389,7 @@ impl ViewportBuilder { /// The application icon, e.g. in the Windows task bar or the alt-tab menu. /// /// The default icon is a white `e` on a black background (for "egui" or "eframe"). - /// If you prefer the OS default, set this to `None`. + /// If you prefer the OS default, set this to `IconData::default()`. #[inline] pub fn with_icon(mut self, icon: impl Into>) -> Self { self.icon = Some(icon.into()); From cee790681d10971afdbb94aa734db71fd9ce28e6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 25 Apr 2024 15:51:01 +0200 Subject: [PATCH 0069/1202] Update to Rust 1.76 (#4411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motivation: I want to replace `cargo-cranky` with workspace lints, first available in Rust 1.74. However, `cargo doc` would hange on `wgpu` and `wgpu-core` on 1.74 and 1.75… so now we're on 1.76. I think this is fine - when 1.78 is released next week we're still two versions behind the bleeding edge. …and the branch name is just wrong 🤦 --- .github/workflows/deploy_web_demo.yml | 2 +- .github/workflows/rust.yml | 10 +++++----- Cargo.toml | 2 +- clippy.toml | 2 +- crates/eframe/src/native/app_icon.rs | 4 +--- crates/eframe/src/native/run.rs | 4 ++++ crates/eframe/src/web/storage.rs | 2 +- crates/egui/src/containers/collapsing_header.rs | 2 +- crates/egui/src/lib.rs | 4 ++-- crates/egui/src/memory.rs | 2 +- crates/egui/src/style.rs | 2 +- crates/egui_extras/src/table.rs | 2 +- crates/epaint/src/shape.rs | 7 +------ examples/confirm_exit/Cargo.toml | 2 +- examples/custom_3d_glow/Cargo.toml | 2 +- examples/custom_font/Cargo.toml | 2 +- examples/custom_font_style/Cargo.toml | 2 +- examples/custom_keypad/Cargo.toml | 2 +- examples/custom_plot_manipulation/Cargo.toml | 2 +- examples/custom_window_frame/Cargo.toml | 2 +- examples/file_dialog/Cargo.toml | 2 +- examples/hello_world/Cargo.toml | 2 +- examples/hello_world_par/Cargo.toml | 2 +- examples/hello_world_simple/Cargo.toml | 2 +- examples/images/Cargo.toml | 2 +- examples/images/src/ferris.svg | 2 +- examples/keyboard_events/Cargo.toml | 2 +- examples/multiple_viewports/Cargo.toml | 2 +- examples/puffin_profiler/Cargo.toml | 2 +- examples/save_plot/Cargo.toml | 2 +- examples/screenshot/Cargo.toml | 2 +- examples/serial_windows/Cargo.toml | 2 +- examples/test_inline_glow_paint/Cargo.toml | 2 +- examples/test_viewports/Cargo.toml | 2 +- examples/user_attention/Cargo.toml | 2 +- rust-toolchain | 2 +- scripts/clippy_wasm/clippy.toml | 2 +- 37 files changed, 45 insertions(+), 48 deletions(-) diff --git a/.github/workflows/deploy_web_demo.yml b/.github/workflows/deploy_web_demo.yml index ea2956db3130..41203da390bd 100644 --- a/.github/workflows/deploy_web_demo.yml +++ b/.github/workflows/deploy_web_demo.yml @@ -43,7 +43,7 @@ jobs: with: profile: minimal target: wasm32-unknown-unknown - toolchain: 1.72.0 + toolchain: 1.76.0 override: true - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d641b17b284c..89983a2c48bd 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,7 +19,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.72.0 + toolchain: 1.76.0 - name: Install packages (Linux) if: runner.os == 'Linux' @@ -93,7 +93,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.72.0 + toolchain: 1.76.0 targets: wasm32-unknown-unknown - run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev @@ -151,7 +151,7 @@ jobs: - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 with: - rust-version: "1.72.0" + rust-version: "1.76.0" log-level: error command: check arguments: --target ${{ matrix.target }} @@ -166,7 +166,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.72.0 + toolchain: 1.76.0 targets: aarch64-linux-android - name: Set up cargo cache @@ -184,7 +184,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.72.0 + toolchain: 1.76.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 diff --git a/Cargo.toml b/Cargo.toml index 425eb8de2085..47ef422cb5a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ members = [ [workspace.package] edition = "2021" license = "MIT OR Apache-2.0" -rust-version = "1.72" +rust-version = "1.76" version = "0.27.2" diff --git a/clippy.toml b/clippy.toml index 93d7874068e8..d91f9666c479 100644 --- a/clippy.toml +++ b/clippy.toml @@ -3,7 +3,7 @@ # ----------------------------------------------------------------------------- # Section identical to scripts/clippy_wasm/clippy.toml: -msrv = "1.72" +msrv = "1.76" allow-unwrap-in-tests = true diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index 4986549cc1ba..39c32dfdb3e3 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -225,9 +225,7 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS } unsafe { - let app = if let Some(app) = NSApp { - app - } else { + let Some(app) = NSApp else { log::debug!("NSApp is null"); return AppIconStatus::NotSetIgnored; }; diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 896d640de368..3ee249edf76e 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -391,6 +391,8 @@ pub fn run_glow( mut native_options: epi::NativeOptions, app_creator: epi::AppCreator, ) -> Result<()> { + #![allow(clippy::needless_return_with_question_mark)] // False positive + use super::glow_integration::GlowWinitApp; #[cfg(not(target_os = "ios"))] @@ -414,6 +416,8 @@ pub fn run_wgpu( mut native_options: epi::NativeOptions, app_creator: epi::AppCreator, ) -> Result<()> { + #![allow(clippy::needless_return_with_question_mark)] // False positive + use super::wgpu_integration::WgpuWinitApp; #[cfg(not(target_os = "ios"))] diff --git a/crates/eframe/src/web/storage.rs b/crates/eframe/src/web/storage.rs index 4a2a53326a19..170798dc6625 100644 --- a/crates/eframe/src/web/storage.rs +++ b/crates/eframe/src/web/storage.rs @@ -31,7 +31,7 @@ pub(crate) fn load_memory(_: &egui::Context) {} #[cfg(feature = "persistence")] pub(crate) fn save_memory(ctx: &egui::Context) { - match ctx.memory(|mem| ron::to_string(mem)) { + match ctx.memory(ron::to_string) { Ok(ron) => { local_storage_set("egui_memory_ron", &ron); } diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index 6fb7662af222..7c541a1a2f73 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -230,7 +230,7 @@ impl CollapsingState { } } - /// Paint this [CollapsingState](CollapsingState)'s toggle button. Takes an [IconPainter](IconPainter) as the icon. + /// Paint this [`CollapsingState`]'s toggle button. Takes an [`IconPainter`] as the icon. /// ``` /// # egui::__run_test_ui(|ui| { /// fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) { diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index b8f0c8fa3d29..30e3b804f119 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -517,7 +517,7 @@ macro_rules! include_image { }; } -/// Create a [`Hyperlink`](crate::Hyperlink) to the current [`file!()`] (and line) on Github +/// Create a [`Hyperlink`] to the current [`file!()`] (and line) on Github /// /// ``` /// # egui::__run_test_ui(|ui| { @@ -532,7 +532,7 @@ macro_rules! github_link_file_line { }}; } -/// Create a [`Hyperlink`](crate::Hyperlink) to the current [`file!()`] on github. +/// Create a [`Hyperlink`] to the current [`file!()`] on github. /// /// ``` /// # egui::__run_test_ui(|ui| { diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 7d61acd965f4..2ef8bbf58ba3 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -868,7 +868,7 @@ impl Memory { // ---------------------------------------------------------------------------- /// Keeps track of [`Area`](crate::containers::area::Area)s, which are free-floating [`Ui`](crate::Ui)s. -/// These [`Area`](crate::containers::area::Area)s can be in any [`Order`](crate::Order). +/// These [`Area`](crate::containers::area::Area)s can be in any [`Order`]. #[derive(Clone, Debug, Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 9f9aac0534a1..aad8e3eea2db 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -288,7 +288,7 @@ pub struct Spacing { /// Default rail height of a [`Slider`]. pub slider_rail_height: f32, - /// Default (minimum) width of a [`ComboBox`](crate::ComboBox). + /// Default (minimum) width of a [`ComboBox`]. pub combo_width: f32, /// Default width of a [`TextEdit`]. diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 5e9cf80fdcdb..0cbf5e13b5f5 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -131,7 +131,7 @@ impl Column { self } - /// Allowed range of movement (in points), if in a resizable [`Table`](crate::table::Table). + /// Allowed range of movement (in points), if in a resizable [`Table`]. #[inline] pub fn range(mut self, range: impl Into) -> Self { self.width_range = range.into(); diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 336c90873165..593cc78b0f3d 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -1293,12 +1293,7 @@ impl std::fmt::Debug for PaintCallback { impl std::cmp::PartialEq for PaintCallback { fn eq(&self, other: &Self) -> bool { - // As I understand it, the problem this clippy is trying to protect against - // can only happen if we do dynamic casts back and forth on the pointers, and we don't do that. - #[allow(clippy::vtable_address_comparisons)] - { - self.rect.eq(&other.rect) && Arc::ptr_eq(&self.callback, &other.callback) - } + self.rect.eq(&other.rect) && Arc::ptr_eq(&self.callback, &other.callback) } } diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index 09ad2c4f6069..8e9d3af28727 100644 --- a/examples/confirm_exit/Cargo.toml +++ b/examples/confirm_exit/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index d7e70401a472..ef3d1cbc828d 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index db633431531e..89840c4526db 100644 --- a/examples/custom_font/Cargo.toml +++ b/examples/custom_font/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index 51990ee919db..60e3e37b9c2a 100644 --- a/examples/custom_font_style/Cargo.toml +++ b/examples/custom_font_style/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["tami5 "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index 1557b35c86c0..f008884c5c7b 100644 --- a/examples/custom_keypad/Cargo.toml +++ b/examples/custom_keypad/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Varphone Wong "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_plot_manipulation/Cargo.toml b/examples/custom_plot_manipulation/Cargo.toml index 3df5de96f3d3..3db38a45b925 100644 --- a/examples/custom_plot_manipulation/Cargo.toml +++ b/examples/custom_plot_manipulation/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Ygor Souza "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index e163bda492a6..c334c6a662bb 100644 --- a/examples/custom_window_frame/Cargo.toml +++ b/examples/custom_window_frame/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index ceafd1be28a6..ef30dfce8e11 100644 --- a/examples/file_dialog/Cargo.toml +++ b/examples/file_dialog/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index e9e113c67226..acf7e2b8ab89 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 442f671e3303..3d0e92eaec44 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Maxim Osipenko "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index 5b93eb686c72..bec4670b790f 100644 --- a/examples/hello_world_simple/Cargo.toml +++ b/examples/hello_world_simple/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index b6cb142f7235..cd9157f5715f 100644 --- a/examples/images/Cargo.toml +++ b/examples/images/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Jan Procházka "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/images/src/ferris.svg b/examples/images/src/ferris.svg index c7f240dd97ab..fe1589d912ce 100644 --- a/examples/images/src/ferris.svg +++ b/examples/images/src/ferris.svg @@ -3,7 +3,7 @@ - + diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index 5428bc6a3aeb..08f7ba3a81ed 100644 --- a/examples/keyboard_events/Cargo.toml +++ b/examples/keyboard_events/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Jose Palazon "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index 37b27cb5f96a..b5bd8e80411a 100644 --- a/examples/multiple_viewports/Cargo.toml +++ b/examples/multiple_viewports/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false [features] diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index 021c05651a66..6b6acad726d0 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/save_plot/Cargo.toml b/examples/save_plot/Cargo.toml index cb31594343c9..7c9eb245285f 100644 --- a/examples/save_plot/Cargo.toml +++ b/examples/save_plot/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["hacknus "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false [dependencies] diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index 5c7bbbeec42b..0fb5da7fd2a5 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -7,7 +7,7 @@ authors = [ ] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index ee0f7b78d256..58d5cdecced3 100644 --- a/examples/serial_windows/Cargo.toml +++ b/examples/serial_windows/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false diff --git a/examples/test_inline_glow_paint/Cargo.toml b/examples/test_inline_glow_paint/Cargo.toml index f1ff645ac277..893b0d07de1f 100644 --- a/examples/test_inline_glow_paint/Cargo.toml +++ b/examples/test_inline_glow_paint/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/examples/test_viewports/Cargo.toml b/examples/test_viewports/Cargo.toml index cd5b2be0e183..4e106e37ec86 100644 --- a/examples/test_viewports/Cargo.toml +++ b/examples/test_viewports/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["konkitoman"] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false [features] diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 02a776477ddd..10f0ce711c0d 100644 --- a/examples/user_attention/Cargo.toml +++ b/examples/user_attention/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["TicClick "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.72" +rust-version = "1.76" publish = false [dependencies] diff --git a/rust-toolchain b/rust-toolchain index 694e5af99e53..871f562485d6 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -5,6 +5,6 @@ # to the user in the error, instead of "error: invalid channel name '[toolchain]'". [toolchain] -channel = "1.72.0" +channel = "1.76.0" components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"] diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index 0e8b9474ea70..943444cfc505 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -6,7 +6,7 @@ # ----------------------------------------------------------------------------- # Section identical to the root clippy.toml: -msrv = "1.72" +msrv = "1.76" allow-unwrap-in-tests = true From 2f508d6a6163c8b40ddded83eb2a7390d1e4c1e7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 25 Apr 2024 17:24:50 +0200 Subject: [PATCH 0070/1202] Replace cargo-cranky with workspace lints (#4413) Replace `cargo-cranky` (which has served us well) with workspace lints --- .github/pull_request_template.md | 2 +- .github/workflows/rust.yml | 20 +- Cargo.toml | 166 +++++++++++++++++ Cranky.toml | 176 ------------------ bacon.toml | 74 -------- crates/ecolor/Cargo.toml | 3 + crates/eframe/Cargo.toml | 3 + crates/eframe/src/native/app_icon.rs | 1 + crates/eframe/src/native/glow_integration.rs | 6 +- crates/eframe/src/web/text_agent.rs | 2 +- crates/egui-wgpu/Cargo.toml | 3 + crates/egui-wgpu/src/winit.rs | 3 + crates/egui-winit/Cargo.toml | 3 + crates/egui-winit/src/clipboard.rs | 2 + crates/egui-winit/src/lib.rs | 5 +- crates/egui/Cargo.toml | 3 + crates/egui/src/context.rs | 4 +- crates/egui/src/data/key.rs | 12 +- crates/egui/src/load/bytes_loader.rs | 2 +- crates/egui/src/response.rs | 2 +- crates/egui/src/text_selection/visuals.rs | 2 +- crates/egui/src/widgets/text_edit/builder.rs | 6 +- crates/egui_demo_app/Cargo.toml | 3 + .../egui_demo_app/src/apps/custom3d_glow.rs | 2 + crates/egui_demo_app/src/apps/http_app.rs | 2 +- crates/egui_demo_app/src/main.rs | 1 + crates/egui_demo_lib/Cargo.toml | 3 + .../src/easy_mark/easy_mark_editor.rs | 2 +- crates/egui_extras/Cargo.toml | 3 + crates/egui_glow/Cargo.toml | 3 + crates/egui_glow/examples/pure_glow.rs | 5 +- crates/egui_glow/src/lib.rs | 1 + crates/egui_glow/src/shader_version.rs | 1 + crates/egui_plot/Cargo.toml | 3 + crates/emath/Cargo.toml | 3 + crates/epaint/Cargo.toml | 3 + examples/confirm_exit/Cargo.toml | 3 + examples/confirm_exit/src/main.rs | 1 + examples/custom_3d_glow/Cargo.toml | 3 + examples/custom_3d_glow/src/main.rs | 2 + examples/custom_font/Cargo.toml | 3 + examples/custom_font/src/main.rs | 1 + examples/custom_font_style/Cargo.toml | 3 + examples/custom_font_style/src/main.rs | 1 + examples/custom_keypad/Cargo.toml | 3 + examples/custom_keypad/src/main.rs | 1 + examples/custom_plot_manipulation/Cargo.toml | 3 + examples/custom_plot_manipulation/src/main.rs | 1 + examples/custom_window_frame/Cargo.toml | 3 + examples/custom_window_frame/src/main.rs | 1 + examples/file_dialog/Cargo.toml | 3 + examples/file_dialog/src/main.rs | 1 + examples/hello_world/Cargo.toml | 3 + examples/hello_world/src/main.rs | 1 + examples/hello_world_par/Cargo.toml | 3 + examples/hello_world_par/src/main.rs | 1 + examples/hello_world_simple/Cargo.toml | 3 + examples/hello_world_simple/src/main.rs | 1 + examples/images/Cargo.toml | 3 + examples/images/src/main.rs | 1 + examples/keyboard_events/Cargo.toml | 3 + examples/keyboard_events/src/main.rs | 1 + examples/multiple_viewports/Cargo.toml | 3 + examples/multiple_viewports/src/main.rs | 1 + examples/puffin_profiler/Cargo.toml | 3 + examples/puffin_profiler/src/main.rs | 1 + examples/save_plot/Cargo.toml | 3 + examples/save_plot/src/main.rs | 1 + examples/screenshot/Cargo.toml | 3 + examples/screenshot/src/main.rs | 1 + examples/serial_windows/Cargo.toml | 3 + examples/serial_windows/src/main.rs | 1 + examples/test_inline_glow_paint/Cargo.toml | 3 + examples/test_inline_glow_paint/src/main.rs | 4 + examples/test_viewports/Cargo.toml | 3 + examples/test_viewports/src/main.rs | 3 + examples/user_attention/Cargo.toml | 3 + examples/user_attention/src/main.rs | 3 + scripts/check.sh | 3 +- scripts/clippy_wasm.sh | 2 +- xtask/Cargo.toml | 3 + xtask/src/main.rs | 2 + 82 files changed, 348 insertions(+), 289 deletions(-) delete mode 100644 Cranky.toml delete mode 100644 bacon.toml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c3bc239276be..98a3b496a219 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,7 +6,7 @@ Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/ * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to add commits to your PR. -* Remember to run `cargo fmt` and `cargo cranky`. +* Remember to run `cargo fmt` and `cargo clippy`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 89983a2c48bd..26404d294a31 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -40,11 +40,6 @@ jobs: - name: Lint vertical spacing run: ./scripts/lint.py - - name: Install cargo-cranky - uses: baptiste0928/cargo-install@v1 - with: - crate: cargo-cranky - - name: check --all-features run: cargo check --locked --all-features --all-targets @@ -78,11 +73,11 @@ jobs: - name: Test run: cargo test --all-features - - name: Cranky - run: cargo cranky --all-targets --all-features -- -D warnings + - name: clippy + run: cargo clippy --all-targets --all-features -- -D warnings - - name: Cranky release - run: cargo cranky --all-targets --all-features --release -- -D warnings + - name: clippy release + run: cargo clippy --all-targets --all-features --release -- -D warnings # --------------------------------------------------------------------------- @@ -101,11 +96,6 @@ jobs: - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - - name: Install cargo-cranky - uses: baptiste0928/cargo-install@v1 - with: - crate: cargo-cranky - - name: Check wasm32 egui_demo_app run: cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown @@ -122,7 +112,7 @@ jobs: - run: ./scripts/wasm_bindgen_check.sh --skip-setup - - name: Cranky wasm32 + - name: clippy wasm32 run: ./scripts/clippy_wasm.sh # --------------------------------------------------------------------------- diff --git a/Cargo.toml b/Cargo.toml index 47ef422cb5a4..c94baab2843e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,3 +84,169 @@ wgpu = { version = "0.19.1", default-features = false, features = [ "fragile-send-sync-non-atomic-wasm", ] } winit = { version = "0.29.4", default-features = false } + + +[workspace.lints.rust] +unsafe_code = "deny" + +elided_lifetimes_in_paths = "warn" +future_incompatible = "warn" +nonstandard_style = "warn" +rust_2018_idioms = "warn" +rust_2021_prelude_collisions = "warn" +semicolon_in_expressions_from_macros = "warn" +trivial_numeric_casts = "warn" +unsafe_op_in_unsafe_fn = "warn" # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668 +unused_extern_crates = "warn" +unused_import_braces = "warn" +unused_lifetimes = "warn" + +trivial_casts = "allow" +unused_qualifications = "allow" + +[workspace.lints.rustdoc] +all = "warn" +missing_crate_level_docs = "warn" + +# See also clippy.toml +[workspace.lints.clippy] +as_ptr_cast_mut = "warn" +await_holding_lock = "warn" +bool_to_int_with_if = "warn" +char_lit_as_u8 = "warn" +checked_conversions = "warn" +clear_with_drain = "warn" +cloned_instead_of_copied = "warn" +dbg_macro = "warn" +debug_assert_with_mut_call = "warn" +derive_partial_eq_without_eq = "warn" +disallowed_macros = "warn" # See clippy.toml +disallowed_methods = "warn" # See clippy.toml +disallowed_names = "warn" # See clippy.toml +disallowed_script_idents = "warn" # See clippy.toml +disallowed_types = "warn" # See clippy.toml +doc_link_with_quotes = "warn" +doc_markdown = "warn" +empty_enum = "warn" +enum_glob_use = "warn" +equatable_if_let = "warn" +exit = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +explicit_iter_loop = "warn" +fallible_impl_from = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp_const = "warn" +fn_params_excessive_bools = "warn" +fn_to_numeric_cast_any = "warn" +from_iter_instead_of_collect = "warn" +get_unwrap = "warn" +if_let_mutex = "warn" +implicit_clone = "warn" +implied_bounds_in_impls = "warn" +imprecise_flops = "warn" +index_refutable_slice = "warn" +inefficient_to_string = "warn" +infinite_loop = "warn" +into_iter_without_iter = "warn" +invalid_upcast_comparisons = "warn" +iter_not_returning_iterator = "warn" +iter_on_empty_collections = "warn" +iter_on_single_items = "warn" +iter_without_into_iter = "warn" +large_digit_groups = "warn" +large_include_file = "warn" +large_stack_arrays = "warn" +large_stack_frames = "warn" +large_types_passed_by_value = "warn" +let_unit_value = "warn" +linkedlist = "warn" +lossy_float_literal = "warn" +macro_use_imports = "warn" +manual_assert = "warn" +manual_clamp = "warn" +manual_instant_elapsed = "warn" +manual_let_else = "warn" +manual_ok_or = "warn" +manual_string_new = "warn" +map_err_ignore = "warn" +map_flatten = "warn" +map_unwrap_or = "warn" +match_on_vec_items = "warn" +match_same_arms = "warn" +match_wild_err_arm = "warn" +match_wildcard_for_single_variants = "warn" +mem_forget = "warn" +mismatched_target_os = "warn" +mismatching_type_param_order = "warn" +missing_enforced_import_renames = "warn" +missing_errors_doc = "warn" +missing_safety_doc = "warn" +mut_mut = "warn" +mutex_integer = "warn" +needless_borrow = "warn" +needless_continue = "warn" +needless_for_each = "warn" +needless_pass_by_ref_mut = "warn" +needless_pass_by_value = "warn" +negative_feature_names = "warn" +nonstandard_macro_braces = "warn" +option_option = "warn" +path_buf_push_overwrite = "warn" +ptr_as_ptr = "warn" +ptr_cast_constness = "warn" +pub_without_shorthand = "warn" +rc_mutex = "warn" +readonly_write_lock = "warn" +redundant_type_annotations = "warn" +ref_option_ref = "warn" +ref_patterns = "warn" +rest_pat_in_fully_bound_structs = "warn" +same_functions_in_if_condition = "warn" +semicolon_if_nothing_returned = "warn" +single_match_else = "warn" +str_to_string = "warn" +string_add = "warn" +string_add_assign = "warn" +string_lit_as_bytes = "warn" +string_lit_chars_any = "warn" +string_to_string = "warn" +suspicious_command_arg_space = "warn" +suspicious_xor_used_as_pow = "warn" +todo = "warn" +trailing_empty_array = "warn" +trait_duplication_in_bounds = "warn" +tuple_array_conversions = "warn" +unchecked_duration_subtraction = "warn" +undocumented_unsafe_blocks = "warn" +unimplemented = "warn" +uninhabited_references = "warn" +uninlined_format_args = "warn" +unnecessary_box_returns = "warn" +unnecessary_safety_doc = "warn" +unnecessary_struct_initialization = "warn" +unnecessary_wraps = "warn" +unnested_or_patterns = "warn" +unused_peekable = "warn" +unused_rounding = "warn" +unused_self = "warn" +useless_transmute = "warn" +verbose_file_reads = "warn" +wildcard_dependencies = "warn" +zero_sized_map_values = "warn" + +# TODO(emilk): enable more of these linits: +iter_over_hash_type = "allow" +let_underscore_untyped = "allow" +missing_assert_message = "allow" +print_stderr = "allow" # TODO(emilk): use `log` crate insteaditer_over_hash_type = "allow" +should_panic_without_expect = "allow" +too_many_lines = "allow" +unwrap_used = "allow" # TODO(emilk): We really wanna warn on this one + +manual_range_contains = "allow" # this one is just worse imho +self_named_module_files = "allow" # Disabled waiting on https://github.com/rust-lang/rust-clippy/issues/9602 +significant_drop_tightening = "allow" # Too many false positives +wildcard_imports = "allow" # we do this a lot in egui diff --git a/Cranky.toml b/Cranky.toml deleted file mode 100644 index db236bff421b..000000000000 --- a/Cranky.toml +++ /dev/null @@ -1,176 +0,0 @@ -# https://github.com/ericseppanen/cargo-cranky -# cargo install cargo-cranky && cargo cranky -# See also clippy.toml - -deny = ["unsafe_code"] - -warn = [ - "clippy::all", - "clippy::as_ptr_cast_mut", - "clippy::await_holding_lock", - "clippy::bool_to_int_with_if", - "clippy::branches_sharing_code", - "clippy::char_lit_as_u8", - "clippy::checked_conversions", - "clippy::clear_with_drain", - "clippy::cloned_instead_of_copied", - "clippy::dbg_macro", - "clippy::debug_assert_with_mut_call", - "clippy::default_union_representation", - "clippy::derive_partial_eq_without_eq", - "clippy::disallowed_macros", # See clippy.toml - "clippy::disallowed_methods", # See clippy.toml - "clippy::disallowed_names", # See clippy.toml - "clippy::disallowed_script_idents", # See clippy.toml - "clippy::disallowed_types", # See clippy.toml - "clippy::doc_link_with_quotes", - "clippy::doc_markdown", - "clippy::empty_enum", - "clippy::empty_line_after_outer_attr", - "clippy::enum_glob_use", - "clippy::equatable_if_let", - "clippy::exit", - "clippy::expl_impl_clone_on_copy", - "clippy::explicit_deref_methods", - "clippy::explicit_into_iter_loop", - "clippy::explicit_iter_loop", - "clippy::fallible_impl_from", - "clippy::filter_map_next", - "clippy::flat_map_option", - "clippy::float_cmp_const", - "clippy::fn_params_excessive_bools", - "clippy::fn_to_numeric_cast_any", - "clippy::from_iter_instead_of_collect", - "clippy::get_unwrap", - "clippy::if_let_mutex", - "clippy::implicit_clone", - "clippy::imprecise_flops", - "clippy::index_refutable_slice", - "clippy::inefficient_to_string", - "clippy::invalid_upcast_comparisons", - "clippy::iter_not_returning_iterator", - "clippy::iter_on_empty_collections", - "clippy::iter_on_single_items", - "clippy::large_digit_groups", - "clippy::large_include_file", - "clippy::large_stack_arrays", - "clippy::large_stack_frames", - "clippy::large_types_passed_by_value", - "clippy::let_unit_value", - "clippy::linkedlist", - "clippy::lossy_float_literal", - "clippy::macro_use_imports", - "clippy::manual_assert", - "clippy::manual_clamp", - "clippy::manual_instant_elapsed", - "clippy::manual_let_else", - "clippy::manual_ok_or", - "clippy::manual_string_new", - "clippy::map_err_ignore", - "clippy::map_flatten", - "clippy::map_unwrap_or", - "clippy::match_on_vec_items", - "clippy::match_same_arms", - "clippy::match_wild_err_arm", - "clippy::match_wildcard_for_single_variants", - "clippy::mem_forget", - "clippy::mismatched_target_os", - "clippy::mismatching_type_param_order", - "clippy::missing_enforced_import_renames", - "clippy::missing_errors_doc", - "clippy::missing_safety_doc", - "clippy::mut_mut", - "clippy::mutex_integer", - "clippy::needless_borrow", - "clippy::needless_continue", - "clippy::needless_for_each", - "clippy::needless_pass_by_value", - "clippy::negative_feature_names", - "clippy::nonstandard_macro_braces", - "clippy::option_option", - "clippy::path_buf_push_overwrite", - "clippy::print_stdout", - "clippy::ptr_as_ptr", - "clippy::ptr_cast_constness", - "clippy::pub_without_shorthand", - "clippy::rc_mutex", - "clippy::redundant_type_annotations", - "clippy::ref_option_ref", - "clippy::rest_pat_in_fully_bound_structs", - "clippy::same_functions_in_if_condition", - "clippy::semicolon_if_nothing_returned", - "clippy::significant_drop_tightening", - "clippy::single_match_else", - "clippy::str_to_string", - "clippy::string_add_assign", - "clippy::string_add", - "clippy::string_lit_as_bytes", - "clippy::string_to_string", - "clippy::suspicious_command_arg_space", - "clippy::suspicious_xor_used_as_pow", - "clippy::todo", - "clippy::trailing_empty_array", - "clippy::trait_duplication_in_bounds", - "clippy::transmute_ptr_to_ptr", - "clippy::tuple_array_conversions", - "clippy::unchecked_duration_subtraction", - "clippy::undocumented_unsafe_blocks", - "clippy::unimplemented", - "clippy::uninlined_format_args", - "clippy::unnecessary_box_returns", - "clippy::unnecessary_safety_comment", - "clippy::unnecessary_safety_doc", - "clippy::unnecessary_self_imports", - "clippy::unnecessary_struct_initialization", - "clippy::unnecessary_wraps", - "clippy::unnested_or_patterns", - "clippy::unused_peekable", - "clippy::unused_rounding", - "clippy::unused_self", - "clippy::use_self", - "clippy::useless_transmute", - "clippy::verbose_file_reads", - "clippy::wildcard_dependencies", - "clippy::wildcard_imports", - "clippy::zero_sized_map_values", - "elided_lifetimes_in_paths", - "future_incompatible", - "nonstandard_style", - "rust_2018_idioms", - "rust_2021_prelude_collisions", - "rustdoc::missing_crate_level_docs", - "semicolon_in_expressions_from_macros", - "trivial_numeric_casts", - "unsafe_op_in_unsafe_fn", # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668 - "unused_extern_crates", - "unused_import_braces", - "unused_lifetimes", - - - # Enable when we update MSRV: - # "clippy::implied_bounds_in_impls", - # "clippy::needless_pass_by_ref_mut", - # "clippy::readonly_write_lock", - # "clippy::should_panic_without_expect", - # "clippy::string_lit_chars_any", -] - -allow = [ - "clippy::manual_range_contains", # this one is just worse imho - "clippy::significant_drop_tightening", # A lot of false positives - - # TODO(emilk): enable more of these lints: - "clippy::cloned_instead_of_copied", - "clippy::let_underscore_untyped", - "clippy::missing_assert_message", - "clippy::missing_errors_doc", - "clippy::print_stderr", # TODO(emilk): use `log` crate instead - "clippy::self_named_module_files", # False positives - "clippy::too_many_lines", - "clippy::undocumented_unsafe_blocks", - "clippy::unwrap_used", - "clippy::useless_let_if_seq", # False positives - "clippy::wildcard_imports", # We do this a lot - "trivial_casts", - "unused_qualifications", -] diff --git a/bacon.toml b/bacon.toml deleted file mode 100644 index 63d72eeb0553..000000000000 --- a/bacon.toml +++ /dev/null @@ -1,74 +0,0 @@ -# This is a configuration file for the bacon tool -# More info at https://github.com/Canop/bacon - -default_job = "cranky" - -[jobs] - -[jobs.cranky] -command = [ - "cargo", - "cranky", - "--all-targets", - "--all-features", - "--color=always", -] -need_stdout = false -watch = ["tests", "benches", "examples"] - -[jobs.wasm] -command = [ - "cargo", - "cranky", - "-p=egui_demo_app", - "--lib", - "--target=wasm32-unknown-unknown", - "--target-dir=target_wasm", - "--all-features", - "--color=always", -] -need_stdout = false -watch = ["tests", "benches", "examples"] - -[jobs.test] -command = ["cargo", "test", "--color=always"] -need_stdout = true -watch = ["tests"] - -[jobs.doc] -command = ["cargo", "doc", "--color=always", "--all-features", "--no-deps"] -need_stdout = false - -# if the doc compiles, then it opens in your browser and bacon switches -# to the previous job -[jobs.doc-open] -command = [ - "cargo", - "doc", - "--color=always", - "--all-features", - "--no-deps", - "--open", -] -need_stdout = false -on_success = "back" # so that we don't open the browser at each change - -# You can run your application and have the result displayed in bacon, -# *if* it makes sense for this crate. You can run an example the same -# way. Don't forget the `--color always` part or the errors won't be -# properly parsed. -[jobs.run] -command = ["cargo", "run", "--color=always"] -need_stdout = true - -# You may define here keybindings that would be specific to -# a project, for example a shortcut to launch a specific job. -# Shortcuts to internal functions (scrolling, toggling, etc.) -# should go in your personal prefs.toml file instead. -[keybindings] -i = "job:initial" -c = "job:cranky" -a = "job:wasm" -d = "job:doc-open" -t = "job:test" -r = "job:run" diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index 611b9a4dec98..ad5d6f96369d 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -16,6 +16,9 @@ categories = ["mathematics", "encoding"] keywords = ["gui", "color", "conversion", "gamedev", "images"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 5b2651c9ad4e..c5cfae877332 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -24,6 +24,9 @@ all-features = true rustc-args = ["--cfg=web_sys_unstable_apis"] targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] +[lints] +workspace = true + [lib] diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index 39c32dfdb3e3..90100298680f 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -224,6 +224,7 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS static NSApp: Option<&'static NSApplication>; } + // SAFETY: we don't do anything dangerous here unsafe { let Some(app) = NSApp else { log::debug!("NSApp is null"); diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 880b906c2b89..f08ed14d6235 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -5,7 +5,11 @@ //! There is a bunch of improvements we could do, //! like removing a bunch of `unwraps`. -#![allow(clippy::arc_with_non_send_sync)] // glow::Context was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e +// `clippy::arc_with_non_send_sync`: `glow::Context` was accidentally non-Sync in glow 0.13, +// but that will be fixed in future releases of glow. +// https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e +#![allow(clippy::arc_with_non_send_sync)] +#![allow(clippy::undocumented_unsafe_blocks)] use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant}; diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index c61b70935965..5cfec81bf30f 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -119,7 +119,7 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> { } /// Focus or blur text agent to toggle mobile keyboard. -pub fn update_text_agent(runner: &mut AppRunner) -> Option<()> { +pub fn update_text_agent(runner: &AppRunner) -> Option<()> { use web_sys::HtmlInputElement; let window = web_sys::window()?; let document = window.document()?; diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 09e48e076431..817233b88bad 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -23,6 +23,9 @@ include = [ "Cargo.toml", ] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 07c917155aae..84dd0b412359 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -1,3 +1,6 @@ +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::undocumented_unsafe_blocks)] + use std::{num::NonZeroU32, sync::Arc}; use egui::{ViewportId, ViewportIdMap, ViewportIdSet}; diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index a09829699fc1..fc2b8a536200 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -13,6 +13,9 @@ categories = ["gui", "game-development"] keywords = ["winit", "egui", "gui", "gamedev"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui-winit/src/clipboard.rs b/crates/egui-winit/src/clipboard.rs index c86a9e19d5b6..44e3840b64f0 100644 --- a/crates/egui-winit/src/clipboard.rs +++ b/crates/egui-winit/src/clipboard.rs @@ -137,6 +137,8 @@ fn init_arboard() -> Option { fn init_smithay_clipboard( raw_display_handle: Option, ) -> Option { + #![allow(clippy::undocumented_unsafe_blocks)] + crate::profile_function!(); if let Some(RawDisplayHandle::Wayland(display)) = raw_display_handle { diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 313cc58906bc..abadd9ee5c22 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -56,7 +56,7 @@ pub struct EventResponse { /// (e.g. a mouse click on an egui window, or entering text into a text field). /// /// For instance, if you use egui for a game, you should only - /// pass on the events to your game when [`Self::consumed`] is `false. + /// pass on the events to your game when [`Self::consumed`] is `false`. /// /// Note that egui uses `tab` to move focus between elements, so this will always be `true` for tabs. pub consumed: bool, @@ -1521,6 +1521,9 @@ fn process_viewport_command( /// Build and intitlaize a window. /// /// Wrapper around `create_winit_window_builder` and `apply_viewport_builder_to_window`. +/// +/// # Errors +/// Possible causes of error include denied permission, incompatible system, and lack of memory. pub fn create_window( egui_ctx: &egui::Context, event_loop: &EventLoopWindowTarget, diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 87b25a7ea4b7..a1929f060ade 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -13,6 +13,9 @@ categories = ["gui", "game-development"] keywords = ["gui", "imgui", "immediate", "portable", "gamedev"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index aa8a0b13e32c..192b0cc49d46 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1838,7 +1838,7 @@ impl Context { let paint_widget_id = |id: Id, text: &str, color: Color32| { if let Some(widget) = - self.write(|ctx| ctx.viewport().widgets_this_frame.get(id).cloned()) + self.write(|ctx| ctx.viewport().widgets_this_frame.get(id).copied()) { paint_widget(&widget, text, color); } @@ -2398,7 +2398,7 @@ impl Context { /// See also [`Response::contains_pointer`]. pub fn rect_contains_pointer(&self, layer_id: LayerId, rect: Rect) -> bool { let rect = - if let Some(transform) = self.memory(|m| m.layer_transforms.get(&layer_id).cloned()) { + if let Some(transform) = self.memory(|m| m.layer_transforms.get(&layer_id).copied()) { transform * rect } else { rect diff --git a/crates/egui/src/data/key.rs b/crates/egui/src/data/key.rs index 92e6b69dba25..66fe6b9336fc 100644 --- a/crates/egui/src/data/key.rs +++ b/crates/egui/src/data/key.rs @@ -35,25 +35,25 @@ pub enum Key { /// `,` Comma, - /// '\\' + /// `\` Backslash, - /// '/' + /// `/` Slash, - /// '|', a vertical bar + /// `|`, a vertical bar Pipe, /// `?` Questionmark, - // '[' + // `[` OpenBracket, - // ']' + // `]` CloseBracket, - /// '`', also known as "backquote" or "grave" + /// \`, also known as "backquote" or "grave" Backtick, /// `-` diff --git a/crates/egui/src/load/bytes_loader.rs b/crates/egui/src/load/bytes_loader.rs index 3ab46794912c..d03b2ad418af 100644 --- a/crates/egui/src/load/bytes_loader.rs +++ b/crates/egui/src/load/bytes_loader.rs @@ -53,7 +53,7 @@ impl BytesLoader for DefaultBytesLoader { #[cfg(feature = "log")] log::trace!("forget {uri:?}"); - let _ = self.cache.lock().remove(uri); + self.cache.lock().remove(uri); } fn forget_all(&self) { diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 1bee1b298608..44eb4f74bd64 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -465,7 +465,7 @@ impl Response { let mut pos = self.ctx.input(|i| i.pointer.hover_pos())?; if let Some(transform) = self .ctx - .memory(|m| m.layer_transforms.get(&self.layer_id).cloned()) + .memory(|m| m.layer_transforms.get(&self.layer_id).copied()) { pos = transform * pos; } diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index 4fc8af0abd46..d31f1756eddb 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -78,7 +78,7 @@ pub fn paint_cursor_end(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) /// Paint one end of the selection, e.g. the primary cursor, with blinking (if enabled). pub fn paint_text_cursor( - ui: &mut Ui, + ui: &Ui, painter: &Painter, primary_cursor_rect: Rect, time_since_last_edit: f64, diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index c0b8532d6d95..440916f31b2e 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -702,7 +702,7 @@ impl<'t> TextEdit<'t> { // Set IME output (in screen coords) when text is editable and visible let transform = ui - .memory(|m| m.layer_transforms.get(&ui.layer_id()).cloned()) + .memory(|m| m.layer_transforms.get(&ui.layer_id()).copied()) .unwrap_or_default(); ui.ctx().output_mut(|o| { @@ -948,7 +948,7 @@ fn events( key, pressed: true, .. - } => check_for_mutating_key_press(os, &mut cursor_range, text, galley, modifiers, *key), + } => check_for_mutating_key_press(os, &cursor_range, text, galley, modifiers, *key), Event::Ime(ime_event) => match ime_event { ImeEvent::Enabled => { @@ -1028,7 +1028,7 @@ fn events( /// Returns `Some(new_cursor)` if we did mutate `text`. fn check_for_mutating_key_press( os: OperatingSystem, - cursor_range: &mut CursorRange, + cursor_range: &CursorRange, text: &mut dyn TextBuffer, galley: &Galley, modifiers: &Modifiers, diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index b928702e08ba..4ae9572bd0a6 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -8,6 +8,9 @@ rust-version.workspace = true publish = false default-run = "egui_demo_app" +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui_demo_app/src/apps/custom3d_glow.rs b/crates/egui_demo_app/src/apps/custom3d_glow.rs index 7f488e7671e6..ad6bfc3bc608 100644 --- a/crates/egui_demo_app/src/apps/custom3d_glow.rs +++ b/crates/egui_demo_app/src/apps/custom3d_glow.rs @@ -1,3 +1,5 @@ +#![allow(clippy::undocumented_unsafe_blocks)] + use std::sync::Arc; use eframe::egui_glow; diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index d0da5c0a3953..d6b57284267d 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -116,7 +116,7 @@ impl eframe::App for HttpApp { } } -fn ui_url(ui: &mut egui::Ui, frame: &mut eframe::Frame, url: &mut String) -> bool { +fn ui_url(ui: &mut egui::Ui, frame: &eframe::Frame, url: &mut String) -> bool { let mut trigger_fetch = false; ui.horizontal(|ui| { diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index 45b0ee067a4f..9d660ed046cd 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -1,6 +1,7 @@ //! Demo app for egui #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example #![allow(clippy::never_loop)] // False positive // When compiling natively: diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index 8c5a703b5d65..d1daa5b8cfba 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -19,6 +19,9 @@ include = [ "data/icon.png", ] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs index 0b0bb9608a3b..be2650cb03e3 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs @@ -45,7 +45,7 @@ impl EasyMarkEditor { pub fn ui(&mut self, ui: &mut egui::Ui) { egui::Grid::new("controls").show(ui, |ui| { - let _ = ui.button("Hotkeys").on_hover_ui(nested_hotkeys_ui); + let _response = ui.button("Hotkeys").on_hover_ui(nested_hotkeys_ui); ui.checkbox(&mut self.show_rendered, "Show rendered"); ui.checkbox(&mut self.highlight_editor, "Highlight editor"); egui::reset_button(ui, self, "Reset"); diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index a0829180924a..372f754cc52e 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -17,6 +17,9 @@ categories = ["gui", "game-development"] keywords = ["gui", "imgui", "immediate", "portable", "gamedev"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 1ee1f6cd90b3..6bb081980d3a 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -19,6 +19,9 @@ include = [ "src/shader/*.glsl", ] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/egui_glow/examples/pure_glow.rs b/crates/egui_glow/examples/pure_glow.rs index 3da382af6ec2..70f07421475c 100644 --- a/crates/egui_glow/examples/pure_glow.rs +++ b/crates/egui_glow/examples/pure_glow.rs @@ -1,8 +1,11 @@ //! Example how to use pure `egui_glow`. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example +#![allow(clippy::undocumented_unsafe_blocks)] +#![allow(clippy::arc_with_non_send_sync)] +// `clippy::arc_with_non_send_sync`: `glow::Context` was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e #![allow(unsafe_code)] -#![allow(clippy::arc_with_non_send_sync)] // glow::Context was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e use std::num::NonZeroU32; diff --git a/crates/egui_glow/src/lib.rs b/crates/egui_glow/src/lib.rs index b12d2ff5f61a..8621dbd74e7d 100644 --- a/crates/egui_glow/src/lib.rs +++ b/crates/egui_glow/src/lib.rs @@ -10,6 +10,7 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] +#![allow(clippy::undocumented_unsafe_blocks)] pub mod painter; pub use glow; diff --git a/crates/egui_glow/src/shader_version.rs b/crates/egui_glow/src/shader_version.rs index c59792a0aba3..77c1e63cea95 100644 --- a/crates/egui_glow/src/shader_version.rs +++ b/crates/egui_glow/src/shader_version.rs @@ -1,4 +1,5 @@ #![allow(unsafe_code)] +#![allow(clippy::undocumented_unsafe_blocks)] use std::convert::TryInto; diff --git a/crates/egui_plot/Cargo.toml b/crates/egui_plot/Cargo.toml index 61faf7432c09..91c4b3a50fc4 100644 --- a/crates/egui_plot/Cargo.toml +++ b/crates/egui_plot/Cargo.toml @@ -17,6 +17,9 @@ categories = ["visualization", "gui"] keywords = ["egui", "plot", "plotting"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/emath/Cargo.toml b/crates/emath/Cargo.toml index a48a9cbdbafb..ae0153cc9f2b 100644 --- a/crates/emath/Cargo.toml +++ b/crates/emath/Cargo.toml @@ -13,6 +13,9 @@ categories = ["mathematics", "gui"] keywords = ["math", "gui"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index a24a299b8ed3..861afb1a14bb 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -22,6 +22,9 @@ include = [ "fonts/UFL.txt", ] +[lints] +workspace = true + [package.metadata.docs.rs] all-features = true diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index 8e9d3af28727..b1cab21a1bba 100644 --- a/examples/confirm_exit/Cargo.toml +++ b/examples/confirm_exit/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/confirm_exit/src/main.rs b/examples/confirm_exit/src/main.rs index 3a09da59d91e..14816e1bdeab 100644 --- a/examples/confirm_exit/src/main.rs +++ b/examples/confirm_exit/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index ef3d1cbc828d..3b8c9a453838 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_3d_glow/src/main.rs b/examples/custom_3d_glow/src/main.rs index a1f6fa269464..241124b7c174 100644 --- a/examples/custom_3d_glow/src/main.rs +++ b/examples/custom_3d_glow/src/main.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example #![allow(unsafe_code)] +#![allow(clippy::undocumented_unsafe_blocks)] use eframe::{egui, egui_glow, glow}; diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index 89840c4526db..d6021c124251 100644 --- a/examples/custom_font/Cargo.toml +++ b/examples/custom_font/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_font/src/main.rs b/examples/custom_font/src/main.rs index 8a852319dd1f..f42c5763150b 100644 --- a/examples/custom_font/src/main.rs +++ b/examples/custom_font/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index 60e3e37b9c2a..65b5045238c0 100644 --- a/examples/custom_font_style/Cargo.toml +++ b/examples/custom_font_style/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_font_style/src/main.rs b/examples/custom_font_style/src/main.rs index 89117b4f01a8..c1a61e3e69ca 100644 --- a/examples/custom_font_style/src/main.rs +++ b/examples/custom_font_style/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; use egui::{FontFamily, FontId, RichText, TextStyle}; diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index f008884c5c7b..7d000fd0d469 100644 --- a/examples/custom_keypad/Cargo.toml +++ b/examples/custom_keypad/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_keypad/src/main.rs b/examples/custom_keypad/src/main.rs index 5cb26240cbde..654de25fa44c 100644 --- a/examples/custom_keypad/src/main.rs +++ b/examples/custom_keypad/src/main.rs @@ -1,4 +1,5 @@ // #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; mod keypad; diff --git a/examples/custom_plot_manipulation/Cargo.toml b/examples/custom_plot_manipulation/Cargo.toml index 3db38a45b925..fc7b66c5ccc0 100644 --- a/examples/custom_plot_manipulation/Cargo.toml +++ b/examples/custom_plot_manipulation/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_plot_manipulation/src/main.rs b/examples/custom_plot_manipulation/src/main.rs index 4dffaf18ed12..e423d890fcbb 100644 --- a/examples/custom_plot_manipulation/src/main.rs +++ b/examples/custom_plot_manipulation/src/main.rs @@ -1,5 +1,6 @@ //! This example shows how to implement custom gestures to pan and zoom in the plot #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui::{self, DragValue, Event, Vec2}; use egui_plot::{Legend, Line, PlotPoints}; diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index c334c6a662bb..6b800d0a0af5 100644 --- a/examples/custom_window_frame/Cargo.toml +++ b/examples/custom_window_frame/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/custom_window_frame/src/main.rs b/examples/custom_window_frame/src/main.rs index 87daf82e06a8..bb098652cbfe 100644 --- a/examples/custom_window_frame/src/main.rs +++ b/examples/custom_window_frame/src/main.rs @@ -1,6 +1,7 @@ //! Show a custom window frame instead of the default OS window chrome decorations. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui::{self, ViewportCommand}; diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index ef30dfce8e11..9684a423256e 100644 --- a/examples/file_dialog/Cargo.toml +++ b/examples/file_dialog/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index 4077c6de4a2e..d0623a73a286 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index acf7e2b8ab89..bdd728a61e18 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/hello_world/src/main.rs b/examples/hello_world/src/main.rs index b3fda5a58105..1c74f4c49928 100644 --- a/examples/hello_world/src/main.rs +++ b/examples/hello_world/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 3d0e92eaec44..e64cdd36108d 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, default-features = false, features = [ diff --git a/examples/hello_world_par/src/main.rs b/examples/hello_world_par/src/main.rs index 617e840dc4cd..2896f24843fc 100644 --- a/examples/hello_world_par/src/main.rs +++ b/examples/hello_world_par/src/main.rs @@ -1,6 +1,7 @@ //! This example shows that you can use egui in parallel from multiple threads. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use std::sync::mpsc; use std::thread::JoinHandle; diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index bec4670b790f..c88382b9f6de 100644 --- a/examples/hello_world_simple/Cargo.toml +++ b/examples/hello_world_simple/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/hello_world_simple/src/main.rs b/examples/hello_world_simple/src/main.rs index 5f0ed31a49f5..4e48feeff90b 100644 --- a/examples/hello_world_simple/src/main.rs +++ b/examples/hello_world_simple/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index cd9157f5715f..092535c91250 100644 --- a/examples/images/Cargo.toml +++ b/examples/images/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/images/src/main.rs b/examples/images/src/main.rs index efb088b67270..8a124e051a55 100644 --- a/examples/images/src/main.rs +++ b/examples/images/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index 08f7ba3a81ed..f7b120470621 100644 --- a/examples/keyboard_events/Cargo.toml +++ b/examples/keyboard_events/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/keyboard_events/src/main.rs b/examples/keyboard_events/src/main.rs index 581ae11eb749..bf0b3389981d 100644 --- a/examples/keyboard_events/src/main.rs +++ b/examples/keyboard_events/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; use egui::*; diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index b5bd8e80411a..7aad28550fc7 100644 --- a/examples/multiple_viewports/Cargo.toml +++ b/examples/multiple_viewports/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [features] wgpu = ["eframe/wgpu"] diff --git a/examples/multiple_viewports/src/main.rs b/examples/multiple_viewports/src/main.rs index 0ef06e7815f4..262002e2c5b7 100644 --- a/examples/multiple_viewports/src/main.rs +++ b/examples/multiple_viewports/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use std::sync::{ atomic::{AtomicBool, Ordering}, diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index 6b6acad726d0..d787eeb9168b 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [features] wgpu = ["eframe/wgpu"] diff --git a/examples/puffin_profiler/src/main.rs b/examples/puffin_profiler/src/main.rs index 521184f506b0..8fe30d679fff 100644 --- a/examples/puffin_profiler/src/main.rs +++ b/examples/puffin_profiler/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use std::sync::{ atomic::{AtomicBool, Ordering}, diff --git a/examples/save_plot/Cargo.toml b/examples/save_plot/Cargo.toml index 7c9eb245285f..c4c287c001de 100644 --- a/examples/save_plot/Cargo.toml +++ b/examples/save_plot/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ "default", diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index 3f13d3f28b69..61657faae861 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; use egui_plot::{Legend, Line, Plot, PlotPoints}; diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index 0fb5da7fd2a5..aba066669c94 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -10,6 +10,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 91f2f92cfc06..c0c627e86a79 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use std::sync::Arc; diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index 58d5cdecced3..eebd08957afb 100644 --- a/examples/serial_windows/Cargo.toml +++ b/examples/serial_windows/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ diff --git a/examples/serial_windows/src/main.rs b/examples/serial_windows/src/main.rs index 57dccaa8b0ec..04bed8006aed 100644 --- a/examples/serial_windows/src/main.rs +++ b/examples/serial_windows/src/main.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; diff --git a/examples/test_inline_glow_paint/Cargo.toml b/examples/test_inline_glow_paint/Cargo.toml index 893b0d07de1f..ae66b6abdd39 100644 --- a/examples/test_inline_glow_paint/Cargo.toml +++ b/examples/test_inline_glow_paint/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/examples/test_inline_glow_paint/src/main.rs b/examples/test_inline_glow_paint/src/main.rs index 1710ee154a6f..7851ad63b8b3 100644 --- a/examples/test_inline_glow_paint/src/main.rs +++ b/examples/test_inline_glow_paint/src/main.rs @@ -1,3 +1,7 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example +#![allow(clippy::undocumented_unsafe_blocks)] + // Test that we can paint to the screen using glow directly. use eframe::egui; diff --git a/examples/test_viewports/Cargo.toml b/examples/test_viewports/Cargo.toml index 4e106e37ec86..142bafe22930 100644 --- a/examples/test_viewports/Cargo.toml +++ b/examples/test_viewports/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [features] wgpu = ["eframe/wgpu"] diff --git a/examples/test_viewports/src/main.rs b/examples/test_viewports/src/main.rs index 056796680132..8bbb1b22a070 100644 --- a/examples/test_viewports/src/main.rs +++ b/examples/test_viewports/src/main.rs @@ -1,3 +1,6 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example + use std::sync::Arc; use eframe::egui; diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 10f0ce711c0d..6c3c91cdc250 100644 --- a/examples/user_attention/Cargo.toml +++ b/examples/user_attention/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" rust-version = "1.76" publish = false +[lints] +workspace = true + [dependencies] eframe = { workspace = true, features = [ "default", diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index 4625abefa93c..cbe27b6d5bc1 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -1,3 +1,6 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's an example + use eframe::{egui, CreationContext, NativeOptions}; use egui::{Button, CentralPanel, Context, UserAttentionType}; diff --git a/scripts/check.sh b/scripts/check.sh index c1852a2d81ae..5b802f6c606d 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -9,7 +9,6 @@ set -x # Checks all tests, lints etc. # Basically does what the CI does. -cargo install --quiet cargo-cranky # Uses lints defined in Cranky.toml. See https://github.com/ericseppanen/cargo-cranky cargo +1.75.0 install --quiet typos-cli # web_sys_unstable_apis is required to enable the web_sys clipboard API which eframe web uses, @@ -24,7 +23,7 @@ cargo fmt --all -- --check cargo doc --quiet --lib --no-deps --all-features cargo doc --quiet --document-private-items --no-deps --all-features -cargo cranky --quiet --all-targets --all-features -- -D warnings +cargo clippy --quiet --all-targets --all-features -- -D warnings ./scripts/clippy_wasm.sh cargo check --quiet --all-targets diff --git a/scripts/clippy_wasm.sh b/scripts/clippy_wasm.sh index 72f6cc0e53e2..3d80bd0f7032 100755 --- a/scripts/clippy_wasm.sh +++ b/scripts/clippy_wasm.sh @@ -10,4 +10,4 @@ set -x # Use scripts/clippy_wasm/clippy.toml export CLIPPY_CONF_DIR="scripts/clippy_wasm" -cargo cranky --quiet --all-features --target wasm32-unknown-unknown --target-dir target_wasm -p egui_demo_app --lib -- --deny warnings +cargo clippy --quiet --all-features --target wasm32-unknown-unknown --target-dir target_wasm -p egui_demo_app --lib -- --deny warnings diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index ba5b93820892..26bdbb47e596 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -6,4 +6,7 @@ rust-version.workspace = true version.workspace = true publish = false +[lints] +workspace = true + [dependencies] diff --git a/xtask/src/main.rs b/xtask/src/main.rs index b194629203a6..0f16c5454276 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,3 +1,5 @@ +//! Helper crate for running scripts within the `egui` repo + #![allow(clippy::print_stdout)] #![allow(clippy::print_stderr)] #![allow(clippy::exit)] From 0bc59f578b907e357f118dba362d89d5e36f4c31 Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Fri, 26 Apr 2024 07:46:25 +0200 Subject: [PATCH 0071/1202] Fix some typos from the cranky-clippy transition (#4417) --- .vscode/settings.json | 4 ++-- Cargo.toml | 4 ++-- clippy.toml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8ec1e4aa67f5..681dc12fe15f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,7 +15,7 @@ // Tell Rust Analyzer to use its own target directory, so we don't need to wait for it to finish wen we want to `cargo run` "rust-analyzer.check.overrideCommand": [ "cargo", - "cranky", + "clippy", "--target-dir=target_ra", "--workspace", "--message-format=json", @@ -24,7 +24,7 @@ ], "rust-analyzer.cargo.buildScripts.overrideCommand": [ "cargo", - "cranky", + "clippy", "--quiet", "--target-dir=target_ra", "--workspace", diff --git a/Cargo.toml b/Cargo.toml index c94baab2843e..0fd46f9ec0c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -237,11 +237,11 @@ verbose_file_reads = "warn" wildcard_dependencies = "warn" zero_sized_map_values = "warn" -# TODO(emilk): enable more of these linits: +# TODO(emilk): enable more of these lints: iter_over_hash_type = "allow" let_underscore_untyped = "allow" missing_assert_message = "allow" -print_stderr = "allow" # TODO(emilk): use `log` crate insteaditer_over_hash_type = "allow" +print_stderr = "allow" # TODO(emilk): use `log` crate instead should_panic_without_expect = "allow" too_many_lines = "allow" unwrap_used = "allow" # TODO(emilk): We really wanna warn on this one diff --git a/clippy.toml b/clippy.toml index d91f9666c479..f480ca93d7eb 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,4 +1,4 @@ -# There is also a scripts/clippy_wasm/clippy.toml which forbids some mthods that are not available in wasm. +# There is also a scripts/clippy_wasm/clippy.toml which forbids some methods that are not available in wasm. # ----------------------------------------------------------------------------- # Section identical to scripts/clippy_wasm/clippy.toml: @@ -67,7 +67,7 @@ disallowed-types = [ # ----------------------------------------------------------------------------- -# Allow-list of words for markdown in dosctrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown +# Allow-list of words for markdown in docstrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown doc-valid-idents = [ # You must also update the same list in the root `clippy.toml`! "AccessKit", From c1fc9213c3fe9e1a034e42515b5db647cd2313b0 Mon Sep 17 00:00:00 2001 From: Simon Frankau Date: Mon, 29 Apr 2024 09:33:23 +0100 Subject: [PATCH 0072/1202] Enable egui_glow's winit feature on wasm (#4420) (#4421) Reverts change in #1303, enabling the egui_glow::winit module when using wasm. * Closes --- crates/egui_glow/Cargo.toml | 2 +- crates/egui_glow/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 6bb081980d3a..44939640cfbd 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -54,6 +54,7 @@ x11 = ["winit?/x11"] [dependencies] egui = { workspace = true, default-features = false, features = ["bytemuck"] } +egui-winit = { workspace = true, optional = true, default-features = false } bytemuck = "1.7" glow.workspace = true @@ -66,7 +67,6 @@ document-features = { workspace = true, optional = true } # Native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -egui-winit = { workspace = true, optional = true, default-features = false } puffin = { workspace = true, optional = true } winit = { workspace = true, optional = true, default-features = false, features = [ "rwh_06", # for compatibility with egui-winit diff --git a/crates/egui_glow/src/lib.rs b/crates/egui_glow/src/lib.rs index 8621dbd74e7d..cfb95acd3d7d 100644 --- a/crates/egui_glow/src/lib.rs +++ b/crates/egui_glow/src/lib.rs @@ -21,9 +21,9 @@ mod vao; pub use shader_version::ShaderVersion; -#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] +#[cfg(feature = "winit")] pub mod winit; -#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] +#[cfg(feature = "winit")] pub use winit::*; /// Check for OpenGL error and report it using `log::error`. From 3bb33980a93547c41898a2f0ba294843adaacea7 Mon Sep 17 00:00:00 2001 From: hardlydearly <167623323+hardlydearly@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:33:29 +0800 Subject: [PATCH 0073/1202] chore: remove repetitive words (#4400) remove repetitive words Signed-off-by: hardlydearly <799511800@qq.com> --- crates/egui/src/callstack.rs | 2 +- crates/egui/src/containers/frame.rs | 2 +- crates/egui/src/context.rs | 4 ++-- crates/egui/src/memory.rs | 4 ++-- crates/egui/src/text_selection/cursor_range.rs | 2 +- crates/egui/src/text_selection/text_cursor_state.rs | 2 +- crates/egui/src/ui.rs | 4 ++-- crates/egui/src/viewport.rs | 4 ++-- crates/egui/src/widgets/text_edit/state.rs | 2 +- crates/epaint/src/tessellator.rs | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/egui/src/callstack.rs b/crates/egui/src/callstack.rs index fac5ac5936ee..aa6f44cb08da 100644 --- a/crates/egui/src/callstack.rs +++ b/crates/egui/src/callstack.rs @@ -77,7 +77,7 @@ pub fn capture() -> String { // Remove stuff that isn't user calls: let skip_prefixes = [ - // "backtrace::", // not needed, since we cut at at egui::callstack::capture + // "backtrace::", // not needed, since we cut at egui::callstack::capture "egui::", "", diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 82ef47770a74..40bd17d3c4d5 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -306,7 +306,7 @@ impl Prepared { self.content_ui.min_rect() + self.frame.inner_margin + self.frame.outer_margin } - /// Allocate the the space that was used by [`Self::content_ui`]. + /// Allocate the space that was used by [`Self::content_ui`]. /// /// This MUST be called, or the parent ui will not know how much space this widget used. /// diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 192b0cc49d46..f697fb4b8e51 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -257,7 +257,7 @@ pub struct RepaintCause { /// What file had the call that requested the repaint? pub file: &'static str, - /// What line number of the the call that requested the repaint? + /// What line number of the call that requested the repaint? pub line: u32, } @@ -924,7 +924,7 @@ impl Context { self.write(move |ctx| writer(&mut ctx.memory.options.tessellation_options)) } - /// If the given [`Id`] has been used previously the same frame at at different position, + /// If the given [`Id`] has been used previously the same frame at different position, /// then an error will be printed on screen. /// /// This function is already called for all widgets that do any interaction, diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 2ef8bbf58ba3..4bee395c530b 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -132,7 +132,7 @@ enum FocusDirection { /// Select the widget below the current focused widget. Down, - /// Select the widget to the left of the the current focused widget. + /// Select the widget to the left of the current focused widget. Left, /// Select the previous widget that had focus. @@ -812,7 +812,7 @@ impl Memory { /// ## Popups /// Popups are things like combo-boxes, color pickers, menus etc. -/// Only one can be be open at a time. +/// Only one can be open at a time. impl Memory { /// Is the given popup open? pub fn is_popup_open(&self, popup_id: Id) -> bool { diff --git a/crates/egui/src/text_selection/cursor_range.rs b/crates/egui/src/text_selection/cursor_range.rs index cebcf3fb56d9..cf17a2e9ba11 100644 --- a/crates/egui/src/text_selection/cursor_range.rs +++ b/crates/egui/src/text_selection/cursor_range.rs @@ -63,7 +63,7 @@ impl CursorRange { self.primary.ccursor == self.secondary.ccursor } - /// Is `self` a super-set of the the other range? + /// Is `self` a super-set of the other range? pub fn contains(&self, other: &Self) -> bool { let [self_min, self_max] = self.sorted_cursors(); let [other_min, other_max] = other.sorted_cursors(); diff --git a/crates/egui/src/text_selection/text_cursor_state.rs b/crates/egui/src/text_selection/text_cursor_state.rs index 7aca96f8b194..bd596e7e8460 100644 --- a/crates/egui/src/text_selection/text_cursor_state.rs +++ b/crates/egui/src/text_selection/text_cursor_state.rs @@ -46,7 +46,7 @@ impl TextCursorState { self.cursor_range.is_none() && self.ccursor_range.is_none() } - /// The the currently selected range of characters. + /// The currently selected range of characters. pub fn char_range(&self) -> Option { self.ccursor_range.or_else(|| { self.cursor_range diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index d160f5d404d0..1215d0eeb0ff 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1886,7 +1886,7 @@ impl Ui { /// adjusted up and down to lie in the center of the horizontal layout. /// The initial height is `style.spacing.interact_size.y`. /// Centering is almost always what you want if you are - /// planning to to mix widgets or use different types of text. + /// planning to mix widgets or use different types of text. /// /// If you don't want the contents to be centered, use [`Self::horizontal_top`] instead. /// @@ -1947,7 +1947,7 @@ impl Ui { /// adjusted up and down to lie in the center of the horizontal layout. /// The initial height is `style.spacing.interact_size.y`. /// Centering is almost always what you want if you are - /// planning to to mix widgets or use different types of text. + /// planning to mix widgets or use different types of text. /// /// The returned [`Response`] will only have checked for mouse hover /// but can be used for tooltips (`on_hover_text`). diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index e1e3ebf0e82e..cce904877217 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -889,7 +889,7 @@ pub enum X11WindowType { /// This property is typically used on override-redirect windows. Combo, - /// This indicates the the window is being dragged. + /// This indicates the window is being dragged. /// This property is typically used on override-redirect windows. Dnd, } @@ -1015,7 +1015,7 @@ pub enum ViewportCommand { /// Set window to be always-on-top, always-on-bottom, or neither. WindowLevel(WindowLevel), - /// The the window icon. + /// The window icon. Icon(Option>), /// Set the IME cursor editing area. diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index ef4a8909a737..c95d676910a9 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -67,7 +67,7 @@ impl TextEditState { ctx.data_mut(|d| d.insert_persisted(id, self)); } - /// The the currently selected range of characters. + /// The currently selected range of characters. #[deprecated = "Use `self.cursor.char_range` instead"] pub fn ccursor_range(&self) -> Option { self.cursor.char_range() diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index c7dc31b75ab5..4135cc58d22b 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -653,7 +653,7 @@ pub struct TessellationOptions { /// Default: `true`. pub feathering: bool, - /// The size of the the feathering, in physical pixels. + /// The size of the feathering, in physical pixels. /// /// The default, and suggested, value for this is `1.0`. /// If you use a larger value, edges will appear blurry. From af39bd22ab3c7f995b7341745ccb99570a094de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Jos=C3=A9=20Pereira?= Date: Mon, 29 Apr 2024 06:16:55 -0300 Subject: [PATCH 0074/1202] crates: egui_demo_lib: Fix table height (#4422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Closes #4404 Signed-off-by: Patrick José Pereira --- crates/egui_demo_lib/src/demo/table_demo.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index 4cf0449fb6f9..c7029d44e499 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -136,6 +136,7 @@ impl TableDemo { .size .max(ui.spacing().interact_size.y); + let available_height = ui.available_height(); let mut table = TableBuilder::new(ui) .striped(self.striped) .resizable(self.resizable) @@ -145,7 +146,8 @@ impl TableDemo { .column(Column::initial(100.0).range(40.0..=300.0)) .column(Column::initial(100.0).at_least(40.0).clip(true)) .column(Column::remainder()) - .min_scrolled_height(0.0); + .min_scrolled_height(0.0) + .max_scroll_height(available_height); if self.clickable { table = table.sense(egui::Sense::click()); From 2fabde1396e3ae5a225ec7e8619678ea1b8f9bca Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 29 Apr 2024 08:22:34 -0400 Subject: [PATCH 0075/1202] Add a `Display` impl for `Vec2`, `Pos2`, and `Rect` (#4428) These three types currently have a `Debug` implementation that only ever prints one decimal point. Sometimes it is useful to see more of the number, or otherwise have specific formatting. Add `Display` implementations that pass the format specification to the member `f32`s for an easier way to control what is shown when debugging. This allows doing e.g. `ui.label(format!("{:.4}", rect * scale))` which currently prints zeroes if scale is small. --- crates/emath/src/pos2.rs | 16 ++++++++++++++-- crates/emath/src/rect.rs | 16 ++++++++++++++-- crates/emath/src/vec2.rs | 16 ++++++++++++++-- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/crates/emath/src/pos2.rs b/crates/emath/src/pos2.rs index f65efaef43d2..6e0ef212ba1f 100644 --- a/crates/emath/src/pos2.rs +++ b/crates/emath/src/pos2.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::ops::{Add, AddAssign, Sub, SubAssign}; use crate::*; @@ -316,8 +317,19 @@ impl Div for Pos2 { } } -impl std::fmt::Debug for Pos2 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Pos2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[{:.1} {:.1}]", self.x, self.y) } } + +impl fmt::Display for Pos2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[")?; + self.x.fmt(f)?; + f.write_str(" ")?; + self.y.fmt(f)?; + f.write_str("]")?; + Ok(()) + } +} diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index f23d35005204..c1a7f603ecbb 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -1,4 +1,5 @@ use std::f32::INFINITY; +use std::fmt; use crate::*; @@ -631,12 +632,23 @@ impl Rect { } } -impl std::fmt::Debug for Rect { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Rect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[{:?} - {:?}]", self.min, self.max) } } +impl fmt::Display for Rect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[")?; + self.min.fmt(f)?; + f.write_str(" - ")?; + self.max.fmt(f)?; + f.write_str("]")?; + Ok(()) + } +} + /// from (min, max) or (left top, right bottom) impl From<[Pos2; 2]> for Rect { #[inline] diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index 0cab00fe62f6..99f7d0c05c7d 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use crate::Vec2b; @@ -464,12 +465,23 @@ impl Div for Vec2 { } } -impl std::fmt::Debug for Vec2 { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Vec2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[{:.1} {:.1}]", self.x, self.y) } } +impl fmt::Display for Vec2 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[")?; + self.x.fmt(f)?; + f.write_str(" ")?; + self.y.fmt(f)?; + f.write_str("]")?; + Ok(()) + } +} + #[test] fn test_vec2() { macro_rules! almost_eq { From c9b24d5a5ca316fd0a8b330ee071dad2e6eab2dc Mon Sep 17 00:00:00 2001 From: Simon Niedermayr <14186588+KeKsBoTer@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:47:12 +0200 Subject: [PATCH 0076/1202] Update to wgpu 0.20 (#4433) updates the wgpu version to 0.20 and changes the API calls accordingly. I had to update wasm-bindgen to "0.2.92". Otherwise, I got this error for the demo app: ``` error: failed to select a version for `wasm-bindgen`. ... required by package `js-sys v0.3.69` ... which satisfies dependency `js-sys = "^0.3.69"` of package `eframe v0.27.2 (/home/user/Projects/egui/crates/eframe)` ... which satisfies path dependency `eframe` (locked to 0.27.2) of package `confirm_exit v0.1.0 (/home/user/Projects/egui/examples/confirm_exit)` versions that meet the requirements `^0.2.92` are: 0.2.92 all possible versions conflict with previously selected packages. previously selected package `wasm-bindgen v0.2.90` ... which satisfies dependency `wasm-bindgen = "=0.2.90"` of package `egui_demo_app v0.27.2 (/home/user/Projects/egui/crates/egui_demo_app)` failed to select a version for `wasm-bindgen` which could resolve this conflict ``` Why is it locked to this version right now? I ran the tests, checked the web demo and my own projects, and everything seems to work fine with wgpu 0.20. --------- Co-authored-by: Andreas Reich --- .github/workflows/rust.yml | 2 +- Cargo.lock | 156 +++++++++--------- Cargo.toml | 2 +- crates/egui-wgpu/src/renderer.rs | 2 + crates/egui_demo_app/Cargo.toml | 2 +- .../egui_demo_app/src/apps/custom3d_wgpu.rs | 2 + scripts/setup_web.sh | 2 +- 7 files changed, 83 insertions(+), 85 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 26404d294a31..8aaa18faa36e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -108,7 +108,7 @@ jobs: - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 with: - version: "0.2.88" + version: "0.2.92" - run: ./scripts/wasm_bindgen_check.sh --skip-setup diff --git a/Cargo.lock b/Cargo.lock index 1afbd85fbd05..4466ae0f3449 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,7 +144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "052ad56e336bcc615a214bffbeca6c181ee9550acec193f0327e0b103b033a4d" dependencies = [ "android-properties", - "bitflags 2.4.0", + "bitflags 2.5.0", "cc", "cesu8", "jni", @@ -511,9 +511,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -650,7 +650,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b50b5a44d59a98c55a9eeb518f39bf7499ba19fd98ee7d22618687f3f10adbf" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "log", "polling 3.3.0", "rustix 0.38.21", @@ -886,9 +886,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -896,9 +896,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" @@ -915,9 +915,9 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1793,7 +1793,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "005459a22af86adc706522d78d360101118e2638ec21df3852fcc626e0dbb212" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "cfg_aliases", "cgl", "core-foundation", @@ -1869,7 +1869,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "gpu-alloc-types", ] @@ -1879,7 +1879,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", ] [[package]] @@ -1897,22 +1897,22 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "gpu-descriptor-types", "hashbrown", ] [[package]] name = "gpu-descriptor-types" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", ] [[package]] @@ -1955,7 +1955,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "com", "libc", "libloading 0.8.0", @@ -2198,9 +2198,9 @@ checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -2361,11 +2361,11 @@ dependencies = [ [[package]] name = "metal" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "block", "core-graphics-types", "foreign-types", @@ -2422,12 +2422,13 @@ dependencies = [ [[package]] name = "naga" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8878eb410fc90853da3908aebfe61d73d26d4437ef850b70050461f939509899" +checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231" dependencies = [ + "arrayvec", "bit-set", - "bitflags 2.4.0", + "bitflags 2.5.0", "codespan-reporting", "hexf-parse", "indexmap", @@ -2446,7 +2447,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "jni-sys", "log", "ndk-sys", @@ -2557,7 +2558,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] [[package]] @@ -2668,15 +2668,6 @@ dependencies = [ "objc2 0.5.1", ] -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - [[package]] name = "objc_id" version = "0.1.1" @@ -3133,9 +3124,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "renderdoc-sys" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "resvg" @@ -3205,7 +3196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64", - "bitflags 2.4.0", + "bitflags 2.5.0", "serde", "serde_derive", ] @@ -3248,7 +3239,7 @@ version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys 0.4.11", @@ -3477,7 +3468,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "calloop", "calloop-wayland-source", "cursor-icon", @@ -3538,7 +3529,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", ] [[package]] @@ -3668,18 +3659,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", @@ -4038,9 +4029,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4048,9 +4039,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -4063,9 +4054,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -4075,9 +4066,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4085,9 +4076,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -4098,9 +4089,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wayland-backend" @@ -4122,7 +4113,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "nix", "wayland-backend", "wayland-scanner", @@ -4134,7 +4125,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "cursor-icon", "wayland-backend", ] @@ -4156,7 +4147,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -4168,7 +4159,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4181,7 +4172,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4213,9 +4204,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -4257,13 +4248,14 @@ checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "wgpu" -version = "0.19.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfe9a310dcf2e6b85f00c46059aaeaf4184caa8e29a1ecd4b7a704c3482332d" +checksum = "32ff1bfee408e1028e2e3acbf6d32d98b08a5a059ccbf5f33305534453ba5d3e" dependencies = [ "arrayvec", "cfg-if", "cfg_aliases", + "document-features", "js-sys", "log", "naga", @@ -4282,15 +4274,16 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b15e451d4060ada0d99a64df44e4d590213496da7c4f245572d51071e8e30ed" +checksum = "ac6a86eaa5e763e59c73cf9e97d55fffd4dfda69fd8bda19589fcf851ddfef1f" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.4.0", + "bitflags 2.5.0", "cfg_aliases", "codespan-reporting", + "document-features", "indexmap", "log", "naga", @@ -4308,14 +4301,14 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f259ceb56727fb097da108d92f8a5cbdb5b74a77f9e396bd43626f67299d61" +checksum = "4d71c8ae05170583049b65ee562fd839fdc0b3e9ddb84f4e40c9d5f8ea0d4c8c" dependencies = [ "android_system_properties", "arrayvec", "ash", - "bitflags 2.4.0", + "bitflags 2.5.0", "block", "cfg_aliases", "core-graphics-types", @@ -4332,6 +4325,7 @@ dependencies = [ "log", "metal", "naga", + "ndk-sys", "objc", "once_cell", "parking_lot", @@ -4349,11 +4343,11 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "895fcbeb772bfb049eb80b2d6e47f6c9af235284e9703c96fc0218a42ffd5af2" +checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "js-sys", "web-sys", ] @@ -4661,7 +4655,7 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.4.0", + "bitflags 2.5.0", "bytemuck", "calloop", "cfg_aliases", @@ -4767,7 +4761,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.5.0", "dlib", "log", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 0fd46f9ec0c6..0853df671d61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ puffin_http = "0.16" raw-window-handle = "0.6.0" thiserror = "1.0.37" web-time = "0.2" # Timekeeping for native and web -wgpu = { version = "0.19.1", default-features = false, features = [ +wgpu = { version = "0.20.0", default-features = false, features = [ # Make the renderer `Sync` even on wasm32, because it makes the code simpler: "fragile-send-sync-non-atomic-wasm", ] } diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index b028af605bdc..52ce7907a50f 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -293,6 +293,7 @@ impl Renderer { // 2: uint color attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32], }], + compilation_options: wgpu::PipelineCompilationOptions::default() }, primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, @@ -334,6 +335,7 @@ impl Renderer { }), write_mask: wgpu::ColorWrites::ALL, })], + compilation_options: wgpu::PipelineCompilationOptions::default() }), multiview: None, } diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 4ae9572bd0a6..e0fb94f64d79 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -85,6 +85,6 @@ rfd = { version = "0.13", optional = true } # web: [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = "=0.2.90" +wasm-bindgen = "=0.2.92" wasm-bindgen-futures = "0.4" web-sys = "0.3" diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index 1676a0ba70c6..95b3ee1a0af4 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -49,11 +49,13 @@ impl Custom3d { module: &shader, entry_point: "vs_main", buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "fs_main", targets: &[Some(wgpu_render_state.target_format.into())], + compilation_options: wgpu::PipelineCompilationOptions::default(), }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, diff --git a/scripts/setup_web.sh b/scripts/setup_web.sh index 36d39128b6ed..51e53d4be4a6 100755 --- a/scripts/setup_web.sh +++ b/scripts/setup_web.sh @@ -7,4 +7,4 @@ cd "$script_path/.." rustup target add wasm32-unknown-unknown # For generating JS bindings: -cargo install --quiet wasm-bindgen-cli --version 0.2.90 +cargo install --quiet wasm-bindgen-cli --version 0.2.92 From ded8dbd45bc8616475c364878f6b8e2f7293d12c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 2 May 2024 17:04:25 +0200 Subject: [PATCH 0077/1202] Fix some clippy warning from Rust 1.78.0 (#4444) --- crates/eframe/src/epi.rs | 6 +++--- crates/eframe/src/native/glow_integration.rs | 15 --------------- crates/eframe/src/native/wgpu_integration.rs | 14 -------------- crates/eframe/src/native/winit_integration.rs | 6 ------ crates/egui-wgpu/src/renderer.rs | 2 +- crates/egui-wgpu/src/winit.rs | 4 ++-- crates/egui/src/frame_state.rs | 4 ++-- crates/egui/src/input_state.rs | 2 +- crates/egui/src/input_state/touch_state.rs | 2 +- crates/egui/src/layout.rs | 4 ++-- crates/egui/src/os.rs | 10 +++++----- crates/egui/src/style.rs | 2 +- crates/egui/src/util/undoer.rs | 4 ++-- crates/egui/src/viewport.rs | 2 +- crates/egui/src/widgets/text_edit/text_buffer.rs | 2 +- crates/egui_demo_app/src/wrap_app.rs | 2 +- .../src/easy_mark/easy_mark_highlighter.rs | 2 +- crates/egui_plot/src/items/values.rs | 14 +++++++------- crates/emath/src/rot2.rs | 4 ++-- crates/epaint/src/text/text_layout_types.rs | 6 +++--- examples/file_dialog/src/main.rs | 2 +- 21 files changed, 37 insertions(+), 72 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index e7b53691cda9..db39a08b15b6 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -232,7 +232,7 @@ pub enum HardwareAcceleration { /// Do NOT use graphics acceleration. /// - /// On some platforms (MacOS) this is ignored and treated the same as [`Self::Preferred`]. + /// On some platforms (macOS) this is ignored and treated the same as [`Self::Preferred`]. Off, } @@ -518,10 +518,10 @@ pub enum WebGlContextOption { /// Force use WebGL2. WebGl2, - /// Use WebGl2 first. + /// Use WebGL2 first. BestFirst, - /// Use WebGl1 first + /// Use WebGL1 first CompatibilityFirst, } diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index f08ed14d6235..0fbb48a09c74 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -360,21 +360,6 @@ impl WinitApp for GlowWinitApp { .map_or(0, |r| r.integration.egui_ctx.frame_nr_for(viewport_id)) } - fn is_focused(&self, window_id: WindowId) -> bool { - if let Some(running) = &self.running { - let glutin = running.glutin.borrow(); - if let Some(window_id) = glutin.viewport_from_window.get(&window_id) { - return glutin.focused_viewport == Some(*window_id); - } - } - - false - } - - fn integration(&self) -> Option<&EpiIntegration> { - self.running.as_ref().map(|r| &r.integration) - } - fn window(&self, window_id: WindowId) -> Option> { let running = self.running.as_ref()?; let glutin = running.glutin.borrow(); diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 1287ce8a01f5..f365d74ad93d 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -341,20 +341,6 @@ impl WinitApp for WgpuWinitApp { .map_or(0, |r| r.integration.egui_ctx.frame_nr_for(viewport_id)) } - fn is_focused(&self, window_id: WindowId) -> bool { - if let Some(running) = &self.running { - let shared = running.shared.borrow(); - let viewport_id = shared.viewport_from_window.get(&window_id).copied(); - shared.focused_viewport.is_some() && shared.focused_viewport == viewport_id - } else { - false - } - } - - fn integration(&self) -> Option<&EpiIntegration> { - self.running.as_ref().map(|r| &r.integration) - } - fn window(&self, window_id: WindowId) -> Option> { self.running .as_ref() diff --git a/crates/eframe/src/native/winit_integration.rs b/crates/eframe/src/native/winit_integration.rs index 541253c0818b..fbbd79107320 100644 --- a/crates/eframe/src/native/winit_integration.rs +++ b/crates/eframe/src/native/winit_integration.rs @@ -9,8 +9,6 @@ use egui::ViewportId; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; -use super::epi_integration::EpiIntegration; - /// Create an egui context, restoring it from storage if possible. pub fn create_egui_context(storage: Option<&dyn crate::Storage>) -> egui::Context { crate::profile_function!(); @@ -64,10 +62,6 @@ pub trait WinitApp { /// The current frame number, as reported by egui. fn frame_nr(&self, viewport_id: ViewportId) -> u64; - fn is_focused(&self, window_id: WindowId) -> bool; - - fn integration(&self) -> Option<&EpiIntegration>; - fn window(&self, window_id: WindowId) -> Option>; fn window_id_from_viewport_id(&self, id: ViewportId) -> Option; diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 52ce7907a50f..49397c9af892 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -162,7 +162,7 @@ pub struct Renderer { texture_bind_group_layout: wgpu::BindGroupLayout, /// Map of egui texture IDs to textures and their associated bindgroups (texture view + - /// sampler). The texture may be None if the TextureId is just a handle to a user-provided + /// sampler). The texture may be None if the `TextureId` is just a handle to a user-provided /// sampler. textures: HashMap, wgpu::BindGroup)>, next_user_texture_id: u64, diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 84dd0b412359..4a909bfc75fe 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -209,7 +209,7 @@ impl Painter { if let Some(window) = window { let size = window.inner_size(); - if self.surfaces.get(&viewport_id).is_none() { + if !self.surfaces.contains_key(&viewport_id) { let surface = self.instance.create_surface(window)?; self.add_surface(surface, viewport_id, size).await?; } @@ -235,7 +235,7 @@ impl Painter { if let Some(window) = window { let size = window.inner_size(); - if self.surfaces.get(&viewport_id).is_none() { + if !self.surfaces.contains_key(&viewport_id) { let surface = unsafe { self.instance .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::from_window(&window)?)? diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index 87074c2a7251..f160d7423454 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -21,12 +21,12 @@ pub(crate) struct FrameState { /// All [`Id`]s that were used this frame. pub(crate) used_ids: IdMap, - /// Starts off as the screen_rect, shrinks as panels are added. + /// Starts off as the `screen_rect`, shrinks as panels are added. /// The [`CentralPanel`] does not change this. /// This is the area available to Window's. pub(crate) available_rect: Rect, - /// Starts off as the screen_rect, shrinks as panels are added. + /// Starts off as the `screen_rect`, shrinks as panels are added. /// The [`CentralPanel`] retracts from this. pub(crate) unused_rect: Rect, diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 9c6103e0a546..57a9e6477bf6 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -35,7 +35,7 @@ pub struct InputState { /// State of the mouse or simple touch gestures which can be mapped to mouse operations. pub pointer: PointerState, - /// State of touches, except those covered by PointerState (like clicks and drags). + /// State of touches, except those covered by `PointerState` (like clicks and drags). /// (We keep a separate [`TouchState`] for each encountered touch device.) touch_states: BTreeMap, diff --git a/crates/egui/src/input_state/touch_state.rs b/crates/egui/src/input_state/touch_state.rs index 43053f821e59..e9425dba24c5 100644 --- a/crates/egui/src/input_state/touch_state.rs +++ b/crates/egui/src/input_state/touch_state.rs @@ -72,7 +72,7 @@ pub(crate) struct TouchState { /// Active touches, if any. /// - /// TouchId is the unique identifier of the touch. It is valid as long as the finger/pen touches the surface. The + /// `TouchId` is the unique identifier of the touch. It is valid as long as the finger/pen touches the surface. The /// next touch will receive a new unique ID. /// /// Refer to [`ActiveTouch`]. diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index a044dce98147..6c93297243a4 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -13,7 +13,7 @@ pub(crate) struct Region { /// Always finite. /// /// The bounding box of all child widgets, but not necessarily a tight bounding box - /// since [`Ui`](crate::Ui) can start with a non-zero min_rect size. + /// since [`Ui`](crate::Ui) can start with a non-zero `min_rect` size. pub min_rect: Rect, /// The maximum size of this [`Ui`](crate::Ui). This is a *soft max* @@ -38,7 +38,7 @@ pub(crate) struct Region { /// So one can think of `cursor` as a constraint on the available region. /// /// If something has already been added, this will point to `style.spacing.item_spacing` beyond the latest child. - /// The cursor can thus be `style.spacing.item_spacing` pixels outside of the min_rect. + /// The cursor can thus be `style.spacing.item_spacing` pixels outside of the `min_rect`. pub(crate) cursor: Rect, } diff --git a/crates/egui/src/os.rs b/crates/egui/src/os.rs index 93db910deb42..766ecf55ae60 100644 --- a/crates/egui/src/os.rs +++ b/crates/egui/src/os.rs @@ -5,19 +5,19 @@ pub enum OperatingSystem { /// Unknown OS - could be wasm Unknown, - /// Android OS. + /// Android OS Android, - /// Apple iPhone OS. + /// Apple iPhone OS IOS, - /// Linux or Unix other than Android. + /// Linux or Unix other than Android Nix, - /// MacOS. + /// macOS Mac, - /// Windows. + /// Windows Windows, } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index aad8e3eea2db..d2d7ce991882 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -152,7 +152,7 @@ pub struct Style { /// /// The most convenient way to look something up in this is to use [`TextStyle::resolve`]. /// - /// If you would like to overwrite app text_styles + /// If you would like to overwrite app `text_styles` /// /// ``` /// # let mut ctx = egui::Context::default(); diff --git a/crates/egui/src/util/undoer.rs b/crates/egui/src/util/undoer.rs index d5f004f86ae5..cd11b6d16306 100644 --- a/crates/egui/src/util/undoer.rs +++ b/crates/egui/src/util/undoer.rs @@ -59,8 +59,8 @@ pub struct Undoer { /// Stores redos immediately after a sequence of undos. /// Gets cleared every time the state changes. - /// Does not need to be a deque, because there can only be up to undos.len() redos, - /// which is already limited to settings.max_undos. + /// Does not need to be a deque, because there can only be up to `undos.len()` redos, + /// which is already limited to `settings.max_undos`. redos: Vec, #[cfg_attr(feature = "serde", serde(skip))] diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index cce904877217..9dd71b4747b8 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -1071,7 +1071,7 @@ pub enum ViewportCommand { /// This is equivalent to the system keyboard shortcut for copy (e.g. CTRL + C). RequestCopy, - /// Request a paste from the clipboard to the current focused TextEdit if any. + /// Request a paste from the clipboard to the current focused `TextEdit` if any. /// /// This is equivalent to the system keyboard shortcut for paste (e.g. CTRL + V). RequestPaste, diff --git a/crates/egui/src/widgets/text_edit/text_buffer.rs b/crates/egui/src/widgets/text_edit/text_buffer.rs index e70ce695af9a..ea3992c57e4f 100644 --- a/crates/egui/src/widgets/text_edit/text_buffer.rs +++ b/crates/egui/src/widgets/text_edit/text_buffer.rs @@ -220,7 +220,7 @@ impl TextBuffer for String { } fn replace_with(&mut self, text: &str) { - *self = text.to_owned(); + text.clone_into(self); } fn take(&mut self) -> String { diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 98e74d0deea8..42adf2b857a4 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -464,7 +464,7 @@ impl WrapApp { // Collect dropped files: ctx.input(|i| { if !i.raw.dropped_files.is_empty() { - self.dropped_files = i.raw.dropped_files.clone(); + self.dropped_files.clone_from(&i.raw.dropped_files); } }); diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs index 7431accc5045..5c6bc2ef0864 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_highlighter.rs @@ -14,7 +14,7 @@ impl MemoizedEasymarkHighlighter { pub fn highlight(&mut self, egui_style: &egui::Style, code: &str) -> egui::text::LayoutJob { if (&self.style, self.code.as_str()) != (egui_style, code) { self.style = egui_style.clone(); - self.code = code.to_owned(); + code.clone_into(&mut self.code); self.output = highlight_easymark(egui_style, code); } self.output.clone() diff --git a/crates/egui_plot/src/items/values.rs b/crates/egui_plot/src/items/values.rs index 5f9fe8183f30..6e9bff096b67 100644 --- a/crates/egui_plot/src/items/values.rs +++ b/crates/egui_plot/src/items/values.rs @@ -123,12 +123,12 @@ impl LineStyle { } } -impl ToString for LineStyle { - fn to_string(&self) -> String { +impl std::fmt::Display for LineStyle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Solid => "Solid".into(), - Self::Dotted { spacing } => format!("Dotted{spacing}Px"), - Self::Dashed { length } => format!("Dashed{length}Px"), + Self::Solid => write!(f, "Solid"), + Self::Dotted { spacing } => write!(f, "Dotted({spacing} px)"), + Self::Dashed { length } => write!(f, "Dashed({length} px)"), } } } @@ -426,9 +426,9 @@ impl ExplicitGenerator { /// Result of [`super::PlotItem::find_closest()`] search, identifies an element inside the item for immediate use pub struct ClosestElem { - /// Position of hovered-over value (or bar/box-plot/...) in PlotItem + /// Position of hovered-over value (or bar/box-plot/...) in `PlotItem` pub index: usize, - /// Squared distance from the mouse cursor (needed to compare against other PlotItems, which might be nearer) + /// Squared distance from the mouse cursor (needed to compare against other `PlotItems`, which might be nearer) pub dist_sq: f32, } diff --git a/crates/emath/src/rot2.rs b/crates/emath/src/rot2.rs index da67acb0c9c8..cb2272e2f8c0 100644 --- a/crates/emath/src/rot2.rs +++ b/crates/emath/src/rot2.rs @@ -18,10 +18,10 @@ use super::Vec2; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] pub struct Rot2 { - /// angle.sin() + /// `angle.sin()` s: f32, - /// angle.cos() + /// `angle.cos()` c: f32, } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 5e4a56d9502e..f0a18ba865d5 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -453,9 +453,9 @@ pub struct Galley { /// `rect.top()` is always 0.0. /// /// With [`LayoutJob::halign`]: - /// * [`Align::LEFT`]: rect.left() == 0.0 - /// * [`Align::Center`]: rect.center() == 0.0 - /// * [`Align::RIGHT`]: rect.right() == 0.0 + /// * [`Align::LEFT`]: `rect.left() == 0.0` + /// * [`Align::Center`]: `rect.center() == 0.0` + /// * [`Align::RIGHT`]: `rect.right() == 0.0` pub rect: Rect, /// Tight bounding box around all the meshes in all the rows. diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index d0623a73a286..267c22e111ce 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -78,7 +78,7 @@ impl eframe::App for MyApp { // Collect dropped files: ctx.input(|i| { if !i.raw.dropped_files.is_empty() { - self.dropped_files = i.raw.dropped_files.clone(); + self.dropped_files.clone_from(&i.raw.dropped_files); } }); } From dce5696b670d43d65e2b846d69e0e708bc709078 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 May 2024 11:38:54 +0200 Subject: [PATCH 0078/1202] Show Cargo.lock in GitHub PR diffs --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..b1f5e1192e45 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto eol=lf +Cargo.lock linguist-generated=false From 155e1389984ecc17f2eb90ab5614ccc8af487b09 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 May 2024 19:21:35 +0200 Subject: [PATCH 0079/1202] Fix debug-assert hit in panel sizing code --- crates/egui/src/containers/panel.rs | 8 ++------ crates/egui/src/placer.rs | 6 ++++++ crates/egui/src/ui.rs | 2 ++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index ee41af879392..324e17bf91fe 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -262,9 +262,7 @@ impl SidePanel { let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height - ui.set_min_width( - width_range.min - (frame.inner_margin.left + frame.inner_margin.right), - ); + ui.set_min_width((width_range.min - frame.inner_margin.sum().x).at_least(0.0)); add_contents(ui) }); @@ -730,9 +728,7 @@ impl TopBottomPanel { let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); let inner_response = frame.show(&mut panel_ui, |ui| { ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width - ui.set_min_height( - height_range.min - (frame.inner_margin.top + frame.inner_margin.bottom), - ); + ui.set_min_height((height_range.min - frame.inner_margin.sum().y).at_least(0.0)); add_contents(ui) }); diff --git a/crates/egui/src/placer.rs b/crates/egui/src/placer.rs index 81c137f4e831..7a3ca433b9b3 100644 --- a/crates/egui/src/placer.rs +++ b/crates/egui/src/placer.rs @@ -248,6 +248,9 @@ impl Placer { /// Set the minimum width of the ui. /// This can't shrink the ui, only make it larger. pub(crate) fn set_min_width(&mut self, width: f32) { + if width <= 0.0 { + return; + } let rect = self.next_widget_space_ignore_wrap_justify(vec2(width, 0.0)); self.region.expand_to_include_x(rect.min.x); self.region.expand_to_include_x(rect.max.x); @@ -256,6 +259,9 @@ impl Placer { /// Set the minimum height of the ui. /// This can't shrink the ui, only make it larger. pub(crate) fn set_min_height(&mut self, height: f32) { + if height <= 0.0 { + return; + } let rect = self.next_widget_space_ignore_wrap_justify(vec2(0.0, height)); self.region.expand_to_include_y(rect.min.y); self.region.expand_to_include_y(rect.max.y); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 1215d0eeb0ff..4885b06bed0c 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -530,12 +530,14 @@ impl Ui { /// Set the minimum width of the ui. /// This can't shrink the ui, only make it larger. pub fn set_min_width(&mut self, width: f32) { + egui_assert!(0.0 <= width); self.placer.set_min_width(width); } /// Set the minimum height of the ui. /// This can't shrink the ui, only make it larger. pub fn set_min_height(&mut self, height: f32) { + egui_assert!(0.0 <= height); self.placer.set_min_height(height); } From f19f99180e6571a780aaa9ce6286e6fd712a4eab Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 10 May 2024 19:39:08 +0200 Subject: [PATCH 0080/1202] Remove `extra_asserts` and `extra_debug_asserts` feature flags (#4478) Removes `egui_assert` etc and replaces it with normal `debug_assert` calls. Previously you could opt-in to more runtime checks using feature flags. Now these extra runtime checks are always enabled for debug builds. You are most likely to encounter them if you use negative sizes or NaNs or other similar bugs. These usually indicate bugs in user space. --- crates/ecolor/Cargo.toml | 4 -- crates/ecolor/src/color32.rs | 4 +- crates/ecolor/src/lib.rs | 16 ------- crates/ecolor/src/rgba.rs | 8 ++-- crates/egui/Cargo.toml | 5 --- crates/egui/src/context.rs | 2 +- crates/egui/src/frame_state.rs | 10 ++--- crates/egui/src/grid.rs | 2 +- crates/egui/src/layout.rs | 46 ++++++++++----------- crates/egui/src/lib.rs | 16 ------- crates/egui/src/placer.rs | 12 +++--- crates/egui/src/response.rs | 2 +- crates/egui/src/ui.rs | 14 +++---- crates/egui/src/widget_rect.rs | 2 +- crates/egui/src/widgets/image.rs | 2 +- crates/egui/src/widgets/label.rs | 2 +- crates/egui/src/widgets/slider.rs | 6 +-- crates/egui_demo_app/Cargo.toml | 7 +--- crates/egui_extras/src/sizing.rs | 2 +- crates/emath/Cargo.toml | 6 --- crates/emath/src/history.rs | 2 +- crates/emath/src/lib.rs | 24 ++--------- crates/emath/src/rot2.rs | 2 +- crates/emath/src/smart_aim.rs | 6 +-- crates/epaint/Cargo.toml | 8 ---- crates/epaint/src/bezier.rs | 10 ++--- crates/epaint/src/lib.rs | 16 ------- crates/epaint/src/mesh.rs | 12 +++--- crates/epaint/src/shape.rs | 2 +- crates/epaint/src/tessellator.rs | 12 +++--- crates/epaint/src/text/text_layout_types.rs | 2 +- crates/epaint/src/textures.rs | 8 ++-- 32 files changed, 90 insertions(+), 182 deletions(-) diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index ad5d6f96369d..fe4c6f444b1a 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -28,10 +28,6 @@ all-features = true [features] default = [] -## Enable additional checks if debug assertions are enabled (debug builds). -extra_debug_asserts = [] -## Always enable additional checks. -extra_asserts = [] [dependencies] #! ### Optional dependencies diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index a2f062948194..807aa7a545be 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -199,7 +199,7 @@ impl Color32 { /// This is perceptually even, and faster that [`Self::linear_multiply`]. #[inline] pub fn gamma_multiply(self, factor: f32) -> Self { - crate::ecolor_assert!(0.0 <= factor && factor <= 1.0); + debug_assert!(0.0 <= factor && factor <= 1.0); let Self([r, g, b, a]) = self; Self([ (r as f32 * factor + 0.5) as u8, @@ -215,7 +215,7 @@ impl Color32 { /// You likely want to use [`Self::gamma_multiply`] instead. #[inline] pub fn linear_multiply(self, factor: f32) -> Self { - crate::ecolor_assert!(0.0 <= factor && factor <= 1.0); + debug_assert!(0.0 <= factor && factor <= 1.0); // As an unfortunate side-effect of using premultiplied alpha // we need a somewhat expensive conversion to linear space and back. Rgba::from(self).multiply(factor).into() diff --git a/crates/ecolor/src/lib.rs b/crates/ecolor/src/lib.rs index a7072d8e89a3..a9400d9de616 100644 --- a/crates/ecolor/src/lib.rs +++ b/crates/ecolor/src/lib.rs @@ -135,22 +135,6 @@ pub fn gamma_from_linear(linear: f32) -> f32 { // ---------------------------------------------------------------------------- -/// An assert that is only active when `epaint` is compiled with the `extra_asserts` feature -/// or with the `extra_debug_asserts` feature in debug builds. -#[macro_export] -macro_rules! ecolor_assert { - ($($arg: tt)*) => { - if cfg!(any( - feature = "extra_asserts", - all(feature = "extra_debug_asserts", debug_assertions), - )) { - assert!($($arg)*); - } - } -} - -// ---------------------------------------------------------------------------- - /// Cheap and ugly. /// Made for graying out disabled `Ui`s. pub fn tint_color_towards(color: Color32, target: Color32) -> Color32 { diff --git a/crates/ecolor/src/rgba.rs b/crates/ecolor/src/rgba.rs index 36cb33bd5992..900286cda434 100644 --- a/crates/ecolor/src/rgba.rs +++ b/crates/ecolor/src/rgba.rs @@ -98,22 +98,22 @@ impl Rgba { #[inline] pub fn from_luminance_alpha(l: f32, a: f32) -> Self { - crate::ecolor_assert!(0.0 <= l && l <= 1.0); - crate::ecolor_assert!(0.0 <= a && a <= 1.0); + debug_assert!(0.0 <= l && l <= 1.0); + debug_assert!(0.0 <= a && a <= 1.0); Self([l * a, l * a, l * a, a]) } /// Transparent black #[inline] pub fn from_black_alpha(a: f32) -> Self { - crate::ecolor_assert!(0.0 <= a && a <= 1.0); + debug_assert!(0.0 <= a && a <= 1.0); Self([0.0, 0.0, 0.0, a]) } /// Transparent white #[inline] pub fn from_white_alpha(a: f32) -> Self { - crate::ecolor_assert!(0.0 <= a && a <= 1.0, "a: {}", a); + debug_assert!(0.0 <= a && a <= 1.0, "a: {a}"); Self([a, a, a, a]) } diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index a1929f060ade..909beba133aa 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -52,11 +52,6 @@ deadlock_detection = ["epaint/deadlock_detection"] ## If you plan on specifying your own fonts you may disable this feature. default_fonts = ["epaint/default_fonts"] -## Enable additional checks if debug assertions are enabled (debug builds). -extra_debug_asserts = ["epaint/extra_debug_asserts"] -## Always enable additional checks. -extra_asserts = ["epaint/extra_asserts"] - ## Turn on the `log` feature, that makes egui log some errors using the [`log`](https://docs.rs/log) crate. log = ["dep:log", "epaint/log"] diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index f697fb4b8e51..04bdd16432aa 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1748,7 +1748,7 @@ impl Context { let name = name.into(); let image = image.into(); let max_texture_side = self.input(|i| i.max_texture_side); - crate::egui_assert!( + debug_assert!( image.width() <= max_texture_side && image.height() <= max_texture_side, "Texture {:?} has size {}x{}, but the maximum texture side is {}", name, diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index f160d7423454..5f966d117a0a 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -117,7 +117,7 @@ impl FrameState { /// This is the "background" area, what egui doesn't cover with panels (but may cover with windows). /// This is also the area to which windows are constrained. pub(crate) fn available_rect(&self) -> Rect { - crate::egui_assert!( + debug_assert!( self.available_rect.is_finite(), "Called `available_rect()` before `Context::run()`" ); @@ -126,7 +126,7 @@ impl FrameState { /// Shrink `available_rect`. pub(crate) fn allocate_left_panel(&mut self, panel_rect: Rect) { - crate::egui_assert!( + debug_assert!( panel_rect.min.distance(self.available_rect.min) < 0.1, "Mismatching left panel. You must not create a panel from within another panel." ); @@ -137,7 +137,7 @@ impl FrameState { /// Shrink `available_rect`. pub(crate) fn allocate_right_panel(&mut self, panel_rect: Rect) { - crate::egui_assert!( + debug_assert!( panel_rect.max.distance(self.available_rect.max) < 0.1, "Mismatching right panel. You must not create a panel from within another panel." ); @@ -148,7 +148,7 @@ impl FrameState { /// Shrink `available_rect`. pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) { - crate::egui_assert!( + debug_assert!( panel_rect.min.distance(self.available_rect.min) < 0.1, "Mismatching top panel. You must not create a panel from within another panel." ); @@ -159,7 +159,7 @@ impl FrameState { /// Shrink `available_rect`. pub(crate) fn allocate_bottom_panel(&mut self, panel_rect: Rect) { - crate::egui_assert!( + debug_assert!( panel_rect.max.distance(self.available_rect.max) < 0.1, "Mismatching bottom panel. You must not create a panel from within another panel." ); diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index 374902e3a4b0..b888f756b5ea 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -85,7 +85,7 @@ impl GridLayout { // TODO(emilk): respect current layout let initial_available = ui.placer().max_rect().intersect(ui.cursor()); - crate::egui_assert!( + debug_assert!( initial_available.min.x.is_finite(), "Grid not yet available for right-to-left layouts" ); diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index 6c93297243a4..15ed2fef079f 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -1,4 +1,4 @@ -use crate::{egui_assert, emath::*, Align}; +use crate::{emath::*, Align}; use std::f32::INFINITY; // ---------------------------------------------------------------------------- @@ -66,9 +66,9 @@ impl Region { } pub fn sanity_check(&self) { - egui_assert!(!self.min_rect.any_nan()); - egui_assert!(!self.max_rect.any_nan()); - egui_assert!(!self.cursor.any_nan()); + debug_assert!(!self.min_rect.any_nan()); + debug_assert!(!self.max_rect.any_nan()); + debug_assert!(!self.cursor.any_nan()); } } @@ -389,8 +389,8 @@ impl Layout { /// ## Doing layout impl Layout { pub fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect { - egui_assert!(size.x >= 0.0 && size.y >= 0.0); - egui_assert!(!outer.is_negative()); + debug_assert!(size.x >= 0.0 && size.y >= 0.0); + debug_assert!(!outer.is_negative()); self.align2().align_size_within_rect(size, outer) } @@ -416,8 +416,8 @@ impl Layout { } pub(crate) fn region_from_max_rect(&self, max_rect: Rect) -> Region { - egui_assert!(!max_rect.any_nan()); - egui_assert!(max_rect.is_finite()); + debug_assert!(!max_rect.any_nan()); + debug_assert!(max_rect.is_finite()); let mut region = Region { min_rect: Rect::NOTHING, // temporary max_rect, @@ -450,9 +450,9 @@ impl Layout { /// Given the cursor in the region, how much space is available /// for the next widget? fn available_from_cursor_max_rect(&self, cursor: Rect, max_rect: Rect) -> Rect { - egui_assert!(!cursor.any_nan()); - egui_assert!(!max_rect.any_nan()); - egui_assert!(max_rect.is_finite()); + debug_assert!(!cursor.any_nan()); + debug_assert!(!max_rect.any_nan()); + debug_assert!(max_rect.is_finite()); // NOTE: in normal top-down layout the cursor has moved below the current max_rect, // but the available shouldn't be negative. @@ -506,7 +506,7 @@ impl Layout { avail.max.y = y; } - egui_assert!(!avail.any_nan()); + debug_assert!(!avail.any_nan()); avail } @@ -517,7 +517,7 @@ impl Layout { /// Use `justify_and_align` to get the inner `widget_rect`. pub(crate) fn next_frame(&self, region: &Region, child_size: Vec2, spacing: Vec2) -> Rect { region.sanity_check(); - egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); if self.main_wrap { let available_size = self.available_rect_before_wrap(region).size(); @@ -597,7 +597,7 @@ impl Layout { fn next_frame_ignore_wrap(&self, region: &Region, child_size: Vec2) -> Rect { region.sanity_check(); - egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); let available_rect = self.available_rect_before_wrap(region); @@ -630,16 +630,16 @@ impl Layout { frame_rect = frame_rect.translate(Vec2::Y * (region.cursor.top() - frame_rect.top())); } - egui_assert!(!frame_rect.any_nan()); - egui_assert!(!frame_rect.is_negative()); + debug_assert!(!frame_rect.any_nan()); + debug_assert!(!frame_rect.is_negative()); frame_rect } /// Apply justify (fill width/height) and/or alignment after calling `next_space`. pub(crate) fn justify_and_align(&self, frame: Rect, mut child_size: Vec2) -> Rect { - egui_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); - egui_assert!(!frame.is_negative()); + debug_assert!(child_size.x >= 0.0 && child_size.y >= 0.0); + debug_assert!(!frame.is_negative()); if self.horizontal_justify() { child_size.x = child_size.x.at_least(frame.width()); // fill full width @@ -657,10 +657,10 @@ impl Layout { ) -> Rect { let frame = self.next_frame_ignore_wrap(region, size); let rect = self.align_size_within_rect(size, frame); - egui_assert!(!rect.any_nan()); - egui_assert!(!rect.is_negative()); - egui_assert!((rect.width() - size.x).abs() < 1.0 || size.x == f32::INFINITY); - egui_assert!((rect.height() - size.y).abs() < 1.0 || size.y == f32::INFINITY); + debug_assert!(!rect.any_nan()); + debug_assert!(!rect.is_negative()); + debug_assert!((rect.width() - size.x).abs() < 1.0 || size.x == f32::INFINITY); + debug_assert!((rect.height() - size.y).abs() < 1.0 || size.y == f32::INFINITY); rect } @@ -703,7 +703,7 @@ impl Layout { widget_rect: Rect, item_spacing: Vec2, ) { - egui_assert!(!cursor.any_nan()); + debug_assert!(!cursor.any_nan()); if self.main_wrap { if cursor.intersects(frame_rect.shrink(1.0)) { // make row/column larger if necessary diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 30e3b804f119..a846acc701e2 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -549,22 +549,6 @@ macro_rules! github_link_file { // ---------------------------------------------------------------------------- -/// An assert that is only active when `egui` is compiled with the `extra_asserts` feature -/// or with the `extra_debug_asserts` feature in debug builds. -#[macro_export] -macro_rules! egui_assert { - ($($arg: tt)*) => { - if cfg!(any( - feature = "extra_asserts", - all(feature = "extra_debug_asserts", debug_assertions), - )) { - assert!($($arg)*); - } - } -} - -// ---------------------------------------------------------------------------- - /// The minus character: pub(crate) const MINUS_CHAR_STR: &str = "−"; diff --git a/crates/egui/src/placer.rs b/crates/egui/src/placer.rs index 7a3ca433b9b3..0be6b413b226 100644 --- a/crates/egui/src/placer.rs +++ b/crates/egui/src/placer.rs @@ -106,7 +106,7 @@ impl Placer { /// This is what you then pass to `advance_after_rects`. /// Use `justify_and_align` to get the inner `widget_rect`. pub(crate) fn next_space(&self, child_size: Vec2, item_spacing: Vec2) -> Rect { - egui_assert!(child_size.is_finite() && child_size.x >= 0.0 && child_size.y >= 0.0); + debug_assert!(child_size.is_finite() && child_size.x >= 0.0 && child_size.y >= 0.0); self.region.sanity_check(); if let Some(grid) = &self.grid { grid.next_cell(self.region.cursor, child_size) @@ -127,8 +127,8 @@ impl Placer { /// Apply justify or alignment after calling `next_space`. pub(crate) fn justify_and_align(&self, rect: Rect, child_size: Vec2) -> Rect { - crate::egui_assert!(!rect.any_nan()); - crate::egui_assert!(!child_size.any_nan()); + debug_assert!(!rect.any_nan()); + debug_assert!(!child_size.any_nan()); if let Some(grid) = &self.grid { grid.justify_and_align(rect, child_size) @@ -140,7 +140,7 @@ impl Placer { /// Advance the cursor by this many points. /// [`Self::min_rect`] will expand to contain the cursor. pub(crate) fn advance_cursor(&mut self, amount: f32) { - crate::egui_assert!( + debug_assert!( self.grid.is_none(), "You cannot advance the cursor when in a grid layout" ); @@ -158,8 +158,8 @@ impl Placer { widget_rect: Rect, item_spacing: Vec2, ) { - egui_assert!(!frame_rect.any_nan()); - egui_assert!(!widget_rect.any_nan()); + debug_assert!(!frame_rect.any_nan()); + debug_assert!(!widget_rect.any_nan()); self.region.sanity_check(); if let Some(grid) = &mut self.grid { diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 44eb4f74bd64..d980bd78eb6c 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -936,7 +936,7 @@ impl Response { /// You may not call [`Self::interact`] on the resulting `Response`. pub fn union(&self, other: Self) -> Self { assert!(self.ctx == other.ctx); - crate::egui_assert!( + debug_assert!( self.layer_id == other.layer_id, "It makes no sense to combine Responses from two different layers" ); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 4885b06bed0c..00b7beef6e42 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -111,7 +111,7 @@ impl Ui { layout: Layout, id_source: impl Hash, ) -> Self { - crate::egui_assert!(!max_rect.any_nan()); + debug_assert!(!max_rect.any_nan()); let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value(); self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1); let child_ui = Ui { @@ -530,14 +530,14 @@ impl Ui { /// Set the minimum width of the ui. /// This can't shrink the ui, only make it larger. pub fn set_min_width(&mut self, width: f32) { - egui_assert!(0.0 <= width); + debug_assert!(0.0 <= width); self.placer.set_min_width(width); } /// Set the minimum height of the ui. /// This can't shrink the ui, only make it larger. pub fn set_min_height(&mut self, height: f32) { - egui_assert!(0.0 <= height); + debug_assert!(0.0 <= height); self.placer.set_min_height(height); } @@ -847,7 +847,7 @@ impl Ui { fn allocate_space_impl(&mut self, desired_size: Vec2) -> Rect { let item_spacing = self.spacing().item_spacing; let frame_rect = self.placer.next_space(desired_size, item_spacing); - egui_assert!(!frame_rect.any_nan()); + debug_assert!(!frame_rect.any_nan()); let widget_rect = self.placer.justify_and_align(frame_rect, desired_size); self.placer @@ -870,7 +870,7 @@ impl Ui { /// Allocate a rect without interacting with it. pub fn advance_cursor_after_rect(&mut self, rect: Rect) -> Id { - egui_assert!(!rect.any_nan()); + debug_assert!(!rect.any_nan()); let item_spacing = self.spacing().item_spacing; self.placer.advance_after_rects(rect, rect, item_spacing); @@ -939,7 +939,7 @@ impl Ui { layout: Layout, add_contents: Box R + 'c>, ) -> InnerResponse { - crate::egui_assert!(desired_size.x >= 0.0 && desired_size.y >= 0.0); + debug_assert!(desired_size.x >= 0.0 && desired_size.y >= 0.0); let item_spacing = self.spacing().item_spacing; let frame_rect = self.placer.next_space(desired_size, item_spacing); let child_rect = self.placer.justify_and_align(frame_rect, desired_size); @@ -964,7 +964,7 @@ impl Ui { max_rect: Rect, add_contents: impl FnOnce(&mut Self) -> R, ) -> InnerResponse { - egui_assert!(max_rect.is_finite()); + debug_assert!(max_rect.is_finite()); let mut child_ui = self.child_ui(max_rect, *self.layout()); let ret = add_contents(&mut child_ui); let final_child_rect = child_ui.min_rect(); diff --git a/crates/egui/src/widget_rect.rs b/crates/egui/src/widget_rect.rs index 7c502f139277..76d03270f26b 100644 --- a/crates/egui/src/widget_rect.rs +++ b/crates/egui/src/widget_rect.rs @@ -139,7 +139,7 @@ impl WidgetRects { // e.g. calling `response.interact(…)` to add more interaction. let (idx_in_layer, existing) = entry.get_mut(); - egui_assert!( + debug_assert!( existing.layer_id == widget_rect.layer_id, "Widget changed layer_id during the frame" ); diff --git a/crates/egui/src/widgets/image.rs b/crates/egui/src/widgets/image.rs index 15b9dcddb2ee..a5aecdce931d 100644 --- a/crates/egui/src/widgets/image.rs +++ b/crates/egui/src/widgets/image.rs @@ -746,7 +746,7 @@ pub fn paint_texture_at( Some((rot, origin)) => { // TODO(emilk): implement this using `PathShape` (add texture support to it). // This will also give us anti-aliasing of rotated images. - egui_assert!( + debug_assert!( options.rounding == Rounding::ZERO, "Image had both rounding and rotation. Please pick only one" ); diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index 10cefc5fb6a3..f09a17baeb25 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -170,7 +170,7 @@ impl Label { let cursor = ui.cursor(); let first_row_indentation = available_width - ui.available_size_before_wrap().x; - egui_assert!(first_row_indentation.is_finite()); + debug_assert!(first_row_indentation.is_finite()); layout_job.wrap.max_width = available_width; layout_job.first_row_min_height = cursor.height(); diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index f303464a4feb..644694cbe816 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -983,7 +983,7 @@ fn value_from_normalized(normalized: f64, range: RangeInclusive, spec: &Sli } } } else { - crate::egui_assert!( + debug_assert!( min.is_finite() && max.is_finite(), "You should use a logarithmic range" ); @@ -1032,7 +1032,7 @@ fn normalized_from_value(value: f64, range: RangeInclusive, spec: &SliderSp } } } else { - crate::egui_assert!( + debug_assert!( min.is_finite() && max.is_finite(), "You should use a logarithmic range" ); @@ -1080,6 +1080,6 @@ fn logarithmic_zero_cutoff(min: f64, max: f64) -> f64 { }; let cutoff = min_magnitude / (min_magnitude + max_magnitude); - crate::egui_assert!(0.0 <= cutoff && cutoff <= 1.0); + debug_assert!(0.0 <= cutoff && cutoff <= 1.0); cutoff } diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index e0fb94f64d79..0491f5ff934e 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -44,12 +44,7 @@ chrono = { version = "0.4", default-features = false, features = [ eframe = { workspace = true, default-features = false, features = [ "web_screen_reader", ] } -egui = { workspace = true, features = [ - "callstack", - "default", - "extra_debug_asserts", - "log", -] } +egui = { workspace = true, features = ["callstack", "default", "log"] } egui_demo_lib = { workspace = true, features = ["default", "chrono"] } egui_extras = { workspace = true, features = ["default", "image"] } log.workspace = true diff --git a/crates/egui_extras/src/sizing.rs b/crates/egui_extras/src/sizing.rs index 1ff7a1a5363e..2c380ae61796 100644 --- a/crates/egui_extras/src/sizing.rs +++ b/crates/egui_extras/src/sizing.rs @@ -32,7 +32,7 @@ impl Size { /// Relative size relative to all available space. Values must be in range `0.0..=1.0`. pub fn relative(fraction: f32) -> Self { - egui::egui_assert!(0.0 <= fraction && fraction <= 1.0); + debug_assert!(0.0 <= fraction && fraction <= 1.0); Self::Relative { fraction, range: Rangef::new(0.0, f32::INFINITY), diff --git a/crates/emath/Cargo.toml b/crates/emath/Cargo.toml index ae0153cc9f2b..99fc1b004b8c 100644 --- a/crates/emath/Cargo.toml +++ b/crates/emath/Cargo.toml @@ -25,12 +25,6 @@ all-features = true [features] default = [] -## Enable additional checks if debug assertions are enabled (debug builds). -extra_debug_asserts = [] - -## Always enable additional checks. -extra_asserts = [] - [dependencies] #! ### Optional dependencies diff --git a/crates/emath/src/history.rs b/crates/emath/src/history.rs index d85a27a398a0..6aafd0af1456 100644 --- a/crates/emath/src/history.rs +++ b/crates/emath/src/history.rs @@ -126,7 +126,7 @@ where /// Values must be added with a monotonically increasing time, or at least not decreasing. pub fn add(&mut self, now: f64, value: T) { if let Some((last_time, _)) = self.values.back() { - crate::emath_assert!(now >= *last_time, "Time shouldn't move backwards"); + debug_assert!(*last_time <= now, "Time shouldn't move backwards"); } self.total_count += 1; self.values.push_back((now, value)); diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index d3e2f5e0313f..4ac46f219470 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -146,7 +146,7 @@ where { let from = from.into(); let to = to.into(); - crate::emath_assert!(from.start() != from.end()); + debug_assert!(from.start() != from.end()); let t = (x - *from.start()) / (*from.end() - *from.start()); lerp(to, t) } @@ -170,7 +170,7 @@ where } else if *from.end() <= x { *to.end() } else { - crate::emath_assert!(from.start() != from.end()); + debug_assert!(from.start() != from.end()); let t = (x - *from.start()) / (*from.end() - *from.start()); // Ensure no numerical inaccuracies sneak in: if T::ONE <= t { @@ -194,8 +194,8 @@ pub fn format_with_minimum_decimals(value: f64, decimals: usize) -> String { pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive) -> String { let min_decimals = *decimal_range.start(); let max_decimals = *decimal_range.end(); - crate::emath_assert!(min_decimals <= max_decimals); - crate::emath_assert!(max_decimals < 100); + debug_assert!(min_decimals <= max_decimals); + debug_assert!(max_decimals < 100); let max_decimals = max_decimals.min(16); let min_decimals = min_decimals.min(max_decimals); @@ -430,19 +430,3 @@ pub fn ease_in_ease_out(t: f32) -> f32 { let t = t.clamp(0.0, 1.0); (3.0 * t * t - 2.0 * t * t * t).clamp(0.0, 1.0) } - -// ---------------------------------------------------------------------------- - -/// An assert that is only active when `emath` is compiled with the `extra_asserts` feature -/// or with the `extra_debug_asserts` feature in debug builds. -#[macro_export] -macro_rules! emath_assert { - ($($arg: tt)*) => { - if cfg!(any( - feature = "extra_asserts", - all(feature = "extra_debug_asserts", debug_assertions), - )) { - assert!($($arg)*); - } - } -} diff --git a/crates/emath/src/rot2.rs b/crates/emath/src/rot2.rs index cb2272e2f8c0..f9e0ae6462c2 100644 --- a/crates/emath/src/rot2.rs +++ b/crates/emath/src/rot2.rs @@ -84,7 +84,7 @@ impl Rot2 { c: self.c / l, s: self.s / l, }; - crate::emath_assert!(ret.is_finite()); + debug_assert!(ret.is_finite()); ret } } diff --git a/crates/emath/src/smart_aim.rs b/crates/emath/src/smart_aim.rs index bd0f7b661977..23e34abf701b 100644 --- a/crates/emath/src/smart_aim.rs +++ b/crates/emath/src/smart_aim.rs @@ -33,7 +33,7 @@ pub fn best_in_range_f64(min: f64, max: f64) -> f64 { if !max.is_finite() { return min; } - crate::emath_assert!(min.is_finite() && max.is_finite()); + debug_assert!(min.is_finite() && max.is_finite()); let min_exponent = min.log10(); let max_exponent = max.log10(); @@ -82,7 +82,7 @@ fn is_integer(f: f64) -> bool { } fn to_decimal_string(v: f64) -> [i32; NUM_DECIMALS] { - crate::emath_assert!(v < 10.0, "{:?}", v); + debug_assert!(v < 10.0, "{v:?}"); let mut digits = [0; NUM_DECIMALS]; let mut v = v.abs(); for r in &mut digits { @@ -104,7 +104,7 @@ fn from_decimal_string(s: &[i32]) -> f64 { /// Find the simplest integer in the range [min, max] fn simplest_digit_closed_range(min: i32, max: i32) -> i32 { - crate::emath_assert!(1 <= min && min <= max && max <= 9); + debug_assert!(1 <= min && min <= max && max <= 9); if min <= 5 && 5 <= max { 5 } else { diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 861afb1a14bb..852066d046cb 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -52,14 +52,6 @@ deadlock_detection = ["dep:backtrace"] ## If you plan on specifying your own fonts you may disable this feature. default_fonts = [] -## Enable additional checks if debug assertions are enabled (debug builds). -extra_debug_asserts = [ - "emath/extra_debug_asserts", - "ecolor/extra_debug_asserts", -] -## Always enable additional checks. -extra_asserts = ["emath/extra_asserts", "ecolor/extra_asserts"] - ## Turn on the `log` feature, that makes egui log some errors using the [`log`](https://docs.rs/log) crate. log = ["dep:log"] diff --git a/crates/epaint/src/bezier.rs b/crates/epaint/src/bezier.rs index 4ad9a28228fe..6f61feb0ac3f 100644 --- a/crates/epaint/src/bezier.rs +++ b/crates/epaint/src/bezier.rs @@ -141,8 +141,8 @@ impl CubicBezierShape { /// split the original cubic curve into a new one within a range. pub fn split_range(&self, t_range: Range) -> Self { - crate::epaint_assert!( - t_range.start >= 0.0 && t_range.end <= 1.0 && t_range.start <= t_range.end, + debug_assert!( + 0.0 <= t_range.start && t_range.end <= 1.0 && t_range.start <= t_range.end, "range should be in [0.0,1.0]" ); @@ -178,7 +178,7 @@ impl CubicBezierShape { // https://scholarsarchive.byu.edu/cgi/viewcontent.cgi?article=1000&context=facpub#section.10.6 // and the error metric from the caffein owl blog post http://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html pub fn num_quadratics(&self, tolerance: f32) -> u32 { - crate::epaint_assert!(tolerance > 0.0, "the tolerance should be positive"); + debug_assert!(tolerance > 0.0, "the tolerance should be positive"); let x = self.points[0].x - 3.0 * self.points[1].x + 3.0 * self.points[2].x - self.points[3].x; @@ -273,7 +273,7 @@ impl CubicBezierShape { /// [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves) /// pub fn sample(&self, t: f32) -> Pos2 { - crate::epaint_assert!( + debug_assert!( t >= 0.0 && t <= 1.0, "the sample value should be in [0.0,1.0]" ); @@ -496,7 +496,7 @@ impl QuadraticBezierShape { /// [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves) /// pub fn sample(&self, t: f32) -> Pos2 { - crate::epaint_assert!( + debug_assert!( t >= 0.0 && t <= 1.0, "the sample value should be in [0.0,1.0]" ); diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index c8c47bdf9b2a..db48fce68763 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -138,22 +138,6 @@ pub enum Primitive { Callback(PaintCallback), } -// ---------------------------------------------------------------------------- - -/// An assert that is only active when `epaint` is compiled with the `extra_asserts` feature -/// or with the `extra_debug_asserts` feature in debug builds. -#[macro_export] -macro_rules! epaint_assert { - ($($arg: tt)*) => { - if cfg!(any( - feature = "extra_asserts", - all(feature = "extra_debug_asserts", debug_assertions), - )) { - assert!($($arg)*); - } - } -} - // --------------------------------------------------------------------------- /// Was epaint compiled with the `rayon` feature? diff --git a/crates/epaint/src/mesh.rs b/crates/epaint/src/mesh.rs index 7d5a51965d9c..9dd919274e69 100644 --- a/crates/epaint/src/mesh.rs +++ b/crates/epaint/src/mesh.rs @@ -109,7 +109,7 @@ impl Mesh { /// Append all the indices and vertices of `other` to `self`. pub fn append(&mut self, other: Self) { crate::profile_function!(); - crate::epaint_assert!(other.is_valid()); + debug_assert!(other.is_valid()); if self.is_empty() { *self = other; @@ -121,7 +121,7 @@ impl Mesh { /// Append all the indices and vertices of `other` to `self` without /// taking ownership. pub fn append_ref(&mut self, other: &Self) { - crate::epaint_assert!(other.is_valid()); + debug_assert!(other.is_valid()); if self.is_empty() { self.texture_id = other.texture_id; @@ -140,7 +140,7 @@ impl Mesh { #[inline(always)] pub fn colored_vertex(&mut self, pos: Pos2, color: Color32) { - crate::epaint_assert!(self.texture_id == TextureId::default()); + debug_assert!(self.texture_id == TextureId::default()); self.vertices.push(Vertex { pos, uv: WHITE_UV, @@ -203,7 +203,7 @@ impl Mesh { /// Uniformly colored rectangle. #[inline(always)] pub fn add_colored_rect(&mut self, rect: Rect, color: Color32) { - crate::epaint_assert!(self.texture_id == TextureId::default()); + debug_assert!(self.texture_id == TextureId::default()); self.add_rect_with_uv(rect, [WHITE_UV, WHITE_UV].into(), color); } @@ -212,7 +212,7 @@ impl Mesh { /// Splits this mesh into many smaller meshes (if needed) /// where the smaller meshes have 16-bit indices. pub fn split_to_u16(self) -> Vec { - crate::epaint_assert!(self.is_valid()); + debug_assert!(self.is_valid()); const MAX_SIZE: u32 = std::u16::MAX as u32; @@ -265,7 +265,7 @@ impl Mesh { vertices: self.vertices[(min_vindex as usize)..=(max_vindex as usize)].to_vec(), texture_id: self.texture_id, }; - crate::epaint_assert!(mesh.is_valid()); + debug_assert!(mesh.is_valid()); output.push(mesh); } output diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 593cc78b0f3d..70f9a8b5cfdd 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -313,7 +313,7 @@ impl Shape { #[inline] pub fn mesh(mesh: Mesh) -> Self { - crate::epaint_assert!(mesh.is_valid()); + debug_assert!(mesh.is_valid()); Self::Mesh(mesh) } diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 4135cc58d22b..71a4b780515b 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1311,7 +1311,7 @@ impl Tessellator { crate::profile_scope!("mesh"); if self.options.validate_meshes && !mesh.is_valid() { - crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh"); + debug_assert!(false, "Invalid Mesh in Shape::Mesh"); return; } // note: `append` still checks if the mesh is valid if extra asserts are enabled. @@ -1480,7 +1480,7 @@ impl Tessellator { /// * `out`: triangles are appended to this. pub fn tessellate_mesh(&mut self, mesh: &Mesh, out: &mut Mesh) { if !mesh.is_valid() { - crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh"); + debug_assert!(false, "Invalid Mesh in Shape::Mesh"); return; } @@ -1554,7 +1554,7 @@ impl Tessellator { } if *fill != Color32::TRANSPARENT { - crate::epaint_assert!( + debug_assert!( closed, "You asked to fill a path that is not closed. That makes no sense." ); @@ -1760,7 +1760,7 @@ impl Tessellator { color = color.gamma_multiply(*opacity_factor); } - crate::epaint_assert!(color != Color32::PLACEHOLDER, "A placeholder color made it to the tessellator. You forgot to set a fallback color."); + debug_assert!(color != Color32::PLACEHOLDER, "A placeholder color made it to the tessellator. You forgot to set a fallback color."); let offset = if *angle == 0.0 { pos.to_vec2() @@ -1864,7 +1864,7 @@ impl Tessellator { self.scratchpad_path.add_open_points(points); } if fill != Color32::TRANSPARENT { - crate::epaint_assert!( + debug_assert!( closed, "You asked to fill a path that is not closed. That makes no sense." ); @@ -1946,7 +1946,7 @@ impl Tessellator { for clipped_primitive in &clipped_primitives { if let Primitive::Mesh(mesh) = &clipped_primitive.primitive { - crate::epaint_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh"); + debug_assert!(mesh.is_valid(), "Tessellator generated invalid Mesh"); } } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index f0a18ba865d5..dec2cb290575 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -890,7 +890,7 @@ impl Galley { pcursor_it.offset += row.char_count_including_newline(); } } - crate::epaint_assert!(ccursor_it == self.end().ccursor); + debug_assert!(ccursor_it == self.end().ccursor); Cursor { ccursor: ccursor_it, // clamp rcursor: self.end_rcursor(), diff --git a/crates/epaint/src/textures.rs b/crates/epaint/src/textures.rs index e4661ff02b7b..4244c6871a7f 100644 --- a/crates/epaint/src/textures.rs +++ b/crates/epaint/src/textures.rs @@ -49,7 +49,7 @@ impl TextureManager { pub fn set(&mut self, id: TextureId, delta: ImageDelta) { if let Some(meta) = self.metas.get_mut(&id) { if let Some(pos) = delta.pos { - crate::epaint_assert!( + debug_assert!( pos[0] + delta.image.width() <= meta.size[0] && pos[1] + delta.image.height() <= meta.size[1], "Partial texture update is outside the bounds of texture {id:?}", @@ -63,7 +63,7 @@ impl TextureManager { } self.delta.set.push((id, delta)); } else { - crate::epaint_assert!(false, "Tried setting texture {id:?} which is not allocated"); + debug_assert!(false, "Tried setting texture {id:?} which is not allocated"); } } @@ -77,7 +77,7 @@ impl TextureManager { self.delta.free.push(id); } } else { - crate::epaint_assert!(false, "Tried freeing texture {id:?} which is not allocated"); + debug_assert!(false, "Tried freeing texture {id:?} which is not allocated"); } } @@ -88,7 +88,7 @@ impl TextureManager { if let Some(meta) = self.metas.get_mut(&id) { meta.retain_count += 1; } else { - crate::epaint_assert!( + debug_assert!( false, "Tried retaining texture {id:?} which is not allocated", ); From 2b2e70cb9179eba39f1d306ff70814f68c323ba4 Mon Sep 17 00:00:00 2001 From: TicClick Date: Sat, 11 May 2024 00:07:42 +0200 Subject: [PATCH 0081/1202] egui-winit: emit physical key presses when a non-Latin layout is active (#4461) resolves https://github.com/emilk/egui/issues/4081 (see discussion starting from https://github.com/emilk/egui/issues/3653#issuecomment-1962740175 for extra context) this partly restores event-emitting behaviour to the state before #3649, when shortcuts such as `Ctrl` + `C` used to work regardless of the active layout. the difference is that physical keys are only used in case of the logical ones' absence now among the named keys. while originally I have only limited this to clipboard shortcuts (Ctrl+C/V/X), honestly it felt like a half-assed solution. as a result, I decided to expand this behaviour to all key events to stick to the original logic, in case there are other workflows and hotkeys people rely on or expect to work out of the box. let me know if this is an issue. --- crates/egui-winit/src/lib.rs | 14 +++++++++----- crates/egui/src/data/input.rs | 8 +++++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index abadd9ee5c22..4ee404e44366 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -746,15 +746,19 @@ impl State { physical_key ); - if let Some(logical_key) = logical_key { + // "Logical OR physical key" is a fallback mechanism for keyboard layouts without Latin characters: it lets them + // emit events as if the corresponding keys from the Latin layout were pressed. In this case, clipboard shortcuts + // are mapped to the physical keys that normally contain C, X, V, etc. + // See also: https://github.com/emilk/egui/issues/3653 + if let Some(active_key) = logical_key.or(physical_key) { if pressed { - if is_cut_command(self.egui_input.modifiers, logical_key) { + if is_cut_command(self.egui_input.modifiers, active_key) { self.egui_input.events.push(egui::Event::Cut); return; - } else if is_copy_command(self.egui_input.modifiers, logical_key) { + } else if is_copy_command(self.egui_input.modifiers, active_key) { self.egui_input.events.push(egui::Event::Copy); return; - } else if is_paste_command(self.egui_input.modifiers, logical_key) { + } else if is_paste_command(self.egui_input.modifiers, active_key) { if let Some(contents) = self.clipboard.get() { let contents = contents.replace("\r\n", "\n"); if !contents.is_empty() { @@ -766,7 +770,7 @@ impl State { } self.egui_input.events.push(egui::Event::Key { - key: logical_key, + key: active_key, physical_key, pressed, repeat: false, // egui will fill this in for us! diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 8217f4f5a4d9..2260db1f433e 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -361,10 +361,12 @@ pub enum Event { /// A key was pressed or released. Key { - /// The logical key, heeding the users keymap. + /// Most of the time, it's the logical key, heeding the active keymap -- for instance, if the user has Dvorak + /// keyboard layout, it will be taken into account. /// - /// For instance, if the user is using Dvorak keyboard layout, - /// this will take that into account. + /// If it's impossible to determine the logical key on desktop platforms (say, in case of non-Latin letters), + /// `key` falls back to the value of the corresponding physical key. This is necessary for proper work of + /// standard shortcuts that only respond to Latin-based bindings (such as `Ctrl` + `V`). key: Key, /// The physical key, corresponding to the actual position on the keyboard. From a9efbcff360e8a6d194bc0593b84effa08ca15eb Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sat, 11 May 2024 07:09:14 +0900 Subject: [PATCH 0082/1202] IME for chinese (#4436) * Completed. * Closes #4430 IME for chinese --- crates/egui-winit/src/lib.rs | 42 +++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 4ee404e44366..058a27f16613 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -342,20 +342,12 @@ impl State { // We use input_method_editor_started to manually insert CompositionStart // between Commits. match ime { - winit::event::Ime::Enabled => { - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Enabled)); - self.has_sent_ime_enabled = true; + winit::event::Ime::Enabled => {} + winit::event::Ime::Preedit(_, None) => { + self.ime_event_enable(); } - winit::event::Ime::Preedit(_, None) => {} winit::event::Ime::Preedit(text, Some(_cursor)) => { - if !self.has_sent_ime_enabled { - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Enabled)); - self.has_sent_ime_enabled = true; - } + self.ime_event_enable(); self.egui_input .events .push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone()))); @@ -364,16 +356,10 @@ impl State { self.egui_input .events .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone()))); - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Disabled)); - self.has_sent_ime_enabled = false; + self.ime_event_disable(); } winit::event::Ime::Disabled => { - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Disabled)); - self.has_sent_ime_enabled = false; + self.ime_event_disable(); } }; @@ -492,6 +478,22 @@ impl State { } } + pub fn ime_event_enable(&mut self) { + if !self.has_sent_ime_enabled { + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Enabled)); + self.has_sent_ime_enabled = true; + } + } + + pub fn ime_event_disable(&mut self) { + self.egui_input + .events + .push(egui::Event::Ime(egui::ImeEvent::Disabled)); + self.has_sent_ime_enabled = false; + } + pub fn on_mouse_motion(&mut self, delta: (f64, f64)) { self.egui_input.events.push(egui::Event::MouseMoved(Vec2 { x: delta.0 as f32, From 27a22f991d9b747e031e7ffd274594facdda2a3a Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sat, 11 May 2024 07:41:02 +0900 Subject: [PATCH 0083/1202] Fix : In Windows, the 'egui_demo_app' screen does not appear. (#4410) * Related #4337 * Closes #4409 Fix : In Windows, the 'egui_demo_app' screen does not appear After the #4337 update. --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/native/epi_integration.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index b09f0f0e0c79..a8208a147ca0 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -20,10 +20,9 @@ pub fn viewport_builder( let mut viewport_builder = native_options.viewport.clone(); - let clamp_size_to_monitor_size = viewport_builder.clamp_size_to_monitor_size.unwrap_or( - // On some Linux systems, a window size larger than the monitor causes crashes - cfg!(target_os = "linux"), - ); + // On some Linux systems, a window size larger than the monitor causes crashes, + // and on Windows the window does not appear at all. + let clamp_size_to_monitor_size = viewport_builder.clamp_size_to_monitor_size.unwrap_or(true); // Always use the default window size / position on iOS. Trying to restore the previous position // causes the window to be shown too small. From 11fa9cc7ee7535fa0047cb96da5302a3c15ce19a Mon Sep 17 00:00:00 2001 From: Varphone Wong Date: Sat, 11 May 2024 06:42:03 +0800 Subject: [PATCH 0084/1202] Disable interaction for `ScrollArea` and `Plot` when UI is disabled (#4457) ## Summary This PR modifies `ScrollArea` and `Plot` to disable their interactions when the UI is disabled. ## Changes - Interaction with `ScrollArea` in `egui` is disabled when the UI is disabled. - Interaction with `Plot` in `egui_plot` is disabled when the UI is disabled. - These changes ensure that `ScrollArea` and `Plot` behave consistently with the rest of the UI, preventing them from responding to user input when the UI is in a disabled state. ## Impact This PR enhances the consistency of `egui`'s UI behavior by ensuring that all elements, including `ScrollArea` and `Plot`, respect the UI's disabled state. This prevents unexpected interactions when the UI is disabled. Closes #4341 --- crates/egui/src/containers/scroll_area.rs | 1 + crates/egui_plot/src/lib.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index c448269fb0cf..700c20402539 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -493,6 +493,7 @@ impl ScrollArea { } = self; let ctx = ui.ctx().clone(); + let scrolling_enabled = scrolling_enabled && ui.is_enabled(); let id_source = id_source.unwrap_or_else(|| Id::new("scroll_area")); let id = ui.make_persistent_id(id_source); diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 2e31f8312f53..cc721bd9cfff 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -766,6 +766,11 @@ impl Plot { sense, } = self; + // Disable interaction if ui is disabled. + let allow_zoom = allow_zoom.and(ui.is_enabled()); + let allow_drag = allow_drag.and(ui.is_enabled()); + let allow_scroll = allow_scroll.and(ui.is_enabled()); + // Determine position of widget. let pos = ui.available_rect_before_wrap().min; // Determine size of widget. From 66d2b3ffe43f969c7b75d70423ec02dacc6ba129 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sat, 11 May 2024 20:17:58 +0900 Subject: [PATCH 0085/1202] Treat `Event::PointerGone` as `PointerEvent::Released` (#4419) * Closes #4406 * Closes #4418 If `Event::PointerGone` occurs, it is treated as `PointerEvent::Released`. --- crates/egui/src/context.rs | 2 +- crates/egui/src/input_state.rs | 4 ++++ crates/egui/src/interaction.rs | 2 +- crates/egui_demo_lib/src/demo/tests.rs | 12 ++++++------ 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 04bdd16432aa..2d58be7e1265 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1875,8 +1875,8 @@ impl Context { drag_started: _, dragged, drag_stopped: _, - contains_pointer, hovered, + contains_pointer, } = interact_widgets; if true { diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 57a9e6477bf6..22851a470b6e 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -800,6 +800,10 @@ impl PointerState { } Event::PointerGone => { self.latest_pos = None; + self.pointer_events.push(PointerEvent::Released { + click: None, + button: PointerButton::Primary, + }); // NOTE: we do NOT clear `self.interact_pos` here. It will be cleared next frame. } Event::MouseMoved(delta) => *self.motion.get_or_insert(Vec2::ZERO) += *delta, diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 8a7b2d0948c5..e25e1c4aa134 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -283,7 +283,7 @@ pub(crate) fn interact( drag_started, dragged, drag_stopped, - contains_pointer, hovered, + contains_pointer, } } diff --git a/crates/egui_demo_lib/src/demo/tests.rs b/crates/egui_demo_lib/src/demo/tests.rs index 44e355d0e8e3..6a8348ac56ad 100644 --- a/crates/egui_demo_lib/src/demo/tests.rs +++ b/crates/egui_demo_lib/src/demo/tests.rs @@ -466,23 +466,23 @@ fn response_summary(response: &egui::Response, show_hovers: bool) -> String { // These are in inverse logical/chonological order, because we show them in the ui that way: if response.triple_clicked_by(button) { - writeln!(new_info, "Triple-clicked{button_suffix}").ok(); + writeln!(new_info, "Triple_clicked_by{button_suffix}").ok(); } if response.double_clicked_by(button) { - writeln!(new_info, "Double-clicked{button_suffix}").ok(); + writeln!(new_info, "Double_clicked_by{button_suffix}").ok(); } if response.clicked_by(button) { - writeln!(new_info, "Clicked{button_suffix}").ok(); + writeln!(new_info, "Clicked_by{button_suffix}").ok(); } if response.drag_stopped_by(button) { - writeln!(new_info, "Drag stopped{button_suffix}").ok(); + writeln!(new_info, "Drag_stopped_by{button_suffix}").ok(); } if response.dragged_by(button) { - writeln!(new_info, "Dragged{button_suffix}").ok(); + writeln!(new_info, "Dragged_by{button_suffix}").ok(); } if response.drag_started_by(button) { - writeln!(new_info, "Drag started{button_suffix}").ok(); + writeln!(new_info, "Drag_started_by{button_suffix}").ok(); } } From e06b225dabd763f7247ff4cab6600737f3327bc6 Mon Sep 17 00:00:00 2001 From: Avery Radmacher <45777186+avery-radmacher@users.noreply.github.com> Date: Sat, 11 May 2024 10:48:12 -0400 Subject: [PATCH 0086/1202] Fix: Window position creeps between executions on scaled monitors (#4443) * Closes * Refactors active monitor detection so it can be called from multiple locations. Compare this gif to the one on the issue report. ![egui_window_position_no_creep](https://github.com/emilk/egui/assets/45777186/8e05d4fb-266e-48b9-9223-b65f16500a99) ### Investigation notes - [`WindowSettings.inner_position_pixels` and `WindowSettings.outer_position_pixels`](https://github.com/emilk/egui/blob/master/crates/egui-winit/src/window_settings.rs#L8-L12) are stored in physical/pixel coordinates. - `ViewportBuilder::with_position` expects to be passed a position in _logical_ coordinates. - Prior to this PR, the position was being passed from `WindowSettings` to `with_position` [without any scaling](https://github.com/emilk/egui/blob/master/crates/egui-winit/src/window_settings.rs#L61-L68). This was the root cause of the issue. - The fix is to first convert the position to logical coordinates, respecting the scaling factor of the active monitor. This requires us to first determine the active monitor, so I factored out some of the logic in [`clamp_pos_to_monitor`](https://github.com/emilk/egui/blob/master/crates/egui-winit/src/window_settings.rs#L130) to find the active monitor. --- crates/eframe/src/native/epi_integration.rs | 6 +++- crates/egui-winit/src/window_settings.rs | 39 +++++++++++++++++---- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index a8208a147ca0..827b9ec249cb 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -38,7 +38,11 @@ pub fn viewport_builder( } window_settings.clamp_position_to_monitors(egui_zoom_factor, event_loop); - viewport_builder = window_settings.initialize_viewport_builder(viewport_builder); + viewport_builder = window_settings.initialize_viewport_builder( + egui_zoom_factor, + event_loop, + viewport_builder, + ); window_settings.inner_size_points() } else { if let Some(pos) = viewport_builder.position { diff --git a/crates/egui-winit/src/window_settings.rs b/crates/egui-winit/src/window_settings.rs index c59a0f451ce4..ec633d3df0a8 100644 --- a/crates/egui-winit/src/window_settings.rs +++ b/crates/egui-winit/src/window_settings.rs @@ -50,8 +50,10 @@ impl WindowSettings { self.inner_size_points } - pub fn initialize_viewport_builder( + pub fn initialize_viewport_builder( &self, + egui_zoom_factor: f32, + event_loop: &winit::event_loop::EventLoopWindowTarget, mut viewport_builder: ViewportBuilder, ) -> ViewportBuilder { crate::profile_function!(); @@ -64,7 +66,15 @@ impl WindowSettings { self.outer_position_pixels }; if let Some(pos) = pos_px { - viewport_builder = viewport_builder.with_position(pos); + let monitor_scale_factor = if let Some(inner_size_points) = self.inner_size_points { + find_active_monitor(egui_zoom_factor, event_loop, inner_size_points, &pos) + .map_or(1.0, |monitor| monitor.scale_factor() as f32) + } else { + 1.0 + }; + + let scaled_pos = pos / (egui_zoom_factor * monitor_scale_factor); + viewport_builder = viewport_builder.with_position(scaled_pos); } if let Some(inner_size_points) = self.inner_size_points { @@ -127,12 +137,12 @@ impl WindowSettings { } } -fn clamp_pos_to_monitors( +fn find_active_monitor( egui_zoom_factor: f32, event_loop: &winit::event_loop::EventLoopWindowTarget, window_size_pts: egui::Vec2, - position_px: &mut egui::Pos2, -) { + position_px: &egui::Pos2, +) -> Option { crate::profile_function!(); let monitors = event_loop.available_monitors(); @@ -142,7 +152,7 @@ fn clamp_pos_to_monitors( .primary_monitor() .or_else(|| event_loop.available_monitors().next()) else { - return; // no monitors 🤷 + return None; // no monitors 🤷 }; for monitor in monitors { @@ -159,6 +169,23 @@ fn clamp_pos_to_monitors( } } + Some(active_monitor) +} + +fn clamp_pos_to_monitors( + egui_zoom_factor: f32, + event_loop: &winit::event_loop::EventLoopWindowTarget, + window_size_pts: egui::Vec2, + position_px: &mut egui::Pos2, +) { + crate::profile_function!(); + + let Some(active_monitor) = + find_active_monitor(egui_zoom_factor, event_loop, window_size_pts, position_px) + else { + return; // no monitors 🤷 + }; + let mut window_size_px = window_size_pts * (egui_zoom_factor * active_monitor.scale_factor() as f32); // Add size of title bar. This is 32 px by default in Win 10/11. From 3b3ce22adc48f9bc362dd546e8c2633d314e68fb Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sat, 11 May 2024 23:49:27 +0900 Subject: [PATCH 0087/1202] Make sure plot size is positive (#4429) * Closes #4425 Fix: in Plot, Minimum values for screen protection. --- crates/egui_demo_lib/src/demo/context_menu.rs | 2 ++ crates/egui_plot/src/lib.rs | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/egui_demo_lib/src/demo/context_menu.rs b/crates/egui_demo_lib/src/demo/context_menu.rs index 1b71eb4f787e..5195a2b06092 100644 --- a/crates/egui_demo_lib/src/demo/context_menu.rs +++ b/crates/egui_demo_lib/src/demo/context_menu.rs @@ -97,11 +97,13 @@ impl super::View for ContextMenus { egui::Grid::new("button_grid").show(ui, |ui| { ui.add( egui::DragValue::new(&mut self.width) + .clamp_range(0.0..=f32::INFINITY) .speed(1.0) .prefix("Width: "), ); ui.add( egui::DragValue::new(&mut self.height) + .clamp_range(0.0..=f32::INFINITY) .speed(1.0) .prefix("Height: "), ); diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index cc721bd9cfff..6b9d622d6e3d 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -742,7 +742,7 @@ impl Plot { margin_fraction, width, height, - min_size, + mut min_size, data_aspect, view_aspect, mut show_x, @@ -773,6 +773,10 @@ impl Plot { // Determine position of widget. let pos = ui.available_rect_before_wrap().min; + // Minimum values for screen protection + min_size.x = min_size.x.at_least(1.0); + min_size.y = min_size.y.at_least(1.0); + // Determine size of widget. let size = { let width = width From 4f2f0575082c4d7ce99b085ff2c4a0ff6735e12d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 11 May 2024 20:01:40 +0200 Subject: [PATCH 0088/1202] Update arboard (#4482) Updating arboard v3.3.1 -> v3.4.0 Updating clipboard-win v5.1.0 -> v5.3.1 --- Cargo.lock | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4466ae0f3449..e751dd77f23a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,17 +199,16 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arboard" -version = "3.3.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1faa3c733d9a3dd6fbaf85da5d162a2e03b2e0033a90dceb0e2a90fdd1e5380a" +checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89" dependencies = [ "clipboard-win", "log", - "objc", - "objc-foundation", - "objc_id", + "objc2 0.5.1", + "objc2-app-kit", + "objc2-foundation", "parking_lot", - "thiserror", "x11rb", ] @@ -797,9 +796,9 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "clipboard-win" -version = "5.1.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec832972fefb8cf9313b45a0d1945e29c9c251f1d4c6eafc5fe2124c02d2e81" +checksum = "79f4473f5144e20d9aceaf2972478f06ddf687831eafeeb434fbaf0acc4144ad" dependencies = [ "error-code", ] @@ -1146,7 +1145,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.0", + "libloading 0.7.4", ] [[package]] @@ -1958,7 +1957,7 @@ dependencies = [ "bitflags 2.5.0", "com", "libc", - "libloading 0.8.0", + "libloading 0.7.4", "thiserror", "widestring", "winapi", @@ -4321,7 +4320,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.0", + "libloading 0.7.4", "log", "metal", "naga", From 059218d954cfbe272e1958a91dfecb701abed7a1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 11 May 2024 20:17:06 +0200 Subject: [PATCH 0089/1202] eframe: Remove dependency on `thiserror` (#4483) Less dependencies => faster compile times * Part of https://github.com/emilk/egui/issues/4481 --- Cargo.lock | 1 - crates/eframe/Cargo.toml | 1 - crates/eframe/src/lib.rs | 99 +++++++++++++++++++++++++++++++++++----- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e751dd77f23a..198494929e54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1203,7 +1203,6 @@ dependencies = [ "ron", "serde", "static_assertions", - "thiserror", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index c5cfae877332..33cc5ba6bfe0 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -134,7 +134,6 @@ log.workspace = true parking_lot.workspace = true raw-window-handle.workspace = true static_assertions = "1.1.0" -thiserror.workspace = true web-time.workspace = true # Optional dependencies diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index 98576358a70d..e1df19a8b0bc 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -331,37 +331,112 @@ pub fn run_simple_native( // ---------------------------------------------------------------------------- /// The different problems that can occur when trying to run `eframe`. -#[derive(thiserror::Error, Debug)] +#[derive(Debug)] pub enum Error { /// An error from [`winit`]. #[cfg(not(target_arch = "wasm32"))] - #[error("winit error: {0}")] - Winit(#[from] winit::error::OsError), + Winit(winit::error::OsError), /// An error from [`winit::event_loop::EventLoop`]. #[cfg(not(target_arch = "wasm32"))] - #[error("winit EventLoopError: {0}")] - WinitEventLoop(#[from] winit::error::EventLoopError), + WinitEventLoop(winit::error::EventLoopError), /// An error from [`glutin`] when using [`glow`]. #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] - #[error("glutin error: {0}")] - Glutin(#[from] glutin::error::Error), + Glutin(glutin::error::Error), /// An error from [`glutin`] when using [`glow`]. #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] - #[error("Found no glutin configs matching the template: {0:?}. Error: {1:?}")] NoGlutinConfigs(glutin::config::ConfigTemplate, Box), /// An error from [`glutin`] when using [`glow`]. #[cfg(feature = "glow")] - #[error("egui_glow: {0}")] - OpenGL(#[from] egui_glow::PainterError), + OpenGL(egui_glow::PainterError), /// An error from [`wgpu`]. #[cfg(feature = "wgpu")] - #[error("WGPU error: {0}")] - Wgpu(#[from] egui_wgpu::WgpuError), + Wgpu(egui_wgpu::WgpuError), +} + +impl std::error::Error for Error {} + +#[cfg(not(target_arch = "wasm32"))] +impl From for Error { + #[inline] + fn from(err: winit::error::OsError) -> Self { + Self::Winit(err) + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for Error { + #[inline] + fn from(err: winit::error::EventLoopError) -> Self { + Self::WinitEventLoop(err) + } +} + +#[cfg(all(feature = "glow", not(target_arch = "wasm32")))] +impl From for Error { + #[inline] + fn from(err: glutin::error::Error) -> Self { + Self::Glutin(err) + } +} + +#[cfg(feature = "glow")] +impl From for Error { + #[inline] + fn from(err: egui_glow::PainterError) -> Self { + Self::OpenGL(err) + } +} + +#[cfg(feature = "wgpu")] +impl From for Error { + #[inline] + fn from(err: egui_wgpu::WgpuError) -> Self { + Self::Wgpu(err) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(not(target_arch = "wasm32"))] + Self::Winit(err) => { + write!(f, "winit error: {err}") + } + + #[cfg(not(target_arch = "wasm32"))] + Self::WinitEventLoop(err) => { + write!(f, "winit EventLoopError: {err}") + } + + #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] + Self::Glutin(err) => { + write!(f, "glutin error: {err}") + } + + #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] + Self::NoGlutinConfigs(template, err) => { + write!( + f, + "Found no glutin configs matching the template: {template:?}. Error: {err}" + ) + } + + #[cfg(feature = "glow")] + Self::OpenGL(err) => { + write!(f, "egui_glow: {err}") + } + + #[cfg(feature = "wgpu")] + Self::Wgpu(err) => { + write!(f, "WGPU error: {err}") + } + } + } } /// Short for `Result`. From c3f386aa301f26106397c4e14434bd5a734ba6b6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 11 May 2024 20:17:19 +0200 Subject: [PATCH 0090/1202] Remove work-around for `unsafe` in puffin macro (#4484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …since it is no longer in the puffin macro --- crates/egui/src/lib.rs | 2 -- crates/egui_demo_lib/src/lib.rs | 2 -- crates/egui_extras/src/lib.rs | 2 -- crates/emath/src/lib.rs | 2 -- crates/epaint/src/lib.rs | 2 -- 5 files changed, 10 deletions(-) diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index a846acc701e2..b367158a58b7 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -371,8 +371,6 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] -#![cfg_attr(feature = "puffin", deny(unsafe_code))] -#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] mod animation_manager; pub mod containers; diff --git a/crates/egui_demo_lib/src/lib.rs b/crates/egui_demo_lib/src/lib.rs index 7dba93b4117d..68bdc8eed855 100644 --- a/crates/egui_demo_lib/src/lib.rs +++ b/crates/egui_demo_lib/src/lib.rs @@ -10,8 +10,6 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] -#![cfg_attr(feature = "puffin", deny(unsafe_code))] -#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] mod demo; pub mod easy_mark; diff --git a/crates/egui_extras/src/lib.rs b/crates/egui_extras/src/lib.rs index e899106cd45a..381242c90b5b 100644 --- a/crates/egui_extras/src/lib.rs +++ b/crates/egui_extras/src/lib.rs @@ -8,8 +8,6 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] -#![cfg_attr(feature = "puffin", deny(unsafe_code))] -#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] #[cfg(feature = "chrono")] mod datepicker; diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 4ac46f219470..6ad48bd25437 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -20,8 +20,6 @@ //! #![allow(clippy::float_cmp)] -#![cfg_attr(feature = "puffin", deny(unsafe_code))] -#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] use std::ops::{Add, Div, Mul, RangeInclusive, Sub}; diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index db48fce68763..d5e7055813b4 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -22,8 +22,6 @@ #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] -#![cfg_attr(feature = "puffin", deny(unsafe_code))] -#![cfg_attr(not(feature = "puffin"), forbid(unsafe_code))] mod bezier; pub mod color; From acfe9f6f3b3eaff9a603c1568bd590948db52329 Mon Sep 17 00:00:00 2001 From: crumblingstatue Date: Mon, 13 May 2024 12:49:31 +0200 Subject: [PATCH 0091/1202] Make `epaint::mutex::RwLock` allow `?Sized` types (#4485) `parking_lot`'s `RwLock` allows this, so probably `epaint`'s `RwLock` should too. Although I'm not sure how much it's intended for users, rather than just internal use by `egui`. --- crates/epaint/src/mutex.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index 2c61f038f9c1..157701c2be04 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -133,14 +133,16 @@ mod rw_lock_impl { /// the feature `deadlock_detection` is turned enabled, in which case /// extra checks are added to detect deadlocks. #[derive(Default)] - pub struct RwLock(parking_lot::RwLock); + pub struct RwLock(parking_lot::RwLock); impl RwLock { #[inline(always)] pub fn new(val: T) -> Self { Self(parking_lot::RwLock::new(val)) } + } + impl RwLock { #[inline(always)] pub fn read(&self) -> RwLockReadGuard<'_, T> { parking_lot::RwLockReadGuard::map(self.0.read(), |v| v) From 44d65f41ac297847ceedc452c6a52299fe17f4a9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 13 May 2024 13:35:15 +0200 Subject: [PATCH 0092/1202] Update `image` crate to 0.25 (#4160) To not produce duplicating deps in Rerun (https://github.com/rerun-io/rerun/pull/5280) I suggest we wait with merging this until these crates have updated to `image` 0.25: * [x] [`arboard`](https://crates.io/crates/arboard) * [x] [`gltf`](https://crates.io/crates/gltf) --- Cargo.lock | 57 ++++++++++------------------ Cargo.toml | 1 + crates/eframe/Cargo.toml | 4 +- crates/eframe/src/icon_data.rs | 2 +- crates/eframe/src/native/app_icon.rs | 2 +- crates/egui_demo_app/Cargo.toml | 5 +-- crates/egui_extras/Cargo.toml | 5 +-- crates/egui_extras/README.md | 2 +- crates/egui_extras/src/image.rs | 4 +- crates/egui_extras/src/loaders.rs | 2 +- examples/images/Cargo.toml | 5 +-- examples/save_plot/Cargo.toml | 2 +- examples/screenshot/Cargo.toml | 2 +- 13 files changed, 33 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 198494929e54..373236f18832 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -819,12 +819,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecdffb913a326b6c642290a0d0ec8e8d6597291acdc07cc4c9cb4b3635d44cf9" -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "com" version = "0.6.0" @@ -2072,17 +2066,16 @@ dependencies = [ [[package]] name = "image" -version = "0.24.7" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +checksum = "a9b4f005360d32e9325029b38ba47ebd7a56f3316df09249368939562d518645" dependencies = [ "bytemuck", "byteorder", - "color_quant", - "jpeg-decoder", - "num-rational", "num-traits", "png", + "zune-core", + "zune-jpeg", ] [[package]] @@ -2188,12 +2181,6 @@ dependencies = [ "libc", ] -[[package]] -name = "jpeg-decoder" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" - [[package]] name = "js-sys" version = "0.3.69" @@ -2498,27 +2485,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.16" @@ -4883,6 +4849,21 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "3.15.0" diff --git a/Cargo.toml b/Cargo.toml index 0853df671d61..aa0d4153fadb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ backtrace = "0.3" criterion = { version = "0.5.1", default-features = false } document-features = " 0.2.8" glow = "0.13" +image = { version = "0.25", default-features = false } log = { version = "0.4", features = ["std"] } nohash-hasher = "0.2" parking_lot = "0.12" diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 33cc5ba6bfe0..bb3544fffa4d 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -154,9 +154,7 @@ egui-winit = { workspace = true, default-features = false, features = [ "clipboard", "links", ] } -image = { version = "0.24", default-features = false, features = [ - "png", -] } # Needed for app icon +image = { workspace = true, features = ["png"] } # Needed for app icon winit = { workspace = true, default-features = false, features = ["rwh_06"] } # optional native: diff --git a/crates/eframe/src/icon_data.rs b/crates/eframe/src/icon_data.rs index 847228f9ec03..ed514d00e1f7 100644 --- a/crates/eframe/src/icon_data.rs +++ b/crates/eframe/src/icon_data.rs @@ -54,7 +54,7 @@ impl IconDataExt for IconData { image .write_to( &mut std::io::Cursor::new(&mut png_bytes), - image::ImageOutputFormat::Png, + image::ImageFormat::Png, ) .map_err(|err| err.to_string())?; Ok(png_bytes) diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index 90100298680f..840bf367b277 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -118,7 +118,7 @@ fn set_app_icon_windows(icon_data: &IconData) -> AppIconStatus { if image_scaled .write_to( &mut std::io::Cursor::new(&mut image_scaled_bytes), - image::ImageOutputFormat::Png, + image::ImageFormat::Png, ) .is_err() { diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 0491f5ff934e..5878848ceae0 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -60,10 +60,7 @@ wgpu = { workspace = true, features = ["webgpu", "webgl"], optional = true } # feature "http": ehttp = { version = "0.5", optional = true } -image = { version = "0.24", optional = true, default-features = false, features = [ - "jpeg", - "png", -] } +image = { workspace = true, optional = true, features = ["jpeg", "png"] } poll-promise = { version = "0.3", optional = true, default-features = false } # feature "persistence": diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index 372f754cc52e..169e94c8b6c0 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -45,8 +45,7 @@ http = ["dep:ehttp"] ## ## You also need to ALSO opt-in to the image formats you want to support, like so: ## ```toml -## image = { version = "0.24", features = ["jpeg", "png"] } # Add the types you want support for - +## image = { version = "0.25", features = ["jpeg", "png"] } # Add the types you want support for ## ``` image = ["dep:image"] @@ -82,7 +81,7 @@ chrono = { version = "0.4", optional = true, default-features = false, features ## Enable this when generating docs. document-features = { workspace = true, optional = true } -image = { version = "0.24", optional = true, default-features = false } +image = { workspace = true, optional = true } # file feature mime_guess2 = { version = "2", optional = true, default-features = false } diff --git a/crates/egui_extras/README.md b/crates/egui_extras/README.md index 0ccb1de2b1e8..4e5e96dce63a 100644 --- a/crates/egui_extras/README.md +++ b/crates/egui_extras/README.md @@ -13,7 +13,7 @@ One thing `egui_extras` is commonly used for is to install image loaders for `eg ```toml egui_extras = { version = "*", features = ["all_loaders"] } -image = { version = "0.24", features = ["jpeg", "png"] } # Add the types you want support for +image = { version = "0.25", features = ["jpeg", "png"] } # Add the types you want support for ``` ```rs diff --git a/crates/egui_extras/src/image.rs b/crates/egui_extras/src/image.rs index a0b042e26ae7..f6301aec8238 100644 --- a/crates/egui_extras/src/image.rs +++ b/crates/egui_extras/src/image.rs @@ -43,7 +43,7 @@ impl RetainedImage { /// `image_bytes` should be the raw contents of an image file (`.png`, `.jpg`, …). /// /// Requires the "image" feature. You must also opt-in to the image formats you need - /// with e.g. `image = { version = "0.24", features = ["jpeg", "png"] }`. + /// with e.g. `image = { version = "0.25", features = ["jpeg", "png"] }`. /// /// # Errors /// On invalid image or unsupported image format. @@ -195,7 +195,7 @@ use egui::ColorImage; /// Load a (non-svg) image. /// /// Requires the "image" feature. You must also opt-in to the image formats you need -/// with e.g. `image = { version = "0.24", features = ["jpeg", "png"] }`. +/// with e.g. `image = { version = "0.25", features = ["jpeg", "png"] }`. /// /// # Errors /// On invalid image or unsupported image format. diff --git a/crates/egui_extras/src/loaders.rs b/crates/egui_extras/src/loaders.rs index 05df9bc8eef9..d66ea483071d 100644 --- a/crates/egui_extras/src/loaders.rs +++ b/crates/egui_extras/src/loaders.rs @@ -20,7 +20,7 @@ /// /// ```toml,ignore /// egui_extras = { version = "*", features = ["all_loaders"] } -/// image = { version = "0.24", features = ["jpeg", "png"] } # Add the types you want support for +/// image = { version = "0.25", features = ["jpeg", "png"] } # Add the types you want support for /// ``` /// /// ⚠ You have to configure both the supported loaders in `egui_extras` _and_ the supported image formats diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index 092535c91250..57d552e3be40 100644 --- a/examples/images/Cargo.toml +++ b/examples/images/Cargo.toml @@ -21,7 +21,4 @@ env_logger = { version = "0.10", default-features = false, features = [ "auto-color", "humantime", ] } -image = { version = "0.24", default-features = false, features = [ - "jpeg", - "png", -] } +image = { workspace = true, features = ["jpeg", "png"] } diff --git a/examples/save_plot/Cargo.toml b/examples/save_plot/Cargo.toml index c4c287c001de..d20c927b058b 100644 --- a/examples/save_plot/Cargo.toml +++ b/examples/save_plot/Cargo.toml @@ -16,7 +16,7 @@ eframe = { workspace = true, features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } egui_plot.workspace = true -image = { version = "0.24", default-features = false, features = ["png"] } +image = { workspace = true, features = ["png"] } rfd = "0.13.0" env_logger = { version = "0.10", default-features = false, features = [ "auto-color", diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index aba066669c94..84399e4bc24b 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -24,4 +24,4 @@ env_logger = { version = "0.10", default-features = false, features = [ "auto-color", "humantime", ] } -image = { version = "0.24", default-features = false, features = ["png"] } +image = { workspace = true, features = ["png"] } From c1eb3f884db8bc4f52dbae4f261619cee651f411 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Tue, 14 May 2024 11:02:49 +0200 Subject: [PATCH 0093/1202] Move dependencies to workspace (#4495) Inspired by: https://github.com/emilk/egui/blob/44d65f41ac297847ceedc452c6a52299fe17f4a9/Cargo.toml#L65 I took the liberty of removing that comment since I *think* that I got all "relevant" ones (showing up more than once, sort of). --- Cargo.toml | 9 ++++++++- crates/ecolor/Cargo.toml | 4 ++-- crates/eframe/Cargo.toml | 16 ++++++++-------- crates/egui-wgpu/Cargo.toml | 2 +- crates/egui-winit/Cargo.toml | 2 +- crates/egui/Cargo.toml | 4 ++-- crates/egui_demo_app/Cargo.toml | 8 ++++---- crates/egui_demo_lib/Cargo.toml | 2 +- crates/egui_extras/Cargo.toml | 2 +- crates/egui_glow/Cargo.toml | 10 +++++----- crates/egui_plot/Cargo.toml | 2 +- crates/emath/Cargo.toml | 4 ++-- crates/epaint/Cargo.toml | 4 ++-- 13 files changed, 38 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aa0d4153fadb..8c59b92deb5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,24 +62,31 @@ egui_demo_lib = { version = "0.27.2", path = "crates/egui_demo_lib", default-fea egui_glow = { version = "0.27.2", path = "crates/egui_glow", default-features = false } eframe = { version = "0.27.2", path = "crates/eframe", default-features = false } -#TODO(emilk): make more things workspace dependencies ahash = { version = "0.8.6", default-features = false, features = [ "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead "std", ] } backtrace = "0.3" +bytemuck = "1.7.2" criterion = { version = "0.5.1", default-features = false } document-features = " 0.2.8" glow = "0.13" +glutin = "0.31" +glutin-winit = "0.4" image = { version = "0.25", default-features = false } log = { version = "0.4", features = ["std"] } nohash-hasher = "0.2" parking_lot = "0.12" puffin = "0.19" puffin_http = "0.16" +ron = "0.8" raw-window-handle = "0.6.0" +serde = { version = "1", features = ["derive"] } thiserror = "1.0.37" web-time = "0.2" # Timekeeping for native and web +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +web-sys = "0.3.58" wgpu = { version = "0.20.0", default-features = false, features = [ # Make the renderer `Sync` even on wasm32, because it makes the code simpler: "fragile-send-sync-non-atomic-wasm", diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index fe4c6f444b1a..6b46bc1062c3 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -33,7 +33,7 @@ default = [] #! ### Optional dependencies ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast `ecolor` types to `&[u8]`. -bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } +bytemuck = { workspace = true, optional = true, features = ["derive"] } ## [`cint`](https://docs.rs/cint) enables interoperability with other color libraries. cint = { version = "0.3.1", optional = true } @@ -45,4 +45,4 @@ color-hex = { version = "0.2.0", optional = true } document-features = { workspace = true, optional = true } ## Allow serialization using [`serde`](https://docs.rs/serde). -serde = { version = "1", optional = true, features = ["derive"] } +serde = { workspace = true, optional = true } diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index bb3544fffa4d..f992c5c66a27 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -144,8 +144,8 @@ glow = { workspace = true, optional = true } rwh_05 = { package = "raw-window-handle", version = "0.5.2", optional = true, features = [ "std", ] } -ron = { version = "0.8", optional = true, features = ["integer128"] } -serde = { version = "1", optional = true, features = ["derive"] } +ron = { workspace = true, optional = true, features = ["integer128"] } +serde = { workspace = true, optional = true } # ------------------------------------------- # native: @@ -166,8 +166,8 @@ pollster = { version = "0.3", optional = true } # needed for wgpu # we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps. # this can be done at the same time we expose x11/wayland features of winit crate. -glutin = { version = "0.31", optional = true } -glutin-winit = { version = "0.4", optional = true } +glutin = { workspace = true, optional = true } +glutin-winit = { workspace = true, optional = true } puffin = { workspace = true, optional = true } wgpu = { workspace = true, optional = true, features = [ # Let's enable some backends so that users can use `eframe` out-of-the-box @@ -199,12 +199,12 @@ winapi = { version = "0.3.9", features = ["winuser"] } # ------------------------------------------- # web: [target.'cfg(target_arch = "wasm32")'.dependencies] -bytemuck = "1.7" +bytemuck.workspace = true js-sys = "0.3" percent-encoding = "2.1" -wasm-bindgen = "0.2" -wasm-bindgen-futures = "0.4" -web-sys = { version = "0.3.58", features = [ +wasm-bindgen.workspace = true +wasm-bindgen-futures.workspace = true +web-sys = { workspace = true, features = [ "BinaryType", "Blob", "Clipboard", diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 817233b88bad..1de7d1446ef1 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -50,7 +50,7 @@ x11 = ["winit?/x11"] egui = { workspace = true, default-features = false } epaint = { workspace = true, default-features = false, features = ["bytemuck"] } -bytemuck = "1.7" +bytemuck.workspace = true document-features.workspace = true log.workspace = true thiserror.workspace = true diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index fc2b8a536200..6354b7a439f1 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -73,7 +73,7 @@ accesskit_winit = { version = "0.16.0", optional = true } document-features = { workspace = true, optional = true } puffin = { workspace = true, optional = true } -serde = { version = "1.0", optional = true, features = ["derive"] } +serde = { workspace = true, optional = true } webbrowser = { version = "1.0.0", optional = true } [target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies] diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 909beba133aa..2e8e1ddd5573 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -95,5 +95,5 @@ document-features = { workspace = true, optional = true } log = { workspace = true, optional = true } puffin = { workspace = true, optional = true } -ron = { version = "0.8", optional = true } -serde = { version = "1", optional = true, features = ["derive", "rc"] } +ron = { workspace = true, optional = true } +serde = { workspace = true, optional = true, features = ["derive", "rc"] } diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 5878848ceae0..87f787a054a6 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -51,7 +51,7 @@ log.workspace = true # Optional dependencies: -bytemuck = { version = "1.7.1", optional = true } +bytemuck = { workspace = true, optional = true } puffin = { workspace = true, optional = true } puffin_http = { workspace = true, optional = true } # Enable both WebGL & WebGPU when targeting the web (these features have no effect when not targeting wasm32) @@ -64,7 +64,7 @@ image = { workspace = true, optional = true, features = ["jpeg", "png"] } poll-promise = { version = "0.3", optional = true, default-features = false } # feature "persistence": -serde = { version = "1", optional = true, features = ["derive"] } +serde = { workspace = true, optional = true } # native: @@ -78,5 +78,5 @@ rfd = { version = "0.13", optional = true } # web: [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "=0.2.92" -wasm-bindgen-futures = "0.4" -web-sys = "0.3" +wasm-bindgen-futures.workspace = true +web-sys.workspace = true diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index d1daa5b8cfba..9a6e79dd99cb 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -52,7 +52,7 @@ unicode_names2 = { version = "0.6.0", default-features = false } # this old vers chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] } ## Enable this when generating docs. document-features = { workspace = true, optional = true } -serde = { version = "1", optional = true, features = ["derive"] } +serde = { workspace = true, optional = true } [dev-dependencies] diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index 169e94c8b6c0..edbac462c944 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -66,7 +66,7 @@ egui = { workspace = true, default-features = false, features = ["serde"] } enum-map = { version = "2", features = ["serde"] } log.workspace = true -serde = { version = "1", features = ["derive"] } +serde.workspace = true #! ### Optional dependencies diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 44939640cfbd..29129dddc3f2 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -56,7 +56,7 @@ x11 = ["winit?/x11"] egui = { workspace = true, default-features = false, features = ["bytemuck"] } egui-winit = { workspace = true, optional = true, default-features = false } -bytemuck = "1.7" +bytemuck.workspace = true glow.workspace = true log.workspace = true memoffset = "0.9" @@ -74,13 +74,13 @@ winit = { workspace = true, optional = true, default-features = false, features # Web: [target.'cfg(target_arch = "wasm32")'.dependencies] -web-sys = { version = "0.3", features = ["console"] } -wasm-bindgen = "0.2" +web-sys = { workspace = true, features = ["console"] } +wasm-bindgen.workspace = true [dev-dependencies] -glutin = "0.31" # examples/pure_glow -glutin-winit = "0.4.0" +glutin.workspace = true # examples/pure_glow +glutin-winit.workspace = true # glutin stuck on old version of raw-window-handle: rwh_05 = { package = "raw-window-handle", version = "0.5.2", features = [ "std", diff --git a/crates/egui_plot/Cargo.toml b/crates/egui_plot/Cargo.toml index 91c4b3a50fc4..bc42765ea162 100644 --- a/crates/egui_plot/Cargo.toml +++ b/crates/egui_plot/Cargo.toml @@ -42,4 +42,4 @@ egui = { workspace = true, default-features = false } ## Enable this when generating docs. document-features = { workspace = true, optional = true } -serde = { version = "1", optional = true, features = ["derive"] } +serde = { workspace = true, optional = true } diff --git a/crates/emath/Cargo.toml b/crates/emath/Cargo.toml index 99fc1b004b8c..f47f281c9681 100644 --- a/crates/emath/Cargo.toml +++ b/crates/emath/Cargo.toml @@ -30,7 +30,7 @@ default = [] #! ### Optional dependencies ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast `emath` types to `&[u8]`. -bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } +bytemuck = { workspace = true, optional = true, features = ["derive"] } ## Enable this when generating docs. document-features = { workspace = true, optional = true } @@ -39,4 +39,4 @@ document-features = { workspace = true, optional = true } mint = { version = "0.5.6", optional = true } ## Allow serialization using [`serde`](https://docs.rs/serde). -serde = { version = "1", optional = true, features = ["derive"] } +serde = { workspace = true, optional = true } diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 852066d046cb..3588a280dc1e 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -84,7 +84,7 @@ nohash-hasher.workspace = true parking_lot.workspace = true # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. #! ### Optional dependencies -bytemuck = { version = "1.7.2", optional = true, features = ["derive"] } +bytemuck = { workspace = true, optional = true, features = ["derive"] } ## Enable this when generating docs. document-features = { workspace = true, optional = true } @@ -94,7 +94,7 @@ puffin = { workspace = true, optional = true } rayon = { version = "1.7", optional = true } ## Allow serialization using [`serde`](https://docs.rs/serde) . -serde = { version = "1", optional = true, features = ["derive", "rc"] } +serde = { workspace = true, optional = true, features = ["derive", "rc"] } # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] From ce59e43869e5bf4b29dc81c1d0556bdad125c92d Mon Sep 17 00:00:00 2001 From: Fabian Lippold Date: Wed, 15 May 2024 09:58:31 +0200 Subject: [PATCH 0094/1202] Introduce lifetime to `egui_plot::Plot` to replace `'static` fields (#4435) * Closes https://github.com/emilk/egui/issues/4434 Shouldn't break anything, because when all arguments have a 'static lifetime (required without this PR), the Plot is also 'static and can be used like before. --- crates/egui_plot/src/axis.rs | 18 +++---- crates/egui_plot/src/items/mod.rs | 8 +-- crates/egui_plot/src/lib.rs | 82 ++++++++++++++++--------------- 3 files changed, 56 insertions(+), 52 deletions(-) diff --git a/crates/egui_plot/src/axis.rs b/crates/egui_plot/src/axis.rs index 3c2972e6d87d..059349d6673b 100644 --- a/crates/egui_plot/src/axis.rs +++ b/crates/egui_plot/src/axis.rs @@ -8,7 +8,7 @@ use egui::{ use super::{transform::PlotTransform, GridMark}; -pub(super) type AxisFormatterFn = dyn Fn(GridMark, usize, &RangeInclusive) -> String; +pub(super) type AxisFormatterFn<'a> = dyn Fn(GridMark, usize, &RangeInclusive) -> String + 'a; /// X or Y axis. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -98,9 +98,9 @@ impl From for VPlacement { /// /// Used to configure axis label and ticks. #[derive(Clone)] -pub struct AxisHints { +pub struct AxisHints<'a> { pub(super) label: WidgetText, - pub(super) formatter: Arc, + pub(super) formatter: Arc>, pub(super) digits: usize, pub(super) placement: Placement, pub(super) label_spacing: Rangef, @@ -109,7 +109,7 @@ pub struct AxisHints { // TODO(JohannesProgrammiert): this just a guess. It might cease to work if a user changes font size. const LINE_HEIGHT: f32 = 12.0; -impl AxisHints { +impl<'a> AxisHints<'a> { /// Initializes a default axis configuration for the X axis. pub fn new_x() -> Self { Self::new(Axis::X) @@ -145,7 +145,7 @@ impl AxisHints { /// The second parameter of `formatter` is the currently shown range on this axis. pub fn formatter( mut self, - fmt: impl Fn(GridMark, usize, &RangeInclusive) -> String + 'static, + fmt: impl Fn(GridMark, usize, &RangeInclusive) -> String + 'a, ) -> Self { self.formatter = Arc::new(fmt); self @@ -230,9 +230,9 @@ impl AxisHints { } #[derive(Clone)] -pub(super) struct AxisWidget { +pub(super) struct AxisWidget<'a> { pub range: RangeInclusive, - pub hints: AxisHints, + pub hints: AxisHints<'a>, /// The region where we draw the axis labels. pub rect: Rect, @@ -240,9 +240,9 @@ pub(super) struct AxisWidget { pub steps: Arc>, } -impl AxisWidget { +impl<'a> AxisWidget<'a> { /// if `rect` as width or height == 0, is will be automatically calculated from ticks and text. - pub fn new(hints: AxisHints, rect: Rect) -> Self { + pub fn new(hints: AxisHints<'a>, rect: Rect) -> Self { Self { range: (0.0..=0.0), hints, diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 56098b366998..0470d9559650 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -81,7 +81,7 @@ pub trait PlotItem { shapes: &mut Vec, cursors: &mut Vec, plot: &PlotConfig<'_>, - label_formatter: &LabelFormatter, + label_formatter: &LabelFormatter<'_>, ) { let points = match self.geometry() { PlotGeometry::Points(points) => points, @@ -1735,7 +1735,7 @@ impl PlotItem for BarChart { shapes: &mut Vec, cursors: &mut Vec, plot: &PlotConfig<'_>, - _: &LabelFormatter, + _: &LabelFormatter<'_>, ) { let bar = &self.bars[elem.index]; @@ -1909,7 +1909,7 @@ impl PlotItem for BoxPlot { shapes: &mut Vec, cursors: &mut Vec, plot: &PlotConfig<'_>, - _: &LabelFormatter, + _: &LabelFormatter<'_>, ) { let box_plot = &self.boxes[elem.index]; @@ -2033,7 +2033,7 @@ pub(super) fn rulers_at_value( plot: &PlotConfig<'_>, shapes: &mut Vec, cursors: &mut Vec, - label_formatter: &LabelFormatter, + label_formatter: &LabelFormatter<'_>, ) { if plot.show_x { cursors.push(Cursor::Vertical { x: value.x }); diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 6b9d622d6e3d..164710bef265 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -37,22 +37,22 @@ use axis::AxisWidget; use items::{horizontal_line, rulers_color, vertical_line}; use legend::LegendWidget; -type LabelFormatterFn = dyn Fn(&str, &PlotPoint) -> String; -pub type LabelFormatter = Option>; +type LabelFormatterFn<'a> = dyn Fn(&str, &PlotPoint) -> String + 'a; +pub type LabelFormatter<'a> = Option>>; -type GridSpacerFn = dyn Fn(GridInput) -> Vec; -type GridSpacer = Box; +type GridSpacerFn<'a> = dyn Fn(GridInput) -> Vec + 'a; +type GridSpacer<'a> = Box>; -type CoordinatesFormatterFn = dyn Fn(&PlotPoint, &PlotBounds) -> String; +type CoordinatesFormatterFn<'a> = dyn Fn(&PlotPoint, &PlotBounds) -> String + 'a; /// Specifies the coordinates formatting when passed to [`Plot::coordinates_formatter`]. -pub struct CoordinatesFormatter { - function: Box, +pub struct CoordinatesFormatter<'a> { + function: Box>, } -impl CoordinatesFormatter { +impl<'a> CoordinatesFormatter<'a> { /// Create a new formatter based on the pointer coordinate and the plot bounds. - pub fn new(function: impl Fn(&PlotPoint, &PlotBounds) -> String + 'static) -> Self { + pub fn new(function: impl Fn(&PlotPoint, &PlotBounds) -> String + 'a) -> Self { Self { function: Box::new(function), } @@ -72,7 +72,7 @@ impl CoordinatesFormatter { } } -impl Default for CoordinatesFormatter { +impl Default for CoordinatesFormatter<'_> { fn default() -> Self { Self::with_decimals(3) } @@ -143,7 +143,7 @@ pub struct PlotResponse { /// Plot::new("my_plot").view_aspect(2.0).show(ui, |plot_ui| plot_ui.line(line)); /// # }); /// ``` -pub struct Plot { +pub struct Plot<'a> { id_source: Id, id: Option, @@ -170,24 +170,24 @@ pub struct Plot { show_x: bool, show_y: bool, - label_formatter: LabelFormatter, - coordinates_formatter: Option<(Corner, CoordinatesFormatter)>, - x_axes: Vec, // default x axes - y_axes: Vec, // default y axes + label_formatter: LabelFormatter<'a>, + coordinates_formatter: Option<(Corner, CoordinatesFormatter<'a>)>, + x_axes: Vec>, // default x axes + y_axes: Vec>, // default y axes legend_config: Option, show_background: bool, show_axes: Vec2b, show_grid: Vec2b, grid_spacing: Rangef, - grid_spacers: [GridSpacer; 2], + grid_spacers: [GridSpacer<'a>; 2], sharp_grid_lines: bool, clamp_grid: bool, sense: Sense, } -impl Plot { +impl<'a> Plot<'a> { /// Give a unique id for each plot within the same [`Ui`]. pub fn new(id_source: impl std::hash::Hash) -> Self { Self { @@ -405,7 +405,7 @@ impl Plot { /// ``` pub fn label_formatter( mut self, - label_formatter: impl Fn(&str, &PlotPoint) -> String + 'static, + label_formatter: impl Fn(&str, &PlotPoint) -> String + 'a, ) -> Self { self.label_formatter = Some(Box::new(label_formatter)); self @@ -415,7 +415,7 @@ impl Plot { pub fn coordinates_formatter( mut self, position: Corner, - formatter: CoordinatesFormatter, + formatter: CoordinatesFormatter<'a>, ) -> Self { self.coordinates_formatter = Some((position, formatter)); self @@ -452,7 +452,7 @@ impl Plot { /// /// There are helpers for common cases, see [`log_grid_spacer`] and [`uniform_grid_spacer`]. #[inline] - pub fn x_grid_spacer(mut self, spacer: impl Fn(GridInput) -> Vec + 'static) -> Self { + pub fn x_grid_spacer(mut self, spacer: impl Fn(GridInput) -> Vec + 'a) -> Self { self.grid_spacers[0] = Box::new(spacer); self } @@ -461,7 +461,7 @@ impl Plot { /// /// See [`Self::x_grid_spacer`] for explanation. #[inline] - pub fn y_grid_spacer(mut self, spacer: impl Fn(GridInput) -> Vec + 'static) -> Self { + pub fn y_grid_spacer(mut self, spacer: impl Fn(GridInput) -> Vec + 'a) -> Self { self.grid_spacers[1] = Box::new(spacer); self } @@ -662,7 +662,7 @@ impl Plot { /// * currently shown range on this axis. pub fn x_axis_formatter( mut self, - fmt: impl Fn(GridMark, usize, &RangeInclusive) -> String + 'static, + fmt: impl Fn(GridMark, usize, &RangeInclusive) -> String + 'a, ) -> Self { if let Some(main) = self.x_axes.first_mut() { main.formatter = Arc::new(fmt); @@ -678,7 +678,7 @@ impl Plot { /// * currently shown range on this axis. pub fn y_axis_formatter( mut self, - fmt: impl Fn(GridMark, usize, &RangeInclusive) -> String + 'static, + fmt: impl Fn(GridMark, usize, &RangeInclusive) -> String + 'a, ) -> Self { if let Some(main) = self.y_axes.first_mut() { main.formatter = Arc::new(fmt); @@ -703,7 +703,7 @@ impl Plot { /// /// More than one axis may be specified. The first specified axis is considered the main axis. #[inline] - pub fn custom_x_axes(mut self, hints: Vec) -> Self { + pub fn custom_x_axes(mut self, hints: Vec>) -> Self { self.x_axes = hints; self } @@ -712,17 +712,21 @@ impl Plot { /// /// More than one axis may be specified. The first specified axis is considered the main axis. #[inline] - pub fn custom_y_axes(mut self, hints: Vec) -> Self { + pub fn custom_y_axes(mut self, hints: Vec>) -> Self { self.y_axes = hints; self } /// Interact with and add items to the plot and finally draw it. - pub fn show(self, ui: &mut Ui, build_fn: impl FnOnce(&mut PlotUi) -> R) -> PlotResponse { + pub fn show( + self, + ui: &mut Ui, + build_fn: impl FnOnce(&mut PlotUi) -> R + 'a, + ) -> PlotResponse { self.show_dyn(ui, Box::new(build_fn)) } - fn show_dyn<'a, R>( + fn show_dyn( self, ui: &mut Ui, build_fn: Box R + 'a>, @@ -1246,12 +1250,12 @@ impl Plot { } /// Returns the rect left after adding axes. -fn axis_widgets( +fn axis_widgets<'a>( mem: Option<&PlotMemory>, show_axes: Vec2b, complete_rect: Rect, - [x_axes, y_axes]: [&[AxisHints]; 2], -) -> ([Vec; 2], Rect) { + [x_axes, y_axes]: [&'a [AxisHints<'a>]; 2], +) -> ([Vec>; 2], Rect) { // Next we want to create this layout. // Indices are only examples. // @@ -1275,8 +1279,8 @@ fn axis_widgets( // + +--------------------+---+ // - let mut x_axis_widgets = Vec::::new(); - let mut y_axis_widgets = Vec::::new(); + let mut x_axis_widgets = Vec::>::new(); + let mut y_axis_widgets = Vec::>::new(); // Will shrink as we add more axes. let mut rect_left = complete_rect; @@ -1404,7 +1408,7 @@ pub struct GridMark { /// /// The logarithmic base, expressing how many times each grid unit is subdivided. /// 10 is a typical value, others are possible though. -pub fn log_grid_spacer(log_base: i64) -> GridSpacer { +pub fn log_grid_spacer(log_base: i64) -> GridSpacer<'static> { let log_base = log_base as f64; let step_sizes = move |input: GridInput| -> Vec { // handle degenerate cases @@ -1435,7 +1439,7 @@ pub fn log_grid_spacer(log_base: i64) -> GridSpacer { /// /// Why only 3 step sizes? Three is the number of different line thicknesses that egui typically uses in the grid. /// Ideally, those 3 are not hardcoded values, but depend on the visible range (accessible through `GridInput`). -pub fn uniform_grid_spacer(spacer: impl Fn(GridInput) -> [f64; 3] + 'static) -> GridSpacer { +pub fn uniform_grid_spacer<'a>(spacer: impl Fn(GridInput) -> [f64; 3] + 'a) -> GridSpacer<'a> { let get_marks = move |input: GridInput| -> Vec { let bounds = input.bounds; let step_sizes = spacer(input); @@ -1447,17 +1451,17 @@ pub fn uniform_grid_spacer(spacer: impl Fn(GridInput) -> [f64; 3] + 'static) -> // ---------------------------------------------------------------------------- -struct PreparedPlot { +struct PreparedPlot<'a> { items: Vec>, show_x: bool, show_y: bool, - label_formatter: LabelFormatter, - coordinates_formatter: Option<(Corner, CoordinatesFormatter)>, + label_formatter: LabelFormatter<'a>, + coordinates_formatter: Option<(Corner, CoordinatesFormatter<'a>)>, // axis_formatters: [AxisFormatter; 2], transform: PlotTransform, show_grid: Vec2b, grid_spacing: Rangef, - grid_spacers: [GridSpacer; 2], + grid_spacers: [GridSpacer<'a>; 2], draw_cursor_x: bool, draw_cursor_y: bool, draw_cursors: Vec, @@ -1466,7 +1470,7 @@ struct PreparedPlot { clamp_grid: bool, } -impl PreparedPlot { +impl<'a> PreparedPlot<'a> { fn ui(self, ui: &mut Ui, response: &Response) -> (Vec, Option) { let mut axes_shapes = Vec::new(); From 6b607ffa2a1f64655b5cd85c4becea9044c9bd7b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 16 May 2024 13:32:49 +0200 Subject: [PATCH 0095/1202] Remove unmaintained `amethyst_egui` from README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 924d509bd80b..5b1e9211b4bd 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,6 @@ These are the official egui integrations: ### 3rd party integrations -* [`amethyst_egui`](https://github.com/jgraef/amethyst_egui) for [the Amethyst game engine](https://amethyst.rs/) * [`egui-ash`](https://github.com/MatchaChoco010/egui-ash) for [`ash`](https://github.com/ash-rs/ash) (a very lightweight wrapper around Vulkan) * [`bevy_egui`](https://github.com/mvlabat/bevy_egui) for [the Bevy game engine](https://bevyengine.org/) * [`egui_gl_glfw`](https://github.com/mrclean71774/egui_gl_glfw) for [GLFW](https://crates.io/crates/glfw) From 738ea75453567c5f17a543e68aec8c48097cae7b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 16 May 2024 17:28:37 +0200 Subject: [PATCH 0096/1202] Add clippy::use_self lint --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 8c59b92deb5b..13c04b7c922d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -240,6 +240,7 @@ unnested_or_patterns = "warn" unused_peekable = "warn" unused_rounding = "warn" unused_self = "warn" +use_self = "warn" useless_transmute = "warn" verbose_file_reads = "warn" wildcard_dependencies = "warn" From 8321f64f6e68c5b8a433517e535e22087573f8a4 Mon Sep 17 00:00:00 2001 From: hellodword <46193371+hellodword@users.noreply.github.com> Date: Mon, 20 May 2024 15:31:17 +0000 Subject: [PATCH 0097/1202] Update ahash 0.8.6 -> 0.8.11 (#4507) See: https://github.com/tkaitchuck/aHash/pull/183#issuecomment-1789806373 * Closes https://github.com/emilk/egui/issues/4476 --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 373236f18832..b3722fdddec2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,9 +110,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", @@ -1139,7 +1139,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.7.4", + "libloading 0.8.0", ] [[package]] @@ -1950,7 +1950,7 @@ dependencies = [ "bitflags 2.5.0", "com", "libc", - "libloading 0.7.4", + "libloading 0.8.0", "thiserror", "widestring", "winapi", @@ -4285,7 +4285,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.7.4", + "libloading 0.8.0", "log", "metal", "naga", diff --git a/Cargo.toml b/Cargo.toml index 13c04b7c922d..a42033c0efb1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ egui_demo_lib = { version = "0.27.2", path = "crates/egui_demo_lib", default-fea egui_glow = { version = "0.27.2", path = "crates/egui_glow", default-features = false } eframe = { version = "0.27.2", path = "crates/eframe", default-features = false } -ahash = { version = "0.8.6", default-features = false, features = [ +ahash = { version = "0.8.11", default-features = false, features = [ "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead "std", ] } From 262a8bcf98870a382cc4a4569719366e1fcad585 Mon Sep 17 00:00:00 2001 From: hut Date: Tue, 21 May 2024 16:29:45 +0000 Subject: [PATCH 0098/1202] Ignore synthetic key presses (#4514) This PR discards "synthetic" winit keypresses, as discussed in the issue #4513. * Closes https://github.com/emilk/egui/issues/4513 --- crates/egui-winit/src/lib.rs | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 058a27f16613..fec16c73ae8c 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -31,6 +31,7 @@ pub(crate) use profiling_scopes::*; use winit::{ dpi::{PhysicalPosition, PhysicalSize}, + event::ElementState, event_loop::EventLoopWindowTarget, window::{CursorGrabMode, Window, WindowButtons, WindowLevel}, }; @@ -368,16 +369,31 @@ impl State { consumed: self.egui_ctx.wants_keyboard_input(), } } - WindowEvent::KeyboardInput { event, .. } => { - self.on_keyboard_input(event); - - // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes. - let consumed = self.egui_ctx.wants_keyboard_input() - || event.logical_key - == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab); - EventResponse { - repaint: true, - consumed, + WindowEvent::KeyboardInput { + event, + is_synthetic, + .. + } => { + // Winit generates fake "synthetic" KeyboardInput events when the focus + // is changed to the window, or away from it. Synthetic key presses + // represent no real key presses and should be ignored. + // See https://github.com/rust-windowing/winit/issues/3543 + if *is_synthetic && event.state == ElementState::Pressed { + EventResponse { + repaint: true, + consumed: false, + } + } else { + self.on_keyboard_input(event); + + // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes. + let consumed = self.egui_ctx.wants_keyboard_input() + || event.logical_key + == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab); + EventResponse { + repaint: true, + consumed, + } } } WindowEvent::Focused(focused) => { From 7035aa4e53148a73ccbe5a533b7976c3b50524eb Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Tue, 21 May 2024 22:44:27 +0200 Subject: [PATCH 0099/1202] `include_image!` now accepts expressions (#4521) Closes https://github.com/emilk/egui/issues/4510 --- crates/egui/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index b367158a58b7..c9d9b37ce48b 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -507,7 +507,7 @@ pub fn warn_if_debug_build(ui: &mut crate::Ui) { /// ``` #[macro_export] macro_rules! include_image { - ($path: literal) => { + ($path:expr $(,)?) => { $crate::ImageSource::Bytes { uri: ::std::borrow::Cow::Borrowed(concat!("bytes://", $path)), bytes: $crate::load::Bytes::Static(include_bytes!($path)), From c8578c9a6b78ff4b37b86f23264fbf78d826365c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 22 May 2024 11:48:34 +0200 Subject: [PATCH 0100/1202] Fix: still track mouse when dragging outside web canvas (#4522) * Closes https://github.com/emilk/egui/issues/3157 If the mouse leaves the canvas when dragging a slider, the slider will still move. --- To support this, I had to revert https://github.com/emilk/egui/pull/4419 Despite that, I fail to reproduce the two issues it claimed to solve: * https://github.com/emilk/egui/issues/4406 may have been solved in another way by this PR * https://github.com/emilk/egui/issues/4418 I cannot reproduce on Mac. If it is still a problem, I think it should be solved by triggering a `PointerEvent::Released` when focus is lost (i.e. on alt-tab), and not on `PointerGone` --- crates/eframe/src/web/events.rs | 107 +++++++++++++++---------- crates/egui/src/context.rs | 2 +- crates/egui/src/input_state.rs | 6 +- crates/egui/src/interaction.rs | 2 +- crates/egui_demo_lib/src/demo/tests.rs | 12 +-- 5 files changed, 73 insertions(+), 56 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index f7234d76fad9..66b90d3e0c22 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -284,6 +284,8 @@ pub(crate) fn install_color_scheme_change_event(runner_ref: &WebRunner) -> Resul pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValue> { let canvas = runner_ref.try_lock().unwrap().canvas().clone(); + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); { let prevent_default_events = [ @@ -333,8 +335,11 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu }, )?; + // NOTE: we register "mousemove" on `document` instead of just the canvas + // in order to track a dragged mouse outside the canvas. + // See https://github.com/emilk/egui/issues/3157 runner_ref.add_event_listener( - &canvas, + &document, "mousemove", |event: web_sys::MouseEvent, runner| { let modifiers = modifiers_from_mouse_event(&event); @@ -347,31 +352,37 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu }, )?; - runner_ref.add_event_listener(&canvas, "mouseup", |event: web_sys::MouseEvent, runner| { - let modifiers = modifiers_from_mouse_event(&event); - runner.input.raw.modifiers = modifiers; - if let Some(button) = button_from_mouse_event(&event) { - let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); - let modifiers = runner.input.raw.modifiers; - runner.input.raw.events.push(egui::Event::PointerButton { - pos, - button, - pressed: false, - modifiers, - }); + // Use `document` here to notice if the user releases a drag outside of the canvas. + // See https://github.com/emilk/egui/issues/3157 + runner_ref.add_event_listener( + &document, + "mouseup", + |event: web_sys::MouseEvent, runner| { + let modifiers = modifiers_from_mouse_event(&event); + runner.input.raw.modifiers = modifiers; + if let Some(button) = button_from_mouse_event(&event) { + let pos = pos_from_mouse_event(runner.canvas(), &event, runner.egui_ctx()); + let modifiers = runner.input.raw.modifiers; + runner.input.raw.events.push(egui::Event::PointerButton { + pos, + button, + pressed: false, + modifiers, + }); - // In Safari we are only allowed to write to the clipboard during the - // event callback, which is why we run the app logic here and now: - runner.logic(); + // In Safari we are only allowed to write to the clipboard during the + // event callback, which is why we run the app logic here and now: + runner.logic(); - // Make sure we paint the output of the above logic call asap: - runner.needs_repaint.repaint_asap(); + // Make sure we paint the output of the above logic call asap: + runner.needs_repaint.repaint_asap(); - text_agent::update_text_agent(runner); - } - event.stop_propagation(); - event.prevent_default(); - })?; + text_agent::update_text_agent(runner); + } + event.stop_propagation(); + event.prevent_default(); + }, + )?; runner_ref.add_event_listener( &canvas, @@ -412,8 +423,10 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu }, )?; + // Use `document` here to notice if the user drag outside of the canvas. + // See https://github.com/emilk/egui/issues/3157 runner_ref.add_event_listener( - &canvas, + &document, "touchmove", |event: web_sys::TouchEvent, runner| { let mut latest_touch_pos_id = runner.input.latest_touch_pos_id; @@ -434,28 +447,34 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu }, )?; - runner_ref.add_event_listener(&canvas, "touchend", |event: web_sys::TouchEvent, runner| { - if let Some(pos) = runner.input.latest_touch_pos { - let modifiers = runner.input.raw.modifiers; - // First release mouse to click: - runner.input.raw.events.push(egui::Event::PointerButton { - pos, - button: egui::PointerButton::Primary, - pressed: false, - modifiers, - }); - // Then remove hover effect: - runner.input.raw.events.push(egui::Event::PointerGone); + // Use `document` here to notice if the user releases a drag outside of the canvas. + // See https://github.com/emilk/egui/issues/3157 + runner_ref.add_event_listener( + &document, + "touchend", + |event: web_sys::TouchEvent, runner| { + if let Some(pos) = runner.input.latest_touch_pos { + let modifiers = runner.input.raw.modifiers; + // First release mouse to click: + runner.input.raw.events.push(egui::Event::PointerButton { + pos, + button: egui::PointerButton::Primary, + pressed: false, + modifiers, + }); + // Then remove hover effect: + runner.input.raw.events.push(egui::Event::PointerGone); - push_touches(runner, egui::TouchPhase::End, &event); - runner.needs_repaint.repaint_asap(); - event.stop_propagation(); - event.prevent_default(); - } + push_touches(runner, egui::TouchPhase::End, &event); + runner.needs_repaint.repaint_asap(); + event.stop_propagation(); + event.prevent_default(); + } - // Finally, focus or blur text agent to toggle mobile keyboard: - text_agent::update_text_agent(runner); - })?; + // Finally, focus or blur text agent to toggle mobile keyboard: + text_agent::update_text_agent(runner); + }, + )?; runner_ref.add_event_listener( &canvas, diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 2d58be7e1265..04bdd16432aa 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1875,8 +1875,8 @@ impl Context { drag_started: _, dragged, drag_stopped: _, - hovered, contains_pointer, + hovered, } = interact_widgets; if true { diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 22851a470b6e..e82fd62b75e1 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -800,10 +800,8 @@ impl PointerState { } Event::PointerGone => { self.latest_pos = None; - self.pointer_events.push(PointerEvent::Released { - click: None, - button: PointerButton::Primary, - }); + // When dragging a slider and the mouse leaves the viewport, we still want the drag to work, + // so we don't treat this as a `PointerEvent::Released`. // NOTE: we do NOT clear `self.interact_pos` here. It will be cleared next frame. } Event::MouseMoved(delta) => *self.motion.get_or_insert(Vec2::ZERO) += *delta, diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index e25e1c4aa134..8a7b2d0948c5 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -283,7 +283,7 @@ pub(crate) fn interact( drag_started, dragged, drag_stopped, - hovered, contains_pointer, + hovered, } } diff --git a/crates/egui_demo_lib/src/demo/tests.rs b/crates/egui_demo_lib/src/demo/tests.rs index 6a8348ac56ad..44e355d0e8e3 100644 --- a/crates/egui_demo_lib/src/demo/tests.rs +++ b/crates/egui_demo_lib/src/demo/tests.rs @@ -466,23 +466,23 @@ fn response_summary(response: &egui::Response, show_hovers: bool) -> String { // These are in inverse logical/chonological order, because we show them in the ui that way: if response.triple_clicked_by(button) { - writeln!(new_info, "Triple_clicked_by{button_suffix}").ok(); + writeln!(new_info, "Triple-clicked{button_suffix}").ok(); } if response.double_clicked_by(button) { - writeln!(new_info, "Double_clicked_by{button_suffix}").ok(); + writeln!(new_info, "Double-clicked{button_suffix}").ok(); } if response.clicked_by(button) { - writeln!(new_info, "Clicked_by{button_suffix}").ok(); + writeln!(new_info, "Clicked{button_suffix}").ok(); } if response.drag_stopped_by(button) { - writeln!(new_info, "Drag_stopped_by{button_suffix}").ok(); + writeln!(new_info, "Drag stopped{button_suffix}").ok(); } if response.dragged_by(button) { - writeln!(new_info, "Dragged_by{button_suffix}").ok(); + writeln!(new_info, "Dragged{button_suffix}").ok(); } if response.drag_started_by(button) { - writeln!(new_info, "Drag_started_by{button_suffix}").ok(); + writeln!(new_info, "Drag started{button_suffix}").ok(); } } From 48045e57da1b4166cdf05a0509c812878a5b122b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 22 May 2024 21:35:15 +0200 Subject: [PATCH 0101/1202] Remove `Event::Scroll` and handle it in egui (#4524) For integrations: just emit `egui::Event::MouseWheel` (like before). egui will interpret that as zoom or pan. On the way towards https://github.com/emilk/egui/issues/4401 --- crates/eframe/src/web/events.rs | 30 ---------------------------- crates/egui-winit/src/lib.rs | 23 ---------------------- crates/egui/src/data/input.rs | 35 ++++++++++++--------------------- crates/egui/src/input_state.rs | 33 +++++++++++++++++++++++++++++-- 4 files changed, 44 insertions(+), 77 deletions(-) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 66b90d3e0c22..56d3fdf071a8 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -503,36 +503,6 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu modifiers, }); - let scroll_multiplier = match unit { - egui::MouseWheelUnit::Page => { - canvas_size_in_points(runner.canvas(), runner.egui_ctx()).y - } - egui::MouseWheelUnit::Line => { - #[allow(clippy::let_and_return)] - let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in winit. - points_per_scroll_line - } - egui::MouseWheelUnit::Point => 1.0, - }; - - let mut delta = scroll_multiplier * delta; - - // Report a zoom event in case CTRL (on Windows or Linux) or CMD (on Mac) is pressed. - // This if-statement is equivalent to how `Modifiers.command` is determined in - // `modifiers_from_kb_event()`, but we cannot directly use that fn for a [`WheelEvent`]. - if event.ctrl_key() || event.meta_key() { - let factor = (delta.y / 200.0).exp(); - runner.input.raw.events.push(egui::Event::Zoom(factor)); - } else { - if event.shift_key() { - // Treat as horizontal scrolling. - // Note: one Mac we already get horizontal scroll events when shift is down. - delta = egui::vec2(delta.x + delta.y, 0.0); - } - - runner.input.raw.events.push(egui::Event::Scroll(delta)); - } - runner.needs_repaint.repaint_asap(); event.stop_propagation(); event.prevent_default(); diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index fec16c73ae8c..7b40f92b7735 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -694,29 +694,6 @@ impl State { modifiers, }); } - let delta = match delta { - winit::event::MouseScrollDelta::LineDelta(x, y) => { - let points_per_scroll_line = 50.0; // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461 - egui::vec2(x, y) * points_per_scroll_line - } - winit::event::MouseScrollDelta::PixelDelta(delta) => { - egui::vec2(delta.x as f32, delta.y as f32) / pixels_per_point - } - }; - - if self.egui_input.modifiers.ctrl || self.egui_input.modifiers.command { - // Treat as zoom instead: - let factor = (delta.y / 200.0).exp(); - self.egui_input.events.push(egui::Event::Zoom(factor)); - } else if self.egui_input.modifiers.shift { - // Treat as horizontal scrolling. - // Note: one Mac we already get horizontal scroll events when shift is down. - self.egui_input - .events - .push(egui::Event::Scroll(egui::vec2(delta.x + delta.y, 0.0))); - } else { - self.egui_input.events.push(egui::Event::Scroll(delta)); - } } fn on_keyboard_input(&mut self, event: &winit::event::KeyEvent) { diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 2260db1f433e..3ee88a49cec5 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -426,21 +426,6 @@ pub enum Event { /// On touch-up first send `PointerButton{pressed: false, …}` followed by `PointerLeft`. PointerGone, - /// How many points (logical pixels) the user scrolled. - /// - /// The direction of the vector indicates how to move the _content_ that is being viewed. - /// So if you get positive values, the content being viewed should move to the right and down, - /// revealing new things to the left and up. - /// - /// A positive X-value indicates the content is being moved right, - /// as when swiping right on a touch-screen or track-pad with natural scrolling. - /// - /// A positive Y-value indicates the content is being moved down, - /// as when swiping down on a touch-screen or track-pad with natural scrolling. - /// - /// Shift-scroll should result in horizontal scrolling (it is up to the integrations to do this). - Scroll(Vec2), - /// Zoom scale factor this frame (e.g. from ctrl-scroll or pinch gesture). /// * `zoom = 1`: no change. /// * `zoom < 1`: pinch together @@ -473,16 +458,22 @@ pub enum Event { force: Option, }, - /// A raw mouse wheel event as sent by the backend (minus the z coordinate), - /// for implementing alternative custom controls. - /// Note that the same event can also trigger [`Self::Zoom`] and [`Self::Scroll`], - /// so you probably want to handle only one of them. + /// A raw mouse wheel event as sent by the backend. + /// + /// Used for scrolling. MouseWheel { - /// The unit of scrolling: points, lines, or pages. + /// The unit of `delta`: points, lines, or pages. unit: MouseWheelUnit, - /// The amount scrolled horizontally and vertically. The amount and direction corresponding - /// to one step of the wheel depends on the platform. + /// The direction of the vector indicates how to move the _content_ that is being viewed. + /// So if you get positive values, the content being viewed should move to the right and down, + /// revealing new things to the left and up. + /// + /// A positive X-value indicates the content is being moved right, + /// as when swiping right on a touch-screen or track-pad with natural scrolling. + /// + /// A positive Y-value indicates the content is being moved down, + /// as when swiping down on a touch-screen or track-pad with natural scrolling. delta: Vec2, /// The state of the modifier keys at the time of the event. diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index e82fd62b75e1..53a5f4167e9f 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -216,8 +216,37 @@ impl InputState { keys_down.remove(key); } } - Event::Scroll(delta) => { - raw_scroll_delta += *delta; + Event::MouseWheel { + unit, + delta, + modifiers, + } => { + let delta = match unit { + MouseWheelUnit::Point => *delta, + MouseWheelUnit::Line => { + // TODO(emilk): figure out why these constants need to be different on web and on native (winit). + let is_web = cfg!(target_arch = "wasm32"); + let points_per_scroll_line = if is_web { + 8.0 + } else { + 50.0 // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461 + }; + + points_per_scroll_line * *delta + } + MouseWheelUnit::Page => screen_rect.height() * *delta, + }; + if modifiers.ctrl || modifiers.command { + // Treat as zoom instead: + let factor = (delta.y / 200.0).exp(); + zoom_factor_delta *= factor; + } else if modifiers.shift { + // Treat as horizontal scrolling. + // Note: one Mac we already get horizontal scroll events when shift is down. + raw_scroll_delta.x += delta.x + delta.y; + } else { + raw_scroll_delta += delta; + } } Event::Zoom(factor) => { zoom_factor_delta *= *factor; From 8db8f6df825ad0061895e1af18ba7bc99f39c011 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 23 May 2024 08:50:48 +0200 Subject: [PATCH 0102/1202] Remove scroll latency for smooth trackpads (#4526) * Closes https://github.com/emilk/egui/issues/4401 It was small, but annoying. Now we get that butter smooth scrolling on mac trackpads again, with no latency --- crates/egui/src/input_state.rs | 49 ++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 53a5f4167e9f..caacd4948444 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -199,8 +199,11 @@ impl InputState { let pointer = self.pointer.begin_frame(time, &new); let mut keys_down = self.keys_down; + let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor let mut raw_scroll_delta = Vec2::ZERO; - let mut zoom_factor_delta = 1.0; + let mut unprocessed_scroll_delta = self.unprocessed_scroll_delta; + let mut smooth_scroll_delta = Vec2::ZERO; + for event in &mut new.events { match event { Event::Key { @@ -221,7 +224,7 @@ impl InputState { delta, modifiers, } => { - let delta = match unit { + let mut delta = match unit { MouseWheelUnit::Point => *delta, MouseWheelUnit::Line => { // TODO(emilk): figure out why these constants need to be different on web and on native (winit). @@ -236,16 +239,35 @@ impl InputState { } MouseWheelUnit::Page => screen_rect.height() * *delta, }; + if modifiers.ctrl || modifiers.command { // Treat as zoom instead: let factor = (delta.y / 200.0).exp(); zoom_factor_delta *= factor; - } else if modifiers.shift { - // Treat as horizontal scrolling. - // Note: one Mac we already get horizontal scroll events when shift is down. - raw_scroll_delta.x += delta.x + delta.y; } else { + if modifiers.shift { + // Treat as horizontal scrolling. + // Note: one Mac we already get horizontal scroll events when shift is down. + delta = vec2(delta.x + delta.y, 0.0); + } + raw_scroll_delta += delta; + + // Mouse wheels often go very large steps. + // A single notch on a logitech mouse wheel connected to a Macbook returns 14.0 raw_scroll_delta. + // So we smooth it out over several frames for a nicer user experience when scrolling in egui. + // BUT: if the user is using a nice smooth mac trackpad, we don't add smoothing, + // because it adds latency. + let is_smooth = match unit { + MouseWheelUnit::Point => delta.length() < 5.0, // a bit arbitrary here + MouseWheelUnit::Line | MouseWheelUnit::Page => false, + }; + + if is_smooth { + smooth_scroll_delta += delta; + } else { + unprocessed_scroll_delta += delta; + } } } Event::Zoom(factor) => { @@ -255,25 +277,18 @@ impl InputState { } } - let mut unprocessed_scroll_delta = self.unprocessed_scroll_delta; - - let mut smooth_scroll_delta = Vec2::ZERO; - { - // Mouse wheels often go very large steps. - // A single notch on a logitech mouse wheel connected to a Macbook returns 14.0 raw_scroll_delta. - // So we smooth it out over several frames for a nicer user experience when scrolling in egui. - unprocessed_scroll_delta += raw_scroll_delta; let dt = stable_dt.at_most(0.1); let t = crate::emath::exponential_smooth_factor(0.90, 0.1, dt); // reach _% in _ seconds. TODO(emilk): parameterize for d in 0..2 { if unprocessed_scroll_delta[d].abs() < 1.0 { - smooth_scroll_delta[d] = unprocessed_scroll_delta[d]; + smooth_scroll_delta[d] += unprocessed_scroll_delta[d]; unprocessed_scroll_delta[d] = 0.0; } else { - smooth_scroll_delta[d] = t * unprocessed_scroll_delta[d]; - unprocessed_scroll_delta[d] -= smooth_scroll_delta[d]; + let applied = t * unprocessed_scroll_delta[d]; + smooth_scroll_delta[d] += applied; + unprocessed_scroll_delta[d] -= applied; } } } From 8433b43231fb8636254f01dac12d87e9be7ebb37 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 23 May 2024 09:56:36 +0200 Subject: [PATCH 0103/1202] Smooth out zooming with discreet scroll wheel (#4530) * closes https://github.com/emilk/egui/issues/4525 You can zoom in using ctrl/cmd + scrolling. When using a discreet scroll wheel, the zoom factor now gets smoothed. --- crates/egui/src/input_state.rs | 90 ++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index caacd4948444..49699795943b 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -42,6 +42,11 @@ pub struct InputState { /// Used for smoothing the scroll delta. unprocessed_scroll_delta: Vec2, + /// Used for smoothing the scroll delta when zooming. + unprocessed_scroll_delta_for_zoom: f32, + + /// You probably want to use [`Self::smooth_scroll_delta`] instead. + /// /// The raw input of how many points the user scrolled. /// /// The delta dictates how the _content_ should move. @@ -152,6 +157,7 @@ impl Default for InputState { pointer: Default::default(), touch_states: Default::default(), unprocessed_scroll_delta: Vec2::ZERO, + unprocessed_scroll_delta_for_zoom: 0.0, raw_scroll_delta: Vec2::ZERO, smooth_scroll_delta: Vec2::ZERO, zoom_factor_delta: 1.0, @@ -201,8 +207,11 @@ impl InputState { let mut keys_down = self.keys_down; let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor let mut raw_scroll_delta = Vec2::ZERO; + let mut unprocessed_scroll_delta = self.unprocessed_scroll_delta; + let mut unprocessed_scroll_delta_for_zoom = self.unprocessed_scroll_delta_for_zoom; let mut smooth_scroll_delta = Vec2::ZERO; + let mut smooth_scroll_delta_for_zoom = 0.0; for event in &mut new.events { match event { @@ -240,29 +249,34 @@ impl InputState { MouseWheelUnit::Page => screen_rect.height() * *delta, }; - if modifiers.ctrl || modifiers.command { - // Treat as zoom instead: - let factor = (delta.y / 200.0).exp(); - zoom_factor_delta *= factor; - } else { - if modifiers.shift { - // Treat as horizontal scrolling. - // Note: one Mac we already get horizontal scroll events when shift is down. - delta = vec2(delta.x + delta.y, 0.0); - } + if modifiers.shift { + // Treat as horizontal scrolling. + // Note: one Mac we already get horizontal scroll events when shift is down. + delta = vec2(delta.x + delta.y, 0.0); + } - raw_scroll_delta += delta; + raw_scroll_delta += delta; - // Mouse wheels often go very large steps. - // A single notch on a logitech mouse wheel connected to a Macbook returns 14.0 raw_scroll_delta. - // So we smooth it out over several frames for a nicer user experience when scrolling in egui. - // BUT: if the user is using a nice smooth mac trackpad, we don't add smoothing, - // because it adds latency. - let is_smooth = match unit { - MouseWheelUnit::Point => delta.length() < 5.0, // a bit arbitrary here - MouseWheelUnit::Line | MouseWheelUnit::Page => false, - }; + // Mouse wheels often go very large steps. + // A single notch on a logitech mouse wheel connected to a Macbook returns 14.0 raw_scroll_delta. + // So we smooth it out over several frames for a nicer user experience when scrolling in egui. + // BUT: if the user is using a nice smooth mac trackpad, we don't add smoothing, + // because it adds latency. + let is_smooth = match unit { + MouseWheelUnit::Point => delta.length() < 8.0, // a bit arbitrary here + MouseWheelUnit::Line | MouseWheelUnit::Page => false, + }; + + let is_zoom = modifiers.ctrl || modifiers.mac_cmd || modifiers.command; + #[allow(clippy::collapsible_else_if)] + if is_zoom { + if is_smooth { + smooth_scroll_delta_for_zoom += delta.y; + } else { + unprocessed_scroll_delta_for_zoom += delta.y; + } + } else { if is_smooth { smooth_scroll_delta += delta; } else { @@ -281,15 +295,31 @@ impl InputState { let dt = stable_dt.at_most(0.1); let t = crate::emath::exponential_smooth_factor(0.90, 0.1, dt); // reach _% in _ seconds. TODO(emilk): parameterize - for d in 0..2 { - if unprocessed_scroll_delta[d].abs() < 1.0 { - smooth_scroll_delta[d] += unprocessed_scroll_delta[d]; - unprocessed_scroll_delta[d] = 0.0; + if unprocessed_scroll_delta != Vec2::ZERO { + for d in 0..2 { + if unprocessed_scroll_delta[d].abs() < 1.0 { + smooth_scroll_delta[d] += unprocessed_scroll_delta[d]; + unprocessed_scroll_delta[d] = 0.0; + } else { + let applied = t * unprocessed_scroll_delta[d]; + smooth_scroll_delta[d] += applied; + unprocessed_scroll_delta[d] -= applied; + } + } + } + + { + // Smooth scroll-to-zoom: + if unprocessed_scroll_delta_for_zoom.abs() < 1.0 { + smooth_scroll_delta_for_zoom += unprocessed_scroll_delta_for_zoom; + unprocessed_scroll_delta_for_zoom = 0.0; } else { - let applied = t * unprocessed_scroll_delta[d]; - smooth_scroll_delta[d] += applied; - unprocessed_scroll_delta[d] -= applied; + let applied = t * unprocessed_scroll_delta_for_zoom; + smooth_scroll_delta_for_zoom += applied; + unprocessed_scroll_delta_for_zoom -= applied; } + + zoom_factor_delta *= (smooth_scroll_delta_for_zoom / 200.0).exp(); } } @@ -297,6 +327,7 @@ impl InputState { pointer, touch_states: self.touch_states, unprocessed_scroll_delta, + unprocessed_scroll_delta_for_zoom, raw_scroll_delta, smooth_scroll_delta, zoom_factor_delta, @@ -369,6 +400,7 @@ impl InputState { pub fn wants_repaint(&self) -> bool { self.pointer.wants_repaint() || self.unprocessed_scroll_delta.abs().max_elem() > 0.2 + || self.unprocessed_scroll_delta_for_zoom.abs() > 0.2 || !self.events.is_empty() // We need to wake up and check for press-and-hold for the context menu. @@ -1157,6 +1189,7 @@ impl InputState { touch_states, unprocessed_scroll_delta, + unprocessed_scroll_delta_for_zoom, raw_scroll_delta, smooth_scroll_delta, @@ -1198,6 +1231,9 @@ impl InputState { ui.label(format!( "unprocessed_scroll_delta: {unprocessed_scroll_delta:?} points" )); + ui.label(format!( + "unprocessed_scroll_delta_for_zoom: {unprocessed_scroll_delta_for_zoom:?} points" + )); } ui.label(format!("raw_scroll_delta: {raw_scroll_delta:?} points")); ui.label(format!( From a98c42e3177fa31807c37921a0d38734e2f7597f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 23 May 2024 10:07:08 +0200 Subject: [PATCH 0104/1202] Add `Options::line_scroll_speed` and `scroll_zoom_speed` (#4532) This lets integrations and user change how sensitive egui is to scroll events --- crates/egui/src/context.rs | 1 + crates/egui/src/input_state.rs | 16 ++++--------- crates/egui/src/memory.rs | 44 ++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 04bdd16432aa..d0dedaa1ac10 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -442,6 +442,7 @@ impl ContextImpl { new_raw_input, viewport.repaint.requested_immediate_repaint_prev_frame(), pixels_per_point, + &self.memory.options, ); let screen_rect = viewport.input.screen_rect; diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 49699795943b..f52bfe24928a 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -183,6 +183,7 @@ impl InputState { mut new: RawInput, requested_immediate_repaint_prev_frame: bool, pixels_per_point: f32, + options: &crate::Options, ) -> Self { crate::profile_function!(); @@ -235,17 +236,7 @@ impl InputState { } => { let mut delta = match unit { MouseWheelUnit::Point => *delta, - MouseWheelUnit::Line => { - // TODO(emilk): figure out why these constants need to be different on web and on native (winit). - let is_web = cfg!(target_arch = "wasm32"); - let points_per_scroll_line = if is_web { - 8.0 - } else { - 50.0 // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461 - }; - - points_per_scroll_line * *delta - } + MouseWheelUnit::Line => options.line_scroll_speed * *delta, MouseWheelUnit::Page => screen_rect.height() * *delta, }; @@ -319,7 +310,8 @@ impl InputState { unprocessed_scroll_delta_for_zoom -= applied; } - zoom_factor_delta *= (smooth_scroll_delta_for_zoom / 200.0).exp(); + zoom_factor_delta *= + (options.scroll_zoom_speed * smooth_scroll_delta_for_zoom).exp(); } } diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 4bee395c530b..8a642adf28ec 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -227,10 +227,26 @@ pub struct Options { /// /// By default this is `true` in debug builds. pub warn_on_id_clash: bool, + + // ------------------------------ + // Input: + /// Multiplier for the scroll speed when reported in [`crate::MouseWheelUnit::Line`]s. + pub line_scroll_speed: f32, + + /// Controls the speed at which we zoom in when doing ctrl/cmd + scroll. + pub scroll_zoom_speed: f32, } impl Default for Options { fn default() -> Self { + // TODO(emilk): figure out why these constants need to be different on web and on native (winit). + let is_web = cfg!(target_arch = "wasm32"); + let line_scroll_speed = if is_web { + 8.0 + } else { + 40.0 // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461 + }; + Self { style: Default::default(), zoom_factor: 1.0, @@ -240,6 +256,10 @@ impl Default for Options { screen_reader: false, preload_font_glyphs: true, warn_on_id_clash: cfg!(debug_assertions), + + // Input: + line_scroll_speed, + scroll_zoom_speed: 1.0 / 200.0, } } } @@ -256,6 +276,9 @@ impl Options { screen_reader: _, // needs to come from the integration preload_font_glyphs: _, warn_on_id_clash, + + line_scroll_speed, + scroll_zoom_speed, } = self; use crate::Widget as _; @@ -292,6 +315,27 @@ impl Options { }); }); + CollapsingHeader::new("🖱 Input") + .default_open(false) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.label("Line scroll speed"); + ui.add( + crate::DragValue::new(line_scroll_speed).clamp_range(0.0..=f32::INFINITY), + ) + .on_hover_text("How many lines to scroll with each tick of the mouse wheel"); + }); + ui.horizontal(|ui| { + ui.label("Scroll zoom speed"); + ui.add( + crate::DragValue::new(scroll_zoom_speed) + .clamp_range(0.0..=f32::INFINITY) + .speed(0.001), + ) + .on_hover_text("How fast to zoom with ctrl/cmd + scroll"); + }); + }); + ui.vertical_centered(|ui| crate::reset_button(ui, self, "Reset all")); } } From 34672bc1bb00eab090a04399f56300aba24c8a3d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 24 May 2024 15:55:36 +0200 Subject: [PATCH 0105/1202] Add improved pixel alignment test with alternating white/black lines (#4537) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a good test, because you will have obvious darkening/lightening when this fails, and/or Moiré patterns. image --- crates/egui_demo_lib/src/rendering_test.rs | 73 +++++++++++++++++++--- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/crates/egui_demo_lib/src/rendering_test.rs b/crates/egui_demo_lib/src/rendering_test.rs index 2aa6194eea1f..63078651ffd5 100644 --- a/crates/egui_demo_lib/src/rendering_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -398,12 +398,27 @@ impl TextureManager { /// Requires eyes and a magnifying glass to verify. pub fn pixel_test(ui: &mut Ui) { ui.heading("Pixel alignment test"); + ui.label("If anything is blurry, then everything will be blurry, including text."); + ui.label("You might need a magnifying glass to check this test."); + + if cfg!(target_arch = "wasm32") { + ui.label("Make sure these test pass even when you zoom in/out and resize the browser."); + } + + ui.add_space(4.0); + + pixel_test_lines(ui); + + ui.add_space(4.0); + + pixel_test_squares(ui); +} + +fn pixel_test_squares(ui: &mut Ui) { ui.label("The first square should be exactly one physical pixel big."); ui.label("They should be exactly one physical pixel apart."); ui.label("Each subsequent square should be one physical pixel larger than the previous."); ui.label("They should be perfectly aligned to the physical pixel grid."); - ui.label("If these squares are blurry, everything will be blurry, including text."); - ui.label("You might need a magnifying glass to check this test."); let color = if ui.style().visuals.dark_mode { egui::Color32::WHITE @@ -412,6 +427,7 @@ pub fn pixel_test(ui: &mut Ui) { }; let pixels_per_point = ui.ctx().pixels_per_point(); + let num_squares = (pixels_per_point * 10.0).round().max(10.0) as u32; let size_pixels = vec2( ((num_squares + 1) * (num_squares + 2) / 2) as f32, @@ -427,17 +443,58 @@ pub fn pixel_test(ui: &mut Ui) { .ceil(); for size in 1..=num_squares { let rect_points = Rect::from_min_size( - Pos2::new( - cursor_pixel.x / pixels_per_point, - cursor_pixel.y / pixels_per_point, - ), - Vec2::splat(size as f32) / pixels_per_point, + Pos2::new(cursor_pixel.x, cursor_pixel.y), + Vec2::splat(size as f32), ); - painter.rect_filled(rect_points, 0.0, color); + painter.rect_filled(rect_points / pixels_per_point, 0.0, color); cursor_pixel.x += (1 + size) as f32; } } +fn pixel_test_lines(ui: &mut Ui) { + let pixels_per_point = ui.ctx().pixels_per_point(); + let n = (96.0 * pixels_per_point) as usize; + + ui.label("The lines should be exactly one physical pixel wide, one physical pixel apart."); + ui.label("They should be perfectly white and black."); + + let hspace_px = pixels_per_point * 4.0; + + let size_px = Vec2::new(2.0 * n as f32 + hspace_px, n as f32); + let size_points = size_px / pixels_per_point + Vec2::splat(2.0); + let (response, painter) = ui.allocate_painter(size_points, Sense::hover()); + + let mut cursor_px = Pos2::new( + response.rect.min.x * pixels_per_point, + response.rect.min.y * pixels_per_point, + ) + .ceil(); + + // Vertical stripes: + for x in 0..n / 2 { + let rect_px = Rect::from_min_size( + Pos2::new(cursor_px.x + 2.0 * x as f32, cursor_px.y), + Vec2::new(1.0, n as f32), + ); + painter.rect_filled(rect_px / pixels_per_point, 0.0, egui::Color32::WHITE); + let rect_px = rect_px.translate(vec2(1.0, 0.0)); + painter.rect_filled(rect_px / pixels_per_point, 0.0, egui::Color32::BLACK); + } + + cursor_px.x += n as f32 + hspace_px; + + // Horizontal stripes: + for y in 0..n / 2 { + let rect_px = Rect::from_min_size( + Pos2::new(cursor_px.x, cursor_px.y + 2.0 * y as f32), + Vec2::new(n as f32, 1.0), + ); + painter.rect_filled(rect_px / pixels_per_point, 0.0, egui::Color32::WHITE); + let rect_px = rect_px.translate(vec2(0.0, 1.0)); + painter.rect_filled(rect_px / pixels_per_point, 0.0, egui::Color32::BLACK); + } +} + fn blending_and_feathering_test(ui: &mut Ui) { ui.label("The left side shows how lines of different widths look."); ui.label("The right side tests text rendering at different opacities and sizes."); From f0cbb189432d23dc8bab40d72759d050b2e423cf Mon Sep 17 00:00:00 2001 From: Ryan Bluth Date: Mon, 27 May 2024 05:53:06 -0400 Subject: [PATCH 0106/1202] Don't panic when replacement glyph is not found (#4542) I wanted to implement a font picker that loads all system fonts but ran into panics due to missing glyphs. Falling back to an empty glyph when none of the fallback glyphs are available avoids the panic. --- crates/epaint/src/text/font.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 989698d7dac5..7520c971a893 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -370,9 +370,11 @@ impl Font { .glyph_info_no_cache_or_fallback(PRIMARY_REPLACEMENT_CHAR) .or_else(|| slf.glyph_info_no_cache_or_fallback(FALLBACK_REPLACEMENT_CHAR)) .unwrap_or_else(|| { - panic!( - "Failed to find replacement characters {PRIMARY_REPLACEMENT_CHAR:?} or {FALLBACK_REPLACEMENT_CHAR:?}" - ) + #[cfg(feature = "log")] + log::warn!( + "Failed to find replacement characters {PRIMARY_REPLACEMENT_CHAR:?} or {FALLBACK_REPLACEMENT_CHAR:?}. Will use empty glyph." + ); + (0, GlyphInfo::default()) }); slf.replacement_glyph = replacement_glyph; From cd45d18615883b84cfcee835a93b38f1828ccace Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson Date: Mon, 27 May 2024 16:24:50 +0200 Subject: [PATCH 0107/1202] Do no use the ahash reimport (#4504) Related to #3482 Not sure what the "best practice" is, to me it seems like one should import from "the original location" if possible, but now it should at least be possible to not re-export ahash without any breakage in the egui code base (but possibly in projects using egui, so one should probably deprecate it if one would like to go that path). It also seems like epaint re-exports ahash. --- Cargo.lock | 6 ++++++ crates/eframe/Cargo.toml | 1 + crates/eframe/src/native/glow_integration.rs | 6 +++--- crates/eframe/src/native/run.rs | 2 +- crates/eframe/src/native/wgpu_integration.rs | 2 +- crates/egui-wgpu/Cargo.toml | 1 + crates/egui-wgpu/src/renderer.rs | 3 ++- crates/egui-winit/Cargo.toml | 2 ++ crates/egui-winit/src/lib.rs | 5 ++--- crates/egui/src/id.rs | 4 ++-- crates/egui_extras/Cargo.toml | 1 + crates/egui_extras/src/loaders/ehttp_loader.rs | 2 +- crates/egui_extras/src/loaders/file_loader.rs | 2 +- crates/egui_extras/src/loaders/image_loader.rs | 2 +- crates/egui_extras/src/loaders/svg_loader.rs | 3 ++- crates/egui_glow/Cargo.toml | 1 + crates/egui_glow/src/winit.rs | 6 +++--- crates/egui_plot/Cargo.toml | 1 + crates/egui_plot/src/lib.rs | 2 +- crates/egui_plot/src/memory.rs | 2 +- 20 files changed, 34 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3722fdddec2..0eb199156cf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1172,6 +1172,7 @@ dependencies = [ name = "eframe" version = "0.27.2" dependencies = [ + "ahash", "bytemuck", "directories-next", "document-features", @@ -1227,6 +1228,7 @@ dependencies = [ name = "egui-wgpu" version = "0.27.2" dependencies = [ + "ahash", "bytemuck", "document-features", "egui", @@ -1245,6 +1247,7 @@ name = "egui-winit" version = "0.27.2" dependencies = [ "accesskit_winit", + "ahash", "arboard", "document-features", "egui", @@ -1302,6 +1305,7 @@ dependencies = [ name = "egui_extras" version = "0.27.2" dependencies = [ + "ahash", "chrono", "document-features", "egui", @@ -1320,6 +1324,7 @@ dependencies = [ name = "egui_glow" version = "0.27.2" dependencies = [ + "ahash", "bytemuck", "document-features", "egui", @@ -1340,6 +1345,7 @@ dependencies = [ name = "egui_plot" version = "0.27.2" dependencies = [ + "ahash", "document-features", "egui", "serde", diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index f992c5c66a27..4a5a9918a127 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -129,6 +129,7 @@ egui = { workspace = true, default-features = false, features = [ "log", ] } +ahash.workspace = true document-features.workspace = true log.workspace = true parking_lot.workspace = true diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 0fbb48a09c74..ec7669b077c2 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -26,10 +26,10 @@ use winit::{ window::{Window, WindowId}, }; +use ahash::{HashMap, HashSet}; use egui::{ - ahash::HashSet, epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, - ViewportBuilder, ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportInfo, - ViewportOutput, + DeferredViewportUiCallback, ImmediateViewport, ViewportBuilder, ViewportClass, ViewportId, + ViewportIdMap, ViewportIdPair, ViewportInfo, ViewportOutput, }; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 3ee249edf76e..7087ed6ae713 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, time::Instant}; use winit::event_loop::{EventLoop, EventLoopBuilder}; -use egui::epaint::ahash::HashMap; +use ahash::HashMap; use crate::{ epi, diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index f365d74ad93d..9380bbf72d6c 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -15,8 +15,8 @@ use winit::{ window::{Window, WindowId}, }; +use ahash::{HashMap, HashSet, HashSetExt}; use egui::{ - ahash::{HashMap, HashSet, HashSetExt}, DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportBuilder, ViewportClass, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput, }; diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 1de7d1446ef1..3eaac2e92f5d 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -50,6 +50,7 @@ x11 = ["winit?/x11"] egui = { workspace = true, default-features = false } epaint = { workspace = true, default-features = false, features = ["bytemuck"] } +ahash.workspace = true bytemuck.workspace = true document-features.workspace = true log.workspace = true diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 49397c9af892..a4bc8809c35c 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -2,7 +2,8 @@ use std::{borrow::Cow, num::NonZeroU64, ops::Range}; -use epaint::{ahash::HashMap, emath::NumExt, PaintCallbackInfo, Primitive, Vertex}; +use ahash::HashMap; +use epaint::{emath::NumExt, PaintCallbackInfo, Primitive, Vertex}; use wgpu::util::DeviceExt as _; diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index 6354b7a439f1..0cf9bf9403ae 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -59,6 +59,8 @@ x11 = ["winit/x11", "bytemuck"] [dependencies] egui = { workspace = true, default-features = false, features = ["log"] } + +ahash.workspace = true log.workspace = true raw-window-handle.workspace = true web-time.workspace = true diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 7b40f92b7735..9a57359887b3 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -14,9 +14,7 @@ pub use accesskit_winit; pub use egui; #[cfg(feature = "accesskit")] use egui::accesskit; -use egui::{ - ahash::HashSet, Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo, -}; +use egui::{Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo}; pub use winit; pub mod clipboard; @@ -24,6 +22,7 @@ mod window_settings; pub use window_settings::WindowSettings; +use ahash::HashSet; use raw_window_handle::HasDisplayHandle; #[allow(unused_imports)] diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 0465c32d76c8..c5047c266137 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -52,13 +52,13 @@ impl Id { /// Generate a new [`Id`] by hashing some source (e.g. a string or integer). pub fn new(source: impl std::hash::Hash) -> Self { - Self::from_hash(epaint::ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source)) + Self::from_hash(ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source)) } /// Generate a new [`Id`] by hashing the parent [`Id`] and the given argument. pub fn with(self, child: impl std::hash::Hash) -> Self { use std::hash::{BuildHasher, Hasher}; - let mut hasher = epaint::ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher(); + let mut hasher = ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher(); hasher.write_u64(self.0.get()); child.hash(&mut hasher); Self::from_hash(hasher.finish()) diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index edbac462c944..b5d8efffac7f 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -64,6 +64,7 @@ syntect = ["dep:syntect"] [dependencies] egui = { workspace = true, default-features = false, features = ["serde"] } +ahash.workspace = true enum-map = { version = "2", features = ["serde"] } log.workspace = true serde.workspace = true diff --git a/crates/egui_extras/src/loaders/ehttp_loader.rs b/crates/egui_extras/src/loaders/ehttp_loader.rs index 9aeb1b663caf..79c776948c33 100644 --- a/crates/egui_extras/src/loaders/ehttp_loader.rs +++ b/crates/egui_extras/src/loaders/ehttp_loader.rs @@ -1,5 +1,5 @@ +use ahash::HashMap; use egui::{ - ahash::HashMap, load::{Bytes, BytesLoadResult, BytesLoader, BytesPoll, LoadError}, mutex::Mutex, }; diff --git a/crates/egui_extras/src/loaders/file_loader.rs b/crates/egui_extras/src/loaders/file_loader.rs index afa7d0eefd6d..b90cff957540 100644 --- a/crates/egui_extras/src/loaders/file_loader.rs +++ b/crates/egui_extras/src/loaders/file_loader.rs @@ -1,5 +1,5 @@ +use ahash::HashMap; use egui::{ - ahash::HashMap, load::{Bytes, BytesLoadResult, BytesLoader, BytesPoll, LoadError}, mutex::Mutex, }; diff --git a/crates/egui_extras/src/loaders/image_loader.rs b/crates/egui_extras/src/loaders/image_loader.rs index 19a91105a63f..14086df935b6 100644 --- a/crates/egui_extras/src/loaders/image_loader.rs +++ b/crates/egui_extras/src/loaders/image_loader.rs @@ -1,5 +1,5 @@ +use ahash::HashMap; use egui::{ - ahash::HashMap, load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint}, mutex::Mutex, ColorImage, diff --git a/crates/egui_extras/src/loaders/svg_loader.rs b/crates/egui_extras/src/loaders/svg_loader.rs index 1794a8724f4d..67b59540c69f 100644 --- a/crates/egui_extras/src/loaders/svg_loader.rs +++ b/crates/egui_extras/src/loaders/svg_loader.rs @@ -1,7 +1,8 @@ use std::{mem::size_of, path::Path, sync::Arc}; +use ahash::HashMap; + use egui::{ - ahash::HashMap, load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint}, mutex::Mutex, ColorImage, diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 29129dddc3f2..28de667e8c1a 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -56,6 +56,7 @@ x11 = ["winit?/x11"] egui = { workspace = true, default-features = false, features = ["bytemuck"] } egui-winit = { workspace = true, optional = true, default-features = false } +ahash.workspace = true bytemuck.workspace = true glow.workspace = true log.workspace = true diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index 0c407ccb75de..5f981ab66d90 100644 --- a/crates/egui_glow/src/winit.rs +++ b/crates/egui_glow/src/winit.rs @@ -1,8 +1,8 @@ +use ahash::HashSet; +use egui::{ViewportId, ViewportOutput}; pub use egui_winit; -pub use egui_winit::EventResponse; - -use egui::{ahash::HashSet, ViewportId, ViewportOutput}; use egui_winit::winit; +pub use egui_winit::EventResponse; use crate::shader_version::ShaderVersion; diff --git a/crates/egui_plot/Cargo.toml b/crates/egui_plot/Cargo.toml index bc42765ea162..17e45852242f 100644 --- a/crates/egui_plot/Cargo.toml +++ b/crates/egui_plot/Cargo.toml @@ -37,6 +37,7 @@ serde = ["dep:serde", "egui/serde"] [dependencies] egui = { workspace = true, default-features = false } +ahash.workspace = true #! ### Optional dependencies ## Enable this when generating docs. diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 164710bef265..a46b7a225a53 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -15,7 +15,7 @@ mod transform; use std::{cmp::Ordering, ops::RangeInclusive, sync::Arc}; -use egui::ahash::HashMap; +use ahash::HashMap; use egui::*; use emath::Float as _; use epaint::Hsva; diff --git a/crates/egui_plot/src/memory.rs b/crates/egui_plot/src/memory.rs index 6a982269f269..5e6718651440 100644 --- a/crates/egui_plot/src/memory.rs +++ b/crates/egui_plot/src/memory.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use egui::{ahash, Context, Id, Pos2, Vec2b}; +use egui::{Context, Id, Pos2, Vec2b}; use crate::{PlotBounds, PlotTransform}; From 1ae2d2803a8b48eb5396c9eb0b4c69a5fe6bfed9 Mon Sep 17 00:00:00 2001 From: Doonv <58695417+doonv@users.noreply.github.com> Date: Mon, 27 May 2024 17:43:48 +0300 Subject: [PATCH 0108/1202] Make `TextEdit::return_key` optional (#4543) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I wanted to disable the return key functionality on my `TextEdit`, so that the `TextEdit` doesn't get unfocused whenever the user submits a command onto my developer console (which is also bound to ↵ Enter). --- crates/egui/src/widgets/text_edit/builder.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 440916f31b2e..b61b0f813f60 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -78,7 +78,7 @@ pub struct TextEdit<'t> { align: Align2, clip_text: bool, char_limit: usize, - return_key: KeyboardShortcut, + return_key: Option, } impl<'t> WidgetWithState for TextEdit<'t> { @@ -135,7 +135,7 @@ impl<'t> TextEdit<'t> { align: Align2::LEFT_TOP, clip_text: false, char_limit: usize::MAX, - return_key: KeyboardShortcut::new(Modifiers::NONE, Key::Enter), + return_key: Some(KeyboardShortcut::new(Modifiers::NONE, Key::Enter)), } } @@ -353,9 +353,11 @@ impl<'t> TextEdit<'t> { /// /// This combination will cause a newline on multiline, /// whereas on singleline it will cause the widget to lose focus. + /// + /// This combination is optional and can be disabled by passing [`None`] into this function. #[inline] - pub fn return_key(mut self, return_key: KeyboardShortcut) -> Self { - self.return_key = return_key; + pub fn return_key(mut self, return_key: impl Into>) -> Self { + self.return_key = return_key.into(); self } } @@ -805,7 +807,7 @@ fn events( default_cursor_range: CursorRange, char_limit: usize, event_filter: EventFilter, - return_key: KeyboardShortcut, + return_key: Option, ) -> (bool, CursorRange) { let os = ui.ctx().os(); @@ -892,8 +894,9 @@ fn events( pressed: true, modifiers, .. - } if *key == return_key.logical_key - && modifiers.matches_logically(return_key.modifiers) => + } if return_key.is_some_and(|return_key| { + *key == return_key.logical_key && modifiers.matches_logically(return_key.modifiers) + }) => { if multiline { let mut ccursor = text.delete_selected(&cursor_range); From ff7a3832b68a8ecccf531511d1a17d92821b5430 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Mon, 27 May 2024 17:28:03 +0200 Subject: [PATCH 0109/1202] TextEdit hint text styling (#4517) Simply adds a `hint_text_font` (any suggestions for a better name? hint_text_style seemed misleading as it doesn't only accept `TextStyle`) method to the TextEdit builder that allows to set the styling (specifically, a `FontSelection`) for the hint text. My personal use-case for this was having body-sized hint text in a heading-sized TextEdit, but I'm sure there's more out there. The PR shouldn't break compatibility, as it's stored as an `Option` that defaults to the `font_id` (which was the only behaviour prior to this) when empty. It looked too trivial to add something to the actual demo, but I have it locally, so let me know if I should commit that. Ran the check script locally, had no complaints. --- crates/egui/src/widgets/text_edit/builder.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index b61b0f813f60..4458fd031f7a 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -60,6 +60,7 @@ use super::{TextEditOutput, TextEditState}; pub struct TextEdit<'t> { text: &'t mut dyn TextBuffer, hint_text: WidgetText, + hint_text_font: Option, id: Option, id_source: Option, font_selection: FontSelection, @@ -111,6 +112,7 @@ impl<'t> TextEdit<'t> { Self { text, hint_text: Default::default(), + hint_text_font: None, id: None, id_source: None, font_selection: Default::default(), @@ -189,6 +191,13 @@ impl<'t> TextEdit<'t> { self } + /// Set a specific style for the hint text. + #[inline] + pub fn hint_text_font(mut self, hint_text_font: impl Into) -> Self { + self.hint_text_font = Some(hint_text_font.into()); + self + } + /// If true, hide the letters from view and prevent copying from the field. #[inline] pub fn password(mut self, password: bool) -> Self { @@ -438,6 +447,7 @@ impl<'t> TextEdit<'t> { let TextEdit { text, hint_text, + hint_text_font, id, id_source, font_selection, @@ -653,10 +663,11 @@ impl<'t> TextEdit<'t> { if text.as_str().is_empty() && !hint_text.is_empty() { let hint_text_color = ui.visuals().weak_text_color(); + let hint_text_font_id = hint_text_font.unwrap_or(font_id.into()); let galley = if multiline { - hint_text.into_galley(ui, Some(true), desired_inner_size.x, font_id) + hint_text.into_galley(ui, Some(true), desired_inner_size.x, hint_text_font_id) } else { - hint_text.into_galley(ui, Some(false), f32::INFINITY, font_id) + hint_text.into_galley(ui, Some(false), f32::INFINITY, hint_text_font_id) }; painter.galley(rect.min, galley, hint_text_color); } From 192a111272e5fef09984aa36bbccec6c5c2ac780 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Mon, 27 May 2024 18:56:16 +0200 Subject: [PATCH 0110/1202] Hide all other series when alt-clicking in the legend (#4549) https://github.com/emilk/egui/assets/49431240/75d32f53-4c1c-4713-b25e-e1787e465a48 * Closes #4548 --- crates/egui_plot/src/legend.rs | 54 +++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/crates/egui_plot/src/legend.rs b/crates/egui_plot/src/legend.rs index a3b353e994cf..538f2baf6230 100644 --- a/crates/egui_plot/src/legend.rs +++ b/crates/egui_plot/src/legend.rs @@ -99,11 +99,11 @@ impl LegendEntry { } } - fn ui(&mut self, ui: &mut Ui, text: String, text_style: &TextStyle) -> Response { + fn ui(&self, ui: &mut Ui, text: String, text_style: &TextStyle) -> Response { let Self { color, checked, - hovered, + hovered: _, } = self; let font_id = text_style.resolve(ui.style()); @@ -162,9 +162,6 @@ impl LegendEntry { let text_position = pos2(text_position_x, rect.center().y - 0.5 * galley.size().y); painter.galley(text_position, galley, visuals.text_color()); - *checked ^= response.clicked_by(PointerButton::Primary); - *hovered = response.hovered(); - response } } @@ -267,14 +264,55 @@ impl Widget for &mut LegendWidget { .multiply_with_opacity(config.background_alpha); background_frame .show(ui, |ui| { - entries + let mut focus_on_item = None; + + let response_union = entries .iter_mut() - .map(|(name, entry)| entry.ui(ui, name.clone(), &config.text_style)) + .map(|(name, entry)| { + let response = entry.ui(ui, name.clone(), &config.text_style); + + // Handle interactions. Alt-clicking must be deferred to end of loop + // since it may affect all entries. + handle_interaction_on_legend_item(&response, entry); + if response.clicked() && ui.input(|r| r.modifiers.alt) { + focus_on_item = Some(name.clone()); + } + + response + }) .reduce(|r1, r2| r1.union(r2)) - .unwrap() + .unwrap(); + + if let Some(focus_on_item) = focus_on_item { + handle_focus_on_legend_item(&focus_on_item, entries); + } + + response_union }) .inner }) .inner } } + +/// Handle per-entry interactions. +fn handle_interaction_on_legend_item(response: &Response, entry: &mut LegendEntry) { + entry.checked ^= response.clicked_by(PointerButton::Primary); + entry.hovered = response.hovered(); +} + +/// Handle alt-click interaction (which may affect all entries). +fn handle_focus_on_legend_item( + clicked_entry_name: &str, + entries: &mut BTreeMap, +) { + // if all other items are already hidden, we show everything + let is_focus_item_only_visible = entries + .iter() + .all(|(name, entry)| !entry.checked || (clicked_entry_name == name)); + + // either show everything or show only the focus item + for (name, entry) in entries.iter_mut() { + entry.checked = is_focus_item_only_visible || clicked_entry_name == name; + } +} From 8553e738e03a54aca6897773fb5f10effa9076ae Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Mon, 27 May 2024 18:57:39 +0200 Subject: [PATCH 0111/1202] eframe: Add `NativeOptions::persistence_path` (#4423) This allows customizing the persistence path in NativeOptions. Previously, persistence wouldn't work with android because directories-next doesn't support android so eframe would just fail to find a place where it could store its config. * Closes #4098 (android users can now specify a path that works with android, by e.g. using app_dirs2, which supports android) --- crates/eframe/src/epi.rs | 9 +++++++++ crates/eframe/src/native/epi_integration.rs | 12 ++++++++++++ crates/eframe/src/native/file_storage.rs | 2 +- crates/eframe/src/native/glow_integration.rs | 18 +++++++++++------- crates/eframe/src/native/wgpu_integration.rs | 18 +++++++++++------- 5 files changed, 44 insertions(+), 15 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index db39a08b15b6..6d15c39e9e83 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -150,6 +150,7 @@ pub trait App { /// On web the state is stored to "Local Storage". /// /// On native the path is picked using [`crate::storage_dir`]. + /// The path can be customized via [`NativeOptions::persistence_path`]. fn save(&mut self, _storage: &mut dyn Storage) {} /// Called once on shutdown, after [`Self::save`]. @@ -362,6 +363,10 @@ pub struct NativeOptions { /// Controls whether or not the native window position and size will be /// persisted (only if the "persistence" feature is enabled). pub persist_window: bool, + + /// The folder where `eframe` will store the app state. If not set, eframe will get the paths + /// from [directories_next]. + pub persistence_path: Option, } #[cfg(not(target_arch = "wasm32"))] @@ -379,6 +384,8 @@ impl Clone for NativeOptions { #[cfg(feature = "wgpu")] wgpu_options: self.wgpu_options.clone(), + persistence_path: self.persistence_path.clone(), + ..*self } } @@ -418,6 +425,8 @@ impl Default for NativeOptions { wgpu_options: egui_wgpu::WgpuConfiguration::default(), persist_window: true, + + persistence_path: None, } } } diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 827b9ec249cb..46ad66817ae5 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -1,6 +1,8 @@ //! Common tools used by [`super::glow_integration`] and [`super::wgpu_integration`]. use web_time::Instant; + +use std::path::PathBuf; use winit::event_loop::EventLoopWindowTarget; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; @@ -132,6 +134,16 @@ pub fn create_storage(_app_name: &str) -> Option> { None } +#[allow(clippy::unnecessary_wraps)] +pub fn create_storage_with_file(_file: impl Into) -> Option> { + #[cfg(feature = "persistence")] + return Some(Box::new( + super::file_storage::FileStorage::from_ron_filepath(_file), + )); + #[cfg(not(feature = "persistence"))] + None +} + // ---------------------------------------------------------------------------- /// Everything needed to make a winit-based integration for [`epi`]. diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index 51c668abdebc..970c35a4f41c 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -41,7 +41,7 @@ impl Drop for FileStorage { impl FileStorage { /// Store the state in this .ron file. - fn from_ron_filepath(ron_filepath: impl Into) -> Self { + pub(crate) fn from_ron_filepath(ron_filepath: impl Into) -> Self { crate::profile_function!(); let ron_filepath: PathBuf = ron_filepath.into(); log::debug!("Loading app state from {:?}…", ron_filepath); diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index ec7669b077c2..28eb1a4ef24d 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -195,13 +195,17 @@ impl GlowWinitApp { ) -> Result<&mut GlowWinitRunning> { crate::profile_function!(); - let storage = epi_integration::create_storage( - self.native_options - .viewport - .app_id - .as_ref() - .unwrap_or(&self.app_name), - ); + let storage = if let Some(file) = &self.native_options.persistence_path { + epi_integration::create_storage_with_file(file) + } else { + epi_integration::create_storage( + self.native_options + .viewport + .app_id + .as_ref() + .unwrap_or(&self.app_name), + ) + }; let egui_ctx = create_egui_context(storage.as_deref()); diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 9380bbf72d6c..dbf4f4446902 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -406,13 +406,17 @@ impl WinitApp for WgpuWinitApp { self.recreate_window(event_loop, running); running } else { - let storage = epi_integration::create_storage( - self.native_options - .viewport - .app_id - .as_ref() - .unwrap_or(&self.app_name), - ); + let storage = if let Some(file) = &self.native_options.persistence_path { + epi_integration::create_storage_with_file(file) + } else { + epi_integration::create_storage( + self.native_options + .viewport + .app_id + .as_ref() + .unwrap_or(&self.app_name), + ) + }; let egui_ctx = winit_integration::create_egui_context(storage.as_deref()); let (window, builder) = create_window( &egui_ctx, From 7a17a6d6ad5a1dc92f0b642ac44d25e12b428f14 Mon Sep 17 00:00:00 2001 From: YgorSouza <43298013+YgorSouza@users.noreply.github.com> Date: Mon, 27 May 2024 19:23:15 +0200 Subject: [PATCH 0112/1202] Plot now respects the `interact_radius` set in the UI's style (#4520) * Closes #4519 --- crates/egui_plot/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index a46b7a225a53..6dec9d7276d8 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -1666,7 +1666,7 @@ impl<'a> PreparedPlot<'a> { return (Vec::new(), None); } - let interact_radius_sq = (16.0_f32).powi(2); + let interact_radius_sq = ui.style().interaction.interact_radius.powi(2); let candidates = items .iter() From a8b50e6aa12f7b25cc00c09794a6a5f4dce2fff0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 27 May 2024 21:28:33 +0200 Subject: [PATCH 0113/1202] Move test crates to own folder (#4554) --- Cargo.toml | 1 + examples/multiple_viewports/README.md | 2 +- examples/test_viewports/screenshot.png | Bin 6496 -> 0 bytes tests/README.md | 4 ++++ .../test_inline_glow_paint/Cargo.toml | 0 .../test_inline_glow_paint/src/main.rs | 0 {examples => tests}/test_viewports/Cargo.toml | 0 {examples => tests}/test_viewports/README.md | 4 +--- {examples => tests}/test_viewports/src/main.rs | 0 9 files changed, 7 insertions(+), 4 deletions(-) delete mode 100644 examples/test_viewports/screenshot.png create mode 100644 tests/README.md rename {examples => tests}/test_inline_glow_paint/Cargo.toml (100%) rename {examples => tests}/test_inline_glow_paint/src/main.rs (100%) rename {examples => tests}/test_viewports/Cargo.toml (100%) rename {examples => tests}/test_viewports/README.md (52%) rename {examples => tests}/test_viewports/src/main.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index a42033c0efb1..77cc2e7ba5d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "crates/epaint", "examples/*", + "tests/*", "xtask", ] diff --git a/examples/multiple_viewports/README.md b/examples/multiple_viewports/README.md index 1ce0a2b98b03..3fa67d8abe79 100644 --- a/examples/multiple_viewports/README.md +++ b/examples/multiple_viewports/README.md @@ -4,4 +4,4 @@ Example how to show multiple viewports (native windows) can be created in `egui` cargo run -p multiple_viewports ``` -For a more advanced example, see [../test_viewports]. +For a more advanced example, see [../../tests/test_viewports]. diff --git a/examples/test_viewports/screenshot.png b/examples/test_viewports/screenshot.png deleted file mode 100644 index 5e8680bbcb0ac68762790d4e31361e4161dd7bf1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6496 zcmds6WmJ@1*9H|B2^j<>q=ym&q@-aOVhHJ!lk2ERtx*I9E^XKSXtX|_e+=X>M3`o!Po=|w@ zdl;iyF<71bJz3m)ec4=ZQ-iwk5;9MNx{2k^i_`v{K_@HHJ8n$i*8PUhqN#uSt&@B# z_e~|P8HaJFT8DAK0BnYPuzw^Fc<1JQ_zU>d{5-(RzdJ#)eMa=Fk8cBK$-o~&sgidG znYIS%Qi=(4v8Fy*&KXUwUpWqL;Oi5?dL3pg9nuyn9gfag2myB^;6jcL!alXGHZkEv z)t@plGU#3ndXZ0KE{pIi{8I9|($l#{CpLwTO%L%69{i+t9-?s+5>fGx=<$Y1_Yr-6 zLiBtNnn!&z5DniM$cwler(<9tz;NVtY52y$pL|ZK zINIPhUsO3XJN9muLs*@(3l0tz%9o=><_}ADB-~)D)9i=)p{VuezWy>9xoLY56?4HTG42&OPb?0oDHOUuxq)4{6}wg}Y72td~G*Zg#B zeb3YowKmgtUm<&@MIm1ua_p?>dIU2e^}{lKN8&V7&1}~9;tf}%bDWpFW-|QYnSVnt z{OK4Gj%M?(=i0Ob>~kvevW>e~op0r3Pr0f!53pfD3j`*Y;<_ov3GgWt>ninc235tK z6Voy7Rd`}gigB0$LF#7jfz07PQ)9t`Vt&+AU9c6TW$7*;*oEt&vpVwV+%yTzn+=tZ zxXf6PuTHzaNKls!CwE08FB>oLE;ucF&lpo2CEhx?3cIs6BNy~mt1Jvwhaf)?D@Eh* z0Eu;;?o}6?nMXL<%!h3H$#dBZVy2(S{9It!95zSJ>>o5u;#|j!ZOggKLjgrjD>%I3 z`6&_~221Iw$6RD4_){;F{F@#4kqo)QJnQ#4_)c1U1$*Au#S+J;dEk^uw9(~kGGH7O zWnQjsIETaEHt~sZWh?$%WGpWNC~mLFC##H>7IJ;L(@m<5LO9-8kS_7>5OCVs#@rM*X)mHY8i;zx+5cI}JP@gb3N zjz@&~S0Ml4$K!V(OTf^VYT^c55Vx!L`fpeOY?;D@abd>^Qp@AnDHv>Geg7;1u6ypF zO}4v(?fWV|s`BZO>yO~dn7u~Hanc66Y57GW+B|8Mra^dc$e)QrOZ1$`oYPr(cCYO( zrgXz{+EFw2*IH&bFdlx0N3+9-s&u$A5>d=6!6Y2wuX08v+P&Z}q$TIQ?a`gYLxfT} zd(+^=s>BhRcla|i^{dQF(Qdy+GbXc!BQ05R1_mSlWO;etb3NUQ)IF(hdj2rF?yj!K zy?W)cB)ktRfU{pucrOn{b}AxS(BSRTG2n6j!|J-^{6^D2s?>;=^xWnNn2XsyxwES& z^%A`rS)<7(nSdo@`Sa6($B@0WggvJo=Sv|522@T7r_LkP{GPYFO?0Tsp!c;czmj{q zS17AvXVpoQg$te#nG1ECH%szqO!85mdkw07<#5o zB%NCozaZ1``n;6xWxYzCAF?KWZU1sS7!Kce$6$lx0d#VRFXZgkyM47NgBbXpnh0#g zRe|)`08uaaLx^88wJOvab|st9>2+yG!5o|8`{-At?aaE_z1R1a3@M~N!7>y{@g!1s zm$ZB=tRp0+tRPKZ$lDpt*M=HNSuqyE)5MxqNg82K;@L!&I5VQ>7s=91bID&A<@1kO zQ+xYZ*u64>SPAVB-+ec%TcgW;!OU8|sPP~$M|Jh+Jc7W(QUHZ4=-$(gjVNS!Zh$T%)~|bs)K{x^*R%Fm?wZit7cXIWR+vdapr_9>ug;}VCCc=)NYxYm(Z96c z!(euPR+!$=$xdYMq5E>56FnhC+c5>@*r`XJwD)kHRxjNBi$*(FlrL`hDlLF zA5Yo6y_{*QJ6veXG}w zS`LM-?&B!LW@z%1O-WFWf;=|lMPSk+>!wLMM6s+L3YSSK-NnJZm3*VB`42kN=svXa zV3{rfuXG+KQJ9M;YF%$gZI|34W<>5L7^z@IVzBY;nOxbdcqfwW{24W0Q!elI|%mf?+FZY zSv#|Btu9y7Y4dMabY@_e$Q&++W`1!tYvQhS;J*3(`e6f^4Gbw#Nc-$(`$~LSXDhpc zE^Azl4jXCRV(cI3I+FN4&1&SupL7HL9ObX;y?yBR+LhYt_~5!AIh#g>x+wcg)spM! z{RGVo%qjPfFF4%1aukl+OR&azZfovf`?dQ*paB+b0zlRD)7_0$Gp^VDtP*#;%qtQi zZ!fE`GT@)5?7{n?mAGj;ab~Zz@R{)QoRk)^j;q<)ra7k>HFYIgeb&trn2Fyw(1+SGg;dzubbNI$h8hnVkpvF-^CCmduf$-1_^mB1Cv3vtKXj6s6BT;$sNE9F7noYK zhh0AmKV7Ho-4$emhd+%jd$YGx_kP{FBsggE z_~Y`_3d7IMuvma+$!)&08+_?bgRunl69eKZYaoH%Krl#w&}HkpxTG5xY|F;9Da?)&ArqZ8NV$iBfViHfDQ z%}5O&4*kAnk`L)hvx>0w1QwB30>qHZb-y}IT;{(kApRH}dH~xMcI^YqTtl~V8^B)K zLp0>T1+!j{KT*!956=>w{5-9d0jG#A=w>#ejD76;HSEKt*ms{v>^pHt=IQA4H3lv> zG`2uvzN~>j6`5WhpR18HB2>kHVIxCsP($530Y~X$`*5j@wTvt zsdZ@~_MtC{0^qL$@IHsbsFcI$J{vLZDS}oOJENf%%w$Sl0ZrayI4Ie$YNJ_`qhyU1 z7LeBDG|<|MnZT!oMsWAK+b_Pv+>5J!z^-jU$FAt;UciTU3u~M!C`nmAe|U!Dje3cZ z#Z~NIwKaz5E4>dr2oeZ{Dwe#1`yrf$Dvsp5=Uf!_GREIqSmHP1zU@>?uPW;@KMWa2 zoz4A)#Xf zc~h=-)x>Ltg_EjoWce>1W(hBJSlHI{Z6$RXvn_dXVlEc$&~Od0jn)?Z0-8x*U%aO}<=@X?kJIBL0o zukKDruA{RikqBzvt&ORGII&+-=h63jML!n2>g}9wJi30jJ6-O14R*EEC3{qN?zp$6 z!zI*&oe?*vTtNy@n1ZtZf0;+uZ z@6vX3bB5uX<@rGMVFlf<=M&yQF;2y}sB;`W$D( zl+0^Ho`I4J>mF_B%P`d4r(zDT&3m`VxX~8Jzj7sr;o)s?WJJ}?}*iW!MGHpda ziapMu5ff}xBkZt|%#k~$SxV9$rwe9P^;GEyhcw@J>%A9XQALlJthwXn+!>9b5Qps0 zBBT&TyNjRI>p~yyLZwgdhk@kIpGJHYrz<>hfjM<9u zN~6l@CT)W9sl7ivrPs>2ECi4?CDu~xZb)s))(eIddr>L=>zS?GqyGK@WB(W7kjK@xIrgiWe_{bUKM!@M*U zW~@ccrPp=Lxg=Ibe(C{8zD_xp4nFNSGQQPa@NRDX)3E$^3l6;PUtl-#7ZwV`!a1Ig zRhl}pzi9UzViN-3SZf9E3D1@!D z6zDbj)g%98HwH3#m@dV~tz5}uk-$t@#HMO0MV!Y-;C6zagi<3Af4uSeuToypv4PV66If7Hxyx?X`JUyRTJ%Z1xW>}M-1dVk~T*G62)En=bc7iTwimYb$D*8jqw zBS?Z=c9DZU`lA;?e)!)+HwTVj!$baJwWcH)V5p~>DgeOuJ{W^w}%^4EF%?-CY}gPV62w-zq8SNaFc-|4nL*}oe5a7A4}81y2&1h9^NR5YfTIZ(ha1yVFR6C#8NpLA73Uf(b{= z1|p<>Wgf+-y`#vE6ez$g3w}Whp|w{b(#LZwy;}s7SNi+bM~R(9Rr_F8Pw#1F0wSAS)l8sLDJUP zE65J2VK~P={CP`?_c7#)kh2{*FoS<26|wMnU{<|;N~R!>I-Fv>RP??r*V`akBWc(= zl{iF0kBPqjsm@RcwQ0Fui7EF~saH1N6qV_$wK0I>_dS@aqcLE`D69xWoh?vX4^<-r7`q@k^R57$P8v1-7&y7%+rOmBv` z=Z&Q$tP``87WLwm7a^>{R8TXBulAZgIPE{hBlumC__^7w9DzF=P&cG=6g7k#8dFv= zD5zedtiju{R3JooMfJ8cJs z*@T_k7JouN9h@u?K8d-A)ulfCwv+UpH8v4K_8o4^D?bKKD?|PwJPi$LH1mT>4dY3q zoE0N!Paj1aB2pOz{_++uk&1qmJUJr()6B{}T7kh}vE}T)HwbKbayp9{E|?Eb&^C<; z$t`?hwj41ni;kfA#%VHOIW5G~%5O67{1a8%5MPh3Z3Y1`52uw!fek>xIHyFPOFMdZ zpfR}o%n<<-B~QQ8HX&U&7)5tR?(=vtkI`YyEo7dcCEJvA%&hB%>mWjbo6$jr#Z<(( zdOcpfXph6tWkW~BHxFMBiIjK>w_A7JQWaWPXH&21J1nXs?L@3zs<3ZNchsi1^71l+ zU}19M+^c!bQ8L=YFJDw6MK_dtkNQBGec!C}0F4Q$Xd-TI@pa0R+D(J05C;3HmaqzR z)IN0M{^6=+sR%hMs@2!?fuGJ1wgGrZSqNQrr%jg5y#dFKZx(E}c3UuRJIQ|}Tjfc} z@LTBT_bAoh5i#ESJHGGsd)YyTZ3nD#c&7s*dz9y>Q)v0WlGynu@O@hWuF*L8OolHzUPE?oWMcBi->?8e8Awc* zS_LXz4wRa>ylCF{H!-0?`vp#Mh(|Mz=2xc+#B=X5@|BX>^!;P%&vf~<;6Ipp>G{{cPQI}HE; diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000000..ac4d8a18f4c7 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,4 @@ +## Test apps +Some application to tests various parts of egui and eframe. + +At some point it would be nice to have automatic screenshot regression tests for these. diff --git a/examples/test_inline_glow_paint/Cargo.toml b/tests/test_inline_glow_paint/Cargo.toml similarity index 100% rename from examples/test_inline_glow_paint/Cargo.toml rename to tests/test_inline_glow_paint/Cargo.toml diff --git a/examples/test_inline_glow_paint/src/main.rs b/tests/test_inline_glow_paint/src/main.rs similarity index 100% rename from examples/test_inline_glow_paint/src/main.rs rename to tests/test_inline_glow_paint/src/main.rs diff --git a/examples/test_viewports/Cargo.toml b/tests/test_viewports/Cargo.toml similarity index 100% rename from examples/test_viewports/Cargo.toml rename to tests/test_viewports/Cargo.toml diff --git a/examples/test_viewports/README.md b/tests/test_viewports/README.md similarity index 52% rename from examples/test_viewports/README.md rename to tests/test_viewports/README.md index 280ffe2325d5..2fa38c802aa0 100644 --- a/examples/test_viewports/README.md +++ b/tests/test_viewports/README.md @@ -1,5 +1,3 @@ This is a test of the viewports feature of eframe and egui, where we show off using multiple windows. -For a simple example, see [`multiple_viewports`](../multiple_viewports). - -![](screenshot.png) +For a simple example, see [`multiple_viewports`](../../examples/multiple_viewports). diff --git a/examples/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs similarity index 100% rename from examples/test_viewports/src/main.rs rename to tests/test_viewports/src/main.rs From 759c8fd2c941828e95f54e39ee051e7428d77694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= Date: Mon, 27 May 2024 21:41:28 +0200 Subject: [PATCH 0114/1202] Use ResizeObserver instead of `resize` event (#4536) Currently, if the size of the canvas element changes independently of the size of the browser window (e.g. due to its parent element shrinking), then no repaints are scheduled. This PR replaces the `resize` event with a `ResizeObserver`, which ensures that _any_ resize of the canvas element (including those caused by browser window resizes) trigger a repaint. The repaint is done synchronously as part of the resize event, to reduce any potential flickering. The result seems to pass the rendering tests on most platform+browser combinations. We tested: - Chrome, Firefox, Safari on macOS - Chrome, Firefox on Linux (ubuntu and arch, both running wayland) - Chrome, Firefox on Windows Firefox still has some antialiasing issues on Linux platforms, but this antialiasing also happens on `master`, so this PR is not a regression there. The code setting `canvas.style.width` and `canvas.style.height` at the start of `AppRunner::logic` was also removed - the canvas _display_ size is now fully controlled by CSS, e.g. by setting `canvas { width: 100%; height: 100%; }`. The approach used here is described in https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html Note: The only remaining place where egui updates the style of the canvas it is rendering to is some of the IME/mobile input handling code. Fixing that is out of scope for this PR, and will be done in a followup PR. --- crates/eframe/Cargo.toml | 7 +++ crates/eframe/src/epi.rs | 7 --- crates/eframe/src/web/app_runner.rs | 2 +- crates/eframe/src/web/events.rs | 79 ++++++++++++++++++++++++++++- crates/eframe/src/web/mod.rs | 51 ------------------- crates/eframe/src/web/web_runner.rs | 32 +++++++++++- web_demo/index.html | 7 +-- 7 files changed, 121 insertions(+), 64 deletions(-) diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 4a5a9918a127..acd7600f6e6f 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -237,7 +237,14 @@ web-sys = { workspace = true, features = [ "MediaQueryListEvent", "MouseEvent", "Navigator", + "Node", + "NodeList", "Performance", + "ResizeObserver", + "ResizeObserverEntry", + "ResizeObserverBoxOptions", + "ResizeObserverOptions", + "ResizeObserverSize", "Storage", "Touch", "TouchEvent", diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 6d15c39e9e83..7b75811ccbc6 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -464,11 +464,6 @@ pub struct WebOptions { /// Configures wgpu instance/device/adapter/surface creation and renderloop. #[cfg(feature = "wgpu")] pub wgpu_options: egui_wgpu::WgpuConfiguration, - - /// The size limit of the web app canvas. - /// - /// By default the max size is [`egui::Vec2::INFINITY`], i.e. unlimited. - pub max_size_points: egui::Vec2, } #[cfg(target_arch = "wasm32")] @@ -484,8 +479,6 @@ impl Default for WebOptions { #[cfg(feature = "wgpu")] wgpu_options: egui_wgpu::WgpuConfiguration::default(), - - max_size_points: egui::Vec2::INFINITY, } } } diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 620fe86fd624..872921591acd 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -5,6 +5,7 @@ use crate::{epi, App}; use super::{now_sec, web_painter::WebPainter, NeedRepaint}; pub struct AppRunner { + #[allow(dead_code)] web_options: crate::WebOptions, pub(crate) frame: epi::Frame, egui_ctx: egui::Context, @@ -184,7 +185,6 @@ impl AppRunner { /// /// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`]. pub fn logic(&mut self) { - super::resize_canvas_to_screen_size(self.canvas(), self.web_options.max_size_points); let canvas_size = super::canvas_size_in_points(self.canvas(), self.egui_ctx()); let raw_input = self.input.new_frame(canvas_size); diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 56d3fdf071a8..33d36c356d79 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -247,7 +247,8 @@ pub(crate) fn install_window_events(runner_ref: &WebRunner) -> Result<(), JsValu runner.save(); })?; - for event_name in &["load", "pagehide", "pageshow", "resize"] { + // NOTE: resize is handled by `ResizeObserver` below + for event_name in &["load", "pagehide", "pageshow"] { runner_ref.add_event_listener(&window, event_name, move |_: web_sys::Event, runner| { // log::debug!("{event_name:?}"); runner.needs_repaint.repaint_asap(); @@ -590,3 +591,79 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu Ok(()) } + +pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsValue> { + let closure = Closure::wrap(Box::new({ + let runner_ref = runner_ref.clone(); + move |entries: js_sys::Array| { + // Only call the wrapped closure if the egui code has not panicked + if let Some(mut runner_lock) = runner_ref.try_lock() { + let canvas = runner_lock.canvas(); + let (width, height) = match get_display_size(&entries) { + Ok(v) => v, + Err(err) => { + log::error!("{}", super::string_from_js_value(&err)); + return; + } + }; + canvas.set_width(width); + canvas.set_height(height); + + // force an immediate repaint + runner_lock.needs_repaint.repaint_asap(); + paint_if_needed(&mut runner_lock); + } + } + }) as Box); + + let observer = web_sys::ResizeObserver::new(closure.as_ref().unchecked_ref())?; + let mut options = web_sys::ResizeObserverOptions::new(); + options.box_(web_sys::ResizeObserverBoxOptions::ContentBox); + if let Some(runner_lock) = runner_ref.try_lock() { + observer.observe_with_options(runner_lock.canvas(), &options); + drop(runner_lock); + runner_ref.set_resize_observer(observer, closure); + } + + Ok(()) +} + +// Code ported to Rust from: +// https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html +fn get_display_size(resize_observer_entries: &js_sys::Array) -> Result<(u32, u32), JsValue> { + let width; + let height; + let mut dpr = web_sys::window().unwrap().device_pixel_ratio(); + + let entry: web_sys::ResizeObserverEntry = resize_observer_entries.at(0).dyn_into()?; + if JsValue::from_str("devicePixelContentBoxSize").js_in(entry.as_ref()) { + // NOTE: Only this path gives the correct answer for most browsers. + // Unfortunately this doesn't work perfectly everywhere. + let size: web_sys::ResizeObserverSize = + entry.device_pixel_content_box_size().at(0).dyn_into()?; + width = size.inline_size(); + height = size.block_size(); + dpr = 1.0; // no need to apply + } else if JsValue::from_str("contentBoxSize").js_in(entry.as_ref()) { + let content_box_size = entry.content_box_size(); + let idx0 = content_box_size.at(0); + if !idx0.is_undefined() { + let size: web_sys::ResizeObserverSize = idx0.dyn_into()?; + width = size.inline_size(); + height = size.block_size(); + } else { + // legacy + let size = JsValue::clone(content_box_size.as_ref()); + let size: web_sys::ResizeObserverSize = size.dyn_into()?; + width = size.inline_size(); + height = size.block_size(); + } + } else { + // legacy + let content_rect = entry.content_rect(); + width = content_rect.width(); + height = content_rect.height(); + } + + Ok(((width.round() * dpr) as u32, (height.round() * dpr) as u32)) +} diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 566c9b03c8d6..f98bbc1ed014 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -40,7 +40,6 @@ pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu; pub use backend::*; -use egui::Vec2; use wasm_bindgen::prelude::*; use web_sys::MediaQueryList; @@ -124,56 +123,6 @@ fn canvas_size_in_points(canvas: &web_sys::HtmlCanvasElement, ctx: &egui::Contex ) } -fn resize_canvas_to_screen_size( - canvas: &web_sys::HtmlCanvasElement, - max_size_points: egui::Vec2, -) -> Option<()> { - let parent = canvas.parent_element()?; - - // In this function we use "pixel" to mean physical pixel, - // and "point" to mean "logical CSS pixel". - let pixels_per_point = native_pixels_per_point(); - - // Prefer the client width and height so that if the parent - // element is resized that the egui canvas resizes appropriately. - let parent_size_points = Vec2 { - x: parent.client_width() as f32, - y: parent.client_height() as f32, - }; - - if parent_size_points.x <= 0.0 || parent_size_points.y <= 0.0 { - log::error!("The parent element of the egui canvas is {}x{}. Try adding `html, body {{ height: 100%; width: 100% }}` to your CSS!", parent_size_points.x, parent_size_points.y); - } - - // We take great care here to ensure the rendered canvas aligns - // perfectly to the physical pixel grid, lest we get blurry text. - // At the time of writing, we get pixel perfection on Chromium and Firefox on Mac, - // but Desktop Safari will be blurry on most zoom levels. - // See https://github.com/emilk/egui/issues/4241 for more. - - let canvas_size_pixels = pixels_per_point * parent_size_points.min(max_size_points); - - // Make sure that the size is always an even number of pixels, - // otherwise, the page renders blurry on some platforms. - // See https://github.com/emilk/egui/issues/103 - let canvas_size_pixels = (canvas_size_pixels / 2.0).round() * 2.0; - - let canvas_size_points = canvas_size_pixels / pixels_per_point; - - canvas - .style() - .set_property("width", &format!("{}px", canvas_size_points.x)) - .ok()?; - canvas - .style() - .set_property("height", &format!("{}px", canvas_size_points.y)) - .ok()?; - canvas.set_width(canvas_size_pixels.x as u32); - canvas.set_height(canvas_size_pixels.y as u32); - - Some(()) -} - // ---------------------------------------------------------------------------- /// Set the cursor icon. diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index f39fc0dcace6..35bb4be334bc 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -28,8 +28,10 @@ pub struct WebRunner { /// the panic handler, since they aren't `Send`. events_to_unsubscribe: Rc>>, - /// Used in `destroy` to cancel a pending frame. + /// Current animation frame in flight. request_animation_frame_id: Cell>, + + resize_observer: Rc>>, } impl WebRunner { @@ -48,6 +50,7 @@ impl WebRunner { runner: Rc::new(RefCell::new(None)), events_to_unsubscribe: Rc::new(RefCell::new(Default::default())), request_animation_frame_id: Cell::new(None), + resize_observer: Default::default(), } } @@ -78,6 +81,8 @@ impl WebRunner { events::install_color_scheme_change_event(self)?; } + events::install_resize_observer(self)?; + self.request_animation_frame()?; } @@ -109,6 +114,11 @@ impl WebRunner { } } } + + if let Some(context) = self.resize_observer.take() { + context.resize_observer.disconnect(); + drop(context.closure); + } } /// Shut down eframe and clean up resources. @@ -198,15 +208,35 @@ impl WebRunner { let runner_ref = self.clone(); move || events::paint_and_schedule(&runner_ref) }); + let id = window.request_animation_frame(closure.as_ref().unchecked_ref())?; self.request_animation_frame_id.set(Some(id)); closure.forget(); // We must forget it, or else the callback is canceled on drop + Ok(()) } + + pub(crate) fn set_resize_observer( + &self, + resize_observer: web_sys::ResizeObserver, + closure: Closure, + ) { + self.resize_observer + .borrow_mut() + .replace(ResizeObserverContext { + resize_observer, + closure, + }); + } } // ---------------------------------------------------------------------------- +struct ResizeObserverContext { + resize_observer: web_sys::ResizeObserver, + closure: Closure, +} + struct TargetEvent { target: web_sys::EventTarget, event_name: String, diff --git a/web_demo/index.html b/web_demo/index.html index 142355b48f83..c4b3bac00950 100644 --- a/web_demo/index.html +++ b/web_demo/index.html @@ -48,9 +48,10 @@ margin-left: auto; display: block; position: absolute; - top: 0%; - left: 50%; - transform: translate(-50%, 0%); + top: 0; + left: 0; + width: 100%; + height: 100%; } .centered { From d131b2b5801827f1324a7b2a331e66691acf5b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= Date: Mon, 27 May 2024 21:55:23 +0200 Subject: [PATCH 0115/1202] Fix: Don't `.forget()` RAF closure (#4551) The closure is now stored in `WebRunner` and dropped in the next `request_animation_frame` instead of being "leaked" via `forget` --------- Co-authored-by: Emil Ernerfeldt --- crates/eframe/src/web/web_runner.rs | 46 ++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index 35bb4be334bc..6a230a8e3292 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -1,7 +1,4 @@ -use std::{ - cell::{Cell, RefCell}, - rc::Rc, -}; +use std::{cell::RefCell, rc::Rc}; use wasm_bindgen::prelude::*; @@ -29,7 +26,7 @@ pub struct WebRunner { events_to_unsubscribe: Rc>>, /// Current animation frame in flight. - request_animation_frame_id: Cell>, + frame: Rc>>, resize_observer: Rc>>, } @@ -49,7 +46,7 @@ impl WebRunner { panic_handler, runner: Rc::new(RefCell::new(None)), events_to_unsubscribe: Rc::new(RefCell::new(Default::default())), - request_animation_frame_id: Cell::new(None), + frame: Default::default(), resize_observer: Default::default(), } } @@ -125,9 +122,9 @@ impl WebRunner { pub fn destroy(&self) { self.unsubscribe_from_all_events(); - if let Some(id) = self.request_animation_frame_id.get() { + if let Some(frame) = self.frame.take() { let window = web_sys::window().unwrap(); - window.cancel_animation_frame(id).ok(); + window.cancel_animation_frame(frame.id).ok(); } if let Some(runner) = self.runner.replace(None) { @@ -202,16 +199,33 @@ impl WebRunner { Ok(()) } + /// Request an animation frame from the browser in which we can perform a paint. + /// + /// It is safe to call `request_animation_frame` multiple times in quick succession, + /// this function guarantees that only one animation frame is scheduled at a time. pub(crate) fn request_animation_frame(&self) -> Result<(), wasm_bindgen::JsValue> { + if self.frame.borrow().is_some() { + // there is already an animation frame in flight + return Ok(()); + } + let window = web_sys::window().unwrap(); let closure = Closure::once({ let runner_ref = self.clone(); - move || events::paint_and_schedule(&runner_ref) + move || { + // We can paint now, so clear the animation frame. + // This drops the `closure` and allows another + // animation frame to be scheduled + let _ = runner_ref.frame.take(); + events::paint_and_schedule(&runner_ref) + } }); let id = window.request_animation_frame(closure.as_ref().unchecked_ref())?; - self.request_animation_frame_id.set(Some(id)); - closure.forget(); // We must forget it, or else the callback is canceled on drop + self.frame.borrow_mut().replace(AnimationFrameRequest { + id, + _closure: closure, + }); Ok(()) } @@ -232,6 +246,16 @@ impl WebRunner { // ---------------------------------------------------------------------------- +// https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/closure/struct.Closure.html#using-fnonce-and-closureonce-with-requestanimationframe +struct AnimationFrameRequest { + /// Represents the ID of a frame in flight. + id: i32, + + /// The callback given to `request_animation_frame`, stored here both to prevent it + /// from being canceled, and from having to `.forget()` it. + _closure: Closure Result<(), JsValue>>, +} + struct ResizeObserverContext { resize_observer: web_sys::ResizeObserver, closure: Closure, From 6f59a14c4d834c0c2df4f576b025c2297815e1d8 Mon Sep 17 00:00:00 2001 From: Varphone Wong Date: Tue, 28 May 2024 15:13:43 +0800 Subject: [PATCH 0116/1202] Add `Options::reduce_texture_memory` to free up RAM (#4431) ## Summary This PR introduces a new configuration option `reduce_texture_memory` in `egui`. ## Changes - Added `reduce_texture_memory` option in `egui`. When set to `true`, `egui` will discard the loaded image data after the texture is uploaded to the GPU, reducing memory usage. This is beneficial when handling a large number of images and retaining the image data is unnecessary, potentially saving substantial memory. However, this makes it impossible to serialize the loaded images or render on non-GPU systems. Default is `false`. ## Impact This new configuration option gives users more control over their memory usage, especially when dealing with a large number or large resolution of images. It allows users to optimize their applications based on their specific needs and constraints. --- crates/egui/src/load/texture_loader.rs | 11 +++++++++++ crates/egui/src/memory.rs | 17 +++++++++++++++++ crates/egui_demo_app/src/apps/image_viewer.rs | 5 +++++ 3 files changed, 33 insertions(+) diff --git a/crates/egui/src/load/texture_loader.rs b/crates/egui/src/load/texture_loader.rs index 89d616e4760d..26c254d4d0b7 100644 --- a/crates/egui/src/load/texture_loader.rs +++ b/crates/egui/src/load/texture_loader.rs @@ -28,6 +28,17 @@ impl TextureLoader for DefaultTextureLoader { let handle = ctx.load_texture(uri, image, texture_options); let texture = SizedTexture::from_handle(&handle); cache.insert((uri.into(), texture_options), handle); + let reduce_texture_memory = ctx.options(|o| o.reduce_texture_memory); + if reduce_texture_memory { + let loaders = ctx.loaders(); + loaders.include.forget(uri); + for loader in loaders.bytes.lock().iter().rev() { + loader.forget(uri); + } + for loader in loaders.image.lock().iter().rev() { + loader.forget(uri); + } + } Ok(TexturePoll::Ready { texture }) } } diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 8a642adf28ec..ed2a7024ba15 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -235,6 +235,19 @@ pub struct Options { /// Controls the speed at which we zoom in when doing ctrl/cmd + scroll. pub scroll_zoom_speed: f32, + + /// If `true`, `egui` will discard the loaded image data after + /// the texture is loaded onto the GPU to reduce memory usage. + /// + /// In modern GPU rendering, the texture data is not required after the texture is loaded. + /// + /// This is beneficial when using a large number or resolution of images and there is no need to + /// retain the image data, potentially saving a significant amount of memory. + /// + /// The drawback is that it becomes impossible to serialize the loaded images or render in non-GPU systems. + /// + /// Default is `false`. + pub reduce_texture_memory: bool, } impl Default for Options { @@ -260,6 +273,7 @@ impl Default for Options { // Input: line_scroll_speed, scroll_zoom_speed: 1.0 / 200.0, + reduce_texture_memory: false, } } } @@ -279,6 +293,7 @@ impl Options { line_scroll_speed, scroll_zoom_speed, + reduce_texture_memory, } = self; use crate::Widget as _; @@ -297,6 +312,8 @@ impl Options { ); ui.checkbox(warn_on_id_clash, "Warn if two widgets have the same Id"); + + ui.checkbox(reduce_texture_memory, "Reduce texture memory"); }); use crate::containers::*; diff --git a/crates/egui_demo_app/src/apps/image_viewer.rs b/crates/egui_demo_app/src/apps/image_viewer.rs index cd834424aa2e..ad7a8448640c 100644 --- a/crates/egui_demo_app/src/apps/image_viewer.rs +++ b/crates/egui_demo_app/src/apps/image_viewer.rs @@ -184,6 +184,11 @@ impl eframe::App for ImageViewer { ui.add_space(5.0); ui.label("Aspect ratio is maintained by scaling both sides as necessary"); ui.checkbox(&mut self.maintain_aspect_ratio, "Maintain aspect ratio"); + + // forget all images + if ui.button("Forget all images").clicked() { + ui.ctx().forget_all_images(); + } }); egui::CentralPanel::default().show(ctx, |ui| { From 4b59c6d414396a6971a12f717cacf4fa0668c7e5 Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Tue, 28 May 2024 09:21:35 +0200 Subject: [PATCH 0117/1202] Fix `Ui::scroll_with_delta` only scrolling if the `ScrollArea` is focused (#4303) This introduces the boolean field force_current_scroll_area to InputState which will be set when scroll_with_delta is called, causing the ScrollArea to skip the check whether it is focused and always consume the smooth scroll delta. * Closes #2783 * Related to #4295 --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/containers/scroll_area.rs | 56 ++++++++++++---------- crates/egui/src/frame_state.rs | 16 ++++++- crates/egui/src/ui.rs | 7 ++- crates/egui_demo_lib/src/demo/scrolling.rs | 21 ++++++++ 4 files changed, 73 insertions(+), 27 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 700c20402539..229870db9d47 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -783,7 +783,14 @@ impl Prepared { let content_size = content_ui.min_size(); + let scroll_delta = content_ui + .ctx() + .frame_state_mut(|state| std::mem::take(&mut state.scroll_delta)); + for d in 0..2 { + // FrameState::scroll_delta is inverted from the way we apply the delta, so we need to negate it. + let mut delta = -scroll_delta[d]; + // We always take both scroll targets regardless of which scroll axes are enabled. This // is to avoid them leaking to other scroll areas. let scroll_target = content_ui @@ -791,7 +798,7 @@ impl Prepared { .frame_state_mut(|state| state.scroll_target[d].take()); if scroll_enabled[d] { - if let Some((target_range, align)) = scroll_target { + delta += if let Some((target_range, align)) = scroll_target { let min = content_ui.min_rect().min[d]; let clip_rect = content_ui.clip_rect(); let visible_range = min..=min + clip_rect.size()[d]; @@ -800,7 +807,7 @@ impl Prepared { let clip_end = clip_rect.max[d]; let mut spacing = ui.spacing().item_spacing[d]; - let delta = if let Some(align) = align { + if let Some(align) = align { let center_factor = align.to_factor(); let offset = @@ -817,31 +824,32 @@ impl Prepared { } else { // Ui is already in view, no need to adjust scroll. 0.0 - }; + } + } else { + 0.0 + }; - if delta != 0.0 { - let target_offset = state.offset[d] + delta; + if delta != 0.0 { + let target_offset = state.offset[d] + delta; - if !animated { - state.offset[d] = target_offset; - } else if let Some(animation) = &mut state.offset_target[d] { - // For instance: the user is continuously calling `ui.scroll_to_cursor`, - // so we don't want to reset the animation, but perhaps update the target: - animation.target_offset = target_offset; - } else { - // The further we scroll, the more time we take. - // TODO(emilk): let users configure this in `Style`. - let now = ui.input(|i| i.time); - let points_per_second = 1000.0; - let animation_duration = - (delta.abs() / points_per_second).clamp(0.1, 0.3); - state.offset_target[d] = Some(ScrollTarget { - animation_time_span: (now, now + animation_duration as f64), - target_offset, - }); - } - ui.ctx().request_repaint(); + if !animated { + state.offset[d] = target_offset; + } else if let Some(animation) = &mut state.offset_target[d] { + // For instance: the user is continuously calling `ui.scroll_to_cursor`, + // so we don't want to reset the animation, but perhaps update the target: + animation.target_offset = target_offset; + } else { + // The further we scroll, the more time we take. + // TODO(emilk): let users configure this in `Style`. + let now = ui.input(|i| i.time); + let points_per_second = 1000.0; + let animation_duration = (delta.abs() / points_per_second).clamp(0.1, 0.3); + state.offset_target[d] = Some(ScrollTarget { + animation_time_span: (now, now + animation_duration as f64), + target_offset, + }); } + ui.ctx().request_repaint(); } } } diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index 5f966d117a0a..e1414bba1661 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -38,9 +38,20 @@ pub(crate) struct FrameState { /// Initialized to `None` at the start of each frame. pub(crate) tooltip_state: Option, - /// horizontal, vertical + /// The current scroll area should scroll to this range (horizontal, vertical). pub(crate) scroll_target: [Option<(Rangef, Option)>; 2], + /// The current scroll area should scroll by this much. + /// + /// The delta dictates how the _content_ should move. + /// + /// A positive X-value indicates the content is being moved right, + /// as when swiping right on a touch-screen or track-pad with natural scrolling. + /// + /// A positive Y-value indicates the content is being moved down, + /// as when swiping down on a touch-screen or track-pad with natural scrolling. + pub(crate) scroll_delta: Vec2, + #[cfg(feature = "accesskit")] pub(crate) accesskit_state: Option, @@ -63,6 +74,7 @@ impl Default for FrameState { used_by_panels: Rect::NAN, tooltip_state: None, scroll_target: [None, None], + scroll_delta: Vec2::default(), #[cfg(feature = "accesskit")] accesskit_state: None, highlight_this_frame: Default::default(), @@ -84,6 +96,7 @@ impl FrameState { used_by_panels, tooltip_state, scroll_target, + scroll_delta, #[cfg(feature = "accesskit")] accesskit_state, highlight_this_frame, @@ -99,6 +112,7 @@ impl FrameState { *used_by_panels = Rect::NOTHING; *tooltip_state = None; *scroll_target = [None, None]; + *scroll_delta = Vec2::default(); #[cfg(debug_assertions)] { diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 00b7beef6e42..7343e9ea723f 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -1075,6 +1075,8 @@ impl Ui { /// A positive Y-value indicates the content is being moved down, /// as when swiping down on a touch-screen or track-pad with natural scrolling. /// + /// If this is called multiple times per frame for the same [`ScrollArea`], the deltas will be summed. + /// /// /// See also: [`Response::scroll_to_me`], [`Ui::scroll_to_rect`], [`Ui::scroll_to_cursor`] /// /// ``` @@ -1093,8 +1095,9 @@ impl Ui { /// # }); /// ``` pub fn scroll_with_delta(&self, delta: Vec2) { - self.ctx() - .input_mut(|input| input.smooth_scroll_delta += delta); + self.ctx().frame_state_mut(|state| { + state.scroll_delta += delta; + }); } } diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index 9c490d23d8a7..71d47f9d84cf 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -236,6 +236,7 @@ struct ScrollTo { track_item: usize, tack_item_align: Option, offset: f32, + delta: f32, } impl Default for ScrollTo { @@ -244,6 +245,7 @@ impl Default for ScrollTo { track_item: 25, tack_item_align: Some(Align::Center), offset: 0.0, + delta: 64.0, } } } @@ -258,6 +260,7 @@ impl super::View for ScrollTo { let mut go_to_scroll_offset = false; let mut scroll_top = false; let mut scroll_bottom = false; + let mut scroll_delta = None; ui.horizontal(|ui| { ui.label("Scroll to a specific item index:"); @@ -294,6 +297,20 @@ impl super::View for ScrollTo { scroll_bottom |= ui.button("Scroll to bottom").clicked(); }); + ui.horizontal(|ui| { + ui.label("Scroll by"); + DragValue::new(&mut self.delta) + .speed(1.0) + .suffix("px") + .ui(ui); + if ui.button("⬇").clicked() { + scroll_delta = Some(self.delta * Vec2::UP); // scroll down (move contents up) + } + if ui.button("⬆").clicked() { + scroll_delta = Some(self.delta * Vec2::DOWN); // scroll up (move contents down) + } + }); + let mut scroll_area = ScrollArea::vertical().max_height(200.0).auto_shrink(false); if go_to_scroll_offset { scroll_area = scroll_area.vertical_scroll_offset(self.offset); @@ -305,6 +322,10 @@ impl super::View for ScrollTo { if scroll_top { ui.scroll_to_cursor(Some(Align::TOP)); } + if let Some(scroll_delta) = scroll_delta { + ui.scroll_with_delta(scroll_delta); + } + ui.vertical(|ui| { for item in 1..=num_items { if track_item && item == self.track_item { From bcd91f27a12530e42c73828a1520f74444a4a516 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Tue, 28 May 2024 13:10:41 +0200 Subject: [PATCH 0118/1202] Add support for text truncation to `egui::Style` (#4556) * Closes #4473 This PR introduce `Style::wrap_mode`, which adds support for text truncation in addition to text wrapping. This PR also update some width calculation of the ComboBox. #### Core - Add `egui::TextWrapMode` (pure enum with `Extend`, `Wrap`, `Truncate`) - Add `Style::wrap_mode: Option` - **DEPRECATED**: `Style::wrap`, use `Style::wrap_mode` instead. - Add `Ui::wrap_mode()` to return the wrap mode to use in the current ui. If specified in `Style`, return it. Otherwise, return `TextWrapMode::Wrap` for vertical layout and wrapping horizontal layout, and `TextWrapMode::Extend` otherwise. - **DEPRECATED**: `Ui::wrap_text()`, use `Ui::wrap_mode` instead. #### Widget - Update the width calculation of the `ComboBox` button (_not_ its popup menu). - Now, `ComboBox::width()` (defaulting to `Spacing::combo_width`) is always considered a minimum width and will extend the `Ui`, regardless of the selected text width and wrap mode. - Introduce `ComboBox::wrap_mode`, which overrides `Ui::wrap_mode` for the selected text layout. - Note: since `ComboBox` uses `ui.horizontal` internally, the default wrap mode is always `TextWrapMode::Extend`, regardless of the caller's `Ui`'s layout. - The `ComboBox` button no longer extend to `ui.available_width()` with wrapping is enabled. - **BREAKING**: `ComboBox::wrap()` no longer has a `bool` argument and is now a short-hand for `ComboBox::wrap_mode(TextWrapMode::Wrap)`. - Added `ComboBox::truncate()` as short-hand for `ComboBox::wrap_mode(TextWrapMode::Truncate)`. - Update `Label` - Add `Label::wrap_mode()` to specify the text wrap mode. - **BREAKING**: `Label::wrap()` no longer has a `bool` argument and is now a short-hand for `Label::wrap_mode(TextWrapMode::Wrap)`. - **BREAKING**: `Label::truncate()` no longer has a `bool` argument and is now a short-hand for `Label::wrap_mode(TextWrapMode::Truncate)`. - Update `Button` - Add `Button::wrap_mode()` to specify the text wrap mode. - **BREAKING**: `Button::wrap()` no longer has a `bool` argument and is now a short-hand for `Button::wrap_mode(TextWrapMode::Wrap)`. - Added `Button::truncate()` as short-hand for `Button::wrap_mode(TextWrapMode::Truncate)`. #### Low-level - **BREAKING**: `WidgetText::into_galley()` now takes an `Option` instead of a `Option` argument. - **BREAKING**: `WidgetText::into_galley_impl(()` now takes a `TextWrapping` argument instead of `wrap: bool` and `availalbe_width: f32` arguments. --------- Co-authored-by: Emil Ernerfeldt --- .../egui/src/containers/collapsing_header.rs | 8 +- crates/egui/src/containers/combo_box.rs | 90 ++++++++++--------- crates/egui/src/containers/window.rs | 7 +- crates/egui/src/debug_text.rs | 6 +- crates/egui/src/introspection.rs | 2 +- crates/egui/src/lib.rs | 4 +- crates/egui/src/menu.rs | 15 +++- crates/egui/src/style.rs | 26 ++++-- crates/egui/src/ui.rs | 39 ++++++-- crates/egui/src/widget_text.rs | 30 +++---- crates/egui/src/widgets/button.rs | 43 ++++++--- crates/egui/src/widgets/drag_value.rs | 2 +- crates/egui/src/widgets/label.rs | 63 +++++-------- crates/egui/src/widgets/progress_bar.rs | 7 +- crates/egui/src/widgets/slider.rs | 3 +- crates/egui/src/widgets/text_edit/builder.rs | 14 ++- crates/egui_demo_app/src/backend_panel.rs | 2 +- .../src/demo/demo_app_windows.rs | 2 +- crates/egui_demo_lib/src/demo/frame_demo.rs | 2 +- crates/egui_demo_lib/src/demo/layout_test.rs | 2 +- .../src/demo/misc_demo_window.rs | 2 +- crates/egui_demo_lib/src/demo/pan_zoom.rs | 3 +- crates/egui_demo_lib/src/demo/plot_demo.rs | 2 +- crates/egui_demo_lib/src/demo/scrolling.rs | 2 +- crates/egui_demo_lib/src/demo/table_demo.rs | 9 +- .../egui_demo_lib/src/demo/widget_gallery.rs | 2 +- crates/egui_plot/src/axis.rs | 9 +- crates/egui_plot/src/items/mod.rs | 10 ++- crates/epaint/src/text/text_layout_types.rs | 37 +++++++- 29 files changed, 281 insertions(+), 162 deletions(-) diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index 7c541a1a2f73..87754adfbf84 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -506,8 +506,12 @@ impl CollapsingHeader { let available = ui.available_rect_before_wrap(); let text_pos = available.min + vec2(ui.spacing().indent, 0.0); let wrap_width = available.right() - text_pos.x; - let wrap = Some(false); - let galley = text.into_galley(ui, wrap, wrap_width, TextStyle::Button); + let galley = text.into_galley( + ui, + Some(TextWrapMode::Extend), + wrap_width, + TextStyle::Button, + ); let text_max_x = text_pos.x + galley.size().x; let mut desired_width = text_max_x + button_padding.x - available.left(); diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index 828ee140aa5d..db5a9e3e8bdc 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -5,7 +5,7 @@ use crate::{style::WidgetVisuals, *}; #[allow(unused_imports)] // Documentation use crate::style::Spacing; -/// Indicate whether or not a popup will be shown above or below the box. +/// Indicate whether a popup will be shown above or below the box. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum AboveOrBelow { Above, @@ -40,7 +40,7 @@ pub struct ComboBox { width: Option, height: Option, icon: Option, - wrap_enabled: bool, + wrap_mode: Option, } impl ComboBox { @@ -53,7 +53,7 @@ impl ComboBox { width: None, height: None, icon: None, - wrap_enabled: false, + wrap_mode: None, } } @@ -67,7 +67,7 @@ impl ComboBox { width: None, height: None, icon: None, - wrap_enabled: false, + wrap_mode: None, } } @@ -80,7 +80,7 @@ impl ComboBox { width: None, height: None, icon: None, - wrap_enabled: false, + wrap_mode: None, } } @@ -148,10 +148,29 @@ impl ComboBox { self } - /// Controls whether text wrap is used for the selected text + /// Controls the wrap mode used for the selected text. + /// + /// By default, [`Ui::wrap_mode`] will be used, which can be overridden with [`Style::wrap_mode`]. + /// + /// Note that any `\n` in the text will always produce a new line. #[inline] - pub fn wrap(mut self, wrap: bool) -> Self { - self.wrap_enabled = wrap; + pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self { + self.wrap_mode = Some(wrap_mode); + self + } + + /// Set [`Self::wrap_mode`] to [`TextWrapMode::Wrap`]. + #[inline] + pub fn wrap(mut self) -> Self { + self.wrap_mode = Some(TextWrapMode::Wrap); + + self + } + + /// Set [`Self::wrap_mode`] to [`TextWrapMode::Truncate`]. + #[inline] + pub fn truncate(mut self) -> Self { + self.wrap_mode = Some(TextWrapMode::Truncate); self } @@ -178,7 +197,7 @@ impl ComboBox { width, height, icon, - wrap_enabled, + wrap_mode, } = self; let button_id = ui.make_persistent_id(id_source); @@ -190,7 +209,7 @@ impl ComboBox { selected_text, menu_contents, icon, - wrap_enabled, + wrap_mode, (width, height), ); if let Some(label) = label { @@ -253,13 +272,14 @@ impl ComboBox { } } +#[allow(clippy::too_many_arguments)] fn combo_box_dyn<'c, R>( ui: &mut Ui, button_id: Id, selected_text: WidgetText, menu_contents: Box R + 'c>, icon: Option, - wrap_enabled: bool, + wrap_mode: Option, (width, height): (Option, Option), ) -> InnerResponse> { let popup_id = button_id.with("popup"); @@ -277,45 +297,33 @@ fn combo_box_dyn<'c, R>( AboveOrBelow::Above }; + let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode()); + let margin = ui.spacing().button_padding; let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| { let icon_spacing = ui.spacing().icon_spacing; - // We don't want to change width when user selects something new - let full_minimum_width = if wrap_enabled { - // Currently selected value's text will be wrapped if needed, so occupy the available width. - ui.available_width() - } else { - // Occupy at least the minimum width assigned to ComboBox. - let width = width.unwrap_or_else(|| ui.spacing().combo_width); - width - 2.0 * margin.x - }; let icon_size = Vec2::splat(ui.spacing().icon_width); - let wrap_width = if wrap_enabled { - // Use the available width, currently selected value's text will be wrapped if exceeds this value. - ui.available_width() - icon_spacing - icon_size.x - } else { - // Use all the width necessary to display the currently selected value's text. - f32::INFINITY - }; - let galley = - selected_text.into_galley(ui, Some(wrap_enabled), wrap_width, TextStyle::Button); + // The combo box selected text will always have this minimum width. + // Note: the `ComboBox::width()` if set or `Spacing::combo_width` are considered as the + // minimum overall width, regardless of the wrap mode. + let minimum_width = width.unwrap_or_else(|| ui.spacing().combo_width) - 2.0 * margin.x; - // The width necessary to contain the whole widget with the currently selected value's text. - let width = if wrap_enabled { - full_minimum_width + // width against which to lay out the selected text + let wrap_width = if wrap_mode == TextWrapMode::Extend { + // Use all the width necessary to display the currently selected value's text. + f32::INFINITY } else { - // Occupy at least the minimum width needed to contain the widget with the currently selected value's text. - galley.size().x + icon_spacing + icon_size.x + // Use the available width, currently selected value's text will be wrapped if exceeds this value. + ui.available_width() - icon_spacing - icon_size.x }; - // Case : wrap_enabled : occupy all the available width. - // Case : !wrap_enabled : occupy at least the minimum width assigned to Slider and ComboBox, - // increase if the currently selected value needs additional horizontal space to fully display its text (up to wrap_width (f32::INFINITY)). - let width = width.at_least(full_minimum_width); - let height = galley.size().y.max(icon_size.y); + let galley = selected_text.into_galley(ui, Some(wrap_mode), wrap_width, TextStyle::Button); + + let actual_width = (galley.size().x + icon_spacing + icon_size.x).at_least(minimum_width); + let actual_height = galley.size().y.max(icon_size.y); - let (_, rect) = ui.allocate_space(Vec2::new(width, height)); + let (_, rect) = ui.allocate_space(Vec2::new(actual_width, actual_height)); let button_rect = ui.min_rect().expand2(ui.spacing().button_padding); let response = ui.interact(button_rect, button_id, Sense::click()); // response.active |= is_popup_open; @@ -371,7 +379,7 @@ fn combo_box_dyn<'c, R>( // result in labels that wrap very early. // Instead, we turn it off by default so that the labels // expand the width of the menu. - ui.style_mut().wrap = Some(false); + ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); menu_contents(ui) }) .inner diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 0f336743bd85..9bf3924e0f2c 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -1028,7 +1028,12 @@ fn show_title_bar( collapsing.show_default_button_with_size(ui, button_size); } - let title_galley = title.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Heading); + let title_galley = title.into_galley( + ui, + Some(crate::TextWrapMode::Extend), + f32::INFINITY, + TextStyle::Heading, + ); let minimum_width = if collapsible || show_close_button { // If at least one button is shown we make room for both buttons (since title is centered): diff --git a/crates/egui/src/debug_text.rs b/crates/egui/src/debug_text.rs index b2551de9ddf9..672bc20c6a63 100644 --- a/crates/egui/src/debug_text.rs +++ b/crates/egui/src/debug_text.rs @@ -12,7 +12,7 @@ use crate::*; /// /// This is a built-in plugin in egui, /// meaning [`Context`] calls this from its `Default` implementation, -/// so this i marked as `pub(crate)`. +/// so this is marked as `pub(crate)`. pub(crate) fn register(ctx: &Context) { ctx.on_end_frame("debug_text", std::sync::Arc::new(State::end_frame)); } @@ -105,13 +105,11 @@ impl State { { // Paint `text` to right of `pos`: - let wrap = true; let available_width = ctx.screen_rect().max.x - pos.x; let galley = text.into_galley_impl( ctx, &ctx.style(), - wrap, - available_width, + text::TextWrapping::wrap_at_width(available_width), font_id.clone().into(), Align::TOP, ); diff --git a/crates/egui/src/introspection.rs b/crates/egui/src/introspection.rs index f71e82194be7..d853b1c6196d 100644 --- a/crates/egui/src/introspection.rs +++ b/crates/egui/src/introspection.rs @@ -129,7 +129,7 @@ impl Widget for &epaint::stats::PaintStats { } fn label(ui: &mut Ui, alloc_info: &epaint::stats::AllocInfo, what: &str) -> Response { - ui.add(Label::new(alloc_info.format(what)).wrap(false)) + ui.add(Label::new(alloc_info.format(what)).wrap_mode(TextWrapMode::Extend)) } impl Widget for &mut epaint::TessellationOptions { diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index c9d9b37ce48b..517ee4c8ca5f 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -338,6 +338,7 @@ //! ## Code snippets //! //! ``` +//! # use egui::TextWrapMode; //! # egui::__run_test_ui(|ui| { //! # let mut some_bool = true; //! // Miscellaneous tips and tricks @@ -358,7 +359,7 @@ //! ui.scope(|ui| { //! ui.visuals_mut().override_text_color = Some(egui::Color32::RED); //! ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); -//! ui.style_mut().wrap = Some(false); +//! ui.style_mut().wrap_mode = Some(TextWrapMode::Truncate); //! //! ui.label("This text will be red, monospace, and won't wrap to a new line"); //! }); // the temporary settings are reverted here @@ -451,6 +452,7 @@ pub use { Key, }, drag_and_drop::DragAndDrop, + epaint::text::TextWrapMode, grid::Grid, id::{Id, IdMap}, input_state::{InputState, MultiTouchInfo, PointerState}, diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 84fd1b2e8e3b..29441d8a74cb 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -479,11 +479,20 @@ impl SubMenuButton { let button_padding = ui.spacing().button_padding; let total_extra = button_padding + button_padding; let text_available_width = ui.available_width() - total_extra.x; - let text_galley = - text.into_galley(ui, Some(true), text_available_width, text_style.clone()); + let text_galley = text.into_galley( + ui, + Some(TextWrapMode::Wrap), + text_available_width, + text_style.clone(), + ); let icon_available_width = text_available_width - text_galley.size().x; - let icon_galley = icon.into_galley(ui, Some(true), icon_available_width, text_style); + let icon_galley = icon.into_galley( + ui, + Some(TextWrapMode::Wrap), + icon_available_width, + text_style, + ); let text_and_icon_size = Vec2::new( text_galley.size().x + icon_galley.size().x, text_galley.size().y.max(icon_galley.size().y), diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index d2d7ce991882..f2410747dfb4 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -182,15 +182,25 @@ pub struct Style { /// The style to use for [`DragValue`] text. pub drag_value_text_style: TextStyle, - /// If set, labels buttons wtc will use this to determine whether or not - /// to wrap the text at the right edge of the [`Ui`] they are in. - /// By default this is `None`. + /// If set, labels, buttons, etc. will use this to determine whether to wrap the text at the + /// right edge of the [`Ui`] they are in. By default, this is `None`. /// - /// * `None`: follow layout - /// * `Some(true)`: default on - /// * `Some(false)`: default off + /// **Note**: this API is deprecated, use `wrap_mode` instead. + /// + /// * `None`: use `wrap_mode` instead + /// * `Some(true)`: wrap mode defaults to [`crate::TextWrapMode::Wrap`] + /// * `Some(false)`: wrap mode defaults to [`crate::TextWrapMode::Extend`] + #[deprecated = "Use wrap_mode instead"] pub wrap: Option, + /// If set, labels, buttons, etc. will use this to determine whether to wrap or truncate the + /// text at the right edge of the [`Ui`] they are in, or to extend it. By default, this is + /// `None`. + /// + /// * `None`: follow layout (with may wrap) + /// * `Some(mode)`: use the specified mode as default + pub wrap_mode: Option, + /// Sizes and distances between widgets pub spacing: Spacing, @@ -1026,12 +1036,14 @@ pub fn default_text_styles() -> BTreeMap { impl Default for Style { fn default() -> Self { + #[allow(deprecated)] Self { override_font_id: None, override_text_style: None, text_styles: default_text_styles(), drag_value_text_style: TextStyle::Button, wrap: None, + wrap_mode: None, spacing: Spacing::default(), interaction: Interaction::default(), visuals: Visuals::default(), @@ -1317,12 +1329,14 @@ use crate::{widgets::*, Ui}; impl Style { pub fn ui(&mut self, ui: &mut crate::Ui) { + #[allow(deprecated)] let Self { override_font_id, override_text_style, text_styles, drag_value_text_style, wrap: _, + wrap_mode: _, spacing, interaction, visuals, diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 7343e9ea723f..f3227109e367 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -329,20 +329,45 @@ impl Ui { self.placer.layout() } - /// Should text wrap in this [`Ui`]? + /// Which wrap mode should the text use in this [`Ui`]? /// - /// This is determined first by [`Style::wrap`], and then by the layout of this [`Ui`]. - pub fn wrap_text(&self) -> bool { - if let Some(wrap) = self.style.wrap { - wrap + /// This is determined first by [`Style::wrap_mode`], and then by the layout of this [`Ui`]. + pub fn wrap_mode(&self) -> TextWrapMode { + #[allow(deprecated)] + if let Some(wrap_mode) = self.style.wrap_mode { + wrap_mode + } + // `wrap` handling for backward compatibility + else if let Some(wrap) = self.style.wrap { + if wrap { + TextWrapMode::Wrap + } else { + TextWrapMode::Extend + } } else if let Some(grid) = self.placer.grid() { - grid.wrap_text() + if grid.wrap_text() { + TextWrapMode::Wrap + } else { + TextWrapMode::Extend + } } else { let layout = self.layout(); - layout.is_vertical() || layout.is_horizontal() && layout.main_wrap() + if layout.is_vertical() || layout.is_horizontal() && layout.main_wrap() { + TextWrapMode::Wrap + } else { + TextWrapMode::Extend + } } } + /// Should text wrap in this [`Ui`]? + /// + /// This is determined first by [`Style::wrap_mode`], and then by the layout of this [`Ui`]. + #[deprecated = "Use `wrap_mode` instead"] + pub fn wrap_text(&self) -> bool { + self.wrap_mode() == TextWrapMode::Wrap + } + /// Create a painter for a sub-region of this Ui. /// /// The clip-rect of the returned [`Painter`] will be the intersection diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index 57643ae45765..29d59ff18d81 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -1,8 +1,8 @@ use std::{borrow::Cow, sync::Arc}; use crate::{ - text::LayoutJob, Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, Ui, - Visuals, + text::{LayoutJob, TextWrapping}, + Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, TextWrapMode, Ui, Visuals, }; /// Text and optional style choices for it. @@ -640,47 +640,39 @@ impl WidgetText { /// Layout with wrap mode based on the containing [`Ui`]. /// - /// wrap: override for [`Ui::wrap_text`]. + /// `wrap_mode`: override for [`Ui::wrap_mode`] pub fn into_galley( self, ui: &Ui, - wrap: Option, + wrap_mode: Option, available_width: f32, fallback_font: impl Into, ) -> Arc { - let wrap = wrap.unwrap_or_else(|| ui.wrap_text()); let valign = ui.layout().vertical_align(); let style = ui.style(); - self.into_galley_impl( - ui.ctx(), - style, - wrap, - available_width, - fallback_font.into(), - valign, - ) + let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode()); + let text_wrapping = TextWrapping::from_wrap_mode_and_width(wrap_mode, available_width); + + self.into_galley_impl(ui.ctx(), style, text_wrapping, fallback_font.into(), valign) } pub fn into_galley_impl( self, ctx: &crate::Context, style: &Style, - wrap: bool, - available_width: f32, + text_wrapping: TextWrapping, fallback_font: FontSelection, default_valign: Align, ) -> Arc { - let wrap_width = if wrap { available_width } else { f32::INFINITY }; - match self { Self::RichText(text) => { let mut layout_job = text.into_layout_job(style, fallback_font, default_valign); - layout_job.wrap.max_width = wrap_width; + layout_job.wrap = text_wrapping; ctx.fonts(|f| f.layout_job(layout_job)) } Self::LayoutJob(mut job) => { - job.wrap.max_width = wrap_width; + job.wrap = text_wrapping; ctx.fonts(|f| f.layout_job(job)) } Self::Galley(galley) => galley, diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 76a621250455..4b5aa6b6bf54 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -23,7 +23,7 @@ pub struct Button<'a> { image: Option>, text: Option, shortcut_text: WidgetText, - wrap: Option, + wrap_mode: Option, /// None means default for interact fill: Option, @@ -58,7 +58,7 @@ impl<'a> Button<'a> { text, image, shortcut_text: Default::default(), - wrap: None, + wrap_mode: None, fill: None, stroke: None, sense: Sense::click(), @@ -70,16 +70,29 @@ impl<'a> Button<'a> { } } - /// If `true`, the text will wrap to stay within the max width of the [`Ui`]. + /// Set the wrap mode for the text. /// - /// By default [`Self::wrap`] will be true in vertical layouts - /// and horizontal layouts with wrapping, - /// and false on non-wrapping horizontal layouts. + /// By default, [`Ui::wrap_mode`] will be used, which can be overridden with [`Style::wrap_mode`]. /// /// Note that any `\n` in the text will always produce a new line. #[inline] - pub fn wrap(mut self, wrap: bool) -> Self { - self.wrap = Some(wrap); + pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self { + self.wrap_mode = Some(wrap_mode); + self + } + + /// Set [`Self::wrap_mode`] to [`TextWrapMode::Wrap`]. + #[inline] + pub fn wrap(mut self) -> Self { + self.wrap_mode = Some(TextWrapMode::Wrap); + + self + } + + /// Set [`Self::wrap_mode`] to [`TextWrapMode::Truncate`]. + #[inline] + pub fn truncate(mut self) -> Self { + self.wrap_mode = Some(TextWrapMode::Truncate); self } @@ -165,7 +178,7 @@ impl Widget for Button<'_> { text, image, shortcut_text, - wrap, + wrap_mode, fill, stroke, sense, @@ -211,9 +224,15 @@ impl Widget for Button<'_> { } let galley = - text.map(|text| text.into_galley(ui, wrap, text_wrap_width, TextStyle::Button)); - let shortcut_galley = (!shortcut_text.is_empty()) - .then(|| shortcut_text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button)); + text.map(|text| text.into_galley(ui, wrap_mode, text_wrap_width, TextStyle::Button)); + let shortcut_galley = (!shortcut_text.is_empty()).then(|| { + shortcut_text.into_galley( + ui, + Some(TextWrapMode::Extend), + f32::INFINITY, + TextStyle::Button, + ) + }); let mut desired_size = Vec2::ZERO; if image.is_some() { diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index 8a7f19028b66..70b957ec1511 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -521,7 +521,7 @@ impl<'a> Widget for DragValue<'a> { RichText::new(format!("{}{}{}", prefix, value_text.clone(), suffix)) .text_style(text_style), ) - .wrap(false) + .wrap_mode(TextWrapMode::Extend) .sense(Sense::click_and_drag()) .min_size(ui.spacing().interact_size); // TODO(emilk): find some more generic solution to `min_size` diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index f09a17baeb25..de6cc98dfe2d 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -9,10 +9,11 @@ use self::text_selection::LabelSelectionState; /// Usually it is more convenient to use [`Ui::label`]. /// /// ``` +/// # use egui::TextWrapMode; /// # egui::__run_test_ui(|ui| { /// ui.label("Equivalent"); /// ui.add(egui::Label::new("Equivalent")); -/// ui.add(egui::Label::new("With Options").wrap(false)); +/// ui.add(egui::Label::new("With Options").truncate()); /// ui.label(egui::RichText::new("With formatting").underline()); /// # }); /// ``` @@ -22,8 +23,7 @@ use self::text_selection::LabelSelectionState; #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] pub struct Label { text: WidgetText, - wrap: Option, - truncate: bool, + wrap_mode: Option, sense: Option, selectable: Option, } @@ -32,8 +32,7 @@ impl Label { pub fn new(text: impl Into) -> Self { Self { text: text.into(), - wrap: None, - truncate: false, + wrap_mode: None, sense: None, selectable: None, } @@ -43,37 +42,29 @@ impl Label { self.text.text() } - /// If `true`, the text will wrap to stay within the max width of the [`Ui`]. + /// Set the wrap mode for the text. /// - /// Calling `wrap` will override [`Self::truncate`]. - /// - /// By default [`Self::wrap`] will be `true` in vertical layouts - /// and horizontal layouts with wrapping, - /// and `false` on non-wrapping horizontal layouts. + /// By default, [`Ui::wrap_mode`] will be used, which can be overridden with [`Style::wrap_mode`]. /// /// Note that any `\n` in the text will always produce a new line. - /// - /// You can also use [`crate::Style::wrap`]. #[inline] - pub fn wrap(mut self, wrap: bool) -> Self { - self.wrap = Some(wrap); - self.truncate = false; + pub fn wrap_mode(mut self, wrap_mode: TextWrapMode) -> Self { + self.wrap_mode = Some(wrap_mode); self } - /// If `true`, the text will stop at the max width of the [`Ui`], - /// and what doesn't fit will be elided, replaced with `…`. - /// - /// If the text is truncated, the full text will be shown on hover as a tool-tip. - /// - /// Default is `false`, which means the text will expand the parent [`Ui`], - /// or wrap if [`Self::wrap`] is set. - /// - /// Calling `truncate` will override [`Self::wrap`]. + /// Set [`Self::wrap_mode`] to [`TextWrapMode::Wrap`]. + #[inline] + pub fn wrap(mut self) -> Self { + self.wrap_mode = Some(TextWrapMode::Wrap); + + self + } + + /// Set [`Self::wrap_mode`] to [`TextWrapMode::Truncate`]. #[inline] - pub fn truncate(mut self, truncate: bool) -> Self { - self.wrap = None; - self.truncate = truncate; + pub fn truncate(mut self) -> Self { + self.wrap_mode = Some(TextWrapMode::Truncate); self } @@ -156,11 +147,10 @@ impl Label { .text .into_layout_job(ui.style(), FontSelection::Default, valign); - let truncate = self.truncate; - let wrap = !truncate && self.wrap.unwrap_or_else(|| ui.wrap_text()); let available_width = ui.available_width(); - if wrap + let wrap_mode = self.wrap_mode.unwrap_or_else(|| ui.wrap_mode()); + if wrap_mode == TextWrapMode::Wrap && ui.layout().main_dir() == Direction::LeftToRight && ui.layout().main_wrap() && available_width.is_finite() @@ -192,15 +182,8 @@ impl Label { } (pos, galley, response) } else { - if truncate { - layout_job.wrap.max_width = available_width; - layout_job.wrap.max_rows = 1; - layout_job.wrap.break_anywhere = true; - } else if wrap { - layout_job.wrap.max_width = available_width; - } else { - layout_job.wrap.max_width = f32::INFINITY; - }; + layout_job.wrap = + text::TextWrapping::from_wrap_mode_and_width(wrap_mode, available_width); if ui.is_grid() { // TODO(emilk): remove special Grid hacks like these diff --git a/crates/egui/src/widgets/progress_bar.rs b/crates/egui/src/widgets/progress_bar.rs index 99c768bf9af8..5f5d21709694 100644 --- a/crates/egui/src/widgets/progress_bar.rs +++ b/crates/egui/src/widgets/progress_bar.rs @@ -183,7 +183,12 @@ impl Widget for ProgressBar { format!("{}%", (progress * 100.0) as usize).into() } }; - let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button); + let galley = text.into_galley( + ui, + Some(TextWrapMode::Extend), + f32::INFINITY, + TextStyle::Button, + ); let text_pos = outer_rect.left_center() - Vec2::new(0.0, galley.size().y / 2.0) + vec2(ui.spacing().item_spacing.x, 0.0); let text_color = visuals diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 644694cbe816..61aa56b8548e 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -904,7 +904,8 @@ impl<'a> Slider<'a> { }; if !self.text.is_empty() { - let label_response = ui.add(Label::new(self.text.clone()).wrap(false)); + let label_response = + ui.add(Label::new(self.text.clone()).wrap_mode(TextWrapMode::Extend)); // The slider already has an accessibility label via widget info, // but sometimes it's useful for a screen reader to know // that a piece of text is a label for another widget, diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 4458fd031f7a..164f928bbf06 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -665,9 +665,19 @@ impl<'t> TextEdit<'t> { let hint_text_color = ui.visuals().weak_text_color(); let hint_text_font_id = hint_text_font.unwrap_or(font_id.into()); let galley = if multiline { - hint_text.into_galley(ui, Some(true), desired_inner_size.x, hint_text_font_id) + hint_text.into_galley( + ui, + Some(TextWrapMode::Wrap), + desired_inner_size.x, + hint_text_font_id, + ) } else { - hint_text.into_galley(ui, Some(false), f32::INFINITY, hint_text_font_id) + hint_text.into_galley( + ui, + Some(TextWrapMode::Extend), + f32::INFINITY, + hint_text_font_id, + ) }; painter.galley(rect.min, galley, hint_text_color); } diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 2176d57a8b61..f82094d71fa5 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -180,7 +180,7 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) { #[cfg(target_arch = "wasm32")] ui.collapsing("Web info (location)", |ui| { - ui.style_mut().wrap = Some(false); + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.monospace(format!("{:#?}", _frame.info().web_info.location)); }); diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 0910df4ef566..1e34c4647596 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -335,7 +335,7 @@ fn file_menu_button(ui: &mut Ui) { ui.menu_button("File", |ui| { ui.set_min_width(220.0); - ui.style_mut().wrap = Some(false); + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); // On the web the browser controls the zoom #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/egui_demo_lib/src/demo/frame_demo.rs b/crates/egui_demo_lib/src/demo/frame_demo.rs index 0027a98d9dae..bd1661827f97 100644 --- a/crates/egui_demo_lib/src/demo/frame_demo.rs +++ b/crates/egui_demo_lib/src/demo/frame_demo.rs @@ -60,7 +60,7 @@ impl super::View for FrameDemo { .rounding(ui.visuals().widgets.noninteractive.rounding) .show(ui, |ui| { self.frame.show(ui, |ui| { - ui.style_mut().wrap = Some(false); + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.label(egui::RichText::new("Content").color(egui::Color32::WHITE)); }); }); diff --git a/crates/egui_demo_lib/src/demo/layout_test.rs b/crates/egui_demo_lib/src/demo/layout_test.rs index a5726c7ab2f7..2bc1fd0d4e9f 100644 --- a/crates/egui_demo_lib/src/demo/layout_test.rs +++ b/crates/egui_demo_lib/src/demo/layout_test.rs @@ -176,7 +176,7 @@ impl LayoutTest { } fn demo_ui(ui: &mut Ui) { - ui.add(egui::Label::new("Wrapping text followed by example widgets:").wrap(true)); + ui.add(egui::Label::new("Wrapping text followed by example widgets:").wrap()); let mut dummy = false; ui.checkbox(&mut dummy, "checkbox"); ui.radio_value(&mut dummy, false, "radio"); diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index f3c7a8dcfec2..6b73fe4048bd 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -223,7 +223,7 @@ fn label_ui(ui: &mut egui::Ui) { egui::Label::new( "Labels containing long text can be set to elide the text that doesn't fit on a single line using `Label::elide`. When hovered, the label will show the full text.", ) - .truncate(true), + .truncate(), ); } diff --git a/crates/egui_demo_lib/src/demo/pan_zoom.rs b/crates/egui_demo_lib/src/demo/pan_zoom.rs index 40323346887e..6ee75e3039a6 100644 --- a/crates/egui_demo_lib/src/demo/pan_zoom.rs +++ b/crates/egui_demo_lib/src/demo/pan_zoom.rs @@ -1,4 +1,5 @@ use egui::emath::TSTransform; +use egui::TextWrapMode; #[derive(Clone, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -122,7 +123,7 @@ impl super::View for PanZoom { .stroke(ui.ctx().style().visuals.window_stroke) .fill(ui.style().visuals.panel_fill) .show(ui, |ui| { - ui.style_mut().wrap = Some(false); + ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); callback(ui, self) }); }) diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 12e45aa0f098..17eceba1855a 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -200,7 +200,7 @@ impl LineDemo { }); ui.vertical(|ui| { - ui.style_mut().wrap = Some(false); + ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); ui.checkbox(animate, "Animate"); ui.checkbox(square, "Square view") .on_hover_text("Always keep the viewport square."); diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index 71d47f9d84cf..ec6ca5e1a987 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -82,7 +82,7 @@ impl super::View for Scrolling { } ScrollDemo::Bidirectional => { egui::ScrollArea::both().show(ui, |ui| { - ui.style_mut().wrap = Some(false); + ui.style_mut().wrap_mode = Some(TextWrapMode::Extend); for _ in 0..100 { ui.label(crate::LOREM_IPSUM); } diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index c7029d44e499..ec6fa58c3490 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -1,4 +1,4 @@ -use egui::TextStyle; +use egui::{TextStyle, TextWrapMode}; #[derive(PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -196,7 +196,7 @@ impl TableDemo { ui.label(long_text(row_index)); }); row.col(|ui| { - ui.style_mut().wrap = Some(false); + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); if is_thick { ui.heading("Extra thick row"); } else { @@ -227,7 +227,8 @@ impl TableDemo { }); row.col(|ui| { ui.add( - egui::Label::new("Thousands of rows of even height").wrap(false), + egui::Label::new("Thousands of rows of even height") + .wrap_mode(TextWrapMode::Extend), ); }); @@ -253,7 +254,7 @@ impl TableDemo { ui.label(long_text(row_index)); }); row.col(|ui| { - ui.style_mut().wrap = Some(false); + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); if thick_row(row_index) { ui.heading("Extra thick row"); } else { diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index d68af17db806..6f5d26f5e57e 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -172,7 +172,7 @@ impl WidgetGallery { egui::ComboBox::from_label("Take your pick") .selected_text(format!("{radio:?}")) .show_ui(ui, |ui| { - ui.style_mut().wrap = Some(false); + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.set_min_width(60.0); ui.selectable_value(radio, Enum::First, "First"); ui.selectable_value(radio, Enum::Second, "Second"); diff --git a/crates/egui_plot/src/axis.rs b/crates/egui_plot/src/axis.rs index 059349d6673b..3827307a2ae4 100644 --- a/crates/egui_plot/src/axis.rs +++ b/crates/egui_plot/src/axis.rs @@ -3,7 +3,7 @@ use std::{fmt::Debug, ops::RangeInclusive, sync::Arc}; use egui::{ emath::{remap_clamp, round_to_decimals, Rot2}, epaint::TextShape, - Pos2, Rangef, Rect, Response, Sense, TextStyle, Ui, Vec2, WidgetText, + Pos2, Rangef, Rect, Response, Sense, TextStyle, TextWrapMode, Ui, Vec2, WidgetText, }; use super::{transform::PlotTransform, GridMark}; @@ -264,7 +264,12 @@ impl<'a> AxisWidget<'a> { { let text = self.hints.label; - let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Body); + let galley = text.into_galley( + ui, + Some(TextWrapMode::Extend), + f32::INFINITY, + TextStyle::Body, + ); let text_color = visuals .override_text_color .unwrap_or_else(|| ui.visuals().text_color()); diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 0470d9559650..8e31c1fb6100 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -850,10 +850,12 @@ impl PlotItem for Text { self.color }; - let galley = - self.text - .clone() - .into_galley(ui, Some(false), f32::INFINITY, TextStyle::Small); + let galley = self.text.clone().into_galley( + ui, + Some(egui::TextWrapMode::Extend), + f32::INFINITY, + TextStyle::Small, + ); let pos = transform.position_from_point(&self.position); let rect = self.anchor.anchor_size(pos, galley.size()); diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index dec2cb290575..c14348ecf29e 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -319,6 +319,24 @@ impl TextFormat { // ---------------------------------------------------------------------------- +/// How to wrap and elide text. +/// +/// This enum is used in high-level APIs where providing a [`TextWrapping`] is too verbose. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub enum TextWrapMode { + /// The text should expand the `Ui` size when reaching its boundary. + Extend, + + /// The text should wrap to the next line when reaching the `Ui` boundary. + Wrap, + + /// The text should be elided using "…" when reaching the `Ui` boundary. + /// + /// Note that using [`TextWrapping`] and [`LayoutJob`] offers more control over the elision. + Truncate, +} + /// Controls the text wrapping and elision of a [`LayoutJob`]. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -335,7 +353,7 @@ pub struct TextWrapping { /// Maximum amount of rows the text galley should have. /// - /// If this limit is reached, text will be truncated and + /// If this limit is reached, text will be truncated /// and [`Self::overflow_character`] appended to the final row. /// You can detect this by checking [`Galley::elided`]. /// @@ -394,6 +412,15 @@ impl Default for TextWrapping { } impl TextWrapping { + /// Create a [`TextWrapping`] from a [`TextWrapMode`] and an available width. + pub fn from_wrap_mode_and_width(mode: TextWrapMode, max_width: f32) -> Self { + match mode { + TextWrapMode::Extend => Self::no_max_width(), + TextWrapMode::Wrap => Self::wrap_at_width(max_width), + TextWrapMode::Truncate => Self::truncate_at_width(max_width), + } + } + /// A row can be as long as it need to be. pub fn no_max_width() -> Self { Self { @@ -402,6 +429,14 @@ impl TextWrapping { } } + /// A row can be at most `max_width` wide but can wrap in any number of lines. + pub fn wrap_at_width(max_width: f32) -> Self { + Self { + max_width, + ..Default::default() + } + } + /// Elide text that doesn't fit within the given width, replaced with `…`. pub fn truncate_at_width(max_width: f32) -> Self { Self { From 26206526d643215dcc43b0120b9b47434c88cf5c Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Tue, 28 May 2024 14:17:37 +0200 Subject: [PATCH 0119/1202] Hide toolip when opening `ComboBox` drop-down (#4546) - Fixes #4338 https://github.com/emilk/egui/assets/49431240/73ea87a1-41ad-40b1-b451-d6be2b38c7e0 Tested using `example/hello_world` modified to: ```rust #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![allow(rustdoc::missing_crate_level_docs)] // it's an example use eframe::egui; fn main() -> Result<(), eframe::Error> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 240.0]), ..Default::default() }; eframe::run_native( "My egui App", options, Box::new(|cc| { // This gives us image support: egui_extras::install_image_loaders(&cc.egui_ctx); Box::::default() }), ) } struct MyApp { name: String, age: u32, } impl Default for MyApp { fn default() -> Self { Self { name: "Arthur".to_owned(), age: 42, } } } impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("My egui Application"); egui::ComboBox::new("combo", "combo box") .selected_text(&self.name) .show_ui(ui, |ui| { ui.selectable_value(&mut self.name, "Arthur".into(), "Arthur") .on_hover_text("This is Arthur"); ui.selectable_value(&mut self.name, "Ford".into(), "Ford") .on_hover_text("This is Ford"); ui.selectable_value(&mut self.name, "Trillian".into(), "Trillian") .on_hover_text("This is Trillian"); }) .response .on_hover_text("This is a combo box"); }); } } ``` --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/containers/combo_box.rs | 12 +++++++++++- crates/egui/src/response.rs | 7 ++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index db5a9e3e8bdc..b4c7bbca7d36 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -270,6 +270,16 @@ impl ComboBox { } response } + + /// Check if the [`ComboBox`] with the given id has its popup menu currently opened. + pub fn is_open(ctx: &Context, id: Id) -> bool { + ctx.memory(|m| m.is_popup_open(Self::widget_to_popup_id(id))) + } + + /// Convert a [`ComboBox`] id to the id used to store it's popup state. + fn widget_to_popup_id(widget_id: Id) -> Id { + widget_id.with("popup") + } } #[allow(clippy::too_many_arguments)] @@ -282,7 +292,7 @@ fn combo_box_dyn<'c, R>( wrap_mode: Option, (width, height): (Option, Option), ) -> InnerResponse> { - let popup_id = button_id.with("popup"); + let popup_id = ComboBox::widget_to_popup_id(button_id); let is_popup_open = ui.memory(|m| m.is_popup_open(popup_id)); diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index d980bd78eb6c..264d53d59833 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -2,7 +2,8 @@ use std::{any::Any, sync::Arc}; use crate::{ emath::{Align, Pos2, Rect, Vec2}, - menu, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetRect, WidgetText, + menu, ComboBox, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetRect, + WidgetText, }; // ---------------------------------------------------------------------------- @@ -571,6 +572,10 @@ impl Response { return false; } + if ComboBox::is_open(&self.ctx, self.id) { + return false; // Don't cover the open ComboBox with a tooltip + } + if self.enabled { if !self.hovered || !self.ctx.input(|i| i.pointer.has_pointer()) { return false; From 1888d19b4a6819f0af151fed66fe79cdb61557bf Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 28 May 2024 14:19:25 +0200 Subject: [PATCH 0120/1202] Better spacing and sizes for (menu) buttons (#4558) --- crates/egui/src/menu.rs | 3 ++- crates/egui/src/widgets/button.rs | 22 ++++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 29441d8a74cb..8b374ce7ad99 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -476,6 +476,7 @@ impl SubMenuButton { let text_style = TextStyle::Button; let sense = Sense::click(); + let text_icon_gap = ui.spacing().item_spacing.x; let button_padding = ui.spacing().button_padding; let total_extra = button_padding + button_padding; let text_available_width = ui.available_width() - total_extra.x; @@ -494,7 +495,7 @@ impl SubMenuButton { text_style, ); let text_and_icon_size = Vec2::new( - text_galley.size().x + icon_galley.size().x, + text_galley.size().x + text_icon_gap + icon_galley.size().x, text_galley.size().y.max(icon_galley.size().y), ); let mut desired_size = text_and_icon_size + 2.0 * button_padding; diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 4b5aa6b6bf54..a2b29327b04b 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -215,16 +215,14 @@ impl Widget for Button<'_> { Vec2::ZERO }; + let gap_before_shortcut_text = ui.spacing().item_spacing.x; + let mut text_wrap_width = ui.available_width() - 2.0 * button_padding.x; if image.is_some() { text_wrap_width -= image_size.x + ui.spacing().icon_spacing; } - if !shortcut_text.is_empty() { - text_wrap_width -= 60.0; // Some space for the shortcut text (which we never wrap). - } - let galley = - text.map(|text| text.into_galley(ui, wrap_mode, text_wrap_width, TextStyle::Button)); + // Note: we don't wrap the shortcut text let shortcut_galley = (!shortcut_text.is_empty()).then(|| { shortcut_text.into_galley( ui, @@ -234,6 +232,14 @@ impl Widget for Button<'_> { ) }); + if let Some(shortcut_galley) = &shortcut_galley { + // Leave space for the shortcut text: + text_wrap_width -= gap_before_shortcut_text + shortcut_galley.size().x; + } + + let galley = + text.map(|text| text.into_galley(ui, wrap_mode, text_wrap_width, TextStyle::Button)); + let mut desired_size = Vec2::ZERO; if image.is_some() { desired_size.x += image_size.x; @@ -246,9 +252,9 @@ impl Widget for Button<'_> { desired_size.x += text.size().x; desired_size.y = desired_size.y.max(text.size().y); } - if let Some(shortcut_text) = &shortcut_galley { - desired_size.x += ui.spacing().item_spacing.x + shortcut_text.size().x; - desired_size.y = desired_size.y.max(shortcut_text.size().y); + if let Some(shortcut_galley) = &shortcut_galley { + desired_size.x += gap_before_shortcut_text + shortcut_galley.size().x; + desired_size.y = desired_size.y.max(shortcut_galley.size().y); } desired_size += 2.0 * button_padding; if !small { From 54429e0549e829c64b8de1b66f846fdf1a7ee37c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 28 May 2024 14:40:43 +0200 Subject: [PATCH 0121/1202] Revert update to wgpu 0.20 => downgrade to wgpu 0.19.1 (#4559) 0.20 has a bunch of bugs that will be fixed by: * https://github.com/gfx-rs/wgpu/pull/5681 At Rerun, we don't want to wait for the wgpu 0.20.1 patch release before we update egui, so we will temporarily downgrade to wgpu 0.19 After reverting I'll open a new PR that will update to 0.20 again, with the intention of merging that once 0.20.1 is released. --- .github/workflows/rust.yml | 2 +- Cargo.lock | 156 +++++++++--------- Cargo.toml | 2 +- crates/egui-wgpu/src/renderer.rs | 2 - crates/egui_demo_app/Cargo.toml | 2 +- .../egui_demo_app/src/apps/custom3d_wgpu.rs | 2 - scripts/setup_web.sh | 2 +- 7 files changed, 85 insertions(+), 83 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8aaa18faa36e..44cabf9a8cdb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -108,7 +108,7 @@ jobs: - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 with: - version: "0.2.92" + version: "0.2.90" - run: ./scripts/wasm_bindgen_check.sh --skip-setup diff --git a/Cargo.lock b/Cargo.lock index 0eb199156cf0..7f726392e339 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,7 +144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "052ad56e336bcc615a214bffbeca6c181ee9550acec193f0327e0b103b033a4d" dependencies = [ "android-properties", - "bitflags 2.5.0", + "bitflags 2.4.0", "cc", "cesu8", "jni", @@ -510,9 +510,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" dependencies = [ "serde", ] @@ -649,7 +649,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b50b5a44d59a98c55a9eeb518f39bf7499ba19fd98ee7d22618687f3f10adbf" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "log", "polling 3.3.0", "rustix 0.38.21", @@ -879,9 +879,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", @@ -889,9 +889,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "core-graphics" @@ -908,9 +908,9 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.3" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1791,7 +1791,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "005459a22af86adc706522d78d360101118e2638ec21df3852fcc626e0dbb212" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "cfg_aliases", "cgl", "core-foundation", @@ -1867,7 +1867,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "gpu-alloc-types", ] @@ -1877,7 +1877,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", ] [[package]] @@ -1895,22 +1895,22 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.3.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "gpu-descriptor-types", "hashbrown", ] [[package]] name = "gpu-descriptor-types" -version = "0.2.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", ] [[package]] @@ -1953,7 +1953,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "com", "libc", "libloading 0.8.0", @@ -2189,9 +2189,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" dependencies = [ "wasm-bindgen", ] @@ -2352,11 +2352,11 @@ dependencies = [ [[package]] name = "metal" -version = "0.28.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "block", "core-graphics-types", "foreign-types", @@ -2413,13 +2413,12 @@ dependencies = [ [[package]] name = "naga" -version = "0.20.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231" +checksum = "8878eb410fc90853da3908aebfe61d73d26d4437ef850b70050461f939509899" dependencies = [ - "arrayvec", "bit-set", - "bitflags 2.5.0", + "bitflags 2.4.0", "codespan-reporting", "hexf-parse", "indexmap", @@ -2438,7 +2437,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "jni-sys", "log", "ndk-sys", @@ -2528,6 +2527,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", + "objc_exception", ] [[package]] @@ -2638,6 +2638,15 @@ dependencies = [ "objc2 0.5.1", ] +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "objc_id" version = "0.1.1" @@ -3094,9 +3103,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "renderdoc-sys" -version = "1.1.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" [[package]] name = "resvg" @@ -3166,7 +3175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64", - "bitflags 2.5.0", + "bitflags 2.4.0", "serde", "serde_derive", ] @@ -3209,7 +3218,7 @@ version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "errno", "libc", "linux-raw-sys 0.4.11", @@ -3438,7 +3447,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "calloop", "calloop-wayland-source", "cursor-icon", @@ -3499,7 +3508,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", ] [[package]] @@ -3629,18 +3638,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", @@ -3999,9 +4008,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4009,9 +4018,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" dependencies = [ "bumpalo", "log", @@ -4024,9 +4033,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" dependencies = [ "cfg-if", "js-sys", @@ -4036,9 +4045,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4046,9 +4055,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" dependencies = [ "proc-macro2", "quote", @@ -4059,9 +4068,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" [[package]] name = "wayland-backend" @@ -4083,7 +4092,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "nix", "wayland-backend", "wayland-scanner", @@ -4095,7 +4104,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "cursor-icon", "wayland-backend", ] @@ -4117,7 +4126,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -4129,7 +4138,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4142,7 +4151,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -4174,9 +4183,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" dependencies = [ "js-sys", "wasm-bindgen", @@ -4218,14 +4227,13 @@ checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "wgpu" -version = "0.20.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ff1bfee408e1028e2e3acbf6d32d98b08a5a059ccbf5f33305534453ba5d3e" +checksum = "0bfe9a310dcf2e6b85f00c46059aaeaf4184caa8e29a1ecd4b7a704c3482332d" dependencies = [ "arrayvec", "cfg-if", "cfg_aliases", - "document-features", "js-sys", "log", "naga", @@ -4244,16 +4252,15 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.20.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6a86eaa5e763e59c73cf9e97d55fffd4dfda69fd8bda19589fcf851ddfef1f" +checksum = "6b15e451d4060ada0d99a64df44e4d590213496da7c4f245572d51071e8e30ed" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.5.0", + "bitflags 2.4.0", "cfg_aliases", "codespan-reporting", - "document-features", "indexmap", "log", "naga", @@ -4271,14 +4278,14 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.20.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d71c8ae05170583049b65ee562fd839fdc0b3e9ddb84f4e40c9d5f8ea0d4c8c" +checksum = "11f259ceb56727fb097da108d92f8a5cbdb5b74a77f9e396bd43626f67299d61" dependencies = [ "android_system_properties", "arrayvec", "ash", - "bitflags 2.5.0", + "bitflags 2.4.0", "block", "cfg_aliases", "core-graphics-types", @@ -4295,7 +4302,6 @@ dependencies = [ "log", "metal", "naga", - "ndk-sys", "objc", "once_cell", "parking_lot", @@ -4313,11 +4319,11 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.20.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef" +checksum = "895fcbeb772bfb049eb80b2d6e47f6c9af235284e9703c96fc0218a42ffd5af2" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "js-sys", "web-sys", ] @@ -4625,7 +4631,7 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.5.0", + "bitflags 2.4.0", "bytemuck", "calloop", "cfg_aliases", @@ -4731,7 +4737,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.4.0", "dlib", "log", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 77cc2e7ba5d9..07e83a4e9c66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,7 @@ web-time = "0.2" # Timekeeping for native and web wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" web-sys = "0.3.58" -wgpu = { version = "0.20.0", default-features = false, features = [ +wgpu = { version = "0.19.1", default-features = false, features = [ # Make the renderer `Sync` even on wasm32, because it makes the code simpler: "fragile-send-sync-non-atomic-wasm", ] } diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index a4bc8809c35c..5853f563ff13 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -294,7 +294,6 @@ impl Renderer { // 2: uint color attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32], }], - compilation_options: wgpu::PipelineCompilationOptions::default() }, primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, @@ -336,7 +335,6 @@ impl Renderer { }), write_mask: wgpu::ColorWrites::ALL, })], - compilation_options: wgpu::PipelineCompilationOptions::default() }), multiview: None, } diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 87f787a054a6..ec3190918fb1 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -77,6 +77,6 @@ rfd = { version = "0.13", optional = true } # web: [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = "=0.2.92" +wasm-bindgen = "=0.2.90" wasm-bindgen-futures.workspace = true web-sys.workspace = true diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index 95b3ee1a0af4..1676a0ba70c6 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -49,13 +49,11 @@ impl Custom3d { module: &shader, entry_point: "vs_main", buffers: &[], - compilation_options: wgpu::PipelineCompilationOptions::default(), }, fragment: Some(wgpu::FragmentState { module: &shader, entry_point: "fs_main", targets: &[Some(wgpu_render_state.target_format.into())], - compilation_options: wgpu::PipelineCompilationOptions::default(), }), primitive: wgpu::PrimitiveState::default(), depth_stencil: None, diff --git a/scripts/setup_web.sh b/scripts/setup_web.sh index 51e53d4be4a6..36d39128b6ed 100755 --- a/scripts/setup_web.sh +++ b/scripts/setup_web.sh @@ -7,4 +7,4 @@ cd "$script_path/.." rustup target add wasm32-unknown-unknown # For generating JS bindings: -cargo install --quiet wasm-bindgen-cli --version 0.2.92 +cargo install --quiet wasm-bindgen-cli --version 0.2.90 From 942fe4ab314323a66077fece4655c6e8e8c810a8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 28 May 2024 21:59:19 +0200 Subject: [PATCH 0122/1202] Support returning errors when creating the app (#4565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The closure passed to `eframe::run_native` now returns a `Result`, allowing you to return an error during app creation, which will be returned to the caller of `run_native`. This means you need to wrap your `Box::new(MyApp::new(…))` in an `Ok(…)`. * Closes https://github.com/emilk/egui/issues/4474 --- crates/eframe/src/epi.rs | 4 +++- crates/eframe/src/lib.rs | 13 +++++++++---- crates/eframe/src/native/glow_integration.rs | 2 +- crates/eframe/src/native/wgpu_integration.rs | 4 ++-- crates/eframe/src/web/app_runner.rs | 7 ++++--- crates/eframe/src/web/web_runner.rs | 2 +- crates/egui_demo_app/src/main.rs | 2 +- crates/egui_demo_app/src/web.rs | 2 +- examples/confirm_exit/src/main.rs | 2 +- examples/custom_3d_glow/src/main.rs | 2 +- examples/custom_font/src/main.rs | 2 +- examples/custom_font_style/src/main.rs | 2 +- examples/custom_keypad/src/main.rs | 2 +- examples/custom_plot_manipulation/src/main.rs | 2 +- examples/custom_window_frame/src/main.rs | 2 +- examples/file_dialog/src/main.rs | 2 +- examples/hello_world/src/main.rs | 2 +- examples/hello_world_par/src/main.rs | 2 +- examples/images/src/main.rs | 2 +- examples/keyboard_events/src/main.rs | 2 +- examples/multiple_viewports/src/main.rs | 2 +- examples/puffin_profiler/src/main.rs | 2 +- examples/save_plot/src/main.rs | 2 +- examples/screenshot/src/main.rs | 2 +- examples/serial_windows/src/main.rs | 6 +++--- examples/user_attention/src/main.rs | 2 +- tests/test_inline_glow_paint/src/main.rs | 2 +- tests/test_viewports/src/main.rs | 2 +- 28 files changed, 44 insertions(+), 36 deletions(-) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 7b75811ccbc6..b1a432dd978b 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -41,10 +41,12 @@ pub type EventLoopBuilderHook = Box) #[cfg(any(feature = "glow", feature = "wgpu"))] pub type WindowBuilderHook = Box egui::ViewportBuilder>; +type DynError = Box; + /// This is how your app is created. /// /// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc. -pub type AppCreator = Box) -> Box>; +pub type AppCreator = Box) -> Result, DynError>>; /// Data that is passed to [`AppCreator`] that can be used to setup and initialize your app. pub struct CreationContext<'s> { diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index e1df19a8b0bc..0fc1f26a1408 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -30,7 +30,7 @@ //! //! fn main() { //! let native_options = eframe::NativeOptions::default(); -//! eframe::run_native("My egui App", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))); +//! eframe::run_native("My egui App", native_options, Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc))))); //! } //! //! #[derive(Default)] @@ -90,7 +90,7 @@ //! .start( //! canvas_id, //! eframe::WebOptions::default(), -//! Box::new(|cc| Box::new(MyEguiApp::new(cc))), +//! Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc))),) //! ) //! .await //! } @@ -199,7 +199,7 @@ pub mod icon_data; /// /// fn main() -> eframe::Result<()> { /// let native_options = eframe::NativeOptions::default(); -/// eframe::run_native("MyApp", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))) +/// eframe::run_native("MyApp", native_options, Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc))))) /// } /// /// #[derive(Default)] @@ -324,7 +324,7 @@ pub fn run_simple_native( run_native( app_name, native_options, - Box::new(|_cc| Box::new(SimpleApp { update_fun })), + Box::new(|_cc| Ok(Box::new(SimpleApp { update_fun }))), ) } @@ -333,6 +333,9 @@ pub fn run_simple_native( /// The different problems that can occur when trying to run `eframe`. #[derive(Debug)] pub enum Error { + /// Something went wrong in user code when creating the app. + AppCreation(Box), + /// An error from [`winit`]. #[cfg(not(target_arch = "wasm32"))] Winit(winit::error::OsError), @@ -403,6 +406,8 @@ impl From for Error { impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Self::AppCreation(err) => write!(f, "app creation error: {err}"), + #[cfg(not(target_arch = "wasm32"))] Self::Winit(err) => { write!(f, "winit error: {err}") diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 28eb1a4ef24d..bfdee21d8d94 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -313,7 +313,7 @@ impl GlowWinitApp { raw_window_handle: window.window_handle().map(|h| h.as_raw()), }; crate::profile_scope!("app_creator"); - app_creator(&cc) + app_creator(&cc).map_err(crate::Error::AppCreation)? }; let glutin = Rc::new(RefCell::new(glutin)); diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index dbf4f4446902..28f149f0b3fd 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -182,7 +182,7 @@ impl WgpuWinitApp { storage: Option>, window: Window, builder: ViewportBuilder, - ) -> Result<&mut WgpuWinitRunning, egui_wgpu::WgpuError> { + ) -> crate::Result<&mut WgpuWinitRunning> { crate::profile_function!(); #[allow(unsafe_code, unused_mut, unused_unsafe)] @@ -272,7 +272,7 @@ impl WgpuWinitApp { }; let app = { crate::profile_scope!("user_app_creator"); - app_creator(&cc) + app_creator(&cc).map_err(crate::Error::AppCreation)? }; let mut viewport_from_window = HashMap::default(); diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 872921591acd..75fee203417f 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -30,7 +30,7 @@ impl Drop for AppRunner { impl AppRunner { /// # Errors - /// Failure to initialize WebGL renderer. + /// Failure to initialize WebGL renderer, or failure to create app. pub async fn new( canvas_id: &str, web_options: crate::WebOptions, @@ -71,7 +71,7 @@ impl AppRunner { let theme = system_theme.unwrap_or(web_options.default_theme); egui_ctx.set_visuals(theme.egui_visuals()); - let app = app_creator(&epi::CreationContext { + let cc = epi::CreationContext { egui_ctx: egui_ctx.clone(), integration_info: info.clone(), storage: Some(&storage), @@ -86,7 +86,8 @@ impl AppRunner { wgpu_render_state: painter.render_state(), #[cfg(all(feature = "wgpu", feature = "glow"))] wgpu_render_state: None, - }); + }; + let app = app_creator(&cc).map_err(|err| err.to_string())?; let frame = epi::Frame { info, diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index 6a230a8e3292..e25d0da181b2 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -54,7 +54,7 @@ impl WebRunner { /// Create the application, install callbacks, and start running the app. /// /// # Errors - /// Failing to initialize graphics. + /// Failing to initialize graphics, or failure to create app. pub async fn start( &self, canvas_id: &str, diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index 9d660ed046cd..e30a73f22eae 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -48,7 +48,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "egui demo app", options, - Box::new(|cc| Box::new(egui_demo_app::WrapApp::new(cc))), + Box::new(|cc| Ok(Box::new(egui_demo_app::WrapApp::new(cc)))), ) } diff --git a/crates/egui_demo_app/src/web.rs b/crates/egui_demo_app/src/web.rs index f4b9e5de15c5..ce06399b27f3 100644 --- a/crates/egui_demo_app/src/web.rs +++ b/crates/egui_demo_app/src/web.rs @@ -32,7 +32,7 @@ impl WebHandle { .start( canvas_id, eframe::WebOptions::default(), - Box::new(|cc| Box::new(WrapApp::new(cc))), + Box::new(|cc| Ok(Box::new(WrapApp::new(cc)))), ) .await } diff --git a/examples/confirm_exit/src/main.rs b/examples/confirm_exit/src/main.rs index 14816e1bdeab..1ec4a7e0f157 100644 --- a/examples/confirm_exit/src/main.rs +++ b/examples/confirm_exit/src/main.rs @@ -12,7 +12,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "Confirm exit", options, - Box::new(|_cc| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), ) } diff --git a/examples/custom_3d_glow/src/main.rs b/examples/custom_3d_glow/src/main.rs index 241124b7c174..c4b89103d3fd 100644 --- a/examples/custom_3d_glow/src/main.rs +++ b/examples/custom_3d_glow/src/main.rs @@ -19,7 +19,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "Custom 3D painting in eframe using glow", options, - Box::new(|cc| Box::new(MyApp::new(cc))), + Box::new(|cc| Ok(Box::new(MyApp::new(cc)))), ) } diff --git a/examples/custom_font/src/main.rs b/examples/custom_font/src/main.rs index f42c5763150b..9687cb0215b4 100644 --- a/examples/custom_font/src/main.rs +++ b/examples/custom_font/src/main.rs @@ -12,7 +12,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "egui example: custom font", options, - Box::new(|cc| Box::new(MyApp::new(cc))), + Box::new(|cc| Ok(Box::new(MyApp::new(cc)))), ) } diff --git a/examples/custom_font_style/src/main.rs b/examples/custom_font_style/src/main.rs index c1a61e3e69ca..11608bfc0120 100644 --- a/examples/custom_font_style/src/main.rs +++ b/examples/custom_font_style/src/main.rs @@ -11,7 +11,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "egui example: global font style", options, - Box::new(|cc| Box::new(MyApp::new(cc))), + Box::new(|cc| Ok(Box::new(MyApp::new(cc)))), ) } diff --git a/examples/custom_keypad/src/main.rs b/examples/custom_keypad/src/main.rs index 654de25fa44c..d72d520a1104 100644 --- a/examples/custom_keypad/src/main.rs +++ b/examples/custom_keypad/src/main.rs @@ -20,7 +20,7 @@ fn main() -> Result<(), eframe::Error> { // This gives us image support: egui_extras::install_image_loaders(&cc.egui_ctx); - Box::::default() + Ok(Box::::default()) }), ) } diff --git a/examples/custom_plot_manipulation/src/main.rs b/examples/custom_plot_manipulation/src/main.rs index e423d890fcbb..1d4abca3a6bb 100644 --- a/examples/custom_plot_manipulation/src/main.rs +++ b/examples/custom_plot_manipulation/src/main.rs @@ -11,7 +11,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "Plot", options, - Box::new(|_cc| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), ) } diff --git a/examples/custom_window_frame/src/main.rs b/examples/custom_window_frame/src/main.rs index bb098652cbfe..bd29788ac1aa 100644 --- a/examples/custom_window_frame/src/main.rs +++ b/examples/custom_window_frame/src/main.rs @@ -19,7 +19,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "Custom window frame", // unused title options, - Box::new(|_cc| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), ) } diff --git a/examples/file_dialog/src/main.rs b/examples/file_dialog/src/main.rs index 267c22e111ce..840a72fb5c75 100644 --- a/examples/file_dialog/src/main.rs +++ b/examples/file_dialog/src/main.rs @@ -14,7 +14,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "Native file dialogs and drag-and-drop files", options, - Box::new(|_cc| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), ) } diff --git a/examples/hello_world/src/main.rs b/examples/hello_world/src/main.rs index 1c74f4c49928..2780d0ce56ed 100644 --- a/examples/hello_world/src/main.rs +++ b/examples/hello_world/src/main.rs @@ -16,7 +16,7 @@ fn main() -> Result<(), eframe::Error> { // This gives us image support: egui_extras::install_image_loaders(&cc.egui_ctx); - Box::::default() + Ok(Box::::default()) }), ) } diff --git a/examples/hello_world_par/src/main.rs b/examples/hello_world_par/src/main.rs index 2896f24843fc..ecf7034c2e13 100644 --- a/examples/hello_world_par/src/main.rs +++ b/examples/hello_world_par/src/main.rs @@ -17,7 +17,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "My parallel egui App", options, - Box::new(|_cc| Box::new(MyApp::new())), + Box::new(|_cc| Ok(Box::new(MyApp::new()))), ) } diff --git a/examples/images/src/main.rs b/examples/images/src/main.rs index 8a124e051a55..a1a15a17c47f 100644 --- a/examples/images/src/main.rs +++ b/examples/images/src/main.rs @@ -15,7 +15,7 @@ fn main() -> Result<(), eframe::Error> { Box::new(|cc| { // This gives us image support: egui_extras::install_image_loaders(&cc.egui_ctx); - Box::::default() + Ok(Box::::default()) }), ) } diff --git a/examples/keyboard_events/src/main.rs b/examples/keyboard_events/src/main.rs index bf0b3389981d..3c1223d671da 100644 --- a/examples/keyboard_events/src/main.rs +++ b/examples/keyboard_events/src/main.rs @@ -10,7 +10,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "Keyboard events", options, - Box::new(|_cc| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), ) } diff --git a/examples/multiple_viewports/src/main.rs b/examples/multiple_viewports/src/main.rs index 262002e2c5b7..0ed26e4e8a00 100644 --- a/examples/multiple_viewports/src/main.rs +++ b/examples/multiple_viewports/src/main.rs @@ -17,7 +17,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "Multiple viewports", options, - Box::new(|_cc| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), ) } diff --git a/examples/puffin_profiler/src/main.rs b/examples/puffin_profiler/src/main.rs index 8fe30d679fff..2987e93c5e8d 100644 --- a/examples/puffin_profiler/src/main.rs +++ b/examples/puffin_profiler/src/main.rs @@ -22,7 +22,7 @@ fn main() -> Result<(), eframe::Error> { ..Default::default() }, - Box::new(|_cc| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), ) } diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index 61657faae861..fa24ec842953 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -14,7 +14,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "My egui App with a plot", options, - Box::new(|_cc| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), ) } diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index c0c627e86a79..24eccd2d27cb 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -14,7 +14,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "Take screenshots and display with eframe/egui", options, - Box::new(|_cc| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), ) } diff --git a/examples/serial_windows/src/main.rs b/examples/serial_windows/src/main.rs index 04bed8006aed..9ea1f3ea8ef7 100644 --- a/examples/serial_windows/src/main.rs +++ b/examples/serial_windows/src/main.rs @@ -23,7 +23,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "First Window", options.clone(), - Box::new(|_cc| Box::new(MyApp { has_next: true })), + Box::new(|_cc| Ok(Box::new(MyApp { has_next: true }))), )?; std::thread::sleep(std::time::Duration::from_secs(2)); @@ -32,7 +32,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "Second Window", options.clone(), - Box::new(|_cc| Box::new(MyApp { has_next: true })), + Box::new(|_cc| Ok(Box::new(MyApp { has_next: true }))), )?; std::thread::sleep(std::time::Duration::from_secs(2)); @@ -41,7 +41,7 @@ fn main() -> Result<(), eframe::Error> { eframe::run_native( "Third Window", options, - Box::new(|_cc| Box::new(MyApp { has_next: false })), + Box::new(|_cc| Ok(Box::new(MyApp { has_next: false }))), ) } diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index cbe27b6d5bc1..15f0676cf0a9 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -15,7 +15,7 @@ fn main() -> eframe::Result<()> { eframe::run_native( "User attention test", native_options, - Box::new(|cc| Box::new(Application::new(cc))), + Box::new(|cc| Ok(Box::new(Application::new(cc)))), ) } diff --git a/tests/test_inline_glow_paint/src/main.rs b/tests/test_inline_glow_paint/src/main.rs index 7851ad63b8b3..12569652665c 100644 --- a/tests/test_inline_glow_paint/src/main.rs +++ b/tests/test_inline_glow_paint/src/main.rs @@ -16,7 +16,7 @@ fn main() -> Result<(), Box> { eframe::run_native( "My test app", options, - Box::new(|_cc| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), )?; Ok(()) } diff --git a/tests/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs index 8bbb1b22a070..6687e8c01563 100644 --- a/tests/test_viewports/src/main.rs +++ b/tests/test_viewports/src/main.rs @@ -22,7 +22,7 @@ fn main() { ..Default::default() }, - Box::new(|_| Box::::default()), + Box::new(|_cc| Ok(Box::::default())), ); } From a768d744119dd94840e08051635f68108fa54364 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 29 May 2024 10:27:04 +0200 Subject: [PATCH 0123/1202] Add `Ui::is_sizing_pass` for better size estimation of `Area`s, and menus in particular (#4557) * Part of https://github.com/emilk/egui/issues/4535 * Closes https://github.com/emilk/egui/issues/3974 This adds a special `sizing_pass` mode to `Ui`, in which we have no centered or justified layouts, and everything is hidden. This is used by `Area` to use the first frame to measure the size of its contents so that it can then set the perfectly correct size the subsequent frames. For menus, where buttons are justified (span the full width), this finally the problem of auto-sizing. Before you would have to pick a width manually, and all buttons would expand to that width. If it was too wide, it looked weird. If it was too narrow, text would wrap. Now all menus are exactly the width they need to be. By default menus will wrap at `Spacing::menu_width`. This affects all situations when you have something that should be as small as possible, but still span the full width/height of the parent. For instance: the `egui::Separator` widget now checks the `ui.is_sizing_pass` flag before deciding on a size. In the sizing pass a horizontal separator is always 0 wide, and only in subsequent passes will it span the full width. --- Cargo.lock | 8 ++ crates/egui/src/containers/area.rs | 86 +++++++++++++++---- crates/egui/src/containers/frame.rs | 3 +- crates/egui/src/containers/popup.rs | 8 +- crates/egui/src/menu.rs | 20 ++--- crates/egui/src/style.rs | 21 ++++- crates/egui/src/ui.rs | 37 +++++++- crates/egui/src/widgets/separator.rs | 6 +- crates/egui_demo_lib/src/demo/context_menu.rs | 4 +- .../src/demo/demo_app_windows.rs | 1 - crates/emath/src/vec2.rs | 1 + tests/test_size_pass/Cargo.toml | 24 ++++++ tests/test_size_pass/src/main.rs | 25 ++++++ 13 files changed, 199 insertions(+), 45 deletions(-) create mode 100644 tests/test_size_pass/Cargo.toml create mode 100644 tests/test_size_pass/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 7f726392e339..c536538b39fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3628,6 +3628,14 @@ dependencies = [ "env_logger", ] +[[package]] +name = "test_size_pass" +version = "0.1.0" +dependencies = [ + "eframe", + "env_logger", +] + [[package]] name = "test_viewports" version = "0.1.0" diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 857569da3ac2..985d3af8c166 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -69,6 +69,7 @@ pub struct Area { constrain_rect: Option, order: Order, default_pos: Option, + default_size: Vec2, pivot: Align2, anchor: Option<(Align2, Vec2)>, new_pos: Option, @@ -87,6 +88,7 @@ impl Area { enabled: true, order: Order::Middle, default_pos: None, + default_size: Vec2::NAN, new_pos: None, pivot: Align2::LEFT_TOP, anchor: None, @@ -163,6 +165,35 @@ impl Area { self } + /// The size used for the [`Ui::max_rect`] the first frame. + /// + /// Text will wrap at this width, and images that expand to fill the available space + /// will expand to this size. + /// + /// If the contents are smaller than this size, the area will shrink to fit the contents. + /// If the contents overflow, the area will grow. + /// + /// If not set, [`style::Spacing::default_area_size`] will be used. + #[inline] + pub fn default_size(mut self, default_size: impl Into) -> Self { + self.default_size = default_size.into(); + self + } + + /// See [`Self::default_size`]. + #[inline] + pub fn default_width(mut self, default_width: f32) -> Self { + self.default_size.x = default_width; + self + } + + /// See [`Self::default_size`]. + #[inline] + pub fn default_height(mut self, default_height: f32) -> Self { + self.default_size.y = default_height; + self + } + /// Positions the window and prevents it from being moved #[inline] pub fn fixed_pos(mut self, fixed_pos: impl Into) -> Self { @@ -247,7 +278,7 @@ pub(crate) struct Prepared { /// This is so that we use the first frame to calculate the window size, /// and then can correctly position the window and its contents the next frame, /// without having one frame where the window is wrongly positioned or sized. - temporarily_invisible: bool, + sizing_pass: bool, } impl Area { @@ -272,6 +303,7 @@ impl Area { interactable, enabled, default_pos, + default_size, new_pos, pivot, anchor, @@ -292,11 +324,29 @@ impl Area { if is_new { ctx.request_repaint(); // if we don't know the previous size we are likely drawing the area in the wrong place } - let mut state = state.unwrap_or_else(|| State { - pivot_pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)), - pivot, - size: Vec2::ZERO, - interactable, + let mut state = state.unwrap_or_else(|| { + // during the sizing pass we will use this as the max size + let mut size = default_size; + + let default_area_size = ctx.style().spacing.default_area_size; + if size.x.is_nan() { + size.x = default_area_size.x; + } + if size.y.is_nan() { + size.y = default_area_size.y; + } + + if constrain { + let constrain_rect = constrain_rect.unwrap_or_else(|| ctx.screen_rect()); + size = size.at_most(constrain_rect.size()); + } + + State { + pivot_pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)), + pivot, + size, + interactable, + } }); state.pivot_pos = new_pos.unwrap_or(state.pivot_pos); state.interactable = interactable; @@ -365,7 +415,7 @@ impl Area { enabled, constrain, constrain_rect, - temporarily_invisible: is_new, + sizing_pass: is_new, } } @@ -431,12 +481,7 @@ impl Prepared { } }; - let max_rect = Rect::from_min_max( - self.state.left_top_pos(), - constrain_rect - .max - .at_least(self.state.left_top_pos() + Vec2::splat(32.0)), - ); + let max_rect = Rect::from_min_size(self.state.left_top_pos(), self.state.size); let clip_rect = constrain_rect; // Don't paint outside our bounds @@ -448,7 +493,9 @@ impl Prepared { clip_rect, ); ui.set_enabled(self.enabled); - ui.set_visible(!self.temporarily_invisible); + if self.sizing_pass { + ui.set_sizing_pass(); + } ui } @@ -461,11 +508,20 @@ impl Prepared { enabled: _, constrain: _, constrain_rect: _, - temporarily_invisible: _, + sizing_pass, } = self; state.size = content_ui.min_size(); + if sizing_pass { + // If during the sizing pass we measure our width to `123.45` and + // then try to wrap to exactly that next frame, + // we may accidentally wrap the last letter of some text. + // We only do this after the initial sizing pass though; + // otherwise we could end up with for-ever expanding areas. + state.size = state.size.ceil(); + } + ctx.memory_mut(|m| m.areas_mut().set_state(layer_id, state)); move_response diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 40bd17d3c4d5..8568a8f0b9b3 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -266,7 +266,8 @@ impl Frame { self.show_dyn(ui, Box::new(add_contents)) } - fn show_dyn<'c, R>( + /// Show using dynamic dispatch. + pub fn show_dyn<'c, R>( self, ui: &mut Ui, add_contents: Box R + 'c>, diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 9643a8eee31c..0aca413a9edf 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -260,15 +260,11 @@ fn show_tooltip_area_dyn<'c, R>( Area::new(area_id) .order(Order::Tooltip) .fixed_pos(window_pos) + .default_width(ctx.style().spacing.tooltip_width) .constrain_to(ctx.screen_rect()) .interactable(false) .show(ctx, |ui| { - Frame::popup(&ctx.style()) - .show(ui, |ui| { - ui.set_max_width(ui.spacing().tooltip_width); - add_contents(ui) - }) - .inner + Frame::popup(&ctx.style()).show_dyn(ui, add_contents).inner }) } diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 8b374ce7ad99..d23d5d20f319 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -133,10 +133,10 @@ pub(crate) fn submenu_button( } /// wrapper for the contents of every menu. -pub(crate) fn menu_ui<'c, R>( +fn menu_popup<'c, R>( ctx: &Context, - menu_id: Id, menu_state_arc: &Arc>, + menu_id: Id, add_contents: impl FnOnce(&mut Ui) -> R + 'c, ) -> InnerResponse { let pos = { @@ -150,6 +150,7 @@ pub(crate) fn menu_ui<'c, R>( .fixed_pos(pos) .constrain_to(ctx.screen_rect()) .interactable(true) + .default_width(ctx.style().spacing.menu_width) .sense(Sense::hover()); let area_response = area.show(ctx, |ui| { @@ -157,7 +158,6 @@ pub(crate) fn menu_ui<'c, R>( Frame::menu(ui.style()) .show(ui, |ui| { - ui.set_max_width(ui.spacing().menu_width); ui.set_menu_state(Some(menu_state_arc.clone())); ui.with_layout(Layout::top_down_justified(Align::LEFT), add_contents) .inner @@ -306,8 +306,7 @@ impl MenuRoot { add_contents: impl FnOnce(&mut Ui) -> R, ) -> (MenuResponse, Option>) { if self.id == button.id { - let inner_response = - MenuState::show(&button.ctx, &self.menu_state, self.id, add_contents); + let inner_response = menu_popup(&button.ctx, &self.menu_state, self.id, add_contents); let menu_state = self.menu_state.read(); if menu_state.response.is_close() { @@ -593,15 +592,6 @@ impl MenuState { self.response = MenuResponse::Close; } - pub fn show( - ctx: &Context, - menu_state: &Arc>, - id: Id, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> InnerResponse { - crate::menu::menu_ui(ctx, id, menu_state, add_contents) - } - fn show_submenu( &mut self, ctx: &Context, @@ -609,7 +599,7 @@ impl MenuState { add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option { let (sub_response, response) = self.submenu(id).map(|sub| { - let inner_response = Self::show(ctx, sub, id, add_contents); + let inner_response = menu_popup(ctx, sub, id, add_contents); (sub.read().response, inner_response.inner) })?; self.cascade_close_response(sub_response); diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index f2410747dfb4..9e34498eb704 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -316,10 +316,21 @@ pub struct Spacing { /// This is the spacing between the icon and the text pub icon_spacing: f32, + /// The size used for the [`Ui::max_rect`] the first frame. + /// + /// Text will wrap at this width, and images that expand to fill the available space + /// will expand to this size. + /// + /// If the contents are smaller than this size, the area will shrink to fit the contents. + /// If the contents overflow, the area will grow. + pub default_area_size: Vec2, + /// Width of a tooltip (`on_hover_ui`, `on_hover_text` etc). pub tooltip_width: f32, - /// The default width of a menu. + /// The default wrapping width of a menu. + /// + /// Items longer than this will wrap to a new line. pub menu_width: f32, /// Horizontal distance between a menu and a submenu. @@ -1073,8 +1084,9 @@ impl Default for Spacing { icon_width: 14.0, icon_width_inner: 8.0, icon_spacing: 4.0, + default_area_size: vec2(600.0, 400.0), tooltip_width: 600.0, - menu_width: 150.0, + menu_width: 400.0, menu_spacing: 2.0, combo_height: 200.0, scroll: Default::default(), @@ -1459,6 +1471,7 @@ impl Spacing { icon_width, icon_width_inner, icon_spacing, + default_area_size, tooltip_width, menu_width, menu_spacing, @@ -1509,6 +1522,10 @@ impl Spacing { ui.add(DragValue::new(combo_width).clamp_range(0.0..=1000.0)); ui.end_row(); + ui.label("Default area size"); + ui.add(two_drag_values(default_area_size, 0.0..=1000.0)); + ui.end_row(); + ui.label("TextEdit width"); ui.add(DragValue::new(text_edit_width).clamp_range(0.0..=1000.0)); ui.end_row(); diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index f3227109e367..40ffea8314e8 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -61,6 +61,10 @@ pub struct Ui { /// and all widgets will assume a gray style. enabled: bool, + /// Set to true in special cases where we do one frame + /// where we size up the contents of the Ui, without actually showing it. + sizing_pass: bool, + /// Indicates whether this Ui belongs to a Menu. menu_state: Option>>, } @@ -82,6 +86,7 @@ impl Ui { style, placer: Placer::new(max_rect, Layout::default()), enabled: true, + sizing_pass: false, menu_state: None, }; @@ -108,9 +113,18 @@ impl Ui { pub fn child_ui_with_id_source( &mut self, max_rect: Rect, - layout: Layout, + mut layout: Layout, id_source: impl Hash, ) -> Self { + if self.sizing_pass { + // During the sizing pass we want widgets to use up as little space as possible, + // so that we measure the only the space we _need_. + layout.cross_justify = false; + if layout.cross_align == Align::Center { + layout.cross_align = Align::Min; + } + } + debug_assert!(!max_rect.any_nan()); let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value(); self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1); @@ -121,6 +135,7 @@ impl Ui { style: self.style.clone(), placer: Placer::new(max_rect, layout), enabled: self.enabled, + sizing_pass: self.sizing_pass, menu_state: self.menu_state.clone(), }; @@ -140,6 +155,26 @@ impl Ui { // ------------------------------------------------- + /// Set to true in special cases where we do one frame + /// where we size up the contents of the Ui, without actually showing it. + /// + /// This will also turn the Ui invisible. + /// Should be called right after [`Self::new`], if at all. + #[inline] + pub fn set_sizing_pass(&mut self) { + self.sizing_pass = true; + self.set_visible(false); + } + + /// Set to true in special cases where we do one frame + /// where we size up the contents of the Ui, without actually showing it. + #[inline] + pub fn is_sizing_pass(&self) -> bool { + self.sizing_pass + } + + // ------------------------------------------------- + /// A unique identity of this [`Ui`]. #[inline] pub fn id(&self) -> Id { diff --git a/crates/egui/src/widgets/separator.rs b/crates/egui/src/widgets/separator.rs index 83c01f31200b..f408c6fb0c5b 100644 --- a/crates/egui/src/widgets/separator.rs +++ b/crates/egui/src/widgets/separator.rs @@ -96,7 +96,11 @@ impl Widget for Separator { let is_horizontal_line = is_horizontal_line .unwrap_or_else(|| ui.is_grid() || !ui.layout().main_dir().is_horizontal()); - let available_space = ui.available_size_before_wrap(); + let available_space = if ui.is_sizing_pass() { + Vec2::ZERO + } else { + ui.available_size_before_wrap() + }; let size = if is_horizontal_line { vec2(available_space.x, spacing) diff --git a/crates/egui_demo_lib/src/demo/context_menu.rs b/crates/egui_demo_lib/src/demo/context_menu.rs index 5195a2b06092..00d1431ef147 100644 --- a/crates/egui_demo_lib/src/demo/context_menu.rs +++ b/crates/egui_demo_lib/src/demo/context_menu.rs @@ -80,8 +80,6 @@ impl super::View for ContextMenus { ui.label("Right-click plot to edit it!"); ui.horizontal(|ui| { self.example_plot(ui).context_menu(|ui| { - ui.set_min_width(220.0); - ui.menu_button("Plot", |ui| { if ui.radio_value(&mut self.plot, Plot::Sin, "Sin").clicked() || ui @@ -188,6 +186,6 @@ impl ContextMenus { ui.close_menu(); } }); - let _ = ui.button("Very long text for this item"); + let _ = ui.button("Very long text for this item that should be wrapped"); } } diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 1e34c4647596..dc0e91f1b476 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -334,7 +334,6 @@ fn file_menu_button(ui: &mut Ui) { } ui.menu_button("File", |ui| { - ui.set_min_width(220.0); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); // On the web the browser controls the zoom diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index 99f7d0c05c7d..29050935fdaf 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -131,6 +131,7 @@ impl Vec2 { pub const ZERO: Self = Self { x: 0.0, y: 0.0 }; pub const INFINITY: Self = Self::splat(f32::INFINITY); + pub const NAN: Self = Self::splat(f32::NAN); #[inline(always)] pub const fn new(x: f32, y: f32) -> Self { diff --git a/tests/test_size_pass/Cargo.toml b/tests/test_size_pass/Cargo.toml new file mode 100644 index 000000000000..6886c7af2a1f --- /dev/null +++ b/tests/test_size_pass/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "test_size_pass" +version = "0.1.0" +authors = ["Emil Ernerfeldt "] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.76" +publish = false + +[lints] +workspace = true + +[features] +wgpu = ["eframe/wgpu"] + +[dependencies] +eframe = { workspace = true, features = [ + "default", + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO +] } +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/tests/test_size_pass/src/main.rs b/tests/test_size_pass/src/main.rs new file mode 100644 index 000000000000..13b49911ffb3 --- /dev/null +++ b/tests/test_size_pass/src/main.rs @@ -0,0 +1,25 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(rustdoc::missing_crate_level_docs)] // it's a test + +use eframe::egui; + +fn main() -> eframe::Result<()> { + env_logger::init(); // Use `RUST_LOG=debug` to see logs. + + let options = eframe::NativeOptions::default(); + eframe::run_simple_native("My egui App", options, move |ctx, _frame| { + egui::CentralPanel::default().show(ctx, |ui| { + ui.label("The menu should be as wide as the widest button"); + ui.menu_button("Click for menu", |ui| { + let _ = ui.button("Narrow").clicked(); + let _ = ui.button("Very wide text").clicked(); + let _ = ui.button("Narrow").clicked(); + }); + + ui.label("Hover for tooltip").on_hover_ui(|ui| { + ui.label("A separator:"); + ui.separator(); + }); + }); + }) +} From ffbc63e14708f04a7d40ca9bf486e138ddaecab8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 29 May 2024 11:47:10 +0200 Subject: [PATCH 0124/1202] `ComboBox`: fix justified layout of popup if wider than parent button (#4570) * Closes https://github.com/emilk/egui/issues/4452 The `ComboBox` popup has a justified layout to make selection of items easier. Thanks to [the new sizing pass logic](https://github.com/emilk/egui/issues/4535) we don't have to know the final width in advance: ![image](https://github.com/emilk/egui/assets/1148717/53b0dda7-14c9-43be-a073-ad49865e69a6) --- crates/egui/src/containers/popup.rs | 11 +++++---- .../egui_demo_lib/src/demo/widget_gallery.rs | 2 -- tests/test_size_pass/src/main.rs | 23 +++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 0aca413a9edf..f0c828197287 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -307,7 +307,7 @@ pub fn popup_below_widget( /// /// Useful for drop-down menus (combo boxes) or suggestion menus under text fields. /// -/// The opened popup will have the same width as the parent. +/// The opened popup will have a minimum width matching its parent. /// /// You must open the popup with [`Memory::open_popup`] or [`Memory::toggle_popup`]. /// @@ -341,18 +341,21 @@ pub fn popup_above_or_below_widget( AboveOrBelow::Below => (widget_response.rect.left_bottom(), Align2::LEFT_TOP), }; + let frame = Frame::popup(parent_ui.style()); + let frame_margin = frame.total_margin(); + let inner_width = widget_response.rect.width() - frame_margin.sum().x; + let inner = Area::new(popup_id) .order(Order::Foreground) .constrain(true) .fixed_pos(pos) + .default_width(inner_width) .pivot(pivot) .show(parent_ui.ctx(), |ui| { - let frame = Frame::popup(parent_ui.style()); - let frame_margin = frame.total_margin(); frame .show(ui, |ui| { ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| { - ui.set_width(widget_response.rect.width() - frame_margin.sum().x); + ui.set_min_width(inner_width); add_contents(ui) }) .inner diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 6f5d26f5e57e..babc8dd95680 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -172,8 +172,6 @@ impl WidgetGallery { egui::ComboBox::from_label("Take your pick") .selected_text(format!("{radio:?}")) .show_ui(ui, |ui| { - ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); - ui.set_min_width(60.0); ui.selectable_value(radio, Enum::First, "First"); ui.selectable_value(radio, Enum::Second, "Second"); ui.selectable_value(radio, Enum::Third, "Third"); diff --git a/tests/test_size_pass/src/main.rs b/tests/test_size_pass/src/main.rs index 13b49911ffb3..f841cd1d9b89 100644 --- a/tests/test_size_pass/src/main.rs +++ b/tests/test_size_pass/src/main.rs @@ -9,6 +9,12 @@ fn main() -> eframe::Result<()> { let options = eframe::NativeOptions::default(); eframe::run_simple_native("My egui App", options, move |ctx, _frame| { egui::CentralPanel::default().show(ctx, |ui| { + if ui.button("Reset egui memory").clicked() { + ctx.memory_mut(|mem| *mem = Default::default()); + } + + ui.separator(); + ui.label("The menu should be as wide as the widest button"); ui.menu_button("Click for menu", |ui| { let _ = ui.button("Narrow").clicked(); @@ -20,6 +26,23 @@ fn main() -> eframe::Result<()> { ui.label("A separator:"); ui.separator(); }); + + ui.separator(); + + let alternatives = [ + "Short", + "Min", + "Very very long text that will extend", + "Short", + ]; + let mut selected = 1; + + egui::ComboBox::from_label("ComboBox").show_index( + ui, + &mut selected, + alternatives.len(), + |i| alternatives[i], + ); }); }) } From 5eee463851e1521fc5191ca8887ca2af364cdca4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 29 May 2024 11:48:50 +0200 Subject: [PATCH 0125/1202] =?UTF-8?q?Replace=20some=20`...`=20with=20`?= =?UTF-8?q?=E2=80=A6`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/egui/src/style.rs | 2 +- crates/egui_demo_app/src/backend_panel.rs | 6 +++--- crates/egui_demo_lib/src/demo/context_menu.rs | 10 +++++----- crates/egui_plot/src/items/values.rs | 2 +- examples/user_attention/src/main.rs | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 9e34498eb704..c04624623c3e 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -232,7 +232,7 @@ pub struct Style { } impl Style { - // TODO(emilk): rename style.interact() to maybe... `style.interactive` ? + // TODO(emilk): rename style.interact() to maybe… `style.interactive` ? /// Use this style for interactive things. /// Note that you must already have a response, /// i.e. you must allocate space and interact BEFORE painting the widget! diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index f82094d71fa5..e50c6b29db12 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -154,10 +154,10 @@ impl BackendPanel { .button("Wait 2s, then request repaint after another 3s") .clicked() { - log::info!("Waiting 2s before requesting repaint..."); + log::info!("Waiting 2s before requesting repaint…"); let ctx = ui.ctx().clone(); call_after_delay(std::time::Duration::from_secs(2), move || { - log::info!("Request a repaint in 3s..."); + log::info!("Request a repaint in 3s…"); ctx.request_repaint_after(std::time::Duration::from_secs(3)); }); } @@ -298,7 +298,7 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) { let mut size = None; egui::ComboBox::from_id_source("viewport-size-combo") - .selected_text("Resize to...") + .selected_text("Resize to…") .show_ui(ui, |ui| { ui.selectable_value( &mut size, diff --git a/crates/egui_demo_lib/src/demo/context_menu.rs b/crates/egui_demo_lib/src/demo/context_menu.rs index 00d1431ef147..86bd2f9d1087 100644 --- a/crates/egui_demo_lib/src/demo/context_menu.rs +++ b/crates/egui_demo_lib/src/demo/context_menu.rs @@ -156,24 +156,24 @@ impl ContextMenus { } fn nested_menus(ui: &mut egui::Ui) { - if ui.button("Open...").clicked() { + if ui.button("Open…").clicked() { ui.close_menu(); } ui.menu_button("SubMenu", |ui| { ui.menu_button("SubMenu", |ui| { - if ui.button("Open...").clicked() { + if ui.button("Open…").clicked() { ui.close_menu(); } let _ = ui.button("Item"); }); ui.menu_button("SubMenu", |ui| { - if ui.button("Open...").clicked() { + if ui.button("Open…").clicked() { ui.close_menu(); } let _ = ui.button("Item"); }); let _ = ui.button("Item"); - if ui.button("Open...").clicked() { + if ui.button("Open…").clicked() { ui.close_menu(); } }); @@ -182,7 +182,7 @@ impl ContextMenus { let _ = ui.button("Item2"); let _ = ui.button("Item3"); let _ = ui.button("Item4"); - if ui.button("Open...").clicked() { + if ui.button("Open…").clicked() { ui.close_menu(); } }); diff --git a/crates/egui_plot/src/items/values.rs b/crates/egui_plot/src/items/values.rs index 6e9bff096b67..dbbb59bb41ab 100644 --- a/crates/egui_plot/src/items/values.rs +++ b/crates/egui_plot/src/items/values.rs @@ -426,7 +426,7 @@ impl ExplicitGenerator { /// Result of [`super::PlotItem::find_closest()`] search, identifies an element inside the item for immediate use pub struct ClosestElem { - /// Position of hovered-over value (or bar/box-plot/...) in `PlotItem` + /// Position of hovered-over value (or bar/box-plot/…) in `PlotItem` pub index: usize, /// Squared distance from the mouse cursor (needed to compare against other `PlotItems`, which might be nearer) diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index 15f0676cf0a9..53c170a11444 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -103,9 +103,9 @@ impl eframe::App for Application { None => "Unfocus the window, fast!".to_owned(), Some(t) => { if let Ok(elapsed) = t.duration_since(SystemTime::now()) { - format!("Resetting attention in {} s...", elapsed.as_secs()) + format!("Resetting attention in {} s…", elapsed.as_secs()) } else { - "Resetting attention...".to_owned() + "Resetting attention…".to_owned() } } } From 514ee0c4334a0917855f9c7082e03b253851a4cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Proch=C3=A1zka?= Date: Wed, 29 May 2024 12:54:33 +0200 Subject: [PATCH 0126/1202] Improve web text agent (#4561) - Closes https://github.com/emilk/egui/issues/4060 - no longer aligned to top - Closes https://github.com/emilk/egui/issues/4479 - `canvas.style` is not set anywhere anymore - Closes https://github.com/emilk/egui/issues/2231 - same as #4060 - Closes https://github.com/emilk/egui/issues/3618 - there is now one `` per `eframe` app, and it's removed transitively by `WebRunner::destroy -> AppRunner::drop -> TextAgent::drop` This PR improves the text agent to make fewer assumptions about how `egui` is embedded into the page: - Text agent no longer sets the canvas position - There is now a text agent for each instance of `WebRunner` - The input element is now moved to the correct position, so the OS can display the IME window in the correct place. Before it would typically be outside of the viewport The best way to test this is to build & server the web demo locally: ``` scripts/build_demo_web.sh && scripts/start_server.sh ``` Then open the EasyMark editor, and try using IME to input some emojis: http://localhost:8888/#EasyMarkEditor To open the emoji keyboard use: - win + . on Windows - ctrl + cmd + space on Mac Tested on: - [x] Windows - [x] Linux - [x] MacOS - [x] Android - [x] iOS ## Migration guide The canvas no longer controls its own size/position on the page. This means that those properties can now be controlled entirely via HTML and CSS, and multiple separate `eframe` apps can coexist better on a single page. To match the old behavior, set the `canvas` width and height to 100% of the `body` element: ```html ``` ```css /* remove default margins and use full viewport */ html, body { margin: 0; width: 100%; height: 100%; } canvas { /* match parent element size */ width: 100%; height: 100%; } ``` Note that there is no need to set `position: absolute`/`left: 50%; transform: translateX(-50%)`/etc., and setting those properties may poorly affect the sharpness of `egui`-rendered text. Because `eframe` no longer updates the canvas style in any way, it also means that on mobile, the canvas no longer collapses upwards to make space for a mobile keyboard. This should be solved in other ways: https://github.com/emilk/egui/issues/4572 --- crates/eframe/src/web/app_runner.rs | 15 +- crates/eframe/src/web/events.rs | 18 +- crates/eframe/src/web/mod.rs | 23 ++ crates/eframe/src/web/text_agent.rs | 339 ++++++++++++---------------- crates/eframe/src/web/web_runner.rs | 7 +- crates/egui/src/data/output.rs | 2 +- 6 files changed, 188 insertions(+), 216 deletions(-) diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 75fee203417f..f8b7f14a9daf 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -2,7 +2,7 @@ use egui::TexturesDelta; use crate::{epi, App}; -use super::{now_sec, web_painter::WebPainter, NeedRepaint}; +use super::{now_sec, text_agent::TextAgent, web_painter::WebPainter, NeedRepaint}; pub struct AppRunner { #[allow(dead_code)] @@ -14,7 +14,7 @@ pub struct AppRunner { app: Box, pub(crate) needs_repaint: std::sync::Arc, last_save_time: f64, - pub(crate) ime: Option, + pub(crate) text_agent: TextAgent, pub(crate) mutable_text_under_cursor: bool, // Output for the last run: @@ -35,6 +35,7 @@ impl AppRunner { canvas_id: &str, web_options: crate::WebOptions, app_creator: epi::AppCreator, + text_agent: TextAgent, ) -> Result { let painter = super::ActiveWebPainter::new(canvas_id, &web_options).await?; @@ -119,7 +120,7 @@ impl AppRunner { app, needs_repaint, last_save_time: now_sec(), - ime: None, + text_agent, mutable_text_under_cursor: false, textures_delta: Default::default(), clipped_primitives: None, @@ -270,9 +271,11 @@ impl AppRunner { self.mutable_text_under_cursor = mutable_text_under_cursor; - if self.ime != ime { - super::text_agent::move_text_cursor(ime, self.canvas()); - self.ime = ime; + if let Err(err) = self.text_agent.move_to(ime, self.canvas()) { + log::error!( + "failed to update text agent position: {}", + super::string_from_js_value(&err) + ); } } } diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 33d36c356d79..b2f371bde3ee 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -94,8 +94,8 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa if !modifiers.ctrl && !modifiers.command && !should_ignore_key(&key) - // When text agent is shown, it sends text event instead. - && text_agent::text_agent().hidden() + // When text agent is focused, it is responsible for handling input events + && !runner.text_agent.has_focus() { runner.input.raw.events.push(egui::Event::Text(key)); } @@ -375,10 +375,12 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu // event callback, which is why we run the app logic here and now: runner.logic(); + runner + .text_agent + .set_focus(runner.mutable_text_under_cursor); + // Make sure we paint the output of the above logic call asap: runner.needs_repaint.repaint_asap(); - - text_agent::update_text_agent(runner); } event.stop_propagation(); event.prevent_default(); @@ -467,13 +469,15 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu runner.input.raw.events.push(egui::Event::PointerGone); push_touches(runner, egui::TouchPhase::End, &event); + + runner + .text_agent + .set_focus(runner.mutable_text_under_cursor); + runner.needs_repaint.repaint_asap(); event.stop_propagation(); event.prevent_default(); } - - // Finally, focus or blur text agent to toggle mobile keyboard: - text_agent::update_text_agent(runner); }, )?; diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index f98bbc1ed014..50e7aeb363e3 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -53,6 +53,29 @@ pub(crate) fn string_from_js_value(value: &JsValue) -> String { value.as_string().unwrap_or_else(|| format!("{value:#?}")) } +/// Returns the `Element` with active focus. +/// +/// Elements can only be focused if they are: +/// - ``/`` with an `href` attribute +/// - ``/`