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 }