diff --git a/Cargo.toml b/Cargo.toml index 251e47609645d..97069bad9ab00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1442,6 +1442,16 @@ description = "Illustrates creating and updating a button" category = "UI (User Interface)" wasm = true +[[example]] +name = "window_fallthrough" +path = "examples/ui/window_fallthrough.rs" + +[package.metadata.example.window_fallthrough] +name = "Window Fallthrough" +description = "Illustrates how to access `winit::window::Window`'s `hittest` functionality." +category = "UI (User Interface)" +wasm = false + [[example]] name = "font_atlas_debug" path = "examples/ui/font_atlas_debug.rs" diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index f0603771373cd..ab76fa1dc379a 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -286,6 +286,7 @@ pub struct Window { cursor_icon: CursorIcon, cursor_visible: bool, cursor_grab_mode: CursorGrabMode, + hittest: bool, physical_cursor_position: Option, raw_handle: Option, focused: bool, @@ -351,6 +352,10 @@ pub enum WindowCommand { SetCursorPosition { position: Vec2, }, + /// Set whether or not mouse events within *this* window are captured, or fall through to the Window below. + SetCursorHitTest { + hittest: bool, + }, /// Set whether or not the window is maximized. SetMaximized { maximized: bool, @@ -435,6 +440,7 @@ impl Window { cursor_visible: window_descriptor.cursor_visible, cursor_grab_mode: window_descriptor.cursor_grab_mode, cursor_icon: CursorIcon::Default, + hittest: true, physical_cursor_position: None, raw_handle, focused: false, @@ -777,7 +783,20 @@ impl Window { self.command_queue .push(WindowCommand::SetCursorPosition { position }); } - + /// Modifies whether the window catches cursor events. + /// + /// If true, the window will catch the cursor events. + /// If false, events are passed through the window such that any other window behind it receives them. By default hittest is enabled. + pub fn set_cursor_hittest(&mut self, hittest: bool) { + self.hittest = hittest; + self.command_queue + .push(WindowCommand::SetCursorHitTest { hittest }); + } + /// Get whether or not the hittest is active. + #[inline] + pub fn hittest(&self) -> bool { + self.hittest + } #[allow(missing_docs)] #[inline] pub fn update_focused_status_from_backend(&mut self, focused: bool) { @@ -961,6 +980,8 @@ pub struct WindowDescriptor { pub cursor_visible: bool, /// Sets whether and how the window grabs the cursor. pub cursor_grab_mode: CursorGrabMode, + /// Sets whether or not the window listens for 'hits' of mouse activity over _this_ window. + pub hittest: bool, /// Sets the [`WindowMode`](crate::WindowMode). /// /// The monitor to go fullscreen on can be selected with the `monitor` field. @@ -1013,6 +1034,7 @@ impl Default for WindowDescriptor { decorations: true, cursor_grab_mode: CursorGrabMode::None, cursor_visible: true, + hittest: true, mode: WindowMode::Windowed, transparent: false, canvas: None, diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 0b1b544eec17b..ec8478ea6e448 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -230,6 +230,10 @@ fn change_window( let window = winit_windows.get_window(id).unwrap(); window.set_always_on_top(always_on_top); } + bevy_window::WindowCommand::SetCursorHitTest { hittest } => { + let window = winit_windows.get_window(id).unwrap(); + window.set_cursor_hittest(hittest).unwrap(); + } bevy_window::WindowCommand::Close => { // Since we have borrowed `windows` to iterate through them, we can't remove the window from it. // Add the removal requests to a queue to solve this diff --git a/examples/README.md b/examples/README.md index 6b57cf3aec06e..e354e6f283120 100644 --- a/examples/README.md +++ b/examples/README.md @@ -319,6 +319,7 @@ Example | Description [UI](../examples/ui/ui.rs) | Illustrates various features of Bevy UI [UI Scaling](../examples/ui/ui_scaling.rs) | Illustrates how to scale the UI [UI Z-Index](../examples/ui/z_index.rs) | Demonstrates how to control the relative depth (z-position) of UI elements +[Window Fallthrough](../examples/ui/window_fallthrough.rs) | Illustrates how to access `winit::window::Window`'s `hittest` functionality. ## Window diff --git a/examples/ui/window_fallthrough.rs b/examples/ui/window_fallthrough.rs new file mode 100644 index 0000000000000..eba60c1e0a137 --- /dev/null +++ b/examples/ui/window_fallthrough.rs @@ -0,0 +1,60 @@ +//! This example illustrates how have a mouse's clicks/wheel/movement etc fall through the spawned transparent window to a window below. +//! If you build this, and hit 'P' it should toggle on/off the mouse's passthrough. + +use bevy::prelude::*; + +fn main() { + // Set the window's parameters, note we're setting always_on_top to be true. + let window_desc = WindowDescriptor { + transparent: true, + decorations: true, + always_on_top: true, + ..default() + }; + + App::new() + .insert_resource(ClearColor(Color::NONE)) // Use a transparent window, to make effects obvious. + .add_plugins(DefaultPlugins.set(WindowPlugin { + window: window_desc, + ..default() + })) + .add_startup_system(setup) + .add_system(toggle_mouse_passthrough) // This allows us to hit 'P' to toggle on/off the mouse's passthrough + .run(); +} + +fn setup(mut commands: Commands, asset_server: Res) { + // UI camera + commands.spawn(Camera2dBundle::default()); + // Text with one section + commands.spawn(( + // Create a TextBundle that has a Text with a single section. + TextBundle::from_section( + // Accepts a `String` or any type that converts into a `String`, such as `&str` + "Hit 'P' then scroll/click around!", + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 100.0, // Nice and big so you can see it! + color: Color::WHITE, + }, + ) + // Set the style of the TextBundle itself. + .with_style(Style { + position_type: PositionType::Absolute, + position: UiRect { + bottom: Val::Px(5.), + right: Val::Px(10.), + ..default() + }, + ..default() + }), + )); +} +// A simple system to handle some keyboard input and toggle on/off the hittest. +fn toggle_mouse_passthrough(keyboard_input: Res>, mut windows: ResMut) { + if keyboard_input.just_pressed(KeyCode::P) { + let window = windows.primary_mut(); + let hittest: bool = window.hittest(); + window.set_cursor_hittest(!hittest); + } +}