diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml new file mode 100644 index 0000000..0df1db3 --- /dev/null +++ b/.github/workflows/build_test.yml @@ -0,0 +1,64 @@ +name: Build test +# Based on Allegro's pipeline + +#on: [push, pull_request] + +jobs: + windows_test: + name: Windows (MSYS2) tests + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@main + with: + fetch-depth: 1 + - uses: msys2/setup-msys2@main + - name: Setup + shell: msys2 {0} + run: | + pacman --noconfirm -S mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake make mingw-w64-x86_64-physfs mingw-w64-x86_64-freetype mingw-w64-x86_64-libvorbis mingw-w64-x86_64-flac mingw-w64-x86_64-dumb mingw-w64-x86_64-libtheora mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-opusfile mingw-w64-x86_64-enet mingw-w64-x86_64-libwebp + mkdir build + - name: Configure + shell: msys2 {0} + run: | + cd build + cmake .. -G"MSYS Makefiles" + - name: Build + shell: msys2 {0} + run: | + cd build + make -j4 + + - name: Save build folder as artifact + uses: actions/upload-artifact@main + with: + name: build_folder_windows + path: ${{ runner.workspace }}/build + ubuntu_test: + name: Ubuntu tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@main + with: + fetch-depth: 1 + - name: Setup + run: | + sudo apt-get update + sudo apt-get install -y xvfb libvorbis-dev libtheora-dev libwebp-dev libphysfs-dev libopusfile-dev libdumb1-dev libflac-dev libpulse-dev libgtk-3-dev pandoc libcurl4-nss-dev libenet-dev pulseaudio libasound2-dev libopenal-dev libgl1-mesa-dev libglu-dev; + mkdir build + cd build + - name: Configure + run: | + cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DWANT_SHADERS_GL=on -DWANT_CURL_EXAMPLE=on + - name: Build + run: | + cd build + make -j4 + + - name: Save build folder as artifact + uses: actions/upload-artifact@main + with: + name: build_folder_ubuntu + path: ${{ runner.workspace }}/build \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c8c3d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vs/ +out/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..26f80fa --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.5) +project (AllegroProject) +include(FetchContent) + +FetchContent_Declare( + allegro5 + GIT_REPOSITORY https://github.com/liballeg/allegro5.git + GIT_TAG 5.2.9.1 +) +FetchContent_GetProperties(allegro5) +if(NOT allegro5_POPULATED) + FetchContent_Populate(allegro5) + if (MSVC) + set(SHARED ON) + else() + set(SHARED OFF) + endif() + set(WANT_TESTS OFF) + set(WANT_EXAMPLES OFF) + set(WANT_DEMO OFF) + add_subdirectory(${allegro5_SOURCE_DIR} ${allegro5_BINARY_DIR} EXCLUDE_FROM_ALL) +endif() + +add_executable(al_example src/main.c) +target_include_directories(al_example PUBLIC ${allegro5_SOURCE_DIR}/include) +target_include_directories(al_example PUBLIC ${allegro5_BINARY_DIR}/include) +target_link_libraries(al_example LINK_PUBLIC allegro allegro_main allegro_font allegro_primitives) + +# These include files are typically copied into the correct places via allegro's install +# target, but we do it manually. +file(COPY ${allegro5_SOURCE_DIR}/addons/font/allegro5/allegro_font.h + DESTINATION ${allegro5_SOURCE_DIR}/include/allegro5 +) +file(COPY ${allegro5_SOURCE_DIR}/addons/primitives/allegro5/allegro_primitives.h + DESTINATION ${allegro5_SOURCE_DIR}/include/allegro5 +) diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..61c1714 --- /dev/null +++ b/src/common.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include + +#ifdef ALLEGRO_ANDROID + #include "allegro5/allegro_android.h" +#endif + +void init_platform_specific(void); +void abort_example(char const *format, ...); +void open_log(void); +void open_log_monospace(void); +void close_log(bool wait_for_user); +void log_printf(char const *format, ...); + +void init_platform_specific(void) +{ +#ifdef ALLEGRO_ANDROID + al_install_touch_input(); + al_android_set_apk_file_interface(); +#endif +} + +#ifdef ALLEGRO_POPUP_EXAMPLES + +#include "allegro5/allegro_native_dialog.h" + +ALLEGRO_TEXTLOG *textlog = NULL; + +void abort_example(char const *format, ...) +{ + char str[1024]; + va_list args; + ALLEGRO_DISPLAY *display; + + va_start(args, format); + vsnprintf(str, sizeof str, format, args); + va_end(args); + + if (al_init_native_dialog_addon()) { + display = al_is_system_installed() ? al_get_current_display() : NULL; + al_show_native_message_box(display, "Error", "Cannot run example", str, NULL, 0); + } + else { + fprintf(stderr, "%s", str); + } + exit(1); +} + +void open_log(void) +{ + if (al_init_native_dialog_addon()) { + textlog = al_open_native_text_log("Log", 0); + } +} + +void open_log_monospace(void) +{ + if (al_init_native_dialog_addon()) { + textlog = al_open_native_text_log("Log", ALLEGRO_TEXTLOG_MONOSPACE); + } +} + +void close_log(bool wait_for_user) +{ + if (textlog && wait_for_user) { + ALLEGRO_EVENT_QUEUE *queue = al_create_event_queue(); + al_register_event_source(queue, al_get_native_text_log_event_source( + textlog)); + al_wait_for_event(queue, NULL); + al_destroy_event_queue(queue); + } + + al_close_native_text_log(textlog); + textlog = NULL; +} + +void log_printf(char const *format, ...) +{ + char str[1024]; + va_list args; + va_start(args, format); + vsnprintf(str, sizeof str, format, args); + va_end(args); + al_append_native_text_log(textlog, "%s", str); +} + +#else + +void abort_example(char const *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + exit(1); +} + +void open_log(void) +{ +} + +void open_log_monospace(void) +{ +} + +void close_log(bool wait_for_user) +{ + (void)wait_for_user; +} + +void log_printf(char const *format, ...) +{ + va_list args; + va_start(args, format); + #ifdef ALLEGRO_ANDROID + char x[1024]; + vsnprintf(x, sizeof x, format, args); + ALLEGRO_TRACE_CHANNEL_LEVEL("log", 1)("%s", x); + #else + vprintf(format, args); + #endif + va_end(args); +} + +#endif + +/* vim: set sts=3 sw=3 et: */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..d34166c --- /dev/null +++ b/src/main.c @@ -0,0 +1,496 @@ +/* + * Example program for the Allegro library, by Peter Wang. + * + * This program tests the polygon routines in the primitives addon. + */ + +#define ALLEGRO_UNSTABLE +#include +#include +#include +#include +#include + +#include "common.c" + +#define MAX_VERTICES 64 +#define MAX_POLYGONS 9 +#define RADIUS 5 + +enum { + MODE_POLYLINE = 0, + MODE_POLYGON, + MODE_FILLED_POLYGON, + MODE_FILLED_HOLES, + MODE_MAX +}; + +enum AddHole { + NOT_ADDING_HOLE, + NEW_HOLE, + GROW_HOLE +}; + +typedef struct Vertex Vertex; +struct Vertex { + float x; + float y; +}; + +struct Example { + ALLEGRO_DISPLAY *display; + ALLEGRO_FONT *font; + ALLEGRO_FONT *fontbmp; + ALLEGRO_EVENT_QUEUE *queue; + ALLEGRO_BITMAP *dbuf; + ALLEGRO_COLOR bg; + ALLEGRO_COLOR fg; + Vertex vertices[MAX_VERTICES]; + int vertex_polygon[MAX_VERTICES]; + int vertex_count; + int cur_vertex; /* -1 = none */ + int cur_polygon; + int mode; + ALLEGRO_LINE_CAP cap_style; + ALLEGRO_LINE_JOIN join_style; + float thickness; + float miter_limit; + bool software; + float zoom; + float scroll_x, scroll_y; + enum AddHole add_hole; +}; + +static struct Example ex; + +static void reset(void) +{ + ex.vertex_count = 0; + ex.cur_vertex = -1; + ex.cur_polygon = 0; + ex.cap_style = ALLEGRO_LINE_CAP_NONE; + ex.join_style = ALLEGRO_LINE_JOIN_NONE; + ex.thickness = 1.0f; + ex.miter_limit = 1.0f; + ex.software = false; + ex.zoom = 1; + ex.scroll_x = 0; + ex.scroll_y = 0; + ex.add_hole = NOT_ADDING_HOLE; +} + +static void transform(float *x, float *y) +{ + *x /= ex.zoom; + *y /= ex.zoom; + *x -= ex.scroll_x; + *y -= ex.scroll_y; +} + +static int hit_vertex(int mx, int my) +{ + int i; + + for (i = 0; i < ex.vertex_count; i++) { + int dx = ex.vertices[i].x - mx; + int dy = ex.vertices[i].y - my; + int dd = dx*dx + dy*dy; + if (dd <= RADIUS * RADIUS) + return i; + } + + return -1; +} + +static void lclick(int mx, int my) +{ + ex.cur_vertex = hit_vertex(mx, my); + + if (ex.cur_vertex < 0 && ex.vertex_count < MAX_VERTICES) { + int i = ex.vertex_count++; + ex.vertices[i].x = mx; + ex.vertices[i].y = my; + ex.cur_vertex = i; + + if (ex.add_hole == NEW_HOLE && ex.cur_polygon < MAX_POLYGONS) { + ex.cur_polygon++; + ex.add_hole = GROW_HOLE; + } + + ex.vertex_polygon[i] = ex.cur_polygon; + } +} + +static void rclick(int mx, int my) +{ + const int i = hit_vertex(mx, my); + if (i >= 0 && ex.add_hole == NOT_ADDING_HOLE) { + ex.vertex_count--; + memmove(&ex.vertices[i], &ex.vertices[i + 1], + sizeof(Vertex) * (ex.vertex_count - i)); + memmove(&ex.vertex_polygon[i], &ex.vertex_polygon[i + 1], + sizeof(int) * (ex.vertex_count - i)); + } + ex.cur_vertex = -1; +} + +static void drag(int mx, int my) +{ + if (ex.cur_vertex >= 0) { + ex.vertices[ex.cur_vertex].x = mx; + ex.vertices[ex.cur_vertex].y = my; + } +} + +static void scroll(int mx, int my) +{ + ex.scroll_x += mx; + ex.scroll_y += my; +} + +static const char *join_style_to_string(ALLEGRO_LINE_JOIN x) +{ + switch (x) { + case ALLEGRO_LINE_JOIN_NONE: + return "NONE"; + case ALLEGRO_LINE_JOIN_BEVEL: + return "BEVEL"; + case ALLEGRO_LINE_JOIN_ROUND: + return "ROUND"; + case ALLEGRO_LINE_JOIN_MITER: + return "MITER"; + default: + return "unknown"; + } +} + +static const char *cap_style_to_string(ALLEGRO_LINE_CAP x) +{ + switch (x) { + case ALLEGRO_LINE_CAP_NONE: + return "NONE"; + case ALLEGRO_LINE_CAP_SQUARE: + return "SQUARE"; + case ALLEGRO_LINE_CAP_ROUND: + return "ROUND"; + case ALLEGRO_LINE_CAP_TRIANGLE: + return "TRIANGLE"; + case ALLEGRO_LINE_CAP_CLOSED: + return "CLOSED"; + default: + return "unknown"; + } +} + +static ALLEGRO_FONT *choose_font(void) +{ + return (ex.software) ? ex.fontbmp : ex.font; +} + +static void draw_vertices(void) +{ + ALLEGRO_FONT *f = choose_font(); + ALLEGRO_COLOR vertc = al_map_rgba_f(0.7, 0, 0, 0.7); + ALLEGRO_COLOR textc = al_map_rgba_f(0, 0, 0, 0.7); + int i; + + for (i = 0; i < ex.vertex_count; i++) { + float x = ex.vertices[i].x; + float y = ex.vertices[i].y; + + al_draw_filled_circle(x, y, RADIUS, vertc); + al_draw_textf(f, textc, x + RADIUS, y + RADIUS, 0, "%d", i); + } +} + +static void compute_polygon_vertex_counts( + int polygon_vertex_count[MAX_POLYGONS + 1]) +{ + int i; + + /* This also implicitly terminates the array with a zero. */ + memset(polygon_vertex_count, 0, sizeof(int) * (MAX_POLYGONS + 1)); + for (i = 0; i < ex.vertex_count; i++) { + const int poly = ex.vertex_polygon[i]; + polygon_vertex_count[poly]++; + } +} + +static void draw_all(void) +{ + ALLEGRO_FONT *f = choose_font(); + ALLEGRO_COLOR textc = al_map_rgb(0, 0, 0); + float texth = al_get_font_line_height(f) * 1.5; + float textx = 5; + float texty = 5; + ALLEGRO_TRANSFORM t; + ALLEGRO_COLOR holec; + + al_clear_to_color(ex.bg); + + al_identity_transform(&t); + al_translate_transform(&t, ex.scroll_x, ex.scroll_y); + al_scale_transform(&t, ex.zoom, ex.zoom); + al_use_transform(&t); + + if (ex.mode == MODE_POLYLINE) { + if (ex.vertex_count >= 2) { + al_draw_polyline( + (float *)ex.vertices, sizeof(Vertex), ex.vertex_count, + ex.join_style, ex.cap_style, ex.fg, ex.thickness, ex.miter_limit); + } + } + else if (ex.mode == MODE_FILLED_POLYGON) { + if (ex.vertex_count >= 2) { + al_draw_filled_polygon( + (float *)ex.vertices, ex.vertex_count, ex.fg); + } + } + else if (ex.mode == MODE_POLYGON) { + if (ex.vertex_count >= 2) { + al_draw_polygon( + (float *)ex.vertices, ex.vertex_count, + ex.join_style, ex.fg, ex.thickness, ex.miter_limit); + } + } + else if (ex.mode == MODE_FILLED_HOLES) { + if (ex.vertex_count >= 2) { + int polygon_vertex_count[MAX_POLYGONS + 1]; + compute_polygon_vertex_counts(polygon_vertex_count); + al_draw_filled_polygon_with_holes( + (float *)ex.vertices, polygon_vertex_count, ex.fg); + } + } + + draw_vertices(); + + al_identity_transform(&t); + al_use_transform(&t); + + if (ex.mode == MODE_POLYLINE) { + al_draw_textf(f, textc, textx, texty, 0, + "al_draw_polyline (SPACE)"); + texty += texth; + } + else if (ex.mode == MODE_FILLED_POLYGON) { + al_draw_textf(f, textc, textx, texty, 0, + "al_draw_filled_polygon (SPACE)"); + texty += texth; + } + else if (ex.mode == MODE_POLYGON) { + al_draw_textf(f, textc, textx, texty, 0, + "al_draw_polygon (SPACE)"); + texty += texth; + } + else if (ex.mode == MODE_FILLED_HOLES) { + al_draw_textf(f, textc, textx, texty, 0, + "al_draw_filled_polygon_with_holes (SPACE)"); + texty += texth; + } + + al_draw_textf(f, textc, textx, texty, 0, + "Line join style: %s (J)", join_style_to_string(ex.join_style)); + texty += texth; + + al_draw_textf(f, textc, textx, texty, 0, + "Line cap style: %s (C)", cap_style_to_string(ex.cap_style)); + texty += texth; + + al_draw_textf(f, textc, textx, texty, 0, + "Line thickness: %.2f (+/-)", ex.thickness); + texty += texth; + + al_draw_textf(f, textc, textx, texty, 0, + "Miter limit: %.2f ([/])", ex.miter_limit); + texty += texth; + + al_draw_textf(f, textc, textx, texty, 0, + "Zoom: %.2f (wheel)", ex.zoom); + texty += texth; + + al_draw_textf(f, textc, textx, texty, 0, + "%s (S)", (ex.software ? "Software rendering" : "Hardware rendering")); + texty += texth; + + al_draw_textf(f, textc, textx, texty, 0, + "Reset (R)"); + texty += texth; + + if (ex.add_hole == NOT_ADDING_HOLE) + holec = textc; + else if (ex.add_hole == GROW_HOLE) + holec = al_map_rgb(200, 0, 0); + else + holec = al_map_rgb(0, 200, 0); + al_draw_textf(f, holec, textx, texty, 0, + "Add Hole (%d) (H)", ex.cur_polygon); + texty += texth; +} + +/* Print vertices in a format for the test suite. */ +static void print_vertices(void) +{ + int i; + + for (i = 0; i < ex.vertex_count; i++) { + log_printf("v%-2d= %.2f, %.2f\n", + i, ex.vertices[i].x, ex.vertices[i].y); + } + log_printf("\n"); +} + +int main(int argc, char **argv) +{ + ALLEGRO_EVENT event; + bool have_touch_input; + bool mdown = false; + + (void)argc; + (void)argv; + + if (!al_init()) { + abort_example("Could not init Allegro.\n"); + } + + open_log(); + + if (!al_init_primitives_addon()) { + abort_example("Could not init primitives.\n"); + } + al_init_font_addon(); + al_install_mouse(); + al_install_keyboard(); + have_touch_input = al_install_touch_input(); + + ex.display = al_create_display(800, 600); + if (!ex.display) { + abort_example("Error creating display\n"); + } + + ex.font = al_create_builtin_font(); + if (!ex.font) { + abort_example("Error creating builtin font\n"); + } + + al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP); + ex.dbuf = al_create_bitmap(800, 600); + ex.fontbmp = al_create_builtin_font(); + if (!ex.fontbmp) { + abort_example("Error creating builtin font\n"); + } + + ex.queue = al_create_event_queue(); + al_register_event_source(ex.queue, al_get_keyboard_event_source()); + al_register_event_source(ex.queue, al_get_mouse_event_source()); + al_register_event_source(ex.queue, al_get_display_event_source(ex.display)); + if (have_touch_input) { + al_register_event_source(ex.queue, al_get_touch_input_event_source()); + al_register_event_source(ex.queue, al_get_touch_input_mouse_emulation_event_source()); + } + + ex.bg = al_map_rgba_f(1, 1, 0.9, 1); + ex.fg = al_map_rgba_f(0, 0.5, 1, 1); + + reset(); + + for (;;) { + if (al_is_event_queue_empty(ex.queue)) { + if (ex.software) { + al_set_target_bitmap(ex.dbuf); + draw_all(); + al_set_target_backbuffer(ex.display); + al_draw_bitmap(ex.dbuf, 0, 0, 0); + } + else { + al_set_target_backbuffer(ex.display); + draw_all(); + } + al_flip_display(); + } + + al_wait_for_event(ex.queue, &event); + if (event.type == ALLEGRO_EVENT_DISPLAY_CLOSE) + break; + if (event.type == ALLEGRO_EVENT_KEY_CHAR) { + if (event.keyboard.keycode == ALLEGRO_KEY_ESCAPE) + break; + switch (toupper(event.keyboard.unichar)) { + case ' ': + if (++ex.mode >= MODE_MAX) + ex.mode = 0; + break; + case 'J': + if (++ex.join_style > ALLEGRO_LINE_JOIN_MITER) + ex.join_style = ALLEGRO_LINE_JOIN_NONE; + break; + case 'C': + if (++ex.cap_style > ALLEGRO_LINE_CAP_CLOSED) + ex.cap_style = ALLEGRO_LINE_CAP_NONE; + break; + case '+': + ex.thickness += 0.25f; + break; + case '-': + ex.thickness -= 0.25f; + if (ex.thickness <= 0.0f) + ex.thickness = 0.0f; + break; + case '[': + ex.miter_limit -= 0.1f; + if (ex.miter_limit < 0.0f) + ex.miter_limit = 0.0f; + break; + case ']': + ex.miter_limit += 0.1f; + if (ex.miter_limit >= 10.0f) + ex.miter_limit = 10.0f; + break; + case 'S': + ex.software = !ex.software; + break; + case 'R': + reset(); + break; + case 'P': + print_vertices(); + break; + case 'H': + ex.add_hole = NEW_HOLE; + break; + } + } + if (event.type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN) { + float x = event.mouse.x, y = event.mouse.y; + transform(&x, &y); + if (event.mouse.button == 1) + lclick(x, y); + if (event.mouse.button == 2) + rclick(x, y); + if (event.mouse.button == 3) + mdown = true; + } + if (event.type == ALLEGRO_EVENT_MOUSE_BUTTON_UP) { + ex.cur_vertex = -1; + if (event.mouse.button == 3) + mdown = false; + } + if (event.type == ALLEGRO_EVENT_MOUSE_AXES) { + float x = event.mouse.x, y = event.mouse.y; + transform(&x, &y); + + if (mdown) + scroll(event.mouse.dx, event.mouse.dy); + else + drag(x, y); + + ex.zoom *= pow(0.9, event.mouse.dz); + } + } + + al_destroy_display(ex.display); + close_log(true); + + return 0; +} + +/* vim: set sts=3 sw=3 et: */