diff --git a/CHANGELOG.md b/CHANGELOG.md index 893c0573..58460f62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # UNRELEASED +- Changed: simplified debug settings and examples. Debug settings can be changed with the + `DebugPickingMode` resource. - Fixed: replaced uses of `.insert` with `.try_insert`, where they could potentially panic. - Fixed: replace all `.single` calls with matched `.get_single` calls to avoid crashing in environments where there is no window available diff --git a/Cargo.toml b/Cargo.toml index 38025291..07e0376a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,22 +19,23 @@ all-features = true members = ["crates/*", "backends/*"] [dependencies] -bevy_app = { version = "0.12", default-features = false } -bevy_core = { version = "0.12", default-features = false } -bevy_ecs = { version = "0.12", default-features = false } -bevy_math = { version = "0.12", default-features = false } -bevy_reflect = { version = "0.12", default-features = false } -bevy_render = { version = "0.12", default-features = false } -bevy_text = { version = "0.12", optional = true, default-features = false, features = [ +bevy_app = { version = "0.13", default-features = false } +bevy_core = { version = "0.13", default-features = false } +bevy_ecs = { version = "0.13", default-features = false } +bevy_math = { version = "0.13", default-features = false } +bevy_reflect = { version = "0.13", default-features = false } +bevy_render = { version = "0.13", default-features = false } +bevy_core_pipeline = { version = "0.13", optional = true, default-features = false } +bevy_text = { version = "0.13", optional = true, default-features = false, features = [ "default_font", ] } -bevy_ui = { version = "0.12", optional = true, default-features = false } -bevy_utils = { version = "0.12", default-features = false } -bevy_window = { version = "0.12", default-features = false } +bevy_ui = { version = "0.13", optional = true, default-features = false } +bevy_utils = { version = "0.13", default-features = false } +bevy_window = { version = "0.13", default-features = false } -bevy_eventlistener = "0.6" -bevy_egui = { optional = true, version = "0.23" } -bevy_rapier3d = { optional = true, version = "0.23" } +bevy_eventlistener = "0.7" +bevy_egui = { optional = true, version = "0.25" } +bevy_rapier3d = { optional = true, version = "0.25" } # Local bevy_picking_core = { path = "crates/bevy_picking_core", version = "0.17" } @@ -48,7 +49,7 @@ bevy_picking_sprite = { optional = true, path = "backends/bevy_picking_sprite", bevy_picking_egui = { optional = true, path = "backends/bevy_picking_egui", version = "0.17" } [dev-dependencies] -bevy = { version = "0.12", default-features = false, features = [ +bevy = { version = "0.13", default-features = false, features = [ "bevy_winit", "x11", "bevy_gltf", @@ -87,7 +88,7 @@ selection = [ "bevy_picking_highlight/selection", ] highlight = ["bevy_picking_highlight/pbr"] -debug = ["bevy_text", "bevy_ui/bevy_text"] +debug = ["bevy_text", "bevy_ui/bevy_text", "bevy_core_pipeline"] backend_raycast = ["bevy_picking_raycast"] backend_rapier = ["bevy_picking_rapier", "bevy_rapier3d"] backend_sprite = ["bevy_picking_sprite", "bevy_picking_highlight/sprite"] @@ -112,7 +113,7 @@ required-features = ["backend_egui"] [[example]] name = "multiple_windows" path = "examples/multiple_windows.rs" -required-features = ["backend_egui"] +# required-features = ["backend_egui"] [[example]] name = "virtual_pointer" diff --git a/backends/bevy_picking_egui/Cargo.toml b/backends/bevy_picking_egui/Cargo.toml index b536a04a..85a5c76f 100644 --- a/backends/bevy_picking_egui/Cargo.toml +++ b/backends/bevy_picking_egui/Cargo.toml @@ -13,12 +13,12 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bevy_app = { version = "0.12", default-features = false } -bevy_ecs = { version = "0.12", default-features = false } -bevy_reflect = { version = "0.12", default-features = false } -bevy_render = { version = "0.12", default-features = false } +bevy_app = { version = "0.13", default-features = false } +bevy_ecs = { version = "0.13", default-features = false } +bevy_reflect = { version = "0.13", default-features = false } +bevy_render = { version = "0.13", default-features = false } -bevy_egui = "0.23" +bevy_egui = "0.25" # Local bevy_picking_core = { path = "../../crates/bevy_picking_core", version = "0.17" } bevy_picking_selection = { path = "../../crates/bevy_picking_selection", optional = true, version = "0.17" } diff --git a/backends/bevy_picking_egui/src/lib.rs b/backends/bevy_picking_egui/src/lib.rs index 6ecad457..0e105608 100644 --- a/backends/bevy_picking_egui/src/lib.rs +++ b/backends/bevy_picking_egui/src/lib.rs @@ -88,7 +88,7 @@ pub fn egui_picking( if ctx.get_mut().wants_pointer_input() { let entry = (entity, HitData::new(entity, 0.0, None, None)); let order = 1_000_000f32; // Assume egui should be on top of everything else. - output.send(PointerHits::new(*pointer, Vec::from([entry]), order)) + output.send(PointerHits::new(*pointer, Vec::from([entry]), order)); } } } diff --git a/backends/bevy_picking_rapier/Cargo.toml b/backends/bevy_picking_rapier/Cargo.toml index 0ce46f57..c9704427 100644 --- a/backends/bevy_picking_rapier/Cargo.toml +++ b/backends/bevy_picking_rapier/Cargo.toml @@ -13,13 +13,13 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bevy_app = { version = "0.12", default-features = false } -bevy_ecs = { version = "0.12", default-features = false } -bevy_reflect = { version = "0.12", default-features = false } -bevy_render = { version = "0.12", default-features = false } -bevy_transform = { version = "0.12", default-features = false } -bevy_window = { version = "0.12", default-features = false } +bevy_app = { version = "0.13", default-features = false } +bevy_ecs = { version = "0.13", default-features = false } +bevy_reflect = { version = "0.13", default-features = false } +bevy_render = { version = "0.13", default-features = false } +bevy_transform = { version = "0.13", default-features = false } +bevy_window = { version = "0.13", default-features = false } -bevy_rapier3d = "0.23" +bevy_rapier3d = "0.25" # Local bevy_picking_core = { path = "../../crates/bevy_picking_core", version = "0.17" } diff --git a/backends/bevy_picking_rapier/src/lib.rs b/backends/bevy_picking_rapier/src/lib.rs index 9e806149..e75b10c0 100644 --- a/backends/bevy_picking_rapier/src/lib.rs +++ b/backends/bevy_picking_rapier/src/lib.rs @@ -112,7 +112,7 @@ pub fn update_hits( if let Some((entity, hit_data)) = rapier_context .cast_ray_and_get_normal( ray.origin, - ray.direction, + *ray.direction, f32::MAX, true, QueryFilter::new().predicate(&predicate), diff --git a/backends/bevy_picking_raycast/Cargo.toml b/backends/bevy_picking_raycast/Cargo.toml index a356bd75..e12cfd1e 100644 --- a/backends/bevy_picking_raycast/Cargo.toml +++ b/backends/bevy_picking_raycast/Cargo.toml @@ -13,13 +13,13 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bevy_app = { version = "0.12", default-features = false } -bevy_ecs = { version = "0.12", default-features = false } -bevy_reflect = { version = "0.12", default-features = false } -bevy_render = { version = "0.12", default-features = false } -bevy_transform = { version = "0.12", default-features = false } -bevy_window = { version = "0.12", default-features = false } +bevy_app = { version = "0.13", default-features = false } +bevy_ecs = { version = "0.13", default-features = false } +bevy_reflect = { version = "0.13", default-features = false } +bevy_render = { version = "0.13", default-features = false } +bevy_transform = { version = "0.13", default-features = false } +bevy_window = { version = "0.13", default-features = false } -bevy_mod_raycast = "0.16" +bevy_mod_raycast = { version = "0.17.0" } # Local bevy_picking_core = { path = "../../crates/bevy_picking_core", version = "0.17" } diff --git a/backends/bevy_picking_raycast/src/lib.rs b/backends/bevy_picking_raycast/src/lib.rs index da6a942d..6ef57a23 100644 --- a/backends/bevy_picking_raycast/src/lib.rs +++ b/backends/bevy_picking_raycast/src/lib.rs @@ -111,7 +111,7 @@ pub fn update_hits( }, }; let picks = raycast - .cast_ray(ray.into(), &settings) + .cast_ray(ray, &settings) .iter() .map(|(entity, hit)| { let hit_data = HitData::new( diff --git a/backends/bevy_picking_sprite/Cargo.toml b/backends/bevy_picking_sprite/Cargo.toml index dd982213..d55c7d0d 100644 --- a/backends/bevy_picking_sprite/Cargo.toml +++ b/backends/bevy_picking_sprite/Cargo.toml @@ -13,13 +13,13 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bevy_app = { version = "0.12", default-features = false } -bevy_asset = { version = "0.12", default-features = false } -bevy_ecs = { version = "0.12", default-features = false } -bevy_math = { version = "0.12", default-features = false } -bevy_render = { version = "0.12", default-features = false } -bevy_sprite = { version = "0.12", default-features = false } -bevy_transform = { version = "0.12", default-features = false } -bevy_window = { version = "0.12", default-features = false } +bevy_app = { version = "0.13", default-features = false } +bevy_asset = { version = "0.13", default-features = false } +bevy_ecs = { version = "0.13", default-features = false } +bevy_math = { version = "0.13", default-features = false } +bevy_render = { version = "0.13", default-features = false } +bevy_sprite = { version = "0.13", default-features = false } +bevy_transform = { version = "0.13", default-features = false } +bevy_window = { version = "0.13", default-features = false } # Local bevy_picking_core = { path = "../../crates/bevy_picking_core", version = "0.17" } diff --git a/backends/bevy_picking_sprite/src/lib.rs b/backends/bevy_picking_sprite/src/lib.rs index 1bffe4ec..b2d41f0c 100644 --- a/backends/bevy_picking_sprite/src/lib.rs +++ b/backends/bevy_picking_sprite/src/lib.rs @@ -11,7 +11,7 @@ use bevy_asset::prelude::*; use bevy_ecs::prelude::*; use bevy_math::prelude::*; use bevy_render::prelude::*; -use bevy_sprite::{Sprite, TextureAtlas, TextureAtlasSprite}; +use bevy_sprite::{Sprite, TextureAtlas, TextureAtlasLayout}; use bevy_transform::prelude::*; use bevy_window::PrimaryWindow; @@ -38,24 +38,25 @@ pub fn sprite_picking( cameras: Query<(Entity, &Camera, &GlobalTransform, &OrthographicProjection)>, primary_window: Query>, images: Res>, - texture_atlas: Res>, + texture_atlas_layout: Res>, sprite_query: Query< ( Entity, - (Option<&Sprite>, Option<&TextureAtlasSprite>), - (Option<&Handle>, Option<&Handle>), + Option<&Sprite>, + Option<&TextureAtlas>, + Option<&Handle>, &GlobalTransform, Option<&Pickable>, &ViewVisibility, ), - Or<(With, With)>, + Or<(With, With)>, >, mut output: EventWriter, ) { let mut sorted_sprites: Vec<_> = sprite_query.iter().collect(); sorted_sprites.sort_by(|a, b| { - (b.3.translation().z) - .partial_cmp(&a.3.translation().z) + (b.4.translation().z) + .partial_cmp(&a.4.translation().z) .unwrap_or(Ordering::Equal) }); @@ -89,51 +90,54 @@ pub fn sprite_picking( .iter() .copied() .filter(|(.., visibility)| visibility.get()) - .filter_map(|(entity, sprite, image, sprite_transform, pickable, ..)| { - if blocked { - return None; - } - - // Hit box in sprite coordinate system - let (extents, anchor) = if let Some((sprite, image)) = sprite.0.zip(image.0) { - let extents = sprite - .custom_size - .or_else(|| images.get(image).map(|f| f.size().as_vec2()))?; - let anchor = sprite.anchor.as_vec(); - (extents, anchor) - } else if let Some((sprite, atlas)) = sprite.1.zip(image.1) { - let extents = sprite.custom_size.or_else(|| { - texture_atlas - .get(atlas) - .map(|f| f.textures[sprite.index].size()) - })?; - let anchor = sprite.anchor.as_vec(); - (extents, anchor) - } else { - return None; - }; - - let center = -anchor * extents; - let rect = Rect::from_center_half_size(center, extents / 2.0); - - // Transform cursor pos to sprite coordinate system - let cursor_pos_sprite = sprite_transform - .affine() - .inverse() - .transform_point3((cursor_pos_world, 0.0).into()); - - let is_cursor_in_sprite = rect.contains(cursor_pos_sprite.truncate()); - blocked = - is_cursor_in_sprite && pickable.map(|p| p.should_block_lower) != Some(false); - - // HitData requires a depth as calculated from the camera's near clipping plane - let depth = -cam_ortho.near - sprite_transform.translation().z; - - is_cursor_in_sprite.then_some((entity, HitData::new(cam_entity, depth, None, None))) - }) + .filter_map( + |(entity, sprite, atlas, image, sprite_transform, pickable, ..)| { + if blocked { + return None; + } + + // Hit box in sprite coordinate system + let (extents, anchor) = if let Some((sprite, atlas)) = sprite.zip(atlas) { + let extents = sprite.custom_size.or_else(|| { + texture_atlas_layout + .get(&atlas.layout) + .map(|f| f.textures[atlas.index].size()) + })?; + let anchor = sprite.anchor.as_vec(); + (extents, anchor) + } else if let Some((sprite, image)) = sprite.zip(image) { + let extents = sprite + .custom_size + .or_else(|| images.get(image).map(|f| f.size().as_vec2()))?; + let anchor = sprite.anchor.as_vec(); + (extents, anchor) + } else { + return None; + }; + + let center = -anchor * extents; + let rect = Rect::from_center_half_size(center, extents / 2.0); + + // Transform cursor pos to sprite coordinate system + let cursor_pos_sprite = sprite_transform + .affine() + .inverse() + .transform_point3((cursor_pos_world, 0.0).into()); + + let is_cursor_in_sprite = rect.contains(cursor_pos_sprite.truncate()); + blocked = is_cursor_in_sprite + && pickable.map(|p| p.should_block_lower) != Some(false); + + // HitData requires a depth as calculated from the camera's near clipping plane + let depth = -cam_ortho.near - sprite_transform.translation().z; + + is_cursor_in_sprite + .then_some((entity, HitData::new(cam_entity, depth, None, None))) + }, + ) .collect(); let order = camera.order as f32; - output.send(PointerHits::new(*pointer, picks, order)) + output.send(PointerHits::new(*pointer, picks, order)); } } diff --git a/backends/bevy_picking_ui/Cargo.toml b/backends/bevy_picking_ui/Cargo.toml index 66d176f7..2f56470c 100644 --- a/backends/bevy_picking_ui/Cargo.toml +++ b/backends/bevy_picking_ui/Cargo.toml @@ -13,11 +13,15 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bevy_app = { version = "0.12", default-features = false } -bevy_ecs = { version = "0.12", default-features = false } -bevy_render = { version = "0.12", default-features = false } -bevy_transform = { version = "0.12", default-features = false } -bevy_window = { version = "0.12", default-features = false } -bevy_ui = { version = "0.12", default-features = false } +bevy_app = { version = "0.13", default-features = false } +bevy_ecs = { version = "0.13", default-features = false } +bevy_hierarchy = { version = "0.13", default-features = false } +bevy_render = { version = "0.13", default-features = false } +bevy_transform = { version = "0.13", default-features = false } +bevy_window = { version = "0.13", default-features = false } +bevy_ui = { version = "0.13", default-features = false } +bevy_utils = { version = "0.13", default-features = false } +bevy_math = { version = "0.13", default-features = false } + # Local bevy_picking_core = { path = "../../crates/bevy_picking_core", version = "0.17" } diff --git a/backends/bevy_picking_ui/src/lib.rs b/backends/bevy_picking_ui/src/lib.rs index d249e17f..d500adfc 100644 --- a/backends/bevy_picking_ui/src/lib.rs +++ b/backends/bevy_picking_ui/src/lib.rs @@ -23,14 +23,15 @@ #![deny(missing_docs)] use bevy_app::prelude::*; -use bevy_ecs::{prelude::*, query::WorldQuery}; -use bevy_render::{camera::NormalizedRenderTarget, prelude::*}; +use bevy_ecs::{prelude::*, query::QueryData}; +use bevy_math::Vec2; +use bevy_render::prelude::*; use bevy_transform::prelude::*; use bevy_ui::{prelude::*, RelativeCursorPosition, UiStack}; +use bevy_utils::hashbrown::HashMap; use bevy_window::PrimaryWindow; use bevy_picking_core::backend::prelude::*; -use bevy_picking_core::pointer::Location; /// Commonly used imports for the [`bevy_picking_ui`](crate) crate. pub mod prelude { @@ -47,8 +48,8 @@ impl Plugin for BevyUiBackend { } /// Main query from bevy's `ui_focus_system` -#[derive(WorldQuery)] -#[world_query(mutable)] +#[derive(QueryData)] +#[query_data(mutable)] pub struct NodeQuery { entity: Entity, node: &'static Node, @@ -57,6 +58,7 @@ pub struct NodeQuery { pickable: Option<&'static Pickable>, calculated_clip: Option<&'static CalculatedClip>, view_visibility: Option<&'static ViewVisibility>, + target_camera: Option<&'static TargetCamera>, } /// Computes the UI node entities under each pointer. @@ -65,102 +67,129 @@ pub struct NodeQuery { /// we need for determining picking. pub fn ui_picking( pointers: Query<(&PointerId, &PointerLocation)>, - cameras: Query<(Entity, &Camera, Option<&UiCameraConfig>)>, + camera_query: Query<(Entity, &Camera, Has)>, + default_ui_camera: DefaultUiCamera, primary_window: Query>, + ui_scale: Res, ui_stack: Res, - ui_scale: Option>, mut node_query: Query, mut output: EventWriter, ) { - let ui_scale = ui_scale.map(|f| f.0).unwrap_or(1.0) as f32; - for (pointer, location) in pointers.iter().filter_map(|(pointer, pointer_location)| { - pointer_location - .location() - // TODO: update when proper multi-window UI is implemented - .filter(|loc| { - if let NormalizedRenderTarget::Window(window) = loc.target { - if primary_window.contains(window.entity()) { - return true; - } - } - false - }) - .cloned() - .map(|loc| { + // For each camera, the pointer and its position + let mut pointer_pos_by_camera = HashMap::>::new(); + + for (pointer_id, pointer_location) in + pointers.iter().filter_map(|(pointer, pointer_location)| { + Some(*pointer).zip(pointer_location.location().cloned()) + }) + { + // This pointer is associated with a render target, which could be used by multiple + // cameras. We want to ensure we return all cameras with a matching target. + for camera in camera_query + .iter() + .map(|(entity, camera, _)| { ( - pointer, - Location { - position: loc.position / ui_scale, - ..loc - }, + entity, + camera.target.normalize(primary_window.get_single().ok()), ) }) - }) { - let window_entity = match primary_window.get_single() { - Ok(w) => w, - Err(_) => continue, - }; + .filter_map(|(entity, target)| Some(entity).zip(target)) + .filter(|(_entity, target)| target == &pointer_location.target) + .map(|(cam_entity, _target)| cam_entity) + { + let Ok((_, camera_data, _)) = camera_query.get(camera) else { + continue; + }; + let mut pointer_pos = pointer_location.position; + if let Some(viewport) = camera_data.logical_viewport_rect() { + pointer_pos -= viewport.min; + } + let scaled_pointer_pos = pointer_pos / **ui_scale; + pointer_pos_by_camera + .entry(camera) + .or_default() + .insert(pointer_id, scaled_pointer_pos); + } + } - // Find the topmost bevy_ui camera with the same target as this pointer. - // - // Bevy ui can render on many cameras, but it will be the same UI, and we only want to - // consider the topmost one rendering UI in this window. - let mut ui_cameras: Vec<_> = cameras - .iter() - .filter(|(_entity, camera, _)| { - camera.is_active - && camera.target.normalize(Some(window_entity)).unwrap() == location.target - }) - .filter(|(_, _, ui_config)| ui_config.map(|config| config.show_ui).unwrap_or(true)) - .collect(); - ui_cameras.sort_by_key(|(_, camera, _)| camera.order); + // The list of node entities hovered for each (camera, pointer) combo + let mut hit_nodes = HashMap::<(Entity, PointerId), Vec>::new(); - // The last camera in the list will be the one with the highest order, and be the topmost. - let Some((camera_entity, camera, _)) = ui_cameras.last() else { + // prepare an iterator that contains all the nodes that have the cursor in their rect, + // from the top node to the bottom one. this will also reset the interaction to `None` + // for all nodes encountered that are no longer hovered. + for node_entity in ui_stack + .uinodes + .iter() + // reverse the iterator to traverse the tree from closest nodes to furthest + .rev() + { + let Ok(node) = node_query.get_mut(*node_entity) else { continue; }; - let mut hovered_nodes = ui_stack - .uinodes - .iter() - // reverse the iterator to traverse the tree from closest nodes to furthest - .rev() - .filter_map(|entity| { - if let Ok(node) = node_query.get_mut(*entity) { - // Nodes that are not rendered should not be interactable - if let Some(view_visibility) = node.view_visibility { - if !view_visibility.get() { - return None; - } - } - - let node_rect = node.node.logical_rect(node.global_transform); - let visible_rect = node - .calculated_clip - .map(|clip| node_rect.intersect(clip.clip)) - .unwrap_or(node_rect); - if visible_rect.contains(location.position) { - Some(*entity) - } else { - None - } - } else { - None - } - }) - .collect::>() - .into_iter(); + // Nodes that are not rendered should not be interactable + if node + .view_visibility + .map(|view_visibility| view_visibility.get()) + != Some(true) + { + continue; + } + let Some(camera_entity) = node + .target_camera + .map(TargetCamera::entity) + .or(default_ui_camera.get()) + else { + continue; + }; + + let node_rect = node.node.logical_rect(node.global_transform); + + // Intersect with the calculated clip rect to find the bounds of the visible region of the node + let visible_rect = node + .calculated_clip + .map(|clip| node_rect.intersect(clip.clip)) + .unwrap_or(node_rect); + + let pointers_on_this_cam = pointer_pos_by_camera.get(&camera_entity); + + // The mouse position relative to the node + // (0., 0.) is the top-left corner, (1., 1.) is the bottom-right corner + // Coordinates are relative to the entire node, not just the visible region. + for (pointer_id, cursor_position) in pointers_on_this_cam.iter().flat_map(|h| h.iter()) { + let relative_cursor_position = (*cursor_position - node_rect.min) / node_rect.size(); + + if visible_rect + .normalize(node_rect) + .contains(relative_cursor_position) + { + hit_nodes + .entry((camera_entity, *pointer_id)) + .or_default() + .push(*node_entity); + } + } + } + for ((camera, pointer), hovered_nodes) in hit_nodes.iter() { // As soon as a node with a `Block` focus policy is detected, the iteration will stop on it // because it "captures" the interaction. - let mut iter = node_query.iter_many_mut(hovered_nodes.by_ref()); + let mut iter = node_query.iter_many_mut(hovered_nodes.iter()); let mut picks = Vec::new(); let mut depth = 0.0; while let Some(node) = iter.fetch_next() { - let mut push_hit = - || picks.push((node.entity, HitData::new(*camera_entity, depth, None, None))); - push_hit(); + let Some(camera_entity) = node + .target_camera + .map(TargetCamera::entity) + .or(default_ui_camera.get()) + else { + continue; + }; + + picks.push((node.entity, HitData::new(camera_entity, depth, None, None))); + if let Some(pickable) = node.pickable { // If an entity has a `Pickable` component, we will use that as the source of truth. if pickable.should_block_lower { @@ -173,7 +202,13 @@ pub fn ui_picking( depth += 0.00001; // keep depth near 0 for precision } - let order = camera.order as f32 + 0.5; // bevy ui can run on any camera, it's a special case - output.send(PointerHits::new(*pointer, picks, order)) + + let order = camera_query + .get(*camera) + .map(|(_, cam, _)| cam.order) + .unwrap_or_default() as f32 + + 0.5; // bevy ui can run on any camera, it's a special case + + output.send(PointerHits::new(*pointer, picks, order)); } } diff --git a/crates/bevy_picking_core/Cargo.toml b/crates/bevy_picking_core/Cargo.toml index e532307d..42b710d2 100644 --- a/crates/bevy_picking_core/Cargo.toml +++ b/crates/bevy_picking_core/Cargo.toml @@ -13,14 +13,14 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bevy_app = { version = "0.12", default-features = false } -bevy_derive = { version = "0.12", default-features = false } -bevy_ecs = { version = "0.12", default-features = false } -bevy_math = { version = "0.12", default-features = false } -bevy_reflect = { version = "0.12", default-features = false } -bevy_render = { version = "0.12", default-features = false } -bevy_transform = { version = "0.12", default-features = false } -bevy_utils = { version = "0.12", default-features = false } -bevy_window = { version = "0.12", default-features = false } +bevy_app = { version = "0.13", default-features = false } +bevy_derive = { version = "0.13", default-features = false } +bevy_ecs = { version = "0.13", default-features = false } +bevy_math = { version = "0.13", default-features = false } +bevy_reflect = { version = "0.13", default-features = false } +bevy_render = { version = "0.13", default-features = false } +bevy_utils = { version = "0.13", default-features = false } +bevy_window = { version = "0.13", default-features = false } +bevy_transform = { version = "0.13", default-features = false } -bevy_eventlistener = "0.6" +bevy_eventlistener = "0.7" diff --git a/crates/bevy_picking_core/src/backend.rs b/crates/bevy_picking_core/src/backend.rs index 304be5ab..7fcf1c26 100644 --- a/crates/bevy_picking_core/src/backend.rs +++ b/crates/bevy_picking_core/src/backend.rs @@ -119,7 +119,7 @@ pub mod ray { use crate::backend::prelude::{PointerId, PointerLocation}; use bevy_ecs::prelude::*; - use bevy_math::Ray; + use bevy_math::Ray3d; use bevy_reflect::Reflect; use bevy_render::camera::Camera; use bevy_transform::prelude::GlobalTransform; @@ -143,7 +143,7 @@ pub mod ray { } } - /// A map from [`RayId`] to [`Ray`]. + /// A map from [`RayId`] to [`Ray3d`]. /// /// This map is cleared and re-populated every frame before any backends run. Ray-based picking /// backends should use this when possible, as it automatically handles viewports, DPI, and @@ -151,7 +151,7 @@ pub mod ray { /// /// ## Usage /// - /// Iterate over each [`Ray`] and its [`RayId`] with [`RayMap::iter`]. + /// Iterate over each [`Ray3d`] and its [`RayId`] with [`RayMap::iter`]. /// /// ``` /// # use bevy_ecs::prelude::*; @@ -166,17 +166,17 @@ pub mod ray { /// ``` #[derive(Clone, Debug, Default, Resource)] pub struct RayMap { - map: HashMap, + map: HashMap, } impl RayMap { /// Iterates over all world space rays for every picking pointer. - pub fn iter(&self) -> Iter<'_, RayId, Ray> { + pub fn iter(&self) -> Iter<'_, RayId, Ray3d> { self.map.iter() } /// The hash map of all rays cast in the current frame. - pub fn map(&self) -> &HashMap { + pub fn map(&self) -> &HashMap { &self.map } @@ -214,7 +214,7 @@ pub mod ray { camera: &Camera, camera_tfm: &GlobalTransform, pointer_loc: &PointerLocation, - ) -> Option { + ) -> Option { let pointer_loc = pointer_loc.location()?; if !pointer_loc.is_in_viewport(camera, primary_window_entity) { return None; diff --git a/crates/bevy_picking_core/src/events.rs b/crates/bevy_picking_core/src/events.rs index def8ce55..21a82144 100644 --- a/crates/bevy_picking_core/src/events.rs +++ b/crates/bevy_picking_core/src/events.rs @@ -19,6 +19,7 @@ use bevy_utils::{tracing::error, HashMap}; /// Stores the common data needed for all `PointerEvent`s. #[derive(Clone, PartialEq, Debug, Reflect, Event, EntityEvent)] +#[can_bubble] pub struct Pointer { /// The target of this event #[target] @@ -232,7 +233,7 @@ pub fn pointer_events( location.clone(), hovered_entity, Move { hit, delta }, - )) + )); } } @@ -259,7 +260,7 @@ pub fn pointer_events( location, hovered_entity, Up { button, hit }, - )) + )); } } for (hovered_entity, hit) in hover_map @@ -280,7 +281,7 @@ pub fn pointer_events( location, hovered_entity, Down { button, hit }, - )) + )); } } } @@ -404,7 +405,7 @@ pub fn send_click_and_drag_events( button, hit: down.hit.clone(), }, - )) + )); } for (dragged_entity, drag) in drag_list.iter_mut() { @@ -419,7 +420,7 @@ pub fn send_click_and_drag_events( location.clone(), *dragged_entity, drag_event, - )) + )); } } } @@ -530,7 +531,7 @@ pub fn send_drag_over_events( pointer_location.clone(), target, event, - )) + )); } } } @@ -561,7 +562,7 @@ pub fn send_drag_over_events( dragged: *drag_target, hit: hit.clone(), }, - )) + )); } } } @@ -632,7 +633,7 @@ pub fn send_drag_over_events( dragged: *drag_target, hit: hit.clone(), }, - )) + )); } } } diff --git a/crates/bevy_picking_core/src/pointer.rs b/crates/bevy_picking_core/src/pointer.rs index 059a7e57..2d4fbb3c 100644 --- a/crates/bevy_picking_core/src/pointer.rs +++ b/crates/bevy_picking_core/src/pointer.rs @@ -130,7 +130,7 @@ impl PointerPress { } /// Pointer input event for button presses. Fires when a pointer button changes state. -#[derive(Event, Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Event, Debug, Clone, Copy, PartialEq, Eq, Reflect)] pub struct InputPress { /// The [`PointerId`] of the pointer that pressed a button. pub pointer_id: PointerId, @@ -176,7 +176,7 @@ impl InputPress { mut pointers: Query<(&PointerId, &mut PointerPress)>, ) { for input_press_event in events.read() { - pointers.for_each_mut(|(pointer_id, mut pointer)| { + pointers.iter_mut().for_each(|(pointer_id, mut pointer)| { if *pointer_id == input_press_event.pointer_id { let is_down = input_press_event.direction == PressDirection::Down; match input_press_event.button { @@ -191,7 +191,7 @@ impl InputPress { } /// The stage of the pointer button press event -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)] pub enum PressDirection { /// The pointer button was just pressed Down, @@ -235,7 +235,7 @@ impl PointerLocation { } /// Pointer input event for pointer moves. Fires when a pointer changes location. -#[derive(Event, Debug, Clone)] +#[derive(Event, Debug, Clone, Reflect)] pub struct InputMove { /// The [`PointerId`] of the pointer that is moving. pub pointer_id: PointerId, @@ -260,7 +260,7 @@ impl InputMove { mut pointers: Query<(&PointerId, &mut PointerLocation)>, ) { for event_pointer in events.read() { - pointers.for_each_mut(|(id, mut pointer)| { + pointers.iter_mut().for_each(|(id, mut pointer)| { if *id == event_pointer.pointer_id { pointer.location = Some(event_pointer.location.to_owned()); } diff --git a/crates/bevy_picking_highlight/Cargo.toml b/crates/bevy_picking_highlight/Cargo.toml index eb4daa86..b4d7d2b4 100644 --- a/crates/bevy_picking_highlight/Cargo.toml +++ b/crates/bevy_picking_highlight/Cargo.toml @@ -13,13 +13,13 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bevy_app = { version = "0.12", default-features = false } -bevy_asset = { version = "0.12", default-features = false } -bevy_ecs = { version = "0.12", default-features = false } -bevy_reflect = { version = "0.12", default-features = false } -bevy_render = { version = "0.12", optional = true, default-features = false } -bevy_pbr = { version = "0.12", optional = true, default-features = false } -bevy_sprite = { version = "0.12", optional = true, default-features = false } +bevy_app = { version = "0.13", default-features = false } +bevy_asset = { version = "0.13", default-features = false } +bevy_ecs = { version = "0.13", default-features = false } +bevy_reflect = { version = "0.13", default-features = false } +bevy_render = { version = "0.13", optional = true, default-features = false } +bevy_pbr = { version = "0.13", optional = true, default-features = false } +bevy_sprite = { version = "0.13", optional = true, default-features = false } bevy_picking_core = { path = "../bevy_picking_core", version = "0.17" } bevy_picking_selection = { optional = true, path = "../bevy_picking_selection", version = "0.17" } diff --git a/crates/bevy_picking_highlight/src/lib.rs b/crates/bevy_picking_highlight/src/lib.rs index 5ebff787..5aa0cfda 100644 --- a/crates/bevy_picking_highlight/src/lib.rs +++ b/crates/bevy_picking_highlight/src/lib.rs @@ -68,20 +68,20 @@ impl Plugin for DefaultHighlightingPlugin { #[cfg(feature = "pbr")] app.add_plugins(HighlightPlugin:: { highlighting_default: |mut assets| GlobalHighlight { - hovered: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.35).into()), - pressed: assets.add(bevy_render::color::Color::rgb(0.35, 0.75, 0.35).into()), + hovered: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.35)), + pressed: assets.add(bevy_render::color::Color::rgb(0.35, 0.75, 0.35)), #[cfg(feature = "selection")] - selected: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.75).into()), + selected: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.75)), }, }); #[cfg(feature = "sprite")] app.add_plugins(HighlightPlugin:: { highlighting_default: |mut assets| GlobalHighlight { - hovered: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.35).into()), - pressed: assets.add(bevy_render::color::Color::rgb(0.35, 0.75, 0.35).into()), + hovered: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.35)), + pressed: assets.add(bevy_render::color::Color::rgb(0.35, 0.75, 0.35)), #[cfg(feature = "selection")] - selected: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.75).into()), + selected: assets.add(bevy_render::color::Color::rgb(0.35, 0.35, 0.75)), }, }); } diff --git a/crates/bevy_picking_input/Cargo.toml b/crates/bevy_picking_input/Cargo.toml index 05f9fc16..e33c9e5a 100644 --- a/crates/bevy_picking_input/Cargo.toml +++ b/crates/bevy_picking_input/Cargo.toml @@ -13,15 +13,15 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bevy_app = { version = "0.12", default-features = false } -bevy_ecs = { version = "0.12", default-features = false } -bevy_hierarchy = { version = "0.12", default-features = false } -bevy_input = { version = "0.12", default-features = false } -bevy_math = { version = "0.12", default-features = false } -bevy_reflect = { version = "0.12", default-features = false } -bevy_render = { version = "0.12", default-features = false } -bevy_utils = { version = "0.12", default-features = false } -bevy_window = { version = "0.12", default-features = false } +bevy_app = { version = "0.13", default-features = false } +bevy_ecs = { version = "0.13", default-features = false } +bevy_hierarchy = { version = "0.13", default-features = false } +bevy_input = { version = "0.13", default-features = false } +bevy_math = { version = "0.13", default-features = false } +bevy_reflect = { version = "0.13", default-features = false } +bevy_render = { version = "0.13", default-features = false } +bevy_utils = { version = "0.13", default-features = false } +bevy_window = { version = "0.13", default-features = false } bevy_picking_core = { path = "../bevy_picking_core", version = "0.17" } bevy_picking_selection = { optional = true, path = "../bevy_picking_selection", version = "0.17" } diff --git a/crates/bevy_picking_input/src/debug.rs b/crates/bevy_picking_input/src/debug.rs deleted file mode 100644 index 7bd6487a..00000000 --- a/crates/bevy_picking_input/src/debug.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! Debug tools for picking inputs - -use bevy_ecs::prelude::*; -use bevy_utils::tracing::info; - -use bevy_picking_core::pointer::{InputMove, InputPress}; - -/// Listens for input events and prints them. -pub fn print(mut moves: EventReader, mut presses: EventReader) { - for event in moves.read() { - info!("Input Move: {:?}", event.pointer_id); - } - for event in presses.read() { - info!("Input Press: {:?}, {:?}", event.pointer_id, event.direction); - } -} diff --git a/crates/bevy_picking_input/src/lib.rs b/crates/bevy_picking_input/src/lib.rs index 25817c1b..a2062df3 100644 --- a/crates/bevy_picking_input/src/lib.rs +++ b/crates/bevy_picking_input/src/lib.rs @@ -20,7 +20,6 @@ use bevy_reflect::prelude::*; use bevy_picking_core::PickSet; -pub mod debug; pub mod mouse; pub mod touch; diff --git a/crates/bevy_picking_input/src/mouse.rs b/crates/bevy_picking_input/src/mouse.rs index 4438d482..016ac062 100644 --- a/crates/bevy_picking_input/src/mouse.rs +++ b/crates/bevy_picking_input/src/mouse.rs @@ -57,14 +57,16 @@ pub fn mouse_pick_events( MouseButton::Right => PointerButton::Secondary, MouseButton::Middle => PointerButton::Middle, MouseButton::Other(_) => continue, + MouseButton::Back => continue, + MouseButton::Forward => continue, }; match input.state { ButtonState::Pressed => { - pointer_presses.send(InputPress::new_down(PointerId::Mouse, button)) + pointer_presses.send(InputPress::new_down(PointerId::Mouse, button)); } ButtonState::Released => { - pointer_presses.send(InputPress::new_up(PointerId::Mouse, button)) + pointer_presses.send(InputPress::new_up(PointerId::Mouse, button)); } } } diff --git a/crates/bevy_picking_input/src/touch.rs b/crates/bevy_picking_input/src/touch.rs index a280fbcb..93dba830 100644 --- a/crates/bevy_picking_input/src/touch.rs +++ b/crates/bevy_picking_input/src/touch.rs @@ -79,7 +79,7 @@ pub fn touch_pick_events( location_cache.remove(&touch.id); cancel_events.send(PointerCancel { pointer_id: pointer, - }) + }); } } } diff --git a/crates/bevy_picking_selection/Cargo.toml b/crates/bevy_picking_selection/Cargo.toml index acf1215c..2dd53966 100644 --- a/crates/bevy_picking_selection/Cargo.toml +++ b/crates/bevy_picking_selection/Cargo.toml @@ -13,12 +13,12 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bevy_app = { version = "0.12", default-features = false } -bevy_ecs = { version = "0.12", default-features = false } -bevy_input = { version = "0.12", default-features = false } -bevy_reflect = { version = "0.12", default-features = false } -bevy_utils = { version = "0.12", default-features = false } +bevy_app = { version = "0.13", default-features = false } +bevy_ecs = { version = "0.13", default-features = false } +bevy_input = { version = "0.13", default-features = false } +bevy_reflect = { version = "0.13", default-features = false } +bevy_utils = { version = "0.13", default-features = false } -bevy_eventlistener = "0.6" +bevy_eventlistener = "0.7" bevy_picking_core = { path = "../bevy_picking_core", version = "0.17" } diff --git a/crates/bevy_picking_selection/src/lib.rs b/crates/bevy_picking_selection/src/lib.rs index 19694f24..c95da4ee 100644 --- a/crates/bevy_picking_selection/src/lib.rs +++ b/crates/bevy_picking_selection/src/lib.rs @@ -9,7 +9,7 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use bevy_input::{keyboard::KeyCode, Input}; +use bevy_input::{keyboard::KeyCode, ButtonInput}; use bevy_reflect::prelude::*; use bevy_utils::hashbrown::HashSet; @@ -105,7 +105,7 @@ pub struct Deselect; /// Unsurprising default multiselect inputs: both control and shift keys. pub fn multiselect_events( - keyboard: Res>, + keyboard: Res>, mut pointer_query: Query<&mut PointerMultiselect>, ) { let is_multiselect_pressed = keyboard.any_pressed([ @@ -162,7 +162,7 @@ pub fn send_selection_events( pointer_location.to_owned(), entity, Deselect, - )) + )); } } } @@ -189,7 +189,7 @@ pub fn send_selection_events( if !pointer_down_list.contains(&id) && !multiselect { for (entity, selection) in selectables.iter() { if selection.is_selected { - deselections.send(Pointer::new(id, location.clone(), entity, Deselect)) + deselections.send(Pointer::new(id, location.clone(), entity, Deselect)); } } } @@ -212,26 +212,30 @@ pub fn send_selection_events( if let Ok((entity, selection)) = selectables.get(*target) { if multiselect { match selection.is_selected { - true => deselections.send(Pointer::new( - *pointer_id, - pointer_location.to_owned(), - entity, - Deselect, - )), - false => selections.send(Pointer::new( - *pointer_id, - pointer_location.to_owned(), - entity, - Select, - )), - } + true => { + deselections.send(Pointer::new( + *pointer_id, + pointer_location.to_owned(), + entity, + Deselect, + )); + } + false => { + selections.send(Pointer::new( + *pointer_id, + pointer_location.to_owned(), + entity, + Select, + )); + } + }; } else if !selection.is_selected { selections.send(Pointer::new( *pointer_id, pointer_location.to_owned(), entity, Select, - )) + )); } } } diff --git a/examples/bevy_ui.rs b/examples/bevy_ui.rs index 6dd7a121..2773b9bb 100644 --- a/examples/bevy_ui.rs +++ b/examples/bevy_ui.rs @@ -7,9 +7,10 @@ fn main() { App::new() .add_plugins(DefaultPlugins.set(low_latency_window_plugin())) .add_plugins(DefaultPickingPlugins) - .add_systems(Startup, (setup, setup_3d)) - .add_systems(Update, update_button_colors) + .add_systems(Startup, (setup_3d, setup_ui).chain()) + .add_systems(Update, (update_button_colors, set_camera_viewports)) .insert_resource(UiScale(1.5)) + .insert_resource(DebugPickingMode::Normal) .run(); } @@ -27,7 +28,7 @@ fn update_button_colors( } } -fn setup(mut commands: Commands) { +fn setup_ui(mut commands: Commands, camera: Query>) { let root = commands .spawn(( NodeBundle { @@ -50,6 +51,7 @@ fn setup(mut commands: Commands) { // width of the buttons, but will be invisible. Try commenting out this line or changing // it to see what happens. Pickable::IGNORE, + TargetCamera(camera.single()), )) .id(); @@ -68,16 +70,19 @@ fn setup_3d( ) { commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Plane::from_size(5.0))), - material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), + mesh: meshes.add(bevy_render::mesh::PlaneMeshBuilder { + half_size: Vec2::splat(2.5), + ..default() + }), + material: materials.add(Color::rgb(0.3, 0.5, 0.3)), ..default() }, PickableBundle::default(), // <- Makes the mesh pickable. )); commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), - material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), + mesh: meshes.add(Cuboid::default()), + material: materials.add(Color::rgb(0.8, 0.7, 0.6)), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() }, @@ -85,28 +90,82 @@ fn setup_3d( )); commands.spawn(PointLightBundle { point_light: PointLight { - intensity: 1500.0, shadows_enabled: true, ..default() }, transform: Transform::from_xyz(4.0, 8.0, -4.0), ..default() }); - commands.spawn((Camera3dBundle { - transform: Transform::from_xyz(3.0, 3.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y), - camera: Camera { - order: 1, + commands.spawn(( + Camera3dBundle { + transform: Transform::from_xyz(0.0, 20.0, -10.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }, - ..default() - },)); + LeftCamera, + )); + + commands.spawn(( + Camera3dBundle { + transform: Transform::from_xyz(10.0, 10., 15.0).looking_at(Vec3::ZERO, Vec3::Y), + camera: Camera { + // don't clear on the second camera because the first camera already cleared the + // window + clear_color: ClearColorConfig::None, + // Renders the right camera after the left camera, which has a default priority + // of 0 + order: 1, + ..default() + }, + ..default() + }, + RightCamera, + )); +} + +#[derive(Component)] +struct LeftCamera; + +#[derive(Component)] +struct RightCamera; + +fn set_camera_viewports( + windows: Query<&Window>, + mut resize_events: EventReader, + mut left_camera: Query<&mut Camera, (With, Without)>, + mut right_camera: Query<&mut Camera, With>, +) { + // We need to dynamically resize the camera's viewports whenever the window size changes so then + // each camera always takes up half the screen. A resize_event is sent when the window is first + // created, allowing us to reuse this system for initial setup. + for resize_event in resize_events.read() { + let window = windows.get(resize_event.window).unwrap(); + let mut left_camera = left_camera.single_mut(); + left_camera.viewport = Some(bevy::render::camera::Viewport { + physical_position: UVec2::new(0, 0), + physical_size: UVec2::new( + window.resolution.physical_width() / 2, + window.resolution.physical_height(), + ), + ..default() + }); + + let mut right_camera = right_camera.single_mut(); + right_camera.viewport = Some(bevy::render::camera::Viewport { + physical_position: UVec2::new(window.resolution.physical_width() / 2, 0), + physical_size: UVec2::new( + window.resolution.physical_width() / 2, + window.resolution.physical_height(), + ), + ..default() + }); + } } trait NewButton { fn add_button(self, text: &str) -> Self; } -impl<'w, 's, 'a> NewButton for EntityCommands<'w, 's, 'a> { +impl<'a> NewButton for EntityCommands<'a> { fn add_button(mut self, text: &str) -> Self { let text_string = text.to_string(); let child = self diff --git a/examples/debug.rs b/examples/debug.rs index f93154f8..bc160a89 100644 --- a/examples/debug.rs +++ b/examples/debug.rs @@ -1,165 +1,66 @@ -//! Shows how to toggle debug logging and the pointer debug overlay at runtime -//! -//! This is all essentially identical to bevy_ui, except the buttons -//! are configured to send custom events, and new small systems which -//! react to the button clicks. `cycle_logging()` shows how to change -//! the State which controls debug log verbosity. -//! -//! Note that the visual overlay next to the pointer is enabled with -//! debug logging on, and disabled when it is off. +//! Demonstrates how to change debug settings -use bevy::{app::AppExit, log::LogPlugin}; -use bevy::{ecs::system::EntityCommands, prelude::*}; -use bevy_eventlistener::prelude::*; +use bevy::prelude::*; use bevy_mod_picking::prelude::*; -use bevy_utils::tracing::Level; - -// See bevy_eventlistener. In particular, look at the event_listeners.rs example. -#[derive(Clone, Event)] -struct CycleLogging(Entity); - -impl From>> for CycleLogging { - fn from(event: ListenerInput>) -> Self { - CycleLogging(event.target) // you could use this to choose between different buttons - } -} - -// change log verbosity by cycling through the DebugPickingMode state -fn cycle_logging( - logging_state: Res>, - mut logging_next_state: ResMut>, -) { - match logging_state.get() { - debug::DebugPickingMode::Normal => { - info!("Changing state from Normal to Noisy."); - logging_next_state.set(debug::DebugPickingMode::Noisy); - } - debug::DebugPickingMode::Noisy => { - info!("Changing state from Noisy to Disabled."); - logging_next_state.set(debug::DebugPickingMode::Disabled); - } - debug::DebugPickingMode::Disabled => { - info!("Changing state from Disabled to Normal."); - logging_next_state.set(debug::DebugPickingMode::Normal); - } - } -} - -// basically same as above, but does something different. -#[derive(Clone, Event)] -struct Shutdown; - -impl From>> for Shutdown { - fn from(_event: ListenerInput>) -> Self { - Shutdown - } -} - -fn shutdown(mut exit_events: EventWriter) { - exit_events.send(AppExit); -} fn main() { App::new() .add_plugins( DefaultPlugins .set(low_latency_window_plugin()) - .set(LogPlugin { + .set(bevy::log::LogPlugin { filter: "bevy_mod_picking=trace".into(), // Show picking logs trace level and up - level: Level::ERROR, // Show all other logs only at the error level and up + ..default() }), ) .add_plugins(DefaultPickingPlugins) - .add_event::() - .add_event::() - .add_systems(Startup, (setup, setup_3d)) - .add_systems(Update, update_button_colors) - // add our button-event response systems, set to only run when the - // respective events are triggered. - .add_systems(Update, cycle_logging.run_if(on_event::())) - .add_systems(Update, shutdown.run_if(on_event::())) - .run(); -} - -// Everything below this line is identical to what's in bevy_ui, except the event listener is passed -// to .add_button along with the text to display. -// ---------------------------------------------------------------------------- - -/// Use the [`PickingInteraction`] state of each button to update its color. -fn update_button_colors( - mut buttons: Query<(Option<&PickingInteraction>, &mut BackgroundColor), With