2021-10-15 23:47:42 +00:00
use bevy_math ::{ DVec2 , IVec2 , Vec2 } ;
2021-03-03 02:56:50 +00:00
use bevy_utils ::{ tracing ::warn , Uuid } ;
2021-06-02 02:59:17 +00:00
use raw_window_handle ::RawWindowHandle ;
2020-04-05 21:12:14 +00:00
Camera Driven Rendering (#4745)
This adds "high level camera driven rendering" to Bevy. The goal is to give users more control over what gets rendered (and where) without needing to deal with render logic. This will make scenarios like "render to texture", "multiple windows", "split screen", "2d on 3d", "3d on 2d", "pass layering", and more significantly easier.
Here is an [example of a 2d render sandwiched between two 3d renders (each from a different perspective)](https://gist.github.com/cart/4fe56874b2e53bc5594a182fc76f4915):
![image](https://user-images.githubusercontent.com/2694663/168411086-af13dec8-0093-4a84-bdd4-d4362d850ffa.png)
Users can now spawn a camera, point it at a RenderTarget (a texture or a window), and it will "just work".
Rendering to a second window is as simple as spawning a second camera and assigning it to a specific window id:
```rust
// main camera (main window)
commands.spawn_bundle(Camera2dBundle::default());
// second camera (other window)
commands.spawn_bundle(Camera2dBundle {
camera: Camera {
target: RenderTarget::Window(window_id),
..default()
},
..default()
});
```
Rendering to a texture is as simple as pointing the camera at a texture:
```rust
commands.spawn_bundle(Camera2dBundle {
camera: Camera {
target: RenderTarget::Texture(image_handle),
..default()
},
..default()
});
```
Cameras now have a "render priority", which controls the order they are drawn in. If you want to use a camera's output texture as a texture in the main pass, just set the priority to a number lower than the main pass camera (which defaults to `0`).
```rust
// main pass camera with a default priority of 0
commands.spawn_bundle(Camera2dBundle::default());
commands.spawn_bundle(Camera2dBundle {
camera: Camera {
target: RenderTarget::Texture(image_handle.clone()),
priority: -1,
..default()
},
..default()
});
commands.spawn_bundle(SpriteBundle {
texture: image_handle,
..default()
})
```
Priority can also be used to layer to cameras on top of each other for the same RenderTarget. This is what "2d on top of 3d" looks like in the new system:
```rust
commands.spawn_bundle(Camera3dBundle::default());
commands.spawn_bundle(Camera2dBundle {
camera: Camera {
// this will render 2d entities "on top" of the default 3d camera's render
priority: 1,
..default()
},
..default()
});
```
There is no longer the concept of a global "active camera". Resources like `ActiveCamera<Camera2d>` and `ActiveCamera<Camera3d>` have been replaced with the camera-specific `Camera::is_active` field. This does put the onus on users to manage which cameras should be active.
Cameras are now assigned a single render graph as an "entry point", which is configured on each camera entity using the new `CameraRenderGraph` component. The old `PerspectiveCameraBundle` and `OrthographicCameraBundle` (generic on camera marker components like Camera2d and Camera3d) have been replaced by `Camera3dBundle` and `Camera2dBundle`, which set 3d and 2d default values for the `CameraRenderGraph` and projections.
```rust
// old 3d perspective camera
commands.spawn_bundle(PerspectiveCameraBundle::default())
// new 3d perspective camera
commands.spawn_bundle(Camera3dBundle::default())
```
```rust
// old 2d orthographic camera
commands.spawn_bundle(OrthographicCameraBundle::new_2d())
// new 2d orthographic camera
commands.spawn_bundle(Camera2dBundle::default())
```
```rust
// old 3d orthographic camera
commands.spawn_bundle(OrthographicCameraBundle::new_3d())
// new 3d orthographic camera
commands.spawn_bundle(Camera3dBundle {
projection: OrthographicProjection {
scale: 3.0,
scaling_mode: ScalingMode::FixedVertical,
..default()
}.into(),
..default()
})
```
Note that `Camera3dBundle` now uses a new `Projection` enum instead of hard coding the projection into the type. There are a number of motivators for this change: the render graph is now a part of the bundle, the way "generic bundles" work in the rust type system prevents nice `..default()` syntax, and changing projections at runtime is much easier with an enum (ex for editor scenarios). I'm open to discussing this choice, but I'm relatively certain we will all come to the same conclusion here. Camera2dBundle and Camera3dBundle are much clearer than being generic on marker components / using non-default constructors.
If you want to run a custom render graph on a camera, just set the `CameraRenderGraph` component:
```rust
commands.spawn_bundle(Camera3dBundle {
camera_render_graph: CameraRenderGraph::new(some_render_graph_name),
..default()
})
```
Just note that if the graph requires data from specific components to work (such as `Camera3d` config, which is provided in the `Camera3dBundle`), make sure the relevant components have been added.
Speaking of using components to configure graphs / passes, there are a number of new configuration options:
```rust
commands.spawn_bundle(Camera3dBundle {
camera_3d: Camera3d {
// overrides the default global clear color
clear_color: ClearColorConfig::Custom(Color::RED),
..default()
},
..default()
})
commands.spawn_bundle(Camera3dBundle {
camera_3d: Camera3d {
// disables clearing
clear_color: ClearColorConfig::None,
..default()
},
..default()
})
```
Expect to see more of the "graph configuration Components on Cameras" pattern in the future.
By popular demand, UI no longer requires a dedicated camera. `UiCameraBundle` has been removed. `Camera2dBundle` and `Camera3dBundle` now both default to rendering UI as part of their own render graphs. To disable UI rendering for a camera, disable it using the CameraUi component:
```rust
commands
.spawn_bundle(Camera3dBundle::default())
.insert(CameraUi {
is_enabled: false,
..default()
})
```
## Other Changes
* The separate clear pass has been removed. We should revisit this for things like sky rendering, but I think this PR should "keep it simple" until we're ready to properly support that (for code complexity and performance reasons). We can come up with the right design for a modular clear pass in a followup pr.
* I reorganized bevy_core_pipeline into Core2dPlugin and Core3dPlugin (and core_2d / core_3d modules). Everything is pretty much the same as before, just logically separate. I've moved relevant types (like Camera2d, Camera3d, Camera3dBundle, Camera2dBundle) into their relevant modules, which is what motivated this reorganization.
* I adapted the `scene_viewer` example (which relied on the ActiveCameras behavior) to the new system. I also refactored bits and pieces to be a bit simpler.
* All of the examples have been ported to the new camera approach. `render_to_texture` and `multiple_windows` are now _much_ simpler. I removed `two_passes` because it is less relevant with the new approach. If someone wants to add a new "layered custom pass with CameraRenderGraph" example, that might fill a similar niche. But I don't feel much pressure to add that in this pr.
* Cameras now have `target_logical_size` and `target_physical_size` fields, which makes finding the size of a camera's render target _much_ simpler. As a result, the `Assets<Image>` and `Windows` parameters were removed from `Camera::world_to_screen`, making that operation much more ergonomic.
* Render order ambiguities between cameras with the same target and the same priority now produce a warning. This accomplishes two goals:
1. Now that there is no "global" active camera, by default spawning two cameras will result in two renders (one covering the other). This would be a silent performance killer that would be hard to detect after the fact. By detecting ambiguities, we can provide a helpful warning when this occurs.
2. Render order ambiguities could result in unexpected / unpredictable render results. Resolving them makes sense.
## Follow Up Work
* Per-Camera viewports, which will make it possible to render to a smaller area inside of a RenderTarget (great for something like splitscreen)
* Camera-specific MSAA config (should use the same "overriding" pattern used for ClearColor)
* Graph Based Camera Ordering: priorities are simple, but they make complicated ordering constraints harder to express. We should consider adopting a "graph based" camera ordering model with "before" and "after" relationships to other cameras (or build it "on top" of the priority system).
* Consider allowing graphs to run subgraphs from any nest level (aka a global namespace for graphs). Right now the 2d and 3d graphs each need their own UI subgraph, which feels "fine" in the short term. But being able to share subgraphs between other subgraphs seems valuable.
* Consider splitting `bevy_core_pipeline` into `bevy_core_2d` and `bevy_core_3d` packages. Theres a shared "clear color" dependency here, which would need a new home.
2022-06-02 00:12:17 +00:00
#[ derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord) ]
2022-06-16 13:20:37 +00:00
/// A unique ID for a [`Window`].
2020-04-05 21:12:14 +00:00
pub struct WindowId ( Uuid ) ;
2022-02-04 03:37:44 +00:00
/// Presentation mode for a window.
///
/// The presentation mode specifies when a frame is presented to the window. The `Fifo`
/// option corresponds to a traditional `VSync`, where the framerate is capped by the
/// display refresh rate. Both `Immediate` and `Mailbox` are low-latency and are not
/// capped by the refresh rate, but may not be available on all platforms. Tearing
/// may be observed with `Immediate` mode, but will not be observed with `Mailbox` or
/// `Fifo`.
///
/// `Immediate` or `Mailbox` will gracefully fallback to `Fifo` when unavailable.
///
/// The presentation mode may be declared in the [`WindowDescriptor`](WindowDescriptor::present_mode)
/// or updated on a [`Window`](Window::set_present_mode).
#[ repr(C) ]
#[ derive(Copy, Clone, Debug, PartialEq, Eq, Hash) ]
#[ doc(alias = " vsync " ) ]
pub enum PresentMode {
/// The presentation engine does **not** wait for a vertical blanking period and
/// the request is presented immediately. This is a low-latency presentation mode,
/// but visible tearing may be observed. Will fallback to `Fifo` if unavailable on the
/// selected platform and backend. Not optimal for mobile.
Immediate = 0 ,
/// The presentation engine waits for the next vertical blanking period to update
/// the current image, but frames may be submitted without delay. This is a low-latency
/// presentation mode and visible tearing will **not** be observed. Will fallback to `Fifo`
/// if unavailable on the selected platform and backend. Not optimal for mobile.
Mailbox = 1 ,
/// The presentation engine waits for the next vertical blanking period to update
/// the current image. The framerate will be capped at the display refresh rate,
/// corresponding to the `VSync`. Tearing cannot be observed. Optimal for mobile.
Fifo = 2 , // NOTE: The explicit ordinal values mirror wgpu and the vulkan spec.
}
2020-04-05 21:12:14 +00:00
impl WindowId {
2022-06-16 13:20:37 +00:00
/// Creates a new [`WindowId`].
2020-06-25 23:02:21 +00:00
pub fn new ( ) -> Self {
WindowId ( Uuid ::new_v4 ( ) )
}
2022-06-16 13:20:37 +00:00
/// The [`WindowId`] for the primary window.
2020-07-25 06:04:45 +00:00
pub fn primary ( ) -> Self {
WindowId ( Uuid ::from_u128 ( 0 ) )
}
2022-06-16 13:20:37 +00:00
/// Get whether or not this [`WindowId`] is for the primary window.
2020-07-25 06:04:45 +00:00
pub fn is_primary ( & self ) -> bool {
2020-07-26 19:10:18 +00:00
* self = = WindowId ::primary ( )
2020-07-25 06:04:45 +00:00
}
2020-08-16 07:30:04 +00:00
}
2021-12-20 22:04:45 +00:00
use crate ::CursorIcon ;
2020-08-16 07:30:04 +00:00
use std ::fmt ;
2020-07-25 06:04:45 +00:00
2021-06-02 02:59:17 +00:00
use crate ::raw_window_handle ::RawWindowHandleWrapper ;
2020-08-16 07:30:04 +00:00
impl fmt ::Display for WindowId {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
2022-05-30 17:26:23 +00:00
self . 0. as_simple ( ) . fmt ( f )
2020-04-05 21:12:14 +00:00
}
}
2020-07-25 06:04:45 +00:00
impl Default for WindowId {
fn default ( ) -> Self {
WindowId ::primary ( )
}
}
2021-03-03 02:56:50 +00:00
/// The size limits on a window.
2022-06-16 13:20:37 +00:00
///
2021-03-03 02:56:50 +00:00
/// These values are measured in logical pixels, so the user's
/// scale factor does affect the size limits on the window.
/// Please note that if the window is resizable, then when the window is
/// maximized it may have a size outside of these limits. The functionality
/// required to disable maximizing is not yet exposed by winit.
#[ derive(Debug, Clone, Copy) ]
pub struct WindowResizeConstraints {
pub min_width : f32 ,
pub min_height : f32 ,
pub max_width : f32 ,
pub max_height : f32 ,
}
impl Default for WindowResizeConstraints {
fn default ( ) -> Self {
Self {
min_width : 180. ,
min_height : 120. ,
max_width : f32 ::INFINITY ,
max_height : f32 ::INFINITY ,
}
}
}
impl WindowResizeConstraints {
2022-02-13 22:33:55 +00:00
#[ must_use ]
pub fn check_constraints ( & self ) -> Self {
2021-03-03 02:56:50 +00:00
let WindowResizeConstraints {
mut min_width ,
mut min_height ,
mut max_width ,
mut max_height ,
} = self ;
min_width = min_width . max ( 1. ) ;
min_height = min_height . max ( 1. ) ;
if max_width < min_width {
warn! (
" The given maximum width {} is smaller than the minimum width {} " ,
max_width , min_width
) ;
max_width = min_width ;
}
if max_height < min_height {
warn! (
" The given maximum height {} is smaller than the minimum height {} " ,
max_height , min_height
) ;
max_height = min_height ;
}
WindowResizeConstraints {
min_width ,
min_height ,
max_width ,
max_height ,
}
}
}
2020-12-13 23:05:56 +00:00
/// An operating system window that can present content and receive user input.
///
2022-06-16 13:20:37 +00:00
/// To create a window, use a [`EventWriter<CreateWindow>`](`crate::CreateWindow`).
///
2020-12-13 23:05:56 +00:00
/// ## Window Sizes
///
/// There are three sizes associated with a window. The physical size which is
/// the height and width in physical pixels on the monitor. The logical size
/// which is the physical size scaled by an operating system provided factor to
/// account for monitors with differing pixel densities or user preference. And
/// the requested size, measured in logical pixels, which is the value submitted
/// to the API when creating the window, or requesting that it be resized.
///
/// The actual size, in logical pixels, of the window may not match the
/// requested size due to operating system limits on the window size, or the
/// quantization of the logical size when converting the physical size to the
/// logical size through the scaling factor.
2022-06-16 13:20:37 +00:00
///
/// ## Accessing a `Window` from a system
///
/// To access a `Window` from a system, use [`bevy_ecs::change_detection::ResMut`]`<`[`crate::Windows`]`>`.
///
/// ### Example
/// ```no_run
/// # use bevy_app::App;
/// # use bevy_window::Windows;
/// # use bevy_ecs::change_detection::ResMut;
/// # fn main(){
/// # App::new().add_system(access_window_system).run();
/// # }
/// fn access_window_system(mut windows: ResMut<Windows>){
/// for mut window in windows.iter_mut(){
/// window.set_title(String::from("Yay, I'm a window!"));
/// }
/// }
/// ```
2020-06-22 20:21:39 +00:00
#[ derive(Debug) ]
2020-04-05 21:12:14 +00:00
pub struct Window {
2020-10-15 18:42:19 +00:00
id : WindowId ,
2020-12-13 23:05:56 +00:00
requested_width : f32 ,
requested_height : f32 ,
2020-12-07 21:32:57 +00:00
physical_width : u32 ,
physical_height : u32 ,
2021-03-03 02:56:50 +00:00
resize_constraints : WindowResizeConstraints ,
2021-01-25 04:06:06 +00:00
position : Option < IVec2 > ,
2020-12-28 20:26:50 +00:00
scale_factor_override : Option < f64 > ,
backend_scale_factor : f64 ,
2020-10-15 18:42:19 +00:00
title : String ,
2022-02-04 03:37:44 +00:00
present_mode : PresentMode ,
2020-10-15 18:42:19 +00:00
resizable : bool ,
decorations : bool ,
2021-12-20 22:04:45 +00:00
cursor_icon : CursorIcon ,
2020-10-16 21:07:01 +00:00
cursor_visible : bool ,
cursor_locked : bool ,
2021-10-15 23:47:42 +00:00
physical_cursor_position : Option < DVec2 > ,
2021-06-02 02:59:17 +00:00
raw_window_handle : RawWindowHandleWrapper ,
2021-02-13 05:32:32 +00:00
focused : bool ,
2020-10-15 18:42:19 +00:00
mode : WindowMode ,
Optionally resize Window canvas element to fit parent element (#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). https://github.com/bevyengine/bevy-website/issues/371
In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: https://github.com/rust-windowing/winit/pull/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>
```
2022-05-20 23:13:48 +00:00
canvas : Option < String > ,
fit_canvas_to_parent : bool ,
2020-10-15 18:42:19 +00:00
command_queue : Vec < WindowCommand > ,
}
2022-06-16 13:20:37 +00:00
/// A command to be sent to a window.
///
/// Bevy apps don't interact with this `enum` directly. Instead, they should use the methods on [`Window`].
/// This `enum` is meant for authors of windowing plugins. See the documentation on [`crate::WindowPlugin`] for more information.
2020-10-15 18:42:19 +00:00
#[ derive(Debug) ]
pub enum WindowCommand {
2022-06-16 13:20:37 +00:00
/// Set the window's [`WindowMode`].
2020-10-15 18:42:19 +00:00
SetWindowMode {
mode : WindowMode ,
resolution : ( u32 , u32 ) ,
} ,
2022-06-16 13:20:37 +00:00
/// Set the window's title.
2020-10-15 18:42:19 +00:00
SetTitle {
title : String ,
} ,
2022-06-16 13:20:37 +00:00
/// Set the window's scale factor.
2020-12-28 20:26:50 +00:00
SetScaleFactor {
scale_factor : f64 ,
} ,
2022-06-16 13:20:37 +00:00
/// Set the window's resolution.
2020-10-15 18:42:19 +00:00
SetResolution {
2020-12-28 20:26:50 +00:00
logical_resolution : ( f32 , f32 ) ,
scale_factor : f64 ,
2020-10-15 18:42:19 +00:00
} ,
2022-06-16 13:20:37 +00:00
/// Set the window's [`PresentMode`].
2022-02-04 03:37:44 +00:00
SetPresentMode {
present_mode : PresentMode ,
2020-10-15 18:42:19 +00:00
} ,
2022-06-16 13:20:37 +00:00
/// Set whether or not the window is resizable.
2020-10-15 18:42:19 +00:00
SetResizable {
resizable : bool ,
} ,
2022-06-16 13:20:37 +00:00
/// Set whether or not the window has decorations.
///
/// Examples of decorations include the close, full screen, and minimize buttons
2020-10-15 18:42:19 +00:00
SetDecorations {
decorations : bool ,
} ,
2022-06-16 13:20:37 +00:00
/// Set whether or not the cursor's postition is locked.
2020-10-16 21:07:01 +00:00
SetCursorLockMode {
locked : bool ,
} ,
2022-06-16 13:20:37 +00:00
/// Set the cursor's [`CursorIcon`].
2021-12-20 22:04:45 +00:00
SetCursorIcon {
icon : CursorIcon ,
} ,
2022-06-16 13:20:37 +00:00
/// Set whether or not the cursor is visible.
2020-10-16 21:07:01 +00:00
SetCursorVisibility {
visible : bool ,
} ,
2022-06-16 13:20:37 +00:00
/// Set the cursor's position.
2020-11-26 01:31:10 +00:00
SetCursorPosition {
2020-12-03 20:39:03 +00:00
position : Vec2 ,
2020-11-26 01:31:10 +00:00
} ,
2022-06-16 13:20:37 +00:00
/// Set whether or not the window is maxizimed.
2020-12-04 22:31:17 +00:00
SetMaximized {
maximized : bool ,
} ,
2022-06-16 13:20:37 +00:00
/// Set whether or not the window is minimized.
2021-01-25 04:06:06 +00:00
SetMinimized {
minimized : bool ,
} ,
2022-06-16 13:20:37 +00:00
/// Set the window's position on the screen.
2021-01-25 04:06:06 +00:00
SetPosition {
position : IVec2 ,
} ,
2022-06-16 13:20:37 +00:00
/// Set the window's [`WindowResizeConstraints`]
2021-03-03 02:56:50 +00:00
SetResizeConstraints {
resize_constraints : WindowResizeConstraints ,
} ,
2022-05-05 13:35:43 +00:00
Close ,
2020-08-13 08:47:40 +00:00
}
2022-06-16 13:20:37 +00:00
/// Defines the way a window is displayed.
2022-07-01 13:41:23 +00:00
#[ derive(Debug, Clone, Copy, PartialEq, Eq) ]
2020-08-13 08:47:40 +00:00
pub enum WindowMode {
2022-06-16 13:20:37 +00:00
/// Creates a window that uses the given size.
2020-08-13 08:47:40 +00:00
Windowed ,
2022-06-16 13:20:37 +00:00
/// Creates a borderless window that uses the full size of the screen.
2020-08-13 08:47:40 +00:00
BorderlessFullscreen ,
2022-06-16 13:20:37 +00:00
/// Creates a fullscreen window that will render at desktop resolution.
///
/// The app will use the closest supported size from the given size and scale it to fit the screen.
2021-11-30 23:51:11 +00:00
SizedFullscreen ,
2022-06-16 13:20:37 +00:00
/// Creates a fullscreen window that uses the maximum supported size.
2021-11-30 23:51:11 +00:00
Fullscreen ,
2020-04-05 21:12:14 +00:00
}
impl Window {
2022-06-16 13:20:37 +00:00
/// Creates a new [`Window`].
2020-12-13 23:05:56 +00:00
pub fn new (
id : WindowId ,
window_descriptor : & WindowDescriptor ,
physical_width : u32 ,
physical_height : u32 ,
scale_factor : f64 ,
2021-01-25 04:06:06 +00:00
position : Option < IVec2 > ,
2021-06-02 02:59:17 +00:00
raw_window_handle : RawWindowHandle ,
2020-12-13 23:05:56 +00:00
) -> Self {
2020-04-05 21:12:14 +00:00
Window {
2020-06-25 23:02:21 +00:00
id ,
2020-12-13 23:05:56 +00:00
requested_width : window_descriptor . width ,
requested_height : window_descriptor . height ,
2021-01-25 04:06:06 +00:00
position ,
2020-12-13 23:05:56 +00:00
physical_width ,
physical_height ,
2021-03-03 02:56:50 +00:00
resize_constraints : window_descriptor . resize_constraints ,
2020-12-28 20:26:50 +00:00
scale_factor_override : window_descriptor . scale_factor_override ,
backend_scale_factor : scale_factor ,
2020-04-05 21:12:14 +00:00
title : window_descriptor . title . clone ( ) ,
2022-02-04 03:37:44 +00:00
present_mode : window_descriptor . present_mode ,
2020-08-13 08:47:40 +00:00
resizable : window_descriptor . resizable ,
2020-10-05 17:51:36 +00:00
decorations : window_descriptor . decorations ,
2020-10-16 21:07:01 +00:00
cursor_visible : window_descriptor . cursor_visible ,
cursor_locked : window_descriptor . cursor_locked ,
2021-12-20 22:04:45 +00:00
cursor_icon : CursorIcon ::Default ,
2021-10-15 23:47:42 +00:00
physical_cursor_position : None ,
2021-06-02 02:59:17 +00:00
raw_window_handle : RawWindowHandleWrapper ::new ( raw_window_handle ) ,
2021-02-13 05:32:32 +00:00
focused : true ,
2020-08-13 08:47:40 +00:00
mode : window_descriptor . mode ,
2020-09-21 23:12:34 +00:00
canvas : window_descriptor . canvas . clone ( ) ,
Optionally resize Window canvas element to fit parent element (#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). https://github.com/bevyengine/bevy-website/issues/371
In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: https://github.com/rust-windowing/winit/pull/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>
```
2022-05-20 23:13:48 +00:00
fit_canvas_to_parent : window_descriptor . fit_canvas_to_parent ,
2020-10-15 18:42:19 +00:00
command_queue : Vec ::new ( ) ,
2020-04-05 21:12:14 +00:00
}
}
2022-06-16 13:20:37 +00:00
/// Get the window's [`WindowId`].
2020-10-15 18:42:19 +00:00
#[ inline ]
pub fn id ( & self ) -> WindowId {
self . id
}
2020-12-13 23:05:56 +00:00
/// The current logical width of the window's client area.
2020-10-15 18:42:19 +00:00
#[ inline ]
2020-12-13 23:05:56 +00:00
pub fn width ( & self ) -> f32 {
2020-12-28 20:26:50 +00:00
( self . physical_width as f64 / self . scale_factor ( ) ) as f32
2020-10-15 18:42:19 +00:00
}
2020-12-13 23:05:56 +00:00
/// The current logical height of the window's client area.
2020-12-07 21:32:57 +00:00
#[ inline ]
2020-12-13 23:05:56 +00:00
pub fn height ( & self ) -> f32 {
2020-12-28 20:26:50 +00:00
( self . physical_height as f64 / self . scale_factor ( ) ) as f32
2020-12-02 04:25:31 +00:00
}
2020-12-13 23:05:56 +00:00
/// The requested window client area width in logical pixels from window
2022-01-09 23:20:13 +00:00
/// creation or the last call to [`set_resolution`](Window::set_resolution).
2020-12-13 23:05:56 +00:00
///
/// This may differ from the actual width depending on OS size limits and
/// the scaling factor for high DPI monitors.
2020-12-07 21:32:57 +00:00
#[ inline ]
2020-12-13 23:05:56 +00:00
pub fn requested_width ( & self ) -> f32 {
self . requested_width
2020-12-02 04:25:31 +00:00
}
2020-12-13 23:05:56 +00:00
/// The requested window client area height in logical pixels from window
2022-01-09 23:20:13 +00:00
/// creation or the last call to [`set_resolution`](Window::set_resolution).
2020-12-13 23:05:56 +00:00
///
/// This may differ from the actual width depending on OS size limits and
/// the scaling factor for high DPI monitors.
2020-10-15 18:42:19 +00:00
#[ inline ]
2020-12-13 23:05:56 +00:00
pub fn requested_height ( & self ) -> f32 {
self . requested_height
2020-12-07 21:32:57 +00:00
}
2020-12-13 23:05:56 +00:00
/// The window's client area width in physical pixels.
2020-12-07 21:32:57 +00:00
#[ inline ]
pub fn physical_width ( & self ) -> u32 {
self . physical_width
}
2020-12-13 23:05:56 +00:00
/// The window's client area height in physical pixels.
2020-12-07 21:32:57 +00:00
#[ inline ]
pub fn physical_height ( & self ) -> u32 {
self . physical_height
2020-10-15 18:42:19 +00:00
}
2021-03-03 02:56:50 +00:00
/// The window's client resize constraint in logical pixels.
#[ inline ]
pub fn resize_constraints ( & self ) -> WindowResizeConstraints {
self . resize_constraints
}
2021-01-25 04:06:06 +00:00
/// The window's client position in physical pixels.
#[ inline ]
pub fn position ( & self ) -> Option < IVec2 > {
self . position
}
2022-06-16 13:20:37 +00:00
/// Set whether or not the window is maximized.
2020-12-04 22:31:17 +00:00
#[ inline ]
pub fn set_maximized ( & mut self , maximized : bool ) {
self . command_queue
. push ( WindowCommand ::SetMaximized { maximized } ) ;
}
2021-01-25 04:06:06 +00:00
/// Sets the window to minimized or back.
///
/// # Platform-specific
/// - iOS / Android / Web: Unsupported.
/// - Wayland: Un-minimize is unsupported.
#[ inline ]
pub fn set_minimized ( & mut self , minimized : bool ) {
self . command_queue
. push ( WindowCommand ::SetMinimized { minimized } ) ;
}
/// Modifies the position of the window in physical pixels.
///
2021-03-11 00:27:30 +00:00
/// Note that the top-left hand corner of the desktop is not necessarily the same as the screen.
/// If the user uses a desktop with multiple monitors, the top-left hand corner of the
/// desktop is the top-left hand corner of the monitor at the top-left of the desktop. This
/// automatically un-maximizes the window if it's maximized.
2021-01-25 04:06:06 +00:00
///
/// # Platform-specific
///
2021-03-11 00:27:30 +00:00
/// - iOS: Can only be called on the main thread. Sets the top left coordinates of the window in
/// the screen space coordinate system.
2021-01-25 04:06:06 +00:00
/// - Web: Sets the top-left coordinates relative to the viewport.
/// - Android / Wayland: Unsupported.
#[ inline ]
pub fn set_position ( & mut self , position : IVec2 ) {
self . command_queue
2022-02-13 22:33:55 +00:00
. push ( WindowCommand ::SetPosition { position } ) ;
2021-01-25 04:06:06 +00:00
}
2021-03-03 02:56:50 +00:00
/// Modifies the minimum and maximum window bounds for resizing in logical pixels.
#[ inline ]
pub fn set_resize_constraints ( & mut self , resize_constraints : WindowResizeConstraints ) {
self . command_queue
. push ( WindowCommand ::SetResizeConstraints { resize_constraints } ) ;
}
2020-12-13 23:05:56 +00:00
/// Request the OS to resize the window such the the client area matches the
/// specified width and height.
2020-12-28 20:26:50 +00:00
#[ allow(clippy::float_cmp) ]
2020-12-13 23:05:56 +00:00
pub fn set_resolution ( & mut self , width : f32 , height : f32 ) {
2020-12-28 20:26:50 +00:00
if self . requested_width = = width & & self . requested_height = = height {
return ;
}
2021-03-03 02:56:50 +00:00
2020-12-13 23:05:56 +00:00
self . requested_width = width ;
self . requested_height = height ;
2020-12-07 21:32:57 +00:00
self . command_queue . push ( WindowCommand ::SetResolution {
2020-12-28 20:26:50 +00:00
logical_resolution : ( self . requested_width , self . requested_height ) ,
scale_factor : self . scale_factor ( ) ,
} ) ;
}
2022-06-16 13:20:37 +00:00
/// Override the os-reported scaling factor.
2020-12-28 20:26:50 +00:00
#[ allow(clippy::float_cmp) ]
pub fn set_scale_factor_override ( & mut self , scale_factor : Option < f64 > ) {
if self . scale_factor_override = = scale_factor {
return ;
}
self . scale_factor_override = scale_factor ;
self . command_queue . push ( WindowCommand ::SetScaleFactor {
scale_factor : self . scale_factor ( ) ,
} ) ;
self . command_queue . push ( WindowCommand ::SetResolution {
logical_resolution : ( self . requested_width , self . requested_height ) ,
scale_factor : self . scale_factor ( ) ,
2020-12-07 21:32:57 +00:00
} ) ;
2020-10-15 18:42:19 +00:00
}
2020-12-03 19:30:27 +00:00
#[ allow(missing_docs) ]
#[ inline ]
2020-12-13 23:05:56 +00:00
pub fn update_scale_factor_from_backend ( & mut self , scale_factor : f64 ) {
2020-12-28 20:26:50 +00:00
self . backend_scale_factor = scale_factor ;
2020-10-15 18:42:19 +00:00
}
2020-12-01 02:24:49 +00:00
2020-12-03 19:30:27 +00:00
#[ allow(missing_docs) ]
#[ inline ]
2020-12-13 23:05:56 +00:00
pub fn update_actual_size_from_backend ( & mut self , physical_width : u32 , physical_height : u32 ) {
self . physical_width = physical_width ;
self . physical_height = physical_height ;
2020-12-01 02:24:49 +00:00
}
2021-01-25 04:06:06 +00:00
#[ allow(missing_docs) ]
#[ inline ]
pub fn update_actual_position_from_backend ( & mut self , position : IVec2 ) {
self . position = Some ( position ) ;
}
2020-12-13 23:05:56 +00:00
/// The ratio of physical pixels to logical pixels
///
/// `physical_pixels = logical_pixels * scale_factor`
2020-12-01 02:24:49 +00:00
pub fn scale_factor ( & self ) -> f64 {
2020-12-28 20:26:50 +00:00
self . scale_factor_override
. unwrap_or ( self . backend_scale_factor )
}
/// The window scale factor as reported by the window backend.
2022-06-16 13:20:37 +00:00
///
2022-01-09 23:20:13 +00:00
/// This value is unaffected by [`scale_factor_override`](Window::scale_factor_override).
2020-12-28 20:26:50 +00:00
#[ inline ]
pub fn backend_scale_factor ( & self ) -> f64 {
self . backend_scale_factor
}
2022-06-16 13:20:37 +00:00
/// The scale factor set with [`set_scale_factor_override`](Window::set_scale_factor_override).
///
/// This value may be different from the scale factor reported by the window backend.
2020-12-28 20:26:50 +00:00
#[ inline ]
pub fn scale_factor_override ( & self ) -> Option < f64 > {
self . scale_factor_override
2020-12-01 02:24:49 +00:00
}
2022-06-16 13:20:37 +00:00
/// Get the window's title.
2020-11-11 01:20:31 +00:00
#[ inline ]
2020-10-15 18:42:19 +00:00
pub fn title ( & self ) -> & str {
& self . title
}
2022-06-16 13:20:37 +00:00
/// Set the window's title.
2020-10-15 18:42:19 +00:00
pub fn set_title ( & mut self , title : String ) {
self . title = title . to_string ( ) ;
self . command_queue . push ( WindowCommand ::SetTitle { title } ) ;
}
2020-11-11 01:20:31 +00:00
#[ inline ]
2022-02-04 03:37:44 +00:00
#[ doc(alias = " vsync " ) ]
2022-06-16 13:20:37 +00:00
/// Get the window's [`PresentMode`].
2022-02-04 03:37:44 +00:00
pub fn present_mode ( & self ) -> PresentMode {
self . present_mode
2020-10-15 18:42:19 +00:00
}
2020-11-11 01:20:31 +00:00
#[ inline ]
2022-02-04 03:37:44 +00:00
#[ doc(alias = " set_vsync " ) ]
2022-06-16 13:20:37 +00:00
/// Set the window's [`PresentMode`].
2022-02-04 03:37:44 +00:00
pub fn set_present_mode ( & mut self , present_mode : PresentMode ) {
self . present_mode = present_mode ;
self . command_queue
. push ( WindowCommand ::SetPresentMode { present_mode } ) ;
2020-10-15 18:42:19 +00:00
}
2022-06-16 13:20:37 +00:00
/// Get whether or not the window is resizable.
2020-11-11 01:20:31 +00:00
#[ inline ]
2020-10-15 18:42:19 +00:00
pub fn resizable ( & self ) -> bool {
self . resizable
}
2022-06-16 13:20:37 +00:00
/// Set whether or not the window is resizable.
2020-10-15 18:42:19 +00:00
pub fn set_resizable ( & mut self , resizable : bool ) {
self . resizable = resizable ;
self . command_queue
. push ( WindowCommand ::SetResizable { resizable } ) ;
}
2022-06-16 13:20:37 +00:00
/// Get whether or not decorations are enabled.
///
/// (Decorations are the minimize, maximize, and close buttons on desktop apps)
///
/// ## Platform-specific
///
/// **`iOS`**, **`Android`**, and the **`Web`** do not have decorations.
2020-11-11 01:20:31 +00:00
#[ inline ]
2020-10-15 18:42:19 +00:00
pub fn decorations ( & self ) -> bool {
self . decorations
}
2022-06-16 13:20:37 +00:00
/// Set whether or not decorations are enabled.
///
/// (Decorations are the minimize, maximize, and close buttons on desktop apps)
///
/// ## Platform-specific
///
/// **`iOS`**, **`Android`**, and the **`Web`** do not have decorations.
2020-10-15 18:42:19 +00:00
pub fn set_decorations ( & mut self , decorations : bool ) {
self . decorations = decorations ;
self . command_queue
. push ( WindowCommand ::SetDecorations { decorations } ) ;
}
2022-06-16 13:20:37 +00:00
/// Get whether or not the cursor is locked.
///
/// ## Platform-specific
///
/// - **`macOS`** doesn't support cursor lock, but most windowing plugins can emulate it. See [issue #4875](https://github.com/bevyengine/bevy/issues/4875#issuecomment-1153977546) for more information.
/// - **`iOS/Android`** don't have cursors.
2020-11-11 01:20:31 +00:00
#[ inline ]
2020-10-16 21:07:01 +00:00
pub fn cursor_locked ( & self ) -> bool {
self . cursor_locked
}
2022-06-16 13:20:37 +00:00
/// Set whether or not the cursor is locked.
///
/// This doesn't hide the cursor. For that, use [`set_cursor_visibility`](Window::set_cursor_visibility)
///
/// ## Platform-specific
///
/// - **`macOS`** doesn't support cursor lock, but most windowing plugins can emulate it. See [issue #4875](https://github.com/bevyengine/bevy/issues/4875#issuecomment-1153977546) for more information.
/// - **`iOS/Android`** don't have cursors.
2020-10-16 21:07:01 +00:00
pub fn set_cursor_lock_mode ( & mut self , lock_mode : bool ) {
self . cursor_locked = lock_mode ;
self . command_queue
. push ( WindowCommand ::SetCursorLockMode { locked : lock_mode } ) ;
}
2022-06-16 13:20:37 +00:00
/// Get whether or not the cursor is visible.
///
/// ## Platform-specific
///
/// - **`Windows`**, **`X11`**, and **`Wayland`**: The cursor is hidden only when inside the window. To stop the cursor from leaving the window, use [`set_cursor_lock_mode`](Window::set_cursor_lock_mode).
/// - **`macOS`**: The cursor is hidden only when the window is focused.
/// - **`iOS`** and **`Android`** do not have cursors
2020-11-11 01:20:31 +00:00
#[ inline ]
2020-10-16 21:07:01 +00:00
pub fn cursor_visible ( & self ) -> bool {
self . cursor_visible
}
2022-06-16 13:20:37 +00:00
/// Set whether or not the cursor is visible.
///
/// ## Platform-specific
///
/// - **`Windows`**, **`X11`**, and **`Wayland`**: The cursor is hidden only when inside the window. To stop the cursor from leaving the window, use [`set_cursor_lock_mode`](Window::set_cursor_lock_mode).
/// - **`macOS`**: The cursor is hidden only when the window is focused.
/// - **`iOS`** and **`Android`** do not have cursors
2020-10-16 21:07:01 +00:00
pub fn set_cursor_visibility ( & mut self , visibile_mode : bool ) {
self . cursor_visible = visibile_mode ;
self . command_queue . push ( WindowCommand ::SetCursorVisibility {
visible : visibile_mode ,
} ) ;
}
2022-06-16 13:20:37 +00:00
/// Get the current [`CursorIcon`]
2021-12-20 22:04:45 +00:00
#[ inline ]
pub fn cursor_icon ( & self ) -> CursorIcon {
self . cursor_icon
}
2022-06-16 13:20:37 +00:00
/// Set the [`CursorIcon`]
2021-12-20 22:04:45 +00:00
pub fn set_cursor_icon ( & mut self , icon : CursorIcon ) {
self . command_queue
. push ( WindowCommand ::SetCursorIcon { icon } ) ;
}
2021-10-15 23:47:42 +00:00
/// The current mouse position, in physical pixels.
#[ inline ]
pub fn physical_cursor_position ( & self ) -> Option < DVec2 > {
self . physical_cursor_position
}
/// The current mouse position, in logical pixels, taking into account the screen scale factor.
2020-12-03 19:30:27 +00:00
#[ inline ]
2021-04-28 21:26:47 +00:00
#[ doc(alias = " mouse position " ) ]
2020-12-03 19:30:27 +00:00
pub fn cursor_position ( & self ) -> Option < Vec2 > {
2021-10-15 23:47:42 +00:00
self . physical_cursor_position
. map ( | p | ( p / self . scale_factor ( ) ) . as_vec2 ( ) )
2020-12-03 19:30:27 +00:00
}
2022-06-16 13:20:37 +00:00
/// Set the cursor's position
2020-12-03 20:39:03 +00:00
pub fn set_cursor_position ( & mut self , position : Vec2 ) {
2020-11-26 01:31:10 +00:00
self . command_queue
2020-12-03 20:39:03 +00:00
. push ( WindowCommand ::SetCursorPosition { position } ) ;
2020-11-26 01:31:10 +00:00
}
2021-02-13 05:32:32 +00:00
#[ allow(missing_docs) ]
#[ inline ]
pub fn update_focused_status_from_backend ( & mut self , focused : bool ) {
self . focused = focused ;
}
2020-12-03 19:30:27 +00:00
#[ allow(missing_docs) ]
#[ inline ]
2021-10-15 23:47:42 +00:00
pub fn update_cursor_physical_position_from_backend ( & mut self , cursor_position : Option < DVec2 > ) {
self . physical_cursor_position = cursor_position ;
2020-12-03 19:30:27 +00:00
}
2022-06-16 13:20:37 +00:00
/// Get the window's [`WindowMode`]
2020-11-11 01:20:31 +00:00
#[ inline ]
2020-10-15 18:42:19 +00:00
pub fn mode ( & self ) -> WindowMode {
self . mode
}
2022-06-16 13:20:37 +00:00
/// Set the window's [`WindowMode`]
2020-10-15 18:42:19 +00:00
pub fn set_mode ( & mut self , mode : WindowMode ) {
self . mode = mode ;
self . command_queue . push ( WindowCommand ::SetWindowMode {
mode ,
2020-12-07 21:32:57 +00:00
resolution : ( self . physical_width , self . physical_height ) ,
2020-10-15 18:42:19 +00:00
} ) ;
}
2022-06-16 13:20:37 +00:00
/// Close the operating system window corresponding to this [`Window`].
///
2022-05-05 13:35:43 +00:00
/// This will also lead to this [`Window`] being removed from the
/// [`Windows`] resource.
///
/// If the default [`WindowPlugin`] is used, when no windows are
/// open, the [app will exit](bevy_app::AppExit).
/// To disable this behaviour, set `exit_on_all_closed` on the [`WindowPlugin`]
/// to `false`
///
/// [`Windows`]: crate::Windows
/// [`WindowPlugin`]: crate::WindowPlugin
pub fn close ( & mut self ) {
self . command_queue . push ( WindowCommand ::Close ) ;
}
2020-11-11 01:20:31 +00:00
#[ inline ]
2020-10-15 19:49:56 +00:00
pub fn drain_commands ( & mut self ) -> impl Iterator < Item = WindowCommand > + '_ {
2020-10-15 18:42:19 +00:00
self . command_queue . drain ( .. )
}
2022-06-16 13:20:37 +00:00
/// Get whether or not the window has focus.
///
/// A window loses focus when the user switches to another window, and regains focus when the user uses the window again
2021-02-13 05:32:32 +00:00
#[ inline ]
pub fn is_focused ( & self ) -> bool {
self . focused
}
2022-06-16 13:20:37 +00:00
/// Get the [`RawWindowHandleWrapper`] corresponding to this window
2021-06-02 02:59:17 +00:00
pub fn raw_window_handle ( & self ) -> RawWindowHandleWrapper {
self . raw_window_handle . clone ( )
}
Optionally resize Window canvas element to fit parent element (#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). https://github.com/bevyengine/bevy-website/issues/371
In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: https://github.com/rust-windowing/winit/pull/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>
```
2022-05-20 23:13:48 +00:00
2022-06-16 13:20:37 +00:00
/// The "html canvas" element selector.
///
/// If set, this selector will be used to find a matching html canvas element,
Optionally resize Window canvas element to fit parent element (#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). https://github.com/bevyengine/bevy-website/issues/371
In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: https://github.com/rust-windowing/winit/pull/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>
```
2022-05-20 23:13:48 +00:00
/// rather than creating a new one.
/// Uses the [CSS selector format](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
///
/// This value has no effect on non-web platforms.
#[ inline ]
pub fn canvas ( & self ) -> Option < & str > {
self . canvas . as_deref ( )
}
/// Whether or not to fit the canvas element's size to its parent element's size.
///
/// **Warning**: this will not behave as expected for parents that set their size according to the size of their
/// children. This creates a "feedback loop" that will result in the canvas growing on each resize. When using this
/// feature, ensure the parent's size is not affected by its children.
///
/// This value has no effect on non-web platforms.
#[ inline ]
pub fn fit_canvas_to_parent ( & self ) -> bool {
self . fit_canvas_to_parent
}
2020-04-05 21:12:14 +00:00
}
2022-05-30 16:59:41 +00:00
/// Describes the information needed for creating a window.
///
/// This should be set up before adding the [`WindowPlugin`](crate::WindowPlugin).
/// Most of these settings can also later be configured through the [`Window`](crate::Window) resource.
///
/// See [`examples/window/window_settings.rs`] for usage.
///
/// [`examples/window/window_settings.rs`]: https://github.com/bevyengine/bevy/blob/latest/examples/window/window_settings.rs
2020-04-05 21:12:14 +00:00
#[ derive(Debug, Clone) ]
pub struct WindowDescriptor {
2022-05-30 16:59:41 +00:00
/// The requested logical width of the window's client area.
2022-06-16 13:20:37 +00:00
///
2022-05-30 16:59:41 +00:00
/// May vary from the physical width due to different pixel density on different monitors.
2020-12-13 23:05:56 +00:00
pub width : f32 ,
2022-05-30 16:59:41 +00:00
/// The requested logical height of the window's client area.
2022-06-16 13:20:37 +00:00
///
2022-05-30 16:59:41 +00:00
/// May vary from the physical height due to different pixel density on different monitors.
2020-12-13 23:05:56 +00:00
pub height : f32 ,
2022-05-30 16:59:41 +00:00
/// The position on the screen that the window will be centered at.
2022-06-16 13:20:37 +00:00
///
2022-05-30 16:59:41 +00:00
/// If set to `None`, some platform-specific position will be chosen.
2021-11-06 20:34:31 +00:00
pub position : Option < Vec2 > ,
2022-05-30 16:59:41 +00:00
/// Sets minimum and maximum resize limits.
2021-03-03 02:56:50 +00:00
pub resize_constraints : WindowResizeConstraints ,
2022-05-30 16:59:41 +00:00
/// Overrides the window's ratio of physical pixels to logical pixels.
2022-06-16 13:20:37 +00:00
///
2022-05-30 16:59:41 +00:00
/// If there are some scaling problems on X11 try to set this option to `Some(1.0)`.
2020-12-28 20:26:50 +00:00
pub scale_factor_override : Option < f64 > ,
2022-05-30 16:59:41 +00:00
/// Sets the title that displays on the window top bar, on the system task bar and other OS specific places.
2022-06-16 13:20:37 +00:00
///
2022-05-30 16:59:41 +00:00
/// ## Platform-specific
/// - Web: Unsupported.
2020-04-05 21:12:14 +00:00
pub title : String ,
2022-05-30 16:59:41 +00:00
/// Controls when a frame is presented to the screen.
2022-02-04 03:37:44 +00:00
#[ doc(alias = " vsync " ) ]
2022-06-16 13:20:37 +00:00
/// The window's [`PresentMode`].
///
/// Used to select whether or not VSync is used
2022-02-04 03:37:44 +00:00
pub present_mode : PresentMode ,
2022-05-30 16:59:41 +00:00
/// Sets whether the window is resizable.
2022-06-16 13:20:37 +00:00
///
2022-05-30 16:59:41 +00:00
/// ## Platform-specific
/// - iOS / Android / Web: Unsupported.
2020-08-13 08:47:40 +00:00
pub resizable : bool ,
2022-05-30 16:59:41 +00:00
/// Sets whether the window should have borders and bars.
2020-10-05 17:51:36 +00:00
pub decorations : bool ,
2022-05-30 16:59:41 +00:00
/// Sets whether the cursor is visible when the window has focus.
2020-10-16 21:07:01 +00:00
pub cursor_visible : bool ,
2022-05-30 16:59:41 +00:00
/// Sets whether the window locks the cursor inside its borders when the window has focus.
2020-10-16 21:07:01 +00:00
pub cursor_locked : bool ,
2022-05-30 16:59:41 +00:00
/// Sets the [`WindowMode`](crate::WindowMode).
2020-08-13 08:47:40 +00:00
pub mode : WindowMode ,
2021-12-08 20:53:35 +00:00
/// Sets whether the background of the window should be transparent.
2022-06-16 13:20:37 +00:00
///
2022-05-30 16:59:41 +00:00
/// ## Platform-specific
2021-12-08 20:53:35 +00:00
/// - iOS / Android / Web: Unsupported.
2021-12-09 20:14:00 +00:00
/// - macOS X: Not working as expected.
/// - Windows 11: Not working as expected
2021-12-18 00:09:23 +00:00
/// macOS X transparent works with winit out of the box, so this issue might be related to: <https://github.com/gfx-rs/wgpu/issues/687>
/// Windows 11 is related to <https://github.com/rust-windowing/winit/issues/2082>
2021-12-08 20:53:35 +00:00
pub transparent : bool ,
2022-06-16 13:20:37 +00:00
/// The "html canvas" element selector.
///
/// If set, this selector will be used to find a matching html canvas element,
Optionally resize Window canvas element to fit parent element (#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). https://github.com/bevyengine/bevy-website/issues/371
In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: https://github.com/rust-windowing/winit/pull/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>
```
2022-05-20 23:13:48 +00:00
/// rather than creating a new one.
/// Uses the [CSS selector format](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
///
/// This value has no effect on non-web platforms.
2020-09-21 23:12:34 +00:00
pub canvas : Option < String > ,
Optionally resize Window canvas element to fit parent element (#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). https://github.com/bevyengine/bevy-website/issues/371
In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: https://github.com/rust-windowing/winit/pull/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>
```
2022-05-20 23:13:48 +00:00
/// Whether or not to fit the canvas element's size to its parent element's size.
///
/// **Warning**: this will not behave as expected for parents that set their size according to the size of their
/// children. This creates a "feedback loop" that will result in the canvas growing on each resize. When using this
/// feature, ensure the parent's size is not affected by its children.
///
/// This value has no effect on non-web platforms.
pub fit_canvas_to_parent : bool ,
2020-04-05 21:12:14 +00:00
}
impl Default for WindowDescriptor {
fn default ( ) -> Self {
WindowDescriptor {
2022-02-04 02:42:58 +00:00
title : " app " . to_string ( ) ,
2020-12-13 23:05:56 +00:00
width : 1280. ,
height : 720. ,
2021-11-06 20:34:31 +00:00
position : None ,
2021-03-03 02:56:50 +00:00
resize_constraints : WindowResizeConstraints ::default ( ) ,
2020-12-28 20:26:50 +00:00
scale_factor_override : None ,
2022-02-04 03:37:44 +00:00
present_mode : PresentMode ::Fifo ,
2020-08-13 08:47:40 +00:00
resizable : true ,
2020-10-05 17:51:36 +00:00
decorations : true ,
2020-10-16 21:07:01 +00:00
cursor_locked : false ,
cursor_visible : true ,
2020-08-13 08:47:40 +00:00
mode : WindowMode ::Windowed ,
2021-12-08 20:53:35 +00:00
transparent : false ,
2020-09-21 23:12:34 +00:00
canvas : None ,
Optionally resize Window canvas element to fit parent element (#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). https://github.com/bevyengine/bevy-website/issues/371
In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: https://github.com/rust-windowing/winit/pull/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>
```
2022-05-20 23:13:48 +00:00
fit_canvas_to_parent : false ,
2020-04-05 21:12:14 +00:00
}
}
2020-04-06 23:15:59 +00:00
}