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

Cannot set winit cursor icon in Bevy #229

Open
JeanMertz opened this issue Nov 23, 2023 · 6 comments
Open

Cannot set winit cursor icon in Bevy #229

JeanMertz opened this issue Nov 23, 2023 · 6 comments

Comments

@JeanMertz
Copy link

I have a relatively simple app, which has a few egui windows, and a regular Bevy 2d canvas to which I paint some shapes. I also have a system that queries &mut Window and then changes window.cursor.icon = CursorIcon::Hand.

This didn't seem to work, even though there is an actual window_settings example in Bevy that does something similar, and works as expected.

After some debugging, I noticed that disabling this plugin allows me to set the cursor in Bevy again.

I should note that when I'm setting the cursor, the pointer is not hovering over (or interacting with) any egui windows.

Is there a way to get this to work as expected?

@JeanMertz
Copy link
Author

I should note that I tried to set the cursor using

ctx.set_cursor_icon(bevy_egui::egui::CursorIcon::PointingHand);

And I can see the cursor change for one frame, but then change back again. I can only assume this is because my non-ui system doesn't run on every tick (it doesn't need to), but egui — being an immediate mode GUI library — expects this to be called each frame, and resets it back to the default cursor if the cursor isn't explicitly changed on a given frame.

Also, aside from the resetting issue, there is also the architectural issue of my system, that doesn't involve anything related to any egui windows, now needs to know about EguiContexts, which isn't what I'd want to see happen (e.g. I want to contain the need/usage of EguiContexts to any system that explicitly deals with the egui UI parts of my app).

@JeanMertz
Copy link
Author

I've worked around this for now by introducing a new Cursor resource, which other systems can change, and then the egui rendering system sets the correct cursor on every frame.

It works, but it might be considered a workaround, not the actual solution. I'll let you be the judge of that, feel free to close this ticket if this is working as intended and the global resource-approach is the correct one.

@lgrossi
Copy link

lgrossi commented Jan 16, 2024

I'm facing the same issue. This plugin hijacks the cursor control and it becomes egui responsibility, even in windows that are non egui related. I'd argue that this is indeed a bug, since egui context should be isolated from bevy context. Not sure if this is in the radar, but would be nice to have a definitive solution for that.

@wiggleforlife
Copy link

Also facing this! @JeanMertz I used your solution, but now egui is of course not able to change the icon when the cursor is resizing an egui window, etc. Did you have a way around this?

@JeanMertz
Copy link
Author

@wiggleforlife I do not, as I haven't had a need for custom cursors in egui. One solution that might work is checking if "egui wants focus", and then not applying my above logic. There's an example in the bevy_pancam plugin on how to check if egui has focus or not:

https://github.com/johanhelsing/bevy_pancam/blob/205f07ba164e5d6602c198440f7bfe4713b336d4/src/lib.rs#L40-L58

@dimvoly
Copy link
Contributor

dimvoly commented Jul 10, 2024

I did do a "workaround" too. I've added a [Resource] into bevy_egui which lets you hijack its processing of the cursor.

I cloned the bevy_egui library and added this resource:

/// What the cursor should be when it isn't over a window.
#[derive(Resource, Default)]
pub struct EguiDefaultCursor(pub bevy::prelude::CursorIcon);

You'd initialize it:

impl Plugin for EguiPlugin {
    fn build(&self, app: &mut App) {
        let world = &mut app.world;
        // ...
        world.init_resource::<EguiDefaultCursor>();
       // ...
    }
}

Then the hijacking happens here in systems.rs:

/// Reads Egui output.
pub fn process_output_system(
    // ...
    default_cursor: Res<EguiDefaultCursor>,
) {
    for mut context in contexts.iter_mut() {
        // ...

        // Check if we're over ANY egui windows with the mouse.
        let egui_using_cursor = (!{
            ctx.is_pointer_over_area() || ctx.is_using_pointer() || {
                ctx.pointer_latest_pos()
                    .map(|vec2| {
                        // Manually check if we're in the top ribbon panel
                        // (the above two checks don't sense panels).
                        vec2.y < 147.0
                    })
                    .unwrap_or_default()
            }
        })
        .then(|| default_cursor.0);

        let mut set_icon = || {
            context.window.cursor.icon = egui_using_cursor.unwrap_or_else(|| {
                egui_to_winit_cursor_icon(platform_output.cursor_icon)
                    .unwrap_or(bevy::window::CursorIcon::Default)
            });
        };
        
        // ...

        
    }
}

Likely most people won't have a top panel, so you can skip the pointer_latest_pos check.

Then in your code, you set the cursor that you want in the [EguiDefaultCursor] resource.

You do end up having bevy_egui as a pretty low level dependency. Pretty hacky but works for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants