diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 8b92a945c899..99165149f1d6 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -351,8 +351,6 @@ pub fn popup_above_or_below_widget( .fixed_pos(pos) .pivot(pivot) .show(ui.ctx(), |ui| { - // Note: we use a separate clip-rect for this area, so the popup can be outside the parent. - // See https://github.com/emilk/egui/issues/825 let frame = Frame::popup(ui.style()); let frame_margin = frame.total_margin(); frame diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 9b7b5220487b..f1b808b765be 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -231,6 +231,11 @@ impl ViewportRepaintInfo { // ---------------------------------------------------------------------------- +struct DebugText { + location: String, + text: WidgetText, +} + #[derive(Default)] struct ContextImpl { /// Since we could have multiple viewport across multiple monitors with @@ -278,6 +283,8 @@ struct ContextImpl { accesskit_node_classes: accesskit::NodeClassSet, loaders: Arc, + + debug_texts: Vec, } impl ContextImpl { @@ -1060,6 +1067,31 @@ impl Context { Self::layer_painter(self, LayerId::debug()) } + /// Print this text next to the cursor at the end of the frame. + /// + /// If you call this multiple times, the text will be appended. + /// + /// This only works if compiled with `debug_assertions`. + /// + /// ``` + /// # let ctx = egui::Context::default(); + /// # let state = true; + /// ctx.debug_text(format!("State: {state:?}")); + /// ``` + #[track_caller] + pub fn debug_text(&self, text: impl Into) { + if cfg!(debug_assertions) { + let location = std::panic::Location::caller(); + let location = format!("{}:{}", location.file(), location.line()); + self.write(|c| { + c.debug_texts.push(DebugText { + location, + text: text.into(), + }); + }); + } + } + /// What operating system are we running on? /// /// When compiling natively, this is @@ -1578,6 +1610,63 @@ impl Context { crate::gui_zoom::zoom_with_keyboard(self); } + let debug_texts = self.write(|ctx| std::mem::take(&mut ctx.debug_texts)); + if !debug_texts.is_empty() { + // Show debug-text next to the cursor. + let mut pos = self + .input(|i| i.pointer.latest_pos()) + .unwrap_or_else(|| self.screen_rect().center()) + + 8.0 * Vec2::Y; + + let painter = self.debug_painter(); + let where_to_put_background = painter.add(Shape::Noop); + + let mut bounding_rect = Rect::from_points(&[pos]); + + let color = Color32::GRAY; + let font_id = FontId::new(10.0, FontFamily::Proportional); + + for DebugText { location, text } in debug_texts { + { + // Paint location to left of `pos`: + let location_galley = + self.fonts(|f| f.layout(location, font_id.clone(), color, f32::INFINITY)); + let location_rect = + Align2::RIGHT_TOP.anchor_size(pos - 4.0 * Vec2::X, location_galley.size()); + painter.galley(location_rect.min, location_galley, color); + bounding_rect = bounding_rect.union(location_rect); + } + + { + // Paint `text` to right of `pos`: + let wrap = true; + let available_width = self.screen_rect().max.x - pos.x; + let galley = text.into_galley_impl( + self, + &self.style(), + wrap, + available_width, + font_id.clone().into(), + Align::TOP, + ); + let rect = Align2::LEFT_TOP.anchor_size(pos, galley.size()); + painter.galley(rect.min, galley, color); + bounding_rect = bounding_rect.union(rect); + } + + pos.y = bounding_rect.max.y + 4.0; + } + + painter.set( + where_to_put_background, + Shape::rect_filled( + bounding_rect.expand(4.0), + 2.0, + Color32::from_black_alpha(192), + ), + ); + } + self.write(|ctx| ctx.end_frame()) } } diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 8d6e3e6a8d61..951c04478ad7 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -219,7 +219,9 @@ impl Painter { self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {text}")) } - /// text with a background + /// Text with a background. + /// + /// See also [`Context::debug_text`]. #[allow(clippy::needless_pass_by_value)] pub fn debug_text( &self, @@ -229,7 +231,7 @@ impl Painter { text: impl ToString, ) -> Rect { let galley = self.layout_no_wrap(text.to_string(), FontId::monospace(12.0), color); - let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size())); + let rect = anchor.anchor_size(pos, galley.size()); let frame_rect = rect.expand(2.0); self.add(Shape::rect_filled( frame_rect, @@ -378,7 +380,7 @@ impl Painter { text_color: Color32, ) -> Rect { let galley = self.layout_no_wrap(text.to_string(), font_id, text_color); - let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size())); + let rect = anchor.anchor_size(pos, galley.size()); self.galley(rect.min, galley, text_color); rect } diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index 24f4a9c13807..57643ae45765 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -649,18 +649,39 @@ impl WidgetText { fallback_font: impl Into, ) -> Arc { let wrap = wrap.unwrap_or_else(|| ui.wrap_text()); + let valign = ui.layout().vertical_align(); + let style = ui.style(); + + self.into_galley_impl( + ui.ctx(), + style, + wrap, + available_width, + fallback_font.into(), + valign, + ) + } + + pub fn into_galley_impl( + self, + ctx: &crate::Context, + style: &Style, + wrap: bool, + available_width: f32, + fallback_font: FontSelection, + default_valign: Align, + ) -> Arc { let wrap_width = if wrap { available_width } else { f32::INFINITY }; match self { Self::RichText(text) => { - let valign = ui.layout().vertical_align(); - let mut layout_job = text.into_layout_job(ui.style(), fallback_font.into(), valign); + let mut layout_job = text.into_layout_job(style, fallback_font, default_valign); layout_job.wrap.max_width = wrap_width; - ui.fonts(|f| f.layout_job(layout_job)) + ctx.fonts(|f| f.layout_job(layout_job)) } Self::LayoutJob(mut job) => { job.wrap.max_width = wrap_width; - ui.fonts(|f| f.layout_job(job)) + ctx.fonts(|f| f.layout_job(job)) } Self::Galley(galley) => galley, } diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 4d1859b9e4a6..5bb6605b2d38 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -11,23 +11,11 @@ use egui::text::LayoutJob; pub fn code_view_ui( ui: &mut egui::Ui, theme: &CodeTheme, - mut code: &str, + code: &str, language: &str, ) -> egui::Response { - let mut layouter = |ui: &egui::Ui, string: &str, _wrap_width: f32| { - let layout_job = highlight(ui.ctx(), theme, string, language); - // layout_job.wrap.max_width = wrap_width; // no wrapping - ui.fonts(|f| f.layout_job(layout_job)) - }; - - ui.add( - egui::TextEdit::multiline(&mut code) - .font(egui::TextStyle::Monospace) // for cursor height - .code_editor() - .desired_rows(1) - .lock_focus(true) - .layouter(&mut layouter), - ) + let layout_job = highlight(ui.ctx(), theme, code, language); + ui.add(egui::Label::new(layout_job).selectable(true)) } /// Add syntax highlighting to a code string. diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index 4c2eb77a92e9..24bcb9d6ef97 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -728,9 +728,7 @@ impl PlotItem for Text { .into_galley(ui, Some(false), f32::INFINITY, TextStyle::Small); let pos = transform.position_from_point(&self.position); - let rect = self - .anchor - .anchor_rect(Rect::from_min_size(pos, galley.size())); + let rect = self.anchor.anchor_size(pos, galley.size()); shapes.push(epaint::TextShape::new(rect.min, galley, color).into()); diff --git a/crates/emath/src/align.rs b/crates/emath/src/align.rs index 97ae44ff4443..8cc515836cb6 100644 --- a/crates/emath/src/align.rs +++ b/crates/emath/src/align.rs @@ -186,6 +186,23 @@ impl Align2 { Rect::from_min_size(pos2(x, y), rect.size()) } + /// Use this anchor to position something around `pos`, + /// e.g. [`Self::RIGHT_TOP`] means the right-top of the rect + /// will end up at `pos`. + pub fn anchor_size(self, pos: Pos2, size: Vec2) -> Rect { + let x = match self.x() { + Align::Min => pos.x, + Align::Center => pos.x - 0.5 * size.x, + Align::Max => pos.x - size.x, + }; + let y = match self.y() { + Align::Min => pos.y, + Align::Center => pos.y - 0.5 * size.y, + Align::Max => pos.y - size.y, + }; + Rect::from_min_size(pos2(x, y), size) + } + /// e.g. center a size within a given frame pub fn align_size_within_rect(self, size: Vec2, frame: Rect) -> Rect { let x_range = self.x().align_size_within_range(size.x, frame.x_range()); diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 4ecba1d1d1ef..61b9d75e92b8 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -264,7 +264,7 @@ impl Shape { color: Color32, ) -> Self { let galley = fonts.layout_no_wrap(text.to_string(), font_id, color); - let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size())); + let rect = anchor.anchor_size(pos, galley.size()); Self::galley(rect.min, galley, color) }