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.

564 lines
16KB

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