Windows as Entities (#5589)

# Objective

Fix https://github.com/bevyengine/bevy/issues/4530

- Make it easier to open/close/modify windows by setting them up as `Entity`s with a `Window` component.
- Make multiple windows very simple to set up. (just add a `Window` component to an entity and it should open)

## Solution

- Move all properties of window descriptor to ~components~ a component.
- Replace `WindowId` with `Entity`.
- ~Use change detection for components to update backend rather than events/commands. (The `CursorMoved`/`WindowResized`/... events are kept for user convenience.~
  Check each field individually to see what we need to update, events are still kept for user convenience.

---

## Changelog

- `WindowDescriptor` renamed to `Window`.
    - Width/height consolidated into a `WindowResolution` component.
    - Requesting maximization/minimization is done on the [`Window::state`] field.
- `WindowId` is now `Entity`.

## Migration Guide

- Replace `WindowDescriptor` with `Window`.
    - Change `width` and `height` fields in a `WindowResolution`, either by doing
      ```rust
      WindowResolution::new(width, height) // Explicitly
      // or using From<_> for tuples for convenience
      (1920., 1080.).into()
      ```
- Replace any `WindowCommand` code to just modify the `Window`'s fields directly  and creating/closing windows is now by spawning/despawning an entity with a `Window` component like so:
  ```rust
  let window = commands.spawn(Window { ... }).id(); // open window
  commands.entity(window).despawn(); // close window
  ```

## Unresolved
- ~How do we tell when a window is minimized by a user?~
  ~Currently using the `Resize(0, 0)` as an indicator of minimization.~
  No longer attempting to tell given how finnicky this was across platforms, now the user can only request that a window be maximized/minimized.
  
 ## Future work
 - Move `exit_on_close` functionality out from windowing and into app(?)
 - https://github.com/bevyengine/bevy/issues/5621
 - https://github.com/bevyengine/bevy/issues/7099
 - https://github.com/bevyengine/bevy/issues/7098


Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Aceeri 2023-01-19 00:38:28 +00:00
parent f0c504947c
commit ddfafab971
47 changed files with 1940 additions and 1892 deletions

View file

@ -13,6 +13,7 @@ use bevy_ecs::{
component::Component,
entity::Entity,
event::EventReader,
prelude::With,
reflect::ReflectComponent,
system::{Commands, Query, Res},
};
@ -21,7 +22,10 @@ use bevy_reflect::prelude::*;
use bevy_reflect::FromReflect;
use bevy_transform::components::GlobalTransform;
use bevy_utils::HashSet;
use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
use bevy_window::{
NormalizedWindowRef, PrimaryWindow, Window, WindowCreated, WindowRef, WindowResized,
};
use std::{borrow::Cow, ops::Range};
use wgpu::{Extent3d, TextureFormat};
@ -325,10 +329,21 @@ impl CameraRenderGraph {
/// The "target" that a [`Camera`] will render to. For example, this could be a [`Window`](bevy_window::Window)
/// swapchain or an [`Image`].
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Clone, Reflect)]
pub enum RenderTarget {
/// Window to which the camera's view is rendered.
Window(WindowId),
Window(WindowRef),
/// Image to which the camera's view is rendered.
Image(Handle<Image>),
}
/// Normalized version of the render target.
///
/// Once we have this we shouldn't need to resolve it down anymore.
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum NormalizedRenderTarget {
/// Window to which the camera's view is rendered.
Window(NormalizedWindowRef),
/// Image to which the camera's view is rendered.
Image(Handle<Image>),
}
@ -340,16 +355,28 @@ impl Default for RenderTarget {
}
impl RenderTarget {
/// Normalize the render target down to a more concrete value, mostly used for equality comparisons.
pub fn normalize(&self, primary_window: Option<Entity>) -> Option<NormalizedRenderTarget> {
match self {
RenderTarget::Window(window_ref) => window_ref
.normalize(primary_window)
.map(NormalizedRenderTarget::Window),
RenderTarget::Image(handle) => Some(NormalizedRenderTarget::Image(handle.clone())),
}
}
}
impl NormalizedRenderTarget {
pub fn get_texture_view<'a>(
&self,
windows: &'a ExtractedWindows,
images: &'a RenderAssets<Image>,
) -> Option<&'a TextureView> {
match self {
RenderTarget::Window(window_id) => windows
.get(window_id)
NormalizedRenderTarget::Window(window_ref) => windows
.get(&window_ref.entity())
.and_then(|window| window.swap_chain_texture.as_ref()),
RenderTarget::Image(image_handle) => {
NormalizedRenderTarget::Image(image_handle) => {
images.get(image_handle).map(|image| &image.texture_view)
}
}
@ -362,47 +389,55 @@ impl RenderTarget {
images: &'a RenderAssets<Image>,
) -> Option<TextureFormat> {
match self {
RenderTarget::Window(window_id) => windows
.get(window_id)
NormalizedRenderTarget::Window(window_ref) => windows
.get(&window_ref.entity())
.and_then(|window| window.swap_chain_texture_format),
RenderTarget::Image(image_handle) => {
NormalizedRenderTarget::Image(image_handle) => {
images.get(image_handle).map(|image| image.texture_format)
}
}
}
pub fn get_render_target_info(
pub fn get_render_target_info<'a>(
&self,
windows: &Windows,
resolutions: impl IntoIterator<Item = (Entity, &'a Window)>,
images: &Assets<Image>,
) -> Option<RenderTargetInfo> {
Some(match self {
RenderTarget::Window(window_id) => {
let window = windows.get(*window_id)?;
RenderTargetInfo {
physical_size: UVec2::new(window.physical_width(), window.physical_height()),
scale_factor: window.scale_factor(),
}
}
RenderTarget::Image(image_handle) => {
match self {
NormalizedRenderTarget::Window(window_ref) => resolutions
.into_iter()
.find(|(entity, _)| *entity == window_ref.entity())
.map(|(_, window)| RenderTargetInfo {
physical_size: UVec2::new(
window.resolution.physical_width(),
window.resolution.physical_height(),
),
scale_factor: window.resolution.scale_factor(),
}),
NormalizedRenderTarget::Image(image_handle) => {
let image = images.get(image_handle)?;
let Extent3d { width, height, .. } = image.texture_descriptor.size;
RenderTargetInfo {
Some(RenderTargetInfo {
physical_size: UVec2::new(width, height),
scale_factor: 1.0,
}
})
}
})
}
}
// Check if this render target is contained in the given changed windows or images.
fn is_changed(
&self,
changed_window_ids: &[WindowId],
changed_window_ids: &HashSet<Entity>,
changed_image_handles: &HashSet<&Handle<Image>>,
) -> bool {
match self {
RenderTarget::Window(window_id) => changed_window_ids.contains(window_id),
RenderTarget::Image(image_handle) => changed_image_handles.contains(&image_handle),
NormalizedRenderTarget::Window(window_ref) => {
changed_window_ids.contains(&window_ref.entity())
}
NormalizedRenderTarget::Image(image_handle) => {
changed_image_handles.contains(&image_handle)
}
}
}
}
@ -431,29 +466,16 @@ pub fn camera_system<T: CameraProjection + Component>(
mut window_resized_events: EventReader<WindowResized>,
mut window_created_events: EventReader<WindowCreated>,
mut image_asset_events: EventReader<AssetEvent<Image>>,
windows: Res<Windows>,
primary_window: Query<Entity, With<PrimaryWindow>>,
windows: Query<(Entity, &Window)>,
images: Res<Assets<Image>>,
mut cameras: Query<(&mut Camera, &mut T)>,
) {
let mut changed_window_ids = Vec::new();
let primary_window = primary_window.iter().next();
// Collect all unique window IDs of changed windows by inspecting created windows
for event in window_created_events.iter() {
if changed_window_ids.contains(&event.id) {
continue;
}
changed_window_ids.push(event.id);
}
// Collect all unique window IDs of changed windows by inspecting resized windows
for event in window_resized_events.iter() {
if changed_window_ids.contains(&event.id) {
continue;
}
changed_window_ids.push(event.id);
}
let mut changed_window_ids = HashSet::new();
changed_window_ids.extend(window_created_events.iter().map(|event| event.window));
changed_window_ids.extend(window_resized_events.iter().map(|event| event.window));
let changed_image_handles: HashSet<&Handle<Image>> = image_asset_events
.iter()
@ -472,18 +494,18 @@ pub fn camera_system<T: CameraProjection + Component>(
.as_ref()
.map(|viewport| viewport.physical_size);
if camera
.target
.is_changed(&changed_window_ids, &changed_image_handles)
|| camera.is_added()
|| camera_projection.is_changed()
|| camera.computed.old_viewport_size != viewport_size
{
camera.computed.target_info = camera.target.get_render_target_info(&windows, &images);
camera.computed.old_viewport_size = viewport_size;
if let Some(size) = camera.logical_viewport_size() {
camera_projection.update(size.x, size.y);
camera.computed.projection_matrix = camera_projection.get_projection_matrix();
if let Some(normalized_target) = camera.target.normalize(primary_window) {
if normalized_target.is_changed(&changed_window_ids, &changed_image_handles)
|| camera.is_added()
|| camera_projection.is_changed()
|| camera.computed.old_viewport_size != viewport_size
{
camera.computed.target_info =
normalized_target.get_render_target_info(&windows, &images);
if let Some(size) = camera.logical_viewport_size() {
camera_projection.update(size.x, size.y);
camera.computed.projection_matrix = camera_projection.get_projection_matrix();
}
}
}
}
@ -491,7 +513,7 @@ pub fn camera_system<T: CameraProjection + Component>(
#[derive(Component, Debug)]
pub struct ExtractedCamera {
pub target: RenderTarget,
pub target: Option<NormalizedRenderTarget>,
pub physical_viewport_size: Option<UVec2>,
pub physical_target_size: Option<UVec2>,
pub viewport: Option<Viewport>,
@ -510,7 +532,9 @@ pub fn extract_cameras(
&VisibleEntities,
)>,
>,
primary_window: Extract<Query<Entity, With<PrimaryWindow>>>,
) {
let primary_window = primary_window.iter().next();
for (entity, camera, camera_render_graph, transform, visible_entities) in query.iter() {
if !camera.is_active {
continue;
@ -525,7 +549,7 @@ pub fn extract_cameras(
}
commands.get_or_spawn(entity).insert((
ExtractedCamera {
target: camera.target.clone(),
target: camera.target.normalize(primary_window),
viewport: camera.viewport.clone(),
physical_viewport_size: Some(viewport_size),
physical_target_size: Some(target_size),

View file

@ -1,5 +1,5 @@
use crate::{
camera::{ExtractedCamera, RenderTarget},
camera::{ExtractedCamera, NormalizedRenderTarget},
render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue},
renderer::RenderContext,
view::ExtractedWindows,
@ -52,8 +52,8 @@ impl Node for CameraDriverNode {
}
previous_order_target = Some(new_order_target);
if let Ok((_, camera)) = self.cameras.get_manual(world, entity) {
if let RenderTarget::Window(id) = camera.target {
camera_windows.insert(id);
if let Some(NormalizedRenderTarget::Window(window_ref)) = camera.target {
camera_windows.insert(window_ref.entity());
}
graph
.run_sub_graph(camera.render_graph.clone(), vec![SlotValue::Entity(entity)])?;

View file

@ -37,6 +37,7 @@ pub mod prelude {
};
}
use bevy_window::{PrimaryWindow, RawHandleWrapper};
use globals::GlobalsPlugin;
pub use once_cell;
@ -50,7 +51,7 @@ use crate::{
};
use bevy_app::{App, AppLabel, Plugin};
use bevy_asset::{AddAsset, AssetServer};
use bevy_ecs::prelude::*;
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_utils::tracing::debug;
use std::{
any::TypeId,
@ -138,17 +139,17 @@ impl Plugin for RenderPlugin {
.init_asset_loader::<ShaderLoader>()
.init_debug_asset_loader::<ShaderLoader>();
if let Some(backends) = self.wgpu_settings.backends {
let windows = app.world.resource_mut::<bevy_window::Windows>();
let instance = wgpu::Instance::new(backends);
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
SystemState::new(&mut app.world);
let primary_window = system_state.get(&app.world);
let surface = windows
.get_primary()
.and_then(|window| window.raw_handle())
.map(|wrapper| unsafe {
let handle = wrapper.get_handle();
instance.create_surface(&handle)
});
if let Some(backends) = self.wgpu_settings.backends {
let instance = wgpu::Instance::new(backends);
let surface = primary_window.get_single().ok().map(|wrapper| unsafe {
// SAFETY: Plugins should be set up on the main thread.
let handle = wrapper.get_handle();
instance.create_surface(&handle)
});
let request_adapter_options = wgpu::RequestAdapterOptions {
power_preference: self.wgpu_settings.power_preference,

View file

@ -285,10 +285,10 @@ fn prepare_view_targets(
) {
let mut textures = HashMap::default();
for (entity, camera, view) in cameras.iter() {
if let Some(target_size) = camera.physical_target_size {
if let (Some(target_size), Some(target)) = (camera.physical_target_size, &camera.target) {
if let (Some(out_texture_view), Some(out_texture_format)) = (
camera.target.get_texture_view(&windows, &images),
camera.target.get_texture_format(&windows, &images),
target.get_texture_view(&windows, &images),
target.get_texture_format(&windows, &images),
) {
let size = Extent3d {
width: target_size.x,

View file

@ -7,7 +7,7 @@ use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
use bevy_utils::{tracing::debug, HashMap, HashSet};
use bevy_window::{
CompositeAlphaMode, PresentMode, RawHandleWrapper, WindowClosed, WindowId, Windows,
CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosed,
};
use std::ops::{Deref, DerefMut};
use wgpu::TextureFormat;
@ -40,8 +40,9 @@ impl Plugin for WindowRenderPlugin {
}
pub struct ExtractedWindow {
pub id: WindowId,
pub raw_handle: Option<RawHandleWrapper>,
/// An entity that contains the components in [`Window`].
pub entity: Entity,
pub handle: RawHandleWrapper,
pub physical_width: u32,
pub physical_height: u32,
pub present_mode: PresentMode,
@ -54,11 +55,12 @@ pub struct ExtractedWindow {
#[derive(Default, Resource)]
pub struct ExtractedWindows {
pub windows: HashMap<WindowId, ExtractedWindow>,
pub primary: Option<Entity>,
pub windows: HashMap<Entity, ExtractedWindow>,
}
impl Deref for ExtractedWindows {
type Target = HashMap<WindowId, ExtractedWindow>;
type Target = HashMap<Entity, ExtractedWindow>;
fn deref(&self) -> &Self::Target {
&self.windows
@ -74,36 +76,37 @@ impl DerefMut for ExtractedWindows {
fn extract_windows(
mut extracted_windows: ResMut<ExtractedWindows>,
mut closed: Extract<EventReader<WindowClosed>>,
windows: Extract<Res<Windows>>,
windows: Extract<Query<(Entity, &Window, &RawHandleWrapper, Option<&PrimaryWindow>)>>,
) {
for window in windows.iter() {
let (new_width, new_height) = (
window.physical_width().max(1),
window.physical_height().max(1),
);
let new_present_mode = window.present_mode();
for (entity, window, handle, primary) in windows.iter() {
if primary.is_some() {
extracted_windows.primary = Some(entity);
}
let mut extracted_window =
extracted_windows
.entry(window.id())
.or_insert(ExtractedWindow {
id: window.id(),
raw_handle: window.raw_handle(),
physical_width: new_width,
physical_height: new_height,
present_mode: window.present_mode(),
swap_chain_texture: None,
swap_chain_texture_format: None,
size_changed: false,
present_mode_changed: false,
alpha_mode: window.alpha_mode(),
});
let (new_width, new_height) = (
window.resolution.physical_width().max(1),
window.resolution.physical_height().max(1),
);
let mut extracted_window = extracted_windows.entry(entity).or_insert(ExtractedWindow {
entity,
handle: handle.clone(),
physical_width: new_width,
physical_height: new_height,
present_mode: window.present_mode,
swap_chain_texture: None,
size_changed: false,
swap_chain_texture_format: None,
present_mode_changed: false,
alpha_mode: window.composite_alpha_mode,
});
// NOTE: Drop the swap chain frame here
extracted_window.swap_chain_texture = None;
extracted_window.size_changed = new_width != extracted_window.physical_width
|| new_height != extracted_window.physical_height;
extracted_window.present_mode_changed = new_present_mode != extracted_window.present_mode;
extracted_window.present_mode_changed =
window.present_mode != extracted_window.present_mode;
if extracted_window.size_changed {
debug!(
@ -120,13 +123,14 @@ fn extract_windows(
if extracted_window.present_mode_changed {
debug!(
"Window Present Mode changed from {:?} to {:?}",
extracted_window.present_mode, new_present_mode
extracted_window.present_mode, window.present_mode
);
extracted_window.present_mode = new_present_mode;
extracted_window.present_mode = window.present_mode;
}
}
for closed_window in closed.iter() {
extracted_windows.remove(&closed_window.id);
extracted_windows.remove(&closed_window.window);
}
}
@ -137,9 +141,9 @@ struct SurfaceData {
#[derive(Resource, Default)]
pub struct WindowSurfaces {
surfaces: HashMap<WindowId, SurfaceData>,
surfaces: HashMap<Entity, SurfaceData>,
/// List of windows that we have already called the initial `configure_surface` for
configured_windows: HashSet<WindowId>,
configured_windows: HashSet<Entity>,
}
/// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering.
@ -173,20 +177,14 @@ pub fn prepare_windows(
render_instance: Res<RenderInstance>,
render_adapter: Res<RenderAdapter>,
) {
for window in windows
.windows
.values_mut()
// value of raw_handle is only None in synthetic tests
.filter(|x| x.raw_handle.is_some())
{
for window in windows.windows.values_mut() {
let window_surfaces = window_surfaces.deref_mut();
let surface_data = window_surfaces
.surfaces
.entry(window.id)
.entry(window.entity)
.or_insert_with(|| unsafe {
// NOTE: On some OSes this MUST be called from the main thread.
let surface = render_instance
.create_surface(&window.raw_handle.as_ref().unwrap().get_handle());
let surface = render_instance.create_surface(&window.handle.get_handle());
let format = *surface
.get_supported_formats(&render_adapter)
.get(0)
@ -236,7 +234,7 @@ pub fn prepare_windows(
})
};
let not_already_configured = window_surfaces.configured_windows.insert(window.id);
let not_already_configured = window_surfaces.configured_windows.insert(window.entity);
let surface = &surface_data.surface;
if not_already_configured || window.size_changed || window.present_mode_changed {

View file

@ -5,6 +5,7 @@ use bevy_ecs::{
component::Component,
entity::Entity,
event::EventReader,
prelude::With,
reflect::ReflectComponent,
system::{Commands, Local, Query, Res, ResMut},
};
@ -19,7 +20,7 @@ use bevy_render::{
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas};
use bevy_transform::prelude::{GlobalTransform, Transform};
use bevy_utils::HashSet;
use bevy_window::{WindowId, WindowScaleFactorChanged, Windows};
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
use crate::{
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline,
@ -69,7 +70,7 @@ pub struct Text2dBundle {
pub fn extract_text2d_sprite(
mut extracted_sprites: ResMut<ExtractedSprites>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
windows: Extract<Res<Windows>>,
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
text2d_query: Extract<
Query<(
Entity,
@ -81,7 +82,11 @@ pub fn extract_text2d_sprite(
)>,
>,
) {
let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
let scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor() as f32)
.unwrap_or(1.0);
for (entity, computed_visibility, text, text_layout_info, anchor, text_transform) in
text2d_query.iter()
@ -146,9 +151,9 @@ pub fn update_text2d_layout(
mut queue: Local<HashSet<Entity>>,
mut textures: ResMut<Assets<Image>>,
fonts: Res<Assets<Font>>,
windows: Res<Windows>,
text_settings: Res<TextSettings>,
mut font_atlas_warning: ResMut<FontAtlasWarning>,
windows: Query<&Window, With<PrimaryWindow>>,
mut scale_factor_changed: EventReader<WindowScaleFactorChanged>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
@ -162,7 +167,12 @@ pub fn update_text2d_layout(
) {
// We need to consume the entire iterator, hence `last`
let factor_changed = scale_factor_changed.iter().last().is_some();
let scale_factor = windows.scale_factor(WindowId::primary());
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
let scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor())
.unwrap_or(1.0);
for (entity, text, bounds, text_layout_info) in &mut text_query {
if factor_changed || text.is_changed() || queue.remove(&entity) {

View file

@ -13,7 +13,7 @@ use bevy_log::warn;
use bevy_math::Vec2;
use bevy_transform::components::Transform;
use bevy_utils::HashMap;
use bevy_window::{Window, WindowId, WindowScaleFactorChanged, Windows};
use bevy_window::{PrimaryWindow, Window, WindowResolution, WindowScaleFactorChanged};
use std::fmt;
use taffy::{
prelude::{AvailableSpace, Size},
@ -23,7 +23,7 @@ use taffy::{
#[derive(Resource)]
pub struct FlexSurface {
entity_to_taffy: HashMap<Entity, taffy::node::Node>,
window_nodes: HashMap<WindowId, taffy::node::Node>,
window_nodes: HashMap<Entity, taffy::node::Node>,
taffy: Taffy,
}
@ -36,7 +36,6 @@ unsafe impl Sync for FlexSurface {}
fn _assert_send_sync_flex_surface_impl_safe() {
fn _assert_send_sync<T: Send + Sync>() {}
_assert_send_sync::<HashMap<Entity, taffy::node::Node>>();
_assert_send_sync::<HashMap<WindowId, taffy::node::Node>>();
// FIXME https://github.com/DioxusLabs/taffy/issues/146
// _assert_send_sync::<Taffy>();
}
@ -145,11 +144,11 @@ without UI components as a child of an entity with UI components, results may be
}
}
pub fn update_window(&mut self, window: &Window) {
pub fn update_window(&mut self, window: Entity, window_resolution: &WindowResolution) {
let taffy = &mut self.taffy;
let node = self
.window_nodes
.entry(window.id())
.entry(window)
.or_insert_with(|| taffy.new_leaf(taffy::style::Style::default()).unwrap());
taffy
@ -157,8 +156,12 @@ without UI components as a child of an entity with UI components, results may be
*node,
taffy::style::Style {
size: taffy::geometry::Size {
width: taffy::style::Dimension::Points(window.physical_width() as f32),
height: taffy::style::Dimension::Points(window.physical_height() as f32),
width: taffy::style::Dimension::Points(
window_resolution.physical_width() as f32
),
height: taffy::style::Dimension::Points(
window_resolution.physical_height() as f32,
),
},
..Default::default()
},
@ -168,10 +171,10 @@ without UI components as a child of an entity with UI components, results may be
pub fn set_window_children(
&mut self,
window_id: WindowId,
parent_window: Entity,
children: impl Iterator<Item = Entity>,
) {
let taffy_node = self.window_nodes.get(&window_id).unwrap();
let taffy_node = self.window_nodes.get(&parent_window).unwrap();
let child_nodes = children
.map(|e| *self.entity_to_taffy.get(&e).unwrap())
.collect::<Vec<taffy::node::Node>>();
@ -218,7 +221,8 @@ pub enum FlexError {
#[allow(clippy::too_many_arguments)]
pub fn flex_node_system(
windows: Res<Windows>,
primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
windows: Query<(Entity, &Window)>,
ui_scale: Res<UiScale>,
mut scale_factor_events: EventReader<WindowScaleFactorChanged>,
mut flex_surface: ResMut<FlexSurface>,
@ -234,13 +238,20 @@ pub fn flex_node_system(
mut node_transform_query: Query<(Entity, &mut Node, &mut Transform, Option<&Parent>)>,
removed_nodes: RemovedComponents<Node>,
) {
// assume one window for time being...
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
let (primary_window_entity, logical_to_physical_factor) =
if let Ok((entity, primary_window)) = primary_window.get_single() {
(entity, primary_window.resolution.scale_factor())
} else {
return;
};
// update window root nodes
for window in windows.iter() {
flex_surface.update_window(window);
for (entity, window) in windows.iter() {
flex_surface.update_window(entity, &window.resolution);
}
// assume one window for time being...
let logical_to_physical_factor = windows.scale_factor(WindowId::primary());
let scale_factor = logical_to_physical_factor * ui_scale.scale;
fn update_changed<F: ReadOnlyWorldQuery>(
@ -273,9 +284,7 @@ pub fn flex_node_system(
flex_surface.remove_entities(&removed_nodes);
// update window children (for now assuming all Nodes live in the primary window)
if let Some(primary_window) = windows.get_primary() {
flex_surface.set_window_children(primary_window.id(), root_node_query.iter());
}
flex_surface.set_window_children(primary_window_entity, root_node_query.iter());
// update and remove children
for entity in &removed_children {

View file

@ -3,7 +3,7 @@ use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
change_detection::DetectChangesMut,
entity::Entity,
prelude::Component,
prelude::{Component, With},
query::WorldQuery,
reflect::ReflectComponent,
system::{Local, Query, Res},
@ -11,10 +11,10 @@ use bevy_ecs::{
use bevy_input::{mouse::MouseButton, touch::Touches, Input};
use bevy_math::Vec2;
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::camera::{Camera, RenderTarget};
use bevy_render::view::ComputedVisibility;
use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::ComputedVisibility};
use bevy_transform::components::GlobalTransform;
use bevy_window::Windows;
use bevy_window::{PrimaryWindow, Window};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
@ -129,15 +129,19 @@ pub struct NodeQuery {
/// The system that sets Interaction for all UI elements based on the mouse cursor activity
///
/// Entities with a hidden [`ComputedVisibility`] are always treated as released.
#[allow(clippy::too_many_arguments)]
pub fn ui_focus_system(
mut state: Local<State>,
camera: Query<(&Camera, Option<&UiCameraConfig>)>,
windows: Res<Windows>,
windows: Query<&Window>,
mouse_button_input: Res<Input<MouseButton>>,
touches_input: Res<Touches>,
ui_stack: Res<UiStack>,
mut node_query: Query<NodeQuery>,
primary_window: Query<Entity, With<PrimaryWindow>>,
) {
let primary_window = primary_window.iter().next();
// reset entities that were both clicked and released in the last frame
for entity in state.entities_to_reset.drain(..) {
if let Ok(mut interaction) = node_query.get_component_mut::<Interaction>(entity) {
@ -167,18 +171,20 @@ pub fn ui_focus_system(
.iter()
.filter(|(_, camera_ui)| !is_ui_disabled(*camera_ui))
.filter_map(|(camera, _)| {
if let RenderTarget::Window(window_id) = camera.target {
if let Some(NormalizedRenderTarget::Window(window_id)) =
camera.target.normalize(primary_window)
{
Some(window_id)
} else {
None
}
})
.filter_map(|window_id| windows.get(window_id))
.filter(|window| window.is_focused())
.find_map(|window| {
window.cursor_position().map(|mut cursor_pos| {
cursor_pos.y = window.height() - cursor_pos.y;
cursor_pos
.find_map(|window_ref| {
windows.get(window_ref.entity()).ok().and_then(|window| {
window.cursor.position.map(|mut cursor_pos| {
cursor_pos.y = window.height() as f64 - cursor_pos.y;
cursor_pos.as_vec2()
})
})
})
.or_else(|| touches_input.first_pressed_position());

View file

@ -2,6 +2,7 @@ mod pipeline;
mod render_pass;
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
use bevy_window::{PrimaryWindow, Window};
pub use pipeline::*;
pub use render_pass::*;
@ -29,7 +30,6 @@ use bevy_text::{Text, TextLayoutInfo};
use bevy_transform::components::GlobalTransform;
use bevy_utils::FloatOrd;
use bevy_utils::HashMap;
use bevy_window::{WindowId, Windows};
use bytemuck::{Pod, Zeroable};
use std::ops::Range;
@ -297,7 +297,7 @@ pub fn extract_default_ui_camera_view<T: Component>(
pub fn extract_text_uinodes(
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
windows: Extract<Res<Windows>>,
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
ui_stack: Extract<Res<UiStack>>,
uinode_query: Extract<
Query<(
@ -310,7 +310,12 @@ pub fn extract_text_uinodes(
)>,
>,
) {
let scale_factor = windows.scale_factor(WindowId::primary()) as f32;
// TODO: Support window-independent UI scale: https://github.com/bevyengine/bevy/issues/5621
let scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor() as f32)
.unwrap_or(1.0);
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
if let Ok((uinode, global_transform, text, text_layout_info, visibility, clip)) =
uinode_query.get(*entity)

View file

@ -12,7 +12,7 @@ use bevy_text::{
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline,
TextSettings, YAxisOrientation,
};
use bevy_window::Windows;
use bevy_window::{PrimaryWindow, Window};
#[derive(Debug, Default)]
pub struct QueuedText {
@ -51,7 +51,7 @@ pub fn text_system(
mut last_scale_factor: Local<f64>,
mut textures: ResMut<Assets<Image>>,
fonts: Res<Assets<Font>>,
windows: Res<Windows>,
windows: Query<&Window, With<PrimaryWindow>>,
text_settings: Res<TextSettings>,
mut font_atlas_warning: ResMut<FontAtlasWarning>,
ui_scale: Res<UiScale>,
@ -69,13 +69,11 @@ pub fn text_system(
)>,
)>,
) {
// TODO: This should support window-independent scale settings.
// See https://github.com/bevyengine/bevy/issues/5621
let scale_factor = if let Some(window) = windows.get_primary() {
window.scale_factor() * ui_scale.scale
} else {
ui_scale.scale
};
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
let scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor())
.unwrap_or(ui_scale.scale);
let inv_scale_factor = 1. / scale_factor;

View file

@ -1,12 +1,23 @@
use bevy_reflect::{prelude::ReflectDefault, FromReflect, Reflect};
#[cfg(feature = "serialize")]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// The icon to display for a window's cursor.
///
/// Examples of all of these cursors can be found [here](https://www.w3schools.com/cssref/playit.asp?filename=playcss_cursor).
/// This `enum` is simply a copy of a similar `enum` found in [`winit`](https://docs.rs/winit/latest/winit/window/enum.CursorIcon.html).
/// `winit`, in turn, mostly copied cursor types available in the browser.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy, Reflect, FromReflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq, Default)]
pub enum CursorIcon {
/// The platform-dependent default cursor.
#[default]
Default,
/// A simple crosshair.
Crosshair,

View file

@ -1,6 +1,6 @@
use std::path::PathBuf;
use super::{WindowDescriptor, WindowId};
use bevy_ecs::entity::Entity;
use bevy_math::{IVec2, Vec2};
use bevy_reflect::{FromReflect, Reflect};
@ -16,26 +16,15 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
reflect(Serialize, Deserialize)
)]
pub struct WindowResized {
pub id: WindowId,
/// Window that has changed.
pub window: Entity,
/// The new logical width of the window.
pub width: f32,
/// The new logical height of the window.
pub height: f32,
}
/// An event that indicates that a new window should be created.
#[derive(Debug, Clone, PartialEq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct CreateWindow {
pub id: WindowId,
pub descriptor: WindowDescriptor,
}
// TODO: This would redraw all windows ? If yes, update docs to reflect this
/// An event that indicates the window should redraw, even if its control flow is set to `Wait` and
/// there have been no window events.
#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)]
@ -49,8 +38,7 @@ pub struct RequestRedraw;
/// An event that is sent whenever a new window is created.
///
/// To create a new window, send a [`CreateWindow`] event - this
/// event will be sent in the handler for that event.
/// To create a new window, spawn an entity with a [`crate::Window`] on it.
#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
@ -59,20 +47,20 @@ pub struct RequestRedraw;
reflect(Serialize, Deserialize)
)]
pub struct WindowCreated {
pub id: WindowId,
/// Window that has been created.
pub window: Entity,
}
/// An event that is sent whenever the operating systems requests that a window
/// be closed. This will be sent when the close button of the window is pressed.
///
/// If the default [`WindowPlugin`] is used, these events are handled
/// by [closing] the corresponding [`Window`].
/// by closing the corresponding [`Window`].
/// To disable this behaviour, set `close_when_requested` on the [`WindowPlugin`]
/// to `false`.
///
/// [`WindowPlugin`]: crate::WindowPlugin
/// [`Window`]: crate::Window
/// [closing]: crate::Window::close
#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
@ -81,13 +69,12 @@ pub struct WindowCreated {
reflect(Serialize, Deserialize)
)]
pub struct WindowCloseRequested {
pub id: WindowId,
/// Window to close.
pub window: Entity,
}
/// An event that is sent whenever a window is closed. This will be sent by the
/// handler for [`Window::close`].
///
/// [`Window::close`]: crate::Window::close
/// An event that is sent whenever a window is closed. This will be sent when
/// the window entity loses its `Window` component or is despawned.
#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
@ -96,10 +83,13 @@ pub struct WindowCloseRequested {
reflect(Serialize, Deserialize)
)]
pub struct WindowClosed {
pub id: WindowId,
/// Window that has been closed.
///
/// Note that this entity probably no longer exists
/// by the time this event is received.
pub window: Entity,
}
/// An event reporting that the mouse cursor has moved on a window.
/// An event reporting that the mouse cursor has moved inside a window.
///
/// The event is sent only if the cursor is over one of the application's windows.
/// It is the translated version of [`WindowEvent::CursorMoved`] from the `winit` crate.
@ -116,10 +106,9 @@ pub struct WindowClosed {
reflect(Serialize, Deserialize)
)]
pub struct CursorMoved {
/// The identifier of the window the cursor has moved on.
pub id: WindowId,
/// The position of the cursor, in window coordinates.
/// Window that the cursor moved inside.
pub window: Entity,
/// The cursor position in logical pixels.
pub position: Vec2,
}
@ -132,7 +121,8 @@ pub struct CursorMoved {
reflect(Serialize, Deserialize)
)]
pub struct CursorEntered {
pub id: WindowId,
/// Window that the cursor entered.
pub window: Entity,
}
/// An event that is sent whenever the user's cursor leaves a window.
@ -144,7 +134,8 @@ pub struct CursorEntered {
reflect(Serialize, Deserialize)
)]
pub struct CursorLeft {
pub id: WindowId,
/// Window that the cursor left.
pub window: Entity,
}
/// An event that is sent whenever a window receives a character from the OS or underlying system.
@ -156,7 +147,9 @@ pub struct CursorLeft {
reflect(Serialize, Deserialize)
)]
pub struct ReceivedCharacter {
pub id: WindowId,
/// Window that received the character.
pub window: Entity,
/// Received character.
pub char: char,
}
@ -169,7 +162,9 @@ pub struct ReceivedCharacter {
reflect(Serialize, Deserialize)
)]
pub struct WindowFocused {
pub id: WindowId,
/// Window that changed focus.
pub window: Entity,
/// Whether it was focused (true) or lost focused (false).
pub focused: bool,
}
@ -182,7 +177,9 @@ pub struct WindowFocused {
reflect(Serialize, Deserialize)
)]
pub struct WindowScaleFactorChanged {
pub id: WindowId,
/// Window that had it's scale factor changed.
pub window: Entity,
/// The new scale factor.
pub scale_factor: f64,
}
@ -195,7 +192,9 @@ pub struct WindowScaleFactorChanged {
reflect(Serialize, Deserialize)
)]
pub struct WindowBackendScaleFactorChanged {
pub id: WindowId,
/// Window that had it's scale factor changed by the backend.
pub window: Entity,
/// The new scale factor.
pub scale_factor: f64,
}
@ -208,11 +207,27 @@ pub struct WindowBackendScaleFactorChanged {
reflect(Serialize, Deserialize)
)]
pub enum FileDragAndDrop {
DroppedFile { id: WindowId, path_buf: PathBuf },
/// File is being dropped into a window.
DroppedFile {
/// Window the file was dropped into.
window: Entity,
/// Path to the file that was dropped in.
path_buf: PathBuf,
},
HoveredFile { id: WindowId, path_buf: PathBuf },
/// File is currently being hovered over a window.
HoveredFile {
/// Window a file is possibly going to be dropped into.
window: Entity,
/// Path to the file that might be dropped in.
path_buf: PathBuf,
},
HoveredFileCancelled { id: WindowId },
/// File hovering was cancelled.
HoveredFileCancelled {
/// Window that had a cancelled file drop.
window: Entity,
},
}
/// An event that is sent when a window is repositioned in physical pixels.
@ -224,6 +239,8 @@ pub enum FileDragAndDrop {
reflect(Serialize, Deserialize)
)]
pub struct WindowMoved {
pub id: WindowId,
/// Window that moved.
pub entity: Entity,
/// Where the window moved to in physical pixels.
pub position: IVec2,
}

View file

@ -4,34 +4,33 @@ mod event;
mod raw_handle;
mod system;
mod window;
mod windows;
pub use crate::raw_handle::*;
pub use cursor::*;
pub use event::*;
pub use system::*;
pub use window::*;
pub use windows::*;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
CursorEntered, CursorIcon, CursorLeft, CursorMoved, FileDragAndDrop, MonitorSelection,
ReceivedCharacter, Window, WindowDescriptor, WindowMode, WindowMoved, WindowPlugin,
WindowPosition, Windows,
ReceivedCharacter, Window, WindowMoved, WindowPlugin, WindowPosition,
WindowResizeConstraints,
};
}
use bevy_app::prelude::*;
use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel};
use std::path::PathBuf;
use bevy_app::prelude::*;
use bevy_ecs::schedule::SystemLabel;
impl Default for WindowPlugin {
fn default() -> Self {
WindowPlugin {
window: Default::default(),
add_primary_window: true,
exit_on_all_closed: true,
primary_window: Some(Window::default()),
exit_condition: ExitCondition::OnAllClosed,
close_when_requested: true,
}
}
@ -39,21 +38,26 @@ impl Default for WindowPlugin {
/// A [`Plugin`] that defines an interface for windowing support in Bevy.
pub struct WindowPlugin {
pub window: WindowDescriptor,
/// Whether to create a window when added.
/// Settings for the primary window. This will be spawned by
/// default, if you want to run without a primary window you should
/// set this to `None`.
///
/// Note that if there are no windows, by default the App will exit,
/// due to [`exit_on_all_closed`].
pub add_primary_window: bool,
pub primary_window: Option<Window>,
/// Whether to exit the app when there are no open windows.
///
/// If disabling this, ensure that you send the [`bevy_app::AppExit`]
/// event when the app should exit. If this does not occur, you will
/// create 'headless' processes (processes without windows), which may
/// surprise your users. It is recommended to leave this setting as `true`.
/// surprise your users. It is recommended to leave this setting to
/// either [`ExitCondition::OnAllClosed`] or [`ExitCondition::OnPrimaryClosed`].
///
/// If true, this plugin will add [`exit_on_all_closed`] to [`CoreStage::PostUpdate`].
pub exit_on_all_closed: bool,
/// [`ExitCondition::OnAllClosed`] will add [`exit_on_all_closed`] to [`CoreStage::Update`].
/// [`ExitCondition::OnPrimaryClosed`] will add [`exit_on_primary_closed`] to [`CoreStage::Update`].
pub exit_condition: ExitCondition,
/// Whether to close windows when they are requested to be closed (i.e.
/// when the close button is pressed).
///
@ -65,8 +69,8 @@ pub struct WindowPlugin {
impl Plugin for WindowPlugin {
fn build(&self, app: &mut App) {
// User convenience events
app.add_event::<WindowResized>()
.add_event::<CreateWindow>()
.add_event::<WindowCreated>()
.add_event::<WindowClosed>()
.add_event::<WindowCloseRequested>()
@ -79,29 +83,30 @@ impl Plugin for WindowPlugin {
.add_event::<WindowScaleFactorChanged>()
.add_event::<WindowBackendScaleFactorChanged>()
.add_event::<FileDragAndDrop>()
.add_event::<WindowMoved>()
.init_resource::<Windows>();
.add_event::<WindowMoved>();
if self.add_primary_window {
app.world.send_event(CreateWindow {
id: WindowId::primary(),
descriptor: self.window.clone(),
});
if let Some(primary_window) = &self.primary_window {
app.world
.spawn(primary_window.clone())
.insert(PrimaryWindow);
}
if self.exit_on_all_closed {
app.add_system_to_stage(
CoreStage::PostUpdate,
exit_on_all_closed.after(ModifiesWindows),
);
match self.exit_condition {
ExitCondition::OnPrimaryClosed => {
app.add_system(exit_on_primary_closed);
}
ExitCondition::OnAllClosed => {
app.add_system(exit_on_all_closed);
}
ExitCondition::DontExit => {}
}
if self.close_when_requested {
app.add_system(close_when_requested);
app.add_system_to_stage(CoreStage::First, close_when_requested);
}
// Register event types
app.register_type::<WindowResized>()
.register_type::<CreateWindow>()
.register_type::<RequestRedraw>()
.register_type::<WindowCreated>()
.register_type::<WindowCloseRequested>()
@ -117,18 +122,41 @@ impl Plugin for WindowPlugin {
.register_type::<WindowMoved>();
// Register window descriptor and related types
app.register_type::<WindowId>()
.register_type::<PresentMode>()
.register_type::<WindowResizeConstraints>()
.register_type::<WindowMode>()
app.register_type::<Window>()
.register_type::<Cursor>()
.register_type::<WindowResolution>()
.register_type::<WindowPosition>()
.register_type::<WindowMode>()
.register_type::<PresentMode>()
.register_type::<InternalWindowState>()
.register_type::<MonitorSelection>()
.register_type::<WindowDescriptor>();
.register_type::<WindowResizeConstraints>();
// Register `PathBuf` as it's used by `FileDragAndDrop`
app.register_type::<PathBuf>();
}
}
/// System Label marking when changes are applied to windows
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub struct ModifiesWindows;
/// Defines the specific conditions the application should exit on
#[derive(Clone)]
pub enum ExitCondition {
/// Close application when the primary window is closed
///
/// The plugin will add [`exit_on_primary_closed`] to [`CoreStage::Update`].
OnPrimaryClosed,
/// Close application when all windows are closed
///
/// The plugin will add [`exit_on_all_closed`] to [`CoreStage::Update`].
OnAllClosed,
/// Keep application running headless even after closing all windows
///
/// If selecting this, ensure that you send the [`bevy_app::AppExit`]
/// event when the app should exit. If this does not occur, you will
/// create 'headless' processes (processes without windows), which may
/// surprise your users.
DontExit,
}

View file

@ -1,3 +1,4 @@
use bevy_ecs::prelude::Component;
use raw_window_handle::{
HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
};
@ -7,7 +8,7 @@ use raw_window_handle::{
/// Depending on the platform, the underlying pointer-containing handle cannot be used on all threads,
/// and so we cannot simply make it (or any type that has a safe operation to get a [`RawWindowHandle`] or [`RawDisplayHandle`])
/// thread-safe.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Component)]
pub struct RawHandleWrapper {
pub window_handle: RawWindowHandle,
pub display_handle: RawDisplayHandle,

View file

@ -1,4 +1,4 @@
use crate::{Window, WindowCloseRequested, Windows};
use crate::{PrimaryWindow, Window, WindowCloseRequested};
use bevy_app::AppExit;
use bevy_ecs::prelude::*;
@ -11,8 +11,24 @@ use bevy_input::{keyboard::KeyCode, Input};
/// Ensure that you read the caveats documented on that field if doing so.
///
/// [`WindowPlugin`]: crate::WindowPlugin
pub fn exit_on_all_closed(mut app_exit_events: EventWriter<AppExit>, windows: Res<Windows>) {
if windows.iter().count() == 0 {
pub fn exit_on_all_closed(mut app_exit_events: EventWriter<AppExit>, windows: Query<&Window>) {
if windows.is_empty() {
bevy_utils::tracing::info!("No windows are open, exiting");
app_exit_events.send(AppExit);
}
}
/// Exit the application when the primary window has been closed
///
/// This system is added by the [`WindowPlugin`]
///
/// [`WindowPlugin`]: crate::WindowPlugin
pub fn exit_on_primary_closed(
mut app_exit_events: EventWriter<AppExit>,
windows: Query<(), (With<Window>, With<PrimaryWindow>)>,
) {
if windows.is_empty() {
bevy_utils::tracing::info!("Primary windows was closed, exiting");
app_exit_events.send(AppExit);
}
}
@ -24,22 +40,27 @@ pub fn exit_on_all_closed(mut app_exit_events: EventWriter<AppExit>, windows: Re
/// Ensure that you read the caveats documented on that field if doing so.
///
/// [`WindowPlugin`]: crate::WindowPlugin
pub fn close_when_requested(
mut windows: ResMut<Windows>,
mut closed: EventReader<WindowCloseRequested>,
) {
pub fn close_when_requested(mut commands: Commands, mut closed: EventReader<WindowCloseRequested>) {
for event in closed.iter() {
windows.get_mut(event.id).map(Window::close);
commands.entity(event.window).despawn();
}
}
/// Close the focused window whenever the escape key (<kbd>Esc</kbd>) is pressed
///
/// This is useful for examples or prototyping.
pub fn close_on_esc(mut windows: ResMut<Windows>, input: Res<Input<KeyCode>>) {
if input.just_pressed(KeyCode::Escape) {
if let Some(window) = windows.get_focused_mut() {
window.close();
pub fn close_on_esc(
mut commands: Commands,
focused_windows: Query<(Entity, &Window)>,
input: Res<Input<KeyCode>>,
) {
for (window, focus) in focused_windows.iter() {
if !focus.focused {
continue;
}
if input.just_pressed(KeyCode::Escape) {
commands.entity(window).despawn();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -33,7 +33,7 @@ pub fn convert_mouse_button(mouse_button: winit::event::MouseButton) -> MouseBut
pub fn convert_touch_input(
touch_input: winit::event::Touch,
location: winit::dpi::LogicalPosition<f32>,
location: winit::dpi::LogicalPosition<f64>,
) -> TouchInput {
TouchInput {
phase: match touch_input.phase {
@ -42,7 +42,7 @@ pub fn convert_touch_input(
winit::event::TouchPhase::Ended => TouchPhase::Ended,
winit::event::TouchPhase::Cancelled => TouchPhase::Cancelled,
},
position: Vec2::new(location.x, location.y),
position: Vec2::new(location.x as f32, location.y as f32),
force: touch_input.force.map(|f| match f {
winit::event::Force::Calibrated {
force,

View file

@ -1,266 +1,112 @@
mod converters;
mod system;
#[cfg(target_arch = "wasm32")]
mod web_resize;
mod winit_config;
mod winit_windows;
use winit::window::CursorGrabMode;
use bevy_ecs::system::{SystemParam, SystemState};
use system::{changed_window, create_window, despawn_window};
pub use winit_config::*;
pub use winit_windows::*;
use bevy_app::{App, AppExit, CoreStage, Plugin};
use bevy_ecs::event::{Events, ManualEventReader};
use bevy_ecs::prelude::*;
use bevy_ecs::{
event::{Events, ManualEventReader},
world::World,
use bevy_input::{
keyboard::KeyboardInput,
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
touch::TouchInput,
};
use bevy_input::mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel};
use bevy_math::{ivec2, DVec2, UVec2, Vec2};
use bevy_math::{ivec2, DVec2, Vec2};
use bevy_utils::{
tracing::{error, info, trace, warn},
tracing::{trace, warn},
Instant,
};
use bevy_window::{
CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ModifiesWindows,
ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested,
WindowClosed, WindowCreated, WindowFocused, WindowMoved, WindowResized,
WindowScaleFactorChanged, Windows,
CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ModifiesWindows, ReceivedCharacter,
RequestRedraw, Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated,
WindowFocused, WindowMoved, WindowResized, WindowScaleFactorChanged,
};
use winit::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition},
event::{self, DeviceEvent, Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
};
use crate::system::WinitWindowInfo;
#[cfg(target_arch = "wasm32")]
use crate::web_resize::{CanvasParentResizeEventChannel, CanvasParentResizePlugin};
#[derive(Default)]
pub struct WinitPlugin;
impl Plugin for WinitPlugin {
fn build(&self, app: &mut App) {
let event_loop = EventLoop::new();
app.insert_non_send_resource(event_loop);
app.init_non_send_resource::<WinitWindows>()
.init_resource::<WinitSettings>()
.set_runner(winit_runner)
.add_system_to_stage(CoreStage::PostUpdate, change_window.label(ModifiesWindows));
.add_system_set_to_stage(
CoreStage::PostUpdate,
SystemSet::new()
.label(ModifiesWindows)
.with_system(changed_window)
.with_system(despawn_window),
);
#[cfg(target_arch = "wasm32")]
app.add_plugin(web_resize::CanvasParentResizePlugin);
let event_loop = EventLoop::new();
#[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))]
let mut create_window_reader = WinitCreateWindowReader::default();
#[cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))]
let create_window_reader = WinitCreateWindowReader::default();
// Note that we create a window here "early" because WASM/WebGL requires the window to exist prior to initializing
// the renderer.
app.add_plugin(CanvasParentResizePlugin);
#[cfg(not(target_arch = "wasm32"))]
let mut create_window_system_state: SystemState<(
Commands,
NonSendMut<EventLoop<()>>,
Query<(Entity, &mut Window)>,
EventWriter<WindowCreated>,
NonSendMut<WinitWindows>,
)> = SystemState::from_world(&mut app.world);
#[cfg(target_arch = "wasm32")]
let mut create_window_system_state: SystemState<(
Commands,
NonSendMut<EventLoop<()>>,
Query<(Entity, &mut Window)>,
EventWriter<WindowCreated>,
NonSendMut<WinitWindows>,
ResMut<CanvasParentResizeEventChannel>,
)> = SystemState::from_world(&mut app.world);
// And for ios and macos, we should not create window early, all ui related code should be executed inside
// UIApplicationMain/NSApplicationMain.
#[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))]
handle_create_window_events(&mut app.world, &event_loop, &mut create_window_reader.0);
app.insert_resource(create_window_reader)
.insert_non_send_resource(event_loop);
}
}
{
#[cfg(not(target_arch = "wasm32"))]
let (commands, event_loop, mut new_windows, event_writer, winit_windows) =
create_window_system_state.get_mut(&mut app.world);
fn change_window(
mut winit_windows: NonSendMut<WinitWindows>,
mut windows: ResMut<Windows>,
mut window_dpi_changed_events: EventWriter<WindowScaleFactorChanged>,
mut window_close_events: EventWriter<WindowClosed>,
) {
let mut removed_windows = vec![];
for bevy_window in windows.iter_mut() {
let id = bevy_window.id();
for command in bevy_window.drain_commands() {
match command {
bevy_window::WindowCommand::SetWindowMode {
mode,
resolution:
UVec2 {
x: width,
y: height,
},
} => {
let window = winit_windows.get_window(id).unwrap();
match mode {
bevy_window::WindowMode::BorderlessFullscreen => {
window
.set_fullscreen(Some(winit::window::Fullscreen::Borderless(None)));
}
bevy_window::WindowMode::Fullscreen => {
window.set_fullscreen(Some(winit::window::Fullscreen::Exclusive(
get_best_videomode(&window.current_monitor().unwrap()),
)));
}
bevy_window::WindowMode::SizedFullscreen => window.set_fullscreen(Some(
winit::window::Fullscreen::Exclusive(get_fitting_videomode(
&window.current_monitor().unwrap(),
width,
height,
)),
)),
bevy_window::WindowMode::Windowed => window.set_fullscreen(None),
}
}
bevy_window::WindowCommand::SetTitle { title } => {
let window = winit_windows.get_window(id).unwrap();
window.set_title(&title);
}
bevy_window::WindowCommand::SetScaleFactor { scale_factor } => {
window_dpi_changed_events.send(WindowScaleFactorChanged { id, scale_factor });
}
bevy_window::WindowCommand::SetResolution {
logical_resolution:
Vec2 {
x: width,
y: height,
},
scale_factor,
} => {
let window = winit_windows.get_window(id).unwrap();
window.set_inner_size(
winit::dpi::LogicalSize::new(width, height)
.to_physical::<f64>(scale_factor),
);
}
bevy_window::WindowCommand::SetPresentMode { .. } => (),
bevy_window::WindowCommand::SetResizable { resizable } => {
let window = winit_windows.get_window(id).unwrap();
window.set_resizable(resizable);
}
bevy_window::WindowCommand::SetDecorations { decorations } => {
let window = winit_windows.get_window(id).unwrap();
window.set_decorations(decorations);
}
bevy_window::WindowCommand::SetCursorIcon { icon } => {
let window = winit_windows.get_window(id).unwrap();
window.set_cursor_icon(converters::convert_cursor_icon(icon));
}
bevy_window::WindowCommand::SetCursorGrabMode { grab_mode } => {
let window = winit_windows.get_window(id).unwrap();
match grab_mode {
bevy_window::CursorGrabMode::None => {
window.set_cursor_grab(CursorGrabMode::None)
}
bevy_window::CursorGrabMode::Confined => window
.set_cursor_grab(CursorGrabMode::Confined)
.or_else(|_e| window.set_cursor_grab(CursorGrabMode::Locked)),
bevy_window::CursorGrabMode::Locked => window
.set_cursor_grab(CursorGrabMode::Locked)
.or_else(|_e| window.set_cursor_grab(CursorGrabMode::Confined)),
}
.unwrap_or_else(|e| error!("Unable to un/grab cursor: {}", e));
}
bevy_window::WindowCommand::SetCursorVisibility { visible } => {
let window = winit_windows.get_window(id).unwrap();
window.set_cursor_visible(visible);
}
bevy_window::WindowCommand::SetCursorPosition { position } => {
let window = winit_windows.get_window(id).unwrap();
let inner_size = window.inner_size().to_logical::<f32>(window.scale_factor());
window
.set_cursor_position(LogicalPosition::new(
position.x,
inner_size.height - position.y,
))
.unwrap_or_else(|e| error!("Unable to set cursor position: {}", e));
}
bevy_window::WindowCommand::SetMaximized { maximized } => {
let window = winit_windows.get_window(id).unwrap();
window.set_maximized(maximized);
}
bevy_window::WindowCommand::SetMinimized { minimized } => {
let window = winit_windows.get_window(id).unwrap();
window.set_minimized(minimized);
}
bevy_window::WindowCommand::SetPosition {
monitor_selection,
position,
} => {
let window = winit_windows.get_window(id).unwrap();
#[cfg(target_arch = "wasm32")]
let (commands, event_loop, mut new_windows, event_writer, winit_windows, event_channel) =
create_window_system_state.get_mut(&mut app.world);
use bevy_window::MonitorSelection::*;
let maybe_monitor = match monitor_selection {
Current => window.current_monitor(),
Primary => window.primary_monitor(),
Index(i) => window.available_monitors().nth(i),
};
if let Some(monitor) = maybe_monitor {
let monitor_position = DVec2::from(<(_, _)>::from(monitor.position()));
let position = monitor_position + position.as_dvec2();
window.set_outer_position(LogicalPosition::new(position.x, position.y));
} else {
warn!("Couldn't get monitor selected with: {monitor_selection:?}");
}
}
bevy_window::WindowCommand::Center(monitor_selection) => {
let window = winit_windows.get_window(id).unwrap();
use bevy_window::MonitorSelection::*;
let maybe_monitor = match monitor_selection {
Current => window.current_monitor(),
Primary => window.primary_monitor(),
Index(i) => window.available_monitors().nth(i),
};
if let Some(monitor) = maybe_monitor {
let monitor_size = monitor.size();
let monitor_position = monitor.position().cast::<f64>();
let window_size = window.outer_size();
window.set_outer_position(PhysicalPosition {
x: monitor_size.width.saturating_sub(window_size.width) as f64 / 2.
+ monitor_position.x,
y: monitor_size.height.saturating_sub(window_size.height) as f64 / 2.
+ monitor_position.y,
});
} else {
warn!("Couldn't get monitor selected with: {monitor_selection:?}");
}
}
bevy_window::WindowCommand::SetResizeConstraints { resize_constraints } => {
let window = winit_windows.get_window(id).unwrap();
let constraints = resize_constraints.check_constraints();
let min_inner_size = LogicalSize {
width: constraints.min_width,
height: constraints.min_height,
};
let max_inner_size = LogicalSize {
width: constraints.max_width,
height: constraints.max_height,
};
window.set_min_inner_size(Some(min_inner_size));
if constraints.max_width.is_finite() && constraints.max_height.is_finite() {
window.set_max_inner_size(Some(max_inner_size));
}
}
bevy_window::WindowCommand::SetAlwaysOnTop { always_on_top } => {
let window = winit_windows.get_window(id).unwrap();
window.set_always_on_top(always_on_top);
}
bevy_window::WindowCommand::SetCursorHitTest { hittest } => {
let window = winit_windows.get_window(id).unwrap();
window.set_cursor_hittest(hittest).unwrap();
}
bevy_window::WindowCommand::Close => {
// Since we have borrowed `windows` to iterate through them, we can't remove the window from it.
// Add the removal requests to a queue to solve this
removed_windows.push(id);
// No need to run any further commands - this drops the rest of the commands, although the `bevy_window::Window` will be dropped later anyway
break;
}
}
}
}
if !removed_windows.is_empty() {
for id in removed_windows {
// Close the OS window. (The `Drop` impl actually closes the window)
let _ = winit_windows.remove_window(id);
// Clean up our own data structures
windows.remove(id);
window_close_events.send(WindowClosed { id });
// Here we need to create a winit-window and give it a WindowHandle which the renderer can use.
// It needs to be spawned before the start of the startup-stage, so we cannot use a regular system.
// Instead we need to create the window and spawn it using direct world access
create_window(
commands,
&event_loop,
new_windows.iter_mut(),
event_writer,
winit_windows,
#[cfg(target_arch = "wasm32")]
event_channel,
);
}
create_window_system_state.apply(&mut app.world);
}
}
@ -307,8 +153,30 @@ where
panic!("Run return is not supported on this platform!")
}
pub fn winit_runner(app: App) {
winit_runner_with(app);
#[derive(SystemParam)]
struct WindowEvents<'w> {
window_resized: EventWriter<'w, WindowResized>,
window_close_requested: EventWriter<'w, WindowCloseRequested>,
window_scale_factor_changed: EventWriter<'w, WindowScaleFactorChanged>,
window_backend_scale_factor_changed: EventWriter<'w, WindowBackendScaleFactorChanged>,
window_focused: EventWriter<'w, WindowFocused>,
window_moved: EventWriter<'w, WindowMoved>,
}
#[derive(SystemParam)]
struct InputEvents<'w> {
keyboard_input: EventWriter<'w, KeyboardInput>,
character_input: EventWriter<'w, ReceivedCharacter>,
mouse_button_input: EventWriter<'w, MouseButtonInput>,
mouse_wheel_input: EventWriter<'w, MouseWheel>,
touch_input: EventWriter<'w, TouchInput>,
}
#[derive(SystemParam)]
struct CursorEvents<'w> {
cursor_moved: EventWriter<'w, CursorMoved>,
cursor_entered: EventWriter<'w, CursorEntered>,
cursor_left: EventWriter<'w, CursorLeft>,
}
// #[cfg(any(
@ -347,19 +215,13 @@ impl Default for WinitPersistentState {
}
}
#[derive(Default, Resource)]
struct WinitCreateWindowReader(ManualEventReader<CreateWindow>);
pub fn winit_runner_with(mut app: App) {
pub fn winit_runner(mut app: App) {
// We remove this so that we have ownership over it.
let mut event_loop = app
.world
.remove_non_send_resource::<EventLoop<()>>()
.unwrap();
let mut create_window_event_reader = app
.world
.remove_resource::<WinitCreateWindowReader>()
.unwrap()
.0;
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
let mut winit_state = WinitPersistentState::default();
@ -370,23 +232,80 @@ pub fn winit_runner_with(mut app: App) {
trace!("Entering winit event loop");
let mut focused_window_state: SystemState<(Res<WinitSettings>, Query<&Window>)> =
SystemState::from_world(&mut app.world);
#[cfg(not(target_arch = "wasm32"))]
let mut create_window_system_state: SystemState<(
Commands,
Query<(Entity, &mut Window), Added<Window>>,
EventWriter<WindowCreated>,
NonSendMut<WinitWindows>,
)> = SystemState::from_world(&mut app.world);
#[cfg(target_arch = "wasm32")]
let mut create_window_system_state: SystemState<(
Commands,
Query<(Entity, &mut Window), Added<Window>>,
EventWriter<WindowCreated>,
NonSendMut<WinitWindows>,
ResMut<CanvasParentResizeEventChannel>,
)> = SystemState::from_world(&mut app.world);
let event_handler = move |event: Event<()>,
event_loop: &EventLoopWindowTarget<()>,
control_flow: &mut ControlFlow| {
#[cfg(feature = "trace")]
let _span = bevy_utils::tracing::info_span!("winit event_handler").entered();
if let Some(app_exit_events) = app.world.get_resource::<Events<AppExit>>() {
if app_exit_event_reader.iter(app_exit_events).last().is_some() {
*control_flow = ControlFlow::Exit;
return;
}
}
{
#[cfg(not(target_arch = "wasm32"))]
let (commands, mut new_windows, created_window_writer, winit_windows) =
create_window_system_state.get_mut(&mut app.world);
#[cfg(target_arch = "wasm32")]
let (
commands,
mut new_windows,
created_window_writer,
winit_windows,
canvas_parent_resize_channel,
) = create_window_system_state.get_mut(&mut app.world);
// Responsible for creating new windows
create_window(
commands,
event_loop,
new_windows.iter_mut(),
created_window_writer,
winit_windows,
#[cfg(target_arch = "wasm32")]
canvas_parent_resize_channel,
);
create_window_system_state.apply(&mut app.world);
}
match event {
event::Event::NewEvents(start) => {
let winit_config = app.world.resource::<WinitSettings>();
let windows = app.world.resource::<Windows>();
let focused = windows.iter().any(|w| w.is_focused());
let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
let app_focused = window_focused_query.iter().any(|window| window.focused);
// Check if either the `WaitUntil` timeout was triggered by winit, or that same
// amount of time has elapsed since the last app update. This manual check is needed
// because we don't know if the criteria for an app update were met until the end of
// the frame.
let auto_timeout_reached = matches!(start, StartCause::ResumeTimeReached { .. });
let now = Instant::now();
let manual_timeout_reached = match winit_config.update_mode(focused) {
let manual_timeout_reached = match winit_config.update_mode(app_focused) {
UpdateMode::Continuous => false,
UpdateMode::Reactive { max_wait }
| UpdateMode::ReactiveLowPower { max_wait } => {
@ -402,81 +321,120 @@ pub fn winit_runner_with(mut app: App) {
window_id: winit_window_id,
..
} => {
let world = app.world.cell();
let winit_windows = world.non_send_resource_mut::<WinitWindows>();
let mut windows = world.resource_mut::<Windows>();
let window_id =
if let Some(window_id) = winit_windows.get_window_id(winit_window_id) {
window_id
// Fetch and prepare details from the world
let mut system_state: SystemState<(
NonSend<WinitWindows>,
Query<(&mut Window, &mut WinitWindowInfo)>,
WindowEvents,
InputEvents,
CursorEvents,
EventWriter<FileDragAndDrop>,
)> = SystemState::new(&mut app.world);
let (
winit_windows,
mut window_query,
mut window_events,
mut input_events,
mut cursor_events,
mut file_drag_and_drop_events,
) = system_state.get_mut(&mut app.world);
// Entity of this window
let window_entity =
if let Some(entity) = winit_windows.get_window_entity(winit_window_id) {
entity
} else {
warn!(
"Skipped event for unknown winit Window Id {:?}",
winit_window_id
"Skipped event {:?} for unknown winit Window Id {:?}",
event, winit_window_id
);
return;
};
let (mut window, mut info) =
if let Ok((window, info)) = window_query.get_mut(window_entity) {
(window, info)
} else {
warn!(
"Window {:?} is missing `Window` component, skipping event {:?}",
window_entity, event
);
return;
};
let Some(window) = windows.get_mut(window_id) else {
// If we're here, this window was previously opened
info!("Skipped event for closed window: {:?}", window_id);
return;
};
winit_state.low_power_event = true;
match event {
WindowEvent::Resized(size) => {
window.update_actual_size_from_backend(size.width, size.height);
world.send_event(WindowResized {
id: window_id,
window
.resolution
.set_physical_resolution(size.width, size.height);
info.last_winit_size = size;
window_events.window_resized.send(WindowResized {
window: window_entity,
width: window.width(),
height: window.height(),
});
}
WindowEvent::CloseRequested => {
world.send_event(WindowCloseRequested { id: window_id });
window_events
.window_close_requested
.send(WindowCloseRequested {
window: window_entity,
});
}
WindowEvent::KeyboardInput { ref input, .. } => {
world.send_event(converters::convert_keyboard_input(input));
input_events
.keyboard_input
.send(converters::convert_keyboard_input(input));
}
WindowEvent::CursorMoved { position, .. } => {
let winit_window = winit_windows.get_window(window_id).unwrap();
let inner_size = winit_window.inner_size();
let physical_position = DVec2::new(
position.x,
// Flip the coordinate space from winit's context to our context.
window.resolution.physical_height() as f64 - position.y,
);
// move origin to bottom left
let y_position = inner_size.height as f64 - position.y;
window.cursor.position = Some(physical_position);
let physical_position = DVec2::new(position.x, y_position);
window
.update_cursor_physical_position_from_backend(Some(physical_position));
world.send_event(CursorMoved {
id: window_id,
position: (physical_position / window.scale_factor()).as_vec2(),
cursor_events.cursor_moved.send(CursorMoved {
window: window_entity,
position: (physical_position / window.resolution.scale_factor())
.as_vec2(),
});
}
WindowEvent::CursorEntered { .. } => {
world.send_event(CursorEntered { id: window_id });
cursor_events.cursor_entered.send(CursorEntered {
window: window_entity,
});
}
WindowEvent::CursorLeft { .. } => {
window.update_cursor_physical_position_from_backend(None);
world.send_event(CursorLeft { id: window_id });
// Component
if let Ok((mut window, _)) = window_query.get_mut(window_entity) {
window.cursor.position = None;
}
cursor_events.cursor_left.send(CursorLeft {
window: window_entity,
});
}
WindowEvent::MouseInput { state, button, .. } => {
world.send_event(MouseButtonInput {
input_events.mouse_button_input.send(MouseButtonInput {
button: converters::convert_mouse_button(button),
state: converters::convert_element_state(state),
});
}
WindowEvent::MouseWheel { delta, .. } => match delta {
event::MouseScrollDelta::LineDelta(x, y) => {
world.send_event(MouseWheel {
input_events.mouse_wheel_input.send(MouseWheel {
unit: MouseScrollUnit::Line,
x,
y,
});
}
event::MouseScrollDelta::PixelDelta(p) => {
world.send_event(MouseWheel {
input_events.mouse_wheel_input.send(MouseWheel {
unit: MouseScrollUnit::Pixel,
x: p.x as f32,
y: p.y as f32,
@ -484,12 +442,23 @@ pub fn winit_runner_with(mut app: App) {
}
},
WindowEvent::Touch(touch) => {
let location = touch.location.to_logical(window.scale_factor());
world.send_event(converters::convert_touch_input(touch, location));
let mut location =
touch.location.to_logical(window.resolution.scale_factor());
// On a mobile window, the start is from the top while on PC/Linux/OSX from
// bottom
if cfg!(target_os = "android") || cfg!(target_os = "ios") {
location.y = window.height() as f64 - location.y;
}
// Event
input_events
.touch_input
.send(converters::convert_touch_input(touch, location));
}
WindowEvent::ReceivedCharacter(c) => {
world.send_event(ReceivedCharacter {
id: window_id,
input_events.character_input.send(ReceivedCharacter {
window: window_entity,
char: c,
});
}
@ -497,73 +466,84 @@ pub fn winit_runner_with(mut app: App) {
scale_factor,
new_inner_size,
} => {
world.send_event(WindowBackendScaleFactorChanged {
id: window_id,
scale_factor,
});
let prior_factor = window.scale_factor();
window.update_scale_factor_from_backend(scale_factor);
let new_factor = window.scale_factor();
if let Some(forced_factor) = window.scale_factor_override() {
window_events.window_backend_scale_factor_changed.send(
WindowBackendScaleFactorChanged {
window: window_entity,
scale_factor,
},
);
let prior_factor = window.resolution.scale_factor();
window.resolution.set_scale_factor(scale_factor);
let new_factor = window.resolution.scale_factor();
if let Some(forced_factor) = window.resolution.scale_factor_override() {
// If there is a scale factor override, then force that to be used
// Otherwise, use the OS suggested size
// We have already told the OS about our resize constraints, so
// the new_inner_size should take those into account
*new_inner_size = winit::dpi::LogicalSize::new(
window.requested_width(),
window.requested_height(),
)
.to_physical::<u32>(forced_factor);
*new_inner_size =
winit::dpi::LogicalSize::new(window.width(), window.height())
.to_physical::<u32>(forced_factor);
// TODO: Should this not trigger a WindowsScaleFactorChanged?
} else if approx::relative_ne!(new_factor, prior_factor) {
world.send_event(WindowScaleFactorChanged {
id: window_id,
scale_factor,
});
// Trigger a change event if they are approximately different
window_events.window_scale_factor_changed.send(
WindowScaleFactorChanged {
window: window_entity,
scale_factor,
},
);
}
let new_logical_width = new_inner_size.width as f64 / new_factor;
let new_logical_height = new_inner_size.height as f64 / new_factor;
if approx::relative_ne!(window.width() as f64, new_logical_width)
|| approx::relative_ne!(window.height() as f64, new_logical_height)
let new_logical_width = (new_inner_size.width as f64 / new_factor) as f32;
let new_logical_height = (new_inner_size.height as f64 / new_factor) as f32;
if approx::relative_ne!(window.width(), new_logical_width)
|| approx::relative_ne!(window.height(), new_logical_height)
{
world.send_event(WindowResized {
id: window_id,
width: new_logical_width as f32,
height: new_logical_height as f32,
window_events.window_resized.send(WindowResized {
window: window_entity,
width: new_logical_width,
height: new_logical_height,
});
}
window.update_actual_size_from_backend(
new_inner_size.width,
new_inner_size.height,
);
window
.resolution
.set_physical_resolution(new_inner_size.width, new_inner_size.height);
}
WindowEvent::Focused(focused) => {
window.update_focused_status_from_backend(focused);
world.send_event(WindowFocused {
id: window_id,
// Component
window.focused = focused;
window_events.window_focused.send(WindowFocused {
window: window_entity,
focused,
});
}
WindowEvent::DroppedFile(path_buf) => {
world.send_event(FileDragAndDrop::DroppedFile {
id: window_id,
file_drag_and_drop_events.send(FileDragAndDrop::DroppedFile {
window: window_entity,
path_buf,
});
}
WindowEvent::HoveredFile(path_buf) => {
world.send_event(FileDragAndDrop::HoveredFile {
id: window_id,
file_drag_and_drop_events.send(FileDragAndDrop::HoveredFile {
window: window_entity,
path_buf,
});
}
WindowEvent::HoveredFileCancelled => {
world.send_event(FileDragAndDrop::HoveredFileCancelled { id: window_id });
file_drag_and_drop_events.send(FileDragAndDrop::HoveredFileCancelled {
window: window_entity,
});
}
WindowEvent::Moved(position) => {
let position = ivec2(position.x, position.y);
window.update_actual_position_from_backend(position);
world.send_event(WindowMoved {
id: window_id,
window.position.set(position);
window_events.window_moved.send(WindowMoved {
entity: window_entity,
position,
});
}
@ -574,8 +554,12 @@ pub fn winit_runner_with(mut app: App) {
event: DeviceEvent::MouseMotion { delta: (x, y) },
..
} => {
app.world.send_event(MouseMotion {
delta: DVec2 { x, y }.as_vec2(),
let mut system_state: SystemState<EventWriter<MouseMotion>> =
SystemState::new(&mut app.world);
let mut mouse_motion = system_state.get_mut(&mut app.world);
mouse_motion.send(MouseMotion {
delta: Vec2::new(x as f32, y as f32),
});
}
event::Event::Suspended => {
@ -585,16 +569,12 @@ pub fn winit_runner_with(mut app: App) {
winit_state.active = true;
}
event::Event::MainEventsCleared => {
handle_create_window_events(
&mut app.world,
event_loop,
&mut create_window_event_reader,
);
let winit_config = app.world.resource::<WinitSettings>();
let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
let update = if winit_state.active {
let windows = app.world.resource::<Windows>();
let focused = windows.iter().any(|w| w.is_focused());
match winit_config.update_mode(focused) {
// True if _any_ windows are currently being focused
let app_focused = window_focused_query.iter().any(|window| window.focused);
match winit_config.update_mode(app_focused) {
UpdateMode::Continuous | UpdateMode::Reactive { .. } => true,
UpdateMode::ReactiveLowPower { .. } => {
winit_state.low_power_event
@ -605,6 +585,7 @@ pub fn winit_runner_with(mut app: App) {
} else {
false
};
if update {
winit_state.last_update = Instant::now();
app.update();
@ -612,12 +593,15 @@ pub fn winit_runner_with(mut app: App) {
}
Event::RedrawEventsCleared => {
{
let winit_config = app.world.resource::<WinitSettings>();
let windows = app.world.resource::<Windows>();
let focused = windows.iter().any(|w| w.is_focused());
// Fetch from world
let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
// True if _any_ windows are currently being focused
let app_focused = window_focused_query.iter().any(|window| window.focused);
let now = Instant::now();
use UpdateMode::*;
*control_flow = match winit_config.update_mode(focused) {
*control_flow = match winit_config.update_mode(app_focused) {
Continuous => ControlFlow::Poll,
Reactive { max_wait } | ReactiveLowPower { max_wait } => {
if let Some(instant) = now.checked_add(*max_wait) {
@ -628,6 +612,7 @@ pub fn winit_runner_with(mut app: App) {
}
};
}
// This block needs to run after `app.update()` in `MainEventsCleared`. Otherwise,
// we won't be able to see redraw requests until the next event, defeating the
// purpose of a redraw request!
@ -638,64 +623,18 @@ pub fn winit_runner_with(mut app: App) {
redraw = true;
}
}
if let Some(app_exit_events) = app.world.get_resource::<Events<AppExit>>() {
if app_exit_event_reader.iter(app_exit_events).last().is_some() {
*control_flow = ControlFlow::Exit;
}
}
winit_state.redraw_request_sent = redraw;
}
_ => (),
}
};
// If true, returns control from Winit back to the main Bevy loop
if return_from_run {
run_return(&mut event_loop, event_handler);
} else {
run(event_loop, event_handler);
}
}
fn handle_create_window_events(
world: &mut World,
event_loop: &EventLoopWindowTarget<()>,
create_window_event_reader: &mut ManualEventReader<CreateWindow>,
) {
let world = world.cell();
let mut winit_windows = world.non_send_resource_mut::<WinitWindows>();
let mut windows = world.resource_mut::<Windows>();
let create_window_events = world.resource::<Events<CreateWindow>>();
for create_window_event in create_window_event_reader.iter(&create_window_events) {
let window = winit_windows.create_window(
event_loop,
create_window_event.id,
&create_window_event.descriptor,
);
// This event is already sent on windows, x11, and xwayland.
// TODO: we aren't yet sure about native wayland, so we might be able to exclude it,
// but sending a duplicate event isn't problematic, as windows already does this.
#[cfg(not(any(target_os = "windows", target_feature = "x11")))]
world.send_event(WindowResized {
id: create_window_event.id,
width: window.width(),
height: window.height(),
});
windows.add(window);
world.send_event(WindowCreated {
id: create_window_event.id,
});
#[cfg(target_arch = "wasm32")]
{
let channel = world.resource_mut::<web_resize::CanvasParentResizeEventChannel>();
if create_window_event.descriptor.fit_canvas_to_parent {
let selector = if let Some(selector) = &create_window_event.descriptor.canvas {
selector
} else {
web_resize::WINIT_CANVAS_SELECTOR
};
channel.listen_to_selector(create_window_event.id, selector);
}
}
}
}

View file

@ -0,0 +1,284 @@
use bevy_ecs::{
entity::Entity,
event::EventWriter,
prelude::{Changed, Component, Resource},
system::{Commands, NonSendMut, Query, RemovedComponents},
world::Mut,
};
use bevy_utils::{
tracing::{error, info, warn},
HashMap,
};
use bevy_window::{RawHandleWrapper, Window, WindowClosed, WindowCreated};
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use winit::{
dpi::{LogicalSize, PhysicalPosition, PhysicalSize},
event_loop::EventLoopWindowTarget,
};
#[cfg(target_arch = "wasm32")]
use crate::web_resize::{CanvasParentResizeEventChannel, WINIT_CANVAS_SELECTOR};
use crate::{converters, get_best_videomode, get_fitting_videomode, WinitWindows};
#[cfg(target_arch = "wasm32")]
use bevy_ecs::system::ResMut;
/// System responsible for creating new windows whenever a `Window` component is added
/// to an entity.
///
/// This will default any necessary components if they are not already added.
pub(crate) fn create_window<'a>(
mut commands: Commands,
event_loop: &EventLoopWindowTarget<()>,
created_windows: impl Iterator<Item = (Entity, Mut<'a, Window>)>,
mut event_writer: EventWriter<WindowCreated>,
mut winit_windows: NonSendMut<WinitWindows>,
#[cfg(target_arch = "wasm32")] event_channel: ResMut<CanvasParentResizeEventChannel>,
) {
for (entity, mut component) in created_windows {
if winit_windows.get_window(entity).is_some() {
continue;
}
info!(
"Creating new window {:?} ({:?})",
component.title.as_str(),
entity
);
let winit_window = winit_windows.create_window(event_loop, entity, &component);
let current_size = winit_window.inner_size();
component
.resolution
.set_scale_factor(winit_window.scale_factor());
commands
.entity(entity)
.insert(RawHandleWrapper {
window_handle: winit_window.raw_window_handle(),
display_handle: winit_window.raw_display_handle(),
})
.insert(WinitWindowInfo {
previous: component.clone(),
last_winit_size: PhysicalSize {
width: current_size.width,
height: current_size.height,
},
});
#[cfg(target_arch = "wasm32")]
{
if component.fit_canvas_to_parent {
let selector = if let Some(selector) = &component.canvas {
selector
} else {
WINIT_CANVAS_SELECTOR
};
event_channel.listen_to_selector(entity, selector);
}
}
event_writer.send(WindowCreated { window: entity });
}
}
/// Cache for closing windows so we can get better debug information.
#[derive(Debug, Clone, Resource)]
pub struct WindowTitleCache(HashMap<Entity, String>);
pub(crate) fn despawn_window(
closed: RemovedComponents<Window>,
mut close_events: EventWriter<WindowClosed>,
mut winit_windows: NonSendMut<WinitWindows>,
) {
for window in closed.iter() {
info!("Closing window {:?}", window);
winit_windows.remove_window(window);
close_events.send(WindowClosed { window });
}
}
/// Previous state of the window so we can check sub-portions of what actually was changed.
#[derive(Debug, Clone, Component)]
pub struct WinitWindowInfo {
pub previous: Window,
pub last_winit_size: PhysicalSize<u32>,
}
// Detect changes to the window and update the winit window accordingly.
//
// Notes:
// - [`Window::present_mode`] and [`Window::composite_alpha_mode`] updating should be handled in the bevy render crate.
// - [`Window::transparent`] currently cannot be updated after startup for winit.
// - [`Window::canvas`] currently cannot be updated after startup, not entirely sure if it would work well with the
// event channel stuff.
pub(crate) fn changed_window(
mut changed_windows: Query<(Entity, &mut Window, &mut WinitWindowInfo), Changed<Window>>,
winit_windows: NonSendMut<WinitWindows>,
) {
for (entity, mut window, mut info) in &mut changed_windows {
let previous = &info.previous;
if let Some(winit_window) = winit_windows.get_window(entity) {
if window.title != previous.title {
winit_window.set_title(window.title.as_str());
}
if window.mode != previous.mode {
let new_mode = match window.mode {
bevy_window::WindowMode::BorderlessFullscreen => {
Some(winit::window::Fullscreen::Borderless(None))
}
bevy_window::WindowMode::Fullscreen => {
Some(winit::window::Fullscreen::Exclusive(get_best_videomode(
&winit_window.current_monitor().unwrap(),
)))
}
bevy_window::WindowMode::SizedFullscreen => {
Some(winit::window::Fullscreen::Exclusive(get_fitting_videomode(
&winit_window.current_monitor().unwrap(),
window.width() as u32,
window.height() as u32,
)))
}
bevy_window::WindowMode::Windowed => None,
};
if winit_window.fullscreen() != new_mode {
winit_window.set_fullscreen(new_mode);
}
}
if window.resolution != previous.resolution {
let physical_size = PhysicalSize::new(
window.resolution.physical_width(),
window.resolution.physical_height(),
);
// Prevents "window.resolution values set from a winit resize event" from
// being set here, creating feedback loops.
if physical_size != info.last_winit_size {
winit_window.set_inner_size(physical_size);
}
}
if window.cursor.position != previous.cursor.position {
if let Some(physical_position) = window.cursor.position {
let inner_size = winit_window.inner_size();
let position = PhysicalPosition::new(
physical_position.x,
// Flip the coordinate space back to winit's context.
inner_size.height as f64 - physical_position.y,
);
if let Err(err) = winit_window.set_cursor_position(position) {
error!("could not set cursor position: {:?}", err);
}
}
}
if window.cursor.icon != previous.cursor.icon {
winit_window.set_cursor_icon(converters::convert_cursor_icon(window.cursor.icon));
}
if window.cursor.grab_mode != previous.cursor.grab_mode {
crate::winit_windows::attempt_grab(winit_window, window.cursor.grab_mode);
}
if window.cursor.visible != previous.cursor.visible {
winit_window.set_cursor_visible(window.cursor.visible);
}
if window.cursor.hit_test != previous.cursor.hit_test {
if let Err(err) = winit_window.set_cursor_hittest(window.cursor.hit_test) {
window.cursor.hit_test = previous.cursor.hit_test;
warn!(
"Could not set cursor hit test for window {:?}: {:?}",
window.title, err
);
}
}
if window.decorations != previous.decorations
&& window.decorations != winit_window.is_decorated()
{
winit_window.set_decorations(window.decorations);
}
if window.resizable != previous.resizable
&& window.resizable != winit_window.is_resizable()
{
winit_window.set_resizable(window.resizable);
}
if window.resize_constraints != previous.resize_constraints {
let constraints = window.resize_constraints.check_constraints();
let min_inner_size = LogicalSize {
width: constraints.min_width,
height: constraints.min_height,
};
let max_inner_size = LogicalSize {
width: constraints.max_width,
height: constraints.max_height,
};
winit_window.set_min_inner_size(Some(min_inner_size));
if constraints.max_width.is_finite() && constraints.max_height.is_finite() {
winit_window.set_max_inner_size(Some(max_inner_size));
}
}
if window.position != previous.position {
if let Some(position) = crate::winit_window_position(
&window.position,
&window.resolution,
winit_window.available_monitors(),
winit_window.primary_monitor(),
winit_window.current_monitor(),
) {
let should_set = match winit_window.outer_position() {
Ok(current_position) => current_position != position,
_ => true,
};
if should_set {
winit_window.set_outer_position(position);
}
}
}
if let Some(maximized) = window.internal.take_maximize_request() {
winit_window.set_maximized(maximized);
}
if let Some(minimized) = window.internal.take_minimize_request() {
winit_window.set_minimized(minimized);
}
if window.focused != previous.focused && window.focused {
winit_window.focus_window();
}
if window.always_on_top != previous.always_on_top {
winit_window.set_always_on_top(window.always_on_top);
}
// Currently unsupported changes
if window.transparent != previous.transparent {
window.transparent = previous.transparent;
warn!(
"Winit does not currently support updating transparency after window creation."
);
}
#[cfg(target_arch = "wasm32")]
if window.canvas != previous.canvas {
window.canvas = previous.canvas.clone();
warn!(
"Bevy currently doesn't support modifying the window canvas after initialization."
);
}
info.previous = window.clone();
}
}
}

View file

@ -1,7 +1,6 @@
use crate::WinitWindows;
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
use bevy_window::WindowId;
use crossbeam_channel::{Receiver, Sender};
use wasm_bindgen::JsCast;
use winit::dpi::LogicalSize;
@ -17,7 +16,7 @@ impl Plugin for CanvasParentResizePlugin {
struct ResizeEvent {
size: LogicalSize<f32>,
window_id: WindowId,
window: Entity,
}
#[derive(Resource)]
@ -31,7 +30,7 @@ fn canvas_parent_resize_event_handler(
resize_events: Res<CanvasParentResizeEventChannel>,
) {
for event in resize_events.receiver.try_iter() {
if let Some(window) = winit_windows.get_window(event.window_id) {
if let Some(window) = winit_windows.get_window(event.window) {
window.set_inner_size(event.size);
}
}
@ -59,12 +58,12 @@ impl Default for CanvasParentResizeEventChannel {
}
impl CanvasParentResizeEventChannel {
pub(crate) fn listen_to_selector(&self, window_id: WindowId, selector: &str) {
pub(crate) fn listen_to_selector(&self, window: Entity, selector: &str) {
let sender = self.sender.clone();
let owned_selector = selector.to_string();
let resize = move || {
if let Some(size) = get_size(&owned_selector) {
sender.send(ResizeEvent { size, window_id }).unwrap();
sender.send(ResizeEvent { size, window }).unwrap();
}
};

View file

@ -1,20 +1,18 @@
use bevy_math::{DVec2, IVec2};
use bevy_utils::HashMap;
use bevy_window::{
CursorGrabMode, MonitorSelection, RawHandleWrapper, Window, WindowDescriptor, WindowId,
WindowMode,
};
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use bevy_ecs::entity::Entity;
use bevy_utils::{tracing::warn, HashMap};
use bevy_window::{CursorGrabMode, Window, WindowMode, WindowPosition, WindowResolution};
use winit::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
window::Fullscreen,
dpi::{LogicalSize, PhysicalPosition},
monitor::MonitorHandle,
};
#[derive(Debug, Default)]
pub struct WinitWindows {
pub windows: HashMap<winit::window::WindowId, winit::window::Window>,
pub window_id_to_winit: HashMap<WindowId, winit::window::WindowId>,
pub winit_to_window_id: HashMap<winit::window::WindowId, WindowId>,
pub entity_to_winit: HashMap<Entity, winit::window::WindowId>,
pub winit_to_entity: HashMap<winit::window::WindowId, Entity>,
// Some winit functions, such as `set_window_icon` can only be used from the main thread. If
// they are used in another thread, the app will hang. This marker ensures `WinitWindows` is
// only ever accessed with bevy's non-send functions and in NonSend systems.
@ -25,45 +23,40 @@ impl WinitWindows {
pub fn create_window(
&mut self,
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
window_id: WindowId,
window_descriptor: &WindowDescriptor,
) -> Window {
entity: Entity,
window: &Window,
) -> &winit::window::Window {
let mut winit_window_builder = winit::window::WindowBuilder::new();
let &WindowDescriptor {
width,
height,
position,
monitor,
scale_factor_override,
..
} = window_descriptor;
let logical_size = LogicalSize::new(width, height);
let monitor = match monitor {
MonitorSelection::Current => None,
MonitorSelection::Primary => event_loop.primary_monitor(),
MonitorSelection::Index(i) => event_loop.available_monitors().nth(i),
};
let selected_or_primary_monitor = monitor.clone().or_else(|| event_loop.primary_monitor());
winit_window_builder = match window_descriptor.mode {
WindowMode::BorderlessFullscreen => winit_window_builder
.with_fullscreen(Some(Fullscreen::Borderless(selected_or_primary_monitor))),
WindowMode::Fullscreen => winit_window_builder.with_fullscreen(Some(
Fullscreen::Exclusive(get_best_videomode(&selected_or_primary_monitor.unwrap())),
winit_window_builder = match window.mode {
WindowMode::BorderlessFullscreen => winit_window_builder.with_fullscreen(Some(
winit::window::Fullscreen::Borderless(event_loop.primary_monitor()),
)),
WindowMode::Fullscreen => {
winit_window_builder.with_fullscreen(Some(winit::window::Fullscreen::Exclusive(
get_best_videomode(&event_loop.primary_monitor().unwrap()),
)))
}
WindowMode::SizedFullscreen => winit_window_builder.with_fullscreen(Some(
Fullscreen::Exclusive(get_fitting_videomode(
&selected_or_primary_monitor.unwrap(),
window_descriptor.width as u32,
window_descriptor.height as u32,
winit::window::Fullscreen::Exclusive(get_fitting_videomode(
&event_loop.primary_monitor().unwrap(),
window.width() as u32,
window.height() as u32,
)),
)),
WindowMode::Windowed => {
if let Some(sf) = scale_factor_override {
if let Some(position) = winit_window_position(
&window.position,
&window.resolution,
event_loop.available_monitors(),
event_loop.primary_monitor(),
None,
) {
winit_window_builder = winit_window_builder.with_position(position);
}
let logical_size = LogicalSize::new(window.width(), window.height());
if let Some(sf) = window.resolution.scale_factor_override() {
winit_window_builder.with_inner_size(logical_size.to_physical::<f64>(sf))
} else {
winit_window_builder.with_inner_size(logical_size)
@ -72,12 +65,12 @@ impl WinitWindows {
};
winit_window_builder = winit_window_builder
.with_resizable(window_descriptor.resizable)
.with_decorations(window_descriptor.decorations)
.with_transparent(window_descriptor.transparent)
.with_always_on_top(window_descriptor.always_on_top);
.with_always_on_top(window.always_on_top)
.with_resizable(window.resizable)
.with_decorations(window.decorations)
.with_transparent(window.transparent);
let constraints = window_descriptor.resize_constraints.check_constraints();
let constraints = window.resize_constraints.check_constraints();
let min_inner_size = LogicalSize {
width: constraints.min_width,
height: constraints.min_height,
@ -97,14 +90,14 @@ impl WinitWindows {
};
#[allow(unused_mut)]
let mut winit_window_builder = winit_window_builder.with_title(&window_descriptor.title);
let mut winit_window_builder = winit_window_builder.with_title(window.title.as_str());
#[cfg(target_arch = "wasm32")]
{
use wasm_bindgen::JsCast;
use winit::platform::web::WindowBuilderExtWebSys;
if let Some(selector) = &window_descriptor.canvas {
if let Some(selector) = &window.canvas {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let canvas = document
@ -121,59 +114,21 @@ impl WinitWindows {
let winit_window = winit_window_builder.build(event_loop).unwrap();
if window_descriptor.mode == WindowMode::Windowed {
use bevy_window::WindowPosition::*;
match position {
Automatic => {
if let Some(monitor) = monitor {
winit_window.set_outer_position(monitor.position());
}
}
Centered => {
if let Some(monitor) = monitor.or_else(|| winit_window.current_monitor()) {
let monitor_position = monitor.position().cast::<f64>();
let size = monitor.size();
// Logical to physical window size
let PhysicalSize::<u32> { width, height } =
logical_size.to_physical(monitor.scale_factor());
let position = PhysicalPosition {
x: size.width.saturating_sub(width) as f64 / 2. + monitor_position.x,
y: size.height.saturating_sub(height) as f64 / 2. + monitor_position.y,
};
winit_window.set_outer_position(position);
}
}
At(position) => {
if let Some(monitor) = monitor.or_else(|| winit_window.current_monitor()) {
let monitor_position = DVec2::from(<(_, _)>::from(monitor.position()));
let position = monitor_position + position.as_dvec2();
if let Some(sf) = scale_factor_override {
winit_window.set_outer_position(
LogicalPosition::new(position.x, position.y).to_physical::<f64>(sf),
);
} else {
winit_window
.set_outer_position(LogicalPosition::new(position.x, position.y));
}
}
}
}
// Do not set the grab mode on window creation if it's none, this can fail on mobile
if window.cursor.grab_mode != CursorGrabMode::None {
attempt_grab(&winit_window, window.cursor.grab_mode);
}
winit_window.set_cursor_visible(window_descriptor.cursor_visible);
winit_window.set_cursor_visible(window.cursor.visible);
self.window_id_to_winit.insert(window_id, winit_window.id());
self.winit_to_window_id.insert(winit_window.id(), window_id);
self.entity_to_winit.insert(entity, winit_window.id());
self.winit_to_entity.insert(winit_window.id(), entity);
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowExtWebSys;
if window_descriptor.canvas.is_none() {
if window.canvas.is_none() {
let canvas = winit_window.canvas();
let window = web_sys::window().unwrap();
@ -185,45 +140,31 @@ impl WinitWindows {
}
}
let position = winit_window
.outer_position()
.ok()
.map(|position| IVec2::new(position.x, position.y));
let inner_size = winit_window.inner_size();
let scale_factor = winit_window.scale_factor();
let raw_handle = RawHandleWrapper {
window_handle: winit_window.raw_window_handle(),
display_handle: winit_window.raw_display_handle(),
};
self.windows.insert(winit_window.id(), winit_window);
let mut window = Window::new(
window_id,
window_descriptor,
inner_size.width,
inner_size.height,
scale_factor,
position,
Some(raw_handle),
);
// Do not set the grab mode on window creation if it's none, this can fail on mobile
if window_descriptor.cursor_grab_mode != CursorGrabMode::None {
window.set_cursor_grab_mode(window_descriptor.cursor_grab_mode);
}
window
self.windows
.entry(winit_window.id())
.insert(winit_window)
.into_mut()
}
pub fn get_window(&self, id: WindowId) -> Option<&winit::window::Window> {
self.window_id_to_winit
.get(&id)
.and_then(|id| self.windows.get(id))
/// Get the winit window that is associated with our entity.
pub fn get_window(&self, entity: Entity) -> Option<&winit::window::Window> {
self.entity_to_winit
.get(&entity)
.and_then(|winit_id| self.windows.get(winit_id))
}
pub fn get_window_id(&self, id: winit::window::WindowId) -> Option<WindowId> {
self.winit_to_window_id.get(&id).cloned()
/// Get the entity associated with the winit window id.
///
/// This is mostly just an intermediary step between us and winit.
pub fn get_window_entity(&self, winit_id: winit::window::WindowId) -> Option<Entity> {
self.winit_to_entity.get(&winit_id).cloned()
}
pub fn remove_window(&mut self, id: WindowId) -> Option<winit::window::Window> {
let winit_id = self.window_id_to_winit.remove(&id)?;
/// Remove a window from winit.
///
/// This should mostly just be called when the window is closing.
pub fn remove_window(&mut self, entity: Entity) -> Option<winit::window::Window> {
let winit_id = self.entity_to_winit.remove(&entity)?;
// Don't remove from winit_to_window_id, to track that we used to know about this winit window
self.windows.remove(&winit_id)
}
@ -278,3 +219,89 @@ pub fn get_best_videomode(monitor: &winit::monitor::MonitorHandle) -> winit::mon
modes.first().unwrap().clone()
}
pub(crate) fn attempt_grab(winit_window: &winit::window::Window, grab_mode: CursorGrabMode) {
let grab_result = match grab_mode {
bevy_window::CursorGrabMode::None => {
winit_window.set_cursor_grab(winit::window::CursorGrabMode::None)
}
bevy_window::CursorGrabMode::Confined => winit_window
.set_cursor_grab(winit::window::CursorGrabMode::Confined)
.or_else(|_e| winit_window.set_cursor_grab(winit::window::CursorGrabMode::Locked)),
bevy_window::CursorGrabMode::Locked => winit_window
.set_cursor_grab(winit::window::CursorGrabMode::Locked)
.or_else(|_e| winit_window.set_cursor_grab(winit::window::CursorGrabMode::Confined)),
};
if let Err(err) = grab_result {
let err_desc = match grab_mode {
bevy_window::CursorGrabMode::Confined | bevy_window::CursorGrabMode::Locked => "grab",
bevy_window::CursorGrabMode::None => "ungrab",
};
bevy_utils::tracing::error!("Unable to {} cursor: {}", err_desc, err);
}
}
// Ideally we could generify this across window backends, but we only really have winit atm
// so whatever.
pub fn winit_window_position(
position: &WindowPosition,
resolution: &WindowResolution,
mut available_monitors: impl Iterator<Item = MonitorHandle>,
primary_monitor: Option<MonitorHandle>,
current_monitor: Option<MonitorHandle>,
) -> Option<PhysicalPosition<i32>> {
match position {
WindowPosition::Automatic => {
/* Window manager will handle position */
None
}
WindowPosition::Centered(monitor_selection) => {
use bevy_window::MonitorSelection::*;
let maybe_monitor = match monitor_selection {
Current => {
if current_monitor.is_none() {
warn!("Can't select current monitor on window creation or cannot find current monitor!");
}
current_monitor
}
Primary => primary_monitor,
Index(n) => available_monitors.nth(*n),
};
if let Some(monitor) = maybe_monitor {
let screen_size = monitor.size();
let scale_factor = resolution.base_scale_factor();
// Logical to physical window size
let (width, height): (u32, u32) =
LogicalSize::new(resolution.width(), resolution.height())
.to_physical::<u32>(scale_factor)
.into();
let position = PhysicalPosition {
x: screen_size.width.saturating_sub(width) as f64 / 2.
+ monitor.position().x as f64,
y: screen_size.height.saturating_sub(height) as f64 / 2.
+ monitor.position().y as f64,
};
Some(position.cast::<i32>())
} else {
warn!("Couldn't get monitor selected with: {monitor_selection:?}");
None
}
}
WindowPosition::At(position) => {
Some(PhysicalPosition::new(position[0] as f64, position[1] as f64).cast::<i32>())
}
}
}
// WARNING: this only works under the assumption that wasm runtime is single threaded
#[cfg(target_arch = "wasm32")]
unsafe impl Send for WinitWindows {}
#[cfg(target_arch = "wasm32")]
unsafe impl Sync for WinitWindows {}

View file

@ -3,10 +3,8 @@
use std::f32::consts::PI;
use bevy::{
core_pipeline::clear_color::ClearColorConfig,
prelude::*,
render::camera::Viewport,
window::{WindowId, WindowResized},
core_pipeline::clear_color::ClearColorConfig, prelude::*, render::camera::Viewport,
window::WindowResized,
};
fn main() {
@ -82,7 +80,7 @@ struct LeftCamera;
struct RightCamera;
fn set_camera_viewports(
windows: Res<Windows>,
windows: Query<&Window>,
mut resize_events: EventReader<WindowResized>,
mut left_camera: Query<&mut Camera, (With<LeftCamera>, Without<RightCamera>)>,
mut right_camera: Query<&mut Camera, With<RightCamera>>,
@ -91,21 +89,25 @@ fn set_camera_viewports(
// so then each camera always takes up half the screen.
// A resize_event is sent when the window is first created, allowing us to reuse this system for initial setup.
for resize_event in resize_events.iter() {
if resize_event.id == WindowId::primary() {
let window = windows.primary();
let mut left_camera = left_camera.single_mut();
left_camera.viewport = Some(Viewport {
physical_position: UVec2::new(0, 0),
physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()),
..default()
});
let window = windows.get(resize_event.window).unwrap();
let mut left_camera = left_camera.single_mut();
left_camera.viewport = Some(Viewport {
physical_position: UVec2::new(0, 0),
physical_size: UVec2::new(
window.resolution.physical_width() / 2,
window.resolution.physical_height(),
),
..default()
});
let mut right_camera = right_camera.single_mut();
right_camera.viewport = Some(Viewport {
physical_position: UVec2::new(window.physical_width() / 2, 0),
physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()),
..default()
});
}
let mut right_camera = right_camera.single_mut();
right_camera.viewport = Some(Viewport {
physical_position: UVec2::new(window.resolution.physical_width() / 2, 0),
physical_size: UVec2::new(
window.resolution.physical_width() / 2,
window.resolution.physical_height(),
),
..default()
});
}
}

View file

@ -11,7 +11,7 @@
//! - <https://docs.rs/bevy/latest/bevy/app/struct.App.html#method.run>
//! - <https://docs.rs/bevy/latest/bevy/winit/struct.WinitSettings.html#structfield.return_from_run>
use bevy::{prelude::*, winit::WinitSettings};
use bevy::{prelude::*, window::WindowPlugin, winit::WinitSettings};
fn main() {
println!("Running Bevy App");
@ -21,10 +21,10 @@ fn main() {
..default()
})
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
title: "Close the window to return to the main function".to_owned(),
primary_window: Some(Window {
title: "Close the window to return to the main function".into(),
..default()
},
}),
..default()
}))
.add_system(system)

View file

@ -37,8 +37,8 @@ fn move_system(mut sprites: Query<(&mut Transform, &Velocity)>) {
}
// Bounce sprites outside the window
fn bounce_system(windows: Res<Windows>, mut sprites: Query<(&Transform, &mut Velocity)>) {
let window = windows.primary();
fn bounce_system(windows: Query<&Window>, mut sprites: Query<(&Transform, &mut Velocity)>) {
let window = windows.single();
let width = window.width();
let height = window.height();
let left = width / -2.0;

View file

@ -245,17 +245,15 @@ fn velocity_system(time: Res<Time>, mut velocity_query: Query<&mut Velocity>) {
/// velocity. On collision with the ground it applies an upwards
/// force.
fn collision_system(
windows: Res<Windows>,
windows: Query<&Window>,
mut query: Query<(&mut Velocity, &mut Transform), With<Contributor>>,
) {
let Some(window) = windows.get_primary() else {
return;
};
let window = windows.single();
let ceiling = window.height() / 2.;
let ground = -(window.height() / 2.);
let ground = -window.height() / 2.;
let wall_left = -(window.width() / 2.);
let wall_left = -window.width() / 2.;
let wall_right = window.width() / 2.;
// The maximum height the birbs should try to reach is one birb below the top of the window.

View file

@ -1,7 +1,6 @@
//! Demonstrates how to grab and hide the mouse cursor.
use bevy::prelude::*;
use bevy::window::CursorGrabMode;
use bevy::{prelude::*, window::CursorGrabMode};
fn main() {
App::new()
@ -13,17 +12,19 @@ fn main() {
// This system grabs the mouse when the left mouse button is pressed
// and releases it when the escape key is pressed
fn grab_mouse(
mut windows: ResMut<Windows>,
mut windows: Query<&mut Window>,
mouse: Res<Input<MouseButton>>,
key: Res<Input<KeyCode>>,
) {
let window = windows.primary_mut();
let mut window = windows.single_mut();
if mouse.just_pressed(MouseButton::Left) {
window.set_cursor_visibility(false);
window.set_cursor_grab_mode(CursorGrabMode::Locked);
window.cursor.visible = false;
window.cursor.grab_mode = CursorGrabMode::Locked;
}
if key.just_pressed(KeyCode::Escape) {
window.set_cursor_visibility(true);
window.set_cursor_grab_mode(CursorGrabMode::None);
window.cursor.visible = true;
window.cursor.grab_mode = CursorGrabMode::None;
}
}

View file

@ -5,11 +5,11 @@ use bevy::{input::touch::TouchPhase, prelude::*, window::WindowMode};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
primary_window: Some(Window {
resizable: false,
mode: WindowMode::BorderlessFullscreen,
..default()
},
}),
..default()
}))
.add_startup_system(setup_scene)
@ -20,17 +20,18 @@ fn main() {
}
fn touch_camera(
windows: ResMut<Windows>,
windows: Query<&Window>,
mut touches: EventReader<TouchInput>,
mut camera: Query<&mut Transform, With<Camera3d>>,
mut last_position: Local<Option<Vec2>>,
) {
let window = windows.single();
for touch in touches.iter() {
if touch.phase == TouchPhase::Started {
*last_position = None;
}
if let Some(last_position) = *last_position {
let window = windows.primary();
let mut transform = camera.single_mut();
*transform = Transform::from_xyz(
transform.translation.x

View file

@ -13,6 +13,7 @@ use bevy::{
renderer::{RenderContext, RenderDevice},
RenderApp, RenderStage,
},
window::WindowPlugin,
};
use std::borrow::Cow;
@ -23,11 +24,11 @@ fn main() {
App::new()
.insert_resource(ClearColor(Color::BLACK))
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
primary_window: Some(Window {
// uncomment for unthrottled FPS
// present_mode: bevy::window::PresentMode::AutoNoVsync,
..default()
},
}),
..default()
}))
.add_plugin(GameOfLifeComputePlugin)

View file

@ -34,16 +34,18 @@ struct MainCube;
fn setup(
mut commands: Commands,
mut windows: ResMut<Windows>,
windows: Query<&Window>,
mut meshes: ResMut<Assets<Mesh>>,
mut post_processing_materials: ResMut<Assets<PostProcessingMaterial>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut images: ResMut<Assets<Image>>,
) {
let window = windows.primary_mut();
// This assumes we only have a single window
let window = windows.single();
let size = Extent3d {
width: window.physical_width(),
height: window.physical_height(),
width: window.resolution.physical_width(),
height: window.resolution.physical_height(),
..default()
};

View file

@ -6,7 +6,7 @@ use bevy::{
diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
time::FixedTimestep,
window::PresentMode,
window::{PresentMode, WindowResolution},
};
use rand::{thread_rng, Rng};
@ -30,14 +30,12 @@ struct Bird {
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
title: "BevyMark".to_string(),
width: 800.,
height: 600.,
primary_window: Some(Window {
title: "BevyMark".into(),
resolution: (800., 600.).into(),
present_mode: PresentMode::AutoNoVsync,
resizable: true,
..default()
},
}),
..default()
}))
.add_plugin(FrameTimeDiagnosticsPlugin::default())
@ -67,15 +65,17 @@ struct BirdScheduled {
fn scheduled_spawner(
mut commands: Commands,
windows: Res<Windows>,
windows: Query<&Window>,
mut scheduled: ResMut<BirdScheduled>,
mut counter: ResMut<BevyCounter>,
bird_texture: Res<BirdTexture>,
) {
let window = windows.single();
if scheduled.wave > 0 {
spawn_birds(
&mut commands,
&windows,
&window.resolution,
&mut counter,
scheduled.per_wave,
bird_texture.clone_weak(),
@ -150,10 +150,12 @@ fn mouse_handler(
mut commands: Commands,
time: Res<Time>,
mouse_button_input: Res<Input<MouseButton>>,
windows: Res<Windows>,
windows: Query<&Window>,
bird_texture: Res<BirdTexture>,
mut counter: ResMut<BevyCounter>,
) {
let window = windows.single();
if mouse_button_input.just_released(MouseButton::Left) {
let mut rng = thread_rng();
counter.color = Color::rgb_linear(rng.gen(), rng.gen(), rng.gen());
@ -163,7 +165,7 @@ fn mouse_handler(
let spawn_count = (BIRDS_PER_SECOND as f64 * time.delta_seconds_f64()) as usize;
spawn_birds(
&mut commands,
&windows,
&window.resolution,
&mut counter,
spawn_count,
bird_texture.clone_weak(),
@ -173,14 +175,13 @@ fn mouse_handler(
fn spawn_birds(
commands: &mut Commands,
windows: &Windows,
primary_window_resolution: &WindowResolution,
counter: &mut BevyCounter,
spawn_count: usize,
texture: Handle<Image>,
) {
let window = windows.primary();
let bird_x = (window.width() / -2.) + HALF_BIRD_SIZE;
let bird_y = (window.height() / 2.) - HALF_BIRD_SIZE;
let bird_x = (primary_window_resolution.width() / -2.) + HALF_BIRD_SIZE;
let bird_y = (primary_window_resolution.height() / 2.) - HALF_BIRD_SIZE;
let mut rng = thread_rng();
for count in 0..spawn_count {
@ -219,8 +220,9 @@ fn movement_system(time: Res<Time>, mut bird_query: Query<(&mut Bird, &mut Trans
}
}
fn collision_system(windows: Res<Windows>, mut bird_query: Query<(&mut Bird, &Transform)>) {
let window = windows.primary();
fn collision_system(windows: Query<&Window>, mut bird_query: Query<(&mut Bird, &Transform)>) {
let window = windows.single();
let half_width = window.width() * 0.5;
let half_height = window.height() * 0.5;

View file

@ -1,7 +1,7 @@
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
window::PresentMode,
window::{PresentMode, WindowPlugin},
};
// For a total of 110 * 110 = 12100 buttons with text
@ -12,10 +12,10 @@ const FONT_SIZE: f32 = 7.0;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
primary_window: Some(Window {
present_mode: PresentMode::Immediate,
..default()
},
}),
..default()
}))
.add_plugin(FrameTimeDiagnosticsPlugin::default())

View file

@ -16,16 +16,16 @@ use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
math::{DVec2, DVec3},
prelude::*,
window::PresentMode,
window::{PresentMode, WindowPlugin},
};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
primary_window: Some(Window {
present_mode: PresentMode::AutoNoVsync,
..default()
},
}),
..default()
}))
.add_plugin(FrameTimeDiagnosticsPlugin::default())

View file

@ -7,7 +7,7 @@ use std::time::Duration;
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
window::PresentMode,
window::{PresentMode, WindowPlugin},
};
#[derive(Resource)]
@ -20,11 +20,11 @@ struct Foxes {
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
title: "🦊🦊🦊 Many Foxes! 🦊🦊🦊".to_string(),
primary_window: Some(Window {
title: "🦊🦊🦊 Many Foxes! 🦊🦊🦊".into(),
present_mode: PresentMode::AutoNoVsync,
..default()
},
}),
..default()
}))
.add_plugin(FrameTimeDiagnosticsPlugin)

View file

@ -9,20 +9,19 @@ use bevy::{
pbr::{ExtractedPointLight, GlobalLightMeta},
prelude::*,
render::{camera::ScalingMode, Extract, RenderApp, RenderStage},
window::PresentMode,
window::{PresentMode, WindowPlugin},
};
use rand::{thread_rng, Rng};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
width: 1024.0,
height: 768.0,
title: "many_lights".to_string(),
primary_window: Some(Window {
resolution: (1024.0, 768.0).into(),
title: "many_lights".into(),
present_mode: PresentMode::AutoNoVsync,
..default()
},
}),
..default()
}))
.add_plugin(FrameTimeDiagnosticsPlugin::default())

View file

@ -10,7 +10,7 @@
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
window::PresentMode,
window::{PresentMode, WindowPlugin},
};
use rand::Rng;
@ -31,10 +31,10 @@ fn main() {
.add_plugin(LogDiagnosticsPlugin::default())
.add_plugin(FrameTimeDiagnosticsPlugin::default())
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
primary_window: Some(Window {
present_mode: PresentMode::AutoNoVsync,
..default()
},
}),
..default()
}))
.add_startup_system(setup)

View file

@ -97,14 +97,13 @@ impl Plugin for CameraControllerPlugin {
fn camera_controller(
time: Res<Time>,
mut windows: ResMut<Windows>,
mut windows: Query<&mut Window>,
mut mouse_events: EventReader<MouseMotion>,
mouse_button_input: Res<Input<MouseButton>>,
key_input: Res<Input<KeyCode>>,
mut move_toggled: Local<bool>,
mut query: Query<(&mut Transform, &mut CameraController), With<Camera>>,
) {
let window = windows.primary_mut();
let dt = time.delta_seconds();
if let Ok((mut transform, mut options)) = query.get_single_mut() {
@ -166,15 +165,23 @@ fn camera_controller(
// Handle mouse input
let mut mouse_delta = Vec2::ZERO;
if mouse_button_input.pressed(options.mouse_key_enable_mouse) || *move_toggled {
window.set_cursor_grab_mode(CursorGrabMode::Locked);
window.set_cursor_visibility(false);
for mut window in &mut windows {
if !window.focused {
continue;
}
window.cursor.grab_mode = CursorGrabMode::Locked;
window.cursor.visible = false;
}
for mouse_event in mouse_events.iter() {
mouse_delta += mouse_event.delta;
}
} else {
window.set_cursor_grab_mode(CursorGrabMode::None);
window.set_cursor_visibility(true);
for mut window in &mut windows {
window.cursor.grab_mode = CursorGrabMode::None;
window.cursor.visible = true;
}
}
if mouse_delta != Vec2::ZERO {

View file

@ -9,6 +9,7 @@ use bevy::{
math::Vec3A,
prelude::*,
render::primitives::{Aabb, Sphere},
window::WindowPlugin,
};
mod camera_controller_plugin;
@ -26,10 +27,10 @@ fn main() {
.add_plugins(
DefaultPlugins
.set(WindowPlugin {
window: WindowDescriptor {
primary_window: Some(Window {
title: "bevy scene viewer".to_string(),
..default()
},
}),
..default()
})
.set(AssetPlugin {

View file

@ -3,16 +3,16 @@
use bevy::{
diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin},
prelude::*,
window::PresentMode,
window::{PresentMode, WindowPlugin},
};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
primary_window: Some(Window {
present_mode: PresentMode::AutoNoVsync,
..default()
},
}),
..default()
}))
.add_plugin(FrameTimeDiagnosticsPlugin)

View file

@ -4,18 +4,16 @@
use bevy::prelude::*;
fn main() {
// Set the window's parameters, note we're setting always_on_top to be true.
let window_desc = WindowDescriptor {
transparent: true,
decorations: true,
always_on_top: true,
..default()
};
App::new()
.insert_resource(ClearColor(Color::NONE)) // Use a transparent window, to make effects obvious.
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: window_desc,
primary_window: Some(Window {
// Set the window's parameters, note we're setting the window to always be on top.
transparent: true,
decorations: true,
always_on_top: true,
..default()
}),
..default()
}))
.add_startup_system(setup)
@ -51,10 +49,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
));
}
// A simple system to handle some keyboard input and toggle on/off the hittest.
fn toggle_mouse_passthrough(keyboard_input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) {
fn toggle_mouse_passthrough(keyboard_input: Res<Input<KeyCode>>, mut windows: Query<&mut Window>) {
if keyboard_input.just_pressed(KeyCode::P) {
let window = windows.primary_mut();
let hittest: bool = window.hittest();
window.set_cursor_hittest(!hittest);
let mut window = windows.single_mut();
window.cursor.hit_test = !window.cursor.hit_test;
}
}

View file

@ -6,7 +6,7 @@
use bevy::{
prelude::*,
utils::Duration,
window::{PresentMode, RequestRedraw},
window::{PresentMode, RequestRedraw, WindowPlugin},
winit::WinitSettings,
};
@ -26,11 +26,11 @@ fn main() {
})
.insert_resource(ExampleMode::Game)
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
primary_window: Some(Window {
// Turn off vsync to maximize CPU/GPU usage
present_mode: PresentMode::AutoNoVsync,
..default()
},
}),
..default()
}))
.add_startup_system(test_setup::setup)

View file

@ -1,24 +1,17 @@
//! Uses two windows to visualize a 3D model from different angles.
use bevy::{
prelude::*,
render::camera::RenderTarget,
window::{CreateWindow, PresentMode, WindowId},
};
use bevy::{prelude::*, render::camera::RenderTarget, window::WindowRef};
fn main() {
App::new()
// By default, a primary window gets spawned by `WindowPlugin`, contained in `DefaultPlugins`
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_startup_system(setup_scene)
.add_system(bevy::window::close_on_esc)
.run();
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut create_window_events: EventWriter<CreateWindow>,
) {
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
// add entities to the world
commands.spawn(SceneBundle {
scene: asset_server.load("models/monkey/Monkey.gltf#Scene0"),
@ -29,31 +22,26 @@ fn setup(
transform: Transform::from_xyz(4.0, 5.0, 4.0),
..default()
});
// main camera
// main camera, cameras default to the primary window
// so we don't need to specify that.
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(0.0, 0.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
let window_id = WindowId::new();
// sends out a "CreateWindow" event, which will be received by the windowing backend
create_window_events.send(CreateWindow {
id: window_id,
descriptor: WindowDescriptor {
width: 800.,
height: 600.,
present_mode: PresentMode::AutoNoVsync,
title: "Second window".to_string(),
// Spawn a second window
let second_window = commands
.spawn(Window {
title: "Second window".to_owned(),
..default()
},
});
})
.id();
// second window camera
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
camera: Camera {
target: RenderTarget::Window(window_id),
target: RenderTarget::Window(WindowRef::Entity(second_window)),
..default()
},
..default()

View file

@ -1,19 +1,19 @@
//! This example illustrates how to override the window scale factor imposed by the
//! operating system.
use bevy::prelude::*;
use bevy::{prelude::*, window::WindowResolution};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
width: 500.,
height: 300.,
primary_window: Some(Window {
resolution: WindowResolution::new(500., 300.).with_scale_factor_override(1.0),
..default()
},
}),
..default()
}))
.add_startup_system(setup)
.add_system(display_override)
.add_system(toggle_override)
.add_system(change_scale_factor)
.run();
@ -63,20 +63,39 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
});
}
/// Set the title of the window to the current override
fn display_override(mut windows: Query<&mut Window>) {
let mut window = windows.single_mut();
window.title = format!(
"Scale override: {:?}",
window.resolution.scale_factor_override()
);
}
/// This system toggles scale factor overrides when enter is pressed
fn toggle_override(input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) {
let window = windows.primary_mut();
fn toggle_override(input: Res<Input<KeyCode>>, mut windows: Query<&mut Window>) {
let mut window = windows.single_mut();
if input.just_pressed(KeyCode::Return) {
window.set_scale_factor_override(window.scale_factor_override().xor(Some(1.)));
let scale_factor_override = window.resolution.scale_factor_override();
window
.resolution
.set_scale_factor_override(scale_factor_override.xor(Some(1.0)));
}
}
/// This system changes the scale factor override when up or down is pressed
fn change_scale_factor(input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) {
let window = windows.primary_mut();
fn change_scale_factor(input: Res<Input<KeyCode>>, mut windows: Query<&mut Window>) {
let mut window = windows.single_mut();
let scale_factor_override = window.resolution.scale_factor_override();
if input.just_pressed(KeyCode::Up) {
window.set_scale_factor_override(window.scale_factor_override().map(|n| n + 1.));
window
.resolution
.set_scale_factor_override(scale_factor_override.map(|n| n + 1.0));
} else if input.just_pressed(KeyCode::Down) {
window.set_scale_factor_override(window.scale_factor_override().map(|n| (n - 1.).max(1.)));
window
.resolution
.set_scale_factor_override(scale_factor_override.map(|n| (n - 1.0).max(1.0)));
}
}

View file

@ -4,7 +4,10 @@
//! [documentation](https://docs.rs/bevy/latest/bevy/prelude/struct.WindowDescriptor.html#structfield.transparent)
//! for more details.
use bevy::prelude::*;
use bevy::{
prelude::*,
window::{Window, WindowPlugin},
};
fn main() {
App::new()
@ -12,13 +15,13 @@ fn main() {
.insert_resource(ClearColor(Color::NONE))
.add_startup_system(setup)
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
primary_window: Some(Window {
// Setting `transparent` allows the `ClearColor`'s alpha value to take effect
transparent: true,
// Disabling window decorations to make it feel more like a widget than a window
decorations: false,
..default()
},
}),
..default()
}))
.run();

View file

@ -62,22 +62,22 @@ fn setup_ui(mut cmd: Commands, asset_server: Res<AssetServer>) {
/// This system shows how to request the window to a new resolution
fn toggle_resolution(
keys: Res<Input<KeyCode>>,
mut windows: ResMut<Windows>,
mut windows: Query<&mut Window>,
resolution: Res<ResolutionSettings>,
) {
let window = windows.primary_mut();
let mut window = windows.single_mut();
if keys.just_pressed(KeyCode::Key1) {
let res = resolution.small;
window.set_resolution(res.x, res.y);
window.resolution.set(res.x, res.y);
}
if keys.just_pressed(KeyCode::Key2) {
let res = resolution.medium;
window.set_resolution(res.x, res.y);
window.resolution.set(res.x, res.y);
}
if keys.just_pressed(KeyCode::Key3) {
let res = resolution.large;
window.set_resolution(res.x, res.y);
window.resolution.set(res.x, res.y);
}
}

View file

@ -10,14 +10,14 @@ use bevy::{
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
title: "I am a window!".to_string(),
width: 500.,
height: 300.,
primary_window: Some(Window {
title: "I am a window!".into(),
resolution: (500., 300.).into(),
present_mode: PresentMode::AutoVsync,
always_on_top: true,
// Tells wasm to resize the window according to the available canvas
fit_canvas_to_parent: true,
..default()
},
}),
..default()
}))
.add_plugin(LogDiagnosticsPlugin::default())
@ -32,16 +32,16 @@ fn main() {
/// This system toggles the vsync mode when pressing the button V.
/// You'll see fps increase displayed in the console.
fn toggle_vsync(input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) {
fn toggle_vsync(input: Res<Input<KeyCode>>, mut windows: Query<&mut Window>) {
if input.just_pressed(KeyCode::V) {
let window = windows.primary_mut();
let mut window = windows.single_mut();
window.set_present_mode(if matches!(window.present_mode(), PresentMode::AutoVsync) {
window.present_mode = if matches!(window.present_mode, PresentMode::AutoVsync) {
PresentMode::AutoNoVsync
} else {
PresentMode::AutoVsync
});
info!("PRESENT_MODE: {:?}", window.present_mode());
};
info!("PRESENT_MODE: {:?}", window.present_mode);
}
}
@ -51,49 +51,49 @@ fn toggle_vsync(input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) {
/// This feature only works on some platforms. Please check the
/// [documentation](https://docs.rs/bevy/latest/bevy/prelude/struct.WindowDescriptor.html#structfield.always_on_top)
/// for more details.
fn toggle_always_on_top(input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) {
fn toggle_always_on_top(input: Res<Input<KeyCode>>, mut windows: Query<&mut Window>) {
if input.just_pressed(KeyCode::T) {
let window = windows.primary_mut();
let mut window = windows.single_mut();
let on_top: bool = window.always_on_top();
window.always_on_top = !window.always_on_top;
if on_top {
info!("UNLOCKING WINDOW");
} else {
if window.always_on_top {
info!("LOCKING WINDOW ON TOP");
} else {
info!("UNLOCKING WINDOW");
}
window.set_always_on_top(!on_top);
}
}
/// This system will then change the title during execution
fn change_title(time: Res<Time>, mut windows: ResMut<Windows>) {
let window = windows.primary_mut();
window.set_title(format!(
fn change_title(mut windows: Query<&mut Window>, time: Res<Time>) {
let mut window = windows.single_mut();
window.title = format!(
"Seconds since startup: {}",
time.elapsed_seconds().round()
));
time.elapsed().as_secs_f32().round()
);
}
/// This system toggles the cursor's visibility when the space bar is pressed
fn toggle_cursor(input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) {
let window = windows.primary_mut();
fn toggle_cursor(mut windows: Query<&mut Window>, input: Res<Input<KeyCode>>) {
if input.just_pressed(KeyCode::Space) {
window.set_cursor_grab_mode(match window.cursor_grab_mode() {
let mut window = windows.single_mut();
window.cursor.visible = !window.cursor.visible;
window.cursor.grab_mode = match window.cursor.grab_mode {
CursorGrabMode::None => CursorGrabMode::Locked,
CursorGrabMode::Locked | CursorGrabMode::Confined => CursorGrabMode::None,
});
window.set_cursor_visibility(!window.cursor_visible());
};
}
}
/// This system cycles the cursor's icon through a small set of icons when clicking
fn cycle_cursor_icon(
mut windows: Query<&mut Window>,
input: Res<Input<MouseButton>>,
mut windows: ResMut<Windows>,
mut index: Local<usize>,
) {
let mut window = windows.single_mut();
const ICONS: &[CursorIcon] = &[
CursorIcon::Default,
CursorIcon::Hand,
@ -101,16 +101,16 @@ fn cycle_cursor_icon(
CursorIcon::Text,
CursorIcon::Copy,
];
let window = windows.primary_mut();
if input.just_pressed(MouseButton::Left) {
*index = (*index + 1) % ICONS.len();
window.set_cursor_icon(ICONS[*index]);
} else if input.just_pressed(MouseButton::Right) {
*index = if *index == 0 {
ICONS.len() - 1
} else {
*index - 1
};
window.set_cursor_icon(ICONS[*index]);
}
window.cursor.icon = ICONS[*index];
}

View file

@ -7,10 +7,10 @@ fn main() {
// it is currently.
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
primary_window: Some(Window {
title: "Minimising".into(),
..Default::default()
},
..default()
}),
..default()
}))
.add_system(minimise_automatically)
@ -19,9 +19,10 @@ fn main() {
.run();
}
fn minimise_automatically(mut windows: ResMut<Windows>, mut frames: Local<u32>) {
fn minimise_automatically(mut windows: Query<&mut Window>, mut frames: Local<u32>) {
if *frames == 60 {
windows.primary_mut().set_minimized(true);
let mut window = windows.single_mut();
window.set_minimized(true);
} else {
*frames += 1;
}

View file

@ -1,7 +1,7 @@
//! A test to confirm that `bevy` allows setting the window to arbitrary small sizes
//! This is run in CI to ensure that this doesn't regress again.
use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*};
use bevy::{core_pipeline::clear_color::ClearColorConfig, prelude::*, window::WindowResolution};
// The smallest size reached is 1x1, as X11 doesn't support windows with a 0 dimension
// TODO: Add a check for platforms other than X11 for 0xk and kx0, despite those currently unsupported on CI.
@ -23,16 +23,17 @@ fn main() {
width: MAX_WIDTH,
height: MAX_HEIGHT,
})
.add_plugins(DefaultPlugins.set(WindowPlugin {
window: WindowDescriptor {
width: MAX_WIDTH.try_into().unwrap(),
height: MAX_HEIGHT.try_into().unwrap(),
scale_factor_override: Some(1.),
title: "Resizing".into(),
..Default::default()
},
..default()
}))
.add_plugins(
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::new(MAX_WIDTH as f32, MAX_HEIGHT as f32)
.with_scale_factor_override(1.0),
title: "Resizing".into(),
..default()
}),
..default()
}),
)
.insert_resource(Phase::ContractingY)
.add_system(change_window_size)
.add_system(sync_dimensions)
@ -98,12 +99,10 @@ fn change_window_size(
}
}
fn sync_dimensions(dim: Res<Dimensions>, mut windows: ResMut<Windows>) {
fn sync_dimensions(dim: Res<Dimensions>, mut windows: Query<&mut Window>) {
if dim.is_changed() {
windows.primary_mut().set_resolution(
dim.width.try_into().unwrap(),
dim.height.try_into().unwrap(),
);
let mut window = windows.single_mut();
window.resolution.set(dim.width as f32, dim.height as f32);
}
}