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 }