Difference between revisions of "ButtonMonster class"

From GiderosMobile
Line 2: Line 2:
  
 
This ButtonMonster class allows you to build any button, from the simplest (text) to the most complicated (tooltip, keyboard navigation). It is optimised for menus and in games.
 
This ButtonMonster class allows you to build any button, from the simplest (text) to the most complicated (tooltip, keyboard navigation). It is optimised for menus and in games.
 
'''Requirements''':
 
'''You need to include ''luashader'' standard library in your project'''
 
'''''luashader'' standard library is available in your Gideros installation folder under ''Library'''''
 
  
 
=== ButtonMonster '''Class''' ===
 
=== ButtonMonster '''Class''' ===
Line 14: Line 10:
 
-- Up, Down, Disabled, Hover,
 
-- Up, Down, Disabled, Hover,
 
-- Sfx, Touch, Mouse and Keyboard navigation!
 
-- Sfx, Touch, Mouse and Keyboard navigation!
v 0.2.0: 2023-11-29 terminator, should be fine in games too now
+
v 0.2.0: 2023-12-01 terminator, should be fine in games too now
 
v 0.1.0: 2021-06-01 total recall, this class has become a Monster! best used in menus but who knows?
 
v 0.1.0: 2021-06-01 total recall, this class has become a Monster! best used in menus but who knows?
 
v 0.0.1: 2020-03-28 init (based on the initial generic Gideros Button class)
 
v 0.0.1: 2020-03-28 init (based on the initial generic Gideros Button class)
 
]]
 
]]
 
-- Shader, please adapt the path accordingly!
 
--!NEEDS:../luashader/luashader.lua
 
 
function vshaderpixelslot(vVertex, vColor, vTexCoord) : Shader
 
local vertex = hF4(vVertex, 0.0, 1.0)
 
fTexCoord = vTexCoord
 
return vMatrix*vertex
 
end
 
function fshaderpixelslot() : Shader
 
local frag = texture2D(fTexture, fTexCoord) -- 0, not focused
 
if slot == 2.0 then frag = texture2D(fTexture3, fTexCoord) -- 2, disabled
 
elseif slot == 1.0 then frag = texture2D(fTexture2, fTexCoord) -- 1, focused
 
end
 
return lF4(fColor*fColor.a)*frag -- alpha
 
end
 
 
local shaderpixelslot=Shader.lua(vshaderpixelslot, fshaderpixelslot, 0,
 
{
 
{name="vMatrix", type=Shader.CMATRIX, sys=Shader.SYS_WVP, vertex=true},
 
{name="fColor", type=Shader.CFLOAT4, sys=Shader.SYS_COLOR, vertex=false}, -- 1st color slot
 
{name="fColor2", type=Shader.CFLOAT4, sys=Shader.SYS_COLOR, vertex=false}, -- 2nd color slot
 
{name="fColor3", type=Shader.CFLOAT4, sys=Shader.SYS_COLOR, vertex=false}, -- 3rd color slot
 
{name="fTexture", type=Shader.CTEXTURE, vertex=false},
 
{name="fTexture2", type=Shader.CTEXTURE, vertex=false},
 
{name="fTexture3", type=Shader.CTEXTURE, vertex=false},
 
{name="slot", type=Shader.CFLOAT, vertex=false},
 
},
 
{
 
{name="vVertex", type=Shader.DFLOAT, mult=2, slot=0, offset=0},
 
{name="vColor", type=Shader.DUBYTE, mult=4, slot=1, offset=0},
 
{name="vTexCoord", type=Shader.DFLOAT, mult=2, slot=2, offset=0},
 
},
 
{
 
{name="fTexCoord", type=Shader.CFLOAT2},
 
}
 
)
 
  
 
-- Class
 
-- Class
Line 88: Line 47:
 
self.params.pixelscalexdown = xparams.pixelscalexdown or self.params.pixelscalexup -- number
 
self.params.pixelscalexdown = xparams.pixelscalexdown or self.params.pixelscalexup -- number
 
self.params.pixelscaleydown = xparams.pixelscaleydown or self.params.pixelscaleyup -- number
 
self.params.pixelscaleydown = xparams.pixelscaleydown or self.params.pixelscaleyup -- number
self.params.pixelwidth = xparams.pixelwidth or 8*3 -- number (autoscale = x padding else width)
+
self.params.pixelwidth = xparams.pixelwidth or 24 -- 24, number (autoscale = x padding else width)
 
self.params.pixelheight = xparams.pixelheight or self.params.pixelwidth -- number (autoscale = y padding else height)
 
self.params.pixelheight = xparams.pixelheight or self.params.pixelwidth -- number (autoscale = y padding else height)
self.params.ninepatch = xparams.ninepatch or 8 -- 0, number
+
self.params.ninepatch = xparams.ninepatch or 16 -- 0, 8, number
 
-- text?
 
-- text?
 
self.params.text = xparams.text or nil -- string
 
self.params.text = xparams.text or nil -- string
Line 113: Line 72:
 
self.params.sound = xparams.sound or nil -- sound fx
 
self.params.sound = xparams.sound or nil -- sound fx
 
self.params.volume = xparams.volume or nil -- sound volume
 
self.params.volume = xparams.volume or nil -- sound volume
-- warnings, errors?
 
if self.params.pixelalphaup <= 0 then self.params.pixelalphaup = 0.01 end -- alpha <= 0 breaks shader!
 
if self.params.pixelalphadown <= 0 then self.params.pixelalphadown = 0.01 end -- alpha <= 0 breaks shader!
 
 
-- let's go!
 
-- let's go!
 
self:setButton()
 
self:setButton()
Line 148: Line 104:
 
end
 
end
 
-- first add pixel
 
-- first add pixel
local pixelimg = false
 
 
if self.params.autoscale and self.params.text then
 
if self.params.autoscale and self.params.text then
 
self.pixel = Pixel.new(self.params.pixelcolorup, self.params.pixelalphaup,
 
self.pixel = Pixel.new(self.params.pixelcolorup, self.params.pixelalphaup,
Line 155: Line 110:
 
self.pixel = Pixel.new(self.params.pixelcolorup, self.params.pixelalphaup,
 
self.pixel = Pixel.new(self.params.pixelcolorup, self.params.pixelalphaup,
 
self.params.pixelwidth, self.params.pixelheight)
 
self.params.pixelwidth, self.params.pixelheight)
end
 
if self.params.pixelimgup then
 
self.pixel:setTexture(self.params.pixelimgup, 0) -- Texture, slot
 
pixelimg = true
 
end
 
if self.params.pixelimgdown then
 
self.pixel:setTexture(self.params.pixelimgdown, 1) -- Texture, slot
 
pixelimg = true
 
end
 
if self.params.pixelimgdisabled then
 
self.pixel:setTexture(self.params.pixelimgdisabled, 2) -- Texture, slot
 
pixelimg = true
 
 
end
 
end
 
self.pixel:setScale(self.params.pixelscalexup, self.params.pixelscaleyup)
 
self.pixel:setScale(self.params.pixelscalexup, self.params.pixelscaleyup)
 
self.pixel:setAnchorPoint(0.5, 0.5)
 
self.pixel:setAnchorPoint(0.5, 0.5)
if pixelimg then self.pixel:setShader(shaderpixelslot) end -- apply shader
+
self.pixel:setNinePatch(self.params.ninepatch)
 +
if self.params.pixelimgup then self.pixel:setTexture(self.params.pixelimgup)
 +
elseif self.params.pixelimgdown then self.pixel:setTexture(self.params.pixelimgdown)
 +
elseif self.params.pixelimgdisabled then self.pixel:setTexture(self.params.pixelimgdisabled)
 +
end
 
self:addChild(self.pixel)
 
self:addChild(self.pixel)
 
-- then add text?
 
-- then add text?
Line 188: Line 135:
 
function ButtonMonster:updateVisualState()
 
function ButtonMonster:updateVisualState()
 
local function visualState(btn, btnscalex, btnscaley, btnalpha, textcolor, textscalex, textscaley,
 
local function visualState(btn, btnscalex, btnscaley, btnalpha, textcolor, textscalex, textscaley,
pixtexslot, pixelcolor, pixelalpha, pixelscalex, pixelscaley)
+
pixeltex, pixelcolor, pixelalpha, pixelscalex, pixelscaley)
 
btn:setScale(btnscalex, btnscaley)
 
btn:setScale(btnscalex, btnscaley)
 
btn:setAlpha(btnalpha)
 
btn:setAlpha(btnalpha)
Line 195: Line 142:
 
btn.text:setScale(textscalex, textscaley)
 
btn.text:setScale(textscalex, textscaley)
 
end
 
end
if btn.params.pixelimgup then -- texture
+
if pixeltex then btn.pixel:setTexture(pixeltex) end
local r, g, b = (pixelcolor >> 16 & 0xff) / 255, (pixelcolor >> 8 & 0xff) / 255, (pixelcolor & 0xff) / 255
+
btn.pixel:setColor(pixelcolor, pixelalpha)
btn.pixel:setShaderConstant("slot", Shader.CFLOAT, 1, pixtexslot) -- set Pixel texture slot
 
btn.pixel:setShaderConstant("fColor", Shader.CFLOAT4, 1, r, g, b, pixelalpha) -- set Pixel color
 
else
 
btn.pixel:setColor(pixelcolor, pixelalpha)
 
end
 
 
btn.pixel:setScale(pixelscalex, pixelscaley)
 
btn.pixel:setScale(pixelscalex, pixelscaley)
 
end
 
end
Line 210: Line 152:
 
visualState(v, v.params.btnscalexdown, v.params.btnscaleydown, v.params.btnalphadown,
 
visualState(v, v.params.btnscalexdown, v.params.btnscaleydown, v.params.btnalphadown,
 
v.params.textcolordisabled, v.params.textscalexdown, v.params.textscaleydown,
 
v.params.textcolordisabled, v.params.textscalexdown, v.params.textscaleydown,
2.0, v.params.pixelcolordisabled, v.params.pixelalphadown, v.params.pixelscalexdown, v.params.pixelscaleydown)
+
v.params.pixelimgdisabled, v.params.pixelcolordisabled,
 +
v.params.pixelalphadown, v.params.pixelscalexdown, v.params.pixelscaleydown)
 
-- if v.ttiptext and not v.disabled then -- OPTION 1: hides tooltip when button is Disabled
 
-- if v.ttiptext and not v.disabled then -- OPTION 1: hides tooltip when button is Disabled
 
if v.ttiptext then -- OPTION 2: shows tooltip even if button is Disabled, you choose!
 
if v.ttiptext then -- OPTION 2: shows tooltip even if button is Disabled, you choose!
Line 221: Line 164:
 
visualState(v, v.params.btnscalexdown, v.params.btnscaleydown, v.params.btnalphadown,
 
visualState(v, v.params.btnscalexdown, v.params.btnscaleydown, v.params.btnalphadown,
 
v.params.textcolordown, v.params.textscalexdown, v.params.textscaleydown,
 
v.params.textcolordown, v.params.textscalexdown, v.params.textscaleydown,
1.0, v.params.pixelcolordown, v.params.pixelalphadown, v.params.pixelscalexdown, v.params.pixelscaleydown)
+
v.params.pixelimgdown, v.params.pixelcolordown,
 +
v.params.pixelalphadown, v.params.pixelscalexdown, v.params.pixelscaleydown)
 
if v.ttiptext then
 
if v.ttiptext then
 
v.ttiptext:setText(v.params.tooltiptext)
 
v.ttiptext:setText(v.params.tooltiptext)
Line 236: Line 180:
 
visualState(v, v.params.btnscalexup, v.params.btnscaleyup, v.params.btnalphaup,
 
visualState(v, v.params.btnscalexup, v.params.btnscaleyup, v.params.btnalphaup,
 
v.params.textcolorup, v.params.textscalexup, v.params.textscaleyup,
 
v.params.textcolorup, v.params.textscalexup, v.params.textscaleyup,
0.0, v.params.pixelcolorup, v.params.pixelalphaup, v.params.pixelscalexup, v.params.pixelscaleyup)
+
v.params.pixelimgup, v.params.pixelcolorup,
 +
v.params.pixelalphaup, v.params.pixelscalexup, v.params.pixelscaleyup)
 
if v.ttiptext then v.ttiptext:setVisible(false) end
 
if v.ttiptext then v.ttiptext:setVisible(false) end
 
end
 
end
Line 245: Line 190:
 
visualState(self, self.params.btnscalexdown, self.params.btnscaleydown, self.params.btnalphadown,
 
visualState(self, self.params.btnscalexdown, self.params.btnscaleydown, self.params.btnalphadown,
 
self.params.textcolordisabled, self.params.textscalexdown, self.params.textscaleydown,
 
self.params.textcolordisabled, self.params.textscalexdown, self.params.textscaleydown,
2.0, self.params.pixelcolordisabled, self.params.pixelalphadown, self.params.pixelscalexdown, self.params.pixelscaleydown)
+
self.params.pixelimgdisabled, self.params.pixelcolordisabled,
 +
self.params.pixelalphadown, self.params.pixelscalexdown, self.params.pixelscaleydown)
 
-- if self.ttiptext and not self.disabled then -- OPTION 1: hides tooltip when button is Disabled
 
-- if self.ttiptext and not self.disabled then -- OPTION 1: hides tooltip when button is Disabled
 
if self.ttiptext then -- OPTION 2: shows tooltip even if button is Disabled, you choose!
 
if self.ttiptext then -- OPTION 2: shows tooltip even if button is Disabled, you choose!
Line 256: Line 202:
 
visualState(self, self.params.btnscalexdown, self.params.btnscaleydown, self.params.btnalphadown,
 
visualState(self, self.params.btnscalexdown, self.params.btnscaleydown, self.params.btnalphadown,
 
self.params.textcolordown, self.params.textscalexdown, self.params.textscaleydown,
 
self.params.textcolordown, self.params.textscalexdown, self.params.textscaleydown,
1.0, self.params.pixelcolordown, self.params.pixelalphadown, self.params.pixelscalexdown, self.params.pixelscaleydown)
+
self.params.pixelimgdown, self.params.pixelcolordown,
 +
self.params.pixelalphadown, self.params.pixelscalexdown, self.params.pixelscaleydown)
 
if self.ttiptext then
 
if self.ttiptext then
 
self.ttiptext:setText(self.params.tooltiptext)
 
self.ttiptext:setText(self.params.tooltiptext)
Line 264: Line 211:
 
visualState(self, self.params.btnscalexup, self.params.btnscaleyup, self.params.btnalphaup,
 
visualState(self, self.params.btnscalexup, self.params.btnscaleyup, self.params.btnalphaup,
 
self.params.textcolorup, self.params.textscalexup, self.params.textscaleyup,
 
self.params.textcolorup, self.params.textscalexup, self.params.textscaleyup,
0.0, self.params.pixelcolorup, self.params.pixelalphaup, self.params.pixelscalexup, self.params.pixelscaleyup)
+
self.params.pixelimgup, self.params.pixelcolorup,
 +
self.params.pixelalphaup, self.params.pixelscalexup, self.params.pixelscaleyup)
 
if self.ttiptext then self.ttiptext:setVisible(false) end
 
if self.ttiptext then self.ttiptext:setVisible(false) end
 
end
 
end
Line 289: Line 237:
 
end
 
end
 
self:updateVisualState()
 
self:updateVisualState()
self:selectionSfx() -- play sound fx
+
self:selectionSfx() -- play sound fx?
 
if self.ttiptext then -- set tooltip initial position
 
if self.ttiptext then -- set tooltip initial position
 
if self.tooltiplayer then
 
if self.tooltiplayer then
Line 369: Line 317:
 
timer:start()
 
timer:start()
 
else
 
else
 +
self.hover = false -- XXX
 
self.onexit = true
 
self.onexit = true
 
end
 
end
Line 388: Line 337:
 
-- if button is on focus, stop propagation of touch events
 
-- if button is on focus, stop propagation of touch events
 
function ButtonMonster:onTouchesBegin(ev) -- touch only
 
function ButtonMonster:onTouchesBegin(ev) -- touch only
if self.focus then ev:stopPropagation() end
+
if self.focus then
 +
ev:stopPropagation()
 +
end
 
end
 
end
 
-- if button is on focus, stop propagation of touch events
 
-- if button is on focus, stop propagation of touch events
 
function ButtonMonster:onTouchesMove(ev) -- touch only
 
function ButtonMonster:onTouchesMove(ev) -- touch only
if self.focus then ev:stopPropagation() end
+
if self.focus then
 +
ev:stopPropagation()
 +
end
 
end
 
end
 
-- if button is on focus, stop propagation of touch events
 
-- if button is on focus, stop propagation of touch events
Line 447: Line 400:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
 
-- ButtonMonster DEMO 1
 
-- ButtonMonster DEMO 1
 +
-- bg
 
application:setBackgroundColor(0x6c6c6c)
 
application:setBackgroundColor(0x6c6c6c)
 
 
-- font
 
-- font
 
local myttf = TTFont.new("fonts/Cabin-Bold-TTF.ttf", 20)
 
local myttf = TTFont.new("fonts/Cabin-Bold-TTF.ttf", 20)
local myttipttf = TTFont.new("fonts/Cabin-Bold-TTF.ttf", 18)
 
 
-- textures
 
-- textures
 
local btnuptex = Texture.new("gfx/ui/btn_01_up.png")
 
local btnuptex = Texture.new("gfx/ui/btn_01_up.png")
Line 462: Line 414:
 
local btn01 = ButtonMonster.new({
 
local btn01 = ButtonMonster.new({
 
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
 
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
 +
pixelscaleyup=0.7, pixelscaleydown=1,
 
text="button 1", ttf=myttf,
 
text="button 1", ttf=myttf,
 
sound=btnsound, volume=volume,
 
sound=btnsound, volume=volume,
Line 467: Line 420:
 
local btn02 = ButtonMonster.new({
 
local btn02 = ButtonMonster.new({
 
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
 
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
 +
pixelscaleyup=0.7, pixelscaleydown=1,
 
text="button 2", ttf=myttf,
 
text="button 2", ttf=myttf,
 
sound=btnsound, volume=volume,
 
sound=btnsound, volume=volume,
Line 472: Line 426:
 
local btn03 = ButtonMonster.new({
 
local btn03 = ButtonMonster.new({
 
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
 
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
 +
pixelscaleyup=0.7, pixelscaleydown=1,
 
text="button 3", ttf=myttf,
 
text="button 3", ttf=myttf,
 
sound=btnsound, volume=volume,
 
sound=btnsound, volume=volume,
Line 477: Line 432:
 
local btn04 = ButtonMonster.new({
 
local btn04 = ButtonMonster.new({
 
autoscale=false,
 
autoscale=false,
pixelwidth=8*5, pixelheight=8*24,
+
pixelwidth=8*6, pixelheight=8*30,
 
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
 
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
 +
pixelscalexup=0.7, pixelscalexdown=1,
 
pixelcolordown=0x00ff00,
 
pixelcolordown=0x00ff00,
 
text="b\nt\nn\n \n4", ttf=myttf,
 
text="b\nt\nn\n \n4", ttf=myttf,
Line 565: Line 521:
 
pixelwidth=8*5, pixelheight=8*24,
 
pixelwidth=8*5, pixelheight=8*24,
 
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
 
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
 +
ninepatch=32,
 
pixelcolordown=0x00ff00,
 
pixelcolordown=0x00ff00,
 
text="b\nt\nn\n \n4", ttf=myttf,
 
text="b\nt\nn\n \n4", ttf=myttf,

Revision as of 19:43, 1 December 2023

This ButtonMonster class allows you to build any button, from the simplest (text) to the most complicated (tooltip, keyboard navigation). It is optimised for menus and in games.

ButtonMonster Class

--[[
-- ButtonMonster
-- Pixel, Image, 9patch, Text, Tooltip,
-- Up, Down, Disabled, Hover,
-- Sfx, Touch, Mouse and Keyboard navigation!
v 0.2.0: 2023-12-01 terminator, should be fine in games too now
v 0.1.0: 2021-06-01 total recall, this class has become a Monster! best used in menus but who knows?
v 0.0.1: 2020-03-28 init (based on the initial generic Gideros Button class)
]]

-- Class
ButtonMonster = Core.class(Sprite)

function ButtonMonster:init(xparams, xselector, xttlayer)
	-- user params
	self.params = xparams or {}
	-- add keyboard navigation?
	self.selector = xselector or nil -- button id selector
	self.btns = nil -- assign this value directly from your code (you assign it a list of buttons)
	-- add a layer for the tooltip?
	self.tooltiplayer = xttlayer or nil
	-- button params
	self.params.autoscale = xparams.autoscale or (xparams.autoscale == nil) -- bool (default = true)
	self.params.btnscalexup = xparams.btnscalexup or 1 -- number
	self.params.btnscaleyup = xparams.btnscaleyup or self.params.btnscalexup -- number
	self.params.btnscalexdown = xparams.btnscalexdown or self.params.btnscalexup -- number
	self.params.btnscaleydown = xparams.btnscaleydown or self.params.btnscaleyup -- number
	self.params.btnalphaup = xparams.btnalphaup or 1 -- number
	self.params.btnalphadown = xparams.btnalphadown or self.params.btnalphaup -- number
	-- pixel?
	self.params.pixelcolorup = xparams.pixelcolorup or 0xffffff -- color
	self.params.pixelcolordown = xparams.pixelcolordown or self.params.pixelcolorup -- color
	self.params.pixelcolordisabled = xparams.pixelcolordisabled or 0x555555 -- color
	self.params.pixelimgup = xparams.pixelimgup or nil -- img Up Texture
	self.params.pixelimgdown = xparams.pixelimgdown or self.params.pixelimgup -- img Down Texture
	self.params.pixelimgdisabled = xparams.pixelimgdisabled or self.params.pixelimgup -- img Disabled Texture
	self.params.pixelalphaup = xparams.pixelalphaup or 1 -- number
	self.params.pixelalphadown = xparams.pixelalphadown or self.params.pixelalphaup -- number
	self.params.pixelscalexup = xparams.pixelscalexup or 1 -- number
	self.params.pixelscaleyup = xparams.pixelscaleyup or self.params.pixelscalexup -- number
	self.params.pixelscalexdown = xparams.pixelscalexdown or self.params.pixelscalexup -- number
	self.params.pixelscaleydown = xparams.pixelscaleydown or self.params.pixelscaleyup -- number
	self.params.pixelwidth = xparams.pixelwidth or 24 -- 24, number (autoscale = x padding else width)
	self.params.pixelheight = xparams.pixelheight or self.params.pixelwidth -- number (autoscale = y padding else height)
	self.params.ninepatch = xparams.ninepatch or 16 -- 0, 8, number
	-- text?
	self.params.text = xparams.text or nil -- string
	self.params.ttf = xparams.ttf or nil -- ttf font
	self.params.textcolorup = xparams.textcolorup or 0x0 -- color
	self.params.textcolordown = xparams.textcolordown or self.params.textcolorup -- color
	self.params.textcolordisabled = xparams.textcolordisabled or 0x777777 -- color
	self.params.textalphaup = xparams.textalphaup or 1 -- number
	self.params.textalphadown = xparams.textalphaup or self.params.textalphaup -- number
	self.params.textscalexup = xparams.textscalexup or 1 -- number
	self.params.textscaleyup = xparams.textscaleyup or self.params.textscalexup -- number
	self.params.textscalexdown = xparams.textscalexdown or self.params.textscalexup -- number
	self.params.textscaleydown = xparams.textscaleydown or self.params.textscaleyup -- number
	-- tool tip?
	self.params.tooltiptext = xparams.tooltiptext or nil -- string
	self.params.tooltipttf = xparams.tooltipttf or nil -- ttf font
	self.params.tooltiptextcolor = xparams.tooltiptextcolor or 0x0 -- color
	self.params.tooltiptextscale = xparams.tooltiptextscale or 1 -- number
	self.params.tooltipoffsetx = xparams.tooltipoffsetx or 0 -- number
	self.params.tooltipoffsety = xparams.tooltipoffsety or 0 -- self.params.tooltipoffsetx -- number
	-- audio?
	self.params.sound = xparams.sound or nil -- sound fx
	self.params.volume = xparams.volume or nil -- sound volume
	-- let's go!
	self:setButton()
	-- update visual state
	self.focus = false
	self.hover = false
	self.disabled = false
	self:updateVisualState()
	-- mouse event listeners
	self:addEventListener(Event.MOUSE_DOWN, self.onMouseDown, self)
	self:addEventListener(Event.MOUSE_MOVE, self.onMouseMove, self)
	self:addEventListener(Event.MOUSE_UP, self.onMouseUp, self)
	self:addEventListener(Event.MOUSE_HOVER, self.onMouseHover, self)
	-- touches event listeners
	self:addEventListener(Event.TOUCHES_BEGIN, self.onTouchesBegin, self)
	self:addEventListener(Event.TOUCHES_MOVE, self.onTouchesMove, self)
	self:addEventListener(Event.TOUCHES_END, self.onTouchesEnd, self)
	self:addEventListener(Event.TOUCHES_CANCEL, self.onTouchesCancel, self)
end

-- FUNCTIONS
function ButtonMonster:setButton()
	-- text dimensions
	local textwidth, textheight
	if self.params.text then
		self.text = TextField.new(self.params.ttf, self.params.text, self.params.text)
		self.text:setAnchorPoint(0.5, 0.5)
		self.text:setScale(self.params.textscalexup, self.params.textscaleyup)
		self.text:setTextColor(self.params.textcolorup)
		self.text:setAlpha(self.params.textalphaup)
		textwidth, textheight = self.text:getWidth(), self.text:getHeight()
	end
	-- first add pixel
	if self.params.autoscale and self.params.text then
		self.pixel = Pixel.new(self.params.pixelcolorup, self.params.pixelalphaup,
			textwidth+self.params.pixelwidth, textheight+self.params.pixelheight)
	else
		self.pixel = Pixel.new(self.params.pixelcolorup, self.params.pixelalphaup,
			self.params.pixelwidth, self.params.pixelheight)
	end
	self.pixel:setScale(self.params.pixelscalexup, self.params.pixelscaleyup)
	self.pixel:setAnchorPoint(0.5, 0.5)
	self.pixel:setNinePatch(self.params.ninepatch)
	if self.params.pixelimgup then self.pixel:setTexture(self.params.pixelimgup)
	elseif self.params.pixelimgdown then self.pixel:setTexture(self.params.pixelimgdown)
	elseif self.params.pixelimgdisabled then self.pixel:setTexture(self.params.pixelimgdisabled)
	end
	self:addChild(self.pixel)
	-- then add text?
	if self.params.text then self:addChild(self.text) end
	-- finally add tooltip?
	if self.params.tooltiptext then
		self.ttiptext = TextField.new(self.params.tooltipttf, self.params.tooltiptext, self.params.tooltiptext)
		self.ttiptext:setScale(self.params.tooltiptextscale)
		self.ttiptext:setTextColor(self.params.tooltiptextcolor)
		self.ttiptext:setVisible(false)
		if self.tooltiplayer then self.tooltiplayer:addChild(self.ttiptext)
		else self:addChild(self.ttiptext)
		end
	end
end

function ButtonMonster:updateVisualState()
	local function visualState(btn, btnscalex, btnscaley, btnalpha, textcolor, textscalex, textscaley,
			pixeltex, pixelcolor, pixelalpha, pixelscalex, pixelscaley)
		btn:setScale(btnscalex, btnscaley)
		btn:setAlpha(btnalpha)
		if btn.params.text then
			btn.text:setTextColor(textcolor)
			btn.text:setScale(textscalex, textscaley)
		end
		if pixeltex then btn.pixel:setTexture(pixeltex) end
		btn.pixel:setColor(pixelcolor, pixelalpha)
		btn.pixel:setScale(pixelscalex, pixelscaley)
	end
	if self.btns then
--		print("KEYBOARD NAVIGATION")
		for k, v in ipairs(self.btns) do
			if v.disabled then -- disabledState
				visualState(v, v.params.btnscalexdown, v.params.btnscaleydown, v.params.btnalphadown,
					v.params.textcolordisabled, v.params.textscalexdown, v.params.textscaleydown,
					v.params.pixelimgdisabled, v.params.pixelcolordisabled,
					v.params.pixelalphadown, v.params.pixelscalexdown, v.params.pixelscaleydown)
--				if v.ttiptext and not v.disabled then -- OPTION 1: hides tooltip when button is Disabled
				if v.ttiptext then -- OPTION 2: shows tooltip even if button is Disabled, you choose!
					v.ttiptext:setText("("..v.params.tooltiptext..")") -- extra!
					if k == v.currselector then v.ttiptext:setVisible(true)
					else v.ttiptext:setVisible(false)
					end
				end
			elseif k == v.currselector then -- downState
				visualState(v, v.params.btnscalexdown, v.params.btnscaleydown, v.params.btnalphadown,
					v.params.textcolordown, v.params.textscalexdown, v.params.textscaleydown,
					v.params.pixelimgdown, v.params.pixelcolordown,
					v.params.pixelalphadown, v.params.pixelscalexdown, v.params.pixelscaleydown)
				if v.ttiptext then
					v.ttiptext:setText(v.params.tooltiptext)
					if v.tooltiplayer then -- reset tooltip text position
						v.ttiptext:setPosition(
							v:getX()+v.params.tooltipoffsetx, v:getY()+v.params.tooltipoffsety)
					else
						v.ttiptext:setPosition(v:globalToLocal(
							v:getX()+v.params.tooltipoffsetx, v:getY()+v.params.tooltipoffsety))
					end
					v.ttiptext:setVisible(true)
				end
			else -- upState
				visualState(v, v.params.btnscalexup, v.params.btnscaleyup, v.params.btnalphaup,
					v.params.textcolorup, v.params.textscalexup, v.params.textscaleyup,
					v.params.pixelimgup, v.params.pixelcolorup,
					v.params.pixelalphaup, v.params.pixelscalexup, v.params.pixelscaleyup)
				if v.ttiptext then v.ttiptext:setVisible(false) end
			end
		end
	else
--		print("TOUCH, MOUSE NAVIGATION")
		if self.disabled then -- disabledState
			visualState(self, self.params.btnscalexdown, self.params.btnscaleydown, self.params.btnalphadown,
				self.params.textcolordisabled, self.params.textscalexdown, self.params.textscaleydown,
				self.params.pixelimgdisabled, self.params.pixelcolordisabled,
				self.params.pixelalphadown, self.params.pixelscalexdown, self.params.pixelscaleydown)
--			if self.ttiptext and not self.disabled then -- OPTION 1: hides tooltip when button is Disabled
			if self.ttiptext then -- OPTION 2: shows tooltip even if button is Disabled, you choose!
				self.ttiptext:setText("("..self.params.tooltiptext..")") -- extra!
				if self.focus then self.ttiptext:setVisible(true)
				else self.ttiptext:setVisible(false)
				end
			end
		elseif self.focus or self.hover then -- downState
			visualState(self, self.params.btnscalexdown, self.params.btnscaleydown, self.params.btnalphadown,
				self.params.textcolordown, self.params.textscalexdown, self.params.textscaleydown,
				self.params.pixelimgdown, self.params.pixelcolordown,
				self.params.pixelalphadown, self.params.pixelscalexdown, self.params.pixelscaleydown)
			if self.ttiptext then
				self.ttiptext:setText(self.params.tooltiptext)
				self.ttiptext:setVisible(true)
			end
		else -- upState
			visualState(self, self.params.btnscalexup, self.params.btnscaleyup, self.params.btnalphaup,
				self.params.textcolorup, self.params.textscalexup, self.params.textscaleyup,
				self.params.pixelimgup, self.params.pixelcolorup,
				self.params.pixelalphaup, self.params.pixelscalexup, self.params.pixelscaleyup)
			if self.ttiptext then self.ttiptext:setVisible(false) end
		end
	end
end

-- DISABLED
function ButtonMonster:setDisabled(disabled)
	if self.disabled == disabled then return end
	self.disabled = disabled
	self:updateVisualState()
end
function ButtonMonster:getDisabled()
	return self.disabled
end

-- LISTENERS
-- MOUSE
function ButtonMonster:onMouseDown(ev) -- both mouse and touch
	if self:hitTestPoint(ev.x, ev.y, true) then
		self.focus = true
		if self.btns then -- update keyboard button id selector
			for k, v in ipairs(self.btns) do v.currselector = self.selector end
		end
		self:updateVisualState()
		self:selectionSfx() -- play sound fx?
		if self.ttiptext then -- set tooltip initial position
			if self.tooltiplayer then
				self.ttiptext:setPosition(
					self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
			else
				self.ttiptext:setPosition(self:globalToLocal(
					self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
			end
		end
		ev:stopPropagation()
	end
end
function ButtonMonster:onMouseMove(ev) -- both mouse and touch
	if self.focus then
		if self.ttiptext then -- tooltip follows position
			if self.tooltiplayer then
				self.ttiptext:setPosition(
					ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety)
			else
				self.ttiptext:setPosition(self:globalToLocal(
					ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety))
			end
		end
		if not self:hitTestPoint(ev.x, ev.y, true) then
			self.focus = false
			self:updateVisualState()
		end
		ev:stopPropagation()
	elseif self.ttiptext then -- reset tooltip text position
		if self.tooltiplayer then
			self.ttiptext:setPosition(
				self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
		else
			self.ttiptext:setPosition(self:globalToLocal(
				self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
		end
	end
end
function ButtonMonster:onMouseUp(ev) -- both mouse and touch
	if self.focus then
		self.focus = false
		self:updateVisualState()
		local e = Event.new("clicked")
		e.currselector = self.selector -- update button id selector
		e.disabled = self.disabled -- update button disabled
		self:dispatchEvent(e) -- button is clicked, dispatch "clicked" event
		ev:stopPropagation()
	end
end
function ButtonMonster:onMouseHover(ev) -- mouse only
	if self:hitTestPoint(ev.x, ev.y, true) then -- onenter
		self.focus = true
		self.hover = true
		if self.ttiptext then -- tooltip follows mouse position
			if self.tooltiplayer then
				self.ttiptext:setPosition(
					ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety)
			else
				self.ttiptext:setPosition(self:globalToLocal(
					ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety))
			end
		end
		-- execute onenter code only once
		self.onenter = not self.onenter
		if not self.onenter then self.moving = true end
		if not self.moving then
			if self.btns then -- update keyboard button id selector
				for k, v in ipairs(self.btns) do v.currselector = self.selector end
			end
			local e = Event.new("hovered") -- dispatch "hovered" event
			e.currselector = self.selector -- update button id selector
			e.disabled = self.disabled -- update button disabled
			self:dispatchEvent(e)
--			self:selectionSfx() -- play sound fx? you choose!
			-- trick to remove residuals when fast moving mouse
			local timer = Timer.new(100*1, 1) -- number of repetition, the higher the safer
			timer:addEventListener(Event.TIMER, function() self:updateVisualState() end)
			timer:start()
		else
			self.hover = false -- XXX
			self.onexit = true
		end
		ev:stopPropagation()
	else -- onexit
		self.focus = false
		self.hover = false
		self.onenter = false
		self.moving = false
		if self.onexit then
			-- execute onexit code only once
			self.onexit = false
			self:updateVisualState()
		end
	end
end

-- TOUCHES
-- if button is on focus, stop propagation of touch events
function ButtonMonster:onTouchesBegin(ev) -- touch only
	if self.focus then
		ev:stopPropagation()
	end
end
-- if button is on focus, stop propagation of touch events
function ButtonMonster:onTouchesMove(ev) -- touch only
	if self.focus then
		ev:stopPropagation()
	end
end
-- if button is on focus, stop propagation of touch events
function ButtonMonster:onTouchesEnd(ev) -- touch only
	if self.focus then
		ev:stopPropagation()
	elseif self.ttiptext then -- reset tooltip text position
		if self.tooltiplayer then
			self.ttiptext:setPosition(
				self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
		else
			self.ttiptext:setPosition(self:globalToLocal(
				self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
		end
	end
end
-- if touches are cancelled, reset the state of the button
function ButtonMonster:onTouchesCancel(ev) -- app interrupted (phone call, ...), touch only
	if self.focus then
		self.focus = false
		if self.btns then -- update keyboard button id selector
			for k, v in ipairs(self.btns) do v.currselector = self.selector end
		end
		self:updateVisualState()
	elseif self.ttiptext then -- reset tooltip text position
		if self.tooltiplayer then
			self.ttiptext:setPosition(
				self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
		else
			self.ttiptext:setPosition(self:globalToLocal(
				self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
		end
	end
end

-- AUDIO
function ButtonMonster:selectionSfx()
	if self.params.sound then
		local snd = self.params.sound
		local curr = os.timer()
		local prev = snd.time
		if curr - prev > snd.delay then
			snd.sound:play():setVolume(self.params.volume)
			snd.time = curr
		end
	end
end

ButtonMonster Demos

Demo 1

-- ButtonMonster DEMO 1
-- bg
application:setBackgroundColor(0x6c6c6c)
-- font
local myttf = TTFont.new("fonts/Cabin-Bold-TTF.ttf", 20)
-- textures
local btnuptex = Texture.new("gfx/ui/btn_01_up.png")
local btndowntex = Texture.new("gfx/ui/btn_01_down.png")
local btndisabledtex = Texture.new("gfx/ui/btn_01_disabled.png")
-- buttons sound
local btnsound = {sound=Sound.new("audio/Braam - Retro Pulse.wav"), time=0, delay=0.5} -- delay=0.5, 0.05
local volume = 0.3
-- buttons
local btn01 = ButtonMonster.new({
	pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
	pixelscaleyup=0.7, pixelscaleydown=1,
	text="button 1", ttf=myttf,
	sound=btnsound, volume=volume,
}, 1)
local btn02 = ButtonMonster.new({
	pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
	pixelscaleyup=0.7, pixelscaleydown=1,
	text="button 2", ttf=myttf,
	sound=btnsound, volume=volume,
}, 2)
local btn03 = ButtonMonster.new({
	pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
	pixelscaleyup=0.7, pixelscaleydown=1,
	text="button 3", ttf=myttf,
	sound=btnsound, volume=volume,
}, 3)
local btn04 = ButtonMonster.new({
	autoscale=false,
	pixelwidth=8*6, pixelheight=8*30,
	pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
	pixelscalexup=0.7, pixelscalexdown=1,
	pixelcolordown=0x00ff00,
	text="b\nt\nn\n \n4", ttf=myttf,
	sound=btnsound, volume=volume,
}, 4)
local btnexit = ButtonMonster.new({
	pixelcolorup=0xff0000, pixelcolordown=0x00ff00,
	text="EXIT", ttf=myttf,
})
-- position
btn01:setPosition(16*5, 16*3)
btn02:setPosition(16*5, 16*8)
btn03:setPosition(16*5, 16*12.5)
btn04:setPosition(16*12, 16*8)
btnexit:setPosition(16*24, 16*16)
-- order
stage:addChild(btn01)
stage:addChild(btn02)
stage:addChild(btn03)
stage:addChild(btn04)
stage:addChild(btnexit)

-- add listeners
function clicked(btn)
	print(btn.currselector, btn.disabled)
	if btn.currselector == 2 then btn03:setDisabled(not btn03:getDisabled())
	elseif btn.currselector == 5 then
		if not application:isPlayerMode() then application:exit()
		else print("EXIT")
		end
	end
end
btn01:addEventListener("clicked", clicked)
btn02:addEventListener("clicked", clicked)
btn03:addEventListener("clicked", clicked)
btn04:addEventListener("clicked", clicked)
btnexit:addEventListener("clicked", clicked)

Demo 2

-- ButtonMonster DEMO 2: keyboard navigation (arrow keys + ENTER)
application:setBackgroundColor(0x6c6c6c)
-- a gradient bg
local gradient = Pixel.new(0xffffff, 1, application:getContentWidth(), application:getContentHeight())
gradient:setColor(0x0, 1, 0xaa5500, 1, 15*16)
gradient:setAnchorPoint(0.5, 0.5)

-- font
local myttf = TTFont.new("fonts/Cabin-Bold-TTF.ttf", 20)
local myttipttf = TTFont.new("fonts/Cabin-Bold-TTF.ttf", 18)
-- textures
local btnuptex = Texture.new("gfx/ui/btn_01_up.png")
local btndowntex = Texture.new("gfx/ui/btn_01_down.png")
local btndisabledtex = Texture.new("gfx/ui/btn_01_disabled.png")
-- buttons tooltip layer
local tooltiplayer = Sprite.new()
-- buttons sound
local btnsound = {sound=Sound.new("audio/Braam - Retro Pulse.wav"), time=0, delay=0.5} -- delay=0.5, 0.05
local volume = 0.3
-- initial button selected
local selector = 1
-- buttons
local btn01 = ButtonMonster.new({
	pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
	text="button 1", ttf=myttf,
	tooltiptext="btn1", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8*-1, tooltipoffsety=8*3,
	sound=btnsound, volume=volume,
}, 1, tooltiplayer)
local btn02 = ButtonMonster.new({
	pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
	text="button 2", ttf=myttf,
	tooltiptext="click me!", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8*-1, tooltipoffsety=8*3,
	sound=btnsound, volume=volume,
}, 2, tooltiplayer)
local btn03 = ButtonMonster.new({
	pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
	text="button 3", ttf=myttf,
	tooltiptext="btn3", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8*-1, tooltipoffsety=8*3,
	sound=btnsound, volume=volume,
}, 3, tooltiplayer)
local btn04 = ButtonMonster.new({
	autoscale=false,
	pixelwidth=8*5, pixelheight=8*24,
	pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
	ninepatch=32,
	pixelcolordown=0x00ff00,
	text="b\nt\nn\n \n4", ttf=myttf,
	tooltiptext="btn4", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8*-4, tooltipoffsety=8*12,
	sound=btnsound, volume=volume,
}, 4, tooltiplayer)
local btnexit = ButtonMonster.new({
	pixelcolorup=0xff0000, pixelcolordown=0x00ff00,
	text="EXIT", ttf=myttf,
}, 5, tooltiplayer)
-- keyboard navigation
local btns = {}
btns[#btns + 1] = btn01
btns[#btns + 1] = btn02
btns[#btns + 1] = btn03
btns[#btns + 1] = btn04
btns[#btns + 1] = btnexit
-- position
gradient:setPosition(application:getContentWidth()/2, application:getContentHeight()/2)
btn01:setPosition(16*5, 16*3)
btn02:setPosition(16*5, 16*8)
btn03:setPosition(16*5, 16*12.5)
btn04:setPosition(16*12, 16*8)
btnexit:setPosition(16*24, 16*16)
-- order
stage:addChild(gradient)
for k, v in ipairs(btns) do
	stage:addChild(v)
end
stage:addChild(tooltiplayer)

-- shared listener functions
function clicked(input, btn)
	selector = btn.currselector
	print(input, btn.currselector, btn.disabled)
	if btn.currselector == 2 then btn03:setDisabled(not btn03:getDisabled())
	elseif btn.currselector == 5 then
		if not application:isPlayerMode() then application:exit()
		else print("EXIT")
		end
	end
end
-- add listeners
for k, v in ipairs(btns) do
	v:addEventListener("clicked", clicked, "mouse", v)
	v:addEventListener("hovered", function(e) selector = e.currselector end)
	v.btns = btns -- list of navigatable buttons
end

-- keyboard handler
function updateButton()
	for k, v in ipairs(btns) do
		v.currselector = selector
		v:updateVisualState()
		if k == selector then v:selectionSfx() end
	end
end
stage:addEventListener(Event.KEY_DOWN, function(e)
	if e.keyCode == KeyCode.UP or e.keyCode == KeyCode.LEFT then
		selector -= 1 if selector < 1 then selector = #btns end updateButton()
	elseif e.keyCode == KeyCode.DOWN or e.keyCode == KeyCode.RIGHT then
		selector += 1 if selector > #btns then selector = 1 end updateButton()
	elseif e.keyCode == KeyCode.ENTER then
		clicked("keyboard", btns[selector])
	end
end)

-- let's go!
updateButton() -- highlight first button


UI Buttons