Skip to content
Permalink
Tree: 1179dabf8e
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
391 lines (356 sloc) 12.2 KB
use std::collections::HashMap;
use std::sync::mpsc::Sender;
use std::path::{Path, PathBuf};
use cgmath::{self, InnerSpace, Matrix4, SquareMatrix, Vector2, Zero};
use glutin::{self, Api, GlContext, MouseButton};
use glutin::ElementState::{Pressed, Released};
use rusttype;
use gfx::traits::{Device, FactoryExt};
use gfx::handle::Program;
use gfx;
use gfx_device_gl;
use gfx_window_glutin;
use settings::Settings;
use event::Event;
use screen;
use fs;
use geom::{Point, Size};
use pipeline::pipe;
use mesh::Mesh;
use texture::{self, Texture};
use text;
fn shader_version_string(api: Api) -> String {
match api {
Api::OpenGl => "#version 120\n".into(),
Api::OpenGlEs | Api::WebGl => "#version 100\n".into(),
}
}
fn vertex_shader(api: Api) -> String {
let shader = r#"
uniform mat4 u_ModelViewProj;
attribute vec2 a_Pos;
attribute vec2 a_Uv;
varying vec2 v_Uv;
void main() {
v_Uv = a_Uv;
gl_Position = u_ModelViewProj * vec4(a_Pos, 0.0, 1.0);
}
"#;
shader_version_string(api) + shader
}
fn fragment_shader(api: Api) -> String {
let mut header = shader_version_string(api);
if api == Api::OpenGlEs || api == Api::WebGl {
header += "precision mediump float;\n";
}
let shader = r#"
uniform vec4 u_Basic_color;
uniform sampler2D t_Tex;
varying vec2 v_Uv;
void main() {
gl_FragColor = u_Basic_color * texture2D(t_Tex, v_Uv);
}
"#;
header + shader
}
fn new_shader(
window: &glutin::GlWindow,
factory: &mut gfx_device_gl::Factory,
) -> Program<gfx_device_gl::Resources> {
let api = window.get_api();
let vs = vertex_shader(api);
let fs = fragment_shader(api);
factory.link_program(vs.as_bytes(), fs.as_bytes()).unwrap()
}
fn new_pso(
factory: &mut gfx_device_gl::Factory,
program: &Program<gfx_device_gl::Resources>,
primitive: gfx::Primitive,
) -> gfx::PipelineState<gfx_device_gl::Resources, pipe::Meta> {
let rasterizer = gfx::state::Rasterizer::new_fill();
let pso = factory.create_pipeline_from_program(program, primitive, rasterizer, pipe::new());
pso.unwrap()
}
fn load_font_from_vec(data: Vec<u8>) -> rusttype::Font<'static> {
let collection = rusttype::FontCollection::from_bytes(data);
collection.into_font().unwrap()
}
fn load_font(settings: &Settings) -> rusttype::Font<'static> {
// Blame https://github.com/ron-rs/ron/issues/55 for this '<embedded>' hack:
if settings.font == Path::new("<embedded>") {
let data = include_bytes!("Karla-Regular.ttf");
load_font_from_vec(data.to_vec())
} else {
load_font_from_vec(fs::load(&settings.font))
}
}
fn get_win_size(window: &glutin::Window) -> Size<i32> {
let (w, h) = window.get_inner_size().unwrap();
Size {
w: w as i32,
h: h as i32,
}
}
fn window_to_screen(context: &Context, x: f32, y: f32) -> Point {
let dpi = context.window.hidpi_factor();
let w = context.win_size.w as f32;
let h = context.win_size.h as f32;
let aspect_ratio = w / h;
Point(Vector2 {
x: (2.0 * (x / dpi) / w - 1.0) * aspect_ratio,
y: 1.0 - 2.0 * (y / dpi) / h,
})
}
pub fn projection_matrix(win_size: Size<i32>) -> Matrix4<f32> {
let aspect_ratio = win_size.w as f32 / win_size.h as f32;
cgmath::Ortho {
left: -aspect_ratio,
right: aspect_ratio,
bottom: -1.0,
top: 1.0,
near: -1.0,
far: 1.0,
}.into()
}
#[derive(Clone, Debug)]
pub struct MouseState {
pub last_press_pos: Point,
pub pos: Point,
}
impl Default for MouseState {
fn default() -> Self {
Self {
last_press_pos: Point(Vector2::zero()),
pos: Point(Vector2::zero()),
}
}
}
fn gl_version() -> glutin::GlRequest {
glutin::GlRequest::GlThenGles {
opengles_version: (2, 0),
opengl_version: (2, 1),
}
}
// TODO: use gfx-rs generics, not gfx_device_gl types
pub struct Context {
events_loop: glutin::EventsLoop,
win_size: Size<i32>,
projection_matrix: Matrix4<f32>,
mouse: MouseState,
should_close: bool,
commands_tx: Sender<screen::Command>,
window: glutin::GlWindow,
clear_color: [f32; 4],
device: gfx_device_gl::Device,
encoder: gfx::Encoder<gfx_device_gl::Resources, gfx_device_gl::CommandBuffer>,
data: pipe::Data<gfx_device_gl::Resources>,
pso: gfx::PipelineState<gfx_device_gl::Resources, pipe::Meta>,
factory: gfx_device_gl::Factory,
font: rusttype::Font<'static>,
events: Vec<Event>,
settings: Settings,
texture_cache: HashMap<PathBuf, Texture>,
text_texture_cache: HashMap<String, Texture>,
}
impl Context {
pub(crate) fn new(tx: Sender<screen::Command>, settings: Settings) -> Context {
let window_builder = glutin::WindowBuilder::new().with_title("Zemeroth".to_string());
let context_builder = glutin::ContextBuilder::new()
.with_gl(gl_version())
.with_gl_profile(glutin::GlProfile::Compatibility)
.with_pixel_format(24, 8);
let events_loop = glutin::EventsLoop::new();
let (window, device, mut factory, out, out_depth) =
gfx_window_glutin::init(window_builder, context_builder, &events_loop);
let encoder = factory.create_command_buffer().into();
let program = new_shader(&window, &mut factory);
let primitive = gfx::Primitive::TriangleList;
let pso = new_pso(&mut factory, &program, primitive);
let sampler = factory.create_sampler_linear();
let win_size = get_win_size(&window);
let projection_matrix = projection_matrix(win_size);
let fake_texture = texture::load_raw(&mut factory, Size { w: 2, h: 2 }, &[0; 4]);
let fake_mesh = [];
let data = pipe::Data {
basic_color: [1.0, 1.0, 1.0, 1.0],
vbuf: factory.create_vertex_buffer(&fake_mesh),
texture: (fake_texture.raw, sampler),
out,
out_depth,
mvp: Matrix4::identity().into(),
};
let font = load_font(&settings);
Context {
settings,
events_loop,
data,
win_size,
projection_matrix,
clear_color: [1.0, 1.0, 1.0, 1.0],
window,
device,
factory,
encoder,
pso,
should_close: false,
commands_tx: tx,
font,
mouse: MouseState::default(),
events: Vec::new(),
texture_cache: HashMap::new(),
text_texture_cache: HashMap::new(),
}
}
pub fn settings(&self) -> &Settings {
&self.settings
}
/// Loads a texture with caching
pub(crate) fn load_texture<P: AsRef<Path>>(&mut self, path: P) -> Texture {
let path = path.as_ref().to_path_buf();
if let Some(texture) = self.texture_cache.get(&path) {
return texture.clone();
}
let texture = texture::load(self, &fs::load(&path));
self.texture_cache.insert(path, texture.clone());
texture
}
pub(crate) fn text_texture(&mut self, label: &str) -> Texture {
if let Some(texture) = self.text_texture_cache.get(label) {
return texture.clone();
}
let text_texture_height = self.settings().text_texture_height;
let (texture_size, texture_data) =
text::text_to_texture(self.font(), text_texture_height, label);
let texture = texture::load_raw(self.factory_mut(), texture_size, &texture_data);
self.text_texture_cache
.insert(label.to_owned(), texture.clone());
texture
}
pub(crate) fn clear(&mut self) {
self.encoder.clear(&self.data.out, self.clear_color);
self.encoder.clear_depth(&self.data.out_depth, 1.0);
}
pub(crate) fn should_close(&self) -> bool {
self.should_close
}
pub(crate) fn flush(&mut self) {
self.encoder.flush(&mut self.device);
self.window.swap_buffers().expect("Can`t swap buffers");
self.device.cleanup();
}
pub(crate) fn font(&self) -> &rusttype::Font {
&self.font
}
pub(crate) fn factory_mut(&mut self) -> &mut gfx_device_gl::Factory {
&mut self.factory
}
// TODO: add `set_bg_color`
pub fn set_color(&mut self, color: [f32; 4]) {
self.data.basic_color = color;
}
fn mouse(&self) -> &MouseState {
&self.mouse
}
pub(crate) fn draw_mesh(&mut self, mvp: Matrix4<f32>, mesh: &Mesh) {
self.data.mvp = mvp.into();
self.data.texture.0 = mesh.texture().raw.clone();
self.data.vbuf = mesh.vertex_buffer().clone();
self.encoder.draw(mesh.slice(), &self.pso, &self.data);
}
pub fn aspect_ratio(&self) -> f32 {
self.win_size.w as f32 / self.win_size.h as f32
}
pub fn add_command(&mut self, command: screen::Command) {
self.commands_tx.send(command).unwrap();
}
pub fn pull_events(&mut self) -> Vec<Event> {
let mut raw_events = Vec::new();
self.events_loop.poll_events(|e| raw_events.push(e));
for event in &raw_events {
if let glutin::Event::WindowEvent { ref event, .. } = *event {
self.handle_event(event);
}
}
self.events.split_off(0)
}
fn handle_event(&mut self, event: &glutin::WindowEvent) {
match *event {
glutin::WindowEvent::Closed => {
self.should_close = true;
}
glutin::WindowEvent::MouseInput {
state: Released,
button: MouseButton::Left,
..
} => if self.is_tap() {
self.events.push(Event::Click {
pos: self.mouse.pos,
});
},
glutin::WindowEvent::MouseInput {
state: Pressed,
button: MouseButton::Left,
..
} => {
self.mouse.last_press_pos = self.mouse.pos;
}
glutin::WindowEvent::CursorMoved {
position: (x, y), ..
} => {
self.mouse.pos = window_to_screen(self, x as f32, y as f32);
}
glutin::WindowEvent::Touch(touch_event) => {
let (x, y) = touch_event.location;
let pos = window_to_screen(self, x as f32, y as f32);
match touch_event.phase {
glutin::TouchPhase::Moved => {
self.mouse.pos = pos;
}
glutin::TouchPhase::Started => {
self.mouse.pos = pos;
self.mouse.last_press_pos = pos;
}
glutin::TouchPhase::Ended => {
self.mouse.pos = pos;
if self.is_tap() {
// TODO: check that this is an actual position!
self.events.push(Event::Click {
pos: self.mouse.pos,
});
}
}
glutin::TouchPhase::Cancelled => {
unimplemented!();
}
}
}
glutin::WindowEvent::Resized(w, h) => {
if w == 0 || h == 0 {
return;
}
self.win_size = Size {
w: w as i32,
h: h as i32,
};
self.projection_matrix = projection_matrix(self.win_size);
gfx_window_glutin::update_views(
&self.window,
&mut self.data.out,
&mut self.data.out_depth,
);
let aspect_ratio = self.aspect_ratio();
self.events.push(Event::Resize { aspect_ratio });
}
_ => {}
}
}
/// Check if this was a tap or a swipe
fn is_tap(&self) -> bool {
let mouse = self.mouse();
let diff = mouse.pos.0 - mouse.last_press_pos.0;
diff.magnitude() < self.settings.tap_tolerance
}
pub fn projection_matrix(&self) -> Matrix4<f32> {
self.projection_matrix
}
}
You can’t perform that action at this time.