Check more return codes
[clav.git] / ui-sdl.c
blob7c925159468e2eb0ce3455972c62a69a4c7db39d
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 /* The window we'll be using */
19 static SDL_Window *sdl_win;
21 /* The renderer we'll be using */
22 static SDL_Renderer *sdl_renderer;
24 /* How to limit the framerate */
25 static Uint32 frame_start_ticks;
27 /* If conf/deny, the message onscreen */
28 static const char *conf_deny_text;
30 /* Buffer for event queue */
31 static struct ui_event *eq_buf;
33 /* Current max length of event queue */
34 static size_t eq_len;
36 /* Current head of queue */
37 static size_t eq_head;
39 /* Current tail of queue */
40 static size_t eq_tail;
42 /* The quiver we'll be using */
43 static struct quiver *q;
45 /* Width of drawing area in pixels */
46 static int da_pix_width;
48 /* Height of drawing area in pixels */
49 static int da_pix_height;
51 /* The background color */
52 static SDL_Color color_bg = { .r = 0xe2, .g = 0xe2, .b = 0xe2, .a = 0xff };
54 /* The normal vertex color */
55 static SDL_Color color_v = { .r = 0xe2, .g = 0x82, .b = 0x82, .a = 0xff };
57 /* The normal vertex color */
58 static SDL_Color color_outline = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x80 };
60 /* The font color */
61 static SDL_Color color_font = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x80 };
63 /* The normal edge color */
64 static SDL_Color color_e_normal = { .r = 0x12, .g = 0x12, .b = 0x12, .a =
65 0xff };
67 /* The half edge color */
68 static SDL_Color color_e_half = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x60 };
70 /* The abnormal edge color */
71 static SDL_Color color_e_abnormal = { .r = 0xd0, .g = 0x12, .b = 0x12, .a =
72 0xf0 };
74 /* The font for node names, instructions, etc */
75 static TTF_Font *normal_font;
77 /* The font for big messages */
78 static TTF_Font *big_font;
80 /* The base font size */
81 static uint_fast8_t base_font_size = 12;
83 /* The textures containing the names of all vertices - indexed just like q */
84 static SDL_Texture **vertex_names;
86 /* The number of elements of vertex_names */
87 static size_t vertex_names_len;
89 /* Drawing offset x */
90 static int offset_x;
92 /* Drawing offset x */
93 static int offset_y;
95 /* How wide (in pixels) each node should be */
96 static int node_radius = 15;
98 /* How wide (in pixels) the outline should be */
99 static int outline_width = 2;
101 /* How wide (in pixels) the arrowheads should be */
102 static int arrow_length = 7;
104 /* How narrow the arrowheads should be */
105 static double arrow_angle = 6.5 * M_PI / 8.0;
107 /* If we're interacting with a vertex, which one */
108 /* static size_t selected_vertex = (size_t) -1; */
109 /* If we're interacting with an edge, the i */
110 /* static size_t selected_edge_i */
111 /* If we're interacting with an edge, the j */
112 /* static size_t selected_edge_j */
113 /* x-coordinate of last mouse position */
114 static int last_mouse_x = -1;
116 /* y-coordinate of last mouse position */
117 static int last_mouse_y = -1;
119 /* sine tables */
120 static double precalc_sins[TRIG_PRECALC_NUM];
122 /* cosine tables */
123 static double precalc_coss[TRIG_PRECALC_NUM];
125 /* Precalculate sines and cosines */
126 static void precalc_trig(void)
128 size_t j = 0;
129 double theta;
131 precalc_coss[0] = 0.0;
132 precalc_sins[0] = 1.0;
134 for (j = 1; j < TRIG_PRECALC_NUM - 1; ++j) {
135 theta = (M_PI * j) / (2 * (TRIG_PRECALC_NUM - 1));
136 precalc_sins[j] = sin(theta);
137 precalc_coss[j] = cos(theta);
140 precalc_coss[TRIG_PRECALC_NUM - 1] = 0.0;
141 precalc_sins[TRIG_PRECALC_NUM - 1] = 1.0;
144 /* GCD */
145 static int gcd(uint_fast8_t x, uint_fast8_t y)
147 uint_fast8_t r = 0;
149 if (!x &&
150 !y) {
151 return 1;
154 while (y) {
155 r = x % y;
156 x = y;
157 y = r;
160 return x;
163 /* Load fonts */
164 static int load_fonts(void)
166 int ret = 0;
168 if (normal_font) {
169 TTF_CloseFont(normal_font);
172 if (big_font) {
173 TTF_CloseFont(big_font);
176 if (!(normal_font = TTF_OpenFont(FONT_PATH, base_font_size))) {
177 ret = ENOMEDIUM;
178 fprintf(stderr, L("TTF_OpenFont(): %s\n"), TTF_GetError());
179 goto done;
182 if (!(big_font = TTF_OpenFont(FONT_PATH, base_font_size + 5))) {
183 ret = ENOMEDIUM;
184 fprintf(stderr, L("TTF_OpenFont(): %s\n"), TTF_GetError());
185 goto done;
188 done:
190 if (ret) {
191 if (normal_font) {
192 TTF_CloseFont(normal_font);
195 if (big_font) {
196 TTF_CloseFont(big_font);
199 normal_font = 0;
200 big_font = 0;
203 return ret;
206 /* Render vertex names as textures */
207 static int render_vertex_names(void)
209 size_t j;
210 int sv_err = 0;
211 SDL_Surface *surf = 0;
213 if (vertex_names) {
214 for (j = 0; j < vertex_names_len; ++j) {
215 SDL_DestroyTexture(vertex_names[j]);
216 vertex_names[j] = 0;
220 if (!(vertex_names = realloc(vertex_names, q->v_num *
221 sizeof(*vertex_names)))) {
222 sv_err = errno;
223 perror(L("realloc()"));
224 vertex_names_len = 0;
226 return sv_err;
229 vertex_names_len = q->v_num;
231 for (j = 0; j < vertex_names_len; ++j) {
232 vertex_names[j] = 0;
235 for (j = 0; j < vertex_names_len; ++j) {
236 if (!(surf = TTF_RenderUTF8_Shaded(normal_font, q->v[j].name,
237 color_font, color_v))) {
238 fprintf(stderr,
239 "\n\nWas trying to render \u300c%s\u300d\n",
240 q->v[j].name);
241 fprintf(stderr, L("TTF_RenderUTF8_Shaded(): %s\n"),
242 TTF_GetError());
244 return ENOMEDIUM;
247 if (!(vertex_names[j] = SDL_CreateTextureFromSurface(
248 sdl_renderer, surf))) {
249 fprintf(stderr, L(
250 "SDL_CreateTextureFromSurface(): %s\n"),
251 SDL_GetError());
253 return ENOMEDIUM;
256 SDL_FreeSurface(surf);
257 surf = 0;
260 return 0;
263 /* Convert `internal coordinates' to pixel coordinates */
264 static int internal_to_pixel_xy(int in_x, int in_y, int *out_x, int *out_y)
266 *out_x = in_x + offset_x;
267 *out_y = in_y + offset_y;
269 return 0;
272 /* Convert pixel coordinates to `internal coordinates' */
275 static int pixel_to_internal_xy(int in_x, int in_y, float *out_x, float *out_y)
277 * out_x = (in_x - 0.5f - offset_x) / ic_to_pix;
278 * out_y = (in_y - 0.5f - offset_y) / ic_to_pix;
280 return 0;
284 /* Get information about the window */
285 static void react_to_window_resized(void)
287 int old_pix_width = da_pix_width;
288 int old_pix_height = da_pix_height;
290 SDL_GetWindowSize(sdl_win, &da_pix_width, &da_pix_height);
292 if (old_pix_width == da_pix_width &&
293 old_pix_height == da_pix_height) {
294 return;
297 offset_x += (da_pix_width - old_pix_width) / 2;
298 offset_y += (da_pix_height - old_pix_height) / 2;
301 /* Pop from queue */
302 static void eq_pop(struct ui_event *out)
304 if (eq_head == eq_tail) {
305 *out = (struct ui_event) { 0 };
307 return;
310 memcpy(out, eq_buf + eq_head, sizeof *out);
311 eq_buf[eq_head] = (struct ui_event) { 0 };
312 eq_head = (eq_head + 1) % eq_len;
315 /* Push into queue */
316 static int eq_push(struct ui_event *in)
318 void *newmem;
319 int sv_err;
321 if (((eq_tail + 1) % eq_len) == eq_head) {
322 if ((eq_len * sizeof *in) >= ((size_t) -1) / 2) {
323 fprintf(stderr, L(
324 "eq_push: Impossibly large buffer\n"));
326 return ENOMEM;
329 if (!(newmem = realloc(eq_buf, (eq_len * 2) *
330 sizeof *eq_buf))) {
331 sv_err = errno;
332 perror(L("realloc"));
334 return sv_err;
337 eq_buf = (struct ui_event *) newmem;
338 eq_len *= 2;
341 memcpy(eq_buf + eq_tail, in, sizeof *in);
342 eq_tail = (eq_tail + 1) % eq_len;
344 return 0;
347 /* Initialize SDL */
348 int ui_init(struct quiver *i_q)
350 int ret;
352 q = i_q;
354 if (SDL_Init(SDL_INIT_VIDEO) < 0) {
355 fprintf(stderr, L("SDL_Init(): %s\n"), SDL_GetError());
357 return ENOMEDIUM;
360 sdl_win = SDL_CreateWindow("Cluster Algebra Visualizer",
361 SDL_WINDOWPOS_UNDEFINED,
362 SDL_WINDOWPOS_UNDEFINED, 1000, 1000,
363 SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
365 if (!sdl_win) {
366 fprintf(stderr, L("SDL_CreateWindow(): %s\n"), SDL_GetError());
368 return ENOMEDIUM;
371 sdl_renderer = SDL_CreateRenderer(sdl_win, -1,
372 SDL_RENDERER_ACCELERATED);
374 if (!sdl_renderer) {
375 fprintf(stderr, L("SDL_CreateRenderer(): %s\n"),
376 SDL_GetError());
378 return ENOMEDIUM;
381 if (TTF_Init() < 0) {
382 fprintf(stderr, L("TTF_Init(): %s\n"), TTF_GetError());
384 return ENOMEDIUM;
387 if ((ret = load_fonts())) {
388 goto done;
391 if ((ret = render_vertex_names())) {
392 goto done;
395 if ((ret = SDL_SetRenderDrawColor(sdl_renderer, color_bg.r, color_bg.g,
396 color_bg.b, color_bg.a))) {
397 fprintf(stderr, L("SDL_SetRenderDrawColor(): %s\n"),
398 SDL_GetError());
399 goto done;
402 if ((ret = SDL_RenderClear(sdl_renderer))) {
403 fprintf(stderr, L("SDL_RenderClear(): %s\n"), SDL_GetError());
404 goto done;
407 SDL_RenderPresent(sdl_renderer);
408 react_to_window_resized();
410 /* Set up queue for returning data */
411 if (!(eq_buf = calloc(2, sizeof *eq_buf))) {
412 ret = errno;
413 perror(L("malloc"));
414 goto done;
417 eq_len = 2;
418 eq_head = 0;
419 eq_tail = 0;
421 /* Sines and cosines for drawing circles */
422 precalc_trig();
423 done:
425 return ret;
428 /* Deal with the fact that the quiver was changed */
429 int ui_respond_quiver_change(void)
431 return render_vertex_names();
434 /* Tear down SDL */
435 int ui_teardown(void)
437 size_t j = 0;
439 if (vertex_names) {
440 for (j = 0; j < vertex_names_len; ++j) {
441 SDL_DestroyTexture(vertex_names[j]);
442 vertex_names[j] = 0;
446 if (sdl_win) {
447 SDL_DestroyWindow(sdl_win);
450 SDL_Quit();
452 return 0;
455 /* Record that a frame has been started */
456 int ui_start_frame(void)
458 int ret = 0;
459 struct vertex *v;
460 struct rational *eij;
461 struct rational *eji;
462 int cx = 0;
463 int cy = 0;
464 int cx2 = 0;
465 int cy2 = 0;
466 double theta = 0;
467 int d = 0;
468 size_t j = 0;
469 size_t i = 0;
470 int rho = 0;
471 SDL_Rect r = { 0 };
472 Uint32 dummy_format;
473 int dummy_access;
474 int tex_w;
475 int tex_h;
477 frame_start_ticks = SDL_GetTicks();
479 /* Draw the damn thing */
480 SDL_SetRenderDrawColor(sdl_renderer, color_bg.r, color_bg.g, color_bg.b,
481 color_bg.a);
482 SDL_RenderClear(sdl_renderer);
484 for (j = 0; j < q->v_num; ++j) {
485 for (i = 0; i < j; ++i) {
486 /* First, determine if we're looking at a half-edge or a full-edge */
487 eij = &(q->e[i * q->v_len + j]);
488 eji = &(q->e[j * q->v_len + i]);
490 if (!eij->p &&
491 !eji->p) {
492 continue;
495 d = gcd(q->v[i].fatness, q->v[j].fatness);
497 /* This is the (eij)/dj = -(eji)/di condition */
498 if (eij->p * q->v[i].fatness * eji->q != -eji->p *
499 q->v[j].fatness * eij->q) {
500 ret = SDL_SetRenderDrawColor(sdl_renderer,
501 color_e_abnormal.r,
502 color_e_abnormal.g,
503 color_e_abnormal.b,
504 color_e_abnormal.a);
505 } else if (abs(eij->p) * d == q->v[j].fatness *
506 eij->q) {
507 ret = SDL_SetRenderDrawColor(sdl_renderer,
508 color_e_normal.r,
509 color_e_normal.g,
510 color_e_normal.b,
511 color_e_normal.a);
512 } else if (2 * abs(eij->p) * d == q->v[j].fatness *
513 eij->q) {
514 ret = SDL_SetRenderDrawColor(sdl_renderer,
515 color_e_half.r,
516 color_e_half.g,
517 color_e_half.b,
518 color_e_half.a);
519 } else {
520 ret = SDL_SetRenderDrawColor(sdl_renderer,
521 color_e_abnormal.r,
522 color_e_abnormal.g,
523 color_e_abnormal.b,
524 color_e_abnormal.a);
527 if (ret) {
528 fprintf(stderr, L(
529 "SDL_SetRenderDrawColor(): %s\n"),
530 SDL_GetError());
531 goto done;
534 if ((ret = internal_to_pixel_xy(q->v[i].x, q->v[i].y,
535 &cx, &cy))) {
536 goto done;
539 if ((ret = internal_to_pixel_xy(q->v[j].x, q->v[j].y,
540 &cx2, &cy2))) {
541 goto done;
544 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx2,
545 cy2))) {
546 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
547 SDL_GetError());
548 goto done;
551 if (cx == cx2) {
552 theta = (cy2 > cy) ? M_PI / 2.0 : -M_PI / 2.0;
553 } else {
554 theta = atan2f(cy2 - cy, cx2 - cx);
557 if ((eij->p < 0)) {
558 theta += M_PI;
561 cx = (cx + cx2) / 2;
562 cy = (cy + cy2) / 2;
563 cx2 = cx + arrow_length * cos(theta + arrow_angle);
564 cy2 = cy + arrow_length * sin(theta + arrow_angle);
566 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx2,
567 cy2))) {
568 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
569 SDL_GetError());
570 goto done;
573 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx +
574 arrow_length * cos(theta -
575 arrow_angle),
576 cy +
577 arrow_length * sin(theta -
578 arrow_angle))))
580 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
581 SDL_GetError());
582 goto done;
587 for (j = 0; j < q->v_num; ++j) {
588 v = &(q->v[j]);
590 if ((ret = internal_to_pixel_xy(v->x, v->y, &cx, &cy))) {
591 goto done;
594 /* Central square */
595 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_NONE);
596 SDL_SetRenderDrawColor(sdl_renderer, color_v.r, color_v.g,
597 color_v.b, color_v.a);
598 rho = node_radius * v->fatness;
599 r.x = cx - rho;
600 r.y = cy - rho;
601 r.w = 2 * rho;
602 r.h = 2 * rho;
603 SDL_RenderFillRect(sdl_renderer, &r);
605 /* Outline */
606 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
607 SDL_SetRenderDrawColor(sdl_renderer, color_outline.r,
608 color_outline.g, color_outline.b,
609 color_outline.a);
610 r.x = cx - rho;
611 r.y = cy - rho;
612 r.w = 2 * rho - outline_width;
613 r.h = outline_width;
614 SDL_RenderFillRect(sdl_renderer, &r);
615 r.x = cx + rho - outline_width;
616 r.y = cy - rho;
617 r.w = outline_width;
618 r.h = 2 * rho - outline_width;
619 SDL_RenderFillRect(sdl_renderer, &r);
620 r.x = cx - rho + outline_width;
621 r.y = cy + rho - outline_width;
622 r.w = 2 * rho - outline_width;
623 r.h = outline_width;
624 SDL_RenderFillRect(sdl_renderer, &r);
625 r.x = cx - rho;
626 r.y = cy - rho + outline_width;
627 r.w = outline_width;
628 r.h = 2 * rho - outline_width;
629 SDL_RenderFillRect(sdl_renderer, &r);
631 /* Text */
632 if (j >= vertex_names_len) {
633 fprintf(stderr, L(
634 "render_vertex_names() was not called, somehow\n"));
635 ret = EINVAL;
636 goto done;
639 if (SDL_QueryTexture(vertex_names[j], &dummy_format,
640 &dummy_access, &tex_w, &tex_h)) {
641 fprintf(stderr, L("SDL_QueryTexture(): %s\n"),
642 SDL_GetError());
643 ret = ENOMEDIUM;
644 goto done;
647 r.x = cx - tex_w / 2;
648 r.y = cy - tex_h / 2;
649 r.w = tex_w;
650 r.h = tex_h;
651 SDL_RenderCopy(sdl_renderer, vertex_names[j], 0, &r);
654 done:
656 return ret;
659 /* Draw a frame, possibly sleeping for framelimit */
660 int ui_finish_frame(void)
662 struct ui_event ui_e = { 0 };
663 SDL_Event sdl_e = { 0 };
664 Uint32 elapsed_time = 0;
665 Uint32 now = 0;
666 int ret = 0;
668 SDL_RenderPresent(sdl_renderer);
670 /* Handle user input */
671 while (SDL_PollEvent(&sdl_e) != 0) {
672 switch (sdl_e.type) {
673 case SDL_QUIT:
674 ui_e = (struct ui_event) { .type = ET_FORCE_QUIT };
675 ret = eq_push(&ui_e);
676 break;
677 case SDL_KEYUP:
679 if (sdl_e.key.keysym.sym == SDLK_q) {
680 ui_e = (struct ui_event) { .type =
681 ET_INITIAL_QUIT };
682 ret = eq_push(&ui_e);
683 } else if (sdl_e.key.keysym.sym == SDLK_y) {
684 ui_e = (struct ui_event) { .type = ET_CONFIRM };
685 ret = eq_push(&ui_e);
686 } else if (sdl_e.key.keysym.sym == SDLK_n) {
687 ui_e = (struct ui_event) { .type = ET_DENY };
688 ret = eq_push(&ui_e);
691 break;
692 case SDL_WINDOWEVENT:
694 if (sdl_e.window.event == SDL_WINDOWEVENT_RESIZED ||
695 sdl_e.window.event == SDL_WINDOWEVENT_MAXIMIZED ||
696 sdl_e.window.event == SDL_WINDOWEVENT_RESTORED) {
697 react_to_window_resized();
698 } else if (sdl_e.window.event ==
699 SDL_WINDOWEVENT_LEAVE) {
700 /* This tells the dragging code to not respond */
701 last_mouse_x = -1;
702 last_mouse_y = -1;
705 break;
706 case SDL_MOUSEMOTION:
708 if (sdl_e.motion.state & SDL_BUTTON_LMASK) {
709 if (last_mouse_x >= 0 &&
710 last_mouse_y >= 0) {
711 offset_x += (sdl_e.motion.x -
712 last_mouse_x);
713 offset_y += (sdl_e.motion.y -
714 last_mouse_y);
717 last_mouse_x = sdl_e.motion.x;
718 last_mouse_y = sdl_e.motion.y;
721 break;
722 case SDL_MOUSEBUTTONDOWN:
723 case SDL_MOUSEBUTTONUP:
725 if (sdl_e.button.state & SDL_BUTTON_LMASK) {
726 last_mouse_x = -1;
727 last_mouse_y = -1;
730 break;
733 if (ret) {
734 goto done;
738 /* framelimit */
739 now = SDL_GetTicks();
741 if (frame_start_ticks < now) {
742 elapsed_time = now - frame_start_ticks;
744 if (elapsed_time < TICKS_PER_FRAME) {
745 SDL_Delay(TICKS_PER_FRAME - elapsed_time);
749 done:
751 return ret;
754 /* Return an event to the main loop */
755 int ui_get_event(struct ui_event *e, uint_fast8_t *more)
757 eq_pop(e);
758 *more = eq_head != eq_tail;
760 return 0;
763 /* Be told to display a confirm dialog */
764 int ui_confirm_deny(const char *s)
766 conf_deny_text = s;
768 return 0;