use core::num::NonZero;
use bevy_ecs::{
entity::{Entity, VisitEntities, VisitEntitiesMut},
prelude::{Component, ReflectComponent},
};
use bevy_math::{CompassOctant, DVec2, IVec2, UVec2, Vec2};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
#[cfg(feature = "serialize")]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use bevy_utils::tracing::warn;
#[derive(Default, Debug, Component, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Reflect)]
#[reflect(Component, Debug, Default, PartialEq)]
pub struct PrimaryWindow;
#[repr(C)]
#[derive(Default, Copy, Clone, Debug, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
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)
}
}
impl VisitEntities for WindowRef {
fn visit_entities<F: FnMut(Entity)>(&self, mut f: F) {
match self {
Self::Entity(entity) => f(*entity),
Self::Primary => {}
}
}
}
impl VisitEntitiesMut for WindowRef {
fn visit_entities_mut<F: FnMut(&mut Entity)>(&mut self, mut f: F) {
match self {
Self::Entity(entity) => f(entity),
Self::Primary => {}
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct NormalizedWindowRef(Entity);
impl NormalizedWindowRef {
pub fn entity(&self) -> Entity {
self.0
}
}
#[derive(Component, Debug, Clone, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Component, Default, Debug)]
pub struct Window {
pub cursor_options: CursorOptions,
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 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,
}
impl Default for Window {
fn default() -> Self {
Self {
title: "App".to_owned(),
name: None,
cursor_options: Default::default(),
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,
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,
}
}
}
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, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq, Default)]
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 {} is smaller than the minimum width {}",
max_width, min_width
);
max_width = min_width;
}
if max_height < min_height {
warn!(
"The given maximum height {} is smaller than the minimum height {}",
max_height, min_height
);
max_height = min_height;
}
WindowResizeConstraints {
min_width,
min_height,
max_width,
max_height,
}
}
}
#[derive(Debug, Clone, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, Default)]
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, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq)]
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, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq, Default)]
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: f32, physical_height: f32) -> Self {
Self {
physical_width: physical_width as u32,
physical_height: physical_height as u32,
..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<I> From<(I, I)> for WindowResolution
where
I: Into<f32>,
{
fn from((width, height): (I, I)) -> WindowResolution {
WindowResolution::new(width.into(), height.into())
}
}
impl<I> From<[I; 2]> for WindowResolution
where
I: Into<f32>,
{
fn from([width, height]: [I; 2]) -> WindowResolution {
WindowResolution::new(width.into(), height.into())
}
}
impl From<Vec2> for WindowResolution {
fn from(res: Vec2) -> WindowResolution {
WindowResolution::new(res.x, res.y)
}
}
impl From<DVec2> for WindowResolution {
fn from(res: DVec2) -> WindowResolution {
WindowResolution::new(res.x as f32, res.y as f32)
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq, Default)]
pub enum CursorGrabMode {
#[default]
None,
Confined,
Locked,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq, Default)]
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, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq)]
pub enum MonitorSelection {
Current,
Primary,
Index(usize),
Entity(Entity),
}
#[repr(C)]
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Hash, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq, Hash)]
#[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, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq, Hash)]
pub enum CompositeAlphaMode {
#[default]
Auto = 0,
Opaque = 1,
PreMultiplied = 2,
PostMultiplied = 3,
Inherit = 4,
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq)]
pub enum WindowMode {
#[default]
Windowed,
BorderlessFullscreen(MonitorSelection),
SizedFullscreen(MonitorSelection),
Fullscreen(MonitorSelection),
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq)]
pub enum WindowLevel {
AlwaysOnBottom,
#[default]
Normal,
AlwaysOnTop,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq)]
pub enum WindowTheme {
Light,
Dark,
}
#[derive(Debug, Copy, Clone, PartialEq, Reflect)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
#[reflect(Debug, PartialEq, Default)]
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)]
pub struct ClosingWindow;
#[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());
}
}