Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Cider2 - version 1.02 - Windowed controls for Codea
--# Main
function setup()
displayMode(FULLSCREEN)
--get an account key from BING to make this work.
--key = "YOUR_ACCOUNT_KEY_FROM_BING"
bing = BingImageSearch(key)
--Redirect()
--DEBUG = true
-- ===================
-- Simple UI used in both worlds...
-- ===================
local params = {pos=vec2(WIDTH/2-100,HEIGHT-125), size=vec2(120,44), fontsize=14,
backColor=color(141, 137, 197, 255), highlight=color(206, 206, 206, 255),
foreColor=color(0, 0, 0, 255), id="btnBing",
text="Run Bing Search", onClicked=btnBing_Clicked}
btnBing = TextButton(params)
local params = {pos=vec2(WIDTH/2+300,HEIGHT-125), size=vec2(120,44), fontsize=14,
backColor=color(141, 137, 197, 255), highlight=color(206, 206, 206, 255),
foreColor=color(0, 0, 0, 255), id="btnNext",
text="Next >>", onClicked=btnNext_Clicked}
btnNext = TextButton(params)
local params = {pos=vec2(100,HEIGHT-125), size=vec2(120,44), fontsize=14,
backColor=color(141, 137, 197, 255), highlight=color(206, 206, 206, 255),
foreColor=color(0, 0, 0, 255), id="btnMode",
text="Mode", onClicked=btnMode_Clicked}
btnMode = TextButton(params)
local params = {pos=vec2(64,HEIGHT-64), size=vec2(WIDTH-128,44), fontsize=24,
text="", backColor=color(255, 255, 255, 255), highlight=color(255, 0, 0, 255),
id="txtBox"}
txtBox = TextInput(params)
local params = {pos=vec2(100,100), size=vec2(800,600), id="picCtl",
text="mainpic", onClicked=function(s) s.img = nil end}
zoomed = PictureControl(params)
local params = {pos=vec2(100,0), size=vec2(WIDTH-200,150), text="Search Results",
fontsize=24, highlight=color(255, 0, 0, 255),foreColor=color(0, 0, 0, 255),
backColor=color(137, 135, 103, 255),
id="container"
}
c = ContainerControl(params)
images = {}
Windowed = false
version = "1.0"
AppMode()
--consider integrating into the backup code.
saveProjectInfo( "Description", "Version " ..version .."\nExample Cider2 project. Hand coded.\nLast run on " .. os.date() .. "." )
saveProjectInfo( "Author", "Antonio Ciolino")
Backup("BingImageSearch", version)
end
function draw()
background(20, 20, 20, 255)
if (Windowed) then
bw:draw()
else
btnBing:draw()
btnNext:draw()
txtBox:draw()
btnMode:draw()
zoomed:draw()
c:draw()
end
if wait ~= nil then wait:draw() end
end
function touched(touch)
if (Windowed) then
bw:touched(touch)
else
btnBing:touched(touch)
btnNext:touched(touch)
txtBox:touched(touch)
zoomed:touched(touch)
btnMode:touched(touch)
c:touched(touch)
end
end
function AppMode()
if (Windowed) then
wh = WindowHandler() --when we want to go to windowed mode
bw = BingSearchWindow()
else
if wh then wh=nil end
end
end
function btnBing_Clicked()
images={}
if string.len(txtBox.text) == 0 then
AboutBox("Please enter some text to search.")
return
end
CreateWait()
bing:Search(txtBox.text, 10, ProcessSearch )
end
function btnNext_Clicked()
if next ~= nil then
images={}
bing:Search(nil, 10, ProcessSearch )
end
end
function btnMode_Clicked()
if Windowed ==true then Windowed=false else Windowed=true end
AppMode()
end
function httpError(error)
print("HTTP Error:"..error)
end
function CreateWait()
if wait == nil then
params = {pos = vec2(btnBing:midX()-15, btnBing:midY()-15),
size=vec2(30,30), foreColor=color(40,60,77,255), backColor=color(120,140,157),
}
wait = AppleHourGlass(params)
end
end
function ProcessSearch(data)
d = {}
d = data["d"]
next=d["__next"]
c:clearControls()
results = d["results"]
print(#results.." results.")
--pull images with http
for k,v in pairs(results) do
imgUrl = v["MediaUrl"]
http.request(imgUrl,
function(data,status,headers)
wait=nil --kll hourglass
if (status==200) then
local params = {
pos=vec2(140*(#c.controls), 40),
size=vec2(130,75),
id="imgctl"..#c.controls,
img=data,
text="picture", onClicked=function(s)
local i = s.img:copy()
if (Windowed) then
CreateImageWindow(i)
else
zoomed:LoadPicture(i)
end
end
}
local imgCtl = PictureControl(params)
imgCtl:LoadPicture(data)
c:addControl(imgCtl)
end
end)
end
end
--Both of these functios are copied from Cider2 main.
--We need a better way of handling this scenario!
function keyboard(key)
--Handles Keyboard Input Ending Correctly.
--This must be any program that handles a Text Input Box
if key == RETURN then
hideKeyboard()
lastKey = RETURN
else
--sound(SOUND_HIT, 38912)
--if isKeyboardShowing() then
lastKey = key
--end
end
end
function AboutBox(msg)
params = {id="About", immutable = true, backColor=color(195, 195, 195, 197),title="nil",
DesignMode = false,
fixed = true,
pos = vec2(0,0),
size = vec2(WIDTH, HEIGHT),
borderSize=10,
zOrder = 999 , --topmost topmost
toolbarButtons = {},
text="test dialog"
}
a = ModalDialog(params)
a:setText(msg)
if (Windowed) then
wh:addWindow(a)
end
end
--# BingSearchWindow
BingSearchWindow = class()
function BingSearchWindow:init()
wh = WindowHandler()
self.WindowInit(self)
end
function BingSearchWindow:draw()
-- This sets a dark background color
background(69, 69, 69, 255)
-- this is forced so that all calculations use the same coord sytem.
--VERY SLOPPY!!
ellipseMode(CORNER)
spriteMode(CORNER)
rectMode(CORNER)
textMode(CENTER)
--Cycles Through All Windows attached to the Window Handler
--and Draws Windows which has their isOpen variable set to true
wh:draw()
--AC: this takes over everything...
end
function BingSearchWindow:touched(touch)
wh:touched(touch)
end
-- startup for any initialization
function BingSearchWindow:WindowInit()
local imgWindow = Window( {title="Search",
id="winSearch",
pos=vec2(200,HEIGHT-450),
size=vec2(700, 450),
borderColor=color(57, 68, 131, 255),
borderSize=4 , fixed = false})
wh:addWindow(imgWindow)
imgWindow:addControl(btnBing)
imgWindow:addControl(btnNext)
imgWindow:addControl(txtBox)
imgWindow:addControl(btnMode)
--imgWindow:addControl(zoomed) --removed because its a window now
imgWindow:addControl(c)
btnBing.pos = btnBing.pos * .5
btnNext.pos = btnNext.pos * .5
btnMode.pos = btnMode.pos * .5
txtBox.pos = txtBox.pos * .52
txtBox.size.x = txtBox.size.x * .65
--zoomed will actually be a new window
c.pos.x = 0
c.size.x = c.parent.size.x
end
function CreateImageWindow(imageSrc)
local img = (imageSrc) ---readImage(imageSrc)
if img ~= nil then
id="win"..math.random(100000)
local sz = vec2(imageSrc.width, imageSrc.height)
local picWindow = Window({title=id,
pos=vec2(100+math.random(25)*25,100+ math.random(5) * 25),
size=sz/2+vec2(0,WB_SIZE), id=id,
borderColor=color(57, 68, 131, 255),
borderSize=4 , fixed = false, zOrder=50, DesignMode=false})
wh:addWindow(picWindow)
local params = {id="pic"..id, pos=vec2(0,0), size=sz/2,
text="PictureControl", fontsize=12, url=nil, img=imageSrc}
imgCtl = PictureControl(params)
imgCtl:LoadPicture(imageSrc)
picWindow:addControl(imgCtl)
local t = "Properties\nWidth: " .. imageSrc.width .. " Height: " ..imageSrc.height
local params = {id="lbl"..id, pos=vec2(50, 50), size=vec2(150,80),
text=t, zOrder=99,
wrapWidth=WIDTH/2, textAlign=CENTER, vertAlign=CA_MIDDLE,
backColor=color(255, 255, 255, 186),
canResize=true, fontsize=12}
label = LabelControl(params)
picWindow:addControl(label)
-- find the resizer
local rz = picWindow:getControlByName(picWindow.id.."_resizer")
rz.onMoving =function(x) Resizer.onMoving(x)
--get the resizers' parent, as that is the window....
ctl = x.parent:getControlByName("pic"..x.parent.id)
--resize the picture control found
ctl.size = x.parent.size - vec2(0,x.parent.titleBarSize)
end
end
end
--# BingImageSearch
--BingImageSearch features
--This code allows an application to search Bing Images. Bing does allow OAuth,
--but we re going to opt for the simple BASIC auth.
BingImageSearch = class()
assert(url_encode ~= nil, "url_encode is missing. Check the dependencies")
BingImageSearchUrl = "https://api.datamarket.azure.com/Bing/Search/v1/Image"
--BingImageSearchUrl = "https://api.datamarket.azure.com/Bing/Search/v1/Composite"
Images = {} --make a table to hold all images found
--We allow the developer to set these. This is the account Key that the API uses
-- generally these should be read from storage, but that's inflexible for my needs.
function BingImageSearch:init(key)
if key then
self.appKey = key
self.appSecret = nil --unused in the search api
--this is a cache of the querystring with all of the params. This should not ever
--be directly assigned. I did this for speed.
self.params = nil
--re-write the key immediately!
saveLocalData("BingImageSearch_account_key",self.appKey)
saveGlobalData("BingImageSearch_account_key",self.appKey) --so other lua apps can use it!
else
self.appKey = readLocalData("BingImageSearch_account_key")
end
assert(self.appKey ~= nil, "You must have a Bing account key to do searches.")
end
--Unused Utility: wer'e only clearing the in-memory params.
--consider a clearing of the localdata later.
function BingImageSearch:ClearMemoryParams()
self.params = nil
--clearLocalData()
end
--Helper function.
--Used to call various BingImageSearch endpoints.
--Will parse the resulting JSON into a LUA table.
function BingImageSearch:_GetJSONResult(url, callback, header)
local cb = callback or nil
http.request(url,
function(data,status,headers)
if (status==200) then
result = {}
result= JSON:decode(data)
if cb ~= nil then cb(result) end
else
print("status=".. status .. ":" .. data)
return nil
end
end,
httpError, header) --if there are headers, they are here [Content-Type], etc.
end
function BingImageSearch:Search(query, items, callback)
local url
if items==nil then items=10 end
local vlu = Base64.encode(self.appKey..":" ..self.appKey)
local headers = {["Authorization"] = "Basic " .. vlu }
local tbl = {["method"]="GET", ["data"]=searchparams, ["headers"]=headers}
if query==nil and next ~= nil then
url=next
--fix
--url=url:gsub("u0027", "%27")
else
query = url_encode(query)
--AC: URLEncoded. %27 is a ', %3a is a :.
local searchparams = "?Query=%27" .. query .."%27"
-- .. "&Adult=%27Moderate%27"
.. "&Adult=%27Strict%27"
.. "&ImageFilters=%27size%3Awidth%3A800%27"
.. "&$top="..items
.. "&$format=json"
-- .. "&Sources=%27jpg%27" -- for composite searches
url =BingImageSearchUrl .. searchparams
print(url)
end
BingImageSearch:_GetJSONResult(url, callback, tbl)
end
--end
--# Base64
-- Base64.lua
-- #!/usr/bin/env lua
-- working lua base64 codec (c) 2006-2008 by Alex Kloss
-- compatible with lua 5.1
-- http://www.it-rfc.de
-- licensed under the terms of the LGPL2
-- modified by Rui Viana for gitty client
Base64 = class()
-- bitshift functions (<<, >> equivalent)
-- shift left
function Base64.lsh(value,shift)
return (value*(2^shift)) % 256
end
-- shift right
function Base64.rsh(value,shift)
return math.floor(value/2^shift) % 256
end
-- return single bit (for OR)
function Base64.bit(x,b)
return (x % 2^b - x % 2^(b-1) > 0)
end
-- logic OR for number values
function Base64.lor(x,y)
result = 0
for p=1,8 do result = result + (((Base64.bit(x,p) or Base64.bit(y,p)) == true) and 2^(p-1) or 0) end
return result
end
-- encryption table
local base64chars = {[0]='A',[1]='B',[2]='C',[3]='D',[4]='E',[5]='F',[6]='G',[7]='H',[8]='I',[9]='J',[10]='K',[11]='L',[12]='M',[13]='N',[14]='O',[15]='P',[16]='Q',[17]='R',[18]='S',[19]='T',[20]='U',[21]='V',[22]='W',[23]='X',[24]='Y',[25]='Z',[26]='a',[27]='b',[28]='c',[29]='d',[30]='e',[31]='f',[32]='g',[33]='h',[34]='i',[35]='j',[36]='k',[37]='l',[38]='m',[39]='n',[40]='o',[41]='p',[42]='q',[43]='r',[44]='s',[45]='t',[46]='u',[47]='v',[48]='w',[49]='x',[50]='y',[51]='z',[52]='0',[53]='1',[54]='2',[55]='3',[56]='4',[57]='5',[58]='6',[59]='7',[60]='8',[61]='9',[62]='-',[63]='_'}
-- function encode
-- encodes input string to base64.
function Base64.encode(data)
local bytes = {}
local result = ""
for spos=0,string.len(data)-1,3 do
for byte=1,3 do bytes[byte] = string.byte(string.sub(data,(spos+byte))) or 0 end
result = string.format('%s%s%s%s%s',
result,
base64chars[Base64.rsh(bytes[1],2)],
base64chars[Base64.lor(
Base64.lsh((bytes[1] % 4),4),
Base64.rsh(bytes[2],4))] or "=",
((#data-spos) > 1) and
base64chars[Base64.lor(Base64.lsh(bytes[2] % 16,2),
Base64.rsh(bytes[3],6))] or "=",
((#data-spos) > 2) and base64chars[(bytes[3] % 64)] or "=")
end
return result
end
-- decryption table
local base64bytes = {['A']=0,['B']=1,['C']=2,['D']=3,['E']=4,['F']=5,['G']=6,['H']=7,['I']=8,['J']=9,['K']=10,['L']=11,['M']=12,['N']=13,['O']=14,['P']=15,['Q']=16,['R']=17,['S']=18,['T']=19,['U']=20,['V']=21,['W']=22,['X']=23,['Y']=24,['Z']=25,['a']=26,['b']=27,['c']=28,['d']=29,['e']=30,['f']=31,['g']=32,['h']=33,['i']=34,['j']=35,['k']=36,['l']=37,['m']=38,['n']=39,['o']=40,['p']=41,['q']=42,['r']=43,['s']=44,['t']=45,['u']=46,['v']=47,['w']=48,['x']=49,['y']=50,['z']=51,['0']=52,['1']=53,['2']=54,['3']=55,['4']=56,['5']=57,['6']=58,['7']=59,['8']=60,['9']=61,['-']=62,['_']=63,['=']=nil}
-- function decode
-- decode base64 input to string
function Base64.decode(data)
local chars = {}
local result=""
for dpos=0,string.len(data)-1,4 do
for char=1,4 do chars[char] = base64bytes[(string.sub(data,(dpos+char),(dpos+char)) or "=")] end
result = string.format('%s%s%s%s',
result,
string.char(Base64.lor(Base64.lsh(chars[1],2), Base64.rsh(chars[2],4))),
(chars[3] ~= nil) and
string.char(Base64.lor(Base64.lsh(chars[2],4), Base64.rsh(chars[3],2))) or "",
(chars[4] ~= nil) and
string.char(Base64.lor(Base64.lsh(chars[3],6) % 192, (chars[4]))) or "")
end
return result
end
--# Control
--Control
----
--base control class. override this to do stuff.
-- most importantly, this calculates the screen position for the override class.
Control = class()
--Control Actions
WC_NORMAL = 0
WC_MOVING = 2
WC_RESIZE = 5
SYSTEM_FONTLIST =
[[
American Typerwriter;Arial;ArialMT;Baskerville;Copperplate;Courier;Futura;Georgia;Helvetica Neue;Inconsolata;Marion;Marker Felt;Optima;Palatino;Papyrus;Snell Roundhand;Times New Roman MT;Verdana;Zapfino
]]
SYSTEM_FONTSIZE = "8;10;12;14;16;18;24;28;32;36;48;72"
SYSTEM_DEFAULTFONT="Helvetica Neue" -- attempt to make consistent
function Control:init(params)
self.id = params.id
-- this is REQUIRED in the code so we can persist and load the control types. important!
self.controlType = params.controlType or "Control"
self.controlName = self.controlType
-- do not remove!!
self.fontsize = params.fontsize or 12
self.pos = params.pos --control's relative position
self.size = params.size --size of control
self.screenpos = vec2(0,0) --variable used to plot REAL screen coords.
self.lastPosition = vec2(0,0) --variable to track "last" position of this control
--Themed properties.
self.foreColor =params.foreColor or color(80, 82, 206, 255)
self.backColor =params.backColor or color(255, 255, 255, 255)
self.borderColor = params.borderColor or color(145, 148, 169, 255)
self.highlight = params.highlight or color(158, 167, 200, 128)
self.borderWidth = params.borderWidth or 1
self.fontsize = params.fontsize or 16
self.fontname = params.fontname or SYSTEM_DEFAULTFONT
self.textAlign = params.textAlign or LEFT
self.vertAlign = params.vertAlign or CA_TOP
self.wrapWidth = params.wrapWidth or 500
self.text = params.text or ""
self.isActive = true --is control enabled?
self.zOrder = params.zOrder or 0 --alow controls to have zOrder. Menus really need this.
--helpers. not important to store, but they do get carried along in json.
self.left = 0
self.right = 0
self.top = 0
self.bottom = 0
--used in various methods for dragging. PictureControl currently.
self.lastTouched = vec2()
-- immutable controls cannot be moved nor do they get properties (v1) HACK!
self.immutable = params.immutable or false
self.fixed= params.fixed or false
self.canResize = params.canResize or false -- seems redundant.used for controlresizer
--if events are sent in params use them else use the event stored in the control type.
self.onClicked = params.onClicked or self.onClicked
self.onSubmit = params.onSubmit or self.onSubmit
self.onMoving = params.onMoving or self.onMoving
self.onSelected = params.onSelected or self.onSelected
self.onDesignMode = params.onDesignMode or self.onDesignMode
self.onPropertyChanged = params.onPropertyChanged or self.onPropertyChanged
self.onLoaded = params.onLoaded or self.onLoaded
self.onGotFocus = params.gotFocus or self.onGotFocus
self.onLostFocus = params.gotFocus or self.onLostFocus
self.parent = params.parent or nil --if we were given a parent...
-- if this control is in a design palette use alternate rendering
self.inPalette = params.inPalette or false
--added a property bag for data to carry around if needed
self.assets = {} --try to keep this to strings and numbers only!!!
end
function Control:draw()
--FORCE POS to ALWAYS BE A VEC2
--this can become a vec4 for a few microseconds when a control is created or loaded
--from serialization...and when we copy controls from window to window, we go through a
--serialization step! This way, we can assure that a pos is a vec2, and do vec2 math.
self.pos = vec2(self.pos.x, self.pos.y)
self.screenpos = vec2(self.screenpos.x, self.screenpos.y)
--set up a few default things. note that these wil get overriden in the calling
--control, but if they are NOT we want to at least stick with the theme.
fontSize(self.fontsize)
font(self.fontname)
strokeWidth(self.borderWidth)
stroke(self.foreColor)
fill(self.foreColor)
textWrapWidth(self.wrapWidth)
if self.parent ~= nil then
--set the screen position variables from the "pos" variables ***all the time***
self.screenpos.x = self.parent.pos.x+self.pos.x
self.screenpos.y = self.parent.pos.y+self.pos.y
--if we are in a container we need to add its offset too
if self.parent.controlType == "ContainerControl" then
self.screenpos.x = self.pos.x
self.screenpos.y = self.pos.y
end
--ac old school
if self.parent.id == "winPalette" then self:drawPalette() end
if self.parent.DesignMode == true then
self:drawPalette()
end
else
--no parent=abs coords
self.screenpos.x = self.pos.x
self.screenpos.y = self.pos.y
end
-- stub for old Cider Controls
self.left = self.screenpos.x
self.right = self.screenpos.x + self.size.x
--inverted coordsystem
--AC: the sysetm becomes unusable if we reverse these.
self.bottom = (self.screenpos.y+self.size.y)
self.top = self.screenpos.y
end
-- draws the palette representation of the control.
function Control:drawPalette()
pushStyle()
fill(255, 255, 255, 32)
rect(self.screenpos.x, self.screenpos.y, self.size.x, self.size.y)
fill(0,0,0,32)
textMode(CENTER)
fontSize(8)
text(self.controlType, self.screenpos.x + self.size.x/2,
self.screenpos.y+ self.size.y/2)
popStyle()
end
function Control:touched(touch)
--set me as the active control. Note that a window would get set here
--if we allowed Control:Touched(self, touch) in the window class. DON'T DO IT.
--This code really belongs in onSelected()
if type(touch) == 'function' then
assert(1==0,"bad touch")
end
--ALLOWING LEGACY CODE TO STILL FUNCTION - IF WE do not have a parent, then we
--are going to just pass touch through to the caller, and let it handle the message.
--that makes the caller responsible for touch detection instead of the window handler,
--which already has done this work.
if self.parent then
--if self.controlType ~= 'Window' and touch.state==MOVING then
if wh ~= nil then --gracefully allow non Cider2 execution
wh:setActiveControl (self)
end
if self.parent.DesignMode == true then
if touch.tapCount == 2 and self.immutable == false then
self.onDesignMode(self)
end
end
if self.controlType ~= 'Window' and self.canResize == true then self:AddResizer() end
end
end
-----------------------------------------------
-----------------------------------------------
function Control:setParent(window)
self.parent = window
end
function Control:setID(id)
self.id = id
end
-----------------------------------------------
-- i can still touch things that are outside of the clipping window...hmmm.
-- remember we are using a lower left coordinate system. toolbar is on top...
--vec() are raw screen coords.
-----------------------------------------------
function Control:ptIn(vec)
if vec.x >= self.left and vec.x <= self.right then
if vec.y >= self.top and vec.y <= self.bottom then
return true
end
end
return false
end
function Control:midX()
return self.screenpos.x + (self.size.x / 2)
end
function Control:midY()
return self.screenpos.y + (self.size.y / 2)
end
function Control:width()
return self.size.x
end
function Control:height()
return self.size.y
end
--ac fix later to allow the window and the stanrd controls to use this same code!
function Control:AddResizer()
if self.canResize == true then
-- if self.controlType == "TextButton" then
if self.parent:getControlByName(self.id .. "_ctlresizer") == nil then
local resizerparams = {id=self.id .. "_ctlresizer", pos=vec2(0, 0),
size=vec2(32,32),backColor=color(0,0,0,0), targetControl=self.id}
local thisControl = ControlResizer(resizerparams)
self.parent:addControl(thisControl) ---add to window
end
end
end
-- inverted coords
function Control:inset(dx, dy)
local left = self.screenpos.x + dx
local right = self.screenpos.x+self.size.x - dx
local bottom = self.screenpos.y- self.size.y + dy
local top = self.screenpos.y - dy
return left,right,bottom,top
end
function Control:offset(dx, dy)
local left = self.screenpos.x + dx
local right = self.screenpos.x+self.size.x + dx
local bottom = self.screenpos.y- self.size.y + dy
local top = self.screenpos.y + dy
return left,right,bottom,top
end
--glosses the frame's base color
function Control:gloss(baseclr, step)
local i, t, r, g, b, y
pushStyle()
if baseclr == nil then baseclr = color(194, 194, 194, 255) end
fill(baseclr)
rectMode(CORNERS)
rect(self.left, self.bottom, self.right, self.top)
r = baseclr.r
g = baseclr.g
b = baseclr.b
for i = 1 , self:height() / 2 do
r = r - step
g = g - step
b = b - step
stroke(r, g, b, 255)
y = (self.bottom + self.top) / 2
line(self.left, y + i, self.right, y + i)
line(self.left, y - i, self.right, y - i)
end
popStyle()
end
function Control:shade(base, step)
pushStyle()
for y = self.left, self.right do
i = self.bottom - (y - self.left)
stroke(base - i * step, base - i * step, base - i * step, 255)
line(self.left, y, self.right, y)
end
popStyle()
end
--HELPERS - make it easy to recall how to get certain info.
--gets the position in the window vs. the screen
function Control:getControlPos()
return vec2(self.pos.x, self.pos.y)
end
--get the position of the screen vs. the window
function Control:getScreenPos()
return vec2(self.screenpos.x, self.screenpos.y)
end
function Control:roundRect(r)
pushStyle()
insetPos = vec2(self.left + r,self.top + r)
insetSize = vec2(self:width() - 2 * r,self:height() - 2 * r)
rectMode(CORNER)
rect(insetPos.x, insetPos.y, insetSize.x, insetSize.y)
if r > 0 then
smooth()
lineCapMode(ROUND)
strokeWidth(r * 2)
line(insetPos.x, insetPos.y,
insetPos.x + insetSize.x, insetPos.y)
line(insetPos.x, insetPos.y,
insetPos.x, insetPos.y + insetSize.y)
line(insetPos.x, insetPos.y + insetSize.y,
insetPos.x + insetSize.x, insetPos.y + insetSize.y)
line(insetPos.x + insetSize.x, insetPos.y,
insetPos.x + insetSize.x, insetPos.y + insetSize.y)
end
popStyle()
end
--------------------------------------------
---forces a clip to stay constrained inside the window.
--------------------------------------------
function Control:clip(x,y,w,h)
if self.parent then
if self.parent.controlType ~= "ContainerControl" then --hack!!!
if (x~=nil) then
x = clamp(x, self.parent.pos.x, self.parent.pos.x+self.parent.size.x)
y = clamp(y, self.parent.pos.y, self.parent.pos.y+self.parent.size.y)
w = clamp(w, 0, self.parent.size.x+self.parent.borderWidth/2-self.pos.x)
h = clamp(h, 0, self.parent.size.y-self.pos.y)
clip(x,y,w,h)
else
self:clip(self.parent.pos.x,self.parent.pos.y,
self.parent.size.x+self.parent.borderWidth/2,self.parent.size.y)
end
end
else --reset
if x ~= nil then
clip(x,y,w,h)
else
clip()
end
end
end
--------------------------------------------
-- property persistence
--------------------------------------------
function Control:Save()
--have to strip the parent object our or we stack overflow.
local myParent = self.parent
self.parent = nil
self.resizer=nil
persist = encode(self)
self.parent = myParent
return persist
end
--Load a specific JSON control
function Control:Load(value)
valuetable = {}
valuetable = decode(value)
self:Create(valuetable)
end
-- Generic control creator. if a class is in here, we make a blank version of
-- the control. it is the callers responsibilty to populate, typically through
-- json save()/load()
--AC: Why do I have this? Load() is pretty much the EXACT SAME code.
function Control:Create(t)
--t is a table of json controls
-- default required params for any control.
--ac:we have dupe code with load()
--create the control specified
params={}
for k,v in pairs(t) do
-- any key that starts with a "(" was a userdata. HACK!!
params[k]=v
--log(k) == shows the parameter from the json string.
if type(v)=='string' then
if string.startsWith(v, "(") then
local cc = 0
v:gsub(",", function(c) cc = cc + 1 end)
if cc == 1 then objType = "vec2" end
if cc == 2 then objType = "vec3" end
if cc == 3 then objType = "color" end
--AC: Came across an issue with this - po is a vec2() and I was trying to do
--math based on it being a vec2. But from above, pos ill shortly be a vec4!
--fixed in in the Control class.
--execute the built string.
build = "parsedvalue=" .. objType .. v
loadstring(build)()
--store the value of parsedvalue in the destination control bucket.
params[k]=parsedvalue
end
end
end
log("Create:" .. params["controlType"])
if _G[params.id] then params.id = params.id.."_copy" end
loadstring("ctl=" .. params["controlType"] .. "(params)")()
--if we made any custom events, wire for them. if they do not exist, then the
--controls' base event is called.
ctl.onClicked = _G[ctl.id.."_onClicked"]
return ctl
end
-- duplicate this control and return it
function Control:Copy()
--copy the control!
s =self:Save()
assert(s~=nil,"s==nil")
newCtl = Control:Create(decode(s) )
newCtl:Load(s)
return newCtl
end
------------------------------------------------------------
-- default events - stubs - override for unique behaviors --
------------------------------------------------------------
function Control:onSelected()
end
function Control:onLoaded()
--
end
function Control:onClicked()
sound(DATA, "ZgNAOhcUQEBAQEBAAAAAAGRXtjzZoJc+LABAf0BTQEBAQEBA")
--log(self.id .. "clicked")
end
function Control:onBegan()
sound(DATA, "ZgNAbUcUQEBAQEBAAAAAAGRXtjzZoJc+QABAf0BTQEBAQEBA")
end
function Control:onMoving()
--noise here is annoying...:)
--print("OnMoving")
end
function Control:onEnded()
sound(DATA, "ZgNAOhcUQEBAQEBAAAAAAGRXtjzZoJc+LABAf0BTQEBAQEBA")
end
function Control:onGotFocus()
self.active = true --textinput control uses this
if DEBUG then print("onGotFocus") end
end
function Control:onLostFocus()
self.active = false --textinput control uses this
if DEBUG then print("onLostFocus") end
end
function Control:onPropertyChanged()
--override to set behaviors after the property panel
end
function Control:onDesignMode()
sound(SOUND_HIT, 17484)
local winPropertyBuilder=wh:GetPropertyBuilder()
winPropertyBuilder:Show(self)
end
------------------------------------------------------------
-- the property builder calls the control for the array of properties
------------------------------------------------------------
function Control:getProperties()
--find all windows
allWindows = "nil;"
for i,v in ipairs(wh.list) do
allWindows = allWindows .. v.id ..";"
-- for _,ctl in ipairs(v.controls) do
-- if ctl.controlType == "ContainerControl" then
-- allWindows = allWindows .. ctl.id ..";"
-- end
-- end
end
local props = {}
table.insert(props, {"id", "TextInput"})
table.insert(props, {"backColor", "TextButton"})
table.insert(props, {"foreColor", "TextButton"})
table.insert(props, {"borderColor", "TextButton"})
table.insert(props, {"highlight", "TextButton"})
table.insert(props, {"fontname", "DropList", SYSTEM_FONTLIST})
table.insert(props, {"fontsize", "DropList", SYSTEM_FONTSIZE})
table.insert(props, {"parent", "DropList", allWindows})
table.insert(props, {"borderWidth", "TextInput"})
return props
end
--------------------------------------------
-- move later to a global class area.
--------------------------------------------
function string.startsWith(str,start)
if str~=nil and string.len(str) > 0 then
return string.sub(str,1,string.len(start))==start
end
end
function string.endsWith(String,End)
return End=='' or string.sub(String,-string.len(End))==End
end
function clamp(x, min, max)
return math.max(min, math.min(max, x))
end
function log(str)
hdr = os.date("%x %X")
hdr=""
print (hdr ..": "..str)
end
--currently unused...
local typeTable = {
[getmetatable(vec2()).__index ] = "vec2",
[getmetatable(vec3()).__index ] = "vec3",
[getmetatable(vec4()).__index ] = "vec4",
[getmetatable(color()).__index ] = "color",
[getmetatable(image(1,1)).__index ] = "image",
[getmetatable(matrix()).__index] = "matrix",
[getmetatable(mesh()).__index ] = "mesh"
}
function convertType(x)
local txt = type(x)
if txt == "userdata" then
return typeTable[getmetatable(x).__index]
elseif txt == "table" then
return "{"..self:type(x[1]).."}"
end
return txt
end
--# AppleHourGlass
-- AppleHourGlass.lua
-- v1.0 Lifted from ruilov's GITHUB code, converted to Cider2.
AppleHourGlass = class(Control)
function AppleHourGlass:init(params)
self.controlType = "AppleHourGlass"
Control.init(self,params)
self:makeLines()
self.pointing = 0
end
function AppleHourGlass:makeLines()
self.lines = {}
for ang = 0,360,30 do
local a = math.rad(ang)
local x1 = self.size.x * math.cos(a) * .25 + self.pos.x + self.size.x/2
local y1 = self.size.y * math.sin(a) * .25 + self.pos.y + self.size.y/2
local x2 = self.size.x * math.cos(a) * .5 + self.pos.x + self.size.x/2
local y2 = self.size.y * math.sin(a) * .5 + self.pos.y + self.size.y/2
table.insert(self.lines,{x1=x1,y1=y1,x2=x2,y2=y2,ang=ang})
end
end
function AppleHourGlass:draw()
pushStyle()
strokeWidth(3)
lineCapMode(PROJECT)
for _,elem in ipairs(self.lines) do
local dang = math.abs(elem.ang - self.pointing)
if dang < 45 or dang > 315 then
stroke(self.foreColor)
else
stroke(self.highlight)
end
line(elem.x1,elem.y1,elem.x2,elem.y2)
end
self.pointing = (self.pointing - 12)%360
popStyle()
end
--# ContainerControl
ContainerControl = class(Control)
function ContainerControl:init(params)
params.controlType="ContainerControl"
Control.init(self,params)
self.controls={} --pointers to other controls...go by name or by control?
self.scrollable = true --can we scroll this container?
--params.scrollable or
self.offset = vec2(0,0) --offset for rendering
self.lastTouch = vec2(0,0)
self.ext = vec2(0,0) --extents of the container
delta = vec2(0,0) --AC: fix this global.:(
self.activeControl = nil --what control was touched, if any?
end
--AC: HACK!!! Controls assume that the parent id the offset but as thisis a control
--subcontrols need this to axt as "0,0" to porperly sclculate screen positions.
--fake it by moving and resetting after the context render.
function ContainerControl:draw()
if self.parent then self.DesignMode = self.parent.DesignMode end
self.zOrder = 0 --low zOrder
Control.draw(self)
--calculate the size of the image
for _,v in ipairs(self.controls) do
v.DesignMode = self.DesignMode
if v.pos.x + v.size.x > self.ext.x then self.ext.x = v.pos.x + v.size.x end
if v.pos.y + v.size.y > self.ext.y then self.ext.y = v.pos.y + v.size.y end
end
local ctx = image(self.ext.x, self.ext.y)
pushStyle()
--BUG WITH SETCONTEXT - work around it -- seems that sprite or mesh require a non-clipped
--area to work "well".
clip() --HARD CLIP so that setcontext will operate correctly
setContext(ctx)
fill(self.backColor)
for k,v in pairs(self.controls) do
v:draw()
if self.activeControl == v then
--Control.draw(v)
pushStyle()
fill(self.highlight)
--this is not quite right...
-- rect(v.left+self.screenpos.x, v.top+self.screenpos.y, v.size.x, v.size.y)
popStyle()
end
end
popStyle()
setContext()
--draw the controls - container clip to viewport
self:clip(self.screenpos.x, self.screenpos.y, self.size.x, self.size.y)
stroke(self.borderColor)
fill(self.backColor)
strokeWidth(self.borderWidth)
rect(self.screenpos.x, self.screenpos.y, self.size.x, self.size.y)
spriteMode(CORNER)--not sur why/how this got changed...IMPORTANT!!!
sprite(ctx, self.screenpos.x-self.offset.x, self.screenpos.y-self.offset.y)
--if we have any scrollbars, render them
--AC a bit hinky right now...
if CurrentTouch.state == MOVING then
pushStyle()
--AC: should move this code - inefficient
strokeWidth(7)
lineCapMode(ROUND)
fill(self.highlight)
local padding = 6 --px
local baroffset = 5 --px
if (self.ext.x > self.size.x) then
if self.parent then fill(self.parent.backColor) else fill (self.backColor) end
--self.ext = full width, largest # / 1000
--self.offset is distance already scrolled / 150
--self.size is window size / 200
local ratio = self.ext.x /self.size.x --1 size px per 5 ext px
local scrollbar = self.offset.x/ratio --150 / 5 = 30 px
local barsize = self.size.x / ratio --trying to get rounded shown
scrollbar = scrollbar - baroffset
barsize = barsize - padding
line(self.screenpos.x+scrollbar+padding,
self.screenpos.y+baroffset,
self.screenpos.x+scrollbar + barsize+padding,
self.screenpos.y+baroffset)
end
if (self.ext.y > self.size.y) then
if self.parent then fill(self.parent.backColor) else fill (self.backColor) end
local ratio = self.ext.y /self.size.y --1 size px per 5 ext px
local scrollbar = self.offset.y/ratio --150 / 5 = 30 px
local barsize = self.size.y / ratio
scrollbar = scrollbar - baroffset
barsize = barsize - padding
line(self.screenpos.x+self.size.x-(baroffset*2),
self.screenpos.y+scrollbar+padding,
self.screenpos.x+self.size.x-(baroffset*2),
self.screenpos.y+scrollbar+barsize+padding)
end
popStyle()
end
pushStyle()
--AC: make a rect for this to that it "overwrites" the border
rectMode(CENTER)
local sz = textSize(self.text) + 6 --6==padding
--caption uses SIZE not extents.
self:clip(self.screenpos.x, self.screenpos.y, self.size.x, self.size.y+50)
fill(self.backColor)
rect(self:midX(), self.screenpos.y + self.size.y, sz, self.fontsize+1)
fill(self.foreColor)
text(self.text, self:midX(), self.screenpos.y + self.size.y)
popStyle()
--reset to windows clip
self:clip()
ctx=nil
end
function ContainerControl:touched(touch)
Control.touched(self, touch)
--support legacy / non windowed feature
local msg = false
if self.parent then
-- if self.parent.DesignMode then return end
msg = true --WE ASSUME windowing system took care of touch message checking.
else
msg = self:ptIn(vec2(touch.x, touch.y) )
end
if msg then
for k,v in ipairs(self.controls) do
--check to see if we touched this - this might allow us to move stuff around
if v:ptIn(vec2(touch.x-(self.screenpos.x-self.offset.x),
touch.y-(self.screenpos.y-self.offset.y))) then
v:touched(touch)
self.activeControl = v --store which control was active. Allow only ONE
--return --no overlap
end
end
--Decided to only allow scrolling during run and NOT during Design.
--working too cumbersome for design mode.
if touch.state == MOVING and self.scrollable == true and self.DesignMode ~= true then
self:onScrolling(vec2(touch.x, touch.y))
return
end
--AC: is this code EVER getting hit yet? Creaates ne controls from dragdrop
if touch.state == MOVING and activeControl ~= nil then
if self.DesignMode then
if activeControl.id ~= self.id then
--copy the control!
local oldWin = self
local newCtl = activeControl:Copy()
newCtl.fixed = false --allow new controls to move around
newCtl.inPalette = false -- no longer in palette
newCtl.id = newCtl.id .. tostring(self.newID)
self.newID = self.newID + 1
newCtl.parent=self
self:addControl(newCtl)
--if it came from somewhere other than the
--control palette, remove the origianl.
if activeControl.inPalette == false then
--move from win1 to win2
oldWin:removeControl(activeControl)
wh:Sort(oldWin)
end
newCtl.pos.x = touch.x - self.pos.x
newCtl.pos.y = touch.y - self.pos.y
end
end
end
end
if touch.state == ENDED then
self.lastTouch = vec2(0,0)
self.activeControl = nil
end
end
------------------------------------------------
function ContainerControl:drawPalette()
local size =vec2(50,50)
pushStyle()
fill(255, 255, 255, 32)
rect(self.screenpos.x, self.screenpos.y,size.x,size.y)
fill(0,0,0,32)
textMode(CENTER)
fontSize(8)
text(self.controlType, self.screenpos.x + self.size.x/2,
self.screenpos.y+ self.size.y/2)
popStyle()
end
function ContainerControl:addControl(control)
local c=self:getControlByName(control.id)
assert(c==nil, "control id "..control.id.." is not unique." )
control:setParent(self) -- AC: control attached needs this in its class
table.insert(self.controls,control)
if wh ~= nil then
wh:Sort(self.controls) --to allow zOrder to work.
end
end
function ContainerControl:removeControl(control)
for k,v in pairs(self.controls) do
if control.id == v.id then
table.remove(self.controls, k)
end
end
if wh ~= nil then
wh:Sort(self.controls) --to allow zOrder to work.
end
end
function ContainerControl:clearControls()
for k,v in pairs(self.controls) do
self.controls={}
end
print (#c.controls .. " left.")
end
function ContainerControl:getControlByName(name)
for k,v in pairs(self.controls) do
if v.id == name then
return v
end
end
end
function ContainerControl:SnapToGrid(ctl)
--self.parent.SnapToGrid(ctl)
end
function ContainerControl:onScrolling(t)
if self.lastTouch ~= vec2(0,0) then
delta.x = t.x - self.lastTouch.x
delta.y = t.y - self.lastTouch.y
self.offset.x = self.offset.x - delta.x
self.offset.y = self.offset.y - delta.y
self.offset.x= clamp(self.offset.x, 0, self.ext.x - self.size.x)
self.offset.y = clamp(self.offset.y, 0, self.ext.y - self.size.y)
end
self.lastTouch.x = t.x
self.lastTouch.y = t.y
end
function ContainerControl:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
table.insert(props, {"borderWidth", "TextInput"})
--table.insert(props, {"scrollable", "Switch"})
return props
end
--# MiscUtils
PublicDropBoxUrl="http://dl.dropbox.com/u/24774303/" --AC's public dropbox url
--helper function to easily create a redirector
function Redirect()
print.redirect.font("Inconsolata", 17)
pane1 = print.redirect.addPane(WIDTH/2, 0, WIDTH/2, HEIGHT, "Output")
print.redirect.currentPane(pane1)
print.redirect.on()
end
-- used function.
function replace_char(pos, str, r)
if (r==0) then r="\0" end
return str:sub(1, pos-1) .. r .. str:sub(pos+1)
end
--Codea doesn't have this!
function math.sgn(x)
if x == 0 then
return 0
elseif x < 0 then
return -1
elseif x > 0 then
return 1
else
return x -- x is NaN
end
end
function sign(x)
return math.sgn(x)
end
-----------------------------------------------------------------------------
--
-----------------------------------------------------------------------------
function Utf(aString)
local str=""
for b in string.gfind(aString, ".") do
c = string.byte(b)
if c~=0 then
if c <=127 then
str=str .. b
end
end
end
return str
end
function clamp(x, min, max)
return math.max(min, math.min(max, x))
end
--gets called in a lot of places.
function log(str)
print (str)
end
--[[
Decode an URL-encoded string
(Note that you should only decode a URL string after splitting it; this allows you to correctly process quoted "?" characters in the query string or base part, for instance.)
]]
function url_decode(str)
str = string.gsub (str, "+", " ")
str = string.gsub (str, "%%(%x%x)",
function(h) return string.char(tonumber(h,16)) end)
str = string.gsub (str, "\r\n", "\n")
return str
end
--[[
URL-encode a string
]]
function url_encode(str)
if (str) then
str = string.gsub (str, "\n", "\r\n")
str = string.gsub (str, "([^%w ])",
function (c) return string.format ("%%%02X", string.byte(c)) end)
str = string.gsub (str, " ", "+")
end
return str
end
--Text Wrapping
--[[Wrap a string at a given margin
This is intended for strings without newlines in them (i.e. after reflowing the text and breaking it into paragraphs.) ]]
-- Treat this as a private function, the public function would be reflow()
function wrap(str, limit, indent, indent1)
indent = indent or ""
indent1 = indent1 or indent
limit = limit or 72
local here = 1-#indent1
return indent1..str:gsub("(%s+)()(%S+)()",
function(sp, st, word, fi)
if fi-here > limit then
here = st - #indent
return "\n"..indent..word
end
end)
end
--[[Reflowing text into paragraphs
This builds on wrap to do a quick-and-dirty reflow: paragraphs are defined as lines starting with a space, or having a blank line between them: ]]
--Call this, this will call wrap for each line it finds
function reflow(str, limit, indent, indent1)
return (str:gsub("%s*\n%s+", "\n")
:gsub("%s%s+", " ")
:gsub("[^\n]+",
function(line)
return wrap(line, limit, indent, indent1)
end))
end
--# Window
--Window
----
--This is a window, subclassing Control.
--The WindowManager stores instances of these.
Window = class(Control)
--Window States.
WS_NORMAL = 1
WS_MINIMIZED = 2
WS_MAXIMIZED = 3
WS_HIDDEN = -1
--Window (title) bar size
WB_SIZE=40
WB_CLOSE = 1
WB_MINMAX = 2
--Window Actions
WA_NORMAL = 0
WA_MOVING = 2
WA_RESIZE = 5
--TODO Window Properties. These get tricky because we not' have a bit library.
WP_TOPMOST = 64
--pass a params table to the window. Set up defaults also.
--to test: can a window contain a window that references ts parent or itself?
-- might cause stack overflow...protect aainst that?
function Window:init(params)
--required!
params.controlType="Window"
Control.init(self, params)
--AC: Is this true? Couldn't we auto-generate an ID?
assert(params.id, "Windows must have ID's assigned to store")
self.id = params.id
--self.controlType = "Window" --so that tables can still know I'm a window.
--self.controlName = "Window" --old code - need to go though and replace these
--these two will make window extents
self.pos = params.pos or vec2(100,100)
self.size = params.size or vec2(200,200)
--other properties
self.borderSize = params.borderSize or 3
self.title = params.title or ""
self.titleColor = params.titleColor or color(255, 255, 255, 255)
self.titleBarColor = params.titleBarColor or color(101, 110, 222, 255)
self.titleBarSize = WB_SIZE --AC: This is THEMED/GLOBAL
--HACK!!! Buttons used this way seem to auto-order themselves alpha. Order buttons alpha
-- todo bitwise seperate flags
self.toolbarButtons = params.toolbarButtons or { MINMAX=true, WINCLOSE=true}
-- Active Window Values
self.windowState = params.windowState or WS_NORMAL --window is not minimized?
self.zOrder=params.zOrder or 0 --HACK! Anything greater than the WP_TOPMOST is...
self.GridSize = params.GridSize or 20 --must be > 5 for snap to occur.
self.fixed = params.fixed or false
--variables
self.lastTouchBegan = nil
--all controls that this window manages / renders / sends messages
self.controls = {}
self.titlebarcontrols = {}
-- TODO allow window to tell wh what control is active for the window...
self.activeControl = nil
--as we cretae new controls, increment the id.
self.newID = 0
--set this to design a window else the window and controls are fixed.
self.DesignMode = params.DesignMode or false
--scrollable
self.scrollsize = nil -- if set to anything, our window has scrollbars
--add a resizer to window (non ctl based..why?)
if self.fixed ~= true then
local resizerparams = {id=self.id .. "_resizer", pos=vec2(self.size.x-32, 0),
size=vec2(32,32),backColor=color(0,0,0,0), parent=self}
local resizer = Resizer(resizerparams)
self:addControl(resizer)
end
self:SetupToolbar()
end
function Window:draw()
--AC: Should we override / remove this?
--Control.draw(self)
if self.windowState == WS_HIDDEN then return end
--set up clipping for window
clip(self.pos.x,self.pos.y,
self.size.x+self.borderSize/2,self.size.y)
--Draw Main Window
pushStyle()
strokeWidth(self.borderSize)
stroke(self.borderColor)
font(self.fontname)
fontSize(self.fontsize)
fill(self.backColor)
--if we are NOT minimized, show the window extents.
local offset = vec2(0,0)
if self.windowState == WS_NORMAL then
if self.scrollsize ~= nil then
--offset = vec2(horiz.val, vert.val) --self.scrollsize
end
rect(self.pos.x+offset.x,self.pos.y+offset.y,
self.size.x+self.borderSize/2,self.size.y+self.borderSize/2)
--do this here,before title bar is rendered.
if self.DesignMode then self:DrawGridLines() end
-- Draw all controls
for i,v in ipairs(self.controls) do
--set up clipping for window
clip(self.pos.x,self.pos.y,
self.size.x+self.borderSize/2,self.size.y)
v:draw()
clip()
end
end
--if we have a titlebar draw it, with it's controls.
if self.title ~= nil then
self:DrawTitleBar()
for i,v in ipairs(self.titlebarcontrols) do
v:draw()
end
end
-- If we have an active control, outline it visually.
if self.DesignMode then
ctl = wh:getActiveControl()
if ctl ~= nil then
stroke(255, 0, 10, 255)
strokeWidth(5)
fill(127, 127, 127, 16)
--if we are in a cotainer, outline with the offset.
local offset = vec2(0,0)
if ctl.parent.controlType == "ContainerControl" then
offset = ctl.parent.screenpos
end
rect(ctl.screenpos.x+offset.x,ctl.screenpos.y+offset.y,ctl.size.x,ctl.size.y)
--to remove, it think...
--rect(ctl.left+self.screenpos.x,ctl.top+self.screenpos.y,
-- ctl.size.x, ctl.size.y)
end
end
popStyle()
clip()
end
--Used to detemine title bar drawing.
function Window:GetTitleBarExtents()
local titleBarPosX = self.pos.x
local titleBarPosY = (self.pos.y+self.size.y-self.titleBarSize)
local titleBarSizeX = self.size.x
local titleBarSizeY = self.titleBarSize
if self.title~= nil then
return vec2(titleBarPosX, titleBarPosY), vec2(titleBarSizeX, titleBarSizeY)
else
return vec2(titleBarPosX, titleBarPosY), vec2(titleBarSizeX, titleBarSizeY)
end
end
---draw a title bar and text. does not render buttons for the title bar.
function Window:DrawTitleBar()
local titlepos, titlesize = self:GetTitleBarExtents()
strokeWidth(self.borderSize)
stroke(self.borderColor)
fill(self.titleBarColor)
font(self.fontname)
fontSize(self.fontsize)
rect(titlepos.x, titlepos.y, titlesize.x, titlesize.y)
-- Draw Window Title Bar Text
if self.title ~= nil then
fill(self.titleColor)
if self.DesignMode==true then self.prefix = "(Design) " else self.prefix = "" end
textWrapWidth(500)
text(self.prefix .. self.title ,titlepos.x+(titlesize.x/2),titlepos.y+(titlesize.y/2))
end
end
function Window:DrawGridLines()
if self.GridSize >= 5 then
local x
strokeWidth(2)
stroke(255, 0, 20, 255)
for x = 0, self.size.x, self.GridSize do
line( self.pos.x + x, self.pos.y, self.pos.x+x, self.pos.y+self.size.y)
end
for x = 0, self.size.y, self.GridSize do
line( self.pos.x , self.pos.y+x, self.pos.x + self.size.x, self.pos.y+x)
end
end
end
---touch is tricky. the goal is to intercet a newage and process it as high up in the stack
--as possible and get outt of this loop. LOTS OF EARLY RETURNS HERE.
function Window:touched(touch)
--AC: Should we override / remove this?
--Control.touched(self, touch)
local activeControl = wh:getActiveControl()
if touch.state==ENDED and activeControl ~= nil then
activeControl:touched(touch)
wh:clearActiveControl()
end
--see if we are editing the window itself
if touch.state==BEGAN and touch.tapCount == 3 and
self.DesignMode == true and wh:getActiveControl() == nil then
self.onDesignMode(self)
return
end
-- Ac this seems wrong to do here, but...
-- if we are resizing a control with the resizer, override ptIn checks and bypass.
if activeControl ~= nil and touch.state == MOVING then
if activeControl.controlType == "ControlResizer" then
activeControl:onMoving(touch)
return
end
end
local i
--check the toolbar
for i,v in pairs(self.titlebarcontrols) do
msg = v:ptIn(vec2(touch.x, touch.y) )
if msg ==true then
v:touched(touch)
return
end
end
if self.action == WA_RESIZE then
--send this touch directly to the window resizer - override ptIn checks
for i,v in pairs(self.controls) do
if v.id == self.id .. "_resizer" then
v:onMoving(touch)
--realign the toolbar buttons.
local i=1
local bsize = vec2(WB_SIZE+15, WB_SIZE) --title bar sized button
local titlepos, titlesize = self:GetTitleBarExtents()
local k,c
for k,c in pairs(self.titlebarcontrols) do
c.pos = vec2((titlepos.x+titlesize.x)-(bsize.x*i),titlepos.y)
c.pos = c.pos - self.pos
i=i+1
end
end
end
end
--each window has control over design mode seperately.
--need to extend so windows have active controls of their own
if self.DesignMode then
--local activeControl = wh:getActiveControl()
--ac sloppy...STOP THE CURRENTLY DRAGGED CONTROL from stealing all messages.
--see if the user dragged an active control
--protect against Windows as active controls in here.
if activeControl ~= nil and touch.state == MOVING and activeControl.parent ~= nil then
--Override movements for container control!
if activeControl.parent.controlType == "ContainerControl" then
activeControl.pos.x = touch.x - self.pos.x - activeControl.parent.pos.x
activeControl.pos.y = touch.y - self.pos.y - activeControl.parent.pos.y
if (self.GridSize > 5) then
self:SnapToGrid(activeControl)
end
return
end
if activeControl.fixed == false or activeControl.immutable == false then
-- from a different window onto "me"
if activeControl.parent.id ~= self.id then
--copy the control!
oldWin = wh:findWindow(activeControl.parent.id)
newCtl = activeControl:Copy()
newCtl.fixed = false --allow new controls to move around
newCtl.inPalette = false -- no longer in palette
newCtl.id = newCtl.id .. tostring(self.newID)
self.newID = self.newID + 1
newCtl.parent=sel
self:addControl(newCtl)
--if it came from somewhere other than the
--control palette, remove the origianl.
if oldWin == nil then oldWin = self end
if activeControl.inPalette == false then
--move from win1 to win2
oldWin:removeControl(activeControl)
wh:Sort(oldWin)
end
newCtl.pos.x = touch.x - self.pos.x
newCtl.pos.y = touch.y - self.pos.y
if (self.GridSize > 5) then
self:SnapToGrid(newCtl)
end
wh:setActiveControl(newCtl)
wh:Sort(self)
else
if activeControl.fixed == false then --lock control to position
activeControl.pos.x = touch.x - self.pos.x
activeControl.pos.y = touch.y - self.pos.y
if (self.GridSize > 5) then
self:SnapToGrid(activeControl)
end
end
end
end
end
end
---This code stops the palette from being "sticky"; controls touched there
--wil become active with no indicator nor a way to deactivate. maybe later
--allow a border or something. for now, just "let go" on it.
if touch.state == BEGAN then
wh:clearActiveControl()
activeControl = nil
end
--Pass touch to controls' touched functions
if self.windowState ~= WS_HIDDEN then --only pass if window is exposed , we do max now!
if touch.state==BEGAN then
wh:setActiveWindow(self.id)
end
--if we are not moving the window, send move messages
--ac: only if we do not have a control being moved, otherwise controls
-- drag over and steal focus
-- to fix : works but breaks menu...menu needs help via msgs not called directly.
if self.action ~= WA_MOVING and activeControl == nil then
--because we check for the control extens here already, controls that do
--not require resizing (dropdown, menu) no longer require checking to see
--if they were "touched". Checkbox is a good example, there is no self.ptIn()
for i,v in pairs(self.controls) do
if v:ptIn(vec2(touch.x, touch.y) ) then
local msg = v:touched(touch)
if msg then
wh:setActiveControl(v)
return
end
end
end
end
end
--see if we are dragging the toolbar around
local titlepos, titlesize = self:GetTitleBarExtents()
--make some vars for easier comaprison of the toolbar
local btnsize = vec2(WB_SIZE+15, WB_SIZE) --title bar sized button
--early abort = maximized windows are fixed (and should be full window...
if self.windowState == WS_MAXIMIZED then return end
--TOOLBAR CODE - SPECIAL HANDLING NEEDED FOR TOUCH
--if we touch the toolbar, get focus and check for button presses
--AC: Also, if we were already moving, just keep moving!
if self.action == WA_MOVING or
((touch.x > titlepos.x and touch.x < titlepos.x + titlesize.x) and
(touch.y >titlepos.y and touch.y < titlepos.y + titlesize.y)) then
--see if we touched the window and set active if we did
if touch.state == BEGAN then
self.lastTouchBegan = vec2(touch.x,touch.y)
wh:setActiveWindow(self.id)
sound(DATA, "ZgNAbUcUQEBAQEBAAAAAAGRXtjzZoJc+QABAf0BTQEBAQEBA")
end
if touch.state == ENDED then
sound(DATA, "ZgNAbUcUQEBAQEBAAAAAAGRXtjzZoJc+QABAf0BTQEBAQEBA")
self.action = WA_NORMAL
wh:clearActiveControl() --avoid resizer getting sticky
end
local aw = wh:getActiveWindow() --only move active window?
if aw ~=nil then
if touch.state == MOVING and aw.id == self.id and self.lastTouchBegan ~= nil then
-- set a begin point and then take the measurement
local xDiff = touch.x - self.lastTouchBegan.x
local yDiff = touch.y - self.lastTouchBegan.y
self.pos.x = self.pos.x + xDiff
self.pos.y = self.pos.y + yDiff
self.lastTouchBegan = vec2(touch.x,touch.y)
self.action = WA_MOVING
end
end
end
end
---------------------------------------------------------------
function Window:addTitle(title,titleColor,titleBarColor)
self.title = title
self.titleColor = titleColor
self.titleBarColor = titleBarColor
end
---------------------------------------------------------------
function Window:Save()
local archive = {}
local funcs = "" --build a string to write for function definition overrides
-- save all controls
for k,v in pairs(self.controls) do
--don't save ANY resizers.
if string.find(v.id, "resizer") == nil then
local x = v:Save()
table.insert(archive, x)
funcs = funcs .. "\n" .. v.id .."_onClicked=function (b) print(b.id) end\n"
end
end
-- save the table of json strings
return encode(archive), funcs
end
---archive is a table of json encoded strings
function Window:Load(archive)
local controlTable={}
controlTable=decode(archive)
--log("Start loading")
for k,v in pairs(controlTable) do
entry=decode(v) -- v is ctl string
--entry is a decoded json string which makes a table
-- ctl=Control:Load(entry) -- load uses a string
ctl=Control:Create(entry) --this is a json decoded table.
ctl.parent=self
table.insert(self.controls, ctl)
end
end
---------------------------------------------------------------
function Window:close()
if wh then
wh:removeWindow(self.id)
end
end
function Window:minMaxToggle()
if self.windowState == WS_NORMAL then
self.windowState = WS_MINIMIZED
else
if self.windowState == WS_MINIMIZED then
self.windowState = WS_NORMAL
end
end
end
function Window:getControlByName(name)
for k,v in pairs(self.controls) do
if v.id == name then
return v
end
end
end
function Window:addControl(control)
local c=self:getControlByName(control.id)
assert(c==nil, "control id not unique" )
control:setParent(self) -- AC: control attached needs this in its class
table.insert(self.controls,control)
if wh then
wh:Sort(self.controls) --to allow zOrder to work.
end
end
function Window:removeControl(control)
for k,v in pairs(self.controls) do
if control.id == v.id then
table.remove(self.controls, k)
end
end
if wh then
wh:Sort(self.controls) --to allow zOrder to work.
end
end
function Window:SnapToGrid(newCtl)
if self.fixed == false or self.immutable==false then
newCtl.pos.x = math.floor(newCtl.pos.x/self.GridSize)*self.GridSize
newCtl.pos.y = math.floor(newCtl.pos.y/self.GridSize)*self.GridSize
newCtl.size.x = math.ceil(newCtl.size.x/self.GridSize)*self.GridSize
newCtl.size.y = math.ceil(newCtl.size.y/self.GridSize)*self.GridSize
--protect against zero
if newCtl.size.x ==0 or newCtl.size.y ==0 then
newCtl.size = vec2(self.GridSize, self.GridSize)
end
end
end
-----------------------------------------------
function Window:setID(id)
self.id = id
end
-- is this corret? copied.
function Window:midX()
return self.screenpos.x + (self.size.x / 2)
end
-- is this corret? copied.
function Window:midY()
return self.screenpos.y + (self.size.y / 2)
end
function Window:width()
return self.size.x
end
function Window:height()
return self.size.y
end
-- window could be minimized, adjust for that.
function Window:ptIn(vec)
local titlepos, titlesize = self:GetTitleBarExtents()
local sz=self.size
local sp=self.pos
if self.windowState == WS_MINIMIZED then
sz = titlesize
sp=titlepos
end
if vec.x >= sp.x and vec.x <= sp.x+sz.x then
if vec.y >= sp.y and vec.y <= sp.y+sz.y then
return true
end
end
return false
end
--note that we do this so early in the lifecycle, we don't have the helper functions
--or even screenpos calculated yet, which happen after Control.draw()
function Window:SetupToolbar()
local titlepos, titlesize = self:GetTitleBarExtents()
if self.toolbarButtons == nil or self.title == nil then return end
local onEnded, onBegan
local i=1
local bsize = vec2(WB_SIZE+15, WB_SIZE) --title bar sized button
local startText = "" --override this as needed
for k,v in pairs(self.toolbarButtons) do
--hack code!
if (k=="MINMAX" and v~= false) then
startText = "--"
onEnded=function(b)
self:minMaxToggle()
if self.windowState == WS_MINIMIZED then b.text = "O" else b.text = "--" end
wh:clearActiveControl() --stop multitoggling!
end
end
if (k=="WINCLOSE" and v~=false ) then
startText = "X"
onEnded=function() wh:clearActiveControl() self:close() end
end
if startText ~= "" then
local ctlpos = vec2((titlepos.x+titlesize.x)-(bsize.x*i), titlepos.y)
ctlpos = ctlpos - self.pos
local params = {id= "toolButton"..i,
text=startText, zOrder = self.zOrder+1,
font="Arial-BoldMT",
pos=ctlpos,
size=vec2(bsize.x,bsize.y),
immutable = true,
fixed=true,
canMove=false,
backColor = color(10, 9, 9, 255),
onEnded=onEnded,
onBegan = onBegan,
--debug=true, --so we can get a frame + background fill
parent=self
}
local btn = TouchBox(params) --make a TouchBox
btn.parent = self --doing it this way does NOT auto add it to the main control list
table.insert(self.titlebarcontrols, btn)
i=i+1
end
end
end
function Window:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"title", "TextInput"})
table.insert(props, {"titleColor", "TextButton"})
table.insert(props, {"titleBarColor", "TextButton"})
table.insert(props, {"titleBarSize", "TextInput"}) --AC: Note that this is a number!
table.insert(props, {"GridSize", "DropList", "10;20;24;30;36;40;48;50;64"})
-- not working yet
-- table.insert(props, {"TODO_CLOSE", "Switch"})
-- table.insert(props, {"TODO_MINMAX", "Switch"})
return props
end
--# Frame
Frame = class()
-- Frame
-- ver. 1.5
-- a simple rectangle for holding controls.
-- ====================
function Frame:init(left, bottom, right, top)
self.left = left
self.right = right
self.bottom = bottom
self.top = top
end
function Frame:inset(dx, dy)
self.left = self.left + dx
self.right = self.right - dx
self.bottom = self.bottom + dy
self.top = self.top - dy
end
function Frame:offset(dx, dy)
self.left = self.left + dx
self.right = self.right + dx
self.bottom = self.bottom + dy
self.top = self.top + dy
end
--glosses the frame's base color
function Frame:gloss(baseclr)
local i, t, r, g, b, y
pushStyle()
if baseclr == nil then baseclr = color(194, 194, 194, 255) end
fill(baseclr)
rectMode(CORNERS)
rect(self.left, self.bottom, self.right, self.top)
r = baseclr.r
g = baseclr.g
b = baseclr.b
for i = 1 , self:height() / 2 do
r = r - 1
g = g - 1
b = b - 1
stroke(r, g, b, 255)
y = (self.bottom + self.top) / 2
line(self.left, y + i, self.right, y + i)
line(self.left, y - i, self.right, y - i)
end
popStyle()
end
function Frame:shade(base, step)
pushStyle()
strokeWidth(1)
for y = self.bottom, self.top do
i = self.top - y
stroke(base - i * step, base - i * step, base - i * step, 255)
line(self.left, y, self.right, y)
end
popStyle()
end
--# MultiButton
MultiButton = class(Control)
-- MultiButton 1.2
-- =====================
-- Designed for use with the Cider Controls
-- Allows selecting between 2-x choices
-- =====================
function MultiButton:init(s, left, bottom, right, top, callback)
Control.init(self, s, left, bottom, right, top, callback)
self.controlName = "MultiButton"
self.itemText = {}
self:splitText()
self.background = color(136, 168, 199, 255)
end
function MultiButton:splitText()
local i, k
i = 0
for k in string.gmatch(self.text,"([^;]+)") do
i = i + 1
self.itemText[i] = k
end
end
function MultiButton:draw()
local w, i, b, h
pushStyle()
w = (self:width()) / #self.itemText
h = self:height()
strokeWidth(2)
fill(self.background)
stroke(self.foreground)
self:roundRect(6)
noStroke()
stroke(self.background)
self:inset(2, 2)
self:roundRect(6)
self:inset(-2, -2)
stroke(self.foreground)
textMode(CENTER)
font(self.font)
fontSize(self.fontSize)
for i, b in ipairs(self.itemText) do
fill(self.foreground)
strokeWidth(2)
if i < #self.itemText then
line(self.left + i * w, self.top,
self.left + i * w, self.bottom)
end
text(b, (self.left + i * w) - (w / 2), self:midY())
noStroke()
fill(0, 0, 0, 22)
if i ~= self.selected then
h = self:height()
rect(self.left + i * w - w, self.bottom,
w, h * 0.6 )
rect(self.left + i * w - w, self.bottom,
w, h * 0.4 )
rect(self.left + i * w - w, self.bottom,
w, h * 0.2 )
else
fill(self.highlight)
rect(self.left + i * w - w + 1, self.bottom + 3,
w - 2, self:height() - 6)
fill(0, 0, 0, 28)
rect(self.left + i * w - w, self:midY() - h/4,
w, self:height() * 0.6)
rect(self.left + i * w - w, self:midY(),
w, self:height() * 0.4 )
rect(self.left + i * w - w, self:midY() + h/4,
w, self:height() * 0.3 )
fill(self.foreground)
text(b, (self.left + i * w) - (w / 2), self:midY())
end
end
popStyle()
end
function MultiButton:touched(touch)
if self:ptIn(touch.x, touch.y) then
if touch.state == BEGAN then
w = (self:width()) / #self.itemText
i = math.floor((touch.x - self.left) / w) + 1
self.selected = i
end
if self.callback ~= nil then self.callback() end
return true
end
end
--# NotchSlider
NotchSlider = class(Control)
-- NotchSlider 1.0
-- =====================
-- Designed for use with Cider controls
-- offers option of sliding to preset positions
-- =====================
function NotchSlider:init(s, left, bottom, right, top, callback)
Control.init(self, s, left, bottom, right, top, callback)
self.controlName = "NotchSlider"
self.itemText = {}
self:splitText()
end
function NotchSlider:splitText()
local i, k
i = 0
for k in string.gmatch(self.text,"([^;]+)") do
i = i + 1
self.itemText[i] = k
end
end
function NotchSlider:draw()
local x, i, scale
pushStyle()
font(self.font)
fontSize(self.fontSize)
textAlign(CENTER)
textMode(CENTER)
strokeWidth(20)
stroke(self.background)
fill(self.background)
line(self.left, self:midY(), self.right, self:midY())
if #self.itemText > 1 then
x = self:width() / (#self.itemText - 1)
for i = 1, #self.itemText do
fill(self.background)
stroke(self.background)
ellipse(self.left + (i-1) * x, self:midY(), 35)
fill(self.foreground)
text(self.itemText[i], self.left + (i-1) * x,
self:midY() + 25)
end
x = self.left + (self.selected - 1) * x
else
x = self:midX()
end
fill(0, 0, 0, 16)
noStroke()
ellipse(x, self:midY() - 12, 25, 12)
ellipse(x, self:midY() - 10, 45, 30)
strokeWidth(12)
stroke(self.highlight)
ellipse(x, self:midY(), 40)
fill(255, 255, 255, 56)
noStroke()
ellipse(x, self:midY() + 12, 25, 12)
popStyle()
end
function NotchSlider:touched(touch)
local x, scale
if touch.state == BEGAN or touch.state == MOVING then
if self:ptIn(touch.x, touch.y) then
if #self.itemText > 1 then
scale = self:width() / (#self.itemText - 1)
x = touch.x - self.left + 20
self.selected = math.floor(x / scale) + 1
end
if self.callback ~= nil then self.callback() end
return true
end
end
end
--# JSON
--captureTab("JSON.lua", captureCode(1))
----
-- JSON encode / decode functionality
-- ret = encode(object)
-- tbl = decode(ret)
-- or --
-- ret = JSON:encode(object)
-- tbl = JSON:decode(ret)
JSON = class()
-----------------------------------------------------------------------------
-- JSON4Lua: JSON encoding / decoding support for the Lua language.
-- json Module.
-- Author: Craig Mason-Jones
-- Homepage: http://json.luaforge.net/
-- Version: 0.9.40
-- This module is released under the MIT License (MIT).
-- Please see LICENCE.txt for details.
--
-- USAGE:
-- This module exposes two functions:
-- encode(o)
-- Returns the table / string / boolean / number / nil / json.null
-- value as a JSON-encoded string.
-- decode(json_string)
-- Returns a Lua object populated with the data encoded in the JSON string json_string.
--
-- REQUIREMENTS:
-- compat-5.1 if using Lua 5.0
--
-- CHANGELOG
-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix).
-- Fixed Lua 5.1 compatibility issues.
-- Introduced json.null to have null values in associative arrays.
-- encode() performance improvement (more than 50%) through table.concat rather than ..
-- Introduced decode ability to ignore /**/ comments in the JSON string.
-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays.
-- 0.9.10.ac.1 Codea: added gloabal JSON: class wrappers as coding style helpers.
-- AC: added userdata as a filter.
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- Imports and dependencies
-----------------------------------------------------------------------------
--local math = require('math')
--local string = require("string")
--local table = require("table")
local base = _G
-----------------------------------------------------------------------------
-- Module declaration
-----------------------------------------------------------------------------
--module("json")
-- Private functions
local decode_scanArray
local decode_scanComment
local decode_scanConstant
local decode_scanNumber
local decode_scanObject
local decode_scanString
local decode_scanWhitespace
local encodeString
local isArray
local isEncodable
-----------------------------------------------------------------------------
-- CLASS WRAPPERS : can use as statics.
-- note that this class is not clean, the functions are globally defined, we should
-- consider making them locals at least, and namespacing them at best.
-----------------------------------------------------------------------------
function JSON:encode(v)
return encode(v)
end
function JSON:decode(s, startPos)
return decode(s, startPos)
end
-----------------------------------------------------------------------------
-- PUBLIC FUNCTIONS
-----------------------------------------------------------------------------
--- Encodes an arbitrary Lua object / variable.
-- @param v The Lua object / variable to be JSON encoded.
-- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode)
function encode (v)
-- Handle nil values
if v==nil then
return "null"
end
local vtype = base.type(v)
-- Handle strings
if vtype=='string' then
return '"' .. encodeString(v) .. '"' -- Need to handle encoding in string
end
-- Handle booleans
if vtype=='number' or vtype=='boolean' then
return base.tostring(v)
end
-- handle userdata...somehow...
if vtype=='userdata' then
return '"' .. encodeString(tostring(v)) .. '"' -- Need to handle encoding in string
end
-- Handle tables
if vtype=='table' then
local rval = {}
-- Consider arrays separately
local bArray, maxCount = isArray(v)
if bArray then
for i = 1,maxCount do
table.insert(rval, encode(v[i]))
end
else -- An object, not an array
for i,j in base.pairs(v) do
if isEncodable(i) and isEncodable(j) then
table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j))
end
end
end
if bArray then
return '[' .. table.concat(rval,',') ..']'
else
return '{' .. table.concat(rval,',') .. '}'
end
end
-- Handle null values
if vtype=='function' and v==null then
return 'null'
end
base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v))
end
--- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
-- @param s The string to scan.
-- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1.
-- @param Lua object, number The object that was scanned, as a
-- Lua table / string / number / boolean or nil,
-- and the position of the first character after
-- the scanned JSON object.
function decode(s, startPos)
startPos = startPos and startPos or 1
startPos = decode_scanWhitespace(s,startPos)
base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
local curChar = string.sub(s,startPos,startPos)
-- Object
if curChar=='{' then
return decode_scanObject(s,startPos)
end
-- Array
if curChar=='[' then
return decode_scanArray(s,startPos)
end
-- Number
if string.find("+-0123456789.e", curChar, 1, true) then
return decode_scanNumber(s,startPos)
end
-- String
if curChar==[["]] or curChar==[[']] then
return decode_scanString(s,startPos)
end
if string.sub(s,startPos,startPos+1)=='/*' then
return decode(s, decode_scanComment(s,startPos))
end
-- Otherwise, it must be a constant
return decode_scanConstant(s,startPos)
end
--- The null function allows one to specify a null value in an associative array (which is otherwise
-- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null }
function null()
return null -- so json.null() will also return null ;-)
end
-----------------------------------------------------------------------------
-- Internal, PRIVATE functions.
-- Following a Python-like convention, I have prefixed all these 'PRIVATE'
-- functions with an underscore.
-----------------------------------------------------------------------------
--- Scans an array from JSON into a Lua object
-- startPos begins at the start of the array.
-- Returns the array and the next starting position
-- @param s The string being scanned.
-- @param startPos The starting position for the scan.
-- @return table, int The scanned array as a table, and the position of the next character to scan.
function decode_scanArray(s,startPos)
local array = {} -- The