bevy/release-content/migration-guides/split-window.md
Jan Hohenheim a750cfe4a1
Split CursorOptions off of Window (#19668)
# Objective

- Fixes #19627 
- Tackles part of #19644 
- Supersedes #19629
- `Window` has become a very very very big component
- As such, our change detection does not *really* work on it, as e.g.
moving the mouse will cause a change for the entire window
- We circumvented this with a cache
- But, some things *shouldn't* be cached as they can be changed from
outside the user's control, notably the cursor grab mode on web
- So, we need to disable the cache for that
- But because change detection is broken, that would result in the
cursor grab mode being set every frame the mouse is moved
- That is usually *not* what a dev wants, as it forces the cursor to be
locked even when the end-user is trying to free the cursor on the
browser
  - the cache in this situation is invalid due to #8949

## Solution

- Split `Window` into multiple components, each with working change
detection
- Disable caching of the cursor grab mode
- This will only attempt to force the grab mode when the `CursorOptions`
were touched by the user, which is *much* rarer than simply moving the
mouse.
- If this PR is merged, I'll do the exact same for the other
constituents of `Window` as a follow-up

## Testing

- Ran all the changed examples
2025-06-17 20:20:13 +00:00

1.2 KiB

title pull_requests
Window is now split into multiple components
19668

Window has become a very large component over the last few releases. To improve our internal handling of it and to make it more approachable, we have split it into multiple components, all on the same entity. So far, this affects CursorOptions:

// old
fn lock_cursor(primary_window: Single<&mut Window, With<PrimaryWindow>>) {
    primary_window.cursor_options.grab_mode = CursorGrabMode::Locked;
}

// new
fn lock_cursor(primary_cursor_options: Single<&mut CursorOptions, With<PrimaryWindow>>) {
    primary_cursor_options.grab_mode = CursorGrabMode::Locked;
}

This split also applies when specifying the initial settings for the primary window:

// old
app.add_plugins(DefaultPlugins.set(WindowPlugin {
    primary_window: Some(Window {
        cursor_options: CursorOptions {
            grab_mode: CursorGrabMode::Locked,
            ..default()
        },
        ..default()
    }),
    ..default()
}));

// new
app.add_plugins(DefaultPlugins.set(WindowPlugin {
    primary_cursor_options: Some(CursorOptions {
        grab_mode: CursorGrabMode::Locked,
        ..default()
    }),
    ..default()
}));