From c16a257ae35efd054caf08b29e35ba7227b9eb9d Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Wed, 30 Jan 2019 12:11:34 +0000 Subject: [PATCH] Added server-cli, UI tests Former-commit-id: 93bf5b39138920aa7a4a773a8247d716866c4a05 --- Cargo.toml | 1 + server-cli/.gitignore | 3 + server-cli/Cargo.toml | 12 ++ server-cli/src/main.rs | 35 ++++++ server/src/lib.rs | 6 + voxygen/Cargo.toml | 1 + voxygen/shaders/ui.frag | 17 +++ voxygen/shaders/ui.vert | 22 ++++ voxygen/src/main.rs | 15 ++- voxygen/src/menu/title.rs | 27 ++++- voxygen/src/render/mod.rs | 8 ++ voxygen/src/render/pipelines/mod.rs | 1 + voxygen/src/render/pipelines/ui.rs | 76 ++++++++++++ voxygen/src/render/renderer.rs | 51 +++++++- voxygen/src/render/texture.rs | 58 +++++++++ voxygen/src/scene/camera.rs | 6 + voxygen/src/scene/mod.rs | 12 ++ voxygen/src/session.rs | 4 - voxygen/src/ui/element/image.rs | 80 ++++++++++++ voxygen/src/ui/element/mod.rs | 181 ++++++++++++++++++++++++++++ voxygen/src/ui/mod.rs | 85 +++++++++++++ voxygen/src/ui/size_request.rs | 30 +++++ voxygen/src/ui/span.rs | 47 ++++++++ voxygen/src/window.rs | 6 + voxygen/test_assets/.gitattributes | 1 + voxygen/test_assets/test.png | 3 + voxygen/test_assets/wall.png | 3 + world/src/lib.rs | 3 +- 28 files changed, 779 insertions(+), 15 deletions(-) create mode 100644 server-cli/.gitignore create mode 100644 server-cli/Cargo.toml create mode 100644 server-cli/src/main.rs create mode 100644 voxygen/shaders/ui.frag create mode 100644 voxygen/shaders/ui.vert create mode 100644 voxygen/src/render/pipelines/ui.rs create mode 100644 voxygen/src/render/texture.rs create mode 100644 voxygen/src/ui/element/image.rs create mode 100644 voxygen/src/ui/element/mod.rs create mode 100644 voxygen/src/ui/mod.rs create mode 100644 voxygen/src/ui/size_request.rs create mode 100644 voxygen/src/ui/span.rs create mode 100644 voxygen/test_assets/.gitattributes create mode 100644 voxygen/test_assets/test.png create mode 100644 voxygen/test_assets/wall.png diff --git a/Cargo.toml b/Cargo.toml index 3730694b99..f15ce50095 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "common", "client", "server", + "server-cli", "voxygen", "world", ] diff --git a/server-cli/.gitignore b/server-cli/.gitignore new file mode 100644 index 0000000000..693699042b --- /dev/null +++ b/server-cli/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/server-cli/Cargo.toml b/server-cli/Cargo.toml new file mode 100644 index 0000000000..d682c8b971 --- /dev/null +++ b/server-cli/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "veloren-server-cli" +version = "0.1.0" +authors = ["Joshua Barretto "] +edition = "2018" + +[dependencies] +server = { package = "veloren-server", path = "../server" } +common = { package = "veloren-common", path = "../common" } + +log = "0.4" +pretty_env_logger = "0.3" diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs new file mode 100644 index 0000000000..a2f280f9e6 --- /dev/null +++ b/server-cli/src/main.rs @@ -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)); + } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 3e202b9482..04daffe622 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -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(); + } } diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 9535b62ecf..c3e3076510 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -29,3 +29,4 @@ lazy_static = "1.1" log = "0.4" pretty_env_logger = "0.3" dot_vox = "1.0" +image = "0.21" diff --git a/voxygen/shaders/ui.frag b/voxygen/shaders/ui.frag new file mode 100644 index 0000000000..ab281e85f3 --- /dev/null +++ b/voxygen/shaders/ui.frag @@ -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); +} diff --git a/voxygen/shaders/ui.vert b/voxygen/shaders/ui.vert new file mode 100644 index 0000000000..453cad7719 --- /dev/null +++ b/voxygen/shaders/ui.vert @@ -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); +} diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index 3143258e71..790287d321 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -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> = 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> = 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. diff --git a/voxygen/src/menu/title.rs b/voxygen/src/menu/title.rs index 39c48e12a6..b9534a0a3e 100644 --- a/voxygen/src/menu/title.rs +++ b/voxygen/src/menu/title.rs @@ -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 diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 7088eb4c19..5d06de118a 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -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), UpdateError(gfx::UpdateError), + CombinedError(gfx::CombinedError), } /// Used to represent a specific rendering configuration. diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index 445259b8ce..6de0d417ef 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -1,6 +1,7 @@ pub mod figure; pub mod skybox; pub mod terrain; +pub mod ui; // Library use gfx::{ diff --git a/voxygen/src/render/pipelines/ui.rs b/voxygen/src/render/pipelines/ui.rs new file mode 100644 index 0000000000..4d3b224734 --- /dev/null +++ b/voxygen/src/render/pipelines/ui.rs @@ -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 = (), + + locals: gfx::ConstantBuffer = "u_locals", + tex: gfx::TextureSampler<[f32; 4]> = "u_tex", + + tgt_color: gfx::RenderTarget = "tgt_color", + tgt_depth: gfx::DepthTarget = 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 { + 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 +} diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index c2e431cd9c..5f6ce2e416 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -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>, figure_pipeline: GfxPipeline>, terrain_pipeline: GfxPipeline>, + ui_pipeline: GfxPipeline>, } 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 { + 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) { @@ -144,6 +165,14 @@ impl Renderer { )) } + /// Create a new texture from the provided image. + pub fn create_texture(&mut self, image: &image::DynamicImage) -> Result, 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, + locals: &Consts, + tex: &Texture, + ) { + 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 { diff --git a/voxygen/src/render/texture.rs b/voxygen/src/render/texture.rs new file mode 100644 index 0000000000..cff9fb1e20 --- /dev/null +++ b/voxygen/src/render/texture.rs @@ -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 { + pub tex: gfx::handle::Texture::Surface>, + pub srv: gfx::handle::ShaderResourceView::View>, + pub sampler: gfx::handle::Sampler, + _phantom: PhantomData

, +} + +impl Texture

{ + pub fn new( + factory: &mut gfx_backend::Factory, + image: &DynamicImage, + ) -> Result { + let (tex, srv) = factory.create_texture_immutable_u8::( + 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, + }) + } +} diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index c84072350a..dc5ad7d870 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -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 { self.focus } /// Set the focus position of the camera. diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 4e440d97a1..3a8e772c00 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -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, } diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 850094d186..6b51b49ab5 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -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 diff --git a/voxygen/src/ui/element/image.rs b/voxygen/src/ui/element/image.rs new file mode 100644 index 0000000000..cc782221f9 --- /dev/null +++ b/voxygen/src/ui/element/image.rs @@ -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>, + locals: Consts, +} + +impl Image { + pub fn new(renderer: &mut Renderer, image: &DynamicImage) -> Result { + 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, + resolution: Vec2, + ) { + 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, + resolution: Vec2, + ) { + renderer.render_ui_element( + cache.model(), + &self.locals, + &self.texture, + ); + } +} diff --git a/voxygen/src/ui/element/mod.rs b/voxygen/src/ui/element/mod.rs new file mode 100644 index 0000000000..1f8d486d1b --- /dev/null +++ b/voxygen/src/ui/element/mod.rs @@ -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 = Rect; + +pub trait BoundsExt { + fn relative_to(self, other: Self) -> Self; +} + +impl BoundsExt for Bounds { + 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) -> Bounds; +} + +impl BoundsSpan for Bounds { + fn in_resolution(self, resolution: Vec2) -> Bounds { + 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; + + fn get_hsize_request(&self) -> SizeRequest; + fn get_vsize_request(&self) -> SizeRequest; + + fn maintain( + &mut self, + renderer: &mut Renderer, + cache: &Cache, + bounds: Bounds, + resolution: Vec2, + ); + + fn render( + &self, + renderer: &mut Renderer, + cache: &Cache, + bounds: Bounds, + resolution: Vec2, + ); +} + +// Surface + +#[derive(Clone)] +pub enum Surface { + Transparent, + Color(Rgba), + Texture(Rc>), + Bevel, +} + +// Widget + +#[derive(Clone)] +pub struct Widget { + inner: Box, + background: Surface, + margin_top: Span, + margin_bottom: Span, + margin_left: Span, + margin_right: Span, + locals: Consts, +} + +impl Widget { + pub fn new(renderer: &mut Renderer, inner: E) -> Result, 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 { + 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 Element for Widget { + 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, + resolution: Vec2, + ) { + 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, + resolution: Vec2, + ) { + 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); + } +} diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs new file mode 100644 index 0000000000..89183c37ff --- /dev/null +++ b/voxygen/src/ui/mod.rs @@ -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, + blank_texture: Texture, +} + +impl Cache { + pub fn new(renderer: &mut Renderer) -> Result { + 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 { &self.model } + pub fn blank_texture(&self) -> &Texture { &self.blank_texture } +} + +pub struct Ui { + base: Box, + cache: Cache, +} + +impl Ui { + pub fn new(renderer: &mut Renderer, base: Box) -> Result { + 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), + ); + } +} diff --git a/voxygen/src/ui/size_request.rs b/voxygen/src/ui/size_request.rs new file mode 100644 index 0000000000..ad8658e099 --- /dev/null +++ b/voxygen/src/ui/size_request.rs @@ -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 for SizeRequest { + type Output = Self; + + fn add(self, span: Span) -> Self { + Self { + min: self.min + span, + max: self.max + span, + } + } +} diff --git a/voxygen/src/ui/span.rs b/voxygen/src/ui/span.rs new file mode 100644 index 0000000000..e06674164b --- /dev/null +++ b/voxygen/src/ui/span.rs @@ -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, + } + } +} diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 5c56310d8a..da6f086813 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -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), + /// 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 diff --git a/voxygen/test_assets/.gitattributes b/voxygen/test_assets/.gitattributes new file mode 100644 index 0000000000..c091529f36 --- /dev/null +++ b/voxygen/test_assets/.gitattributes @@ -0,0 +1 @@ +*.png filter=lfs diff=lfs merge=lfs -text \ No newline at end of file diff --git a/voxygen/test_assets/test.png b/voxygen/test_assets/test.png new file mode 100644 index 0000000000..8c3da8f238 --- /dev/null +++ b/voxygen/test_assets/test.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2918209672b43a1d58994dfa4e1daa477b38af6c5a183ae58bd776838dfb6012 +size 4648282 diff --git a/voxygen/test_assets/wall.png b/voxygen/test_assets/wall.png new file mode 100644 index 0000000000..fd437c4da0 --- /dev/null +++ b/voxygen/test_assets/wall.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:772c1420edbb88d3b47efb2b2cdc781d23ff17de20b36f07a7eb065a43018a08 +size 32152 diff --git a/world/src/lib.rs b/world/src/lib.rs index aa5407b9da..b296ff2e6b 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -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