1 // Written in the D programming language.
2
3 /**
4
5 This module contains definition of signals / listeners.
6
7 Similar to std.signals.
8
9 Unlike std.signals, supports any types of delegates, and as well interfaces with single method.
10
11 Unlike std.signals, can support return types for slots.
12
13 Caution: unlike std.signals, does not disconnect signal from slots belonging to destroyed objects.
14
15 Listener here stand for holder of single delegate (slot).
16
17 Signal is the same but supports multiple slots.
18
19 Listener has smaller memory footprint, but allows only single slot.
20
21
22 Can be declared either using list of result value and argument types, or by interface name with single method.
23
24
25 Synopsis:
26
27 ----
28 import dlangui.core.signals;
29
30 interface SomeInterface {
31 bool someMethod(string s, int n);
32 }
33 class Foo : SomeInterface {
34 override bool someMethod(string s, int n) {
35 writeln("someMethod called ", s, ", ", n);
36 return n > 10; // can return value
37 }
38 }
39
40 // Listener! can hold arbitrary number of connected slots
41
42 // declare using list of return value and parameter types
43 Listener!(bool, string, n) signal1;
44
45 Foo f = new Foo();
46 // when signal is defined as type list, you can use delegate
47 signal1 = bool delegate(string s, int n) { writeln("inside delegate - ", s, n); return false; }
48 // or method reference
49 signal1 = &f.someMethod;
50
51 // declare using interface with single method
52 Listener!SomeInterface signal2;
53 // you still can use any delegate
54 signal2 = bool delegate(string s, int n) { writeln("inside delegate - ", s, n); return false; }
55 // but for class method which overrides interface method, you can use simple syntax
56 signal2 = f; // it will automatically take &f.someMethod
57
58
59 // call listener(s) either by opcall or explicit emit
60 signal1("text", 1);
61 signal1.emit("text", 2);
62 signal2.emit("text", 3);
63
64 // check if any slit is connected
65 if (signal1.assigned)
66 writeln("has listeners");
67
68 // Signal! can hold arbitrary number of connected slots
69
70 // declare using list of return value and parameter types
71 Signal!(bool, string, n) signal3;
72
73 // add listeners via connect call
74 signal3.connect(bool delegate(string, int) { return false; });
75 // or via ~= operator
76 signal3 ~= bool delegate(string, int) { return false; };
77
78 // declare using interface with single method
79 Signal!SomeInterface signal4;
80
81 // you can connect several slots to signal
82 signal4 ~= f;
83 signal4 ~= bool delegate(string, int) { return true; }
84
85 // calling of listeners of Signal! is similar to Listener!
86 // using opCall
87 bool res = signal4("blah", 5);
88 // call listeners using emit
89 bool res = signal4.emit("blah", 5);
90
91 // you can disconnect individual slots
92 // using disconnect()
93 signal4.disconnect(f);
94 // or -= operator
95 signal4 -= f;
96
97 ----
98
99 Copyright: Vadim Lopatin, 2014
100 License: Boost License 1.0
101 Authors: Vadim Lopatin, coolreader.org@gmail.com
102 */
103 module dlangui.core.signals;
104
105 import std.traits;
106 import dlangui.core.collections;
107
108 /// Single listener; parameter is interface with single method
109 struct Listener(T1) if (is(T1 == interface) && __traits(allMembers, T1).length == 1) {
110 alias return_t = ReturnType!(__traits(getMember, T1, __traits(allMembers, T1)[0]));
111 alias params_t = ParameterTypeTuple!(__traits(getMember, T1, __traits(allMembers, T1)[0]));
112 alias slot_t = return_t delegate(params_t);
113 private slot_t _listener;
114 /// returns true if listener is assigned
115 final bool assigned() {
116 return _listener !is null;
117 }
118 /// assign delegate
119 final void opAssign(slot_t listenerDelegate) {
120 _listener = listenerDelegate;
121 }
122 /// assign object implementing interface
123 final void opAssign(T1 listenerObject) {
124 _listener = &(__traits(getMember, listenerObject, __traits(allMembers, T1)[0]));
125 }
126 final return_t opCall(params_t params) {
127 static if (is(return_t == void)) {
128 if (_listener !is null)
129 _listener(params);
130 } else {
131 if (_listener !is null)
132 return _listener(params);
133 return return_t.init;
134 }
135 }
136 final slot_t get() {
137 return _listener;
138 }
139 alias get this;
140 }
141
142 /// Single listener; implicitly specified return and parameter types
143 struct Listener(RETURN_T, T1...)
144 {
145 alias slot_t = RETURN_T delegate(T1);
146 private slot_t _listener;
147 /// returns true if listener is assigned
148 final bool assigned() {
149 return _listener !is null;
150 }
151 final void opAssign(slot_t listener) {
152 _listener = listener;
153 }
154 final RETURN_T opCall(T1 params) {
155 static if (is (RETURN_T == void)) {
156 if (_listener !is null)
157 _listener(params);
158 } else {
159 if (_listener !is null)
160 return _listener(params);
161 return RETURN_T.init;
162 }
163 }
164 final slot_t get() {
165 return _listener;
166 }
167 /// disconnect all listeners
168 final void clear() {
169 _listener = null;
170 }
171 alias get this;
172 }
173
174 /// Multiple listeners; implicitly specified return and parameter types
175 struct Signal(T1) if (is(T1 == interface) && __traits(allMembers, T1).length == 1) {
176 alias return_t = ReturnType!(__traits(getMember, T1, __traits(allMembers, T1)[0]));
177 alias params_t = ParameterTypeTuple!(__traits(getMember, T1, __traits(allMembers, T1)[0]));
178 alias slot_t = return_t delegate(params_t);
179 private Collection!slot_t _listeners;
180
181 this(ref Signal!T1 v) {
182 _listeners.addAll(v._listeners);
183 }
184
185 /// returns true if listener is assigned
186 final bool assigned() {
187 return _listeners.length > 0;
188 }
189 /// replace all previously assigned listeners with new one (if null passed, remove all listeners)
190 final void opAssign(slot_t listener) {
191 _listeners.clear();
192 if (listener !is null)
193 _listeners ~= listener;
194 }
195 /// replace all previously assigned listeners with new one (if null passed, remove all listeners)
196 final void opAssign(T1 listener) {
197 opAssign(&__traits(getMember, listener, __traits(allMembers, T1)[0]));
198 }
199 /// call all listeners; for signals having non-void return type, stop iterating when first return value is nonzero
200 static if (is (return_t == void)) {
201 // call all listeners
202 final return_t opCall(params_t params) {
203 foreach(listener; _listeners)
204 listener(params);
205 }
206 // call all listeners
207 final return_t emit(params_t params) {
208 foreach(listener; _listeners)
209 listener(params);
210 }
211 } else {
212 // call listeners, stop calling on first non-zero -- if (res) return res
213 final return_t opCall(params_t params) {
214 return emit(params);
215 }
216 // call listeners, stop calling on first non-zero -- if (res) return res
217 final return_t emit(params_t params) {
218 foreach(listener; _listeners) {
219 return_t res = listener(params);
220 if (res)
221 return res;
222 }
223 return return_t.init;
224 }
225 }
226 /// add listener
227 final void connect(slot_t listener) {
228 _listeners ~= listener;
229 }
230 /// remove listener
231 final void disconnect(slot_t listener) {
232 _listeners -= listener;
233 }
234 /// add listener - as interface member
235 final void connect(T1 listener) {
236 connect(&__traits(getMember, listener, __traits(allMembers, T1)[0]));
237 }
238 /// add listener - as interface member
239 final void disconnect(T1 listener) {
240 disconnect(&__traits(getMember, listener, __traits(allMembers, T1)[0]));
241 }
242 /// disconnect all listeners
243 final void clear() {
244 _listeners.clear();
245 }
246
247 /// Provides `~=` syntax for connecting to this `Signal`
248 void opOpAssign(string op, T1)(T1 listener)
249 if(op == "~=")
250 {
251 connect(listener);
252 }
253 }
254
255 /// Multiple listeners; implicitly specified return and parameter types
256 struct Signal(RETURN_T, T1...)
257 {
258 alias slot_t = RETURN_T delegate(T1);
259 private Collection!slot_t _listeners;
260 /// returns true if listener is assigned
261 final bool assigned() {
262 return _listeners.length > 0;
263 }
264 /// replace all previously assigned listeners with new one (if null passed, remove all listeners)
265 final void opAssign(slot_t listener) {
266 _listeners.clear();
267 if (listener !is null)
268 _listeners ~= listener;
269 }
270 /// call all listeners; for signals having non-void return type, stop iterating when first return value is nonzero
271 static if (is (RETURN_T == void)) {
272 // call all listeners
273 final RETURN_T opCall(T1 params) {
274 foreach(listener; _listeners)
275 listener(params);
276 }
277 // call all listeners
278 final RETURN_T emit(T1 params) {
279 foreach(listener; _listeners)
280 listener(params);
281 }
282 } else {
283 // call listeners, stop calling on first non-zero -- if (res) return res
284 final RETURN_T opCall(T1 params) {
285 return emit(params);
286 }
287 // call listeners, stop calling on first non-zero -- if (res) return res
288 final RETURN_T emit(T1 params) {
289 foreach(listener; _listeners) {
290 RETURN_T res = listener(params);
291 if (res)
292 return res;
293 }
294 return RETURN_T.init;
295 }
296 }
297 /// add listener
298 final void connect(slot_t listener) {
299 _listeners ~= listener;
300 }
301 /// remove listener
302 final void disconnect(slot_t listener) {
303 _listeners -= listener;
304 }
305 /// disconnect all listeners
306 final void clear() {
307 _listeners.clear();
308 }
309
310 /// Provides `~=` syntax for connecting to this `Signal`
311 void opOpAssign(string op, T1)(T1 listener)
312 if(op == "~=")
313 {
314 connect(listener);
315 }
316 }
317
318 unittest
319 {
320 interface IHandler
321 {
322 void onHandler();
323 }
324
325 class Component
326 {
327 Signal!IHandler signal;
328
329 void doStuff()
330 {
331 signal();
332 }
333 }
334
335 class Handler : IHandler
336 {
337 this()
338 {
339 c = new Component();
340 c.signal = this;
341 }
342
343 void onHandler()
344 {
345 res = 1;
346 }
347
348 int res = 0;
349 Component c;
350 }
351
352 scope Handler h = new Handler();
353 h.c.doStuff();
354 assert(h.c.signal.assigned);
355 assert(h.res == 1);
356 }