Add information about currently highlighted piece
[clav.git] / ui-sdl.c
blob4a18d91646be3af10d8c0cda53aebf947ed09b39
1 #include <errno.h>
2 #include <math.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
7 #include <SDL.h>
8 #include <SDL_ttf.h>
10 #include "macros.h"
11 #include "quiver.h"
12 #include "ui.h"
14 #define TICKS_PER_FRAME (1000 / 60)
16 #define TRIG_PRECALC_NUM 4
18 /* What clicking does */
19 static enum ui_action {
20 UIA_NONE = 0,
21 UIA_CONF_DENY,
22 UIA_DEC_FATNESS,
23 UIA_DELETE,
24 UIA_INC_FATNESS,
25 UIA_MUTATE,
26 UIA_NEW_EDGE_1,
27 UIA_NEW_EDGE_2,
28 UIA_NEW_H_EDGE_1,
29 UIA_NEW_H_EDGE_2,
30 UIA_NEW_VERTEX,
31 UIA_LEN
32 } ui_action;
34 /* The window we'll be using */
35 static SDL_Window *sdl_win;
37 /* The renderer we'll be using */
38 static SDL_Renderer *sdl_renderer;
40 /* How to limit the framerate */
41 static Uint32 frame_start_ticks;
43 /* If conf/deny, the texture onscreen */
44 static SDL_Texture *conf_deny;
46 /* The informative texture */
47 static SDL_Texture *selected_info;
49 /* Buffer for event queue */
50 static struct ui_event *eq_buf;
52 /* Current max length of event queue */
53 static size_t eq_len;
55 /* Current head of queue */
56 static size_t eq_head;
58 /* Current tail of queue */
59 static size_t eq_tail;
61 /* The quiver we'll be using */
62 static struct quiver *q;
64 /* Width of drawing area in pixels */
65 static int da_pix_width;
67 /* Height of drawing area in pixels */
68 static int da_pix_height;
70 /* The background color */
71 static SDL_Color color_bg = { .r = 0xe2, .g = 0xe2, .b = 0xe2, .a = 0xff };
73 /* The normal vertex color */
74 static SDL_Color color_v = { .r = 0x82, .g = 0x82, .b = 0xb2, .a = 0xff };
76 /* The vertex color for preview vertices */
77 static SDL_Color color_v_preview = { .r = 0x82, .g = 0x82, .b = 0xb2, .a =
78 0x40 };
80 /* The normal vertex outline color */
81 static SDL_Color color_outline = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x80 };
83 /* The vertex outline color for preview vertices */
84 static SDL_Color color_outline_preview = { .r = 0x12, .g = 0x12, .b = 0x12, .a =
85 0x20 };
87 /* The selected vertex outline color */
88 static SDL_Color color_outline_sel = { .r = 0x42, .g = 0x42, .b = 0xe2, .a =
89 0x80 };
91 /* The font color */
92 static SDL_Color color_font = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x80 };
94 /* The normal edge color */
95 static SDL_Color color_e_normal = { .r = 0x12, .g = 0x12, .b = 0x12, .a =
96 0xff };
98 /* The half edge color */
99 static SDL_Color color_e_half = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x60 };
101 /* The abnormal edge color */
102 static SDL_Color color_e_abnormal = { .r = 0xd0, .g = 0x12, .b = 0x12, .a =
103 0xf0 };
105 /* The selected edge color */
106 static SDL_Color color_e_sel = { .r = 0xb2, .g = 0xb2, .b = 0xe2, .a = 0x40 };
108 /* The font for node names, instructions, etc */
109 static TTF_Font *normal_font;
111 /* The base font size */
112 static uint_fast8_t base_font_size = 12;
114 /* How much space (pixels) between lower left of screen and bottom text */
115 static unsigned int text_border_padding = 24;
117 /* Strings to display at bottom of screen */
118 static const char *bottom_string[] = {
119 /* */
120 [UIA_NONE] = "[m] Mutate\n[v] Create new vertex\n"
121 "[d] Delete vertex/arrow\n[e] Add edge\n"
122 "[h] Add half edge\n[f] Increase vertex fatness\n"
123 "[g] Decrease vertex fatness\n[q] Quit\n",
124 [UIA_NEW_VERTEX] =
125 "[Mouse1] Create new vertex\n[ESC] Cancel", /* */
126 [UIA_DELETE] = "[Mouse1] Delete vertex/edge\n[ESC] Cancel", /* */
127 [UIA_NEW_EDGE_1] = "[Mouse1] Select start\n[ESC] Cancel", /* */
128 [UIA_NEW_EDGE_2] = "[Mouse1] Select end\n[ESC] Cancel", /* */
129 [UIA_NEW_H_EDGE_1] = "[Mouse1] Select start\n[ESC] Cancel", /* */
130 [UIA_NEW_H_EDGE_2] = "[Mouse1] Select end\n[ESC] Cancel", /* */
131 [UIA_INC_FATNESS] = "[Mouse1] Increase fatness\n[ESC] Cancel", /* */
132 [UIA_DEC_FATNESS] = "[Mouse1] Decrease fatness\n[ESC] Cancel", /* */
133 [UIA_MUTATE] = "[Mouse1] Mutate at vertex\n[ESC] Cancel", /* */
134 [UIA_CONF_DENY] = "[y] Confirm\n[n] Cancel", /* */
135 [UIA_LEN] = "" /* */
138 /* The texture containing what to show on the bottom */
139 static SDL_Texture *bottom_text[UIA_LEN];
141 /* The textures containing the names of all vertices - indexed just like q */
142 static SDL_Texture **vertex_names;
144 /* The number of elements of vertex_names */
145 static size_t vertex_names_len;
147 /* Drawing offset x */
148 static int offset_x;
150 /* Drawing offset x */
151 static int offset_y;
153 /* How wide (in pixels) a fatness 1 node should be */
154 static unsigned int base_node_radius = 8;
156 /* How much (in pixels) a node should widen for each fatness level */
157 static unsigned int node_radius_per_fatness = 7;
159 /* How wide (in pixels) the outline should be */
160 static int outline_width = 2;
162 /* How wide (in pixels) the arrowheads should be */
163 static int arrow_length = 7;
165 /* How narrow the arrowheads should be */
166 static double arrow_angle = 6.5 * M_PI / 8.0;
168 /* How close to an arrow (in pixels) for it to be selected */
169 static int edge_margin = 5;
171 /* If we're interacting with a vertex, which one */
172 static size_t selected_vertex = (size_t) -1;
174 /* If we're interacting with an edge, the i */
175 static size_t selected_edge_i = (size_t) -1;
177 /* If we're interacting with an edge, the j */
178 static size_t selected_edge_j = (size_t) -1;
180 /* If we're adding an edge, the last vertex we clicked on */
181 static size_t last_clicked_vertex = (size_t) -1;
183 /* x-coordinate of last mouse position */
184 static int last_mouse_x = -1;
186 /* y-coordinate of last mouse position */
187 static int last_mouse_y = -1;
189 /* sine tables */
190 static double precalc_sins[TRIG_PRECALC_NUM];
192 /* cosine tables */
193 static double precalc_coss[TRIG_PRECALC_NUM];
195 /* Precalculate sines and cosines */
196 static void precalc_trig(void)
198 precalc_coss[0] = 0.0;
199 precalc_sins[0] = 1.0;
201 for (size_t j = 1; j < TRIG_PRECALC_NUM - 1; ++j) {
202 double theta = (M_PI * j) / (2 * (TRIG_PRECALC_NUM - 1));
204 precalc_sins[j] = sin(theta);
205 precalc_coss[j] = cos(theta);
208 precalc_coss[TRIG_PRECALC_NUM - 1] = 0.0;
209 precalc_sins[TRIG_PRECALC_NUM - 1] = 1.0;
212 /* GCD */
213 static int gcd(uint_fast8_t x, uint_fast8_t y)
215 uint_fast8_t r = 0;
217 if (!x &&
218 !y) {
219 return 1;
222 while (y) {
223 r = x % y;
224 x = y;
225 y = r;
228 return x;
231 /* Allocate and print a rational in the way a user expects */
232 static int pretty_fraction(struct rational *r, char **out)
234 int ret = 0;
236 if (r->q == 1) {
237 size_t len = snprintf(0, 0, "%d", (int) r->p);
239 if (!(*out = malloc(len + 1))) {
240 ret = errno;
241 perror(L("malloc"));
242 goto done;
245 sprintf(*out, "%d", (int) r->p);
246 goto done;
249 size_t len = snprintf(0, 0, "%d/%u", (int) r->p, (unsigned int) r->q);
251 if (!(*out = malloc(len + 1))) {
252 ret = errno;
253 perror(L("malloc"));
254 goto done;
257 sprintf(*out, "%d/%u", (int) r->p, (unsigned int) r->q);
258 done:
260 return ret;
263 /* Render text to texture */
264 static int render_text(const char *text, SDL_Texture **out)
266 if (!out) {
267 return 0;
270 if (*out) {
271 SDL_DestroyTexture(*out);
274 int ret = 0;
275 SDL_Surface *surf = 0;
277 if (!(surf = TTF_RenderUTF8_Blended_Wrapped(normal_font, text,
278 color_font, 800))) {
279 fprintf(stderr, L("TTF_RenderUTF8_Shaded(): %s\n"),
280 TTF_GetError());
281 ret = ENOMEDIUM;
282 goto done;
285 if (!(*out = SDL_CreateTextureFromSurface(sdl_renderer, surf))) {
286 fprintf(stderr, L("SDL_CreateTextureFromSurface(): %s\n"),
287 SDL_GetError());
288 ret = ENOMEDIUM;
289 goto done;
292 done:
293 SDL_FreeSurface(surf);
295 return ret;
298 /* Load fonts */
299 static int load_fonts(void)
301 int ret = 0;
303 if (normal_font) {
304 TTF_CloseFont(normal_font);
305 normal_font = 0;
308 if (!(normal_font = TTF_OpenFont(FONT_PATH, base_font_size))) {
309 ret = ENOMEDIUM;
310 fprintf(stderr, L("TTF_OpenFont(): %s\n"), TTF_GetError());
311 goto done;
314 for (size_t j = 0; j < UIA_LEN; ++j) {
315 if ((ret = render_text(bottom_string[j], &bottom_text[j]))) {
316 goto done;
320 done:
322 if (ret) {
323 if (normal_font) {
324 TTF_CloseFont(normal_font);
327 normal_font = 0;
330 return ret;
333 /* Convert `internal coordinates' to pixel coordinates */
334 static void internal_to_pixel_xy(int in_x, int in_y, int *out_x, int *out_y)
336 *out_x = in_x + offset_x;
337 *out_y = in_y + offset_y;
340 /* Convert pixel coordinates to `internal coordinates' */
341 static void pixel_to_internal_xy(int in_x, int in_y, int *out_x, int *out_y)
343 *out_x = in_x - offset_x;
344 *out_y = in_y - offset_y;
347 /* Set selected_vertex and selected_edge_{i,j} */
348 static int recalculate_selected_items(int mx, int my)
350 int x = 0;
351 int y = 0;
352 int ret = 0;
353 char *s = 0;
354 char *sij = 0;
355 char *sji = 0;
357 pixel_to_internal_xy(mx, my, &x, &y);
358 size_t last_vertex = selected_vertex;
359 size_t last_edge_i = selected_edge_i;
360 size_t last_edge_j = selected_edge_j;
362 selected_vertex = (size_t) -1;
363 selected_edge_i = (size_t) -1;
364 selected_edge_j = (size_t) -1;
366 for (size_t j = q->v_num; j > 0; --j) {
367 struct vertex *v = &(q->v[j - 1]);
368 int r = base_node_radius + v->fatness * node_radius_per_fatness;
370 if (x > v->x - r &&
371 x < v->x + r &&
372 y > v->y - r &&
373 y < v->y + r) {
374 selected_vertex = j - 1;
375 goto compute_str;
379 for (size_t j = 1; j < q->v_num; ++j) {
380 struct vertex *v1 = &(q->v[j]);
382 for (size_t i = 0; i < j; ++i) {
383 if (!q->e[i * q->v_len + j].p &&
384 !q->e[j * q->v_len + i].p) {
385 continue;
388 struct vertex *v2 = &(q->v[i]);
390 if ((x - edge_margin > v1->x &&
391 x - edge_margin > v2->x) ||
392 (x + edge_margin < v1->x &&
393 x + edge_margin < v2->x) ||
394 (y - edge_margin > v1->y &&
395 y - edge_margin > v2->y) ||
396 (y + edge_margin < v1->y &&
397 y + edge_margin < v2->y)) {
398 continue;
401 if (v1->x == v2->x) {
402 if (x + edge_margin > v1->x &&
403 x - edge_margin < v1->x) {
404 selected_edge_i = i;
405 selected_edge_j = j;
406 goto compute_str;
408 } else if (v1->y == v2->y) {
409 if (y + edge_margin > v1->y &&
410 y - edge_margin < v1->y) {
411 selected_edge_i = i;
412 selected_edge_j = j;
413 goto compute_str;
415 } else {
416 double m1 = ((double) (v2->y - v1->y)) /
417 ((double) (v2->x - v1->x));
418 double m2 = -1.0 / m1;
419 double xint = ((double) y - (double) v1->y +
420 m1 * v1->x - m2 * x) / (m1 - m2);
421 double yint = m1 * xint - m1 * v1->x +
422 (double) v1->y;
424 if ((x - xint) * (x - xint) + (y - yint) * (y -
425 yint)
426 < edge_margin * edge_margin) {
427 selected_edge_i = i;
428 selected_edge_j = j;
429 goto compute_str;
435 compute_str:
437 if (selected_vertex != last_vertex &&
438 selected_vertex != (size_t) -1) {
439 struct vertex *v = &(q->v[selected_vertex]);
440 size_t len = snprintf(0, 0, "Name: %s\nFatness: %d", v->name,
441 (int) v->fatness);
443 if (!(s = malloc(len + 1))) {
444 ret = errno;
445 perror(L("malloc"));
446 goto done;
449 sprintf(s, "Name: %s\nFatness: %d", v->name,
450 (int) v->fatness);
452 if ((ret = render_text(s, &selected_info))) {
453 goto done;
455 } else if (last_edge_i != selected_edge_i &&
456 last_edge_j != selected_edge_j &&
457 selected_edge_i != (size_t) -1 &&
458 selected_edge_j != (size_t) -1) {
459 struct vertex *i = &(q->v[selected_edge_i]);
460 struct vertex *j = &(q->v[selected_edge_j]);
461 struct rational *eij = &(q->e[selected_edge_i * q->v_len +
462 selected_edge_j]);
463 struct rational *eji = &(q->e[selected_edge_j * q->v_len +
464 selected_edge_i]);
466 if ((ret = pretty_fraction(eij, &sij))) {
467 goto done;
470 if ((ret = pretty_fraction(eji, &sji))) {
471 goto done;
474 size_t len = snprintf(0, 0,
475 "%s \u2192 %s: %s\n%s \u2192 %s: %s",
476 i->name, j->name, sij,
477 j->name, i->name, sji);
479 if (!(s = malloc(len + 1))) {
480 ret = errno;
481 perror(L("malloc"));
482 goto done;
485 sprintf(s, "%s \u2192 %s: %s\n%s \u2192 %s: %s", i->name,
486 j->name, sij, j->name, i->name, sji);
488 if ((ret = render_text(s, &selected_info))) {
489 goto done;
493 done:
494 free(s);
495 free(sij);
496 free(sji);
498 return ret;
501 /* Render vertex names as textures */
502 static int render_vertex_names(void)
504 if (vertex_names) {
505 for (size_t j = 0; j < vertex_names_len; ++j) {
506 SDL_DestroyTexture(vertex_names[j]);
507 vertex_names[j] = 0;
511 if (!(vertex_names = realloc(vertex_names, q->v_num *
512 sizeof(*vertex_names)))) {
513 int sv_err = errno;
515 perror(L("realloc()"));
516 vertex_names_len = 0;
518 return sv_err;
521 vertex_names_len = q->v_num;
523 for (size_t j = 0; j < vertex_names_len; ++j) {
524 vertex_names[j] = 0;
527 for (size_t j = 0; j < vertex_names_len; ++j) {
528 int ret = 0;
530 if ((ret = render_text(q->v[j].name, &(vertex_names[j])))) {
531 return ret;
535 return 0;
538 /* Get information about the window */
539 static void react_to_window_resized(void)
541 int old_pix_width = da_pix_width;
542 int old_pix_height = da_pix_height;
544 SDL_GetWindowSize(sdl_win, &da_pix_width, &da_pix_height);
546 if (old_pix_width == da_pix_width &&
547 old_pix_height == da_pix_height) {
548 return;
551 offset_x += (da_pix_width - old_pix_width) / 2;
552 offset_y += (da_pix_height - old_pix_height) / 2;
555 /* Pop from queue */
556 static void eq_pop(struct ui_event *out)
558 if (eq_head == eq_tail) {
559 *out = (struct ui_event) { 0 };
561 return;
564 memcpy(out, eq_buf + eq_head, sizeof *out);
565 eq_buf[eq_head] = (struct ui_event) { 0 };
566 eq_head = (eq_head + 1) % eq_len;
569 /* Push into queue */
570 static int eq_push(struct ui_event *in)
572 if (((eq_tail + 1) % eq_len) == eq_head) {
573 void *newmem;
575 if ((eq_len * sizeof *in) >= ((size_t) -1) / 2) {
576 fprintf(stderr, L(
577 "eq_push: Impossibly large buffer\n"));
579 return ENOMEM;
582 if (!(newmem = realloc(eq_buf, (eq_len * 2) *
583 sizeof *eq_buf))) {
584 int sv_err = errno;
586 perror(L("realloc"));
588 return sv_err;
591 eq_buf = (struct ui_event *) newmem;
592 eq_len *= 2;
595 memcpy(eq_buf + eq_tail, in, sizeof *in);
596 eq_tail = (eq_tail + 1) % eq_len;
598 return 0;
601 /* Initialize SDL */
602 int ui_init(struct quiver *i_q)
604 int ret;
606 q = i_q;
608 if (SDL_Init(SDL_INIT_VIDEO) < 0) {
609 fprintf(stderr, L("SDL_Init(): %s\n"), SDL_GetError());
611 return ENOMEDIUM;
614 sdl_win = SDL_CreateWindow("Cluster Algebra Visualizer",
615 SDL_WINDOWPOS_UNDEFINED,
616 SDL_WINDOWPOS_UNDEFINED, 1000, 1000,
617 SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
619 if (!sdl_win) {
620 fprintf(stderr, L("SDL_CreateWindow(): %s\n"), SDL_GetError());
622 return ENOMEDIUM;
625 sdl_renderer = SDL_CreateRenderer(sdl_win, -1,
626 SDL_RENDERER_ACCELERATED);
628 if (!sdl_renderer) {
629 fprintf(stderr, L("SDL_CreateRenderer(): %s\n"),
630 SDL_GetError());
632 return ENOMEDIUM;
635 if (TTF_Init() < 0) {
636 fprintf(stderr, L("TTF_Init(): %s\n"), TTF_GetError());
638 return ENOMEDIUM;
641 if ((ret = load_fonts())) {
642 goto done;
645 if ((ret = render_vertex_names())) {
646 goto done;
649 if ((ret = SDL_SetRenderDrawColor(sdl_renderer, color_bg.r, color_bg.g,
650 color_bg.b, color_bg.a))) {
651 fprintf(stderr, L("SDL_SetRenderDrawColor(): %s\n"),
652 SDL_GetError());
653 goto done;
656 if ((ret = SDL_RenderClear(sdl_renderer))) {
657 fprintf(stderr, L("SDL_RenderClear(): %s\n"), SDL_GetError());
658 goto done;
661 SDL_RenderPresent(sdl_renderer);
662 react_to_window_resized();
664 /* Set up queue for returning data */
665 if (!(eq_buf = calloc(2, sizeof *eq_buf))) {
666 ret = errno;
667 perror(L("malloc"));
668 goto done;
671 eq_len = 2;
672 eq_head = 0;
673 eq_tail = 0;
675 /* Sines and cosines for drawing circles */
676 precalc_trig();
677 done:
679 return ret;
682 /* Deal with the fact that the quiver was changed */
683 int ui_respond_quiver_change(void)
685 return render_vertex_names();
688 /* Tear down SDL */
689 int ui_teardown(void)
691 if (vertex_names) {
692 for (size_t j = 0; j < vertex_names_len; ++j) {
693 SDL_DestroyTexture(vertex_names[j]);
694 vertex_names[j] = 0;
698 for (size_t j = 0; j < UIA_LEN; ++j) {
699 SDL_DestroyTexture(bottom_text[j]);
700 bottom_text[j] = 0;
703 if (conf_deny) {
704 SDL_DestroyTexture(conf_deny);
705 conf_deny = 0;
708 if (selected_info) {
709 SDL_DestroyTexture(selected_info);
710 selected_info = 0;
713 if (normal_font) {
714 TTF_CloseFont(normal_font);
715 normal_font = 0;
718 if (sdl_win) {
719 SDL_DestroyWindow(sdl_win);
722 SDL_Quit();
724 return 0;
727 /* Record that a frame has been started */
728 int ui_start_frame(void)
730 int ret = 0;
731 int rho = 0;
732 SDL_Rect r = { 0 };
733 Uint32 dummy_format;
734 int dummy_access;
735 int tex_w;
736 int tex_h;
738 frame_start_ticks = SDL_GetTicks();
740 /* Draw the damn thing */
741 SDL_SetRenderDrawColor(sdl_renderer, color_bg.r, color_bg.g, color_bg.b,
742 color_bg.a);
743 SDL_RenderClear(sdl_renderer);
745 /* Draw each edge */
746 for (size_t j = 0; j < q->v_num; ++j) {
747 for (size_t i = 0; i < j; ++i) {
748 /* First, determine if we're looking at a half-edge or a full-edge */
749 int d = gcd(q->v[i].fatness, q->v[j].fatness);
750 struct rational *eij = &(q->e[i * q->v_len + j]);
751 struct rational *eji = &(q->e[j * q->v_len + i]);
752 int cx = 0;
753 int cy = 0;
754 int cx2 = 0;
755 int cy2 = 0;
756 double theta = 0.0;
758 if (!eij->p &&
759 !eji->p) {
760 continue;
763 internal_to_pixel_xy(q->v[i].x, q->v[i].y, &cx, &cy);
764 internal_to_pixel_xy(q->v[j].x, q->v[j].y, &cx2, &cy2);
766 if (selected_edge_i == i &&
767 selected_edge_j == j) {
768 if ((ret = SDL_SetRenderDrawColor(sdl_renderer,
769 color_e_sel.r,
770 color_e_sel.g,
771 color_e_sel.b,
772 color_e_sel.a)))
774 fprintf(stderr, L(
775 "SDL_RenderDrawColor(): %s\n"),
776 SDL_GetError());
777 goto done;
780 for (int id = -edge_margin; id < edge_margin;
781 ++id) {
782 for (int jd = -edge_margin; jd <
783 edge_margin; ++jd) {
784 if ((ret = SDL_RenderDrawLine(
785 sdl_renderer, cx +
786 id, cy + jd,
787 cx2 + id, cy2 +
788 jd))) {
789 fprintf(stderr, L(
790 "SDL_RenderDrawLine(): %s\n"),
791 SDL_GetError());
792 goto done;
798 /* This is the (eij)/dj = -(eji)/di condition */
799 if (eij->p * q->v[i].fatness * eji->q != -eji->p *
800 q->v[j].fatness * eij->q) {
801 ret = SDL_SetRenderDrawColor(sdl_renderer,
802 color_e_abnormal.r,
803 color_e_abnormal.g,
804 color_e_abnormal.b,
805 color_e_abnormal.a);
806 } else if (abs(eij->p) * d == q->v[j].fatness *
807 eij->q) {
808 ret = SDL_SetRenderDrawColor(sdl_renderer,
809 color_e_normal.r,
810 color_e_normal.g,
811 color_e_normal.b,
812 color_e_normal.a);
813 } else if (2 * abs(eij->p) * d == q->v[j].fatness *
814 eij->q) {
815 ret = SDL_SetRenderDrawColor(sdl_renderer,
816 color_e_half.r,
817 color_e_half.g,
818 color_e_half.b,
819 color_e_half.a);
820 } else {
821 ret = SDL_SetRenderDrawColor(sdl_renderer,
822 color_e_abnormal.r,
823 color_e_abnormal.g,
824 color_e_abnormal.b,
825 color_e_abnormal.a);
828 if (ret) {
829 fprintf(stderr, L(
830 "SDL_SetRenderDrawColor(): %s\n"),
831 SDL_GetError());
832 goto done;
835 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx2,
836 cy2))) {
837 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
838 SDL_GetError());
839 goto done;
842 if (cx == cx2) {
843 theta = (cy2 > cy) ? M_PI / 2.0 : -M_PI / 2.0;
844 } else {
845 theta = atan2f(cy2 - cy, cx2 - cx);
848 if ((eij->p < 0)) {
849 theta += M_PI;
852 cx = (cx + cx2) / 2;
853 cy = (cy + cy2) / 2;
854 cx2 = cx + arrow_length * cos(theta + arrow_angle);
855 cy2 = cy + arrow_length * sin(theta + arrow_angle);
857 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx2,
858 cy2))) {
859 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
860 SDL_GetError());
861 goto done;
864 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx +
865 arrow_length * cos(theta -
866 arrow_angle),
867 cy +
868 arrow_length * sin(theta -
869 arrow_angle))))
871 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
872 SDL_GetError());
873 goto done;
878 /* Draw each vertex as a box */
879 for (size_t j = 0; j < q->v_num; ++j) {
880 struct vertex *v = &(q->v[j]);
881 int cx = 0;
882 int cy = 0;
884 internal_to_pixel_xy(v->x, v->y, &cx, &cy);
886 /* Central square */
887 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_NONE);
888 SDL_SetRenderDrawColor(sdl_renderer, color_v.r, color_v.g,
889 color_v.b, color_v.a);
890 rho = base_node_radius + node_radius_per_fatness * v->fatness;
891 r.x = cx - rho;
892 r.y = cy - rho;
893 r.w = 2 * rho;
894 r.h = 2 * rho;
895 SDL_RenderFillRect(sdl_renderer, &r);
897 /* Outline */
898 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
900 if (j == selected_vertex) {
901 SDL_SetRenderDrawColor(sdl_renderer,
902 color_outline_sel.r,
903 color_outline_sel.g,
904 color_outline_sel.b,
905 color_outline_sel.a);
906 } else {
907 SDL_SetRenderDrawColor(sdl_renderer, color_outline.r,
908 color_outline.g, color_outline.b,
909 color_outline.a);
912 r.x = cx - rho;
913 r.y = cy - rho;
914 r.w = 2 * rho - outline_width;
915 r.h = outline_width;
916 SDL_RenderFillRect(sdl_renderer, &r);
917 r.x = cx + rho - outline_width;
918 r.y = cy - rho;
919 r.w = outline_width;
920 r.h = 2 * rho - outline_width;
921 SDL_RenderFillRect(sdl_renderer, &r);
922 r.x = cx - rho + outline_width;
923 r.y = cy + rho - outline_width;
924 r.w = 2 * rho - outline_width;
925 r.h = outline_width;
926 SDL_RenderFillRect(sdl_renderer, &r);
927 r.x = cx - rho;
928 r.y = cy - rho + outline_width;
929 r.w = outline_width;
930 r.h = 2 * rho - outline_width;
931 SDL_RenderFillRect(sdl_renderer, &r);
933 /* Text */
934 if (j >= vertex_names_len) {
935 fprintf(stderr, L(
936 "render_vertex_names() was not called, somehow\n"));
937 ret = EINVAL;
938 goto done;
941 if (SDL_QueryTexture(vertex_names[j], &dummy_format,
942 &dummy_access, &tex_w, &tex_h)) {
943 fprintf(stderr, L("SDL_QueryTexture(): %s\n"),
944 SDL_GetError());
945 ret = ENOMEDIUM;
946 goto done;
949 r.x = cx - tex_w / 2;
950 r.y = cy - tex_h / 2;
951 r.w = tex_w;
952 r.h = tex_h;
953 SDL_RenderCopy(sdl_renderer, vertex_names[j], 0, &r);
956 /* If adding a new vertex, draw preview */
957 if (ui_action == UIA_NEW_VERTEX &&
958 last_mouse_x != -1 &&
959 last_mouse_y != -1) {
960 /* Central square */
961 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_NONE);
962 SDL_SetRenderDrawColor(sdl_renderer, color_v_preview.r,
963 color_v_preview.g, color_v_preview.b,
964 color_v_preview.a);
965 rho = base_node_radius;
966 r.x = last_mouse_x - rho;
967 r.y = last_mouse_y - rho;
968 r.w = 2 * rho;
969 r.h = 2 * rho;
970 SDL_RenderFillRect(sdl_renderer, &r);
972 /* Outline */
973 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
974 SDL_SetRenderDrawColor(sdl_renderer, color_outline_preview.r,
975 color_outline_preview.g,
976 color_outline_preview.b,
977 color_outline_preview.a);
978 r.x = last_mouse_x - rho;
979 r.y = last_mouse_y - rho;
980 r.w = 2 * rho - outline_width;
981 r.h = outline_width;
982 SDL_RenderFillRect(sdl_renderer, &r);
983 r.x = last_mouse_x + rho - outline_width;
984 r.y = last_mouse_y - rho;
985 r.w = outline_width;
986 r.h = 2 * rho - outline_width;
987 SDL_RenderFillRect(sdl_renderer, &r);
988 r.x = last_mouse_x - rho + outline_width;
989 r.y = last_mouse_y + rho - outline_width;
990 r.w = 2 * rho - outline_width;
991 r.h = outline_width;
992 SDL_RenderFillRect(sdl_renderer, &r);
993 r.x = last_mouse_x - rho;
994 r.y = last_mouse_y - rho + outline_width;
995 r.w = outline_width;
996 r.h = 2 * rho - outline_width;
997 SDL_RenderFillRect(sdl_renderer, &r);
1000 /* If adding a new edge, draw possible */
1001 if ((ui_action == UIA_NEW_EDGE_2 ||
1002 ui_action == UIA_NEW_H_EDGE_2) &&
1004 /* last_clicked_vertex != (size_t) -1 && */
1005 last_mouse_x != -1 &&
1006 last_mouse_y != -1) {
1007 int cx = 0;
1008 int cy = 0;
1010 ret = SDL_SetRenderDrawColor(sdl_renderer, color_e_normal.r,
1011 color_e_normal.g, color_e_normal.b,
1012 color_e_normal.a);
1013 internal_to_pixel_xy(q->v[last_clicked_vertex].x,
1014 q->v[last_clicked_vertex].y, &cx, &cy);
1016 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy,
1017 last_mouse_x, last_mouse_y))) {
1018 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
1019 SDL_GetError());
1020 goto done;
1024 /* Bottom text */
1025 if (SDL_QueryTexture(bottom_text[ui_action], &dummy_format,
1026 &dummy_access, &tex_w, &tex_h)) {
1027 fprintf(stderr, L("SDL_QueryTexture(): %s\n"), SDL_GetError());
1028 ret = ENOMEDIUM;
1029 goto done;
1032 r.x = text_border_padding;
1033 r.y = da_pix_height - tex_h - text_border_padding;
1034 r.w = tex_w;
1035 r.h = tex_h;
1036 SDL_RenderCopy(sdl_renderer, bottom_text[ui_action], 0, &r);
1038 /* Special case for if a question is being asked */
1039 if (ui_action == UIA_CONF_DENY) {
1040 if (SDL_QueryTexture(conf_deny, &dummy_format, &dummy_access,
1041 &tex_w, &tex_h)) {
1042 fprintf(stderr, L("SDL_QueryTexture(): %s\n"),
1043 SDL_GetError());
1044 ret = ENOMEDIUM;
1045 goto done;
1048 r.x = text_border_padding;
1049 r.y = r.y - tex_h - text_border_padding;
1050 r.w = tex_w;
1051 r.h = tex_h;
1052 SDL_RenderCopy(sdl_renderer, conf_deny, 0, &r);
1055 /* If something is selected */
1056 if (selected_info &&
1057 (selected_vertex != (size_t) -1 ||
1058 (selected_edge_i != (size_t) -1 &&
1059 selected_edge_j != (size_t) -1))) {
1060 if (SDL_QueryTexture(selected_info, &dummy_format,
1061 &dummy_access, &tex_w, &tex_h)) {
1062 fprintf(stderr, L("SDL_QueryTexture(): %s\n"),
1063 SDL_GetError());
1064 ret = ENOMEDIUM;
1065 goto done;
1068 r.x = text_border_padding;
1069 r.y = text_border_padding;
1070 r.w = tex_w;
1071 r.h = tex_h;
1072 SDL_RenderCopy(sdl_renderer, selected_info, 0, &r);
1075 done:
1077 return ret;
1080 /* Draw a frame, possibly sleeping for framelimit */
1081 int ui_finish_frame(void)
1083 int ret = 0;
1084 struct ui_event ui_e = { 0 };
1085 SDL_Event sdl_e = { 0 };
1086 Uint32 now = 0;
1088 SDL_RenderPresent(sdl_renderer);
1090 /* Handle user input */
1091 while (SDL_PollEvent(&sdl_e) != 0) {
1092 SDL_Keycode k = 0;
1094 switch (sdl_e.type) {
1095 case SDL_QUIT:
1096 ui_e = (struct ui_event) { .type = ET_FORCE_QUIT };
1097 ret = eq_push(&ui_e);
1098 break;
1099 case SDL_KEYUP:
1100 k = sdl_e.key.keysym.sym;
1102 switch (ui_action) {
1103 case UIA_NONE:
1105 if (k == SDLK_q) {
1106 ui_e = (struct ui_event) { .type =
1107 ET_INITIAL_QUIT };
1108 ret = eq_push(&ui_e);
1109 } else if (k == SDLK_m) {
1110 ui_action = UIA_MUTATE;
1111 } else if (k == SDLK_v) {
1112 ui_action = UIA_NEW_VERTEX;
1113 } else if (k == SDLK_d) {
1114 ui_action = UIA_DELETE;
1115 } else if (k == SDLK_e) {
1116 ui_action = UIA_NEW_EDGE_1;
1117 } else if (k == SDLK_h) {
1118 ui_action = UIA_NEW_H_EDGE_1;
1119 } else if (k == SDLK_f) {
1120 ui_action = UIA_INC_FATNESS;
1121 } else if (k == SDLK_g) {
1122 ui_action = UIA_DEC_FATNESS;
1125 break;
1126 case UIA_CONF_DENY:
1128 if (k == SDLK_n ||
1129 k == SDLK_ESCAPE) {
1130 ui_e = (struct ui_event) { .type =
1131 ET_DENY };
1132 ret = eq_push(&ui_e);
1133 ui_action = UIA_NONE;
1134 } else if (k == SDLK_y) {
1135 ui_e = (struct ui_event) { .type =
1136 ET_CONFIRM };
1137 ret = eq_push(&ui_e);
1138 ui_action = UIA_NONE;
1141 break;
1142 default:
1144 if (k == SDLK_q ||
1145 k == SDLK_ESCAPE) {
1146 ui_action = UIA_NONE;
1149 break;
1152 break;
1153 case SDL_WINDOWEVENT:
1155 if (sdl_e.window.event == SDL_WINDOWEVENT_RESIZED ||
1156 sdl_e.window.event == SDL_WINDOWEVENT_MAXIMIZED ||
1157 sdl_e.window.event == SDL_WINDOWEVENT_RESTORED) {
1158 react_to_window_resized();
1159 } else if (sdl_e.window.event ==
1160 SDL_WINDOWEVENT_LEAVE) {
1161 /* This tells the dragging code to not respond */
1162 last_mouse_x = -1;
1163 last_mouse_y = -1;
1166 break;
1167 case SDL_MOUSEMOTION:
1169 if (sdl_e.motion.state & SDL_BUTTON_LMASK) {
1170 int x = sdl_e.motion.x;
1171 int y = sdl_e.motion.y;
1173 if (last_mouse_x >= 0 &&
1174 last_mouse_y >= 0) {
1175 if (selected_vertex != (size_t) -1) {
1176 q->v[selected_vertex].x += (x -
1177 last_mouse_x);
1178 q->v[selected_vertex].y += (y -
1179 last_mouse_y);
1180 } else {
1181 offset_x += (x - last_mouse_x);
1182 offset_y += (y - last_mouse_y);
1185 } else {
1186 recalculate_selected_items(sdl_e.motion.x,
1187 sdl_e.motion.y);
1190 last_mouse_x = sdl_e.motion.x;
1191 last_mouse_y = sdl_e.motion.y;
1192 break;
1193 case SDL_MOUSEBUTTONUP:
1195 if ((sdl_e.button.state & SDL_BUTTON_LMASK) &&
1196 ui_action != UIA_NEW_VERTEX) {
1197 last_mouse_x = -1;
1198 last_mouse_y = -1;
1201 recalculate_selected_items(sdl_e.button.x,
1202 sdl_e.button.y);
1203 break;
1204 case SDL_MOUSEBUTTONDOWN:
1206 if (!(sdl_e.button.state & SDL_BUTTON_LMASK)) {
1207 break;
1210 if (ui_action != UIA_NEW_VERTEX) {
1211 last_mouse_x = -1;
1212 last_mouse_y = -1;
1215 switch (ui_action) {
1216 case UIA_MUTATE:
1218 if (selected_vertex == (size_t) -1) {
1219 break;
1222 ui_e = (struct ui_event) {
1223 /* */
1224 .type = ET_MUTATE, .idx_1 =
1225 selected_vertex
1227 ret = eq_push(&ui_e);
1228 ui_action = UIA_NONE;
1229 break;
1230 case UIA_NEW_VERTEX:
1232 if (selected_vertex != (size_t) -1 ||
1233 selected_edge_i != (size_t) -1 ||
1234 selected_edge_j != (size_t) -1) {
1235 break;
1238 int cx = sdl_e.button.x - offset_x;
1239 int cy = sdl_e.button.y - offset_y;
1241 ui_e = (struct ui_event) {
1242 /* */
1243 .type = ET_NEW_VERTEX, .int_1 = cx,
1244 .int_2 = cy
1246 ret = eq_push(&ui_e);
1247 ui_action = UIA_NONE;
1248 break;
1249 case UIA_NEW_EDGE_1:
1250 case UIA_NEW_H_EDGE_1:
1252 if (selected_vertex == (size_t) -1) {
1253 ui_action = UIA_NONE;
1254 break;
1257 last_clicked_vertex = selected_vertex;
1258 ui_action = (ui_action == UIA_NEW_EDGE_1 ?
1259 UIA_NEW_EDGE_2 : UIA_NEW_H_EDGE_2);
1260 break;
1261 case UIA_NEW_EDGE_2:
1262 case UIA_NEW_H_EDGE_2:
1264 if (selected_vertex == (size_t) -1 ||
1265 selected_vertex == last_clicked_vertex) {
1266 ui_action = UIA_NONE;
1267 break;
1270 ui_e = (struct ui_event) {
1271 /* */
1272 .type = ET_NEW_EDGE, .idx_1 =
1273 last_clicked_vertex, .idx_2 =
1274 selected_vertex, .a = 1, .b =
1275 (ui_action == UIA_NEW_EDGE_2 ?
1276 1 : 2)
1278 ret = eq_push(&ui_e);
1279 ui_action = UIA_NONE;
1280 break;
1281 case UIA_DELETE:
1283 if (selected_vertex != (size_t) -1) {
1284 ui_e = (struct ui_event) {
1285 /* */
1286 .type = ET_DELETE_VERTEX,
1287 .idx_1 = selected_vertex
1289 ret = eq_push(&ui_e);
1290 } else if (selected_edge_i != (size_t) -1 &&
1291 selected_edge_j != (size_t) -1) {
1292 ui_e = (struct ui_event) {
1293 /* */
1294 .type = ET_DELETE_EDGE, .idx_1 =
1295 selected_edge_i,
1296 .idx_2 =
1297 selected_edge_j
1299 ret = eq_push(&ui_e);
1302 ui_action = UIA_NONE;
1303 break;
1304 case UIA_INC_FATNESS:
1305 case UIA_DEC_FATNESS:
1307 if (selected_vertex == (size_t) -1) {
1308 break;
1311 ui_e = (struct ui_event) {
1312 /* */
1313 .type = ET_CHANGE_FATNESS, .idx_1 =
1314 selected_vertex, .int_1 =
1315 (ui_action ==
1316 UIA_INC_FATNESS
1317 ? 1 : -1)
1319 ret = eq_push(&ui_e);
1320 ui_action = UIA_NONE;
1321 break;
1322 case UIA_NONE:
1323 case UIA_CONF_DENY:
1324 case UIA_LEN:
1325 break;
1328 break;
1331 if (ret) {
1332 goto done;
1336 /* framelimit */
1337 now = SDL_GetTicks();
1339 if (frame_start_ticks < now) {
1340 Uint32 elapsed_time = now - frame_start_ticks;
1342 if (elapsed_time < TICKS_PER_FRAME) {
1343 SDL_Delay(TICKS_PER_FRAME - elapsed_time);
1347 done:
1349 return ret;
1352 /* Return an event to the main loop */
1353 int ui_get_event(struct ui_event *e, uint_fast8_t *more)
1355 eq_pop(e);
1356 *more = eq_head != eq_tail;
1358 return 0;
1361 /* Be told to display a confirm dialog */
1362 int ui_confirm_deny(const char *s)
1364 int ret = 0;
1366 if ((ret = render_text(s, &conf_deny))) {
1367 goto done;
1370 ui_action = UIA_CONF_DENY;
1371 done:
1373 return ret;