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