1 module fluid.style_macros; 2 3 import std.range; 4 import std.traits; 5 import std.string; 6 7 import fluid.style; 8 9 @safe: 10 11 private static { 12 13 Theme currentTheme; 14 Style[] styleStack; 15 16 } 17 18 /// Create a new theme defined from D code given through a template argument. The code will define the default style 19 /// for each node, and can use `Node.(...)Add` calls to define other styles within the theme. 20 /// 21 /// Params: 22 /// init = D code to initialize the Node style with. 23 /// parent = Inherit styles from a parent theme. 24 /// Returns: The created theme. 25 template makeTheme(string init) { 26 27 import fluid.default_theme; 28 29 // This ugly template is a workaround for https://issues.dlang.org/show_bug.cgi?id=22208 30 // We can't use inout here, sorry... 31 32 Theme makeTheme(Theme theme = fluidDefaultTheme) { 33 34 makeThemeImpl!init(theme.dup); 35 return currentTheme; 36 37 } 38 39 const(Theme) makeTheme(const Theme theme) @trusted { 40 41 makeThemeImpl!init(cast(Theme) theme.dup); 42 return cast(const) currentTheme; 43 44 } 45 46 immutable(Theme) makeTheme(immutable Theme theme) @trusted { 47 48 makeThemeImpl!init(cast(Theme) theme.dup); 49 return cast(immutable) currentTheme; 50 51 } 52 53 } 54 55 private void makeThemeImpl(string init)(Theme parent) { 56 57 import fluid.node; 58 59 // Create the theme 60 currentTheme = parent; 61 62 // Load init 63 { 64 65 // If the theme has a default style definition, push it 66 if (auto nodeStyle = &Node.styleKey in currentTheme) { 67 68 styleStack ~= *nodeStyle; 69 70 } 71 72 // No, push the default style instead 73 else styleStack ~= Style.init; 74 75 // Clear the style when done 76 scope (exit) styleStack.popBack(); 77 78 // Add the node style 79 nestStyle!(init, Node.styleKey); 80 81 } 82 83 assert(styleStack.length == 0, "The style stack has not been emptied"); 84 85 } 86 87 // Internal, public because required in mixins 88 Style nestStyle(string init, alias styleKey)() { 89 90 // TODO: inherit from previous instance 91 Style[] inheritance; 92 93 // Inherit from previous instance 94 if (auto prev = &styleKey in currentTheme) inheritance ~= *prev; 95 96 // Inherit from the parent style 97 // Takes precendence, as this behavior is more expected 98 if (styleStack.length) inheritance ~= styleStack[$-1]; 99 100 101 // Create a new style inheriting from them 102 auto style = new Style(inheritance); 103 104 105 // Init was given 106 static if (init.length) { 107 108 // Push the style to the stack 109 styleStack ~= style; 110 scope (exit) styleStack.popBack(); 111 112 // Update the style 113 style.update!(init, __traits(parent, styleKey)); 114 115 } 116 117 // Add the result to the theme 118 return currentTheme[&styleKey] = style; 119 120 } 121 122 /// `DefineStyles` has been renamed to `defineStyles`. Deprecation warning is not to be issued yet. 123 alias DefineStyles = defineStyles; 124 125 /// Define style fields for a node and let them be affected by themes. 126 /// 127 /// Note: This should be used in *every* node, even if empty, to ensure keys are inherited properly. 128 /// 129 /// Params: 130 /// names = A list of styles to define. 131 mixin template defineStyles(names...) { 132 133 @safe: 134 135 import std.meta : Filter; 136 import std.format : format; 137 import std.traits : BaseClassesTuple; 138 139 import fluid.style : StyleKey; 140 import fluid.utils : StaticFieldNames; 141 142 private alias Parent = BaseClassesTuple!(typeof(this))[0]; 143 private alias MemberType(alias member) = typeof(__traits(getMember, Parent, member)); 144 145 private enum isStyleKey(alias member) = is(MemberType!member == immutable(StyleKey)); 146 private alias StyleKeys = Filter!(isStyleKey, StaticFieldNames!Parent); 147 148 // Inherit style keys 149 static foreach (name; StyleKeys) { 150 151 // Inherit default value 152 mixin("static immutable StyleKey " ~ name ~ ";"); 153 154 // Helper function to declare nested styles 155 mixin(name[0 .. $-3].format!q{ 156 157 static Style %1$sAdd(string content = "")() { 158 159 return nestStyle!(content, %1$sKey); 160 161 } 162 163 }); 164 165 } 166 167 // Local styles 168 static foreach(i, name; names) { 169 170 // Only check even names 171 static if (i % 2 == 0) { 172 173 // Define the key 174 // TODO: Make the stylekey private and add a getter for it. This getter could statically check for accessing 175 // missing `mixin DefineStyles` statements and then imply the statement with a warning. 176 mixin(name.format!q{ static immutable StyleKey %sKey; }); 177 178 // Define the value 179 mixin(name.format!q{ protected Style %s; }); 180 181 // Helper function to declare nested styles 182 mixin(name.format!q{ 183 184 static Style %sAdd(string content = "")() { 185 186 return nestStyle!(content, %1$sKey); 187 188 } 189 190 }); 191 192 } 193 194 } 195 196 private enum inherits = !is(typeof(super) == Object); 197 198 // Load styles 199 override protected void reloadStylesImpl() { 200 201 // Inherit styles 202 static if (inherits) super.reloadStylesImpl(); 203 204 // Load inherited keys (so class A:B will load both B.styleKey and A.styleKey) 205 static foreach (name; StyleKeys) {{ 206 207 if (auto style = &mixin(name) in theme) { 208 209 mixin("this." ~ name[0 .. $-3]) = *style; 210 211 } 212 213 // No default value, the parent has already handled it 214 215 }} 216 217 // Load local keys and load defaults if none are set 218 static foreach (i, name; names) { 219 220 static if (i % 2 == 0) { 221 222 // We're deferring the default for later to make sure it uses up-to-date values 223 mixin("this." ~ name) = theme.get(&mixin(name ~ "Key"), null); 224 225 } 226 227 } 228 229 } 230 231 override void loadDefaultStyles() { 232 233 // Inherit 234 static if (inherits) super.loadDefaultStyles(); 235 236 // Load defaults for each unset style 237 static foreach (i, name; names) { 238 239 static if (i % 2 == 0) { 240 241 // Found an unset value 242 if (mixin("this." ~ name) is null) { 243 244 // Set the default 245 mixin("this." ~ name) = mixin(names[i+1]); 246 247 } 248 249 } 250 251 } 252 253 } 254 255 }