Last active
December 16, 2015 01:39
-
-
Save AntonioCiolino/5356607 to your computer and use it in GitHub Desktop.
Cider2 - version 1.02 - Windowed controls for Codea
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# Main | |
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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# 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