-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
CustomCursor::Image could use a custom size option #17276
Comments
I started looking at this and it got me wondering about the best behavior for CursorIcon::Custom(CustomCursor::Image {
handle: asset_server.load("rotate-cursor.png"), // source is 32x32
texture_atlas: None,
flip_x: true,
flip_y: false,
rect: None,
hotspot: (15, 14), // should users pass the hotspot that matches the source or the transformed image?
custom_size: Some((64, 64).into()), // x2
}) Also note that the cursor has Should users provided the final Another alternative is to not add CursorIcon::Custom(CustomCursor::Image {
handle: asset_server.load("rotate-cursor.png"), // source is 32x32
texture_atlas: None,
flip_x: true,
flip_y: false,
rect: None,
hotspot: (15, 14), // should users pass the hotspot that matches the source or the transformed image?
scale: Some((2.0, 2.0).into()),
}) With regards to Another benefit of A downside of At least for my own use case, I actually just want to scale my cursors up by |
Scale makes sense to me and should cover most use-cases. |
Scale being So this either makes scale a non-starter, or we need to remove |
Proposed type with #[derive(Debug, Clone, Reflect, PartialEq)]
pub enum CustomCursor {
/// Image to use as a cursor.
Image {
/// The image must be in 8 bit int or 32 bit float rgba. PNG images
/// work well for this.
handle: Handle<Image>,
/// The (optional) texture atlas used to render the image.
texture_atlas: Option<TextureAtlas>,
/// Whether the image should be flipped along its x-axis.
///
/// If true, the cursor's hotspot will be flipped along with the image.
/// You don't need to adjust the `hotspot` that you provide to account
/// for the flip.
flip_x: bool,
/// Whether the image should be flipped along its y-axis.
///
/// If true, the cursor's hotspot will be flipped along with the image.
/// You don't need to adjust the `hotspot` that you provide to account
/// for the flip.
flip_y: bool,
/// An optional rectangle representing the region of the image to
/// render, instead of rendering the full image. This is an easy one-off
/// alternative to using a [`TextureAtlas`].
///
/// When used with a [`TextureAtlas`], the rect is offset by the atlas's
/// minimal (top-left) corner position.
rect: Option<URect>,
/// X and Y coordinates of the hotspot in pixels. The hotspot must be
/// within the image bounds.
///
/// If you are flipping or scaling the image using `flip_x`, `flip_y`,
/// or `scale`, you don't need to adjust the `hotspot` that you provide
/// to account for the flip or scale: it will be adjusted automatically.
hotspot: (u16, u16),
/// An optional scale factor to apply to the image.
///
/// If provided, the image will be scaled by this factor. The cursor's
/// hotspot will be scaled along with the image. You don't need to
/// adjust the `hotspot` that you provide to account for the scale.
scale: Option<Vec2>,
#[reflect(ignore)]
/// An optional filter to apply when scaling the image. If not provided,
/// defaults to linear filtering.
scale_filter: Option<imageops::FilterType>,
},
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
/// A URL to an image to use as the cursor.
Url {
/// Web URL to an image to use as the cursor. PNGs preferred. Cursor
/// creation can fail if the image is invalid or not reachable.
url: String,
/// X and Y coordinates of the hotspot in pixels. The hotspot must be
/// within the image bounds.
hotspot: (u16, u16),
},
} Edit: To support reflect, the We could consider merging the |
POC.
Screen.Recording.2025-01-23.at.3.08.09.pm.mov |
Can you explain a little about why you included |
If the image is normally 32x32 and you set let resized = dyn_image.resize(
(width * scale.x) as u32,
(height * scale.y) as u32,
scale_filter.unwrap_or(image::imageops::FilterType::Nearest),
); To do the resizing, and it needs users to tell it what filter to use when scaling. As an aside: To answer your question practically, this is one of my cursors scaled to 1.5x with the default / nearest.movAnd with calmullrom.movTherefore, I did this to have choice here. |
Got it, that's a compelling example thanks! |
Can you open a PR with the work you've done? It looks good to me, and further design changes can be done after review if necessary. |
Absolutely (will push as soon as I can). I think the removal of @marioferpa when you see the activity here, can you let us know if/how it addresses your original request? |
Yes it does, actually it goes above and beyond, I didn't think of having a texture atlas in ther for example, and that seems super useful. |
@marioferpa cool. Note texture atlas support already exists in main, as of a few days before/after you opening this issue, and will be in Bevy 0.16. |
@eero-lehtinen if you get a chance, feel free to offer your input on the design aspects in this issue. |
This is definitely convenient, but I'm not sure if it's smart because image scaling is a complicated issue. The results of arbitrary scalings are almost guaranteed to look bad. Also if people are expecting smooth results from lerping the cursor scale, that won't really happen because the OS APIs are restricted to a strict pixel grid. It can also waste a lot of memory because we cache all cursors. So I guess it might be fine to add this for convenience, but instruct people to not rely on it. Instead preferring to author mindfully scaled source images, or just use sprites or other "software cursors" if they really want freely scalable cursors (also allows to do crazier things like rotations). Also as a side note we should probably split a struct out of |
|
It should not be necessary to cache all scales of the same image. Only the most recent scale.
Software cursors are a non-option for many games due to input lag. |
Some platforms just do their own upscaling and there is nothing winit can do. At least web does this, but I'm pretty sure other platforms don't usually do that. |
Yep, makes sense. I have a builder here: https://github.com/mgi388/bevy-cursor-kit/blob/main/src/builder.rs but hopefully we can just use a struct inside Bevy and maybe my builder will become redundant.
At least for my own use case, I don't want to animate the scale I just want to be able to upscale and pick a filtering option (this also means the cache isn't a problem because I'm not asking Bevy to cache N different scales for each frame). However, I also know that I can do this in a "preprocessing" step outside of Bevy similar to how I do color keying in my I think I could go either way on whether this exists in Bevy or not. I suppose other texture things in Bevy get image scaling "out of the box" e.g. I think you can scale sprites and UI images, right? So having cursor image scaling may be uncontroversial from a parity perspective. Even if "image scaling is a complicated issue", would it be fair to say that it becomes less complicated if we consider that |
@mgi388 If you make a PR I can approve it. I guess it's nice to have the option even if its slightly unoptimal to use automatic upscaling. |
OS cursors are just janky, not much can be done. |
Or there might be more complicated OS APIs available that allow specifying images for different scales, but winit doesn't support them. That likely requires the use of platform specific cursor files and this simple way of specifying pixels is not enough. |
@eero-lehtinen do you have any opinions on the hotspot question? I think it would be worth formally deciding on that question. My opinion is:
But once you add |
I agree. |
Yeah I started looking at the winit PRs and quickly saw the sentiment seemed to be "there's not really enough / consistent OS support here to do anything atm". |
It could be pretty easy to implement at least for windows. Winit can already load icons from resources. https://docs.rs/winit/latest/x86_64-pc-windows-msvc/winit/platform/windows/trait.IconExtWindows.html#tymethod.from_resource Though at that point the implementation would be pretty annoying because we would need to generate windows cursor resources at runtime or implement another cursor type. |
I'll push out some PRs as soon as I can:
That should help reviewers, and also keep the most controversial one not blocking the other improvements. |
…17518) # Objective - Make `CustomCursor::Image` easier to work with by splitting the enum variants off into `CustomCursorImage` and `CustomCursorUrl` structs and deriving `Default` on those structs. - Refs #17276. ## Testing - Ran two examples: `cargo run --example custom_cursor_image --features=custom_cursor` and `cargo run --example window_settings --features=custom_cursor` - CI. --- ## Migration Guide The `CustomCursor` enum's variants now hold instances of `CustomCursorImage` or `CustomCursorUrl`. Update your uses of `CustomCursor` accordingly.
…#17540) # Objective - As discussed in #17276 (comment), we should transform the cursor's hotspot if the user is asking for the image to be flipped. - This becomes more important when a `scale` transform option exists. It's harder for users to transform the hotspot themselves when using `scale` because they'd need to look up the image to get its dimensions. Instead, we let Bevy handle the hotspot transforms and make the `hotspot` field the "original/source" hotspot. - Refs #17276. ## Solution - When the image needs to be transformed, also transform the hotspot. If the image does not need to be transformed (i.e. fast path), no hotspot transformation is applied. ## Testing - Ran the example: `cargo run --example custom_cursor_image --features=custom_cursor`. - Add unit tests for the hotspot transform function. - I also ran the example I have in my `bevy_cursor_kit` crate, which I think is a good illustration of the reason for this PR. - In the following videos, there is an arrow pointing up. The button hover event fires as I move the mouse over it. - When I press `Y`, the cursor flips. - In the first video, on `bevy@main` **before** this PR, notice how the hotspot is wrong after flipping and no longer hovering the button. The arrow head and hotspot are no longer synced. - In the second video, on the branch of **this** PR, notice how the hotspot gets flipped as soon as I press `Y` and the cursor arrow head is in the correct position on the screen and still hovering the button. Speaking back to the objective listed at the start: The user originally defined the _source_ hotspot for the arrow. Later, they decide they want to flip the cursor vertically: It's nice that Bevy can automatically flip the _source_ hotspot for them at the same time it flips the _source_ image. First video (main): https://github.com/user-attachments/assets/1955048c-2f85-4951-bfd6-f0e7cfef0cf8 Second video (this PR): https://github.com/user-attachments/assets/73cb9095-ecb5-4bfd-af5b-9f772e92bd16
…evyengine#17518) # Objective - Make `CustomCursor::Image` easier to work with by splitting the enum variants off into `CustomCursorImage` and `CustomCursorUrl` structs and deriving `Default` on those structs. - Refs bevyengine#17276. ## Testing - Ran two examples: `cargo run --example custom_cursor_image --features=custom_cursor` and `cargo run --example window_settings --features=custom_cursor` - CI. --- ## Migration Guide The `CustomCursor` enum's variants now hold instances of `CustomCursorImage` or `CustomCursorUrl`. Update your uses of `CustomCursor` accordingly.
…bevyengine#17540) # Objective - As discussed in bevyengine#17276 (comment), we should transform the cursor's hotspot if the user is asking for the image to be flipped. - This becomes more important when a `scale` transform option exists. It's harder for users to transform the hotspot themselves when using `scale` because they'd need to look up the image to get its dimensions. Instead, we let Bevy handle the hotspot transforms and make the `hotspot` field the "original/source" hotspot. - Refs bevyengine#17276. ## Solution - When the image needs to be transformed, also transform the hotspot. If the image does not need to be transformed (i.e. fast path), no hotspot transformation is applied. ## Testing - Ran the example: `cargo run --example custom_cursor_image --features=custom_cursor`. - Add unit tests for the hotspot transform function. - I also ran the example I have in my `bevy_cursor_kit` crate, which I think is a good illustration of the reason for this PR. - In the following videos, there is an arrow pointing up. The button hover event fires as I move the mouse over it. - When I press `Y`, the cursor flips. - In the first video, on `bevy@main` **before** this PR, notice how the hotspot is wrong after flipping and no longer hovering the button. The arrow head and hotspot are no longer synced. - In the second video, on the branch of **this** PR, notice how the hotspot gets flipped as soon as I press `Y` and the cursor arrow head is in the correct position on the screen and still hovering the button. Speaking back to the objective listed at the start: The user originally defined the _source_ hotspot for the arrow. Later, they decide they want to flip the cursor vertically: It's nice that Bevy can automatically flip the _source_ hotspot for them at the same time it flips the _source_ image. First video (main): https://github.com/user-attachments/assets/1955048c-2f85-4951-bfd6-f0e7cfef0cf8 Second video (this PR): https://github.com/user-attachments/assets/73cb9095-ecb5-4bfd-af5b-9f772e92bd16
What problem does this solve or what need does it fill?
The new CustomCursor::Image solution in Bevy 0.15 is a great addition, making it simple to replace the cursor with an image instead of hiding the original cursor and making a Sprite follow its position.
An advantage that the previous method had, however, was complete control about the image size, rotation, etc. In my current project, as an example, sprites are not represented with their original resolution, making the cursor look too small in comparison.
What solution would you like?
An extra field in CustomCursor::Image allowing for the sprite to be stretched or shrinked.
What alternative(s) have you considered?
A quick solution would be to modify the source file to make it bigger. This would mean however that using a different tileset with a different resolution would require making a new cursor as well, so their relative sizes keep being the same.
If one wanted to animate the cursor (making it grow when approaching something, or making it grow and shrink rhythmically) it would require making a cursor spritesheet, when changing the size value would be simpler.
The text was updated successfully, but these errors were encountered: