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.

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