Remove ui_confirm_deny() as an extraneous abstraction
[clav.git] / ui-sdl.c
blobd2792b8be21974743493ae2e03b0279db3347bbf
1 /*
2 * Copyright (c) 2016, S. Gilles <sgilles@math.umd.edu>
4 * Permission to use, copy, modify, and/or distribute this software
5 * for any purpose with or without fee is hereby granted, provided
6 * that the above copyright notice and this permission notice appear
7 * in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
13 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
14 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
15 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <errno.h>
19 #include <math.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
24 #include <SDL.h>
25 #include <SDL_ttf.h>
27 #include "macros.h"
28 #include "quiver.h"
29 #include "ui.h"
31 #define TICKS_PER_FRAME (1000 / 60)
33 #define TRIG_PRECALC_NUM 4
35 /* What clicking does */
36 static enum ui_action {
37 UIA_NONE = 0,
38 UIA_ASK_QUIT,
39 UIA_DEC_FATNESS,
40 UIA_DELETE,
41 UIA_INC_FATNESS,
42 UIA_MUTATE,
43 UIA_NEW_EDGE_1,
44 UIA_NEW_EDGE_2,
45 UIA_NEW_H_EDGE_1,
46 UIA_NEW_H_EDGE_2,
47 UIA_NEW_VERTEX,
48 UIA_LEN
49 } ui_action;
51 /* The window we'll be using */
52 static SDL_Window *sdl_win;
54 /* The renderer we'll be using */
55 static SDL_Renderer *sdl_renderer;
57 /* How to limit the framerate */
58 static Uint32 frame_start_ticks;
60 /* The informative texture */
61 static SDL_Texture *selected_info;
63 /* Buffer for event queue */
64 static struct ui_event *eq_buf;
66 /* Current max length of event queue */
67 static size_t eq_len;
69 /* Current head of queue */
70 static size_t eq_head;
72 /* Current tail of queue */
73 static size_t eq_tail;
75 /* The quiver we'll be using */
76 static struct quiver *q;
78 /* Width of drawing area in pixels */
79 static int da_pix_width;
81 /* Height of drawing area in pixels */
82 static int da_pix_height;
84 /* The background color */
85 static SDL_Color color_bg = { .r = 0xe2, .g = 0xe2, .b = 0xe2, .a = 0xff };
87 /* The normal vertex color */
88 static SDL_Color color_v = { .r = 0x82, .g = 0x82, .b = 0xb2, .a = 0xff };
90 /* The vertex color for preview vertices */
91 static SDL_Color color_v_preview = { .r = 0x82, .g = 0x82, .b = 0xb2, .a =
92 0x40 };
94 /* The normal vertex outline color */
95 static SDL_Color color_outline = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x80 };
97 /* The vertex outline color for preview vertices */
98 static SDL_Color color_outline_preview = { .r = 0x12, .g = 0x12, .b = 0x12, .a =
99 0x20 };
101 /* The selected vertex outline color */
102 static SDL_Color color_outline_sel = { .r = 0x42, .g = 0x42, .b = 0xe2, .a =
103 0x80 };
105 /* The font color */
106 static SDL_Color color_font = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x80 };
108 /* The normal edge color */
109 static SDL_Color color_e_normal = { .r = 0x12, .g = 0x12, .b = 0x12, .a =
110 0xff };
112 /* The half edge color */
113 static SDL_Color color_e_half = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x60 };
115 /* The abnormal edge color */
116 static SDL_Color color_e_abnormal = { .r = 0xd0, .g = 0x12, .b = 0x12, .a =
117 0xf0 };
119 /* The selected edge color */
120 static SDL_Color color_e_sel = { .r = 0xb2, .g = 0xb2, .b = 0xe2, .a = 0x40 };
122 /* The font for node names, instructions, etc */
123 static TTF_Font *normal_font;
125 /* The base font size */
126 static uint_fast8_t base_font_size = 12;
128 /* How much space (pixels) between lower left of screen and bottom text */
129 static unsigned int text_border_padding = 24;
131 /* Strings to display at bottom of screen */
132 static const char *bottom_string[] = {
133 /* */
134 [UIA_NONE] = "[m] Mutate\n[v] Create new vertex\n"
135 "[d] Delete vertex/edge\n[e] Add edge\n"
136 "[h] Add half edge\n[f] Increase vertex fatness\n"
137 "[g] Decrease vertex fatness\n[q] Quit", [UIA_NEW_VERTEX] =
138 "[Mouse1] Create new vertex\n[ESC] Cancel", /* */
139 [UIA_DELETE] = "[Mouse1] Delete vertex/edge\n[ESC] Cancel", /* */
140 [UIA_NEW_EDGE_1] = "[Mouse1] Select start\n[ESC] Cancel", /* */
141 [UIA_NEW_EDGE_2] = "[Mouse1] Select end\n[ESC] Cancel", /* */
142 [UIA_NEW_H_EDGE_1] = "[Mouse1] Select start\n[ESC] Cancel", /* */
143 [UIA_NEW_H_EDGE_2] = "[Mouse1] Select end\n[ESC] Cancel", /* */
144 [UIA_INC_FATNESS] = "[Mouse1] Increase fatness\n[ESC] Cancel", /* */
145 [UIA_DEC_FATNESS] = "[Mouse1] Decrease fatness\n[ESC] Cancel", /* */
146 [UIA_MUTATE] = "[Mouse1] Mutate at vertex\n[ESC] Cancel", /* */
147 [UIA_ASK_QUIT] = "Quit?\n\n[y] Confirm\n[n] Cancel", /* */
148 [UIA_LEN] = "" /* */
151 /* The texture containing what to show on the bottom */
152 static SDL_Texture *bottom_text[UIA_LEN];
154 /* The textures containing the names of all vertices - indexed just like q */
155 static SDL_Texture **vertex_names;
157 /* The number of elements of vertex_names */
158 static size_t vertex_names_len;
160 /* Drawing offset x */
161 static int offset_x;
163 /* Drawing offset x */
164 static int offset_y;
166 /* How wide (in pixels) a fatness 1 node should be */
167 static unsigned int base_node_radius = 8;
169 /* How much (in pixels) a node should widen for each fatness level */
170 static unsigned int node_radius_per_fatness = 7;
172 /* How wide (in pixels) the outline should be */
173 static int outline_width = 2;
175 /* How wide (in pixels) the arrowheads should be */
176 static int arrow_length = 7;
178 /* How narrow the arrowheads should be */
179 static double arrow_angle = 6.5 * M_PI / 8.0;
181 /* How close to an arrow (in pixels) for it to be selected */
182 static int edge_margin = 5;
184 /* If we're interacting with a vertex, which one */
185 static size_t selected_vertex = (size_t) -1;
187 /* If we're interacting with an edge, the i */
188 static size_t selected_edge_i = (size_t) -1;
190 /* If we're interacting with an edge, the j */
191 static size_t selected_edge_j = (size_t) -1;
193 /* If we're adding an edge, the last vertex we clicked on */
194 static size_t last_clicked_vertex = (size_t) -1;
196 /* x-coordinate of last mouse position */
197 static int last_mouse_x = -1;
199 /* y-coordinate of last mouse position */
200 static int last_mouse_y = -1;
202 /* sine tables */
203 static double precalc_sins[TRIG_PRECALC_NUM];
205 /* cosine tables */
206 static double precalc_coss[TRIG_PRECALC_NUM];
208 /* Precalculate sines and cosines */
209 static void precalc_trig(void)
211 precalc_coss[0] = 0.0;
212 precalc_sins[0] = 1.0;
214 for (size_t j = 1; j < TRIG_PRECALC_NUM - 1; ++j) {
215 double theta = (M_PI * j) / (2 * (TRIG_PRECALC_NUM - 1));
217 precalc_sins[j] = sin(theta);
218 precalc_coss[j] = cos(theta);
221 precalc_coss[TRIG_PRECALC_NUM - 1] = 0.0;
222 precalc_sins[TRIG_PRECALC_NUM - 1] = 1.0;
225 /* GCD */
226 static int gcd(uint_fast8_t x, uint_fast8_t y)
228 uint_fast8_t r = 0;
230 if (!x &&
231 !y) {
232 return 1;
235 while (y) {
236 r = x % y;
237 x = y;
238 y = r;
241 return x;
244 /* Allocate and print a rational in the way a user expects */
245 static int pretty_fraction(struct rational *r, char **out)
247 int ret = 0;
249 if (r->q == 1) {
250 size_t len = snprintf(0, 0, "%d", (int) r->p);
252 if (!(*out = malloc(len + 1))) {
253 ret = errno;
254 perror(L("malloc"));
255 goto done;
258 sprintf(*out, "%d", (int) r->p);
259 goto done;
262 size_t len = snprintf(0, 0, "%d/%u", (int) r->p, (unsigned int) r->q);
264 if (!(*out = malloc(len + 1))) {
265 ret = errno;
266 perror(L("malloc"));
267 goto done;
270 sprintf(*out, "%d/%u", (int) r->p, (unsigned int) r->q);
271 done:
273 return ret;
276 /* Render text to texture */
277 static int render_text(const char *text, SDL_Texture **out)
279 if (!out) {
280 return 0;
283 if (*out) {
284 SDL_DestroyTexture(*out);
287 int ret = 0;
288 SDL_Surface *surf = 0;
290 if (!(surf = TTF_RenderUTF8_Blended_Wrapped(normal_font, text,
291 color_font, 800))) {
292 fprintf(stderr, L("TTF_RenderUTF8_Shaded(): %s\n"),
293 TTF_GetError());
294 ret = ENOMEDIUM;
295 goto done;
298 if (!(*out = SDL_CreateTextureFromSurface(sdl_renderer, surf))) {
299 fprintf(stderr, L("SDL_CreateTextureFromSurface(): %s\n"),
300 SDL_GetError());
301 ret = ENOMEDIUM;
302 goto done;
305 done:
306 SDL_FreeSurface(surf);
308 return ret;
311 /* Load fonts */
312 static int load_fonts(void)
314 int ret = 0;
316 if (normal_font) {
317 TTF_CloseFont(normal_font);
318 normal_font = 0;
321 if (!(normal_font = TTF_OpenFont(FONT_PATH, base_font_size))) {
322 ret = ENOMEDIUM;
323 fprintf(stderr, L("TTF_OpenFont(): %s\n"), TTF_GetError());
324 goto done;
327 for (size_t j = 0; j < UIA_LEN; ++j) {
328 if ((ret = render_text(bottom_string[j], &bottom_text[j]))) {
329 goto done;
333 done:
335 if (ret) {
336 if (normal_font) {
337 TTF_CloseFont(normal_font);
340 normal_font = 0;
343 return ret;
346 /* Convert `internal coordinates' to pixel coordinates */
347 static void internal_to_pixel_xy(int in_x, int in_y, int *out_x, int *out_y)
349 *out_x = in_x + offset_x;
350 *out_y = in_y + offset_y;
353 /* Convert pixel coordinates to `internal coordinates' */
354 static void pixel_to_internal_xy(int in_x, int in_y, int *out_x, int *out_y)
356 *out_x = in_x - offset_x;
357 *out_y = in_y - offset_y;
360 /* Set selected_vertex and selected_edge_{i,j} */
361 static int recalculate_selected_items(int mx, int my)
363 int x = 0;
364 int y = 0;
365 int ret = 0;
366 char *s = 0;
367 char *sij = 0;
368 char *sji = 0;
370 pixel_to_internal_xy(mx, my, &x, &y);
371 size_t last_vertex = selected_vertex;
372 size_t last_edge_i = selected_edge_i;
373 size_t last_edge_j = selected_edge_j;
375 selected_vertex = (size_t) -1;
376 selected_edge_i = (size_t) -1;
377 selected_edge_j = (size_t) -1;
379 for (size_t j = q->v_num; j > 0; --j) {
380 struct vertex *v = &(q->v[j - 1]);
381 int r = base_node_radius + v->fatness * node_radius_per_fatness;
383 if (x > v->x - r &&
384 x < v->x + r &&
385 y > v->y - r &&
386 y < v->y + r) {
387 selected_vertex = j - 1;
388 goto compute_str;
392 for (size_t j = 1; j < q->v_num; ++j) {
393 struct vertex *v1 = &(q->v[j]);
395 for (size_t i = 0; i < j; ++i) {
396 if (!q->e[i * q->v_len + j].p &&
397 !q->e[j * q->v_len + i].p) {
398 continue;
401 struct vertex *v2 = &(q->v[i]);
403 if ((x - edge_margin > v1->x &&
404 x - edge_margin > v2->x) ||
405 (x + edge_margin < v1->x &&
406 x + edge_margin < v2->x) ||
407 (y - edge_margin > v1->y &&
408 y - edge_margin > v2->y) ||
409 (y + edge_margin < v1->y &&
410 y + edge_margin < v2->y)) {
411 continue;
414 if (v1->x == v2->x) {
415 if (x + edge_margin > v1->x &&
416 x - edge_margin < v1->x) {
417 selected_edge_i = i;
418 selected_edge_j = j;
419 goto compute_str;
421 } else if (v1->y == v2->y) {
422 if (y + edge_margin > v1->y &&
423 y - edge_margin < v1->y) {
424 selected_edge_i = i;
425 selected_edge_j = j;
426 goto compute_str;
428 } else {
429 double m1 = ((double) (v2->y - v1->y)) /
430 ((double) (v2->x - v1->x));
431 double m2 = -1.0 / m1;
432 double xint = ((double) y - (double) v1->y +
433 m1 * v1->x - m2 * x) / (m1 - m2);
434 double yint = m1 * xint - m1 * v1->x +
435 (double) v1->y;
437 if ((x - xint) * (x - xint) + (y - yint) * (y -
438 yint)
439 < edge_margin * edge_margin) {
440 selected_edge_i = i;
441 selected_edge_j = j;
442 goto compute_str;
448 compute_str:
450 if (selected_vertex != last_vertex &&
451 selected_vertex != (size_t) -1) {
452 struct vertex *v = &(q->v[selected_vertex]);
453 size_t len = snprintf(0, 0,
454 "Name: %s\nFatness: %d\nPosition: (%d,%d)",
455 v->name,
456 (int) v->fatness, v->x, v->y);
458 if (!(s = malloc(len + 1))) {
459 ret = errno;
460 perror(L("malloc"));
461 goto done;
464 sprintf(s, "Name: %s\nFatness: %d\nPosition: (%d,%d)",
465 v->name, (int) v->fatness, v->x, v->y);
467 if ((ret = render_text(s, &selected_info))) {
468 goto done;
470 } else if ((selected_edge_i != last_edge_i ||
471 selected_edge_j != last_edge_j) &&
472 selected_edge_i != (size_t) -1 &&
473 selected_edge_j != (size_t) -1) {
474 struct vertex *i = &(q->v[selected_edge_i]);
475 struct vertex *j = &(q->v[selected_edge_j]);
476 struct rational *eij = &(q->e[selected_edge_i * q->v_len +
477 selected_edge_j]);
478 struct rational *eji = &(q->e[selected_edge_j * q->v_len +
479 selected_edge_i]);
481 if ((ret = pretty_fraction(eij, &sij))) {
482 goto done;
485 if ((ret = pretty_fraction(eji, &sji))) {
486 goto done;
489 size_t len = snprintf(0, 0,
490 "%s \u2192 %s: %s\n%s \u2192 %s: %s",
491 i->name, j->name, sij,
492 j->name, i->name, sji);
494 if (!(s = malloc(len + 1))) {
495 ret = errno;
496 perror(L("malloc"));
497 goto done;
500 sprintf(s, "%s \u2192 %s: %s\n%s \u2192 %s: %s", i->name,
501 j->name, sij, j->name, i->name, sji);
503 if ((ret = render_text(s, &selected_info))) {
504 goto done;
508 done:
509 free(s);
510 free(sij);
511 free(sji);
513 return ret;
516 /* Render vertex names as textures */
517 static int render_vertex_names(void)
519 if (vertex_names) {
520 for (size_t j = 0; j < vertex_names_len; ++j) {
521 SDL_DestroyTexture(vertex_names[j]);
522 vertex_names[j] = 0;
526 if (!(vertex_names = realloc(vertex_names, q->v_num *
527 sizeof(*vertex_names)))) {
528 int sv_err = errno;
530 perror(L("realloc()"));
531 vertex_names_len = 0;
533 return sv_err;
536 vertex_names_len = q->v_num;
538 for (size_t j = 0; j < vertex_names_len; ++j) {
539 vertex_names[j] = 0;
542 for (size_t j = 0; j < vertex_names_len; ++j) {
543 int ret = 0;
545 if ((ret = render_text(q->v[j].name, &(vertex_names[j])))) {
546 return ret;
550 return 0;
553 /* Get information about the window */
554 static void react_to_window_resized(void)
556 int old_pix_width = da_pix_width;
557 int old_pix_height = da_pix_height;
559 SDL_GetWindowSize(sdl_win, &da_pix_width, &da_pix_height);
561 if (old_pix_width == da_pix_width &&
562 old_pix_height == da_pix_height) {
563 return;
566 offset_x += (da_pix_width - old_pix_width) / 2;
567 offset_y += (da_pix_height - old_pix_height) / 2;
570 /* Pop from queue */
571 static void eq_pop(struct ui_event *out)
573 if (eq_head == eq_tail) {
574 *out = (struct ui_event) { 0 };
576 return;
579 memcpy(out, eq_buf + eq_head, sizeof *out);
580 eq_buf[eq_head] = (struct ui_event) { 0 };
581 eq_head = (eq_head + 1) % eq_len;
584 /* Push into queue */
585 static int eq_push(struct ui_event *in)
587 if (((eq_tail + 1) % eq_len) == eq_head) {
588 void *newmem;
590 if ((eq_len * sizeof *in) >= ((size_t) -1) / 2) {
591 fprintf(stderr, L(
592 "eq_push: Impossibly large buffer\n"));
594 return ENOMEM;
597 if (!(newmem = realloc(eq_buf, (eq_len * 2) *
598 sizeof *eq_buf))) {
599 int sv_err = errno;
601 perror(L("realloc"));
603 return sv_err;
606 eq_buf = (struct ui_event *) newmem;
607 eq_len *= 2;
610 memcpy(eq_buf + eq_tail, in, sizeof *in);
611 eq_tail = (eq_tail + 1) % eq_len;
613 return 0;
616 /* Initialize SDL */
617 int ui_init(struct quiver *i_q)
619 int ret;
621 q = i_q;
623 if (SDL_Init(SDL_INIT_VIDEO) < 0) {
624 fprintf(stderr, L("SDL_Init(): %s\n"), SDL_GetError());
626 return ENOMEDIUM;
629 sdl_win = SDL_CreateWindow("Cluster Algebra Visualizer",
630 SDL_WINDOWPOS_UNDEFINED,
631 SDL_WINDOWPOS_UNDEFINED, 1000, 1000,
632 SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
634 if (!sdl_win) {
635 fprintf(stderr, L("SDL_CreateWindow(): %s\n"), SDL_GetError());
637 return ENOMEDIUM;
640 sdl_renderer = SDL_CreateRenderer(sdl_win, -1,
641 SDL_RENDERER_ACCELERATED);
643 if (!sdl_renderer) {
644 fprintf(stderr, L("SDL_CreateRenderer(): %s\n"),
645 SDL_GetError());
647 return ENOMEDIUM;
650 if (TTF_Init() < 0) {
651 fprintf(stderr, L("TTF_Init(): %s\n"), TTF_GetError());
653 return ENOMEDIUM;
656 if ((ret = load_fonts())) {
657 goto done;
660 if ((ret = render_vertex_names())) {
661 goto done;
664 if ((ret = SDL_SetRenderDrawColor(sdl_renderer, color_bg.r, color_bg.g,
665 color_bg.b, color_bg.a))) {
666 fprintf(stderr, L("SDL_SetRenderDrawColor(): %s\n"),
667 SDL_GetError());
668 goto done;
671 if ((ret = SDL_RenderClear(sdl_renderer))) {
672 fprintf(stderr, L("SDL_RenderClear(): %s\n"), SDL_GetError());
673 goto done;
676 SDL_RenderPresent(sdl_renderer);
677 react_to_window_resized();
679 /* Set up queue for returning data */
680 if (!(eq_buf = calloc(2, sizeof *eq_buf))) {
681 ret = errno;
682 perror(L("malloc"));
683 goto done;
686 eq_len = 2;
687 eq_head = 0;
688 eq_tail = 0;
690 /* Sines and cosines for drawing circles */
691 precalc_trig();
692 done:
694 return ret;
697 /* Deal with the fact that the quiver was changed */
698 int ui_respond_quiver_change(void)
700 return render_vertex_names();
703 /* Tear down SDL */
704 int ui_teardown(void)
706 if (vertex_names) {
707 for (size_t j = 0; j < vertex_names_len; ++j) {
708 SDL_DestroyTexture(vertex_names[j]);
709 vertex_names[j] = 0;
713 for (size_t j = 0; j < UIA_LEN; ++j) {
714 SDL_DestroyTexture(bottom_text[j]);
715 bottom_text[j] = 0;
718 if (selected_info) {
719 SDL_DestroyTexture(selected_info);
720 selected_info = 0;
723 if (normal_font) {
724 TTF_CloseFont(normal_font);
725 normal_font = 0;
728 if (sdl_win) {
729 SDL_DestroyWindow(sdl_win);
732 SDL_Quit();
734 return 0;
737 /* Record that a frame has been started */
738 int ui_start_frame(void)
740 int ret = 0;
741 int rho = 0;
742 SDL_Rect r = { 0 };
743 Uint32 dummy_format;
744 int dummy_access;
745 int tex_w;
746 int tex_h;
748 frame_start_ticks = SDL_GetTicks();
750 /* Draw the damn thing */
751 SDL_SetRenderDrawColor(sdl_renderer, color_bg.r, color_bg.g, color_bg.b,
752 color_bg.a);
753 SDL_RenderClear(sdl_renderer);
755 /* Draw each edge */
756 for (size_t j = 0; j < q->v_num; ++j) {
757 for (size_t i = 0; i < j; ++i) {
758 /* First, determine if we're looking at a half-edge or a full-edge */
759 int d = gcd(q->v[i].fatness, q->v[j].fatness);
760 struct rational *eij = &(q->e[i * q->v_len + j]);
761 struct rational *eji = &(q->e[j * q->v_len + i]);
762 int cx = 0;
763 int cy = 0;
764 int cx2 = 0;
765 int cy2 = 0;
766 double theta = 0.0;
768 if (!eij->p &&
769 !eji->p) {
770 continue;
773 internal_to_pixel_xy(q->v[i].x, q->v[i].y, &cx, &cy);
774 internal_to_pixel_xy(q->v[j].x, q->v[j].y, &cx2, &cy2);
776 if (selected_edge_i == i &&
777 selected_edge_j == j) {
778 if ((ret = SDL_SetRenderDrawColor(sdl_renderer,
779 color_e_sel.r,
780 color_e_sel.g,
781 color_e_sel.b,
782 color_e_sel.a)))
784 fprintf(stderr, L(
785 "SDL_RenderDrawColor(): %s\n"),
786 SDL_GetError());
787 goto done;
790 for (int id = -edge_margin; id < edge_margin;
791 ++id) {
792 for (int jd = -edge_margin; jd <
793 edge_margin; ++jd) {
794 if ((ret = SDL_RenderDrawLine(
795 sdl_renderer, cx +
796 id, cy + jd,
797 cx2 + id, cy2 +
798 jd))) {
799 fprintf(stderr, L(
800 "SDL_RenderDrawLine(): %s\n"),
801 SDL_GetError());
802 goto done;
808 /* This is the (eij)/dj = -(eji)/di condition */
809 if (eij->p * q->v[i].fatness * eji->q != -eji->p *
810 q->v[j].fatness * eij->q) {
811 ret = SDL_SetRenderDrawColor(sdl_renderer,
812 color_e_abnormal.r,
813 color_e_abnormal.g,
814 color_e_abnormal.b,
815 color_e_abnormal.a);
816 } else if (abs(eij->p) * d == q->v[j].fatness *
817 eij->q) {
818 ret = SDL_SetRenderDrawColor(sdl_renderer,
819 color_e_normal.r,
820 color_e_normal.g,
821 color_e_normal.b,
822 color_e_normal.a);
823 } else if (2 * abs(eij->p) * d == q->v[j].fatness *
824 eij->q) {
825 ret = SDL_SetRenderDrawColor(sdl_renderer,
826 color_e_half.r,
827 color_e_half.g,
828 color_e_half.b,
829 color_e_half.a);
830 } else {
831 ret = SDL_SetRenderDrawColor(sdl_renderer,
832 color_e_abnormal.r,
833 color_e_abnormal.g,
834 color_e_abnormal.b,
835 color_e_abnormal.a);
838 if (ret) {
839 fprintf(stderr, L(
840 "SDL_SetRenderDrawColor(): %s\n"),
841 SDL_GetError());
842 goto done;
845 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx2,
846 cy2))) {
847 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
848 SDL_GetError());
849 goto done;
852 if (cx == cx2) {
853 theta = (cy2 > cy) ? M_PI / 2.0 : -M_PI / 2.0;
854 } else {
855 theta = atan2f(cy2 - cy, cx2 - cx);
858 if ((eij->p < 0)) {
859 theta += M_PI;
862 cx = (cx + cx2) / 2;
863 cy = (cy + cy2) / 2;
864 cx2 = cx + arrow_length * cos(theta + arrow_angle);
865 cy2 = cy + arrow_length * sin(theta + arrow_angle);
867 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx2,
868 cy2))) {
869 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
870 SDL_GetError());
871 goto done;
874 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx +
875 arrow_length * cos(theta -
876 arrow_angle),
877 cy +
878 arrow_length * sin(theta -
879 arrow_angle))))
881 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
882 SDL_GetError());
883 goto done;
888 /* Draw each vertex as a box */
889 for (size_t j = 0; j < q->v_num; ++j) {
890 struct vertex *v = &(q->v[j]);
891 int cx = 0;
892 int cy = 0;
894 internal_to_pixel_xy(v->x, v->y, &cx, &cy);
896 /* Central square */
897 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_NONE);
898 SDL_SetRenderDrawColor(sdl_renderer, color_v.r, color_v.g,
899 color_v.b, color_v.a);
900 rho = base_node_radius + node_radius_per_fatness * v->fatness;
901 r.x = cx - rho;
902 r.y = cy - rho;
903 r.w = 2 * rho;
904 r.h = 2 * rho;
905 SDL_RenderFillRect(sdl_renderer, &r);
907 /* Outline */
908 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
910 if (j == selected_vertex) {
911 SDL_SetRenderDrawColor(sdl_renderer,
912 color_outline_sel.r,
913 color_outline_sel.g,
914 color_outline_sel.b,
915 color_outline_sel.a);
916 } else {
917 SDL_SetRenderDrawColor(sdl_renderer, color_outline.r,
918 color_outline.g, color_outline.b,
919 color_outline.a);
922 r.x = cx - rho;
923 r.y = cy - rho;
924 r.w = 2 * rho - outline_width;
925 r.h = outline_width;
926 SDL_RenderFillRect(sdl_renderer, &r);
927 r.x = cx + rho - outline_width;
928 r.y = cy - rho;
929 r.w = outline_width;
930 r.h = 2 * rho - outline_width;
931 SDL_RenderFillRect(sdl_renderer, &r);
932 r.x = cx - rho + outline_width;
933 r.y = cy + rho - outline_width;
934 r.w = 2 * rho - outline_width;
935 r.h = outline_width;
936 SDL_RenderFillRect(sdl_renderer, &r);
937 r.x = cx - rho;
938 r.y = cy - rho + outline_width;
939 r.w = outline_width;
940 r.h = 2 * rho - outline_width;
941 SDL_RenderFillRect(sdl_renderer, &r);
943 /* Text */
944 if (j >= vertex_names_len) {
945 fprintf(stderr, L(
946 "render_vertex_names() was not called, somehow\n"));
947 ret = EINVAL;
948 goto done;
951 if (SDL_QueryTexture(vertex_names[j], &dummy_format,
952 &dummy_access, &tex_w, &tex_h)) {
953 fprintf(stderr, L("SDL_QueryTexture(): %s\n"),
954 SDL_GetError());
955 ret = ENOMEDIUM;
956 goto done;
959 r.x = cx - tex_w / 2;
960 r.y = cy - tex_h / 2;
961 r.w = tex_w;
962 r.h = tex_h;
963 SDL_RenderCopy(sdl_renderer, vertex_names[j], 0, &r);
966 /* If adding a new vertex, draw preview */
967 if (ui_action == UIA_NEW_VERTEX &&
968 last_mouse_x != -1 &&
969 last_mouse_y != -1) {
970 /* Central square */
971 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_NONE);
972 SDL_SetRenderDrawColor(sdl_renderer, color_v_preview.r,
973 color_v_preview.g, color_v_preview.b,
974 color_v_preview.a);
975 rho = base_node_radius;
976 r.x = last_mouse_x - rho;
977 r.y = last_mouse_y - rho;
978 r.w = 2 * rho;
979 r.h = 2 * rho;
980 SDL_RenderFillRect(sdl_renderer, &r);
982 /* Outline */
983 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
984 SDL_SetRenderDrawColor(sdl_renderer, color_outline_preview.r,
985 color_outline_preview.g,
986 color_outline_preview.b,
987 color_outline_preview.a);
988 r.x = last_mouse_x - rho;
989 r.y = last_mouse_y - rho;
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 - outline_width;
994 r.y = last_mouse_y - rho;
995 r.w = outline_width;
996 r.h = 2 * rho - outline_width;
997 SDL_RenderFillRect(sdl_renderer, &r);
998 r.x = last_mouse_x - rho + outline_width;
999 r.y = last_mouse_y + rho - outline_width;
1000 r.w = 2 * rho - outline_width;
1001 r.h = outline_width;
1002 SDL_RenderFillRect(sdl_renderer, &r);
1003 r.x = last_mouse_x - rho;
1004 r.y = last_mouse_y - rho + outline_width;
1005 r.w = outline_width;
1006 r.h = 2 * rho - outline_width;
1007 SDL_RenderFillRect(sdl_renderer, &r);
1010 /* If adding a new edge, draw possible */
1011 if ((ui_action == UIA_NEW_EDGE_2 ||
1012 ui_action == UIA_NEW_H_EDGE_2) &&
1014 /* last_clicked_vertex != (size_t) -1 && */
1015 last_mouse_x != -1 &&
1016 last_mouse_y != -1) {
1017 int cx = 0;
1018 int cy = 0;
1020 ret = SDL_SetRenderDrawColor(sdl_renderer, color_e_normal.r,
1021 color_e_normal.g, color_e_normal.b,
1022 color_e_normal.a);
1023 internal_to_pixel_xy(q->v[last_clicked_vertex].x,
1024 q->v[last_clicked_vertex].y, &cx, &cy);
1026 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy,
1027 last_mouse_x, last_mouse_y))) {
1028 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
1029 SDL_GetError());
1030 goto done;
1034 /* Bottom text */
1035 if (SDL_QueryTexture(bottom_text[ui_action], &dummy_format,
1036 &dummy_access, &tex_w, &tex_h)) {
1037 fprintf(stderr, L("SDL_QueryTexture(): %s\n"), SDL_GetError());
1038 ret = ENOMEDIUM;
1039 goto done;
1042 r.x = text_border_padding;
1043 r.y = da_pix_height - tex_h - text_border_padding;
1044 r.w = tex_w;
1045 r.h = tex_h;
1046 SDL_RenderCopy(sdl_renderer, bottom_text[ui_action], 0, &r);
1048 /* If something is selected */
1049 if (selected_info &&
1050 (selected_vertex != (size_t) -1 ||
1051 (selected_edge_i != (size_t) -1 &&
1052 selected_edge_j != (size_t) -1))) {
1053 if (SDL_QueryTexture(selected_info, &dummy_format,
1054 &dummy_access, &tex_w, &tex_h)) {
1055 fprintf(stderr, L("SDL_QueryTexture(): %s\n"),
1056 SDL_GetError());
1057 ret = ENOMEDIUM;
1058 goto done;
1061 r.x = text_border_padding;
1062 r.y = text_border_padding;
1063 r.w = tex_w;
1064 r.h = tex_h;
1065 SDL_RenderCopy(sdl_renderer, selected_info, 0, &r);
1068 done:
1070 return ret;
1073 /* Draw a frame, possibly sleeping for framelimit */
1074 int ui_finish_frame(void)
1076 int ret = 0;
1077 struct ui_event ui_e = { 0 };
1078 SDL_Event sdl_e = { 0 };
1079 Uint32 now = 0;
1081 SDL_RenderPresent(sdl_renderer);
1083 /* Handle user input */
1084 while (SDL_PollEvent(&sdl_e) != 0) {
1085 SDL_Keycode k = 0;
1087 switch (sdl_e.type) {
1088 case SDL_QUIT:
1089 ui_e = (struct ui_event) { .type = ET_QUIT };
1090 ret = eq_push(&ui_e);
1091 break;
1092 case SDL_KEYUP:
1093 k = sdl_e.key.keysym.sym;
1095 switch (ui_action) {
1096 case UIA_NONE:
1098 if (k == SDLK_q) {
1099 ui_action = UIA_ASK_QUIT;
1100 } else if (k == SDLK_m) {
1101 ui_action = UIA_MUTATE;
1102 } else if (k == SDLK_v) {
1103 ui_action = UIA_NEW_VERTEX;
1104 } else if (k == SDLK_d) {
1105 ui_action = UIA_DELETE;
1106 } else if (k == SDLK_e) {
1107 ui_action = UIA_NEW_EDGE_1;
1108 } else if (k == SDLK_h) {
1109 ui_action = UIA_NEW_H_EDGE_1;
1110 } else if (k == SDLK_f) {
1111 ui_action = UIA_INC_FATNESS;
1112 } else if (k == SDLK_g) {
1113 ui_action = UIA_DEC_FATNESS;
1116 break;
1117 case UIA_ASK_QUIT:
1119 if (k == SDLK_n ||
1120 k == SDLK_ESCAPE) {
1121 ui_action = UIA_NONE;
1122 } else if (k == SDLK_y) {
1123 ui_e = (struct ui_event) { .type =
1124 ET_QUIT };
1125 ret = eq_push(&ui_e);
1126 ui_action = UIA_NONE;
1129 break;
1130 default:
1132 if (k == SDLK_q ||
1133 k == SDLK_ESCAPE) {
1134 ui_action = UIA_NONE;
1137 break;
1140 break;
1141 case SDL_WINDOWEVENT:
1143 if (sdl_e.window.event == SDL_WINDOWEVENT_RESIZED ||
1144 sdl_e.window.event == SDL_WINDOWEVENT_MAXIMIZED ||
1145 sdl_e.window.event == SDL_WINDOWEVENT_RESTORED) {
1146 react_to_window_resized();
1147 } else if (sdl_e.window.event ==
1148 SDL_WINDOWEVENT_LEAVE) {
1149 /* This tells the dragging code to not respond */
1150 last_mouse_x = -1;
1151 last_mouse_y = -1;
1154 break;
1155 case SDL_MOUSEMOTION:
1157 if (sdl_e.motion.state & SDL_BUTTON_LMASK) {
1158 int x = sdl_e.motion.x;
1159 int y = sdl_e.motion.y;
1161 if (last_mouse_x >= 0 &&
1162 last_mouse_y >= 0) {
1163 if (selected_vertex != (size_t) -1) {
1164 q->v[selected_vertex].x += (x -
1165 last_mouse_x);
1166 q->v[selected_vertex].y += (y -
1167 last_mouse_y);
1168 } else {
1169 offset_x += (x - last_mouse_x);
1170 offset_y += (y - last_mouse_y);
1173 } else {
1174 recalculate_selected_items(sdl_e.motion.x,
1175 sdl_e.motion.y);
1178 last_mouse_x = sdl_e.motion.x;
1179 last_mouse_y = sdl_e.motion.y;
1180 break;
1181 case SDL_MOUSEBUTTONUP:
1183 if ((sdl_e.button.state & SDL_BUTTON_LMASK) &&
1184 ui_action != UIA_NEW_VERTEX) {
1185 last_mouse_x = -1;
1186 last_mouse_y = -1;
1189 recalculate_selected_items(sdl_e.button.x,
1190 sdl_e.button.y);
1191 break;
1192 case SDL_MOUSEBUTTONDOWN:
1194 if (!(sdl_e.button.state & SDL_BUTTON_LMASK)) {
1195 break;
1198 if (ui_action != UIA_NEW_VERTEX) {
1199 last_mouse_x = -1;
1200 last_mouse_y = -1;
1203 switch (ui_action) {
1204 case UIA_MUTATE:
1206 if (selected_vertex == (size_t) -1) {
1207 break;
1210 ui_e = (struct ui_event) {
1211 /* */
1212 .type = ET_MUTATE, .idx_1 =
1213 selected_vertex
1215 ret = eq_push(&ui_e);
1216 ui_action = UIA_NONE;
1217 break;
1218 case UIA_NEW_VERTEX:
1220 if (selected_vertex != (size_t) -1 ||
1221 selected_edge_i != (size_t) -1 ||
1222 selected_edge_j != (size_t) -1) {
1223 break;
1226 int cx = sdl_e.button.x - offset_x;
1227 int cy = sdl_e.button.y - offset_y;
1229 ui_e = (struct ui_event) {
1230 /* */
1231 .type = ET_NEW_VERTEX, .int_1 = cx,
1232 .int_2 = cy
1234 ret = eq_push(&ui_e);
1235 ui_action = UIA_NONE;
1236 break;
1237 case UIA_NEW_EDGE_1:
1238 case UIA_NEW_H_EDGE_1:
1240 if (selected_vertex == (size_t) -1) {
1241 ui_action = UIA_NONE;
1242 break;
1245 last_clicked_vertex = selected_vertex;
1246 ui_action = (ui_action == UIA_NEW_EDGE_1 ?
1247 UIA_NEW_EDGE_2 : UIA_NEW_H_EDGE_2);
1248 break;
1249 case UIA_NEW_EDGE_2:
1250 case UIA_NEW_H_EDGE_2:
1252 if (selected_vertex == (size_t) -1 ||
1253 selected_vertex == last_clicked_vertex) {
1254 ui_action = UIA_NONE;
1255 break;
1258 ui_e = (struct ui_event) {
1259 /* */
1260 .type = ET_NEW_EDGE, .idx_1 =
1261 last_clicked_vertex, .idx_2 =
1262 selected_vertex, .a = 1, .b =
1263 (ui_action == UIA_NEW_EDGE_2 ?
1264 1 : 2)
1266 ret = eq_push(&ui_e);
1267 ui_action = UIA_NONE;
1268 break;
1269 case UIA_DELETE:
1271 if (selected_vertex != (size_t) -1) {
1272 ui_e = (struct ui_event) {
1273 /* */
1274 .type = ET_DELETE_VERTEX,
1275 .idx_1 = selected_vertex
1277 ret = eq_push(&ui_e);
1278 } else if (selected_edge_i != (size_t) -1 &&
1279 selected_edge_j != (size_t) -1) {
1280 ui_e = (struct ui_event) {
1281 /* */
1282 .type = ET_DELETE_EDGE, .idx_1 =
1283 selected_edge_i,
1284 .idx_2 =
1285 selected_edge_j
1287 ret = eq_push(&ui_e);
1290 ui_action = UIA_NONE;
1291 break;
1292 case UIA_INC_FATNESS:
1293 case UIA_DEC_FATNESS:
1295 if (selected_vertex == (size_t) -1) {
1296 break;
1299 ui_e = (struct ui_event) {
1300 /* */
1301 .type = ET_CHANGE_FATNESS, .idx_1 =
1302 selected_vertex, .int_1 =
1303 (ui_action ==
1304 UIA_INC_FATNESS
1305 ? 1 : -1)
1307 ret = eq_push(&ui_e);
1308 ui_action = UIA_NONE;
1309 break;
1310 case UIA_NONE:
1311 case UIA_ASK_QUIT:
1312 case UIA_LEN:
1313 break;
1316 break;
1319 if (ret) {
1320 goto done;
1324 /* framelimit */
1325 now = SDL_GetTicks();
1327 if (frame_start_ticks < now) {
1328 Uint32 elapsed_time = now - frame_start_ticks;
1330 if (elapsed_time < TICKS_PER_FRAME) {
1331 SDL_Delay(TICKS_PER_FRAME - elapsed_time);
1335 done:
1337 return ret;
1340 /* Return an event to the main loop */
1341 int ui_get_event(struct ui_event *e, uint_fast8_t *more)
1343 eq_pop(e);
1344 *more = eq_head != eq_tail;
1346 return 0;