
UILib = {}

UILib.spriteDataPool = {}
UILib.focusedSpriteWidget = nil
UILib.hoveredSpriteWidget = nil
UILib.modalOpen = false
UILib.modalJustOpen = false
UILib.collectSpritePool = true
UILib.cursor = 1
UILib.cursorChanged = false

UILib.uiDepth_mainButtons = -10.0
UILib.uiDepth_views = 0.0
UILib.uiDepth_overViews = 100.0
UILib.uiDepth_cursor = 900.0

UILib.CURSOR_HIDDEN = 0
UILib.CURSOR = 1
UILib.CURSOR_MOVE = 2
UILib.CURSOR_ROTATE = 3
UILib.CURSOR_SCALE = 4
UILib.CURSOR_PIXEL = 5
UILib.CURSOR_SLIDER = 6

UILib._draggingSlider = nil
UILib._sliderDragStartX = 0.0
UILib._sliderDragStartValue = 0.0

function UILib.isSliderDragging()
	return UILib._draggingSlider ~= nil
end

function UILib.clear()
	UILib.spriteDataPool = {}
	UILib.focusedSpriteWidget = nil
	UILib.hoveredSpriteWidget = nil
	UILib.modalOpen = false
	UILib.modalJustOpen = false
	UILib.collectSpritePool = true
	UILib.cursor = 1
	UILib.cursorSprite = nil
	UILib.cursorSpriteData = nil
	UILib._draggingSlider = nil
end

function UILib._applyCursorType(cursorId)
	if UILib.cursorSpriteData == nil then return end

	if cursorId == 0 then
		UILib.cursorSpriteData.texturePos.x = 8.0 * 8
		UILib.cursorSpriteData.texturePos.y = 8.0 * 5

		UILib.cursorSpriteData.textureSize.x = 8.0
		UILib.cursorSpriteData.textureSize.y = 8.0
	elseif cursorId == UILib.CURSOR_SLIDER then
		UILib.cursorSpriteData.texturePos.x = 8.0 * 7
		UILib.cursorSpriteData.texturePos.y = 8.0 * 6

		UILib.cursorSpriteData.textureSize.x = 16.0
		UILib.cursorSpriteData.textureSize.y = 16.0
	else
		local textureOffset = (cursorId - 1) * 2
		UILib.cursorSpriteData.texturePos.x = 8.0 * (5 + textureOffset)
		UILib.cursorSpriteData.texturePos.y = 8.0 * 4

		UILib.cursorSpriteData.textureSize.x = 16.0
		UILib.cursorSpriteData.textureSize.y = 16.0
	end
end

function UILib.setCursor(cursorId)
	if cursorId ~= UILib.cursor then
		UILib.cursor = cursorId
		UILib.cursorChanged = true
		UILib._applyCursorType(cursorId)
	end
end

function UILib.isHovered()
	if UILib.hoveredSpriteWidget ~= nil then
		return true
	end

	return false
end

function UILib.mousePressedFromUI()
	return UILib.mousePressedOnUI
end

function UILib.setFocusedSpriteWidget(spriteData, state)
	if UILib.focusedSpriteWidget ~= nil then
		if UILib.focusedSpriteWidget.onFocus ~= nil then
			UILib.focusedSpriteWidget.onFocus(UILib.focusedSpriteWidget, false)
		end

		UILib.focusedSpriteWidget = nil
	end

	if spriteData ~= nil and spriteData.onFocus ~= nil then
		spriteData.onFocus(spriteData, state)
	end

	if state == true then
		UILib.focusedSpriteWidget = spriteData
	end
end

function UILib.setCollectSpritePool(state)
	UILib.collectSpritePool = state
end

function UILib.newSpriteWidget()
	local spriteData = {}

	if UILib.collectSpritePool == true then
		UILib.spriteDataPool[#UILib.spriteDataPool + 1] = spriteData
	end

	spriteData.sprite = allocSprite()
	spriteData.update = UILib.baseSpriteWidgetUpdate
	spriteData.position = vec3(0.0, 0.0, 0.0)
	spriteData.size = vec2(0.0, 0.0)
	spriteData.color = vec4(1.0, 1.0, 1.0, 1.0)
	spriteData.visible = true
	spriteData.hovered = false
	spriteData.changed = true

	return spriteData
end

function UILib.baseSpriteWidgetUpdate(spriteData, mouseInSprite, mousePressed, mouseDown, rightMousePressed)
	if UILib.cursor == UILib.CURSOR_HIDDEN then
		drawSprite2D(spriteData.sprite)

		return
	end

	if mouseInSprite == spriteData.hovered and spriteData.changed == false then
		drawSprite2D(spriteData.sprite)

		return
	end

	spriteData.hovered = mouseInSprite

	if spriteData.hoverEnabled == true then
		local hovered = spriteData.hovered

		if spriteData.forceHovered == true then
			hovered = true
		end

		if hovered == true then
			spriteData.color = spriteData.colorHover

			if spriteData.borderColorBase ~= nil then
				spriteData.borderColor = spriteData.borderColorBase + 30
			end
		else
			spriteData.color = spriteData.colorBase

			if spriteData.borderColorBase ~= nil then
				spriteData.borderColor = spriteData.borderColorBase
			end
		end
	end

	setSprite(spriteData.sprite, spriteData)
	drawSprite2D(spriteData.sprite)

	spriteData.changed = false
end

function UILib.panel(text, pos, size, color, textColor, callback)
	local spriteData = UILib.newSpriteWidget()

	spriteData.position.x = pos.x
	spriteData.position.y = pos.y
	spriteData.position.z = pos.z

	spriteData.size.x = size.x
	spriteData.size.y = size.y

	spriteData.color.x = color.x
	spriteData.color.y = color.y
	spriteData.color.z = color.z
	spriteData.color.w = color.w

	if text ~= "" and text ~= nil then
		spriteData.text = text
	end
	
	spriteData.roundCorner = 0.0
	spriteData.visible = true

	if textColor ~= nil then
		spriteData.textColor = vec4Copy(textColor)
	else
		spriteData.textColor = vec4(255.0, 255.0, 255.0, 255.0)
	end

	if callback ~= nil then
		spriteData.callback = callback
		spriteData.hoverEnabled = true
		spriteData.colorHover = color + 50.0
	end
	
	return spriteData
end

function UILib.setButtonPressed(button, state)
	if state == button.pressed then
		return
	end

	button.pressed = state

	if button.pressed == true then
		button.colorBaseOld = vec4(button.colorBase)
		button.colorBase = vec4(button.colorPressed)
		button.colorHover = button.colorPressed + 20

		button.textColor = Config.paletteNames.grey

		if button.iconSpriteData ~= nil then
			button.iconSpriteData.color = Config.paletteNames.grey
			setSprite(button.iconSprite, button.iconSpriteData)
		end
	else
		if button.colorBaseOld ~= nil then
			button.colorBase = vec4(button.colorBaseOld)
			button.colorHover = button.colorBaseOld + 50
		end

		button.textColor = Config.paletteNames.white

		if button.iconSpriteData ~= nil then
			button.iconSpriteData.color = Config.paletteNames.white
			setSprite(button.iconSprite, button.iconSpriteData)
		end
	end

	button.changed = true
end

function UILib.button(text, pos, size, color, callback)
	local spriteData = UILib.newSpriteWidget()

	spriteData.pressed = false

	spriteData.position.x = pos.x
	spriteData.position.y = pos.y
	spriteData.position.z = pos.z

	spriteData.size.x = size.x
	spriteData.size.y = size.y

	spriteData.color.x = color.x
	spriteData.color.y = color.y
	spriteData.color.z = color.z
	spriteData.color.w = color.w

	spriteData.hoverEnabled = true
	spriteData.textColor = vec4(255, 255, 255, 255)
	spriteData.colorBase = vec4(color.x, color.y, color.z, color.w)
	spriteData.colorHover = color + 50
	spriteData.colorPressed = Config.uiColors.accent
	spriteData.textOffset = vec2(0, 0)
	spriteData.visible = true

	spriteData.roundCorner = 0.0

	spriteData.text = text

	spriteData.callback = callback

	return spriteData
end

function UILib.buttonIcon(pos, size, color, iconPos, iconSize, iconTexturePos, iconTextureSize, iconColor, callback)
	local spriteData = UILib.button("", pos, size, color, callback)

	spriteData.iconSprite = allocSprite()
	spriteData.iconSpriteData = {}
	
	spriteData.iconSpriteData.position = vec3()
	spriteData.iconSpriteData.position.x = pos.x + iconPos.x
	spriteData.iconSpriteData.position.y = pos.y + iconPos.y
	spriteData.iconSpriteData.position.z = pos.z + 0.1

	spriteData.iconSpriteData.size = vec2()
	spriteData.iconSpriteData.size.x = iconSize.x
	spriteData.iconSpriteData.size.y = iconSize.y

	spriteData.iconSpriteData.color = vec4()
	spriteData.iconSpriteData.color.x = iconColor.x
	spriteData.iconSpriteData.color.y = iconColor.y
	spriteData.iconSpriteData.color.z = iconColor.z
	spriteData.iconSpriteData.color.w = iconColor.w

	spriteData.iconSpriteData.textureId = TEXTURE_ICONS
	spriteData.iconSpriteData.texturePos = iconTexturePos
	spriteData.iconSpriteData.textureSize = iconTextureSize
	
	spriteData.update = function(spriteData, mouseInSprite, mousePressed, mouseDown, rightMousePressed)
		if spriteData.changed == true then
			setSprite(spriteData.iconSprite, spriteData.iconSpriteData)
		end

		UILib.baseSpriteWidgetUpdate(spriteData, mouseInSprite, mousePressed, mouseDown, rightMousePressed)
		
		drawSprite2D(spriteData.iconSprite)
	end

	spriteData.callback = callback

	setSprite(spriteData.iconSprite, spriteData.iconSpriteData)

	return spriteData
end

function UILib.colorPicker()
	UILib.setCollectSpritePool(false)

	local colorPicker = {}
	colorPicker.children = {}

	colorPicker.color = vec4(0, 0, 0, 255)

	colorPicker.updateColorCallback = nil
	colorPicker.copyColorCallback = nil
	colorPicker.pasteColorCallback = nil
	colorPicker.startColorPickCallback = nil

	colorPicker.visible = true

	colorPicker.paletteCanBeChanged = false

	local width = 44.0 + (8.0 * 3)
	colorPicker.panel = UILib.panel("", vec3(242.0, 100.0, 10.0), vec2(width, 120.0), Config.uiColors.panelLight)
	colorPicker.children[#colorPicker.children + 1] = colorPicker.panel
	colorPicker.panel.hoverEnabled = false

	colorPicker.position = colorPicker.panel.position
	colorPicker.size = colorPicker.panel.size

	colorPicker.textLineR = UILib.textLine(vec3(40.0, 4.0, 10.1), vec2(8.0 * 3, 8.0), function() colorPicker.rTextCanged(colorPicker) end)
	colorPicker.textLineR.localPos = vec3Copy(colorPicker.textLineR.position)
	colorPicker.children[#colorPicker.children + 1] = colorPicker.textLineR

	colorPicker.textLineG = UILib.textLine(vec3(40.0, 4.0 * 4 , 10.1), vec2(8.0 * 3, 8.0), function() colorPicker.gTextCanged(colorPicker) end)
	colorPicker.textLineG.localPos = vec3Copy(colorPicker.textLineG.position)
	colorPicker.children[#colorPicker.children + 1] = colorPicker.textLineG

	colorPicker.textLineB = UILib.textLine(vec3(40.0, 4.0 * 7, 10.1), vec2(8.0 * 3, 8.0), function() colorPicker.bTextCanged(colorPicker) end)
	colorPicker.textLineB.localPos = vec3Copy(colorPicker.textLineB.position)
	colorPicker.children[#colorPicker.children + 1] = colorPicker.textLineB

	colorPicker.textLineA = UILib.textLine(vec3(40.0, 4.0 * 10, 10.1), vec2(8.0 * 3, 8.0), function() colorPicker.aTextCanged(colorPicker) end)
	colorPicker.textLineA.localPos = vec3Copy(colorPicker.textLineA.position)
	colorPicker.children[#colorPicker.children + 1] = colorPicker.textLineA

	colorPicker.rColorA = vec4(255, 255, 255, 255)
	colorPicker.rColorB = vec4(255, 255, 255, 255)

	colorPicker.gColorA = vec4(255, 255, 255, 255)
	colorPicker.gColorB = vec4(255, 255, 255, 255)

	colorPicker.bColorA = vec4(255, 255, 255, 255)
	colorPicker.bColorB = vec4(255, 255, 255, 255)

	colorPicker.aColorA = colorPicker.panel.color
	colorPicker.aColorB = vec4(255, 255, 255, 255)

	colorPicker.draggingRGBA = -1

	colorPicker.colorR = {}
	colorPicker.colorG = {}
	colorPicker.colorB = {}
	colorPicker.colorA = {}

	colorPicker.colorR.value = 0
	colorPicker.colorG.value = 0
	colorPicker.colorB.value = 0
	colorPicker.colorA.value = 255

	colorPicker.colorR.position = vec2()
	colorPicker.colorR.size = vec2(32.0, 8.0)

	colorPicker.colorG.position = vec2()
	colorPicker.colorG.size = vec2(32.0, 8.0)

	colorPicker.colorB.position = vec2()
	colorPicker.colorB.size = vec2(32.0, 8.0)

	colorPicker.colorA.position = vec2()
	colorPicker.colorA.size = vec2(32.0, 8.0)

	colorPicker.colorR.handle = UILib.button("", vec3(0.0, 0.0, 10.2), vec2(3.0, 10.0), Config.uiColors.handle - 50.0, function() end)
	colorPicker.colorR.handle.border = 1.0
	colorPicker.colorR.handle.borderColorBase = colorPicker.colorR.handle.color - 50.0
	colorPicker.children[#colorPicker.children + 1] = colorPicker.colorR.handle

	colorPicker.colorG.handle = UILib.button("", vec3(0.0, 0.0, 10.2), vec2(3.0, 10.0), Config.uiColors.handle - 50.0, function() end)
	colorPicker.colorG.handle.border = 1.0
	colorPicker.colorG.handle.borderColorBase = colorPicker.colorG.handle.color - 50.0
	colorPicker.children[#colorPicker.children + 1] = colorPicker.colorG.handle

	colorPicker.colorB.handle = UILib.button("", vec3(0.0, 0.0, 10.2), vec2(3.0, 10.0), Config.uiColors.handle - 50.0, function() end)
	colorPicker.colorB.handle.border = 1.0
	colorPicker.colorB.handle.borderColorBase = colorPicker.colorB.handle.color - 50.0
	colorPicker.children[#colorPicker.children + 1] = colorPicker.colorB.handle

	colorPicker.colorA.handle = UILib.button("", vec3(0.0, 0.0, 10.2), vec2(3.0, 10.0), Config.uiColors.handle - 50.0, function() end)
	colorPicker.colorA.handle.border = 1.0
	colorPicker.colorA.handle.borderColorBase = colorPicker.colorA.handle.color - 50.0
	colorPicker.children[#colorPicker.children + 1] = colorPicker.colorA.handle

	for i = 1, 32 do
		local colorR = {}
		colorR.sprite = allocSprite()
		colorR.spriteData = {}
		colorR.spriteData.localPos = vec3((i - 1) + 4.0, 4.0, 10.1)
		colorR.spriteData.position = vec3(0.0, 0.0, 10.1)
		colorR.spriteData.size = vec2(2.0, 8.0)
		colorPicker.colorR[i] = colorR

		local colorG = {}
		colorG.sprite = allocSprite()
		colorG.spriteData = {}
		colorG.spriteData.localPos = vec3((i - 1) + 4.0, 4.0 * 4, 10.1)
		colorG.spriteData.position = vec3(0.0, 0.0, 10.1)
		colorG.spriteData.size = vec2(2.0, 8.0)
		colorPicker.colorG[i] = colorG

		local colorB = {}
		colorB.sprite = allocSprite()
		colorB.spriteData = {}
		colorB.spriteData.localPos = vec3((i - 1) + 4.0, 4.0 * 7, 10.1)
		colorB.spriteData.position = vec3(0.0, 0.0, 10.1)
		colorB.spriteData.size = vec2(2.0, 8.0)
		colorPicker.colorB[i] = colorB

		local colorA = {}
		colorA.sprite = allocSprite()
		colorA.spriteData = {}
		colorA.spriteData.localPos = vec3((i - 1) + 4.0, 4.0 * 10, 10.1)
		colorA.spriteData.position = vec3(0.0, 0.0, 10.1)
		colorA.spriteData.size = vec2(2.0, 8.0)
		colorPicker.colorA[i] = colorA
	end

	colorPicker.currentColorSpriteData = {}
	colorPicker.currentColorSpriteData.sprite = allocSprite()
	colorPicker.currentColorSpriteData.localPos = vec3(4.0, (4.0 * 11.0) + 8.0, 10.1)
	colorPicker.currentColorSpriteData.position = vec3(0.0, 0.0, 10.1)
	colorPicker.currentColorSpriteData.color = vec4(255.0, 255.0, 255.0, 255.0)
	colorPicker.currentColorSpriteData.size = vec2(32.0, 20.0)
	
	colorPicker.copyButton = UILib.buttonIcon(
		vec3(40.0, (4.0 * 11.0) + 8.0, 10.1), 
		vec2(8.0, 8.0), 
		vec4(0, 0, 0, 0),
		vec2(0.0, 0.0), vec2(8.0, 8.0),
		vec2(8.0 * 5, 8.0), vec2(8.0, 8.0),
		Config.uiColors.icon, function() 
			colorPicker.copyColorCallback()
		end)
	colorPicker.copyButton.localPos = vec3Copy(colorPicker.copyButton.position)
	colorPicker.copyButton.colorBase = colorPicker.panel.color
	colorPicker.copyButton.colorHover = colorPicker.panel.color + 50.0
	colorPicker.children[#colorPicker.children + 1] = colorPicker.copyButton
	colorPicker.copyButton.desc = "copy color to clipboard"

	colorPicker.pasteButton = UILib.buttonIcon(
		vec3(40.0 + 8.0 + 4.0, (4.0 * 11.0) + 8.0, 10.1), 
		vec2(8.0, 8.0), 
		vec4(0, 0, 0, 0),
		vec2(0.0, 0.0), vec2(8.0, 8.0),
		vec2(8.0 * 6, 8.0), vec2(8.0, 8.0),
		Config.uiColors.icon, function()
			colorPicker.pasteColorCallback()
		end)
	colorPicker.pasteButton.localPos = vec3Copy(colorPicker.pasteButton.position)
	colorPicker.pasteButton.colorBase = colorPicker.panel.color
	colorPicker.pasteButton.colorHover = colorPicker.panel.color + 50.0
	colorPicker.children[#colorPicker.children + 1] = colorPicker.pasteButton
	colorPicker.pasteButton.desc = "paste color from clipboard"

	colorPicker.pickerButton = UILib.buttonIcon(
		vec3(40.0, (4.0 * 13.0) + 8.0 + 4.0, 10.1), 
		vec2(8.0, 8.0), 
		vec4(0, 0, 0, 0),
		vec2(0.0, 0.0), vec2(8.0, 8.0),
		vec2(8.0 * 7, 8.0), vec2(8.0, 8.0),
		Config.uiColors.icon, function() 
			UILib.setButtonPressed(colorPicker.pickerButton, true)
			colorPicker.startColorPickCallback()
		end)
	colorPicker.pickerButton.localPos = vec3Copy(colorPicker.pickerButton.position)
	colorPicker.pickerButton.colorBase = colorPicker.panel.color
	colorPicker.pickerButton.colorHover = colorPicker.panel.color + 50.0
	colorPicker.children[#colorPicker.children + 1] = colorPicker.pickerButton
	colorPicker.pickerButton.desc = "pick color"
	UILib.setButtonPressed(colorPicker.pickerButton, false)

	colorPicker.paletteLockButton = UILib.button("P",
		vec3(40.0 + 8.0 + 4.0, (4.0 * 13.0) + 8.0 + 4.0, 10.1), 
		vec2(8.0, 8.0), 
		Config.uiColors.icon, function() colorPicker:paletteLockButtonPressed() end)
	colorPicker.paletteLockButton.localPos = vec3Copy(colorPicker.paletteLockButton.position)
	colorPicker.paletteLockButton.colorBase = colorPicker.panel.color
	colorPicker.paletteLockButton.colorHover = colorPicker.panel.color + 50.0
	colorPicker.children[#colorPicker.children + 1] = colorPicker.paletteLockButton
	colorPicker.paletteLockButton.desc = "enable palette color changes"

	colorPicker.selectedPaletteId = -1

	colorPicker.palette = {} 
	colorPicker.paletteButtons = {}
	for i = 1, 24 do
		local color = vec4(255, 255, 255, 255)
		colorPicker.palette[i] = color

		local paletteButton = UILib.button("", vec3(0.0, 0.0, 10.1), vec2(8.0, 8.0), color, function() colorPicker.paletteButtonPressed(colorPicker, i) end)
		paletteButton.localPos = vec3Copy(paletteButton.position)
		paletteButton.roundCorner = 0.7
		paletteButton.border = 1.0
		paletteButton.borderColorBase = color - 10
		colorPicker.children[#colorPicker.children + 1] = paletteButton

		colorPicker.paletteButtons[i] = paletteButton
	end

	UILib.setCollectSpritePool(true)

	-- color picker methods

	colorPicker.endPicking = function()
		UILib.setButtonPressed(colorPicker.pickerButton, false)
	end

	colorPicker.paletteButtonPressed = function(colorPicker, paletteId)
		if colorPicker.selectedPaletteId ~= paletteId then
			colorPicker.selectedPaletteId = paletteId

			colorPicker:setColor(colorPicker.palette[colorPicker.selectedPaletteId])
		else
			colorPicker.selectedPaletteId = -1
		end

		if colorPicker.paletteCanBeChanged == false then
			colorPicker.selectedPaletteId = -1
		end
	end

	colorPicker.paletteLockButtonPressed = function(colorPicker)
		colorPicker.paletteCanBeChanged = not colorPicker.paletteCanBeChanged
		colorPicker.selectedPaletteId = -1
	end

	colorPicker.rTextCanged = function(colorPicker)
		local val = tonumber(colorPicker.textLineR.text)
		if val == nil then
			val = 0
		end

		colorPicker.colorR.value = round(clamp(val, 0.0, 255.0))
	end

	colorPicker.gTextCanged = function(colorPicker)
		local val = tonumber(colorPicker.textLineG.text)
		if val == nil then
			val = 0
		end

		colorPicker.colorG.value = round(clamp(val, 0.0, 255.0))
	end

	colorPicker.bTextCanged = function(colorPicker)
		local val = tonumber(colorPicker.textLineB.text)
		if val == nil then
			val = 0
		end

		colorPicker.colorB.value = round(clamp(val, 0.0, 255.0))
	end

	colorPicker.aTextCanged = function(colorPicker)
		local val = tonumber(colorPicker.textLineA.text)
		if val == nil then
			val = 0
		end

		colorPicker.colorA.value = round(clamp(val, 0.0, 255.0))
	end

	colorPicker.setPalette = function(colorPicker, colors)
		colorPicker.palette = {}
		for i =1, #colors do
			colorPicker.palette[i] = vec4(colors[i])
		end
	end
	
	colorPicker.setVisible = function(colorPicker, state)
		if colorPicker.visible == state then
			return
		end

		colorPicker.visible = state

		colorPicker.readyToClose = nil

		if state == true then
			for i = 1, 24 do
				local color = colorPicker.palette[i]
				local paletteButton = colorPicker.paletteButtons[i]
				paletteButton.color = vec4(color)
				paletteButton.colorBase = vec4(color)
				paletteButton.colorHover = color + 50
				paletteButton.borderColor = color - 10
				paletteButton.borderColorBase = color - 10
				
				paletteButton.changed = true
			end

			colorPicker:update(colorPicker, false, false, false)

			colorPicker.skipFirstClick = true
		else
			colorPicker.updateColorCallback = nil
		end

		UILib.setButtonPressed(colorPicker.pickerButton, false)
	end

	colorPicker.setColor = function(colorPicker, color)
		colorPicker.color = vec4Copy(color)

		colorPicker.color.x = clamp(colorPicker.color.x, 0, 255)
		colorPicker.color.y = clamp(colorPicker.color.y, 0, 255)
		colorPicker.color.z = clamp(colorPicker.color.z, 0, 255)
		colorPicker.color.w = clamp(colorPicker.color.w, 0, 255)

		colorPicker.colorR.value = round(colorPicker.color.x)
		colorPicker.colorG.value = round(colorPicker.color.y)
		colorPicker.colorB.value = round(colorPicker.color.z)
		colorPicker.colorA.value = round(colorPicker.color.w)
	end
	
	colorPicker.update = function(colorPicker, mouseInSprite, mousePressed, mouseDown)
		local mousePos = getMousePosition()

		colorPicker.hovered = false

		if getLastKeyPressed() == KEY_ESCAPE then
			colorPicker:setVisible(false)

			return
		end

		local mouseReleased = getMouseButtonReleased(MOUSE_BUTTON_LEFT)
		if mouseReleased == true and mouseInSprite == false and colorPicker.readyToClose == true then
			colorPicker:setVisible(false)
		end

		if mouseInSprite == true then
			colorPicker.hovered = true
		end

		colorPicker.skipFirstClick = false

		if mouseReleased == true and colorPicker.readyToClose == nil then
			colorPicker.readyToClose = true
		end

		colorPicker.panel.changed = true
		UILib.baseSpriteWidgetUpdate(colorPicker.panel, mouseInSprite, mousePressed, mouseDown)

		colorPicker.rColorA.x = clamp(colorPicker.color.x - 255, 0, 255)
		colorPicker.rColorA.y = colorPicker.color.y
		colorPicker.rColorA.z = colorPicker.color.z

		colorPicker.rColorB.x = clamp(colorPicker.color.x + 255, 0, 255)
		colorPicker.rColorB.y = colorPicker.color.y
		colorPicker.rColorB.z = colorPicker.color.z

		colorPicker.gColorA.x = colorPicker.color.x
		colorPicker.gColorA.y = clamp(colorPicker.color.y - 255, 0, 255)
		colorPicker.gColorA.z = colorPicker.color.z

		colorPicker.gColorB.x = colorPicker.color.x
		colorPicker.gColorB.y = clamp(colorPicker.color.y + 255, 0, 255)
		colorPicker.gColorB.z = colorPicker.color.z

		colorPicker.bColorA.x = colorPicker.color.x
		colorPicker.bColorA.y = colorPicker.color.y
		colorPicker.bColorA.z = clamp(colorPicker.color.z - 255, 0, 255)

		colorPicker.bColorB.x = colorPicker.color.x
		colorPicker.bColorB.y = colorPicker.color.y
		colorPicker.bColorB.z = clamp(colorPicker.color.z + 255, 0, 255)

		colorPicker.aColorA.x = colorPicker.panel.color.x
		colorPicker.aColorA.y = colorPicker.panel.color.y
		colorPicker.aColorA.z = colorPicker.panel.color.z

		colorPicker.aColorB.x = 255.0
		colorPicker.aColorB.y = 255.0
		colorPicker.aColorB.z = 255.0

		for i = 1, 32 do
			local t = (1.0 / 32.0) * (i - 1)

			local colorR = colorPicker.colorR[i]
			colorR.spriteData.position.x = colorPicker.panel.position.x + colorR.spriteData.localPos.x
			colorR.spriteData.position.y = colorPicker.panel.position.y + colorR.spriteData.localPos.y

			colorR.spriteData.color = vec4Lerp(colorPicker.rColorA, colorPicker.rColorB, t)
			setSprite(colorR.sprite, colorR.spriteData)
			drawSprite2D(colorR.sprite)

			local colorG = colorPicker.colorG[i]
			colorG.spriteData.position.x = colorPicker.panel.position.x + colorG.spriteData.localPos.x
			colorG.spriteData.position.y = colorPicker.panel.position.y + colorG.spriteData.localPos.y

			colorG.spriteData.color = vec4Lerp(colorPicker.gColorA, colorPicker.gColorB, t)
			setSprite(colorG.sprite, colorG.spriteData)
			drawSprite2D(colorG.sprite)

			local colorB = colorPicker.colorB[i]
			colorB.spriteData.position.x = colorPicker.panel.position.x + colorB.spriteData.localPos.x
			colorB.spriteData.position.y = colorPicker.panel.position.y + colorB.spriteData.localPos.y

			colorB.spriteData.color = vec4Lerp(colorPicker.bColorA, colorPicker.bColorB, t)
			setSprite(colorB.sprite, colorB.spriteData)
			drawSprite2D(colorB.sprite)

			local colorA = colorPicker.colorA[i]
			colorA.spriteData.position.x = colorPicker.panel.position.x + colorA.spriteData.localPos.x
			colorA.spriteData.position.y = colorPicker.panel.position.y + colorA.spriteData.localPos.y

			colorA.spriteData.color = vec4Lerp(colorPicker.aColorA, colorPicker.aColorB, t)
			setSprite(colorA.sprite, colorA.spriteData)
			drawSprite2D(colorA.sprite)
		end

		colorPicker.colorR.position.x = colorPicker.panel.position.x + colorPicker.colorR[1].spriteData.localPos.x
		colorPicker.colorR.position.y = colorPicker.panel.position.y + colorPicker.colorR[1].spriteData.localPos.y

		colorPicker.colorG.position.x = colorPicker.panel.position.x + colorPicker.colorG[1].spriteData.localPos.x
		colorPicker.colorG.position.y = colorPicker.panel.position.y + colorPicker.colorG[1].spriteData.localPos.y

		colorPicker.colorB.position.x = colorPicker.panel.position.x + colorPicker.colorB[1].spriteData.localPos.x
		colorPicker.colorB.position.y = colorPicker.panel.position.y + colorPicker.colorB[1].spriteData.localPos.y

		colorPicker.colorA.position.x = colorPicker.panel.position.x + colorPicker.colorA[1].spriteData.localPos.x
		colorPicker.colorA.position.y = colorPicker.panel.position.y + colorPicker.colorA[1].spriteData.localPos.y

		colorPicker.colorR.handle.forceHovered = false
		colorPicker.colorG.handle.forceHovered = false
		colorPicker.colorB.handle.forceHovered = false
		colorPicker.colorA.handle.forceHovered = false

		if mouseInSprite == true then
			if intersectRect(mousePos, colorPicker.colorR.position, colorPicker.colorR.size) then
				if mousePressed == true then
					colorPicker.draggingRGBA = 1
				end

				colorPicker.colorR.handle.forceHovered = true
				
			elseif intersectRect(mousePos, colorPicker.colorG.position, colorPicker.colorG.size) then
				if mousePressed == true then
					colorPicker.draggingRGBA = 2
				end

				colorPicker.colorG.handle.forceHovered = true

			elseif intersectRect(mousePos, colorPicker.colorB.position, colorPicker.colorB.size) then
				if mousePressed == true then
					colorPicker.draggingRGBA = 3
				end

				colorPicker.colorB.handle.forceHovered = true

			elseif intersectRect(mousePos, colorPicker.colorA.position, colorPicker.colorA.size) then
				if mousePressed == true then
					colorPicker.draggingRGBA = 4
				end

				colorPicker.colorA.handle.forceHovered = true

			end
		end

		if colorPicker.draggingRGBA ~= -1 and getMouseButtonDown(MOUSE_BUTTON_LEFT) == true then
			local mousePos = getMousePosition()

			if colorPicker.draggingRGBA == 1 then
				colorPicker.colorR.value = round(clamp(((mousePos.x - colorPicker.colorR.position.x) / 32.0), 0.0, 1.0) * 255.0)
			
			elseif colorPicker.draggingRGBA == 2 then
				colorPicker.colorG.value = round(clamp(((mousePos.x - colorPicker.colorG.position.x) / 32.0), 0.0, 1.0) * 255.0)
			
			elseif colorPicker.draggingRGBA == 3 then
				colorPicker.colorB.value = round(clamp(((mousePos.x - colorPicker.colorB.position.x) / 32.0), 0.0, 1.0) * 255.0)
			
			elseif colorPicker.draggingRGBA == 4 then
				colorPicker.colorA.value = round(clamp(((mousePos.x - colorPicker.colorB.position.x) / 32.0), 0.0, 1.0) * 255.0)
			end
		else
			colorPicker.draggingRGBA = -1
		end

		if colorPicker.textLineR.editing == false then
			colorPicker.textLineR.text = tostring(colorPicker.colorR.value)
		end

		if colorPicker.textLineG.editing == false then
			colorPicker.textLineG.text = tostring(colorPicker.colorG.value)
		end

		if colorPicker.textLineB.editing == false then
			colorPicker.textLineB.text = tostring(colorPicker.colorB.value)
		end

		if colorPicker.textLineA.editing == false then
			colorPicker.textLineA.text = tostring(colorPicker.colorA.value)
		end

		colorPicker.color.x = colorPicker.colorR.value
		colorPicker.color.y = colorPicker.colorG.value
		colorPicker.color.z = colorPicker.colorB.value
		colorPicker.color.w = colorPicker.colorA.value

		if colorPicker.selectedPaletteId ~= -1 then
			local oldPaletteColor = colorPicker.palette[colorPicker.selectedPaletteId]
			colorPicker.palette[colorPicker.selectedPaletteId] = vec4Copy(colorPicker.color)

			colorPicker.paletteButtons[colorPicker.selectedPaletteId].colorBase = colorPicker.palette[colorPicker.selectedPaletteId]
			colorPicker.paletteButtons[colorPicker.selectedPaletteId].borderColorBase = colorPicker.color - 10
			colorPicker.paletteButtons[colorPicker.selectedPaletteId].colorHover = colorPicker.color + 50

			if vec4Length(oldPaletteColor - colorPicker.color) > 0.0 then
				colorPicker.paletteChanged = true
			end

			if mouseDown == false then
				if colorPicker.paletteChanged == true then
					colorPicker.paletteChanged = false

					if colorPicker.paletteChangeCallback ~= nil then
						colorPicker.paletteChangeCallback()
					end
				end	
			end
		end

		colorPicker.currentColorSpriteData.color = colorPicker.color

		colorPicker.colorR.handle.position.x = (colorPicker.panel.position.x + colorPicker.colorR[1].spriteData.localPos.x) + (32.0 * (colorPicker.colorR.value / 255.0))
		colorPicker.colorR.handle.position.y = colorPicker.panel.position.y + colorPicker.colorR[1].spriteData.localPos.y - 1.0

		colorPicker.colorG.handle.position.x = colorPicker.panel.position.x + colorPicker.colorG[1].spriteData.localPos.x + (32.0 * (colorPicker.colorG.value / 255.0))
		colorPicker.colorG.handle.position.y = colorPicker.panel.position.y + colorPicker.colorG[1].spriteData.localPos.y - 1.0

		colorPicker.colorB.handle.position.x = colorPicker.panel.position.x + colorPicker.colorB[1].spriteData.localPos.x + (32.0 * (colorPicker.colorB.value / 255.0))
		colorPicker.colorB.handle.position.y = colorPicker.panel.position.y + colorPicker.colorB[1].spriteData.localPos.y - 1.0

		colorPicker.colorA.handle.position.x = colorPicker.panel.position.x + colorPicker.colorA[1].spriteData.localPos.x + (32.0 * (colorPicker.colorA.value / 255.0))
		colorPicker.colorA.handle.position.y = colorPicker.panel.position.y + colorPicker.colorA[1].spriteData.localPos.y - 1.0

		colorPicker.textLineR.position.x = colorPicker.panel.position.x + colorPicker.textLineR.localPos.x
		colorPicker.textLineR.position.y = colorPicker.panel.position.y + colorPicker.textLineR.localPos.y

		colorPicker.textLineG.position.x = colorPicker.panel.position.x + colorPicker.textLineG.localPos.x
		colorPicker.textLineG.position.y = colorPicker.panel.position.y + colorPicker.textLineG.localPos.y

		colorPicker.textLineB.position.x = colorPicker.panel.position.x + colorPicker.textLineB.localPos.x
		colorPicker.textLineB.position.y = colorPicker.panel.position.y + colorPicker.textLineB.localPos.y

		colorPicker.textLineA.position.x = colorPicker.panel.position.x + colorPicker.textLineA.localPos.x
		colorPicker.textLineA.position.y = colorPicker.panel.position.y + colorPicker.textLineA.localPos.y

		colorPicker.currentColorSpriteData.position.x = colorPicker.panel.position.x + colorPicker.currentColorSpriteData.localPos.x
		colorPicker.currentColorSpriteData.position.y = colorPicker.panel.position.y + colorPicker.currentColorSpriteData.localPos.y

		setSprite(colorPicker.currentColorSpriteData.sprite, colorPicker.currentColorSpriteData)
		drawSprite2D(colorPicker.currentColorSpriteData.sprite)

		colorPicker.copyButton.position.x = colorPicker.panel.position.x + colorPicker.copyButton.localPos.x
		colorPicker.copyButton.position.y = colorPicker.panel.position.y + colorPicker.copyButton.localPos.y
		colorPicker.copyButton.iconSpriteData.position.x = colorPicker.copyButton.position.x
		colorPicker.copyButton.iconSpriteData.position.y = colorPicker.copyButton.position.y

		colorPicker.pasteButton.position.x = colorPicker.panel.position.x + colorPicker.pasteButton.localPos.x
		colorPicker.pasteButton.position.y = colorPicker.panel.position.y + colorPicker.pasteButton.localPos.y
		colorPicker.pasteButton.iconSpriteData.position.x = colorPicker.pasteButton.position.x
		colorPicker.pasteButton.iconSpriteData.position.y = colorPicker.pasteButton.position.y

		colorPicker.pickerButton.position.x = colorPicker.panel.position.x + colorPicker.pickerButton.localPos.x
		colorPicker.pickerButton.position.y = colorPicker.panel.position.y + colorPicker.pickerButton.localPos.y
		colorPicker.pickerButton.iconSpriteData.position.x = colorPicker.pickerButton.position.x
		colorPicker.pickerButton.iconSpriteData.position.y = colorPicker.pickerButton.position.y

		colorPicker.paletteLockButton.position.x = colorPicker.panel.position.x + colorPicker.paletteLockButton.localPos.x
		colorPicker.paletteLockButton.position.y = colorPicker.panel.position.y + colorPicker.paletteLockButton.localPos.y

		if colorPicker.paletteCanBeChanged == true then
			colorPicker.paletteLockButton.colorBase = Config.paletteNames.red
			colorPicker.paletteLockButton.colorHover = Config.paletteNames.red + 50.0
		else
			colorPicker.paletteLockButton.colorBase = colorPicker.panel.color
			colorPicker.paletteLockButton.colorHover = colorPicker.panel.color + 50.0
		end

		for x = 1, 6 do
			for y = 1, 4 do
				local it = ((y - 1) * 6) + x

				local paletteButton = colorPicker.paletteButtons[it]

				paletteButton.position.x = colorPicker.panel.position.x + (4.0 + ((8.0 + 2.0) * (x - 1)))
				paletteButton.position.y = colorPicker.panel.position.y + ((4.0 * 15.0) + ((8.0 + 2.0) * y)) + 6.0

				if colorPicker.selectedPaletteId == it then
					paletteButton.borderColorBase = Config.paletteNames.white
				else
					paletteButton.borderColorBase = paletteButton.colorBase - 10
				end

				setSprite(paletteButton.sprite, paletteButton)
				drawSprite2D(paletteButton.sprite)
			end
		end

		if colorPicker.updateColorCallback ~= nil then
			colorPicker.updateColorCallback()
		end

		for i = 1, #colorPicker.children do
			local child = colorPicker.children[i]

			child.changed = true
			UILib.updateUISprite(child, mousePos, mousePressed, mouseDown)
		end
	end

	UILib.spriteDataPool[#UILib.spriteDataPool + 1] = colorPicker

	return colorPicker
end

function UILib.textLine(pos, size, callback)
	local spriteData = UILib.newSpriteWidget()

	spriteData.position = pos
	spriteData.size = size
	spriteData.defaultWidth = size.x
	spriteData.color = Config.uiColors.textField
	spriteData.text = ""
	spriteData.textColor = Config.uiColors.textFieldText
	spriteData.roundCorner = 0.0
	spriteData.visible = true
	spriteData.hoverEnabled = true
	spriteData.callback = callback

	spriteData.colorBase = Config.uiColors.textField
	spriteData.colorHover = Config.uiColors.textField - 50
	
	spriteData.text = ""

	spriteData.editing = false
	spriteData.editingEnabled = true

	spriteData.cursorSpriteData = {}
	spriteData.cursorSpriteData.sprite = allocSprite()
	spriteData.cursorSpriteData.position = vec3(0.0, 0.0, 0.0)
	spriteData.cursorSpriteData.size = vec2(1.0, 6.0)
	spriteData.cursorSpriteData.color = Config.uiColors.textFieldCursor

	spriteData.selectSpriteData = {}
	spriteData.selectSpriteData.sprite = allocSprite()
	spriteData.selectSpriteData.position = vec3(0.0, 0.0, 0.0)
	spriteData.selectSpriteData.size = vec2(1.0, 6.0)
	spriteData.selectSpriteData.color = Config.uiColors.textFieldSelected * 0.5

	spriteData.cursorCharPos = 0
	spriteData.selectedChars = {}
	spriteData.selectedCharStart = 0
	spriteData.selectedCharEnd = 0
	spriteData.selectedCharClick = 0
	spriteData.doubleclickTime = 0.0

	spriteData.setFocusWidth = function(spriteData, width)
		spriteData.focusWidth = width
	end

	spriteData.setFocusColor = function(spriteData, color)
		spriteData.focusColor = vec4(color)
		spriteData.defaultColor = vec4(spriteData.colorBase)
	end

	spriteData.setEditing = function(spriteData, state)
		if spriteData.editingEnabled == false then
			state = false
		end

		spriteData:onFocus(state)
		
		if spriteData.startEditCallback ~= nil then
			spriteData.startEditCallback()
		end
	end

	spriteData.onFocus = function(spriteData, state)
		if spriteData.editingEnabled == false then
			return
		end

		if spriteData.hasFocus == state then
			return
		end
		
		spriteData.hasFocus = state

		UILib.setFocusedSpriteWidget(spriteData, state)

		if state == false and spriteData.editing == true then
			spriteData.cursorCharPos = 0
			spriteData.selectedCharStart = 0
			spriteData.selectedCharEnd = 0

			if spriteData.callback ~= nil then
				spriteData.callback()
			end
		end

		spriteData.editing = state

		if spriteData.editing == true then
			spriteData.defaultDepth = spriteData.position.z
			spriteData.position.z = UILib.uiDepth_overViews + 1.0

			if spriteData.focusWidth ~= nil then
				spriteData.size.x = spriteData.focusWidth
				spriteData.changed = true
			end

			if spriteData.focusColor ~= nil then
				spriteData.color = vec4(spriteData.focusColor)
				spriteData.colorBase = vec4(spriteData.focusColor)
				spriteData.colorHover = spriteData.focusColor + 50

				spriteData.changed = true
			end
		else
			spriteData.size.x = spriteData.defaultWidth
			spriteData.position.z = spriteData.defaultDepth

			if spriteData.defaultColor ~= nil then
				spriteData.color = vec4(spriteData.defaultColor)
				spriteData.colorBase = vec4(spriteData.defaultColor)
				spriteData.colorHover = spriteData.defaultColor + 50
			end

			spriteData.changed = true

			if spriteData.focusChangeCallback ~= nil then
				spriteData.focusChangeCallback(spriteData.hasFocus)
			end
		end
	end

	spriteData.update = function(spriteData, mouseInSprite, mousePressed, mouseDown)
		if spriteData.editingEnabled == false then
			mousePressed = false
			mouseDown = false
		end

		if spriteData.hoverEnabled == true then
			spriteData.hovered = mouseInSprite

			local hovered = spriteData.hovered
			if spriteData.forceHovered == true then
				hovered = true
			end

			if hovered == true then
				spriteData.color = spriteData.colorHover
			else
				spriteData.color = spriteData.colorBase
			end
		end

		setSprite(spriteData.sprite, spriteData)
		drawSprite2D(spriteData.sprite)

		local mousePos = getMousePosition()

		if mouseInSprite == true and mousePressed == true and spriteData.editing == true then
			spriteData.draggingSelection = true

			spriteData.cursorCharPos = round((mousePos.x - spriteData.position.x) / 8.0)
			spriteData.selectedCharStart = spriteData.cursorCharPos
			spriteData.selectedCharEnd = spriteData.cursorCharPos

			spriteData.selectedCharClick = spriteData.cursorCharPos
		end

		if spriteData.draggingSelection == true then
			local charPos = round((mousePos.x - spriteData.position.x) / 8.0)
			
			if charPos > spriteData.selectedCharClick then
				spriteData.selectedCharStart = spriteData.selectedCharClick
				spriteData.selectedCharEnd = charPos
				spriteData.cursorCharPos = spriteData.selectedCharStart
			else
				spriteData.selectedCharStart = charPos
				spriteData.selectedCharEnd = spriteData.selectedCharClick
				spriteData.cursorCharPos = spriteData.selectedCharStart
			end
		end

		if spriteData.selectedCharStart < 0 then
			spriteData.selectedCharStart = 0
		end

		if spriteData.selectedCharEnd > #spriteData.text then
			spriteData.selectedCharEnd = #spriteData.text
		end

		if getMouseButtonDown(MOUSE_BUTTON_LEFT) == false then
			spriteData.draggingSelection = false
		end

		if mouseInSprite == true and mousePressed == true and spriteData.editing == false then
			if spriteData.startEditCallback ~= nil then
				spriteData.startEditCallback()
			end

			spriteData.editing = true
			spriteData.selectedCharStart = 0
			spriteData.selectedCharEnd = #spriteData.text

			UILib.setFocusedSpriteWidget(spriteData, true)
		end

		if spriteData.editing == true then
			if spriteData.cursorCharPos < 0 then
				spriteData.cursorCharPos = 0
			elseif spriteData.cursorCharPos > #spriteData.text then
				spriteData.cursorCharPos = #spriteData.text
			end

			if mousePressed == true and mouseInSprite == true then
				local time = getTime()

				local deltaTime = time - spriteData.doubleclickTime
				
				if deltaTime < 0.3 then
					spriteData.cursorCharPos = 0
					spriteData.selectedCharStart = 0
					spriteData.selectedCharEnd = #spriteData.text

					spriteData.draggingSelection = false
				end

				spriteData.doubleclickTime = time
			end

			local numSelChars = spriteData.selectedCharEnd - spriteData.selectedCharStart

			if getLastKeyPressed() == KEY_LEFT then
				spriteData.cursorCharPos = spriteData.cursorCharPos - 1

				spriteData.selectedCharStart = 0
				spriteData.selectedCharEnd = 0

			elseif getLastKeyPressed() == KEY_RIGHT then
				spriteData.cursorCharPos = spriteData.cursorCharPos + 1

				spriteData.selectedCharStart = 0
				spriteData.selectedCharEnd = 0

			elseif getLastKeyPressed() == KEY_BACKSPACE then
				if spriteData.selectedCharEnd - spriteData.selectedCharStart == 0 then
					spriteData.selectedCharStart = 0
					spriteData.selectedCharEnd = 0
					spriteData.cursorCharPos = spriteData.cursorCharPos - 1
					
					if spriteData.cursorCharPos < 0 then
						spriteData.cursorCharPos = 0
					elseif spriteData.cursorCharPos + 1 > #spriteData.text then
						spriteData.text = spriteData.text:sub(1, spriteData.cursorCharPos)
					else
						local t = spriteData.text
						local t1 = t:sub(1, spriteData.cursorCharPos)
						local t2 = t:sub(spriteData.cursorCharPos + 2, #t)
						
						spriteData.text = t1 .. t2
					end
				else
					local prefix = spriteData.text:sub(1, spriteData.selectedCharStart)
					local suffix = spriteData.text:sub(spriteData.selectedCharEnd + 1, #spriteData.text)
					
					spriteData.text = prefix .. suffix

					spriteData.cursorCharPos = #prefix

					spriteData.selectedCharStart = 0
					spriteData.selectedCharEnd = 0
				end

			elseif getLastKeyPressed() == KEY_ENTER then
				UILib.setFocusedSpriteWidget(spriteData, false)

				if spriteData.editing == true and spriteData.callback ~= nil then
					spriteData.callback()
				end

				spriteData.editing = false

			elseif getLastKeyPressed() == KEY_RETURN then
				UILib.setFocusedSpriteWidget(spriteData, false)

				if spriteData.editing == true and spriteData.callback ~= nil then
					spriteData.callback()
				end

				spriteData.editing = false

			elseif getLastKeyPressed() == KEY_ESCAPE then
				UILib.setFocusedSpriteWidget(spriteData, false)

				if spriteData.editing == true and spriteData.callback ~= nil then
					spriteData.callback()
				end

				spriteData.editing = false

			else -- begin editing
				local textInput = getTextInput()
				
				if textInput ~= "" then
					if (spriteData.selectedCharEnd - spriteData.selectedCharStart) > 0 then
						local replStr = ""
						for i = spriteData.selectedCharStart + 1, spriteData.selectedCharEnd do
							replStr = replStr .. spriteData.text:sub(i, i)
						end

						spriteData.text = replaceStr(spriteData.text, replStr, textInput)

						spriteData.selectedCharStart = 0
						spriteData.selectedCharEnd = 0
						spriteData.cursorCharPos = #spriteData.text
					else
						if spriteData.cursorCharPos < #spriteData.text then
							local prefix = spriteData.text:sub(1, spriteData.cursorCharPos)
							local suffix = spriteData.text:sub(spriteData.cursorCharPos + 1, #spriteData.text)
							
							spriteData.text = prefix .. textInput .. suffix

							spriteData.cursorCharPos = #(prefix .. textInput)
						else
							spriteData.text = spriteData.text .. textInput
							
							spriteData.cursorCharPos = #spriteData.text
						end
					end
				end
			end

			-- if (8.0 * #spriteData.text) > spriteData.size.x then
			-- 	local trimmedStr = ""
			-- 	local oldStr = spriteData.text

			-- 	local numChars = floor(spriteData.size.x / 8.0)

			-- 	spriteData.text = spriteData.text:sub(1, numChars)
			-- end

			spriteData.cursorSpriteData.position.x = spriteData.position.x + (8.0 * spriteData.cursorCharPos)
			spriteData.cursorSpriteData.position.y = spriteData.position.y + 1.0
			spriteData.cursorSpriteData.position.z = spriteData.position.z + 0.3

			spriteData.selectSpriteData.position.x = spriteData.position.x + (8.0 * spriteData.selectedCharStart)
			spriteData.selectSpriteData.position.y = spriteData.position.y
			spriteData.selectSpriteData.position.z = spriteData.position.z + 0.2

			spriteData.selectSpriteData.color.w = 100.0

			spriteData.selectSpriteData.size.x = 8.0 * numSelChars
			spriteData.selectSpriteData.size.y = 8.0

			if numSelChars == 0 then
				setSprite(spriteData.cursorSpriteData.sprite, spriteData.cursorSpriteData)
				drawSprite2D(spriteData.cursorSpriteData.sprite)
			else
				setSprite(spriteData.selectSpriteData.sprite, spriteData.selectSpriteData)
				drawSprite2D(spriteData.selectSpriteData.sprite)
			end
		end
	end
		
	return spriteData
end

function UILib.slider(labelText, pos, labelSize, fieldSize, opts)
	opts = opts or {}
	local sensitivity = opts.sensitivity or 0.1
	local defaultValue = opts.defaultValue or 0.0
	local format = opts.format or "%.2f"
	local gap = opts.gap or 0.0
	local labelColor = opts.labelColor or Config.uiColors.panel
	local labelTextColor = opts.labelTextColor or Config.uiColors.text
	local fieldColor = opts.fieldColor or Config.uiColors.textField
	local fieldColorHover = opts.fieldColorHover or (fieldColor - 50)
	local fieldTextColor = opts.fieldTextColor or Config.uiColors.textFieldText
	local fieldTextColorActive = opts.fieldTextColorActive or Config.paletteNames.white

	local slider = {}
	slider.position = vec3(pos)
	slider.visible = true
	slider.value = defaultValue
	slider.sensitivity = sensitivity
	slider.format = format
	slider.onChange = opts.onChange
	slider.onRelease = opts.onRelease
	slider.onTextEdit = opts.onTextEdit
	slider.children = {}
	slider._dragging = false
	slider._dragArmed = false

	UILib.setCollectSpritePool(false)

	local hasLabel = labelText ~= "" or labelSize.x > 0
	if hasLabel then
		slider.label = UILib.panel(labelText, vec3(pos), labelSize, labelColor, labelTextColor)
		slider.label.textOffset = vec2(2.0, 1.0)
		slider.children[#slider.children + 1] = slider.label
	end

	local fieldOffset = opts.fieldOffset
	if fieldOffset == nil then
		if hasLabel then
			fieldOffset = vec2(labelSize.x + gap, 0.0)
		else
			fieldOffset = vec2(0.0, 0.0)
		end
	end
	local fieldPos = vec3(pos.x + fieldOffset.x, pos.y + fieldOffset.y, pos.z)

	slider.field = UILib.textLine(fieldPos, fieldSize, function()
		if UILib._draggingSlider == slider then return end
		if getMouseButtonDown(MOUSE_BUTTON_LEFT) then return end
		local val = tonumber(slider.field.text)
		if val ~= nil then
			slider.value = val
		end
		slider.field.text = string.format(slider.format, slider.value)
		slider.field.changed = true
		if slider.onTextEdit ~= nil then
			slider.onTextEdit(slider, slider.value)
		end
	end)
	slider.field.text = string.format(format, defaultValue)
	slider.field.colorBase = fieldColor
	slider.field.color = fieldColor
	slider.field.colorHover = fieldColorHover
	slider.field.textColor = fieldTextColor
	slider.field.defaultDepth = slider.field.position.z
	slider.field._textColorBase = fieldTextColor
	slider.field._textColorActive = fieldTextColorActive
	slider.children[#slider.children + 1] = slider.field

	local totalW = fieldOffset.x + fieldSize.x
	local totalH = math.max(fieldOffset.y + fieldSize.y, hasLabel and labelSize.y or 0.0)
	slider.size = vec2(totalW, totalH)

	slider.setValue = function(slider, val)
		slider.value = val
		slider.field.text = string.format(slider.format, val)
		slider.field.changed = true
	end

	slider.update = function(slider, mouseInSprite, mousePressed, mouseDown)
		if slider.visible == false then return end

		local mousePos = getMousePosition()

		-- sampled before the children update, which is what turns editing on when the field is clicked
		local editing = slider.field.editing == true

		for i = 1, #slider.children do
			local child = slider.children[i]
			child.changed = true
			UILib.updateUISprite(child, mousePos, mousePressed, mouseDown)
		end

		if UILib._draggingSlider == nil then
			if slider.field.hovered == true and editing == false then
				slider._cursorSet = true
				UILib.setCursor(UILib.CURSOR_SLIDER)
			elseif slider._cursorSet == true then
				slider._cursorSet = false
				UILib.setCursor(UILib.CURSOR)
			end
		end

		if mouseDown == false then
			slider._dragArmed = false
		end

		if mousePressed == true and slider.field.hovered == true and UILib._draggingSlider == nil then
			UILib._sliderDragStartX = mousePos.x
			UILib._sliderDragStartValue = slider.value

			-- a press on a field that was already being edited selects text instead of scrubbing the value
			slider._dragArmed = editing == false
		end

		local delta = mousePos.x - UILib._sliderDragStartX

		if mouseDown == true then
			if math.abs(delta) > 0.0 and slider._dragging == false and slider.field.hovered == true and slider._dragArmed == true and UILib._draggingSlider == nil then
				slider._dragging = true
				UILib._draggingSlider = slider
				slider.field.hoverEnabled = false
			end
		end

		if UILib._draggingSlider == slider then
			if mouseDown == true then
				local newValue = UILib._sliderDragStartValue + delta * slider.sensitivity
				slider.value = newValue
				slider.field.text = string.format(slider.format, newValue)
				slider.field.textColor = slider.field._textColorActive
				slider.field.changed = true
				UILib.setCursor(UILib.CURSOR_SLIDER)
				if slider.field.hasFocus == true then
					slider.field:setEditing(false)
				end
				if slider.onChange ~= nil then
					slider.onChange(slider, newValue)
				end
			else
				slider.field.hoverEnabled = true
				slider.field.textColor = slider.field._textColorBase
				slider.field.changed = true
				slider._dragging = false
				slider._cursorSet = false
				UILib._draggingSlider = nil
				UILib.setCursor(UILib.CURSOR)
				if slider.onRelease ~= nil then
					slider.onRelease(slider, slider.value)
				end
			end
		end
	end

	slider.sprite = slider.field.sprite
	slider.hovered = false
	slider.changed = true
	slider.color = vec4(0, 0, 0, 0)

	UILib.spriteDataPool[#UILib.spriteDataPool + 1] = slider

	UILib.setCollectSpritePool(true)

	return slider
end

function updateModal(mousePos, mousePressed, mouseDown)
	UILib.updateUISprite(UILib.modalBackground, mousePos, mousePressed, mouseDown)
	UILib.updateUISprite(UILib.modalPanel, mousePos, mousePressed, mouseDown)
	UILib.updateUISprite(UILib.modalOkButton, mousePos, mousePressed, mouseDown)

	if UILib.modalOkButton.hovered == true and mousePressed == true then
		UILib.modalOkButton.callback()
	end

	if UILib.modalCancelButton.visible == true then
		UILib.updateUISprite(UILib.modalCancelButton, mousePos, mousePressed, mouseDown)

		if UILib.modalCancelButton.hovered == true and mousePressed == true then
			UILib.modalCancelButton.callback()
		end
	end

	if UILib.modalChildren ~= nil then
		for i = 1, #UILib.modalChildren do
			local child = UILib.modalChildren[i]
			child.changed = true
			UILib.updateUISprite(child, mousePos, mousePressed, mouseDown)
		end
	end
end

function UILib.closeModal()
	UILib.modalOpen = false
	UILib.modalChildren = nil
end

function modalOkCancelPressed(callback)
	if callback ~= nil then
		callback()
	end

	UILib.closeModal()
end

function UILib.openModal(viewSizeX, viewSizeY, message, okLabel, okCallback, cancelLabel, cancelCallback, children)
	UILib.modalJustOpen = true
	UILib.modalChildren = children

	UILib.modalBackground.size.x = viewSizeX
	UILib.modalBackground.size.y = viewSizeY

	local lines = splitStr(message, "\n")

	local maxChar = 0
	for i = 1, #lines do
		local line = lines[i]

		if #line > maxChar then
			maxChar = #line
		end
	end

	local buttonsWidth = #okLabel
	if cancelLabel ~= nil then
		buttonsWidth = buttonsWidth + 1 + #cancelLabel
	end

	if maxChar < buttonsWidth then
		maxChar = buttonsWidth
	end

	local childrenHeight = 0
	if children ~= nil then
		childrenHeight = #children * 16
	end

	UILib.modalPanel.size.x = 8 * (maxChar + 2)
	UILib.modalPanel.size.y = 8 * (#lines + 5) + childrenHeight

	UILib.modalPanel.position.x = (viewSizeX * 0.5) - (UILib.modalPanel.size.x * 0.5)
	UILib.modalPanel.position.y = (viewSizeY * 0.5) - (UILib.modalPanel.size.y * 0.5)

	UILib.modalPanel.text = message
	UILib.modalPanel.textOffset = vec2(8, 8)

	UILib.modalPanel.changed = true

	if children ~= nil then
		local childY = UILib.modalPanel.position.y + 8 + (#lines * 8) + 8
		for i = 1, #children do
			local child = children[i]
			child.position.x = UILib.modalPanel.position.x + 8
			child.position.y = childY
			child.position.z = UILib.uiDepth_overViews + 0.3
			childY = childY + 16
		end
	end

	UILib.modalOkButton.text = okLabel

	UILib.modalOkButton.size.x = 8 * #UILib.modalOkButton.text
	UILib.modalOkButton.size.y = 8

	UILib.modalOkButton.position.x = UILib.modalPanel.position.x + UILib.modalPanel.size.x - UILib.modalOkButton.size.x - 8
	UILib.modalOkButton.position.y = UILib.modalPanel.position.y + UILib.modalPanel.size.y - UILib.modalOkButton.size.y - 8

	UILib.modalOkButton.callback = function() modalOkCancelPressed(okCallback) end
	UILib.modalOkButton.changed = true

	UILib.modalCancelButton.visible = false

	if cancelLabel then
		UILib.modalCancelButton.text = cancelLabel

		UILib.modalCancelButton.size.x = 8 * #UILib.modalCancelButton.text
		UILib.modalCancelButton.size.y = 8

		UILib.modalCancelButton.position.x = UILib.modalPanel.position.x + UILib.modalPanel.size.x - UILib.modalCancelButton.size.x - 8
		UILib.modalCancelButton.position.y = UILib.modalPanel.position.y + UILib.modalPanel.size.y - UILib.modalCancelButton.size.y - 8

		UILib.modalOkButton.position.x = UILib.modalCancelButton.position.x - UILib.modalOkButton.size.x - 8

		UILib.modalCancelButton.visible = true
		UILib.modalCancelButton.callback = function() modalOkCancelPressed(cancelCallback) end
		UILib.modalCancelButton.changed = true
	end

	UILib.modalOpen = true
end

function updateMenu(mousePos, mousePressed, mouseDown)
	UILib.updateUISprite(UILib.menuPanel, mousePos, mousePressed, mouseDown)

	for i = 1, #UILib.currentMenuTable do
		local menuEntry = UILib.currentMenuTable[i]
		UILib.menuButton.text = menuEntry[1]
		UILib.menuButton.position.x = UILib.menuPanel.position.x
		UILib.menuButton.position.y = UILib.menuPanel.position.y + (8 * (i - 1))

		UILib.menuButton.size.x = UILib.menuPanel.size.x
		UILib.menuButton.size.y = 8

		UILib.menuButton.changed = true
		
		UILib.updateUISprite(UILib.menuButton, mousePos, mousePressed, mouseDown)

		if UILib.menuButton.hovered == true and mousePressed == true then
			UILib.menuButton.callback()
		end
	end
end

function menuItemPressed()
	local mousePos = getMousePosition()
	local menuEntryId = round((mousePos.y - UILib.menuPanel.position.y + 4) / 8)

	local entry = UILib.currentMenuTable[menuEntryId]
	if entry == nil then
		return
	end

	local callback = entry[2]
	if callback ~= nil then
		callback()
	end
end

function UILib.openMenu(parentButton, menuTable)
	if UILib.lastParentMenuButton == parentButton and UILib.menuPanel.visible == true then
		UILib.lastParentMenuButton = nil

		return
	end

	UILib.lastParentMenuButton = parentButton
	UILib.currentMenuTable = menuTable

	local menuWidth = 0
	for i = 1, #UILib.currentMenuTable do
		local menuEntry = UILib.currentMenuTable[i]
		if #menuEntry[1] > menuWidth then
			menuWidth = #menuEntry[1]
		end
	end

	UILib.menuPanel.position.x = parentButton.position.x
	UILib.menuPanel.position.y = parentButton.position.y + parentButton.size.y
	UILib.menuPanel.size.x = 8 * menuWidth
	UILib.menuPanel.size.y = 8 * #menuTable
	UILib.menuPanel.visible = true
	UILib.menuPanel.changed = true

	UILib.menuJustOpen = true
end

function UILib.closeMenu()
	UILib.menuPanel.visible = false
end

function UILib.hierarchyInspector()
	UILib.setCollectSpritePool(false)

	local hierarchyInspector = {}
	hierarchyInspector.children = {}

	hierarchyInspector.visible = true
	hierarchyInspector.position = vec3(10.0, 10.0, 10.0)
	hierarchyInspector.size = vec2(200.0, 300.0)

	hierarchyInspector.entryHeight = 8.0

	hierarchyInspector.panel = UILib.panel("Hierarchy", hierarchyInspector.position, hierarchyInspector.size, Config.uiColors.panelLight)
	hierarchyInspector.children[#hierarchyInspector.children + 1] = hierarchyInspector.panel
	hierarchyInspector.panel.hoverEnabled = false

	hierarchyInspector.hierarchyTable = nil
	hierarchyInspector.nodeButtons = {}
	hierarchyInspector.collapseButtons = {}
	hierarchyInspector.nodeData = {} -- flattened list for rendering

	hierarchyInspector.potentialDragNodeIndex = -1
	hierarchyInspector.mouseClickPos = vec2()
	hierarchyInspector.draggingNodeIndex = -1
	hierarchyInspector.dropTargetIndex = -1
	hierarchyInspector.dropZone = -1 -- 0: top, 1: middle, 2: bottom

	hierarchyInspector.lineSpriteData = {}
	hierarchyInspector.lineSpriteData.sprite = allocSprite()
	hierarchyInspector.lineSpriteData.position = vec3(0, 0, 10.5)
	hierarchyInspector.lineSpriteData.size = vec2(100.0, 2.0)
	hierarchyInspector.lineSpriteData.color = Config.uiColors.accent

	hierarchyInspector.enableHierarchyChange = true
	hierarchyInspector.enableRename = true
	hierarchyInspector.hierarchyChangeCallback = nil
	hierarchyInspector.nodeClickCallback = nil
	hierarchyInspector.nodeRenameCallback = nil

	hierarchyInspector.editingNodeIndex = -1
	hierarchyInspector.originalDisplayName = ""
	hierarchyInspector.renameCancelled = false
	hierarchyInspector.renameJustStarted = false

	hierarchyInspector.renameTextLine = UILib.textLine(vec3(0, 0, 0), vec2(100, 8), nil)
	hierarchyInspector.renameTextLine.focusChangeCallback = function(hasFocus)
		if hasFocus == false then
			hierarchyInspector:finishRename()
		end
	end
	hierarchyInspector.renameTextLine.visible = false
	hierarchyInspector.children[#hierarchyInspector.children + 1] = hierarchyInspector.renameTextLine

	hierarchyInspector.startRename = function(hierarchyInspector, index)
		local data = hierarchyInspector.nodeData[index]
		if not data then return end

		hierarchyInspector.editingNodeIndex = index
		hierarchyInspector.originalDisplayName = data.node.displayName or data.node.name or ""
		hierarchyInspector.renameCancelled = false

		local btn = hierarchyInspector.nodeButtons[index]

		hierarchyInspector.renameTextLine.position.x = btn.position.x
		hierarchyInspector.renameTextLine.position.y = btn.position.y
		hierarchyInspector.renameTextLine.position.z = btn.position.z + 0.2
		hierarchyInspector.renameTextLine.size.x = btn.size.x
		hierarchyInspector.renameTextLine.size.y = btn.size.y
		hierarchyInspector.renameTextLine.text = data.node.displayName or data.node.name or ""
		hierarchyInspector.renameTextLine.visible = true

		btn.visible = false

		hierarchyInspector.renameTextLine.hovered = true
		hierarchyInspector.renameTextLine:setEditing(true)
		hierarchyInspector.renameJustStarted = true
	end

	hierarchyInspector.finishRename = function(hierarchyInspector)
		if hierarchyInspector.editingNodeIndex == -1 then return end

		local data = hierarchyInspector.nodeData[hierarchyInspector.editingNodeIndex]
		hierarchyInspector.renameTextLine.visible = false
		hierarchyInspector.editingNodeIndex = -1

		if hierarchyInspector.renameCancelled then
			data.node.displayName = hierarchyInspector.originalDisplayName
			data.node.name = hierarchyInspector.originalDisplayName
		else
			local newName = hierarchyInspector.renameTextLine.text
			if newName == nil or newName == "" then
				newName = hierarchyInspector.originalDisplayName
			end
			data.node.displayName = newName
			data.node.name = newName

			if hierarchyInspector.nodeRenameCallback then
				hierarchyInspector.nodeRenameCallback(data.node, newName)
			end
		end

		hierarchyInspector:refresh()
	end

	hierarchyInspector.setVisible = function(hierarchyInspector, state)
		hierarchyInspector.visible = state
	end
	
	local function isDescendantOf(node, potentialAncestor)
		if not potentialAncestor.children then return false end
		for _, child in ipairs(potentialAncestor.children) do
			if child == node then return true end
			if isDescendantOf(node, child) then return true end
		end
		return false
	end

	local function flattenHierarchy(nodeTable, depth, resultList, parentList, indexInParent)
		for i = 1, #nodeTable do
			local node = nodeTable[i]
			resultList[#resultList + 1] = {
				node = node,
				depth = depth,
				parentList = nodeTable,
				indexInParent = i,
				globalIndex = #resultList + 1
			}
			if node.children and node.collapsed ~= true then
				flattenHierarchy(node.children, depth + 1, resultList, node.children, 1)
			end
		end
	end

	hierarchyInspector.refresh = function(hierarchyInspector)
		if hierarchyInspector.editingNodeIndex ~= -1 then
			hierarchyInspector.editingNodeIndex = -1
			hierarchyInspector.renameTextLine.visible = false
			hierarchyInspector.renameTextLine:setEditing(false)
		end

		local enableCollectPool = false
		if UILib.collectSpritePool == true then
			UILib.setCollectSpritePool(false)

			enableCollectPool = true
		end

		hierarchyInspector.nodeData = {}
		if hierarchyInspector.hierarchyTable then
			flattenHierarchy(hierarchyInspector.hierarchyTable, 0, hierarchyInspector.nodeData, hierarchyInspector.hierarchyTable, 1)
		end

		-- Ensure enough buttons
		for i = #hierarchyInspector.nodeButtons + 1, #hierarchyInspector.nodeData do
			local btn = UILib.button("", vec3(0,0,0), vec2(10,10), Config.uiColors.panelLight + 20, function() end)
			btn.textOffset = vec2(4, 2)
			hierarchyInspector.nodeButtons[i] = btn
			hierarchyInspector.children[#hierarchyInspector.children + 1] = btn

			local colBtn = UILib.button("-", vec3(0,0,0), vec2(10, 10), Config.uiColors.panelLight + 10, function()
				local data = hierarchyInspector.nodeData[i]
				if data then
					if data.node.collapsed == nil then
						data.node.collapsed = true
					else
						data.node.collapsed = not data.node.collapsed
					end
					hierarchyInspector:refresh()
				end
			end)
			colBtn.textOffset = vec2(1, 2)
			hierarchyInspector.collapseButtons[i] = colBtn
			hierarchyInspector.children[#hierarchyInspector.children + 1] = colBtn

			UILib.setCollectSpritePool(false)
		end

		for i = 1, #hierarchyInspector.nodeButtons do
			hierarchyInspector.nodeButtons[i].visible = false
			hierarchyInspector.collapseButtons[i].visible = false
		end

		local currentY = hierarchyInspector.position.y + 10.0
		for i = 1, #hierarchyInspector.nodeData do
			local data = hierarchyInspector.nodeData[i]
			local btn = hierarchyInspector.nodeButtons[i]
			local colBtn = hierarchyInspector.collapseButtons[i]

			colBtn.visible = true
			if data.node.children and #data.node.children > 0 then
				if data.node.collapsed == true then
					colBtn.text = "+"
				else
					colBtn.text = "-"
				end
			else
				colBtn.text = ""
			end

			colBtn.position.x = hierarchyInspector.position.x + 4.0 + (data.depth * 12.0)
			colBtn.position.y = currentY
			colBtn.position.z = hierarchyInspector.position.z + 0.1
			colBtn.size.x = 10.0
			colBtn.size.y = hierarchyInspector.entryHeight
			colBtn.textOffset.y = 0.0

			btn.visible = true
			btn.text = data.node.displayName or data.node.name or "unnamed"
			if data.node.selected then
				btn.color = Config.uiColors.accent
				btn.colorBase = Config.uiColors.accent
				btn.colorHover = Config.uiColors.accent + 20
				btn.textColor = Config.paletteNames.greyDark
			elseif data.node.hovered then
				btn.color = Config.uiColors.panelLight + 50
				btn.colorBase = Config.uiColors.panelLight + 50
				btn.colorHover = Config.uiColors.panelLight + 70
				btn.textColor = Config.paletteNames.yellow
			else
				btn.color = Config.uiColors.panelLight + 20
				btn.colorBase = Config.uiColors.panelLight + 20
				btn.colorHover = Config.uiColors.panelLight + 70
				if data.node.color then
					btn.textColor = data.node.color
				else
					btn.textColor = vec4(255, 255, 255, 255)
				end
			end
			btn.position.x = colBtn.position.x + 12.0
			btn.position.y = currentY
			btn.position.z = hierarchyInspector.position.z + 0.1
			btn.size.x = hierarchyInspector.size.x - 8.0 - (data.depth * 12.0) - 12.0
			btn.size.y = hierarchyInspector.entryHeight
			btn.textOffset.y = 0.0

			currentY = currentY + hierarchyInspector.entryHeight + 2.0
		end

		if enableCollectPool == true then
			UILib.setCollectSpritePool(true)
		end
	end

	hierarchyInspector.inspect = function(hierarchyInspector, hierarchyTable)
		hierarchyInspector.hierarchyTable = hierarchyTable
		hierarchyInspector:refresh()
	end

	hierarchyInspector.update = function(hierarchyInspector, mouseInSprite, mousePressed, mouseDown)
		if hierarchyInspector.visible == false then return end

		local mousePos = getMousePosition()
		
		hierarchyInspector.panel.position.x = hierarchyInspector.position.x
		hierarchyInspector.panel.position.y = hierarchyInspector.position.y
		hierarchyInspector.panel.size.x = hierarchyInspector.size.x
		hierarchyInspector.panel.size.y = hierarchyInspector.size.y
		hierarchyInspector.panel.changed = true
		UILib.baseSpriteWidgetUpdate(hierarchyInspector.panel, mouseInSprite, mousePressed, mouseDown)

		local isDraggingNow = false

		if hierarchyInspector.draggingNodeIndex ~= -1 then
			if mouseDown == true then
				isDraggingNow = true
				local draggedBtn = hierarchyInspector.nodeButtons[hierarchyInspector.draggingNodeIndex]
				draggedBtn.position.x = mousePos.x - (draggedBtn.size.x * 0.5)
				draggedBtn.position.y = mousePos.y - (draggedBtn.size.y * 0.5)
				draggedBtn.position.z = hierarchyInspector.position.z + 1.0
				draggedBtn.changed = true

				-- Find drop target
				hierarchyInspector.dropTargetIndex = -1
				hierarchyInspector.dropAsChild = false
				
				local bestDistY = 9999.0
				for i = 1, #hierarchyInspector.nodeData do
					if i ~= hierarchyInspector.draggingNodeIndex then
						local targetBtn = hierarchyInspector.nodeButtons[i]
						
						-- Check if mouse is near this button vertically
						local centerY = targetBtn.position.y + (targetBtn.size.y * 0.5)
						local distY = math.abs(mousePos.y - centerY)

						if distY < hierarchyInspector.entryHeight and distY < bestDistY then
							bestDistY = distY
							hierarchyInspector.dropTargetIndex = i
							
							local localY = mousePos.y - targetBtn.position.y
							if localY < targetBtn.size.y * 0.25 then
								hierarchyInspector.dropZone = 0 -- before
							elseif localY > targetBtn.size.y * 0.75 then
								hierarchyInspector.dropZone = 2 -- after
							else
								hierarchyInspector.dropZone = 1 -- inside
							end
						end
					end
				end
			else
				-- Mouse released, apply drop
				if hierarchyInspector.dropTargetIndex ~= -1 then
					local draggedData = hierarchyInspector.nodeData[hierarchyInspector.draggingNodeIndex]
					local targetData = hierarchyInspector.nodeData[hierarchyInspector.dropTargetIndex]

					if not isDescendantOf(targetData.node, draggedData.node) then
						-- Remove from old parent
						table.remove(draggedData.parentList, draggedData.indexInParent)

						if hierarchyInspector.dropZone == 1 then
							-- Add as child
							if not targetData.node.children then
								targetData.node.children = {}
							end
							table.insert(targetData.node.children, draggedData.node)
						else
							-- Insert before (0) or after (2) target
							local insertIdx = targetData.indexInParent
							if hierarchyInspector.dropZone == 2 then
								insertIdx = targetData.indexInParent + 1
							end

							-- Adjust index if we removed an item before this insertion point in the same list
							if targetData.parentList == draggedData.parentList and draggedData.indexInParent < insertIdx then
								insertIdx = insertIdx - 1
							end

							if insertIdx < 1 then insertIdx = 1 end

							table.insert(targetData.parentList, insertIdx, draggedData.node)
						end
					end
				else
					-- Check if dropped below the very last node
					if #hierarchyInspector.nodeData > 0 then
						local lastNodeBtn = hierarchyInspector.nodeButtons[#hierarchyInspector.nodeData]
						if mousePos.y > lastNodeBtn.position.y + lastNodeBtn.size.y then
							local draggedData = hierarchyInspector.nodeData[hierarchyInspector.draggingNodeIndex]
							-- Only reparent if it's not already the very last item in the root list
							if not (draggedData.parentList == hierarchyInspector.hierarchyTable and draggedData.indexInParent == #hierarchyInspector.hierarchyTable) then
								table.remove(draggedData.parentList, draggedData.indexInParent)
								table.insert(hierarchyInspector.hierarchyTable, draggedData.node)
							end
						end
					end
				end

				if hierarchyInspector.hierarchyChangeCallback ~= nil then
					hierarchyInspector.hierarchyChangeCallback()
				end
				
				hierarchyInspector.draggingNodeIndex = -1
				hierarchyInspector.dropTargetIndex = -1
				hierarchyInspector.dropZone = -1
				hierarchyInspector:refresh()
			end
		else
			-- Check for mouse down to start drag
			if mousePressed == true and hierarchyInspector.editingNodeIndex == -1 then
				for i = 1, #hierarchyInspector.nodeData do
					local btn = hierarchyInspector.nodeButtons[i]
					UILib.tmpVec2.x = btn.position.x
					UILib.tmpVec2.y = btn.position.y
					if intersectRect(mousePos, UILib.tmpVec2, btn.size) then
						hierarchyInspector.potentialDragNodeIndex = i
						hierarchyInspector.mouseClickPos.x = mousePos.x
						hierarchyInspector.mouseClickPos.y = mousePos.y
						break
					end
				end
			end
			
			if mouseDown == true and hierarchyInspector.potentialDragNodeIndex ~= -1 and hierarchyInspector.enableHierarchyChange then
				local dx = mousePos.x - hierarchyInspector.mouseClickPos.x
				local dy = mousePos.y - hierarchyInspector.mouseClickPos.y
				local distSq = (dx * dx) + (dy * dy)

				if distSq > 16.0 then -- 4 pixels threshold
					hierarchyInspector.draggingNodeIndex = hierarchyInspector.potentialDragNodeIndex
					hierarchyInspector.potentialDragNodeIndex = -1
				end
			end
			
			if mouseDown == false then
				if hierarchyInspector.potentialDragNodeIndex ~= -1 then
					local data = hierarchyInspector.nodeData[hierarchyInspector.potentialDragNodeIndex]
					if data then
						if data.node.selected == true and hierarchyInspector.editingNodeIndex == -1 and hierarchyInspector.enableRename then
							hierarchyInspector:startRename(hierarchyInspector.potentialDragNodeIndex)
						elseif hierarchyInspector.nodeClickCallback then
							hierarchyInspector.nodeClickCallback(data.node)
						end
					end
				end
				hierarchyInspector.potentialDragNodeIndex = -1
			end
		end

		if hierarchyInspector.editingNodeIndex ~= -1 then
			if getLastKeyPressed() == KEY_ESCAPE then
				hierarchyInspector.renameCancelled = true
			end
		end

		for i = 1, #hierarchyInspector.children do
			local child = hierarchyInspector.children[i]
			if child.visible ~= false then
				child.changed = true
				if child == hierarchyInspector.renameTextLine and hierarchyInspector.renameJustStarted then
					UILib.updateUISprite(child, mousePos, false, false)
				elseif child ~= hierarchyInspector.nodeButtons[hierarchyInspector.draggingNodeIndex] then
					UILib.updateUISprite(child, mousePos, mousePressed, mouseDown)
				end
			end
		end
		hierarchyInspector.renameJustStarted = false

		if isDraggingNow then
			local draggedBtn = hierarchyInspector.nodeButtons[hierarchyInspector.draggingNodeIndex]
			UILib.updateUISprite(draggedBtn, mousePos, mousePressed, mouseDown)

			if hierarchyInspector.dropTargetIndex ~= -1 then
				local targetBtn = hierarchyInspector.nodeButtons[hierarchyInspector.dropTargetIndex]
				hierarchyInspector.lineSpriteData.position.x = targetBtn.position.x
				hierarchyInspector.lineSpriteData.size.x = targetBtn.size.x

				if hierarchyInspector.dropZone == 1 then
					-- Drop inside (highlight entire button)
					hierarchyInspector.lineSpriteData.position.y = targetBtn.position.y + (targetBtn.size.y * 0.5) - 0.5
					hierarchyInspector.lineSpriteData.size.y = targetBtn.size.y
					-- Make it slightly transparent so we can still read the text
					hierarchyInspector.lineSpriteData.color.w = 150.0
				else
					-- Drop before (0) or after (2)
					hierarchyInspector.lineSpriteData.size.y = 2.0
					hierarchyInspector.lineSpriteData.color.w = 255.0
					
					if hierarchyInspector.dropZone == 0 then
						hierarchyInspector.lineSpriteData.position.y = targetBtn.position.y - 2.0
					else
						hierarchyInspector.lineSpriteData.position.y = targetBtn.position.y + targetBtn.size.y + 2.0
					end
				end
				
				setSprite(hierarchyInspector.lineSpriteData.sprite, hierarchyInspector.lineSpriteData)
				drawSprite2D(hierarchyInspector.lineSpriteData.sprite)
			else
				-- If no target but mouse is below all nodes, show root drop line at the bottom
				if #hierarchyInspector.nodeData > 0 then
					local lastNodeBtn = hierarchyInspector.nodeButtons[#hierarchyInspector.nodeData]
					if mousePos.y > lastNodeBtn.position.y + lastNodeBtn.size.y then
						hierarchyInspector.lineSpriteData.size.y = 2.0
						hierarchyInspector.lineSpriteData.color.w = 255.0
						hierarchyInspector.lineSpriteData.position.x = hierarchyInspector.position.x + 4.0
						hierarchyInspector.lineSpriteData.size.x = hierarchyInspector.size.x - 8.0
						hierarchyInspector.lineSpriteData.position.y = lastNodeBtn.position.y + lastNodeBtn.size.y + 2.0
						
						setSprite(hierarchyInspector.lineSpriteData.sprite, hierarchyInspector.lineSpriteData)
						drawSprite2D(hierarchyInspector.lineSpriteData.sprite)
					end
				end
			end
		end
	end

	UILib.spriteDataPool[#UILib.spriteDataPool + 1] = hierarchyInspector
	UILib.setCollectSpritePool(true)

	return hierarchyInspector
end

UILib.tmpVec2 = vec2()

function UILib.updateUISprite(spriteData, mousePos, mousePressed, mouseDown, rightMousePressed)
	local mouseInSprite = false

	UILib.tmpVec2.x = spriteData.position.x
	UILib.tmpVec2.y = spriteData.position.y

	if intersectRect(mousePos, UILib.tmpVec2, spriteData.size) then
		mouseInSprite = true
	end

	if spriteData.hoverEnabled == false then
		mousePressed = false
		rightMousePressed = false
	end
		
	spriteData.update(spriteData, mouseInSprite, mousePressed, mouseDown, rightMousePressed)

	if spriteData.hovered == true then
		local oldHovered = UILib.hoveredSpriteWidget

		if oldHovered ~= nil then
			if spriteData.position.z < oldHovered.position.z then
				return
			end
		end

		UILib.hoveredSpriteWidget = spriteData
	end
end

function UILib.init()
	UILib.clear()

	UILib.cursorSprite = allocSprite()
	UILib.cursorSpriteData = {}
	UILib.cursorSpriteData.position = vec3(0.0, 0.0, UILib.uiDepth_cursor)
	UILib.cursorSpriteData.size = vec2(16.0, 16.0)
	UILib.cursorSpriteData.color = vec4(255.0, 255.0, 255.0, 255.0)
	UILib.cursorSpriteData.textureId = TEXTURE_ICONS
	UILib.cursorSpriteData.texturePos = vec2(8.0 * 5, 8.0 * 4)
	UILib.cursorSpriteData.textureSize = vec2(16.0, 16.0)

	UILib.setCollectSpritePool(false)

	-- modal dialog
	UILib.modalBackground = UILib.panel("", vec3(0.0, 0.0, UILib.uiDepth_overViews), vec2(), Config.paletteNames.blackLight * 0.7, Config.uiColors.text)
	UILib.modalBackground.hoverEnabled = false

	UILib.modalPanel = UILib.panel("", vec3(0.0, 0.0, UILib.uiDepth_overViews + 0.1), vec2(), Config.uiColors.panel, Config.uiColors.text)
	UILib.modalPanel.hoverEnabled = false

	UILib.modalOkButton = UILib.button("", vec3(0.0, 0.0, UILib.uiDepth_overViews + 0.2), vec2(), Config.uiColors.panelLight) 
	UILib.modalCancelButton = UILib.button("", vec3(0.0, 0.0, UILib.uiDepth_overViews + 0.2), vec2(), Config.uiColors.panelLight) 

	-- menu
	UILib.menuPanel = UILib.panel("", vec3(0.0, 0.0, UILib.uiDepth_overViews + 300), vec2(), Config.uiColors.panelLight, Config.uiColors.text)
	UILib.menuPanel.hoverEnabled = false
	UILib.menuPanel.visible = false

	UILib.menuButton = UILib.button("", vec3(0.0, 0.0, UILib.uiDepth_overViews + 300.1), vec2(), Config.uiColors.panelLight) 
	UILib.menuButton.callback = menuItemPressed

	UILib.setCollectSpritePool(true)
end

function UILib.update()
	if UILib.cursorChanged == true then
		UILib.cursorChanged = false
		invokeProgram(MAIN_PROGRAMID, "setCursor", UILib.cursor)
	end

	local mousePos = getMousePosition()
	
	local mousePressed = getMouseButtonPressed(MOUSE_BUTTON_LEFT)
	local mouseDown = getMouseButtonDown(MOUSE_BUTTON_LEFT)
	local rightMousePressed = getMouseButtonPressed(MOUSE_BUTTON_RIGHT)

	if UILib.cursor == UILib.CURSOR_HIDDEN then
		mousePressed = false
		mouseDown = false
		rightMousePressed = false
	end

	if UILib.modalOpen == true then
		mousePos.x = 0.0
		mousePos.y = 0.0

		mousePressed = false
		mouseDown = false
	end

	if mousePressed == true then
		if UILib.focusedSpriteWidget ~= nil and UILib.focusedSpriteWidget.hovered == false then
			UILib.setFocusedSpriteWidget(UILib.focusedSpriteWidget, false)
		end
	end

	UILib.hoveredSpriteWidget = nil

	UILib.clickConsumed = false
	
	for i = 1, #UILib.spriteDataPool do
		local spriteData = UILib.spriteDataPool[i]

		if spriteData.visible == true and spriteData.update ~= nil then
			UILib.updateUISprite(spriteData, mousePos, mousePressed, mouseDown, rightMousePressed)
		end
	end

	if UILib.hoveredSpriteWidget ~= nil then
		if mousePressed == true then
			if UILib.hoveredSpriteWidget.callback ~= nil then
				UILib.hoveredSpriteWidget.callback()
			end
		elseif rightMousePressed == true then
			if UILib.hoveredSpriteWidget.rightPressCallback ~= nil then
				UILib.hoveredSpriteWidget.rightPressCallback()
			end
		end
	end
	
	if UILib.menuPanel.visible == true then
		updateMenu(mousePos, mousePressed, mouseDown)
		
		if mousePressed == true then
			if UILib.menuJustOpen == false then
				UILib.menuPanel.visible = false
			end
		end

		UILib.menuJustOpen = false
	end

	if UILib.modalOpen == true then
		local mousePressed = getMouseButtonPressed(MOUSE_BUTTON_LEFT)
		local mouseDown = getMouseButtonDown(MOUSE_BUTTON_LEFT)
		local mousePos = getMousePosition()

		if UILib.modalJustOpen == true then
			mousePressed = false
			mouseDown = false

			UILib.modalJustOpen = false
		end

		updateModal(mousePos, mousePressed, mouseDown)
	end
end

function UILib.drawCursor(mousePos)
	if UILib.cursorSpriteData == nil or UILib.cursor == UILib.CURSOR_HIDDEN then 
		return
	end

	UILib.cursorSpriteData.position.x = round(mousePos.x) - 8
	UILib.cursorSpriteData.position.y = round(mousePos.y) - 8
	setSprite(UILib.cursorSprite, UILib.cursorSpriteData)
	drawSprite2D(UILib.cursorSprite)
end
