2 * Copyright (c) 2017, 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
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.
27 #include "file-selection.h"
32 #define TICKS_PER_FRAME (1000 / 60)
35 #define M_PI (3.14159265358979323846)
38 /* What clicking does */
39 static enum ui_action
{
58 /* The window we'll be using */
59 static SDL_Window
*sdl_win
;
61 /* The renderer we'll be using */
62 static SDL_Renderer
*sdl_renderer
;
64 /* How to limit the framerate */
65 static Uint32 frame_start_ticks
;
67 /* Whether the scene needs to be drawn again */
68 static uint_fast8_t redraw
;
70 /* Whether a vertex was just dragged (necessitating info redraw) */
71 static uint_fast8_t dragged_selected_vertex
;
73 /* The informative texture */
74 static SDL_Texture
*selected_info
;
76 /* Buffer for event queue */
77 static struct ui_event
*eq_buf
;
79 /* Current max length of event queue */
82 /* Current head of queue */
83 static size_t eq_head
;
85 /* Current tail of queue */
86 static size_t eq_tail
;
88 /* The quiver we'll be using */
89 static struct quiver
*q
;
91 /* Width of drawing area in pixels */
92 static int da_pix_width
;
94 /* Height of drawing area in pixels */
95 static int da_pix_height
;
97 /* The background color */
98 static SDL_Color color_bg
= { .r
= 0xe2, .g
= 0xe2, .b
= 0xe2, .a
= 0xff };
100 /* The normal vertex color */
101 static SDL_Color color_v
= { .r
= 0x82, .g
= 0x82, .b
= 0xb2, .a
= 0xff };
103 /* The vertex color for preview vertices */
104 static SDL_Color color_v_preview
= { .r
= 0x82, .g
= 0x82, .b
= 0xb2, .a
=
107 /* The normal vertex outline color */
108 static SDL_Color color_outline
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
= 0x80 };
110 /* The vertex outline color for preview vertices */
111 static SDL_Color color_outline_preview
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
=
114 /* The selected vertex outline color */
115 static SDL_Color color_outline_sel
= { .r
= 0x42, .g
= 0x42, .b
= 0xe2, .a
=
119 static SDL_Color color_font
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
= 0x80 };
121 /* The normal edge color */
122 static SDL_Color color_e_normal
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
=
125 /* The half edge color */
126 static SDL_Color color_e_half
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
= 0x60 };
128 /* The abnormal edge color */
129 static SDL_Color color_e_abnormal
= { .r
= 0xd0, .g
= 0x12, .b
= 0x12, .a
=
132 /* The selected edge color */
133 static SDL_Color color_e_sel
= { .r
= 0xb2, .g
= 0xb2, .b
= 0xe2, .a
= 0x40 };
135 /* The font for node names, instructions, etc */
136 static TTF_Font
*normal_font
;
138 /* The base font size */
139 static uint_fast8_t base_font_size
= 12;
141 /* How much space (pixels) between lower left of screen and bottom text */
142 static unsigned int text_border_padding
= 24;
144 /* Strings to display at bottom of screen */
145 static const char *bottom_string
[] = {
147 [UIA_NONE
] = "[m] Mutate\n[v] Create new vertex\n"
148 "[d] Delete vertex/edge\n[e] Add edge\n"
149 "[h] Add half edge\n[f] Increase vertex fatness\n"
150 "[g] Decrease vertex fatness\n[r] Rename vertex\n"
151 "[s] Save quiver\n[l] Load quiver\n[q] Quit", /* */
152 [UIA_ASK_QUIT
] = "Quit?\n\n[y] Confirm\n[n] Cancel", /* */
153 [UIA_DEC_FATNESS
] = "[Mouse1] Decrease fatness\n[ESC] Cancel", /* */
154 [UIA_DELETE
] = "[Mouse1] Delete vertex/edge\n[ESC] Cancel", /* */
155 [UIA_ENTER_LOAD
] = "[Enter] Load from\n[ESC] Cancel", /* */
156 [UIA_ENTER_RENAME
] = "[Enter] Rename\n[ESC] Cancel", /* */
157 [UIA_ENTER_SAVE
] = "[Enter] Save to\n[ESC] Cancel", /* */
158 [UIA_INC_FATNESS
] = "[Mouse1] Increase fatness\n[ESC] Cancel", /* */
159 [UIA_MUTATE
] = "[Mouse1] Mutate at vertex\n[ESC] Cancel", /* */
160 [UIA_NEW_EDGE_1
] = "[Mouse1] Select start\n[ESC] Cancel", /* */
161 [UIA_NEW_EDGE_2
] = "[Mouse1] Select end\n[ESC] Cancel", /* */
162 [UIA_NEW_H_EDGE_1
] = "[Mouse1] Select start\n[ESC] Cancel", /* */
163 [UIA_NEW_H_EDGE_2
] = "[Mouse1] Select end\n[ESC] Cancel", /* */
164 [UIA_NEW_VERTEX
] = "[Mouse1] Create new vertex\n[ESC] Cancel", /* */
165 [UIA_RENAME
] = "[Mouse1] Rename vertex\n[ESC] Cancel", /* */
169 /* The texture containing what to show on the bottom */
170 static SDL_Texture
*bottom_text
[UIA_LEN
];
172 /* The textures containing the names of all vertices - indexed just like q */
173 static SDL_Texture
**vertex_names
;
175 /* The number of elements of vertex_names */
176 static size_t vertex_names_len
;
178 /* Drawing offset x */
181 /* Drawing offset x */
184 /* How wide (in pixels) a fatness 1 node should be */
185 static unsigned int base_node_radius
= 8;
187 /* How much (in pixels) a node should widen for each fatness level */
188 static unsigned int node_radius_per_fatness
= 7;
190 /* How wide (in pixels) the outline should be */
191 static int outline_width
= 2;
193 /* How wide (in pixels) the arrowheads should be */
194 static int arrow_length
= 7;
196 /* How narrow the arrowheads should be */
197 static double arrow_angle
= 6.5 * M_PI
/ 8.0;
199 /* How close to an arrow (in pixels) for it to be selected */
200 static int edge_margin
= 5;
202 /* If we're interacting with a vertex, which one */
203 static size_t selected_vertex
= (size_t) -1;
205 /* If we're interacting with an edge, the i */
206 static size_t selected_edge_i
= (size_t) -1;
208 /* If we're interacting with an edge, the j */
209 static size_t selected_edge_j
= (size_t) -1;
211 /* If we're adding an edge, the last vertex we clicked on */
212 static size_t last_clicked_vertex
= (size_t) -1;
214 /* x-coordinate of last mouse position */
215 static int last_mouse_x
= -1;
217 /* y-coordinate of last mouse position */
218 static int last_mouse_y
= -1;
221 static char window_title
[2048];
223 /* What name we last loaded/saved */
224 static char current_filename
[2048] = { 'U', 'n', 't', 'i', 't', 'l', 'e', 'd',
227 /* Whether we've edited since last loading/saving */
228 static uint_fast8_t current_file_edited
= 0;
230 /* Maximum length of an input string we'll accept */
231 static size_t max_input_size
= 1 << 10;
233 /* Current position in input */
234 static size_t input_idx
;
236 /* Current length of string held in input */
237 static size_t input_len
;
239 /* If a string is being typed in, what it is (in UTF-8 as per SDL2) */
242 /* Input, with `Filename:' or `New name:' prepended (for rendering) */
243 static char *input_with_prefix
;
245 /* Texture for what user is currently entering */
246 static SDL_Texture
*input_texture
;
248 /* Whether we need to re-render the input */
249 static uint_fast8_t rerender_input_string
;
251 /* To prevent awkward repeats in SDL events, rate-limit `s' and `l' */
252 static uint_fast8_t save_load_delay
;
254 /* If something needs to be freed next frame, but not before */
255 static void *free_this
;
258 static int gcd(uint_fast8_t x
, uint_fast8_t y
)
276 /* Allocate and print a rational in the way a user expects */
277 static int pretty_fraction(struct rational
*r
, char **out
)
282 size_t len
= snprintf(0, 0, "%d", (int) r
->p
);
285 ret
= errno
= EOVERFLOW
;
290 if (!(*out
= malloc(len
+ 1))) {
296 sprintf(*out
, "%d", (int) r
->p
);
300 size_t len
= snprintf(0, 0, "%d/%u", (int) r
->p
, (unsigned int) r
->q
);
303 ret
= errno
= EOVERFLOW
;
308 if (!(*out
= malloc(len
+ 1))) {
314 sprintf(*out
, "%d/%u", (int) r
->p
, (unsigned int) r
->q
);
320 /* Render text to texture */
321 static int render_text(const char *text
, SDL_Texture
**out
)
328 SDL_DestroyTexture(*out
);
332 SDL_Surface
*surf
= 0;
334 if (!(surf
= TTF_RenderUTF8_Blended_Wrapped(normal_font
, text
,
336 fprintf(stderr
, L("TTF_RenderUTF8_Shaded(): %s\n"),
342 if (!(*out
= SDL_CreateTextureFromSurface(sdl_renderer
, surf
))) {
343 fprintf(stderr
, L("SDL_CreateTextureFromSurface(): %s\n"),
350 SDL_FreeSurface(surf
);
356 static int load_fonts(void)
361 TTF_CloseFont(normal_font
);
365 if (!(normal_font
= TTF_OpenFont(FONT_PATH
, base_font_size
))) {
367 fprintf(stderr
, L("TTF_OpenFont(): %s\n"), TTF_GetError());
371 for (size_t j
= 0; j
< UIA_LEN
; ++j
) {
372 if ((ret
= render_text(bottom_string
[j
], &bottom_text
[j
]))) {
381 TTF_CloseFont(normal_font
);
390 /* Insert str into input at given position */
391 static void text_input(const char *str
)
393 size_t l
= strlen(str
);
395 if (l
+ input_len
+ 1 >= max_input_size
) {
399 for (size_t k
= input_len
; k
> input_idx
; --k
) {
400 input
[k
+ l
] = input
[k
];
403 /* Special-case for input_idx: 0 prohibits `>=' above */
404 input
[input_idx
+ l
] = input
[input_idx
];
406 for (size_t k
= 0; k
< l
; ++k
) {
407 input
[input_idx
+ k
] = str
[k
];
412 rerender_input_string
= 1;
415 /* Move input cursor left */
416 static void text_input_left(void)
422 while (input_idx
> 0) {
423 unsigned char c
= input
[input_idx
];
425 if ((c
& 0xc0) == 0x80) {
433 /* Move input cursor right */
434 static void text_input_right(void)
437 unsigned char c
= input
[input_idx
];
439 if ((c
& 0x80) == 0x0) {
441 } else if ((c
& 0xe0) == 0xc0) {
443 } else if ((c
& 0xf0) == 0xe0) {
445 } else if ((c
& 0xf8) == 0xf0) {
449 if (input_idx
+ adv
<= input_len
) {
455 static void text_input_bs(void)
463 while (input_idx
> bs
) {
464 unsigned char c
= input
[input_idx
- bs
];
466 if ((c
& 0xc0) == 0x80) {
473 for (size_t k
= input_idx
- bs
; k
<= input_len
- bs
; k
++) {
474 input
[k
] = input
[k
+ bs
];
479 rerender_input_string
= 1;
482 /* Set the title to something like `clav-sdl - foobar.txt*' */
483 static void update_window_title(void)
485 size_t cutoff
= sizeof (window_title
) / sizeof (window_title
[0]);
486 size_t r
= snprintf(window_title
, cutoff
, "clav-sdl - %s%s",
487 current_filename
, (current_file_edited
? "*" : ""));
490 window_title
[cutoff
- 1] = '\0';
493 SDL_SetWindowTitle(sdl_win
, window_title
);
496 /* Make sure the window title displays as edited */
497 static void mark_as_edited(void)
499 if (current_file_edited
) {
503 current_file_edited
= 1;
504 update_window_title();
507 /* Convert `internal coordinates' to pixel coordinates */
508 static void internal_to_pixel_xy(int in_x
, int in_y
, int *out_x
, int *out_y
)
510 *out_x
= in_x
+ offset_x
;
511 *out_y
= in_y
+ offset_y
;
514 /* Convert pixel coordinates to `internal coordinates' */
515 static void pixel_to_internal_xy(int in_x
, int in_y
, int *out_x
, int *out_y
)
517 *out_x
= in_x
- offset_x
;
518 *out_y
= in_y
- offset_y
;
521 /* Set selected_vertex and selected_edge_{i,j} */
522 static int recalculate_selected_items(int mx
, int my
)
531 pixel_to_internal_xy(mx
, my
, &x
, &y
);
532 size_t last_vertex
= selected_vertex
;
533 size_t last_edge_i
= selected_edge_i
;
534 size_t last_edge_j
= selected_edge_j
;
536 selected_vertex
= (size_t) -1;
537 selected_edge_i
= (size_t) -1;
538 selected_edge_j
= (size_t) -1;
540 if (last_vertex
!= (size_t) -1) {
541 struct vertex
*v
= &(q
->v
[last_vertex
]);
542 int r
= base_node_radius
+ v
->fatness
* node_radius_per_fatness
;
548 selected_vertex
= last_vertex
;
553 for (size_t j
= q
->v_num
; j
> 0; --j
) {
554 struct vertex
*v
= &(q
->v
[j
- 1]);
555 int r
= base_node_radius
+ v
->fatness
* node_radius_per_fatness
;
561 selected_vertex
= j
- 1;
566 for (size_t j
= 1; j
< q
->v_num
; ++j
) {
567 struct vertex
*v1
= &(q
->v
[j
]);
569 for (size_t i
= 0; i
< j
; ++i
) {
570 if (!q
->e
[i
* q
->v_len
+ j
].p
&&
571 !q
->e
[j
* q
->v_len
+ i
].p
) {
575 struct vertex
*v2
= &(q
->v
[i
]);
577 if ((x
- edge_margin
> v1
->x
&&
578 x
- edge_margin
> v2
->x
) ||
579 (x
+ edge_margin
< v1
->x
&&
580 x
+ edge_margin
< v2
->x
) ||
581 (y
- edge_margin
> v1
->y
&&
582 y
- edge_margin
> v2
->y
) ||
583 (y
+ edge_margin
< v1
->y
&&
584 y
+ edge_margin
< v2
->y
)) {
588 if (v1
->x
== v2
->x
) {
589 if (x
+ edge_margin
> v1
->x
&&
590 x
- edge_margin
< v1
->x
) {
595 } else if (v1
->y
== v2
->y
) {
596 if (y
+ edge_margin
> v1
->y
&&
597 y
- edge_margin
< v1
->y
) {
603 double m1
= ((double) (v2
->y
- v1
->y
)) /
604 ((double) (v2
->x
- v1
->x
));
605 double m2
= -1.0 / m1
;
606 double xint
= ((double) y
- (double) v1
->y
+
607 m1
* v1
->x
- m2
* x
) / (m1
- m2
);
608 double yint
= m1
* xint
- m1
* v1
->x
+
611 if ((x
- xint
) * (x
- xint
) + (y
- yint
) * (y
-
613 < edge_margin
* edge_margin
) {
624 if (selected_vertex
!= (size_t) -1 &&
625 (selected_vertex
!= last_vertex
||
626 dragged_selected_vertex
)) {
627 dragged_selected_vertex
= 0;
628 struct vertex
*v
= &(q
->v
[selected_vertex
]);
629 size_t len
= snprintf(0, 0,
630 "Name: %s\nFatness: %d\nPosition: (%d,%d)",
632 (int) v
->fatness
, v
->x
, v
->y
);
635 ret
= errno
= EOVERFLOW
;
640 if (!(s
= malloc(len
+ 1))) {
646 sprintf(s
, "Name: %s\nFatness: %d\nPosition: (%d,%d)",
647 v
->name
, (int) v
->fatness
, v
->x
, v
->y
);
649 if ((ret
= render_text(s
, &selected_info
))) {
652 } else if ((selected_edge_i
!= last_edge_i
||
653 selected_edge_j
!= last_edge_j
) &&
654 selected_edge_i
!= (size_t) -1 &&
655 selected_edge_j
!= (size_t) -1) {
656 struct vertex
*i
= &(q
->v
[selected_edge_i
]);
657 struct vertex
*j
= &(q
->v
[selected_edge_j
]);
658 struct rational
*eij
= &(q
->e
[selected_edge_i
* q
->v_len
+
660 struct rational
*eji
= &(q
->e
[selected_edge_j
* q
->v_len
+
663 if ((ret
= pretty_fraction(eij
, &sij
))) {
667 if ((ret
= pretty_fraction(eji
, &sji
))) {
671 size_t len
= snprintf(0, 0,
672 "%s \u2192 %s: %s\n%s \u2192 %s: %s",
673 i
->name
, j
->name
, sij
,
674 j
->name
, i
->name
, sji
);
677 ret
= errno
= EOVERFLOW
;
682 if (!(s
= malloc(len
+ 1))) {
688 sprintf(s
, "%s \u2192 %s: %s\n%s \u2192 %s: %s", i
->name
,
689 j
->name
, sij
, j
->name
, i
->name
, sji
);
691 if ((ret
= render_text(s
, &selected_info
))) {
704 /* Render vertex names as textures */
705 static int render_vertex_names(void)
708 for (size_t j
= 0; j
< vertex_names_len
; ++j
) {
709 SDL_DestroyTexture(vertex_names
[j
]);
718 if ((q
->v_num
* sizeof *vertex_names
) / q
->v_num
!=
719 sizeof *vertex_names
) {
722 vertex_names_len
= 0;
727 if (!(vertex_names
= realloc(vertex_names
, q
->v_num
*
728 sizeof(*vertex_names
)))) {
731 perror(L("realloc"));
732 vertex_names_len
= 0;
737 vertex_names_len
= q
->v_num
;
739 for (size_t j
= 0; j
< vertex_names_len
; ++j
) {
743 for (size_t j
= 0; j
< vertex_names_len
; ++j
) {
746 if ((ret
= render_text(q
->v
[j
].name
, &(vertex_names
[j
])))) {
754 /* Get information about the window */
755 static void react_to_window_resized(void)
757 int old_pix_width
= da_pix_width
;
758 int old_pix_height
= da_pix_height
;
760 SDL_GetWindowSize(sdl_win
, &da_pix_width
, &da_pix_height
);
762 if (old_pix_width
== da_pix_width
&&
763 old_pix_height
== da_pix_height
) {
767 offset_x
+= (da_pix_width
- old_pix_width
) / 2;
768 offset_y
+= (da_pix_height
- old_pix_height
) / 2;
772 static void eq_pop(struct ui_event
*out
)
774 if (eq_head
== eq_tail
) {
775 *out
= (struct ui_event
) { 0 };
780 memcpy(out
, eq_buf
+ eq_head
, sizeof *out
);
781 eq_buf
[eq_head
] = (struct ui_event
) { 0 };
782 eq_head
= (eq_head
+ 1) % eq_len
;
785 /* Push into queue */
786 static int eq_push(struct ui_event
*in
)
788 if (((eq_tail
+ 1) % eq_len
) == eq_head
) {
791 if ((eq_len
* sizeof *in
) >= ((size_t) -1) / 2) {
793 "eq_push: Impossibly large buffer\n"));
798 if ((eq_len
* 2) / 2 != eq_len
||
799 (eq_len
* 2 * sizeof *eq_buf
) / (eq_len
* 2) !=
807 if (!(newmem
= realloc(eq_buf
, (eq_len
* 2) *
811 perror(L("realloc"));
816 eq_buf
= (struct ui_event
*) newmem
;
820 memcpy(eq_buf
+ eq_tail
, in
, sizeof *in
);
821 eq_tail
= (eq_tail
+ 1) % eq_len
;
827 int ui_init(struct quiver
*i_q
)
832 size_t padding
= strlen("Filename: ");
834 /* No need for overflow check, max_input_size is capped */
835 if (!(input_with_prefix
= malloc(max_input_size
+ padding
))) {
842 strcpy(input_with_prefix
, "Filename: ");
843 input
= input_with_prefix
+ padding
;
847 if (SDL_Init(SDL_INIT_VIDEO
) < 0) {
848 fprintf(stderr
, L("SDL_Init(): %s\n"), SDL_GetError());
853 sdl_win
= SDL_CreateWindow("clav-sdl", SDL_WINDOWPOS_UNDEFINED
,
854 SDL_WINDOWPOS_UNDEFINED
, 1000, 1000,
856 SDL_WINDOW_RESIZABLE
);
859 fprintf(stderr
, L("SDL_CreateWindow(): %s\n"), SDL_GetError());
864 sdl_renderer
= SDL_CreateRenderer(sdl_win
, -1,
865 SDL_RENDERER_ACCELERATED
);
868 fprintf(stderr
, L("SDL_CreateRenderer(): %s\n"),
874 if (TTF_Init() < 0) {
875 fprintf(stderr
, L("TTF_Init(): %s\n"), TTF_GetError());
880 if ((ret
= load_fonts())) {
884 if ((ret
= render_vertex_names())) {
888 if ((ret
= SDL_SetRenderDrawColor(sdl_renderer
, color_bg
.r
, color_bg
.g
,
889 color_bg
.b
, color_bg
.a
))) {
890 fprintf(stderr
, L("SDL_SetRenderDrawColor(): %s\n"),
895 if ((ret
= SDL_RenderClear(sdl_renderer
))) {
896 fprintf(stderr
, L("SDL_RenderClear(): %s\n"), SDL_GetError());
900 update_window_title();
901 SDL_RenderPresent(sdl_renderer
);
902 react_to_window_resized();
904 /* Set up queue for returning data */
905 if (!(eq_buf
= calloc(2, sizeof *eq_buf
))) {
919 /* Deal with the fact that the quiver was changed */
920 int ui_respond_quiver_change(void)
922 return render_vertex_names();
925 /* Acknowledge a successful load */
926 int ui_respond_successful_load(const char *filename
)
928 const char *tail
= (const char *) strrchr(filename
, '/');
936 size_t cutoff
= sizeof(current_filename
) / sizeof(current_filename
[0]);
937 size_t r
= snprintf(current_filename
, cutoff
, "%s", tail
);
940 current_filename
[cutoff
- 1] = '\0';
943 current_file_edited
= 0;
944 update_window_title();
949 /* Acknowledge a successful save */
950 int ui_respond_successful_save(const char *filename
)
952 return ui_respond_successful_load(filename
);
956 int ui_teardown(void)
959 for (size_t j
= 0; j
< vertex_names_len
; ++j
) {
960 SDL_DestroyTexture(vertex_names
[j
]);
968 for (size_t j
= 0; j
< UIA_LEN
; ++j
) {
969 SDL_DestroyTexture(bottom_text
[j
]);
974 SDL_DestroyTexture(selected_info
);
979 SDL_DestroyTexture(input_texture
);
984 TTF_CloseFont(normal_font
);
989 SDL_DestroyWindow(sdl_win
);
995 free(input_with_prefix
);
996 input_with_prefix
= 0;
1003 /* Record that a frame has been started */
1004 int ui_start_frame(void)
1009 Uint32 dummy_format
;
1014 frame_start_ticks
= SDL_GetTicks();
1022 /* Draw the damn thing */
1023 SDL_SetRenderDrawColor(sdl_renderer
, color_bg
.r
, color_bg
.g
, color_bg
.b
,
1025 SDL_RenderClear(sdl_renderer
);
1027 /* Special case for if text input is going on */
1028 if (rerender_input_string
) {
1029 rerender_input_string
= 0;
1031 if ((ret
= render_text(input_with_prefix
, &input_texture
))) {
1036 /* Draw each edge */
1037 for (size_t j
= 0; j
< q
->v_num
; ++j
) {
1038 for (size_t i
= 0; i
< j
; ++i
) {
1039 /* First, determine if we're looking at a half-edge or a full-edge */
1040 int d
= gcd(q
->v
[i
].fatness
, q
->v
[j
].fatness
);
1041 struct rational
*eij
= &(q
->e
[i
* q
->v_len
+ j
]);
1042 struct rational
*eji
= &(q
->e
[j
* q
->v_len
+ i
]);
1054 internal_to_pixel_xy(q
->v
[i
].x
, q
->v
[i
].y
, &cx
, &cy
);
1055 internal_to_pixel_xy(q
->v
[j
].x
, q
->v
[j
].y
, &cx2
, &cy2
);
1057 if (selected_edge_i
== i
&&
1058 selected_edge_j
== j
) {
1059 if ((ret
= SDL_SetRenderDrawColor(sdl_renderer
,
1066 "SDL_RenderDrawColor(): %s\n"),
1071 for (int id
= -edge_margin
; id
< edge_margin
;
1073 for (int jd
= -edge_margin
; jd
<
1074 edge_margin
; ++jd
) {
1075 if ((ret
= SDL_RenderDrawLine(
1081 "SDL_RenderDrawLine(): %s\n"),
1089 /* This is the (eij)/dj = -(eji)/di condition */
1090 if (eij
->p
* q
->v
[i
].fatness
* eji
->q
!= -eji
->p
*
1091 q
->v
[j
].fatness
* eij
->q
) {
1092 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
1096 color_e_abnormal
.a
);
1097 } else if (abs(eij
->p
) * d
== q
->v
[j
].fatness
*
1099 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
1104 } else if (2 * abs(eij
->p
) * d
== q
->v
[j
].fatness
*
1106 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
1112 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
1116 color_e_abnormal
.a
);
1121 "SDL_SetRenderDrawColor(): %s\n"),
1126 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, cx
, cy
, cx2
,
1128 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1134 theta
= (cy2
> cy
) ? M_PI
/ 2.0 : -M_PI
/ 2.0;
1136 theta
= atan2f(cy2
- cy
, cx2
- cx
);
1143 cx
= (cx
+ cx2
) / 2;
1144 cy
= (cy
+ cy2
) / 2;
1145 cx2
= cx
+ arrow_length
* cos(theta
+ arrow_angle
);
1146 cy2
= cy
+ arrow_length
* sin(theta
+ arrow_angle
);
1148 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, cx
, cy
, cx2
,
1150 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1155 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, cx
, cy
, cx
+
1156 arrow_length
* cos(theta
-
1159 arrow_length
* sin(theta
-
1162 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1169 /* Draw each vertex as a box */
1170 for (size_t j
= 0; j
< q
->v_num
; ++j
) {
1171 struct vertex
*v
= &(q
->v
[j
]);
1175 internal_to_pixel_xy(v
->x
, v
->y
, &cx
, &cy
);
1177 /* Central square */
1178 SDL_SetRenderDrawBlendMode(sdl_renderer
, SDL_BLENDMODE_NONE
);
1179 SDL_SetRenderDrawColor(sdl_renderer
, color_v
.r
, color_v
.g
,
1180 color_v
.b
, color_v
.a
);
1181 rho
= base_node_radius
+ node_radius_per_fatness
* v
->fatness
;
1186 SDL_RenderFillRect(sdl_renderer
, &r
);
1189 SDL_SetRenderDrawBlendMode(sdl_renderer
, SDL_BLENDMODE_BLEND
);
1191 if (j
== selected_vertex
||
1192 j
== last_clicked_vertex
) {
1193 SDL_SetRenderDrawColor(sdl_renderer
,
1194 color_outline_sel
.r
,
1195 color_outline_sel
.g
,
1196 color_outline_sel
.b
,
1197 color_outline_sel
.a
);
1199 SDL_SetRenderDrawColor(sdl_renderer
, color_outline
.r
,
1200 color_outline
.g
, color_outline
.b
,
1206 r
.w
= 2 * rho
- outline_width
;
1207 r
.h
= outline_width
;
1208 SDL_RenderFillRect(sdl_renderer
, &r
);
1209 r
.x
= cx
+ rho
- outline_width
;
1211 r
.w
= outline_width
;
1212 r
.h
= 2 * rho
- outline_width
;
1213 SDL_RenderFillRect(sdl_renderer
, &r
);
1214 r
.x
= cx
- rho
+ outline_width
;
1215 r
.y
= cy
+ rho
- outline_width
;
1216 r
.w
= 2 * rho
- outline_width
;
1217 r
.h
= outline_width
;
1218 SDL_RenderFillRect(sdl_renderer
, &r
);
1220 r
.y
= cy
- rho
+ outline_width
;
1221 r
.w
= outline_width
;
1222 r
.h
= 2 * rho
- outline_width
;
1223 SDL_RenderFillRect(sdl_renderer
, &r
);
1226 if (j
>= vertex_names_len
) {
1228 "render_vertex_names() was not called, somehow\n"));
1233 if (SDL_QueryTexture(vertex_names
[j
], &dummy_format
,
1234 &dummy_access
, &tex_w
, &tex_h
)) {
1235 fprintf(stderr
, L("SDL_QueryTexture(): %s\n"),
1241 r
.x
= cx
- tex_w
/ 2;
1242 r
.y
= cy
- tex_h
/ 2;
1245 SDL_RenderCopy(sdl_renderer
, vertex_names
[j
], 0, &r
);
1248 /* If adding a new vertex, draw preview */
1249 if (ui_action
== UIA_NEW_VERTEX
&&
1250 last_mouse_x
!= -1 &&
1251 last_mouse_y
!= -1) {
1252 /* Central square */
1253 SDL_SetRenderDrawBlendMode(sdl_renderer
, SDL_BLENDMODE_NONE
);
1254 SDL_SetRenderDrawColor(sdl_renderer
, color_v_preview
.r
,
1255 color_v_preview
.g
, color_v_preview
.b
,
1257 rho
= base_node_radius
;
1258 r
.x
= last_mouse_x
- rho
;
1259 r
.y
= last_mouse_y
- rho
;
1262 SDL_RenderFillRect(sdl_renderer
, &r
);
1265 SDL_SetRenderDrawBlendMode(sdl_renderer
, SDL_BLENDMODE_BLEND
);
1266 SDL_SetRenderDrawColor(sdl_renderer
, color_outline_preview
.r
,
1267 color_outline_preview
.g
,
1268 color_outline_preview
.b
,
1269 color_outline_preview
.a
);
1270 r
.x
= last_mouse_x
- rho
;
1271 r
.y
= last_mouse_y
- rho
;
1272 r
.w
= 2 * rho
- outline_width
;
1273 r
.h
= outline_width
;
1274 SDL_RenderFillRect(sdl_renderer
, &r
);
1275 r
.x
= last_mouse_x
+ rho
- outline_width
;
1276 r
.y
= last_mouse_y
- rho
;
1277 r
.w
= outline_width
;
1278 r
.h
= 2 * rho
- outline_width
;
1279 SDL_RenderFillRect(sdl_renderer
, &r
);
1280 r
.x
= last_mouse_x
- rho
+ outline_width
;
1281 r
.y
= last_mouse_y
+ rho
- outline_width
;
1282 r
.w
= 2 * rho
- outline_width
;
1283 r
.h
= outline_width
;
1284 SDL_RenderFillRect(sdl_renderer
, &r
);
1285 r
.x
= last_mouse_x
- rho
;
1286 r
.y
= last_mouse_y
- rho
+ outline_width
;
1287 r
.w
= outline_width
;
1288 r
.h
= 2 * rho
- outline_width
;
1289 SDL_RenderFillRect(sdl_renderer
, &r
);
1292 /* If adding a new edge, draw possible */
1293 if ((ui_action
== UIA_NEW_EDGE_2
||
1294 ui_action
== UIA_NEW_H_EDGE_2
) &&
1296 /* last_clicked_vertex != (size_t) -1 && */
1297 last_mouse_x
!= -1 &&
1298 last_mouse_y
!= -1) {
1302 if ((ret
= SDL_SetRenderDrawColor(sdl_renderer
,
1306 color_e_normal
.a
))) {
1307 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1312 internal_to_pixel_xy(q
->v
[last_clicked_vertex
].x
,
1313 q
->v
[last_clicked_vertex
].y
, &cx
, &cy
);
1315 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, cx
, cy
,
1316 last_mouse_x
, last_mouse_y
))) {
1317 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1324 if (SDL_QueryTexture(bottom_text
[ui_action
], &dummy_format
,
1325 &dummy_access
, &tex_w
, &tex_h
)) {
1326 fprintf(stderr
, L("SDL_QueryTexture(): %s\n"), SDL_GetError());
1331 r
.x
= text_border_padding
;
1332 r
.y
= da_pix_height
- tex_h
- text_border_padding
;
1335 SDL_RenderCopy(sdl_renderer
, bottom_text
[ui_action
], 0, &r
);
1337 /* If something is selected */
1338 if (selected_info
&&
1339 (selected_vertex
!= (size_t) -1 ||
1340 (selected_edge_i
!= (size_t) -1 &&
1341 selected_edge_j
!= (size_t) -1))) {
1342 if (SDL_QueryTexture(selected_info
, &dummy_format
,
1343 &dummy_access
, &tex_w
, &tex_h
)) {
1344 fprintf(stderr
, L("SDL_QueryTexture(): %s\n"),
1350 r
.x
= text_border_padding
;
1351 r
.y
= text_border_padding
;
1354 SDL_RenderCopy(sdl_renderer
, selected_info
, 0, &r
);
1357 /* If user is entering text */
1358 if (ui_action
== UIA_ENTER_SAVE
||
1359 ui_action
== UIA_ENTER_LOAD
||
1360 ui_action
== UIA_ENTER_RENAME
) {
1361 if (SDL_QueryTexture(bottom_text
[ui_action
], &dummy_format
,
1362 &dummy_access
, &tex_w
, &tex_h
)) {
1363 fprintf(stderr
, L("SDL_QueryTexture(): %s\n"),
1369 r
.x
= text_border_padding
;
1370 r
.y
= da_pix_height
- tex_h
- text_border_padding
;
1372 if (SDL_QueryTexture(input_texture
, &dummy_format
,
1373 &dummy_access
, &tex_w
, &tex_h
)) {
1374 fprintf(stderr
, L("SDL_QueryTexture(): %s\n"),
1380 r
.y
-= (tex_h
+ text_border_padding
);
1383 SDL_RenderCopy(sdl_renderer
, input_texture
, 0, &r
);
1385 /* Now draw a cursor */
1386 char store_idxchar
= input
[input_idx
];
1388 input
[input_idx
] = '\0';
1390 if ((ret
= TTF_SizeUTF8(normal_font
, input_with_prefix
, &tex_w
,
1392 fprintf(stderr
, L("TTF_SizeText(): %s\n"),
1397 input
[input_idx
] = store_idxchar
;
1399 if ((ret
= SDL_SetRenderDrawColor(sdl_renderer
, color_font
.r
,
1400 color_font
.g
, color_font
.b
,
1402 fprintf(stderr
, L("SDL_SetRenderDrawColor(): %s\n"),
1407 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, r
.x
+ tex_w
, r
.y
,
1408 r
.x
+ tex_w
, r
.y
+ tex_h
))) {
1409 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1420 /* Draw a frame, possibly sleeping for framelimit */
1421 int ui_finish_frame(void)
1424 struct ui_event ui_e
= { 0 };
1425 SDL_Event sdl_e
= { 0 };
1427 uint_fast8_t save_requested
= 0;
1428 uint_fast8_t load_requested
= 0;
1435 if (save_load_delay
) {
1439 SDL_RenderPresent(sdl_renderer
);
1441 /* Handle user input */
1442 while (SDL_PollEvent(&sdl_e
) != 0) {
1446 switch (sdl_e
.type
) {
1448 ui_e
= (struct ui_event
) { .type
= ET_QUIT
};
1449 ret
= eq_push(&ui_e
);
1452 text_input(sdl_e
.text
.text
);
1456 if (sdl_e
.key
.repeat
) {
1460 k
= sdl_e
.key
.keysym
.sym
;
1462 switch (ui_action
) {
1463 case UIA_ENTER_SAVE
:
1464 case UIA_ENTER_LOAD
:
1465 case UIA_ENTER_RENAME
:
1467 if (k
== SDLK_ESCAPE
) {
1468 SDL_StopTextInput();
1469 ui_action
= UIA_NONE
;
1470 last_clicked_vertex
= (size_t) -1;
1471 } else if (k
== SDLK_RETURN
||
1472 k
== SDLK_RETURN2
) {
1473 SDL_StopTextInput();
1474 ui_e
= (struct ui_event
) { .str
=
1477 if (ui_action
== UIA_ENTER_SAVE
) {
1478 ui_e
.type
= ET_SAVE
;
1479 } else if (ui_action
==
1481 ui_e
.type
= ET_LOAD
;
1482 } else if (ui_action
==
1484 ui_e
.type
= ET_RENAME
;
1486 last_clicked_vertex
;
1489 ret
= eq_push(&ui_e
);
1490 ui_action
= UIA_NONE
;
1491 last_clicked_vertex
= (size_t) -1;
1492 } else if (k
== SDLK_LEFT
) {
1494 } else if (k
== SDLK_RIGHT
) {
1496 } else if (k
== SDLK_BACKSPACE
) {
1504 ui_action
= UIA_ASK_QUIT
;
1505 } else if (k
== SDLK_d
) {
1506 ui_action
= UIA_DELETE
;
1507 } else if (k
== SDLK_e
) {
1508 ui_action
= UIA_NEW_EDGE_1
;
1509 } else if (k
== SDLK_f
) {
1510 ui_action
= UIA_INC_FATNESS
;
1511 } else if (k
== SDLK_g
) {
1512 ui_action
= UIA_DEC_FATNESS
;
1513 } else if (k
== SDLK_h
) {
1514 ui_action
= UIA_NEW_H_EDGE_1
;
1515 } else if (k
== SDLK_m
) {
1516 ui_action
= UIA_MUTATE
;
1517 } else if (k
== SDLK_r
) {
1518 ui_action
= UIA_RENAME
;
1519 } else if (k
== SDLK_v
) {
1520 ui_action
= UIA_NEW_VERTEX
;
1521 } else if (k
== SDLK_l
&&
1523 /* Don't load - SDL_KEYUP repeats */
1525 } else if (k
== SDLK_s
&&
1535 ui_action
= UIA_NONE
;
1536 } else if (k
== SDLK_y
) {
1537 ui_e
= (struct ui_event
) { .type
=
1539 ret
= eq_push(&ui_e
);
1540 ui_action
= UIA_NONE
;
1548 ui_action
= UIA_NONE
;
1555 case SDL_WINDOWEVENT
:
1557 if (sdl_e
.window
.event
== SDL_WINDOWEVENT_RESIZED
||
1558 sdl_e
.window
.event
== SDL_WINDOWEVENT_MAXIMIZED
||
1559 sdl_e
.window
.event
== SDL_WINDOWEVENT_RESTORED
) {
1560 react_to_window_resized();
1561 } else if (sdl_e
.window
.event
==
1562 SDL_WINDOWEVENT_LEAVE
) {
1563 /* This tells the dragging code to not respond */
1569 case SDL_MOUSEMOTION
:
1571 if (sdl_e
.motion
.state
& SDL_BUTTON_LMASK
) {
1572 int x
= sdl_e
.motion
.x
;
1573 int y
= sdl_e
.motion
.y
;
1575 if (last_mouse_x
>= 0 &&
1576 last_mouse_y
>= 0) {
1577 if (selected_vertex
!= (size_t) -1) {
1578 q
->v
[selected_vertex
].x
+= (x
-
1580 q
->v
[selected_vertex
].y
+= (y
-
1582 dragged_selected_vertex
= 1;
1583 recalculate_selected_items(
1588 offset_x
+= (x
- last_mouse_x
);
1589 offset_y
+= (y
- last_mouse_y
);
1593 recalculate_selected_items(sdl_e
.motion
.x
,
1597 last_mouse_x
= sdl_e
.motion
.x
;
1598 last_mouse_y
= sdl_e
.motion
.y
;
1600 case SDL_MOUSEBUTTONUP
:
1602 if ((sdl_e
.button
.state
& SDL_BUTTON_LMASK
) &&
1603 ui_action
!= UIA_NEW_VERTEX
) {
1608 recalculate_selected_items(sdl_e
.button
.x
,
1611 case SDL_MOUSEBUTTONDOWN
:
1613 if (!(sdl_e
.button
.state
& SDL_BUTTON_LMASK
)) {
1617 if (ui_action
!= UIA_NEW_VERTEX
) {
1622 switch (ui_action
) {
1625 if (selected_vertex
== (size_t) -1) {
1629 ui_e
= (struct ui_event
) {
1631 .type
= ET_MUTATE
, .idx_1
=
1634 ret
= eq_push(&ui_e
);
1635 ui_action
= UIA_NONE
;
1637 case UIA_NEW_VERTEX
:
1639 if (selected_vertex
!= (size_t) -1 ||
1640 selected_edge_i
!= (size_t) -1 ||
1641 selected_edge_j
!= (size_t) -1) {
1645 int cx
= sdl_e
.button
.x
- offset_x
;
1646 int cy
= sdl_e
.button
.y
- offset_y
;
1648 ui_e
= (struct ui_event
) {
1650 .type
= ET_NEW_VERTEX
, .int_1
= cx
,
1653 ret
= eq_push(&ui_e
);
1654 ui_action
= UIA_NONE
;
1656 case UIA_NEW_EDGE_1
:
1657 case UIA_NEW_H_EDGE_1
:
1660 if (selected_vertex
== (size_t) -1) {
1661 ui_action
= UIA_NONE
;
1665 last_clicked_vertex
= selected_vertex
;
1667 if (ui_action
== UIA_NEW_EDGE_1
) {
1668 ui_action
= UIA_NEW_EDGE_2
;
1669 } else if (ui_action
== UIA_NEW_H_EDGE_1
) {
1670 ui_action
= UIA_NEW_H_EDGE_2
;
1671 } else if (ui_action
== UIA_RENAME
) {
1672 ui_action
= UIA_ENTER_RENAME
;
1676 SDL_StartTextInput();
1678 /* Intentionally not copying null terminator */
1679 strncpy(input_with_prefix
, "New name: ",
1681 rerender_input_string
= 1;
1685 case UIA_NEW_EDGE_2
:
1686 case UIA_NEW_H_EDGE_2
:
1688 if (selected_vertex
== (size_t) -1 ||
1689 selected_vertex
== last_clicked_vertex
) {
1690 ui_action
= UIA_NONE
;
1691 last_clicked_vertex
= (size_t) -1;
1695 ui_e
= (struct ui_event
) {
1697 .type
= ET_NEW_EDGE
, .idx_1
=
1698 last_clicked_vertex
, .idx_2
=
1699 selected_vertex
, .a
= 1, .b
=
1700 (ui_action
== UIA_NEW_EDGE_2
?
1703 ret
= eq_push(&ui_e
);
1704 ui_action
= UIA_NONE
;
1705 last_clicked_vertex
= (size_t) -1;
1709 if (selected_vertex
!= (size_t) -1) {
1710 ui_e
= (struct ui_event
) {
1712 .type
= ET_DELETE_VERTEX
,
1713 .idx_1
= selected_vertex
1715 ret
= eq_push(&ui_e
);
1716 } else if (selected_edge_i
!= (size_t) -1 &&
1717 selected_edge_j
!= (size_t) -1) {
1718 ui_e
= (struct ui_event
) {
1720 .type
= ET_DELETE_EDGE
, .idx_1
=
1725 ret
= eq_push(&ui_e
);
1728 ui_action
= UIA_NONE
;
1730 case UIA_INC_FATNESS
:
1731 case UIA_DEC_FATNESS
:
1733 if (selected_vertex
== (size_t) -1) {
1737 ui_e
= (struct ui_event
) {
1739 .type
= ET_CHANGE_FATNESS
, .idx_1
=
1740 selected_vertex
, .int_1
=
1745 ret
= eq_push(&ui_e
);
1746 ui_action
= UIA_NONE
;
1750 case UIA_ENTER_SAVE
:
1751 case UIA_ENTER_LOAD
:
1752 case UIA_ENTER_RENAME
:
1765 if (load_requested
||
1767 save_load_delay
= 30;
1771 if (load_requested
) {
1772 r
= choose_load_file(&f
);
1774 r
= choose_save_file(&f
);
1779 ui_e
= (struct ui_event
) { .str
= f
};
1780 ui_e
.type
= load_requested
? ET_LOAD
: ET_SAVE
;
1782 /* f is freed on next ui_finish_frame */
1784 ret
= eq_push(&ui_e
);
1787 ui_action
= load_requested
? UIA_ENTER_LOAD
:
1792 SDL_StartTextInput();
1794 /* Intentionally not copying null terminator */
1795 strncpy(input_with_prefix
, "Filename: ", 10);
1796 rerender_input_string
= 1;
1801 now
= SDL_GetTicks();
1803 if (frame_start_ticks
< now
) {
1804 Uint32 elapsed_time
= now
- frame_start_ticks
;
1806 if (elapsed_time
< TICKS_PER_FRAME
) {
1807 SDL_Delay(TICKS_PER_FRAME
- elapsed_time
);
1816 /* Return an event to the main loop */
1817 int ui_get_event(struct ui_event
*e
, uint_fast8_t *more
)
1820 *more
= eq_head
!= eq_tail
;
1829 case ET_CHANGE_FATNESS
:
1830 case ET_DELETE_EDGE
:
1831 case ET_DELETE_VERTEX
: