Blinking button in LookAndFeel / timerCallback?


#1

Hello,
I try to make blinking button. Do I need 3 separated class?
I made class BlinkingButton inherited from Timer, which has two LookAndFeel variables, like:

OnLookAndFeel onLookAndFeel;
OffLookAndFeel offLookAndFeel;

And in BlinkingButton I have also timer which change my button lookAndFeel.

But I wonder can’t I do that simply directly in one LookAndFeel class?

I tried to use Timer directly in BlinkingButtonLookAndFeel but I get some errors there was some conflicts.


#2
struct MyBlinker : public Component, public Timer
{
    MyBlinker() 
    {
       startTimer(500);
    }

    void paint(Graphics& g) override
    {
       if( on ) g.setColour(Colours::red);
       else g.setColour(Colours::black);
   
       g.fillAll();
    }

    void timerCallback() override;
    {    
        on = !on;
        repaint();
    }

    bool on = false;
};

#3

I don’t think it’s a good idea to do this in the lookandfeel class. I always create a new button class that derives from the standard button for stuff like this.


#4

It can be done in one LookAndFeel, you definitely don’t need more than one. But the timer part needs to be in the actual instance, since the LookAndFeel only aggregates functionality.

So you just need to make sure, the button redraws constantly, and in the drawButtonBackground you can use the Time::getMillisecondCounter()

I just bodged an example together, in a simple MainComponent.h (header only):

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

class AnimatedLookAndFeel : public LookAndFeel_V4
{
public:
    AnimatedLookAndFeel() = default;

    void drawButtonBackground (Graphics& g,
                               Button& button,
                               const Colour& backgroundColour,
                               bool shouldDrawButtonAsHighlighted,
                               bool shouldDrawButtonAsDown) override
    {
        auto cornerSize = 6.0f;
        auto bounds = button.getLocalBounds().toFloat().reduced (0.5f, 0.5f);
        
        auto baseColour = backgroundColour.withMultipliedSaturation (button.hasKeyboardFocus (true) ? 1.3f : 0.9f)
        .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.5f);
        
        if (shouldDrawButtonAsDown || shouldDrawButtonAsHighlighted)
            baseColour = baseColour.contrasting (shouldDrawButtonAsDown ? 0.2f : 0.05f);

        // just added this:
        bool dir = Time::getCurrentTime().getSeconds() % 2 == 0;
        auto p   = dir ? bounds.getTopLeft() +  (bounds.getTopRight() - bounds.getTopLeft()) * ((Time::getMillisecondCounter() % 1000) * 0.001)
                       : bounds.getTopRight() + (bounds.getTopLeft() - bounds.getTopRight()) * ((Time::getMillisecondCounter() % 1000) * 0.001);
        ColourGradient gradient (baseColour,     dir ? bounds.getTopLeft() : p,
                                 Colours::green, dir ? p : bounds.getTopRight(), false);
        g.setFillType (FillType (gradient));
        g.setGradientFill (gradient);
        // end addition

        if (button.isConnectedOnLeft() || button.isConnectedOnRight())
        {
            Path path;
            path.addRoundedRectangle (bounds.getX(), bounds.getY(),
                                      bounds.getWidth(), bounds.getHeight(),
                                      cornerSize, cornerSize,
                                      ! button.isConnectedOnLeft(),
                                      ! button.isConnectedOnRight(),
                                      ! button.isConnectedOnLeft(),
                                      ! button.isConnectedOnRight());
            
            g.fillPath (path);
            
            g.setColour (button.findColour (ComboBox::outlineColourId));
            g.strokePath (path, PathStrokeType (1.0f));
        }
        else
        {
            g.fillRoundedRectangle (bounds, cornerSize);
            
            g.setColour (button.findColour (ComboBox::outlineColourId));
            g.drawRoundedRectangle (bounds, cornerSize, 1.0f);
        }
    }

};

class MainComponent   : public Component, private Timer
{
public:
     MainComponent()
    {
        setLookAndFeel (&lnf);
        addAndMakeVisible (a);
        addAndMakeVisible (b);
        addAndMakeVisible (c);
        setSize (600, 400);

        startTimerHz (30);
    }

    virtual ~MainComponent()
    {
        setLookAndFeel (nullptr);
    }

    void resized() override
    {
        auto bounds = getLocalBounds();
        auto w = getWidth() / 3;
        a.setBounds (bounds.removeFromLeft (w).reduced (20));
        b.setBounds (bounds.removeFromLeft (w).reduced (20));
        c.setBounds (bounds.reduced (20));
    }

    void timerCallback() override
    {
        repaint();
    }

private:
    AnimatedLookAndFeel lnf;
    TextButton a {"A"}, b {"B"}, c {"C"};

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

43%20(3)