]> git.lizzy.rs Git - dragonblocks_alpha.git/blob - src/client/gui.c
8af0e006f9cd259194e3a5188675fc63c06adbf2
[dragonblocks_alpha.git] / src / client / gui.c
1 #include <assert.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <GL/glew.h>
6 #include <GL/gl.h>
7 #include "client/client.h"
8 #include "client/cube.h"
9 #include "client/gui.h"
10 #include "client/mesh.h"
11 #include "client/shader.h"
12 #include "client/vertex.h"
13 #include "util.h"
14
15 static struct
16 {
17         List elements;
18
19         GLuint background_prog;
20         GLint background_loc_model;
21         GLint background_loc_projection;
22         GLint background_loc_color;
23         Mesh *background_mesh;
24
25         GLuint image_prog;
26         GLint image_loc_model;
27         GLint image_loc_projection;
28         Mesh *image_mesh;
29
30         GLuint font_prog;
31         GLint font_loc_model;
32         GLint font_loc_projection;
33         GLint font_loc_color;
34
35         mat4x4 projection;
36 } gui;
37
38 GUIElement gui_root;
39
40 typedef struct
41 {
42         GLfloat x, y;
43 } __attribute__((packed)) VertexBackgroundPosition;
44
45 typedef struct
46 {
47         VertexBackgroundPosition position;
48 } __attribute__((packed)) VertexBackground;
49
50 static VertexAttribute background_vertex_attributes[1] = {
51         // position
52         {
53                 .type = GL_FLOAT,
54                 .length = 2,
55                 .size = sizeof(VertexBackgroundPosition),
56         },
57 };
58
59 static VertexLayout background_vertex_layout = {
60         .attributes = background_vertex_attributes,
61         .count = 1,
62         .size = sizeof(VertexBackground),
63 };
64
65 static VertexBackground background_vertices[6] = {
66         {{0.0, 0.0}},
67         {{1.0, 0.0}},
68         {{1.0, 1.0}},
69         {{1.0, 1.0}},
70         {{0.0, 1.0}},
71         {{0.0, 0.0}},
72 };
73
74 typedef struct
75 {
76         GLfloat x, y;
77 } __attribute__((packed)) VertexImagePosition;
78
79 typedef struct
80 {
81         GLfloat s, t;
82 } __attribute__((packed)) VertexImageTextureCoordinates;
83
84 typedef struct
85 {
86         VertexImagePosition position;
87         VertexImageTextureCoordinates textureCoordinates;
88 } __attribute__((packed)) VertexImage;
89
90 static VertexAttribute image_vertex_attributes[2] = {
91         // position
92         {
93                 .type = GL_FLOAT,
94                 .length = 2,
95                 .size = sizeof(VertexImagePosition),
96         },
97         // textureCoordinates
98         {
99                 .type = GL_FLOAT,
100                 .length = 2,
101                 .size = sizeof(VertexImageTextureCoordinates),
102         },
103 };
104
105 static VertexLayout image_vertex_layout = {
106         .attributes = image_vertex_attributes,
107         .count = 2,
108         .size = sizeof(VertexImage),
109 };
110
111 static VertexImage image_vertices[6] = {
112         {{0.0, 0.0}, {0.0, 0.0}},
113         {{1.0, 0.0}, {1.0, 0.0}},
114         {{1.0, 1.0}, {1.0, 1.0}},
115         {{1.0, 1.0}, {1.0, 1.0}},
116         {{0.0, 1.0}, {0.0, 1.0}},
117         {{0.0, 0.0}, {0.0, 0.0}},
118 };
119
120 static int bintree_compare_f32(void *v1, void *v2, unused Bintree *tree)
121 {
122         f32 diff = (*(f32 *) v1) - (*(f32 *) v2);
123         return CMPBOUNDS(diff);
124 }
125
126 bool gui_init()
127 {
128         // initialize background pipeline
129
130         if (! shader_program_create(RESSOURCEPATH "shaders/gui/background", &gui.background_prog, NULL)) {
131                 fprintf(stderr, "Failed to create GUI background shader program\n");
132                 return false;
133         }
134
135         gui.background_loc_model = glGetUniformLocation(gui.background_prog, "model");
136         gui.background_loc_projection = glGetUniformLocation(gui.background_prog, "projection");
137         gui.background_loc_color = glGetUniformLocation(gui.background_prog, "color");
138
139         gui.background_mesh = mesh_create();
140         gui.background_mesh->textures = NULL;
141         gui.background_mesh->textures_count = 0;
142         gui.background_mesh->free_textures = false;
143         gui.background_mesh->vertices = background_vertices;
144         gui.background_mesh->vertices_count = 6;
145         gui.background_mesh->free_vertices = false;
146         gui.background_mesh->layout = &background_vertex_layout;
147
148         // initialize image pipeline
149
150         if (! shader_program_create(RESSOURCEPATH "shaders/gui/image", &gui.image_prog, NULL)) {
151                 fprintf(stderr, "Failed to create GUI image shader program\n");
152                 return false;
153         }
154
155         gui.image_loc_model = glGetUniformLocation(gui.image_prog, "model");
156         gui.image_loc_projection = glGetUniformLocation(gui.image_prog, "projection");
157
158         gui.image_mesh = mesh_create();
159         gui.image_mesh->textures = NULL;
160         gui.image_mesh->textures_count = 1;
161         gui.image_mesh->free_textures = false;
162         gui.image_mesh->vertices = image_vertices;
163         gui.image_mesh->vertices_count = 6;
164         gui.image_mesh->free_vertices = false;
165         gui.image_mesh->layout = &image_vertex_layout;
166
167         // initialize font pipeline
168
169         if (! shader_program_create(RESSOURCEPATH "shaders/gui/font", &gui.font_prog, NULL)) {
170                 fprintf(stderr, "Failed to create GUI font shader program\n");
171                 return false;
172         }
173
174         gui.font_loc_model = glGetUniformLocation(gui.font_prog, "model");
175         gui.font_loc_projection = glGetUniformLocation(gui.font_prog, "projection");
176         gui.font_loc_color = glGetUniformLocation(gui.font_prog, "color");
177
178         // font meshes are initialized in font.c
179
180         // initialize GUI root element
181
182         gui_root.def.pos = (v2f32) {0.0f, 0.0f};
183         gui_root.def.z_index = 0.0f;
184         gui_root.def.offset = (v2s32) {0, 0};
185         gui_root.def.align = (v2f32) {0.0f, 0.0f};
186         gui_root.def.scale = (v2f32) {0.0f, 0.0f};
187         gui_root.def.scale_type = GST_NONE;
188         gui_root.def.affect_parent_scale = false;
189         gui_root.def.text = NULL;
190         gui_root.def.image = NULL;
191         gui_root.def.text_color = (v4f32) {0.0f, 0.0f, 0.0f, 0.0f};
192         gui_root.def.bg_color = (v4f32) {0.0f, 0.0f, 0.0f, 0.0f};
193         gui_root.visible = true;
194         gui_root.pos = (v2f32)  {0.0f, 0.0f};
195         gui_root.scale = (v2f32) {0.0f, 0.0f};
196         gui_root.text = NULL;
197         gui_root.parent = &gui_root;
198         gui_root.children = bintree_create(sizeof(f32), &bintree_compare_f32);
199
200         return true;
201 }
202
203 static void free_element(BintreeNode *node, unused void *arg)
204 {
205         GUIElement *element = node->value;
206
207         bintree_clear(&element->children, &free_element, NULL);
208
209         if (element->def.text)
210                 free(element->def.text);
211
212         if (element->text)
213                 font_delete(element->text);
214
215         free(element);
216 }
217
218 void gui_deinit()
219 {
220         glDeleteProgram(gui.background_prog);
221         mesh_delete(gui.background_mesh);
222
223         glDeleteProgram(gui.image_prog);
224         mesh_delete(gui.image_mesh);
225
226         glDeleteProgram(gui.font_prog);
227
228         bintree_clear(&gui_root.children, &free_element, NULL);
229 }
230
231 void gui_on_resize(int width, int height)
232 {
233         mat4x4_ortho(gui.projection, 0, width, height, 0, -1.0f, 1.0f);
234         glProgramUniformMatrix4fv(gui.background_prog, gui.background_loc_projection, 1, GL_FALSE, gui.projection[0]);
235         glProgramUniformMatrix4fv(gui.image_prog, gui.image_loc_projection, 1, GL_FALSE, gui.projection[0]);
236         glProgramUniformMatrix4fv(gui.font_prog, gui.font_loc_projection, 1, GL_FALSE, gui.projection[0]);
237
238         gui_root.def.scale.x = width;
239         gui_root.def.scale.y = height;
240
241         gui_update_transform(&gui_root);
242 }
243
244 static void render_element(BintreeNode *node, unused void *arg)
245 {
246         GUIElement *element = node->value;
247
248         if (element->visible) {
249                 if (element->def.bg_color.w > 0.0f) {
250                         glUseProgram(gui.background_prog);
251                         glUniformMatrix4fv(gui.background_loc_model, 1, GL_FALSE, element->transform[0]);
252                         glUniform4f(gui.background_loc_color, element->def.bg_color.x, element->def.bg_color.y, element->def.bg_color.z, element->def.bg_color.w);
253                         mesh_render(gui.background_mesh);
254                 }
255
256                 if (element->def.image) {
257                         glUseProgram(gui.image_prog);
258                         glUniformMatrix4fv(gui.image_loc_model, 1, GL_FALSE, element->transform[0]);
259                         gui.image_mesh->textures = &element->def.image->id;
260                         mesh_render(gui.image_mesh);
261                 }
262
263                 if (element->text && element->def.text_color.w > 0.0f) {
264                         glUseProgram(gui.font_prog);
265                         glUniformMatrix4fv(gui.font_loc_model, 1, GL_FALSE, element->text_transform[0]);
266                         glUniform4f(gui.font_loc_color, element->def.text_color.x, element->def.text_color.y, element->def.text_color.z, element->def.text_color.w);
267                         font_render(element->text);
268                 }
269
270                 bintree_traverse(&element->children, BTT_INORDER, &render_element, NULL);
271         }
272 }
273
274 void gui_render()
275 {
276         glDisable(GL_DEPTH_TEST);
277         bintree_traverse(&gui_root.children, BTT_INORDER, &render_element, NULL);
278         glEnable(GL_DEPTH_TEST);
279 }
280
281 GUIElement *gui_add(GUIElement *parent, GUIElementDefinition def)
282 {
283         GUIElement *element = malloc(sizeof(GUIElement));
284         element->def = def;
285         element->visible = true;
286         element->parent = parent;
287
288         if (element->def.text) {
289                 element->def.text = strdup(element->def.text);
290                 element->text = font_create(element->def.text);
291         }
292
293         bintree_insert(&parent->children, &element->def.z_index, element);
294
295         element->children = bintree_create(sizeof(f32), &bintree_compare_f32);
296
297         if (element->def.affect_parent_scale)
298                 gui_update_transform(parent);
299         else
300                 gui_update_transform(element);
301
302         return element;
303 }
304
305 void gui_set_text(GUIElement *element, const char *text)
306 {
307         if (! element->def.text || strcmp(element->def.text, text)) {
308                 element->def.text = strdup(text);
309                 font_delete(element->text);
310                 element->text = font_create(text);
311                 gui_update_transform(element);
312         }
313 }
314
315 // transform code
316
317 typedef struct
318 {
319         List left_nodes;
320         v2f32 result;
321 } PrecalculateChildrenScaleData;
322
323 static void precalculate_children_scale(BintreeNode *node, void *arg);
324 static void bintree_calculate_element_scale(BintreeNode *node, void *arg);
325 static void list_calculate_element_scale(void *key, void *value, void *arg);
326 static void bintree_calculate_element_transform(BintreeNode *node, unused void *arg);
327
328 static void calculate_element_scale(GUIElement *element)
329 {
330         element->scale = (v2f32) {
331                 element->def.scale.x,
332                 element->def.scale.y,
333         };
334
335         bool traversed_children = false;
336
337         switch (element->def.scale_type) {
338                 case GST_IMAGE:
339                         assert(element->def.image);
340                         element->scale.x *= element->def.image->width;
341                         element->scale.y *= element->def.image->height;
342                         break;
343
344                 case GST_TEXT:
345                         assert(element->text);
346                         element->scale.x *= element->text->size.x;
347                         element->scale.y *= element->text->size.y;
348                         break;
349
350                 case GST_PARENT:
351                         element->scale.x *= element->parent->scale.x;
352                         element->scale.y *= element->parent->scale.y;
353                         break;
354
355                 case GST_CHILDREN: {
356                         PrecalculateChildrenScaleData pdata = {
357                                 .left_nodes = list_create(NULL),
358                                 .result = {0.0f, 0.0f},
359                         };
360
361                         bintree_traverse(&element->children, BTT_INORDER, &precalculate_children_scale, &pdata);
362
363                         element->scale.x *= pdata.result.x;
364                         element->scale.y *= pdata.result.y;
365
366                         list_clear_func(&pdata.left_nodes, &list_calculate_element_scale, NULL);
367                         traversed_children = true;
368                 } break;
369
370                 case GST_NONE:
371                         break;
372         }
373
374         if (! traversed_children)
375                 bintree_traverse(&element->children, BTT_INORDER, &bintree_calculate_element_scale, NULL);
376 }
377
378 static void precalculate_children_scale(BintreeNode *node, void *arg)
379 {
380         GUIElement *element = node->value;
381         PrecalculateChildrenScaleData *pdata = arg;
382
383         if (element->def.affect_parent_scale) {
384                 assert(element->def.scale_type != GST_PARENT);
385                 calculate_element_scale(element);
386
387                 if (element->scale.x > pdata->result.x)
388                         pdata->result.x = element->scale.x;
389
390                 if (element->scale.y > pdata->result.y)
391                         pdata->result.y = element->scale.y;
392         } else {
393                 list_put(&pdata->left_nodes, element, NULL);
394         }
395 }
396
397 static void bintree_calculate_element_scale(BintreeNode *node, unused void *arg)
398 {
399         calculate_element_scale(node->value);
400 }
401
402 static void list_calculate_element_scale(void *key, unused void *value, unused void *arg)
403 {
404         calculate_element_scale(key);
405 }
406
407 static void calculate_element_transform(GUIElement *element)
408 {
409         element->pos = (v2f32) {
410                 element->parent->pos.x + element->def.offset.x + element->def.pos.x * element->parent->scale.x - element->def.align.x * element->scale.x,
411                 element->parent->pos.y + element->def.offset.y + element->def.pos.y * element->parent->scale.y - element->def.align.y * element->scale.y,
412         };
413
414         mat4x4_translate(element->transform, element->pos.x - element->def.margin.x, element->pos.y - element->def.margin.y, 0.0f);
415         mat4x4_translate(element->text_transform, element->pos.x, element->pos.y, 0.0f);
416
417 #pragma GCC diagnostic push
418 #pragma GCC diagnostic ignored "-Wpedantic"
419         mat4x4_scale_aniso(element->transform, element->transform, element->scale.x + element->def.margin.x * 2.0f, element->scale.y + element->def.margin.y * 2.0f, 1.0f);
420 #pragma GCC diagnostic pop
421
422         bintree_traverse(&element->children, BTT_INORDER, &bintree_calculate_element_transform, NULL);
423 }
424
425 static void bintree_calculate_element_transform(BintreeNode *node, unused void *arg)
426 {
427         calculate_element_transform(node->value);
428 }
429
430 void gui_update_transform(GUIElement *element)
431 {
432         calculate_element_scale(element);
433         calculate_element_transform(element);
434 }