local sin = math.sin
local cos = math.cos
local tan = math.tan

local shadow
local bcol, btcol = { r = 0, g = 0, b = 0, a = 0.5 }, { r = 1, g = 1, b = 1, a = 1 }
local col, tcol
local hover
local dotOff
local ellipseOff
local edge
local textX, textY
local gradient
local tipShadow
local toggleBox = {}

local actions = {
  ["Start Selection"] = {
    store = function()
      local storedEnts = lume.map(editor.selectedEntities, function(ent)
        return ent.id
      end)
      return storedEnts
    end,
    restore = function(storedEnts)
      lume.clear(editor.selectedEntities)
      for i, v in ipairs(storedEnts) do
        editor.selectedEntities[i] = v
      end
    end,
  },
}

local function getFrontCoords(position, ray, view)
  local view = view or ctx.viewport
  local ray = ray or view.camera.ray
  trace("Ray")
  trace(ray.position, ray.direction)
  local intersect, dist = cpml.intersect.ray_plane(ray, { position = position, normal = vec3(view.camera.lookDir.x, 0, view.camera.lookDir.z) })
  return (intersect or vec3(0))
end

local function getFloorCoords(position, ray)
  local ray = ray or ctx.viewport.camera.ray
  local intersect, dist = cpml.intersect.ray_plane(ray, { position = vec3(0, position.y, 0), normal = vec3(0, 1, 0) })
  return intersect and intersect or nil
end

   function newSphereMesh(radius, stacks, slices)
    local vertices = {}
    local indices = {}
    local stacks = stacks or radius
    local slices = slices or radius
    local lstackRadius, lstackY

    for stack = 0, stacks do
        local stackAngle = math.pi / stacks * stack
        local stackRadius = math.sin(stackAngle) * radius
        local stackY = math.cos(stackAngle) * radius

        for slice = 0, slices do
            local sliceAngle = 2 * math.pi / slices * slice

            table.insert(vertices, {math.sin(sliceAngle) * stackRadius,stackY,math.cos(sliceAngle) * stackRadius,1, 0,0, 1,1,1,1})

            if stack>0  then
              table.insert(vertices, {math.sin(sliceAngle) * lstackRadius,lstackY,math.cos(sliceAngle) * lstackRadius,1, 0,0, 1,1,1,1})
            end
        end
        lstackRadius, lstackY = stackRadius, stackY
    end

    return gfx.newMesh(zformat, vertices, "strip","static")
end
  

   function newRingMesh(inner, outer, curve, arc1, arc2, centerline)
    local ring = {}
    local tris = {}
    local curve = curve or 1
    local outer = outer or inner * 1.1
    local arc1, arc2 = arc1 or 0, arc2 or pi * 2
    local thicc = outer - inner
    local iN = 4 * curve
    local jN = curve * 16

    for i = 0, iN, 1 do
      local ca1, ca2 = (i / iN) * 2 * pi, ((i + 1) / iN) * 2 * pi
      local d1, z1 = inner + math.sin(ca1) * thicc, math.cos(ca1) * thicc
      local d2, z2 = inner + math.sin(ca2) * thicc, math.cos(ca2) * thicc
      for j = 0, jN, 1 do
        local ra1, ra2 = (j / jN) * 2 * pi, ((j + 1) / jN) * 2 * pi
        if ra1 >= arc1 and ra2 <= arc2 then
          local xl, yl = math.sin(ra1), math.cos(ra1)
          local xr, yr = math.sin(ra2), math.cos(ra2)
          table.insert(ring, { xl * d1, yl * d1, z1, 1, 0, 0, 1, 1, 1, 1 })
          table.insert(ring, { xr * d1, yr * d1, z1, 1, 0, 0, 1, 1, 1, 1 })
          table.insert(ring, { xl * d2, yl * d2, z2, 1, 0, 0, 1, 1, 1, 1 })

          table.insert(ring, { xl * d2, yl * d2, z2, 1, 0, 0, 1, 1, 1, 1 })
          table.insert(ring, { xr * d2, yr * d2, z2, 1, 0, 0, 1, 1, 1, 1 })
          table.insert(ring, { xr * d1, yr * d1, z1, 1, 0, 0, 1, 1, 1, 1 })
        end
      end
    end

    if centerline then 
      table.insert(ring, { 0,0, -9999, 1, 0, 0, 1, 1, 1, 1 })
      table.insert(ring, { 1,1, 9999, 1, 0, 0, 1, 1, 1, 1 })
      table.insert(ring, { 0,0, -9999, 1, 0, 0, 1, 1, 1, 1 })
    end

    return gfx.newMesh(zformat, ring, "triangles")
  end

   function newArcMesh(inner, outer, curve, arc1, arc2)
    local ring = {}
    local outer = outer or inner * 1.1
    local a1, a2 = arc1 or 0, arc2 or pi * 2
    local thicc = outer - inner
    local r = inner
    local segs = 32
    local step = ((a2 - a1) / segs)
    local w = thicc
    local x, y = 0, 0
    local bx, by, nx, ny
    for i = 0, segs, 1 do
      local a = a1 + step * i - pi / 2
      local nx, ny = math.cos(a), math.sin(a)
      local ax, ay = x + nx * r, y + ny * r
      if i > 0 then
        table.insert(ring, { bx + mx * w, by + my * w, 0, 1, 0, 0, 1, 1, 1, 1 })
        table.insert(ring, { bx - mx * w, by - my * w, 0, 1, 0, 0, 1, 1, 1, 1 })
        table.insert(ring, { ax + nx * w, ay + ny * w, 0, 1, 0, 0, 1, 1, 1, 1 })

        table.insert(ring, { bx - mx * w, by - my * w, 0, 1, 0, 0, 1, 1, 1, 1 })
        table.insert(ring, { ax + nx * w, ay + ny * w, 0, 1, 0, 0, 1, 1, 1, 1 })
        table.insert(ring, { ax - nx * w, ay - ny * w, 0, 1, 0, 0, 1, 1, 1, 1 })
      end
      bx, by = ax, ay
      mx, my = nx, ny
    end
    -- if line and a2-a1>=2*pi then
    --     local nx, ny = 0, -1
    --     local ax, ay = x + nx * r, y + ny * r
    --     batchVN = batchVN + 1
    --     shapeVerts[batchVN].x, shapeVerts[batchVN].y = ax + nx * w, ay + ny * w
    --     batchVN = batchVN + 1
    --     shapeVerts[batchVN].x, shapeVerts[batchVN].y = ax - nx * w, ay - ny * w
    -- end

    return gfx.newMesh(zformat, ring, "triangles")
  end

function getCameraPlaneCoords(position, ray)
  local ray = ray or ctx.viewport.camera.ray
  local intersect, dist = ray
      and cpml.intersect.ray_plane(ray, {
        position = position or ctx.viewport.camera.ray.position + ctx.viewport.camera.lookDir * 500,
        normal = ctx.viewport.camera.lookDir,
      })
    or false
  return intersect and intersect or nil
end

function getPlaneCoords(position, normal, tri, ray)
  local ray = ray or ctx.viewport.camera.ray
  local plane = { position = vec3(position.x, position.y, position.z), normal = -normal }
  local intersect, dist = cpml.intersect.ray_plane(ray, plane)
  if not intersect then trace("no intersect!!") end
  if tri then
    if intersect then
      return vec2(
        vec3.dot(tri[2] - tri[1], intersect - tri[1]) / vec3.dist(tri[2], tri[1]),
        vec3.dot(tri[3] - tri[1], intersect - tri[1]) / vec3.dist(tri[3], tri[1])
      ),
        -intersect
    else
      return vec2(0, 0), vec3(position)
    end
  else
    return -(intersect or plane.position)
  end
end

function editor_pushDraw(proc)
  table.insert(editor.drawQueue,proc)
end

function editor_defineEditor()
  if game.state ~= "Edit" then return end

  if type(config.autoSaveEnabled) ~= "nil" then
    editor.autoSaveEnabled = config.autoSaveEnabled
  else
    config.autoSaveEnabled = true
  end

  editor.debug = config.debug or { trace = {} }

  editor.drawingResolution = config.drawingResolution or 1

  editor.helpEnabled = config.helpEnabled
  editor.thumbSize = 64
  editor.transforming = false
  editor.resized = 2
  editor.warnings = true
  editor.drawQueue = {}
  editor.buttonQueue = {}
  editor.undos = {}
  editor.maxUndos = 100
  editor.zmode = false
  editor.setUpdateLinks = true
  editor.roomPreviews = {}
  editor.setPreviews = {}
  editor.roomLink = {}
  editor.roomPreview = { w = 70, h = 48 }
  editor.roomContextMode = "Rooms"
  editor.toolModels = {}
  editor.transformTime = 0
  editor.toolTip = { text = "", prev = "", x = 0, y = 0 }
  
  editor.autoSaveGraphic = {
    y = 10,
    width = 20,
  }
  editor.autoSaveEnabled = config.autoSaveEnabled
  editor.autoSaveTime = config.autoSaveTime or 10
  editor.saveTime = 0

  editor.setPreview = nil
  editor.stackStart = nil
  editor.stackPreview = { w = 100, h = 80 }

  ctx.roomZoom = ctx.pageView and 1 or 0
  editor.animHovered = { key = "", object = "" }

  editor.stackEnd = 0
  editor.roomLink = nil
  editor.roomLinkPreview = { x = -100, y = 75, w = 100, h = 80, notri = true, noshadow = true, preview = true }
  editor.drawingLink = false
  editor.cachedLink = nil
  editor.linkRes = 6

  editor.colorTip = {}
  editor.previewTri = { vec3(), vec3(), vec3() }

  editor.renderLines = config.renderLines or 3
  editor.lineRenderModes = 3
  editor.gridEnabled = config.gridEnabled
  editor.gridFade = editor.gridEnabled and 1 or 0
  editor.pasteboard = {}

  editor.hoveredEntity = {}
  editor.selectedEntities = {}
  editor.rectSelecting = false

  editor.droppingItem = false
  editor.dropScale = 1

  editor.selectAlpha = 0
  editor.selectBounds = {}
  editor.stageHovered = true
  editor.selectAll = false
  editor.numSelected = 0

  sidebar = {
    flatUI = false,
    darkMode = true,
    darkness = 30,
    darkShade = 30,
    lightShade = 220,
    visible = false,
    ox = 0,
    x = screenW + 10000,
    y = -4,
    width = 360,
    height = screenH,
    maxWidth = 450, --ignore, these are set in updatePanels as *x.2 of splitwidth
    minWidth = 370, --ignore, these are set in updatePanels as *x   of splitwidth
    splitWidth = 300,
    resizeSpace = 6,
    titleH = 20,
    titleHp = 25,
    panelTitleHeight = 30,
    row = 20,
  }

  sidebar = lume.merge(sidebar, config.sidebar or {})

  sidebar.owidth = sidebar.width
  sidebar.oldwidth = sidebar.width
  sidebar.oldheight = sidebar.height

  editor.mouseStored = { x = 0, y = 0 }
  editor.savedMouse = { x = 0, y = 0 }
  editor.shapeEditMouse = { x = 0, y = 0 }
  editor.mouseLocalDrop = {}

  cursors = {
    scale_h = mouse_newCursor(load_imageData("cursor_scale_h"), 8, 8),
    scale_d1 = mouse_newCursor(load_imageData("cursor_scale_d1"), 8, 8),
    scale_d2 = mouse_newCursor(load_imageData("cursor_scale_d2"), 8, 8),
    scale_w = mouse_newCursor(load_imageData("cursor_scale_w"), 8, 8),
    rotate = mouse_newCursor(load_imageData("cursor_rotate"), 0, 0),
    curve = mouse_newCursor(load_imageData("cursor_curve"), 0, 0),
    corner = mouse_newCursor(load_imageData("cursor_corner"), 0, 0),
    move = mouse_newCursor(load_imageData("cursor_move"), 0, 0),
    hand = mouse_newCursor(load_imageData("cursor_hand"), 16, 16),
    grab = mouse_newCursor(load_imageData("cursor_grab"), 12, 12),
    plus = mouse_newCursor(load_imageData("cursor_plus"), 6, 6),
    fill = mouse_newCursor(load_imageData("cursor_fill"), 6, 21),
    lasso = mouse_newCursor(load_imageData("cursor_lasso"), 4, 15),
    eraser = mouse_newCursor(load_imageData("cursor_erase"), 6, 14),
  }

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

  editor.preTransform = false

  game.levelClicked = false

  gfxStats = false
  sidebar.height = screenH

  editor.panels = {
    planPanel,
    pathsPanel,
    animatePanel,
    galleryPanel,
    -- scriptsPanel,
  }

  -- create editor grid mesh
  local scale = 1000
  local gap = 50
  local verts = {}
  local z = 0
  for i = 0, (scale / gap) * 2, 1 do
    table.insert(verts, { -scale + i * gap, -scale, 0, 1, 0, 0, 1, 1, 1, 1 })
    table.insert(verts, { -scale + i * gap, scale, z, 1, 0, 0, 1, 1, 1, 1 })
    table.insert(verts, { -scale + i * gap, -scale, 0, 1, 0, 0, 1, 1, 1, 1 })
  end
  for i = 0, (scale / gap) * 2, 1 do
    table.insert(verts, { scale, -scale + i * gap, 0, 1, 0, 0, 1, 1, 1, 1 })
    table.insert(verts, { -scale, -scale + i * gap, z, 1, 0, 0, 1, 1, 1, 1 })
    table.insert(verts, { scale, -scale + i * gap, 0, 1, 0, 0, 1, 1, 1, 1 })
  end

  editor.debugGrid = gfx.newMesh(zformat, verts, "triangles")
  editor.sphereMesh = newSphereMesh(32)

  editor.gizmo = {
    dragging = false,
    rotating = false,
    scaling = false,
    sorting = false,
    transform = mat4(),
    translation = vec3(0),
    axis = vec3(0, 1, 0),
    angle = 0,
    start = { diff = { x = 0, y = 0 } },
  }

  local mul = 1
  editor.gizmo.ringMeshThin = newRingMesh(9.4*mul, 9.6*mul, 2, 0, pi)
  editor.gizmo.ringMeshFat = newRingMesh(9.1*mul, 9.9*mul, 2, 0, pi)
  editor.gizmo.circleMeshThin = newRingMesh(9.4*mul, 9.6*mul, 2, 0, pi*2,true)
  editor.gizmo.circleMeshFat = newRingMesh(9.2*mul, 10*mul, 2, 0, pi*2,true)

  editor.gizmo.items = {
    rotate = {
      rot=true,
      name="rotate",
      color = { 0, 0, 0, 1 },
      model = {
        mesh = newRingMesh(9.4*mul, 9.6*mul, 2, 0, pi*2),
        transform = mat4():rotate(mat4(), pi / 2, vec3(0, 0, 1)),
        parent = editor.gizmo,
      },
    },
    trackball = {
      rot=true,
      name="trackball",
      color = { 0.3, 0.3, 0.3, 0.1 },
      model = {
        cullMode = "back",
        parent = editor.gizmo,
        mesh = editor.sphereMesh,
        transform = mat4()
      }
    },
    rotateZ = {
      name = "rotateZ",
      rot = true,
      a = -pi,
      plane = vec3(0, 0, -1),
      plane1 = vec3(1, 0, 0),
      plane2 = vec3(0, 1, 0),
      axis="Z",
      color = { 0.3, 0.3, 1, 1 },
      model = {
        wireframe = true,
        transform = mat4():rotate(mat4(), pi / 2, vec3(0, 0, 1)),
        parent = editor.gizmo,
      },
    },
    rotateX = {
      name = "rotateX",
      rot = true,
      a = -pi,
      plane = vec3(1, 0, 0),
      plane1 = vec3(0, 0, 1),
      plane2 = vec3(0, 1, 0),
      
      axis="X",
      color = {1,  0.3,  0.3, 1 },
      model = {
        wireframe = true,
        transform = mat4():rotate(mat4(), pi / 2, vec3(1, 0, 0)),
        parent = editor.gizmo,
      },
    },
    rotateY = {
      name = "rotateY",
      rot = true,
      a = -pi,
      plane = vec3(0, 1, 0),
      plane1 = vec3(1, 0, 0),
      plane2 = vec3(0, 0, 1),
      axis="Y",
      color = {  0.3,1, 0.3, 1 },
      model = {
        wireframe = true,
        transform = mat4():rotate(mat4():rotate(mat4(), pi / 2, vec3(0, 1, 0)), pi, vec3(1, 0, 0)),
        parent = editor.gizmo,
      },
    },
  }
end

function editor_drawSelectRect()
  if
    game.levelClicked
    and (not animatePanel.open or animatePanel.l.remodel or animatePanel.l.repose)
    and (editor.state == "Normal" or editor.state == "Transforming")
  then
    editor.rectSelecting = true
    editor.selectBounds.x2 = mouse.x / game.resolution
    editor.selectBounds.y2 = mouse.y / game.resolution
    editor.selectBounds.x1 = editor.selectBounds.x1 or mouse.x
    editor.selectBounds.y1 = editor.selectBounds.y1 or mouse.y
    editor.selectBounds.h = math.abs(editor.selectBounds.y2 - editor.selectBounds.y1)
    editor.selectBounds.w = math.abs(editor.selectBounds.x2 - editor.selectBounds.x1)
    editor.selectBounds.x = editor.selectBounds.x2 > editor.selectBounds.x1 and editor.selectBounds.x1
      or editor.selectBounds.x2
    editor.selectBounds.y = editor.selectBounds.y2 > editor.selectBounds.y1 and editor.selectBounds.y1
      or editor.selectBounds.y2
    editor.selectAlpha = 0.8

    editor.modelHovered = nil
  else
    editor.rectSelecting = false
  end

  if editor.selectBounds.h and editor.selectBounds.h > 4 and editor.selectBounds.w > 4 then
    editor_pushDraw(function()
    gfx.setColor(1, 1, 1, editor.selectAlpha / 8)
    gfx.rectangle(
      "fill",
      editor.selectBounds.x,
      editor.selectBounds.y,
      editor.selectBounds.w,
      editor.selectBounds.h
    )
    gfx.setLineWidth(1)
    gfx.setColor(1, 1, 1, editor.selectAlpha)
    gfx.rectangle(
      "line",
      editor.selectBounds.x,
      editor.selectBounds.y,
      editor.selectBounds.w,
      editor.selectBounds.h,
      2,
      2
    )
    gfx.setColor(0, 0, 0, editor.selectAlpha)
    --gfx.rectangle("line",selectBounds.x+1,selectBounds.y+1,selectBounds.w,selectBounds.h)
    end)
  end
  editor.selectAlpha = math.max(0, editor.selectAlpha - 0.1)
end

function editor_pushUndo(target, state)
  if target.undone ~= time then
    target.undone = time
    target.maxUndos = target.maxUndos or editor.maxUndos
    target.undoPos = target.undoPos or 0

    if #target.undos > target.maxUndos then
      table.remove(target.undos, 1) -- collectgarbage()
    end

    if target.undoPos > 0 then target.undos = lume.slice(target.undos, 1, math.max(1, #target.undos - target.undoPos)) end
    target.undoPos = 0

    table.insert(target.undos, state)
  end
end

function editor_undo(target)
  target.undoPos = target.undoPos ~= nil and target.undoPos or 0
  local first = target.undoPos == 0
  local undoPos = mc(0, shiftDown and target.undoPos - 1 or target.undoPos + 1, #target.undos)
  if undoPos ~= target.undoPos then
    if target.undoPos == 0 then
      undoPos = undoPos + 1
      target.pushUndoState("", true) --target.lastState and target.lastState.action or "",true)
    end

    target.undoPos = mc(0, undoPos, #target.undos)
    target.lastState = target.undos[mc(1, #target.undos - target.undoPos + 1, #target.undos)]
    local redo = shiftDown and target.undos[mc(1, #target.undos - target.undoPos, #target.undos)] or nil
    target.undoFunc(target, redo)
    editor_tempTip(shiftDown and "Redo " .. (redo and redo.action or "") or "Undo " .. (target.lastState.action or ""))
    if undoPos == 0 then table.remove(target.undos, #target.undos) end
  end
end

function editor_pushUndoState(action) end

function editor_undoFunc(target) end

function merge(t1, t2, depth, visited, prt)
  local visited = visited or {}
  local depth = depth or 5
  if depth > 0 then
    depth = depth - 1
    for k, v in pairs(t1) do
      if t1[k] and t2[k] then
        if type(v) == "table" then
          local vised = inTable(visited, v)
          if not vised then
            if v.isEntity then print("ent") end
            merge(t1[k], t2[k], depth, lume.concat(visited, { v }), prt)
          end
        else
          t1[k] = t2[k]
        end
      end
    end
  end
end

function editor_playLevel()
  ctx.editing = false
  editor.wascoding = editor.coding and scriptsPanel.key or ""
  editor.coding = false
  editor.curoom = lume.find(ctx.views, ctx.viewport)
  editor.resolution = game.resolution
  pathsPanel.wallCutting = false

  preCam = { angle = lume.clone(editor.viewport.camera.angle) }

  game_setResolution(game.playRes)
  editor_closePanel(scriptsPanel)
  game_unPause()
  editor_storeSelected()
  editor_togglePanel(pathsPanel, true)
  scene_destroy()
  scene_load()
end

function editor_exitEditor()
  namer_warning("Exit editor?", game_exit)
end

function editor_resumeEdit()
  editor.refreshSelection = true
  delayedCall(function()
    editor.refreshSelection = false
  end, 0.1)

  game_setResolution(editor.resolution)
  game_unPause()
  mouse_setVisible(true)
  ctx.editing = true
  scene_destroy()
  scene_load()
  editor_restoreSelected()
  paths_restorePathPanel()

  editor.viewport.camera.angle = lume.clone(preCam.angle)

  if editor.wascoding and editor.wascoding ~= "" then editor_openScript(editor.wascoding) end
end

function editor_storeSelected()
  editor.selectedIDs = {}
  if not editor.selectedEntities then return end
  for i, e in ipairs(editor.selectedEntities) do
    table.insert(editor.selectedIDs, e.uid)
  end
end

function editor_restoreSelected(ids)
  lume.clear(editor.selectedEntities)
  for i, d in ipairs(ids or editor.selectedIDs or {}) do
    table.insert(editor.selectedEntities, scene_getEntityByID(d))
  end
end

function editor_setState(state) end

function editor_updateEditor()
  bench.push("editor_update")

  if not ctx.isAnim then lwindow.setTitle(ctx.config.name) end

  if editor.intersect and editor.debug.trace_mouse_intersect then trace("intersect", editor.intersect.x, editor.intersect.y, editor.intersect.z) end

  editor.saveStep = ctx.editing and editor.autoSaveEnabled and editor.saveStep + engine.dt or editor.saveStep
  editor.buttonsClipped = 0
  editor.buttonsDrawn = 0
  editor.resized = editor.resized > 0 and editor.resized - 1 or 0
  editor.refresh = false
  editor.nearestPlane = 9999999
  editor.pointing = false
  editor.drawTip = false
  editor.viewHovered = nil
  --mouse.locked          = false
  mouse.z = 0
  editor.setUpdateLinks = false
  editor.buttonQueue = {}
  editor.drawQueue = {}

  editor_traceEditorHelp()
  game.wobbleRoom = ""

  if not editor.tempCam and editor.preTempCam then
    editor_resetCamera(editor.preTempCam.index, true)
    ctx.viewport.camera = editor.preTempCam
  elseif editor.tempCam then
    editor_resetCamera(editor.tempCam, true)
    editor.tempCam = nil
  end

  gfx.fontSize(12)

  editor.wasRoomView = ctx.pageView
  editor.gridFade = easeTo(editor.gridFade, editor.gridEnabled and 1 or 0, 3)
  editor.animHovered = { key = "", object = "" }

  scene_setView(ctx.viewport)
  game_switchSet()
  game_switchRoom()

  if ctx.editing then editor_click() end

  if editor.viewport.room then
    camera_control(editor.viewport.camera)
    editor_transformEntities(editor.viewport)
    editor_editorKeys()
    save_update()
    editor_saveSize()
    paths_dropNewPathPoint()
    editor_mouseUpdates()

    if
      (editor.state == "Normal" or editor.state == "Transforming" or (not game.levelClicked and animatePanel.open))
      and (not ctx.pageView or editor.viewHovered)
      and editor.stageHovered
    then
      editor.readyToClick = true
    else
      editor.readyToClick = false
    end

    if editor.state == "Panning" then camera_pan(editor.viewport.camera) end
    

  end

  gfx.setShader()
  gfx.setDepthMode()

  editor_updatePanels()

  if #contextMenu.items > 0 then
    editor_updateContextMenu()
  else
    if editor.colorTip.open then
      input_unlockMouse()
      editor_updateColorTip()
      input_lockMouse()
    end
    if editor.state == "Context Menu" then editor.state = "Normal" end
  end

  if mouse.down.L or mouse.down.R then
    editor_setToolTip("")
    editor.mouseStill = -120
    editor.toolTip.a = 0
  else
    editor_toolTip()
  end
  namer_update()

  bench.pop("editor_update")
end

function editor_toggleRooms()
  ctx.pageView = not ctx.pageView
end

function editor_copyEntities()
  editor.pasteboard = {}
  for i, entity in lume.ripairs(editor.selectedEntities) do
    if not entity.simple then table.insert(editor.pasteboard, deepcopy(entity)) end
  end
end

function editor_pasteEntities()
  editor_pushUndoState("Paste")

  lume.clear(editor.selectedEntities)

  --table.sort(editor.pasteboard, function(a, b) return a.z > b.z end)

  for i, v in ipairs(editor.pasteboard) do
    local pasted = deepcopy(v)
    pasted.x = pasted.x + 30
    pasted.y = pasted.y + 30
    pasted.z = pasted.z + 30
    pasted.uid = uuid()
    table.insert(editor.selectedEntities, scene_newEntity(ctx.viewport.room, scene_returnObject(pasted)))
  end
  log("pasted", #editor.selectedEntities)
end

function editor_setPlayer()
  if #editor.selectedEntities == 1 then namer_warning("Set Player", function()
    room.player = editor.selectedEntities[1].uid
  end) end
end

function editor_renameInstance(entity)
  namer_typeString("Rename Instance", entity.name, { x = entity.render.x, y = entity.render.y }, function(x)
    for i, e in ipairs(room.entities) do
      if e.name == x then
        editor_tempTip("Name Taken")
        return
      end
    end
    entity.name = x
  end)
end

function editor_editPort()
  ctx.pageView = true
end

function editor_editFloor()
  ctx.pageView = false
end

function editor_setCamera(index)
  local view = editor.viewport
  view.room.cameras[index] = camera_new(view.camera)
  view.cameraID = index
  editor_tempTip("Set camera " .. index)
  print("SET CAM")
end

function editor_resetCamera(index, notip)
  local view = editor.viewport
  if view.room.cameras[index] then
    view.cameraID = index
    view.camera = view.room.cameras[index] and deepcopy(view.room.cameras[index]) or view.camera
    if not notip then editor_tempTip("Switched to camera " .. index) end
  end
end

function editor_setViewport(index)
  room.viewports[index] = deepcopy(ctx.viewport.viewport)
  ctx.viewport.viewportID = index
  ctx.viewport.viewport = room.viewports[index]
  editor_tempTip("Set viewport " .. index)
end

function editor_resetViewport(index)
  if room.viewports[index] then
    ctx.viewport.viewportID = index
    ctx.viewport.viewport = room.viewports[index] and deepcopy(room.viewports[index]) or ctx.viewport.viewport
    editor_tempTip("Switched to viewport " .. index)
  end
end

function editor_drawGrid(view)
  if not (ctx.roomZoom < 1 and editor.gridFade > 0.05 and view.key == editor.viewport.key and not editor.drawingLink) then return end
  bench.push("drawGrid")

  local gridAlpha = editor.gridFade * 0.3 * (1 - ctx.roomZoom)

  gfx.setColor(1, 1, 1, gridAlpha)
  editor.gridMat = mat4.rotate(mat4(), mat4(), pi / 2, axes.x)
  
  -- gfx.draw3D(editor.sphereMesh)
  gfx.setWireframe(true)
  -- gfx.draw3D(editor.sphereMesh,mat4.translate(mat4(),mat4(),vec3(15,0,0)))
  shaders.forwardRender:send("drawShadowed", false)
  shaders.forwardRender:send("fadePoint", { view.camera.pos.x, view.camera.pos.y, view.camera.pos.z })
  shaders.forwardRender:send("fadeDist", 600 + math.abs(ctx.viewport.camera.pos.y) * 2)
  gfx.draw3D(editor.debugGrid, editor.gridMat)
  shaders.forwardRender:send("fadeDist", 0)
  shaders.forwardRender:send("drawShadowed", view.drawShadowed)
  gfx.setWireframe(false)

  bench.pop("drawGrid")
end


function editor_mouseUpdates()
  if (not ctx.pageView or ctx.viewport == editor.viewHovered) and not scriptsPanel.focused then
    if (not ctx.pageView or editor.viewHovered) and editor.stageHovered and not mouse.locked and not mouse_in(scriptsPanel.border) then
      editor_clickStage()
    end
  end

  mouse_pushRelease(editor_releaseMouse)
  mouse_pushReleaseR(editor_releaseMouse)

  camera_scrollZoom(editor.viewport.camera)
  editor_storeMouse()
  editor_previewDrop()
end

function editor_previewDrop()
  if ctx.viewport.camera and not contextMenu.open then
    editor.dropPoint = editor.intersect and vec3(editor.intersect) or editor.dropPoint

    editor.mouseLocalDrop = editor.intersect and { x = editor.intersect.x, y = editor.intersect.y, z = editor.intersect.z }
      or mth.vec3Table(vec3(getCameraPlaneCoords()))
      or { x = 0, y = 0, z = 0 }

    editor.mouseLocalDrop.dropped = true
  end

  if type(editor.mouseLocalDrop) ~= "table" then return end

  if #galleryPanel.selected == 0 then return end
  -- for i,item in ipairs(galleryPanel.selected) do
  --  if stageHovered and droppingItem and getSymbol(item.key)~=nil then
  --    local frame = getSymbol(item.key).animations[ getSymbol(item.key).default].layers[1].frames[1]
  --    local dim   = (frame.w>frame.h) and frame.w*cam.zoom or frame.h*cam.zoom
  --    editor.dropScale   = easeTo(editor.dropScale,mouseIn(sidebar) and 1 or dim/thumbSize,8)
  --      gfx.setBlendMode("alpha","premultiplied")
  --      gfx.setColor(1,1,1,1)
  --      --gfx.draw(load_imageTexThumb(item.key), mouse.x-editor.dropScale*thumbSize*0.5, mouse.y-editor.dropScale*thumbSize*0.5,0, editor.dropScale,editor.dropScale)
  --  end
  -- end
end

function editor_storeMouse()
  editor.savedMouse = {
    x = mouse.x,
    y = mouse.y,
  }
end

function editor_contextColorPicker(target)
  editor.pickingColor = true
  contextMenu.colorTarget = target
  editor.storedMouse = vec2(mouse)
end

function editor_toggleLines()
  editor.renderLines = editor.renderLines < editor.lineRenderModes and editor.lineRenderModes or 1
end

function editor_loadSet(key)
  scene_loadSet(key)
  editor.editingPage = key
end

function editor_showInFinder(dir)
  local spaced = dir:gsub("%s+", "\\ ")
  if os.name == "Windows" then
    local thing = 'explorer /select, "' .. dir .. '"'
    os.execute(thing)
  else
    os.execute("open -R file://" .. spaced)
  end
end

function editor_dropFile(file, copy)
  if file ~= nil then engine.fileDropped = true end
  if engine.fileDropped and ctx.editing then
    local filename = type(file) == "string" and file or file:getFilename()
    local basename = basename(filename)
    local extension = string.lower(filename:sub(-3, -1))
    local name = uuid()
    local destFolder = "Game"
    local destTags = {}
    local sourcePath = '"' .. filename .. '"'

    if not editor.stageHovered and galleryPanel.hovered then destTags = copy and load_getSymbol(copy).tags or galleryPanel.selectedTags end

    print(extension)
    if multicheckOR(extension, { "mp3", "wav" }) then
      local to = ' "' .. PROJ_DIR .. "audio" .. "/" .. basename .. '"'
      if animatePanel.open then
        lfs.mkdir(LINK_DIR .. "/" .. "audio")
        print(os.copy .. " " .. sourcePath .. " " .. to)
        os.execute(os.copy .. " " .. sourcePath .. " " .. to)
        editor_setAnimationAudio(animatePanel, basename)
      end
    end

    if animatePanel.open then
      if multicheckOR(extension, { "png", "jpg" }) then
        if animatePanel.l.format == "raster" then
          local new = gfx.newImage(file)
          gfx.push("all")
          gfx.setCanvas(animatePanel.f.canvas)
          gfx.draw(new)
          gfx.pop()
        end
      end
    else
      if extension == "png" or extension == "obj" then
        local from = copy and ' "' .. PROJ_DIR .. copy .. '.png"' or sourcePath
        local to

        if extension == "png" then
          to = ' "' .. PROJ_DIR .. "symbols" .. "/" .. name .. '.png"'
        elseif extension == "obj" then
          to = ' "' .. PROJ_DIR .. "models" .. "/" .. name .. '.obj"'
        end

        log(os.copy .. " " .. from .. " " .. to)
        os.execute(os.copy .. " " .. from .. " " .. to)

        local addedImg = load_loadSymbol(name, destFolder, true)
        addedImg.tags = destTags
        load_tagImage(addedImg)

        if editor.stageHovered then log(editor_newEntity(name, editor.mouseLocalDrop).uid) end

        engine.fileDropped = false
        return name
      end
    end
  end
end

function editor_updatePanels()
  if not editor.panels then return end

  editor_positionSidebar()
  editor_toggleSidebar()
  editor_drawSidebarBase()

  local totalHeight = 4
  local panelWidth = sidebar.width - 18

  -- if not animatePanel.open then
  sidebar.visible = false
  -- end
  sidebar.x = screenW + 1000

  for i, panel in ipairs(editor.panels) do
    panel.isPanel = true
    panel.x = panel.x or 0
    if panel.docked and not panel.bottom then
      panel.oldY = panel.y
      panel.oldHeight = panel.height
      panel.y = easeTo(panel.y, totalHeight, 2)
      panel.x = sidebar.x + 7

      if panel.visible then totalHeight = totalHeight + (panel.open and panel.height + 2 or sidebar.titleH) end
    end
  end

  sidebar.height = 2 -- easeTo(sidebar.height, totalHeight , 2)

  --editor.titlebar = {x=-10,y=0,width=screenWidth+20,height=20,alpha=1,frost=0}
  

  local preHovered = true
  if not editor.stageHovered then
    mouse_set(nothing)
    mouse_setR(nothing)
    mouse_setM(nothing)
  end
  gfx.fontSize(12)

  for i, panel in ipairs(editor.panels) do
    -- bench.push(panel.name)
    panel.width = panel.width or panelWidth
    if panel.docked then
      -- editor_togglePanel(panel)
    else 
      editor_dragPanel(panel) 
    end
  
    panel.update(panel)
    if panel.visible and (panel.open or not panel.docked) and mouse_in(panel.border or panel) then
      panel.hovered = true
      preHovered = false
    else
      if contextMenu.open and mouse_in(contextMenu) then
        preHovered = false
      elseif panel.docked == false and panel ~= animatePanel then
        if not mouse_in(panel.border or panel) then mouse_pushDown(function()
          panel.focused = false
        end) end
        if panel.open and not contextMenu.open and not panel.stayFloat or sidebar.visible == true then
          mouse_pushDown(function()
            editor_closePanel(panel)
          end)
        end
      end
    end
    -- bench.pop(panel.name)
  end

  if
    mouse.locked
    or (ctx.pageView and altDown and not animatePanel.open)
    or mouse_in(sidebar.x - sidebar.resizeSpace / 2, sidebar.y, sidebar.width + sidebar.resizeSpace, sidebar.height + 8)
    or mouse_in(engine.trace.x, 0, engine.trace.width, engine.trace.height)
  then
    preHovered = false
  end
  editor.stageHovered = preHovered

  if not editor.stageHovered then mouse_setVisible(true) end
end

function editor_closePanel(panel, func)
  scene_tween(panel, { y = panel.y + 16, alpha = 0 }, 0.1, "easeIn", 0.5, function()
    panel.x = screenW
    panel.y = screenH
    panel.open = false
    (func or nothing)()
  end)
end

function editor_floatPanel(panel, target)
  target = target or mouse
  panel.docked = false
  panel.open = true
  panel.x = target.x
  panel.y = math.min(target.y + 16, screenH - 200)
  panel.alpha = 0.005
  editor_closeContextMenu()
  scene_tween(panel, { y = panel.y - 16, alpha = 1 }, 0.1, "easeOut", 1)
end

function editor_toggleSidebar()
  if input_keyPressed("tab") and ctx.editing then
    sidebar.visible = not sidebar.visible
    sidebar.touched = true
  end
  sidebar.width = mc(sidebar.minWidth, sidebar.width, sidebar.maxWidth)
end

function editor_positionSidebar()
  sidebar.minWidth = animatePanel.open and 260 or sidebar.splitWidth
  local targetX = sidebar.visible and screenW - sidebar.width + 4 or screenW + 50
  sidebar.x = easeTo(sidebar.x, targetX, 4)
  sidebar.alpha = 1 - (sidebar.x - (screenW - sidebar.width + 4)) / ((screenW + 50) - (screenW - sidebar.width + 4))
end

function editor_activateSidebar()
  sidebar.touched = false
  sidebar.wasOpen = sidebar.visible
  sidebar.visible = true
end

function editor_deactivateSidebar()
  --if not sidebar.touched then
  sidebar.visible = sidebar.wasOpen
  --end
end

function editor_saveSize()
  sidebar.oldwidth = sidebar.width
  sidebar.oldheight = sidebar.height
end

function editor_resizePanel()
  --sidebar.width     = mc(sidebar.minWidth, screenWidth-mouse.x, sidebar.maxWidth)

  sidebar.width = mf(easeTo(sidebar.width, mc(sidebar.minWidth, screenW - mouse.x + sidebar.resizeSpace, sidebar.maxWidth), 2))

  sidebar.x = mf(easeTo(sidebar.x, mc(screenW - sidebar.maxWidth, mouse.x - sidebar.resizeSpace, screenW - sidebar.minWidth) + 4, 2))

  mouse_setCursor(cursors.resize)
end

function editor_togglePanelWithSelected(panel, one)
  if #editor.selectedEntities == 0 or anySelectedSimple or (one and #editor.selectedEntities > 1) then
    panel.visible = false
  elseif (one and #editor.selectedEntities == 1) or (not one and #editor.selectedEntities > 0) then
    panel.visible = true
    editor_togglePanel(panel, false)
  end
end

function editor_togglePanel(panel, closeOnly, func)
  local closeOnly = closeOnly or false
  toggleBox.x = panel.x
  toggleBox.y = panel.y
  toggleBox.width = math.max(panel.width / 4, 50)
  toggleBox.height = sidebar.titleH
  if panel.open and panel.visible and mouse_in(toggleBox) then
    mouse_set(function()
      if closeOnly then
        panel.open = false
      else
        panel.open = not panel.open
      end
      local funced = func and func() or nil
    end)
  end
end

function editor_dragPanel(panel)
  if panel.open and panel.visible and not panel.docked and mouse_in(lume.merge(panel.border or panel, { h = sidebar.titleH })) then
    mouse_set(function()
      panel.dragOffset = { x = mouse.x - panel.x, y = mouse.y - panel.y }
      panel.dragging = true
      editor.state = "Dragging"
    end)
  end

  if editor.state == "Dragging" and panel.dragging then
    panel.x = mc(16, mouse.x - panel.dragOffset.x, screenW - (panel.border and panel.border.width or panel.width) - 16)
    panel.y = mc(16, mouse.y - panel.dragOffset.y, screenH - (panel.border and panel.border.height or panel.height) - 16)
    mouse_pushRelease(function()
      editor.state = "Normal"
    end)
  end
end

function editor_drawSidebarBase()
  if sidebar.height < 30 or sidebar.x > screenW then return end
  sidebar.darkness = easeTo(sidebar.darkness, sidebar.darkMode and sidebar.darkShade or sidebar.lightShade, 4)
  gfx.setBlendMode("alpha")
  editor_drawContextBack({
    x = sidebar.x + 2,
    y = sidebar.y,
    width = sidebar.width + 20,
    height = sidebar.height + 6,
    frost = 1,
    shadow = 0,
  })
end

function editor_drawPanel(panel)
  gfx.setBlendMode("alpha")
      
  if panel.backTex then
    gfx.setColor(1, 1, 1, panel.alpha)
    gfx.draw(panel.backTex, 0, 0, 0, panel.width / panel.backTex:getWidth(), panel.height / panel.backTex:getHeight())
  else
    panel.backingRect = panel.backingRect or {}
    panel.backingRect.x = panel.x - 2
    panel.backingRect.y = panel.y - 2
    panel.backingRect.w = panel.width + 8
    panel.backingRect.h = panel.height + 8 + (panel.bonusHeight or 0)
    panel.backingRect.alpha = panel.alpha or 1
    panel.backingRect.color = panel.color
    panel.backingRect.frost = 0
    panel.backingRect.shadow = 0.2
    panel.backingRect.sharp = panel.sharp
    editor_drawContextBack(panel.backingRect)
  end

  if not panel.noTitle then 
    editor_drawPanelTitle(panel) 
  end

  gfx.setColor(1, 1, 1, 1)
end

function editor_drawPanelTitle(panel)
  gfx.setFont(fonts.interfont, 14)
  gfx.setColor(1, 1, 1, 1)
  gfx.print(panel.name, panel.x + 4, mf(panel.y + 2))
end

function editor_shapeEditor(target, scale, previewArea, activeArea, size)
  local scale = scale or 1
  local activeArea = activeArea or previewArea
  local size = size or previewArea
  local res = previewArea.res or 1
  local dotSize = (previewArea.dotSize or 8) * res
  local changeState = editor.state == "Viewport" and "Normal" or editor.state

  if not editor.stageHovered then return end

  previewArea.limit = previewArea.limit or function()
    return true
  end
  target.draggingPoint = target.draggingPoint or { i = 0, j = 0 }

  editor.shapeEditMouse = (mouse_in(previewArea) or previewArea.out)
      and {
        x = (mouse.x - activeArea.x) / (size.w * scale),
        y = (mouse.y - activeArea.y) / (size.h * scale),
      }
    or { x = editor.shapeEditMouse.x, y = editor.shapeEditMouse.y }
  local close = false
  -- trace("previewArea.close",previewArea.close)
  for i, s in lume.ripairs(target.shapes) do
    local tempPoly = {}
    local shapeHovered = target.polys and point_in_poly(target.polys[i], editor.shapeEditMouse) or false

    target.polys = target.polys or {}
    target.polys[i] = {}

    for j, p in lume.ripairs(s) do
      p.sx = p.x
      p.sy = p.y
      -- CURRENT POINT
      local point = { x = activeArea.x + size.w * scale * p.x, y = activeArea.y + size.h * scale * p.y }
      -- LAST POINT (FOR LINES)
      local prevpoint = {
        x = activeArea.x + size.w * scale * s[(j > 1) and j - 1 or #s].x,
        y = activeArea.y + size.h * scale * s[(j > 1) and j - 1 or #s].y,
      }
      -- LINE
      local lineHovered = false

      -- MOVE POINT TO MOUSE
      local dragging = target.draggingPoint.i == i and target.draggingPoint.j == j

      if mouse_in(previewArea) and previewArea.limit() and target.draggingPoint.i == 0 and target.draggingPoint.j == 0 then
        point.hovered = (math.dist(point.x, point.y, mouse.x, mouse.y) < dotSize)
        point.near = (math.dist(point.x, point.y, mouse.x, mouse.y) < dotSize * 2)
        prevpoint.hovered = (math.dist(prevpoint.x, prevpoint.y, mouse.x, mouse.y) < dotSize)
        prevpoint.near = (math.dist(prevpoint.x, prevpoint.y, mouse.x, mouse.y) < dotSize * 2)
        lineHovered = distPointToLineSegment({ mouse.x, mouse.y }, { point.x, point.y }, { prevpoint.x, prevpoint.y }) < dotSize
      end

      -- START DRAGGING POINT
      mouse_pushClick(function()
        p.dx = editor.shapeEditMouse.x - p.x
        p.dy = editor.shapeEditMouse.y - p.y

        if point.hovered then
          target.draggingPoint = { i = i, j = j }
        elseif input_keyDown("v") and shapeHovered then
          changeState = "Viewport"
          target.draggingPoint = { i = i, j = 0 }
        end
      end)

      if
        p.dx
        and (
          (target.draggingPoint.i == i and target.draggingPoint.j == j)
          or (input_keyDown("v") and target.draggingPoint.j == 0 and target.draggingPoint.i == i)
        )
      then
        local offset = (activeArea.x - previewArea.x) / previewArea.w
        p.x = mc(-offset, editor.shapeEditMouse.x - p.dx, 1 - offset)
        p.y = mc(-offset, editor.shapeEditMouse.y - p.dy, 1 - offset)
      end

      -- RENOVE POINT
      if point.hovered then
        mouse_setR(function()
          if #s > 3 then
            table.remove(s, j)
          elseif #target.shapes > 1 then
            table.remove(target.shapes, i)
          end
        end, true)
      end

      gfx.setColor(editor.viewOutlineColor)
      gfx.setLineWidth(mc(1, dotSize / (previewArea.close and 2 or 2), 16))
      gfx.line(point.x * res, point.y * res, prevpoint.x * res, prevpoint.y * res)

      -- HOVER LINE
      gfx.setColor(1, 1, 1, 0.7)
      if mouse_in(previewArea) and lineHovered and not point.near and not prevpoint.near then
        mouse_set(function()
          target.draggingPoint = { i = i, j = j }
          table.insert(s, j, { x = editor.shapeEditMouse.x, y = editor.shapeEditMouse.y, dx = 0, dy = 0 })
        end)
        close = true
        gfx.setLineWidth(mc(1, dotSize / 3, 16))
      else
        gfx.setLineWidth(1)
      end

      gfx.line(point.x * res, point.y * res, prevpoint.x * res, prevpoint.y * res)

      -- PUSH POINTS TO POLYS
      table.insert(tempPoly, point.x * res)
      table.insert(tempPoly, point.y * res)
      table.insert(target.polys[i], p.x)
      table.insert(target.polys[i], p.y)

      -- DRAW POINTS
      gfx.setColor(editor.viewOutlineColor)
      gfx.circle("fill", point.x * res, point.y * res, res * ((point.hovered or dragging) and dotSize or dotSize / 2))
      gfx.setColor(1, 1, 1, 0.7)
      gfx.circle("fill", point.x * res, point.y * res, res * ((point.hovered or dragging) and dotSize / 2 or dotSize / 4))

      if (point.near or lineHovered) and not game.levelClicked then
        changeState = "Viewport"
        mouse_setCursor(defaultCursor)
      end

      if dragging then
        for l, p2 in lume.ripairs(s) do
          local dist = math.dist(p.x, p.y, p2.x, p2.y)
          if l ~= j and dist < 0.025 then
            local ang = math.angle(p.x, p.y, p2.x, p2.y)
            --p2.x=p2.x+(0.026-dist)*math.sin(ang)
            --p2.y=p2.y+(0.026-dist)*math.cos(ang)
          end
        end
      end
    end

    if not previewArea.skipOutline then
      -- DRAW SHAPE LINE
      gfx.setLineWidth(3)
      gfx.polygon("line", tempPoly)
    end
  end

  if previewArea.close then previewArea.close = false end
  if close then previewArea.close = true end
  -- trace("previewArea.close",previewArea.close)
  editor.state = changeState

  if mouse_in(previewArea) then
    --  target.draggingPoint={i=0,j=0}
  end
  mouse_pushRelease(function()
    target.draggingPoint = { i = 0, j = 0 }
  end)
end

function editor_parseShapes(source, dest)
  dest = lume.clear(dest or {})
  for i, s in ipairs(source) do
    table.insert(dest, {})
    for j, p in ipairs(s) do
      if j % 2 == 0 then
        dest[i][#dest[i]].y = p
      else
        table.insert(dest[i], { x = 1, y = 1 })
        dest[i][#dest[i]].x = p
      end
    end
  end
end

function editor_tempTip(tip, time)
  if not config.tempTips then return end
  editor_setToolTip(tip, { temp = true, time = time or 1.5 })
  -- print(tip)
end

function editor_setToolTip(tip, args)
  if tip ~= "" then editor.lastTip = tip end

  editor.toolTip = { text = tostring(tip) }

  if args then
    args.time = args.time * 60
    args.x = args.x or screenW
    args.y = args.y or 0
    editor.toolTip = lume.merge(editor.toolTip, args)
  end
end

function editor_toolTip()
  if mouse.movedSinceClick then
    editor.mouseStill = (editor.toolTip.text == "") and editor.mouseStill - 1
      or galleryPanel.hovered and (galleryPanel.anyHoveredItem and editor.mouseStill + 2 or editor.mouseStill + 1)
      or ((mouse.x == editor.savedMouse.x and mouse.y == editor.savedMouse.y) and editor.mouseStill + 1 or editor.mouseStill - 0.1)
  end

  local toolTime = 20

  editor.mouseStill = mc(0, editor.mouseStill, toolTime + 10)

  editor.toolTip.a = mc(0, editor.toolTip.time and math.min(toolTime, editor.toolTip.time / 60) or (editor.mouseStill - toolTime) / 10, 1)

  if editor.toolTip.time then editor.toolTip.time = editor.toolTip.time - 2 end

  if
    (editor.mouseStill > toolTime or (editor.toolTip.time and editor.toolTip.time > 0))
    and (editor.state == "Normal" or editor.state == "Context Menu")
    and not namer.enabled
  then
    editor.drawTip = true
  end

  editor.toolTip.prev = editor.toolTip.text

  editor.toolTip.text = (editor.toolTip.time and editor.toolTip.time > 0) and editor.toolTip.text or ""
end

function editor_drawTip(tip)
  local fsize = (tip.fontSize or 11) + 1
  gfx.setFont(fonts.interfont, fsize)
  local tip = lume.clone(tip)
  local otip = lume.clone(tip)
  local targ = tip.y and true or false
  local h = tip.h and true or false
  local tri = ((not tip.time or tip.time <= 0) and not tip.notri)
  local notip = false
  tip.text = tip.text == "" and (not tip.cursor and editor.lastTip) or tip.text
  local lines

  if not tip.text or tip.text == "" then
    tip.text = ""
    tip.lines = 0
  elseif not (tip.nowrap or tip.popup) then
    tip.wrapped, lines, tip.longest = lume.wordwrap(tip.text, 25)
    -- tip.text = lume.trim(tip.text == "" and "" or tip.wrapped)
    if tip.text == "" then
      tip.lines = 1
    else
      _, tip.lines = (tip.text .. ""):gsub("\n", "\n")
    end
    if tip.cursor then
      local i = 0
      tip.curse = ""
      while i < #tip.wrapped + 1 do
        i = i + 1
        if i <= #tip.wrapped and tip.wrapped:sub(i, i + 1) == "\n" then
          tip.curse = tip.curse .. "\n"
        elseif #tip.curse == tip.cursor then
          tip.curse = tip.curse .. "|"
        else
          tip.curse = tip.curse .. " "
        end
      end
      -- trace("tip.curse",tip.curse)
    end
  end

  local bound = tip.free and function(a, b, c)
    return b
  end or mc
  local charw = gfx.currentFont:getWidth("w")
  local fw, fh = gfx.currentFont:getWidth(tip.longest or tip.text), gfx.currentFont:getHeight()

  
  gfx.setFont(fonts.interfont, fsize)
  if tip.nowrap then tip.lines = 0 end
  if tip.popup then tip.lines = 1 end
  tip.a = tip.a or 1
  tip.w = mf(tip.w or mc(80, fw + 16, 300))
  tip.h = mf(tip.h or 8 + fh * (tip.lines + 1) + fh * tip.lines * 0.5) + (tip.popup and 4 or 0)
  tip.x = mf(bound(8, mf((tip.x or mouse.x) - tip.w / 2), screenW - tip.w - 8))
  if tip.a == 0 then
    gfx.setFont(fonts.inconsolata, fsize)
    return tip
  end

  local top = 8 --editor and (editor.roomLink and editor.roomLinkPreview.y+editor.roomLinkPreview.h/3 or ((editor.stackEnd>0 and tip.x<editor.stackEnd) and 60 or 16)) or 8
  local upperPad = tip.dome and 0 or 8
  tip.y = mf(bound(top, tip.y and tip.y - tip.h or mouse.y - tip.h, screenH - tip.h - upperPad))
    - (tri and upperPad or (tip.y == 0 and 0 or -upperPad))

  if tip.nodraw then return tip end

  if tip.direction then
    if tip.direction == "down" then
    end
  end

  gfx.setBlendMode("alpha")

  if tip.backing then
    gfx.setColor(0.1, 0.1, 0.1, (tip.a ^ 1.3) * 0.5)
    gfx.rectangle("fill", tip.backing.x, tip.backing.y, tip.backing.w, tip.backing.h, 2)
  end

  if not tip.noshadow then
    if tip.h < 100 then
      shadow = shadow or load_imageTex("ContextShadowShort")
      local sx, sy = (tip.w / 512) * 1.4, (tip.h / shadow:getHeight()) * 2
      local w, h = sx * shadow:getWidth() / 2, sy * shadow:getHeight() / 2
      gfx.setColor(0, 0, 0, tip.a ^ 2)
      -- gfx.draw(shadow , tip.x+tip.w/2 - w , tip.y+tip.h/2 - h + 5*math.max(0,math.max(0,tip.h-30)/50)^2 , 0, sx,sy )
    else
      local r = 20
      gfx.setFont(fonts.inconsolata, fsize)

      tipShadow = editor_nineSlice(
        load_imageTex("ContextShadow"),
        { x = tip.x - r, y = tip.y - r + 5 + 20, w = tip.w + r * 2, h = tip.h + r * 2, a = 0.5 },
        tipShadow
      )
      
      -- gfx.setColor(0,0,0,tip.a^2)
      -- gfx.draw(tipShadow)
    end
  end

  local tipCol = tip.temp and 0.1 or 1
  local textCol = tip.temp and 1 or 0
  gfx.setColor(tipCol, tipCol, tipCol, (tip.amult and tip.a * tip.amult or tip.a))

  if type(tip.dome) == "number" then
    gfx.rectangle("fill", tip.x, tip.y + tip.dome, tip.w, tip.h - tip.dome, 3)
  elseif tip.dome then
    gfx.setLineWidth(4)
    gfx.circle("fill", tip.x + tip.w / 2, tip.y + tip.w / 2, tip.w / 2 )
    gfx.circle("line", tip.x + tip.w / 2, tip.y + tip.w / 2, tip.w / 2 )
    gfx.rectangle("fill", tip.x, tip.y + tip.w / 2, tip.w, tip.h - tip.w / 2, 3)
    gfx.rectangle("line", tip.x, tip.y + tip.w / 2, tip.w, tip.h - tip.w / 2, 3)
  else
    gfx.rectangle("fill", tip.x, tip.y, tip.w, tip.h, 3)
  end

  tip.hovered = mouse_in(tip)

  if tri then
    local x = bound(18, targ and otip.x or mouse.x, screenW - 18)
    gfx.polygon("fill", x - 8, tip.y - 1 + tip.h, x, targ and tip.y - 1 + tip.h + 6 or mouse.y - 2, x + 8, tip.y - 1 + tip.h)
    gfx.setColor(tipCol, tipCol, tipCol, ((tip.amult and tip.a * tip.amult or tip.a) ^ 2))
    gfx.setLineWidth(1)
    --gfx.line(x-8,tip.y+tip.h,x,targ and tip.y+tip.h+6 or mouse.y-2,x+8,tip.y+tip.h)
  end

  if tip.cursor and tip.curse then gfx.setFont(fonts.inconsolata, fsize) end

  gfx.setColor(textCol, textCol, textCol, tip.a * (tip.ta or 1))
  gfx.printf(tip.text, tip.x + (tip.indent or 0), (h or tip.lines == 0) and tip.y + 3 or tip.y + 3, tip.w - 4, tip.align or "center")

  if tip.cursor and tip.curse then
    local a = math.sin(tip.cursorTime * 8) / 2 + 0.5
    gfx.setColor(0.3, 0.4, 0.6, a)
    gfx.printf(tip.curse, tip.x, tip.y + 2, tip.w - 4, tip.align or "center")
    gfx.printf(tip.curse, tip.x - 0.3, tip.y + 2, tip.w - 4, tip.align or "center")
    gfx.printf(tip.curse, tip.x, tip.y + 3, tip.w - 4, tip.align or "center")
    gfx.printf(tip.curse, tip.x - 0.3, tip.y + 3, tip.w - 4, tip.align or "center")
  end
  -- gfx.printf(tip.text,tip.x,((h or tip.lines==0) and tip.y+3 or tip.y+3)+0.2,tip.w-4+0.3,tip.align or "center")
  return tip
end


function editor_buttonVisible(button)
  return not (button.x < -(button.w or button.width) or button.y < -(button.h or button.height) or button.x > screenW or button.y > screenH)
end


function editor_queueButton(button)
  editor_updateButton(button)  
    print("insert button")
    print_info(3)
    table.insert(editor.buttonQueue,button)
end

function editor_updateButton(button,offset)
  if not button then return end
  if not (button.x and button.width) then return end
  if not button or button.x < -(button.w or button.width) or button.y < -(button.h or button.height) or button.x > screenW or button.y > screenH then
    editor.buttonsClipped = editor.buttonsClipped + 1
    return
  end

  editor.buttonsDrawn = editor.buttonsDrawn + 1
  
  (button.update or nothing)(button)

  button.w = button.w or button.width
  button.h = button.h or button.height

  if button.font then gfx.setFont(button.font) end
  button.h = button.h or gfx.currentFont:getHeight()

  button.cx, button.cy = button.x + button.w / 2, button.y + button.h / 2

  if not (button.static or button.noClick) then
    local wasHovered = button.hovered
    local ox,oy = button.offset and button.offset.x or 0,button.offset and button.offset.y or 0
    button.hovered = mouse_in(button.x+ox,button.y+oy,button.w,button.h)

    if wasHovered and not button.hovered then
      (button.rollOff or nothing)(button)
    end

    if button.slider then

      local item = button
      item.align = item.align or "left"
      if button.hovered then
        mouse_set(function()
          editor.sliding = item
        end)
      end
      -- if item.slider.min and item.slider.max and item.slider.target[item.slider.prop] then
      --   item.slider.target[item.slider.prop] = mc(item.slider.min, item.slider.target[item.slider.prop], item.slider.max)
      -- end
      if item.slider.target then 
        if mouse.down.L and editor.sliding==item  then
          item.slider.target[item.slider.prop] =
            lerp(item.slider.min, item.slider.max, math.pow(mc(0, (mouse.x - (item.x+ox)) / item.width, 1), (item.slider.pow or 1)))
            item.slider.target[item.slider.prop] = mc(item.slider.min, item.slider.target[item.slider.prop], item.slider.max)
            -- print(mouse.x - (item.x+ox))
          if item.slide and mouse.moved then
            item.slide()
          end
          if item.slider.update then item.slider.update(item.slider.target) end
        end
        if item.slider.target[item.slider.prop] and item.round then
          item.slider.target[item.slider.prop] = lume.round(item.slider.target[item.slider.prop], item.round)
        end
        local prop = item.slider.target[item.slider.prop] or 0
        local num = lume.round(prop, item.slider.round or 0)
        local pct = (prop - item.slider.min) / (item.slider.max - item.slider.min)
        if item.slider.percent then num = tostring(lume.round(pct * 100,1)) .. "%" end
        item.text = (item.name == "" and "" or (item.name .. ": ")) .. tostring(num or "")
        local text = item.slider.noval and item.name or item.text
        item.text = item.nocap and text or capFirst(text)

        item[item.slider.style or "pos"] = math.pow(pct, 1 / (item.slider.pow or 1))
      end

      mouse_pushRelease(function()
        editor.sliding=false
      end)

    elseif button.hovered then
      if button.tip then editor_setToolTip(button.tip) end
      (button.hover or nothing)(button)
      if button.func then 
        mouse_set(function()
          if button.func then
            button.func(button)
            if button.closes then 
              editor_closeContextMenu()
              return
            end
            if button.toggle then
              button.toggle.targ[button.toggle.prop] = not button.toggle.targ[button.toggle.prop]
              local context = button.context
              if context then 
                if button.list then 
                  context.listing[button.parent or button] = false
                  editor_createMenuItems(context)
                end
              end
            end
          end
        end) 
      end
      if button.click then mouse_set(button.click) end
      if button.release then mouse_set(button.release, true) end
      -- if button.funcR then mouse_setR(button.funcR) end
      -- if button.releaseR then mouse_setR(button.releaseR, true) end
    end
  end


end

function editor_drawButton(button)
  if not button then return end
  button.w = button.width
  button.h = button.height
  if not (button.x and button.y and button.w and button.h) then return end
  if not button or button.x < -(button.w or button.width) or button.y < -(button.h or button.height) or button.x > screenW or button.y > screenH then
    editor.buttonsClipped = editor.buttonsClipped + 1
    return
  end

  local x,y,w,h = button.x,button.y,button.w,button.h

  gfx.setLineWidth(1)

  if not button.static then
    

    -- button.hovered = pointInBox2(mouse.x,mouse.y-1,x,y+1,w,h)

    local ccolr, ccolg, ccolb, ccola = 0,0,0,1
    if button.col then 
      ccolr, ccolg, ccolb, ccola = button.col.r or 0, button.col.g or 0, button.col.b or 0, button.col.a or 1
    end

    if button.hovered or button.underline or button.box or button.outline then 
      gfx.setColor(ccolr, ccolg, ccolb,
        (button.outline) and 1 or (button.hovered and 0.5 or 0.2) )
      if button.nolight then 
      elseif button.outline then 
        if button.twoTone then 
          -- gfx.rectangle("line", x -1 , y -1 , w +2, h+2 , button.corner or 2)
        else
          -- gfx.rectangle("line", x + 1, y + 1, w - 2, h - 2, button.corner or 2)
        end
      else 
        gfx.rectangle("fill", x , y , w , h , button.corner or 2)
      end
    end

    if button.gradient then
      if type(button.gradient) == "table" then
        -- button.gradient = gradientMesh("horizontal",button.gradient[1],button.gradient[2])
      end
      if button.gradient then
        -- gfx.draw(button.gradient,x,y,0,w, h)
      end
    end

    if button.slider and (button.pct or button.pos) then
      if button.pct then
        local w = math.max(2,(w - 4) * (button.pct or button.pos))
        gfx.setColor(0, 0, 0, 0.5)
        gfx.rectangle("fill", x + 2, y + 1, w, h - 4, w> 4 and 2 or 0)
        gfx.setColor(1, 1, 1, editor.sliding==button and 1 or button.hovered and 0.5 or 0.3)
        gfx.line(x + 2, y +h-1, x+2+w, y + h-1)
      end

      if button.pos then
        local x = x + button.pos * (w - 2)
        if button.twoTone then 
          gfx.setLineWidth(1)
          gfx.setColor(1, 1, 1, 0.8)
          gfx.line(x, y+h/ 2, x, y+h)
          gfx.setColor(0, 0, 0, 0.8)
          gfx.line(x, y, x, y+h/2)
        else
          -- gfx.setColor(1, 1, 1, editor.sliding==button and 1 or 0.5)
          -- gfx.line(x, y +3, x, y + h -9)
          
          gfx.setColor(1, 1, 1, editor.sliding==button and 1 or button.hovered and 0.5 or 0.3)
          gfx.line(x-1, y+1, x, y +4)
          gfx.line(x+1, y+1, x, y +4)
          gfx.line(x-1, y+h-1, x, y+h-4)
          gfx.line(x+1, y+h-1, x, y+h-4)
        end
      end
    end

    if button.underline then
      if type(button.underline) == "table" then
        gfx.setColor(unpackColor(button.underline))
      else
        gfx.setColor(1, 1, 1, 0.5)
      end
      gfx.line(x + 1, y + h -2, x + w - 1, y + h -2)
    end

  end

  buttonImages(button)

  local pfontSize = gfx.currentFontSize
  local fontSize = button.fontSize or gfx.currentFontSize

  if button.fontSize then gfx.fontSize(fontSize) end
  
  (button.draw or nothing)(button)

  if button.outline then
    if type(button.outline) == "table" then
      gfx.setColor(unpackColor(button.outline))
    else
      gfx.setColor(1, 1, 1, button.hovered and 0.8 or 0.4)
    end
    if button.twoTone then 
      gfx.rectangle("line", x  , y  , w , h , button.corner or 2)
    else
      gfx.rectangle("line", x + 1, y + 1, w - 2, h - 2, button.corner or 2)
    end
  end

  if button.text ~= nil and button.text ~= "" and not button.notext then
    gfx.setColor(1,1,1,1)
    if type(button.text) == "string" then
      if button.text:sub(1, 2) == "-l" then
        button.imgActual = true
        button.img = "path" .. capFirst(pathsPanel.loopTypes[tonumber(button.text:sub(3, 3))])
        buttonImages(button)
      elseif button.text:sub(1, 2) == "-w" then
        button.imgActual = true
        button.img = "wall" .. capFirst(pathsPanel.wallTypes[tonumber(button.text:sub(3, 3))])
        buttonImages(button)
      elseif button.text == "unticked" then
        button.box=true
        gfx.setColor(1,1,1,0.1)
        gfx.rectangle("line", x + 1, y+1 , w - 2, h-2 , 2)
      elseif button.text == "-rename" then
        button.img = "pen"
        buttonImages(button)
      elseif button.text == "ticked" then
        button.box=true
        gfx.line(x + 5, y + h / 2, x + w / 2 - 2, y + h - 7, x + w - 6, y + 5)
        gfx.setColor(1,1,1,0.1)
        gfx.rectangle("line", x + 1, y+1 , w - 2, h-2 , 2)
      elseif button.text == "-x" then
        local border = 5
        gfx.line(x + border, y + border, x + w - border, y + h - border)
        gfx.line(x + w - border, y + border, x + border, y + h - border)
      elseif button.text == "-search" and w < h * 1.5 then
        local dim = (h > w and w or h)
        gfx.circle("line", x + w / 2.3, y + h / 2.3, dim / 4.2, 16)
        gfx.circle("line", x + w / 2.3, y + h / 2.3, dim / 4, 16)
        gfx.circle("line", x + w / 2.3, y + h / 2.3, dim / 3.8, 16)
        gfx.line(x + w / 1.7, y + h / 1.7, x + w / 1.3, y + h / 1.3)
        gfx.line(x + w / 1.7, y + h / 1.7, x + w / 1.3, y + h / 1.3 + 0.2)
      --else
      --  gfx.printf("?", x, mf(y+h/6), w, "center")
      --end
      else

        local dotOff = button.text == "···" and -2 or 0
        local ellipseOff = button.text == "…" and 3 or 0
        local edge = button.full and 2 or 4
        local textX = x + edge - dotOff
        local textY = button.full and y + 2 or mf(y + (h - fontSize) / 2) - ellipseOff
        if button.harsh then gfx.setShader(shaders.harsh) end
        if button.cacheText then
          button.textObject = not button.refreshText and button.textObject or gfx.newText(gfx.currentFont, button.text)
          button.refreshText = false
        end
        if button.textObject then
          gfx.draw(button.textObject, textX, textY)
        else
          gfx.printf(
            (button.full or button.text == "···") and button.text or ellipses(button.text, w - 10),
            textX,
            textY,
            w - edge * 2 + dotOff * 2,
            button.align or "left"
          )
        end
        if button.harsh then gfx.setShader() end
      end
    else
      gfx.draw(button.text, x, y)
    end
  end
  if button.fontSize then gfx.fontSize(pfontSize) end
end

function buttonImages(button)
  local w,h = button.width, button.height
  if button.img then
    if button.img ~= "none" then
      button.cx, button.cy = button.x + w / 2, button.y + h / 2

      local img = type(button.img) == "string" and load_imageTex(button.img) or button.img
      local col = button.icol or button.col
      gfx.setColor(1, 1, 1, col and col.a or button.a or 1)
      if button.checkers then
        gfx.setColor(1, 1, 1, 1)
        gfx.draw(
          load_imageTex("checkersRounded"),
          button.x + 1.75,
          button.y + 1.75,
          0,
          (w - 3) / editor.thumbSize,
          (h - 3) / editor.thumbSize,
          w / editor.thumbSize,
          h / editor.thumbSize
        )
        return
      end
      local buttonImg = { w = img:getWidth(), h = img:getHeight(), s = h }
      local scale = (buttonImg.w > buttonImg.h) and buttonImg.s / buttonImg.w or buttonImg.s / buttonImg.h
      scale = 1--button.imgScaleOverride or (scale * (button.imgScale or 1 ))

      local thumbOffset = (buttonImg.w > buttonImg.h) and { x = 0, y = (buttonImg.s - buttonImg.h * scale) / 2 }
        or { x = (buttonImg.s - buttonImg.w * scale) / 2, y = 0 }
      scale = button.imgActual and 1 or scale
      if load_imageName(button.img) == "checkers" then
      else
        gfx.draw(img, button.cx, button.cy, 0, scale, scale, buttonImg.w / 2, buttonImg.h / 2)
      end
    end
  end
end


function namer_open(target)
  local namer = ctx.namer
  namer.enabled = true
  namer.text = ""
  namer.time = 0
  namer.width = 250
  namer.simple = target or false
  namer.target = target or { x = screenW / 2, y = screenH / 2 }
  namer.cursor = nil
  namer.limit = namer.target.limit or math.huge
end

function namer_close()
  local namer = ctx.namer
  namer.enabled = false
  namer.cursor = nil
  input_unlockMouse()
  input_unlockKeys()
  namer.closing = true

  if not contextMenu.open then delayedCall(function()
    editor_closeContextMenu()
  end, 0.1) end
end

function namer_warning(warning, options)
  local namer = ctx.namer

  editor_closeContextMenu()
  namer_open()
  namer.mode = "warning"
  namer.type = ""
  namer.warnFunc = options[1].func
  namer.text = warning .. "?"
  namer.target.align = left
  namer.target.popup = true
  namer.target.fontSize = 15
  namer.options = options
  -- namer.warnFunc()

  if not editor.warnings then return end
end

function namer_typeString(var, val, target, func, update, num)
  local namer = ctx.namer
  func = func or function(x) end
  editor.state = "Typing"
  namer_open(target)
  namer.mode = "typing"
  namer.type = var or ""
  namer.num = num
  namer.updateFunc = update
  namer.setText = function(x)
    --editor_tempTip    ("Text Set",2.5)
    val = tostring(namer.text)
    func(val)
  end
  namer.text = val
  -- namer.options = {"Cancel", "Set"}
end

function namer_optionsUpdate()
  local namer = ctx.namer
  input_unlockMouse()
  mouse_setVisible(true)

  local total = 0
  for i = 1, #namer.options do
    total = total + gfx.currentFont:getWidth(namer.options[i].name)
    -- total = total + namer.tip.w/3
  end

  local x = namer.target.x - namer.tip.w / 2
  for i = 1, #namer.options do
    local op = namer.options[i]
    local offx = (gfx.currentFont:getWidth(op.name) / total) * namer.tip.w
    op.w = 16 + gfx.currentFont:getWidth(op.name)
    op.h = 24
    op.x = x + offx / 2 - op.w / 2
    op.y = namer.tip.y + 24
    x = x + offx
    op.fontSize = 13
    op.alpha = namer.backOpacity
    op.text = op.name
    editor_queueButton(op)
  end

  input_lockMouse()
end

function namer_update()
  local namer = ctx.namer
  input_unlockKeys()

  if namer.target and not namer.target.hidden then
    namer.backOpacity = easeTo(namer.backOpacity, namer.enabled and 1 or 0, 3)
    if namer.backOpacity > 0.05 then
      if namer.mode == "warning" then
        editor_pushDraw(function()
        gfx.setColor(0, 0, 0, namer.backOpacity / 2)
        gfx.rectangle("fill", 0, 0, screenW, screenH)
        end)
      end
      namer.tip = editor_drawTip(lume.merge(namer.target, {
        text = namer.text,
        w = namer.target.width,
        a = namer.backOpacity,
        cursor = namer.cursor,
        cursorTime = namer.time,
      }))
      namer_optionsUpdate()
    end
  end

  if not namer.enabled then return 0 end
  mouse_setCursor(defaultCursor)

  namer.alertTimer = namer.alertTimer - 1

  if namer.updateFunc then namer.updateFunc(namer.text) end

  -- trace("cursor",namer.cursor)
  if input_keyPressed("escape") then
    namer_close()
    return
  end
  if namer.simple and not mouse_in(namer) then
    mouse_pushClick(function()
      namer_close()
    end)
    mouse_pushClickR(function()
      namer_close()
    end)
  end
  namer.time = namer.time + engine.dt

  if namer.mode == "warning" then
  else
    namer.cursor = mc(0, namer.cursor or #namer.text, #namer.text)
    for k, key in pairs(engine.keysDown) do
      if input_keyPressed(key) or (input_keyRepeat(key)) then
        if key == "left" or key == "right" then
          namer.cursor = mc(0, namer.cursor + (key == "left" and -1 or 1), #namer.text)
          namer.time = pi / 2
        elseif #key == 1 or key == "space" then
          if (not namer.num or (tonumber(key) and tonumber(key) >= 0 and tonumber(key) < 10)) and #namer.text < namer.limit then
            namer.time = pi / 2
            if key == "space" then key = " " end
            if shiftDown then key = string.upper(key) end
            if namer.cursor == 0 then
              namer.text = key .. namer.text
            elseif namer.cursor == #namer.text then
              namer.text = namer.text .. key
            else
              namer.text = namer.text:sub(1, namer.cursor) .. key .. namer.text:sub(namer.cursor + 1)
            end
            namer.cursor = namer.cursor + 1
          end
        elseif key == "backspace" then
          namer.time = pi / 2
          if cmdDown then
            namer.text = ""
          else
            if namer.cursor == 0 then
            elseif namer.cursor == #namer.text then
              namer.text = namer.text:sub(1, #namer.text - 1)
            else
              namer.text = namer.text:sub(1, namer.cursor - 1) .. namer.text:sub(namer.cursor + 1)
            end
            namer.cursor = namer.cursor - 1
          end
        end
      end
    end
  end

  if input_keyPressed("return") then
    if namer.mode == "warning" then namer.warnFunc() end
    if namer.mode == "typing" then
      if namer.text ~= "" then
      else
        return
      end
    end
    namer_close()
  end
  if namer.enabled then input_lockKeys() end
end

function namer_nameTaken()
  local namer = ctx.namer
  namer.alertText = "Name Taken!"
  namer.alertTimer = namer.alertMax
end

function editor_deleteSelected()
  for i, entity in ipairs(editor.selectedEntities) do
    scene_setDelete(entity)
  end
  lume.clear(editor.selectedEntities)
end

function editor_deleteFromSelectedEntities(ent)
  for i, e in ipairs(editor.selectedEntities) do
    if e.uid == ent.uid then table.remove(editor.selectedEntities, i) end
  end
end

function editor_checkForSimples()
  anySelectedSimple = false
  if editor.hoveredEntity ~= nil and editor.hoveredEntity.simple then anySelectedSimple = true end
  for i, entity in ipairs(editor.selectedEntities) do
    if entity.simple then anySelectedSimple = true end
  end
  return anySelectedSimple
end

function editor_checkForSemiSimples()
  local anySelectedSemiSimple = false
  if
    editor.hoveredEntity ~= nil
    and (editor.hoveredEntity.simple or (editor.hoveredEntity.light and editor.hoveredEntity.light.enabled and not editor.hoveredEntity.visible))
  then
    anySelectedSemiSimple = true
  end
  for i, entity in ipairs(editor.selectedEntities) do
    if entity.simple or (entity.light and entity.light.enabled and not entity.visible) then anySelectedSemiSimple = true end
  end
  return anySelectedSemiSimple
end

function plane(entity, pos)
  pos = getFloorCoords(entity)
  entity:setPosition(entity.x, entity.y, pos and -pos.z + entity.offsetz or entity.z)
  pos = getFrontCoords(entity)
  entity:setPosition(pos and -pos.x + entity.offsetx or entity.x, entity.y, entity.z)
end

function editor_updateEntityTransform(entity)
  if entity.selected or animatePanel.open then
    local pos
    local ylock = false
    local planing = (entity.simple and pathsPanel.open and (pathsPanel.mode == "floors" or pathsPanel.mode == "ramps"))
    local ramping = (entity.simple and pathsPanel.open and pathsPanel.mode == "ramps" and #editor.selectedEntities == 1)
    local flooring = (entity.simple and pathsPanel.open and pathsPanel.mode == "floors")
    if flooring and #editor.selectedEntities ~= #pathsPanel.points then ylock = true end

    local overhead = (ctx.viewport.camera.overhead or ylock)
    if quat.is_real(entity.orientation) and entity.orientation.w == 1 then entity.orientation = quat(0, 0, 1, 1) end
    entity:updateTransform()
    -- trace("update transform")
    -- trace(entity.position)
    -- trace(entity.orientation)
    -- trace(entity.scale)

    ------------------------ SORT OBJECT  ---------------------------
    if editor.gizmo.sorting or flooring then
      --  local mult    = room.zbounds.range*(shiftDown and 0.5 or 1)
      --  local diff = lerp(0,1, (math.abs(editor.gizmo.start.y-mouse.y)/(screenHeight/2)) ) * lume.sign(editor.gizmo.start.y-mouse.y)
    end
  end
end

function applyTransform(e, p, o, s)
  if e and (e.abs or not e.transform) then return end
  e = e or {}
  e.transform = e.transform and e.transform:identity() or mat4()
  e.transform:scale(e.transform, s or e.scale or vec3(1))
  e.transform:mul(e.transform, mat4.from_quaternion(quat(0, 1, 0, -1)))
  e.transform:translate(e.transform, p or e.position or vec3(0))
  return e.transform
end

function previewTransform(e)
  if editor.gizmo.dragging then
    e.nextPosition = e.position + editor.gizmo.translation
    e.transform = mat4()
    e.transform = e.transform * mat4.from_quaternion(e.orientation)
    e.transform:translate(e.transform, e.nextPosition)
  elseif editor.gizmo.rotating then
    e.transform = mat4()
    e.nextOrientation = e.orientation * quat.rotate(editor.gizmo.angle, editor.gizmo.axis)
    e.transform = e.transform * mat4.from_quaternion(e.nextOrientation)
    e.transform:translate(e.transform, editor.gizmo.position - (editor.gizmo.position - e.position):rotate(-editor.gizmo.angle, editor.gizmo.axis))
  else
    e.transform = mat4()
    e.transform:scale(e.transform, e.scale)
    e.transform = e.transform * mat4.from_quaternion(e.orientation)
    e.transform:translate(e.transform, e.position)
  end

  for m, model in ipairs(e.models) do
    model.matrix = scene_getMatrix(model)
  end
  return e.transform
end

function editor_resetTransform(e)
  if e then
  else
    for i, e in ipairs(editor.selectedEntities) do
      e:setScale(1, 1, 1)
      e:setPosition(0, 0, 0)
      e:setRotation(2 * pi, vec3(1, 0, 0))
    end
  end
end

function editor_applyTransforms()
  for e, entity in ipairs(editor.selectedEntities) do
    entity:applyTransform()
  end
  if animatePanel.open then
    if animatePanel.transformer.position and animatePanel.instance.orientation then editor_applyFrameTransform(animatePanel) end
  end
  editor.gizmo.translation = vec3(0)
  editor.gizmo.screenRotate = false
end

function editor_shouldPlane(entity)
  return math.abs(entity.y - ctx.viewport.camera.pos.y) > 200
end

function editor_transformEntities(view)
  --trace("anySelectedSimple",anySelectedSimple)

  editor.selectAll = false
  editor.preTransform = false
  editor.wasTransforming = editor.transforming
  editor.transforming = (editor.gizmo.dragging or editor.gizmo.rotating or editor.gizmo.scaling or editor.gizmo.sorting)
  editor.transformTime = editor.transforming and editor.transformTime + 1 or 0
  
  if cmdDown then 
    editor.gizmo.cmdDown = true
  end

  local startedTransform = (editor.transforming and not editor.wasTransforming)
  editor.gizmo.hovered = false

  local yLocked = (editor.selectedEntities[1] and editor.selectedEntities[1].mode and editor.selectedEntities[1].mode == "floors")
  editor.gizmo.showArcs = (not pathsPanel.open or pathsPanel.mode == "paths")
    and not (cmdDown and editor.gizmo.dragging)
    and (not editor_checkForSimples() or #editor.selectedEntities > 1)

  if animatePanel.open and animatePanel.l.type == "model" then 
    editor.gizmo.showArcs = false 
  end

  if startedTransform then
    --editor.storeeditor.gizmoStart()
  end

  if animatePanel.open and animatePanel.transformer and animatePanel.transformer.enabled then
    lume.clear(editor.selectedEntities)
    editor.selectedEntities[1] = animatePanel.transformer
    animatePanel.transformer.selected = true
    if animatePanel.tool ~= "move" or animatePanel.selection.mesh then return end
  end

  if
    not (#editor.selectedEntities > 0
    and editor.stageHovered
    and (editor.state == "Normal" or editor.state == "Transforming")
    and not (input_keyDown("space") or (input_keyDown("left") or input_keyDown("right"))))
  then
    editor.gizmo.active = false
    return 
  end  

  editor.gizmo.active = true
  ---------------------------------



  local mul = { x = 0, y = 0, rx = 0, ry = 0, r = 0, s = 0, z = 0 }

  for i, entity in lume.ripairs(editor.selectedEntities) do
    if entity.x then
      if entity.IsEntity then
        scene_updateEntityVars(entity)
        scene_updateEntityBounds(entity)
      end

      --if not editor.gizmo.dragging then
      mul.x = mul.x + entity.position.x
      mul.y = mul.y + entity.position.y
      mul.z = mul.z + entity.position.z
      --end
    end
  end

  editor.gizmo.position = (editor.gizmo.dragging and editor.wasTransforming) and editor.gizmo.position
    or vec3(mul.x / #editor.selectedEntities, mul.y / #editor.selectedEntities, mul.z / #editor.selectedEntities)

  local screenAxis =
    quat.from_direction(editor.gizmo.position - view.camera.pos, vec3.perpendicular(editor.gizmo.position - view.camera.pos))
  editor.gizmo.screenAxis = screenAxis

  editor.gizmo.ori = 
  --quat(0, 1, 0, 1) 
  -- if space == "local" then 
    editor.selectedEntities[1].orientation:clone()
  -- end

  editor.gizmo.transform = (not cmdDown and #editor.selectedEntities == 1)
      and (editor.selectedEntities[1].transform or editor.selectedEntities[1]:updateTransform()):clone()
    or mat4():translate(mat4(), editor.gizmo.position + (editor.gizmo.translation or vec3(0, 0, 0)))

  local tooling = false
  local screenRotate = false
  
  local items = {}
  if #editor.selectedEntities == 1 and editor.selectedEntities[1].light.enabled then
    items = { move = editor.gizmo.items.move }
  else
    items = {}
  end
  if editor.gizmo.showArcs then 
    table.insert(items,editor.gizmo.items.trackball)
    table.insert(items,editor.gizmo.items.rotateX)
    table.insert(items,editor.gizmo.items.rotateY)
    table.insert(items,editor.gizmo.items.rotateZ)
    table.insert(items,editor.gizmo.items.rotate)
  end



  local projected = camera_project(editor.gizmo.position)
  if projected then
    editor.gizmo.screenCurrent = projected
    editor.gizmo.screenStart = projected:clone()
    if startedTransform then 
      editor.gizmo.diff = { 
        x = mouse.x - editor.gizmo.screenStart.x, 
        y = mouse.y - editor.gizmo.screenStart.y 
      } 
    end
  end

  if editor.gizmo.dragging then 
    editor.gizmo.ray = camera_cursorRay(mouse.x - editor.gizmo.diff.x, mouse.y - editor.gizmo.diff.y) 
  end


  for k, item in pairs(items) do
    local current = editor.gizmo.current==item
    local show = true
    

    if item.model then 
      item.hovered = editor.modelHovered==item.model
      item.model.matrix = item.model.transform
      local pos = editor.gizmo.new or editor.gizmo.position
      local campos = view.camera.pos
      local dist = vec3.dist(pos,campos)
      
      if item.rot then
        if item.axis == "X" then 
          local rX = math.angle(pos.y, pos.z, campos.y,campos.z)
          item.model.transform = mat4():rotate(mat4():rotate(mat4(), pi / 2, vec3(0, 1, 0)), rX+pi/2, vec3(1, 0, 0))
        elseif item.axis == "Y" then 
          local rY = math.angle(pos.x, pos.z, campos.x,campos.z)
          item.model.transform = mat4():rotate(mat4():rotate(mat4(), pi / 2, vec3(1, 0, 0)), -rY, vec3(0, 1, 0))
        elseif item.axis == "Z" then 
          local rZ = math.angle(pos.x, pos.y, campos.x,campos.y)
          item.model.transform = mat4():rotate(mat4(), rZ, vec3(0, 0, 1))
        elseif item.name=="rotate" then 
          item.model.transform=mat4()
          mat4.scale(item.model.transform,item.model.transform,vec3(0.0056*(dist)))
          mat4.mul(item.model.transform, -mat4.look_at(mat4(), pos,campos,  vec3(0,1,0)),item.model.transform)
          item.model.mesh = editor.gizmo.circleMeshFat
        else
          mat4.scale(item.model.transform,mat4(),vec3(0.0014*(dist)))
          mat4.translate(item.model.transform,item.model.transform,pos)
        end
        if item.axis then 
          mat4.scale(item.model.transform,item.model.transform,vec3(0.005*(dist)))
          mat4.translate(item.model.transform,item.model.transform,pos)
          item.model.mesh = editor.gizmo.ringMeshFat
        end
      end
      scene_testCursorModel(item.model)

      
      if item.rot then 
        if item.name=="trackball" then
        elseif item.name=="rotate" or (editor.gizmo.rotating and editor.gizmo.current == item) then 
          item.model.mesh = editor.gizmo.circleMeshThin
        else
          item.model.mesh = editor.gizmo.ringMeshThin
        end
        if editor.gizmo.rotating then 
          show = current 
        end
        item.model.color = (item.hovered or current) and item.color or {
          (item.color[1]+1)/2,
          (item.color[2]+1)/2,
          (item.color[3]+1)/2,
          item.color[4]
        }
      end

      if show and not animatePanel.playing then
        table.insert(editor.toolModels, item.model) 
      end
    end

    if item.hovered and not editor.gizmo.rotating then

      if item.name=="trackball" then 

      elseif item.name=="rotate" then
        local az = vec3.normalize(view.camera.pos - editor.gizmo.position)
        local ax = vec3.rotate(az, -pi / 2, vec3.unit_x)
        editor.gizmo.axis = az--item.axis == "Z" and az or (item.axis == "X" and ax or (item.axis == "Y" and vec3.cross(az, view.camera.lookDir)))
        editor.gizmo.startAngle = editor_getToolAngle()
      elseif item.plane then
        editor.gizmo.axis = -mat4.from_quaternion(editor.gizmo.ori) * item.plane
        local coords = getPlaneCoords(editor.gizmo.position, editor.gizmo.axis)
        local v1 = coords - editor.gizmo.position
        editor.gizmo.startAngle = math.angle(0, 0, vec3.dot(editor.gizmo.ori * item.plane1, v1), vec3.dot(editor.gizmo.ori * item.plane2, v1))
      end
    end

    editor.preTransform = item.hovered and true or editor.preTransform

    if item.hovered then
      if not mouse.down.L then 
        tooling = true 
      end
      editor.gizmo.name = editor.transforming and editor.gizmo.name or item.name
      editor.gizmo.hovered = true

      mouse_set(function()
        if animatePanel.open then
          animatePanel.pushUndoState()
        else
          editor_pushUndoState("Start Transform")
        end
        if item.rot then 
          editor.gizmo.rotating=true
        end
        editor.gizmo.mouse = { x = mouse.x, y = mouse.y }
        editor.gizmo.startTransform = editor.gizmo.transform:clone()
        editor.gizmo.startOri = editor.gizmo.ori:clone()
        editor.gizmo.current = item
        if item.func then 
          item.func()
        end
      end)
    end
  end



  if editor.gizmo.dragging and editor.gizmo.position then
    trace("mtool")
    trace(">li", editor.lastIntersect)
    trace(">in", editor.intersect)
    if editor.gizmo.cmdDown and editor.debug.trace_mouse_intersect then trace("intersect", inspect(editor.intersect)) end
    editor.gizmo.new = editor.gizmo.cmdDown and editor.lastIntersect
      or (
        yLocked and (getFloorCoords(editor.gizmo.position, editor.gizmo.ray) or editor.gizmo.new)
        or (getFrontCoords(editor.gizmo.position, editor.gizmo.ray) or editor.gizmo.new)
      )

    -- print(editor.gizmo.new-editor.gizmo.position , editor.gizmo.translation , vec3(0))
    editor.gizmo.translation = editor.gizmo.new - editor.gizmo.position
  elseif editor.gizmo.rotating then
    if editor.gizmo.current.name == "trackball" then
      -- editor.gizmo.angle = -(mouse.x - editor.gizmo.mouse.x) / 100
      -- editor.gizmo.angle = -(mouse.y - editor.gizmo.mouse.y) / 100
    else
      editor.gizmo.angle = editor_getToolStartAngle() - editor_getToolAngle() 
    end
    trace("editor.gizmo.axis",editor.gizmo.axis)
    trace("editor.gizmo.angle",editor.gizmo.angle)
  elseif editor.gizmo.scaling then
    local dist = vec2.dot(vec2(mouse.x - editor.gizmo.mouse.x, mouse.y - editor.gizmo.mouse.y), vec2(1, -1)) / 10

    editor.gizmo.scale = vec3(1 / ((dist + 50) / 50))
    if editor.gizmo.screenScale then
    else
    end
  else
    editor.gizmo.mouse = { x = mouse.x, y = mouse.y }
  end

  editor.state = tooling and "Transforming" or "Normal"

  if not animatePanel.open and input_keyPressed("backspace") then editor_deleteSelected() end
end


function editor_getToolAngle()
  return math.angle(editor.gizmo.screenCurrent.x, editor.gizmo.screenCurrent.y, mouse.x, mouse.y) + pi / 2
end

function editor_getToolStartAngle()
  return math.angle(editor.gizmo.screenCurrent.x, editor.gizmo.screenCurrent.y, editor.mouseStored.x, editor.mouseStored.y) + pi / 2
end

function editor_getToolDist()
  return math.dist(editor.gizmo.screenCurrent.x, editor.gizmo.screenCurrent.y, mouse.x, mouse.y)
end
