Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_dev_tools/src/picking_debug.rs
6595 views
1
//! Text and on-screen debugging tools
2
3
use bevy_app::prelude::*;
4
use bevy_camera::visibility::Visibility;
5
use bevy_camera::Camera;
6
use bevy_color::prelude::*;
7
use bevy_ecs::prelude::*;
8
use bevy_picking::backend::HitData;
9
use bevy_picking::hover::HoverMap;
10
use bevy_picking::pointer::{Location, PointerId, PointerInput, PointerLocation, PointerPress};
11
use bevy_picking::prelude::*;
12
use bevy_picking::PickingSystems;
13
use bevy_reflect::prelude::*;
14
use bevy_text::prelude::*;
15
use bevy_ui::prelude::*;
16
use core::cmp::Ordering;
17
use core::fmt::{Debug, Display, Formatter, Result};
18
use tracing::{debug, trace};
19
20
/// This resource determines the runtime behavior of the debug plugin.
21
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, Resource)]
22
pub enum DebugPickingMode {
23
/// Only log non-noisy events, show the debug overlay.
24
Normal,
25
/// Log all events, including noisy events like `Move` and `Drag`, show the debug overlay.
26
Noisy,
27
/// Do not show the debug overlay or log any messages.
28
#[default]
29
Disabled,
30
}
31
32
impl DebugPickingMode {
33
/// A condition indicating the plugin is enabled
34
pub fn is_enabled(this: Res<Self>) -> bool {
35
matches!(*this, Self::Normal | Self::Noisy)
36
}
37
/// A condition indicating the plugin is disabled
38
pub fn is_disabled(this: Res<Self>) -> bool {
39
matches!(*this, Self::Disabled)
40
}
41
/// A condition indicating the plugin is enabled and in noisy mode
42
pub fn is_noisy(this: Res<Self>) -> bool {
43
matches!(*this, Self::Noisy)
44
}
45
}
46
47
/// Logs events for debugging
48
///
49
/// "Normal" events are logged at the `debug` level. "Noisy" events are logged at the `trace` level.
50
/// See [Bevy's LogPlugin](https://docs.rs/bevy/latest/bevy/log/struct.LogPlugin.html) and [Bevy
51
/// Cheatbook: Logging, Console Messages](https://bevy-cheatbook.github.io/features/log.html) for
52
/// details.
53
///
54
/// Usually, the default level printed is `info`, so debug and trace messages will not be displayed
55
/// even when this plugin is active. You can set `RUST_LOG` to change this.
56
///
57
/// You can also change the log filter at runtime in your code. The [LogPlugin
58
/// docs](https://docs.rs/bevy/latest/bevy/log/struct.LogPlugin.html) give an example.
59
///
60
/// Use the [`DebugPickingMode`] state resource to control this plugin. Example:
61
///
62
/// ```ignore
63
/// use DebugPickingMode::{Normal, Disabled};
64
/// app.insert_resource(DebugPickingMode::Normal)
65
/// .add_systems(
66
/// PreUpdate,
67
/// (|mut mode: ResMut<DebugPickingMode>| {
68
/// *mode = match *mode {
69
/// DebugPickingMode::Disabled => DebugPickingMode::Normal,
70
/// _ => DebugPickingMode::Disabled,
71
/// };
72
/// })
73
/// .distributive_run_if(bevy::input::common_conditions::input_just_pressed(
74
/// KeyCode::F3,
75
/// )),
76
/// )
77
/// ```
78
/// This sets the starting mode of the plugin to [`DebugPickingMode::Disabled`] and binds the F3 key
79
/// to toggle it.
80
#[derive(Debug, Default, Clone)]
81
pub struct DebugPickingPlugin;
82
83
impl Plugin for DebugPickingPlugin {
84
fn build(&self, app: &mut App) {
85
app.init_resource::<DebugPickingMode>()
86
.add_systems(
87
PreUpdate,
88
pointer_debug_visibility.in_set(PickingSystems::PostHover),
89
)
90
.add_systems(
91
PreUpdate,
92
(
93
// This leaves room to easily change the log-level associated
94
// with different events, should that be desired.
95
log_event_debug::<PointerInput>.run_if(DebugPickingMode::is_noisy),
96
log_pointer_event_debug::<Over>,
97
log_pointer_event_debug::<Out>,
98
log_pointer_event_debug::<Press>,
99
log_pointer_event_debug::<Release>,
100
log_pointer_event_debug::<Click>,
101
log_pointer_event_trace::<Move>.run_if(DebugPickingMode::is_noisy),
102
log_pointer_event_debug::<DragStart>,
103
log_pointer_event_trace::<Drag>.run_if(DebugPickingMode::is_noisy),
104
log_pointer_event_debug::<DragEnd>,
105
log_pointer_event_debug::<DragEnter>,
106
log_pointer_event_trace::<DragOver>.run_if(DebugPickingMode::is_noisy),
107
log_pointer_event_debug::<DragLeave>,
108
log_pointer_event_debug::<DragDrop>,
109
)
110
.distributive_run_if(DebugPickingMode::is_enabled)
111
.in_set(PickingSystems::Last),
112
);
113
114
app.add_systems(
115
PreUpdate,
116
(add_pointer_debug, update_debug_data, debug_draw)
117
.chain()
118
.distributive_run_if(DebugPickingMode::is_enabled)
119
.in_set(PickingSystems::Last),
120
);
121
}
122
}
123
124
/// Listen for any event and logs it at the debug level
125
pub fn log_event_debug<E: BufferedEvent + Debug>(mut events: EventReader<PointerInput>) {
126
for event in events.read() {
127
debug!("{event:?}");
128
}
129
}
130
131
/// Listens for pointer events of type `E` and logs them at "debug" level
132
pub fn log_pointer_event_debug<E: Debug + Clone + Reflect>(
133
mut pointer_events: EventReader<Pointer<E>>,
134
) {
135
for event in pointer_events.read() {
136
debug!("{event}");
137
}
138
}
139
140
/// Listens for pointer events of type `E` and logs them at "trace" level
141
pub fn log_pointer_event_trace<E: Debug + Clone + Reflect>(
142
mut pointer_events: EventReader<Pointer<E>>,
143
) {
144
for event in pointer_events.read() {
145
trace!("{event}");
146
}
147
}
148
149
/// Adds [`PointerDebug`] to pointers automatically.
150
pub fn add_pointer_debug(
151
mut commands: Commands,
152
pointers: Query<Entity, (With<PointerId>, Without<PointerDebug>)>,
153
) {
154
for entity in &pointers {
155
commands.entity(entity).insert(PointerDebug::default());
156
}
157
}
158
159
/// Hide text from pointers.
160
pub fn pointer_debug_visibility(
161
debug: Res<DebugPickingMode>,
162
mut pointers: Query<&mut Visibility, With<PointerId>>,
163
) {
164
let visible = match *debug {
165
DebugPickingMode::Disabled => Visibility::Hidden,
166
_ => Visibility::Visible,
167
};
168
for mut vis in &mut pointers {
169
*vis = visible;
170
}
171
}
172
173
/// Storage for per-pointer debug information.
174
#[derive(Debug, Component, Clone, Default)]
175
pub struct PointerDebug {
176
/// The pointer location.
177
pub location: Option<Location>,
178
179
/// Representation of the different pointer button states.
180
pub press: PointerPress,
181
182
/// List of hit elements to be displayed.
183
pub hits: Vec<(String, HitData)>,
184
}
185
186
fn bool_to_icon(f: &mut Formatter, prefix: &str, input: bool) -> Result {
187
write!(f, "{prefix}{}", if input { "[X]" } else { "[ ]" })
188
}
189
190
impl Display for PointerDebug {
191
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
192
if let Some(location) = &self.location {
193
writeln!(f, "Location: {:.2?}", location.position)?;
194
}
195
bool_to_icon(f, "Pressed: ", self.press.is_primary_pressed())?;
196
bool_to_icon(f, " ", self.press.is_middle_pressed())?;
197
bool_to_icon(f, " ", self.press.is_secondary_pressed())?;
198
let mut sorted_hits = self.hits.clone();
199
sorted_hits.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
200
for (entity, hit) in sorted_hits.iter() {
201
write!(f, "\nEntity: {entity:?}")?;
202
if let Some((position, normal)) = hit.position.zip(hit.normal) {
203
write!(f, ", Position: {position:.2?}, Normal: {normal:.2?}")?;
204
}
205
write!(f, ", Depth: {:.2?}", hit.depth)?;
206
}
207
208
Ok(())
209
}
210
}
211
212
/// Update typed debug data used to draw overlays
213
pub fn update_debug_data(
214
hover_map: Res<HoverMap>,
215
entity_names: Query<NameOrEntity>,
216
mut pointers: Query<(
217
&PointerId,
218
&PointerLocation,
219
&PointerPress,
220
&mut PointerDebug,
221
)>,
222
) {
223
for (id, location, press, mut debug) in &mut pointers {
224
*debug = PointerDebug {
225
location: location.location().cloned(),
226
press: press.to_owned(),
227
hits: hover_map
228
.get(id)
229
.iter()
230
.flat_map(|h| h.iter())
231
.filter_map(|(e, h)| {
232
if let Ok(entity_name) = entity_names.get(*e) {
233
Some((entity_name.to_string(), h.to_owned()))
234
} else {
235
None
236
}
237
})
238
.collect(),
239
};
240
}
241
}
242
243
/// Draw text on each cursor with debug info
244
pub fn debug_draw(
245
mut commands: Commands,
246
camera_query: Query<(Entity, &Camera)>,
247
primary_window: Query<Entity, With<bevy_window::PrimaryWindow>>,
248
pointers: Query<(Entity, &PointerId, &PointerDebug)>,
249
scale: Res<UiScale>,
250
) {
251
for (entity, id, debug) in &pointers {
252
let Some(pointer_location) = &debug.location else {
253
continue;
254
};
255
let text = format!("{id:?}\n{debug}");
256
257
for (camera, _) in camera_query.iter().filter(|(_, camera)| {
258
camera
259
.target
260
.normalize(primary_window.single().ok())
261
.is_some_and(|target| target == pointer_location.target)
262
}) {
263
let mut pointer_pos = pointer_location.position;
264
if let Some(viewport) = camera_query
265
.get(camera)
266
.ok()
267
.and_then(|(_, camera)| camera.logical_viewport_rect())
268
{
269
pointer_pos -= viewport.min;
270
}
271
272
commands
273
.entity(entity)
274
.despawn_related::<Children>()
275
.insert((
276
Node {
277
position_type: PositionType::Absolute,
278
left: Val::Px(pointer_pos.x + 5.0) / scale.0,
279
top: Val::Px(pointer_pos.y + 5.0) / scale.0,
280
padding: UiRect::px(10.0, 10.0, 8.0, 6.0),
281
..Default::default()
282
},
283
BackgroundColor(Color::BLACK.with_alpha(0.75)),
284
GlobalZIndex(i32::MAX),
285
Pickable::IGNORE,
286
UiTargetCamera(camera),
287
children![(Text::new(text.clone()), TextFont::from_font_size(12.0))],
288
));
289
}
290
}
291
}
292
293