1 module spasm.event;
2 
3 import spasm.types;
4 version (LDC)
5 import ldc.attributes;
6 
7 @trusted
8 extern(C)
9 export
10 @assumeUsed
11 nothrow
12 void domEvent(uint ctx, uint fun, EventType type) {
13   // NOTE: since all the Event structs (MouseEvent, KeyboardEvent)
14   // are all just empty structs with only static functions,
15   // the type signature of the delegate doesn't really matter much,
16   // as long as there is one param
17   // This allows us to sidestep the fact that we should have had
18   // one event handler per event type
19   // Type safety is ensured with static asserts in addEventListenerTyped
20   static struct Handler
21   {
22     union
23     {
24       void delegate(Event) nothrow handle;
25       struct
26       {
27         void* contextPtr;
28         void* funcPtr;
29       }
30     }
31   }
32   Handler c;
33   c.contextPtr = cast(void*) ctx;
34   c.funcPtr = cast(void*) fun;
35   c.handle(Event());
36 }
37 
38 @safe:
39 nothrow:
40 
41 private extern(C) {
42   nothrow:
43   bool getEventBool(string prop);
44   uint getEventInt(string prop);
45   string getEventString(string prop);
46   void removeEventListener(Handle node, ListenerType type, uint ctx, uint fun, EventType type);
47   void addEventListener(Handle node, ListenerType type, uint ctx, uint fun, EventType type);
48 }
49 enum eventemitter;
50 
51 // TODO: check if these are inlined
52 mixin template BoolProperty(alias name) {
53   mixin("static bool "~name~"() { return getEventBool(\""~name~"\");}");
54 }
55 
56 mixin template IntProperty(alias name) {
57   mixin("static int "~name~"() { return getEventInt(\""~name~"\");}");
58 }
59 
60 mixin template StringProperty(alias name) {
61   mixin("static string "~name~"() { return getEventString(\""~name~"\");}");
62 }
63 
64 struct Event {
65   nothrow:
66   mixin BoolProperty!("bubbles");
67   mixin BoolProperty!("isComposing");
68   mixin IntProperty!("eventPhase");
69 }
70 
71 struct KeyboardEvent {
72   nothrow:
73   mixin BoolProperty!("altKey");
74   mixin StringProperty!("key");
75 }
76 
77 struct InputEvent {
78   nothrow:
79   mixin BoolProperty!("isComposing");
80   // mixin StringProperty!("inputType");
81 }
82 
83 struct MouseEvent {
84   nothrow:
85   mixin IntProperty!("x");
86   mixin IntProperty!("y");
87   mixin IntProperty!("offsetX");
88   mixin IntProperty!("offsetY");
89 }
90 
91 struct FocusEvent {
92 }
93 
94 template ToEvent(EventType type) {
95   import spasm.ct : capitalize;
96   static if (type == EventType.event)
97     alias ToEvent = Event;
98   else {
99     // TODO: really want to use phobos functions to extract name but prevented by https://issues.dlang.org/show_bug.cgi?id=19268
100     static foreach (t; __traits(allMembers, EventType))
101     {
102       static if (__traits(getMember, EventType, t) == type)
103         mixin("alias ToEvent = "~capitalize!(t)~"Event;");
104     }
105   }
106 }
107 
108 auto toTuple(Delegate)(Delegate d) {
109   import spasm.ct : tuple;
110   auto ctx = cast(uint)d.ptr;
111   auto func = cast(uint)d.funcptr;
112   return tuple!("ctx","func")(ctx,func);
113 }
114 
115 EventType toEventType(Node)(ListenerType listener) {
116   with (ListenerType) {
117     final switch(listener) {
118     case click:
119       return EventType.mouse;
120     case input:
121       return EventType.input;
122     case change:
123       return EventType.event;
124     case keyup:
125     case keydown:
126     case keypress:
127       return EventType.keyboard;
128     case dblclick:
129       return EventType.mouse;
130     case focus:
131       return EventType.focus;
132     case blur:
133       return EventType.event;
134     case mouseup:
135     case mousedown:
136     case mousemove:
137       return EventType.mouse;
138       }
139   }
140 }
141 
142 template toListenerType(string t) {
143   mixin("enum toListenerType = ListenerType."~t~";");
144 }
145 
146 
147 // TODO: please combine with function below
148 auto removeEventListenerTyped(string name, T)(Handle node, auto ref T t) {
149   import std.traits : fullyQualifiedName, Parameters;
150   import std.algorithm : findSplitAfter;
151   import spasm.ct : toLower;
152   // TODO: really want to use std.uni.toLower here but prevented by https://issues.dlang.org/show_bug.cgi?id=19268
153   enum type = toLower!(name[2..$]);
154   enum listenerType = toListenerType!type;
155   auto delPtr = &__traits(getMember, t, name);
156   enum eventType = listenerType.toEventType!T;
157   alias Event = ToEvent!eventType;
158   alias delParams = Parameters!(typeof(delPtr));
159   static if (delParams.length != 1)
160     static assert(false, "Expected 1 param of type "~Event.stringof~" in "~fullyQualifiedName!T~"."~name);
161   else static if (!is(delParams[0] == Event))
162     static assert(false,
163         "Expected param 1 of type " ~ Event.stringof ~ " instead of "
164         ~ delParams[0].stringof ~ " in ..." ~ name); // TODO: next line is not working so using ... instead. Due to bug https://issues.dlang.org/show_bug.cgi?id=19268
165     // static assert(false, "Expected param 1 of type "~Event.stringof~" instead of "~delParams[0].stringof~" in "~fullyQualifiedName!T~"."~name);
166   removeEventListener(node, listenerType, toTuple(delPtr).expand, eventType);
167 }
168 
169 auto addEventListenerTyped(string name, T)(Handle node, auto ref T t) {
170   import std.traits : fullyQualifiedName, Parameters;
171   import std.algorithm : findSplitAfter;
172   import spasm.ct : toLower;
173   // TODO: really want to use std.uni.toLower here but prevented by https://issues.dlang.org/show_bug.cgi?id=19268
174   enum type = toLower!(name[2..$]);
175   enum listenerType = toListenerType!type;
176   auto delPtr = &__traits(getMember, t, name);
177   enum eventType = listenerType.toEventType!T;
178   alias Event = ToEvent!eventType;
179   alias delParams = Parameters!(typeof(delPtr));
180   static if (delParams.length != 1)
181     static assert(false, "Expected 1 param of type "~Event.stringof~" in "~fullyQualifiedName!T~"."~name);
182   else static if (!is(delParams[0] == Event))
183     static assert(false,
184         "Expected param 1 of type " ~ Event.stringof ~ " instead of "
185         ~ delParams[0].stringof ~ " in ..." ~ name); // TODO: next line is not working so using ... instead. Due to bug https://issues.dlang.org/show_bug.cgi?id=19268
186     // static assert(false, "Expected param 1 of type "~Event.stringof~" instead of "~delParams[0].stringof~" in "~fullyQualifiedName!T~"."~name);
187   addEventListener(node, listenerType, toTuple(delPtr).expand, eventType);
188 }
189 
190 struct EventEmitter {
191   nothrow:
192   void delegate() plain = null;
193   void delegate(size_t) addr = null;
194   void add(void delegate() nothrow @safe del) {
195     plain = del;
196   }
197   void add(void delegate(size_t) nothrow @safe del) {
198     addr = del;
199   }
200 }
201 
202 mixin template Slot(string type) {
203   mixin("@eventemitter EventEmitter "~type~";");
204 }
205 
206 auto emit(T)(ref T t, EventEmitter emitter) {
207   if (emitter.plain != null)
208     emitter.plain();
209   if (emitter.addr != null) {
210     size_t addr = cast(size_t)(&t);
211     emitter.addr(addr);
212   }
213 }
214 
215 auto emit(EventEmitter emitter, size_t addr) {
216   if (emitter.plain != null)
217     emitter.plain();
218   if (emitter.addr != null) {
219     emitter.addr(addr);
220   }
221 }
222