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.

580 lines
17KB

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