1 module w95.theme; 2 3 import w95; 4 import fluid; 5 import raylib; 6 7 import std.file; 8 import std.path; 9 import fluid.typeface; 10 11 import w95.border; 12 13 @safe: 14 15 @NodeTag 16 enum W95Tag { 17 compact, 18 windowContent, 19 column, 20 note, 21 teal, 22 inset, 23 white, 24 strong, 25 desktopText, 26 menuLogo, 27 table, 28 tableHeaderRow, 29 } 30 31 Theme w95Theme; 32 Theme w95TitlebarTheme; 33 34 Typeface w95Typeface; 35 Typeface w95BoldTypeface; 36 37 package string exeDir; 38 39 static this() { 40 41 import fluid.theme; 42 43 const windowBackgroundColor = color("#b7bbb7"); 44 45 exeDir = thisExePath.dirName; 46 47 auto genericFrame = rule( 48 border = 2, 49 backgroundColor = windowBackgroundColor, 50 borderStyle = w95Border(color("#fff"), color("#777b77")), 51 ); 52 53 auto clearBorder = rule( 54 borderStyle = null, 55 border = 0, 56 ); 57 58 auto clearFrame = rule( 59 clearBorder, 60 margin = 0, 61 padding = 0, 62 gap = 0, 63 backgroundColor = color("#0000"), 64 ); 65 66 auto outsetBorder = w95Border(color("#b6bbb7"), color("#777b77")) 67 .setShine(color("#fff")); 68 auto insetBorder = w95Border(color("#85898d"), color("#c2c6ca")) 69 .setShine(color("#000")) 70 .setShadow(color("#fff")); 71 72 auto insetFrame = rule( 73 border = 2, 74 borderStyle = insetBorder, 75 backgroundColor = color("#fff"), 76 ); 77 78 w95Typeface = Style.loadTypeface(exeDir.buildPath("resources/W95font.otf")); 79 w95BoldTypeface = Style.loadTypeface(exeDir.buildPath("resources/W95font-Bold.otf")); 80 81 w95Theme = nullTheme.derive( 82 83 rule!Node( 84 typeface = w95Typeface, 85 fontSize = 14, 86 textColor = color("#000"), 87 ), 88 rule!(Node, W95Tag.strong)( 89 typeface = w95BoldTypeface, 90 ), 91 rule!Space( 92 gap = 8, 93 margin.sideX = 8, 94 ), 95 rule!(Space, W95Tag.column)( 96 margin.sideX = 8, 97 ), 98 rule!(Space, W95Tag.compact)( 99 gap = 4, 100 margin = 0, 101 ), 102 rule!Frame( 103 genericFrame, 104 padding = [8, 8, 10, 8], 105 ), 106 rule!(Frame, W95Tag.compact)( 107 padding = 0, 108 ), 109 rule!(Frame, W95Tag.white)( 110 backgroundColor = color("#fff"), 111 ), 112 rule!PopupFrame( 113 padding = 0, 114 // i wish i could add a rule to affect all nested buttons 115 ), 116 rule!ScrollInput( 117 backgroundColor = color("#dbdbdb"), 118 ), 119 rule!ScrollInputHandle( 120 genericFrame, 121 ), 122 rule!OnionFrame(clearFrame), 123 rule!MapFrame(clearFrame), 124 rule!Window95( 125 margin = 0, 126 backgroundColor = windowBackgroundColor, 127 border = 3, 128 gap = 16, 129 padding = 0, 130 padding.sideBottom = 8, 131 borderStyle = outsetBorder, 132 ), 133 rule!Desktop( 134 clearFrame, 135 backgroundColor = color("#007f7f"), 136 ), 137 rule!Toolbar( 138 margin = 0, 139 borderStyle = w95Border(color("#dedede"), color("#0000")) 140 .setShine(color("#fff")), 141 border.sideX = 0, 142 border.sideBottom = 0, 143 padding = 2, 144 ), 145 rule!Clock( 146 insetFrame, 147 backgroundColor = color("#0000"), 148 margin = 0, 149 padding.sideX = 8, 150 padding.sideBottom = -4, 151 ), 152 rule!Separator( 153 border = 1, 154 borderStyle = outsetBorder, 155 ), 156 rule!Label( 157 textColor = color("#000"), 158 ), 159 rule!(Label, W95Tag.desktopText)( 160 textColor = color("#fff"), 161 ), 162 rule!Button( 163 textColor = color("#000"), 164 padding.sideX = 14, 165 padding.sideY = 2, 166 genericFrame, 167 mouseCursor = FluidMouseCursor.pointer, 168 ), 169 rule!IconButton( 170 padding.sideY = 2, 171 padding.sideX = 1, 172 margin = 0, 173 gap = 10, 174 ), 175 rule!(IconButton, W95Tag.compact)( 176 gap = 2, 177 ), 178 rule!DesktopIcon( 179 clearBorder, 180 backgroundColor = color("#0000"), 181 ), 182 rule!StartMenu( 183 gap = 0, 184 padding = 0, 185 children!IconButton( 186 clearBorder, 187 padding.sideLeft = 6, 188 padding.sideRight = 32, 189 ), 190 ), 191 rule!(Node, W95Tag.menuLogo)( 192 backgroundColor = color("#7f7f7f"), 193 padding.sideY = 7, 194 padding.sideLeft = 4, 195 padding.sideRight = 3, 196 ), 197 rule!ImageButton( 198 genericFrame, 199 mouseCursor = FluidMouseCursor.pointer, 200 ), 201 rule!GridFrame( 202 clearBorder, 203 padding = 0, 204 ), 205 rule!GridRow( 206 clearFrame, 207 gap = 6, 208 ), 209 rule!TextInput( 210 insetFrame, 211 padding = 2, 212 when!"a.isEmpty"( 213 textColor = color("#000a"), 214 ), 215 ), 216 rule!Checkbox( 217 insetFrame, 218 border = 1, 219 padding = 1, 220 mouseCursor = FluidMouseCursor.pointer, 221 222 when!"a.isFocused"(backgroundColor = color("#ddd")), 223 when!"a.isChecked"( 224 extra = new Checkbox.Extra( 225 loadImage(exeDir.buildPath("resources/checkmark.png")), 226 ), 227 ), 228 ), 229 rule!Radiobox( 230 clearFrame, 231 extra = new Checkbox.Extra( 232 loadImage(exeDir.buildPath("resources/radiobox.png")), 233 ), 234 when!"a.isChecked"( 235 extra = new Checkbox.Extra( 236 loadImage(exeDir.buildPath("resources/radiobox-checked.png")), 237 ), 238 ), 239 ), 240 rule!(Frame, W95Tag.note)( 241 insetFrame, 242 backgroundColor = color("#fcfcd1"), // TODO this should use dithering instead 243 padding.sideX = 14, 244 padding.sideY = 24, 245 ), 246 rule!(Node, W95Tag.inset)( 247 border = 2, 248 borderStyle = insetBorder, 249 ), 250 rule!(Frame, W95Tag.inset)( 251 border = 2, 252 borderStyle = insetBorder, 253 ), 254 rule!(GridFrame, W95Tag.inset)( 255 border = 2, 256 borderStyle = insetBorder, 257 ), 258 rule!(GridFrame, W95Tag.table)( 259 insetFrame, 260 gap = 2, 261 children!GridRow( 262 margin.sideX = 4, 263 ), 264 children!(GridRow, W95Tag.tableHeaderRow)( 265 margin.sideX = 0, 266 ), 267 ), 268 rule!(GridRow, W95Tag.tableHeaderRow)( 269 gap = 0, 270 children( 271 margin = 0, 272 border = 2, 273 borderStyle = outsetBorder, 274 padding.sideX = 4, 275 backgroundColor = windowBackgroundColor, 276 ), 277 ), 278 ); 279 280 w95TitlebarTheme = w95Theme.derive( 281 rule!Node( 282 typeface = w95BoldTypeface, 283 ), 284 rule!Titlebar( 285 margin = 0, 286 border = 0, 287 borderStyle = null, 288 padding = 2, 289 backgroundColor = color("#000083"), 290 ), 291 rule!(Titlebar, W95Tag.teal)( 292 backgroundColor = color("#007b77"), 293 ), 294 rule!Label( 295 textColor = color("#fff"), 296 ), 297 rule!Button( 298 margin = 0, 299 ), 300 rule!ImageButton( 301 margin = 0, 302 ), 303 ); 304 305 } 306 307 private fluid.Image loadImage(string source) @trusted { 308 309 import std.string; 310 311 auto image = LoadImage(source.toStringz); 312 scope (exit) UnloadImage(image); 313 314 auto colorsC = LoadImageColors(image); 315 auto colors = colorsC[0 .. image.width * image.height].dup; 316 scope (exit) UnloadImageColors(colorsC); 317 318 return fluid.Image( 319 colors, 320 image.width, 321 image.height 322 ); 323 324 }