diff --git a/demo/src/main.c b/demo/src/main.c index 7b363ff..3966402 100644 --- a/demo/src/main.c +++ b/demo/src/main.c @@ -60,9 +60,16 @@ void draw() { point(mouseX(), mouseY()); } +void deinit() { + soundDestroy(beep); + groupDestroy(edges); + groupDestroy(movement); +} + int main() { worldSetInit(&init); worldSetDraw(&draw); + worldSetDeinit(&deinit); worldRun(); return 0; } diff --git a/src/animation.c b/src/animation.c index 967c391..05cf557 100644 --- a/src/animation.c +++ b/src/animation.c @@ -1,5 +1,4 @@ -#include #include #include "private.h" @@ -33,23 +32,16 @@ static HeliAnimation* load(const char *path) { HeliAnimation *a = calloc(1, sizeof(*a)); a->path = heliStringCopy(path); - GDir *dir = g_dir_open(path, 0, NULL); - if (dir) { - HeliArray files; - while(TRUE) { - const char* name = g_dir_read_name(dir); - if (!name) break; - char *p = heliStringConcat3(path, "/", name); - heliStringmapAdd(&files, p, NULL, NULL); + Directory d = openDirectory(path); + if (d) { + int count = directoryGetCount(d); + for(int i = 0; i < count; ++i) { + char *p = heliStringConcat3(path, "/", directoryGet(d, i)); + cairo_surface_t *frame = loadFrame(p); free(p); - } - g_dir_close(dir); - - for(int i = 0; i < files.count; ++i) { - cairo_surface_t *frame = loadFrame( (char*)files.items[i].key ); if (frame) heliArrayInsert(&a->frames, -1, frame, (HeliFreeCallback)&cairo_surface_destroy); } - heliArrayDestroy(&files); + closeDirectory(d); } else { cairo_surface_t *frame = loadFrame(path); if (frame) { diff --git a/src/array.c b/src/array.c index d6b56d2..20ff47e 100644 --- a/src/array.c +++ b/src/array.c @@ -9,7 +9,7 @@ void heliPairInit(HeliPair *p) { void heliPairDestroy(HeliPair *p) { if (p->key && p->freeKey) p->freeKey(p->key); - if (p->value && p->freeValue) p->freeKey(p->value); + if (p->value && p->freeValue) p->freeValue(p->value); heliPairInit(p); } diff --git a/src/common.c b/src/common.c index a46a70f..21344e4 100644 --- a/src/common.c +++ b/src/common.c @@ -1,10 +1,17 @@ +#include + #include "private.h" cairo_t *heliCairo; +struct _Directory { + HeliArray files; +}; + + static char *colors[] = { "transparent", "0 0 0 0", @@ -35,6 +42,41 @@ double randomFloat() { return (double)rand()/(double)RAND_MAX; } +Directory openDirectory(const char *path) { + GDir *dir = g_dir_open(path, 0, NULL); + if (!dir) return NULL; + + Directory d = calloc(1, sizeof(Directory)); + while(TRUE) { + const char* name = g_dir_read_name(dir); + if (!name) break; + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; + heliStringmapAdd(&d->files, name, NULL, NULL); + } + g_dir_close(dir); + + return d; +} + +void closeDirectory(Directory directory) { + if (!directory) return; + heliArrayDestroy(&directory->files); + free(directory); +} + +int directoryGetCount(Directory directory) + { return directory->files.count; } + +const char* directoryGet(Directory directory, int i) + { return (const char*)heliArrayGetKey(&directory->files, i); } + + +int fileExists(const char *path) + { return g_file_test(path, G_FILE_TEST_IS_REGULAR) ? TRUE : FALSE; } +int directoryExists(const char *path) + { return g_file_test(path, G_FILE_TEST_IS_DIR) ? TRUE : FALSE; } + + char* heliStringCopy(const char *x) { int len = strlen(x) + 1; char *cp = malloc(len + 1); diff --git a/src/common.h b/src/common.h index 1a25cb9..d2a2000 100644 --- a/src/common.h +++ b/src/common.h @@ -15,8 +15,19 @@ #endif +typedef struct _Directory *Directory; + + int randomNumber(int min, int max); double randomFloat(); +Directory openDirectory(const char *path); +void closeDirectory(Directory directory); +int directoryGetCount(Directory directory); +const char* directoryGet(Directory directory, int i); + +int fileExists(const char *path); +int directoryExists(const char *path); + #endif diff --git a/src/drawing.c b/src/drawing.c index 0a6fcb7..bd4c12e 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -161,15 +161,15 @@ void text(const char *text, double x, double y) { cairo_text_extents_t extents; cairo_text_extents(cr, text, &extents); - double w = extents.x_bearing + extents.width; - double h = extents.y_bearing + extents.height; + double w = extents.width; + double h = extents.height; if (horAlign == HALIGN_CENTER) x -= w*0.5; if (horAlign == HALIGN_RIGHT ) x -= w; if (vertAlign == VALIGN_CENTER) y -= h*0.5; if (vertAlign == VALIGN_BOTTOM) y -= h; cairo_set_source_rgba(cr, colorStroke[0], colorStroke[1], colorStroke[2], colorStroke[3]); - cairo_move_to(cr, x, y); + cairo_move_to(cr, x - extents.x_bearing, y - extents.y_bearing); cairo_show_text(cr, text); cairo_restore(cr); diff --git a/src/sound.c b/src/sound.c index 1678712..a064a3a 100644 --- a/src/sound.c +++ b/src/sound.c @@ -47,7 +47,7 @@ static void trashAdd(Mix_Chunk *chunk) { size_t prevSize = trashSize; trashSize += trashSize/4 + 32; - trash = realloc(trash, trashSize); + trash = realloc(trash, trashSize*sizeof(*trash)); memset(&trash[prevSize], 0, (trashSize - prevSize)*sizeof(*trash)); trash[prevSize].chunk = chunk; diff --git a/src/world.c b/src/world.c index 79c3c3e..30c5218 100644 --- a/src/world.c +++ b/src/world.c @@ -8,16 +8,29 @@ #include "world.h" +static int resizable; static GtkWidget *window; +static GtkWidget *dialogMessage; +static GtkWidget *dialogMessageLabel; +static GtkWidget *dialogAsk; +static GtkWidget *dialogAskLabel; +static GtkWidget *dialogAskEntry; +static GtkWidget *dialogAskMultiline; +static GtkWidget *dialogAskMultilineLabel; +static GtkWidget *dialogAskMultilineEntry; + static int started; static int stopped; +static int inDialog; static unsigned long long frameCount; static gint64 startTime; static gint64 currentTime; +static gint64 dialogTime; static Callback initCallback; static Callback drawCallback; +static Callback deinitCallback; static cairo_surface_t *cairoSurface; static int width = 400; @@ -29,12 +42,8 @@ static double cameraX; static double cameraY; static double cameraZoom = 1; -static HeliArray keysPressed; -static HeliArray keysPressedInFrame; -static HeliArray keysReleasedInFrame; -static HeliArray buttonsPressed; -static HeliArray buttonsPressedInFrame; -static HeliArray buttonsReleasedInFrame; +static HeliArray keyEvents[6]; +static int keyEventsCount = (int)(sizeof(keyEvents)/sizeof(*keyEvents)); static int mouseMovedInFrame; static double _mouseX; @@ -47,34 +56,59 @@ static char* buttonNames[] = { static int buttonsCount = (int)(sizeof(buttonNames)/sizeof(*buttonNames)); +int keyEventGetCount(KeyEvent mode) + { return (int)mode >= 0 && (int)mode <= keyEventsCount ? keyEvents[mode].count : 0; } +const char *keyEventGet(KeyEvent mode, int i) + { return (int)mode >= 0 && (int)mode <= keyEventsCount ? heliArrayGetValue(&keyEvents[mode], i) : NULL; } + +static int keyEventCheck(KeyEvent mode, const char *code) { + int count = keyEventGetCount(mode); + for(int i = 0; i < count; ++i) + if (strcmp(keyEventGet(mode, i), code) == 0) + return TRUE; + return FALSE; +} + +static void keyEventAdd(KeyEvent mode, const char *code) { + if ((int)mode >= 0 && mode <= (int)keyEventsCount && !keyEventCheck(mode, code)) + heliArrayInsert(&keyEvents[mode], -1, heliStringCopy(code), &free); +} + +static void keyEventRemove(KeyEvent mode, const char *code) { + if ((int)mode >= 0 && mode <= (int)keyEventsCount) + for(int i = keyEvents[mode].count-1; i >= 0; --i) + if (strcmp(keyEvents[mode].items[i].value, code) == 0) + heliArrayRemove(&keyEvents[mode], i); +} + int keyDown(const char *code) - { return heliStringmapGet(&keysPressed, code) != NULL; } + { return keyEventCheck(KEYEVENT_KEY_DOWN, code); } int keyWentDown(const char *code) - { return heliStringmapGet(&keysPressedInFrame, code) != NULL; } + { return keyEventCheck(KEYEVENT_KEY_WENTDOWN, code); } int keyWentUp(const char *code) - { return heliStringmapGet(&keysReleasedInFrame, code) != NULL; } + { return keyEventCheck(KEYEVENT_KEY_WENTUP, code); } int mouseDidMove() { return mouseMovedInFrame; } int mouseDown(const char *code) - { return heliStringmapGet(&buttonsPressed, code) != NULL; } + { return keyEventCheck(KEYEVENT_MOUSE_DOWN, code); } int mouseWentDown(const char *code) - { return heliStringmapGet(&buttonsPressedInFrame, code) != NULL; } + { return keyEventCheck(KEYEVENT_MOUSE_WENTDOWN, code); } int mouseWentUp(const char *code) - { return heliStringmapGet(&buttonsReleasedInFrame, code) != NULL; } + { return keyEventCheck(KEYEVENT_MOUSE_WENTUP, code); } double mouseX() { return _mouseX; } double mouseY() { return _mouseY; } int mousePressedOver(Sprite sprite) - { return buttonsPressed.count && mouseIsOver(sprite); } + { return keyEventGetCount(KEYEVENT_MOUSE_DOWN) && mouseIsOver(sprite); } static void resize(int w, int h) { width = w > 200 ? w : 100; height = h > 200 ? h : 200; - if (started) { + if (started && window) { gtk_window_set_default_size(GTK_WINDOW(window), width, height); gtk_widget_set_size_request(window, width, height); } @@ -110,6 +144,8 @@ void worldSetInit(Callback init) { initCallback = init; } void worldSetDraw(Callback draw) { drawCallback = draw; } +void worldSetDeinit(Callback deinit) + { deinitCallback = deinit; } void worldStop() { if (started) stopped = TRUE; } @@ -141,6 +177,64 @@ double cameraGetZoom() void cameraSetZoom(double x) { cameraZoom = x > HELI_PRECISION ? x : HELI_PRECISION; } + +static void beginDialog() { + inDialog = TRUE; + dialogTime = g_get_monotonic_time() - startTime; +} + +static void endDialog() { + for(int i = 0; i < 100; ++i) gtk_main_iteration(); + currentTime = g_get_monotonic_time(); + startTime = currentTime - dialogTime; + inDialog = FALSE; +} + + +void messageBox(const char *message) { + if (!started || stopped || !window) + return; + beginDialog(); + gtk_label_set_label(GTK_LABEL(dialogMessageLabel), message); + gtk_dialog_run(GTK_DIALOG(dialogMessage)); + gtk_widget_hide(dialogMessage); + endDialog(); +} + +void askTextEx(const char *question, char *answer, int maxAnswerSize, int multiline, int password) { + if (!started || stopped || !window) + return; + beginDialog(); + if (multiline) { + gtk_label_set_label(GTK_LABEL(dialogAskMultilineLabel), question); + GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(dialogAskMultilineEntry)); + gtk_text_buffer_set_text(buffer, answer, -1); + gtk_dialog_run(GTK_DIALOG(dialogAskMultiline)); + gtk_widget_hide(dialogAskMultiline); + + GtkTextIter start, end; + gtk_text_buffer_get_bounds(buffer, &start, &end); + const char *a = gtk_text_buffer_get_text(buffer, &start, &end, FALSE); + for(int i = 0; i < maxAnswerSize; ++i) + { answer[i] = a[i]; if (!a[i]) break; } + } else { + gtk_label_set_label(GTK_LABEL(dialogAskLabel), question); + gtk_entry_set_text(GTK_ENTRY(dialogAskEntry), answer); + gtk_entry_set_input_purpose(GTK_ENTRY(dialogAskEntry), password ? GTK_INPUT_PURPOSE_PASSWORD : GTK_INPUT_PURPOSE_FREE_FORM); + gtk_entry_set_visibility(GTK_ENTRY(dialogAskEntry), !password); + gtk_dialog_run(GTK_DIALOG(dialogAsk)); + gtk_widget_hide(dialogAsk); + const char *a = gtk_entry_get_text(GTK_ENTRY(dialogAskEntry)); + for(int i = 0; i < maxAnswerSize; ++i) + { answer[i] = a[i]; if (!a[i]) break; } + } + endDialog(); +} + +void askText(const char *question, char *answer, int maxAnswerSize) + { askTextEx(question, answer, maxAnswerSize, FALSE, FALSE); } + + static gboolean idle(gpointer data G_GNUC_UNUSED) { if (stopped) { if (window) { @@ -176,6 +270,9 @@ static gboolean draw(GtkWidget *widget G_GNUC_UNUSED, cairo_t *cr, gpointer data if (currentTime < newTime) doFrame = TRUE; + if (inDialog) + doFrame = FALSE; + if (doFrame) { if (frameCount == 0) heliDoTests(); @@ -191,10 +288,10 @@ static gboolean draw(GtkWidget *widget G_GNUC_UNUSED, cairo_t *cr, gpointer data heliSpriteUpdate(dt); heliSoundUpdate(); - heliArrayClear(&keysPressedInFrame); - heliArrayClear(&keysReleasedInFrame); - heliArrayClear(&buttonsPressedInFrame); - heliArrayClear(&buttonsReleasedInFrame); + heliArrayClear(&keyEvents[KEYEVENT_KEY_WENTDOWN]); + heliArrayClear(&keyEvents[KEYEVENT_KEY_WENTUP]); + heliArrayClear(&keyEvents[KEYEVENT_MOUSE_WENTDOWN]); + heliArrayClear(&keyEvents[KEYEVENT_MOUSE_WENTUP]); if (stopped) g_idle_add_full(G_PRIORITY_DEFAULT, idle, NULL, NULL); } @@ -228,22 +325,22 @@ static gboolean handleEvent(GtkWidget *widget G_GNUC_UNUSED, GdkEvent *event, gp case GDK_KEY_PRESS: keyname = heliStringCopy( gdk_keyval_name(event->key.keyval) ); heliLowercase(keyname); - heliStringmapAdd(&keysPressed, keyname, NULL, NULL); - heliStringmapAdd(&keysPressedInFrame, keyname, NULL, NULL); + keyEventAdd(KEYEVENT_KEY_DOWN, keyname); + keyEventAdd(KEYEVENT_KEY_WENTDOWN, keyname); free(keyname); return TRUE; case GDK_KEY_RELEASE: keyname = heliStringCopy( gdk_keyval_name(event->key.keyval) ); heliLowercase(keyname); - heliStringmapRemove(&keysPressed, keyname); - heliStringmapAdd(&keysReleasedInFrame, keyname, NULL, NULL); + keyEventRemove(KEYEVENT_KEY_DOWN, keyname); + keyEventAdd(KEYEVENT_KEY_WENTUP, keyname); free(keyname); return TRUE; case GDK_BUTTON_PRESS: button = event->button.button; if (button >= 1 && button <= buttonsCount) { - heliStringmapAdd(&buttonsPressed, buttonNames[button - 1], NULL, NULL); - heliStringmapAdd(&buttonsPressedInFrame, buttonNames[button - 1], NULL, NULL); + keyEventAdd(KEYEVENT_MOUSE_DOWN, buttonNames[button - 1]); + keyEventAdd(KEYEVENT_MOUSE_WENTDOWN, buttonNames[button - 1]); } _mouseX = event->button.x; _mouseY = event->button.y; @@ -251,8 +348,8 @@ static gboolean handleEvent(GtkWidget *widget G_GNUC_UNUSED, GdkEvent *event, gp case GDK_BUTTON_RELEASE: button = event->button.button; if (button >= 1 && button <= buttonsCount) { - heliStringmapRemove(&buttonsPressed, buttonNames[button - 1]); - heliStringmapAdd(&buttonsReleasedInFrame, buttonNames[button - 1], NULL, NULL); + keyEventRemove(KEYEVENT_MOUSE_DOWN, buttonNames[button - 1]); + keyEventAdd(KEYEVENT_MOUSE_WENTUP, buttonNames[button - 1]); } _mouseX = event->button.x; _mouseY = event->button.y; @@ -261,6 +358,14 @@ static gboolean handleEvent(GtkWidget *widget G_GNUC_UNUSED, GdkEvent *event, gp _mouseX = event->motion.x; _mouseY = event->motion.y; return TRUE; + case GDK_FOCUS_CHANGE: + if (!event->focus_change.in) { + int count = keyEventGetCount(KEYEVENT_KEY_DOWN); + for(int i = 0; i < count; ++i) + keyEventAdd(KEYEVENT_KEY_WENTUP, keyEventGet(KEYEVENT_KEY_DOWN, i)); + heliArrayClear(&keyEvents[KEYEVENT_KEY_DOWN]); + } + break; case GDK_DELETE: stopped = TRUE; window = NULL; @@ -275,24 +380,73 @@ static void activate(GtkApplication* app, gpointer data G_GNUC_UNUSED) { window = gtk_application_window_new(app); g_signal_connect(window, "draw", G_CALLBACK(draw), NULL); g_signal_connect(window, "event", G_CALLBACK(handleEvent), NULL); - gtk_window_set_resizable(GTK_WINDOW(window), FALSE); + gtk_window_set_resizable(GTK_WINDOW(window), resizable); gtk_window_set_default_size(GTK_WINDOW(window), width, height); gtk_widget_add_events( window, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK - | GDK_KEY_RELEASE_MASK ); + | GDK_KEY_RELEASE_MASK + | GDK_FOCUS_CHANGE_MASK ); gtk_widget_set_size_request(window, width, height); gtk_widget_show(window); + dialogMessage = gtk_dialog_new_with_buttons( + "", GTK_WINDOW(window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + "_OK", GTK_RESPONSE_OK, NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialogMessage), GTK_RESPONSE_OK); + dialogMessageLabel = gtk_label_new(""); + gtk_widget_show(dialogMessageLabel); + gtk_container_add( + GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialogMessage))), + dialogMessageLabel ); + + dialogAsk = gtk_dialog_new_with_buttons( + "", GTK_WINDOW(window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + "_OK", GTK_RESPONSE_OK, NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialogAsk), GTK_RESPONSE_OK); + GtkWidget *askBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + dialogAskLabel = gtk_label_new(""); + dialogAskEntry = gtk_entry_new(); + gtk_entry_set_activates_default(GTK_ENTRY(dialogAskEntry), TRUE); + gtk_container_add(GTK_CONTAINER(askBox), dialogAskLabel); + gtk_container_add(GTK_CONTAINER(askBox), dialogAskEntry); + gtk_widget_show_all(askBox); + gtk_container_add( + GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialogAsk))), + askBox ); + + dialogAskMultiline = gtk_dialog_new_with_buttons( + "", GTK_WINDOW(window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + "_OK", GTK_RESPONSE_OK, NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialogAskMultiline), GTK_RESPONSE_OK); + GtkWidget *askMultilineBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + dialogAskMultilineLabel = gtk_label_new(""); + dialogAskMultilineEntry = gtk_text_view_new(); + GtkWidget *askMultilineScrollBox = gtk_scrolled_window_new(NULL, NULL); + gtk_container_add(GTK_CONTAINER(askMultilineScrollBox), dialogAskMultilineEntry); + gtk_container_add(GTK_CONTAINER(askMultilineBox), dialogAskMultilineLabel); + gtk_container_add(GTK_CONTAINER(askMultilineBox), askMultilineScrollBox); + gtk_widget_show_all(askMultilineBox); + gtk_container_add( + GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialogAskMultiline))), + askMultilineBox ); + currentTime = startTime = 0; if (initCallback) initCallback(); g_idle_add(idle, NULL); } -void worldRun() { +void worldRun() + { worldRunEx(FALSE); } + +void worldRunEx(int rs) { if (started) return; + resizable = rs; started = TRUE; stopped = FALSE; @@ -303,6 +457,17 @@ void worldRun() { g_signal_connect(application, "activate", G_CALLBACK(activate), NULL); g_application_run(G_APPLICATION(application), 0, NULL); g_object_unref(application); + window = NULL; + dialogMessage = NULL; + dialogMessageLabel = NULL; + dialogAsk = NULL; + dialogAskEntry = NULL; + dialogAskLabel = NULL; + dialogAskMultiline = NULL; + dialogAskMultilineEntry = NULL; + dialogAskMultilineLabel = NULL; + + if (deinitCallback) deinitCallback(); heliSpriteFinish(); heliDrawingFinish(); @@ -312,13 +477,8 @@ void worldRun() { if (cairoSurface) cairo_surface_destroy(cairoSurface); cairoSurface = NULL; - heliArrayDestroy(&keysPressed); - heliArrayDestroy(&keysPressed); - heliArrayDestroy(&keysPressedInFrame); - heliArrayDestroy(&keysReleasedInFrame); - heliArrayDestroy(&buttonsPressed); - heliArrayDestroy(&buttonsPressedInFrame); - heliArrayDestroy(&buttonsReleasedInFrame); + for(int i = 0; i < keyEventsCount; ++i) + heliArrayDestroy(&keyEvents[i]); started = FALSE; } diff --git a/src/world.h b/src/world.h index c01e67f..2d81a95 100644 --- a/src/world.h +++ b/src/world.h @@ -8,6 +8,15 @@ typedef void (*Callback)(); typedef struct _Sound *Sound; +typedef enum _KeyEvent { + KEYEVENT_KEY_DOWN, + KEYEVENT_KEY_WENTDOWN, + KEYEVENT_KEY_WENTUP, + KEYEVENT_MOUSE_DOWN, + KEYEVENT_MOUSE_WENTDOWN, + KEYEVENT_MOUSE_WENTUP, +} KeyEvent; + void drawSprites(); @@ -20,6 +29,9 @@ int keyDown(const char *code); int keyWentDown(const char *code); int keyWentUp(const char *code); +int keyEventGetCount(KeyEvent mode); +const char *keyEventGet(KeyEvent mode, int i); + int mouseDidMove(); int mouseDown(const char *code); int mouseWentDown(const char *code); @@ -30,6 +42,9 @@ double mouseY(); int mouseIsOver(Sprite sprite); int mousePressedOver(Sprite sprite); +void askText(const char *question, char *answer, int maxAnswerSize); +void askTextEx(const char *question, char *answer, int maxAnswerSize, int multiline, int password); +void messageBox(const char *message); int worldGetSpriteCount(); Sprite worldGetSprite(int i); @@ -50,7 +65,9 @@ double worldGetSeconds(); void worldSetInit(Callback init); void worldSetDraw(Callback draw); +void worldSetDeinit(Callback deinit); void worldRun(); +void worldRunEx(int resizable); void worldStop();