use std::collections::HashMap;
use std::sync::mpsc::Sender;
use std::path::{Path, PathBuf};
use std::time;
use cgmath::{self, Vector2, Matrix4, SquareMatrix, Zero, InnerSpace};
use glutin::{self, Api, MouseButton};
use glutin::ElementState::{Pressed, Released};
use rusttype;
use gfx::traits::{FactoryExt, Device};
use gfx::handle::Program;
use gfx;
use gfx_device_gl;
use gfx_window_glutin;
use ::{Settings, Time, Event};
use ::{screen, fs};
use geom::{Size, Point};
use pipeline::pipe;
use mesh::Mesh;
use texture::{self, Texture};
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 {
shader_version_string(api) + &fs::load_as_string("shader/v.glsl")
}
fn fragment_shader(api: Api) -> String {
let mut text = shader_version_string(api);
if api == Api::OpenGlEs || api == Api::WebGl {
text += "precision mediump float;\n";
}
text + &fs::load_as_string("shader/f.glsl")
}
fn new_shader(
window: &glutin::Window,
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 new_font<P: AsRef<Path>>(path: P) -> rusttype::Font<'static> {
let collection = rusttype::FontCollection::from_bytes(fs::load(path));
collection.into_font().unwrap()
}
fn new_window_builder() -> glutin::WindowBuilder<'static> {
let gl_version = glutin::GlRequest::GlThenGles {
opengles_version: (2, 0),
opengl_version: (2, 1),
};
glutin::WindowBuilder::new()
.with_title("Zemeroth".to_string())
.with_pixel_format(24, 8)
.with_gl(gl_version)
}
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 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 / w - 1.0) * aspect_ratio,
y: 1.0 - 2.0 * y / 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,
}
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::Window,
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>,
start_time: time::Instant,
events: Vec<Event>,
settings: Settings,
texture_cache: HashMap<PathBuf, Texture>,
}
impl Context {
pub(super) fn new(tx: Sender<screen::Command>, settings: Settings) -> Context {
let events_loop = glutin::EventsLoop::new();
let (window, device, mut factory, out, out_depth) =
gfx_window_glutin::init(new_window_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.0, sampler),
out,
out_depth,
mvp: Matrix4::identity().into(),
};
let mouse = MouseState {
last_press_pos: Point(Vector2::zero()),
pos: Point(Vector2::zero()),
};
let font = new_font(&settings.font);
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,
start_time: time::Instant::now(),
events: Vec::new(),
texture_cache: HashMap::new(),
}
}
pub fn settings(&self) -> &Settings {
&self.settings
}
pub(super) 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(super) fn clear(&mut self) {
self.encoder.clear(&self.data.out, self.clear_color);
self.encoder.clear_depth(&self.data.out_depth, 1.0);
}
pub fn now(&self) -> Time {
Time::from_duration(time::Instant::now() - self.start_time)
}
pub(super) fn should_close(&self) -> bool {
self.should_close
}
pub(super) fn flush(&mut self) {
self.encoder.flush(&mut self.device);
self.window.swap_buffers().expect("Can`t swap buffers");
self.device.cleanup();
}
pub(super) fn font(&self) -> &rusttype::Font {
&self.font
}
pub(super) fn factory_mut(&mut self) -> &mut gfx_device_gl::Factory {
&mut self.factory
}
pub fn set_color(&mut self, color: [f32; 4]) {
self.data.basic_color = color;
}
fn mouse(&self) -> &MouseState {
&self.mouse
}
pub(super) fn draw_mesh(&mut self, mvp: Matrix4<f32>, mesh: &Mesh) {
self.data.mvp = mvp.into();
self.data.texture.0 = mesh.texture().0.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 {
let event = match *event {
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(Released, MouseButton::Left) => {
if self.is_tap() {
self.events.push(Event::Click {
pos: self.mouse.pos,
});
}
}
glutin::WindowEvent::MouseInput(Pressed, MouseButton::Left) => {
self.mouse.last_press_pos = self.mouse.pos;
}
glutin::WindowEvent::MouseMoved(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() {
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 });
}
_ => {}
}
}
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
}
}