Skip to content

Instantly share code, notes, and snippets.

@geekhunger
Last active August 29, 2015 14:11
Show Gist options
  • Save geekhunger/4609954c9a484a085650 to your computer and use it in GitHub Desktop.
Save geekhunger/4609954c9a484a085650 to your computer and use it in GitHub Desktop.
Full Source Of Pixelboy
--# Main
-------------------------------------------------------------- NAMESPACES
local Touches = {}
local Frames = {}
local CurrentFrame = 0
local Action = {}
local Actions, CurrentAction = {}
local Canvas = {}
-------------------------------------------------------------- /NAMESPACES
-------------------------------------------------------------- CANVAS
function Canvas:setup(width, height)
self.x = WIDTH/2
self.y = HEIGHT/2
self.width = width
self.height = height
self.zoom = 1
Action:clearAll()
self:addFrame()
end
function Canvas:emptyImage()
return image(self.width, self.height)
end
function Canvas:addFrame()
local key = tostring(math.random()):sub(3)
local img = self:emptyImage()
CurrentFrame = CurrentFrame + 1
saveImage("Documents:Pixelboy_"..key, img)
table.insert(Frames, CurrentFrame, key)
self.image = readImage("Documents:Pixelboy_"..Frames[CurrentFrame])
Action:saveState()
end
function Canvas:moveFrame(from, to)
local _key = Frames[from]
CurrentFrame = to
table.remove(Frames, from)
table.insert(Frames, to, _key)
Action:saveState()
end
function Canvas:deleteFrame(frame)
frame = frame or CurrentFrame
if frame <= #Frames then
table.remove(Frames, CurrentFrame, frame)
CurrentFrame = math.max(CurrentFrame - 1, 1)
if #Frames == 0 then
CurrentFrame = 0
self:addFrame()
else
Action:saveState()
end
end
end
function Canvas:draw()
noStroke()
fill(100)
rect(Canvas.x, Canvas.y, Canvas.width * Canvas.zoom, Canvas.height * Canvas.zoom)
sprite(Canvas.image, Canvas.x, Canvas.y, Canvas.width * Canvas.zoom, Canvas.height * Canvas.zoom)
end
-------------------------------------------------------------- /CANVAS
-------------------------------------------------------------- ACTIONS
function Action:saveState()
-- Lua memory storage is restricted to ~4MB = 4096Kb on iPad. If Actions stack rises to high, lower it (this also cleans "Documents")
while #Actions > 1 and collectgarbage("count") > 4000 do -- max 3.9MB, just to be save
local whitelist = table.concat(Actions[2].Frames, " ")
for imgKey, _ in pairs(Actions[1].Frames) do
if not whitelist:find(imgKey) then
saveImage("Documents:Pixelboy_"..imgKey, nil)
end
end
table.remove(Actions, 1)
collectgarbage()
end
-- Undid some Actions until CurrentAction -> Delete all redundant Actions from stack until #Actions
if CurrentAction then
local whitelist = table.concat(Actions[CurrentAction].Frames, " ")
for id = CurrentAction + 1, #Actions do
for imgKey, pos in pairs(Actions[id].Frames) do
if pos > 1 and not whitelist:find(imgKey) then
saveImage("Documents:Pixelboy_"..imgKey, nil)
end
end
Actions[id] = nil
end
CurrentAction = nil
end
-- Add new Action to the stack
local frames = {}
for pos, imgKey in ipairs(Frames) do
frames[imgKey] = pos
end
table.insert(Actions, {CurrentFrame = CurrentFrame, Frames = frames})
end
function Action:restoreState()
if CurrentAction then
local action = Actions[CurrentAction]
CurrentFrame = action.CurrentFrame
Frames = {}
for imgKey, pos in pairs(action.Frames) do
Frames[pos] = imgKey
end
Canvas.image = readImage("Documents:Pixelboy_"..Frames[CurrentFrame])
end
end
function Action:clearAll()
local list = assetList("Documents")
for id, imgKey in ipairs(list) do
if imgKey:find("Pixelboy_") then
saveImage("Documents:"..imgKey, nil)
end
end
end
function Action:undo()
if #Actions > 0 then
CurrentAction = math.max((CurrentAction or #Actions) - 1, 1)
Action:restoreState()
end
end
function Action:redo()
if CurrentAction then
CurrentAction = math.min(CurrentAction + 1, #Actions)
Action:restoreState()
if CurrentAction == #Actions then CurrentAction = nil end
end
end
-------------------------------------------------------------- /ACTIONS
-------------------------------------------------------------- PARAMETERS
local function onUndo()
Action:undo()
end
local function onRedo()
Action:redo()
end
local function onAddFrame()
Canvas:addFrame()
end
local function onDeleteFrame()
Canvas:deleteFrame()
end
local function onPrevFrame()
CurrentFrame = CurrentFrame - 1
if CurrentFrame < 1 then CurrentFrame = #Frames end
Canvas.image = readImage("Documents:Pixelboy_"..Frames[CurrentFrame])
--Action:saveState()
end
local function onNextFrame()
CurrentFrame = CurrentFrame + 1
if CurrentFrame > #Frames then CurrentFrame = 1 end
Canvas.image = readImage("Documents:Pixelboy_"..Frames[CurrentFrame])
--Action:saveState()
end
-------------------------------------------------------------- /PARAMETERS
-------------------------------------------------------------- APP LOGIC
function setup()
displayMode(STANDARD)
noSmooth()
rectMode(CENTER)
Canvas:setup(128, 128)
parameter.watch("DEBUGGER")
parameter.color("Color", color(0))
parameter.integer("Size", 1, 10, 1)
parameter.action("Undo", onUndo)
parameter.action("Redo", onRedo)
parameter.action("addFrame", onAddFrame)
parameter.action("deleteFrame", onDeleteFrame)
parameter.action("prevFrame", onPrevFrame)
parameter.action("nextFrame", onNextFrame)
end
function draw()
background(80, 70, 80)
Canvas:draw()
-- DEBUG
collectgarbage()
DEBUGGER = math.floor(1/DeltaTime).. "fps | "..string.format("%iKb", collectgarbage("count"))
end
function touched(touch)
-- REGISTER and UPDATE registered TOUCHES
local regId do
if #Touches > 0 then
for i, t in ipairs(Touches) do
if t.id == touch.id then
regId = i
Touches[regId] = touch -- update
break
end
end
end
if not regId and touch.state == BEGAN then
local allow = true
if #Touches > 0 then
for _, t in ipairs(Touches) do
if t.state ~= BEGAN then -- forbid multitouch when singeltouch is already acting
allow = false
break
end
end
end
if allow then
--if allow or multitouchPaused then -- but allow multitouch to continue if it was just paused
table.insert(Touches, touch)
regId = #Touches
end
end
end
-- MULTITOUCH
if #Touches > 1 then
if multitouchPaused then multitouchPaused = nil end -- reset
-- Two-Finger Gestures
if #Touches == 2 then
local Touch1, Touch2 = Touches[#Touches-1], Touches[#Touches] -- Touch references
-- Pan/Zoom
if Touch1.state == MOVING and Touch2.state == MOVING then
local prevCenter = (vec2(Touch1.deltaX, Touch1.deltaY) + vec2(Touch2.deltaX, Touch2.deltaY)) / 2
local currCenter = (vec2(Touch1.x, Touch1.y) + vec2(Touch2.x, Touch2.y)) / 2
local prevDist = vec2(Touch1.prevX, Touch1.prevY):dist(vec2(Touch2.prevX, Touch2.prevY))
local currDist = vec2(Touch1.x, Touch1.y):dist(vec2(Touch2.x, Touch2.y))
local pinchDelta = currDist / prevDist
if currDist > 150 then -- max finger distance for jerk-free pan!
Canvas.zoom = math.min(math.max(Canvas.zoom * pinchDelta, .2), 200) -- min/max zoom factors!
end
Canvas.offset = Canvas.offset or vec2(0, 0) -- defaults
Canvas.pivot = Canvas.pivot or vec2(Canvas.x, Canvas.y)
Canvas.offset = Canvas.offset + (currCenter - Canvas.pivot)/Canvas.zoom - prevCenter/Canvas.zoom/2
Canvas.pivot = currCenter -- update pivot
Canvas.x = Canvas.pivot.x - Canvas.offset.x * Canvas.zoom
Canvas.y = Canvas.pivot.y - Canvas.offset.y * Canvas.zoom
-- Reset Canvas zoom and position
else
if Touch1.tapCount > 2 or Touch2.tapCount > 2 then -- Tripple-Tap
Canvas.x = WIDTH/2
Canvas.y = HEIGHT/2
Canvas.zoom = 1
Canvas.pivot = nil
Canvas.offset = nil
end
end
end
-- SINGLETOUCH
elseif #Touches == 1 then
local Touch = Touches[#Touches] -- Touch reference
-- Scales to Canvas bounds
local canvasScaleFactor = vec2(Canvas.width / (Canvas.width * Canvas.zoom), Canvas.height / (Canvas.height * Canvas.zoom))
local canvasPos = vec2(Canvas.x * canvasScaleFactor.x - Canvas.width/2, Canvas.y * canvasScaleFactor.y - Canvas.height/2)
local prevPos = vec2(Touch.prevX * canvasScaleFactor.x, Touch.prevY * canvasScaleFactor.y) - canvasPos
local currPos = vec2(Touch.x * canvasScaleFactor.x, Touch.y * canvasScaleFactor.y) - canvasPos
-- Touching Canvas
if currPos.x > 0 and currPos.x < Canvas.width and currPos.y > 0 and currPos.y < Canvas.height then
-- Draw on Canvas
if Touch.state ~= BEGAN then
strokeWidth(Size/2)
stroke(Color)
if Color.a == 0 then
stroke(0)
blendMode(ZERO, ONE_MINUS_SRC_ALPHA)
end
setContext(Canvas.image)
rect(currPos.x, currPos.y, strokeWidth(), strokeWidth()) -- noSmooth() fix to line()
line(prevPos.x, prevPos.y, currPos.x, currPos.y)
setContext()
blendMode(NORMAL)
canvasContact = true -- for Action.saveState, after gesture ENDED!
end
-- Touching UI
else
--CODE--
print(Touch.state)
end
end
-- DEREGISTER TOUCHES
if regId and Touches[regId].state == ENDED then
if #Touches > 1 then
--multitouchPaused = true -- Lock on unfinished multitouch
Touchs = {}
else
-- Save version of Canvas image and record Canvas state for undo/redo!
if canvasContact then
local key = tostring(math.random()):sub(3)
local img = Canvas.image
Frames[CurrentFrame] = key
saveImage("Documents:Pixelboy_"..key, img)
Action:saveState()
canvasContact = nil
end
multitouchPaused = nil
end
table.remove(Touches, regId)
end
end
-------------------------------------------------------------- /APP LOGIC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment