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