Skip to content

Fix sprite picking viewport #19747

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

Merged
merged 1 commit into from
Jun 30, 2025

Conversation

jannik4
Copy link
Contributor

@jannik4 jannik4 commented Jun 19, 2025

Objective

Fixes sprite picking when using a viewport. Related to #19744.

Solution

  • Do not substract viewport.min as Camera::viewport_to_world already does that.
  • Skip pointers outside the viewport.

Testing

Tested with the following example:

Click to expand code
use bevy::{
    prelude::*, render::camera::Viewport, window::SystemCursorIcon, winit::cursor::CursorIcon,
};

fn main() -> AppExit {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .insert_resource(ClearColor(Color::BLACK))
        .run()
}

fn setup(mut commands: Commands, window: Single<&Window>) {
    commands.spawn((
        Camera2d,
        Camera {
            clear_color: ClearColorConfig::Custom(Color::WHITE),
            viewport: Some(Viewport {
                physical_position: UVec2::new(
                    window.physical_width() / 4,
                    window.physical_height() / 4,
                ),
                physical_size: UVec2::new(
                    window.physical_width() / 2,
                    window.physical_height() / 2,
                ),
                ..default()
            }),
            ..default()
        },
    ));

    commands
        .spawn((
            Transform::from_xyz(100.0, 100.0, 0.0),
            Sprite::from_color(Color::srgb(0.0, 1.0, 0.0), Vec2::new(200.0, 200.0)),
            Pickable::default(),
        ))
        .observe(
            |trigger: On<Pointer<Drag>>, mut transform: Query<&mut Transform>| {
                let mut transform = transform.get_mut(trigger.target()).unwrap();
                transform.translation.x += trigger.delta.x;
                transform.translation.y -= trigger.delta.y;
            },
        )
        .observe(
            |_: On<Pointer<DragStart>>,
             window: Single<Entity, With<Window>>,
             mut commands: Commands| {
                commands
                    .entity(*window)
                    .insert(CursorIcon::from(SystemCursorIcon::Grabbing));
            },
        )
        .observe(
            |_: On<Pointer<DragEnd>>,
             window: Single<Entity, With<Window>>,
             mut commands: Commands| {
                commands.entity(*window).remove::<CursorIcon>();
            },
        );
}

@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior S-Needs-Review Needs reviewer attention (from anyone!) to move forward A-Picking Pointing at and selecting objects of all sorts labels Jun 19, 2025
Comment on lines +149 to +150
if let Some(viewport) = camera.logical_viewport_rect() {
if !viewport.contains(viewport_pos) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This could use is_some_and or the newly released let chains to avoid the unnecessary double if, but it's just a nitpick.

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 30, 2025
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jun 30, 2025
Merged via the queue into bevyengine:main with commit 169254b Jun 30, 2025
41 checks passed
github-merge-queue bot pushed a commit that referenced this pull request Jun 30, 2025
# Objective

Fixes #19692 

## Solution

- Skip pointers outside the viewport.

## Testing

Tested with the following example:

<details>
<summary>Click to expand code</summary>

```rust
use bevy::{
    prelude::*, render::camera::Viewport, window::SystemCursorIcon, winit::cursor::CursorIcon,
};

fn main() -> AppExit {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .insert_resource(ClearColor(Color::BLACK))
        .run()
}

fn setup(mut commands: Commands, window: Single<&Window>) {
    //
    commands.spawn((
        Camera2d,
        Camera {
            clear_color: ClearColorConfig::Custom(Color::WHITE),
            viewport: Some(Viewport {
                physical_position: UVec2::new(
                    window.physical_width() / 4,
                    window.physical_height() / 4,
                ),
                physical_size: UVec2::new(
                    window.physical_width() / 2,
                    window.physical_height() / 2,
                ),
                ..default()
            }),
            ..default()
        },
    ));

    commands
        .spawn((
            Node {
                top: Val::Px(100.0),
                left: Val::Px(100.0),
                width: Val::Px(200.0),
                height: Val::Px(200.0),
                ..default()
            },
            BackgroundColor(Color::srgb(1.0, 0.0, 0.0)),
        ))
        .observe(|trigger: On<Pointer<Drag>>, mut node: Query<&mut Node>| {
            let mut node = node.get_mut(trigger.target()).unwrap();
            node.left = Val::Px(px(node.left) + trigger.delta.x);
            node.top = Val::Px(px(node.top) + trigger.delta.y);
        })
        .observe(
            |_: On<Pointer<DragStart>>,
             window: Single<Entity, With<Window>>,
             mut commands: Commands| {
                commands
                    .entity(*window)
                    .insert(CursorIcon::from(SystemCursorIcon::Grabbing));
            },
        )
        .observe(
            |_: On<Pointer<DragEnd>>,
             window: Single<Entity, With<Window>>,
             mut commands: Commands| {
                commands.entity(*window).remove::<CursorIcon>();
            },
        );
}

fn px(val: Val) -> f32 {
    match val {
        Val::Px(px) => px,
        _ => 0.0,
    }
}
```
</details>

## Additional information

This is at least also broken on the sprite picking backend. I guess the
fix for other backends are also trivial if this is correct.
(Sprite picking: #19747)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Picking Pointing at and selecting objects of all sorts C-Bug An unexpected or incorrect behavior S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants