1use super::{Prank3d, Prank3dActive};
4use crate::PrankConfig;
5use bevy::prelude::*;
6
7pub(super) struct Prank3dHudPlugin;
8
9impl Plugin for Prank3dHudPlugin {
10 fn build(&self, app: &mut App) {
11 app.add_systems(
12 Update,
13 (
14 spawn
15 .run_if(|active: Res<Prank3dActive>| active.is_changed() && active.0.is_some()),
16 despawn
17 .run_if(|active: Res<Prank3dActive>| active.is_changed() && active.0.is_none()),
18 sync_translation,
19 sync_fps,
20 sync_fov,
21 sync_speed,
22 ),
23 );
24 }
25}
26
27#[derive(Clone)]
29pub struct Prank3dHudConfig {
30 pub height: Val,
32
33 pub background_color: BackgroundColor,
35
36 pub text_style: TextStyle,
38}
39
40impl Default for Prank3dHudConfig {
41 fn default() -> Self {
42 Self {
43 height: Val::Px(25.0),
44 background_color: Color::BLACK.with_a(0.5).into(),
45 text_style: TextStyle {
46 font_size: 14.0,
47 color: Color::WHITE,
48 ..default()
49 },
50 }
51 }
52}
53
54#[derive(Component)]
55struct Hud;
56
57#[derive(Component)]
58struct HudTranslation;
59
60#[derive(Component)]
61struct HudFps;
62
63#[derive(Component)]
64struct HudFov;
65
66#[derive(Component)]
67struct HudSpeed;
68
69fn spawn(mut commands: Commands, hud: Query<(), With<Hud>>, config: Res<PrankConfig>) {
70 if !hud.is_empty() {
71 return;
72 }
73 let Some(config) = config.hud3d.clone() else {
74 return;
75 };
76
77 commands
78 .spawn((
79 Name::new("Hud"),
80 Hud,
81 NodeBundle {
82 background_color: config.background_color,
83 style: Style {
84 position_type: PositionType::Absolute,
85 bottom: Val::Px(0.0),
86 width: Val::Vw(100.0),
87 height: config.height,
88 padding: UiRect::horizontal(Val::Px(5.0)),
89 column_gap: Val::Px(20.0),
90 align_items: AlignItems::Center,
91 ..default()
92 },
93 ..default()
94 },
95 ))
96 .with_children(|parent| {
97 parent.spawn((
98 Name::new("HudTranslation"),
99 HudTranslation,
100 TextBundle::from_section("", config.text_style.clone()),
101 ));
102
103 parent.spawn((
104 Name::new("HudFps"),
105 HudFps,
106 TextBundle::from_section("", config.text_style.clone()),
107 ));
108
109 parent.spawn((
110 Name::new("HudFov"),
111 HudFov,
112 TextBundle::from_section("", config.text_style.clone()),
113 ));
114
115 parent.spawn((
116 Name::new("HudSpeed"),
117 HudSpeed,
118 TextBundle::from_section("", config.text_style.clone()),
119 ));
120 });
121}
122
123fn despawn(mut commands: Commands, hud: Query<Entity, With<Hud>>) {
124 let Ok(entity) = hud.get_single() else {
125 return;
126 };
127
128 commands.entity(entity).despawn_recursive();
129}
130
131fn sync_translation(
132 mut hud_translation: Query<&mut Text, With<HudTranslation>>,
133 active: Res<Prank3dActive>,
134 pranks: Query<&Prank3d>,
135) {
136 let Ok(mut text) = hud_translation.get_single_mut() else {
137 return;
138 };
139 let Some(entity) = active.0 else {
140 return;
141 };
142 let Ok(prank) = pranks.get(entity) else {
143 return;
144 };
145
146 let Vec3 { x, y, z } = prank.translation;
147 text.sections[0].value = format!("Translation: [{:.2}, {:.2}, {:.2}]", x, y, z);
148}
149
150fn sync_fps(mut hud_fps: Query<&mut Text, With<HudFps>>, time: Res<Time>) {
151 let Ok(mut text) = hud_fps.get_single_mut() else {
152 return;
153 };
154
155 text.sections[0].value = format!("FPS: {:.0}", time.delta_seconds().recip());
156}
157
158fn sync_fov(
159 mut hud_fov: Query<&mut Text, With<HudFov>>,
160 active: Res<Prank3dActive>,
161 pranks: Query<&Projection, With<Prank3d>>,
162) {
163 let Ok(mut text) = hud_fov.get_single_mut() else {
164 return;
165 };
166 let Some(entity) = active.0 else {
167 return;
168 };
169 let Ok(projection) = pranks.get(entity) else {
170 return;
171 };
172
173 text.sections[0].value = match projection {
174 Projection::Perspective(projection) => format!("FOV: {:.0}", projection.fov.to_degrees()),
175 Projection::Orthographic(projection) => format!("SCALE: {:.2}", projection.scale),
176 };
177}
178
179fn sync_speed(
180 mut hud_speed: Query<&mut Text, With<HudSpeed>>,
181 active: Res<Prank3dActive>,
182 pranks: Query<&Prank3d>,
183) {
184 let Ok(mut text) = hud_speed.get_single_mut() else {
185 return;
186 };
187 let Some(entity) = active.0 else {
188 return;
189 };
190 let Ok(prank) = pranks.get(entity) else {
191 return;
192 };
193
194 text.sections[0].value = format!("Speed Scalar: {:.1}", prank.speed_scalar);
195}