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