diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index c1c179f2..f6d6ba83 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1942,6 +1942,10 @@ impl Context { paint_widget(widget, "drag", Color32::GREEN); } } + + if let Some(debug_rect) = self.frame_state_mut(|fs| fs.debug_rect.take()) { + debug_rect.paint(&self.debug_painter()); + } } } diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index 184ab0d6..04286a90 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -47,6 +47,99 @@ pub struct AccessKitFrameState { pub parent_stack: Vec, } +#[cfg(debug_assertions)] +#[derive(Clone)] +pub struct DebugRect { + pub rect: Rect, + pub callstack: String, + pub is_clicking: bool, +} + +#[cfg(debug_assertions)] +impl DebugRect { + pub fn paint(self, painter: &Painter) { + let Self { + rect, + callstack, + is_clicking, + } = self; + + let ctx = painter.ctx(); + + // Paint rectangle around widget: + { + // Print width and height: + let text_color = if ctx.style().visuals.dark_mode { + Color32::WHITE + } else { + Color32::BLACK + }; + painter.debug_text( + rect.left_center() + 2.0 * Vec2::LEFT, + Align2::RIGHT_CENTER, + text_color, + format!("H: {:.1}", rect.height()), + ); + painter.debug_text( + rect.center_top(), + Align2::CENTER_BOTTOM, + text_color, + format!("W: {:.1}", rect.width()), + ); + + // Paint rect: + let rect_fg_color = if is_clicking { + Color32::WHITE + } else { + Color32::LIGHT_BLUE + }; + let rect_bg_color = Color32::BLUE.gamma_multiply(0.5); + painter.rect(rect, 0.0, rect_bg_color, (1.0, rect_fg_color)); + } + + if !callstack.is_empty() { + let font_id = FontId::monospace(12.0); + let text = format!("{callstack}\n\n(click to copy)"); + let text_color = Color32::WHITE; + let galley = painter.layout_no_wrap(text, font_id, text_color); + + // Position the text either under or above: + let screen_rect = ctx.screen_rect(); + let y = if galley.size().y <= rect.top() { + // Above + rect.top() - galley.size().y - 16.0 + } else { + // Below + rect.bottom() + }; + + let y = y + .at_most(screen_rect.bottom() - galley.size().y) + .at_least(0.0); + + let x = rect + .left() + .at_most(screen_rect.right() - galley.size().x) + .at_least(0.0); + let text_pos = pos2(x, y); + + let text_bg_color = Color32::from_black_alpha(180); + let text_rect_stroke_color = if is_clicking { + Color32::WHITE + } else { + text_bg_color + }; + let text_rect = Rect::from_min_size(text_pos, galley.size()); + painter.rect(text_rect, 0.0, text_bg_color, (1.0, text_rect_stroke_color)); + painter.galley(text_pos, galley, text_color); + + if is_clicking { + ctx.copy_text(callstack); + } + } + } +} + /// State that is collected during a frame, then saved for the next frame, /// and then cleared. /// @@ -99,7 +192,7 @@ pub struct FrameState { pub highlight_next_frame: IdSet, #[cfg(debug_assertions)] - pub has_debug_viewed_this_frame: bool, + pub debug_rect: Option, } impl Default for FrameState { @@ -119,7 +212,7 @@ impl Default for FrameState { highlight_next_frame: Default::default(), #[cfg(debug_assertions)] - has_debug_viewed_this_frame: false, + debug_rect: None, } } } @@ -142,7 +235,7 @@ impl FrameState { highlight_next_frame, #[cfg(debug_assertions)] - has_debug_viewed_this_frame, + debug_rect, } = self; used_ids.clear(); @@ -157,7 +250,7 @@ impl FrameState { #[cfg(debug_assertions)] { - *has_debug_viewed_this_frame = false; + *debug_rect = None; } #[cfg(feature = "accesskit")] diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index a3f3d729..89e0ffab 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -106,7 +106,7 @@ //! //! `egui` uses logical _points_ as its coordinate system. //! Those related to physical _pixels_ by the `pixels_per_point` scale factor. -//! For example, a high-dpi screeen can have `pixels_per_point = 2.0`, +//! For example, a high-dpi screen can have `pixels_per_point = 2.0`, //! meaning there are two physical screen pixels for each logical point. //! //! Angles are in radians, and are measured clockwise from the X-axis, which has angle=0. diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 6009a6b6..21d542c5 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -727,7 +727,7 @@ pub struct Interaction { /// Can the user select text that span multiple labels? /// - /// The default is `true`, but text seelction can be slightly glitchy, + /// The default is `true`, but text selection can be slightly glitchy, /// so you may want to disable it. pub multi_widget_text_select: bool, } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index a7a7712b..7317d94d 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -2599,110 +2599,50 @@ fn register_rect(ui: &Ui, rect: Rect) { return; } - if ui.ctx().frame_state(|o| o.has_debug_viewed_this_frame) { - return; - } - if !ui.rect_contains_pointer(rect) { return; } - // We only show one debug rectangle, or things get confusing: - ui.ctx() - .frame_state_mut(|o| o.has_debug_viewed_this_frame = true); - - // ---------------------------------------------- - let is_clicking = ui.input(|i| i.pointer.could_any_button_be_click()); - // Use the debug-painter to avoid clip rect, - // otherwise the content of the widget may cover what we paint here! - let painter = ui.ctx().debug_painter(); - - // Paint rectangle around widget: - { - // Print width and height: - let text_color = if ui.visuals().dark_mode { - Color32::WHITE - } else { - Color32::BLACK - }; - painter.debug_text( - rect.left_center() + 2.0 * Vec2::LEFT, - Align2::RIGHT_CENTER, - text_color, - format!("H: {:.1}", rect.height()), - ); - painter.debug_text( - rect.center_top(), - Align2::CENTER_BOTTOM, - text_color, - format!("W: {:.1}", rect.width()), - ); - - // Paint rect: - let rect_fg_color = if is_clicking { - Color32::WHITE - } else { - Color32::LIGHT_BLUE - }; - let rect_bg_color = Color32::BLUE.gamma_multiply(0.5); - painter.rect(rect, 0.0, rect_bg_color, (1.0, rect_fg_color)); - } - - // ---------------------------------------------- - - if debug.hover_shows_next { - ui.placer.debug_paint_cursor(&painter, "next"); - } - - // ---------------------------------------------- - #[cfg(feature = "callstack")] let callstack = crate::callstack::capture(); #[cfg(not(feature = "callstack"))] let callstack = String::default(); - if !callstack.is_empty() { - let font_id = FontId::monospace(12.0); - let text = format!("{callstack}\n\n(click to copy)"); - let text_color = Color32::WHITE; - let galley = painter.layout_no_wrap(text, font_id, text_color); + // We only show one debug rectangle, or things get confusing: + let debug_rect = frame_state::DebugRect { + rect, + callstack, + is_clicking, + }; - // Position the text either under or above: - let screen_rect = ui.ctx().screen_rect(); - let y = if galley.size().y <= rect.top() { - // Above - rect.top() - galley.size().y - 16.0 + let mut kept = false; + ui.ctx().frame_state_mut(|fs| { + if let Some(final_debug_rect) = &mut fs.debug_rect { + // or maybe pick the one with deepest callstack? + if final_debug_rect.rect.contains_rect(rect) { + *final_debug_rect = debug_rect; + kept = true; + } } else { - // Below - rect.bottom() - }; - - let y = y - .at_most(screen_rect.bottom() - galley.size().y) - .at_least(0.0); - - let x = rect - .left() - .at_most(screen_rect.right() - galley.size().x) - .at_least(0.0); - let text_pos = pos2(x, y); - - let text_bg_color = Color32::from_black_alpha(180); - let text_rect_stroke_color = if is_clicking { - Color32::WHITE - } else { - text_bg_color - }; - let text_rect = Rect::from_min_size(text_pos, galley.size()); - painter.rect(text_rect, 0.0, text_bg_color, (1.0, text_rect_stroke_color)); - painter.galley(text_pos, galley, text_color); - - if ui.input(|i| i.pointer.any_click()) { - ui.ctx().copy_text(callstack); + fs.debug_rect = Some(debug_rect); + kept = true; } + }); + if !kept { + return; + } + + // ---------------------------------------------- + + // Use the debug-painter to avoid clip rect, + // otherwise the content of the widget may cover what we paint here! + let painter = ui.ctx().debug_painter(); + + if debug.hover_shows_next { + ui.placer.debug_paint_cursor(&painter, "next"); } } diff --git a/crates/epaint/src/bezier.rs b/crates/epaint/src/bezier.rs index 6f61feb0..a48c161c 100644 --- a/crates/epaint/src/bezier.rs +++ b/crates/epaint/src/bezier.rs @@ -713,7 +713,7 @@ fn quadratic_for_each_local_extremum(p0: f32, p1: f32, p2: f32, c fn cubic_for_each_local_extremum(p0: f32, p1: f32, p2: f32, p3: f32, cb: &mut F) { // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation - // A cubic Bézier curve can be derivated by the following equation: + // A cubic Bézier curve can be derived by the following equation: // B'(t) = 3(1-t)^2(p1-p0) + 6(1-t)t(p2-p1) + 3t^2(p3-p2) or // f(x) = a * x² + b * x + c let a = 3.0 * (p3 + 3.0 * (p1 - p2) - p0); diff --git a/crates/epaint/src/lib.rs b/crates/epaint/src/lib.rs index d5e70558..a8fba51e 100644 --- a/crates/epaint/src/lib.rs +++ b/crates/epaint/src/lib.rs @@ -11,7 +11,7 @@ //! //! `epaint` uses logical _points_ as its coordinate system. //! Those related to physical _pixels_ by the `pixels_per_point` scale factor. -//! For example, a high-dpi screeen can have `pixels_per_point = 2.0`, +//! For example, a high-dpi screen can have `pixels_per_point = 2.0`, //! meaning there are two physical screen pixels for each logical point. //! //! Angles are in radians, and are measured clockwise from the X-axis, which has angle=0. diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index 943444cf..f91ef9dd 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -42,7 +42,7 @@ disallowed-types = [ # { path = "std::path::PathBuf", reason = "Can't read/write files on web" }, // TODO(emilk): consider banning Path on wasm ] -# Allow-list of words for markdown in dosctrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown +# Allow-list of words for markdown in docstrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown doc-valid-idents = [ # You must also update the same list in the root `clippy.toml`! "AccessKit",