New version after 51 days


I just published a new version with a few changes. Firstly, an incompatibility: creating an editor now requires a font, where it used to implicitly use the current font. I'm fixing one previous Devlog post that used this feature. Hopefully there won't be any further incompatibilities.


This version also makes 2 bugfixes:


Finally, we have a new feature: a little helper called `run_screen` to load code from one screen into another. Many thanks to Ryan for the suggestion. I've been feeling the need for an escape hatch like this as some of my programs have gotten too large to edit on my phone. The shrinking scrollbar has gotten harder to grab while scrolling. And Ryan's idea is just what I need. For example, a few weeks ago I showed an equation plotter in Lua Carousel that let you pan and zoom around on an infinite surface. One thing I'd been wanting to add to this program was ticks along the x- and y-axis. But my attempts took my script too far away from the ideal size of 100 lines or so. Now I have it across two screens, and as a bonus the 'ticks' screen is easy to reuse from other scripts. Here's how it looks:

function ticks(lo, hi)
  local om = order_of_magnitude(hi-lo)
  return approximate(lo, om), approximate_up(hi, om)
end
function order_of_magnitude(n)
  return floor(math.log(n)/math.log(10))
end
function approximate(n, zeros)
  -- turn n into a number with n zeros
  if zeros >= 0 then
    for i=1,zeros do n = n/10 end
  else
    for i=zeros,0 do n = n*10 end
  end
  n = floor(n)
  if zeros >= 0 then
    for i=1,zeros do n = n*10 end
  else
    for i=zeros,0 do n = n/10 end
  end
  return n
end
function approximate_up(n, zeros)
  -- turn n into a number with n zeros
  if zeros >= 0 then
    for i=1,zeros do n = n/10 end
  else
    for i=zeros,0 do n = n*10 end
  end
  n = ceil(n)
  if n == 0 then n = 1 end
  if zeros >= 0 then
    for i=1,zeros do n = n*10 end
  else
    for i=zeros,0 do n = n/10 end
  end
  return n
end

To add ticks to my plotter -- that adapt to my panning and zooming -- I run this screen and then call `ticks()` for each axis with the appropriate bounds to get back a reasonably round number near the bounds to start or stop drawing ticks at. Here's how the plotter looks now:

run_screen('ticks')
v = {x=-5, y=-5*Safe_height/Safe_width}
v.w, v.h = -2*v.x, -2*v.y
v.zoom = Safe_width/v.w
f,s = nil  -- ids of first and second touches
start, curr = {}, {}  -- coords of touches
initzoom = nil
initpos = nil -- for panning
function car.draw()
  draw_axes()
  color(0,0,1)
  plot(sin)
  color(0,0.6,0)
  plot(function(x) return sin(2*x) end)
  color(0,0.8,1)
  plot(function(x) return 2*sin(x) end)
  color(0.8,0.6,0)
  plot(function(x) return x^2 end)
  draw_hud()
end
function plot(f)
  local prev_vy = vy(f(sx(0)))
  for vx=1,Safe_width do
    local curr_vy = vy(f(sx(vx)))
    line(vx-1, prev_vy, vx, curr_vy)
    prev_vy = curr_vy
  end
end
function draw_axes()
  color(0.5,0.5,0.5)
  line(0, vy(0), Safe_width, vy(0))
  line(vx(0), 0, vx(0), Safe_height)
  local xlo, xhi = ticks(sx(0), sx(Safe_width))
  for i=0,10 do
    local x = xlo+i/10*(xhi-xlo)
    local vx, vy = vx(x), vy(0)
    line(vx, vy, vx, vy+5)
    g.print(x, vx-10, vy+10)
  end
  local ylo, yhi = ticks(sy(Menu_bottom), sy(Safe_height))
  for i=0,10 do
    local y = ylo+i/10*(yhi-ylo)
    local vx, vy = vx(0), vy(y)
    line(vx, vy, vx+5, vy)
    g.print(y, vx+10, vy+5)
  end
end
function draw_hud()
  local w = App.width('sin(2*x)')
  local y = Menu_bottom+10
  color(0,0,1)
  g.print('sin(x)', Safe_width-w-35, y)
  y = y+Line_height
  color(0,0.6,0)
  g.print('sin(2x)', Safe_width-w-35, y)
  y = y+Line_height
  color(0,0.8,1)
  g.print('2sin(x)', Safe_width-w-35, y)
  y = y+Line_height
  color(0.8,0.6,0)
  g.print('x^2', Safe_width-w-35, y)
  color(0.5, 0.5, 0.5)
  for _,touch in ipairs(touches()) do
    if curr[touch] then
      circle('fill', curr[touch].x, curr[touch].y, 10)
    end
  end
end
function car.touchpressed(id, x,y, ...)
  if f == nil then
    f = id
    initpos = {x=v.x, y=v.y}
  else
    s = id
    initzoom = v.zoom
  end
  start[id] = {x=x, y=y}
  curr[id] = {x=x, y=y}
end
function car.touchreleased(id, x,y, ...)
  f,s = nil
  start, curr = {}, {}
  initzoom = nil
  initpos = nil
end
function car.touchmoved(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
      adjust_viewport(oldzoom, v.zoom)
    elseif f then
      v.x = initpos.x + iscale(start[f].x - x)
      v.y = initpos.y - iscale(start[f].y - y)
    end end end
function adjust_viewport(oldzoom, zoom)
  -- ensure centroid of fingers remains in view
  local c = centroid(curr[f], curr[s])
  v.x = v.x + c.x/oldzoom - c.x/zoom
  v.y = v.y + c.y/oldzoom - c.y/zoom
end
function centroid(a, b)
  return{x=(a.x+b.x)/2, y=(a.y+b.y)/2}
end
function dist(p1, p2)
  return ((p2.x-p1.x)^2 + (p2.y-p1.y)^2) ^ 0.5
end
function vx(sx) return scale(sx-v.x) end
function vy(sy) return Safe_height - scale(sy-v.y) end
function scale(d) return d*v.zoom end
function sx(vx) return v.x + iscale(vx) end
function sy(vy) return v.y + iscale(Safe_height-vy) end
function iscale(d) return d/v.zoom end

If you try pasting this program into Lua Carousel, remember to first run the abbreviations on one of the example screens. Or if you've deleted that screen, here are the abbreviations I used in this post:

g = love.graphics
line, circle = g.line, g.circle
color = g.setColor
floor, ceil = math.floor, math.ceil
sin = math.sin
touches = love.touch.getTouches

Files

carousel-bf.love 113 kB
Feb 16, 2024
carousel-bf-safe.love 113 kB
Feb 16, 2024

Get Lua Carousel

Leave a comment

Log in with itch.io to leave a comment.