Template repo for tiny cross-platform apps that can be modified on phone, tablet or computer.
-- main responsibilities:
--  render widgets based on current viewport settings
--  dispatch keyboard and touch events to appropriate widget
--  pan viewport based on one-finger touch (from either mouse or touchscreen)
--  adjust zoom based on two-finger touch (touchscreen only)

ui_state = {}  -- for buttons; recreated each frame
widgets = {}  -- more versatile UI elements

function car.draw()
  ui_state.button_handlers = {}
  for _,w in ipairs(widgets) do w.draw() end
  draw_hud()
end

function car.update(dt)
  for _,w in ipairs(widgets) do
    if w.update then
      w.update(dt, App.mouse_x(), App.mouse_y())
    end
  end
end

function car.mouse_press(x,y, b)
  skip_touch = nil
  if mouse_press_consumed_by_any_button(ui_state, x,y, b) then
    skip_touch = true
    return
  end
  cursor = nil
  for _,w in ipairs(widgets) do
    if w.ispress(x,y) then
      skip_touch = true
      return w.press(x,y, b)
    end
  end
  if f == nil then
    start.mouse = {x=x, y=y}
    curr.mouse = {x=x, y=y}
    f = 'mouse'
    initpos = {x=v.x, y=v.y}
  end
end

function car.touch_press(id, x,y, ...)
  if skip_touch then return end
  start[id] = {x=x, y=y}
  curr[id] = {x=x, y=y}
  if f == 'mouse' then
    f = id
  elseif f then
    s = id
    initzoom = v.zoom
    initpos = nil
  end
end

function car.mouse_move(x,y)
  if start.mouse then
    curr.mouse = {x=x, y=y}
    if not s then
      v.x = initpos.x + iscale(start.mouse.x - x)
      v.y = initpos.y + iscale(start.mouse.y - y)
    end end end

function car.touch_move(id, x,y, ...)
  if start[id] then
    curr[id] = {x=x, y=y}
    if s then
      local oldzoom = v.zoom
      v.zoom = dist(curr[f], curr[s])/dist(start[f], start[s])*initzoom
      local c = centroid(curr[f], curr[s])
      v.x = v.x + c.x/oldzoom - c.x/v.zoom
      v.y = v.y + c.y/oldzoom - c.y/v.zoom
      for _,w in ipairs(widgets) do
        if w.refresh_font then w.refresh_font() end
      end
    end end end

function car.mouse_release(x,y, b)
  for _,w in ipairs(widgets) do
    if w.release then w.release(x,y, b) end
  end
  f,s = nil
  start, curr = {}, {}
  skip_touch = nil
  initzoom = nil
  initpos = nil
end

function car.keychord_press(chord, key)
  if cursor then cursor.presschord(chord, key) end
end

function car.text_input(t)
  if cursor then cursor.textinput(t) end
end

function car.key_release(key)
  if cursor then cursor.releasekey(key) end
end

function car.quit()
  for name,w in pairs(widgets) do
    if w.quit then w.quit() end
  end
end