1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
29 struct ColourComponentSlider  : public Slider
30 {
ColourComponentSliderjuce::ColourComponentSlider31     ColourComponentSlider (const String& name)  : Slider (name)
32     {
33         setRange (0.0, 255.0, 1.0);
34     }
35 
getTextFromValuejuce::ColourComponentSlider36     String getTextFromValue (double value) override
37     {
38         return String::toHexString ((int) value).toUpperCase().paddedLeft ('0', 2);
39     }
40 
getValueFromTextjuce::ColourComponentSlider41     double getValueFromText (const String& text) override
42     {
43         return (double) text.getHexValue32();
44     }
45 };
46 
47 //==============================================================================
48 class ColourSelector::ColourSpaceView  : public Component
49 {
50 public:
ColourSpaceView(ColourSelector & cs,float & hue,float & sat,float & val,int edgeSize)51     ColourSpaceView (ColourSelector& cs, float& hue, float& sat, float& val, int edgeSize)
52         : owner (cs), h (hue), s (sat), v (val), edge (edgeSize)
53     {
54         addAndMakeVisible (marker);
55         setMouseCursor (MouseCursor::CrosshairCursor);
56     }
57 
paint(Graphics & g)58     void paint (Graphics& g) override
59     {
60         if (colours.isNull())
61         {
62             auto width = getWidth() / 2;
63             auto height = getHeight() / 2;
64             colours = Image (Image::RGB, width, height, false);
65 
66             Image::BitmapData pixels (colours, Image::BitmapData::writeOnly);
67 
68             for (int y = 0; y < height; ++y)
69             {
70                 auto val = 1.0f - (float) y / (float) height;
71 
72                 for (int x = 0; x < width; ++x)
73                 {
74                     auto sat = (float) x / (float) width;
75                     pixels.setPixelColour (x, y, Colour (h, sat, val, 1.0f));
76                 }
77             }
78         }
79 
80         g.setOpacity (1.0f);
81         g.drawImageTransformed (colours,
82                                 RectanglePlacement (RectanglePlacement::stretchToFit)
83                                     .getTransformToFit (colours.getBounds().toFloat(),
84                                                         getLocalBounds().reduced (edge).toFloat()),
85                                 false);
86     }
87 
mouseDown(const MouseEvent & e)88     void mouseDown (const MouseEvent& e) override
89     {
90         mouseDrag (e);
91     }
92 
mouseDrag(const MouseEvent & e)93     void mouseDrag (const MouseEvent& e) override
94     {
95         auto sat =        (float) (e.x - edge) / (float) (getWidth()  - edge * 2);
96         auto val = 1.0f - (float) (e.y - edge) / (float) (getHeight() - edge * 2);
97 
98         owner.setSV (sat, val);
99     }
100 
updateIfNeeded()101     void updateIfNeeded()
102     {
103         if (lastHue != h)
104         {
105             lastHue = h;
106             colours = {};
107             repaint();
108         }
109 
110         updateMarker();
111     }
112 
resized()113     void resized() override
114     {
115         colours = {};
116         updateMarker();
117     }
118 
119 private:
120     ColourSelector& owner;
121     float& h;
122     float& s;
123     float& v;
124     float lastHue = 0;
125     const int edge;
126     Image colours;
127 
128     struct ColourSpaceMarker  : public Component
129     {
ColourSpaceMarkerjuce::ColourSelector::ColourSpaceView::ColourSpaceMarker130         ColourSpaceMarker()
131         {
132             setInterceptsMouseClicks (false, false);
133         }
134 
paintjuce::ColourSelector::ColourSpaceView::ColourSpaceMarker135         void paint (Graphics& g) override
136         {
137             g.setColour (Colour::greyLevel (0.1f));
138             g.drawEllipse (1.0f, 1.0f, (float) getWidth() - 2.0f, (float) getHeight() - 2.0f, 1.0f);
139             g.setColour (Colour::greyLevel (0.9f));
140             g.drawEllipse (2.0f, 2.0f, (float) getWidth() - 4.0f, (float) getHeight() - 4.0f, 1.0f);
141         }
142     };
143 
144     ColourSpaceMarker marker;
145 
updateMarker()146     void updateMarker()
147     {
148         auto markerSize = jmax (14, edge * 2);
149         auto area = getLocalBounds().reduced (edge);
150 
151         marker.setBounds (Rectangle<int> (markerSize, markerSize)
152                             .withCentre (area.getRelativePoint (s, 1.0f - v)));
153     }
154 
155     JUCE_DECLARE_NON_COPYABLE (ColourSpaceView)
156 };
157 
158 //==============================================================================
159 class ColourSelector::HueSelectorComp  : public Component
160 {
161 public:
HueSelectorComp(ColourSelector & cs,float & hue,int edgeSize)162     HueSelectorComp (ColourSelector& cs, float& hue, int edgeSize)
163         : owner (cs), h (hue), edge (edgeSize)
164     {
165         addAndMakeVisible (marker);
166     }
167 
paint(Graphics & g)168     void paint (Graphics& g) override
169     {
170         ColourGradient cg;
171         cg.isRadial = false;
172         cg.point1.setXY (0.0f, (float) edge);
173         cg.point2.setXY (0.0f, (float) getHeight());
174 
175         for (float i = 0.0f; i <= 1.0f; i += 0.02f)
176             cg.addColour (i, Colour (i, 1.0f, 1.0f, 1.0f));
177 
178         g.setGradientFill (cg);
179         g.fillRect (getLocalBounds().reduced (edge));
180     }
181 
resized()182     void resized() override
183     {
184         auto markerSize = jmax (14, edge * 2);
185         auto area = getLocalBounds().reduced (edge);
186 
187         marker.setBounds (Rectangle<int> (getWidth(), markerSize)
188                             .withCentre (area.getRelativePoint (0.5f, h)));
189     }
190 
mouseDown(const MouseEvent & e)191     void mouseDown (const MouseEvent& e) override
192     {
193         mouseDrag (e);
194     }
195 
mouseDrag(const MouseEvent & e)196     void mouseDrag (const MouseEvent& e) override
197     {
198         owner.setHue ((float) (e.y - edge) / (float) (getHeight() - edge * 2));
199     }
200 
updateIfNeeded()201     void updateIfNeeded()
202     {
203         resized();
204     }
205 
206 private:
207     ColourSelector& owner;
208     float& h;
209     const int edge;
210 
211     struct HueSelectorMarker  : public Component
212     {
HueSelectorMarkerjuce::ColourSelector::HueSelectorComp::HueSelectorMarker213         HueSelectorMarker()
214         {
215             setInterceptsMouseClicks (false, false);
216         }
217 
paintjuce::ColourSelector::HueSelectorComp::HueSelectorMarker218         void paint (Graphics& g) override
219         {
220             auto cw = (float) getWidth();
221             auto ch = (float) getHeight();
222 
223             Path p;
224             p.addTriangle (1.0f, 1.0f,
225                            cw * 0.3f, ch * 0.5f,
226                            1.0f, ch - 1.0f);
227 
228             p.addTriangle (cw - 1.0f, 1.0f,
229                            cw * 0.7f, ch * 0.5f,
230                            cw - 1.0f, ch - 1.0f);
231 
232             g.setColour (Colours::white.withAlpha (0.75f));
233             g.fillPath (p);
234 
235             g.setColour (Colours::black.withAlpha (0.75f));
236             g.strokePath (p, PathStrokeType (1.2f));
237         }
238     };
239 
240     HueSelectorMarker marker;
241 
242     JUCE_DECLARE_NON_COPYABLE (HueSelectorComp)
243 };
244 
245 //==============================================================================
246 class ColourSelector::SwatchComponent   : public Component
247 {
248 public:
SwatchComponent(ColourSelector & cs,int itemIndex)249     SwatchComponent (ColourSelector& cs, int itemIndex)
250         : owner (cs), index (itemIndex)
251     {
252     }
253 
paint(Graphics & g)254     void paint (Graphics& g) override
255     {
256         auto col = owner.getSwatchColour (index);
257 
258         g.fillCheckerBoard (getLocalBounds().toFloat(), 6.0f, 6.0f,
259                             Colour (0xffdddddd).overlaidWith (col),
260                             Colour (0xffffffff).overlaidWith (col));
261     }
262 
mouseDown(const MouseEvent &)263     void mouseDown (const MouseEvent&) override
264     {
265         PopupMenu m;
266         m.addItem (1, TRANS("Use this swatch as the current colour"));
267         m.addSeparator();
268         m.addItem (2, TRANS("Set this swatch to the current colour"));
269 
270         m.showMenuAsync (PopupMenu::Options().withTargetComponent (this),
271                          ModalCallbackFunction::forComponent (menuStaticCallback, this));
272     }
273 
274 private:
275     ColourSelector& owner;
276     const int index;
277 
menuStaticCallback(int result,SwatchComponent * comp)278     static void menuStaticCallback (int result, SwatchComponent* comp)
279     {
280         if (comp != nullptr)
281         {
282             if (result == 1)  comp->setColourFromSwatch();
283             if (result == 2)  comp->setSwatchFromColour();
284         }
285     }
286 
setColourFromSwatch()287     void setColourFromSwatch()
288     {
289         owner.setCurrentColour (owner.getSwatchColour (index));
290     }
291 
setSwatchFromColour()292     void setSwatchFromColour()
293     {
294         if (owner.getSwatchColour (index) != owner.getCurrentColour())
295         {
296             owner.setSwatchColour (index, owner.getCurrentColour());
297             repaint();
298         }
299     }
300 
301     JUCE_DECLARE_NON_COPYABLE (SwatchComponent)
302 };
303 
304 //==============================================================================
305 class ColourSelector::ColourPreviewComp  : public Component
306 {
307 public:
ColourPreviewComp(ColourSelector & cs,bool isEditable)308     ColourPreviewComp (ColourSelector& cs, bool isEditable)
309         : owner (cs)
310     {
311         colourLabel.setFont (labelFont);
312         colourLabel.setJustificationType (Justification::centred);
313 
314         if (isEditable)
315         {
316             colourLabel.setEditable (true);
317 
318             colourLabel.onEditorShow = [this]
319             {
320                 if (auto* ed = colourLabel.getCurrentTextEditor())
321                     ed->setInputRestrictions ((owner.flags & showAlphaChannel) ? 8 : 6, "1234567890ABCDEFabcdef");
322             };
323 
324             colourLabel.onEditorHide = [this]
325             {
326                 updateColourIfNecessary (colourLabel.getText());
327             };
328         }
329 
330         addAndMakeVisible (colourLabel);
331     }
332 
updateIfNeeded()333     void updateIfNeeded()
334     {
335         auto newColour = owner.getCurrentColour();
336 
337         if (currentColour != newColour)
338         {
339             currentColour = newColour;
340             auto textColour = (Colours::white.overlaidWith (currentColour).contrasting());
341 
342             colourLabel.setColour (Label::textColourId,            textColour);
343             colourLabel.setColour (Label::textWhenEditingColourId, textColour);
344             colourLabel.setText (currentColour.toDisplayString ((owner.flags & showAlphaChannel) != 0), dontSendNotification);
345 
346             labelWidth = labelFont.getStringWidth (colourLabel.getText());
347 
348             repaint();
349         }
350     }
351 
paint(Graphics & g)352     void paint (Graphics& g) override
353     {
354         g.fillCheckerBoard (getLocalBounds().toFloat(), 10.0f, 10.0f,
355                             Colour (0xffdddddd).overlaidWith (currentColour),
356                             Colour (0xffffffff).overlaidWith (currentColour));
357     }
358 
resized()359     void resized() override
360     {
361         colourLabel.centreWithSize (labelWidth + 10, (int) labelFont.getHeight() + 10);
362     }
363 
364 private:
updateColourIfNecessary(const String & newColourString)365     void updateColourIfNecessary (const String& newColourString)
366     {
367         auto newColour = Colour::fromString (newColourString);
368 
369         if (newColour != currentColour)
370             owner.setCurrentColour (newColour);
371     }
372 
373     ColourSelector& owner;
374 
375     Colour currentColour;
376     Font labelFont { 14.0f, Font::bold };
377     int labelWidth = 0;
378     Label colourLabel;
379 
380     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ColourPreviewComp)
381 };
382 
383 //==============================================================================
ColourSelector(int sectionsToShow,int edge,int gapAroundColourSpaceComponent)384 ColourSelector::ColourSelector (int sectionsToShow, int edge, int gapAroundColourSpaceComponent)
385     : colour (Colours::white),
386       flags (sectionsToShow),
387       edgeGap (edge)
388 {
389     // not much point having a selector with no components in it!
390     jassert ((flags & (showColourAtTop | showSliders | showColourspace)) != 0);
391 
392     updateHSV();
393 
394     if ((flags & showColourAtTop) != 0)
395     {
396         previewComponent.reset (new ColourPreviewComp (*this, (flags & editableColour) != 0));
397         addAndMakeVisible (previewComponent.get());
398     }
399 
400     if ((flags & showSliders) != 0)
401     {
402         sliders[0].reset (new ColourComponentSlider (TRANS ("red")));
403         sliders[1].reset (new ColourComponentSlider (TRANS ("green")));
404         sliders[2].reset (new ColourComponentSlider (TRANS ("blue")));
405         sliders[3].reset (new ColourComponentSlider (TRANS ("alpha")));
406 
407         addAndMakeVisible (sliders[0].get());
408         addAndMakeVisible (sliders[1].get());
409         addAndMakeVisible (sliders[2].get());
410         addChildComponent (sliders[3].get());
411 
412         sliders[3]->setVisible ((flags & showAlphaChannel) != 0);
413 
414         // VS2015 needs some scoping braces around this if statement to
415         // avoid a compiler bug.
416         for (auto& slider : sliders)
417         {
418             slider->onValueChange = [this] { changeColour(); };
419         }
420     }
421 
422     if ((flags & showColourspace) != 0)
423     {
424         colourSpace.reset (new ColourSpaceView (*this, h, s, v, gapAroundColourSpaceComponent));
425         hueSelector.reset (new HueSelectorComp (*this, h, gapAroundColourSpaceComponent));
426 
427         addAndMakeVisible (colourSpace.get());
428         addAndMakeVisible (hueSelector.get());
429     }
430 
431     update (dontSendNotification);
432 }
433 
~ColourSelector()434 ColourSelector::~ColourSelector()
435 {
436     dispatchPendingMessages();
437     swatchComponents.clear();
438 }
439 
440 //==============================================================================
getCurrentColour() const441 Colour ColourSelector::getCurrentColour() const
442 {
443     return ((flags & showAlphaChannel) != 0) ? colour : colour.withAlpha ((uint8) 0xff);
444 }
445 
setCurrentColour(Colour c,NotificationType notification)446 void ColourSelector::setCurrentColour (Colour c, NotificationType notification)
447 {
448     if (c != colour)
449     {
450         colour = ((flags & showAlphaChannel) != 0) ? c : c.withAlpha ((uint8) 0xff);
451 
452         updateHSV();
453         update (notification);
454     }
455 }
456 
setHue(float newH)457 void ColourSelector::setHue (float newH)
458 {
459     newH = jlimit (0.0f, 1.0f, newH);
460 
461     if (h != newH)
462     {
463         h = newH;
464         colour = Colour (h, s, v, colour.getFloatAlpha());
465         update (sendNotification);
466     }
467 }
468 
setSV(float newS,float newV)469 void ColourSelector::setSV (float newS, float newV)
470 {
471     newS = jlimit (0.0f, 1.0f, newS);
472     newV = jlimit (0.0f, 1.0f, newV);
473 
474     if (s != newS || v != newV)
475     {
476         s = newS;
477         v = newV;
478         colour = Colour (h, s, v, colour.getFloatAlpha());
479         update (sendNotification);
480     }
481 }
482 
483 //==============================================================================
updateHSV()484 void ColourSelector::updateHSV()
485 {
486     colour.getHSB (h, s, v);
487 }
488 
update(NotificationType notification)489 void ColourSelector::update (NotificationType notification)
490 {
491     if (sliders[0] != nullptr)
492     {
493         sliders[0]->setValue ((int) colour.getRed(),   notification);
494         sliders[1]->setValue ((int) colour.getGreen(), notification);
495         sliders[2]->setValue ((int) colour.getBlue(),  notification);
496         sliders[3]->setValue ((int) colour.getAlpha(), notification);
497     }
498 
499     if (colourSpace != nullptr)
500     {
501         colourSpace->updateIfNeeded();
502         hueSelector->updateIfNeeded();
503     }
504 
505     if (previewComponent != nullptr)
506         previewComponent->updateIfNeeded();
507 
508     if (notification != dontSendNotification)
509         sendChangeMessage();
510 
511     if (notification == sendNotificationSync)
512         dispatchPendingMessages();
513 }
514 
515 //==============================================================================
paint(Graphics & g)516 void ColourSelector::paint (Graphics& g)
517 {
518     g.fillAll (findColour (backgroundColourId));
519 
520     if ((flags & showSliders) != 0)
521     {
522         g.setColour (findColour (labelTextColourId));
523         g.setFont (11.0f);
524 
525         for (auto& slider : sliders)
526         {
527             if (slider->isVisible())
528                 g.drawText (slider->getName() + ":",
529                             0, slider->getY(),
530                             slider->getX() - 8, slider->getHeight(),
531                             Justification::centredRight, false);
532         }
533     }
534 }
535 
resized()536 void ColourSelector::resized()
537 {
538     const int swatchesPerRow = 8;
539     const int swatchHeight = 22;
540 
541     const int numSliders = ((flags & showAlphaChannel) != 0) ? 4 : 3;
542     const int numSwatches = getNumSwatches();
543 
544     const int swatchSpace = numSwatches > 0 ? edgeGap + swatchHeight * ((numSwatches + 7) / swatchesPerRow) : 0;
545     const int sliderSpace = ((flags & showSliders) != 0)  ? jmin (22 * numSliders + edgeGap, proportionOfHeight (0.3f)) : 0;
546     const int topSpace = ((flags & showColourAtTop) != 0) ? jmin (30 + edgeGap * 2, proportionOfHeight (0.2f)) : edgeGap;
547 
548     if (previewComponent != nullptr)
549         previewComponent->setBounds (edgeGap, edgeGap, getWidth() - edgeGap * 2, topSpace - edgeGap * 2);
550 
551     int y = topSpace;
552 
553     if ((flags & showColourspace) != 0)
554     {
555         const int hueWidth = jmin (50, proportionOfWidth (0.15f));
556 
557         colourSpace->setBounds (edgeGap, y,
558                                 getWidth() - hueWidth - edgeGap - 4,
559                                 getHeight() - topSpace - sliderSpace - swatchSpace - edgeGap);
560 
561         hueSelector->setBounds (colourSpace->getRight() + 4, y,
562                                 getWidth() - edgeGap - (colourSpace->getRight() + 4),
563                                 colourSpace->getHeight());
564 
565         y = getHeight() - sliderSpace - swatchSpace - edgeGap;
566     }
567 
568     if ((flags & showSliders) != 0)
569     {
570         auto sliderHeight = jmax (4, sliderSpace / numSliders);
571 
572         for (int i = 0; i < numSliders; ++i)
573         {
574             sliders[i]->setBounds (proportionOfWidth (0.2f), y,
575                                    proportionOfWidth (0.72f), sliderHeight - 2);
576 
577             y += sliderHeight;
578         }
579     }
580 
581     if (numSwatches > 0)
582     {
583         const int startX = 8;
584         const int xGap = 4;
585         const int yGap = 4;
586         const int swatchWidth = (getWidth() - startX * 2) / swatchesPerRow;
587         y += edgeGap;
588 
589         if (swatchComponents.size() != numSwatches)
590         {
591             swatchComponents.clear();
592 
593             for (int i = 0; i < numSwatches; ++i)
594             {
595                 auto* sc = new SwatchComponent (*this, i);
596                 swatchComponents.add (sc);
597                 addAndMakeVisible (sc);
598             }
599         }
600 
601         int x = startX;
602 
603         for (int i = 0; i < swatchComponents.size(); ++i)
604         {
605             auto* sc = swatchComponents.getUnchecked(i);
606 
607             sc->setBounds (x + xGap / 2,
608                            y + yGap / 2,
609                            swatchWidth - xGap,
610                            swatchHeight - yGap);
611 
612             if (((i + 1) % swatchesPerRow) == 0)
613             {
614                 x = startX;
615                 y += swatchHeight;
616             }
617             else
618             {
619                 x += swatchWidth;
620             }
621         }
622     }
623 }
624 
changeColour()625 void ColourSelector::changeColour()
626 {
627     if (sliders[0] != nullptr)
628         setCurrentColour (Colour ((uint8) sliders[0]->getValue(),
629                                   (uint8) sliders[1]->getValue(),
630                                   (uint8) sliders[2]->getValue(),
631                                   (uint8) sliders[3]->getValue()));
632 }
633 
634 //==============================================================================
getNumSwatches() const635 int ColourSelector::getNumSwatches() const
636 {
637     return 0;
638 }
639 
getSwatchColour(int) const640 Colour ColourSelector::getSwatchColour (int) const
641 {
642     jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method
643     return Colours::black;
644 }
645 
setSwatchColour(int,const Colour &)646 void ColourSelector::setSwatchColour (int, const Colour&)
647 {
648     jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method
649 }
650 
651 } // namespace juce
652