use ffi::gl;
#[cfg(not(target_arch="wasm32"))] use glutin;
use geom::{ Rectangle, Transform, Vector};
#[cfg(not(target_arch="wasm32"))] use glutin::{EventsLoop, GlContext};
use graphics::{Backend, BlendMode, Color, Drawable, GpuTriangle, ResizeStrategy, Vertex, View};
use input::{ButtonState, Event, Gamepad, GamepadProvider, Keyboard, Mouse};
#[repr(u32)]
#[derive(Debug)]
pub enum ImageScaleStrategy {
Pixelate = gl::NEAREST,
Blur = gl::LINEAR
}
#[derive(Debug)]
pub struct WindowBuilder {
title: &'static str,
width: u32,
height: u32,
show_cursor: bool,
#[cfg(not(target_arch="wasm32"))]
min_size: Option<Vector>,
#[cfg(not(target_arch="wasm32"))]
max_size: Option<Vector>,
resize: ResizeStrategy,
scale: ImageScaleStrategy,
fullscreen: bool
}
impl WindowBuilder {
pub fn new(title: &'static str, width: u32, height: u32) -> WindowBuilder {
WindowBuilder {
title,
width,
height,
show_cursor: true,
#[cfg(not(target_arch="wasm32"))]
min_size: None,
#[cfg(not(target_arch="wasm32"))]
max_size: None,
resize: ResizeStrategy::Fit,
scale: ImageScaleStrategy::Pixelate,
fullscreen: false
}
}
pub fn with_show_cursor(self, show_cursor: bool) -> WindowBuilder {
WindowBuilder {
show_cursor,
..self
}
}
pub fn with_resize_strategy(self, resize: ResizeStrategy) -> WindowBuilder {
WindowBuilder {
resize,
..self
}
}
pub fn with_minimum_size(self, _min_size: Vector) -> WindowBuilder {
WindowBuilder {
#[cfg(not(target_arch="wasm32"))]
min_size: Some(_min_size),
..self
}
}
pub fn with_maximum_size(self, _max_size: Vector) -> WindowBuilder {
WindowBuilder {
#[cfg(not(target_arch="wasm32"))]
max_size: Some(_max_size),
..self
}
}
pub fn with_scaling_strategy(self, scale: ImageScaleStrategy) -> WindowBuilder {
WindowBuilder {
scale,
..self
}
}
pub fn with_fullscreen(self, fullscreen: bool) -> WindowBuilder {
WindowBuilder {
fullscreen,
..self
}
}
#[cfg(not(target_arch="wasm32"))]
pub(crate) fn build(self) -> (Window, EventsLoop) {
let mut actual_width = self.width;
let mut actual_height = self.height;
let events = glutin::EventsLoop::new();
let window = glutin::WindowBuilder::new()
.with_decorations(!self.fullscreen)
.with_title(self.title);
let window = match self.min_size {
Some(v) => window.with_min_dimensions(v.x as u32, v.y as u32),
None => window
};
let window = match self.max_size {
Some(v) => window.with_max_dimensions(v.x as u32, v.y as u32),
None => window
};
if self.fullscreen {
let (w, h) = events.get_primary_monitor().get_dimensions();
actual_width = w;
actual_height = h;
}
let window = window.with_dimensions(actual_width, actual_height);
let context = glutin::ContextBuilder::new().with_vsync(true);
let gl_window = glutin::GlWindow::new(window, context, &events).unwrap();
unsafe {
gl_window.make_current().unwrap();
gl::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _);
}
gl_window.set_cursor_state(if self.show_cursor {
glutin::CursorState::Normal } else { glutin::CursorState::Hide }).unwrap();
let scale_factor = gl_window.hidpi_factor(); let screen_region = self.resize.resize(Vector::new(self.width, self.height), Vector::new(actual_width, actual_height));
let view = View::new(Rectangle::newv_sized(screen_region.size()));
(Window {
gl_window,
gamepads: Vec::new(),
gamepad_buffer: Vec::new(),
provider: GamepadProvider::new(),
resize: self.resize,
screen_region,
scale_factor,
keyboard: Keyboard { keys: [ButtonState::NotPressed; 256] },
mouse: Mouse { pos: Vector::zero(), buttons: [ButtonState::NotPressed; 3], wheel: Vector::zero() },
view,
backend: Backend::new(self.scale as u32),
vertices: Vec::new(),
triangles: Vec::new()
}, events)
}
#[cfg(target_arch="wasm32")]
pub(crate) fn build(self) -> Window {
let mut actual_width = self.width;
let mut actual_height = self.height;
use ffi::wasm;
use std::ffi::CString;
unsafe {
wasm::set_show_mouse(self.show_cursor);
if self.fullscreen {
actual_width = wasm::get_page_width();
actual_height = wasm::get_page_height();
}
wasm::create_context(CString::new(self.title).unwrap().into_raw(), actual_width, actual_height);
}
let screen_region = self.resize.resize(Vector::new(self.width, self.height), Vector::new(actual_width, actual_height));
let view = View::new(Rectangle::newv_sized(screen_region.size()));
Window {
gamepads: Vec::new(),
gamepad_buffer: Vec::new(),
provider: GamepadProvider::new(),
resize: self.resize,
screen_region,
scale_factor: 1.0,
keyboard: Keyboard { keys: [ButtonState::NotPressed; 256] },
mouse: Mouse { pos: Vector::zero(), buttons: [ButtonState::NotPressed; 3], wheel: Vector::zero() },
view,
backend: Backend::new(self.scale as u32),
vertices: Vec::new(),
triangles: Vec::new()
}
}
}
pub struct Window {
#[cfg(not(target_arch="wasm32"))]
pub(crate) gl_window: glutin::GlWindow,
provider: GamepadProvider,
gamepads: Vec<Gamepad>,
gamepad_buffer: Vec<Gamepad>, resize: ResizeStrategy,
pub(crate) scale_factor: f32,
screen_region: Rectangle,
keyboard: Keyboard,
mouse: Mouse,
view: View,
pub(crate) backend: Backend,
vertices: Vec<Vertex>,
triangles: Vec<GpuTriangle>
}
impl Window {
pub(crate) fn process_event(&mut self, event: &Event) {
match event {
&Event::Key(key, state) => self.keyboard.process_event(key as usize, state),
&Event::MouseMoved(pos) => self.mouse = Mouse {
pos: self.unproject() * pos,
..self.mouse
},
&Event::MouseWheel(wheel) => self.mouse = Mouse { wheel, ..self.mouse },
&Event::MouseButton(button, state) => self.mouse.process_button(button, state),
_ => ()
}
}
pub(crate) fn update_gamepads(&mut self, events: &mut Vec<Event>) {
self.provider.provide_gamepads(&mut self.gamepad_buffer);
let (mut i, mut j) = (0, 0);
while i < self.gamepads.len() && j < self.gamepad_buffer.len() {
if self.gamepads[i].id() == self.gamepad_buffer[j].id() {
self.gamepad_buffer[j].set_previous(&self.gamepads[i], events);
i += 1;
j += 1;
} else if self.gamepads[i].id() > self.gamepad_buffer[j].id() {
events.push(Event::GamepadDisconnected(self.gamepad_buffer[j].id()));
j += 1;
} else {
events.push(Event::GamepadConnected(self.gamepads[i].id()));
i += 1;
}
}
self.gamepads.clear();
self.gamepads.append(&mut self.gamepad_buffer);
}
pub fn clear_temporary_states(&mut self) {
self.keyboard.clear_temporary_states();
self.mouse.clear_temporary_states();
for gamepad in self.gamepads.iter_mut() {
gamepad.clear_temporary_states();
}
}
pub(crate) fn adjust_size(&mut self, available: Vector) {
self.screen_region = self.resize.resize(self.screen_region.size(), available);
unsafe { gl::Viewport(self.screen_region.x as i32, self.screen_region.y as i32,
self.screen_region.width as i32, self.screen_region.height as i32); }
#[cfg(not(target_arch="wasm32"))]
self.gl_window.resize(self.screen_region.width as u32, self.screen_region.height as u32);
}
pub fn view(&self) -> View {
self.view
}
pub fn set_view(&mut self, view: View) {
self.view = view;
}
pub fn resize_strategy(&self) -> ResizeStrategy {
self.resize
}
pub fn set_resize_strategy(&mut self, resize: ResizeStrategy) {
let available = self.resize.get_window_size(self.screen_region);
self.resize = resize;
self.adjust_size(available);
}
#[cfg(not(target_arch="wasm32"))]
pub(crate) fn screen_offset(&self) -> Vector {
self.screen_region.top_left()
}
pub fn screen_size(&self) -> Vector {
self.screen_region.size()
}
pub fn unproject(&self) -> Transform {
Transform::scale(self.screen_size() / self.scale_factor)
* self.view.normalize
}
pub fn project(&self) -> Transform {
self.unproject().inverse()
}
pub fn keyboard(&self) -> &Keyboard {
&self.keyboard
}
pub fn mouse(&self) -> Mouse {
Mouse {
pos: self.project() * self.mouse.pos,
..self.mouse.clone()
}
}
pub fn set_title(&self, title: &str) {
self.set_title_impl(title);
}
#[cfg(not(target_arch="wasm32"))]
fn set_title_impl(&self, title: &str) {
self.gl_window.set_title(title);
}
#[cfg(target_arch="wasm32")]
fn set_title_impl(&self, title: &str) {
use ffi::wasm;
use std::ffi::CString;
unsafe { wasm::set_title(CString::new(title).unwrap().into_raw()) };
}
pub fn clear(&mut self, color: Color) {
self.vertices.clear();
self.triangles.clear();
self.backend.clear(color);
self.backend.reset_blend_mode();
}
pub fn present(&mut self) {
self.flush();
#[cfg(not(target_arch="wasm32"))]
self.gl_window.swap_buffers().unwrap();
}
pub fn flush(&mut self) {
self.triangles.sort();
self.backend.draw(self.vertices.as_slice(), self.triangles.as_slice());
self.vertices.clear();
self.triangles.clear();
}
pub fn set_blend_mode(&mut self, blend: BlendMode) {
self.flush();
self.backend.set_blend_mode(blend);
}
pub fn reset_blend_mode(&mut self) {
self.flush();
self.backend.reset_blend_mode();
}
pub fn draw<T: Drawable>(&mut self, item: &T) {
item.draw(self);
}
pub fn add_vertices<V, T>(&mut self, vertices: V, triangles: T) where V: Iterator<Item = Vertex>, T: Iterator<Item = GpuTriangle> {
let offset = self.vertices.len() as u32;
self.triangles.extend(triangles.map(|t| GpuTriangle {
indices: [t.indices[0] + offset, t.indices[1] + offset, t.indices[2] + offset],
..t
}));
let opengl = self.view.opengl;
self.vertices.extend(vertices.map(|v| Vertex {
pos: opengl * v.pos,
..v
}));
}
pub fn gamepads(&self) -> &Vec<Gamepad> {
&self.gamepads
}
}