use super::*;
pub fn mark_subtree_dirty(widget: &mut dyn Widget) {
widget.mark_dirty();
for child in widget.children_mut().iter_mut() {
mark_subtree_dirty(child.as_mut());
}
}
pub fn hit_test_subtree(widget: &dyn Widget, local_pos: Point) -> Option<Vec<usize>> {
if !widget.is_visible() || !widget.hit_test(local_pos) {
return None;
}
if widget.claims_pointer_exclusively(local_pos) {
return Some(vec![]);
}
for (i, child) in widget.children().iter().enumerate().rev() {
let child_local = Point::new(
local_pos.x - child.bounds().x,
local_pos.y - child.bounds().y,
);
if let Some(mut sub_path) = hit_test_subtree(child.as_ref(), child_local) {
sub_path.insert(0, i);
return Some(sub_path);
}
}
Some(vec![]) }
pub fn active_modal_path(widget: &dyn Widget) -> Option<Vec<usize>> {
if !widget.is_visible() {
return None;
}
for (i, child) in widget.children().iter().enumerate().rev() {
if let Some(mut sub_path) = active_modal_path(child.as_ref()) {
sub_path.insert(0, i);
return Some(sub_path);
}
}
if widget.has_active_modal() {
Some(vec![])
} else {
None
}
}
pub fn global_overlay_hit_path(widget: &dyn Widget, local_pos: Point) -> Option<Vec<usize>> {
if !widget.is_visible() {
return None;
}
for (i, child) in widget.children().iter().enumerate().rev() {
let child_local = Point::new(
local_pos.x - child.bounds().x,
local_pos.y - child.bounds().y,
);
if let Some(mut sub_path) = global_overlay_hit_path(child.as_ref(), child_local) {
sub_path.insert(0, i);
return Some(sub_path);
}
}
if widget.hit_test_global_overlay(local_pos) {
Some(vec![])
} else {
None
}
}
pub fn dispatch_event(
root: &mut Box<dyn Widget>,
path: &[usize],
event: &Event,
pos_in_root: Point,
) -> EventResult {
if path.is_empty() {
let before = crate::animation::invalidation_epoch();
let result = root.on_event(event);
if result == EventResult::Consumed || before != crate::animation::invalidation_epoch() {
root.mark_dirty();
}
return result;
}
let idx = path[0];
if idx >= root.children().len() {
return root.on_event(event);
}
let child_bounds = root.children()[idx].bounds();
let child_pos = Point::new(
pos_in_root.x - child_bounds.x,
pos_in_root.y - child_bounds.y,
);
let translated_event = translate_event(event, child_pos);
let before_child = crate::animation::invalidation_epoch();
let child_result = dispatch_event(
&mut root.children_mut()[idx],
&path[1..],
&translated_event,
child_pos,
);
if child_result == EventResult::Consumed {
root.mark_dirty();
return EventResult::Consumed;
}
if before_child != crate::animation::invalidation_epoch() {
root.mark_dirty();
}
let before_self = crate::animation::invalidation_epoch();
let result = root.on_event(event);
if result == EventResult::Consumed || before_self != crate::animation::invalidation_epoch() {
root.mark_dirty();
}
result
}
pub fn dispatch_unconsumed_key(
widget: &mut dyn Widget,
key: &Key,
modifiers: Modifiers,
) -> EventResult {
if !widget.is_visible() {
return EventResult::Ignored;
}
for child in widget.children_mut().iter_mut().rev() {
if dispatch_unconsumed_key(child.as_mut(), key, modifiers) == EventResult::Consumed {
widget.mark_dirty();
return EventResult::Consumed;
}
}
let before = crate::animation::invalidation_epoch();
let result = widget.on_unconsumed_key(key, modifiers);
if result == EventResult::Consumed || before != crate::animation::invalidation_epoch() {
widget.mark_dirty();
}
result
}
fn translate_event(event: &Event, new_pos: Point) -> Event {
match event {
Event::MouseMove { .. } => Event::MouseMove { pos: new_pos },
Event::MouseDown {
button, modifiers, ..
} => Event::MouseDown {
pos: new_pos,
button: *button,
modifiers: *modifiers,
},
Event::MouseUp {
button, modifiers, ..
} => Event::MouseUp {
pos: new_pos,
button: *button,
modifiers: *modifiers,
},
Event::MouseWheel {
delta_y,
delta_x,
modifiers,
..
} => Event::MouseWheel {
pos: new_pos,
delta_y: *delta_y,
delta_x: *delta_x,
modifiers: *modifiers,
},
other => other.clone(),
}
}
#[derive(Clone)]
pub struct InspectorNode {
pub type_name: &'static str,
pub screen_bounds: Rect,
pub margin: crate::layout_props::Insets,
pub padding: crate::layout_props::Insets,
pub h_anchor: crate::layout_props::HAnchor,
pub v_anchor: crate::layout_props::VAnchor,
pub depth: usize,
pub path: Vec<usize>,
pub properties: Vec<(&'static str, String)>,
}
#[cfg(feature = "reflect")]
pub fn reflect_fields(reflected: &dyn bevy_reflect::Reflect) -> Vec<(&'static str, String)> {
use bevy_reflect::{ReflectRef, TypeInfo};
let mut out = Vec::new();
if let ReflectRef::Struct(s) = reflected.reflect_ref() {
let names: Vec<&'static str> = if let Some(TypeInfo::Struct(info)) =
reflected.get_represented_type_info()
{
(0..s.field_len())
.map(|i| info.field_at(i).map(|f| f.name()).unwrap_or(""))
.collect()
} else {
vec![""; s.field_len()]
};
for i in 0..s.field_len() {
let name = names.get(i).copied().unwrap_or("");
if name.is_empty() {
continue;
}
if let Some(field) = s.field_at(i) {
out.push((name, format_reflect_value(field)));
}
}
}
out
}
#[cfg(feature = "reflect")]
fn format_reflect_value(value: &dyn bevy_reflect::PartialReflect) -> String {
if let Some(v) = value.try_downcast_ref::<bool>() {
return v.to_string();
}
if let Some(v) = value.try_downcast_ref::<f64>() {
return format!("{v:.3}");
}
if let Some(v) = value.try_downcast_ref::<f32>() {
return format!("{v:.3}");
}
if let Some(v) = value.try_downcast_ref::<i32>() {
return v.to_string();
}
if let Some(v) = value.try_downcast_ref::<u32>() {
return v.to_string();
}
if let Some(v) = value.try_downcast_ref::<usize>() {
return v.to_string();
}
if let Some(v) = value.try_downcast_ref::<String>() {
return format!("\"{v}\"");
}
if let Some(v) = value.try_downcast_ref::<crate::color::Color>() {
return format!(
"rgba({:.2}, {:.2}, {:.2}, {:.2})",
v.r, v.g, v.b, v.a
);
}
format!("{value:?}")
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct InspectorOverlay {
pub bounds: Rect,
pub margin: crate::layout_props::Insets,
pub padding: crate::layout_props::Insets,
}
thread_local! {
static CURRENT_MOUSE_WORLD: std::cell::Cell<Option<Point>> =
std::cell::Cell::new(None);
static CURRENT_VIEWPORT: std::cell::Cell<Size> =
std::cell::Cell::new(Size::new(1.0, 1.0));
}
pub fn set_current_mouse_world(p: Point) {
CURRENT_MOUSE_WORLD.with(|c| c.set(Some(p)));
}
pub fn current_mouse_world() -> Option<Point> {
CURRENT_MOUSE_WORLD.with(|c| c.get())
}
pub fn set_current_viewport(s: Size) {
CURRENT_VIEWPORT.with(|c| c.set(s));
}
pub fn current_viewport() -> Size {
CURRENT_VIEWPORT.with(|c| c.get())
}
pub fn find_widget_by_id<'a>(widget: &'a dyn Widget, id: &str) -> Option<&'a dyn Widget> {
if widget.id() == Some(id) {
return Some(widget);
}
for child in widget.children() {
if let Some(found) = find_widget_by_id(child.as_ref(), id) {
return Some(found);
}
}
None
}
pub fn find_widget_by_id_mut<'a>(
widget: &'a mut dyn Widget,
id: &str,
) -> Option<&'a mut dyn Widget> {
if widget.id() == Some(id) {
return Some(widget);
}
for child in widget.children_mut().iter_mut() {
if let Some(found) = find_widget_by_id_mut(child.as_mut(), id) {
return Some(found);
}
}
None
}
pub fn find_widget_by_type<'a>(widget: &'a dyn Widget, type_name: &str) -> Option<&'a dyn Widget> {
if widget.type_name() == type_name {
return Some(widget);
}
for child in widget.children() {
if let Some(found) = find_widget_by_type(child.as_ref(), type_name) {
return Some(found);
}
}
None
}
pub fn collect_inspector_nodes(
widget: &dyn Widget,
depth: usize,
screen_origin: Point,
out: &mut Vec<InspectorNode>,
) {
collect_inspector_nodes_with_path(widget, depth, screen_origin, &[], out);
}
fn collect_inspector_nodes_with_path(
widget: &dyn Widget,
depth: usize,
screen_origin: Point,
path_prefix: &[usize],
out: &mut Vec<InspectorNode>,
) {
if !widget.is_visible() {
return;
}
if !widget.show_in_inspector() {
return;
}
let b = widget.bounds();
let abs = Rect::new(
screen_origin.x + b.x,
screen_origin.y + b.y,
b.width,
b.height,
);
let mut props = vec![(
"backbuffer",
if widget.has_backbuffer() {
"true".to_string()
} else {
"false".to_string()
},
)];
props.extend(widget.properties());
#[cfg(feature = "reflect")]
if let Some(reflected) = widget.as_reflect() {
props.extend(reflect_fields(reflected));
}
let (h_anchor, v_anchor) = widget
.widget_base()
.map(|b| (b.h_anchor, b.v_anchor))
.unwrap_or((
crate::layout_props::HAnchor::FIT,
crate::layout_props::VAnchor::FIT,
));
out.push(InspectorNode {
type_name: widget.type_name(),
screen_bounds: abs,
margin: widget.margin(),
padding: widget.padding(),
h_anchor,
v_anchor,
depth,
path: path_prefix.to_vec(),
properties: props,
});
if !widget.contributes_children_to_inspector() {
return;
}
let child_origin = Point::new(abs.x, abs.y);
let mut child_path: Vec<usize> = Vec::with_capacity(path_prefix.len() + 1);
child_path.extend_from_slice(path_prefix);
child_path.push(0);
for (i, child) in widget.children().iter().enumerate() {
*child_path.last_mut().unwrap() = i;
collect_inspector_nodes_with_path(
child.as_ref(),
depth + 1,
child_origin,
&child_path,
out,
);
}
}
pub fn walk_path_mut<'a>(
root: &'a mut dyn Widget,
path: &[usize],
) -> Option<&'a mut dyn Widget> {
let mut node: &mut dyn Widget = root;
for &idx in path {
let children = node.children_mut();
if idx >= children.len() {
return None;
}
node = children[idx].as_mut();
}
Some(node)
}
#[cfg(feature = "reflect")]
pub struct InspectorEdit {
pub path: Vec<usize>,
pub field_path: String,
pub new_value: Box<dyn bevy_reflect::PartialReflect>,
}
#[cfg(feature = "reflect")]
impl std::fmt::Debug for InspectorEdit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InspectorEdit")
.field("path", &self.path)
.field("field_path", &self.field_path)
.finish_non_exhaustive()
}
}
#[derive(Clone, Debug)]
pub enum WidgetBaseField {
MarginLeft(f64),
MarginRight(f64),
MarginTop(f64),
MarginBottom(f64),
HAnchor(crate::layout_props::HAnchor),
VAnchor(crate::layout_props::VAnchor),
MinWidth(f64),
MinHeight(f64),
MaxWidth(f64),
MaxHeight(f64),
}
#[derive(Clone, Debug)]
pub struct WidgetBaseEdit {
pub path: Vec<usize>,
pub field: WidgetBaseField,
}
pub fn apply_widget_base_edit(root: &mut dyn Widget, edit: &WidgetBaseEdit) -> bool {
let Some(target) = walk_path_mut(root, &edit.path) else {
return false;
};
let Some(base) = target.widget_base_mut() else {
return false;
};
match &edit.field {
WidgetBaseField::MarginLeft(v) => base.margin.left = *v,
WidgetBaseField::MarginRight(v) => base.margin.right = *v,
WidgetBaseField::MarginTop(v) => base.margin.top = *v,
WidgetBaseField::MarginBottom(v) => base.margin.bottom = *v,
WidgetBaseField::HAnchor(a) => base.h_anchor = *a,
WidgetBaseField::VAnchor(a) => base.v_anchor = *a,
WidgetBaseField::MinWidth(v) => base.min_size.width = v.max(0.0),
WidgetBaseField::MinHeight(v) => base.min_size.height = v.max(0.0),
WidgetBaseField::MaxWidth(v) => base.max_size.width = v.max(0.0),
WidgetBaseField::MaxHeight(v) => base.max_size.height = v.max(0.0),
}
target.mark_dirty();
crate::animation::request_draw();
true
}
#[cfg(feature = "reflect")]
pub fn apply_inspector_edit(root: &mut dyn Widget, edit: &InspectorEdit) -> bool {
use bevy_reflect::{GetPath, PartialReflect};
let Some(target) = walk_path_mut(root, &edit.path) else {
return false;
};
let applied;
{
let Some(reflected) = target.as_reflect_mut() else {
return false;
};
let Ok(field) = reflected.reflect_path_mut(edit.field_path.as_str()) else {
return false;
};
let field: &mut dyn PartialReflect = field;
applied = field.try_apply(edit.new_value.as_ref()).is_ok();
}
if applied {
target.mark_dirty();
crate::animation::request_draw();
}
applied
}