1 module fluid.showcase.themes;
2 
3 import fluid;
4 import fluid.showcase;
5 
6 
7 @safe:
8 
9 
10 @(
11     () => label(.layout!"fill", .warningTheme, "Warning: Themes will be completely reworked in 0.7.0. None of the code "
12         ~ "in this article is guaranteed to work in the future. Themes are currently also far more complex than they "
13         ~ "need to be — prepare for a ride."),
14     () => label("One won't get far with Fluid without changing the theme of their app. While the default theme is "
15         ~ "enough just to get started, one will quickly realize styling is crucial in creating legible and "
16         ~ "understandable user interfaces. Let's start from the basics:"),
17 )
18 Frame themeExample() {
19 
20     // Create a theme using makeTheme and specify rules for each node
21     auto theme = makeTheme!q{
22 
23         // Give frames a dark-colored background
24         // We're using CSS hex codes to specify colors
25         Frame.styleAdd!q{
26             backgroundColor = color!"#1c1c1c";
27         };
28 
29         // Make text white to make it readable
30         Label.styleAdd!q{
31             textColor = color!"#fff";
32         };
33 
34     };
35 
36     return vframe(
37         .layout!"fill",
38         theme,  // <- Pass the theme as an argument
39         label("This text will display on a dark background."),
40     );
41 
42 }
43 
44 @(
45     () => label("There might be a quite a lot happening in the example above. Let's unpack:"),
46     () => hspace(label("— "), label("'makeTheme' will produce a new theme which you can later use in your app. Themes "
47         ~ "are constructed from rules which you define inside the q{ } blocks.")),
48     () => hspace(label("— "),
49         vspace(
50             label("We define two rules, one for frames and one for labels. We change their "
51                 ~ "colors, which should probably be fairly easy to understand. For reference on CSS hex colors, see "),
52             button("MDN <hex-color>", delegate {
53                 openURL(`https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color`);
54             })
55         )),
56     () => hspace(label("— "), label("Finally, we pass the theme next to the layout argument of the frame. It's "
57         ~ "important that the layout and theme arguments, if either or both are present, must be the first "
58         ~ "arguments.")),
59     () => label("Nodes will automatically inherit themes from their parents, until they have another theme assigned."),
60 )
61 Frame twoThemesExample() {
62 
63     auto theme = makeTheme!q{
64 
65         // For single properties, we can use the shorthand syntax
66         Frame.styleAdd.backgroundColor = color!"#54b8ff";
67 
68     };
69 
70     auto otherTheme = makeTheme!q{
71         Label.styleAdd!q{
72             backgroundColor = color!"#751fbe";
73             textColor = color!"#fff";
74         };
75     };
76 
77     return vframe(
78         .layout!"fill",
79         theme,
80         label(
81             .layout!(1, "fill"),
82             "First theme"
83         ),
84         label(
85             .layout!(1, "fill"),
86             otherTheme,
87             "Second theme!"
88         ),
89     );
90 
91 }
92 
93 @(
94     () => label("Some components, like buttons, can have multiple styles, which they switch between based on their "
95         ~ "state. You can define a different style for a button that is hovered, held down, or focused (relevant if "
96         ~ "using a keyboard)."),
97 )
98 Button!() buttonExample() {
99 
100     auto myTheme = makeTheme!q{
101         Button!().styleAdd!q{
102             // Light grey by default
103             backgroundColor = color!"#ddd";
104 
105             // Light grey when hovered, light blue when focused, grey when pressed
106             hoverStyleAdd.backgroundColor = color!"#bbb";
107             focusStyleAdd.backgroundColor = color!"#9aedf4";
108             pressStyleAdd.backgroundColor = color!"#444";
109         };
110     };
111 
112     return button(myTheme, "Play with me!", delegate { });
113 
114 }
115 
116 @(
117     () => label("You can change the font used on labels or buttons using 'Style.loadTypeface'."),
118 )
119 Frame typefaceExample() {
120 
121     auto myTheme = makeTheme!q{
122 
123         // Load typeface from given file at 14pts
124         Label.styleAdd!q{
125             import std.file, std.path;
126             auto fontPath = thisExePath.dirName.buildPath("../examples/ibm-plex-mono.ttf");
127             typeface = Style.loadTypeface(fontPath, 14);
128         };
129 
130     };
131 
132     return vframe(
133         label("Default font"),
134         label(myTheme, "Custom font"),
135     );
136 
137 }
138 
139 @(
140     () => label(.headingTheme, "Padding, margin and borders"),
141     () => label("To make the UI less cluttered, it's a good idea give it some space. You can utilize padding and "
142         ~ "margin for this purpose. Both have the same meaning in Fluid as they have in HTML/CSS, if you're familiar "
143         ~ "with web development."),
144     () => label("Both serve a similar purpose, but apply to a different part of the node: Padding is present on the "
145         ~ "interior, margin, on the exterior. This is visible "
146         ~ "when paired with background or borders. Padding will increase space covered by the background, whereas "
147         ~ "margin will display completely outside of the node. Let's start by exemplifying usage of padding:"),
148     () => label("Note: It's not possible to change padding or margin using state-based styles, like the ones you can "
149         ~ "use on a button."),
150 )
151 Frame paddingExample() {
152 
153     auto myTheme = makeTheme!q{
154 
155         Label.styleAdd!q{
156             padding = 10;
157             margin = 20;
158             backgroundColor = color!"#54b8ff";
159         };
160 
161     };
162 
163     return vframe(
164         label("Default settings"),
165         label(myTheme, "Newly created theme with padding added"),
166     );
167 
168 }
169 
170 @(
171     () => label("You can also add borders to nodes. Borders are defined by two properties, 'border' and 'borderStyle'. "
172         ~ "The former is familiar, because it works exactly the same as margin and padding — you can adjust border "
173         ~ "width separately for each side. The latter defines how exactly should the border be displayed. You can "
174         ~ "easily create a simple colored border:"),
175     () => label("Note: It's not possible to change border width using state-based styles, like the ones you can "
176         ~ "use on a button."),
177 )
178 Frame borderExample() {
179 
180     auto myTheme = makeTheme!q{
181         Frame.styleAdd!q{
182             backgroundColor = color!"#54b8ff";
183             padding = 6;
184             margin = 4;
185             border = 2;
186             borderStyle = colorBorder(color!"#1e425a");
187         };
188     };
189 
190     return vframe(
191         myTheme,
192         label("This frame has border!"),
193     );
194 
195 }
196 
197 @(
198     () => label("All of the properties; padding, margin and border, can be specified separately for each side. "
199         ~ "Assigning them directly, like above, makes them equal on every side."),
200 )
201 void sideArrayExample() {
202 
203     auto myTheme = makeTheme!q{
204 
205         // Assign all sides
206         padding = 4;
207 
208         // Assign each side individually
209         // Values are ordered by axis
210         // In order: Left, right, top, bottom
211         padding = [2, 4, 6, 8];
212 
213         // The above is equivalent to the more verbose form:
214         padding.sideLeft = 2;
215         padding.sideRight = 4;
216         padding.sideTop = 6;
217         padding.sideBottom = 8;
218 
219         // You can also assign both values on the axis at a time
220         padding.sideX = 4;  // left and right
221         padding.sideY = 8;  // top and bottom
222 
223         // All of the same rules apply also for margin and border
224         border = [4, 4, 6, 6];
225         padding.sideX = 6;
226 
227     };
228 
229 }
230 
231 @(
232     () => label("Border color can also be defined separately for each side. This might be great if you like "
233         ~ "retro-looking buttons.")
234 )
235 Button!() fancyButtonExample() {
236 
237     auto myTheme = makeTheme!q{
238         Button!().styleAdd!q{
239             auto start = color!"#fff";
240             auto end = color!"#666";
241             borderStyle = colorBorder([start, end, start, end]);
242             border = 3;
243             padding = 4;
244 
245             // Make it inset when pressed
246             hoverStyleAdd;
247             focusStyleAdd.backgroundColor = color!"#b1c6e4";
248             pressStyleAdd.backgroundColor = color!"#aaa";
249             pressStyleAdd.borderStyle = colorBorder([end, start, end, start]);
250         };
251     };
252 
253     return button(myTheme, "Fancy button!", delegate { });
254 
255 }
256 
257 @(
258     () => label(.headingTheme, "Spaces"),
259     () => label("Frames are versatile. They can be used to group nodes, configure their layout, but also to set a "
260         ~ "common background or to add extra margin around a set of nodes. Frames, by grouping a number of related "
261         ~ "items can also form an important semantic role. However, often it's useful to insert a frame "
262         ~ "just to reconfigure the layout."),
263     () => label("Once you go through the step of styling a frame — adding background, margin, padding, or border, "
264         ~ "every other frame sharing the same theme will have the same style. For cases where you don't want any of "
265         ~ "that fanciness, you could work with a secondary theme, but there's an easier way through. Instead of using "
266         ~ "a frame node, there exists a dedicated node for the purpose — a Space."),
267     () => label("Spaces and frames share all the traits except for one. They cannot have background or border. It's "
268         ~ "a small difference in the long run, but it establishes Space as the minimal and more "
269         ~ "layout-oriented 'helper' node."),
270     () => label("Usage is identical, just use 'vspace' and 'hspace'. In object-oriented programming terms, frames "
271         ~ "are subclasses of spaces, which means any frame will fit where a space will."),
272 )
273 Space spaceExample() {
274 
275     auto myTheme = makeTheme!q{
276         Frame.styleAdd!q{
277             border = 1;
278             borderStyle = colorBorder(color!"#000");
279         };
280     };
281 
282     return vspace(
283         myTheme,
284         hframe(
285             label(.layout!1, "Text inside of a frame. This frame is surrounded by a border."),
286             button("Decorative button", delegate { }),
287         ),
288         hspace(
289             label(.layout!1, "Text inside of a space. It's as plain as it can be."),
290             button("Decorative button", delegate { }),
291         ),
292     );
293 
294 }