mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added server-cli, UI tests
Former-commit-id: 93bf5b39138920aa7a4a773a8247d716866c4a05
This commit is contained in:
parent
a1617e7b5c
commit
c16a257ae3
@ -3,6 +3,7 @@ members = [
|
||||
"common",
|
||||
"client",
|
||||
"server",
|
||||
"server-cli",
|
||||
"voxygen",
|
||||
"world",
|
||||
]
|
||||
|
3
server-cli/.gitignore
vendored
Normal file
3
server-cli/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
12
server-cli/Cargo.toml
Normal file
12
server-cli/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "veloren-server-cli"
|
||||
version = "0.1.0"
|
||||
authors = ["Joshua Barretto <joshua.s.barretto@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
server = { package = "veloren-server", path = "../server" }
|
||||
common = { package = "veloren-common", path = "../common" }
|
||||
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.3"
|
35
server-cli/src/main.rs
Normal file
35
server-cli/src/main.rs
Normal file
@ -0,0 +1,35 @@
|
||||
// Standard
|
||||
use std::time::Duration;
|
||||
|
||||
// Library
|
||||
use log::info;
|
||||
|
||||
// Project
|
||||
use server::{self, Server};
|
||||
use common::clock::Clock;
|
||||
|
||||
const FPS: u64 = 60;
|
||||
|
||||
fn main() {
|
||||
// Init logging
|
||||
pretty_env_logger::init();
|
||||
|
||||
info!("Starting server-cli...");
|
||||
|
||||
// Set up an fps clock
|
||||
let mut clock = Clock::new();
|
||||
|
||||
// Create server
|
||||
let mut server = Server::new();
|
||||
|
||||
loop {
|
||||
server.tick(server::Input {}, clock.get_last_delta())
|
||||
.expect("Failed to tick server");
|
||||
|
||||
// Clean up the server after a tick
|
||||
server.cleanup();
|
||||
|
||||
// Wait for the next tick
|
||||
clock.tick(Duration::from_millis(1000 / FPS));
|
||||
}
|
||||
}
|
@ -62,4 +62,10 @@ impl Server {
|
||||
// Finish the tick, pass control back to the frontend (step 6)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clean up the server after a tick
|
||||
pub fn cleanup(&mut self) {
|
||||
// Cleanup the local state
|
||||
self.state.cleanup();
|
||||
}
|
||||
}
|
||||
|
@ -29,3 +29,4 @@ lazy_static = "1.1"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.3"
|
||||
dot_vox = "1.0"
|
||||
image = "0.21"
|
||||
|
17
voxygen/shaders/ui.frag
Normal file
17
voxygen/shaders/ui.frag
Normal file
@ -0,0 +1,17 @@
|
||||
#version 330 core
|
||||
|
||||
in vec3 f_pos;
|
||||
in vec2 f_uv;
|
||||
|
||||
layout (std140)
|
||||
uniform u_locals {
|
||||
vec4 bounds;
|
||||
};
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
|
||||
out vec4 tgt_color;
|
||||
|
||||
void main() {
|
||||
tgt_color = texture(u_tex, f_uv);
|
||||
}
|
22
voxygen/shaders/ui.vert
Normal file
22
voxygen/shaders/ui.vert
Normal file
@ -0,0 +1,22 @@
|
||||
#version 330 core
|
||||
|
||||
in vec3 v_pos;
|
||||
in vec2 v_uv;
|
||||
|
||||
layout (std140)
|
||||
uniform u_locals {
|
||||
vec4 bounds;
|
||||
};
|
||||
|
||||
uniform sampler2D u_tex;
|
||||
|
||||
out vec3 f_pos;
|
||||
out vec2 f_uv;
|
||||
|
||||
void main() {
|
||||
f_uv = v_uv;
|
||||
f_pos = vec3(vec2(bounds.x, bounds.y) + v_pos.xy * vec2(bounds.z, bounds.w), 0);
|
||||
f_pos.xy = vec2(f_pos.x * 2.0 - 1.0, f_pos.y * -2.0 + 1.0);
|
||||
|
||||
gl_Position = vec4(f_pos, 1);
|
||||
}
|
@ -7,6 +7,7 @@ pub mod mesh;
|
||||
pub mod render;
|
||||
pub mod scene;
|
||||
pub mod session;
|
||||
pub mod ui;
|
||||
pub mod window;
|
||||
|
||||
// Reexports
|
||||
@ -66,18 +67,20 @@ fn main() {
|
||||
// Init logging
|
||||
pretty_env_logger::init();
|
||||
|
||||
// Set up the initial play state
|
||||
let mut states: Vec<Box<dyn PlayState>> = vec![Box::new(TitleState::new())];
|
||||
states.last().map(|current_state| {
|
||||
log::info!("Started game with state '{}'", current_state.name())
|
||||
});
|
||||
|
||||
// Set up the global state
|
||||
let mut global_state = GlobalState {
|
||||
window: Window::new()
|
||||
.expect("Failed to create window"),
|
||||
};
|
||||
|
||||
// Set up the initial play state
|
||||
let mut states: Vec<Box<dyn PlayState>> = vec![Box::new(TitleState::new(
|
||||
&mut global_state.window.renderer_mut(),
|
||||
))];
|
||||
states.last().map(|current_state| {
|
||||
log::info!("Started game with state '{}'", current_state.name())
|
||||
});
|
||||
|
||||
// What's going on here?
|
||||
// ---------------------
|
||||
// The state system used by Voxygen allows for the easy development of stack-based menus.
|
||||
|
@ -1,5 +1,6 @@
|
||||
// Library
|
||||
use vek::*;
|
||||
use image;
|
||||
|
||||
// Crate
|
||||
use crate::{
|
||||
@ -8,14 +9,28 @@ use crate::{
|
||||
GlobalState,
|
||||
window::Event,
|
||||
session::SessionState,
|
||||
render::Renderer,
|
||||
ui::{
|
||||
Ui,
|
||||
element::{
|
||||
Widget,
|
||||
image::Image,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct TitleState;
|
||||
pub struct TitleState {
|
||||
ui: Ui,
|
||||
}
|
||||
|
||||
impl TitleState {
|
||||
/// Create a new `TitleState`
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
pub fn new(renderer: &mut Renderer) -> Self {
|
||||
let img = Image::new(renderer, &image::open(concat!(env!("CARGO_MANIFEST_DIR"), "/test_assets/test.png")).unwrap()).unwrap();
|
||||
let widget = Widget::new(renderer, img).unwrap();
|
||||
Self {
|
||||
ui: Ui::new(renderer, widget).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,6 +56,12 @@ impl PlayState for TitleState {
|
||||
// Clear the screen
|
||||
global_state.window.renderer_mut().clear(BG_COLOR);
|
||||
|
||||
// Maintain the UI
|
||||
self.ui.maintain(global_state.window.renderer_mut());
|
||||
|
||||
// Draw the UI to the screen
|
||||
self.ui.render(global_state.window.renderer_mut());
|
||||
|
||||
// Finish the frame
|
||||
global_state.window.renderer_mut().flush();
|
||||
global_state.window
|
||||
|
@ -3,6 +3,7 @@ pub mod mesh;
|
||||
pub mod model;
|
||||
pub mod pipelines;
|
||||
pub mod renderer;
|
||||
pub mod texture;
|
||||
mod util;
|
||||
|
||||
// Reexports
|
||||
@ -10,6 +11,7 @@ pub use self::{
|
||||
consts::Consts,
|
||||
mesh::{Mesh, Tri, Quad},
|
||||
model::Model,
|
||||
texture::Texture,
|
||||
renderer::{Renderer, TgtColorFmt, TgtDepthFmt},
|
||||
pipelines::{
|
||||
Globals,
|
||||
@ -27,6 +29,11 @@ pub use self::{
|
||||
TerrainPipeline,
|
||||
Locals as TerrainLocals,
|
||||
},
|
||||
ui::{
|
||||
create_quad_mesh as create_ui_quad_mesh,
|
||||
UiPipeline,
|
||||
Locals as UiLocals,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -41,6 +48,7 @@ use gfx;
|
||||
pub enum RenderError {
|
||||
PipelineError(gfx::PipelineStateError<String>),
|
||||
UpdateError(gfx::UpdateError<usize>),
|
||||
CombinedError(gfx::CombinedError),
|
||||
}
|
||||
|
||||
/// Used to represent a specific rendering configuration.
|
||||
|
@ -1,6 +1,7 @@
|
||||
pub mod figure;
|
||||
pub mod skybox;
|
||||
pub mod terrain;
|
||||
pub mod ui;
|
||||
|
||||
// Library
|
||||
use gfx::{
|
||||
|
76
voxygen/src/render/pipelines/ui.rs
Normal file
76
voxygen/src/render/pipelines/ui.rs
Normal file
@ -0,0 +1,76 @@
|
||||
// Library
|
||||
use gfx::{
|
||||
self,
|
||||
// Macros
|
||||
gfx_defines,
|
||||
gfx_vertex_struct_meta,
|
||||
gfx_constant_struct_meta,
|
||||
gfx_impl_struct_meta,
|
||||
gfx_pipeline,
|
||||
gfx_pipeline_inner,
|
||||
};
|
||||
|
||||
// Local
|
||||
use super::{
|
||||
Globals,
|
||||
super::{
|
||||
Pipeline,
|
||||
TgtColorFmt,
|
||||
TgtDepthFmt,
|
||||
Mesh,
|
||||
Quad,
|
||||
},
|
||||
};
|
||||
|
||||
gfx_defines! {
|
||||
vertex Vertex {
|
||||
pos: [f32; 3] = "v_pos",
|
||||
uv: [f32; 2] = "v_uv",
|
||||
}
|
||||
|
||||
constant Locals {
|
||||
bounds: [f32; 4] = "bounds",
|
||||
}
|
||||
|
||||
pipeline pipe {
|
||||
vbuf: gfx::VertexBuffer<Vertex> = (),
|
||||
|
||||
locals: gfx::ConstantBuffer<Locals> = "u_locals",
|
||||
tex: gfx::TextureSampler<[f32; 4]> = "u_tex",
|
||||
|
||||
tgt_color: gfx::RenderTarget<TgtColorFmt> = "tgt_color",
|
||||
tgt_depth: gfx::DepthTarget<TgtDepthFmt> = gfx::preset::depth::PASS_TEST,
|
||||
}
|
||||
}
|
||||
|
||||
impl Locals {
|
||||
pub fn default() -> Self {
|
||||
Self { bounds: [0.0, 0.0, 1.0, 1.0] }
|
||||
}
|
||||
|
||||
pub fn new(bounds: [f32; 4]) -> Self {
|
||||
Self {
|
||||
bounds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UiPipeline;
|
||||
|
||||
impl Pipeline for UiPipeline {
|
||||
type Vertex = Vertex;
|
||||
}
|
||||
|
||||
pub fn create_quad_mesh() -> Mesh<UiPipeline> {
|
||||
let mut mesh = Mesh::new();
|
||||
|
||||
#[rustfmt::skip]
|
||||
mesh.push_quad(Quad::new(
|
||||
Vertex { pos: [0.0, 0.0, 0.0], uv: [0.0, 0.0] },
|
||||
Vertex { pos: [0.0, 1.0, 0.0], uv: [0.0, 1.0] },
|
||||
Vertex { pos: [1.0, 1.0, 0.0], uv: [1.0, 1.0] },
|
||||
Vertex { pos: [1.0, 0.0, 0.0], uv: [1.0, 0.0] },
|
||||
));
|
||||
|
||||
mesh
|
||||
}
|
@ -4,12 +4,14 @@ use gfx::{
|
||||
self,
|
||||
traits::{Device, FactoryExt},
|
||||
};
|
||||
use image;
|
||||
|
||||
// Local
|
||||
use super::{
|
||||
consts::Consts,
|
||||
model::Model,
|
||||
mesh::Mesh,
|
||||
model::Model,
|
||||
texture::Texture,
|
||||
Pipeline,
|
||||
RenderError,
|
||||
gfx_backend,
|
||||
@ -18,6 +20,7 @@ use super::{
|
||||
figure,
|
||||
skybox,
|
||||
terrain,
|
||||
ui,
|
||||
},
|
||||
};
|
||||
|
||||
@ -45,6 +48,7 @@ pub struct Renderer {
|
||||
skybox_pipeline: GfxPipeline<skybox::pipe::Init<'static>>,
|
||||
figure_pipeline: GfxPipeline<figure::pipe::Init<'static>>,
|
||||
terrain_pipeline: GfxPipeline<terrain::pipe::Init<'static>>,
|
||||
ui_pipeline: GfxPipeline<ui::pipe::Init<'static>>,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
@ -80,6 +84,14 @@ impl Renderer {
|
||||
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/terrain.frag")),
|
||||
)?;
|
||||
|
||||
// Construct a pipeline for rendering UI elements
|
||||
let ui_pipeline = create_pipeline(
|
||||
&mut factory,
|
||||
ui::pipe::new(),
|
||||
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/ui.vert")),
|
||||
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/ui.frag")),
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
device,
|
||||
encoder: factory.create_command_buffer().into(),
|
||||
@ -91,6 +103,7 @@ impl Renderer {
|
||||
skybox_pipeline,
|
||||
figure_pipeline,
|
||||
terrain_pipeline,
|
||||
ui_pipeline,
|
||||
})
|
||||
}
|
||||
|
||||
@ -104,6 +117,14 @@ impl Renderer {
|
||||
(&mut self.tgt_color_view, &mut self.tgt_depth_view)
|
||||
}
|
||||
|
||||
/// Get the resolution of the render target.
|
||||
pub fn get_resolution(&self) -> Vec2<u16> {
|
||||
Vec2::new(
|
||||
self.tgt_color_view.get_dimensions().0,
|
||||
self.tgt_color_view.get_dimensions().1,
|
||||
)
|
||||
}
|
||||
|
||||
/// Queue the clearing of the color and depth targets ready for a new frame to be rendered.
|
||||
/// TODO: Make a version of this that doesn't clear the colour target for speed
|
||||
pub fn clear(&mut self, col: Rgba<f32>) {
|
||||
@ -144,6 +165,14 @@ impl Renderer {
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a new texture from the provided image.
|
||||
pub fn create_texture<P: Pipeline>(&mut self, image: &image::DynamicImage) -> Result<Texture<P>, RenderError> {
|
||||
Texture::new(
|
||||
&mut self.factory,
|
||||
image,
|
||||
)
|
||||
}
|
||||
|
||||
/// Queue the rendering of the provided skybox model in the upcoming frame.
|
||||
pub fn render_skybox(
|
||||
&mut self,
|
||||
@ -205,6 +234,26 @@ impl Renderer {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Queue the rendering of the provided UI element in the upcoming frame.
|
||||
pub fn render_ui_element(
|
||||
&mut self,
|
||||
model: &Model<ui::UiPipeline>,
|
||||
locals: &Consts<ui::Locals>,
|
||||
tex: &Texture<ui::UiPipeline>,
|
||||
) {
|
||||
self.encoder.draw(
|
||||
&model.slice,
|
||||
&self.ui_pipeline.pso,
|
||||
&ui::pipe::Data {
|
||||
vbuf: model.vbuf.clone(),
|
||||
locals: locals.buf.clone(),
|
||||
tex: (tex.srv.clone(), tex.sampler.clone()),
|
||||
tgt_color: self.tgt_color_view.clone(),
|
||||
tgt_depth: self.tgt_depth_view.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct GfxPipeline<P: gfx::pso::PipelineInit> {
|
||||
|
58
voxygen/src/render/texture.rs
Normal file
58
voxygen/src/render/texture.rs
Normal file
@ -0,0 +1,58 @@
|
||||
// Standard
|
||||
use std::marker::PhantomData;
|
||||
|
||||
// Library
|
||||
use gfx::{
|
||||
self,
|
||||
traits::{Factory, FactoryExt},
|
||||
};
|
||||
use image::{
|
||||
DynamicImage,
|
||||
GenericImageView,
|
||||
};
|
||||
|
||||
// Local
|
||||
use super::{
|
||||
RenderError,
|
||||
mesh::Mesh,
|
||||
Pipeline,
|
||||
gfx_backend,
|
||||
};
|
||||
|
||||
type ShaderFormat = (gfx::format::R8_G8_B8_A8, gfx::format::Srgb);
|
||||
|
||||
/// Represents an image that has been uploaded to the GPU.
|
||||
pub struct Texture<P: Pipeline> {
|
||||
pub tex: gfx::handle::Texture<gfx_backend::Resources, <ShaderFormat as gfx::format::Formatted>::Surface>,
|
||||
pub srv: gfx::handle::ShaderResourceView<gfx_backend::Resources, <ShaderFormat as gfx::format::Formatted>::View>,
|
||||
pub sampler: gfx::handle::Sampler<gfx_backend::Resources>,
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: Pipeline> Texture<P> {
|
||||
pub fn new(
|
||||
factory: &mut gfx_backend::Factory,
|
||||
image: &DynamicImage,
|
||||
) -> Result<Self, RenderError> {
|
||||
let (tex, srv) = factory.create_texture_immutable_u8::<ShaderFormat>(
|
||||
gfx::texture::Kind::D2(
|
||||
image.width() as u16,
|
||||
image.height() as u16,
|
||||
gfx::texture::AaMode::Single,
|
||||
),
|
||||
gfx::texture::Mipmap::Provided,
|
||||
&[&image.to_rgba().into_raw()],
|
||||
)
|
||||
.map_err(|err| RenderError::CombinedError(err))?;
|
||||
|
||||
Ok(Self {
|
||||
tex,
|
||||
srv,
|
||||
sampler: factory.create_sampler(gfx::texture::SamplerInfo::new(
|
||||
gfx::texture::FilterMethod::Scale,
|
||||
gfx::texture::WrapMode::Clamp,
|
||||
)),
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
@ -60,6 +60,12 @@ impl Camera {
|
||||
.max(-PI / 2.0);
|
||||
}
|
||||
|
||||
/// Zoom the camera by the given delta, limiting the input accordingly.
|
||||
pub fn zoom_by(&mut self, delta: f32) {
|
||||
// Clamp camera dist to the 0 <= x <= infinity range
|
||||
self.dist = (self.dist + delta).max(0.0);
|
||||
}
|
||||
|
||||
/// Get the focus position of the camera.
|
||||
pub fn get_focus_pos(&self) -> Vec3<f32> { self.focus }
|
||||
/// Set the focus position of the camera.
|
||||
|
@ -112,13 +112,25 @@ impl Scene {
|
||||
pub fn camera_mut(&mut self) -> &mut Camera { &mut self.camera }
|
||||
|
||||
/// Handle an incoming user input event (i.e: cursor moved, key pressed, window closed, etc.).
|
||||
///
|
||||
/// If the event is handled, return true
|
||||
pub fn handle_input_event(&mut self, event: Event) -> bool {
|
||||
match event {
|
||||
// When the window is resized, change the camera's aspect ratio
|
||||
Event::Resize(dims) => {
|
||||
self.camera.set_aspect_ratio(dims.x as f32 / dims.y as f32);
|
||||
true
|
||||
},
|
||||
// Panning the cursor makes the camera rotate
|
||||
Event::CursorPan(delta) => {
|
||||
self.camera.rotate_by(Vec3::from(delta) * CURSOR_PAN_SCALE);
|
||||
true
|
||||
},
|
||||
// Zoom the camera when a zoom event occurs
|
||||
Event::Zoom(delta) => {
|
||||
self.camera.zoom_by(delta);
|
||||
true
|
||||
},
|
||||
// All other events are unhandled
|
||||
_ => false,
|
||||
}
|
||||
|
@ -95,10 +95,6 @@ impl PlayState for SessionState {
|
||||
for event in global_state.window.fetch_events() {
|
||||
let _handled = match event {
|
||||
Event::Close => return PlayStateResult::Shutdown,
|
||||
// When the window is resized, change the camera's aspect ratio
|
||||
Event::Resize(dims) => {
|
||||
self.scene.camera_mut().set_aspect_ratio(dims.x as f32 / dims.y as f32);
|
||||
},
|
||||
// When 'q' is pressed, exit the session
|
||||
Event::Char('q') => return PlayStateResult::Pop,
|
||||
// Toggle cursor grabbing
|
||||
|
80
voxygen/src/ui/element/image.rs
Normal file
80
voxygen/src/ui/element/image.rs
Normal file
@ -0,0 +1,80 @@
|
||||
// Standard
|
||||
use std::{
|
||||
rc::Rc,
|
||||
cell::RefCell,
|
||||
};
|
||||
|
||||
// Library
|
||||
use image::DynamicImage;
|
||||
use vek::*;
|
||||
|
||||
// Crate
|
||||
use crate::render::{
|
||||
Consts,
|
||||
UiLocals,
|
||||
Renderer,
|
||||
Texture,
|
||||
UiPipeline,
|
||||
};
|
||||
|
||||
// Local
|
||||
use super::{
|
||||
super::{
|
||||
UiError,
|
||||
Cache,
|
||||
},
|
||||
Element,
|
||||
Bounds,
|
||||
SizeRequest,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Image {
|
||||
texture: Rc<Texture<UiPipeline>>,
|
||||
locals: Consts<UiLocals>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn new(renderer: &mut Renderer, image: &DynamicImage) -> Result<Self, UiError> {
|
||||
Ok(Self {
|
||||
texture: Rc::new(
|
||||
renderer.create_texture(image)
|
||||
.map_err(|err| UiError::RenderError(err))?
|
||||
),
|
||||
locals: renderer.create_consts(&[UiLocals::default()])
|
||||
.map_err(|err| UiError::RenderError(err))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Image {
|
||||
fn get_hsize_request(&self) -> SizeRequest { SizeRequest::indifferent() }
|
||||
fn get_vsize_request(&self) -> SizeRequest { SizeRequest::indifferent() }
|
||||
|
||||
fn maintain(
|
||||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
) {
|
||||
renderer.update_consts(&mut self.locals, &[UiLocals::new(
|
||||
[bounds.x, bounds.y, bounds.w, bounds.h],
|
||||
)])
|
||||
.expect("Could not update UI image consts");
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
) {
|
||||
renderer.render_ui_element(
|
||||
cache.model(),
|
||||
&self.locals,
|
||||
&self.texture,
|
||||
);
|
||||
}
|
||||
}
|
181
voxygen/src/ui/element/mod.rs
Normal file
181
voxygen/src/ui/element/mod.rs
Normal file
@ -0,0 +1,181 @@
|
||||
pub mod image;
|
||||
|
||||
// Standard
|
||||
use std::rc::Rc;
|
||||
|
||||
// Library
|
||||
use vek::*;
|
||||
|
||||
// Crate
|
||||
use crate::render::{
|
||||
Renderer,
|
||||
Texture,
|
||||
Consts,
|
||||
UiLocals,
|
||||
UiPipeline,
|
||||
};
|
||||
|
||||
// Local
|
||||
use super::{
|
||||
UiError,
|
||||
Cache,
|
||||
Span,
|
||||
SizeRequest,
|
||||
};
|
||||
|
||||
// Bounds
|
||||
|
||||
pub type Bounds<T> = Rect<T, T>;
|
||||
|
||||
pub trait BoundsExt {
|
||||
fn relative_to(self, other: Self) -> Self;
|
||||
}
|
||||
|
||||
impl BoundsExt for Bounds<f32> {
|
||||
fn relative_to(self, other: Self) -> Self {
|
||||
Self::new(
|
||||
other.x + self.x * other.w,
|
||||
other.y + self.y * other.h,
|
||||
self.w * other.w,
|
||||
self.h * other.h,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BoundsSpan {
|
||||
fn in_resolution(self, resolution: Vec2<f32>) -> Bounds<f32>;
|
||||
}
|
||||
|
||||
impl BoundsSpan for Bounds<Span> {
|
||||
fn in_resolution(self, resolution: Vec2<f32>) -> Bounds<f32> {
|
||||
Bounds::new(
|
||||
self.x.to_rel(resolution.x).rel,
|
||||
self.y.to_rel(resolution.y).rel,
|
||||
self.w.to_rel(resolution.x).rel,
|
||||
self.h.to_rel(resolution.y).rel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Element
|
||||
|
||||
pub trait Element: 'static {
|
||||
//fn deep_clone(&self) -> Rc<dyn Element>;
|
||||
|
||||
fn get_hsize_request(&self) -> SizeRequest;
|
||||
fn get_vsize_request(&self) -> SizeRequest;
|
||||
|
||||
fn maintain(
|
||||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
);
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
);
|
||||
}
|
||||
|
||||
// Surface
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Surface {
|
||||
Transparent,
|
||||
Color(Rgba<f32>),
|
||||
Texture(Rc<Texture<UiPipeline>>),
|
||||
Bevel,
|
||||
}
|
||||
|
||||
// Widget
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Widget<E: Element> {
|
||||
inner: Box<E>,
|
||||
background: Surface,
|
||||
margin_top: Span,
|
||||
margin_bottom: Span,
|
||||
margin_left: Span,
|
||||
margin_right: Span,
|
||||
locals: Consts<UiLocals>,
|
||||
}
|
||||
|
||||
impl<E: Element> Widget<E> {
|
||||
pub fn new(renderer: &mut Renderer, inner: E) -> Result<Box<Self>, UiError> {
|
||||
Ok(Box::new(Self {
|
||||
inner: Box::new(inner),
|
||||
background: Surface::Transparent,
|
||||
margin_top: Span::rel(0.2),
|
||||
margin_bottom: Span::rel(0.2),
|
||||
margin_left: Span::rel(0.2),
|
||||
margin_right: Span::rel(0.2),
|
||||
locals: renderer.create_consts(&[UiLocals::default()])
|
||||
.map_err(|err| UiError::RenderError(err))?,
|
||||
}))
|
||||
}
|
||||
|
||||
fn get_inner_bounds(&self) -> Bounds<Span> {
|
||||
Bounds::new(
|
||||
self.margin_left,
|
||||
self.margin_top,
|
||||
Span::full() - self.margin_left - self.margin_right,
|
||||
Span::full() - self.margin_top - self.margin_bottom,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Element> Element for Widget<E> {
|
||||
fn get_hsize_request(&self) -> SizeRequest {
|
||||
self.inner.get_hsize_request() + self.margin_left + self.margin_right
|
||||
}
|
||||
|
||||
fn get_vsize_request(&self) -> SizeRequest {
|
||||
self.inner.get_vsize_request() + self.margin_top + self.margin_bottom
|
||||
}
|
||||
|
||||
fn maintain(
|
||||
&mut self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
) {
|
||||
renderer.update_consts(&mut self.locals, &[UiLocals::new(
|
||||
[bounds.x, bounds.y, bounds.w, bounds.h],
|
||||
)])
|
||||
.expect("Could not update UI image consts");
|
||||
|
||||
let inner_bounds = self
|
||||
.get_inner_bounds()
|
||||
.in_resolution(resolution)
|
||||
.relative_to(bounds);
|
||||
|
||||
self.inner.maintain(renderer, cache, inner_bounds, resolution);
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
cache: &Cache,
|
||||
bounds: Bounds<f32>,
|
||||
resolution: Vec2<f32>,
|
||||
) {
|
||||
renderer.render_ui_element(
|
||||
cache.model(),
|
||||
&self.locals,
|
||||
&cache.blank_texture(),
|
||||
);
|
||||
|
||||
let inner_bounds = self
|
||||
.get_inner_bounds()
|
||||
.in_resolution(resolution)
|
||||
.relative_to(bounds);
|
||||
|
||||
self.inner.render(renderer, cache, inner_bounds, resolution);
|
||||
}
|
||||
}
|
85
voxygen/src/ui/mod.rs
Normal file
85
voxygen/src/ui/mod.rs
Normal file
@ -0,0 +1,85 @@
|
||||
pub mod element;
|
||||
pub mod size_request;
|
||||
pub mod span;
|
||||
|
||||
// Reexports
|
||||
pub use self::{
|
||||
span::Span,
|
||||
size_request::SizeRequest,
|
||||
};
|
||||
|
||||
// Library
|
||||
use image::DynamicImage;
|
||||
|
||||
// Crate
|
||||
use crate::{
|
||||
Error,
|
||||
render::{
|
||||
RenderError,
|
||||
Renderer,
|
||||
Model,
|
||||
Texture,
|
||||
UiPipeline,
|
||||
create_ui_quad_mesh,
|
||||
},
|
||||
};
|
||||
|
||||
// Local
|
||||
use self::element::{
|
||||
Element,
|
||||
Bounds,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UiError {
|
||||
RenderError(RenderError),
|
||||
}
|
||||
|
||||
pub struct Cache {
|
||||
model: Model<UiPipeline>,
|
||||
blank_texture: Texture<UiPipeline>,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub fn new(renderer: &mut Renderer) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
model: renderer.create_model(&create_ui_quad_mesh())?,
|
||||
blank_texture: renderer.create_texture(&DynamicImage::new_rgba8(1, 1))?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn model(&self) -> &Model<UiPipeline> { &self.model }
|
||||
pub fn blank_texture(&self) -> &Texture<UiPipeline> { &self.blank_texture }
|
||||
}
|
||||
|
||||
pub struct Ui {
|
||||
base: Box<dyn Element>,
|
||||
cache: Cache,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn new<E: Element>(renderer: &mut Renderer, base: Box<E>) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
base,
|
||||
cache: Cache::new(renderer)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn maintain(&mut self, renderer: &mut Renderer) {
|
||||
self.base.maintain(
|
||||
renderer,
|
||||
&self.cache,
|
||||
Bounds::new(0.0, 0.0, 1.0, 1.0),
|
||||
renderer.get_resolution().map(|e| e as f32),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render(&self, renderer: &mut Renderer) {
|
||||
self.base.render(
|
||||
renderer,
|
||||
&self.cache,
|
||||
Bounds::new(0.0, 0.0, 1.0, 1.0),
|
||||
renderer.get_resolution().map(|e| e as f32),
|
||||
);
|
||||
}
|
||||
}
|
30
voxygen/src/ui/size_request.rs
Normal file
30
voxygen/src/ui/size_request.rs
Normal file
@ -0,0 +1,30 @@
|
||||
// Standard
|
||||
use std::ops::Add;
|
||||
|
||||
// Local
|
||||
use super::Span;
|
||||
|
||||
pub struct SizeRequest {
|
||||
min: Span,
|
||||
max: Span,
|
||||
}
|
||||
|
||||
impl SizeRequest {
|
||||
pub fn indifferent() -> Self {
|
||||
Self {
|
||||
min: Span::rel(0.0),
|
||||
max: Span::rel(std::f32::INFINITY),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Span> for SizeRequest {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, span: Span) -> Self {
|
||||
Self {
|
||||
min: self.min + span,
|
||||
max: self.max + span,
|
||||
}
|
||||
}
|
||||
}
|
47
voxygen/src/ui/span.rs
Normal file
47
voxygen/src/ui/span.rs
Normal file
@ -0,0 +1,47 @@
|
||||
// Standard
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Span {
|
||||
pub rel: f32,
|
||||
pub abs: f32,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub fn rel(rel: f32) -> Self { Self { rel, abs: 0.0 } }
|
||||
pub fn abs(abs: f32) -> Self { Self { rel: 0.0, abs } }
|
||||
|
||||
pub fn full() -> Self { Self { rel: 1.0, abs: 0.0 } }
|
||||
pub fn half() -> Self { Self { rel: 0.5, abs: 0.0 } }
|
||||
pub fn none() -> Self { Self { rel: 0.0, abs: 0.0 } }
|
||||
|
||||
pub fn to_abs(self, res: f32) -> Self {
|
||||
Self { rel: 0.0, abs: self.rel * res + self.abs }
|
||||
}
|
||||
|
||||
pub fn to_rel(self, res: f32) -> Self {
|
||||
Self { rel: self.rel + self.abs / res, abs: 0.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Span {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel + other.rel,
|
||||
abs: self.abs + other.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Span {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel - other.rel,
|
||||
abs: self.abs - other.abs,
|
||||
}
|
||||
}
|
||||
}
|
@ -97,6 +97,10 @@ impl Window {
|
||||
glutin::Event::DeviceEvent { event, .. } => match event {
|
||||
glutin::DeviceEvent::MouseMotion { delta: (dx, dy), .. } if cursor_grabbed =>
|
||||
events.push(Event::CursorPan(Vec2::new(dx as f32, dy as f32))),
|
||||
glutin::DeviceEvent::MouseWheel {
|
||||
delta: glutin::MouseScrollDelta::LineDelta(_x, y),
|
||||
..
|
||||
} if cursor_grabbed => events.push(Event::Zoom(y as f32)),
|
||||
_ => {},
|
||||
},
|
||||
_ => {},
|
||||
@ -136,6 +140,8 @@ pub enum Event {
|
||||
Char(char),
|
||||
/// The cursor has been panned across the screen while grabbed.
|
||||
CursorPan(Vec2<f32>),
|
||||
/// The camera has been requested to zoom.
|
||||
Zoom(f32),
|
||||
/// A key that the game recognises has been pressed down
|
||||
KeyDown(Key),
|
||||
/// A key that the game recognises has been released down
|
||||
|
1
voxygen/test_assets/.gitattributes
vendored
Normal file
1
voxygen/test_assets/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
BIN
voxygen/test_assets/test.png
(Stored with Git LFS)
Normal file
BIN
voxygen/test_assets/test.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
voxygen/test_assets/wall.png
(Stored with Git LFS)
Normal file
BIN
voxygen/test_assets/wall.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -32,6 +32,7 @@ impl World {
|
||||
let air = Block::empty();
|
||||
let stone = Block::new(1, Rgb::new(200, 220, 255));
|
||||
let grass = Block::new(2, Rgb::new(50, 255, 0));
|
||||
let sand = Block::new(3, Rgb::new(180, 150, 50));
|
||||
|
||||
let perlin_nz = Perlin::new();
|
||||
|
||||
@ -48,7 +49,7 @@ impl World {
|
||||
if wposf.z < height - 1.0 {
|
||||
stone
|
||||
} else {
|
||||
grass
|
||||
sand
|
||||
}
|
||||
} else {
|
||||
air
|
||||
|
Loading…
Reference in New Issue
Block a user