mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added cursor trapping, more documentation
This commit is contained in:
parent
c9d877de7a
commit
45d5a0a396
@ -14,7 +14,7 @@ uniform u_globals {
|
|||||||
vec4 cam_pos;
|
vec4 cam_pos;
|
||||||
vec4 focus_pos;
|
vec4 focus_pos;
|
||||||
vec4 view_distance;
|
vec4 view_distance;
|
||||||
vec4 tod;
|
vec4 time_of_day;
|
||||||
vec4 time;
|
vec4 time;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ uniform u_globals {
|
|||||||
vec4 cam_pos;
|
vec4 cam_pos;
|
||||||
vec4 focus_pos;
|
vec4 focus_pos;
|
||||||
vec4 view_distance;
|
vec4 view_distance;
|
||||||
vec4 tod;
|
vec4 time_of_day;
|
||||||
vec4 time;
|
vec4 time;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ uniform u_globals {
|
|||||||
vec4 cam_pos;
|
vec4 cam_pos;
|
||||||
vec4 focus_pos;
|
vec4 focus_pos;
|
||||||
vec4 view_distance;
|
vec4 view_distance;
|
||||||
vec4 tod;
|
vec4 time_of_day;
|
||||||
vec4 time;
|
vec4 time;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ uniform u_globals {
|
|||||||
vec4 cam_pos;
|
vec4 cam_pos;
|
||||||
vec4 focus_pos;
|
vec4 focus_pos;
|
||||||
vec4 view_distance;
|
vec4 view_distance;
|
||||||
vec4 tod;
|
vec4 time_of_day;
|
||||||
vec4 time;
|
vec4 time;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,8 +12,6 @@ pub use crate::error::Error;
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
// Library
|
// Library
|
||||||
use glutin;
|
|
||||||
use failure;
|
|
||||||
use log;
|
use log;
|
||||||
use pretty_env_logger;
|
use pretty_env_logger;
|
||||||
|
|
||||||
@ -28,6 +26,12 @@ pub struct GlobalState {
|
|||||||
window: Window,
|
window: Window,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GlobalState {
|
||||||
|
pub fn on_play_state_changed(&mut self) {
|
||||||
|
self.window.untrap_cursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// States can either close (and revert to a previous state), push a new state on top of themselves,
|
// States can either close (and revert to a previous state), push a new state on top of themselves,
|
||||||
// or switch to a totally different state
|
// or switch to a totally different state
|
||||||
pub enum PlayStateResult {
|
pub enum PlayStateResult {
|
||||||
@ -82,23 +86,27 @@ fn main() {
|
|||||||
log::info!("Shutting down all states...");
|
log::info!("Shutting down all states...");
|
||||||
while states.last().is_some() {
|
while states.last().is_some() {
|
||||||
states.pop().map(|old_state| {
|
states.pop().map(|old_state| {
|
||||||
log::info!("Popped state '{}'", old_state.name())
|
log::info!("Popped state '{}'", old_state.name());
|
||||||
|
global_state.on_play_state_changed();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PlayStateResult::Pop => {
|
PlayStateResult::Pop => {
|
||||||
states.pop().map(|old_state| {
|
states.pop().map(|old_state| {
|
||||||
log::info!("Popped state '{}'", old_state.name())
|
log::info!("Popped state '{}'", old_state.name());
|
||||||
|
global_state.on_play_state_changed();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
PlayStateResult::Push(new_state) => {
|
PlayStateResult::Push(new_state) => {
|
||||||
log::info!("Pushed state '{}'", new_state.name());
|
log::info!("Pushed state '{}'", new_state.name());
|
||||||
states.push(new_state);
|
states.push(new_state);
|
||||||
|
global_state.on_play_state_changed();
|
||||||
},
|
},
|
||||||
PlayStateResult::Switch(mut new_state) => {
|
PlayStateResult::Switch(mut new_state) => {
|
||||||
states.last_mut().map(|old_state| {
|
states.last_mut().map(|old_state| {
|
||||||
log::info!("Switching to state '{}' from state '{}'", new_state.name(), old_state.name());
|
log::info!("Switching to state '{}' from state '{}'", new_state.name(), old_state.name());
|
||||||
mem::swap(old_state, &mut new_state);
|
mem::swap(old_state, &mut new_state);
|
||||||
|
global_state.on_play_state_changed();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ use crate::{
|
|||||||
PlayStateResult,
|
PlayStateResult,
|
||||||
GlobalState,
|
GlobalState,
|
||||||
window::Event,
|
window::Event,
|
||||||
render,
|
|
||||||
session::SessionState,
|
session::SessionState,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,8 +43,9 @@ impl PlayState for TitleState {
|
|||||||
|
|
||||||
// Finish the frame
|
// Finish the frame
|
||||||
global_state.window.renderer_mut().flush();
|
global_state.window.renderer_mut().flush();
|
||||||
global_state.window.display()
|
global_state.window
|
||||||
.expect("Failed to display window");
|
.swap_buffers()
|
||||||
|
.expect("Failed to swap window buffers");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ pub mod mesh;
|
|||||||
pub mod model;
|
pub mod model;
|
||||||
pub mod pipelines;
|
pub mod pipelines;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
|
mod util;
|
||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
pub use self::{
|
pub use self::{
|
||||||
|
@ -11,6 +11,9 @@ use gfx::{
|
|||||||
};
|
};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
// Local
|
||||||
|
use super::util::arr_to_mat;
|
||||||
|
|
||||||
gfx_defines! {
|
gfx_defines! {
|
||||||
constant Globals {
|
constant Globals {
|
||||||
view_mat: [[f32; 4]; 4] = "view_mat",
|
view_mat: [[f32; 4]; 4] = "view_mat",
|
||||||
@ -18,32 +21,43 @@ gfx_defines! {
|
|||||||
cam_pos: [f32; 4] = "cam_pos",
|
cam_pos: [f32; 4] = "cam_pos",
|
||||||
focus_pos: [f32; 4] = "focus_pos",
|
focus_pos: [f32; 4] = "focus_pos",
|
||||||
view_distance: [f32; 4] = "view_distance",
|
view_distance: [f32; 4] = "view_distance",
|
||||||
tod: [f32; 4] = "tod",
|
time_of_day: [f32; 4] = "time_of_day",
|
||||||
time: [f32; 4] = "time",
|
time: [f32; 4] = "time",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Globals {
|
impl Globals {
|
||||||
pub fn new() -> Self {
|
/// Create global consts with default values.
|
||||||
// TODO: Get rid of this ugliness
|
pub fn default() -> Self {
|
||||||
#[rustfmt::skip]
|
|
||||||
fn f32_arr_to_mat(arr: [f32; 16]) -> [[f32; 4]; 4] {
|
|
||||||
[
|
|
||||||
[arr[ 0], arr[ 1], arr[ 2], arr[ 3]],
|
|
||||||
[arr[ 4], arr[ 5], arr[ 6], arr[ 7]],
|
|
||||||
[arr[ 8], arr[ 9], arr[10], arr[11]],
|
|
||||||
[arr[12], arr[13], arr[14], arr[15]],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
view_mat: f32_arr_to_mat(Mat4::identity().into_col_array()),
|
view_mat: arr_to_mat(Mat4::identity().into_col_array()),
|
||||||
proj_mat: f32_arr_to_mat(Mat4::identity().into_col_array()),
|
proj_mat: arr_to_mat(Mat4::identity().into_col_array()),
|
||||||
cam_pos: [0.0; 4],
|
cam_pos: [0.0; 4],
|
||||||
focus_pos: [0.0; 4],
|
focus_pos: [0.0; 4],
|
||||||
view_distance: [0.0; 4],
|
view_distance: [0.0; 4],
|
||||||
tod: [0.0; 4],
|
time_of_day: [0.0; 4],
|
||||||
time: [0.0; 4],
|
time: [0.0; 4],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create global consts from the provided parameters.
|
||||||
|
pub fn new(
|
||||||
|
view_mat: Mat4<f32>,
|
||||||
|
proj_mat: Mat4<f32>,
|
||||||
|
cam_pos: Vec3<f32>,
|
||||||
|
focus_pos: Vec3<f32>,
|
||||||
|
view_distance: f32,
|
||||||
|
time_of_day: f32,
|
||||||
|
time: f32,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
view_mat: arr_to_mat(view_mat.into_col_array()),
|
||||||
|
proj_mat: arr_to_mat(proj_mat.into_col_array()),
|
||||||
|
cam_pos: Vec4::from(cam_pos).into_array(),
|
||||||
|
focus_pos: Vec4::from(focus_pos).into_array(),
|
||||||
|
view_distance: [view_distance; 4],
|
||||||
|
time_of_day: [time_of_day; 4],
|
||||||
|
time: [time; 4],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ gfx_defines! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Locals {
|
impl Locals {
|
||||||
pub fn new() -> Self {
|
pub fn default() -> Self {
|
||||||
Self { nul: [0.0; 4] }
|
Self { nul: [0.0; 4] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,8 @@ pub struct Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer {
|
impl Renderer {
|
||||||
/// Create a new `Renderer` from a variety of backend-specific components and the window targets.
|
/// Create a new `Renderer` from a variety of backend-specific components and the window
|
||||||
|
/// targets.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
device: gfx_backend::Device,
|
device: gfx_backend::Device,
|
||||||
mut factory: gfx_backend::Factory,
|
mut factory: gfx_backend::Factory,
|
||||||
@ -99,13 +100,25 @@ impl Renderer {
|
|||||||
Ok(Consts::new(&mut self.factory))
|
Ok(Consts::new(&mut self.factory))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new set of constants and update then with a value.
|
/// Create a new set of constants with a value.
|
||||||
pub fn create_consts_with<T: Copy + gfx::traits::Pod>(&mut self, val: T) -> Result<Consts<T>, RenderError> {
|
pub fn create_consts_with<T: Copy + gfx::traits::Pod>(
|
||||||
|
&mut self,
|
||||||
|
val: T
|
||||||
|
) -> Result<Consts<T>, RenderError> {
|
||||||
let mut consts = self.create_consts()?;
|
let mut consts = self.create_consts()?;
|
||||||
consts.update(&mut self.encoder, val)?;
|
consts.update(&mut self.encoder, val)?;
|
||||||
Ok(consts)
|
Ok(consts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update a set of constants with a new value.
|
||||||
|
pub fn update_consts<T: Copy + gfx::traits::Pod>(
|
||||||
|
&mut self,
|
||||||
|
consts: &mut Consts<T>,
|
||||||
|
val: T
|
||||||
|
) -> Result<(), RenderError> {
|
||||||
|
consts.update(&mut self.encoder, val)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new model from the provided mesh.
|
/// Create a new model from the provided mesh.
|
||||||
pub fn create_model<P: Pipeline>(&mut self, mesh: &Mesh<P>) -> Result<Model<P>, RenderError> {
|
pub fn create_model<P: Pipeline>(&mut self, mesh: &Mesh<P>) -> Result<Model<P>, RenderError> {
|
||||||
Ok(Model::new(
|
Ok(Model::new(
|
||||||
@ -136,7 +149,6 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct GfxPipeline<P: gfx::pso::PipelineInit> {
|
struct GfxPipeline<P: gfx::pso::PipelineInit> {
|
||||||
program: gfx::handle::Program<gfx_backend::Resources>,
|
|
||||||
pso: gfx::pso::PipelineState<gfx_backend::Resources, P::Meta>,
|
pso: gfx::pso::PipelineState<gfx_backend::Resources, P::Meta>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,10 +178,12 @@ fn create_pipeline<'a, P: gfx::pso::PipelineInit>(
|
|||||||
)
|
)
|
||||||
// Do some funky things to work around an oddity in gfx's error ownership rules
|
// Do some funky things to work around an oddity in gfx's error ownership rules
|
||||||
.map_err(|err| RenderError::PipelineError(match err {
|
.map_err(|err| RenderError::PipelineError(match err {
|
||||||
gfx::PipelineStateError::Program(err) => gfx::PipelineStateError::Program(err),
|
gfx::PipelineStateError::Program(err) =>
|
||||||
gfx::PipelineStateError::DescriptorInit(err) => gfx::PipelineStateError::DescriptorInit(err.into()),
|
gfx::PipelineStateError::Program(err),
|
||||||
gfx::PipelineStateError::DeviceCreate(err) => gfx::PipelineStateError::DeviceCreate(err),
|
gfx::PipelineStateError::DescriptorInit(err) =>
|
||||||
|
gfx::PipelineStateError::DescriptorInit(err.into()),
|
||||||
|
gfx::PipelineStateError::DeviceCreate(err) =>
|
||||||
|
gfx::PipelineStateError::DeviceCreate(err),
|
||||||
}))?,
|
}))?,
|
||||||
program,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
10
voxygen/src/render/util.rs
Normal file
10
voxygen/src/render/util.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// TODO: Get rid of this ugliness
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub fn arr_to_mat(arr: [f32; 16]) -> [[f32; 4]; 4] {
|
||||||
|
[
|
||||||
|
[arr[ 0], arr[ 1], arr[ 2], arr[ 3]],
|
||||||
|
[arr[ 4], arr[ 5], arr[ 6], arr[ 7]],
|
||||||
|
[arr[ 8], arr[ 9], arr[10], arr[11]],
|
||||||
|
[arr[12], arr[13], arr[14], arr[15]],
|
||||||
|
]
|
||||||
|
}
|
@ -21,15 +21,15 @@ impl Camera {
|
|||||||
Self {
|
Self {
|
||||||
focus: Vec3::zero(),
|
focus: Vec3::zero(),
|
||||||
ori: Vec3::zero(),
|
ori: Vec3::zero(),
|
||||||
dist: 10.0,
|
dist: 5.0,
|
||||||
fov: 1.3,
|
fov: 1.3,
|
||||||
aspect: 1.618,
|
aspect: 1.618,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the transformation matrices (view matrix and projection matrix) for the camera.
|
/// Compute the transformation matrices (view matrix and projection matrix) for the camera.
|
||||||
pub fn compute_matrices(&self) -> (Mat4<f32>, Mat4<f32>) {
|
pub fn compute_dependents(&self) -> (Mat4<f32>, Mat4<f32>, Vec3<f32>) {
|
||||||
let view = Mat4::<f32>::identity()
|
let view_mat = Mat4::<f32>::identity()
|
||||||
* Mat4::translation_3d(-Vec3::unit_z() * self.dist)
|
* Mat4::translation_3d(-Vec3::unit_z() * self.dist)
|
||||||
* Mat4::rotation_z(self.ori.z)
|
* Mat4::rotation_z(self.ori.z)
|
||||||
* Mat4::rotation_x(self.ori.y)
|
* Mat4::rotation_x(self.ori.y)
|
||||||
@ -37,13 +37,21 @@ impl Camera {
|
|||||||
* Mat4::rotation_3d(PI / 2.0, -Vec4::unit_x())
|
* Mat4::rotation_3d(PI / 2.0, -Vec4::unit_x())
|
||||||
* Mat4::translation_3d(-self.focus);
|
* Mat4::translation_3d(-self.focus);
|
||||||
|
|
||||||
let proj = Mat4::perspective_rh_no(
|
let proj_mat = Mat4::perspective_rh_no(
|
||||||
self.fov,
|
self.fov,
|
||||||
self.aspect,
|
self.aspect,
|
||||||
NEAR_PLANE,
|
NEAR_PLANE,
|
||||||
FAR_PLANE,
|
FAR_PLANE,
|
||||||
);
|
);
|
||||||
|
|
||||||
(view, proj)
|
// TODO: Make this more efficient
|
||||||
|
let cam_pos = Vec3::from(view_mat.inverted() * Vec4::unit_w());
|
||||||
|
|
||||||
|
(view_mat, proj_mat, cam_pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the focus position of the camera.
|
||||||
|
pub fn get_focus_pos(&self) -> Vec3<f32> { self.focus }
|
||||||
|
/// Set the focus position of the camera.
|
||||||
|
pub fn set_focus_pos(&mut self, focus: Vec3<f32>) { self.focus = focus; }
|
||||||
}
|
}
|
||||||
|
@ -31,19 +31,36 @@ impl Scene {
|
|||||||
Self {
|
Self {
|
||||||
camera: Camera::new(),
|
camera: Camera::new(),
|
||||||
globals: renderer
|
globals: renderer
|
||||||
.create_consts_with(Globals::new())
|
.create_consts_with(Globals::default())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
skybox: Skybox {
|
skybox: Skybox {
|
||||||
model: renderer
|
model: renderer
|
||||||
.create_model(&create_skybox_mesh())
|
.create_model(&create_skybox_mesh())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
locals: renderer
|
locals: renderer
|
||||||
.create_consts_with(SkyboxLocals::new())
|
.create_consts_with(SkyboxLocals::default())
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn maintain_gpu_data(&mut self, renderer: &mut Renderer) {
|
||||||
|
// Compute camera matrices
|
||||||
|
let (view_mat, proj_mat, cam_pos) = self.camera.compute_dependents();
|
||||||
|
|
||||||
|
// Update global constants
|
||||||
|
renderer.update_consts(&mut self.globals, Globals::new(
|
||||||
|
view_mat,
|
||||||
|
proj_mat,
|
||||||
|
cam_pos,
|
||||||
|
self.camera.get_focus_pos(),
|
||||||
|
10.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
))
|
||||||
|
.expect("Failed to update global constants");
|
||||||
|
}
|
||||||
|
|
||||||
/// Render the scene using the provided `Renderer`
|
/// Render the scene using the provided `Renderer`
|
||||||
pub fn render_to(&self, renderer: &mut Renderer) {
|
pub fn render_to(&self, renderer: &mut Renderer) {
|
||||||
// Render the skybox first (it appears over everything else so must be rendered first)
|
// Render the skybox first (it appears over everything else so must be rendered first)
|
||||||
|
@ -31,6 +31,9 @@ const BG_COLOR: Rgba<f32> = Rgba { r: 0.0, g: 0.3, b: 1.0, a: 1.0 };
|
|||||||
|
|
||||||
impl PlayState for SessionState {
|
impl PlayState for SessionState {
|
||||||
fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult {
|
fn play(&mut self, global_state: &mut GlobalState) -> PlayStateResult {
|
||||||
|
// Trap the cursor
|
||||||
|
global_state.window.trap_cursor();
|
||||||
|
|
||||||
// Game loop
|
// Game loop
|
||||||
loop {
|
loop {
|
||||||
// Handle window events
|
// Handle window events
|
||||||
@ -44,6 +47,9 @@ impl PlayState for SessionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maintain scene GPU data
|
||||||
|
self.scene.maintain_gpu_data(global_state.window.renderer_mut());
|
||||||
|
|
||||||
// Clear the screen
|
// Clear the screen
|
||||||
global_state.window.renderer_mut().clear(BG_COLOR);
|
global_state.window.renderer_mut().clear(BG_COLOR);
|
||||||
|
|
||||||
@ -52,8 +58,9 @@ impl PlayState for SessionState {
|
|||||||
|
|
||||||
// Finish the frame
|
// Finish the frame
|
||||||
global_state.window.renderer_mut().flush();
|
global_state.window.renderer_mut().flush();
|
||||||
global_state.window.display()
|
global_state.window
|
||||||
.expect("Failed to display window");
|
.swap_buffers()
|
||||||
|
.expect("Failed to swap window buffers");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Library
|
// Library
|
||||||
use glutin;
|
use glutin;
|
||||||
use gfx_window_glutin;
|
use gfx_window_glutin;
|
||||||
|
use vek::*;
|
||||||
|
|
||||||
// Crate
|
// Crate
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -73,13 +74,30 @@ impl Window {
|
|||||||
events
|
events
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display(&self) -> Result<(), Error> {
|
pub fn swap_buffers(&self) -> Result<(), Error> {
|
||||||
self.window.swap_buffers()
|
self.window.swap_buffers()
|
||||||
.map_err(|err| Error::BackendError(Box::new(err)))
|
.map_err(|err| Error::BackendError(Box::new(err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn trap_cursor(&mut self) {
|
||||||
|
self.window.hide_cursor(true);
|
||||||
|
self.window.grab_cursor(true)
|
||||||
|
.expect("Failed to grab cursor");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn untrap_cursor(&mut self) {
|
||||||
|
self.window.hide_cursor(false);
|
||||||
|
self.window.grab_cursor(false)
|
||||||
|
.expect("Failed to ungrab cursor");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents an incoming event from the window
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
/// The window has been requested to close.
|
||||||
Close,
|
Close,
|
||||||
|
/// A key has been typed that corresponds to a specific character.
|
||||||
Char(char),
|
Char(char),
|
||||||
|
/// The cursor has been panned across the screen while trapped.
|
||||||
|
CursorPan(Vec2<f32>),
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user