New version after 41 days, and stop-motion animation


Today's version has some minor bugfixes and API changes. Here's a list of key changes in the repository:

Nothing very likely, but still worth an upgrade.

I found out recently that a nice UI for stop-motion animation is to show previous frame(s) overlaid when recording the next frame. Here's a program that demonstrates this idea:

run_screen('widgets')
local I = {}
local mode = 'record'
-- available modes
I.record = {widgets={}}
I.play = {widgets={}}
I.speed = 3 -- frames/s
function car.draw()
  for name,w in pairs(I[mode].widgets) do
    if w.draw then w.draw() end 
  end
  if I[mode].draw then
    I[mode].draw()
  end
end
function car.mouse_press(x,y, b)
  for name,w in pairs(I[mode].widgets) do
    if w.ispress and w.ispress(x,y) and w.press then
      return w.press()
    end end
  if I[mode].mouse_press then
    I[mode].mouse_press(x,y, b)
  end
end
function car.mouse_release(...)
  if I[mode].mouse_release then
    I[mode].mouse_release(...)
  end
end
function car.mouse_move(...)
  if I[mode].mouse_move then
    I[mode].mouse_move(...)
  end
end
function car.update(...)
  if I[mode].update then
    I[mode].update(...)
  end
end
I.frames = {}
I.strokes = {}
function I.record.draw()
  g.setLineWidth(3)
  g.print(#I.frames, 100,100)
  color(0.5,0.5,0.5)
  for _,frame in ipairs(I.frames) do
    for _,stroke in ipairs(frame) do
    line(unpack(stroke))
  end end
  color(0,0,0)
  for _,stroke in ipairs(I.strokes) do
    line(unpack(stroke))
  end
end
function I.record.mouse_press(x,y)
  I.stroke = {x,y,x,y}
  table.insert(I.strokes, I.stroke)
end
function I.record.mouse_release()
  I.stroke = nil
end
function I.record.mouse_move(x,y)
  if I.stroke == nil then return end
  table.insert(I.stroke, x)
  table.insert(I.stroke, y)
end
function I.add_next()
  local x,y, w,h = 200, Menu_bottom+100, 20,20
  local draw = function()
    color(1,0,1)
    g.print('>', x,y)
  end
  local ispress = function(x2,y2)
    return x <= x2 and x2 <= x+w and y <= y2 and y2 <= y+h
  end
  local press = function()
    table.insert(I.frames, I.strokes)
    I.strokes = {}
  end
  return {draw=draw, ispress=ispress, press=press}
end
I.record.widgets.add_next = I.add_next()
function I.remove_previous()
  local x,y, w,h = 160, Menu_bottom+100, 20,20
  local draw = function()
    color(1,0,1)
    g.print('<', x,y)
  end
  local ispress = function(x2,y2)
    return x <= x2 and x2 <= x+w and y <= y2 and y2 <= y+h
  end
  local press = function()
    table.remove(I.frames)
  end
  return {draw=draw, ispress=ispress, press=press}
end
I.record.widgets.remove_previous = I.remove_previous()
function I.play_widget()
  local x,y, w,h = 250, Menu_bottom+100, 20,20
  local draw = function()
    color(1,0,1)
    poly('fill', x,y, x+w, y+h/2, x, y+h)
  end
  local ispress = function(x2,y2)
    return x <= x2 and x2 <= x+w and y <= y2 and y2 <= y+h
  end
  local press = function()
    print('play')
    mode = 'play'
  end
  return {draw=draw, ispress=ispress, press=press}
end
I.record.widgets.play = I.play_widget()
function I.record_widget()
  local x,y, w,h = 250, Menu_bottom+100, 20,20
  local draw = function()
    color(1,0,1)
    circle('fill', x+w/2,y+h/2, min(w,h)/2)
  end
  local ispress = function(x2,y2)
    return x <= x2 and x2 <= x+w and y <= y2 and y2 <= y+h
  end
  local press = function()
    print('record')
    mode = 'record'
  end
  return {draw=draw, ispress=ispress, press=press}
end
I.play.widgets.record = I.record_widget()
I.frame_index = 1
function I.play.draw()
  g.setLineWidth(3)
  color(0,0,0)
  local f = floor(I.frame_index)
  g.print(f, 100,100)
  for _,stroke in ipairs(I.frames[f]) do
    line(unpack(stroke))
  end
  color(0,0,0)
end
function I.play.update(dt)
  I.frame_index = I.frame_index+dt*I.speed
  if I.frame_index >= #I.frames+1 then
    I.frame_index = 1
  end
end

You'll also need the 'widgets' screen I showed a while ago:

-- helper screen for creating UI widgets on screen
widgets = {}  -- global; clear stuff from any previous windows
              -- still ugly, though; you'll see cruft when you switch screens. TODO
long_press_duration = 1
function widgets.__draw()
  for name,w in pairs(widgets) do
    if type(name) ~= 'string' or name:find('__') == nil and w.draw then
      w.draw()
    end end end
function widgets.__press(x,y, b)
  for name,w in pairs(widgets) do
    if type(name) ~= 'string' or name:find('__') == nil then
      if w.ispress and w.ispress(x,y) and w.press then
        return w.press()
      end end end end
function widgets.__update(dt)
  local x, y = App.mouse_x(), App.mouse_y()
  local mouse_down = App.mouse_down(1)
  for name,w in pairs(widgets) do
    if name:find('__') == nil then
      if w.update then w.update(dt, x,y) end
      if w.long_press then
        if mouse_down and w.ispress(x,y) then
          if w.press_time == nil then
            w.press_time = 0
          else
            w.press_time = w.press_time+dt
            if w.press_time > long_press_duration then
              w.long_press()
              w.press_time = nil
            end
          end
        else
          w.press_time = nil
        end
      end end end end
function widgets.__release(x,y, b)
  for name,w in pairs(widgets) do
    if type(name) ~= 'string' or name:find('__') == nil then
      if w.ispress and w.ispress(x,y) and w.release then
        return w.release()
      end end end end

Finally, you'll also need 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 = g.line
poly = g.polygon
circle = g.circle
color = g.setColor
min, max = math.min, math.max
floor, ceil = math.floor, math.ceil

Files

carousel2-al.love 104 kB
5 days ago

Get Lua Carousel

Leave a comment

Log in with itch.io to leave a comment.