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 }