|
Buttons = {} |
|
-- this class manages all buttons |
|
|
|
-- ########### INSTRUCTIONS #################### |
|
Buttons.tab = "Buttons" -- this must be the name of this tab |
|
--[[ |
|
-- this is how to install the buttons in your main tab: |
|
function setup() |
|
Buttons:editMode(true) |
|
end |
|
function draw() |
|
-- your draw here |
|
Buttons:draw() |
|
end |
|
function touched(t) |
|
if Buttons:touched(t) then return end |
|
-- your touch here |
|
end |
|
--]] |
|
-- ########### END OF INSTRUCTIONS ############## |
|
|
|
Buttons.edit = false -- true qctivate edit mode |
|
Buttons.list = {} |
|
Buttons.buttonsCreated = false |
|
function Buttons:editMode(edit) |
|
if not Buttons.buttonsCreated then |
|
if edit then displayMode(STANDARD) end |
|
if self.createButtons then self:createButtons() end |
|
Buttons.buttonsCreated = true |
|
end |
|
self.edit = edit |
|
if self.edit then -- show the menu |
|
self:parameterTopMenu() |
|
end |
|
for i,b in ipairs(Buttons.list) do b.edit = edit end |
|
end |
|
|
|
function Buttons:draw() |
|
pushMatrix()pushStyle() |
|
resetMatrix()resetStyle() |
|
for i,b in ipairs(Buttons.list) do b:draw() end |
|
popMatrix()popStyle() |
|
end |
|
|
|
function Buttons:touched(touch) |
|
local n = #Buttons.list |
|
for i=1,n do |
|
local b = Buttons.list[n-i+1] |
|
if b:touched(touch) then return true end |
|
end |
|
return false |
|
end |
|
|
|
function Buttons:register(b) |
|
local n = #self.list+1 |
|
self.list[n] = b |
|
self.list[b.name] = b |
|
end |
|
function Buttons:delete(but) |
|
for i,b in ipairs(Buttons.list) do |
|
if b==but then |
|
table.remove(Buttons.list,i) |
|
end |
|
end |
|
Buttons.list[but.name] = nil |
|
Buttons.parameterTopMenu() |
|
end |
|
|
|
function Buttons.new() |
|
local n = 0 |
|
local name |
|
repeat |
|
n = n + 1 |
|
name = "button"..tostring(n) |
|
until Buttons.list[name] == nil |
|
|
|
local txt = "text "..tostring(n) |
|
local b = Button({name=name, txt = txt}) |
|
Buttons:register(b) |
|
end |
|
|
|
function Buttons.save() |
|
local d = {} |
|
d[#d+1] = "function Buttons:createButtons()" |
|
for i,b in ipairs(Buttons.list) do |
|
d[#d+1] = "Buttons:register(" |
|
d[#d+1] = b:tostring() |
|
d[#d+1] = ")" |
|
end |
|
d[#d+1] = "end" |
|
|
|
local strOut = table.concat(d,"\n") |
|
local str = readProjectTab(Buttons.tab) |
|
local splitter,start,plainText = "-- list of buttons\n",1,true |
|
local a,b = str:find(splitter,start,plainText) |
|
local s = str:sub(1, b) |
|
s = s .. strOut |
|
saveProjectTab(Buttons.tab,s) |
|
output.clear() |
|
print("saved") |
|
end |
|
|
|
function Buttons.switchMode() |
|
Buttons:editMode( not Buttons.edit ) |
|
output.clear() |
|
print("edit mode : "..tostring(Buttons.edit)) |
|
if Buttons.edit then |
|
print("you can edit your buttons, but callbacks do not work") |
|
else |
|
print("callbacks work, but buttons cant be edited") |
|
end |
|
Buttons.parameterTopMenu() |
|
end |
|
|
|
function Buttons.setAutosave(choice) |
|
Buttons.autosave = choice |
|
end |
|
|
|
function Buttons.parameterTopMenu() |
|
parameter.clear() |
|
parameter.action("BACK") |
|
if Buttons.edit then |
|
parameter.action("save changes",Buttons.save) |
|
parameter.boolean("B_autosave",Buttons.autosave,Buttons.setAutosave) |
|
parameter.action("new button",Buttons.new) |
|
parameter.action("tap a button to edit") |
|
end |
|
parameter.action("edit mode true/false",Buttons.switchMode) |
|
Buttons.current = nil -- no button selected |
|
end |
|
|
|
function Buttons.saveLast(key,value) |
|
Buttons.lastKey = key |
|
Buttons.lastValue = value |
|
end |
|
function Buttons.applyLastToAll() |
|
local key = Buttons.lastKey |
|
local value = Buttons.lastValue |
|
for i,b in ipairs(Buttons.list) do b[key] = value ; b:reset(b) end |
|
end |
|
function Buttons.applyCurrentButtonToAll() |
|
local except = {name=true,txt=true,callback=true,x=true,y=true,rx=true,ry=true} |
|
for i,b in ipairs(Buttons.list) do |
|
for key,value in pairs(Buttons.current) do |
|
if not except[key] then |
|
b[key] = value |
|
end |
|
end |
|
b:reset(b) |
|
end |
|
if Buttons.autosave then Buttons:save() end |
|
end |
|
|
|
-- ############################################################### |
|
|
|
Button = class() |
|
|
|
-- returns true if touched, fire callback if any |
|
|
|
function Button:init(d) |
|
self.name = d.name |
|
self:reset(d) |
|
tween.delay(1,self.reset,self,self) |
|
end |
|
|
|
function Button:reset(d) |
|
local d = d or {} |
|
local default = self.default |
|
for key,value in pairs(default) do |
|
if d[key]~= nil then self[key] = d[key] else self[key] = value end |
|
end |
|
pushMatrix() pushStyle() resetMatrix() resetStyle() |
|
|
|
-- position |
|
if self.rx >= 0 then self.x = self.rx else self.x = WIDTH + self.rx end |
|
if self.ry >= 0 then self.y = self.ry else self.y = HEIGHT + self.ry end |
|
-- state |
|
self:setOff() |
|
-- size |
|
font( self.fontName) |
|
fontSize(self.fontSize) |
|
local w,h = textSize(self.txt) |
|
local wo,ho,ro = self.w,self.h,self.r |
|
self.w = w + 2*self.innerMargin |
|
self.h = h + 2*self.innerMargin |
|
self.r = self.cornerRadius |
|
-- shape |
|
if wo~=self.w or ho~=self.h or ro~=self.r then self:createImg() end |
|
-- shadow position |
|
self:setShadow(self.shadow) |
|
popMatrix() popStyle() |
|
end |
|
|
|
function Button:setShadow(s) |
|
self.sx = self.x + s |
|
self.sy = self.y - s |
|
end |
|
function Button:shift(s) |
|
self.shiftX = s |
|
self.shiftY = -s |
|
end |
|
|
|
function Button:createImg() |
|
local img |
|
local w,h,r = self.w,self.h,self.r |
|
img = image(w,h) |
|
setContext(img) |
|
resetMatrix() resetStyle() noSmooth() noStroke() |
|
ellipseMode(RADIUS) |
|
fill(255) |
|
rect(r,0,w-2*r,h) |
|
rect(0,r,w,h-2*r) |
|
ellipse(r,r,r) |
|
ellipse(r,h-r,r) |
|
ellipse(w-r,r,r) |
|
ellipse(w-r,h-r,r) |
|
setContext() |
|
self.img = img |
|
end |
|
|
|
local d,range = {},{} |
|
d.name = "" |
|
d.txt = "" |
|
d.rx = 300 |
|
d.ry = 300 |
|
d.fontName = "ArialMT" |
|
d.fontSize = 27 |
|
range.fontSize = {min=10,max=50} |
|
d.fontColor=color(255, 255, 255, 255) |
|
d.colorOff = color(31, 42, 95, 255) |
|
d.colorOn=color(21, 25, 178, 255) |
|
d.isPushButton = true |
|
d.visible = true |
|
d.innerMargin = 14 |
|
range.innerMargin = {min=0,max=100} |
|
d.cornerRadius = 10 |
|
range.cornerRadius = {min=0,max=100} |
|
d.shadow = 5 |
|
range.shadow = {min=0,max=10} |
|
d.callback = function(self) print("tap on "..self.name) end |
|
Button.default = d |
|
Button.range = range |
|
-- these keys are the one the user can change |
|
Button.keys = {"txt","fontName","fontSize","innerMargin","fontColor", |
|
"cornerRadius","shadow","colorOff","colorOn","isPushButton"} |
|
|
|
function Button:tostring() |
|
local d ={} |
|
local default = Button.default |
|
for key,value in pairs(default) do |
|
if value ~= self[key] and type(value) ~= "function" then |
|
d[key] = self[key] |
|
end |
|
end |
|
local t = {} |
|
t[#t+1] = "Button({ " |
|
for key,value in pairs(d) do |
|
if type(value) == "string" then |
|
t[#t+1] = " " .. key .. "=\"" .. tostring(value) .."\"," |
|
elseif type(value) == "userdata" and value.a then |
|
t[#t+1] = " " .. key .. "=color" .. tostring(value) .."," |
|
else |
|
t[#t+1] = " " .. key .. "=" .. tostring(value) .."," |
|
end |
|
end |
|
t[#t+1] = "})" |
|
return table.concat(t,"\n") |
|
end |
|
|
|
function Button:setState(choice) |
|
if choice == nil then return self.state end |
|
if choice then self:setOn() else self:setOff() end |
|
end |
|
|
|
function Button:setOn() |
|
self.state = true |
|
self.c = self.colorOn |
|
self:shift(self.shadow) |
|
self:setShadow(self.shadow) |
|
|
|
if self.isPushButton then |
|
tween.delay(0.5,self.setOff,self) end |
|
end |
|
function Button:setOff() |
|
self.state = false |
|
self.c = self.colorOff |
|
self:shift(0) |
|
self:setShadow(self.shadow) |
|
end |
|
|
|
function Button:draw() |
|
if not self.visible then return end |
|
resetMatrix() resetStyle() |
|
noSmooth() noStroke() strokeWidth(0) spriteMode(CORNER) |
|
-- shadow box |
|
if self.shadow ~= 0 then |
|
translate(self.sx, self.sy) |
|
tint(0,0,0,128) |
|
sprite(self.img) |
|
end |
|
-- button box |
|
resetMatrix() translate(self.x+self.shiftX, self.y+self.shiftY) |
|
tint(self.c) |
|
sprite(self.img) |
|
noTint() |
|
-- button text |
|
if self.txt then |
|
font( self.fontName) |
|
fontSize(self.fontSize) |
|
fill(self.fontColor) |
|
textMode(CORNER) |
|
text( self.txt, self.innerMargin, self.innerMargin ) |
|
end |
|
end |
|
|
|
function Button:touched(t) |
|
if not self.visible then return false end |
|
-- detect if not touched (writen to be the fastest!) |
|
if t.x < self.x or t.x > self.x + self.w |
|
or t.y < self.y or t.y > self.y + self.h |
|
then return false end |
|
-- action when finger up only |
|
if self.edit == nil then self.edit = Buttons.edit end |
|
if self.edit then |
|
if t.state == BEGAN then |
|
self:moveStart(t) |
|
self:setState( not self.state ) |
|
elseif t.state == MOVING then |
|
self:move(t) |
|
elseif t.state == ENDED or t.state == CANCELLED then |
|
self:moveEnd(t) |
|
end |
|
else |
|
if t.state == BEGAN then |
|
self:setState( not self.state ) |
|
elseif t.state == ENDED then |
|
if self.callback then self:callback() end |
|
end |
|
end |
|
-- tell caller i have been touched |
|
return true |
|
end |
|
|
|
function Button:moveStart(t) |
|
self.dx = t.x - self.x |
|
self.dy = t.y - self.y |
|
self.lastx = self.x |
|
self.lasty = self.y |
|
if Buttons.current ~= self then self:parametersShow() end |
|
end |
|
function Button:move(t) |
|
local floor = math.floor |
|
local k = 10 |
|
local x,y = self.x,self.y |
|
|
|
local newx = floor( (t.x - self.dx)/k +0.5)*k |
|
if newx < 0 then newx = 0 end |
|
if newx + self.w > WIDTH then newx = WIDTH - self.w end |
|
|
|
local newy = floor( (t.y - self.dy)/k +0.5)*k |
|
if newy < 0 then newy = 0 end |
|
if newy + self.h > HEIGHT then newy = HEIGHT - self.h end |
|
|
|
if newx~=x or newy~=y then |
|
self.x,self.y = newx,newy |
|
self.sx,self.sy = self.sx + (newx-x), self.sy + (newy-y) |
|
|
|
if self.x+self.w/2 > WIDTH/2 |
|
then self.rx = self.x - WIDTH |
|
else self.rx = self.x |
|
end |
|
|
|
if self.y+self.h/2 > HEIGHT/2 |
|
then self.ry = self.y - HEIGHT |
|
else self.ry = self.y |
|
end |
|
end |
|
end |
|
function Button:moveEnd(t) |
|
|
|
end |
|
|
|
function Button:parametersShow() |
|
parameter.clear() |
|
parameter.action("BACK",Buttons.parameterTopMenu) |
|
local demo = function() |
|
output.clear() |
|
print("you can associate a callback function to this button as shown below:") |
|
print( |
|
"\t"..self.name .."= Buttons.list[\"".. self.name .."\"] -- get the button \n" .. |
|
"\t"..self.name ..".callback = function(self) -- define its callback \n" .. |
|
"\t print(\"name: \" .. self.name ) -- button name \n" .. |
|
"\t print(\"label: \" .. self.txt ) -- button label \n" .. |
|
"\t print(\"state: \" , self.state ) -- toggle true/false \n" .. |
|
"\t print(\"visible: \" , self.visible ) -- hide/show \n" .. |
|
"\tend" |
|
) |
|
end |
|
parameter.action("how to use "..self.name,demo) |
|
|
|
local del = function() Buttons:delete(self) end |
|
parameter.action("DELETE "..self.name,del) |
|
|
|
parameter.action("this style > all buttons",Buttons.applyCurrentButtonToAll) |
|
|
|
for i,key in ipairs(self.keys) do |
|
local value = self[key] |
|
local func |
|
if type(value) == "boolean" then |
|
func = function() self:changeBoolean(key) end |
|
elseif type(value) == "string" then |
|
func = function() self:changeString(key) end |
|
elseif type(value) == "number" then |
|
func = function() self:changeNumber(key) end |
|
elseif value.a then |
|
func = function() self:changeColor(key) end |
|
end |
|
if func then |
|
parameter.action(key,func) |
|
end |
|
end |
|
Buttons.current = self |
|
|
|
end |
|
function Button:changeBoolean(key) |
|
local value = self[key] |
|
func = function(new) self[key]=new ; self:reset(self) ; |
|
Buttons.saveLast(key,new) end |
|
parameter.clear() |
|
parameter.boolean("button_"..key,value,func) |
|
parameter.action("apply to all",Buttons.applyLastToAll) |
|
self:parameterBackAction() |
|
end |
|
function Button:changeString(key) |
|
local value = self[key] |
|
func = function(new) self[key]=new ; self:reset(self) ; |
|
Buttons.saveLast(key,new) end |
|
parameter.clear() |
|
parameter.text("button_"..key,value,func) |
|
parameter.action("apply to all",Buttons.applyLastToAll) |
|
self:parameterBackAction() |
|
end |
|
function Button:changeNumber(key) |
|
local value = self[key] |
|
func = function(new) self[key]=new ; self:reset(self) |
|
Buttons.saveLast(key,new) end |
|
parameter.clear() |
|
local range = self.range[key] |
|
if range then |
|
parameter.integer("button_"..key, |
|
range.min, range.max, value, func) |
|
end |
|
parameter.action("apply to all",Buttons.applyLastToAll) |
|
self:parameterBackAction() |
|
end |
|
function Button:changeColor(key) |
|
local value = self[key] |
|
func = function(new) self[key]=new ; self:reset(self) ; |
|
Buttons.saveLast(key,new) end |
|
parameter.clear() |
|
parameter.color("button_"..key,value,func) |
|
parameter.action("apply to all",Buttons.applyLastToAll) |
|
self:parameterBackAction() |
|
end |
|
function Button:parameterBackAction() |
|
if not self.backFunc then |
|
self.backFunc = function() |
|
Button.currentKey = nil |
|
self:parametersShow() |
|
if Buttons.autosave then Buttons:save() end |
|
end |
|
end |
|
parameter.action("BACK",self.backFunc) |
|
end |
|
function Button:parameterBefore(key) |
|
if self.firstPass then return end |
|
Button.currentKey = nil |
|
return false |
|
end |
|
|
|
-- ############################################################### |
|
|
|
-- list of buttons |
|
function Buttons:createButtons() |
|
Buttons:register( |
|
Button({ |
|
txt="bigger", |
|
name="button1", |
|
rx=-119, |
|
ry=-78, |
|
}) |
|
) |
|
Buttons:register( |
|
Button({ |
|
txt="smaller", |
|
name="button2", |
|
rx=-119, |
|
ry=-148, |
|
}) |
|
) |
|
Buttons:register( |
|
Button({ |
|
txt="rotate", |
|
rx=70, |
|
ry=-78, |
|
isPushButton=false, |
|
name="button3", |
|
}) |
|
) |
|
Buttons:register( |
|
Button({ |
|
txt="hide", |
|
rx=180, |
|
ry=-78, |
|
isPushButton=false, |
|
name="button4", |
|
}) |
|
) |
|
end |