1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE examples.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    The code included in this file is provided under the terms of the ISC license
8    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
9    To use, copy, modify, and/or distribute this software for any purpose with or
10    without fee is hereby granted provided that the above copyright notice and
11    this permission notice appear in all copies.
12 
13    THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
14    WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
15    PURPOSE, ARE DISCLAIMED.
16 
17   ==============================================================================
18 */
19 
20 /*******************************************************************************
21  The block below describes the properties of this PIP. A PIP is a short snippet
22  of code that can be read by the Projucer and used to generate a JUCE project.
23 
24  BEGIN_JUCE_PIP_METADATA
25 
26  name:             TimersAndEventsDemo
27  version:          1.0.0
28  vendor:           JUCE
29  website:          http://juce.com
30  description:      Application using timers and events.
31 
32  dependencies:     juce_core, juce_data_structures, juce_events, juce_graphics,
33                    juce_gui_basics
34  exporters:        xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
35 
36  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1
37 
38  type:             Component
39  mainClass:        TimersAndEventsDemo
40 
41  useLocalCopy:     1
42 
43  END_JUCE_PIP_METADATA
44 
45 *******************************************************************************/
46 
47 #pragma once
48 
49 #include "../Assets/DemoUtilities.h"
50 
51 //==============================================================================
52 /** Simple message that holds a Colour. */
53 struct ColourMessage  : public Message
54 {
ColourMessageColourMessage55     ColourMessage (Colour col)  : colour (col) {}
56 
57     /** Returns the colour of a ColourMessage of white if the message is not a ColourMessage. */
getColourColourMessage58     static Colour getColour (const Message& message)
59     {
60         if (auto* cm = dynamic_cast<const ColourMessage*> (&message))
61             return cm->colour;
62 
63         return Colours::white;
64     }
65 
66     Colour colour;
67 
68     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ColourMessage)
69 };
70 
71 //==============================================================================
72 /** Simple component that can be triggered to flash.
73     The flash will then fade using a Timer to repaint itself and will send a change
74     message once it is finished.
75  */
76 class FlashingComponent   : public Component,
77                             public MessageListener,
78                             public ChangeBroadcaster,
79                             private Timer
80 {
81 public:
FlashingComponent()82     FlashingComponent() {}
83 
startFlashing()84     void startFlashing()
85     {
86         flashAlpha = 1.0f;
87         startTimerHz (25);
88     }
89 
90     /** Stops this component flashing without sending a change message. */
stopFlashing()91     void stopFlashing()
92     {
93         flashAlpha = 0.0f;
94         stopTimer();
95         repaint();
96     }
97 
98     /** Sets the colour of the component. */
setFlashColour(const Colour newColour)99     void setFlashColour (const Colour newColour)
100     {
101         colour = newColour;
102         repaint();
103     }
104 
105     /** Draws our component. */
paint(Graphics & g)106     void paint (Graphics& g) override
107     {
108         g.setColour (colour.overlaidWith (Colours::white.withAlpha (flashAlpha)));
109         g.fillEllipse (getLocalBounds().toFloat());
110     }
111 
112     /** Custom mouse handler to trigger a flash. */
mouseDown(const MouseEvent &)113     void mouseDown (const MouseEvent&) override
114     {
115         startFlashing();
116     }
117 
118     /** Message listener callback used to change our colour */
handleMessage(const Message & message)119     void handleMessage (const Message& message) override
120     {
121         setFlashColour (ColourMessage::getColour (message));
122     }
123 
124 private:
125     float flashAlpha = 0.0f;
126     Colour colour { Colours::red };
127 
timerCallback()128     void timerCallback() override
129     {
130         // Reduce the alpha level of the flash slightly so it fades out
131         flashAlpha -= 0.075f;
132 
133         if (flashAlpha < 0.05f)
134         {
135             stopFlashing();
136             sendChangeMessage();
137             // Once we've finished flashing send a change message to trigger the next component to flash
138         }
139 
140         repaint();
141     }
142 
143     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlashingComponent)
144 };
145 
146 //==============================================================================
147 class TimersAndEventsDemo   : public Component,
148                               private ChangeListener
149 {
150 public:
TimersAndEventsDemo()151     TimersAndEventsDemo()
152     {
153         setOpaque (true);
154 
155         // Create and add our FlashingComponents with some random colours and sizes
156         for (int i = 0; i < numFlashingComponents; ++i)
157         {
158             auto* newFlasher = new FlashingComponent();
159             flashingComponents.add (newFlasher);
160 
161             newFlasher->setFlashColour (getRandomBrightColour());
162             newFlasher->addChangeListener (this);
163 
164             auto diameter = 25 + random.nextInt (75);
165             newFlasher->setSize (diameter, diameter);
166 
167             addAndMakeVisible (newFlasher);
168         }
169 
170         addAndMakeVisible (stopButton);
171         stopButton.onClick = [this] { stopButtonClicked(); };
172 
173         addAndMakeVisible (randomColourButton);
174         randomColourButton.onClick = [this] { randomColourButtonClicked(); };
175 
176         // lay out our components in a pseudo random grid
177         Rectangle<int> area (0, 100, 150, 150);
178 
179         for (auto* comp : flashingComponents)
180         {
181             auto buttonArea = area.withSize (comp->getWidth(), comp->getHeight());
182             buttonArea.translate (random.nextInt (area.getWidth()  - comp->getWidth()),
183                                   random.nextInt (area.getHeight() - comp->getHeight()));
184             comp->setBounds (buttonArea);
185 
186             area.translate (area.getWidth(), 0);
187 
188             // if we go off the right start a new row
189             if (area.getRight() > (800 - area.getWidth()))
190             {
191                 area.translate (0, area.getWidth());
192                 area.setX (0);
193             }
194         }
195 
196         setSize (600, 600);
197     }
198 
~TimersAndEventsDemo()199     ~TimersAndEventsDemo() override
200     {
201         for (auto* fc : flashingComponents)
202             fc->removeChangeListener (this);
203     }
204 
paint(Graphics & g)205     void paint (Graphics& g) override
206     {
207         g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground,
208                                            Colours::darkgrey));
209     }
210 
paintOverChildren(Graphics & g)211     void paintOverChildren (Graphics& g) override
212     {
213         auto explanationArea = getLocalBounds().removeFromTop (100);
214 
215         AttributedString s;
216         s.append ("Click on a circle to make it flash. When it has finished flashing it will send a message which causes the next circle to flash");
217         s.append (newLine);
218         s.append ("Click the \"Set Random Colour\" button to change the colour of one of the circles.");
219         s.append (newLine);
220         s.setFont (16.0f);
221         s.setColour (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::defaultText, Colours::lightgrey));
222         s.draw (g, explanationArea.reduced (10).toFloat());
223     }
224 
resized()225     void resized() override
226     {
227         auto area = getLocalBounds().removeFromBottom (40);
228         randomColourButton.setBounds (area.removeFromLeft (166) .reduced (8));
229         stopButton        .setBounds (area.removeFromRight (166).reduced (8));
230     }
231 
232 private:
233     enum { numFlashingComponents = 9 };
234 
235     OwnedArray<FlashingComponent> flashingComponents;
236     TextButton randomColourButton  { "Set Random Colour" },
237                stopButton          { "Stop" };
238     Random random;
239 
changeListenerCallback(ChangeBroadcaster * source)240     void changeListenerCallback (ChangeBroadcaster* source) override
241     {
242         for (int i = 0; i < flashingComponents.size(); ++i)
243             if (source == flashingComponents.getUnchecked (i))
244                 flashingComponents.getUnchecked ((i + 1) % flashingComponents.size())->startFlashing();
245     }
246 
randomColourButtonClicked()247     void randomColourButtonClicked()
248     {
249         // Here we post a new ColourMessage with a random colour to a random flashing component.
250         // This will send a message to the component asynchronously and trigger its handleMessage callback
251         flashingComponents.getUnchecked (random.nextInt (flashingComponents.size()))->postMessage (new ColourMessage (getRandomBrightColour()));
252     }
253 
stopButtonClicked()254     void stopButtonClicked()
255     {
256         for (auto* fc : flashingComponents)
257             fc->stopFlashing();
258     }
259 
260     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimersAndEventsDemo)
261 };
262