Blob Blame Raw

#include <glib.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>

#include "private.h"

#include "world.h"


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;
static int height = 400;
static int resizable;
static char title[1000];
static int titleSize = (int)(sizeof(title)/sizeof(*title));
static double frameRate = 24;

static int cameraEnabled;
static double cameraX;
static double cameraY;
static double cameraZoom = 1;

static HeliArray keyEvents[6];
static int keyEventsCount = (int)(sizeof(keyEvents)/sizeof(*keyEvents));

static int mouseMovedInFrame;
static double _mouseX;
static double _mouseY;

static char* buttonNames[] = {
	"left",
	"middle",
	"right" };
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 keyEventCheck(KEYEVENT_KEY_DOWN, code); }
int keyWentDown(const char *code)
	{ return keyEventCheck(KEYEVENT_KEY_WENTDOWN, code); }
int keyWentUp(const char *code)
	{ return keyEventCheck(KEYEVENT_KEY_WENTUP, code); }

int mouseDidMove()
	{ return mouseMovedInFrame; }
int mouseDown(const char *code)
	{ return keyEventCheck(KEYEVENT_MOUSE_DOWN, code); }
int mouseWentDown(const char *code)
	{ return keyEventCheck(KEYEVENT_MOUSE_WENTDOWN, code); }
int mouseWentUp(const char *code)
	{ return keyEventCheck(KEYEVENT_MOUSE_WENTUP, code); }
double mouseX()
	{ return _mouseX; }
double mouseY()
	{ return _mouseY; }

int mousePressedOver(Sprite 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 && !stopped && window) {
		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); }

int worldGetResizable()
	{ return resizable; }
void worldSetResizable(int r) {
	if (resizable == r) return;
	resizable = r ? TRUE : FALSE;
	if (started && !stopped && window)
		gtk_window_set_resizable(GTK_WINDOW(window), resizable);
}

const char* worldGetTitle()
	{ return title; }
void worldSetTitle(const char *t) {
	int changed = FALSE;
	for(int i = 0; i < titleSize-1; ++i) {
		if (title[i] != t[i]) changed = TRUE;
		title[i] = t[i];
		if (!t[i]) break;
	}
	if (changed && started && !stopped && window)
		gtk_window_set_title(GTK_WINDOW(window), title);
}

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

double worldGetTimeStep()
	{ return 1/frameRate; }

int worldGetFrameCount()
	{ return (int)frameCount; }
double worldGetSeconds()
	{ return started ? (currentTime - startTime)*HELI_PRECISION : 0.0; }

void worldSetInit(Callback init)
	{ initCallback = init; }
void worldSetDraw(Callback draw)
	{ drawCallback = draw; }
void worldSetDeinit(Callback deinit)
	{ deinitCallback = deinit; }
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 > 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) {
			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 (inDialog)
		doFrame = FALSE;
	
	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(&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);
	}
	
	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);
		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);
		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) {
			keyEventAdd(KEYEVENT_MOUSE_DOWN, buttonNames[button - 1]);
			keyEventAdd(KEYEVENT_MOUSE_WENTDOWN, buttonNames[button - 1]);
		}
		_mouseX = event->button.x;
		_mouseY = event->button.y;
		return TRUE;
	case GDK_BUTTON_RELEASE:
		button = event->button.button;
		if (button >= 1 && button <= buttonsCount) {
			keyEventRemove(KEYEVENT_MOUSE_DOWN, buttonNames[button - 1]);
			keyEventAdd(KEYEVENT_MOUSE_WENTUP, buttonNames[button - 1]);
		}
		_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_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;
		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), resizable);
	gtk_window_set_default_size(GTK_WINDOW(window), width, height);
	gtk_window_set_title(GTK_WINDOW(window), title);
	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_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_with_properties(
		GTK_CONTAINER(askMultilineBox), askMultilineScrollBox,
		"expand", TRUE, "fill", TRUE, NULL );
	gtk_widget_show_all(askMultilineBox);
	gtk_container_add_with_properties(
		GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialogAskMultiline))),
		askMultilineBox,
		"expand", TRUE, "fill", TRUE, NULL );

	currentTime = startTime = 0;
	heliInitialized = TRUE;
	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);
	window = NULL;
	dialogMessage = NULL;
	dialogMessageLabel = NULL;
	dialogAsk = NULL;
	dialogAskEntry = NULL;
	dialogAskLabel = NULL;
	dialogAskMultiline = NULL;
	dialogAskMultilineEntry = NULL;
	dialogAskMultilineLabel = NULL;
	
	if (deinitCallback) deinitCallback();
	
	heliArrayClear(&heliObjectsSet);
	heliSpriteFinish();
	heliDrawingFinish();
	heliAnimationFinish();
	heliSoundFinish();
	heliArrayDestroy(&heliObjectsSet);
	
	heliInitialized = FALSE;
	
	if (cairoSurface) cairo_surface_destroy(cairoSurface);
	cairoSurface = NULL;
	
	for(int i = 0; i < keyEventsCount; ++i)
		heliArrayDestroy(&keyEvents[i]);
	
	started = FALSE;
}