1use super::*;
19
20pub fn mark_subtree_dirty(widget: &mut dyn Widget) {
28 widget.mark_dirty();
29 for child in widget.children_mut().iter_mut() {
30 mark_subtree_dirty(child.as_mut());
31 }
32}
33
34pub fn hit_test_subtree(widget: &dyn Widget, local_pos: Point) -> Option<Vec<usize>> {
43 if !widget.is_visible() || !widget.hit_test(local_pos) {
44 return None;
45 }
46 if widget.claims_pointer_exclusively(local_pos) {
49 return Some(vec![]);
50 }
51 for (i, child) in widget.children().iter().enumerate().rev() {
53 let child_local = Point::new(
54 local_pos.x - child.bounds().x,
55 local_pos.y - child.bounds().y,
56 );
57 if let Some(mut sub_path) = hit_test_subtree(child.as_ref(), child_local) {
58 sub_path.insert(0, i);
59 return Some(sub_path);
60 }
61 }
62 Some(vec![]) }
64
65pub fn active_modal_path(widget: &dyn Widget) -> Option<Vec<usize>> {
69 if !widget.is_visible() {
70 return None;
71 }
72 for (i, child) in widget.children().iter().enumerate().rev() {
73 if let Some(mut sub_path) = active_modal_path(child.as_ref()) {
74 sub_path.insert(0, i);
75 return Some(sub_path);
76 }
77 }
78 if widget.has_active_modal() {
79 Some(vec![])
80 } else {
81 None
82 }
83}
84
85pub fn global_overlay_hit_path(widget: &dyn Widget, local_pos: Point) -> Option<Vec<usize>> {
91 if !widget.is_visible() {
92 return None;
93 }
94 for (i, child) in widget.children().iter().enumerate().rev() {
95 let child_local = Point::new(
96 local_pos.x - child.bounds().x,
97 local_pos.y - child.bounds().y,
98 );
99 if let Some(mut sub_path) = global_overlay_hit_path(child.as_ref(), child_local) {
100 sub_path.insert(0, i);
101 return Some(sub_path);
102 }
103 }
104 if widget.hit_test_global_overlay(local_pos) {
105 Some(vec![])
106 } else {
107 None
108 }
109}
110
111pub fn dispatch_event(
117 root: &mut Box<dyn Widget>,
118 path: &[usize],
119 event: &Event,
120 pos_in_root: Point,
121) -> EventResult {
122 if path.is_empty() {
123 let before = crate::animation::invalidation_epoch();
124 let result = root.on_event(event);
125 if result == EventResult::Consumed || before != crate::animation::invalidation_epoch() {
126 root.mark_dirty();
127 }
128 return result;
129 }
130 let idx = path[0];
131 if idx >= root.children().len() {
136 return root.on_event(event);
137 }
138 let child_bounds = root.children()[idx].bounds();
139 let child_pos = Point::new(
140 pos_in_root.x - child_bounds.x,
141 pos_in_root.y - child_bounds.y,
142 );
143 let translated_event = translate_event(event, child_pos);
144
145 let before_child = crate::animation::invalidation_epoch();
146 let child_result = dispatch_event(
147 &mut root.children_mut()[idx],
148 &path[1..],
149 &translated_event,
150 child_pos,
151 );
152 if child_result == EventResult::Consumed {
153 root.mark_dirty();
154 return EventResult::Consumed;
155 }
156 if before_child != crate::animation::invalidation_epoch() {
157 root.mark_dirty();
158 }
159 let before_self = crate::animation::invalidation_epoch();
161 let result = root.on_event(event);
162 if result == EventResult::Consumed || before_self != crate::animation::invalidation_epoch() {
163 root.mark_dirty();
164 }
165 result
166}
167
168pub fn dispatch_event_dyn(
174 root: &mut dyn Widget,
175 path: &[usize],
176 event: &Event,
177 pos_in_root: Point,
178) -> EventResult {
179 if path.is_empty() {
180 let before = crate::animation::invalidation_epoch();
181 let result = root.on_event(event);
182 if result == EventResult::Consumed || before != crate::animation::invalidation_epoch() {
183 root.mark_dirty();
184 }
185 return result;
186 }
187 let idx = path[0];
188 if idx >= root.children().len() {
189 return root.on_event(event);
190 }
191 let child_bounds = root.children()[idx].bounds();
192 let child_pos = Point::new(
193 pos_in_root.x - child_bounds.x,
194 pos_in_root.y - child_bounds.y,
195 );
196 let translated_event = translate_event(event, child_pos);
197
198 let before_child = crate::animation::invalidation_epoch();
199 let child_result = dispatch_event(
202 &mut root.children_mut()[idx],
203 &path[1..],
204 &translated_event,
205 child_pos,
206 );
207 if child_result == EventResult::Consumed {
208 root.mark_dirty();
209 return EventResult::Consumed;
210 }
211 if before_child != crate::animation::invalidation_epoch() {
212 root.mark_dirty();
213 }
214 let before_self = crate::animation::invalidation_epoch();
215 let result = root.on_event(event);
216 if result == EventResult::Consumed || before_self != crate::animation::invalidation_epoch() {
217 root.mark_dirty();
218 }
219 result
220}
221
222pub fn dispatch_unconsumed_key(
226 widget: &mut dyn Widget,
227 key: &Key,
228 modifiers: Modifiers,
229) -> EventResult {
230 if !widget.is_visible() {
231 return EventResult::Ignored;
232 }
233 for child in widget.children_mut().iter_mut().rev() {
234 if dispatch_unconsumed_key(child.as_mut(), key, modifiers) == EventResult::Consumed {
235 widget.mark_dirty();
236 return EventResult::Consumed;
237 }
238 }
239 let before = crate::animation::invalidation_epoch();
240 let result = widget.on_unconsumed_key(key, modifiers);
241 if result == EventResult::Consumed || before != crate::animation::invalidation_epoch() {
242 widget.mark_dirty();
243 }
244 result
245}
246
247fn translate_event(event: &Event, new_pos: Point) -> Event {
250 match event {
251 Event::MouseMove { .. } => Event::MouseMove { pos: new_pos },
252 Event::MouseDown {
253 button, modifiers, ..
254 } => Event::MouseDown {
255 pos: new_pos,
256 button: *button,
257 modifiers: *modifiers,
258 },
259 Event::MouseUp {
260 button, modifiers, ..
261 } => Event::MouseUp {
262 pos: new_pos,
263 button: *button,
264 modifiers: *modifiers,
265 },
266 Event::MouseWheel {
267 delta_y,
268 delta_x,
269 modifiers,
270 ..
271 } => Event::MouseWheel {
272 pos: new_pos,
273 delta_y: *delta_y,
274 delta_x: *delta_x,
275 modifiers: *modifiers,
276 },
277 other => other.clone(),
278 }
279}
280
281#[derive(Clone)]
287pub struct InspectorNode {
288 pub type_name: &'static str,
289 pub screen_bounds: Rect,
291 pub margin: crate::layout_props::Insets,
294 pub padding: crate::layout_props::Insets,
298 pub h_anchor: crate::layout_props::HAnchor,
300 pub v_anchor: crate::layout_props::VAnchor,
302 pub depth: usize,
303 pub path: Vec<usize>,
307 pub properties: Vec<(&'static str, String)>,
309}
310
311#[cfg(feature = "reflect")]
316pub fn reflect_fields(reflected: &dyn bevy_reflect::Reflect) -> Vec<(&'static str, String)> {
317 use bevy_reflect::{ReflectRef, TypeInfo};
318 let mut out = Vec::new();
319 if let ReflectRef::Struct(s) = reflected.reflect_ref() {
320 let names: Vec<&'static str> =
326 if let Some(TypeInfo::Struct(info)) = reflected.get_represented_type_info() {
327 (0..s.field_len())
328 .map(|i| info.field_at(i).map(|f| f.name()).unwrap_or(""))
329 .collect()
330 } else {
331 vec![""; s.field_len()]
332 };
333 for i in 0..s.field_len() {
334 let name = names.get(i).copied().unwrap_or("");
335 if name.is_empty() {
336 continue;
337 }
338 if let Some(field) = s.field_at(i) {
339 out.push((name, format_reflect_value(field)));
340 }
341 }
342 }
343 out
344}
345
346#[cfg(feature = "reflect")]
347fn format_reflect_value(value: &dyn bevy_reflect::PartialReflect) -> String {
348 if let Some(v) = value.try_downcast_ref::<bool>() {
353 return v.to_string();
354 }
355 if let Some(v) = value.try_downcast_ref::<f64>() {
356 return format!("{v:.3}");
357 }
358 if let Some(v) = value.try_downcast_ref::<f32>() {
359 return format!("{v:.3}");
360 }
361 if let Some(v) = value.try_downcast_ref::<i32>() {
362 return v.to_string();
363 }
364 if let Some(v) = value.try_downcast_ref::<u32>() {
365 return v.to_string();
366 }
367 if let Some(v) = value.try_downcast_ref::<usize>() {
368 return v.to_string();
369 }
370 if let Some(v) = value.try_downcast_ref::<String>() {
371 return format!("\"{v}\"");
372 }
373 if let Some(v) = value.try_downcast_ref::<crate::color::Color>() {
374 return format!("rgba({:.2}, {:.2}, {:.2}, {:.2})", v.r, v.g, v.b, v.a);
375 }
376 format!("{value:?}")
378}
379
380#[derive(Clone, Copy, Debug, PartialEq)]
384pub struct InspectorOverlay {
385 pub bounds: Rect,
386 pub margin: crate::layout_props::Insets,
387 pub padding: crate::layout_props::Insets,
388}
389
390thread_local! {
394 static CURRENT_MOUSE_WORLD: std::cell::Cell<Option<Point>> =
395 std::cell::Cell::new(None);
396 static CURRENT_VIEWPORT: std::cell::Cell<Size> =
397 std::cell::Cell::new(Size::new(1.0, 1.0));
398}
399
400pub fn set_current_mouse_world(p: Point) {
403 CURRENT_MOUSE_WORLD.with(|c| c.set(Some(p)));
404}
405
406pub fn current_mouse_world() -> Option<Point> {
413 CURRENT_MOUSE_WORLD.with(|c| c.get())
414}
415
416pub fn set_current_viewport(s: Size) {
418 CURRENT_VIEWPORT.with(|c| c.set(s));
419}
420
421pub fn current_viewport() -> Size {
423 CURRENT_VIEWPORT.with(|c| c.get())
424}
425
426pub fn find_widget_by_id<'a>(widget: &'a dyn Widget, id: &str) -> Option<&'a dyn Widget> {
431 if widget.id() == Some(id) {
432 return Some(widget);
433 }
434 for child in widget.children() {
435 if let Some(found) = find_widget_by_id(child.as_ref(), id) {
436 return Some(found);
437 }
438 }
439 None
440}
441
442pub fn find_widget_by_id_mut<'a>(
446 widget: &'a mut dyn Widget,
447 id: &str,
448) -> Option<&'a mut dyn Widget> {
449 if widget.id() == Some(id) {
450 return Some(widget);
451 }
452 for child in widget.children_mut().iter_mut() {
453 if let Some(found) = find_widget_by_id_mut(child.as_mut(), id) {
454 return Some(found);
455 }
456 }
457 None
458}
459
460pub fn find_widget_by_type<'a>(widget: &'a dyn Widget, type_name: &str) -> Option<&'a dyn Widget> {
465 if widget.type_name() == type_name {
466 return Some(widget);
467 }
468 for child in widget.children() {
469 if let Some(found) = find_widget_by_type(child.as_ref(), type_name) {
470 return Some(found);
471 }
472 }
473 None
474}
475
476pub fn collect_inspector_nodes(
485 widget: &dyn Widget,
486 depth: usize,
487 screen_origin: Point,
488 out: &mut Vec<InspectorNode>,
489) {
490 let mut parent_to_screen = crate::TransAffine::new();
491 parent_to_screen.translate(screen_origin.x, screen_origin.y);
492 collect_inspector_nodes_with_path(widget, depth, &parent_to_screen, &[], out);
493}
494
495fn transform_rect_aabb(t: &crate::TransAffine, rect: Rect) -> Rect {
499 let corners = [
500 (rect.x, rect.y),
501 (rect.x + rect.width, rect.y),
502 (rect.x, rect.y + rect.height),
503 (rect.x + rect.width, rect.y + rect.height),
504 ];
505 let mut min_x = f64::INFINITY;
506 let mut min_y = f64::INFINITY;
507 let mut max_x = f64::NEG_INFINITY;
508 let mut max_y = f64::NEG_INFINITY;
509 for (mut x, mut y) in corners {
510 t.transform(&mut x, &mut y);
511 if x < min_x {
512 min_x = x;
513 }
514 if x > max_x {
515 max_x = x;
516 }
517 if y < min_y {
518 min_y = y;
519 }
520 if y > max_y {
521 max_y = y;
522 }
523 }
524 Rect::new(
525 min_x,
526 min_y,
527 (max_x - min_x).max(0.0),
528 (max_y - min_y).max(0.0),
529 )
530}
531
532fn effective_scale(t: &crate::TransAffine) -> f64 {
539 let sx = (t.sx * t.sx + t.shy * t.shy).sqrt();
540 let sy = (t.shx * t.shx + t.sy * t.sy).sqrt();
541 if sx > 0.0 && sy > 0.0 {
542 (sx * sy).sqrt()
543 } else {
544 1.0
545 }
546}
547
548fn collect_inspector_nodes_with_path(
549 widget: &dyn Widget,
550 depth: usize,
551 parent_to_screen: &crate::TransAffine,
552 path_prefix: &[usize],
553 out: &mut Vec<InspectorNode>,
554) {
555 if !widget.is_visible() {
558 return;
559 }
560 if !widget.show_in_inspector() {
562 return;
563 }
564
565 let b = widget.bounds();
566 let abs = transform_rect_aabb(parent_to_screen, b);
572 let scale = effective_scale(parent_to_screen);
573 let mut props = vec![(
577 "backbuffer",
578 if widget.has_backbuffer() {
579 "true".to_string()
580 } else {
581 "false".to_string()
582 },
583 )];
584 props.extend(widget.properties());
585 #[cfg(feature = "reflect")]
592 if let Some(reflected) = widget.as_reflect() {
593 props.extend(reflect_fields(reflected));
594 }
595 let (h_anchor, v_anchor) = widget
596 .widget_base()
597 .map(|b| (b.h_anchor, b.v_anchor))
598 .unwrap_or((
599 crate::layout_props::HAnchor::FIT,
600 crate::layout_props::VAnchor::FIT,
601 ));
602 let margin_logical = widget.margin();
603 let padding_logical = widget.padding();
604 let scaled_margin = crate::layout_props::Insets {
605 left: margin_logical.left * scale,
606 right: margin_logical.right * scale,
607 top: margin_logical.top * scale,
608 bottom: margin_logical.bottom * scale,
609 };
610 let scaled_padding = crate::layout_props::Insets {
611 left: padding_logical.left * scale,
612 right: padding_logical.right * scale,
613 top: padding_logical.top * scale,
614 bottom: padding_logical.bottom * scale,
615 };
616 out.push(InspectorNode {
617 type_name: widget.type_name(),
618 screen_bounds: abs,
619 margin: scaled_margin,
620 padding: scaled_padding,
621 h_anchor,
622 v_anchor,
623 depth,
624 path: path_prefix.to_vec(),
625 properties: props,
626 });
627
628 if !widget.contributes_children_to_inspector() {
633 return;
634 }
635
636 let mut child_to_screen = *parent_to_screen;
644 child_to_screen.translate(b.x, b.y);
645 let extra = widget.inspector_child_transform();
646 child_to_screen.premultiply(&extra);
647
648 let mut child_path: Vec<usize> = Vec::with_capacity(path_prefix.len() + 1);
649 child_path.extend_from_slice(path_prefix);
650 child_path.push(0);
651 for (i, child) in widget.children().iter().enumerate() {
652 *child_path.last_mut().unwrap() = i;
653 collect_inspector_nodes_with_path(
654 child.as_ref(),
655 depth + 1,
656 &child_to_screen,
657 &child_path,
658 out,
659 );
660 }
661}
662
663pub fn walk_path_mut<'a>(root: &'a mut dyn Widget, path: &[usize]) -> Option<&'a mut dyn Widget> {
668 let mut node: &mut dyn Widget = root;
669 for &idx in path {
670 let children = node.children_mut();
671 if idx >= children.len() {
672 return None;
673 }
674 node = children[idx].as_mut();
675 }
676 Some(node)
677}
678
679#[cfg(feature = "reflect")]
686pub struct InspectorEdit {
687 pub path: Vec<usize>,
688 pub field_path: String,
691 pub new_value: Box<dyn bevy_reflect::PartialReflect>,
693}
694
695#[cfg(feature = "reflect")]
696impl std::fmt::Debug for InspectorEdit {
697 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
698 f.debug_struct("InspectorEdit")
699 .field("path", &self.path)
700 .field("field_path", &self.field_path)
701 .finish_non_exhaustive()
702 }
703}
704
705#[derive(Clone, Debug)]
712pub enum WidgetBaseField {
713 MarginLeft(f64),
714 MarginRight(f64),
715 MarginTop(f64),
716 MarginBottom(f64),
717 HAnchor(crate::layout_props::HAnchor),
718 VAnchor(crate::layout_props::VAnchor),
719 MinWidth(f64),
720 MinHeight(f64),
721 MaxWidth(f64),
722 MaxHeight(f64),
723}
724
725#[derive(Clone, Debug)]
728pub struct WidgetBaseEdit {
729 pub path: Vec<usize>,
731 pub field: WidgetBaseField,
732}
733
734pub fn apply_widget_base_edit(root: &mut dyn Widget, edit: &WidgetBaseEdit) -> bool {
738 let Some(target) = walk_path_mut(root, &edit.path) else {
739 return false;
740 };
741 let Some(base) = target.widget_base_mut() else {
742 return false;
743 };
744 match &edit.field {
745 WidgetBaseField::MarginLeft(v) => base.margin.left = *v,
746 WidgetBaseField::MarginRight(v) => base.margin.right = *v,
747 WidgetBaseField::MarginTop(v) => base.margin.top = *v,
748 WidgetBaseField::MarginBottom(v) => base.margin.bottom = *v,
749 WidgetBaseField::HAnchor(a) => base.h_anchor = *a,
750 WidgetBaseField::VAnchor(a) => base.v_anchor = *a,
751 WidgetBaseField::MinWidth(v) => base.min_size.width = v.max(0.0),
752 WidgetBaseField::MinHeight(v) => base.min_size.height = v.max(0.0),
753 WidgetBaseField::MaxWidth(v) => base.max_size.width = v.max(0.0),
754 WidgetBaseField::MaxHeight(v) => base.max_size.height = v.max(0.0),
755 }
756 target.mark_dirty();
757 crate::animation::request_draw();
758 true
759}
760
761#[cfg(feature = "reflect")]
765pub fn apply_inspector_edit(root: &mut dyn Widget, edit: &InspectorEdit) -> bool {
766 use bevy_reflect::{GetPath, PartialReflect};
767 let Some(target) = walk_path_mut(root, &edit.path) else {
768 return false;
769 };
770 let applied;
771 {
772 let Some(reflected) = target.as_reflect_mut() else {
773 return false;
774 };
775 let Ok(field) = reflected.reflect_path_mut(edit.field_path.as_str()) else {
776 return false;
777 };
778 let field: &mut dyn PartialReflect = field;
779 applied = field.try_apply(edit.new_value.as_ref()).is_ok();
780 }
781 if applied {
785 target.mark_dirty();
786 crate::animation::request_draw();
787 }
788 applied
789}