From beb2ecf7e475eaa07ccc755626e05c1906c4e750 Mon Sep 17 00:00:00 2001 From: jacekpoz <64381190+jacekpoz@users.noreply.github.com> Date: Wed, 26 Jul 2023 16:59:49 +0000 Subject: [PATCH 01/54] fix typo in NativeOptions docs (#3108) Co-authored-by: jacekpoz --- crates/eframe/src/epi/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index 0f6f42290..fd4f0280f 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -431,7 +431,7 @@ pub struct NativeOptions { /// will be used instead. /// /// ### On Wayland - /// On Wauland this sets the Application ID for the window. + /// On Wayland this sets the Application ID for the window. /// /// The application ID is used in several places of the compositor, e.g. for /// grouping windows of the same application. It is also important for From 67d5bd4392daaf520fd733aa190dca5fde799470 Mon Sep 17 00:00:00 2001 From: bernsteining <32954876+bernsteining@users.noreply.github.com> Date: Wed, 26 Jul 2023 19:00:13 +0200 Subject: [PATCH 02/54] fix(docs): remove duplicate typo (#3111) --- crates/egui-winit/src/clipboard.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui-winit/src/clipboard.rs b/crates/egui-winit/src/clipboard.rs index e80414961..71d913dbb 100644 --- a/crates/egui-winit/src/clipboard.rs +++ b/crates/egui-winit/src/clipboard.rs @@ -3,7 +3,7 @@ use raw_window_handle::HasRawDisplayHandle; /// Handles interfacing with the OS clipboard. /// /// If the "clipboard" feature is off, or we cannot connect to the OS clipboard, -/// then a fallback clipboard that just works works within the same app is used instead. +/// then a fallback clipboard that just works within the same app is used instead. pub struct Clipboard { #[cfg(all(feature = "arboard", not(target_os = "android")))] arboard: Option, From 936c9583b5b15a766c9aac52ccc35f1625d5d69d Mon Sep 17 00:00:00 2001 From: Forest Anderson Date: Wed, 26 Jul 2023 13:00:51 -0400 Subject: [PATCH 03/54] Spelling fix (#3133) --- crates/egui_demo_lib/src/demo/plot_demo.rs | 42 +++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 12d79ce0e..8c5bcfd3c 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -39,8 +39,8 @@ pub struct PlotDemo { charts_demo: ChartsDemo, items_demo: ItemsDemo, interaction_demo: InteractionDemo, - custom_axes_demo: CustomAxisDemo, - linked_axes_demo: LinkedAxisDemo, + custom_axes_demo: CustomAxesDemo, + linked_axes_demo: LinkedAxesDemo, open_panel: Panel, } @@ -448,19 +448,19 @@ impl LegendDemo { // ---------------------------------------------------------------------------- #[derive(PartialEq, Default)] -struct CustomAxisDemo {} +struct CustomAxesDemo {} -impl CustomAxisDemo { +impl CustomAxesDemo { const MINS_PER_DAY: f64 = 24.0 * 60.0; const MINS_PER_H: f64 = 60.0; fn logistic_fn() -> Line { fn days(min: f64) -> f64 { - CustomAxisDemo::MINS_PER_DAY * min + CustomAxesDemo::MINS_PER_DAY * min } let values = PlotPoints::from_explicit_callback( - move |x| 1.0 / (1.0 + (-2.5 * (x / CustomAxisDemo::MINS_PER_DAY - 2.0)).exp()), + move |x| 1.0 / (1.0 + (-2.5 * (x / CustomAxesDemo::MINS_PER_DAY - 2.0)).exp()), days(0.0)..days(5.0), 100, ); @@ -504,8 +504,8 @@ impl CustomAxisDemo { #[allow(clippy::unused_self)] fn ui(&mut self, ui: &mut Ui) -> Response { - const MINS_PER_DAY: f64 = CustomAxisDemo::MINS_PER_DAY; - const MINS_PER_H: f64 = CustomAxisDemo::MINS_PER_H; + const MINS_PER_DAY: f64 = CustomAxesDemo::MINS_PER_DAY; + const MINS_PER_H: f64 = CustomAxesDemo::MINS_PER_H; fn day(x: f64) -> f64 { (x / MINS_PER_DAY).floor() @@ -561,10 +561,10 @@ impl CustomAxisDemo { .data_aspect(2.0 * MINS_PER_DAY as f32) .x_axis_formatter(x_fmt) .y_axis_formatter(y_fmt) - .x_grid_spacer(CustomAxisDemo::x_grid) + .x_grid_spacer(CustomAxesDemo::x_grid) .label_formatter(label_fmt) .show(ui, |plot_ui| { - plot_ui.line(CustomAxisDemo::logistic_fn()); + plot_ui.line(CustomAxesDemo::logistic_fn()); }) .response } @@ -573,14 +573,14 @@ impl CustomAxisDemo { // ---------------------------------------------------------------------------- #[derive(PartialEq)] -struct LinkedAxisDemo { +struct LinkedAxesDemo { link_x: bool, link_y: bool, link_cursor_x: bool, link_cursor_y: bool, } -impl Default for LinkedAxisDemo { +impl Default for LinkedAxesDemo { fn default() -> Self { let link_x = true; let link_y = false; @@ -595,7 +595,7 @@ impl Default for LinkedAxisDemo { } } -impl LinkedAxisDemo { +impl LinkedAxesDemo { fn line_with_slope(slope: f64) -> Line { Line::new(PlotPoints::from_explicit_callback( move |x| slope * x, @@ -621,11 +621,11 @@ impl LinkedAxisDemo { } fn configure_plot(plot_ui: &mut plot::PlotUi) { - plot_ui.line(LinkedAxisDemo::line_with_slope(0.5)); - plot_ui.line(LinkedAxisDemo::line_with_slope(1.0)); - plot_ui.line(LinkedAxisDemo::line_with_slope(2.0)); - plot_ui.line(LinkedAxisDemo::sin()); - plot_ui.line(LinkedAxisDemo::cos()); + plot_ui.line(LinkedAxesDemo::line_with_slope(0.5)); + plot_ui.line(LinkedAxesDemo::line_with_slope(1.0)); + plot_ui.line(LinkedAxesDemo::line_with_slope(2.0)); + plot_ui.line(LinkedAxesDemo::sin()); + plot_ui.line(LinkedAxesDemo::cos()); } fn ui(&mut self, ui: &mut Ui) -> Response { @@ -648,14 +648,14 @@ impl LinkedAxisDemo { .height(250.0) .link_axis(link_group_id, self.link_x, self.link_y) .link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y) - .show(ui, LinkedAxisDemo::configure_plot); + .show(ui, LinkedAxesDemo::configure_plot); Plot::new("linked_axis_2") .data_aspect(2.0) .width(150.0) .height(250.0) .link_axis(link_group_id, self.link_x, self.link_y) .link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y) - .show(ui, LinkedAxisDemo::configure_plot); + .show(ui, LinkedAxesDemo::configure_plot); }); Plot::new("linked_axis_3") .data_aspect(0.5) @@ -663,7 +663,7 @@ impl LinkedAxisDemo { .height(150.0) .link_axis(link_group_id, self.link_x, self.link_y) .link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y) - .show(ui, LinkedAxisDemo::configure_plot) + .show(ui, LinkedAxesDemo::configure_plot) .response } } From 65eecde2446495c0ea88c37318ead66457c7ab81 Mon Sep 17 00:00:00 2001 From: Matt Fellenz Date: Wed, 26 Jul 2023 10:07:05 -0700 Subject: [PATCH 04/54] Use cfg attribute (#3113) --- crates/eframe/src/native/run.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 367daea74..7771265a1 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -144,11 +144,13 @@ fn run_and_return( // Platform-dependent event handlers to workaround a winit bug // See: https://github.com/rust-windowing/winit/issues/987 // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => { + #[cfg(windows)] + winit::event::Event::RedrawEventsCleared => { next_repaint_time = extremely_far_future(); winit_app.run_ui_and_paint() } - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => { + #[cfg(not(windows))] + winit::event::Event::RedrawRequested(_) => { next_repaint_time = extremely_far_future(); winit_app.run_ui_and_paint() } From 339b758c60d04688e7b53cd964473524650036e8 Mon Sep 17 00:00:00 2001 From: Serv Date: Wed, 26 Jul 2023 22:21:28 +0400 Subject: [PATCH 05/54] fixed char limit (#3173) --- crates/egui/src/widgets/text_edit/builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index b76c6e336..c9eaa1d4b 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -1185,7 +1185,7 @@ fn insert_text( if char_limit < usize::MAX { let mut new_string = text_to_insert; // Avoid subtract with overflow panic - let cutoff = char_limit.saturating_sub(text.as_str().len()); + let cutoff = char_limit.saturating_sub(text.as_str().chars().count()); new_string = match new_string.char_indices().nth(cutoff) { None => new_string, From bdc8795b0476c25faab927fc3c731f2d79f2098f Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Fri, 28 Jul 2023 17:53:06 +0200 Subject: [PATCH 06/54] Set the correct unicode character for "ctrl" shortcuts (#3186) Fixes rerun-io/rerun#2862 --- crates/egui/src/data/input.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 99ea6ac03..29ff3f5dd 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -610,11 +610,11 @@ pub struct ModifierNames<'a> { } impl ModifierNames<'static> { - /// ⌥ ^ ⇧ ⌘ - NOTE: not supported by the default egui font. + /// ⌥ ⌃ ⇧ ⌘ - NOTE: not supported by the default egui font. pub const SYMBOLS: Self = Self { is_short: true, alt: "⌥", - ctrl: "^", + ctrl: "⌃", shift: "⇧", mac_cmd: "⌘", mac_alt: "⌥", @@ -906,7 +906,7 @@ fn format_kb_shortcut() { cmd_shift_f.format(&ModifierNames::NAMES, true), "Shift+Cmd+F" ); - assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, false), "^⇧F"); + assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, false), "⌃⇧F"); assert_eq!(cmd_shift_f.format(&ModifierNames::SYMBOLS, true), "⇧⌘F"); } From 924a903610f1bee64178531d943a91e8922411aa Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 8 Aug 2023 10:34:37 +0200 Subject: [PATCH 07/54] Rename _typos.toml to .typos.toml --- _typos.toml => .typos.toml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename _typos.toml => .typos.toml (100%) diff --git a/_typos.toml b/.typos.toml similarity index 100% rename from _typos.toml rename to .typos.toml From 0e5f93b65d7613de58ce8d16e9eba652d1d3d562 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Tue, 8 Aug 2023 11:50:10 +0200 Subject: [PATCH 08/54] Add `Margin::expand_rect` and `shrink_rect` (#3214) * Add `Margin::expand_rect` and `shrink_rect` * fix typo * Use the new helpers --- crates/egui/src/containers/frame.rs | 16 +++++----------- crates/egui/src/containers/scroll_area.rs | 2 +- crates/egui/src/style.rs | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index fa1caee70..08b5e5a0e 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -193,9 +193,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 = outer_rect_bounds; - inner_rect.min += self.outer_margin.left_top() + self.inner_margin.left_top(); - inner_rect.max -= self.outer_margin.right_bottom() + self.inner_margin.right_bottom(); + let mut inner_rect = (self.inner_margin + self.outer_margin).shrink_rect(outer_rect_bounds); // Make sure we don't shrink to the negative: inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x); @@ -256,17 +254,13 @@ impl Frame { impl Prepared { fn paint_rect(&self) -> Rect { - let mut rect = self.content_ui.min_rect(); - rect.min -= self.frame.inner_margin.left_top(); - rect.max += self.frame.inner_margin.right_bottom(); - rect + self.frame + .inner_margin + .expand_rect(self.content_ui.min_rect()) } fn content_with_margin(&self) -> Rect { - let mut rect = self.content_ui.min_rect(); - rect.min -= self.frame.inner_margin.left_top() + self.frame.outer_margin.left_top(); - rect.max += self.frame.inner_margin.right_bottom() + self.frame.outer_margin.right_bottom(); - rect + (self.frame.inner_margin + self.frame.outer_margin).expand_rect(self.content_ui.min_rect()) } pub fn end(self, ui: &mut Ui) -> Response { diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 2f67bf3e0..341e61f2f 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -459,7 +459,7 @@ impl ScrollArea { content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d]; } } - // Make sure we din't accidentally expand the clip rect + // Make sure we didn't accidentally expand the clip rect content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); content_ui.set_clip_rect(content_clip_rect); } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index eb3b6036b..09eb5835b 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -360,30 +360,46 @@ impl Margin { } /// Total margins on both sides + #[inline] pub fn sum(&self) -> Vec2 { vec2(self.left + self.right, self.top + self.bottom) } + #[inline] pub fn left_top(&self) -> Vec2 { vec2(self.left, self.top) } + #[inline] pub fn right_bottom(&self) -> Vec2 { vec2(self.right, self.bottom) } + #[inline] pub fn is_same(&self) -> bool { self.left == self.right && self.left == self.top && self.left == self.bottom } + + #[inline] + pub fn expand_rect(&self, rect: Rect) -> Rect { + Rect::from_min_max(rect.min - self.left_top(), rect.max + self.right_bottom()) + } + + #[inline] + pub fn shrink_rect(&self, rect: Rect) -> Rect { + Rect::from_min_max(rect.min + self.left_top(), rect.max - self.right_bottom()) + } } impl From for Margin { + #[inline] fn from(v: f32) -> Self { Self::same(v) } } impl From for Margin { + #[inline] fn from(v: Vec2) -> Self { Self::symmetric(v.x, v.y) } @@ -392,6 +408,7 @@ impl From for Margin { impl std::ops::Add for Margin { type Output = Self; + #[inline] fn add(self, other: Self) -> Self { Self { left: self.left + other.left, From cd917e49f210b14b6961379e672fa60f84a78fe6 Mon Sep 17 00:00:00 2001 From: lampsitter <96946613+lampsitter@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:07:40 +0200 Subject: [PATCH 09/54] Separate text cursor from selection visuals (#3181) --- crates/egui/src/style.rs | 11 +++++++---- crates/egui/src/widgets/text_edit/builder.rs | 7 ++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 09eb5835b..4a8dba7c0 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -508,7 +508,8 @@ pub struct Visuals { pub resize_corner_size: f32, - pub text_cursor_width: 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, @@ -784,7 +785,7 @@ impl Visuals { popup_shadow: Shadow::small_dark(), resize_corner_size: 12.0, - text_cursor_width: 2.0, + text_cursor: Stroke::new(2.0, Color32::from_rgb(192, 222, 255)), text_cursor_preview: false, clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion button_frame: true, @@ -817,6 +818,7 @@ impl Visuals { panel_fill: Color32::from_gray(248), popup_shadow: Shadow::small_light(), + text_cursor: Stroke::new(2.0, Color32::from_rgb(0, 83, 125)), ..Self::dark() } } @@ -1351,7 +1353,7 @@ impl Visuals { popup_shadow, resize_corner_size, - text_cursor_width, + text_cursor, text_cursor_preview, clip_rect_margin, button_frame, @@ -1409,8 +1411,9 @@ impl Visuals { }); ui_color(ui, hyperlink_color, "hyperlink_color"); + stroke_ui(ui, text_cursor, "Text Cursor"); + ui.add(Slider::new(resize_corner_size, 0.0..=20.0).text("resize_corner_size")); - ui.add(Slider::new(text_cursor_width, 0.0..=4.0).text("text_cursor_width")); 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")); diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index c9eaa1d4b..e92bf9dbc 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -1138,7 +1138,7 @@ fn paint_cursor_end( galley: &Galley, cursor: &Cursor, ) -> Rect { - let stroke = ui.visuals().selection.stroke; + let stroke = ui.visuals().text_cursor; let mut cursor_pos = galley.pos_from_cursor(cursor).translate(pos.to_vec2()); cursor_pos.max.y = cursor_pos.max.y.at_least(cursor_pos.min.y + row_height); // Handle completely empty galleys @@ -1147,10 +1147,7 @@ fn paint_cursor_end( let top = cursor_pos.center_top(); let bottom = cursor_pos.center_bottom(); - painter.line_segment( - [top, bottom], - (ui.visuals().text_cursor_width, stroke.color), - ); + painter.line_segment([top, bottom], (stroke.width, stroke.color)); if false { // Roof/floor: From d483ac47ba05311cbb2098e27f31a89450b3560f Mon Sep 17 00:00:00 2001 From: Felix Wiegand Date: Wed, 9 Aug 2023 11:08:04 +0200 Subject: [PATCH 10/54] Fix auto_bounds when only one axis has restricted navigation (#3171) --- crates/egui/src/widgets/plot/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui/src/widgets/plot/mod.rs index c5db91992..8f04be4fa 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui/src/widgets/plot/mod.rs @@ -864,7 +864,7 @@ impl Plot { delta.y = 0.0; } transform.translate_bounds(delta); - bounds_modified = true.into(); + bounds_modified = allow_drag; } // Zooming @@ -935,7 +935,7 @@ impl Plot { } if zoom_factor != Vec2::splat(1.0) { transform.zoom(zoom_factor, hover_pos); - bounds_modified = true.into(); + bounds_modified = allow_zoom; } } if allow_scroll { From 387b07568198c04c7f15ebeab8c4a6533bc65a26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:15:37 +0200 Subject: [PATCH 11/54] Bump xml-rs from 0.8.13 to 0.8.15 (#3145) Bumps [xml-rs](https://github.com/kornelski/xml-rs) from 0.8.13 to 0.8.15. - [Changelog](https://github.com/kornelski/xml-rs/blob/main/Changelog.md) - [Commits](https://github.com/kornelski/xml-rs/compare/0.8.13...0.8.15) --- updated-dependencies: - dependency-name: xml-rs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c781b851b..632040aa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4465,9 +4465,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.13" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d8f380ae16a37b30e6a2cf67040608071384b1450c189e61bea3ff57cde922d" +checksum = "5a56c84a8ccd4258aed21c92f70c0f6dea75356b6892ae27c24139da456f9336" [[package]] name = "xmlparser" From 67ba4f2811d49c1b2446d114e5a7e7f31ce50368 Mon Sep 17 00:00:00 2001 From: Brian Janssen Date: Wed, 9 Aug 2023 11:48:54 +0200 Subject: [PATCH 12/54] Add comment explaining Tab button (#2872) --- crates/egui-winit/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 75c9028cf..ae82f6e4c 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -307,6 +307,7 @@ impl State { } WindowEvent::KeyboardInput { input, .. } => { self.on_keyboard_input(input); + // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes. let consumed = egui_ctx.wants_keyboard_input() || input.virtual_keycode == Some(winit::event::VirtualKeyCode::Tab); EventResponse { From 486cff8ac39cfba0a804a6126621dd9093adaea9 Mon Sep 17 00:00:00 2001 From: "Stephen M. Coakley" Date: Wed, 9 Aug 2023 05:42:43 -0500 Subject: [PATCH 13/54] Fix panic with persistence without window (#3167) A window may not always be available and may have already been closed by the time an eframe app is closing. An example of this is Android, where the main activity window may have been stopped or discarded because the app is no longer in the foreground, and then the user decides to close your app without resuming it using the multitasking view. In this case, skip the window persistence step if it does not exist anymore by the time we are saving the persistence data. Currently eframe will panic with `winit window doesn't exist` instead. --- crates/eframe/src/native/epi_integration.rs | 20 +++++++++++--------- crates/eframe/src/native/run.rs | 8 ++++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 4409c1298..eeb7d485d 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -562,24 +562,26 @@ impl EpiIntegration { pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) { let now = std::time::Instant::now(); if now - self.last_auto_save > app.auto_save_interval() { - self.save(app, window); + self.save(app, Some(window)); self.last_auto_save = now; } } #[allow(clippy::unused_self)] - pub fn save(&mut self, _app: &mut dyn epi::App, _window: &winit::window::Window) { + pub fn save(&mut self, _app: &mut dyn epi::App, _window: Option<&winit::window::Window>) { #[cfg(feature = "persistence")] if let Some(storage) = self.frame.storage_mut() { crate::profile_function!(); - if _app.persist_native_window() { - crate::profile_scope!("native_window"); - epi::set_value( - storage, - STORAGE_WINDOW_KEY, - &WindowSettings::from_display(_window), - ); + if let Some(window) = _window { + if _app.persist_native_window() { + crate::profile_scope!("native_window"); + epi::set_value( + storage, + STORAGE_WINDOW_KEY, + &WindowSettings::from_display(window), + ); + } } if _app.persist_egui_memory() { crate::profile_scope!("egui_memory"); diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 7771265a1..3b4cc5c46 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -801,7 +801,7 @@ mod glow_integration { if let Some(mut running) = self.running.take() { running .integration - .save(running.app.as_mut(), running.gl_window.window()); + .save(running.app.as_mut(), running.gl_window.window.as_ref()); running.app.on_exit(Some(&running.gl)); running.painter.destroy(); } @@ -1260,9 +1260,9 @@ mod wgpu_integration { fn save_and_destroy(&mut self) { if let Some(mut running) = self.running.take() { - if let Some(window) = &self.window { - running.integration.save(running.app.as_mut(), window); - } + running + .integration + .save(running.app.as_mut(), self.window.as_ref()); #[cfg(feature = "glow")] running.app.on_exit(None); From 9731cfd9cf61cf1836ca7e867a3d98a9c8de6e91 Mon Sep 17 00:00:00 2001 From: Dirk Stolle Date: Wed, 9 Aug 2023 12:50:33 +0200 Subject: [PATCH 14/54] Update GitHub Actions CI (#3150) The following updates are performed: * update actions/checkout to v3 * replace unmaintained actions-rs/toolchain by dtolnay/rust-toolchain * replace unmaintained actions-rs/cargo by direct invocation of cargo --- .github/workflows/rust.yml | 110 +++++++++--------------------------- .github/workflows/typos.yml | 2 +- 2 files changed, 28 insertions(+), 84 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 201fec289..55865370a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -15,13 +15,11 @@ jobs: name: Format + check + test runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: default toolchain: 1.65.0 - override: true - name: Install packages (Linux) if: runner.os == 'Linux' @@ -37,10 +35,7 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Rustfmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + run: cargo fmt --all -- --check - name: Install cargo-cranky uses: baptiste0928/cargo-install@v1 @@ -48,70 +43,37 @@ jobs: crate: cargo-cranky - name: check --all-features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --all-features --all-targets + run: cargo check --locked --all-features --all-targets - name: check egui_extras --all-features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --all-features --all-targets -p egui_extras + run: cargo check --locked --all-features --all-targets -p egui_extras - name: check default features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --all-targets + run: cargo check --locked --all-targets - name: check --no-default-features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --no-default-features --lib --all-targets + run: cargo check --locked --no-default-features --lib --all-targets - name: check epaint --no-default-features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --no-default-features --lib --all-targets -p epaint + run: cargo check --locked --no-default-features --lib --all-targets -p epaint - name: check eframe --no-default-features - uses: actions-rs/cargo@v1 - with: - command: check - args: --locked --no-default-features --lib --all-targets -p eframe + run: cargo check --locked --no-default-features --lib --all-targets -p eframe - name: Test doc-tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --doc --all-features + run: cargo test --doc --all-features - name: cargo doc --lib - uses: actions-rs/cargo@v1 - with: - command: doc - args: --lib --no-deps --all-features + run: cargo doc --lib --no-deps --all-features - name: cargo doc --document-private-items - uses: actions-rs/cargo@v1 - with: - command: doc - args: --document-private-items --no-deps --all-features + run: cargo doc --document-private-items --no-deps --all-features - name: Test - uses: actions-rs/cargo@v1 - with: - command: test - args: --all-features + run: cargo test --all-features - name: Cranky - uses: actions-rs/cargo@v1 - with: - command: cranky - args: --all-targets --all-features -- -D warnings + run: cargo cranky --all-targets --all-features -- -D warnings # --------------------------------------------------------------------------- @@ -119,13 +81,11 @@ jobs: name: Check wasm32 + wasm-bindgen runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: 1.65.0 - target: wasm32-unknown-unknown - override: true + targets: wasm32-unknown-unknown - run: sudo apt-get update && sudo apt-get install libgtk-3-dev @@ -138,22 +98,13 @@ jobs: crate: cargo-cranky - name: Check wasm32 egui_demo_app - uses: actions-rs/cargo@v1 - with: - command: check - args: -p egui_demo_app --lib --target wasm32-unknown-unknown + run: cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown - name: Check wasm32 egui_demo_app --all-features - uses: actions-rs/cargo@v1 - with: - command: check - args: -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features + run: cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features - name: Check wasm32 eframe - uses: actions-rs/cargo@v1 - with: - command: check - args: -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown + run: cargo check -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 @@ -188,7 +139,7 @@ jobs: name: cargo-deny ${{ matrix.target }} runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: EmbarkStudios/cargo-deny-action@v1 with: rust-version: "1.65.0" @@ -202,14 +153,12 @@ jobs: name: android runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: 1.65.0 - target: aarch64-linux-android - override: true + targets: aarch64-linux-android - name: Set up cargo cache uses: Swatinem/rust-cache@v2 @@ -223,18 +172,13 @@ jobs: name: Check Windows runs-on: windows-latest steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: 1.65.0 - override: true - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - name: Check - uses: actions-rs/cargo@v1 - with: - command: check - args: --all-targets --all-features + run: cargo check --all-targets --all-features diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index c3bc84125..4f3ae1d8b 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions Repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Check spelling of entire workspace uses: crate-ci/typos@master From 9c15783fab8328a4de0bfdfa1c9218c926b3f4ab Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 9 Aug 2023 12:51:08 +0200 Subject: [PATCH 15/54] CI: update list of accepted PR labels --- .github/workflows/labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index e5292c15b..fe72e5870 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -29,4 +29,4 @@ jobs: with: mode: minimum count: 1 - labels: "ecolor, eframe, egui_extras, egui_glow, egui-wgpu, egui-winit, egui, epaint" + labels: "CI, dependencies, docs and examples, ecolor, eframe, egui_extras, egui_glow, egui-wgpu, egui-winit, egui, epaint" From 46daaa8a340ddcbae5491a0f720b7167fc211e54 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 9 Aug 2023 12:51:22 +0200 Subject: [PATCH 16/54] Improve PR template --- .github/pull_request_template.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 57db1de4a..c00eac197 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,8 +4,8 @@ Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/ * Keep your PR:s small and focused. * 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 thart makes it difficult for maintainers to add commits to your PR. -* Remember to run `cargo fmt` and `cargo clippy`. +* 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`. * 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. From 20be0847c80624290d669e4228062d2dcfc8819b Mon Sep 17 00:00:00 2001 From: Yuan Chang Date: Wed, 9 Aug 2023 22:20:59 +0800 Subject: [PATCH 17/54] Add `into_inner` methods. (#3110) --- crates/epaint/src/mutex.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index f3a18bf15..cca86466e 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -82,6 +82,11 @@ mod mutex_impl { MutexGuard(self.0.lock(), ptr) } + + #[inline(always)] + pub fn into_inner(self) -> T { + self.0.into_inner() + } } impl Drop for MutexGuard<'_, T> { @@ -314,6 +319,11 @@ mod rw_lock_impl { holders: Arc::clone(&self.holders), } } + + #[inline(always)] + pub fn into_inner(self) -> T { + self.lock.into_inner() + } } fn make_backtrace() -> backtrace::Backtrace { @@ -366,6 +376,11 @@ mod mutex_impl { pub fn lock(&self) -> MutexGuard<'_, T> { self.0.borrow_mut() } + + #[inline(always)] + pub fn into_inner(self) -> T { + self.0.into_inner() + } } } @@ -401,6 +416,11 @@ mod rw_lock_impl { pub fn write(&self) -> RwLockWriteGuard<'_, T> { self.0.borrow_mut() } + + #[inline(always)] + pub fn into_inner(self) -> T { + self.0.into_inner() + } } } From 35027d3ebe4ca04dd3a3ad02aeb1a5099615950c Mon Sep 17 00:00:00 2001 From: Ho Kim Date: Wed, 9 Aug 2023 14:24:32 +0000 Subject: [PATCH 18/54] Fix a document: `eframe::start_web` (#3026) In `0.22.0`, `eframe::start_web` has been replaced with `eframe::WebRunner`, which also installs a nice panic hook (no need for `console_error_panic_hook`). --- crates/eframe/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index afa602cc9..8164e7e65 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -7,7 +7,7 @@ //! To learn how to set up `eframe` for web and native, go to and follow the instructions there! //! //! In short, you implement [`App`] (especially [`App::update`]) and then -//! call [`crate::run_native`] from your `main.rs`, and/or call `eframe::start_web` from your `lib.rs`. +//! call [`crate::run_native`] from your `main.rs`, and/or use `eframe::WebRunner` from your `lib.rs`. //! //! ## Usage, native: //! ``` no_run From e82ec74c5cc3a9e63f058580a283bf14025c4385 Mon Sep 17 00:00:00 2001 From: thomaseliot Date: Wed, 9 Aug 2023 07:46:45 -0700 Subject: [PATCH 19/54] epaint: Add `ColorImage::from_gray` (#3166) * epaint: Add from_gray_unmultiplied function * Rename to just `from_gray` --------- Co-authored-by: Emil Ernerfeldt --- crates/epaint/src/image.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/epaint/src/image.rs b/crates/epaint/src/image.rs index 35b0885c7..7ca33b3ac 100644 --- a/crates/epaint/src/image.rs +++ b/crates/epaint/src/image.rs @@ -110,6 +110,15 @@ impl ColorImage { Self { size, pixels } } + /// Create a [`ColorImage`] from flat opaque gray data. + /// + /// Panics if `size[0] * size[1] != gray.len()`. + pub fn from_gray(size: [usize; 2], gray: &[u8]) -> Self { + assert_eq!(size[0] * size[1], gray.len()); + let pixels = gray.iter().map(|p| Color32::from_gray(*p)).collect(); + Self { size, pixels } + } + /// A view of the underlying data as `&[u8]` #[cfg(feature = "bytemuck")] pub fn as_raw(&self) -> &[u8] { From cbdf748a3b49ba4f516cfba6def9c9c6bbf40c89 Mon Sep 17 00:00:00 2001 From: Nicolas Riebesel Date: Wed, 9 Aug 2023 16:47:46 +0200 Subject: [PATCH 20/54] egui: Plot Arrows add method to specify tip_size (#3138) Normally the tip_size will be dependent on the arrow length. This patch adds a new method to explicitly set the arrow tip size. --- crates/egui/src/widgets/plot/items/mod.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/plot/items/mod.rs b/crates/egui/src/widgets/plot/items/mod.rs index e96d8b3b4..c56de6bb7 100644 --- a/crates/egui/src/widgets/plot/items/mod.rs +++ b/crates/egui/src/widgets/plot/items/mod.rs @@ -997,6 +997,7 @@ impl PlotItem for Points { pub struct Arrows { pub(super) origins: PlotPoints, pub(super) tips: PlotPoints, + pub(super) tip_length: Option, pub(super) color: Color32, pub(super) name: String, pub(super) highlight: bool, @@ -1007,6 +1008,7 @@ impl Arrows { Self { origins: origins.into(), tips: tips.into(), + tip_length: None, color: Color32::TRANSPARENT, name: Default::default(), highlight: false, @@ -1019,6 +1021,12 @@ impl Arrows { self } + /// Set the length of the arrow tips + pub fn tip_length(mut self, tip_length: f32) -> Self { + self.tip_length = Some(tip_length); + self + } + /// Set the arrows' color. pub fn color(mut self, color: impl Into) -> Self { self.color = color.into(); @@ -1044,6 +1052,7 @@ impl PlotItem for Arrows { let Self { origins, tips, + tip_length, color, highlight, .. @@ -1062,7 +1071,11 @@ impl PlotItem for Arrows { .for_each(|(origin, tip)| { let vector = tip - origin; let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0); - let tip_length = vector.length() / 4.0; + let tip_length = if let Some(tip_length) = tip_length { + *tip_length + } else { + vector.length() / 4.0 + }; let tip = origin + vector; let dir = vector.normalized(); shapes.push(Shape::line_segment([origin, tip], stroke)); From f2769f30101ea73c53578ea2cbbc6c9bc89fd82b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20D=C3=ADaz?= <49416765+AgustinRamiroDiaz@users.noreply.github.com> Date: Wed, 9 Aug 2023 11:48:21 -0300 Subject: [PATCH 21/54] Fix dobule "for" typo (#3189) --- crates/egui_extras/src/strip.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui_extras/src/strip.rs b/crates/egui_extras/src/strip.rs index 52a768f35..86b8db3e2 100644 --- a/crates/egui_extras/src/strip.rs +++ b/crates/egui_extras/src/strip.rs @@ -72,13 +72,13 @@ impl<'a> StripBuilder<'a> { self } - /// Allocate space for for one column/row. + /// Allocate space for one column/row. pub fn size(mut self, size: Size) -> Self { self.sizing.add(size); self } - /// Allocate space for for several columns/rows at once. + /// Allocate space for several columns/rows at once. pub fn sizes(mut self, size: Size, count: usize) -> Self { for _ in 0..count { self.sizing.add(size); From b2b6558c78ee8a85d778f2c885639a45a7eab87f Mon Sep 17 00:00:00 2001 From: Steven Weiss Date: Wed, 9 Aug 2023 08:06:33 -0700 Subject: [PATCH 22/54] Tiny PR: Document available_width() and available_height() (#3130) * Update ui.rs docs to clarify available_size and co Added docs to the available_width() and available_height() methods. Added a quick note about the wrapping versions of the methods as well. * fix a couple of typos, and use code-style for doc-links * fix doclinks --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/ui.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 13b3e81f1..4e4b4559d 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -556,6 +556,7 @@ impl Ui { // Layout related measures: /// The available space at the moment, given the current cursor. + /// /// This how much more space we can take up without overflowing our parent. /// Shrinks as widgets allocate space and the cursor moves. /// A small size should be interpreted as "as little as possible". @@ -564,19 +565,30 @@ impl Ui { self.placer.available_size() } + /// The available width at the moment, given the current cursor. + /// + /// See [`Self::available_size`] for more information. pub fn available_width(&self) -> f32 { self.available_size().x } + /// The available height at the moment, given the current cursor. + /// + /// See [`Self::available_size`] for more information. pub fn available_height(&self) -> f32 { self.available_size().y } /// In case of a wrapping layout, how much space is left on this row/column? + /// + /// If the layout does not wrap, this will return the same value as [`Self::available_size`]. pub fn available_size_before_wrap(&self) -> Vec2 { self.placer.available_rect_before_wrap().size() } + /// In case of a wrapping layout, how much space is left on this row/column? + /// + /// If the layout does not wrap, this will return the same value as [`Self::available_size`]. pub fn available_rect_before_wrap(&self) -> Rect { self.placer.available_rect_before_wrap() } From 92593b70facdf3af1cae32ea5fa4ba612cfe43be Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 9 Aug 2023 17:12:47 +0200 Subject: [PATCH 23/54] Changelogs: document that they are updated upon release (not by users) --- CHANGELOG.md | 4 ++-- crates/ecolor/CHANGELOG.md | 3 ++- crates/eframe/CHANGELOG.md | 4 ++-- crates/egui-wgpu/CHANGELOG.md | 4 ++-- crates/egui-winit/CHANGELOG.md | 4 ++-- crates/egui_extras/CHANGELOG.md | 4 ++-- crates/egui_glium/CHANGELOG.md | 3 +++ crates/egui_glow/CHANGELOG.md | 4 ++-- crates/epaint/CHANGELOG.md | 3 ++- 9 files changed, 19 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cddbc286a..fc129dd8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,8 @@ All notable changes to the `egui` crate will be documented in this file. NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG.md), [`egui-winit`](crates/egui-winit/CHANGELOG.md), [`egui_glium`](crates/egui_glium/CHANGELOG.md), [`egui_glow`](crates/egui_glow/CHANGELOG.md) and [`egui-wgpu`](crates/egui-wgpu/CHANGELOG.md) have their own changelogs! - -## Unreleased +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 - A plethora of small improvements diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index f4a3e1d72..228275e04 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -2,7 +2,8 @@ All notable changes to the `ecolor` crate will be noted in this file. -## Unreleased +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 37eb3b985..072a54e8c 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -3,9 +3,9 @@ All notable changes to the `eframe` crate. NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/CHANGELOG.md), [`egui_glow`](../egui_glow/CHANGELOG.md),and [`egui-wgpu`](../egui-wgpu/CHANGELOG.md) have their own changelogs! +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. -## Unreleased -* Expose raw window and display handles in `CreationContext` and `Frame` ## 0.22.0 - 2023-05-23 * Fix: `request_repaint_after` works even when called from background thread [#2939](https://github.com/emilk/egui/pull/2939) diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index ecae53cfd..d7cbee4d1 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -2,8 +2,8 @@ All notable changes to the `egui-wgpu` integration will be noted in this file. -## Unreleased -* Fix panic on wgpu GL backend due to new screenshot capability ([#3068](https://github.com/emilk/egui/issues/3068), [#3078](https://github.com/emilk/egui/pull/3078) +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index af64b1b46..d181b99b4 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog for egui-winit All notable changes to the `egui-winit` integration will be noted in this file. - -## Unreleased +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index ab60317d6..3a968179b 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog for egui_extras All notable changes to the `egui_extras` integration will be noted in this file. - -## Unreleased +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 diff --git a/crates/egui_glium/CHANGELOG.md b/crates/egui_glium/CHANGELOG.md index a34c23416..c1e98201d 100644 --- a/crates/egui_glium/CHANGELOG.md +++ b/crates/egui_glium/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog for egui_glium All notable changes to the `egui_glium` integration will be noted in this file. +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. + ## Unreleased * Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)). diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index a3dc5acfa..3710edab4 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog for egui_glow All notable changes to the `egui_glow` integration will be noted in this file. - -## Unreleased +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. ## 0.22.0 - 2023-05-23 diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index ebe9e6f6e..e0f4e0569 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/CHANGELOG.md @@ -1,8 +1,9 @@ # epaint changelog All notable changes to the epaint crate will be documented in this file. +This file is updated upon each release. +Changes since the last release can be found by running the `scripts/generate_changelog.py` script. -## Unreleased ## 0.22.0 - 2023-05-23 From 51da2ac490da3f50b15ed6e6b93bc289ff3f61bd Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 9 Aug 2023 17:13:10 +0200 Subject: [PATCH 24/54] Improve documentation of Window/Area::resizable --- crates/egui/src/containers/resize.rs | 5 ++++- crates/egui/src/containers/window.rs | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index befb51a10..2f71a57c0 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -124,7 +124,10 @@ impl Resize { } /// Can you resize it with the mouse? - /// Note that a window can still auto-resize + /// + /// Note that a window can still auto-resize. + /// + /// Default is `true`. pub fn resizable(mut self, resizable: bool) -> Self { self.resizable = resizable; self diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index d5c3f680f..1d2444ca3 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -217,7 +217,10 @@ impl<'open> Window<'open> { } /// Can the user resize the window by dragging its edges? + /// /// Note that even if you set this to `false` the window may still auto-resize. + /// + /// Default is `true`. pub fn resizable(mut self, resizable: bool) -> Self { self.resize = self.resize.resizable(resizable); self From abe91b00a47e54f6b37c917c1f6a0f0f8696c995 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 9 Aug 2023 17:13:22 +0200 Subject: [PATCH 25/54] Improve generate_changelog.py --- .github/workflows/labels.yml | 2 +- scripts/generate_changelog.py | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index fe72e5870..193ac9134 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -29,4 +29,4 @@ jobs: with: mode: minimum count: 1 - labels: "CI, dependencies, docs and examples, ecolor, eframe, egui_extras, egui_glow, egui-wgpu, egui-winit, egui, epaint" + labels: "CI, dependencies, docs and examples, ecolor, eframe, egui_extras, egui_glow, egui-wgpu, egui-winit, egui, epaint, plot, typo" diff --git a/scripts/generate_changelog.py b/scripts/generate_changelog.py index 3431bf985..2e97d01b2 100755 --- a/scripts/generate_changelog.py +++ b/scripts/generate_changelog.py @@ -141,6 +141,8 @@ def main() -> None: unsorted_prs = [] unsorted_commits = [] + plot = [] + for commit_info, pr_info in zip(commit_infos, pr_infos): hexsha = commit_info.hexsha title = commit_info.title @@ -164,11 +166,18 @@ def main() -> None: if gh_user_name not in OFFICIAL_DEVS: summary += f" (thanks [@{gh_user_name}](https://github.com/{gh_user_name})!)" + if 'typo' in labels: + continue # We get so many typo PRs. Let's not flood the changelog with them. + added = False - for crate in crate_names: - if crate in labels: - sections.setdefault(crate, []).append(summary) - added = True + if 'plot' in labels: + plot.append(summary) + added = True + else: + for crate in crate_names: + if crate in labels: + sections.setdefault(crate, []).append(summary) + added = True if not added: if not any(label in labels for label in ignore_labels): @@ -179,6 +188,9 @@ def main() -> None: if crate in sections: summary = sections[crate] print_section(crate, summary) + if crate == 'egui': + if 0 < len(plot): + print_section("egui plot", plot) print_section("Unsorted PRs", unsorted_prs) print_section("Unsorted commits", unsorted_commits) From f9f9abf7496e41ae9b67201f08452fdbdffc8382 Mon Sep 17 00:00:00 2001 From: Barugon <16503728+Barugon@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:13:58 -0700 Subject: [PATCH 26/54] `DragValue`: update value on each key press by default (#2880) * Use `Response::changed` to fix editing issues * Update comment * Make update while editing an option * improve docstring --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/widgets/drag_value.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index 0d33978f2..1a965d4e1 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -63,6 +63,7 @@ pub struct DragValue<'a> { max_decimals: Option, custom_formatter: Option>, custom_parser: Option>, + update_while_editing: bool, } impl<'a> DragValue<'a> { @@ -94,6 +95,7 @@ impl<'a> DragValue<'a> { max_decimals: None, custom_formatter: None, custom_parser: None, + update_while_editing: true, } } @@ -352,6 +354,15 @@ impl<'a> DragValue<'a> { } .custom_parser(|s| i64::from_str_radix(s, 16).map(|n| n as f64).ok()) } + + /// Update the value on each key press when text-editing the value. + /// + /// Default: `true`. + /// If `false`, the value will only be updated when user presses enter or deselects the value. + pub fn update_while_editing(mut self, update: bool) -> Self { + self.update_while_editing = update; + self + } } impl<'a> Widget for DragValue<'a> { @@ -366,6 +377,7 @@ impl<'a> Widget for DragValue<'a> { max_decimals, custom_formatter, custom_parser, + update_while_editing, } = self; let shift = ui.input(|i| i.modifiers.shift_only()); @@ -475,9 +487,15 @@ impl<'a> Widget for DragValue<'a> { .desired_width(ui.spacing().interact_size.x) .font(text_style), ); - // Only update the value when the user presses enter, or clicks elsewhere. NOT every frame. - // See https://github.com/emilk/egui/issues/2687 - if response.lost_focus() { + + let update = if update_while_editing { + // Update when the edit content has changed. + response.changed() + } else { + // Update only when the edit has lost focus. + response.lost_focus() + }; + if update { let parsed_value = match custom_parser { Some(parser) => parser(&value_text), None => value_text.parse().ok(), From af6419343d8e864e0c9afaa559363315abb55424 Mon Sep 17 00:00:00 2001 From: mauliu <67208725+mauliu@users.noreply.github.com> Date: Wed, 9 Aug 2023 20:59:36 +0200 Subject: [PATCH 27/54] fix depth texture init with multisampling (#3207) Co-authored-by: user --- crates/egui-wgpu/src/winit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 7b6a9b180..171d5f08e 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -279,7 +279,7 @@ impl Painter { depth_or_array_layers: 1, }, mip_level_count: 1, - sample_count: 1, + sample_count: self.msaa_samples, dimension: wgpu::TextureDimension::D2, format: depth_format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT From f2a58244c8e27ceeb9a95abd2573bc9b20979ca3 Mon Sep 17 00:00:00 2001 From: jacekpoz <64381190+jacekpoz@users.noreply.github.com> Date: Thu, 10 Aug 2023 09:50:15 +0200 Subject: [PATCH 28/54] fix the title not being used when app_id is not set (#3107) Co-authored-by: jacekpoz --- crates/eframe/src/native/epi_integration.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index eeb7d485d..faed7360c 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -121,9 +121,12 @@ pub fn window_builder( } #[cfg(all(feature = "wayland", target_os = "linux"))] - if let Some(app_id) = &native_options.app_id { + { use winit::platform::wayland::WindowBuilderExtWayland as _; - window_builder = window_builder.with_name(app_id, ""); + match &native_options.app_id { + Some(app_id) => window_builder = window_builder.with_name(app_id, ""), + None => window_builder = window_builder.with_name(title, ""), + } } if let Some(min_size) = *min_window_size { From 01b1b2da761f618ec1a89f21c76f7bc1c1c6ac99 Mon Sep 17 00:00:00 2001 From: Roshan Mehta Date: Thu, 10 Aug 2023 00:50:25 -0700 Subject: [PATCH 29/54] rotation feature to plot images (#3121) --- crates/egui/src/widgets/plot/items/mod.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/widgets/plot/items/mod.rs b/crates/egui/src/widgets/plot/items/mod.rs index c56de6bb7..3f21b0e83 100644 --- a/crates/egui/src/widgets/plot/items/mod.rs +++ b/crates/egui/src/widgets/plot/items/mod.rs @@ -1132,6 +1132,7 @@ pub struct PlotImage { pub(super) tint: Color32, pub(super) highlight: bool, pub(super) name: String, + pub(crate) rotation: Option<(f32, Vec2)>, } impl PlotImage { @@ -1150,6 +1151,7 @@ impl PlotImage { size: size.into(), bg_fill: Default::default(), tint: Color32::WHITE, + rotation: None, } } @@ -1188,6 +1190,17 @@ impl PlotImage { self.name = name.to_string(); self } + + /// Rotate the image about an origin by some angle + /// + /// Positive angle is clockwise. + /// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right). + /// + /// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin. + pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self { + self.rotation = Some((angle, origin)); + self + } } impl PlotItem for PlotImage { @@ -1215,11 +1228,14 @@ impl PlotItem for PlotImage { let right_bottom_tf = transform.position_from_point(&right_bottom); Rect::from_two_pos(left_top_tf, right_bottom_tf) }; - Image::new(*texture_id, *size) + let mut image = Image::new(*texture_id, *size) .bg_fill(*bg_fill) .tint(*tint) - .uv(*uv) - .paint_at(ui, rect); + .uv(*uv); + if let Some((angle, origin)) = self.rotation { + image = image.rotate(angle, origin); + } + image.paint_at(ui, rect); if *highlight { shapes.push(Shape::rect_stroke( rect, From 871041c4e7cd2d38bda7d2e0c57002b631a8618e Mon Sep 17 00:00:00 2001 From: hacknus <33124824+hacknus@users.noreply.github.com> Date: Thu, 10 Aug 2023 12:35:11 +0200 Subject: [PATCH 30/54] Added an example to save plot to image (#2769) * implement save_plot * fix for check.sh * clippy * add save_plot to Cargo.lock * adapted for PR #2676 (removes unsafe code) * add some comments * implemented the comments from emilk * update comments in code * rustfmt * remove picked_path * add more comments * removed unused import * use `inner.response.rect` as the plot position * remove plot_location from MyApp members * sort entries * Update examples/save_plot/src/main.rs Co-authored-by: Emil Ernerfeldt * use env_logger instead of tracing subscriber * use env_logger instead of tracing subscriber and combine if let --------- Co-authored-by: Emil Ernerfeldt --- Cargo.lock | 10 ++++ examples/save_plot/Cargo.toml | 16 ++++++ examples/save_plot/README.md | 5 ++ examples/save_plot/src/main.rs | 99 ++++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+) create mode 100644 examples/save_plot/Cargo.toml create mode 100644 examples/save_plot/README.md create mode 100644 examples/save_plot/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 632040aa4..37dd41bc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3102,6 +3102,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "save_plot" +version = "0.1.0" +dependencies = [ + "eframe", + "env_logger", + "image", + "rfd", +] + [[package]] name = "scoped-tls" version = "1.0.1" diff --git a/examples/save_plot/Cargo.toml b/examples/save_plot/Cargo.toml new file mode 100644 index 000000000..83f919d79 --- /dev/null +++ b/examples/save_plot/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "save_plot" +version = "0.1.0" +authors = ["hacknus "] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.65" +publish = false + +[dependencies] +eframe = { path = "../../crates/eframe", features = [ + "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO +] } +image = { version = "0.24", default-features = false, features = ["png"] } +rfd = "0.11.0" +env_logger = "0.10" diff --git a/examples/save_plot/README.md b/examples/save_plot/README.md new file mode 100644 index 000000000..523af1d6b --- /dev/null +++ b/examples/save_plot/README.md @@ -0,0 +1,5 @@ +This example shows that you can save a plot in egui as a png. + +```sh +cargo run -p save_plot +``` diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs new file mode 100644 index 000000000..68c48182a --- /dev/null +++ b/examples/save_plot/src/main.rs @@ -0,0 +1,99 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release + +use eframe::egui; +use eframe::egui::plot::{Legend, Line, Plot, PlotPoints}; +use eframe::egui::ColorImage; + +fn main() -> Result<(), eframe::Error> { + env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). + + let options = eframe::NativeOptions { + initial_window_size: Some(egui::vec2(350.0, 400.0)), + ..Default::default() + }; + eframe::run_native( + "My egui App with a plot", + options, + Box::new(|_cc| Box::new(MyApp::default())), + ) +} + +#[derive(Default)] +struct MyApp { + screenshot: Option, +} + +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + let mut plot_rect = None; + egui::CentralPanel::default().show(ctx, |ui| { + // these are just some dummy variables for the example, + // such that the plot is not at position (0,0) + let height = 200.0; + let border_x = 11.0; + let border_y = 18.0; + let width = 300.0; + + ui.heading("My egui Application"); + + // add some whitespace in y direction + ui.add_space(border_y); + + if ui.button("Save Plot").clicked() { + frame.request_screenshot(); + } + + // add some whitespace in y direction + ui.add_space(border_y); + + ui.horizontal(|ui| { + // add some whitespace in x direction + ui.add_space(border_x); + + let my_plot = Plot::new("My Plot") + .height(height) + .width(width) + .legend(Legend::default()); + + // let's create a dummy line in the plot + let graph: Vec<[f64; 2]> = vec![[0.0, 1.0], [2.0, 3.0], [3.0, 2.0]]; + let inner = my_plot.show(ui, |plot_ui| { + plot_ui.line(Line::new(PlotPoints::from(graph)).name("curve")); + }); + // Remember the position of the plot + plot_rect = Some(inner.response.rect); + }); + + // add some whitespace in y direction + ui.add_space(border_y); + }); + + if let (Some(screenshot), Some(plot_location)) = (self.screenshot.take(), plot_rect) { + if let Some(mut path) = rfd::FileDialog::new().save_file() { + path.set_extension("png"); + + // for a full size application, we should put this in a different thread, + // so that the GUI doesn't lag during saving + + let pixels_per_point = frame.info().native_pixels_per_point; + let plot = screenshot.region(&plot_location, pixels_per_point); + // save the plot to png + image::save_buffer( + &path, + plot.as_raw(), + plot.width() as u32, + plot.height() as u32, + image::ColorType::Rgba8, + ) + .unwrap(); + } + } + } + + fn post_rendering(&mut self, _screen_size_px: [u32; 2], frame: &eframe::Frame) { + // this is inspired by the Egui screenshot example + if let Some(screenshot) = frame.screenshot() { + self.screenshot = Some(screenshot); + } + } +} From 4c3b380889af5ad3c8b97d7193a3fa69a9f9443d Mon Sep 17 00:00:00 2001 From: "Valeriy V. Vorotyntsev" Date: Thu, 10 Aug 2023 13:35:40 +0300 Subject: [PATCH 31/54] Fix the "ever-growing height" problem of Strip and Table demos (#3122) * Fix the "ever-growing height" problem of Strip and Table Demos Problem ------- The height of "Table Demo" or "Strip Demo" floating window grows when the demo app is interacted with. If 'Continuous' mode is enabled in 'Backend' settings, the window grows irrespectively of user interaction. Observations ------------ I noticed that [`area_content_ui.min_rect().max.y`][1] is increasing monotonically with speed 0.5 px/frame. I also noticed that commenting out `ui.add(crate::egui_github_link_file!());` [statement][2] in `table_demo.rs` makes the problem disappear. The "Fix" --------- I added 0.5 to the height of the row with GitHub link. This solved the problem. Closes #3029. Warning ------- I failed to find the root cause of the problem. I don't understand why this change makes the problem disappear. [1]: https://github.com/emilk/egui/blob/9478e50d012c5138551c38cbee16b07bc1fcf283/crates/egui/src/containers/window.rs#L403 [2]: https://github.com/emilk/egui/blob/9478e50d012c5138551c38cbee16b07bc1fcf283/crates/egui_demo_lib/src/demo/table_demo.rs#L114 * Document `Rect::size` Other changes: - `area.rs`: Use existing API. - `table_demo.rs`: Remove unnecessary call. --- crates/egui/src/containers/area.rs | 2 +- crates/egui_demo_lib/src/demo/strip_demo.rs | 2 +- crates/egui_demo_lib/src/demo/table_demo.rs | 3 +-- crates/emath/src/rect.rs | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 380c175b3..953391cad 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -426,7 +426,7 @@ impl Prepared { temporarily_invisible: _, } = self; - state.size = content_ui.min_rect().size(); + state.size = content_ui.min_size(); ctx.memory_mut(|m| m.areas.set_state(layer_id, state)); diff --git a/crates/egui_demo_lib/src/demo/strip_demo.rs b/crates/egui_demo_lib/src/demo/strip_demo.rs index 2e9038b3a..db42fcb0b 100644 --- a/crates/egui_demo_lib/src/demo/strip_demo.rs +++ b/crates/egui_demo_lib/src/demo/strip_demo.rs @@ -37,7 +37,7 @@ impl super::View for StripDemo { .size(Size::exact(50.0)) .size(Size::remainder()) .size(Size::relative(0.5).at_least(60.0)) - .size(Size::exact(10.0)) + .size(Size::exact(10.5)) .vertical(|mut strip| { strip.cell(|ui| { ui.painter().rect_filled( diff --git a/crates/egui_demo_lib/src/demo/table_demo.rs b/crates/egui_demo_lib/src/demo/table_demo.rs index 8fdea4202..6523805ba 100644 --- a/crates/egui_demo_lib/src/demo/table_demo.rs +++ b/crates/egui_demo_lib/src/demo/table_demo.rs @@ -38,7 +38,6 @@ impl super::Demo for TableDemo { fn show(&mut self, ctx: &egui::Context, open: &mut bool) { egui::Window::new(self.name()) .open(open) - .resizable(true) .default_width(400.0) .show(ctx, |ui| { use super::View as _; @@ -102,7 +101,7 @@ impl super::View for TableDemo { use egui_extras::{Size, StripBuilder}; StripBuilder::new(ui) .size(Size::remainder().at_least(100.0)) // for the table - .size(Size::exact(10.0)) // for the source code link + .size(Size::exact(10.5)) // for the source code link .vertical(|mut strip| { strip.cell(|ui| { egui::ScrollArea::horizontal().show(ui, |ui| { diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index b8c6085a2..de115b985 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -280,6 +280,7 @@ impl Rect { } } + /// `rect.size() == Vec2 { x: rect.width(), y: rect.height() }` #[inline(always)] pub fn size(&self) -> Vec2 { self.max - self.min From 8cdffc4e2d66d54f3059b3a31fac924f630dce8b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Aug 2023 13:07:00 +0200 Subject: [PATCH 32/54] Replace uses of `RangeInclusive` with `emath::Rangef` (#3221) * Replace uses of `RangeInclusive` with `emath::Rangef` * Fix doc-test --- crates/egui/src/containers/panel.rs | 71 ++++++++++++----------- crates/egui/src/containers/scroll_area.rs | 3 +- crates/egui/src/frame_state.rs | 4 +- crates/egui/src/lib.rs | 4 +- crates/egui/src/painter.rs | 7 +-- crates/egui/src/ui.rs | 20 ++++--- crates/egui/src/widgets/slider.rs | 39 ++++++------- crates/egui_extras/src/sizing.rs | 42 ++++++-------- crates/egui_extras/src/table.rs | 54 +++++++++-------- crates/emath/src/align.rs | 18 +++--- crates/emath/src/lib.rs | 15 ++++- crates/emath/src/range.rs | 64 +++++++++++++++++++- crates/emath/src/rect.rs | 22 +++---- crates/epaint/src/shape.rs | 11 ++-- 14 files changed, 213 insertions(+), 161 deletions(-) diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index d829ac219..f5267e0f0 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -15,8 +15,6 @@ //! //! Add your [`Window`]:s after any top-level panels. -use std::ops::RangeInclusive; - use crate::*; /// State regarding panels. @@ -99,7 +97,7 @@ pub struct SidePanel { resizable: bool, show_separator_line: bool, default_width: f32, - width_range: RangeInclusive, + width_range: Rangef, } impl SidePanel { @@ -122,7 +120,7 @@ impl SidePanel { resizable: true, show_separator_line: true, default_width: 200.0, - width_range: 96.0..=f32::INFINITY, + width_range: Rangef::new(96.0, f32::INFINITY), } } @@ -153,26 +151,29 @@ impl SidePanel { /// The initial wrapping width of the [`SidePanel`]. pub fn default_width(mut self, default_width: f32) -> Self { self.default_width = default_width; - self.width_range = self.width_range.start().at_most(default_width) - ..=self.width_range.end().at_least(default_width); + self.width_range = Rangef::new( + self.width_range.min.at_most(default_width), + self.width_range.max.at_least(default_width), + ); self } /// Minimum width of the panel. pub fn min_width(mut self, min_width: f32) -> Self { - self.width_range = min_width..=self.width_range.end().at_least(min_width); + self.width_range = Rangef::new(min_width, self.width_range.max.at_least(min_width)); self } /// Maximum width of the panel. pub fn max_width(mut self, max_width: f32) -> Self { - self.width_range = self.width_range.start().at_most(max_width)..=max_width; + self.width_range = Rangef::new(self.width_range.min.at_most(max_width), max_width); self } /// The allowable width range for the panel. - pub fn width_range(mut self, width_range: RangeInclusive) -> Self { - self.default_width = clamp_to_range(self.default_width, width_range.clone()); + pub fn width_range(mut self, width_range: impl Into) -> Self { + let width_range = width_range.into(); + self.default_width = clamp_to_range(self.default_width, width_range); self.width_range = width_range; self } @@ -180,7 +181,7 @@ impl SidePanel { /// Enforce this exact width. pub fn exact_width(mut self, width: f32) -> Self { self.default_width = width; - self.width_range = width..=width; + self.width_range = Rangef::point(width); self } @@ -224,7 +225,7 @@ impl SidePanel { if let Some(state) = PanelState::load(ui.ctx(), id) { width = state.rect.width(); } - width = clamp_to_range(width, width_range.clone()).at_most(available_rect.width()); + width = clamp_to_range(width, width_range).at_most(available_rect.width()); side.set_rect_width(&mut panel_rect, width); ui.ctx().check_for_id_clash(id, panel_rect, "SidePanel"); } @@ -241,7 +242,7 @@ impl SidePanel { let resize_x = side.opposite().side_x(panel_rect); let mouse_over_resize_line = we_are_on_top - && panel_rect.y_range().contains(&pointer.y) + && panel_rect.y_range().contains(pointer.y) && (resize_x - pointer.x).abs() <= ui.style().interaction.resize_grab_radius_side; @@ -253,8 +254,7 @@ impl SidePanel { is_resizing = ui.memory(|mem| mem.is_being_dragged(resize_id)); if is_resizing { let width = (pointer.x - side.side_x(panel_rect)).abs(); - let width = - clamp_to_range(width, width_range.clone()).at_most(available_rect.width()); + let width = clamp_to_range(width, width_range).at_most(available_rect.width()); side.set_rect_width(&mut panel_rect, width); } @@ -273,7 +273,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.start()); + ui.set_min_width(width_range.min); add_contents(ui) }); @@ -544,7 +544,7 @@ pub struct TopBottomPanel { resizable: bool, show_separator_line: bool, default_height: Option, - height_range: RangeInclusive, + height_range: Rangef, } impl TopBottomPanel { @@ -567,7 +567,7 @@ impl TopBottomPanel { resizable: false, show_separator_line: true, default_height: None, - height_range: 20.0..=f32::INFINITY, + height_range: Rangef::new(20.0, f32::INFINITY), } } @@ -599,28 +599,31 @@ impl TopBottomPanel { /// Defaults to [`style::Spacing::interact_size`].y. pub fn default_height(mut self, default_height: f32) -> Self { self.default_height = Some(default_height); - self.height_range = self.height_range.start().at_most(default_height) - ..=self.height_range.end().at_least(default_height); + self.height_range = Rangef::new( + self.height_range.min.at_most(default_height), + self.height_range.max.at_least(default_height), + ); self } /// Minimum height of the panel. pub fn min_height(mut self, min_height: f32) -> Self { - self.height_range = min_height..=self.height_range.end().at_least(min_height); + self.height_range = Rangef::new(min_height, self.height_range.max.at_least(min_height)); self } /// Maximum height of the panel. pub fn max_height(mut self, max_height: f32) -> Self { - self.height_range = self.height_range.start().at_most(max_height)..=max_height; + self.height_range = Rangef::new(self.height_range.min.at_most(max_height), max_height); self } /// The allowable height range for the panel. - pub fn height_range(mut self, height_range: RangeInclusive) -> Self { + pub fn height_range(mut self, height_range: impl Into) -> Self { + let height_range = height_range.into(); self.default_height = self .default_height - .map(|default_height| clamp_to_range(default_height, height_range.clone())); + .map(|default_height| clamp_to_range(default_height, height_range)); self.height_range = height_range; self } @@ -628,7 +631,7 @@ impl TopBottomPanel { /// Enforce this exact height. pub fn exact_height(mut self, height: f32) -> Self { self.default_height = Some(height); - self.height_range = height..=height; + self.height_range = Rangef::point(height); self } @@ -673,7 +676,7 @@ impl TopBottomPanel { } else { default_height.unwrap_or_else(|| ui.style().spacing.interact_size.y) }; - height = clamp_to_range(height, height_range.clone()).at_most(available_rect.height()); + height = clamp_to_range(height, height_range).at_most(available_rect.height()); side.set_rect_height(&mut panel_rect, height); ui.ctx() .check_for_id_clash(id, panel_rect, "TopBottomPanel"); @@ -692,7 +695,7 @@ impl TopBottomPanel { let resize_y = side.opposite().side_y(panel_rect); let mouse_over_resize_line = we_are_on_top - && panel_rect.x_range().contains(&pointer.x) + && panel_rect.x_range().contains(pointer.x) && (resize_y - pointer.y).abs() <= ui.style().interaction.resize_grab_radius_side; @@ -704,8 +707,8 @@ impl TopBottomPanel { is_resizing = ui.memory(|mem| mem.interaction.drag_id == Some(resize_id)); if is_resizing { let height = (pointer.y - side.side_y(panel_rect)).abs(); - let height = clamp_to_range(height, height_range.clone()) - .at_most(available_rect.height()); + let height = + clamp_to_range(height, height_range).at_most(available_rect.height()); side.set_rect_height(&mut panel_rect, height); } @@ -724,7 +727,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.start()); + ui.set_min_height(height_range.min); add_contents(ui) }); @@ -1056,9 +1059,7 @@ impl CentralPanel { } } -fn clamp_to_range(x: f32, range: RangeInclusive) -> f32 { - x.clamp( - range.start().min(*range.end()), - range.start().max(*range.end()), - ) +fn clamp_to_range(x: f32, range: Rangef) -> f32 { + let range = range.as_positive(); + x.clamp(range.min, range.max) } diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 341e61f2f..b75a16ab2 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -640,8 +640,7 @@ impl Prepared { let min = content_ui.min_rect().min[d]; let clip_rect = content_ui.clip_rect(); let visible_range = min..=min + clip_rect.size()[d]; - let start = *scroll.start(); - let end = *scroll.end(); + let (start, end) = (scroll.min, scroll.max); let clip_start = clip_rect.min[d]; let clip_end = clip_rect.max[d]; let mut spacing = ui.spacing().item_spacing[d]; diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index 0d7106843..287b35c56 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -1,5 +1,3 @@ -use std::ops::RangeInclusive; - use crate::{id::IdSet, *}; #[derive(Clone, Copy, Debug)] @@ -46,7 +44,7 @@ pub(crate) struct FrameState { pub(crate) scroll_delta: Vec2, // TODO(emilk): move to `InputState` ? /// horizontal, vertical - pub(crate) scroll_target: [Option<(RangeInclusive, Option)>; 2], + pub(crate) scroll_target: [Option<(Rangef, Option)>; 2], #[cfg(feature = "accesskit")] pub(crate) accesskit_state: Option, diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index f7eef71a9..aab9049b6 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -335,7 +335,9 @@ pub use epaint::emath; #[cfg(feature = "color-hex")] pub use ecolor::hex_color; pub use ecolor::{Color32, Rgba}; -pub use emath::{lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rect, Vec2}; +pub use emath::{ + lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos2, Rangef, Rect, Vec2, +}; pub use epaint::{ mutex, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 214bbe175..baf3b9212 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -1,8 +1,7 @@ -use std::ops::RangeInclusive; use std::sync::Arc; use crate::{ - emath::{Align2, Pos2, Rect, Vec2}, + emath::{Align2, Pos2, Rangef, Rect, Vec2}, layers::{LayerId, PaintList, ShapeIdx}, Color32, Context, FontId, }; @@ -263,12 +262,12 @@ impl Painter { } /// Paints a horizontal line. - pub fn hline(&self, x: RangeInclusive, y: f32, stroke: impl Into) { + pub fn hline(&self, x: impl Into, y: f32, stroke: impl Into) { self.add(Shape::hline(x, y, stroke)); } /// Paints a vertical line. - pub fn vline(&self, x: f32, y: RangeInclusive, stroke: impl Into) { + pub fn vline(&self, x: f32, y: impl Into, stroke: impl Into) { self.add(Shape::vline(x, y, stroke)); } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 4e4b4559d..83713e487 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -517,15 +517,17 @@ impl Ui { } /// `ui.set_width_range(min..=max);` is equivalent to `ui.set_min_width(min); ui.set_max_width(max);`. - pub fn set_width_range(&mut self, width: std::ops::RangeInclusive) { - self.set_min_width(*width.start()); - self.set_max_width(*width.end()); + pub fn set_width_range(&mut self, width: impl Into) { + let width = width.into(); + self.set_min_width(width.min); + self.set_max_width(width.max); } /// `ui.set_height_range(min..=max);` is equivalent to `ui.set_min_height(min); ui.set_max_height(max);`. - pub fn set_height_range(&mut self, height: std::ops::RangeInclusive) { - self.set_min_height(*height.start()); - self.set_max_height(*height.end()); + pub fn set_height_range(&mut self, height: impl Into) { + let height = height.into(); + self.set_min_height(height.min); + self.set_max_height(height.max); } /// Set both the minimum and maximum width. @@ -978,7 +980,7 @@ impl Ui { /// ``` pub fn scroll_to_rect(&self, rect: Rect, align: Option) { for d in 0..2 { - let range = rect.min[d]..=rect.max[d]; + let range = Rangef::new(rect.min[d], rect.max[d]); self.ctx() .frame_state_mut(|state| state.scroll_target[d] = Some((range, align))); } @@ -1008,9 +1010,9 @@ impl Ui { pub fn scroll_to_cursor(&self, align: Option) { let target = self.next_widget_position(); for d in 0..2 { - let target = target[d]; + let target = Rangef::point(target[d]); self.ctx() - .frame_state_mut(|state| state.scroll_target[d] = Some((target..=target, align))); + .frame_state_mut(|state| state.scroll_target[d] = Some((target, align))); } } diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 9ebcaf493..f298a7831 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -524,12 +524,12 @@ impl<'a> Slider<'a> { } /// For instance, `position` is the mouse position and `position_range` is the physical location of the slider on the screen. - fn value_from_position(&self, position: f32, position_range: RangeInclusive) -> f64 { + fn value_from_position(&self, position: f32, position_range: Rangef) -> f64 { let normalized = remap_clamp(position, position_range, 0.0..=1.0) as f64; value_from_normalized(normalized, self.range(), &self.spec) } - fn position_from_value(&self, value: f64, position_range: RangeInclusive) -> f32 { + fn position_from_value(&self, value: f64, position_range: Rangef) -> f32 { let normalized = normalized_from_value(value, self.range(), &self.spec); lerp(position_range, normalized as f32) } @@ -555,11 +555,11 @@ impl<'a> Slider<'a> { let new_value = if self.smart_aim { let aim_radius = ui.input(|i| i.aim_radius()); emath::smart_aim::best_in_range_f64( - self.value_from_position(position - aim_radius, position_range.clone()), - self.value_from_position(position + aim_radius, position_range.clone()), + self.value_from_position(position - aim_radius, position_range), + self.value_from_position(position + aim_radius, position_range), ) } else { - self.value_from_position(position, position_range.clone()) + self.value_from_position(position, position_range) }; self.set_value(new_value); } @@ -594,18 +594,18 @@ impl<'a> Slider<'a> { if kb_step != 0.0 { let prev_value = self.get_value(); - let prev_position = self.position_from_value(prev_value, position_range.clone()); + let prev_position = self.position_from_value(prev_value, position_range); let new_position = prev_position + kb_step; let new_value = match self.step { Some(step) => prev_value + (kb_step as f64 * step), None if self.smart_aim => { let aim_radius = ui.input(|i| i.aim_radius()); emath::smart_aim::best_in_range_f64( - self.value_from_position(new_position - aim_radius, position_range.clone()), - self.value_from_position(new_position + aim_radius, position_range.clone()), + self.value_from_position(new_position - aim_radius, position_range), + self.value_from_position(new_position + aim_radius, position_range), ) } - _ => self.value_from_position(new_position, position_range.clone()), + _ => self.value_from_position(new_position, position_range), }; self.set_value(new_value); } @@ -686,15 +686,11 @@ impl<'a> Slider<'a> { } } - fn position_range(&self, rect: &Rect) -> RangeInclusive { + fn position_range(&self, rect: &Rect) -> Rangef { let handle_radius = self.handle_radius(rect); match self.orientation { - SliderOrientation::Horizontal => { - (rect.left() + handle_radius)..=(rect.right() - handle_radius) - } - SliderOrientation::Vertical => { - (rect.bottom() - handle_radius)..=(rect.top() + handle_radius) - } + SliderOrientation::Horizontal => rect.x_range().shrink(handle_radius), + SliderOrientation::Vertical => rect.y_range().shrink(handle_radius), } } @@ -726,7 +722,7 @@ impl<'a> Slider<'a> { } } - fn value_ui(&mut self, ui: &mut Ui, position_range: RangeInclusive) -> Response { + fn value_ui(&mut self, ui: &mut Ui, position_range: Rangef) -> Response { // If [`DragValue`] is controlled from the keyboard and `step` is defined, set speed to `step` let change = ui.input(|input| { input.num_presses(Key::ArrowUp) as i32 + input.num_presses(Key::ArrowRight) as i32 @@ -740,7 +736,7 @@ impl<'a> Slider<'a> { step } else { self.drag_value_speed - .unwrap_or_else(|| self.current_gradient(&position_range)) + .unwrap_or_else(|| self.current_gradient(position_range)) }; let mut value = self.get_value(); @@ -767,12 +763,11 @@ impl<'a> Slider<'a> { } /// delta(value) / delta(points) - fn current_gradient(&mut self, position_range: &RangeInclusive) -> f64 { + fn current_gradient(&mut self, position_range: Rangef) -> f64 { // TODO(emilk): handle clamping let value = self.get_value(); - let value_from_pos = - |position: f32| self.value_from_position(position, position_range.clone()); - let pos_from_value = |value: f64| self.position_from_value(value, position_range.clone()); + let value_from_pos = |position: f32| self.value_from_position(position, position_range); + let pos_from_value = |value: f64| self.position_from_value(value, position_range); let left_value = value_from_pos(pos_from_value(value) - 0.5); let right_value = value_from_pos(pos_from_value(value) + 0.5); right_value - left_value diff --git a/crates/egui_extras/src/sizing.rs b/crates/egui_extras/src/sizing.rs index 8a58302d5..c6ae9d6da 100644 --- a/crates/egui_extras/src/sizing.rs +++ b/crates/egui_extras/src/sizing.rs @@ -1,14 +1,16 @@ +use egui::Rangef; + /// Size hint for table column/strip cell. #[derive(Clone, Debug, Copy)] pub enum Size { /// Absolute size in points, with a given range of allowed sizes to resize within. - Absolute { initial: f32, range: (f32, f32) }, + Absolute { initial: f32, range: Rangef }, /// Relative size relative to all available space. - Relative { fraction: f32, range: (f32, f32) }, + Relative { fraction: f32, range: Rangef }, /// Multiple remainders each get the same space. - Remainder { range: (f32, f32) }, + Remainder { range: Rangef }, } impl Size { @@ -16,7 +18,7 @@ impl Size { pub fn exact(points: f32) -> Self { Self::Absolute { initial: points, - range: (points, points), + range: Rangef::new(points, points), } } @@ -24,7 +26,7 @@ impl Size { pub fn initial(points: f32) -> Self { Self::Absolute { initial: points, - range: (0.0, f32::INFINITY), + range: Rangef::new(0.0, f32::INFINITY), } } @@ -33,14 +35,14 @@ impl Size { egui::egui_assert!(0.0 <= fraction && fraction <= 1.0); Self::Relative { fraction, - range: (0.0, f32::INFINITY), + range: Rangef::new(0.0, f32::INFINITY), } } /// Multiple remainders each get the same space. pub fn remainder() -> Self { Self::Remainder { - range: (0.0, f32::INFINITY), + range: Rangef::new(0.0, f32::INFINITY), } } @@ -50,7 +52,7 @@ impl Size { Self::Absolute { range, .. } | Self::Relative { range, .. } | Self::Remainder { range, .. } => { - range.0 = minimum; + range.min = minimum; } } self @@ -62,14 +64,14 @@ impl Size { Self::Absolute { range, .. } | Self::Relative { range, .. } | Self::Remainder { range, .. } => { - range.1 = maximum; + range.max = maximum; } } self } /// Allowed range of movement (in points), if in a resizable [`Table`](crate::table::Table). - pub fn range(self) -> (f32, f32) { + pub fn range(self) -> Rangef { match self { Self::Absolute { range, .. } | Self::Relative { range, .. } @@ -99,12 +101,9 @@ impl Sizing { .iter() .map(|&size| match size { Size::Absolute { initial, .. } => initial, - Size::Relative { - fraction, - range: (min, max), - } => { + Size::Relative { fraction, range } => { assert!(0.0 <= fraction && fraction <= 1.0); - (length * fraction).clamp(min, max) + range.clamp(length * fraction) } Size::Remainder { .. } => { remainders += 1; @@ -120,9 +119,9 @@ impl Sizing { let mut remainder_length = length - sum_non_remainder; let avg_remainder_length = 0.0f32.max(remainder_length / remainders as f32).floor(); self.sizes.iter().for_each(|&size| { - if let Size::Remainder { range: (min, _max) } = size { - if avg_remainder_length < min { - remainder_length -= min; + if let Size::Remainder { range } = size { + if avg_remainder_length < range.min { + remainder_length -= range.min; remainders -= 1; } } @@ -138,11 +137,8 @@ impl Sizing { .iter() .map(|&size| match size { Size::Absolute { initial, .. } => initial, - Size::Relative { - fraction, - range: (min, max), - } => (length * fraction).clamp(min, max), - Size::Remainder { range: (min, max) } => avg_remainder_length.clamp(min, max), + Size::Relative { fraction, range } => range.clamp(length * fraction), + Size::Remainder { range } => range.clamp(avg_remainder_length), }) .collect() } diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 2283ab4d7..9966a8b99 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -3,7 +3,7 @@ //! | fixed size | all available space/minimum | 30% of available width | fixed size | //! Takes all available height, so if you want something below the table, put it in a strip. -use egui::{Align, NumExt as _, Rect, Response, ScrollArea, Ui, Vec2}; +use egui::{Align, NumExt as _, Rangef, Rect, Response, ScrollArea, Ui, Vec2}; use crate::{ layout::{CellDirection, CellSize}, @@ -28,7 +28,7 @@ enum InitialColumnSize { #[derive(Clone, Copy, Debug, PartialEq)] pub struct Column { initial_width: InitialColumnSize, - width_range: (f32, f32), + width_range: Rangef, /// Clip contents if too narrow? clip: bool, @@ -78,7 +78,7 @@ impl Column { fn new(initial_width: InitialColumnSize) -> Self { Self { initial_width, - width_range: (0.0, f32::INFINITY), + width_range: Rangef::new(0.0, f32::INFINITY), resizable: None, clip: false, } @@ -110,7 +110,7 @@ impl Column { /// /// Default: 0.0 pub fn at_least(mut self, minimum: f32) -> Self { - self.width_range.0 = minimum; + self.width_range.min = minimum; self } @@ -118,13 +118,13 @@ impl Column { /// /// Default: [`f32::INFINITY`] pub fn at_most(mut self, maximum: f32) -> Self { - self.width_range.1 = maximum; + self.width_range.max = maximum; self } /// Allowed range of movement (in points), if in a resizable [`Table`](crate::table::Table). - pub fn range(mut self, range: std::ops::RangeInclusive) -> Self { - self.width_range = (*range.start(), *range.end()); + pub fn range(mut self, range: impl Into) -> Self { + self.width_range = range.into(); self } @@ -146,8 +146,8 @@ fn to_sizing(columns: &[Column]) -> crate::sizing::Sizing { InitialColumnSize::Automatic(suggested_width) => Size::initial(suggested_width), InitialColumnSize::Remainder => Size::remainder(), } - .at_least(column.width_range.0) - .at_most(column.width_range.1); + .at_least(column.width_range.min) + .at_most(column.width_range.max); sizing.add(size); } sizing @@ -598,13 +598,13 @@ impl<'a> Table<'a> { if scroll_to_row.is_some() && scroll_to_y_range.is_none() { // TableBody::row didn't find the right row, so scroll to the bottom: - scroll_to_y_range = Some((f32::INFINITY, f32::INFINITY)); + scroll_to_y_range = Some(Rangef::new(f32::INFINITY, f32::INFINITY)); } }); - if let Some((min_y, max_y)) = scroll_to_y_range { + if let Some(y_range) = scroll_to_y_range { let x = 0.0; // ignored, we only have vertical scrolling - let rect = egui::Rect::from_min_max(egui::pos2(x, min_y), egui::pos2(x, max_y)); + let rect = egui::Rect::from_x_y_ranges(x..=x, y_range); let align = scroll_to_row.and_then(|(_, a)| a); ui.scroll_to_rect(rect, align); } @@ -617,14 +617,14 @@ impl<'a> Table<'a> { for (i, column_width) in state.column_widths.iter_mut().enumerate() { let column = &columns[i]; let column_is_resizable = column.resizable.unwrap_or(resizable); - let (min_width, max_width) = column.width_range; + let width_range = column.width_range; if !column.clip { // Unless we clip we don't want to shrink below the // size that was actually used: *column_width = column_width.at_least(max_used_widths[i]); } - *column_width = column_width.clamp(min_width, max_width); + *column_width = width_range.clamp(*column_width); let is_last_column = i + 1 == columns.len(); @@ -633,7 +633,7 @@ impl<'a> Table<'a> { let eps = 0.1; // just to avoid some rounding errors. *column_width = available_width - eps; *column_width = column_width.at_least(max_used_widths[i]); - *column_width = column_width.clamp(min_width, max_width); + *column_width = width_range.clamp(*column_width); break; } @@ -641,7 +641,7 @@ impl<'a> Table<'a> { if column.is_auto() && (first_frame_auto_size_columns || !column_is_resizable) { *column_width = max_used_widths[i]; - *column_width = column_width.clamp(min_width, max_width); + *column_width = width_range.clamp(*column_width); } else if column_is_resizable { let column_resize_id = ui.id().with("resize_column").with(i); @@ -656,7 +656,7 @@ impl<'a> Table<'a> { if resize_response.double_clicked() { // Resize to the minimum of what is needed. - *column_width = max_used_widths[i].clamp(min_width, max_width); + *column_width = width_range.clamp(max_used_widths[i]); } else if resize_response.dragged() { if let Some(pointer) = ui.ctx().pointer_latest_pos() { let mut new_width = *column_width + pointer.x - x; @@ -671,7 +671,7 @@ impl<'a> Table<'a> { new_width = new_width.at_least(max_used_widths[i] - max_shrinkage_per_frame); } - new_width = new_width.clamp(min_width, max_width); + new_width = width_range.clamp(new_width); let x = x - *column_width + new_width; (p0.x, p1.x) = (x, x); @@ -731,7 +731,7 @@ pub struct TableBody<'a> { /// If we find the correct row to scroll to, /// this is set to the y-range of the row. - scroll_to_y_range: &'a mut Option<(f32, f32)>, + scroll_to_y_range: &'a mut Option, } impl<'a> TableBody<'a> { @@ -779,7 +779,7 @@ impl<'a> TableBody<'a> { let bottom_y = self.layout.cursor.y; if Some(self.row_nr) == self.scroll_to_row { - *self.scroll_to_y_range = Some((top_y, bottom_y)); + *self.scroll_to_y_range = Some(Rangef::new(top_y, bottom_y)); } self.row_nr += 1; @@ -819,7 +819,7 @@ impl<'a> TableBody<'a> { if let Some(scroll_to_row) = self.scroll_to_row { let scroll_to_row = scroll_to_row.at_most(total_rows.saturating_sub(1)) as f32; - *self.scroll_to_y_range = Some(( + *self.scroll_to_y_range = Some(Rangef::new( self.layout.cursor.y + scroll_to_row * row_height_with_spacing, self.layout.cursor.y + (scroll_to_row + 1.0) * row_height_with_spacing, )); @@ -909,7 +909,7 @@ impl<'a> TableBody<'a> { cursor_y += (row_height + spacing.y) as f64; if Some(row_index) == self.scroll_to_row { - *self.scroll_to_y_range = Some(( + *self.scroll_to_y_range = Some(Rangef::new( (scroll_to_y_range_offset + old_cursor_y) as f32, (scroll_to_y_range_offset + cursor_y) as f32, )); @@ -953,7 +953,7 @@ impl<'a> TableBody<'a> { cursor_y += (row_height + spacing.y) as f64; if Some(row_index) == self.scroll_to_row { - *self.scroll_to_y_range = Some(( + *self.scroll_to_y_range = Some(Rangef::new( (scroll_to_y_range_offset + top_y) as f32, (scroll_to_y_range_offset + cursor_y) as f32, )); @@ -972,7 +972,7 @@ impl<'a> TableBody<'a> { let top_y = cursor_y; cursor_y += (row_height + spacing.y) as f64; if Some(row_index) == self.scroll_to_row { - *self.scroll_to_y_range = Some(( + *self.scroll_to_y_range = Some(Rangef::new( (scroll_to_y_range_offset + top_y) as f32, (scroll_to_y_range_offset + cursor_y) as f32, )); @@ -981,10 +981,8 @@ impl<'a> TableBody<'a> { if self.scroll_to_row.is_some() && self.scroll_to_y_range.is_none() { // Catch desire to scroll past the end: - *self.scroll_to_y_range = Some(( - (scroll_to_y_range_offset + cursor_y) as f32, - (scroll_to_y_range_offset + cursor_y) as f32, - )); + *self.scroll_to_y_range = + Some(Rangef::point((scroll_to_y_range_offset + cursor_y) as f32)); } if height_below_visible > 0.0 { diff --git a/crates/emath/src/align.rs b/crates/emath/src/align.rs index 9d7ccf81e..ef07cc1d3 100644 --- a/crates/emath/src/align.rs +++ b/crates/emath/src/align.rs @@ -110,29 +110,25 @@ impl Align { /// assert_eq!(Max .align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0); /// ``` #[inline] - pub fn align_size_within_range( - self, - size: f32, - range: RangeInclusive, - ) -> RangeInclusive { - let min = *range.start(); - let max = *range.end(); + pub fn align_size_within_range(self, size: f32, range: impl Into) -> Rangef { + let range = range.into(); + let Rangef { min, max } = range; if max - min == f32::INFINITY && size == f32::INFINITY { return range; } match self { - Self::Min => min..=min + size, + Self::Min => Rangef::new(min, min + size), Self::Center => { if size == f32::INFINITY { - f32::NEG_INFINITY..=f32::INFINITY + Rangef::new(f32::NEG_INFINITY, f32::INFINITY) } else { let left = (min + max) / 2.0 - size / 2.0; - left..=left + size + Rangef::new(left, left + size) } } - Self::Max => max - size..=max, + Self::Max => Rangef::new(max - size, max), } } } diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 89b2db209..25b3b9eac 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -99,11 +99,12 @@ impl Real for f64 {} /// assert_eq!(lerp(1.0..=5.0, 2.0), 9.0); /// ``` #[inline(always)] -pub fn lerp(range: RangeInclusive, t: T) -> R +pub fn lerp(range: impl Into>, t: T) -> R where T: Real + Mul, R: Copy + Add, { + let range = range.into(); (T::one() - t) * *range.start() + t * *range.end() } @@ -138,20 +139,28 @@ where /// Linearly remap a value from one range to another, /// so that when `x == from.start()` returns `to.start()` /// and when `x == from.end()` returns `to.end()`. -pub fn remap(x: T, from: RangeInclusive, to: RangeInclusive) -> T +pub fn remap(x: T, from: impl Into>, to: impl Into>) -> T where T: Real, { + let from = from.into(); + let to = to.into(); crate::emath_assert!(from.start() != from.end()); let t = (x - *from.start()) / (*from.end() - *from.start()); lerp(to, t) } /// Like [`remap`], but also clamps the value so that the returned value is always in the `to` range. -pub fn remap_clamp(x: T, from: RangeInclusive, to: RangeInclusive) -> T +pub fn remap_clamp( + x: T, + from: impl Into>, + to: impl Into>, +) -> T where T: Real, { + let from = from.into(); + let to = to.into(); if from.end() < from.start() { return remap_clamp(x, *from.end()..=*from.start(), *to.end()..=*to.start()); } diff --git a/crates/emath/src/range.rs b/crates/emath/src/range.rs index ae3e3f0e5..11459763d 100644 --- a/crates/emath/src/range.rs +++ b/crates/emath/src/range.rs @@ -36,14 +36,60 @@ impl Rangef { } #[inline] - pub fn span(&self) -> f32 { + pub fn point(min_and_max: f32) -> Self { + Self { + min: min_and_max, + max: min_and_max, + } + } + + /// The length of the range, i.e. `max - min`. + #[inline] + pub fn span(self) -> f32 { self.max - self.min } #[inline] - pub fn contains(&self, x: f32) -> bool { + #[must_use] + pub fn contains(self, x: f32) -> bool { self.min <= x && x <= self.max } + + /// Equivalent to `x.clamp(min, max)` + #[inline] + #[must_use] + pub fn clamp(self, x: f32) -> f32 { + x.clamp(self.min, self.max) + } + + /// Flip `min` and `max` if needed, so that `min <= max` after. + #[inline] + pub fn as_positive(self) -> Self { + Rangef { + min: self.min.min(self.max), + max: self.min.max(self.max), + } + } + + /// Shrink by this much on each side, keeping the center + #[inline] + #[must_use] + pub fn shrink(self, amnt: f32) -> Self { + Self { + min: self.min + amnt, + max: self.max - amnt, + } + } + + /// Expand by this much on each side, keeping the center + #[inline] + #[must_use] + pub fn expand(self, amnt: f32) -> Self { + Self { + min: self.min - amnt, + max: self.max + amnt, + } + } } impl From for RangeInclusive { @@ -108,3 +154,17 @@ impl From> for Rangef { Self::new(f32::NEG_INFINITY, range.end) } } + +impl PartialEq> for Rangef { + #[inline] + fn eq(&self, other: &RangeInclusive) -> bool { + self.min == *other.start() && self.max == *other.end() + } +} + +impl PartialEq for RangeInclusive { + #[inline] + fn eq(&self, other: &Rangef) -> bool { + *self.start() == other.min && *self.end() == other.max + } +} diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index de115b985..d6d148d27 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -1,5 +1,4 @@ use std::f32::INFINITY; -use std::ops::RangeInclusive; use crate::*; @@ -82,15 +81,12 @@ impl Rect { } #[inline(always)] - pub fn from_x_y_ranges( - x_range: impl Into>, - y_range: impl Into>, - ) -> Self { + pub fn from_x_y_ranges(x_range: impl Into, y_range: impl Into) -> Self { let x_range = x_range.into(); let y_range = y_range.into(); Rect { - min: pos2(*x_range.start(), *y_range.start()), - max: pos2(*x_range.end(), *y_range.end()), + min: pos2(x_range.min, y_range.min), + max: pos2(x_range.max, y_range.max), } } @@ -390,18 +386,18 @@ impl Rect { } #[inline(always)] - pub fn x_range(&self) -> RangeInclusive { - self.min.x..=self.max.x + pub fn x_range(&self) -> Rangef { + Rangef::new(self.min.x, self.max.x) } #[inline(always)] - pub fn y_range(&self) -> RangeInclusive { - self.min.y..=self.max.y + pub fn y_range(&self) -> Rangef { + Rangef::new(self.min.y, self.max.y) } #[inline(always)] - pub fn bottom_up_range(&self) -> RangeInclusive { - self.max.y..=self.min.y + pub fn bottom_up_range(&self) -> Rangef { + Rangef::new(self.max.y, self.min.y) } /// `width < 0 || height < 0` diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 046215661..401f51d8a 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -1,6 +1,5 @@ //! The different shapes that can be painted. -use std::ops::RangeInclusive; use std::{any::Any, sync::Arc}; use crate::{ @@ -94,17 +93,19 @@ impl Shape { } /// A horizontal line. - pub fn hline(x: RangeInclusive, y: f32, stroke: impl Into) -> Self { + pub fn hline(x: impl Into, y: f32, stroke: impl Into) -> Self { + let x = x.into(); Shape::LineSegment { - points: [pos2(*x.start(), y), pos2(*x.end(), y)], + points: [pos2(x.min, y), pos2(x.max, y)], stroke: stroke.into(), } } /// A vertical line. - pub fn vline(x: f32, y: RangeInclusive, stroke: impl Into) -> Self { + pub fn vline(x: f32, y: impl Into, stroke: impl Into) -> Self { + let y = y.into(); Shape::LineSegment { - points: [pos2(x, *y.start()), pos2(x, *y.end())], + points: [pos2(x, y.min), pos2(x, y.max)], stroke: stroke.into(), } } From b15e17587a5ceec9bd0c024b53d506d4fd87d695 Mon Sep 17 00:00:00 2001 From: Valentin Date: Thu, 10 Aug 2023 13:11:56 +0200 Subject: [PATCH 33/54] Document when Galleys get invalidated (#3024) --- crates/epaint/src/text/text_layout_types.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index d6d81d88b..28301c85a 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -310,7 +310,13 @@ impl Default for TextWrapping { /// /// You can create a [`Galley`] using [`crate::Fonts::layout_job`]; /// -/// This needs to be recreated if `pixels_per_point` (dpi scale) changes. +/// Needs to be recreated if the underlying font atlas texture changes, which +/// happens under the following conditions: +/// - `pixels_per_point` or `max_texture_size` change. These parameters are set +/// in [`crate::text::Fonts::begin_frame`]. When using `egui` they are set +/// from `egui::InputState` and can change at any time. +/// - The atlas has become full. This can happen any time a new glyph is added +/// to the atlas, which in turn can happen any time new text is laid out. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Galley { From 66cbb61ad5a4434d6c723ff81f416205d39c6299 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Aug 2023 13:14:19 +0200 Subject: [PATCH 34/54] Add `PlotUi::response()` to replace `plot_clicked()` etc (#3223) Closes https://github.com/emilk/egui/pull/2571 --- crates/egui/src/widgets/plot/mod.rs | 8 ++++++++ crates/egui_demo_lib/src/demo/plot_demo.rs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui/src/widgets/plot/mod.rs index 8f04be4fa..9fa09b7e1 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui/src/widgets/plot/mod.rs @@ -1081,17 +1081,25 @@ impl PlotUi { .push(BoundsModification::Translate(delta_pos)); } + /// Can be used to check if the plot was hovered or clicked. + pub fn response(&self) -> &Response { + &self.response + } + /// Returns `true` if the plot area is currently hovered. + #[deprecated = "Use plot_ui.response().hovered()"] pub fn plot_hovered(&self) -> bool { self.response.hovered() } /// Returns `true` if the plot was clicked by the primary button. + #[deprecated = "Use plot_ui.response().clicked()"] pub fn plot_clicked(&self) -> bool { self.response.clicked() } /// Returns `true` if the plot was clicked by the secondary button. + #[deprecated = "Use plot_ui.response().secondary_clicked()"] pub fn plot_secondary_clicked(&self) -> bool { self.response.secondary_clicked() } diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 8c5bcfd3c..2f6180eae 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -761,7 +761,7 @@ impl InteractionDemo { plot_ui.pointer_coordinate(), plot_ui.pointer_coordinate_drag_delta(), plot_ui.plot_bounds(), - plot_ui.plot_hovered(), + plot_ui.response().hovered(), ) }); From 83c18498e9853fdd953a43e6bd133e3e4ba2c367 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Aug 2023 14:50:11 +0200 Subject: [PATCH 35/54] Refactor: turn `ClippedShape` from struct-enum to a normal struct (#3225) --- crates/egui/src/layers.rs | 8 ++++---- crates/epaint/benches/benchmark.rs | 2 +- crates/epaint/src/lib.rs | 9 +++++---- crates/epaint/src/stats.rs | 2 +- crates/epaint/src/tessellator.rs | 18 +++++++++++++++--- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/crates/egui/src/layers.rs b/crates/egui/src/layers.rs index 9e951f0dc..83e7fdeb6 100644 --- a/crates/egui/src/layers.rs +++ b/crates/egui/src/layers.rs @@ -127,7 +127,7 @@ impl PaintList { #[inline(always)] pub fn add(&mut self, clip_rect: Rect, shape: Shape) -> ShapeIdx { let idx = ShapeIdx(self.0.len()); - self.0.push(ClippedShape(clip_rect, shape)); + self.0.push(ClippedShape { clip_rect, shape }); idx } @@ -135,7 +135,7 @@ impl PaintList { self.0.extend( shapes .into_iter() - .map(|shape| ClippedShape(clip_rect, shape)), + .map(|shape| ClippedShape { clip_rect, shape }), ); } @@ -148,12 +148,12 @@ impl PaintList { /// and then later setting it using `paint_list.set(idx, cr, frame);`. #[inline(always)] pub fn set(&mut self, idx: ShapeIdx, clip_rect: Rect, shape: Shape) { - self.0[idx.0] = ClippedShape(clip_rect, shape); + self.0[idx.0] = ClippedShape { clip_rect, shape }; } /// Translate each [`Shape`] and clip rectangle by this much, in-place pub fn translate(&mut self, delta: Vec2) { - for ClippedShape(clip_rect, shape) in &mut self.0 { + for ClippedShape { clip_rect, shape } in &mut self.0 { *clip_rect = clip_rect.translate(delta); shape.translate(delta); } diff --git a/crates/epaint/benches/benchmark.rs b/crates/epaint/benches/benchmark.rs index 126ec2bf2..7f7c9f1b1 100644 --- a/crates/epaint/benches/benchmark.rs +++ b/crates/epaint/benches/benchmark.rs @@ -47,7 +47,7 @@ fn tessellate_circles(c: &mut Criterion) { for _ in 0..10_000 { let clip_rect = Rect::from_min_size(Pos2::ZERO, Vec2::splat(1024.0)); let shape = Shape::circle_filled(Pos2::new(10.0, 10.0), r, Color32::WHITE); - clipped_shapes.push(ClippedShape(clip_rect, shape)); + clipped_shapes.push(ClippedShape { clip_rect, shape }); } } assert_eq!(clipped_shapes.len(), 100_000); diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index 82fc7e0f4..1662c94a1 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -90,13 +90,14 @@ impl Default for TextureId { /// /// Everything is using logical points. #[derive(Clone, Debug, PartialEq)] -pub struct ClippedShape( +pub struct ClippedShape { /// Clip / scissor rectangle. /// Only show the part of the [`Shape`] that falls within this. - pub emath::Rect, + pub clip_rect: emath::Rect, + /// The shape - pub Shape, -); + pub shape: Shape, +} /// A [`Mesh`] or [`PaintCallback`] within a clip rectangle. /// diff --git a/crates/epaint/src/stats.rs b/crates/epaint/src/stats.rs index 5abbe456f..24293aa7b 100644 --- a/crates/epaint/src/stats.rs +++ b/crates/epaint/src/stats.rs @@ -183,7 +183,7 @@ impl PaintStats { stats.shape_vec.element_size = ElementSize::Heterogenous; // nicer display later stats.shapes = AllocInfo::from_slice(shapes); - for ClippedShape(_, shape) in shapes { + for ClippedShape { shape, .. } in shapes { stats.add(shape); } stats diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 75b5dc963..6671859b1 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1036,7 +1036,10 @@ impl Tessellator { clipped_shape: ClippedShape, out_primitives: &mut Vec, ) { - let ClippedShape(new_clip_rect, new_shape) = clipped_shape; + let ClippedShape { + clip_rect: new_clip_rect, + shape: new_shape, + } = clipped_shape; if !new_clip_rect.is_positive() { return; // skip empty clip rectangles @@ -1044,7 +1047,13 @@ impl Tessellator { if let Shape::Vec(shapes) = new_shape { for shape in shapes { - self.tessellate_clipped_shape(ClippedShape(new_clip_rect, shape), out_primitives); + self.tessellate_clipped_shape( + ClippedShape { + clip_rect: new_clip_rect, + shape, + }, + out_primitives, + ); } return; } @@ -1641,7 +1650,10 @@ fn test_tessellator() { shapes.push(Shape::mesh(mesh)); let shape = Shape::Vec(shapes); - let clipped_shapes = vec![ClippedShape(rect, shape)]; + let clipped_shapes = vec![ClippedShape { + clip_rect: rect, + shape, + }]; let font_tex_size = [1024, 1024]; // unused let prepared_discs = vec![]; // unused From d568d9f5d0c36728afad1aaf365e91a9d3e0b899 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Aug 2023 15:26:54 +0200 Subject: [PATCH 36/54] Lint vertical spacing in the code (#3224) * Lint vertical spacing in the code * Add some vertical spacing for readability --- .github/workflows/rust.yml | 7 +- CONTRIBUTING.md | 2 +- crates/eframe/src/lib.rs | 1 + crates/eframe/src/native/epi_integration.rs | 2 + crates/eframe/src/native/run.rs | 1 + crates/egui/src/containers/scroll_area.rs | 6 + crates/egui/src/data/input.rs | 10 ++ crates/egui/src/grid.rs | 1 + crates/egui/src/memory.rs | 2 + crates/egui/src/widgets/button.rs | 1 + crates/egui/src/widgets/drag_value.rs | 1 + crates/egui/src/widgets/plot/items/mod.rs | 11 ++ crates/egui/src/widgets/plot/mod.rs | 2 + crates/egui/src/widgets/slider.rs | 2 + crates/egui_extras/src/image.rs | 4 + crates/egui_extras/src/layout.rs | 2 + crates/egui_extras/src/table.rs | 6 + crates/epaint/src/tessellator.rs | 20 ++- crates/epaint/src/text/font.rs | 6 + crates/epaint/src/text/text_layout_types.rs | 8 ++ crates/epaint/src/textures.rs | 2 + scripts/lint.py | 132 ++++++++++++++++++++ 22 files changed, 215 insertions(+), 14 deletions(-) create mode 100755 scripts/lint.py diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 55865370a..6d4e8e5dd 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,6 +1,6 @@ on: [push, pull_request] -name: CI +name: Rust env: # web_sys_unstable_apis is required to enable the web_sys clipboard API which eframe web uses, @@ -37,6 +37,9 @@ jobs: - name: Rustfmt run: cargo fmt --all -- --check + - name: Lint vertical spacing + run: ./scripts/lint.py + - name: Install cargo-cranky uses: baptiste0928/cargo-install@v1 with: @@ -145,7 +148,7 @@ jobs: rust-version: "1.65.0" log-level: error command: check - arguments: ${{ matrix.flags }} --target ${{ matrix.target }} + arguments: --target ${{ matrix.target }} # --------------------------------------------------------------------------- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28ff70d42..8d3897457 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,7 +60,7 @@ Read the section on integrations at ## Code Conventions Conventions unless otherwise specified: -* angles are in radians +* angles are in radians and clock-wise * `Vec2::X` is right and `Vec2::Y` is down. * `Pos2::ZERO` is left top. diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index 8164e7e65..8093339e8 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -272,6 +272,7 @@ pub fn run_simple_native( struct SimpleApp { update_fun: U, } + impl App for SimpleApp { fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) { (self.update_fun)(ctx, frame); diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index faed7360c..a0bec23bd 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -335,8 +335,10 @@ pub struct EpiIntegration { pub egui_ctx: egui::Context, pending_full_output: egui::FullOutput, egui_winit: egui_winit::State, + /// When set, it is time to close the native window. close: bool, + can_drag_window: bool, window_state: WindowState, follow_system_theme: bool, diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 3b4cc5c46..3672c9bf1 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -22,6 +22,7 @@ use super::epi_integration::{self, EpiIntegration}; pub enum UserEvent { RequestRepaint { when: Instant, + /// What the frame number was when the repaint was _requested_. frame_nr: u64, }, diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index b75a16ab2..8bd8142cf 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -334,16 +334,22 @@ struct Prepared { state: State, has_bar: [bool; 2], auto_shrink: [bool; 2], + /// How much horizontal and vertical space are used up by the /// width of the vertical bar, and the height of the horizontal bar? current_bar_use: Vec2, + scroll_bar_visibility: ScrollBarVisibility, + /// Where on the screen the content is (excludes scroll bars). inner_rect: Rect, + content_ui: Ui, + /// Relative coordinates: the offset and size of the view of the inner UI. /// `viewport.min == ZERO` means we scrolled to the top. viewport: Rect, + scrolling_enabled: bool, stick_to_end: [bool; 2], } diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 29ff3f5dd..4bb9fdc06 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -693,27 +693,37 @@ pub enum Key { /// The virtual keycode for the Minus key. Minus, + /// The virtual keycode for the Plus/Equals key. PlusEquals, /// Either from the main row or from the numpad. Num0, + /// Either from the main row or from the numpad. Num1, + /// Either from the main row or from the numpad. Num2, + /// Either from the main row or from the numpad. Num3, + /// Either from the main row or from the numpad. Num4, + /// Either from the main row or from the numpad. Num5, + /// Either from the main row or from the numpad. Num6, + /// Either from the main row or from the numpad. Num7, + /// Either from the main row or from the numpad. Num8, + /// Either from the main row or from the numpad. Num9, diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index 314266a75..eb33e00ed 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -60,6 +60,7 @@ pub(crate) struct GridLayout { /// State previous frame (if any). /// This can be used to predict future sizes of cells. prev_state: State, + /// State accumulated during the current frame. curr_state: State, initial_available: Rect, diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index db76f7e7c..d89fd344a 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -546,8 +546,10 @@ impl Memory { #[cfg_attr(feature = "serde", serde(default))] pub struct Areas { areas: IdMap, + /// Back-to-front. Top is last. order: Vec, + visible_last_frame: ahash::HashSet, visible_current_frame: ahash::HashSet, diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index a7fa67818..ca211987a 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -23,6 +23,7 @@ pub struct Button { text: WidgetText, shortcut_text: WidgetText, wrap: Option, + /// None means default for interact fill: Option, stroke: Option, diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index 1a965d4e1..b09033c19 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -11,6 +11,7 @@ use crate::*; pub(crate) struct MonoState { last_dragged_id: Option, last_dragged_value: Option, + /// For temporary edit of a [`DragValue`] value. /// Couples with the current focus id. edit_string: Option, diff --git a/crates/egui/src/widgets/plot/items/mod.rs b/crates/egui/src/widgets/plot/items/mod.rs index 3f21b0e83..f18f34aff 100644 --- a/crates/egui/src/widgets/plot/items/mod.rs +++ b/crates/egui/src/widgets/plot/items/mod.rs @@ -760,15 +760,22 @@ impl PlotItem for Text { /// A set of points. pub struct Points { pub(super) series: PlotPoints, + pub(super) shape: MarkerShape, + /// Color of the marker. `Color32::TRANSPARENT` means that it will be picked automatically. pub(super) color: Color32, + /// Whether to fill the marker. Does not apply to all types. pub(super) filled: bool, + /// The maximum extent of the marker from its center. pub(super) radius: f32, + pub(super) name: String, + pub(super) highlight: bool, + pub(super) stems: Option, } @@ -1290,8 +1297,10 @@ pub struct BarChart { pub(super) bars: Vec, pub(super) default_color: Color32, pub(super) name: String, + /// A custom element formatter pub(super) element_formatter: Option String>>, + highlight: bool, } @@ -1460,8 +1469,10 @@ pub struct BoxPlot { pub(super) boxes: Vec, pub(super) default_color: Color32, pub(super) name: String, + /// A custom element formatter pub(super) element_formatter: Option String>>, + highlight: bool, } diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui/src/widgets/plot/mod.rs index 9fa09b7e1..9eca796dc 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui/src/widgets/plot/mod.rs @@ -101,9 +101,11 @@ struct PlotMemory { /// Indicates if the user has modified the bounds, for example by moving or zooming, /// or if the bounds should be calculated based by included point or auto bounds. bounds_modified: AxisBools, + hovered_entry: Option, hidden_items: ahash::HashSet, last_plot_transform: PlotTransform, + /// Allows to remember the first click position when performing a boxed zoom last_click_pos_for_zoom: Option, } diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index f298a7831..f793d86e7 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -77,8 +77,10 @@ pub struct Slider<'a> { prefix: String, suffix: String, text: WidgetText, + /// Sets the minimal step of the widget value step: Option, + drag_value_speed: Option, min_decimals: usize, max_decimals: Option, diff --git a/crates/egui_extras/src/image.rs b/crates/egui_extras/src/image.rs index 3526b6de2..ba8acaf41 100644 --- a/crates/egui_extras/src/image.rs +++ b/crates/egui_extras/src/image.rs @@ -10,11 +10,15 @@ pub use usvg::FitTo; /// Use the `svg` and `image` features to enable more constructors. pub struct RetainedImage { debug_name: String, + size: [usize; 2], + /// Cleared once [`Self::texture`] has been loaded. image: Mutex, + /// Lazily loaded when we have an egui context. texture: Mutex>, + options: TextureOptions, } diff --git a/crates/egui_extras/src/layout.rs b/crates/egui_extras/src/layout.rs index 932594bf5..fbd15e7ad 100644 --- a/crates/egui_extras/src/layout.rs +++ b/crates/egui_extras/src/layout.rs @@ -32,9 +32,11 @@ pub struct StripLayout<'l> { direction: CellDirection, pub(crate) rect: Rect, pub(crate) cursor: Pos2, + /// Keeps track of the max used position, /// so we know how much space we used. max: Pos2, + cell_layout: egui::Layout, } diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 9966a8b99..de0e98704 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -28,7 +28,9 @@ enum InitialColumnSize { #[derive(Clone, Copy, Debug, PartialEq)] pub struct Column { initial_width: InitialColumnSize, + width_range: Rangef, + /// Clip contents if too narrow? clip: bool, @@ -511,8 +513,10 @@ pub struct Table<'a> { columns: Vec, available_width: f32, state: TableState, + /// Accumulated maximum used widths for each column. max_used_widths: Vec, + first_frame_auto_size_columns: bool, resizable: bool, striped: bool, @@ -1011,8 +1015,10 @@ pub struct TableRow<'a, 'b> { layout: &'b mut StripLayout<'a>, columns: &'b [Column], widths: &'b [f32], + /// grows during building with the maximum widths max_used_widths: &'b mut [f32], + col_index: usize, striped: bool, height: f32, diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index 6671859b1..facb2612d 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -13,17 +13,15 @@ use emath::*; #[allow(clippy::approx_constant)] mod precomputed_vertices { - /* - fn main() { - let n = 64; - println!("pub const CIRCLE_{}: [Vec2; {}] = [", n, n+1); - for i in 0..=n { - let a = std::f64::consts::TAU * i as f64 / n as f64; - println!(" vec2({:.06}, {:.06}),", a.cos(), a.sin()); - } - println!("];") - } - */ + // fn main() { + // let n = 64; + // println!("pub const CIRCLE_{}: [Vec2; {}] = [", n, n+1); + // for i in 0..=n { + // let a = std::f64::consts::TAU * i as f64 / n as f64; + // println!(" vec2({:.06}, {:.06}),", a.cos(), a.sin()); + // } + // println!("];") + // } use emath::{vec2, Vec2}; diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 939cdf9ee..4cf26014f 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -78,11 +78,15 @@ impl Default for GlyphInfo { pub struct FontImpl { name: String, ab_glyph_font: ab_glyph::FontArc, + /// Maximum character height scale_in_pixels: u32, + height_in_points: f32, + // move each character by this much (hack) y_offset: f32, + ascent: f32, pixels_per_point: f32, glyph_info_cache: RwLock>, // TODO(emilk): standard Mutex @@ -320,8 +324,10 @@ type FontIndex = usize; /// Wrapper over multiple [`FontImpl`] (e.g. a primary + fallbacks for emojis) pub struct Font { fonts: Vec>, + /// Lazily calculated. characters: Option>, + replacement_glyph: (FontIndex, GlyphInfo), pixels_per_point: f32, row_height: f32, diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 28301c85a..8ec829b8a 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -193,8 +193,10 @@ impl std::hash::Hash for LayoutJob { pub struct LayoutSection { /// Can be used for first row indentation. pub leading_space: f32, + /// Range into the galley text pub byte_range: Range, + pub format: TextFormat, } @@ -218,12 +220,18 @@ impl std::hash::Hash for LayoutSection { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct TextFormat { pub font_id: FontId, + /// Text color pub color: Color32, + pub background: Color32, + pub italics: bool, + pub underline: Stroke, + pub strikethrough: Stroke, + /// If you use a small font and [`Align::TOP`] you /// can get the effect of raised text. pub valign: Align, diff --git a/crates/epaint/src/textures.rs b/crates/epaint/src/textures.rs index e6b423746..abe8d0b91 100644 --- a/crates/epaint/src/textures.rs +++ b/crates/epaint/src/textures.rs @@ -9,8 +9,10 @@ use crate::{ImageData, ImageDelta, TextureId}; pub struct TextureManager { /// We allocate texture id:s linearly. next_id: u64, + /// Information about currently allocated textures. metas: ahash::HashMap, + delta: TexturesDelta, } diff --git a/scripts/lint.py b/scripts/lint.py new file mode 100755 index 000000000..93851ffa1 --- /dev/null +++ b/scripts/lint.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python +""" +Runs custom linting on Rust code. +""" + +import argparse +import os +import re +import sys + + +def lint_file_path(filepath, args) -> int: + with open(filepath) as f: + lines_in = f.readlines() + + errors, lines_out = lint_lines(filepath, lines_in) + + for error in errors: + print(error) + + if args.fix and lines_in != lines_out: + with open(filepath, 'w') as f: + f.writelines(lines_out) + print(f'{filepath} fixed.') + + return len(errors) + + + +def lint_lines(filepath, lines_in): + last_line_was_empty = True + + errors = [] + lines_out = [] + + for line_nr, line in enumerate(lines_in): + line_nr = line_nr+1 + + # TODO: only # and /// on lines before a keyword + + pattern = r'^\s*((///)|((pub(\(\w*\))? )?((impl|fn|struct|enum|union|trait)\b))).*$' + if re.match(pattern, line): + if not last_line_was_empty: + errors.append( + f'{filepath}:{line_nr}: for readability, add newline before `{line.strip()}`') + lines_out.append("\n") + lines_out.append(line) + + stripped = line.strip() + last_line_was_empty = stripped == '' or \ + stripped.startswith('#') or \ + stripped.startswith('//') or \ + stripped.endswith('{') or \ + stripped.endswith('(') or \ + stripped.endswith('\\') or \ + stripped.endswith('r"') or \ + stripped.endswith(']') + + return errors, lines_out + + +def test_lint(): + should_pass = [ + "hello world", + """ + /// docstring + foo + + /// docstring + bar + """ + ] + + should_fail = [ + """ + /// docstring + foo + /// docstring + bar + """ + ] + + for code in should_pass: + errors, _ = lint_lines("test.py", code.split('\n')) + assert len(errors) == 0, f'expected this to pass:\n{code}\ngot: {errors}' + + for code in should_fail: + errors, _ = lint_lines("test.py", code.split('\n')) + assert len(errors) > 0, f'expected this to fail:\n{code}' + + pass + + +def main(): + test_lint() # Make sure we are bug free before we run! + + parser = argparse.ArgumentParser( + description='Lint Rust code with custom linter.') + parser.add_argument('files', metavar='file', type=str, nargs='*', + help='File paths. Empty = all files, recursively.') + parser.add_argument('--fix', dest='fix', action='store_true', + help='Automatically fix the files') + + args = parser.parse_args() + + num_errors = 0 + + if args.files: + for filepath in args.files: + num_errors += lint_file_path(filepath, args) + else: + script_dirpath = os.path.dirname(os.path.realpath(__file__)) + root_dirpath = os.path.abspath(f'{script_dirpath}/..') + os.chdir(root_dirpath) + + exclude = set(['target', 'target_ra']) + for root, dirs, files in os.walk('.', topdown=True): + dirs[:] = [d for d in dirs if d not in exclude] + for filename in files: + if filename.endswith('.rs'): + filepath = os.path.join(root, filename) + num_errors += lint_file_path(filepath, args) + + if num_errors == 0: + print(f"{sys.argv[0]} finished without error") + sys.exit(0) + else: + print(f"{sys.argv[0]} found {num_errors} errors.") + sys.exit(1) + +if __name__ == '__main__': + main() From 7e035c6dd16e605ad640475dbb37bc62867e428e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Aug 2023 17:09:01 +0200 Subject: [PATCH 37/54] Allow users to opt-out of default `winit` features (#3228) * Do not enable winit features by default * Enable default winit features by default * Add x11 feature --- .github/workflows/rust.yml | 2 +- crates/eframe/Cargo.toml | 14 ++++++++++++-- crates/egui-wgpu/Cargo.toml | 2 +- crates/egui-winit/Cargo.toml | 5 ++++- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6d4e8e5dd..61fb36f79 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -61,7 +61,7 @@ jobs: run: cargo check --locked --no-default-features --lib --all-targets -p epaint - name: check eframe --no-default-features - run: cargo check --locked --no-default-features --lib --all-targets -p eframe + run: cargo check --locked --no-default-features --features x11 --lib --all-targets -p eframe - name: Test doc-tests run: cargo test --doc --all-features diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 87ad0a2d7..81b36d82a 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -27,7 +27,14 @@ targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [features] -default = ["accesskit", "default_fonts", "glow"] +default = [ + "accesskit", + "default_fonts", + "glow", + "wayland", + "winit/default", + "x11", +] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). accesskit = ["egui/accesskit", "egui-winit/accesskit"] @@ -42,6 +49,9 @@ glow = ["dep:glow", "dep:egui_glow", "dep:glutin", "dep:glutin-winit"] ## Enables wayland support and fixes clipboard issue. wayland = ["egui-winit/wayland"] +## Enables compiling for x11. +x11 = ["egui-winit/x11"] + ## Enable saving app state to disk. persistence = [ "directories-next", @@ -109,7 +119,7 @@ image = { version = "0.24", default-features = false, features = [ "png", ] } # Needed for app icon raw-window-handle = { version = "0.5.0" } -winit = "0.28.1" +winit = { version = "0.28.1", default-features = false } # optional native: directories-next = { version = "2", optional = true } diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 7fc095b2d..d1812dcb0 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -50,7 +50,7 @@ wgpu = "0.16.0" ## Enable this when generating docs. document-features = { version = "0.2", optional = true } -winit = { version = "0.28", optional = true } +winit = { version = "0.28", default-features = false, optional = true } # Native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index bf3f128f3..27a456da9 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -18,7 +18,7 @@ all-features = true [features] -default = ["clipboard", "links", "wayland", "winit/default"] +default = ["clipboard", "links", "wayland", "winit/default", "x11"] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). accesskit = ["accesskit_winit", "egui/accesskit"] @@ -42,6 +42,9 @@ serde = ["egui/serde", "dep:serde"] ## Enables Wayland support. wayland = ["winit/wayland"] +## Enables compiling for x11. +x11 = ["winit/x11"] + # Allow crates to choose an android-activity backend via Winit # - It's important that most applications should not have to depend on android-activity directly, and can # rely on Winit to pull in a suitable version (unlike most Rust crates, any version conflicts won't link) From 1e885abe0835589f6aaf14ec8849cec22b02e079 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 10 Aug 2023 17:28:21 +0200 Subject: [PATCH 38/54] Gracefully catch error saving state to disk (#3230) --- crates/eframe/src/native/file_storage.rs | 45 ++++++++++++++++++++---- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index 30ec3cb64..2f316a8c9 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -80,14 +80,45 @@ impl crate::Storage for FileStorage { join_handle.join().ok(); } - let join_handle = std::thread::spawn(move || { - let file = std::fs::File::create(&file_path).unwrap(); - let config = Default::default(); - ron::ser::to_writer_pretty(file, &kv, config).unwrap(); - log::trace!("Persisted to {:?}", file_path); - }); + match std::thread::Builder::new() + .name("eframe_persist".to_owned()) + .spawn(move || { + save_to_disk(&file_path, &kv); + }) { + Ok(join_handle) => { + self.last_save_join_handle = Some(join_handle); + } + Err(err) => { + log::warn!("Failed to spawn thread to save app state: {err}"); + } + } + } + } +} - self.last_save_join_handle = Some(join_handle); +fn save_to_disk(file_path: &PathBuf, kv: &HashMap) { + crate::profile_function!(); + + if let Some(parent_dir) = file_path.parent() { + if !parent_dir.exists() { + if let Err(err) = std::fs::create_dir_all(parent_dir) { + log::warn!("Failed to create directory {parent_dir:?}: {err}"); + } + } + } + + match std::fs::File::create(file_path) { + Ok(file) => { + let config = Default::default(); + + if let Err(err) = ron::ser::to_writer_pretty(file, &kv, config) { + log::warn!("Failed to serialize app state: {err}"); + } else { + log::trace!("Persisted to {:?}", file_path); + } + } + Err(err) => { + log::warn!("Failed to create file {file_path:?}: {err}"); } } } From ea6bdfc1c9544e4a9239ec8007107f13c10eb4c7 Mon Sep 17 00:00:00 2001 From: Idan Arye Date: Fri, 11 Aug 2023 09:23:29 +0300 Subject: [PATCH 39/54] Force `ColorPickerFn` to be `Send + Sync` (#3148) (#3233) --- crates/egui/src/grid.rs | 4 ++-- crates/egui/src/ui.rs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index eb33e00ed..acd5c3576 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -47,7 +47,7 @@ impl State { // ---------------------------------------------------------------------------- // type alias for boxed function to determine row color during grid generation -type ColorPickerFn = Box Option>; +type ColorPickerFn = Box Option>; pub(crate) struct GridLayout { ctx: Context, @@ -312,7 +312,7 @@ impl Grid { /// Setting this will allow for dynamic coloring of rows of the grid object pub fn with_row_color(mut self, color_picker: F) -> Self where - F: Fn(usize, &Style) -> Option + 'static, + F: Send + Sync + Fn(usize, &Style) -> Option + 'static, { self.color_picker = Some(Box::new(color_picker)); self diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 83713e487..e29a40097 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -2245,3 +2245,9 @@ impl Ui { } } } + +#[test] +fn ui_impl_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); +} From bdeae9e9596f2b2fbe20c77d2c3a5e54da507f8c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 11 Aug 2023 08:24:39 +0200 Subject: [PATCH 40/54] Fix crash in DragValue when only setting `min_decimals` (#3231) --- crates/egui/src/widgets/drag_value.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index b09033c19..2b62c8d8f 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -405,7 +405,9 @@ impl<'a> Widget for DragValue<'a> { let auto_decimals = (aim_rad / speed.abs()).log10().ceil().clamp(0.0, 15.0) as usize; let auto_decimals = auto_decimals + is_slow_speed as usize; - let max_decimals = max_decimals.unwrap_or(auto_decimals + 2); + let max_decimals = max_decimals + .unwrap_or(auto_decimals + 2) + .at_least(min_decimals); let auto_decimals = auto_decimals.clamp(min_decimals, max_decimals); let change = ui.input_mut(|input| { From 08fb447fb55293b2d49343cf5ade2c59d436bc58 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 11 Aug 2023 13:54:02 +0200 Subject: [PATCH 41/54] Increase MSRV to 1.67 (#3234) * Bump MSRV to 1.67 * clippy fixes * cargo clippy: inline format args * Add `clippy::uninlined_format_args` to cranky lints * Fix clippy on wasm * More clippy fixes --- .github/workflows/rust.yml | 10 ++--- Cranky.toml | 1 + clippy.toml | 2 +- crates/ecolor/Cargo.toml | 2 +- crates/eframe/Cargo.toml | 2 +- crates/eframe/src/native/run.rs | 2 +- crates/eframe/src/web/mod.rs | 2 +- crates/eframe/src/web/text_agent.rs | 7 ++- crates/eframe/src/web/web_painter_glow.rs | 2 +- crates/eframe/src/web/web_painter_wgpu.rs | 3 +- crates/egui-wgpu/Cargo.toml | 2 +- crates/egui-wgpu/src/renderer.rs | 5 +-- crates/egui-winit/Cargo.toml | 2 +- crates/egui/Cargo.toml | 2 +- crates/egui/src/context.rs | 19 ++++---- crates/egui/src/data/input.rs | 14 +++--- crates/egui/src/data/output.rs | 20 ++++----- crates/egui/src/input_state.rs | 43 +++++++++---------- crates/egui/src/input_state/touch_state.rs | 4 +- crates/egui/src/introspection.rs | 7 +-- crates/egui/src/painter.rs | 2 +- crates/egui/src/widgets/color_picker.rs | 8 ++-- crates/egui/src/widgets/drag_value.rs | 2 +- crates/egui/src/widgets/plot/items/mod.rs | 2 +- crates/egui/src/widgets/plot/items/values.rs | 4 +- crates/egui/src/widgets/plot/mod.rs | 4 +- crates/egui_demo_app/Cargo.toml | 2 +- crates/egui_demo_app/src/apps/http_app.rs | 2 +- crates/egui_demo_app/src/backend_panel.rs | 5 +-- crates/egui_demo_app/src/wrap_app.rs | 2 +- crates/egui_demo_lib/Cargo.toml | 2 +- crates/egui_demo_lib/src/demo/about.rs | 4 +- crates/egui_demo_lib/src/demo/code_example.rs | 2 +- .../src/demo/demo_app_windows.rs | 4 +- crates/egui_demo_lib/src/demo/layout_test.rs | 4 +- .../src/demo/misc_demo_window.rs | 2 +- crates/egui_demo_lib/src/demo/multi_touch.rs | 2 +- crates/egui_demo_lib/src/demo/plot_demo.rs | 13 +++--- crates/egui_demo_lib/src/demo/scrolling.rs | 7 ++- crates/egui_demo_lib/src/demo/tests.rs | 10 ++--- .../egui_demo_lib/src/demo/widget_gallery.rs | 8 ++-- .../src/easy_mark/easy_mark_viewer.rs | 2 +- crates/egui_extras/Cargo.toml | 2 +- crates/egui_extras/src/datepicker/popup.rs | 2 +- crates/egui_extras/src/image.rs | 2 +- crates/egui_glium/Cargo.toml | 2 +- crates/egui_glow/Cargo.toml | 2 +- crates/emath/Cargo.toml | 2 +- crates/emath/src/lib.rs | 8 ++-- crates/emath/src/pos2.rs | 4 +- crates/emath/src/rot2.rs | 5 +-- crates/emath/src/vec2.rs | 4 +- crates/epaint/Cargo.toml | 2 +- crates/epaint/src/mesh.rs | 3 +- crates/epaint/src/mutex.rs | 2 +- crates/epaint/src/stats.rs | 2 +- crates/epaint/src/text/font.rs | 3 +- crates/epaint/src/text/fonts.rs | 17 +++----- examples/confirm_exit/Cargo.toml | 2 +- examples/custom_3d_glow/Cargo.toml | 2 +- examples/custom_3d_glow/src/main.rs | 2 +- examples/custom_font/Cargo.toml | 2 +- examples/custom_font_style/Cargo.toml | 2 +- examples/custom_window_frame/Cargo.toml | 2 +- examples/download_image/Cargo.toml | 2 +- examples/download_image/src/main.rs | 3 +- examples/file_dialog/Cargo.toml | 2 +- examples/hello_world/Cargo.toml | 2 +- examples/hello_world_par/Cargo.toml | 2 +- examples/hello_world_par/src/main.rs | 2 +- examples/hello_world_simple/Cargo.toml | 2 +- examples/keyboard_events/Cargo.toml | 2 +- examples/puffin_profiler/Cargo.toml | 2 +- examples/puffin_profiler/src/main.rs | 2 +- examples/retained_image/Cargo.toml | 2 +- examples/save_plot/Cargo.toml | 2 +- examples/save_plot/src/main.rs | 2 +- examples/screenshot/Cargo.toml | 2 +- examples/serial_windows/Cargo.toml | 2 +- examples/svg/Cargo.toml | 2 +- examples/user_attention/Cargo.toml | 2 +- examples/user_attention/src/main.rs | 2 +- rust-toolchain | 2 +- scripts/clippy_wasm/clippy.toml | 2 +- 84 files changed, 166 insertions(+), 193 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 61fb36f79..cd73fd9d8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -19,7 +19,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.65.0 + toolchain: 1.67.0 - name: Install packages (Linux) if: runner.os == 'Linux' @@ -87,7 +87,7 @@ jobs: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.65.0 + toolchain: 1.67.0 targets: wasm32-unknown-unknown - run: sudo apt-get update && sudo apt-get install libgtk-3-dev @@ -145,7 +145,7 @@ jobs: - uses: actions/checkout@v3 - uses: EmbarkStudios/cargo-deny-action@v1 with: - rust-version: "1.65.0" + rust-version: "1.67.0" log-level: error command: check arguments: --target ${{ matrix.target }} @@ -160,7 +160,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.65.0 + toolchain: 1.67.0 targets: aarch64-linux-android - name: Set up cargo cache @@ -178,7 +178,7 @@ jobs: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.65.0 + toolchain: 1.67.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 diff --git a/Cranky.toml b/Cranky.toml index 6fa6ca093..aaa2d8d6d 100644 --- a/Cranky.toml +++ b/Cranky.toml @@ -91,6 +91,7 @@ warn = [ "clippy::trailing_empty_array", "clippy::trait_duplication_in_bounds", "clippy::unimplemented", + "clippy::uninlined_format_args", "clippy::unnecessary_wraps", "clippy::unnested_or_patterns", "clippy::unused_peekable", diff --git a/clippy.toml b/clippy.toml index 31bbadb86..38feddcf0 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,6 +1,6 @@ # There is also a scripts/clippy_wasm/clippy.toml which forbids some mthods that are not available in wasm. -msrv = "1.65" +msrv = "1.67" # Allow-list of words for markdown in dosctrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown doc-valid-idents = [ diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index 6a2feb2ee..ea2f895fd 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -7,7 +7,7 @@ authors = [ ] description = "Color structs and color conversion utilities" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 81b36d82a..8046394e8 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "egui framework - write GUI apps that compiles to web and/or natively" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/eframe" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 3672c9bf1..af8d3aed6 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -708,7 +708,7 @@ mod glow_integration { let painter = egui_glow::Painter::new(gl.clone(), "", self.native_options.shader_version) - .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); + .unwrap_or_else(|err| panic!("An OpenGL error occurred: {err}\n")); let system_theme = system_theme(gl_window.window(), &self.native_options); let mut integration = epi_integration::EpiIntegration::new( diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 5cea5d612..9c3920a50 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -104,7 +104,7 @@ pub fn canvas_element(canvas_id: &str) -> Option { pub fn canvas_element_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement { canvas_element(canvas_id) - .unwrap_or_else(|| panic!("Failed to find canvas with id {:?}", canvas_id)) + .unwrap_or_else(|| panic!("Failed to find canvas with id {canvas_id:?}")) } fn canvas_origin(canvas_id: &str) -> egui::Pos2 { diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index 579f98c79..1688163b3 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -104,8 +104,7 @@ pub fn install_text_agent(runner_ref: &WebRunner) -> Result<(), JsValue> { runner_ref.add_event_listener(&input, "focusout", move |_event: web_sys::MouseEvent, _| { // Delay 10 ms, and focus again. let func = js_sys::Function::new_no_args(&format!( - "document.getElementById('{}').focus()", - AGENT_ID + "document.getElementById('{AGENT_ID}').focus()" )); window .set_timeout_with_callback_and_timeout_and_arguments_0(&func, 10) @@ -221,8 +220,8 @@ pub fn move_text_cursor(cursor: Option, canvas_id: &str) -> Option<( let x = (x - canvas.offset_width() as f32 / 2.0) .min(canvas.client_width() as f32 - bounding_rect.width() as f32); style.set_property("position", "absolute").ok()?; - style.set_property("top", &format!("{}px", y)).ok()?; - style.set_property("left", &format!("{}px", x)).ok() + style.set_property("top", &format!("{y}px")).ok()?; + style.set_property("left", &format!("{x}px")).ok() }) } else { style.set_property("position", "absolute").ok()?; diff --git a/crates/eframe/src/web/web_painter_glow.rs b/crates/eframe/src/web/web_painter_glow.rs index 939ac8a0b..b12ac1cef 100644 --- a/crates/eframe/src/web/web_painter_glow.rs +++ b/crates/eframe/src/web/web_painter_glow.rs @@ -27,7 +27,7 @@ impl WebPainterGlow { let gl = std::sync::Arc::new(gl); let painter = egui_glow::Painter::new(gl, shader_prefix, None) - .map_err(|error| format!("Error starting glow painter: {}", error))?; + .map_err(|err| format!("Error starting glow painter: {err}"))?; Ok(Self { canvas, diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 80abb5bf7..83510dc4f 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -87,8 +87,7 @@ impl WebPainterWgpu { } else { // Workaround for https://github.com/gfx-rs/wgpu/issues/3710: // Don't use `create_surface_from_canvas`, but `create_surface` instead! - let raw_window = - EguiWebWindow(egui::util::hash(&format!("egui on wgpu {canvas_id}")) as u32); + let raw_window = EguiWebWindow(egui::util::hash(("egui on wgpu", canvas_id)) as u32); canvas.set_attribute("data-raw-handle", &raw_window.0.to_string()); #[allow(unsafe_code)] diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index d1812dcb0..7df86b6aa 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -8,7 +8,7 @@ authors = [ "Emil Ernerfeldt ", ] edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/egui-wgpu" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index fe925857d..c3def5fce 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -560,7 +560,7 @@ impl Renderer { } else { // allocate a new texture // Use same label for all resources associated with this texture id (no point in retyping the type) - let label_str = format!("egui_texid_{:?}", id); + let label_str = format!("egui_texid_{id:?}"); let label = Some(label_str.as_str()); let texture = device.create_texture(&wgpu::TextureDescriptor { label, @@ -904,8 +904,7 @@ fn create_sampler( }; device.create_sampler(&wgpu::SamplerDescriptor { label: Some(&format!( - "egui sampler (mag: {:?}, min {:?})", - mag_filter, min_filter + "egui sampler (mag: {mag_filter:?}, min {min_filter:?})" )), mag_filter, min_filter, diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index 27a456da9..2d6833d91 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Bindings for using egui with winit" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/egui-winit" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 913a17cd1..615195836 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "An easy-to-use immediate mode GUI that runs on both web and native" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui" license = "MIT OR Apache-2.0" readme = "../../README.md" diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index bcb61d2c0..d8d5a0f5a 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -566,7 +566,7 @@ impl Context { } let show_error = |widget_rect: Rect, text: String| { - let text = format!("🔥 {}", text); + let text = format!("🔥 {text}"); let color = self.style().visuals.error_fg_color; let painter = self.debug_painter(); painter.rect_stroke(widget_rect, 0.0, (1.0, color)); @@ -612,10 +612,10 @@ impl Context { let id_str = id.short_debug_format(); if prev_rect.min.distance(new_rect.min) < 4.0 { - show_error(new_rect, format!("Double use of {} ID {}", what, id_str)); + show_error(new_rect, format!("Double use of {what} ID {id_str}")); } else { - show_error(prev_rect, format!("First use of {} ID {}", what, id_str)); - show_error(new_rect, format!("Second use of {} ID {}", what, id_str)); + show_error(prev_rect, format!("First use of {what} ID {id_str}")); + show_error(new_rect, format!("Second use of {what} ID {id_str}")); } } @@ -1574,14 +1574,14 @@ impl Context { let pointer_pos = self .pointer_hover_pos() - .map_or_else(String::new, |pos| format!("{:?}", pos)); - ui.label(format!("Pointer pos: {}", pointer_pos)); + .map_or_else(String::new, |pos| format!("{pos:?}")); + ui.label(format!("Pointer pos: {pointer_pos}")); let top_layer = self .pointer_hover_pos() .and_then(|pos| self.layer_id_at(pos)) .map_or_else(String::new, |layer| layer.short_debug_format()); - ui.label(format!("Top layer under mouse: {}", top_layer)); + ui.label(format!("Top layer under mouse: {top_layer}")); ui.add_space(16.0); @@ -1667,7 +1667,7 @@ impl Context { ui.image(texture_id, size); }); - ui.label(format!("{} x {}", w, h)); + ui.label(format!("{w} x {h}")); ui.label(format!("{:.3} MB", meta.bytes_used() as f64 * 1e-6)); ui.label(format!("{:?}", meta.name)); ui.end_row(); @@ -1688,8 +1688,7 @@ impl Context { let (num_state, num_serialized) = self.data(|d| (d.len(), d.count_serialized())); ui.label(format!( - "{} widget states stored (of which {} are serialized).", - num_state, num_serialized + "{num_state} widget states stored (of which {num_serialized} are serialized)." )); ui.horizontal(|ui| { diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 4bb9fdc06..bb1b9f6a9 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -937,25 +937,25 @@ impl RawInput { focused, } = self; - ui.label(format!("screen_rect: {:?} points", screen_rect)); - ui.label(format!("pixels_per_point: {:?}", pixels_per_point)) + ui.label(format!("screen_rect: {screen_rect:?} points")); + ui.label(format!("pixels_per_point: {pixels_per_point:?}")) .on_hover_text( "Also called HDPI factor.\nNumber of physical pixels per each logical pixel.", ); - ui.label(format!("max_texture_side: {:?}", max_texture_side)); + ui.label(format!("max_texture_side: {max_texture_side:?}")); if let Some(time) = time { - ui.label(format!("time: {:.3} s", time)); + ui.label(format!("time: {time:.3} s")); } else { ui.label("time: None"); } ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt)); - ui.label(format!("modifiers: {:#?}", modifiers)); + ui.label(format!("modifiers: {modifiers:#?}")); ui.label(format!("hovered_files: {}", hovered_files.len())); ui.label(format!("dropped_files: {}", dropped_files.len())); - ui.label(format!("focused: {}", focused)); + ui.label(format!("focused: {focused}")); ui.scope(|ui| { ui.set_min_height(150.0); - ui.label(format!("events: {:#?}", events)) + ui.label(format!("events: {events:#?}")) .on_hover_text("key presses etc"); }); } diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index cea031ed0..156a34928 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -100,7 +100,7 @@ impl PlatformOutput { /// This can be used by a text-to-speech system to describe the events (if any). pub fn events_description(&self) -> String { // only describe last event: - if let Some(event) = self.events.iter().rev().next() { + if let Some(event) = self.events.iter().next_back() { match event { OutputEvent::Clicked(widget_info) | OutputEvent::DoubleClicked(widget_info) @@ -417,12 +417,12 @@ impl OutputEvent { impl std::fmt::Debug for OutputEvent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Clicked(wi) => write!(f, "Clicked({:?})", wi), - Self::DoubleClicked(wi) => write!(f, "DoubleClicked({:?})", wi), - Self::TripleClicked(wi) => write!(f, "TripleClicked({:?})", wi), - Self::FocusGained(wi) => write!(f, "FocusGained({:?})", wi), - Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({:?})", wi), - Self::ValueChanged(wi) => write!(f, "ValueChanged({:?})", wi), + Self::Clicked(wi) => write!(f, "Clicked({wi:?})"), + Self::DoubleClicked(wi) => write!(f, "DoubleClicked({wi:?})"), + Self::TripleClicked(wi) => write!(f, "TripleClicked({wi:?})"), + Self::FocusGained(wi) => write!(f, "FocusGained({wi:?})"), + Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({wi:?})"), + Self::ValueChanged(wi) => write!(f, "ValueChanged({wi:?})"), } } } @@ -609,14 +609,14 @@ impl WidgetInfo { if let Some(selected) = selected { if *typ == WidgetType::Checkbox { let state = if *selected { "checked" } else { "unchecked" }; - description = format!("{} {}", state, description); + description = format!("{state} {description}"); } else { description += if *selected { "selected" } else { "" }; }; } if let Some(label) = label { - description = format!("{}: {}", label, description); + description = format!("{label}: {description}"); } if typ == &WidgetType::TextEdit { @@ -630,7 +630,7 @@ impl WidgetInfo { } else { text = "blank".into(); } - description = format!("{}: {}", text, description); + description = format!("{text}: {description}"); } if let Some(value) = value { diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 1b49757bc..1c0fe22f6 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -990,30 +990,28 @@ impl InputState { }); } - ui.label(format!("scroll_delta: {:?} points", scroll_delta)); - ui.label(format!("zoom_factor_delta: {:4.2}x", zoom_factor_delta)); - ui.label(format!("screen_rect: {:?} points", screen_rect)); + ui.label(format!("scroll_delta: {scroll_delta:?} points")); + ui.label(format!("zoom_factor_delta: {zoom_factor_delta:4.2}x")); + ui.label(format!("screen_rect: {screen_rect:?} points")); ui.label(format!( - "{} physical pixels for each logical point", - pixels_per_point + "{pixels_per_point} physical pixels for each logical point" )); ui.label(format!( - "max texture size (on each side): {}", - max_texture_side + "max texture size (on each side): {max_texture_side}" )); - ui.label(format!("time: {:.3} s", time)); + ui.label(format!("time: {time:.3} s")); ui.label(format!( "time since previous frame: {:.1} ms", 1e3 * unstable_dt )); ui.label(format!("predicted_dt: {:.1} ms", 1e3 * predicted_dt)); ui.label(format!("stable_dt: {:.1} ms", 1e3 * stable_dt)); - ui.label(format!("focused: {}", focused)); - ui.label(format!("modifiers: {:#?}", modifiers)); - ui.label(format!("keys_down: {:?}", keys_down)); + ui.label(format!("focused: {focused}")); + ui.label(format!("modifiers: {modifiers:#?}")); + ui.label(format!("keys_down: {keys_down:?}")); ui.scope(|ui| { ui.set_min_height(150.0); - ui.label(format!("events: {:#?}", events)) + ui.label(format!("events: {events:#?}")) .on_hover_text("key presses etc"); }); } @@ -1037,22 +1035,21 @@ impl PointerState { pointer_events, } = self; - ui.label(format!("latest_pos: {:?}", latest_pos)); - ui.label(format!("interact_pos: {:?}", interact_pos)); - ui.label(format!("delta: {:?}", delta)); + ui.label(format!("latest_pos: {latest_pos:?}")); + ui.label(format!("interact_pos: {interact_pos:?}")); + ui.label(format!("delta: {delta:?}")); ui.label(format!( "velocity: [{:3.0} {:3.0}] points/sec", velocity.x, velocity.y )); - ui.label(format!("down: {:#?}", down)); - ui.label(format!("press_origin: {:?}", press_origin)); - ui.label(format!("press_start_time: {:?} s", press_start_time)); + ui.label(format!("down: {down:#?}")); + ui.label(format!("press_origin: {press_origin:?}")); + ui.label(format!("press_start_time: {press_start_time:?} s")); ui.label(format!( - "has_moved_too_much_for_a_click: {}", - has_moved_too_much_for_a_click + "has_moved_too_much_for_a_click: {has_moved_too_much_for_a_click}" )); - ui.label(format!("last_click_time: {:#?}", last_click_time)); - ui.label(format!("last_last_click_time: {:#?}", last_last_click_time)); - ui.label(format!("pointer_events: {:?}", pointer_events)); + ui.label(format!("last_click_time: {last_click_time:#?}")); + ui.label(format!("last_last_click_time: {last_last_click_time:#?}")); + ui.label(format!("pointer_events: {pointer_events:?}")); } } diff --git a/crates/egui/src/input_state/touch_state.rs b/crates/egui/src/input_state/touch_state.rs index becd0d527..37d418a53 100644 --- a/crates/egui/src/input_state/touch_state.rs +++ b/crates/egui/src/input_state/touch_state.rs @@ -286,7 +286,7 @@ impl TouchState { impl TouchState { pub fn ui(&self, ui: &mut crate::Ui) { - ui.label(format!("{:?}", self)); + ui.label(format!("{self:?}")); } } @@ -294,7 +294,7 @@ impl Debug for TouchState { // This outputs less clutter than `#[derive(Debug)]`: fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for (id, touch) in &self.active_touches { - f.write_fmt(format_args!("#{:?}: {:#?}\n", id, touch))?; + f.write_fmt(format_args!("#{id:?}: {touch:#?}\n"))?; } f.write_fmt(format_args!("gesture: {:#?}\n", self.gesture_state))?; Ok(()) diff --git a/crates/egui/src/introspection.rs b/crates/egui/src/introspection.rs index ae66cddb0..6f0cada78 100644 --- a/crates/egui/src/introspection.rs +++ b/crates/egui/src/introspection.rs @@ -31,10 +31,7 @@ pub(crate) fn font_texture_ui(ui: &mut Ui, [width, height]: [usize; 2]) -> Respo Color32::BLACK }; - ui.label(format!( - "Texture size: {} x {} (hover to zoom)", - width, height - )); + ui.label(format!("Texture size: {width} x {height} (hover to zoom)")); if width <= 1 || height <= 1 { return; } @@ -108,7 +105,7 @@ impl Widget for &epaint::stats::PaintStats { label(ui, shape_path, "paths"); label(ui, shape_mesh, "nested meshes"); label(ui, shape_vec, "nested shapes"); - ui.label(format!("{:6} callbacks", num_callbacks)); + ui.label(format!("{num_callbacks:6} callbacks")); ui.add_space(10.0); ui.label("Text shapes:"); diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index baf3b9212..17028e6cc 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -226,7 +226,7 @@ impl Painter { pub fn error(&self, pos: Pos2, text: impl std::fmt::Display) -> Rect { let color = self.ctx.style().visuals.error_fg_color; - self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {}", text)) + self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {text}")) } /// text with a background diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index da5f2867d..fcb701f47 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -234,17 +234,17 @@ fn color_text_ui(ui: &mut Ui, color: impl Into, alpha: Alpha) { if ui.button("📋").on_hover_text("Click to copy").clicked() { if alpha == Alpha::Opaque { - ui.output_mut(|o| o.copied_text = format!("{}, {}, {}", r, g, b)); + ui.output_mut(|o| o.copied_text = format!("{r}, {g}, {b}")); } else { - ui.output_mut(|o| o.copied_text = format!("{}, {}, {}, {}", r, g, b, a)); + ui.output_mut(|o| o.copied_text = format!("{r}, {g}, {b}, {a}")); } } if alpha == Alpha::Opaque { - ui.label(format!("rgb({}, {}, {})", r, g, b)) + ui.label(format!("rgb({r}, {g}, {b})")) .on_hover_text("Red Green Blue"); } else { - ui.label(format!("rgba({}, {}, {}, {})", r, g, b, a)) + ui.label(format!("rgba({r}, {g}, {b}, {a})")) .on_hover_text("Red Green Blue with premultiplied Alpha"); } }); diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index 2b62c8d8f..0e1b54605 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -627,7 +627,7 @@ impl<'a> Widget for DragValue<'a> { // The value is exposed as a string by the text edit widget // when in edit mode. if !is_kb_editing { - let value_text = format!("{}{}{}", prefix, value_text, suffix); + let value_text = format!("{prefix}{value_text}{suffix}"); builder.set_value(value_text); } }); diff --git a/crates/egui/src/widgets/plot/items/mod.rs b/crates/egui/src/widgets/plot/items/mod.rs index f18f34aff..2cbcb26dd 100644 --- a/crates/egui/src/widgets/plot/items/mod.rs +++ b/crates/egui/src/widgets/plot/items/mod.rs @@ -1732,7 +1732,7 @@ pub(super) fn rulers_at_value( let mut prefix = String::new(); if !name.is_empty() { - prefix = format!("{}\n", name); + prefix = format!("{name}\n"); } let text = { diff --git a/crates/egui/src/widgets/plot/items/values.rs b/crates/egui/src/widgets/plot/items/values.rs index 739490dcf..52fceba7b 100644 --- a/crates/egui/src/widgets/plot/items/values.rs +++ b/crates/egui/src/widgets/plot/items/values.rs @@ -125,8 +125,8 @@ impl ToString for LineStyle { fn to_string(&self) -> String { match self { LineStyle::Solid => "Solid".into(), - LineStyle::Dotted { spacing } => format!("Dotted{}Px", spacing), - LineStyle::Dashed { length } => format!("Dashed{}Px", length), + LineStyle::Dotted { spacing } => format!("Dotted{spacing}Px"), + LineStyle::Dashed { length } => format!("Dashed{length}Px"), } } } diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui/src/widgets/plot/mod.rs index 9eca796dc..ebc73d728 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui/src/widgets/plot/mod.rs @@ -1453,7 +1453,7 @@ impl PreparedPlot { let axis_range = match axis { 0 => bounds.range_x(), 1 => bounds.range_y(), - _ => panic!("Axis {} does not exist.", axis), + _ => panic!("Axis {axis} does not exist."), }; let font_id = TextStyle::Body.resolve(ui.style()); @@ -1676,7 +1676,7 @@ pub fn format_number(number: f64, num_decimals: usize) -> String { let is_integral = number as i64 as f64 == number; if is_integral { // perfect integer - show it as such: - format!("{:.0}", number) + format!("{number:.0}") } else { // make sure we tell the user it is not an integer by always showing a decimal or two: format!("{:.*}", num_decimals.at_least(1), number) diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index af3eef61f..79ae48746 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false default-run = "egui_demo_app" diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index 4d87a3e29..e550c1dbc 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -133,7 +133,7 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut eframe::Frame, url: &mut String) -> boo if ui.button("Random image").clicked() { let seed = ui.input(|i| i.time); let side = 640; - *url = format!("https://picsum.photos/seed/{}/{}", seed, side); + *url = format!("https://picsum.photos/seed/{seed}/{side}"); trigger_fetch = true; } }); diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 33d1cc630..6480756f2 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -232,8 +232,7 @@ impl BackendPanel { if ui .add_enabled(enabled, egui::Button::new("Reset")) .on_hover_text(format!( - "Reset scale to native value ({:.1})", - native_pixels_per_point + "Reset scale to native value ({native_pixels_per_point:.1})" )) .clicked() { @@ -441,7 +440,7 @@ impl EguiWindows { .stick_to_bottom(true) .show(ui, |ui| { for event in output_event_history { - ui.label(format!("{:?}", event)); + ui.label(format!("{event:?}")); } }); }); diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 3b2c09a79..69a23e7ab 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -354,7 +354,7 @@ impl WrapApp { { selected_anchor = anchor; if frame.is_web() { - ui.output_mut(|o| o.open_url(format!("#{}", anchor))); + ui.output_mut(|o| o.open_url(format!("#{anchor}"))); } } } diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index d8f7c688d..e86baca29 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Example library for egui" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/egui_demo_lib" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/egui_demo_lib/src/demo/about.rs b/crates/egui_demo_lib/src/demo/about.rs index 0fbb97642..4972b224b 100644 --- a/crates/egui_demo_lib/src/demo/about.rs +++ b/crates/egui_demo_lib/src/demo/about.rs @@ -83,11 +83,11 @@ fn about_immediate_mode(ui: &mut egui::Ui) { fn links(ui: &mut egui::Ui) { use egui::special_emojis::{GITHUB, TWITTER}; ui.hyperlink_to( - format!("{} egui on GitHub", GITHUB), + format!("{GITHUB} egui on GitHub"), "https://github.com/emilk/egui", ); ui.hyperlink_to( - format!("{} @ernerfeldt", TWITTER), + format!("{TWITTER} @ernerfeldt"), "https://twitter.com/ernerfeldt", ); ui.hyperlink_to("egui documentation", "https://docs.rs/egui/"); diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index c72fde221..7b1351476 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -121,7 +121,7 @@ impl CodeExample { ui.separator(); - code_view_ui(ui, &format!("{:#?}", self)); + code_view_ui(ui, &format!("{self:#?}")); ui.separator(); 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 4ad6c2d95..354bb44e6 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -251,11 +251,11 @@ impl DemoWindows { use egui::special_emojis::{GITHUB, TWITTER}; ui.hyperlink_to( - format!("{} egui on GitHub", GITHUB), + format!("{GITHUB} egui on GitHub"), "https://github.com/emilk/egui", ); ui.hyperlink_to( - format!("{} @ernerfeldt", TWITTER), + format!("{TWITTER} @ernerfeldt"), "https://twitter.com/ernerfeldt", ); diff --git a/crates/egui_demo_lib/src/demo/layout_test.rs b/crates/egui_demo_lib/src/demo/layout_test.rs index 2d4fa8a82..8adf1bb6b 100644 --- a/crates/egui_demo_lib/src/demo/layout_test.rs +++ b/crates/egui_demo_lib/src/demo/layout_test.rs @@ -140,7 +140,7 @@ impl LayoutTest { Direction::TopDown, Direction::BottomUp, ] { - ui.radio_value(&mut self.layout.main_dir, dir, format!("{:?}", dir)); + ui.radio_value(&mut self.layout.main_dir, dir, format!("{dir:?}")); } }); @@ -162,7 +162,7 @@ impl LayoutTest { ui.horizontal(|ui| { ui.label("Cross Align:"); for &align in &[Align::Min, Align::Center, Align::Max] { - ui.radio_value(&mut self.layout.cross_align, align, format!("{:?}", align)); + ui.radio_value(&mut self.layout.cross_align, align, format!("{align:?}")); } }); 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 61a6091e9..e4cf92644 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -455,7 +455,7 @@ impl Tree { .into_iter() .enumerate() .filter_map(|(i, mut tree)| { - if tree.ui_impl(ui, depth + 1, &format!("child #{}", i)) == Action::Keep { + if tree.ui_impl(ui, depth + 1, &format!("child #{i}")) == Action::Keep { Some(tree) } else { None diff --git a/crates/egui_demo_lib/src/demo/multi_touch.rs b/crates/egui_demo_lib/src/demo/multi_touch.rs index c358f6b21..1a3b29e8e 100644 --- a/crates/egui_demo_lib/src/demo/multi_touch.rs +++ b/crates/egui_demo_lib/src/demo/multi_touch.rs @@ -50,7 +50,7 @@ impl super::View for MultiTouch { ui.label("Try touch gestures Pinch/Stretch, Rotation, and Pressure with 2+ fingers."); let num_touches = ui.input(|i| i.multi_touch().map_or(0, |mt| mt.num_touches)); - ui.label(format!("Current touches: {}", num_touches)); + ui.label(format!("Current touches: {num_touches}")); let color = if ui.visuals().dark_mode { Color32::WHITE diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 2f6180eae..8daf3c377 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -326,7 +326,7 @@ impl MarkerDemo { [5.0, 0.0 + y_offset], [6.0, 0.5 + y_offset], ]) - .name(format!("{:?}", marker)) + .name(format!("{marker:?}")) .filled(self.fill_markers) .radius(self.marker_radius) .shape(marker); @@ -416,7 +416,7 @@ impl LegendDemo { ui.label("Position:"); ui.horizontal(|ui| { Corner::all().for_each(|position| { - ui.selectable_value(&mut config.position, position, format!("{:?}", position)); + ui.selectable_value(&mut config.position, position, format!("{position:?}")); }); }); ui.end_row(); @@ -774,21 +774,18 @@ impl InteractionDemo { "origin in screen coordinates: x: {:.02}, y: {:.02}", screen_pos.x, screen_pos.y )); - ui.label(format!("plot hovered: {}", hovered)); + ui.label(format!("plot hovered: {hovered}")); let coordinate_text = if let Some(coordinate) = pointer_coordinate { format!("x: {:.02}, y: {:.02}", coordinate.x, coordinate.y) } else { "None".to_owned() }; - ui.label(format!("pointer coordinate: {}", coordinate_text)); + ui.label(format!("pointer coordinate: {coordinate_text}")); let coordinate_text = format!( "x: {:.02}, y: {:.02}", pointer_coordinate_drag_delta.x, pointer_coordinate_drag_delta.y ); - ui.label(format!( - "pointer coordinate drag delta: {}", - coordinate_text - )); + ui.label(format!("pointer coordinate drag delta: {coordinate_text}")); response } diff --git a/crates/egui_demo_lib/src/demo/scrolling.rs b/crates/egui_demo_lib/src/demo/scrolling.rs index b970b3264..f5833521b 100644 --- a/crates/egui_demo_lib/src/demo/scrolling.rs +++ b/crates/egui_demo_lib/src/demo/scrolling.rs @@ -232,10 +232,10 @@ impl super::View for ScrollTo { for item in 1..=50 { if track_item && item == self.track_item { let response = - ui.colored_label(Color32::YELLOW, format!("This is item {}", item)); + ui.colored_label(Color32::YELLOW, format!("This is item {item}")); response.scroll_to_me(self.tack_item_align); } else { - ui.label(format!("This is item {}", item)); + ui.label(format!("This is item {item}")); } } }); @@ -254,8 +254,7 @@ impl super::View for ScrollTo { ui.separator(); ui.label(format!( - "Scroll offset: {:.0}/{:.0} px", - current_scroll, max_scroll + "Scroll offset: {current_scroll:.0}/{max_scroll:.0} px" )); ui.separator(); diff --git a/crates/egui_demo_lib/src/demo/tests.rs b/crates/egui_demo_lib/src/demo/tests.rs index 83e798769..784b9525c 100644 --- a/crates/egui_demo_lib/src/demo/tests.rs +++ b/crates/egui_demo_lib/src/demo/tests.rs @@ -20,7 +20,7 @@ impl super::View for CursorTest { ui.heading("Hover to switch cursor icon:"); for &cursor_icon in &egui::CursorIcon::ALL { let _ = ui - .button(format!("{:?}", cursor_icon)) + .button(format!("{cursor_icon:?}")) .on_hover_cursor(cursor_icon); } ui.add(crate::egui_github_link_file!()); @@ -239,7 +239,7 @@ impl super::View for TableTest { for row in 0..self.num_rows { for col in 0..self.num_cols { if col == 0 { - ui.label(format!("row {}", row)); + ui.label(format!("row {row}")); } else { let word_idx = row * 3 + col * 5; let word_count = (row * 5 + col * 75) % 13; @@ -350,13 +350,13 @@ impl super::View for InputTest { use std::fmt::Write as _; if response.clicked_by(button) { - writeln!(new_info, "Clicked by {:?} button", button).ok(); + writeln!(new_info, "Clicked by {button:?} button").ok(); } if response.double_clicked_by(button) { - writeln!(new_info, "Double-clicked by {:?} button", button).ok(); + writeln!(new_info, "Double-clicked by {button:?} button").ok(); } if response.triple_clicked_by(button) { - writeln!(new_info, "Triple-clicked by {:?} button", button).ok(); + writeln!(new_info, "Triple-clicked by {button:?} button").ok(); } if response.dragged_by(button) { writeln!( diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index d91ae395e..eb424e9e3 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -126,7 +126,7 @@ impl WidgetGallery { ui.add(doc_link_label("Hyperlink", "Hyperlink")); use egui::special_emojis::GITHUB; ui.hyperlink_to( - format!("{} egui on GitHub", GITHUB), + format!("{GITHUB} egui on GitHub"), "https://github.com/emilk/egui", ); ui.end_row(); @@ -173,7 +173,7 @@ impl WidgetGallery { ui.add(doc_link_label("ComboBox", "ComboBox")); egui::ComboBox::from_label("Take your pick") - .selected_text(format!("{:?}", radio)) + .selected_text(format!("{radio:?}")) .show_ui(ui, |ui| { ui.style_mut().wrap = Some(false); ui.set_min_width(60.0); @@ -277,8 +277,8 @@ fn example_plot(ui: &mut egui::Ui) -> egui::Response { } fn doc_link_label<'a>(title: &'a str, search_term: &'a str) -> impl egui::Widget + 'a { - let label = format!("{}:", title); - let url = format!("https://docs.rs/egui?search={}", search_term); + let label = format!("{title}:"); + let url = format!("https://docs.rs/egui?search={search_term}"); move |ui: &mut egui::Ui| { ui.hyperlink_to(label, url).on_hover_ui(|ui| { ui.horizontal_wrapped(|ui| { diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs index 44408ff2b..29be2d2ee 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_viewer.rs @@ -161,7 +161,7 @@ fn numbered_point(ui: &mut Ui, width: f32, number: &str) -> Response { let font_id = TextStyle::Body.resolve(ui.style()); let row_height = ui.fonts(|f| f.row_height(&font_id)); let (rect, response) = ui.allocate_exact_size(vec2(width, row_height), Sense::hover()); - let text = format!("{}.", number); + let text = format!("{number}."); let text_color = ui.visuals().strong_text_color(); ui.painter().text( rect.right_center(), diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index f5f4595aa..fe3bf8876 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -8,7 +8,7 @@ authors = [ ] description = "Extra functionality and widgets for the egui GUI library" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/egui_extras/src/datepicker/popup.rs b/crates/egui_extras/src/datepicker/popup.rs index 7da8cbc1a..d8dd46724 100644 --- a/crates/egui_extras/src/datepicker/popup.rs +++ b/crates/egui_extras/src/datepicker/popup.rs @@ -428,6 +428,6 @@ fn month_name(i: u32) -> &'static str { 10 => "October", 11 => "November", 12 => "December", - _ => panic!("Unknown month: {}", i), + _ => panic!("Unknown month: {i}"), } } diff --git a/crates/egui_extras/src/image.rs b/crates/egui_extras/src/image.rs index ba8acaf41..bec11d1ab 100644 --- a/crates/egui_extras/src/image.rs +++ b/crates/egui_extras/src/image.rs @@ -258,7 +258,7 @@ pub fn load_svg_bytes_with_size( }; let mut pixmap = tiny_skia::Pixmap::new(w, h) - .ok_or_else(|| format!("Failed to create SVG Pixmap of size {}x{}", w, h))?; + .ok_or_else(|| format!("Failed to create SVG Pixmap of size {w}x{h}"))?; resvg::render(&rtree, fit_to, Default::default(), pixmap.as_mut()) .ok_or_else(|| "Failed to render SVG".to_owned())?; diff --git a/crates/egui_glium/Cargo.toml b/crates/egui_glium/Cargo.toml index 9be5870dd..a4be314d4 100644 --- a/crates/egui_glium/Cargo.toml +++ b/crates/egui_glium/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Bindings for using egui natively using the glium library" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glium" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 1c1a797cb..dfd6e0de4 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Bindings for using egui natively using the glow library" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glow" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/emath/Cargo.toml b/crates/emath/Cargo.toml index d4eec7c7e..8fea677e3 100644 --- a/crates/emath/Cargo.toml +++ b/crates/emath/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Minimal 2D math library for GUI work" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/emath" license = "MIT OR Apache-2.0" readme = "README.md" diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 25b3b9eac..8cfe16de0 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -183,9 +183,7 @@ where /// Round a value to the given number of decimal places. pub fn round_to_decimals(value: f64, decimal_places: usize) -> f64 { // This is a stupid way of doing this, but stupid works. - format!("{:.*}", decimal_places, value) - .parse() - .unwrap_or(value) + format!("{value:.decimal_places$}").parse().unwrap_or(value) } pub fn format_with_minimum_decimals(value: f64, decimals: usize) -> String { @@ -203,7 +201,7 @@ pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive().unwrap(), value as f32, epsilon) { // Enough precision to show the value accurately - good! @@ -214,7 +212,7 @@ pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive for Pos2 { match index { 0 => &self.x, 1 => &self.y, - _ => panic!("Pos2 index out of bounds: {}", index), + _ => panic!("Pos2 index out of bounds: {index}"), } } } @@ -217,7 +217,7 @@ impl std::ops::IndexMut for Pos2 { match index { 0 => &mut self.x, 1 => &mut self.y, - _ => panic!("Pos2 index out of bounds: {}", index), + _ => panic!("Pos2 index out of bounds: {index}"), } } } diff --git a/crates/emath/src/rot2.rs b/crates/emath/src/rot2.rs index e29f18f4e..b281ca58f 100644 --- a/crates/emath/src/rot2.rs +++ b/crates/emath/src/rot2.rs @@ -184,10 +184,7 @@ mod test { let expected = vec2(0.0, 3.0); assert!( (rotated - expected).length() < 1e-5, - "Expected {:?} to equal {:?}. rot: {:?}", - rotated, - expected, - rot, + "Expected {rotated:?} to equal {expected:?}. rot: {rot:?}", ); let undone = rot.inverse() * rot; diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index ac70f9b47..133c3fd3c 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -292,7 +292,7 @@ impl std::ops::Index for Vec2 { match index { 0 => &self.x, 1 => &self.y, - _ => panic!("Vec2 index out of bounds: {}", index), + _ => panic!("Vec2 index out of bounds: {index}"), } } } @@ -303,7 +303,7 @@ impl std::ops::IndexMut for Vec2 { match index { 0 => &mut self.x, 1 => &mut self.y, - _ => panic!("Vec2 index out of bounds: {}", index), + _ => panic!("Vec2 index out of bounds: {index}"), } } } diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 094b6c45e..5a4c74242 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -4,7 +4,7 @@ version = "0.22.0" authors = ["Emil Ernerfeldt "] description = "Minimal 2D graphics library for GUI work" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" homepage = "https://github.com/emilk/egui/tree/master/crates/epaint" license = "(MIT OR Apache-2.0) AND OFL-1.1 AND LicenseRef-UFL-1.0" # OFL and UFL used by default_fonts. See https://github.com/emilk/egui/issues/2321 readme = "README.md" diff --git a/crates/epaint/src/mesh.rs b/crates/epaint/src/mesh.rs index 09025268a..62f0de21f 100644 --- a/crates/epaint/src/mesh.rs +++ b/crates/epaint/src/mesh.rs @@ -251,8 +251,7 @@ impl Mesh { assert!( index_cursor > span_start, - "One triangle spanned more than {} vertices", - MAX_SIZE + "One triangle spanned more than {MAX_SIZE} vertices" ); let mesh = Mesh16 { diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index cca86466e..ce2bf7c93 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -333,7 +333,7 @@ mod rw_lock_impl { fn format_backtrace(backtrace: &mut backtrace::Backtrace) -> String { backtrace.resolve(); - let stacktrace = format!("{:?}", backtrace); + let stacktrace = format!("{backtrace:?}"); // Remove irrelevant parts of the stacktrace: let end_offset = stacktrace diff --git a/crates/epaint/src/stats.rs b/crates/epaint/src/stats.rs index 24293aa7b..6723b61f4 100644 --- a/crates/epaint/src/stats.rs +++ b/crates/epaint/src/stats.rs @@ -106,7 +106,7 @@ impl AllocInfo { element_size: ElementSize::Homogeneous(element_size), num_allocs: 1, num_elements: slice.len(), - num_bytes: slice.len() * element_size, + num_bytes: std::mem::size_of_val(slice), } } diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 4cf26014f..dfcb52a11 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -367,8 +367,7 @@ impl Font { .or_else(|| slf.glyph_info_no_cache_or_fallback(FALLBACK_REPLACEMENT_CHAR)) .unwrap_or_else(|| { panic!( - "Failed to find replacement characters {:?} or {:?}", - PRIMARY_REPLACEMENT_CHAR, FALLBACK_REPLACEMENT_CHAR + "Failed to find replacement characters {PRIMARY_REPLACEMENT_CHAR:?} or {FALLBACK_REPLACEMENT_CHAR:?}" ) }); slf.replacement_glyph = replacement_glyph; diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 829a593f6..474d6e70e 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -200,7 +200,7 @@ fn ab_glyph_font_from_font_data(name: &str, data: &FontData) -> ab_glyph::FontAr .map(ab_glyph::FontArc::from) } } - .unwrap_or_else(|err| panic!("Error parsing {:?} TTF/OTF font file: {}", name, err)) + .unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}")) } /// Describes the font data and the sizes to use. @@ -586,8 +586,7 @@ impl FontsImpl { ) -> Self { assert!( 0.0 < pixels_per_point && pixels_per_point < 100.0, - "pixels_per_point out of range: {}", - pixels_per_point + "pixels_per_point out of range: {pixels_per_point}" ); let texture_width = max_texture_side.at_most(8 * 1024); @@ -627,9 +626,8 @@ impl FontsImpl { .entry((HashableF32(*size), family.clone())) .or_insert_with(|| { let fonts = &self.definitions.families.get(family); - let fonts = fonts.unwrap_or_else(|| { - panic!("FontFamily::{:?} is not bound to any fonts", family) - }); + let fonts = fonts + .unwrap_or_else(|| panic!("FontFamily::{family:?} is not bound to any fonts")); let fonts: Vec> = fonts .iter() @@ -752,17 +750,14 @@ impl FontImplCache { let (tweak, ab_glyph_font) = self .ab_glyph_fonts .get(font_name) - .unwrap_or_else(|| panic!("No font data found for {:?}", font_name)) + .unwrap_or_else(|| panic!("No font data found for {font_name:?}")) .clone(); let scale_in_pixels = self.pixels_per_point * scale_in_points; // Scale the font properly (see https://github.com/emilk/egui/issues/2068). let units_per_em = ab_glyph_font.units_per_em().unwrap_or_else(|| { - panic!( - "The font unit size of {:?} exceeds the expected range (16..=16384)", - font_name - ) + panic!("The font unit size of {font_name:?} exceeds the expected range (16..=16384)") }); let font_scaling = ab_glyph_font.height_unscaled() / units_per_em; let scale_in_pixels = scale_in_pixels * font_scaling; diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index e4b193e33..08144e7f6 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.65" +rust-version = "1.67" publish = false diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index 9dfa403c6..c75ee3c4d 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.65" +rust-version = "1.67" publish = false diff --git a/examples/custom_3d_glow/src/main.rs b/examples/custom_3d_glow/src/main.rs index f72859d23..7cd954c39 100644 --- a/examples/custom_3d_glow/src/main.rs +++ b/examples/custom_3d_glow/src/main.rs @@ -144,7 +144,7 @@ impl RotatingTriangle { let shader = gl .create_shader(*shader_type) .expect("Cannot create shader"); - gl.shader_source(shader, &format!("{}\n{}", shader_version, shader_source)); + gl.shader_source(shader, &format!("{shader_version}\n{shader_source}")); gl.compile_shader(shader); assert!( gl.get_shader_compile_status(shader), diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index 78d0491d3..c7b212b5e 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.65" +rust-version = "1.67" publish = false diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index 3eda5ee33..3c5e9600f 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.65" +rust-version = "1.67" publish = false diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index 1cad5da20..448d8c2bb 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.65" +rust-version = "1.67" publish = false diff --git a/examples/download_image/Cargo.toml b/examples/download_image/Cargo.toml index 0097d9e33..5e4f28e1c 100644 --- a/examples/download_image/Cargo.toml +++ b/examples/download_image/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/download_image/src/main.rs b/examples/download_image/src/main.rs index 2a2c3540e..9a48c8951 100644 --- a/examples/download_image/src/main.rs +++ b/examples/download_image/src/main.rs @@ -58,8 +58,7 @@ fn parse_response(response: ehttp::Response) -> Result { RetainedImage::from_image_bytes(&response.url, &response.bytes) } else { Err(format!( - "Expected image, found content-type {:?}", - content_type + "Expected image, found content-type {content_type:?}" )) } } diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index 5447e6815..02045d624 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.65" +rust-version = "1.67" publish = false diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index 9dc377ce9..580fe04e0 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.65" +rust-version = "1.67" publish = false diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 9b02345f5..76c1ac88a 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.65" +rust-version = "1.67" publish = false diff --git a/examples/hello_world_par/src/main.rs b/examples/hello_world_par/src/main.rs index b9d03ba6a..8a568cb1f 100644 --- a/examples/hello_world_par/src/main.rs +++ b/examples/hello_world_par/src/main.rs @@ -63,7 +63,7 @@ fn new_worker( ) -> (JoinHandle<()>, mpsc::SyncSender) { let (show_tx, show_rc) = mpsc::sync_channel(0); let handle = std::thread::Builder::new() - .name(format!("EguiPanelWorker {}", thread_nr)) + .name(format!("EguiPanelWorker {thread_nr}")) .spawn(move || { let mut state = ThreadState::new(thread_nr); while let Ok(ctx) = show_rc.recv() { diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index 1c6bcfa40..be399552b 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.65" +rust-version = "1.67" publish = false diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index 7e544a2e7..39a389f33 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.65" +rust-version = "1.67" publish = false diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index dbb048fea..90baa1acc 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.65" +rust-version = "1.67" publish = false diff --git a/examples/puffin_profiler/src/main.rs b/examples/puffin_profiler/src/main.rs index 2cfc5e61d..f8d52e91c 100644 --- a/examples/puffin_profiler/src/main.rs +++ b/examples/puffin_profiler/src/main.rs @@ -62,7 +62,7 @@ fn start_puffin_server() { std::mem::forget(puffin_server); } Err(err) => { - eprintln!("Failed to start puffin server: {}", err); + eprintln!("Failed to start puffin server: {err}"); } }; } diff --git a/examples/retained_image/Cargo.toml b/examples/retained_image/Cargo.toml index abbe01fd8..a1df10396 100644 --- a/examples/retained_image/Cargo.toml +++ b/examples/retained_image/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/save_plot/Cargo.toml b/examples/save_plot/Cargo.toml index 83f919d79..2a12692f8 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.65" +rust-version = "1.67" publish = false [dependencies] diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index 68c48182a..0d9de35cc 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::new(MyApp::default())), + Box::new(|_cc| Box::::default()), ) } diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index 692fc3678..3cc5887fb 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.65" +rust-version = "1.67" publish = false diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index 089858dd3..39f5a0222 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.65" +rust-version = "1.67" publish = false diff --git a/examples/svg/Cargo.toml b/examples/svg/Cargo.toml index fcf56e49f..219efd58c 100644 --- a/examples/svg/Cargo.toml +++ b/examples/svg/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65" +rust-version = "1.67" publish = false diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 8b4387936..3a7e49a13 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.65" +rust-version = "1.67" publish = false [dependencies] diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index 4f7e521d2..8134062fd 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -19,7 +19,7 @@ fn main() -> eframe::Result<()> { } fn repr(attention: UserAttentionType) -> String { - format!("{:?}", attention) + format!("{attention:?}") } struct Application { diff --git a/rust-toolchain b/rust-toolchain index 0e334035e..5698c9e2d 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.65.0" +channel = "1.67.0" components = [ "rustfmt", "clippy" ] targets = [ "wasm32-unknown-unknown" ] diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index e2ec8be96..436e1fb84 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -3,7 +3,7 @@ # We cannot forbid all these methods in the main `clippy.toml` because of # https://github.com/rust-lang/rust-clippy/issues/10406 -msrv = "1.65" +msrv = "1.67" # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods disallowed-methods = [ From dd5285cccb58fb9f312838120ecb30b5b767dee9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 11 Aug 2023 15:08:00 +0200 Subject: [PATCH 42/54] Support multi-threaded Wasm (#3236) Replace `atomic_refcell` with `parking_lot` on wasm32. `parking_lot` has had problems running on wasm32 before (https://github.com/emilk/egui/issues/1401) but it works these days. If we have problems again we can always switch to `std::sync::Mutex`. Closes https://github.com/emilk/egui/issues/3102 --- Cargo.lock | 7 --- crates/epaint/Cargo.toml | 6 +-- crates/epaint/src/mutex.rs | 104 +++++++------------------------------ 3 files changed, 19 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 37dd41bc6..d7e47d1c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,12 +326,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "atomic_refcell" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31" - [[package]] name = "atspi" version = "0.10.1" @@ -1394,7 +1388,6 @@ version = "0.22.0" dependencies = [ "ab_glyph", "ahash 0.8.3", - "atomic_refcell", "backtrace", "bytemuck", "criterion", diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 5a4c74242..747c69b15 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -79,6 +79,7 @@ ahash = { version = "0.8.1", default-features = false, features = [ "std", ] } nohash-hasher = "0.2" +parking_lot = "0.12" # 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"] } @@ -94,11 +95,6 @@ serde = { version = "1", optional = true, features = ["derive", "rc"] } # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] backtrace = { version = "0.3", optional = true } -parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. - -# web: -[target.'cfg(target_arch = "wasm32")'.dependencies] -atomic_refcell = "0.1" # Used instead of parking_lot on on wasm. See https://github.com/emilk/egui/issues/1401 [dev-dependencies] diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index ce2bf7c93..050fe9dfa 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -1,13 +1,14 @@ -//! Helper module that wraps some Mutex types with different implementations. +//! Helper module that adds extra checks when the `deadlock_detection` feature is turned on. // ---------------------------------------------------------------------------- -#[cfg(not(target_arch = "wasm32"))] -#[cfg(not(debug_assertions))] +#[cfg(not(feature = "deadlock_detection"))] mod mutex_impl { /// Provides interior mutability. /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. + /// This is a thin wrapper around [`parking_lot::Mutex`], except if + /// the feature `deadlock_detection` is turned enabled, in which case + /// extra checks are added to detect deadlocks. #[derive(Default)] pub struct Mutex(parking_lot::Mutex); @@ -27,12 +28,13 @@ mod mutex_impl { } } -#[cfg(not(target_arch = "wasm32"))] -#[cfg(debug_assertions)] +#[cfg(feature = "deadlock_detection")] mod mutex_impl { /// Provides interior mutability. /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. + /// This is a thin wrapper around [`parking_lot::Mutex`], except if + /// the feature `deadlock_detection` is turned enabled, in which case + /// extra checks are added to detect deadlocks. #[derive(Default)] pub struct Mutex(parking_lot::Mutex); @@ -115,7 +117,8 @@ mod mutex_impl { } } -#[cfg(not(target_arch = "wasm32"))] +// ---------------------------------------------------------------------------- + #[cfg(not(feature = "deadlock_detection"))] mod rw_lock_impl { /// The lock you get from [`RwLock::read`]. @@ -126,7 +129,9 @@ mod rw_lock_impl { /// Provides interior mutability. /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. + /// This is a thin wrapper around [`parking_lot::RwLock`], except if + /// 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); @@ -148,7 +153,6 @@ mod rw_lock_impl { } } -#[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "deadlock_detection")] mod rw_lock_impl { use std::{ @@ -246,7 +250,9 @@ mod rw_lock_impl { /// Provides interior mutability. /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. + /// This is a thin wrapper around [`parking_lot::RwLock`], except if + /// the feature `deadlock_detection` is turned enabled, in which case + /// extra checks are added to detect deadlocks. #[derive(Default)] pub struct RwLock { lock: parking_lot::RwLock, @@ -352,80 +358,6 @@ mod rw_lock_impl { // ---------------------------------------------------------------------------- -#[cfg(target_arch = "wasm32")] -mod mutex_impl { - // `atomic_refcell` will panic if multiple threads try to access the same value - - /// Provides interior mutability. - /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. - #[derive(Default)] - pub struct Mutex(atomic_refcell::AtomicRefCell); - - /// The lock you get from [`Mutex`]. - pub use atomic_refcell::AtomicRefMut as MutexGuard; - - impl Mutex { - #[inline(always)] - pub fn new(val: T) -> Self { - Self(atomic_refcell::AtomicRefCell::new(val)) - } - - /// Panics if already locked. - #[inline(always)] - pub fn lock(&self) -> MutexGuard<'_, T> { - self.0.borrow_mut() - } - - #[inline(always)] - pub fn into_inner(self) -> T { - self.0.into_inner() - } - } -} - -#[cfg(target_arch = "wasm32")] -mod rw_lock_impl { - // `atomic_refcell` will panic if multiple threads try to access the same value - - /// The lock you get from [`RwLock::read`]. - pub use atomic_refcell::AtomicRef as RwLockReadGuard; - - /// The lock you get from [`RwLock::write`]. - pub use atomic_refcell::AtomicRefMut as RwLockWriteGuard; - - /// Provides interior mutability. - /// - /// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets. - #[derive(Default)] - pub struct RwLock(atomic_refcell::AtomicRefCell); - - impl RwLock { - #[inline(always)] - pub fn new(val: T) -> Self { - Self(atomic_refcell::AtomicRefCell::new(val)) - } - - #[inline(always)] - pub fn read(&self) -> RwLockReadGuard<'_, T> { - self.0.borrow() - } - - /// Panics if already locked. - #[inline(always)] - pub fn write(&self) -> RwLockWriteGuard<'_, T> { - self.0.borrow_mut() - } - - #[inline(always)] - pub fn into_inner(self) -> T { - self.0.into_inner() - } - } -} - -// ---------------------------------------------------------------------------- - pub use mutex_impl::{Mutex, MutexGuard}; pub use rw_lock_impl::{RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -469,7 +401,7 @@ mod tests { let other_thread = { let one = Arc::clone(&one); std::thread::spawn(move || { - let _ = one.lock(); + let _lock = one.lock(); }) }; std::thread::sleep(Duration::from_millis(200)); From f78db80840c387f5f5fa6d1108db9791185450cd Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 11 Aug 2023 15:34:16 +0200 Subject: [PATCH 43/54] Update to wasm-bindgen 0.2.87 (#3237) * Update to wasm-bindgen 0.2.87 Required by the new `wgpu` version * Catch unknown arguments to build_demo_web.sh --- .github/workflows/rust.yml | 2 +- Cargo.lock | 20 ++++++++++---------- crates/eframe/Cargo.toml | 2 +- crates/egui_demo_app/Cargo.toml | 2 +- scripts/build_demo_web.sh | 3 ++- scripts/setup_web.sh | 2 +- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index cd73fd9d8..b4920b3bf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -112,7 +112,7 @@ jobs: - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 with: - version: "0.2.86" + version: "0.2.87" - run: ./scripts/wasm_bindgen_check.sh --skip-setup diff --git a/Cargo.lock b/Cargo.lock index d7e47d1c9..ff260433d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3854,9 +3854,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3864,9 +3864,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", @@ -3891,9 +3891,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3901,9 +3901,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", @@ -3914,9 +3914,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wayland-client" diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 8046394e8..d89f92d35 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -150,7 +150,7 @@ winapi = "0.3.9" bytemuck = "1.7" js-sys = "0.3" percent-encoding = "2.1" -wasm-bindgen = "0.2.86" +wasm-bindgen = "0.2.87" wasm-bindgen-futures = "0.4" web-sys = { version = "0.3.58", features = [ "BinaryType", diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 79ae48746..2f6b185f2 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -65,6 +65,6 @@ env_logger = "0.10" # web: [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = "=0.2.86" +wasm-bindgen = "=0.2.87" wasm-bindgen-futures = "0.4" web-sys = "0.3" diff --git a/scripts/build_demo_web.sh b/scripts/build_demo_web.sh index d77279b7b..37abcffc6 100755 --- a/scripts/build_demo_web.sh +++ b/scripts/build_demo_web.sh @@ -55,7 +55,8 @@ while test $# -gt 0; do ;; *) - break + echo "Unknown option: $1" + exit 1 ;; esac done diff --git a/scripts/setup_web.sh b/scripts/setup_web.sh index f8d3d08a0..53896e989 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 wasm-bindgen-cli --version 0.2.86 +cargo install wasm-bindgen-cli --version 0.2.87 From dd417cfc1acdf7ac88f214015d4d8e7a21835d30 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 11 Aug 2023 16:25:22 +0200 Subject: [PATCH 44/54] eframe: Better restore Window position on Mac when on secondary monitor (#3239) --- crates/eframe/src/native/epi_integration.rs | 15 +- crates/eframe/src/native/run.rs | 12 +- crates/egui-winit/src/window_settings.rs | 191 ++++++++++++-------- 3 files changed, 135 insertions(+), 83 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index a0bec23bd..725522eb0 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -140,10 +140,11 @@ pub fn window_builder( let inner_size_points = if let Some(mut window_settings) = window_settings { // Restore pos/size from previous session - window_settings.clamp_to_sane_values(largest_monitor_point_size(event_loop)); - #[cfg(windows)] - window_settings.clamp_window_to_sane_position(event_loop); - window_builder = window_settings.initialize_window(window_builder); + + window_settings.clamp_size_to_sane_values(largest_monitor_point_size(event_loop)); + window_settings.clamp_position_to_monitors(event_loop); + + window_builder = window_settings.initialize_window_builder(window_builder); window_settings.inner_size_points() } else { if let Some(pos) = *initial_window_pos { @@ -173,12 +174,14 @@ pub fn window_builder( } } } + window_builder } pub fn apply_native_options_to_window( window: &winit::window::Window, native_options: &crate::NativeOptions, + window_settings: Option, ) { use winit::window::WindowLevel; window.set_window_level(if native_options.always_on_top { @@ -186,6 +189,10 @@ pub fn apply_native_options_to_window( } else { WindowLevel::Normal }); + + if let Some(window_settings) = window_settings { + window_settings.initialize_window(window); + } } fn largest_monitor_point_size(event_loop: &EventLoopWindowTarget) -> egui::Vec2 { diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index af8d3aed6..00bc23bac 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -675,7 +675,11 @@ mod glow_integration { glutin_window_context.on_resume(event_loop)?; if let Some(window) = &glutin_window_context.window { - epi_integration::apply_native_options_to_window(window, native_options); + epi_integration::apply_native_options_to_window( + window, + native_options, + window_settings, + ); } let gl = unsafe { @@ -1128,7 +1132,11 @@ mod wgpu_integration { let window_builder = epi_integration::window_builder(event_loop, title, native_options, window_settings); let window = window_builder.build(event_loop)?; - epi_integration::apply_native_options_to_window(&window, native_options); + epi_integration::apply_native_options_to_window( + &window, + native_options, + window_settings, + ); Ok(window) } diff --git a/crates/egui-winit/src/window_settings.rs b/crates/egui-winit/src/window_settings.rs index 0137af22e..5d685765e 100644 --- a/crates/egui-winit/src/window_settings.rs +++ b/crates/egui-winit/src/window_settings.rs @@ -1,11 +1,13 @@ /// Can be used to store native window settings (position and size). -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] pub struct WindowSettings { - /// Position of window in physical pixels. This is either - /// the inner or outer position depending on the platform. - /// See [`winit::window::WindowBuilder::with_position`] for details. - position: Option, + /// Position of window content in physical pixels. + inner_position_pixels: Option, + + /// Position of window frame/titlebar in physical pixels. + outer_position_pixels: Option, fullscreen: bool, @@ -16,22 +18,20 @@ pub struct WindowSettings { impl WindowSettings { pub fn from_display(window: &winit::window::Window) -> Self { let inner_size_points = window.inner_size().to_logical::(window.scale_factor()); - let position = if cfg!(macos) { - // MacOS uses inner position when positioning windows. - window - .inner_position() - .ok() - .map(|p| egui::pos2(p.x as f32, p.y as f32)) - } else { - // Other platforms use the outer position. - window - .outer_position() - .ok() - .map(|p| egui::pos2(p.x as f32, p.y as f32)) - }; + + let inner_position_pixels = window + .inner_position() + .ok() + .map(|p| egui::pos2(p.x as f32, p.y as f32)); + + let outer_position_pixels = window + .outer_position() + .ok() + .map(|p| egui::pos2(p.x as f32, p.y as f32)); Self { - position, + inner_position_pixels, + outer_position_pixels, fullscreen: window.fullscreen().is_some(), @@ -46,19 +46,21 @@ impl WindowSettings { self.inner_size_points } - pub fn initialize_window( + pub fn initialize_window_builder( &self, mut window: winit::window::WindowBuilder, ) -> winit::window::WindowBuilder { - // If the app last ran on two monitors and only one is now connected, then - // the given position is invalid. - // If this happens on Mac, the window is clamped into valid area. - // If this happens on Windows, the clamping behavior is managed by the function - // clamp_window_to_sane_position. - if let Some(pos) = self.position { + // `WindowBuilder::with_position` expects inner position in Macos, and outer position elsewhere + // See [`winit::window::WindowBuilder::with_position`] for details. + let pos_px = if cfg!(target_os = "macos") { + self.inner_position_pixels + } else { + self.outer_position_pixels + }; + if let Some(pos_px) = pos_px { window = window.with_position(winit::dpi::PhysicalPosition { - x: pos.x as f64, - y: pos.y as f64, + x: pos_px.x as f64, + y: pos_px.y as f64, }); } @@ -77,68 +79,103 @@ impl WindowSettings { } } - pub fn clamp_to_sane_values(&mut self, max_size: egui::Vec2) { + pub fn initialize_window(&self, window: &winit::window::Window) { + if cfg!(target_os = "macos") { + // Mac sometimes has problems restoring the window to secondary monitors + // using only `WindowBuilder::with_position`, so we need this extra step: + if let Some(pos) = self.outer_position_pixels { + window.set_outer_position(winit::dpi::PhysicalPosition { x: pos.x, y: pos.y }); + } + } + } + + pub fn clamp_size_to_sane_values(&mut self, largest_monitor_size_points: egui::Vec2) { use egui::NumExt as _; if let Some(size) = &mut self.inner_size_points { - // Prevent ridiculously small windows + // Prevent ridiculously small windows: let min_size = egui::Vec2::splat(64.0); *size = size.at_least(min_size); - *size = size.at_most(max_size); + + // Make sure we don't try to create a window larger than the largest monitor + // because on Linux that can lead to a crash. + *size = size.at_most(largest_monitor_size_points); } } - pub fn clamp_window_to_sane_position( + pub fn clamp_position_to_monitors( &mut self, event_loop: &winit::event_loop::EventLoopWindowTarget, ) { - if let (Some(position), Some(inner_size_points)) = - (&mut self.position, &self.inner_size_points) - { - let monitors = event_loop.available_monitors(); - // default to primary monitor, in case the correct monitor was disconnected. - let mut active_monitor = if let Some(active_monitor) = event_loop - .primary_monitor() - .or_else(|| event_loop.available_monitors().next()) - { - active_monitor - } else { - return; // no monitors 🤷 - }; - for monitor in monitors { - let monitor_x_range = (monitor.position().x - inner_size_points.x as i32) - ..(monitor.position().x + monitor.size().width as i32); - let monitor_y_range = (monitor.position().y - inner_size_points.y as i32) - ..(monitor.position().y + monitor.size().height as i32); + // If the app last ran on two monitors and only one is now connected, then + // the given position is invalid. + // If this happens on Mac, the window is clamped into valid area. + // If this happens on Windows, the window becomes invisible to the user 🤦‍♂️ + // So on Windows we clamp the position to the monitor it is on. + if !cfg!(target_os = "windows") { + return; + } - if monitor_x_range.contains(&(position.x as i32)) - && monitor_y_range.contains(&(position.y as i32)) - { - active_monitor = monitor; - } - } + let Some(inner_size_points) = self.inner_size_points else { return; }; - let mut inner_size_pixels = *inner_size_points * (active_monitor.scale_factor() as f32); - // Add size of title bar. This is 32 px by default in Win 10/11. - if cfg!(target_os = "windows") { - inner_size_pixels += - egui::Vec2::new(0.0, 32.0 * active_monitor.scale_factor() as f32); - } - let monitor_position = egui::Pos2::new( - active_monitor.position().x as f32, - active_monitor.position().y as f32, - ); - let monitor_size = egui::Vec2::new( - active_monitor.size().width as f32, - active_monitor.size().height as f32, - ); - - // Window size cannot be negative or the subsequent `clamp` will panic. - let window_size = (monitor_size - inner_size_pixels).max(egui::Vec2::ZERO); - // To get the maximum position, we get the rightmost corner of the display, then - // subtract the size of the window to get the bottom right most value window.position - // can have. - *position = position.clamp(monitor_position, monitor_position + window_size); + if let Some(pos_px) = &mut self.inner_position_pixels { + clamp_pos_to_monitors(event_loop, inner_size_points, pos_px); + } + if let Some(pos_px) = &mut self.outer_position_pixels { + clamp_pos_to_monitors(event_loop, inner_size_points, pos_px); } } } + +fn clamp_pos_to_monitors( + event_loop: &winit::event_loop::EventLoopWindowTarget, + window_size_pts: egui::Vec2, + position_px: &mut egui::Pos2, +) { + let monitors = event_loop.available_monitors(); + + // default to primary monitor, in case the correct monitor was disconnected. + let mut active_monitor = if let Some(active_monitor) = event_loop + .primary_monitor() + .or_else(|| event_loop.available_monitors().next()) + { + active_monitor + } else { + return; // no monitors 🤷 + }; + + for monitor in monitors { + let window_size_px = window_size_pts * (monitor.scale_factor() as f32); + let monitor_x_range = (monitor.position().x - window_size_px.x as i32) + ..(monitor.position().x + monitor.size().width as i32); + let monitor_y_range = (monitor.position().y - window_size_px.y as i32) + ..(monitor.position().y + monitor.size().height as i32); + + if monitor_x_range.contains(&(position_px.x as i32)) + && monitor_y_range.contains(&(position_px.y as i32)) + { + active_monitor = monitor; + } + } + + let mut window_size_px = window_size_pts * (active_monitor.scale_factor() as f32); + // Add size of title bar. This is 32 px by default in Win 10/11. + if cfg!(target_os = "windows") { + window_size_px += egui::Vec2::new(0.0, 32.0 * active_monitor.scale_factor() as f32); + } + let monitor_position = egui::Pos2::new( + active_monitor.position().x as f32, + active_monitor.position().y as f32, + ); + let monitor_size_px = egui::Vec2::new( + active_monitor.size().width as f32, + active_monitor.size().height as f32, + ); + + // Window size cannot be negative or the subsequent `clamp` will panic. + let window_size = (monitor_size_px - window_size_px).max(egui::Vec2::ZERO); + // To get the maximum position, we get the rightmost corner of the display, then + // subtract the size of the window to get the bottom right most value window.position + // can have. + *position_px = position_px.clamp(monitor_position, monitor_position + window_size); +} From 98087029e020a1b2d78a4eb840d0a8505340ecad Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Fri, 11 Aug 2023 11:10:10 -0400 Subject: [PATCH 45/54] Update to `wgpu` 0.17.0 (#3170) * Bump `wgpu` to 0.17.0 This required bumping wasm-bindgen to 0.2.87 * cargo deny exception for `foreign-types` * sort deny.toml * Add fragile-send-sync-non-atomic-wasm feature to wgpu * cargo deny: ignore children of foreign-types --------- Co-authored-by: Andreas Reich Co-authored-by: Emil Ernerfeldt --- Cargo.lock | 105 ++++++++++++++++++++++-------------- Cargo.toml | 1 + crates/eframe/Cargo.toml | 4 +- crates/egui-wgpu/Cargo.toml | 2 +- deny.toml | 5 +- 5 files changed, 73 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff260433d..e5b016134 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,9 +215,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "ash" -version = "0.37.2+1.3.238" +version = "0.37.3+1.3.251" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28bf19c1f0a470be5fbf7522a308a05df06610252c5bcf5143e1b23f629a9a03" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" dependencies = [ "libloading 0.7.4", ] @@ -708,7 +708,7 @@ dependencies = [ "cocoa-foundation", "core-foundation", "core-graphics", - "foreign-types", + "foreign-types 0.3.2", "libc", "objc", ] @@ -723,7 +723,7 @@ dependencies = [ "block", "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", "libc", "objc", ] @@ -808,7 +808,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", "libc", ] @@ -820,7 +820,7 @@ checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" dependencies = [ "bitflags 1.3.2", "core-foundation", - "foreign-types", + "foreign-types 0.3.2", "libc", ] @@ -941,12 +941,12 @@ dependencies = [ [[package]] name = "d3d12" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" +checksum = "e16e44ab292b1dddfdaf7be62cfd8877df52f2f3fde5858d95bab606be259f20" dependencies = [ - "bitflags 1.3.2", - "libloading 0.7.4", + "bitflags 2.3.1", + "libloading 0.8.0", "winapi", ] @@ -1493,7 +1493,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", ] [[package]] @@ -1502,6 +1523,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -1675,9 +1702,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "glow" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e007a07a24de5ecae94160f141029e9a347282cfe25d1d58d85d845cf3130f1" +checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" dependencies = [ "js-sys", "slotmap", @@ -1762,21 +1789,21 @@ dependencies = [ [[package]] name = "gpu-alloc" -version = "0.5.4" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22beaafc29b38204457ea030f6fb7a84c9e4dd1b86e311ba0542533453d87f62" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.3.1", "gpu-alloc-types", ] [[package]] name = "gpu-alloc-types" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.3.1", ] [[package]] @@ -2091,9 +2118,9 @@ checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -2257,16 +2284,17 @@ dependencies = [ [[package]] name = "metal" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060" +checksum = "623b5e6cefd76e58f774bd3cc0c6f5c7615c58c03a97815245a25c3c9bdee318" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.3.1", "block", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "log", "objc", + "paste", ] [[package]] @@ -2304,12 +2332,12 @@ dependencies = [ [[package]] name = "naga" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d3edd593521f4a1dfd9b25193ed0224764572905f013d30ca5fbb85e010876" +checksum = "c1ceaaa4eedaece7e4ec08c55c640ba03dbb73fb812a6570a59bcf1930d0f70e" dependencies = [ "bit-set", - "bitflags 1.3.2", + "bitflags 2.3.1", "codespan-reporting", "hexf-parse", "indexmap", @@ -4005,9 +4033,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -4051,9 +4079,9 @@ dependencies = [ [[package]] name = "wgpu" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13edd72c7b08615b7179dd7e778ee3f0bdc870ef2de9019844ff2cceeee80b11" +checksum = "7472f3b69449a8ae073f6ec41d05b6f846902d92a6c45313c50cb25857b736ce" dependencies = [ "arrayvec", "cfg-if", @@ -4075,9 +4103,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625bea30a0ba50d88025f95c80211d1a85c86901423647fb74f397f614abbd9a" +checksum = "ecf7454d9386f602f7399225c92dd2fbdcde52c519bc8fb0bd6fbeb388075dc2" dependencies = [ "arrayvec", "bit-vec", @@ -4098,9 +4126,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41af2ea7d87bd41ad0a37146252d5f7c26490209f47f544b2ee3b3ff34c7732e" +checksum = "6654a13885a17f475e8324efb46dc6986d7aaaa98353330f8de2077b153d0101" dependencies = [ "android_system_properties", "arrayvec", @@ -4110,7 +4138,6 @@ dependencies = [ "block", "core-graphics-types", "d3d12", - "foreign-types", "glow", "gpu-alloc", "gpu-allocator", @@ -4140,9 +4167,9 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd33a976130f03dcdcd39b3810c0c3fc05daf86f0aaf867db14bfb7c4a9a32b" +checksum = "ee64d7398d0c2f9ca48922c902ef69c42d000c759f3db41e355f4a570b052b67" dependencies = [ "bitflags 2.3.1", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 62d3fc4f2..4cace8d49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,4 @@ opt-level = 2 [workspace.dependencies] thiserror = "1.0.37" +wgpu = { version = "0.17.0", features = ["fragile-send-sync-non-atomic-wasm"] } diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index d89f92d35..fd4076ed0 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -133,7 +133,7 @@ pollster = { version = "0.3", optional = true } # needed for wgpu glutin = { version = "0.30", optional = true } glutin-winit = { version = "0.3.0", optional = true } puffin = { version = "0.16", optional = true } -wgpu = { version = "0.16.0", optional = true } +wgpu = { workspace = true, optional = true } # mac: [target.'cfg(any(target_os = "macos"))'.dependencies] @@ -200,4 +200,4 @@ web-sys = { version = "0.3.58", features = [ egui-wgpu = { version = "0.22.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit raw-window-handle = { version = "0.5.2", optional = true } tts = { version = "0.25", optional = true, default-features = false } -wgpu = { version = "0.16.0", optional = true } +wgpu = { workspace = true, optional = true } diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 7df86b6aa..187b05613 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -44,7 +44,7 @@ bytemuck = "1.7" log = { version = "0.4", features = ["std"] } thiserror.workspace = true type-map = "0.5.0" -wgpu = "0.16.0" +wgpu.workspace = true #! ### Optional dependencies ## Enable this when generating docs. diff --git a/deny.toml b/deny.toml index 202c2fd06..3c1f21d8c 100644 --- a/deny.toml +++ b/deny.toml @@ -47,8 +47,9 @@ skip = [ { name = "windows" }, # old version via accesskit ] skip-tree = [ - { name = "criterion" }, # dev-dependency - { name = "rfd" }, # example dependency + { name = "criterion" }, # dev-dependency + { name = "foreign-types" }, # small crate. Old version via cocoa and core-graphics (winit). + { name = "rfd" }, # example dependency ] From 6633ecce64ed3183ee121a6285116fa227dddea2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 12 Aug 2023 13:50:31 +0200 Subject: [PATCH 46/54] Fix wrong detection of OS (#3238) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We had a bunch of `cfg!(windows)` and `cfg!(macos)` which should have been `cfg!(target_os = "windows")`. I wonder what the effects of this PR will be fore Windows 😬 --- crates/eframe/src/native/run.rs | 14 +++++++------- crates/egui_glium/examples/native_texture.rs | 4 ++-- crates/egui_glium/examples/pure_glium.rs | 4 ++-- crates/egui_glow/examples/pure_glow.rs | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 00bc23bac..bd084a501 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -145,12 +145,12 @@ fn run_and_return( // Platform-dependent event handlers to workaround a winit bug // See: https://github.com/rust-windowing/winit/issues/987 // See: https://github.com/rust-windowing/winit/issues/1619 - #[cfg(windows)] + #[cfg(target_os = "windows")] winit::event::Event::RedrawEventsCleared => { next_repaint_time = extremely_far_future(); winit_app.run_ui_and_paint() } - #[cfg(not(windows))] + #[cfg(not(target_os = "windows"))] winit::event::Event::RedrawRequested(_) => { next_repaint_time = extremely_far_future(); winit_app.run_ui_and_paint() @@ -196,7 +196,7 @@ fn run_and_return( EventResult::Wait => {} EventResult::RepaintNow => { log::trace!("Repaint caused by winit::Event: {:?}", event); - if cfg!(windows) { + if cfg!(target_os = "windows") { // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 next_repaint_time = extremely_far_future(); winit_app.run_ui_and_paint(); @@ -245,7 +245,7 @@ fn run_and_return( // // Note that this approach may cause issues on macOS (emilk/egui#2768); therefore, // we only apply this approach on Windows to minimize the affect. - #[cfg(windows)] + #[cfg(target_os = "windows")] { event_loop.run_return(|_, _, control_flow| { control_flow.set_exit(); @@ -270,11 +270,11 @@ fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + // Platform-dependent event handlers to workaround a winit bug // See: https://github.com/rust-windowing/winit/issues/987 // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => { + winit::event::Event::RedrawEventsCleared if cfg!(target_os = "windows") => { next_repaint_time = extremely_far_future(); winit_app.run_ui_and_paint() } - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => { + winit::event::Event::RedrawRequested(_) if !cfg!(target_os = "windows") => { next_repaint_time = extremely_far_future(); winit_app.run_ui_and_paint() } @@ -302,7 +302,7 @@ fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + match event_result { EventResult::Wait => {} EventResult::RepaintNow => { - if cfg!(windows) { + if cfg!(target_os = "windows") { // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 next_repaint_time = extremely_far_future(); winit_app.run_ui_and_paint(); diff --git a/crates/egui_glium/examples/native_texture.rs b/crates/egui_glium/examples/native_texture.rs index 19e4eaec6..b19b08f0c 100644 --- a/crates/egui_glium/examples/native_texture.rs +++ b/crates/egui_glium/examples/native_texture.rs @@ -78,8 +78,8 @@ fn main() { // Platform-dependent event handlers to workaround a winit bug // See: https://github.com/rust-windowing/winit/issues/987 // See: https://github.com/rust-windowing/winit/issues/1619 - glutin::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), - glutin::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), + glutin::event::Event::RedrawEventsCleared if cfg!(target_os = "windows") => redraw(), + glutin::event::Event::RedrawRequested(_) if !cfg!(target_os = "windows") => redraw(), glutin::event::Event::WindowEvent { event, .. } => { use glutin::event::WindowEvent; diff --git a/crates/egui_glium/examples/pure_glium.rs b/crates/egui_glium/examples/pure_glium.rs index dc7ad58f0..801f7b145 100644 --- a/crates/egui_glium/examples/pure_glium.rs +++ b/crates/egui_glium/examples/pure_glium.rs @@ -65,8 +65,8 @@ fn main() { // Platform-dependent event handlers to workaround a winit bug // See: https://github.com/rust-windowing/winit/issues/987 // See: https://github.com/rust-windowing/winit/issues/1619 - glutin::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), - glutin::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), + glutin::event::Event::RedrawEventsCleared if cfg!(target_os = "windows") => redraw(), + glutin::event::Event::RedrawRequested(_) if !cfg!(target_os = "windows") => redraw(), glutin::event::Event::WindowEvent { event, .. } => { use glutin::event::WindowEvent; diff --git a/crates/egui_glow/examples/pure_glow.rs b/crates/egui_glow/examples/pure_glow.rs index 104660488..008c3dad3 100644 --- a/crates/egui_glow/examples/pure_glow.rs +++ b/crates/egui_glow/examples/pure_glow.rs @@ -200,8 +200,8 @@ fn main() { // Platform-dependent event handlers to workaround a winit bug // See: https://github.com/rust-windowing/winit/issues/987 // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), - winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), + winit::event::Event::RedrawEventsCleared if cfg!(target_os = "windows") => redraw(), + winit::event::Event::RedrawRequested(_) if !cfg!(target_os = "windows") => redraw(), winit::event::Event::WindowEvent { event, .. } => { use winit::event::WindowEvent; From 1036cb1f7d7f2aa55369da5468dafcf972e2b006 Mon Sep 17 00:00:00 2001 From: lucasmerlin Date: Sat, 12 Aug 2023 13:50:40 +0200 Subject: [PATCH 47/54] Change force to be Option instead of f32 (#3240) --- crates/eframe/src/web/input.rs | 2 +- crates/egui-winit/src/lib.rs | 12 ++++++------ crates/egui/src/data/input.rs | 4 ++-- crates/egui/src/input_state/touch_state.rs | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/eframe/src/web/input.rs b/crates/eframe/src/web/input.rs index 443c26fe9..eacd1704f 100644 --- a/crates/eframe/src/web/input.rs +++ b/crates/eframe/src/web/input.rs @@ -68,7 +68,7 @@ pub fn push_touches(runner: &mut AppRunner, phase: egui::TouchPhase, event: &web id: egui::TouchId::from(touch.identifier()), phase, pos: pos_from_touch(canvas_origin, &touch), - force: touch.force(), + force: Some(touch.force()), }); } } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index ae82f6e4c..15e3bb1a7 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -441,7 +441,7 @@ impl State { id: egui::TouchId(0), phase: egui::TouchPhase::Start, pos, - force: 0.0, + force: None, }); } else { self.any_pointer_button_down = false; @@ -453,7 +453,7 @@ impl State { id: egui::TouchId(0), phase: egui::TouchPhase::End, pos, - force: 0.0, + force: None, }); }; } @@ -479,7 +479,7 @@ impl State { id: egui::TouchId(0), phase: egui::TouchPhase::Move, pos: pos_in_points, - force: 0.0, + force: None, }); } } else { @@ -505,13 +505,13 @@ impl State { touch.location.y as f32 / self.pixels_per_point(), ), force: match touch.force { - Some(winit::event::Force::Normalized(force)) => force as f32, + Some(winit::event::Force::Normalized(force)) => Some(force as f32), Some(winit::event::Force::Calibrated { force, max_possible_force, .. - }) => (force / max_possible_force) as f32, - None => 0_f32, + }) => Some((force / max_possible_force) as f32), + None => None, }, }); // If we're not yet translating a touch or we're translating this very diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index bb1b9f6a9..ed61aa216 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -274,10 +274,10 @@ pub enum Event { /// Position of the touch (or where the touch was last detected) pos: Pos2, - /// Describes how hard the touch device was pressed. May always be `0` if the platform does + /// Describes how hard the touch device was pressed. May always be `None` if the platform does /// not support pressure sensitivity. /// The value is in the range from 0.0 (no pressure) to 1.0 (maximum pressure). - force: f32, + force: Option, }, /// A raw mouse wheel event as sent by the backend (minus the z coordinate), diff --git a/crates/egui/src/input_state/touch_state.rs b/crates/egui/src/input_state/touch_state.rs index 37d418a53..a948ab16f 100644 --- a/crates/egui/src/input_state/touch_state.rs +++ b/crates/egui/src/input_state/touch_state.rs @@ -118,7 +118,7 @@ struct ActiveTouch { /// /// Note that a value of 0.0 either indicates a very light touch, or it means that the device /// is not capable of measuring the touch force. - force: f32, + force: Option, } impl TouchState { @@ -249,7 +249,7 @@ impl TouchState { // first pass: calculate force and center of touch positions: for touch in self.active_touches.values() { - state.avg_force += touch.force; + state.avg_force += touch.force.unwrap_or(0.0); state.avg_pos.x += touch.pos.x; state.avg_pos.y += touch.pos.y; } From 1023f937a67f3771e25c45cc49067acb71740e97 Mon Sep 17 00:00:00 2001 From: Frederic L <27208977+FreddyFunk@users.noreply.github.com> Date: Sat, 12 Aug 2023 14:05:49 +0200 Subject: [PATCH 48/54] Add option to always open hyperlink in a new browser tab (#3242) * add option to always open hyperlink in a new browser tab * Fix logic error --- crates/egui/src/widgets/hyperlink.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/widgets/hyperlink.rs b/crates/egui/src/widgets/hyperlink.rs index 361b21bb5..0ef0ee488 100644 --- a/crates/egui/src/widgets/hyperlink.rs +++ b/crates/egui/src/widgets/hyperlink.rs @@ -83,6 +83,7 @@ impl Widget for Link { pub struct Hyperlink { url: String, text: WidgetText, + new_tab: bool, } impl Hyperlink { @@ -92,6 +93,7 @@ impl Hyperlink { Self { url: url.clone(), text: url.into(), + new_tab: false, } } @@ -100,13 +102,20 @@ impl Hyperlink { Self { url: url.to_string(), text: text.into(), + new_tab: false, } } + + /// Always open this hyperlink in a new browser tab. + pub fn open_in_new_tab(mut self, new_tab: bool) -> Self { + self.new_tab = new_tab; + self + } } impl Widget for Hyperlink { fn ui(self, ui: &mut Ui) -> Response { - let Self { url, text } = self; + let Self { url, text, new_tab } = self; let response = ui.add(Link::new(text)); if response.clicked() { @@ -114,7 +123,7 @@ impl Widget for Hyperlink { ui.ctx().output_mut(|o| { o.open_url = Some(crate::output::OpenUrl { url: url.clone(), - new_tab: modifiers.any(), + new_tab: new_tab || modifiers.any(), }); }); } From a3ae81cadba35a115f6be03d69235e6fb9faeaca Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 14 Aug 2023 11:22:04 +0200 Subject: [PATCH 49/54] Add option to truncate text at wrap width (#3244) * Add option to clip text to wrap width * Spelling * Better naming, and report back wether the text was elided * Improve docstrings * Simplify * Fix max_rows with multiple paragraphs * Add note * Typos * fix doclink * Add `Label::elide` * Label: show full non-elided text on hover * Add demo of `Label::elide` * Call it `Label::truncate` * Clarify limitations of `break_anywhere` * Better docstrings --- crates/egui/src/widget_text.rs | 2 +- crates/egui/src/widgets/label.rs | 42 +++- .../src/demo/misc_demo_window.rs | 183 +++++++++++------- crates/epaint/src/text/text_layout.rs | 69 +++++-- crates/epaint/src/text/text_layout_types.rs | 86 ++++++-- 5 files changed, 280 insertions(+), 102 deletions(-) diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index bc71c2b6b..3d252cb21 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -676,7 +676,7 @@ impl WidgetTextGalley { self.galley.size() } - /// Size of the laid out text. + /// The full, non-elided text of the input job. #[inline] pub fn text(&self) -> &str { self.galley.text() diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index 3e387fca8..288fcb422 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -12,10 +12,14 @@ use crate::{widget_text::WidgetTextGalley, *}; /// ui.label(egui::RichText::new("With formatting").underline()); /// # }); /// ``` +/// +/// For full control of the text you can use [`crate::text::LayoutJob`] +/// as argument to [`Self::new`]. #[must_use = "You should put this widget in an ui with `ui.add(widget);`"] pub struct Label { text: WidgetText, wrap: Option, + truncate: bool, sense: Option, } @@ -24,6 +28,7 @@ impl Label { Self { text: text.into(), wrap: None, + truncate: false, sense: None, } } @@ -34,6 +39,8 @@ impl Label { /// If `true`, the text will wrap to stay within the max width of the [`Ui`]. /// + /// 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. @@ -44,6 +51,23 @@ impl Label { #[inline] pub fn wrap(mut self, wrap: bool) -> Self { self.wrap = Some(wrap); + self.truncate = false; + 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`]. + #[inline] + pub fn truncate(mut self, truncate: bool) -> Self { + self.wrap = None; + self.truncate = truncate; self } @@ -98,10 +122,11 @@ impl Label { .text .into_text_job(ui.style(), FontSelection::Default, valign); - let should_wrap = self.wrap.unwrap_or_else(|| ui.wrap_text()); + let truncate = self.truncate; + let wrap = !truncate && self.wrap.unwrap_or_else(|| ui.wrap_text()); let available_width = ui.available_width(); - if should_wrap + if wrap && ui.layout().main_dir() == Direction::LeftToRight && ui.layout().main_wrap() && available_width.is_finite() @@ -138,7 +163,11 @@ impl Label { } (pos, text_galley, response) } else { - if should_wrap { + if truncate { + text_job.job.wrap.max_width = available_width; + text_job.job.wrap.max_rows = 1; + text_job.job.wrap.break_anywhere = true; + } else if wrap { text_job.job.wrap.max_width = available_width; } else { text_job.job.wrap.max_width = f32::INFINITY; @@ -167,9 +196,14 @@ impl Label { impl Widget for Label { fn ui(self, ui: &mut Ui) -> Response { - let (pos, text_galley, response) = self.layout_in_ui(ui); + let (pos, text_galley, mut response) = self.layout_in_ui(ui); response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, text_galley.text())); + if text_galley.galley.elided { + // Show the full (non-elided) text on hover: + response = response.on_hover_text(text_galley.text()); + } + if ui.is_rect_visible(response.rect) { let response_color = ui.style().interact(&response).text_color(); 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 e4cf92644..b48b85c2a 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -1,5 +1,4 @@ use super::*; -use crate::LOREM_IPSUM; use egui::{epaint::text::TextWrapping, *}; /// Showcase some ui code @@ -8,9 +7,7 @@ use egui::{epaint::text::TextWrapping, *}; pub struct MiscDemoWindow { num_columns: usize, - break_anywhere: bool, - max_rows: usize, - overflow_character: Option, + text_break: TextBreakDemo, widgets: Widgets, colors: ColorWidgets, @@ -27,9 +24,7 @@ impl Default for MiscDemoWindow { MiscDemoWindow { num_columns: 2, - max_rows: 2, - break_anywhere: false, - overflow_character: Some('…'), + text_break: Default::default(), widgets: Default::default(), colors: Default::default(), @@ -61,8 +56,14 @@ impl View for MiscDemoWindow { fn ui(&mut self, ui: &mut Ui) { ui.set_min_width(250.0); - CollapsingHeader::new("Widgets") + CollapsingHeader::new("Label") .default_open(true) + .show(ui, |ui| { + label_ui(ui); + }); + + CollapsingHeader::new("Misc widgets") + .default_open(false) .show(ui, |ui| { self.widgets.ui(ui); }); @@ -70,12 +71,12 @@ impl View for MiscDemoWindow { CollapsingHeader::new("Text layout") .default_open(false) .show(ui, |ui| { - text_layout_ui( - ui, - &mut self.max_rows, - &mut self.break_anywhere, - &mut self.overflow_character, - ); + text_layout_demo(ui); + ui.separator(); + self.text_break.ui(ui); + ui.vertical_centered(|ui| { + ui.add(crate::egui_github_link_file_line!()); + }); }); CollapsingHeader::new("Colors") @@ -177,6 +178,43 @@ impl View for MiscDemoWindow { // ---------------------------------------------------------------------------- +fn label_ui(ui: &mut egui::Ui) { + ui.vertical_centered(|ui| { + ui.add(crate::egui_github_link_file_line!()); + }); + + ui.horizontal_wrapped(|ui| { + // Trick so we don't have to add spaces in the text below: + let width = ui.fonts(|f|f.glyph_width(&TextStyle::Body.resolve(ui.style()), ' ')); + ui.spacing_mut().item_spacing.x = width; + + ui.label(RichText::new("Text can have").color(Color32::from_rgb(110, 255, 110))); + ui.colored_label(Color32::from_rgb(128, 140, 255), "color"); // Shortcut version + ui.label("and tooltips.").on_hover_text( + "This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.", + ); + + ui.label("You can mix in other widgets into text, like"); + let _ = ui.small_button("this button"); + ui.label("."); + + ui.label("The default font supports all latin and cyrillic characters (ИÅđ…), common math symbols (∫√∞²⅓…), and many emojis (💓🌟🖩…).") + .on_hover_text("There is currently no support for right-to-left languages."); + ui.label("See the 🔤 Font Book for more!"); + + ui.monospace("There is also a monospace font."); + }); + + ui.add( + 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), + ); +} + +// ---------------------------------------------------------------------------- + #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct Widgets { @@ -200,28 +238,6 @@ impl Widgets { ui.add(crate::egui_github_link_file_line!()); }); - ui.horizontal_wrapped(|ui| { - // Trick so we don't have to add spaces in the text below: - let width = ui.fonts(|f|f.glyph_width(&TextStyle::Body.resolve(ui.style()), ' ')); - ui.spacing_mut().item_spacing.x = width; - - ui.label(RichText::new("Text can have").color(Color32::from_rgb(110, 255, 110))); - ui.colored_label(Color32::from_rgb(128, 140, 255), "color"); // Shortcut version - ui.label("and tooltips.").on_hover_text( - "This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.", - ); - - ui.label("You can mix in other widgets into text, like"); - let _ = ui.small_button("this button"); - ui.label("."); - - ui.label("The default font supports all latin and cyrillic characters (ИÅđ…), common math symbols (∫√∞²⅓…), and many emojis (💓🌟🖩…).") - .on_hover_text("There is currently no support for right-to-left languages."); - ui.label("See the 🔤 Font Book for more!"); - - ui.monospace("There is also a monospace font."); - }); - let tooltip_ui = |ui: &mut Ui| { ui.heading("The name of the tooltip"); ui.horizontal(|ui| { @@ -473,12 +489,7 @@ impl Tree { // ---------------------------------------------------------------------------- -fn text_layout_ui( - ui: &mut egui::Ui, - max_rows: &mut usize, - break_anywhere: &mut bool, - overflow_character: &mut Option, -) { +fn text_layout_demo(ui: &mut Ui) { use egui::text::LayoutJob; let mut job = LayoutJob::default(); @@ -632,32 +643,64 @@ fn text_layout_ui( ); ui.label(job); - - ui.separator(); - - ui.horizontal(|ui| { - ui.add(DragValue::new(max_rows)); - ui.label("Max rows"); - }); - ui.checkbox(break_anywhere, "Break anywhere"); - ui.horizontal(|ui| { - ui.selectable_value(overflow_character, None, "None"); - ui.selectable_value(overflow_character, Some('…'), "…"); - ui.selectable_value(overflow_character, Some('—'), "—"); - ui.selectable_value(overflow_character, Some('-'), " - "); - ui.label("Overflow character"); - }); - - let mut job = LayoutJob::single_section(LOREM_IPSUM.to_owned(), TextFormat::default()); - job.wrap = TextWrapping { - max_rows: *max_rows, - break_anywhere: *break_anywhere, - overflow_character: *overflow_character, - ..Default::default() - }; - ui.label(job); - - ui.vertical_centered(|ui| { - ui.add(crate::egui_github_link_file_line!()); - }); +} + +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +struct TextBreakDemo { + break_anywhere: bool, + max_rows: usize, + overflow_character: Option, +} + +impl Default for TextBreakDemo { + fn default() -> Self { + Self { + max_rows: 1, + break_anywhere: true, + overflow_character: Some('…'), + } + } +} + +impl TextBreakDemo { + pub fn ui(&mut self, ui: &mut Ui) { + let Self { + break_anywhere, + max_rows, + overflow_character, + } = self; + + use egui::text::LayoutJob; + + ui.horizontal(|ui| { + ui.add(DragValue::new(max_rows)); + ui.label("Max rows"); + }); + + ui.horizontal(|ui| { + ui.label("Line-break:"); + ui.radio_value(break_anywhere, false, "word boundaries"); + ui.radio_value(break_anywhere, true, "anywhere"); + }); + + ui.horizontal(|ui| { + ui.selectable_value(overflow_character, None, "None"); + ui.selectable_value(overflow_character, Some('…'), "…"); + ui.selectable_value(overflow_character, Some('—'), "—"); + ui.selectable_value(overflow_character, Some('-'), " - "); + ui.label("Overflow character"); + }); + + let mut job = + LayoutJob::single_section(crate::LOREM_IPSUM_LONG.to_owned(), TextFormat::default()); + job.wrap = TextWrapping { + max_rows: *max_rows, + break_anywhere: *break_anywhere, + overflow_character: *overflow_character, + ..Default::default() + }; + + ui.label(job); // `Label` overrides some of the wrapping settings, e.g. wrap width + } } diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index c493f8f94..5aab349e2 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -1,10 +1,12 @@ use std::ops::RangeInclusive; use std::sync::Arc; -use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals}; -use crate::{Color32, Mesh, Stroke, Vertex}; use emath::*; +use crate::{Color32, Mesh, Stroke, Vertex}; + +use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals}; + // ---------------------------------------------------------------------------- /// Represents GUI scale and convenience methods for rounding to pixels. @@ -54,6 +56,20 @@ struct Paragraph { /// In most cases you should use [`crate::Fonts::layout_job`] instead /// since that memoizes the input, making subsequent layouting of the same text much faster. pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { + if job.wrap.max_rows == 0 { + // Early-out: no text + return Galley { + job, + rows: Default::default(), + rect: Rect::from_min_max(Pos2::ZERO, Pos2::ZERO), + mesh_bounds: Rect::NOTHING, + num_vertices: 0, + num_indices: 0, + pixels_per_point: fonts.pixels_per_point(), + elided: true, + }; + } + let mut paragraphs = vec![Paragraph::default()]; for (section_index, section) in job.sections.iter().enumerate() { layout_section(fonts, &job, section_index as u32, section, &mut paragraphs); @@ -61,7 +77,8 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { let point_scale = PointScale::new(fonts.pixels_per_point()); - let mut rows = rows_from_paragraphs(fonts, paragraphs, &job); + let mut elided = false; + let mut rows = rows_from_paragraphs(fonts, paragraphs, &job, &mut elided); let justify = job.justify && job.wrap.max_width.is_finite(); @@ -80,7 +97,7 @@ pub fn layout(fonts: &mut FontsImpl, job: Arc) -> Galley { } } - galley_from_rows(point_scale, job, rows) + galley_from_rows(point_scale, job, rows, elided) } fn layout_section( @@ -145,12 +162,18 @@ fn rows_from_paragraphs( fonts: &mut FontsImpl, paragraphs: Vec, job: &LayoutJob, + elided: &mut bool, ) -> Vec { let num_paragraphs = paragraphs.len(); let mut rows = vec![]; for (i, paragraph) in paragraphs.into_iter().enumerate() { + if job.wrap.max_rows <= rows.len() { + *elided = true; + break; + } + let is_last_paragraph = (i + 1) == num_paragraphs; if paragraph.glyphs.is_empty() { @@ -166,7 +189,7 @@ fn rows_from_paragraphs( } else { let paragraph_max_x = paragraph.glyphs.last().unwrap().max_x(); if paragraph_max_x <= job.wrap.max_width { - // early-out optimization + // Early-out optimization: the whole paragraph fits on one row. let paragraph_min_x = paragraph.glyphs[0].pos.x; rows.push(Row { glyphs: paragraph.glyphs, @@ -175,7 +198,7 @@ fn rows_from_paragraphs( ends_with_newline: !is_last_paragraph, }); } else { - line_break(fonts, ¶graph, job, &mut rows); + line_break(fonts, ¶graph, job, &mut rows, elided); rows.last_mut().unwrap().ends_with_newline = !is_last_paragraph; } } @@ -189,6 +212,7 @@ fn line_break( paragraph: &Paragraph, job: &LayoutJob, out_rows: &mut Vec, + elided: &mut bool, ) { // Keeps track of good places to insert row break if we exceed `wrap_width`. let mut row_break_candidates = RowBreakCandidates::default(); @@ -196,16 +220,18 @@ fn line_break( let mut first_row_indentation = paragraph.glyphs[0].pos.x; let mut row_start_x = 0.0; let mut row_start_idx = 0; - let mut non_empty_rows = 0; for i in 0..paragraph.glyphs.len() { let potential_row_width = paragraph.glyphs[i].max_x() - row_start_x; - if job.wrap.max_rows > 0 && non_empty_rows >= job.wrap.max_rows { + if job.wrap.max_rows <= out_rows.len() { + *elided = true; break; } - if potential_row_width > job.wrap.max_width { + if job.wrap.max_width < potential_row_width { + // Row break: + if first_row_indentation > 0.0 && !row_break_candidates.has_good_candidate(job.wrap.break_anywhere) { @@ -240,10 +266,10 @@ fn line_break( ends_with_newline: false, }); + // 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(); - non_empty_rows += 1; } else { // Found no place to break, so we have to overrun wrap_width. } @@ -253,9 +279,12 @@ fn line_break( } if row_start_idx < paragraph.glyphs.len() { - if job.wrap.max_rows > 0 && non_empty_rows == job.wrap.max_rows { + // Final row of text: + + if job.wrap.max_rows <= out_rows.len() { if let Some(last_row) = out_rows.last_mut() { replace_last_glyph_with_overflow_character(fonts, job, last_row); + *elided = true; } } else { let glyphs: Vec = paragraph.glyphs[row_start_idx..] @@ -280,6 +309,7 @@ fn line_break( } } +/// Trims the last glyphs in the row and replaces it with an overflow character (e.g. `…`). fn replace_last_glyph_with_overflow_character( fonts: &mut FontsImpl, job: &LayoutJob, @@ -318,6 +348,7 @@ fn replace_last_glyph_with_overflow_character( let (font_impl, glyph_info) = font.glyph_info_and_font_impl(last_glyph.chr); last_glyph.size = vec2(glyph_info.advance_width, font_height); last_glyph.uv_rect = glyph_info.uv_rect; + last_glyph.ascent = glyph_info.ascent; // reapply kerning last_glyph.pos.x += font_impl @@ -325,16 +356,20 @@ fn replace_last_glyph_with_overflow_character( .map(|(font_impl, prev_glyph_id)| font_impl.pair_kerning(prev_glyph_id, glyph_info.id)) .unwrap_or_default(); - // check if we're still within width budget + row.rect.max.x = last_glyph.max_x(); + + // check if we're within width budget let row_end_x = last_glyph.max_x(); let row_start_x = row.glyphs.first().unwrap().pos.x; // if `last_mut()` returned `Some`, then so will `first()` let row_width = row_end_x - row_start_x; if row_width <= job.wrap.max_width { - break; + return; // we are done } row.glyphs.pop(); } + + // We failed to insert `overflow_character` without exceeding `wrap_width`. } fn halign_and_justify_row( @@ -428,7 +463,12 @@ fn halign_and_justify_row( } /// Calculate the Y positions and tessellate the text. -fn galley_from_rows(point_scale: PointScale, job: Arc, mut rows: Vec) -> Galley { +fn galley_from_rows( + point_scale: PointScale, + job: Arc, + mut rows: Vec, + elided: bool, +) -> Galley { let mut first_row_min_height = job.first_row_min_height; let mut cursor_y = 0.0; let mut min_x: f32 = 0.0; @@ -489,6 +529,7 @@ fn galley_from_rows(point_scale: PointScale, job: Arc, mut rows: Vec< Galley { job, rows, + elided, rect, mesh_bounds, num_vertices, diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 8ec829b8a..2a73bd60f 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -49,6 +49,7 @@ pub struct LayoutJob { /// The different section, which can have different fonts, colors, etc. pub sections: Vec, + /// Controls the text wrapping and elision. pub wrap: TextWrapping, /// The first row must be at least this high. @@ -58,15 +59,19 @@ pub struct LayoutJob { /// In other cases, set this to `0.0`. pub first_row_min_height: f32, - /// If `false`, all newlines characters will be ignored + /// If `true`, all `\n` characters will result in a new _paragraph_, + /// starting on a new row. + /// + /// If `false`, all `\n` characters will be ignored /// and show up as the replacement character. + /// /// Default: `true`. pub break_on_newline: bool, /// How to horizontally align the text (`Align::LEFT`, `Align::Center`, `Align::RIGHT`). pub halign: Align, - /// Justify text so that word-wrapped rows fill the whole [`TextWrapping::max_width`] + /// Justify text so that word-wrapped rows fill the whole [`TextWrapping::max_width`]. pub justify: bool, } @@ -153,7 +158,7 @@ impl LayoutJob { }); } - /// The height of the tallest used font in the job. + /// The height of the tallest font used in the job. pub fn font_height(&self, fonts: &crate::Fonts) -> f32 { let mut max_height = 0.0_f32; for section in &self.sections { @@ -266,22 +271,50 @@ impl TextFormat { // ---------------------------------------------------------------------------- +/// Controls the text wrapping and elision of a [`LayoutJob`]. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct TextWrapping { - /// Try to break text so that no row is wider than this. - /// Set to [`f32::INFINITY`] to turn off wrapping. - /// Note that `\n` always produces a new line. + /// Wrap text so that no row is wider than this. + /// + /// If you would rather truncate text that doesn't fit, set [`Self::max_rows`] to `1`. + /// + /// Set `max_width` to [`f32::INFINITY`] to turn off wrapping and elision. + /// + /// Note that `\n` always produces a new row + /// if [`LayoutJob::break_on_newline`] is `true`. pub max_width: f32, - /// Maximum amount of rows the text should have. - /// Set to `0` to disable this. + /// Maximum amount of rows the text galley should have. + /// + /// If this limit is reached, text will be truncated and + /// and [`Self::overflow_character`] appended to the final row. + /// You can detect this by checking [`Galley::elided`]. + /// + /// If set to `0`, no text will be outputted. + /// + /// If set to `1`, a single row will be outputted, + /// eliding the text after [`Self::max_width`] is reached. + /// When you set `max_rows = 1`, it is recommended you also set [`Self::break_anywhere`] to `true`. + /// + /// Default value: `usize::MAX`. pub max_rows: usize, - /// Don't try to break text at an appropriate place. + /// If `true`: Allow breaking between any characters. + /// If `false` (default): prefer breaking between words, etc. + /// + /// NOTE: Due to limitations in the current implementation, + /// when truncating text using [`Self::max_rows`] the text may be truncated + /// in the middle of a word even if [`Self::break_anywhere`] is `false`. + /// Therefore it is recommended to set [`Self::break_anywhere`] to `true` + /// whenever [`Self::max_rows`] is set to `1`. pub break_anywhere: bool, - /// Character to use to represent clipped text, `…` for example, which is the default. + /// Character to use to represent elided text. + /// + /// The default is `…`. + /// + /// If not set, no character will be used (but the text will still be elided). pub overflow_character: Option, } @@ -305,13 +338,33 @@ impl Default for TextWrapping { fn default() -> Self { Self { max_width: f32::INFINITY, - max_rows: 0, + max_rows: usize::MAX, break_anywhere: false, overflow_character: Some('…'), } } } +impl TextWrapping { + /// A row can be as long as it need to be + pub fn no_max_width() -> Self { + Self { + max_width: f32::INFINITY, + ..Default::default() + } + } + + /// Elide text that doesn't fit within the given width. + pub fn elide_at_width(max_width: f32) -> Self { + Self { + max_width, + max_rows: 1, + break_anywhere: true, + ..Default::default() + } + } +} + // ---------------------------------------------------------------------------- /// Text that has been laid out, ready for painting. @@ -333,11 +386,17 @@ pub struct Galley { pub job: Arc, /// Rows of text, from top to bottom. - /// The number of characters in all rows sum up to `job.text.chars().count()`. - /// Note that each paragraph (pieces of text separated with `\n`) + /// + /// The number of characters in all rows sum up to `job.text.chars().count()` + /// unless [`Self::elided`] is `true`. + /// + /// Note that a paragraph (a piece of text separated with `\n`) /// can be split up into multiple rows. pub rows: Vec, + /// Set to true the text was truncated due to [`TextWrapping::max_rows`]. + pub elided: bool, + /// Bounding rect. /// /// `rect.top()` is always 0.0. @@ -505,6 +564,7 @@ impl Galley { self.job.is_empty() } + /// The full, non-elided text of the input job. #[inline(always)] pub fn text(&self) -> &str { &self.job.text From dbe55ba46a7987bb4caa94856112ed48d1f9407b Mon Sep 17 00:00:00 2001 From: JohannesProgrammiert <80203331+JohannesProgrammiert@users.noreply.github.com> Date: Mon, 14 Aug 2023 17:51:17 +0200 Subject: [PATCH 50/54] Draw axis labels and ticks outside of plotting window (#2284) * Always draw axis labels at plot borders * Revert "Always draw axis labels at plot borders" This reverts commit 9235e6603366d3b8a8189e2a5fc28c9780b7f54f. * Add axis labels for plots * First Draft of axis labels outside of plotting window * plot: Tick placement of opposite axes and digit constraints * plot: Axis label API * plot: Update demo lib * plot: resolve clippy warning * Update changelog * Remove default axis * Fix clippy * plot: Remove unused comments * plot-axis: Rebase label opacity calculation on master * plot: Resolve check.sh warnings * plot-axis: Use 'into impl' as axis label formatter * plot-axis: Expose more conveniece functions to public API. Add axis labels to demo app * plot-axes: Resolve ./scripts/check.sh warnings * typo in comment * Use `TAU` instead of the legacy `PI` * Simpler generic syntax * Use `Arc` to avoid some expensive clones * Use `Margin` instead of a,b,c,d * Add some vertical spacing * De-duplicate color_from_contrast * better naming * Fix typos * cnt -> num * Axis are present by default, with empty names * Add HPlacement and VPlacement * Don't catch clicks and drags on axes * Remove generics to minimize monomorphization code bloat * Create helper function * Remove changelog entry * Simplify more --------- Co-authored-by: Emil Ernerfeldt --- crates/egui/src/style.rs | 7 + crates/egui/src/widgets/mod.rs | 2 + crates/egui/src/widgets/plot/axis.rs | 318 ++++++++++ crates/egui/src/widgets/plot/memory.rs | 33 ++ crates/egui/src/widgets/plot/mod.rs | 549 ++++++++++++------ crates/egui_demo_lib/src/demo/plot_demo.rs | 99 ++-- .../egui_demo_lib/src/demo/widget_gallery.rs | 1 + 7 files changed, 807 insertions(+), 202 deletions(-) create mode 100644 crates/egui/src/widgets/plot/axis.rs create mode 100644 crates/egui/src/widgets/plot/memory.rs diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 4a8dba7c0..d8517fc91 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -338,6 +338,13 @@ pub struct Margin { } impl Margin { + pub const ZERO: Self = Self { + left: 0.0, + right: 0.0, + top: 0.0, + bottom: 0.0, + }; + #[inline] pub fn same(margin: f32) -> Self { Self { diff --git a/crates/egui/src/widgets/mod.rs b/crates/egui/src/widgets/mod.rs index f782056ba..5cfe1816f 100644 --- a/crates/egui/src/widgets/mod.rs +++ b/crates/egui/src/widgets/mod.rs @@ -38,6 +38,8 @@ pub use text_edit::{TextBuffer, TextEdit}; /// /// [`Button`], [`Label`], [`Slider`], etc all implement the [`Widget`] trait. /// +/// You only need to implement `Widget` if you care about being able to do `ui.add(your_widget);`. +/// /// Note that the widgets ([`Button`], [`TextEdit`] etc) are /// [builders](https://doc.rust-lang.org/1.0.0/style/ownership/builders.html), /// and not objects that hold state. diff --git a/crates/egui/src/widgets/plot/axis.rs b/crates/egui/src/widgets/plot/axis.rs new file mode 100644 index 000000000..6430378bd --- /dev/null +++ b/crates/egui/src/widgets/plot/axis.rs @@ -0,0 +1,318 @@ +use std::{fmt::Debug, ops::RangeInclusive, sync::Arc}; + +use epaint::{ + emath::{remap_clamp, round_to_decimals}, + Pos2, Rect, Shape, Stroke, TextShape, +}; + +use crate::{Response, Sense, TextStyle, Ui, WidgetText}; + +use super::{transform::PlotTransform, GridMark}; + +pub(super) type AxisFormatterFn = fn(f64, usize, &RangeInclusive) -> String; + +/// X or Y axis. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Axis { + /// Horizontal X-Axis + X, + + /// Vertical Y-axis + Y, +} + +impl From for usize { + #[inline] + fn from(value: Axis) -> Self { + match value { + Axis::X => 0, + Axis::Y => 1, + } + } +} + +/// Placement of the horizontal X-Axis. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VPlacement { + Top, + Bottom, +} + +/// Placement of the vertical Y-Axis. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HPlacement { + Left, + Right, +} + +/// Placement of an axis. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Placement { + /// Bottom for X-axis, or left for Y-axis. + LeftBottom, + + /// Top for x-axis and right for y-axis. + RightTop, +} + +impl From for Placement { + #[inline] + fn from(placement: HPlacement) -> Self { + match placement { + HPlacement::Left => Placement::LeftBottom, + HPlacement::Right => Placement::RightTop, + } + } +} + +impl From for Placement { + #[inline] + fn from(placement: VPlacement) -> Self { + match placement { + VPlacement::Top => Placement::RightTop, + VPlacement::Bottom => Placement::LeftBottom, + } + } +} + +/// Axis configuration. +/// +/// Used to configure axis label and ticks. +#[derive(Clone)] +pub struct AxisHints { + pub(super) label: WidgetText, + pub(super) formatter: AxisFormatterFn, + pub(super) digits: usize, + pub(super) placement: Placement, +} + +// TODO: this just a guess. It might cease to work if a user changes font size. +const LINE_HEIGHT: f32 = 12.0; + +impl Default for AxisHints { + /// Initializes a default axis configuration for the specified axis. + /// + /// `label` is empty. + /// `formatter` is default float to string formatter. + /// maximum `digits` on tick label is 5. + fn default() -> Self { + Self { + label: Default::default(), + formatter: Self::default_formatter, + digits: 5, + placement: Placement::LeftBottom, + } + } +} + +impl AxisHints { + /// Specify custom formatter for ticks. + /// + /// The first parameter of `formatter` is the raw tick value as `f64`. + /// The second parameter is the maximum number of characters that fit into y-labels. + /// The second parameter of `formatter` is the currently shown range on this axis. + pub fn formatter(mut self, fmt: fn(f64, usize, &RangeInclusive) -> String) -> Self { + self.formatter = fmt; + self + } + + fn default_formatter(tick: f64, max_digits: usize, _range: &RangeInclusive) -> String { + if tick.abs() > 10.0_f64.powf(max_digits as f64) { + let tick_rounded = tick as isize; + return format!("{tick_rounded:+e}"); + } + let tick_rounded = round_to_decimals(tick, max_digits); + if tick.abs() < 10.0_f64.powf(-(max_digits as f64)) && tick != 0.0 { + return format!("{tick_rounded:+e}"); + } + tick_rounded.to_string() + } + + /// Specify axis label. + /// + /// The default is 'x' for x-axes and 'y' for y-axes. + pub fn label(mut self, label: impl Into) -> Self { + self.label = label.into(); + self + } + + /// Specify maximum number of digits for ticks. + /// + /// This is considered by the default tick formatter and affects the width of the y-axis + pub fn max_digits(mut self, digits: usize) -> Self { + self.digits = digits; + self + } + + /// Specify the placement of the axis. + /// + /// For X-axis, use [`VPlacement`]. + /// For Y-axis, use [`HPlacement`]. + pub fn placement(mut self, placement: impl Into) -> Self { + self.placement = placement.into(); + self + } + + pub(super) fn thickness(&self, axis: Axis) -> f32 { + match axis { + Axis::X => { + if self.label.is_empty() { + 1.0 * LINE_HEIGHT + } else { + 3.0 * LINE_HEIGHT + } + } + Axis::Y => { + if self.label.is_empty() { + (self.digits as f32) * LINE_HEIGHT + } else { + (self.digits as f32 + 1.0) * LINE_HEIGHT + } + } + } + } +} + +#[derive(Clone)] +pub(super) struct AxisWidget { + pub(super) range: RangeInclusive, + pub(super) hints: AxisHints, + pub(super) rect: Rect, + pub(super) transform: Option, + pub(super) steps: Arc>, +} + +impl AxisWidget { + /// if `rect` as width or height == 0, is will be automatically calculated from ticks and text. + pub(super) fn new(hints: AxisHints, rect: Rect) -> Self { + Self { + range: (0.0..=0.0), + hints, + rect, + transform: None, + steps: Default::default(), + } + } + + pub fn ui(self, ui: &mut Ui, axis: Axis) -> Response { + let response = ui.allocate_rect(self.rect, Sense::hover()); + + if ui.is_rect_visible(response.rect) { + let visuals = ui.style().visuals.clone(); + let text = self.hints.label; + let galley = text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Body); + let text_color = visuals + .override_text_color + .unwrap_or_else(|| ui.visuals().text_color()); + let angle: f32 = match axis { + Axis::X => 0.0, + Axis::Y => -std::f32::consts::TAU * 0.25, + }; + // select text_pos and angle depending on placement and orientation of widget + let text_pos = match self.hints.placement { + Placement::LeftBottom => match axis { + Axis::X => { + let pos = response.rect.center_bottom(); + Pos2 { + x: pos.x - galley.size().x / 2.0, + y: pos.y - galley.size().y * 1.25, + } + } + Axis::Y => { + let pos = response.rect.left_center(); + Pos2 { + x: pos.x, + y: pos.y + galley.size().x / 2.0, + } + } + }, + Placement::RightTop => match axis { + Axis::X => { + let pos = response.rect.center_top(); + Pos2 { + x: pos.x - galley.size().x / 2.0, + y: pos.y + galley.size().y * 0.25, + } + } + Axis::Y => { + let pos = response.rect.right_center(); + Pos2 { + x: pos.x - galley.size().y * 1.5, + y: pos.y + galley.size().x / 2.0, + } + } + }, + }; + let shape = TextShape { + pos: text_pos, + galley: galley.galley, + underline: Stroke::NONE, + override_text_color: Some(text_color), + angle, + }; + ui.painter().add(shape); + + // --- add ticks --- + let font_id = TextStyle::Body.resolve(ui.style()); + let transform = match self.transform { + Some(t) => t, + None => return response, + }; + + for step in self.steps.iter() { + let text = (self.hints.formatter)(step.value, self.hints.digits, &self.range); + if !text.is_empty() { + const MIN_TEXT_SPACING: f32 = 20.0; + const FULL_CONTRAST_SPACING: f32 = 40.0; + let spacing_in_points = + (transform.dpos_dvalue()[usize::from(axis)] * step.step_size).abs() as f32; + + if spacing_in_points <= MIN_TEXT_SPACING { + continue; + } + let line_strength = remap_clamp( + spacing_in_points, + MIN_TEXT_SPACING..=FULL_CONTRAST_SPACING, + 0.0..=1.0, + ); + + let line_color = super::color_from_strength(ui, line_strength); + let galley = ui + .painter() + .layout_no_wrap(text, font_id.clone(), line_color); + + let text_pos = match axis { + Axis::X => { + let y = match self.hints.placement { + Placement::LeftBottom => self.rect.min.y, + Placement::RightTop => self.rect.max.y - galley.size().y, + }; + let projected_point = super::PlotPoint::new(step.value, 0.0); + Pos2 { + x: transform.position_from_point(&projected_point).x + - galley.size().x / 2.0, + y, + } + } + Axis::Y => { + let x = match self.hints.placement { + Placement::LeftBottom => self.rect.max.x - galley.size().x, + Placement::RightTop => self.rect.min.x, + }; + let projected_point = super::PlotPoint::new(0.0, step.value); + Pos2 { + x, + y: transform.position_from_point(&projected_point).y + - galley.size().y / 2.0, + } + } + }; + + ui.painter().add(Shape::galley(text_pos, galley)); + } + } + } + + response + } +} diff --git a/crates/egui/src/widgets/plot/memory.rs b/crates/egui/src/widgets/plot/memory.rs new file mode 100644 index 000000000..3e47a5082 --- /dev/null +++ b/crates/egui/src/widgets/plot/memory.rs @@ -0,0 +1,33 @@ +use epaint::Pos2; + +use crate::{Context, Id}; + +use super::{transform::ScreenTransform, AxisBools}; + +/// Information about the plot that has to persist between frames. +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[derive(Clone)] +pub(super) struct PlotMemory { + /// Indicates if the user has modified the bounds, for example by moving or zooming, + /// or if the bounds should be calculated based by included point or auto bounds. + pub(super) bounds_modified: AxisBools, + + pub(super) hovered_entry: Option, + + pub(super) hidden_items: ahash::HashSet, + + pub(super) last_screen_transform: ScreenTransform, + + /// Allows to remember the first click position when performing a boxed zoom + pub(super) last_click_pos_for_zoom: Option, +} + +impl PlotMemory { + pub fn load(ctx: &Context, id: Id) -> Option { + ctx.data().get_persisted(id) + } + + pub fn store(self, ctx: &Context, id: Id) { + ctx.data().insert_persisted(id, self); + } +} diff --git a/crates/egui/src/widgets/plot/mod.rs b/crates/egui/src/widgets/plot/mod.rs index ebc73d728..456f3dc16 100644 --- a/crates/egui/src/widgets/plot/mod.rs +++ b/crates/egui/src/widgets/plot/mod.rs @@ -1,15 +1,17 @@ //! Simple plotting library. -use ahash::HashMap; -use std::ops::RangeInclusive; +use std::{ops::RangeInclusive, sync::Arc}; -use crate::*; +use ahash::HashMap; use epaint::util::FloatOrd; use epaint::Hsva; +use axis::AxisWidget; use items::PlotItem; use legend::LegendWidget; +use crate::*; + pub use items::{ Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape, Orientation, PlotImage, PlotPoint, PlotPoints, Points, Polygon, Text, VLine, @@ -17,16 +19,17 @@ pub use items::{ pub use legend::{Corner, Legend}; pub use transform::{PlotBounds, PlotTransform}; -use self::items::{horizontal_line, rulers_color, vertical_line}; +use items::{horizontal_line, rulers_color, vertical_line}; +pub use axis::{Axis, AxisHints, HPlacement, Placement, VPlacement}; + +mod axis; mod items; mod legend; mod transform; type LabelFormatterFn = dyn Fn(&str, &PlotPoint) -> String; type LabelFormatter = Option>; -type AxisFormatterFn = dyn Fn(f64, &RangeInclusive) -> String; -type AxisFormatter = Option>; type GridSpacerFn = dyn Fn(GridInput) -> Vec; type GridSpacer = Box; @@ -78,6 +81,7 @@ pub struct AxisBools { } impl AxisBools { + #[inline] pub fn new(x: bool, y: bool) -> Self { Self { x, y } } @@ -89,11 +93,19 @@ impl AxisBools { } impl From for AxisBools { + #[inline] fn from(val: bool) -> Self { AxisBools { x: val, y: val } } } +impl From<[bool; 2]> for AxisBools { + #[inline] + fn from([x, y]: [bool; 2]) -> Self { + AxisBools { x, y } + } +} + /// Information about the plot that has to persist between frames. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone)] @@ -182,8 +194,7 @@ pub struct PlotResponse { pub struct Plot { id_source: Id, - center_x_axis: bool, - center_y_axis: bool, + center_axis: AxisBools, allow_zoom: AxisBools, allow_drag: AxisBools, allow_scroll: bool, @@ -208,11 +219,12 @@ pub struct Plot { show_y: bool, label_formatter: LabelFormatter, coordinates_formatter: Option<(Corner, CoordinatesFormatter)>, - axis_formatters: [AxisFormatter; 2], + x_axes: Vec, // default x axes + y_axes: Vec, // default y axes legend_config: Option, show_background: bool, - show_axes: [bool; 2], - + show_axes: AxisBools, + show_grid: AxisBools, grid_spacers: [GridSpacer; 2], sharp_grid_lines: bool, clamp_grid: bool, @@ -224,8 +236,7 @@ impl Plot { Self { id_source: Id::new(id_source), - center_x_axis: false, - center_y_axis: false, + center_axis: false.into(), allow_zoom: true.into(), allow_drag: true.into(), allow_scroll: true, @@ -250,11 +261,12 @@ impl Plot { show_y: true, label_formatter: None, coordinates_formatter: None, - axis_formatters: [None, None], // [None; 2] requires Copy + x_axes: vec![Default::default()], + y_axes: vec![Default::default()], legend_config: None, show_background: true, - show_axes: [true; 2], - + show_axes: true.into(), + show_grid: true.into(), grid_spacers: [log_grid_spacer(10), log_grid_spacer(10)], sharp_grid_lines: true, clamp_grid: false, @@ -311,15 +323,15 @@ impl Plot { self } - /// Always keep the x-axis centered. Default: `false`. + /// Always keep the X-axis centered. Default: `false`. pub fn center_x_axis(mut self, on: bool) -> Self { - self.center_x_axis = on; + self.center_axis.x = on; self } - /// Always keep the y-axis centered. Default: `false`. + /// Always keep the Y-axis centered. Default: `false`. pub fn center_y_axis(mut self, on: bool) -> Self { - self.center_y_axis = on; + self.center_axis.y = on; self } @@ -417,36 +429,6 @@ impl Plot { self } - /// Provide a function to customize the labels for the X axis based on the current visible value range. - /// - /// This is useful for custom input domains, e.g. date/time. - /// - /// If axis labels should not appear for certain values or beyond a certain zoom/resolution, - /// the formatter function can return empty strings. This is also useful if your domain is - /// discrete (e.g. only full days in a calendar). - pub fn x_axis_formatter( - mut self, - func: impl Fn(f64, &RangeInclusive) -> String + 'static, - ) -> Self { - self.axis_formatters[0] = Some(Box::new(func)); - self - } - - /// Provide a function to customize the labels for the Y axis based on the current value range. - /// - /// This is useful for custom value representation, e.g. percentage or units. - /// - /// If axis labels should not appear for certain values or beyond a certain zoom/resolution, - /// the formatter function can return empty strings. This is also useful if your Y values are - /// discrete (e.g. only integers). - pub fn y_axis_formatter( - mut self, - func: impl Fn(f64, &RangeInclusive) -> String + 'static, - ) -> Self { - self.axis_formatters[1] = Some(Box::new(func)); - self - } - /// Configure how the grid in the background is spaced apart along the X axis. /// /// Default is a log-10 grid, i.e. every plot unit is divided into 10 other units. @@ -538,11 +520,19 @@ impl Plot { self } - /// Show the axes. - /// Can be useful to disable if the plot is overlaid over an existing grid or content. + /// Show axis labels and grid tick values on the side of the plot. + /// /// Default: `[true; 2]`. - pub fn show_axes(mut self, show: [bool; 2]) -> Self { - self.show_axes = show; + pub fn show_axes(mut self, show: impl Into) -> Self { + self.show_axes = show.into(); + self + } + + /// Show a grid overlay on the plot. + /// + /// Default: `[true; 2]`. + pub fn show_grid(mut self, show: impl Into) -> Self { + self.show_grid = show.into(); self } @@ -585,6 +575,94 @@ impl Plot { self } + /// Set the x axis label of the main X-axis. + /// + /// Default: no label. + pub fn x_axis_label(mut self, label: impl Into) -> Self { + if let Some(main) = self.x_axes.first_mut() { + main.label = label.into(); + } + self + } + + /// Set the y axis label of the main Y-axis. + /// + /// Default: no label. + pub fn y_axis_label(mut self, label: impl Into) -> Self { + if let Some(main) = self.y_axes.first_mut() { + main.label = label.into(); + } + self + } + + /// Set the position of the main X-axis. + pub fn x_axis_position(mut self, placement: axis::VPlacement) -> Self { + if let Some(main) = self.x_axes.first_mut() { + main.placement = placement.into(); + } + self + } + + /// Set the position of the main Y-axis. + pub fn y_axis_position(mut self, placement: axis::HPlacement) -> Self { + if let Some(main) = self.y_axes.first_mut() { + main.placement = placement.into(); + } + self + } + + /// Specify custom formatter for ticks on the main X-axis. + /// + /// The first parameter of `fmt` is the raw tick value as `f64`. + /// The second parameter is the maximum requested number of characters per tick label. + /// The second parameter of `fmt` is the currently shown range on this axis. + pub fn x_axis_formatter(mut self, fmt: fn(f64, usize, &RangeInclusive) -> String) -> Self { + if let Some(main) = self.x_axes.first_mut() { + main.formatter = fmt; + } + self + } + + /// Specify custom formatter for ticks on the main Y-axis. + /// + /// The first parameter of `formatter` is the raw tick value as `f64`. + /// The second parameter is the maximum requested number of characters per tick label. + /// The second parameter of `formatter` is the currently shown range on this axis. + pub fn y_axis_formatter(mut self, fmt: fn(f64, usize, &RangeInclusive) -> String) -> Self { + if let Some(main) = self.y_axes.first_mut() { + main.formatter = fmt; + } + self + } + + /// Set the main Y-axis-width by number of digits + /// + /// The default is 5 digits. + /// + /// > Todo: This is experimental. Changing the font size might break this. + pub fn y_axis_width(mut self, digits: usize) -> Self { + if let Some(main) = self.y_axes.first_mut() { + main.digits = digits; + } + self + } + + /// Set custom configuration for X-axis + /// + /// More than one axis may be specified. The first specified axis is considered the main axis. + pub fn custom_x_axes(mut self, hints: Vec) -> Self { + self.x_axes = hints; + self + } + + /// Set custom configuration for left Y-axis + /// + /// More than one axis may be specified. The first specified axis is considered the main axis. + 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 { self.show_dyn(ui, Box::new(build_fn)) @@ -597,8 +675,7 @@ impl Plot { ) -> PlotResponse { let Self { id_source, - center_x_axis, - center_y_axis, + center_axis, allow_zoom, allow_drag, allow_scroll, @@ -617,11 +694,13 @@ impl Plot { mut show_y, label_formatter, coordinates_formatter, - axis_formatters, + x_axes, + y_axes, legend_config, reset, show_background, show_axes, + show_grid, linked_axes, linked_cursors, @@ -630,7 +709,9 @@ impl Plot { sharp_grid_lines, } = self; - // Determine the size of the plot in the UI + // Determine position of widget. + let pos = ui.available_rect_before_wrap().min; + // Determine size of widget. let size = { let width = width .unwrap_or_else(|| { @@ -653,9 +734,79 @@ impl Plot { .at_least(min_size.y); vec2(width, height) }; + // Determine complete rect of widget. + let complete_rect = Rect { + min: pos, + max: pos + size, + }; + // Next we want to create this layout. + // Incides are only examples. + // + // left right + // +---+---------x----------+ + + // | | X-axis 3 | + // | +--------------------+ top + // | | X-axis 2 | + // +-+-+--------------------+-+-+ + // |y|y| |y|y| + // |-|-| |-|-| + // |A|A| |A|A| + // y|x|x| Plot Window |x|x| + // |i|i| |i|i| + // |s|s| |s|s| + // |1|0| |2|3| + // +-+-+--------------------+-+-+ + // | X-axis 0 | | + // +--------------------+ | bottom + // | X-axis 1 | | + // + +--------------------+---+ + // - // Allocate the space. - let (rect, response) = ui.allocate_exact_size(size, Sense::drag()); + let mut plot_rect: Rect = { + // Calcuclate the space needed for each axis labels. + let mut margin = Margin::ZERO; + if show_axes.x { + for cfg in &x_axes { + match cfg.placement { + axis::Placement::LeftBottom => { + margin.bottom += cfg.thickness(Axis::X); + } + axis::Placement::RightTop => { + margin.top += cfg.thickness(Axis::X); + } + } + } + } + if show_axes.y { + for cfg in &y_axes { + match cfg.placement { + axis::Placement::LeftBottom => { + margin.left += cfg.thickness(Axis::Y); + } + axis::Placement::RightTop => { + margin.right += cfg.thickness(Axis::Y); + } + } + } + } + + // determine plot rectangle + margin.shrink_rect(complete_rect) + }; + + let [mut x_axis_widgets, mut y_axis_widgets] = + axis_widgets(show_axes, plot_rect, [&x_axes, &y_axes]); + + // If too little space, remove axis widgets + if plot_rect.width() <= 0.0 || plot_rect.height() <= 0.0 { + y_axis_widgets.clear(); + x_axis_widgets.clear(); + plot_rect = complete_rect; + } + + // Allocate the plot window. + let response = ui.allocate_rect(plot_rect, Sense::drag()); + let rect = plot_rect; // Load or initialize the memory. let plot_id = ui.make_persistent_id(id_source); @@ -679,8 +830,8 @@ impl Plot { last_plot_transform: PlotTransform::new( rect, min_auto_bounds, - center_x_axis, - center_y_axis, + center_axis.x, + center_axis.y, ), last_click_pos_for_zoom: None, }); @@ -841,7 +992,7 @@ impl Plot { } } - let mut transform = PlotTransform::new(rect, bounds, center_x_axis, center_y_axis); + let mut transform = PlotTransform::new(rect, bounds, center_axis.x, center_axis.y); // Enforce aspect ratio if let Some(data_aspect) = data_aspect { @@ -949,6 +1100,39 @@ impl Plot { } } + // --- transform initialized + + // Add legend widgets to plot + let bounds = transform.bounds(); + let x_axis_range = bounds.range_x(); + let x_steps = Arc::new({ + let input = GridInput { + bounds: (bounds.min[0], bounds.max[0]), + base_step_size: transform.dvalue_dpos()[0] * MIN_LINE_SPACING_IN_POINTS * 2.0, + }; + (grid_spacers[0])(input) + }); + let y_axis_range = bounds.range_y(); + let y_steps = Arc::new({ + let input = GridInput { + bounds: (bounds.min[1], bounds.max[1]), + base_step_size: transform.dvalue_dpos()[1] * MIN_LINE_SPACING_IN_POINTS * 2.0, + }; + (grid_spacers[1])(input) + }); + for mut widget in x_axis_widgets { + widget.range = x_axis_range.clone(); + widget.transform = Some(transform); + widget.steps = x_steps.clone(); + widget.ui(ui, Axis::X); + } + for mut widget in y_axis_widgets { + widget.range = y_axis_range.clone(); + widget.transform = Some(transform); + widget.steps = y_steps.clone(); + widget.ui(ui, Axis::Y); + } + // Initialize values from functions. for item in &mut items { item.initialize(transform.bounds().range_x()); @@ -960,16 +1144,16 @@ impl Plot { show_y, label_formatter, coordinates_formatter, - axis_formatters, - show_axes, + show_grid, transform, - draw_cursor_x: linked_cursors.as_ref().map_or(false, |(_, group)| group.x), - draw_cursor_y: linked_cursors.as_ref().map_or(false, |(_, group)| group.y), + draw_cursor_x: linked_cursors.as_ref().map_or(false, |group| group.1.x), + draw_cursor_y: linked_cursors.as_ref().map_or(false, |group| group.1.y), draw_cursors, grid_spacers, sharp_grid_lines, clamp_grid, }; + let plot_cursors = prepared.ui(ui, &response); if let Some(boxed_zoom_rect) = boxed_zoom_rect { @@ -1024,7 +1208,7 @@ impl Plot { } else { response }; - + ui.advance_cursor_after_rect(complete_rect); PlotResponse { inner, response, @@ -1033,6 +1217,79 @@ impl Plot { } } +fn axis_widgets( + show_axes: AxisBools, + plot_rect: Rect, + [x_axes, y_axes]: [&[AxisHints]; 2], +) -> [Vec; 2] { + let mut x_axis_widgets = Vec::::new(); + let mut y_axis_widgets = Vec::::new(); + + // Widget count per border of plot in order left, top, right, bottom + struct NumWidgets { + left: usize, + top: usize, + right: usize, + bottom: usize, + } + let mut num_widgets = NumWidgets { + left: 0, + top: 0, + right: 0, + bottom: 0, + }; + if show_axes.x { + for cfg in x_axes { + let size_y = Vec2::new(0.0, cfg.thickness(Axis::X)); + let rect = match cfg.placement { + axis::Placement::LeftBottom => { + let off = num_widgets.bottom as f32; + num_widgets.bottom += 1; + Rect { + min: plot_rect.left_bottom() + size_y * off, + max: plot_rect.right_bottom() + size_y * (off + 1.0), + } + } + axis::Placement::RightTop => { + let off = num_widgets.top as f32; + num_widgets.top += 1; + Rect { + min: plot_rect.left_top() - size_y * (off + 1.0), + max: plot_rect.right_top() - size_y * off, + } + } + }; + x_axis_widgets.push(AxisWidget::new(cfg.clone(), rect)); + } + } + if show_axes.y { + for cfg in y_axes { + let size_x = Vec2::new(cfg.thickness(Axis::Y), 0.0); + let rect = match cfg.placement { + axis::Placement::LeftBottom => { + let off = num_widgets.left as f32; + num_widgets.left += 1; + Rect { + min: plot_rect.left_top() - size_x * (off + 1.0), + max: plot_rect.left_bottom() - size_x * off, + } + } + axis::Placement::RightTop => { + let off = num_widgets.right as f32; + num_widgets.right += 1; + Rect { + min: plot_rect.right_top() + size_x * off, + max: plot_rect.right_bottom() + size_x * (off + 1.0), + } + } + }; + y_axis_widgets.push(AxisWidget::new(cfg.clone(), rect)); + } + } + + [x_axis_widgets, y_axis_widgets] +} + /// User-requested modifications to the plot bounds. We collect them in the plot build function to later apply /// them at the right time, as other modifications need to happen first. enum BoundsModification { @@ -1268,6 +1525,7 @@ pub struct GridInput { } /// One mark (horizontal or vertical line) in the background grid of a plot. +#[derive(Debug, Clone, Copy)] pub struct GridMark { /// X or Y value in the plot. pub value: f64, @@ -1329,14 +1587,14 @@ struct PreparedPlot { show_y: bool, label_formatter: LabelFormatter, coordinates_formatter: Option<(Corner, CoordinatesFormatter)>, - axis_formatters: [AxisFormatter; 2], - show_axes: [bool; 2], + // axis_formatters: [AxisFormatter; 2], transform: PlotTransform, + show_grid: AxisBools, + grid_spacers: [GridSpacer; 2], draw_cursor_x: bool, draw_cursor_y: bool, draw_cursors: Vec, - grid_spacers: [GridSpacer; 2], sharp_grid_lines: bool, clamp_grid: bool, } @@ -1345,16 +1603,11 @@ impl PreparedPlot { fn ui(self, ui: &mut Ui, response: &Response) -> Vec { let mut axes_shapes = Vec::new(); - for d in 0..2 { - if self.show_axes[d] { - self.paint_axis( - ui, - d, - self.show_axes[1 - d], - &mut axes_shapes, - self.sharp_grid_lines, - ); - } + if self.show_grid.x { + self.paint_grid(ui, &mut axes_shapes, Axis::X); + } + if self.show_grid.y { + self.paint_grid(ui, &mut axes_shapes, Axis::Y); } // Sort the axes by strength so that those with higher strength are drawn in front. @@ -1431,41 +1684,27 @@ impl PreparedPlot { cursors } - fn paint_axis( - &self, - ui: &Ui, - axis: usize, - other_axis_shown: bool, - shapes: &mut Vec<(Shape, f32)>, - sharp_grid_lines: bool, - ) { + fn paint_grid(&self, ui: &Ui, shapes: &mut Vec<(Shape, f32)>, axis: Axis) { #![allow(clippy::collapsible_else_if)] - let Self { transform, - axis_formatters, + // axis_formatters, grid_spacers, clamp_grid, .. } = self; - let bounds = transform.bounds(); - let axis_range = match axis { - 0 => bounds.range_x(), - 1 => bounds.range_y(), - _ => panic!("Axis {axis} does not exist."), - }; - - let font_id = TextStyle::Body.resolve(ui.style()); + let iaxis = usize::from(axis); // Where on the cross-dimension to show the label values - let value_cross = 0.0_f64.clamp(bounds.min[1 - axis], bounds.max[1 - axis]); + let bounds = transform.bounds(); + let value_cross = 0.0_f64.clamp(bounds.min[1 - iaxis], bounds.max[1 - iaxis]); let input = GridInput { - bounds: (bounds.min[axis], bounds.max[axis]), - base_step_size: transform.dvalue_dpos()[axis] * MIN_LINE_SPACING_IN_POINTS, + bounds: (bounds.min[iaxis], bounds.max[iaxis]), + base_step_size: transform.dvalue_dpos()[iaxis] * MIN_LINE_SPACING_IN_POINTS, }; - let steps = (grid_spacers[axis])(input); + let steps = (grid_spacers[iaxis])(input); let clamp_range = clamp_grid.then(|| { let mut tight_bounds = PlotBounds::NOTHING; @@ -1481,25 +1720,27 @@ impl PreparedPlot { let value_main = step.value; if let Some(clamp_range) = clamp_range { - if axis == 0 { - if !clamp_range.range_x().contains(&value_main) { - continue; - }; - } else { - if !clamp_range.range_y().contains(&value_main) { - continue; - }; + match axis { + Axis::X => { + if !clamp_range.range_x().contains(&value_main) { + continue; + }; + } + Axis::Y => { + if !clamp_range.range_y().contains(&value_main) { + continue; + }; + } } } - let value = if axis == 0 { - PlotPoint::new(value_main, value_cross) - } else { - PlotPoint::new(value_cross, value_main) + let value = match axis { + Axis::X => PlotPoint::new(value_main, value_cross), + Axis::Y => PlotPoint::new(value_cross, value_main), }; let pos_in_gui = transform.position_from_point(&value); - let spacing_in_points = (transform.dpos_dvalue()[axis] * step.step_size).abs() as f32; + let spacing_in_points = (transform.dpos_dvalue()[iaxis] * step.step_size).abs() as f32; if spacing_in_points > MIN_LINE_SPACING_IN_POINTS as f32 { let line_strength = remap_clamp( @@ -1508,24 +1749,27 @@ impl PreparedPlot { 0.0..=1.0, ); - let line_color = color_from_contrast(ui, line_strength); + let line_color = color_from_strength(ui, line_strength); let mut p0 = pos_in_gui; let mut p1 = pos_in_gui; - p0[1 - axis] = transform.frame().min[1 - axis]; - p1[1 - axis] = transform.frame().max[1 - axis]; + p0[1 - iaxis] = transform.frame().min[1 - iaxis]; + p1[1 - iaxis] = transform.frame().max[1 - iaxis]; if let Some(clamp_range) = clamp_range { - if axis == 0 { - p0.y = transform.position_from_point_y(clamp_range.min[1]); - p1.y = transform.position_from_point_y(clamp_range.max[1]); - } else { - p0.x = transform.position_from_point_x(clamp_range.min[0]); - p1.x = transform.position_from_point_x(clamp_range.max[0]); + match axis { + Axis::X => { + p0.y = transform.position_from_point_y(clamp_range.min[1]); + p1.y = transform.position_from_point_y(clamp_range.max[1]); + } + Axis::Y => { + p0.x = transform.position_from_point_x(clamp_range.min[0]); + p1.x = transform.position_from_point_x(clamp_range.max[0]); + } } } - if sharp_grid_lines { + if self.sharp_grid_lines { // Round to avoid aliasing p0 = ui.ctx().round_pos_to_pixels(p0); p1 = ui.ctx().round_pos_to_pixels(p1); @@ -1536,47 +1780,6 @@ impl PreparedPlot { line_strength, )); } - - const MIN_TEXT_SPACING: f32 = 40.0; - if spacing_in_points > MIN_TEXT_SPACING { - let text_strength = - remap_clamp(spacing_in_points, MIN_TEXT_SPACING..=150.0, 0.0..=1.0); - let color = color_from_contrast(ui, text_strength); - - let text: String = if let Some(formatter) = axis_formatters[axis].as_deref() { - formatter(value_main, &axis_range) - } else { - emath::round_to_decimals(value_main, 5).to_string() // hack - }; - - // Skip origin label for y-axis if x-axis is already showing it (otherwise displayed twice) - let skip_origin_y = axis == 1 && other_axis_shown && value_main == 0.0; - - // Custom formatters can return empty string to signal "no label at this resolution" - if !text.is_empty() && !skip_origin_y { - let galley = ui.painter().layout_no_wrap(text, font_id.clone(), color); - - let mut text_pos = pos_in_gui + vec2(1.0, -galley.size().y); - - // Make sure we see the labels, even if the axis is off-screen: - text_pos[1 - axis] = text_pos[1 - axis] - .at_most(transform.frame().max[1 - axis] - galley.size()[1 - axis] - 2.0) - .at_least(transform.frame().min[1 - axis] + 1.0); - - shapes.push((Shape::galley(text_pos, galley), text_strength)); - } - } - } - - fn color_from_contrast(ui: &Ui, contrast: f32) -> Color32 { - let bg = ui.visuals().extreme_bg_color; - let fg = ui.visuals().widgets.open.fg_stroke.color; - let mix = 0.5 * contrast.sqrt(); - Color32::from_rgb( - lerp((bg.r() as f32)..=(fg.r() as f32), mix) as u8, - lerp((bg.g() as f32)..=(fg.g() as f32), mix) as u8, - lerp((bg.b() as f32)..=(fg.b() as f32), mix) as u8, - ) } } @@ -1682,3 +1885,15 @@ pub fn format_number(number: f64, num_decimals: usize) -> String { format!("{:.*}", num_decimals.at_least(1), number) } } + +/// Determine a color from a 0-1 strength value. +pub fn color_from_strength(ui: &Ui, strength: f32) -> Color32 { + let bg = ui.visuals().extreme_bg_color; + let fg = ui.visuals().widgets.open.fg_stroke.color; + let mix = 0.5 * strength.sqrt(); + Color32::from_rgb( + lerp((bg.r() as f32)..=(fg.r() as f32), mix) as u8, + lerp((bg.g() as f32)..=(fg.g() as f32), mix) as u8, + lerp((bg.b() as f32)..=(fg.b() as f32), mix) as u8, + ) +} diff --git a/crates/egui_demo_lib/src/demo/plot_demo.rs b/crates/egui_demo_lib/src/demo/plot_demo.rs index 8daf3c377..c27117922 100644 --- a/crates/egui_demo_lib/src/demo/plot_demo.rs +++ b/crates/egui_demo_lib/src/demo/plot_demo.rs @@ -1,12 +1,12 @@ use std::f64::consts::TAU; use std::ops::RangeInclusive; -use egui::plot::{AxisBools, GridInput, GridMark, PlotResponse}; use egui::*; -use plot::{ - Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, Corner, HLine, - Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, PlotPoint, PlotPoints, Points, Polygon, - Text, VLine, + +use egui::plot::{ + Arrows, AxisBools, AxisHints, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, CoordinatesFormatter, + Corner, GridInput, GridMark, HLine, Legend, Line, LineStyle, MarkerShape, Plot, PlotImage, + PlotPoint, PlotPoints, PlotResponse, Points, Polygon, Text, VLine, }; // ---------------------------------------------------------------------------- @@ -119,17 +119,9 @@ impl super::View for PlotDemo { } } -fn is_approx_zero(val: f64) -> bool { - val.abs() < 1e-6 -} - -fn is_approx_integer(val: f64) -> bool { - val.fract().abs() < 1e-6 -} - // ---------------------------------------------------------------------------- -#[derive(PartialEq)] +#[derive(Copy, Clone, PartialEq)] struct LineDemo { animate: bool, time: f64, @@ -138,6 +130,8 @@ struct LineDemo { square: bool, proportional: bool, coordinates: bool, + show_axes: bool, + show_grid: bool, line_style: LineStyle, } @@ -151,6 +145,8 @@ impl Default for LineDemo { square: false, proportional: true, coordinates: true, + show_axes: true, + show_grid: true, line_style: LineStyle::Solid, } } @@ -165,9 +161,10 @@ impl LineDemo { circle_center, square, proportional, - line_style, coordinates, - .. + show_axes, + show_grid, + line_style, } = self; ui.horizontal(|ui| { @@ -195,6 +192,13 @@ impl LineDemo { }); }); + ui.vertical(|ui| { + ui.checkbox(show_axes, "Show axes"); + ui.checkbox(show_grid, "Show grid"); + ui.checkbox(coordinates, "Show coordinates on hover") + .on_hover_text("Can take a custom formatting function."); + }); + ui.vertical(|ui| { ui.style_mut().wrap = Some(false); ui.checkbox(animate, "Animate"); @@ -202,8 +206,6 @@ impl LineDemo { .on_hover_text("Always keep the viewport square."); ui.checkbox(proportional, "Proportional data axes") .on_hover_text("Tick are the same size on both axes."); - ui.checkbox(coordinates, "Show coordinates") - .on_hover_text("Can take a custom formatting function."); ComboBox::from_label("Line style") .selected_text(line_style.to_string()) @@ -268,11 +270,16 @@ impl LineDemo { impl LineDemo { fn ui(&mut self, ui: &mut Ui) -> Response { self.options_ui(ui); + if self.animate { ui.ctx().request_repaint(); self.time += ui.input(|i| i.unstable_dt).at_most(1.0 / 30.0) as f64; }; - let mut plot = Plot::new("lines_demo").legend(Legend::default()); + let mut plot = Plot::new("lines_demo") + .legend(Legend::default()) + .y_axis_width(4) + .show_axes(self.show_axes) + .show_grid(self.show_grid); if self.square { plot = plot.view_aspect(1.0); } @@ -429,8 +436,8 @@ impl LegendDemo { ); ui.end_row(); }); - let legend_plot = Plot::new("legend_demo") + .y_axis_width(2) .legend(config.clone()) .data_aspect(1.0); legend_plot @@ -523,7 +530,7 @@ impl CustomAxesDemo { 100.0 * y } - let x_fmt = |x, _range: &RangeInclusive| { + let x_fmt = |x, _digits, _range: &RangeInclusive| { if x < 0.0 * MINS_PER_DAY || x >= 5.0 * MINS_PER_DAY { // No labels outside value bounds String::new() @@ -536,7 +543,7 @@ impl CustomAxesDemo { } }; - let y_fmt = |y, _range: &RangeInclusive| { + let y_fmt = |y, _digits, _range: &RangeInclusive| { // Display only integer percentages if !is_approx_zero(y) && is_approx_integer(100.0 * y) { format!("{:.0}%", percent(y)) @@ -557,10 +564,23 @@ impl CustomAxesDemo { ui.label("Zoom in on the X-axis to see hours and minutes"); + let x_axes = vec![ + AxisHints::default().label("Time").formatter(x_fmt), + AxisHints::default().label("Value"), + ]; + let y_axes = vec![ + AxisHints::default() + .label("Percent") + .formatter(y_fmt) + .max_digits(4), + AxisHints::default() + .label("Absolute") + .placement(plot::HPlacement::Right), + ]; Plot::new("custom_axes") .data_aspect(2.0 * MINS_PER_DAY as f32) - .x_axis_formatter(x_fmt) - .y_axis_formatter(y_fmt) + .custom_x_axes(x_axes) + .custom_y_axes(y_axes) .x_grid_spacer(CustomAxesDemo::x_grid) .label_formatter(label_fmt) .show(ui, |plot_ui| { @@ -582,15 +602,11 @@ struct LinkedAxesDemo { impl Default for LinkedAxesDemo { fn default() -> Self { - let link_x = true; - let link_y = false; - let link_cursor_x = true; - let link_cursor_y = false; Self { - link_x, - link_y, - link_cursor_x, - link_cursor_y, + link_x: true, + link_y: true, + link_cursor_x: true, + link_cursor_y: true, } } } @@ -642,25 +658,29 @@ impl LinkedAxesDemo { let link_group_id = ui.id().with("linked_demo"); ui.horizontal(|ui| { - Plot::new("linked_axis_1") + Plot::new("left-top") .data_aspect(1.0) .width(250.0) .height(250.0) .link_axis(link_group_id, self.link_x, self.link_y) .link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y) .show(ui, LinkedAxesDemo::configure_plot); - Plot::new("linked_axis_2") + Plot::new("right-top") .data_aspect(2.0) .width(150.0) .height(250.0) + .y_axis_width(3) + .y_axis_label("y") + .y_axis_position(plot::HPlacement::Right) .link_axis(link_group_id, self.link_x, self.link_y) .link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y) .show(ui, LinkedAxesDemo::configure_plot); }); - Plot::new("linked_axis_3") + Plot::new("left-bottom") .data_aspect(0.5) .width(250.0) .height(150.0) + .x_axis_label("x") .link_axis(link_group_id, self.link_x, self.link_y) .link_cursor(link_group_id, self.link_cursor_x, self.link_cursor_y) .show(ui, LinkedAxesDemo::configure_plot) @@ -889,6 +909,7 @@ impl ChartsDemo { Plot::new("Normal Distribution Demo") .legend(Legend::default()) .clamp_grid(true) + .y_axis_width(3) .allow_zoom(self.allow_zoom) .allow_drag(self.allow_drag) .show(ui, |plot_ui| plot_ui.bar_chart(chart)) @@ -1003,3 +1024,11 @@ impl ChartsDemo { .response } } + +fn is_approx_zero(val: f64) -> bool { + val.abs() < 1e-6 +} + +fn is_approx_integer(val: f64) -> bool { + val.fract().abs() < 1e-6 +} diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index eb424e9e3..27dc4026b 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -271,6 +271,7 @@ fn example_plot(ui: &mut egui::Ui) -> egui::Response { let line = Line::new(line_points); egui::plot::Plot::new("example_plot") .height(32.0) + .show_axes(false) .data_aspect(1.0) .show(ui, |plot_ui| plot_ui.line(line)) .response From 043183a3a4596e43218c3c835e0dd2399113f7b0 Mon Sep 17 00:00:00 2001 From: Kamen Yovchevski <57301959+KYovchevski@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:21:42 +0200 Subject: [PATCH 51/54] Add `TableBuilder::drag_to_scroll` (#3100) * Add TableBuilder::drag_to_scroll * Add reference to ScrollArea::drag_to_scroll --- crates/egui_extras/src/table.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index de0e98704..e30367db4 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -159,6 +159,7 @@ fn to_sizing(columns: &[Column]) -> crate::sizing::Sizing { struct TableScrollOptions { vscroll: bool, + drag_to_scroll: bool, stick_to_bottom: bool, scroll_to_row: Option<(usize, Option)>, scroll_offset_y: Option, @@ -171,6 +172,7 @@ impl Default for TableScrollOptions { fn default() -> Self { Self { vscroll: true, + drag_to_scroll: true, stick_to_bottom: false, scroll_to_row: None, scroll_offset_y: None, @@ -273,6 +275,14 @@ impl<'a> TableBuilder<'a> { self.vscroll(vscroll) } + /// Enables scrolling the table's contents using mouse drag (default: `true`). + /// + /// See [`ScrollArea::drag_to_scroll`] for more. + pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self { + self.scroll_options.drag_to_scroll = drag_to_scroll; + self + } + /// Should the scroll handle stick to the bottom position even as the content size changes /// dynamically? The scroll handle remains stuck until manually changed, and will become stuck /// once again when repositioned to the bottom. Default: `false`. @@ -555,6 +565,7 @@ impl<'a> Table<'a> { let TableScrollOptions { vscroll, + drag_to_scroll, stick_to_bottom, scroll_to_row, scroll_offset_y, @@ -567,6 +578,7 @@ impl<'a> Table<'a> { let mut scroll_area = ScrollArea::new([false, vscroll]) .auto_shrink([true; 2]) + .drag_to_scroll(drag_to_scroll) .stick_to_bottom(stick_to_bottom) .min_scrolled_height(min_scrolled_height) .max_height(max_scroll_height) From e5428a3084c6e82d9c58bec449e3815198bfb8d5 Mon Sep 17 00:00:00 2001 From: Kamen Yovchevski <57301959+KYovchevski@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:44:29 +0200 Subject: [PATCH 52/54] Add Window::drag-to-scroll (#3118) --- crates/egui/src/containers/window.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 1d2444ca3..2e4a9a3ef 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -266,6 +266,14 @@ impl<'open> Window<'open> { self } + /// Enable/disable scrolling on the window by dragging with the pointer. `true` by default. + /// + /// See [`ScrollArea::drag_to_scroll`] for more. + pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self { + self.scroll = self.scroll.drag_to_scroll(drag_to_scroll); + self + } + /// Constrain the area up to which the window can be dragged. pub fn drag_bounds(mut self, bounds: Rect) -> Self { self.area = self.area.drag_bounds(bounds); From 8ee506ec374c950c1e66cbb3dbbe4edc2a566085 Mon Sep 17 00:00:00 2001 From: dmackdev <79006698+dmackdev@users.noreply.github.com> Date: Mon, 14 Aug 2023 17:57:07 +0100 Subject: [PATCH 53/54] Added remove method for CollapsingState. (#3252) --- crates/egui/src/containers/collapsing_header.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index 00f4ff73b..5eda61b60 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -36,6 +36,10 @@ impl CollapsingState { ctx.data_mut(|d| d.insert_persisted(self.id, self.state)); } + pub fn remove(&self, ctx: &Context) { + ctx.data_mut(|d| d.remove::(self.id)); + } + pub fn id(&self) -> Id { self.id } From 481f44828cea1a679c5c65665ffc050e65b775f2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 14 Aug 2023 18:57:39 +0200 Subject: [PATCH 54/54] Improve "Reset everything" button in demo (#3255) Closes https://github.com/emilk/egui/issues/3254 --- crates/egui_demo_app/src/wrap_app.rs | 48 ++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 69a23e7ab..dbfcfd5ea 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -124,6 +124,15 @@ impl Default for Anchor { // ---------------------------------------------------------------------------- +#[derive(Clone, Copy, Debug)] +#[must_use] +enum Command { + Nothing, + ResetEverything, +} + +// ---------------------------------------------------------------------------- + /// The state that we persist (serialize). #[derive(Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -240,18 +249,19 @@ impl eframe::App for WrapApp { frame.set_fullscreen(!frame.info().window_info.fullscreen); } + let mut cmd = Command::Nothing; egui::TopBottomPanel::top("wrap_app_top_bar").show(ctx, |ui| { egui::trace!(ui); ui.horizontal_wrapped(|ui| { ui.visuals_mut().button_frame = false; - self.bar_contents(ui, frame); + self.bar_contents(ui, frame, &mut cmd); }); }); self.state.backend_panel.update(ctx, frame); if !is_mobile(ctx) { - self.backend_panel(ctx, frame); + cmd = self.backend_panel(ctx, frame); } self.show_selected_app(ctx, frame); @@ -264,6 +274,8 @@ impl eframe::App for WrapApp { if !frame.is_web() { egui::gui_zoom::zoom_with_keyboard_shortcuts(ctx, frame.info().native_pixels_per_point); } + + self.run_cmd(ctx, cmd); } #[cfg(feature = "glow")] @@ -280,12 +292,14 @@ impl eframe::App for WrapApp { } impl WrapApp { - fn backend_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + fn backend_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) -> Command { // The backend-panel can be toggled on/off. // We show a little animation when the user switches it. let is_open = self.state.backend_panel.open || ctx.memory(|mem| mem.everything_is_visible()); + let mut cmd = Command::Nothing; + egui::SidePanel::left("backend_panel") .resizable(false) .show_animated(ctx, is_open, |ui| { @@ -294,11 +308,28 @@ impl WrapApp { }); ui.separator(); - self.backend_panel_contents(ui, frame); + self.backend_panel_contents(ui, frame, &mut cmd); }); + + cmd } - fn backend_panel_contents(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { + fn run_cmd(&mut self, ctx: &egui::Context, cmd: Command) { + match cmd { + Command::Nothing => {} + Command::ResetEverything => { + self.state = Default::default(); + ctx.memory_mut(|mem| *mem = Default::default()); + } + } + } + + fn backend_panel_contents( + &mut self, + ui: &mut egui::Ui, + frame: &mut eframe::Frame, + cmd: &mut Command, + ) { self.state.backend_panel.ui(ui, frame); ui.separator(); @@ -314,8 +345,7 @@ impl WrapApp { } if ui.button("Reset everything").clicked() { - self.state = Default::default(); - ui.ctx().memory_mut(|mem| *mem = Default::default()); + *cmd = Command::ResetEverything; ui.close_menu(); } }); @@ -330,7 +360,7 @@ impl WrapApp { } } - fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { + fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cmd: &mut Command) { egui::widgets::global_dark_light_mode_switch(ui); ui.separator(); @@ -338,7 +368,7 @@ impl WrapApp { if is_mobile(ui.ctx()) { ui.menu_button("💻 Backend", |ui| { ui.set_style(ui.ctx().style()); // ignore the "menu" style set by `menu_button`. - self.backend_panel_contents(ui, frame); + self.backend_panel_contents(ui, frame, cmd); }); } else { ui.toggle_value(&mut self.state.backend_panel.open, "💻 Backend");