Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Requesting cursor position from window doesn't return expected result #9622

Open
kALLEBALIK opened this issue Aug 29, 2023 · 4 comments
Open
Labels
A-Input Player input via keyboard, mouse, gamepad, and more A-Windowing Platform-agnostic interface layer to run your app in C-Bug An unexpected or incorrect behavior S-Blocked This cannot move forward until something else changes

Comments

@kALLEBALIK
Copy link

I'm trying to implement drag and drop between two separate windows and came across something that i think might be a bug.

I'm iterating over all windows to check whether the window contains the cursor or not. This usually works as expected and window.cursor_position() returns the position of the cursor if it hovers the window. But if you start a drag in one window and move the mouse over to another window window.cursor_position() will return None for all windows except for the window where you started your drag. This behavior is shared by events like CursorMoved, CursorEntered and CursorLeft where the leave and enter events are not fired until you release the mouse button.

One could argue that regarding the events this is expected and wanted behavior since this is the exact behavior in Winit. But i think that when we request the cursor position from a window in Bevy it should return the position regardless if we initiated a drag in another window.

Requesting cursor position test in Bevy:

use bevy::prelude::*;

fn main() {
    let mut app = App::new();
    app.add_plugins(DefaultPlugins)
       .add_systems(Startup, spawn_second_window)
       .add_systems(Update, print_window)
       .run();
}

fn spawn_second_window(mut commands: Commands) {
    commands.spawn(Window::default());
}

pub fn print_window(
    mut window_q: Query<(Entity, &mut Window)>,
    mut cursor_moved_events: EventReader<CursorMoved>,
    mut cursor_entered_events: EventReader<CursorEntered>,
    mut cursor_left_events: EventReader<CursorLeft>,
) {
    for (window_entity, window) in window_q.iter_mut() {
        if let Some(_) = window.cursor_position() {
            println!("cursor position window: {:?}", window_entity);
        }
    }

    for moved_event in cursor_moved_events.iter() {
        println!("moved inside window: {:?}", moved_event.window);
    }

    for entered_event in cursor_entered_events.iter() {
        println!("entered window: {:?}", entered_event.window);
    } 

    for left_event in cursor_left_events.iter() {
        println!("left window: {:?}", left_event.window);
    } 
}

Testing Winit cursor events:

use std::collections::HashMap;
use winit::{
    event::{Event, WindowEvent},
    event_loop::EventLoop,
    window::WindowBuilder
};

fn main() {
    let event_loop = EventLoop::new();

    let mut windows = HashMap::new();
    for _ in 0..2 {
        let window = WindowBuilder::new().build(&event_loop).unwrap();
        windows.insert(window.id(), window);
    }

    event_loop.run(move |event, _, control_flow| {
        control_flow.set_wait();

        match event {
            Event::WindowEvent { event, window_id } => {
                match event {
                    WindowEvent::CursorMoved { .. } => {
                        println!("CursorMoved: {window_id:?}");
                    }
                    WindowEvent::CursorEntered { .. } => {
                        println!("CursorEntered: {window_id:?} ");
                    }
                    WindowEvent::CursorLeft { .. } => {
                        println!("CursorLeft: {window_id:?} ");
                    }
                    _ => (),
                }
            }
            _ => (),
        }
    })
}

Workaround

I thought there might be some sort of workaround where you just calculate the bounds of all windows and check if the mouse is inside. But then we get to the z-ordering issue and it does not seem to be possible to get the z-order of a window in Bevy or Winit.

If someone have an idea here I would appreciate it because right now I'm stuck...

System information

OS: Windows 11
Bevy version: 0.11.2
Winit version: 0.28.6 (for the winit test)

@kALLEBALIK kALLEBALIK added C-Bug An unexpected or incorrect behavior S-Needs-Triage This issue needs to be labelled labels Aug 29, 2023
@SkiFire13
Copy link
Contributor

This is due to winit capturing the mouse when a mouse button is pressed down. This in turn makes the native WM_MOUSEMOVE event fire only for the window that captured the mouse, and that event is the source for CursorMoved, CursorEntered, CursorLeft and bevy's cursor_position.

I don't see a way in winit to avoid capturing the mouse, and thus for bevy to change this behaviour.

@alice-i-cecile alice-i-cecile added A-Input Player input via keyboard, mouse, gamepad, and more A-Windowing Platform-agnostic interface layer to run your app in S-Blocked This cannot move forward until something else changes and removed S-Needs-Triage This issue needs to be labelled labels Aug 29, 2023
@Aceeri
Copy link
Member

Aceeri commented Aug 30, 2023

One way I think we could patch over this is by directly querying each platform (or making a library for doing so for mouse location). For prior art: https://crates.io/crates/device_query

Edit: Some discussion on discord, previous pr about cursor position here might be the problem: #8855

@kALLEBALIK
Copy link
Author

The only solution i could think of is to to use the win32 api and enumerate over the windows (which return z-ordered) and return the window with the lowest z-index that contains the mouse position.

This at least made me able to continue my project for now.

@benfrankel
Copy link
Contributor

This issue may be related: #8507.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Input Player input via keyboard, mouse, gamepad, and more A-Windowing Platform-agnostic interface layer to run your app in C-Bug An unexpected or incorrect behavior S-Blocked This cannot move forward until something else changes
Projects
None yet
Development

No branches or pull requests

5 participants