Audio plugin host https://kx.studio/carla
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.

576 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, const 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. const int width = getWidth() / 2;
  71. const int 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. const float val = 1.0f - y / (float) height;
  77. for (int x = 0; x < width; ++x)
  78. {
  79. const float 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. const float sat = (e.x - edge) / (float) (getWidth() - edge * 2);
  98. const float 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. const float cw = (float) getWidth();
  144. const float 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, const 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. const Colour c (owner.getSwatchColour (index));
  214. g.fillCheckerBoard (getLocalBounds(), 6, 6,
  215. Colour (0xffdddddd).overlaidWith (c),
  216. Colour (0xffffffff).overlaidWith (c));
  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 (const int sectionsToShow, const int edge, const 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. addAndMakeVisible (sliders[0] = new ColourComponentSlider (TRANS ("red")));
  266. addAndMakeVisible (sliders[1] = new ColourComponentSlider (TRANS ("green")));
  267. addAndMakeVisible (sliders[2] = new ColourComponentSlider (TRANS ("blue")));
  268. addChildComponent (sliders[3] = new ColourComponentSlider (TRANS ("alpha")));
  269. sliders[3]->setVisible ((flags & showAlphaChannel) != 0);
  270. for (int i = 4; --i >= 0;)
  271. sliders[i]->addListener (this);
  272. }
  273. if ((flags & showColourspace) != 0)
  274. {
  275. addAndMakeVisible (colourSpace = new ColourSpaceView (*this, h, s, v, gapAroundColourSpaceComponent));
  276. addAndMakeVisible (hueSelector = new HueSelectorComp (*this, h, gapAroundColourSpaceComponent));
  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. const Colour currentColour (getCurrentColour());
  354. g.fillCheckerBoard (previewArea, 10, 10,
  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 (int i = 4; --i >= 0;)
  367. {
  368. if (sliders[i]->isVisible())
  369. g.drawText (sliders[i]->getName() + ":",
  370. 0, sliders[i]->getY(),
  371. sliders[i]->getX() - 8, sliders[i]->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. const int 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::sliderValueChanged (Slider*)
  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 (const 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