mirror of
https://github.com/bevyengine/bevy
synced 2025-02-17 22:48:38 +00:00
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:
parent
f0c504947c
commit
ddfafab971
47 changed files with 1940 additions and 1892 deletions
|
@ -13,6 +13,7 @@ use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
|
prelude::With,
|
||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
system::{Commands, Query, Res},
|
system::{Commands, Query, Res},
|
||||||
};
|
};
|
||||||
|
@ -21,7 +22,10 @@ use bevy_reflect::prelude::*;
|
||||||
use bevy_reflect::FromReflect;
|
use bevy_reflect::FromReflect;
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
use bevy_utils::HashSet;
|
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 std::{borrow::Cow, ops::Range};
|
||||||
use wgpu::{Extent3d, TextureFormat};
|
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)
|
/// The "target" that a [`Camera`] will render to. For example, this could be a [`Window`](bevy_window::Window)
|
||||||
/// swapchain or an [`Image`].
|
/// swapchain or an [`Image`].
|
||||||
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Reflect)]
|
||||||
pub enum RenderTarget {
|
pub enum RenderTarget {
|
||||||
/// Window to which the camera's view is rendered.
|
/// 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 to which the camera's view is rendered.
|
||||||
Image(Handle<Image>),
|
Image(Handle<Image>),
|
||||||
}
|
}
|
||||||
|
@ -340,16 +355,28 @@ impl Default for RenderTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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>(
|
pub fn get_texture_view<'a>(
|
||||||
&self,
|
&self,
|
||||||
windows: &'a ExtractedWindows,
|
windows: &'a ExtractedWindows,
|
||||||
images: &'a RenderAssets<Image>,
|
images: &'a RenderAssets<Image>,
|
||||||
) -> Option<&'a TextureView> {
|
) -> Option<&'a TextureView> {
|
||||||
match self {
|
match self {
|
||||||
RenderTarget::Window(window_id) => windows
|
NormalizedRenderTarget::Window(window_ref) => windows
|
||||||
.get(window_id)
|
.get(&window_ref.entity())
|
||||||
.and_then(|window| window.swap_chain_texture.as_ref()),
|
.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)
|
images.get(image_handle).map(|image| &image.texture_view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -362,47 +389,55 @@ impl RenderTarget {
|
||||||
images: &'a RenderAssets<Image>,
|
images: &'a RenderAssets<Image>,
|
||||||
) -> Option<TextureFormat> {
|
) -> Option<TextureFormat> {
|
||||||
match self {
|
match self {
|
||||||
RenderTarget::Window(window_id) => windows
|
NormalizedRenderTarget::Window(window_ref) => windows
|
||||||
.get(window_id)
|
.get(&window_ref.entity())
|
||||||
.and_then(|window| window.swap_chain_texture_format),
|
.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)
|
images.get(image_handle).map(|image| image.texture_format)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_render_target_info(
|
pub fn get_render_target_info<'a>(
|
||||||
&self,
|
&self,
|
||||||
windows: &Windows,
|
resolutions: impl IntoIterator<Item = (Entity, &'a Window)>,
|
||||||
images: &Assets<Image>,
|
images: &Assets<Image>,
|
||||||
) -> Option<RenderTargetInfo> {
|
) -> Option<RenderTargetInfo> {
|
||||||
Some(match self {
|
match self {
|
||||||
RenderTarget::Window(window_id) => {
|
NormalizedRenderTarget::Window(window_ref) => resolutions
|
||||||
let window = windows.get(*window_id)?;
|
.into_iter()
|
||||||
RenderTargetInfo {
|
.find(|(entity, _)| *entity == window_ref.entity())
|
||||||
physical_size: UVec2::new(window.physical_width(), window.physical_height()),
|
.map(|(_, window)| RenderTargetInfo {
|
||||||
scale_factor: window.scale_factor(),
|
physical_size: UVec2::new(
|
||||||
}
|
window.resolution.physical_width(),
|
||||||
}
|
window.resolution.physical_height(),
|
||||||
RenderTarget::Image(image_handle) => {
|
),
|
||||||
|
scale_factor: window.resolution.scale_factor(),
|
||||||
|
}),
|
||||||
|
NormalizedRenderTarget::Image(image_handle) => {
|
||||||
let image = images.get(image_handle)?;
|
let image = images.get(image_handle)?;
|
||||||
let Extent3d { width, height, .. } = image.texture_descriptor.size;
|
let Extent3d { width, height, .. } = image.texture_descriptor.size;
|
||||||
RenderTargetInfo {
|
Some(RenderTargetInfo {
|
||||||
physical_size: UVec2::new(width, height),
|
physical_size: UVec2::new(width, height),
|
||||||
scale_factor: 1.0,
|
scale_factor: 1.0,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this render target is contained in the given changed windows or images.
|
// Check if this render target is contained in the given changed windows or images.
|
||||||
fn is_changed(
|
fn is_changed(
|
||||||
&self,
|
&self,
|
||||||
changed_window_ids: &[WindowId],
|
changed_window_ids: &HashSet<Entity>,
|
||||||
changed_image_handles: &HashSet<&Handle<Image>>,
|
changed_image_handles: &HashSet<&Handle<Image>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self {
|
match self {
|
||||||
RenderTarget::Window(window_id) => changed_window_ids.contains(window_id),
|
NormalizedRenderTarget::Window(window_ref) => {
|
||||||
RenderTarget::Image(image_handle) => changed_image_handles.contains(&image_handle),
|
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_resized_events: EventReader<WindowResized>,
|
||||||
mut window_created_events: EventReader<WindowCreated>,
|
mut window_created_events: EventReader<WindowCreated>,
|
||||||
mut image_asset_events: EventReader<AssetEvent<Image>>,
|
mut image_asset_events: EventReader<AssetEvent<Image>>,
|
||||||
windows: Res<Windows>,
|
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||||
|
windows: Query<(Entity, &Window)>,
|
||||||
images: Res<Assets<Image>>,
|
images: Res<Assets<Image>>,
|
||||||
mut cameras: Query<(&mut Camera, &mut T)>,
|
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
|
let mut changed_window_ids = HashSet::new();
|
||||||
for event in window_created_events.iter() {
|
changed_window_ids.extend(window_created_events.iter().map(|event| event.window));
|
||||||
if changed_window_ids.contains(&event.id) {
|
changed_window_ids.extend(window_resized_events.iter().map(|event| event.window));
|
||||||
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 changed_image_handles: HashSet<&Handle<Image>> = image_asset_events
|
let changed_image_handles: HashSet<&Handle<Image>> = image_asset_events
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -472,18 +494,18 @@ pub fn camera_system<T: CameraProjection + Component>(
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|viewport| viewport.physical_size);
|
.map(|viewport| viewport.physical_size);
|
||||||
|
|
||||||
if camera
|
if let Some(normalized_target) = camera.target.normalize(primary_window) {
|
||||||
.target
|
if normalized_target.is_changed(&changed_window_ids, &changed_image_handles)
|
||||||
.is_changed(&changed_window_ids, &changed_image_handles)
|
|| camera.is_added()
|
||||||
|| camera.is_added()
|
|| camera_projection.is_changed()
|
||||||
|| camera_projection.is_changed()
|
|| camera.computed.old_viewport_size != viewport_size
|
||||||
|| camera.computed.old_viewport_size != viewport_size
|
{
|
||||||
{
|
camera.computed.target_info =
|
||||||
camera.computed.target_info = camera.target.get_render_target_info(&windows, &images);
|
normalized_target.get_render_target_info(&windows, &images);
|
||||||
camera.computed.old_viewport_size = viewport_size;
|
if let Some(size) = camera.logical_viewport_size() {
|
||||||
if let Some(size) = camera.logical_viewport_size() {
|
camera_projection.update(size.x, size.y);
|
||||||
camera_projection.update(size.x, size.y);
|
camera.computed.projection_matrix = camera_projection.get_projection_matrix();
|
||||||
camera.computed.projection_matrix = camera_projection.get_projection_matrix();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -491,7 +513,7 @@ pub fn camera_system<T: CameraProjection + Component>(
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
pub struct ExtractedCamera {
|
pub struct ExtractedCamera {
|
||||||
pub target: RenderTarget,
|
pub target: Option<NormalizedRenderTarget>,
|
||||||
pub physical_viewport_size: Option<UVec2>,
|
pub physical_viewport_size: Option<UVec2>,
|
||||||
pub physical_target_size: Option<UVec2>,
|
pub physical_target_size: Option<UVec2>,
|
||||||
pub viewport: Option<Viewport>,
|
pub viewport: Option<Viewport>,
|
||||||
|
@ -510,7 +532,9 @@ pub fn extract_cameras(
|
||||||
&VisibleEntities,
|
&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() {
|
for (entity, camera, camera_render_graph, transform, visible_entities) in query.iter() {
|
||||||
if !camera.is_active {
|
if !camera.is_active {
|
||||||
continue;
|
continue;
|
||||||
|
@ -525,7 +549,7 @@ pub fn extract_cameras(
|
||||||
}
|
}
|
||||||
commands.get_or_spawn(entity).insert((
|
commands.get_or_spawn(entity).insert((
|
||||||
ExtractedCamera {
|
ExtractedCamera {
|
||||||
target: camera.target.clone(),
|
target: camera.target.normalize(primary_window),
|
||||||
viewport: camera.viewport.clone(),
|
viewport: camera.viewport.clone(),
|
||||||
physical_viewport_size: Some(viewport_size),
|
physical_viewport_size: Some(viewport_size),
|
||||||
physical_target_size: Some(target_size),
|
physical_target_size: Some(target_size),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::{ExtractedCamera, RenderTarget},
|
camera::{ExtractedCamera, NormalizedRenderTarget},
|
||||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue},
|
render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue},
|
||||||
renderer::RenderContext,
|
renderer::RenderContext,
|
||||||
view::ExtractedWindows,
|
view::ExtractedWindows,
|
||||||
|
@ -52,8 +52,8 @@ impl Node for CameraDriverNode {
|
||||||
}
|
}
|
||||||
previous_order_target = Some(new_order_target);
|
previous_order_target = Some(new_order_target);
|
||||||
if let Ok((_, camera)) = self.cameras.get_manual(world, entity) {
|
if let Ok((_, camera)) = self.cameras.get_manual(world, entity) {
|
||||||
if let RenderTarget::Window(id) = camera.target {
|
if let Some(NormalizedRenderTarget::Window(window_ref)) = camera.target {
|
||||||
camera_windows.insert(id);
|
camera_windows.insert(window_ref.entity());
|
||||||
}
|
}
|
||||||
graph
|
graph
|
||||||
.run_sub_graph(camera.render_graph.clone(), vec![SlotValue::Entity(entity)])?;
|
.run_sub_graph(camera.render_graph.clone(), vec![SlotValue::Entity(entity)])?;
|
||||||
|
|
|
@ -37,6 +37,7 @@ pub mod prelude {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use bevy_window::{PrimaryWindow, RawHandleWrapper};
|
||||||
use globals::GlobalsPlugin;
|
use globals::GlobalsPlugin;
|
||||||
pub use once_cell;
|
pub use once_cell;
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use bevy_app::{App, AppLabel, Plugin};
|
use bevy_app::{App, AppLabel, Plugin};
|
||||||
use bevy_asset::{AddAsset, AssetServer};
|
use bevy_asset::{AddAsset, AssetServer};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::{prelude::*, system::SystemState};
|
||||||
use bevy_utils::tracing::debug;
|
use bevy_utils::tracing::debug;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
|
@ -138,17 +139,17 @@ impl Plugin for RenderPlugin {
|
||||||
.init_asset_loader::<ShaderLoader>()
|
.init_asset_loader::<ShaderLoader>()
|
||||||
.init_debug_asset_loader::<ShaderLoader>();
|
.init_debug_asset_loader::<ShaderLoader>();
|
||||||
|
|
||||||
if let Some(backends) = self.wgpu_settings.backends {
|
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
|
||||||
let windows = app.world.resource_mut::<bevy_window::Windows>();
|
SystemState::new(&mut app.world);
|
||||||
let instance = wgpu::Instance::new(backends);
|
let primary_window = system_state.get(&app.world);
|
||||||
|
|
||||||
let surface = windows
|
if let Some(backends) = self.wgpu_settings.backends {
|
||||||
.get_primary()
|
let instance = wgpu::Instance::new(backends);
|
||||||
.and_then(|window| window.raw_handle())
|
let surface = primary_window.get_single().ok().map(|wrapper| unsafe {
|
||||||
.map(|wrapper| unsafe {
|
// SAFETY: Plugins should be set up on the main thread.
|
||||||
let handle = wrapper.get_handle();
|
let handle = wrapper.get_handle();
|
||||||
instance.create_surface(&handle)
|
instance.create_surface(&handle)
|
||||||
});
|
});
|
||||||
|
|
||||||
let request_adapter_options = wgpu::RequestAdapterOptions {
|
let request_adapter_options = wgpu::RequestAdapterOptions {
|
||||||
power_preference: self.wgpu_settings.power_preference,
|
power_preference: self.wgpu_settings.power_preference,
|
||||||
|
|
|
@ -285,10 +285,10 @@ fn prepare_view_targets(
|
||||||
) {
|
) {
|
||||||
let mut textures = HashMap::default();
|
let mut textures = HashMap::default();
|
||||||
for (entity, camera, view) in cameras.iter() {
|
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)) = (
|
if let (Some(out_texture_view), Some(out_texture_format)) = (
|
||||||
camera.target.get_texture_view(&windows, &images),
|
target.get_texture_view(&windows, &images),
|
||||||
camera.target.get_texture_format(&windows, &images),
|
target.get_texture_format(&windows, &images),
|
||||||
) {
|
) {
|
||||||
let size = Extent3d {
|
let size = Extent3d {
|
||||||
width: target_size.x,
|
width: target_size.x,
|
||||||
|
|
|
@ -7,7 +7,7 @@ use bevy_app::{App, Plugin};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_utils::{tracing::debug, HashMap, HashSet};
|
use bevy_utils::{tracing::debug, HashMap, HashSet};
|
||||||
use bevy_window::{
|
use bevy_window::{
|
||||||
CompositeAlphaMode, PresentMode, RawHandleWrapper, WindowClosed, WindowId, Windows,
|
CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosed,
|
||||||
};
|
};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use wgpu::TextureFormat;
|
use wgpu::TextureFormat;
|
||||||
|
@ -40,8 +40,9 @@ impl Plugin for WindowRenderPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExtractedWindow {
|
pub struct ExtractedWindow {
|
||||||
pub id: WindowId,
|
/// An entity that contains the components in [`Window`].
|
||||||
pub raw_handle: Option<RawHandleWrapper>,
|
pub entity: Entity,
|
||||||
|
pub handle: RawHandleWrapper,
|
||||||
pub physical_width: u32,
|
pub physical_width: u32,
|
||||||
pub physical_height: u32,
|
pub physical_height: u32,
|
||||||
pub present_mode: PresentMode,
|
pub present_mode: PresentMode,
|
||||||
|
@ -54,11 +55,12 @@ pub struct ExtractedWindow {
|
||||||
|
|
||||||
#[derive(Default, Resource)]
|
#[derive(Default, Resource)]
|
||||||
pub struct ExtractedWindows {
|
pub struct ExtractedWindows {
|
||||||
pub windows: HashMap<WindowId, ExtractedWindow>,
|
pub primary: Option<Entity>,
|
||||||
|
pub windows: HashMap<Entity, ExtractedWindow>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for ExtractedWindows {
|
impl Deref for ExtractedWindows {
|
||||||
type Target = HashMap<WindowId, ExtractedWindow>;
|
type Target = HashMap<Entity, ExtractedWindow>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.windows
|
&self.windows
|
||||||
|
@ -74,36 +76,37 @@ impl DerefMut for ExtractedWindows {
|
||||||
fn extract_windows(
|
fn extract_windows(
|
||||||
mut extracted_windows: ResMut<ExtractedWindows>,
|
mut extracted_windows: ResMut<ExtractedWindows>,
|
||||||
mut closed: Extract<EventReader<WindowClosed>>,
|
mut closed: Extract<EventReader<WindowClosed>>,
|
||||||
windows: Extract<Res<Windows>>,
|
windows: Extract<Query<(Entity, &Window, &RawHandleWrapper, Option<&PrimaryWindow>)>>,
|
||||||
) {
|
) {
|
||||||
for window in windows.iter() {
|
for (entity, window, handle, primary) in windows.iter() {
|
||||||
let (new_width, new_height) = (
|
if primary.is_some() {
|
||||||
window.physical_width().max(1),
|
extracted_windows.primary = Some(entity);
|
||||||
window.physical_height().max(1),
|
}
|
||||||
);
|
|
||||||
let new_present_mode = window.present_mode();
|
|
||||||
|
|
||||||
let mut extracted_window =
|
let (new_width, new_height) = (
|
||||||
extracted_windows
|
window.resolution.physical_width().max(1),
|
||||||
.entry(window.id())
|
window.resolution.physical_height().max(1),
|
||||||
.or_insert(ExtractedWindow {
|
);
|
||||||
id: window.id(),
|
|
||||||
raw_handle: window.raw_handle(),
|
let mut extracted_window = extracted_windows.entry(entity).or_insert(ExtractedWindow {
|
||||||
physical_width: new_width,
|
entity,
|
||||||
physical_height: new_height,
|
handle: handle.clone(),
|
||||||
present_mode: window.present_mode(),
|
physical_width: new_width,
|
||||||
swap_chain_texture: None,
|
physical_height: new_height,
|
||||||
swap_chain_texture_format: None,
|
present_mode: window.present_mode,
|
||||||
size_changed: false,
|
swap_chain_texture: None,
|
||||||
present_mode_changed: false,
|
size_changed: false,
|
||||||
alpha_mode: window.alpha_mode(),
|
swap_chain_texture_format: None,
|
||||||
});
|
present_mode_changed: false,
|
||||||
|
alpha_mode: window.composite_alpha_mode,
|
||||||
|
});
|
||||||
|
|
||||||
// NOTE: Drop the swap chain frame here
|
// NOTE: Drop the swap chain frame here
|
||||||
extracted_window.swap_chain_texture = None;
|
extracted_window.swap_chain_texture = None;
|
||||||
extracted_window.size_changed = new_width != extracted_window.physical_width
|
extracted_window.size_changed = new_width != extracted_window.physical_width
|
||||||
|| new_height != extracted_window.physical_height;
|
|| 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 {
|
if extracted_window.size_changed {
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -120,13 +123,14 @@ fn extract_windows(
|
||||||
if extracted_window.present_mode_changed {
|
if extracted_window.present_mode_changed {
|
||||||
debug!(
|
debug!(
|
||||||
"Window Present Mode changed from {:?} to {:?}",
|
"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() {
|
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)]
|
#[derive(Resource, Default)]
|
||||||
pub struct WindowSurfaces {
|
pub struct WindowSurfaces {
|
||||||
surfaces: HashMap<WindowId, SurfaceData>,
|
surfaces: HashMap<Entity, SurfaceData>,
|
||||||
/// List of windows that we have already called the initial `configure_surface` for
|
/// 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.
|
/// 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_instance: Res<RenderInstance>,
|
||||||
render_adapter: Res<RenderAdapter>,
|
render_adapter: Res<RenderAdapter>,
|
||||||
) {
|
) {
|
||||||
for window in windows
|
for window in windows.windows.values_mut() {
|
||||||
.windows
|
|
||||||
.values_mut()
|
|
||||||
// value of raw_handle is only None in synthetic tests
|
|
||||||
.filter(|x| x.raw_handle.is_some())
|
|
||||||
{
|
|
||||||
let window_surfaces = window_surfaces.deref_mut();
|
let window_surfaces = window_surfaces.deref_mut();
|
||||||
let surface_data = window_surfaces
|
let surface_data = window_surfaces
|
||||||
.surfaces
|
.surfaces
|
||||||
.entry(window.id)
|
.entry(window.entity)
|
||||||
.or_insert_with(|| unsafe {
|
.or_insert_with(|| unsafe {
|
||||||
// NOTE: On some OSes this MUST be called from the main thread.
|
// NOTE: On some OSes this MUST be called from the main thread.
|
||||||
let surface = render_instance
|
let surface = render_instance.create_surface(&window.handle.get_handle());
|
||||||
.create_surface(&window.raw_handle.as_ref().unwrap().get_handle());
|
|
||||||
let format = *surface
|
let format = *surface
|
||||||
.get_supported_formats(&render_adapter)
|
.get_supported_formats(&render_adapter)
|
||||||
.get(0)
|
.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;
|
let surface = &surface_data.surface;
|
||||||
if not_already_configured || window.size_changed || window.present_mode_changed {
|
if not_already_configured || window.size_changed || window.present_mode_changed {
|
||||||
|
|
|
@ -5,6 +5,7 @@ use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
|
prelude::With,
|
||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
system::{Commands, Local, Query, Res, ResMut},
|
system::{Commands, Local, Query, Res, ResMut},
|
||||||
};
|
};
|
||||||
|
@ -19,7 +20,7 @@ use bevy_render::{
|
||||||
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas};
|
use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas};
|
||||||
use bevy_transform::prelude::{GlobalTransform, Transform};
|
use bevy_transform::prelude::{GlobalTransform, Transform};
|
||||||
use bevy_utils::HashSet;
|
use bevy_utils::HashSet;
|
||||||
use bevy_window::{WindowId, WindowScaleFactorChanged, Windows};
|
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline,
|
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline,
|
||||||
|
@ -69,7 +70,7 @@ pub struct Text2dBundle {
|
||||||
pub fn extract_text2d_sprite(
|
pub fn extract_text2d_sprite(
|
||||||
mut extracted_sprites: ResMut<ExtractedSprites>,
|
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
||||||
windows: Extract<Res<Windows>>,
|
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||||
text2d_query: Extract<
|
text2d_query: Extract<
|
||||||
Query<(
|
Query<(
|
||||||
Entity,
|
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
|
for (entity, computed_visibility, text, text_layout_info, anchor, text_transform) in
|
||||||
text2d_query.iter()
|
text2d_query.iter()
|
||||||
|
@ -146,9 +151,9 @@ pub fn update_text2d_layout(
|
||||||
mut queue: Local<HashSet<Entity>>,
|
mut queue: Local<HashSet<Entity>>,
|
||||||
mut textures: ResMut<Assets<Image>>,
|
mut textures: ResMut<Assets<Image>>,
|
||||||
fonts: Res<Assets<Font>>,
|
fonts: Res<Assets<Font>>,
|
||||||
windows: Res<Windows>,
|
|
||||||
text_settings: Res<TextSettings>,
|
text_settings: Res<TextSettings>,
|
||||||
mut font_atlas_warning: ResMut<FontAtlasWarning>,
|
mut font_atlas_warning: ResMut<FontAtlasWarning>,
|
||||||
|
windows: Query<&Window, With<PrimaryWindow>>,
|
||||||
mut scale_factor_changed: EventReader<WindowScaleFactorChanged>,
|
mut scale_factor_changed: EventReader<WindowScaleFactorChanged>,
|
||||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||||
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
|
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`
|
// We need to consume the entire iterator, hence `last`
|
||||||
let factor_changed = scale_factor_changed.iter().last().is_some();
|
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 {
|
for (entity, text, bounds, text_layout_info) in &mut text_query {
|
||||||
if factor_changed || text.is_changed() || queue.remove(&entity) {
|
if factor_changed || text.is_changed() || queue.remove(&entity) {
|
||||||
|
|
|
@ -13,7 +13,7 @@ use bevy_log::warn;
|
||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
use bevy_transform::components::Transform;
|
use bevy_transform::components::Transform;
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
use bevy_window::{Window, WindowId, WindowScaleFactorChanged, Windows};
|
use bevy_window::{PrimaryWindow, Window, WindowResolution, WindowScaleFactorChanged};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use taffy::{
|
use taffy::{
|
||||||
prelude::{AvailableSpace, Size},
|
prelude::{AvailableSpace, Size},
|
||||||
|
@ -23,7 +23,7 @@ use taffy::{
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct FlexSurface {
|
pub struct FlexSurface {
|
||||||
entity_to_taffy: HashMap<Entity, taffy::node::Node>,
|
entity_to_taffy: HashMap<Entity, taffy::node::Node>,
|
||||||
window_nodes: HashMap<WindowId, taffy::node::Node>,
|
window_nodes: HashMap<Entity, taffy::node::Node>,
|
||||||
taffy: Taffy,
|
taffy: Taffy,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ unsafe impl Sync for FlexSurface {}
|
||||||
fn _assert_send_sync_flex_surface_impl_safe() {
|
fn _assert_send_sync_flex_surface_impl_safe() {
|
||||||
fn _assert_send_sync<T: Send + Sync>() {}
|
fn _assert_send_sync<T: Send + Sync>() {}
|
||||||
_assert_send_sync::<HashMap<Entity, taffy::node::Node>>();
|
_assert_send_sync::<HashMap<Entity, taffy::node::Node>>();
|
||||||
_assert_send_sync::<HashMap<WindowId, taffy::node::Node>>();
|
|
||||||
// FIXME https://github.com/DioxusLabs/taffy/issues/146
|
// FIXME https://github.com/DioxusLabs/taffy/issues/146
|
||||||
// _assert_send_sync::<Taffy>();
|
// _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 taffy = &mut self.taffy;
|
||||||
let node = self
|
let node = self
|
||||||
.window_nodes
|
.window_nodes
|
||||||
.entry(window.id())
|
.entry(window)
|
||||||
.or_insert_with(|| taffy.new_leaf(taffy::style::Style::default()).unwrap());
|
.or_insert_with(|| taffy.new_leaf(taffy::style::Style::default()).unwrap());
|
||||||
|
|
||||||
taffy
|
taffy
|
||||||
|
@ -157,8 +156,12 @@ without UI components as a child of an entity with UI components, results may be
|
||||||
*node,
|
*node,
|
||||||
taffy::style::Style {
|
taffy::style::Style {
|
||||||
size: taffy::geometry::Size {
|
size: taffy::geometry::Size {
|
||||||
width: taffy::style::Dimension::Points(window.physical_width() as f32),
|
width: taffy::style::Dimension::Points(
|
||||||
height: taffy::style::Dimension::Points(window.physical_height() as f32),
|
window_resolution.physical_width() as f32
|
||||||
|
),
|
||||||
|
height: taffy::style::Dimension::Points(
|
||||||
|
window_resolution.physical_height() as f32,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..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(
|
pub fn set_window_children(
|
||||||
&mut self,
|
&mut self,
|
||||||
window_id: WindowId,
|
parent_window: Entity,
|
||||||
children: impl Iterator<Item = 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
|
let child_nodes = children
|
||||||
.map(|e| *self.entity_to_taffy.get(&e).unwrap())
|
.map(|e| *self.entity_to_taffy.get(&e).unwrap())
|
||||||
.collect::<Vec<taffy::node::Node>>();
|
.collect::<Vec<taffy::node::Node>>();
|
||||||
|
@ -218,7 +221,8 @@ pub enum FlexError {
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn flex_node_system(
|
pub fn flex_node_system(
|
||||||
windows: Res<Windows>,
|
primary_window: Query<(Entity, &Window), With<PrimaryWindow>>,
|
||||||
|
windows: Query<(Entity, &Window)>,
|
||||||
ui_scale: Res<UiScale>,
|
ui_scale: Res<UiScale>,
|
||||||
mut scale_factor_events: EventReader<WindowScaleFactorChanged>,
|
mut scale_factor_events: EventReader<WindowScaleFactorChanged>,
|
||||||
mut flex_surface: ResMut<FlexSurface>,
|
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>)>,
|
mut node_transform_query: Query<(Entity, &mut Node, &mut Transform, Option<&Parent>)>,
|
||||||
removed_nodes: RemovedComponents<Node>,
|
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
|
// update window root nodes
|
||||||
for window in windows.iter() {
|
for (entity, window) in windows.iter() {
|
||||||
flex_surface.update_window(window);
|
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;
|
let scale_factor = logical_to_physical_factor * ui_scale.scale;
|
||||||
|
|
||||||
fn update_changed<F: ReadOnlyWorldQuery>(
|
fn update_changed<F: ReadOnlyWorldQuery>(
|
||||||
|
@ -273,9 +284,7 @@ pub fn flex_node_system(
|
||||||
flex_surface.remove_entities(&removed_nodes);
|
flex_surface.remove_entities(&removed_nodes);
|
||||||
|
|
||||||
// update window children (for now assuming all Nodes live in the primary window)
|
// 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_entity, root_node_query.iter());
|
||||||
flex_surface.set_window_children(primary_window.id(), root_node_query.iter());
|
|
||||||
}
|
|
||||||
|
|
||||||
// update and remove children
|
// update and remove children
|
||||||
for entity in &removed_children {
|
for entity in &removed_children {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::DetectChangesMut,
|
change_detection::DetectChangesMut,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
prelude::Component,
|
prelude::{Component, With},
|
||||||
query::WorldQuery,
|
query::WorldQuery,
|
||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
system::{Local, Query, Res},
|
system::{Local, Query, Res},
|
||||||
|
@ -11,10 +11,10 @@ use bevy_ecs::{
|
||||||
use bevy_input::{mouse::MouseButton, touch::Touches, Input};
|
use bevy_input::{mouse::MouseButton, touch::Touches, Input};
|
||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
|
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
|
||||||
use bevy_render::camera::{Camera, RenderTarget};
|
use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::ComputedVisibility};
|
||||||
use bevy_render::view::ComputedVisibility;
|
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
use bevy_window::Windows;
|
|
||||||
|
use bevy_window::{PrimaryWindow, Window};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smallvec::SmallVec;
|
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
|
/// 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.
|
/// Entities with a hidden [`ComputedVisibility`] are always treated as released.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn ui_focus_system(
|
pub fn ui_focus_system(
|
||||||
mut state: Local<State>,
|
mut state: Local<State>,
|
||||||
camera: Query<(&Camera, Option<&UiCameraConfig>)>,
|
camera: Query<(&Camera, Option<&UiCameraConfig>)>,
|
||||||
windows: Res<Windows>,
|
windows: Query<&Window>,
|
||||||
mouse_button_input: Res<Input<MouseButton>>,
|
mouse_button_input: Res<Input<MouseButton>>,
|
||||||
touches_input: Res<Touches>,
|
touches_input: Res<Touches>,
|
||||||
ui_stack: Res<UiStack>,
|
ui_stack: Res<UiStack>,
|
||||||
mut node_query: Query<NodeQuery>,
|
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
|
// reset entities that were both clicked and released in the last frame
|
||||||
for entity in state.entities_to_reset.drain(..) {
|
for entity in state.entities_to_reset.drain(..) {
|
||||||
if let Ok(mut interaction) = node_query.get_component_mut::<Interaction>(entity) {
|
if let Ok(mut interaction) = node_query.get_component_mut::<Interaction>(entity) {
|
||||||
|
@ -167,18 +171,20 @@ pub fn ui_focus_system(
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, camera_ui)| !is_ui_disabled(*camera_ui))
|
.filter(|(_, camera_ui)| !is_ui_disabled(*camera_ui))
|
||||||
.filter_map(|(camera, _)| {
|
.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)
|
Some(window_id)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter_map(|window_id| windows.get(window_id))
|
.find_map(|window_ref| {
|
||||||
.filter(|window| window.is_focused())
|
windows.get(window_ref.entity()).ok().and_then(|window| {
|
||||||
.find_map(|window| {
|
window.cursor.position.map(|mut cursor_pos| {
|
||||||
window.cursor_position().map(|mut cursor_pos| {
|
cursor_pos.y = window.height() as f64 - cursor_pos.y;
|
||||||
cursor_pos.y = window.height() - cursor_pos.y;
|
cursor_pos.as_vec2()
|
||||||
cursor_pos
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.or_else(|| touches_input.first_pressed_position());
|
.or_else(|| touches_input.first_pressed_position());
|
||||||
|
|
|
@ -2,6 +2,7 @@ mod pipeline;
|
||||||
mod render_pass;
|
mod render_pass;
|
||||||
|
|
||||||
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
|
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
|
||||||
|
use bevy_window::{PrimaryWindow, Window};
|
||||||
pub use pipeline::*;
|
pub use pipeline::*;
|
||||||
pub use render_pass::*;
|
pub use render_pass::*;
|
||||||
|
|
||||||
|
@ -29,7 +30,6 @@ use bevy_text::{Text, TextLayoutInfo};
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
use bevy_utils::FloatOrd;
|
use bevy_utils::FloatOrd;
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
use bevy_window::{WindowId, Windows};
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
@ -297,7 +297,7 @@ pub fn extract_default_ui_camera_view<T: Component>(
|
||||||
pub fn extract_text_uinodes(
|
pub fn extract_text_uinodes(
|
||||||
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
mut extracted_uinodes: ResMut<ExtractedUiNodes>,
|
||||||
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
texture_atlases: Extract<Res<Assets<TextureAtlas>>>,
|
||||||
windows: Extract<Res<Windows>>,
|
windows: Extract<Query<&Window, With<PrimaryWindow>>>,
|
||||||
ui_stack: Extract<Res<UiStack>>,
|
ui_stack: Extract<Res<UiStack>>,
|
||||||
uinode_query: Extract<
|
uinode_query: Extract<
|
||||||
Query<(
|
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() {
|
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
|
||||||
if let Ok((uinode, global_transform, text, text_layout_info, visibility, clip)) =
|
if let Ok((uinode, global_transform, text, text_layout_info, visibility, clip)) =
|
||||||
uinode_query.get(*entity)
|
uinode_query.get(*entity)
|
||||||
|
|
|
@ -12,7 +12,7 @@ use bevy_text::{
|
||||||
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline,
|
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline,
|
||||||
TextSettings, YAxisOrientation,
|
TextSettings, YAxisOrientation,
|
||||||
};
|
};
|
||||||
use bevy_window::Windows;
|
use bevy_window::{PrimaryWindow, Window};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct QueuedText {
|
pub struct QueuedText {
|
||||||
|
@ -51,7 +51,7 @@ pub fn text_system(
|
||||||
mut last_scale_factor: Local<f64>,
|
mut last_scale_factor: Local<f64>,
|
||||||
mut textures: ResMut<Assets<Image>>,
|
mut textures: ResMut<Assets<Image>>,
|
||||||
fonts: Res<Assets<Font>>,
|
fonts: Res<Assets<Font>>,
|
||||||
windows: Res<Windows>,
|
windows: Query<&Window, With<PrimaryWindow>>,
|
||||||
text_settings: Res<TextSettings>,
|
text_settings: Res<TextSettings>,
|
||||||
mut font_atlas_warning: ResMut<FontAtlasWarning>,
|
mut font_atlas_warning: ResMut<FontAtlasWarning>,
|
||||||
ui_scale: Res<UiScale>,
|
ui_scale: Res<UiScale>,
|
||||||
|
@ -69,13 +69,11 @@ pub fn text_system(
|
||||||
)>,
|
)>,
|
||||||
)>,
|
)>,
|
||||||
) {
|
) {
|
||||||
// TODO: This should support window-independent scale settings.
|
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
|
||||||
// See https://github.com/bevyengine/bevy/issues/5621
|
let scale_factor = windows
|
||||||
let scale_factor = if let Some(window) = windows.get_primary() {
|
.get_single()
|
||||||
window.scale_factor() * ui_scale.scale
|
.map(|window| window.resolution.scale_factor())
|
||||||
} else {
|
.unwrap_or(ui_scale.scale);
|
||||||
ui_scale.scale
|
|
||||||
};
|
|
||||||
|
|
||||||
let inv_scale_factor = 1. / scale_factor;
|
let inv_scale_factor = 1. / scale_factor;
|
||||||
|
|
||||||
|
|
|
@ -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.
|
/// 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).
|
/// 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).
|
/// 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.
|
/// `winit`, in turn, mostly copied cursor types available in the browser.
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
#[derive(Default, Debug, Hash, PartialEq, Eq, Clone, Copy, Reflect, FromReflect)]
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(
|
||||||
|
feature = "serialize",
|
||||||
|
derive(serde::Serialize, serde::Deserialize),
|
||||||
|
reflect(Serialize, Deserialize)
|
||||||
|
)]
|
||||||
|
#[reflect(Debug, PartialEq, Default)]
|
||||||
pub enum CursorIcon {
|
pub enum CursorIcon {
|
||||||
/// The platform-dependent default cursor.
|
/// The platform-dependent default cursor.
|
||||||
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
/// A simple crosshair.
|
/// A simple crosshair.
|
||||||
Crosshair,
|
Crosshair,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::{WindowDescriptor, WindowId};
|
use bevy_ecs::entity::Entity;
|
||||||
use bevy_math::{IVec2, Vec2};
|
use bevy_math::{IVec2, Vec2};
|
||||||
use bevy_reflect::{FromReflect, Reflect};
|
use bevy_reflect::{FromReflect, Reflect};
|
||||||
|
|
||||||
|
@ -16,26 +16,15 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct WindowResized {
|
pub struct WindowResized {
|
||||||
pub id: WindowId,
|
/// Window that has changed.
|
||||||
|
pub window: Entity,
|
||||||
/// The new logical width of the window.
|
/// The new logical width of the window.
|
||||||
pub width: f32,
|
pub width: f32,
|
||||||
/// The new logical height of the window.
|
/// The new logical height of the window.
|
||||||
pub height: f32,
|
pub height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An event that indicates that a new window should be created.
|
// TODO: This would redraw all windows ? If yes, update docs to reflect this
|
||||||
#[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,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An event that indicates the window should redraw, even if its control flow is set to `Wait` and
|
/// An event that indicates the window should redraw, even if its control flow is set to `Wait` and
|
||||||
/// there have been no window events.
|
/// there have been no window events.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)]
|
#[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.
|
/// An event that is sent whenever a new window is created.
|
||||||
///
|
///
|
||||||
/// To create a new window, send a [`CreateWindow`] event - this
|
/// To create a new window, spawn an entity with a [`crate::Window`] on it.
|
||||||
/// event will be sent in the handler for that event.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)]
|
#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)]
|
||||||
#[reflect(Debug, PartialEq)]
|
#[reflect(Debug, PartialEq)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
|
@ -59,20 +47,20 @@ pub struct RequestRedraw;
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct WindowCreated {
|
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
|
/// 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.
|
/// 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
|
/// 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 disable this behaviour, set `close_when_requested` on the [`WindowPlugin`]
|
||||||
/// to `false`.
|
/// to `false`.
|
||||||
///
|
///
|
||||||
/// [`WindowPlugin`]: crate::WindowPlugin
|
/// [`WindowPlugin`]: crate::WindowPlugin
|
||||||
/// [`Window`]: crate::Window
|
/// [`Window`]: crate::Window
|
||||||
/// [closing]: crate::Window::close
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)]
|
#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)]
|
||||||
#[reflect(Debug, PartialEq)]
|
#[reflect(Debug, PartialEq)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
|
@ -81,13 +69,12 @@ pub struct WindowCreated {
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct WindowCloseRequested {
|
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
|
/// An event that is sent whenever a window is closed. This will be sent when
|
||||||
/// handler for [`Window::close`].
|
/// the window entity loses its `Window` component or is despawned.
|
||||||
///
|
|
||||||
/// [`Window::close`]: crate::Window::close
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)]
|
#[derive(Debug, Clone, PartialEq, Eq, Reflect, FromReflect)]
|
||||||
#[reflect(Debug, PartialEq)]
|
#[reflect(Debug, PartialEq)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
|
@ -96,10 +83,13 @@ pub struct WindowCloseRequested {
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct WindowClosed {
|
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 inside a window.
|
||||||
/// An event reporting that the mouse cursor has moved on a window.
|
|
||||||
///
|
///
|
||||||
/// The event is sent only if the cursor is over one of the application's windows.
|
/// 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.
|
/// It is the translated version of [`WindowEvent::CursorMoved`] from the `winit` crate.
|
||||||
|
@ -116,10 +106,9 @@ pub struct WindowClosed {
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct CursorMoved {
|
pub struct CursorMoved {
|
||||||
/// The identifier of the window the cursor has moved on.
|
/// Window that the cursor moved inside.
|
||||||
pub id: WindowId,
|
pub window: Entity,
|
||||||
|
/// The cursor position in logical pixels.
|
||||||
/// The position of the cursor, in window coordinates.
|
|
||||||
pub position: Vec2,
|
pub position: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +121,8 @@ pub struct CursorMoved {
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct CursorEntered {
|
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.
|
/// An event that is sent whenever the user's cursor leaves a window.
|
||||||
|
@ -144,7 +134,8 @@ pub struct CursorEntered {
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct CursorLeft {
|
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.
|
/// 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)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct ReceivedCharacter {
|
pub struct ReceivedCharacter {
|
||||||
pub id: WindowId,
|
/// Window that received the character.
|
||||||
|
pub window: Entity,
|
||||||
|
/// Received character.
|
||||||
pub char: char,
|
pub char: char,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +162,9 @@ pub struct ReceivedCharacter {
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct WindowFocused {
|
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,
|
pub focused: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +177,9 @@ pub struct WindowFocused {
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct WindowScaleFactorChanged {
|
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,
|
pub scale_factor: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +192,9 @@ pub struct WindowScaleFactorChanged {
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct WindowBackendScaleFactorChanged {
|
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,
|
pub scale_factor: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,11 +207,27 @@ pub struct WindowBackendScaleFactorChanged {
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub enum FileDragAndDrop {
|
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.
|
/// An event that is sent when a window is repositioned in physical pixels.
|
||||||
|
@ -224,6 +239,8 @@ pub enum FileDragAndDrop {
|
||||||
reflect(Serialize, Deserialize)
|
reflect(Serialize, Deserialize)
|
||||||
)]
|
)]
|
||||||
pub struct WindowMoved {
|
pub struct WindowMoved {
|
||||||
pub id: WindowId,
|
/// Window that moved.
|
||||||
|
pub entity: Entity,
|
||||||
|
/// Where the window moved to in physical pixels.
|
||||||
pub position: IVec2,
|
pub position: IVec2,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,34 +4,33 @@ mod event;
|
||||||
mod raw_handle;
|
mod raw_handle;
|
||||||
mod system;
|
mod system;
|
||||||
mod window;
|
mod window;
|
||||||
mod windows;
|
|
||||||
|
|
||||||
pub use crate::raw_handle::*;
|
pub use crate::raw_handle::*;
|
||||||
|
|
||||||
pub use cursor::*;
|
pub use cursor::*;
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
pub use system::*;
|
pub use system::*;
|
||||||
pub use window::*;
|
pub use window::*;
|
||||||
pub use windows::*;
|
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
CursorEntered, CursorIcon, CursorLeft, CursorMoved, FileDragAndDrop, MonitorSelection,
|
CursorEntered, CursorIcon, CursorLeft, CursorMoved, FileDragAndDrop, MonitorSelection,
|
||||||
ReceivedCharacter, Window, WindowDescriptor, WindowMode, WindowMoved, WindowPlugin,
|
ReceivedCharacter, Window, WindowMoved, WindowPlugin, WindowPosition,
|
||||||
WindowPosition, Windows,
|
WindowResizeConstraints,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
|
||||||
use bevy_ecs::schedule::{IntoSystemDescriptor, SystemLabel};
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use bevy_app::prelude::*;
|
||||||
|
use bevy_ecs::schedule::SystemLabel;
|
||||||
|
|
||||||
impl Default for WindowPlugin {
|
impl Default for WindowPlugin {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
WindowPlugin {
|
WindowPlugin {
|
||||||
window: Default::default(),
|
primary_window: Some(Window::default()),
|
||||||
add_primary_window: true,
|
exit_condition: ExitCondition::OnAllClosed,
|
||||||
exit_on_all_closed: true,
|
|
||||||
close_when_requested: true,
|
close_when_requested: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,21 +38,26 @@ impl Default for WindowPlugin {
|
||||||
|
|
||||||
/// A [`Plugin`] that defines an interface for windowing support in Bevy.
|
/// A [`Plugin`] that defines an interface for windowing support in Bevy.
|
||||||
pub struct WindowPlugin {
|
pub struct WindowPlugin {
|
||||||
pub window: WindowDescriptor,
|
/// Settings for the primary window. This will be spawned by
|
||||||
/// Whether to create a window when added.
|
/// 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,
|
/// Note that if there are no windows, by default the App will exit,
|
||||||
/// due to [`exit_on_all_closed`].
|
/// 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.
|
/// Whether to exit the app when there are no open windows.
|
||||||
///
|
///
|
||||||
/// If disabling this, ensure that you send the [`bevy_app::AppExit`]
|
/// If disabling this, ensure that you send the [`bevy_app::AppExit`]
|
||||||
/// event when the app should exit. If this does not occur, you will
|
/// event when the app should exit. If this does not occur, you will
|
||||||
/// create 'headless' processes (processes without windows), which may
|
/// 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`].
|
/// [`ExitCondition::OnAllClosed`] will add [`exit_on_all_closed`] to [`CoreStage::Update`].
|
||||||
pub exit_on_all_closed: bool,
|
/// [`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.
|
/// Whether to close windows when they are requested to be closed (i.e.
|
||||||
/// when the close button is pressed).
|
/// when the close button is pressed).
|
||||||
///
|
///
|
||||||
|
@ -65,8 +69,8 @@ pub struct WindowPlugin {
|
||||||
|
|
||||||
impl Plugin for WindowPlugin {
|
impl Plugin for WindowPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
|
// User convenience events
|
||||||
app.add_event::<WindowResized>()
|
app.add_event::<WindowResized>()
|
||||||
.add_event::<CreateWindow>()
|
|
||||||
.add_event::<WindowCreated>()
|
.add_event::<WindowCreated>()
|
||||||
.add_event::<WindowClosed>()
|
.add_event::<WindowClosed>()
|
||||||
.add_event::<WindowCloseRequested>()
|
.add_event::<WindowCloseRequested>()
|
||||||
|
@ -79,29 +83,30 @@ impl Plugin for WindowPlugin {
|
||||||
.add_event::<WindowScaleFactorChanged>()
|
.add_event::<WindowScaleFactorChanged>()
|
||||||
.add_event::<WindowBackendScaleFactorChanged>()
|
.add_event::<WindowBackendScaleFactorChanged>()
|
||||||
.add_event::<FileDragAndDrop>()
|
.add_event::<FileDragAndDrop>()
|
||||||
.add_event::<WindowMoved>()
|
.add_event::<WindowMoved>();
|
||||||
.init_resource::<Windows>();
|
|
||||||
|
|
||||||
if self.add_primary_window {
|
if let Some(primary_window) = &self.primary_window {
|
||||||
app.world.send_event(CreateWindow {
|
app.world
|
||||||
id: WindowId::primary(),
|
.spawn(primary_window.clone())
|
||||||
descriptor: self.window.clone(),
|
.insert(PrimaryWindow);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.exit_on_all_closed {
|
match self.exit_condition {
|
||||||
app.add_system_to_stage(
|
ExitCondition::OnPrimaryClosed => {
|
||||||
CoreStage::PostUpdate,
|
app.add_system(exit_on_primary_closed);
|
||||||
exit_on_all_closed.after(ModifiesWindows),
|
}
|
||||||
);
|
ExitCondition::OnAllClosed => {
|
||||||
|
app.add_system(exit_on_all_closed);
|
||||||
|
}
|
||||||
|
ExitCondition::DontExit => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.close_when_requested {
|
if self.close_when_requested {
|
||||||
app.add_system(close_when_requested);
|
app.add_system_to_stage(CoreStage::First, close_when_requested);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register event types
|
// Register event types
|
||||||
app.register_type::<WindowResized>()
|
app.register_type::<WindowResized>()
|
||||||
.register_type::<CreateWindow>()
|
|
||||||
.register_type::<RequestRedraw>()
|
.register_type::<RequestRedraw>()
|
||||||
.register_type::<WindowCreated>()
|
.register_type::<WindowCreated>()
|
||||||
.register_type::<WindowCloseRequested>()
|
.register_type::<WindowCloseRequested>()
|
||||||
|
@ -117,18 +122,41 @@ impl Plugin for WindowPlugin {
|
||||||
.register_type::<WindowMoved>();
|
.register_type::<WindowMoved>();
|
||||||
|
|
||||||
// Register window descriptor and related types
|
// Register window descriptor and related types
|
||||||
app.register_type::<WindowId>()
|
app.register_type::<Window>()
|
||||||
.register_type::<PresentMode>()
|
.register_type::<Cursor>()
|
||||||
.register_type::<WindowResizeConstraints>()
|
.register_type::<WindowResolution>()
|
||||||
.register_type::<WindowMode>()
|
|
||||||
.register_type::<WindowPosition>()
|
.register_type::<WindowPosition>()
|
||||||
|
.register_type::<WindowMode>()
|
||||||
|
.register_type::<PresentMode>()
|
||||||
|
.register_type::<InternalWindowState>()
|
||||||
.register_type::<MonitorSelection>()
|
.register_type::<MonitorSelection>()
|
||||||
.register_type::<WindowDescriptor>();
|
.register_type::<WindowResizeConstraints>();
|
||||||
|
|
||||||
// Register `PathBuf` as it's used by `FileDragAndDrop`
|
// Register `PathBuf` as it's used by `FileDragAndDrop`
|
||||||
app.register_type::<PathBuf>();
|
app.register_type::<PathBuf>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// System Label marking when changes are applied to windows
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
|
||||||
pub struct ModifiesWindows;
|
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,
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use bevy_ecs::prelude::Component;
|
||||||
use raw_window_handle::{
|
use raw_window_handle::{
|
||||||
HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
|
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,
|
/// 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`])
|
/// and so we cannot simply make it (or any type that has a safe operation to get a [`RawWindowHandle`] or [`RawDisplayHandle`])
|
||||||
/// thread-safe.
|
/// thread-safe.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Component)]
|
||||||
pub struct RawHandleWrapper {
|
pub struct RawHandleWrapper {
|
||||||
pub window_handle: RawWindowHandle,
|
pub window_handle: RawWindowHandle,
|
||||||
pub display_handle: RawDisplayHandle,
|
pub display_handle: RawDisplayHandle,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Window, WindowCloseRequested, Windows};
|
use crate::{PrimaryWindow, Window, WindowCloseRequested};
|
||||||
|
|
||||||
use bevy_app::AppExit;
|
use bevy_app::AppExit;
|
||||||
use bevy_ecs::prelude::*;
|
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.
|
/// Ensure that you read the caveats documented on that field if doing so.
|
||||||
///
|
///
|
||||||
/// [`WindowPlugin`]: crate::WindowPlugin
|
/// [`WindowPlugin`]: crate::WindowPlugin
|
||||||
pub fn exit_on_all_closed(mut app_exit_events: EventWriter<AppExit>, windows: Res<Windows>) {
|
pub fn exit_on_all_closed(mut app_exit_events: EventWriter<AppExit>, windows: Query<&Window>) {
|
||||||
if windows.iter().count() == 0 {
|
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);
|
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.
|
/// Ensure that you read the caveats documented on that field if doing so.
|
||||||
///
|
///
|
||||||
/// [`WindowPlugin`]: crate::WindowPlugin
|
/// [`WindowPlugin`]: crate::WindowPlugin
|
||||||
pub fn close_when_requested(
|
pub fn close_when_requested(mut commands: Commands, mut closed: EventReader<WindowCloseRequested>) {
|
||||||
mut windows: ResMut<Windows>,
|
|
||||||
mut closed: EventReader<WindowCloseRequested>,
|
|
||||||
) {
|
|
||||||
for event in closed.iter() {
|
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
|
/// Close the focused window whenever the escape key (<kbd>Esc</kbd>) is pressed
|
||||||
///
|
///
|
||||||
/// This is useful for examples or prototyping.
|
/// This is useful for examples or prototyping.
|
||||||
pub fn close_on_esc(mut windows: ResMut<Windows>, input: Res<Input<KeyCode>>) {
|
pub fn close_on_esc(
|
||||||
if input.just_pressed(KeyCode::Escape) {
|
mut commands: Commands,
|
||||||
if let Some(window) = windows.get_focused_mut() {
|
focused_windows: Query<(Entity, &Window)>,
|
||||||
window.close();
|
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
|
@ -33,7 +33,7 @@ pub fn convert_mouse_button(mouse_button: winit::event::MouseButton) -> MouseBut
|
||||||
|
|
||||||
pub fn convert_touch_input(
|
pub fn convert_touch_input(
|
||||||
touch_input: winit::event::Touch,
|
touch_input: winit::event::Touch,
|
||||||
location: winit::dpi::LogicalPosition<f32>,
|
location: winit::dpi::LogicalPosition<f64>,
|
||||||
) -> TouchInput {
|
) -> TouchInput {
|
||||||
TouchInput {
|
TouchInput {
|
||||||
phase: match touch_input.phase {
|
phase: match touch_input.phase {
|
||||||
|
@ -42,7 +42,7 @@ pub fn convert_touch_input(
|
||||||
winit::event::TouchPhase::Ended => TouchPhase::Ended,
|
winit::event::TouchPhase::Ended => TouchPhase::Ended,
|
||||||
winit::event::TouchPhase::Cancelled => TouchPhase::Cancelled,
|
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 {
|
force: touch_input.force.map(|f| match f {
|
||||||
winit::event::Force::Calibrated {
|
winit::event::Force::Calibrated {
|
||||||
force,
|
force,
|
||||||
|
|
|
@ -1,266 +1,112 @@
|
||||||
mod converters;
|
mod converters;
|
||||||
|
mod system;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
mod web_resize;
|
mod web_resize;
|
||||||
mod winit_config;
|
mod winit_config;
|
||||||
mod winit_windows;
|
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_config::*;
|
||||||
pub use winit_windows::*;
|
pub use winit_windows::*;
|
||||||
|
|
||||||
use bevy_app::{App, AppExit, CoreStage, Plugin};
|
use bevy_app::{App, AppExit, CoreStage, Plugin};
|
||||||
|
use bevy_ecs::event::{Events, ManualEventReader};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_ecs::{
|
use bevy_input::{
|
||||||
event::{Events, ManualEventReader},
|
keyboard::KeyboardInput,
|
||||||
world::World,
|
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
|
||||||
|
touch::TouchInput,
|
||||||
};
|
};
|
||||||
use bevy_input::mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel};
|
use bevy_math::{ivec2, DVec2, Vec2};
|
||||||
use bevy_math::{ivec2, DVec2, UVec2, Vec2};
|
|
||||||
use bevy_utils::{
|
use bevy_utils::{
|
||||||
tracing::{error, info, trace, warn},
|
tracing::{trace, warn},
|
||||||
Instant,
|
Instant,
|
||||||
};
|
};
|
||||||
use bevy_window::{
|
use bevy_window::{
|
||||||
CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ModifiesWindows,
|
CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ModifiesWindows, ReceivedCharacter,
|
||||||
ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested,
|
RequestRedraw, Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated,
|
||||||
WindowClosed, WindowCreated, WindowFocused, WindowMoved, WindowResized,
|
WindowFocused, WindowMoved, WindowResized, WindowScaleFactorChanged,
|
||||||
WindowScaleFactorChanged, Windows,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use winit::{
|
use winit::{
|
||||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition},
|
|
||||||
event::{self, DeviceEvent, Event, StartCause, WindowEvent},
|
event::{self, DeviceEvent, Event, StartCause, WindowEvent},
|
||||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::system::WinitWindowInfo;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
use crate::web_resize::{CanvasParentResizeEventChannel, CanvasParentResizePlugin};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct WinitPlugin;
|
pub struct WinitPlugin;
|
||||||
|
|
||||||
impl Plugin for WinitPlugin {
|
impl Plugin for WinitPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
app.insert_non_send_resource(event_loop);
|
||||||
|
|
||||||
app.init_non_send_resource::<WinitWindows>()
|
app.init_non_send_resource::<WinitWindows>()
|
||||||
.init_resource::<WinitSettings>()
|
.init_resource::<WinitSettings>()
|
||||||
.set_runner(winit_runner)
|
.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")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
app.add_plugin(web_resize::CanvasParentResizePlugin);
|
app.add_plugin(CanvasParentResizePlugin);
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let mut create_window_reader = WinitCreateWindowReader::default();
|
let mut create_window_system_state: SystemState<(
|
||||||
#[cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))]
|
Commands,
|
||||||
let create_window_reader = WinitCreateWindowReader::default();
|
NonSendMut<EventLoop<()>>,
|
||||||
// Note that we create a window here "early" because WASM/WebGL requires the window to exist prior to initializing
|
Query<(Entity, &mut Window)>,
|
||||||
// the renderer.
|
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
|
// And for ios and macos, we should not create window early, all ui related code should be executed inside
|
||||||
// UIApplicationMain/NSApplicationMain.
|
// UIApplicationMain/NSApplicationMain.
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))]
|
#[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)
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
.insert_non_send_resource(event_loop);
|
let (commands, event_loop, mut new_windows, event_writer, winit_windows) =
|
||||||
}
|
create_window_system_state.get_mut(&mut app.world);
|
||||||
}
|
|
||||||
|
|
||||||
fn change_window(
|
#[cfg(target_arch = "wasm32")]
|
||||||
mut winit_windows: NonSendMut<WinitWindows>,
|
let (commands, event_loop, mut new_windows, event_writer, winit_windows, event_channel) =
|
||||||
mut windows: ResMut<Windows>,
|
create_window_system_state.get_mut(&mut app.world);
|
||||||
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();
|
|
||||||
|
|
||||||
use bevy_window::MonitorSelection::*;
|
// Here we need to create a winit-window and give it a WindowHandle which the renderer can use.
|
||||||
let maybe_monitor = match monitor_selection {
|
// It needs to be spawned before the start of the startup-stage, so we cannot use a regular system.
|
||||||
Current => window.current_monitor(),
|
// Instead we need to create the window and spawn it using direct world access
|
||||||
Primary => window.primary_monitor(),
|
create_window(
|
||||||
Index(i) => window.available_monitors().nth(i),
|
commands,
|
||||||
};
|
&event_loop,
|
||||||
if let Some(monitor) = maybe_monitor {
|
new_windows.iter_mut(),
|
||||||
let monitor_position = DVec2::from(<(_, _)>::from(monitor.position()));
|
event_writer,
|
||||||
let position = monitor_position + position.as_dvec2();
|
winit_windows,
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
window.set_outer_position(LogicalPosition::new(position.x, position.y));
|
event_channel,
|
||||||
} 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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create_window_system_state.apply(&mut app.world);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,8 +153,30 @@ where
|
||||||
panic!("Run return is not supported on this platform!")
|
panic!("Run return is not supported on this platform!")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn winit_runner(app: App) {
|
#[derive(SystemParam)]
|
||||||
winit_runner_with(app);
|
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(
|
// #[cfg(any(
|
||||||
|
@ -347,19 +215,13 @@ impl Default for WinitPersistentState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Resource)]
|
pub fn winit_runner(mut app: App) {
|
||||||
struct WinitCreateWindowReader(ManualEventReader<CreateWindow>);
|
// We remove this so that we have ownership over it.
|
||||||
|
|
||||||
pub fn winit_runner_with(mut app: App) {
|
|
||||||
let mut event_loop = app
|
let mut event_loop = app
|
||||||
.world
|
.world
|
||||||
.remove_non_send_resource::<EventLoop<()>>()
|
.remove_non_send_resource::<EventLoop<()>>()
|
||||||
.unwrap();
|
.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 app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
||||||
let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
|
let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
|
||||||
let mut winit_state = WinitPersistentState::default();
|
let mut winit_state = WinitPersistentState::default();
|
||||||
|
@ -370,23 +232,80 @@ pub fn winit_runner_with(mut app: App) {
|
||||||
|
|
||||||
trace!("Entering winit event loop");
|
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<()>,
|
let event_handler = move |event: Event<()>,
|
||||||
event_loop: &EventLoopWindowTarget<()>,
|
event_loop: &EventLoopWindowTarget<()>,
|
||||||
control_flow: &mut ControlFlow| {
|
control_flow: &mut ControlFlow| {
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
let _span = bevy_utils::tracing::info_span!("winit event_handler").entered();
|
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 {
|
match event {
|
||||||
event::Event::NewEvents(start) => {
|
event::Event::NewEvents(start) => {
|
||||||
let winit_config = app.world.resource::<WinitSettings>();
|
let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
|
||||||
let windows = app.world.resource::<Windows>();
|
|
||||||
let focused = windows.iter().any(|w| w.is_focused());
|
let app_focused = window_focused_query.iter().any(|window| window.focused);
|
||||||
|
|
||||||
// Check if either the `WaitUntil` timeout was triggered by winit, or that same
|
// 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
|
// 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
|
// because we don't know if the criteria for an app update were met until the end of
|
||||||
// the frame.
|
// the frame.
|
||||||
let auto_timeout_reached = matches!(start, StartCause::ResumeTimeReached { .. });
|
let auto_timeout_reached = matches!(start, StartCause::ResumeTimeReached { .. });
|
||||||
let now = Instant::now();
|
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::Continuous => false,
|
||||||
UpdateMode::Reactive { max_wait }
|
UpdateMode::Reactive { max_wait }
|
||||||
| UpdateMode::ReactiveLowPower { max_wait } => {
|
| UpdateMode::ReactiveLowPower { max_wait } => {
|
||||||
|
@ -402,81 +321,120 @@ pub fn winit_runner_with(mut app: App) {
|
||||||
window_id: winit_window_id,
|
window_id: winit_window_id,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let world = app.world.cell();
|
// Fetch and prepare details from the world
|
||||||
let winit_windows = world.non_send_resource_mut::<WinitWindows>();
|
let mut system_state: SystemState<(
|
||||||
let mut windows = world.resource_mut::<Windows>();
|
NonSend<WinitWindows>,
|
||||||
let window_id =
|
Query<(&mut Window, &mut WinitWindowInfo)>,
|
||||||
if let Some(window_id) = winit_windows.get_window_id(winit_window_id) {
|
WindowEvents,
|
||||||
window_id
|
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 {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
"Skipped event for unknown winit Window Id {:?}",
|
"Skipped event {:?} for unknown winit Window Id {:?}",
|
||||||
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;
|
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;
|
winit_state.low_power_event = true;
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
WindowEvent::Resized(size) => {
|
WindowEvent::Resized(size) => {
|
||||||
window.update_actual_size_from_backend(size.width, size.height);
|
window
|
||||||
world.send_event(WindowResized {
|
.resolution
|
||||||
id: window_id,
|
.set_physical_resolution(size.width, size.height);
|
||||||
|
info.last_winit_size = size;
|
||||||
|
|
||||||
|
window_events.window_resized.send(WindowResized {
|
||||||
|
window: window_entity,
|
||||||
width: window.width(),
|
width: window.width(),
|
||||||
height: window.height(),
|
height: window.height(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::CloseRequested => {
|
WindowEvent::CloseRequested => {
|
||||||
world.send_event(WindowCloseRequested { id: window_id });
|
window_events
|
||||||
|
.window_close_requested
|
||||||
|
.send(WindowCloseRequested {
|
||||||
|
window: window_entity,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::KeyboardInput { ref input, .. } => {
|
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, .. } => {
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
let winit_window = winit_windows.get_window(window_id).unwrap();
|
let physical_position = DVec2::new(
|
||||||
let inner_size = winit_window.inner_size();
|
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
|
window.cursor.position = Some(physical_position);
|
||||||
let y_position = inner_size.height as f64 - position.y;
|
|
||||||
|
|
||||||
let physical_position = DVec2::new(position.x, y_position);
|
cursor_events.cursor_moved.send(CursorMoved {
|
||||||
window
|
window: window_entity,
|
||||||
.update_cursor_physical_position_from_backend(Some(physical_position));
|
position: (physical_position / window.resolution.scale_factor())
|
||||||
|
.as_vec2(),
|
||||||
world.send_event(CursorMoved {
|
|
||||||
id: window_id,
|
|
||||||
position: (physical_position / window.scale_factor()).as_vec2(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::CursorEntered { .. } => {
|
WindowEvent::CursorEntered { .. } => {
|
||||||
world.send_event(CursorEntered { id: window_id });
|
cursor_events.cursor_entered.send(CursorEntered {
|
||||||
|
window: window_entity,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::CursorLeft { .. } => {
|
WindowEvent::CursorLeft { .. } => {
|
||||||
window.update_cursor_physical_position_from_backend(None);
|
// Component
|
||||||
world.send_event(CursorLeft { id: window_id });
|
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, .. } => {
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
world.send_event(MouseButtonInput {
|
input_events.mouse_button_input.send(MouseButtonInput {
|
||||||
button: converters::convert_mouse_button(button),
|
button: converters::convert_mouse_button(button),
|
||||||
state: converters::convert_element_state(state),
|
state: converters::convert_element_state(state),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::MouseWheel { delta, .. } => match delta {
|
WindowEvent::MouseWheel { delta, .. } => match delta {
|
||||||
event::MouseScrollDelta::LineDelta(x, y) => {
|
event::MouseScrollDelta::LineDelta(x, y) => {
|
||||||
world.send_event(MouseWheel {
|
input_events.mouse_wheel_input.send(MouseWheel {
|
||||||
unit: MouseScrollUnit::Line,
|
unit: MouseScrollUnit::Line,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
event::MouseScrollDelta::PixelDelta(p) => {
|
event::MouseScrollDelta::PixelDelta(p) => {
|
||||||
world.send_event(MouseWheel {
|
input_events.mouse_wheel_input.send(MouseWheel {
|
||||||
unit: MouseScrollUnit::Pixel,
|
unit: MouseScrollUnit::Pixel,
|
||||||
x: p.x as f32,
|
x: p.x as f32,
|
||||||
y: p.y as f32,
|
y: p.y as f32,
|
||||||
|
@ -484,12 +442,23 @@ pub fn winit_runner_with(mut app: App) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
WindowEvent::Touch(touch) => {
|
WindowEvent::Touch(touch) => {
|
||||||
let location = touch.location.to_logical(window.scale_factor());
|
let mut location =
|
||||||
world.send_event(converters::convert_touch_input(touch, 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) => {
|
WindowEvent::ReceivedCharacter(c) => {
|
||||||
world.send_event(ReceivedCharacter {
|
input_events.character_input.send(ReceivedCharacter {
|
||||||
id: window_id,
|
window: window_entity,
|
||||||
char: c,
|
char: c,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -497,73 +466,84 @@ pub fn winit_runner_with(mut app: App) {
|
||||||
scale_factor,
|
scale_factor,
|
||||||
new_inner_size,
|
new_inner_size,
|
||||||
} => {
|
} => {
|
||||||
world.send_event(WindowBackendScaleFactorChanged {
|
window_events.window_backend_scale_factor_changed.send(
|
||||||
id: window_id,
|
WindowBackendScaleFactorChanged {
|
||||||
scale_factor,
|
window: window_entity,
|
||||||
});
|
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() {
|
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
|
// If there is a scale factor override, then force that to be used
|
||||||
// Otherwise, use the OS suggested size
|
// Otherwise, use the OS suggested size
|
||||||
// We have already told the OS about our resize constraints, so
|
// We have already told the OS about our resize constraints, so
|
||||||
// the new_inner_size should take those into account
|
// the new_inner_size should take those into account
|
||||||
*new_inner_size = winit::dpi::LogicalSize::new(
|
*new_inner_size =
|
||||||
window.requested_width(),
|
winit::dpi::LogicalSize::new(window.width(), window.height())
|
||||||
window.requested_height(),
|
.to_physical::<u32>(forced_factor);
|
||||||
)
|
// TODO: Should this not trigger a WindowsScaleFactorChanged?
|
||||||
.to_physical::<u32>(forced_factor);
|
|
||||||
} else if approx::relative_ne!(new_factor, prior_factor) {
|
} else if approx::relative_ne!(new_factor, prior_factor) {
|
||||||
world.send_event(WindowScaleFactorChanged {
|
// Trigger a change event if they are approximately different
|
||||||
id: window_id,
|
window_events.window_scale_factor_changed.send(
|
||||||
scale_factor,
|
WindowScaleFactorChanged {
|
||||||
});
|
window: window_entity,
|
||||||
|
scale_factor,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_logical_width = new_inner_size.width as f64 / new_factor;
|
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;
|
let new_logical_height = (new_inner_size.height as f64 / new_factor) as f32;
|
||||||
if approx::relative_ne!(window.width() as f64, new_logical_width)
|
if approx::relative_ne!(window.width(), new_logical_width)
|
||||||
|| approx::relative_ne!(window.height() as f64, new_logical_height)
|
|| approx::relative_ne!(window.height(), new_logical_height)
|
||||||
{
|
{
|
||||||
world.send_event(WindowResized {
|
window_events.window_resized.send(WindowResized {
|
||||||
id: window_id,
|
window: window_entity,
|
||||||
width: new_logical_width as f32,
|
width: new_logical_width,
|
||||||
height: new_logical_height as f32,
|
height: new_logical_height,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
window.update_actual_size_from_backend(
|
window
|
||||||
new_inner_size.width,
|
.resolution
|
||||||
new_inner_size.height,
|
.set_physical_resolution(new_inner_size.width, new_inner_size.height);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
WindowEvent::Focused(focused) => {
|
WindowEvent::Focused(focused) => {
|
||||||
window.update_focused_status_from_backend(focused);
|
// Component
|
||||||
world.send_event(WindowFocused {
|
window.focused = focused;
|
||||||
id: window_id,
|
|
||||||
|
window_events.window_focused.send(WindowFocused {
|
||||||
|
window: window_entity,
|
||||||
focused,
|
focused,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::DroppedFile(path_buf) => {
|
WindowEvent::DroppedFile(path_buf) => {
|
||||||
world.send_event(FileDragAndDrop::DroppedFile {
|
file_drag_and_drop_events.send(FileDragAndDrop::DroppedFile {
|
||||||
id: window_id,
|
window: window_entity,
|
||||||
path_buf,
|
path_buf,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::HoveredFile(path_buf) => {
|
WindowEvent::HoveredFile(path_buf) => {
|
||||||
world.send_event(FileDragAndDrop::HoveredFile {
|
file_drag_and_drop_events.send(FileDragAndDrop::HoveredFile {
|
||||||
id: window_id,
|
window: window_entity,
|
||||||
path_buf,
|
path_buf,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::HoveredFileCancelled => {
|
WindowEvent::HoveredFileCancelled => {
|
||||||
world.send_event(FileDragAndDrop::HoveredFileCancelled { id: window_id });
|
file_drag_and_drop_events.send(FileDragAndDrop::HoveredFileCancelled {
|
||||||
|
window: window_entity,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
WindowEvent::Moved(position) => {
|
WindowEvent::Moved(position) => {
|
||||||
let position = ivec2(position.x, position.y);
|
let position = ivec2(position.x, position.y);
|
||||||
window.update_actual_position_from_backend(position);
|
|
||||||
world.send_event(WindowMoved {
|
window.position.set(position);
|
||||||
id: window_id,
|
|
||||||
|
window_events.window_moved.send(WindowMoved {
|
||||||
|
entity: window_entity,
|
||||||
position,
|
position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -574,8 +554,12 @@ pub fn winit_runner_with(mut app: App) {
|
||||||
event: DeviceEvent::MouseMotion { delta: (x, y) },
|
event: DeviceEvent::MouseMotion { delta: (x, y) },
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
app.world.send_event(MouseMotion {
|
let mut system_state: SystemState<EventWriter<MouseMotion>> =
|
||||||
delta: DVec2 { x, y }.as_vec2(),
|
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 => {
|
event::Event::Suspended => {
|
||||||
|
@ -585,16 +569,12 @@ pub fn winit_runner_with(mut app: App) {
|
||||||
winit_state.active = true;
|
winit_state.active = true;
|
||||||
}
|
}
|
||||||
event::Event::MainEventsCleared => {
|
event::Event::MainEventsCleared => {
|
||||||
handle_create_window_events(
|
let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
|
||||||
&mut app.world,
|
|
||||||
event_loop,
|
|
||||||
&mut create_window_event_reader,
|
|
||||||
);
|
|
||||||
let winit_config = app.world.resource::<WinitSettings>();
|
|
||||||
let update = if winit_state.active {
|
let update = if winit_state.active {
|
||||||
let windows = app.world.resource::<Windows>();
|
// True if _any_ windows are currently being focused
|
||||||
let focused = windows.iter().any(|w| w.is_focused());
|
let app_focused = window_focused_query.iter().any(|window| window.focused);
|
||||||
match winit_config.update_mode(focused) {
|
match winit_config.update_mode(app_focused) {
|
||||||
UpdateMode::Continuous | UpdateMode::Reactive { .. } => true,
|
UpdateMode::Continuous | UpdateMode::Reactive { .. } => true,
|
||||||
UpdateMode::ReactiveLowPower { .. } => {
|
UpdateMode::ReactiveLowPower { .. } => {
|
||||||
winit_state.low_power_event
|
winit_state.low_power_event
|
||||||
|
@ -605,6 +585,7 @@ pub fn winit_runner_with(mut app: App) {
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
if update {
|
if update {
|
||||||
winit_state.last_update = Instant::now();
|
winit_state.last_update = Instant::now();
|
||||||
app.update();
|
app.update();
|
||||||
|
@ -612,12 +593,15 @@ pub fn winit_runner_with(mut app: App) {
|
||||||
}
|
}
|
||||||
Event::RedrawEventsCleared => {
|
Event::RedrawEventsCleared => {
|
||||||
{
|
{
|
||||||
let winit_config = app.world.resource::<WinitSettings>();
|
// Fetch from world
|
||||||
let windows = app.world.resource::<Windows>();
|
let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
|
||||||
let focused = windows.iter().any(|w| w.is_focused());
|
|
||||||
|
// True if _any_ windows are currently being focused
|
||||||
|
let app_focused = window_focused_query.iter().any(|window| window.focused);
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
use UpdateMode::*;
|
use UpdateMode::*;
|
||||||
*control_flow = match winit_config.update_mode(focused) {
|
*control_flow = match winit_config.update_mode(app_focused) {
|
||||||
Continuous => ControlFlow::Poll,
|
Continuous => ControlFlow::Poll,
|
||||||
Reactive { max_wait } | ReactiveLowPower { max_wait } => {
|
Reactive { max_wait } | ReactiveLowPower { max_wait } => {
|
||||||
if let Some(instant) = now.checked_add(*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,
|
// 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
|
// we won't be able to see redraw requests until the next event, defeating the
|
||||||
// purpose of a redraw request!
|
// purpose of a redraw request!
|
||||||
|
@ -638,64 +623,18 @@ pub fn winit_runner_with(mut app: App) {
|
||||||
redraw = true;
|
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;
|
winit_state.redraw_request_sent = redraw;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If true, returns control from Winit back to the main Bevy loop
|
||||||
if return_from_run {
|
if return_from_run {
|
||||||
run_return(&mut event_loop, event_handler);
|
run_return(&mut event_loop, event_handler);
|
||||||
} else {
|
} else {
|
||||||
run(event_loop, event_handler);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
284
crates/bevy_winit/src/system.rs
Normal file
284
crates/bevy_winit/src/system.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::WinitWindows;
|
use crate::WinitWindows;
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_window::WindowId;
|
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use winit::dpi::LogicalSize;
|
use winit::dpi::LogicalSize;
|
||||||
|
@ -17,7 +16,7 @@ impl Plugin for CanvasParentResizePlugin {
|
||||||
|
|
||||||
struct ResizeEvent {
|
struct ResizeEvent {
|
||||||
size: LogicalSize<f32>,
|
size: LogicalSize<f32>,
|
||||||
window_id: WindowId,
|
window: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
|
@ -31,7 +30,7 @@ fn canvas_parent_resize_event_handler(
|
||||||
resize_events: Res<CanvasParentResizeEventChannel>,
|
resize_events: Res<CanvasParentResizeEventChannel>,
|
||||||
) {
|
) {
|
||||||
for event in resize_events.receiver.try_iter() {
|
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);
|
window.set_inner_size(event.size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,12 +58,12 @@ impl Default for CanvasParentResizeEventChannel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 sender = self.sender.clone();
|
||||||
let owned_selector = selector.to_string();
|
let owned_selector = selector.to_string();
|
||||||
let resize = move || {
|
let resize = move || {
|
||||||
if let Some(size) = get_size(&owned_selector) {
|
if let Some(size) = get_size(&owned_selector) {
|
||||||
sender.send(ResizeEvent { size, window_id }).unwrap();
|
sender.send(ResizeEvent { size, window }).unwrap();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
use bevy_math::{DVec2, IVec2};
|
use bevy_ecs::entity::Entity;
|
||||||
use bevy_utils::HashMap;
|
|
||||||
use bevy_window::{
|
use bevy_utils::{tracing::warn, HashMap};
|
||||||
CursorGrabMode, MonitorSelection, RawHandleWrapper, Window, WindowDescriptor, WindowId,
|
use bevy_window::{CursorGrabMode, Window, WindowMode, WindowPosition, WindowResolution};
|
||||||
WindowMode,
|
|
||||||
};
|
|
||||||
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
|
||||||
use winit::{
|
use winit::{
|
||||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
|
dpi::{LogicalSize, PhysicalPosition},
|
||||||
window::Fullscreen,
|
monitor::MonitorHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct WinitWindows {
|
pub struct WinitWindows {
|
||||||
pub windows: HashMap<winit::window::WindowId, winit::window::Window>,
|
pub windows: HashMap<winit::window::WindowId, winit::window::Window>,
|
||||||
pub window_id_to_winit: HashMap<WindowId, winit::window::WindowId>,
|
pub entity_to_winit: HashMap<Entity, winit::window::WindowId>,
|
||||||
pub winit_to_window_id: HashMap<winit::window::WindowId, 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
|
// 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
|
// 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.
|
// only ever accessed with bevy's non-send functions and in NonSend systems.
|
||||||
|
@ -25,45 +23,40 @@ impl WinitWindows {
|
||||||
pub fn create_window(
|
pub fn create_window(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
|
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
|
||||||
window_id: WindowId,
|
entity: Entity,
|
||||||
window_descriptor: &WindowDescriptor,
|
window: &Window,
|
||||||
) -> Window {
|
) -> &winit::window::Window {
|
||||||
let mut winit_window_builder = winit::window::WindowBuilder::new();
|
let mut winit_window_builder = winit::window::WindowBuilder::new();
|
||||||
|
|
||||||
let &WindowDescriptor {
|
winit_window_builder = match window.mode {
|
||||||
width,
|
WindowMode::BorderlessFullscreen => winit_window_builder.with_fullscreen(Some(
|
||||||
height,
|
winit::window::Fullscreen::Borderless(event_loop.primary_monitor()),
|
||||||
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())),
|
|
||||||
)),
|
)),
|
||||||
|
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(
|
WindowMode::SizedFullscreen => winit_window_builder.with_fullscreen(Some(
|
||||||
Fullscreen::Exclusive(get_fitting_videomode(
|
winit::window::Fullscreen::Exclusive(get_fitting_videomode(
|
||||||
&selected_or_primary_monitor.unwrap(),
|
&event_loop.primary_monitor().unwrap(),
|
||||||
window_descriptor.width as u32,
|
window.width() as u32,
|
||||||
window_descriptor.height as u32,
|
window.height() as u32,
|
||||||
)),
|
)),
|
||||||
)),
|
)),
|
||||||
WindowMode::Windowed => {
|
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))
|
winit_window_builder.with_inner_size(logical_size.to_physical::<f64>(sf))
|
||||||
} else {
|
} else {
|
||||||
winit_window_builder.with_inner_size(logical_size)
|
winit_window_builder.with_inner_size(logical_size)
|
||||||
|
@ -72,12 +65,12 @@ impl WinitWindows {
|
||||||
};
|
};
|
||||||
|
|
||||||
winit_window_builder = winit_window_builder
|
winit_window_builder = winit_window_builder
|
||||||
.with_resizable(window_descriptor.resizable)
|
.with_always_on_top(window.always_on_top)
|
||||||
.with_decorations(window_descriptor.decorations)
|
.with_resizable(window.resizable)
|
||||||
.with_transparent(window_descriptor.transparent)
|
.with_decorations(window.decorations)
|
||||||
.with_always_on_top(window_descriptor.always_on_top);
|
.with_transparent(window.transparent);
|
||||||
|
|
||||||
let constraints = window_descriptor.resize_constraints.check_constraints();
|
let constraints = window.resize_constraints.check_constraints();
|
||||||
let min_inner_size = LogicalSize {
|
let min_inner_size = LogicalSize {
|
||||||
width: constraints.min_width,
|
width: constraints.min_width,
|
||||||
height: constraints.min_height,
|
height: constraints.min_height,
|
||||||
|
@ -97,14 +90,14 @@ impl WinitWindows {
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
#[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")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use winit::platform::web::WindowBuilderExtWebSys;
|
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 window = web_sys::window().unwrap();
|
||||||
let document = window.document().unwrap();
|
let document = window.document().unwrap();
|
||||||
let canvas = document
|
let canvas = document
|
||||||
|
@ -121,59 +114,21 @@ impl WinitWindows {
|
||||||
|
|
||||||
let winit_window = winit_window_builder.build(event_loop).unwrap();
|
let winit_window = winit_window_builder.build(event_loop).unwrap();
|
||||||
|
|
||||||
if window_descriptor.mode == WindowMode::Windowed {
|
// Do not set the grab mode on window creation if it's none, this can fail on mobile
|
||||||
use bevy_window::WindowPosition::*;
|
if window.cursor.grab_mode != CursorGrabMode::None {
|
||||||
match position {
|
attempt_grab(&winit_window, window.cursor.grab_mode);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.entity_to_winit.insert(entity, winit_window.id());
|
||||||
self.winit_to_window_id.insert(winit_window.id(), window_id);
|
self.winit_to_entity.insert(winit_window.id(), entity);
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
use winit::platform::web::WindowExtWebSys;
|
use winit::platform::web::WindowExtWebSys;
|
||||||
|
|
||||||
if window_descriptor.canvas.is_none() {
|
if window.canvas.is_none() {
|
||||||
let canvas = winit_window.canvas();
|
let canvas = winit_window.canvas();
|
||||||
|
|
||||||
let window = web_sys::window().unwrap();
|
let window = web_sys::window().unwrap();
|
||||||
|
@ -185,45 +140,31 @@ impl WinitWindows {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let position = winit_window
|
self.windows
|
||||||
.outer_position()
|
.entry(winit_window.id())
|
||||||
.ok()
|
.insert(winit_window)
|
||||||
.map(|position| IVec2::new(position.x, position.y));
|
.into_mut()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_window(&self, id: WindowId) -> Option<&winit::window::Window> {
|
/// Get the winit window that is associated with our entity.
|
||||||
self.window_id_to_winit
|
pub fn get_window(&self, entity: Entity) -> Option<&winit::window::Window> {
|
||||||
.get(&id)
|
self.entity_to_winit
|
||||||
.and_then(|id| self.windows.get(id))
|
.get(&entity)
|
||||||
|
.and_then(|winit_id| self.windows.get(winit_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_window_id(&self, id: winit::window::WindowId) -> Option<WindowId> {
|
/// Get the entity associated with the winit window id.
|
||||||
self.winit_to_window_id.get(&id).cloned()
|
///
|
||||||
|
/// 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> {
|
/// Remove a window from winit.
|
||||||
let winit_id = self.window_id_to_winit.remove(&id)?;
|
///
|
||||||
|
/// 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
|
// Don't remove from winit_to_window_id, to track that we used to know about this winit window
|
||||||
self.windows.remove(&winit_id)
|
self.windows.remove(&winit_id)
|
||||||
}
|
}
|
||||||
|
@ -278,3 +219,89 @@ pub fn get_best_videomode(monitor: &winit::monitor::MonitorHandle) -> winit::mon
|
||||||
|
|
||||||
modes.first().unwrap().clone()
|
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 {}
|
||||||
|
|
|
@ -3,10 +3,8 @@
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
core_pipeline::clear_color::ClearColorConfig,
|
core_pipeline::clear_color::ClearColorConfig, prelude::*, render::camera::Viewport,
|
||||||
prelude::*,
|
window::WindowResized,
|
||||||
render::camera::Viewport,
|
|
||||||
window::{WindowId, WindowResized},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -82,7 +80,7 @@ struct LeftCamera;
|
||||||
struct RightCamera;
|
struct RightCamera;
|
||||||
|
|
||||||
fn set_camera_viewports(
|
fn set_camera_viewports(
|
||||||
windows: Res<Windows>,
|
windows: Query<&Window>,
|
||||||
mut resize_events: EventReader<WindowResized>,
|
mut resize_events: EventReader<WindowResized>,
|
||||||
mut left_camera: Query<&mut Camera, (With<LeftCamera>, Without<RightCamera>)>,
|
mut left_camera: Query<&mut Camera, (With<LeftCamera>, Without<RightCamera>)>,
|
||||||
mut right_camera: Query<&mut Camera, With<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.
|
// 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.
|
// 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() {
|
for resize_event in resize_events.iter() {
|
||||||
if resize_event.id == WindowId::primary() {
|
let window = windows.get(resize_event.window).unwrap();
|
||||||
let window = windows.primary();
|
let mut left_camera = left_camera.single_mut();
|
||||||
let mut left_camera = left_camera.single_mut();
|
left_camera.viewport = Some(Viewport {
|
||||||
left_camera.viewport = Some(Viewport {
|
physical_position: UVec2::new(0, 0),
|
||||||
physical_position: UVec2::new(0, 0),
|
physical_size: UVec2::new(
|
||||||
physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()),
|
window.resolution.physical_width() / 2,
|
||||||
..default()
|
window.resolution.physical_height(),
|
||||||
});
|
),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
let mut right_camera = right_camera.single_mut();
|
let mut right_camera = right_camera.single_mut();
|
||||||
right_camera.viewport = Some(Viewport {
|
right_camera.viewport = Some(Viewport {
|
||||||
physical_position: UVec2::new(window.physical_width() / 2, 0),
|
physical_position: UVec2::new(window.resolution.physical_width() / 2, 0),
|
||||||
physical_size: UVec2::new(window.physical_width() / 2, window.physical_height()),
|
physical_size: UVec2::new(
|
||||||
..default()
|
window.resolution.physical_width() / 2,
|
||||||
});
|
window.resolution.physical_height(),
|
||||||
}
|
),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
//! - <https://docs.rs/bevy/latest/bevy/app/struct.App.html#method.run>
|
//! - <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>
|
//! - <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() {
|
fn main() {
|
||||||
println!("Running Bevy App");
|
println!("Running Bevy App");
|
||||||
|
@ -21,10 +21,10 @@ fn main() {
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
title: "Close the window to return to the main function".to_owned(),
|
title: "Close the window to return to the main function".into(),
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_system(system)
|
.add_system(system)
|
||||||
|
|
|
@ -37,8 +37,8 @@ fn move_system(mut sprites: Query<(&mut Transform, &Velocity)>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bounce sprites outside the window
|
// Bounce sprites outside the window
|
||||||
fn bounce_system(windows: Res<Windows>, mut sprites: Query<(&Transform, &mut Velocity)>) {
|
fn bounce_system(windows: Query<&Window>, mut sprites: Query<(&Transform, &mut Velocity)>) {
|
||||||
let window = windows.primary();
|
let window = windows.single();
|
||||||
let width = window.width();
|
let width = window.width();
|
||||||
let height = window.height();
|
let height = window.height();
|
||||||
let left = width / -2.0;
|
let left = width / -2.0;
|
||||||
|
|
|
@ -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
|
/// velocity. On collision with the ground it applies an upwards
|
||||||
/// force.
|
/// force.
|
||||||
fn collision_system(
|
fn collision_system(
|
||||||
windows: Res<Windows>,
|
windows: Query<&Window>,
|
||||||
mut query: Query<(&mut Velocity, &mut Transform), With<Contributor>>,
|
mut query: Query<(&mut Velocity, &mut Transform), With<Contributor>>,
|
||||||
) {
|
) {
|
||||||
let Some(window) = windows.get_primary() else {
|
let window = windows.single();
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let ceiling = window.height() / 2.;
|
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.;
|
let wall_right = window.width() / 2.;
|
||||||
|
|
||||||
// The maximum height the birbs should try to reach is one birb below the top of the window.
|
// The maximum height the birbs should try to reach is one birb below the top of the window.
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
//! Demonstrates how to grab and hide the mouse cursor.
|
//! Demonstrates how to grab and hide the mouse cursor.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::{prelude::*, window::CursorGrabMode};
|
||||||
use bevy::window::CursorGrabMode;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
|
@ -13,17 +12,19 @@ fn main() {
|
||||||
// This system grabs the mouse when the left mouse button is pressed
|
// This system grabs the mouse when the left mouse button is pressed
|
||||||
// and releases it when the escape key is pressed
|
// and releases it when the escape key is pressed
|
||||||
fn grab_mouse(
|
fn grab_mouse(
|
||||||
mut windows: ResMut<Windows>,
|
mut windows: Query<&mut Window>,
|
||||||
mouse: Res<Input<MouseButton>>,
|
mouse: Res<Input<MouseButton>>,
|
||||||
key: Res<Input<KeyCode>>,
|
key: Res<Input<KeyCode>>,
|
||||||
) {
|
) {
|
||||||
let window = windows.primary_mut();
|
let mut window = windows.single_mut();
|
||||||
|
|
||||||
if mouse.just_pressed(MouseButton::Left) {
|
if mouse.just_pressed(MouseButton::Left) {
|
||||||
window.set_cursor_visibility(false);
|
window.cursor.visible = false;
|
||||||
window.set_cursor_grab_mode(CursorGrabMode::Locked);
|
window.cursor.grab_mode = CursorGrabMode::Locked;
|
||||||
}
|
}
|
||||||
|
|
||||||
if key.just_pressed(KeyCode::Escape) {
|
if key.just_pressed(KeyCode::Escape) {
|
||||||
window.set_cursor_visibility(true);
|
window.cursor.visible = true;
|
||||||
window.set_cursor_grab_mode(CursorGrabMode::None);
|
window.cursor.grab_mode = CursorGrabMode::None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,11 @@ use bevy::{input::touch::TouchPhase, prelude::*, window::WindowMode};
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
resizable: false,
|
resizable: false,
|
||||||
mode: WindowMode::BorderlessFullscreen,
|
mode: WindowMode::BorderlessFullscreen,
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_startup_system(setup_scene)
|
.add_startup_system(setup_scene)
|
||||||
|
@ -20,17 +20,18 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn touch_camera(
|
fn touch_camera(
|
||||||
windows: ResMut<Windows>,
|
windows: Query<&Window>,
|
||||||
mut touches: EventReader<TouchInput>,
|
mut touches: EventReader<TouchInput>,
|
||||||
mut camera: Query<&mut Transform, With<Camera3d>>,
|
mut camera: Query<&mut Transform, With<Camera3d>>,
|
||||||
mut last_position: Local<Option<Vec2>>,
|
mut last_position: Local<Option<Vec2>>,
|
||||||
) {
|
) {
|
||||||
|
let window = windows.single();
|
||||||
|
|
||||||
for touch in touches.iter() {
|
for touch in touches.iter() {
|
||||||
if touch.phase == TouchPhase::Started {
|
if touch.phase == TouchPhase::Started {
|
||||||
*last_position = None;
|
*last_position = None;
|
||||||
}
|
}
|
||||||
if let Some(last_position) = *last_position {
|
if let Some(last_position) = *last_position {
|
||||||
let window = windows.primary();
|
|
||||||
let mut transform = camera.single_mut();
|
let mut transform = camera.single_mut();
|
||||||
*transform = Transform::from_xyz(
|
*transform = Transform::from_xyz(
|
||||||
transform.translation.x
|
transform.translation.x
|
||||||
|
|
|
@ -13,6 +13,7 @@ use bevy::{
|
||||||
renderer::{RenderContext, RenderDevice},
|
renderer::{RenderContext, RenderDevice},
|
||||||
RenderApp, RenderStage,
|
RenderApp, RenderStage,
|
||||||
},
|
},
|
||||||
|
window::WindowPlugin,
|
||||||
};
|
};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
@ -23,11 +24,11 @@ fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.insert_resource(ClearColor(Color::BLACK))
|
.insert_resource(ClearColor(Color::BLACK))
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
// uncomment for unthrottled FPS
|
// uncomment for unthrottled FPS
|
||||||
// present_mode: bevy::window::PresentMode::AutoNoVsync,
|
// present_mode: bevy::window::PresentMode::AutoNoVsync,
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_plugin(GameOfLifeComputePlugin)
|
.add_plugin(GameOfLifeComputePlugin)
|
||||||
|
|
|
@ -34,16 +34,18 @@ struct MainCube;
|
||||||
|
|
||||||
fn setup(
|
fn setup(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut windows: ResMut<Windows>,
|
windows: Query<&Window>,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut post_processing_materials: ResMut<Assets<PostProcessingMaterial>>,
|
mut post_processing_materials: ResMut<Assets<PostProcessingMaterial>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
mut images: ResMut<Assets<Image>>,
|
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 {
|
let size = Extent3d {
|
||||||
width: window.physical_width(),
|
width: window.resolution.physical_width(),
|
||||||
height: window.physical_height(),
|
height: window.resolution.physical_height(),
|
||||||
..default()
|
..default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ use bevy::{
|
||||||
diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
time::FixedTimestep,
|
time::FixedTimestep,
|
||||||
window::PresentMode,
|
window::{PresentMode, WindowResolution},
|
||||||
};
|
};
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
|
@ -30,14 +30,12 @@ struct Bird {
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
title: "BevyMark".to_string(),
|
title: "BevyMark".into(),
|
||||||
width: 800.,
|
resolution: (800., 600.).into(),
|
||||||
height: 600.,
|
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
resizable: true,
|
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||||
|
@ -67,15 +65,17 @@ struct BirdScheduled {
|
||||||
|
|
||||||
fn scheduled_spawner(
|
fn scheduled_spawner(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
windows: Res<Windows>,
|
windows: Query<&Window>,
|
||||||
mut scheduled: ResMut<BirdScheduled>,
|
mut scheduled: ResMut<BirdScheduled>,
|
||||||
mut counter: ResMut<BevyCounter>,
|
mut counter: ResMut<BevyCounter>,
|
||||||
bird_texture: Res<BirdTexture>,
|
bird_texture: Res<BirdTexture>,
|
||||||
) {
|
) {
|
||||||
|
let window = windows.single();
|
||||||
|
|
||||||
if scheduled.wave > 0 {
|
if scheduled.wave > 0 {
|
||||||
spawn_birds(
|
spawn_birds(
|
||||||
&mut commands,
|
&mut commands,
|
||||||
&windows,
|
&window.resolution,
|
||||||
&mut counter,
|
&mut counter,
|
||||||
scheduled.per_wave,
|
scheduled.per_wave,
|
||||||
bird_texture.clone_weak(),
|
bird_texture.clone_weak(),
|
||||||
|
@ -150,10 +150,12 @@ fn mouse_handler(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mouse_button_input: Res<Input<MouseButton>>,
|
mouse_button_input: Res<Input<MouseButton>>,
|
||||||
windows: Res<Windows>,
|
windows: Query<&Window>,
|
||||||
bird_texture: Res<BirdTexture>,
|
bird_texture: Res<BirdTexture>,
|
||||||
mut counter: ResMut<BevyCounter>,
|
mut counter: ResMut<BevyCounter>,
|
||||||
) {
|
) {
|
||||||
|
let window = windows.single();
|
||||||
|
|
||||||
if mouse_button_input.just_released(MouseButton::Left) {
|
if mouse_button_input.just_released(MouseButton::Left) {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
counter.color = Color::rgb_linear(rng.gen(), rng.gen(), rng.gen());
|
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;
|
let spawn_count = (BIRDS_PER_SECOND as f64 * time.delta_seconds_f64()) as usize;
|
||||||
spawn_birds(
|
spawn_birds(
|
||||||
&mut commands,
|
&mut commands,
|
||||||
&windows,
|
&window.resolution,
|
||||||
&mut counter,
|
&mut counter,
|
||||||
spawn_count,
|
spawn_count,
|
||||||
bird_texture.clone_weak(),
|
bird_texture.clone_weak(),
|
||||||
|
@ -173,14 +175,13 @@ fn mouse_handler(
|
||||||
|
|
||||||
fn spawn_birds(
|
fn spawn_birds(
|
||||||
commands: &mut Commands,
|
commands: &mut Commands,
|
||||||
windows: &Windows,
|
primary_window_resolution: &WindowResolution,
|
||||||
counter: &mut BevyCounter,
|
counter: &mut BevyCounter,
|
||||||
spawn_count: usize,
|
spawn_count: usize,
|
||||||
texture: Handle<Image>,
|
texture: Handle<Image>,
|
||||||
) {
|
) {
|
||||||
let window = windows.primary();
|
let bird_x = (primary_window_resolution.width() / -2.) + HALF_BIRD_SIZE;
|
||||||
let bird_x = (window.width() / -2.) + HALF_BIRD_SIZE;
|
let bird_y = (primary_window_resolution.height() / 2.) - HALF_BIRD_SIZE;
|
||||||
let bird_y = (window.height() / 2.) - HALF_BIRD_SIZE;
|
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
for count in 0..spawn_count {
|
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)>) {
|
fn collision_system(windows: Query<&Window>, mut bird_query: Query<(&mut Bird, &Transform)>) {
|
||||||
let window = windows.primary();
|
let window = windows.single();
|
||||||
|
|
||||||
let half_width = window.width() * 0.5;
|
let half_width = window.width() * 0.5;
|
||||||
let half_height = window.height() * 0.5;
|
let half_height = window.height() * 0.5;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use bevy::{
|
use bevy::{
|
||||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
window::PresentMode,
|
window::{PresentMode, WindowPlugin},
|
||||||
};
|
};
|
||||||
|
|
||||||
// For a total of 110 * 110 = 12100 buttons with text
|
// For a total of 110 * 110 = 12100 buttons with text
|
||||||
|
@ -12,10 +12,10 @@ const FONT_SIZE: f32 = 7.0;
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
present_mode: PresentMode::Immediate,
|
present_mode: PresentMode::Immediate,
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||||
|
|
|
@ -16,16 +16,16 @@ use bevy::{
|
||||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
math::{DVec2, DVec3},
|
math::{DVec2, DVec3},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
window::PresentMode,
|
window::{PresentMode, WindowPlugin},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::time::Duration;
|
||||||
use bevy::{
|
use bevy::{
|
||||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
window::PresentMode,
|
window::{PresentMode, WindowPlugin},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
|
@ -20,11 +20,11 @@ struct Foxes {
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
title: "🦊🦊🦊 Many Foxes! 🦊🦊🦊".to_string(),
|
title: "🦊🦊🦊 Many Foxes! 🦊🦊🦊".into(),
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_plugin(FrameTimeDiagnosticsPlugin)
|
.add_plugin(FrameTimeDiagnosticsPlugin)
|
||||||
|
|
|
@ -9,20 +9,19 @@ use bevy::{
|
||||||
pbr::{ExtractedPointLight, GlobalLightMeta},
|
pbr::{ExtractedPointLight, GlobalLightMeta},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
render::{camera::ScalingMode, Extract, RenderApp, RenderStage},
|
render::{camera::ScalingMode, Extract, RenderApp, RenderStage},
|
||||||
window::PresentMode,
|
window::{PresentMode, WindowPlugin},
|
||||||
};
|
};
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
width: 1024.0,
|
resolution: (1024.0, 768.0).into(),
|
||||||
height: 768.0,
|
title: "many_lights".into(),
|
||||||
title: "many_lights".to_string(),
|
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
use bevy::{
|
use bevy::{
|
||||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
window::PresentMode,
|
window::{PresentMode, WindowPlugin},
|
||||||
};
|
};
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
@ -31,10 +31,10 @@ fn main() {
|
||||||
.add_plugin(LogDiagnosticsPlugin::default())
|
.add_plugin(LogDiagnosticsPlugin::default())
|
||||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_startup_system(setup)
|
.add_startup_system(setup)
|
||||||
|
|
|
@ -97,14 +97,13 @@ impl Plugin for CameraControllerPlugin {
|
||||||
|
|
||||||
fn camera_controller(
|
fn camera_controller(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
mut windows: ResMut<Windows>,
|
mut windows: Query<&mut Window>,
|
||||||
mut mouse_events: EventReader<MouseMotion>,
|
mut mouse_events: EventReader<MouseMotion>,
|
||||||
mouse_button_input: Res<Input<MouseButton>>,
|
mouse_button_input: Res<Input<MouseButton>>,
|
||||||
key_input: Res<Input<KeyCode>>,
|
key_input: Res<Input<KeyCode>>,
|
||||||
mut move_toggled: Local<bool>,
|
mut move_toggled: Local<bool>,
|
||||||
mut query: Query<(&mut Transform, &mut CameraController), With<Camera>>,
|
mut query: Query<(&mut Transform, &mut CameraController), With<Camera>>,
|
||||||
) {
|
) {
|
||||||
let window = windows.primary_mut();
|
|
||||||
let dt = time.delta_seconds();
|
let dt = time.delta_seconds();
|
||||||
|
|
||||||
if let Ok((mut transform, mut options)) = query.get_single_mut() {
|
if let Ok((mut transform, mut options)) = query.get_single_mut() {
|
||||||
|
@ -166,15 +165,23 @@ fn camera_controller(
|
||||||
// Handle mouse input
|
// Handle mouse input
|
||||||
let mut mouse_delta = Vec2::ZERO;
|
let mut mouse_delta = Vec2::ZERO;
|
||||||
if mouse_button_input.pressed(options.mouse_key_enable_mouse) || *move_toggled {
|
if mouse_button_input.pressed(options.mouse_key_enable_mouse) || *move_toggled {
|
||||||
window.set_cursor_grab_mode(CursorGrabMode::Locked);
|
for mut window in &mut windows {
|
||||||
window.set_cursor_visibility(false);
|
if !window.focused {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.cursor.grab_mode = CursorGrabMode::Locked;
|
||||||
|
window.cursor.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
for mouse_event in mouse_events.iter() {
|
for mouse_event in mouse_events.iter() {
|
||||||
mouse_delta += mouse_event.delta;
|
mouse_delta += mouse_event.delta;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window.set_cursor_grab_mode(CursorGrabMode::None);
|
for mut window in &mut windows {
|
||||||
window.set_cursor_visibility(true);
|
window.cursor.grab_mode = CursorGrabMode::None;
|
||||||
|
window.cursor.visible = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mouse_delta != Vec2::ZERO {
|
if mouse_delta != Vec2::ZERO {
|
||||||
|
|
|
@ -9,6 +9,7 @@ use bevy::{
|
||||||
math::Vec3A,
|
math::Vec3A,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
render::primitives::{Aabb, Sphere},
|
render::primitives::{Aabb, Sphere},
|
||||||
|
window::WindowPlugin,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod camera_controller_plugin;
|
mod camera_controller_plugin;
|
||||||
|
@ -26,10 +27,10 @@ fn main() {
|
||||||
.add_plugins(
|
.add_plugins(
|
||||||
DefaultPlugins
|
DefaultPlugins
|
||||||
.set(WindowPlugin {
|
.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
title: "bevy scene viewer".to_string(),
|
title: "bevy scene viewer".to_string(),
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
.set(AssetPlugin {
|
.set(AssetPlugin {
|
||||||
|
|
|
@ -3,16 +3,16 @@
|
||||||
use bevy::{
|
use bevy::{
|
||||||
diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin},
|
diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
window::PresentMode,
|
window::{PresentMode, WindowPlugin},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_plugin(FrameTimeDiagnosticsPlugin)
|
.add_plugin(FrameTimeDiagnosticsPlugin)
|
||||||
|
|
|
@ -4,18 +4,16 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
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()
|
App::new()
|
||||||
.insert_resource(ClearColor(Color::NONE)) // Use a transparent window, to make effects obvious.
|
.insert_resource(ClearColor(Color::NONE)) // Use a transparent window, to make effects obvious.
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.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()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_startup_system(setup)
|
.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.
|
// 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) {
|
if keyboard_input.just_pressed(KeyCode::P) {
|
||||||
let window = windows.primary_mut();
|
let mut window = windows.single_mut();
|
||||||
let hittest: bool = window.hittest();
|
window.cursor.hit_test = !window.cursor.hit_test;
|
||||||
window.set_cursor_hittest(!hittest);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
utils::Duration,
|
utils::Duration,
|
||||||
window::{PresentMode, RequestRedraw},
|
window::{PresentMode, RequestRedraw, WindowPlugin},
|
||||||
winit::WinitSettings,
|
winit::WinitSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,11 +26,11 @@ fn main() {
|
||||||
})
|
})
|
||||||
.insert_resource(ExampleMode::Game)
|
.insert_resource(ExampleMode::Game)
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
// Turn off vsync to maximize CPU/GPU usage
|
// Turn off vsync to maximize CPU/GPU usage
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_startup_system(test_setup::setup)
|
.add_startup_system(test_setup::setup)
|
||||||
|
|
|
@ -1,24 +1,17 @@
|
||||||
//! Uses two windows to visualize a 3D model from different angles.
|
//! Uses two windows to visualize a 3D model from different angles.
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{prelude::*, render::camera::RenderTarget, window::WindowRef};
|
||||||
prelude::*,
|
|
||||||
render::camera::RenderTarget,
|
|
||||||
window::{CreateWindow, PresentMode, WindowId},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
|
// By default, a primary window gets spawned by `WindowPlugin`, contained in `DefaultPlugins`
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_startup_system(setup)
|
.add_startup_system(setup_scene)
|
||||||
.add_system(bevy::window::close_on_esc)
|
.add_system(bevy::window::close_on_esc)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(
|
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
mut commands: Commands,
|
|
||||||
asset_server: Res<AssetServer>,
|
|
||||||
mut create_window_events: EventWriter<CreateWindow>,
|
|
||||||
) {
|
|
||||||
// add entities to the world
|
// add entities to the world
|
||||||
commands.spawn(SceneBundle {
|
commands.spawn(SceneBundle {
|
||||||
scene: asset_server.load("models/monkey/Monkey.gltf#Scene0"),
|
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),
|
transform: Transform::from_xyz(4.0, 5.0, 4.0),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
// main camera
|
// main camera, cameras default to the primary window
|
||||||
|
// so we don't need to specify that.
|
||||||
commands.spawn(Camera3dBundle {
|
commands.spawn(Camera3dBundle {
|
||||||
transform: Transform::from_xyz(0.0, 0.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
|
transform: Transform::from_xyz(0.0, 0.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
let window_id = WindowId::new();
|
// Spawn a second window
|
||||||
|
let second_window = commands
|
||||||
// sends out a "CreateWindow" event, which will be received by the windowing backend
|
.spawn(Window {
|
||||||
create_window_events.send(CreateWindow {
|
title: "Second window".to_owned(),
|
||||||
id: window_id,
|
|
||||||
descriptor: WindowDescriptor {
|
|
||||||
width: 800.,
|
|
||||||
height: 600.,
|
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
|
||||||
title: "Second window".to_string(),
|
|
||||||
..default()
|
..default()
|
||||||
},
|
})
|
||||||
});
|
.id();
|
||||||
|
|
||||||
// second window camera
|
// second window camera
|
||||||
commands.spawn(Camera3dBundle {
|
commands.spawn(Camera3dBundle {
|
||||||
transform: Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
|
transform: Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
camera: Camera {
|
camera: Camera {
|
||||||
target: RenderTarget::Window(window_id),
|
target: RenderTarget::Window(WindowRef::Entity(second_window)),
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
//! This example illustrates how to override the window scale factor imposed by the
|
//! This example illustrates how to override the window scale factor imposed by the
|
||||||
//! operating system.
|
//! operating system.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::{prelude::*, window::WindowResolution};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
width: 500.,
|
resolution: WindowResolution::new(500., 300.).with_scale_factor_override(1.0),
|
||||||
height: 300.,
|
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_startup_system(setup)
|
.add_startup_system(setup)
|
||||||
|
.add_system(display_override)
|
||||||
.add_system(toggle_override)
|
.add_system(toggle_override)
|
||||||
.add_system(change_scale_factor)
|
.add_system(change_scale_factor)
|
||||||
.run();
|
.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
|
/// This system toggles scale factor overrides when enter is pressed
|
||||||
fn toggle_override(input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) {
|
fn toggle_override(input: Res<Input<KeyCode>>, mut windows: Query<&mut Window>) {
|
||||||
let window = windows.primary_mut();
|
let mut window = windows.single_mut();
|
||||||
|
|
||||||
if input.just_pressed(KeyCode::Return) {
|
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
|
/// 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>) {
|
fn change_scale_factor(input: Res<Input<KeyCode>>, mut windows: Query<&mut Window>) {
|
||||||
let window = windows.primary_mut();
|
let mut window = windows.single_mut();
|
||||||
|
let scale_factor_override = window.resolution.scale_factor_override();
|
||||||
if input.just_pressed(KeyCode::Up) {
|
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) {
|
} 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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
//! [documentation](https://docs.rs/bevy/latest/bevy/prelude/struct.WindowDescriptor.html#structfield.transparent)
|
//! [documentation](https://docs.rs/bevy/latest/bevy/prelude/struct.WindowDescriptor.html#structfield.transparent)
|
||||||
//! for more details.
|
//! for more details.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::{
|
||||||
|
prelude::*,
|
||||||
|
window::{Window, WindowPlugin},
|
||||||
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
|
@ -12,13 +15,13 @@ fn main() {
|
||||||
.insert_resource(ClearColor(Color::NONE))
|
.insert_resource(ClearColor(Color::NONE))
|
||||||
.add_startup_system(setup)
|
.add_startup_system(setup)
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
// Setting `transparent` allows the `ClearColor`'s alpha value to take effect
|
// Setting `transparent` allows the `ClearColor`'s alpha value to take effect
|
||||||
transparent: true,
|
transparent: true,
|
||||||
// Disabling window decorations to make it feel more like a widget than a window
|
// Disabling window decorations to make it feel more like a widget than a window
|
||||||
decorations: false,
|
decorations: false,
|
||||||
..default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.run();
|
.run();
|
||||||
|
|
|
@ -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
|
/// This system shows how to request the window to a new resolution
|
||||||
fn toggle_resolution(
|
fn toggle_resolution(
|
||||||
keys: Res<Input<KeyCode>>,
|
keys: Res<Input<KeyCode>>,
|
||||||
mut windows: ResMut<Windows>,
|
mut windows: Query<&mut Window>,
|
||||||
resolution: Res<ResolutionSettings>,
|
resolution: Res<ResolutionSettings>,
|
||||||
) {
|
) {
|
||||||
let window = windows.primary_mut();
|
let mut window = windows.single_mut();
|
||||||
|
|
||||||
if keys.just_pressed(KeyCode::Key1) {
|
if keys.just_pressed(KeyCode::Key1) {
|
||||||
let res = resolution.small;
|
let res = resolution.small;
|
||||||
window.set_resolution(res.x, res.y);
|
window.resolution.set(res.x, res.y);
|
||||||
}
|
}
|
||||||
if keys.just_pressed(KeyCode::Key2) {
|
if keys.just_pressed(KeyCode::Key2) {
|
||||||
let res = resolution.medium;
|
let res = resolution.medium;
|
||||||
window.set_resolution(res.x, res.y);
|
window.resolution.set(res.x, res.y);
|
||||||
}
|
}
|
||||||
if keys.just_pressed(KeyCode::Key3) {
|
if keys.just_pressed(KeyCode::Key3) {
|
||||||
let res = resolution.large;
|
let res = resolution.large;
|
||||||
window.set_resolution(res.x, res.y);
|
window.resolution.set(res.x, res.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,14 @@ use bevy::{
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
title: "I am a window!".to_string(),
|
title: "I am a window!".into(),
|
||||||
width: 500.,
|
resolution: (500., 300.).into(),
|
||||||
height: 300.,
|
|
||||||
present_mode: PresentMode::AutoVsync,
|
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()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_plugin(LogDiagnosticsPlugin::default())
|
.add_plugin(LogDiagnosticsPlugin::default())
|
||||||
|
@ -32,16 +32,16 @@ fn main() {
|
||||||
|
|
||||||
/// This system toggles the vsync mode when pressing the button V.
|
/// This system toggles the vsync mode when pressing the button V.
|
||||||
/// You'll see fps increase displayed in the console.
|
/// 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) {
|
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
|
PresentMode::AutoNoVsync
|
||||||
} else {
|
} else {
|
||||||
PresentMode::AutoVsync
|
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
|
/// 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)
|
/// [documentation](https://docs.rs/bevy/latest/bevy/prelude/struct.WindowDescriptor.html#structfield.always_on_top)
|
||||||
/// for more details.
|
/// 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) {
|
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 {
|
if window.always_on_top {
|
||||||
info!("UNLOCKING WINDOW");
|
|
||||||
} else {
|
|
||||||
info!("LOCKING WINDOW 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
|
/// This system will then change the title during execution
|
||||||
fn change_title(time: Res<Time>, mut windows: ResMut<Windows>) {
|
fn change_title(mut windows: Query<&mut Window>, time: Res<Time>) {
|
||||||
let window = windows.primary_mut();
|
let mut window = windows.single_mut();
|
||||||
window.set_title(format!(
|
window.title = format!(
|
||||||
"Seconds since startup: {}",
|
"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(mut windows: Query<&mut Window>, input: Res<Input<KeyCode>>) {
|
||||||
fn toggle_cursor(input: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) {
|
|
||||||
let window = windows.primary_mut();
|
|
||||||
if input.just_pressed(KeyCode::Space) {
|
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::None => CursorGrabMode::Locked,
|
||||||
CursorGrabMode::Locked | CursorGrabMode::Confined => CursorGrabMode::None,
|
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
|
/// This system cycles the cursor's icon through a small set of icons when clicking
|
||||||
fn cycle_cursor_icon(
|
fn cycle_cursor_icon(
|
||||||
|
mut windows: Query<&mut Window>,
|
||||||
input: Res<Input<MouseButton>>,
|
input: Res<Input<MouseButton>>,
|
||||||
mut windows: ResMut<Windows>,
|
|
||||||
mut index: Local<usize>,
|
mut index: Local<usize>,
|
||||||
) {
|
) {
|
||||||
|
let mut window = windows.single_mut();
|
||||||
|
|
||||||
const ICONS: &[CursorIcon] = &[
|
const ICONS: &[CursorIcon] = &[
|
||||||
CursorIcon::Default,
|
CursorIcon::Default,
|
||||||
CursorIcon::Hand,
|
CursorIcon::Hand,
|
||||||
|
@ -101,16 +101,16 @@ fn cycle_cursor_icon(
|
||||||
CursorIcon::Text,
|
CursorIcon::Text,
|
||||||
CursorIcon::Copy,
|
CursorIcon::Copy,
|
||||||
];
|
];
|
||||||
let window = windows.primary_mut();
|
|
||||||
if input.just_pressed(MouseButton::Left) {
|
if input.just_pressed(MouseButton::Left) {
|
||||||
*index = (*index + 1) % ICONS.len();
|
*index = (*index + 1) % ICONS.len();
|
||||||
window.set_cursor_icon(ICONS[*index]);
|
|
||||||
} else if input.just_pressed(MouseButton::Right) {
|
} else if input.just_pressed(MouseButton::Right) {
|
||||||
*index = if *index == 0 {
|
*index = if *index == 0 {
|
||||||
ICONS.len() - 1
|
ICONS.len() - 1
|
||||||
} else {
|
} else {
|
||||||
*index - 1
|
*index - 1
|
||||||
};
|
};
|
||||||
window.set_cursor_icon(ICONS[*index]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.cursor.icon = ICONS[*index];
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,10 @@ fn main() {
|
||||||
// it is currently.
|
// it is currently.
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||||
window: WindowDescriptor {
|
primary_window: Some(Window {
|
||||||
title: "Minimising".into(),
|
title: "Minimising".into(),
|
||||||
..Default::default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}))
|
||||||
.add_system(minimise_automatically)
|
.add_system(minimise_automatically)
|
||||||
|
@ -19,9 +19,10 @@ fn main() {
|
||||||
.run();
|
.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 {
|
if *frames == 60 {
|
||||||
windows.primary_mut().set_minimized(true);
|
let mut window = windows.single_mut();
|
||||||
|
window.set_minimized(true);
|
||||||
} else {
|
} else {
|
||||||
*frames += 1;
|
*frames += 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! A test to confirm that `bevy` allows setting the window to arbitrary small sizes
|
//! 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.
|
//! 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
|
// 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.
|
// 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,
|
width: MAX_WIDTH,
|
||||||
height: MAX_HEIGHT,
|
height: MAX_HEIGHT,
|
||||||
})
|
})
|
||||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
.add_plugins(
|
||||||
window: WindowDescriptor {
|
DefaultPlugins.set(WindowPlugin {
|
||||||
width: MAX_WIDTH.try_into().unwrap(),
|
primary_window: Some(Window {
|
||||||
height: MAX_HEIGHT.try_into().unwrap(),
|
resolution: WindowResolution::new(MAX_WIDTH as f32, MAX_HEIGHT as f32)
|
||||||
scale_factor_override: Some(1.),
|
.with_scale_factor_override(1.0),
|
||||||
title: "Resizing".into(),
|
title: "Resizing".into(),
|
||||||
..Default::default()
|
..default()
|
||||||
},
|
}),
|
||||||
..default()
|
..default()
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
.insert_resource(Phase::ContractingY)
|
.insert_resource(Phase::ContractingY)
|
||||||
.add_system(change_window_size)
|
.add_system(change_window_size)
|
||||||
.add_system(sync_dimensions)
|
.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() {
|
if dim.is_changed() {
|
||||||
windows.primary_mut().set_resolution(
|
let mut window = windows.single_mut();
|
||||||
dim.width.try_into().unwrap(),
|
window.resolution.set(dim.width as f32, dim.height as f32);
|
||||||
dim.height.try_into().unwrap(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue