1 module spasm.dom; 2 3 import spasm.types; 4 import spasm.dom; 5 import spasm.ct; 6 import std.traits : hasMember, isAggregateType; 7 import std.traits : TemplateArgsOf, staticMap, isPointer, PointerTarget, getUDAs, EnumMembers, isInstanceOf, isBasicType, isIntegral; 8 import std.traits : getSymbolsByUDA, getUDAs; 9 import std.meta : Filter, AliasSeq, ApplyLeft, ApplyRight; 10 import spasm.css; 11 import spasm.node; 12 import spasm.event; 13 import std.meta : staticIndexOf; 14 import spasm.array; 15 import spasm.rt.array; 16 17 @safe: 18 nothrow: 19 20 version (unittest) { 21 import spasm.sumtype; 22 import std.array : Appender; 23 import std.algorithm : remove, countUntil; 24 import std.array : insertInPlace; 25 class UnittestDomNode { 26 alias Property = SumType!(string,int,bool,double); 27 alias Attribute = SumType!(string,int); 28 NodeType type; 29 Handle handle; 30 UnittestDomNode parent; 31 Property[string] properties; 32 Attribute[string] attributes; 33 string[] classes; 34 Appender!(UnittestDomNode[]) children; 35 this(NodeType type, Handle handle) nothrow { this.type = type; this.handle = handle; } 36 void setAttribute(T)(string name, T value) nothrow @trusted { 37 attributes[name] = Attribute(value); 38 } 39 Attribute getAttribute(string name) nothrow { 40 return attributes[name]; 41 } 42 void setProperty(T)(string name, T value) nothrow @trusted { 43 properties[name] = Property(value); 44 } 45 Property getProperty(string name) nothrow { 46 return properties[name]; 47 } 48 void toString(scope void delegate(const(char)[]) @safe sink) @trusted { 49 import std.algorithm : each; 50 import std.format : formattedWrite, format; 51 import std.array : join; 52 import std.conv : to; 53 if (type != NodeType.root) { 54 sink.formattedWrite("<%s", type); 55 // write attributes and properties 56 if (classes.length > 0) { 57 sink.formattedWrite(" class=\"%s\"", classes.join(" ")); 58 } 59 foreach (kv; properties.byKeyValue()) { 60 sink.formattedWrite(" %s=%s", kv.key, spasm.sumtype.match!((string s)=>format(`"%s"`,s),(i)=>i.to!string)(kv.value)); 61 } 62 foreach (kv; attributes.byKeyValue()) { 63 sink.formattedWrite(" %s=%s", kv.key, spasm.sumtype.match!((string s)=>format(`"%s"`,s),(int i)=>i.to!string)(kv.value)); 64 } 65 sink(">"); 66 } 67 children.data.each!(c => c.toString(sink)); 68 // write children 69 if (type != NodeType.root) 70 sink.formattedWrite("</%s>", type); 71 } 72 } 73 Appender!(UnittestDomNode[]) unittest_dom_nodes; 74 private auto getNode(Handle node) { 75 assert(node > 0); 76 return unittest_dom_nodes.data[node - 1]; 77 } 78 private auto assumeNoException(Block)(lazy Block block) { 79 try { 80 block(); 81 } catch(Exception e) { 82 assert(0, e.msg); 83 } 84 } 85 extern(C) { 86 Handle createElement(NodeType type) { 87 uint idx = cast(uint)unittest_dom_nodes.data.length; 88 unittest_dom_nodes.put(new UnittestDomNode(type, idx + 1)); 89 return idx + 1; 90 } 91 void addClass(Handle node, string className) { 92 node.getNode().classes ~= className; 93 } 94 void setProperty(Handle node, string prop, string value) { 95 node.getNode().setProperty(prop, value); 96 } 97 void removeChild(Handle childPtr) @trusted { 98 import std.algorithm : remove; 99 import std.algorithm : countUntil; 100 auto child = childPtr.getNode(); 101 auto childIdx = child.parent.children.data.countUntil!(c => c is child); 102 assert(childIdx >= 0); 103 child.parent.children.data.remove(childIdx); 104 child.parent.children.shrinkTo(child.parent.children.data.length - 1).assumeNoException(); 105 } 106 void unmount(Handle childPtr) { 107 removeChild(childPtr); 108 } 109 void appendChild(Handle parentPtr, Handle childPtr) @trusted { 110 auto parent = parentPtr.getNode(); 111 auto child = childPtr.getNode(); 112 if (child.parent) { 113 auto childIdx = child.parent.children.data.countUntil!(c => c is child); 114 if (childIdx > 0) { 115 child.parent.children.data.remove(childIdx); 116 child.parent.children.shrinkTo(child.parent.children.data.length - 1).assumeNoException(); 117 } 118 } 119 child.parent = parent; 120 parent.children.put(child); 121 } 122 void insertBefore(Handle parentPtr, Handle childPtr, Handle siblingPtr) @trusted { 123 auto parent = parentPtr.getNode(); 124 auto child = childPtr.getNode(); 125 auto sibling = siblingPtr.getNode(); 126 if (child.parent) { 127 auto childIdx = child.parent.children.data.countUntil!(c => c is child); 128 if (childIdx > 0) { 129 child.parent.children.data.remove(childIdx); 130 child.parent.children.shrinkTo(child.parent.children.data.length - 1).assumeNoException(); 131 } 132 } 133 child.parent = parent; 134 auto siblingIdx = parent.children.data.countUntil!(c => c is sibling); 135 assert(siblingIdx >= 0); 136 parent.children.put(child); 137 auto arr = parent.children.data; 138 foreach(i; 0 .. arr.length - siblingIdx) 139 arr[arr.length - 1 - i] = arr[arr.length - 2 - i]; 140 arr[siblingIdx] = child; 141 } 142 void setAttribute(Handle node, string attr, string value) { 143 node.getNode().setAttribute(attr, value); 144 } 145 void setAttributeInt(Handle node, string attr, int value) { 146 node.getNode().setAttribute(attr, value); 147 } 148 void setPropertyBool(Handle node, string prop, bool value) { 149 node.getNode().setProperty(prop, value); 150 } 151 void setPropertyInt(Handle node, string prop, int value) { 152 node.getNode().setProperty(prop, value); 153 } 154 void setPropertyDouble(Handle node, string prop, double value) { 155 node.getNode().setProperty(prop, value); 156 } 157 void innerText(Handle nodePtr, string text) { 158 } 159 void removeClass(Handle node, string className) { 160 import std.algorithm : remove; 161 auto n = node.getNode(); 162 n.classes = n.classes.remove!(i => i==className); 163 } 164 void changeClass(Handle node, string className, bool on) { 165 import std.algorithm : any; 166 auto n = node.getNode(); 167 if (on && !n.classes.any!(c => c == className)) 168 n.classes ~= className; 169 else if (!on) 170 removeClass(node, className); 171 } 172 Optional!string getProperty(Handle node, string name) { 173 auto str = node.getNode().getProperty(name).trustedGet!string; 174 return some(str); 175 } 176 Optional!int getPropertyInt(Handle node, string prop) { 177 return some(node.getNode().getProperty(prop).trustedGet!int); 178 } 179 Optional!bool getPropertyBool(Handle node, string prop) { 180 return some(node.getNode().getProperty(prop).trustedGet!bool); 181 } 182 Optional!double getPropertyDouble(Handle node, string prop) { 183 return some(node.getNode().getProperty(prop).trustedGet!double); 184 } 185 void setSelectionRange(Handle node, uint start, uint end) { 186 } 187 void addCss(string css) { 188 } 189 } 190 } else { 191 private extern(C) { 192 Handle createElement(NodeType type); 193 void addClass(Handle node, string className); 194 void setProperty(Handle node, string prop, string value); 195 void removeChild(Handle childPtr); 196 void unmount(Handle childPtr); 197 void appendChild(Handle parentPtr, Handle childPtr); 198 void insertBefore(Handle parentPtr, Handle childPtr, Handle sibling); 199 void setAttribute(Handle nodePtr, string attr, string value); 200 void setAttributeInt(Handle nodePtr, string attr, int value); 201 void setPropertyBool(Handle nodePtr, string attr, bool value); 202 void setPropertyInt(Handle nodePtr, string attr, int value); 203 void setPropertyDouble(Handle nodePtr, string attr, double value); 204 void innerText(Handle nodePtr, string text); 205 void removeClass(Handle node, string className); 206 void changeClass(Handle node, string className, bool on); 207 } 208 209 extern(C) { 210 string getProperty(Handle node, string prop); 211 int getPropertyInt(Handle node, string prop); 212 bool getPropertyBool(Handle node, string prop); 213 void getPropertyDouble(Handle nodePtr, string prop); 214 void setSelectionRange(Handle node, uint start, uint end); 215 void addCss(string css); 216 } 217 } 218 219 import spasm.bindings.dom : Document; 220 import spasm.bindings.html : Window; 221 222 @trusted ref auto undefined() { 223 static __gshared u = Any(JsHandle(0)); 224 return u; 225 } 226 @trusted ref auto document() { 227 static __gshared d = Document(JsHandle(1)); 228 return d; 229 } 230 @trusted ref auto window() { 231 static __gshared w = Window(JsHandle(2)); 232 return w; 233 } 234 235 void unmount(T)(auto ref T t) if (is(T : SumType!(Types), Types...)) { 236 t.match!(unmount); 237 } 238 239 void unmount(T)(auto ref T t) if (hasMember!(T, "node")) { 240 unmount(t.node.node); 241 static if (hasMember!(T, "node")) 242 t.node.mounted = false; 243 t.propagateOnUnmount(); 244 } 245 246 auto removeChild(T)(T* t) if (is(T : SumType!(Types), Types...)) { 247 unmount(*t); 248 } 249 250 auto removeChild(T)(auto ref T t) if (hasMember!(T,"node")) { 251 removeChild(t.node.node); 252 static if (hasMember!(T, "node")) 253 t.node.mounted = false; 254 t.propagateOnUnmount(); 255 } 256 257 auto focus(T)(auto ref T t) if (hasMember!(T,"node")) { 258 t.node.node.focus(); 259 } 260 261 auto renderBefore(T, Ts...)(Handle parent, auto ref T t, Handle sibling, auto ref Ts ts) { 262 static if (is(T : SumType!Types, Types...)) { 263 t.match!((scope ref i) => renderBefore(parent, i, sibling, ts)); 264 } else { 265 if (parent == invalidHandle) 266 return; 267 renderIntoNode(parent, t, ts); 268 static if (hasMember!(T, "node")) { 269 parent.insertBefore(t.node.node, sibling); 270 t.node.mounted = true; 271 } 272 t.propagateOnMount(); 273 } 274 } 275 276 auto render(T, Ts...)(Handle parent, auto ref T t, auto ref Ts ts) @trusted { 277 static if (is(T : SumType!Types, Types...)) { 278 t.match!((scope ref i) => render(parent, i, ts)); 279 } else { 280 if (parent == invalidHandle) 281 return; 282 renderIntoNode(parent, t, ts); 283 static if (hasMember!(T, "node")) { 284 if (!t.getNamedNode().mounted) { 285 parent.appendChild(t.getNamedNode.node); 286 t.getNamedNode().mounted = true; 287 } 288 } 289 t.propagateOnMount(); 290 } 291 } 292 293 import std.traits : isFunction; 294 auto propagateOnMount(T)(auto ref T t) { 295 static foreach (c; getChildren!T) 296 __traits(getMember, t, c).propagateOnMount(); 297 static if (hasMember!(T, "onMount") && isFunction!(T.onMount)) 298 t.onMount(); 299 } 300 301 auto propagateOnUnmount(T)(auto ref T t) 302 { 303 static foreach (c; getChildren!T) 304 __traits(getMember, t, c).propagateOnMount(); 305 static if (hasMember!(T, "onUnmount") && isFunction!(T.onUnmount)) 306 t.onUnmount(); 307 } 308 309 auto remount(string field, Parent)(auto ref Parent parent) { 310 import std.traits : hasUDA; 311 import std.meta : AliasSeq; 312 alias fields = AliasSeq!(__traits(allMembers, Parent)); 313 alias idx = staticIndexOf!(field,fields); 314 static if (fields.length > idx+1) { 315 static foreach(child; fields[idx+1..$]) { 316 static if (hasUDA!(__traits(getMember, Parent, child), spasm.types.child)) { 317 if (__traits(getMember, parent, child).node.mounted) 318 return renderBefore(parent.node.node, __traits(getMember, parent, field), __traits(getMember, parent, child).node.node, parent); 319 } 320 } 321 } 322 return render(parent.node.node, __traits(getMember, parent, field), parent); 323 } 324 325 template isValue(alias t) { 326 enum isValue = __traits(compiles, { enum p = t; }); 327 } 328 329 template createParameterTuple(Params...) { 330 auto createParameterTuple(Ts...)(auto ref Ts ts) { 331 import std.traits : TemplateArgsOf, staticMap; 332 import spasm.spa : Param; 333 template extractName(Arg) { 334 enum extractName = Arg.Name; 335 } 336 static auto extractField(alias sym)(auto ref Ts ts) @trusted { 337 enum name = sym.stringof; 338 enum literal = isValue!(sym); 339 static if (isValue!(sym)) { 340 static if (isBasicType!(typeof(sym))) { 341 typeof(sym) val = sym; 342 return val; 343 } else { 344 __gshared static auto val = sym; 345 return &val; 346 } 347 } else { 348 alias ContainingType = AliasSeq!(__traits(parent, sym))[0]; 349 template isContainingType(ContainingType, T) { 350 enum isContainingType = is(ContainingType == T); 351 } 352 enum index = indexOfPred!(ApplyLeft!(isContainingType, ContainingType), AliasSeq!Ts); 353 return &__traits(getMember, ts[index], name); 354 } 355 } 356 static auto extractFields(Args...)(auto ref Ts ts) @safe if (Args.length > 0) { 357 static if (Args.length > 1) 358 return tuple(extractField!(TemplateArgsOf!(Args[0])[1])(ts), extractFields!(Args[1..$])(ts).expand); 359 else 360 return tuple(extractField!(TemplateArgsOf!(Args[0])[1])(ts)); 361 } 362 alias ParamsTuple = staticMap!(TemplateArgsOf, Params); 363 alias Names = staticMap!(extractName, ParamsTuple); 364 auto Fields = extractFields!(ParamsTuple)(ts); 365 return tuple!(Names)(Fields.expand); 366 } 367 } 368 369 @trusted 370 void setParamFromParent(string name, T, Ts...)(ref T t, auto ref Ts ts) { 371 import std.traits : PointerTarget, isPointer; 372 import std.meta : AliasSeq; 373 alias TargetType = typeof(getMember!(T, name)); 374 static if (isPointer!(TargetType)) 375 alias FieldType = PointerTarget!(TargetType); 376 else 377 alias FieldType = TargetType; 378 template matchesField(Parent) { 379 static if (!hasMember!(Parent,name)) 380 enum matchesField = false; 381 else { 382 alias ItemTypeParent = AliasSeq!(__traits(parent, getMember!(Parent, name)))[0]; 383 static if (is(T == ItemTypeParent)) 384 enum matchesField = false; 385 else { 386 alias ItemType = typeof(getMember!(Parent, name)); 387 enum matchesField = (is(ItemType == FieldType) || is (ItemType == FieldType*)); 388 } 389 } 390 } 391 enum index = indexOfPred!(matchesField, AliasSeq!Ts); 392 static if (index >= ts.length) { 393 return; 394 } else { 395 alias SourceType = typeof(__traits(getMember, Ts[index], name)); 396 static if(isPointer!TargetType) { 397 static if (isPointer!SourceType) 398 __traits(getMember, t, name) = __traits(getMember, ts[index], name); 399 else { 400 static if (is(Ts[index] : Tuple!Fields, Fields...)) { 401 static assert(!isBasicType!SourceType, "Cannot assign literal in param uda to a pointer, use member field or avoid use of pointer."); 402 } 403 __traits(getMember, t, name) = &__traits(getMember, ts[index], name); 404 } 405 } else static if (isPointer!SourceType) 406 __traits(getMember, t, name) = *__traits(getMember, ts[index], name); 407 else 408 __traits(getMember, t, name) = __traits(getMember, ts[index], name); 409 } 410 } 411 412 void setChildFromParent(string name, T, Ts...)(auto ref T t, auto ref Ts ts) { 413 template containsChild(Parent) { 414 enum containsChild = hasMember!(Parent, name); 415 } 416 enum index = indexOfPred!(containsChild, AliasSeq!Ts); 417 static if (index != -1) { 418 alias ChildMemberType = typeof(__traits(getMember, T, name)); 419 alias Parent = Ts[index]; 420 alias ParentMemberType = typeof(__traits(getMember, Parent, name)); 421 static assert(__traits(hasMember, ParentMemberType, "node"), __traits(identifier, ParentMemberType) ~ " doesn't have a member 'node'."); 422 alias ParentMemberNodeType = typeof(ParentMemberType.node); 423 alias ChildMemberNodeType = typeof(ChildMemberType.node); 424 static assert(is(ParentMemberNodeType : NamedNode!type, string type), __traits(identifier, ParentMemberType) ~ "'s member 'node' needs to be of type NamedNode."); 425 // NOTE: for now we assert that the ChildMemberNodeType is an HTMLElement, it would be nice to 426 // allow the component to restrict the nodetype of child elements, in which case 427 // we need to check if ChildMemberNodeType is a supertype or the same as ParentMemberNodeType 428 // this way we could e.g. enforce a child of a Table component to be <tr> 429 import spasm.bindings.html : HTMLElement; 430 static assert(is(ChildMemberNodeType : HTMLElement)); 431 auto ptr = &__traits(getMember, ts[index], name).node; 432 __traits(getMember, t, name) = cast(ChildMemberType)ptr; 433 } 434 } 435 436 void verifyChildParams(Super, string parentField, Params...)() { 437 import std.traits : hasUDA; 438 alias ParamsTuple = staticMap!(TemplateArgsOf, Params); 439 static foreach(Param; AliasSeq!ParamsTuple) {{ 440 enum childName = __traits(identifier, Param.Field); 441 alias ChildMemberType = typeof(Param.Field); 442 static if (!isBasicType!(ChildMemberType) && !isValue!(Param.Field) && hasMember!(Super, childName)) { 443 enum childOffset = __traits(getMember, Super, childName).offsetof; 444 enum parentOffset = __traits(getMember, Super, parentField).offsetof; 445 static assert(!hasUDA!(__traits(getMember, Super, childName), child), "Propagated member '"~ childName~ "' cannot have @child attribute in struct "~ Super.stringof); 446 static assert(childOffset > parentOffset, "Propagated member '"~ childName~ "' needs to be declared after '"~parentField~ "' in struct "~ Super.stringof); 447 } 448 }} 449 } 450 451 auto setPointers(T, Ts...)(auto ref T t, auto ref Ts ts) { 452 import std.meta : AliasSeq; 453 import std.traits : hasUDA; 454 static foreach(sym; T.tupleof) {{ 455 static if (isPointer!(typeof(sym))) 456 alias ChildType = PointerTarget!(typeof(sym)); 457 else 458 alias ChildType = typeof(sym); 459 enum i = sym.stringof; // TODO: can this be fieldName = __traits(identifier, sym) instead of i = sym.stringof ?? 460 enum isImmutable = is(typeof(sym) : immutable(T), T); 461 enum isPublic = __traits(getProtection, sym) == "public"; 462 static if (!isImmutable) { 463 static if (is(ChildType : NamedNode!name, string name)) { 464 static if (i != "node") 465 setChildFromParent!(__traits(identifier, sym))(t, ts); 466 } else { 467 static if (isPublic) 468 setParamFromParent!(i)(t, ts); 469 static if (!is(sym) && isAggregateType!T) { 470 static if (is(typeof(sym) : DynamicArray!(Item), Item)) { 471 // items in appenders need to be set via render functions 472 } else { 473 static if (!isCallable!(typeof(sym))) {//} && !isPointer!(typeof(sym))) { 474 import spasm.spa; 475 alias Params = getUDAs!(sym, Parameters); 476 static if (Params.length > 0) { 477 verifyChildParams!(T, i, Params); 478 auto params = createParameterTuple!(Params)(AliasSeq!(t, ts)); 479 } 480 else 481 alias params = AliasSeq!(); 482 static if (isAggregateType!(ChildType)) { 483 static if (isPointer!(typeof(sym))) 484 setPointers(*__traits(getMember, t, i), AliasSeq!(params, t, ts)); 485 else 486 setPointers(__traits(getMember, t, i), AliasSeq!(params, t, ts)); 487 } 488 } 489 } 490 } 491 } 492 } 493 }} 494 } 495 496 auto isChildVisible(string child, Parent)(auto ref Parent parent) { 497 alias visiblePreds = getSymbolsByUDA!(Parent, visible); 498 static foreach(sym; visiblePreds) {{ 499 alias vs = getUDAs!(sym, visible); 500 // TODO: static assert sym is callable 501 static foreach(v; vs) {{ 502 static if (is(v == visible!name, string name) && child == name) { 503 static if (is(typeof(sym) == bool)) { 504 if (!__traits(getMember, parent, __traits(identifier,sym))) 505 return false; 506 } else { 507 auto result = callMember!(__traits(identifier, sym))(parent); 508 if (result == false) 509 return false; 510 } 511 } 512 }} 513 }} 514 return true; 515 } 516 517 auto callMember(string fun, T)(auto ref T t) { 518 import spasm.ct : ParameterIdentifierTuple; 519 import std.meta : staticMap, AliasSeq; 520 alias params = ParameterIdentifierTuple!(__traits(getMember, t, fun)); 521 static if (params.length == 0) { 522 return __traits(getMember, t, fun)(); 523 } else static if (params.length == 1) { 524 return __traits(getMember, t, fun)(__traits(getMember, t, params[0])); 525 } else static if (params.length == 2) { 526 return __traits(getMember, t, fun)(__traits(getMember, t, params[0]),__traits(getMember, t, params[1])); 527 } 528 else { 529 static assert(false, "Not implemented"); 530 } 531 } 532 533 auto renderIntoNode(T, Ts...)(Handle parent, auto ref T t, auto ref Ts ts) if (isPointer!T) { 534 return renderIntoNode(parent, *t, ts); 535 } 536 537 ref auto getNamedNode(T)(auto ref T t) if (isPointer!T) { 538 return (*t).getNamedNode(); 539 } 540 541 ref auto getNamedNode(T)(auto ref T t) if (!isPointer!T) { 542 import spasm.node : NamedNode; 543 static if (is(T : NamedNode!name, string name)) { 544 return t; 545 } else static if (hasMember!(T, "node")) { 546 return t.node; 547 } else { 548 alias children = getSymbolsByUDA!(T, child); 549 static assert(children.length == 1); 550 return __traits(getMember, t, __traits(identifier, children[0])).getNamedNode(); 551 } 552 } 553 554 template createNestedChildRenderFuncs(string memberName) { 555 auto createNestedChildRenderFuncs(T)(auto ref T t) { 556 import std.traits : hasUDA; 557 import std.typecons : tuple; 558 template isNestedChild(alias member) { 559 alias MemberType = typeof(member); 560 static if (!__traits(hasMember, MemberType, "node")) 561 enum isNestedChild = false; 562 else 563 enum isNestedChild = is(typeof(MemberType.node) : NamedNode!type, string type); 564 } 565 template extractName(alias param) { 566 alias extractName = param.Name; 567 } 568 template extractField(alias param) { 569 alias extractField = param.Field; 570 } 571 template isFieldNestedChild(alias param) { 572 enum isFieldNestedChild = isNestedChild!(extractField!(param)); 573 } 574 auto createRenderFunc(alias param)() @trusted { 575 enum nestedChildName = __traits(identifier, extractField!(param)); 576 return cast(NestedChildRenderFunc)(Handle parent) @safe { 577 auto nestedChildRenderFuncs = .createNestedChildRenderFuncs!(nestedChildName)(t); 578 render(parent, __traits(getMember, t, nestedChildName), nestedChildRenderFuncs); 579 }; 580 } 581 alias ParamsTuple = getAnnotatedParameters!(__traits(getMember, T, memberName)); 582 alias ParamFieldsTuple = staticMap!(extractField, ParamsTuple); 583 alias nestedChildParams = Filter!(isFieldNestedChild, ParamsTuple); 584 alias Names = staticMap!(extractName, nestedChildParams); 585 static if (Names.length == 0) 586 return tuple(); 587 else 588 return tuple!(Names)(staticMap!(createRenderFunc, nestedChildParams)); 589 } 590 } 591 592 alias NestedChildRenderFunc = void delegate(uint) nothrow @safe; 593 594 template renderNestedChild(string field) { 595 template hasRenderFunc(alias T) { 596 static if (!hasMember!(T, field)) 597 enum hasRenderFunc = false; 598 else { 599 alias fieldType = typeof(__traits(getMember, T, field)); 600 enum hasRenderFunc = is(fieldType : NestedChildRenderFunc); 601 } 602 } 603 auto renderNestedChild(T, Ts...)(Handle parent, auto ref T t, auto ref Ts ts) { 604 enum tupleIndex = indexOfPred!(hasRenderFunc, Ts); 605 static if (tupleIndex != -1) { 606 __traits(getMember, ts[tupleIndex], field)(parent); 607 } 608 } 609 } 610 // NOTE: only trusted because of the one __gshared thing 611 @trusted auto renderIntoNode(T, Ts...)(Handle parent, auto ref T t, auto ref Ts ts) if (!isPointer!T) { 612 import std.traits : hasUDA, getUDAs; 613 import std.meta : AliasSeq; 614 import std.meta : staticMap; 615 import std.traits : isCallable, getSymbolsByUDA, isPointer; 616 import std.conv : text; 617 enum hasNode = hasMember!(T, "node"); 618 static if (hasNode) { 619 bool shouldRender = t.getNamedNode().node == invalidHandle; 620 } else 621 bool shouldRender = true; 622 if (shouldRender) { 623 auto node = createNode(parent, t); 624 static if (hasMember!(T, "node")) { 625 t.getNamedNode().node.handle.handle = node; 626 } 627 alias StyleSet = getStyleSet!T; 628 static foreach(sym; T.tupleof) {{ 629 enum i = sym.stringof; 630 alias name = getSymbolCustomName!(sym, domName!i); 631 static if (!is(sym)) { 632 alias styles = getStyles!(sym); 633 static if (hasUDA!(sym, child)) { 634 import spasm.spa; 635 alias params = AliasSeq!(); 636 if (isChildVisible!(i)(t)) { 637 static if (is(typeof(sym) : DynamicArray!(Item*), Item)) { 638 foreach(ref item; __traits(getMember, t, i)) { 639 node.render(*item, AliasSeq!(params)); 640 static if (is(typeof(t) == Array!Item)) 641 t.assignEventListeners(*item); 642 } 643 } else static if (is(typeof(sym) : NamedNode!(name)*, string name)) { 644 renderNestedChild!(i)(node, t, ts); 645 } else { 646 auto nestedChildRenderFuncs = createNestedChildRenderFuncs!(i)(t); 647 node.render(__traits(getMember, t, i), AliasSeq!(params, nestedChildRenderFuncs)); 648 } 649 } 650 } else static if (hasUDA!(sym, prop)) { 651 node.setPropertyTyped!name(__traits(getMember, t, i)); 652 } else static if (hasUDA!(sym, attr)) { 653 node.setAttributeTyped!name(__traits(getMember, t, i)); 654 } 655 656 alias extendedStyles = getStyleSets!(sym); 657 static foreach(style; extendedStyles) { 658 static assert(hasMember!(typeof(sym), "node"), "styleset on field is currently only possible when said field has a Node mixin"); 659 __traits(getMember, t, i).node.setAttribute(GenerateExtendedStyleSetName!style,""); 660 } 661 static if (i == "node") { 662 node.applyStyles!(T, styles); 663 } else static if (styles.length > 0) { 664 static if (isCallable!(sym)) { 665 auto result = callMember!(i)(t); 666 if (result == true) { 667 t.getNamedNode.applyStyles!(T, styles); 668 } 669 } else static if (is(typeof(sym) == bool)) { 670 if (__traits(getMember, t, i) == true) 671 t.getNamedNode.applyStyles!(T, styles); 672 } else static if (hasUDA!(sym, child)) { 673 getNamedNode(__traits(getMember, t, i)).applyStyles!(T, styles); 674 } 675 } 676 } 677 }} 678 static foreach(i; __traits(allMembers, T)) {{ 679 static if (!is(__traits(getMember, T, i)) && isCallable!(__traits(getMember, T, i))) { 680 alias sym = AliasSeq!(__traits(getMember, T, i))[0]; 681 682 alias name = getSymbolCustomName!(sym, domName!i); 683 static if (hasUDA!(sym, child)) 684 static assert(false, "we don't support @child functions"); 685 else static if (hasUDA!(sym, prop)) { 686 auto result = callMember!(i)(t); 687 t.getNamedNode.setPropertyTyped!name(result); 688 } else static if (hasUDA!(sym, callback)) { 689 t.getNamedNode.addEventListenerTyped!i(t); 690 } else static if (hasUDA!(sym, attr)) { 691 auto result = callMember!(i)(t); 692 t.getNamedNode.setAttributeTyped!name(result); 693 } else static if (hasUDA!(sym, style)) { 694 auto result = callMember!(i)(t); 695 static foreach (style; getStyles!(sym)) { 696 __gshared static string className = GetCssClassName!(T, style); 697 t.getNamedNode.changeClass(className,result); 698 } 699 } else static if (hasUDA!(sym, connect)) { 700 alias connects = getUDAs!(sym, connect); 701 static foreach(c; connects) { 702 auto del = &__traits(getMember, t, i); 703 static if (is(c: connect!(a,b), alias a, alias b)) { 704 mixin("t."~a~"."~replace!(b,'.','_')~".add(del);"); 705 } else static if (is(c : connect!field, alias field)) { 706 static assert(__traits(compiles, mixin("t."~field)), "Cannot find property "~field~" on "~T.stringof~" in @connect"); 707 mixin("t."~field~".add(del);"); 708 } else static if (is(c : connect!field, string field)) { 709 mixin("t."~field~".add(del);"); 710 } 711 } 712 } 713 } 714 }} 715 716 alias enumsWithApplyStyles = getSymbolsByUDA!(T, ApplyStyle); 717 static foreach(member; enumsWithApplyStyles) {{ 718 alias ApplyStyles = getUDAs!(member, ApplyStyle); 719 static foreach(s; ApplyStyles) { 720 static if (is(s == ApplyStyle!Target, alias Target)) { 721 static if (isFunction!member) 722 alias EnumType = ReturnType!member; 723 else 724 alias EnumType = typeof(member); 725 alias GetUDAs = ApplyRight!(ApplyLeft!(getEnumUDAs, EnumType), style); 726 alias table = staticMap!(GetUDAs, __traits(allMembers, EnumType)); 727 alias GetCssClass = ApplyLeft!(GetCssClassName, T); 728 alias classes = staticMap!(GetCssClass, staticMap!(extractStyleStruct, table)); 729 enum string[classes.length] styleTable = [classes]; 730 size_t idx = __traits(getMember, t, __traits(identifier, member)); 731 static if (__traits(identifier, Target) == "node") { 732 node.addClass(styleTable[idx]); 733 } else static if (hasUDA!(Target, child)) { 734 __traits(getMember, t, __traits(identifier, Target)).getNamedNode.addClass(styleTable[idx]); 735 } else 736 static assert(false, "Can only have ApplyStyle point to node or to a child component"); 737 } 738 } 739 }} 740 } 741 } 742 743 template getSymbolCustomName(alias symbol, string defaultName) { 744 alias names = getStringUDAs!(symbol); 745 static if (names.length > 0) 746 enum getSymbolCustomName = names[0]; 747 else 748 enum getSymbolCustomName = defaultName; 749 } 750 template getEnumUDAs(EnumType, string field, alias UDA) { 751 alias udas = AliasSeq!(__traits(getAttributes, __traits(getMember, EnumType, field))); 752 alias getEnumUDAs = Filter!(isDesiredUDA!UDA, udas); 753 } 754 755 private template isDesiredUDA(alias attribute) { 756 template isDesiredUDA(alias toCheck) { 757 static if (is(typeof(attribute)) && !__traits(isTemplate, attribute)) { 758 static if (__traits(compiles, toCheck == attribute)) 759 enum isDesiredUDA = toCheck == attribute; 760 else 761 enum isDesiredUDA = false; 762 } else static if (is(typeof(toCheck))) { 763 static if (__traits(isTemplate, attribute)) 764 enum isDesiredUDA = isInstanceOf!(attribute, typeof(toCheck)); 765 else 766 enum isDesiredUDA = is(typeof(toCheck) == attribute); 767 } else static if (__traits(isTemplate, attribute)) 768 enum isDesiredUDA = isInstanceOf!(attribute, toCheck); 769 else 770 enum isDesiredUDA = is(toCheck == attribute); 771 } 772 } 773 774 template among(alias field, T...) { 775 static if (T.length == 0) 776 enum among = false; 777 else static if (T.length == 1) 778 enum among = field.stringof == T[0]; 779 else 780 enum among = among!(field,T[0..$/2]) || among!(field,T[$/2..$]); 781 } 782 783 template getAnnotatedParameters(alias symbol) { 784 import spasm.spa; 785 alias Params = getUDAs!(symbol, Parameters); 786 alias getAnnotatedParameters = staticMap!(TemplateArgsOf, Params); 787 } 788 789 template updateChildren(alias member) { 790 enum field = __traits(identifier, member); 791 alias Source = AliasSeq!(__traits(parent, member))[0]; 792 template isParamField(Param) { 793 enum isParamField = TemplateArgsOf!(Param)[1].stringof == field; 794 } 795 static void updateChildren(Parent)(auto ref Parent parent) { 796 // we are updating field in parent 797 // all children that have a pointer with the exact same name 798 // should get an update 799 // all children that have a params annotation that refers to the field 800 // should get an update 801 import std.traits : getSymbolsByUDA; 802 import std.meta : ApplyLeft, staticMap; 803 static if (isPointer!(Parent)) 804 alias ParentType = PointerTarget!(Parent); 805 else 806 alias ParentType = Parent; 807 static foreach(t; ParentType.tupleof) { 808 static foreach(param; getAnnotatedParameters!(t)) {{ 809 static if (!isValue!(TemplateArgsOf!(param)[1])) { 810 alias target = TemplateArgsOf!(param)[1]; 811 static if (__traits(isSame, target, member)) { 812 __traits(getMember, parent, __traits(identifier, t)).update!(__traits(getMember, __traits(getMember, parent, __traits(identifier, t)), param.Name)); 813 } 814 } 815 }} 816 } 817 alias getSymbol = ApplyLeft!(getMember, parent); 818 alias childrenNames = getChildren!Parent; 819 alias children = staticMap!(getSymbol,childrenNames); 820 static foreach(c; children) {{ 821 alias ChildType = typeof(c); 822 static if (hasMember!(ChildType, field)) { 823 __traits(getMember, parent, c.stringof).update!(__traits(getMember, __traits(getMember, parent, c.stringof), field)); 824 } else 825 .updateChildren!(member)(__traits(getMember, parent, c.stringof)); 826 }} 827 } 828 } 829 830 auto update(T)(ref T node) if (hasMember!(T, "node")){ 831 struct Inner { 832 nothrow: 833 void opDispatch(string name, V)(auto ref V v) const @safe { 834 mixin("node.update!(node." ~ name ~ ")(v);"); 835 // NOTE: static assert won't work in opDispatch, as the compiler will not output the string but just ignore the opDispatch call and error out saying missing field on Inner 836 static if(!hasMember!(T, name)) 837 pragma(msg, "********* Error: " ~ T.stringof ~ " has no property named "~name); 838 } 839 } 840 return Inner(); 841 } 842 843 void update(Range, Sink)(auto ref Range source, ref Sink sink) { 844 import std.range : ElementType; 845 import std.algorithm : copy; 846 alias E = ElementType!Range; 847 auto output = Updater!(Sink)(&sink); 848 foreach(i; source) 849 output.put(i); 850 } 851 852 void setVisible(string field, Parent)(auto ref Parent parent, bool visible) { 853 bool current = __traits(getMember, parent, field).node.mounted; 854 if (current != visible) { 855 if (visible) { 856 remount!(field)(parent); 857 } else { 858 unmount(__traits(getMember, parent, field)); 859 } 860 } 861 } 862 863 @trusted 864 template update(alias field) { 865 import std.traits : isPointer; 866 static void updateDom(Parent, T)(auto ref Parent parent, auto ref T t) { 867 import spasm.ct : ParameterIdentifierTuple; 868 import std.traits : hasUDA, isCallable, getUDAs; 869 import std.meta : AliasSeq; 870 import std.meta : staticMap; 871 alias name = domName!(field.stringof); 872 static if (hasUDA!(field, prop)) { 873 parent.node.setPropertyTyped!name(t); 874 } else static if (hasUDA!(field, attr)) { 875 parent.node.setAttributeTyped!name(t); 876 } 877 static if (is(T == bool)) { 878 alias styles = getStyles!(field); 879 static foreach(style; styles) { 880 __gshared static string className = GetCssClassName!(Parent, style); 881 parent.getNamedNode.changeClass(className,t); 882 } 883 static if (hasUDA!(field, visible)) { 884 alias udas = getUDAs!(field, visible); 885 static foreach (uda; udas) { 886 static if (is(uda : visible!elem, alias elem)) { 887 setVisible!(elem)(parent, __traits(getMember, parent, __traits(identifier, field))); 888 } 889 } 890 } 891 } 892 static foreach(i; __traits(allMembers, Parent)) {{ 893 alias sym = AliasSeq!(__traits(getMember, parent, i))[0]; 894 static if (isCallable!(sym)) { 895 alias params = ParameterIdentifierTuple!sym; 896 static if (among!(field, params)) { 897 static if (hasUDA!(sym, prop)) { 898 alias cleanName = domName!i; 899 auto result = callMember!(i)(parent); 900 parent.node.node.setPropertyTyped!cleanName(result); 901 } 902 else static if (hasUDA!(sym, attr)) { 903 alias cleanName = domName!i; 904 auto result = callMember!(i)(parent); 905 parent.node.node.setAttributeTyped!cleanName(result); 906 } 907 else static if (hasUDA!(sym, style)) { 908 auto result = callMember!(i)(parent); 909 static foreach (style; getStyles!(sym)) 910 { 911 __gshared static string className = GetCssClassName!(Parent, style); 912 parent.node.node.changeClass(className,result); 913 } 914 } else { 915 import std.traits : ReturnType; 916 alias RType = ReturnType!(__traits(getMember, parent, i)); 917 static if (is(RType : void)) 918 callMember!(i)(parent); 919 else { 920 auto result = callMember!(i)(parent); 921 static if (hasUDA!(sym, visible)) { 922 alias udas = getUDAs!(sym, visible); 923 static foreach(uda; udas) { 924 static if (is(uda : visible!elem, alias elem)) { 925 setVisible!(elem)(parent, result); 926 } 927 } 928 } 929 } 930 } 931 } 932 } 933 }} 934 updateChildren!(field)(parent); 935 } 936 static void update(Parent)(scope auto ref Parent parent) { 937 if (!parent.node.mounted) 938 return; 939 static if (isPointer!Parent) 940 updateDom(*parent, __traits(getMember, parent, field.stringof)); 941 else 942 updateDom(parent, __traits(getMember, parent, field.stringof)); 943 } 944 static void update(Parent, T)(scope auto ref Parent parent, T t) { 945 mixin("parent."~field.stringof~" = t;"); 946 if (!parent.node.mounted) 947 return; 948 static if (isPointer!Parent) 949 updateDom(*parent, t); 950 else 951 updateDom(parent, t); 952 } 953 } 954 955 template symbolFromAliasThis(Parent, string name) { 956 import std.meta : anySatisfy; 957 alias aliasThises = AliasSeq!(__traits(getAliasThis, Parent)); 958 static if (aliasThises.length == 0) 959 enum symbolFromAliasThis = false; 960 else { 961 alias hasSymbol = ApplyRight!(hasMember, name); 962 enum symbolFromAliasThis = anySatisfy!(hasSymbol, aliasThises); 963 } 964 } 965 966 auto setAttributeTyped(string name, T)(Handle node, auto ref T t) { 967 import std.traits : isPointer; 968 static if (isPointer!T) { 969 if (t !is null) 970 node.setAttributeTyped!name(*t); 971 } else static if (is(T == bool)) 972 node.setAttributeBool(name, t); 973 else static if (is(T : int)) { 974 node.setAttributeInt(name, t); 975 } else { 976 node.setAttribute(name, t); 977 } 978 } 979 980 auto setPropertyTyped(string name, T)(Handle node, auto ref T t) { 981 import std.traits : isPointer, isNumeric; 982 static if (isPointer!T) { 983 if (t !is null) 984 node.setPropertyTyped!name(*t); 985 } 986 else static if (is(T == bool)) 987 node.setPropertyBool(name, t); 988 else static if (isIntegral!(T)) 989 node.setPropertyInt(name, t); 990 else static if (isNumeric!(T)) 991 node.setPropertyDouble(name, t); 992 else 993 { 994 static if (__traits(compiles, __traits(getMember, api, name))) 995 __traits(getMember, api, name)(node, t); 996 else 997 node.setProperty(name, t); 998 } 999 } 1000 1001 auto applyStyles(T, styles...)(Handle node) { 1002 static foreach(style; styles) { 1003 node.addClass(GetCssClassName!(T, style)); 1004 } 1005 } 1006 1007 Handle createNode(T)(Handle parent, ref T t) { 1008 enum hasNode = hasMember!(T, "node"); 1009 static if (hasNode && is(typeof(t.node) : NamedNode!tag, alias tag)) { 1010 mixin("NodeType n = NodeType." ~ tag ~ ";"); 1011 return createElement(n); 1012 } else 1013 return parent; 1014 } 1015 1016 template indexOfPred(alias Pred, TList...) { 1017 enum indexOfPred = indexOf!(Pred, TList).index; 1018 } 1019 1020 template indexOf(alias Pred, args...) { 1021 import std.meta : AliasSeq; 1022 static if (args.length > 0) { 1023 static if (Pred!(args[0])) { 1024 enum index = 0; 1025 } else { 1026 enum next = indexOf!(Pred, AliasSeq!(args[1..$])).index; 1027 enum index = (next == -1) ? -1 : 1 + next; 1028 } 1029 } else { 1030 enum index = -1; 1031 } 1032 } 1033 1034 template domName(string name) { 1035 import std.algorithm : stripRight; 1036 import std.conv : text; 1037 static if (name[$-1] == '_') 1038 enum domName = name[0..$-1]; 1039 else 1040 enum domName = name; 1041 } 1042 1043 template join(Seq...) { 1044 static if (is(typeof(Seq) == string)) 1045 enum join = Seq; 1046 else { 1047 static if (Seq.length == 1) { 1048 enum join = Seq[0]; 1049 } 1050 else { 1051 enum join = Seq[0] ~ "," ~ join!(Seq[1 .. $]); 1052 } 1053 } 1054 }