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.

571 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 ColourSelector::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 ColourSelector::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_, float& h_, float& s_, float& v_, const int edgeSize)
  60. : owner (owner_), h (h_), s (s_), v (v_), lastHue (0.0f), edge (edgeSize)
  61. {
  62. addAndMakeVisible (&marker);
  63. setMouseCursor (MouseCursor::CrosshairCursor);
  64. }
  65. void paint (Graphics& g)
  66. {
  67. if (colours.isNull())
  68. {
  69. const int width = getWidth() / 2;
  70. const int height = getHeight() / 2;
  71. colours = Image (Image::RGB, width, height, false);
  72. Image::BitmapData pixels (colours, Image::BitmapData::writeOnly);
  73. for (int y = 0; y < height; ++y)
  74. {
  75. const float val = 1.0f - y / (float) height;
  76. for (int x = 0; x < width; ++x)
  77. {
  78. const float sat = x / (float) width;
  79. pixels.setPixelColour (x, y, Colour (h, sat, val, 1.0f));
  80. }
  81. }
  82. }
  83. g.setOpacity (1.0f);
  84. g.drawImage (colours, edge, edge, getWidth() - edge * 2, getHeight() - edge * 2,
  85. 0, 0, colours.getWidth(), colours.getHeight());
  86. }
  87. void mouseDown (const MouseEvent& e)
  88. {
  89. mouseDrag (e);
  90. }
  91. void mouseDrag (const MouseEvent& e)
  92. {
  93. const float sat = (e.x - edge) / (float) (getWidth() - edge * 2);
  94. const float val = 1.0f - (e.y - edge) / (float) (getHeight() - edge * 2);
  95. owner.setSV (sat, val);
  96. }
  97. void updateIfNeeded()
  98. {
  99. if (lastHue != h)
  100. {
  101. lastHue = h;
  102. colours = Image::null;
  103. repaint();
  104. }
  105. updateMarker();
  106. }
  107. void resized()
  108. {
  109. colours = Image::null;
  110. updateMarker();
  111. }
  112. private:
  113. ColourSelector& owner;
  114. float& h;
  115. float& s;
  116. float& v;
  117. float lastHue;
  118. ColourSpaceMarker marker;
  119. const int edge;
  120. Image colours;
  121. void updateMarker()
  122. {
  123. marker.setBounds (roundToInt ((getWidth() - edge * 2) * s),
  124. roundToInt ((getHeight() - edge * 2) * (1.0f - v)),
  125. edge * 2, edge * 2);
  126. }
  127. JUCE_DECLARE_NON_COPYABLE (ColourSpaceView);
  128. };
  129. //==============================================================================
  130. class ColourSelector::HueSelectorMarker : public Component
  131. {
  132. public:
  133. HueSelectorMarker()
  134. {
  135. setInterceptsMouseClicks (false, false);
  136. }
  137. void paint (Graphics& g)
  138. {
  139. const float w = (float) getWidth();
  140. const float h = (float) getHeight();
  141. Path p;
  142. p.addTriangle (1.0f, 1.0f,
  143. w * 0.3f, h * 0.5f,
  144. 1.0f, h - 1.0f);
  145. p.addTriangle (w - 1.0f, 1.0f,
  146. w * 0.7f, h * 0.5f,
  147. w - 1.0f, h - 1.0f);
  148. g.setColour (Colours::white.withAlpha (0.75f));
  149. g.fillPath (p);
  150. g.setColour (Colours::black.withAlpha (0.75f));
  151. g.strokePath (p, PathStrokeType (1.2f));
  152. }
  153. private:
  154. JUCE_DECLARE_NON_COPYABLE (HueSelectorMarker);
  155. };
  156. //==============================================================================
  157. class ColourSelector::HueSelectorComp : public Component
  158. {
  159. public:
  160. HueSelectorComp (ColourSelector& owner_, float& h_, float& s_, float& v_, const int edgeSize)
  161. : owner (owner_), h (h_), s (s_), v (v_), edge (edgeSize)
  162. {
  163. addAndMakeVisible (&marker);
  164. }
  165. void paint (Graphics& g)
  166. {
  167. const float yScale = 1.0f / (getHeight() - edge * 2);
  168. const Rectangle<int> clip (g.getClipBounds());
  169. for (int y = jmin (clip.getBottom(), getHeight() - edge); --y >= jmax (edge, clip.getY());)
  170. {
  171. g.setColour (Colour ((y - edge) * yScale, 1.0f, 1.0f, 1.0f));
  172. g.fillRect (edge, y, getWidth() - edge * 2, 1);
  173. }
  174. }
  175. void resized()
  176. {
  177. marker.setBounds (0, roundToInt ((getHeight() - edge * 2) * h),
  178. getWidth(), edge * 2);
  179. }
  180. void mouseDown (const MouseEvent& e)
  181. {
  182. mouseDrag (e);
  183. }
  184. void mouseDrag (const MouseEvent& e)
  185. {
  186. owner.setHue ((e.y - edge) / (float) (getHeight() - edge * 2));
  187. }
  188. void updateIfNeeded()
  189. {
  190. resized();
  191. }
  192. private:
  193. ColourSelector& owner;
  194. float& h;
  195. float& s;
  196. float& v;
  197. HueSelectorMarker marker;
  198. const int edge;
  199. JUCE_DECLARE_NON_COPYABLE (HueSelectorComp);
  200. };
  201. //==============================================================================
  202. class ColourSelector::SwatchComponent : public Component
  203. {
  204. public:
  205. SwatchComponent (ColourSelector& owner_, int index_)
  206. : owner (owner_), index (index_)
  207. {
  208. }
  209. void paint (Graphics& g)
  210. {
  211. const Colour colour (owner.getSwatchColour (index));
  212. g.fillCheckerBoard (getLocalBounds(), 6, 6,
  213. Colour (0xffdddddd).overlaidWith (colour),
  214. Colour (0xffffffff).overlaidWith (colour));
  215. }
  216. void mouseDown (const MouseEvent&)
  217. {
  218. PopupMenu m;
  219. m.addItem (1, TRANS("Use this swatch as the current colour"));
  220. m.addSeparator();
  221. m.addItem (2, TRANS("Set this swatch to the current colour"));
  222. m.showMenuAsync (PopupMenu::Options().withTargetComponent (this),
  223. ModalCallbackFunction::forComponent (menuStaticCallback, this));
  224. }
  225. private:
  226. ColourSelector& owner;
  227. const int index;
  228. static void menuStaticCallback (int result, SwatchComponent* comp)
  229. {
  230. if (comp != nullptr)
  231. {
  232. if (result == 1)
  233. comp->setColourFromSwatch();
  234. else if (result == 2)
  235. comp->setSwatchFromColour();
  236. }
  237. }
  238. void setColourFromSwatch()
  239. {
  240. owner.setCurrentColour (owner.getSwatchColour (index));
  241. }
  242. void setSwatchFromColour()
  243. {
  244. if (owner.getSwatchColour (index) != owner.getCurrentColour())
  245. {
  246. owner.setSwatchColour (index, owner.getCurrentColour());
  247. repaint();
  248. }
  249. }
  250. JUCE_DECLARE_NON_COPYABLE (SwatchComponent);
  251. };
  252. //==============================================================================
  253. ColourSelector::ColourSelector (const int flags_,
  254. const int edgeGap_,
  255. const int gapAroundColourSpaceComponent)
  256. : colour (Colours::white),
  257. flags (flags_),
  258. edgeGap (edgeGap_)
  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, s, v, gapAroundColourSpaceComponent));
  277. }
  278. update();
  279. }
  280. ColourSelector::~ColourSelector()
  281. {
  282. dispatchPendingMessages();
  283. swatchComponents.clear();
  284. }
  285. //==============================================================================
  286. Colour ColourSelector::getCurrentColour() const
  287. {
  288. return ((flags & showAlphaChannel) != 0) ? colour
  289. : colour.withAlpha ((uint8) 0xff);
  290. }
  291. void ColourSelector::setCurrentColour (const Colour& c)
  292. {
  293. if (c != colour)
  294. {
  295. colour = ((flags & showAlphaChannel) != 0) ? c : c.withAlpha ((uint8) 0xff);
  296. updateHSV();
  297. update();
  298. }
  299. }
  300. void ColourSelector::setHue (float newH)
  301. {
  302. newH = jlimit (0.0f, 1.0f, newH);
  303. if (h != newH)
  304. {
  305. h = newH;
  306. colour = Colour (h, s, v, colour.getFloatAlpha());
  307. update();
  308. }
  309. }
  310. void ColourSelector::setSV (float newS, float newV)
  311. {
  312. newS = jlimit (0.0f, 1.0f, newS);
  313. newV = jlimit (0.0f, 1.0f, newV);
  314. if (s != newS || v != newV)
  315. {
  316. s = newS;
  317. v = newV;
  318. colour = Colour (h, s, v, colour.getFloatAlpha());
  319. update();
  320. }
  321. }
  322. //==============================================================================
  323. void ColourSelector::updateHSV()
  324. {
  325. colour.getHSB (h, s, v);
  326. }
  327. void ColourSelector::update()
  328. {
  329. if (sliders[0] != nullptr)
  330. {
  331. sliders[0]->setValue ((int) colour.getRed());
  332. sliders[1]->setValue ((int) colour.getGreen());
  333. sliders[2]->setValue ((int) colour.getBlue());
  334. sliders[3]->setValue ((int) colour.getAlpha());
  335. }
  336. if (colourSpace != nullptr)
  337. {
  338. colourSpace->updateIfNeeded();
  339. hueSelector->updateIfNeeded();
  340. }
  341. if ((flags & showColourAtTop) != 0)
  342. repaint (previewArea);
  343. sendChangeMessage();
  344. }
  345. //==============================================================================
  346. void ColourSelector::paint (Graphics& g)
  347. {
  348. g.fillAll (findColour (backgroundColourId));
  349. if ((flags & showColourAtTop) != 0)
  350. {
  351. const Colour currentColour (getCurrentColour());
  352. g.fillCheckerBoard (previewArea, 10, 10,
  353. Colour (0xffdddddd).overlaidWith (currentColour),
  354. Colour (0xffffffff).overlaidWith (currentColour));
  355. g.setColour (Colours::white.overlaidWith (currentColour).contrasting());
  356. g.setFont (Font (14.0f, Font::bold));
  357. g.drawText (currentColour.toDisplayString ((flags & showAlphaChannel) != 0),
  358. previewArea.getX(), previewArea.getY(), previewArea.getWidth(), previewArea.getHeight(),
  359. Justification::centred, false);
  360. }
  361. if ((flags & showSliders) != 0)
  362. {
  363. g.setColour (findColour (labelTextColourId));
  364. g.setFont (11.0f);
  365. for (int i = 4; --i >= 0;)
  366. {
  367. if (sliders[i]->isVisible())
  368. g.drawText (sliders[i]->getName() + ":",
  369. 0, sliders[i]->getY(),
  370. sliders[i]->getX() - 8, sliders[i]->getHeight(),
  371. Justification::centredRight, false);
  372. }
  373. }
  374. }
  375. void ColourSelector::resized()
  376. {
  377. const int swatchesPerRow = 8;
  378. const int swatchHeight = 22;
  379. const int numSliders = ((flags & showAlphaChannel) != 0) ? 4 : 3;
  380. const int numSwatches = getNumSwatches();
  381. const int swatchSpace = numSwatches > 0 ? edgeGap + swatchHeight * ((numSwatches + 7) / swatchesPerRow) : 0;
  382. const int sliderSpace = ((flags & showSliders) != 0) ? jmin (22 * numSliders + edgeGap, proportionOfHeight (0.3f)) : 0;
  383. const int topSpace = ((flags & showColourAtTop) != 0) ? jmin (30 + edgeGap * 2, proportionOfHeight (0.2f)) : edgeGap;
  384. previewArea.setBounds (edgeGap, edgeGap, getWidth() - edgeGap * 2, topSpace - edgeGap * 2);
  385. int y = topSpace;
  386. if ((flags & showColourspace) != 0)
  387. {
  388. const int hueWidth = jmin (50, proportionOfWidth (0.15f));
  389. colourSpace->setBounds (edgeGap, y,
  390. getWidth() - hueWidth - edgeGap - 4,
  391. getHeight() - topSpace - sliderSpace - swatchSpace - edgeGap);
  392. hueSelector->setBounds (colourSpace->getRight() + 4, y,
  393. getWidth() - edgeGap - (colourSpace->getRight() + 4),
  394. colourSpace->getHeight());
  395. y = getHeight() - sliderSpace - swatchSpace - edgeGap;
  396. }
  397. if ((flags & showSliders) != 0)
  398. {
  399. const int sliderHeight = jmax (4, sliderSpace / numSliders);
  400. for (int i = 0; i < numSliders; ++i)
  401. {
  402. sliders[i]->setBounds (proportionOfWidth (0.2f), y,
  403. proportionOfWidth (0.72f), sliderHeight - 2);
  404. y += sliderHeight;
  405. }
  406. }
  407. if (numSwatches > 0)
  408. {
  409. const int startX = 8;
  410. const int xGap = 4;
  411. const int yGap = 4;
  412. const int swatchWidth = (getWidth() - startX * 2) / swatchesPerRow;
  413. y += edgeGap;
  414. if (swatchComponents.size() != numSwatches)
  415. {
  416. swatchComponents.clear();
  417. for (int i = 0; i < numSwatches; ++i)
  418. {
  419. SwatchComponent* const sc = new SwatchComponent (*this, i);
  420. swatchComponents.add (sc);
  421. addAndMakeVisible (sc);
  422. }
  423. }
  424. int x = startX;
  425. for (int i = 0; i < swatchComponents.size(); ++i)
  426. {
  427. SwatchComponent* const sc = swatchComponents.getUnchecked(i);
  428. sc->setBounds (x + xGap / 2,
  429. y + yGap / 2,
  430. swatchWidth - xGap,
  431. swatchHeight - yGap);
  432. if (((i + 1) % swatchesPerRow) == 0)
  433. {
  434. x = startX;
  435. y += swatchHeight;
  436. }
  437. else
  438. {
  439. x += swatchWidth;
  440. }
  441. }
  442. }
  443. }
  444. void ColourSelector::sliderValueChanged (Slider*)
  445. {
  446. if (sliders[0] != nullptr)
  447. setCurrentColour (Colour ((uint8) sliders[0]->getValue(),
  448. (uint8) sliders[1]->getValue(),
  449. (uint8) sliders[2]->getValue(),
  450. (uint8) sliders[3]->getValue()));
  451. }
  452. //==============================================================================
  453. int ColourSelector::getNumSwatches() const
  454. {
  455. return 0;
  456. }
  457. Colour ColourSelector::getSwatchColour (const int) const
  458. {
  459. jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method
  460. return Colours::black;
  461. }
  462. void ColourSelector::setSwatchColour (const int, const Colour&) const
  463. {
  464. jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method
  465. }