Blob Blame Raw

#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 Callback initCallback;
static Callback drawCallback;

static cairo_surface_t *cairoSurface;
static int width = 400;
static int height = 400;
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));



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); }

static void resize(int w, int h) {
	width  = w > 200 ? w : 100;
	height = h > 200 ? h : 200;
	if (started) {
		gtk_window_set_default_size(GTK_WINDOW(window), width, height);
		gtk_widget_set_size_request(window, width, height);
	}
}
	
int worldGetWidth()
	{ return width; }
void worldSetWidth(int w)
	{ resize(w, height); }

int worldGetHeight()
	{ return height; }
void worldSetHeight(int h)
	{ resize(width, h); }

double worldGetFrameRate()
	{ return frameRate; }
void worldSetFrameRate(double frameRate) {
	if (!(frameRate > 1)) frameRate = 1;
	if (!(frameRate < 100)) frameRate = 100;
}

double worldGetTimeStep()
	{ return 1/frameRate; }

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 idle(gpointer data G_GNUC_UNUSED) {
	if (stopped) {
		if (window) {
			gtk_window_close(GTK_WINDOW(window));
			window = NULL;
		}
		return FALSE;
	}
	gtk_widget_queue_draw(window);
	return TRUE;
}

static gboolean draw(GtkWidget *widget G_GNUC_UNUSED, cairo_t *cr, gpointer data G_GNUC_UNUSED) {
	int doFrame = FALSE;
	
	if ( cairoSurface
	  && cairo_image_surface_get_width(cairoSurface) != width
	  && cairo_image_surface_get_height(cairoSurface) != height )
		{ cairo_surface_destroy(cairoSurface); cairoSurface = NULL; }
	if (!cairoSurface) {
		cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
		doFrame = TRUE;
	}

	if (frameCount == 0) {
		currentTime = startTime = g_get_monotonic_time();
		doFrame = TRUE;
	}

	double dt = 1/frameRate;
	gint64 timeStep = (gint64)round(dt*1e6);
	gint64 newTime = g_get_monotonic_time();
	if (currentTime < newTime)
		doFrame = TRUE;
	
	if (doFrame) {
		if (frameCount == 0) heliDoTests();

		heliCairo = cairo_create(cairoSurface);
		heliDrawingPrepareFrame();
		if (drawCallback) drawCallback();
		cairo_surface_flush(cairoSurface);
		cairo_destroy(heliCairo);
		heliCairo = NULL;
		
		currentTime += timeStep;
		++frameCount;
		heliSpriteUpdate(dt);
		heliSoundUpdate();
		
		heliArrayClear(&keysPressedInFrame);
		heliArrayClear(&keysReleasedInFrame);
		heliArrayClear(&buttonsPressedInFrame);
		heliArrayClear(&buttonsReleasedInFrame);
		
		if (stopped) g_idle_add_full(G_PRIORITY_DEFAULT, idle, NULL, NULL);
	}
	
	gint64 delta = newTime - currentTime - 2000000;
	if (delta > 0) {
		startTime += delta;
		currentTime += delta;
	}
	
	heliDrawingClearFrame(cr);
	cairo_save(cr);
	cairo_set_source_surface(cr, cairoSurface, 0, 0);
	cairo_paint(cr);
	cairo_restore(cr);
	
	if (!stopped) gtk_widget_queue_draw(window);
	
	delta = currentTime - g_get_monotonic_time();
	if (delta > 100) g_usleep(delta - 100);
	
	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 >= 1 && button <= buttonsCount) {
			heliStringmapAdd(&buttonsPressed, buttonNames[button - 1], NULL, NULL);
			heliStringmapAdd(&buttonsPressedInFrame, buttonNames[button - 1], NULL, NULL);
		}
		_mouseX = event->button.x;
		_mouseY = event->button.y;
		return TRUE;
	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);
		}
		_mouseX = event->button.x;
		_mouseY = event->button.y;
		return TRUE;
	case GDK_MOTION_NOTIFY:
		_mouseX = event->motion.x;
		_mouseY = event->motion.y;
		return TRUE;
	case GDK_DELETE:
		stopped = TRUE;
		window = NULL;
		break;
	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_window_set_resizable(GTK_WINDOW(window), FALSE);
	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 );
	gtk_widget_set_size_request(window, width, height);
	gtk_widget_show(window);

	currentTime = startTime = 0;
	if (initCallback) initCallback();
	g_idle_add(idle, NULL);
}

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();
	heliSoundFinish();
	
	if (cairoSurface) cairo_surface_destroy(cairoSurface);
	cairoSurface = NULL;
	
	heliArrayDestroy(&keysPressed);
	heliArrayDestroy(&keysPressed);
	heliArrayDestroy(&keysPressedInFrame);
	heliArrayDestroy(&keysReleasedInFrame);
	heliArrayDestroy(&buttonsPressed);
	heliArrayDestroy(&buttonsPressedInFrame);
	heliArrayDestroy(&buttonsReleasedInFrame);
	
	started = FALSE;
}