The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

653 lines
19KB

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