Compare commits
2 Commits
2107b8f7b0
...
393b4206d3
Author | SHA1 | Date |
---|---|---|
SeanOMik | 393b4206d3 | |
SeanOMik | 782d64f6cf |
|
@ -1,3 +1,5 @@
|
||||||
|
#![feature(associated_type_defaults)]
|
||||||
|
|
||||||
extern crate self as lyra_ecs;
|
extern crate self as lyra_ecs;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use crate::{query::{AsFilter, AsQuery, Fetch, Filter, Query}, Component, ComponentColumn, DynTypeId, Tick, World};
|
||||||
|
|
||||||
|
pub struct ChangedFetcher<'a, T> {
|
||||||
|
col: &'a ComponentColumn,
|
||||||
|
tick: Tick,
|
||||||
|
_phantom: PhantomData<&'a T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Fetch<'a> for ChangedFetcher<'a, T>
|
||||||
|
where
|
||||||
|
T: 'a,
|
||||||
|
{
|
||||||
|
type Item = bool;
|
||||||
|
|
||||||
|
fn dangling() -> Self {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||||
|
let tick = self.col.entity_ticks[entity.0 as usize];
|
||||||
|
tick >= self.tick
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A filter that fetches components that have changed.
|
||||||
|
///
|
||||||
|
/// Since [`AsQuery`] is implemented for `&T`, you can use this query like this:
|
||||||
|
/// ```nobuild
|
||||||
|
/// for ts in world.view::<&T>() {
|
||||||
|
/// println!("Got a &T!");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct Changed<T> {
|
||||||
|
type_id: DynTypeId,
|
||||||
|
_phantom: PhantomData<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Component> Default for Changed<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
type_id: DynTypeId::of::<T>(),
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// manually implemented to avoid a Copy bound on T
|
||||||
|
impl<T> Copy for Changed<T> {}
|
||||||
|
|
||||||
|
// manually implemented to avoid a Clone bound on T
|
||||||
|
impl<T> Clone for Changed<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Component> Changed<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Component> Query for Changed<T>
|
||||||
|
where
|
||||||
|
T: 'static
|
||||||
|
{
|
||||||
|
type Item<'a> = bool;
|
||||||
|
type Fetch<'a> = ChangedFetcher<'a, T>;
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Changed::<T>::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
||||||
|
archetype.has_column(self.type_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn fetch<'a>(&self, w: &'a World, a: &'a crate::archetype::Archetype, _: crate::Tick) -> Self::Fetch<'a> {
|
||||||
|
ChangedFetcher {
|
||||||
|
col: a.get_column(self.type_id).unwrap(),
|
||||||
|
tick: w.current_tick(),
|
||||||
|
_phantom: PhantomData::<&T>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Component> AsQuery for Changed<T> {
|
||||||
|
type Query = Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: Component> Filter for Changed<T> { }
|
||||||
|
|
||||||
|
impl<T: Component> AsFilter for Changed<T> {
|
||||||
|
type Filter = Self;
|
||||||
|
}
|
|
@ -6,3 +6,6 @@ pub use or::*;
|
||||||
|
|
||||||
mod not;
|
mod not;
|
||||||
pub use not::*;
|
pub use not::*;
|
||||||
|
|
||||||
|
mod changed;
|
||||||
|
pub use changed::*;
|
|
@ -89,6 +89,12 @@ pub trait AsQuery {
|
||||||
type Query: Query;
|
type Query: Query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trait for getting the filter of a type.
|
||||||
|
pub trait AsFilter {
|
||||||
|
/// The query for this type
|
||||||
|
type Filter: Filter;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait IntoQuery {
|
pub trait IntoQuery {
|
||||||
fn into_query(self) -> Self;
|
fn into_query(self) -> Self;
|
||||||
}
|
}
|
||||||
|
@ -125,10 +131,22 @@ impl Query for () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Filter for () {
|
||||||
|
type Item<'a> = bool;
|
||||||
|
}
|
||||||
|
|
||||||
impl AsQuery for () {
|
impl AsQuery for () {
|
||||||
type Query = ();
|
type Query = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Filter: Query {
|
||||||
|
type Item<'a> = bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsFilter for () {
|
||||||
|
type Filter = ();
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{World, archetype::Archetype, tests::Vec2};
|
use crate::{World, archetype::Archetype, tests::Vec2};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
use super::{Query, Fetch, AsQuery};
|
use super::{Query, Fetch, AsQuery, Filter, AsFilter};
|
||||||
|
|
||||||
impl<'a, F1> Fetch<'a> for (F1,)
|
/* impl<'a, F1> Fetch<'a> for (F1,)
|
||||||
where
|
where
|
||||||
F1: Fetch<'a>,
|
F1: Fetch<'a>,
|
||||||
{
|
{
|
||||||
|
@ -102,7 +102,7 @@ where
|
||||||
Q2: AsQuery,
|
Q2: AsQuery,
|
||||||
{
|
{
|
||||||
type Query = (Q1::Query, Q2::Query);
|
type Query = (Q1::Query, Q2::Query);
|
||||||
}
|
} */
|
||||||
|
|
||||||
macro_rules! impl_bundle_tuple {
|
macro_rules! impl_bundle_tuple {
|
||||||
( $($name: ident),+ ) => (
|
( $($name: ident),+ ) => (
|
||||||
|
@ -154,10 +154,39 @@ macro_rules! impl_bundle_tuple {
|
||||||
impl<$($name: AsQuery),+> AsQuery for ($($name,)+) {
|
impl<$($name: AsQuery),+> AsQuery for ($($name,)+) {
|
||||||
type Query = ($($name::Query,)+);
|
type Query = ($($name::Query,)+);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
impl<$($name: Filter),+> Filter for ($($name,)+) {
|
||||||
|
//type Item<'a> = ($(<$name as Query>::Item<'a>,)+);
|
||||||
|
//type Fetch<'a> = ($(<$name as Query>::Fetch<'a>,)+);
|
||||||
|
|
||||||
|
/* fn new() -> Self {
|
||||||
|
( $($name::new(),)+ )
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
|
||||||
|
let ( $($name,)+ ) = self;
|
||||||
|
|
||||||
|
// this is the only way I could figure out how to do an 'and'
|
||||||
|
let bools = vec![$($name.can_visit_archetype(archetype),)+];
|
||||||
|
bools.iter().all(|b| *b)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||||
|
let ( $($name,)+ ) = self;
|
||||||
|
( $($name.fetch(world, archetype, tick),)+ )
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<$($name: AsFilter),+> AsFilter for ($($name,)+) {
|
||||||
|
type Filter = ($($name::Filter,)+);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hopefully up to 16 queries in a SINGLE view is enough
|
// Hopefully up to 16 queries in a SINGLE view is enough
|
||||||
|
impl_bundle_tuple! { Q1 }
|
||||||
|
impl_bundle_tuple! { Q1, Q2 }
|
||||||
impl_bundle_tuple! { Q1, Q2, Q3 }
|
impl_bundle_tuple! { Q1, Q2, Q3 }
|
||||||
impl_bundle_tuple! { Q1, Q2, Q3, Q4 }
|
impl_bundle_tuple! { Q1, Q2, Q3, Q4 }
|
||||||
impl_bundle_tuple! { Q1, Q2, Q3, Q4, Q5 }
|
impl_bundle_tuple! { Q1, Q2, Q3, Q4, Q5 }
|
||||||
|
|
|
@ -2,11 +2,11 @@ use std::ops::Range;
|
||||||
|
|
||||||
use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, Tick};
|
use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, Tick};
|
||||||
|
|
||||||
use super::{Query, Fetch, AsQuery};
|
use super::{AsFilter, AsQuery, Fetch, Filter, Query};
|
||||||
|
|
||||||
pub type View<'a, Q, F = ()> = ViewState<'a, <Q as AsQuery>::Query, <F as AsQuery>::Query>;
|
pub type View<'a, Q, F = ()> = ViewState<'a, <Q as AsQuery>::Query, <F as AsQuery>::Query>;
|
||||||
|
|
||||||
pub struct ViewState<'a, Q: Query, F: Query> {
|
pub struct ViewState<'a, Q: Query, F: Filter> {
|
||||||
world: &'a World,
|
world: &'a World,
|
||||||
query: Q,
|
query: Q,
|
||||||
filter: F,
|
filter: F,
|
||||||
|
@ -16,7 +16,7 @@ pub struct ViewState<'a, Q: Query, F: Query> {
|
||||||
impl<'a, Q, F> ViewState<'a, Q, F>
|
impl<'a, Q, F> ViewState<'a, Q, F>
|
||||||
where
|
where
|
||||||
Q: Query,
|
Q: Query,
|
||||||
F: Query,
|
F: Filter,
|
||||||
{
|
{
|
||||||
pub fn new(world: &'a World, query: Q, filter: F, archetypes: Vec<&'a Archetype>) -> Self {
|
pub fn new(world: &'a World, query: Q, filter: F, archetypes: Vec<&'a Archetype>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -38,7 +38,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes `self`, adding a filter to the view.
|
/// Consumes `self`, adding a filter to the view.
|
||||||
pub fn with<U: AsQuery>(self, filter: U::Query) -> ViewState<'a, Q, (F, U::Query)> {
|
pub fn with<U: AsFilter>(self, filter: U::Filter) -> ViewState<'a, Q, (F, U::Filter)> {
|
||||||
ViewState::new(self.world, self.query, (self.filter, filter), self.archetypes)
|
ViewState::new(self.world, self.query, (self.filter, filter), self.archetypes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,18 +46,19 @@ where
|
||||||
impl<'a, Q, F> IntoIterator for ViewState<'a, Q, F>
|
impl<'a, Q, F> IntoIterator for ViewState<'a, Q, F>
|
||||||
where
|
where
|
||||||
Q: Query,
|
Q: Query,
|
||||||
F: Query,
|
F: Filter,
|
||||||
{
|
{
|
||||||
type Item = Q::Item<'a>;
|
type Item = Q::Item<'a>;
|
||||||
|
|
||||||
type IntoIter = ViewIter<'a, Q, F>;
|
type IntoIter = ViewIter<'a, Q, F>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
let tick = self.world.tick_tracker().tick_when(Q::MUTATES);
|
//let tick = self.world.tick_tracker().tick_when(Q::MUTATES);
|
||||||
|
|
||||||
ViewIter {
|
ViewIter {
|
||||||
world: self.world,
|
world: self.world,
|
||||||
tick,
|
tick: self.world.current_tick(),
|
||||||
|
has_ticked: false,
|
||||||
query: self.query,
|
query: self.query,
|
||||||
filter: self.filter,
|
filter: self.filter,
|
||||||
fetcher: None,
|
fetcher: None,
|
||||||
|
@ -69,9 +70,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ViewIter<'a, Q: Query, F: Query> {
|
pub struct ViewIter<'a, Q: Query, F: Filter> {
|
||||||
world: &'a World,
|
world: &'a World,
|
||||||
tick: Tick,
|
tick: Tick,
|
||||||
|
has_ticked: bool,
|
||||||
query: Q,
|
query: Q,
|
||||||
filter: F,
|
filter: F,
|
||||||
fetcher: Option<Q::Fetch<'a>>,
|
fetcher: Option<Q::Fetch<'a>>,
|
||||||
|
@ -84,7 +86,7 @@ pub struct ViewIter<'a, Q: Query, F: Query> {
|
||||||
impl<'a, Q, F> Iterator for ViewIter<'a, Q, F>
|
impl<'a, Q, F> Iterator for ViewIter<'a, Q, F>
|
||||||
where
|
where
|
||||||
Q: Query,
|
Q: Query,
|
||||||
F: Query,
|
F: Filter,
|
||||||
{
|
{
|
||||||
type Item = Q::Item<'a>;
|
type Item = Q::Item<'a>;
|
||||||
|
|
||||||
|
@ -110,6 +112,13 @@ where
|
||||||
let entity_index = ArchetypeEntityId(entity_index);
|
let entity_index = ArchetypeEntityId(entity_index);
|
||||||
|
|
||||||
if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) {
|
if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) {
|
||||||
|
// only tick the world if the filter has fetched, and when the world hasn't
|
||||||
|
// been ticked yet.
|
||||||
|
if Q::MUTATES && !self.has_ticked {
|
||||||
|
self.has_ticked = true;
|
||||||
|
self.tick = self.world.tick();
|
||||||
|
}
|
||||||
|
|
||||||
let i = unsafe { fetcher.get_item(entity_index) };
|
let i = unsafe { fetcher.get_item(entity_index) };
|
||||||
return Some(i);
|
return Some(i);
|
||||||
}
|
}
|
||||||
|
@ -147,17 +156,17 @@ pub struct ViewOne<'a, Q: Query> {
|
||||||
|
|
||||||
impl<'a, Q: Query> ViewOne<'a, Q> {
|
impl<'a, Q: Query> ViewOne<'a, Q> {
|
||||||
pub fn new(world: &'a World, entity: EntityId, query: Q) -> Self {
|
pub fn new(world: &'a World, entity: EntityId, query: Q) -> Self {
|
||||||
let tick = world.tick_tracker().tick_when(Q::MUTATES);
|
//let tick = world.tick_tracker().tick_when(Q::MUTATES);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
world,
|
world,
|
||||||
tick,
|
tick: world.current_tick(),
|
||||||
entity,
|
entity,
|
||||||
query
|
query
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self) -> Option<Q::Item<'a>> {
|
pub fn get(self) -> Option<Q::Item<'a>> {
|
||||||
if let Some(record) = self.world.entities.arch_index.get(&self.entity) {
|
if let Some(record) = self.world.entities.arch_index.get(&self.entity) {
|
||||||
let arch = self.world.archetypes.get(&record.id)
|
let arch = self.world.archetypes.get(&record.id)
|
||||||
.expect("An invalid record was specified for an entity");
|
.expect("An invalid record was specified for an entity");
|
||||||
|
@ -165,6 +174,9 @@ impl<'a, Q: Query> ViewOne<'a, Q> {
|
||||||
if self.query.can_visit_archetype(arch) {
|
if self.query.can_visit_archetype(arch) {
|
||||||
let mut fetch = unsafe { self.query.fetch(self.world, arch, self.tick) };
|
let mut fetch = unsafe { self.query.fetch(self.world, arch, self.tick) };
|
||||||
if fetch.can_visit_item(record.index) {
|
if fetch.can_visit_item(record.index) {
|
||||||
|
// only tick the world when something is actually fetched.
|
||||||
|
self.world.tick();
|
||||||
|
|
||||||
return Some(unsafe { fetch.get_item(record.index) });
|
return Some(unsafe { fetch.get_item(record.index) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::marker::PhantomData;
|
||||||
|
|
||||||
use lyra_ecs_derive::Component;
|
use lyra_ecs_derive::Component;
|
||||||
|
|
||||||
|
use crate::query::Filter;
|
||||||
use crate::query::Query;
|
use crate::query::Query;
|
||||||
use crate::query::ViewState;
|
use crate::query::ViewState;
|
||||||
use crate::Entity;
|
use crate::Entity;
|
||||||
|
@ -98,7 +99,7 @@ impl World {
|
||||||
impl<'a, Q, F> ViewState<'a, Q, F>
|
impl<'a, Q, F> ViewState<'a, Q, F>
|
||||||
where
|
where
|
||||||
Q: Query,
|
Q: Query,
|
||||||
F: Query,
|
F: Filter,
|
||||||
{
|
{
|
||||||
/// Consumes `self` to return a view that fetches the relation to a specific target entity.
|
/// Consumes `self` to return a view that fetches the relation to a specific target entity.
|
||||||
pub fn relates_to<R>(self, target: Entity) -> ViewState<'a, (Q, QueryRelatesTo<R>), F>
|
pub fn relates_to<R>(self, target: Entity) -> ViewState<'a, (Q, QueryRelatesTo<R>), F>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{any::Any, marker::PhantomData, ptr::NonNull};
|
use std::{any::Any, marker::PhantomData, ptr::NonNull};
|
||||||
|
|
||||||
use paste::paste;
|
use paste::paste;
|
||||||
use crate::{World, Access, ResourceObject, query::{Query, ViewState, ResMut, Res}};
|
use crate::{World, Access, ResourceObject, query::{Query, Filter, ViewState, ResMut, Res}};
|
||||||
|
|
||||||
use super::{System, IntoSystem};
|
use super::{System, IntoSystem};
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P }
|
||||||
impl<'c, Q, F> FnArgFetcher for ViewState<'c, Q, F>
|
impl<'c, Q, F> FnArgFetcher for ViewState<'c, Q, F>
|
||||||
where
|
where
|
||||||
Q: Query + 'static,
|
Q: Query + 'static,
|
||||||
F: Query + 'static,
|
F: Filter + 'static,
|
||||||
{
|
{
|
||||||
type State = (Q, F);
|
type State = (Q, F);
|
||||||
type Arg<'a, 'state> = ViewState<'a, Q, F>;
|
type Arg<'a, 'state> = ViewState<'a, Q, F>;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{any::TypeId, collections::HashMap, ptr::NonNull};
|
||||||
|
|
||||||
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
||||||
|
|
||||||
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker};
|
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsFilter, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker};
|
||||||
|
|
||||||
/// The id of the entity for the Archetype.
|
/// The id of the entity for the Archetype.
|
||||||
///
|
///
|
||||||
|
@ -347,9 +347,9 @@ impl World {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// View into the world for a set of entities that satisfy the query and the filter.
|
/// View into the world for a set of entities that satisfy the query and the filter.
|
||||||
pub fn filtered_view<Q: AsQuery, F: AsQuery>(&self) -> ViewState<Q::Query, F::Query> {
|
pub fn filtered_view<Q: AsQuery, F: AsFilter>(&self) -> ViewState<Q::Query, F::Filter> {
|
||||||
let archetypes = self.archetypes.values().collect();
|
let archetypes = self.archetypes.values().collect();
|
||||||
ViewState::<Q::Query, F::Query>::new(self, Q::Query::new(), F::Query::new(), archetypes)
|
ViewState::<Q::Query, F::Filter>::new(self, Q::Query::new(), F::Filter::new(), archetypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// View into the world for a set of entities that satisfy the queries.
|
/// View into the world for a set of entities that satisfy the queries.
|
||||||
|
@ -360,9 +360,9 @@ impl World {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// View into the world for a set of entities that satisfy the queries.
|
/// View into the world for a set of entities that satisfy the queries.
|
||||||
pub fn filtered_view_iter<Q: AsQuery, F: AsQuery>(&self) -> ViewIter<Q::Query, F::Query> {
|
pub fn filtered_view_iter<Q: AsQuery, F: AsFilter>(&self) -> ViewIter<Q::Query, F::Filter> {
|
||||||
let archetypes = self.archetypes.values().collect();
|
let archetypes = self.archetypes.values().collect();
|
||||||
let v = ViewState::new(self, Q::Query::new(), F::Query::new(), archetypes);
|
let v = ViewState::new(self, Q::Query::new(), F::Filter::new(), archetypes);
|
||||||
v.into_iter()
|
v.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use glam::{IVec2, Vec2};
|
use glam::{IVec2, Vec2};
|
||||||
use lyra_ecs::{query::{Entities, ResMut, TickOf}, Component, World};
|
use lyra_ecs::{query::{filter::Changed, Entities, ResMut, TickOf}, Component, World};
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
use winit::{
|
use winit::{
|
||||||
dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError, monitor::VideoModeHandle, window::{Fullscreen, Window}
|
dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError, monitor::VideoModeHandle, window::{CustomCursor, Fullscreen, Window, WindowAttributes}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel};
|
pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel};
|
||||||
|
@ -19,7 +19,7 @@ pub struct Area {
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum Size {
|
pub enum Size {
|
||||||
Physical { x: i32, y: i32 },
|
Physical { x: u32, y: u32 },
|
||||||
Logical { x: f64, y: f64 },
|
Logical { x: f64, y: f64 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,9 +38,18 @@ impl Into<winit::dpi::Size> for Size {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<winit::dpi::Size> for Size {
|
||||||
|
fn from(value: winit::dpi::Size) -> Self {
|
||||||
|
match value {
|
||||||
|
winit::dpi::Size::Physical(physical_position) => Self::new_physical(physical_position.width, physical_position.height),
|
||||||
|
winit::dpi::Size::Logical(logical_position) => Self::new_logical(logical_position.width, logical_position.height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Size {
|
impl Size {
|
||||||
pub fn new_physical(x: i32, y: i32) -> Self {
|
pub fn new_physical(x: u32, y: u32) -> Self {
|
||||||
Self::Physical { x, y }
|
Self::Physical { x, y }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +79,15 @@ impl Into<winit::dpi::Position> for Position {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<winit::dpi::Position> for Position {
|
||||||
|
fn from(value: winit::dpi::Position) -> Self {
|
||||||
|
match value {
|
||||||
|
winit::dpi::Position::Physical(physical_position) => Self::new_physical(physical_position.x, physical_position.y),
|
||||||
|
winit::dpi::Position::Logical(logical_position) => Self::new_logical(logical_position.x, logical_position.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Position {
|
impl Position {
|
||||||
pub fn new_physical(x: i32, y: i32) -> Self {
|
pub fn new_physical(x: i32, y: i32) -> Self {
|
||||||
Self::Physical { x, y }
|
Self::Physical { x, y }
|
||||||
|
@ -84,331 +102,344 @@ impl Position {
|
||||||
#[derive(Clone, Component)]
|
#[derive(Clone, Component)]
|
||||||
pub struct PrimaryWindow;
|
pub struct PrimaryWindow;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum CursorAppearance {
|
||||||
|
Icon(CursorIcon),
|
||||||
|
Custom(CustomCursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Cursor {
|
||||||
|
/// Modifies the cursor icon of the window.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **iOS / Android / Orbital:** Unsupported.
|
||||||
|
/// * **Web:** Custom cursors have to be loaded and decoded first, until then the previous cursor is shown.
|
||||||
|
appearance: CursorAppearance,
|
||||||
|
|
||||||
|
/// Gets/sets the window's cursor grab mode
|
||||||
|
///
|
||||||
|
/// # Tip:
|
||||||
|
/// First try confining the cursor, and if it fails, try locking it instead.
|
||||||
|
grab: CursorGrabMode,
|
||||||
|
|
||||||
|
/// Gets/sets whether the window catches cursor events.
|
||||||
|
///
|
||||||
|
/// If `false`, events are passed through the window such that any other window behind it
|
||||||
|
/// receives them. By default hittest is enabled.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
||||||
|
hittest: bool,
|
||||||
|
|
||||||
|
/// Gets/sets the cursor's visibility
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **Windows / X11 / Wayland:** The cursor is only hidden within the confines of the window.
|
||||||
|
/// * **macOS:** The cursor is hidden as long as the window has input focus, even if the
|
||||||
|
/// cursor is outside of the window.
|
||||||
|
/// * **iOS / Android:** Unsupported.
|
||||||
|
visible: bool,
|
||||||
|
//cursor_position: Option<PhysicalPosition<i32>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Options that the window will be created with.
|
/// Options that the window will be created with.
|
||||||
#[derive(Clone, Component)]
|
#[derive(Clone, Component)]
|
||||||
pub struct WindowOptions {
|
pub struct WindowOptions {
|
||||||
/// Prevents the window contents from being captured by other apps.
|
/// The enabled window buttons.
|
||||||
///
|
///
|
||||||
/// Platform-specific:
|
/// Platform-specific
|
||||||
/// * macOS: if false, NSWindowSharingNone is used but doesn’t completely prevent all apps
|
/// * **Wayland / X11 / Orbital:** Not implemented. Always set to [`WindowButtons::all`].
|
||||||
/// from reading the window content, for instance, QuickTime.
|
/// * **Web / iOS / Android:** Unsupported. Always set to [`WindowButtons::all`].
|
||||||
/// * iOS / Android / x11 / Wayland / Web / Orbital: Unsupported.
|
enabled_buttons: WindowButtons,
|
||||||
pub content_protected: bool,
|
|
||||||
|
|
||||||
/// Set grabbing mode on the cursor preventing it from leaving the window.
|
/// Gets or sets if the window is in focus.
|
||||||
pub cursor_grab: CursorGrabMode,
|
|
||||||
|
|
||||||
/// Modifies whether the window catches cursor events.
|
|
||||||
|
|
||||||
/// If true, the window will catch the cursor events. If false, events are passed through
|
|
||||||
/// the window such that any otherwindow behind it receives them. By default hittest is enabled.
|
|
||||||
///
|
///
|
||||||
/// Platform-specific:
|
/// Platform-specific
|
||||||
/// * iOS / Android / Web / X11 / Orbital: Unsupported.
|
/// * **iOS / Android / Wayland / Orbital:** Unsupported.
|
||||||
pub cursor_hittest: bool,
|
focused: bool,
|
||||||
|
|
||||||
/// Modifies the cursor icon of the window.
|
/// Gets or sets the fullscreen setting.
|
||||||
///
|
///
|
||||||
/// Platform-specific:
|
/// If this is `None`, the window is windowed.
|
||||||
/// * iOS / Android / Orbital: Unsupported.
|
|
||||||
/// * Web: Custom cursors have to be loaded and decoded first, until then the previous
|
|
||||||
/// cursor is shown.
|
|
||||||
pub cursor: winit::window::Cursor,
|
|
||||||
|
|
||||||
/// The cursor’s visibility.
|
|
||||||
/// If false, this will hide the cursor. If true, this will show the cursor.
|
|
||||||
///
|
///
|
||||||
/// Platform-specific:
|
/// Platform-specific
|
||||||
/// * Windows: The cursor is only hidden within the confines of the window.
|
/// * **iOS:** Can only be called on the main thread.
|
||||||
/// * X11: The cursor is only hidden within the confines of the window.
|
/// * **Android / Orbital:** Will always return None.
|
||||||
/// * Wayland: The cursor is only hidden within the confines of the window.
|
/// * **Wayland:** Can return Borderless(None) when there are no monitors.
|
||||||
/// * macOS: The cursor is hidden as long as the window has input focus, even if
|
/// * **Web:** Can only return None or Borderless(None).
|
||||||
/// the cursor is outside of the window.
|
fullscreen: Option<Fullscreen>,
|
||||||
/// * iOS / Android / Orbital: Unsupported.
|
|
||||||
pub cursor_visible: bool,
|
|
||||||
|
|
||||||
/// The window’s current visibility state.
|
/// Gets/sets the position of the top-left hand corner of the window’s client area relative to
|
||||||
|
/// the top-left hand corner of the desktop.
|
||||||
|
///
|
||||||
|
/// Note that the top-left hand corner of the desktop is not necessarily the same
|
||||||
|
/// as the screen. If the user uses a desktop with multiple monitors, the top-left
|
||||||
|
/// hand corner of the desktop is the top-left hand corner of the monitor at the
|
||||||
|
/// top-left of the desktop.
|
||||||
|
///
|
||||||
|
/// If this is none, the position will be chosen by the windowing manager at creation, then set
|
||||||
|
/// when the window is created.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **iOS:** Value is the top left coordinates of the window’s safe area in the screen
|
||||||
|
/// space coordinate system.
|
||||||
|
/// * **Web:** Value is the top-left coordinates relative to the viewport. Note: this will be
|
||||||
|
/// the same value as [`WindowOptions::outer_position`].
|
||||||
|
/// * **Android / Wayland:** Unsupported.
|
||||||
|
inner_position: Option<Position>,
|
||||||
|
|
||||||
|
/// Gets/sets the size of the view in the window.
|
||||||
|
///
|
||||||
|
/// The size does not include the window title bars and borders.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **Web:** The size of the canvas element. Doesn’t account for CSS `transform`.
|
||||||
|
size: Option<Size>,
|
||||||
|
|
||||||
|
/// Gets/sets if the window has decorations.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **iOS / Android / Web:** Always set to `true`.
|
||||||
|
decorated: bool,
|
||||||
|
|
||||||
|
/// Gets/sets the window's current maximized state
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **iOS / Android / Web:** Unsupported.
|
||||||
|
maximized: bool,
|
||||||
|
|
||||||
|
/// Gets/sets the window's current minimized state.
|
||||||
|
///
|
||||||
|
/// Is `None` if the minimized state could not be determined.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **Wayland:** always `None`, un-minimize is unsupported.
|
||||||
|
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
||||||
|
minimized: Option<bool>,
|
||||||
|
|
||||||
|
/// Gets/sets the window's current resizable state
|
||||||
|
///
|
||||||
|
/// If this is false, the window can still be resized by changing [`WindowOptions::size`].
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// Setting this only has an affect on desktop platforms.
|
||||||
|
///
|
||||||
|
/// * **X11:** Due to a bug in XFCE, setting this has no effect..
|
||||||
|
/// * **iOS / Android / Web:** Unsupported.
|
||||||
|
resizable: bool,
|
||||||
|
|
||||||
|
/// Gets/sets the window's current visibility state.
|
||||||
|
///
|
||||||
|
/// `None` means it couldn't be determined.
|
||||||
///
|
///
|
||||||
/// Platform-specific
|
/// Platform-specific
|
||||||
/// * **X11:** Not implemented.
|
/// * **X11:** Not implemented.
|
||||||
/// * **Wayland / iOS / Android / Web:** Unsupported.
|
/// * **Wayland / Android / Web:** Unsupported.
|
||||||
pub visible: bool,
|
/// * **iOS:** Setting is not implemented, getting is unsupported.
|
||||||
|
visible: Option<bool>,
|
||||||
|
|
||||||
/// The window's transparency state.
|
/// Gets/sets the position of the top-left hand corner of the window relative to
|
||||||
|
/// the top-left hand corner of the desktop.
|
||||||
|
///
|
||||||
|
/// Note that the top-left hand corner of the desktop is not necessarily the same
|
||||||
|
/// as the screen. If the user uses a desktop with multiple monitors, the top-left
|
||||||
|
/// hand corner of the desktop is the top-left hand corner of the monitor at the
|
||||||
|
/// top-left of the desktop.
|
||||||
|
///
|
||||||
|
/// If this is none, the position will be chosen by the windowing manager at creation, then set
|
||||||
|
/// when the window is created.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **iOS:** Value is the top left coordinates of the window’s safe area in the screen
|
||||||
|
/// space coordinate system.
|
||||||
|
/// * **Web:** Value is the top-left coordinates relative to the viewport.
|
||||||
|
/// * **Android / Wayland:** Unsupported.
|
||||||
|
outer_position: Option<Position>,
|
||||||
|
|
||||||
|
/// Gets/sets the window resize increments.
|
||||||
|
///
|
||||||
|
/// This is a niche constraint hint usually employed by terminal emulators and other apps
|
||||||
|
/// that need “blocky” resizes.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **macOS:** Increments are converted to logical size and then macOS rounds them to whole numbers.
|
||||||
|
/// * **Wayland:** Not implemented, always `None`.
|
||||||
|
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
||||||
|
resize_increments: Option<Size>,
|
||||||
|
|
||||||
|
/// Gets/sets the scale factor.
|
||||||
|
///
|
||||||
|
/// The scale factor is the ratio of physical pixels to logical pixels.
|
||||||
|
/// See [winit docs](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.scale_factor)
|
||||||
|
/// for more information.
|
||||||
|
scale_factor: f64,
|
||||||
|
|
||||||
|
/// Gets/sets the window's blur state.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **Android / iOS / X11 / Web / Windows:** Unsupported.
|
||||||
|
/// * **Wayland:** Only works with org_kde_kwin_blur_manager protocol.
|
||||||
|
blur: bool,
|
||||||
|
|
||||||
|
/// Prevents the window contents from being captured by other apps.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **macOS:** if false, [`NSWindowSharingNone`](https://developer.apple.com/documentation/appkit/nswindowsharingtype/nswindowsharingnone)
|
||||||
|
/// is used but doesn’t completely prevent all apps from reading the window content,
|
||||||
|
/// for instance, QuickTime.
|
||||||
|
/// * **iOS / Android / x11 / Wayland / Web / Orbital:** Unsupported.
|
||||||
|
content_protected: bool,
|
||||||
|
|
||||||
|
cursor: Cursor,
|
||||||
|
|
||||||
|
/// Sets whether the window should get IME events
|
||||||
|
///
|
||||||
|
/// When IME is allowed, the window will receive [`Ime`](winit::event::WindowEvent::Ime)
|
||||||
|
/// events, and during the preedit phase the window will NOT get KeyboardInput events.
|
||||||
|
/// The window should allow IME while it is expecting text input.
|
||||||
|
///
|
||||||
|
/// When IME is not allowed, the window won’t receive [`Ime`](winit::event::WindowEvent::Ime)
|
||||||
|
/// events, and will receive [`KeyboardInput`](winit::event::WindowEvent::KeyboardInput) events
|
||||||
|
/// for every keypress instead. Not allowing IME is useful for games for example.
|
||||||
|
/// IME is not allowed by default.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **macOS:** IME must be enabled to receive text-input where dead-key sequences are combined.
|
||||||
|
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
||||||
|
/// * **X11:** Enabling IME will disable dead keys reporting during compose.
|
||||||
|
ime_allowed: bool,
|
||||||
|
|
||||||
|
/// Sets area of IME candidate box in window client area coordinates relative to the top left.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **X11:** - area is not supported, only position.
|
||||||
|
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
||||||
|
ime_cursor_area: Option<Area>,
|
||||||
|
|
||||||
|
/// Gets/sets the minimum size of the window.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **iOS / Android / Orbital:** Unsupported.
|
||||||
|
min_size: Option<Size>,
|
||||||
|
|
||||||
|
/// Gets/sets the maximum size of the window.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **iOS / Android / Orbital:** Unsupported.
|
||||||
|
max_size: Option<Size>,
|
||||||
|
|
||||||
|
/// Gets/sets the current window theme.
|
||||||
|
///
|
||||||
|
/// Specify `None` to reset the theme to the system default. May also be `None` on unsupported
|
||||||
|
/// platforms.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **Wayland:** Sets the theme for the client side decorations. Using `None` will use dbus
|
||||||
|
/// to get the system preference.
|
||||||
|
/// * **X11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used,
|
||||||
|
/// it will default to [`Theme::Dark`](winit::window::Theme::Dark).
|
||||||
|
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
||||||
|
theme: Option<Theme>,
|
||||||
|
|
||||||
|
/// Gets/sets the title of the window.
|
||||||
|
///
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **iOS / Android:** Unsupported.
|
||||||
|
/// * **X11 / Wayland / Web:** Cannot get, will always be an empty string.
|
||||||
|
title: String,
|
||||||
|
|
||||||
|
/// Gets/sets the window's transparency state.
|
||||||
///
|
///
|
||||||
/// This is just a hint that may not change anything about the window transparency, however
|
/// This is just a hint that may not change anything about the window transparency, however
|
||||||
/// doing a mismatch between the content of your window and this hint may result in visual
|
/// doing a mismatch between the content of your window and this hint may result in visual
|
||||||
/// artifacts.
|
/// artifacts.
|
||||||
///
|
///
|
||||||
/// The default value follows the [`winit::window::WindowAttributes::with_transparent`].
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
/// Platform-specific
|
||||||
/// * **macOS:** This will reset the window’s background color.
|
/// * **macOS:** This will reset the window’s background color.
|
||||||
/// * **Web / iOS / Android:** Unsupported.
|
/// * **Web / iOS / Android:** Unsupported.
|
||||||
/// * **X11:** Can only be set while building the window, with
|
/// * **X11:** Can only be set while building the window.
|
||||||
/// [`winit::window::WindowAttributes::with_transparent`].
|
transparent: bool,
|
||||||
pub transparent: bool,
|
|
||||||
|
|
||||||
/// The current blur state of the window
|
/// Sets the window's icon.
|
||||||
///
|
///
|
||||||
/// If `true`, this will make the transparent window background blurry.
|
/// On Windows and X11, this is typically the small icon in the top-left corner of
|
||||||
|
/// the titlebar.
|
||||||
///
|
///
|
||||||
/// Platform-specific
|
/// Platform-specific
|
||||||
/// * **Android / iOS / X11 / Web / Windows:** Unsupported.
|
/// * **iOS / Android / Web / Wayland / macOS / Orbital:** Unsupported.
|
||||||
/// * **Wayland:** Only works with org_kde_kwin_blur_manager protocol.
|
/// * **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it’s
|
||||||
pub blur: bool,
|
/// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
|
||||||
|
/// * **X11:** Has no universal guidelines for icon sizes, so you’re at the whims of
|
||||||
/// Turn window decorations on or off.
|
/// the WM. That said, it’s usually in the same ballpark as on Windows.
|
||||||
/// Enable/disable window decorations provided by the server or Winit. By default this is enabled.
|
window_icon: Option<Icon>,
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * iOS / Android / Web: No effect.
|
|
||||||
pub decorations: bool,
|
|
||||||
|
|
||||||
/// Sets the enabled window buttons.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * Wayland / X11 / Orbital: Not implemented.
|
|
||||||
/// * Web / iOS / Android: Unsupported.
|
|
||||||
pub enabled_buttons: WindowButtons,
|
|
||||||
|
|
||||||
/// The fullscreen settings for the monitor.
|
|
||||||
///
|
|
||||||
/// Set to `None` for windowed.
|
|
||||||
pub fullscreen: Option<Fullscreen>,
|
|
||||||
|
|
||||||
/// Sets whether the window should get IME events.
|
|
||||||
///
|
|
||||||
/// If its allowed, the window will receive Ime events instead of KeyboardInput events.
|
|
||||||
/// This should only be allowed if the window is expecting text input.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * iOS / Android / Web / Orbital: Unsupported.
|
|
||||||
pub ime_allowed: bool,
|
|
||||||
|
|
||||||
/// Set the IME cursor editing area, where the `position` is the top left corner of that
|
|
||||||
/// area and `size` is the size of this area starting from the position. An example of such
|
|
||||||
/// area could be a input field in the UI or line in the editor.
|
|
||||||
///
|
|
||||||
/// The windowing system could place a candidate box close to that area, but try to not
|
|
||||||
/// obscure the specified area, so the user input to it stays visible.
|
|
||||||
///
|
|
||||||
/// The candidate box is the window / popup / overlay that allows you to select the desired
|
|
||||||
/// characters. The look of this box may differ between input devices, even on the same
|
|
||||||
/// platform.
|
|
||||||
///
|
|
||||||
/// (Apple’s official term is “candidate window”, see their chinese and japanese guides).
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **X11:** - area is not supported, only position.
|
|
||||||
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
|
||||||
pub ime_cursor_area: Area,
|
|
||||||
|
|
||||||
/// Modifies the inner size of the window.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * iOS / Android: Unsupported.
|
|
||||||
/// * Web: Sets the size of the canvas element.
|
|
||||||
pub inner_size: Size,
|
|
||||||
|
|
||||||
/// Sets a maximum dimension size for the window.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * iOS / Android / Web / Orbital: Unsupported.
|
|
||||||
pub max_inner_size: Option<Size>,
|
|
||||||
|
|
||||||
/// Sets a minimum dimension size for the window.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * iOS / Android / Web / Orbital: Unsupported.
|
|
||||||
pub min_inner_size: Option<Size>,
|
|
||||||
|
|
||||||
/// The top-left hand corner of the window relative to the top-left hand corner of the desktop.
|
|
||||||
///
|
|
||||||
/// Note that the top-left hand corner of the desktop is not necessarily the same as the
|
|
||||||
/// screen. If the user uses a desktop with multiple monitors, the top-left hand corner of
|
|
||||||
/// the desktop is the top-left hand corner of the monitor at the top-left of the desktop.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS:** Can only be called on the main thread. Returns the top left coordinates of
|
|
||||||
/// the window in the screen space coordinate system.
|
|
||||||
/// * **Web:** Returns the top-left coordinates relative to the viewport.
|
|
||||||
/// * **Android / Wayland:** Always returns NotSupportedError.
|
|
||||||
pub outer_position: Option<Position>,
|
|
||||||
|
|
||||||
/// Returns the position of the top-left hand corner of the window’s client area relative to
|
|
||||||
/// the top-left hand corner of the desktop.
|
|
||||||
///
|
|
||||||
/// The same conditions that apply to `WindowOptions::outer_position` apply to this.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * **iOS:** Can only be called on the main thread. Returns the top left coordinates of the
|
|
||||||
/// window’s safe area in the screen space coordinate system.
|
|
||||||
/// * **Web:** Returns the top-left coordinates relative to the viewport.
|
|
||||||
/// * **Android / Wayland:** Always returns NotSupportedError.
|
|
||||||
pub inner_position: Option<Position>,
|
|
||||||
|
|
||||||
/// Sets the window to maximized or back.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * iOS / Android / Web / Orbital: Unsupported.
|
|
||||||
pub maximized: bool,
|
|
||||||
|
|
||||||
/// Sets the window to minimized or back.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * iOS / Android / Web / Orbital: Unsupported.
|
|
||||||
pub minimized: bool,
|
|
||||||
|
|
||||||
/// Modifies the position of the window.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * Web: Sets the top-left coordinates relative to the viewport.
|
|
||||||
/// * Android / Wayland: Unsupported.
|
|
||||||
//pub outer_position: Vec2,
|
|
||||||
|
|
||||||
/// Sets whether the window is resizable or not.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * X11: Due to a bug in XFCE, this has no effect on Xfwm.
|
|
||||||
/// * iOS / Android / Web: Unsupported.
|
|
||||||
pub resizeable: bool,
|
|
||||||
|
|
||||||
/// Sets window resize increments.
|
|
||||||
/// This is a niche constraint hint usually employed by terminal emulators and other apps that need “blocky” resizes.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * Wayland / Windows: Not implemented.
|
|
||||||
/// * iOS / Android / Web / Orbital: Unsupported.
|
|
||||||
pub resize_increments: Option<Size>,
|
|
||||||
|
|
||||||
/// Sets the current window theme. Use None to fallback to system default.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * macOS: This is an app-wide setting.
|
|
||||||
/// * x11: If None is used, it will default to Theme::Dark.
|
|
||||||
/// * iOS / Android / Web / x11 / Orbital: Unsupported.
|
|
||||||
pub theme: Option<Theme>,
|
|
||||||
|
|
||||||
/// Modifies the title of the window.
|
|
||||||
///
|
|
||||||
/// Platform-specific:
|
|
||||||
/// * iOS / Android: Unsupported.
|
|
||||||
pub title: String,
|
|
||||||
|
|
||||||
/// Sets the window icon.
|
|
||||||
/// On Windows and X11, this is typically the small icon in the top-left corner of the titlebar.
|
|
||||||
///
|
|
||||||
/// Platform-specific
|
|
||||||
/// * iOS / Android / Web / Wayland / macOS / Orbital: Unsupported.
|
|
||||||
/// * Windows: Sets ICON_SMALL. The base size for a window icon is 16x16, but it’s recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
|
|
||||||
/// * X11: Has no universal guidelines for icon sizes, so you’re at the whims of the WM. That said, it’s usually in the same ballpark as on Windows.
|
|
||||||
pub icon: Option<Icon>,
|
|
||||||
|
|
||||||
/// Change the window level.
|
/// Change the window level.
|
||||||
|
///
|
||||||
/// This is just a hint to the OS, and the system could ignore it.
|
/// This is just a hint to the OS, and the system could ignore it.
|
||||||
pub level: WindowLevel,
|
///
|
||||||
|
/// See [`WindowLevel`] for details.
|
||||||
|
window_level: WindowLevel,
|
||||||
|
|
||||||
/// Get/set the window's focused state.
|
/// Show [window menu](https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu)
|
||||||
pub focused: bool,
|
/// at a specified position.
|
||||||
|
///
|
||||||
|
/// This is the context menu that is normally shown when interacting with the title bar. This is useful when implementing custom decorations.
|
||||||
|
/// Platform-specific
|
||||||
|
/// * **Android / iOS / macOS / Orbital / Wayland / Web / X11:** Unsupported.
|
||||||
|
show_window_menu: Option<Position>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Get whether or not the cursor is inside the window.
|
impl From<winit::window::WindowAttributes> for WindowOptions {
|
||||||
pub cursor_inside_window: bool,
|
fn from(value: winit::window::WindowAttributes) -> Self {
|
||||||
|
Self {
|
||||||
|
enabled_buttons: value.enabled_buttons,
|
||||||
|
focused: false,
|
||||||
|
fullscreen: value.fullscreen,
|
||||||
|
inner_position: None,
|
||||||
|
size: value.inner_size.map(|s| s.into()),
|
||||||
|
decorated: value.decorations,
|
||||||
|
maximized: value.maximized,
|
||||||
|
minimized: None,
|
||||||
|
resizable: value.resizable,
|
||||||
|
visible: Some(value.visible),
|
||||||
|
outer_position: value.position.map(|p| p.into()),
|
||||||
|
resize_increments: value.resize_increments.map(|r| r.into()),
|
||||||
|
scale_factor: 1.0,
|
||||||
|
blur: value.blur,
|
||||||
|
content_protected: value.content_protected,
|
||||||
|
cursor: Cursor {
|
||||||
|
appearance: match value.cursor {
|
||||||
|
winit::window::Cursor::Icon(icon) => CursorAppearance::Icon(icon),
|
||||||
|
winit::window::Cursor::Custom(custom) => CursorAppearance::Custom(custom),
|
||||||
|
},
|
||||||
|
grab: CursorGrabMode::None,
|
||||||
|
hittest: true,
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
ime_allowed: false,
|
||||||
|
ime_cursor_area: None,
|
||||||
|
min_size: value.min_inner_size.map(|m| m.into()),
|
||||||
|
max_size: value.max_inner_size.map(|m| m.into()),
|
||||||
|
theme: value.preferred_theme,
|
||||||
|
title: value.title,
|
||||||
|
transparent: value.transparent,
|
||||||
|
window_icon: value.window_icon,
|
||||||
|
window_level: value.window_level,
|
||||||
|
show_window_menu: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WindowOptions {
|
impl Default for WindowOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self::from(Window::default_attributes())
|
||||||
content_protected: false,
|
|
||||||
cursor_grab: CursorGrabMode::None,
|
|
||||||
cursor_hittest: true,
|
|
||||||
cursor: Default::default(),
|
|
||||||
cursor_visible: true,
|
|
||||||
decorations: true,
|
|
||||||
enabled_buttons: WindowButtons::all(),
|
|
||||||
fullscreen: None,
|
|
||||||
ime_allowed: false,
|
|
||||||
ime_cursor_area: Area::default(),
|
|
||||||
inner_size: Size::new_physical(800, 600),
|
|
||||||
max_inner_size: None,
|
|
||||||
min_inner_size: None,
|
|
||||||
maximized: false,
|
|
||||||
minimized: false,
|
|
||||||
//outer_position: Default::default(),
|
|
||||||
resizeable: false,
|
|
||||||
resize_increments: None,
|
|
||||||
theme: None,
|
|
||||||
title: "Lyra Engine Game".to_string(),
|
|
||||||
icon: None,
|
|
||||||
level: WindowLevel::Normal,
|
|
||||||
focused: false,
|
|
||||||
cursor_inside_window: false,
|
|
||||||
blur: false,
|
|
||||||
inner_position: None,
|
|
||||||
outer_position: None,
|
|
||||||
transparent: false,
|
|
||||||
visible: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowOptions {
|
#[derive(Clone, Component)]
|
||||||
pub(crate) fn as_winit_attributes(&self) -> winit::window::WindowAttributes {
|
struct LastWindow {
|
||||||
let mut att = Window::default_attributes();
|
last: WindowOptions,
|
||||||
|
|
||||||
att.inner_size = Some(self.inner_size.into());
|
|
||||||
|
|
||||||
if let Some(min_inner_size) = self.min_inner_size {
|
|
||||||
att.min_inner_size = Some(min_inner_size.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(max_inner_size) = self.max_inner_size {
|
|
||||||
att.max_inner_size = Some(max_inner_size.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(position) = self.outer_position.clone()
|
|
||||||
.or(self.inner_position.clone())
|
|
||||||
{
|
|
||||||
att.position = Some(position.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
att.resizable = self.resizeable;
|
|
||||||
att.enabled_buttons = self.enabled_buttons.clone();
|
|
||||||
att.title = self.title.clone();
|
|
||||||
att.maximized = self.maximized;
|
|
||||||
att.visible = self.visible;
|
|
||||||
att.transparent = self.transparent;
|
|
||||||
att.blur = self.blur;
|
|
||||||
att.decorations = self.decorations;
|
|
||||||
//att.window_icon = self.icon.clone
|
|
||||||
|
|
||||||
todo!()
|
|
||||||
|
|
||||||
/* winit::window::WindowAttributes {
|
|
||||||
inner_size: Some(self.inner_size.into()),
|
|
||||||
min_inner_size: self.min_inner_size.map(|v| v.into()),
|
|
||||||
max_inner_size: self.max_inner_size.map(|v| v.into()),
|
|
||||||
position: self.outer_position.clone().or(self.inner_position.clone()).map(|v| v.into()), // TODO: sync in system
|
|
||||||
resizable: self.resizeable,
|
|
||||||
enabled_buttons: self.enabled_buttons.clone(),
|
|
||||||
title: self.title.clone(),
|
|
||||||
maximized: self.maximized,
|
|
||||||
visible: self.visible, // TODO: sync in system
|
|
||||||
transparent: self.transparent, // TODO: sync in system
|
|
||||||
blur: self.blur, // TODO: sync in system
|
|
||||||
decorations: self.decorations,
|
|
||||||
window_icon: self.icon.clone(),
|
|
||||||
preferred_theme: self.theme,
|
|
||||||
resize_increments: self.resize_increments.map(|v| v.into()),
|
|
||||||
content_protected: self.content_protected,
|
|
||||||
window_level: self.level,
|
|
||||||
active: false, // TODO
|
|
||||||
cursor: self.cursor.clone(),
|
|
||||||
fullscreen: self.fullscreen.clone(),
|
|
||||||
parent_window: todo!(),
|
|
||||||
platform_specific: todo!(),
|
|
||||||
} */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -445,7 +476,7 @@ fn vec2_to_logical_size_op(size: Option<Vec2>) -> Option<LogicalSize<f32>> {
|
||||||
|
|
||||||
/// Set the cursor grab of a window depending on the platform.
|
/// Set the cursor grab of a window depending on the platform.
|
||||||
/// This will also modify the parameter `grab` to ensure it matches what the platform can support
|
/// This will also modify the parameter `grab` to ensure it matches what the platform can support
|
||||||
fn set_cursor_grab(window: &Window, grab: &mut CursorGrabMode) -> anyhow::Result<()> {
|
/* fn set_cursor_grab(window: &Window, grab: &mut CursorGrabMode) -> anyhow::Result<()> {
|
||||||
if *grab != CursorGrabMode::None {
|
if *grab != CursorGrabMode::None {
|
||||||
if cfg!(unix) {
|
if cfg!(unix) {
|
||||||
*grab = CursorGrabMode::Confined;
|
*grab = CursorGrabMode::Confined;
|
||||||
|
@ -483,76 +514,17 @@ fn center_mouse(window: &Window, options: &WindowOptions) {
|
||||||
};
|
};
|
||||||
window.set_cursor_position(middle).unwrap();
|
window.set_cursor_position(middle).unwrap();
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
fn window_updater_system(world: &mut World) -> anyhow::Result<()> {
|
/// A system that syncs Winit Windows with [`WindowOptions`] components.
|
||||||
/* if let (Some(window), Some(opts)) = (
|
pub fn window_sync_system(world: &mut World) -> anyhow::Result<()> {
|
||||||
world.try_get_resource::<Arc<Window>>(),
|
for (entity, mut opts, mut last_window, windows) in world.filtered_view_iter::<(Entities, &mut WindowOptions, &mut LastWindow, ResMut<WinitWindows>), Changed<WindowOptions>>() {
|
||||||
world.try_get_resource::<Ct<WindowOptions>>(),
|
|
||||||
) { */
|
|
||||||
let tick = world.tick();
|
|
||||||
for (entity, mut opts, window_tick, windows) in world.view_iter::<(Entities, &mut WindowOptions, TickOf<WindowOptions>, ResMut<WinitWindows>)>() {
|
|
||||||
let window = windows.get_entity_window(entity)
|
let window = windows.get_entity_window(entity)
|
||||||
.expect("entity's window is missing");
|
.expect("entity's window is missing");
|
||||||
|
|
||||||
if window_tick == tick {
|
|
||||||
if opts.focused {
|
|
||||||
window.focus_window();
|
|
||||||
}
|
|
||||||
window.set_content_protected(opts.content_protected);
|
|
||||||
set_cursor_grab(&window, &mut opts.cursor_grab)?;
|
|
||||||
match window.set_cursor_hittest(opts.cursor_hittest) {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(ExternalError::NotSupported(_)) => { /* ignore */ }
|
|
||||||
Err(e) => {
|
|
||||||
error!("OS error when setting cursor hittest: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.set_cursor(opts.cursor.clone()); // TODO: Handle unsupported platforms
|
|
||||||
window.set_cursor_visible(opts.cursor_visible); // TODO: Handle unsupported platforms
|
|
||||||
window.set_decorations(opts.decorations); // TODO: Handle unsupported platforms
|
|
||||||
window.set_enabled_buttons(opts.enabled_buttons); // TODO: Handle unsupported platforms
|
|
||||||
window.set_fullscreen(opts.fullscreen.clone());
|
|
||||||
window.set_ime_allowed(opts.ime_allowed);
|
|
||||||
window.set_ime_cursor_area(opts.ime_cursor_area.position, opts.ime_cursor_area.size);
|
|
||||||
window.request_inner_size(opts.inner_size);
|
|
||||||
if opts.max_inner_size.is_some() {
|
|
||||||
window.set_max_inner_size(opts.max_inner_size);
|
|
||||||
}
|
|
||||||
if opts.min_inner_size.is_some() {
|
|
||||||
window.set_min_inner_size(opts.min_inner_size);
|
|
||||||
}
|
|
||||||
window.set_maximized(opts.maximized);
|
|
||||||
window.set_minimized(opts.minimized);
|
|
||||||
window.set_resizable(opts.resizeable);
|
|
||||||
window.set_resize_increments(opts.resize_increments);
|
|
||||||
window.set_theme(opts.theme);
|
|
||||||
window.set_title(&opts.title);
|
|
||||||
window.set_window_icon(opts.icon.clone());
|
|
||||||
window.set_window_level(opts.level);
|
|
||||||
|
|
||||||
center_mouse(&window, &opts);
|
|
||||||
} else {
|
|
||||||
if let Some(event_queue) = world.try_get_resource_mut::<EventQueue>() {
|
|
||||||
if let Some(events) = event_queue.read_events::<InputEvent>() {
|
|
||||||
for ev in events {
|
|
||||||
match ev {
|
|
||||||
InputEvent::CursorEntered { .. } => {
|
|
||||||
opts.cursor_inside_window = true;
|
|
||||||
}
|
|
||||||
InputEvent::CursorLeft { .. } => {
|
|
||||||
opts.cursor_inside_window = false;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the stored state of the window to match the actual window
|
todo!()
|
||||||
opts.focused = window.has_focus();
|
|
||||||
center_mouse(&window, &opts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -563,6 +535,6 @@ impl Plugin for WindowPlugin {
|
||||||
let window_options = WindowOptions::default();
|
let window_options = WindowOptions::default();
|
||||||
|
|
||||||
app.world.add_resource(Ct::new(window_options));
|
app.world.add_resource(Ct::new(window_options));
|
||||||
app.with_system("window_updater", window_updater_system, &[]);
|
app.with_system("window_sync", window_sync_system, &[]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue