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 }