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.

581 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. 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. ColourSelector::ColourSelector (int sectionsToShow, int edge, int gapAroundColourSpaceComponent)
  248. : colour (Colours::white),
  249. flags (sectionsToShow),
  250. edgeGap (edge)
  251. {
  252. // not much point having a selector with no components in it!
  253. jassert ((flags & (showColourAtTop | showSliders | showColourspace)) != 0);
  254. updateHSV();
  255. if ((flags & showSliders) != 0)
  256. {
  257. sliders[0].reset (new ColourComponentSlider (TRANS ("red")));
  258. sliders[1].reset (new ColourComponentSlider (TRANS ("green")));
  259. sliders[2].reset (new ColourComponentSlider (TRANS ("blue")));
  260. sliders[3].reset (new ColourComponentSlider (TRANS ("alpha")));
  261. addAndMakeVisible (sliders[0].get());
  262. addAndMakeVisible (sliders[1].get());
  263. addAndMakeVisible (sliders[2].get());
  264. addChildComponent (sliders[3].get());
  265. sliders[3]->setVisible ((flags & showAlphaChannel) != 0);
  266. // VS2015 needs some scoping braces around this if statement to
  267. // avoid a compiler bug.
  268. for (auto& slider : sliders)
  269. {
  270. slider->onValueChange = [this] { changeColour(); };
  271. }
  272. }
  273. if ((flags & showColourspace) != 0)
  274. {
  275. colourSpace.reset (new ColourSpaceView (*this, h, s, v, gapAroundColourSpaceComponent));
  276. hueSelector.reset (new HueSelectorComp (*this, h, gapAroundColourSpaceComponent));
  277. addAndMakeVisible (colourSpace.get());
  278. addAndMakeVisible (hueSelector.get());
  279. }
  280. update (dontSendNotification);
  281. }
  282. ColourSelector::~ColourSelector()
  283. {
  284. dispatchPendingMessages();
  285. swatchComponents.clear();
  286. }
  287. //==============================================================================
  288. Colour ColourSelector::getCurrentColour() const
  289. {
  290. return ((flags & showAlphaChannel) != 0) ? colour : colour.withAlpha ((uint8) 0xff);
  291. }
  292. void ColourSelector::setCurrentColour (Colour c, NotificationType notification)
  293. {
  294. if (c != colour)
  295. {
  296. colour = ((flags & showAlphaChannel) != 0) ? c : c.withAlpha ((uint8) 0xff);
  297. updateHSV();
  298. update (notification);
  299. }
  300. }
  301. void ColourSelector::setHue (float newH)
  302. {
  303. newH = jlimit (0.0f, 1.0f, newH);
  304. if (h != newH)
  305. {
  306. h = newH;
  307. colour = Colour (h, s, v, colour.getFloatAlpha());
  308. update (sendNotification);
  309. }
  310. }
  311. void ColourSelector::setSV (float newS, float newV)
  312. {
  313. newS = jlimit (0.0f, 1.0f, newS);
  314. newV = jlimit (0.0f, 1.0f, newV);
  315. if (s != newS || v != newV)
  316. {
  317. s = newS;
  318. v = newV;
  319. colour = Colour (h, s, v, colour.getFloatAlpha());
  320. update (sendNotification);
  321. }
  322. }
  323. //==============================================================================
  324. void ColourSelector::updateHSV()
  325. {
  326. colour.getHSB (h, s, v);
  327. }
  328. void ColourSelector::update (NotificationType notification)
  329. {
  330. if (sliders[0] != nullptr)
  331. {
  332. sliders[0]->setValue ((int) colour.getRed(), notification);
  333. sliders[1]->setValue ((int) colour.getGreen(), notification);
  334. sliders[2]->setValue ((int) colour.getBlue(), notification);
  335. sliders[3]->setValue ((int) colour.getAlpha(), notification);
  336. }
  337. if (colourSpace != nullptr)
  338. {
  339. colourSpace->updateIfNeeded();
  340. hueSelector->updateIfNeeded();
  341. }
  342. if ((flags & showColourAtTop) != 0)
  343. repaint (previewArea);
  344. if (notification != dontSendNotification)
  345. sendChangeMessage();
  346. if (notification == sendNotificationSync)
  347. dispatchPendingMessages();
  348. }
  349. //==============================================================================
  350. void ColourSelector::paint (Graphics& g)
  351. {
  352. g.fillAll (findColour (backgroundColourId));
  353. if ((flags & showColourAtTop) != 0)
  354. {
  355. auto currentColour = getCurrentColour();
  356. g.fillCheckerBoard (previewArea.toFloat(), 10.0f, 10.0f,
  357. Colour (0xffdddddd).overlaidWith (currentColour),
  358. Colour (0xffffffff).overlaidWith (currentColour));
  359. g.setColour (Colours::white.overlaidWith (currentColour).contrasting());
  360. g.setFont (Font (14.0f, Font::bold));
  361. g.drawText (currentColour.toDisplayString ((flags & showAlphaChannel) != 0),
  362. previewArea, Justification::centred, false);
  363. }
  364. if ((flags & showSliders) != 0)
  365. {
  366. g.setColour (findColour (labelTextColourId));
  367. g.setFont (11.0f);
  368. for (auto& slider : sliders)
  369. {
  370. if (slider->isVisible())
  371. g.drawText (slider->getName() + ":",
  372. 0, slider->getY(),
  373. slider->getX() - 8, slider->getHeight(),
  374. Justification::centredRight, false);
  375. }
  376. }
  377. }
  378. void ColourSelector::resized()
  379. {
  380. const int swatchesPerRow = 8;
  381. const int swatchHeight = 22;
  382. const int numSliders = ((flags & showAlphaChannel) != 0) ? 4 : 3;
  383. const int numSwatches = getNumSwatches();
  384. const int swatchSpace = numSwatches > 0 ? edgeGap + swatchHeight * ((numSwatches + 7) / swatchesPerRow) : 0;
  385. const int sliderSpace = ((flags & showSliders) != 0) ? jmin (22 * numSliders + edgeGap, proportionOfHeight (0.3f)) : 0;
  386. const int topSpace = ((flags & showColourAtTop) != 0) ? jmin (30 + edgeGap * 2, proportionOfHeight (0.2f)) : edgeGap;
  387. previewArea.setBounds (edgeGap, edgeGap, getWidth() - edgeGap * 2, topSpace - edgeGap * 2);
  388. int y = topSpace;
  389. if ((flags & showColourspace) != 0)
  390. {
  391. const int hueWidth = jmin (50, proportionOfWidth (0.15f));
  392. colourSpace->setBounds (edgeGap, y,
  393. getWidth() - hueWidth - edgeGap - 4,
  394. getHeight() - topSpace - sliderSpace - swatchSpace - edgeGap);
  395. hueSelector->setBounds (colourSpace->getRight() + 4, y,
  396. getWidth() - edgeGap - (colourSpace->getRight() + 4),
  397. colourSpace->getHeight());
  398. y = getHeight() - sliderSpace - swatchSpace - edgeGap;
  399. }
  400. if ((flags & showSliders) != 0)
  401. {
  402. auto sliderHeight = jmax (4, sliderSpace / numSliders);
  403. for (int i = 0; i < numSliders; ++i)
  404. {
  405. sliders[i]->setBounds (proportionOfWidth (0.2f), y,
  406. proportionOfWidth (0.72f), sliderHeight - 2);
  407. y += sliderHeight;
  408. }
  409. }
  410. if (numSwatches > 0)
  411. {
  412. const int startX = 8;
  413. const int xGap = 4;
  414. const int yGap = 4;
  415. const int swatchWidth = (getWidth() - startX * 2) / swatchesPerRow;
  416. y += edgeGap;
  417. if (swatchComponents.size() != numSwatches)
  418. {
  419. swatchComponents.clear();
  420. for (int i = 0; i < numSwatches; ++i)
  421. {
  422. auto* sc = new SwatchComponent (*this, i);
  423. swatchComponents.add (sc);
  424. addAndMakeVisible (sc);
  425. }
  426. }
  427. int x = startX;
  428. for (int i = 0; i < swatchComponents.size(); ++i)
  429. {
  430. auto* sc = swatchComponents.getUnchecked(i);
  431. sc->setBounds (x + xGap / 2,
  432. y + yGap / 2,
  433. swatchWidth - xGap,
  434. swatchHeight - yGap);
  435. if (((i + 1) % swatchesPerRow) == 0)
  436. {
  437. x = startX;
  438. y += swatchHeight;
  439. }
  440. else
  441. {
  442. x += swatchWidth;
  443. }
  444. }
  445. }
  446. }
  447. void ColourSelector::changeColour()
  448. {
  449. if (sliders[0] != nullptr)
  450. setCurrentColour (Colour ((uint8) sliders[0]->getValue(),
  451. (uint8) sliders[1]->getValue(),
  452. (uint8) sliders[2]->getValue(),
  453. (uint8) sliders[3]->getValue()));
  454. }
  455. //==============================================================================
  456. int ColourSelector::getNumSwatches() const
  457. {
  458. return 0;
  459. }
  460. Colour ColourSelector::getSwatchColour (int) const
  461. {
  462. jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method
  463. return Colours::black;
  464. }
  465. void ColourSelector::setSwatchColour (int, const Colour&)
  466. {
  467. jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method
  468. }
  469. } // namespace juce