1 module libwasm.css; 2 3 import std.meta : staticMap, ApplyRight, AliasSeq, NoDuplicates, ApplyLeft, Filter; 4 import std.traits : getSymbolsByUDA, hasUDA, hasMember, getUDAs, Fields, FieldNameTuple, PointerTarget, isPointer, isType, isAggregateType; 5 import memutils.ct; 6 import libwasm.types; 7 8 struct styleset(alias set) 9 { 10 }; 11 struct Extend(alias target) 12 { 13 }; 14 struct style(alias s) 15 { 16 }; 17 struct not(alias s) 18 { 19 }; 20 struct media(string content) 21 { 22 } 23 24 struct ApplyStyle(alias target) 25 { 26 } 27 28 version (unittest) 29 { 30 import unit_threaded; 31 32 private struct Style 33 { 34 struct disabled 35 { 36 } 37 38 struct focused 39 { 40 } 41 42 struct root 43 { 44 string backgroundColor = "blue"; 45 @("after") struct after 46 { 47 auto content = `""`; 48 } 49 50 @(disabled, "after") struct afterDisabled 51 { 52 auto content = `""`; 53 } 54 55 @(not!focused, "after") struct afterNotFocused 56 { 57 auto content = `""`; 58 } 59 60 @("hover", not!disabled, "before") struct hoverBefore 61 { 62 auto content = `""`; 63 } 64 65 @(media!"hover: none") struct resetTouch 66 { 67 auto content = `""`; 68 } 69 } 70 } 71 72 private struct StyleTmpl(Theme) 73 { 74 struct disabled 75 { 76 } 77 78 struct focused 79 { 80 } 81 } 82 } 83 84 template TypeOf(alias symbol) 85 { 86 alias TypeOf = typeof(symbol); 87 } 88 89 template Symbol(T, string field) 90 { 91 import std.meta : AliasSeq; 92 93 alias Symbol = AliasSeq!(__traits(getMember, T, field))[0]; 94 } 95 96 template extractStyleSetStruct(T) 97 { 98 static if (is(T : styleset!Set, Set)) 99 { 100 alias extractStyleSetStruct = Set; 101 } 102 else static if (is(T : styleset!Set, alias Set)) 103 { 104 alias extractStyleSetStruct = Set; 105 } 106 } 107 108 template extractStyleStruct(T) 109 { 110 static if (is(T : style!clsName, string clsName)) 111 alias extractStyleStruct = clsName; 112 } 113 114 template getStyles(alias field) 115 { 116 alias sets = getUDAs!(field, style); 117 alias getStyles = staticMap!(extractStyleStruct, sets); 118 } 119 120 template getStyleSet(T) 121 { 122 static if (isPointer!T) 123 { 124 alias getStyleSet = .getStyleSet!(PointerTarget!T); 125 } 126 else static if (isCallable!T) 127 { 128 alias getStyleSet = AliasSeq!(); 129 } 130 else 131 { 132 alias sets = getUDAs!(T, styleset); 133 alias getStyleSet = staticMap!(extractStyleSetStruct, sets); 134 } 135 } 136 137 template getStyleSets(alias field) 138 { 139 alias sets = getUDAs!(field, styleset); 140 alias getStyleSets = staticMap!(extractStyleSetStruct, sets); 141 } 142 143 template getStyleSets(T) 144 { 145 import libwasm.array : List; 146 147 static if (is(T : List!(Item, tag), Item, string tag)) 148 { 149 alias getStyleSets = .getStyleSets!(Item); 150 } 151 else static if (isPointer!T) 152 { 153 alias getStyleSets = .getStyleSets!(PointerTarget!T); 154 } 155 else static if (isCallable!T) 156 { 157 import std.traits : ReturnType; 158 159 alias getStyleSets = .getStyleSets!(ReturnType!T); 160 } 161 else 162 { 163 alias symbols = getSymbolsByUDA!(T, child); 164 alias children = staticMap!(TypeOf, symbols); 165 static if (symbols.length == 0) 166 alias getStyleSets = AliasSeq!(getStyleSet!T); 167 else 168 alias getStyleSets = NoDuplicates!(AliasSeq!(staticMap!(.getStyleSets, children), getStyleSet!T)); 169 } 170 } 171 172 template isNonType(alias T) 173 { 174 enum isNonType = __traits(compiles, { alias B = typeof(T); }); 175 } 176 177 template hasStyleSetUDA(alias T) 178 { 179 enum hasStyleSetUDA = hasUDA!(T, styleset); 180 } 181 182 template getStyleSetsExtends(T) 183 { 184 static if (isPointer!T) 185 { 186 alias getStyleSetsExtends = .getStyleSetsExtends!(PointerTarget!T); 187 } 188 else static if (isCallable!T) 189 { 190 import std.traits : ReturnType; 191 192 alias getStyleSetsExtends = .getStyleSetsExtends!(ReturnType!T); 193 } 194 else 195 { 196 alias styleSetSymbols = Filter!(hasStyleSetUDA, T.tupleof); 197 alias children = staticMap!(TypeOf, getSymbolsByUDA!(T, child)); 198 alias extendedStyleSets = staticMap!(extractExtendedStyleSet, styleSetSymbols); 199 static if (children.length > 0) 200 alias childrenExtendedStyleSets = staticMap!(.getStyleSetsExtends, children); 201 else 202 alias childrenExtendedStyleSets = AliasSeq!(); 203 alias getStyleSetsExtends = AliasSeq!(childrenExtendedStyleSets, extendedStyleSets); 204 } 205 } 206 207 struct ExtendedStyleSet(alias Set, alias sym) 208 { 209 } 210 211 template extractExtendedStyleSet(alias sym) 212 { 213 alias ExtendedStyleSetCurried = ApplyRight!(ExtendedStyleSet, sym); 214 alias extractExtendedStyleSet = staticMap!(ExtendedStyleSetCurried, staticMap!( 215 extractStyleSetStruct, getUDAs!(sym, styleset))); 216 } 217 218 template getFullName(alias sym) 219 { 220 import std.traits; 221 222 static if (is(sym) && !is(TemplateOf!sym : void)) 223 { 224 alias Base = TemplateOf!sym; 225 enum namePart = __traits(identifier, sym); //Base.stringof; 226 } 227 else 228 { 229 alias Base = sym; 230 enum namePart = __traits(identifier, sym); 231 } 232 static if (__traits(compiles, __traits(parent, Base))) 233 { 234 enum getFullName = namePart ~ "." ~ getFullName!(__traits(parent, Base)); 235 } 236 else 237 enum getFullName = namePart; 238 } 239 240 unittest 241 { 242 struct Theme 243 { 244 } 245 246 getFullName!(Style.disabled).should == "disabled.Style.css.libwasm"; 247 getFullName!(StyleTmpl!(Theme).disabled).should == "disabled.StyleTmpl.css.libwasm"; 248 } 249 250 template GetCssClassName(Node, string style) 251 { 252 alias StyleSets = getStyleSet!Node; 253 static if (StyleSets.length == 0) 254 enum GetCssClassName = style; 255 else static if (StyleSets.length > 1) 256 static assert("Cannot have more than one styleset"); 257 else 258 { 259 enum GetCssClassName = GenerateCssClassName!(style ~ "." ~ getFullName!(StyleSets[0])); 260 } 261 } 262 263 template getCssKeyValue(T, string defaultName) 264 { 265 alias symbol = Symbol!(T, defaultName); 266 static if (isAggregateType!(typeof(symbol))) 267 { 268 alias Tchild = typeof(symbol); 269 alias names = FieldNameTuple!Tchild; 270 alias values = staticMap!(ApplyLeft!(.getCssKeyValue, Tchild), names); 271 enum getCssKeyValue = values; 272 } 273 else 274 { 275 alias names = getStringUDAs!(symbol); 276 static if (names.length > 0) 277 enum name = names[0]; 278 else 279 enum name = defaultName; 280 enum getCssKeyValue = AliasSeq!(tuple(toCssProperty!name, __traits(getMember, T.init, defaultName))); 281 } 282 } 283 284 template toCssProperty(string str) 285 { 286 static if (str.length == 0) 287 enum toCssProperty = ""; 288 else static if (str[0] < 0xAA) 289 { 290 static if (str[0] < 'A') 291 enum toCssProperty = str[0] ~ toCssProperty!(str[1 .. $]); 292 else static if (str[0] <= 'Z') 293 enum toCssProperty = "-" ~ (str[0] + 32) ~ toCssProperty!(str[1 .. $]); 294 else 295 enum toCssProperty = str[0] ~ toCssProperty!(str[1 .. $]); 296 } 297 else 298 enum toCssProperty = str[0] ~ toCssProperty!(str[1 .. $]); 299 } 300 301 template toCss(keyValues...) 302 { 303 static if (keyValues.length == 0) 304 { 305 enum toCss = ""; 306 } 307 else 308 { 309 enum toCss = keyValues[0][0] ~ ":" ~ keyValues[0][1] ~ ";" ~ toCss!(keyValues[1 .. $]); 310 } 311 } 312 313 template GenerateCss(T) 314 { 315 alias names = FieldNameTuple!T; 316 alias values = staticMap!(ApplyLeft!(getCssKeyValue, T), names); 317 static if (values.length > 0) 318 enum GenerateCss = "{" ~ toCss!(values)[0 .. $ - 1] ~ "}"; 319 else 320 enum GenerateCss = ""; 321 } 322 323 template chunk(string str, size_t size) 324 { 325 import std.meta : AliasSeq; 326 327 static if (str.length <= size) 328 { 329 enum chunk = AliasSeq!(str); 330 } 331 else 332 { 333 enum chunk = AliasSeq!(str[0 .. size], chunk!(str[size .. $], size)); 334 } 335 } 336 337 template xor(char a, char b) 338 { 339 enum char xor = a ^ b; 340 } 341 342 template hashChunk(string a, B...) 343 { 344 static if (is(typeof(B[0]) == string)) 345 { 346 enum b = B[0]; 347 import std.meta : AliasSeq; 348 349 static if (a.length == 0 && b.length == 0) 350 enum hashChunk = AliasSeq!(); 351 else static if (a.length == 0) 352 enum hashChunk = AliasSeq!(int(b[0]), hashChunk!(a, b[1 .. $])); 353 else static if (b.length == 0) 354 enum hashChunk = AliasSeq!(int(a[0]), hashChunk!(a[1 .. $], b)); 355 else 356 { 357 enum hashChunk = AliasSeq!(xor!(a[0], b[0]), hashChunk!(a[1 .. $], b[1 .. $])); 358 } 359 } 360 else 361 { 362 import std.meta : AliasSeq; 363 364 static if (a.length == 0 && B.length == 0) 365 enum hashChunk = AliasSeq!(); 366 else static if (a.length == 0) 367 enum hashChunk = AliasSeq!(int(B[0]), hashChunk!(a, B[1 .. $])); 368 else static if (B.length == 0) 369 enum hashChunk = AliasSeq!(int(a[0]), hashChunk!(a[1 .. $], B)); 370 else 371 { 372 enum hashChunk = AliasSeq!(xor!(a[0], B[0]), hashChunk!(a[1 .. $], B[1 .. $])); 373 } 374 } 375 } 376 377 template cssIdentifierChar32(size_t idx) 378 { 379 immutable string chars = "abcdefghijklmnopqrstuvwxyz123456"; 380 enum cssIdentifierChar32 = chars[idx .. idx + 1]; 381 } 382 383 template cssIdentifierChar(size_t idx) 384 { 385 immutable string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; 386 enum cssIdentifierChar = chars[idx .. idx + 1]; 387 } 388 389 template toCssIdentifier(Bytes...) if (Bytes.length == 6) 390 { 391 enum toCssIdentifier = cssIdentifierChar!((Bytes[0] >> 4) & 0xF) ~ cssIdentifierChar!( 392 (Bytes[0] & 0xF) | ((Bytes[1] >> 2) & 0x30)) ~ cssIdentifierChar!( 393 Bytes[1] & 0x3F) ~ cssIdentifierChar!(Bytes[2] >> 2) ~ cssIdentifierChar!( 394 (Bytes[2] & 0x3) | ((Bytes[3] >> 2) & 0x3C)) ~ cssIdentifierChar!( 395 (Bytes[3] & 0xF) | ((Bytes[4] >> 2) & 0x30)) ~ cssIdentifierChar!( 396 Bytes[4] & 0x3F) ~ cssIdentifierChar!(Bytes[5] >> 2) ~ cssIdentifierChar!(Bytes[5] & 0x3); 397 } 398 399 template toCssIdentifier32(Bytes...) if (Bytes.length == 6) 400 { 401 enum toCssIdentifier32 = cssIdentifierChar32!((Bytes[0] >> 4) & 0xF) ~ cssIdentifierChar32!( 402 (Bytes[0] & 0xF) | ((Bytes[1] >> 3) & 0x10)) ~ cssIdentifierChar32!( 403 (Bytes[1] >> 2) & 0x1F) ~ cssIdentifierChar32!( 404 (Bytes[1] & 0x3) | ((Bytes[2] >> 3) & 0x1C)) ~ cssIdentifierChar32!( 405 Bytes[2] & 0x1F) ~ cssIdentifierChar32!( 406 (Bytes[3] >> 3) & 0x1F) ~ cssIdentifierChar32!( 407 (Bytes[3] & 0x3) | ((Bytes[4] >> 3) & 0x10)) ~ cssIdentifierChar32!( 408 (Bytes[4] >> 2) & 0x1F) ~ cssIdentifierChar32!( 409 (Bytes[4] & 0x3) | ((Bytes[5] >> 3) & 0x1C)) ~ cssIdentifierChar32!(Bytes[5] & 0x1F); 410 } 411 412 template reduceChunks(Chunks...) if (Chunks.length > 0) 413 { 414 import std.meta : AliasSeq; 415 416 static if (Chunks.length == 1) 417 enum reduceChunks = Chunks[0]; 418 else static if (Chunks.length == 2) 419 enum reduceChunks = hashChunk!(Chunks[0], Chunks[1]); 420 else static if (Chunks.length > 2) 421 { 422 enum reduceChunks = hashChunk!(Chunks[0], reduceChunks!(Chunks[1 .. $])); 423 } 424 } 425 426 template toCssName(string s) 427 { 428 enum toCssName = toCssIdentifier!(reduceChunks!(chunk!(s, 6))); 429 } 430 431 template toCssNameInsensitive(string s) 432 { 433 enum toCssNameInsensitive = toCssIdentifier32!(reduceChunks!(chunk!(s, 6))); 434 } 435 436 unittest 437 { 438 import unit_threaded; 439 440 enum i = toCssName!"{backgroundColor:gray2}"; 441 enum g = toCssName!"{display:inline}"; 442 enum h = toCssName!"{backgroundColor:gray}"; 443 i.should == "BEAASdIQD"; 444 g.should == "HTzNaHeAA"; 445 h.should == "BEAACC1QD"; 446 } 447 448 template GenerateCssClassName(string content) 449 { 450 enum GenerateCssClassName = toCssName!content; 451 } 452 453 template GenerateCssClassName(string base, alias T) 454 { 455 enum nestedName = __traits(identifier, T); 456 enum uniqueName = nestedName ~ "." ~ base; 457 alias GenerateCssClassName = GenerateCssClassName!uniqueName; 458 } 459 460 template GenerateCssClass(string base, alias T) 461 { 462 alias name = GenerateCssClassName!(base, T); 463 enum content = GenerateCss!T; 464 alias nestedClasses = GenerateNestedCssClasses!(T); 465 static if (content.length == 0) 466 { 467 enum GenerateCssClass = nestedClasses; 468 } 469 else 470 enum GenerateCssClass = "." ~ name ~ content ~ nestedClasses; 471 } 472 473 template GenerateNamedCssClass(string name, alias T) 474 { 475 alias content = GenerateCss!T; 476 alias nestedClasses = GenerateNestedCssClasses!("." ~ name, T); 477 enum GenerateNamedCssClass = "." ~ name ~ content ~ nestedClasses; 478 } 479 480 template GetPseudoCssSelector(alias symbol) 481 { 482 template GetName(alias attr) 483 { 484 static if (is(attr : media!content, string content)) 485 { 486 enum GetName = "@media(" ~ content ~ ")"; 487 } 488 else static if (is(attr : not!cls, cls)) 489 { 490 enum GetName = ":not(." ~ toCssName!(getFullName!cls) ~ ")"; 491 } 492 else static if (is(attr)) 493 { 494 enum GetName = "." ~ toCssName!(getFullName!attr); 495 } 496 else 497 enum GetName = ":" ~ attr; 498 } 499 500 alias attrs = AliasSeq!(__traits(getAttributes, symbol)); 501 static assert(attrs.length > 0, "Nested css class must have pseudo class attribute"); 502 alias parent = __traits(parent, symbol); 503 enum parentHash = toCssName!(getFullName!(parent)); 504 enum GetPseudoCssSelector = "." ~ parentHash ~ Joiner!(staticMap!(GetName, attrs)); 505 } 506 507 unittest 508 { 509 GetPseudoCssSelector!(Style.root.after).should == ".ACEXGQpUC:after"; 510 } 511 512 unittest 513 { 514 GetPseudoCssSelector!(Style.root.afterDisabled).should == ".ACEXGQpUC.DQpGODSBD:after"; 515 } 516 517 unittest 518 { 519 GetPseudoCssSelector!(Style.root.afterNotFocused).should == ".ACEXGQpUC:not(.GGGMBFYVA):after"; 520 } 521 522 unittest 523 { 524 GetPseudoCssSelector!(Style.root.hoverBefore).should == ".ACEXGQpUC:hover:not(.DQpGODSBD):before"; 525 } 526 527 unittest 528 { 529 GetPseudoCssSelector!(Style.root.resetTouch).should == ".ACEXGQpUC@media(hover: none)"; 530 } 531 532 unittest 533 { 534 struct Empty 535 { 536 } 537 538 GenerateCssSet!(Style, Empty).should == `.ACEXGQpUC{background-color:blue}.ACEXGQpUC:after{content:""}.ACEXGQpUC.DQpGODSBD:after{content:""}.ACEXGQpUC:not(.GGGMBFYVA):after{content:""}.ACEXGQpUC:hover:not(.DQpGODSBD):before{content:""}.ACEXGQpUC@media(hover: none){content:""}`; 539 } 540 541 unittest 542 { 543 struct Empty 544 { 545 } 546 547 struct Derived 548 { 549 struct root 550 { 551 Style.root base; 552 auto color = "green"; 553 } 554 } 555 556 GenerateCssSet!(Derived, Empty).should == `.DRECNBcCA{background-color:blue;color:green}`; 557 } 558 559 template GenerateNestedCssClass(alias symbol) 560 { 561 alias content = GenerateCss!symbol; 562 enum GenerateNestedCssClass = GetPseudoCssSelector!(symbol) ~ content; 563 } 564 565 template GenerateNestedCssClasses(alias base, T) 566 { 567 template WithPrefix(alias symbol) 568 { 569 enum WithPrefix = base ~ GenerateNestedCssClass!symbol; 570 } 571 572 alias members = AliasSeq!(__traits(allMembers, T)); 573 alias symbols = staticMap!(ApplyLeft!(Symbol, T), members); 574 alias nestedClasses = Filter!(isType, symbols); 575 static if (nestedClasses.length == 0) 576 enum GenerateNestedCssClasses = ""; 577 else 578 enum GenerateNestedCssClasses = Joiner!(staticMap!(WithPrefix, nestedClasses)); 579 } 580 581 template GenerateNestedCssClasses(T) 582 { 583 enum GenerateNestedCssClasses = GenerateNestedCssClasses!("", T); 584 } 585 586 template GenerateCssSet(alias T, Theme) 587 { 588 template isTypeInvert(alias T) 589 { 590 enum isTypeInvert = !isType!T; 591 } 592 593 static if (__traits(isTemplate, T)) 594 alias StyleSet = T!Theme; 595 else 596 alias StyleSet = T; 597 enum baseName = getFullName!(T); 598 alias members = AliasSeq!(__traits(allMembers, StyleSet)); 599 alias symbols = staticMap!(ApplyLeft!(Symbol, StyleSet), members); 600 alias typeSymbols = Filter!(isType, symbols); 601 enum GenerateCssSet = Joiner!(staticMap!(ApplyLeft!(GenerateCssClass, baseName), typeSymbols)); 602 } 603 604 template GenerateExtendedCssClass(alias T, string name, Child) 605 { 606 enum isDirectExtendedStyle = getSymbolsByUDA!(Child, style!(T.stringof)).length > 0; 607 enum attributeSelector = "[" ~ name ~ "]"; 608 static if (isDirectExtendedStyle) 609 { 610 alias content = GenerateCss!T; 611 alias nestedClasses = GenerateNestedCssClasses!(attributeSelector, T); 612 enum GenerateExtendedCssClass = attributeSelector ~ content ~ nestedClasses; 613 } 614 else static if (!hasUDA!(T, Extend)) 615 { 616 static assert(false, T.stringof ~ " needs an Extend attribute"); 617 } 618 else 619 { 620 alias extendsAttrs = getUDAs!(T, Extend); 621 static assert(extendsAttrs.length == 1, T.stringof ~ " can only have one Extend attribute"); 622 static if (is(extendsAttrs[0] : Extend!(Base), Base)) 623 { 624 alias baseContent = GenerateCss!Base; 625 alias baseName = GenerateCssClassName!baseContent; 626 enum GenerateExtendedCssClass = attributeSelector ~ " " ~ GenerateNamedCssClass!(baseName, T); 627 } 628 } 629 } 630 631 template GenerateExtendedStyleSetName(alias Set) 632 { 633 alias name = getFullName!(Set); 634 alias GenerateExtendedStyleSetName = toCssNameInsensitive!name; 635 } 636 637 template GenerateCssSetExtends(alias T, Theme) 638 { 639 static if (is(T : ExtendedStyleSet!(Set, sym), alias Set, alias sym)) 640 { 641 static if (__traits(isTemplate, Set)) 642 alias StyleSet = Set!Theme; 643 else 644 alias StyleSet = Set; 645 alias members = AliasSeq!(__traits(allMembers, StyleSet)); 646 alias symbols = staticMap!(ApplyLeft!(Symbol, StyleSet), members); 647 alias name = GenerateExtendedStyleSetName!Set; 648 enum GenerateCssSetExtends = Joiner!(staticMap!(ApplyRight!(GenerateExtendedCssClass, name, typeof( 649 sym)), symbols)); 650 } 651 else 652 enum GenerateCssSetExtends = ""; 653 } 654 655 unittest 656 { 657 import unit_threaded; 658 import libwasm.node; 659 660 struct Empty 661 { 662 } 663 664 struct Overwrite(Theme) 665 { 666 @Extend!(Style.root) 667 struct stuff 668 { 669 string backgroundColor = "green"; 670 } 671 } 672 673 @styleset!Style 674 struct Bar 675 { 676 @style!"root"mixin NodeDef!"div"; 677 } 678 679 struct Foo 680 { 681 mixin NodeDef!"span"; 682 @styleset!Overwrite @child Bar bar; 683 } 684 // TODO: currently extending only works when the StyleSet is a Template 685 // TODO: fix the generated class name that is extended 686 // GetCss!(Foo, Empty).should == "asdfasdf"; 687 } 688 689 template GetCss(T, Theme) 690 { 691 alias extendedSets = getStyleSetsExtends!T; 692 alias sets = getStyleSets!T; 693 enum css = Joiner!(staticMap!(ApplyRight!(GenerateCssSet, Theme), sets), staticMap!( 694 ApplyRight!(GenerateCssSetExtends, Theme), AliasSeq!(extendedSets))); 695 static if (css.length == 0) 696 enum GetCss = ""; 697 else 698 enum GetCss = css; 699 } 700 701 // TODO: extending styles should be done in a more wrapping kind of way. That is, we should just wrap the component in a WithStyles!(Comp, StyleSet) where the Styles overwrites the @StyleSet defined in library. It begs the question whether we shouldn't always use WithStyles!(Comp, StyleSet). 702 // NOTE: a nice idea but currently impossible. Within the struct we have no access to the overwritten styles and no way to figure out what classname we should apply. A possible solution would be to templatize the struct on the StyleSet. 703 // NOTE: this can lead to a nice situation where a component has several templated arguments (style, props) and we can indeed have WithStyleSet!(), WithProps!(), etc. which overwrite (or unwrap), previous Withx's