1 /*
2                                     __
3                                    / _|
4   __ _ _   _ _ __ ___  _ __ __ _  | |_ ___  ___ ___
5  / _` | | | | '__/ _ \| '__/ _` | |  _/ _ \/ __/ __|
6 | (_| | |_| | | | (_) | | | (_| | | || (_) \__ \__ \
7  \__,_|\__,_|_|  \___/|_|  \__,_| |_| \___/|___/___/
8 
9 Copyright (C) 2020 Aurora Free Open Source Software.
10 Copyright (C) 2020 João Lourenço <com (dot) gmail (at) jlourenco5691, backward>
11 
12 This file is part of the Aurora Free Open Source Software. This
13 organization promote free and open source software that you can
14 redistribute and/or modify under the terms of the GNU Lesser General
15 Public License Version 3 as published by the Free Software Foundation or
16 (at your option) any later version approved by the Aurora Free Open Source
17 Software Organization. The license is available in the package root path
18 as 'LICENSE' file. Please review the following information to ensure the
19 GNU Lesser General Public License version 3 requirements will be met:
20 https://www.gnu.org/licenses/lgpl.html .
21 
22 Alternatively, this file may be used under the terms of the GNU General
23 Public License version 3 or later as published by the Free Software
24 Foundation. Please review the following information to ensure the GNU
25 General Public License requirements will be met:
26 https://www.gnu.org/licenses/gpl-3.0.html.
27 
28 NOTE: All products, services or anything associated to trademarks and
29 service marks used or referenced on this file are the property of their
30 respective companies/owners or its subsidiaries. Other names and brands
31 may be claimed as the property of others.
32 
33 For more info about intellectual property visit: aurorafoss.org or
34 directly send an email to: contact (at) aurorafoss.org .
35 */
36 
37 module aurorafw.event.eventsystem.event;
38 
39 version (unittest) import aurorafw.unit;
40 
41 /** Main event class
42  *
43  * This is class is abstract and only used to establish some default functions
44  *     to every extended Event. \
45  */
46 @safe
47 public abstract class Event
48 {
49 public:
50 	@safe pure
51 	abstract @property string eventType() const;
52 
53 	@safe
54 	override string toString() const
55 	{
56 		return eventType;
57 	}
58 
59 	/** Compare two EventTypes
60 	 *
61 	 * Params:
62 	 *     otherType = type to be compared
63 	 *
64 	 * Returns:
65 	 *     `true` if both types are equal. \
66 	 *     `false` otherwise
67 	 */
68 	@safe pure
69 	bool isEventTypeOf(in string otherType) const
70 	{
71 		return eventType == otherType;
72 	}
73 
74 	// defaults to false
75 	bool handled;
76 }
77 
78 /** Default EventType struct
79  *
80  * Used as the main data storage for the events. \
81  * You must define this as an UDA when declaring your custom events. \
82  *
83  * Examples:
84  * --------------------
85  * @EventType("MyEvent")
86  * class MyEvent : Event { // ... }
87  * --------------------
88  *
89  * Note: To use the `basicEventType` mixin template you need to define an
90  *     EventType UDA otherwise it'll not work! You CAN create your own EventType
91  *     version without UDAs but keep in mind you'll have to implement your custom
92  *     logic of the abstract methods. The EventDispatcher is not afected by this.
93  */
94 @safe pure
95 struct EventType
96 {
97 	const string name;
98 }
99 
100 /** Quick way to implement your default abstract and static methods
101  *
102  * Call this mixin template on your custom event. This will generate your getters
103  *     for the EventType.
104  *
105  * Examples:
106  * --------------------
107  * @EventType("MyEvent")
108  * class MyEvent : Event
109  * {
110  *     mixin basciEventType!MyEvent;
111  * }
112  * --------------------
113  *
114  * Note: You need to declare an EventType UDA otherwise this won't compile!
115  *
116  * Params:
117  *     T = class type extended from Event
118  */
119 mixin template basicEventType(T : Event)
120 {
121 public:
122 	@safe pure
123 	static @property string staticEventType()
124 	{
125 		import std.traits : getUDAs;
126 
127 		return getUDAs!(T, EventType)[0].name;
128 	}
129 
130 	@safe pure
131 	override @property string eventType() const
132 	{
133 		return staticEventType;
134 	}
135 }
136 
137 ///
138 @safe pure
139 @("Event: basicEventType")
140 unittest
141 {
142 	@EventType("MyEvent")
143 	class MyEvent : Event
144 	{
145 		mixin basicEventType!MyEvent;
146 	}
147 
148 	MyEvent event = new MyEvent();
149 	assertEquals(event.staticEventType, "MyEvent");
150 	assertEquals(event.staticEventType, event.eventType);
151 	assertTrue(event.isEventTypeOf(MyEvent.staticEventType));
152 }
153 
154 /** Generate generic callback functions
155  *
156  * Params:
157  *     T = class which sends the signal
158  *     E = event to look for
159  *     args = E var types and E var names
160  *
161  * Examples:
162  * --------------------
163  * @EventType("ClickEvent")
164  * class ClickEvent : Event
165  * {
166  *     this(in size_t x, in size_t y)
167  *     {
168  *         this.x = x;
169  *         this.y = y;
170  *     }
171  *     mixin basicEventType!ClickEvent;
172  * private :
173  *     const size_t x, y;
174  * }
175  *
176  * class Foo
177  * {
178  *     mixin genCallback!(const Foo, ClickEvent, const size_t, "x", const size_t, "y") onClicked;
179  *     // Note: you can leave the parameters blank, if you do, then the event
180  *     //     itself will be passed as the second parameter as const
181  * public :
182  *     void click(in size_t x, in size_t y)
183  *     {
184  *         // click stuff here
185  *         onClicked.emit(x, y);
186  *     }
187  *
188  *     void onEvent(Event event)
189  *     {
190  *         // handle your events here
191  *         auto dispacher = scoped!EventDispatcher(event);
192  *         dispacher.dispatch!ClickEvent(&onClicked.dispatch);
193  *     }
194  * }
195  *
196  * bool onFooClicked(in Foo sender, in size_t x, in size_t y)
197  * {
198  *     // do stuff here
199  *     assert([x, y] == [4, 5]);
200  *
201  *     // true to handle the event
202  *     // false to propagate
203  *     return true;
204  * }
205  *
206  * void main()
207  * {
208  *     Foo foo = new Foo();
209  *     foo.onClicked.connect(toDelegate(&onFooClicked));
210  *     foo.click(4,5); // callback!
211  * }
212  * --------------------
213  */
214 mixin template genCallback(T, E:
215 		Event, args...)
216 {
217 	static assert(is(T == class));
218 
219 	import std.typecons : scoped;
220 	import std.string : format;
221 
222 	/**
223 	 * if parameters as passed then use them as the callback delegate parameters,
224 	 * otherwise just pass the event itself
225 	 */
226 	static if (args.length)
227 	{
228 		import std.meta : AliasSeq, Filter, templateNot, Stride;
229 		import std.traits : isType;
230 		import aurorafw.event.eventsystem.event : toEventFormat, joinTypeName, stringTuple;
231 
232 		private alias _as = AliasSeq!args;
233 		private alias _types = Filter!(isType, _as);
234 		private enum _eventnames = toEventFormat!(Filter!(templateNot!isType, _as));
235 		private enum _names = Filter!(templateNot!isType, _as);
236 
237 		/**
238 		 * just check if types aren't followed by another type
239 		 * do not let the user insert args like (int, int, "foo", "bar")
240 		 */
241 		import std.typecons : Tuple;
242 
243 		static assert(is(Tuple!_types == Tuple!(Stride!(2, _as))),
244 				"Cannot have sequential types!");
245 	}
246 	else
247 	{
248 		private alias _types = E;
249 		private enum _eventnames = ",event";
250 	}
251 
252 public:
253 	@safe pure
254 	void connect(bool delegate(T, _types) dg)
255 	in (dg.funcptr !is null || dg.ptr !is null)
256 	{
257 		callback = dg;
258 	}
259 
260 	static if (args.length)
261 	{
262 		private alias ctorOverloads = __traits(getOverloads, E, "__ctor");
263 		private enum len = ctorOverloads.length;
264 		static foreach (j, t; ctorOverloads)
265 		{
266 			import std.traits : Parameters, isImplicitlyConvertible;
267 
268 			static foreach (i, type; _types)
269 			{
270 				static if (j == len - 1)
271 				{
272 					static if (_types.length != (Parameters!t).length)
273 					{
274 						static assert(_types.length == (Parameters!t).length,
275 								"Type(s) in %s are not valid in any of the %s ctor overloads!"
276 								.format(_types.stringof, E.stringof)
277 								~ " Failed on: types.length <> ctor_parameters.length (%s <> %s) at %s"
278 								.format(_types.length, (Parameters!t).length, typeof(t).stringof));
279 					}
280 					else static if ((!isImplicitlyConvertible!(type, (Parameters!t)[i])))
281 					{
282 						static assert(isImplicitlyConvertible!(type, (Parameters!t)[i]),
283 								"Type(s) in %s are not valid in any of the %s ctor overloads!"
284 								.format(_types.stringof, E.stringof)
285 								~ " Failed on: <%s> cannot convert to <%s> at %s"
286 								.format(type.stringof, (Parameters!t)[i].stringof, typeof(t).stringof));
287 					}
288 				}
289 			}
290 		}
291 		mixin(q{
292 			void emit(}
293 				~ joinTypeName!args ~ q{)
294 			{
295 				import std.string : format;
296 				mixin(q{ auto event = scoped!E(%s); }.format(stringTuple!_names));
297 				emit(event);
298 			}});
299 	}
300 	else static if (__traits(compiles, scoped!E()))
301 	{
302 		void emit()
303 		{
304 			auto event = scoped!E;
305 			onEvent(event);
306 		}
307 	}
308 
309 	/**
310 	 * in case the user wants to declare the event beforehand or if the user
311 	 *   wants a callback only with sender and the event but the event doesn't
312 	 *   have an empty ctor
313 	 */
314 	void emit(E event)
315 	in (event !is null, "%s cannot be null".format(E.stringof))
316 	{
317 		if (callback.funcptr !is null || callback.ptr !is null)
318 			onEvent(event);
319 	}
320 
321 protected:
322 	void dispatch(E event)
323 	{
324 		import std.string : format;
325 
326 		if (callback.funcptr !is null || callback.ptr !is null)
327 			mixin(q{
328 				event.handled = callback(this%s);
329 			}.format(_eventnames));
330 	}
331 
332 	bool delegate(T, _types) callback;
333 }
334 
335 /* This template is used **internaly** only!
336  *
337  * Generate a string with the format `,event.<args[0],event.args[1],...`
338  *
339  * Params:
340  *     args = string values to be formated
341  *
342  * Examples:
343  * --------------------
344  * enum str = toEventFormat!("a", "b", "c");
345  * --------------------
346  * --------------------
347  * static assert(toEventFormat!("a", "b"), ",event.a,event.b");
348  * --------------------
349  *
350  * Returns:
351  *     string `enum`
352  */
353 template toEventFormat(args...)
354 {
355 	@safe pure
356 	auto toEventFormat()
357 	{
358 		import std.array : appender;
359 
360 		auto ret = appender!string;
361 		foreach (var; args)
362 		{
363 			import std.string : format;
364 			import std.traits : isType;
365 
366 			static assert(!isType!var);
367 			static assert(is(typeof(var) == string));
368 			ret ~= ",event." ~ var;
369 		}
370 		return ret.data;
371 	}
372 }
373 
374 ///
375 @safe pure
376 @("Event: toEventFormat")
377 unittest
378 {
379 	assertEquals(toEventFormat!("a", "b"), (",event.a,event.b"));
380 }
381 
382 /* This is used internaly only!
383  *
384  * Generate function parameters with named types.
385  * Takes in var types and literal strings alternatively and joins them in a
386  *     string.
387  *
388  * Examples:
389  * --------------------
390  * enum params = joinTypeName!(int,"var1",const char,"var2",string,"var3");
391  * assert(params == "int var1,const(char) var2,string var3");
392  * --------------------
393  *
394  * Returns:
395  *     `string` of named parameters
396  */
397 template joinTypeName(args...)
398 {
399 	@safe pure
400 	auto joinTypeName()
401 	{
402 		import std.array : appender;
403 		import std.meta : AliasSeq, Filter, templateNot;
404 		import std.traits : isType;
405 		import std.string : format;
406 
407 		alias _as = AliasSeq!args;
408 		alias types = Filter!(isType, _as);
409 		enum names = Filter!(templateNot!isType, _as);
410 
411 		static assert(names.length == types.length,
412 				"Types length and Names length do not match! (%s types and %s names)"
413 				.format(types.length, names.length));
414 
415 		auto ret = appender!string;
416 		foreach (i, type; types)
417 		{
418 			ret ~= type.stringof ~ " " ~ names[i] ~ ",";
419 		}
420 		return ret.data[0 .. $ - 1];
421 	}
422 }
423 
424 ///
425 @safe pure
426 @("Event: joinTypeName")
427 unittest
428 {
429 	assertEquals(joinTypeName!(int, "foo", string, "bar"), "int foo,string bar");
430 	assertEquals(joinTypeName!(const int, "i", immutable char, "c"), "const(int) i,immutable(char) c");
431 }
432 
433 /* Joins strings with a coma
434  *
435  * Takes in multiple strings and joins them into one string
436  *     separated by comas;
437  *
438  * Examples:
439  * --------------------
440  * assertEquals(stringTuple!("hi", "there"), "hi,there");
441  * --------------------
442  *
443  * Returns:
444  *     `string` of joined string separated by comas
445  */
446 template stringTuple(args...)
447 {
448 	@safe pure
449 	auto stringTuple()
450 	{
451 		import std.array : appender;
452 		import std.string : format;
453 		import std.traits : isSomeString, isTypeTuple;
454 
455 		foreach (v; args)
456 		{
457 			static assert(isSomeString!(typeof(v)),
458 					"Args must be a string! (%s of type \'%s\' is not a string)"
459 					.format(v, typeof(v).stringof));
460 		}
461 
462 		auto ret = appender!string;
463 		foreach (str; args)
464 			ret ~= str ~ ",";
465 		return ret.data[0 .. $ - 1];
466 	}
467 }
468 
469 ///
470 @safe pure
471 @("Event: stringTuple")
472 unittest
473 {
474 	assertEquals(stringTuple!("hi", "there"), "hi,there");
475 }
476 
477 private version (unittest)
478 {
479 	@safe
480 	bool onFooEvent(in Foo sender, in int a, int b, int c)
481 	{
482 		assertEquals([a, b, c], [2, 4, 5]);
483 
484 		return true;
485 	}
486 
487 	@safe
488 	bool onBarEvent(in Foo sender, in BarEvent event)
489 	{
490 		assertEquals(event.toString, "BarEvent");
491 		assertEquals([event.x, event.y], [2, 4]);
492 
493 		// handle
494 		return true;
495 	}
496 
497 	@EventType("FooEvent")
498 	@safe class FooEvent : Event
499 	{
500 		mixin basicEventType!FooEvent;
501 
502 	public:
503 		this()
504 		{
505 			a = b = c = int.init;
506 		}
507 
508 		this(in int a, in int b, in int c)
509 		{
510 			this.a = a;
511 			this.b = b;
512 			this.c = c;
513 		}
514 
515 		const int a, b, c;
516 	}
517 
518 	@EventType("BarEvent")
519 	@safe class BarEvent : Event
520 	{
521 		mixin basicEventType!BarEvent;
522 
523 	public:
524 		this(in size_t x, in size_t y)
525 		{
526 			this.x = x;
527 			this.y = y;
528 		}
529 
530 		const size_t x, y;
531 	}
532 
533 	@EventType("BazEvent")
534 	@safe class BazEvent : BarEvent
535 	{
536 		mixin basicEventType!BazEvent;
537 
538 	public:
539 		this()
540 		{
541 			super(3, 7);
542 		}
543 	}
544 
545 	class Foo
546 	{
547 		mixin genCallback!(const Foo, FooEvent, const int, "a", int, "b", int, "c") onFoo;
548 		mixin genCallback!(const Foo, BarEvent) onBar;
549 		mixin genCallback!(Foo, BazEvent) onBaz;
550 
551 	public:
552 		this(in int a, in int b, in int c)
553 		{
554 			this.a = a;
555 			this.b = b;
556 			this.c = c;
557 
558 			onBaz.connect(delegate bool(Foo, BazEvent event) { assertEquals([event.x, event.y], [3, 7]); return true; });
559 		}
560 
561 		void bar()
562 		{
563 			import std.typecons : scoped;
564 
565 			auto event = scoped!BarEvent(a, b);
566 			onBar.emit(event);
567 
568 			assertTrue(event.handled);
569 		}
570 
571 		void foo()
572 		{
573 			onFoo.emit(a, b, c);
574 		}
575 
576 		void baz()
577 		{
578 			onBaz.emit();
579 		}
580 
581 	private:
582 		@system
583 		void onEvent(Event event)
584 		{
585 			import std.typecons : scoped;
586 			import aurorafw.event.eventsystem.eventdispatcher : EventDispatcher;
587 
588 			auto ed = new EventDispatcher(event);
589 			ed.dispatch!FooEvent(&onFoo.dispatch);
590 			ed.dispatch!BarEvent(&onBar.dispatch);
591 			ed.dispatch!BazEvent(&onBaz.dispatch);
592 		}
593 
594 		const int a, b, c;
595 	}
596 }
597 
598 @safe
599 @("Event: event ctor")
600 unittest
601 {
602 	FooEvent event = new FooEvent(1, 2, 3);
603 	assertEquals([1, 2, 3], [event.a, event.b, event.c]);
604 }
605 
606 @system
607 @("Event: connect to a delegate")
608 unittest
609 {
610 	Foo foo = new Foo(2, 4, 5);
611 	foo.foo(); // nothing fires
612 	foo.baz(); // fires the default onBaz event
613 
614 	import std.functional : toDelegate;
615 
616 	foo.onFoo.connect(toDelegate(&onFooEvent));
617 	foo.onBar.connect(toDelegate(&onBarEvent));
618 
619 	foo.foo(); // fires onFooEvent
620 	foo.bar(); // fires onBarEvent
621 }