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.

584 lines
17KB

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