Reduce flicker on some SDL platforms
[clav.git] / ui-sdl.c
blobd4927e7e6a90f079a7eaa861a73ae8fe8a2c38da
1 /*
2 * Copyright (c) 2016-2019, 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 "color-selection.h"
28 #include "file-selection.h"
29 #include "macros.h"
30 #include "quiver.h"
31 #include "ui-sdl-font.h"
32 #include "ui.h"
34 #define TICKS_PER_FRAME (1000 / 60)
36 #ifndef M_PI
37 #define M_PI (3.14159265358979323846)
38 #endif
40 /* What clicking does */
41 static enum ui_action {
42 UIA_NONE = 0,
43 UIA_ASK_QUIT,
44 UIA_DEC_FATNESS,
45 UIA_DELETE,
46 UIA_ENTER_LOAD,
47 UIA_ENTER_RENAME,
48 UIA_ENTER_SAVE,
49 UIA_ENTER_COLOR,
50 UIA_INC_FATNESS,
51 UIA_CHANGE_COLOR,
52 UIA_MUTATE,
53 UIA_NEW_EDGE_1,
54 UIA_NEW_EDGE_2,
55 UIA_NEW_H_EDGE_1,
56 UIA_NEW_H_EDGE_2,
57 UIA_NEW_VERTEX,
58 UIA_RENAME,
59 UIA_LEN
60 } ui_action;
62 /* Whether to cycle the state indefinitely (add many vertices, &c) */
63 static uint_fast8_t ui_action_loop;
65 /* The window we'll be using */
66 static SDL_Window *sdl_win;
68 /* The renderer we'll be using */
69 static SDL_Renderer *sdl_renderer;
71 /* How to limit the framerate */
72 static Uint32 frame_start_ticks;
74 /* Whether the scene needs to be drawn again */
75 static uint_fast8_t redraw;
77 /* Whether a vertex was just dragged (necessitating info redraw) */
78 static uint_fast8_t dragged_selected_vertex;
80 /* The informative texture */
81 static SDL_Texture *selected_info;
83 /* Buffer for event queue */
84 static struct ui_event *eq_buf;
86 /* Current max length of event queue */
87 static size_t eq_len;
89 /* Current head of queue */
90 static size_t eq_head;
92 /* Current tail of queue */
93 static size_t eq_tail;
95 /* The quiver we'll be using */
96 static struct quiver *q;
98 /* Width of drawing area in pixels */
99 static int da_pix_width;
101 /* Height of drawing area in pixels */
102 static int da_pix_height;
104 /* The background color */
105 static SDL_Color color_bg = { .r = 0xe2, .g = 0xe2, .b = 0xe2, .a = 0xff };
107 /* The normal vertex color */
108 static SDL_Color color_v = { .r = 0x82, .g = 0x82, .b = 0xb2, .a = 0xff };
110 /* The vertex color for preview vertices */
111 static SDL_Color color_v_preview = { .r = 0x82, .g = 0x82, .b = 0xb2, .a =
112 0x40 };
114 /* The normal vertex outline color */
115 static SDL_Color color_outline = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x80 };
117 /* The vertex outline color for preview vertices */
118 static SDL_Color color_outline_preview = { .r = 0x12, .g = 0x12, .b = 0x12, .a =
119 0x20 };
121 /* The selected vertex outline color */
122 static SDL_Color color_outline_sel = { .r = 0x42, .g = 0x42, .b = 0xe2, .a =
123 0x80 };
125 /* The font color */
126 static SDL_Color color_font = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x80 };
128 /* The normal edge color */
129 static SDL_Color color_e_normal = { .r = 0x12, .g = 0x12, .b = 0x12, .a =
130 0xff };
132 /* The half edge color */
133 static SDL_Color color_e_half = { .r = 0x12, .g = 0x12, .b = 0x12, .a = 0x60 };
135 /* The abnormal edge color */
136 static SDL_Color color_e_abnormal = { .r = 0xd0, .g = 0x12, .b = 0x12, .a =
137 0xf0 };
139 /* The selected edge color */
140 static SDL_Color color_e_sel = { .r = 0xb2, .g = 0xb2, .b = 0xe2, .a = 0x40 };
142 /* The font for node names, instructions, etc */
143 static TTF_Font *normal_font;
145 /* SDL wants a SDL_RWops pointer to deal with in-memory font */
146 static SDL_RWops *normal_font_rw;
148 /* The base font size */
149 static uint_fast8_t base_font_size = 12;
151 /* How much space (pixels) between lower left of screen and bottom text */
152 static unsigned int text_border_padding = 24;
154 /* Strings to display at bottom of screen */
155 static const char *bottom_string[] = {
156 /* */
157 [UIA_NONE] = "[m] Mutate\n[v] Create new vertex\n"
158 "[d] Delete vertex/edge\n[e] Add edge\n"
159 "[h] Add half edge\n[f] Increase vertex fatness\n"
160 "[g] Decrease vertex fatness\n[r] Rename vertex\n"
161 "[c] Change vertex color\n[Shift+key] as [key] "
162 "above, but loop\n[s] Save quiver\n[l] "
163 "Load quiver\n[q] Quit", /* */
164 [UIA_ASK_QUIT] = "Quit?\n\n[y] Confirm\n[n] Cancel", /* */
165 [UIA_DEC_FATNESS] = "[Mouse1] Decrease fatness\n[ESC] Cancel", /* */
166 [UIA_DELETE] = "[Mouse1] Delete vertex/edge\n[ESC] Cancel", /* */
167 [UIA_ENTER_LOAD] = "[Enter] Load from\n[ESC] Cancel", /* */
168 [UIA_ENTER_RENAME] = "[Enter] Rename\n[ESC] Cancel", /* */
169 [UIA_ENTER_SAVE] = "[Enter] Save to\n[ESC] Cancel", /* */
170 [UIA_ENTER_COLOR] = "[Enter] Use color (rrggbb)\n[ESC] Cancel", /* */
171 [UIA_INC_FATNESS] = "[Mouse1] Increase fatness\n[ESC] Cancel", /* */
172 [UIA_CHANGE_COLOR] = "[Mouse1] Change color\n[ESC] Cancel", /* */
173 [UIA_MUTATE] = "[Mouse1] Mutate at vertex\n[ESC] Cancel", /* */
174 [UIA_NEW_EDGE_1] = "[Mouse1] Select start\n[ESC] Cancel", /* */
175 [UIA_NEW_EDGE_2] = "[Mouse1] Select end\n[ESC] Cancel", /* */
176 [UIA_NEW_H_EDGE_1] = "[Mouse1] Select start\n[ESC] Cancel", /* */
177 [UIA_NEW_H_EDGE_2] = "[Mouse1] Select end\n[ESC] Cancel", /* */
178 [UIA_NEW_VERTEX] = "[Mouse1] Create new vertex\n[ESC] Cancel", /* */
179 [UIA_RENAME] = "[Mouse1] Rename vertex\n[ESC] Cancel", /* */
180 [UIA_LEN] = "" /* */
183 /* The texture containing what to show on the bottom */
184 static SDL_Texture *bottom_text[UIA_LEN];
186 /* The textures containing the names of all vertices - indexed just like q */
187 static SDL_Texture **vertex_names;
189 /* The number of elements of vertex_names */
190 static size_t vertex_names_len;
192 /* Drawing offset x */
193 static int offset_x;
195 /* Drawing offset x */
196 static int offset_y;
198 /* How wide (in pixels) a fatness 1 node should be */
199 static unsigned int base_node_radius = 8;
201 /* How much (in pixels) a node should widen for each fatness level */
202 static unsigned int node_radius_per_fatness = 7;
204 /* How wide (in pixels) the outline should be */
205 static int outline_width = 2;
207 /* How wide (in pixels) the arrowheads should be */
208 static int arrow_length = 7;
210 /* How narrow the arrowheads should be */
211 static double arrow_angle = 6.5 * M_PI / 8.0;
213 /* How close to an arrow (in pixels) for it to be selected */
214 static int edge_margin = 5;
216 /* If we're interacting with a vertex, which one */
217 static size_t selected_vertex = (size_t) -1;
219 /* If we're interacting with an edge, the i */
220 static size_t selected_edge_i = (size_t) -1;
222 /* If we're interacting with an edge, the j */
223 static size_t selected_edge_j = (size_t) -1;
225 /* If we're adding an edge, the last vertex we clicked on */
226 static size_t last_clicked_vertex = (size_t) -1;
228 /* If we're adding a new vertex, the number to use */
229 static uint16_t next_v_num = 1;
231 /* Memory for next vertex name */
232 static char next_v_name[32] = { 'v', '1', 0 };
234 /* x-coordinate of last mouse position */
235 static int last_mouse_x = -1;
237 /* y-coordinate of last mouse position */
238 static int last_mouse_y = -1;
240 /* Window title */
241 static char window_title[2048];
243 /* What name we last loaded/saved */
244 static char current_filename[2048] = { 'U', 'n', 't', 'i', 't', 'l', 'e', 'd',
245 0 };
247 /* Whether we've edited since last loading/saving */
248 static uint_fast8_t current_file_edited = 0;
250 /* Maximum length of an input string we'll accept */
251 static size_t max_input_size = 1 << 10;
253 /* Current position in input */
254 static size_t input_idx;
256 /* Current length of string held in input */
257 static size_t input_len;
259 /* If a string is being typed in, what it is (in UTF-8 as per SDL2) */
260 static char *input;
262 /* Input, with `Filename:' or `New name:' prepended (for rendering) */
263 static char *input_with_prefix;
265 /* Texture for what user is currently entering */
266 static SDL_Texture *input_texture;
268 /* Whether we need to re-render the input */
269 static uint_fast8_t rerender_input_string;
271 /* To prevent awkward repeats in SDL events, rate-limit `s' and `l' */
272 static uint_fast8_t save_load_delay;
274 /* If something needs to be freed next frame, but not before */
275 static void *free_this;
277 /* GCD */
278 static int
279 gcd(uint_fast8_t x, uint_fast8_t y)
281 uint_fast8_t r = 0;
283 if (!x &&
284 !y) {
285 return 1;
288 while (y) {
289 r = x % y;
290 x = y;
291 y = r;
294 return x;
297 /* Hex to uint */
298 static int
299 decode_hex(char *s, uint32_t *out_argb)
301 uint32_t z = 0;
303 while (*s &&
304 (*s == ' ' ||
305 *s == '#')) {
306 s++;
309 if (strlen(s) != 6) {
310 return -1;
313 while (*s) {
314 z <<= 4;
316 if ('0' <= *s &&
317 *s <= '9') {
318 z |= (*s - '0');
319 } else if ('a' <= *s &&
320 *s <= 'f') {
321 z |= (*s - 'a' + 10);
322 } else if ('A' <= *s &&
323 *s <= 'F') {
324 z |= (*s - 'A' + 10);
325 } else {
326 return -1;
329 s++;
332 *out_argb = z;
334 return 0;
337 /* Allocate and print a rational in the way a user expects */
338 static int
339 pretty_fraction(struct rational *r, char **out)
341 int ret = 0;
343 if (r->q == 1) {
344 size_t len = snprintf(0, 0, "%d", (int) r->p);
346 if (len + 1 < len) {
347 ret = errno = EOVERFLOW;
348 perror(L(""));
349 goto done;
352 if (!(*out = malloc(len + 1))) {
353 ret = errno;
354 perror(L("malloc"));
355 goto done;
358 sprintf(*out, "%d", (int) r->p);
359 goto done;
362 size_t len = snprintf(0, 0, "%d/%u", (int) r->p, (unsigned int) r->q);
364 if (len + 1 < len) {
365 ret = errno = EOVERFLOW;
366 perror(L(""));
367 goto done;
370 if (!(*out = malloc(len + 1))) {
371 ret = errno;
372 perror(L("malloc"));
373 goto done;
376 sprintf(*out, "%d/%u", (int) r->p, (unsigned int) r->q);
377 done:
379 return ret;
382 /* Render text to texture */
383 static int
384 render_text(const char *text, SDL_Texture **out)
386 if (!out) {
387 return 0;
390 if (*out) {
391 SDL_DestroyTexture(*out);
394 int ret = 0;
395 SDL_Surface *surf = 0;
397 if (!(surf = TTF_RenderUTF8_Blended_Wrapped(normal_font, text,
398 color_font, 800))) {
399 fprintf(stderr, L("TTF_RenderUTF8_Shaded(): %s\n"),
400 TTF_GetError());
401 ret = EINVAL;
402 goto done;
405 if (!(*out = SDL_CreateTextureFromSurface(sdl_renderer, surf))) {
406 fprintf(stderr, L("SDL_CreateTextureFromSurface(): %s\n"),
407 SDL_GetError());
408 ret = EINVAL;
409 goto done;
412 done:
413 SDL_FreeSurface(surf);
415 return ret;
418 /* Load fonts */
419 static int
420 load_fonts(void)
422 int ret = 0;
424 if (normal_font) {
425 TTF_CloseFont(normal_font);
426 normal_font = 0;
429 if (normal_font_rw) {
430 SDL_RWclose(normal_font_rw);
431 normal_font_rw = 0;
434 if (!(normal_font_rw = SDL_RWFromConstMem(ttf_memory,
435 sizeof_ttf_memory()))) {
436 ret = EINVAL;
437 fprintf(stderr, L("SDL_RWFromConstMem(): %s\n"),
438 SDL_GetError());
439 goto done;
442 if (!(normal_font = TTF_OpenFontIndexRW(normal_font_rw, 0,
443 base_font_size, 0))) {
444 ret = EINVAL;
445 fprintf(stderr, L("TTF_OpenFont(): %s\n"), TTF_GetError());
446 goto done;
449 for (size_t j = 0; j < UIA_LEN; ++j) {
450 if ((ret = render_text(bottom_string[j], &bottom_text[j]))) {
451 goto done;
455 done:
457 if (ret) {
458 if (normal_font) {
459 TTF_CloseFont(normal_font);
462 if (normal_font_rw) {
463 SDL_RWclose(normal_font_rw);
466 normal_font_rw = 0;
467 normal_font = 0;
470 return ret;
473 /* Insert str into input at given position */
474 static void
475 text_input(const char *str)
477 size_t l = strlen(str);
479 if (l + input_len + 1 >= max_input_size) {
480 return;
483 for (size_t k = input_len; k > input_idx; --k) {
484 input[k + l] = input[k];
487 /* Special-case for input_idx: 0 prohibits `>=' above */
488 input[input_idx + l] = input[input_idx];
490 for (size_t k = 0; k < l; ++k) {
491 input[input_idx + k] = str[k];
494 input_idx += l;
495 input_len += l;
496 rerender_input_string = 1;
499 /* Move input cursor left */
500 static void
501 text_input_left(void)
503 if (input_idx > 0) {
504 input_idx--;
507 while (input_idx > 0) {
508 unsigned char c = input[input_idx];
510 if ((c & 0xc0) == 0x80) {
511 input_idx--;
512 } else {
513 break;
518 /* Move input cursor right */
519 static void
520 text_input_right(void)
522 size_t adv = 1;
523 unsigned char c = input[input_idx];
525 if ((c & 0x80) == 0x0) {
526 adv = 1;
527 } else if ((c & 0xe0) == 0xc0) {
528 adv = 2;
529 } else if ((c & 0xf0) == 0xe0) {
530 adv = 3;
531 } else if ((c & 0xf8) == 0xf0) {
532 adv = 4;
535 if (input_idx + adv <= input_len) {
536 input_idx += adv;
540 /* Backspace */
541 static void
542 text_input_bs(void)
544 if (!input_idx) {
545 return;
548 size_t bs = 1;
550 while (input_idx > bs) {
551 unsigned char c = input[input_idx - bs];
553 if ((c & 0xc0) == 0x80) {
554 bs++;
555 } else {
556 break;
560 for (size_t k = input_idx - bs; k <= input_len - bs; k++) {
561 input[k] = input[k + bs];
564 input_len -= bs;
565 input_idx -= bs;
566 rerender_input_string = 1;
569 /* Set the title to something like `clav-sdl - foobar.txt*' */
570 static void
571 update_window_title(void)
573 size_t cutoff = sizeof (window_title) / sizeof (window_title[0]);
574 size_t r = snprintf(window_title, cutoff, "clav-sdl - %s%s",
575 current_filename, (current_file_edited ? "*" : ""));
577 if (r >= cutoff) {
578 window_title[cutoff - 1] = '\0';
581 SDL_SetWindowTitle(sdl_win, window_title);
584 /* Make sure the window title displays as edited */
585 static void
586 mark_as_edited(void)
588 if (current_file_edited) {
589 return;
592 current_file_edited = 1;
593 update_window_title();
596 /* Convert `internal coordinates' to pixel coordinates */
597 static void
598 internal_to_pixel_xy(int in_x, int in_y, int *out_x, int *out_y)
600 *out_x = in_x + offset_x;
601 *out_y = in_y + offset_y;
604 /* Convert pixel coordinates to `internal coordinates' */
605 static void
606 pixel_to_internal_xy(int in_x, int in_y, int *out_x, int *out_y)
608 *out_x = in_x - offset_x;
609 *out_y = in_y - offset_y;
612 /* Set selected_vertex and selected_edge_{i,j} */
613 static int
614 recalculate_selected_items(int mx, int my)
616 int x = 0;
617 int y = 0;
618 int ret = 0;
619 char *s = 0;
620 char *sij = 0;
621 char *sji = 0;
623 pixel_to_internal_xy(mx, my, &x, &y);
624 size_t last_vertex = selected_vertex;
625 size_t last_edge_i = selected_edge_i;
626 size_t last_edge_j = selected_edge_j;
628 selected_vertex = (size_t) -1;
629 selected_edge_i = (size_t) -1;
630 selected_edge_j = (size_t) -1;
632 if (last_vertex != (size_t) -1) {
633 struct vertex *v = &(q->v[last_vertex]);
634 int r = base_node_radius + v->fatness * node_radius_per_fatness;
636 if (x > v->x - r &&
637 x < v->x + r &&
638 y > v->y - r &&
639 y < v->y + r) {
640 selected_vertex = last_vertex;
641 goto compute_str;
645 for (size_t j = q->v_num; j > 0; --j) {
646 struct vertex *v = &(q->v[j - 1]);
647 int r = base_node_radius + v->fatness * node_radius_per_fatness;
649 if (x > v->x - r &&
650 x < v->x + r &&
651 y > v->y - r &&
652 y < v->y + r) {
653 selected_vertex = j - 1;
654 goto compute_str;
658 for (size_t j = 1; j < q->v_num; ++j) {
659 struct vertex *v1 = &(q->v[j]);
661 for (size_t i = 0; i < j; ++i) {
662 if (!q->e[i * q->v_len + j].p &&
663 !q->e[j * q->v_len + i].p) {
664 continue;
667 struct vertex *v2 = &(q->v[i]);
669 if ((x - edge_margin > v1->x &&
670 x - edge_margin > v2->x) ||
671 (x + edge_margin < v1->x &&
672 x + edge_margin < v2->x) ||
673 (y - edge_margin > v1->y &&
674 y - edge_margin > v2->y) ||
675 (y + edge_margin < v1->y &&
676 y + edge_margin < v2->y)) {
677 continue;
680 if (v1->x == v2->x) {
681 if (x + edge_margin > v1->x &&
682 x - edge_margin < v1->x) {
683 selected_edge_i = i;
684 selected_edge_j = j;
685 goto compute_str;
687 } else if (v1->y == v2->y) {
688 if (y + edge_margin > v1->y &&
689 y - edge_margin < v1->y) {
690 selected_edge_i = i;
691 selected_edge_j = j;
692 goto compute_str;
694 } else {
695 double m1 = ((double) (v2->y - v1->y)) /
696 ((double) (v2->x - v1->x));
697 double m2 = -1.0 / m1;
698 double xint = ((double) y - (double) v1->y +
699 m1 * v1->x - m2 * x) / (m1 - m2);
700 double yint = m1 * xint - m1 * v1->x +
701 (double) v1->y;
703 if ((x - xint) * (x - xint) + (y - yint) * (y -
704 yint)
705 < edge_margin * edge_margin) {
706 selected_edge_i = i;
707 selected_edge_j = j;
708 goto compute_str;
714 compute_str:
716 if (selected_vertex != (size_t) -1 &&
717 (selected_vertex != last_vertex ||
718 dragged_selected_vertex)) {
719 dragged_selected_vertex = 0;
720 struct vertex *v = &(q->v[selected_vertex]);
721 size_t len = snprintf(0, 0,
722 "Name: %s\nFatness: %d\nPosition: (%d,%d)",
723 v->name,
724 (int) v->fatness, v->x, v->y);
726 if (len + 1 < len) {
727 ret = errno = EOVERFLOW;
728 perror(L(""));
729 goto done;
732 if (!(s = malloc(len + 1))) {
733 ret = errno;
734 perror(L("malloc"));
735 goto done;
738 sprintf(s, "Name: %s\nFatness: %d\nPosition: (%d,%d)",
739 v->name, (int) v->fatness, v->x, v->y);
741 if ((ret = render_text(s, &selected_info))) {
742 goto done;
744 } else if ((selected_edge_i != last_edge_i ||
745 selected_edge_j != last_edge_j) &&
746 selected_edge_i != (size_t) -1 &&
747 selected_edge_j != (size_t) -1) {
748 struct vertex *i = &(q->v[selected_edge_i]);
749 struct vertex *j = &(q->v[selected_edge_j]);
750 struct rational *eij = &(q->e[selected_edge_i * q->v_len +
751 selected_edge_j]);
752 struct rational *eji = &(q->e[selected_edge_j * q->v_len +
753 selected_edge_i]);
755 if ((ret = pretty_fraction(eij, &sij))) {
756 goto done;
759 if ((ret = pretty_fraction(eji, &sji))) {
760 goto done;
763 size_t len = snprintf(0, 0,
764 "%s \u2192 %s: %s\n%s \u2192 %s: %s",
765 i->name, j->name, sij,
766 j->name, i->name, sji);
768 if (len + 1 < len) {
769 ret = errno = EOVERFLOW;
770 perror(L(""));
771 goto done;
774 if (!(s = malloc(len + 1))) {
775 ret = errno;
776 perror(L("malloc"));
777 goto done;
780 sprintf(s, "%s \u2192 %s: %s\n%s \u2192 %s: %s", i->name,
781 j->name, sij, j->name, i->name, sji);
783 if ((ret = render_text(s, &selected_info))) {
784 goto done;
788 done:
789 free(s);
790 free(sij);
791 free(sji);
793 return ret;
796 /* Render vertex names as textures */
797 static int
798 render_vertex_names(void)
800 if (vertex_names) {
801 for (size_t j = 0; j < vertex_names_len; ++j) {
802 SDL_DestroyTexture(vertex_names[j]);
803 vertex_names[j] = 0;
807 if (!q->v_num) {
808 return 0;
811 if ((q->v_num * sizeof *vertex_names) / q->v_num !=
812 sizeof *vertex_names) {
813 errno = EOVERFLOW;
814 perror(L(""));
815 vertex_names_len = 0;
817 return EOVERFLOW;
820 if (!(vertex_names = realloc(vertex_names, q->v_num *
821 sizeof(*vertex_names)))) {
822 int sv_err = errno;
824 perror(L("realloc"));
825 vertex_names_len = 0;
827 return sv_err;
830 vertex_names_len = q->v_num;
832 for (size_t j = 0; j < vertex_names_len; ++j) {
833 vertex_names[j] = 0;
836 for (size_t j = 0; j < vertex_names_len; ++j) {
837 int ret = 0;
839 if ((ret = render_text(q->v[j].name, &(vertex_names[j])))) {
840 return ret;
844 return 0;
847 /* Get information about the window */
848 static void
849 react_to_window_resized(void)
851 int old_pix_width = da_pix_width;
852 int old_pix_height = da_pix_height;
854 SDL_GetWindowSize(sdl_win, &da_pix_width, &da_pix_height);
856 if (old_pix_width == da_pix_width &&
857 old_pix_height == da_pix_height) {
858 return;
861 offset_x += (da_pix_width - old_pix_width) / 2;
862 offset_y += (da_pix_height - old_pix_height) / 2;
865 /* Pop from queue */
866 static void
867 eq_pop(struct ui_event *out)
869 if (eq_head == eq_tail) {
870 *out = (struct ui_event) { 0 };
872 return;
875 memcpy(out, eq_buf + eq_head, sizeof *out);
876 eq_buf[eq_head] = (struct ui_event) { 0 };
877 eq_head = (eq_head + 1) % eq_len;
880 /* Push into queue */
881 static int
882 eq_push(struct ui_event *in)
884 if (((eq_tail + 1) % eq_len) == eq_head) {
885 void *newmem;
887 if ((eq_len * sizeof *in) >= ((size_t) -1) / 2) {
888 fprintf(stderr, L(
889 "eq_push: Impossibly large buffer\n"));
891 return ENOMEM;
894 if ((eq_len * 2) / 2 != eq_len ||
895 (eq_len * 2 * sizeof *eq_buf) / (eq_len * 2) !=
896 sizeof *eq_buf) {
897 errno = EOVERFLOW;
898 perror(L(""));
900 return EOVERFLOW;
903 if (!(newmem = realloc(eq_buf, (eq_len * 2) *
904 sizeof *eq_buf))) {
905 int sv_err = errno;
907 perror(L("realloc"));
909 return sv_err;
912 eq_buf = (struct ui_event *) newmem;
913 eq_len *= 2;
916 memcpy(eq_buf + eq_tail, in, sizeof *in);
917 eq_tail = (eq_tail + 1) % eq_len;
919 return 0;
922 /* Initialize SDL */
924 ui_init(struct quiver *i_q)
926 int ret;
928 q = i_q;
929 size_t padding = strlen("Filename: ");
931 /* No need for overflow check, max_input_size is capped */
932 if (!(input_with_prefix = malloc(max_input_size + padding))) {
933 ret = errno;
934 perror(L("malloc"));
936 return ret;
939 strcpy(input_with_prefix, "Filename: ");
940 input = input_with_prefix + padding;
941 input_idx = 0;
942 input_len = 0;
944 if (SDL_Init(SDL_INIT_VIDEO) < 0) {
945 fprintf(stderr, L("SDL_Init(): %s\n"), SDL_GetError());
947 return ENXIO;
950 sdl_win = SDL_CreateWindow("clav-sdl", SDL_WINDOWPOS_UNDEFINED,
951 SDL_WINDOWPOS_UNDEFINED, 1000, 1000,
952 SDL_WINDOW_SHOWN |
953 SDL_WINDOW_RESIZABLE);
955 if (!sdl_win) {
956 fprintf(stderr, L("SDL_CreateWindow(): %s\n"), SDL_GetError());
958 return EINVAL;
961 sdl_renderer = SDL_CreateRenderer(sdl_win, -1,
962 SDL_RENDERER_ACCELERATED);
964 if (!sdl_renderer) {
965 fprintf(stderr, L("SDL_CreateRenderer(): %s\n"),
966 SDL_GetError());
968 return EINVAL;
971 if (TTF_Init() < 0) {
972 fprintf(stderr, L("TTF_Init(): %s\n"), TTF_GetError());
974 return EINVAL;
977 if ((ret = load_fonts())) {
978 goto done;
981 if ((ret = render_vertex_names())) {
982 goto done;
985 if ((ret = SDL_SetRenderDrawColor(sdl_renderer, color_bg.r, color_bg.g,
986 color_bg.b, color_bg.a))) {
987 fprintf(stderr, L("SDL_SetRenderDrawColor(): %s\n"),
988 SDL_GetError());
989 goto done;
992 if ((ret = SDL_RenderClear(sdl_renderer))) {
993 fprintf(stderr, L("SDL_RenderClear(): %s\n"), SDL_GetError());
994 goto done;
997 update_window_title();
998 SDL_RenderPresent(sdl_renderer);
999 react_to_window_resized();
1001 /* Set up queue for returning data */
1002 if (!(eq_buf = calloc(2, sizeof *eq_buf))) {
1003 ret = errno;
1004 perror(L("calloc"));
1005 goto done;
1008 eq_len = 2;
1009 eq_head = 0;
1010 eq_tail = 0;
1011 done:
1013 return ret;
1016 /* Deal with the fact that the quiver was changed */
1018 ui_respond_quiver_change(void)
1020 redraw = 4;
1021 return render_vertex_names();
1024 /* Acknowledge a successful load */
1026 ui_respond_successful_load(const char *filename)
1028 const char *tail = (const char *) strrchr(filename, '/');
1030 if (!tail) {
1031 tail = filename;
1032 } else {
1033 tail++;
1036 size_t cutoff = sizeof(current_filename) / sizeof(current_filename[0]);
1037 size_t r = snprintf(current_filename, cutoff, "%s", tail);
1039 if (r >= cutoff) {
1040 current_filename[cutoff - 1] = '\0';
1043 current_file_edited = 0;
1044 next_v_num = 1;
1045 update_window_title();
1047 return 0;
1050 /* Acknowledge a successful save */
1052 ui_respond_successful_save(const char *filename)
1054 return ui_respond_successful_load(filename);
1057 /* Tear down SDL */
1059 ui_teardown(void)
1061 if (vertex_names) {
1062 for (size_t j = 0; j < vertex_names_len; ++j) {
1063 SDL_DestroyTexture(vertex_names[j]);
1064 vertex_names[j] = 0;
1067 free(vertex_names);
1068 vertex_names = 0;
1071 for (size_t j = 0; j < UIA_LEN; ++j) {
1072 SDL_DestroyTexture(bottom_text[j]);
1073 bottom_text[j] = 0;
1076 if (selected_info) {
1077 SDL_DestroyTexture(selected_info);
1078 selected_info = 0;
1081 if (input_texture) {
1082 SDL_DestroyTexture(input_texture);
1083 input_texture = 0;
1086 if (normal_font) {
1087 TTF_CloseFont(normal_font);
1088 normal_font = 0;
1091 if (normal_font_rw) {
1092 SDL_RWclose(normal_font_rw);
1093 normal_font_rw = 0;
1096 if (sdl_win) {
1097 SDL_DestroyWindow(sdl_win);
1098 sdl_win = 0;
1101 free(eq_buf);
1102 eq_buf = 0;
1103 free(input_with_prefix);
1104 input_with_prefix = 0;
1105 input = 0;
1106 SDL_Quit();
1108 return 0;
1111 /* Record that a frame has been started */
1113 ui_start_frame(void)
1115 int ret = 0;
1116 int rho = 0;
1117 SDL_Rect r = { 0 };
1118 Uint32 dummy_format;
1119 int dummy_access;
1120 int tex_w;
1121 int tex_h;
1123 frame_start_ticks = SDL_GetTicks();
1125 if (!redraw) {
1126 goto done;
1129 redraw--;
1131 /* Draw the damn thing */
1132 SDL_SetRenderDrawColor(sdl_renderer, color_bg.r, color_bg.g, color_bg.b,
1133 color_bg.a);
1134 SDL_RenderClear(sdl_renderer);
1136 /* Special case for if text input is going on */
1137 if (rerender_input_string) {
1138 rerender_input_string = 0;
1140 if ((ret = render_text(input_with_prefix, &input_texture))) {
1141 goto done;
1145 /* Draw each edge */
1146 for (size_t j = 0; j < q->v_num; ++j) {
1147 for (size_t i = 0; i < j; ++i) {
1148 /* First, determine if we're looking at a half-edge or a full-edge */
1149 int d = gcd(q->v[i].fatness, q->v[j].fatness);
1150 struct rational *eij = &(q->e[i * q->v_len + j]);
1151 struct rational *eji = &(q->e[j * q->v_len + i]);
1152 int cx = 0;
1153 int cy = 0;
1154 int cx2 = 0;
1155 int cy2 = 0;
1156 double theta = 0.0;
1158 if (!eij->p &&
1159 !eji->p) {
1160 continue;
1163 internal_to_pixel_xy(q->v[i].x, q->v[i].y, &cx, &cy);
1164 internal_to_pixel_xy(q->v[j].x, q->v[j].y, &cx2, &cy2);
1166 if (selected_edge_i == i &&
1167 selected_edge_j == j) {
1168 if ((ret = SDL_SetRenderDrawColor(sdl_renderer,
1169 color_e_sel.r,
1170 color_e_sel.g,
1171 color_e_sel.b,
1172 color_e_sel.a)))
1174 fprintf(stderr, L(
1175 "SDL_RenderDrawColor(): %s\n"),
1176 SDL_GetError());
1177 goto done;
1180 for (int id = -edge_margin; id < edge_margin;
1181 ++id) {
1182 for (int jd = -edge_margin; jd <
1183 edge_margin; ++jd) {
1184 if ((ret = SDL_RenderDrawLine(
1185 sdl_renderer, cx +
1186 id, cy + jd,
1187 cx2 + id, cy2 +
1188 jd))) {
1189 fprintf(stderr, L(
1190 "SDL_RenderDrawLine(): %s\n"),
1191 SDL_GetError());
1192 goto done;
1198 /* This is the (eij)/dj = -(eji)/di condition */
1199 if (eij->p * q->v[i].fatness * eji->q != -eji->p *
1200 q->v[j].fatness * eij->q) {
1201 ret = SDL_SetRenderDrawColor(sdl_renderer,
1202 color_e_abnormal.r,
1203 color_e_abnormal.g,
1204 color_e_abnormal.b,
1205 color_e_abnormal.a);
1206 } else if ((int) (abs(eij->p) * d) ==
1207 (int) (q->v[j].fatness * eij->q)) {
1208 ret = SDL_SetRenderDrawColor(sdl_renderer,
1209 color_e_normal.r,
1210 color_e_normal.g,
1211 color_e_normal.b,
1212 color_e_normal.a);
1213 } else if ((int) (2 * abs(eij->p) * d) ==
1214 (int) (q->v[j].fatness * eij->q)) {
1215 ret = SDL_SetRenderDrawColor(sdl_renderer,
1216 color_e_half.r,
1217 color_e_half.g,
1218 color_e_half.b,
1219 color_e_half.a);
1220 } else {
1221 ret = SDL_SetRenderDrawColor(sdl_renderer,
1222 color_e_abnormal.r,
1223 color_e_abnormal.g,
1224 color_e_abnormal.b,
1225 color_e_abnormal.a);
1228 if (ret) {
1229 fprintf(stderr, L(
1230 "SDL_SetRenderDrawColor(): %s\n"),
1231 SDL_GetError());
1232 goto done;
1235 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx2,
1236 cy2))) {
1237 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
1238 SDL_GetError());
1239 goto done;
1242 if (cx == cx2) {
1243 theta = (cy2 > cy) ? M_PI / 2.0 : -M_PI / 2.0;
1244 } else {
1245 theta = atan2f(cy2 - cy, cx2 - cx);
1248 if ((eij->p < 0)) {
1249 theta += M_PI;
1252 cx = (cx + cx2) / 2;
1253 cy = (cy + cy2) / 2;
1254 cx2 = cx + arrow_length * cos(theta + arrow_angle);
1255 cy2 = cy + arrow_length * sin(theta + arrow_angle);
1257 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx2,
1258 cy2))) {
1259 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
1260 SDL_GetError());
1261 goto done;
1264 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy, cx +
1265 arrow_length * cos(theta -
1266 arrow_angle),
1267 cy +
1268 arrow_length * sin(theta -
1269 arrow_angle))))
1271 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
1272 SDL_GetError());
1273 goto done;
1278 /* Draw each vertex as a box */
1279 for (size_t j = 0; j < q->v_num; ++j) {
1280 struct vertex *v = &(q->v[j]);
1281 int cx = 0;
1282 int cy = 0;
1284 internal_to_pixel_xy(v->x, v->y, &cx, &cy);
1286 /* Central square */
1287 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_NONE);
1288 SDL_SetRenderDrawColor(sdl_renderer, v->r, v->g, v->b, 0xff);
1289 rho = base_node_radius + node_radius_per_fatness * v->fatness;
1290 r.x = cx - rho;
1291 r.y = cy - rho;
1292 r.w = 2 * rho;
1293 r.h = 2 * rho;
1294 SDL_RenderFillRect(sdl_renderer, &r);
1296 /* Outline */
1297 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
1299 if (j == selected_vertex ||
1300 j == last_clicked_vertex) {
1301 SDL_SetRenderDrawColor(sdl_renderer,
1302 color_outline_sel.r,
1303 color_outline_sel.g,
1304 color_outline_sel.b,
1305 color_outline_sel.a);
1306 } else {
1307 SDL_SetRenderDrawColor(sdl_renderer, color_outline.r,
1308 color_outline.g, color_outline.b,
1309 color_outline.a);
1312 r.x = cx - rho;
1313 r.y = cy - rho;
1314 r.w = 2 * rho - outline_width;
1315 r.h = outline_width;
1316 SDL_RenderFillRect(sdl_renderer, &r);
1317 r.x = cx + rho - outline_width;
1318 r.y = cy - rho;
1319 r.w = outline_width;
1320 r.h = 2 * rho - outline_width;
1321 SDL_RenderFillRect(sdl_renderer, &r);
1322 r.x = cx - rho + outline_width;
1323 r.y = cy + rho - outline_width;
1324 r.w = 2 * rho - outline_width;
1325 r.h = outline_width;
1326 SDL_RenderFillRect(sdl_renderer, &r);
1327 r.x = cx - rho;
1328 r.y = cy - rho + outline_width;
1329 r.w = outline_width;
1330 r.h = 2 * rho - outline_width;
1331 SDL_RenderFillRect(sdl_renderer, &r);
1333 /* Text */
1334 if (j >= vertex_names_len) {
1335 fprintf(stderr, L(
1336 "render_vertex_names() was not called, somehow\n"));
1337 ret = EINVAL;
1338 goto done;
1341 if (SDL_QueryTexture(vertex_names[j], &dummy_format,
1342 &dummy_access, &tex_w, &tex_h)) {
1343 fprintf(stderr, L("SDL_QueryTexture(): %s\n"),
1344 SDL_GetError());
1345 ret = EINVAL;
1346 goto done;
1349 r.x = cx - tex_w / 2;
1350 r.y = cy - tex_h / 2;
1351 r.w = tex_w;
1352 r.h = tex_h;
1353 SDL_RenderCopy(sdl_renderer, vertex_names[j], 0, &r);
1356 /* If adding a new vertex, draw preview */
1357 if (ui_action == UIA_NEW_VERTEX &&
1358 last_mouse_x != -1 &&
1359 last_mouse_y != -1) {
1360 /* Central square */
1361 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_NONE);
1362 SDL_SetRenderDrawColor(sdl_renderer, color_v_preview.r,
1363 color_v_preview.g, color_v_preview.b,
1364 color_v_preview.a);
1365 rho = base_node_radius;
1366 r.x = last_mouse_x - rho;
1367 r.y = last_mouse_y - rho;
1368 r.w = 2 * rho;
1369 r.h = 2 * rho;
1370 SDL_RenderFillRect(sdl_renderer, &r);
1372 /* Outline */
1373 SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
1374 SDL_SetRenderDrawColor(sdl_renderer, color_outline_preview.r,
1375 color_outline_preview.g,
1376 color_outline_preview.b,
1377 color_outline_preview.a);
1378 r.x = last_mouse_x - rho;
1379 r.y = last_mouse_y - rho;
1380 r.w = 2 * rho - outline_width;
1381 r.h = outline_width;
1382 SDL_RenderFillRect(sdl_renderer, &r);
1383 r.x = last_mouse_x + rho - outline_width;
1384 r.y = last_mouse_y - rho;
1385 r.w = outline_width;
1386 r.h = 2 * rho - outline_width;
1387 SDL_RenderFillRect(sdl_renderer, &r);
1388 r.x = last_mouse_x - rho + outline_width;
1389 r.y = last_mouse_y + rho - outline_width;
1390 r.w = 2 * rho - outline_width;
1391 r.h = outline_width;
1392 SDL_RenderFillRect(sdl_renderer, &r);
1393 r.x = last_mouse_x - rho;
1394 r.y = last_mouse_y - rho + outline_width;
1395 r.w = outline_width;
1396 r.h = 2 * rho - outline_width;
1397 SDL_RenderFillRect(sdl_renderer, &r);
1400 /* If adding a new edge, draw possible */
1401 if ((ui_action == UIA_NEW_EDGE_2 ||
1402 ui_action == UIA_NEW_H_EDGE_2) &&
1404 /* last_clicked_vertex != (size_t) -1 && */
1405 last_mouse_x != -1 &&
1406 last_mouse_y != -1) {
1407 int cx = 0;
1408 int cy = 0;
1410 if ((ret = SDL_SetRenderDrawColor(sdl_renderer,
1411 color_e_normal.r,
1412 color_e_normal.g,
1413 color_e_normal.b,
1414 color_e_normal.a))) {
1415 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
1416 SDL_GetError());
1417 goto done;
1420 internal_to_pixel_xy(q->v[last_clicked_vertex].x,
1421 q->v[last_clicked_vertex].y, &cx, &cy);
1423 if ((ret = SDL_RenderDrawLine(sdl_renderer, cx, cy,
1424 last_mouse_x, last_mouse_y))) {
1425 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
1426 SDL_GetError());
1427 goto done;
1431 /* Bottom text */
1432 if (SDL_QueryTexture(bottom_text[ui_action], &dummy_format,
1433 &dummy_access, &tex_w, &tex_h)) {
1434 fprintf(stderr, L("SDL_QueryTexture(): %s\n"), SDL_GetError());
1435 ret = EINVAL;
1436 goto done;
1439 r.x = text_border_padding;
1440 r.y = da_pix_height - tex_h - text_border_padding;
1441 r.w = tex_w;
1442 r.h = tex_h;
1443 SDL_RenderCopy(sdl_renderer, bottom_text[ui_action], 0, &r);
1445 /* If something is selected */
1446 if (selected_info &&
1447 (selected_vertex != (size_t) -1 ||
1448 (selected_edge_i != (size_t) -1 &&
1449 selected_edge_j != (size_t) -1))) {
1450 if (SDL_QueryTexture(selected_info, &dummy_format,
1451 &dummy_access, &tex_w, &tex_h)) {
1452 fprintf(stderr, L("SDL_QueryTexture(): %s\n"),
1453 SDL_GetError());
1454 ret = EINVAL;
1455 goto done;
1458 r.x = text_border_padding;
1459 r.y = text_border_padding;
1460 r.w = tex_w;
1461 r.h = tex_h;
1462 SDL_RenderCopy(sdl_renderer, selected_info, 0, &r);
1465 /* If user is entering text */
1466 if (ui_action == UIA_ENTER_SAVE ||
1467 ui_action == UIA_ENTER_LOAD ||
1468 ui_action == UIA_ENTER_RENAME ||
1469 ui_action == UIA_ENTER_COLOR) {
1470 if (SDL_QueryTexture(bottom_text[ui_action], &dummy_format,
1471 &dummy_access, &tex_w, &tex_h)) {
1472 fprintf(stderr, L("SDL_QueryTexture(): %s\n"),
1473 SDL_GetError());
1474 ret = EINVAL;
1475 goto done;
1478 r.x = text_border_padding;
1479 r.y = da_pix_height - tex_h - text_border_padding;
1481 if (SDL_QueryTexture(input_texture, &dummy_format,
1482 &dummy_access, &tex_w, &tex_h)) {
1483 fprintf(stderr, L("SDL_QueryTexture(): %s\n"),
1484 SDL_GetError());
1485 ret = EINVAL;
1486 goto done;
1489 r.y -= (tex_h + text_border_padding);
1490 r.w = tex_w;
1491 r.h = tex_h;
1492 SDL_RenderCopy(sdl_renderer, input_texture, 0, &r);
1494 /* Now draw a cursor */
1495 char store_idxchar = input[input_idx];
1497 input[input_idx] = '\0';
1499 if ((ret = TTF_SizeUTF8(normal_font, input_with_prefix, &tex_w,
1500 &tex_h))) {
1501 fprintf(stderr, L("TTF_SizeText(): %s\n"),
1502 TTF_GetError());
1503 goto done;
1506 input[input_idx] = store_idxchar;
1508 if ((ret = SDL_SetRenderDrawColor(sdl_renderer, color_font.r,
1509 color_font.g, color_font.b,
1510 color_font.a))) {
1511 fprintf(stderr, L("SDL_SetRenderDrawColor(): %s\n"),
1512 SDL_GetError());
1513 goto done;
1516 if ((ret = SDL_RenderDrawLine(sdl_renderer, r.x + tex_w, r.y,
1517 r.x + tex_w, r.y + tex_h))) {
1518 fprintf(stderr, L("SDL_RenderDrawLine(): %s\n"),
1519 SDL_GetError());
1520 goto done;
1524 done:
1526 return ret;
1529 /* Draw a frame, possibly sleeping for framelimit */
1531 ui_finish_frame(void)
1533 int ret = 0;
1534 struct ui_event ui_e = { 0 };
1535 SDL_Event sdl_e = { 0 };
1536 Uint32 now = 0;
1537 uint_fast8_t save_requested = 0;
1538 uint_fast8_t load_requested = 0;
1539 int color_ret = 0;
1540 uint_fast8_t color_affirmed = 0;
1542 if (free_this) {
1543 free(free_this);
1544 free_this = 0;
1547 if (save_load_delay) {
1548 save_load_delay--;
1551 SDL_RenderPresent(sdl_renderer);
1553 /* Handle user input */
1554 while (SDL_PollEvent(&sdl_e) != 0) {
1556 * Redraw should be at least 2, to deal with
1557 * double-buffering. Triple-buffering is occasionally
1558 * used, let's go with 4 to be safe.
1560 redraw = 4;
1561 SDL_Keycode k = 0;
1562 SDL_Keymod m = 0;
1564 switch (sdl_e.type) {
1565 case SDL_QUIT:
1566 ui_e = (struct ui_event) { .type = ET_QUIT };
1567 ret = eq_push(&ui_e);
1568 break;
1569 case SDL_TEXTINPUT:
1570 text_input(sdl_e.text.text);
1571 break;
1572 case SDL_KEYDOWN:
1574 if (sdl_e.key.repeat) {
1575 break;
1578 k = sdl_e.key.keysym.sym;
1579 m = sdl_e.key.keysym.mod;
1581 switch (ui_action) {
1582 case UIA_ENTER_SAVE:
1583 case UIA_ENTER_LOAD:
1584 case UIA_ENTER_RENAME:
1585 case UIA_ENTER_COLOR:
1587 if (k == SDLK_ESCAPE) {
1588 SDL_StopTextInput();
1589 ui_action = UIA_NONE;
1590 last_clicked_vertex = (size_t) -1;
1591 } else if (k == SDLK_RETURN ||
1592 k == SDLK_RETURN2) {
1593 SDL_StopTextInput();
1594 ui_e = (struct ui_event) { .str =
1595 input };
1597 if (ui_action == UIA_ENTER_SAVE) {
1598 ui_e.type = ET_SAVE;
1599 } else if (ui_action ==
1600 UIA_ENTER_LOAD) {
1601 ui_e.type = ET_LOAD;
1602 } else if (ui_action ==
1603 UIA_ENTER_RENAME) {
1604 ui_e.type = ET_RENAME;
1605 ui_e.idx_1 =
1606 last_clicked_vertex;
1607 } else if (ui_action ==
1608 UIA_ENTER_COLOR) {
1609 ui_e.type = ET_CHANGE_COLOR;
1610 ui_e.idx_1 =
1611 last_clicked_vertex;
1612 ui_e.str = 0;
1614 if (decode_hex(input,
1615 &ui_e.z)) {
1616 ui_action =
1617 ui_action_loop ?
1618 UIA_CHANGE_COLOR
1620 UIA_NONE;
1621 last_clicked_vertex =
1622 (size_t) -1;
1623 break;
1627 ret = eq_push(&ui_e);
1629 if (ui_action_loop) {
1630 if (ui_action ==
1631 UIA_ENTER_RENAME) {
1632 ui_action = UIA_RENAME;
1633 } else if (ui_action ==
1634 UIA_ENTER_COLOR) {
1635 ui_action =
1636 UIA_CHANGE_COLOR;
1638 } else {
1639 ui_action = UIA_NONE;
1642 last_clicked_vertex = (size_t) -1;
1643 } else if (k == SDLK_LEFT) {
1644 text_input_left();
1645 } else if (k == SDLK_RIGHT) {
1646 text_input_right();
1647 } else if (k == SDLK_BACKSPACE) {
1648 text_input_bs();
1651 break;
1652 case UIA_NONE:
1654 if (k == SDLK_q) {
1655 ui_action = UIA_ASK_QUIT;
1656 } else if (k == SDLK_d) {
1657 ui_action = UIA_DELETE;
1658 ui_action_loop = (m & KMOD_SHIFT);
1659 } else if (k == SDLK_e) {
1660 ui_action = UIA_NEW_EDGE_1;
1661 ui_action_loop = (m & KMOD_SHIFT);
1662 } else if (k == SDLK_f) {
1663 ui_action = UIA_INC_FATNESS;
1664 ui_action_loop = (m & KMOD_SHIFT);
1665 } else if (k == SDLK_g) {
1666 ui_action = UIA_DEC_FATNESS;
1667 ui_action_loop = (m & KMOD_SHIFT);
1668 } else if (k == SDLK_c) {
1669 ui_action = UIA_CHANGE_COLOR;
1670 ui_action_loop = (m & KMOD_SHIFT);
1671 } else if (k == SDLK_h) {
1672 ui_action = UIA_NEW_H_EDGE_1;
1673 ui_action_loop = (m & KMOD_SHIFT);
1674 } else if (k == SDLK_m) {
1675 ui_action = UIA_MUTATE;
1676 ui_action_loop = (m & KMOD_SHIFT);
1677 } else if (k == SDLK_r) {
1678 ui_action = UIA_RENAME;
1679 ui_action_loop = (m & KMOD_SHIFT);
1680 } else if (k == SDLK_v) {
1681 ui_action = UIA_NEW_VERTEX;
1682 ui_action_loop = (m & KMOD_SHIFT);
1683 } else if (k == SDLK_l &&
1684 !save_load_delay) {
1685 /* Don't load - SDL_KEYDOWN repeats */
1686 load_requested = 1;
1687 } else if (k == SDLK_s &&
1688 !save_load_delay) {
1689 save_requested = 1;
1692 break;
1693 case UIA_ASK_QUIT:
1695 if (k == SDLK_n ||
1696 k == SDLK_ESCAPE) {
1697 ui_action = UIA_NONE;
1698 } else if (k == SDLK_y) {
1699 ui_e = (struct ui_event) { .type =
1700 ET_QUIT };
1701 ret = eq_push(&ui_e);
1702 ui_action = UIA_NONE;
1705 break;
1706 default:
1708 if (k == SDLK_q ||
1709 k == SDLK_ESCAPE) {
1710 ui_action = UIA_NONE;
1713 break;
1716 break;
1717 case SDL_WINDOWEVENT:
1719 if (sdl_e.window.event == SDL_WINDOWEVENT_RESIZED ||
1720 sdl_e.window.event == SDL_WINDOWEVENT_MAXIMIZED ||
1721 sdl_e.window.event == SDL_WINDOWEVENT_RESTORED) {
1722 react_to_window_resized();
1723 } else if (sdl_e.window.event ==
1724 SDL_WINDOWEVENT_LEAVE) {
1725 /* This tells the dragging code to not respond */
1726 last_mouse_x = -1;
1727 last_mouse_y = -1;
1730 break;
1731 case SDL_MOUSEMOTION:
1733 if (sdl_e.motion.state & SDL_BUTTON_LMASK) {
1734 int x = sdl_e.motion.x;
1735 int y = sdl_e.motion.y;
1737 if (last_mouse_x >= 0 &&
1738 last_mouse_y >= 0) {
1739 if (selected_vertex != (size_t) -1) {
1740 q->v[selected_vertex].x += (x -
1741 last_mouse_x);
1742 q->v[selected_vertex].y += (y -
1743 last_mouse_y);
1744 dragged_selected_vertex = 1;
1745 recalculate_selected_items(
1746 sdl_e.motion.x,
1747 sdl_e.motion.y);
1748 mark_as_edited();
1749 } else {
1750 offset_x += (x - last_mouse_x);
1751 offset_y += (y - last_mouse_y);
1754 } else {
1755 recalculate_selected_items(sdl_e.motion.x,
1756 sdl_e.motion.y);
1759 last_mouse_x = sdl_e.motion.x;
1760 last_mouse_y = sdl_e.motion.y;
1761 break;
1762 case SDL_MOUSEBUTTONUP:
1764 if ((sdl_e.button.state & SDL_BUTTON_LMASK) &&
1765 ui_action != UIA_NEW_VERTEX) {
1766 last_mouse_x = -1;
1767 last_mouse_y = -1;
1770 recalculate_selected_items(sdl_e.button.x,
1771 sdl_e.button.y);
1772 break;
1773 case SDL_MOUSEBUTTONDOWN:
1775 if (!(sdl_e.button.state & SDL_BUTTON_LMASK)) {
1776 break;
1779 if (ui_action != UIA_NEW_VERTEX) {
1780 last_mouse_x = -1;
1781 last_mouse_y = -1;
1784 switch (ui_action) {
1785 case UIA_MUTATE:
1787 if (selected_vertex == (size_t) -1) {
1788 break;
1791 ui_e = (struct ui_event) {
1792 /* */
1793 .type = ET_MUTATE, .idx_1 =
1794 selected_vertex
1796 ret = eq_push(&ui_e);
1797 ui_action = ui_action_loop ? UIA_MUTATE :
1798 UIA_NONE;
1799 break;
1800 case UIA_CHANGE_COLOR:
1802 if (selected_vertex == (size_t) -1) {
1803 break;
1806 ui_e = (struct ui_event) {
1807 /* */
1808 .type = ET_CHANGE_COLOR, .idx_1 =
1809 selected_vertex, .z = 0
1811 color_ret = choose_color(&ui_e.z,
1812 &color_affirmed);
1814 if (!color_ret) {
1815 if (color_affirmed) {
1816 ret = eq_push(&ui_e);
1819 ui_action = ui_action_loop ?
1820 UIA_CHANGE_COLOR : UIA_NONE;
1821 } else {
1822 ui_action = UIA_ENTER_COLOR;
1823 last_clicked_vertex = selected_vertex;
1824 input_idx = 0;
1825 input_len = 0;
1826 input[0] = '\0';
1827 SDL_StartTextInput();
1829 /* The nul terminator bleeds over into input */
1830 strncpy(input_with_prefix, " rrggbb: ",
1831 11);
1832 rerender_input_string = 1;
1835 break;
1836 case UIA_NEW_VERTEX:
1838 if (selected_vertex != (size_t) -1 ||
1839 selected_edge_i != (size_t) -1 ||
1840 selected_edge_j != (size_t) -1) {
1841 break;
1844 int cx = sdl_e.button.x - offset_x;
1845 int cy = sdl_e.button.y - offset_y;
1847 sprintf(next_v_name, "v%zu",
1848 (size_t) next_v_num);
1849 next_v_num++;
1850 ui_e = (struct ui_event) {
1851 /* */
1852 .type = ET_NEW_VERTEX, .int_1 = cx,
1853 .int_2 = cy, .z = (color_v.r << 16) |
1854 (color_v.g << 8) |
1855 color_v.b, .str =
1856 next_v_name,
1858 ret = eq_push(&ui_e);
1859 ui_action = ui_action_loop ? UIA_NEW_VERTEX :
1860 UIA_NONE;
1861 break;
1862 case UIA_NEW_EDGE_1:
1863 case UIA_NEW_H_EDGE_1:
1864 case UIA_RENAME:
1866 if (selected_vertex == (size_t) -1) {
1867 if (!ui_action_loop) {
1868 ui_action = UIA_NONE;
1871 break;
1874 last_clicked_vertex = selected_vertex;
1876 if (ui_action == UIA_NEW_EDGE_1) {
1877 ui_action = UIA_NEW_EDGE_2;
1878 } else if (ui_action == UIA_NEW_H_EDGE_1) {
1879 ui_action = UIA_NEW_H_EDGE_2;
1880 } else if (ui_action == UIA_RENAME) {
1881 ui_action = UIA_ENTER_RENAME;
1882 input_len = 0;
1883 input_idx = 0;
1884 input[0] = '\0';
1885 SDL_StartTextInput();
1887 /* The nul terminator bleeds over into input */
1888 strncpy(input_with_prefix, "New name: ",
1889 11);
1890 rerender_input_string = 1;
1893 break;
1894 case UIA_NEW_EDGE_2:
1895 case UIA_NEW_H_EDGE_2:
1897 if (selected_vertex == (size_t) -1 ||
1898 selected_vertex == last_clicked_vertex) {
1899 switch (ui_action) {
1900 case UIA_NEW_EDGE_2:
1901 ui_action = UIA_NEW_EDGE_1;
1902 break;
1903 case UIA_NEW_H_EDGE_2:
1904 ui_action = UIA_NEW_H_EDGE_1;
1905 break;
1906 default:
1907 ui_action = UIA_NONE;
1910 last_clicked_vertex = (size_t) -1;
1911 break;
1914 ui_e = (struct ui_event) {
1915 /* */
1916 .type = ET_NEW_EDGE, .idx_1 =
1917 last_clicked_vertex, .idx_2 =
1918 selected_vertex, .a = 1, .b =
1919 (ui_action == UIA_NEW_EDGE_2 ?
1920 1 : 2)
1922 ret = eq_push(&ui_e);
1924 if (!ui_action_loop) {
1925 ui_action = UIA_NONE;
1926 } else if (ui_action == UIA_NEW_EDGE_2) {
1927 ui_action = UIA_NEW_EDGE_1;
1928 } else {
1929 ui_action = UIA_NEW_H_EDGE_1;
1932 last_clicked_vertex = (size_t) -1;
1933 break;
1934 case UIA_DELETE:
1936 if (selected_vertex != (size_t) -1) {
1937 ui_e = (struct ui_event) {
1938 /* */
1939 .type = ET_DELETE_VERTEX,
1940 .idx_1 = selected_vertex
1942 ret = eq_push(&ui_e);
1943 } else if (selected_edge_i != (size_t) -1 &&
1944 selected_edge_j != (size_t) -1) {
1945 ui_e = (struct ui_event) {
1946 /* */
1947 .type = ET_DELETE_EDGE, .idx_1 =
1948 selected_edge_i,
1949 .idx_2 =
1950 selected_edge_j
1952 ret = eq_push(&ui_e);
1955 ui_action = ui_action_loop ? UIA_DELETE :
1956 UIA_NONE;
1957 break;
1958 case UIA_INC_FATNESS:
1959 case UIA_DEC_FATNESS:
1961 if (selected_vertex == (size_t) -1) {
1962 break;
1965 ui_e = (struct ui_event) {
1966 /* */
1967 .type = ET_CHANGE_FATNESS, .idx_1 =
1968 selected_vertex, .int_1 =
1969 (ui_action ==
1970 UIA_INC_FATNESS
1971 ? 1 : -1)
1973 ret = eq_push(&ui_e);
1975 if (!ui_action_loop) {
1976 ui_action = UIA_NONE;
1979 break;
1980 case UIA_NONE:
1981 case UIA_ASK_QUIT:
1982 case UIA_ENTER_SAVE:
1983 case UIA_ENTER_LOAD:
1984 case UIA_ENTER_RENAME:
1985 case UIA_ENTER_COLOR:
1986 case UIA_LEN:
1987 break;
1990 break;
1993 if (ret) {
1994 goto done;
1998 if (load_requested ||
1999 save_requested) {
2000 save_load_delay = 30;
2001 char *f = 0;
2002 int r = 0;
2004 if (load_requested) {
2005 r = choose_load_file(&f);
2006 } else {
2007 r = choose_save_file(&f);
2010 if (!r) {
2011 if (f) {
2012 ui_e = (struct ui_event) { .str = f };
2013 ui_e.type = load_requested ? ET_LOAD : ET_SAVE;
2015 /* f is freed on next ui_finish_frame */
2016 free_this = f;
2017 ret = eq_push(&ui_e);
2019 } else {
2020 ui_action = load_requested ? UIA_ENTER_LOAD :
2021 UIA_ENTER_SAVE;
2022 input_idx = 0;
2023 input_len = 0;
2024 input[0] = '\0';
2025 SDL_StartTextInput();
2027 /* The nul terminator bleeds over into input */
2028 strncpy(input_with_prefix, "Filename: ", 11);
2029 rerender_input_string = 1;
2033 /* framelimit */
2034 now = SDL_GetTicks();
2036 if (frame_start_ticks <= now) {
2037 Uint32 elapsed_time = now - frame_start_ticks;
2039 if (elapsed_time < TICKS_PER_FRAME) {
2040 SDL_Delay(TICKS_PER_FRAME - elapsed_time);
2044 done:
2046 return ret;
2049 /* Return an event to the main loop */
2051 ui_get_event(struct ui_event *e, uint_fast8_t *more)
2053 eq_pop(e);
2054 *more = eq_head != eq_tail;
2056 if (e) {
2057 switch (e->type) {
2058 case ET_NONE:
2059 case ET_LOAD:
2060 case ET_SAVE:
2061 case ET_QUIT:
2062 break;
2063 case ET_CHANGE_FATNESS:
2064 case ET_CHANGE_COLOR:
2065 case ET_DELETE_EDGE:
2066 case ET_DELETE_VERTEX:
2067 case ET_MUTATE:
2068 case ET_NEW_EDGE:
2069 case ET_NEW_VERTEX:
2070 case ET_RENAME:
2071 mark_as_edited();
2072 break;
2076 return 0;