Difference between revisions of "ButtonMonster class"
From GiderosMobile
(Created page with "__TOC__ This button class is useful to build buttons which you can navigate using the mouse and the keyboard. It is optimised for menus and in game. '''Note: needs Gideros...") |
|||
Line 9: | Line 9: | ||
--[[ | --[[ | ||
-- ButtonMonster | -- ButtonMonster | ||
− | -- | + | -- Pixel, Image, 9patch, Text, Tooltip, |
− | + | -- Up, Down, Disabled, Hover, | |
− | -- Up | + | -- 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-11- | ||
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! | + | -- Shader, please adapt the path accordingly! |
--!NEEDS:../luashader/luashader.lua | --!NEEDS:../luashader/luashader.lua | ||
Line 115: | Line 114: | ||
if self.params.pixelalphaup <= 0 then self.params.pixelalphaup = 0.01 end -- alpha <= 0 breaks shader! | 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! | 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() | ||
-- update visual state | -- update visual state | ||
+ | self.focus = false | ||
+ | self.hover = false | ||
+ | self.disabled = false | ||
self:updateVisualState() | self:updateVisualState() | ||
− | + | -- mouse event listeners | |
− | |||
− | |||
− | -- event listeners | ||
self:addEventListener(Event.MOUSE_DOWN, self.onMouseDown, self) | 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_UP, self.onMouseUp, self) | ||
self:addEventListener(Event.MOUSE_HOVER, self.onMouseHover, 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 | end | ||
Line 153: | Line 155: | ||
end | end | ||
if self.params.pixelimgup then | if self.params.pixelimgup then | ||
− | self.pixel:setTexture(self.params.pixelimgup, 0) | + | self.pixel:setTexture(self.params.pixelimgup, 0) -- Texture, slot |
pixelimg = true | pixelimg = true | ||
end | end | ||
if self.params.pixelimgdown then | if self.params.pixelimgdown then | ||
− | self.pixel:setTexture(self.params.pixelimgdown, 1) | + | self.pixel:setTexture(self.params.pixelimgdown, 1) -- Texture, slot |
pixelimg = true | pixelimg = true | ||
end | end | ||
if self.params.pixelimgdisabled then | if self.params.pixelimgdisabled then | ||
− | self.pixel:setTexture(self.params.pixelimgdisabled, 2) | + | self.pixel:setTexture(self.params.pixelimgdisabled, 2) -- Texture, slot |
pixelimg = true | 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 | + | if pixelimg then self.pixel:setShader(shaderpixelslot) end -- apply shader |
− | self | + | self:addChild(self.pixel) |
-- then add text? | -- then add text? | ||
− | if self.params.text then self | + | if self.params.text then self:addChild(self.text) end |
-- finally add tooltip? | -- finally add tooltip? | ||
if self.params.tooltiptext then | if self.params.tooltiptext then | ||
Line 182: | Line 184: | ||
end | end | ||
− | |||
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, | ||
Line 200: | Line 201: | ||
end | end | ||
btn.pixel:setScale(pixelscalex, pixelscaley) | btn.pixel:setScale(pixelscalex, pixelscaley) | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
if self.btns then | if self.btns then | ||
-- print("KEYBOARD NAVIGATION") | -- print("KEYBOARD NAVIGATION") | ||
for k, v in ipairs(self.btns) do | for k, v in ipairs(self.btns) do | ||
− | if v.disabled then -- | + | if v.disabled then -- disabledState |
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) | 2.0, 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 | |
− | elseif k == v.currselector then -- | + | 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, | 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) | 1.0, v.params.pixelcolordown, v.params.pixelalphadown, v.params.pixelscalexdown, v.params.pixelscaleydown) | ||
− | + | if v.ttiptext then | |
− | else -- | + | 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, | 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) | 0.0, v.params.pixelcolorup, v.params.pixelalphaup, v.params.pixelscalexup, v.params.pixelscaleyup) | ||
− | + | if v.ttiptext then v.ttiptext:setVisible(false) end | |
end | end | ||
end | end | ||
− | + | else | |
− | -- print("MOUSE NAVIGATION") | + | -- print("TOUCH, MOUSE NAVIGATION") |
− | + | if self.disabled then -- disabledState | |
− | if self.disabled then -- | ||
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, | ||
Line 252: | Line 246: | ||
-- 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! | ||
− | + | self.ttiptext:setText("("..self.params.tooltiptext..")") -- extra! | |
− | + | if self.focus then self.ttiptext:setVisible(true) | |
− | |||
− | if self. | ||
else self.ttiptext:setVisible(false) | else self.ttiptext:setVisible(false) | ||
end | end | ||
end | end | ||
− | elseif self. | + | elseif self.focus or self.hover then -- downState |
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) | 1.0, self.params.pixelcolordown, self.params.pixelalphadown, self.params.pixelscalexdown, self.params.pixelscaleydown) | ||
− | if self.ttiptext then self.ttiptext:setVisible(true) end | + | if self.ttiptext then |
− | else -- | + | self.ttiptext:setText(self.params.tooltiptext) |
+ | self.ttiptext:setVisible(true) | ||
+ | end | ||
+ | else -- upState | ||
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, | ||
Line 273: | Line 268: | ||
end | end | ||
− | -- | + | -- DISABLED |
function ButtonMonster:setDisabled(disabled) | function ButtonMonster:setDisabled(disabled) | ||
if self.disabled == disabled then return end | if self.disabled == disabled then return end | ||
Line 283: | Line 278: | ||
end | end | ||
− | -- MOUSE | + | -- LISTENERS |
− | function ButtonMonster:onMouseDown(ev) | + | -- MOUSE |
− | if self | + | 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() | 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 | ||
end | end | ||
− | function ButtonMonster:onMouseUp(ev) | + | function ButtonMonster:onMouseUp(ev) -- both mouse and touch |
− | if self. | + | if self.focus then |
+ | self.focus = false | ||
+ | self:updateVisualState() | ||
local e = Event.new("clicked") | local e = Event.new("clicked") | ||
e.currselector = self.selector -- update button id selector | e.currselector = self.selector -- update button id selector | ||
Line 299: | Line 337: | ||
end | end | ||
end | end | ||
− | function ButtonMonster:onMouseHover(ev) | + | function ButtonMonster:onMouseHover(ev) -- mouse only |
− | if self | + | if self:hitTestPoint(ev.x, ev.y, true) then -- onenter |
− | self. | + | self.focus = true |
− | self. | + | self.hover = true |
if self.ttiptext then -- tooltip follows mouse position | if self.ttiptext then -- tooltip follows mouse position | ||
if self.tooltiplayer then | if self.tooltiplayer then | ||
Line 308: | Line 346: | ||
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 | + | self.ttiptext: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 319: | Line 357: | ||
for k, v in ipairs(self.btns) do v.currselector = self.selector end | for k, v in ipairs(self.btns) do v.currselector = self.selector end | ||
end | end | ||
− | local e = Event.new("hovered") -- | + | local e = Event.new("hovered") -- dispatch "hovered" event |
e.currselector = self.selector -- update button id selector | e.currselector = self.selector -- update button id selector | ||
+ | e.disabled = self.disabled -- update button disabled | ||
self:dispatchEvent(e) | self:dispatchEvent(e) | ||
− | + | -- 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 | ||
Line 332: | Line 371: | ||
ev:stopPropagation() | ev:stopPropagation() | ||
else -- onexit | else -- onexit | ||
− | self. | + | self.focus = false |
+ | self.hover = false | ||
self.onenter = false | self.onenter = false | ||
self.moving = false | self.moving = false | ||
Line 343: | Line 383: | ||
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() | function ButtonMonster:selectionSfx() | ||
if self.params.sound then | if self.params.sound then | ||
Line 359: | Line 441: | ||
=== ButtonMonster '''Demos''' === | === ButtonMonster '''Demos''' === | ||
'''Demo 1''' | '''Demo 1''' | ||
+ | |||
+ | {{#widget:GApp|app=ButtonMonsterDemo1x.GApp|width=480|height=320}} | ||
<syntaxhighlight lang="lua"> | <syntaxhighlight lang="lua"> | ||
− | -- DEMO 1 | + | -- ButtonMonster DEMO 1 |
− | + | application:setBackgroundColor(0x6c6c6c) | |
− | application:setBackgroundColor( | ||
− | -- | + | -- font |
− | local | + | 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 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 | -- buttons | ||
− | local btn01 = ButtonMonster.new({ | + | local btn01 = ButtonMonster.new({ |
− | local btn02 = ButtonMonster.new({ | + | pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, |
− | local btn03 = ButtonMonster.new({ | + | text="button 1", ttf=myttf, |
− | local btn04 = ButtonMonster.new({ | + | sound=btnsound, volume=volume, |
− | + | }, 1) | |
− | local | + | local btn02 = ButtonMonster.new({ |
− | + | pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, | |
− | + | text="button 2", ttf=myttf, | |
− | + | sound=btnsound, volume=volume, | |
− | + | }, 2) | |
+ | local btn03 = ButtonMonster.new({ | ||
+ | pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, | ||
+ | text="button 3", ttf=myttf, | ||
+ | sound=btnsound, volume=volume, | ||
+ | }, 3) | ||
+ | local btn04 = ButtonMonster.new({ | ||
+ | autoscale=false, | ||
+ | pixelwidth=8*5, pixelheight=8*24, | ||
+ | pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, | ||
+ | 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 | -- position | ||
btn01:setPosition(16*5, 16*3) | btn01:setPosition(16*5, 16*3) | ||
− | btn02:setPosition(16*5, 16* | + | btn02:setPosition(16*5, 16*8) |
− | btn03:setPosition(16*5, 16* | + | btn03:setPosition(16*5, 16*12.5) |
− | btn04:setPosition(16* | + | btn04:setPosition(16*12, 16*8) |
+ | btnexit:setPosition(16*24, 16*16) | ||
-- order | -- order | ||
stage:addChild(btn01) | stage:addChild(btn01) | ||
Line 387: | Line 496: | ||
stage:addChild(btn03) | stage:addChild(btn03) | ||
stage:addChild(btn04) | stage:addChild(btn04) | ||
+ | stage:addChild(btnexit) | ||
− | -- | + | -- add listeners |
− | function clicked( | + | function clicked(btn) |
− | print( | + | 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 | ||
end | end | ||
− | + | btn01:addEventListener("clicked", clicked) | |
− | + | btn02:addEventListener("clicked", clicked) | |
− | + | btn03:addEventListener("clicked", clicked) | |
+ | btn04:addEventListener("clicked", clicked) | ||
+ | btnexit:addEventListener("clicked", clicked) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
'''Demo 2''' | '''Demo 2''' | ||
− | {{#widget:GApp|app= | + | {{#widget:GApp|app=ButtonMonsterDemo2x.GApp|width=480|height=320}} |
<syntaxhighlight lang="lua"> | <syntaxhighlight lang="lua"> | ||
− | -- DEMO 2 | + | -- ButtonMonster DEMO 2: keyboard navigation (arrow keys + ENTER) |
+ | application:setBackgroundColor(0x6c6c6c) | ||
-- a gradient bg | -- a gradient bg | ||
local gradient = Pixel.new(0xffffff, 1, application:getContentWidth(), application:getContentHeight()) | local gradient = Pixel.new(0xffffff, 1, application:getContentWidth(), application:getContentHeight()) | ||
Line 448: | Line 543: | ||
local btn01 = ButtonMonster.new({ | local btn01 = ButtonMonster.new({ | ||
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, | pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, | ||
− | text=" | + | text="button 1", ttf=myttf, |
− | tooltiptext="btn1", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8* | + | tooltiptext="btn1", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8*-1, tooltipoffsety=8*3, |
sound=btnsound, volume=volume, | sound=btnsound, volume=volume, | ||
}, 1, tooltiplayer) | }, 1, tooltiplayer) | ||
local btn02 = ButtonMonster.new({ | local btn02 = ButtonMonster.new({ | ||
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, | pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, | ||
− | text=" | + | text="button 2", ttf=myttf, |
− | tooltiptext="click me!", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8*1, tooltipoffsety=8*3, | + | tooltiptext="click me!", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8*-1, tooltipoffsety=8*3, |
sound=btnsound, volume=volume, | sound=btnsound, volume=volume, | ||
}, 2, tooltiplayer) | }, 2, tooltiplayer) | ||
local btn03 = ButtonMonster.new({ | local btn03 = ButtonMonster.new({ | ||
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, | pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, | ||
− | text=" | + | text="button 3", ttf=myttf, |
− | tooltiptext="btn3", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8* | + | tooltiptext="btn3", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8*-1, tooltipoffsety=8*3, |
sound=btnsound, volume=volume, | sound=btnsound, volume=volume, | ||
}, 3, tooltiplayer) | }, 3, tooltiplayer) | ||
local btn04 = ButtonMonster.new({ | local btn04 = ButtonMonster.new({ | ||
autoscale=false, | autoscale=false, | ||
− | pixelwidth=8* | + | pixelwidth=8*5, pixelheight=8*24, |
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, | pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex, | ||
pixelcolordown=0x00ff00, | pixelcolordown=0x00ff00, | ||
− | text="b\nt\nn\n4", ttf=myttf, | + | text="b\nt\nn\n \n4", ttf=myttf, |
− | tooltiptext="btn4", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8*4, | + | tooltiptext="btn4", tooltipttf=myttipttf, tooltiptextcolor=0xaaff00, tooltipoffsetx=8*-4, tooltipoffsety=8*12, |
sound=btnsound, volume=volume, | sound=btnsound, volume=volume, | ||
}, 4, tooltiplayer) | }, 4, tooltiplayer) | ||
+ | local btnexit = ButtonMonster.new({ | ||
+ | pixelcolorup=0xff0000, pixelcolordown=0x00ff00, | ||
+ | text="EXIT", ttf=myttf, | ||
+ | }, 5, tooltiplayer) | ||
-- keyboard navigation | -- keyboard navigation | ||
local btns = {} | local btns = {} | ||
Line 479: | Line 578: | ||
btns[#btns + 1] = btn03 | btns[#btns + 1] = btn03 | ||
btns[#btns + 1] = btn04 | btns[#btns + 1] = btn04 | ||
+ | btns[#btns + 1] = btnexit | ||
-- position | -- position | ||
gradient:setPosition(application:getContentWidth()/2, application:getContentHeight()/2) | gradient:setPosition(application:getContentWidth()/2, application:getContentHeight()/2) | ||
Line 485: | Line 585: | ||
btn03:setPosition(16*5, 16*12.5) | btn03:setPosition(16*5, 16*12.5) | ||
btn04:setPosition(16*12, 16*8) | btn04:setPosition(16*12, 16*8) | ||
+ | btnexit:setPosition(16*24, 16*16) | ||
-- order | -- order | ||
stage:addChild(gradient) | stage:addChild(gradient) | ||
− | + | for k, v in ipairs(btns) do | |
− | + | stage:addChild(v) | |
− | stage:addChild( | + | end |
− | |||
stage:addChild(tooltiplayer) | stage:addChild(tooltiplayer) | ||
-- shared listener functions | -- shared listener functions | ||
function clicked(input, btn) | function clicked(input, btn) | ||
+ | selector = btn.currselector | ||
print(input, btn.currselector, btn.disabled) | print(input, btn.currselector, btn.disabled) | ||
− | if btn.currselector == 2 then btn03:setDisabled(not btn03:getDisabled()) end | + | 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 | end | ||
-- add listeners | -- add listeners | ||
for k, v in ipairs(btns) do | for k, v in ipairs(btns) do | ||
v:addEventListener("clicked", clicked, "mouse", v) | v:addEventListener("clicked", clicked, "mouse", v) | ||
− | v:addEventListener("hovered", function(e) selector = e.currselector end) | + | v:addEventListener("hovered", function(e) selector = e.currselector end) |
v.btns = btns -- list of navigatable buttons | v.btns = btns -- list of navigatable buttons | ||
end | end | ||
-- keyboard handler | -- 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) | stage:addEventListener(Event.KEY_DOWN, function(e) | ||
if e.keyCode == KeyCode.UP or e.keyCode == KeyCode.LEFT then | if e.keyCode == KeyCode.UP or e.keyCode == KeyCode.LEFT then | ||
Line 515: | Line 628: | ||
end | end | ||
end) | end) | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
-- let's go! | -- let's go! |
Revision as of 21:37, 29 November 2023
This button class is useful to build buttons which you can navigate using the mouse and the keyboard. It is optimised for menus and in game.
Note: needs Gideros luashader Library
ButtonMonster Class
--[[
-- ButtonMonster
-- Pixel, Image, 9patch, Text, Tooltip,
-- Up, Down, Disabled, Hover,
-- Sfx, Touch, Mouse and Keyboard navigation!
v 0.2.0: 2023-11-29 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)
]]
-- 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
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 8*3 -- 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 8 -- 0, 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
-- 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!
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
local pixelimg = false
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
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
self.pixel:setScale(self.params.pixelscalexup, self.params.pixelscaleyup)
self.pixel:setAnchorPoint(0.5, 0.5)
if pixelimg then self.pixel:setShader(shaderpixelslot) end -- apply shader
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,
pixtexslot, 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 btn.params.pixelimgup then -- texture
local r, g, b = (pixelcolor >> 16 & 0xff) / 255, (pixelcolor >> 8 & 0xff) / 255, (pixelcolor & 0xff) / 255
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)
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,
2.0, 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,
1.0, 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,
0.0, 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,
2.0, 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,
1.0, 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,
0.0, 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.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
application:setBackgroundColor(0x6c6c6c)
-- 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 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,
text="button 1", ttf=myttf,
sound=btnsound, volume=volume,
}, 1)
local btn02 = ButtonMonster.new({
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
text="button 2", ttf=myttf,
sound=btnsound, volume=volume,
}, 2)
local btn03 = ButtonMonster.new({
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
text="button 3", ttf=myttf,
sound=btnsound, volume=volume,
}, 3)
local btn04 = ButtonMonster.new({
autoscale=false,
pixelwidth=8*5, pixelheight=8*24,
pixelimgup=btnuptex, pixelimgdown=btndowntex, pixelimgdisabled=btndisabledtex,
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,
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