├── Cargo.png ├── .travis.yml ├── examples ├── window_builder.rs ├── window_settings.rs ├── window_opengl.rs └── keyboard.rs ├── .gitignore ├── Cargo.dot ├── Cargo.toml ├── LICENSE ├── README.md └── src └── lib.rs /Cargo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PistonDevelopers/glutin_window/HEAD/Cargo.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - nightly 5 | - stable 6 | 7 | cache: cargo 8 | 9 | addons: 10 | apt: 11 | packages: 12 | - libxxf86vm-dev 13 | 14 | script: 15 | - cargo build --verbose 16 | - cargo test --verbose 17 | 18 | os: 19 | - linux 20 | - osx 21 | -------------------------------------------------------------------------------- /examples/window_builder.rs: -------------------------------------------------------------------------------- 1 | 2 | extern crate glutin_window; 3 | extern crate window; 4 | 5 | use glutin_window::GlutinWindow; 6 | use window::WindowSettings; 7 | 8 | fn main() { 9 | let _: GlutinWindow = WindowSettings::new("Glutin Window", (640, 480)) 10 | .fullscreen(false) 11 | .vsync(true) 12 | .build() 13 | .unwrap(); 14 | } 15 | -------------------------------------------------------------------------------- /examples/window_settings.rs: -------------------------------------------------------------------------------- 1 | 2 | extern crate glutin_window; 3 | extern crate window; 4 | 5 | use glutin_window::GlutinWindow; 6 | use window::WindowSettings; 7 | 8 | fn main() { 9 | let _ = GlutinWindow::new( 10 | &WindowSettings::new("Glutin Window", (640, 480)) 11 | .fullscreen(false) 12 | .vsync(true) // etc 13 | ).unwrap(); 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | *# 4 | *.o 5 | *.so 6 | *.swp 7 | *.old 8 | *.bak 9 | *.kate-swp 10 | *.dylib 11 | *.dSYM 12 | *.dll 13 | *.rlib 14 | *.dummy 15 | *.exe 16 | *-test 17 | /bin/main 18 | /bin/test-internal 19 | /bin/test-external 20 | /doc/ 21 | /target/ 22 | /build/ 23 | /.rust/ 24 | rusti.sh 25 | watch.sh 26 | /examples/** 27 | !/examples/*.rs 28 | !/examples/assets/ 29 | !/bin/assets/ 30 | 31 | Cargo.lock 32 | -------------------------------------------------------------------------------- /examples/window_opengl.rs: -------------------------------------------------------------------------------- 1 | 2 | extern crate glutin_window; 3 | extern crate shader_version; 4 | extern crate window; 5 | 6 | use glutin_window::GlutinWindow; 7 | use shader_version::OpenGL; 8 | use window::WindowSettings; 9 | 10 | fn main() { 11 | let _ = GlutinWindow::new( 12 | &WindowSettings::new("Glutin Window", (640, 480)) 13 | .fullscreen(false) 14 | .vsync(true) 15 | .graphics_api(OpenGL::V2_1) // etc 16 | ).unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /Cargo.dot: -------------------------------------------------------------------------------- 1 | digraph dependencies { 2 | N0[label="pistoncore-glutin_window"]; 3 | N1[label="gl"]; 4 | N2[label="glutin"]; 5 | N3[label="pistoncore-input"]; 6 | N4[label="pistoncore-window"]; 7 | N5[label="shader_version"]; 8 | N7[label="bitflags"]; 9 | N9[label="cgl"]; 10 | N10[label="gleam"]; 11 | N11[label="libc"]; 12 | N0 -> N1[label=""]; 13 | N0 -> N2[label=""]; 14 | N0 -> N3[label=""]; 15 | N0 -> N4[label=""]; 16 | N0 -> N5[label=""]; 17 | N2 -> N9[label=""]; 18 | N2 -> N11[label=""]; 19 | N3 -> N7[label=""]; 20 | N4 -> N3[label=""]; 21 | N4 -> N5[label=""]; 22 | N9 -> N10[label=""]; 23 | N9 -> N11[label=""]; 24 | } 25 | -------------------------------------------------------------------------------- /examples/keyboard.rs: -------------------------------------------------------------------------------- 1 | 2 | extern crate glutin_window; 3 | extern crate shader_version; 4 | extern crate window; 5 | extern crate piston; 6 | 7 | use glutin_window::GlutinWindow; 8 | use shader_version::OpenGL; 9 | use window::WindowSettings; 10 | 11 | use piston::*; 12 | 13 | fn main() { 14 | let mut window = GlutinWindow::new( 15 | &WindowSettings::new("Glutin Window", (640, 480)) 16 | .fullscreen(false) 17 | .vsync(true) 18 | .graphics_api(OpenGL::V2_1) // etc 19 | ).unwrap(); 20 | 21 | let mut events = Events::new(EventSettings::new()); 22 | while let Some(e) = events.next(&mut window) { 23 | if let Some(button) = e.press_args() { 24 | println!("{:?}", button); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "pistoncore-glutin_window" 4 | version = "0.73.2" 5 | authors = ["bvssvni "] 6 | keywords = ["glutin", "window", "piston"] 7 | description = "A Piston window back-end using the Glutin library" 8 | license = "MIT" 9 | readme = "README.md" 10 | repository = "https://github.com/pistondevelopers/glutin_window.git" 11 | homepage = "https://github.com/pistondevelopers/glutin_window" 12 | 13 | [lib] 14 | name = "glutin_window" 15 | path = "src/lib.rs" 16 | 17 | [dependencies] 18 | gl = "0.14.0" 19 | glutin = "0.32.3" 20 | winit = "0.30.0" 21 | glutin-winit = "0.5.0" 22 | raw-window-handle = "0.6.2" 23 | pistoncore-input = "1.0.0" 24 | pistoncore-window = "1.0.0" 25 | shader_version = "0.7.0" 26 | rustc-hash = "2.1.1" 27 | 28 | [dev-dependencies] 29 | piston = "1.0.0" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 PistonDevelopers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # glutin_window [![Build Status](https://travis-ci.org/PistonDevelopers/glutin_window.svg?branch=master)](https://travis-ci.org/PistonDevelopers/glutin_window) [![Crates.io](https://img.shields.io/crates/v/pistoncore-glutin_window.svg)](https://crates.io/crates/pistoncore-glutin_window) [![Crates.io](https://img.shields.io/crates/l/pistoncore-glutin_window.svg)](https://github.com/PistonDevelopers/glutin_window/blob/master/LICENSE) 2 | 3 | A glutin back-end for the Piston game engine 4 | 5 | [How to contribute](https://github.com/PistonDevelopers/piston/blob/master/CONTRIBUTING.md) 6 | 7 | # Installation 8 | To use this as a dependency, add the following code to your Cargo.toml file: 9 | 10 | ```Rust 11 | [dependencies.pistoncore-glutin_window] 12 | git = "https://github.com/PistonDevelopers/glutin_window" 13 | ``` 14 | 15 | ## How to create a window 16 | 17 | ```Rust 18 | let mut window: GlutinWindow = WindowSettings::new("Glutin Window", (640, 480)) 19 | .fullscreen(false) 20 | .vsync(true) 21 | .build() 22 | .unwrap(); 23 | ``` 24 | 25 | See the examples for more ways to create a window. 26 | 27 | ## Dependencies 28 | 29 | ![dependencies](./Cargo.png) 30 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | //! A Glutin window back-end for the Piston game engine. 4 | 5 | extern crate glutin; 6 | extern crate gl; 7 | extern crate glutin_winit; 8 | extern crate input; 9 | extern crate raw_window_handle; 10 | extern crate window; 11 | extern crate winit; 12 | extern crate shader_version; 13 | extern crate rustc_hash; 14 | 15 | use rustc_hash::FxHashMap; 16 | 17 | use std::collections::VecDeque; 18 | use std::error::Error; 19 | 20 | // External crates. 21 | use input::{ 22 | ButtonArgs, 23 | ButtonState, 24 | CloseArgs, 25 | Event, 26 | Key, 27 | Motion, 28 | MouseButton, 29 | Button, 30 | Input, 31 | ResizeArgs, 32 | }; 33 | use window::{ 34 | BuildFromWindowSettings, 35 | OpenGLWindow, 36 | Window, 37 | AdvancedWindow, 38 | ProcAddress, 39 | WindowSettings, 40 | Size, 41 | Position, 42 | Api, 43 | UnsupportedGraphicsApiError, 44 | }; 45 | use winit::{ 46 | application::ApplicationHandler, 47 | dpi::{LogicalPosition, LogicalSize}, 48 | event_loop::{ActiveEventLoop, EventLoop}, 49 | event::{DeviceId, ElementState, MouseScrollDelta, WindowEvent}, 50 | window::WindowId, 51 | }; 52 | use glutin::context::PossiblyCurrentGlContext; 53 | use glutin::display::GlDisplay; 54 | use glutin::prelude::GlSurface; 55 | use std::time::Duration; 56 | use std::sync::Arc; 57 | 58 | pub use shader_version::OpenGL; 59 | 60 | 61 | /// Settings for whether to ignore modifiers and use standard keyboard layouts instead. 62 | /// 63 | /// This does not affect `piston::input::TextEvent`. 64 | /// 65 | /// Piston uses the same key codes as in SDL2. 66 | /// The problem is that without knowing the keyboard layout, 67 | /// there is no coherent way of generating key codes. 68 | /// 69 | /// This option choose different tradeoffs depending on need. 70 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 71 | pub enum KeyboardIgnoreModifiers { 72 | /// Keep the key codes that are affected by modifiers. 73 | /// 74 | /// This is a good default for most applications. 75 | /// However, make sure to display understandable information to the user. 76 | /// 77 | /// If you experience user problems among gamers, 78 | /// then you might consider allowing other options in your game engine. 79 | /// Some gamers might be used to how stuff works in other traditional game engines 80 | /// and struggle understanding this configuration, depending on how you use keyboard layout. 81 | None, 82 | /// Assume the user's keyboard layout is standard English ABC. 83 | /// 84 | /// In some non-English speaking countries, this might be more user friendly for some gamers. 85 | /// 86 | /// This might sound counter-intuitive at first, so here is the reason: 87 | /// 88 | /// Gamers can customize their keyboard layout without needing to understand scan codes. 89 | /// When gamers want physically accuracy with good default options, 90 | /// they can simply use standard English ABC. 91 | /// 92 | /// In other cases, this option displays understandable information for game instructions. 93 | /// This information makes it easier for users to correct the problem themselves. 94 | /// 95 | /// Most gaming consoles use standard controllers. 96 | /// Typically, the only device that might be problematic for users is the keyboard. 97 | /// Instead of solving this problem in your game engine, let users do it in the OS. 98 | /// 99 | /// This option gives more control to users and is also better for user data privacy. 100 | /// Detecting keyboard layout is usually not needed. 101 | /// Instead, provide options for the user where they can modify the keys. 102 | /// If users want to switch layout in the middle of a game, they can do it through the OS. 103 | AbcKeyCode, 104 | } 105 | 106 | /// Contains stuff for game window. 107 | pub struct GlutinWindow { 108 | /// The OpenGL context. 109 | pub ctx: Option, 110 | /// The window surface. 111 | pub surface: Option>, 112 | /// The graphics display. 113 | pub display: Option, 114 | /// The event loop of the window. 115 | /// 116 | /// This is optional because when pumping events using `ApplicationHandler`, 117 | /// the event loop can not be owned by `WinitWindow`. 118 | pub event_loop: Option>, 119 | /// Sets keyboard layout. 120 | /// 121 | /// When set, the key codes are 122 | pub keyboard_ignore_modifiers: KeyboardIgnoreModifiers, 123 | /// The Winit window. 124 | /// 125 | /// This is optional because when creating the window, 126 | /// it is only accessible by `ActiveEventLoop::create_window`, 127 | /// which in turn requires `ApplicationHandler`. 128 | /// One call to `Window::pull_event` is needed to trigger 129 | /// Winit to call `ApplicationHandler::request_redraw`, 130 | /// which creates the window. 131 | pub window: Option>, 132 | /// Keeps track of connected devices. 133 | pub devices: u32, 134 | /// Maps device id to a unique id used by Piston. 135 | pub device_id_map: FxHashMap, 136 | // The window settings that created the window. 137 | settings: WindowSettings, 138 | // The back-end does not remember the title. 139 | title: String, 140 | exit_on_esc: bool, 141 | should_close: bool, 142 | automatic_close: bool, 143 | // Used to fake capturing of cursor, 144 | // to get relative mouse events. 145 | is_capturing_cursor: bool, 146 | // Stores the last known cursor position. 147 | last_cursor_pos: Option<[f64; 2]>, 148 | // Stores relative coordinates to emit on next poll. 149 | mouse_relative: Option<(f64, f64)>, 150 | // Used to emit cursor event after enter/leave. 151 | cursor_pos: Option<[f64; 2]>, 152 | // Used to filter repeated key presses (does not affect text repeat). 153 | last_key_pressed: Option, 154 | // Stores list of events ready for processing. 155 | events: VecDeque, 156 | } 157 | 158 | fn graphics_api_from_settings(settings: &WindowSettings) -> Result> { 159 | let api = settings.get_maybe_graphics_api().unwrap_or(Api::opengl(3, 2)); 160 | if api.api != "OpenGL" { 161 | return Err(UnsupportedGraphicsApiError { 162 | found: api.api, 163 | expected: vec!["OpenGL".into()] 164 | }.into()); 165 | }; 166 | Ok(api) 167 | } 168 | 169 | fn surface_attributes_builder_from_settings( 170 | settings: &WindowSettings 171 | ) -> glutin::surface::SurfaceAttributesBuilder { 172 | glutin::surface::SurfaceAttributesBuilder::::new() 173 | .with_srgb(Some(settings.get_srgb())) 174 | } 175 | 176 | fn config_template_builder_from_settings( 177 | settings: &WindowSettings 178 | ) -> glutin::config::ConfigTemplateBuilder { 179 | let x = glutin::config::ConfigTemplateBuilder::new() 180 | .with_transparency(settings.get_transparent()); 181 | let samples = settings.get_samples(); 182 | if samples == 0 {x} else { 183 | x.with_multisampling(samples) 184 | } 185 | } 186 | 187 | impl GlutinWindow { 188 | 189 | /// Creates a new game window for Glutin. 190 | pub fn new(settings: &WindowSettings) -> Result> { 191 | let event_loop = winit::event_loop::EventLoop::with_user_event().build()?; 192 | Self::from_event_loop(settings, event_loop) 193 | } 194 | 195 | /// Creates a game window from a pre-existing Glutin event loop. 196 | pub fn from_event_loop( 197 | settings: &WindowSettings, 198 | event_loop: winit::event_loop::EventLoop, 199 | ) -> Result> { 200 | let title = settings.get_title(); 201 | let exit_on_esc = settings.get_exit_on_esc(); 202 | 203 | let mut w = GlutinWindow { 204 | ctx: None, 205 | display: None, 206 | surface: None, 207 | window: None, 208 | title, 209 | exit_on_esc, 210 | settings: settings.clone(), 211 | should_close: false, 212 | automatic_close: settings.get_automatic_close(), 213 | cursor_pos: None, 214 | is_capturing_cursor: false, 215 | last_cursor_pos: None, 216 | mouse_relative: None, 217 | last_key_pressed: None, 218 | event_loop: Some(event_loop), 219 | keyboard_ignore_modifiers: KeyboardIgnoreModifiers::None, 220 | events: VecDeque::new(), 221 | 222 | devices: 0, 223 | device_id_map: FxHashMap::default(), 224 | }; 225 | // Causes the window to be created through `ApplicationHandler::request_redraw`. 226 | if let Some(e) = w.poll_event() {w.events.push_front(e)} 227 | Ok(w) 228 | } 229 | 230 | /// Gets a reference to the window. 231 | /// 232 | /// This is faster than [get_window], but borrows self. 233 | pub fn get_window_ref(&self) -> &winit::window::Window { 234 | self.window.as_ref().unwrap() 235 | } 236 | 237 | /// Returns a cloned smart pointer to the underlying Winit window. 238 | pub fn get_window(&self) -> Arc { 239 | self.window.as_ref().unwrap().clone() 240 | } 241 | 242 | // These events are emitted before popping a new event from the queue. 243 | // This is because Piston handles some events separately. 244 | fn pre_pop_front_event(&mut self) -> Option { 245 | use input::Motion; 246 | 247 | // Check for a pending mouse cursor move event. 248 | if let Some(pos) = self.cursor_pos { 249 | self.cursor_pos = None; 250 | return Some(Input::Move(Motion::MouseCursor(pos))); 251 | } 252 | 253 | // Check for a pending relative mouse move event. 254 | if let Some((x, y)) = self.mouse_relative { 255 | self.mouse_relative = None; 256 | return Some(Input::Move(Motion::MouseRelative([x, y]))); 257 | } 258 | 259 | None 260 | } 261 | 262 | /// Convert an incoming Winit event to Piston input. 263 | /// Update cursor state if necessary. 264 | /// 265 | /// The `unknown` flag is set to `true` when the event is not recognized. 266 | /// This is used to poll another event to make the event loop logic sound. 267 | /// When `unknown` is `true`, the return value is `None`. 268 | fn handle_event( 269 | &mut self, 270 | event: winit::event::WindowEvent, 271 | unknown: &mut bool, 272 | ) -> Option { 273 | use winit::keyboard::{Key, NamedKey}; 274 | 275 | match event { 276 | WindowEvent::KeyboardInput { event: ref ev, .. } => { 277 | if self.exit_on_esc { 278 | if let Key::Named(NamedKey::Escape) = ev.logical_key { 279 | self.set_should_close(true); 280 | return None; 281 | } 282 | } 283 | if let Some(s) = &ev.text { 284 | let s = s.to_string(); 285 | let repeat = ev.repeat; 286 | if !repeat { 287 | if let Some(input) = map_window_event( 288 | event, 289 | self.get_window_ref().scale_factor(), 290 | self.keyboard_ignore_modifiers, 291 | unknown, 292 | &mut self.last_key_pressed, 293 | &mut self.devices, 294 | &mut self.device_id_map, 295 | ) { 296 | self.events.push_back(Event::Input(input, None)); 297 | } 298 | } 299 | 300 | return Some(Input::Text(s)); 301 | } 302 | } 303 | WindowEvent::CursorMoved { position, .. } => { 304 | let scale = self.get_window_ref().scale_factor(); 305 | let position = position.to_logical::(scale); 306 | let x = f64::from(position.x); 307 | let y = f64::from(position.y); 308 | 309 | let pre_event = self.pre_pop_front_event(); 310 | let mut input = || { 311 | if let Some(pos) = self.last_cursor_pos { 312 | let dx = x - pos[0]; 313 | let dy = y - pos[1]; 314 | if self.is_capturing_cursor { 315 | self.last_cursor_pos = Some([x, y]); 316 | self.fake_capture(); 317 | // Skip normal mouse movement and emit relative motion only. 318 | return Some(Input::Move(Motion::MouseRelative([dx as f64, dy as f64]))); 319 | } 320 | // Send relative mouse movement next time. 321 | self.mouse_relative = Some((dx as f64, dy as f64)); 322 | } else if self.is_capturing_cursor { 323 | // Ignore this event since mouse positions 324 | // should not be emitted when capturing cursor. 325 | self.last_cursor_pos = Some([x, y]); 326 | return None; 327 | } 328 | 329 | self.last_cursor_pos = Some([x, y]); 330 | return Some(Input::Move(Motion::MouseCursor([x, y]))) 331 | }; 332 | 333 | let input = input(); 334 | return if pre_event.is_some() { 335 | if let Some(input) = input { 336 | self.events.push_back(Event::Input(input, None)); 337 | } 338 | pre_event 339 | } else {input} 340 | } 341 | _ => {} 342 | } 343 | 344 | // Usual events are handled here and passed to user. 345 | let input = map_window_event( 346 | event, 347 | self.get_window_ref().scale_factor(), 348 | self.keyboard_ignore_modifiers, 349 | unknown, 350 | &mut self.last_key_pressed, 351 | &mut self.devices, 352 | &mut self.device_id_map, 353 | ); 354 | 355 | let pre_event = self.pre_pop_front_event(); 356 | if pre_event.is_some() { 357 | if let Some(input) = input { 358 | self.events.push_back(Event::Input(input, None)); 359 | } 360 | pre_event 361 | } else {input} 362 | } 363 | 364 | fn fake_capture(&mut self) { 365 | if let Some(pos) = self.last_cursor_pos { 366 | // Fake capturing of cursor. 367 | let size = self.size(); 368 | let cx = size.width / 2.0; 369 | let cy = size.height / 2.0; 370 | let dx = cx - pos[0]; 371 | let dy = cy - pos[1]; 372 | if dx != 0.0 || dy != 0.0 { 373 | let pos = winit::dpi::LogicalPosition::new(cx, cy); 374 | if let Ok(_) = self.get_window_ref().set_cursor_position(pos) { 375 | self.last_cursor_pos = Some([cx, cy]); 376 | } 377 | } 378 | } 379 | } 380 | } 381 | 382 | impl ApplicationHandler for GlutinWindow { 383 | fn resumed(&mut self, event_loop: &ActiveEventLoop) { 384 | use glutin::display::GetGlDisplay; 385 | use glutin::config::GlConfig; 386 | use glutin::context::ContextApi; 387 | use glutin::context::NotCurrentGlContext; 388 | use raw_window_handle::HasRawWindowHandle; 389 | use std::num::NonZeroU32; 390 | 391 | let settings = &self.settings; 392 | 393 | let template = config_template_builder_from_settings(settings); 394 | let display_builder = glutin_winit::DisplayBuilder::new(); 395 | let (_, gl_config) = display_builder 396 | .build(event_loop, template, |configs| { 397 | configs.reduce(|accum, config| { 398 | let transparency_check = config.supports_transparency().unwrap_or(false) 399 | & !accum.supports_transparency().unwrap_or(false); 400 | 401 | if transparency_check || config.num_samples() > accum.num_samples() { 402 | config 403 | } else { 404 | accum 405 | } 406 | }) 407 | .unwrap() 408 | }).unwrap(); 409 | 410 | let window = event_loop.create_window(winit::window::Window::default_attributes() 411 | .with_inner_size(LogicalSize::::new( 412 | settings.get_size().width.into(), 413 | settings.get_size().height.into(), 414 | )) 415 | .with_title(settings.get_title()) 416 | ).unwrap(); 417 | 418 | let raw_window_handle = window.raw_window_handle().unwrap(); 419 | let draw_size = window.inner_size(); 420 | let dw = NonZeroU32::new(draw_size.width).unwrap(); 421 | let dh = NonZeroU32::new(draw_size.height).unwrap(); 422 | let surface_attributes = surface_attributes_builder_from_settings(settings) 423 | .build(raw_window_handle, dw, dh); 424 | 425 | let display: glutin::display::Display = gl_config.display(); 426 | let surface = unsafe {display.create_window_surface(&gl_config, &surface_attributes).unwrap()}; 427 | 428 | let api = graphics_api_from_settings(settings).unwrap(); 429 | let context_attributes = glutin::context::ContextAttributesBuilder::new() 430 | .with_context_api(glutin::context::ContextApi::OpenGl(Some(glutin::context::Version::new(api.major as u8, api.minor as u8)))) 431 | .build(Some(raw_window_handle)); 432 | 433 | let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new() 434 | .with_context_api(ContextApi::Gles(None)) 435 | .build(Some(raw_window_handle)); 436 | 437 | let legacy_context_attributes = glutin::context::ContextAttributesBuilder::new() 438 | .with_context_api(glutin::context::ContextApi::OpenGl(Some(glutin::context::Version::new(2, 1)))) 439 | .build(Some(raw_window_handle)); 440 | 441 | let mut not_current_gl_context = Some(unsafe { 442 | if let Ok(x) = display.create_context(&gl_config, &context_attributes) {x} 443 | else if let Ok(x) = display.create_context(&gl_config, &fallback_context_attributes) {x} 444 | else { 445 | display.create_context(&gl_config, &legacy_context_attributes).unwrap() 446 | } 447 | }); 448 | 449 | let ctx: glutin::context::PossiblyCurrentContext = not_current_gl_context.take().unwrap() 450 | .make_current(&surface).unwrap(); 451 | 452 | if settings.get_vsync() { 453 | surface.set_swap_interval(&ctx, 454 | glutin::surface::SwapInterval::Wait(NonZeroU32::new(1).unwrap())).unwrap(); 455 | } 456 | 457 | // Load the OpenGL function pointers. 458 | gl::load_with(|s| { 459 | use std::ffi::CString; 460 | 461 | let s = CString::new(s).expect("CString::new failed"); 462 | display.get_proc_address(&s) as *const _ 463 | }); 464 | 465 | self.ctx = Some(ctx); 466 | self.surface = Some(surface); 467 | self.display = Some(display); 468 | self.window = Some(Arc::new(window)); 469 | } 470 | 471 | fn window_event( 472 | &mut self, 473 | event_loop: &ActiveEventLoop, 474 | _window_id: WindowId, 475 | event: WindowEvent, 476 | ) { 477 | let window = &self.get_window_ref(); 478 | 479 | match event { 480 | WindowEvent::CloseRequested => { 481 | if self.automatic_close { 482 | self.should_close = true; 483 | event_loop.exit(); 484 | } 485 | } 486 | WindowEvent::RedrawRequested => { 487 | window.request_redraw(); 488 | }, 489 | event => { 490 | let mut unknown = false; 491 | if let Some(ev) = self.handle_event(event, &mut unknown) { 492 | if !unknown { 493 | self.events.push_back(Event::Input(ev, None)); 494 | } 495 | } 496 | } 497 | } 498 | } 499 | } 500 | 501 | impl Window for GlutinWindow { 502 | fn size(&self) -> Size { 503 | let window = self.get_window_ref(); 504 | let (w, h): (u32, u32) = window.inner_size().into(); 505 | let hidpi = window.scale_factor(); 506 | ((w as f64 / hidpi) as u32, (h as f64 / hidpi) as u32).into() 507 | } 508 | 509 | fn should_close(&self) -> bool { self.should_close } 510 | 511 | fn set_should_close(&mut self, value: bool) { self.should_close = value; } 512 | 513 | fn swap_buffers(&mut self) { 514 | if let (Some(ctx), Some(surface)) = (&self.ctx, &self.surface) { 515 | let _ = surface.swap_buffers(ctx); 516 | } 517 | } 518 | 519 | fn wait_event(&mut self) -> Event { 520 | use winit::platform::pump_events::EventLoopExtPumpEvents; 521 | use input::{IdleArgs, Loop}; 522 | 523 | // Add all events we got to the event queue, since winit only allows us to get all pending 524 | // events at once. 525 | if let Some(mut event_loop) = std::mem::replace(&mut self.event_loop, None) { 526 | let event_loop_proxy = event_loop.create_proxy(); 527 | event_loop_proxy 528 | .send_event(UserEvent::WakeUp) 529 | .expect("Event loop is closed before property handling all events."); 530 | event_loop.pump_app_events(None, self); 531 | self.event_loop = Some(event_loop); 532 | } 533 | 534 | // Get the first event in the queue 535 | let event = self.events.pop_front(); 536 | 537 | // Check if we got a close event, if we did we need to mark ourselves as should-close 538 | if let &Some(Event::Input(Input::Close(_), ..)) = &event { 539 | self.set_should_close(true); 540 | } 541 | 542 | event.unwrap_or(Event::Loop(Loop::Idle(IdleArgs {dt: 0.0}))) 543 | } 544 | fn wait_event_timeout(&mut self, timeout: Duration) -> Option { 545 | use winit::platform::pump_events::EventLoopExtPumpEvents; 546 | 547 | // Add all events we got to the event queue, since winit only allows us to get all pending 548 | // events at once. 549 | if let Some(mut event_loop) = std::mem::replace(&mut self.event_loop, None) { 550 | let event_loop_proxy = event_loop.create_proxy(); 551 | event_loop_proxy 552 | .send_event(UserEvent::WakeUp) 553 | .expect("Event loop is closed before property handling all events."); 554 | event_loop.pump_app_events(Some(timeout), self); 555 | self.event_loop = Some(event_loop); 556 | } 557 | 558 | // Get the first event in the queue 559 | let event = self.events.pop_front(); 560 | 561 | // Check if we got a close event, if we did we need to mark ourselves as should-close 562 | if let &Some(Event::Input(Input::Close(_), ..)) = &event { 563 | self.set_should_close(true); 564 | } 565 | 566 | event 567 | } 568 | fn poll_event(&mut self) -> Option { 569 | use winit::platform::pump_events::EventLoopExtPumpEvents; 570 | 571 | // Add all events we got to the event queue, since winit only allows us to get all pending 572 | // events at once. 573 | if let Some(mut event_loop) = std::mem::replace(&mut self.event_loop, None) { 574 | let event_loop_proxy = event_loop.create_proxy(); 575 | event_loop_proxy 576 | .send_event(UserEvent::WakeUp) 577 | .expect("Event loop is closed before property handling all events."); 578 | event_loop.pump_app_events(Some(Duration::ZERO), self); 579 | self.event_loop = Some(event_loop); 580 | } 581 | 582 | // Get the first event in the queue 583 | let event = self.events.pop_front(); 584 | 585 | // Check if we got a close event, if we did we need to mark ourselves as should-close 586 | if let &Some(Event::Input(Input::Close(_), ..)) = &event { 587 | self.set_should_close(true); 588 | } 589 | 590 | event 591 | } 592 | 593 | fn draw_size(&self) -> Size { 594 | let size: (f64, f64) = self.get_window_ref().inner_size().into(); 595 | size.into() 596 | } 597 | } 598 | 599 | impl BuildFromWindowSettings for GlutinWindow { 600 | fn build_from_window_settings(settings: &WindowSettings) 601 | -> Result> { 602 | GlutinWindow::new(settings) 603 | } 604 | } 605 | 606 | impl AdvancedWindow for GlutinWindow { 607 | fn get_title(&self) -> String { 608 | self.title.clone() 609 | } 610 | 611 | fn set_title(&mut self, value: String) { 612 | self.get_window_ref().set_title(&value); 613 | self.title = value; 614 | } 615 | 616 | fn get_exit_on_esc(&self) -> bool { 617 | self.exit_on_esc 618 | } 619 | 620 | fn set_exit_on_esc(&mut self, value: bool) { 621 | self.exit_on_esc = value 622 | } 623 | 624 | fn set_capture_cursor(&mut self, value: bool) { 625 | // Normally we would call `.set_cursor_grab` 626 | // but since relative mouse events does not work, 627 | // because device deltas have unspecified coordinates, 628 | // the capturing of cursor is faked by hiding the cursor 629 | // and setting the position to the center of window. 630 | self.is_capturing_cursor = value; 631 | self.get_window_ref().set_cursor_visible(!value); 632 | if value { 633 | self.fake_capture(); 634 | } 635 | } 636 | 637 | fn get_automatic_close(&self) -> bool {self.automatic_close} 638 | 639 | fn set_automatic_close(&mut self, value: bool) {self.automatic_close = value} 640 | 641 | fn show(&mut self) { 642 | self.get_window_ref().set_visible(true); 643 | } 644 | 645 | fn hide(&mut self) { 646 | self.get_window_ref().set_visible(false); 647 | } 648 | 649 | fn get_position(&self) -> Option { 650 | self.get_window_ref() 651 | .outer_position() 652 | .map(|p| Position { x: p.x, y: p.y }) 653 | .ok() 654 | } 655 | 656 | fn set_position>(&mut self, val: P) { 657 | let val = val.into(); 658 | self.get_window_ref() 659 | .set_outer_position(LogicalPosition::new(val.x as f64, val.y as f64)) 660 | } 661 | 662 | fn set_size>(&mut self, size: S) { 663 | let size: Size = size.into(); 664 | let w = self.get_window_ref(); 665 | let _ = w.request_inner_size(LogicalSize::new( 666 | size.width as f64, 667 | size.height as f64, 668 | )); 669 | } 670 | } 671 | 672 | impl OpenGLWindow for GlutinWindow { 673 | fn get_proc_address(&mut self, proc_name: &str) -> ProcAddress { 674 | use std::ffi::CString; 675 | 676 | let s = CString::new(proc_name).expect("CString::new failed"); 677 | self.display.as_ref().expect("No display").get_proc_address(&s) as *const _ 678 | } 679 | 680 | fn is_current(&self) -> bool { 681 | if let Some(ctx) = &self.ctx { 682 | ctx.is_current() 683 | } else {false} 684 | } 685 | 686 | fn make_current(&mut self) { 687 | if let (Some(ctx), Some(surface)) = (&self.ctx, &self.surface) { 688 | let _ = ctx.make_current(surface); 689 | } 690 | } 691 | } 692 | 693 | fn map_key(input: &winit::event::KeyEvent, kim: KeyboardIgnoreModifiers) -> Key { 694 | use winit::keyboard::NamedKey::*; 695 | use winit::keyboard::Key::*; 696 | use KeyboardIgnoreModifiers as KIM; 697 | 698 | match input.logical_key { 699 | Character(ref ch) => match ch.as_str() { 700 | "0" | ")" if kim == KIM::AbcKeyCode => Key::D0, 701 | "0" => Key::D0, 702 | ")" => Key::RightParen, 703 | "1" | "!" if kim == KIM::AbcKeyCode => Key::D1, 704 | "1" => Key::D1, 705 | "!" => Key::NumPadExclam, 706 | "2" | "@" if kim == KIM::AbcKeyCode => Key::D2, 707 | "2" => Key::D2, 708 | "@" => Key::At, 709 | "3" | "#" if kim == KIM::AbcKeyCode => Key::D3, 710 | "3" => Key::D3, 711 | "#" => Key::Hash, 712 | "4" | "$" if kim == KIM::AbcKeyCode => Key::D4, 713 | "4" => Key::D4, 714 | "$" => Key::Dollar, 715 | "5" | "%" if kim == KIM::AbcKeyCode => Key::D5, 716 | "5" => Key::D5, 717 | "%" => Key::Percent, 718 | "6" | "^" if kim == KIM::AbcKeyCode => Key::D6, 719 | "6" => Key::D6, 720 | "^" => Key::Caret, 721 | "7" | "&" if kim == KIM::AbcKeyCode => Key::D7, 722 | "7" => Key::D7, 723 | "&" => Key::Ampersand, 724 | "8" | "*" if kim == KIM::AbcKeyCode => Key::D8, 725 | "8" => Key::D8, 726 | "*" => Key::Asterisk, 727 | "9" | "(" if kim == KIM::AbcKeyCode => Key::D9, 728 | "9" => Key::D9, 729 | "(" => Key::LeftParen, 730 | "a" | "A" => Key::A, 731 | "b" | "B" => Key::B, 732 | "c" | "C" => Key::C, 733 | "d" | "D" => Key::D, 734 | "e" | "E" => Key::E, 735 | "f" | "F" => Key::F, 736 | "g" | "G" => Key::G, 737 | "h" | "H" => Key::H, 738 | "i" | "I" => Key::I, 739 | "j" | "J" => Key::J, 740 | "k" | "K" => Key::K, 741 | "l" | "L" => Key::L, 742 | "m" | "M" => Key::M, 743 | "n" | "N" => Key::N, 744 | "o" | "O" => Key::O, 745 | "p" | "P" => Key::P, 746 | "q" | "Q" => Key::Q, 747 | "r" | "R" => Key::R, 748 | "s" | "S" => Key::S, 749 | "t" | "T" => Key::T, 750 | "u" | "U" => Key::U, 751 | "v" | "V" => Key::V, 752 | "w" | "W" => Key::W, 753 | "x" | "X" => Key::X, 754 | "y" | "Y" => Key::Y, 755 | "z" | "Z" => Key::Z, 756 | "'" | "\"" if kim == KIM::AbcKeyCode => Key::Quote, 757 | "'" => Key::Quote, 758 | "\"" => Key::Quotedbl, 759 | ";" | ":" if kim == KIM::AbcKeyCode => Key::Semicolon, 760 | ";" => Key::Semicolon, 761 | ":" => Key::Colon, 762 | "[" | "{" if kim == KIM::AbcKeyCode => Key::LeftBracket, 763 | "[" => Key::LeftBracket, 764 | "{" => Key::NumPadLeftBrace, 765 | "]" | "}" if kim == KIM::AbcKeyCode => Key::RightBracket, 766 | "]" => Key::RightBracket, 767 | "}" => Key::NumPadRightBrace, 768 | "\\" | "|" if kim == KIM::AbcKeyCode => Key::Backslash, 769 | "\\" => Key::Backslash, 770 | "|" => Key::NumPadVerticalBar, 771 | "," | "<" if kim == KIM::AbcKeyCode => Key::Comma, 772 | "," => Key::Comma, 773 | "<" => Key::Less, 774 | "." | ">" if kim == KIM::AbcKeyCode => Key::Period, 775 | "." => Key::Period, 776 | ">" => Key::Greater, 777 | "/" | "?" if kim == KIM::AbcKeyCode => Key::Slash, 778 | "/" => Key::Slash, 779 | "?" => Key::Question, 780 | "`" | "~" if kim == KIM::AbcKeyCode => Key::Backquote, 781 | "`" => Key::Backquote, 782 | // Piston v1.0 does not support `~` using modifier. 783 | // Use `KeyboardIgnoreModifiers::AbcKeyCode` on window to fix this issue. 784 | // It will be mapped to `Key::Backquote`. 785 | "~" => Key::Unknown, 786 | _ => Key::Unknown, 787 | } 788 | Named(Escape) => Key::Escape, 789 | Named(F1) => Key::F1, 790 | Named(F2) => Key::F2, 791 | Named(F3) => Key::F3, 792 | Named(F4) => Key::F4, 793 | Named(F5) => Key::F5, 794 | Named(F6) => Key::F6, 795 | Named(F7) => Key::F7, 796 | Named(F8) => Key::F8, 797 | Named(F9) => Key::F9, 798 | Named(F10) => Key::F10, 799 | Named(F11) => Key::F11, 800 | Named(F12) => Key::F12, 801 | Named(F13) => Key::F13, 802 | Named(F14) => Key::F14, 803 | Named(F15) => Key::F15, 804 | 805 | Named(Delete) => Key::Delete, 806 | 807 | Named(ArrowLeft) => Key::Left, 808 | Named(ArrowUp) => Key::Up, 809 | Named(ArrowRight) => Key::Right, 810 | Named(ArrowDown) => Key::Down, 811 | 812 | Named(Backspace) => Key::Backspace, 813 | Named(Enter) => Key::Return, 814 | Named(Space) => Key::Space, 815 | 816 | Named(Alt) => Key::LAlt, 817 | Named(AltGraph) => Key::RAlt, 818 | Named(Control) => Key::LCtrl, 819 | Named(Super) => Key::Menu, 820 | Named(Shift) => Key::LShift, 821 | 822 | Named(Tab) => Key::Tab, 823 | _ => Key::Unknown, 824 | } 825 | } 826 | 827 | fn map_keyboard_input( 828 | input: &winit::event::KeyEvent, 829 | kim: KeyboardIgnoreModifiers, 830 | unknown: &mut bool, 831 | last_key_pressed: &mut Option, 832 | ) -> Option { 833 | let key = map_key(input, kim); 834 | 835 | let state = if input.state == ElementState::Pressed { 836 | // Filter repeated key presses (does not affect text repeat when holding keys). 837 | if let Some(last_key) = &*last_key_pressed { 838 | if last_key == &key { 839 | *unknown = true; 840 | return None; 841 | } 842 | } 843 | *last_key_pressed = Some(key); 844 | 845 | ButtonState::Press 846 | } else { 847 | if let Some(last_key) = &*last_key_pressed { 848 | if last_key == &key { 849 | *last_key_pressed = None; 850 | } 851 | } 852 | ButtonState::Release 853 | }; 854 | 855 | Some(Input::Button(ButtonArgs { 856 | state: state, 857 | button: Button::Keyboard(key), 858 | scancode: if let winit::keyboard::PhysicalKey::Code(code) = input.physical_key { 859 | Some(code as i32) 860 | } else {None}, 861 | })) 862 | } 863 | 864 | /// Maps Glutin's mouse button to Piston's mouse button. 865 | pub fn map_mouse(mouse_button: winit::event::MouseButton) -> MouseButton { 866 | use winit::event::MouseButton as M; 867 | 868 | match mouse_button { 869 | M::Left => MouseButton::Left, 870 | M::Right => MouseButton::Right, 871 | M::Middle => MouseButton::Middle, 872 | M::Other(0) => MouseButton::X1, 873 | M::Other(1) => MouseButton::X2, 874 | M::Other(2) => MouseButton::Button6, 875 | M::Other(3) => MouseButton::Button7, 876 | M::Other(4) => MouseButton::Button8, 877 | _ => MouseButton::Unknown 878 | } 879 | } 880 | 881 | /// Converts a winit's [`WindowEvent`] into a piston's [`Input`]. 882 | /// 883 | /// For some events that will not be passed to the user, returns `None`. 884 | fn map_window_event( 885 | window_event: WindowEvent, 886 | scale_factor: f64, 887 | kim: KeyboardIgnoreModifiers, 888 | unknown: &mut bool, 889 | last_key_pressed: &mut Option, 890 | devices: &mut u32, 891 | device_id_map: &mut FxHashMap, 892 | ) -> Option { 893 | use input::FileDrag; 894 | 895 | match window_event { 896 | WindowEvent::DroppedFile(path) => 897 | Some(Input::FileDrag(FileDrag::Drop(path))), 898 | WindowEvent::HoveredFile(path) => 899 | Some(Input::FileDrag(FileDrag::Hover(path))), 900 | WindowEvent::HoveredFileCancelled => 901 | Some(Input::FileDrag(FileDrag::Cancel)), 902 | WindowEvent::Resized(size) => Some(Input::Resize(ResizeArgs { 903 | window_size: [size.width as f64, size.height as f64], 904 | draw_size: Size { 905 | width: size.width as f64, 906 | height: size.height as f64, 907 | } 908 | .into(), 909 | })), 910 | WindowEvent::CloseRequested => Some(Input::Close(CloseArgs)), 911 | WindowEvent::Destroyed => Some(Input::Close(CloseArgs)), 912 | WindowEvent::Focused(focused) => Some(Input::Focus(focused)), 913 | WindowEvent::KeyboardInput { ref event, .. } => { 914 | map_keyboard_input(event, kim, unknown, last_key_pressed) 915 | } 916 | WindowEvent::CursorMoved { position, .. } => { 917 | let position = position.to_logical(scale_factor); 918 | Some(Input::Move(Motion::MouseCursor([position.x, position.y]))) 919 | } 920 | WindowEvent::CursorEntered { .. } => Some(Input::Cursor(true)), 921 | WindowEvent::CursorLeft { .. } => Some(Input::Cursor(false)), 922 | WindowEvent::MouseWheel { delta, .. } => match delta { 923 | MouseScrollDelta::PixelDelta(position) => { 924 | let position = position.to_logical(scale_factor); 925 | Some(Input::Move(Motion::MouseScroll([position.x, position.y]))) 926 | } 927 | MouseScrollDelta::LineDelta(x, y) => 928 | Some(Input::Move(Motion::MouseScroll([x as f64, y as f64]))), 929 | }, 930 | WindowEvent::MouseInput { state, button, .. } => { 931 | let button = map_mouse(button); 932 | let state = match state { 933 | ElementState::Pressed => ButtonState::Press, 934 | ElementState::Released => ButtonState::Release, 935 | }; 936 | 937 | Some(Input::Button(ButtonArgs { 938 | state, 939 | button: Button::Mouse(button), 940 | scancode: None, 941 | })) 942 | } 943 | WindowEvent::AxisMotion { device_id, axis, value } => { 944 | use input::ControllerAxisArgs; 945 | 946 | Some(Input::Move(Motion::ControllerAxis(ControllerAxisArgs::new( 947 | { 948 | if let Some(id) = device_id_map.get(&device_id) {*id} 949 | else { 950 | let id = *devices; 951 | *devices += 1; 952 | device_id_map.insert(device_id, id); 953 | id 954 | } 955 | }, 956 | axis as u8, 957 | value, 958 | )))) 959 | } 960 | WindowEvent::Touch(winit::event::Touch { phase, location, id, .. }) => { 961 | use winit::event::TouchPhase; 962 | use input::{Touch, TouchArgs}; 963 | 964 | let location = location.to_logical::(scale_factor); 965 | 966 | Some(Input::Move(Motion::Touch(TouchArgs::new( 967 | 0, id as i64, [location.x, location.y], 1.0, match phase { 968 | TouchPhase::Started => Touch::Start, 969 | TouchPhase::Moved => Touch::Move, 970 | TouchPhase::Ended => Touch::End, 971 | TouchPhase::Cancelled => Touch::Cancel 972 | } 973 | )))) 974 | } 975 | // Events not built-in by Piston v1.0. 976 | // It is possible to use Piston's `Event::Custom`. 977 | // This might be added as a library in the future to Piston's ecosystem. 978 | WindowEvent::TouchpadPressure { .. } | 979 | WindowEvent::PinchGesture { .. } | 980 | WindowEvent::RotationGesture { .. } | 981 | WindowEvent::PanGesture { .. } | 982 | WindowEvent::DoubleTapGesture { .. } => None, 983 | WindowEvent::ScaleFactorChanged { .. } => None, 984 | WindowEvent::ActivationTokenDone { .. } => None, 985 | WindowEvent::ThemeChanged(_) => None, 986 | WindowEvent::Ime(_) => None, 987 | WindowEvent::Occluded(_) => None, 988 | WindowEvent::RedrawRequested { .. } => None, 989 | WindowEvent::Moved(_) => None, 990 | WindowEvent::ModifiersChanged(_) => None, 991 | } 992 | } 993 | 994 | #[derive(Debug, Eq, PartialEq)] 995 | /// Custom events for the glutin event loop 996 | pub enum UserEvent { 997 | /// Do nothing, just spin the event loop 998 | WakeUp, 999 | } 1000 | --------------------------------------------------------------------------------