egui/
debug_text.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! This is an example of how to create a plugin for egui.
//!
//! A plugin usually consist of a struct that holds some state,
//! which is stored using [`Context::data_mut`].
//! The plugin registers itself onto a specific [`Context`]
//! to get callbacks on certain events ([`Context::on_begin_pass`], [`Context::on_end_pass`]).

use crate::{
    text, Align, Align2, Color32, Context, FontFamily, FontId, Id, Rect, Shape, Vec2, WidgetText,
};

/// Register this plugin on the given egui context,
/// so that it will be called every pass.
///
/// This is a built-in plugin in egui,
/// meaning [`Context`] calls this from its `Default` implementation,
/// so this is marked as `pub(crate)`.
pub(crate) fn register(ctx: &Context) {
    ctx.on_end_pass("debug_text", std::sync::Arc::new(State::end_pass));
}

/// Print this text next to the cursor at the end of the pass.
///
/// 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;
/// egui::debug_text::print(ctx, format!("State: {state:?}"));
/// ```
#[track_caller]
pub fn print(ctx: &Context, text: impl Into<WidgetText>) {
    if !cfg!(debug_assertions) {
        return;
    }

    let location = std::panic::Location::caller();
    let location = format!("{}:{}", location.file(), location.line());
    ctx.data_mut(|data| {
        // We use `Id::NULL` as the id, since we only have one instance of this plugin.
        // We use the `temp` version instead of `persisted` since we don't want to
        // persist state on disk when the egui app is closed.
        let state = data.get_temp_mut_or_default::<State>(Id::NULL);
        state.entries.push(Entry {
            location,
            text: text.into(),
        });
    });
}

#[derive(Clone)]
struct Entry {
    location: String,
    text: WidgetText,
}

/// A plugin for easily showing debug-text on-screen.
///
/// This is a built-in plugin in egui.
#[derive(Clone, Default)]
struct State {
    // This gets re-filled every pass.
    entries: Vec<Entry>,
}

impl State {
    fn end_pass(ctx: &Context) {
        let state = ctx.data_mut(|data| data.remove_temp::<Self>(Id::NULL));
        if let Some(state) = state {
            state.paint(ctx);
        }
    }

    fn paint(self, ctx: &Context) {
        let Self { entries } = self;

        if entries.is_empty() {
            return;
        }

        // Show debug-text next to the cursor.
        let mut pos = ctx
            .input(|i| i.pointer.latest_pos())
            .unwrap_or_else(|| ctx.screen_rect().center())
            + 8.0 * Vec2::Y;

        let painter = ctx.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 Entry { location, text } in entries {
            {
                // Paint location to left of `pos`:
                let location_galley =
                    ctx.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 available_width = ctx.screen_rect().max.x - pos.x;
                let galley = text.into_galley_impl(
                    ctx,
                    &ctx.style(),
                    text::TextWrapping::wrap_at_width(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),
            ),
        );
    }
}