2 * Copyright (c) 2016, S. Gilles <sgilles@math.umd.edu>
4 * Permission to use, copy, modify, and/or distribute this software
5 * for any purpose with or without fee is hereby granted, provided
6 * that the above copyright notice and this permission notice appear
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)
34 #define TRIG_PRECALC_NUM 4
36 /* What clicking does */
37 static enum ui_action
{
56 /* The window we'll be using */
57 static SDL_Window
*sdl_win
;
59 /* The renderer we'll be using */
60 static SDL_Renderer
*sdl_renderer
;
62 /* How to limit the framerate */
63 static Uint32 frame_start_ticks
;
65 /* Whether the scene needs to be drawn again */
66 static uint_fast8_t redraw
;
68 /* The informative texture */
69 static SDL_Texture
*selected_info
;
71 /* Buffer for event queue */
72 static struct ui_event
*eq_buf
;
74 /* Current max length of event queue */
77 /* Current head of queue */
78 static size_t eq_head
;
80 /* Current tail of queue */
81 static size_t eq_tail
;
83 /* The quiver we'll be using */
84 static struct quiver
*q
;
86 /* Width of drawing area in pixels */
87 static int da_pix_width
;
89 /* Height of drawing area in pixels */
90 static int da_pix_height
;
92 /* The background color */
93 static SDL_Color color_bg
= { .r
= 0xe2, .g
= 0xe2, .b
= 0xe2, .a
= 0xff };
95 /* The normal vertex color */
96 static SDL_Color color_v
= { .r
= 0x82, .g
= 0x82, .b
= 0xb2, .a
= 0xff };
98 /* The vertex color for preview vertices */
99 static SDL_Color color_v_preview
= { .r
= 0x82, .g
= 0x82, .b
= 0xb2, .a
=
102 /* The normal vertex outline color */
103 static SDL_Color color_outline
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
= 0x80 };
105 /* The vertex outline color for preview vertices */
106 static SDL_Color color_outline_preview
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
=
109 /* The selected vertex outline color */
110 static SDL_Color color_outline_sel
= { .r
= 0x42, .g
= 0x42, .b
= 0xe2, .a
=
114 static SDL_Color color_font
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
= 0x80 };
116 /* The normal edge color */
117 static SDL_Color color_e_normal
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
=
120 /* The half edge color */
121 static SDL_Color color_e_half
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
= 0x60 };
123 /* The abnormal edge color */
124 static SDL_Color color_e_abnormal
= { .r
= 0xd0, .g
= 0x12, .b
= 0x12, .a
=
127 /* The selected edge color */
128 static SDL_Color color_e_sel
= { .r
= 0xb2, .g
= 0xb2, .b
= 0xe2, .a
= 0x40 };
130 /* The font for node names, instructions, etc */
131 static TTF_Font
*normal_font
;
133 /* The base font size */
134 static uint_fast8_t base_font_size
= 12;
136 /* How much space (pixels) between lower left of screen and bottom text */
137 static unsigned int text_border_padding
= 24;
139 /* Strings to display at bottom of screen */
140 static const char *bottom_string
[] = {
142 [UIA_NONE
] = "[m] Mutate\n[v] Create new vertex\n"
143 "[d] Delete vertex/edge\n[e] Add edge\n"
144 "[h] Add half edge\n[f] Increase vertex fatness\n"
145 "[g] Decrease vertex fatness\n[r] Rename vertex\n"
146 "[s] Save quiver\n[l] Load quiver\n[q] Quit", /* */
147 [UIA_ASK_QUIT
] = "Quit?\n\n[y] Confirm\n[n] Cancel", /* */
148 [UIA_DEC_FATNESS
] = "[Mouse1] Decrease fatness\n[ESC] Cancel", /* */
149 [UIA_DELETE
] = "[Mouse1] Delete vertex/edge\n[ESC] Cancel", /* */
150 [UIA_ENTER_LOAD
] = "[Enter] Load from\n[ESC] Cancel", /* */
151 [UIA_ENTER_RENAME
] = "[Enter] Rename\n[ESC] Cancel", /* */
152 [UIA_ENTER_SAVE
] = "[Enter] Save to\n[ESC] Cancel", /* */
153 [UIA_INC_FATNESS
] = "[Mouse1] Increase fatness\n[ESC] Cancel", /* */
154 [UIA_MUTATE
] = "[Mouse1] Mutate at vertex\n[ESC] Cancel", /* */
155 [UIA_NEW_EDGE_1
] = "[Mouse1] Select start\n[ESC] Cancel", /* */
156 [UIA_NEW_EDGE_2
] = "[Mouse1] Select end\n[ESC] Cancel", /* */
157 [UIA_NEW_H_EDGE_1
] = "[Mouse1] Select start\n[ESC] Cancel", /* */
158 [UIA_NEW_H_EDGE_2
] = "[Mouse1] Select end\n[ESC] Cancel", /* */
159 [UIA_NEW_VERTEX
] = "[Mouse1] Create new vertex\n[ESC] Cancel", /* */
160 [UIA_RENAME
] = "[Mouse1] Rename vertex\n[ESC] Cancel", /* */
164 /* The texture containing what to show on the bottom */
165 static SDL_Texture
*bottom_text
[UIA_LEN
];
167 /* The textures containing the names of all vertices - indexed just like q */
168 static SDL_Texture
**vertex_names
;
170 /* The number of elements of vertex_names */
171 static size_t vertex_names_len
;
173 /* Drawing offset x */
176 /* Drawing offset x */
179 /* How wide (in pixels) a fatness 1 node should be */
180 static unsigned int base_node_radius
= 8;
182 /* How much (in pixels) a node should widen for each fatness level */
183 static unsigned int node_radius_per_fatness
= 7;
185 /* How wide (in pixels) the outline should be */
186 static int outline_width
= 2;
188 /* How wide (in pixels) the arrowheads should be */
189 static int arrow_length
= 7;
191 /* How narrow the arrowheads should be */
192 static double arrow_angle
= 6.5 * M_PI
/ 8.0;
194 /* How close to an arrow (in pixels) for it to be selected */
195 static int edge_margin
= 5;
197 /* If we're interacting with a vertex, which one */
198 static size_t selected_vertex
= (size_t) -1;
200 /* If we're interacting with an edge, the i */
201 static size_t selected_edge_i
= (size_t) -1;
203 /* If we're interacting with an edge, the j */
204 static size_t selected_edge_j
= (size_t) -1;
206 /* If we're adding an edge, the last vertex we clicked on */
207 static size_t last_clicked_vertex
= (size_t) -1;
209 /* x-coordinate of last mouse position */
210 static int last_mouse_x
= -1;
212 /* y-coordinate of last mouse position */
213 static int last_mouse_y
= -1;
215 /* Maximum length of an input string we'll accept */
216 static size_t max_input_size
= 1 << 10;
218 /* Current position in input */
219 static size_t input_idx
;
221 /* Current length of string held in input */
222 static size_t input_len
;
224 /* If a string is being typed in, what it is (in UTF-8 as per SDL2) */
227 /* Input, with `Filename:' or `New name:' prepended (for rendering) */
228 static char *input_with_prefix
;
230 /* Texture for what user is currently entering */
231 static SDL_Texture
*input_texture
;
233 /* Whether we need to re-render the input */
234 static uint_fast8_t rerender_input_string
;
236 /* To prevent awkward repeats in SDL events, rate-limit `s' and `l' */
237 static uint_fast8_t save_load_delay
;
239 /* If something needs to be freed next frame, but not before */
240 static void *free_this
;
243 static double precalc_sins
[TRIG_PRECALC_NUM
];
246 static double precalc_coss
[TRIG_PRECALC_NUM
];
248 /* Precalculate sines and cosines */
249 static void precalc_trig(void)
251 precalc_coss
[0] = 0.0;
252 precalc_sins
[0] = 1.0;
254 for (size_t j
= 1; j
< TRIG_PRECALC_NUM
- 1; ++j
) {
255 double theta
= (M_PI
* j
) / (2 * (TRIG_PRECALC_NUM
- 1));
257 precalc_sins
[j
] = sin(theta
);
258 precalc_coss
[j
] = cos(theta
);
261 precalc_coss
[TRIG_PRECALC_NUM
- 1] = 0.0;
262 precalc_sins
[TRIG_PRECALC_NUM
- 1] = 1.0;
266 static int gcd(uint_fast8_t x
, uint_fast8_t y
)
284 /* Allocate and print a rational in the way a user expects */
285 static int pretty_fraction(struct rational
*r
, char **out
)
290 size_t len
= snprintf(0, 0, "%d", (int) r
->p
);
292 if (!(*out
= malloc(len
+ 1))) {
298 sprintf(*out
, "%d", (int) r
->p
);
302 size_t len
= snprintf(0, 0, "%d/%u", (int) r
->p
, (unsigned int) r
->q
);
304 if (!(*out
= malloc(len
+ 1))) {
310 sprintf(*out
, "%d/%u", (int) r
->p
, (unsigned int) r
->q
);
316 /* Render text to texture */
317 static int render_text(const char *text
, SDL_Texture
**out
)
324 SDL_DestroyTexture(*out
);
328 SDL_Surface
*surf
= 0;
330 if (!(surf
= TTF_RenderUTF8_Blended_Wrapped(normal_font
, text
,
332 fprintf(stderr
, L("TTF_RenderUTF8_Shaded(): %s\n"),
338 if (!(*out
= SDL_CreateTextureFromSurface(sdl_renderer
, surf
))) {
339 fprintf(stderr
, L("SDL_CreateTextureFromSurface(): %s\n"),
346 SDL_FreeSurface(surf
);
352 static int load_fonts(void)
357 TTF_CloseFont(normal_font
);
361 if (!(normal_font
= TTF_OpenFont(FONT_PATH
, base_font_size
))) {
363 fprintf(stderr
, L("TTF_OpenFont(): %s\n"), TTF_GetError());
367 for (size_t j
= 0; j
< UIA_LEN
; ++j
) {
368 if ((ret
= render_text(bottom_string
[j
], &bottom_text
[j
]))) {
377 TTF_CloseFont(normal_font
);
386 /* Insert str into input at given position */
387 static void text_input(const char *str
)
389 size_t l
= strlen(str
);
391 if (l
+ input_len
+ 1 >= max_input_size
) {
395 for (size_t k
= input_len
; k
> input_idx
; --k
) {
396 input
[k
+ l
] = input
[k
];
399 /* Special-case for input_idx: 0 prohibits `>=' above */
400 input
[input_idx
+ l
] = input
[input_idx
];
402 for (size_t k
= 0; k
< l
; ++k
) {
403 input
[input_idx
+ k
] = str
[k
];
408 rerender_input_string
= 1;
411 /* Move input cursor left */
412 static void text_input_left(void)
418 while (input_idx
> 0) {
419 unsigned char c
= input
[input_idx
];
421 if ((c
& 0xc0) == 0x80) {
429 /* Move input cursor right */
430 static void text_input_right(void)
433 unsigned char c
= input
[input_idx
];
435 if ((c
& 0x80) == 0x0) {
437 } else if ((c
& 0xe0) == 0xc0) {
439 } else if ((c
& 0xf0) == 0xe0) {
441 } else if ((c
& 0xf8) == 0xf0) {
445 if (input_idx
+ adv
<= input_len
) {
451 static void text_input_bs(void)
459 while (input_idx
> bs
) {
460 unsigned char c
= input
[input_idx
- bs
];
462 if ((c
& 0xc0) == 0x80) {
469 for (size_t k
= input_idx
- bs
; k
<= input_len
- bs
; k
++) {
470 input
[k
] = input
[k
+ bs
];
475 rerender_input_string
= 1;
478 /* Convert `internal coordinates' to pixel coordinates */
479 static void internal_to_pixel_xy(int in_x
, int in_y
, int *out_x
, int *out_y
)
481 *out_x
= in_x
+ offset_x
;
482 *out_y
= in_y
+ offset_y
;
485 /* Convert pixel coordinates to `internal coordinates' */
486 static void pixel_to_internal_xy(int in_x
, int in_y
, int *out_x
, int *out_y
)
488 *out_x
= in_x
- offset_x
;
489 *out_y
= in_y
- offset_y
;
492 /* Set selected_vertex and selected_edge_{i,j} */
493 static int recalculate_selected_items(int mx
, int my
)
502 pixel_to_internal_xy(mx
, my
, &x
, &y
);
503 size_t last_vertex
= selected_vertex
;
504 size_t last_edge_i
= selected_edge_i
;
505 size_t last_edge_j
= selected_edge_j
;
507 selected_vertex
= (size_t) -1;
508 selected_edge_i
= (size_t) -1;
509 selected_edge_j
= (size_t) -1;
511 for (size_t j
= q
->v_num
; j
> 0; --j
) {
512 struct vertex
*v
= &(q
->v
[j
- 1]);
513 int r
= base_node_radius
+ v
->fatness
* node_radius_per_fatness
;
519 selected_vertex
= j
- 1;
524 for (size_t j
= 1; j
< q
->v_num
; ++j
) {
525 struct vertex
*v1
= &(q
->v
[j
]);
527 for (size_t i
= 0; i
< j
; ++i
) {
528 if (!q
->e
[i
* q
->v_len
+ j
].p
&&
529 !q
->e
[j
* q
->v_len
+ i
].p
) {
533 struct vertex
*v2
= &(q
->v
[i
]);
535 if ((x
- edge_margin
> v1
->x
&&
536 x
- edge_margin
> v2
->x
) ||
537 (x
+ edge_margin
< v1
->x
&&
538 x
+ edge_margin
< v2
->x
) ||
539 (y
- edge_margin
> v1
->y
&&
540 y
- edge_margin
> v2
->y
) ||
541 (y
+ edge_margin
< v1
->y
&&
542 y
+ edge_margin
< v2
->y
)) {
546 if (v1
->x
== v2
->x
) {
547 if (x
+ edge_margin
> v1
->x
&&
548 x
- edge_margin
< v1
->x
) {
553 } else if (v1
->y
== v2
->y
) {
554 if (y
+ edge_margin
> v1
->y
&&
555 y
- edge_margin
< v1
->y
) {
561 double m1
= ((double) (v2
->y
- v1
->y
)) /
562 ((double) (v2
->x
- v1
->x
));
563 double m2
= -1.0 / m1
;
564 double xint
= ((double) y
- (double) v1
->y
+
565 m1
* v1
->x
- m2
* x
) / (m1
- m2
);
566 double yint
= m1
* xint
- m1
* v1
->x
+
569 if ((x
- xint
) * (x
- xint
) + (y
- yint
) * (y
-
571 < edge_margin
* edge_margin
) {
582 if (selected_vertex
!= last_vertex
&&
583 selected_vertex
!= (size_t) -1) {
584 struct vertex
*v
= &(q
->v
[selected_vertex
]);
585 size_t len
= snprintf(0, 0,
586 "Name: %s\nFatness: %d\nPosition: (%d,%d)",
588 (int) v
->fatness
, v
->x
, v
->y
);
590 if (!(s
= malloc(len
+ 1))) {
596 sprintf(s
, "Name: %s\nFatness: %d\nPosition: (%d,%d)",
597 v
->name
, (int) v
->fatness
, v
->x
, v
->y
);
599 if ((ret
= render_text(s
, &selected_info
))) {
602 } else if ((selected_edge_i
!= last_edge_i
||
603 selected_edge_j
!= last_edge_j
) &&
604 selected_edge_i
!= (size_t) -1 &&
605 selected_edge_j
!= (size_t) -1) {
606 struct vertex
*i
= &(q
->v
[selected_edge_i
]);
607 struct vertex
*j
= &(q
->v
[selected_edge_j
]);
608 struct rational
*eij
= &(q
->e
[selected_edge_i
* q
->v_len
+
610 struct rational
*eji
= &(q
->e
[selected_edge_j
* q
->v_len
+
613 if ((ret
= pretty_fraction(eij
, &sij
))) {
617 if ((ret
= pretty_fraction(eji
, &sji
))) {
621 size_t len
= snprintf(0, 0,
622 "%s \u2192 %s: %s\n%s \u2192 %s: %s",
623 i
->name
, j
->name
, sij
,
624 j
->name
, i
->name
, sji
);
626 if (!(s
= malloc(len
+ 1))) {
632 sprintf(s
, "%s \u2192 %s: %s\n%s \u2192 %s: %s", i
->name
,
633 j
->name
, sij
, j
->name
, i
->name
, sji
);
635 if ((ret
= render_text(s
, &selected_info
))) {
648 /* Render vertex names as textures */
649 static int render_vertex_names(void)
652 for (size_t j
= 0; j
< vertex_names_len
; ++j
) {
653 SDL_DestroyTexture(vertex_names
[j
]);
662 if (!(vertex_names
= realloc(vertex_names
, q
->v_num
*
663 sizeof(*vertex_names
)))) {
666 perror(L("realloc()"));
667 vertex_names_len
= 0;
672 vertex_names_len
= q
->v_num
;
674 for (size_t j
= 0; j
< vertex_names_len
; ++j
) {
678 for (size_t j
= 0; j
< vertex_names_len
; ++j
) {
681 if ((ret
= render_text(q
->v
[j
].name
, &(vertex_names
[j
])))) {
689 /* Get information about the window */
690 static void react_to_window_resized(void)
692 int old_pix_width
= da_pix_width
;
693 int old_pix_height
= da_pix_height
;
695 SDL_GetWindowSize(sdl_win
, &da_pix_width
, &da_pix_height
);
697 if (old_pix_width
== da_pix_width
&&
698 old_pix_height
== da_pix_height
) {
702 offset_x
+= (da_pix_width
- old_pix_width
) / 2;
703 offset_y
+= (da_pix_height
- old_pix_height
) / 2;
707 static void eq_pop(struct ui_event
*out
)
709 if (eq_head
== eq_tail
) {
710 *out
= (struct ui_event
) { 0 };
715 memcpy(out
, eq_buf
+ eq_head
, sizeof *out
);
716 eq_buf
[eq_head
] = (struct ui_event
) { 0 };
717 eq_head
= (eq_head
+ 1) % eq_len
;
720 /* Push into queue */
721 static int eq_push(struct ui_event
*in
)
723 if (((eq_tail
+ 1) % eq_len
) == eq_head
) {
726 if ((eq_len
* sizeof *in
) >= ((size_t) -1) / 2) {
728 "eq_push: Impossibly large buffer\n"));
733 if (!(newmem
= realloc(eq_buf
, (eq_len
* 2) *
737 perror(L("realloc"));
742 eq_buf
= (struct ui_event
*) newmem
;
746 memcpy(eq_buf
+ eq_tail
, in
, sizeof *in
);
747 eq_tail
= (eq_tail
+ 1) % eq_len
;
753 int ui_init(struct quiver
*i_q
)
758 size_t padding
= strlen("Filename: ");
760 if (!(input_with_prefix
= malloc(max_input_size
+ padding
))) {
767 strcpy(input_with_prefix
, "Filename: ");
768 input
= input_with_prefix
+ padding
;
772 if (SDL_Init(SDL_INIT_VIDEO
) < 0) {
773 fprintf(stderr
, L("SDL_Init(): %s\n"), SDL_GetError());
778 sdl_win
= SDL_CreateWindow("Cluster Algebra Visualizer",
779 SDL_WINDOWPOS_UNDEFINED
,
780 SDL_WINDOWPOS_UNDEFINED
, 1000, 1000,
781 SDL_WINDOW_SHOWN
| SDL_WINDOW_RESIZABLE
);
784 fprintf(stderr
, L("SDL_CreateWindow(): %s\n"), SDL_GetError());
789 sdl_renderer
= SDL_CreateRenderer(sdl_win
, -1,
790 SDL_RENDERER_ACCELERATED
);
793 fprintf(stderr
, L("SDL_CreateRenderer(): %s\n"),
799 if (TTF_Init() < 0) {
800 fprintf(stderr
, L("TTF_Init(): %s\n"), TTF_GetError());
805 if ((ret
= load_fonts())) {
809 if ((ret
= render_vertex_names())) {
813 if ((ret
= SDL_SetRenderDrawColor(sdl_renderer
, color_bg
.r
, color_bg
.g
,
814 color_bg
.b
, color_bg
.a
))) {
815 fprintf(stderr
, L("SDL_SetRenderDrawColor(): %s\n"),
820 if ((ret
= SDL_RenderClear(sdl_renderer
))) {
821 fprintf(stderr
, L("SDL_RenderClear(): %s\n"), SDL_GetError());
825 SDL_RenderPresent(sdl_renderer
);
826 react_to_window_resized();
828 /* Set up queue for returning data */
829 if (!(eq_buf
= calloc(2, sizeof *eq_buf
))) {
839 /* Sines and cosines for drawing circles */
846 /* Deal with the fact that the quiver was changed */
847 int ui_respond_quiver_change(void)
849 return render_vertex_names();
853 int ui_teardown(void)
856 for (size_t j
= 0; j
< vertex_names_len
; ++j
) {
857 SDL_DestroyTexture(vertex_names
[j
]);
862 for (size_t j
= 0; j
< UIA_LEN
; ++j
) {
863 SDL_DestroyTexture(bottom_text
[j
]);
868 SDL_DestroyTexture(selected_info
);
873 SDL_DestroyTexture(input_texture
);
877 TTF_CloseFont(normal_font
);
882 SDL_DestroyWindow(sdl_win
);
885 free(input_with_prefix
);
892 /* Record that a frame has been started */
893 int ui_start_frame(void)
903 frame_start_ticks
= SDL_GetTicks();
911 /* Draw the damn thing */
912 SDL_SetRenderDrawColor(sdl_renderer
, color_bg
.r
, color_bg
.g
, color_bg
.b
,
914 SDL_RenderClear(sdl_renderer
);
916 /* Special case for if text input is going on */
917 if (rerender_input_string
) {
918 rerender_input_string
= 0;
920 if ((ret
= render_text(input_with_prefix
, &input_texture
))) {
926 for (size_t j
= 0; j
< q
->v_num
; ++j
) {
927 for (size_t i
= 0; i
< j
; ++i
) {
928 /* First, determine if we're looking at a half-edge or a full-edge */
929 int d
= gcd(q
->v
[i
].fatness
, q
->v
[j
].fatness
);
930 struct rational
*eij
= &(q
->e
[i
* q
->v_len
+ j
]);
931 struct rational
*eji
= &(q
->e
[j
* q
->v_len
+ i
]);
943 internal_to_pixel_xy(q
->v
[i
].x
, q
->v
[i
].y
, &cx
, &cy
);
944 internal_to_pixel_xy(q
->v
[j
].x
, q
->v
[j
].y
, &cx2
, &cy2
);
946 if (selected_edge_i
== i
&&
947 selected_edge_j
== j
) {
948 if ((ret
= SDL_SetRenderDrawColor(sdl_renderer
,
955 "SDL_RenderDrawColor(): %s\n"),
960 for (int id
= -edge_margin
; id
< edge_margin
;
962 for (int jd
= -edge_margin
; jd
<
964 if ((ret
= SDL_RenderDrawLine(
970 "SDL_RenderDrawLine(): %s\n"),
978 /* This is the (eij)/dj = -(eji)/di condition */
979 if (eij
->p
* q
->v
[i
].fatness
* eji
->q
!= -eji
->p
*
980 q
->v
[j
].fatness
* eij
->q
) {
981 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
986 } else if (abs(eij
->p
) * d
== q
->v
[j
].fatness
*
988 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
993 } else if (2 * abs(eij
->p
) * d
== q
->v
[j
].fatness
*
995 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
1001 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
1005 color_e_abnormal
.a
);
1010 "SDL_SetRenderDrawColor(): %s\n"),
1015 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, cx
, cy
, cx2
,
1017 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1023 theta
= (cy2
> cy
) ? M_PI
/ 2.0 : -M_PI
/ 2.0;
1025 theta
= atan2f(cy2
- cy
, cx2
- cx
);
1032 cx
= (cx
+ cx2
) / 2;
1033 cy
= (cy
+ cy2
) / 2;
1034 cx2
= cx
+ arrow_length
* cos(theta
+ arrow_angle
);
1035 cy2
= cy
+ arrow_length
* sin(theta
+ arrow_angle
);
1037 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, cx
, cy
, cx2
,
1039 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1044 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, cx
, cy
, cx
+
1045 arrow_length
* cos(theta
-
1048 arrow_length
* sin(theta
-
1051 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1058 /* Draw each vertex as a box */
1059 for (size_t j
= 0; j
< q
->v_num
; ++j
) {
1060 struct vertex
*v
= &(q
->v
[j
]);
1064 internal_to_pixel_xy(v
->x
, v
->y
, &cx
, &cy
);
1066 /* Central square */
1067 SDL_SetRenderDrawBlendMode(sdl_renderer
, SDL_BLENDMODE_NONE
);
1068 SDL_SetRenderDrawColor(sdl_renderer
, color_v
.r
, color_v
.g
,
1069 color_v
.b
, color_v
.a
);
1070 rho
= base_node_radius
+ node_radius_per_fatness
* v
->fatness
;
1075 SDL_RenderFillRect(sdl_renderer
, &r
);
1078 SDL_SetRenderDrawBlendMode(sdl_renderer
, SDL_BLENDMODE_BLEND
);
1080 if (j
== selected_vertex
||
1081 j
== last_clicked_vertex
) {
1082 SDL_SetRenderDrawColor(sdl_renderer
,
1083 color_outline_sel
.r
,
1084 color_outline_sel
.g
,
1085 color_outline_sel
.b
,
1086 color_outline_sel
.a
);
1088 SDL_SetRenderDrawColor(sdl_renderer
, color_outline
.r
,
1089 color_outline
.g
, color_outline
.b
,
1095 r
.w
= 2 * rho
- outline_width
;
1096 r
.h
= outline_width
;
1097 SDL_RenderFillRect(sdl_renderer
, &r
);
1098 r
.x
= cx
+ rho
- outline_width
;
1100 r
.w
= outline_width
;
1101 r
.h
= 2 * rho
- outline_width
;
1102 SDL_RenderFillRect(sdl_renderer
, &r
);
1103 r
.x
= cx
- rho
+ outline_width
;
1104 r
.y
= cy
+ rho
- outline_width
;
1105 r
.w
= 2 * rho
- outline_width
;
1106 r
.h
= outline_width
;
1107 SDL_RenderFillRect(sdl_renderer
, &r
);
1109 r
.y
= cy
- rho
+ outline_width
;
1110 r
.w
= outline_width
;
1111 r
.h
= 2 * rho
- outline_width
;
1112 SDL_RenderFillRect(sdl_renderer
, &r
);
1115 if (j
>= vertex_names_len
) {
1117 "render_vertex_names() was not called, somehow\n"));
1122 if (SDL_QueryTexture(vertex_names
[j
], &dummy_format
,
1123 &dummy_access
, &tex_w
, &tex_h
)) {
1124 fprintf(stderr
, L("SDL_QueryTexture(): %s\n"),
1130 r
.x
= cx
- tex_w
/ 2;
1131 r
.y
= cy
- tex_h
/ 2;
1134 SDL_RenderCopy(sdl_renderer
, vertex_names
[j
], 0, &r
);
1137 /* If adding a new vertex, draw preview */
1138 if (ui_action
== UIA_NEW_VERTEX
&&
1139 last_mouse_x
!= -1 &&
1140 last_mouse_y
!= -1) {
1141 /* Central square */
1142 SDL_SetRenderDrawBlendMode(sdl_renderer
, SDL_BLENDMODE_NONE
);
1143 SDL_SetRenderDrawColor(sdl_renderer
, color_v_preview
.r
,
1144 color_v_preview
.g
, color_v_preview
.b
,
1146 rho
= base_node_radius
;
1147 r
.x
= last_mouse_x
- rho
;
1148 r
.y
= last_mouse_y
- rho
;
1151 SDL_RenderFillRect(sdl_renderer
, &r
);
1154 SDL_SetRenderDrawBlendMode(sdl_renderer
, SDL_BLENDMODE_BLEND
);
1155 SDL_SetRenderDrawColor(sdl_renderer
, color_outline_preview
.r
,
1156 color_outline_preview
.g
,
1157 color_outline_preview
.b
,
1158 color_outline_preview
.a
);
1159 r
.x
= last_mouse_x
- rho
;
1160 r
.y
= last_mouse_y
- rho
;
1161 r
.w
= 2 * rho
- outline_width
;
1162 r
.h
= outline_width
;
1163 SDL_RenderFillRect(sdl_renderer
, &r
);
1164 r
.x
= last_mouse_x
+ rho
- outline_width
;
1165 r
.y
= last_mouse_y
- rho
;
1166 r
.w
= outline_width
;
1167 r
.h
= 2 * rho
- outline_width
;
1168 SDL_RenderFillRect(sdl_renderer
, &r
);
1169 r
.x
= last_mouse_x
- rho
+ outline_width
;
1170 r
.y
= last_mouse_y
+ rho
- outline_width
;
1171 r
.w
= 2 * rho
- outline_width
;
1172 r
.h
= outline_width
;
1173 SDL_RenderFillRect(sdl_renderer
, &r
);
1174 r
.x
= last_mouse_x
- rho
;
1175 r
.y
= last_mouse_y
- rho
+ outline_width
;
1176 r
.w
= outline_width
;
1177 r
.h
= 2 * rho
- outline_width
;
1178 SDL_RenderFillRect(sdl_renderer
, &r
);
1181 /* If adding a new edge, draw possible */
1182 if ((ui_action
== UIA_NEW_EDGE_2
||
1183 ui_action
== UIA_NEW_H_EDGE_2
) &&
1185 /* last_clicked_vertex != (size_t) -1 && */
1186 last_mouse_x
!= -1 &&
1187 last_mouse_y
!= -1) {
1191 if ((ret
= SDL_SetRenderDrawColor(sdl_renderer
,
1195 color_e_normal
.a
))) {
1196 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1201 internal_to_pixel_xy(q
->v
[last_clicked_vertex
].x
,
1202 q
->v
[last_clicked_vertex
].y
, &cx
, &cy
);
1204 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, cx
, cy
,
1205 last_mouse_x
, last_mouse_y
))) {
1206 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1213 if (SDL_QueryTexture(bottom_text
[ui_action
], &dummy_format
,
1214 &dummy_access
, &tex_w
, &tex_h
)) {
1215 fprintf(stderr
, L("SDL_QueryTexture(): %s\n"), SDL_GetError());
1220 r
.x
= text_border_padding
;
1221 r
.y
= da_pix_height
- tex_h
- text_border_padding
;
1224 SDL_RenderCopy(sdl_renderer
, bottom_text
[ui_action
], 0, &r
);
1226 /* If something is selected */
1227 if (selected_info
&&
1228 (selected_vertex
!= (size_t) -1 ||
1229 (selected_edge_i
!= (size_t) -1 &&
1230 selected_edge_j
!= (size_t) -1))) {
1231 if (SDL_QueryTexture(selected_info
, &dummy_format
,
1232 &dummy_access
, &tex_w
, &tex_h
)) {
1233 fprintf(stderr
, L("SDL_QueryTexture(): %s\n"),
1239 r
.x
= text_border_padding
;
1240 r
.y
= text_border_padding
;
1243 SDL_RenderCopy(sdl_renderer
, selected_info
, 0, &r
);
1246 /* If user is entering text */
1247 if (ui_action
== UIA_ENTER_SAVE
||
1248 ui_action
== UIA_ENTER_LOAD
||
1249 ui_action
== UIA_ENTER_RENAME
) {
1250 if (SDL_QueryTexture(bottom_text
[ui_action
], &dummy_format
,
1251 &dummy_access
, &tex_w
, &tex_h
)) {
1252 fprintf(stderr
, L("SDL_QueryTexture(): %s\n"),
1258 r
.x
= text_border_padding
;
1259 r
.y
= da_pix_height
- tex_h
- text_border_padding
;
1261 if (SDL_QueryTexture(input_texture
, &dummy_format
,
1262 &dummy_access
, &tex_w
, &tex_h
)) {
1263 fprintf(stderr
, L("SDL_QueryTexture(): %s\n"),
1269 r
.y
-= (tex_h
+ text_border_padding
);
1272 SDL_RenderCopy(sdl_renderer
, input_texture
, 0, &r
);
1274 /* Now draw a cursor */
1275 char store_idxchar
= input
[input_idx
];
1277 input
[input_idx
] = '\0';
1279 if ((ret
= TTF_SizeUTF8(normal_font
, input_with_prefix
, &tex_w
,
1281 fprintf(stderr
, L("TTF_SizeText(): %s\n"),
1286 input
[input_idx
] = store_idxchar
;
1288 if ((ret
= SDL_SetRenderDrawColor(sdl_renderer
, color_font
.r
,
1289 color_font
.g
, color_font
.b
,
1291 fprintf(stderr
, L("SDL_SetRenderDrawColor(): %s\n"),
1296 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, r
.x
+ tex_w
, r
.y
,
1297 r
.x
+ tex_w
, r
.y
+ tex_h
))) {
1298 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
1309 /* Draw a frame, possibly sleeping for framelimit */
1310 int ui_finish_frame(void)
1313 struct ui_event ui_e
= { 0 };
1314 SDL_Event sdl_e
= { 0 };
1316 uint_fast8_t save_requested
= 0;
1317 uint_fast8_t load_requested
= 0;
1324 if (save_load_delay
) {
1328 SDL_RenderPresent(sdl_renderer
);
1330 /* Handle user input */
1331 while (SDL_PollEvent(&sdl_e
) != 0) {
1335 switch (sdl_e
.type
) {
1337 ui_e
= (struct ui_event
) { .type
= ET_QUIT
};
1338 ret
= eq_push(&ui_e
);
1341 text_input(sdl_e
.text
.text
);
1345 if (sdl_e
.key
.repeat
) {
1349 k
= sdl_e
.key
.keysym
.sym
;
1351 switch (ui_action
) {
1352 case UIA_ENTER_SAVE
:
1353 case UIA_ENTER_LOAD
:
1354 case UIA_ENTER_RENAME
:
1356 if (k
== SDLK_ESCAPE
) {
1357 SDL_StopTextInput();
1358 ui_action
= UIA_NONE
;
1359 last_clicked_vertex
= (size_t) -1;
1360 } else if (k
== SDLK_RETURN
||
1361 k
== SDLK_RETURN2
) {
1362 SDL_StopTextInput();
1363 ui_e
= (struct ui_event
) { .str
=
1366 if (ui_action
== UIA_ENTER_SAVE
) {
1367 ui_e
.type
= ET_SAVE
;
1368 } else if (ui_action
==
1370 ui_e
.type
= ET_LOAD
;
1371 } else if (ui_action
==
1373 ui_e
.type
= ET_RENAME
;
1375 last_clicked_vertex
;
1378 ret
= eq_push(&ui_e
);
1379 ui_action
= UIA_NONE
;
1380 last_clicked_vertex
= (size_t) -1;
1381 } else if (k
== SDLK_LEFT
) {
1383 } else if (k
== SDLK_RIGHT
) {
1385 } else if (k
== SDLK_BACKSPACE
) {
1393 ui_action
= UIA_ASK_QUIT
;
1394 } else if (k
== SDLK_d
) {
1395 ui_action
= UIA_DELETE
;
1396 } else if (k
== SDLK_e
) {
1397 ui_action
= UIA_NEW_EDGE_1
;
1398 } else if (k
== SDLK_f
) {
1399 ui_action
= UIA_INC_FATNESS
;
1400 } else if (k
== SDLK_g
) {
1401 ui_action
= UIA_DEC_FATNESS
;
1402 } else if (k
== SDLK_h
) {
1403 ui_action
= UIA_NEW_H_EDGE_1
;
1404 } else if (k
== SDLK_m
) {
1405 ui_action
= UIA_MUTATE
;
1406 } else if (k
== SDLK_r
) {
1407 ui_action
= UIA_RENAME
;
1408 } else if (k
== SDLK_v
) {
1409 ui_action
= UIA_NEW_VERTEX
;
1410 } else if (k
== SDLK_l
&&
1412 /* Don't load - SDL_KEYUP repeats */
1414 } else if (k
== SDLK_s
&&
1424 ui_action
= UIA_NONE
;
1425 } else if (k
== SDLK_y
) {
1426 ui_e
= (struct ui_event
) { .type
=
1428 ret
= eq_push(&ui_e
);
1429 ui_action
= UIA_NONE
;
1437 ui_action
= UIA_NONE
;
1444 case SDL_WINDOWEVENT
:
1446 if (sdl_e
.window
.event
== SDL_WINDOWEVENT_RESIZED
||
1447 sdl_e
.window
.event
== SDL_WINDOWEVENT_MAXIMIZED
||
1448 sdl_e
.window
.event
== SDL_WINDOWEVENT_RESTORED
) {
1449 react_to_window_resized();
1450 } else if (sdl_e
.window
.event
==
1451 SDL_WINDOWEVENT_LEAVE
) {
1452 /* This tells the dragging code to not respond */
1458 case SDL_MOUSEMOTION
:
1460 if (sdl_e
.motion
.state
& SDL_BUTTON_LMASK
) {
1461 int x
= sdl_e
.motion
.x
;
1462 int y
= sdl_e
.motion
.y
;
1464 if (last_mouse_x
>= 0 &&
1465 last_mouse_y
>= 0) {
1466 if (selected_vertex
!= (size_t) -1) {
1467 q
->v
[selected_vertex
].x
+= (x
-
1469 q
->v
[selected_vertex
].y
+= (y
-
1472 offset_x
+= (x
- last_mouse_x
);
1473 offset_y
+= (y
- last_mouse_y
);
1477 recalculate_selected_items(sdl_e
.motion
.x
,
1481 last_mouse_x
= sdl_e
.motion
.x
;
1482 last_mouse_y
= sdl_e
.motion
.y
;
1484 case SDL_MOUSEBUTTONUP
:
1486 if ((sdl_e
.button
.state
& SDL_BUTTON_LMASK
) &&
1487 ui_action
!= UIA_NEW_VERTEX
) {
1492 recalculate_selected_items(sdl_e
.button
.x
,
1495 case SDL_MOUSEBUTTONDOWN
:
1497 if (!(sdl_e
.button
.state
& SDL_BUTTON_LMASK
)) {
1501 if (ui_action
!= UIA_NEW_VERTEX
) {
1506 switch (ui_action
) {
1509 if (selected_vertex
== (size_t) -1) {
1513 ui_e
= (struct ui_event
) {
1515 .type
= ET_MUTATE
, .idx_1
=
1518 ret
= eq_push(&ui_e
);
1519 ui_action
= UIA_NONE
;
1521 case UIA_NEW_VERTEX
:
1523 if (selected_vertex
!= (size_t) -1 ||
1524 selected_edge_i
!= (size_t) -1 ||
1525 selected_edge_j
!= (size_t) -1) {
1529 int cx
= sdl_e
.button
.x
- offset_x
;
1530 int cy
= sdl_e
.button
.y
- offset_y
;
1532 ui_e
= (struct ui_event
) {
1534 .type
= ET_NEW_VERTEX
, .int_1
= cx
,
1537 ret
= eq_push(&ui_e
);
1538 ui_action
= UIA_NONE
;
1540 case UIA_NEW_EDGE_1
:
1541 case UIA_NEW_H_EDGE_1
:
1544 if (selected_vertex
== (size_t) -1) {
1545 ui_action
= UIA_NONE
;
1549 last_clicked_vertex
= selected_vertex
;
1551 if (ui_action
== UIA_NEW_EDGE_1
) {
1552 ui_action
= UIA_NEW_EDGE_2
;
1553 } else if (ui_action
== UIA_NEW_H_EDGE_1
) {
1554 ui_action
= UIA_NEW_H_EDGE_2
;
1555 } else if (ui_action
== UIA_RENAME
) {
1556 ui_action
= UIA_ENTER_RENAME
;
1560 SDL_StartTextInput();
1562 /* Intentionally not copying null terminator */
1563 strncpy(input_with_prefix
, "New name: ",
1565 rerender_input_string
= 1;
1569 case UIA_NEW_EDGE_2
:
1570 case UIA_NEW_H_EDGE_2
:
1572 if (selected_vertex
== (size_t) -1 ||
1573 selected_vertex
== last_clicked_vertex
) {
1574 ui_action
= UIA_NONE
;
1575 last_clicked_vertex
= (size_t) -1;
1579 ui_e
= (struct ui_event
) {
1581 .type
= ET_NEW_EDGE
, .idx_1
=
1582 last_clicked_vertex
, .idx_2
=
1583 selected_vertex
, .a
= 1, .b
=
1584 (ui_action
== UIA_NEW_EDGE_2
?
1587 ret
= eq_push(&ui_e
);
1588 ui_action
= UIA_NONE
;
1589 last_clicked_vertex
= (size_t) -1;
1593 if (selected_vertex
!= (size_t) -1) {
1594 ui_e
= (struct ui_event
) {
1596 .type
= ET_DELETE_VERTEX
,
1597 .idx_1
= selected_vertex
1599 ret
= eq_push(&ui_e
);
1600 } else if (selected_edge_i
!= (size_t) -1 &&
1601 selected_edge_j
!= (size_t) -1) {
1602 ui_e
= (struct ui_event
) {
1604 .type
= ET_DELETE_EDGE
, .idx_1
=
1609 ret
= eq_push(&ui_e
);
1612 ui_action
= UIA_NONE
;
1614 case UIA_INC_FATNESS
:
1615 case UIA_DEC_FATNESS
:
1617 if (selected_vertex
== (size_t) -1) {
1621 ui_e
= (struct ui_event
) {
1623 .type
= ET_CHANGE_FATNESS
, .idx_1
=
1624 selected_vertex
, .int_1
=
1629 ret
= eq_push(&ui_e
);
1630 ui_action
= UIA_NONE
;
1634 case UIA_ENTER_SAVE
:
1635 case UIA_ENTER_LOAD
:
1636 case UIA_ENTER_RENAME
:
1649 if (load_requested
||
1651 save_load_delay
= 30;
1655 if (load_requested
) {
1656 r
= choose_load_file(&f
);
1658 r
= choose_save_file(&f
);
1663 ui_e
= (struct ui_event
) { .str
= f
};
1664 ui_e
.type
= load_requested
? ET_LOAD
: ET_SAVE
;
1666 /* f is freed on next ui_finish_frame */
1668 ret
= eq_push(&ui_e
);
1671 ui_action
= load_requested
? UIA_ENTER_LOAD
:
1676 SDL_StartTextInput();
1678 /* Intentionally not copying null terminator */
1679 strncpy(input_with_prefix
, "Filename: ", 10);
1680 rerender_input_string
= 1;
1685 now
= SDL_GetTicks();
1687 if (frame_start_ticks
< now
) {
1688 Uint32 elapsed_time
= now
- frame_start_ticks
;
1690 if (elapsed_time
< TICKS_PER_FRAME
) {
1691 SDL_Delay(TICKS_PER_FRAME
- elapsed_time
);
1700 /* Return an event to the main loop */
1701 int ui_get_event(struct ui_event
*e
, uint_fast8_t *more
)
1704 *more
= eq_head
!= eq_tail
;