1 module fluid.tour.themes;
2 
3 import fluid;
4 import fluid.theme;
5 import fluid.tour;
6 
7 
8 @safe:
9 
10 
11 @(
12     () => label("While the default theme of Fluid is just enough to get started quickly, one will quickly realize "
13         ~ "the importance styling has in creating legible and intuitive user interfaces. Let's start from the basics, "
14         ~ "make some text red:"),
15 )
16 Label labelColorExample() {
17 
18     import fluid.theme;
19 
20     auto myTheme = Theme(
21         rule!Label(
22             textColor = color("#ff0000"),
23         ),
24     );
25 
26     return label(myTheme, "Hello, World!");
27 
28 }
29 
30 @(
31     () => label("To define themes, the 'fluid.theme' module has to be imported. There's quite a few symbols it "
32         ~ "comes with, so it's not imported by default. It's recommended not to import it globally, but only "
33         ~ "within static constructors or relevant functions."),
34 )
35 void importExample() {
36 
37     import fluid;
38 
39     Theme makeTheme() {
40         import fluid.theme;
41 
42         return Theme(
43             rule!Label(
44                 textColor = color("#ff0000"),
45             ),
46         );
47     }
48 
49 }
50 
51 @(
52     () => label("A theme is defined by a set of rules. Each rule selects a set of nodes and applies styling "
53         ~ "properties for them. 'rule!Label' defines style for Label, 'rule!Frame' defines style for Frame and "
54         ~ "so on. 'textColor' is probably self-explanatory, it changes the color of the text drawn by the node."),
55     () => label("color() creates a color given its hex code. If you're not familiar with hex codes, you can read "
56         ~ "on it on MDN:"),
57     () => button("<hex-color>", delegate { openURL("https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color"); }),
58 )
59 Frame rulesExample() {
60 
61     auto theme = Theme(
62         rule!Label(
63             textColor = color("#f00"),
64         ),
65         rule!Button(
66             textColor = color("#00f"),
67         ),
68     );
69 
70     return vframe(
71         theme,
72         label("Red text label"),
73         button("Blue text button", delegate { }),
74     );
75 
76 }
77 
78 @(
79     () => label("Other than 'textColor', other properties can be adjusted, such as 'backgroundColor'."),
80 )
81 Frame backgroundColorExample() {
82 
83     auto theme = Theme(
84         rule!Frame(
85             // Black background
86             backgroundColor = color("#000"),
87         ),
88         rule!Label(
89             // White text
90             textColor = color("#fff"),
91         ),
92     );
93 
94     return vframe(
95         .layout!(1, "fill"),
96         theme,
97         label("Dark mode on."),
98     );
99 
100 }
101 
102 @(
103     () => label(.tags!(Tags.heading), "Reacting to user input"),
104     () => label("Nodes, especially input nodes, will change with the user input to provide feedback and guide "
105         ~ "the user. You can set how this should look like using the 'when' rule. It accepts a predicate which "
106         ~ "specifies *when* the rule should apply — and change its properties at runtime. The argument, 'a', "
107         ~ "is the node that is being tested."),
108 )
109 Button whenExample() {
110 
111     auto theme = Theme(
112         rule!Button(
113             backgroundColor = color("#444444"),
114             textColor = color("#ffffff"),
115 
116             when!"a.isHovered || a.isFocused"(
117                 backgroundColor = color("#2e956f")
118             ),
119             when!"a.isPressed"(
120                 backgroundColor = color("#35aa7f")
121             ),
122         ),
123     );
124 
125     return button(theme, "Click me!", delegate { });
126 
127 }
128 
129 @(
130     () => label("You can use the 'otherwise' method to branch out in case the predicate fails."),
131 )
132 Button otherwiseExample() {
133 
134     auto theme = Theme(
135         rule!Button(
136             textColor = color("#ffffff"),
137 
138             when!"a.isPressed"(
139                 backgroundColor = color("#35aa7f"),
140             )
141             .otherwise(
142                 backgroundColor = color("#444444"),
143             ),
144         ),
145     );
146 
147     return button(theme, "Click me!", delegate { });
148 
149 }
150 
151 @(
152     () => label(.tags!(Tags.heading), "Copying and deriving themes"),
153     () => label("In case you need to change some rules just for a portion of the tree, you can 'derive' themes from "
154         ~ "others. To apply a theme, pass it to the constructor of the node. It will be passed down to its "
155         ~ "children, unless one has its own theme specified:"),
156 )
157 Frame deriveExample() {
158 
159     auto theme = Theme(
160         rule!Frame(
161             backgroundColor = color("#000"),
162         ),
163         rule!Label(
164             textColor = color("#fff"),
165         ),
166     );
167 
168     // Use `derive` to create a new version of the same theme
169     auto blueTextTheme = theme.derive(
170         rule!Label(
171             textColor = color("#55b9ff"),
172         ),
173     );
174 
175     return vframe(
176         .layout!(1, "fill"),
177         theme,
178         label("White text on black."),
179         vframe(
180             label("Themes apply recursively, and also apply to children nodes."),
181             vframe(
182                 blueTextTheme,
183                 label("Unless the children have a different theme applied."),
184             ),
185         ),
186     );
187 
188 }
189 
190 @(
191     () => label(.tags!(Tags.heading), "Tags"),
192     () => label("If you need to make some nodes have a different look, perhaps because they serve a different "
193         ~ "purpose or just need to stand out, you can use tags. If you're familiar with web development, tags are "
194         ~ "very similar to CSS classes."),
195     () => label("Tags have to be defined ahead of time. Create an enum to store your tags and mark it with "
196         ~ "'@NodeTag'."),
197 )
198 Frame tagsExample() {
199 
200     @NodeTag
201     enum Tags {
202         green,
203     }
204 
205     auto theme = Theme(
206         rule!(Button, Tags.green)(
207             backgroundColor = color("#009b00"),
208         ),
209     );
210 
211     return vframe(
212         theme,
213         button("Regular button", delegate { }),
214         button(
215             .tags!(Tags.green),
216             "Green button",
217             delegate { }
218         ),
219     );
220 
221 }
222 
223 @(
224     () => label("The advantage tags have over buttons is that a single node can have multiple tags, which can be "
225         ~ "mixed together. They also require less work if you need to add another tag, and do not apply recursively.")
226 )
227 Frame multipleTagsExample() {
228 
229     @NodeTag
230     enum Tags {
231         whiteLabel,
232         greenButton,
233     }
234 
235     auto theme = Theme(
236         rule!(Label, Tags.whiteLabel)(
237             textColor = color("#fff"),
238         ),
239         rule!(Button, Tags.greenButton)(
240             backgroundColor = color("#009b00"),
241         ),
242     );
243 
244     return vframe(
245         theme,
246         button("Regular button", delegate { }),
247         button(
248             .tags!(Tags.greenButton, Tags.whiteLabel),
249             "Green button with white text",
250             delegate { }
251         ),
252     );
253 
254 }