#include "ui_internal.h"
#include "stringmap.h"
#include "sys_log.h"
#include <stdlib.h>
#include <stdio.h>
#include "platform.h"
#include "system.h"
#include "scenefile.h"
#include "localisation.h"
#include "localsettings.h"
#include "quadexport.h"
#include "imgload.h"

int ui_parse(const char* f, yrUiScreen s);

static const char* screendefs[us_end] = {
	"data/ui/vrmode.yud",
	"data/ui/file.yud",
	"data/ui/backgrounds.yud",
	"data/ui/export.yud",
	"data/ui/export_progress.yud",
	"data/ui/import.yud"
};

static int link_vrmode(yrUiScreen s);
static int link_file(yrUiScreen s);
static int link_backgrounds(yrUiScreen s);
static int link_export(yrUiScreen s);
static int link_export_progress(yrUiScreen s);
static int link_import(yrUiScreen s);
static void update_vrmode(yrUiScreen s);
static void update_file(yrUiScreen s);
static void update_backgrounds(yrUiScreen s);
static void update_export(yrUiScreen s);
static void update_export_progress(yrUiScreen s);
static void update_import(yrUiScreen s);

static void yrUiScreen_cleanup_data(yrUiScreen s);

yrUiScreen yrUiScreen_load(enum UiScreenId id)
{
	//alloc init
	yrUiScreen s = malloc(sizeof(struct _yr_ui_screen));
	if(!s) { yrLog(0, "Out of memory"); return NULL;}

	s->id = id;
	s->next = us_end;
	s->elemcount = 0;
	s->elems = NULL;
	s->scale = 143.9f;
	s->ar = 1.0f;
	s->idmap = NULL;
	s->data = NULL;
	memset(s->cb_elems, 0, sizeof(s->cb_elems));

	//load screen
	int err = ui_parse(screendefs[id], s);
	if(err) {
		free(s);
		return NULL;
	}

	//link screen
	switch(id) {
		case usVRMode:			err = link_vrmode(s); break;
		case usFile:			err = link_file(s); break;
		case usBackgrounds:		err = link_backgrounds(s); break;
		case usExport:			err = link_export(s); break;
		case usExportProgress:	err = link_export_progress(s); break;
		case usImport:			err = link_import(s); break;
	}
	if(err) {
		yrLog(0, "Error during screen link");
		yrUiScreen_cleanup_data(s);
		yrStringmap_destroy(s->idmap, 0);
		free(s);
		return NULL;
	}

	//done
	return s;
}

void yrUiScreen_destroy(yrUiScreen s)
{
	for(unsigned i = 0; i < s->elemcount; ++i) {
		yrUiElem_destroy(s->elems[i]);
	}
	yrUiScreen_cleanup_data(s);
	yrStringmap_destroy(s->idmap, 0);
	free(s->elems);
	free(s);
}

enum UiScreenId	yrUiScreen_nextscreen(yrUiScreen s)
{
	return s->next;
}

void yrUiScreen_tick(yrUiScreen s)
{
	//update screen
	switch(s->id) {
		case usVRMode:			update_vrmode(s); break;
		case usFile:			update_file(s); break;
		case usBackgrounds:		update_backgrounds(s); break;
		case usExport:			update_export(s); break;
		case usExportProgress:	update_export_progress(s); break;
		case usImport:			update_import(s); break;
	}
}

void yrUiScreen_render(yrUiScreen s, struct yr_ui_rendertexes* unis)
{
	//render elements
	for(unsigned i = 0; i < s->elemcount; ++i) {
		yrUiElem_render(s->elems[i], 0xFF000000ul, unis);
	}
}

void yrUiScreen_resize(yrUiScreen s, float scale, float ar)
{
	s->scale = scale;
	s->ar = ar;
	for(unsigned i = 0; i < s->elemcount; ++i) {
		s->elems[i]->h = 100.0f / ar;
		yrUiElem_layout(s->elems[i], scale, ar);
	}
}

void yrUiScreen_mousepos(yrUiScreen s, float x, float y)
{
	for(unsigned i = 0; i < s->elemcount; ++i) {
		yrUiElem_mousepos(s->elems[i], x, y);
	}
}

void yrUiScreen_keyevent(yrUiScreen s, unsigned keycode, int down)
{
	for(unsigned i = 0; i < s->elemcount; ++i) {
		yrUiElem_keyevent(s->elems[i], keycode, down, s);
	}
}

void yrUiScreen_char(yrUiScreen s, unsigned c)
{
	for(unsigned i = 0; i < s->elemcount; ++i) {
		yrUiElem_char(s->elems[i], c);
	}
}

void yrUiScreen_reset(yrUiScreen s)
{
	s->next = us_end;
	for(unsigned i = 0; i < s->elemcount; ++i) {
		yrUiElem_reset(s->elems[i]);
	}
	switch(s->id) {
		case usVRMode:			break;
		case usFile:			break; //TODO: update file meta
		case usBackgrounds:		s->data = (void*)(uint64_t)1; break;
		case usExport:			break;
		case usExportProgress:	break;
		case usImport:			break;
	}
}

void yrUiScreen_elemcallback(yrUiScreen s, yrUiElem e)
{
	YR_ASSERT(e);
	for(size_t i = 0; i < UISCREEN_MAX_CALLBACKS; ++i)
		if(s->cb_elems[i] == e)
			s->cb_funcs[i](s);
}

/***************
* Menu callbacks
****************/
void menu_cb_vrmode(yrUiScreen s)		{ s->next = usVRMode; }
void menu_cb_file(yrUiScreen s)			{ if(!yrSystem_vr_state()) s->next = usFile; }
void menu_cb_backgrounds(yrUiScreen s)	{ if(!yrSystem_vr_state() && yrSystem_get_scenefilename()) s->next = usBackgrounds; }
void menu_cb_export(yrUiScreen s)		{ if(!yrSystem_vr_state() && yrSystem_get_scenefilename()) s->next = usExport; }
void menu_cb_import(yrUiScreen s)		{ if(!yrSystem_vr_state() && yrSystem_get_scenefilename()) s->next = usImport; }

/***************
* SCREEN: VrMode
****************/
union vrscreen_data
{
	uint64_t as_uint;
	struct {
		uint8_t vrinit;
		uint8_t pendingchange;
		uint8_t mirror;
	};
};

void vrmode_cb_button(yrUiScreen s)
{
	union vrscreen_data* vsd = (union vrscreen_data*)&s->data;
	yrUiElem button = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_buttontext");
	yrUiElem status = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_status");
	if(yrSystem_vr_state()) {
		yrUiElemText_set_text(button, "");
		yrUiElemText_set_text(status, yrLocalise_get_string("vr_Stopping"));
		yrSystem_vr_stop();
	} else {
		yrUiElemText_set_text(button, "");
		yrUiElemText_set_text(status, yrLocalise_get_string("vr_Launching"));
		yrUiElem_set_enabled(s->cb_elems[1], 0);
		yrUiElem_set_enabled(s->cb_elems[2], 0);
		yrUiElem_set_enabled(s->cb_elems[3], 0);
		yrUiElem_set_enabled(s->cb_elems[4], 0);
		yrSystem_vr_init();
	}
	yrUiElem_set_enabled(s->cb_elems[0], 0);
	yrUiElem_set_enabled(s->cb_elems[5], 0);
	yrUiElem_set_enabled(s->cb_elems[6], 0);
	vsd->pendingchange = 1;
	yrUiScreen_resize(s, s->scale, s->ar); //redraw
}

void vrmode_cb_mirror(yrUiScreen s)
{
	union vrscreen_data* vsd = (union vrscreen_data*)&s->data;
	yrUiElem eyeon = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_eyeon");
	yrUiElem eyeoff = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_eyeoff");
	yrUiElem eyetext = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_eyetext");

	vsd->mirror = !vsd->mirror;
	yrUiElemText_set_text(eyetext, yrLocalise_get_string(vsd->mirror ? "vr_MirrorOn" : "vr_MirrorOff"));
	yrUiElem_set_enabled(eyeon, !yrSystem_vr_state());
	yrUiElem_set_enabled(eyeoff, !vsd->mirror);
	yrUiScreen_resize(s, s->scale, s->ar); //redraw

	yrLocalSettings_mirror_set(vsd->mirror);
}

static int link_vrmode(yrUiScreen s)
{
	yrUiElem e = NULL;
	int lsmirror = yrLocalSettings_mirror_get();

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_button");
	if(!e) { yrLog(0, "Screen link error: missing \"vr_button\" on vrmode screen."); return -1;}
	s->cb_elems[0] = e;
	s->cb_funcs[0] = vrmode_cb_button;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_file");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_file\" on vrmode screen."); return -1;}
	s->cb_elems[1] = e;
	s->cb_funcs[1] = menu_cb_file;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_backgrounds");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_backgrounds\" on vrmode screen."); return -1;}
	s->cb_elems[2] = e;
	s->cb_funcs[2] = menu_cb_backgrounds;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_export");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_export\" on vrmode screen."); return -1;}
	s->cb_elems[3] = e;
	s->cb_funcs[3] = menu_cb_export;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_import");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_import\" on vrmode screen."); return -1;}
	s->cb_elems[4] = e;
	s->cb_funcs[4] = menu_cb_import;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_buttonoff");
	if(!e) { yrLog(0, "Screen link error: missing \"vr_buttonoff\" on vrmode screen."); return -1;}
	s->cb_elems[5] = e;
	s->cb_funcs[5] = vrmode_cb_button;
	yrUiElem_set_enabled(e, 0);

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_mirrorbutton");
	if(!e) { yrLog(0, "Screen link error: missing \"vr_mirrorbutton\" on vrmode screen."); return -1;}
	s->cb_elems[6] = e;
	s->cb_funcs[6] = vrmode_cb_mirror;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_status");
	if(!e) { yrLog(0, "Screen link error: missing \"vr_status\" on vrmode screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"vr_status\" must be of type 'text' on vrmode screen."); return -1;}

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_buttontext");
	if(!e) { yrLog(0, "Screen link error: missing \"vr_buttontext\" on vrmode screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"vr_buttontext\" must be of type 'text' on vrmode screen."); return -1;}

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_mirror");
	if(!e) { yrLog(0, "Screen link error: missing \"vr_mirror\" on vrmode screen."); return -1;}
	if(e->type != etMirror) { yrLog(0, "Screen link error: \"vr_mirror\" must be of type 'mirror' on vrmode screen."); return -1;}
	yrUiElem_set_enabled(e, 0);

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_eyeon");
	if(!e) { yrLog(0, "Screen link error: missing \"vr_eyeon\" on vrmode screen."); return -1;}
	e = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_eyeoff");
	if(!e) { yrLog(0, "Screen link error: missing \"vr_eyeoff\" on vrmode screen."); return -1;}
	yrUiElem_set_enabled(e, !lsmirror);

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_eyetext");
	if(!e) { yrLog(0, "Screen link error: missing \"vr_eyetext\" on vrmode screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"vr_eyetext\" must be of type 'text' on vrmode screen."); return -1;}
	yrUiElemText_set_text(e, yrLocalise_get_string(lsmirror ? "vr_MirrorOn" : "vr_MirrorOff"));

	union vrscreen_data* vsd = (union vrscreen_data*)&s->data;
	vsd->as_uint = 0;
	vsd->vrinit = (uint8_t) yrSystem_vr_fromstart();
	vsd->mirror = lsmirror;

	return 0;
}

static void update_vrmode(yrUiScreen s)
{
	union vrscreen_data* vsd = (union vrscreen_data*)&s->data;
	//if immediate vr start
	if(vsd->vrinit)
	{
		const char* scenec;
		const char* path;
		int64_t timestamp;
		yrLocalSettings_recent_get(0, &scenec, &path, &timestamp);
		yrSystem_set_scenefilename(path);
		vsd->vrinit = 0;
		vrmode_cb_button(s); //simulate user pressing the vr mode start button
	}
	//if vr state has changed
	else if(vsd->pendingchange && yrSystem_reached_runlevel()) {		
		yrUiElem button = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_buttontext");
		yrUiElem status = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_status");
		yrUiElem mirror = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_mirror");
		yrUiElem eyeon = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_eyeon");
		yrUiElem eyeoff = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_eyeoff");
		yrUiElem eyetext = (yrUiElem) yrStringmap_lookup(s->idmap, "vr_eyetext");
		if(yrSystem_vr_state()) {
			yrUiElemText_set_text(button, yrLocalise_get_string("vr_StopVR"));
			yrUiElemText_set_text(status, vsd->mirror ? "" : yrLocalise_get_string("vr_Active"));
			vsd->pendingchange = 0;
		} else {
			yrUiElemText_set_text(button, yrLocalise_get_string("vr_StartVR"));
			yrUiElemText_set_text(status, yrLocalise_get_string("vr_NotActive"));
			vsd->pendingchange = 0;
		}
		//enable/disable buttons and mirror
		int enb = !yrSystem_vr_state();
		yrUiElem_set_enabled(s->cb_elems[0], enb);
		yrUiElem_set_enabled(s->cb_elems[1], enb);
		yrUiElem_set_enabled(s->cb_elems[2], enb);
		yrUiElem_set_enabled(s->cb_elems[3], enb);
		yrUiElem_set_enabled(s->cb_elems[4], enb);
		yrUiElem_set_enabled(s->cb_elems[5], !enb);
		yrUiElem_set_enabled(s->cb_elems[6], enb);
		yrUiElem_set_enabled(mirror, !enb && vsd->mirror);
		yrUiElem_set_enabled(eyeon, enb);
		yrUiElem_set_enabled(eyeoff, !vsd->mirror);
		yrUiElem_set_enabled(eyetext, enb);
		yrUiScreen_resize(s, s->scale, s->ar); //redraw
	}
}

/*************
* SCREEN: File
**************/
struct filescreen_data
{
	yrUiElem filelist[8];
	unsigned selected;
	const char* lastfile;
};

static void file_enablemenu(yrUiScreen s, int enable)
{
	yrUiElem_set_enabled(s->cb_elems[1], enable);
	yrUiElem_set_enabled(s->cb_elems[2], enable);
	yrUiElem_set_enabled(s->cb_elems[3], enable);
}

static char* getsizestring(int64_t size)
{
	unsigned unit;
	const char* unit_str[4] = {
		yrLocalise_get_string("util_KB"),
		yrLocalise_get_string("util_MB"),
		yrLocalise_get_string("util_GB"),
		yrLocalise_get_string("util_TB"),
	};
	for(unit = 0; unit < 3; ++unit) {
		if(size < 900 * 1024) break;
		size /= 1024;
	}
	char* out = malloc(strlen(unit_str[unit]) + 13); //13: 1 space, 1 sign, 8 digits max TB size, 1 decimal sep, 1 digit precision, 1 zero term
	sprintf(out, "%.1f %s", (float) size / 1024.0f, unit_str[unit]);
	return out;
}

static char* gettimestring(int64_t timestamp)
{
	if(timestamp == 0) return _strdup("");
	uint64_t now = yr_get_timestamp();
	uint64_t timediff_s = (now - timestamp)/10000000;
	char* out = NULL;
	if(timediff_s < 20*60*60) {
		out = yr_timestamp_to_timestring(timestamp);
	} else {
		out = yr_timestamp_to_datestring(timestamp);
	}
	if(!out) out = _strdup("");
	return out;
}

static void file_refreshlist(yrUiScreen s)
{
	struct filescreen_data* fsd = s->data;
	unsigned i = 0;
	//set entries
	for(; i < 8; i += 1) {
		const char* scenec;
		const char* path;
		int64_t timestamp;
		char* timestr;

		yrLocalSettings_recent_get(i, &scenec, &path, &timestamp);
		if(!scenec) break;

		timestr = gettimestring(timestamp);
		yrUiElem filename = ((yrUiElemRect)fsd->filelist[i])->children[0];
		yrUiElem filetime = ((yrUiElemRect)fsd->filelist[i])->children[1];
		yrUiElemText_set_text(filename, scenec);
		yrUiElemText_set_text(filetime, timestr);
		yrUiElem_set_enabled(fsd->filelist[i], 1);
		fsd->filelist[i]->bg = (i == fsd->selected) ? fsd->filelist[i]->bg_down : 0x00000000ul;
		fsd->filelist[i]->fg = (i == fsd->selected) ? fsd->filelist[i]->fg_down : color_undefined;
		free(timestr);
	}
	//"no recent files"
	if(i == 0) {
		yrUiElem filename = ((yrUiElemRect)fsd->filelist[i])->children[0];
		yrUiElem filetime = ((yrUiElemRect)fsd->filelist[i])->children[1];
		yrUiElemText_set_text(filename, yrLocalise_get_string("file_NoRecentFiles"));
		yrUiElemText_set_text(filetime, "");
		yrUiElem_set_enabled(fsd->filelist[i], 0);
		fsd->filelist[i]->bg = 0x00000000ul;
		fsd->filelist[i]->fg = color_undefined;
		i += 1;
	}
	//clear other list elems
	for(; i < 8; i += 1) {
		yrUiElem filename = ((yrUiElemRect)fsd->filelist[i])->children[0];
		yrUiElem filetime = ((yrUiElemRect)fsd->filelist[i])->children[1];
		yrUiElemText_set_text(filename, "");
		yrUiElemText_set_text(filetime, "");
		yrUiElem_set_enabled(fsd->filelist[i], 0);
		fsd->filelist[i]->bg = 0x00000000ul;
		fsd->filelist[i]->fg = color_undefined;
	}
	//redraw
	yrUiScreen_resize(s, s->scale, s->ar);
}

static int file_openscene(yrUiScreen s, const char* path)
{
	int err = 0;
	int64_t size = 0;
	char* time_created = NULL;
	char* time_modified = NULL;

	//check
	err = yrSceneFile_isvalid(path);
		 if(err == -1)	{ yr_msgbox(yrLocalise_get_string("err_FileNotFound"), path); goto onerror; } //TODO: don't use msgbox?
	else if(err == -2)	{ yr_msgbox(yrLocalise_get_string("err_FileReadError"), path); goto onerror; }
	else if(err == -3)	{ yr_msgbox(yrLocalise_get_string("err_NotASceneFile"), path); goto onerror; }
	else if(err == -4)	{ yr_msgbox(yrLocalise_get_string("err_FileBadVersion"), path); goto onerror; }
	else if(err)		{ yr_msgbox(yrLocalise_get_string("err_FileReadError"), path); goto onerror; }

	//get meta
	err = yrFile_get_filemeta(path, &size, &time_created, &time_modified);
	if(err) { yr_msgbox(yrLocalise_get_string("err_FileReadError"), path); goto onerror; }

	//add to recent files list
	const char* fws_pos = strrchr(path, '/');
	const char* bws_pos = strrchr(path, '\\');
	const char* scenec = path;
	if(fws_pos) scenec = fws_pos + 1;
	if(bws_pos && bws_pos + 1 > scenec) scenec = bws_pos + 1;
	yrLocalSettings_recent_add(path, yr_get_timestamp());

	//update UI
	char* sizestr = getsizestring(size);
	yrUiElem ueheader =		(yrUiElem) yrStringmap_lookup(s->idmap, "file_scenename");
	yrUiElem uecreated =	(yrUiElem) yrStringmap_lookup(s->idmap, "file_created");
	yrUiElem uemodified =	(yrUiElem) yrStringmap_lookup(s->idmap, "file_modified");
	yrUiElem uesize =		(yrUiElem) yrStringmap_lookup(s->idmap, "file_size");
	yrUiElemText_set_text(ueheader, scenec);
	yrUiElemText_set_text(uecreated, time_created);
	yrUiElemText_set_text(uemodified, time_modified);
	yrUiElemText_set_text(uesize, sizestr);
	file_refreshlist(s);

	//done
	free(sizestr);
	free(time_created);
	free(time_modified);
	return 0;
onerror:
	yrUiScreen_reset(s); //msgbox eats mouseup
	free(time_created);
	free(time_modified);
	return err;
}

void file_cb_recentx(yrUiScreen s, unsigned x)
{
	struct filescreen_data* fsd = s->data;
	fsd->selected = x;
	file_refreshlist(s);
}

void file_cb_recent0(yrUiScreen s) { file_cb_recentx(s, 0); }
void file_cb_recent1(yrUiScreen s) { file_cb_recentx(s, 1); }
void file_cb_recent2(yrUiScreen s) { file_cb_recentx(s, 2); }
void file_cb_recent3(yrUiScreen s) { file_cb_recentx(s, 3); }
void file_cb_recent4(yrUiScreen s) { file_cb_recentx(s, 4); }
void file_cb_recent5(yrUiScreen s) { file_cb_recentx(s, 5); }
void file_cb_recent6(yrUiScreen s) { file_cb_recentx(s, 6); }
void file_cb_recent7(yrUiScreen s) { file_cb_recentx(s, 7); }

func_screen_callback file_cb_recent[8] = {
	file_cb_recent0,
	file_cb_recent1,
	file_cb_recent2,
	file_cb_recent3,
	file_cb_recent4,
	file_cb_recent5,
	file_cb_recent6,
	file_cb_recent7,
};

void file_cb_open(yrUiScreen s)
{
	struct filescreen_data* fsd = s->data;
	const char* scenec;
	const char* path;
	int64_t timestamp;
	unsigned sel = fsd->selected;
	fsd->selected = 0; //if all goes well the entry will be on top of the list
	yrLocalSettings_recent_get(sel, &scenec, &path, &timestamp);
	if(!scenec ) {fsd->selected = sel; return;}

	int err = file_openscene(s, path);
	if(err) {
		yrLocalSettings_recent_remove(sel);
		fsd->selected = 8;
		file_refreshlist(s);
	} else {
		yrSystem_set_scenefilename(path);
	}
}

void file_cb_new(yrUiScreen s)
{
	const char* filtername = "Virtual Idea Area Scene";
	const char* filterspec = "*.ysf";
	char* path = yrFile_path_dialog(yrFDlgID_Scene, 1, 0, 1,&filtername, &filterspec, "ysf");
	yrUiScreen_reset(s); //pathdialog eats mouseup
	if(!path) return;
	//create empty file
	yrSceneFile* sf = yrSceneFile_open(path, 1);
	if(!sf) {
		yr_msgbox(yrLocalise_get_string("err_FileCreateError"), path);
		free(path);
		return;
	}
	yrSceneFile_close(sf);
	//open file
	int err = file_openscene(s, path);
	if(err) { free(path); return;}
	//actually open
	yrSystem_set_scenefilename(path);
	free(path);
	//update ui
	struct filescreen_data* fsd = s->data;
	fsd->lastfile = yrSystem_get_scenefilename();
	file_enablemenu(s, 1);
}

void file_cb_browse(yrUiScreen s)
{
	const char* filtername = "Virtual Idea Area Scene";
	const char* filterspec = "*.ysf";
	char* path = yrFile_path_dialog(yrFDlgID_Scene, 0, 0, 1,&filtername, &filterspec, "ysf");
	yrUiScreen_reset(s); //pathdialog eats mouseup
	if(!path) return;
	//open file
	int err = file_openscene(s, path);
	if(err) { free(path); return;}
	//actually open
	yrSystem_set_scenefilename(path);
	free(path);
	//update ui
	struct filescreen_data* fsd = s->data;
	fsd->lastfile = yrSystem_get_scenefilename();
	file_enablemenu(s, 1);
}

static int link_file(yrUiScreen s)
{
	yrUiElem e = NULL;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_vr");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_vr\" on file screen."); return -1;}
	s->cb_elems[0] = e;
	s->cb_funcs[0] = menu_cb_vrmode;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_backgrounds");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_backgrounds\" on file screen."); return -1;}
	s->cb_elems[1] = e;
	s->cb_funcs[1] = menu_cb_backgrounds;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_export");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_export\" on file screen."); return -1;}
	s->cb_elems[2] = e;
	s->cb_funcs[2] = menu_cb_export;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_import");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_import\" on file screen."); return -1;}
	s->cb_elems[3] = e;
	s->cb_funcs[3] = menu_cb_import;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "file_open");
	if(!e) { yrLog(0, "Screen link error: missing \"file_open\" on file screen."); return -1;}
	s->cb_elems[4] = e;
	s->cb_funcs[4] = file_cb_open;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "file_new");
	if(!e) { yrLog(0, "Screen link error: missing \"file_new\" on file screen."); return -1;}
	s->cb_elems[5] = e;
	s->cb_funcs[5] = file_cb_new;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "file_browse");
	if(!e) { yrLog(0, "Screen link error: missing \"file_browse\" on file screen."); return -1;}
	s->cb_elems[6] = e;
	s->cb_funcs[6] = file_cb_browse;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "file_scenename");
	if(!e) { yrLog(0, "Screen link error: missing \"file_scenename\" on vrmode screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"file_scenename\" must be of type 'text' on file screen."); return -1;}

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "file_created");
	if(!e) { yrLog(0, "Screen link error: missing \"file_created\" on vrmode screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"file_created\" must be of type 'text' on file screen."); return -1;}

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "file_modified");
	if(!e) { yrLog(0, "Screen link error: missing \"file_modified\" on vrmode screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"file_modified\" must be of type 'text' on file screen."); return -1;}

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "file_size");
	if(!e) { yrLog(0, "Screen link error: missing \"file_size\" on vrmode screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"file_size\" must of be type 'text' on file screen."); return -1;}

	unsigned cboff = 7;
	s->data = malloc(sizeof(struct filescreen_data));
	if(!s->data) { yrLog(0, "Out of memory"); return -1;}
	struct filescreen_data* fsd = s->data;
	fsd->lastfile = (const char*) 0xFFFFFFFFFFFFFFFFull;
	fsd->selected = 8;
	fsd->filelist[0] = (yrUiElem) yrStringmap_lookup(s->idmap, "file_recent0");
	fsd->filelist[1] = (yrUiElem) yrStringmap_lookup(s->idmap, "file_recent1");
	fsd->filelist[2] = (yrUiElem) yrStringmap_lookup(s->idmap, "file_recent2");
	fsd->filelist[3] = (yrUiElem) yrStringmap_lookup(s->idmap, "file_recent3");
	fsd->filelist[4] = (yrUiElem) yrStringmap_lookup(s->idmap, "file_recent4");
	fsd->filelist[5] = (yrUiElem) yrStringmap_lookup(s->idmap, "file_recent5");
	fsd->filelist[6] = (yrUiElem) yrStringmap_lookup(s->idmap, "file_recent6");
	fsd->filelist[7] = (yrUiElem) yrStringmap_lookup(s->idmap, "file_recent7");
	for(unsigned i = 0; i < 8; ++i) {
		int ok = 1;
		if(ok && !fsd->filelist[i])					{ yrLog(0, "Screen link error: missing one of \"file_recent0-7\" on file screen."); ok = 0; }
		if(ok && fsd->filelist[i]->type != etRect)	{ yrLog(0, "Screen link error: \"file_recentx\" must of type 'rect' on file screen."); ok = 0; }
		yrUiElemRect r = (yrUiElemRect) fsd->filelist[i];
		if(ok && r->childcount != 2) { yrLog(0, "Screen link error: \"file_recentx\" must have exactly 2 children."); ok = 0; }
		if(ok && r->children[0]->type != etText) { yrLog(0, "Screen link error: \"file_recentx\"'s first child must be of type 'text'."); ok = 0; }
		if(ok && r->children[1]->type != etText) { yrLog(0, "Screen link error: \"file_recentx\"'s second child must be of type 'text'."); ok = 0; }
		if(!ok) {
			free(s->data);
			s->data = NULL;
			return -1;
		}
		s->cb_elems[cboff+i] = fsd->filelist[i];
		s->cb_funcs[cboff+i] = file_cb_recent[i];
	}
	file_enablemenu(s, 0);

	return 0;
}

static void update_file(yrUiScreen s)
{
	struct filescreen_data* fsd = s->data;
	const char* file = yrSystem_get_scenefilename();
	if(fsd->lastfile != file) { //TODO: not 100% waterproof (but saves an alloc every change and a strcmp every frame)
		fsd->selected = 8;
		fsd->lastfile = file;
		if(file) {
			file_openscene(s, file);
			file_enablemenu(s, 1);
		}
		else {
			yrUiElem ueheader =		(yrUiElem) yrStringmap_lookup(s->idmap, "file_scenename");
			yrUiElem uecreated =	(yrUiElem) yrStringmap_lookup(s->idmap, "file_created");
			yrUiElem uemodified =	(yrUiElem) yrStringmap_lookup(s->idmap, "file_modified");
			yrUiElem uesize =		(yrUiElem) yrStringmap_lookup(s->idmap, "file_size");
			yrUiElemText_set_text(ueheader, yrLocalise_get_string("file_NoFile"));
			yrUiElemText_set_text(uecreated, yrLocalise_get_string("file_NA"));
			yrUiElemText_set_text(uemodified, yrLocalise_get_string("file_NA"));
			yrUiElemText_set_text(uesize, yrLocalise_get_string("file_NA"));
			file_enablemenu(s, 0);
			file_refreshlist(s);
		}
	}
}

/********************
* SCREEN: Backgrounds
*********************/

static void backgrounds_enable_buttons(yrUiScreen s, int enable)
{
	yrUiElem_set_enabled(s->cb_elems[4], enable);
	yrUiElem_set_enabled(s->cb_elems[8], enable);
}

void backgrounds_cb_undo(yrUiScreen s)
{
	s->data = (void*)(uint64_t)(1);
}

void backgrounds_cb_remove(yrUiScreen s)
{
	yrUiElem bgl = (yrUiElem) yrStringmap_lookup(s->idmap, "bgs_list");
	yrUiElemBgList_remove_selected(bgl);
	backgrounds_enable_buttons(s, 1);
}

void backgrounds_cb_replace(yrUiScreen s)
{
	yrUiElem bgl = (yrUiElem) yrStringmap_lookup(s->idmap, "bgs_list");
	yrUiElemBgList_replace_selected(bgl);
	backgrounds_enable_buttons(s, 1);
}

void backgrounds_cb_add(yrUiScreen s)
{
	yrUiElem bgl = (yrUiElem) yrStringmap_lookup(s->idmap, "bgs_list");
	yrUiElemBgList_add(bgl);
	backgrounds_enable_buttons(s, 1);
}

void backgrounds_cb_save(yrUiScreen s)
{
	yrUiElem bgl = (yrUiElem) yrStringmap_lookup(s->idmap, "bgs_list");
	yrUiElemBgList_savetofile(bgl, yrSystem_get_scenefilename());
	backgrounds_enable_buttons(s, 0);
}

static int link_backgrounds(yrUiScreen s)
{
	yrUiElem e = NULL;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_vr");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_vr\" on backgrounds screen."); return -1;}
	s->cb_elems[0] = e;
	s->cb_funcs[0] = menu_cb_vrmode;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_file");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_file\" on backgrounds screen."); return -1;}
	s->cb_elems[1] = e;
	s->cb_funcs[1] = menu_cb_file;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_export");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_export\" on backgrounds screen."); return -1;}
	s->cb_elems[2] = e;
	s->cb_funcs[2] = menu_cb_export;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_import");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_import\" on backgrounds screen."); return -1;}
	s->cb_elems[3] = e;
	s->cb_funcs[3] = menu_cb_import;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "bgs_undo");
	if(!e) { yrLog(0, "Screen link error: missing \"bgs_undo\" on backgrounds screen."); return -1;}
	s->cb_elems[4] = e;
	s->cb_funcs[4] = backgrounds_cb_undo;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "bgs_delete");
	if(!e) { yrLog(0, "Screen link error: missing \"bgs_delete\" on backgrounds screen."); return -1;}
	s->cb_elems[5] = e;
	s->cb_funcs[5] = backgrounds_cb_remove;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "bgs_replace");
	if(!e) { yrLog(0, "Screen link error: missing \"bgs_replace\" on backgrounds screen."); return -1;}
	s->cb_elems[6] = e;
	s->cb_funcs[6] = backgrounds_cb_replace;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "bgs_add");
	if(!e) { yrLog(0, "Screen link error: missing \"bgs_add\" on backgrounds screen."); return -1;}
	s->cb_elems[7] = e;
	s->cb_funcs[7] = backgrounds_cb_add;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "bgs_save");
	if(!e) { yrLog(0, "Screen link error: missing \"bgs_save\" on backgrounds screen."); return -1;}
	s->cb_elems[8] = e;
	s->cb_funcs[8] = backgrounds_cb_save;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "bgs_list");
	if(!e) { yrLog(0, "Screen link error: missing \"bgs_list\" on backgrounds screen."); return -1;}
	if(e->type != etBgList) { yrLog(0, "Screen link error: \"bgs_list\" must be of type 'bglist' on backgrounds screen."); return -1;}

	s->data = (void*)(uint64_t)(1);

	return 0;
}

static void update_backgrounds(yrUiScreen s)
{
	//init backgrounds list
	if(s->data)
	{
		yrUiElem bgl = (yrUiElem) yrStringmap_lookup(s->idmap, "bgs_list");
		yrUiElemBgList_populate(bgl, yrSystem_get_scenefilename());
		s->data = NULL;
		backgrounds_enable_buttons(s, 0);
	}
}

/***************
* SCREEN: Export
****************/
static struct
{
	int consumed;
	enum QEColor bg_color;
	int bg_image;
	float range;
	char* folder;
}
export_options = {0};

struct export_data
{
	yrUiElem radio_color[3];
	yrUiElem radio_image[2];
	yrUiElem radio_filter[2];
	yrUiElem edit_range;
	yrUiElem label_path;
	char* folder;
	int color;
	int image;
	int filter;
	float range;
};

void export_cb_browse(yrUiScreen s)
{
	struct export_data* ed = s->data;
	//dialog
	char* path = yrFile_path_dialog(yrFDlgID_Export, 0, 1, 0, NULL, NULL, NULL);
	yrUiScreen_reset(s); //pathdialog eats mouseup
	if(!path) return;
	//replace path
	free(ed->folder);
	ed->folder = path;
	//update ui
	yrUiElemText_set_text(ed->label_path, ed->folder);
	yrUiScreen_resize(s, s->scale, s->ar);
}

void export_cb_bgcolor_original(yrUiScreen s)
{
	struct export_data* ed = s->data;
	ed->color = 0;
	yrUiElemCheck_set(ed->radio_color[0], 1);
	yrUiElemCheck_set(ed->radio_color[1], 0);
	yrUiElemCheck_set(ed->radio_color[2], 0);
	yrUiElemEdit_input_enabled(ed->edit_range, 0);
}

void export_cb_bgcolor_transparent(yrUiScreen s)
{
	struct export_data* ed = s->data;
	ed->color = 1;
	yrUiElemCheck_set(ed->radio_color[0], 0);
	yrUiElemCheck_set(ed->radio_color[1], 1);
	yrUiElemCheck_set(ed->radio_color[2], 0);
	yrUiElemEdit_input_enabled(ed->edit_range, 0);
}

void export_cb_bgcolor_white(yrUiScreen s)
{
	struct export_data* ed = s->data;
	ed->color = 2;
	yrUiElemCheck_set(ed->radio_color[0], 0);
	yrUiElemCheck_set(ed->radio_color[1], 0);
	yrUiElemCheck_set(ed->radio_color[2], 1);
	yrUiElemEdit_input_enabled(ed->edit_range, 0);
}

void export_cb_bgimg_on(yrUiScreen s)
{
	struct export_data* ed = s->data;
	ed->image = 0;
	yrUiElemCheck_set(ed->radio_image[0], 1);
	yrUiElemCheck_set(ed->radio_image[1], 0);
	yrUiElemEdit_input_enabled(ed->edit_range, 0);
}

void export_cb_bgimg_off(yrUiScreen s)
{
	struct export_data* ed = s->data;
	ed->image = 1;
	yrUiElemCheck_set(ed->radio_image[0], 0);
	yrUiElemCheck_set(ed->radio_image[1], 1);
	yrUiElemEdit_input_enabled(ed->edit_range, 0);
}

void export_cb_filterall(yrUiScreen s)
{
	struct export_data* ed = s->data;
	ed->filter = 0;
	yrUiElemCheck_set(ed->radio_filter[0], 1);
	yrUiElemCheck_set(ed->radio_filter[1], 0);
	yrUiElemEdit_input_enabled(ed->edit_range, 0);
}

void export_cb_filterrange(yrUiScreen s)
{
	struct export_data* ed = s->data;
	ed->filter = 1;
	yrUiElemCheck_set(ed->radio_filter[0], 0);
	yrUiElemCheck_set(ed->radio_filter[1], 1);
	yrUiElemEdit_input_enabled(ed->edit_range, 0);
}

void export_cb_editfocus(yrUiScreen s)
{
	struct export_data* ed = s->data;
	yrUiElemEdit_input_enabled(ed->edit_range, 1);
}

void export_cb_export(yrUiScreen s)
{
	struct export_data* ed = s->data;
	if(!ed->folder) {
		yr_msgbox(yrLocalise_get_string("err_SelectFolder"));
		return;
	}
	yrLocalSettings_export_setdir(ed->folder);
	const enum QEColor opt_color[3] = {qecOriginal, qecTransparent, qecWhite};
	const int opt_image[2] = {1, 0};
	export_options.folder = ed->folder;
	export_options.range = ed->filter ? strtof(((yrUiElemEdit) ed->edit_range)->buffer, NULL) : -1.0f;
	export_options.bg_color = opt_color[ed->color];
	export_options.bg_image = opt_image[ed->image];
	export_options.consumed = 0;
	s->next = usExportProgress;
}

static yrUiElem getradio(yrUiElem e, const char* name)
{
	if(e->type != etRect) { yrLog(0, "Screen link error: \"%s\" must be of type 'rect' on screen.", name); return NULL;}
	yrUiElemRect r = (yrUiElemRect) e;
	if(r->childcount == 0) { yrLog(0, "Screen link error: \"%s\" must have at least one child.", name); return NULL;}
	if(r->children[0]->type != etCheck) { yrLog(0, "Screen link error: the first child of \"%s\" must be of type 'check'.", name); return NULL;}
	return r->children[0];
}

static int link_export(yrUiScreen s)
{
	yrUiElem e = NULL;
	s->data = calloc(1, sizeof(struct export_data));
	if(!s->data) {yrLog(0, "Out of memory"); return -1;}
	struct export_data* ed = s->data;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_vr");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_vr\" on export screen."); return -1;}
	s->cb_elems[0] = e;
	s->cb_funcs[0] = menu_cb_vrmode;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_file");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_file\" on export screen."); return -1;}
	s->cb_elems[1] = e;
	s->cb_funcs[1] = menu_cb_file;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_backgrounds");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_backgrounds\" on export screen."); return -1;}
	s->cb_elems[2] = e;
	s->cb_funcs[2] = menu_cb_backgrounds;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_import");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_import\" on export screen."); return -1;}
	s->cb_elems[3] = e;
	s->cb_funcs[3] = menu_cb_import;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "exp_pathbutton");
	if(!e) { yrLog(0, "Screen link error: missing \"exp_pathbutton\" on export screen."); return -1;}
	s->cb_elems[4] = e;
	s->cb_funcs[4] = export_cb_browse;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "exp_bgoriginal");
	if(!e) { yrLog(0, "Screen link error: missing \"exp_bgoriginal\" on export screen."); return -1;}
	s->cb_elems[5] = e;
	s->cb_funcs[5] = export_cb_bgcolor_original;
	ed->radio_color[0] = getradio(e, "exp_bgoriginal");
	if(!ed->radio_color[0]) return -1;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "exp_bgtransparent");
	if(!e) { yrLog(0, "Screen link error: missing \"exp_bgtransparent\" on export screen."); return -1;}
	s->cb_elems[6] = e;
	s->cb_funcs[6] = export_cb_bgcolor_transparent;
	ed->radio_color[1] = getradio(e, "exp_bgtransparent");
	if(!ed->radio_color[1]) return -1;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "exp_bgwhite");
	if(!e) { yrLog(0, "Screen link error: missing \"exp_bgwhite\" on export screen."); return -1;}
	s->cb_elems[7] = e;
	s->cb_funcs[7] = export_cb_bgcolor_white;
	ed->radio_color[2] = getradio(e, "exp_bgwhite");
	if(!ed->radio_color[2]) return -1;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "exp_bgshow");
	if(!e) { yrLog(0, "Screen link error: missing \"exp_bgshow\" on export screen."); return -1;}
	s->cb_elems[8] = e;
	s->cb_funcs[8] = export_cb_bgimg_on;
	ed->radio_image[0] = getradio(e, "exp_bgshow");
	if(!ed->radio_image[0]) return -1;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "exp_bghide");
	if(!e) { yrLog(0, "Screen link error: missing \"exp_bghide\" on export screen."); return -1;}
	s->cb_elems[9] = e;
	s->cb_funcs[9] = export_cb_bgimg_off;
	ed->radio_image[1] = getradio(e, "exp_bghide");
	if(!ed->radio_image[1]) return -1;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "exp_rangeall");
	if(!e) { yrLog(0, "Screen link error: missing \"exp_rangeall\" on export screen."); return -1;}
	s->cb_elems[10] = e;
	s->cb_funcs[10] = export_cb_filterall;
	ed->radio_filter[0] = getradio(e, "exp_rangeall");
	if(!ed->radio_filter[0]) return -1;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "exp_rangerange");
	if(!e) { yrLog(0, "Screen link error: missing \"exp_rangerange\" on export screen."); return -1;}
	s->cb_elems[11] = e;
	s->cb_funcs[11] = export_cb_filterrange;
	ed->radio_filter[1] = getradio(e, "exp_rangerange");
	if(!ed->radio_filter[1]) return -1;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "exp_rangeinput");
	if(!e) { yrLog(0, "Screen link error: missing \"exp_rangeinput\" on export screen."); return -1;}
	if(e->type != etEdit) { yrLog(0, "Screen link error: \"exp_rangeinput\" must be of type 'edit' on export screen."); return -1;}
	s->cb_elems[12] = e;
	s->cb_funcs[12] = export_cb_editfocus;
	ed->edit_range = e;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "exp_export");
	if(!e) { yrLog(0, "Screen link error: missing \"exp_export\" on export screen."); return -1;}
	s->cb_elems[13] = e;
	s->cb_funcs[13] = export_cb_export;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "exp_pathlabel");
	if(!e) { yrLog(0, "Screen link error: missing \"exp_pathlabel\" on export screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"exp_pathlabel\" must be of type 'text' on export screen."); return -1;}
	ed->label_path = e;

	const char* last_folder = yrLocalSettings_export_getdir();
	ed->folder = last_folder ? _strdup(last_folder) : NULL;
	yrUiElemText_set_text(ed->label_path, ed->folder ? ed->folder : yrLocalise_get_string("exp_NoPath"));
	export_cb_bgcolor_original(s);
	export_cb_bgimg_on(s);
	export_cb_filterall(s);
	((yrUiElemEdit) ed->edit_range)->buffer[0] = '5'; //parser should have guaranteed a non-zero capacity
	((yrUiElemEdit) ed->edit_range)->buffer[1] = 0;	
	((yrUiElemEdit) ed->edit_range)->caretpos = 1;

	return 0;
}

static void update_export(yrUiScreen s)
{
	;//nothing
}

/************************
* SCREEN: Export Progress
*************************/
struct export_progress_data
{
	yrQuadExporter* exporter;
	enum QEStatus laststatus;
	size_t total;
	size_t processed;
	size_t exported;
	size_t ignored;
	size_t failed;
	yrUiElem progress;
	yrUiElem label_status;
	yrUiElem label_processed;
	yrUiElem label_exported;
	yrUiElem label_ignored;
	yrUiElem label_failed;
	yrUiElem button;
};

static const char* expp_statustext[qes_end] = {
	"expp_Progressing",
	"expp_InitFail",
	"expp_FailFinish",
	"expp_Finished",
	"expp_Cancelled",
};

static void expp_updatelabels(yrUiScreen s)
{
	struct export_progress_data* epd = (struct export_progress_data*) s->data;
	char numbuf[] = "18446744073709551616";

	yrUiElemText_set_text(epd->label_status, yrLocalise_get_string(expp_statustext[epd->laststatus]));
	sprintf(numbuf, "%zu", epd->processed);
	yrUiElemText_set_text(epd->label_processed, numbuf);
	sprintf(numbuf, "%zu", epd->exported);
	yrUiElemText_set_text(epd->label_exported, numbuf);
	sprintf(numbuf, "%zu", epd->ignored);
	yrUiElemText_set_text(epd->label_ignored, numbuf);
	sprintf(numbuf, "%zu", epd->failed);
	yrUiElemText_set_text(epd->label_failed, numbuf);

	if(epd->laststatus == qesBusy || epd->laststatus == qesCancelled) {
		yrUiElemProgress_set_progress(epd->progress, (float) epd->processed / (epd->total ? epd->total : 1));
	} else if(epd->laststatus == qesFinished || epd->laststatus == qesFailed) {
		yrUiElemProgress_set_progress(epd->progress, 1.0f);
	} else {
		yrUiElemProgress_set_progress(epd->progress, 0.0f);
	}

	yrUiScreen_resize(s, s->scale, s->ar);
}

static void expp_cb_button(yrUiScreen s)
{
	struct export_progress_data* epd = (struct export_progress_data*) s->data;
	if(epd->exporter) {
		//cancel button
		yrQuadExport_cancel(epd->exporter);
		yrUiElemText_set_text(epd->label_status, yrLocalise_get_string("expp_Cancelling"));
		yrUiScreen_resize(s, s->scale, s->ar);
	} else {
		//back button
		s->next = usExport;
	}
}

static void expp_button_cancel(yrUiScreen s)
{
	yrUiElem buttontext = (yrUiElem) yrStringmap_lookup(s->idmap, "expp_buttontext");
	yrUiElemText_set_text(buttontext, yrLocalise_get_string("expp_Cancel"));
	yrUiElem menu_vr =			(yrUiElem) yrStringmap_lookup(s->idmap, "menu_vr");
	yrUiElem menu_file =		(yrUiElem) yrStringmap_lookup(s->idmap, "menu_file");
	yrUiElem menu_export =		(yrUiElem) yrStringmap_lookup(s->idmap, "menu_export");
	yrUiElem menu_import =		(yrUiElem) yrStringmap_lookup(s->idmap, "menu_import");
	yrUiElem menu_backgrounds =	(yrUiElem) yrStringmap_lookup(s->idmap, "menu_backgrounds");
	yrUiElem_set_enabled(menu_vr, 0);
	yrUiElem_set_enabled(menu_file, 0);
	yrUiElem_set_enabled(menu_export, 0);
	yrUiElem_set_enabled(menu_import, 0);
	yrUiElem_set_enabled(menu_backgrounds, 0);
	yrUiScreen_resize(s, s->scale, s->ar);
}

static void expp_button_back(yrUiScreen s)
{
	yrUiElem buttontext = (yrUiElem) yrStringmap_lookup(s->idmap, "expp_buttontext");
	yrUiElemText_set_text(buttontext, yrLocalise_get_string("expp_Back"));
	yrUiElem menu_vr =			(yrUiElem) yrStringmap_lookup(s->idmap, "menu_vr");
	yrUiElem menu_file =		(yrUiElem) yrStringmap_lookup(s->idmap, "menu_file");
	yrUiElem menu_export =		(yrUiElem) yrStringmap_lookup(s->idmap, "menu_export");
	yrUiElem menu_import =		(yrUiElem) yrStringmap_lookup(s->idmap, "menu_import");
	yrUiElem menu_backgrounds =	(yrUiElem) yrStringmap_lookup(s->idmap, "menu_backgrounds");
	yrUiElem_set_enabled(menu_vr, 1);
	yrUiElem_set_enabled(menu_file, 1);
	yrUiElem_set_enabled(menu_export, 1);
	yrUiElem_set_enabled(menu_import, 1);
	yrUiElem_set_enabled(menu_backgrounds, 1);
	yrUiScreen_resize(s, s->scale, s->ar);
}

static int link_export_progress(yrUiScreen s)
{
	yrUiElem e = NULL;
	s->data = calloc(1, sizeof(struct export_progress_data));
	if(!s->data) {yrLog(0, "Out of memory"); return -1;}
	struct export_progress_data* epd = s->data;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_vr");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_vr\" on export progress screen."); return -1;}
	s->cb_elems[0] = e;
	s->cb_funcs[0] = menu_cb_vrmode;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_file");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_file\" on export progress screen."); return -1;}
	s->cb_elems[1] = e;
	s->cb_funcs[1] = menu_cb_file;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_backgrounds");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_backgrounds\" on export progress screen."); return -1;}
	s->cb_elems[2] = e;
	s->cb_funcs[2] = menu_cb_backgrounds;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_export");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_export\" on export progress screen."); return -1;}
	s->cb_elems[2] = e;
	s->cb_funcs[2] = menu_cb_export;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_import");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_import\" on export progress screen."); return -1;}
	s->cb_elems[3] = e;
	s->cb_funcs[3] = menu_cb_import;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_import");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_import\" on export progress screen."); return -1;}
	s->cb_elems[4] = e;
	s->cb_funcs[4] = menu_cb_export;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "expp_button");
	if(!e) { yrLog(0, "Screen link error: missing \"expp_button\" on export progress screen."); return -1;}
	s->cb_elems[5] = e;
	s->cb_funcs[5] = expp_cb_button;
	epd->button = e;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "expp_progress");
	if(!e) { yrLog(0, "Screen link error: missing \"expp_progress\" on export progress screen."); return -1;}
	if(e->type != etProgress) { yrLog(0, "Screen link error: \"expp_progress\" must be of type 'progress' on vrmode screen."); return -1;}
	epd->progress = e;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "expp_status");
	if(!e) { yrLog(0, "Screen link error: missing \"expp_status\" on export progress screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"expp_status\" must be of type 'text' on vrmode screen."); return -1;}
	epd->label_status = e;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "expp_processed");
	if(!e) { yrLog(0, "Screen link error: missing \"expp_processed\" on export progress screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"expp_processed\" must be of type 'text' on vrmode screen."); return -1;}
	epd->label_processed = e;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "expp_exported");
	if(!e) { yrLog(0, "Screen link error: missing \"expp_exported\" on export progress screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"expp_exported\" must be of type 'text' on vrmode screen."); return -1;}
	epd->label_exported = e;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "expp_ignored");
	if(!e) { yrLog(0, "Screen link error: missing \"expp_ignored\" on export progress screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"expp_ignored\" must be of type 'text' on vrmode screen."); return -1;}
	epd->label_ignored = e;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "expp_failed");
	if(!e) { yrLog(0, "Screen link error: missing \"expp_failed\" on export progress screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"expp_failed\" must be of type 'text' on vrmode screen."); return -1;}
	epd->label_failed = e;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "expp_buttontext");
	if(!e) { yrLog(0, "Screen link error: missing \"expp_buttontext\" on export progress screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"expp_buttontext\" must be of type 'text' on vrmode screen."); return -1;}

	return 0;
}

static void update_export_progress(yrUiScreen s)
{
	struct export_progress_data* epd = (struct export_progress_data*) s->data;
	//export int progress
	if(epd->exporter) {
		//get latest export status
		enum QEStatus status;
		size_t total;
		size_t processed;
		size_t exported;
		size_t ignored;
		size_t failed;
		status = yrQuadExport_status(epd->exporter, &total, &processed, &exported, &ignored, &failed);
		//only redraw if something changed
		if(status != epd->laststatus ||
		   total != epd->total ||
		   processed != epd->processed ||
		   exported != epd->exported ||
		   ignored != epd->ignored ||
		   failed != epd->failed)
		{
			epd->laststatus = status;
			epd->total = total;
			epd->processed = processed;
			epd->exported = exported;
			epd->ignored = ignored;
			epd->failed = failed;
			expp_updatelabels(s);
		}
		if(status != qesBusy) {
			//cleanup exporter
			yrQuadExport_destroy(epd->exporter);
			epd->exporter = NULL;
			expp_button_back(s);
		}
	//no export in progress
	} else {
		//an export job is requested, start it
		if(!export_options.consumed) {
			export_options.consumed = 1;
			epd->exporter = yrQuadExport_init(yrSystem_get_scenefilename(),
											  export_options.folder,
											  export_options.range,
											  export_options.bg_color,
											  export_options.bg_image);
			if(epd->exporter) {
				expp_button_cancel(s);
			} else {
				epd->laststatus = qesInitFailed;
				epd->total = 1;
				epd->processed = 0;
				epd->exported = 0;
				epd->ignored = 0;
				epd->failed = 0;
				expp_updatelabels(s);
			}
		}
	}
}

/***************
* SCREEN: Import
****************/
struct import_data
{
	yrUiElem radio_w[2];
	yrUiElem radio_h[2];
	yrUiElem edit_w;
	yrUiElem edit_h;
	yrUiElem label_path;
	yrUiElem label_status;
	char* image;
	unsigned img_w;
	unsigned img_h;
	unsigned max_w;
	unsigned max_h;
	unsigned width;
	unsigned height;
	int auto_w;
	int auto_h;
	int lock;
};

static void import_refresh_size(yrUiScreen s)
{
	struct import_data* id = s->data;
	if(id->lock) return;
	//get values
	unsigned long edit_w = strtoul(((yrUiElemEdit)id->edit_w)->buffer, NULL, 10);
	unsigned long edit_h = strtoul(((yrUiElemEdit)id->edit_h)->buffer, NULL, 10);
	//adjust values
	if(!id->auto_w && !id->auto_h)
	{
		//just clamp
		if(edit_w < 1) edit_w = 1;
		if(edit_h < 1) edit_h = 1;
		if(edit_w > 2048) edit_w = 2048;
		if(edit_h > 2048) edit_h = 2048;
	}
	else {
		//autocalc
		if(id->auto_w && id->auto_h)
		{
			edit_w = id->img_w;
			edit_h = id->img_h;
		}
		else if(id->auto_w)
		{
			uint64_t foo = id->img_w * edit_h / id->img_h;
			if(foo > UINT32_MAX) foo = UINT32_MAX;
			if(foo < 1) foo = 1;
			edit_w = (unsigned) foo;
		}
		else {
			uint64_t foo = id->img_h * edit_w / id->img_w;
			if(foo > UINT32_MAX) foo = UINT32_MAX;
			if(foo < 1) foo = 1;
			edit_h = (unsigned) foo;
		}
		//fit
		if(edit_w > id->max_w) {
			edit_w = id->max_w;
			edit_h = id->max_h;
		}
	}
	//insert
	id->width = edit_w;
	id->height = edit_h;
	sprintf(((yrUiElemEdit)id->edit_w)->buffer, "%u", id->width);
	sprintf(((yrUiElemEdit)id->edit_h)->buffer, "%u", id->height);

	yrUiElemEdit_input_enabled(id->edit_w, 0);
	yrUiElemEdit_input_enabled(id->edit_h, 0);
}

void import_cb_image(yrUiScreen s)
{
	struct import_data* id = s->data;
	if(id->lock) return;
	//pick image
	const char* filtername = "Image file";
	const char* filterspec = "*.*";
	char* path = yrFile_path_dialog(yrFDlgID_Import, 0, 0, 1,&filtername, &filterspec, NULL);
	yrUiScreen_reset(s); //pathdialog eats mouseup
	if(!path) return;
	free(id->image);
	id->image = path;
	yrUiElemText_set_text(id->label_path, path);
	yrUiElem_set_enabled(s->cb_elems[9], 1);
	//read image header for size
	unsigned w = 0;
	unsigned h = 0;
	const char* errtxt = yrImgLoad_get_dims(path, &w, &h);
	if(errtxt) {yrLog(0,"Could not read image size: %s", errtxt); goto onerror;}
	if(!w) w = 1;
	if(!h) h = 1;
	//set w and h
	id->img_w = w;
	id->img_h = h;
	if(w > h) {
		id->max_w = 2048;
		id->max_h = (unsigned)(((int64_t) 2048 * id->img_h) / id->img_w);
		if(id->max_h < 1) id->max_h = 1;
	} else {
		id->max_h = 2048;
		id->max_w = (unsigned)(((int64_t) 2048 * id->img_w) / id->img_h);
		if(id->max_w < 1) id->max_w = 1;
	}
	//done
	import_refresh_size(s);
	yrUiScreen_resize(s, s->scale, s->ar);
	return;
onerror:
	free(id->image);
	id->image = NULL;
	yrUiElemText_set_text(id->label_path, yrLocalise_get_string("imp_NoPath"));;
	yrUiElemText_set_text(id->label_status, yrLocalise_get_string("imp_ErrFile"));;
	yrUiElem_set_enabled(s->cb_elems[9], 0);
	yrUiScreen_resize(s, s->scale, s->ar);
}

void import_cb_w_auto(yrUiScreen s)
{
	struct import_data* id = s->data;
	if(id->lock) return;
	id->auto_w = 1;
	yrUiElemCheck_set(id->radio_w[0], 1);
	yrUiElemCheck_set(id->radio_w[1], 0);
	import_refresh_size(s);
	yrUiScreen_resize(s, s->scale, s->ar);
}

void import_cb_w_manual(yrUiScreen s)
{
	struct import_data* id = s->data;
	if(id->lock) return;
	id->auto_w = 0;
	yrUiElemCheck_set(id->radio_w[0], 0);
	yrUiElemCheck_set(id->radio_w[1], 1);
	import_refresh_size(s);
	yrUiElemEdit_input_enabled(id->edit_w, 1);
	yrUiScreen_resize(s, s->scale, s->ar);
}

void import_cb_h_auto(yrUiScreen s)
{
	struct import_data* id = s->data;
	if(id->lock) return;
	id->auto_h = 1;
	yrUiElemCheck_set(id->radio_h[0], 1);
	yrUiElemCheck_set(id->radio_h[1], 0);
	import_refresh_size(s);
	yrUiScreen_resize(s, s->scale, s->ar);
}

void import_cb_h_manual(yrUiScreen s)
{
	struct import_data* id = s->data;
	if(id->lock) return;
	id->auto_h = 0;
	yrUiElemCheck_set(id->radio_h[0], 0);
	yrUiElemCheck_set(id->radio_h[1], 1);
	import_refresh_size(s);
	yrUiElemEdit_input_enabled(id->edit_h, 1);
	yrUiScreen_resize(s, s->scale, s->ar);
}

void import_cb_import(yrUiScreen s)
{
	struct import_data* id = s->data;
	if(id->lock) return;
	import_refresh_size(s);
	if(id->image && id->width && id->height)
	{
		const char* fstr = yrLocalise_get_string("imp_Busy");
		char* dstr = _strdup(fstr); //works because strlen("1.000")<=strlen("%d.%03d")
		if(dstr) {
			unsigned w = (id->width > 50) ? id->width : 50;
			unsigned h = (id->height > 50) ? id->height : 50;
			sprintf(dstr, fstr, w/1000, w%1000, h/1000, h%1000);
			yrUiElemText_set_text(id->label_status, dstr);
			free(dstr);
			yrUiScreen_resize(s, s->scale, s->ar);
		}
		id->lock = 2;
	}
}

static int link_import(yrUiScreen s)
{
	yrUiElem e = NULL;
	s->data = calloc(1, sizeof(struct import_data));
	if(!s->data) {yrLog(0, "Out of memory"); return -1;}
	struct import_data* id = s->data;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_vr");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_vr\" on import screen."); return -1;}
	s->cb_elems[0] = e;
	s->cb_funcs[0] = menu_cb_vrmode;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_file");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_file\" on import screen."); return -1;}
	s->cb_elems[1] = e;
	s->cb_funcs[1] = menu_cb_file;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_backgrounds");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_backgrounds\" on import screen."); return -1;}
	s->cb_elems[2] = e;
	s->cb_funcs[2] = menu_cb_backgrounds;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "menu_export");
	if(!e) { yrLog(0, "Screen link error: missing \"menu_export\" on import screen."); return -1;}
	s->cb_elems[3] = e;
	s->cb_funcs[3] = menu_cb_export;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "imp_pathbutton");
	if(!e) { yrLog(0, "Screen link error: missing \"imp_pathbutton\" on import screen."); return -1;}
	s->cb_elems[4] = e;
	s->cb_funcs[4] = import_cb_image;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "imp_w_auto");
	if(!e) { yrLog(0, "Screen link error: missing \"imp_w_auto\" on import screen."); return -1;}
	s->cb_elems[5] = e;
	s->cb_funcs[5] = import_cb_w_auto;
	id->radio_w[0] = getradio(e, "imp_w_auto");
	if(!id->radio_w[0]) return -1;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "imp_w_specify");
	if(!e) { yrLog(0, "Screen link error: missing \"imp_w_specify\" on import screen."); return -1;}
	s->cb_elems[6] = e;
	s->cb_funcs[6] = import_cb_w_manual;
	id->radio_w[1] = getradio(e, "imp_w_specify");
	if(!id->radio_w[1]) return -1;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "imp_h_auto");
	if(!e) { yrLog(0, "Screen link error: missing \"imp_h_auto\" on import screen."); return -1;}
	s->cb_elems[7] = e;
	s->cb_funcs[7] = import_cb_h_auto;
	id->radio_h[0] = getradio(e, "imp_h_auto");
	if(!id->radio_h[0]) return -1;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "imp_h_specify");
	if(!e) { yrLog(0, "Screen link error: missing \"imp_h_specify\" on import screen."); return -1;}
	s->cb_elems[8] = e;
	s->cb_funcs[8] = import_cb_h_manual;
	id->radio_h[1] = getradio(e, "imp_h_specify");
	if(!id->radio_h[1]) return -1;
	
	e = (yrUiElem) yrStringmap_lookup(s->idmap, "imp_w_input");
	if(!e) { yrLog(0, "Screen link error: missing \"imp_w_input\" on import screen."); return -1;}
	if(e->type != etEdit) { yrLog(0, "Screen link error: \"imp_w_input\" must be of type 'edit' on import screen."); return -1;}
	id->edit_w = e;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "imp_h_input");
	if(!e) { yrLog(0, "Screen link error: missing \"imp_h_input\" on import screen."); return -1;}
	if(e->type != etEdit) { yrLog(0, "Screen link error: \"imp_h_input\" must be of type 'edit' on import screen."); return -1;}
	id->edit_h = e;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "imp_import");
	if(!e) { yrLog(0, "Screen link error: missing \"imp_import\" on import screen."); return -1;}
	s->cb_elems[9] = e;
	s->cb_funcs[9] = import_cb_import;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "imp_pathlabel");
	if(!e) { yrLog(0, "Screen link error: missing \"imp_pathlabel\" on import screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"imp_pathlabel\" must be of type 'text' on import screen."); return -1;}
	id->label_path = e;

	e = (yrUiElem) yrStringmap_lookup(s->idmap, "imp_status");
	if(!e) { yrLog(0, "Screen link error: missing \"imp_status\" on import screen."); return -1;}
	if(e->type != etText) { yrLog(0, "Screen link error: \"imp_status\" must be of type 'text' on import screen."); return -1;}
	id->label_status = e;

	id->height = 500;
	id->width = 500;
	id->img_w = 500;
	id->img_h = 500;
	id->max_w = 2048;
	id->max_h = 2048;
	if(((yrUiElemEdit) id->edit_w)->capacity >= 4) {
		((yrUiElemEdit) id->edit_w)->buffer[0] = '5';
		((yrUiElemEdit) id->edit_w)->buffer[1] = '0';
		((yrUiElemEdit) id->edit_w)->buffer[2] = '0';
		((yrUiElemEdit) id->edit_w)->buffer[3] = 0;	
		((yrUiElemEdit) id->edit_w)->caretpos = 3;
	} else { yrLog(0, "Screen link error: \"imp_w_input\" must have a capacity of at least 4."); return -1;}
	if(((yrUiElemEdit) id->edit_h)->capacity >= 4) {
		((yrUiElemEdit) id->edit_h)->buffer[0] = '5';
		((yrUiElemEdit) id->edit_h)->buffer[1] = '0';
		((yrUiElemEdit) id->edit_h)->buffer[2] = '0';
		((yrUiElemEdit) id->edit_h)->buffer[3] = 0;	
		((yrUiElemEdit) id->edit_h)->caretpos = 3;
	} else { yrLog(0, "Screen link error: \"imp_h_input\" must have a capacity of at least 4."); return -1;}
	id->auto_w = 1;
	yrUiElemCheck_set(id->radio_w[0], 1);
	yrUiElemCheck_set(id->radio_w[1], 0);
	id->auto_h = 1;
	yrUiElemCheck_set(id->radio_h[0], 1);
	yrUiElemCheck_set(id->radio_h[1], 0);
	yrUiElem_set_enabled(s->cb_elems[9], 0);

	return 0;
}

static void update_import(yrUiScreen s)
{
	struct import_data* id = s->data;
	if(id->lock) {
		id->lock -= 1;
		if(id->lock) return; //some delay to allow for status message display
		//do import
		int err = yrQuadImport_single(yrSystem_get_scenefilename(), id->image, id->width, id->height);
		const char* status;
		if(!err) status = yrLocalise_get_string("imp_Done");
		else {
			switch(err) {
				case ERRIMP_FILE:	status = yrLocalise_get_string("imp_ErrFile"); break;
				case ERRIMP_MEM:	status = yrLocalise_get_string("imp_ErrMem"); break;
				default:			status = yrLocalise_get_string("imp_ErrGeneric"); break;
			}
		}
		yrUiElemText_set_text(id->label_status, status);
		yrUiScreen_resize(s, s->scale, s->ar);
	}
}

//final cleanup function
static void yrUiScreen_cleanup_data(yrUiScreen s)
{
	switch(s->id) {
		case usVRMode: {
			s->data = NULL;
			break;
		}
		case usFile: {
			free(s->data);
			s->data = NULL;
			break;
		}
		case usBackgrounds: {
			s->data = NULL;
			break;
		}
		case usExport: {
			struct export_data* ed = s->data;
			free(ed->folder);
			free(s->data);
			s->data = NULL;
			break;
		}
		case usExportProgress: {
			struct export_progress_data* epd = s->data;
			if(epd->exporter) yrQuadExport_destroy(epd->exporter);
			free(s->data);
			s->data = NULL;
			break;
		}
		case usImport: {
			struct import_data* id = s->data;
			free(id->image);
			free(s->data);
			s->data = NULL;
			break;
		}
	}
}