1 module spasm.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 spasm.ct;
6 import spasm.types;
7 
8 struct styleset(alias set) {};
9 struct Extend(alias target) {};
10 struct style(alias s) {};
11 struct not(alias s) {};
12 struct media(string content) {}
13 struct ApplyStyle(alias target) {}
14 
15 version(unittest) {
16   import unit_threaded;
17   private struct Style {
18     struct disabled {}
19     struct focused {}
20     struct root {
21       string backgroundColor = "blue";
22       @("after") struct after {
23         auto content = `""`;
24       }
25       @(disabled, "after") struct afterDisabled {
26         auto content = `""`;
27       }
28       @(not!focused, "after") struct afterNotFocused {
29         auto content = `""`;
30       }
31       @("hover", not!disabled, "before") struct hoverBefore {
32         auto content = `""`;
33       }
34       @(media!"hover: none") struct resetTouch {
35         auto content = `""`;
36       }
37     }
38   }
39   private struct StyleTmpl(Theme) {
40     struct disabled {}
41     struct focused {}
42   }
43 }
44 
45 template TypeOf(alias symbol) {
46   alias TypeOf = typeof(symbol);
47 }
48 
49 template Symbol(T, string field) {
50   import std.meta : AliasSeq;
51   alias Symbol = AliasSeq!(__traits(getMember, T, field))[0];
52 }
53 
54 template extractStyleSetStruct(T) {
55   static if (is(T : styleset!Set, Set)) {
56     alias extractStyleSetStruct = Set;
57   } else static if (is(T : styleset!Set, alias Set)) {
58     alias extractStyleSetStruct = Set;
59   }
60 }
61 
62 template extractStyleStruct(T) {
63   static if (is(T : style!clsName, string clsName))
64     alias extractStyleStruct = clsName;
65 }
66 
67 template getStyles(alias field) {
68   alias sets = getUDAs!(field, style);
69   alias getStyles = staticMap!(extractStyleStruct, sets);
70 }
71 
72 template getStyleSet(T) {
73   static if (isPointer!T) {
74     alias getStyleSet = .getStyleSet!(PointerTarget!T);
75   } else static if (isCallable!T) {
76     alias getStyleSet = AliasSeq!();
77   } else {
78     alias sets = getUDAs!(T, styleset);
79     alias getStyleSet = staticMap!(extractStyleSetStruct, sets);
80   }
81 }
82 
83 template getStyleSets(alias field) {
84   alias sets = getUDAs!(field, styleset);
85   alias getStyleSets = staticMap!(extractStyleSetStruct, sets);
86 }
87 
88 template getStyleSets(T) {
89   import spasm.array : List;
90   static if (is(T : List!(Item, tag), Item, string tag)) {
91     alias getStyleSets = .getStyleSets!(Item);
92   } else static if (isPointer!T) {
93     alias getStyleSets = .getStyleSets!(PointerTarget!T);
94   } else static if (isCallable!T) {
95     import std.traits : ReturnType;
96     alias getStyleSets = .getStyleSets!(ReturnType!T);
97   } else {
98     alias symbols = getSymbolsByUDA!(T, child);
99     alias children = staticMap!(TypeOf,symbols);
100     static if (symbols.length == 0)
101       alias getStyleSets = AliasSeq!(getStyleSet!T);
102     else
103       alias getStyleSets = NoDuplicates!(AliasSeq!(staticMap!(.getStyleSets, children), getStyleSet!T));
104   }
105 }
106 
107 template isNonType(alias T) {
108   enum isNonType = __traits(compiles, { alias B = typeof(T); });
109 }
110 
111 template hasStyleSetUDA(alias T){
112   enum hasStyleSetUDA = hasUDA!(T, styleset);
113 }
114 
115 template getStyleSetsExtends(T) {
116   static if (isPointer!T) {
117     alias getStyleSetsExtends = .getStyleSetsExtends!(PointerTarget!T);
118   } else static if (isCallable!T) {
119     import std.traits : ReturnType;
120     alias getStyleSetsExtends = .getStyleSetsExtends!(ReturnType!T);
121   } else {
122     alias styleSetSymbols = Filter!(hasStyleSetUDA, T.tupleof);
123     alias children = staticMap!(TypeOf, getSymbolsByUDA!(T, child));
124     alias extendedStyleSets = staticMap!(extractExtendedStyleSet, styleSetSymbols);
125     static if (children.length > 0)
126       alias childrenExtendedStyleSets = staticMap!(.getStyleSetsExtends, children);
127     else
128       alias childrenExtendedStyleSets = AliasSeq!();
129     alias getStyleSetsExtends = AliasSeq!(childrenExtendedStyleSets, extendedStyleSets);
130   }
131 }
132 
133 struct ExtendedStyleSet(alias Set, alias sym) {}
134 
135 template extractExtendedStyleSet(alias sym) {
136   alias ExtendedStyleSetCurried = ApplyRight!(ExtendedStyleSet, sym);
137   alias extractExtendedStyleSet = staticMap!(ExtendedStyleSetCurried, staticMap!(extractStyleSetStruct, getUDAs!(sym, styleset)));
138 }
139 
140 template getFullName(alias sym) {
141   import std.traits;
142   static if (is(sym) && !is(TemplateOf!sym : void)) {
143     alias Base = TemplateOf!sym;
144     enum namePart = __traits(identifier, sym);//Base.stringof;
145   } else {
146     alias Base = sym;
147     enum namePart = __traits(identifier, sym);
148   }
149   static if (__traits(compiles, __traits(parent, Base))) {
150     enum getFullName = namePart ~ "." ~ getFullName!(__traits(parent, Base));
151   } else
152     enum getFullName = namePart;
153 }
154 
155 unittest {
156   struct Theme {
157   }
158   getFullName!(Style.disabled).should == "disabled.Style.css.spasm";
159   getFullName!(StyleTmpl!(Theme).disabled).should == "disabled.StyleTmpl.css.spasm";
160 }
161 
162 template GetCssClassName(Node, string style) {
163   alias StyleSets = getStyleSet!Node;
164   static if (StyleSets.length == 0)
165     enum GetCssClassName = style;
166   else static if (StyleSets.length > 1)
167     static assert("Cannot have more than one styleset");
168   else {
169     enum GetCssClassName = GenerateCssClassName!(style ~ "." ~ getFullName!(StyleSets[0]));
170   }
171 }
172 
173 template getCssKeyValue(T, string defaultName) {
174   alias symbol = Symbol!(T, defaultName);
175   static if (isAggregateType!(typeof(symbol))) {
176     alias Tchild = typeof(symbol);
177     alias names = FieldNameTuple!Tchild;
178     alias values = staticMap!(ApplyLeft!(.getCssKeyValue, Tchild), names);
179     enum getCssKeyValue = values;
180   } else {
181     alias names = getStringUDAs!(symbol);
182     static if (names.length > 0)
183       enum name = names[0];
184     else
185       enum name = defaultName;
186     enum getCssKeyValue = AliasSeq!(tuple(toCssProperty!name, __traits(getMember, T.init, defaultName)));
187   }
188 }
189 
190 template toCssProperty(string str)
191 {
192   static if (str.length == 0)
193     enum toCssProperty = "";
194   else static if (str[0] < 0xAA)
195     {
196       static if (str[0] < 'A')
197         enum toCssProperty = str[0] ~ toCssProperty!(str[1 .. $]);
198       else static if (str[0] <= 'Z')
199         enum toCssProperty = "-" ~ (str[0] + 32) ~ toCssProperty!(str[1 .. $]);
200       else
201         enum toCssProperty = str[0] ~ toCssProperty!(str[1 .. $]);
202     }
203   else
204     enum toCssProperty = str[0] ~ toCssProperty!(str[1 .. $]);
205 }
206 
207 template toCss(keyValues...) {
208   static if (keyValues.length == 0) {
209     enum toCss = "";
210   } else {
211     enum toCss = keyValues[0][0] ~ ":" ~ keyValues[0][1] ~ ";" ~ toCss!(keyValues[1..$]);
212   }
213 }
214 
215 template GenerateCss(T) {
216   alias names = FieldNameTuple!T;
217   alias values = staticMap!(ApplyLeft!(getCssKeyValue, T), names);
218   static if (values.length > 0)
219     enum GenerateCss = "{" ~ toCss!(values)[0..$-1] ~ "}";
220   else
221     enum GenerateCss = "";
222 }
223 
224 template chunk(string str, size_t size) {
225     import std.meta : AliasSeq;
226     static if (str.length <= size) {
227         enum chunk = AliasSeq!(str);
228     } else {
229         enum chunk = AliasSeq!(str[0..size], chunk!(str[size..$], size));
230     }
231 }
232 
233 template xor(char a, char b) {
234     enum char xor = a ^ b;
235 }
236 
237 template hashChunk(string a, B...) {
238   static if (is(typeof(B[0]) == string)) {
239     enum b = B[0];
240     import std.meta : AliasSeq;
241     static if (a.length == 0 && b.length == 0)
242       enum hashChunk = AliasSeq!();
243     else static if (a.length == 0)
244       enum hashChunk = AliasSeq!(int(b[0]), hashChunk!(a, b[1..$]));
245     else static if (b.length == 0)
246       enum hashChunk = AliasSeq!(int(a[0]), hashChunk!(a[1..$], b));
247     else {
248       enum hashChunk = AliasSeq!(xor!(a[0],b[0]), hashChunk!(a[1..$], b[1..$]));
249     }
250   } else {
251     import std.meta : AliasSeq;
252     static if (a.length == 0 && B.length == 0)
253       enum hashChunk = AliasSeq!();
254     else static if (a.length == 0)
255       enum hashChunk = AliasSeq!(int(B[0]), hashChunk!(a, B[1..$]));
256     else static if (B.length == 0)
257       enum hashChunk = AliasSeq!(int(a[0]), hashChunk!(a[1..$], B));
258     else {
259       enum hashChunk = AliasSeq!(xor!(a[0],B[0]), hashChunk!(a[1..$], B[1..$]));
260     }
261   }
262 }
263 
264 template cssIdentifierChar32(size_t idx) {
265   static enum string chars = "abcdefghijklmnopqrstuvwxyz123456";
266   enum cssIdentifierChar32 = chars[idx..idx+1];
267 }
268 
269 template cssIdentifierChar(size_t idx) {
270     static enum string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
271     enum cssIdentifierChar = chars[idx..idx+1];
272 }
273 
274 template toCssIdentifier(Bytes...) if (Bytes.length == 6) {
275     enum toCssIdentifier = cssIdentifierChar!((Bytes[0] >> 4) & 0xF) ~ cssIdentifierChar!((Bytes[0] & 0xF) | ((Bytes[1] >> 2) & 0x30)) ~ cssIdentifierChar!(Bytes[1] & 0x3F) ~ cssIdentifierChar!(Bytes[2] >> 2) ~ cssIdentifierChar!((Bytes[2] & 0x3) | ((Bytes[3] >> 2) & 0x3C)) ~ cssIdentifierChar!((Bytes[3] & 0xF) | ((Bytes[4] >> 2) & 0x30)) ~ cssIdentifierChar!(Bytes[4] & 0x3F) ~ cssIdentifierChar!(Bytes[5] >> 2) ~ cssIdentifierChar!(Bytes[5] & 0x3);
276 }
277 
278 template toCssIdentifier32(Bytes...) if (Bytes.length == 6) {
279   enum toCssIdentifier32 = cssIdentifierChar32!((Bytes[0] >> 4) & 0xF) ~ cssIdentifierChar32!((Bytes[0] & 0xF) | ((Bytes[1] >> 3) & 0x10)) ~ cssIdentifierChar32!((Bytes[1] >> 2) & 0x1F) ~ cssIdentifierChar32!((Bytes[1] & 0x3) | ((Bytes[2] >> 3) & 0x1C)) ~ cssIdentifierChar32!(Bytes[2] & 0x1F) ~ cssIdentifierChar32!((Bytes[3] >> 3) & 0x1F) ~ cssIdentifierChar32!((Bytes[3] & 0x3) | ((Bytes[4] >> 3) & 0x10)) ~ cssIdentifierChar32!((Bytes[4] >> 2) & 0x1F) ~ cssIdentifierChar32!((Bytes[4] & 0x3) | ((Bytes[5] >> 3) & 0x1C)) ~ cssIdentifierChar32!(Bytes[5] & 0x1F);
280 }
281 
282 template reduceChunks(Chunks...) if (Chunks.length > 0) {
283     import std.meta : AliasSeq;
284     static if (Chunks.length == 1)
285         enum reduceChunks = Chunks[0];
286     else static if (Chunks.length == 2)
287         enum reduceChunks = hashChunk!(Chunks[0],Chunks[1]);
288     else static if (Chunks.length > 2) {
289         enum reduceChunks = hashChunk!(Chunks[0],reduceChunks!(Chunks[1..$]));
290     }
291 }
292 
293 template toCssName(string s) {
294   enum toCssName = toCssIdentifier!(reduceChunks!(chunk!(s,6)));
295 }
296 
297 template toCssNameInsensitive(string s) {
298   enum toCssNameInsensitive = toCssIdentifier32!(reduceChunks!(chunk!(s,6)));
299 }
300 
301 unittest {
302   import unit_threaded;
303   enum i = toCssName!"{backgroundColor:gray2}";
304   enum g = toCssName!"{display:inline}";
305   enum h = toCssName!"{backgroundColor:gray}";
306   i.should == "BEAASdIQD";
307   g.should == "HTzNaHeAA";
308   h.should == "BEAACC1QD";
309 }
310 
311 template GenerateCssClassName(string content) {
312   enum GenerateCssClassName = toCssName!content;
313 }
314 
315 template GenerateCssClassName(string base, alias T) {
316   enum nestedName = __traits(identifier, T);
317   enum uniqueName = nestedName ~ "." ~ base;
318   alias GenerateCssClassName = GenerateCssClassName!uniqueName;
319 }
320 
321 template GenerateCssClass(string base, alias T) {
322   alias name = GenerateCssClassName!(base, T);
323   enum content = GenerateCss!T;
324   alias nestedClasses = GenerateNestedCssClasses!(T);
325   static if (content.length == 0) {
326     enum GenerateCssClass = nestedClasses;
327   } else
328   enum GenerateCssClass = "." ~ name ~ content ~ nestedClasses;
329 }
330 
331 template GenerateNamedCssClass(string name, alias T) {
332   alias content = GenerateCss!T;
333   alias nestedClasses = GenerateNestedCssClasses!("."~name, T);
334   enum GenerateNamedCssClass = "." ~ name ~ content ~ nestedClasses;
335 }
336 
337 template GetPseudoCssSelector(alias symbol) {
338   template GetName(alias attr) {
339     static if (is(attr : media!content, string content)) {
340       enum GetName = "@media("~ content ~")";
341     } else static if (is(attr : not!cls, cls)) {
342       enum GetName = ":not(." ~ toCssName!(getFullName!cls) ~ ")";
343     } else static if (is(attr)) {
344       enum GetName = "." ~ toCssName!(getFullName!attr);
345     } else
346       enum GetName = ":" ~ attr;
347   }
348   alias attrs = AliasSeq!(__traits(getAttributes, symbol));
349   static assert(attrs.length > 0, "Nested css class must have pseudo class attribute");
350   alias parent = __traits(parent, symbol);
351   enum parentHash = toCssName!(getFullName!(parent));
352   enum GetPseudoCssSelector = "." ~ parentHash ~ Joiner!(staticMap!(GetName, attrs));
353 }
354 
355 unittest {
356   GetPseudoCssSelector!(Style.root.after).should == ".AGILZSwUB:after";
357 }
358 unittest {
359   GetPseudoCssSelector!(Style.root.afterDisabled).should == ".AGILZSwUB.EDbAPAWCD:after";
360 }
361 unittest {
362   GetPseudoCssSelector!(Style.root.afterNotFocused).should == ".AGILZSwUB:not(.BEfMCBUJD):after";
363 }
364 unittest {
365   GetPseudoCssSelector!(Style.root.hoverBefore).should == ".AGILZSwUB:hover:not(.EDbAPAWCD):before";
366 }
367 unittest {
368   GetPseudoCssSelector!(Style.root.resetTouch).should == ".AGILZSwUB@media(hover: none)";
369 }
370 unittest {
371   struct Empty{}
372   GenerateCssSet!(Style, Empty).should == `.AGILZSwUB{background-color:blue}.AGILZSwUB:after{content:""}.AGILZSwUB.EDbAPAWCD:after{content:""}.AGILZSwUB:not(.BEfMCBUJD):after{content:""}.AGILZSwUB:hover:not(.EDbAPAWCD):before{content:""}.AGILZSwUB@media(hover: none){content:""}`;
373 }
374 unittest {
375   struct Empty{}
376   struct Derived {
377     struct root {
378       Style.root base;
379       auto color = "green";
380     }
381   }
382   GenerateCssSet!(Derived, Empty).should == `.ETbCNAQeD{background-color:blue;color:green}`;
383 }
384 
385 template GenerateNestedCssClass(alias symbol) {
386   alias content = GenerateCss!symbol;
387   enum GenerateNestedCssClass = GetPseudoCssSelector!(symbol) ~ content;
388 }
389 
390 template GenerateNestedCssClasses(alias base, T) {
391   template WithPrefix(alias symbol) {
392     enum WithPrefix = base ~ GenerateNestedCssClass!symbol;
393   }
394   alias members = AliasSeq!(__traits(allMembers, T));
395   alias symbols = staticMap!(ApplyLeft!(Symbol,T), members);
396   alias nestedClasses = Filter!(isType,symbols);
397   static if (nestedClasses.length == 0)
398     enum GenerateNestedCssClasses = "";
399   else
400     enum GenerateNestedCssClasses = Joiner!(staticMap!(WithPrefix, nestedClasses));
401 }
402 
403 template GenerateNestedCssClasses(T) {
404   enum GenerateNestedCssClasses = GenerateNestedCssClasses!("",T);
405 }
406 
407 template GenerateCssSet(alias T, Theme) {
408   template isTypeInvert(alias T) {
409     enum isTypeInvert = !isType!T;
410   }
411   static if (__traits(isTemplate, T))
412     alias StyleSet = T!Theme;
413   else
414     alias StyleSet = T;
415   enum baseName = getFullName!(T);
416   alias members = AliasSeq!(__traits(allMembers, StyleSet));
417   alias symbols = staticMap!(ApplyLeft!(Symbol,StyleSet), members);
418   alias typeSymbols = Filter!(isType, symbols);
419   enum GenerateCssSet = Joiner!(staticMap!(ApplyLeft!(GenerateCssClass, baseName), typeSymbols));
420 }
421 
422 template GenerateExtendedCssClass(alias T, string name, Child) {
423   enum isDirectExtendedStyle = getSymbolsByUDA!(Child, style!(T.stringof)).length > 0;
424   enum attributeSelector = "["~name~"]";
425   static if (isDirectExtendedStyle) {
426     alias content = GenerateCss!T;
427     alias nestedClasses = GenerateNestedCssClasses!(attributeSelector, T);
428     enum GenerateExtendedCssClass = attributeSelector~content~nestedClasses;
429   } else static if (!hasUDA!(T, Extend)) {
430     static assert(false, T.stringof ~ " needs an Extend attribute");
431   } else {
432     alias extendsAttrs = getUDAs!(T, Extend);
433     static assert(extendsAttrs.length == 1, T.stringof ~ " can only have one Extend attribute");
434     static if (is(extendsAttrs[0] : Extend!(Base), Base)) {
435       alias baseContent = GenerateCss!Base;
436       alias baseName = GenerateCssClassName!baseContent;
437       enum GenerateExtendedCssClass = attributeSelector~" "~GenerateNamedCssClass!(baseName, T);
438     }
439   }
440 }
441 
442 template GenerateExtendedStyleSetName(alias Set) {
443   alias name = getFullName!(Set);
444   alias GenerateExtendedStyleSetName = toCssNameInsensitive!name;
445 }
446 
447 template GenerateCssSetExtends(alias T, Theme) {
448   static if (is(T : ExtendedStyleSet!(Set, sym), alias Set, alias sym)) {
449     static if (__traits(isTemplate, Set))
450       alias StyleSet = Set!Theme;
451     else
452       alias StyleSet = Set;
453     alias members = AliasSeq!(__traits(allMembers, StyleSet));
454     alias symbols = staticMap!(ApplyLeft!(Symbol,StyleSet), members);
455     alias name = GenerateExtendedStyleSetName!Set;
456     enum GenerateCssSetExtends = Joiner!(staticMap!(ApplyRight!(GenerateExtendedCssClass, name, typeof(sym)), symbols));
457   } else
458     enum GenerateCssSetExtends = "";
459 }
460 
461 unittest {
462   import unit_threaded;
463   import spasm.node;
464   struct Empty{}
465   struct Overwrite(Theme) {
466     @Extend!(Style.root)
467     struct stuff {
468       string backgroundColor = "green";
469     }
470   }
471   @styleset!Style
472   struct Bar {
473     @style!"root" mixin Node!"div";
474   }
475   struct Foo {
476     mixin Node!"span";
477     @styleset!Overwrite @child Bar bar;
478   }
479   // TODO: currently extending only works when the StyleSet is a Template
480   // TODO: fix the generated class name that is extended
481   // GetCss!(Foo, Empty).should == "asdfasdf";
482 }
483 
484 template GetCss(T, Theme) {
485   alias extendedSets = getStyleSetsExtends!T;
486   alias sets = getStyleSets!T;
487   enum css = Joiner!(staticMap!(ApplyRight!(GenerateCssSet, Theme), sets), staticMap!(ApplyRight!(GenerateCssSetExtends, Theme), AliasSeq!(extendedSets)));
488   static if (css.length == 0)
489     enum GetCss = "";
490   else
491     enum GetCss = css;
492 }
493 
494 // 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).
495 // 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.
496 // 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