Difference between revisions of "ButtonMonster class"

From GiderosMobile
 
(6 intermediate revisions by the same user not shown)
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.
 +
 +
'''[[Media:buttonMonster.lua]]''' '''(tip: right click and save link as)'''
  
 
=== ButtonMonster '''Class''' ===
 
=== ButtonMonster '''Class''' ===
Line 7: Line 9:
 
--[[
 
--[[
 
-- ButtonMonster
 
-- ButtonMonster
 +
-- mokalux, cc0
 
-- Pixel, Image, 9patch, Text, Tooltip,
 
-- Pixel, Image, 9patch, Text, Tooltip,
 
-- Up, Down, Disabled, Hover,
 
-- Up, Down, Disabled, Hover,
 
-- Sfx, Touch, Mouse and Keyboard navigation!
 
-- Sfx, Touch, Mouse and Keyboard navigation!
 +
-- Functions
 +
v 0.2.2: 2024-11-08 selectionSfx(): added channel checks before setting volume
 +
v 0.2.1: 2024-04-22 added function callback (functions with no parameters for now!)
 
v 0.2.0: 2023-12-01 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?
Line 71: Line 77:
 
-- audio?
 
-- audio?
 
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 0.5 -- sound volume
 +
-- EXTRAS
 +
self.params.fun = xparams.fun or nil -- function (please check function name if not working!)
 +
-- set warnings, errors
 +
if self.params.sound and (type(self.params.sound) ~= "table" or self.params.sound.sound == nil) then
 +
print("*** ERROR ***", "ON BUTTON: "..self.selector or "?",
 +
"YOUR SOUND MUST BE A TABLE, SOMETHING LIKE: { sound=Sound.new(\"path/to/your/sound.wav\"), time=0, delay=0.2 }")
 +
self.params.sound = nil
 +
end
 +
if self.params.fun and type(self.params.fun) ~= "function" then
 +
print("*** ERROR ***", "ON BUTTON: "..self.selector or "?", "YOU ARE NOT PASSING A FUNCTION")
 +
if self.params.text then self.params.text = self.params.text.." (error)"
 +
else self.params.text = "error"
 +
end
 +
self.params.ttf = nil self.params.textscalexup = 3 self.params.textscaleyup = 3
 +
self.params.textcolorup = 0xff0000
 +
end
 
-- let's go!
 
-- let's go!
 
self:setButton()
 
self:setButton()
Line 84: Line 106:
 
self:addEventListener(Event.MOUSE_UP, self.onMouseUp, self)
 
self:addEventListener(Event.MOUSE_UP, self.onMouseUp, self)
 
self:addEventListener(Event.MOUSE_HOVER, self.onMouseHover, self)
 
self:addEventListener(Event.MOUSE_HOVER, self.onMouseHover, self)
-- touches event listeners
+
-- touch event listeners
 
self:addEventListener(Event.TOUCHES_BEGIN, self.onTouchesBegin, self)
 
self:addEventListener(Event.TOUCHES_BEGIN, self.onTouchesBegin, self)
 
self:addEventListener(Event.TOUCHES_MOVE, self.onTouchesMove, self)
 
self:addEventListener(Event.TOUCHES_MOVE, self.onTouchesMove, self)
Line 96: Line 118:
 
local textwidth, textheight
 
local textwidth, textheight
 
if self.params.text then
 
if self.params.text then
self.text = TextField.new(self.params.ttf, self.params.text, self.params.text)
+
if self.tf then self:removeChild(self.tf) self.tf = nil end
self.text:setAnchorPoint(0.5, 0.5)
+
self.tf = TextField.new(self.params.ttf, self.params.text, self.params.text)
self.text:setScale(self.params.textscalexup, self.params.textscaleyup)
+
self.tf:setAnchorPoint(0.5, 0.5)
self.text:setTextColor(self.params.textcolorup)
+
self.tf:setScale(self.params.textscalexup, self.params.textscaleyup)
self.text:setAlpha(self.params.textalphaup)
+
self.tf:setTextColor(self.params.textcolorup)
textwidth, textheight = self.text:getWidth(), self.text:getHeight()
+
self.tf:setAlpha(self.params.textalphaup)
 +
textwidth, textheight = self.tf:getWidth(), self.tf:getHeight()
 
end
 
end
 
-- first add pixel
 
-- first add pixel
 +
if self.pixel then self:removeChild(self.pixel) self.pixel = nil end
 
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 120: Line 144:
 
self:addChild(self.pixel)
 
self:addChild(self.pixel)
 
-- then add text?
 
-- then add text?
if self.params.text then self:addChild(self.text) end
+
if self.params.text then self:addChild(self.tf) end
 
-- finally add tooltip?
 
-- finally add tooltip?
 
if self.params.tooltiptext then
 
if self.params.tooltiptext then
self.ttiptext = TextField.new(self.params.tooltipttf, self.params.tooltiptext, self.params.tooltiptext)
+
if self.ttiptf then
self.ttiptext:setScale(self.params.tooltiptextscale)
+
if self.tooltiplayer then self.tooltiplayer:removeChild(self.ttiptf)
self.ttiptext:setTextColor(self.params.tooltiptextcolor)
+
else self:removeChild(self.ttiptf)
self.ttiptext:setVisible(false)
+
end self.ttiptf = nil
if self.tooltiplayer then self.tooltiplayer:addChild(self.ttiptext)
+
end
else self:addChild(self.ttiptext)
+
self.ttiptf = TextField.new(self.params.tooltipttf, self.params.tooltiptext, self.params.tooltiptext)
 +
self.ttiptf:setScale(self.params.tooltiptextscale)
 +
self.ttiptf:setTextColor(self.params.tooltiptextcolor)
 +
self.ttiptf:setVisible(false)
 +
if self.tooltiplayer then self.tooltiplayer:addChild(self.ttiptf)
 +
else self:addChild(self.ttiptf)
 
end
 
end
 
end
 
end
Line 139: Line 168:
 
btn:setAlpha(btnalpha)
 
btn:setAlpha(btnalpha)
 
if btn.params.text then
 
if btn.params.text then
btn.text:setTextColor(textcolor)
+
btn.tf:setTextColor(textcolor)
btn.text:setScale(textscalex, textscaley)
+
btn.tf:setScale(textscalex, textscaley)
 
end
 
end
 
if pixeltex then btn.pixel:setTexture(pixeltex) end
 
if pixeltex then btn.pixel:setTexture(pixeltex) end
Line 154: Line 183:
 
v.params.pixelimgdisabled, v.params.pixelcolordisabled,
 
v.params.pixelimgdisabled, v.params.pixelcolordisabled,
 
v.params.pixelalphadown, v.params.pixelscalexdown, v.params.pixelscaleydown)
 
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.ttiptf 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.ttiptf then -- OPTION 2: shows tooltip even if button is Disabled, you choose!
v.ttiptext:setText("("..v.params.tooltiptext..")") -- extra!
+
v.ttiptf:setText("("..v.params.tooltiptext..")") -- extra!
if k == v.currselector then v.ttiptext:setVisible(true)
+
if k == v.currselector then v.ttiptf:setVisible(true)
else v.ttiptext:setVisible(false)
+
else v.ttiptf:setVisible(false)
 
end
 
end
 
end
 
end
Line 166: Line 195:
 
v.params.pixelimgdown, v.params.pixelcolordown,
 
v.params.pixelimgdown, v.params.pixelcolordown,
 
v.params.pixelalphadown, v.params.pixelscalexdown, v.params.pixelscaleydown)
 
v.params.pixelalphadown, v.params.pixelscalexdown, v.params.pixelscaleydown)
if v.ttiptext then
+
if v.ttiptf then
v.ttiptext:setText(v.params.tooltiptext)
+
v.ttiptf:setText(v.params.tooltiptext)
 
if v.tooltiplayer then -- reset tooltip text position
 
if v.tooltiplayer then -- reset tooltip text position
v.ttiptext:setPosition(
+
v.ttiptf:setPosition(
 
v:getX()+v.params.tooltipoffsetx, v:getY()+v.params.tooltipoffsety)
 
v:getX()+v.params.tooltipoffsetx, v:getY()+v.params.tooltipoffsety)
 
else
 
else
v.ttiptext:setPosition(v:globalToLocal(
+
v.ttiptf:setPosition(v:globalToLocal(
 
v:getX()+v.params.tooltipoffsetx, v:getY()+v.params.tooltipoffsety))
 
v:getX()+v.params.tooltipoffsetx, v:getY()+v.params.tooltipoffsety))
 
end
 
end
v.ttiptext:setVisible(true)
+
v.ttiptf:setVisible(true)
 
end
 
end
 
else -- upState
 
else -- upState
Line 182: Line 211:
 
v.params.pixelimgup, v.params.pixelcolorup,
 
v.params.pixelimgup, v.params.pixelcolorup,
 
v.params.pixelalphaup, v.params.pixelscalexup, v.params.pixelscaleyup)
 
v.params.pixelalphaup, v.params.pixelscalexup, v.params.pixelscaleyup)
if v.ttiptext then v.ttiptext:setVisible(false) end
+
if v.ttiptf then v.ttiptf:setVisible(false) end
 
end
 
end
 
end
 
end
Line 192: Line 221:
 
self.params.pixelimgdisabled, self.params.pixelcolordisabled,
 
self.params.pixelimgdisabled, self.params.pixelcolordisabled,
 
self.params.pixelalphadown, self.params.pixelscalexdown, self.params.pixelscaleydown)
 
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.ttiptf 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.ttiptf then -- OPTION 2: shows tooltip even if button is Disabled, you choose!
self.ttiptext:setText("("..self.params.tooltiptext..")") -- extra!
+
self.ttiptf:setText("("..self.params.tooltiptext..")") -- extra!
if self.focus then self.ttiptext:setVisible(true)
+
if self.focus then self.ttiptf:setVisible(true)
else self.ttiptext:setVisible(false)
+
else self.ttiptf:setVisible(false)
 
end
 
end
 
end
 
end
Line 204: Line 233:
 
self.params.pixelimgdown, self.params.pixelcolordown,
 
self.params.pixelimgdown, self.params.pixelcolordown,
 
self.params.pixelalphadown, self.params.pixelscalexdown, self.params.pixelscaleydown)
 
self.params.pixelalphadown, self.params.pixelscalexdown, self.params.pixelscaleydown)
if self.ttiptext then
+
if self.ttiptf then
self.ttiptext:setText(self.params.tooltiptext)
+
self.ttiptf:setText(self.params.tooltiptext)
self.ttiptext:setVisible(true)
+
self.ttiptf:setVisible(true)
 
end
 
end
 
else -- upState
 
else -- upState
Line 213: Line 242:
 
self.params.pixelimgup, self.params.pixelcolorup,
 
self.params.pixelimgup, self.params.pixelcolorup,
 
self.params.pixelalphaup, self.params.pixelscalexup, self.params.pixelscaleyup)
 
self.params.pixelalphaup, self.params.pixelscalexup, self.params.pixelscaleyup)
if self.ttiptext then self.ttiptext:setVisible(false) end
+
if self.ttiptf then self.ttiptf:setVisible(false) end
 
end
 
end
 
end
 
end
 
end
 
end
  
-- DISABLED
+
-- SOME UTILITY FUNCTIONS
 +
function ButtonMonster:changeText(xtext)
 +
self.params.text = xtext
 +
self:setButton()
 +
end
 +
function ButtonMonster:getText()
 +
return self.params.text
 +
end
 
function ButtonMonster:setDisabled(disabled)
 
function ButtonMonster:setDisabled(disabled)
 
if self.disabled == disabled then return end
 
if self.disabled == disabled then return end
Line 231: Line 267:
 
-- MOUSE
 
-- MOUSE
 
function ButtonMonster:onMouseDown(ev) -- both mouse and touch
 
function ButtonMonster:onMouseDown(ev) -- both mouse and touch
if self:hitTestPoint(ev.x, ev.y, true) then
+
if self:hitTestPoint(ev.x, ev.y, true) then -- (x,y,onlyvisible,ref)
 
self.focus = true
 
self.focus = true
 
if self.btns then -- update keyboard button id selector
 
if self.btns then -- update keyboard button id selector
Line 237: Line 273:
 
end
 
end
 
self:updateVisualState()
 
self:updateVisualState()
self:selectionSfx() -- play sound fx?
+
-- self:selectionSfx() -- play sound fx, you choose!
if self.ttiptext then -- set tooltip initial position
+
local e = Event.new("pressed")
 +
e.currselector = self.selector -- update button id selector
 +
e.disabled = self.disabled -- update button disabled
 +
self:dispatchEvent(e) -- button is clicked, dispatch "clicked" event
 +
-- if self.params.fun then self.params.fun(self:getParent()) end -- YOU CAN ADD THIS HERE
 +
if self.ttiptf then -- set tooltip initial position
 
if self.tooltiplayer then
 
if self.tooltiplayer then
self.ttiptext:setPosition(
+
self.ttiptf:setPosition(
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
 
else
 
else
self.ttiptext:setPosition(self:globalToLocal(
+
self.ttiptf:setPosition(self:globalToLocal(
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
 
end
 
end
Line 252: Line 293:
 
function ButtonMonster:onMouseMove(ev) -- both mouse and touch
 
function ButtonMonster:onMouseMove(ev) -- both mouse and touch
 
if self.focus then
 
if self.focus then
if self.ttiptext then -- tooltip follows position
+
if self.ttiptf then -- tooltip follows position
 
if self.tooltiplayer then
 
if self.tooltiplayer then
self.ttiptext:setPosition(
+
self.ttiptf:setPosition(
 
ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety)
 
ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety)
 
else
 
else
self.ttiptext:setPosition(self:globalToLocal(
+
self.ttiptf:setPosition(self:globalToLocal(
 
ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety))
 
ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety))
 
end
 
end
 
end
 
end
if not self:hitTestPoint(ev.x, ev.y, true) then
+
if not self:hitTestPoint(ev.x, ev.y, true) then -- (x,y,onlyvisible,ref)
 
self.focus = false
 
self.focus = false
 
self:updateVisualState()
 
self:updateVisualState()
 
end
 
end
 
ev:stopPropagation()
 
ev:stopPropagation()
elseif self.ttiptext then -- reset tooltip text position
+
elseif self.ttiptf then -- reset tooltip text position
 
if self.tooltiplayer then
 
if self.tooltiplayer then
self.ttiptext:setPosition(
+
self.ttiptf:setPosition(
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
 
else
 
else
self.ttiptext:setPosition(self:globalToLocal(
+
self.ttiptf:setPosition(self:globalToLocal(
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
 
end
 
end
Line 284: Line 325:
 
e.disabled = self.disabled -- update button disabled
 
e.disabled = self.disabled -- update button disabled
 
self:dispatchEvent(e) -- button is clicked, dispatch "clicked" event
 
self:dispatchEvent(e) -- button is clicked, dispatch "clicked" event
 +
if self.params.fun then self.params.fun(self:getParent()) end -- OR EVEN HERE?
 
ev:stopPropagation()
 
ev:stopPropagation()
 
end
 
end
Line 291: Line 333:
 
self.focus = true
 
self.focus = true
 
self.hover = true
 
self.hover = true
if self.ttiptext then -- tooltip follows mouse position
+
if self.ttiptf then -- tooltip follows mouse position
 
if self.tooltiplayer then
 
if self.tooltiplayer then
self.ttiptext:setPosition(
+
self.ttiptf:setPosition(
 
ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety)
 
ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety)
 
else
 
else
self.ttiptext:setPosition(self:globalToLocal(
+
self.ttiptf:setPosition(self:globalToLocal(
 
ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety))
 
ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety))
 
end
 
end
Line 311: Line 353:
 
e.disabled = self.disabled -- update button disabled
 
e.disabled = self.disabled -- update button disabled
 
self:dispatchEvent(e)
 
self:dispatchEvent(e)
-- self:selectionSfx() -- play sound fx? you choose!
+
self:selectionSfx() -- play sound fx? you choose!
 
-- trick to remove residuals when fast moving mouse
 
-- trick to remove residuals when fast moving mouse
local timer = Timer.new(100*1, 1) -- number of repetition, the higher the safer
+
local timer = Timer.new(100*1, 1) -- number of repetition, the higher the safer, (delay,repeatCount)
 
timer:addEventListener(Event.TIMER, function() self:updateVisualState() end)
 
timer:addEventListener(Event.TIMER, function() self:updateVisualState() end)
 
timer:start()
 
timer:start()
 +
-- if self.params.fun then self.params.fun(self:getParent()) end -- HERE COULD ALSO BE USEFUL?
 
else
 
else
self.hover = false -- XXX
 
 
self.onexit = true
 
self.onexit = true
 
end
 
end
Line 326: Line 368:
 
self.onenter = false
 
self.onenter = false
 
self.moving = false
 
self.moving = false
if self.onexit then
+
if self.onexit then -- execute onexit code only once
-- execute onexit code only once
 
 
self.onexit = false
 
self.onexit = false
 
self:updateVisualState()
 
self:updateVisualState()
Line 351: Line 392:
 
if self.focus then
 
if self.focus then
 
ev:stopPropagation()
 
ev:stopPropagation()
elseif self.ttiptext then -- reset tooltip text position
+
elseif self.ttiptf then -- reset tooltip text position
 
if self.tooltiplayer then
 
if self.tooltiplayer then
self.ttiptext:setPosition(
+
self.ttiptf:setPosition(
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
 
else
 
else
self.ttiptext:setPosition(self:globalToLocal(
+
self.ttiptf:setPosition(self:globalToLocal(
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
 
end
 
end
Line 369: Line 410:
 
end
 
end
 
self:updateVisualState()
 
self:updateVisualState()
elseif self.ttiptext then -- reset tooltip text position
+
elseif self.ttiptf then -- reset tooltip text position
 
if self.tooltiplayer then
 
if self.tooltiplayer then
self.ttiptext:setPosition(
+
self.ttiptf:setPosition(
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
 
else
 
else
self.ttiptext:setPosition(self:globalToLocal(
+
self.ttiptf:setPosition(self:globalToLocal(
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
 
self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety))
 
end
 
end
Line 387: Line 428:
 
local prev = snd.time
 
local prev = snd.time
 
if curr - prev > snd.delay then
 
if curr - prev > snd.delay then
snd.sound:play():setVolume(self.params.volume)
+
local channel = snd.sound:play()
 +
if channel then
 +
channel:setVolume(self.params.volume)
 +
end
 
snd.time = curr
 
snd.time = curr
 
end
 
end
 
end
 
end
 +
end
 +
function ButtonMonster:setVolume(xvolume)
 +
self.params.volume = xvolume
 +
end
 +
function ButtonMonster:getVolume()
 +
return self.params.volume
 
end
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 474: Line 524:
  
 
'''Demo 2'''
 
'''Demo 2'''
 +
 +
Keyboard navigation (arrow keys + ENTER).
  
 
{{#widget:GApp|app=ButtonMonsterDemo2x.GApp|width=480|height=320}}
 
{{#widget:GApp|app=ButtonMonsterDemo2x.GApp|width=480|height=320}}

Latest revision as of 02:31, 9 November 2024

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.

Media:buttonMonster.lua (tip: right click and save link as)

ButtonMonster Class

--[[
-- ButtonMonster
-- mokalux, cc0
-- Pixel, Image, 9patch, Text, Tooltip,
-- Up, Down, Disabled, Hover,
-- Sfx, Touch, Mouse and Keyboard navigation!
-- Functions
v 0.2.2: 2024-11-08 selectionSfx(): added channel checks before setting volume
v 0.2.1: 2024-04-22 added function callback (functions with no parameters for 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.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 0.5 -- sound volume
	-- EXTRAS
	self.params.fun = xparams.fun or nil -- function (please check function name if not working!)
	-- set warnings, errors
	if self.params.sound and (type(self.params.sound) ~= "table" or self.params.sound.sound == nil) then
		print("*** ERROR ***", "ON BUTTON: "..self.selector or "?",
			"YOUR SOUND MUST BE A TABLE, SOMETHING LIKE: { sound=Sound.new(\"path/to/your/sound.wav\"), time=0, delay=0.2 }")
		self.params.sound = nil
	end
	if self.params.fun and type(self.params.fun) ~= "function" then
		print("*** ERROR ***", "ON BUTTON: "..self.selector or "?", "YOU ARE NOT PASSING A FUNCTION")
		if self.params.text then self.params.text = self.params.text.." (error)"
		else self.params.text = "error"
		end
		self.params.ttf = nil self.params.textscalexup = 3 self.params.textscaleyup = 3
		self.params.textcolorup = 0xff0000
	end
	-- 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)
	-- touch 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
		if self.tf then self:removeChild(self.tf) self.tf = nil end
		self.tf = TextField.new(self.params.ttf, self.params.text, self.params.text)
		self.tf:setAnchorPoint(0.5, 0.5)
		self.tf:setScale(self.params.textscalexup, self.params.textscaleyup)
		self.tf:setTextColor(self.params.textcolorup)
		self.tf:setAlpha(self.params.textalphaup)
		textwidth, textheight = self.tf:getWidth(), self.tf:getHeight()
	end
	-- first add pixel
	if self.pixel then self:removeChild(self.pixel) self.pixel = nil end
	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.tf) end
	-- finally add tooltip?
	if self.params.tooltiptext then
		if self.ttiptf then
			if self.tooltiplayer then self.tooltiplayer:removeChild(self.ttiptf)
			else self:removeChild(self.ttiptf)
			end self.ttiptf = nil
		end
		self.ttiptf = TextField.new(self.params.tooltipttf, self.params.tooltiptext, self.params.tooltiptext)
		self.ttiptf:setScale(self.params.tooltiptextscale)
		self.ttiptf:setTextColor(self.params.tooltiptextcolor)
		self.ttiptf:setVisible(false)
		if self.tooltiplayer then self.tooltiplayer:addChild(self.ttiptf)
		else self:addChild(self.ttiptf)
		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.tf:setTextColor(textcolor)
			btn.tf: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.ttiptf and not v.disabled then -- OPTION 1: hides tooltip when button is Disabled
				if v.ttiptf then -- OPTION 2: shows tooltip even if button is Disabled, you choose!
					v.ttiptf:setText("("..v.params.tooltiptext..")") -- extra!
					if k == v.currselector then v.ttiptf:setVisible(true)
					else v.ttiptf: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.ttiptf then
					v.ttiptf:setText(v.params.tooltiptext)
					if v.tooltiplayer then -- reset tooltip text position
						v.ttiptf:setPosition(
							v:getX()+v.params.tooltipoffsetx, v:getY()+v.params.tooltipoffsety)
					else
						v.ttiptf:setPosition(v:globalToLocal(
							v:getX()+v.params.tooltipoffsetx, v:getY()+v.params.tooltipoffsety))
					end
					v.ttiptf: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.ttiptf then v.ttiptf: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.ttiptf and not self.disabled then -- OPTION 1: hides tooltip when button is Disabled
			if self.ttiptf then -- OPTION 2: shows tooltip even if button is Disabled, you choose!
				self.ttiptf:setText("("..self.params.tooltiptext..")") -- extra!
				if self.focus then self.ttiptf:setVisible(true)
				else self.ttiptf: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.ttiptf then
				self.ttiptf:setText(self.params.tooltiptext)
				self.ttiptf: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.ttiptf then self.ttiptf:setVisible(false) end
		end
	end
end

-- SOME UTILITY FUNCTIONS
function ButtonMonster:changeText(xtext)
	self.params.text = xtext
	self:setButton()
end
function ButtonMonster:getText()
	return self.params.text
end
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 -- (x,y,onlyvisible,ref)
		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, you choose!
		local e = Event.new("pressed")
		e.currselector = self.selector -- update button id selector
		e.disabled = self.disabled -- update button disabled
		self:dispatchEvent(e) -- button is clicked, dispatch "clicked" event
--		if self.params.fun then self.params.fun(self:getParent()) end -- YOU CAN ADD THIS HERE
		if self.ttiptf then -- set tooltip initial position
			if self.tooltiplayer then
				self.ttiptf:setPosition(
					self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
			else
				self.ttiptf: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.ttiptf then -- tooltip follows position
			if self.tooltiplayer then
				self.ttiptf:setPosition(
					ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety)
			else
				self.ttiptf: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 -- (x,y,onlyvisible,ref)
			self.focus = false
			self:updateVisualState()
		end
		ev:stopPropagation()
	elseif self.ttiptf then -- reset tooltip text position
		if self.tooltiplayer then
			self.ttiptf:setPosition(
				self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
		else
			self.ttiptf: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
		if self.params.fun then self.params.fun(self:getParent()) end -- OR EVEN HERE?
		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.ttiptf then -- tooltip follows mouse position
			if self.tooltiplayer then
				self.ttiptf:setPosition(
					ev.x + self.params.tooltipoffsetx, ev.y + self.params.tooltipoffsety)
			else
				self.ttiptf: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, (delay,repeatCount)
			timer:addEventListener(Event.TIMER, function() self:updateVisualState() end)
			timer:start()
--			if self.params.fun then self.params.fun(self:getParent()) end -- HERE COULD ALSO BE USEFUL?
		else
			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.ttiptf then -- reset tooltip text position
		if self.tooltiplayer then
			self.ttiptf:setPosition(
				self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
		else
			self.ttiptf: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.ttiptf then -- reset tooltip text position
		if self.tooltiplayer then
			self.ttiptf:setPosition(
				self:getX()+self.params.tooltipoffsetx, self:getY()+self.params.tooltipoffsety)
		else
			self.ttiptf: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
			local channel = snd.sound:play()
			if channel then
				channel:setVolume(self.params.volume)
			end
			snd.time = curr
		end
	end
end
function ButtonMonster:setVolume(xvolume)
	self.params.volume = xvolume
end
function ButtonMonster:getVolume()
	return self.params.volume
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

Keyboard navigation (arrow keys + ENTER).

-- 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