function editor_updateContextMenu()
  contextMenu.fontSize = 12

  contextMenu.hovered = false

  if #contextMenu.items == 0 or not contextMenu.open then return end

  input_unlockMouse()

  if editor.pickingColor then
    editor_updateLoupe()
    mouse_set(function()
      editor_pickColor(contextMenu.colorTarget)
      editor.pickingColor = false
      mouse_setPosition(editor.storedMouse.x, editor.storedMouse.y)
    end)
  else
    if editor.colorTip.open then input_lockMouse() end

    editor_updateContextMenuItems(contextMenu)
    input_unlockMouse()
    editor_updateColorTip()

    if input_keyPressed("escape") then
      editor_closeContextMenu()
      return
    end
    if not contextMenu.hovered and not editor.colorTip.open then
      mouse_set(function()
        editor_closeContextMenu()
      end)
    else
      editor.stageHovered = false
    end
  end

  input_lockMouse()
end



function editor_deselectContext(context)
  context.selected = { split = 0, item = 0 }
  for i, item in ipairs(context.items) do
    if item.items ~= nil then editor_deselectContext(item) end
  end
end

function editor_makeToggle(targ, prop, over, list, slide)
  local t = {}
  local over = over or {}
  local slide = type(prop) == "table" and "slideToggle" or slide or "toggle"
  local tclick = over.click or nothing
  local sclick = slide == "toggle" and over.click or (over.slide or nothing)

  local prop1 = type(prop) == "table" and prop[1] or prop
  local prop2 = type(prop) == "table" and prop[2] or prop
  local prop3 = type(prop) == "table" and prop[3] or prop

  if over.percent then
    -- print("overprecent",over.percent)
  end
  t = {
    {
      width = over.width,
      height = over.height,
      nocap = over.nocap,
      name = type(over.name) == "function" and over.name or (over.nocap and tostring or capFirst)(over.name or prop3),
      size = 10,
      col = { r = 0, g = 0, b = 0, a = 0 },
      hover = hover,
      tip = over.tip,
      align = "left",
      toggle = slide == "toggle" and { targ = targ, prop = prop1 } or nil,
      func = sclick,
      slide = over.slide,
      update = over.update,
      round = over.round,
      list = slide == "toggle" and list or nil,
      slider = slide ~= "toggle" and {
        percent = over.percent,
        noval = over and over.noval or false,
        target = targ,
        style = "pct",
        prop = prop1,
        min = over.min or 0,
        pow = over.pow or 1,
        max = over.max or 1,
        round = over.round or 0.01,
      } or nil,
    },
    slide ~= "slide" and {
      name = "",
      align = "center",
      size = -1,
      col = { r = 0, g = 0, b = 0, a = 0.6 },
      hover = hover,
      tip = over.toggleTip,
      func = tclick,
      list = list,
      toggle = { targ = targ, prop = prop2 },
      update = function(item)
        item.toggle.targ = item.toggle.targ or {}
        item.text = item.toggle.targ[prop2] and "ticked" or "unticked"
      end,
    } or nil,
  }

  if over.colors then
    for i, color in lume.ripairs(over.colors) do
      table.insert(t, 2, editor_colorSquare(targ, color, over.parent, { targ, "enabled" }))
    end
  end

  return t
end

function editor_makeCheckbox(targ, prop, tip)
  return {
    name = "",
    align = "center",
    size = -1,
    col = { r = 0, g = 0, b = 0, a = 0.6 },
    hover = nothing,
    func = nothing,
    list = list,
    tip = tip,
    toggle = { targ = targ, prop = prop },
    update = function(x)
      x.text = targ[prop] and "ticked" or "unticked"
    end,
  }
end

function editor_makeSlider(targ, prop, over, list)
  return editor_makeToggle(targ, prop, over, list, "slide")
end

function editor_makeModes(targ, prop, modes, options)
  local out = {}
  if options and options.prefix then
    table.insert(out, {
      name = options.prefix,
      align = options and options.align or "left",
      static = true,
    })
  end
  for m, mode in ipairs(modes) do
    local name, tip, color, img
    if type(mode) == "table" then
      name = mode.name
      tip = mode.tip
      color = mode.color
      img = mode.img
    else
      name = mode
    end
    table.insert(out, {
      name = name,
      align = options and options.align or "center",
      tip = tip,
      img = img,
      open = true,
      ismode = true,
      suffix = options and options.suffix or "",
      before = options and options.before or nothing,
      after = options and options.after or nothing,
      func = function(item)
        item.before()
        targ[prop] = name
        item.after()
        return item.open
      end,
      -- rollOn=function(item)
      --  editor_setToolTip(item.tip)
      -- end,
      update = function(item)
        item.selectedMode = (targ[prop] == item.name)
        item.underline = item.selectedMode
        item.col = { a = item.selectedMode and 1 or 0 }
        item.icol = { a = item.selectedMode and 1 or 0.6 }
        item.tcol = { a = item.selectedMode and 1 or 0.7 }
        item.text = item.name .. item.suffix
        if item.text == "last pen" then item.text = item.text .. " (" .. animatePanel.pen.group .. ")" end
      end,
    })
    if color then
      table.insert(out, editor_colorSquare(mode, "color"))
      if mode.swap then
        out[#out], out[#out - 1] = out[#out - 1], out[#out]
      end
    end
  end
  return out
end

local function contextSplit(context,item, splitx)
  if item.devonly and lfs.isFused() then
    item.name = not (item.name:find("disabled")) and item.name .. " (disabled)" or item.name
    item.static = true
    item.tcol = { a = 0.5 }
  end
  item.scale = item.scale or 1
  item.width = context.back.width
  item.height = item.height or contextMenu.itemHeight
  item.y = context.back.y + context.yoff + contextMenu.spacing
  local indent = (item.indent and (16 - item.indent) / 16 or 1)
  local indent2 = (1 - indent) * (context.subWidth or context.back.width)
  item.x = context.back.x + (splitx or 0) * item.width * indent + indent2
  item.width = item.width * item.scale * indent
  item.context = context
  item.col = lume.merge({ r = 0, g = 0, b = 0, a = 0 }, item.col or {})
  item.text = item.text or item.name
  item.funcR = item.funcR or nothing

  if item.subitems then 
    if not context.subTitle then 
      context.subTitle = item.name
      context.items = lume.concat({ context.items[1] }, item.subitems)
    end
    
    item.update = function(item)
      item.underline = context.subTitle==item.name
      if not item.hovered and mouse_in(item) then 
        contextMenu.reSub = {context,item}
      end
    end
  end
end

function createMenuItem(context, item,index)
  if not item then return false end
  contextMenu.split = contextMenu.split + 1
  local list
  local modes=false
  if #item > 0 then
    local splitx = 0
    local sumSizes = 0
    local absWidth = 0

    for j, jtem in ipairs(item) do
      if type(jtem) == "table" then
        if jtem.ismode then 
          modes=true
        end
        jtem.size = (jtem.size or 1)
        if jtem.size > 0 then
          sumSizes = sumSizes + jtem.size
        else
          jtem.size = jtem.size == -1 and -contextMenu.itemHeight or jtem.size
          absWidth = absWidth - jtem.size
        end
      end
    end

    local cw = context.subWidth or context.back.width or 180
    local indent = (item[1].indent and (16 - item[1].indent) / 16 or 1)
    cw = cw * indent

    for s, split in ipairs(item) do
      if type(split) == "table" then
        split.size = (split.size or 1)
        split.scale = split.size > 0 and (split.size / sumSizes) * ((cw - absWidth) / cw) or math.abs(split.size) / cw
        split.parent = item
        contextSplit(context,split, splitx) 
        splitx = splitx + split.scale
        if modes then 
          if split.selectedMode then 
            list = split.list
          end
        elseif split.toggle and split.toggle.targ[split.toggle.prop]  then
          list = split.list
        end
      end
    end
  else
    contextSplit(context,item)
    if item.toggle and item.toggle.targ[item.toggle.prop]  then 
      list = item.list
    end
  end

  context.yoff = context.yoff + (item.height or contextMenu.itemHeight)

  if list then
    context.listing[item] = true
    context.listLevel = context.listLevel + 1
    for l=#list,1,-1 do
      if list[l] then
        list[l].indent = (item.indent or 0) + 1
        for s, split in ipairs(list[l]) do
          split.indent = list[l].indent
        end
        list[l].header = item
        table.insert(context.items,index+1,list[l])
      end
    end
  end

   -- if item.warning then
   --          local rval = item.warning == "open" and item.warning or nil
   --          context.items[i] = {
   --            {
   --              name = "Confirm",
   --              col = { r = 0.4, g = 1, b = 0.4, a = 0.25 },
   --              func = function()
   --                click()
   --                return rval
   --              end,
   --            },
   --            {
   --              name = "Cancel",
   --              col = { r = 1, g = 0.4, b = 0.4, a = 0.25 },
   --              func = function()
   --                context.items[i] = preWarn
   --                return item.warning
   --              end,
   --            },
   --          }
   --        end
   
  return true
end

function editor_createMenuItems(context)
  if not context.items then return end
  
  context.listing = {}
  context.selected = context.selected or { split = 0, item = 0 }
  context.listLevel = 0
  context.yoff = 0

  local k = 0

  for i=#context.items,1,-1 do
    local item = context.items[i]
    if item.header and not context.listing[item.header] then 
      table.remove(context.items,i)
    end
  end

  for i=1,32,1 do
    local item = context.items[i]
    if not createMenuItem(context,item,i) then 
      break
    end
  end

  context.created = true
end

function editor_openContextMenu(contextItems, props)


  editor_closeColorTip()
  if  #contextItems == 0 then return end
  for i, item in lume.ripairs(contextItems) do
    if type(item) ~= "table" then table.remove(contextItems, i) end
  end
  if #contextItems == 0 then return end

  local props = props or {}
  props.width = props.width or 180
  props.subWidth = props.subWidth or 180
  props.x = mc(16, props.x or mouse.x / game.resolution + 4, screenW - props.width - 16)
  props.y = props.y or mouse.y / game.resolution

  editor.state = "Context Menu"
  contextMenu = {
    back = {
      alpha = 0.95,
      padding = 4,
      x = props.x,
      y = props.y,
      width = props.width
    },
    x = props.x,
    y = props.y,
    name = "context base",
    open = true,
    split = 0,
    alpha=0.8,
    selected = { split = 0, item = 0 },
    subWidth = props.subWidth,
    items = contextItems,
    itemHeight = 20,
    spacing = 0,
    hovered = true,
    shadow = 0,
    color = { r = 0, g = 0, b = 0, a = 0.4 },
    col = { a = 130 },
    up = props.up,
    scroll = 0,
    direction = (props.x > screenW - (props.width + (props.subWidth or props.width)) - 16) and -1 or 1,
  }

  contextMenu.back.height = #contextMenu.items * (contextMenu.itemHeight + contextMenu.spacing) + contextMenu.spacing
  contextMenu.back.y = mc(16, contextMenu.back.y + 0  + (props.up and -contextMenu.back.height or 0), screenH - (contextMenu.back.height+contextMenu.back.padding+1))

  for t, tween in ipairs(game.tweens) do
    if tween.thing == contextMenu then table.remove(game.tweens, t) end
  end
  -- scene_tween(contextMenu, { y = contextMenu.y - 0 , alpha = 1 }, 2, "easeOut", 1)
  editor_createMenuItems(contextMenu)
end

function editor_closeContextMenu()
  editor.state = "Normal"
  if contextMenu.open then 
    -- scene_tween(contextMenu.back, { alpha = 0 }, 0.5, "easeIn", 1, function()
      contextMenu.open = false
      mouse.down.L = true
      contextMenu.items = {}
      contextMenu.back.x = screenW
      contextMenu.back.y = screenH
      input_unlockMouse()
    -- end)
  end

  editor_closeColorTip()
end

function editor_openColorTip(targ, prop)
  editor.colorTip =  {}

  editor.colorTip.open = true
  editor.colorTip.a = 0
  editor.colorTip.targ = targ
  editor.colorTip.prop = prop
  editor.colorTip.x = mouse.x
  editor.colorTip.y = mouse.y

  editor.colorTip.h = 220
  editor.colorTip.expandLimit = editor.colorTip.y - 220
  editor.colorTip.shape = "quad"
  editor.colorTip.dragging = ""
  editor.colorTip.scrollSpeed = 0
  editor.colorTip.scrollLimit = 210
  editor.colorTip.mode = editor.colorTip.targ[editor.colorTip.prop].pattern and "patterns" or "colors"
  editor.colorTip.scroll = editor.colorTip.mode == "patterns" and editor.colorTip.scrollLimit or 0
  editor.colorTip.sliders = {}
  editor.colorTip.tri = gfx.newMesh(zformat, {
    { 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 },
    { 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 },
    { 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 },
  }, "fan")
  editor.colorTip.quad = gfx.newMesh(zformat, 2000, "triangles")
  -- editor.colorTip.palettes
  scene_tween(editor.colorTip, { a = 1 }, 0.2, "easeOut", 1)
end

function editor_hueWheel(inner, outer)
  local segs = 54
  local inner = inner or 0
  local outer = outer or 1
  local verts = {}
  local r, g, b
  for i = 1, segs + 1, 1 do
    r, g, b = hsvToRgb(i * (1 / segs), 1, 1)
    local ang = (i / segs) * pi * 2
    verts[i * 2 - 1] = 
    { inner * math.cos(ang), inner * math.sin(ang), 0, 1, 0, 0, r, g, b, 1 }
    verts[i * 2] = 
    { outer * math.cos(ang), outer * math.sin(ang), 0, 1, 0, 0, r, g, b, 1 }
  end
  -- table.insert(verts,verts[#verts])
  -- table.insert(verts, verts[3])
  return gfx.newMesh(zformat, verts, "strip")
end

function editor_drawColorTip()
  if not editor.colorTip.open then return end
  local cross = editor.colorTip.cross
  local col = editor.colorTip.targ[editor.colorTip.prop]
  local ang = col.h * pi * 2 + pi / 2
  local sliderOffset = 53
  local tip = {
    x = editor.colorTip.x,
    y = editor.colorTip.y,
    w = editor.colorTip.w,
    h = editor.colorTip.h,
    temp = true,
    dome = true,
  }
  tip =editor_drawTip(tip)
  -- lume.merge(editor.colorTip,{h=editor.colorTip.h+math.clamp(0,editor.colorTip.expandLimit,editor.colorTip.scroll),temp=true,noshadow=true,dome=patternMode and 118 or true}))

  if not editor.colorTip.tip then 
    editor.colorTip.tip = tip
    return
  end

  gfx.setBlendMode("alpha")

  gfx.push("all")
  gfx.translate(tip.x, tip.y)
  -- gfx.setScissor(tip.x,tip.y+2,tip.w,tip.h-4)
  -- gfx.setShader(shaders.forwardRender)

  if not ctx.isAnim then
    local pat = math.dist(tip.x + 15.5, tip.y + tip.w - 15.5, mouse.x, mouse.y) < 12

    gfx.setColor(setAll(pat and 0.6 or 0.2))
    gfx.circle("line", 15.5, tip.w - 15.5, 12)
    gfx.setColor(1, 1, 1, 1)
    img = load_imageTex("checkersRounded")
    img:setFilter("linear", "linear")
    -- gfx.draw(img, 15.5, tip.w - 15.5, 0, 1 / 4, 1 / 4, 32, 32)
  end

  if editor.colorTip.mode == "colors" then
    -- Color Wheel
    gfx.setShader(shaders.forwardRender)
    gfx.draw(editor.colorTip.wheel, tip.w / 2, tip.w / 2)
    gfx.setColor(0, 0, 0, 0.2)
    gfx.circle("fill", tip.w / 2, tip.w / 2, editor.colorTip.inner, 64)

    -- Color "Cube"
    gfx.push("all")
    -- gfx.setShader(shaders.noGamma)
    gfx.setShader(shaders.forwardRender)
    gfx.setColor(1,1,1,1)
    if editor.colorTip.shape == "tri" then
      gfx.draw(editor.colorTip.tri, tip.w / 2, tip.w / 2)
    elseif editor.colorTip.shape == "quad" then
      gfx.draw(editor.colorTip.quad, tip.w / 2, tip.w / 2, 0, 1, -1)
    end
    gfx.pop()

    -- Wheel Highlight and Cross
    -- if editor.colorTip.shape == "tri" then gfx.line(vLine[1].x, vLine[1].y, vLine[2].x, vLine[2].y) end
    do 
    gfx.push("all")
      gfx.translate(tip.w / 2, tip.w / 2)
      gfx.setColor(1, 1, 1, 1)
      local dx,dy = math.cos(ang-pi/2),math.sin(ang-pi/2)
      local i,o = 56,68
      gfx.line(dx*i,dy*i,dx*o,dy*o)
      gfx.translate(cross.x, cross.y)
      gfx.setColor(1, 1, 1, 1)
      gfx.line(0, -3, 0, 3)
      gfx.line(-3, 0, 3, 0)
      gfx.setColor(col.r, col.g, col.b, 1)
      gfx.rectangle("fill", -1, -1, 2, 2)
    gfx.pop()
    end

    -- Eyedrop tool
    -- local pick = math.dist(tip.x+tip.w-16.5,tip.y+tip.w-16.5,mouse.x,mouse.y) <12
    -- if pick then
    --  mouse_set(function()
    --    editor_contextColorPicker(editor.colorTip.targ[editor.colorTip.prop])
    --  end)
    -- end
    -- gfx.setColor(setAll(pick and 0.6 or 0.2))
    -- gfx.circle("line",tip.w-16.5,tip.w-16.5,12)
    -- gfx.setColor(1,1,1,1)
    -- img = load_imageTex("eyedrop")
    -- img:setFilter("linear","linear")
    -- gfx.draw(img,tip.w-16.5,tip.w-16.5,0,1,1,10,10)
  end
  gfx.translate(4, tip.w)

  if editor.colorTip.mode == "colors" then
    gfx.setMeshCullMode("none")
    local hsv = config.colorSlidersHsv
    for i, v in ipairs(hsv and { "h", "s", "v", "a" } or { "r", "g", "b", "a" }) do
      local slider = editor.colorTip.sliders[v]
      gfx.setColor(1, 1, 1, 1)
      if slider.gradient2 then gfx.draw(slider.gradient2, 0, slider.y, 0, slider.width, slider.height) end
      gfx.draw(slider.gradient, 0, slider.y, 0, slider.width, slider.height)

      editor_drawButton(slider)

      local c = (slider.hovered or editor.colorTip.sliding == v) and 0.8 or 0
      gfx.setColor(c, c, c, 0.3)
      gfx.setLineWidth(2)
      gfx.rectangle("line", 0, mf((i - 1) * 13), editor.colorTip.w - 8, 10, 2)
      -- gfx.setColor(1,1,1,1)
      -- gfx.line(mf(col[v]*paddedWidth),slider.y,mf(col[v]*paddedWidth),slider.y+10)
    end
    gfx.translate(-1, sliderOffset)
  else 
    editor_drawButton(editor.colorTip.patternScale)
  end

   if editor.colorTip.mode == "patterns" then
    -- local w = 28
    -- local w2 = w - 1
    -- local n = mu((editor.colorTip.w - 12) / w)
    -- for i = 1, #patterns.dims, 1 do
    --   local pattern = patterns.size[i]
    --   local x, y = ((i - 1) % n) * (w + 1), mf((i - 1) / n) * (w + 1)
    --   gfx.setColor(1, 1, 1, 1)
    --   gfx.draw(
    --     patterns.atlas,
    --     gfx_copy.newQuad(pattern.x, pattern.y, pattern.w, pattern.w, patterns.w, patterns.h),
    --     x,
    --     y,
    --     0,
    --     w / pattern.w,
    --     w / pattern.w
    --   )

    --   if editor.colorTip.targ[editor.colorTip.prop].pattern and editor.colorTip.targ[editor.colorTip.prop].pattern == i then
    --     gfx.setColor(0, 0, 0, 1)
    --     gfx.rectangle("line", x + 0.5, y + 0.5, w2, w2)
    --   end
    --   if mouseIn(tip) and mouseIn(x, y, w2, w2, mouse.x - tip.x - 4, mouse.y - tip.y - tip.w) then
    --     gfx.setColor(1, 1, 1, 0.5)
    --     gfx.rectangle("line", x, y, w2, w2)
    --     mouse_set(function()
    --       editor.colorTip.targ[editor.colorTip.prop].pattern = i
    --     end)
    --   end
    -- end
  else
    local recent = editor.recentColors
    if recent then
      local w = 16
      local w2 = w - 1
      local n = (editor.colorTip.w - 12) / w
      for i = 1, mu(n), 1 do
        local col = recent[i]
        if col and col.r then
          gfx.setColor(col.r, col.g, col.b, 1)
          gfx.rectangle("fill", (i - 1) * w, 0, w2, w2)
          local ox,oy = tip.x - 4, tip.y - tip.w - sliderOffset
          if mouse_in((i - 1) * w + ox, 0+oy, w2, w2) then
            gfx.setColor(1, 1, 1, 0.5)
            gfx.rectangle("line", (i - 1) * w, 0, w2, w2)
            mouse_set(function()
              editor.colorTip.targ[editor.colorTip.prop].r = col.r
              editor.colorTip.targ[editor.colorTip.prop].g = col.g
              editor.colorTip.targ[editor.colorTip.prop].b = col.b
              editor.colorTip.targ[editor.colorTip.prop].a = col.a or 1
            end)
          end
        end
      end
    end
  end

  gfx.pop()
  gfx.setScissor()
end

function editor_updateColorTip()
  if not editor.colorTip.open then return end
  
  local cross = editor.colorTip.cross or vec2()
  mouse_setCursor(defaultCursor)
  editor.colorTip.w = 150 --+math.clamp(0,editor.colorTip.expandLimit,editor.colorTip.scroll)/2
  local patternMode = editor.colorTip.mode == "patterns"
  local col = editor.colorTip.targ[editor.colorTip.prop]

  editor.colorTip.inner = editor.colorTip.w / 2 - 18
  editor.colorTip.outer = editor.colorTip.w / 2 - 8
  editor.colorTip.wheel = editor.colorTip.wheel  or editor_hueWheel(editor.colorTip.inner, editor.colorTip.outer)

  -- local tip=editor_drawTip(lume.merge(editor.colorTip,{h=editor.colorTip.h+math.clamp(0,editor.colorTip.expandLimit,editor.colorTip.scroll),temp=true,noshadow=true,dome=patternMode and 118 or true}))

  if not editor.colorTip.tip then return end

  local tip = editor.colorTip.tip

  local third, pt, tri, verts, vOff, vLine, quad, c
  local ang = col.h * pi * 2 + pi / 2
  local m = { x = mouse.x, y = mouse.y }
  local m2 = { m.x - tip.x - tip.w / 2, m.y - tip.y - tip.w+4, 0 }

  if editor.colorTip.shape == "tri" then
    third = (pi * 2) / 3
    pt = vec2.from_cartesian(54, third)
    tri = { l = pt.x, r = 54, t = pt.y, b = -pt.y }
    verts = {{ tri.l, tri.b, 0, 1, 1, 1, 1, 1 }, { tri.r, 0, 0, 1, hsvToRgb(col.h, 1, 1) }, {tri.l, tri.t, 0, 1, 0, 0, 0, 1, }, }
    vOff = vec2.from_cartesian((1 - col.v) * 80.35, math.angle(verts[1][1], verts[1][2], verts[2][1], verts[2][2]) + pi / 2)
    vLine = { vec2(verts[1][1], verts[1][2]) + vOff, vec2(verts[2][1], verts[2][2]) + vOff }
    c = vec3(math.clamp(tri.l, tri.r, m2[1]), math.clamp(tri.b, tri.t, m2[2]), m2[3])
    local hit, crosspos = math.intersect(vec2(c.x, tri.b), vec2(c.x, tri.t), vLine[1], vLine[2])
    if hit then cross = vec2(crosspos.x, crosspos.y) end
  else
    size = editor.colorTip.inner / 1.5
    quad = { x = -size, y = -size, w = size * 2.15, h = size * 2 }
    verts = {}
    local num = 16
    local step = quad.w / (num + 1)
    local r, g, b, h, s, v
    local x1, x2, y1, y2 = quad.x, quad.x + step, quad.y, quad.y + step
    for i = 0, num - 1, 1 do
      x1, x2 = quad.x, quad.x + step
      for j = 0, num - 1, 1 do
        local r1, g1, b1 = hsvToRgb(col.h, j / num, i / num)
        local r2, g2, b2 = hsvToRgb(col.h, (j + 1) / num, i / num)
        local r3, g3, b3 = hsvToRgb(col.h, j / num, (i + 1) / num)
        local r4, g4, b4 = hsvToRgb(col.h, (j + 1) / num, (i + 1) / num)

        table.insert(verts, { x1, y1, 0, 0, 0, 0, r1, g1, b1, 1 })
        table.insert(verts, { x2, y1, 0, 0, 0, 0, r2, g2, b2, 1 })
        table.insert(verts, { x2, y2, 0, 0, 0, 0, r4, g4, b4, 1 })
        table.insert(verts, { x2, y2, 0, 0, 0, 0, r4, g4, b4, 1 })
        table.insert(verts, { x1, y2, 0, 0, 0, 0, r3, g3, b3, 1 })
        table.insert(verts, { x1, y1, 0, 0, 0, 0, r1, g1, b1, 1 })

        x1, x2 = x1 + step, x2 + step
      end
      y1, y2 = y1 + step, y2 + step
    end
    cross = {
      x = utils.map(col.s, 0, 1, quad.x, quad.x + quad.w),
      y = utils.map(col.v, 1, 0, quad.y, quad.y + quad.h),
    }
  end

  if not mouse_in(tip.x-tip.w/2,tip.y,tip.w*2,tip.h) then
    mouse_set(editor_closeColorTip)
  else
    mouse_set(nothing)

    editor.colorTip.scrollSpeed = easeTo(editor.colorTip.scrollSpeed, editor.colorTip.scrollSpeed - mouse.scroll.y * 8, 4)

    if editor.colorTip.shape == "tri" and point_in_triangle(verts[1], verts[2], verts[3], m2) then
      mouse_set(function()
        editor.colorTip.dragging = "tri"
      end)
    elseif editor.colorTip.shape == "quad" and simplePointInRect(vec2(m2),quad) then
      mouse_set(function()
        editor.colorTip.dragging = "quad"
      end)
    end

    local dist = math.dist(0, 0, m2[1], m2[2])
    if dist > editor.colorTip.inner and dist < editor.colorTip.outer then mouse_set(function()
      editor.colorTip.dragging = "wheel"
    end) end
  end

  editor.colorTip.scroll = editor.colorTip.scroll + editor.colorTip.scrollSpeed
  editor.colorTip.scroll = easeTo(
    editor.colorTip.scroll,
    editor.colorTip.scroll < 0 and 0
      or (editor.colorTip.scroll > editor.colorTip.scrollLimit and editor.colorTip.scrollLimit or editor.colorTip.scroll)
  )
  editor.colorTip.scrollSpeed = editor.colorTip.scrollSpeed * 0.5

  -- Pattern toggle
  if not ctx.isAnim then
    local pat = math.dist(tip.x + 15.5, tip.y + tip.w - 15.5, mouse.x, mouse.y) < 12
    if pat then
      mouse_set(function()
        editor.colorTip.mode = cycleTable({ "patterns", "colors" }, editor.colorTip.mode)
        if editor.colorTip.mode == "patterns" then
          editor.colorTip.scroll = editor.colorTip.scrollLimit
          editor.colorTip.targ[editor.colorTip.prop].pattern = editor.colorTip.targ[editor.colorTip.prop].pattern or 1
        else
          editor.colorTip.targ[editor.colorTip.prop].pattern = nil
        end
      end)
    end
  end

  -- Sliders
  local paddedWidth = editor.colorTip.w - 8
  local m3 = vec2(m) + vec2(-tip.x - 4, -tip.y - tip.w)

  local sliderOffset = 53
  if editor.colorTip.mode == "colors" then
    local hsv = config.colorSlidersHsv
    for i, v in ipairs(hsv and { "h", "s", "v", "a" } or { "r", "g", "b", "a" }) do
      local gradient
      if not hsv then
        gradient = {
          { v == "r" and 0 or col.r, v == "g" and 0 or col.g, v == "b" and 0 or col.b, v == "a" and 0 or 1 },
          { v == "r" and 1 or col.r, v == "g" and 1 or col.g, v == "b" and 1 or col.b, v == "a" and 1 or 1 },
        }
      else
        local h, s, v = rgbToHsv(col.r, col.g, col.b)
        local r1, g1, b1 = hsvToRgb(v == "h" and 0 or h, v == "s" and 0 or s, v == "v" and 0 or v)
        local r2, g2, b2 = hsvToRgb(v == "h" and 1 or h, v == "s" and 1 or s, v == "v" and 1 or v)
        gradient = { { r1, g1, b1, 1 }, { r2, g2, b2, 1 } }
      end
      
      editor.colorTip.sliders[v] = editor.colorTip.sliders[v] or {}

      local slider = editor.colorTip.sliders[v]
      slider.name = v
      slider.x = 0
      slider.y = (i - 1) * 13
      slider.width = paddedWidth
      slider.height = 10
      slider.twoTone=true
      slider.outline=true
      slider.offset = {x=tip.x -tip.w/2+8, y=tip.y+150}
      slider.slider = slider.slider or { target = editor.colorTip.targ[editor.colorTip.prop], round = 0.01, prop = v, min = 0, max = 1 }
      slider.slider.pos = col[v]
      slider.notext = true
      slider.func = slider.func or function()
        editor.colorTip.sliding = v
      end

      local dat = gfx.gradientMesh("horizontal", gradient[1], gradient[2])
      table.insert(dat, dat[3])
      table.insert(dat, dat[2])
      if slider.gradient then  
        slider.gradient:setVertices(dat)
      else 
        slider.gradient = gfx.newMesh(zformat, dat, "triangles")
      end
      if v == "a" and not slider.gradient2 then
        local w, h = slider.width / 128, slider.height / 128
        local dat2 = {
          { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 },
          { 1, 0, 0, 0, w, 0, 1, 1, 1, 1 },
          { 1, 1, 0, 0, w, h, 1, 1, 1, 1 },

          { 1, 1, 0, 0, w, h, 1, 1, 1, 1 },
          { 0, 1, 0, 0, 0, h, 1, 1, 1, 1 },
          { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 },
        }
        if slider.gradient2 then  
          slider.gradient2:setVertices(dat2)
        else 
          slider.gradient2 = gfx.newMesh(zformat, dat2, "triangles")
        end
        slider.gradient2:setTexture(load_imageTex("checkers"))
      end

      editor.colorTip.targ[editor.colorTip.prop][v] = editor.colorTip.targ[editor.colorTip.prop][v] or 1
      local val = editor.colorTip.targ[editor.colorTip.prop][v] * slider.width
      editor_updateButton(slider)

      if editor.colorTip.sliding == v then editor.colorTip.targ[editor.colorTip.prop][v] = math.clamp(0, 1, utils.map(m3.x, 0, paddedWidth, 0, 1)) end

      calcHSV(editor.colorTip.targ[editor.colorTip.prop])
      
    end

   if editor.colorTip.shape == "tri" then
      editor.colorTip.tri:setVertices(verts)
    elseif editor.colorTip.shape == "quad" then
      editor.colorTip.quad:setVertices(verts)
    end
    
  else
    editor.colorTip.patternScale = {
      name = "scale",
      x = 28,
      y = 0,
      width = paddedWidth,
      height = 20,
      pct = 0.5,
      offset = {x=-tip.x - 4, y=-tip.y - tip.w},
      slider = {
        target = editor.colorTip.targ[editor.colorTip.prop],
        round = 0.01,
        prop = "scale",
        min = 0,
        max = 1,
        pct = 1,
      },
      func = function()
        editor.colorTip.sliding = "scale"
      end,
    }
    editor_updateButton(editor.colorTip.patternScale)
  end

  -- Recent colors/patterns
  if editor.colorTip.mode == "patterns" then
    -- local w = 28
    -- local w2 = w - 1
    -- local n = mu((editor.colorTip.w - 12) / w)
    -- for i = 1, #patterns.dims, 1 do
    --   local x, y = ((i - 1) % n) * (w + 1), mf((i - 1) / n) * (w + 1)

    --   if mouseIn(tip) and mouseIn(x, y, w2, w2, mouse.x - tip.x - 4, mouse.y - tip.y - tip.w) then
    --     mouse_set(function()
    --       editor.colorTip.targ[editor.colorTip.prop].pattern = i
    --     end)
    --   end
    -- end
  else
    local recent = editor.recentColors
    if recent then
      local w = 16
      local w2 = w - 1
      local n = (editor.colorTip.w - 12) / w
      for i = 1, mu(n), 1 do
        local col = recent[i]
        if col and col.r then
          if mouse_in((i - 1) * w, 0, w2, w2, mouse.x - tip.x - 4, mouse.y - tip.y - tip.w - sliderOffset) then
            mouse_set(function()
              editor.colorTip.targ[editor.colorTip.prop].r = col.r
              editor.colorTip.targ[editor.colorTip.prop].g = col.g
              editor.colorTip.targ[editor.colorTip.prop].b = col.b
              editor.colorTip.targ[editor.colorTip.prop].a = col.a or 1
            end)
          end
        end
      end
    end

    if editor.colorTip.dragging == "tri" then
      local c = vec3(math.clamp(tri.l, tri.r, m2[1]), m2[2], m2[3])
      local cols = { vec3(1), vec3(hsvToRgb(col.h, 1, 1)), vec3(0) }
      local pts = { vec3(verts[1]), vec3(verts[2]), vec3(verts[3]) }
      local u = pts[2] - pts[1]
      local v = pts[3] - pts[1]
      local w = c - pts[1]
      local vw = v:cross(w)
      local vu = v:cross(u)
      local uw = u:cross(w)
      local uv = u:cross(v)
      local d = uv:len()
      local r = vw:len() / d
      local t = uw:len() / d

      local result = cols[1] * (1 - (r + t)) + cols[2] * r + cols[3] * t
      if col.h then
        --local r,g,b = result.x,result.y,result.z
        --local h,s,v = rgbToHsv(r,g,b)
        --local s = utils.map(m2[1],tri.l,tri.r,0,1)
        local s = 1 - distPointToLineSegment({ c.x, c.y }, verts[2], verts[3]) / 80.35
        local v = 1 - distPointToLineSegment({ c.x, c.y }, verts[1], verts[2]) / 80.35
        local r, g, b = hsvToRgb(col.h, s, v)
        trace("result", result.x, result.y, result.z)
        trace("result", r, g, b)
        merge(editor.colorTip.targ[editor.colorTip.prop] ,{ 
          r = r, g = g, b = b, h = col.h, s = s, v = v 
        })
      end
    end

    if editor.colorTip.dragging == "quad" then
      if col.h then
        local s = math.clamp(0, 1, utils.map(m2[1], quad.x, quad.x + quad.w, 0, 1))
        local v = math.clamp(0, 1, utils.map(m2[2], quad.y, quad.y + quad.h, 1, 0))
        local r, g, b = hsvToRgb(col.h, s, v)
        merge(editor.colorTip.targ[editor.colorTip.prop] ,{
          r = r,
          g = g,
          b = b,
          h = col.h,
          s = s,
          v = v,
          a = editor.colorTip.targ[editor.colorTip.prop].a or 1,
        })
      end
    end

    if editor.colorTip.dragging == "wheel" then
      local h = (math.angle(0, 0, m2[1], m2[2])) / (pi * 2)
      local r, g, b = hsvToRgb(h, col.s, col.v)
      merge(editor.colorTip.targ[editor.colorTip.prop] ,{
        r = r,
        g = g,
        b = b,
        h = h,
        s = col.s,
        v = col.v,
        a = editor.colorTip.targ[editor.colorTip.prop].a or 1,
      })
    end
  end
  
  editor.colorTip.cross = cross

  mouse_pushUp(function()
    editor.colorTip.dragging = ""
    editor.colorTip.sliding = nil
  end)
end

function editor_closeColorTip()
  if not editor.colorTip.open then return end
  scene_tween(editor.colorTip, { a = 0 }, 0.06, "easeIn", 1, function()
    editor.colorTip.open = false
    if editor.colorTip.targ and editor.colorTip.targ[editor.colorTip.prop] then
      editor_pushColor(editor.colorTip.targ == animatePanel and animatePanel or editor, editor.colorTip.targ[editor.colorTip.prop])
    end
    if not contextMenu.open then
      input_unlockMouse()
      mouse.down.L = true
    end
  end)
end

function editor_colorSquare(targ, prop, parent, enable)
  local tip = type(prop) == "table" and (prop.tip or prop.name) or prop
  local prop = type(prop) == "table" and prop.name or prop
  return {
    name = "",
    size = -1,
    tip = tip,
    swatch=true,
    box=true,
    outline=true,
    col = {},
    func = function(x)
      editor_openColorTip(targ, prop)
      return ""
    end,
    update = function(x)
      if enable then
        x.active = enable[1][enable[2]]
      else
        x.active = true
      end
      x.col = {
        r = targ[prop] and targ[prop].r or 1,
        g = targ[prop] and targ[prop].g or 1,
        b = targ[prop] and targ[prop].b or 1,
        a = 1, --x.active and 1 or 0.5
      }
      x.ocol = { r = 1, g = 1, b = 1, a = x.hovered and 1 or 0.3 }
      calcHSV(targ[prop])
    end,
  }
end

function calcRGB(color, a)
  color.hued = color.hued or { r = 0, g = 0, b = 0, a = 1 }
  color.saturated = color.saturated or { r = 0, g = 0, b = 0, a = 1 }
  color.valued = color.valued or { r = 0, g = 0, b = 0, a = 1 }

  color.r, color.g, color.b, color.a = hsvToRgb(color.h, color.s, color.v, (a or color.a or 1))

  color.hued.r, color.hued.g, color.hued.b, color.hued.a = hsvToRgb(color.h, 0.5, 0.5, (a or color.a or 1))
  color.saturated.r, color.saturated.g, color.saturated.b, color.saturated.a = hsvToRgb(color.h, color.s, 0.5, (a or color.a or 1))
  color.valued.r, color.valued.g, color.valued.b, color.valued.a = hsvToRgb(color.h, 0.5, color.v, (a or color.a or 1))
end

function calcHSV(color, a)
  color.h, color.s, color.v = rgbToHsv(color.r, color.g, color.b, a or color.a)
end

function editor_contextRooms(page)
  local isPage = page ~= nil
  local isView = not isPage
  editor.roomContextMode = isPage and "Settings" or editor.roomContextMode
  return {
    -- size = -1,
    -- name = isView and "···" or "Page",
    -- align = isView and "center" or "left",
    -- col = { r = 0, g = 0, b = 0, a = isView and 0.5 or 0 },
    subWidth = 250,
    subHeight = 300,
    sameY = true,
    sub = editor.roomContextMode,
    items = lume.concat({
      {
        {
          name = "Settings",
          size = 1,
          subitems = isView and editor_roomSettings() or editor_pageSettings(page),
          align = "center",
          func = function()
            return true
          end,
          rollOn = function(x)
            editor.roomContextMode = x.name
          end,
        },
        {
          name = "Pages",
          subitems = editor_setSelector(),
          align = "center",
          func = function()
            return true
          end,
          rollOn = function(x)
            editor.roomContextMode = x.name
          end,
        },
        {
          name = "Rooms",
          subitems = editor_roomSelector(),
          align = "center",
          func = function()
            return true
          end,
          rollOn = function(x)
            editor.roomContextMode = x.name
          end,
        },
        isView and {
          name = "Cameras",
          subitems = editor_cameraSelector(),
          align = "center",
          func = function()
            return true
          end,
          rollOn = function(x)
            editor.roomContextMode = x.name
            editor.preTempCam = editor.viewport.camera
            editor.preTempCam.index = editor.viewport.cameraID
          end,
        } or nil,
      },
    }),
  }
end

function editor_pageSettings(page)
  local items = {}
  table.insert(items, {
    {
      size = 8,
      name = page.name,
      tip = "key: " .. page.key,
      update = function(x)
        x.name = page.name
      end,
    },
    {
      size = -1,
      name = "-rename",
      func = function()
        editor_renameRoom(room)
        return "rename"
      end,
      col = { r = 0, g = 0, b = 0, a = 0.5 },
    },
  })
  table.insert(items, { { name = "Color" }, editor_colorSquare(page.sky, "color") })
  return items
end

function editor_roomSettings()
  local items = {}
  local room = editor.viewport.room

  table.insert(items, {
    {
      size = 8,
      name = room.name,
      tip = "key: " .. room.key,
      update = function(x)
        x.name = room.name
      end,
      func = function()
        editor_renameRoom(room)
        return "rename"
      end,
    },
    editor_colorSquare(room.sky, "color")
  })
  local l = room.lighting
  table.insert(
    items,
    editor_makeToggle(l, "enabled", { name = "lighting" }, {
      editor_makeToggle(l.ambient, { "amount", "enabled", "ambient light" }, { colors = { "color" } }),
      editor_makeToggle(l.static, "enabled", { name = "static lighting" }, {
        editor_makeToggle(l.render, "enabled", {
          name = "prerender",
          click = function()
            l.render.scale = l.render.scale or 1
            l.render.exposure = l.render.exposure or 1
          end,
        }, {
          editor_makeToggle(l.render, "run", { name = "run" }),
          editor_makeSlider(l.render, "scale", { min = 1, max = 4, round = 1 }),
          editor_makeSlider(l.render, "exposure", { min = 0.1, max = 2 }),
          editor_makeToggle(l.render, { "blurSize", "blurEnabled", "blur" }, { min = 0, max = 4 }),
          editor_makeToggle(l.render, "lock", { name = "lock view" }),
          -- editor_makeToggle(l.render,"gpu",{name="GPU mode"}),
          -- editor_makeToggle(l.render,"shuffle",{name="shuffle rays"}),
          -- editor_makeSlider(l.render,"depth",{min=2,max=32,round=1}),
          -- editor_makeSlider(l.render,"numrays",{min=8,max=256,round=2}),
          -- editor_makeToggle(l.render,{"atlasSize","showPreview","show atlas"},{max=2}),
        }),
      }),
      editor_makeToggle(l.dynamic, "enabled", { name = "dynamic lighting" }, {
        editor_makeToggle(l.probes, "enabled", { name = "light probes" }, {
          editor_makeToggle(l.probes, { "blur", "convolve", "convolve" }),
          editor_makeToggle(l.probes, "showProbes", { name = "show probes" }, {
            editor_makeToggle(l.probes, "test", { name = "test probes" }),
            editor_makeToggle(l.probes, { "atlasSize", "showAtlas", "show atlas" }, { max = 2 }),
          }),
        }),
        editor_makeToggle(l.vct, "enabled", { name = "voxel cone tracing" }, {
          editor_makeSlider(l.vct, "resolution", { min = 16, max = 128, round = 16 }),
          -- editor_makeSlider(l.vct,"bounces",{min=1,max=3,round=1}),
          -- editor_makeToggle(l.vct,"useShadowmap",{name="mapped shadows"}),
          editor_makeToggle(l.vct, "showOcclusion", { name = "show occlusion" }),
          editor_makeToggle(l.vct, "showTraced", { name = "show traced" }),
          editor_makeToggle(l.vct, "showVoxels", { name = "show voxels" }, {
            editor_makeToggle(l.vct, "showAverage", { name = "split faces" }),
            editor_makeSlider(l.vct, "lod", { min = 0, max = 6, round = 1 }),
            editor_makeModes(l.vct, "show", { "diffuse", "normal", "occlusion", "light" }),
          }),
        }),
      }),
      editor_makeToggle(l.toon, { "amount", "enabled", "toon stuff" }),
    })
  )

  --table.insert(settings, {name="Lock"})
  --table.insert(settings, {name="FOV"})
  --table.insert(settings, {name="Wobble"})
  --table.insert(settings, {name="Interest"})

  if ctx.pageView then
    table.insert(items, {
      name = "View Depth",
      slider = { target = editor.viewport, prop = "depth", min = 0, max = 1, round = 0.01 },
    })
    table.insert(items, {
      name = "View Wobble Speed",
      slider = { target = editor.viewport.viewport.wobble, prop = "speed", min = 0, max = 20, round = 0.1 },
    })
    table.insert(items, {
      name = "View Wobble Strength",
      slider = { target = editor.viewport.viewport.wobble, prop = "dist", min = 0, max = 50, round = 0.1 },
    })
    table.insert(items, {
      name = "Duplicate View",
      func = function()
        game_duplicateRoom(editor.viewport, { offset = { x = 20, y = 20 } })
      end,
    })
    table.insert(items, {
      name = "Remove View",
      func = function()
        game_removeRoom(editor.viewport)
      end,
      warning = "close",
      col = { r = 0.8, g = 0.2, b = 0.2, a = 0.4 },
    })
  end

  return items
end

function editor_cameraSettings()
  local settings = {}
  local buttons = {}

  table.insert(
    settings,
    classicConcat(
      { { name = "switch to:", size = 2.5, align = "left", func = nothing } },
      lume.map(editor.viewport.room.cameras, function(cam)
        return {
          name = tostring(cam.index),
          func = function()
            editor_resetCamera(cam.index)
            editor.preTempCam = editor.viewport.camera
            editor.preTempCam.index = editor.viewport.cameraID
            return true
          end,
          hover = function()
            editor.tempCam = cam.index
          end,
          update = function(x)
            x.col = (editor.preTempCam and editor.preTempCam.index == cam.index) and { r = 0, g = 0, b = 0, a = 0.5 } or nil
          end,
        }
      end)
    )
  )
  table.insert(
    settings,
    classicConcat(
      { { name = "set to:", size = 2.5, align = "left", func = nothing } },
      lume.map(editor.viewport.room.cameras, function(cam)
        return {
          name = tostring(cam.index),
          func = function()
            editor_setCamera(cam.index)
            return true
          end,
        }
      end)
    )
  )
  

  return settings
end

function editor_newUntitledRoom()
  local key = uuid()
  ctx.rooms[key] = deepcopy(engine.defaultLevel.rooms.defaultRoom)
  ctx.rooms[key].name = "Untitled Room " .. lume.count(ctx.rooms)
  ctx.rooms[key].sky.color = ctx.rooms[key].sky.color
  local view = {
    key = key,
    cameraID = 1,
    viewportID = 1,
    depth = 1,
    room = ctx.rooms[key],
    camera = deepcopy(ctx.rooms[key].cameras[1]),
    viewport = deepcopy(ctx.rooms[key].viewports[1]),
  }
  if add then table.insert(ctx.views, view) end
  scene_loadRoom(key)
  scene_setView(view)
  return view
end
local function drawSelectorBox(item)
  local current,txt,cut 
  if not item.target then 
    item.outline=false
    gfx.setColor(1, 1, 1, 1)
    gfx.draw(load_imageTex("bigPlus"), item.x + 8, item.y - 4) 
    gfx.printf(item.name2, mf(item.x + 2), mf(item.y + item.height - 15),item.width, "center")
    return 
  end
  gfx.fontSize(contextMenu.fontSize)
  if item.target.isRoom then 
    current = editor.viewport.key == item.key
    txt = item.key and ctx.rooms[item.key].name or item.name2
  elseif item.target.isCamera then
    txt = "Camera "..tostring(item.target.index)
  elseif item.target.views then
    current = ctx.page.key == item.key
    txt = item.key and ctx.level.pages[item.key].name or item.name2 or ""
  end

  if item.img then gfx.draw(item.img, item.x + 2, item.y + 2, 0, 0.5, 0.5) end
  local w = item.width - 4
  if contextMenu.open and item.options and item.context.editing == item.key then
    gfx.setColor(0, 0, 0, 0.6)
    gfx.rectangle("fill", mf(item.x + item.width - 17), mf(item.y ), 17, item.height - 2)
    for b, button in pairs(item.options) do
      editor_drawButton(button)
    end
    w = w - 17
  end
  
  if not item.target or item.hovered then
    gfx.fontSize(10)
    gfx.setColor(0, 0, 0, 0.8)
    gfx.rectangle("fill", item.x, mf(item.y + item.height - 12), item.width, 12)
    gfx.setColor(1, 1, 1, 0.9)
    gfx.printf(txt, mf(item.x + 2), mf(item.y + item.height - 12), w, "center")
  end
  if current then
    gfx.setLineWidth(1)
    gfx.setColor(1, 1, 1, 0.4)
    gfx.rectangle("line", mf(item.x + 2), mf(item.y + 2), cut == w and w or w + 1, mf(item.height - 3), 2)
  end
  gfx.fontSize(contextMenu.fontSize)
end

function editor_cameraSelector()
  local items = {}
  local row = {}
  local numRooms = lume.count(ctx.rooms)
  editor.roomPreview = { w = 70, h = 48 }

  local refresh = function()
    if contextMenu.open then
      local selected = contextMenu.selected
      contextMenu.items[1][2] = editor_contextRooms()
      contextMenu.selected = selected
      contextMenu.refresh = true
    end
  end

  local makeButton = function(target)
    table.insert(row, {
      name = "",
      key = target and target.key or nil,
      name2 = target and target.name or "new camera",
      align = "center",
      height = 50,
      outline = true,
      img = target and target.preview or nil,
      target = target,
      draw = drawSelectorBox,
      funcR = target and function(item)
        item.context.editing = item.key
        item.options = {}
      end or nil,
      hover = function()
        if target then 
          editor.tempCam = target.index
        end
      end,
      func = function(item)
       
      end,
    })
    if #row == 3 then
      row.height = 50
      table.insert(items, row)
      row = {}
    end
  end

  makeButton()
  for c,cam in ipairs(editor.viewport.room.cameras) do
    cam.index = c
    makeButton(cam)
  end

  if #row > 0 and #row < 3 then table.insert(items, { row[1], row[2] or { static = true }, row[3] or { static = true }, height = 50 }) end
  return items
end


function editor_roomSelector()
  local items = {}
  local row = {}
  local numRooms = lume.count(ctx.rooms)
  editor.roomPreview = { w = 70, h = 48 }

  local refresh = function()
    if contextMenu.open then
      local selected = contextMenu.selected
      contextMenu.items[1][2] = editor_contextRooms()
      contextMenu.selected = selected
      contextMenu.refresh = true
    end
  end

  local makeButton = function(target)
    table.insert(row, {
      name = "",
      key = target and target.key or nil,
      name2 = target and target.name or "new room",
      align = "center",
      height = 50,
      outline = true,
      col = target and target.sky.color or nil,
      img = target and target.preview or nil,
      target = target,
      draw = drawSelectorBox,
      funcR = target and function(item)
        item.context.editing = item.key
        item.options = {
          {
            x = item.x + item.width - 16,
            y = item.y + 3,
            w = 14,
            h = 14,
            col = { r = 0.8 / 1.3, g = 0.4 / 1.3, b = 0.2 / 1.3, a = 0.8 },
            func = function()
              editor_deleteRoom(item.key)
              refresh()
            end,
            text = "-x",
          },
          ctx.pageView and {
            x = item.x + item.width - 16,
            y = item.y + 19,
            w = 14,
            h = 14,
            col = { r = 0.4 / 2, g = 1 / 2, b = 0.5 / 2, a = 0.8 },
            func = function()
              game_addRoom(item.key)
            end,
            text = "+",
          } or nil,
          {
            x = item.x + item.width - 16,
            y = item.y + 18 + 16,
            w = 14,
            h = 14,
            col = { r = 0.1, g = 0.1, b = 0.1, a = 0.9 },
            func = function()
              editor_renameRoom(target)
            end,
            text = "…",
          },
        }
      end or nil,
      func = function(item)
        if item.context.editing ~= item.key or mouse.x < item.x + item.width - 18 then
          if not target then
            editor_newRoom()
            engine_waitFor(function()
              if numRooms ~= lume.count(ctx.rooms) then
                refresh()
                return true
              end
            end)
          elseif editor.viewport.key == target.key and ctx.pageView then
            game_enterRoom()
          elseif editor.viewport.key ~= target.key then
            game_openRoom(target.key)
          end
          item.context.editing = ""
        end
      end,
    })
    if #row == 3 then
      row.height = 50
      table.insert(items, row)
      row = {}
    end
  end

  makeButton()
  for r, room in lume.ripairs(sortBy(ctx.rooms, "dateCreated", "name", "key")) do
    makeButton(room)
  end

  if #row > 0 and #row < 3 then table.insert(items, { row[1], row[2] or { static = true }, row[3] or { static = true }, height = 50 }) end
  return items
end

function editor_setSelector()
  local items = {}
  local row = {}
  local numSets = lume.count(ctx.level.pages)

  local refresh = function()
    local selected = contextMenu.selected
    contextMenu.items[1][2] = editor_contextRooms()
    contextMenu.selected = selected
  end

  local makeButton = function(target)
    table.insert(row, {
      name = "",
      key = target and target.key or nil,
      name2 = target and target.name or "new set",
      align = "center",
      height = 50,
      outline = true,
      col = target and (target.color or ctx.page.sky.color) or nil,
      img = target and target.preview or nil,
      target = target,
      draw = drawSelectorBox,
      funcR = target and function(item)
        item.context.editing = item.key
        item.options = {
          {
            x = item.x + item.width - 16,
            y = item.y + 3,
            w = 14,
            h = 14,
            col = { r = 0.8 / 1.3, g = 0.4 / 1.3, b = 0.3 / 1.3, a = 0.8 },
            func = function()
              editor_deleteSet(item.key)
              refresh()
            end,
            text = "-x",
          },
          {
            x = item.x + item.width - 16,
            y = item.y + 19,
            w = 14,
            h = 14,
            col = { r = 0.1, g = 0.2, b = 0.4, a = 0.9 },
            func = function()
              editor_duplicateSet(item.key)
              refresh()
            end,
            text = "D",
          },
          {
            x = item.x + item.width - 16,
            y = item.y + 18 + 16,
            w = 14,
            h = 14,
            col = { r = 0.1, g = 0.1, b = 0.1, a = 0.9 },
            func = function()
              editor_renameSet(item.target)
            end,
            text = "…",
          },
        }
      end or nil,
      func = function(item)
        if item.context.editing ~= item.key or mouse.x < item.x + item.width - 18 then
          if not target then
            editor_newSet()
            engine_waitFor(function()
              if numSets ~= lume.count(ctx.level.pages) then
                refresh()
                return true
              end
            end)
          elseif target.key then
            game_openSet(target.key)
          end
          item.context.editing = ""
        end
      end,
    })
    if #row == 3 then
      row.height = 50
      table.insert(items, row)
      row = {}
    end
  end
  makeButton()
  for s, set in lume.ripairs(sortBy(ctx.level.pages, "dateCreated", "name", "key")) do
    makeButton(set)
  end

  if #row > 0 and #row < 3 then table.insert(items, { row[1], row[2] or { static = true }, row[3] or { static = true }, height = 50 }) end
  return items
end

function editor_duplicateSet(key)
  local new = uuid()
  ctx.level.pages[new] = deepcopy(ctx.level.pages[key])
  ctx.level.pages[new].key = new
  ctx.level.pages[new].dateCreated = os.time()
  ctx.level.pages[new].name = ctx.level.pages[key].name .. " copy"
end

function editor_deleteRoom(key)
  local delete = function(key)
    for i, s in pairs(ctx.level.pages) do
      for j, r in lume.ripairs(s.rooms) do
        if r.key == key then table.remove(ctx.level.pages[i].rooms, j) end
      end
    end

    for j, r in lume.ripairs(ctx.views) do
      if r.key == key then table.remove(ctx.views, j) end
    end

    ctx.rooms[key] = nil

    scene_setView(ctx.views[1])
  end

  if not ctx.pageView and key == editor.viewport.key then
    game_enterStackRoom(-1)
    editor_closeContextMenu()
    editor.viewport = nil
    engine_waitFor(function()
      if ctx.roomZoom == 1 then
        editor_deleteRoom(key)
        return true
      end
    end)
  else
    delete(key)
  end
end

function editor_deleteSet(key)
  if not ctx.pageView and key == ctx.page.key then
    game_enterStackRoom(-1)
    ctx.level.pages[key] = nil
    ctx.page = ctx.level.pages[lume.last(lume.sort(ctx.level.pages, "dateCreated")).key]
  else
    ctx.level.pages[key] = nil
  end
end

function editor_renameRoom(room)
  namer_typeString("Rename Room", room.name, mouse_getCoords(), function(x)
    room.name = x
  end)
end

function editor_renameSet(set)
  namer_typeString("Rename Set", set.name, mouse_getCoords(), function(x)
    set.name = x
  end)
end

function editor_newRoom(set, add)
  namer_typeString("New Room", "Untitled Room " .. tostring(lume.count(ctx.rooms) + 1), { x = mouse.x, y = mouse.y }, function(x)
    local key = uuid()
    ctx.rooms[key] = deepcopy(engine.defaultLevel.rooms.defaultRoom)
    ctx.rooms[key].key = key
    ctx.rooms[key].name = x
    ctx.rooms[key].dateCreated = os.time()
    ctx.rooms[key].sky.color = ctx.rooms[key].sky.color
    ctx.rooms[key].cameras = { camera_new() }
    if planPanel.open then
      ctx.rooms[key].ox = planPanel.moose.x
      ctx.rooms[key].oy = planPanel.moose.y
      plan_newRoom(ctx.rooms[key])
    end
    local view = {
      key = key,
      cameraID = 1,
      viewportID = 1,
      depth = 1,
      room = ctx.rooms[key],
      camera = deepcopy(ctx.rooms[key].cameras[1]),
      viewport = deepcopy(ctx.rooms[key].viewports[1]),
    }
    if set then table.insert(set, view) end
    scene_loadRoom(key)
    if add then scene_setView(view) end
  end)
end

function editor_newSet()
  namer_typeString("New Set", "Untitled Set " .. tostring(lume.count(ctx.level.pages) + 1), { x = mouse.x, y = mouse.y }, function(x)
    local key = uuid()
    ctx.level.pages[key] = { key = key, name = x, rooms = {}, dateCreated = os.time() }
  end)
end

local function setVert(t, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10)
  t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10] = v1, v2, v3, v4, v5, v6, v7, v8, v9, v10
  return t
end

function editor_nineSlice(image, rect, slice)
  local cw, ch = 80, 80 --image:getWidth()/2,image:getHeight()/2 -- corner size
  local diffy = (rect.y + rect.h - ch) - (rect.y + ch)
  if diffy < 0 then ch = ch + diffy / 2 end
  if not editor_nineSliceVerts then
    editor_nineSliceVerts = {}
    for i = 1, 54, 1 do
      table.insert(editor_nineSliceVerts, {})
    end
  end

  local slice = slice
  if not slice then
    slice = gfx.newMesh(zformat, 54, "triangles") -- print("MAKE SLICE",slice)
  end
  local verts = editor_nineSliceVerts
  local l, t, r, b, u, v, p, q
  local x1, x2, x3, x4 = rect.x, rect.x + cw, rect.x + rect.w - cw, rect.x + rect.w
  local y1, y2, y3, y4 = rect.y, rect.y + ch, rect.y + rect.h - ch, rect.y + rect.h
  local u1, u2, u3, u4 = 0, 0.5, 0.5, 1
  local v1, v2, v3, v4 = 0, 0.5, 0.5, 1
  local vert = 1

  local a = rect.a or 1
  for i = 1, 3, 1 do
    for j = 1, 3, 1 do
      l = i == 1 and x1 or (i == 2 and x2 or x3)
      t = j == 1 and y1 or (j == 2 and y2 or y3)
      r = i == 1 and x2 or (i == 2 and x3 or x4)
      b = j == 1 and y2 or (j == 2 and y3 or y4)

      u = i == 1 and u1 or (i == 2 and u2 or u3)
      v = j == 1 and v1 or (j == 2 and v2 or v3)
      p = i == 1 and u2 or (i == 2 and u3 or u4)
      q = j == 1 and v2 or (j == 2 and v3 or v4)

      setVert(verts[vert], l, t, 0, 0, u, v, 1, 1, 1, a)
      vert = vert + 1
      setVert(verts[vert], r, t, 0, 0, p, v, 1, 1, 1, a)
      vert = vert + 1
      setVert(verts[vert], r, b, 0, 0, p, q, 1, 1, 1, a)
      vert = vert + 1
      setVert(verts[vert], l, t, 0, 0, u, v, 1, 1, 1, a)
      vert = vert + 1
      setVert(verts[vert], r, b, 0, 0, p, q, 1, 1, 1, a)
      vert = vert + 1
      setVert(verts[vert], l, b, 0, 0, u, q, 1, 1, 1, a)
      vert = vert + 1
    end
  end

  slice:setVertices(verts)
  -- gfx.setWireframe(true)
  slice:setTexture(image)
  -- gfx.setWireframe(false)
  return slice
end

function editor_makeDropShadow(cm,r)
  if game.state == "Load" then return end
  local cmw = cm.w or cm.width
  local cmh = cm.h or cm.height
  cm.shadowMesh = editor_nineSlice(load_imageTex("ContextShadow"), { x = cm.x - r, y = cm.y - r + 5, w = cmw + r * 2, h = cmh + r * 2 }, cm.shadowMesh)
end

function editor_updateLoupe()
  mouse_setVisible(false)
  local scale = 1
  editor.pickerCanvas = editor.pickerCanvas or newCanvas(scale, scale)

  gfx.push("all")
  gfx.setCanvas(editor.pickerCanvas)
  gfx.draw(engine.canvas, -mouse.x, -(screenH - mouse.y), 0, scale, scale)
  gfx.pop()

  local verts = ffi.new("m_vert[?]", 64)
  local radius = 15
  for i = 0, 32, 1 do
    local x, y = math.cos((i / 16) * pi), math.sin((i / 16) * pi)
    local v = verts[i]
    v.x, v.y, v.z, v.w, v.u, v.v, v.r, v.g, v.b, v.a =
      radius * x, radius * y, 0, 1, x * 0.5 + 0.5, y * 0.5 + 0.5, 1, 1, 1, 1
  end
  editor.pickerMesh = editor.pickerMesh or gfx.newMesh(zformat, 32, "fan")
  editor.pickerMesh:setVertices(verts,1,32)
  gfx.setTexture(editor.pickerMesh, editor.pickerCanvas)

  if not editor.pickerBacker then 
    local radius = 18
    for i = 0, 32, 1 do
      local x, y = radius * math.cos((i / 16) * pi), radius * math.sin((i / 16) * pi)
      if i > 8 and i < 16 then
        x, y = -radius, radius
      end
      local v = verts[i]
      v.x, v.y, v.z, v.w, v.u, v.v, v.r, v.g, v.b, v.a = x, y, 0, 1, 0, 0, 0.1, 0.1, 0.1, 1
    end
    editor.pickerBacker = gfx.newMesh(zformat, 32, "fan")
    editor.pickerBacker:setVertices(verts,1,32)
  end
  editor.pickerRadius = 18
  editor.drawLoupe=true
end

function editor_drawLoupe()
  if not (editor.pickingColor or editor.drawLoupe or altDown) then return end
  if not editor.pickerCanvas then return end
  gfx.push("all")
  gfx.setBlendMode("alpha")
  gfx.draw(editor.pickerCanvas)
  gfx.translate((mouse.x + editor.pickerRadius) / game.resolution, (mouse.y - editor.pickerRadius) / game.resolution)
  gfx.setShader(shaders.forwardRender)
  gfx.setDepthMode("always")
  gfx.setColor(1, 1, 1, 1)
  gfx.draw(editor.pickerBacker)
  gfx.setColor(1, 1, 1, 1)
  gfx.draw(editor.pickerMesh)
  gfx.setShader()
  gfx.setLineWidth(1)
  -- gfx.arc("line","open", 0, 0, 32 / 2, 0,pi,16)
  -- gfx.setColor(0,0,0,1)
  -- gfx.arc("line","open", 0, 0, 32 / 2, pi,2*pi,16)
  local s = 4
  gfx.setColor(1, 1, 1, 0.3)
  -- gfx.line(-s,-s,s,-s)
  -- gfx.line(-s,-s,-s,s)
  gfx.setColor(1, 1, 1, 0.3)
  -- gfx.line(s,s,s,-s)
  -- gfx.line(s,s,-s,s)
  gfx.line(-s, 0, s, 0)
  gfx.line(0, -s, 0, s)
  gfx.pop()
  editor.drawLoupe=false
end

function editor_drawContextBack(cm)
  cm.shadow = cm.shadow or 0.5

  local alpha = cm.backTex and 1 or (cm.alpha or 1)
  local cmx , cmy , cmw , cmh = cm.x, cm.y, cm.w or cm.width, cm.h or cm.height

  if cm.padding then
    cmx = cmx - cm.padding
    cmy = cmy - cm.padding
    cmw = cmw + cm.padding * 2
    cmh = cmh + cm.padding * 2
  end

  if game.state ~= "Load" and cm.shadowMesh then
    gfx.push("all")
    gfx.setBlendMode("alpha")
    gfx.setColor(1, 1, 1, 0)
    gfx.draw(cm.shadowMesh)
    gfx.pop()
  end

  gfx.push("all")
  gfx.setShader()
  gfx.setBlendMode("alpha")
  if cm.color then gfx.setColor(cm.color.r, cm.color.g, cm.color.b, cm.color.a * alpha) end
  gfx.setColor(0.1, 0.1, 0.1, alpha)
  gfx.rectangle("fill", cmx, cmy, cmw, cmh, cm.sharp and 0 or 6)
  gfx.pop()
end


function editor_updateMenuItem(menu,item)
  local hovered = item.hovered
  if not (item.subitems) then 
    item.y = menu.back.y+menu.yoff+menu.scroll
  else 
    -- item.
  end
  editor_updateButton(item)

  if item.hovered and not hovered then 
    if item.items then
      menu.subSelected = item
      if not item.created then 
        item.back = {}
          item.back.padding = menu.back.padding
          item.back.height = item.subHeight or lume.count(item.items) * (contextMenu.itemHeight + contextMenu.spacing)
          item.back.width = item.subWidth or (menu.subWidth or menu.width)
          item.direction =  (menu.back.x+menu.back.width+item.back.width > screenW) and -1 or 1
          item.back.x =menu.back.x + ((item.direction < 0 and item.back.width or menu.back.width) + item.back.padding*2) * item.direction
          item.back.y = (menu.up and -item.back.height + item.height or 0) + item.y - math.max(0,item.y+item.back.height+item.back.padding+1-screenH)
          item.back.alpha = menu.back.alpha
        item.subWidth = item.back.width
        item.scroll = 0
        item.up = menu.up
        editor_createMenuItems(item)
      end
    else 
      menu.subSelected = nil 
    end
  end

  item.open = menu.subSelected == item


  if item.open then  
    editor_updateContextMenuItems(item)
    item.scroll = mc(-math.zero(item.yoff-item.back.height), item.scroll + mouse.scroll.y * 5,0) or 0
    -- print(item.scroll,mouse.scroll.y*5)
  end
end

function editor_updateContextMenuItems(menu)
  if mouse_in(menu.back) then
    contextMenu.hovered = true
    mouse_setVisible(true)
  end
  if menu==contextMenu and contextMenu.reSub then 
    local context,item = contextMenu.reSub[1],contextMenu.reSub[2]
    context.subTitle = item.name
    context.items = lume.concat({ context.items[1] }, item.subitems)
    contextMenu.reSub=false
    editor_createMenuItems(context)
  end

  if not (menu.created and menu.open) then return end
  menu.yoff = 0
  for i, item in ipairs(menu.items) do
    if #item > 0 then
      for s, split in ipairs(item) do
        editor_updateMenuItem(menu,split)
      end
    else
      editor_updateMenuItem(menu,item)
    end
    menu.yoff = menu.yoff + (item.height or contextMenu.itemHeight)
  end
end

function editor_drawMenuItem(menu,item)
  editor_drawButton(item)
  editor_drawContextMenu(item)
end

function editor_drawContextMenu(menu)
  if not (menu.created and menu.open) then return end
  editor_drawContextBack(menu.back)
  gfx.setFont(fonts.interfont, contextMenu.fontSize)
  for i, item in ipairs(menu.items) do
    if #item > 0 then
      for s, split in ipairs(item) do
        editor_drawMenuItem(menu,split)
      end
      if i==1 and item[1].subitems then 
        gfx.setScissor(menu.back.x,menu.back.y+contextMenu.itemHeight,menu.back.width,menu.back.height-contextMenu.itemHeight)
      end
    else
      editor_drawMenuItem(menu,item)
    end
  end
  gfx.setScissor()
end
