egui/
debug_text.rs

1//! This is an example of how to create a plugin for egui.
2//!
3//! A plugin usually consist of a struct that holds some state,
4//! which is stored using [`Context::data_mut`].
5//! The plugin registers itself onto a specific [`Context`]
6//! to get callbacks on certain events ([`Context::on_begin_frame`], [`Context::on_end_frame`]).
7
8use crate::*;
9
10/// Register this plugin on the given egui context,
11/// so that it will be called every frame.
12///
13/// This is a built-in plugin in egui,
14/// meaning [`Context`] calls this from its `Default` implementation,
15/// so this is marked as `pub(crate)`.
16pub(crate) fn register(ctx: &Context) {
17    ctx.on_end_frame("debug_text", std::sync::Arc::new(State::end_frame));
18}
19
20/// Print this text next to the cursor at the end of the frame.
21///
22/// If you call this multiple times, the text will be appended.
23///
24/// This only works if compiled with `debug_assertions`.
25///
26/// ```
27/// # let ctx = &egui::Context::default();
28/// # let state = true;
29/// egui::debug_text::print(ctx, format!("State: {state:?}"));
30/// ```
31#[track_caller]
32pub fn print(ctx: &Context, text: impl Into<WidgetText>) {
33    if !cfg!(debug_assertions) {
34        return;
35    }
36
37    let location = std::panic::Location::caller();
38    let location = format!("{}:{}", location.file(), location.line());
39    ctx.data_mut(|data| {
40        // We use `Id::NULL` as the id, since we only have one instance of this plugin.
41        // We use the `temp` version instead of `persisted` since we don't want to
42        // persist state on disk when the egui app is closed.
43        let state = data.get_temp_mut_or_default::<State>(Id::NULL);
44        state.entries.push(Entry {
45            location,
46            text: text.into(),
47        });
48    });
49}
50
51#[derive(Clone)]
52struct Entry {
53    location: String,
54    text: WidgetText,
55}
56
57/// A plugin for easily showing debug-text on-screen.
58///
59/// This is a built-in plugin in egui.
60#[derive(Clone, Default)]
61struct State {
62    // This gets re-filled every frame.
63    entries: Vec<Entry>,
64}
65
66impl State {
67    fn end_frame(ctx: &Context) {
68        let state = ctx.data_mut(|data| data.remove_temp::<Self>(Id::NULL));
69        if let Some(state) = state {
70            state.paint(ctx);
71        }
72    }
73
74    fn paint(self, ctx: &Context) {
75        let Self { entries } = self;
76
77        if entries.is_empty() {
78            return;
79        }
80
81        // Show debug-text next to the cursor.
82        let mut pos = ctx
83            .input(|i| i.pointer.latest_pos())
84            .unwrap_or_else(|| ctx.screen_rect().center())
85            + 8.0 * Vec2::Y;
86
87        let painter = ctx.debug_painter();
88        let where_to_put_background = painter.add(Shape::Noop);
89
90        let mut bounding_rect = Rect::from_points(&[pos]);
91
92        let color = Color32::GRAY;
93        let font_id = FontId::new(10.0, FontFamily::Proportional);
94
95        for Entry { location, text } in entries {
96            {
97                // Paint location to left of `pos`:
98                let location_galley =
99                    ctx.fonts(|f| f.layout(location, font_id.clone(), color, f32::INFINITY));
100                let location_rect =
101                    Align2::RIGHT_TOP.anchor_size(pos - 4.0 * Vec2::X, location_galley.size());
102                painter.galley(location_rect.min, location_galley, color);
103                bounding_rect = bounding_rect.union(location_rect);
104            }
105
106            {
107                // Paint `text` to right of `pos`:
108                let available_width = ctx.screen_rect().max.x - pos.x;
109                let galley = text.into_galley_impl(
110                    ctx,
111                    &ctx.style(),
112                    text::TextWrapping::wrap_at_width(available_width),
113                    font_id.clone().into(),
114                    Align::TOP,
115                );
116                let rect = Align2::LEFT_TOP.anchor_size(pos, galley.size());
117                painter.galley(rect.min, galley, color);
118                bounding_rect = bounding_rect.union(rect);
119            }
120
121            pos.y = bounding_rect.max.y + 4.0;
122        }
123
124        painter.set(
125            where_to_put_background,
126            Shape::rect_filled(
127                bounding_rect.expand(4.0),
128                2.0,
129                Color32::from_black_alpha(192),
130            ),
131        );
132    }
133}