game: improve event system
This commit is contained in:
parent
f5aca87ede
commit
d6d6b2df72
|
@ -1,17 +1,12 @@
|
|||
use lyra_engine::{
|
||||
assets::{gltf::Gltf, ResourceManager},
|
||||
game::App,
|
||||
input::{
|
||||
assets::{gltf::Gltf, ResourceManager}, ecs::query::View, game::App, input::{
|
||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||
},
|
||||
math::{self, Transform, Vec3},
|
||||
render::light::directional::DirectionalLight,
|
||||
scene::{
|
||||
}, math::{self, Transform, Vec3}, render::{light::directional::DirectionalLight, window::WindowOptions}, scene::{
|
||||
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
|
||||
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
|
||||
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
#[async_std::main]
|
||||
|
@ -52,7 +47,7 @@ async fn main() {
|
|||
.bind(
|
||||
ACTLBL_LOOK_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
||||
//ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::ArrowLeft).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::ArrowRight).into_binding_modifier(1.0),
|
||||
],
|
||||
|
@ -60,7 +55,7 @@ async fn main() {
|
|||
.bind(
|
||||
ACTLBL_LOOK_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
||||
//ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::ArrowUp).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::ArrowDown).into_binding_modifier(1.0),
|
||||
],
|
||||
|
@ -87,6 +82,7 @@ async fn main() {
|
|||
|
||||
let mut a = App::new();
|
||||
a.with_plugin(lyra_engine::DefaultPlugins)
|
||||
.with_system("mouse_pos_print", mouse_pos_system, &[])
|
||||
.with_plugin(setup_scene_plugin)
|
||||
.with_plugin(action_handler_plugin)
|
||||
//.with_plugin(camera_debug_plugin)
|
||||
|
@ -96,7 +92,7 @@ async fn main() {
|
|||
|
||||
fn setup_scene_plugin(app: &mut App) {
|
||||
let world = &mut app.world;
|
||||
let resman = world.get_resource_mut::<ResourceManager>();
|
||||
let resman = world.get_resource_mut::<ResourceManager>().unwrap();
|
||||
|
||||
/* let camera_gltf = resman
|
||||
.request::<Gltf>("../assets/AntiqueCamera.glb")
|
||||
|
@ -145,3 +141,11 @@ fn setup_scene_plugin(app: &mut App) {
|
|||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
||||
world.spawn((camera, FreeFlyCamera::default()));
|
||||
}
|
||||
|
||||
fn mouse_pos_system(view: View<&WindowOptions>) -> anyhow::Result<()> {
|
||||
for win in view.iter() {
|
||||
//println!("Mouse pos: {:?}", win.cursor_position());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use instant::Instant;
|
||||
use lyra_ecs::{Component, World};
|
||||
use lyra_ecs::{query::ResMut, Component};
|
||||
use lyra_reflect::Reflect;
|
||||
|
||||
use crate::{plugin::Plugin, game::GameStages};
|
||||
|
@ -30,9 +30,8 @@ impl std::ops::DerefMut for DeltaTime {
|
|||
/// A system that updates the [`DeltaTime``] resource.
|
||||
///
|
||||
/// The resource is updated in the [`GameStages::First`] stage.
|
||||
pub fn delta_time_system(world: &mut World) -> anyhow::Result<()> {
|
||||
pub fn delta_time_system(mut delta: ResMut<DeltaTime>) -> anyhow::Result<()> {
|
||||
let now = Instant::now();
|
||||
let mut delta = world.get_resource_mut::<DeltaTime>();
|
||||
delta.0 = delta.1.unwrap_or(now).elapsed().as_secs_f32();
|
||||
delta.1 = Some(now);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::sync::Arc;
|
||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
use lyra_ecs::{query::{ResMut, WorldTick}, system::FnArgFetcher, Tick};
|
||||
|
@ -6,14 +6,83 @@ use lyra_ecs::{query::{ResMut, WorldTick}, system::FnArgFetcher, Tick};
|
|||
pub trait Event: Clone + Send + Sync + 'static {}
|
||||
impl<T: Clone + Send + Sync + 'static> Event for T {}
|
||||
|
||||
/// A Vec with other Vecs in it to track relative age of items.
|
||||
///
|
||||
/// The vec has 3 levels, a `newest`, `medium` and `old`. Items are pushed to the `newest`
|
||||
/// internal vec. When [`WaterfallVec::waterfall`] is called the items in `newest` are
|
||||
/// put into `medium`, and items in `medium` goes to `old`.
|
||||
///
|
||||
/// By checking the items in each internal vec, you can see a relative age between the items.
|
||||
/// The event system uses this to clear the `old` vec to ensure keep events for only two
|
||||
/// frames at a time.
|
||||
struct WaterfallVec<T> {
|
||||
newest: Vec<T>,
|
||||
medium: Vec<T>,
|
||||
old: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for WaterfallVec<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
newest: Default::default(),
|
||||
medium: Default::default(),
|
||||
old: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> WaterfallVec<T> {
|
||||
fn total_len(&self) -> usize {
|
||||
self.newest.len() + self.medium.len() + self.old.len()
|
||||
}
|
||||
|
||||
fn get(&self, mut i: usize) -> Option<&T> {
|
||||
if i >= self.old.len() {
|
||||
i -= self.old.len();
|
||||
|
||||
if i >= self.medium.len() {
|
||||
i -= self.medium.len();
|
||||
self.newest.get(i)
|
||||
} else {
|
||||
self.medium.get(i)
|
||||
}
|
||||
} else {
|
||||
self.old.get(i)
|
||||
}
|
||||
}
|
||||
|
||||
/// Age elements.
|
||||
///
|
||||
/// This moves elements in `newest` to `medium` and elements in `medium` to `old`.
|
||||
/// This is what drives the relative age of the [`WaterfallVec`].
|
||||
fn waterfall(&mut self) {
|
||||
self.old.append(&mut self.medium);
|
||||
self.medium.append(&mut self.newest);
|
||||
}
|
||||
|
||||
/// Push a new element to the newest queue.
|
||||
fn push(&mut self, event: T) {
|
||||
self.newest.push(event);
|
||||
}
|
||||
|
||||
/// Clear oldest items.
|
||||
fn clear_oldest(&mut self) {
|
||||
self.old.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Events<T: Event> {
|
||||
events: Arc<AtomicRefCell<Vec<T>>>,
|
||||
last_updated_tick: Option<Tick>,
|
||||
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
|
||||
/// Used to track when the old events were last cleared.
|
||||
last_cleared_at: Tick,
|
||||
/// Used to indicate when the cursor in readers should be reset to zero.
|
||||
/// This becomes true after the old events are cleared.
|
||||
reset_cursor: bool,
|
||||
}
|
||||
|
||||
impl<T: Event> Default for Events<T> {
|
||||
fn default() -> Self {
|
||||
Self { events: Default::default(), last_updated_tick: None }
|
||||
Self { events: Default::default(), last_cleared_at: Default::default(), reset_cursor: false }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +99,7 @@ impl<T: Event> Events<T> {
|
|||
pub fn reader(&self) -> EventReader<T> {
|
||||
EventReader {
|
||||
events: self.events.clone(),
|
||||
cursor: 0,
|
||||
cursor: Rc::new(RefCell::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,27 +111,28 @@ impl<T: Event> Events<T> {
|
|||
}
|
||||
|
||||
pub struct EventReader<T: Event> {
|
||||
events: Arc<AtomicRefCell<Vec<T>>>,
|
||||
cursor: usize,
|
||||
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
|
||||
cursor: Rc<RefCell<usize>>,
|
||||
}
|
||||
|
||||
impl<T: Event> EventReader<T> {
|
||||
pub fn read(&mut self) -> Option<atomic_refcell::AtomicRef<T>> {
|
||||
let events = self.events.borrow();
|
||||
|
||||
if self.cursor >= events.len() {
|
||||
let mut cursor = self.cursor.borrow_mut();
|
||||
if *cursor >= events.total_len() {
|
||||
None
|
||||
} else {
|
||||
let e = atomic_refcell::AtomicRef::map(events,
|
||||
|e| e.get(self.cursor).unwrap());
|
||||
self.cursor += 1;
|
||||
|e| e.get(*cursor).unwrap());
|
||||
*cursor += 1;
|
||||
Some(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventWriter<T: Event> {
|
||||
events: Arc<AtomicRefCell<Vec<T>>>,
|
||||
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Event> EventWriter<T> {
|
||||
|
@ -77,40 +147,48 @@ pub fn event_cleaner_system<T>(tick: WorldTick, mut events: ResMut<Events<T>>) -
|
|||
where
|
||||
T: Event
|
||||
{
|
||||
let last_tick = *events.last_updated_tick.unwrap_or(*tick);
|
||||
let last_tick = *events.last_cleared_at;
|
||||
let world_tick = **tick;
|
||||
|
||||
if last_tick + 2 < world_tick {
|
||||
events.last_updated_tick = Some(*tick);
|
||||
events.last_cleared_at = *tick;
|
||||
events.reset_cursor = true;
|
||||
|
||||
let mut events = events.events.borrow_mut();
|
||||
events.clear();
|
||||
events.clear_oldest();
|
||||
} else {
|
||||
events.reset_cursor = false;
|
||||
}
|
||||
|
||||
let mut events = events.events.borrow_mut();
|
||||
events.waterfall();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<T: Event> FnArgFetcher for EventReader<T> {
|
||||
type State = usize;
|
||||
type State = Rc<RefCell<usize>>;
|
||||
|
||||
type Arg<'a, 'state> = EventReader<T>;
|
||||
|
||||
fn create_state(_: std::ptr::NonNull<lyra_ecs::World>) -> Self::State {
|
||||
0
|
||||
Rc::new(RefCell::new(0))
|
||||
}
|
||||
|
||||
unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: std::ptr::NonNull<lyra_ecs::World>) -> Self::Arg<'a, 'state> {
|
||||
let world = world.as_ref();
|
||||
let events = world.get_resource::<Events<T>>();
|
||||
let events = world.get_resource::<Events<T>>()
|
||||
.unwrap_or_else(|| panic!("world missing Events<{}> resource", std::any::type_name::<T>()));
|
||||
|
||||
// reset the reader cursor when the events are cleared
|
||||
let world_tick = world.current_tick();
|
||||
if world_tick == events.last_updated_tick.unwrap_or(world_tick) {
|
||||
*state = 0;
|
||||
if events.reset_cursor {
|
||||
let mut state_num = state.borrow_mut();
|
||||
*state_num = 0;
|
||||
}
|
||||
|
||||
let mut reader = events.reader();
|
||||
reader.cursor = *state;
|
||||
let reader = EventReader {
|
||||
events: events.events.clone(),
|
||||
cursor: state.clone(),
|
||||
};
|
||||
|
||||
reader
|
||||
}
|
||||
|
@ -129,7 +207,8 @@ impl<T: Event> FnArgFetcher for EventWriter<T> {
|
|||
|
||||
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: std::ptr::NonNull<lyra_ecs::World>) -> Self::Arg<'a, 'state> {
|
||||
let world = world.as_ref();
|
||||
let events = world.get_resource::<Events<T>>();
|
||||
let events = world.get_resource::<Events<T>>()
|
||||
.unwrap_or_else(|| panic!("world missing Events<{}> resource", std::any::type_name::<T>()));
|
||||
events.writer()
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{cell::OnceCell, collections::VecDeque, ptr::NonNull};
|
|||
|
||||
use lyra_ecs::{system::{IntoSystem, System}, ResourceObject, World};
|
||||
use lyra_math::IVec2;
|
||||
use tracing::{info, error, Level};
|
||||
use tracing::{error, info, Level};
|
||||
use tracing_appender::non_blocking;
|
||||
use tracing_subscriber::{
|
||||
layer::SubscriberExt,
|
||||
|
@ -102,6 +102,7 @@ impl App {
|
|||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.world.tick();
|
||||
let wptr = NonNull::from(&self.world);
|
||||
|
||||
if let Err(e) = self.staged_exec.execute(wptr, true) {
|
||||
|
@ -227,7 +228,7 @@ impl App {
|
|||
|
||||
pub fn push_event<T: Event>(&mut self, event: T) {
|
||||
let world = &mut self.world;
|
||||
let mut events = world.try_get_resource_mut::<Events<T>>()
|
||||
let mut events = world.get_resource_mut::<Events<T>>()
|
||||
.expect("missing events for event type! Must use `App::register_event` first");
|
||||
events.push_event(event);
|
||||
}
|
||||
|
|
|
@ -203,7 +203,8 @@ impl Node for LightCullComputePass {
|
|||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, context: &mut RenderGraphContext) {
|
||||
context.queue_buffer_write_with(LightCullComputePassSlots::IndexCounterBuffer, 0, 0);
|
||||
|
||||
let screen_size = world.get_resource::<ScreenSize>();
|
||||
let screen_size = world.get_resource::<ScreenSize>()
|
||||
.expect("world missing ScreenSize resource");
|
||||
if screen_size.xy() != self.workgroup_size {
|
||||
self.workgroup_size = screen_size.xy();
|
||||
todo!("Resize buffers and other resources");
|
||||
|
|
|
@ -286,7 +286,8 @@ impl Node for MeshPrepNode {
|
|||
Self::try_init_resource::<RenderAssets<Arc<GpuMaterial>>>(world);
|
||||
Self::try_init_resource::<FxHashMap<Entity, uuid::Uuid>>(world);
|
||||
|
||||
let mut render_meshes = world.get_resource_mut::<RenderMeshes>();
|
||||
let mut render_meshes = world.get_resource_mut::<RenderMeshes>()
|
||||
.expect("world missing RenderMeshes resource");
|
||||
render_meshes.clear();
|
||||
}
|
||||
|
||||
|
@ -459,7 +460,8 @@ impl Node for MeshPrepNode {
|
|||
world.insert(en, interp);
|
||||
}
|
||||
|
||||
let mut transforms = world.get_resource_mut::<TransformBuffers>();
|
||||
let mut transforms = world.get_resource_mut::<TransformBuffers>()
|
||||
.expect("world missing TransformBuffers resource");
|
||||
transforms.send_to_gpu(queue);
|
||||
}
|
||||
|
||||
|
|
|
@ -305,17 +305,17 @@ impl Node for MeshPass {
|
|||
});
|
||||
|
||||
let transforms = world
|
||||
.try_get_resource_data::<TransformBuffers>()
|
||||
.get_resource_data::<TransformBuffers>()
|
||||
.expect("Missing transform buffers");
|
||||
self.transform_buffers = Some(transforms.clone());
|
||||
|
||||
let render_meshes = world
|
||||
.try_get_resource_data::<RenderMeshes>()
|
||||
.get_resource_data::<RenderMeshes>()
|
||||
.expect("Missing transform buffers");
|
||||
self.render_meshes = Some(render_meshes.clone());
|
||||
|
||||
let mesh_buffers = world
|
||||
.try_get_resource_data::<RenderAssets<MeshBufferStorage>>()
|
||||
.get_resource_data::<RenderAssets<MeshBufferStorage>>()
|
||||
.expect("Missing render meshes");
|
||||
self.mesh_buffers = Some(mesh_buffers.clone());
|
||||
|
||||
|
|
|
@ -639,7 +639,8 @@ impl Node for ShadowMapsPass {
|
|||
if world.has_resource_changed::<ShadowCasterSettings>() {
|
||||
debug!("Detected change in ShadowSettings, recreating poisson disks");
|
||||
|
||||
let settings = world.get_resource::<ShadowCasterSettings>();
|
||||
let settings = world.get_resource::<ShadowCasterSettings>()
|
||||
.expect("world missing ShadowCasterSettings resource");
|
||||
// convert to uniform now since the from impl limits to max values
|
||||
let uniform = ShadowSettingsUniform::from(*settings);
|
||||
|
||||
|
@ -671,16 +672,17 @@ impl Node for ShadowMapsPass {
|
|||
|
||||
context.queue_buffer_write_with(ShadowMapsPassSlots::ShadowSettingsUniform, 0, uniform);
|
||||
}
|
||||
let settings = *world.get_resource::<ShadowCasterSettings>();
|
||||
let settings = *world.get_resource::<ShadowCasterSettings>()
|
||||
.expect("world missing ShadowCasterSettings resource");
|
||||
|
||||
if settings.use_back_faces {
|
||||
// TODO: shadow maps rendering with back faces
|
||||
todo!("render with back faces");
|
||||
}
|
||||
|
||||
self.render_meshes = world.try_get_resource_data::<RenderMeshes>();
|
||||
self.transform_buffers = world.try_get_resource_data::<TransformBuffers>();
|
||||
self.mesh_buffers = world.try_get_resource_data::<RenderAssets<MeshBufferStorage>>();
|
||||
self.render_meshes = world.get_resource_data::<RenderMeshes>();
|
||||
self.transform_buffers = world.get_resource_data::<TransformBuffers>();
|
||||
self.mesh_buffers = world.get_resource_data::<RenderAssets<MeshBufferStorage>>();
|
||||
|
||||
world.add_resource(self.atlas.clone());
|
||||
|
||||
|
|
|
@ -269,7 +269,8 @@ impl Renderer for BasicRenderer {
|
|||
rt.surface.configure(&self.device, &rt.surface_config); */
|
||||
|
||||
// update screen size resource in ecs
|
||||
let mut world_ss = world.get_resource_mut::<ScreenSize>();
|
||||
let mut world_ss = world.get_resource_mut::<ScreenSize>()
|
||||
.expect("world missing ScreenSize resource");
|
||||
world_ss.0 = glam::UVec2::new(new_size.width, new_size.height);
|
||||
|
||||
|
||||
|
|
|
@ -1,101 +1,16 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use glam::{DVec2, IVec2, UVec2, Vec2};
|
||||
use lyra_ecs::{query::{filter::Changed, Entities, Res, View}, Component};
|
||||
use lyra_math::Area;
|
||||
use lyra_resource::Image;
|
||||
use tracing::error;
|
||||
use winit::window::{CustomCursor, Fullscreen, Window};
|
||||
use winit::{dpi::{PhysicalPosition, PhysicalSize, Position, Size}, window::{CustomCursor, Fullscreen, Window}};
|
||||
|
||||
pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel};
|
||||
|
||||
use crate::{plugin::Plugin, winit::WinitWindows};
|
||||
|
||||
#[derive(Default, Clone, Copy, PartialEq)]
|
||||
pub struct Area {
|
||||
position: Position,
|
||||
size: Size,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Size {
|
||||
Physical { x: u32, y: u32 },
|
||||
Logical { x: f64, y: f64 },
|
||||
}
|
||||
|
||||
impl Default for Size {
|
||||
fn default() -> Self {
|
||||
Self::Physical { x: 0, y: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<winit::dpi::Size> for Size {
|
||||
fn into(self) -> winit::dpi::Size {
|
||||
match self {
|
||||
Size::Physical { x, y } => winit::dpi::PhysicalSize::new(x, y).into(),
|
||||
Size::Logical { x, y } => winit::dpi::LogicalSize::new(x, y).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
pub fn new_physical(x: u32, y: u32) -> Self {
|
||||
Self::Physical { x, y }
|
||||
}
|
||||
|
||||
pub fn new_logical(x: f64, y: f64) -> Self {
|
||||
Self::Logical { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Position {
|
||||
Physical { x: i32, y: i32 },
|
||||
Logical { x: f64, y: f64 },
|
||||
}
|
||||
|
||||
impl Default for Position {
|
||||
fn default() -> Self {
|
||||
Self::Physical { x: 0, y: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<winit::dpi::Position> for Position {
|
||||
fn into(self) -> winit::dpi::Position {
|
||||
match self {
|
||||
Position::Physical { x, y } => winit::dpi::PhysicalPosition::new(x, y).into(),
|
||||
Position::Logical { x, y } => winit::dpi::LogicalPosition::new(x, y).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
pub fn new_physical(x: i32, y: i32) -> Self {
|
||||
Self::Physical { x, y }
|
||||
}
|
||||
|
||||
pub fn new_logical(x: f64, y: f64) -> Self {
|
||||
Self::Logical { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
/// Flag component that
|
||||
#[derive(Clone, Component)]
|
||||
pub struct PrimaryWindow;
|
||||
|
@ -168,7 +83,7 @@ pub struct WindowOptions {
|
|||
/// * **Web:** Can only return None or Borderless(None).
|
||||
pub fullscreen: Option<Fullscreen>,
|
||||
|
||||
/// Gets the position of the top-left hand corner of the window’s client area relative to
|
||||
/// 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
|
||||
|
@ -185,7 +100,7 @@ pub struct WindowOptions {
|
|||
/// * **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.
|
||||
pub inner_position: Option<Position>,
|
||||
pub position: Option<IVec2>,
|
||||
|
||||
/// Gets/sets the size of the view in the window.
|
||||
///
|
||||
|
@ -193,7 +108,7 @@ pub struct WindowOptions {
|
|||
///
|
||||
/// Platform-specific
|
||||
/// * **Web:** The size of the canvas element. Doesn’t account for CSS `transform`.
|
||||
pub size: Size,
|
||||
physical_size: UVec2,
|
||||
|
||||
/// Gets/sets if the window has decorations.
|
||||
///
|
||||
|
@ -237,24 +152,6 @@ pub struct WindowOptions {
|
|||
/// * **iOS:** Setting is not implemented, getting is unsupported.
|
||||
pub visible: Option<bool>,
|
||||
|
||||
/// 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.
|
||||
pub outer_position: Option<Position>,
|
||||
|
||||
/// Gets/sets the window resize increments.
|
||||
///
|
||||
/// This is a niche constraint hint usually employed by terminal emulators and other apps
|
||||
|
@ -308,12 +205,12 @@ pub struct WindowOptions {
|
|||
/// * **X11:** Enabling IME will disable dead keys reporting during compose.
|
||||
pub ime_allowed: bool,
|
||||
|
||||
/// Sets area of IME candidate box in window client area coordinates relative to the top left.
|
||||
/// Sets area of IME box in physical coordinates relative to the top left.
|
||||
///
|
||||
/// Platform-specific
|
||||
/// * **X11:** - area is not supported, only position.
|
||||
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
||||
pub ime_cursor_area: Option<Area>,
|
||||
pub physical_ime_cursor_area: Option<Area<Vec2, Vec2>>,
|
||||
|
||||
/// Gets/sets the minimum size of the window.
|
||||
///
|
||||
|
@ -380,12 +277,12 @@ pub struct WindowOptions {
|
|||
pub window_level: WindowLevel,
|
||||
|
||||
/// Show [window menu](https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu)
|
||||
/// at a specified position.
|
||||
/// at a specified position in physical coordinates.
|
||||
///
|
||||
/// 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.
|
||||
pub show_window_menu: Option<Position>,
|
||||
pub physical_window_menu_pos: Option<Vec2>,
|
||||
|
||||
/// Gets the window's occluded state (completely hidden from view).
|
||||
///
|
||||
|
@ -400,6 +297,13 @@ pub struct WindowOptions {
|
|||
/// * **Web:** Doesn't take into account CSS border, padding, or transform.
|
||||
/// * **Android / Wayland / Windows / Orbital:** Unsupported.
|
||||
pub occluded: bool,
|
||||
|
||||
/// Gets/sets the position of the cursor in physical coordinates.
|
||||
///
|
||||
/// Platform-specific
|
||||
/// * **Wayland:** Cursor must be in [`CursorGrabMode::Locked`].
|
||||
/// * **iOS / Android / Web / Orbital:** Unsupported.
|
||||
physical_cursor_position: Option<DVec2>,
|
||||
}
|
||||
|
||||
impl From<winit::window::WindowAttributes> for WindowOptions {
|
||||
|
@ -408,15 +312,19 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
|
|||
enabled_buttons: value.enabled_buttons,
|
||||
focused: false,
|
||||
fullscreen: value.fullscreen,
|
||||
inner_position: None,
|
||||
size: value.inner_size.map(|s| s.into())
|
||||
.unwrap_or(Size::new_physical(1280, 720)),
|
||||
position: value.position.map(|p| {
|
||||
let s = p.to_physical::<i32>(1.0);
|
||||
IVec2::new(s.x, s.y)
|
||||
}),
|
||||
physical_size: value.inner_size.map(|s| {
|
||||
let s = s.to_physical::<u32>(1.0);
|
||||
UVec2::new(s.width, s.height)
|
||||
}).unwrap_or(UVec2::new(1280, 720)),
|
||||
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,
|
||||
|
@ -431,7 +339,7 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
|
|||
visible: true,
|
||||
},
|
||||
ime_allowed: false,
|
||||
ime_cursor_area: None,
|
||||
physical_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,
|
||||
|
@ -439,8 +347,9 @@ impl From<winit::window::WindowAttributes> for WindowOptions {
|
|||
transparent: value.transparent,
|
||||
window_icon: None,
|
||||
window_level: value.window_level,
|
||||
show_window_menu: None,
|
||||
physical_window_menu_pos: None,
|
||||
occluded: false,
|
||||
physical_cursor_position: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -458,12 +367,12 @@ impl WindowOptions {
|
|||
|
||||
att.enabled_buttons = self.enabled_buttons.clone();
|
||||
att.fullscreen = self.fullscreen.clone();
|
||||
att.inner_size = Some(self.size.into());
|
||||
att.inner_size = Some(Size::Physical(PhysicalSize::new(self.physical_size.x, self.physical_size.y)));
|
||||
att.decorations = self.decorated;
|
||||
att.maximized = self.maximized;
|
||||
att.resizable = self.resizable;
|
||||
att.visible = self.visible.unwrap_or(true);
|
||||
att.position = self.outer_position.map(|p| p.into());
|
||||
att.position = self.position.map(|p| Position::Physical(PhysicalPosition::new(p.x, p.y)));
|
||||
att.resize_increments = self.resize_increments.map(|i| i.into());
|
||||
att.blur = self.blur;
|
||||
att.content_protected = self.content_protected;
|
||||
|
@ -483,6 +392,73 @@ impl WindowOptions {
|
|||
|
||||
att
|
||||
}
|
||||
|
||||
/// The size of the window in physical coordinates.
|
||||
pub fn physical_size(&self) -> UVec2 {
|
||||
self.physical_size
|
||||
}
|
||||
|
||||
/// Set the size of the window in physical coordinates.
|
||||
pub fn set_physical_size(&mut self, size: UVec2) {
|
||||
self.physical_size = size;
|
||||
}
|
||||
|
||||
/// The size of the window in logical coordinates.
|
||||
pub fn size(&self) -> Vec2 {
|
||||
self.physical_size.as_vec2() / self.scale_factor as f32
|
||||
}
|
||||
|
||||
/// Set the size of the window in logical coordinates.
|
||||
pub fn set_size(&mut self, size: Vec2) {
|
||||
self.physical_size = (size * self.scale_factor as f32).as_uvec2();
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating if the mouse is inside the window.
|
||||
pub fn is_mouse_inside(&self) -> bool {
|
||||
if let Some(pos) = self.physical_cursor_position {
|
||||
let s = self.physical_size;
|
||||
return pos.x >= 0.0 && pos.x <= s.x as f64
|
||||
&& pos.y >= 0.0 && pos.y <= s.y as f64;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// The cursor position in the window in logical coordinates.
|
||||
///
|
||||
/// Returns `None` if the cursor is not in the window.
|
||||
pub fn cursor_position(&self) -> Option<Vec2> {
|
||||
if !self.is_mouse_inside() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.physical_cursor_position.map(|p| (p / self.scale_factor).as_vec2())
|
||||
}
|
||||
|
||||
/// The cursor position in the window in physical coordinates.
|
||||
///
|
||||
/// Returns `None` if the cursor is not in the window.
|
||||
pub fn physical_cursor_position(&self) -> Option<Vec2> {
|
||||
if !self.is_mouse_inside() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.physical_cursor_position.map(|p| p.as_vec2())
|
||||
}
|
||||
|
||||
/// Set the cursor position in logical coordinates.
|
||||
///
|
||||
/// Can be used to mark the cursor outside of the window as well.
|
||||
pub fn set_cursor_position(&mut self, pos: Option<Vec2>) {
|
||||
self.physical_cursor_position = pos.map(|p| p.as_dvec2() * self.scale_factor);
|
||||
}
|
||||
|
||||
/// Set the cursor position in physical coordinates.
|
||||
///
|
||||
/// Can be used to mark the cursor outside of the window as well.
|
||||
pub fn set_physical_cursor_position(&mut self, pos: Option<DVec2>) {
|
||||
self.physical_cursor_position = pos;
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of the window last time it was changed.
|
||||
|
@ -491,7 +467,7 @@ impl WindowOptions {
|
|||
/// when syncing the winit window with the component.
|
||||
#[derive(Clone, Component)]
|
||||
pub struct LastWindow {
|
||||
last: WindowOptions,
|
||||
pub last: WindowOptions,
|
||||
}
|
||||
|
||||
impl Deref for LastWindow {
|
||||
|
@ -532,8 +508,9 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
|
|||
window.set_fullscreen(opts.fullscreen.clone());
|
||||
}
|
||||
|
||||
if opts.size != last.size {
|
||||
if window.request_inner_size(opts.size).is_some() {
|
||||
if opts.physical_size != last.physical_size {
|
||||
let size = PhysicalSize::new(opts.physical_size.x, opts.physical_size.y);
|
||||
if window.request_inner_size(size).is_some() {
|
||||
error!("request to increase window size failed");
|
||||
}
|
||||
}
|
||||
|
@ -554,8 +531,10 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
|
|||
window.set_visible(opts.visible.unwrap());
|
||||
}
|
||||
|
||||
if opts.outer_position != last.outer_position && opts.outer_position.is_some() {
|
||||
window.set_outer_position(opts.outer_position.unwrap());
|
||||
if opts.position != last.position && opts.position.is_some() {
|
||||
let pos = opts.position.unwrap();
|
||||
let pos = PhysicalPosition::new(pos.x, pos.y);
|
||||
window.set_outer_position(pos);
|
||||
}
|
||||
|
||||
if opts.resize_increments != last.resize_increments {
|
||||
|
@ -563,7 +542,7 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
|
|||
}
|
||||
|
||||
if opts.blur != last.blur {
|
||||
window.set_blur(opts.blur)
|
||||
window.set_blur(opts.blur);
|
||||
}
|
||||
|
||||
if opts.content_protected != last.content_protected {
|
||||
|
@ -597,9 +576,11 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
|
|||
window.set_ime_allowed(opts.ime_allowed);
|
||||
}
|
||||
|
||||
if opts.ime_cursor_area != last.ime_cursor_area && opts.ime_cursor_area.is_some() {
|
||||
let area = opts.ime_cursor_area.as_ref().unwrap();
|
||||
window.set_ime_cursor_area(area.position, area.size);
|
||||
if opts.physical_ime_cursor_area != last.physical_ime_cursor_area && opts.physical_ime_cursor_area.is_some() {
|
||||
let area = opts.physical_ime_cursor_area.unwrap();
|
||||
let pos = PhysicalPosition::new(area.position.x, area.position.y);
|
||||
let size = PhysicalSize::new(area.size.x, area.size.y);
|
||||
window.set_ime_cursor_area(pos, size);
|
||||
}
|
||||
|
||||
if opts.min_size != last.min_size {
|
||||
|
@ -636,8 +617,18 @@ pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &Win
|
|||
window.set_window_level(opts.window_level);
|
||||
}
|
||||
|
||||
if opts.show_window_menu != last.show_window_menu && opts.show_window_menu.is_some() {
|
||||
window.show_window_menu(opts.show_window_menu.unwrap());
|
||||
if opts.physical_window_menu_pos != last.physical_window_menu_pos && opts.physical_window_menu_pos.is_some() {
|
||||
let pos = opts.physical_window_menu_pos.unwrap();
|
||||
let pos = PhysicalPosition::new(pos.x, pos.y);
|
||||
window.show_window_menu(pos);
|
||||
}
|
||||
|
||||
if opts.physical_cursor_position != last.physical_cursor_position && opts.physical_cursor_position.is_some() {
|
||||
let pos = opts.physical_cursor_position.unwrap();
|
||||
let pos = PhysicalPosition::new(pos.x, pos.y);
|
||||
if let Err(e) = window.set_cursor_position(pos) {
|
||||
error!("failed to set cursor position: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
last.last = opts.clone();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{collections::VecDeque, sync::Arc};
|
||||
|
||||
use async_std::task::block_on;
|
||||
use glam::IVec2;
|
||||
use glam::{DVec2, IVec2, UVec2};
|
||||
use lyra_ecs::Entity;
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::{debug, error, warn};
|
||||
|
@ -17,7 +17,7 @@ use crate::{
|
|||
plugin::Plugin,
|
||||
render::{
|
||||
renderer::BasicRenderer,
|
||||
window::{PrimaryWindow, Size, WindowOptions},
|
||||
window::{LastWindow, PrimaryWindow, WindowOptions},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -143,7 +143,8 @@ impl ApplicationHandler for WinitRunner {
|
|||
Err(e) => eprintln!("{:?}", e),
|
||||
}
|
||||
|
||||
let windows = self.app.world.get_resource::<WinitWindows>();
|
||||
let windows = self.app.world.get_resource::<WinitWindows>()
|
||||
.expect("world missing WinitWindows resource");
|
||||
for window in windows.windows.values() {
|
||||
window.request_redraw();
|
||||
}
|
||||
|
@ -152,13 +153,15 @@ impl ApplicationHandler for WinitRunner {
|
|||
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
|
||||
let world = &mut self.app.world;
|
||||
|
||||
let mut windows = world.get_resource_mut::<WinitWindows>();
|
||||
let mut windows = world.get_resource_mut::<WinitWindows>()
|
||||
.expect("world missing WinitWindows resource");
|
||||
let to_create_window = windows.window_queue.pop_front().unwrap_or_default();
|
||||
let window_attr = to_create_window.as_attributes();
|
||||
drop(windows);
|
||||
let en = world.spawn((to_create_window, PrimaryWindow));
|
||||
let en = world.spawn((to_create_window.clone(), LastWindow { last: to_create_window }, PrimaryWindow));
|
||||
|
||||
let mut windows = world.get_resource_mut::<WinitWindows>();
|
||||
let mut windows = world.get_resource_mut::<WinitWindows>()
|
||||
.expect("world missing WinitWindows resource");
|
||||
let wid = windows.create_window(event_loop, en, window_attr).unwrap();
|
||||
let window = windows.windows.get(&wid).unwrap().clone();
|
||||
drop(windows);
|
||||
|
@ -195,28 +198,59 @@ impl ApplicationHandler for WinitRunner {
|
|||
|
||||
self.app.push_event(event.clone());
|
||||
match event {
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
let windows = self.app.world.get_resource::<WinitWindows>()
|
||||
.expect("world missing WinitWindows resource");
|
||||
let en = windows.window_to_entity.get(&window_id)
|
||||
.expect("missing window entity");
|
||||
|
||||
// update the window and its cache so the sync system doesn't try to update the window
|
||||
let (mut en_window, mut en_last_win) = self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*en).get().unwrap();
|
||||
let pos = Some(DVec2::new(position.x, position.y));
|
||||
en_window.set_physical_cursor_position(pos);
|
||||
en_last_win.set_physical_cursor_position(pos);
|
||||
},
|
||||
WindowEvent::ActivationTokenDone { .. } => todo!(),
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
self.app.on_resize(physical_size);
|
||||
|
||||
let mut window_opts = self
|
||||
let (mut window, mut last_window) = self
|
||||
.app
|
||||
.world
|
||||
.get_resource::<WinitWindows>()
|
||||
.expect("world missing WinitWindows resource")
|
||||
.window_to_entity
|
||||
.get(&window_id)
|
||||
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
|
||||
.and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e).get())
|
||||
.unwrap();
|
||||
window_opts.size = Size::new_physical(physical_size.width, physical_size.height);
|
||||
}
|
||||
|
||||
// update the window and its cache so the sync system doesn't try to update the window
|
||||
let size = UVec2::new(physical_size.width, physical_size.height);
|
||||
window.set_physical_size(size);
|
||||
last_window.set_physical_size(size);
|
||||
},
|
||||
// Mark the cursor as outside the window when it leaves
|
||||
WindowEvent::CursorLeft { .. } => {
|
||||
let (mut window, mut last_window) = self
|
||||
.app
|
||||
.world
|
||||
.get_resource::<WinitWindows>()
|
||||
.expect("world missing WinitWindows resource")
|
||||
.window_to_entity
|
||||
.get(&window_id)
|
||||
.and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e).get())
|
||||
.unwrap();
|
||||
window.set_physical_cursor_position(None);
|
||||
last_window.set_physical_cursor_position(None);
|
||||
},
|
||||
WindowEvent::Moved(physical_position) => {
|
||||
let mut state = self.app.world.get_resource_or_else(WindowState::new);
|
||||
state.position = IVec2::new(physical_position.x, physical_position.y);
|
||||
}
|
||||
},
|
||||
WindowEvent::CloseRequested => {
|
||||
self.app.on_exit();
|
||||
event_loop.exit();
|
||||
}
|
||||
},
|
||||
WindowEvent::Destroyed => todo!(),
|
||||
WindowEvent::DroppedFile(_path_buf) => todo!(),
|
||||
WindowEvent::HoveredFile(_path_buf) => todo!(),
|
||||
|
@ -226,51 +260,55 @@ impl ApplicationHandler for WinitRunner {
|
|||
.app
|
||||
.world
|
||||
.get_resource::<WinitWindows>()
|
||||
.expect("world missing WinitWindows resource")
|
||||
.window_to_entity
|
||||
.get(&window_id)
|
||||
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
|
||||
.unwrap();
|
||||
window_opts.focused = focused;
|
||||
}
|
||||
},
|
||||
WindowEvent::ModifiersChanged(modifiers) => {
|
||||
debug!("modifiers changed: {:?}", modifiers)
|
||||
}
|
||||
},
|
||||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
||||
let mut window_opts = self
|
||||
.app
|
||||
.world
|
||||
.get_resource::<WinitWindows>()
|
||||
.expect("world missing WinitWindows resource")
|
||||
.window_to_entity
|
||||
.get(&window_id)
|
||||
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
|
||||
.unwrap();
|
||||
window_opts.scale_factor = scale_factor;
|
||||
}
|
||||
},
|
||||
WindowEvent::ThemeChanged(theme) => {
|
||||
let mut window_opts = self
|
||||
.app
|
||||
.world
|
||||
.get_resource::<WinitWindows>()
|
||||
.expect("world missing WinitWindows resource")
|
||||
.window_to_entity
|
||||
.get(&window_id)
|
||||
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
|
||||
.unwrap();
|
||||
window_opts.theme = Some(theme);
|
||||
}
|
||||
},
|
||||
WindowEvent::Occluded(occ) => {
|
||||
let mut window_opts = self
|
||||
.app
|
||||
.world
|
||||
.get_resource::<WinitWindows>()
|
||||
.expect("world missing WinitWindows resource")
|
||||
.window_to_entity
|
||||
.get(&window_id)
|
||||
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
|
||||
.unwrap();
|
||||
window_opts.occluded = occ;
|
||||
}
|
||||
},
|
||||
WindowEvent::RedrawRequested => {
|
||||
//debug!("should redraw");
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub struct Area<P, S>
|
||||
where
|
||||
P: Clone + Copy + PartialEq,
|
||||
S: Clone + Copy + PartialEq,
|
||||
{
|
||||
pub position: P,
|
||||
pub size: S
|
||||
}
|
||||
|
||||
impl<P, S> Area<P, S>
|
||||
where
|
||||
P: Clone + Copy + PartialEq,
|
||||
S: Clone + Copy + PartialEq,
|
||||
{
|
||||
pub fn new(pos: P, size: S) -> Self {
|
||||
Self {
|
||||
position: pos,
|
||||
size,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,9 @@ pub use glam::*;
|
|||
pub mod angle;
|
||||
pub use angle::*;
|
||||
|
||||
mod area;
|
||||
pub use area::*;
|
||||
|
||||
pub mod transform;
|
||||
pub use transform::*;
|
||||
|
||||
|
|
Loading…
Reference in New Issue