1 /// This module defines templates and structs used to build themes, including a set of special setters to use within 2 /// theme definitions. Because of the amount of general symbols defined by the module, it is not imported by default 3 /// and has to be imported explicitly. Do not import this globally, but within functions that define themes. 4 module fluid.theme; 5 6 import std.meta; 7 import std.range; 8 import std.string; 9 import std.traits; 10 11 import fluid.node; 12 import fluid.style; 13 import fluid.backend; 14 import fluid.structs; 15 16 17 @safe: 18 19 20 deprecated("Styles have been reworked and defineStyles is now a no-op. To be removed in 0.8.0.") { 21 mixin template defineStyles(args...) { } 22 mixin template DefineStyles(args...) { } 23 } 24 25 deprecated("makeTheme is now a no-op. Use `Theme()` and refer to the changelog for updates. To be removed in 0.8.0.") 26 Theme makeTheme(string s, Ts...)(Ts) { 27 28 return Theme.init; 29 30 } 31 32 /// Node theme. 33 struct Theme { 34 35 Rule[][TypeInfo_Class] rules; 36 37 /// Create a new theme using the given rules. 38 this(Rule[] rules...) { 39 40 // Inherit from default theme 41 this(fluidDefaultTheme.rules.dup); 42 add(rules); 43 44 } 45 46 /// Create a theme using the given set of rules. 47 this(Rule[][TypeInfo_Class] rules) { 48 49 this.rules = rules; 50 51 } 52 53 /// Check if the theme was initialized. 54 bool opCast(T : bool)() const { 55 56 return rules !is null; 57 58 } 59 60 /// Create a new theme that derives from another. 61 /// 62 /// Note: This doesn't duplicate rules. If rules are changed or reassigned, they will may the parent theme. In a 63 /// typical scenario you only add new rules. 64 Theme derive(Rule[] rules...) { 65 66 auto newTheme = this.dup; 67 newTheme.add(rules); 68 return newTheme; 69 70 } 71 72 /// Add rules to the theme. 73 void add(Rule[] rules...) { 74 75 foreach (rule; rules) { 76 77 this.rules[rule.selector.type] ~= rule; 78 79 } 80 81 } 82 83 /// Make the node use this theme. 84 void apply(Node node) { 85 86 node.theme = this; 87 88 } 89 90 /// Apply this theme on the given style. 91 /// Returns: An array of delegates used to update the style at runtime. 92 Rule.StyleDelegate[] apply(Node node, ref Style style) { 93 94 Rule.StyleDelegate[] dgs; 95 96 void applyFor(TypeInfo_Class ti) { 97 98 // Inherit from parents 99 if (ti.base) applyFor(ti.base); 100 101 // Find matching rules 102 if (auto rules = ti in rules) { 103 104 // Test against every rule 105 foreach (rule; *rules) { 106 107 // Run the rule, and add the callback if any 108 if (rule.applyStatic(node, style) && rule.styleDelegate) { 109 110 dgs ~= rule.styleDelegate; 111 112 } 113 114 } 115 116 } 117 118 } 119 120 applyFor(typeid(node)); 121 122 return dgs; 123 124 } 125 126 /// Duplicate the theme. This is not recursive; rules are not copied. 127 Theme dup() { 128 129 return Theme(rules.dup); 130 131 } 132 133 } 134 135 unittest { 136 137 import fluid.label; 138 139 Theme theme; 140 141 with (Rule) 142 theme.add( 143 rule!Label( 144 textColor = color!"#abc", 145 ), 146 ); 147 148 auto io = new HeadlessBackend; 149 auto root = label(theme, "placeholder"); 150 root.io = io; 151 152 root.draw(); 153 io.assertTexture(root.text.texture.chunks[0], Vector2(0, 0), color!"#fff"); 154 assert(root.text.texture.chunks[0].palette[0] == color("#abc")); 155 156 } 157 158 unittest { 159 160 import fluid.frame; 161 162 auto frameRule = rule!Frame( 163 Rule.margin.sideX = 8, 164 Rule.margin.sideY = 4, 165 ); 166 auto theme = nullTheme.derive(frameRule); 167 168 // Test selector 169 assert(frameRule.selector.type == typeid(Frame)); 170 assert(frameRule.selector.tags.empty); 171 172 // Test fields 173 auto style = Style.init; 174 frameRule.apply(vframe(), style); 175 assert(style.margin == [8, 8, 4, 4]); 176 assert(style.padding == style.init.padding); 177 assert(style.textColor == style.textColor.init); 178 179 // No dynamic rules 180 assert(rule.styleDelegate is null); 181 182 auto io = new HeadlessBackend; 183 auto root = vframe(theme); 184 root.io = io; 185 186 root.draw(); 187 188 assert(root.style == style); 189 190 } 191 192 unittest { 193 194 // Inheritance 195 196 import fluid.label; 197 import fluid.button; 198 199 auto myTheme = nullTheme.derive( 200 rule!Node( 201 Rule.margin.sideX = 8, 202 ), 203 rule!Label( 204 Rule.margin.sideTop = 6, 205 ), 206 rule!Button( 207 Rule.margin.sideBottom = 4, 208 ), 209 ); 210 211 auto style = Style.init; 212 myTheme.apply(button("", delegate { }), style); 213 214 assert(style.margin == [8, 8, 6, 4]); 215 216 } 217 218 unittest { 219 220 // Copying rules & dynamic rules 221 222 import fluid.label; 223 import fluid.button; 224 225 auto myRule = rule!Label( 226 Rule.textColor = color!"011", 227 Rule.backgroundColor = color!"faf", 228 (Label node) => node.isDisabled 229 ? rule(Rule.tint = color!"000a") 230 : rule() 231 ); 232 233 auto myTheme = Theme( 234 rule!Label( 235 myRule, 236 ), 237 rule!Button( 238 myRule, 239 Rule.textColor = color!"012", 240 ), 241 ); 242 243 auto style = Style.init; 244 auto myLabel = label(""); 245 246 // Apply the style, including dynamic rules 247 auto cbs = myTheme.apply(myLabel, style); 248 assert(cbs.length == 1); 249 cbs[0](myLabel).apply(myLabel, style); 250 251 assert(style.textColor == color!"011"); 252 assert(style.backgroundColor == color!"faf"); 253 assert(style.tint == Style.init.tint); 254 255 // Disable the node and apply again, it should change nothing 256 myLabel.disable(); 257 myTheme.apply(myLabel, style); 258 assert(style.tint == Style.init.tint); 259 260 // Apply the callback, tint should change 261 cbs[0](myLabel).apply(myLabel, style); 262 assert(style.tint == color!"000a"); 263 264 } 265 266 unittest { 267 268 import fluid.label; 269 270 auto myLabel = label(""); 271 272 void testMargin(Rule rule, float[4] margin) { 273 274 auto style = Style.init; 275 276 rule.apply(myLabel, style); 277 278 assert(style.margin == margin); 279 280 } 281 282 with (Rule) { 283 284 testMargin(rule(margin = 2), [2, 2, 2, 2]); 285 testMargin(rule(margin.sideX = 2), [2, 2, 0, 0]); 286 testMargin(rule(margin.sideY = 2), [0, 0, 2, 2]); 287 testMargin(rule(margin.sideTop = 2), [0, 0, 2, 0]); 288 testMargin(rule(margin.sideBottom = 2), [0, 0, 0, 2]); 289 testMargin(rule(margin.sideX = 2, margin.sideY = 4), [2, 2, 4, 4]); 290 testMargin(rule(margin = [1, 2, 3, 4]), [1, 2, 3, 4]); 291 testMargin(rule(margin.sideX = [1, 2]), [1, 2, 0, 0]); 292 293 } 294 295 } 296 297 unittest { 298 299 import std.math; 300 import fluid.label; 301 302 auto myRule = rule( 303 Rule.opacity = 0.5, 304 ); 305 auto style = Style.init; 306 307 myRule.apply(label(""), style); 308 309 assert(style.opacity.isClose(127/255f)); 310 assert(style.tint == color!"ffffff7f"); 311 312 auto secondRule = rule( 313 Rule.tint = color!"abc", 314 Rule.opacity = 0.6, 315 ); 316 317 style = Style.init; 318 secondRule.apply(label(""), style); 319 320 assert(style.opacity.isClose(153/255f)); 321 assert(style.tint == color!"abc9"); 322 323 } 324 325 // Define field setters for each field, to use by importing or through `Rule.property = value` 326 static foreach (field; StyleTemplate.fields) { 327 328 mixin(format!q{ 329 alias %1$s = Field!("%1$s", typeof(field)).make; 330 }(__traits(identifier, field))); 331 332 } 333 334 // Separately define opacity for convenience 335 auto opacity(float value) { 336 337 import std.algorithm : clamp; 338 339 return tint.a = cast(ubyte) clamp(value * ubyte.max, ubyte.min, ubyte.max); 340 341 } 342 343 /// Selector is used to pick a node based on its type and specified tags. 344 struct Selector { 345 346 /// Type of the node to match. 347 TypeInfo_Class type; 348 349 /// Tags needed by the selector. 350 TagList tags; 351 352 /// If true, this selector will reject any match. 353 bool rejectAll; 354 355 /// Returns a selector that doesn't match anything 356 static Selector none() { 357 358 Selector result; 359 result.rejectAll = true; 360 return result; 361 362 } 363 364 /// Test if the selector matches given node. 365 bool test(Node node) { 366 367 return !rejectAll 368 && testType(typeid(node)) 369 && testTags(node.tags); 370 371 } 372 373 /// Test if the given type matches the selector. 374 bool testType(TypeInfo_Class type) { 375 376 return !this.type || this.type.isBaseOf(type); 377 378 } 379 380 unittest { 381 382 import fluid.input; 383 import fluid.label; 384 import fluid.button; 385 386 auto myLabel = label("my label"); 387 auto myButton = button("my button", delegate { }); 388 389 auto selector = Selector(typeid(Node)); 390 391 assert(selector.test(myLabel)); 392 assert(selector.test(myButton)); 393 394 auto anotherSelector = Selector(typeid(Button)); 395 396 assert(!anotherSelector.test(myLabel)); 397 assert(anotherSelector.test(myButton)); 398 399 auto noSelector = Selector(); 400 401 assert(noSelector.test(myLabel)); 402 assert(noSelector.test(myButton)); 403 404 } 405 406 /// True if all tags in this selector are present on the given node. 407 bool testTags(TagList tags) { 408 409 // TODO linear search if there's only one tag 410 return this.tags.intersect(tags).walkLength == this.tags.length; 411 412 } 413 414 unittest { 415 416 import fluid.label; 417 418 @NodeTag enum good; 419 @NodeTag enum bad; 420 421 auto selector = Selector(typeid(Label)).addTags!good; 422 423 assert(selector.test(label(.tags!good, ""))); 424 assert(!selector.test(label(.tags!bad, ""))); 425 assert(!selector.test(label(""))); 426 427 } 428 429 /// Create a new selector requiring the given set of tags. 430 Selector addTags(tags...)() { 431 432 return Selector(type, this.tags.add!tags); 433 434 } 435 436 } 437 438 /// Create a style rule for the given node. 439 /// 440 /// Template parameters are used to select the node the rule applies to, based on its type and the tags it has. Regular 441 /// parameters define the changes made by the rule. These are created by using automatically defined members of `Rule`, 442 /// which match names of `Style` fields. For example, to change the property `Style.textColor`, one would assign 443 /// `Rule.textColor` inside this parameter list. 444 /// 445 /// --- 446 /// rule!Label( 447 /// Rule.textColor = color("#fff"), 448 /// Rule.backgroundColor = color("#000"), 449 /// ) 450 /// --- 451 /// 452 /// It is also possible to pass a `when` subrule to apply changes based on runtime conditions: 453 /// 454 /// --- 455 /// rule!Button( 456 /// Rule.backgroundColor = color("#fff"), 457 /// when!"a.isHovered"( 458 /// Rule.backgroundColor = color("#ccc"), 459 /// ) 460 /// ) 461 /// --- 462 /// 463 /// If some directives are repeated across different rules, they can be reused: 464 /// 465 /// --- 466 /// myRule = rule( 467 /// Rule.textColor = color("#000"), 468 /// ), 469 /// rule!Button( 470 /// myRule, 471 /// Rule.backgroundColor = color("#fff"), 472 /// ) 473 /// --- 474 /// 475 /// Moreover, rules respect inheritance. Since `Button` derives from `Label` and `Node`, `rule!Label` will also apply 476 /// to buttons, and so will `rule!Node`. 477 /// 478 /// For more advanced use-cases, it is possible to directly pass a delegate that accepts a node and returns a 479 /// subrule. 480 /// 481 /// --- 482 /// rule!Button( 483 /// a => rule( 484 /// Rule.backgroundColor = pickColor(), 485 /// ) 486 /// ) 487 /// --- 488 /// 489 /// It is recommended to use the `with (Rule)` statement to make rule definitions clearer. 490 template rule(T : Node = Node, tags...) { 491 492 Rule rule(Ts...)(Ts fields) { 493 494 enum isWhenRule(alias field) = is(typeof(field) : WhenRule!dg, alias dg); 495 enum isDynamicRule(alias field) = isCallable!field || isWhenRule!field || is(typeof(field) : Rule); 496 497 Rule result; 498 499 // Create the selector 500 result.selector = Selector(typeid(T)).addTags!tags; 501 502 // Load fields 503 static foreach (i, field; fields) {{ 504 505 // Directly assigned field 506 static if (is(typeof(field) : Field!(fieldName, T), string fieldName, T)) { 507 508 // Add to the result 509 field.apply(result.fields); 510 511 } 512 513 // Copy from another rule 514 else static if (is(typeof(field) : Rule)) { 515 516 assert(field.selector.testType(typeid(T)), 517 format!"Cannot paste rule for %s into a rule for %s"(field.selector.type, typeid(T))); 518 519 // Copy fields 520 field.fields.apply(result.fields); 521 // Also add delegates below... 522 523 524 } 525 526 // Dynamic rule 527 else static if (isDynamicRule!field) { } 528 529 else static assert(false, format!"Unrecognized type %s (argument index %s)"(typeof(field).stringof, i)); 530 531 }} 532 533 // Load delegates 534 alias delegates = Filter!(isDynamicRule, fields); 535 536 // Build the dynamic rule delegate 537 static if (delegates.length) 538 result.styleDelegate = (Node node) { 539 540 Rule dynamicResult; 541 542 // Cast the node into proper type 543 auto castNode = cast(T) node; 544 assert(castNode, "styleDelegate was passed an invalid node"); 545 546 static foreach (dg; delegates) { 547 548 // A "when" rule 549 static if (isWhenRule!dg) { 550 551 // Test the predicate before applying the result 552 if (dg.predicate(castNode)) { 553 554 dg.rule.apply(node, dynamicResult); 555 556 } 557 558 // Apply the alternative if predicate fails 559 else dg.alternativeRule.apply(node, dynamicResult); 560 561 } 562 563 // Regular rule, forward to its delegate 564 else static if (is(typeof(dg) : Rule)) { 565 566 if (dg.styleDelegate) 567 dynamicResult = dg.styleDelegate(node); 568 569 } 570 571 // Use the delegate and apply the result on the template of choice 572 else dg(castNode).apply(node, dynamicResult); 573 574 } 575 576 return dynamicResult; 577 578 }; 579 580 return result; 581 582 } 583 584 } 585 586 @trusted 587 unittest { 588 589 // Rule copying semantics 590 591 import fluid.label; 592 import fluid.button; 593 import std.exception; 594 import core.exception : AssertError; 595 596 auto generalRule = rule( 597 Rule.textColor = color!"#001", 598 ); 599 auto buttonRule = rule!Button( 600 Rule.backgroundColor = color!"#002", 601 generalRule, 602 ); 603 assertThrown!AssertError( 604 rule!Label(buttonRule), 605 "Label rule cannot inherit from a Button rule." 606 ); 607 608 assertNotThrown(rule!Button(buttonRule)); 609 assertNotThrown(rule!Button(rule!Label())); 610 611 } 612 613 /// Rules specify changes that are to be made to the node's style. 614 struct Rule { 615 616 alias StyleDelegate = Rule delegate(Node node) @safe; 617 alias loadTypeface = Style.loadTypeface; 618 619 public import fluid.theme; 620 621 /// Selector to filter items that should match this rule. 622 Selector selector; 623 624 /// Fields affected by this rule and their values. 625 StyleTemplate fields; 626 627 /// Callback for updating the style dynamically. May be null. 628 StyleDelegate styleDelegate; 629 630 alias field(string name) = __traits(getMember, StyleTemplate, name); 631 alias FieldType(string name) = field!name.Type; 632 633 /// Combine with another rule. Applies dynamic rules immediately. 634 bool apply(Node node, ref Rule rule) { 635 636 // Test against the selector 637 if (!selector.test(node)) return false; 638 639 // Apply changes 640 fields.apply(rule.fields); 641 642 // Check for delegates 643 if (styleDelegate) { 644 645 // Run and apply the delegate 646 styleDelegate(node).apply(node, rule); 647 648 } 649 650 return true; 651 652 } 653 654 /// Apply this rule on the given style. 655 /// Returns: True if applied, false if not. 656 bool apply(Node node, ref Style style) { 657 658 // Apply the rule 659 if (!applyStatic(node, style)) return false; 660 661 // Check for delegates 662 if (styleDelegate) { 663 664 // Run and apply the delegate 665 styleDelegate(node).apply(node, style); 666 667 } 668 669 return true; 670 671 } 672 673 /// Apply this rule on the given style. Ignores dynamic styles. 674 /// Returns: True if applied, false if not. 675 bool applyStatic(Node node, ref Style style) { 676 677 // Test against the selector 678 if (!selector.test(node)) return false; 679 680 // Apply changes 681 fields.apply(style); 682 683 // TODO dynamic rules 684 685 return true; 686 687 } 688 689 } 690 691 /// Branch out in a rule to apply styling based on a runtime condition. 692 WhenRule!predicate when(alias predicate, Args...)(Args args) { 693 694 return WhenRule!predicate(rule(args)); 695 696 } 697 698 struct WhenRule(alias dg) { 699 700 import std.functional; 701 702 /// Function to evaluate to test if the rule should be applied. 703 alias predicate = unaryFun!dg; 704 705 /// Rule to apply. 706 Rule rule; 707 708 /// Rule to apply when the predicate fails. 709 Rule alternativeRule; 710 711 /// Specify rule to apply in case the predicate fails. An `else` branch. 712 WhenRule otherwise(Args...)(Args args) { 713 714 // TODO else if? 715 716 auto result = this; 717 result.alternativeRule = .rule(args); 718 return result; 719 720 } 721 722 723 } 724 725 unittest { 726 727 import fluid.label; 728 729 auto myTheme = Theme( 730 rule!Label( 731 Rule.textColor = color!"100", 732 Rule.backgroundColor = color!"aaa", 733 734 when!"a.isEmpty"(Rule.textColor = color!"200"), 735 when!"a.text == `two`"(Rule.backgroundColor = color!"010") 736 .otherwise(Rule.backgroundColor = color!"020"), 737 ), 738 ); 739 740 auto io = new HeadlessBackend; 741 auto myLabel = label(myTheme, "one"); 742 743 myLabel.io = io; 744 myLabel.draw(); 745 746 assert(myLabel.pickStyle().textColor == color!"100"); 747 assert(myLabel.pickStyle().backgroundColor == color!"020"); 748 assert(myLabel.style.backgroundColor == color!"aaa"); 749 750 myLabel.text = ""; 751 752 assert(myLabel.pickStyle().textColor == color!"200"); 753 assert(myLabel.pickStyle().backgroundColor == color!"020"); 754 assert(myLabel.style.backgroundColor == color!"aaa"); 755 756 myLabel.text = "two"; 757 758 assert(myLabel.pickStyle().textColor == color!"100"); 759 assert(myLabel.pickStyle().backgroundColor == color!"010"); 760 assert(myLabel.style.backgroundColor == color!"aaa"); 761 762 } 763 764 struct StyleTemplate { 765 766 alias fields = NoDuplicates!(getSymbolsByUDA!(Style, Style.Themable)); 767 768 // Create fields for every themable item 769 static foreach (field; fields) { 770 771 static if (!isFunction!(typeof(field))) 772 mixin(q{ FieldValue!(typeof(field)) }, __traits(identifier, field), ";"); 773 774 } 775 776 /// Update the given style using this template. 777 void apply(ref Style style) { 778 779 // TODO only iterate on fields that have changed 780 static foreach (field; fields) {{ 781 782 auto newValue = mixin("this.", __traits(identifier, field)); 783 784 newValue.apply(__traits(child, style, field)); 785 786 }} 787 788 } 789 790 /// Combine with another style template, applying all local rules on the other template. 791 void apply(ref StyleTemplate style) { 792 793 static foreach (i, field; fields) { 794 795 this.tupleof[i].apply(style.tupleof[i]); 796 797 } 798 799 } 800 801 string toString() const @trusted { 802 803 string[] items; 804 805 static foreach (field; fields) {{ 806 807 enum name = __traits(identifier, field); 808 auto value = mixin("this.", name); 809 810 if (value.isSet) 811 items ~= format!"%s: %s"(name, value); 812 813 }} 814 815 return format!"StyleTemplate(%-(%s, %))"(items); 816 817 } 818 819 } 820 821 /// `Field` allows defining and performing partial changes to members of Style. 822 struct Field(string fieldName, T) { 823 824 enum name = fieldName; 825 alias Type = T; 826 827 FieldValue!T value; 828 829 static Field make(Item, size_t n)(Item[n] value) { 830 831 Field field; 832 field.value = value; 833 return field; 834 835 } 836 837 static Field make(T)(T value) 838 if (!isStaticArray!T) 839 do { 840 841 Field field; 842 field.value = value; 843 return field; 844 845 } 846 847 static Field make() { 848 849 return Field(); 850 851 } 852 853 /// Apply on a style template. 854 void apply(ref StyleTemplate style) { 855 856 value.apply(__traits(child, style, Rule.field!fieldName)); 857 858 } 859 860 // Operators for structs 861 static if (is(T == struct)) { 862 863 template opDispatch(string field) 864 if (__traits(hasMember, T, field)) 865 { 866 867 Field opDispatch(Input)(Input input) return { 868 869 __traits(getMember, value, field) = input; 870 return this; 871 872 } 873 874 } 875 876 } 877 878 // Operators for arrays 879 static if (isStaticArray!T) { 880 881 private size_t[2] slice; 882 883 Field opAssign(Input, size_t n)(Input[n] input) return { 884 885 value[slice] = input; 886 return this; 887 888 } 889 890 Field opAssign(Input)(Input input) return 891 if (!isStaticArray!Input) 892 do { 893 894 value[slice] = input; 895 return this; 896 897 } 898 899 inout(Field) opIndex(size_t i) return inout { 900 901 return inout Field(value, [i, i+1]); 902 903 } 904 905 inout(Field) opIndex(return inout Field slice) const { 906 907 return slice; 908 909 } 910 911 inout(Field) opSlice(size_t dimension : 0)(size_t i, size_t j) return inout { 912 913 return Field(value, [i, j]); 914 915 } 916 917 } 918 919 } 920 921 unittest { 922 923 auto typeface = Style.loadTypeface(4); 924 auto sample = Rule.typeface = typeface; 925 926 const originalTypeface = typeface; 927 928 assert(sample.name == "typeface"); 929 930 auto target = Style.loadTypeface(3); 931 sample.value.apply(target); 932 933 assert(target is typeface); 934 assert(typeface is originalTypeface); 935 936 } 937 938 unittest { 939 940 auto sampleField = Rule.margin = 4; 941 942 assert(sampleField.name == "margin"); 943 assert(sampleField.slice == [0, 0]); 944 945 float[4] field = [1, 1, 1, 1]; 946 sampleField.value.apply(field); 947 948 assert(field == [4, 4, 4, 4]); 949 950 } 951 952 unittest { 953 954 auto sampleField = Rule.margin.sideX = 8; 955 956 assert(sampleField.name == "margin"); 957 assert(sampleField.slice == [0, 2]); 958 959 float[4] field = [1, 1, 1, 1]; 960 sampleField.value.apply(field); 961 962 assert(field == [8, 8, 1, 1]); 963 964 } 965 966 template FieldValue(T) { 967 968 // Struct 969 static if (is(T == struct)) 970 alias FieldValue = FieldValueStruct!T; 971 972 // Static array 973 else static if (is(T : E[n], E, size_t n)) 974 alias FieldValue = FieldValueStaticArray!(E, n); 975 976 // Others 977 else alias FieldValue = FieldValueOther!T; 978 979 } 980 981 private struct FieldValueStruct(T) { 982 983 alias Type = T; 984 alias ExpandedType = staticMap!(FieldValue, typeof(Type.tupleof)); 985 986 ExpandedType value; 987 bool isSet; 988 989 FieldValueStruct opAssign(FieldValueStruct value) { 990 991 this.value = value.value; 992 this.isSet = value.isSet; 993 994 return this; 995 996 } 997 998 Type opAssign(Type value) { 999 1000 // Mark as set 1001 isSet = true; 1002 1003 // Assign each field 1004 foreach (i, ref field; this.value) { 1005 1006 field = value.tupleof[i]; 1007 1008 } 1009 1010 return value; 1011 1012 } 1013 1014 template opDispatch(string name) 1015 if (__traits(hasMember, Type, name)) 1016 { 1017 1018 T opDispatch(T)(T value) { 1019 1020 enum i = staticIndexOf!(name, FieldNameTuple!Type); 1021 1022 // Mark as set 1023 isSet = true; 1024 1025 // Assign the field 1026 this.value[i] = value; 1027 1028 return value; 1029 1030 } 1031 1032 } 1033 1034 /// Change the given value to match the requested change. 1035 void apply(ref Type value) { 1036 1037 if (!isSet) return; 1038 1039 foreach (i, field; this.value) { 1040 field.apply(value.tupleof[i]); 1041 } 1042 1043 } 1044 1045 /// Change the given value to match the requested change. 1046 void apply(ref FieldValueStruct value) { 1047 1048 if (!isSet) return; 1049 1050 value.isSet = true; 1051 1052 foreach (i, field; this.value) { 1053 field.apply(value.value[i]); 1054 } 1055 1056 } 1057 1058 } 1059 1060 private struct FieldValueStaticArray(E, size_t n) { 1061 1062 alias Type = E[n]; 1063 alias ExpandedType = FieldValue!E[n]; 1064 1065 ExpandedType value; 1066 bool isSet; 1067 1068 FieldValueStaticArray opAssign(FieldValueStaticArray value) { 1069 1070 this.value = value.value; 1071 this.isSet = value.isSet; 1072 return this; 1073 1074 } 1075 1076 Item[n] opAssign(Item, size_t n)(Item[n] value) { 1077 1078 // Mark as changed 1079 isSet = true; 1080 1081 // Assign each field 1082 foreach (i, ref field; this.value.tupleof) { 1083 1084 field = value.tupleof[i]; 1085 1086 } 1087 1088 return value; 1089 1090 } 1091 1092 Input opAssign(Input)(Input value) 1093 if (!isStaticArray!Input) 1094 do { 1095 1096 // Implicit cast 1097 Type newValue = value; 1098 1099 opAssign(newValue); 1100 1101 return value; 1102 1103 } 1104 1105 Input opIndexAssign(Input)(Input input, size_t index) { 1106 1107 isSet = true; 1108 value[index] = input; 1109 return input; 1110 1111 } 1112 1113 Input[n] opIndexAssign(Input, size_t n)(Input[n] input, size_t[2] indices) { 1114 1115 assert(indices[1] - indices[0] == n, "Invalid slice"); 1116 1117 isSet = true; 1118 foreach (i, ref field; value[indices[0] .. indices[1]]) { 1119 field = input[i]; 1120 } 1121 1122 return input; 1123 1124 } 1125 1126 auto opIndexAssign(Input)(Input input, size_t[2] indices) 1127 if (!isStaticArray!Input) 1128 do { 1129 1130 isSet = true; 1131 return value[indices[0] .. indices[1]] = FieldValue!E(input, true); 1132 1133 } 1134 1135 size_t[2] opSlice(size_t i, size_t j) const { 1136 1137 return [i, j]; 1138 1139 } 1140 1141 /// Change the given value to match the requested change. 1142 void apply(ref Type value) { 1143 1144 if (!isSet) return; 1145 1146 foreach (i, field; this.value.tupleof) { 1147 field.apply(value.tupleof[i]); 1148 } 1149 1150 } 1151 1152 /// Change the given value to match the requested change. 1153 void apply(ref FieldValueStaticArray value) { 1154 1155 if (!isSet) return; 1156 1157 value.isSet = true; 1158 1159 foreach (i, field; this.value.tupleof) { 1160 field.apply(value.value[i]); 1161 } 1162 1163 } 1164 1165 string toString() { 1166 1167 Type output; 1168 apply(output); 1169 return format!"%s"(output); 1170 1171 } 1172 1173 } 1174 1175 private struct FieldValueOther(T) { 1176 1177 alias Type = T; 1178 1179 Type value; 1180 bool isSet; 1181 1182 FieldValueOther opAssign(FieldValueOther value) { 1183 1184 this.value = value.value; 1185 this.isSet = value.isSet; 1186 1187 return this; 1188 1189 } 1190 1191 Type opAssign(Input)(Input value) { 1192 1193 // Mark as set 1194 isSet = true; 1195 1196 return this.value = value; 1197 1198 } 1199 1200 /// Change the given value to match the requested change. 1201 void apply(ref Type value) { 1202 1203 if (!isSet) return; 1204 1205 value = this.value; 1206 1207 } 1208 1209 /// Apply another modification to a field. 1210 void apply(ref FieldValueOther value) { 1211 1212 if (!isSet) return; 1213 1214 value.value = this.value; 1215 value.isSet = true; 1216 1217 } 1218 1219 string toString() const @trusted { 1220 1221 return format!"%s"(value); 1222 1223 } 1224 1225 }