#include "ui_internal.h"
#include <stdlib.h>
#include "sys_log.h"
#include "fontrender.h"
#include "scenefile.h"
#include "imgload.h"
#include "ogl.h"
#include "localisation.h"
#include "platform.h"
#include <math.h>
#include "sys_desktop.h"
#include <stdio.h>

#define eps 0.001f

#ifdef _DEBUG
#define GLERRCHECK {GLuint glerr = glGetError(); if(glerr) yrLog(0, "OpenGL error: %x", glerr);}
#else
#define GLERRCHECK
#endif

static uint64_t getimghandle(GLuint img)
{
	uint64_t handle = glGetTextureHandleARB(img);
	glMakeTextureHandleResidentARB(handle);
	GLERRCHECK
	return handle;
}

static const unsigned zerochars[] = {
	0x00030, 0x00660, 0x006F0, 0x007C0, 0x00966, 0x009E6, 0x00A66, 0x00AE6, 0x00B66, 0x00BE6,
	0x00C66, 0x00CE6, 0x00D66, 0x00DE6, 0x00E50, 0x00ED0, 0x01040, 0x01090, 0x017E0, 0x01810,
	0x01946, 0x019D0, 0x01A80, 0x01A90, 0x01B50, 0x01BB0, 0x01C40, 0x01C50, 0x024EA, 0x024FF,
	0x0A620, 0x0A8D0, 0x0A900, 0x0A9D0, 0x0A9F0, 0x0AA50, 0x0ABF0, 0x0FF10, 0x104A0, 0x11066,
	0x110F0, 0x11136, 0x111D0, 0x112F0, 0x11450, 0x114D0, 0x11650, 0x116C0, 0x11730, 0x118E0,
	0x11C50, 0x16A60, 0x16B50, 0x1D7CE, 0x1D7D8, 0x1D7E2, 0x1D7EC, 0x1D7F6, 0x1E950, 0x1F101,
	0x1F10B, 0x1F10C
};

void yrUiElem_destroy(yrUiElem e)
{
	GLERRCHECK
	switch(e->type)
	{
	case etRect: {
		yrUiElemRect r = (yrUiElemRect)e;
		for(unsigned i = 0; i < r->childcount; ++i) {
			yrUiElem_destroy(r->children[i]);
		}
		free(r->children);
		break;}
	case etImage: {
		yrUiElemImage i = (yrUiElemImage) e;
		if(i->handle_tex)
			glMakeTextureHandleNonResidentARB(i->handle_tex);
		glDeleteTextures(1, &i->tex);
		break;}
	case etNewline:
		break;
	case etText: {
		yrUiElemText t = (yrUiElemText) e;
		free(t->text);
		if(t->handle_bitmap)
			glMakeTextureHandleNonResidentARB(t->handle_bitmap);
		glDeleteTextures(1, &t->bitmap);
		break;}
	case etBgList: {
		yrUiElemBgList bgl = (yrUiElemBgList) e;
		for(size_t i = 0; i < bgl->count; ++i) {
			glMakeTextureHandleNonResidentARB(bgl->handle_bg_tex[i]);
		}
		glDeleteTextures(bgl->count, bgl->bg_tex);
		if(bgl->handle_scroll_icon)
			glMakeTextureHandleNonResidentARB(bgl->handle_scroll_icon);
		glDeleteTextures(1, &bgl->scroll_icon);
		break;}
	case etCheck:
		break;
	case etEdit: {
		yrUiElemEdit ed = (yrUiElemEdit)e;
		free(ed->buffer);
		if(ed->handle_bitmap)
			glMakeTextureHandleNonResidentARB(ed->handle_bitmap);
		glDeleteTextures(1, &ed->bitmap);
		break;}
	case etProgress: {
		yrUiElemProgress p = (yrUiElemProgress) e;
		if(p->handle_bitmap)
			glMakeTextureHandleNonResidentARB(p->handle_bitmap);
		glDeleteTextures(1, &p->bitmap);
		break; }
	case etMirror:
		break;
	}
	free(e);
	GLERRCHECK
}

static struct {
	size_t count;
	struct yr_ui_ssbo* ssbo_map;
}
draw = {0};

void draw_flush(GLuint ssbo)
{
	if(!draw.count) return;
	if(draw.ssbo_map) {
		glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
		draw.ssbo_map = NULL;
	}
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
	glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, (GLsizei) draw.count);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0);
	draw.count = 0;
	GLERRCHECK
}

static void draw_elem(GLuint ssbo, float w, float h, float x, float y, uint32_t fg, uint32_t bg, uint64_t handle)
{
	if(draw.count == UIRENDER_BUFFER) draw_flush(ssbo);

	if(!draw.ssbo_map) {
		glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
		draw.ssbo_map = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, UIRENDER_BUFFER * sizeof(struct yr_ui_ssbo), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT);
		if(!draw.ssbo_map) { yrLog(0, "UI draw mapbuffer failed"); return; }
	}
	
	draw.ssbo_map[draw.count].size[0] = w;
	draw.ssbo_map[draw.count].size[1] = h;
	draw.ssbo_map[draw.count].pos[0] = x;
	draw.ssbo_map[draw.count].pos[1] = y;
	draw.ssbo_map[draw.count].fg = fg;
	draw.ssbo_map[draw.count].bg = bg;
	draw.ssbo_map[draw.count].texhandle = handle;
	draw.count += 1;
	GLERRCHECK
}

static void yrUiElemRect_layout(yrUiElemRect e, float scale, float ar)
{
	float posx = e->hpad;
	float posy = e->vpad;
	float rowh = 0.0f;
	for(unsigned i = 0; i < e->childcount; ++i) {
		yrUiElem child = e->children[i];
		//newline
		if(child->type == etNewline) {
			posx = e->hpad;
			posy += rowh;
			rowh = 0.0f;
			child->x = e->x + posx;
			child->y = e->y + posy;
			child->w = e->w - 2*e->hpad;
			posy += child->h;
		}
		//standard case
		else {
			child->x = e->x + posx;
			child->y = e->y + posy;

			yrUiElem_layout(child, scale, ar);

			posx += child->w;
			rowh = (rowh > child->h) ? rowh : child->h;
			if((posx - eps) > (e->x + e->w - e->hpad)) {
				yrLog(1, "UI: horizontal rect overflow (w:%f, h:%f, #c:%u)", e->w, e->h, e->childcount);
			}
		}
	}
	posy += e->vpad;
	if((posy - eps) > (e->y + e->h)) {
		yrLog(1, "UI: vertical rect overflow (w:%f, h:%f, #c:%u)", e->w, e->h, e->childcount);
	}
	GLERRCHECK
}

static void yrUiElemImage_layout(yrUiElemImage e, float scale, float ar)
{
	;//nothing
}

static float to_px(float scale, float val) { return scale * val / 100.0f; }
static float from_px(float scale, float val) { return 100.0f * val / scale; }

static void yrUiElemText_layout(yrUiElemText e, float scale, float ar)
{
	unsigned height_px;
	unsigned width_px;
	float size_px = to_px(scale, e->size);
	float left_px = to_px(scale, e->x);
	float right_px = to_px(scale, e->x + e->w);
	unsigned w_px = (unsigned)(roundf(right_px) - roundf(left_px));

	GLuint tex = yrFontRender_render(e->text, size_px, w_px, &height_px, e->align, e->singleline, e->autowidth ? &width_px : NULL);

	if(e->bitmap) {
		glMakeTextureHandleNonResidentARB(e->handle_bitmap);
		glDeleteTextures(1, &e->bitmap);
		e->handle_bitmap = 0;
	}

	if(!tex) height_px = (unsigned) ceilf(size_px);
	e->h = from_px(scale, (float) height_px);
	if(e->autowidth) e->w = from_px(scale, (float) width_px);
	e->bitmap = tex;
	if(e->bitmap) {
		e->handle_bitmap = glGetTextureHandleARB(e->bitmap);
		glMakeTextureHandleResidentARB(e->handle_bitmap);
	}
	GLERRCHECK
}

static void yrUiElemNewline_layout(yrUiElemNewline e, float scale, float ar)
{
	;//nothing
}

static void yrUiElemMirror_layout(yrUiElemMirror e, float scale, float ar)
{
	e->scale = scale;
	e->ar = ar;
}

static void yrUiElemCheck_layout(yrUiElemCheck e, float scale, float ar)
{
	;//nothing
}

static void yrUiElemProgress_layout(yrUiElemProgress e, float scale, float ar)
{
	float anchor = e->x + e->progress * e->w;
	float twidth = e->h * 5;

	unsigned height_px;
	float size_px = to_px(scale, e->h);
	float left_px =  to_px(scale, (e->progress > 0.5f) ? (anchor - twidth) : anchor);
	float right_px = to_px(scale, (e->progress > 0.5f) ? anchor : (anchor + twidth));
	unsigned w_px = (unsigned)(roundf(right_px) - roundf(left_px));

	char text[] = "100.0%";
	float pct = e->progress * 100.0f;
	if(pct <= 0.0f) pct = 0.0f;
	if(pct > 100.0f) pct = 100.0f;
	sprintf(text, "%.1f%%", pct);
	GLuint tex = yrFontRender_render(text, size_px, w_px, &height_px, (e->progress > 0.5f) ? taRight : taLeft, 0, NULL);

	if(e->bitmap) {
		glMakeTextureHandleNonResidentARB(e->handle_bitmap);
		glDeleteTextures(1, &e->bitmap);
		e->handle_bitmap = 0;
	}

	if(!tex) height_px = (unsigned) ceilf(size_px);
	e->bitmap_h = from_px(scale, (float) height_px);
	e->bitmap = tex;
	if(e->bitmap) {
		e->handle_bitmap = glGetTextureHandleARB(e->bitmap);
		glMakeTextureHandleResidentARB(e->handle_bitmap);
	}
	GLERRCHECK
}

static void yrUiElem_render_progress(yrUiElemProgress e, uint32_t fg, struct yr_ui_rendertexes* unis)
{
	float barwth = e->progress * e->w;
	float anchor = e->x + barwth;
	float twidth = e->h * 5;
	//render bar
	draw_elem(unis->ssbo, barwth, e->h, e->x, e->y, 0, fg, unis->handle_notex);
	//render text
	float tx =    (e->progress > 0.5f) ? (anchor - twidth) : anchor;
	uint32_t tc = (e->progress > 0.5f) ? e->bg : fg;
	draw_elem(unis->ssbo, twidth, e->bitmap_h, tx, e->y, tc, 0, e->handle_bitmap);
	GLERRCHECK
}

static void yrUiElem_render_mirror(yrUiElemMirror e, uint32_t fg, struct yr_ui_rendertexes* unis)
{
	if(!(e->flags & UIELEM_ENABLED)) return;
	//clip
	float sz = ((e->w / 1.181640625f) < e->h) ? (e->w / 1.181640625f) : e->h;
	float w = sz * 1.181640625f;
	float h = sz;
	float x = e->x + (e->w - w)/2;
	float y = e->y + (e->h - h)/2;
	GLsizei scw = (GLsizei) ceilf(to_px(e->scale, w));
	GLsizei sch = (GLsizei) ceilf(to_px(e->scale, h));
	GLint scx = (GLint) floorf(					to_px(e->scale, x));
	GLint scy = (GLint) floorf(e->scale/e->ar -	to_px(e->scale, y + h));
	glScissor(scx, scy, scw, sch);
	draw_flush(unis->ssbo);
	glEnable(GL_SCISSOR_TEST);
	//render with negative margins
	draw_elem(unis->ssbo,
			  w / 0.8f, -h / 0.6f,
			  x - w/8, y + h + h/3,
			  0xFFFFFFFFul, 0xFF000000ul, unis->handle_mirror);
	//unclip
	draw_flush(unis->ssbo);
	glDisable(GL_SCISSOR_TEST);
	GLERRCHECK
}

static void yrUiElemEdit_layout(yrUiElemEdit e, float scale, float ar);
static void yrUiElem_render_edit(yrUiElemEdit e, uint32_t fg, struct yr_ui_rendertexes* unis);
static void yrUiElemBgList_layout(yrUiElemBgList e, float scale, float ar);
static void yrUiElem_render_bglistcontent(yrUiElemBgList e, uint32_t fg, struct yr_ui_rendertexes* unis);

void yrUiElem_layout(yrUiElem e, float scale, float ar)
{
	switch(e->type)
	{
		case etRect:		yrUiElemRect_layout((yrUiElemRect)e, scale, ar);		break;
		case etImage:		yrUiElemImage_layout((yrUiElemImage)e, scale, ar);		break;
		case etText:		yrUiElemText_layout((yrUiElemText)e, scale, ar);		break;
		case etNewline:		yrUiElemNewline_layout((yrUiElemNewline)e, scale, ar);	break;
		case etBgList:		yrUiElemBgList_layout((yrUiElemBgList)e, scale, ar);	break;
		case etCheck:		yrUiElemCheck_layout((yrUiElemCheck)e, scale, ar);		break;
		case etEdit:		yrUiElemEdit_layout((yrUiElemEdit)e, scale, ar);		break;
		case etProgress:	yrUiElemProgress_layout((yrUiElemProgress)e, scale, ar);	break;
		case etMirror:		yrUiElemMirror_layout((yrUiElemMirror)e, scale, ar);	break;
	}
}

void yrUiElem_render(yrUiElem e, uint32_t inherit_fg, struct yr_ui_rendertexes* unis)
{
	uint32_t fg = (e->flags & UIELEM_DOWN) ? e->fg_down : ((e->flags & UIELEM_HOVER) ? e->fg_hover : e->fg);
	uint32_t bg = (e->flags & UIELEM_DOWN) ? e->bg_down : ((e->flags & UIELEM_HOVER) ? e->bg_hover : e->bg);
	fg = (e->flags & UIELEM_ENABLED) ? fg : e->fg_disabled;
	bg = (e->flags & UIELEM_ENABLED) ? bg : e->bg_disabled;
	if(fg == color_undefined) fg = inherit_fg;

	uint64_t tex;
	int checked = e->flags & UIELEM_CHECKED;
	switch(e->type)
	{
		case etImage:	tex = ((yrUiElemImage)e)->handle_tex;							break;
		case etText:	tex = ((yrUiElemText)e)->handle_bitmap;							break;
		case etCheck:	tex = checked ? unis->handle_checktex : unis->handle_nocheck;	break;
		default: tex = unis->handle_notex;
	}

	draw_elem(unis->ssbo, e->w, e->h, e->x, e->y, fg, bg, tex);

	switch(e->type) 
	{
		case etRect:
			for(unsigned i = 0; i < ((yrUiElemRect)e)->childcount; ++i)
				yrUiElem_render(((yrUiElemRect)e)->children[i], fg, unis);
			break;
		case etBgList:
			yrUiElem_render_bglistcontent((yrUiElemBgList) e, fg, unis);
			break;
		case etEdit:
			yrUiElem_render_edit((yrUiElemEdit) e, fg, unis);
			break;
		case etProgress:
			yrUiElem_render_progress((yrUiElemProgress) e, fg, unis);
			break;
		case etMirror:
			yrUiElem_render_mirror((yrUiElemMirror) e, fg, unis);
			break;
	}
	GLERRCHECK
}

void yrUiElemBgList_mousepos(yrUiElem e, float x, float y);
void yrUiElem_mousepos(yrUiElem e, float x, float y)
{
	if(e->type == etBgList) { yrUiElemBgList_mousepos(e, x, y); return; }
	if(!(e->flags & UIELEM_ENABLED)) return;
	if(x >= e->x && x < (e->x + e->w) &&
	   y >= e->y && y < (e->y + e->h))
	{
		e->flags |= UIELEM_HOVER;
	} else {
		e->flags &= ~UIELEM_HOVER;
	}
	if(e->type == etRect)
		for(unsigned i = 0; i < ((yrUiElemRect)e)->childcount; ++i)
			yrUiElem_mousepos(((yrUiElemRect)e)->children[i], x, y);
}

void yrUiElemBgList_keyevent(yrUiElem e, unsigned keycode, int down, yrUiScreen s);
void yrUiElemEdit_keyevent(yrUiElem e, unsigned keycode, int down, yrUiScreen s);
void yrUiElem_keyevent(yrUiElem e, unsigned keycode, int down, yrUiScreen s)
{
	//TODO: focus (arrows + enter, also autoscroll?), scroll (mousewheel), maybe even hotkeys, or if i hate myself: text entry (which should then nicely tie in with vr keyboard)
	if(e->type == etBgList) { yrUiElemBgList_keyevent(e, keycode, down, s); return; }
	//mouse down
	if(down &&
	   (keycode == VK_LBUTTON) &&
	   (e->flags & UIELEM_HOVER) &&
	   !(e->flags & UIELEM_DOWN))
	{
		e->flags |= UIELEM_DOWN;
		yrUiScreen_elemcallback(s, e);
	}
	//mouse up
	if(!down &&
	   (keycode == VK_LBUTTON) &&
	   (e->flags & UIELEM_DOWN))
	{
		e->flags &= ~UIELEM_DOWN;
	}
	if(e->type == etRect)
		for(unsigned i = 0; i < ((yrUiElemRect)e)->childcount; ++i)
			yrUiElem_keyevent(((yrUiElemRect)e)->children[i], keycode, down, s);
	else if(e->type == etEdit)
		yrUiElemEdit_keyevent(e, keycode, down, s);
}

static void yrUiElemEdit_insert(yrUiElemEdit e, unsigned c);
void yrUiElem_char(yrUiElem e, unsigned c)
{
	//process
	if(e->type == etEdit) {
		yrUiElemEdit i = (yrUiElemEdit)e;
		if(!i->input_enabled) return;

		//for now assume numbers only
		//check which kind of digit (thanks unicode)
		unsigned zeroidx = 0;
		for(; zeroidx < sizeof(zerochars)/sizeof(zerochars[0]); zeroidx += 1)
			if(c < zerochars[zeroidx]) break;
		if(zeroidx == 0) return;
		zeroidx -= 1;

		//check whether it actually is a digit
		unsigned dig = c - zerochars[zeroidx];
		if(dig < 10) {
			yrUiElemEdit_insert(i, (unsigned)'0' + dig);
		}
	}
	//pass to children
	if(e->type == etRect)
		for(unsigned i = 0; i < ((yrUiElemRect)e)->childcount; ++i)
			yrUiElem_char(((yrUiElemRect)e)->children[i], c);
}

void yrUiElem_reset(yrUiElem e)
{
	e->flags &= UIELEM_ENABLED|UIELEM_CHECKED;
	if(e->type == etRect)
		for(unsigned i = 0; i < ((yrUiElemRect)e)->childcount; ++i)
			yrUiElem_reset(((yrUiElemRect)e)->children[i]);
	if(e->type == etEdit)
		yrUiElemEdit_input_enabled(e, 0);
}

void yrUiElemText_set_text(yrUiElem e, const char* text)
{
	if(e->type != etText) return;
	yrUiElemText t = (yrUiElemText)e;
	if(t->bitmap) {
		glMakeTextureHandleNonResidentARB(t->handle_bitmap);
		glDeleteTextures(1, &t->bitmap);
		t->handle_bitmap = 0;
		t->bitmap = 0;
	}
	free(t->text);
	t->text = _strdup(text);
	GLERRCHECK
}

void yrUiElemCheck_set(yrUiElem e, int checked)
{
	if(e->type != etCheck) return;
	if(checked)	e->flags |= UIELEM_CHECKED;
	else		e->flags &= ~UIELEM_CHECKED;
}

void yrUiElemProgress_set_progress(yrUiElem e, float progress)
{
	if(e->type != etProgress) return;
	((yrUiElemProgress)e)->progress = progress;
}

static void yrUiElem_force_up(yrUiElem e)
{
	e->flags &= ~(UIELEM_DOWN | UIELEM_HOVER);
	if(e->type == etRect)
		for(unsigned i = 0; i < ((yrUiElemRect)e)->childcount; ++i)
			yrUiElem_force_up(((yrUiElemRect)e)->children[i]);
}

void yrUiElem_set_enabled(yrUiElem e, int enabled)
{
	if(enabled) e->flags |= UIELEM_ENABLED;
	else {
		e->flags = 0;
		if(e->type == etRect)
			for(unsigned i = 0; i < ((yrUiElemRect)e)->childcount; ++i)
				yrUiElem_force_up(((yrUiElemRect)e)->children[i]);
	}
}

/********************************
* BgList specific implementations
*********************************/
#define bgsize_max (96*1024*1024)

static void* savememtex(GLuint tex, size_t* pngsize)
{
	*pngsize = 0;
	if(!tex) {yrLog(0, "Invalid background texture"); return NULL;}
	unsigned char* pixeldata = NULL;

	//get image size
	GLint w, h;
	glBindTexture(GL_TEXTURE_2D, tex);
	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
	glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
	{ GLenum glerr = glGetError(); if(glerr != GL_NO_ERROR) { yrLog(0, "OpenGL error during background size retrieve: %x", glerr); goto onerror; }}
	if(w==0 || h==0) { yrLog(0, "Invalid background image"); goto onerror; }

	//reserve memory and get image data
	pixeldata = malloc(w*h*4);	if(!pixeldata) {yrLog(0, "Out of memory"); goto onerror;}
	glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, pixeldata);
	glBindTexture(GL_TEXTURE_2D, 0);
	{ GLenum glerr = glGetError(); if(glerr != GL_NO_ERROR) { yrLog(0, "OpenGL error during background retrieve: %x", glerr); goto onerror; }}
		
	//encode as png
	size_t outsize;
	unsigned char* out;
	const char* errtxt = yrImgLoad_encode(&outsize, &out, (unsigned)w, (unsigned) h, pixeldata);
	if(errtxt) {
		yrLog(0, "Error while encoding background texture: %s", errtxt);
		goto onerror;
	}
	
	//done
	free(pixeldata);
	*pngsize = outsize;
	GLERRCHECK
	return (void*) out;

onerror:
	glBindTexture(GL_TEXTURE_2D, 0);
	free(pixeldata);
	return NULL;
}

static GLuint loadmemtex(size_t pnglen, const unsigned char* pngdata, int premul_vflip, unsigned* gpusize)
{
	unsigned int width = 0;
	unsigned int height = 0;
	unsigned char* imgdata = 0;
	GLuint out = 0;
	*gpusize = 0;
	
	//decode png
	const char* errtxt = yrImgLoad_decode(pnglen, (void**) pngdata, &width, &height, &imgdata);
	if(errtxt) {
		yrLog(0, "Error while decoding background texture: %s", errtxt);
		goto onerror;
	}
	*gpusize = width * height * 4;
	*gpusize += *gpusize/3;

	//premultiply and vflip
	if(premul_vflip)
	{
		//reverse rows
		unsigned char* flipswap = malloc(width*4);
		if(!flipswap) {yrLog(0, "Out of memory"); free(imgdata); return 0;}
		for(size_t row = 0; row < height / 2; ++row) {
			unsigned char* row_a = imgdata + row * width * 4;
			unsigned char* row_b = imgdata + (height-row-1) * width * 4;
			memcpy(flipswap, row_a, width*4);
			memcpy(row_a, row_b, width*4);
			memcpy(row_b, flipswap, width*4);
		}
		free(flipswap);
		//note: this implementation drops the error along the edges, seems like an acceptable error
		const float kernel[2][3] = {{0.0f,0.0f,7.0f/16},{3.0f/16,5.0f/16,1.0f/16}};
		float* row_c = malloc(4*(width+2)*sizeof(float));
		float* row_e = malloc(4*(width+2)*sizeof(float));
		if(!row_c || !row_e) {
			free(row_c);
			free(row_e);
			yrLog(0, "Out of memory");
			free(imgdata); 
			return 0;
		}
		memset(row_c, 0, 4*(width+2)*sizeof(float));
		memset(row_e, 0, 4*(width+2)*sizeof(float));
		row_c += 4; //offset adjust
		row_e += 4;
		for(size_t row = 0; row < height; ++row)
		{
			//init current row
			for(size_t c = 0; c < width; ++c) {
				row_c[c*4+0] += imgdata[(row*width+c)*4+0];
				row_c[c*4+1] += imgdata[(row*width+c)*4+1];
				row_c[c*4+2] += imgdata[(row*width+c)*4+2];
				row_c[c*4+3] += imgdata[(row*width+c)*4+3];
			}
			//clear error row
			memset(row_e - 4, 0, 4*(width+2)*sizeof(float));
			//premul and diffuse
			for(size_t c = 0; c < width; ++c) {
				//convert
				float a = row_c[c*4+3]/255.0f;
				float r = row_c[c*4+0]*a;
				float g = row_c[c*4+1]*a;
				float b = row_c[c*4+2]*a;
				//record error
				float err_r = modff(r, &r);
				float err_g = modff(g, &g);
				float err_b = modff(b, &b);
				//write new values
				imgdata[(row*width+c)*4+0] = (unsigned char) r;
				imgdata[(row*width+c)*4+1] = (unsigned char) g;
				imgdata[(row*width+c)*4+2] = (unsigned char) b;
				//diffuse error
				row_c[(c+1)*4+0] += kernel[0][2]*err_r;
				row_c[(c+1)*4+1] += kernel[0][2]*err_g;
				row_c[(c+1)*4+2] += kernel[0][2]*err_b;
				row_e[(c-1)*4+0] += kernel[1][0]*err_r;
				row_e[(c-1)*4+1] += kernel[1][0]*err_g;
				row_e[(c-1)*4+2] += kernel[1][0]*err_b;
				row_e[(c)*4+0] += kernel[1][1]*err_r;
				row_e[(c)*4+1] += kernel[1][1]*err_g;
				row_e[(c)*4+2] += kernel[1][1]*err_b;
				row_e[(c+1)*4+0] += kernel[1][2]*err_r;
				row_e[(c+1)*4+1] += kernel[1][2]*err_g;
				row_e[(c+1)*4+2] += kernel[1][2]*err_b;
			}
			//swap rows to prepare for next iter
			float* swap = row_c;
			row_c = row_e;
			row_e = swap;
		}
		free(row_c-4);
		free(row_e-4);
	}

	//create texture & gen mip
	glGenTextures(1, &out);
	glBindTexture(GL_TEXTURE_2D, out);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0f);
	GLint l = (GLint) ceilf(log2f((float)((width>height)?width:height)));
	glTexStorage2D(GL_TEXTURE_2D, l, GL_SRGB8_ALPHA8, width, height);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, imgdata);
	glGenerateMipmap(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, 0);
	GLenum glerr = glGetError();
	if(glerr != GL_NO_ERROR) {
		yrLog(0, "OpenGL error during ui texture creation or loading: %x", glerr);
		goto onerror;
	}

	//done
	free(imgdata);
	GLERRCHECK
	return out;

onerror:
	yrLog(0, "Failed to load bg texture.");
	if(out) glDeleteTextures(1, &out);
	free(imgdata);
	return 0;
}

static GLuint loaddialogtex(unsigned* gpusize)
{
	*gpusize = 0;
	//get path
	const char* filtername = "Image file";
	const char* filterspec = "*.*";
	char* path = yrFile_path_dialog(yrFDlgID_Backgrounds, 0,0, 1, &filtername, &filterspec, NULL);		if(!path) return 0;

	//read file
	yrFile* f = yrFile_open(path, yrF_read);									if(!f) {yr_msgbox(yrLocalise_get_string("err_FileReadError")); return 0;}
	int64_t rw, flen;
	unsigned char* fblob = NULL;
	flen = yrFile_seek(f, 0, yrF_seekend);
	yrFile_seek(f, 0, yrF_seekset);
	fblob = malloc(flen);														if(!fblob) {yrLog(0, "Out of memory"); yrFile_close(f); return 0;}
	rw = yrFile_read(f, flen, fblob);
	if(rw != flen) {
		yr_msgbox(yrLocalise_get_string("err_FileReadError"));
		free(fblob);
		yrFile_close(f);
		return 0;
	}
	yrFile_close(f);

	//create tex
	GLuint out = loadmemtex(flen, fblob, 1, gpusize);
	free(fblob);
	GLERRCHECK
	return out;
}

void yrUiElemBgList_populate(yrUiElem e, const char* scenec)
{
	yrUiElemBgList bgl = (yrUiElemBgList) e;
	
	//clear bg list
	bgl->scroll_pos = 0.0f;
	bgl->scroll_max = 0.0f;
	for(size_t i = 0; i < bgl->count; ++i) {
		glMakeTextureHandleNonResidentARB(bgl->handle_bg_tex[i]);
	}
	glDeleteTextures(bgl->count, bgl->bg_tex);
	bgl->count = 0;
	bgl->last_id = 1;
	bgl->total_size = 0;
	memset(bgl->id_remap, 0, sizeof(bgl->id_remap));

	//open scenefile and get backgrounds
	if(!scenec) return;
	yrSceneFile* sf = yrSceneFile_open(scenec, 0);
	if(!sf) return;
	unsigned bg_count;
	size_t* bg_pngsize;
	unsigned char* bg_blob;
	int err = yrSceneFile_backgrounds_get(sf, &bg_count, &bg_pngsize, (void**)&bg_blob, &bgl->last_id, bgl->id_remap);
	if(err) return;

	//load backgrounds into list
	size_t bg_off = 0;
	bgl->count = (unsigned char) bg_count;
	for(unsigned i = 0; i < bgl->count; ++i)
	{
		bgl->bg_tex[i] = loadmemtex(bg_pngsize[i], bg_blob + bg_off, 0, bgl->bg_size + i);
		bgl->handle_bg_tex[i] = getimghandle(bgl->bg_tex[i]);
		bg_off += bg_pngsize[i];
		bgl->total_size += bgl->bg_size[i];
	}
	
	//calc scroll max
	bgl->scroll_max = (float)((bg_count + 4)/5);
	
	//update counter
	yrUiElemBgList_layout(bgl, bgl->scale, bgl->ar);
	GLERRCHECK
}

void yrUiElemBgList_savetofile(yrUiElem e, const char* scenec)
{
	yrUiElemBgList bgl = (yrUiElemBgList) e;
	
	void** bgblob = NULL;
	size_t* bgsize = NULL;
	yrSceneFile* sf = NULL;

	//get all backgrounds as png data
	if(bgl->count) {
		bgblob = malloc(bgl->count * sizeof(void*));			if(!bgblob) { yrLog(0, "Out of memory"); goto onerror;}
		memset(bgblob, 0, bgl->count * sizeof(void*));
		bgsize = malloc(bgl->count * sizeof(size_t));			if(!bgsize) { yrLog(0, "Out of memory"); goto onerror;}
	}
	for(unsigned char i = 0; i < bgl->count; i += 1)
	{
		bgblob[i] = savememtex(bgl->bg_tex[i], bgsize + i);		if(!bgblob[i]) goto onerror;
	}

	//open scenefile and save backgrounds
	if(!scenec) goto onerror;
	sf = yrSceneFile_open(scenec, 0);							if(!sf) goto onerror;
	int err = yrSceneFile_backgrounds_set(sf, bgl->count, bgsize, bgblob, bgl->last_id, bgl->id_remap);
	if(err) goto onerror;
	
	//done
	yrSceneFile_close(sf);
	GLERRCHECK
	return;

onerror:
	if(bgblob)
		for(unsigned char i = 0; i < bgl->count; i += 1)
			free(bgblob[i]);
	free(bgblob);
	free(bgsize);
	if(sf) yrSceneFile_close(sf);
}

void yrUiElemBgList_remove_selected(yrUiElem e)
{
	yrUiElemBgList bgl = (yrUiElemBgList) e;
	if(bgl->selected < bgl->count) {
		unsigned char sel = bgl->selected;
		//delete gpu texture
		glMakeTextureHandleNonResidentARB(bgl->handle_bg_tex[sel]);
		glDeleteTextures(1, &bgl->bg_tex[sel]);
		//delete from lists
		bgl->count -= 1;
		bgl->total_size -= bgl->bg_size[sel];
		memmove(bgl->id_remap + sel, bgl->id_remap + sel + 1, (BG_MAX_TEX - sel - 1) * sizeof(uint32_t)); 
		memmove(bgl->bg_tex + sel, bgl->bg_tex + sel + 1, (BG_MAX_TEX - sel - 1) * sizeof(GLuint)); 
		memmove(bgl->bg_size + sel, bgl->bg_size + sel + 1, (BG_MAX_TEX - sel - 1) * sizeof(unsigned)); 
		bgl->id_remap[BG_MAX_TEX-1] = 0;
		bgl->bg_tex[BG_MAX_TEX-1] = 0;
		bgl->bg_size[BG_MAX_TEX-1] = 0;
		if(bgl->selected == bgl->count) bgl->selected -= 1;
		//recalc scrollmax
		bgl->scroll_max = (float)((bgl->count + 4)/5);
		//update counter
		yrUiElemBgList_layout(bgl, bgl->scale, bgl->ar);
	}
	GLERRCHECK
}

void yrUiElemBgList_replace_selected(yrUiElem e)
{
	yrUiElemBgList bgl = (yrUiElemBgList) e;
	//don't if no background selected
	if(bgl->selected >= bgl->count) return;
	unsigned sel = bgl->selected;
	//path dialog and texture load
	unsigned size;
	GLuint tex = loaddialogtex(&size);
	if(!tex) return;
	//update list
	bgl->total_size -= bgl->bg_size[sel];
	bgl->bg_tex[sel] = tex;
	bgl->handle_bg_tex[sel] = getimghandle(tex);
	bgl->bg_size[sel] = size;
	bgl->total_size += bgl->bg_size[sel];
	GLERRCHECK
	return;
}

void yrUiElemBgList_add(yrUiElem e)
{
	yrUiElemBgList bgl = (yrUiElemBgList) e;
	//don't if full already
	if(bgl->count == BG_MAX_TEX) { yr_msgbox(yrLocalise_get_string("bgs_TooMany")); return;}
	if(bgl->total_size >= bgsize_max) { yr_msgbox(yrLocalise_get_string("bgs_TooMuch")); return;}
	//path dialog and texture load
	unsigned size;
	GLuint tex = loaddialogtex(&size);
	if(!tex) return;
	//add to list
	unsigned char sel = bgl->count;
	bgl->count += 1;
	bgl->last_id += 1;
	bgl->id_remap[sel] = bgl->last_id;
	bgl->bg_tex[sel] = tex;
	bgl->handle_bg_tex[sel] = getimghandle(tex);
	bgl->bg_size[sel] = size;
	bgl->total_size += size;
	
	//calc scroll max
	bgl->scroll_max = (float)((bgl->count + 4)/5);
	
	//update counter
	yrUiElemBgList_layout(bgl, bgl->scale, bgl->ar);
	GLERRCHECK
	return;
}

static void yrUiElemBgList_layout(yrUiElemBgList e, float scale, float ar)
{
	e->scale = scale;
	e->ar = ar;
	GLERRCHECK
	//rerender count indicator
	float margin = e->w / 50.0f;
	unsigned height_px = 14;
	float size_px = to_px(scale, margin * 2);
	float left_px = to_px(scale, e->x + 0.8f * e->w - margin);
	float right_px = to_px(scale, e->x + e->w - margin);
	unsigned w_px = (unsigned)(roundf(right_px) - roundf(left_px));

	char text[] = "xxx/125";//BG_MAX_TEX
	text[0] = " 123456789"[e->count/100];
	text[1] = "0123456789"[(e->count%100)/10];
	text[2] = "0123456789"[e->count%10];
	if(e->count < 10) text[1] = ' ';

	if(e->count_indic) {
		glMakeTextureHandleNonResidentARB(e->handle_count_indic);
		glDeleteTextures(1, &e->count_indic);
		e->handle_count_indic = 0;
	}
	e->count_indic = yrFontRender_render(text, size_px, w_px, &height_px, taRight, 0, 0);
	e->count_height = 100 * height_px / scale;
	if(e->count_indic) {
		e->handle_count_indic = glGetTextureHandleARB(e->count_indic);
		glMakeTextureHandleResidentARB(e->handle_count_indic);
	}
	GLERRCHECK
	//capacity bar componenents
	left_px = to_px(scale, e->x + 0.5f * margin);
	right_px = to_px(scale, e->x + 11 * margin);
	w_px = (unsigned)(roundf(right_px) - roundf(left_px));
	if(e->capa_text) {
		glMakeTextureHandleNonResidentARB(e->handle_capa_text);
		glDeleteTextures(1, &e->capa_text);
		e->handle_capa_text = 0;
	}
	e->capa_text = yrFontRender_render(yrLocalise_get_string("bgs_Capacity"), size_px, w_px, &height_px, taLeft, 0, 0);
	e->capa_h_text = 100 * height_px / scale;
	if(e->capa_text) {
		e->handle_capa_text = glGetTextureHandleARB(e->capa_text);
		glMakeTextureHandleResidentARB(e->handle_capa_text);
	}
	GLERRCHECK
	size_px = to_px(scale, margin);
	left_px = to_px(scale, e->x + 12 * margin);
	right_px = to_px(scale, e->x + 15 * margin);
	w_px = (unsigned)(roundf(right_px) - roundf(left_px));
	if(e->capa_0) {
		glMakeTextureHandleNonResidentARB(e->handle_capa_0);
		glDeleteTextures(1, &e->capa_0);
		e->handle_capa_0 = 0;
	}
	e->capa_0 = yrFontRender_render("0%", size_px, w_px, &height_px, taLeft, 0, 0);
	e->capa_h_0 = 100 * height_px / scale;
	if(e->capa_0) {
		e->handle_capa_0 = glGetTextureHandleARB(e->capa_0);
		glMakeTextureHandleResidentARB(e->handle_capa_0);
	}
	GLERRCHECK
	left_px = to_px(scale, e->x + e->w - 15 * margin);
	right_px = to_px(scale, e->x + e->w - 12 * margin);
	w_px = (unsigned)(roundf(right_px) - roundf(left_px));
	if(e->capa_100) {
		glMakeTextureHandleNonResidentARB(e->handle_capa_100);
		glDeleteTextures(1, &e->capa_100);
		e->handle_capa_100 = 0;
	}
	e->capa_100 = yrFontRender_render("100%", size_px, w_px, &height_px, taRight, 0, 0);
	e->capa_h_100 = 100 * height_px / scale;
	if(e->capa_100) {
		e->handle_capa_100 = glGetTextureHandleARB(e->capa_100);
		glMakeTextureHandleResidentARB(e->handle_capa_100);
	}
	GLERRCHECK
}

void yrUiElem_render_bglistcontent(yrUiElemBgList e, uint32_t fg, struct yr_ui_rendertexes* unis)
{
	//calc dims
	float margin = e->w / 50.0f;
	float bg_w = (e->w - 6 * margin)/5;
	float bg_h = bg_w / 1.618f;
	float bg_x = e->x;
	float bg_y = e->y + 6 * margin;
	GLint scx = (GLint) to_px(e->scale, bg_x);
	GLint scy = (GLint) (e->scale/e->ar - to_px(e->scale, bg_y));
	GLsizei scw = (GLsizei) to_px(e->scale, e->w);
	GLsizei sch = (GLsizei) to_px(e->scale, e->h - 9 * margin);
	scy -= sch;
	//render capacity bar labels
	draw_elem(unis->ssbo,
			  10.5f * margin, e->capa_h_text,
			  e->x + 0.5f * margin, e->y + 0.5f * margin,
			  fg, 0, e->handle_capa_text);

	draw_elem(unis->ssbo,
			  3 * margin, e->capa_h_0,
			  e->x + 12 * margin, e->y,
			  fg, 0, e->handle_capa_0);

	draw_elem(unis->ssbo,
			  3 * margin, e->capa_h_100,
			  e->x + e->w - 15 * margin, e->y,
			  fg, 0, e->handle_capa_100);

	//capacity bar markers
	draw_elem(unis->ssbo,
			  -margin / 4, 2 * margin,
			  e->x + 11.75f * margin, e->y + margin / 2,
			  0, fg, unis->handle_notex);

	draw_elem(unis->ssbo,
			  margin / 4, 2 * margin,
			  e->x + e->w - 11.75f * margin, e->y + margin / 2,
			  0, fg, unis->handle_notex);
	
	float barw = e->w - 23.5f * margin;
	draw_elem(unis->ssbo,
			  barw * 1.25f, 0.25f * margin,
			  e->x + 11.75f * margin, e->y + 2.25f * margin,
			  0, fg, unis->handle_notex);

	//capacity bar total
	float pctsize = ((float)e->total_size / bgsize_max);
	float over = pctsize - 1.0f;
	if(pctsize > 1.0f) pctsize = 1.0f;
	draw_elem(unis->ssbo,
			  pctsize * barw, margin,
			  e->x + 11.75f * margin, e->y + 1.25f * margin,
			  0, fg, unis->handle_notex);
	
	//capacity bar overcapacity
	if(over > 0.0f)
	{
		if(over > 0.25f) over = 0.25f;
		draw_elem(unis->ssbo,
			  over * barw, margin,
			  e->x + e->w - 11.75f * margin, e->y + 1.25f * margin,
			  0, e->fg2, unis->handle_notex);
	}
	//capacity bar selected
	if(e->selected < e->count)
	{
		pctsize = ((float)e->bg_size[e->selected] / bgsize_max);
		if(pctsize > 1.25f) pctsize = 1.25f;
		draw_elem(unis->ssbo,
			  pctsize * barw, margin,
			  e->x + 11.75f * margin, e->y + 1.25f * margin,
			  0, e->fg2, unis->handle_notex);
	}

	//render scroll buttons and do scroll
	e->scroll_pos += 0.07f * e->scrolling;
	float realscrollmax = e->scroll_max - (e->h - 10 * margin)/(bg_h + margin);
	uint32_t sfg = (e->scroll_hover == 1) ? e->bg : e->fg2;
	draw_elem(unis->ssbo,
			  e->w, -margin * 3,
			  e->x, e->y + e->h,
			  sfg, fg, unis->handle_notex);

	if(e->scroll_pos >= realscrollmax) {
		e->scroll_pos = realscrollmax;
	} else {
		draw_elem(unis->ssbo,
			  margin * 3, -margin * 3,
			  e->x + (e->w - margin * 3)/2, e->y + e->h,
			  sfg, fg, e->handle_scroll_icon);
	}
	sfg = (e->scroll_hover == -1) ? e->bg : e->fg2;
	draw_elem(unis->ssbo,
			  e->w, margin * 3,
			  e->x, e->y + margin * 3,
			  sfg, fg, unis->handle_notex);
	
	if(e->scroll_pos <= 0.0f) {
		e->scroll_pos = 0.0f;
	} else {
		draw_elem(unis->ssbo,
			  margin * 3, margin * 3,
			  e->x + (e->w - margin * 3)/2, e->y + margin * 3,
			  sfg, fg, e->handle_scroll_icon);
	}
	//render counter
	draw_elem(unis->ssbo,
			  0.2f * e->w, e->count_height,
			  e->x + 0.8f * e->w - margin, e->y + 3.5f * margin,
			  e->fg2, 0, e->handle_count_indic);
	
	//clip rendering
	draw_flush(unis->ssbo);
	glScissor(scx, scy, scw, sch);
	glEnable(GL_SCISSOR_TEST);
	//render select border
	if(e->selected < e->count)
	{
		float col = (float)(e->selected % 5);
		float row = (float)(e->selected / 5) - e->scroll_pos;
		float xoff = bg_x + (col+1)*margin + col*bg_w;
		float yoff = bg_y + (row+1)*margin + row*bg_h;
		draw_elem(unis->ssbo,
			  bg_w + margin, bg_h + margin,
			  xoff - margin/2, yoff - margin/2,
			  0xFFFFFFFFul, e->fg2, unis->handle_notex);
	}
	//render backgrounds in list
	unsigned start = ((unsigned) floorf(e->scroll_pos)) * 5;
	unsigned end = start + 5 + 5 * (unsigned) ceilf((e->h - 7 * margin)/(bg_h + margin));
	if(end > e->count) end = e->count;
	for(unsigned i = start; i < end; i += 1)
	{
		float col = (float)(i % 5);
		float row = (float)(i / 5) - e->scroll_pos;
		float xoff = bg_x + (col+1)*margin + col*bg_w;
		float yoff = bg_y + (row+1)*margin + (row+1)*bg_h;
		draw_elem(unis->ssbo,
				  bg_w, -bg_h,
				  xoff, yoff,
				  0xFFFFFFFFul, e->fg2 & 0x7F7F7F7Ful, e->handle_bg_tex[i]);
	}
	draw_flush(unis->ssbo);
	glDisable(GL_SCISSOR_TEST);
	GLERRCHECK
}

void yrUiElemBgList_mousepos(yrUiElem e, float x, float y)
{
	yrUiElemBgList bgl = (yrUiElemBgList) e;
	x -= bgl->x;
	y -= bgl->y;
	bgl->mousex = x;
	bgl->mousey = y;
	float margin = e->w / 50.0f;
	if(x >= 0.0f && x < e->w &&
	   y >= 3*margin && y < e->h)
	{
		if(y < 6*margin) bgl->scroll_hover = -1;
		else if(y > e->h - 3*margin) bgl->scroll_hover = 1;
		else bgl->scroll_hover = 0;
	}
	else bgl->scroll_hover = 0;
	GLERRCHECK
}

void yrUiElemBgList_keyevent(yrUiElem e, unsigned keycode, int down, yrUiScreen s)
{
	float margin = e->w / 50.0f;
	yrUiElemBgList bgl = (yrUiElemBgList) e;
	if(keycode == 0x1400) //mousewheel
	{
		bgl->scroll_pos -= 0.5f * down;
	}
	else if(keycode == VK_LBUTTON)
	{
		if(!down) bgl->scrolling = 0;
		else bgl->scrolling = (float) bgl->scroll_hover;

		float x = bgl->mousex;
		float y = bgl->mousey;
		if(y > 6*margin && y < e->h - 3*margin)
		{
			float bg_w = (e->w - 6 * margin)/5;
			float bg_h = bg_w / 1.618f;
			bg_w += margin;
			bg_h += margin;
			x -= margin/2;
			y -= 6.5f * margin;
			x /= bg_w;
			y /= bg_h;
			y += bgl->scroll_pos;
			unsigned idx = BG_MAX_TEX;
			if(x >= 0.0f && x < 5.0f && y >= 0.0f) {
				idx = ((unsigned) y)*5 + ((unsigned) x);
				if(idx >= bgl->count) idx = BG_MAX_TEX;
			}
			bgl->selected = (unsigned char)idx;
		}
	}
	GLERRCHECK
}

/******************************
* Edit specific implementations
*******************************/
static void yrUiElemEdit_layout(yrUiElemEdit e, float scale, float ar)
{
	//text rerender
	unsigned height_px;
	unsigned width_px;
	float size_px = to_px(scale, e->size);
	float left_px = to_px(scale, e->x);
	float right_px = to_px(scale, e->x + e->w);
	unsigned w_px = (unsigned)(roundf(right_px) - roundf(left_px));

	GLuint tex = yrFontRender_render(e->buffer, size_px, w_px, &height_px, e->align, 1, &width_px);

	if(e->bitmap) {
		glMakeTextureHandleNonResidentARB(e->handle_bitmap);
		glDeleteTextures(1, &e->bitmap);
		e->handle_bitmap = 0;
	}

	if(!tex) height_px = (unsigned) ceilf(size_px);
	e->h = from_px(scale, (float) height_px);
	e->bitmap_w = from_px(scale, (float) width_px);
	e->bitmap = tex;
	if(e->bitmap) {
		e->handle_bitmap = glGetTextureHandleARB(e->bitmap);
		glMakeTextureHandleResidentARB(e->handle_bitmap);
	}

	//text position update
	e->text_x = e->x + e->border/2;
	float text_w = e->w - e->border;
	switch(e->align) {
		case taLeft: {
			if(e->bitmap_w > text_w)
				e->text_x -= (e->bitmap_w - text_w);
			break;}
		case taRight: {
			e->text_x -= (e->bitmap_w - text_w);
			break;}
		case taCenter: {
			if(e->bitmap_w > text_w)
				e->text_x -= (e->bitmap_w - text_w);
			else
				e->text_x -= (e->bitmap_w - text_w)/2;
			break;}
	}

	//caret update
	float charw = (e->buffer[0] ? (e->bitmap_w / strlen(e->buffer)) : 0.0f); //terrible approximation, should hold up good enough though
	e->caret_x = e->text_x + e->caretpos * charw - e->border/2;
	yrWindow* win = yrDesktop_get_window();
	int winx, winy;
	yrDesktop_get_offset(&winx, &winy);
	if(e->scale != scale) yrWindow_caret_resize(win, (int)ceilf(to_px(scale, e->border/2)), (int)to_px(scale, e->h));
	yrWindow_caret_move(win, winx + (int)to_px(scale, e->caret_x), winy + (int)to_px(scale, e->y + e->border/2));
	e->lastblink = yr_get_microtime();
	e->caret_on = 1;

	//scale and ar update
	e->scale = scale;
	e->ar = ar;
	GLERRCHECK
}

void yrUiElem_render_edit(yrUiElemEdit e, uint32_t fg, struct yr_ui_rendertexes* unis)
{
	//draw border
	draw_elem(unis->ssbo,
			  e->w + 2*e->border, e->border,
			  e->x - e->border, e->y - e->border,
			  0, fg, unis->handle_notex);
	draw_elem(unis->ssbo,
			  e->w + 2*e->border, e->border,
			  e->x - e->border, e->y + e->h,
			  0, fg, unis->handle_notex);
	draw_elem(unis->ssbo,
			  e->border, e->h,
			  e->x - e->border, e->y,
			  0, fg, unis->handle_notex);
	draw_elem(unis->ssbo,
			  e->border, e->h,
			  e->x + e->w, e->y,
			  0, fg, unis->handle_notex);

	//viewport editfield
	GLint scx = (GLint) floorf(to_px(e->scale, e->x + e->border/2));
	GLint scy = (GLint) floorf(e->scale/e->ar - to_px(e->scale, e->y + e->h));
	GLsizei scw = (GLsizei) ceilf(to_px(e->scale, e->w - e->border));
	GLsizei sch = (GLsizei) ceilf(to_px(e->scale, e->h));
	draw_flush(unis->ssbo);
	glScissor(scx, scy, scw, sch);

	//draw text
	glEnable(GL_SCISSOR_TEST);
	float text_x = e->text_x;
	float text_y = e->y;
	float text_w = e->bitmap_w;
	float text_h = e->h;
	draw_elem(unis->ssbo,
			  text_w, text_h,
			  text_x, text_y,
			  fg, 0, e->handle_bitmap);

	//draw caret
	float caret_x = e->caret_x;
	float caret_y = text_y + e->border/2;
	float caret_w = e->border/2;
	float caret_h = text_h - e->border;
	if(e->input_enabled &&
	   e->caret_on &&
	   caret_x > e->x &&
	   caret_x < (e->x + e->w))
	{
		draw_elem(unis->ssbo,
			  caret_w, caret_h,
			  caret_x, caret_y,
			  0, fg, unis->handle_notex);
	}
	uint64_t now = yr_get_microtime();
	if(now > e->lastblink + 600000) {
		e->caret_on = !e->caret_on;
		e->lastblink = now;
	}
	draw_flush(unis->ssbo);
	glDisable(GL_SCISSOR_TEST);
	GLERRCHECK
}

void yrUiElemEdit_input_enabled(yrUiElem e, int enabled)
{
	if(e->type != etEdit) return;
	yrUiElemEdit i = (yrUiElemEdit)e;
	i->input_enabled = enabled;
	if(enabled) {
		yrWindow* win = yrDesktop_get_window();
		int winx, winy;
		yrDesktop_get_offset(&winx, &winy);
		yrWindow_caret_move(win, winx + (int)to_px(i->scale, i->caret_x), winy + (int)to_px(i->scale, i->y + i->border/2));
		yrWindow_caret_enable(yrDesktop_get_window(), enabled);
	}
}

static void utf8_convert(unsigned cp, unsigned char* out)
{
	//TODO only ascii digits implemented for now
	if(cp >= '0' && cp <= '9') {
		out[0] = (unsigned char) cp;
		out[1] = 0;
	} else {
		out[0] = 0;
	}
}

static void yrUiElemEdit_insert(yrUiElemEdit e, unsigned c)
{
	//convert to utf8 and check capacity
	char utf8[7];
	utf8_convert(c, utf8);
	size_t clen = strlen(utf8);
	size_t slen = strlen(e->buffer);
	if(clen == 0) return;
	if(slen + clen > e->capacity) return;

	//insert text
	char* insert = e->buffer + e->caretpos;
	char* after = insert + clen;
	size_t afterlen = slen - e->caretpos + 1;
	memmove(after, insert, afterlen);
	memcpy(insert, utf8, clen);

	//move caret and update visually
	e->caretpos += (unsigned) clen;
	yrUiElemEdit_layout(e, e->scale, e->ar);
}

static void yrUiElemEdit_caret_forward(yrUiElemEdit e)
{
	//TODO step one utf8 char
	//TODO only ascii implemented for now
	size_t slen = strlen(e->buffer);
	if(e->caretpos < slen) {
		e->caretpos += 1;
		yrUiElemEdit_layout(e, e->scale, e->ar);
	}
}

static void yrUiElemEdit_caret_back(yrUiElemEdit e)
{
	//TODO rev step one utf8 char
	//TODO only ascii implemented for now
	if(e->caretpos > 0) {
		e->caretpos -= 1;
		yrUiElemEdit_layout(e, e->scale, e->ar);
	}
}

static void yrUiElemEdit_del(yrUiElemEdit e)
{
	//TODO del one utf8 char
	//TODO only ascii implemented for now
	size_t slen = strlen(e->buffer);
	if(e->caretpos < slen) {
		char* insert = e->buffer + e->caretpos;
		char* after = insert + 1;
		size_t afterlen = strlen(after) + 1;
		memmove(insert, after, afterlen);
		yrUiElemEdit_layout(e, e->scale, e->ar);
	}
}

static void yrUiElemEdit_backsp(yrUiElemEdit e)
{
	//TODO rev del one utf8 char
	//TODO only ascii implemented for now
	if(e->caretpos > 0) {
		char* after = e->buffer + e->caretpos;
		char* insert = after - 1;
		size_t afterlen = strlen(after) + 1;
		memmove(insert, after, afterlen);
		e->caretpos -= 1;
		yrUiElemEdit_layout(e, e->scale, e->ar);
	}
}

void yrUiElemEdit_keyevent(yrUiElem e, unsigned keycode, int down, yrUiScreen s)
{
	yrUiElemEdit i = (yrUiElemEdit) e;
	if(!i->input_enabled) return;
	if(!down) return;
	switch(keycode) {
		case VK_LEFT:	yrUiElemEdit_caret_back(i); break;
		case VK_RIGHT:	yrUiElemEdit_caret_forward(i); break;
		case VK_DELETE:	yrUiElemEdit_del(i); break;
		case VK_BACK:	yrUiElemEdit_backsp(i); break;
		case VK_HOME:	i->caretpos = 0; yrUiElemEdit_layout(i, i->scale, i->ar); break;
		case VK_END:	i->caretpos = (unsigned) strlen(i->buffer); yrUiElemEdit_layout(i, i->scale, i->ar); break;
	}
}