1 module nodes.input_map_chain; 2 3 import fluid; 4 5 @safe: 6 7 class ActionTester : InputNode!Node { 8 9 mixin enableInputActions; 10 11 int pressed; 12 int submitted; 13 int broken; 14 int frameCalls; 15 16 bool disableBreaking; 17 18 override void resizeImpl(Vector2 space) { } 19 override void drawImpl(Rectangle, Rectangle) { } 20 21 @(ActionIO.CoreAction.frame, WhileHeld) 22 void frame() { 23 frameCalls++; 24 } 25 26 @(FluidInputAction.press) 27 bool press() { 28 pressed++; 29 return true; 30 } 31 32 @(FluidInputAction.submit) 33 bool submit() { 34 submitted++; 35 return true; 36 } 37 38 @(FluidInputAction.breakLine) 39 bool breakLine() { 40 41 if (disableBreaking) return false; 42 43 broken++; 44 return true; 45 46 } 47 48 bool runInputAction(immutable InputActionID actionID, bool isActive, int) { 49 50 return super.runInputAction(actionID, isActive); 51 52 } 53 54 } 55 56 alias actionTester = nodeBuilder!ActionTester; 57 58 @("InputMapChain can trigger input events") 59 unittest { 60 61 // Create bindings 62 auto map = InputMapping(); 63 map.bindNew!(FluidInputAction.press)(KeyboardIO.codes.space); 64 map.bindNew!(FluidInputAction.submit)(KeyboardIO.codes.enter); 65 map.bindNew!(FluidInputAction.breakLine)(KeyboardIO.codes.enter); 66 map.bindNew!(FluidInputAction.submit)(KeyboardIO.codes.leftControl, KeyboardIO.codes.enter); 67 68 auto tester = actionTester(); 69 auto root = inputMapChain(map, tester); 70 71 root.draw(); 72 assert(tester.pressed == 0); 73 74 // Press the button using an event 75 root.emitEvent( 76 KeyboardIO.createEvent(KeyboardIO.Key.space, true), 77 0, 78 &tester.runInputAction, 79 ); 80 root.draw(); 81 assert(tester.pressed == 1); 82 83 // Break line 84 root.emitEvent( 85 KeyboardIO.createEvent(KeyboardIO.Key.enter, true), 86 0, 87 &tester.runInputAction, 88 ); 89 root.draw(); 90 assert(tester.pressed == 1); 91 assert(tester.broken == 1); 92 assert(tester.submitted == 0, "Enter doesn't submit as breakLine takes priority"); 93 94 // Submit 95 tester.disableBreaking = true; 96 root.emitEvent( 97 KeyboardIO.createEvent(KeyboardIO.Key.enter, true), 98 0, 99 &tester.runInputAction, 100 ); 101 root.draw(); 102 assert(tester.pressed == 1); 103 assert(tester.broken == 1); 104 assert(tester.submitted == 1); 105 106 } 107 108 @("InputMapChain emits a fallback event") 109 unittest { 110 111 auto map = InputMapping(); 112 map.bindNew!(FluidInputAction.press)(KeyboardIO.codes.space); 113 114 auto tester = actionTester(); 115 auto root = inputMapChain(map, tester); 116 117 root.draw(); 118 assert(tester.frameCalls == 0); 119 120 // Emit frame event and expect it to be handled 121 root.emitEvent(ActionIO.frameEvent, 0, &tester.runInputAction); 122 assert(tester.frameCalls == 0); 123 root.draw(); 124 assert(tester.frameCalls == 1); 125 126 // Emit frame event alongside a space press event 127 root.emitEvent(ActionIO.frameEvent, 0, &tester.runInputAction); 128 root.emitEvent(KeyboardIO.press.space, 0, &tester.runInputAction); 129 assert(tester.frameCalls == 1); 130 assert(tester.pressed == 0); 131 root.draw(); 132 assert(tester.pressed == 1); 133 134 } 135 136 @("Bindings are evaluated in a first-in-last-out manner") 137 unittest { 138 139 auto map = InputMapping(); 140 map.bindNew!(FluidInputAction.press)(KeyboardIO.codes.enter); 141 142 auto tester = actionTester(); 143 auto root = inputMapChain(map, tester); 144 145 root.draw(); 146 assert(tester.pressed == 0); 147 assert(tester.broken == 0); 148 149 root.emitEvent(KeyboardIO.press.enter, 0, &tester.runInputAction); 150 root.draw(); 151 assert(tester.pressed == 1); 152 assert(tester.broken == 0); 153 154 map.bindNew!(FluidInputAction.breakLine)(KeyboardIO.codes.enter); 155 root.emitEvent(KeyboardIO.press.enter, 0, &tester.runInputAction); 156 root.draw(); 157 assert(tester.pressed == 1); 158 assert(tester.broken == 1); 159 160 }