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 }