local panel = panel
local frameDot, frameGap = {}, {}
local vBuffer
local vBufferN = 0

local function pprect(mode, x, y, w, h, r)
  gfx.rectangle(mode, mf(x), mf(y), mf(w), mf(h), r)
end

local function updateClip(r)
  if not (r.frames.last and r.frames.first and r.layers.last and r.layers.first) then return end

  if not r.f2 or (panel.scrubbing and not r.isClip) then
    r.f1, r.f2 = math.min(r.frames.last, r.frames.first), math.max(r.frames.last, r.frames.first)
    r.l1, r.l2 = math.min(r.layers.last, r.layers.first), math.max(r.layers.last, r.layers.first)
    r.f3 = r.f3 or 0
  end

  local fw = r.f2 - r.f1 + 1
  local lh = r.l2 - r.l1 + 1
  r.fw = fw
  r.lh = lh
  local k = editor_getKeyFrame(panel, r.l1, r.f1)
  local a = editor_getAbsoluteFrame(panel, r.l1, k)
  if fw == 1 and lh == 1 and a ~= r.f1 and not config.flashFrames then return end

  r.k1, r.k2 = editor_getKeyFrame(panel, r.l1, r.f1), editor_getKeyFrame(panel, r.l1, r.f2)
  r.k1 = r.k1 + (editor_getAbsoluteFrame(panel, r.l1, r.k1) == r.f1 and 0 or 1)

  local aframe = editor_getAbsoluteFrame(panel, r.l1, r.k1)
  if not aframe then return end
  r.left = r.left or aframe - r.f1 + 1

  if not panel.layers[r.l2].element then return end

  r.rect = {
    x = (r.f1 - 1) * panel.frameWidth + panel.framesX,
    y = panel.layers[r.l2].element.y + 1,
    w = fw * panel.frameWidth,
    h = lh * panel.row - 4,
  }
  r.canDrag = (mouse_in(lume.merge(r.rect, { w = (fw + (r.f3 or 0)) * panel.frameWidth })))

  if panel.draggingFrame then
    r.canDrag = panel.activeClip == r
  else
    r.right = r.f2 + r.f3 - editor_getAbsoluteFrame(panel, r.l1, r.k2)
    r.max = r.f2 - editor_getAbsoluteFrame(panel, r.l1, r.k2)
  end

  if r.isClip then
    local w = math.max(6, panel.frameWidth / 2)
    local lx = r.rect.x + r.rect.w
    if r.canDrag then
      r.canLoop = mouse_in({ x = lx - w, y = r.rect.y, w = w, h = r.rect.h / 2 })
      if r.canLoop and not mouse.down.L then editor_setToolTip("Loop Clip") end
    end
  end
end

local function drawClip(r)
  local mul = r.canDrag and 1 or 0.6
  local corner = editor.debug.timelineZoom > 0.2 and 2 or 0
  if not r.rect then return end
  gfx.setColor(r.isClip and 1 or 0.6, 0.7, 1, 0.1 * mul)
  gfx.rectangle("fill", r.rect.x, r.rect.y, r.rect.w, r.rect.h, corner)

  if r.isClip then
    local w = math.max(6, panel.frameWidth / 2)
    local lx = r.rect.x + r.rect.w
    if r.f3 and r.f3 > 0 then
      local loops = mu(r.f3 / fw)
      local img = load_imageTex("reload")
      img:setFilter("linear", "linear")
      for i = 1, loops, 1 do
        gfx.setColor(1, 1, 1, 0.2)
        gfx.draw(img, lx + 1, r.rect.y + 8, pi / 2 + pi / 4, 0.12, 0.12, 64, 64)
        gfx.setColor(1, 0.7, 1, 0.06 * mul)
        gfx.rectangle("fill", lx, r.rect.y, i == loops and (r.f3 - ((loops - 1) * fw)) * panel.frameWidth or r.rect.w, r.rect.h, 2)
        lx = i == loops and lx + (r.f3 - ((loops - 1) * fw)) * panel.frameWidth or lx + r.rect.w
      end
    end

    if r.canDrag then
      gfx.setColor(0.6, 0.7, 1, 0.8 * mul)
      gfx.rectangle("fill", lx - w, r.rect.y, w, r.rect.h / 2, corner)
      -- gfx.setColor(0.6,1,1,0.8*mul)
      -- pprect("fill", lx-w,r.rect.y+r.rect.h/2,w,r.rect.h/2 ,2)
    end
  end

  if not r.isClip then
    gfx.push("all")
    local wave = (math.sin(time * 5) / 2 + 0.5)
    -- gfx.setBlendMode("multiply")
    gfx.setColor(0.5, 0.5, 1, 0.6 + 0.4 * wave)
    gfx.setLineWidth(2)
    -- gfx.rectangle("fill", r.rect.x+1,r.rect.y+1,r.rect.w-2,r.rect.h-2,corner )
    gfx.rectangle("line", r.rect.x, r.rect.y, r.rect.w, r.rect.h, corner)
    gfx.setColor(0.6, 0.6, 1, 0.5 * mul)
    gfx.rectangle("line", r.rect.x - 1.5, r.rect.y - 1.5, r.rect.w + 3, r.rect.h + 3, corner)
    gfx.pop()
  end
end

local function addFrameVert(x, y, u, v, r, g, b, a, t)
  local n = 2
  local t = (t or 0) / n
  vBufferN = vBufferN + 1
  -- vBuffer[vBufferN] = vBuffer[vBufferN] or {}
  local vert = vBuffer[vBufferN]
  vert.x = x
  vert.y = y
  vert.z = 0
  vert.w = 0
  vert.u = u / n + t
  vert.v = v
  vert.r = r
  vert.g = g
  vert.b = b
  vert.a = a
end

local function layerOptions(l)
  -- {
  --  name = "camera",
  --  click = function()
  --    layer.camera = not layer.camera
  --  end,
  --  draw = function(option)
  --    gfx.setColor(1,1,1,option.hovered and 0.6 or (layer.smooth and 80 or 40))
  --    gfx.rectangle("line",option.x+4,option.y+5,10,6,layer.smooth and 3 or 0)
  --  end
  -- },
  -- {
  --  name = "billboard",
  --  click = function()
  --    layer.billboard = not layer.billboard
  --  end,
  --  draw = function(option)
  --    gfx.setColor(1,1,1,option.hovered and 0.6 or (layer.smooth and 80 or 40))
  --    gfx.rectangle("line",option.x+4,option.y+5,10,6,layer.smooth and 3 or 0)
  --  end
  -- },
  -- {
  --  name = "format",
  --  click = function()
  --    layer.format = layer.format=="raster" and "r8" or "raster"
  --  end,
  --  draw = function(option)
  --    gfx.setColor(1,1,1,option.hovered and 0.6 or (layer.smooth and 80 or 40))
  --    gfx.rectangle("line",option.x+4,option.y+5,10,6,layer.smooth and 3 or 0)
  --  end
  -- },

  local layer = panel.layers[l]
  if #layer.options == 0 then
    layer.options = {}
    if not ctx.isAnim then
      table.insert(layer.options, {
        name = "smooth tween",
        click = function(option)
          local layer = panel.layers[option.l]
          layer.smooth = not layer.smooth
        end,
        draw = function(option)
          local layer = panel.layers[option.l]
          gfx.setColor(1, 1, 1, option.hovered and 0.6 or (layer.smooth and 0.4 or 0.2))
          gfx.rectangle("line", option.x + 4, option.y + 5, 10, 6, layer.smooth and 3 or 0)
        end,
      })
    end

    table.insert(layer.options, {
      name = "solo",
      click = function(option)
        local layer = panel.layers[option.l]
        editor_changeLayer(panel, option.l)
        panel.solo = panel.solo > 0 and 0 or panel.layer
      end,
      line = load_imageTex("circleLine12"),
      draw = function(option)
        local layer = panel.layers[option.l]
        gfx.setColor(0.8, 0.8, 1, option.hovered and 0.6 or (panel.solo == option.l and 0.8 or 0.3))
        -- gfx.circle("line", 8,8, 6,16)
        gfx.draw(option.line, option.x + 2, option.y + (option.scale == 1 and 2 or 0), 0, option.scale, option.scale)
        if panel.solo == option.l then gfx.line(option.x + 5, option.y + 8, option.x + 10, option.y + 8) end
      end,
    })

    table.insert(layer.options, {
      name = "visible",
      click = function(option)
        local layer = panel.layers[option.l]
        layer.visible = not layer.visible
      end,
      line = load_imageTex("circleLine12"),
      fill = load_imageTex("circleFill8"),
      draw = function(option)
        local layer = panel.layers[option.l]
        gfx.setColor(1, 1, 1, option.hovered and 0.6 or (layer.visible and 0.4 or 0.2))

        if option.scale == 1 then
          gfx.draw(option.line, option.x + 2, option.y + 2)
          if layer.visible then gfx.draw(option.fill, option.x + 4, option.y + 4) end
        else
          gfx.draw(option.line, option.x + 2, option.y, 0, 0.6, 0.6)
          if layer.visible then gfx.draw(option.fill, option.x + 3.25, option.y + 1, 0, 0.6, 0.6) end
        end
      end,
    })

    -- table.insert(layer.options,{
    --  name = "loop",
    --  click = function(option   --    layer.loop = not layer.loop
    -- local layer = panel.layers[option.l]
    --  end,
    --  draw = function(option)
    -- local layer = panel.layers[option.l]
    --    gfx.setColor(1,1,1, layer.loop and 0.6 or (option.hovered and 0.4 or 0.1))
    --    gfx.draw(load_imageTex("loop"),0,  0,0,0.25,0.25)
    --  end
    -- })

    if layer.type == "drawing" then
      if not ctx.isAnim then
        table.insert(layer.options, 1, {
          name = "auto center frames",
          click = function(option)
            local layer = panel.layers[option.l]
            layer.center = not layer.center
          end,
          draw = function(option)
            local layer = panel.layers[option.l]
            gfx.push()
            gfx.translate(-2, 0)
            gfx.setColor(1, layer.center and 0.8 or 1, layer.center and 0.8 or 1, option.hovered and 0.6 or (layer.center and 0.4 or 0.2))
            local set = layer.center and 2 or 4
            gfx.line(option.x + 10 - set, option.y + 8, option.x + 10 + set, option.y + 8)
            gfx.line(option.x + 10, option.y + 8 - set, option.x + 10, option.y + 8 + set)
            if layer.center then gfx.rectangle("line", option.x + 5, option.y + 3, 10, 10, 1) end
            gfx.pop()
          end,
        })
      end
      gfx.setFilter(load_imageTex("rasterIcon"), "linear")
      gfx.setFilter(load_imageTex("playIcon"), "linear")
      table.insert(layer.options, 1, {
        name = "type: " .. layer.format,
        vectorIcon = load_imageTex("playIcon"),
        rasterIcon = load_imageTex("rasterIcon"),
        draw = function(option)
          local layer = panel.layers[option.l]
          if layer.format == "vector" then
            gfx.setColor(0.8, 1, 0.8, 0.4)
            -- gfx.line(4,4, 14,4, 9,13 ,4,4)
            -- gfx.printf("V",6,3,6)
            -- gfx.line(7,6, 11,6, 9,11, 7,6)
            -- gfx.polygon("fill", 4,4, 14,4, 9,13)
            gfx.draw(
              option.vectorIcon,
              option.x + (option.scale == 1 and 14 or 10.5),
              option.y + (option.scale == 1 and 1.5 or 0),
              pi / 2,
              0.4 * option.scale,
              0.4 * option.scale
            )
          elseif layer.format == "raster" then
            gfx.setColor(0.8, 0.1, 0.8, 0.5)
            -- gfx.rectangle("line",3,3,11,11)
            -- gfx.rectangle("line",5,5,7,7)
            local s = option.scale == 1 and 0.8 or 0.5
            gfx.draw(option.rasterIcon, option.x + (option.scale == 1 and 1.5 or 4), option.y + (option.scale == 1 and 2 or -1), 0, s, s)
          elseif layer.format == "r8" then
          elseif layer.format == "model" then
          elseif layer.format == "camera" then
          end
        end,
        click = function(option)
          local layer = panel.layers[option.l]
        end,
      })
    end

    if layer.type == "model" then
      table.insert(layer.options, 1, {
        name = "edit mesh",
        click = function(option)
          local layer = panel.layers[option.l]
          layer.remodel = not layer.remodel
        end,
        draw = function(option)
          local layer = panel.layers[option.l]
          gfx.setColor(1, layer.remodel and 0.6 or 1, layer.remodel and 0.3 or 1, option.hovered and 0.6 or (layer.remodel and 0.4 or 0.2))
          gfx.draw(load_imageTex("remodel"), option.x + 2, option.y + 2, 0, 0.2, 0.2)
        end,
      })
      table.insert(layer.options, 1, {
        name = "wireframe",
        click = function(option)
          local layer = panel.layers[option.l]
          layer.wireframe = not layer.wireframe
        end,
        draw = function(option)
          local layer = panel.layers[option.l]
          gfx.setColor(layer.wireframe and 0.5 or 1, layer.wireframe and 0.8 or 1, 1, option.hovered and 0.6 or (layer.wireframe and 0.4 or 0.2))
          gfx.draw(load_imageTex("wireframe"), option.x + 2, option.y + 2, 0, 0.2, 0.2)
        end,
      })
    end
  end
end

local function frameGapContext(items, l, f, frame)
  frame.tween = frame.tween or { easeIn = 0, easeOut = 0, enabled = false }
  table.insert(items, {
    name = "Insert Frame",
    func = function()
      panel.pushUndoState("Split Frame")
      editor_changeLayer(panel, l)
      animatePanel.hovered = false
      editor_addBlankFrame(panel)
      timeline_refresh()
      panel.changed = true
    end,
  })

  table.insert(items, {
    name = "Paste Frame",
    func = function() end,
  })

  -- table.insert(items,{
  --  {
  --  name = "Tween", size=6, func=function()
  --    panel.pushUndoState()
  --    frame.tween.enabled = not frame.tween.enabled
  --    return "toggle"
  --  end
  --  },{
  --    name = "", align="center",size=1, col = {r=0,g=0,b=0,a=0.6},
  --    hover= hover,
  --    func = function()
  --      frame.tween.enabled = not frame.tween.enabled
  --      return "toggle"
  --    end,
  --    update  = function(x)
  --      x.text = frame.tween.enabled and "ticked" or "unticked"
  --    end
  --  }
  -- })

  -- table.insert(items,{
  --  {
  --  name = "Blend", size=6, func=function()
  --    panel.pushUndoState()
  --    frame.tween.blend = not frame.tween.blend
  --    return "toggle"
  --  end
  --  },{
  --    name = "", align="center",size=1, col = {r=0,g=0,b=0,a=0.6},
  --    hover= hover,
  --    func = function()
  --      frame.tween.blend = not frame.tween.blend
  --      return "toggle"
  --    end,
  --    update  = function(x)
  --      x.text = frame.tween.blend and "ticked" or "unticked"
  --    end
  --  }
  -- })
end

local function processRegion(region, proc)
  if not (region and region.l1) then return end
  region.l2 = region.l2 or region.l1 + region.height - 1
  region.f2 = region.f2 or region.f1 + region.length - 1
  for l = region.l1, region.l2, 1 do
    local k1, k2 = editor_getKeyFrame(panel, l, region.f1), editor_getKeyFrame(panel, l, region.f2)
    local k1 = panel.layers[l].frames[k1].abs == region.f1 and k1 or k1 + 1
    for k = k1, k2, 1 do
      local frame = panel.layers[l].frames[k1]
      proc(l, frame.abs, k1, frame, panel.layers[l].frames[k1 - 1], region)
    end
  end
end

local function startDragFrames(f, l)
  local f1, f2, l1, l2
  if type(f) == "table" and f.k1 then
    panel.activeClip = f
    panel.copiedRegion = timeline_copyRegion(f)
    print("STARTDRAGREGION")
  end

  panel.draggingFrame = true
end

local function startLoopFrames(clip)
  panel.activeClip = clip
  local f, l, f2, l2 = clip.k1, clip.l1, clip.k2
  panel.pushUndoState()
  startDragFrames(f2 + 1, l)
  panel.draggingLoop = true
  clip.f3 = clip.f3 or 0
  clip.sf3 = clip.f3
end

local function scrollTimeline()
  panel.prevScrollPos = panel.scrollPos
  if panel.panTimeline then return end
  if mouse_in(panel) then panel.scrollSpeed = panel.scrollSpeed + mouse.scroll.x * 16 end

  local headPos = panel.currentFrame * panel.frameWidth - panel.scrollPos
  local safeZone = 150
  local left, right = panel.layerWidth + safeZone, panel.width - safeZone

  panel.scrollSpeed = panel.scrollSpeed
    + (panel.draggingFrame and (mouse.x < left and -(left - mouse.x) / safeZone or (mouse.x > right and (mouse.x - right) / safeZone or 0)) * 10 or 0)

  left, right = safeZone, panel.width - panel.layerWidth - safeZone
  local pos = headPos < left and (headPos - left) * 1 or (headPos > right and (headPos - right) * 1 or 0)

  if
    panel.timelineTracking
    and (input_anyKeyDown({
      "1",
      "2",
      "3",
      "4",
      "k",
      "q",
      "s",
      "o",
      ",",
      ".",
      "delete",
      "left",
      "right",
      "backspace",
    }))
    and not panel.playing
  then
    panel.trackPos = panel.scrollPos + pos
    panel.trackHead = math.abs(pos) > 0
    timeline_refresh()
  end

  if mouse_in(panel) then panel.trackHead = false end
  if panel.playing then panel.trackHead = false end
  if panel.timelineTracking and (panel.playing or panel.trackHead) then
    if panel.playing then
      panel.playTrackSpeed = 4 --mc(8,panel.playTrackSpeed-1,128)
    end
    local mid = panel.currentFrame * panel.frameWidth - panel.width / 2
    panel.scrollPos =
      easeTo(panel.scrollPos, panel.trackHead and panel.trackPos or mid, panel.skipTrack and 1 or (panel.trackHead and 4 or panel.playTrackSpeed))
    panel.skipTrack = false
    if pos < left and math.abs(panel.framesX - panel.layerWidth) < 5 then panel.trackHead = false end
    if pos == 0 then panel.trackHead = false end
  elseif panel.trackHead then
  end

  panel.scrollSpeed = easeTo(panel.scrollSpeed, 0, 2)
  panel.scrollPos = panel.scrollPos + panel.scrollSpeed
  panel.scrollLimit = math.max(
    math.max(panel.animation.length, panel.animation.source and panel.animation.source:getDuration() * panel.animation.fps or 0) * panel.frameWidth
      - panel.width / 2,
    0
  )
  local func = (panel.refreshTimeline > 0 or panel.playing) and mc or math.sling
  panel.scrollPos = func(0, panel.scrollPos, panel.scrollLimit, 4)
end

local function timelineSliders()
  if panel.exporting then return end

  timeline_updateHeight(4)
  gfx.fontSize(9)

  panel.onionButton.tip = "Onion Skin (" .. tostring(animatePanel.onion.enabled and "On" or "Off") .. ")"

  panel.onion.x = panel.x + panel.layerWidth / 1.62 - 13
  panel.onion.y = panel.y + panel.height - 3
  panel.onionButton.x = (panel.x + panel.layerWidth / 1.62 - 13) - 17
  panel.onionButton.y = panel.y + panel.height

  panel.sizeSlider = panel.sizeSlider
    or editor_makeSlider(panel, "frameWidth", {
      name = "zoom",
      min = 6,
      max = 19,
      align = "center",
      nocap = true,
      percent = true,
      width = panel.layerWidth / 4,
      height = panel.row - 3,
      update = timeline_refresh,
    })[1]
  panel.sizeSlider.box = true
  panel.fpsSlider = panel.fpsSlider
    or editor_makeSlider(panel.anims[panel.anim], "fps", {
      name = "fps",
      min = 2,
      max = 60,
      align = "center",
      nocap = true,
      round = 1,
      width = panel.layerWidth / 2.15,
      height = panel.row - 3,
    })[1]
  panel.fpsSlider.box = true

  panel.fpsSlider.x = panel.x
  panel.fpsSlider.y = panel.y + panel.height

  panel.sizeSlider.x = panel.x + panel.layerWidth - panel.sizeSlider.width - 2
  panel.sizeSlider.y = panel.y + panel.height

  editor_updateButton(panel.fpsSlider)
  editor_updateButton(panel.sizeSlider)
  editor_updateButton(panel.onionButton)
end

function timeline_setPanel(p)
  panel = p
end

function timeline_updateHeight(h)
  panel.height = panel.height + (h or panel.row)
end

function timeline_deselectRegion()
  panel.region = nil
end

function timeline_refresh()
  if animatePanel then animatePanel.refreshTimeline = 2 end
end

function timeline_refresh2()
  if animatePanel then animatePanel.refreshTimeline = 4 end
end

function timeline_update()
  bench.push("timeline")
  -- sidebar.row = 20
  -- rowHeight = 20

  if not lwindow.getVSync() or not panel.visible then
    bench.pop("timeline")
    return
  end
  timeline_title()
  timeline_updateHeight(sidebar.titleH)
  local mouseIn = mouse_in
  if panel.clickedCanvas then mouseIn = nothing end

  if not lfs.isFused() then trace(" > refresh timeline", panel.refreshTimeline) end

  local updateLayers = panel.drawTime < 2
  local updateFrames = updateLayers
    and (
      panel.prevScrollPos ~= panel.scrollPos
      or (panel.timelineMousing and (mouse.down.L or mouse.down.R))
      or (panel.sizeSlider and panel.sizeSlider.sliding)
      or (panel.refreshTimeline > 0)
      or panel.unblanked
    )

  panel.frameHover.hovered = false
  -- editor.resized>0
  -- or panel.scrubbing
  -- or panel.panTimeline
  -- or panel.unblanked
  -- or ((not panel.clickedCanvas)  and
  --    ( panel.playing
  --      or panel.refreshTimeline>0
  --      or panel.sizeSlider.sliding
  --      or panel.fpsSlider.sliding
  --      or panel.timelineMousing
  --       )
  --    )
  panel.refreshTimeline = 0

  if (panel.y < screenH + 5) and panel.drawTime < 2 then
    bench.push("pre")
    panel.unblanked = false
    panel.layerWidth = 200 --panel.baseLayerWidth + (panel.width/10)
    local scrubPoint
    local layerWidth = panel.layerWidth
    local framesX = panel.x + layerWidth
    local frameOffset = 0
    local currentFrameOffset = 0
    local sum = 0
    local length = panel.animation.length
    local hoveredLayer = 0
    local timeline = panel.timeline
    timeline.x = panel.x + layerWidth
    timeline.y = panel.y + 20
    timeline.w = panel.width - layerWidth
    timeline.h = panel.savedHeight - 22
    timeline.top = timeline.top or {}
    timeline.top.x = panel.x + layerWidth
    timeline.top.y = panel.y
    timeline.top.w = panel.width - layerWidth
    timeline.top.h = 20
    local layerswap
    local frameWidth = panel.frameWidth
    local circleScale = frameWidth > 12 and 12 or 8

    if panel.pframeWidth ~= panel.frameWidth then
      panel.pframeWidth = panel.frameWidth
      print("frameWidth", frameWidth, panel.frameWidth)
      -- print("circleScale",circleScale)
      -- print("circleFill"..tostring(circleScale))
      panel.circleFill = load_imageTex("circleFill" .. tostring(circleScale))
      panel.circleLine = load_imageTex("circleLine" .. tostring(circleScale))
      panel.circleFill:setFilter("linear", "linear")
      panel.circleLine:setFilter("linear", "linear")
      load_imageTex("frameCircles"):setFilter("linear", "linear")
    end

    if mouseIn(timeline) then
      mouse_setCursor(cursors.plus)
    elseif mouseIn(panel.x, panel.y, panel.width, panel.savedHeight) then
      mouse_setCursor()
    end

    framesX = framesX - panel.scrollPos

    -- print(math.max(length,(panel.width+100)/frameWidth))

    if panel.tweenDrag.side ~= "" then
      trace(panel.tweenDrag.side .. tostring(mouse.start.x - mouse.x))
      panel.tweenDrag.frame.tween["ease" .. capFirst(panel.tweenDrag.side)] =
        mc(0, panel.tweenDrag.startEase + (panel.tweenDrag.side == "in" and mouse.start.x - mouse.x or mouse.x - mouse.start.x) / 80, 1)
      trace(panel.tweenDrag.frame.tween["ease" .. capFirst(panel.tweenDrag.side)])
    end

    bench.pop("pre")

    bench.push("layers")
    for l, layer in lume.ripairs(panel.layers) do
      local compactLayer = config.compactLayers and not layer.selected --and not panel.timelineMousing
      local rowHeight = compactLayer and 9 or panel.row
      local fsize = compactLayer and 9 or 11
      bench.push("preframe")
      local fillFrame = layer.iqm or layer.obj or layer.external
      local optionWidth = 15
      local framesUpped, fl, nextFrame, prevFrame, frame = 0

      if updateLayers then
        framesX = panel.x + layerWidth
        frameOffset = 0
        currentFrameOffset = 0
        sum = 0

        framesX = framesX - panel.scrollPos
        panel.framesX = framesX

        layer.options = layer.options or {}
        layerOptions(l)

        layer.element = layer.element or {
          back = {},
          rename = {},
        }
        layer.element.x = panel.x + optionWidth * #layer.options + 4
        layer.element.y = layer.element.y or panel.y + panel.height

        if panel.draggedLayer == layer.id then
          if panel.draggedLayerStart:dist(mouse) > 3 then
            layer.element.y =
              easeTo(layer.element.y, mc(panel.y + rowHeight * 0.9, mouse.y - rowHeight / 2, panel.y + panel.savedHeight - rowHeight * 1.9), 1)
          end
        else
          layer.element.y = easeTo(layer.element.y, panel.y + panel.height, 1)
        end
        layer.element.cy = layer.element.y + rowHeight / 2 - 10
        layer.element.h = rowHeight

        layer.element.w = layerWidth - optionWidth * #layer.options
        layer.element.hovered = mouse.x < panel.x + layerWidth
          and (mouseIn(layer.element.x, layer.element.y + 2, layer.element.w, layer.element.h) and not panel.draggedLayer)

        if layer.element.hovered then
          mouse_setCursor(defaultCursor)
          mouse_set(function()
            timeline_deselectRegion()
            if cmdDown then
              if layer.format == panel.l.format then layer.selected = not layer.selected end
            elseif shiftDown then
              for i = panel.layer, l, panel.layer < l and 1 or -1 do
                if panel.layers[i].format == panel.l.format then panel.layers[i].selected = true end
              end
            else
              editor_changeLayer(panel, l)
              panel.draggedLayer = layer.id
              print(mouse.x, mouse.y)
              panel.draggedLayerStart = vec2(mouse.x, mouse.y)
            end
          end)
          mouse_setR(function()
            if not layer.selected then editor_changeLayer(panel, l) end

            local items = {}

            if layer.type ~= "model" then
              table.insert(items, {
                name = "Format: " .. layer.format,
                static = true,
                -- tip = "There are two possible formats for the raster element of a layer \n\nNormal is regular full color \n\nLimited has a maximum of 255 colors, but uses about a quarter of the memory",
                func = function() end,
                update = function(item)
                  item.name = "Format: " .. capFirst(layer.format)
                end,
              })
              
              table.insert(items, editor_makeSlider(layer, "alpha", { name = "Opacity" ,percent = true}))

              if layer.format == "vector" then
                table.insert(
                  items,
                  editor_makeToggle(layer, { "lineWidth", "lineWidthOverride" }, {
                    min = 0.25,
                    max = 2,
                    name = "Line Width Modifier",
                    click = editor_refreshStrokes,
                    slide = editor_refreshStrokes,
                    toggleTip = "force constant width",
                  })
                )
                table.insert(items, editor_makeToggle(layer, "onePixel", { name = "1px lines", click = editor_refreshStrokes }))
                -- table.insert(items, editor_makeToggle(layer,"textureEnabled",{name="texture strokes",click=editor_refreshStrokes,tip="this is just a test, actual pen texturing will work completely differently"}))
                -- table.insert(items, editor_makeSlider(layer,"catmull",{name="Line Smoothing",min=0,max=4,round=1,slide=editor_refreshStrokes}))
                table.insert(items, editor_makeSlider(layer, "wobble", { name = "Wobble" }))
              end
              if layer.format == "raster" then
                table.insert(items, editor_makeToggle(layer, "autoExpand", { name = "Auto Expand (experimental)" }))
              end
              -- table.insert(items, editor_makeSlider(layer,"resolution",{name="Resolution"}))
            end

            table.insert(items, {
              name = "New Layer",
              col = colors.green,
              func = function()
                editor_newLayer(panel, l + 1)
              end,
              items = {
                { name = "Format:", static = true },
                -- {name="Limited",tip="Limited Color Raster",func=function()editor_newLayer(panel,l,"r8") end},
                {
                  name = "Vector",
                  closes = true,
                  tip = "",
                  func = function()
                    editor_newLayer(panel, l + 1, "vector")
                  end,
                },
                {
                  name = "Raster",
                  closes = true,
                  tip = "",
                  func = function()
                    editor_newLayer(panel, l + 1, "raster")
                  end,
                },

                {
                  name = "Camera",
                  closes = true,
                  devonly = true,
                  func = function()
                    editor_newLayer(panel, l + 1, "camera")
                  end,
                },
                {
                  name = "3D Model",
                  closes = true,
                  devonly = true,
                  func = function()
                    editor_newLayer(panel, l + 1, "model")
                  end,
                  items = { { name = "Cube" }, { name = "Sphere" }, { name = "Cylinder" } },
                },
              },
            })
            if #panel.layers > 1 then
              table.insert(items, {
                name = "Delete Layer" .. (panel.numLayersSelected > 1 and "s" or ""),
                col = colors.red,
                warning = "open",
                func = function()
                  editor_deleteSelectedLayers(panel)
                end,
              })
            end
            editor_openContextMenu(items, { y = mouse.y - 15, up = true })
          end)
        end

        layer.element.back.x = panel.x + layerWidth
        layer.element.back.y = layer.element.cy
        layer.element.back.w = panel.width - layerWidth + 2
        layer.element.back.h = rowHeight - 2
        layer.element.back.hovered = (mouseIn(layer.element.back) and not panel.draggedLayer)
        hoveredLayer = layer.element.back.hovered and l or 0
        layer.hovered = mouse.x < panel.x + layerWidth and mouseIn(0, layer.element.back.y, layerWidth, layer.element.back.h)

        layer.element.rename.x = layer.element.x + layer.element.w - optionWidth - 8
        layer.element.rename.y = layer.element.cy + 2
        layer.element.rename.w = optionWidth
        layer.element.rename.h = rowHeight - 4
        layer.element.rename.hovered = mouse.x < panel.x + layerWidth and mouseIn(layer.element.rename)
        if layer.element.rename.hovered then
          editor_setToolTip("Rename Layer")
          local renameW = layer.element.w - 20
          mouse_set(function()
            namer_typeString("Rename Layer", panel.layers[l].name, {
              indent = 3,
              fontSize = fsize,
              align = "left",
              notri = true,
              width = renameW,
              x = layer.element.x + renameW / 2 - 2.5,
              y = layer.element.cy + 12.5,
            }, function(n)
              panel.layers[l].name = n
            end)
          end)
        end

        bench.push("options")

        for o, option in ipairs(layer.options) do
          option.x = layer.element.x - optionWidth * (#layer.options - o + 1) - 4
          option.y = layer.element.cy + 1
          if compactLayer then option.y = option.y + 5 end
          option.w = optionWidth
          option.h = compactLayer and rowHeight - 3 or rowHeight - 2
          option.hovered = mouse.x < layer.element.x and mouseIn(option)
          option.l = l
          option.scale = compactLayer and 0.5 or 1
          if option.hovered then
            editor_setToolTip(option.name)
            mouse_set({ option.click, option })
          end
        end

        bench.pop("options")
      end

      if not layer.framesVerts then layer.framesVerts = ffi.new("m_vert[?]", 2 ^ 16) end

      vBuffer = layer.framesVerts
      vBufferN = 0

      if not panel.playing and (layer.element.back.hovered or mouseIn(timeline.top)) then
        mouse_pushClick(function()
          if mouseIn(timeline.top) then panel.topScrub = true end

          if not (panel.region and panel.region.canDrag) then
            panel.scrubbing = true
            if mouseIn(layer.element.back) then
              editor_changeLayer(panel, l)
              panel.region = {
                selected = true,
                frames = {},
                layers = {},
              }
            end
          end
        end)
      end

      bench.pop("preframe")
      bench.push("frames")

      local framesupdated = 0
      for f = 1, #layer.frames, 1 do
        frame = layer.frames[f]

        fl = frame.l and frame.l or 1

        if f == panel.frame and l == panel.layer then currentFrameOffset = frameOffset end

        local psum = sum + 1
        sum = sum + frame.l
        frameOffset = frameOffset + fl * frameWidth
        -- if 1>2 then --------------------------
        if
          (frameOffset + fl * frameWidth > panel.scrollPos and frameOffset - fl * frameWidth < panel.scrollPos + panel.width) or f == #layer.frames
        then
          nextFrame = layer.frames[f == #layer.frames and 1 or f + 1]
          prevFrame = layer.frames[f == 1 and #layer.frames or f - 1]

          frame.abs = psum --editor_getAbsoluteFrame(panel,l,f)
          framesUpped = framesUpped + 1
          frameOffset = frameOffset - fl * frameWidth

          -- frameDots[f] = frameDots[f] or {}
          -- frameDot = frameDots[f]
          frameDot.x = framesX + frameOffset
          frameDot.y = layer.element.y - 1
          frameDot.w = frameWidth
          frameDot.h = rowHeight
          frameDot.l = fl * frameWidth

          -- frameGaps[f] = frameGaps[f] or {}
          -- frameGap = frameGaps[f]
          frameGap.x = frameDot.x + frameWidth
          frameGap.y = frameDot.y
          frameGap.w = frameWidth * (fl - 1)
          frameGap.h = rowHeight
          frameGap.l = fl * frameWidth

          frameDot.hoved = false --mouseIn(frameDot) and not panel.draggingFrame and not (panel.region and panel.region.canDrag)
          frameDot.r = ((panel.draggingFrame and panel.draggedFrame == f - 1 and panel.draggedFrameLayer == l) and frameWidth / 2.5 or frameWidth / 3)
          frameOffset = frameOffset + fl * frameWidth

          local side = panel.tweenDrag.side
          local newL = (not mouseIn(frameGap)) and 1
          local frameHov = mf((mouse.x - framesX) / frameWidth) - sum
          local openContext = false
          local items = {}

          if not panel.playing then
            if mouse.x > panel.x + panel.layerWidth then
              -- CLICK INBETWEEN CIRCLES TO MAKE NEW FRAMES --
              if mouseIn(frameGap) or (mouseIn(layer.element.back) and f == #layer.frames and mouse.x - framesX > sum * frameWidth) then
                openContext = true
                side = (mouse.x < frameGap.x + frameGap.w / 2 and "in" or "out")

                frameGapContext(items, l, f, frame)

                if frame.tween and frame.tween.enabled then
                  mouse_set(function()
                    panel.tweenDrag = {
                      side = side,
                      frame = frame,
                      startEase = frame.tween["ease" .. capFirst(side)],
                    }
                  end)
                end

              -- CLICK TIMELINE CIRCLES --
              elseif mouseIn(frameDot) then
                openContext = true

                if not shiftDown then
                  mouse_set(function()
                    panel.pushUndoState()
                    editor_changeLayer(panel, l)
                    panel.frame = f
                    editor_setAbsoluteFrame(panel)
                    -- editor_startDragFrames(f,l)
                  end)
                end

                mouse_setR(function() end)

                if layer.iqm then
                  if frame.anim then
                    gfx.push("all")
                    gfx.setScissor()
                    editor_drawTip({ text = frame.anim })
                    gfx.pop()
                  end
                  local animList = {}
                  for a, anim in ipairs(layer.iqmAnims) do
                    table.insert(animList, {
                      name = anim.name,
                      func = function()
                        frame.anim = anim.name
                        return true
                      end,
                      update = function(x)
                        x.col = frame.anim == anim.name and { r = 0, g = 0, b = 0, a = 0.8 } or { r = 0, g = 0, b = 0, a = 0 }
                      end,
                    })
                  end

                  table.insert(items, {
                    name = "Animation",
                    items = animList,
                  })
                end
              end
            end

            if (panel.scrubbing or panel.draggingFrame) and not scrubPoint then
              scrubPoint = math.max(1, sum + frameHov + 1)
              if panel.scrubbing and panel.region and panel.region.frames and not panel.topScrub then
                trace("reg")
                panel.region.frames.first = panel.region.frames.first or scrubPoint
                panel.region.frames.last = scrubPoint
              end
              trace("scrubbing", panel.scrubbing)
            end

            if openContext then
              mouse_setR(function()
                panel.rightClickedAbsFrame = scrubPoint or (panel.preScrub and panel.preScrub[1])
                print("panel.rightClickedAbsFrame", panel.rightClickedAbsFrame)
                if panel.region and panel.region.rect then
                  if mouseIn(panel.region.rect) then
                    table.insert(items, {
                      name = "Create Clip",
                      func = function()
                        panel.pushUndoState("clip")
                        local clip = deepcopy(panel.region)
                        clip.isClip = true
                        table.insert(layer.clips, clip)
                        panel.region = {}
                      end,
                    })
                  else
                    panel.scrubbing = true
                    panel.region = nil
                  end
                else
                  panel.scrubbing = true
                end
                editor_changeLayer(panel, l)
                panel.frame = f
                --editor_setAbsoluteFrame(panel)
                editor_openContextMenu(items, { y = mouse.y - 15, up = true, width = 140, subWidth = 120 })
              end, true)
            end
          end

          if updateFrames then
            -- START DRAW TIMELINE LINES + CIRCLES ---
            local dotSelected = (l == panel.layer and f == panel.frame)
            local dotBefore = (
              l == panel.layer
              and ((layer.loop and panel.frame == 1) and f == #panel.frames or f == panel.frame - 1)
              and (panel.onion.before.enabled and panel.onion.enabled)
            )
            local dotAfter = (
              l == panel.layer
              and ((layer.loop and panel.frame == panel.frames) and f == 1 or f == panel.frame + 1)
              and (panel.onion.after.enabled and panel.onion.enabled)
            )

            local circleSize = (1 / circleScale)
              * (fillFrame and 0.5 or 1)
              * (frameDot.r / (dotSelected and 0.9 or 1.1))
              * (l == panel.layer and 1 or 0.8)
            local cx, cy, w = frameDot.x + frameWidth / 2 - framesX, frameDot.y + rowHeight / 2 - layer.element.y, circleScale * circleSize

            local r, g, b = 1, 1, 1
            if dotBefore then
              local bef = panel.onion.before.tint
              r, g, b = bef.r, bef.g, bef.b
            elseif dotAfter then
              local aft = panel.onion.after.tint
              r, g, b = aft.r, aft.g, aft.b
            elseif frame.tween and frame.tween.enabled then
              r, g, b = 0, 0, 1
            end
            local a = l == panel.layer and (dotSelected and 1 or 0.75) or 0.5

            local x1, x2 = frameDot.x + frameWidth / 2 + frameDot.r - framesX, frameDot.x + frameWidth / 2 + frameDot.r * 1.8 + frameGap.w - framesX
            local y1, y2, y3 = frameDot.y + 7, frameDot.y + rowHeight - 7, frameDot.y + rowHeight / 2

            if layer.iqm and frame.anim then
              local x, y = frameDot.x + frameWidth / 2, frameDot.y + rowHeight / 2
              gfx.polygon("fill", x - 3, y - 5, x - 3, y + 5, x + 5, y)
            --gfx.circle( "line", x,y-2, frameDot.r/(dotSelected and 1 or 1.3)+0.5,16 )
            else
              local fill = (not (frameDot.hoved or frame.blank) or fillFrame) and "fill" or "line"
              if layer.type == "model" then
                local size = frameWidth * 0.8
                gfx.push()
                gfx.translate(frameDot.x + frameWidth / 2, frameDot.y + rowHeight / 2)
                gfx.rotate(pi / 4)
                gfx.rectangle("fill", -size / 2, -size / 2, size, size)
                gfx.pop()
              else
                if panel.debugReleasedFrames then
                  if frame.released then
                    r, g, b = 1, 0.2, 0.2
                  else
                    if frame.dirty then
                      r, g, b = 0.2, 1, 0.2
                    end
                  end
                end
                if config.flashFrames then
                else
                  if vBufferN > 0 then
                    vBuffer[vBufferN - 1].x, vBuffer[vBufferN - 1].y = cx - w, cy - w
                    vBuffer[vBufferN].x, vBuffer[vBufferN].y = cx - w, cy + w
                  end
                  local t = frame.blank and 1 or 0
                  -- DRAW FRAME
                  if frame.isKey then
                    local o = rowHeight / 6
                    addFrameVert(cx - w, cy - w - o, 0, 0, r, g, b, a, t)
                    addFrameVert(cx - w, cy - o, 0, 0.5, r, g, b, a, t)
                    addFrameVert(cx + w, cy - w - o, 1, 0, r, g, b, a, t)
                    addFrameVert(cx + w, cy - o, 1, 0.5, r, g, b, a, t)

                    addFrameVert(cx + w, cy - o, 1, 0.5, r, g, b, a, t)
                    addFrameVert(cx - w, cy - o, 0, 0.5, r, g, b, a, t)
                    addFrameVert(cx + w, cy + o, 1, 0.5, r, g, b, a, t)
                    addFrameVert(cx - w, cy + o, 0, 0.5, r, g, b, a, t)

                    addFrameVert(cx - w, cy + o, 0, 0.5, r, g, b, a, t)
                    addFrameVert(cx - w, cy + w + o, 0, 1, r, g, b, a, t)
                    addFrameVert(cx + w, cy + o, 1, 0.5, r, g, b, a, t)
                    addFrameVert(cx + w, cy + w + o, 1, 1, r, g, b, a, t)
                  else
                    addFrameVert(cx - w, cy - w, 0, 0, r, g, b, a, t)
                    addFrameVert(cx - w, cy + w, 0, 1, r, g, b, a, t)
                    addFrameVert(cx + w, cy - w, 1, 0, r, g, b, a, t)
                    addFrameVert(cx + w, cy + w, 1, 1, r, g, b, a, t)
                  end
                end
              end
            end

            -- DRAW LINE
            if not frame.blanker then
              if config.flashFrames then
                -- gfx.setAlpha(15 * (frame.blank and 0.1 or 1) * 0.1 * (panel.layer == l and 1 or 0.5))
                -- gfx.setLineWidth(1)
                -- local a = (0.5 + a) * 0.5 * (frame.blank and 0.5 or 0.9)
                -- gfx.setColor(r * a, g * a, b * a, 1)
                -- gfx.rectangle(
                --  "fill",
                --  frameDot.x,
                --  frameDot.y + 1,
                --  frameGap.w + frameWidth,
                --  rowHeight - 2
                -- )
                -- gfx.setColor(0, 0, 0, 1)
                -- gfx.rectangle(
                --  "line",
                --  frameDot.x,
                --  frameDot.y + 1,
                --  frameGap.w + frameWidth,
                --  rowHeight - 2
                -- )
                -- gfx.setColor(r * a, g * a, b * a, 1)
              else
                local lw = (dotSelected and 2 or 1) * (l == panel.layer and 2 or 1)
                local a = (frame.blank and (r + g + b == 3 and 0.2 or 0.6) or 1) * (l == panel.layer and 0.6 or 0.3)
                local inset = w > 5 and 0 or 0
                if config.hideFrameGaps then a = 0 end
                addFrameVert(cx + w, cy, 0.5, 0.5, r, g, b, a)
                addFrameVert(cx + w - inset, cy - lw, 0.5, 0.5, r, g, b, a)
                addFrameVert(cx + w, cy - lw, 0.5, 0.5, r, g, b, a)

                addFrameVert(cx + w, cy, 0.5, 0.5, r, g, b, a)
                addFrameVert(cx + w - inset, cy + lw, 0.5, 0.5, r, g, b, a)
                addFrameVert(cx + w, cy + lw, 0.5, 0.5, r, g, b, a)

                addFrameVert(cx + w, cy - lw, 0.5, 0.5, r, g, b, a)
                addFrameVert(cx + w, cy + lw, 0.5, 0.5, r, g, b, a)

                addFrameVert(x2 - 5, cy - lw, 0.5, 0.5, r, g, b, a)
                addFrameVert(x2 - 5, cy + lw, 0.5, 0.5, r, g, b, a)
                addFrameVert(x2 + 5, cy, 0.5, 0.5, 1, 1, 1, 0)
                addFrameVert(x2 + 5, cy, 0.5, 0.5, 1, 1, 1, 0)
                addFrameVert(x2 + 5, cy, 0.5, 0.5, 1, 1, 1, 0)
                addFrameVert(x2 + 5, cy, 0.5, 0.5, 1, 1, 1, 0)
              end
            end
            framesupdated = framesupdated + 1
          end
        end
      end
      bench.pop("frames")

      bench.push("postframe")

      if updateFrames then
        bench.push("postupdateframes")
        if not layer.framesMesh or vBufferN > layer.framesMesh:getVertexCount() then
          layer.framesMesh = gfx.newMesh(zformat, vBufferN, "strip")
          if layer.framesMesh then layer.framesMesh:setTexture(load_imageTex("frameCircles")) end
        end

        if layer.framesMesh and not panel.clickedCanvas then
          -- print("setframeverts", vBufferN, time)
          layer.framesMesh.name = "frameverts?"
          layer.framesMesh:setDrawRange(1, vBufferN)
          layer.framesMesh:setVertices(layer.framesVerts, 1, vBufferN)
          -- print("framvertsset", vBufferN, time)
        end
        bench.pop("postupdateframes")
      end

      if updateLayers then
        if mouseIn(layer.element.back) and panel.scrubbing and panel.region and not panel.topScrub then
          panel.region.layers.first = panel.region.layers.first or l
          panel.region.layers.last = l
        end

        -- ADD FRAME HOVER
        if l == panel.layer then
          if mouse.x > panel.x + layerWidth + 3 and mouseIn(lume.merge(timeline, { h = timeline.h - rowHeight })) then
            panel.frameHover = {
              x = framesX + (mf((mouse.x - framesX) / frameWidth)) * frameWidth, --mf((mouse.x-framesX+frameWidth/2)/frameWidth)*frameWidth
              y = panel.sy - rowHeight + mc(1, mf((mouse.y - panel.sy + rowHeight) / rowHeight), #panel.layers) * rowHeight,
            }
          end

          if
            mouseIn(timeline)
            and not (panel.region and mouseIn(panel.region.rect))
            and (mouseIn(lume.merge(timeline, { h = timeline.h - rowHeight })) or contextMenu.open)
          then
            panel.frameHover.hovered = true
          end
        end

        for c, clip in ipairs(layer.clips) do
          clip.l1, clip.l2 = l, l
          -- drawClip(panel,clip)
        end

        -- DRAG LAYER
        if panel.draggedLayer and panel.draggedLayer == layer.id and panel.draggedLayerStart:dist(mouse) > 3 then
          -- print("layerswap")
          local y1, y2 = panel.y, panel.y
          y1 = y2
          y2 = y2 + (config.compactLayers and 9 * 3 or panel.row)
          for l2, layer2 in lume.ripairs(panel.layers) do
            y1 = y2
            y2 = y2 + (config.compactLayers and 9 or panel.row)
            if mouse.y > y1 and mouse.y < y2 then
              layerswap = { l, mc(1, l2, #panel.layers) }
              -- print(l2,layerswap[2])
              break
            end
          end
          -- layerswap = {l, mc(1,#panel.layers-1-lume.round((layer.element.y-panel.y)/layer.element.h)+2,#panel.layers)}
        end
      end
      timeline_updateHeight(layer.element.h)
      bench.pop("postframe")
    end
    bench.pop("layers")

    bench.push("post")

    if panel.region and panel.region.selected then updateClip(panel.region) end

    if panel.draggingFrame and panel.copiedRegion then
      -- print("panel.regionPreviewPosition.l",panel.regionPreviewPosition.l)
      panel.regionPreviewPosition = {
        f = mf((mouse.x - panel.copiedRegion.offset.x - framesX) / frameWidth),
        l = mc(1, mf((mouse.y - panel.copiedRegion.offset.y - panel.sy + panel.row * 1.5) / panel.row), #panel.layers),
      }
    end

    for l, layer in ipairs(panel.layers) do
      for c, clip in ipairs(layer.clips) do
        if not clip.canDrag then clip.canLoop = false end
        if clip.canLoop then
          mouse_set(function()
            startLoopFrames(clip)
          end)
        elseif clip.canDrag then
          mouse_set(function()
            startDragFrames(clip)
          end)
          mouse_setR(function()
            clip.loopMode = clip.loopMode or "repeat"
            editor_openContextMenu({
              editor_makeModes(clip, "loopMode", panel.loopModes),
              {
                name = "release clip",
                func = function()
                  table.remove(layer.clips, c)
                end,
              },
            })
          end)
        end
      end
    end

    if panel.region and panel.region.canDrag then mouse_set(function()
      startDragFrames(panel.region)
    end) end

    if mouseIn(timeline.top) then
      mouse_setR(function()
        panel.preScrub = { panel.currentFrame, panel.scrollPos }
        local pct = math.floor(panel.currentFrame / panel.animation.length * 98)
        local name = string.rep(" ", pct) .. "|"
        editor_openContextMenu({
          {
            name = name,
            func = function() end,
            update = function(item)
              if item.hovered then
                local scr = ((mouse.x - item.x) / item.width)
                panel.currentFrameFastScrubbed = scr * panel.animation.length
                timeline_refresh()
              end
            end,
          },
        }, { y = panel.y - 32, width = 300 })
      end)
    end

    -- CHANGE LAYER
    if layerswap and #layerswap > 0 then
      local moving = panel.layers[layerswap[1]]
      table.remove(panel.layers, layerswap[1])
      table.insert(panel.layers, layerswap[2], moving)
      editor_changeLayer(panel, layerswap[2])
    end

    panel.currentFrame = scrubPoint or panel.currentFrame

    if mouse_release() then
      if panel.draggingFrame and panel.copiedRegion then
        print("release")
        -- move region

        panel.pushUndoState("Move Frames")
        panel.pushUndoData({ source = timeline_copyRegion(panel.region) })

        -- delete source region
        if not altDown then timeline_deleteRegion(panel.region) end

        -- paste source region to dest (includes deleting space at dest)
        print("panel.regionPreviewPosition.l", panel.regionPreviewPosition.l)
        timeline_pasteRegion(
          mf((mouse.x - panel.copiedRegion.offset.x - framesX) / frameWidth + 1),
          #panel.layers - panel.regionPreviewPosition.l + 1,
          panel.copiedRegion,
          true
        )
        panel.l.selected = false
        panel.layer = #panel.layers - panel.regionPreviewPosition.l + 1
        panel.layers[panel.layer].selected = true
        if shiftDown then
          -- insert
        end
      end
      if panel.activeClip then panel.activeClip.foff = nil end
      panel.scrubbing = false
      panel.topScrub = false
      panel.draggingFrame = false
      panel.draggedLayer = nil
      panel.draggedFrameEnd = nil
      panel.draggingLoop = false
      panel.panTimeline = false
      panel.tweenDrag = { side = "", frame = 0, layer = 0 }
    end

    if contextMenu.open then panel.scrubbing = false end

    bench.pop("post")

    timelineSliders()

    timeline_updateHeight()

    panel.ty = panel.y
    panel.th = panel.height

    scrollTimeline()

    local mouseinp = mouseIn()
    if panel.timelineMousing ~= mouseinp then timeline_refresh() end
    if not contextMenu.open then panel.timelineMousing = mouseinp end
  end

  if mouse.y > screenH - 22 and mouse.x > panel.layerWidth then mouse_set(function()
    panel.panTimeline = true
  end) end
  if panel.panTimeline then
    local new = panel.scrollPos - mouse.change.x
    local diff = 1
    -- print("mouse.change.x",mouse.change.x)
    if new < 0 then diff = diff + math.abs(new / 10) end
    if new > panel.scrollLimit then diff = diff + math.abs((new - panel.scrollLimit) / 10) end
    panel.scrollPos = panel.scrollPos - mouse.change.x / diff
  end

  panel.height = panel.th or panel.height
  panel.y = panel.ty or panel.y

  bench.pop("timeline")
end

local function drawOnionIndicator()
  -- if mouseIn({x=panel.onion.x-15,y=panel.onion.y,w=50,h=20}) then
  --  editor_setToolTip("Onion Skin")
  -- end
  -- print("hi")
  -- gfx.rectangle("fill",panel.onion.x, panel.onion.y,100,100)
  gfx.setColor(1, 1, 1, panel.onion.enabled and 0.7 or 0.4)
  gfx.draw(load_imageTex("onionIcon"), panel.onion.x, panel.onion.y, 0, 0.7, 0.7)
  local s1, s2 = 0.4, 0.3
  local bef, aft = panel.onion.before.tint, panel.onion.after.tint
  gfx.setColor(0, 0, 0, panel.onion.enabled and 0.8 or 0)
  gfx.draw(load_imageTex("playIcon"), panel.onion.x - 1, panel.onion.y + 4, 0, -s1, s1)
  gfx.setColor(bef.r, bef.g, bef.b, (panel.onion.enabled and panel.onion.before.enabled) and 1 or 0.1)
  gfx.draw(load_imageTex("playIcon"), panel.onion.x - 2, panel.onion.y + 5, 0, -s2, s2)

  gfx.setColor(0, 0, 0, panel.onion.enabled and 0.8 or 0)
  gfx.draw(load_imageTex("playIcon"), panel.onion.x + 24, panel.onion.y + 4, 0, s1, s1)
  gfx.setColor(aft.r, aft.g, aft.b, (panel.onion.enabled and panel.onion.after.enabled) and 1 or 0.1)
  gfx.draw(load_imageTex("playIcon"), panel.onion.x + 25, panel.onion.y + 5, 0, s2, s2)
end

function timeline_draw()
  if panel.y > screenH + 5 or not panel.timeline.h then return end
  bench.push("timeline_draw")
  local timeline = panel.timeline
  local length = panel.animation.length
  local layerWidth = panel.layerWidth
  local frameWidth = panel.frameWidth
  local framesX = panel.x + layerWidth - panel.scrollPos
  gfx.push("all")

  gfx.setScissor(timeline.x, panel.y, timeline.w, timeline.h + 15)
  gfx.fontSize(9)
  gfx.setColor(1, 1, 1, 1)

  -- todo: prerender all these to an atlas, map to a long mesh and just change the vertices of that
  for i = 1, math.max(length, (panel.width + 100) / frameWidth), 1 do
    if i > panel.scrollPos / frameWidth and i < (panel.scrollPos + panel.width - layerWidth + 20) / frameWidth and (i % 4 == 0) then
      gfx.print(i, mf(panel.x - panel.scrollPos + layerWidth + i * frameWidth - frameWidth / 1.5 - (#tostring(i) - 1) * 2), mf(panel.y + 10))
    end
  end
  for i = 1, math.max(length, (panel.width + 100) / frameWidth), 1 do
    if i % panel.animation.fps == 0 then
      local seconds = i / panel.animation.fps
      local minutes = mf(seconds / 60)
      seconds = seconds - minutes * 60
      gfx.print(
        string.format("%02d:%02d", minutes, seconds),
        panel.x - panel.scrollPos + layerWidth + i * frameWidth - frameWidth - (#tostring(i)) * 2,
        panel.y - 1
      )
    end
  end

  gfx.setScissor()

  gfx.fontSize(12)
  for l, layer in lume.ripairs(panel.layers) do
    if not layer.element then goto skip end
    local compactLayer = config.compactLayers and not layer.selected --and not panel.timelineMousing
    local rowHeight = compactLayer and 9 or panel.row
    local fsize = compactLayer and 9 or 11
    if layer.hovered then
      gfx.setColor(1, 1, 1, layer.element.rename.hovered and 0.3 or layer.hovered and 0.2 or 0.05)
      gfx.draw(load_imageTex("pen"), layer.element.rename.x + 4, layer.element.rename.y + 1, 0, 0.6, 0.6)
    end

    -- gfx.fontSize(12)
    -- gfx.setLineWidth(1)

    local highlightLayer = l == panel.layer or layer.selected
    local additionalLayer = l ~= panel.layer and layer.selected
    local light = additionalLayer and 0.8 or 0.6
    if (highlightLayer or layer.element.hovered) then
      gfx.setColor(light, light, 1, additionalLayer and 0.15 or 0.3)
      gfx.rectangle("fill", 0, layer.element.y, layerWidth + 2, layer.element.h)
    end

    if highlightLayer or panel.draggedLayer == layer.id then
      gfx.setColor(light, light, 1, additionalLayer and 0.15 or 0.3)
      gfx.rectangle("fill", layer.element.back.x, layer.element.y, layer.element.back.w, layer.element.h)
    end

    gfx.setColor(0.9, 0.9, 0.9, 1)

    -- gfx.fontSize(fsize)
    gfx.setFont(fonts.interfont, fsize)
    gfx.print(
      layer.name, --:sub(1,((layerWidth-optionWidth)/6)-(layer.hovered and 2 or 0)),
      layer.element.x,
      layer.element.cy + math.sqrt(fsize) + (compactLayer and 2 or 02)
    )
    gfx.setFont(fonts.inconsolata, fsize)

    for o, option in ipairs(layer.options) do
      option.draw(option)
    end

    gfx.setScissor(timeline.x, timeline.y, timeline.w, timeline.h)

    -- for f = 1, #layer.frames, 1 do
    --   frame = layer.frames[f]
    -- DRAW FRAME STUFF IF FRAME IS .inRANGE ie
    -- if frame.tween.enabled and frame.l > 1 then
    --   frame.tween.easeIn = frame.tween.easeIn or 0
    --   frame.tween.easeOut = frame.tween.easeOut or 0
    --   gfx.setLineWidth(1)

    --   local n = frame.l + 1
    --   for i = 0, n, 1 do
    --     local p1 = (i / n)
    --     local p2 = p1 < 0.5 and ((p1 * 2) ^ (1 + frame.tween.easeIn)) / 2
    --       or 1 - math.abs(p1 * 2 - 2) ^ (1 + frame.tween.easeOut) / 2
    --     local x = lerp(x1 + frameWidth / 3, x2 - frameWidth / 3, p2)
    --     local y4 = (panel.tweenDrag.side == "" or panel.tweenDrag.frame == frame)
    --         and ((side == "in" and i < n / 2) and 2 or ((side == "out" and i > n / 2) and 2 or 0))
    --       or 0
    --     gfx.line(x, y1 - y4, x, y2 + y4)
    --   end
    -- end

    -- end

    if layer.framesMesh then
      gfx.setMeshCullMode("none")
      gfx.setShader()
      gfx.setColor(1, 1, 1, 1)
      gfx.draw(layer.framesMesh, framesX, layer.element.y)
      gfx.setShader()
      gfx.setWireframe(false)
    end

    if panel.frameHover.hovered then
      gfx.setColor(1, 1, 1, 0.2)
      gfx.rectangle("fill", panel.frameHover.x, panel.frameHover.y, frameWidth, rowHeight - 2, 2)
    end

    gfx.setScissor()

    -- DRAW CURRENT FRAME BACK --
    gfx.setScissor(timeline.x, panel.y, timeline.w, timeline.h)
    if config.flashFrames then
      gfx.setColor(1, 0.2, 0.2, 0.6)
      gfx.rectangle("fill", framesX + (panel.currentFrame - 0.77) * frameWidth, panel.y - 3, frameWidth * 0.6, 20)
      gfx.rectangle("fill", framesX + (panel.currentFrame - 0.5) * frameWidth - 1, panel.sy - 3, 2, #panel.layers * panel.row)
    else
      gfx.setColor(1, 1, 1, 0.1)
      gfx.rectangle("fill", framesX + (panel.currentFrame - 1) * frameWidth, panel.y + 10, frameWidth, panel.savedHeight - 12 - panel.row, 2)
    end
    gfx.setLineStyle("smooth")
    gfx.setLineWidth(1)
    gfx.setBlendMode("alpha")
    gfx.setColor(1, 1, 1, 0.5)

    if not (config.flashFrames or panel.l.remodel) then gfx.circle("fill", framesX + (panel.currentFrame - 1 + 0.5) * frameWidth, panel.y + 4, 2) end

    gfx.setScissor()

    if panel.region and panel.region.selected then
      drawClip(panel.region)

      if panel.region.frames.first and panel.region.layers.last then
        trace(
          string.format(
            " > timeline region: layers %i-%i, frames %i-%i",
            panel.region.layers.first,
            panel.region.layers.last,
            panel.region.frames.first,
            panel.region.frames.last
          )
        )
      end
    end

    if panel.draggingFrame and panel.copiedRegion then
      local dragX = (panel.regionPreviewPosition.f * frameWidth) + framesX
      local dragY = panel.sy - panel.row + panel.regionPreviewPosition.l * panel.row
      gfx.setColor(0.2, 0.2, 0.25, 0.5)
      gfx.rectangle("fill", dragX, dragY, panel.copiedRegion.rect.w, panel.copiedRegion.rect.h, 2)
      -- gfx.setColor(0.6, 0.6, 1, 0.4)
      -- gfx.rectangle("fill", dragX, dragY, panel.copiedRegion.rect.w, panel.copiedRegion.rect.h, 2)
      gfx.setColor(0.6, 0.6, 1, 1)
      gfx.rectangle("line", dragX, dragY, panel.copiedRegion.rect.w, panel.copiedRegion.rect.h, 2)
    end

    if panel.waveForm then
      gfx.setScissor()
      gfx.setScissor(panel.x + panel.layerWidth, timeline.y, panel.width - panel.layerWidth, 9999)
      gfx.setColor(1, 1, 1, panel.mute and 0.1 or 1)
      gfx.draw(
        panel.waveForm,
        panel.x + panel.layerWidth - panel.scrollPos,
        panel.y + panel.height + 26 / 2,
        0,
        panel.frameWidth * panel.animation.fps,
        1
      )
      gfx.setScissor()
    end
    ::skip::
  end
  gfx.setScissor()
  editor_drawButton(panel.fpsSlider)
  editor_drawButton(panel.sizeSlider)
  editor_drawButton(panel.onionButton)
  editor_drawButton(panel.titleButton)
  editor_drawButton(panel.audioButton)
  drawOnionIndicator()
  gfx.pop()
  bench.pop("timeline_draw")
end

function timeline_deleteRegion(region)
  processRegion(region, function(l, f, k, frame, prev, region)
    print("del region", l, f, k, prev.l, frame.l, frame)
    prev.l = prev.l + frame.l
    table.remove(panel.layers[l].frames, k)
  end)
end

function timeline_pasteRegion(f, l, copied, pushUndo)
  timeline_refresh()

  panel.region = panel.region or {}
  panel.region.f1 = f
  panel.region.f2 = panel.region.f1 + copied.length - 1

  panel.region.l1 = (l + copied.height - 1)
  panel.region.l2 = l

  print("copied.l1", copied.l1, panel.region.l1)
  if pushUndo then panel.pushUndoData({ dest = timeline_copyRegion(panel.region) }) end
  timeline_deleteRegion(panel.region)

  local offset = 0
  -- print("#copied.layers",#copied.layers)
  -- print("copied",inspect(copied))

  for c = 1, #copied.layers, 1 do
    local source = copied.layers[c]
    local layer_index = l + c - 1
    local layer = panel.layers[layer_index]

    if source and layer then
      local k = editor_getKeyFrame(panel, layer_index, f) + 1
      local skipFirst = false

      local frame_abs = layer.frames[k] and editor_getAbsoluteFrame(panel, layer_index, k) or 0
      local lastFrameLength = frame_abs > 0 and (frame_abs - panel.region.f2) or 1
      print("frame_abs,lastFrameLength", frame_abs, lastFrameLength)

      if copied.l1 == panel.region.l1 and k - 1 == source[1].index and source[1].pad then
        layer.frames[k - 1].l = f + source[1].l - layer.frames[k - 1].abs
        skipFirst = true
      else
        layer.frames[k - 1].l = f - layer.frames[k - 1].abs
      end

      for f, frame in ipairs(source) do
        if not (f == 1 and skipFirst) then
          table.insert(layer.frames, k, frame)
          if lastFrameLength and f == #source then layer.frames[k].l = lastFrameLength end
          k = k + 1
        end
      end
    end
  end
end

function timeline_pasteFrame(l, f, frame)
  --find key
  local k = editor_getKeyFrame(panel, l, f)
  print("paste", l, f, k)
  table.insert(panel.layers[l].frames, k + 1, frame)
  local diff = f - editor_getAbsoluteFrame(panel, l, k)
  local left, inserted = panel.layers[l].frames[k], panel.layers[l].frames[k + 1]
  print("FRAME DIFFFFFFFFFFF", diff)
  if not (diff > left.l) then inserted.l = left.l - diff end
  panel.frame = k + 1
  left.l = diff
  left.lastDrawn = os.time()
  -- editor_setKeyFrame(panel)
  panel.currentFrame = editor_getAbsoluteFrame(panel)
end

function timeline_copyRegion(region, cut)
  print("region.rectregion.rectregion.rectregion.rectregion.rectregion.rectregion.rect", inspect(region.rect))
  local copied = {
    f1 = region.f1,
    l1 = region.l1,
    rect = region.rect,
    length = region.fw,
    height = region.lh,
    offset = vec2(mouse.x - region.rect.x, mouse.y - region.rect.y),
    layers = {},
  }

  processRegion(region, function(l, f, k, frame, prev, region)
    local l_count = l - region.l1 + 1
    copied.layers[l_count] = copied.layers[l_count] or {}
    local copiedLayer = copied.layers[l_count]
    copiedLayer.index = l
    table.insert(copiedLayer, editor_copyFrame(panel, frame))
    copiedLayer[#copiedLayer].index = k
    if #copiedLayer == 1 then
      local abs = editor_getAbsoluteFrame(panel, l, k)
      if abs ~= region.f1 then
        local diff = abs + frame.l - region.f1
        copiedLayer[1].l = diff
        copiedLayer[1].pad = true
      end
    end
  end)

  for l, layer in ipairs(copied.layers) do
    local frame = layer[#layer]
    frame.l = region.f2 - editor_getAbsoluteFrame(panel, layer.index, frame.index) + 1
  end

  return copied
end

function timeline_shiftFrameLeft()
  if panel.frame > 1 then
    if panel.frames[panel.frame - 1].l == 1 then
      local l1, l2 = panel.frames[panel.frame - 1].l, panel.frames[panel.frame].l
      panel.frames[panel.frame - 1], panel.frames[panel.frame] = panel.frames[panel.frame], panel.frames[panel.frame - 1]
      panel.frames[panel.frame].l, panel.frames[panel.frame - 1].l = l2 + 1, l1
      panel.frame = panel.frame - 1
      if panel.frame > 1 then panel.frames[panel.frame - 1].l = math.max(1, panel.frames[panel.frame - 1].l - 1) end
    else
      panel.frames[panel.frame - 1].l = math.max(1, panel.frames[panel.frame - 1].l - 1)
      panel.frames[panel.frame].l = math.max(1, panel.frames[panel.frame].l + 1)
    end
    panel.currentFrame = panel.currentFrame - 1
    editor_setAbsoluteFrame(panel)
    panel.frames[panel.frame].lastDrawn = os.time()
  end
end

function timeline_shiftFrameRight()
  if panel.frame > 1 then
    panel.frames[panel.frame - 1].l = math.max(1, panel.frames[panel.frame - 1].l + 1)
    if panel.frame < #panel.frames then
      if panel.f.l == 1 then
        local l1, l2 = panel.frames[panel.frame].l, panel.frames[panel.frame + 1].l
        panel.frames[panel.frame + 1], panel.frames[panel.frame] = panel.frames[panel.frame], panel.frames[panel.frame + 1]
        panel.frames[panel.frame].l, panel.frames[panel.frame + 1].l = l1, l2
        panel.frame = panel.frame + 1
      end
    end
    panel.frames[panel.frame].l = math.max(1, panel.frames[panel.frame].l - 1)
    panel.currentFrame = panel.currentFrame + 1
    editor_setAbsoluteFrame(panel)
    panel.frames[panel.frame].lastDrawn = os.time()
  end
end

function timeline_toggleAudio()
  if not panel.animation.audio then return end

  panel.audioButton = {
    text = (panel.animation.audio or "none"),
    y = panel.y - 2,
    fontSize = 12,
    underline = true,
    x = panel.x + panel.layerWidth - 100,
    w = 150,
    h = sidebar.titleH - 2,
    tip = "Click to mute/unmute, drag another mp3/wav/ogg into the window to change audio",
    func = function()
      panel.mute = not panel.mute
      afx.setVolume(panel.mute and 0 or 1)
    end,
  }

  panel.audioButton.w = gfx.currentFont:getWidth(panel.audioButton.text) * 1.1 + 10
  panel.audioButton.x = panel.x + panel.layerWidth - panel.audioButton.w - 4
  editor_updateButton(panel.audioButton)
end

function timeline_resetHeight()
  panel.savedHeight = panel.height
end

function timeline_title()
  -- DROP IT
  if panel.bottom then
    panel.width = panel.l.remodel and panel.layerWidth or screenW - 4
    local y = screenH - panel.savedHeight + (panel.l.remodel and panel.row or 0)
    panel.y = panel.visible and math.max(y, easeTo(panel.y, y, panel.hideSpeed)) or easeTo(panel.y, screenH + 5, panel.hideSpeed)
    panel.x = 2
  else
    panel.y = panel.y + 2
  end

  -- OTHER STUFF
  panel.sy = panel.y + sidebar.titleH
  panel.height = 0

  if panel.exporting then return end
  -- TITLE
  gfx.setFont(fonts.interfont, 11)

  local tWidth = gfx.currentFont:getWidth(panel.animation.name) + 16

  if panel.l.remodel then
    panel.titleButton = nil
    return
  end

  panel.titleButton = panel.titleButton or {}
  panel.titleButton.align = "left"
  panel.titleButton.font = fonts.interfont
  panel.titleButton.fontSize = 12
  panel.titleButton.underline = true
  panel.titleButton.menuY = (panel.bottom and panel.y - (lume.count(panel.anims) + 1.7) * 20 or panel.y + panel.height + sidebar.titleH / 2)
  panel.titleButton.release = function()
    if not animatePanel.listOpen then
      panel.listOpen = true
      local items = {}
      local keys = {}
      for a, anim in pairs(panel.anims) do
        table.insert(keys, a)
      end
      table.sort(keys)
      for _, key in pairs(keys) do
        local anim = panel.anims[key]
        local current = (panel.anim == anim.key)
        local item = {}

        -- table.insert(item, current and { size=-1,static=true,name=">"} or
        --  { size=-1, name="-x",align="center",    col = colors.red, func = function() editor_deleteAnimation(panel.symbol.key,anim.key) return "remove" end })

        if not ctx.isAnim then
          table.insert(item, {
            size = 1,
            name = "",
            anim = key,
            align = "center",
            func = function()
              panel.symbol.default = key
              return ""
            end,
            update = function(x)
              x.name = (panel.symbol.default == x.key) and "-" or ">"
            end,
          })
        end

        table.insert(item, {
          size = 7,
          name = anim.name,
          tcol = current and { a = 1 } or nil,
          func = function()
            editor_switchAnimation(key)
            return ""
          end,
        })

        table.insert(item, {
          size = 1,
          name = "-rename",
          align = "center",
          col = { r = 0, g = 0, b = 0, a = 0.5 },
          func = function()
            editor_renameAnimation(panel.symbol.key, anim.key)
            return "rename"
          end,
          tip = "rename",
        })

        -- {  size=1, name="D", align="center", col = {r=0.1,g=0.2,b=0.4,a=0.9}, func = function() editor_newAnimation(nil,true) end } ,

        table.insert(items, item)
      end

      table.insert(items, {
        name = "New Animation",
        func = editor_newAnimation,
        update = function()
          panel.listOpen = true
        end,
        col = { r = 0.3, g = 0.8, b = 0.35, a = 0.4 },
      })

      editor_openContextMenu(items, {
        x = panel.x - 8,
        y = panel.titleButton.menuY,
        width = 200,
      })
    end
  end

  panel.titleButton.text = ellipses(panel.animation.name, panel.layerWidth / 2)
  panel.titleButton.width = panel.layerWidth / 2
  panel.titleButton.height = sidebar.titleH - 2
  panel.titleButton.x = panel.x
  panel.titleButton.y = panel.y - 2

  if not contextMenu.open then panel.listOpen = false end

  -- PLAYING INDICATOR
  if panel.playing and panel.animation.length == 1 then
    gfx.fontSize(12)
    gfx.printf("playing...", panel.x + panel.layerWidth, panel.y, 100, "left")
  end
end
