1 /** 2 * Defines the static Input class, which is responsible for handling all keyboard/mouse/controller interactions. 3 */ 4 module dash.utility.input.input; 5 import dash.utility, dash.core, dash.graphics; 6 7 import yaml; 8 import derelict.opengl3.gl3; 9 import std.algorithm, std.conv, std.uuid; 10 11 /// The event type for button events. 12 package alias ButtonEvent = void delegate( ButtonStorageType ); 13 /// The event type for axis events. 14 package alias AxisEvent = void delegate( AxisStorageType ); 15 16 /** 17 * Manages all input events. 18 */ 19 final abstract class Input 20 { 21 static: 22 private: 23 static struct Binding 24 { 25 public: 26 @ignore 27 string name; 28 29 @rename( "Keyboard" ) @byName @optional 30 Keyboard.Buttons[] keyboardButtons; 31 32 @rename( "Mouse" ) @byName @optional 33 Mouse.Buttons[] mouseButtons; 34 35 @rename( "MouseAxes" ) @byName @optional 36 Mouse.Axes[] mouseAxes; 37 } 38 39 /// Bindings directly from Config/Input 40 Binding[string] bindings; 41 /// The file the bindings came from. 42 Resource bindingFile = internalResource; 43 44 /// The registered button events. 45 Tuple!( UUID, ButtonEvent )[][ string ] buttonEvents; 46 /// The registered axis events. 47 Tuple!( UUID, AxisEvent )[][ string ] axisEvents; 48 49 public: 50 /** 51 * Processes Config/Input.yml and pulls input string bindings. 52 */ 53 void initialize() 54 { 55 try 56 { 57 auto bindingRes = deserializeFileByName!(typeof(bindings))( Resources.InputBindings ); 58 bindings = bindingRes[ 0 ]; 59 bindingFile = bindingRes[ 1 ]; 60 61 foreach( name, ref binding; bindings ) 62 binding.name = name; 63 } 64 catch( Exception e ) 65 { 66 errorf( "Error parsing config file:%s\n", e.toString() ); 67 } 68 69 Keyboard.initialize(); 70 Mouse.initialize(); 71 } 72 73 /** 74 * Updates the key states, and calls all key events. 75 */ 76 void update() 77 { 78 Keyboard.update(); 79 Mouse.update(); 80 } 81 82 /// Called by InputSystem to report changed inputs. 83 package void processDiffs( InputSystem, InputEnum, StateRep )( Tuple!( InputEnum, StateRep )[] diffs ) 84 { 85 // Iterate over each diff. 86 foreach( diff; diffs ) 87 { 88 // Iterate over each binding. 89 foreach( name, binding; bindings ) 90 { 91 // Get the array of bindings to check and events to call. 92 static if( is( InputEnum == Keyboard.Buttons ) ) 93 { 94 auto bindingArray = binding.keyboardButtons; 95 auto eventArray = buttonEvents; 96 } 97 else static if( is( InputEnum == Mouse.Buttons ) ) 98 { 99 auto bindingArray = binding.mouseButtons; 100 auto eventArray = buttonEvents; 101 } 102 else static if( is( InputEnum == Mouse.Axes ) ) 103 { 104 auto bindingArray = binding.mouseAxes; 105 auto eventArray = axisEvents; 106 } 107 else static assert( false, "InputEnum unsupported." ); 108 109 // Check the binding for the changed input. 110 bindingsLoop: 111 foreach( button; bindingArray ) 112 { 113 if( button == diff[ 0 ] && name in eventArray ) 114 { 115 foreach( eventTup; eventArray[ name ] ) 116 { 117 eventTup[ 1 ]( diff[ 1 ] ); 118 break bindingsLoop; 119 } 120 } 121 } 122 } 123 } 124 } 125 126 /** 127 * Gets the state of a string-bound input. 128 * 129 * Params: 130 * input = The input to check for. 131 * checkPrevious = Whether or not to make sure the key was up last frame. 132 */ 133 T getState( T = bool )( string input, bool checkPrevious = false ) if( is( T == bool ) || is( T == float ) ) 134 { 135 if( auto binding = input in bindings ) 136 { 137 static if( is( T == bool ) ) 138 { 139 foreach( key; binding.keyboardButtons ) 140 if( Keyboard.isButtonDown( key, checkPrevious ) ) 141 return true; 142 foreach( mb; binding.mouseButtons ) 143 if( Mouse.isButtonDown( mb, checkPrevious ) ) 144 return true; 145 146 return false; 147 } 148 else static if( is( T == float ) ) 149 { 150 foreach( ma; binding.mouseAxes ) 151 { 152 auto state = Mouse.getAxisState( ma, checkPrevious ); 153 if( state != 0.0f ) 154 return state; 155 } 156 157 return 0.0f; 158 } 159 else static assert( false, "Unsupported return type." ); 160 } 161 162 throw new Exception( "Input " ~ input ~ " not bound." ); 163 } 164 165 /** 166 * Check if a given button is down. 167 * 168 * Params: 169 * buttonName = The name of the button to check. 170 * checkPrevious = Whether or not to make sure the button was down last frame. 171 */ 172 bool isButtonDown( string buttonName, bool checkPrevious = false ) 173 { 174 if( auto binding = buttonName in bindings ) 175 { 176 foreach( key; binding.keyboardButtons ) 177 if( Keyboard.isButtonDown( key, checkPrevious ) ) 178 return true; 179 180 foreach( mb; binding.mouseButtons ) 181 if( Mouse.isButtonDown( mb, checkPrevious ) ) 182 return true; 183 } 184 185 return false; 186 } 187 188 /** 189 * Check if a given button is down. 190 * 191 * Params: 192 * buttonName = The name of the button to check. 193 * checkPrevious = Whether or not to make sure the button was down last frame. 194 */ 195 bool isButtonUp( string buttonName, bool checkPrevious = false ) 196 { 197 if( auto binding = buttonName in bindings ) 198 { 199 foreach( key; binding.keyboardButtons ) 200 if( Keyboard.isButtonUp( key, checkPrevious ) ) 201 return true; 202 203 foreach( mb; binding.mouseButtons ) 204 if( Mouse.isButtonUp( mb, checkPrevious ) ) 205 return true; 206 } 207 208 return false; 209 } 210 211 /** 212 * Add an event for when a button state changes. 213 * 214 * Params: 215 * buttonName = The binding name of the button for the event. 216 * event = The event to call when the button changes. 217 * 218 * Returns: The id of the new event. 219 */ 220 UUID addButtonEvent( string buttonName, ButtonEvent event ) 221 { 222 auto id = randomUUID(); 223 buttonEvents[ buttonName ] ~= tuple( id, event ); 224 return id; 225 } 226 227 /** 228 * Add an event for when a button goes down. 229 * 230 * Params: 231 * buttonName = The binding name of the button for the event. 232 * event = The event to call when the button changes. 233 * 234 * Returns: The id of the new event. 235 */ 236 UUID addButtonDownEvent( string buttonName, ButtonEvent event ) 237 { 238 return addButtonEvent( buttonName, ( newState ) { if( newState ) event( newState ); } ); 239 } 240 241 /** 242 * Add an event for when a button goes up. 243 * 244 * Params: 245 * buttonName = The binding name of the button for the event. 246 * event = The event to call when the button changes. 247 * 248 * Returns: The id of the new event. 249 */ 250 UUID addButtonUpEvent( string buttonName, ButtonEvent event ) 251 { 252 return addButtonEvent( buttonName, ( newState ) { if( !newState ) event( newState ); } ); 253 } 254 255 /** 256 * Add an event for when an axis changes. 257 * 258 * Params: 259 * axisName = The binding name of the axis for the event. 260 * event = The event to call when the button changes. 261 * 262 * Returns: The id of the new event. 263 */ 264 UUID addAxisEvent( string axisName, AxisEvent event ) 265 { 266 auto id = randomUUID(); 267 axisEvents[ axisName ] ~= tuple( id, event ); 268 return id; 269 } 270 271 /** 272 * Remove a button event. 273 * 274 * Params: 275 * buttonName = The binding name of the button for the event. 276 * event = The event to call when the button changes. 277 * 278 * Returns: The id of the new event. 279 */ 280 bool removeButtonEvent( UUID id ) 281 { 282 foreach( name, ref eventGroup; buttonEvents ) 283 { 284 auto i = eventGroup.countUntil!( tup => tup[ 0 ] == id ); 285 286 if( i == -1 ) 287 continue; 288 289 // Get tasks after one being removed.s 290 auto end = eventGroup[ i+1..$ ]; 291 // Get tasks before one being removed. 292 eventGroup = eventGroup[ 0..i ]; 293 // Add end back. 294 eventGroup ~= end; 295 296 return true; 297 } 298 299 return false; 300 } 301 302 /** 303 * Gets the position of the cursor. 304 * 305 * Returns: The position of the mouse cursor. 306 */ 307 vec2ui mousePos() 308 { 309 version( DashUseSDL2 ) 310 { 311 import dash.graphics; 312 313 if( !Sdl.get() ) 314 return vec2ui( 0, 0 ); 315 316 auto mouse = Sdl.get().sdl.mouse; 317 return vec2ui( cast(uint)mouse.x, cast(uint)( Graphics.height - mouse.y ) ); 318 } 319 else version( Windows ) 320 { 321 if( !Win32GL.get() ) 322 return vec2ui( 0, 0 ); 323 324 import dash.graphics; 325 import win32.windows; 326 POINT i; 327 GetCursorPos( &i ); 328 ScreenToClient( Win32GL.get().hWnd, &i ); 329 330 return vec2ui( i.x, Graphics.height - i.y ); 331 } 332 else 333 { 334 return vec2ui( 0, 0 ); 335 } 336 } 337 338 /** 339 * Gets the world position of the cursor in the active scene. 340 * 341 * Returns: The position of the mouse cursor in world space. 342 */ 343 vec3f mousePosView() 344 { 345 if( !DGame.instance.activeScene ) 346 { 347 warning( "No active scene." ); 348 return vec3f( 0.0f, 0.0f, 0.0f ); 349 } 350 351 auto scene = DGame.instance.activeScene; 352 353 if( !scene.camera ) 354 { 355 warning( "No camera on active scene." ); 356 return vec3f( 0.0f, 0.0f, 0.0f ); 357 } 358 vec2ui mouse = mousePos(); 359 float depth; 360 int x = mouse.x; 361 int y = mouse.y; 362 auto view = vec3f( 0, 0, 0 ); 363 364 if( x >= 0 && x <= Graphics.width && y >= 0 && y <= Graphics.height ) 365 { 366 depth = Graphics.getDepthAtScreenPoint( mouse ); 367 368 auto linearDepth = scene.camera.projectionConstants.x / ( scene.camera.projectionConstants.y - depth ); 369 //Convert x and y to normalized device coords 370 float screenX = ( mouse.x / cast(float)Graphics.width ) * 2 - 1; 371 float screenY = -( ( mouse.y / cast(float)Graphics.height ) * 2 - 1 ); 372 373 auto viewSpace = scene.camera.inversePerspectiveMatrix * vec4f( screenX, screenY, 1.0f, 1.0f); 374 auto viewRay = vec3f( viewSpace.xy * (1.0f / viewSpace.z), 1.0f); 375 view = viewRay * linearDepth; 376 } 377 378 return view; 379 } 380 381 /** 382 * Gets the world position of the cursor in the active scene. 383 * 384 * Returns: The position of the mouse cursor in world space. 385 */ 386 vec3f mousePosWorld() 387 { 388 return (DGame.instance.activeScene.camera.inverseViewMatrix * vec4f( mousePosView(), 1.0f )).xyz; 389 } 390 391 /** 392 * Gets the world position of the cursor in the active scene. 393 * 394 * Returns: The GameObject located at the current mouse Position 395 */ 396 GameObject mouseObject() 397 { 398 if( !DGame.instance.activeScene ) 399 { 400 warning( "No active scene." ); 401 return null; 402 } 403 404 auto scene = DGame.instance.activeScene; 405 406 if( !scene.camera ) 407 { 408 warning( "No camera on active scene." ); 409 return null; 410 } 411 412 vec2ui mouse = mousePos(); 413 414 if( mouse.x >= 0 && mouse.x <= Graphics.width && mouse.y >= 0 && mouse.y <= Graphics.height ) 415 { 416 uint id = Graphics.getObjectIDAtScreenPoint( mouse ); 417 418 if( id > 0 ) 419 { 420 return scene[ id ]; 421 } 422 } 423 424 return null; 425 } 426 } 427 428 unittest 429 { 430 import std.stdio; 431 writeln( "Dash Input isKeyUp unittest" ); 432 433 Config.initialize(); 434 Input.initialize(); 435 Keyboard.setButtonState( Keyboard.Buttons.Space, true ); 436 437 Keyboard.update(); 438 Keyboard.setButtonState( Keyboard.Buttons.Space, false ); 439 440 Keyboard.update(); 441 assert( Keyboard.isButtonUp( Keyboard.Buttons.Space, true ) ); 442 assert( Keyboard.isButtonUp( Keyboard.Buttons.Space, false ) ); 443 444 Keyboard.update(); 445 assert( !Keyboard.isButtonUp( Keyboard.Buttons.Space, true ) ); 446 assert( Keyboard.isButtonUp( Keyboard.Buttons.Space, false ) ); 447 } 448 449 unittest 450 { 451 import std.stdio; 452 writeln( "Dash Input addKeyEvent unittest" ); 453 454 Config.initialize(); 455 Input.initialize(); 456 457 bool keyDown; 458 Keyboard.addButtonEvent( Keyboard.Buttons.Space, ( keyCode, newState ) 459 { 460 keyDown = newState; 461 } ); 462 463 Keyboard.setButtonState( Keyboard.Buttons.Space, true ); 464 Input.update(); 465 assert( keyDown ); 466 467 Keyboard.setButtonState( Keyboard.Buttons.Space, false ); 468 Input.update(); 469 assert( !keyDown ); 470 } 471 472 unittest 473 { 474 import std.stdio; 475 writeln( "Dash Input isKeyDown unittest" ); 476 477 Config.initialize(); 478 Input.initialize(); 479 Keyboard.setButtonState( Keyboard.Buttons.Space, true ); 480 481 Input.update(); 482 assert( Keyboard.isButtonDown( Keyboard.Buttons.Space, true ) ); 483 assert( Keyboard.isButtonDown( Keyboard.Buttons.Space, false ) ); 484 485 Input.update(); 486 assert( !Keyboard.isButtonDown( Keyboard.Buttons.Space, true ) ); 487 assert( Keyboard.isButtonDown( Keyboard.Buttons.Space, false ) ); 488 }