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:             LookAndFeelDemo
27  version:          1.0.0
28  vendor:           JUCE
29  website:          http://juce.com
30  description:      Showcases custom look and feel components.
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:        LookAndFeelDemo
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 /** Custom Look And Feel subclasss.
53 
54     Simply override the methods you need to, anything else will be inherited from the base class.
55     It's a good idea not to hard code your colours, use the findColour method along with appropriate
56     ColourIds so you can set these on a per-component basis.
57 */
58 struct CustomLookAndFeel    : public LookAndFeel_V4
59 {
drawRoundThumbCustomLookAndFeel60     void drawRoundThumb (Graphics& g, float x, float y, float diameter, Colour colour, float outlineThickness)
61     {
62         auto halfThickness = outlineThickness * 0.5f;
63 
64         Path p;
65         p.addEllipse (x + halfThickness,
66                       y + halfThickness,
67                       diameter - outlineThickness,
68                       diameter - outlineThickness);
69 
70         DropShadow (Colours::black, 1, {}).drawForPath (g, p);
71 
72         g.setColour (colour);
73         g.fillPath (p);
74 
75         g.setColour (colour.brighter());
76         g.strokePath (p, PathStrokeType (outlineThickness));
77     }
78 
drawButtonBackgroundCustomLookAndFeel79     void drawButtonBackground (Graphics& g, Button& button, const Colour& backgroundColour,
80                                bool isMouseOverButton, bool isButtonDown) override
81     {
82         auto baseColour = backgroundColour.withMultipliedSaturation (button.hasKeyboardFocus (true) ? 1.3f : 0.9f)
83                                           .withMultipliedAlpha      (button.isEnabled() ? 0.9f : 0.5f);
84 
85         if (isButtonDown || isMouseOverButton)
86             baseColour = baseColour.contrasting (isButtonDown ? 0.2f : 0.1f);
87 
88         auto flatOnLeft   = button.isConnectedOnLeft();
89         auto flatOnRight  = button.isConnectedOnRight();
90         auto flatOnTop    = button.isConnectedOnTop();
91         auto flatOnBottom = button.isConnectedOnBottom();
92 
93         auto width  = (float) button.getWidth()  - 1.0f;
94         auto height = (float) button.getHeight() - 1.0f;
95 
96         if (width > 0 && height > 0)
97         {
98             auto cornerSize = jmin (15.0f, jmin (width, height) * 0.45f);
99             auto lineThickness = cornerSize    * 0.1f;
100             auto halfThickness = lineThickness * 0.5f;
101 
102             Path outline;
103             outline.addRoundedRectangle (0.5f + halfThickness, 0.5f + halfThickness, width - lineThickness, height - lineThickness,
104                                          cornerSize, cornerSize,
105                                          ! (flatOnLeft  || flatOnTop),
106                                          ! (flatOnRight || flatOnTop),
107                                          ! (flatOnLeft  || flatOnBottom),
108                                          ! (flatOnRight || flatOnBottom));
109 
110             auto outlineColour = button.findColour (button.getToggleState() ? TextButton::textColourOnId
111                                                                             : TextButton::textColourOffId);
112 
113             g.setColour (baseColour);
114             g.fillPath (outline);
115 
116             if (! button.getToggleState())
117             {
118                 g.setColour (outlineColour);
119                 g.strokePath (outline, PathStrokeType (lineThickness));
120             }
121         }
122     }
123 
drawTickBoxCustomLookAndFeel124     void drawTickBox (Graphics& g, Component& component,
125                       float x, float y, float w, float h,
126                       bool ticked,
127                       bool isEnabled,
128                       bool isMouseOverButton,
129                       bool isButtonDown) override
130     {
131         auto boxSize = w * 0.7f;
132 
133         auto isDownOrDragging = component.isEnabled() && (component.isMouseOverOrDragging() || component.isMouseButtonDown());
134 
135         auto colour = component.findColour (TextButton::buttonColourId)
136                                .withMultipliedSaturation ((component.hasKeyboardFocus (false) || isDownOrDragging) ? 1.3f : 0.9f)
137                                .withMultipliedAlpha (component.isEnabled() ? 1.0f : 0.7f);
138 
139         drawRoundThumb (g, x, y + (h - boxSize) * 0.5f, boxSize, colour,
140                         isEnabled ? ((isButtonDown || isMouseOverButton) ? 1.1f : 0.5f) : 0.3f);
141 
142         if (ticked)
143         {
144             g.setColour (isEnabled ? findColour (TextButton::buttonOnColourId) : Colours::grey);
145 
146             auto scale = 9.0f;
147             auto trans = AffineTransform::scale (w / scale, h / scale).translated (x - 2.5f, y + 1.0f);
148 
149             g.fillPath (LookAndFeel_V4::getTickShape (6.0f), trans);
150         }
151     }
152 
drawLinearSliderThumbCustomLookAndFeel153     void drawLinearSliderThumb (Graphics& g, int x, int y, int width, int height,
154                                 float sliderPos, float minSliderPos, float maxSliderPos,
155                                 const Slider::SliderStyle style, Slider& slider) override
156     {
157         auto sliderRadius = (float) (getSliderThumbRadius (slider) - 2);
158 
159         auto isDownOrDragging = slider.isEnabled() && (slider.isMouseOverOrDragging() || slider.isMouseButtonDown());
160 
161         auto knobColour = slider.findColour (Slider::thumbColourId)
162                                 .withMultipliedSaturation ((slider.hasKeyboardFocus (false) || isDownOrDragging) ? 1.3f : 0.9f)
163                                 .withMultipliedAlpha (slider.isEnabled() ? 1.0f : 0.7f);
164 
165         if (style == Slider::LinearHorizontal || style == Slider::LinearVertical)
166         {
167             float kx, ky;
168 
169             if (style == Slider::LinearVertical)
170             {
171                 kx = (float) x + (float) width * 0.5f;
172                 ky = sliderPos;
173             }
174             else
175             {
176                 kx = sliderPos;
177                 ky = (float) y + (float) height * 0.5f;
178             }
179 
180             auto outlineThickness = slider.isEnabled() ? 0.8f : 0.3f;
181 
182             drawRoundThumb (g,
183                             kx - sliderRadius,
184                             ky - sliderRadius,
185                             sliderRadius * 2.0f,
186                             knobColour, outlineThickness);
187         }
188         else
189         {
190             // Just call the base class for the demo
191             LookAndFeel_V2::drawLinearSliderThumb (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider);
192         }
193     }
194 
drawLinearSliderCustomLookAndFeel195     void drawLinearSlider (Graphics& g, int x, int y, int width, int height,
196                            float sliderPos, float minSliderPos, float maxSliderPos,
197                            const Slider::SliderStyle style, Slider& slider) override
198     {
199         g.fillAll (slider.findColour (Slider::backgroundColourId));
200 
201         if (style == Slider::LinearBar || style == Slider::LinearBarVertical)
202         {
203             Path p;
204 
205             if (style == Slider::LinearBarVertical)
206                 p.addRectangle ((float) x, sliderPos, (float) width, 1.0f + (float) height - sliderPos);
207             else
208                 p.addRectangle ((float) x, (float) y, sliderPos - (float) x, (float) height);
209 
210             auto baseColour = slider.findColour (Slider::rotarySliderFillColourId)
211                                     .withMultipliedSaturation (slider.isEnabled() ? 1.0f : 0.5f)
212                                     .withMultipliedAlpha (0.8f);
213 
214             g.setColour (baseColour);
215             g.fillPath (p);
216 
217             auto lineThickness = jmin (15.0f, (float) jmin (width, height) * 0.45f) * 0.1f;
218             g.drawRect (slider.getLocalBounds().toFloat(), lineThickness);
219         }
220         else
221         {
222             drawLinearSliderBackground (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider);
223             drawLinearSliderThumb      (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider);
224         }
225     }
226 
drawLinearSliderBackgroundCustomLookAndFeel227     void drawLinearSliderBackground (Graphics& g, int x, int y, int width, int height,
228                                      float /*sliderPos*/,
229                                      float /*minSliderPos*/,
230                                      float /*maxSliderPos*/,
231                                      const Slider::SliderStyle /*style*/, Slider& slider) override
232     {
233         auto sliderRadius = (float) getSliderThumbRadius (slider) - 5.0f;
234         Path on, off;
235 
236         if (slider.isHorizontal())
237         {
238             auto iy = (float) y + (float) height * 0.5f - sliderRadius * 0.5f;
239             Rectangle<float> r ((float) x - sliderRadius * 0.5f, iy, (float) width + sliderRadius, sliderRadius);
240             auto onW = r.getWidth() * ((float) slider.valueToProportionOfLength (slider.getValue()));
241 
242             on.addRectangle (r.removeFromLeft (onW));
243             off.addRectangle (r);
244         }
245         else
246         {
247             auto ix = (float) x + (float) width * 0.5f - sliderRadius * 0.5f;
248             Rectangle<float> r (ix, (float) y - sliderRadius * 0.5f, sliderRadius, (float) height + sliderRadius);
249             auto onH = r.getHeight() * ((float) slider.valueToProportionOfLength (slider.getValue()));
250 
251             on.addRectangle (r.removeFromBottom (onH));
252             off.addRectangle (r);
253         }
254 
255         g.setColour (slider.findColour (Slider::rotarySliderFillColourId));
256         g.fillPath (on);
257 
258         g.setColour (slider.findColour (Slider::trackColourId));
259         g.fillPath (off);
260     }
261 
drawRotarySliderCustomLookAndFeel262     void drawRotarySlider (Graphics& g, int x, int y, int width, int height, float sliderPos,
263                            float rotaryStartAngle, float rotaryEndAngle, Slider& slider) override
264     {
265         auto radius = (float) jmin (width / 2, height / 2) - 2.0f;
266         auto centreX = (float) x + (float) width  * 0.5f;
267         auto centreY = (float) y + (float) height * 0.5f;
268         auto rx = centreX - radius;
269         auto ry = centreY - radius;
270         auto rw = radius * 2.0f;
271         auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
272         auto isMouseOver = slider.isMouseOverOrDragging() && slider.isEnabled();
273 
274         if (slider.isEnabled())
275             g.setColour (slider.findColour (Slider::rotarySliderFillColourId).withAlpha (isMouseOver ? 1.0f : 0.7f));
276         else
277             g.setColour (Colour (0x80808080));
278 
279         {
280             Path filledArc;
281             filledArc.addPieSegment (rx, ry, rw, rw, rotaryStartAngle, angle, 0.0);
282             g.fillPath (filledArc);
283         }
284 
285         {
286             auto lineThickness = jmin (15.0f, (float) jmin (width, height) * 0.45f) * 0.1f;
287             Path outlineArc;
288             outlineArc.addPieSegment (rx, ry, rw, rw, rotaryStartAngle, rotaryEndAngle, 0.0);
289             g.strokePath (outlineArc, PathStrokeType (lineThickness));
290         }
291     }
292 };
293 
294 //==============================================================================
295 /** Another really simple look and feel that is very flat and square.
296 
297     This inherits from CustomLookAndFeel above for the linear bar and slider backgrounds.
298 */
299 struct SquareLookAndFeel    : public CustomLookAndFeel
300 {
drawButtonBackgroundSquareLookAndFeel301     void drawButtonBackground (Graphics& g, Button& button, const Colour& backgroundColour,
302                                bool isMouseOverButton, bool isButtonDown) override
303     {
304         auto baseColour = backgroundColour.withMultipliedSaturation (button.hasKeyboardFocus (true) ? 1.3f : 0.9f)
305                                           .withMultipliedAlpha      (button.isEnabled() ? 0.9f : 0.5f);
306 
307         if (isButtonDown || isMouseOverButton)
308             baseColour = baseColour.contrasting (isButtonDown ? 0.2f : 0.1f);
309 
310         auto width  = (float) button.getWidth()  - 1.0f;
311         auto height = (float) button.getHeight() - 1.0f;
312 
313         if (width > 0 && height > 0)
314         {
315             g.setGradientFill (ColourGradient::vertical (baseColour, 0.0f,
316                                                          baseColour.darker (0.1f), height));
317 
318             g.fillRect (button.getLocalBounds());
319         }
320     }
321 
drawTickBoxSquareLookAndFeel322     void drawTickBox (Graphics& g, Component& component,
323                       float x, float y, float w, float h,
324                       bool ticked,
325                       bool isEnabled,
326                       bool /*isMouseOverButton*/,
327                       bool /*isButtonDown*/) override
328     {
329         auto boxSize = w * 0.7f;
330 
331         auto isDownOrDragging = component.isEnabled() && (component.isMouseOverOrDragging() || component.isMouseButtonDown());
332 
333         auto colour = component.findColour (TextButton::buttonOnColourId)
334                                .withMultipliedSaturation ((component.hasKeyboardFocus (false) || isDownOrDragging) ? 1.3f : 0.9f)
335                                .withMultipliedAlpha (component.isEnabled() ? 1.0f : 0.7f);
336 
337         g.setColour (colour);
338 
339         Rectangle<float> r (x, y + (h - boxSize) * 0.5f, boxSize, boxSize);
340         g.fillRect (r);
341 
342         if (ticked)
343         {
344             auto tickPath = LookAndFeel_V4::getTickShape (6.0f);
345             g.setColour (isEnabled ? findColour (TextButton::buttonColourId) : Colours::grey);
346 
347             auto transform = RectanglePlacement (RectanglePlacement::centred)
348                                .getTransformToFit (tickPath.getBounds(),
349                                                    r.reduced (r.getHeight() * 0.05f));
350 
351             g.fillPath (tickPath, transform);
352         }
353     }
354 
drawLinearSliderThumbSquareLookAndFeel355     void drawLinearSliderThumb (Graphics& g, int x, int y, int width, int height,
356                                 float sliderPos, float minSliderPos, float maxSliderPos,
357                                 const Slider::SliderStyle style, Slider& slider) override
358     {
359         auto sliderRadius = (float) getSliderThumbRadius (slider);
360 
361         bool isDownOrDragging = slider.isEnabled() && (slider.isMouseOverOrDragging() || slider.isMouseButtonDown());
362 
363         auto knobColour = slider.findColour (Slider::rotarySliderFillColourId)
364                                 .withMultipliedSaturation ((slider.hasKeyboardFocus (false) || isDownOrDragging) ? 1.3f : 0.9f)
365                                 .withMultipliedAlpha (slider.isEnabled() ? 1.0f : 0.7f);
366 
367         g.setColour (knobColour);
368 
369         if (style == Slider::LinearHorizontal || style == Slider::LinearVertical)
370         {
371             float kx, ky;
372 
373             if (style == Slider::LinearVertical)
374             {
375                 kx = (float) x + (float) width * 0.5f;
376                 ky = sliderPos;
377                 g.fillRect (Rectangle<float> (kx - sliderRadius, ky - 2.5f, sliderRadius * 2.0f, 5.0f));
378             }
379             else
380             {
381                 kx = sliderPos;
382                 ky = (float) y + (float) height * 0.5f;
383                 g.fillRect (Rectangle<float> (kx - 2.5f, ky - sliderRadius, 5.0f, sliderRadius * 2.0f));
384             }
385         }
386         else
387         {
388             // Just call the base class for the demo
389             LookAndFeel_V2::drawLinearSliderThumb (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider);
390         }
391     }
392 
drawRotarySliderSquareLookAndFeel393     void drawRotarySlider (Graphics& g, int x, int y, int width, int height, float sliderPos,
394                            float rotaryStartAngle, float rotaryEndAngle, Slider& slider) override
395     {
396         auto diameter = (float) jmin (width, height) - 4.0f;
397         auto radius = (diameter / 2.0f) * std::cos (MathConstants<float>::pi / 4.0f);
398         auto centreX = (float) x + (float) width  * 0.5f;
399         auto centreY = (float) y + (float) height * 0.5f;
400         auto rx = centreX - radius;
401         auto ry = centreY - radius;
402         auto rw = radius * 2.0f;
403         auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
404         bool isMouseOver = slider.isMouseOverOrDragging() && slider.isEnabled();
405 
406         auto baseColour = slider.isEnabled() ? slider.findColour (Slider::rotarySliderFillColourId).withAlpha (isMouseOver ? 0.8f : 1.0f)
407                                              : Colour (0x80808080);
408 
409         Rectangle<float> r (rx, ry, rw, rw);
410         auto transform = AffineTransform::rotation (angle, r.getCentreX(), r.getCentreY());
411 
412         auto x1 = r.getTopLeft()   .getX();
413         auto y1 = r.getTopLeft()   .getY();
414         auto x2 = r.getBottomLeft().getX();
415         auto y2 = r.getBottomLeft().getY();
416 
417         transform.transformPoints (x1, y1, x2, y2);
418 
419         g.setGradientFill (ColourGradient (baseColour, x1, y1,
420                                            baseColour.darker (0.1f), x2, y2,
421                                            false));
422 
423         Path knob;
424         knob.addRectangle (r);
425         g.fillPath (knob, transform);
426 
427         Path needle;
428         auto r2 = r * 0.1f;
429         needle.addRectangle (r2.withPosition ({ r.getCentreX() - (r2.getWidth() / 2.0f), r.getY() }));
430 
431         g.setColour (slider.findColour (Slider::rotarySliderOutlineColourId));
432         g.fillPath (needle, AffineTransform::rotation (angle, r.getCentreX(), r.getCentreY()));
433     }
434 };
435 
436 //==============================================================================
437 struct LookAndFeelDemoComponent  : public Component
438 {
LookAndFeelDemoComponentLookAndFeelDemoComponent439     LookAndFeelDemoComponent()
440     {
441         addAndMakeVisible (rotarySlider);
442         rotarySlider.setValue (2.5);
443 
444         addAndMakeVisible (verticalSlider);
445         verticalSlider.setValue (6.2);
446 
447         addAndMakeVisible (barSlider);
448         barSlider.setValue (4.5);
449 
450         addAndMakeVisible (incDecSlider);
451         incDecSlider.setRange (0.0, 10.0, 1.0);
452         incDecSlider.setIncDecButtonsMode (Slider::incDecButtonsDraggable_Horizontal);
453 
454         addAndMakeVisible (button1);
455 
456         addAndMakeVisible (button2);
457         button2.setClickingTogglesState (true);
458         button2.setToggleState (true, dontSendNotification);
459 
460         addAndMakeVisible (button3);
461 
462         addAndMakeVisible (button4);
463         button4.setToggleState (true, dontSendNotification);
464 
465         for (int i = 0; i < 3; ++i)
466         {
467             auto* b = radioButtons.add (new TextButton ("Button " + String (i + 1)));
468 
469             addAndMakeVisible (b);
470             b->setRadioGroupId (42);
471             b->setClickingTogglesState (true);
472 
473             switch (i)
474             {
475                 case 0:     b->setConnectedEdges (Button::ConnectedOnRight);                            break;
476                 case 1:     b->setConnectedEdges (Button::ConnectedOnRight + Button::ConnectedOnLeft);  break;
477                 case 2:     b->setConnectedEdges (Button::ConnectedOnLeft);                             break;
478                 default:    break;
479             }
480         }
481 
482         radioButtons.getUnchecked (2)->setToggleState (true, dontSendNotification);
483     }
484 
resizedLookAndFeelDemoComponent485     void resized() override
486     {
487         auto area = getLocalBounds().reduced (10);
488         auto row = area.removeFromTop (100);
489 
490         rotarySlider  .setBounds (row.removeFromLeft (100).reduced (5));
491         verticalSlider.setBounds (row.removeFromLeft (100).reduced (5));
492         barSlider     .setBounds (row.removeFromLeft (100).reduced (5, 25));
493         incDecSlider  .setBounds (row.removeFromLeft (100).reduced (5, 28));
494 
495         row = area.removeFromTop (100);
496         button1.setBounds (row.removeFromLeft (100).reduced (5));
497 
498         auto row2 = row.removeFromTop (row.getHeight() / 2).reduced (0, 10);
499         button2.setBounds (row2.removeFromLeft (100).reduced (5, 0));
500         button3.setBounds (row2.removeFromLeft (100).reduced (5, 0));
501         button4.setBounds (row2.removeFromLeft (100).reduced (5, 0));
502 
503         row2 = (row.removeFromTop (row2.getHeight() + 20).reduced (5, 10));
504 
505         for (auto* b : radioButtons)
506             b->setBounds (row2.removeFromLeft (100));
507     }
508 
509     Slider rotarySlider    { Slider::RotaryHorizontalVerticalDrag, Slider::NoTextBox},
510            verticalSlider  { Slider::LinearVertical, Slider::NoTextBox },
511            barSlider       { Slider::LinearBar, Slider::NoTextBox },
512            incDecSlider    { Slider::IncDecButtons, Slider::TextBoxBelow };
513 
514     TextButton button1  { "Hello World!" },
515                button2  { "Hello World!" },
516                button3  { "Hello World!" };
517 
518     ToggleButton button4 { "Toggle Me" };
519 
520     OwnedArray<TextButton> radioButtons;
521 };
522 
523 //==============================================================================
524 class LookAndFeelDemo   : public Component
525 {
526 public:
LookAndFeelDemo()527     LookAndFeelDemo()
528     {
529         descriptionLabel.setMinimumHorizontalScale (1.0f);
530         descriptionLabel.setText ("This demonstrates how to create a custom look and feel by overriding only the desired methods.\n\n"
531                                   "Components can have their look and feel individually assigned or they will inherit it from their parent. "
532                                   "Colours work in a similar way, they can be set for individual components or a look and feel as a whole.",
533                                   dontSendNotification);
534 
535         addAndMakeVisible (descriptionLabel);
536         addAndMakeVisible (lafBox);
537         addAndMakeVisible (demoComp);
538 
539         addLookAndFeel (new LookAndFeel_V1(), "LookAndFeel_V1");
540         addLookAndFeel (new LookAndFeel_V2(), "LookAndFeel_V2");
541         addLookAndFeel (new LookAndFeel_V3(), "LookAndFeel_V3");
542         addLookAndFeel (new LookAndFeel_V4(), "LookAndFeel_V4 (Dark)");
543         addLookAndFeel (new LookAndFeel_V4 (LookAndFeel_V4::getMidnightColourScheme()), "LookAndFeel_V4 (Midnight)");
544         addLookAndFeel (new LookAndFeel_V4 (LookAndFeel_V4::getGreyColourScheme()),     "LookAndFeel_V4 (Grey)");
545         addLookAndFeel (new LookAndFeel_V4 (LookAndFeel_V4::getLightColourScheme()),    "LookAndFeel_V4 (Light)");
546 
547         auto* claf = new CustomLookAndFeel();
548         addLookAndFeel (claf, "Custom Look And Feel");
549         setupCustomLookAndFeelColours (*claf);
550 
551         auto* slaf = new SquareLookAndFeel();
552         addLookAndFeel (slaf, "Square Look And Feel");
553         setupSquareLookAndFeelColours (*slaf);
554 
555         lafBox.onChange = [this] { setAllLookAndFeels (lookAndFeels[lafBox.getSelectedItemIndex()]); };
556         lafBox.setSelectedItemIndex (3);
557 
558         addAndMakeVisible (randomButton);
559         randomButton.onClick = [this] { lafBox.setSelectedItemIndex (Random().nextInt (lafBox.getNumItems())); };
560 
561         setSize (500, 500);
562     }
563 
paint(Graphics & g)564     void paint (Graphics& g) override
565     {
566         g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground,
567                                            Colour::greyLevel (0.4f)));
568     }
569 
resized()570     void resized() override
571     {
572         auto r = getLocalBounds().reduced (10);
573 
574         descriptionLabel.setBounds (r.removeFromTop (150));
575         lafBox          .setBounds (r.removeFromTop (22).removeFromLeft (250));
576         randomButton    .setBounds (lafBox.getBounds().withX (lafBox.getRight() + 20).withWidth (140));
577         demoComp        .setBounds (r.withTrimmedTop (10));
578     }
579 
580 private:
581     Label descriptionLabel;
582     ComboBox lafBox;
583     TextButton randomButton  { "Assign Randomly" };
584     OwnedArray<LookAndFeel> lookAndFeels;
585     LookAndFeelDemoComponent demoComp;
586 
addLookAndFeel(LookAndFeel * laf,const String & name)587     void addLookAndFeel (LookAndFeel* laf, const String& name)
588     {
589         lookAndFeels.add (laf);
590         lafBox.addItem (name, lafBox.getNumItems() + 1);
591     }
592 
setupCustomLookAndFeelColours(LookAndFeel & laf)593     void setupCustomLookAndFeelColours (LookAndFeel& laf)
594     {
595         laf.setColour (Slider::thumbColourId,               Colour::greyLevel (0.95f));
596         laf.setColour (Slider::textBoxOutlineColourId,      Colours::transparentWhite);
597         laf.setColour (Slider::rotarySliderFillColourId,    Colour (0xff00b5f6));
598         laf.setColour (Slider::rotarySliderOutlineColourId, Colours::white);
599 
600         laf.setColour (TextButton::buttonColourId,  Colours::white);
601         laf.setColour (TextButton::textColourOffId, Colour (0xff00b5f6));
602 
603         laf.setColour (TextButton::buttonOnColourId, laf.findColour (TextButton::textColourOffId));
604         laf.setColour (TextButton::textColourOnId,   laf.findColour (TextButton::buttonColourId));
605     }
606 
setupSquareLookAndFeelColours(LookAndFeel & laf)607     void setupSquareLookAndFeelColours (LookAndFeel& laf)
608     {
609         auto baseColour = Colours::red;
610 
611         laf.setColour (Slider::thumbColourId,               Colour::greyLevel (0.95f));
612         laf.setColour (Slider::textBoxOutlineColourId,      Colours::transparentWhite);
613         laf.setColour (Slider::rotarySliderFillColourId,    baseColour);
614         laf.setColour (Slider::rotarySliderOutlineColourId, Colours::white);
615         laf.setColour (Slider::trackColourId,               Colours::black);
616 
617         laf.setColour (TextButton::buttonColourId,  Colours::white);
618         laf.setColour (TextButton::textColourOffId, baseColour);
619 
620         laf.setColour (TextButton::buttonOnColourId, laf.findColour (TextButton::textColourOffId));
621         laf.setColour (TextButton::textColourOnId,   laf.findColour (TextButton::buttonColourId));
622     }
623 
setAllLookAndFeels(LookAndFeel * laf)624     void setAllLookAndFeels (LookAndFeel* laf)
625     {
626         for (auto* child : demoComp.getChildren())
627             child->setLookAndFeel (laf);
628     }
629 
630     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookAndFeelDemo)
631 };
632