#include <glib.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include "private.h"
#include "world.h"
static GtkWidget *window;
static int started;
static int stopped;
static unsigned long long frameCount;
static gint64 startTime;
static gint64 currentTime;
static guint timerInterval;
static Callback initCallback;
static Callback drawCallback;
static int width;
static int height;
static double frameRate = 30;
static int cameraEnabled;
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 int mouseMovedInFrame;
static double _mouseX;
static double _mouseY;
static char* buttonNames[] = {
"left",
"middle",
"right" };
static int buttonsCount = (int)(sizeof(buttonNames)/sizeof(*buttonNames));
void playSound(const char *path, int loop) {
#warning "TODO: playSound: not implemented yet"
}
void stopSound(const char *path) {
#warning "TODO: stopSound: not implemented yet"
}
int keyDown(const char *code)
{ return heliStringmapGet(&keysPressed, code) != NULL; }
int keyWentDown(const char *code)
{ return heliStringmapGet(&keysPressedInFrame, code) != NULL; }
int keyWentUp(const char *code)
{ return heliStringmapGet(&keysReleasedInFrame, code) != NULL; }
int mouseDidMove()
{ return mouseMovedInFrame; }
int mouseDown(const char *code)
{ return heliStringmapGet(&buttonsPressed, code) != NULL; }
int mouseWentDown(const char *code)
{ return heliStringmapGet(&buttonsPressedInFrame, code) != NULL; }
int mouseWentUp(const char *code)
{ return heliStringmapGet(&buttonsReleasedInFrame, code) != NULL; }
double mouseX()
{ return _mouseX; }
double mouseY()
{ return _mouseY; }
int mousePressedOver(Sprite sprite)
{ return buttonsPressed.count && mouseIsOver(sprite); }
int worldGetWidth()
{ return width; }
void worldSetWidth(int w) {
width = w;
if (started) gtk_widget_set_size_request(window, width, height);
}
int worldGetHeight()
{ return height; }
void worldSetHeight(int h) {
height = h;
if (started) gtk_widget_set_size_request(window, width, height);
}
double worldGetFrameRate()
{ return frameRate; }
void worldSetFrameRate(double frameRate) {
if (!(frameRate > 1)) frameRate = 1;
if (!(frameRate < 100)) frameRate = 100;
}
int worldGetFrameCount()
{ return (int)frameCount; }
double worldGetSeconds()
{ return started ? (currentTime - startTime)*1e-6 : 0.0; }
void worldSetInit(Callback init)
{ initCallback = init; }
void worldSetDraw(Callback draw)
{ drawCallback = draw; }
void worldStop()
{ if (started) stopped = TRUE; }
void cameraOn()
{ cameraEnabled = TRUE; }
void cameraOff()
{ cameraEnabled = FALSE; }
int cameraIsActive()
{ return cameraEnabled; }
double cameraMouseX()
{ return _mouseX/cameraZoom + cameraX; }
double cameraMouseY()
{ return _mouseY/cameraZoom + cameraY; }
double cameraGetX()
{ return cameraX; }
void cameraSetX(double x)
{ cameraX = x; }
double cameraGetY()
{ return cameraY; }
void cameraSetY(double y)
{ cameraY = y; }
double cameraGetZoom()
{ return cameraZoom; }
void cameraSetZoom(double x)
{ cameraZoom = x > 1e-6 ? x : 1e-6; }
static gboolean timeout(gpointer data G_GNUC_UNUSED) {
gtk_widget_queue_draw(window);
if (stopped) {
gtk_window_close(GTK_WINDOW(window));
return FALSE;
}
guint interval = (guint)round(1000/frameRate);
if (interval != timerInterval) {
timerInterval = interval;
g_timeout_add(timerInterval, timeout, NULL);
return FALSE;
}
return TRUE;
}
static gboolean draw(GtkWidget *widget G_GNUC_UNUSED, cairo_t *cr, gpointer data G_GNUC_UNUSED) {
guint64 newTime = g_get_monotonic_time();
heliCairo = cr;
if (!frameCount) {
startTime = currentTime = newTime;
if (initCallback) initCallback();
heliDrawingPrepareFrame();
if (drawCallback) drawCallback();
heliArrayClear(&keysPressedInFrame);
heliArrayClear(&keysReleasedInFrame);
heliArrayClear(&buttonsPressedInFrame);
heliArrayClear(&buttonsReleasedInFrame);
++frameCount;
} else {
double dt = 1/frameRate;
guint64 timeStep = (guint64)round(dt*1e6);
while(currentTime < newTime) {
currentTime += timeStep;
++frameCount;
heliSpriteUpdate(dt);
heliDrawingPrepareFrame();
if (drawCallback) drawCallback();
heliArrayClear(&keysPressedInFrame);
heliArrayClear(&keysReleasedInFrame);
heliArrayClear(&buttonsPressedInFrame);
heliArrayClear(&buttonsReleasedInFrame);
}
}
heliCairo = NULL;
return TRUE;
}
static gboolean handleEvent(GtkWidget *widget G_GNUC_UNUSED, GdkEvent *event, gpointer data G_GNUC_UNUSED) {
gchar *keyname;
int button;
switch(event->type) {
case GDK_KEY_PRESS:
keyname = heliStringCopy( gdk_keyval_name(event->key.keyval) );
heliLowercase(keyname);
heliStringmapAdd(&keysPressed, keyname, NULL, NULL);
heliStringmapAdd(&keysPressedInFrame, keyname, NULL, NULL);
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);
free(keyname);
return TRUE;
case GDK_BUTTON_PRESS:
button = event->button.button;
if (button >= 0 && button < buttonsCount) {
heliStringmapAdd(&buttonsPressed, buttonNames[button], NULL, NULL);
heliStringmapAdd(&buttonsPressedInFrame, buttonNames[button], NULL, NULL);
}
_mouseX = event->button.x;
_mouseY = event->button.y;
return TRUE;
case GDK_BUTTON_RELEASE:
button = event->button.button;
if (button >= 0 && button < buttonsCount) {
heliStringmapRemove(&buttonsPressed, buttonNames[button]);
heliStringmapAdd(&buttonsReleasedInFrame, buttonNames[button], NULL, NULL);
}
_mouseX = event->button.x;
_mouseY = event->button.y;
return TRUE;
case GDK_MOTION_NOTIFY:
_mouseX = event->motion.x;
_mouseY = event->motion.y;
return TRUE;
default:
break;
}
return FALSE;
}
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_widget_add_events( window,
GDK_POINTER_MOTION_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_KEY_PRESS_MASK
| GDK_KEY_RELEASE_MASK );
gtk_widget_set_size_request(window, width, height);
gtk_widget_show(window);
timerInterval = (guint)round(1000/frameRate);
g_timeout_add(timerInterval, timeout, NULL);
if (initCallback) initCallback();
}
void worldRun() {
if (started) return;
started = TRUE;
stopped = FALSE;
frameCount = 0;
srand(time(NULL));
GtkApplication *application = gtk_application_new(NULL, 0);
g_signal_connect(application, "activate", G_CALLBACK(activate), NULL);
g_application_run(G_APPLICATION(application), 0, NULL);
g_object_unref(application);
heliSpriteFinish();
heliDrawingFinish();
heliAnimationFinish();
heliArrayDestroy(&keysPressed);
heliArrayDestroy(&keysPressed);
heliArrayDestroy(&keysPressedInFrame);
heliArrayDestroy(&keysReleasedInFrame);
heliArrayDestroy(&buttonsPressed);
heliArrayDestroy(&buttonsPressedInFrame);
heliArrayDestroy(&buttonsReleasedInFrame);
started = FALSE;
}