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 }