1 module glui.style_macros; 2 3 import std.range; 4 import std.traits; 5 import std.string; 6 import std.typecons; 7 8 import glui.style; 9 import glui.default_theme; 10 11 @safe: 12 13 private static { 14 15 Theme currentTheme; 16 Style[] styleStack; 17 18 } 19 20 /// Create a new theme defined from D code given through a template argument. The code will define the default style 21 /// for each node, and can use `Node.(...)Add` calls to define other styles within the theme. 22 /// 23 /// Params: 24 /// init = D code to initialize the Node style with. 25 /// parent = Inherit styles from a parent theme. 26 /// Returns: The created theme. 27 template makeTheme(string init) { 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) { 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 = gluiDefaultTheme) @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 glui.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 = &GluiNode.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, GluiNode.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 /// Define style fields for a node and let them be affected by themes. 123 /// 124 /// Note: This should be used in *every* node, even if empty, to ensure keys are inherited properly. 125 /// 126 /// Params: 127 /// names = A list of styles to define. 128 mixin template DefineStyles(names...) { 129 130 @safe: 131 132 import std.meta : Filter; 133 import std.format : format; 134 import std.typecons : Rebindable; 135 import std.traits : BaseClassesTuple; 136 137 import glui.utils : StaticFieldNames; 138 139 private alias Parent = BaseClassesTuple!(typeof(this))[0]; 140 private alias MemberType(alias member) = typeof(__traits(getMember, Parent, member)); 141 142 private enum isStyleKey(alias member) = is(MemberType!member == immutable(StyleKey)); 143 private alias StyleKeys = Filter!(isStyleKey, StaticFieldNames!Parent); 144 145 // Inherit style keys 146 static foreach (name; StyleKeys) { 147 148 // Inherit default value 149 mixin("static immutable StyleKey " ~ name ~ ";"); 150 151 // Helper function to declare nested styles 152 mixin(name[0 .. $-3].format!q{ 153 154 static Style %1$sAdd(string content = "")() { 155 156 return nestStyle!(content, %1$sKey); 157 158 } 159 160 }); 161 162 } 163 164 // Local styles 165 static foreach(i, name; names) { 166 167 // Only check even names 168 static if (i % 2 == 0) { 169 170 // Define the key 171 // TODO: Make the stylekey private and add a getter for it. This getter could statically check for accessing 172 // missing `mixin DefineStyles` statements and then imply the statement with a warning. 173 mixin(name.format!q{ static immutable StyleKey %sKey; }); 174 175 // Define the value 176 mixin(name.format!q{ protected Rebindable!(const Style) %s; }); 177 178 // Helper function to declare nested styles 179 mixin(name.format!q{ 180 181 static Style %sAdd(string content = "")() { 182 183 return nestStyle!(content, %1$sKey); 184 185 } 186 187 }); 188 189 } 190 191 } 192 193 private enum inherits = !is(typeof(super) == Object); 194 195 // Load styles 196 override protected void reloadStylesImpl() { 197 198 // Inherit styles 199 static if (inherits) super.reloadStylesImpl(); 200 201 // Load inherited keys (so class A:B will load both B.styleKey and A.styleKey) 202 static foreach (name; StyleKeys) {{ 203 204 if (auto style = &mixin(name) in theme) { 205 206 mixin("this." ~ name[0 .. $-3]) = *style; 207 208 } 209 210 // No default value, the parent has already handled it 211 212 }} 213 214 // Load local keys and load defaults if none are set 215 static foreach (i, name; names) { 216 217 static if (i % 2 == 0) { 218 219 // We're deferring the default for later to make sure it uses up-to-date values 220 mixin("this." ~ name) = theme.get(&mixin(name ~ "Key"), null); 221 222 } 223 224 } 225 226 } 227 228 override void loadDefaultStyles() { 229 230 // Inherit 231 static if (inherits) super.loadDefaultStyles(); 232 233 // Load defaults for each unset style 234 static foreach (i, name; names) { 235 236 static if (i % 2 == 0) { 237 238 // Found an unset value 239 if (mixin("this." ~ name) is null) { 240 241 // Set the default 242 mixin("this." ~ name) = mixin(names[i+1]); 243 244 } 245 246 } 247 248 } 249 250 } 251 252 }