1pub mod components;
2pub mod helper;
3pub mod tests;
4
5use crate::utils::*;
6use crate::widgets::*;
7use crate::widgets::text::base_text::*;
8use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
9use bevy::prelude::*;
10
11pub(crate) use components::*;
12pub(crate) use helper::*;
13use famiq_macros::set_widget_attributes;
14
15use super::color::{GREEN_COLOR, WHITE_COLOR, WARNING_COLOR, DANGER_COLOR};
16
17const DEFAULT_FPS_TEXT_SIZE: f32 = 18.0;
18
19#[set_widget_attributes]
20#[derive(Clone, Debug)]
21pub struct FpsBuilder {
22 pub change_color: RVal,
23 pub right_side: RVal,
24 pub all_reactive_keys: Vec<String>,
25 pub root_node: Entity,
26 pub count_text_entity: Option<Entity>
27}
28
29impl FpsBuilder {
30 pub fn new(root_node: Entity, font_handle: &Handle<Font>) -> Self {
31 let mut attributes = WidgetAttributes::default();
32 attributes.font_handle = Some(font_handle.clone());
33 Self {
34 attributes,
35 root_node,
36 all_reactive_keys: Vec::new(),
37 cloned_attrs: WidgetAttributes::default(),
38 change_color: RVal::Bool(true),
39 right_side: RVal::Bool(false),
40 count_text_entity: None
41 }
42 }
43
44 pub fn build_fps_count_text(&mut self, commands: &mut Commands) -> Entity {
45 let text_font = TextFont {
46 font: self.cloned_attrs.font_handle.clone().unwrap(),
47 font_size: DEFAULT_FPS_TEXT_SIZE,
48 ..default()
49 };
50 let entity = commands
51 .spawn((
52 TextSpan::default(),
53 text_font.clone(),
54 TextColor(WHITE_COLOR),
55 IsFPSTextCount,
56 DefaultTextSpanConfig::new(
57 TextSpan::default(),
58 text_font,
59 TextColor(WHITE_COLOR)
60 )
61 ))
62 .id();
63 insert_class_id(commands, entity, &self.cloned_attrs.id, &self.cloned_attrs.class);
64 self.count_text_entity = Some(entity);
65 entity
66 }
67
68 pub fn rebuild_count_text(&mut self, world: &mut World) {
69 insert_class_id_world(
70 world,
71 self.count_text_entity.unwrap(),
72 &self.cloned_attrs.id,
73 &self.cloned_attrs.class
74 );
75 }
76
77 pub(crate) fn update_fps_count_system(
79 diagnostics: Res<DiagnosticsStore>,
80 mut text_q: Query<(&mut TextSpan, &mut TextColor, &CanChangeColor, &IsFPSTextCount)>
81 ) {
82 for (mut text, mut color, change_color, _) in text_q.iter_mut() {
83 if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
84 if let Some(value) = fps.smoothed() {
85 let value = value as usize;
86 text.0 = format!("{value}");
87
88 if change_color.0 {
89 if value >= 100 {
90 color.0 = GREEN_COLOR;
91 }
92 else if value >= 60 && value < 100 {
93 color.0 = WARNING_COLOR;
94 }
95 else {
96 color.0 = DANGER_COLOR;
97 }
98 }
99 }
100 }
101 }
102 }
103
104 pub fn set_node_right_side(&mut self, state: bool) {
105 if state {
106 self.cloned_attrs.node.left = Val::Auto;
107 self.cloned_attrs.node.right = Val::Px(6.0);
108 } else {
109 self.cloned_attrs.node.right = Val::Auto;
110 self.cloned_attrs.node.left = Val::Px(6.0);
111 }
112 }
113
114 pub(crate) fn handle_side_val(&mut self, r_data: &HashMap<String, RVal>) {
115 match self.right_side.to_owned() {
116 RVal::Bool(v) => self.set_node_right_side(v),
117 RVal::Str(v) => {
118 let reactive_keys = get_reactive_key(&v);
119 for key in reactive_keys.iter() {
120 if let Some(r_v) = r_data.get(key) {
121 match r_v {
122 RVal::Bool(state) => self.set_node_right_side(*state),
123 _ => {}
124 }
125 }
126 }
127 self.all_reactive_keys.extend_from_slice(&reactive_keys);
128 }
129 _ => {}
130 }
131 }
132
133 pub(crate) fn prepar_attrs(&mut self, r_data: &HashMap<String, RVal>) {
134 self.cloned_attrs = self.attributes.clone();
135 self.cloned_attrs.node = default_fps_container_node();
136 self.cloned_attrs.override_text_size = Some(DEFAULT_FPS_TEXT_SIZE);
137 self.cloned_attrs.default_visibility = Visibility::Visible;
138 self.handle_side_val(r_data);
139 replace_reactive_keys_common_attrs(&mut self.cloned_attrs, r_data, &mut self.all_reactive_keys);
140 }
141}
142
143impl SetupWidget for FpsBuilder {
144 fn components(&mut self) -> impl Bundle {
145 let mut style_components = BaseStyleComponents::default();
146 style_components.node = self.cloned_attrs.node.clone();
147 self._process_built_in_spacing_class();
148 (
149 MainWidget,
150 IsFPSTextLabel,
151 GlobalZIndex(6),
152 style_components.clone(),
153 DefaultWidgetConfig::from(style_components),
154 ReactiveWidget
155 )
156 }
157
158 fn build(&mut self, r_data: &HashMap<String, RVal>, commands: &mut Commands) -> Entity {
159 self.prepar_attrs(r_data);
160 let mut label = FaBaseText::new_with_attributes("FPS:", &self.cloned_attrs);
161 label.use_get_color = true;
162
163 let label_entity = label.build(r_data, commands);
164 let count_entity = self.build_fps_count_text(commands);
165
166 match self.change_color.to_owned() {
167 RVal::Bool(v) => {
168 commands.entity(count_entity).insert(CanChangeColor(v));
169 }
170 RVal::Str(v) => {
171 let reactive_keys = get_reactive_key(&v);
172 for key in reactive_keys.iter() {
173 if let Some(r_v) = r_data.get(key) {
174 match r_v {
175 RVal::Bool(state) => {
176 commands.entity(count_entity).insert(CanChangeColor(*state));
177 }
178 _ => {}
179 }
180 }
181 }
182 self.all_reactive_keys.extend_from_slice(&reactive_keys);
183 }
184 _ => {}
185 }
186
187 commands.entity(label_entity).add_child(count_entity).insert(self.components());
188 commands.entity(self.root_node).add_child(label_entity);
189 insert_class_id(commands, label_entity, &self.attributes.id, &self.attributes.class);
190
191 let cloned_builder = self.clone();
192 let ar_keys = self.all_reactive_keys.clone();
193 commands.queue(move |w: &mut World| {
194 w.send_event(UpdateReactiveSubscriberEvent::new(
195 ar_keys,
196 label_entity,
197 WidgetBuilder {
198 builder: BuilderType::Fps(cloned_builder)
199 }
200 ));
201 });
202 self.all_reactive_keys.clear();
203 label_entity
204 }
205
206 fn rebuild(&mut self, r_data: &HashMap<String, RVal>, old_entity: Entity, world: &mut World) {
207 self.prepar_attrs(r_data);
208 let mut label = FaBaseText::new_with_attributes("FPS:", &self.cloned_attrs);
209 label.use_get_color = true;
210 label.rebuild(r_data, old_entity, world);
211 self.rebuild_count_text(world);
212
213 match self.change_color.to_owned() {
214 RVal::Bool(v) => {
215 world.entity_mut(self.count_text_entity.unwrap()).insert(CanChangeColor(v));
216 }
217 RVal::Str(v) => {
218 let reactive_keys = get_reactive_key(&v);
219 for key in reactive_keys.iter() {
220 if let Some(r_v) = r_data.get(key) {
221 match r_v {
222 RVal::Bool(state) => {
223 world.entity_mut(self.count_text_entity.unwrap()).insert(CanChangeColor(*state));
224 }
225 _ => {}
226 }
227 }
228 }
229 self.all_reactive_keys.extend_from_slice(&reactive_keys);
230 }
231 _ => {}
232 }
233 world.entity_mut(old_entity).insert(self.components());
234 insert_class_id_world(world, old_entity, &self.attributes.id, &self.attributes.class);
235
236 let cloned_builder = self.clone();
237 let ar_keys = self.all_reactive_keys.clone();
238 world.send_event(UpdateReactiveSubscriberEvent::new(
239 ar_keys,
240 old_entity,
241 WidgetBuilder {
242 builder: BuilderType::Fps(cloned_builder)
243 }
244 ));
245 self.all_reactive_keys.clear();
246 }
247}
248
249#[macro_export]
251macro_rules! fps {
252 ( $( $key:ident : $value:tt ),* $(,)? ) => {{
253 let famiq_builder = builder_mut();
254 let root_entity = famiq_builder.resource.root_node_entity.unwrap();
255 let f_builder = &mut FpsBuilder::new(root_entity, &famiq_builder.get_font_handle());
256 $(
257 $crate::fps_text_attributes!(f_builder, $key : $value);
258 )*
259 f_builder.build(
260 &famiq_builder.reactive_data.data,
261 &mut famiq_builder.ui_root_node.commands()
262 )
263 }};
264}
265
266#[macro_export]
267macro_rules! fps_text_attributes {
268 ($f_builder:ident, change_color: $change_color:expr) => {{
269 match to_rval($change_color) {
270 Ok(v) => $f_builder.change_color = v,
271 Err(_) => panic!("\nchange_color attribute accepts only bool and reactive string\n")
272 }
273 }};
274 ($f_builder:ident, right_side: $right_side:expr) => {{
275 match to_rval($right_side) {
276 Ok(v) => $f_builder.right_side = v,
277 Err(_) => panic!("\right_side attribute accepts only bool and reactive string\n")
278 }
279 }};
280 ($f_builder:ident, $key:ident : $value:expr) => {{
281 $crate::common_attributes!($f_builder, $key : $value);
282 }};
283}
284
285pub fn can_run_fps_systems(fps_q: Query<&IsFPSTextLabel>) -> bool {
289 !fps_q.is_empty()
290}