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