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 }