Last active
August 29, 2015 14:11
-
-
Save geekhunger/4609954c9a484a085650 to your computer and use it in GitHub Desktop.
Full Source Of Pixelboy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# 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