1 /** 2 * Delegate-based event system. 3 * 4 * `Event!(T...)` is a lightweight multicast delegate: handlers are added with 5 * `event ~= &handler` and invoked together with `event.fire(args)`. This is the 6 * spine of Deft's UI notifications — buttons, windows, list selections and so on 7 * expose `Event!(...)` fields that user code subscribes to. 8 */ 9 module deft.events; 10 11 /** 12 * A multicast list of `void delegate(T...)` handlers. 13 * 14 * Add handlers with `~=`, invoke them all with `fire`, and remove one with 15 * `disconnect`. Firing with no registered handlers is a no-op. Handlers are 16 * invoked in registration order. 17 */ 18 struct Event(T...) 19 { 20 private void delegate(T)[] listeners; 21 22 /// Subscribe a handler: `event ~= &handler`. 23 ref typeof(this) opOpAssign(string op : "~")(void delegate(T) handler) return 24 { 25 listeners ~= handler; 26 return this; 27 } 28 29 /// Subscribe a handler (named-method equivalent of `~=`). 30 void connect(void delegate(T) handler) 31 { 32 listeners ~= handler; 33 } 34 35 /// Remove a previously-subscribed handler. Unknown handlers are ignored. 36 void disconnect(void delegate(T) handler) 37 { 38 foreach (i, listener; listeners) 39 { 40 if (listener == handler) 41 { 42 listeners = listeners[0 .. i] ~ listeners[i + 1 .. $]; 43 return; 44 } 45 } 46 } 47 48 /// Invoke every subscribed handler, in order, with the given arguments. 49 void fire(T args) 50 { 51 // Iterate over a snapshot so a handler may safely (dis)connect during 52 // dispatch without disturbing this fire. 53 auto snapshot = listeners; 54 foreach (listener; snapshot) 55 listener(args); 56 } 57 58 /// Number of currently-subscribed handlers. 59 size_t length() const @safe pure nothrow @nogc 60 { 61 return listeners.length; 62 } 63 64 /// Remove all handlers. 65 void clear() @safe pure nothrow @nogc 66 { 67 listeners = null; 68 } 69 } 70 71 /// A handler taking no arguments — e.g. a button click. 72 alias Action = void delegate(); 73 74 /// A handler receiving a selected item index. 75 alias SelectionEvent = void delegate(int index); 76 77 /// A handler receiving keyboard event details. 78 alias KeyEvent = void delegate(KeyEventArgs args); 79 80 /// A handler receiving mouse event details. 81 alias MouseEvent = void delegate(MouseEventArgs args); 82 83 /// A handler receiving a text payload. 84 alias TextEvent = void delegate(string text); 85 86 /// Mouse buttons reported by `MouseEventArgs`. 87 enum MouseButton 88 { 89 none, 90 left, 91 right, 92 middle, 93 } 94 95 /** 96 * Details of a keyboard event. 97 * 98 * Set `handled = true` in a handler to indicate the key was consumed and that 99 * further default processing should be suppressed. 100 */ 101 struct KeyEventArgs 102 { 103 uint keyCode; 104 bool ctrl; 105 bool shift; 106 bool alt; 107 bool handled; 108 } 109 110 /// Details of a mouse event, in client coordinates. 111 struct MouseEventArgs 112 { 113 int x; 114 int y; 115 MouseButton button; 116 } 117 118 unittest 119 { 120 // Register a handler, fire, verify it was called. 121 Event!() ev; 122 int count; 123 void onFire() { ++count; } 124 125 ev ~= &onFire; 126 ev.fire(); 127 assert(count == 1); 128 assert(ev.length == 1); 129 } 130 131 unittest 132 { 133 // Multiple handlers are all invoked, in registration order. 134 Event!() ev; 135 int[] order; 136 void first() { order ~= 1; } 137 void second() { order ~= 2; } 138 139 ev ~= &first; 140 ev ~= &second; 141 ev.fire(); 142 assert(order == [1, 2]); 143 } 144 145 unittest 146 { 147 // Disconnecting a handler stops it from being called. 148 Event!() ev; 149 int a, b; 150 void ha() { ++a; } 151 void hb() { ++b; } 152 153 ev ~= &ha; 154 ev ~= &hb; 155 ev.disconnect(&ha); 156 ev.fire(); 157 assert(a == 0); 158 assert(b == 1); 159 assert(ev.length == 1); 160 } 161 162 unittest 163 { 164 // Firing with no handlers does not crash. 165 Event!() ev; 166 ev.fire(); 167 168 Event!(int) evi; 169 evi.fire(7); 170 } 171 172 unittest 173 { 174 // Arguments are delivered to handlers. 175 Event!(int, string) ev; 176 int gotInt; 177 string gotStr; 178 void handler(int i, string s) { gotInt = i; gotStr = s; } 179 180 ev ~= &handler; 181 ev.fire(42, "hello"); 182 assert(gotInt == 42); 183 assert(gotStr == "hello"); 184 } 185 186 unittest 187 { 188 // Disconnecting an unknown handler is a harmless no-op. 189 Event!() ev; 190 void known() {} 191 void unknown() {} 192 193 ev ~= &known; 194 ev.disconnect(&unknown); 195 assert(ev.length == 1); 196 }