Button onHold

Hi,

I would like to implement a button that, when pressed, performs an action. Moreover, it needs to keep performing this action until released.

As an example, increasing the count of a variable. When pressed and released it adds one to the variable. However, when the button is held for 5 seconds it would add 5 (updating a label 1 by 1).

I was thinking this could be achieved with a loop but am not sure how to approach it in a single threaded environment.

Hold = true  
Count = 0  
local Add = function(event)  
 if (event.phase == "press") then  
 Hold = true  
 Adding(event)  
 elseif (event.phase == "release") then  
 Hold = false  
 end   
end  
  
Adding = function(event)  
 while (Hold == true) do  
 Count = Count + 1  
 CountLabel:setText(Count)  
 end   
end  

Any ideas on the best way to implement said behaviour? [import]uid: 7412 topic_id: 1394 reply_id: 301394[/import]

Will not work like this because there is now an infinite loop.

You could use a perform with delay which. Retriggers itself as long as the button was not released…

I would just implement my own button logic and handle the state myself from a touch event handler.

I implemented a button which performs an action after being hold for about a second which is different from the action for a normal press of the same button.

Btw…if you miss threading … Simulate it by using a enterFrame Händler and use that As you own little taskmanager :slight_smile: … Or just use Timers and Events for that. [import]uid: 6928 topic_id: 1394 reply_id: 3884[/import]

Thanks OderWat.

This is what I came up with… This lets me add a handler to an Add or Minus button and set the id of a variable I wish to increment/decrement. As I have about 20 variables this works well. Note sure if it makes sense (or if its the best way to do it) but I thought I’d share it as I found it hard to find any samples to follow.

[code]
– variables that relate to an Id. e.g. id 0 = VarToAddOrMinus0
Var0 = 0
Var1 = 0

– an object that performs an add/minus to a variable based on Id.
local Totaliser = {}
– init params in a stopped state
Totaliser.Id = -1
Totaliser.Running = false
Totaliser.Amount = -1

function Totaliser:timer(event)
if (self.Id == 0) then
Var0 = Var0 + self.Amount
elseif (self.Id == 1) then
Var1 = Var1 + self.Amount
– elseif… etc.
end

if (self.Running == false) then
timer.cancel(event.source)
end
end

function Totaliser:AddTo(id)
self.Id = id
self.Amount = 1
timer.performWithDelay(100, self, 0)
end

function Totaliser:MinusTo(id)
self.Id = id
self.Amount = -1
timer.performWithDelay(100, self, 0)
end

– the event handlers that will be added to the buttons.
– NB. Buttons include an Id that matches the variable
– (e.g. But0 has an Id=0 so it relates to Var0).

local AddHandler = function(event)
if (event.phase == “press”) then
if (Totaliser.Running == false) then
Totaliser.Running = true
Totaliser:AddTo(event.id)
end
elseif (event.phase == “release”) then
if (Totaliser.Running == true) then
Totaliser.Running = false
end
end
end

local MinusHandler = function(event)
if (event.phase == “press”) then
if (Totaliser.Running == false) then
Totaliser.Running = true
Totaliser:MinusTo(event.id)
end
elseif (event.phase == “release”) then
if (Totaliser.Running == true) then
Totaliser.Running = false
end
end
end
[/code] [import]uid: 7412 topic_id: 1394 reply_id: 3892[/import]

Glad you found a solution for your problem!

I would change some stuff (well I am a guy which rewrites working code to be more elegant…)

You have this “Var0” “Var1” and then this potentially long “if then elseif then…” …

As “id” is something you can use to index. You could use that to create the vars as an table with id be the index…

BUT … Lua allows you to have every object extended on the fly…

So your “var” could be a pointer inside the “button” as can be the “perform with delay” timer…

Here a bit (not validated) code illustrating what I mean:

-- the "timer" table listener event handler which does  
-- all the counting  
  
function stepHelper(self,event)  
 if self.running then  
 -- increment the count inside the "value"  
 self.value[1]=self.value[1]+self.stepping  
 timer.performWithDelay(100,self)  
 -- maybe do something like update screen with value  
 end  
 -- if not running we do not retrigger the event anymore  
end  
  
local value1={0} -- its a table so we can reference it from a plus and a minus button  
local value2={0}  
  
-- helper function for less code on setup per button  
function setupButton(but,value,steps)  
 but.value=value -- points to the table with the count  
 but.stepping=steps -- is the stepping  
 but.running=false -- indicator if still counting  
 but.timer=stepHelper -- setting the timer table listener  
end  
  
local but\_plus1=ui.newButton()  
setupButton(but\_plus1,value1,1)  
  
local but\_minus1=ui.newButton()  
setupButton(but\_minus1,value1,-1)  
  
local but\_plus2=ui.newButton()  
setupButton(but\_plus2,value2,1)  
  
local but\_minus2=ui.newButton()  
setupButton(but\_minus2,value2,-1)  
  

You would then set “but_plus.running” to true and call “but_plus.timer(but_plus, nil)” function yourself on “press” and set the but_plus.running to false on release.

This whole “id” thing for the ui.button is IMHO … well … no good coding. It is kind of a workaround.

The ui.Button uses an object oriented approach itself but asks for function pointers and this “id” to handle the events.

Instead of this it should create it’s own real (custom) events and make the button a real object you can add your own table listeners too. It also could offer a lot more functionality this way.

I wonder why that was done this way (and used as example code)… as the original implementation of the events typically use “event.target” to indicate which object was “touched”… which is much better and more universal.

BTW: If you use multiple buttons with your counting functionality… You could easily extend the “ui.lua” code with your functionality. That “button” stuff inside is not rocket science… [import]uid: 6928 topic_id: 1394 reply_id: 3894[/import]

Far more elegant… will have a play around and see where I land. Thanks, I appreciate you taking the time to help a newbie. :smiley: [import]uid: 7412 topic_id: 1394 reply_id: 3901[/import]

You are welcome… explaining basic things does refresh your own memory, helps to focus on the basics and inspires!

In fact I wrote my song playlist (it is in the Sample Code section of the Forum) after helping somebody to understand why his first version did not work… just to find that there is a bug in coronas ‘playSound()’ listener code…

Which then made me pick up an earlier mentioned concept to handle “song advancement” by using song duration.

That adds the need to handle system events for suspend/resume states (because of lost time).

This again is explained in the documentation for enterFrame handler based animation. Which is for sure a result of their considerations about how to implement the transition.to() functionality (which is very well implemented!).

At the same time I was not satisfied with the “sample” quality of “ui.lua” and esp. the “newButton()” code… which I ditched and wrote my own “non universal” version some weeks ago.

But I wanted to try my own concept of extended object encapsulation… so I tried to write a more elegant version using custom event handlers, to test if it works as I think it would be best practice.

In the end… this code illustrates a lot of concepts on how stuff can be done using all the available techniques in a real world example which in slightly enhanced version is now used in my application.

For me … this kind of “support” to other people pays out and I also like it to see when “newbies” open up to advanced concepts and start to take a different point of view (less user… more maker) in programming.

Stuff goes hand in hand and when you reach this level of understanding… you suddenly stop struggling about how to code something… but think about how to code it “elegant”.

This results in much better manageable and understandable code and that helps us all! [import]uid: 6928 topic_id: 1394 reply_id: 3902[/import]

Indeed.

One issue that I have come across now that I have things working is that, when running on an iPhone, the button ‘release’ event is often never fired. This is due to the user’s finger moving off the button prior to lifting from the screen, meaning a ‘release’ event does fire, but it does not relate to the button. In testing this occurs quite a bit so I need to fix it (as, when no ‘release’ event is handled for a button, the counter continues to increment).

My initial thinking is to scrap the button event handler and add a single event handler to the entire screen. Then, when a finger touches the screen, see if the location of any buttons (just images) match the co-ords of the touched point. If so, handle the event as if that button had been pressed. If not, ignore the touch. This then lets me handle the ‘release’ event and cancel the currently pressed (if any) button.

Can you think of a better implementation? [import]uid: 7412 topic_id: 1394 reply_id: 3915[/import]

This is my personal button code (a bit stripped down)

The Buttons for this code are “Groups” with a “Rectangle” as first and a “Text” as second member in the group. I animate the buttons on “Press” and “Release” with a scaling an color effect. The color effect is removed from the example code below (to distracting from what I want to demonstrate).

Each buttong (GROUP) gets an event listener (all the same: self) and this event listener checks against event.target to find the button which was pressed and calls the code below. This could be optimized for multiple buttons by using a table with the target as index…

function foo:touchBut(event, press)  
 local phase = event.phase  
 local n  
  
 if "began" == phase then  
 -- we take focus on "touch"  
 display.getCurrentStage():setFocus( event.target )  
 for n=1, event.target.numChildren do  
 transition.to( event.target[n], {time=66, xScale=1.1, yScale=1.1})  
 end  
 else  
 local inside=false  
 -- using the stage bounds of the rectangle of the button itself!  
 local sb=event.target[1].stageBounds  
 if event.x \>= sb.xMin and event.x \<= sb.xMax and  
 event.y \>= sb.yMin and event.y \<= sb.yMax then  
 inside=true  
 end  
 if "moved" == phase then  
 -- grabing and keeping focus too if we get "moved" first  
 display.getCurrentStage():setFocus( event.target )  
 if inside and self.but\_press ~= nil then  
 for n=1, event.target.numChildren do  
 transition.to( event.target[n], {time=66, xScale=1.1, yScale=1.1})  
 end  
 else  
 for n=1, event.target.numChildren do  
 transition.to( event.target[n], {time=100, xScale=1, yScale=1})  
 end  
 end  
 elseif "ended" == phase then  
 if inside and self.but\_press ~= nil then  
 -- call the press method  
 self.but\_press(self,event.x-sb.xMin, event.y-sb.yMin)  
 end  
 for n=1, event.target.numChildren do  
 transition.to( event.target[n], {time=100, xScale=1, yScale=1})  
 end  
 display.getCurrentStage():setFocus( nil )  
 end  
  
 end  
 return true  
end  
  
-- this is the event handler for all my touch related events (simplified)  
  
function foo:touch(event)  
 if event.target == self.butOpenFeint then  
 return self:touchBut(event, self.pressOpenFeint)  
 elseif event.target == self.butSound then  
 return self:touchBut(event, self.pressSound)  
 elseif event.target == self.butMusic then  
 return self:touchBut(event, self.pressMusic)  
 end  
end  
  

The behavior is like "you touch the button… it scales up… if you move the finger of the button… it scales back… if you move the finger again over the button… it scales up again… if you lift the finger on the button “press” is called… if you lift the finger outside… nothing happens… If you cross other elements in between they do not respond at all…

This is how I think a button has to work (usually) and replicates how buttons work in most gui.

My code is stripped down… and in this form not tested (I removed color changing and support for long-press and swipe buttons). Still you may get the idea… [import]uid: 6928 topic_id: 1394 reply_id: 3916[/import]

Ah, ignore me. Had a look at ui.lua and then found another post on the same topic. Seems I can tweak the code to fire cancelled when no within the button’s bounds.

Oderwat, methinks you are correct - its best to handcraft your own objects… [import]uid: 7412 topic_id: 1394 reply_id: 3917[/import]

Yup, I did that too- tweaked ui.lia to pass the cancelled event phase.

[import]uid: 106887 topic_id: 1394 reply_id: 89016[/import]