#[cfg(feature = "std")]
use alloc::format;
use alloc::{borrow::ToOwned, string::String};
use core::num::NonZero;
use bevy_ecs::{
entity::{ContainsEntity, Entity},
prelude::Component,
};
use bevy_math::{CompassOctant, DVec2, IVec2, UVec2, Vec2};
use bevy_platform::sync::LazyLock;
use log::warn;
#[cfg(feature = "bevy_reflect")]
use {
bevy_ecs::prelude::ReflectComponent,
bevy_reflect::{std_traits::ReflectDefault, Reflect},
};
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use crate::VideoMode;
static DEFAULT_WINDOW_TITLE: LazyLock<String> = LazyLock::new(|| {
#[cfg(feature = "std")]
{
std::env::current_exe()
.ok()
.and_then(|current_exe| Some(format!("{}", current_exe.file_stem()?.to_string_lossy())))
.unwrap_or_else(|| "App".to_owned())
}
#[cfg(not(feature = "std"))]
{
"App".to_owned()
}
});
#[derive(Default, Debug, Component, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Component, Debug, Default, PartialEq, Clone)
)]
pub struct PrimaryWindow;
#[repr(C)]
#[derive(Default, Copy, Clone, Debug)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, Default, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum WindowRef {
#[default]
Primary,
Entity(Entity),
}
impl WindowRef {
pub fn normalize(&self, primary_window: Option<Entity>) -> Option<NormalizedWindowRef> {
let entity = match self {
Self::Primary => primary_window,
Self::Entity(entity) => Some(*entity),
};
entity.map(NormalizedWindowRef)
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct NormalizedWindowRef(Entity);
impl ContainsEntity for NormalizedWindowRef {
fn entity(&self) -> Entity {
self.0
}
}
#[derive(Component, Debug, Clone)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Component, Default, Debug, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[require(CursorOptions)]
pub struct Window {
pub present_mode: PresentMode,
pub mode: WindowMode,
pub position: WindowPosition,
pub resolution: WindowResolution,
pub title: String,
pub name: Option<String>,
pub composite_alpha_mode: CompositeAlphaMode,
pub resize_constraints: WindowResizeConstraints,
pub resizable: bool,
pub enabled_buttons: EnabledButtons,
pub decorations: bool,
pub transparent: bool,
pub focused: bool,
pub window_level: WindowLevel,
pub canvas: Option<String>,
pub fit_canvas_to_parent: bool,
pub prevent_default_event_handling: bool,
pub internal: InternalWindowState,
pub ime_enabled: bool,
pub ime_position: Vec2,
pub window_theme: Option<WindowTheme>,
pub visible: bool,
pub skip_taskbar: bool,
pub clip_children: bool,
pub desired_maximum_frame_latency: Option<NonZero<u32>>,
pub recognize_pinch_gesture: bool,
pub recognize_rotation_gesture: bool,
pub recognize_doubletap_gesture: bool,
pub recognize_pan_gesture: Option<(u8, u8)>,
pub movable_by_window_background: bool,
pub fullsize_content_view: bool,
pub has_shadow: bool,
pub titlebar_shown: bool,
pub titlebar_transparent: bool,
pub titlebar_show_title: bool,
pub titlebar_show_buttons: bool,
pub prefers_home_indicator_hidden: bool,
pub prefers_status_bar_hidden: bool,
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
}
impl Default for Window {
fn default() -> Self {
Self {
title: DEFAULT_WINDOW_TITLE.to_owned(),
name: None,
present_mode: Default::default(),
mode: Default::default(),
position: Default::default(),
resolution: Default::default(),
internal: Default::default(),
composite_alpha_mode: Default::default(),
resize_constraints: Default::default(),
ime_enabled: Default::default(),
ime_position: Default::default(),
resizable: true,
enabled_buttons: Default::default(),
decorations: true,
transparent: false,
focused: true,
window_level: Default::default(),
fit_canvas_to_parent: false,
prevent_default_event_handling: true,
canvas: None,
window_theme: None,
visible: true,
skip_taskbar: false,
clip_children: true,
desired_maximum_frame_latency: None,
recognize_pinch_gesture: false,
recognize_rotation_gesture: false,
recognize_doubletap_gesture: false,
recognize_pan_gesture: None,
movable_by_window_background: false,
fullsize_content_view: false,
has_shadow: true,
titlebar_shown: true,
titlebar_transparent: false,
titlebar_show_title: true,
titlebar_show_buttons: true,
prefers_home_indicator_hidden: false,
prefers_status_bar_hidden: false,
preferred_screen_edges_deferring_system_gestures: Default::default(),
}
}
}
impl Window {
pub fn set_maximized(&mut self, maximized: bool) {
self.internal.maximize_request = Some(maximized);
}
pub fn set_minimized(&mut self, minimized: bool) {
self.internal.minimize_request = Some(minimized);
}
pub fn start_drag_move(&mut self) {
self.internal.drag_move_request = true;
}
pub fn start_drag_resize(&mut self, direction: CompassOctant) {
self.internal.drag_resize_request = Some(direction);
}
#[inline]
pub fn width(&self) -> f32 {
self.resolution.width()
}
#[inline]
pub fn height(&self) -> f32 {
self.resolution.height()
}
#[inline]
pub fn size(&self) -> Vec2 {
self.resolution.size()
}
#[inline]
pub fn physical_width(&self) -> u32 {
self.resolution.physical_width()
}
#[inline]
pub fn physical_height(&self) -> u32 {
self.resolution.physical_height()
}
#[inline]
pub fn physical_size(&self) -> UVec2 {
self.resolution.physical_size()
}
#[inline]
pub fn scale_factor(&self) -> f32 {
self.resolution.scale_factor()
}
#[inline]
pub fn cursor_position(&self) -> Option<Vec2> {
self.physical_cursor_position()
.map(|position| (position.as_dvec2() / self.scale_factor() as f64).as_vec2())
}
#[inline]
pub fn physical_cursor_position(&self) -> Option<Vec2> {
match self.internal.physical_cursor_position {
Some(position) => {
if position.x >= 0.
&& position.y >= 0.
&& position.x < self.physical_width() as f64
&& position.y < self.physical_height() as f64
{
Some(position.as_vec2())
} else {
None
}
}
None => None,
}
}
pub fn set_cursor_position(&mut self, position: Option<Vec2>) {
self.internal.physical_cursor_position =
position.map(|p| p.as_dvec2() * self.scale_factor() as f64);
}
pub fn set_physical_cursor_position(&mut self, position: Option<DVec2>) {
self.internal.physical_cursor_position = position;
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct WindowResizeConstraints {
pub min_width: f32,
pub min_height: f32,
pub max_width: f32,
pub max_height: f32,
}
impl Default for WindowResizeConstraints {
fn default() -> Self {
Self {
min_width: 180.,
min_height: 120.,
max_width: f32::INFINITY,
max_height: f32::INFINITY,
}
}
}
impl WindowResizeConstraints {
#[must_use]
pub fn check_constraints(&self) -> Self {
let &WindowResizeConstraints {
mut min_width,
mut min_height,
mut max_width,
mut max_height,
} = self;
min_width = min_width.max(1.);
min_height = min_height.max(1.);
if max_width < min_width {
warn!(
"The given maximum width {max_width} is smaller than the minimum width {min_width}"
);
max_width = min_width;
}
if max_height < min_height {
warn!(
"The given maximum height {max_height} is smaller than the minimum height {min_height}",
);
max_height = min_height;
}
WindowResizeConstraints {
min_width,
min_height,
max_width,
max_height,
}
}
}
#[derive(Component, Debug, Clone)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Component, Debug, Default, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct CursorOptions {
pub visible: bool,
pub grab_mode: CursorGrabMode,
pub hit_test: bool,
}
impl Default for CursorOptions {
fn default() -> Self {
CursorOptions {
visible: true,
grab_mode: CursorGrabMode::None,
hit_test: true,
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum WindowPosition {
#[default]
Automatic,
Centered(MonitorSelection),
At(IVec2),
}
impl WindowPosition {
pub fn new(position: IVec2) -> Self {
Self::At(position)
}
pub fn set(&mut self, position: IVec2) {
*self = WindowPosition::At(position);
}
pub fn center(&mut self, monitor: MonitorSelection) {
*self = WindowPosition::Centered(monitor);
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct WindowResolution {
physical_width: u32,
physical_height: u32,
scale_factor_override: Option<f32>,
scale_factor: f32,
}
impl Default for WindowResolution {
fn default() -> Self {
WindowResolution {
physical_width: 1280,
physical_height: 720,
scale_factor_override: None,
scale_factor: 1.0,
}
}
}
impl WindowResolution {
pub fn new(physical_width: u32, physical_height: u32) -> Self {
Self {
physical_width,
physical_height,
..Default::default()
}
}
pub fn with_scale_factor_override(mut self, scale_factor_override: f32) -> Self {
self.set_scale_factor_override(Some(scale_factor_override));
self
}
#[inline]
pub fn width(&self) -> f32 {
self.physical_width() as f32 / self.scale_factor()
}
#[inline]
pub fn height(&self) -> f32 {
self.physical_height() as f32 / self.scale_factor()
}
#[inline]
pub fn size(&self) -> Vec2 {
Vec2::new(self.width(), self.height())
}
#[inline]
pub fn physical_width(&self) -> u32 {
self.physical_width
}
#[inline]
pub fn physical_height(&self) -> u32 {
self.physical_height
}
#[inline]
pub fn physical_size(&self) -> UVec2 {
UVec2::new(self.physical_width, self.physical_height)
}
pub fn scale_factor(&self) -> f32 {
self.scale_factor_override
.unwrap_or_else(|| self.base_scale_factor())
}
#[inline]
pub fn base_scale_factor(&self) -> f32 {
self.scale_factor
}
#[inline]
pub fn scale_factor_override(&self) -> Option<f32> {
self.scale_factor_override
}
#[inline]
pub fn set(&mut self, width: f32, height: f32) {
self.set_physical_resolution(
(width * self.scale_factor()) as u32,
(height * self.scale_factor()) as u32,
);
}
#[inline]
pub fn set_physical_resolution(&mut self, width: u32, height: u32) {
self.physical_width = width;
self.physical_height = height;
}
#[inline]
pub fn set_scale_factor(&mut self, scale_factor: f32) {
self.scale_factor = scale_factor;
}
#[inline]
#[doc(hidden)]
pub fn set_scale_factor_and_apply_to_physical_size(&mut self, scale_factor: f32) {
self.scale_factor = scale_factor;
self.physical_width = (self.physical_width as f32 * scale_factor) as u32;
self.physical_height = (self.physical_height as f32 * scale_factor) as u32;
}
#[inline]
pub fn set_scale_factor_override(&mut self, scale_factor_override: Option<f32>) {
self.scale_factor_override = scale_factor_override;
}
}
impl From<(u32, u32)> for WindowResolution {
fn from((width, height): (u32, u32)) -> Self {
WindowResolution::new(width, height)
}
}
impl From<[u32; 2]> for WindowResolution {
fn from([width, height]: [u32; 2]) -> WindowResolution {
WindowResolution::new(width, height)
}
}
impl From<UVec2> for WindowResolution {
fn from(res: UVec2) -> WindowResolution {
WindowResolution::new(res.x, res.y)
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum CursorGrabMode {
#[default]
None,
Confined,
Locked,
}
#[derive(Default, Debug, Copy, Clone, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct InternalWindowState {
minimize_request: Option<bool>,
maximize_request: Option<bool>,
drag_move_request: bool,
drag_resize_request: Option<CompassOctant>,
physical_cursor_position: Option<DVec2>,
}
impl InternalWindowState {
pub fn take_maximize_request(&mut self) -> Option<bool> {
self.maximize_request.take()
}
pub fn take_minimize_request(&mut self) -> Option<bool> {
self.minimize_request.take()
}
pub fn take_move_request(&mut self) -> bool {
core::mem::take(&mut self.drag_move_request)
}
pub fn take_resize_request(&mut self) -> Option<CompassOctant> {
self.drag_resize_request.take()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum MonitorSelection {
Current,
Primary,
Index(usize),
Entity(Entity),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum VideoModeSelection {
Current,
Specific(VideoMode),
}
#[repr(C)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[doc(alias = "vsync")]
pub enum PresentMode {
AutoVsync = 0, AutoNoVsync = 1,
#[default]
Fifo = 2,
FifoRelaxed = 3,
Immediate = 4,
Mailbox = 5,
}
#[repr(C)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Hash, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum CompositeAlphaMode {
#[default]
Auto = 0,
Opaque = 1,
PreMultiplied = 2,
PostMultiplied = 3,
Inherit = 4,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum WindowMode {
#[default]
Windowed,
BorderlessFullscreen(MonitorSelection),
Fullscreen(MonitorSelection, VideoModeSelection),
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum WindowLevel {
AlwaysOnBottom,
#[default]
Normal,
AlwaysOnTop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum WindowTheme {
Light,
Dark,
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Debug, PartialEq, Default, Clone)
)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct EnabledButtons {
pub minimize: bool,
pub maximize: bool,
pub close: bool,
}
impl Default for EnabledButtons {
fn default() -> Self {
Self {
minimize: true,
maximize: true,
close: true,
}
}
}
#[derive(Component, Default)]
pub struct ClosingWindow;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub enum ScreenEdge {
#[default]
None,
Top,
Left,
Bottom,
Right,
All,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cursor_position_within_window_bounds() {
let mut window = Window {
resolution: WindowResolution::new(800, 600),
..Default::default()
};
window.set_physical_cursor_position(Some(DVec2::new(0., 300.)));
assert_eq!(window.physical_cursor_position(), Some(Vec2::new(0., 300.)));
window.set_physical_cursor_position(Some(DVec2::new(400., 0.)));
assert_eq!(window.physical_cursor_position(), Some(Vec2::new(400., 0.)));
window.set_physical_cursor_position(Some(DVec2::new(799.999, 300.)));
assert_eq!(
window.physical_cursor_position(),
Some(Vec2::new(799.999, 300.)),
);
window.set_physical_cursor_position(Some(DVec2::new(400., 599.999)));
assert_eq!(
window.physical_cursor_position(),
Some(Vec2::new(400., 599.999))
);
}
#[test]
fn cursor_position_not_within_window_bounds() {
let mut window = Window {
resolution: WindowResolution::new(800, 600),
..Default::default()
};
window.set_physical_cursor_position(Some(DVec2::new(-0.001, 300.)));
assert!(window.physical_cursor_position().is_none());
window.set_physical_cursor_position(Some(DVec2::new(400., -0.001)));
assert!(window.physical_cursor_position().is_none());
window.set_physical_cursor_position(Some(DVec2::new(800., 300.)));
assert!(window.physical_cursor_position().is_none());
window.set_physical_cursor_position(Some(DVec2::new(400., 600.)));
assert!(window.physical_cursor_position().is_none());
}
}