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