Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

juce_ColourSelector.cpp 19KB

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