1use super::*;
2
3pub fn mark_subtree_dirty(widget: &mut dyn Widget) {
11 widget.mark_dirty();
12 for child in widget.children_mut().iter_mut() {
13 mark_subtree_dirty(child.as_mut());
14 }
15}
16
17pub fn hit_test_subtree(widget: &dyn Widget, local_pos: Point) -> Option<Vec<usize>> {
26 if !widget.is_visible() || !widget.hit_test(local_pos) {
27 return None;
28 }
29 if widget.claims_pointer_exclusively(local_pos) {
32 return Some(vec![]);
33 }
34 for (i, child) in widget.children().iter().enumerate().rev() {
36 let child_local = Point::new(
37 local_pos.x - child.bounds().x,
38 local_pos.y - child.bounds().y,
39 );
40 if let Some(mut sub_path) = hit_test_subtree(child.as_ref(), child_local) {
41 sub_path.insert(0, i);
42 return Some(sub_path);
43 }
44 }
45 Some(vec![]) }
47
48pub fn active_modal_path(widget: &dyn Widget) -> Option<Vec<usize>> {
52 if !widget.is_visible() {
53 return None;
54 }
55 for (i, child) in widget.children().iter().enumerate().rev() {
56 if let Some(mut sub_path) = active_modal_path(child.as_ref()) {
57 sub_path.insert(0, i);
58 return Some(sub_path);
59 }
60 }
61 if widget.has_active_modal() {
62 Some(vec![])
63 } else {
64 None
65 }
66}
67
68pub fn global_overlay_hit_path(widget: &dyn Widget, local_pos: Point) -> Option<Vec<usize>> {
74 if !widget.is_visible() {
75 return None;
76 }
77 for (i, child) in widget.children().iter().enumerate().rev() {
78 let child_local = Point::new(
79 local_pos.x - child.bounds().x,
80 local_pos.y - child.bounds().y,
81 );
82 if let Some(mut sub_path) = global_overlay_hit_path(child.as_ref(), child_local) {
83 sub_path.insert(0, i);
84 return Some(sub_path);
85 }
86 }
87 if widget.hit_test_global_overlay(local_pos) {
88 Some(vec![])
89 } else {
90 None
91 }
92}
93
94pub fn dispatch_event(
100 root: &mut Box<dyn Widget>,
101 path: &[usize],
102 event: &Event,
103 pos_in_root: Point,
104) -> EventResult {
105 if path.is_empty() {
106 let before = crate::animation::invalidation_epoch();
107 let result = root.on_event(event);
108 if result == EventResult::Consumed || before != crate::animation::invalidation_epoch() {
109 root.mark_dirty();
110 }
111 return result;
112 }
113 let idx = path[0];
114 if idx >= root.children().len() {
119 return root.on_event(event);
120 }
121 let child_bounds = root.children()[idx].bounds();
122 let child_pos = Point::new(
123 pos_in_root.x - child_bounds.x,
124 pos_in_root.y - child_bounds.y,
125 );
126 let translated_event = translate_event(event, child_pos);
127
128 let before_child = crate::animation::invalidation_epoch();
129 let child_result = dispatch_event(
130 &mut root.children_mut()[idx],
131 &path[1..],
132 &translated_event,
133 child_pos,
134 );
135 if child_result == EventResult::Consumed {
136 root.mark_dirty();
137 return EventResult::Consumed;
138 }
139 if before_child != crate::animation::invalidation_epoch() {
140 root.mark_dirty();
141 }
142 let before_self = crate::animation::invalidation_epoch();
144 let result = root.on_event(event);
145 if result == EventResult::Consumed || before_self != crate::animation::invalidation_epoch() {
146 root.mark_dirty();
147 }
148 result
149}
150
151pub fn dispatch_unconsumed_key(
155 widget: &mut dyn Widget,
156 key: &Key,
157 modifiers: Modifiers,
158) -> EventResult {
159 if !widget.is_visible() {
160 return EventResult::Ignored;
161 }
162 for child in widget.children_mut().iter_mut().rev() {
163 if dispatch_unconsumed_key(child.as_mut(), key, modifiers) == EventResult::Consumed {
164 widget.mark_dirty();
165 return EventResult::Consumed;
166 }
167 }
168 let before = crate::animation::invalidation_epoch();
169 let result = widget.on_unconsumed_key(key, modifiers);
170 if result == EventResult::Consumed || before != crate::animation::invalidation_epoch() {
171 widget.mark_dirty();
172 }
173 result
174}
175
176fn translate_event(event: &Event, new_pos: Point) -> Event {
179 match event {
180 Event::MouseMove { .. } => Event::MouseMove { pos: new_pos },
181 Event::MouseDown {
182 button, modifiers, ..
183 } => Event::MouseDown {
184 pos: new_pos,
185 button: *button,
186 modifiers: *modifiers,
187 },
188 Event::MouseUp {
189 button, modifiers, ..
190 } => Event::MouseUp {
191 pos: new_pos,
192 button: *button,
193 modifiers: *modifiers,
194 },
195 Event::MouseWheel {
196 delta_y,
197 delta_x,
198 modifiers,
199 ..
200 } => Event::MouseWheel {
201 pos: new_pos,
202 delta_y: *delta_y,
203 delta_x: *delta_x,
204 modifiers: *modifiers,
205 },
206 other => other.clone(),
207 }
208}
209
210#[derive(Clone)]
216pub struct InspectorNode {
217 pub type_name: &'static str,
218 pub screen_bounds: Rect,
220 pub margin: crate::layout_props::Insets,
223 pub padding: crate::layout_props::Insets,
227 pub h_anchor: crate::layout_props::HAnchor,
229 pub v_anchor: crate::layout_props::VAnchor,
231 pub depth: usize,
232 pub path: Vec<usize>,
236 pub properties: Vec<(&'static str, String)>,
238}
239
240#[cfg(feature = "reflect")]
245pub fn reflect_fields(reflected: &dyn bevy_reflect::Reflect) -> Vec<(&'static str, String)> {
246 use bevy_reflect::{ReflectRef, TypeInfo};
247 let mut out = Vec::new();
248 if let ReflectRef::Struct(s) = reflected.reflect_ref() {
249 let names: Vec<&'static str> = if let Some(TypeInfo::Struct(info)) =
255 reflected.get_represented_type_info()
256 {
257 (0..s.field_len())
258 .map(|i| info.field_at(i).map(|f| f.name()).unwrap_or(""))
259 .collect()
260 } else {
261 vec![""; s.field_len()]
262 };
263 for i in 0..s.field_len() {
264 let name = names.get(i).copied().unwrap_or("");
265 if name.is_empty() {
266 continue;
267 }
268 if let Some(field) = s.field_at(i) {
269 out.push((name, format_reflect_value(field)));
270 }
271 }
272 }
273 out
274}
275
276#[cfg(feature = "reflect")]
277fn format_reflect_value(value: &dyn bevy_reflect::PartialReflect) -> String {
278 if let Some(v) = value.try_downcast_ref::<bool>() {
283 return v.to_string();
284 }
285 if let Some(v) = value.try_downcast_ref::<f64>() {
286 return format!("{v:.3}");
287 }
288 if let Some(v) = value.try_downcast_ref::<f32>() {
289 return format!("{v:.3}");
290 }
291 if let Some(v) = value.try_downcast_ref::<i32>() {
292 return v.to_string();
293 }
294 if let Some(v) = value.try_downcast_ref::<u32>() {
295 return v.to_string();
296 }
297 if let Some(v) = value.try_downcast_ref::<usize>() {
298 return v.to_string();
299 }
300 if let Some(v) = value.try_downcast_ref::<String>() {
301 return format!("\"{v}\"");
302 }
303 if let Some(v) = value.try_downcast_ref::<crate::color::Color>() {
304 return format!(
305 "rgba({:.2}, {:.2}, {:.2}, {:.2})",
306 v.r, v.g, v.b, v.a
307 );
308 }
309 format!("{value:?}")
311}
312
313#[derive(Clone, Copy, Debug, PartialEq)]
317pub struct InspectorOverlay {
318 pub bounds: Rect,
319 pub margin: crate::layout_props::Insets,
320 pub padding: crate::layout_props::Insets,
321}
322
323thread_local! {
327 static CURRENT_MOUSE_WORLD: std::cell::Cell<Option<Point>> =
328 std::cell::Cell::new(None);
329 static CURRENT_VIEWPORT: std::cell::Cell<Size> =
330 std::cell::Cell::new(Size::new(1.0, 1.0));
331}
332
333pub fn set_current_mouse_world(p: Point) {
336 CURRENT_MOUSE_WORLD.with(|c| c.set(Some(p)));
337}
338
339pub fn current_mouse_world() -> Option<Point> {
346 CURRENT_MOUSE_WORLD.with(|c| c.get())
347}
348
349pub fn set_current_viewport(s: Size) {
351 CURRENT_VIEWPORT.with(|c| c.set(s));
352}
353
354pub fn current_viewport() -> Size {
356 CURRENT_VIEWPORT.with(|c| c.get())
357}
358
359pub fn find_widget_by_id<'a>(widget: &'a dyn Widget, id: &str) -> Option<&'a dyn Widget> {
364 if widget.id() == Some(id) {
365 return Some(widget);
366 }
367 for child in widget.children() {
368 if let Some(found) = find_widget_by_id(child.as_ref(), id) {
369 return Some(found);
370 }
371 }
372 None
373}
374
375pub fn find_widget_by_id_mut<'a>(
379 widget: &'a mut dyn Widget,
380 id: &str,
381) -> Option<&'a mut dyn Widget> {
382 if widget.id() == Some(id) {
383 return Some(widget);
384 }
385 for child in widget.children_mut().iter_mut() {
386 if let Some(found) = find_widget_by_id_mut(child.as_mut(), id) {
387 return Some(found);
388 }
389 }
390 None
391}
392
393pub fn find_widget_by_type<'a>(widget: &'a dyn Widget, type_name: &str) -> Option<&'a dyn Widget> {
398 if widget.type_name() == type_name {
399 return Some(widget);
400 }
401 for child in widget.children() {
402 if let Some(found) = find_widget_by_type(child.as_ref(), type_name) {
403 return Some(found);
404 }
405 }
406 None
407}
408
409pub fn collect_inspector_nodes(
414 widget: &dyn Widget,
415 depth: usize,
416 screen_origin: Point,
417 out: &mut Vec<InspectorNode>,
418) {
419 collect_inspector_nodes_with_path(widget, depth, screen_origin, &[], out);
420}
421
422fn collect_inspector_nodes_with_path(
423 widget: &dyn Widget,
424 depth: usize,
425 screen_origin: Point,
426 path_prefix: &[usize],
427 out: &mut Vec<InspectorNode>,
428) {
429 if !widget.is_visible() {
432 return;
433 }
434 if !widget.show_in_inspector() {
436 return;
437 }
438
439 let b = widget.bounds();
440 let abs = Rect::new(
441 screen_origin.x + b.x,
442 screen_origin.y + b.y,
443 b.width,
444 b.height,
445 );
446 let mut props = vec![(
450 "backbuffer",
451 if widget.has_backbuffer() {
452 "true".to_string()
453 } else {
454 "false".to_string()
455 },
456 )];
457 props.extend(widget.properties());
458 #[cfg(feature = "reflect")]
465 if let Some(reflected) = widget.as_reflect() {
466 props.extend(reflect_fields(reflected));
467 }
468 let (h_anchor, v_anchor) = widget
469 .widget_base()
470 .map(|b| (b.h_anchor, b.v_anchor))
471 .unwrap_or((
472 crate::layout_props::HAnchor::FIT,
473 crate::layout_props::VAnchor::FIT,
474 ));
475 out.push(InspectorNode {
476 type_name: widget.type_name(),
477 screen_bounds: abs,
478 margin: widget.margin(),
479 padding: widget.padding(),
480 h_anchor,
481 v_anchor,
482 depth,
483 path: path_prefix.to_vec(),
484 properties: props,
485 });
486
487 if !widget.contributes_children_to_inspector() {
492 return;
493 }
494
495 let child_origin = Point::new(abs.x, abs.y);
496 let mut child_path: Vec<usize> = Vec::with_capacity(path_prefix.len() + 1);
497 child_path.extend_from_slice(path_prefix);
498 child_path.push(0);
499 for (i, child) in widget.children().iter().enumerate() {
500 *child_path.last_mut().unwrap() = i;
501 collect_inspector_nodes_with_path(
502 child.as_ref(),
503 depth + 1,
504 child_origin,
505 &child_path,
506 out,
507 );
508 }
509}
510
511pub fn walk_path_mut<'a>(
516 root: &'a mut dyn Widget,
517 path: &[usize],
518) -> Option<&'a mut dyn Widget> {
519 let mut node: &mut dyn Widget = root;
520 for &idx in path {
521 let children = node.children_mut();
522 if idx >= children.len() {
523 return None;
524 }
525 node = children[idx].as_mut();
526 }
527 Some(node)
528}
529
530#[cfg(feature = "reflect")]
537pub struct InspectorEdit {
538 pub path: Vec<usize>,
539 pub field_path: String,
542 pub new_value: Box<dyn bevy_reflect::PartialReflect>,
544}
545
546#[cfg(feature = "reflect")]
547impl std::fmt::Debug for InspectorEdit {
548 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549 f.debug_struct("InspectorEdit")
550 .field("path", &self.path)
551 .field("field_path", &self.field_path)
552 .finish_non_exhaustive()
553 }
554}
555
556#[derive(Clone, Debug)]
563pub enum WidgetBaseField {
564 MarginLeft(f64),
565 MarginRight(f64),
566 MarginTop(f64),
567 MarginBottom(f64),
568 HAnchor(crate::layout_props::HAnchor),
569 VAnchor(crate::layout_props::VAnchor),
570 MinWidth(f64),
571 MinHeight(f64),
572 MaxWidth(f64),
573 MaxHeight(f64),
574}
575
576#[derive(Clone, Debug)]
579pub struct WidgetBaseEdit {
580 pub path: Vec<usize>,
582 pub field: WidgetBaseField,
583}
584
585pub fn apply_widget_base_edit(root: &mut dyn Widget, edit: &WidgetBaseEdit) -> bool {
589 let Some(target) = walk_path_mut(root, &edit.path) else {
590 return false;
591 };
592 let Some(base) = target.widget_base_mut() else {
593 return false;
594 };
595 match &edit.field {
596 WidgetBaseField::MarginLeft(v) => base.margin.left = *v,
597 WidgetBaseField::MarginRight(v) => base.margin.right = *v,
598 WidgetBaseField::MarginTop(v) => base.margin.top = *v,
599 WidgetBaseField::MarginBottom(v) => base.margin.bottom = *v,
600 WidgetBaseField::HAnchor(a) => base.h_anchor = *a,
601 WidgetBaseField::VAnchor(a) => base.v_anchor = *a,
602 WidgetBaseField::MinWidth(v) => base.min_size.width = v.max(0.0),
603 WidgetBaseField::MinHeight(v) => base.min_size.height = v.max(0.0),
604 WidgetBaseField::MaxWidth(v) => base.max_size.width = v.max(0.0),
605 WidgetBaseField::MaxHeight(v) => base.max_size.height = v.max(0.0),
606 }
607 target.mark_dirty();
608 crate::animation::request_draw();
609 true
610}
611
612#[cfg(feature = "reflect")]
616pub fn apply_inspector_edit(root: &mut dyn Widget, edit: &InspectorEdit) -> bool {
617 use bevy_reflect::{GetPath, PartialReflect};
618 let Some(target) = walk_path_mut(root, &edit.path) else {
619 return false;
620 };
621 let applied;
622 {
623 let Some(reflected) = target.as_reflect_mut() else {
624 return false;
625 };
626 let Ok(field) = reflected.reflect_path_mut(edit.field_path.as_str()) else {
627 return false;
628 };
629 let field: &mut dyn PartialReflect = field;
630 applied = field.try_apply(edit.new_value.as_ref()).is_ok();
631 }
632 if applied {
636 target.mark_dirty();
637 crate::animation::request_draw();
638 }
639 applied
640}