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:             GraphicsDemo
27  version:          1.0.0
28  vendor:           JUCE
29  website:          http://juce.com
30  description:      Showcases various graphics features.
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:        GraphicsDemo
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 /** Holds the various toggle buttons for the animation modes. */
53 class ControllersComponent  : public Component
54 {
55 public:
ControllersComponent()56     ControllersComponent()
57     {
58         setOpaque (true);
59 
60         initialiseToggle (animatePosition, "Animate Position",  true);
61         initialiseToggle (animateRotation, "Animate Rotation",  true);
62         initialiseToggle (animateSize,     "Animate Size",      false);
63         initialiseToggle (animateShear,    "Animate Shearing",  false);
64         initialiseToggle (animateAlpha,    "Animate Alpha",     false);
65         initialiseToggle (clipToRectangle, "Clip to Rectangle", false);
66         initialiseToggle (clipToPath,      "Clip to Path",      false);
67         initialiseToggle (clipToImage,     "Clip to Image",     false);
68         initialiseToggle (quality,         "Higher quality image interpolation", false);
69     }
70 
paint(Graphics & g)71     void paint (Graphics& g) override
72     {
73         g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
74     }
75 
resized()76     void resized() override
77     {
78         auto r = getLocalBounds().reduced (4);
79 
80         int buttonHeight = 22;
81 
82         auto columns = r.removeFromTop (buttonHeight * 4);
83         auto col = columns.removeFromLeft (200);
84 
85         animatePosition.setBounds (col.removeFromTop (buttonHeight));
86         animateRotation.setBounds (col.removeFromTop (buttonHeight));
87         animateSize    .setBounds (col.removeFromTop (buttonHeight));
88         animateShear   .setBounds (col.removeFromTop (buttonHeight));
89 
90         columns.removeFromLeft (20);
91         col = columns.removeFromLeft (200);
92 
93         animateAlpha   .setBounds (col.removeFromTop (buttonHeight));
94         clipToRectangle.setBounds (col.removeFromTop (buttonHeight));
95         clipToPath     .setBounds (col.removeFromTop (buttonHeight));
96         clipToImage    .setBounds (col.removeFromTop (buttonHeight));
97 
98         r.removeFromBottom (6);
99         quality.setBounds (r.removeFromTop (buttonHeight));
100     }
101 
initialiseToggle(ToggleButton & b,const char * name,bool on)102     void initialiseToggle (ToggleButton& b, const char* name, bool on)
103     {
104         addAndMakeVisible (b);
105         b.setButtonText (name);
106         b.setToggleState (on, dontSendNotification);
107     }
108 
109     ToggleButton animateRotation, animatePosition, animateAlpha, animateSize, animateShear;
110     ToggleButton clipToRectangle, clipToPath, clipToImage, quality;
111 
112     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControllersComponent)
113 };
114 
115 //==============================================================================
116 class GraphicsDemoBase  : public Component
117 {
118 public:
GraphicsDemoBase(ControllersComponent & cc,const String & name)119     GraphicsDemoBase (ControllersComponent& cc, const String& name)
120         : Component (name),
121           controls (cc)
122     {
123         displayFont = Font (Font::getDefaultMonospacedFontName(), 12.0f, Font::bold);
124     }
125 
getTransform()126     AffineTransform getTransform()
127     {
128         auto hw = 0.5f * (float) getWidth();
129         auto hh = 0.5f * (float) getHeight();
130 
131         AffineTransform t;
132 
133         if (controls.animateRotation.getToggleState())
134             t = t.rotated (rotation.getValue() * MathConstants<float>::twoPi);
135 
136         if (controls.animateSize.getToggleState())
137             t = t.scaled (0.3f + size.getValue() * 2.0f);
138 
139         if (controls.animatePosition.getToggleState())
140             t = t.translated (hw + hw * (offsetX.getValue() - 0.5f),
141                               hh + hh * (offsetY.getValue() - 0.5f));
142         else
143             t = t.translated (hw, hh);
144 
145         if (controls.animateShear.getToggleState())
146             t = t.sheared (shear.getValue() * 2.0f - 1.0f, 0.0f);
147 
148         return t;
149     }
150 
getAlpha()151     float getAlpha() const
152     {
153         if (controls.animateAlpha.getToggleState())
154             return alpha.getValue();
155 
156         return 1.0f;
157     }
158 
paint(Graphics & g)159     void paint (Graphics& g) override
160     {
161         auto startTime = 0.0;
162 
163         {
164             // A ScopedSaveState will return the Graphics context to the state it was at the time of
165             // construction when it goes out of scope. We use it here to avoid clipping the fps text
166             const Graphics::ScopedSaveState state (g);
167 
168             if (controls.clipToRectangle.getToggleState())  clipToRectangle (g);
169             if (controls.clipToPath     .getToggleState())  clipToPath (g);
170             if (controls.clipToImage    .getToggleState())  clipToImage (g);
171 
172             g.setImageResamplingQuality (controls.quality.getToggleState() ? Graphics::highResamplingQuality
173                                                                            : Graphics::mediumResamplingQuality);
174 
175             // take a note of the time before the render
176             startTime = Time::getMillisecondCounterHiRes();
177 
178             // then let the demo draw itself..
179             drawDemo (g);
180         }
181 
182         auto now = Time::getMillisecondCounterHiRes();
183         auto filtering = 0.08;
184 
185         auto elapsedMs = now - startTime;
186         averageTimeMs += (elapsedMs - averageTimeMs) * filtering;
187 
188         auto sinceLastRender = now - lastRenderStartTime;
189         lastRenderStartTime = now;
190 
191         auto effectiveFPS = 1000.0 / averageTimeMs;
192         auto actualFPS = sinceLastRender > 0 ? (1000.0 / sinceLastRender) : 0;
193         averageActualFPS += (actualFPS - averageActualFPS) * filtering;
194 
195         GlyphArrangement ga;
196         ga.addFittedText (displayFont,
197                           "Time: " + String (averageTimeMs, 2)
198                             + " ms\nEffective FPS: " + String (effectiveFPS, 1)
199                             + "\nActual FPS: " + String (averageActualFPS, 1),
200                           0, 10.0f, (float) getWidth() - 10.0f, (float) getHeight(), Justification::topRight, 3);
201 
202         g.setColour (Colours::white.withAlpha (0.5f));
203         g.fillRect (ga.getBoundingBox (0, ga.getNumGlyphs(), true).getSmallestIntegerContainer().expanded (4));
204 
205         g.setColour (Colours::black);
206         ga.draw (g);
207     }
208 
209     virtual void drawDemo (Graphics&) = 0;
210 
clipToRectangle(Graphics & g)211     void clipToRectangle (Graphics& g)
212     {
213         auto w = getWidth()  / 2;
214         auto h = getHeight() / 2;
215 
216         auto x = (int) ((float) w * clipRectX.getValue());
217         auto y = (int) ((float) h * clipRectY.getValue());
218 
219         g.reduceClipRegion (x, y, w, h);
220     }
221 
clipToPath(Graphics & g)222     void clipToPath (Graphics& g)
223     {
224         auto pathSize = (float) jmin (getWidth(), getHeight());
225 
226         Path p;
227         p.addStar (Point<float> (clipPathX.getValue(),
228                                  clipPathY.getValue()) * pathSize,
229                    7,
230                    pathSize * (0.5f + clipPathDepth.getValue()),
231                    pathSize * 0.5f,
232                    clipPathAngle.getValue());
233 
234         g.reduceClipRegion (p, AffineTransform());
235     }
236 
clipToImage(Graphics & g)237     void clipToImage (Graphics& g)
238     {
239         if (! clipImage.isValid())
240             createClipImage();
241 
242         AffineTransform transform (AffineTransform::translation ((float) clipImage.getWidth()  / -2.0f,
243                                                                  (float) clipImage.getHeight() / -2.0f)
244                                    .rotated (clipImageAngle.getValue() * MathConstants<float>::twoPi)
245                                    .scaled (2.0f + clipImageSize.getValue() * 3.0f)
246                                    .translated ((float) getWidth()  * 0.5f,
247                                                 (float) getHeight() * 0.5f));
248 
249         g.reduceClipRegion (clipImage, transform);
250     }
251 
createClipImage()252     void createClipImage()
253     {
254         clipImage = Image (Image::ARGB, 300, 300, true);
255 
256         Graphics g (clipImage);
257 
258         g.setGradientFill (ColourGradient (Colours::transparentBlack, 0, 0,
259                                            Colours::black, 0, 300, false));
260 
261         for (int i = 0; i < 20; ++i)
262             g.fillRect (Random::getSystemRandom().nextInt (200),
263                         Random::getSystemRandom().nextInt (200),
264                         Random::getSystemRandom().nextInt (100),
265                         Random::getSystemRandom().nextInt (100));
266     }
267 
268     //==============================================================================
269     ControllersComponent& controls;
270 
271     SlowerBouncingNumber offsetX, offsetY, rotation, size, shear, alpha, clipRectX,
272                          clipRectY, clipPathX, clipPathY, clipPathDepth, clipPathAngle,
273                          clipImageX, clipImageY, clipImageAngle, clipImageSize;
274 
275     double lastRenderStartTime = 0.0, averageTimeMs = 0.0, averageActualFPS = 0.0;
276     Image clipImage;
277     Font displayFont;
278 
279     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GraphicsDemoBase)
280 };
281 
282 //==============================================================================
283 class RectangleFillTypesDemo  : public GraphicsDemoBase
284 {
285 public:
RectangleFillTypesDemo(ControllersComponent & cc)286     RectangleFillTypesDemo (ControllersComponent& cc)
287         : GraphicsDemoBase (cc, "Fill Types: Rectangles")
288     {}
289 
drawDemo(Graphics & g)290     void drawDemo (Graphics& g) override
291     {
292         g.addTransform (getTransform());
293 
294         const int rectSize = jmin (getWidth(), getHeight()) / 2 - 20;
295 
296         g.setColour (colour1.withAlpha (getAlpha()));
297         g.fillRect (-rectSize, -rectSize, rectSize, rectSize);
298 
299         g.setGradientFill (ColourGradient (colour1, 10.0f, (float) -rectSize,
300                                            colour2, 10.0f + (float) rectSize, 0.0f, false));
301         g.setOpacity (getAlpha());
302         g.fillRect (10, -rectSize, rectSize, rectSize);
303 
304         g.setGradientFill (ColourGradient (colour1, (float) rectSize * -0.5f, 10.0f + (float) rectSize * 0.5f,
305                                            colour2, 0, 10.0f + (float) rectSize, true));
306         g.setOpacity (getAlpha());
307         g.fillRect (-rectSize, 10, rectSize, rectSize);
308 
309         g.setGradientFill (ColourGradient (colour1, 10.0f, 10.0f,
310                                            colour2, 10.0f + (float) rectSize, 10.0f + (float) rectSize, false));
311         g.setOpacity (getAlpha());
312         g.drawRect (10, 10, rectSize, rectSize, 5);
313     }
314 
315     Colour colour1 { Colours::red }, colour2 { Colours::green };
316 };
317 
318 //==============================================================================
319 class PathsDemo  : public GraphicsDemoBase
320 {
321 public:
PathsDemo(ControllersComponent & cc,bool linear,bool radial)322     PathsDemo (ControllersComponent& cc, bool linear, bool radial)
323         : GraphicsDemoBase (cc, String ("Paths") + (radial ? ": Radial Gradients"
324                                                            : (linear ? ": Linear Gradients"
325                                                                      : ": Solid"))),
326           useLinearGradient (linear), useRadialGradient (radial)
327     {
328         logoPath = getJUCELogoPath();
329 
330         // rescale the logo path so that it's centred about the origin and has the right size.
331         logoPath.applyTransform (RectanglePlacement (RectanglePlacement::centred)
332                                  .getTransformToFit (logoPath.getBounds(),
333                                                      Rectangle<float> (-120.0f, -120.0f, 240.0f, 240.0f)));
334 
335         // Surround it with some other shapes..
336         logoPath.addStar ({ -300.0f, -50.0f }, 7, 30.0f, 70.0f, 0.1f);
337         logoPath.addStar ({ 300.0f, 50.0f }, 6, 40.0f, 70.0f, 0.1f);
338         logoPath.addEllipse (-100.0f, 150.0f, 200.0f, 140.0f);
339         logoPath.addRectangle (-100.0f, -280.0f, 200.0f, 140.0f);
340     }
341 
drawDemo(Graphics & g)342     void drawDemo (Graphics& g) override
343     {
344         auto& p = logoPath;
345 
346         if (useLinearGradient || useRadialGradient)
347         {
348             Colour c1 (gradientColours[0].getValue(), gradientColours[1].getValue(), gradientColours[2].getValue(), 1.0f);
349             Colour c2 (gradientColours[3].getValue(), gradientColours[4].getValue(), gradientColours[5].getValue(), 1.0f);
350             Colour c3 (gradientColours[6].getValue(), gradientColours[7].getValue(), gradientColours[8].getValue(), 1.0f);
351 
352             auto x1 = gradientPositions[0].getValue() * (float) getWidth()  * 0.25f;
353             auto y1 = gradientPositions[1].getValue() * (float) getHeight() * 0.25f;
354             auto x2 = gradientPositions[2].getValue() * (float) getWidth()  * 0.75f;
355             auto y2 = gradientPositions[3].getValue() * (float) getHeight() * 0.75f;
356 
357             ColourGradient gradient (c1, x1, y1,
358                                      c2, x2, y2,
359                                      useRadialGradient);
360 
361             gradient.addColour (gradientIntermediate.getValue(), c3);
362 
363             g.setGradientFill (gradient);
364         }
365         else
366         {
367             g.setColour (Colours::blue);
368         }
369 
370         g.setOpacity (getAlpha());
371         g.fillPath (p, getTransform());
372     }
373 
374     Path logoPath;
375     bool useLinearGradient, useRadialGradient;
376     SlowerBouncingNumber gradientColours[9], gradientPositions[4], gradientIntermediate;
377 };
378 
379 //==============================================================================
380 class StrokesDemo  : public GraphicsDemoBase
381 {
382 public:
StrokesDemo(ControllersComponent & cc)383     StrokesDemo (ControllersComponent& cc)
384         : GraphicsDemoBase (cc, "Paths: Stroked")
385     {}
386 
drawDemo(Graphics & g)387     void drawDemo (Graphics& g) override
388     {
389         auto w = (float) getWidth();
390         auto h = (float) getHeight();
391 
392         Path p;
393         p.startNewSubPath (points[0].getValue() * w,
394                            points[1].getValue() * h);
395 
396         for (int i = 2; i < numElementsInArray (points); i += 4)
397             p.quadraticTo (points[i]    .getValue() * w,
398                            points[i + 1].getValue() * h,
399                            points[i + 2].getValue() * w,
400                            points[i + 3].getValue() * h);
401 
402         p.closeSubPath();
403 
404         PathStrokeType stroke (0.5f + 10.0f * thickness.getValue());
405         g.setColour (Colours::purple.withAlpha (getAlpha()));
406         g.strokePath (p, stroke, AffineTransform());
407     }
408 
409     SlowerBouncingNumber points[2 + 4 * 8], thickness;
410 };
411 
412 //==============================================================================
413 class ImagesRenderingDemo  : public GraphicsDemoBase
414 {
415 public:
ImagesRenderingDemo(ControllersComponent & cc,bool argb,bool tiled)416     ImagesRenderingDemo (ControllersComponent& cc, bool argb, bool tiled)
417         : GraphicsDemoBase (cc, String ("Images") + (argb ? ": ARGB" : ": RGB") + (tiled ? " Tiled" : String() )),
418           isArgb (argb), isTiled (tiled)
419     {
420         argbImage = getImageFromAssets ("juce_icon.png");
421         rgbImage  = getImageFromAssets ("portmeirion.jpg");
422     }
423 
drawDemo(Graphics & g)424     void drawDemo (Graphics& g) override
425     {
426         auto image = isArgb ? argbImage : rgbImage;
427 
428         AffineTransform transform (AffineTransform::translation ((float) (image.getWidth()  / -2),
429                                                                  (float) (image.getHeight() / -2))
430                                    .followedBy (getTransform()));
431 
432         if (isTiled)
433         {
434             FillType fill (image, transform);
435             fill.setOpacity (getAlpha());
436             g.setFillType (fill);
437             g.fillAll();
438         }
439         else
440         {
441             g.setOpacity (getAlpha());
442             g.drawImageTransformed (image, transform, false);
443         }
444     }
445 
446     bool isArgb, isTiled;
447     Image rgbImage, argbImage;
448 };
449 
450 //==============================================================================
451 class GlyphsDemo  : public GraphicsDemoBase
452 {
453 public:
GlyphsDemo(ControllersComponent & cc)454     GlyphsDemo (ControllersComponent& cc)
455         : GraphicsDemoBase (cc, "Glyphs")
456     {
457         glyphs.addFittedText ({ 20.0f }, "The Quick Brown Fox Jumped Over The Lazy Dog",
458                               -120, -50, 240, 100, Justification::centred, 2, 1.0f);
459     }
460 
drawDemo(Graphics & g)461     void drawDemo (Graphics& g) override
462     {
463         g.setColour (Colours::black.withAlpha (getAlpha()));
464         glyphs.draw (g, getTransform());
465     }
466 
467     GlyphArrangement glyphs;
468 };
469 
470 //==============================================================================
471 class SVGDemo  : public GraphicsDemoBase
472 {
473 public:
SVGDemo(ControllersComponent & cc)474     SVGDemo (ControllersComponent& cc)
475         : GraphicsDemoBase (cc, "SVG")
476     {
477         createSVGDrawable();
478     }
479 
drawDemo(Graphics & g)480     void drawDemo (Graphics& g) override
481     {
482         if (Time::getCurrentTime().toMilliseconds() > lastSVGLoadTime.toMilliseconds() + 2000)
483             createSVGDrawable();
484 
485         svgDrawable->draw (g, getAlpha(), getTransform());
486     }
487 
createSVGDrawable()488     void createSVGDrawable()
489     {
490         lastSVGLoadTime = Time::getCurrentTime();
491 
492         ZipFile icons (createAssetInputStream ("icons.zip").release(), true);
493 
494         // Load a random SVG file from our embedded icons.zip file.
495         const std::unique_ptr<InputStream> svgFileStream (icons.createStreamForEntry (Random::getSystemRandom().nextInt (icons.getNumEntries())));
496 
497         if (svgFileStream.get() != nullptr)
498         {
499             svgDrawable = Drawable::createFromImageDataStream (*svgFileStream);
500 
501             if (svgDrawable != nullptr)
502             {
503                 // to make our icon the right size, we'll set its bounding box to the size and position that we want.
504 
505                 if (auto comp = dynamic_cast<DrawableComposite*> (svgDrawable.get()))
506                     comp->setBoundingBox ({ -100.0f, -100.0f, 200.0f, 200.0f });
507             }
508         }
509     }
510 
511     Time lastSVGLoadTime;
512     std::unique_ptr<Drawable> svgDrawable;
513 };
514 
515 //==============================================================================
516 class LinesDemo  : public GraphicsDemoBase
517 {
518 public:
LinesDemo(ControllersComponent & cc)519     LinesDemo (ControllersComponent& cc)
520         : GraphicsDemoBase (cc, "Lines")
521     {}
522 
drawDemo(Graphics & g)523     void drawDemo (Graphics& g) override
524     {
525         {
526             RectangleList<float> verticalLines;
527             verticalLines.ensureStorageAllocated (getWidth());
528 
529             auto pos = offset.getValue();
530 
531             for (int x = 0; x < getWidth(); ++x)
532             {
533                 auto y = (float) getHeight() * 0.3f;
534                 auto length = y * std::abs (std::sin ((float) x / 100.0f + 2.0f * pos));
535                 verticalLines.addWithoutMerging (Rectangle<float> ((float) x, y - length * 0.5f, 1.0f, length));
536             }
537 
538             g.setColour (Colours::blue.withAlpha (getAlpha()));
539             g.fillRectList (verticalLines);
540         }
541 
542         {
543             RectangleList<float> horizontalLines;
544             horizontalLines.ensureStorageAllocated (getHeight());
545 
546             auto pos = offset.getValue();
547 
548             for (int y = 0; y < getHeight(); ++y)
549             {
550                 auto x = (float) getWidth() * 0.3f;
551                 auto length = x * std::abs (std::sin ((float) y / 100.0f + 2.0f * pos));
552                 horizontalLines.addWithoutMerging (Rectangle<float> (x - length * 0.5f, (float) y, length, 1.0f));
553             }
554 
555             g.setColour (Colours::green.withAlpha (getAlpha()));
556             g.fillRectList (horizontalLines);
557         }
558 
559         g.setColour (Colours::red.withAlpha (getAlpha()));
560 
561         auto w = (float) getWidth();
562         auto h = (float) getHeight();
563 
564         g.drawLine (positions[0].getValue() * w,
565                     positions[1].getValue() * h,
566                     positions[2].getValue() * w,
567                     positions[3].getValue() * h);
568 
569         g.drawLine (positions[4].getValue() * w,
570                     positions[5].getValue() * h,
571                     positions[6].getValue() * w,
572                     positions[7].getValue() * h);
573     }
574 
575     SlowerBouncingNumber offset, positions[8];
576 };
577 
578 //==============================================================================
579 class DemoHolderComponent  : public Component,
580                              private Timer
581 {
582 public:
DemoHolderComponent()583     DemoHolderComponent()
584     {
585         setOpaque (true);
586     }
587 
paint(Graphics & g)588     void paint (Graphics& g) override
589     {
590         g.fillCheckerBoard (getLocalBounds().toFloat(), 48.0f, 48.0f,
591                             Colours::lightgrey, Colours::white);
592     }
593 
timerCallback()594     void timerCallback() override
595     {
596         if (currentDemo != nullptr)
597             currentDemo->repaint();
598     }
599 
setDemo(GraphicsDemoBase * newDemo)600     void setDemo (GraphicsDemoBase* newDemo)
601     {
602         if (currentDemo != nullptr)
603             removeChildComponent (currentDemo);
604 
605         currentDemo = newDemo;
606 
607         if (currentDemo != nullptr)
608         {
609             addAndMakeVisible (currentDemo);
610             startTimerHz (60);
611             resized();
612         }
613     }
614 
resized()615     void resized() override
616     {
617         if (currentDemo != nullptr)
618             currentDemo->setBounds (getLocalBounds());
619     }
620 
621 private:
622     GraphicsDemoBase* currentDemo = nullptr;
623 };
624 
625 //==============================================================================
626 class TestListComponent   : public Component,
627                             private ListBoxModel
628 {
629 public:
TestListComponent(DemoHolderComponent & holder,ControllersComponent & controls)630     TestListComponent (DemoHolderComponent& holder, ControllersComponent& controls)
631         : demoHolder (holder)
632     {
633         demos.add (new PathsDemo (controls, false, true));
634         demos.add (new PathsDemo (controls, true,  false));
635         demos.add (new PathsDemo (controls, false, false));
636         demos.add (new RectangleFillTypesDemo (controls));
637         demos.add (new StrokesDemo (controls));
638         demos.add (new ImagesRenderingDemo (controls, false, false));
639         demos.add (new ImagesRenderingDemo (controls, false, true));
640         demos.add (new ImagesRenderingDemo (controls, true,  false));
641         demos.add (new ImagesRenderingDemo (controls, true,  true));
642         demos.add (new GlyphsDemo (controls));
643         demos.add (new SVGDemo    (controls));
644         demos.add (new LinesDemo  (controls));
645 
646         addAndMakeVisible (listBox);
647         listBox.setModel (this);
648         listBox.selectRow (0);
649     }
650 
resized()651     void resized()
652     {
653         listBox.setBounds (getLocalBounds());
654     }
655 
getNumRows()656     int getNumRows()
657     {
658         return demos.size();
659     }
660 
paintListBoxItem(int rowNumber,Graphics & g,int width,int height,bool rowIsSelected)661     void paintListBoxItem (int rowNumber, Graphics& g, int width, int height, bool rowIsSelected)
662     {
663         if (auto* demo = demos[rowNumber])
664         {
665             if (rowIsSelected)
666                 g.fillAll (Colour::contrasting (findColour (ListBox::textColourId),
667                                                 findColour (ListBox::backgroundColourId)));
668 
669             g.setColour (findColour (ListBox::textColourId));
670             g.setFont (14.0f);
671             g.drawFittedText (demo->getName(), 8, 0, width - 10, height, Justification::centredLeft, 2);
672         }
673     }
674 
selectedRowsChanged(int lastRowSelected)675     void selectedRowsChanged (int lastRowSelected)
676     {
677         demoHolder.setDemo (demos [lastRowSelected]);
678     }
679 
680 private:
681     DemoHolderComponent& demoHolder;
682     ListBox listBox;
683     OwnedArray<GraphicsDemoBase> demos;
684 
685     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TestListComponent)
686 };
687 
688 //==============================================================================
689 class GraphicsDemo  : public Component
690 {
691 public:
GraphicsDemo()692     GraphicsDemo()
693         : testList (demoHolder, controllersComponent)
694     {
695         setOpaque (true);
696 
697         addAndMakeVisible (demoHolder);
698         addAndMakeVisible (controllersComponent);
699         addAndMakeVisible (performanceDisplay);
700         addAndMakeVisible (testList);
701 
702         setSize (750, 750);
703     }
704 
paint(Graphics & g)705     void paint (Graphics& g) override
706     {
707         g.fillAll (Colours::grey);
708     }
709 
resized()710     void resized() override
711     {
712         auto area = getLocalBounds();
713 
714         controllersComponent.setBounds (area.removeFromBottom (150));
715         testList            .setBounds (area.removeFromRight (150));
716         demoHolder          .setBounds (area);
717         performanceDisplay  .setBounds (area.removeFromTop (20).removeFromRight (100));
718     }
719 
720 private:
721     ControllersComponent controllersComponent;
722     DemoHolderComponent demoHolder;
723     Label performanceDisplay;
724     TestListComponent testList;
725 
726     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GraphicsDemo)
727 };
728