Pulsating Button


#1

Hey Guys

I put together a pulsating button class that takes a TextButton and gives it a “pulsating light” effect. I’m using it as a subtle way of getting the user’s attention. I thought you all might find it useful so here’s the code. And if anyone has any ideas on how to make it better I’m all ears!

Pulsator.h


class Pulsator;

class PulsatingButton : public TextButton,
						public AsyncUpdater
{
public:
	PulsatingButton();
	~PulsatingButton();
	void handleAsyncUpdate();
	void pulsate(bool doPulsate);
	bool isPulsating();
	
private:	
	int green;
	bool up, reset;
	Pulsator* pulsator;
};

class Pulsator : public Thread
{
public:
	Pulsator();
	~Pulsator();
	void setTarget(PulsatingButton* button_);
	void run();
	
private:
	PulsatingButton* button;
};

Pulsator.cpp

[code]PulsatingButton::PulsatingButton()
: green (187),
up (true)
{
pulsator = new Pulsator();
pulsator->setTarget(this);
}

PulsatingButton::~PulsatingButton()
{
pulsator->threadShouldExit();
deleteAndZero(pulsator);
}

void PulsatingButton::handleAsyncUpdate()
{
if (!reset)
{
if (up)
{
green += 10;
if (green > 247)
{
green -= 20;
up = false;
}
}
else
{
green -= 10;
if (green < 187)
{
green += 20;
up = true;
}
}

	setColour(TextButton::buttonColourId, Colour(187, green, 255));
}
else 
{
	setColour(TextButton::buttonColourId, Colour(187, 187, 255));
}

}

void PulsatingButton::pulsate(bool doPulsate)
{
if (doPulsate) {
reset = false;
pulsator->startThread();
}
else
{
reset = true;
pulsator->stopThread(100);
}
}

bool PulsatingButton::isPulsating()
{
return pulsator->isThreadRunning();
}

Pulsator::Pulsator()
: Thread (“Pulsator”)
{}

Pulsator::~Pulsator()
{
stopThread(100);
waitForThreadToExit(100);
}

void Pulsator::setTarget(PulsatingButton* button_)
{
button = button_;
}

void Pulsator::run()
{
while (!threadShouldExit())
{
wait(150);
button->triggerAsyncUpdate();
}
}
[/code]

Just create a PulsatingButton object the same way you would a TextButton, and to start and stop the pulsating:

myPulsatingButton->pulsate(true); // to start pulsating myPulsatingButton->pulsate(false); // to stop pulsating


#2

Ok… Any reason why you didn’t just use a Timer?


#3

Well, in part of the program I have audio being generated by the message thread, and I needed the button to pulsate while this audio is being generated. Since the Timer is managed by the message thread, I was afraid it would break up the audio every time it repainted the button’s color.


#4

I don’t have time to put you straight on all the things you seem to be confused about, but basically: use a timer.


#5

Fair enough.


#6

I doubt that somehow. Are you using juce classes for the audio or have you something custom?


#7

I’m using Juce classes (Synthesiser, Transport, and Quicktime). I gather these are setup in such a way that their workload is handled by background threads?


#8

Yeah basically. The audio stuff will be called by another thread so you don’t have to worry about it interfering with the UI. This also means you need to be thread safe between the UI and the audio stuff then.
Also make sure you don’t block in the audio processing thread if you can avoid it i.e. memory allocation, OS functions, I/O or any other long running process.

Anyway using the Timer with the Component is fine and it will give the effect you want.


#9

You should also know that your Component, since it’s currently not interacting with objects that will be read/written on a different thread, will not require threading in any way.

Here’s a Timer based version.

class PulsatingTextButton : public juce::TextButton,
                            private juce::Timer
{
public:
    PulsatingTextButton() :
        pulsating (false),
        brighten (true),
        changeLevel (0.0f),
        baseColour (juce::Colours::blue)
    {
        resetToBaseColour();
    }

    ~PulsatingTextButton()
    {
    }

    //==============================================================================
    void startPulsating (const int intervalMilliseconds)
    {
        changeLevel = 0.0f;
        pulsating   = true;
        brighten    = true;

        resetToBaseColour();
        startTimer (intervalMilliseconds);
    }

    void stopPulsating()
    {
        stopTimer();
        resetToBaseColour();

        changeLevel = 0.0f;
        pulsating   = false;
    }

    const bool isPulsating() const noexcept
    {
        return pulsating;
    }

    //==============================================================================
    void setBaseColour (const juce::Colour& newBaseColour)
    {
        baseColour = newBaseColour;
    }

private:
    //==============================================================================
    bool pulsating;
    bool brighten;

    juce::Colour baseColour;
    float changeLevel;

    //==============================================================================
    void resetToBaseColour()
    {
        setColour (juce::TextButton::buttonColourId, baseColour);
    }

    //==============================================================================
    void timerCallback()
    {
        const float changeFactor = 0.01f;

        if (brighten)
        {
            changeLevel += changeFactor;

            if (changeLevel > 1.0f)
            {
                brighten    = false;
                changeLevel = 1.0f;
            }
        }
        else 
        {
            changeLevel -= changeFactor;

            if (changeLevel <= 0.0f)
            {
                brighten    = true;
                changeLevel = 0.0f;
            }
        }

        setColour (juce::TextButton::buttonColourId, baseColour.brighter (changeLevel));
        repaint();
    }

    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PulsatingTextButton);
};

#10

The problem with Colour::brighter() is that it does not produce perceptually uniform changes. That is to say, that depending on the starting colour, the resulting delta luminance will be different. For example, brighter() will produce a different perceptual brightness change when given RGB=(0,0,0) versus RGB=(128,0,0).

VFLib to the rescue! The vf::LabColour class provides conversions to and from the perceptually uniform CIELAB colour space, allowing for consistent brightness changes:

setColour (juce::TextButton::buttonColourId, vf::LabColour(baseColour).withAddedLuminance (changeLevel).toRGB ());

#11

Thanks for clearing that up Anima. And thanks for the timer based version jrianglos. I originally tried brighter(), but it didn’t make a noticeable difference, probably for the same reasons Vinn mentioned. Plus, by using the RGB values, you have more control over the specific coloring of the pulses.


#12

On a similar topic, what about a better way to animate certain controls? Making half my controls timers seems a bit wonky, but maybe I want a whole batch of controls to get a regular animate opportunity.

In essence, some classes should be able to register that they are animating - by being redrawn, not moved around like the ComponentAnimator, and then one callback would go through all those commands, they all do something (if they want) then get repaint triggered.

Or even, I suppose, they just get marked as dirty and repainted regularly, in a big batch at a definable rate. The each command can use elapsed time to change how it draws slightly.

Anything built-in for that?

Bruce


#13

No, there’s nothing built-in… A class that did that wouldn’t actually be any more efficient than using timers, which are very lightweight objects, but I guess it’d avoid the bother of making your object derive from Timer.


#14

Right. There is also a precedent in the library - ComponentAnimators and notably, the ‘global’ animator instance. We could make any component that wants to move a timer and implement moving.

Maybe ‘animateComponent’ could/should have an another animate method that services multiple flashing or pulsing components. Or maybe it’s an animate component action with a repeating action instead of a finite action, like a move from one place to another, i.e. rotating instead of just going place to place.

Other animator systems, such as Apple’s or Javas use more complex movement commands than just a rect and opacity, extending the system to do more things.

That reminds me… did someone do a ‘timeline’ interface for Components? I’ll check the contributed code forum.

Bruce