1 module fluid.input_map_chain; 2 3 import std.algorithm; 4 5 import fluid.node; 6 import fluid.utils; 7 import fluid.input; 8 import fluid.types; 9 import fluid.node_chain; 10 11 import fluid.io.action; 12 13 import fluid.future.stack; 14 15 @safe: 16 17 alias inputMapChain = nodeBuilder!InputMapChain; 18 19 class InputMapChain : NodeChain, ActionIO { 20 21 mixin controlIO; 22 23 private struct ReceivedInputEvent { 24 InputEvent event; 25 int number; 26 bool delegate(InputActionID action, bool isActive, int number) @safe callback; 27 } 28 29 public { 30 31 /// Map of input events to input actions. 32 InputMapping map; 33 34 } 35 36 private { 37 38 /// All collected input events. 39 Stack!ReceivedInputEvent _events; 40 41 } 42 43 this(InputMapping map, Node next = null) { 44 super(next); 45 this.map = map; 46 } 47 48 this(Node next = null) { 49 this(InputMapping.defaultMapping, next); 50 } 51 52 override void beforeResize(Vector2) { 53 startIO(); 54 } 55 56 override void afterResize(Vector2) { 57 stopIO(); 58 } 59 60 override void afterDraw(Rectangle, Rectangle) { 61 62 // Process all input events 63 processEvents(); 64 _events.clear(); 65 66 } 67 68 override void emitEvent(InputEvent event, int number, 69 bool delegate(InputActionID action, bool isActive, int number) @safe callback) 70 do { 71 72 // Save the event to list 73 _events ~= ReceivedInputEvent(event, number, callback); 74 75 } 76 77 /// Find the given event type among ones that were emitted this frame. 78 /// Safety: 79 /// The range has to be exhausted immediately. 80 /// No input events can be emitted before the range is disposed of, or the range will break. 81 /// Params: 82 /// code = Input event code to find. 83 /// Returns: 84 /// A range with all emitted events that match the query. 85 auto findEvents(InputEventCode code) @system { 86 87 return _events[].filter!(a => a.event.code == code); 88 89 } 90 91 /// Detect all input actions that should be emitted as a consequence of the events that occurred this frame. 92 /// Clears the current list of events when done. 93 private void processEvents() @trusted { 94 95 scope (exit) _events.clear(); 96 97 bool handled; 98 99 // Test noop event first 100 foreach (event; findEvents(noopEvent.code)) { 101 return; 102 } 103 104 // Test all mappings 105 foreach (layer; map.layers) { 106 107 // Check if every modifier in this layer is active 108 if (layer.modifiers.any!(a => findEvents(a).empty)) continue; 109 110 // Found an active layer, test all bound strokes 111 foreach_reverse (binding; layer.bindings) { 112 113 // Check if any of the events matches this binding 114 foreach (event; findEvents(binding.code)) { 115 116 handled = handled || event.callback(binding.inputAction, event.event.isActive, event.number); 117 118 } 119 120 // Stroke handled, stop here 121 if (handled) break; 122 123 } 124 125 // End on this layer 126 break; 127 128 } 129 130 // No event was handled, fire the frame event 131 if (!handled) { 132 133 foreach (event; findEvents(frameEvent.code)) { 134 event.callback(inputActionID!(CoreAction.frame), event.event.isActive, event.number); 135 } 136 137 } 138 139 } 140 141 } 142 143 /// Maps sequences input events to input actions. 144 /// 145 /// Actions are bound to "strokes". A single stroke is a set of modifier events and a trigger event. 146 /// A stroke without modifiers is one that directly binds a button or key to an action, for example 147 /// mapping the backspace key to an "eraseCharacter" action. Modifiers can be added to require that 148 /// multiple other buttons be held for the action to work — the "ctrl+C" stroke, often used to copy 149 /// text, has one modifier key "ctrl" and a trigger key "c". 150 /// 151 /// Mappings are grouped by modifiers into "layers". All mappings that share the same set of modifiers 152 /// will be placed on the same layer. These layers are sorted by number of modifiers, and only one is 153 /// looked up at once; this prevents firing an action with a less complex set of modifiers from 154 /// accidentally firing when performing a more complex one. For example, an action bound to the key "C" 155 /// will not fire when pressing "ctrl+C". 156 /// 157 /// Mappings can combine multiple input events, so it is possible to use both keyboard keys and mouse 158 /// buttons in a mapping. A mouse button could be used as a trigger, combined with a keyboard key as 159 /// a modifier, such as "ctrl + left mouse button". 160 struct InputMapping { 161 162 /// Final element in a stroke, completing the circuit and creating the event. 163 struct Trigger { 164 165 /// Input action that should be emitted. 166 InputActionID inputAction; 167 168 /// Event code that triggers this action. 169 InputEventCode code; 170 } 171 172 /// A layer groups all mappings that share the same set of input event codes. 173 struct Layer { 174 175 /// Modifiers that have to be pressed for this layer to be checked. 176 InputEventCode[] modifiers; 177 178 /// Keys and events on this layer. These binding are tested in reverse-order, 179 /// so bindings that come last are tested first, giving them higher priority. 180 Trigger[] bindings; 181 182 int opCmp(const Layer other) const { 183 184 // You're not going to put 2,147,483,646 modifiers in a single stroke, are you? 185 return cast(int) (other.modifiers.length - modifiers.length); 186 187 } 188 189 } 190 191 /// All input layers that have been mapped. 192 /// 193 /// Input layers have to be sorted, so that the layer with most modifiers have to be first, and layer with no 194 /// modifiers have to be last. Every layer should have a unique set of modifiers. 195 Layer[] layers; 196 197 invariant(layers.isSorted); 198 199 /// Bind an input stroke to an input action. 200 /// 201 /// Does not replace any existing mappings, even in case of collision. Previously created mappings will 202 /// have higher higher priority than this mapping. 203 /// 204 /// Params: 205 /// action = Input action the stroke should trigger 206 /// codes = Sequence of event codes that triggers the event. 207 void bindNew(InputActionID action, InputEventCode[] codes...) 208 in (codes.length >= 1) 209 do { 210 211 auto modifiers = codes[0 .. $-1].dup; 212 const trigger = codes[$-1]; 213 const binding = Trigger(action, trigger); 214 215 // Find a matching layer for this mapping 216 foreach (i, ref layer; layers) { 217 218 if (layer.modifiers.length > modifiers.length) continue; 219 220 // No layer has this set of modifiers 221 if (layer.modifiers.length < modifiers.length) { 222 223 // Create one 224 auto newLayer = Layer(modifiers, [binding]); 225 226 // Insert 227 layers = layers[0..i] ~ newLayer ~ layers[i..$]; 228 return; 229 230 } 231 232 // Found a matching layer 233 if (layer.modifiers == modifiers) { 234 235 // Insert the binding 236 layer.bindings ~= binding; 237 return; 238 239 } 240 241 } 242 243 // This has less modifiers than any existing layer 244 layers ~= Layer(modifiers, [binding]); 245 246 } 247 248 /// ditto 249 void bindNew(alias action)(InputEventCode[] codes...) { 250 251 const actionID = inputActionID!action; 252 bindNew(actionID, codes); 253 254 } 255 256 /// Add a number of bindings to an empty map. 257 unittest { 258 259 import fluid.io.keyboard; 260 261 auto map = InputMapping(); 262 map.bindNew!(FluidInputAction.press) (KeyboardIO.codes.enter); 263 map.bindNew!(FluidInputAction.focusNext)(KeyboardIO.codes.tab); 264 map.bindNew!(FluidInputAction.submit) (KeyboardIO.codes.leftControl, KeyboardIO.codes.enter); 265 map.bindNew!(FluidInputAction.copy) (KeyboardIO.codes.leftControl, KeyboardIO.codes.c); 266 267 // The above creates equivalent layers 268 assert(map.layers == [ 269 Layer([KeyboardIO.codes.leftControl], [ 270 Trigger(inputActionID!(FluidInputAction.submit), KeyboardIO.codes.enter), 271 Trigger(inputActionID!(FluidInputAction.copy), KeyboardIO.codes.c), 272 ]), 273 Layer([], [ 274 Trigger(inputActionID!(FluidInputAction.press), KeyboardIO.codes.enter), 275 Trigger(inputActionID!(FluidInputAction.focusNext), KeyboardIO.codes.tab), 276 ]), 277 ]); 278 279 } 280 281 /// Create a default input map using commonly used input sequences for each of the default actions. 282 /// Returns: 283 /// A newly created input mapping. 284 static InputMapping defaultMapping() { 285 286 import fluid.io.keyboard; 287 import fluid.io.mouse; 288 289 InputMapping mapping; 290 291 /// Get the ID of an input action. 292 auto bind(alias a)(InputEventCode code) { 293 294 return Trigger(InputAction!a.id, code); 295 296 } 297 298 with (FluidInputAction) { 299 300 // System-independent keys 301 auto universalShift = Layer( 302 [KeyboardIO.codes.leftShift], 303 [ 304 bind!focusPrevious(KeyboardIO.codes.tab), 305 bind!contextMenu(KeyboardIO.codes.f10), 306 bind!breakLine(KeyboardIO.codes.enter), 307 bind!selectToLineEnd(KeyboardIO.codes.end), 308 bind!selectToLineStart(KeyboardIO.codes.home), 309 bind!selectNextLine(KeyboardIO.codes.down), 310 bind!selectPreviousLine(KeyboardIO.codes.up), 311 bind!selectNextChar(KeyboardIO.codes.right), 312 bind!selectPreviousChar(KeyboardIO.codes.left), 313 bind!outdent(KeyboardIO.codes.tab), 314 bind!entryPrevious(KeyboardIO.codes.tab), 315 ] 316 ); 317 auto universal = Layer( 318 [], 319 [ 320 // Focus control 321 // bind!focusDown(GamepadButton.dpadDown), TODO 322 bind!focusDown(KeyboardIO.codes.down), 323 // bind!focusUp(GamepadButton.dpadUp), TODO 324 bind!focusUp(KeyboardIO.codes.up), 325 // bind!focusRight(GamepadButton.dpadRight), TODO 326 bind!focusRight(KeyboardIO.codes.right), 327 // bind!focusLeft(GamepadButton.dpadLeft), TODO 328 bind!focusLeft(KeyboardIO.codes.left), 329 // bind!focusNext(GamepadButton.rightButton), TODO 330 bind!focusNext(KeyboardIO.codes.tab), 331 // bind!focusPrevious(GamepadButton.leftButton), TODO 332 333 // Scroll 334 bind!pageDown(KeyboardIO.codes.pageDown), 335 bind!pageUp(KeyboardIO.codes.pageUp), 336 // bind!scrollDown(GamepadButton.dpadDown), TODO 337 bind!scrollDown(KeyboardIO.codes.down), 338 // bind!scrollUp(GamepadButton.dpadUp), TODO 339 bind!scrollUp(KeyboardIO.codes.up), 340 // bind!scrollRight(GamepadButton.dpadRight), TODO 341 bind!scrollRight(KeyboardIO.codes.right), 342 // bind!scrollLeft(GamepadButton.dpadLeft), TODO 343 bind!scrollLeft(KeyboardIO.codes.left), 344 // bind!submit(GamepadButton.cross), TODO 345 346 // Submit 347 bind!submit(KeyboardIO.codes.enter), 348 349 // Text editing 350 bind!insertTab(KeyboardIO.codes.tab), 351 bind!toLineEnd(KeyboardIO.codes.end), 352 bind!toLineStart(KeyboardIO.codes.home), 353 // bind!entryNext(GamepadButton.dpadDown), TODO 354 bind!entryNext(KeyboardIO.codes.tab), 355 bind!entryNext(KeyboardIO.codes.down), 356 // bind!entryPrevious(GamepadButton.dpadUp), TODO 357 bind!entryPrevious(KeyboardIO.codes.up), 358 bind!nextLine(KeyboardIO.codes.down), 359 bind!previousLine(KeyboardIO.codes.up), 360 bind!nextChar(KeyboardIO.codes.right), 361 bind!previousChar(KeyboardIO.codes.left), 362 bind!breakLine(KeyboardIO.codes.enter), 363 bind!deleteChar(KeyboardIO.codes.delete_), 364 bind!backspace(KeyboardIO.codes.backspace), 365 366 // Basic actions 367 bind!contextMenu(KeyboardIO.codes.contextMenu), 368 bind!contextMenu(MouseIO.codes.right), 369 // bind!cancel(GamepadButton.circle), TODO 370 bind!cancel(KeyboardIO.codes.escape), 371 // bind!press(GamepadButton.cross), TODO 372 bind!press(KeyboardIO.codes.enter), 373 bind!press(MouseIO.codes.left), 374 ] 375 ); 376 377 // TODO universal left/right key 378 version (Fluid_MacKeyboard) 379 mapping.layers = [ 380 381 // Shift + Command 382 Layer( 383 [KeyboardIO.codes.leftShift, KeyboardIO.codes.leftSuper], 384 [ 385 // TODO Command should *expand selection* on macOS instead of current 386 // toLineStart/toLineEnd behavior 387 bind!redo(KeyboardIO.codes.z), 388 bind!selectToEnd(KeyboardIO.codes.down), 389 bind!selectToStart(KeyboardIO.codes.up), 390 bind!selectToLineEnd(KeyboardIO.codes.right), 391 bind!selectToLineStart(KeyboardIO.codes.left), 392 ] 393 ), 394 395 // Shift + Option 396 Layer( 397 [KeyboardIO.codes.leftShift, KeyboardIO.codes.leftAlt], 398 [ 399 bind!selectNextWord(KeyboardIO.codes.right), 400 bind!selectPreviousWord(KeyboardIO.codes.left), 401 ] 402 ), 403 404 // Command 405 Layer( 406 [KeyboardIO.codes.leftSuper], 407 [ 408 bind!submit(KeyboardIO.codes.enter), 409 bind!redo(KeyboardIO.codes.y), 410 bind!undo(KeyboardIO.codes.z), 411 bind!paste(KeyboardIO.codes.v), 412 bind!cut(KeyboardIO.codes.x), 413 bind!copy(KeyboardIO.codes.c), 414 bind!selectAll(KeyboardIO.codes.a), 415 bind!toEnd(KeyboardIO.codes.down), 416 bind!toStart(KeyboardIO.codes.up), 417 bind!toLineEnd(KeyboardIO.codes.right), 418 bind!toLineStart(KeyboardIO.codes.left), 419 ] 420 ), 421 422 // Option 423 Layer( 424 [KeyboardIO.codes.leftAlt], 425 [ 426 bind!nextWord(KeyboardIO.codes.right), 427 bind!previousWord(KeyboardIO.codes.left), 428 bind!backspaceWord(KeyboardIO.codes.backspace), 429 bind!deleteWord(KeyboardIO.codes.delete_), 430 ] 431 ), 432 433 // Control 434 Layer( 435 [KeyboardIO.codes.leftControl], 436 [ 437 bind!entryNext(KeyboardIO.codes.n), // emacs 438 bind!entryNext(KeyboardIO.codes.j), // vim 439 bind!entryPrevious(KeyboardIO.codes.p), // emacs 440 bind!entryPrevious(KeyboardIO.codes.k), // vim 441 bind!backspaceWord(KeyboardIO.codes.w), // emacs & vim 442 ] 443 ), 444 445 universalShift, 446 universal, 447 ]; 448 else 449 mapping.layers = [ 450 451 Layer( 452 [KeyboardIO.codes.leftShift, KeyboardIO.codes.leftControl], 453 [ 454 bind!redo(KeyboardIO.codes.z), 455 bind!selectToEnd(KeyboardIO.codes.end), 456 bind!selectToStart(KeyboardIO.codes.home), 457 bind!selectNextWord(KeyboardIO.codes.right), 458 bind!selectPreviousWord(KeyboardIO.codes.left), 459 ] 460 ), 461 462 Layer( 463 [KeyboardIO.codes.leftControl], 464 [ 465 bind!submit(KeyboardIO.codes.enter), 466 bind!toEnd(KeyboardIO.codes.end), 467 bind!toStart(KeyboardIO.codes.home), 468 bind!redo(KeyboardIO.codes.y), 469 bind!undo(KeyboardIO.codes.z), 470 bind!paste(KeyboardIO.codes.v), 471 bind!cut(KeyboardIO.codes.x), 472 bind!copy(KeyboardIO.codes.c), 473 bind!selectAll(KeyboardIO.codes.a), 474 bind!nextWord(KeyboardIO.codes.right), 475 bind!previousWord(KeyboardIO.codes.left), 476 bind!entryNext(KeyboardIO.codes.n), // emacs 477 bind!entryNext(KeyboardIO.codes.j), // vim 478 bind!entryPrevious(KeyboardIO.codes.p), // emacs 479 bind!entryPrevious(KeyboardIO.codes.k), // vim 480 bind!backspaceWord(KeyboardIO.codes.w), // emacs & vim 481 bind!backspaceWord(KeyboardIO.codes.backspace), 482 bind!deleteWord(KeyboardIO.codes.delete_), 483 ] 484 ), 485 486 Layer( 487 [KeyboardIO.codes.leftAlt], 488 [ 489 bind!contextMenu(KeyboardIO.codes.f10), 490 bind!entryUp(KeyboardIO.codes.up), 491 ] 492 ), 493 494 universalShift, 495 universal, 496 497 ]; 498 499 } 500 501 return mapping; 502 503 } 504 505 }