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),
),
);
}
}