1 /*
2  * Nuklear - 1.32.0 - public domain
3  * no warrenty implied; use at your own risk.
4  * authored from 2015-2016 by Micha Mettke
5  */
6 /*
7  * ==============================================================
8  *
9  *                              API
10  *
11  * ===============================================================
12  */
13 #ifndef NK_ALLEGRO5_H_
14 #define NK_ALLEGRO5_H_
15 
16 #include <string.h>
17 #include <allegro5/allegro.h>
18 #include <allegro5/allegro_image.h>
19 #include <allegro5/allegro_primitives.h>
20 #include <allegro5/allegro_font.h>
21 #include <allegro5/allegro_ttf.h>
22 
23 typedef struct NkAllegro5Font NkAllegro5Font;
24 NK_API struct nk_context*     nk_allegro5_init(NkAllegro5Font *font, ALLEGRO_DISPLAY *dsp,
25                                   unsigned int width, unsigned int height);
26 NK_API int                    nk_allegro5_handle_event(ALLEGRO_EVENT *ev);
27 NK_API void                   nk_allegro5_shutdown(void);
28 NK_API void                   nk_allegro5_render(void);
29 
30 NK_API struct nk_image*       nk_allegro5_create_image(const char* file_name);
31 NK_API void                   nk_allegro5_del_image(struct nk_image* image);
32 
33 /* Fonts. We wrap normal allegro fonts in some nuklear book keeping */
34 NK_API NkAllegro5Font*        nk_allegro5_font_create_from_file(const char *file_name, int font_size, int flags);
35 NK_API void                   nk_allegro5_font_del(NkAllegro5Font *font);
36 /* NOTE : just use NkAllegro5Font instead of nk_user_font,
37     since the former just extends the latter*/
38 NK_API void                   nk_allegro5_font_set_font(NkAllegro5Font *font);
39 
40 #endif
41 /*
42  * ==============================================================
43  *
44  *                          IMPLEMENTATION
45  *
46  * ===============================================================
47  */
48 #ifdef NK_ALLEGRO5_IMPLEMENTATION
49 
50 #ifndef NK_ALLEGRO5_TEXT_MAX
51 #define NK_ALLEGRO5_TEXT_MAX 256
52 #endif
53 
54 
55 struct NkAllegro5Font {
56     struct nk_user_font nk;
57     ALLEGRO_FONT *font;
58 };
59 
60 static struct nk_allegro5 {
61     ALLEGRO_DISPLAY *dsp;
62     unsigned int width;
63     unsigned int height;
64     int is_touch_down;
65     int touch_down_id;
66     struct nk_context ctx;
67     struct nk_buffer cmds;
68 } allegro5;
69 
70 
nk_allegro5_create_image(const char * file_name)71 NK_API struct nk_image* nk_allegro5_create_image(const char* file_name)
72 {
73     ALLEGRO_BITMAP *bitmap;
74     struct nk_image *image;
75     if (!al_init_image_addon()) {
76         fprintf(stdout, "Unable to initialize required allegro5 image addon\n");
77         exit(1);
78     }
79 
80     bitmap = al_load_bitmap(file_name);
81     if (bitmap == NULL) {
82         fprintf(stdout, "Unable to load image file: %s\n", file_name);
83         return NULL;
84     }
85 
86     image = (struct nk_image*)calloc(1, sizeof(struct nk_image));
87     image->handle.ptr = bitmap;
88     image->w = al_get_bitmap_width(bitmap);
89     image->h = al_get_bitmap_height(bitmap);
90     return image;
91 }
92 
nk_allegro5_del_image(struct nk_image * image)93 NK_API void nk_allegro5_del_image(struct nk_image* image)
94 {
95     if(!image) return;
96     al_destroy_bitmap(image->handle.ptr);
97     free(image);
98 }
99 
100 static float
nk_allegro5_font_get_text_width(nk_handle handle,float height,const char * text,int len)101 nk_allegro5_font_get_text_width(nk_handle handle, float height, const char *text, int len)
102 {
103     float width;
104     char *strcpy;
105     NkAllegro5Font *font = (NkAllegro5Font*)handle.ptr;
106     NK_UNUSED(height);
107     if (!font || !text) {
108         return 0;
109     }
110     /* We must copy into a new buffer with exact length null-terminated
111        as nuklear uses variable size buffers and al_get_text_width doesn't
112        accept a length, it infers length from null-termination
113        (which is unsafe API design by allegro devs!) */
114     strcpy = malloc(len + 1);
115     strncpy(strcpy, text, len);
116     strcpy[len] = '\0';
117     width = al_get_text_width(font->font, strcpy);
118     free(strcpy);
119     return width;
120 }
121 
122 /* Flags are identical to al_load_font() flags argument */
123 NK_API NkAllegro5Font*
nk_allegro5_font_create_from_file(const char * file_name,int font_size,int flags)124 nk_allegro5_font_create_from_file(const char *file_name, int font_size, int flags)
125 {
126     NkAllegro5Font *font;
127     if (!al_init_image_addon()) {
128         fprintf(stdout, "Unable to initialize required allegro5 image addon\n");
129         exit(1);
130     }
131     if (!al_init_font_addon()) {
132         fprintf(stdout, "Unable to initialize required allegro5 font addon\n");
133         exit(1);
134     }
135     if (!al_init_ttf_addon()) {
136         fprintf(stdout, "Unable to initialize required allegro5 TTF font addon\n");
137         exit(1);
138     }
139     font = (NkAllegro5Font*)calloc(1, sizeof(NkAllegro5Font));
140 
141     font->font = al_load_font(file_name, font_size, flags);
142     if (font->font == NULL) {
143         fprintf(stdout, "Unable to load font file: %s\n", file_name);
144         return NULL;
145     }
146     font->nk.userdata = nk_handle_ptr(font);
147     font->nk.height = (float)al_get_font_line_height(font->font);
148     font->nk.width = nk_allegro5_font_get_text_width;
149     return font;
150 }
151 
152 NK_API void
nk_allegro5_font_set_font(NkAllegro5Font * allegro5font)153 nk_allegro5_font_set_font(NkAllegro5Font *allegro5font)
154 {
155     struct nk_user_font *font = &allegro5font->nk;
156     nk_style_set_font(&allegro5.ctx, font);
157 }
158 
159 NK_API void
nk_allegro5_font_del(NkAllegro5Font * font)160 nk_allegro5_font_del(NkAllegro5Font *font)
161 {
162     if(!font) return;
163     al_destroy_font(font->font);
164     free(font);
165 }
166 
167 static ALLEGRO_COLOR
nk_color_to_allegro_color(struct nk_color color)168 nk_color_to_allegro_color(struct nk_color color)
169 {
170     return al_map_rgba((unsigned char)color.r, (unsigned char)color.g,
171                 (unsigned char)color.b, (unsigned char)color.a);
172 }
173 
174 NK_API void
nk_allegro5_render()175 nk_allegro5_render()
176 {
177     const struct nk_command *cmd;
178 
179     al_set_target_backbuffer(allegro5.dsp);
180 
181     nk_foreach(cmd, &allegro5.ctx)
182     {
183         ALLEGRO_COLOR color;
184         switch (cmd->type) {
185         case NK_COMMAND_NOP: break;
186         case NK_COMMAND_SCISSOR: {
187             const struct nk_command_scissor *s =(const struct nk_command_scissor*)cmd;
188             al_set_clipping_rectangle((int)s->x, (int)s->y, (int)s->w, (int)s->h);
189         } break;
190         case NK_COMMAND_LINE: {
191             const struct nk_command_line *l = (const struct nk_command_line *)cmd;
192             color = nk_color_to_allegro_color(l->color);
193             al_draw_line((float)l->begin.x, (float)l->begin.y, (float)l->end.x,
194                 (float)l->end.y, color, (float)l->line_thickness);
195         } break;
196         case NK_COMMAND_RECT: {
197             const struct nk_command_rect *r = (const struct nk_command_rect *)cmd;
198             color = nk_color_to_allegro_color(r->color);
199             al_draw_rounded_rectangle((float)r->x, (float)r->y, (float)(r->x + r->w),
200                 (float)(r->y + r->h), (float)r->rounding, (float)r->rounding, color,
201                 (float)r->line_thickness);
202         } break;
203         case NK_COMMAND_RECT_FILLED: {
204             const struct nk_command_rect_filled *r = (const struct nk_command_rect_filled *)cmd;
205             color = nk_color_to_allegro_color(r->color);
206             al_draw_filled_rounded_rectangle((float)r->x, (float)r->y,
207                 (float)(r->x + r->w), (float)(r->y + r->h), (float)r->rounding,
208                 (float)r->rounding, color);
209         } break;
210         case NK_COMMAND_CIRCLE: {
211             float xr, yr;
212             const struct nk_command_circle *c = (const struct nk_command_circle *)cmd;
213             color = nk_color_to_allegro_color(c->color);
214             xr = (float)c->w/2;
215             yr = (float)c->h/2;
216             al_draw_ellipse(((float)(c->x)) + xr, ((float)c->y) + yr,
217                 xr, yr, color, (float)c->line_thickness);
218         } break;
219         case NK_COMMAND_CIRCLE_FILLED: {
220             float xr, yr;
221             const struct nk_command_circle_filled *c = (const struct nk_command_circle_filled *)cmd;
222             color = nk_color_to_allegro_color(c->color);
223             xr = (float)c->w/2;
224             yr = (float)c->h/2;
225             al_draw_filled_ellipse(((float)(c->x)) + xr, ((float)c->y) + yr,
226                 xr, yr, color);
227         } break;
228         case NK_COMMAND_TRIANGLE: {
229             const struct nk_command_triangle*t = (const struct nk_command_triangle*)cmd;
230             color = nk_color_to_allegro_color(t->color);
231             al_draw_triangle((float)t->a.x, (float)t->a.y, (float)t->b.x, (float)t->b.y,
232                 (float)t->c.x, (float)t->c.y, color, (float)t->line_thickness);
233         } break;
234         case NK_COMMAND_TRIANGLE_FILLED: {
235             const struct nk_command_triangle_filled *t = (const struct nk_command_triangle_filled *)cmd;
236             color = nk_color_to_allegro_color(t->color);
237             al_draw_filled_triangle((float)t->a.x, (float)t->a.y, (float)t->b.x,
238                 (float)t->b.y, (float)t->c.x, (float)t->c.y, color);
239         } break;
240         case NK_COMMAND_POLYGON: {
241             int i;
242             float *vertices;
243             const struct nk_command_polygon *p = (const struct nk_command_polygon*)cmd;
244             vertices = calloc(p->point_count * 2, sizeof(float));
245             color = nk_color_to_allegro_color(p->color);
246             for (i = 0; i < p->point_count; i++) {
247                 vertices[i*2] = p->points[i].x;
248                 vertices[(i*2) + 1] = p->points[i].y;
249             }
250             al_draw_polyline(vertices, (2 * sizeof(float)),
251                 (int)p->point_count, ALLEGRO_LINE_JOIN_ROUND, ALLEGRO_LINE_CAP_CLOSED,
252                 color, (float)p->line_thickness, 0.0);
253             free(vertices);
254         } break;
255         case NK_COMMAND_POLYGON_FILLED: {
256             int i, j = 0;
257             float *vertices;
258             const struct nk_command_polygon_filled *p = (const struct nk_command_polygon_filled *)cmd;
259             vertices = calloc(p->point_count * 2, sizeof(float));
260             color = nk_color_to_allegro_color(p->color);
261             for (i = p->point_count - 1; i >= 0; i--) {
262                 vertices[j++] = p->points[i].x;
263                 vertices[j++] = p->points[i].y;
264             }
265             al_draw_filled_polygon(vertices, (int)p->point_count, color);
266             free(vertices);
267         } break;
268         case NK_COMMAND_POLYLINE: {
269             int i;
270             float *vertices;
271             const struct nk_command_polyline *p = (const struct nk_command_polyline *)cmd;
272             vertices = calloc(p->point_count * 2, sizeof(float));
273             color = nk_color_to_allegro_color(p->color);
274             for (i = 0; i < p->point_count; i++) {
275                 vertices[i*2] = p->points[i].x;
276                 vertices[(i*2) + 1] = p->points[i].y;
277             }
278             al_draw_polyline(vertices, (2 * sizeof(float)),
279                 (int)p->point_count, ALLEGRO_LINE_JOIN_ROUND, ALLEGRO_LINE_CAP_ROUND,
280                 color, (float)p->line_thickness, 0.0);
281             free(vertices);
282         } break;
283         case NK_COMMAND_TEXT: {
284             NkAllegro5Font *font;
285             const struct nk_command_text *t = (const struct nk_command_text*)cmd;
286             color = nk_color_to_allegro_color(t->foreground);
287             font = (NkAllegro5Font*)t->font->userdata.ptr;
288             al_draw_text(font->font,
289                 color, (float)t->x, (float)t->y, 0,
290                 (const char*)t->string);
291         } break;
292         case NK_COMMAND_CURVE: {
293             float points[8];
294             const struct nk_command_curve *q = (const struct nk_command_curve *)cmd;
295             color = nk_color_to_allegro_color(q->color);
296             points[0] = (float)q->begin.x;
297             points[1] = (float)q->begin.y;
298             points[2] = (float)q->ctrl[0].x;
299             points[3] = (float)q->ctrl[0].y;
300             points[4] = (float)q->ctrl[1].x;
301             points[5] = (float)q->ctrl[1].y;
302             points[6] = (float)q->end.x;
303             points[7] = (float)q->end.y;
304             al_draw_spline(points, color, (float)q->line_thickness);
305         } break;
306         case NK_COMMAND_ARC: {
307             const struct nk_command_arc *a = (const struct nk_command_arc *)cmd;
308             color = nk_color_to_allegro_color(a->color);
309             al_draw_pieslice((float)a->cx, (float)a->cy, (float)a->r, a->a[0],
310                 a->a[1], color, (float)a->line_thickness);
311         } break;
312         case NK_COMMAND_ARC_FILLED: {
313             const struct nk_command_arc_filled *a = (const struct nk_command_arc_filled *)cmd;
314             color = nk_color_to_allegro_color(a->color);
315             al_draw_filled_pieslice((float)a->cx, (float)a->cy, (float)a->r, a->a[0],
316                 a->a[1], color);
317         } break;
318         case NK_COMMAND_IMAGE: {
319             const struct nk_command_image *i = (const struct nk_command_image *)cmd;
320             al_draw_bitmap_region(i->img.handle.ptr, 0, 0, i->w, i->h, i->x, i->y, 0);
321         } break;
322         case NK_COMMAND_RECT_MULTI_COLOR:
323         default: break;
324         }
325     }
326     nk_clear(&allegro5.ctx);
327 }
328 
329 NK_API int
nk_allegro5_handle_event(ALLEGRO_EVENT * ev)330 nk_allegro5_handle_event(ALLEGRO_EVENT *ev)
331 {
332     struct nk_context *ctx = &allegro5.ctx;
333     switch (ev->type) {
334         case ALLEGRO_EVENT_DISPLAY_RESIZE: {
335             allegro5.width = (unsigned int)ev->display.width;
336             allegro5.height = (unsigned int)ev->display.height;
337             al_acknowledge_resize(ev->display.source);
338             return 1;
339         } break;
340         case ALLEGRO_EVENT_MOUSE_AXES: {
341             nk_input_motion(ctx, ev->mouse.x, ev->mouse.y);
342             if (ev->mouse.dz != 0) {
343                 nk_input_scroll(ctx, nk_vec2(0,(float)ev->mouse.dz / al_get_mouse_wheel_precision()));
344             }
345             return 1;
346         } break;
347         case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN:
348         case ALLEGRO_EVENT_MOUSE_BUTTON_UP: {
349             int button = NK_BUTTON_LEFT;
350             if (ev->mouse.button == 2) {
351                 button = NK_BUTTON_RIGHT;
352             }
353             else if (ev->mouse.button == 3) {
354                 button = NK_BUTTON_MIDDLE;
355             }
356             nk_input_button(ctx, button, ev->mouse.x, ev->mouse.y, ev->type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN);
357             return 1;
358         } break;
359         /* This essentially converts touch events to mouse events */
360         case ALLEGRO_EVENT_TOUCH_BEGIN:
361         case ALLEGRO_EVENT_TOUCH_END: {
362             /* We only acknowledge one touch at a time. Otherwise, each touch
363                would be manipulating multiple nuklear elements, as if there
364                were multiple mouse cursors */
365             if (allegro5.is_touch_down && allegro5.touch_down_id != ev->touch.id) {
366                 return 0;
367             }
368             if (ev->type == ALLEGRO_EVENT_TOUCH_BEGIN) {
369                 allegro5.is_touch_down = 1;
370                 allegro5.touch_down_id = ev->touch.id;
371                 /* FIXME: This is a hack to properly simulate
372                    touches as a mouse with nuklear. If you instantly jump
373                    from one place to another without an nk_input_end(), it
374                    confuses the nuklear state. nuklear expects smooth mouse
375                    movements, which is unlike a touch screen */
376                 nk_input_motion(ctx, (int)ev->touch.x, (int)ev->touch.y);
377                 nk_input_end(ctx);
378                 nk_input_begin(ctx);
379             }
380             else {
381                 allegro5.is_touch_down = 0;
382                 allegro5.touch_down_id = -1;
383             }
384             nk_input_button(ctx, NK_BUTTON_LEFT, (int)ev->touch.x, (int)ev->touch.y, ev->type == ALLEGRO_EVENT_TOUCH_BEGIN);
385             return 1;
386         } break;
387         case ALLEGRO_EVENT_TOUCH_MOVE: {
388             /* Only acknowledge movements of a single touch, we are
389                simulating a mouse cursor */
390             if (!allegro5.is_touch_down || allegro5.touch_down_id != ev->touch.id) {
391                 return 0;
392             }
393             nk_input_motion(ctx, (int)ev->touch.x, (int)ev->touch.y);
394             return 1;
395         } break;
396         case ALLEGRO_EVENT_KEY_DOWN:
397         case ALLEGRO_EVENT_KEY_UP: {
398             int kc = ev->keyboard.keycode;
399             int down = ev->type == ALLEGRO_EVENT_KEY_DOWN;
400 
401             if (kc == ALLEGRO_KEY_LSHIFT || kc == ALLEGRO_KEY_RSHIFT) nk_input_key(ctx, NK_KEY_SHIFT, down);
402             else if (kc == ALLEGRO_KEY_DELETE)    nk_input_key(ctx, NK_KEY_DEL, down);
403             else if (kc == ALLEGRO_KEY_ENTER)     nk_input_key(ctx, NK_KEY_ENTER, down);
404             else if (kc == ALLEGRO_KEY_TAB)       nk_input_key(ctx, NK_KEY_TAB, down);
405             else if (kc == ALLEGRO_KEY_LEFT)      nk_input_key(ctx, NK_KEY_LEFT, down);
406             else if (kc == ALLEGRO_KEY_RIGHT)     nk_input_key(ctx, NK_KEY_RIGHT, down);
407             else if (kc == ALLEGRO_KEY_UP)        nk_input_key(ctx, NK_KEY_UP, down);
408             else if (kc == ALLEGRO_KEY_DOWN)      nk_input_key(ctx, NK_KEY_DOWN, down);
409             else if (kc == ALLEGRO_KEY_BACKSPACE) nk_input_key(ctx, NK_KEY_BACKSPACE, down);
410             else if (kc == ALLEGRO_KEY_ESCAPE)    nk_input_key(ctx, NK_KEY_TEXT_RESET_MODE, down);
411             else if (kc == ALLEGRO_KEY_PGUP)      nk_input_key(ctx, NK_KEY_SCROLL_UP, down);
412             else if (kc == ALLEGRO_KEY_PGDN)      nk_input_key(ctx, NK_KEY_SCROLL_DOWN, down);
413             else if (kc == ALLEGRO_KEY_HOME) {
414                 nk_input_key(ctx, NK_KEY_TEXT_START, down);
415                 nk_input_key(ctx, NK_KEY_SCROLL_START, down);
416             } else if (kc == ALLEGRO_KEY_END) {
417                 nk_input_key(ctx, NK_KEY_TEXT_END, down);
418                 nk_input_key(ctx, NK_KEY_SCROLL_END, down);
419             }
420             return 1;
421         } break;
422         case ALLEGRO_EVENT_KEY_CHAR: {
423             int kc = ev->keyboard.keycode;
424             int control_mask = (ev->keyboard.modifiers & ALLEGRO_KEYMOD_CTRL) ||
425                                (ev->keyboard.modifiers & ALLEGRO_KEYMOD_COMMAND);
426 
427             if (kc == ALLEGRO_KEY_C && control_mask) {
428                 nk_input_key(ctx, NK_KEY_COPY, 1);
429             } else if (kc == ALLEGRO_KEY_V && control_mask) {
430                 nk_input_key(ctx, NK_KEY_PASTE, 1);
431             } else if (kc == ALLEGRO_KEY_X && control_mask) {
432                 nk_input_key(ctx, NK_KEY_CUT, 1);
433             } else if (kc == ALLEGRO_KEY_Z && control_mask) {
434                 nk_input_key(ctx, NK_KEY_TEXT_UNDO, 1);
435             } else if (kc == ALLEGRO_KEY_R && control_mask) {
436                 nk_input_key(ctx, NK_KEY_TEXT_REDO, 1);
437             } else if (kc == ALLEGRO_KEY_A && control_mask) {
438                 nk_input_key(ctx, NK_KEY_TEXT_SELECT_ALL, 1);
439             } else {
440                 if (kc != ALLEGRO_KEY_BACKSPACE &&
441                     kc != ALLEGRO_KEY_LEFT &&
442                     kc != ALLEGRO_KEY_RIGHT &&
443                     kc != ALLEGRO_KEY_UP &&
444                     kc != ALLEGRO_KEY_DOWN &&
445                     kc != ALLEGRO_KEY_HOME &&
446                     kc != ALLEGRO_KEY_DELETE &&
447                     kc != ALLEGRO_KEY_ENTER &&
448                     kc != ALLEGRO_KEY_END &&
449                     kc != ALLEGRO_KEY_ESCAPE &&
450                     kc != ALLEGRO_KEY_PGDN &&
451                     kc != ALLEGRO_KEY_PGUP) {
452                     nk_input_unicode(ctx, ev->keyboard.unichar);
453                 }
454             }
455             return 1;
456         } break;
457         default: return 0; break;
458     }
459 }
460 
461 NK_INTERN void
nk_allegro5_clipboard_paste(nk_handle usr,struct nk_text_edit * edit)462 nk_allegro5_clipboard_paste(nk_handle usr, struct nk_text_edit *edit)
463 {
464     char *text = al_get_clipboard_text(allegro5.dsp);
465     if (text) nk_textedit_paste(edit, text, nk_strlen(text));
466     (void)usr;
467     al_free(text);
468 }
469 
470 NK_INTERN void
nk_allegro5_clipboard_copy(nk_handle usr,const char * text,int len)471 nk_allegro5_clipboard_copy(nk_handle usr, const char *text, int len)
472 {
473     char *str = 0;
474     (void)usr;
475     if (!len) return;
476     str = (char*)malloc((size_t)len+1);
477     if (!str) return;
478     memcpy(str, text, (size_t)len);
479     str[len] = '\0';
480     al_set_clipboard_text(allegro5.dsp, str);
481     free(str);
482 }
483 
484 NK_API struct nk_context*
nk_allegro5_init(NkAllegro5Font * allegro5font,ALLEGRO_DISPLAY * dsp,unsigned int width,unsigned int height)485 nk_allegro5_init(NkAllegro5Font *allegro5font, ALLEGRO_DISPLAY *dsp,
486     unsigned int width, unsigned int height)
487 {
488     struct nk_user_font *font;
489     if (!al_init_primitives_addon()) {
490         fprintf(stdout, "Unable to initialize required allegro5 primitives addon\n");
491         exit(1);
492     }
493 
494     font = &allegro5font->nk;
495 
496     allegro5.dsp = dsp;
497     allegro5.width = width;
498     allegro5.height = height;
499     allegro5.is_touch_down = 0;
500     allegro5.touch_down_id = -1;
501 
502     nk_init_default(&allegro5.ctx, font);
503     allegro5.ctx.clip.copy = nk_allegro5_clipboard_copy;
504     allegro5.ctx.clip.paste = nk_allegro5_clipboard_paste;
505     allegro5.ctx.clip.userdata = nk_handle_ptr(0);
506     return &allegro5.ctx;
507 }
508 
509 NK_API
nk_allegro5_shutdown(void)510 void nk_allegro5_shutdown(void)
511 {
512     nk_free(&allegro5.ctx);
513     memset(&allegro5, 0, sizeof(allegro5));
514 }
515 
516 #endif /* NK_ALLEGRO5_IMPLEMENTATION */
517 
518