14 #define TICKS_PER_FRAME (1000 / 60)
16 #define TRIG_PRECALC_NUM 4
18 /* The window we'll be using */
19 static SDL_Window
*sdl_win
;
21 /* The renderer we'll be using */
22 static SDL_Renderer
*sdl_renderer
;
24 /* How to limit the framerate */
25 static Uint32 frame_start_ticks
;
27 /* If conf/deny, the message onscreen */
28 static const char *conf_deny_text
;
30 /* Buffer for event queue */
31 static struct ui_event
*eq_buf
;
33 /* Current max length of event queue */
36 /* Current head of queue */
37 static size_t eq_head
;
39 /* Current tail of queue */
40 static size_t eq_tail
;
42 /* The quiver we'll be using */
43 static struct quiver
*q
;
45 /* Width of drawing area in pixels */
46 static int da_pix_width
;
48 /* Height of drawing area in pixels */
49 static int da_pix_height
;
51 /* The background color */
52 static SDL_Color color_bg
= { .r
= 0xe2, .g
= 0xe2, .b
= 0xe2, .a
= 0xff };
54 /* The normal vertex color */
55 static SDL_Color color_v
= { .r
= 0xe2, .g
= 0x82, .b
= 0x82, .a
= 0xff };
57 /* The normal vertex color */
58 static SDL_Color color_outline
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
= 0x80 };
61 static SDL_Color color_font
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
= 0x80 };
63 /* The normal edge color */
64 static SDL_Color color_e_normal
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
=
67 /* The half edge color */
68 static SDL_Color color_e_half
= { .r
= 0x12, .g
= 0x12, .b
= 0x12, .a
= 0x60 };
70 /* The abnormal edge color */
71 static SDL_Color color_e_abnormal
= { .r
= 0xd0, .g
= 0x12, .b
= 0x12, .a
=
74 /* The font for node names, instructions, etc */
75 static TTF_Font
*normal_font
;
77 /* The font for big messages */
78 static TTF_Font
*big_font
;
80 /* The base font size */
81 static uint_fast8_t base_font_size
= 12;
83 /* The textures containing the names of all vertices - indexed just like q */
84 static SDL_Texture
**vertex_names
;
86 /* The number of elements of vertex_names */
87 static size_t vertex_names_len
;
89 /* Drawing offset x */
92 /* Drawing offset x */
95 /* How wide (in pixels) each node should be */
96 static int node_radius
= 15;
98 /* How wide (in pixels) the outline should be */
99 static int outline_width
= 2;
101 /* How wide (in pixels) the arrowheads should be */
102 static int arrow_length
= 7;
104 /* How narrow the arrowheads should be */
105 static double arrow_angle
= 6.5 * M_PI
/ 8.0;
107 /* If we're interacting with a vertex, which one */
108 /* static size_t selected_vertex = (size_t) -1; */
109 /* If we're interacting with an edge, the i */
110 /* static size_t selected_edge_i */
111 /* If we're interacting with an edge, the j */
112 /* static size_t selected_edge_j */
113 /* x-coordinate of last mouse position */
114 static int last_mouse_x
= -1;
116 /* y-coordinate of last mouse position */
117 static int last_mouse_y
= -1;
120 static double precalc_sins
[TRIG_PRECALC_NUM
];
123 static double precalc_coss
[TRIG_PRECALC_NUM
];
125 /* Precalculate sines and cosines */
126 static void precalc_trig(void)
131 precalc_coss
[0] = 0.0;
132 precalc_sins
[0] = 1.0;
134 for (j
= 1; j
< TRIG_PRECALC_NUM
- 1; ++j
) {
135 theta
= (M_PI
* j
) / (2 * (TRIG_PRECALC_NUM
- 1));
136 precalc_sins
[j
] = sin(theta
);
137 precalc_coss
[j
] = cos(theta
);
140 precalc_coss
[TRIG_PRECALC_NUM
- 1] = 0.0;
141 precalc_sins
[TRIG_PRECALC_NUM
- 1] = 1.0;
145 static int gcd(uint_fast8_t x
, uint_fast8_t y
)
164 static int load_fonts(void)
169 TTF_CloseFont(normal_font
);
173 TTF_CloseFont(big_font
);
176 if (!(normal_font
= TTF_OpenFont(FONT_PATH
, base_font_size
))) {
178 fprintf(stderr
, L("TTF_OpenFont(): %s\n"), TTF_GetError());
182 if (!(big_font
= TTF_OpenFont(FONT_PATH
, base_font_size
+ 5))) {
184 fprintf(stderr
, L("TTF_OpenFont(): %s\n"), TTF_GetError());
192 TTF_CloseFont(normal_font
);
196 TTF_CloseFont(big_font
);
206 /* Render vertex names as textures */
207 static int render_vertex_names(void)
211 SDL_Surface
*surf
= 0;
214 for (j
= 0; j
< vertex_names_len
; ++j
) {
215 SDL_DestroyTexture(vertex_names
[j
]);
220 if (!(vertex_names
= realloc(vertex_names
, q
->v_num
*
221 sizeof(*vertex_names
)))) {
223 perror(L("realloc()"));
224 vertex_names_len
= 0;
229 vertex_names_len
= q
->v_num
;
231 for (j
= 0; j
< vertex_names_len
; ++j
) {
235 for (j
= 0; j
< vertex_names_len
; ++j
) {
236 if (!(surf
= TTF_RenderUTF8_Shaded(normal_font
, q
->v
[j
].name
,
237 color_font
, color_v
))) {
239 "\n\nWas trying to render \u300c%s\u300d\n",
241 fprintf(stderr
, L("TTF_RenderUTF8_Shaded(): %s\n"),
247 if (!(vertex_names
[j
] = SDL_CreateTextureFromSurface(
248 sdl_renderer
, surf
))) {
250 "SDL_CreateTextureFromSurface(): %s\n"),
256 SDL_FreeSurface(surf
);
263 /* Convert `internal coordinates' to pixel coordinates */
264 static int internal_to_pixel_xy(int in_x
, int in_y
, int *out_x
, int *out_y
)
266 *out_x
= in_x
+ offset_x
;
267 *out_y
= in_y
+ offset_y
;
272 /* Convert pixel coordinates to `internal coordinates' */
275 static int pixel_to_internal_xy(int in_x, int in_y, float *out_x, float *out_y)
277 * out_x = (in_x - 0.5f - offset_x) / ic_to_pix;
278 * out_y = (in_y - 0.5f - offset_y) / ic_to_pix;
284 /* Get information about the window */
285 static void react_to_window_resized(void)
287 int old_pix_width
= da_pix_width
;
288 int old_pix_height
= da_pix_height
;
290 SDL_GetWindowSize(sdl_win
, &da_pix_width
, &da_pix_height
);
292 if (old_pix_width
== da_pix_width
&&
293 old_pix_height
== da_pix_height
) {
297 offset_x
+= (da_pix_width
- old_pix_width
) / 2;
298 offset_y
+= (da_pix_height
- old_pix_height
) / 2;
302 static void eq_pop(struct ui_event
*out
)
304 if (eq_head
== eq_tail
) {
305 *out
= (struct ui_event
) { 0 };
310 memcpy(out
, eq_buf
+ eq_head
, sizeof *out
);
311 eq_buf
[eq_head
] = (struct ui_event
) { 0 };
312 eq_head
= (eq_head
+ 1) % eq_len
;
315 /* Push into queue */
316 static int eq_push(struct ui_event
*in
)
321 if (((eq_tail
+ 1) % eq_len
) == eq_head
) {
322 if ((eq_len
* sizeof *in
) >= ((size_t) -1) / 2) {
324 "eq_push: Impossibly large buffer\n"));
329 if (!(newmem
= realloc(eq_buf
, (eq_len
* 2) *
332 perror(L("realloc"));
337 eq_buf
= (struct ui_event
*) newmem
;
341 memcpy(eq_buf
+ eq_tail
, in
, sizeof *in
);
342 eq_tail
= (eq_tail
+ 1) % eq_len
;
348 int ui_init(struct quiver
*i_q
)
354 if (SDL_Init(SDL_INIT_VIDEO
) < 0) {
355 fprintf(stderr
, L("SDL_Init(): %s\n"), SDL_GetError());
360 sdl_win
= SDL_CreateWindow("Cluster Algebra Visualizer",
361 SDL_WINDOWPOS_UNDEFINED
,
362 SDL_WINDOWPOS_UNDEFINED
, 1000, 1000,
363 SDL_WINDOW_SHOWN
| SDL_WINDOW_RESIZABLE
);
366 fprintf(stderr
, L("SDL_CreateWindow(): %s\n"), SDL_GetError());
371 sdl_renderer
= SDL_CreateRenderer(sdl_win
, -1,
372 SDL_RENDERER_ACCELERATED
);
375 fprintf(stderr
, L("SDL_CreateRenderer(): %s\n"),
381 if (TTF_Init() < 0) {
382 fprintf(stderr
, L("TTF_Init(): %s\n"), TTF_GetError());
387 if ((ret
= load_fonts())) {
391 if ((ret
= render_vertex_names())) {
395 if ((ret
= SDL_SetRenderDrawColor(sdl_renderer
, color_bg
.r
, color_bg
.g
,
396 color_bg
.b
, color_bg
.a
))) {
397 fprintf(stderr
, L("SDL_SetRenderDrawColor(): %s\n"),
402 if ((ret
= SDL_RenderClear(sdl_renderer
))) {
403 fprintf(stderr
, L("SDL_RenderClear(): %s\n"), SDL_GetError());
407 SDL_RenderPresent(sdl_renderer
);
408 react_to_window_resized();
410 /* Set up queue for returning data */
411 if (!(eq_buf
= calloc(2, sizeof *eq_buf
))) {
421 /* Sines and cosines for drawing circles */
428 /* Deal with the fact that the quiver was changed */
429 int ui_respond_quiver_change(void)
431 return render_vertex_names();
435 int ui_teardown(void)
440 for (j
= 0; j
< vertex_names_len
; ++j
) {
441 SDL_DestroyTexture(vertex_names
[j
]);
447 SDL_DestroyWindow(sdl_win
);
455 /* Record that a frame has been started */
456 int ui_start_frame(void)
460 struct rational
*eij
;
461 struct rational
*eji
;
477 frame_start_ticks
= SDL_GetTicks();
479 /* Draw the damn thing */
480 SDL_SetRenderDrawColor(sdl_renderer
, color_bg
.r
, color_bg
.g
, color_bg
.b
,
482 SDL_RenderClear(sdl_renderer
);
484 for (j
= 0; j
< q
->v_num
; ++j
) {
485 for (i
= 0; i
< j
; ++i
) {
486 /* First, determine if we're looking at a half-edge or a full-edge */
487 eij
= &(q
->e
[i
* q
->v_len
+ j
]);
488 eji
= &(q
->e
[j
* q
->v_len
+ i
]);
495 d
= gcd(q
->v
[i
].fatness
, q
->v
[j
].fatness
);
497 /* This is the (eij)/dj = -(eji)/di condition */
498 if (eij
->p
* q
->v
[i
].fatness
* eji
->q
!= -eji
->p
*
499 q
->v
[j
].fatness
* eij
->q
) {
500 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
505 } else if (abs(eij
->p
) * d
== q
->v
[j
].fatness
*
507 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
512 } else if (2 * abs(eij
->p
) * d
== q
->v
[j
].fatness
*
514 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
520 ret
= SDL_SetRenderDrawColor(sdl_renderer
,
529 "SDL_SetRenderDrawColor(): %s\n"),
534 if ((ret
= internal_to_pixel_xy(q
->v
[i
].x
, q
->v
[i
].y
,
539 if ((ret
= internal_to_pixel_xy(q
->v
[j
].x
, q
->v
[j
].y
,
544 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, cx
, cy
, cx2
,
546 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
552 theta
= (cy2
> cy
) ? M_PI
/ 2.0 : -M_PI
/ 2.0;
554 theta
= atan2f(cy2
- cy
, cx2
- cx
);
563 cx2
= cx
+ arrow_length
* cos(theta
+ arrow_angle
);
564 cy2
= cy
+ arrow_length
* sin(theta
+ arrow_angle
);
566 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, cx
, cy
, cx2
,
568 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
573 if ((ret
= SDL_RenderDrawLine(sdl_renderer
, cx
, cy
, cx
+
574 arrow_length
* cos(theta
-
577 arrow_length
* sin(theta
-
580 fprintf(stderr
, L("SDL_RenderDrawLine(): %s\n"),
587 for (j
= 0; j
< q
->v_num
; ++j
) {
590 if ((ret
= internal_to_pixel_xy(v
->x
, v
->y
, &cx
, &cy
))) {
595 SDL_SetRenderDrawBlendMode(sdl_renderer
, SDL_BLENDMODE_NONE
);
596 SDL_SetRenderDrawColor(sdl_renderer
, color_v
.r
, color_v
.g
,
597 color_v
.b
, color_v
.a
);
598 rho
= node_radius
* v
->fatness
;
603 SDL_RenderFillRect(sdl_renderer
, &r
);
606 SDL_SetRenderDrawBlendMode(sdl_renderer
, SDL_BLENDMODE_BLEND
);
607 SDL_SetRenderDrawColor(sdl_renderer
, color_outline
.r
,
608 color_outline
.g
, color_outline
.b
,
612 r
.w
= 2 * rho
- outline_width
;
614 SDL_RenderFillRect(sdl_renderer
, &r
);
615 r
.x
= cx
+ rho
- outline_width
;
618 r
.h
= 2 * rho
- outline_width
;
619 SDL_RenderFillRect(sdl_renderer
, &r
);
620 r
.x
= cx
- rho
+ outline_width
;
621 r
.y
= cy
+ rho
- outline_width
;
622 r
.w
= 2 * rho
- outline_width
;
624 SDL_RenderFillRect(sdl_renderer
, &r
);
626 r
.y
= cy
- rho
+ outline_width
;
628 r
.h
= 2 * rho
- outline_width
;
629 SDL_RenderFillRect(sdl_renderer
, &r
);
632 if (j
>= vertex_names_len
) {
634 "render_vertex_names() was not called, somehow\n"));
639 if (SDL_QueryTexture(vertex_names
[j
], &dummy_format
,
640 &dummy_access
, &tex_w
, &tex_h
)) {
641 fprintf(stderr
, L("SDL_QueryTexture(): %s\n"),
647 r
.x
= cx
- tex_w
/ 2;
648 r
.y
= cy
- tex_h
/ 2;
651 SDL_RenderCopy(sdl_renderer
, vertex_names
[j
], 0, &r
);
659 /* Draw a frame, possibly sleeping for framelimit */
660 int ui_finish_frame(void)
662 struct ui_event ui_e
= { 0 };
663 SDL_Event sdl_e
= { 0 };
664 Uint32 elapsed_time
= 0;
668 SDL_RenderPresent(sdl_renderer
);
670 /* Handle user input */
671 while (SDL_PollEvent(&sdl_e
) != 0) {
672 switch (sdl_e
.type
) {
674 ui_e
= (struct ui_event
) { .type
= ET_FORCE_QUIT
};
675 ret
= eq_push(&ui_e
);
679 if (sdl_e
.key
.keysym
.sym
== SDLK_q
) {
680 ui_e
= (struct ui_event
) { .type
=
682 ret
= eq_push(&ui_e
);
683 } else if (sdl_e
.key
.keysym
.sym
== SDLK_y
) {
684 ui_e
= (struct ui_event
) { .type
= ET_CONFIRM
};
685 ret
= eq_push(&ui_e
);
686 } else if (sdl_e
.key
.keysym
.sym
== SDLK_n
) {
687 ui_e
= (struct ui_event
) { .type
= ET_DENY
};
688 ret
= eq_push(&ui_e
);
692 case SDL_WINDOWEVENT
:
694 if (sdl_e
.window
.event
== SDL_WINDOWEVENT_RESIZED
||
695 sdl_e
.window
.event
== SDL_WINDOWEVENT_MAXIMIZED
||
696 sdl_e
.window
.event
== SDL_WINDOWEVENT_RESTORED
) {
697 react_to_window_resized();
698 } else if (sdl_e
.window
.event
==
699 SDL_WINDOWEVENT_LEAVE
) {
700 /* This tells the dragging code to not respond */
706 case SDL_MOUSEMOTION
:
708 if (sdl_e
.motion
.state
& SDL_BUTTON_LMASK
) {
709 if (last_mouse_x
>= 0 &&
711 offset_x
+= (sdl_e
.motion
.x
-
713 offset_y
+= (sdl_e
.motion
.y
-
717 last_mouse_x
= sdl_e
.motion
.x
;
718 last_mouse_y
= sdl_e
.motion
.y
;
722 case SDL_MOUSEBUTTONDOWN
:
723 case SDL_MOUSEBUTTONUP
:
725 if (sdl_e
.button
.state
& SDL_BUTTON_LMASK
) {
739 now
= SDL_GetTicks();
741 if (frame_start_ticks
< now
) {
742 elapsed_time
= now
- frame_start_ticks
;
744 if (elapsed_time
< TICKS_PER_FRAME
) {
745 SDL_Delay(TICKS_PER_FRAME
- elapsed_time
);
754 /* Return an event to the main loop */
755 int ui_get_event(struct ui_event
*e
, uint_fast8_t *more
)
758 *more
= eq_head
!= eq_tail
;
763 /* Be told to display a confirm dialog */
764 int ui_confirm_deny(const char *s
)