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 }