Skip to content

Add Hidden to CursorIcon and CursorSource enums #15386

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

fernanlukban
Copy link
Contributor

@fernanlukban fernanlukban commented Sep 23, 2024

Objective

  • Unifies the old winit cursor api (window.cursor.visible = false) with the new old winit cursor api (CursorIcon implemented in Add custom cursors #14284)
    • Adds a Hidden enum variant for CursorIcon and related CursorSource in the consuming API
  • Fixes Add CursorIcon::Hidden #15379

Solution

I just decided to add Hidden variant to the enum, then decided to fix the errors one by one to find the places where the match guards weren't being exhausted properly. Once I saw that it gets consumed in the update_cursors function, I decided to see how it gets used to actually change the cursor. Looking at #14284, I saw that there was an intermediary CursorSource enum that would get used to actually create the cursor. From what I understand, this enum was created becase we can either have system cursors that exist on our OS or a custom one that we define. My thought process then, was to propagate the Hidden value to this struct and then instead of setting the actual cursor source, figure out how to turn off the visibility. I looked at where the window.cursor.visibility value that the old winit api was using and then saw that we could set winit_window.set_cursor_visible(false).

The only thing I'm worried about now is that I'm wondering about if there's a performance hit of doing this way. I haven't really gotten the time to look through the code properly, but previously we were not changing the visibility of this at all. Right now, I am changing the visibility whenever we are updating the cursors. I'm thinking that since this doesn't get changed too often, changing the visibility should just be minimal impact.

The only thing I wanted to do was something along the lines of

winit_window.set_cursor_visible(final_cursor.is_some())

but I am quite noob at rust and couldn't figure out how to fight back at the borrow checker.

Also, if there are any docs that I should update please let me know.

Testing

  • Built on top of the Add custom cursors #14284 example that was updated, added the enum to the variant and ran, working at least for macos, will be able to test on windows when I get back home in two days
trimmed.mov

Migration Guide

  • This might be a breaking change depending on if users ever matched on the CursorIcon enum, though I'm not really sure if they would ever

Copy link
Contributor

Welcome, new contributor!

Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨

@alice-i-cecile alice-i-cecile added A-Windowing Platform-agnostic interface layer to run your app in D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Sep 23, 2024
@alice-i-cecile
Copy link
Member

I'm thinking that since this doesn't get changed too often, changing the visibility should just be minimal impact.

100% agreed :)

@alice-i-cecile alice-i-cecile added the C-Usability A targeted quality-of-life change that makes Bevy easier to use label Sep 23, 2024
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice! No complaints here; this is how I would have done it (including chasing errors after adding the variant).

@alice-i-cecile alice-i-cecile added this to the 0.15 milestone Sep 23, 2024
Copy link
Member

@tychedelia tychedelia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conflicts with the existing CursorOptions field on Window that sets the visibility in changed_windows. I'm okay with considering the more ECS focused approached, but we should minimally remove visible from CursorOptions, as well as documenting the platform specific gotchas on the enum here, as well as explore adding a CursorGrabMode component and I guess HitTest too? Idk, now I'm also like that's too ECS-y lol, but minimally we need to resolve what to do with the existing window settings.

@alice-i-cecile alice-i-cecile added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Sep 23, 2024
@eero-lehtinen
Copy link
Contributor

With this design it becomes harder to toggle visibility. You would have to store the previous cursor somewhere to unhide it later. But it might not matter too much.

I think this option would fit better as a boolean field within the cursor component, but that unfortunately breaks change detection. Could we use the same diffing system that we use for the window settings, where we store a copy of the previous window component and that way get ergonomics of a single unified component and field level change detection?

@alice-i-cecile alice-i-cecile added the X-Contentious There are nontrivial implications that should be thought through label Sep 23, 2024
@fernanlukban
Copy link
Contributor Author

I kind of get the gist of it and agree with the ergonomics. Do you have a pointer of the window settings thing you're referencing?

@tychedelia
Copy link
Member

I kind of get the gist of it and agree with the ergonomics. Do you have a pointer of the window settings thing you're referencing?

Look at the CursorOptions struct in window.rs.

Fernan Lukban added 3 commits September 24, 2024 00:27
@@ -126,7 +126,7 @@ impl NormalizedWindowRef {
)]
#[reflect(Component, Default, Debug)]
pub struct Window {
/// The cursor options of this window. Cursor icons are set with the `Cursor` component on the
/// The cursor options of this window. Cursor icons are set with the `CursorIcon` component on the
Copy link
Member

@Vrixyz Vrixyz Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 ; I think that's important to note to ease with migration, that being said this property actually refers to bevy_render, and it's annoying that we can't link to it through a real link :( (also, should we at all ? 🤔)

Or should CursorIcon be brought in bevy_window ?

(Those questions are probably outside this PR scope, sorry for derailing :) )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no worries! we have had a whole conversation about it inside a discord thread

tl;dr ideally this would live inside of bevy_winit, to keep ergonomics the same since most of the use cases regarding icons right now involve Query<&Window> and reading the cursor_options field which is of the CursorOptions type, which originally had CursorIcon. Now that we have custom cursor support, this had to live inside bevy_render because the custom cursors require access to Image which can't be imported inside of bevy_winit nor bevy_window due to circular dependencies.

though I do agree that the comment should be linked properly - I tried adding the fully qualified name on another comment, but I wasn't sure about how to run the docs to check if it worked locally (i'm still a rust toolchain noob)

Copy link
Member

@Vrixyz Vrixyz Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the insight !

I tried adding the fully qualified name on another comment

Nice try! the CI docs step fails due to that. Check it out (and I recommend looking at the .github workflow file to see more details on what commands are run 👍

I fear it's impossible to link because bevy_window doesn't rely on bevy_render :(

well I imagine we theoretically could by adding the dependency if building docs, but I personally wouldn't approve that :/

Continued rambling about CursorIcon

I'd prefer a CursorIcon::Custom empty enum variant, and let the other crates handle their custom thing however they want 🤔.

Or a generic Custom ? or a Box with a dyn trait something inside ?

Or straight up remove any mention to those classes and embrace this split 🤔

I wasn't sure about how to run the docs

cargo doc --open will bring you far enough 🤞

Copy link
Contributor

@eero-lehtinen eero-lehtinen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm certainly in favor of moving cursor related options to the cursor component, but it feels weird losing the ability that winit provides to separately toggle visibility and choose the displayed icon. I don't have super strong feelings about though, this way is ok too.

The change to automatically add the cursor component to every window is good. I should have done it that way before. Though I think observers would work better in avoiding scheduling issues.

@@ -149,6 +149,8 @@ pub struct CustomCursorCache(pub HashMap<CustomCursorCacheKey, winit::window::Cu
/// A source for a cursor. Is created in `bevy_render` and consumed by the winit event loop.
#[derive(Debug)]
pub enum CursorSource {
/// A hidden cursor, used to hide the cursor in `winit_window`
Hidden,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should propagate cursor visibility to bevy_winit. Custom cursors are required to be changed in such a convoluted way because they require access to the winit event loop. Cursor visibility can be changed directly with NonSendMut<WinitWindows> in any system just like it was done before with cursor_options.

Copy link
Contributor Author

@fernanlukban fernanlukban Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm starting to think what we really need to do is do something along the lines of not having a cursor from being instead Hidden to just not having a cursor source or None. This clearly denotes the feature set each cursor source has - the SystemCursor is heavily part of the winit package we use, hence the related feature set. Now that we have CursorSource::Custom, if we think that over time it ends up having a different feature set, maybe something like if we were to support gifs as cursors, or sprite sheets as cursors with animations, there might be options relating how fast to play the gif or whether or not to loop it's animation. This way, most of the examples will look almost the same in functionality, just changing a bool

pub enum CursorSource {
    None,
    Custom{ custom_cursor: CustomCursor, visibility: bool },
    System{ system_cursor: SystemCursor, visibility: bool },
}

Overtime, I want to probably work on moving the system related CursorOptions found in bevy_window::Window and moving them with the CursorIcon.

I would ideally like to have these all live in the same crate so maybe looking at breaking the circular dependencies next is the thing to do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As to the

Cursor visibility can be changed directly with NonSendMut<WinitWindows> in any system just like it was done before with cursor_options.

I don't get quite this means? Are you suggesting that if it isn't a CustomCursor then we should just use the system winit related API directly? I think we were initially against that because we would have two API's and it could get confusing, so I would like your take on it.

Copy link
Contributor

@eero-lehtinen eero-lehtinen Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I would be proposing is to just move cursor options from the window component to the cursor component and doing absolutely everything the exact same it was done before and make a new cursor component.

struct Cursor {
    pub visible: bool,
    ... other options ...
    pub icon: CursorIcon,
}

Then move everything that was done in the window update system to the cursor update system.

But I that is a different proposal than what is included in the related issue, so it might not be relevant to this PR.

I don't get quite this means? Are you suggesting that if it isn't a CustomCursor then we should just use the system winit related API directly? I think we were initially against that because we would have two API's and it could get confusing, so I would like your take on it.

Yes. CursorSource is an implementation detail for smuggling cursor icon bytes to the bevy_winit crate. It would lead to less spread out code and less complexity to just set the WinitWindows cursor visibility boolean in the bevy_render cursor system. It doesn't create any additional APIs. Functionally your code does the same thing so consider what would be simpler.

Copy link
Contributor

@eero-lehtinen eero-lehtinen Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have CursorSource::Custom, if we think that over time it ends up having a different feature set.

CursorSource is a direct equivalent to winits native cursors. Those are clearly defined and not flexible, so CursorSource doesn't need to be flexible.

E.g. if we want to use sprite sheets as animated cursors, those would need to be converted to be winit compatible outside of bevy_winit anyway because in can't use assets.

@@ -29,7 +28,7 @@ impl Plugin for CursorPlugin {
app.register_type::<CursorIcon>()
.init_resource::<CustomCursorCache>()
.add_systems(Last, update_cursors)
.add_systems(Update, add_cursor_to_windows);
.observe(add_cursor_to_windows);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eero-lehtinen this was quite pretty to do, I remember reading the example for this last week and now this seems so nice

@@ -19,9 +19,7 @@ fn grab_mouse(
// There are cases where we can have multiple windows and cursors,
// but not in this example. Keeping the naming convention since it
// exists in other examples
let Ok((mut window, mut cursor)) = windows_and_cursors.get_single_mut() else {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it ended up fixing the non-determinism thing here so that we will always have the cursor when windows are created which is perfect

@@ -154,6 +154,8 @@ pub struct CustomCursorCache(pub HashMap<CustomCursorCacheKey, winit::window::Cu
/// A source for a cursor. Is created in `bevy_render` and consumed by the winit event loop.
#[derive(Debug)]
pub enum CursorSource {
/// A hidden cursor, used to hide the cursor in `winit_window`
Hidden,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still missing documentation about platform specific behavior.

@alice-i-cecile alice-i-cecile removed this from the 0.15 milestone Oct 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Windowing Platform-agnostic interface layer to run your app in C-Usability A targeted quality-of-life change that makes Bevy easier to use D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add CursorIcon::Hidden
6 participants