use crate::{
Result,
backend::{Backend, BackendImpl, instance, set_instance},
geom::{Rectangle, Scalar, Transform, Vector},
graphics::{Background, BlendMode, Color, Drawable, Mesh, PixelFormat, ResizeStrategy, View},
input::{ButtonState, Gamepad, Keyboard, Mouse, MouseCursor},
lifecycle::{Event, Settings},
};
use image::{
DynamicImage, RgbImage, RgbaImage,
};
#[cfg(feature = "gilrs")] use {
crate::input::{GAMEPAD_BUTTON_LIST, GILRS_GAMEPAD_LIST},
gilrs::{
Axis, Gilrs,
ev::state::AxisData
}
};
#[cfg(target_arch = "wasm32")]
use {
crate::error::QuicksilverError,
stdweb::{
web::{
IElement, INode, document,
html_element::CanvasElement,
},
unstable::TryInto
}
};
#[cfg(not(target_arch = "wasm32"))]
use {
gl,
glutin::{self, EventsLoop, Icon}
};
pub struct Window {
#[cfg(feature = "gilrs")]
gilrs: Gilrs,
gamepads: Vec<Gamepad>,
gamepad_buffer: Vec<Gamepad>,
resize: ResizeStrategy,
screen_region: Rectangle,
keyboard: Keyboard,
mouse: Mouse,
view: View,
update_rate: f64,
max_updates: u32,
draw_rate: f64,
mesh: Mesh,
frame_count: f64,
fps: f64,
last_framerate: f64,
running: bool,
fullscreen: bool,
}
impl Window {
pub(crate) fn build_agnostic(user_size: Vector, actual_size: Vector, settings: Settings) -> Result<Window> {
let screen_region = settings.resize.resize(user_size, actual_size);
let view = View::new(Rectangle::new_sized(screen_region.size()));
let mut window = Window {
gamepads: Vec::new(),
gamepad_buffer: Vec::new(),
#[cfg(feature = "gilrs")]
gilrs: Gilrs::new()?,
resize: settings.resize,
screen_region,
keyboard: Keyboard {
keys: [ButtonState::NotPressed; 256],
},
mouse: Mouse {
pos: Vector::ZERO,
buttons: [ButtonState::NotPressed; 3],
wheel: Vector::ZERO,
},
view,
update_rate: settings.update_rate,
max_updates: settings.max_updates,
draw_rate: settings.draw_rate,
mesh: Mesh::new(),
frame_count: 0.0,
fps: 0.0,
last_framerate: 0.0,
running: true,
fullscreen: false
};
window.set_cursor(if settings.show_cursor {
MouseCursor::Default
} else {
MouseCursor::None
});
window.set_fullscreen(settings.fullscreen);
Ok(window)
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn build(title: &str, user_size: Vector, settings: Settings) -> Result<(Window, EventsLoop)> {
let events = glutin::EventsLoop::new();
let mut window = glutin::WindowBuilder::new()
.with_title(title)
.with_dimensions(user_size.into());
if let Some(path) = settings.icon_path {
window = window.with_window_icon(Some(Icon::from_path(path)?));
}
if let Some(v) = settings.min_size {
window = window.with_min_dimensions(v.into());
}
if let Some(v) = settings.max_size {
window = window.with_max_dimensions(v.into());
};
let context = glutin::ContextBuilder::new()
.with_vsync(settings.vsync)
.with_multisampling(settings.multisampling.unwrap_or(0));
let gl_window = context.build_windowed(window, &events)?;
let gl_window = unsafe {
let gl_window = match gl_window.make_current() {
Ok(window) => window,
Err((_, err)) => Err(err)?
};
gl::load_with(|symbol| gl_window.get_proc_address(symbol) as *const _);
gl_window
};
unsafe { set_instance(BackendImpl::new(gl_window, settings.scale, settings.multisampling != None)?) };
let window = Window::build_agnostic(user_size, user_size, settings)?;
Ok((window, events))
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn build(title: &str, size: Vector, settings: Settings) -> Result<(Window, CanvasElement)> {
let document = document();
if let Some(path) = settings.icon_path {
let head = document.head().ok_or(QuicksilverError::ContextError("Failed to find head node".to_owned()))?;
let element = document
.create_element("link")
.map_err(|_| QuicksilverError::ContextError("Failed to create link element".to_owned()))?;
element.set_attribute("rel", "shortcut icon")
.map_err(|_| QuicksilverError::ContextError("Failed to create favicon element".to_owned()))?;
element.set_attribute("type", "image/png")
.map_err(|_| QuicksilverError::ContextError("Failed to create favicon element".to_owned()))?;
element.set_attribute("href", path)
.map_err(|_| QuicksilverError::ContextError("Failed to create favicon element".to_owned()))?;
head.append_child(&element);
}
let element = document
.create_element("canvas")
.map_err(|_| QuicksilverError::ContextError("Failed to create canvas element".to_owned()))?;
let canvas: CanvasElement = element.try_into()
.map_err(|_| QuicksilverError::ContextError("Failed to create canvas element".to_owned()))?;
let body = document.body().ok_or(QuicksilverError::ContextError("Failed to find body node".to_owned()))?;
body.append_child(&canvas);
canvas.set_width(size.x as u32);
canvas.set_height(size.y as u32);
unsafe { set_instance(BackendImpl::new(canvas.clone(), settings.scale, settings.multisampling != None)?) };
let mut window = Window::build_agnostic(size, size, settings)?;
window.set_title(title);
Ok((window, canvas))
}
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>) {
#[cfg(feature = "gilrs")]
self.update_gamepads_impl();
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);
}
#[cfg(feature = "gilrs")]
pub(crate) fn update_gamepads_impl(&mut self) {
while let Some(ev) = self.gilrs.next_event() {
self.gilrs.update(&ev);
}
fn axis_value(data: Option<&AxisData>) -> f32 {
match data {
Some(ref data) => data.value(),
None => 0.0
}
}
self.gamepad_buffer.extend(self.gilrs.gamepads().map(|(id, gamepad)| {
let id: usize = id.into();
let id = id as i32;
let axes = [
axis_value(gamepad.axis_data(Axis::LeftStickX)),
axis_value(gamepad.axis_data(Axis::LeftStickY)),
axis_value(gamepad.axis_data(Axis::RightStickX)),
axis_value(gamepad.axis_data(Axis::RightStickY))
];
let mut buttons = [ButtonState::NotPressed; 17];
for i in 0..GAMEPAD_BUTTON_LIST.len() {
let button = GAMEPAD_BUTTON_LIST[i];
let value = match gamepad.button_data(GILRS_GAMEPAD_LIST[i]) {
Some(ref data) => data.is_pressed(),
None => false
};
let state = if value { ButtonState::Pressed } else { ButtonState::Released };
buttons[button as usize] = state;
}
Gamepad { id, axes, buttons }
}));
}
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 { self.backend().set_viewport(self.screen_region); }
}
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);
}
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.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 clear(&mut self, color: Color) -> Result<()> {
self.clear_letterbox_color(color, Color::BLACK)
}
pub fn clear_letterbox_color(&mut self, color: Color, letterbox: Color) -> Result<()> {
self.mesh.clear();
unsafe {
self.backend().reset_blend_mode();
self.backend().clear_color(color, letterbox)
}
}
pub fn flush(&mut self) -> Result<()> {
self.mesh.triangles.sort();
for vertex in self.mesh.vertices.iter_mut() {
vertex.pos = self.view.opengl * vertex.pos;
}
unsafe {
self.backend().draw(self.mesh.vertices.as_slice(), self.mesh.triangles.as_slice())?;
}
self.mesh.clear();
Ok(())
}
pub fn set_blend_mode(&mut self, blend: BlendMode) -> Result<()> {
self.flush()?;
unsafe {
self.backend().set_blend_mode(blend);
}
Ok(())
}
pub fn reset_blend_mode(&mut self) -> Result<()> {
self.flush()?;
unsafe {
self.backend().reset_blend_mode();
}
Ok(())
}
pub fn gamepads(&self) -> &Vec<Gamepad> {
&self.gamepads
}
pub fn draw<'a>(&'a mut self, draw: &impl Drawable, bkg: impl Into<Background<'a>>) {
self.draw_ex(draw, bkg.into(), Transform::IDENTITY, 0);
}
pub fn draw_ex<'a>(&'a mut self, draw: &impl Drawable, bkg: impl Into<Background<'a>>, trans: Transform, z: impl Scalar) {
draw.draw(&mut self.mesh, bkg.into(), trans, z);
}
pub fn mesh(&mut self) -> &mut Mesh {
&mut self.mesh
}
pub fn update_rate(&self) -> f64 {
self.update_rate
}
pub fn set_update_rate(&mut self, update_rate: f64) {
self.update_rate = update_rate;
}
pub fn draw_rate(&self) -> f64 {
self.draw_rate
}
pub fn set_draw_rate(&mut self, draw_rate: f64) {
self.draw_rate = draw_rate;
}
pub(crate) fn log_framerate(&mut self, delay: f64) {
if delay > 0.0 {
let total = self.frame_count * self.fps;
self.frame_count += 1.0;
let framerate = 1000.0 / delay;
self.last_framerate = framerate;
self.fps = (total + framerate) / self.frame_count;
}
}
pub fn current_fps(&self) -> f64 {
self.last_framerate
}
pub fn average_fps(&self) -> f64 {
self.fps
}
pub fn max_updates(&self) -> u32 {
self.max_updates
}
pub fn set_max_updates(&mut self, max_updates: u32) {
self.max_updates = max_updates;
}
#[deprecated(since = "0.3.5", note = "please use `set_cursor` instead")]
pub fn set_show_cursor(&mut self, show_cursor: bool) {
if show_cursor {
self.set_cursor(MouseCursor::Default);
} else {
self.set_cursor(MouseCursor::None);
}
}
pub fn set_cursor(&mut self, cursor: MouseCursor) {
self.backend().set_cursor(cursor);
}
pub fn set_title(&mut self, title: &str) {
self.backend().set_title(title);
}
pub fn get_fullscreen(&self) -> bool {
self.fullscreen
}
pub fn set_fullscreen(&mut self, fullscreen: bool) {
self.fullscreen = fullscreen;
let size = self.backend().set_fullscreen(fullscreen);
if let Some(size) = size {
self.adjust_size(size);
}
}
pub fn set_size(&mut self, size: impl Into<Vector>) {
let size = size.into();
self.backend().resize(size);
self.adjust_size(size);
}
pub fn close(&mut self) {
self.running = false;
}
pub fn screenshot(&mut self, format: PixelFormat) -> DynamicImage {
let (size, buffer) = unsafe { self.backend().screenshot(format) };
let width = size.x as u32;
let height = size.y as u32;
let img = match format {
PixelFormat::RGB => DynamicImage::ImageRgb8(RgbImage::from_raw(width, height, buffer).expect("TODO")),
PixelFormat::RGBA => DynamicImage::ImageRgba8(RgbaImage::from_raw(width, height, buffer).expect("TODO")),
};
img.flipv()
}
pub(crate) fn is_running(&self) -> bool {
self.running
}
pub(crate) fn backend(&mut self) -> &'static mut BackendImpl {
unsafe { instance() }
}
}