local defaultOrientation = function()
	return quat(0, 0, 1, 1)
end
local testC, testCR
local sin = math.sin
local cos = math.cos

local function undoFunc(panel, redo)
	-- print("from after")
	local last = panel.lastState
	local prev = panel.undos[#panel.undos - panel.undoPos + 2]

	local function actionIs(actions)
		if type(actions) == "table" then
			local test = redo and redo.action or last.action
			for a, action in ipairs(actions) do
				if action == test then
					return true
				end
			end
		else
			local action = actions
			return last.action == action or (redo and redo.action == action)
		end
	end
	-- print(string.format("\n undo: %s, %s --------------",last.action or "", redo and redo.action or ""))

	local layerChange = 0
	if last.anims then
		layerChange = #panel.layers - #last.anims[last.anim].layers
	end

	-- print("last,prev",last,prev)

	if last.action == "Stroke" then
		if panel.lastFill then
			panel.lastFill.count = 0
		end
		panel.proccedStrokes = false
	end
	if last.action ~= "Fill" then
		panel.recalcFills = true
	end

	--  for i,undo in ipairs(panel.undos) do
	--  print((#panel.undos-panel.undoPos) ==i and "<" or " ",i,undo.action or "")
	-- end

	print("1")

	panel.y = panel.y + panel.row * layerChange
	panel.height = panel.height - panel.row * layerChange
	panel.savedHeight = panel.savedHeight - panel.row * layerChange

	local undo = redo or last
	-- print("\nanims:",undo.anims or "no undo anims")
	local anims = redo and redo.animsAfter or last.animsBefore or panel.anims
	if anims ~= panel.anims then
		for a, anim in pairs(anims) do
			print("  >", a)
		end
		print("last/redo: ", last.anim)
	end

	panel.anims = anims
	panel.anim = last.anim
	panel.anims[panel.anim] = last.animation and deepcopy(last.animation) or panel.anims[panel.anim]

	panel.region = last.region and deepcopy(last.region)

	panel.layer = last.layer
	panel.frame = last.frame
	panel.currentFrame = last.cframe
	panel.solo = last.solo
	panel.fillsUsed = {}
	panel.cachedStrokes = {}
	panel.cachedSFills = {}

	panel.register()
	timeline_refresh2()

	if actionIs("Delete Frames") then
		if redo then
		else
			timeline_pasteRegion(last.copied.f1, last.copied.l1, last.copied)
		end
	end

	if actionIs("Move Frames") then
		print("mv")
		if redo then
			-- print("redo.source",inspect(redo.source))
			-- editor_deleteRegion(panel,redo.source)
			-- print("redo.dest",inspect(redo.dest))
			if redo.dest then
				timeline_pasteRegion(redo.dest.f1, redo.dest.l1, redo.source)
				panel.region = {
					frames = { first = redo.dest.f1, last = redo.dest.f1 + redo.dest.length - 1 },
					layers = { first = redo.dest.l1, last = redo.dest.l1 + redo.dest.height - 1 },
					selected = true,
				}
				panel.currentFrame = redo.dest.f1
			-- print("panel region",inspect(panel.region))
			else
				panel.currentFrame = panel.region.f1
			end
		-- print("panel.currentFrame",panel.currentFrame)
		else
			-- print("dest",inspect(last.dest))
			timeline_deleteRegion(last.dest)
			timeline_pasteRegion(last.source.f1, last.source.l1, last.source)
			timeline_pasteRegion(last.dest.f1, last.dest.l1, last.dest)
			panel.region = {
				frames = { first = last.source.f1, last = last.source.f1 + last.source.length - 1 },
				layers = { first = last.source.l1, last = last.source.l1 + last.source.height - 1 },
				selected = true,
			}
			panel.currentFrame = last.source.f1
			-- panel.layer=last.source.l1
			-- print("source",inspect(panel.region))
		end
	end

	if actionIs("Move Frame") then
		local tLayer, tKey, oLayer, oFrame = undo.dLayer, undo.dKey, panel.layer, panel.currentFrame
		if redo then
			tLayer, tKey, oLayer, oFrame = panel.layer, panel.frame, undo.dLayer, undo.dFrame
		end
		if undo.dKey then -- and tKey>1 then
			local frames = panel.layers[tLayer].frames
			local targetFrame = frames[tKey]
			table.remove(frames, tKey)
			print("tKey", tKey, "/", #frames)
			if tKey > 1 then
				frames[tKey - 1].l = frames[tKey - 1].l + targetFrame.l
			end
			timeline_pasteFrame(oLayer, oFrame, targetFrame)
		end
	end

	if actionIs("Change Frame") then
		print("goto done")
		goto done
	end

	panel.l.clips = deepcopy(last.clips)

	-- if last then
	--  if panel.layer == last.layer and panel.frame == last.frame then
	--    if last.action=="Stroke" then
	--      if redo then
	--        table.insert(panel.f.strokes,last.addedStroke)
	--      else
	--        prev.addedStroke=panel.f.strokes[#panel.f.strokes]
	--        table.remove(panel.f.strokes,#panel.f.strokes)
	--      end
	--    end
	--  end
	-- end
	print("_____________________________________")
	print(redo and "redo" or "undo", last.action)
	-- print("last.strokesRemoved",last.strokesRemoved and #last.strokesRemoved or "nada")
	-- print("last.strokesAdded",last.strokesAdded and #last.strokesAdded or "nada")

	if actionIs({ "Erase", "Stroke", "Fill", "Drop Selection", "Move", "Rotate", "Scale", "Clear Frame" }) then
		if redo then
			print("redo added/removed")
			if redo.strokesRemoved then
				for r, removed in ipairs(redo.strokesRemoved) do
					local x, rem = lume.remove(removed.frame.strokes, removed.stroke)
					if not rem then
						for s = #removed.frame.strokes, 1, -1 do
							if removed.stroke and removed.frame.strokes[s].id == removed.stroke.id then
								table.remove(removed.frame.strokes, s)
								-- break
							end
						end
					end
				end
			end
			for a, added in ipairs(redo.strokesAdded or {}) do
				table.insert(added.frame.strokes, added.stroke)
				added.frame.blank = false
			end
		else
			print(
				"undo added/removed",
				last.strokesAdded and #last.strokesAdded,
				last.strokesRemoved and #last.strokesRemoved
			)
			if last.strokesAdded then
				for a, added in ipairs(last.strokesAdded) do
					print("id", added.stroke.id)
					local x, rem = lume.remove(added.frame.strokes, added.stroke)
					if not rem then
						for s = #added.frame.strokes, 1, -1 do
							print(" > ", added.frame.strokes[s].id)
							if added.frame.strokes[s].id == added.stroke.id then
								print("remove stroke")
								table.remove(added.frame.strokes, s)
								-- break
							end
						end
					end
				end
			end
			for r, removed in ipairs(last.strokesRemoved or {}) do
				table.insert(removed.frame.strokes, removed.stroke)
				print("inserted stroke", removed.stroke, "#removed.frame.strokes", #removed.frame.strokes)
				removed.frame.blank = false
			end
		end
	elseif actionIs("Insert Frame") or actionIs("Duplicate Frame") then
		if redo then
			if redo.insertedFrames then
				for f, frame in ipairs(redo.insertedFrames.frames) do
					table.insert(redo.insertedFrames.l.frames, redo.insertedFrames.f, frame)
				end
			end
		elseif last.insertedFrames then
			for f, frame in ipairs(last.insertedFrames.frames) do
				table.remove(last.insertedFrames.l.frames, last.insertedFrames.f)
				print("removed frame at ", last.insertedFrames.f)
			end
		end
	elseif actionIs("Delete Frame") then
		if redo then
			table.remove(panel.frames, panel.frame)
		else
			table.insert(panel.frames, panel.frame, last.deletedFrame)
		end
	end

	if redo then
		if redo.stretchedFrame then
			redo.stretchedFrame.l = redo.postLength
		end
	else
		if last.stretchedFrame then
			last.stretchedFrame.l = last.preLength
		end
	end

	panel.justSelected = false
	if last.selection and last.selection.scale then
		panel.selection.pasted = false
		local pointer = last.selection.pointer or last.selection
		print("selection undo ", last.selection.count)
		if not panel.selection.scale then
			print("remake selection from undo")
			-- panel.selection=deepcopy(pointer)
			for k, v in pairs(pointer) do
				if not multicheckOR(k, { "additions", "removals" }) then
					-- print(k,v)
					panel.selection[k] = v
				end
			end
			panel.selection.strokes = pointer.strokes
			print("panel.selection.strokes", panel.selection.strokes and #panel.selection.strokes or "nil")
			panel.selection.mesh = gfx.newMesh(zformat, panel.selection.verts or 0, "triangles")
			vector_genVectorSelectionMesh(panel)
		end

		print("last.selection.count", last.selection.count)

		if last.selection.count == 0 then
			print("BACK TO THA START")
			panel.selection.dontPaste = true
			panel.selection.cut = false
		else
			panel.selection.dontPaste = false
			panel.selection.cut = true
		end

		print("panel.selection.dontPaste", panel.selection.dontPaste)

		panel.selection.scale = shallowcopy(last.selection.scale)
		panel.selection.position = shallowcopy(last.selection.position)
		panel.selection.angle = shallowcopy(last.selection.angle)

		if panel.selection.strokes then
			-- for s,stroke in ipairs(panel.selection.strokes) do
			--  print("stroke pt 1- later",stroke.line[1][1],stroke.line[1][2])
			-- end

			-- remove duplicates
			print("prev action: ", prev and prev.action or "noaction")
			if not panel.selection.dontPaste and (not redo or prev.action ~= "Drop Selection") then
				for s1 = #panel.f.strokes, 1, -1 do
					for s2 = 1, #panel.selection.strokes, 1 do
						local stroke1, stroke2 = panel.f.strokes[s1], panel.selection.strokes[s2]
						if (stroke1 and stroke2) and stroke1.id == stroke2.id then
							print("remove duplicate from selection -------------------------------------")
							table.remove(panel.f.strokes, s1)
						end
					end
				end
			end

			for s1 = #panel.f.strokes, 1, -1 do
				for s2 = 1, #panel.f.strokes, 1 do
					if
						s1 ~= s2
						and (panel.f.strokes[s1] and panel.f.strokes[s2])
						and panel.f.strokes[s1].id == panel.f.strokes[s2].id
					then
						print("remove duplicate-----------------------------------------------------")
						table.remove(panel.f.strokes, math.max(s1, s2))
					end
				end
			end
		end
	else
		-- IF REDOING AND PREVIOUS IS DROP SELECTION THEN dropselection?????????
		if redo and panel.selection and panel.selection.strokes then
			print("DROPPPPPPPPPPPPPPPPPPPPPPPPP IT")
			editor_dropSelection(panel, true)
		end
		panel.selection = {}
	end

	if last.frameData then
		panel.frames[panel.frame] = deepcopy(last.frameData)
	end

	if panel.layers[last.layer].format == "raster" then
		-- print("before",last.rasterDiff.canvasBefore.texture.color.id)
		-- print("after",last.rasterDiff.canvas.texture.color.id)
		local state = redo and panel.lastStateUndid or last
		print("state wh", last.w, last.h)
		print(panel.f.w, last.w, panel.f.h, last.h)
		if not redo then
			editor_unpackCanvas(state.rasterDiff, not redo, panel.f.canvas)
		end
		if last.w and last.h and not (panel.f.w == last.w and panel.f.h == last.h) then
			print("\nREVERT SIZE -------------\n")
			local new = newCanvas(last.w, last.h)
			local x1, y1 = panel.f.ox - panel.f.w / 2, panel.f.oy - panel.f.h / 2
			local x2, y2 = last.ox - last.w / 2, last.oy - last.h / 2
			local dx, dy = x1 - x2, y1 - y2
			print("dy", y1, y2, dy, (last.h - panel.f.h))
			print("")
			gfx.push("all")
			gfx.setCanvas(new)
			gfx.setBlendMode("replace")
			gfx.clear(0, 0, 0, 0)
			if redo then
				gfx.draw(panel.f.canvas, dx, dy == 0 and (last.h - panel.f.h) or 0)
			else
				gfx.draw(panel.f.canvas, dx, dy < 0 and 0 or (last.h - panel.f.h))
			end
			gfx.pop()
			local canv = panel.f.canvas
			panel.f.canvas = new
			panel.f.w, panel.f.h, panel.f.ox, panel.f.oy = last.w, last.h, last.ox, last.oy
		end
		if redo then
			editor_unpackCanvas(state.rasterDiff, not redo, panel.f.canvas)
		end
	end

	if last.strokes then
		-- panel.f.strokes=deepcopy(last.strokes)
	end

	for l, layer in ipairs(panel.layers) do
		layer.selected = last.layersSelected[l]
	end

	if panel.l.format == "vector" then
		for s = #panel.f.strokes, 1, -1 do
			local stroke = panel.f.strokes[s]
			if stroke.debug then
				table.remove(panel.f.strokes, s)
			elseif stroke.preRemove then
				stroke.preRemove = nil
			end
		end
		if panel.tool == "erase" then
			editor_prepareErase(panel)
			fill_prepareSegments(panel)
		end
	end

	if panel.l.remodel and last.meshData then
		for v, vert in ipairs(last.meshData.vertices) do
			setVec(panel.l.data.vertices[v], vert)
		end
		panel.l.data.edges = deepcopy(last.meshData.edges)
		panel.l.data.polygons = deepcopy(last.meshData.polygons)
	end

	--applyTransform(panel.f)

	-----------------------------
	::done::

	applyTransform(panel.transformer, last.transformer.position, last.transformer.orientation, vec3(1))

	panel.undid = 1
	panel.changed = true
	panel.l.updots = true
	editor_refreshStrokes()
	panel.sudoForceRestroke = true
	gfx.setBlendMode("alpha")
	-- print("to before")

	panel.redoing = redo
	panel.undoing = not redo
	panel.lastStateUndid = panel.lastState
end

local function pushUndoState(action, data)
  
	local panel = animatePanel
	local copy = false

	-- local multicheckOR(action,{"Drag Frames"})

	local prev = panel.undos[#panel.undos - panel.undoPos]

	print(string.format("\npush undo state: %i/%i %s", #panel.undos - panel.undoPos, #panel.undos, action))

	if copy then
		print("+++++++++++++++++++++++")
		print("COPY ALL STROKES ")
		print("+++++++++++++++++++++++")
	end

	panel.timelineMouseing = true

	panel.redoing = false
	panel.undoing = false

	if prev then
		if panel.layer == prev.layer and panel.frame == prev.frame then
			if action == "Shorten" and prev.action == "Shorten" then
				-- prev.postLength=prev.postLength-1
				return
			end
			if action == "Lengthen" and prev.action == "Lengthen" then
				-- prev.postLength=prev.postLength+1
				return
			end
		else
			print("CHANGED GRAME/////////////////")
			-- editor_pushUndo(panel,{
			--   action   = "Change Frame"
			--  ,anim     = prev.anim
			--  ,frame    = prev.frame
			--  ,layer    = prev.layer
			--  ,cframe   = prev.currentFrame
			-- })
		end
	end

	local undo = {}

	if action == "Shorten" or action == "Lengthen" then
		undo.stretchedFrame = panel.f
		undo.preLength = panel.f.l
	end

	if action == "Add Cut" or action == "Remove Cut" then
		undo.animsBefore = lume.clone(panel.anims)
	end

	undo.action = action
	undo.anim = panel.anim
	undo.frame = panel.frame
	undo.cframe = panel.currentFrame
	undo.layer = panel.layer
	undo.solo = panel.solo

	undo.region = panel.region and deepcopy(panel.region)
	undo.clips = deepcopy(panel.l.clips)
	undo.frameData = copy and deepcopy(panel.frames[panel.frame])
	undo.transformer = deepcopy(panel.transformer)
	undo.strokes = copy and copyStrokes(panel.frames[panel.frame].strokes)

	undo.frameLength = panel.f.l
	undo.framePosition = panel.f.abs
	undo.layersSelected = lume.clone(panel.layersSelected)

	undo.w = panel.f.w
	undo.h = panel.f.h
	undo.ox = panel.f.ox
	undo.oy = panel.f.oy

	if action == "Erase" then
	elseif action == "Insert Frame" or action == "Duplicate Frame" then
	elseif action == "Delete Frame" then
		if panel.frame > 1 then
			undo.frameLength = panel.frames[panel.frame - 1].l
		end
		undo.deletedFrame = panel.frames[panel.frame]
	elseif action == "Split Frame" then
	elseif action == "Clear Frame" then
		undo.strokesRemoved = {}

		for s, stroke in ipairs(panel.frames[panel.frame].strokes) do
			table.insert(undo.strokesRemoved, { frame = panel.f, stroke = stroke })
		end
	end
	--??????????????????????????????????????????
	--??????????????????????????????????????????
	-- TODO: only store diff
	-- elseif action and action:find("Frame", 0, true) then
	--  local diff = {}
	--  undo.animation = shallowcopy(panel.animation)
	--  for l,layer in ipairs(panel.layers) do
	--    undo.animation.layers[l] = deepcopy(layer)
	--  end

	if panel.l.remodel then
		undo.meshData = {
			vertices = copyVerts(panel.l.data.vertices),
			edges = deepcopy(panel.l.data.edges),
			polygons = deepcopy(panel.l.data.polygons),
		} or nil
	end

	if panel.selection and panel.selection.angle and panel.undos[#panel.undos - panel.undoPos] then
		local lastUndo = panel.undos[#panel.undos - panel.undoPos]
		if lastUndo.selection then
			-- print("shallow copy selection")
			undo.selection = {} --shallowcopy(panel.undos[#panel.undos].selection)

			undo.selection.pointer = lastUndo.selection.pointer or lastUndo.selection

			local selection = lastUndo.selection
			undo.selection.count = selection and selection.count + 1 or 0
			undo.selection.scale = shallowcopy(panel.selection.scale)
			undo.selection.position = shallowcopy(panel.selection.position)
			undo.selection.angle = shallowcopy(panel.selection.angle)
		else
			-- undo.strokes = copyStrokes(panel.frames[panel.frame].strokes)
			undo.selection = {}
			undo.selection.count = 0
			-- print("deep copy selection")
			for name, prop in pairs(panel.selection) do
				local time1 = engine_getTime()
				if gfx.isMesh(prop) then
				elseif gfx.isCanvas(prop) then
				else
					if name == "strokes" then
						if panel.selection.strokes then
							-- error("\n\nPUSH SELCTION STROKES\n\n ")
							-- print("copy strokes in push undo",#panel.selection.strokes)
							undo.selection.strokes = copyStrokes(panel.selection.strokes)
							for s, stroke in ipairs(undo.selection.strokes) do
								-- print("stroke pt 2- start",stroke.line[1][1],stroke.line[1][2])
								editor_adjStroke(stroke, panel.selection, true)
								-- print("stroke pt 2- after",stroke.line[1][1],stroke.line[1][2])
							end
						end
					elseif multicheckOR(name, { "additions", "removals", "frames" }) then
						undo.selection[name] = prop
					-- if panel.selection.additions then
					--  undo.selection.additions={}
					--  for a,add in ipairs(panel.selection.additions) do
					--    table.insert(undo.selection.additions,{frame=add.frame,stroke=copyStroke(add.stroke)}
					--  end
					-- end
					else
						print("name", name)
						undo.selection[name] = shallowcopy(prop)
					end
					-- print(" ",name,math.round(time1-engine_getTime(),4))--,prop,type(prop)=="table" and lume.count(prop) or "")
				end
			end
		end
	end
	----undo.--string   = panel.frames[panel.frame].canvas:newImageData():getString()

	if copy then
		local canvas = panel.frames[panel.frame].canvas
		if canvas ~= panel.blankCanvas and gfx.isCanvas(canvas) then
			gfx.push("all")
			gfx.setCanvas(engine.canvas)
			undo.canvas = panel.frames[panel.frame].canvas:newImageData()
			gfx.pop()
		else
		end
	end

	editor_pushUndo(panel, undo)

	-- for i,undo in ipairs(panel.undos) do
	--  print(i==(#panel.undos-panel.undoPos) and ">>" or " ",i,undo.action or "")
	-- end
end

local function pushUndoData(data)
	local panel = animatePanel
	for k, v in pairs(data) do
		panel.undos[#panel.undos][k] = v
	end
end

local function register()
	local panel = animatePanel
	panel.animation = panel.anims[panel.anim]
	panel.layers = panel.animation.layers
	panel.l = panel.layers[panel.layer]
	-- print("panel.layer",panel.layer,"/",#panel.layers,panel.l)
	panel.frames = panel.l.frames
	panel.frame = mc(1, panel.frame, #panel.frames)
	panel.f = panel.frames[panel.frame]
	panel.l.selected = true
	if panel.prevCurrentFrame ~= panel.currentFrame then
		editor_setKeyFrame(panel)
	end
	panel.prevCurrentFrame = panel.currentFrame

	panel.numLayersSelected = 0
	for l, layer in ipairs(panel.layers) do
		-- print(layer.selected)
		-- for f,frame in ipairs(layer.frames) do
		--  trace(frame.released,frame.canvas and "has canvas" or "no")
		-- end
		layer.index = l
		panel.layersSelected[l] = layer.selected
		if layer.selected then
			panel.numLayersSelected = panel.numLayersSelected + 1
		end
		panel.layersToFill[l] = panel.fillLayer == "visible" or layer.selected
		if not layer.visible then
			panel.layersToFill[l] = false
		end
		if panel.solo > 0 then
			panel.layersToFill[l] = panel.solo == l
		end
	end
end

function editor_defineAnimatePanel(new)
	local panel = {
		name = "Animation",
		open = false,
		docked = true,
		hide = true,
		sharp = true,
		noTitle = true,
		x=0,
    y = 99999,
		visible = true,
		canSave = true,
		timeline = {
    },
		playing = false,
		drawing = false,
		releaseCount = 1,
		releaseLayer = 1,
		prevCurrentFrame = 1,
		timelineTracking = config.timelineTracking,
		scrollSpeed = 0,
		scrollPos = 0,
		timelineMax = 0,
		limitToStageSize = config.limitToStageSize,
		showPasteboard = config.showPasteboard,
		refreshTimeline = 0,
		ctriangulate = false,
		drawTime = 0,
		tick = 0,
		time = 0,
		object = {},
		allFrames = {},
		undos = {},
		undoPos = 0,
		strokeSelected = {},
		layersSelected = {},
		layersToFill = {},
		layersFilled = {},
		tweenDrag = { side = "", frame = 0, layer = 0 },
		frame = 1,
		layer = config.currentLayer or 1,
		anim = "Idle",
		id = "",
		anims = nil,
		solo = 0,
		minWidth = 450,
		layerWidth = 17,
		drawingNormal = vec3(0),
		drawingPosition = vec3(0),
		baseLayerWidth = 70,
		frameWidth = 12.5,
		draggingFrame = false,
		draggedFrame = 0,
    frameHover= { x = 0, y = 0 },
		canvas = nil,
		brushPrev = nil,
		fillDivs = config.fillDivs or 4,
		threadFill = false,
		forceRefill = config.forceRefill,
		antiAlias = config.antiAlias,
		antiAliasing = config.antiAliasing or 4,
		exportBufferCount = config.exportBufferCount or 1,
		frameShift = config.frameShift,
		debugView = (not lfs.isFused()) and config.animDebugView,
		debugThin = config.animDebugThin,
		debugBrush = config.debugBrush,
		debugCompaction = config.debugCompaction,
		debugBoolean = config.debugBoolean,
		layersToDelete = {},
		stageColor = { r = 1, g = 1, b = 1, a = 1 },
		cachedStrokes = {},
		cachedFills = {},
		strokeDiffs = { added = {}, removed = {} },
		--                 brushCanvas  = newCanvas(10,10),
		--                 brushCanvas2   = newCanvas(10,10),
		blankCanvas = newCanvas(10, 10),
		fillBehind = false,
		autoCenter = true,
		defaultSize = 32,
		tileSize = 32,
		padding = 2,
		brushEase = 2,
		brushCoords = { x = 0, y = 0, px = 0, py = 0, ax = 0, ay = 0 },
		frameBounds = false,
		fullWidth = false,
		maxUndos = config.maxUndos or 250,
		undid = 0,
		drawn = 0,
		filter = "linear",
		diff = { x = 0, y = 0, w = 0, h = 0 },
		tipDist = 0,
		tipDist2 = 0,
		selection = {},
    frameSelectionTransfomToolPoints={},
		markSize = 6,
		height = 150,
		savedHeight = 150,
		stage = false,
		savedWidth = 300,
		width = 300,
		dotLineVerts = 10000,
		dotVertCount = 0,
		lineVertCount = 0,
		dotVerts = ffi.new("m_vert[10000]"),
		lineVerts = ffi.new("m_vert[10000]"),

    debugStrokes = {},

		onion = {
			amount = 1,
			enabled = false,
			before = {
				amount = 1,
				enabled = true,
				tint = { r = 1, g = 0.8, b = 0, a = 0.5 },
				alphas = { 0.5, 0, 0 },
			},
			after = {
				amount = 1,
				enabled = true,
				tint = { r = 0.6, g = 0, b = 1, a = 0.5 },
				alphas = { 0.5, 0, 0 },
			},
		},

		cropSelect = config.cropSelect,
		selectMode = { true, false, false },
		selectModeDraws = {
			function()
				gfx.circle("fill", 8, 8, 2)
			end,
			function()
				gfx.line(8, 8, 24, 24)
			end,
			function()
				gfx.rectangle("line", 6, 6, 20, 20)
			end,
		},

		imageFormats = {
			{ name = "none", tip = "" },
			{ name = "png", tip = "Slower export, smaller files" },
			{ name = "tga", tip = "Faster export, larger files" },
		},

		row = config.row,

		exportFormats = config.exportFormats or { mp4 = true, gif = false, sequence = "none" },

		defaultFormat = "vector",
		formats = { "r8", "raster", "vector" },

		loopModes = { "repeat", "pingpong", "random" },

		meshModeNames = { "transform", "pivot", "proportional", "falloff" },

		transformMode = "global",
		transformModes = { "global", "local", "normal" },

		pivotMode = "bound",
		pivotModes = { "bound", "cursor", "origins" },

		proportionalMode = "disabled",
		proportionalModes = { "disabled", "distance", "connected" },

		falloffMode = "smooth",
		falloffModes = { "smooth", "sphere", "sharp", "linear", "constant" },

		renderModes = { "debug", "color", "shaded" },

		meshViewToggles = { "seeThru", "orbit", "unlit" },

		-- drawing stuff ------------------------

		color = config.currentColor or { h = 0.5, s = 0.5, v = 0, r = 0, g = 0, b = 0, a = 1 },
		vector = true,
		brush = config.currentBrush and #config.currentBrush.brush>0 and config.currentBrush.brush or {
			color = { h = 0.5, s = 0.5, v = 0, r = 0, g = 0, b = 0, a = 1 },
			shape = 1,
			flow = 1,
			texture = 0,
			follow = false,
			pressureSizeAmount = 1,
			antialias = true,
			size = 32,
			opacity = 1,
			angle = 0,
			flatness = 0,
			spacing = 0,
			scatter = 0,
		},

		fillColor = config.fillColor or { h = 0.5, s = 0.5, v = 0.5, r = 0, g = 0, b = 0, a = 1 },

		pen = config.currentPen or {
			size = 3,
			ends = "round",
			group = "line",
			stabilizer = 0,
			smoothing = 0.1,
			pressureSizeAmount = 1,
			pressureOpacityAmount = 1,
		},

		penGroups = {
			{ name = "rough", color = config.roughColor or { r = 0.4, g = 0.6, b = 1, a = 0.5 }, swap = true },
			{ name = "line" },
			{ name = "note", color = config.noteColor or { r = 0.9, g = 0.2, b = 0.2, a = 1 } },
		},

		penShortcuts = {
			"Z",
			"X",
			"C",
		},

		penColors = {
			line = { 0, 0, 0, 1 },
			rough = { 0.6, 0.6, 0.9, 1 },
			note = { 1, 0.5, 0.5 },
		},

		penLayers = {
			rough = 0.4,
			fill = 0.3,
			line = 0.2,
			note = 0.1,
		},

		sliding = nil,

		fillDrag = config.fillDrag,
		fillAll = config.fillAll,
		fillBehind = config.fillBehind,
		fillGap = config.fillGap or 0,
		fillRadius = config.fillRadius or 0,
		fillTolerance = config.fillTolerance or 0,
		fillExpand = config.fillExpand or 0,

		sliders = {
			-- opacity  = {name="opacity",   stroke=true, pct=1,  jitter=0 },
			size = { name = "size", stroke = true, pct = 0.2, jitter = 0, min = 1, max = 64 },
			flatness = { name = "flatness", stroke = true, pct = 0, jitter = 0, min = 0, max = 1, percent = true },
			spacing = { name = "spacing", stroke = true, pct = 0, jitter = 0, min = 0, max = 1, percent = true },
			scatter = { name = "scatter", stroke = true, pct = 0, jitter = 0, min = 0, max = 1, percent = true },
			angle = { name = "angle", stroke = true, pct = 0.5, jitter = 0, min = -pi, max = pi },
		},

		eraser = {
			size = 6,
		},

		states = {
			on = { r = 130, g = 100, b = 130 },
			off = { r = 90, g = 120, b = 150 },
			non = { r = 80, g = 80, b = 80 },
		},

		recentColors = config.recentArtColors or {},
		recentBrushes = config.recentBrushes or {},
		wheel = {},
		palettes = {},
		currentPalette = 1,

		numPals = 15,
		recentsHeight = 20,
		squareSize = 30,

		currentStrip = "",
		currentJitter = "",
		numStrips = 40,
		palw = 112,

		hue = config.currentColor and config.currentColor.h or 0.5,
		saturation = config.currentColor and config.currentColor.s or 0.5,
		value = config.currentColor and config.currentColor.v or 0.5,

		colorJitter = config.currentBrush and config.currentBrush.colorJitter or {
			hue = { jitter = 0, stroke = true },
			saturation = { jitter = 0, stroke = true },
			value = { jitter = 0, stroke = true },
		},

		tools = { "brush", "erase", "fill", "move", "select" }, --,"verts"},
		fillModes = { "brush", "line", "last", "both" },
		eraseModesRaster = { "circle" },
		eraseModesVector = { "stroke", "segment", "brush" },
		brushModesVector = {
			{ img = "paint_over_all", name = "over_all", tip = "Paint over lines" },
			{ img = "paint_over_fill", name = "over_fill", tip = "Paint under lines" },
			-- {img="paint_inside_fill",name="inside_fill", tip="Paint inside fills"},
			-- {img="paint_behind_all",name="behind_all", tip="Paint behind fills (experimental)"}
		},
		brushMode = config.brushMode or "over_all",
		eraseGroups = { "all pen", "last pen" },
		selectLayers = { "visible", "selected" },
		strokeSelectModes = { "stroke", "segment", "cut" },
		strokeSelectMode = "stroke",
		selectLayer = config.selectLayer or "selected",
		fillLayer = "selected",
		eraseGroup = "all pen",
		selectGroup = "all pen",
		eraseArea = false,
		strokeSelectFalloff = 0.5,

		tool = "move",
		eraseMode = config.eraseMode or "stroke",
		fillMode = "line",
		eraseLayer = "selected",

		toolShortcuts = {
			{ "e", "erase", 0 },
			{ "b", "brush", 0 },
			{ "p", "pen", 0 },
			-- {"z","pen",0},
			{ "x", "pen", 0 },
			-- {"c","pen",0},
			{ "f", "fill", 0 },
			{ "v", "move", 0 },
			{ "l", "select", 0 },
		},


		update = editor_updateAnimatePanel,
    draw = editor_drawAnimatePanel
	}

  panel.onionButton = {
    width = 56,
    height = panel.row-3,
    click = function()

      editor_openContextMenu({
        editor_makeToggle(animatePanel.onion, { "amount", "enabled" }, { name = "Onion Skin" }),
        editor_makeToggle(
          animatePanel.onion.before,
          { "amount", "enabled" },
          { name = "before", colors = { "tint" }, min = 1, max = 10, round = 1 }
        ),
        editor_makeToggle(
          animatePanel.onion.after,
          { "amount", "enabled" },
          { name = "after", colors = { "tint" }, min = 1, max = 10, round = 1 }
        ),
        editor_makeToggle(animatePanel.onion, "loop", { name = "Loop Onion Skin" }),
      },
      {
        x = panel.x - 8,
        y = panel.y-panel.height,
        -- width = 200,
      })
    end,
  }

	if config.timelineBottom then
		panel.bottom = config.timelineBottom
	else
		panel.bottom = true
	end
	panel.undoFunc = undoFunc
	panel.pushUndoState = pushUndoState
	panel.pushUndoData = pushUndoData
	panel.register = register

	return panel
end

function editor_refreshStrokes(stroke, fast, full)
	local panel = animatePanel
	panel.currentFramePointCount = 0
	panel.currentFrameFillCount = 0
	if panel.f and panel.f.strokes then
		for s, stroke in ipairs(panel.f.strokes) do
			if stroke.group == "fill" then
				panel.currentFrameFillCount = panel.currentFrameFillCount + 1
			end
			if stroke.line and stroke.line[0] and not stroke.debug then
				panel.currentFramePointCount = panel.currentFramePointCount + stroke.line[0][0]
			end
		end
	end
	animatePanel.forceRestroke = true
	animatePanel.forceRestrokeFast = fast
	if stroke then
		animatePanel.forceRestroke = stroke
	end
end
function drawSelectionFrame(panel)
  if not (panel.selection and panel.selection.mesh) then return end
  -- gfx.setScissor(0,0,screenWidth,panel.y-4)
  local square = 5
  local point2
  local pts = {}
  for i = 1, 12, 1 do
    local point = { 
      x = panel.frameSelectionTransfomToolPoints[i].x, 
      y = panel.frameSelectionTransfomToolPoints[i].y, 
      alpha = 0.7 
    }

    if i > 8 then
      if not panel.scalingSelection then
        point.alpha = point.alpha == 1 and 1 or 0.2 --mc(0.2,1-math.dist(mouse.x,mouse.y,point.x,point.y)/128,0.6)

        -- gfx.circle("fill",point.x,point.y,3+5*point.alpha,8)
        gfx.push()
        gfx.translate(point.x, point.y)
        gfx.setLineWidth(2)
        gfx.rotate(panel.selection.angle + ((i - 8) / 4) * pi * 2 + pi / 2)
        gfx.setColor(1, 1, 1, point.alpha)
        -- gfx.circle("line",0,0,3)
        gfx.setLineWidth(4)
        gfx.setColor(0, 0, 0, point.alpha * 2)
        -- gfx.arc("line","open",0,0,3+5*point.alpha,-pi/6,pi/2+pi/6,8)
        gfx.pop()
        -- gfx.circle("line",point.x,point.y,3,8)
      end
    else
      -- if  not panel.rotatingSelection then
      if pts[i - 1] then
        gfx.setLineWidth(2)
        gfx.setColor(1, 1, 1, 1)
        gfx.line(point.x + 1, point.y + 1, pts[i - 1].x + 1, pts[i - 1].y + 1)
        gfx.setColor(0, 0, 0, 1)
        gfx.line(point.x, point.y, pts[i - 1].x, pts[i - 1].y)
      end
      if i == 8 then
        gfx.setLineWidth(2)
        gfx.setColor(1, 1, 1, 1)
        gfx.line(point.x, point.y, pts[1].x, pts[1].y)
        gfx.setColor(0, 0, 0, 1)
        gfx.line(point.x + 1, point.y + 1, pts[1].x + 1, pts[1].y + 1)
      end
      -- end
    end
  end

  for i = 1, 12, 1 do
    if i <= 8 then
      local point = { x = pts[i].x, y = pts[i].y, alpha = 0.7 }
      gfx.push()
      gfx.translate(point.x, point.y)
      gfx.rotate(panel.selection.angle)
      -- gfx.setColor(1,1,1,point.alpha)
      -- gfx.rectangle("fill",-square/2,-square/2,square,square)
      gfx.setColor(0, 0, 0, point.alpha * 1.2)
      gfx.setLineWidth(2)
      gfx.rectangle("fill", -square / 2, -square / 2, square, square)
      gfx.pop()
    end
  end
  -- gfx.setScissor()
end

function drawAreaSelected(panel)
  if not panel.clickedCanvas then return end
  
  local mull = catmullLine(mouse.line, 3)
  if panel.tool == "move" and (panel.selectingRect and panel.selectingRect[6]) then  
      local minx,miny = panel.selectingRect[3],panel.selectingRect[4] 
      local w,h = panel.selectingRect[5],panel.selectingRect[6] 
      -- gfx.push("all")
      if panel.visible then 
      gfx.setScissor(0, 0, screenW, panel.y - 4)
    end
      gfx.translate(-1, -1)
      gfx.setColor(0.3, 0.3, 0.7, 1)
      gfx.setLineWidth(1)
      -- gfx.line(minx,miny,minx+w,miny)
      -- gfx.line(minx,miny,minx,miny+h)
      -- gfx.line(minx+w,miny,minx+w,miny+h)
      -- gfx.line(minx,miny+h,minx+w,miny+h)
      gfx.rectangle("line", minx, miny, w, h)
      gfx.setColor(0.4, 0.3, 0.5, 0.1)
      gfx.rectangle("fill", minx, miny, w, h)
      gfx.translate(1, 1)
      gfx.setScissor()
      -- gfx.pop()
  elseif panel.tool == "select" and #mouse.line > 3 and (#mouse.line)%2==0 then --panel.selection.shape and #panel.selection.shape > 5 then
      -- trace("selection points " .. tostring(#panel.selection.shape))
      gfx.push("all")
      if panel.visible then 
      gfx.setScissor(0, 0, screenW, panel.y - 4)
    end
      gfx.setColor(0.3, 0.3, 0.7, 1)
      gfx.setLineWidth(3)
      gfx.setLineJoin("none")
      gfx.translate(0, 0)
      -- gfx.line(mouse.line)
      -- gfx.translate(0,1) gfx.line(mouse.line)
      -- gfx.translate(0,-1) gfx.line(mouse.line)
      -- gfx.translate(-1,0) gfx.line(mouse.line)
      gfx.setScissor()
      gfx.pop()

      -- gfx.setWireframe(true)
      -- gfx.setColor(1, 1, 1, 1)
      -- gfx.draw(panel.selection.mesh)
      -- gfx.setWireframe(false)
  elseif panel.tool == "erase" and #mull> 4 then
    gfx.push("all")
    gfx.setColor(1, 0.3, 0.3, 1)
    gfx.setLineWidth(3)
    gfx.setLineJoin("none")
    gfx.line(mull)
    gfx.pop()
  end

end

function editor_drawAnimatePanel(panel)
  timeline_draw() 
end

function editor_updateAnimatePanel(panel)
	bench.push("anim_panel")

	bench.push("pre_pre_update")

	panel.bonusHeight = 20
	panel.minWidth = 300

	panel.atRoot = panel.root == panel.symbol

	if panel.drawTime > 0 and config.hideTimelineWhileDrawing then
		if not panel.preVisible then
			panel.preVisible = panel.visible
			panel.visible = false
		end
	else
		if panel.preVisible then
			panel.visible = panel.preVisible
			panel.preVisible = nil
		end
	end

	if ctx.isAnim and panel.animation then
    editor.gridEnabled = false
    --TODO nil value on tab switch
		lwindow.setTitle(
			config.projects[engine.project].name
				.. (lume.count(panel.anims) > 1 and " - " .. panel.animation.name or "")
		)
	end

	local smoothFactor = 1 - (ctx.views[1].camera.zoom - 4.5) / 3
	panel.cat = 2 --mf((utils.map(ctx.views[1].camera.soom,1.3,0.9,1,3.5)^2)/4)
	if config.catmull ~= panel.pcatmull then
		editor_refreshStrokes()
	end
	panel.pcatmull = config.catmull
	-- trace("zoom",ctx.views[1].camera.soom,"cat",panel.cat,panel.cat+config.catmull)
	if panel.cat ~= panel.pcat then
		editor_refreshStrokes()
	end
	panel.pcat = panel.cat

	bench.pop("pre_pre_update")

	if not panel.open  then
		editor_clearBones(panel)
		bench.pop("anim_panel")
		return
	end

	editor_changeBrushSize(panel)
	editor_selectTool(panel)
	panel.color = (panel.tool == "fill" or panel.brush.paint) and panel.fillColor or panel.brush.color
	panel["line color"] = panel.brush.color
	panel["fill color"] = panel.fillColor

	panel.maxUndos = round(panel.maxUndos, 0)

	panel.symbol.stageWidth = tonumber(panel.symbol.stageWidth)
	panel.symbol.stageHeight = tonumber(panel.symbol.stageHeight)
	-- print(panel.root.stageWidth,panel.root.stageHeight,panel.symbol.stageWidth,panel.symbol.stageHeight
	--  ,type(panel.root.stageWidth),type(panel.root.stageHeight),type(panel.symbol.stageWidth),type(panel.symbol.stageHeight) )

	bench.push("pre_update")
	editor.currentTool = panel.l.remodel and "Select" or "Brush"

	pathsPanel.open = false
	scriptsPanel.visible = false

	panel.active = (keyboard.active or mouse.active or panel.playing)

	gfx.setShader()

	editor_traceAnimationHelp(panel)
	editor_traceAnimationUndos(panel)
	
	scene_updateEntityRenderInfo(panel.instance)

	panel.changedFrame = panel.frame ~= panel.prevFrame or panel.layer ~= panel.prevLayer
	panel.changed = panel.changedFrame or panel.pasted
	panel.pasted = false

	if panel.changed then
		panel.proccedStrokes = nil
		-- editor_prepareFill(panel)
	end

	panel.fillTick = engine_getTime()
	if panel.fillThread then
		local _, time = coroutine.resume(panel.fillThread)
		-- print("FILL TIME ====================== ",time)
	end

	if panel.changedFrame then
		if panel.selection.dontPaste then
			table.remove(panel.undos, #panel.undos - panel.undoPos)
		end
		panel.selection = {}
	end

	if panel.changedFrame then
		editor_releaseSelection(panel)
	end

	if panel.three and panel.l.type ~= "model" then
		panel.l.remodel = false
		-- camera_reset(cam)
	end

	panel.filter = panel.antiAlias and "linear" or "nearest"

	panel.three = panel.l.type == "model"

	panel.prevFrame = panel.frame
	panel.prevLayer = panel.layer

	if panel.animation.audio and (not panel.animation.source or not panel.waveForm) then
		editor_setAnimationAudio(panel, panel.animation.audio)
	end

	if not panel.exporting then
		editor_animationKeys(panel)
	end

	if panel.exporting or panel.saving then
		input_unlockKeys()
		if input_keyPressed("escape") then
			panel.canSave = true
			panel.saving = false
			panel.exporting = false
			panel.exportingAlpha = false
			editor_tempTip((panel.saving and "Save" or "Export") .. " Cancelled")
			panel.playing = false
			panel.changed = true
			panel.exportBuffers = nil
			timeline_refresh()
			editor_refreshStrokes()
			lwindow.setVsync(true)
			for i, anim in pairs(panel.anims) do
				for l, layer in ipairs(anim.layers) do
					for f, frame in ipairs(layer.frames) do
						if frame.lastSave == panel.attemptedSaveTime then
							frame.lastSave = frame.preSaveTime
						end
					end
				end
			end
			game.waiting = {}
			input_unlockKeys()
			input_unlockMouse()
		end

		input_lockMouse()
		input_lockKeys()
	end

	if panel.saving then
		editor_tempTip("Saving: " .. tostring(mu((panel.saveFrame / panel.framesToSave) * 100)) .. "%")
	end

	if not panel.canSave then
		gfx.print("Compressing Save...")
	end

	bench.push("register")
	panel.register()
	bench.pop("register")

	bench.push("tracestuff")
	if editor.autoSaveEnabled then
		local seconds = editor.saveTime - editor.saveStep
		local minutes = math.modf(seconds / 60)
		local hours = math.modf(minutes / 60)
		minutes = minutes - hours * 60
		seconds = seconds % 60
		if hours > 0 then
			trace(string.format("Autosaving in %02d:%02d:%02d", hours, minutes, seconds))
		else
			trace(string.format("Autosaving in %02d:%02d", minutes, seconds))
		end
	end

	trace(string.format("undo: %i/%i [max %i]", panel.undoPos, mc(0, #panel.undos, panel.maxUndos), panel.maxUndos))
	trace(string.format("layer #%i, %s, %.2f", panel.layer, panel.l.name, panel.l.alpha ))
	trace(string.format("frame #%i, l:%i id:%s", panel.frame, panel.f.l, stringFrameID(panel.f.id)))
	-- trace("cam.soom",cam.soom)

	trace(
		" > current tool: "
			.. (panel.tool == "brush" and (not panel.brush.paint and "pen" or "brush") or panel.tool)
			.. ((panel.tool == "brush" or panel.tool == "eraser") and ", size: " .. tostring(editor_getBrushSize(panel)) or "")
			.. (panel.clickedCanvas and string.format(", pressure:%.2f", mouse.pressure or 0) or ", not clicked")
	)
  -- trace("< canvasclicked",animatePanel.clickedCanvas)
	trace(" > symbols: " .. tostring(#panel.f.symbols))
	local c = panel.color
	trace(string.format(" > current color: %i,%i,%i,%i", c.r * 255, c.g * 255, c.b * 255, c.a * 255))
	if panel.l.format == "vector" then
		if panel.currentFramePointCount then
			trace(
				" > strokes "
					.. #panel.f.strokes
					.. " ("
					.. panel.currentFrameFillCount
					.. " fills), "
					.. "points "
					.. panel.currentFramePointCount
			)
		end
	elseif panel.l.format == "raster" then
		if panel.f.canvas then
			-- trace(panel.f.canvas:getWidth(),panel.f.canvas:getHeight())
		end
		trace(
			string.format(
				" > size: %ix%i (%i:%i) %.2fmb",
				panel.f.w,
				panel.f.h,
				panel.f.ox,
				panel.f.oy,
				panel.f.canvas and panel.f.canvas.size / (1024 ^ 2) or 0
			)
				.. (
					(panel.f.packedCanvas and not panel.f.dirty)
						and string.format(
							" (%.2f packed)",
							(panel.f.packedCanvas.tileCount * panel.f.packedCanvas.tileSize ^ 2 * 4) / (1024 ^ 2)
						)
					or ""
				)
		)
	end

	if panel.selection then
		if panel.selection.strokes and #panel.selection.strokes > 0 then
			trace(" > strokes selected", #panel.selection.strokes)
			if not lfs.isFused() then
				trace("    dontPaste", panel.selection.dontPaste)
				trace("    pasted", panel.selection.pasted)
			end
		end
		if panel.selection.symbols and #panel.selection.symbols > 0 then
			trace(" > symbols selected", #panel.selection.symbols)
		end
	end

	if ctx.isAnim then
		panel.stageColor = panel.animation.color
		editor.panMult = vec2(editor_prepareToolMouse(panel, true))
	end

	
	bench.pop("tracestuff")

	bench.pop("pre_update")

	if not (panel.playing or panel.exporting) then
		if panel.currentFrameFastScrubbed then
			panel.currentFrame = panel.currentFrameFastScrubbed
			panel.scrollPos = easeTo(
				panel.scrollPos,
				mc(
					0,
					panel.currentFrame * panel.frameWidth - (screenW - panel.layerWidth) / 2,
					panel.animation.length * panel.frameWidth
				),
				1.3
			)
			panel.currentFrameFastScrubbed = false
		end
		bench.push("not playing")
		editor_timelineKeys(panel)
		editor_exportAnim(panel)
		timeline_update()

		bench.push("release/restore")
		editor_releaseFrames(panel)
		editor_restoreFrames(panel)
		bench.pop("release/restore")

		-- for l,layer in ipairs(panel.layers) do
		--  for f,frame in ipairs(layer.frames) do
		local frame = panel.f
		if frame and panel.l.format == "vector" and frame.failedStrokes and #frame.failedStrokes > 0 then
			print("frame.failedStrokes", #frame.failedStrokes)
			table.sort(frame.failedStrokes)
			for i = #frame.failedStrokes, 1, -1 do
				-- print("frame.failedStrokes[i]",frame.failedStrokes[i])
				table.remove(frame.strokes, frame.failedStrokes[i])
			end
			editor_tempTip("fill failed")
			frame.failedStrokes = {}
		end
		--  end
		-- end

		bench.push("tools")
		editor_meshTools(panel)
		editor_poseTools(panel)

		if not (panel.l.remodel or panel.l.repose) then
			editor_drawingTools(panel)
			editor_updateFrameTransform(panel) -- LAG
		elseif animatePanel.transformer then
			scene_deleteEntity(panel.transformer)
		end
		bench.pop("tools")

		bench.push("drawframe")
		editor_updateLayerFrame(panel)
		editor_drawFrameSelection(panel)

		bench.pop("drawframe")

		if cmdDown and input_keyRepeat("z") and not panel.clickedCanvas then
			editor_undo(panel)
		end
		bench.pop("not playing")
	else
		if panel.exporting then
			lwindow.setVsync(false)
			if panel.exportFormats.separateLayers then
				trace("Exporting Layer ", panel.exportLayer)
				panel.solo = panel.exportLayer
			end

			editor_refreshStrokes()

			panel.exportBuffers[panel.exportBufferIndex]:renderTo(function()
				gfx.setColor(1, 1, 1, 1)
				gfx.clear(0, 0, 0, 0)
				gfx.setShader(shaders.unmul)
				gfx.setBlendMode("replace")
				gfx.setMeshCullMode("none")
				gfx.draw(view.canvas, 0, 0, 0, panel.exportScale, panel.exportScale)
			end)

			if
				(panel.currentFrame == panel.animation.length) or (panel.exportBufferIndex == panel.exportBufferCount)
			then
				panel.exportBuffersReady = true
			end

			if panel.exportBuffersReady then
				for b = 1, panel.exportBufferIndex, 1 do
					local frameIndex = panel.currentFrame - panel.exportBufferIndex + b
					local data = panel.exportBuffers[b]:newImageData()
					local prep = SAVE_DIR
					local file = string.format(
						"%s/export/sequence/%sframe_%04d.%s",
						prep,
						panel.exportFormats.separateLayers and panel.layers[panel.exportLayer].name .. "/" or "",
						frameIndex,
						panel.sequenceFormat
					)
					file = file:gsub("//", "/")
					local result = data:encode(panel.sequenceFormat, file)
					-- print("encode ",file,result)
					data:release()
					data = nil
				end
			end

			panel.exportBufferIndex = wrap2(panel.exportBufferIndex + 1, 1, panel.exportBufferCount)

			if panel.currentFrame == panel.animation.length then
				panel.exportBufferIndex = 1
			end
		else
			editor_tempTip("playing")
			if input_keyPressed("escape") then
				editor_playAnimation(nil, panel.currentFrame)
			end

			bench.push("release/restore")
			editor_releaseFrames(panel)
			editor_restoreFrames(panel)
			bench.pop("release/restore")

			timeline_update()
		end

		editor_updateFrame(panel, 1, (ctx.isAnim and not panel.exporting) and engine.dt or nil)

		editor_releaseFrames(panel)
		editor_restoreFrames(panel)

		editor_updateLayerFrame(panel)

		if panel.exporting then
			local exportPercent = 0
			if panel.exportFormats.separateLayers then
				local progress, total = 0, 0
				for l, layer in ipairs(panel.layers) do
					if layer.visible then
						if panel.exportLayer > l then
							progress = progress + panel.animation.length
						end
						total = total + panel.animation.length
					end
				end
				progress = progress + panel.currentFrame
				exportPercent = progress / total
			else
				exportPercent = panel.currentFrame / panel.animation.length
			end
			editor_tempTip("Exporting: " .. tostring(mu(exportPercent * 100)) .. "%")
		end
	end

	bench.push("post_update")
	if panel.movingSelection then
		trace("moving selection", panel.movingSelection)
	end

	if not panel.exporting then
		timeline_toggleAudio()
		editor_updateButton(panel.titleButton)
		-- editor_animationKeys       (panel)
	end

	timeline_resetHeight()
	editor_centerFrames(panel)

	-- if #panel.layersToDelete>0 then
	--  print("#panel.layersToDelete",#panel.layersToDelete)
	--  for i,v in ipairs(panel.layersToDelete) do
	--    editor_deleteLayer(panel,v)
	--    print("i",i)
	--    print("delete",panel.layersToDelete[i])
	--  end
	-- end

	panel.hideSpeed = 2
	bench.pop("post_update")
	bench.pop("anim_panel")

	if panel.close then
		editor_closeAnimatePanel(panel)
	end
  if panel.reset then 
    panel.reset=false
    camera_reset(ctx.viewport.camera)
  end
end

function editor_poseTools(panel)
	scene_inverseKinematics(panel.l, panel.f.pose, panel)
end

function editor_openAnimatePanel(entity, fromStage, selectedAnim)
	local panel = animatePanel
	local time = os.time()

  
	panel.view = ctx.views[1]
	panel.room = panel.view.room
	panel.new = (entity == "nil")

	-- print(entity.key,entity.symbol,getSymbol(entity.key))
	panel.instance = entity
	panel.root = entity.symbol
	panel.symbol = entity.symbol
	panel.newImage = false
	panel.blankAnim = false
	panel.open = true
	panel.draggingFrame = false
	panel.draggedFrame = 0
	panel.draggedLayer = 0
	panel.playing = false
	panel.brushPrev = { x = 0, y = 0 }
	panel.anim = selectedAnim or panel.symbol.default

	-- print(panel.root,panel.symbol)
	-- print(inspect(panel.root),inspect(panel.symbol))
	panel.symbol.stageWidth = panel.symbol.stageWidth or 1920
	panel.symbol.stageHeight = panel.symbol.stageHeight or 1080

	panel.visible = true
	panel.selection = {}
	panel.frame = 1
	panel.currentFrame = config.currentFrame or 1
	panel.stage = fromStage

	panel.undos = {}
	panel.undoPos = 0
	panel.scrollPos = 0
	panel.y = 9999
	panel.pnframe = 1
	panel.pframe = 1
	panel.savedWidth = sidebar.width

	if ctx.isAnim then
    panel.reset = true
		panel.exportScale = level.exportScale
	end

	-- print("entity.symbol",inspect(entity.symbol))
	entity.adata = entity.symbol.animations[entity.anim]

	-- panel.transformer =
	-- 	scene_newEntity(panel.room, { x = panel.instance.x, y = panel.instance.y, z = panel.instance.z, simple = true })

	editor_activateSidebar()

	gfx.reset()
	gfx.setBlendMode("replace", "premultiplied")

	local oldCanvas
	local rawData

	for key, anim in pairs(panel.symbol.animations) do
		if anim.audio then
			--TODO audio
			anim.audio = false
			-- anim.source = afx.newSource( LINK_DIR.."audio/"..anim.audio, "stream" )
		end
		anim.key = key

		for l, layer in ipairs(anim.layers) do
			layer.id = layer.id or uuid()
			layer.data = layer.data and panel.symbol.models[layer.data.id]

			for f, frame in ipairs(layer.frames) do
				frame.lastDrawn = time
				frame.lastSave = time
				frame.released = true
				frame.symbols = frame.symbols or {}

				if layer.format == "raster" then
					if frame.packedCanvas then
						local framePath = load_imagePath(entity.key)[frame.id]
						if lfs.exists(framePath) then
							frame.framePath = framePath
							print("\n FRAME PATH:", frame.framePath)
						end
					end
				end
			end
		end
	end

	panel.anims = panel.symbol.animations
	panel.loaded = true
	panel.animation = panel.anims[panel.anim]
	panel.layers = panel.animation.layers
	panel.layer = mc(1, panel.layer, #panel.layers)
	panel.l = panel.layers[mc(1, panel.layer, #panel.layers)]
	panel.frames = panel.animation.layers[1].frames
	panel.frame = editor_getKeyFrame(panel, panel.layer, panel.currentFrame)
	print("panel.frame", panel.frame)
	print("panel.frames", #panel.frames)
	panel.f = panel.frames[panel.frame]

	panel.instance.models = {}

	panel.restoreTime = 1

	panel.pushUndoState()

	lume.clear(selectedEntities)

	entity.models[#panel.layers + 1] = nil
	entity.models[#panel.layers + 2] = nil

	panel.open = true

	editor_checkTimelineDocked(panel)
	editor_clearBones(panel)

  editor_switchAnimation(panel.anim)


end

function editor_setAnimationAudio(panel, name)
	if true then
		--TODO audio
		print("NO AUDIO YET")
		return
	end
	print("SET AUDIO ----------------------------")
	if panel.animation.source and (name == panel.animation.audio) then
		return
	end
	print("  CREATING WAVEFORM")
	local waveForm = {}
	panel.animation.audio = name
	panel.animation.source = afx.newSource(LINK_DIR .. "audio/" .. panel.animation.audio)
	panel.animation.soundData = sfx.newSoundData(LINK_DIR .. "audio/" .. panel.animation.audio)
	local count = panel.animation.soundData:getSampleCount()
	local rate = panel.animation.soundData:getSampleRate()
	local secs = count / rate
	local samp
	print("  secs", secs)
	local divisions = 64
	local h = 10
	local step = 1 / divisions
	for i = 0, secs, step do
		local samples = 0
		for j = i * rate, (i + step) * rate, (step / 8) * rate do
			samples = samples + math.abs(panel.animation.soundData:getSample(mf(j + math.random() * 2)))
		end
		samp = samples / 8
		-- print(math.abs(samp ))
		samp = mc(0.01, samp, 0.6)
		-- table.insert(waveForm, {i,0,0,1} )
		table.insert(waveForm, { i / 2, -samp * h, 0, 1, 1, 1, 1, 1, 1, 1 })
		table.insert(waveForm, { i / 2, samp * h, 0, 1, 1, 1, 1, 1, 1, 1 })
		-- table.insert(waveForm, {i,0,0,1} )
	end
	print("  count/rate", count, rate)
	print()
	panel.waveForm = gfx.newMesh(zformat, waveForm, "strip")
end

function editor_centerFrames(panel)
	local d, add, new, mat = vec3()
	if panel.drawing or panel.playing then
		return
	end
	for l, layer in ipairs(panel.layers) do
		if layer.center then
			for f, frame in ipairs(layer.frames) do
				local clip = false
				if (frame.pw and frame.w > frame.pw) or (frame.ph and frame.h > frame.ph) then
					editor_clipFrame(frame)
				end
				frame.pw, frame.ph = frame.w, frame.h
				if math.abs(frame.ox) > 1 or math.abs(frame.oy) > 0 then
					d.x, d.y, d.z = -frame.ox, -frame.oy, 0
					frame.ox, frame.oy = 0, 0
					panel.changed = true
					editor_updateFrameTransformer(panel)
				end
			end
		end
	end
end

function editor_releaseFrames(panel)
	local po = panel.onion

	for l, layer in ipairs(panel.layers) do
		local key = editor_getKeyFrame(panel, l)
		local a = key - ((po.enabled and po.before.enabled) and po.before.amount or 0)
		local b = key + ((po.enabled and po.after.enabled) and po.after.amount or 0)
		local first = wrap2(a, 1, #layer.frames)
		local last = wrap2(b, 1, #layer.frames)
		local wrapped = not (a == first and b == last)
		for f, frame in ipairs(layer.frames) do
			-- print("release",first,f,last,(wrapped and f<first and f>last) or ((f<first or f>last) and not wrapped))
			if frame.l < 1 then
				frame.l = 1
			end
			if (wrapped and f < first and f > last) or ((f < first or f > last) and not wrapped) then
				editor_releaseFrame(frame)
			end
		end
	end
end

function editor_releaseFrame(frame)
	if not frame.released then
		frame.released = true
		-- print("releasing frame...")
		if frame.canvas and gfx.isCanvas(frame.canvas) then
			-- print("  checking canvas...")
			if frame.dirty then
				-- print("  try 2 pack...")
				frame.dirty = false
				frame.packedCanvas = editor_packCanvas(frame.canvas)
			end
			print("\nforce release -----------------------")
			frame.canvas:release()
			print("--------------------------------\n")
			frame.canvas = nil
		end
	end
end

function editor_restoreFrames(panel)
	if panel.restoreTime > 0 then
		panel.restoreTime = panel.restoreTime - 1
		return
	end
	local po = panel.onion
	for l, layer in ipairs(panel.layers) do
		local r = editor_getKeyFrame(panel, l)
		local first = r - ((po.enabled and po.before.enabled) and po.before.amount or 0)
		local last = r + ((po.enabled and po.after.enabled) and po.after.amount or 0)
		-- print("restore:",l,r,first,last)
		for f = first, last, 1 do
			local frame = getLooped(layer.frames, f)
			-- frame.position:set(0,0,math.sin(time)*1000)
			-- trace(frame.position,matvecmul(applyTransform(frame),vec3(1,220,203)))
			editor_restoreFrame(panel, layer, frame)
		end
	end
end

function editor_restoreFrame(panel, layer, frame)
	editor_loadFrame(panel, frame)
	if frame.released then
		frame.released = false
		if layer.format == "raster" then
			if frame.packedCanvas then
				frame.canvas = editor_unpackCanvas(frame.packedCanvas)
			else
				frame.canvas = newCanvas(frame.w, frame.h)
				gfx.push("all")
				gfx.setCanvas(frame.canvas)
				gfx.clear(0, 0, 0, 0)
				gfx.pop()
			end
			-- print("restored frame")
			-- frame.canvas =  gfx.isCanvas(frame.canvas) and frame.canvas or gfx.fromTextureData(frame.canvas)
		end
		if layer.format == "vector" then
			-- editor_refreshStrokes()
			-- scene_genVectorFrameMesh(panel,layer,frame)
		end
	end
end

function editor_loadFrame(panel, frame)
	if frame.framePath then
		local img = not frame.blank and gfx.newImage(frame.framePath)
		if img then
			print("  DRAW LOADED FRAME TO CANVAS")

			local packed = frame.packedCanvas
			packed.canvas = newCanvas(img:getWidth(), img:getHeight())
			local packer = gfx.newImageData(packed.tileStride, packed.tileStride)
			local unpacker = gfx.newImageData(packed.width + 1, packed.height + 1)

			ffi_copy(packer.data.data, packed.packer, #packed.packer)

			for t = 0, packed.tileCount - 1, 1 do
				local x, y = t % packed.tileStride, math.floor(t / packed.tileStride)
				local r, g, b, a = packer:getPixel(x, y)
				unpacker:setPixel(r * 255, g * 255, x / 255, y / 255, 0, 1)
			end

			packed.packer = gfx.newCanvas(packed.tileStride, packed.tileStride)
			packed.packer:replacePixels(packer)

			packed.unpacker = gfx.newCanvas(packed.width + 1, packed.height + 1)
			packed.unpacker:replacePixels(unpacker)

			gfx.push("all")
			gfx.setBlendMode("replace")
			gfx.setCanvas(packed.canvas)
			gfx.clear(0, 0, 0, 0)
			gfx.draw(img)
			gfx.pop()

			-- if panel.debugView and panel.debugCompaction then
			--  img:newImageData():encode("png","img.png")
			packer:encode("png", "packerLoad.png")
			unpacker:encode("png", "unpackerLoad.png")
			--  packed.packer:newImageData():encode("png","packerLoad2.png")
			--  packed.unpacker:newImageData():encode("png","unpackerLoad2.png")
			--  packed.canvas:newImageData():encode("png","packed_canvas.png")
			-- end

			img:release()
		end

		frame.framePath = nil
	end
end

function editor_updateFrame(panel, mult, dt)
	local mult = mult or 1
	if panel.exporting then
		panel.time = panel.time + engine.dt
		panel.currentFrame = panel.currentFrame + 1
	elseif dt then
		panel.time = panel.time + engine.dt
		panel.ticked = panel.currentFrame ~= mu(panel.time * panel.animation.fps)
		panel.currentFrame = mu(panel.time * panel.animation.fps)

		panel.ticked2 = panel.currentFrame2 ~= mu(panel.time * panel.animation.fps / 2)
		panel.currentFrame2 = mu(panel.time * panel.animation.fps / 2)
	else
		panel.tick = panel.tick + (1 / 60) * mult
		panel.time = panel.time + (1 / 60) * mult
		if panel.tick > (1 / panel.animation.fps) then
			panel.tick = panel.tick - (1 / panel.animation.fps)
			panel.currentFrame = panel.currentFrame + 1
		elseif panel.tick < 0 then
			panel.tick = 1 / panel.animation.fps + panel.tick
			panel.currentFrame = panel.currentFrame - 1
		end
	end
	editor_loopAnimation(panel)
end

function editor_loopAnimation(panel)
	if panel.currentFrame > panel.animation.length then
		panel.currentFrame = 1
		panel.time = 0
		panel.scrollPos = 0
		if panel.exporting then
			local finish = true
			if panel.exportFormats.separateLayers then
				if panel.exportLayer < #panel.layers then
					while true do
						panel.exportLayer = panel.exportLayer + 1
						if panel.layers[panel.exportLayer] and panel.layers[panel.exportLayer].visible then
							finish = false
							break
						end
					end
					panel.solo = panel.exportLayer
				end
			end
			if finish then
				editor_playAnimation()
				panel.solo = panel.preExportSolo
				panel.exporting = false
				panel.exportingAlpha = false
			end
		else
			if panel.animation.source then
				panel.animation.source:seek(panel.time, "seconds")
				if panel.playing then
					panel.animation.source:play()
				end
			end
		end
	end
	if panel.currentFrame < 1 then
		panel.currentFrame = panel.animation.length
	end
end

function editor_nilFrameCanvases(panel)
	for i, anim in pairs(panel.anims) do
		for l, layer in ipairs(anim.layers) do
			for f, frame in ipairs(layer.frames) do
				frame.canvas = nil
			end
		end
	end
end

function editor_checkTimelineDocked(panel)
	-- panel.docked = not panel.bottom
	panel.y = panel.bottom and screenH or sidebar.height
	for l, layer in ipairs(panel.layers) do
		if layer.element then
			layer.element.y = panel.y
		end
	end
	panel.bottom = panel.bottom
end

function editor_closeAnimatePanel(panel)
	panel.close = false
	panel.open = false
	panel.undos = nil

	for a, anim in pairs(panel.anims) do
		for l, layer in ipairs(anim.layers) do
			layer.poseMesh = nil
			for f, frame in ipairs(layer.frames) do
				frame.canvas = nil
				frame = nil
			end
		end
	end

	panel.anims = nil
	panel.raw = nil
	panel.brushCanvas = nil
	--panel.instance.models = {}
	--panel.instance.img  = load_imageTex(panel.instance.key)
	panel.instance = nil

	collectgarbage()

	editor.state = "Normal"
	editor.currentTool = "Select"

	editor_deactivateSidebar()
	sidebar.width = panel.savedWidth
	scene_deleteEntity(panel.transformer)
	lume.clear(selectedEntities)
	panel.transformer = nil

	mouse_setVisible(true)
end

function editor_clipFrames(panel)
	for a, anim in pairs(panel.anims) do
		for l, layer in ipairs(anim.layers) do
			for i, f in ipairs(layer.frames) do
				editor_clipFrame(f)
			end
		end
	end
end

function editor_clipFrame(f)
	if not f.blank then
		local canvas, od, fdat = gfx.clipCanvas(f.canvas, true, nil)

		f.canvas = canvas
		f.w = fdat.w
		f.h = fdat.h
		f.ox = f.ox - od.xoff * animatePanel.l.resolution
		f.oy = f.oy - od.yoff * animatePanel.l.resolution

		log(od.xoff, od.yoff)

		animatePanel.changed = true
	end
end

function editor_playAnimation(fps, start)
	local panel = animatePanel
	editor_refreshStrokes()
	panel.preFPS = panel.playing and panel.preFPS or panel.animation.fps
	panel.animation.fps = panel.playing and panel.preFPS or fps or panel.animation.fps
	panel.fpsSlider.pct = math.min((panel.animation.fps - 1) / 59, 1)
	panel.playing = not panel.playing
	panel.playStart = panel.playing and panel.currentFrame or panel.playStart

	panel.currentFrame = panel.playing and (start or panel.currentFrame) or (start or panel.playStart)
	panel.tick = 0
	panel.time = (panel.currentFrame - 1) / panel.animation.fps
	panel.playTrackSpeed = 128

	if panel.animation.source and not panel.exporting then
		if not panel.playing then
			afx.stop()
		else
			print("play from ", panel.time)
			if panel.time < panel.animation.source:getDuration() then
				panel.animation.source:seek(math.max(panel.time, 0), "seconds")
				panel.animation.source:play()
			end
		end
	end
end

function editor_drawFrameSelection(panel)
	panel.transformCursor = nil
	-- print("drawFrameSelection",panel.selection.mesh)
  if not (panel.selection and panel.selection.mesh) then return end
  
  local ires = 1 / panel.l.resolution
  local off

  if panel.selection.position then
  	panel.selection.diff = {
  		x = panel.selection.position.x - panel.f.w / 2,
  		y = panel.selection.position.y - panel.f.h / 2,
  	}
  	off = {
  		x = (-panel.selection.diff.x - panel.f.ox * ires) / ires,
  		y = (-panel.selection.diff.y - panel.f.oy * ires) / ires,
  	}
  else
  	off = {
  		x = -panel.f.ox,
  		y = -panel.f.oy,
  	}
  end

  -- panel.selection.offset = offset

  local offset = applyTransform(
  	nil,
  	vec3(
  		-(panel.selection.bbox and -panel.selection.bbox[1] - panel.selection.bbox.w / 2 or -panel.f.w / 2),
  		-(panel.selection.bbox and -panel.selection.bbox[2] - panel.selection.bbox.h / 2 or -panel.f.h / 2),
  		0
  	)
  ) * applyTransform(
  	nil,
  	nil,
  	nil,
  	panel.selection.scale
  			and vec3((1 / panel.selection.scale.x) * ires, (1 / panel.selection.scale.y) * ires, 1)
  		or vec3(ires)
  ) * mat4.from_quaternion(
  	quat.rotate(panel.selection.position and -(panel.selection.angle or 0) + 2 * pi or 2 * pi, axes.z)
  ) * applyTransform(nil, vec3(off.x, off.y, 0))

  local offset1 = (
  	(panel.justSelected or panel.selection.pasted)
  		and applyTransform(
  			nil,
  			vec3(
  				-(
  						panel.selection.bbox and -panel.selection.bbox[1] - panel.selection.bbox.w / 2
  						or -panel.f.w / 2
  					),
  				-(
  						panel.selection.bbox and -panel.selection.bbox[2] - panel.selection.bbox.h / 2
  						or -panel.f.h / 2
  					),
  				0
  			)
  		)
  	or applyTransform(nil, vec3(0, 0, 0))
  )
  	* applyTransform(
  		nil,
  		nil,
  		nil,
  		panel.selection.scale
  				and vec3((1 / panel.selection.scale.x) * ires, (1 / panel.selection.scale.y) * ires, 1)
  			or vec3(ires)
  	)
  	* mat4.from_quaternion(
  		quat.rotate(panel.selection.position and -(panel.selection.angle or 0) + 2 * pi or 2 * pi, axes.z)
  	)
  	* applyTransform(nil, vec3(off.x, off.y, 0))

  local offset2 =
  	-- applyTransform(nil,vec3(
  	--  -(panel.selection.bbox and -panel.selection.bbox[1]-panel.selection.bbox.w/2 or -panel.f.w/2),
  	--  -(panel.selection.bbox and -panel.selection.bbox[2]-panel.selection.bbox.h/2 or -panel.f.h/2),
  	-- 0))
  	mat4.identity() * applyTransform(
  		nil,
  		nil,
  		nil,
  		panel.selection.scale
  				and vec3((1 / panel.selection.scale.x) * ires, -(1 / panel.selection.scale.y) * ires, 1)
  			or vec3(ires)
  	) * mat4.from_quaternion(
  		quat.rotate(panel.selection.position and -(panel.selection.angle or 0) + 2 * pi or 2 * pi, axes.z)
  	) * applyTransform(nil, vec3(off.x, off.y, 0))

  local frameTransform = applyTransform(panel.f) or mat4()
  panel.selection.transform = offset * frameTransform

  if panel.l.modelIndex then
  	if panel.l.format == "raster" or not panel.cropSelect then
  		table.insert(ctx.viewport.room.models, {
  			stripe = -1,
  			color = { 1, 1, 0, 1 },
  			wireframe = true,
  			noCastShadow = true,
  			mesh = panel.selection.mesh,
  			parent = panel.instance,
  			layer = panel.l.modelIndex,
  			transform = -offset * -frameTransform,
  		})
  	end

  	if panel.selection.strokeMesh then
  		if config.highlightOutline and config.highlightOutline.enabled then
  			table.insert(ctx.viewport.room.models, {
  				push = true,
  				outline = config.highlightOutline.size,
  				wireframe = panel.debugView,
  				color = { 1, 1, 1, 1 },
  				mesh = panel.selection.strokeMesh,
  				parent = panel.instance,
  				sort = panel.layer - 0.4,
  				-- isFrame = true,
  				transform = -offset1, --*-(mat4():translate( frameTransform, vec3(0,0,0)))
  			})
  		end
  		local polka = (not config.polkaDots.onHover or panel.hoveringSelection) and config.polkaDots.enabled
  		table.insert(ctx.viewport.room.models, {
  			push = true,
  			polka = polka and config.polkaDots.amount or 0,
  			stripe = polka and config.polkaDots.speed or 0,
  			wireframe = panel.debugView,
  			color = { 1, 1, 1, 1 },
  			mesh = panel.selection.strokeMesh,
  			parent = panel.instance,
  			sort = panel.l.modelIndex + 0.5,
  			transform = -offset1, --*-(mat4():translate( frameTransform, vec3(0,0,0)))
  		})
  	end
  end

  if panel.selection.bbox and panel.instance then
  	local x, y, w, h =
  		panel.selection.bbox[1], panel.selection.bbox[2], panel.selection.bbox.w, panel.selection.bbox.h

  	if panel.selection.format and panel.selection.format == "raster" and panel.selection.cut then
  		gfx.setTexture(panel.selection.shapeMesh, panel.selection.canvas)
  		table.insert(ctx.viewport.room.models, {
  			noCastShadow = true,
  			mesh = panel.selection.shapeMesh,
  			parent = panel.instance,
  			transform = -offset2,
  		})
  	end

  	local size, ang, dist, c, c2 = math.max(0.1, (1 - panel.l.resolution)) * 5
  	local roff = ires / (vec2.len(panel.selection.scale) / 2)
  	local cp = vec2(x + w / 2, y + h / 2)
  	local pad = 0 --cpml.utils.map(cam.soom,0.8,1.2,0,3)
  	local points = {
  		vec2(x - pad, y - pad),
  		vec2(x + w / 2, y - pad), -- side
  		vec2(x + w + pad, y - pad),
  		vec2(x + w + pad, y + h / 2), -- side
  		vec2(x + w + pad, y + h + pad),
  		vec2(x + w / 2, y + h + pad), -- side
  		vec2(x - pad, y + h + pad),
  		vec2(x - pad, y + h / 2), -- side

  		vec2(x - roff, y - roff),
  		vec2(x + w + roff, y - roff),
  		vec2(x + w + roff, y + h + roff),
  		vec2(x - roff, y + h + roff),
  	}

  	-- local points2 = {
  	-- 	vec2(-(w / 2) * panel.selection.scale.x, -(h / 2) * panel.selection.scale.y),
  	-- 	vec2(x, y), -- side
  	-- 	vec2((w / 2) * panel.selection.scale.x, -(h / 2) * panel.selection.scale.y),
  	-- 	vec2(x + w, y), -- side
  	-- 	vec2((w / 2) * panel.selection.scale.x, (h / 2) * panel.selection.scale.y),
  	-- 	vec2(x + w, y + h), -- side
  	-- 	vec2(-(w / 2) * panel.selection.scale.x, (h / 2) * panel.selection.scale.y),
  	-- 	vec2(x, y + h), -- side

  	-- 	vec2(x - roff, y - roff),
  	-- 	vec2(x + w + roff, y - roff),
  	-- 	vec2(x + w + roff, y + h + roff),
  	-- 	vec2(x - roff, y + h + roff),
  	-- }

  	local p1, p2, p3 =
  		vec2(panel.brushCoords),
  		vec2(panel.brushStart),
  		vec2(panel.selection.offset.x, panel.selection.offset.y)

  	local rot = vec2.angle_to(p3, p1) + pi * 2 - pi

  	local square = 5
  	local point2
    panel.selection.pts = {}
  	local pts = panel.selection.pts
  	local pointMat = -offset * -frameTransform * -panel.instance.transform
  	local prepResize
  	local shape = {}

  	panel.selection.preTransform = false
  	for i = 1, 12, 1 do
  		c = points[i]

  		local point = camera_project(pointMat * vec3(c.x, c.y, 0))
      panel.frameSelectionTransfomToolPoints[i]=point
  		-- trace(point.x,point.y)
  		point = { x = point.x, y = point.y, alpha = 0.7 }

  		if not (contextMenu.open or input_keyDown("space")) then
  			if i < 9 then
  				if (i + 1) % 2 == 0 then
  					table.insert(shape, point.x)
  					table.insert(shape, point.y)
  				end

  				if math.dist(mouse.x, mouse.y, point.x, point.y) < 12 then
  					--SCALE
  					point.alpha = 1
  					local angle = vec2.angle_between(vec2(-1, 0), vec2.rotate(c - cp, panel.selection.angle))
  					local angle2 = vec2.angle_to(cp, c) + panel.selection.angle
  					while angle2 > pi do
  						angle2 = angle2 - 2 * pi
  					end
  					while angle2 < -pi do
  						angle2 = angle2 + 2 * pi
  					end
  					trace("transangles", angle, angle2)
  					local neg = angle2 < 0
  					angle = angle < 0 and -angle or angle
  					local inc = pi / 8

  					if angle > (pi - inc) or angle < inc then
  						panel.transformCursor = cursors.scale_w
  					elseif angle > pi / 2 - inc and angle < pi / 2 + inc then
  						panel.transformCursor = cursors.scale_h
  					elseif angle > inc and angle < pi / 2 - inc then
  						panel.transformCursor = neg and cursors.scale_d1 or cursors.scale_d2
  					elseif angle < pi - inc and angle > pi / 2 + inc then
  						panel.transformCursor = neg and cursors.scale_d2 or cursors.scale_d1
  					end

  					mouse_setCursor(panel.transformCursor)
  					prepResize = true
  					panel.selection.preTransform = "Scale"
  					panel.selection.transPoint = i

  					-- mouse_set(function()
  					--  panel.strokeDiffs = {added={},removed={}}
  					--  if config.unlockMouse then lwindow.setVsync(false) end
  					--  if not panel.selection.cut then
  					--    panel.pushUndoState("Scale")
  					--  end
  					--  panel.selection.dontPaste = false
  					--  editor_cutStrokeSelection(panel)
  					--  panel.pushUndoState("Scale")
  					--    panel.scalingSelection = i
  					--    panel.selection.startScale = lume.clone(panel.selection.scale)
  					--    editor_prepareToolMouse(panel)
  					--    panel.selection.offset = lume.clone(panel.selection.position)
  					--    panel.selection.scalePoint = i
  					--  end)
  				end
  			else
  				--ROTATE
  				if
  					not panel.scalingSelection
  					and not prepResize
  					and math.dist(mouse.x, mouse.y, point.x, point.y) < 48
  					and not panel.mouseInSelectionRect
  				then
  					point.alpha = 1
  					panel.transformCursor = cursors.rotate
  					mouse_setCursor(panel.transformCursor)
  					panel.selection.preTransform = "Rotate"
  					panel.selection.preRot = rot
  					panel.selection.transPoint = i
  					-- mouse_set(function()
  					--  panel.strokeDiffs = {added={},removed={}}
  					--  if config.unlockMouse then lwindow.setVsync(false) end
  					--  if not panel.selection.cut then
  					--    panel.pushUndoState("Rotate")
  					--  end
  					--  panel.selection.dontPaste = false
  					--  editor_cutStrokeSelection(panel)
  					--  panel.pushUndoState("Rotate")
  					--    panel.rotatingSelection = true
  					--    panel.selection.startAngle = panel.selection.angle
  					--    panel.selection.startRot   = panel.selection.preRot
  					--    editor_prepareToolMouse(panel)
  					--  panel.brushStart        = {x=panel.brushCoords.x,y=panel.brushCoords.y}
  					--  panel.selection.scalePoint = i
  					--  end)
  				end
  			end
        pts[i] = vec2(point)
  		end  	
  	end



  	trace("pretrans", panel.selection.preTransform)

  	panel.hoveringSelection = point_in_poly(shape, mouse)

  	if panel.rotatingSelection then
  		mouse_setCursor(panel.transformCursor)
  		panel.selection.angle = panel.selection.startAngle + (rot - panel.selection.startRot)

  		--[[(math.angle(panel.selection.bbox[1]+panel.selection.bbox.w/2,panel.selection.bbox[2]+panel.selection.bbox.h/2,panel.brushStart.x,panel.brushStart.y)
        -math.angle(panel.selection.bbox[1]+panel.selection.bbox.w/2,panel.selection.bbox[2]+panel.selection.bbox.h/2,panel.brushCoords.x,panel.brushCoords.y)
      )]]

  		game.angleLine = { p1.x, p1.y, p3.x, p3.y, p2.x, p2.y }

  		-- editor_refreshStrokes()
  		mouse_pushRelease(function()
  			panel.rotatingSelection = false
  		end)
  	elseif panel.scalingSelection then
  		for i = 1, 3 do
  			mouse_setCursor(panel.transformCursor)
  			if shiftDown or #panel.selection.symbols > 0 then
  				local d = math.dist(
  					panel.selection.position.x,
  					panel.selection.position.y,
  					panel.brushCoords.x,
  					panel.brushCoords.y
  				) / (panel.selection.bbox.w / 2)

  				panel.selection.scale.x, panel.selection.scale.y = d, d
  			else
  				local old = vec2(
  					panel.selection.position.x - panel.brushCoords.x,
  					panel.selection.position.y - panel.brushCoords.y
  				)

  				local old2 = panel.brushPrev
  						and vec2(
  							panel.selection.position.x - panel.brushPrev.x,
  							panel.selection.position.y - panel.brushPrev.y
  						)
  					or old
  				-- local ang = math.angle(0,0,points2[panel.selection.scalePoint].x,points2[panel.selection.scalePoint].y)
  				local mid = vec2.rotate(old, -panel.selection.angle)
  				local mid2 = vec2.rotate(old2, -panel.selection.angle)
  				local pscale = vec2(panel.selection.scale)
  				if panel.scalingSelection % 2 ~= 0 then
  					if panel.scalingSelection == 1 then
  						panel.selection.scale.x = (mid.x / panel.selection.bbox.w * 2)
  						panel.selection.scale.y = (mid.y / panel.selection.bbox.h * 2)
  					end
  					if panel.scalingSelection == 3 then
  						panel.selection.scale.x = -(mid.x / panel.selection.bbox.w * 2)
  						panel.selection.scale.y = (mid.y / panel.selection.bbox.h * 2)
  					end
  					if panel.scalingSelection == 5 then
  						panel.selection.scale.x = -(mid.x / panel.selection.bbox.w * 2)
  						panel.selection.scale.y = -(mid.y / panel.selection.bbox.h * 2)
  					end
  					if panel.scalingSelection == 7 then
  						panel.selection.scale.x = (mid.x / panel.selection.bbox.w * 2)
  						panel.selection.scale.y = -(mid.y / panel.selection.bbox.h * 2)
  					end
  				else
  					if panel.scalingSelection % 4 == 0 then
  						local sign = panel.scalingSelection == 4 and -1 or 1
  						-- print("sign",sign)
  						panel.selection.scale.x = altDown
  								and panel.selection.scale.x + sign * (mid - mid2).x / panel.selection.bbox.w
  							or sign * (mid.x / panel.selection.bbox.w * 2)
  						mid.y, mid2.y = 0, 0
  					elseif (panel.scalingSelection + 2) % 4 == 0 then
  						local sign = panel.scalingSelection == 6 and -1 or 1
  						-- print("sign",sign)
  						panel.selection.scale.y = altDown
  								and panel.selection.scale.y + sign * (mid - mid2).y / panel.selection.bbox.h
  							or sign * (mid.y / panel.selection.bbox.h * 2)
  						mid.x, mid2.x = 0, 0
  					end
  				end
  				trace("scale:", panel.selection.scale.x, panel.selection.scale.y)
  				if altDown then
  					panel.selection.position.x, panel.selection.position.y = (
  						vec2(panel.selection.position)
  						- vec2.rotate(mid - mid2, panel.selection.angle)
  							/ 2
  					):unpack()
  				end
  			end
  			panel.brushPrev = deepcopy(panel.brushCoords)
  			-- editor_refreshStrokes()
  			mouse_pushRelease(function()
  				panel.scalingSelection = false
  			end)
  		end
  	else
  		if panel.hoveringSelection then
  			panel.selection.preTransform = "Move"
  			panel.transformCursor = cursors.move
  			mouse_setCursor(panel.transformCursor)
  		end
  	end

  	panel.mouseInSelectionRect = point_in_poly({
  		pts[1].x,
  		pts[1].y,
  		pts[3].x,
  		pts[3].y,
  		pts[5].x,
  		pts[5].y,
  		pts[7].x,
  		pts[7].y,
  	}, mouse)
  end
end

function editor_updateLayerFrame(panel)
	panel.drawing = panel.tool ~= "erase" and panel.clickedCanvas

	local lengthMark = 1

	for l, layer in ipairs(panel.layers) do
		layerLength(layer)
		lengthMark = layer.length > lengthMark and layer.length or lengthMark
		if (layer.visible and panel.solo == 0) or panel.solo == l then
			local frameMark = editor_getKeyFrame(
				panel,
				l,
				layer.loop and (panel.currentFrame - 1) % layer.length + 1 or panel.currentFrame
			)
			layer.frame = frameMark
		end
	end

	panel.animation.length = lengthMark
end

function editor_clearBones(panel)
	if panel.l and panel.l.bones then
		for b, bone in pairs(panel.l.bones) do
			scene_deleteEntity(bone)
		end
		panel.l.bones = nil
		panel.l.chains = nil
		panel.l.poseMesh = nil
	end
end

function editor_updateFrameTransform(panel)
	if not panel.transformer then
		panel.transformer = scene_newEntity(
			panel.room,
			{ x = panel.instance.x, y = panel.instance.y, z = panel.instance.z, simple = true }
		)
		panel.changed = true
	end

	if panel.clearingFrame then
		panel.transformer.position = vec3(0)
		panel.transformer.orientation = defaultOrientation
		panel.transformer.scale = vec3(1)
		panel.transformer.transform = mat4()
		panel.changed = true
	end

	panel.transformer.visible = false

	if panel.changed or panel.undid > 0 then
		editor_updateFrameTransformer(panel)
	end

	if panel.transformer then
		if editor.transforming then
			applyTransform(
				panel.f,
				panel.instance.orientation
					* (panel.transformer[editor.gizmo.dragging and "nextPosition" or "position"] - panel.instance.position)
					* panel.instance.scale,
				panel.transformer[editor.gizmo.rotating and "nextOrientation" or "orientation"]
					* panel.instance.orientation:inverse(),
				panel.transformer[editor.gizmo.scaling and "nextScale" or "scale"]
					* vec3(1 / panel.instance.scale.x, 1 / panel.instance.scale.y, 1 / panel.instance.scale.z)
			)
		else
			if panel.undid > 0 then
				panel.undid = 0
				--temp
				--panel.f.position = panel.instance.orientation * ((panel.transformer.position-panel.instance.position)  )
				--panel.f.orientation = panel.transformer.orientation*panel.instance.orientation:inverse()
				--panel.f.scale = panel.transformer.scale*panel.instance.scale
				--applyTransform(panel.f)
			end
		end

		if panel.clearingFrame then
			panel.f.position = vec3(0)
			panel.f.orientation = defaultOrientation()
			panel.f.scale = vec3(1)
			panel.clearingFrame = false
		end
	end
end

function editor_applyFrameTransform()
	animatePanel.f.position = animatePanel.instance.orientation
		* (animatePanel.transformer.position - animatePanel.instance.position)
		* animatePanel.instance.scale
	animatePanel.f.orientation = animatePanel.transformer.orientation * animatePanel.instance.orientation:inverse()
	animatePanel.f.scale = animatePanel.transformer.scale
		* vec3(1 / animatePanel.instance.scale.x, 1 / animatePanel.instance.scale.y, 1 / animatePanel.instance.scale.z)
	applyTransform(animatePanel.f)
end

function editor_updateFrameTransformer(panel)
	if not panel.instance then
		return
	end
	if not panel.transformer then
		-- panel.transformer =
		-- 	scene_newEntity({ x = panel.instance.x, y = panel.instance.y, z = panel.instance.z, simple = true })
		panel.transformer.visible = true
	end
	if not panel.f.transform then
		return
	end
	if not panel.instance then
		if panel.transformer then
			scene_setDelete(panel.transformer)
		end
		return
	end
	local pos = -panel.f.transform * -panel.instance.transform * vec3(0)
	if panel.transformer then
		panel.transformer.position = -pos
		panel.transformer.orientation = (panel.f.orientation * panel.instance.orientation)
		panel.transformer.scale = panel.f.scale * panel.instance.scale
	end
	applyTransform(panel.transformer)
end

function editor_addBlankFrame(panel, length, prepend)
	local newFrame = editor_newFrame(panel, panel.f)
	timeline_refresh()
	local diff = panel.currentFrame - editor_getAbsoluteFrame(panel)
	print("panel.currentFrame", panel.currentFrame)
	print("digfff", diff)
	local frame = panel.frames[panel.frame]
	if prepend then
		newFrame.l = 1
		table.insert(panel.frames, 1, newFrame)
		frame.l = frame.l - 1
	else
		if diff > 0 then
			if diff > frame.l then
				newFrame.l = 1
			else
				newFrame.l = frame.l - diff
			end
			panel.pushUndoData({ stretchedFrame = frame, preLength = frame.l, postLength = diff })
			frame.l = diff
			frame.lastDrawn = os.time()
			print("newFrame.l", newFrame.l)
		else
			newFrame.l = length or newFrame.l
		end
		table.insert(panel.frames, panel.frame + 1, newFrame)
	end
	panel.frame = panel.frame + 1
	applyTransform(frame)
	editor_setAbsoluteFrame(panel)

	editor_tempTip("New Frame")
end

function editor_clampKeys(panel)
	panel.frame = (panel.frame > #panel.frames) and 1 or (panel.frame < 1 and #panel.frames or panel.frame)
end

function editor_getKeyFrame(panel, layer, currentFrame)
	local layer = layer or panel.layer
	if type(layer) == "number" then
		layer = panel.layers[layer]
	end
	local currentFrame = currentFrame or panel.currentFrame
	layerLength(layer)
	currentFrame = layer.loop and (panel.currentFrame - 1) % layer.length + 1 or currentFrame
	local frameCount = 1
	local frameMark = 1
	for f, frame in ipairs(layer.frames) do
		if not layer.loop and layer.length and currentFrame > layer.length then
			frameMark = #layer.frames
		elseif (frameCount + frame.l >= currentFrame and frameCount <= currentFrame) or frameCount == currentFrame then
			frameMark = f
		end
		frameCount = frameCount + frame.l
	end
	return frameMark
end

function editor_setKeyFrame(panel)
	editor_clampKeys(panel)
	panel.frame = editor_getKeyFrame(panel)
end

function editor_getAbsoluteFrame(panel, layer, keyFrame)
	local layer = layer or panel.layer
	local keyFrame = keyFrame or panel.frame
	local absFrame = 1
	for f, frame in ipairs(panel.layers[layer].frames) do
		if f < keyFrame then
			absFrame = absFrame + frame.l
		else
			frame.abs = absFrame
			return absFrame
		end
	end
end

function editor_setAbsoluteFrame(panel)
	editor_clampKeys(panel)
	panel.currentFrame = editor_getAbsoluteFrame(panel)
	editor_updateFrameTransformer(panel)
	panel.changed = true
	if panel.currentFrame == 1 or panel.currentFrame == panel.l.length then
		panel.skipTrack = true
	end
end

function editor_exportAnim(panel)
	-- LOCK EVERYTHING TILL PLAYBACK DONE
	if not panel.triggerExport then
		return
	end
	panel.triggerExport = false
	panel.preExportSolo = panel.solo
	panel.exporting = true
	panel.exportLayer = 1
	panel.currentFrame = 1
	panel.time = 0
	panel.playing = false
	panel.expFPS = panel.animation.fps
	panel.exportBufferIndex = 1
	editor_playAnimation(60)
	panel.sequenceFormat = panel.exportFormats.sequence == "png" and "png" or "tga"

	-- SET TARGET CANVAS TO STAGE SIZE
	view = ctx.views[1]

	view.canvas = newCanvas(
		panel.root.stageWidth,
		panel.root.stageHeight,
		{ msaa = panel.antiAlias and panel.antiAliasing or nil }
	)
	view.canvas:setFilter(panel.antiAlias and "linear" or "nearest")
	-- screenWidth, screenHeight = panel.root.stageWidth ,panel.root.stageHeight
	view.bounds.w, view.bounds.h = panel.root.stageWidth, panel.root.stageHeight
	panel.stageAspect = panel.root.stageWidth / panel.root.stageHeight
	panel.stageScale = view.bounds.w / animatePanel.root.stageWidth

	if panel.exportFormats.transparent or panel.exportFormats.separateLayers then
		panel.exportingAlpha = true
	end

	panel.exportBuffers = {}
	for b = 1, panel.exportBufferCount, 1 do
		panel.exportBuffers[b] = newCanvas(
			roundToNearest(panel.root.stageWidth * panel.exportScale, 2),
			roundToNearest(panel.root.stageHeight * panel.exportScale, 2)
		)
	end

	local base = ""
	if ctx.zip then
		lfs.mkdir(base .. "/export")
		lfs.remove(base .. "/export/sequence")
	else
		os.execute(os.rm .. ' "' .. GAME_DIR .. '/export/sequence/"')
	end

	lwindow.setVsync(false)

	lfs.mkdir((ctx.zip and base or GAME_DIR) .. "/export/sequence")

	if panel.exportFormats.separateLayers then
		for l, layer in ipairs(panel.layers) do
			lfs.mkdir((ctx.zip and base or GAME_DIR) .. "/export/sequence/" .. layer.name)
		end
	end
	panel.precam = { cam.pos.x, cam.pos.y, cam.soom }

	panel.exportStartTime = engine_getTime()

	engine_waitFor(function()
		if not panel.exporting then
			print("export time", engine_getTime() - panel.exportStartTime)

			view.canvas = newCanvas(renderW, renderH, { msaa = panel.antiAlias and panel.antiAliasing or nil })

			local fps = panel.expFPS or panel.animation.fps

			local out = string
				.format(
					"%s/%s - %s",
					config.projects[engine.project].dir,
					config.projects[engine.project].name,
					panel.animation.name
				)
				:gsub("//", "/")

			local outt = panel.exportFormats.mp4 and out
				or string
					.format(
						"%s/.%s - %s",
						config.projects[engine.project].dir,
						config.projects[engine.project].name,
						panel.animation.name
					)
					:gsub("//", "/")

			local ffpath
			if lfs.isFused() then
				if ffi.os == "Windows" then
					local start, finish = args[0]:find("[%w%s!-={-|]+[_%.].+")
					ffpath = args[0]:sub(1, start - 1) .. "ffmpeg "
					print("ffpath", ffpath)
				-- ffpath = args[0]:sub(1,#args[0]-baselength).."ffmpeg "
				else
					if args[0]:sub(1, 1) == "/" then
						ffpath = args[0]:sub(1, #args[0] - 6) .. "ffmpeg "
					end
				end
			end
			if not ffpath then
				print("use system ffmpeg")
				ffpath = ffi.os == "Windows" and "ffmpeg.exe" or "ffmpeg "
			end
			local ffmpeg = "cd " .. quote(SAVE_DIR .. "export") .. " && " .. ffpath
			if panel.exportFormats.gif or panel.exportFormats.mp4 then
				local strink = ffmpeg
					.. string.format(
						"-y -r %i -f image2 -s %ix%i ",
						fps,
						roundToNearest(panel.root.stageWidth, 2),
						roundToNearest(panel.root.stageHeight, 2)
					)
					.. "-i sequence/frame_%04d."
					.. panel.sequenceFormat
					.. " "
					.. (panel.animation.audio and '-i "' .. PROJ_DIR .. "audio/" .. panel.animation.audio .. '" -acodec aac' or "")
					.. " -t "
					.. tostring(round(panel.animation.length / fps, 2) - 0.01)
					.. " "
					.. " -vcodec libx264 -crf 1"
					.. " -pix_fmt yuv420p "
					.. '"'
					.. outt
					.. '.mp4"'
				print(strink)
				os.execute(strink)
			end

			if panel.exportFormats.gif then
				os.execute(ffmpeg .. '-y -i "' .. outt .. '.mp4" -filter_complex "[0:v] palettegen" palette.png')
				os.execute(
					ffmpeg
						.. '-y -i "'
						.. outt
						.. '.mp4" -i palette.png -filter_complex "[0:v][1:v] paletteuse" "'
						.. out
						.. '.gif"'
				)
				if not panel.exportFormats.mp4 then
					print(os.execute(os.rm .. quote(outt .. ".mp4")))
				end
			end

			if panel.exportFormats.sequence ~= "none" then
				local from = SAVE_DIR:gsub("//", "/") .. "export/sequence"
				print(from, out)
				print(lfs.remove(out,true))
				print(lfs.move(from, out))
			end

			cam.pos.z = -145
			cam.pos.x, cam.pos.y, cam.soom = unpack(panel.precam)
			lwindow.setVsync(true)
			input_unlockMouse()
			return true
		end
		return false
	end)
end

function editor_renameAnimation(key, anim)
	namer_typeString("Rename Cut", animatePanel.anims[anim].name, { x = mouse.x, y = mouse.y }, function(x)
		for i, v in ipairs(contextMenu.items) do
			if v[2] then
				if v[2].name == animatePanel.anims[anim].name then
					contextMenu.items[i][2].name = x
				end
			end
		end
		animatePanel.anims[anim].name = x

		if load_getSymbol(key).animations[anim] then
			load_getSymbol(key).animations[anim].name = x
		end
	end)
end

function editor_deleteAnimation(obj, anim)
	panel.pushUndoState("Remove Cut")
	panel.anims[anim] = nil
	panel.pushUndoData({ animsAfter = lume.clone(panel.anims) })
	-- editor_saveGraphics(animatePanel)
end

function editor_switchAnimation(anim)
	local panel = animatePanel
	if anim ~= panel.animation.key then
  	panel.pushUndoState("Switch Cut")

  	panel.releaseLayer = 1
  	panel.releaseCount = 1
  	editor_clearBones(panel)
  	panel.changed = true
  	panel.anim = anim
  	panel.frame = 1
  	panel.currentFrame = 1

  	panel.animation = panel.anims[panel.anim]

  	panel.layers = panel.animation.layers
  	panel.layer = mc(1, panel.layer, #panel.layers)
  	panel.l = panel.layers[panel.layer]
  	panel.frames = panel.animation.layers[1].frames
	end
end

function editor_changeLayer(panel, l)
	local l = mc(1, l, #panel.layers)
	local changed = l ~= panel.layer
	if changed then
		timeline_refresh()
	end
	panel.layer = l

	panel.l = panel.layers[l]
	panel.currentFrame = mc(1, panel.currentFrame, panel.l.length)
	panel.frames = panel.l.frames
	panel.frame = editor_getKeyFrame(panel, panel.layer, panel.currentFrame)
	for l, layer in ipairs(panel.layers) do
		layer.selected = false
	end
	panel.refreshEntity = true
	panel.l.selected = true
	if panel.solo > 0 and panel.layer == l then
		panel.solo = l
	end
	panel.register()
	return changed
end

function editor_newLayer(panel, insert, format, type)
	local format = format or panel.l.format
	local insert = insert or #panel.layers
	editor_tempTip("New Layer")
	panel.pushUndoState("Add Layer")

	table.insert(panel.layers, insert, {
		name = "Layer " .. tostring(#panel.layers + 1),
		visible = true,
		type = (format == "camera" or format == "model") and format or "drawing",
		resolution = panel.l.resolution,
		format = format,
		id = uuid(),
		frames = {},
		clips = {},
		autoExpand = not panel.limitToStageSize,
		alpha = 1,
		lineWidth = 1,
		wobble = 0,
		length = panel.currentFrame,
		line = { color = { r = 0, g = 0, b = 0, h = 0, s = 0, v = 0, a = 1 }, width = 1 },
		fill = { color = { r = 0, g = 0, b = 0, h = 0, s = 0, v = 0, a = 1 } },
	})

	editor_changeLayer(panel, insert)
	panel.frame = 1

	if format == "model" then
		panel.l.format = false
		editor_createNewModelLayer()
	end

	panel.l.frames[1] = editor_newFrame(panel, { l = panel.currentFrame, canvas = format == "raster" })
	local h = (config.compactLayers and 11 or panel.row)
	panel.y = panel.y - h
	panel.height = panel.height + h

	timeline_refresh()
	panel.register()
end

function editor_deleteSelectedLayers(panel)
	editor_tempTip(
		panel.numLayersSelected > 1 and ("Delete " .. tostring(panel.numLayersSelected) .. " Layers") or "Delete Layer"
	)
	panel.pushUndoState("Delete Layer")
	for l, layer in lume.ripairs(panel.layers) do
		if layer.selected then
			print("delete layer ", l)
			editor_deleteLayer(panel, l)
		end
	end
	editor_changeLayer(panel, panel.layer)
	panel.register()
end

function editor_deleteLayer(panel, layer)
	if #panel.layers == 1 then
		return
	end
	local l = layer or panel.layer
	print("delete layer", l, #panel.layers)
	table.remove(panel.layers, l)
	print("  deleted layer", l, #panel.layers)
	panel.y = panel.y + panel.row + 7
	panel.ty = panel.ty + panel.row + 7
	timeline_refresh()
	-- panel.height=panel.height-panel.row
	-- panel.savedHeight=panel.savedHeight-panel.row
	panel.hideSpeed = 1
end

local function newAnimation(name, duplicate, background)
	local panel = animatePanel
	local animID = uuid()
	local newAnim

	if duplicate then
		newAnim = deepcopy(panel.animation)
		newAnim.key = animID
		newAnim.name = name
	else
		newAnim = {
			key = animID,
			name = name,
			fps = 24,
			length = 1,
			color = { r = 1, g = 1, b = 1, a = 1 },
			layers = {
				{
					id = uuid(),
					clips = {},
					lineWidth = 1,
					visible = true,
					wobble = 0,
					alpha = 1,
					resolution = editor.drawingResolution,
					autoExpand = not panel.limitToStageSize,
					format = "vector",
					type = "drawing",
					loop = false,
					length = 1,
					name = "Layer 1",
					line = { color = { r = 0, g = 0, b = 0, h = 0, s = 0, v = 0, a = 1 }, width = 1 },
					fill = { color = { r = 0, g = 0, b = 0, h = 0, s = 0, v = 0, a = 1 } },
					frames = { editor_newFrame(panel) },
				},
			},
		}
	end

	panel.anims[animID] = newAnim

	if not background then
		panel.anim = animID
		panel.layer = 1
		panel.frame = 1
		panel.currentFrame = 1
		panel.animation = panel.anims[animID]
		panel.frames = panel.anims[animID].layers[1].frames
	end

	return panel.anims[animID]
end

function editor_newAnimation(entity, duplicate)
	-- namer_typeString("new animation","Cut "..(lume.count(animatePanel.anims)+1),entity and entity.render or {x=mouse.x,y=mouse.y},function(name)
	--  if not animatePanel.open and entity then
	--    editor_openAnimatePanel(entity, true)
	--  end
	--  animatePanel.pushUndoState()
	--  newAnimation(name,duplicate)
	-- end,nil,true)
	if not animatePanel.open and entity then
		editor_openAnimatePanel(entity, true)
	end
	animatePanel.pushUndoState("Add Cut")
	newAnimation("Cut " .. (lume.count(animatePanel.anims) + 1))
	animatePanel.pushUndoData({ animsAfter = lume.clone(animatePanel.anims) })
end

function editor_createAnimation(key, size)
	-- position camera
	game_enterRoom()
	local save = false

	if #ctx.viewport.room.instances == 0 then
		editor.mouseLocalDrop = { 0, 0, 0 }
		local entity = editor_drawNewImage("camera x")
		local symbol = load_getSymbol(entity.key)
		level.animEnt = entity.uid
		symbol.animations.default.name = "Cut 1"
		symbol.stageWidth = engine.splash.sizeSelected.width
		symbol.stageHeight = engine.splash.sizeSelected.height
		level.temp = 2
		save = true
	end

	engine_waitFor(function()
		local entity = scene_getEntityByID(level.animEnt)
		local symbol = load_getSymbol(entity.key)
		print("symbol", entity.key, inspect(symbol.animations))
		if symbol.animations then
			symbol.splitFrames = true
			symbol.stageWidth = symbol.stageWidth or level.stageWidth
			symbol.stageHeight = symbol.stageHeight or level.stageHeight
			level.exportScale = level.exportScale or 1

			editor_openAnimatePanel(entity)

			animatePanel.fullWidth = true

			if save then
				save_animation(animatePanel)
				save_project()
			end

			camera_reset(ctx.viewport.camera, true)
			ctx.viewport.camera.soom = 1
			editor.debug.trace_mouse_intersect = false
			return true
		end
	end)
end

function editor_contextAnims(entity)
	local anims = entity.symbol.animations
	local subitems = {}
	local reset = function()
		entity.models = {}
		log("reset")
	end
	for a, anim in pairs(anims) do
		table.insert(subitems, {
			{
				a = a,
				size = 1,
				name = "",
				rollOn = reset,
				rollOff = reset,
				align = "center",
				func = function()
					entity.default = (entity.default ~= a) and a or nil
					entity.models = {}
					return ""
				end,
				update = function(x)
					local default = ((entity.default or entity.symbol.default) == x.a)
					x.col = (default and entity.default) and { r = 0.4, g = 0.4, b = 0.4, a = 0.4 } or {}
					x.name = default and "»" or "•"
				end,
				hover = function()
					editor.animHovered = { key = a, object = entity.uid }
				end,
			},
			{
				name = anim.name,
				rollOn = reset,
				rollOff = reset,
				size = 8,
				func = function()
					editor_openAnimatePanel(entity, true, a)
				end,
				hover = function()
					editor.animHovered = { key = a, object = entity.uid }
				end,
			},
		})
	end
	table.insert(subitems, {
		name = "New " .. (ctx.isAnim and "Cut" or "Animation"),
		col = { r = 0.3, g = 0.8, b = 0.4, a = 0.2 },
		func = function()
			editor_newAnimation(entity)
		end,
	})
	return subitems
end

local function cleanupFrame(frame)
	if PRINT_DESTRUCTORS then
		print("destroying frame")
	end
	-- if #frame.strokes>0 then
	--  print("  destroying strokes")
	--  for s=1,#frame.strokes,1 do
	--    cleanupStroke(frame.strokes[s])
	--  end
	-- end
end

function editor_newFrame(panel, frame)
	local w, h = panel.defaultSize, panel.defaultSize

	if not panel.l.autoExpand then
		w, h = math.ceil(panel.symbol.stageWidth / 32) * 32, math.ceil(panel.symbol.stageHeight / 32) * 32
	end

	local frame = {
		blank = true,
		w = w,
		h = h,
		canvas = frame and frame.canvas,
		isKey = frame and frame.isKey,
		id = newFrameID(),
		tween = frame and frame.tween and lume.clone(frame.tween), -- or {enabled=false,easeIn=0,easeOut=0}),
		ox = 0, --frame and frame.ox or  0,
		oy = 0, --frame and frame.oy or  0,
		l = frame and frame.l or panel.f.l,
		strokes = {},
		symbols = {},
	}

	if frame.canvas then
    print("new frame new canvas")
		frame.canvas = newCanvas(w, h)
		gfx.push("all")
		gfx.setCanvas(frame.canvas)
		gfx.clear(0, 0, 0, 0)
		gfx.pop()
	else
		setGC(frame, cleanupFrame)
	end

	applyTransform(frame)
	return frame
end

function editor_wipeFrame(panel, frame)
  
	local w, h = panel.defaultSize, panel.defaultSize

	if not panel.l.autoExpand then
		w, h = math.ceil(panel.symbol.stageWidth / 32) * 32, math.ceil(panel.symbol.stageHeight / 32) * 32
	end

	local sizeChanged = frame.w ~= w or frame.h ~= h

	frame.blank = true
	frame.w = w
	frame.h = h
	frame.isKey = frame and frame.isKey
	frame.id = frame.id
	frame.tween = frame and frame.tween and lume.clone(frame.tween)
	frame.symbols = {}
	frame.ox = 0
	frame.oy = 0
	frame.strokes = frame.strokes and {}

	if frame.canvas then
		if sizeChanged then
			frame.canvas = newCanvas(w, h)
		end
		frame.packedCanvas = nil
		gfx.push("all")
		gfx.setCanvas(frame.canvas)
		gfx.clear(0, 0, 0, 0)
		gfx.pop()
	end

	applyTransform(frame)
	return frame
end

function editor_copyFrame(panel, frame)
	local copy = editor_newFrame(panel, frame)
	copy.w = frame.w
	copy.h = frame.h
	if not frame.blank then
		if frame.canvas then
			copy.canvas = gfx.copyCanvas(frame.canvas)
			if not frame.packedCanvas then
				frame.packedCanvas = editor_packCanvas(frame.canvas)
			end
			local packed = frame.packedCanvas
			copy.packedCanvas = {
				canvas = gfx.copyCanvas(packed.canvas),
				packer = gfx.copyCanvas(packed.packer),
				unpacker = gfx.copyCanvas(packed.unpacker),
				tileSize = packed.tileSize,
				tileStride = packed.tileStride,
				tileCount = packed.tileCount,
				width = packed.width,
				height = packed.height,
			}
		else
			copy.strokes = copyStrokes(frame.strokes or {})
		end
		if #frame.symbols > 0 then
			for s, symbol in ipairs(frame.symbols) do
				table.insert(copy.symbols, lume.clone(symbol))
			end
		end
	end
	copy.blank = frame.blank
	copy.id = newFrameID()
	editor_refreshStrokes()
	return copy
end

function editor_setPenGroup(group)
	if shiftDown then
		for s, stroke in ipairs(animatePanel.f.strokes) do
			if stroke.group == animatePanel.pen.group then
				stroke.group = group
			end
		end
		editor_refreshStrokes()
	else
		-- animatePanel.tool = "brush"
		animatePanel.pen.group = group
	end
	editor_tempTip((shiftDown and "Convert to: " or "Pen Mode: ") .. capFirst(group))
end

function editor_switchDrawingTool(panel, tool)
	if not (input_keyPressed("z") or input_keyPressed("x") or input_keyPressed("c")) then
		editor_tempTip("Tool: " .. capFirst(tool))
	end
	panel.tool = tool
	if tool == "pen" then
		panel.tool = "brush"
		panel.brush.paint = false
		cursors.circle = nil
	elseif tool == "brush" then
		panel.brush.paint = true
		cursors.circle = nil
	elseif tool == "fill" then
		if animatePanel.l.format == "vector" then
			animatePanel.tool = "fill"
			fill_prepareFill(animatePanel)
		end
	elseif tool == "erase" then
		fill_prepareSegments(animatePanel)
	end
	if animatePanel.l.format == "vector" then
		editor_updateStrokeBounds(animatePanel)
	end
  editor_closeContextMenu()
end

function editor_selectTool(panel)
	if animatePanel.clickedCanvas or shiftDown or cmdDown or mouse_getRelativeMode() then
		return
	end
	for t, tool in ipairs(panel.toolShortcuts) do
		if input_keyPressed(tool[1]) then
			local pretool = panel.tool
			if tool[2] == "move" and panel.tool == tool[2] then
			end
			editor_switchDrawingTool(panel, tool[2])
			if panel.tool ~= pretool and contextMenu.open then
				editor_closeContextMenu()
			end
		end
	end
end

function editor_applyBrushJitter(panel, tip)
	-- for k,v in pairs(panel.sliders) do
	--  if v.stroke == tip then
	--    panel.brush[k] = lerp( v.min, v.max, mc(0,v.pct-(v.pct*math.random()*v.jitter),1))
	--  end
	-- end
	local color = {
		h = (panel.colorJitter.hue.stroke == tip)
				and panel.color.h + (math.random() - 0.5) * panel.colorJitter.hue.jitter
			or panel.color.h,
		s = (panel.colorJitter.saturation.stroke == tip)
				and panel.color.s + (math.random() - 0.5) * panel.colorJitter.saturation.jitter
			or panel.color.s,
		v = (panel.colorJitter.value.stroke == tip)
				and panel.color.v + (math.random() - 0.5) * panel.colorJitter.value.jitter
			or panel.color.v,
		a = panel.color.a,
	}

	color.r, color.g, color.b, color.a = hsvToRgb(color.h, color.s, color.v, color.a)
	-- color.a = panel.brush.opacity
	return color
end

function editor_saveBrush(panel) end

function editor_pushBrush(panel)
	local savedBrush = {
		brush = panel.brush,
		sliders = panel.sliders,
		colorJitter = panel.colorJitter,
	}
	table.insert(panel.recentBrushes, 1, savedBrush)
	panel.recentBrushes = lume.slice(panel.recentBrushes, 1, 10)
	return savedBrush
end

function editor_getBrushSize(panel)
	return panel.tool == "erase" and panel.eraser.size or (panel.brush.paint and panel.brush.size or panel.pen.size)
end

function editor_getBrushGroup(panel)
	return panel.brush.paint and "line" or panel.pen.group
end

function editor_showRecentColors(panel)
	if lume.count(panel.recentColors or {}) == 0 then
		return nil
	end
	local colors = {}
	for i = 1, 9, 1 do
		if panel.recentColors[i] then
			table.insert(colors, {
				col = panel.recentColors[i],
				color = panel.recentColors[i],
				img = "",
				checkers = true,
				func = function(x)
					panel.color.h = panel.recentColors[i].h
					panel.color.s = panel.recentColors[i].s
					panel.color.v = panel.recentColors[i].v
					panel.color.r = panel.recentColors[i].r
					panel.color.g = panel.recentColors[i].g
					panel.color.b = panel.recentColors[i].b
					panel.color.a = panel.recentColors[i].a
					return ""
				end,
				update = function(x)
					x.col.a = (x.hovered and 1 or x.color.a)
				end,
			})
		else
			table.insert(colors, { size = -1 })
		end
	end
	return colors
end

function editor_contextPalettes()
	return nil
end

function editor_pushColor(panel, color)
	local color = color or panel.color
	local col = {
		h = color.h,
		s = color.s,
		v = color.v,
		r = color.r,
		g = color.g,
		b = color.b,
		a = color.a,
	}
	panel.recentColors = panel.recentColors or {}
	for c, col2 in ipairs(panel.recentColors) do
		if math.abs(col.r - col2.r) < 0.01 and math.abs(col.g - col2.g) < 0.01 and math.abs(col.b - col2.b) < 0.01 then
			table.remove(panel.recentColors, c)
		end
	end
	table.insert(panel.recentColors, 1, col)
	if panel.isPanel then
		panel.recentColors = lume.slice(panel.recentColors, 1, 25)
	end
	panel.pushedColor = true
	return col
end

function editor_changeBrushSize(panel)
	-- SIZE CHANGE
	if not shiftDown and not cmdDown then
		if panel.tool == "erase" or panel.tool == "brush" then
			local whichBrush = panel.tool == "erase" and panel.eraser
				or (panel.brush.paint and panel.brush or panel.pen)
			local smaller, bigger = input_keyRepeat("["), input_keyRepeat("]")
			if smaller or bigger then
				if smaller then
					whichBrush.size = math.min(whichBrush.size * 0.9, whichBrush.size - 1)
				elseif bigger then
					whichBrush.size = math.max(whichBrush.size * 1.2, whichBrush.size + 1)
				end
				panel.sliders.size.pct = panel.brush.size / panel.sliders.size.max
				if panel.brush.paint then
					whichBrush.size = mc(4, mf(whichBrush.size), panel.sliders.size.max)
				else
					whichBrush.size = mc(1, mf(whichBrush.size), panel.sliders.size.max / 8)
				end
				print(whichBrush.size)
			end
		elseif panel.tool == "move" then
			panel.strokeSelectFalloff = mc(
				0,
				panel.strokeSelectFalloff + (input_keyPressed("]") and 0.1 or (input_keyPressed("[") and -0.1 or 0)),
				1
			)
		end
	end
end

function editor_pickColor(panel)
	if panel.isPanel then
		local r, g, b, a = editor.pickerCanvas:newImageData():getPixel(0, 0)
		if not ctx.isAnim then
			r, g, b = cpml.color.linear_to_gamma(r, g, b)
		end
		editor.colorToPick = { r, g, b }
		panel.color.r, panel.color.g, panel.color.b = unpack(editor.colorToPick)
		panel.color.h, panel.color.s, panel.color.v = rgbToHsv(panel.color.r, panel.color.g, panel.color.b, 1)
		editor_pushColor(panel)

		animatePanel.clickedCanvas = false
	else
		local r, g, b, a = engine.canvas:newImageData():getPixel(mouse.x * game.resolution, mouse.y * game.resolution)
		panel.r, panel.g, panel.b = r * 1.062, g * 1.062, b * 1.062
		panel.h, panel.s, panel.v = rgbToHsv(panel.r, panel.g, panel.b, 1)
		editor_pushColor(editor, panel)
	end
end
