// Shpuld's Simple UI lib - sui
// Created 11/2018
//
// sui is a simple QuakeC UI lib for drawing and handling game interfaces.
// The API is made simple and easy to build upon, but cuts have been made
// to keep complexity low.
//


#ifdef MENU
const float IE_KEYDOWN = 0;	/* Specifies that a key was pressed. Second argument is the scan code. Third argument is the unicode (printable) char value. Fourth argument denotes which keyboard(or mouse, if its a mouse 'scan' key) the event came from. Note that some systems may completely separate scan codes and unicode values, with a 0 value for the unspecified argument. */
const float IE_KEYUP = 1;	/* Specifies that a key was released. Arguments are the same as IE_KEYDOWN. On some systems, this may be fired instantly after IE_KEYDOWN was fired. */
const float IE_MOUSEDELTA = 2;	/* Specifies that a mouse was moved (touch screens and tablets typically give IE_MOUSEABS events instead, use _windowed_mouse 0 to test code to cope with either). Second argument is the X displacement, third argument is the Y displacement. Fourth argument is which mouse or touch event triggered the event. */
const float IE_MOUSEABS = 3;	/* Specifies that a mouse cursor or touch event was moved to a specific location relative to the virtual screen space. Second argument is the new X position, third argument is the new Y position. Fourth argument is which mouse or touch event triggered the event. */
const float IE_ACCELEROMETER = 4;
const float IE_FOCUS = 5;	/* Specifies that input focus was given. parama says mouse focus, paramb says keyboard focus. If either are -1, then it is unchanged. */
const float IE_JOYAXIS = 6;	/* Specifies that what value a joystick/controller axis currently specifies. x=axis, y=value. Will be called multiple times, once for each axis of each active controller. */

#define printf(x, ...) print(sprintf(x, __VA_ARGS__))

#endif
float _sui_draw_initialized;


// framing

// pseudo windowing, sets a new "frame" for whatever we're drawing, instead of
// always using screen [0, 0] as min and [screen_width, screen_height] as max.
// also allows for aligning content to frame start/end/center on both axis

struct _frame_t {
	vector pos;
	vector size;
	vector align;
};
const float MAX_FRAMES = 64;
_frame_t _frames[MAX_FRAMES];
float _frame_index;

const float SUI_ALIGN_START = 0;
const float SUI_ALIGN_CENTER = 1;
const float SUI_ALIGN_END = 2;

void() sui_reset_align =
{
	_frames[_frame_index].align = [SUI_ALIGN_START, SUI_ALIGN_START];
};

void(float align) sui_set_x_align =
{
	_frames[_frame_index].align.x = align;
};

void(float align) sui_set_y_align =
{
	_frames[_frame_index].align.y = align;
};

void(vector align) sui_set_align =
{
	_frames[_frame_index].align = align;
};

void(__inout vector point) sui_transform_point =
{
	int idx = _frame_index;
	switch (_frames[idx].align.x) 
	{
		case SUI_ALIGN_START:	point_x += _frames[idx].pos.x; break;
		case SUI_ALIGN_CENTER:	point_x += _frames[idx].pos.x + _frames[idx].size.x * 0.5; break;
		case SUI_ALIGN_END:		point_x += _frames[idx].pos.x + _frames[idx].size.x; break;
		default: break;
	}
	switch (_frames[idx].align.y) 
	{
		case SUI_ALIGN_START:	point_y += _frames[idx].pos.y; break;
		case SUI_ALIGN_CENTER:	point_y += _frames[idx].pos.y + _frames[idx].size.y * 0.5; break;
		case SUI_ALIGN_END:		point_y += _frames[idx].pos.y + _frames[idx].size.y; break;
		default: break;
	}
};

void(__inout vector point, vector size) sui_transform_box =
{
	int idx = _frame_index;
	switch (_frames[idx].align.x) 
	{
		case SUI_ALIGN_START:
			point_x += _frames[idx].pos.x; 
			break;
		case SUI_ALIGN_CENTER:
			point_x += _frames[idx].pos.x + _frames[idx].size.x * 0.5 - size_x * 0.5; 
			break;
		case SUI_ALIGN_END:
			point_x += _frames[idx].pos.x + _frames[idx].size.x - size_x; 
			break;
		default: break;
	}
	switch (_frames[idx].align.y) 
	{
		case SUI_ALIGN_START:
			point_y += _frames[idx].pos.y; 
			break;
		case SUI_ALIGN_CENTER:
			point_y += _frames[idx].pos.y + _frames[idx].size.y * 0.5 - size_y * 0.5; 
			break;
		case SUI_ALIGN_END:
			point_y += _frames[idx].pos.y + _frames[idx].size.y - size_y; 
			break;
		default: break;
	}
};

vector() sui_current_frame_pos = 
{ 
	return _frames[_frame_index].pos;
};

vector() sui_current_frame_size =
{
	 return _frames[_frame_index].size;
};

float _sui_is_clipping;
vector _sui_clip_area_mins;
vector _sui_clip_area_maxs; 
void() sui_clip_to_frame =
{
	vector pos = _frames[_frame_index].pos;
	vector size = _frames[_frame_index].size;
	_sui_is_clipping = TRUE;
	_sui_clip_area_mins = pos;
	_sui_clip_area_maxs = pos + size;
	drawsetcliparea(pos.x, pos.y, size.x, size.y);
};

void() sui_reset_clip =
{
	_sui_is_clipping = FALSE;
	drawresetcliparea();
};

float() sui_is_clipping =
{
	return _sui_is_clipping;
};

void(vector pos, vector size) sui_push_frame =
{
	sui_transform_box(pos, size);
	
	_frame_index += 1;
	if (_frame_index >= MAX_FRAMES)
	{
		//printf("^3sui warning: amount of frames = %.0f exceeds MAX_FRAMES = %.0f, consider increasing MAX_FRAMES\n", _frame_index, MAX_FRAMES);
		return;
	}
	
	_frames[_frame_index].pos = pos;
	_frames[_frame_index].size = size;
	_frames[_frame_index].align = [SUI_ALIGN_START, SUI_ALIGN_START]; // TODO allow customizing this
};

void() sui_pop_frame =
{
	if (_frame_index > 0) _frame_index -= 1;
};

void() sui_reset_frame = 
{
	_frame_index = 0;
	sui_reset_align();
};


// actions

// interaction for sui elements, relies a lot on reading globals to see which
// element id is under cursor or held or whatever, not the most elegant
// solution but in this highly imperative world of QuakeC we can live with it

float _holding_click;
vector _cursor_click;
vector _cursor_position;
vector _cursor_relative_click;
vector _cursor_relative_hover;
struct _action_element_t {
	vector pos;
	vector size;
	string id;
	void(float index, vector click_ratios) action;
};
const float MAX_ACTION_ELEMENTS = 256;
_action_element_t _action_elements[MAX_ACTION_ELEMENTS];
float _action_elements_index;


// TODO better naming
float(vector point, vector min, vector max) is_2dpoint_in_bounds =
{
	if (point_x <= min_x || point_y <= min_y) return FALSE;
	if (point_x > max_x || point_y > max_y) return FALSE;
	return TRUE;
};

// TODO better naming
float(vector point, vector pos, vector size) is_2dpoint_in_bbox =
{
	return is_2dpoint_in_bounds(point, pos, pos + size);
};

void() _action_element_count_sanity =
{
	if (_action_elements_index > MAX_ACTION_ELEMENTS)
	{
		// let the user know if they're hitting the bounds
		//printf("^3sui warning: amount of action elements = %.0f exceeds MAX_ACTION_ELEMENTS = %.0f, consider increasing MAX_ACTION_ELEMENTS\n", _action_elements_index, MAX_ACTION_ELEMENTS);
	}
};

const float MAX_MOUSE_ACTIONS = 16;
string _hover_actions[MAX_MOUSE_ACTIONS];
string _click_actions[MAX_MOUSE_ACTIONS];
string _hold_actions[MAX_MOUSE_ACTIONS];
string _release_actions[MAX_MOUSE_ACTIONS];
string _last_clicked_actions[MAX_MOUSE_ACTIONS];

float _hover_action_count;
float _click_action_count;
float _hold_action_count;
float _release_action_count;
float _last_clicked_action_count;


// Resets things you might want to persist normally
void() sui_reset_actions =
{
	_hover_action_count = 0;
	_click_action_count = 0;
	_hold_action_count = 0;
	_release_action_count = 0;
	_last_clicked_action_count = 0;
	_holding_click = FALSE;
};

// Per frame reset?
void() sui_reset_click =
{
	_hold_action_count = 0;
	_click_action_count = 0;
	_holding_click = FALSE;
};

float() sui_click_held = { return _holding_click; };


// click: on mouse1 button down AND button op, once

// Returns true if id was the topmost click (what usually is cared about the most)
float(string id) sui_is_clicked =
{
	return _click_action_count > 0 && _click_actions[0] == id;
};

// Returns the index of the clicked id, -1 if wasn't hit at all. 0 is topmost
float(string id) sui_click_index =
{
	for (int i = 0; i < _click_action_count; i++)
	{
		if (_click_actions[i] == id) return i;
	}
	return -1;
};


// hover: mouse is on top of the action element id

string last_hovered_id;
float(string id) sui_is_hovered =
{
	float is_hovered = _hover_action_count > 0 && _hover_actions[0] == id;

	if (is_hovered && last_hovered_id != id && substring(id, 0, 4) != "soc_") {
		last_hovered_id = id;
		Menu_PlaySound(MENU_SND_NAVIGATE);
	}

	return is_hovered;
};

float(string id) sui_hover_index =
{
	for (int i = 0; i < _hover_action_count; i++)
	{
		if (_hover_actions[i] == id) return i;
	}
	return -1;
};


// hold: mouse button was clicked on top of this id and is held down, but not necessarily over this id anymore

float(string id) sui_is_held =
{
	return _hold_action_count > 0 && _hold_actions[0] == id;
};

float(string id) sui_hold_index =
{
	for (int i = 0; i < _hold_action_count; i++)
	{
		if (_hold_actions[i] == id) return i;
	}
	return -1;
};


// last clicked: is this the last action element that was clicked, good for focusing on input boxes for example

float(string id) sui_is_last_clicked =
{
	return _last_clicked_action_count > 0 && _last_clicked_actions[0] == id;
};

float(string id) sui_last_clicked_index =
{
	for (int i = 0; i < _last_clicked_action_count; i++)
	{
		if (_last_clicked_actions[i] == id) return i;
	}
	return -1;
};


// release: a thing was held, but now it was released, once

float(string id) sui_is_released =
{
	return _release_action_count > 0 && _release_actions[0] == id;
};

float(string id) sui_release_index =
{
	for (int i = 0; i < _release_action_count; i++)
	{
		if (_release_actions[i] == id) return i;
	}
	return -1;
};


float(float num) mouse_action_sanity =
{
	if (num >= MAX_MOUSE_ACTIONS)
	{
		//printf("^3sui warning: you have exceeded the amount of overlapping action elements with %.0f, MAX_MOUSE_ACTIONS = %.0f\n", num, MAX_MOUSE_ACTIONS);
		return TRUE;
	}
	return FALSE;
};

// mouse move, mostly just update hovers

void(vector pos) _sui_mouse_move =
{
	_cursor_position = pos;
	_action_element_count_sanity();
	
	// Reset hover, it'll be back to what it used to be before draw gets called if mouse is still on same element
	_hover_action_count = 0;
	_hover_actions[0] = "";
	
	// Iterate front to back, so topmost element gets the click/hover
	for (int i = min(MAX_ACTION_ELEMENTS, _action_elements_index) - 1; i >= 0; i--)
	{
		if (is_2dpoint_in_bbox(_cursor_position, _action_elements[i].pos, _action_elements[i].size))
		{
			if (mouse_action_sanity(_hover_action_count)) break;
			
			if (_hover_action_count == 0) _cursor_relative_hover = _cursor_position - _action_elements[i].pos; 
			_hover_actions[_hover_action_count] = _action_elements[i].id;
			_hover_action_count += 1;
		}
	}

	// Cypress -- Reset last_hovered if nothing is hovered for menu
	// navigation sounds
	if (!_hover_action_count)
		last_hovered_id = "";
};

void() _sui_menukey_downarrow =
{
	string current_hovered_option = _hover_actions[0];

	switch(current_menu) {

#ifdef MENU

		case MENU_MAIN:
			_hover_actions[0] = Menu_Main_GetNextButton(current_hovered_option);
			break;
		// TODO: do this
#if 0
		case MENU_COOP:
		case MENU_COOPJOIN:
		case MENU_COOPBROWSE:
		case MENU_COOPDIRECT:
		case MENU_COOPCREATE:
		case MENU_COOPSTOCK:
			_hover_actions[0] = Menu_Maps_GetNextButton(current_hovered_option);
			break;
#endif
		case MENU_SOLO:
		case MENU_SOLOUSER:
			_hover_actions[0] = Menu_Maps_GetNextButton(current_hovered_option);
			break;

#else

		case MENU_PAUSE:
			_hover_actions[0] = Menu_Pause_GetNextButton(current_hovered_option);
			break;

#endif // MENU

		case MENU_OPTIONS:
			_hover_actions[0] = Menu_Options_GetNextButton(current_hovered_option);
			break;
		case MENU_VIDEO:
			_hover_actions[0] = Menu_Video_GetNextButton(current_hovered_option);
			break;
		case MENU_AUDIO:
			_hover_actions[0] = Menu_Audio_GetNextButton(current_hovered_option);
			break;
		case MENU_CONTROL:
			_hover_actions[0] = Menu_Control_GetNextButton(current_hovered_option);
			break;
		case MENU_GAMEPAD:
			_hover_actions[0] = Menu_Gamepad_GetNextButton(current_hovered_option);
			break;
		case MENU_BINDINGS:
			_hover_actions[0] = Menu_Bindings_GetNextButton(current_hovered_option);
			break;
		default:
			break;
	}

	_hover_action_count = 1;
};

void() _sui_menukey_uparrow =
{
	string current_hovered_option = _hover_actions[0];

	switch(current_menu) {

#ifdef MENU

		case MENU_MAIN:
			_hover_actions[0] = Menu_Main_GetPreviousButton(current_hovered_option);
			break;
		case MENU_SOLO:
		case MENU_SOLOUSER:
			_hover_actions[0] = Menu_Maps_GetPreviousButton(current_hovered_option);
			break;

#else

		case MENU_PAUSE:
			_hover_actions[0] = Menu_Pause_GetPreviousButton(current_hovered_option);
			break;

#endif // MENU

		case MENU_OPTIONS:
			_hover_actions[0] = Menu_Options_GetPreviousButton(current_hovered_option);
			break;
		case MENU_VIDEO:
			_hover_actions[0] = Menu_Video_GetPreviousButton(current_hovered_option);
			break;
		case MENU_AUDIO:
			_hover_actions[0] = Menu_Audio_GetPreviousButton(current_hovered_option);
			break;
		case MENU_CONTROL:
			_hover_actions[0] = Menu_Control_GetPreviousButton(current_hovered_option);
			break;
		case MENU_GAMEPAD:
			_hover_actions[0] = Menu_Gamepad_GetPreviousButton(current_hovered_option);
			break;
		case MENU_BINDINGS:
			_hover_actions[0] = Menu_Bindings_GetPreviousButton(current_hovered_option);
			break;
		default:
			break;
	}
	
	_hover_action_count = 1;
};

void() _sui_menukey_enter =
{
	if (_hover_actions[0] == "")
		_sui_menukey_downarrow();

	_click_actions[0] =  _hover_actions[0];
	_click_action_count = 1;
	_last_clicked_action_count = 1;
};

// JERK ALERT: hard to pass input params for it without them just being the globals
// ... so it just straight up uses the globals... optimization
void() _sui_mouse1_down =
{
	// Cheap but it should work...
	_cursor_click = _cursor_position;
	_cursor_relative_click = _cursor_relative_hover;
	for (int i = 0; i < _hover_action_count; i++) _hold_actions[i] = _hover_actions[i];
	_hold_action_count = _hover_action_count;
	_holding_click = TRUE;
	_last_clicked_action_count = 0;
};

void() _sui_mouse1_up =
{
	// Can't be cheap here, we have to get the action fn of the element anyway so..
	_action_element_count_sanity();
	
	// Assume we won't hit anything
	_click_action_count = 0;
	_last_clicked_action_count = 0;
	
	// Iterate front to back, so topmost element gets the click/hover
	for (int i = min(MAX_ACTION_ELEMENTS, _action_elements_index) - 1; i >= 0; i--)
	{
		// If the thing wasn't the same thing we started pressing down on, ignore
		for (int j = 0; j < _hold_action_count; j++)
		{
			if (_hold_actions[j] == _action_elements[i].id) // yes this element was held
			{		
				// Still in bounds?
				if (is_2dpoint_in_bbox(_cursor_position, _action_elements[i].pos, _action_elements[i].size))
				{
					if (mouse_action_sanity(_click_action_count)) break;
					
					// Register click
					_click_actions[_click_action_count] = _action_elements[i].id;
					_last_clicked_actions[_last_clicked_action_count] = _action_elements[i].id;
					_click_action_count += 1;
					_last_clicked_action_count += 1;
				}
			}
		}
	}
	
	// In case someone is keeping state on hold and wants to do stuff on release, even if cursor has moved
	for (int i = 0; i < _hold_action_count; i++) _release_actions[i] = _hold_actions[i];
	_release_action_count = _hold_action_count;
	_hold_action_count = 0;
	_holding_click = FALSE;
};

// HACK!
void() sui_reset_hover =
{
	_hover_actions[0] = "";
};

void(vector pos, vector size, string id, void(float index, vector click_ratios) action) sui_action_element =
{
	if (!_sui_draw_initialized) 
	{
		print("^1sui error: adding sui elements before sui_pre_draw!\n^1  Always do your sui menus between sui_pre_draw and sui_draw!\n");
	}
	if (_action_elements_index >= MAX_ACTION_ELEMENTS)
	{
		// Silently fail here, sui will let us know another way, increase the count
		// so that the error in click/mousemove handlers prints correct numbers.
		_action_elements_index += 1;
		return;
	}
	
	sui_transform_box(pos, size);
	
	if (_sui_is_clipping)
	{
		vector oldpos = pos;
		pos_x = max(pos_x, _sui_clip_area_mins_x);
		pos_y = max(pos_y, _sui_clip_area_mins_y);
		
		size -= pos - oldpos;
		
		size_x -= bound(0, (pos_x + size_x - _sui_clip_area_maxs_x), size_x);
		size_y -= bound(0, (pos_y + size_y - _sui_clip_area_maxs_y), size_y);
	}
	
	_action_elements[_action_elements_index].pos = pos;
	_action_elements[_action_elements_index].size = size;
	_action_elements[_action_elements_index].id = id;
	_action_elements[_action_elements_index].action = action;
	
	_action_elements_index += 1;
};


// Input related stuff

string _sui_binding_command;
string _sui_binding_command_name;

struct _input_t {
	float char;
	float scan;
};
const float MAX_INPUTS = 64;

_input_t _input_buffer[MAX_INPUTS];
float _input_index;
float _input_length;


// probably good to use it like while (sui_get_input(char, scan)) { ... };
float(__inout float char, __inout float scan) sui_get_input =
{
	if (_input_index >= _input_length) return FALSE;
	
	char = _input_buffer[_input_index].char;
	scan = _input_buffer[_input_index].scan;
	_input_index++;
	
	return TRUE;
};

// if 2 controls want to read the same input for some reason..
void() sui_reread_input =
{
	_input_index = 0;
};

void() sui_clear_input =
{
	_input_length = 0;
	_input_index = 0;
};

float(float char, float scan) _sui_add_input =
{
	// TODO check if input was listened, return FALSE if not
	if (_input_length >= MAX_INPUTS)
	{
		// printf("^3sui warning: exceeded amount of per frame inputs count MAX_INPUTS = %.0f\n"
		// 	"^3 - make sure sui_input_event isn't being called without sui_draw being called in update loop\n"
		// 	"^3 - consider increasing MAX_INPUTS\n", MAX_INPUTS);
		return TRUE;
	}
	_input_buffer[_input_length].char = char;
	_input_buffer[_input_length].scan = scan;
	_input_length += 1;
	return TRUE;
};

// Listen to a certain keycode if it was pressed, this way sui know it was requested
float(float keycode) sui_listen_keycode_down =
{
	return FALSE;
};

// all text that was input between last and current frame
string() sui_listen_text_input =
{
	return "";
};

void(float char, float scan, __inout string text, __inout float cursor) sui_handle_text_input =
{
	float maxlen = 128;
	
	string prev = text;
	string pre_cursor, post_cursor;
	float length = strlen(prev);
	if (char > 31 && char < 128) //an actual input
	{
		if (length >= maxlen) return;
		pre_cursor = substring(prev, 0, cursor);
		post_cursor = substring(prev, cursor, length);
		
		text = sprintf("%s%s%s", pre_cursor, chr2str(char), post_cursor);
		cursor += 1;
	}
	else if (char == 8 || scan == K_BACKSPACE) // backspace
	{
		if (cursor <= 0) return;
		pre_cursor = substring(prev, 0, cursor - 1);
		post_cursor = substring(prev, cursor, length);
		cursor -= 1;
		cursor = max(0, cursor);
		text = strcat(pre_cursor, post_cursor);
	}
	else if (scan == K_DEL)
	{
		if (cursor >= length) return;
		pre_cursor = substring(prev, 0, cursor);
		post_cursor = substring(prev, cursor + 1, length);
		text = strcat(pre_cursor, post_cursor);
	}
	else if (char == 13 || char == 27 || scan == K_ENTER || scan == K_ESCAPE) // enter or escape
	{
		// Commit and deselect...
		// Let's try a hack..
		_last_clicked_action_count = 0;
	}
	else if (scan == K_LEFTARROW)
	{
		cursor -= 1;
		cursor = max(0, cursor);
	}
	else if (scan == K_RIGHTARROW)
	{
		cursor += 1;
		cursor = min(strlen(prev), cursor);
	}
};

void(float maxlen, __inout string text, __inout float cursor) sui_cap_input_length =
{
	if (strlen(text) > maxlen)
	{
		text = substring(text, 0, strlen(text));
		cursor = strlen(text);
	}
};

void(string command) _sui_unbind =
{
	tokenize(findkeysforcommand(command));
	string keyname = keynumtostring(stof(argv(0)));
	string altkeyname = keynumtostring(stof(argv(1)));
	localcmd(sprintf("unbind %s\n", keyname));
	localcmd(sprintf("unbind %s\n", altkeyname));
};

float menu_bind_currentwatcher;
void(float scan, string command) _sui_do_keybind =
{
	if (scan == K_ESCAPE) 
	{
		_sui_binding_command = "";
		_sui_binding_command_name = "";
		return;
	}
	if (scan == K_BACKSPACE)
	{
		_sui_unbind(command);
		_sui_binding_command = "";
		_sui_binding_command_name = "";
		return;
	}
	string keyname = keynumtostring(scan);
	_sui_unbind(command);
	localcmd(sprintf("bind %s %s\n", keyname, command));
	_sui_binding_command = "";
	_sui_binding_command_name = "";
	menu_bind_currentwatcher = 0;
};

void(string command, string command_name) sui_start_bind =
{
	_sui_binding_command = command;
	_sui_binding_command_name = command_name;
};

// void(float evtype, float scanx, float chary, float devid) sui_input_event
//	 same args is CSQC_InputEvent.
//	 return value tells you if sui used the event or not, in case you want to
//	 not let engine handle it if it was used.
// Sets all the internal sui action stuff, call it in CSQC_InputEvent
float(float evtype, float scanx, float chary, float devid) sui_input_event =
{
	switch (evtype)
	{
	case IE_MOUSEABS:
		_sui_mouse_move([scanx, chary]);
		return TRUE;
	case IE_MOUSEDELTA:
		// Big question mark...
		// maybe make our own delta based sui_cursor here..
		// maybe just ignore delta and let user fake mouseabs with their own
		// delta cursor by passing different params to this func...?
		// for MVP let's just use mouseabs only
		return FALSE;
	case IE_KEYDOWN:
		if (_sui_binding_command != "")
		{
			_sui_do_keybind(scanx, _sui_binding_command);
			return TRUE;
		}
		else if (scanx == K_MOUSE1)
		{
			_sui_mouse1_down();
			return TRUE;
		}
		else if (scanx == K_DOWNARROW || scanx == K_GP_DPAD_DOWN)
		{
			_sui_menukey_downarrow();
			return TRUE;
		}
		else if (scanx == K_UPARROW || scanx == K_GP_DPAD_UP)
		{
			_sui_menukey_uparrow();
			return TRUE;
		}
		else if (scanx == K_ENTER || scanx == K_GP_A)
		{
			_sui_menukey_enter();
			return TRUE;
		}
		else
		{
			if ((scanx == K_ESCAPE || scanx == K_BACKSPACE) && _sui_binding_command != "")
				return TRUE;
			else if (scanx == K_ESCAPE)
				return FALSE;
			return _sui_add_input(chary, scanx);
		}
	case IE_KEYUP:
		if (_sui_binding_command != "")
		{
			// Nothing
			return TRUE;
		}
		else if (scanx == K_MOUSE1)
		{
			_sui_mouse1_up();
			return TRUE;
		}
	default: 
		return FALSE;
	}
	
	return FALSE;
};


// void() sui_pre_draw 
// Resets state for sui actions so that no trouble happens. 
// Call it before your menu code per frame in your draw/updateview.
void(float width, float height) sui_begin =
{
	_action_elements_index = 0;
	_sui_draw_initialized = TRUE;
	
	sui_reset_frame();
	sui_push_frame([0, 0], [width, height]);
}

// void() sui_end
// Call after your menu code per frame in your draw/updateview.
void() sui_end =
{
	// Dirty part:
	_sui_draw_initialized = FALSE;
	// reset "once" type actions
	_click_action_count = 0;
	_release_action_count = 0;
	// empty input buffer
	sui_clear_input();
};


// Different draw components:

void(vector pos, vector size, vector color, float alpha, float flags) sui_fill =
{
	sui_transform_box(pos, size);
	
	drawfill(pos, size, color, alpha, flags);
};

void(vector pos, vector size, string pic, vector color, float alpha, float flags) sui_pic =
{
	sui_transform_box(pos, size);
	
	drawpic(pos, pic, size, color, alpha, flags);
};

void(vector pos, vector size, string pic, vector color, vector source_pos, vector source_size, float alpha, float flags) sui_subpic =
{
	sui_transform_box(pos, size);
	
	drawsubpic(pos, size, pic, source_pos, source_size, color, alpha, flags);
};

// drawsubpic([g_width - 32, g_height - 32], [28, 28], "gfx/menu/social.tga", [0.5, 0], [0.5, 0.5], [1, 1, 1], 1); // YouTube
// 	drawsubpic([g_width - 32, g_height - 64], [28, 28], "gfx/menu/social.tga", [0, 0.5], [0.5, 0.5], [1, 1, 1], 1); // Twitter
// 	drawsubpic([g_width - 32, g_height - 96], [28, 28], "gfx/menu/social.tga", [0.5, 0.5], [0.5, 0.5], [1, 1, 1], 1); // Patreon

void(vector pos, vector size, float width, vector color, float alpha, float flags) sui_border_box =
{
	sui_transform_box(pos, size);
	
	// Top line
	drawfill(pos, [size_x, width], color, alpha, flags);
	// Bottom line
	drawfill([pos_x, pos_y + size_y - width], [size_x, width], color, alpha, flags);
	// Left line
	drawfill([pos_x, pos_y + width], [width, size_y - width * 2], color, alpha, flags);
	// Right line
	drawfill([pos_x + size_x - width, pos_y + width], [width, size_y - width * 2], color, alpha, flags);
};


void(vector pos, vector size, string text, vector color, float alpha, float flags) sui_text =
{
	sui_transform_box(pos, [getTextWidth(text, size_x), size_y]);
	
	Draw_String(pos, text, size, color, alpha, flags);
};

void(float index, vector click_ratios) sui_noop = {};

void(float value) sui_slider_noop = {};

float(string id, vector pos, vector size, vector minmaxsteps, float value, void(float value) action) sui_slidercontrol =
{
	sui_action_element(pos, size, id, sui_noop);
	float newvalue = value;
	
	sui_transform_box(pos, size);
	// user is clicking and holding the slider
	if (sui_is_held(id))
	{
		float min = minmaxsteps[0];
		float max = minmaxsteps[1];
		float steps = minmaxsteps[2];
		float click_ratio = (_cursor_position_x - pos_x) / size_x;
		click_ratio = bound(0, click_ratio, 1);
		if (steps > 0) click_ratio = rint(click_ratio * steps) / steps;
		newvalue = min + click_ratio * (max - min);
		if (newvalue != value) action(newvalue);
	}
	return newvalue;
};

void(string id, vector pos, vector size, __inout string text, __inout float cursor) sui_text_input =
{
	sui_action_element(pos, size, id, sui_noop);
	if (sui_is_clicked(id)) cursor = strlen(text);
	if (sui_is_last_clicked(id))
	{
		float char = 0;
		float scan = 0;
		while(sui_get_input(char, scan)) sui_handle_text_input(char, scan, text, cursor);
	}
};


void(string id, vector size, vector contentsize, __inout vector offset, vector scrollbar_widths) sui_scrollbar =
{
	vector maxoffset = contentsize - size;
	maxoffset_x = max(0, maxoffset_x);
	maxoffset_y = max(0, maxoffset_y);
	sui_push_frame([0, 0], size);
	float ofs;
	float length;
	vector barpos, barsize;
	float scan = 0;
	float char = 0;
	string barname;
	if (maxoffset_y > 0 && contentsize_y > 0)
	{
		sui_set_align([SUI_ALIGN_END, SUI_ALIGN_START]);
		sui_push_frame([0, 0], [scrollbar_widths_y, size_y]);
		ofs = (offset_y / contentsize_y) * size_y;
		length = (size_y / contentsize_y) * size_y;
		barpos = [0, ofs];
		barsize = [scrollbar_widths_y, length];
		barname = strcat(id, "vbar");
		
		if (sui_is_held(barname))
		{
			vector anchor = barpos + _cursor_relative_click;
			sui_transform_point(anchor);
			float diff = _cursor_position_y - anchor_y;
			offset_y += (diff * contentsize_y) / size_y; //  * contentsize_y; // (size_y / contentsize_y);
		}

		sui_fill(barpos, barsize, '0.1 0.1 0.1' * (1 - sui_is_hovered(barname)), 0.66, 0);
		sui_action_element(barpos, barsize, barname, sui_noop);
		sui_pop_frame();
	} 
	sui_pop_frame();
};

void(string id, vector pos, vector size, vector contentsize, __inout vector offset, vector scrollbar_widths) sui_scroll_view_begin =
{
	// make space for scrollbars
	sui_push_frame(pos, size - [scrollbar_widths_y, scrollbar_widths_x]);
	sui_action_element([0, 0], size, id, sui_noop);
	
	if (sui_hover_index(id) > -1)
	{		
		float scrollamount = 0;
		float char = 0;
		float scan = 0;
		sui_reread_input();
		while (sui_get_input(char, scan))
		{
			if (scan == K_MWHEELUP) scrollamount -= 20;
			if (scan == K_MWHEELDOWN) scrollamount += 20;
		}
		offset_y += scrollamount;
	}
	
	vector maxoffset = contentsize - size;
	maxoffset_x = max(0, maxoffset_x);
	maxoffset_y = max(0, maxoffset_y);
	offset_x = bound(0, offset_x, maxoffset_x);
	offset_y = bound(0, offset_y, maxoffset_y);

	sui_scrollbar(id, size, contentsize, offset, scrollbar_widths);
	
	offset_x = bound(0, offset_x, maxoffset_x);
	offset_y = bound(0, offset_y, maxoffset_y);
	
	sui_clip_to_frame();
	

	sui_push_frame(-1 * offset, contentsize);
};

void() sui_scroll_view_end =
{
	sui_pop_frame();
	sui_reset_clip();
	sui_pop_frame();
};

float _sui_list_item_height;
float _sui_list_first;
float _sui_list_last;
float _sui_list_pos;
int _sui_list_index;
void(string id, vector pos, vector size, vector itemsize, float numitems, __inout vector offset, vector scrollbar_widths) sui_list_view_begin =
{
	vector contentsize = [itemsize_x, itemsize_y * numitems];
	sui_scroll_view_begin(id, pos, size, contentsize, offset, scrollbar_widths);
	
	_sui_list_item_height = itemsize_y;
	float hidden_above = floor(offset_y / itemsize_y);
	_sui_list_first = max(0, hidden_above); // Index of first elem
	_sui_list_last = min(_sui_list_first + rint(size_y / itemsize_y) + 1, numitems);
	_sui_list_pos = hidden_above * itemsize_y;
	_sui_list_index = _sui_list_first;
};

float(__inout vector pos) sui_list_item =
{
	if (_sui_list_index >= _sui_list_last) return -1;
	pos = _sui_list_index * [0, _sui_list_item_height];
	_sui_list_index += 1;
	return _sui_list_index - 1;
};

void() sui_list_view_end =
{
	sui_scroll_view_end();
};

string(string id, vector pos, vector size, string name, string command) sui_binder =
{
	sui_action_element(pos, size, id, sui_noop);
	if (sui_is_released(id))
	{
		sui_start_bind(command, name);
	}
	
	tokenize(findkeysforcommand(command));
	string keyname = keynumtostring(stof(argv(0)));
	if (keyname == "01") keyname = "unbound";
	
	return keyname;
};

// -------------------- END OF SUI SYSTEM STUFF --------------------
