Blinking button in LookAndFeel / timerCallback?

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.

struct MyBlinker : public Component, public Timer

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

    void timerCallback() override;
        on = !on;

    bool on = false;

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.

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
    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));
            g.fillRoundedRectangle (bounds, cornerSize);
            g.setColour (button.findColour (ComboBox::outlineColourId));
            g.drawRoundedRectangle (bounds, cornerSize, 1.0f);


class MainComponent   : public Component, private Timer
        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

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