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 }