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

[Merged by Bors] - Optionally resize Window canvas element to fit parent element #4726

Closed
wants to merge 11 commits into from

Conversation

cart
Copy link
Member

@cart cart commented May 11, 2022

Currently Bevy's web canvases are "fixed size". They are manually set to specific dimensions. This might be fine for some games and website layouts, but for sites with flexible layouts, or games that want to "fill" the browser window, Bevy doesn't provide the tools needed to make this easy out of the box.

There are third party plugins like bevy-web-resizer that listen for window resizes, take the new dimensions, and resize the winit window accordingly. However this only covers a subset of cases and this is common enough functionality that it should be baked into Bevy.

A significant motivating use case here is the Bevy WASM Examples page. This scales the canvas to fit smaller windows (such as mobile). But this approach both breaks winit's mouse events and removes pixel-perfect rendering (which means we might be rendering too many or too few pixels). bevyengine/bevy-website#371

In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: rust-windowing/winit#2074. And it builds on the ResizeObserver api, which isn't supported in all browsers yet (and is only supported in very new versions of the popular browsers).

While we wait for a complete winit solution, I've added a fit_canvas_to_parent option to WindowDescriptor / Window, which when enabled will listen for window resizes and resize the Bevy canvas/window to fit its parent element. This enables users to scale bevy canvases using arbitrary CSS, by "inheriting" their parents' size. Note that the wrapper element is required because winit overrides the canvas sizing with absolute values on each resize.

There is one limitation worth calling out here: while the majority of canvas resizes will be triggered by window resizes, modifying element layout at runtime (css animations, javascript-driven element changes, dev-tool-injected changes, etc) will not be detected here. I'm not aware of a good / efficient event-driven way to do this outside of the ResizeObserver api. In practice, window-resize-driven canvas resizing should cover the majority of use cases. Users that want to actively poll for element resizes can just do that (or we can build another feature and let people choose based on their specific needs).

I also took the chance to make a couple of minor tweaks:

  • Made the canvas window setting available on all platforms. Users shouldn't need to deal with cargo feature selection to support web scenarios. We can just ignore the value on non-web platforms. I added documentation that explains this.
  • Removed the redundant "initial create windows" handler. With the addition of the code in this pr, the code duplication was untenable.

This enables a number of patterns:

Easy "fullscreen window" mode for the default canvas

The "parent element" defaults to the <body> element.

app
  .insert_resource(WindowDescriptor {
    fit_canvas_to_parent: true,
    ..default()
  })

And CSS:

html, body {
    margin: 0;
    height: 100%;
}

Fit custom canvas to "wrapper" parent element

app
  .insert_resource(WindowDescriptor {
    fit_canvas_to_parent: true,
    canvas: Some("#bevy".to_string()),
    ..default()
  })

And the HTML:

<div style="width: 50%; height: 100%">
  <canvas id="bevy"></canvas>
</div>

@cart cart added C-Feature A new feature, making something new possible O-Web Specific to web (WASM) builds labels May 11, 2022
@cart cart changed the title Optionally resize window canvas element to fit parent element Optionally resize Window canvas element to fit parent element May 11, 2022
@IceSentry
Copy link
Contributor

IceSentry commented May 11, 2022

The link to the examples page is broken, it points to https://github.com/bevyengine/bevy/pull/bevyengine.org/examples/

Copy link
Contributor

@IceSentry IceSentry left a comment

Choose a reason for hiding this comment

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

LGTM, there's a few unwrap that could probably be returned, but they would most likely just be unwrap at the call site so 🤷‍♂️ .

I really like that canvas is not behind a compile flag, it made it unnecessarily noisy.

Copy link
Contributor

@jakobhellermann jakobhellermann left a comment

Choose a reason for hiding this comment

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

I haven't tested the code but assuming it works the PR gets an "approve" from me 👍

@infmagic2047
Copy link
Contributor

This PR doesn't work for me with Firefox. Every time the window is resized, the canvas becomes 4 pixels taller, outgrowing the window (I added logging to web_resize.rs to verify this).

This is my index.html:

<!DOCTYPE html>
<html lang="en">
    <body style="margin: 0px; overflow: hidden;">
        <script type="module">
            import init from './mygame.js';
            init();
        </script>
    </body>
</html>

Reading the documentation, getBoundingClientRect() returns the size of a element's content. In this case the parent element of canvas (body) doesn't have a fixed size, so its size depends on the size of canvas, and we're essentially doing canvas.size = canvas.size + some_margin here, which certainly won't work.

@cart
Copy link
Member Author

cart commented May 13, 2022

In this case the parent element of canvas (body) doesn't have a fixed size, so its size depends on the size of canvas, and we're essentially doing canvas.size = canvas.size + some_margin here, which certainly won't work.

Hmm worth calling out (this case definitely slipped by me), but I'm not sure theres a good way to work around "parent size relies on child size" problems. The point of the feature is to inherit the parent's size. If the parent size relies on the child's size, this is clearly a feedback loop. The "fix" is to set a specific size for the parent element (ex: set the body height to a percentage or fixed size, or create a new wrapper element / create your own canvas instead of the default) .

I think we should still move forward with this feature and just call out this case in the docs for fit_canvas_to_parent.

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.

Some nits, but I'm happy with it. In the future, do try to use the PR template: I want to get automatic parsing set up based on those headings to make the release note process less painful.

@alice-i-cecile alice-i-cecile added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label May 16, 2022
@FraserLee
Copy link
Contributor

Since the "wrapper" example in the PR selects the canvas by an ID, you might want to edit

canvas: Some("bevy".to_string()),

to instead be "#bevy". As is, the current example won't find the div.

cheers

@cart
Copy link
Member Author

cart commented May 20, 2022

bors r+

bors bot pushed a commit that referenced this pull request May 20, 2022
Currently Bevy's web canvases are "fixed size". They are manually set to specific dimensions. This might be fine for some games and website layouts, but for sites with flexible layouts, or games that want to "fill" the browser window, Bevy doesn't provide the tools needed to make this easy out of the box.

There are third party plugins like [bevy-web-resizer](https://github.com/frewsxcv/bevy-web-resizer/) that listen for window resizes, take the new dimensions, and resize the winit window accordingly. However this only covers a subset of cases and this is common enough functionality that it should be baked into Bevy.

A significant motivating use case here is the [Bevy WASM Examples page](https://bevyengine.org/examples/). This scales the canvas to fit smaller windows (such as mobile). But this approach both breaks winit's mouse events and removes pixel-perfect rendering (which means we might be rendering too many or too few pixels).  bevyengine/bevy-website#371

In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: rust-windowing/winit#2074. And it builds on the ResizeObserver api, which isn't supported in all browsers yet (and is only supported in very new versions of the popular browsers).

While we wait for a complete winit solution, I've added a `fit_canvas_to_parent` option to WindowDescriptor / Window, which when enabled will listen for window resizes and resize the Bevy canvas/window to fit its parent element. This enables users to scale bevy canvases using arbitrary CSS, by "inheriting" their parents' size. Note that the wrapper element _is_ required because winit overrides the canvas sizing with absolute values on each resize.

There is one limitation worth calling out here: while the majority of  canvas resizes will be triggered by window resizes, modifying element layout at runtime (css animations, javascript-driven element changes, dev-tool-injected changes, etc) will not be detected here. I'm not aware of a good / efficient event-driven way to do this outside of the ResizeObserver api. In practice, window-resize-driven canvas resizing should cover the majority of use cases. Users that want to actively poll for element resizes can just do that (or we can build another feature and let people choose based on their specific needs).

I also took the chance to make a couple of minor tweaks:
* Made the `canvas` window setting available on all platforms. Users shouldn't need to deal with cargo feature selection to support web scenarios. We can just ignore the value on non-web platforms. I added documentation that explains this.
*  Removed the redundant "initial create windows" handler. With the addition of the code in this pr, the code duplication was untenable.

This enables a number of patterns:

## Easy "fullscreen window" mode for the default canvas

The "parent element" defaults to the `<body>` element.

```rust
app
  .insert_resource(WindowDescriptor {
    fit_canvas_to_parent: true,
    ..default()
  })
``` 
And CSS:
```css
html, body {
    margin: 0;
    height: 100%;
}
```

## Fit custom canvas to "wrapper" parent element

```rust
app
  .insert_resource(WindowDescriptor {
    fit_canvas_to_parent: true,
    canvas: Some("#bevy".to_string()),
    ..default()
  })
``` 
And the HTML:
```html
<div style="width: 50%; height: 100%">
  <canvas id="bevy"></canvas>
</div>
```
@bors bors bot changed the title Optionally resize Window canvas element to fit parent element [Merged by Bors] - Optionally resize Window canvas element to fit parent element May 20, 2022
@bors bors bot closed this May 20, 2022
james7132 pushed a commit to james7132/bevy that referenced this pull request Jun 7, 2022
…gine#4726)

Currently Bevy's web canvases are "fixed size". They are manually set to specific dimensions. This might be fine for some games and website layouts, but for sites with flexible layouts, or games that want to "fill" the browser window, Bevy doesn't provide the tools needed to make this easy out of the box.

There are third party plugins like [bevy-web-resizer](https://github.com/frewsxcv/bevy-web-resizer/) that listen for window resizes, take the new dimensions, and resize the winit window accordingly. However this only covers a subset of cases and this is common enough functionality that it should be baked into Bevy.

A significant motivating use case here is the [Bevy WASM Examples page](https://bevyengine.org/examples/). This scales the canvas to fit smaller windows (such as mobile). But this approach both breaks winit's mouse events and removes pixel-perfect rendering (which means we might be rendering too many or too few pixels).  bevyengine/bevy-website#371

In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: rust-windowing/winit#2074. And it builds on the ResizeObserver api, which isn't supported in all browsers yet (and is only supported in very new versions of the popular browsers).

While we wait for a complete winit solution, I've added a `fit_canvas_to_parent` option to WindowDescriptor / Window, which when enabled will listen for window resizes and resize the Bevy canvas/window to fit its parent element. This enables users to scale bevy canvases using arbitrary CSS, by "inheriting" their parents' size. Note that the wrapper element _is_ required because winit overrides the canvas sizing with absolute values on each resize.

There is one limitation worth calling out here: while the majority of  canvas resizes will be triggered by window resizes, modifying element layout at runtime (css animations, javascript-driven element changes, dev-tool-injected changes, etc) will not be detected here. I'm not aware of a good / efficient event-driven way to do this outside of the ResizeObserver api. In practice, window-resize-driven canvas resizing should cover the majority of use cases. Users that want to actively poll for element resizes can just do that (or we can build another feature and let people choose based on their specific needs).

I also took the chance to make a couple of minor tweaks:
* Made the `canvas` window setting available on all platforms. Users shouldn't need to deal with cargo feature selection to support web scenarios. We can just ignore the value on non-web platforms. I added documentation that explains this.
*  Removed the redundant "initial create windows" handler. With the addition of the code in this pr, the code duplication was untenable.

This enables a number of patterns:

## Easy "fullscreen window" mode for the default canvas

The "parent element" defaults to the `<body>` element.

```rust
app
  .insert_resource(WindowDescriptor {
    fit_canvas_to_parent: true,
    ..default()
  })
``` 
And CSS:
```css
html, body {
    margin: 0;
    height: 100%;
}
```

## Fit custom canvas to "wrapper" parent element

```rust
app
  .insert_resource(WindowDescriptor {
    fit_canvas_to_parent: true,
    canvas: Some("#bevy".to_string()),
    ..default()
  })
``` 
And the HTML:
```html
<div style="width: 50%; height: 100%">
  <canvas id="bevy"></canvas>
</div>
```
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
…gine#4726)

Currently Bevy's web canvases are "fixed size". They are manually set to specific dimensions. This might be fine for some games and website layouts, but for sites with flexible layouts, or games that want to "fill" the browser window, Bevy doesn't provide the tools needed to make this easy out of the box.

There are third party plugins like [bevy-web-resizer](https://github.com/frewsxcv/bevy-web-resizer/) that listen for window resizes, take the new dimensions, and resize the winit window accordingly. However this only covers a subset of cases and this is common enough functionality that it should be baked into Bevy.

A significant motivating use case here is the [Bevy WASM Examples page](https://bevyengine.org/examples/). This scales the canvas to fit smaller windows (such as mobile). But this approach both breaks winit's mouse events and removes pixel-perfect rendering (which means we might be rendering too many or too few pixels).  bevyengine/bevy-website#371

In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: rust-windowing/winit#2074. And it builds on the ResizeObserver api, which isn't supported in all browsers yet (and is only supported in very new versions of the popular browsers).

While we wait for a complete winit solution, I've added a `fit_canvas_to_parent` option to WindowDescriptor / Window, which when enabled will listen for window resizes and resize the Bevy canvas/window to fit its parent element. This enables users to scale bevy canvases using arbitrary CSS, by "inheriting" their parents' size. Note that the wrapper element _is_ required because winit overrides the canvas sizing with absolute values on each resize.

There is one limitation worth calling out here: while the majority of  canvas resizes will be triggered by window resizes, modifying element layout at runtime (css animations, javascript-driven element changes, dev-tool-injected changes, etc) will not be detected here. I'm not aware of a good / efficient event-driven way to do this outside of the ResizeObserver api. In practice, window-resize-driven canvas resizing should cover the majority of use cases. Users that want to actively poll for element resizes can just do that (or we can build another feature and let people choose based on their specific needs).

I also took the chance to make a couple of minor tweaks:
* Made the `canvas` window setting available on all platforms. Users shouldn't need to deal with cargo feature selection to support web scenarios. We can just ignore the value on non-web platforms. I added documentation that explains this.
*  Removed the redundant "initial create windows" handler. With the addition of the code in this pr, the code duplication was untenable.

This enables a number of patterns:

## Easy "fullscreen window" mode for the default canvas

The "parent element" defaults to the `<body>` element.

```rust
app
  .insert_resource(WindowDescriptor {
    fit_canvas_to_parent: true,
    ..default()
  })
``` 
And CSS:
```css
html, body {
    margin: 0;
    height: 100%;
}
```

## Fit custom canvas to "wrapper" parent element

```rust
app
  .insert_resource(WindowDescriptor {
    fit_canvas_to_parent: true,
    canvas: Some("#bevy".to_string()),
    ..default()
  })
``` 
And the HTML:
```html
<div style="width: 50%; height: 100%">
  <canvas id="bevy"></canvas>
</div>
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-Feature A new feature, making something new possible O-Web Specific to web (WASM) builds S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

9 participants