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