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