Skip to content

Instantly share code, notes, and snippets.

@AntonioCiolino
Last active December 16, 2015 01:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save AntonioCiolino/5356607 to your computer and use it in GitHub Desktop.
Save AntonioCiolino/5356607 to your computer and use it in GitHub Desktop.
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 return value
local stringLen = string.len(s)
base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
startPos = startPos + 1
-- Infinite loop for array elements
repeat
startPos = decode_scanWhitespace(s,startPos)
base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
local curChar = string.sub(s,startPos,startPos)
if (curChar==']') then
return array, startPos+1
end
if (curChar==',') then
startPos = decode_scanWhitespace(s,startPos+1)
end
base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
object, startPos = decode(s,startPos)
table.insert(array,object)
until false
end
--- Scans a comment and discards the comment.
-- Returns the position of the next character following the comment.
-- @param string s The JSON string to scan.
-- @param int startPos The starting position of the comment
function decode_scanComment(s, startPos)
base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
local endPos = string.find(s,'*/',startPos+2)
base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
return endPos+2
end
--- Scans for given constants: true, false or null
-- Returns the appropriate Lua type, and the position of the next character to read.
-- @param s The string being scanned.
-- @param startPos The position in the string at which to start scanning.
-- @return object, int The object (true, false or nil) and the position at which the next character should be
-- scanned.
function decode_scanConstant(s, startPos)
local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
local constNames = {"true","false","null"}
for i,k in base.pairs(constNames) do
--print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
return consts[k], startPos + string.len(k)
end
end
base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
end
--- Scans a number from the JSON encoded string.
-- (in fact, also is able to scan numeric +- eqns, which is not
-- in the JSON spec.)
-- Returns the number, and the position of the next character
-- after the number.
-- @param s The string being scanned.
-- @param startPos The position at which to start scanning.
-- @return number, int The extracted number and the position of the next character to scan.
function decode_scanNumber(s,startPos)
local endPos = startPos+1
local stringLen = string.len(s)
local acceptableChars = "+-0123456789.e"
while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
and endPos<=stringLen
) do
endPos = endPos + 1
end
local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
local stringEval = base.loadstring(stringValue)
base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
return stringEval(), endPos
end
--- Scans a JSON object into a Lua object.
-- startPos begins at the start of the object.
-- Returns the object and the next starting position.
-- @param s The string being scanned.
-- @param startPos The starting position of the scan.
-- @return table, int The scanned object as a table and the position of the next character to scan.
function decode_scanObject(s,startPos)
local object = {}
local stringLen = string.len(s)
local key, value
base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
startPos = startPos + 1
repeat
startPos = decode_scanWhitespace(s,startPos)
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
local curChar = string.sub(s,startPos,startPos)
if (curChar=='}') then
return object,startPos+1
end
if (curChar==',') then
startPos = decode_scanWhitespace(s,startPos+1)
end
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
-- Scan the key
key, startPos = decode(s,startPos)
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
startPos = decode_scanWhitespace(s,startPos)
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
startPos = decode_scanWhitespace(s,startPos+1)
base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
value, startPos = decode(s,startPos)
object[key]=value
until false -- infinite loop while key-value pairs are found
end
--- Scans a JSON string from the opening inverted comma or single quote to the
-- end of the string.
-- Returns the string extracted as a Lua string,
-- and the position of the next non-string character
-- (after the closing inverted comma or single quote).
-- @param s The string being scanned.
-- @param startPos The starting position of the scan.
-- @return string, int The extracted string as a Lua string, and the next character to parse.
function decode_scanString(s,startPos)
base.assert(startPos, 'decode_scanString(..) called without start position')
local startChar = string.sub(s,startPos,startPos)
base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string')
local escaped = false
local endPos = startPos + 1
local bEnded = false
local stringLen = string.len(s)
repeat
local curChar = string.sub(s,endPos,endPos)
-- Character escaping is only used to escape the string delimiters
if not escaped then
if curChar==[[\]] then
escaped = true
else
bEnded = curChar==startChar
end
else
-- If we're escaped, we accept the current character come what may
escaped = false
end
endPos = endPos + 1
base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos)
until bEnded
local stringValue = 'return ' .. string.sub(s, startPos, endPos-1)
local stringEval = base.loadstring(stringValue)
base.assert(stringEval, 'Failed to load string [ ' .. stringValue ..
'] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos)
return stringEval(), endPos
end
--- Scans a JSON string skipping all whitespace from the current start position.
-- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached.
-- @param s The string being scanned
-- @param startPos The starting position where we should begin removing whitespace.
-- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string
-- was reached.
function decode_scanWhitespace(s,startPos)
local whitespace=" \n\r\t"
local stringLen = string.len(s)
while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true)
and startPos <= stringLen) do
startPos = startPos + 1
end
return startPos
end
--- Encodes a string to be JSON-compatible.
-- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-)
-- @param s The string to return as a JSON encoded (i.e. backquoted string)
-- @return The string appropriately escaped.
function encodeString(s)
s = string.gsub(s,'\\','\\\\')
s = string.gsub(s,'"','\\"')
s = string.gsub(s,"'","\\'")
s = string.gsub(s,'\n','\\n')
s = string.gsub(s,'\t','\\t')
return s
end
-- Determines whether the given Lua type is an array or a table / dictionary.
-- We consider any table an array if it has indexes 1..n for its n items, and no
-- other data in the table.
-- I think this method is currently a little 'flaky', but can't think of a good way around it yet...
-- @param t The table to evaluate as an array
-- @return boolean, number True if the table can be represented as an array, false otherwise. If true,
-- the second returned value is the maximum
-- number of indexed elements in the array.
function isArray(t)
-- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
-- (with the possible exception of 'n')
local maxIndex = 0
for k,v in base.pairs(t) do
if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair
if (not isEncodable(v)) then return false end -- All array elements must be encodable
maxIndex = math.max(maxIndex,k)
else
if (k=='n') then
if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements
else -- Else of (k=='n')
if isEncodable(v) then return false end
end -- End of (k~='n')
end -- End of k,v not an indexed pair
end -- End of loop across all pairs
return true, maxIndex
end
--- Determines whether the given Lua object / table / variable can be JSON encoded. The only
-- types that are JSON encodable are: string, boolean, number, nil, table and json.null.
-- In this implementation, all other types are ignored.
-- @param o The object to examine.
-- @return boolean True if the object should be JSON encoded, false if it should be ignored.
function isEncodable(o)
local t = base.type(o)
return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table'
or t=='userdata')
or (t=='function' and o==null)
end
--# GridControl
-- GridControl
-- AC aliased from toucharray
-- the toucharray class takes a string in the latex format rows separated by & and ---
-- columns by \\ and produces a touchable array whose topleft corner is is at x,y and whose
-- celsizes are either automatic or given by the user.
-- the typical call is
-- a=touchArray("aaaaaaaa aaaa & b \\ c& d & t\\ a& 5&", 300,300, "single") or
-- a=touchArray("aaaaaaaa aaaa & b \\ c& d & t\\ a& 5&", 300,300, "single",50,50) or
-- a=touchArray("aaaaaaaa aaaa & b \\ c& d & t\\ a& 5&", 300,300)
-- the single variation allows one to choose a unique cell
--the table self.selected is a the set of selected cells, that is the
-- value of self.selected[1..","..2] is true if the cell 1,2 is touched and false otherwise
-- you can check if the cell i,j is selected via self:check(i,j) and
-- you can get the text in the cel i,j by self.array[i][j].text
GridControl = class(Control)
function GridControl:init(params)
params.controlType="GridControl"
Control.init(self,params)
self.selected={}
self.seltype=params.style
self.cellsize = params.cellsize or vec2() -- will auto-grow
self.tab={} --holds the destination table showing on screen.
self.array={} --not sure holds the controls to render array PER LINE
self.border = 0 --px padding in the cell. Redundant for labelcontrol?
--self.debug = true
end
function GridControl:Configure()
if type(self.text) == "string" then
self:arrange(self.text ) --creates a table
textWrapWidth(self.cellsize.x)
--set cell height --AC: BUG? Y compared against X?
--unneded?
--self.cellsize.y=math.max(self.cellsize.y,20*self.size.y/self.cellsize.y)
else
--AC TODO
assert(type(self.text)== "table" , "Must have a table or LaTeX string to use this code.")
--[[ self.tab = self.data --AC: Note we assuming a table
--AC:assuming the first cell is a model for all other cells. BAAD assumption!
w, h = textSize(self.text[1][1])
-- print(w,h)
self.cellsize.x=math.max(self.cellsize.x, w)
self.cellsize.y=math.max(self.cellsize.y, h)
textWrapWidth(self.cellsize.x)
self.maxWidth=self.cellsize.x --max cell width
]]--
end
local maxHeight = 0
local maxWidth = 0
for i,v in pairs(self.tab) do
self.array[i]={}
if maxHeight < i then maxHeight = i end
for j,k in pairs(v) do
if maxWidth < j then maxWidth = j end
params = {text = k,
--pos =vec2(self.cellsize.x*(j-1), self.cellsize.y*i ),
pos =vec2(100*(j-1), 40*i ),
--size=vec2(self.cellsize.x, self.cellsize.y),
size=vec2(100, 40),
id="Tb"..math.random(1,1000),
borderWidth=8, borderColor=color(218, 147, 147, 255),
wrapWidth=self.cellsize.x, textAlign=CENTER,
vertAlign=CA_MIDDLE,
parent=self.parent, fontsize=12,
onClicked = function() log("click") end
}
local cel = LabelControl(params)
table.insert(self.array[i],cel)
self.selected[i..","..j]=false
end
end
end
function GridControl:arrange(str)
local i=1
for a in string.gmatch(str,"([^\\]+)") do
self.tab[i]={}
for b in string.gmatch(a,"([^&]+)") do
-- make cells strech to whatever max they need to get to.
d=b:match "^%s*(.-)%s*$"
w, h = textSize(d)
self.cellsize.x=math.max(self.cellsize.x, w)
self.cellsize.y=math.max(self.cellsize.y, h)
table.insert(self.tab[i], d)
end
i = i + 1
end
end
function GridControl:draw()
if self.parent.id == "winPalette" then
self.size=vec2(50,50)
self.DesignMode=true
Control.draw(self)
return
end
Control.draw(self)
pushStyle()
self:Configure() --recalc eVERYTHING
fill(self.backColor)
textWrapWidth(self.cellsize.x)
rect(self.left, self.top, self:width(), self:height())
-- using array() for ctl:draw()s.
for i , v in pairs(self.array) do
for j, k in pairs(v) do
local ctl = self.array[i][j]
if self:check(i,j) == true then
ctl.foreColor = self.highlight else
ctl.foreColor = self.foreColor
end
k:draw()
end
end
popStyle()
end
function GridControl:check(i,j)
return self.selected[i..","..j]
end
function GridControl:touched(touch)
Control.touched(self,touch)
if self.parent.DesignMode then return end
for i , v in pairs(self.array) do
for j, k in pairs(v) do
self.selected[i..","..j]=k.selected
if k:ptIn(vec2(touch.x, touch.y)) then
k:touched(touch)
wh:setActiveControl(k)
self.selected[i..","..j]=true
end
--if we want only once cell, clean all others.
if self.seltype=="single" then
if k.selected then
for a,b in pairs(self.array) do
for c,d in pairs(b) do
if d ~= k then d.selected=false end
end
end
end
end
end
end
end
function GridControl:onGotFocus()
self:Configure()
end
--make a click sound when the control is touched
function GridControl:click(val)
sound(SOUND_HIT, 1960)
if val.selected then
print(val.text)
end
end
function GridControl:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
return props
end
function GridControl:Save()
--have to strip the parent object our or we stack overflow.
local myParent = self.parent
self.parent = nil
self.tab=nil
self.selected=nil
self.array=nil
persist = encode(self)
log(persist)
self.parent = myParent
return persist
end
--# Switch
Switch = class(Control)
-- Switch 2.0
-- =====================
-- Designed for use with Cider controls
-- two-position selector
-- =====================
-- 1.3 cosmetic changes, refactored, based on Control
function Switch:init(params)
params.controlType="Switch"
Control.init(self, params)
self.itemText = {} -- going to depend non ipairs this time...
self:splitText()
end
function Switch:splitText()
local i, k
i = 0
for k in string.gmatch(self.text,"([^;]+)") do
i = i + 1
self.itemText[i] = k
end
end
--unsed code - attempted to draw gradients, etc differently.
function Switch:draw2()
local posx
pushStyle()
Control.draw(self)
clip(self.screenpos.x, self.screenpos.y, self:width(), self:height())
h = self:height()
stroke(self.backColor)
strokeWidth(h)
fill(self.backColor)
-- rect(self.screenpos.x, self.screenpos.y, self:width(), self:height())
local r= h/2 --half height up for drawing.
fill(self.backColor)
stroke(self.backColor)
ellipseMode(CORNER)
insetPos = vec2(self.screenpos.x + r, self.screenpos.y + r)
insetSize = vec2(self.screenpos.x+self:width() - r, self.screenpos.y+r)
ellipse(insetPos.x, insetPos.y, self:width(), self:height())
fill(0, 0, 0, 0)
stroke(0, 0, 0, 0)
insetPos = vec2(self.screenpos.x + r, self.screenpos.y + r)
insetSize = vec2(self.screenpos.x+self:width() - r, self.screenpos.y+r)
line(insetPos.x, insetPos.y, insetSize.x, insetSize.y)
if self.selected then
--only draw if it's ON
stroke(255, 255, 255, 125)
insetPos = vec2(self.screenpos.x + r*2, self.screenpos.y )
insetSize = vec2(self.screenpos.x+self:width() , self.screenpos.y)
line(insetPos.x, insetPos.y, insetSize.x, insetSize.y)
end
--draw button header
if self.selected then
posx = self.right - h
else
posx=self.left
end
noStroke()
stroke(self.borderColor)
fill(self.foreColor)
ellipse(posx, self.screenpos.y+self.size.y-h, h )
strokeWidth(1)
ellipse(posx, self.screenpos.y+self.size.y-h, h)
fill(self.foreColor)
if self.selected then chkVal = 0 else chkVal = 1 end
if #self.itemText > chkVal then
--fill(self.backColor)
--text(self.itemText[chkVal+1], self:midX()-1, self:midY()-1)
fill(self.foreColor)
text(self.itemText[chkVal+1], self:midX(), self:midY())
end
clip()
popStyle()
end
function Switch:draw()
Control.draw(self)
pushStyle()
font(self.fontname)
fontSize(self.fontsize)
strokeWidth(self.borderWidth)
if self.selected then
stroke(self.highlight)
fill(self.highlight)
else
fill(self.backColor)
stroke(self.backColor)
end
h = self:height()
self:roundRect(h/2+1)
self:roundRect(h/2)
--fill(0, 0, 0, 0) --ignored?
--stroke(self.borderColor) --overrides my background
self:roundRect(h/2+1)
--stroke(self.highlight)
--fill(self.highlight)
self:roundRect(h/2)
if self.selected then
posx = self.right - h
else
posx=self.left
end
noStroke()
--draw button header
stroke(self.borderColor)
fill(self.foreColor)
ellipse(posx, self.screenpos.y+self.size.y-h, h )
strokeWidth(1)
-- fill(self.knobColor)
fill(255, 255, 255, 148)
ellipse(posx, self.screenpos.y+self.size.y-h, h)
if self.selected then chkVal = 0 else chkVal = 1 end
if #self.itemText > chkVal then
--fill(self.backColor)
--text(self.itemText[chkVal+1], self:midX()-1, self:midY()-1)
fill(self.foreColor)
text(self.itemText[chkVal+1], self:midX(), self:midY())
end
popStyle()
end
function Switch: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
if touch.state == BEGAN then
self.selected = not self.selected
if self.onClicked ~= nil then self.onClicked(self) end
end
end
end
function Switch:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
return props
end
function Switch:onPropertyChanged()
self.itemText={}
self:splitText()
end
--# TouchBox
--used for controls that have a UI component
--this might actually belong rolled into Control...
TouchBox = class(Control)
--tab order!!!
assert (Control ~= nil, "Control class is not defined")
function TouchBox:init(params)
params.controlType = params.controlType or "TouchBox" --do not override controlresizer!
--assert(params.parent ~= nil, "Touchbox must have a parent control.")
Control.init(self, params)
self.onBegan = params.onBegan or TouchBox.onBegan
self.onMoving = params.onMoving or TouchBox.onMoving
self.onEnded = params.onEnded or TouchBox.onEnded
self.debug = params.debug or false
end
--caller should override this to draw correctly.
function TouchBox:draw()
Control.draw(self)
pushStyle()
if self.text== nil then self.text="" end
strokeWidth(2)
stroke(self.borderColor) --alter change to strokeColor?
fill(self.backColor)
if self.debug == true then
rectMode(CENTER)
rect(self.screenpos.x,self.screenpos.y,self.size.x,self.size.y)
end
fill(self.foreColor)
textMode(CENTER)
local w,h = textSize(self.text)
text(self.text, self.screenpos.x+ self.size.x/2,self.screenpos.y+self.size.y/2)
popStyle()
self.lastPosition = self.screenpos
end
--Ignore designmode flag - it stops the touchbox (resizer, close/minimize) from operating!
function TouchBox:touched(touch)
Control.touched(self,touch)
if touch.state == BEGAN and self.onBegan ~= nil then self.onBegan(self) end
if touch.state == MOVING and self.onMoving ~= nil then self.onMoving(self) end
if touch.state == ENDED and self.onEnded ~= nil then self.onEnded(self) end
end
function TouchBox:onBegan()
self.lastTouched = vec2(CurrentTouch.x, CurrentTouch.y)
Control.onBegan(self)
end
function TouchBox:onMoving()
self.lastTouched = vec2(CurrentTouch.x, CurrentTouch.y)
Control.onMoving(self)
end
function TouchBox:onEnded()
Control.onEnded(self)
self.lastTouched = nil
end
function TouchBox:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
return props
end
--# ControlResizer
--ControlResizer - used to resize controls. a control creates an instance of
-- this and attaches it to
--itself. a control without this effectively cannot be resized by the UI.
--ac hack!! copied from Resizer.
ControlResizer = class(TouchBox)
--tab order!!!
assert (TouchBox ~= nil, "Touchbox class is not defined")
function ControlResizer:init(params)
params.controlType="ControlResizer"
params.canResize=false
TouchBox.init(self, params)
self.onBegan = ControlResizer.onBegan
self.onMoving = ControlResizer.onMoving
self.onEnded = ControlResizer.onEnded
self.immutable = true --cannot be stolen in design mode cannot resize itself...
self.fixed=true -- do i need this? added for clarity
self.zOrder=99 -- always on top
self.targetControl = params.targetControl
self.ctl=nil
self.resizeMesh = mesh()
self.resizeMesh.vertices = { vec2(self.size.x,self.size.y), vec2(0,0), vec2(self.size.x,0)}
end
function ControlResizer:draw()
if self.ctl==nil then
self.ctl = self.parent:getControlByName(self.targetControl)
assert(self.ctl ~= nil, "Cannot find target control " .. self.targetControl)
end
if self.ctl.parent ==nil then return end --grcefully fail in non-windowed mode.
if self.ctl.parent.DesignMode == false then return end
self.pos = self.ctl:getControlPos() + vec2(self.ctl.size.x, -16)
TouchBox.draw(self)
pushStyle()
pushMatrix()
translate(self.screenpos.x, self.screenpos.y)
self.resizeMesh:draw()
popMatrix()
popStyle()
self.lastPosition = self.screenpos
end
function ControlResizer:onBegan()
sound(DATA, "ZgNAbUcUQEBAQEBAAAAAAGRXtjzZoJc+QABAf0BTQEBAQEBA")
self.lastTouched = vec2(CurrentTouch.x, CurrentTouch.y)
self.ctl.action =WC_RESIZE
wh:setActiveControl(self)
end
function ControlResizer:onMoving()
if self.lastTouched ~= nil then
--determine movement
local deltaTouch = self:getScreenPos() - vec2(CurrentTouch.x, CurrentTouch.y)
local delta = self.lastTouched - vec2(CurrentTouch.x, CurrentTouch.y)
--if self.ctl.size.y + delta.y > WB_SIZE + self.size.y then
--if self.ctl.size.x - delta.x > self.size.x + 125 then --125 HACK! Button Size
--self.pos.x = self.pos.x - delta.x
self.ctl.pos.y = self.ctl.pos.y - delta.y
self.ctl.size.x = self.ctl.size.x - delta.x
self.ctl.size.y = self.ctl.size.y + delta.y --adjust so ttlbar stays "fixed"
self.lastTouched = vec2(CurrentTouch.x, CurrentTouch.y)
--else
--self.parent.action = WC_NORMAL
--end
--end
else
self.lastTouched = vec2(CurrentTouch.x, CurrentTouch.y)
end
end
function ControlResizer:onEnded()
sound(DATA, "ZgNAOhcUQEBAQEBAAAAAAGRXtjzZoJc+LABAf0BTQEBAQEBA")
self.lastTouched= nil
self.ctl.action = WC_NORMAL
wh:clearActiveControl()
self.ctl.parent:SnapToGrid(self.ctl)
self.ctl.parent:removeControl(self)
end
function ControlResizer:onDesignMode()
local winColorPicker=wh:GetColorPicker()
winColorPicker:Show(self.ctl, self.ctl.backColor)
end
function ControlResizer:Save()
-- override
end
--# TextButton
TextButton = class(Control)
-- TextButton
-- ver. 2.0
-- a control for displaying a simple button
-- ====================
-- 1.7 derived from Control class, appearance changes
-- 1.6 cosmetic changes
function TextButton:init(params)
params.controlType = "TextButton"
Control.init(self, params)
self.pressed = false
-- try to affect button
end
function TextButton:draw()
Control.draw(self)
local img = image(self.size.x, self.size.y)
m=mesh()
-- wanted to make a glossy button. if i ever figure it out, generic it for
-- PictureControl to act as a button?
--m.shader=shader("Documents:Lighting")
clip()
if self.isActive == false then return end --AC: Bug!
setContext(img)
pushStyle()
--AC hack for mesh use - store location to recalc later.
local ol = self.left
local ot = self.top
self.left=0
self.top=0
font(self.fontname)
fontSize(self.fontsize)
stroke(self.foreColor)
fill(self.foreColor)
-- self:roundRect(5)
--self:inset(2, 2)
if self.pressed then
stroke(self.highlight)
fill(self.highlight)
--m.shader=shader("Basic:Invert")
else
stroke(self.backColor)
fill(self.backColor)
end
self:roundRect(5)
self:inset(-2, -2)
self.left=ol
self.top = ot
noStroke()
fill(self.foreColor)
textMode(CENTER)
text(self.text, self:width()/2, self:height()/2)
popStyle()
setContext()
if self.parent then
--set up clipping for window again
clip(self.parent.pos.x,self.parent.pos.y,
self.parent.size.x+self.parent.borderSize/2,self.parent.size.y)
else
clip()
end
--sprite(img,self.screenpos.x, self.screenpos.y)
pushMatrix()
m:addRect(0,0,img.width, img.height)
m.texture=img
--[[
m.shader.freq=1
m.shader.time=ElapsedTime
m.shader.vAmbientMaterial = .8
m.shader.vDiffuseMaterial = 1
m.shader.vSpecularMaterial = .5
m.shader.lightColor = color(255, 255, 255, 255)
m.shader.mView = viewMatrix()
m.shader.mProjection = projectionMatrix()
m.shader.mModel = modelMatrix()
m.shader.vEyePosition = vec4(10,10,-20,0)
lightPosition = vec4(-200,-400,-200,0)
m.shader.vLightPosition = lightPosition]]
translate(self.screenpos.x+self:width()/2, self.screenpos.y+self:height()/2)
m:draw()
m=nil
img=nil
popMatrix()
fill(self.foreColor)
textMode(CENTER)
--text(self.text, self:width()/2, self:height()/2)
popStyle()
if self.resizer ~= nil then self.resizer:draw() end
end
function TextButton:touched(touch)
Control.touched(self,touch)
--support legacy / non windowed feature
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 --we touched this control. YES, we checked twice...
if touch.state == ENDED then --press ended
if self.onClicked ~= nil then self.onClicked(self) end
self.pressed = false
else
self:onSelected()
self.pressed = true
end
end
end
function TextButton:onLostFocus()
self.pressed = false
end
function TextButton:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
return props
end
--# TextButton
TextButton = class(Control)
-- TextButton
-- ver. 2.0
-- a control for displaying a simple button
-- ====================
-- 1.7 derived from Control class, appearance changes
-- 1.6 cosmetic changes
function TextButton:init(params)
params.controlType = "TextButton"
Control.init(self, params)
self.pressed = false
-- try to affect button
end
function TextButton:draw()
Control.draw(self)
local img = image(self.size.x, self.size.y)
m=mesh()
-- wanted to make a glossy button. if i ever figure it out, generic it for
-- PictureControl to act as a button?
--m.shader=shader("Documents:Lighting")
clip()
if self.isActive == false then return end --AC: Bug!
setContext(img)
pushStyle()
--AC hack for mesh use - store location to recalc later.
local ol = self.left
local ot = self.top
self.left=0
self.top=0
font(self.fontname)
fontSize(self.fontsize)
stroke(self.foreColor)
fill(self.foreColor)
-- self:roundRect(5)
--self:inset(2, 2)
if self.pressed then
stroke(self.highlight)
fill(self.highlight)
--m.shader=shader("Basic:Invert")
else
stroke(self.backColor)
fill(self.backColor)
end
self:roundRect(5)
self:inset(-2, -2)
self.left=ol
self.top = ot
noStroke()
fill(self.foreColor)
textMode(CENTER)
text(self.text, self:width()/2, self:height()/2)
popStyle()
setContext()
if self.parent then
--set up clipping for window again
clip(self.parent.pos.x,self.parent.pos.y,
self.parent.size.x+self.parent.borderSize/2,self.parent.size.y)
else
clip()
end
--sprite(img,self.screenpos.x, self.screenpos.y)
pushMatrix()
m:addRect(0,0,img.width, img.height)
m.texture=img
--[[
m.shader.freq=1
m.shader.time=ElapsedTime
m.shader.vAmbientMaterial = .8
m.shader.vDiffuseMaterial = 1
m.shader.vSpecularMaterial = .5
m.shader.lightColor = color(255, 255, 255, 255)
m.shader.mView = viewMatrix()
m.shader.mProjection = projectionMatrix()
m.shader.mModel = modelMatrix()
m.shader.vEyePosition = vec4(10,10,-20,0)
lightPosition = vec4(-200,-400,-200,0)
m.shader.vLightPosition = lightPosition]]
translate(self.screenpos.x+self:width()/2, self.screenpos.y+self:height()/2)
m:draw()
m=nil
img=nil
popMatrix()
fill(self.foreColor)
textMode(CENTER)
--text(self.text, self:width()/2, self:height()/2)
popStyle()
if self.resizer ~= nil then self.resizer:draw() end
end
function TextButton:touched(touch)
Control.touched(self,touch)
--support legacy / non windowed feature
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 --we touched this control. YES, we checked twice...
if touch.state == ENDED then --press ended
if self.onClicked ~= nil then self.onClicked(self) end
self.pressed = false
else
self:onSelected()
self.pressed = true
end
end
end
function TextButton:onLostFocus()
self.pressed = false
end
function TextButton:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
return props
end
--# Switch
Switch = class(Control)
-- Switch 2.0
-- =====================
-- Designed for use with Cider controls
-- two-position selector
-- =====================
-- 1.3 cosmetic changes, refactored, based on Control
function Switch:init(params)
params.controlType="Switch"
Control.init(self, params)
self.itemText = {} -- going to depend non ipairs this time...
self:splitText()
end
function Switch:splitText()
local i, k
i = 0
for k in string.gmatch(self.text,"([^;]+)") do
i = i + 1
self.itemText[i] = k
end
end
--unsed code - attempted to draw gradients, etc differently.
function Switch:draw2()
local posx
pushStyle()
Control.draw(self)
clip(self.screenpos.x, self.screenpos.y, self:width(), self:height())
h = self:height()
stroke(self.backColor)
strokeWidth(h)
fill(self.backColor)
-- rect(self.screenpos.x, self.screenpos.y, self:width(), self:height())
local r= h/2 --half height up for drawing.
fill(self.backColor)
stroke(self.backColor)
ellipseMode(CORNER)
insetPos = vec2(self.screenpos.x + r, self.screenpos.y + r)
insetSize = vec2(self.screenpos.x+self:width() - r, self.screenpos.y+r)
ellipse(insetPos.x, insetPos.y, self:width(), self:height())
fill(0, 0, 0, 0)
stroke(0, 0, 0, 0)
insetPos = vec2(self.screenpos.x + r, self.screenpos.y + r)
insetSize = vec2(self.screenpos.x+self:width() - r, self.screenpos.y+r)
line(insetPos.x, insetPos.y, insetSize.x, insetSize.y)
if self.selected then
--only draw if it's ON
stroke(255, 255, 255, 125)
insetPos = vec2(self.screenpos.x + r*2, self.screenpos.y )
insetSize = vec2(self.screenpos.x+self:width() , self.screenpos.y)
line(insetPos.x, insetPos.y, insetSize.x, insetSize.y)
end
--draw button header
if self.selected then
posx = self.right - h
else
posx=self.left
end
noStroke()
stroke(self.borderColor)
fill(self.foreColor)
ellipse(posx, self.screenpos.y+self.size.y-h, h )
strokeWidth(1)
ellipse(posx, self.screenpos.y+self.size.y-h, h)
fill(self.foreColor)
if self.selected then chkVal = 0 else chkVal = 1 end
if #self.itemText > chkVal then
--fill(self.backColor)
--text(self.itemText[chkVal+1], self:midX()-1, self:midY()-1)
fill(self.foreColor)
text(self.itemText[chkVal+1], self:midX(), self:midY())
end
clip()
popStyle()
end
function Switch:draw()
Control.draw(self)
pushStyle()
font(self.fontname)
fontSize(self.fontsize)
strokeWidth(self.borderWidth)
if self.selected then
stroke(self.highlight)
fill(self.highlight)
else
fill(self.backColor)
stroke(self.backColor)
end
h = self:height()
self:roundRect(h/2+1)
self:roundRect(h/2)
--fill(0, 0, 0, 0) --ignored?
--stroke(self.borderColor) --overrides my background
self:roundRect(h/2+1)
--stroke(self.highlight)
--fill(self.highlight)
self:roundRect(h/2)
if self.selected then
posx = self.right - h
else
posx=self.left
end
noStroke()
--draw button header
stroke(self.borderColor)
fill(self.foreColor)
ellipse(posx, self.screenpos.y+self.size.y-h, h )
strokeWidth(1)
-- fill(self.knobColor)
fill(255, 255, 255, 148)
ellipse(posx, self.screenpos.y+self.size.y-h, h)
if self.selected then chkVal = 0 else chkVal = 1 end
if #self.itemText > chkVal then
--fill(self.backColor)
--text(self.itemText[chkVal+1], self:midX()-1, self:midY()-1)
fill(self.foreColor)
text(self.itemText[chkVal+1], self:midX(), self:midY())
end
popStyle()
end
function Switch: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
if touch.state == BEGAN then
self.selected = not self.selected
if self.onClicked ~= nil then self.onClicked(self) end
end
end
end
function Switch:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
return props
end
function Switch:onPropertyChanged()
self.itemText={}
self:splitText()
end
--# Menu
--captureTab("Menu.lua", captureCode(1))
----
--# Menu
--taken from jvm38's code base to enable features off of the control panel
-- heavily culled to operate a a windo menu ONLY
Menu = class(Control)
function Menu:init(args)
args.controlType="Menu"
Control.init(self, args)
-- actions
-- layout
self.title = args.title or "Menu"
self.list, self.disabled = {}, {}
self.action = args.action or {} -- set up menu to call different functions
local sourcelist = args.list or {"choice1",true,"choice2",true,"choice3",true}
-- manage various drawing states
self.deployed = false -- menu opened...
self.highlighted = nil -- the choice highlighted
self.selected = args.selected or -1
self.img = {} --images of the menu items
self.position = {} --array of menu positions
self:BuildMenuItems(sourcelist)
--self:BuildMenuImages() --creates the images
self:BuildMenuPositions()
end
function Menu:BuildMenuItems(list)
--parse through the "option, true" list to get options only for our lists.
local imax = #list/2
for i=1,imax do
self.list[i] = list[2*i-1]
self.disabled[i] = not list[2*i]
end
end
--[[
function Menu:BuildMenuImages()
--create text images
self.img["title"] = self:calcImg(self.title,self.size.x,self.size.y)
for i,txt in ipairs(self.list) do
self.img[i] = self:calcImg(txt,self.size.x, self.size.y)
end
end
]]--
function Menu:BuildMenuPositions()
local titlepos, titlesize = self.parent:GetTitleBarExtents()
--4 is probably borderwidth...
local startposition = vec2(self:width()/2+4, self.parent.size.y-self.size.y)
self.position["title"] = startposition
local x,y = startposition.x,startposition.y
local dx,dy = 0, -self.size.y-2 -- menu item position deltas
for i,img in ipairs(self.list) do --self.img
x,y = x+dx,y+dy
self.position[i] = vec2(x,y)
end
end
function Menu:doNothing()
-- do nothing
end
--creates an image from the text of the menu.
-- this was needede in the original becuase the menu was able to be rotated.
--we don't do that here,so we might tear this out later and draw directly with text()
function Menu:calcImg(txt,w,h)
pushStyle() pushMatrix()
font(self.fontname)
fontSize(self.fontsize)
stroke(self.foreColor)
fill(self.foreColor)
local w0,h0 = w,h
local img0 = image(w0,h0)
setContext(img0)
rectMode(CENTER)
textMode(CENTER)
strokeWidth(1)
background(self.backColor)
text(txt,w0/2,h0/2)
setContext()
popMatrix() popStyle()
return img0
end
--render menu
function Menu:draw()
Control.draw(self)
self:BuildMenuPositions() -- when we resize the menu moves. this fixes that.
pushStyle()
spriteMode(CENTER)
-- menu title
if self.deployed then
fill(self.highlight) else fill(255, 255, 255, 255)
end
--AC: we really need to draw a big box around the rest...
-- menu title position
local screen = self.position["title"] + self.parent.pos
--sprite(self.img["title"],
-- screen.x, screen.y-self.size.y+4,
-- self:width(),self:height())
rectMode(CENTER)
fill(self.backColor)
rect(screen.x, screen.y-self.size.y+4, self:width(),self:height())
fill(self.foreColor)
text(self.title, screen.x, screen.y-self.size.y+4)
-- menu items rendered
if self.deployed then
local isel = self.selected
for i,img in ipairs(self.list) do --self.img
pushStyle()
local screen = self.position[i] + self.parent.pos
local c = self.backColor
if self.disabled[i] then c=color(72, 72, 72, 255)
elseif i==isel then c=color(255,255,255,255)
else c = self.highlight end
-- sprite(img,
-- screen.x, screen.y-self.size.y,
-- self:width(),self:height())
fill(c)
rect(screen.x, screen.y-self.size.y+4, self:width(),self:height())
fill(self.foreColor)
text(self.list[i], screen.x, screen.y-self.size.y)
popStyle()
end
end
-- draw a borderline
strokeWidth(2)
stroke(self.backColor)
noSmooth()
local bottom = vec2(screen.x-self:width()/2, screen.y-self:height()-12)
--line( bottom.x , bottom.y,
-- screen.x+self.parent:width(),screen.y-self:height()-12)
popStyle()
end
-- allow uers to add items to the menu. note no removeitem...
-- untested buggy
function Menu:AddItem(item, callback)
table.insert(self.list,item)
table.insert(self.action, callback)
-- recalc the entire menu.
self:BuildMenuItems(self.list)
self:BuildMenuImages() --creates the images
self:BuildMenuPositions()
end
-- did we touch the menu to open it?
function Menu:titleTouched(touch)
assert(self.parent ~= nil, "bad parent for this menu")
local titlepos, titlesize = self.parent:GetTitleBarExtents()
assert(self.position["title"].y ~= nil, "bad ypos for this menu")
local goodZone = false
if math.abs((touch.y-titlepos.y))<self.position["title"].y then
goodZone = true
end
self.choiceDone = false
return goodZone
end
-- did we touch any items on the menu?
function Menu:selectTouched(touch)
for i,v in ipairs(self.position) do
local screeny = self.position[i].y + self.parent.pos.y
if touch.y<screeny and not self.disabled[i] then
self.selected = i
self.choiceDone=true
end
end
end
function Menu:touched(touch)
Control.touched(self, touch)
if touch.state == MOVING then
if self.deployed then
self:selectTouched(touch)
end
end
if touch.state == BEGAN and self.deployed ==false then
self.deployed=true
self.initialSelect = self.selected
end
if touch.state == ENDED and self.deployed then
if self.choiceDone then
local action = self.action[self.selected]
if action ~= nil then action(self) end
end
self.deployed=false
self.selected=-1
end
end
-- override ptIn for custom checks.
function Menu:ptIn(vec)
local titlepos, titlesize = self.parent:GetTitleBarExtents()
if self.deployed then
if vec.x >= titlepos.x and vec.x <= titlepos.x+self.size.x then
if vec.y >= titlepos.y - self.size.y*(#self.list+1) and
vec.y <= titlepos.y then
return true
end
end
end
if vec.x >= titlepos.x and vec.x <= titlepos.x+self.size.x then
if vec.y >= titlepos.y - WB_SIZE
then
return true
end
end
return false
end
--not actually called...?
function Menu:onDesignMode()
log("menu:ondesignmode")
--self:BuildMenuItems(sourcelist)
--self:BuildMenuImages() --creates the images
--self:BuildMenuPositions()
end
function Menu:onMoving()
--if touch.state == MOVING then
if self.deployed then
self:selectTouched(touch)
end
--end
end
function Menu:Save()
--
end
---probably not used...
function Menu:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
return props
end
--# ColorPicker
ColorPicker = class(Window)
function ColorPicker:init(params)
Window.init(self, params)
self.sliders={}
self.startColor = params.startColor or color(127, 127, 127, 255)
self.DesignMode = false
--what to call after we click Ok
self.onSubmit = params.onSubmit or ColorPicker.onSubmit
if self.size.y < 300 then self.size.y=300 end
for x = 0,3 do
if x==0 then v=self.startColor.r end
if x==1 then v=self.startColor.g end
if x==2 then v=self.startColor.b end
if x==3 then v=self.startColor.a end
params = {id="slider"..x, text="", min=0, max=255, val=v,
pos=vec2(10, 45 + (x*50)),fontsize=12,size=vec2(400,25),
foreColor=color(182, 52, 52, 255),fontname="ArialMT",
fixed=true,immutable=true,
onMoving=function() end}
sl=Slider(params)
table.insert(self.sliders, sl)
self:addControl(sl)
end
params = {id="btnColorPicker", text="Select",
pos=vec2(88, 4),fontsize=12,size=vec2(64,24),
foreColor=color(182, 52, 52, 255),fontname="ArialMT", zOrder=5,
fixed=true,immutable=true,
-- this works, but is longer to write.
--onClicked=function() doNothing() end
}
--easier to trace and handle events here...
local b=TextButton(params)
b.onClicked=function() self.onSubmit() end
self:addControl(b)
--cancel
params = {id="btnColorCancel", text="Cancel",
pos=vec2(243, 4),fontsize=12,size=vec2(64,24),
foreColor=color(182, 52, 52, 255),fontname="ArialMT", zOrder=5,
fixed=true,immutable=true,
onClicked=function() self.windowState = WS_HIDDEN end
}
local cancel=TextButton(params)
self:addControl(cancel)
end
function ColorPicker:draw()
self.backColor=self:getCurrentColor()
Window.draw(self)
end
function ColorPicker:Show(ctl, c)
self.caller = ctl
wh:setActiveWindow(self)
self:setColor(c)
self.windowState = WS_NORMAL
for k,v in pairs(self.controls) do
v.pressed = false
end
end
function ColorPicker:touched(touch)
Window.touched(self,touch)
return true
end
function ColorPicker:setColor(c)
self.sliders[1].val =c.r
self.sliders[2].val=c.g
self.sliders[3].val=c.b
self.sliders[4].val=c.a
self.startColor = c
end
function ColorPicker:getCurrentColor()
return color(
self.sliders[1].val,
self.sliders[2].val,
self.sliders[3].val,
self.sliders[4].val)
end
-- create an onfinished event to return the value?
function ColorPicker:onSubmit()
-- default behavior if none supplied
winColorPicker.windowState = WS_HIDDEN
winColorPicker.caller.backColor=winColorPicker:getCurrentColor()
end
--# ModalDialog
ModalDialog = class(Window)
function ModalDialog:init(params)
Window.init(self, params)
--only local stuff goes in here. Dialog creation stuff has to be sent as params
--so window:init() gets them.
self.title = nil --force it to nil
self.windowState = WS_NORMAL --WS_MAXIMIZED not working yet...
--create a lbl control
local params = {id="lbl1", zOrder=1,
backColor=color(212, 160, 107, 255),
foreColor=color(0, 0, 0, 255),
pos=vec2(WIDTH/4,HEIGHT/4),
size=vec2(WIDTH/2,HEIGHT/2),
text=self.text, wrapWidth=WIDTH/2,
textAlign=CENTER, vertAlign=CA_MIDDLE,
fontsize=self.fontsize}
self.lbl = LabelControl(params)
self:addControl(self.lbl)
--OK
params = {id="btnClose", text="OK",
pos=vec2(WIDTH/2-15,self.lbl.pos.y+15),
fontsize=12,size=vec2(64,48),
foreColor=color(182, 52, 52, 255),fontname=SYSTEM_DEFAULTFONT, zOrder=90,
fixed=true,immutable=true,
onClicked=function()
self.windowState = WS_HIDDEN
if wh then wh:clearActiveControl() end
self:close()
end
}
local cancel=TextButton(params)
self:addControl(cancel)
end
function ModalDialog:draw()
--force clip() to override everything!
clip()
fill(self.lbl.backColor)
stroke(self.borderColor)
strokeWidth(self.borderWidth)
rect(self.lbl.pos.x, self.lbl.pos.y,self.lbl:width(),self.lbl:height())
Window.draw(self)
end
function ModalDialog:touched(touch)
Window.touched(self,touch)
--if we touched the label, let go of it immediately. we don't need it.
if wh then
wh:clearActiveControl()
end
return true --eat all touches
end
function ModalDialog:setText(newText)
local ctl = self:getControlByName("lbl1")
ctl.text = newText
end
--# CheckBox
CheckBox = class(Control)
-- CheckBox 2.0
-- =====================
-- Designed for use with the Cider2 interface designer
-- A two-state inducator that alters state on touch
-- =====================
function CheckBox:init(params)
params.controlType="CheckBox"
Control.init(self, params)
self.onChecked = params.onChecked or Control.onChecked
end
function CheckBox:draw()
Control.draw(self)
pushStyle()
ellipseMode(CENTER)
noStroke()
fill(self.backColor)
---fill(0,0,0,255)
ellipse(self.screenpos.x , self.screenpos.y+self.size.y/2, self.size.y)
if self.selected then
fill(self.foreColor.r, self.foreColor.g, self.foreColor.b, 99)
ellipse(self.screenpos.x , self.screenpos.y+self.size.y/2, self.size.y-5)
fill(self.foreColor)
ellipse(self.screenpos.x , self.screenpos.y+self.size.y/2, self.size.y-5)
fill(255, 255, 255, 53)
ellipse(self.screenpos.x , self.screenpos.y+self.size.y/2 + 5, 20, 10)
fill(0, 0, 0, 33)
ellipse(self.screenpos.x, self.screenpos.y+self.size.y/2 - 6, 20, 10)
else
fill(0, 0, 0, 80)
ellipse(self.screenpos.x , self.screenpos.y+self.size.y/2, self.size.y)
end
fill(self.foreColor)
textAlign(CENTER)
textMode(CENTER)
text(self.text, self.screenpos.x+self.size.y*2, self.screenpos.y+self.size.y/2)
popStyle()
end
function CheckBox:touched(touch)
Control.touched(self, touch)
--support legacy / non windowed feature
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 --we touched this control. YES, we checked twice...
if touch.state == BEGAN then
self.selected = not self.selected
if self.onClicked ~= nil then self.onClicked(self) end
end
end
end
function CheckBox:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
table.insert(props, {"selected", "CheckBox"})
return props
end
--# CheckBox
CheckBox = class(Control)
-- CheckBox 2.0
-- =====================
-- Designed for use with the Cider2 interface designer
-- A two-state inducator that alters state on touch
-- =====================
function CheckBox:init(params)
params.controlType="CheckBox"
Control.init(self, params)
self.onChecked = params.onChecked or Control.onChecked
end
function CheckBox:draw()
Control.draw(self)
pushStyle()
ellipseMode(CENTER)
noStroke()
fill(self.backColor)
---fill(0,0,0,255)
ellipse(self.screenpos.x , self.screenpos.y+self.size.y/2, self.size.y)
if self.selected then
fill(self.foreColor.r, self.foreColor.g, self.foreColor.b, 99)
ellipse(self.screenpos.x , self.screenpos.y+self.size.y/2, self.size.y-5)
fill(self.foreColor)
ellipse(self.screenpos.x , self.screenpos.y+self.size.y/2, self.size.y-5)
fill(255, 255, 255, 53)
ellipse(self.screenpos.x , self.screenpos.y+self.size.y/2 + 5, 20, 10)
fill(0, 0, 0, 33)
ellipse(self.screenpos.x, self.screenpos.y+self.size.y/2 - 6, 20, 10)
else
fill(0, 0, 0, 80)
ellipse(self.screenpos.x , self.screenpos.y+self.size.y/2, self.size.y)
end
fill(self.foreColor)
textAlign(CENTER)
textMode(CENTER)
text(self.text, self.screenpos.x+self.size.y*2, self.screenpos.y+self.size.y/2)
popStyle()
end
function CheckBox:touched(touch)
Control.touched(self, touch)
--support legacy / non windowed feature
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 --we touched this control. YES, we checked twice...
if touch.state == BEGAN then
self.selected = not self.selected
if self.onClicked ~= nil then self.onClicked(self) end
end
end
end
function CheckBox:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
table.insert(props, {"selected", "CheckBox"})
return props
end
--# PropertyBuilder
PropertyBuilder = class(Window)
function PropertyBuilder:init(params)
Window.init(self, params)
self.DesignMode = false
self.rootTitle = self.title
--what to call after we click Ok
self.onSubmit = params.onSubmit or PropertyBuilder.onSubmit
end
function PropertyBuilder:draw()
if self.windowState == WS_NORMAL then Window.draw(self) end
end
--ask the control for wahat properties we can mdoify. table is k,v pairs.
function PropertyBuilder:CreatePropertyControls()
if self.caller.DesignMode == false then return end
local offsetX = 0
self.controls = {}
self:CreateDefaultControls()
local i=1
prop = self.caller:getProperties()
for _, template in pairs(prop) do
local controlWidth = 32
local controlHeight = 32
local row = math.ceil(i /8) *8 --use later
if i > 8 then offsetX = 225 offsetY = i-8 else offsetX = 0 offsetY = i end
local property = template[1] --backcolor, etc.
local controlType = template[2] --controltype
local stub = Creator[controlType] --create a type of control
local pos = vec2(offsetX+90, self.size.y - self.titleBarSize - (offsetY*40))
local myCtl=stub(pos) --create the template control
myCtl.id = property --assign the property to the ID of the ctl
myCtl.fixed = true --do not allow dragging thse controls, must
myCtl.immutable = true -- set both properties
if controlType == "TextButton" then
myCtl.size = vec2(controlHeight,controlHeight)
myCtl.backColor = self.caller[property] --show the color!
myCtl.foreColor = myCtl.backColor --set so that the foreColor doesn't override
myCtl.text = ""
end
if controlType == "CheckBox" then
myCtl.size = vec2(controlWidth,controlHeight)
myCtl.selected = self.caller[property] --show the value
myCtl.text = property
print(self.caller[property] ) --show
end
if controlType == "TextInput" then
myCtl.size = vec2(controlWidth*4,controlHeight)
--get the text prop. (ID, text, etc)
--AC: HACK!!!! Numbers are represented as strings!
myCtl:setText(tostring(self.caller[property]))
end
if controlType == "DropList" then
myCtl.size = vec2(controlWidth*4, controlHeight)
if property == "fontname" then --AC: HACK!!! exception due to large list
myCtl.size=vec2(controlWidth*4, controlHeight/1.35)
end
myCtl:setText(template[3]) --that's right, an addition param!
--see if we can find it
--look through the string, the 'indexed' item == selected.
for k,v in ipairs(myCtl.itemText) do
if v == tostring(self.caller[property]) then
myCtl.selected = k
myCtl.onSelected(myCtl)
end
if property=="parent" and self.caller[property] ~= nil then
if v == tostring(self.caller[property].id) then
myCtl.selected = k
myCtl.onSelected(myCtl)
end
end
end
myCtl.zOrder = 60 --popover.
end
myCtl.parent=self
self:addControl(myCtl)
--add a caption control for each property
pos = myCtl.pos - vec2(75,0)
local caption = Creator["LabelControl"](pos)
caption.id = myCtl.id.."_caption"
caption.text = property
caption.size = vec2(80,controlHeight)
caption.wrapWidth = 200
caption.fixed = true --do not allow dragging thse controls, must
caption.immutable = true -- set both properties
caption.parent=self
self:addControl(caption)
--pop up a window if picking colors.
if controlType == "TextButton" then
myCtl.onClicked = function()
wh:GetColorPicker():Show(myCtl, myCtl.backColor)
end
end
i=i+1
end
end
function PropertyBuilder:CreateDefaultControls()
params = {id="btnPropertyBuilder", text="Select",
pos=vec2(132, 4),fontsize=12,size=vec2(64,24),
foreColor=color(182, 52, 52, 255),fontname=SYSTEM_DEFAULTFONT, zOrder=5,
fixed=true,immutable=true,
}
--easier to trace and handle events here...
local b=TextButton(params)
b.parent=self
b.onClicked=function() self.onSubmit()
wh:setActiveControl(winPropertyBuilder.caller)
end
self:addControl(b)
--cancel
params = {id="btnPropertyCancel", text="Cancel",
pos=vec2(325, 4),fontsize=12,size=vec2(64,24),
foreColor=color(182, 52, 52, 255),fontname=SYSTEM_DEFAULTFONT, zOrder=5,
fixed=true,immutable=true,
onClicked=function()
wh:setActiveControl(winPropertyBuilder.caller)
self.windowState = WS_HIDDEN
end
}
local cancel=TextButton(params)
cancel.parent=self
self:addControl(cancel)
--cancel
params = {id="btnPropertyDelete", text="DELETE",
pos=vec2(4, 4),fontsize=12,size=vec2(64,24),
foreColor=color(182, 52, 52, 255),fontname=SYSTEM_DEFAULTFONT, zOrder=5,
fixed=true,immutable=true,
onClicked=function()
self.windowState = WS_HIDDEN
winPropertyBuilder.caller.parent:removeControl(winPropertyBuilder.caller)
local rz = winPropertyBuilder.caller.parent:getControlByName(
winPropertyBuilder.caller.id .. "_ctlresizer")
if rz ~= nil then
winPropertyBuilder.caller.parent:removeControl(rz)
end
end
}
if winPropertyBuilder.caller.parent~=nil then --can't "delete" windows
local delete=TextButton(params)
delete.parent=self
self:addControl(delete)
end
end
--this is the main entrance to the dialog window.
function PropertyBuilder:Show(ctl)
if ctl.DesignMode == false then return end
self.caller = ctl
self.title = self.rootTitle .. " for " .. self.caller.id
wh:setActiveWindow(self)
self.windowState = WS_NORMAL
--if any ctls were pressed before, unpress them.
for k,v in pairs(self.controls) do
v.pressed = false
end
self:CreatePropertyControls()
end
function PropertyBuilder:touched(touch)
if self.windowState== WS_NORMAL then
Window.touched(self,touch)
return true
end
end
function PropertyBuilder:setProperties()
if winPropertyBuilder.caller== nil then return end
--make the target control active
wh:setActiveControl(winPropertyBuilder.caller)
--assign properties back to controls
for k,v in pairs(winPropertyBuilder.controls) do
--v is a dialog control. get it's "value" to assign
if v.controlType == "TextButton" then
winPropertyBuilder.caller[v.id] = v.backColor
end
if v.controlType == "Checkbox" then
winPropertyBuilder.caller[v.id] = v.selected
end
if v.controlType == "TextInput" or v.controlType == "DropList" then
if v.id ~= "parent" then --special case
--if we had a number have to convert it back...
vlu = tonumber(v.text)
if vlu ~= nil then
winPropertyBuilder.caller[v.id] = vlu
else
winPropertyBuilder.caller[v.id] = v.text
end
else
if v.text ~= "nil" then
--we have to MOVE this control in the window list
oldP = winPropertyBuilder.caller.parent
p = wh:findWindow(v.text)
--if p==nil then
-- --assume a control container
-- p = oldP:getControlByName(v.text)
-- p:addControl(winPropertyBuilder.caller)
--else
if oldP ~= p then
p:addControl(winPropertyBuilder.caller)
oldP:removeControl(winPropertyBuilder.caller)
--kill the resizer
c = oldP:getControlByName(winPropertyBuilder.caller.id .."_ctlresizer")
oldP:removeControl(c)
end
--end
end
end
end
--mybe in the future. for nw, just one mesage at onSubmit
-- winPropertyBuilder.caller:onPropertyChanged(v.id)
end
end
function PropertyBuilder:getProperties(ctl)
end
function PropertyBuilder:onSubmit()
-- default behavior if none supplied
winPropertyBuilder.windowState = WS_HIDDEN
winPropertyBuilder.setProperties()
winPropertyBuilder.caller:onPropertyChanged()
end
--# DialControl
-- Added 12/4 from Mark on Codea forum.
-- Dial display control
Dial = class(Control)
--assert(Control ~= nil, "CiderControls not found. Is this tab in front of CiderControls?")
function Dial:init(params)
params.controlType="Dial"
Control.init(self, params)
self.min = min or 0
self.max = max or 100
self.val = val or 50
--just the needle color, not the same as doughnut.
self.hotColor = params.highlight or color(195, 195, 195, 255)
end
function Dial:draw()
Control.draw(self)
--in Utils:MiscUtils class
--AC: There's a "peg" so let's allow going "over" a bit
self.val = clamp(self.val, self.min, self.max + self.max * .08)
local i, x, w, h
pushStyle()
pushMatrix()
ellipseMode(CENTER) --special, needs ctr
fontSize(self.fontsize)
strokeWidth(3)
stroke(self.foreColor)
fill(self.backColor)
strokeWidth(1)
translate(self.left, self.top)
translate(self.size.x/2, self.size.y/2)
ellipse(0, 0, self:width())
fill(self.highlight)
ellipse(0, 0, self:width() - 10)
strokeWidth(2)
rotate(180)
strokeWidth(3)
for i=0,10 do
rotate(-30)
line(0, self:width() / 2 - 20, 0, self:width() / 2 - 10)
x = (self.max - self.min) / 10 * i
fill(self.foreColor)
text(x, 0, self:width() / 2 - 30)
end
fill(self.backColor)
rotate(-30)
ellipse(0, self:width() / 2 - 15, 10)
strokeWidth(4)
x = (self.val / (self.max-self.min)) * 300 + 30
rotate(-x)
line(0, 0, 0, self:width() / 2 - 20)
strokeWidth(1)
fill(self.hotColor)
ellipse(0, self:width() / 2 - 20, 10)
fill(self.foreColor)
ellipse(0, 0, 20)
w, h = textSize(self.val)
--place text value in the upper center of the dial.
resetMatrix()
translate(self.screenpos.x, self.screenpos.y)
translate(self.size.x/2, self.size.y/2)
fontSize(self.fontsize + 2)
text(self.val, 0, h*2)
--------
fill(255, 255, 255, 40)
noStroke()
ellipse(self:midX() - 10, self:midY() + 15, self:width() - 30)
popMatrix()
popStyle()
end
function Dial:touched(touch)
Control.touched(self, touch)
if self.parent.DesignMode then return end
self.val=self.val+1
if self.onMoving ~= nil then self.onMoving(self) end
end
function Dial:getProperties()
props = {}
props = Control:getProperties(self)
--table.insert(props, {"warmColor", "TextButton"})
table.insert(props, {"hotColor", "TextButton"})
table.insert(props, {"val", "TextInput"})
table.insert(props, {"min", "TextInput"})
table.insert(props, {"max", "TextInput"})
return props
end
-- Added 12/5 from Mark on Codea forum.
-- doughnut display control
Doughnut = class(Control)
function Doughnut:init(params)
params.controlType="Doughnut"
Control.init(self, params)
self.min = min or 0
self.max = max or 100
self.val = val or 50
self.intervals = 15
self.coolColor = color(0, 255, 107, 255)
self.warmColor = color(247, 244, 22, 255)
self.hotColor = color(247, 25, 18, 255)
self.warm = 10
self.hot = 14
end
function Doughnut:draw()
Control.draw(self)
--in Utils:MiscUtils class
self.val = clamp(self.val, self.min, self.max)
local i, x, w, h
pushStyle()
pushMatrix()
ellipseMode(CENTER) --special, needs ctr
smooth()
noStroke()
translate(self.left, self.top)
translate(self.size.x/2, self.size.y/2)
fontSize(self.fontsize)
--render 3 shadows and the main color
--"center" shadow
fill(self.backColor)
ellipse(0, 0, self:width())
--upper left shadow
fill(255, 255,255,67)
ellipse(-5, 5, self:width()-10)
--lower right shadow
fill(0, 0,0,67)
ellipse(5, -5, self:width()-10)
--main body backcolor
fill(self.backColor)
ellipse(0, 0, self:width()-10)
strokeWidth(2)
stroke(self.foreColor)
fill(255, 255, 255, 74)
ellipse(0, 0, self:width() / 2 + 10)
noStroke()
stroke(self.foreColor)
w, h = textSize(self.val)
text(self.val, 0, h/2)
fill(self.highlight)
text(self.text, 0, -h/2)
rotate(180)
x = (self:width() / (self.intervals / 3.14) ) / 2
rotate(360 / self.intervals)
noStroke()
for i=1,self.intervals do
rotate(-360 / self.intervals)
stroke(self.foreColor)
if self.val >= (self.max-self.min) / self.intervals
* i + self.min then
fill(self.coolColor)
if i >= self.warm then
fill(self.warmColor)
end
if i >= self.hot then
fill(self.hotColor)
end
end
ellipse(0, self:width() / 4 + self:width() / 8, x)
end
popMatrix()
popStyle()
end
function Doughnut:touched(touch)
Control.touched(self, touch)
if self.parent.DesignMode then return end
self.val=self.val+1
if self.onMoving ~= nil then self.onMoving(self) end
end
function Doughnut:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"warmColor", "TextButton"})
table.insert(props, {"hotColor", "TextButton"})
table.insert(props, {"val", "TextInput"})
table.insert(props, {"min", "TextInput"})
table.insert(props, {"max", "TextInput"})
return props
end
--# DropList
--# DropList
DropList = class(Control)
-- DropList 2.0
-- =====================
-- Designed for use with the Cider interface designer
-- drop down menu item list
-- =====================
function DropList:init(params)
params.controlType="DropList"
Control.init(self, params)
self.itemText = {}
self.open = false
self.padding=4
--consider changing prperty name to listitmes?
self.selectText = params.text
dropdownIcon = mesh()
dropdownIcon.vertices = { vec2(-10,10), vec2(10,10), vec2(0,0)}
self.selected=1 --redundant if we use :newText()
self:splitText()
end
function DropList:splitText()
local i, k
i = 0
for k in string.gmatch(self.selectText,"([^;]+)") do
i = i + 1
self.itemText[i] = k
end
end
function DropList:draw()
Control.draw(self)
local i, t, h
pushStyle()
pushMatrix()
font(self.fontname)
fontSize(self.fontsize)
textMode(CORNER)
rectMode(CORNER)
stroke(self.borderColor)
strokeWidth(self.borderWidth)
fill(self.backColor)
rect(self.left-self.padding, self.top,
self.size.x+self.padding,self.size.y)
fill(self.foreColor)
text(self.itemText[self.selected],
self.left, self.top+self.padding)
--dropdown triangle
pushMatrix()
translate(self.right-self.padding-10, self.screenpos.y+8)
dropdownIcon:draw()
popMatrix()
strokeWidth(1)
noSmooth()
--line(self.right - 25, self.bottom,self.right - 25, self.top )
if self.open then
clip()
fill(self.backColor)
rect(self.left-self.padding,self.screenpos.y-self.size.y*#self.itemText,
self.size.x+self.padding, self.size.y*#self.itemText)
fill(self.foreColor)
for i, t in ipairs(self.itemText) do
--AC: HACK! PLAYING AROUND! This works! if we KNEW these were fonts..
--if #self.itemText > 16 then
-- font(t)
--end
text(t, self.left+self.padding,
self.screenpos.y - (i*self.size.y)+self.padding)
end
stroke(self.borderColor)
fill(self.foreColor)
line(self.left - self.padding, self.top - self.selected * self.size.y,
self.right - self.padding, self.top - self.selected * self.size.y)
line(self.left - self.padding, self.top - (self.selected * self.size.y) + self.size.y,
self.right - self.padding, self.top - (self.selected * self.size.y) + self.size.y)
--highlight bar
fill(self.highlight)
rect(self.left-self.padding, self.top - self.selected * self.size.y,
self.size.x+self.padding , self.size.y)
end
popMatrix()
popStyle()
end
function DropList:touched(touch)
Control.touched(self, touch)
--support legacy / non windowed feature
local msg
if self.parent then
if self.parent.DesignMode then self.isActive = false hideKeyboard() 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
local h
h = #self.itemText * self.size.y
if self:ptIn(vec2(touch.x, touch.y)) then
if not self.open then
if touch.state == BEGAN then
self.open = true
end
else
local curselected = math.floor((self.top - touch.y) / self.size.y)
if curselected > #self.itemText then curselected = #self.itemText end
if curselected > 0 then self.selected = curselected end
end
end
if touch.state == ENDED then
if self.onSelected ~= nil then self.onSelected(self) end
end
end
end
-- this control expands so we have to deal with that instead of assuming
--that the size.y is fixed.
function DropList:ptIn(vec)
if self.open then
if vec.x >= self.screenpos.x and vec.x <= self.screenpos.x+self.size.x then
if vec.y >= self.screenpos.y - self.size.y*(#self.itemText+1) and
vec.y <= self.screenpos.y then
return true
end
end
end
if vec.x >= self.screenpos.x and vec.x <= self.screenpos.x+self.size.x then
if vec.y >= self.screenpos.y and vec.y <= self.screenpos.y+self.size.y then
return true
end
end
return false
end
function DropList:setText(newText)
self.selectText = newText
self:splitText()
self.selected=1
self:onSelected()
end
function DropList:onSelected()
self.open = false
self.text = self.itemText[self.selected] --we are trashing the input this way...
end
function DropList:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"selectText", "TextInput"})
return props
end
function DropList:onPropertyChanged()
self:splitText()
end
--# Slider
Slider = class(Control)
-- Slider 1.3
-- =====================
-- Designed for use with Cider controls
-- offers option of sliding to 1-x values
-- =====================
-- 1.3 refactored, based on Control class
function Slider:init(params)
params.controlType="Slider"
Control.init(self, params)
self.min = params.min
self.max = params.max
self.val = params.val
end
--AC: sloppy. line is drawn at y=0 instead of being centered.
function Slider:draw()
Control.draw(self)
local x, y, scale
pushStyle()
font(self.fontname)
fontSize(self.fontsize)
stroke(self.foreColor)
fill(self.backColor)
h, w = textSize(self.max)
scale = (self.size.x - h * 2) / (self.max - self.min)
x = self.left + h + ((self.val - self.min) * scale)
y = self.screenpos.y
strokeWidth(15)
stroke(self.backColor)
line(self.screenpos.x + h, y, self.screenpos.x+self.size.x - h, y)
stroke(self.highlight)
line(self.screenpos.x + h, y, x, y)
stroke(255, 255, 255, 29)
strokeWidth(7)
line(self.screenpos.x + h, y +4, x, y + 4)
strokeWidth(3)
stroke(self.foreColor)
fill(self.highlight)
ellipse(x, y-10, 20)
stroke(self.foreColor)
h, w = textSize("Slider")
textMode(CENTER)
textAlign(LEFT)
text(self.min, self.screenpos.x, y)
textAlign(RIGHT)
text(self.max, self.screenpos.x+self.size.x, y)
textAlign(CENTER)
text(self.text, self.screenpos.x, y + h)
if self.val > self.min and self.val < self.max then
text(self.val, x, y + h / 2)
end
--rect(self.screenpos.x,self.screenpos.y,self.size.x,self.size.y)
popStyle()
end
function Slider:touched(touch)
Control.touched(self, touch)
if self.parent.DesignMode then return end
local x, scale
if touch.state == BEGAN or touch.state == MOVING then
if self:ptIn(vec2(touch.x, touch.y) ) then
x = touch.x - self.left - 10
scale = ((self.right - self.left) - 20) / (self.max - self.min)
self.val = math.floor(x / scale) + self.min
if self.val < self.min then
self.val = self.min
elseif self.val > self.max then
self.val = self.max
end
if self.onMoving ~= nil then self.onMoving(self) end
return true
end
end
end
function Slider:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
return props
end
--# Slider
Slider = class(Control)
-- Slider 1.3
-- =====================
-- Designed for use with Cider controls
-- offers option of sliding to 1-x values
-- =====================
-- 1.3 refactored, based on Control class
function Slider:init(params)
params.controlType="Slider"
Control.init(self, params)
self.min = params.min
self.max = params.max
self.val = params.val
end
--AC: sloppy. line is drawn at y=0 instead of being centered.
function Slider:draw()
Control.draw(self)
local x, y, scale
pushStyle()
font(self.fontname)
fontSize(self.fontsize)
stroke(self.foreColor)
fill(self.backColor)
h, w = textSize(self.max)
scale = (self.size.x - h * 2) / (self.max - self.min)
x = self.left + h + ((self.val - self.min) * scale)
y = self.screenpos.y
strokeWidth(15)
stroke(self.backColor)
line(self.screenpos.x + h, y, self.screenpos.x+self.size.x - h, y)
stroke(self.highlight)
line(self.screenpos.x + h, y, x, y)
stroke(255, 255, 255, 29)
strokeWidth(7)
line(self.screenpos.x + h, y +4, x, y + 4)
strokeWidth(3)
stroke(self.foreColor)
fill(self.highlight)
ellipse(x, y-10, 20)
stroke(self.foreColor)
h, w = textSize("Slider")
textMode(CENTER)
textAlign(LEFT)
text(self.min, self.screenpos.x, y)
textAlign(RIGHT)
text(self.max, self.screenpos.x+self.size.x, y)
textAlign(CENTER)
text(self.text, self.screenpos.x, y + h)
if self.val > self.min and self.val < self.max then
text(self.val, x, y + h / 2)
end
--rect(self.screenpos.x,self.screenpos.y,self.size.x,self.size.y)
popStyle()
end
function Slider:touched(touch)
Control.touched(self, touch)
if self.parent.DesignMode then return end
local x, scale
if touch.state == BEGAN or touch.state == MOVING then
if self:ptIn(vec2(touch.x, touch.y) ) then
x = touch.x - self.left - 10
scale = ((self.right - self.left) - 20) / (self.max - self.min)
self.val = math.floor(x / scale) + self.min
if self.val < self.min then
self.val = self.min
elseif self.val > self.max then
self.val = self.max
end
if self.onMoving ~= nil then self.onMoving(self) end
return true
end
end
end
function Slider:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
return props
end
--# WindowHandler
--WindowHandler
----
--The WindowHandler class manages all windows in the App.
WindowHandler = class()
--Init class to handle window collection. Keep simple.
function WindowHandler:init()
self.list = {}
self.activeWindow = nil --ID value, not a control
self.activeControl = nil --we we allow Drag and Drop between windows...
pushStyle()
self.background = image(WIDTH, HEIGHT)
setContext(self.background )
spriteMode(CORNER)
sprite("Documents:tex1",0,0, WIDTH, HEIGHT)
tint(255,255,255,128)
setContext()
popStyle()
end
function WindowHandler:draw()
--ALWAYS draw all windows. Let the window (for now) deteremine if it's hidden
--or minimized.
background(0,0,0)
--output.clear()
sprite(self.background,0,0,WIDTH,HEIGHT)
--draw the active control as a ghosted item when we are dragging
if CurrentTouch.state==MOVING and self.activeControl ~= nil then
pushStyle()
fill(184, 181, 181, 126)
rect(CurrentTouch.x, CurrentTouch.y,
self.activeControl:width(),self.activeControl:height())
--self.activeControl:drawPalette() --doesn't work, not sure why
popStyle()
end
for i,v in ipairs(self.list) do
v:draw()
end
end
function WindowHandler:touched(touch)
if touch.state~=BEGAN then
local tm = self:getActiveWindow()
--Be forgiving - if we are RESIZING or MOVING then ASSUME we are actively dealing with the
--current window. do so and leave. if we do not, dragging outside of the window
--will stop the drag for happening.
if tm ~= nil and (tm.action == WA_RESIZE or tm.action == WA_MOVING) then
tm:touched(touch) -- do not RETURN or else we have tap twice issues
--return
end
end
--A control is not a window, but a control like a droplist might go
--outside of its host window!
local c = self:getActiveControl()
if c ~= nil then
if c:ptIn(vec2(touch.x, touch.y)) then
c:touched(touch) --send droplist it's data
return
end
end
--send the touch on to the windows that are under the touchpoint.
--first one in the stack will get the message.
for idx = #self.list, 1, -1 do
local v = self.list[idx]
if v.windowState ~= WS_HIDDEN then
local msg = v:ptIn(vec2(touch.x, touch.y))
if msg then
v:touched(touch)
return
end
end
end
--- finally, deactivate any open active controls on untouch...
if touch.state == ENDED then
self:clearActiveControl()
self.activeWindow = nil
--wh:clearActiveWindow()
end
end
--Sorts the table by zOrder - so topmost should just be set zOrder 99
function WindowHandler:Sort(t)
table.sort(t,
function (x,y)
return x.zOrder < y.zOrder
end
)
end
--add a window to the Manager
function WindowHandler:addWindow(win)
table.insert(self.list,win)
self:Sort(self.list)
end
--Removes a window from the Manager
function WindowHandler:removeWindow(id)
for i,v in ipairs(self.list) do
if v.id == id then
table.remove(self.list,i)
v:close()
end
end
self:Sort(self.list)
end
function WindowHandler:findWindow(id)
for i,v in ipairs(self.list) do
if v.id == id then
return v
end
end
return nil
end
--returns a Control or nil, should use this instead of directly accessing
-- the property, so that immutable is valid.
function WindowHandler:getActiveControl()
if self.activeControl == nil then
return nil
else
return self.activeControl
end
end
--returns a Control or nil, should use this instead of directly accessing
-- the property, so that immutable is valid.
--returns the control sent in if it was activatable.
function WindowHandler:setActiveControl(ctl)
if ctl == nil then
return nil
else
if ctl.controlType == "Window" then
self:setActiveWindow(ctl.id)
return nil
else
if self.activeControl==nil or ctl.id ~= self.activeControl.id then
self:clearActiveControl() --will deactivate the old control if one existed
self.activeControl = ctl
if ctl.onGotFocus then ctl.onGotFocus(ctl) end
end
return self.activeControl
end
end
end
function WindowHandler:clearActiveControl()
if self.activeControl ~= nil then
if self.activeControl.onLostFocus then
self.activeControl.onLostFocus(self.activeControl)
end
self.activeControl = nil
self:Sort(self.list)
end
end
--this code is returning the Focused Window
function WindowHandler:getActiveWindow()
if self.activeWindow == nil then return end
for i,v in ipairs(self.list) do
if v.id == self.activeWindow then
return v
end
end
end
--same as a BringToTop()
--we are cheating by using the topmost variable as an int and a flag (later). right now
--it's an int ceiling.
function WindowHandler:setActiveWindow(id)
--we can assume theres' a valid window as we are passing an id...:)
local w = wh:getActiveWindow()
if w ~= nil and w.id~= id and w.zOrder < WP_TOPMOST then
w.zOrder = w.zOrder - 3 --wiggle room
end
for i,v in ipairs(self.list) do
if v.id == id then
self.activeWindow = v.id
v.action = WA_NORMAL --stop any pending resizing
if v.zOrder < WP_TOPMOST then v.zOrder = 25 end --AC: HACK. leave wiggle room.
end
end
self:Sort(self.list)
end
-- helper move later
function WindowHandler:GetColorPicker()
winColorPicker= wh:findWindow("ColorPicker")
if winColorPicker == nil then
--a / the? color picker window
winColorPicker = ColorPicker({title="Color Picker", pos=vec2(0,400),
size=vec2(425,220), id="ColorPicker", zOrder=150,
borderColor=color(57, 68, 131, 255), DesignMode=false,
toolbarButtons ={ WB_MINMAX=false, WB_CLOSE=false } ,
borderSize=1 , fixed = true, state=WS_HIDDEN })
self:addWindow(winColorPicker)
end
return winColorPicker
end
function WindowHandler:GetPropertyBuilder()
winPropertyBuilder = wh:findWindow("PropertyBuilder")
if winPropertyBuilder == nil then
winPropertyBuilder = PropertyBuilder({title="Property Builder",
pos=vec2(100,HEIGHT-400),
size=vec2(550,400), id="PropertyBuilder", zOrder=100,
borderColor=color(57, 68, 131, 255), DesignMode=false,
toolbarButtons ={ WB_MINMAX=false, WB_CLOSE=false } ,
borderSize=1 , fixed = true, state=WS_HIDDEN })
self:addWindow(winPropertyBuilder)
end
return winPropertyBuilder
end
--# TextInput
TextInput = class(Control)
--
function TextInput:init(params)
params.controlType="TextInput"
Control.init(self, params)
self.cursorpos = 0 --where the cursor is (character position)
self.linepos = 0
self.buffer = self.text
self.maxLen = 99
self.isActive = false --by default we are not the "focused" control for keybd
self.OverrideReturn = params.OverrideReturn or false --Override return key close.
end
function TextInput:draw()
Control.draw(self)
self:clip(self.screenpos.x, self.screenpos.y, self:width(), self:height())
--make sure we have a buffer ALWAYS
if self.buffer == nil then
self.buffer = self.text
end
if self.isActive == true then
if lastKey ~= nil then
if lastKey == BACKSPACE then
if self.cursorpos > 0 then
self.buffer = self.buffer:sub(1, self.cursorpos-1) ..
self.buffer:sub(self.cursorpos+1)
self.cursorpos = self.cursorpos - 1
end
else
--not a backspace, process on...
--AC: Allowing return as a character now.
if lastKey == RETURN and self.OverrideReturn == false then
if self.onSubmit ~= nil then self.onSubmit(self) end
else
self.buffer = self.buffer:sub(1, self.cursorpos) .. tostring(lastKey) ..
self.buffer:sub(self.cursorpos+1)
self.cursorpos = self.cursorpos + 1
end
end
--clear all keys that are processed.
lastKey = nil
end
end
local bufferSize = textSize(self.buffer)
pushStyle()
strokeWidth(self.borderWidth)
stroke(self.borderColor)
fill(self.backColor)
if self.isActive == true then strokeWidth(self.borderWidth +2) stroke(self.highlight) end
rect(self.screenpos.x,self.screenpos.y,self.size.x,self.size.y)
popStyle()
pushStyle()
fill(self.foreColor)
fontSize(self.fontsize)
font(self.fontname)
textMode(CORNER)
textAlign(self.vertAlign)
--textWrapWidth(self:width())
local w,h = textSize("b") --trying to get a good "average" word size
local approxChars = self.size.x / w * 1.25
self.buffer=reflow(self.buffer, approxChars, "", "") --AC: can add \n for double lines
text(self.buffer,self.screenpos.x+6, self.screenpos.y+4)
if self.isActive then
-- draw the cursor
if math.floor(ElapsedTime)%2 == 0 then
stroke(95, 85, 85, 255)
strokeWidth(2)
local iter=0
local counted = 0 --characters counted, running total
local myOffset = 0 --CHAR OFFSET BASED ON LINE
local myLine = 0 --LINE OFFSET BASED ON TEXT
for line in self.buffer:gmatch("[^\n]+") do
iter = iter + 1
if counted + string.len(line) >= self.cursorpos then
--we found the line with the characters.
myLine = self:GetTotalLines() - iter --inverted line meauring
--print("checking chars...")
--print("myLine=" .. myLine)
--go through te chars to get the position
local citer = 0
local ow = 0
for c in line:gmatch(".") do
citer = citer + 1
local cw,ch =textSize(c)
ow = ow + cw
if counted + citer == self.cursorpos then myOffset = ow end
end
--print("myOffset = " ..myOffset)
break --get out
else
counted = counted + string.len(line) + 1 --add the newline
--print(counted.."...")
end
end
--self:GetTotalLines()- self.linepos
local cy = self.screenpos.y+ ( myLine ) * h + 6 --padding
local cx = self.screenpos.x+ (myOffset) + self.borderWidth --pading
line (cx, cy, cx , cy + self.fontsize+ 4)
end
end
self:clip()
popStyle()
end
function TextInput:touched(touch)
Control.touched(self, touch)
local msg = false
--support legacy / non windowed feature
if self.parent then
if self.parent.DesignMode then self.isActive = false hideKeyboard() 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
if touch.state == BEGAN then
self:onGotFocus()
self:PositionCursor(self)
end
if touch.state == MOVING then
self:PositionCursor(self)
end
if touch.state == ENDED then
--commit my buffer just in case
self.text = self.buffer
self:PositionCursor(self) --redundant?
end
end
end
function TextInput:GetTotalLines()
local totalLines = 0
for line in self.buffer:gmatch("[^\n]+") do
totalLines = totalLines + 1
end
return totalLines
end
function TextInput:PositionCursor()
local w,h = textSize("b") --trying to get a good "average" word size
--figure out what "line" we touched
--AC: Inverted coord system "issue"
totalLines = self:GetTotalLines()
local tw,th = textSize(self.buffer)
if CurrentTouch.y - self.top > th then
self.linepos = 1 --touched above all our text
else
local offset = CurrentTouch.y - self.top
self.linepos = totalLines - math.floor(offset/h)
end
--find position of cursor
local iter = 0
local charpos = 0
local line
for line in self.buffer:gmatch("[^\n]+") do
iter = iter + 1
if self.linepos == iter then
--log(self.linepos)
--log("searching "..line)
--we know what line we touched
--now we have to figure out WHERE on that line...
local touchoffset = CurrentTouch.x - self.left --ex: 100px
--log("- touch pos "..touchoffset)
local tw,th = textSize(line)
---touchoffset = clamp(touchoffset, 0, tw) --can't be wider than tw
local ow = 0 -- overall width
local citer = 0
local ccharpos = 0 --this will accumulate until we get to our position.
for c in line:gmatch(".") do
citer = citer + 1
local cw,ch =textSize(c)
ow = ow + cw
--log(c..":".. ow .. ";".. touchoffset .. ":" .. ccharpos)
if touchoffset > ow then ccharpos = citer end
end
--log("- char pos "..ccharpos)
--log("+ total pos" .. ccharpos + charpos)
self.cursorpos = ccharpos + charpos
else
charpos = charpos + string.len(line) + 1 --re-add the CR!
end
end
end
--store the data from the buffer to the control
--typically a RETURN does this. this must be called to sync buffers if override is used.
function TextInput:onSubmit()
self.text = self.buffer --:sub(1, self.buffer:len()) --remove return key
self.isActive = false
if self.onLostFocus ~= nil then self.onLostFocus(self) end
end
------------------------------------------
---done because if the editor is already in process, then we cannot set or overide...
--and the editor is ALWAYS in process.
function TextInput:setText(newText)
self.text = newText
self.buffer = self.text
self.cursorpos = string.len(self.text)
end
function TextInput:getProperties()
props = {}
font()
props = Control:getProperties(self)
table.insert(props, {"buffer", "TextInput"})
--table.insert(props, {"OverrideReturn", "CheckBox"})
return props
end
function TextInput:onGotFocus()
self.isActive = true
showKeyboard()
end
--if we lose focus, hide the keyboard.
function TextInput:onLostFocus()
self.isActive = false
hideKeyboard()
end
--# LabelControl
LabelControl = class(Control)
--control alignment. usually used in LabelControl. Global values
CA_TOP = 1
CA_MIDDLE = 2
CA_BOTTOM = 3
function LabelControl:init(params)
params.controlType="LabelControl"
self.vertAlign = params.vertAlign or CA_MIDDLE
Control.init(self,params)
self.txtWrapWidth = params.wrapWidth or 500
end
function LabelControl:draw()
Control.draw(self)
clip(self.screenpos.x, self.screenpos.y, self:width(), self:height())
pushStyle()
fontSize(self.fontsize)
font(self.fontname)
textMode(CORNER)
textAlign(LEFT) --alignment for multiline text used with wrapWidth
textWrapWidth(self.txtWrapWidth)
--AC: Allow labels to have backcolor
fill(self.backColor)
rect(self.screenpos.x, self.screenpos.y, self:width(), self:height())
fill(self.foreColor)
local w, h = textSize(self.text)
local x
if self.textAlign == LEFT then
x = self.left + 4
elseif self.textAlign == CENTER then
x = self:midX() - w / 2
elseif self.textAlign == RIGHT then
x = self.right - w - 8
end
if self.vertAlign == CA_TOP then
text(self.text, x, self.top - h)
elseif self.vertAlign == CA_MIDDLE then
text(self.text, x, self:midY()- h/2)
elseif self.vertAlign == CA_BOTTOM then
text(self.text, x, self.bottom + 4)
end
clip()
popStyle()
end
--put it on, but not usually used
function LabelControl:touched(touch)
Control.touched(self, touch)
if self.parent.DesignMode then return end
if self.onClicked ~= nil then self.onClicked(self) end
end
------------------------------------------------
function LabelControl:setTextWrap(width)
self.txtWrapWidth = width
end
function LabelControl:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"text", "TextInput"})
return props
end
--# PictureControl
PictureControl = class(Control)
function PictureControl:init(params)
params.controlType="PictureControl"
Control.init(self,params)
self.lastTouched = vec2()
self.url = params.url
self.img = params.img or nil
self:onLoaded()
end
function PictureControl:draw()
Control.draw(self)
--ac: third time - should this go into Control:draw()?
--AC: issue here: if i am in a conainercontrol, i effectively clip myself out!
--AC: seems to have gotten fixed as this code is still here and we are not clipped out.
self:clip(self.screenpos.x, self.screenpos.y, self:width(), self:height())
pushStyle()
spriteMode(CORNER)
--if self.img == nil then print ("nil img!") end
if self.img ~= nil then
sprite(self.img,self.screenpos.x,self.screenpos.y,self.size.x,self.size.y)
else
if self.url ~= nil and self.url ~= "" then
self.img = readImage(self.url)
end
end
popStyle()
self:clip()
end
function PictureControl:touched(touch)
Control.touched(self,touch)
local msg
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 --we touched this control.
if touch.state == BEGAN then
self.lastTouched = vec2(touch.x, touch.y)
if self.onSelected ~= nil then self.onSelected(self) end
end
if touch.state == MOVING then
if self.onMoving ~= nil then self.onMoving(self) end
end
if touch.state == ENDED then
if self.onClicked ~= nil then self.onClicked(self) end
end
end
end
-- if we don't have an image, use then url propoerty to get one
function PictureControl:LoadPicture(img)
self.img = img
end
function PictureControl:getProperties()
props = {}
props = Control:getProperties(self)
table.insert(props, {"url", "TextInput"})
return props
end
function PictureControl:onGotFocus()
self.onLoaded(self)
end
function PictureControl:onLoaded()
if self.url ~= nil and self.url ~= "" then
local i = readImage(self.url)
self.img = i:copy()
end
end
function PictureControl:onPropertyChanged()
self.onLoaded(self)
end
--# Resizer
--
----
--Resizer - used to resize windows. a Window creates an instance of this and attaches it to
--itself. a window without this effectively cannot be resized by the UI.
Resizer = class(TouchBox)
--tab order!!!
assert (TouchBox ~= nil, "Touchbox class is not defined")
function Resizer:init(params)
params.controlType="Resizer"
TouchBox.init(self, params)
self.onBegan = Resizer.onBegan
self.onMoving = Resizer.onMoving
self.onEnded = Resizer.onEnded
self.immutable = true --cannot be stolen in design mode cannot resize itself...
self.fixed=true -- do i need this? added for clarity
self.zOrder=99 -- always on top
self.resizeMesh = mesh()
self.resizeMesh.vertices = { vec2(self.size.x,self.size.y), vec2(0,0), vec2(self.size.x,0)}
end
function Resizer:draw()
TouchBox.draw(self)
pushStyle()
pushMatrix()
translate(self.screenpos.x, self.screenpos.y)
self.resizeMesh:draw()
popMatrix()
popStyle()
self.lastPosition = self.screenpos
end
function Resizer:onBegan()
sound(DATA, "ZgNAbUcUQEBAQEBAAAAAAGRXtjzZoJc+QABAf0BTQEBAQEBA")
self.lastTouched = vec2(CurrentTouch.x, CurrentTouch.y)
wh:setActiveWindow(self.parent.id)
self.parent.action = WA_RESIZE
end
function Resizer:onMoving()
if self.lastTouched ~= nil then
--determine movement
local deltaTouch = self:getScreenPos() - vec2(CurrentTouch.x, CurrentTouch.y)
local delta = self.lastTouched - vec2(CurrentTouch.x, CurrentTouch.y)
if self.parent.size.y + delta.y > WB_SIZE + self.size.y and
self.parent.size.x - delta.x > self.size.x + 125 then --125 HACK! Button Size
self.pos.x = self.pos.x - delta.x
-- self.pos.y = self.pos.y + delta.y --NO! Keep Y fixed!
self.parent.pos.y = self.parent.pos.y - delta.y
self.parent.size.x = self.parent.size.x - delta.x
self.parent.size.y = self.parent.size.y + delta.y --adjust so ttlbar stays "fixed"
self.lastTouched = vec2(CurrentTouch.x, CurrentTouch.y)
else
--we just tried to do something illegal - get out of resize mode
self.parent.action = WA_NORMAL
end
else
self.lastTouched = vec2(CurrentTouch.x, CurrentTouch.y)
end
self.parent.action = WA_RESIZE --JIC
end
function Resizer:onEnded()
sound(DATA, "ZgNAOhcUQEBAQEBAAAAAAGRXtjzZoJc+LABAf0BTQEBAQEBA")
self.pos = vec2(self.parent.size.x-32, 0)
self.lastTouched = nil
self.parent.action = WA_NORMAL
wh:clearActiveControl()
end
function Resizer:onDesignMode()
local winColorPicker=wh:GetColorPicker()
winColorPicker:Show(self.parent, self.parent.backColor)
end
--# CreatePalette
function MakeCreator()
--make this global, other things will use, like the palette
-- fontname and fontsize are parameters we can passtoo
Creator={
--["Control"]=function(pos)
-- local params = {id="ctl", pos=pos, size=vec2(40,40), text="Control",
-- parent=win, fontsize=12}
-- return Control(params) end,
["ContainerControl"]=function(pos)
local params = {id="cnt", pos=pos, size=vec2(40,40), text="Container",
parent=win,canResize=true, fontsize=12,borderwidth=7,
backColor=color(255, 255, 255, 0), foreColor=color(0, 0, 0, 255)}
return ContainerControl(params) end,
["LabelControl"]=function(pos)
local params = {id="lbl1", pos=pos, size=vec2(90,40), text="Label",
wrapWidth=WIDTH/2, textAlign=CENTER, vertAlign=CA_MIDDLE,canResize=true,
parent=win, fontsize=12, backColor=color(0, 0, 0, 0)}
return LabelControl(params) end,
["PictureControl"] = function(pos)
local params = {id="pict1", pos=pos, size=vec2(40,40), text="PictureControl",
parent=win, fontsize=12, url="Documents:truck", canResize=true}
return PictureControl(params) end,
["Menu"] = function(x)
menulist={"Load",true,"Save",true,"Design",true,
"Generate", true, "DisplayMode", true, "CCTest", true, "About", true}
--AC:TODO: Will have to change these to strings so that they can be serialized...
action={Load,Save,Design,Generate,ShowHide,CCTest, About}
local menuparams={id="menu", pos=vec2(0,0),size=vec2(80,32), zOrder = 10,
fontsize=12, foreColor=color(255, 0, 0, 255),fontname=SYSTEM_DEFAULTFONT,
backColor=color(255, 255, 255, 255), parent=win, fixed=true,
list=menulist, action=action}
return Menu(menuparams)
end,
["DropList"] =function(pos)
local params = {id="ddl", pos=pos, size=vec2(80,32), text="DropList;1;2;3;4;5",
parent=win, fontsize=12, canResize=true }
return DropList(params) end,
["Slider"]=function(pos)
local params =
{id="slider", displayText="slider", min=0, max=100, val=50,
pos=pos,fontsize=12,size=vec2(128,32),
canResize=true,
mode=CENTER, align=LEFT, parent = win}
return Slider(params)
end,
["Switch"]=function(pos)
local params = {id="sw1", pos=pos, text="yes;No",
size=vec2(60,20), canResize=true,
backColor=color(36, 0, 255, 255), foreColor=color(255, 255, 255, 255),
borderColor=color(127, 127, 127, 255), highlight=color(255, 0, 0, 255),
parent=win, fontsize=18}
return Switch(params) end,
--["TouchBox"]=function(pos) return TouchBox(x) end,
--["Textbox2"]=function(pos)
-- local params = {id="tb", pos=pos, size=vec2(90,45), text="Textbox2",
-- parent=win, fontsize=12,canResize=true }
-- return Textbox2(params)
--end,
["TextInput"]=function(pos)
local params = {id="input", pos=pos, size=vec2(80,40),
text="Textbox",
backColor=color(197, 197, 197, 255), foreColor = color(0,0,0,255),
highlight = color(255, 0, 0, 255), borderColor=color(166, 94, 60, 255),
parent=win,fontsize=12, canResize=true, borderWidth=2, vertAlign=LEFT
}
return TextInput(params)
end,
["TextButton"]=function(pos)
local params = {id="button", pos=pos, size=vec2(80,40), text="Button",
parent=win, fontsize=12, canResize=true}
return TextButton(params)
end,
["Resizer"]=function(pos) --ignore any of htese! HACK
local params = {id="_resizer", pos=pos, size=vec2(40,40), text="RESIZER",
parent=win, fontsize=12}
return LabelControl(params)
end,
["CheckBox"]=function(pos)
local params = {id="chk1", pos=pos, size=vec2(90,20), text="checkbox",
parent=win, canResize=true, fontsize=12}
return CheckBox(params)
end,
["Doughnut"] = function(pos)
local params = {id="dnut", pos=pos, size=vec2(80,80), text="Dough",
min=1, max=100, val=50, backColor=color(145, 155, 223, 255),
parent=win,canResize=true, fontsize=12}
return Doughnut(params)
end,
["Dial"] = function(pos)
local params = {id="dial", pos=pos, size=vec2(80,80), text="Dial",
min=1, max=100, val=50, backColor=color(145, 155, 223, 255),
parent=win,canResize=true, fontsize=12}
return Dial(params)
end,
["GridControl"]=function(pos)
local params = {id="grid", pos=pos,
fontsize=12,
text="SAMPLE & LATEX \\ Format& in \\ text& string",
size=vec2(300,300), parent=win,
canResize=true,
style= "single",
parent=win,
cellsize=vec2(10,10) }
return GridControl(params)
end,
}
end
function CreatePalette()
MakeCreator()
-- need this global temporarily probably because we are going into functions nested deeply?
win = Window({title="Palette", pos=vec2(WIDTH-350,50),
size=vec2(350,HEIGHT-50), id="winPalette",DesignMode = false,
toolbarButtons ={ MINMAX=true,WINCLOSE=false} , GridSize = 10,
backColor=color(200, 200, 200, 255),
borderColor=color(57, 68, 131, 255),
borderSize=2, fixed=true, zOrder = 99})
wh:addWindow(win)
local ctl, i
i=0
iter = 6
for k,v in pairs(Creator) do
log("createpalette():"..k)
local x = math.floor(i/iter)*90
local mi, mr = math.modf(i/iter)
local y = (mr*iter)*90
pos = vec2(30+x,30+y)
ctl=v(pos)
ctl.id = ctl.id .. tostring(i) --math.random(1,100)
ctl.inPalette=true
if ctl ~= nil then
ctl.parent=win
win:addControl(ctl)
i = i + 1
end
end
--make a second window
local win2 = Window({title="Second Window", pos=vec2(76,24),
size=vec2(500,500), id="win2",DesignMode = true , GridSize=50,
toolbarButtons ={ WINCLOSE=true, MINMAX=true } ,
borderColor=color(57, 68, 131, 255),zOrder=5,
borderSize=4 , fixed = false})
menu2=Creator["Menu"](vec2(0,0))
menu2.id="menu00002"
win2:addControl(menu2)
--win2:Load(jsonCtls)
win2.fixed=true
wh:addWindow(win2)
end
--# SampleApp
displayMode(FULLSCREEN_NO_BUTTONS)
function setup()
--Creates Main Window Handler
wh = WindowHandler()
--if we are using designer, we need a palette
--CreatePalette()
-- keep it lean...
init()
end
function 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()
end
function touched(touch)
--Cycles through all windows
wh:touched(touch)
end
--place in different place later
-- startup for any initialization
function init()
source = "Dropbox"
files=spriteList(source)
--files=spriteList("Documents")
local navWindow = Window({title="Navigation Window", pos=vec2(4,4),
toolbarButtons ={ MINMAX=true,WINCLOSE=false} ,
size=vec2(200,HEIGHT-20), id="navwin",
borderColor=color(57, 68, 131, 255),
zOrder=99, borderSize=4 , fixed = true})
wh:addWindow(navWindow)
--create a button for each file
local iter= 0
for k,v in pairs(files) do
local pos = vec2(10, iter*22)
local params = {id="button_"..v, pos=pos, size=vec2(190,22), text=v,
fontsize=12}
local btn = TextButton(params)
btn.onClicked = function() CreateImageWindow(source..":"..btn.text)
end
navWindow:addControl(btn)
iter = iter + 1
end
end
function CreateImageWindow(imageSrc)
local img = readImage(imageSrc)
if img ~= nil then
id="win"..imageSrc
local imgWindow = Window({title="Image: " .. imageSrc,
pos=vec2(210+math.random(5,25)*5,300+math.random(5,25)*5),
size=vec2(300,300), id=id,
borderColor=color(57, 68, 131, 255),
borderSize=4 , fixed = false})
wh:addWindow(imgWindow)
local params = {id=picCtlName, pos=vec2(0,0), size=vec2(300,300),
text="PictureControl", fontsize=12, url=imageSrc}
imgCtl = PictureControl(params)
imgWindow:addControl(imgCtl)
id=params.id
-- find the resizer
local rz = imgWindow:getControlByName(imgWindow.id.."_resizer")
rz.onMoving =function(x) Resizer.onMoving(x)
ctl = x.parent:getControlByName(picCtlName)
ctl.size = x.parent.size
end
end
end
--# Main
-- moved out here because Codea is buggy with fonts, etc.
-- initial menu font was rendering wrong with this inside of setup()
displayMode(FULLSCREEN_NO_BUTTONS)
-- Use this function to perform your initial setup
function setup()
KEYSOUND = false --AC: mode later
backup = true
--Creates Main Window Handler
wh = WindowHandler()
--Sets up Windows based off of defined windows in setupWindows()
CreatePalette()
saveProjectInfo( "Description", "Windowed and screen-based UI controls.\nLast run on " .. os.date() .. "." )
saveProjectInfo( "Author", "Antonio Ciolino")
if backup then
version = "1.02"
assert(Backup~= nil, "Backup is missing. Please add the dependency.")
Backup("Cider2",version)
end
end
-- This function gets called once every frame
function 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()
end
function touched(touch)
--Cycles through all windows
wh:touched(touch)
end
function keyboard(key)
--Handles Keyboard Input Ending Correctly.
--This must be any program that handles a Text Input Box
if key == RETURN then
local ctl = wh:getActiveControl()
--check to see if the caller is taking responsibility for closure
if ctl.OverrideReturn == false then
hideKeyboard()
end
lastKey = key
else
if KEYSOUND then sound(SOUND_HIT, 38912) end
--if isKeyboardShowing() then
lastKey = key
--end
end
end
function Exit()
end
function Generate()
--make a window
local win = Window({title="Generated topmost Window", DesignMode = true ,
pos=vec2(math.random(3,12)*50, math.random(1,8) * 50),
size=vec2(300,300), id="Window" .. math.random(),
borderSize=4 , fixed = false, zOrder = 99})
wh:addWindow(win)
menuparams={id="menu", pos=vec2(65,80), size=vec2(125,30), zOrder = 10,
list=menulist, action=action,
parent=win}
menu2 = Menu(menuparams)
win:addControl(menu2)
end
function Design()
sound(SOUND_PICKUP, 25525)
local win = wh:getActiveWindow()
if win.DesignMode ==true then win.DesignMode = false else win.DesignMode =true end
end
function Save()
local win = wh:getActiveWindow()
tbl,funcs=win:Save()
--now what? we have a table...
serialized = encode(tbl)
saveLocalData(win.id, serialized) --saves the config in local data.
saveProjectTab("UDF", funcs)
end
function Load()
local win = wh:getActiveWindow()
if serialized == nil then serialized = readLocalData(win.id) end
if serialized ~= nil then
win:Load(decode(serialized))
end
end
function ShowHide()
if flag == STANDARD then flag = FULLSCREEN else flag = STANDARD end
displayMode(flag)
end
function About()
msg= [[
this is a test string that is a little long.
this is a test string that is a little long.
this is a test string that is a little long.
this is a test string that is a little long.
this is a test string that is a little long.
]]
AboutBox(msg)
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)
wh:clearActiveControl()
wh:addWindow(a)
end
function CCTest()
--find the container control
local win = wh:getActiveWindow()
for _,cc in pairs(win.controls) do
if cc.controlType == "ContainerControl" then
--force a control for testing
--[[ local b =Creator["TextInput"](vec2(30,30))
b.id="CCText"
cc:addControl(b)
-- local b =Creator["TextButton"](vec2(100,100))
-- b.id="CCButton"
-- cc:addControl(b)
local b =Creator["Dial"](vec2(200,200))
b.id="CCDial"
cc:addControl(b)
]]
for x=1,10 do
local b =Creator["PictureControl"](vec2((x-1)*200,100))
b.id="CCButton"..x
b.text = "Button"..x
b.size = vec2(200,150)
b.onClicked = function () AboutBox("Clicked " .. b.id) end
cc:addControl(b)
end
end
end
end
--# UDF
--user defined functions
--only clicked right now...
input90_onClicked=function (b) print(b.id) end
button71_onClicked=function (b) AboutBox(b.parent:getControlByName("input90").text) end
menu00002_onClicked=function (b) print(b.id) end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment