1 module fluid.showcase.frames;
2 
3 import fluid;
4 import fluid.showcase;
5 
6 
7 @safe:
8 
9 
10 // Start article
11 
12 @(
13     () => label("Every Fluid node can provide hints on how it should be laid out by the node its inside of, its "
14         ~ "parent. These are provided by passing the '.layout' setting as the first argument to the node."),
15     () => label("One of the parameters controlled with this setting is a node's align. Each node is virtually wrapped "
16         ~ "in a box that restricts its boundaries. If a node is given more space than it needs, it will be aligned "
17         ~ "differently within its boundary box based on this parameter. By default, alignment is set to the top-left "
18         ~ `corner, which is equivalent to setting '.layout!("start", "start")'. `),
19 )
20 Label startLayoutExample() {
21 
22     return label(
23         .layout!("start", "start"),
24         "Default alignment"
25     );
26 
27 }
28 
29 @(
30     () => label(`As you can see, the option above does nothing, but each of the two "start" values can be replaced `
31         ~ `with "center", "end" or "fill". "start" corresponds to the left or top side of the available space box, `
32         ~ `while "end" corresponds to the right or bottom side. "center", as you might guess, aligns a node to the `
33         ~ `center.`),
34 )
35 Label centerLayoutExample() {
36 
37     return label(
38         .layout!("center", "start"),
39         "Aligned to the center",
40     );
41 
42 }
43 
44 @(
45     () => label("Layout accepts two separate align values because they correspond to horizontal and vertical axis "
46         ~ "separately. Because it's really common to set them both to the same value, for example to fully center a "
47         ~ "node, it's possible to take a shortcut and specify just one."),
48 )
49 Label symmetricalLayoutExample() {
50 
51     return label(
52         .layout!"center",
53         "Aligned to the middle",
54     );
55 
56 }
57 
58 @(
59     () => label(`You might be curious about the "fill" option now. This one, instead of changing the node's alignment, `
60         ~ `forces the node to take over all of its available space. This is useful when you consider nodes that have `
61         ~ `background, borders or are intended to store child nodes with their own layout — but we'll talk about it `
62         ~ `later. For the purpose of this example, the box of each label node will be highlighted in red.`),
63     () => highlightBoxTheme,
64 )
65 Frame fillExample() {
66 
67     return vframe(
68         .layout!"fill",
69         label("Start-aligned node"),
70         label(.layout!"fill", "Fill-aligned node"),
71     );
72 
73 }
74 
75 
76 // Heading: Shrinking and expanding
77 
78 
79 @(
80     () => label(.headingTheme, `Shrinking and expanding`),
81     () => label(`You might have noticed something is off in the previous example. Despite the '.layout!"fill"' `
82         ~ `option, the label did not expand to the end of the container, but only used up a single line. This has to `
83         ~ `do with "expanding".`),
84     () => label(`By default, nodes operate in "shrink mode," which means they will only take the space they need. `
85         ~ `The parent node may have spare space to give, which means the node has to decide what part of that space it `
86         ~ `has to take. This is what happens when you change alignment: A start aligned node will take the top-left `
87         ~ `corner of that space, while an end aligned node will take the bottom-right corner, but they cannot move `
88         ~ `within space that hasn't been explicitly assigned to them.`),
89     () => label(`Because the job of frames is to align multiple nodes in a single row or column, frames will give `
90         ~ `their children space within a column or row according to their need. The nodes are given maximum space on `
91         ~ `the other axis, however, because it's not shared with any other node.`),
92     () => label(`This is where the 'expand' layout setting is useful. The frame will evenly distribute space among `
93         ~ `expanding nodes.`),
94     () => highlightBoxTheme,
95 )
96 Frame expandExample() {
97 
98     return vframe(
99         .layout!"fill",
100         label(.layout!1, "Start-aligned node"),
101         label(.layout!(1, "fill"), "Fill-aligned node"),
102         label(.layout!(1, "center"), "Center-aligned node"),
103     );
104 
105 }
106 
107 @(
108     () => label(`See? The nodes have been assigned equal heights. They occupy the same amount of space within the `
109         ~ `column. Each uses a different part of the space they have been assigned because of differences in alignment `
110         ~ `of each.`),
111     () => label(`Note that expanding is defined as a number. Let's see what happens if we change the number.`),
112     () => highlightBoxTheme,
113 )
114 Frame expandSegmentExample() {
115 
116     return vframe(
117         .layout!"fill",
118         label(.layout!(1, "fill"), "Expand: 1"),
119         label(.layout!(2, "fill"), "Expand: 2"),
120         label(.layout!(3, "fill"), "Expand: 3"),
121     );
122 
123 }
124 
125 @(
126     () => label(`The space of the frame has been distributed fully between the labels, but each took a different `
127         ~ `fraction. This is exactly why expanding is a number: The first label took one piece of the space, the second `
128         ~ `took two, and the last took three. It defines a fraction, where the specified number is the numerator. The `
129         ~ `denominator is the sum of all expand settings — in this case that's 6. Which means, each node takes 1/6, `
130         ~ `2/6 and 3/6 of the space respectively.`),
131     () => label(`Note: Setting expand to 0 effectively disables expand and makes the node shrink. In this case, the `
132         ~ `expand argument can be omitted.`),
133     () => label(`Shrinking and expanding nodes can be used alongside in the same frame:`),
134     () => highlightBoxTheme,
135 )
136 Frame shrinkNExpandExample() {
137 
138     return vframe(
139         .layout!"fill",
140         label(.layout!(0, "fill"), "Shrink"),
141         label(.layout!(1, "fill"), "Expand"),
142     );
143 
144 }
145 
146 
147 // Heading
148 
149 
150 @(
151     () => label(.headingTheme, "Practical examples"),
152     () => label(`Mixing shrinking and expanding nodes in the same frame is very useful. Consider implementing two `
153         ~ `buttons for switching pages, just like at the end of the page. The "back" button on the left side, the `
154         ~ `"right" button on the right.`),
155 )
156 Frame switchPagesExample() {
157 
158     return hframe(
159         .layout!("fill", "center"),
160         label("Previous page"),
161         label(.layout!(1, "end"), "Next page"),
162     );
163 
164 }
165 
166 @(
167     () => label("Of course, the labels above are not functional as buttons. Buttons will be covered in a separate "
168         ~ "chapter."),
169     () => label("To make the example more complex, let's try creating a more complete pagination panel, with two "
170         ~ "buttons like above, and a few buttons in the center to jump to a specific page."),
171 )
172 Frame paginationExample() {
173 
174     return hframe(
175         .layout!("fill", "center"),
176         label(.layout!(1, "start"), "Previous page"),
177         hframe(
178             label(" 1 "),
179             label(" 2 "),
180             label(" 3 "),
181         ),
182         label(.layout!(1, "end"), "Next page"),
183     );
184 
185 }