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.

645 lines
19KB

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