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.

433 lines
15KB

  1. #ifndef BLOCKCOMPONENTS_H_INCLUDED
  2. #define BLOCKCOMPONENTS_H_INCLUDED
  3. #include "../JuceLibraryCode/JuceHeader.h"
  4. //==============================================================================
  5. /**
  6. Base class that renders a Block on the screen
  7. */
  8. class BlockComponent : public Component,
  9. public SettableTooltipClient,
  10. private TouchSurface::Listener,
  11. private ControlButton::Listener,
  12. private Timer
  13. {
  14. public:
  15. BlockComponent (Block::Ptr blockToUse)
  16. : block (blockToUse)
  17. {
  18. updateStatsAndTooltip();
  19. // Register BlockComponent as a listener to the touch surface
  20. if (auto touchSurface = block->getTouchSurface())
  21. touchSurface->addListener (this);
  22. // Register BlockComponent as a listener to any buttons
  23. for (auto button : block->getButtons())
  24. button->addListener (this);
  25. // If this is a Lightpad then set the grid program to be blank
  26. if (auto grid = block->getLEDGrid())
  27. grid->setProgram (new BitmapLEDProgram(*grid));
  28. // If this is a Lightpad then redraw it at 25Hz
  29. if (block->getType() == Block::lightPadBlock)
  30. startTimerHz (25);
  31. }
  32. ~BlockComponent()
  33. {
  34. // Remove any listeners
  35. if (auto touchSurface = block->getTouchSurface())
  36. touchSurface->removeListener (this);
  37. for (auto button : block->getButtons())
  38. button->removeListener (this);
  39. }
  40. /** Called periodically to update the tooltip with inforamtion about the Block */
  41. void updateStatsAndTooltip()
  42. {
  43. // Get the battery level of this Block and inform any subclasses
  44. auto batteryLevel = block->getBatteryLevel();
  45. handleBatteryLevelUpdate (batteryLevel);
  46. // Update the tooltip
  47. setTooltip ("Name = " + block->getDeviceDescription() + "\n"
  48. + "UID = " + String (block->uid) + "\n"
  49. + "Serial number = " + block->serialNumber + "\n"
  50. + "Battery level = " + String ((int) (batteryLevel * 100)) + "%"
  51. + (block->isBatteryCharging() ? "++"
  52. : "--"));
  53. }
  54. /** Subclasses should override this to paint the Block object on the screen */
  55. virtual void paint (Graphics&) override = 0;
  56. /** Subclasses can override this to receive button down events from the Block */
  57. virtual void handleButtonPressed (ControlButton::ButtonFunction, uint32) {}
  58. /** Subclasses can override this to receive button up events from the Block */
  59. virtual void handleButtonReleased (ControlButton::ButtonFunction, uint32) {}
  60. /** Subclasses can override this to receive touch events from the Block */
  61. virtual void handleTouchChange (TouchSurface::Touch) {}
  62. /** Subclasses can override this to battery level updates from the Block */
  63. virtual void handleBatteryLevelUpdate (float) {}
  64. /** The Block object that this class represents */
  65. Block::Ptr block;
  66. //==============================================================================
  67. /** Returns an integer index corresponding to a physical position on the hardware
  68. for each type of Control Block. */
  69. static int controlButtonFunctionToIndex (ControlButton::ButtonFunction f)
  70. {
  71. using CB = ControlButton;
  72. static Array<ControlButton::ButtonFunction> map[] =
  73. {
  74. { CB::mode, CB::button0 },
  75. { CB::volume, CB::button1 },
  76. { CB::scale, CB::button2, CB::click },
  77. { CB::chord, CB::button3, CB::snap },
  78. { CB::arp, CB::button4, CB::back },
  79. { CB::sustain, CB::button5, CB::playOrPause },
  80. { CB::octave, CB::button6, CB::record },
  81. { CB::love, CB::button7, CB::learn },
  82. { CB::up },
  83. { CB::down }
  84. };
  85. for (int i = 0; i < numElementsInArray (map); ++i)
  86. if (map[i].contains (f))
  87. return i;
  88. return -1;
  89. }
  90. private:
  91. /** Used to call repaint() periodically */
  92. void timerCallback() override { repaint(); }
  93. /** Overridden from TouchSurface::Listener */
  94. void touchChanged (TouchSurface&, const TouchSurface::Touch& t) override { handleTouchChange (t); }
  95. /** Overridden from ControlButton::Listener */
  96. void buttonPressed (ControlButton& b, Block::Timestamp t) override { handleButtonPressed (b.getType(), t); }
  97. /** Overridden from ControlButton::Listener */
  98. void buttonReleased (ControlButton& b, Block::Timestamp t) override { handleButtonReleased (b.getType(), t); }
  99. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlockComponent)
  100. };
  101. //==============================================================================
  102. /**
  103. Class that renders a Lightpad on the screen
  104. */
  105. class LightpadComponent : public BlockComponent
  106. {
  107. public:
  108. LightpadComponent (Block::Ptr blockToUse)
  109. : BlockComponent (blockToUse)
  110. {
  111. }
  112. void paint (Graphics& g) override
  113. {
  114. auto r = getLocalBounds().toFloat();
  115. // clip the drawing area to only draw in the block area
  116. {
  117. Path clipArea;
  118. clipArea.addRoundedRectangle (r, r.getWidth() / 20.0f);
  119. g.reduceClipRegion (clipArea);
  120. }
  121. // Fill a black square for the Lightpad
  122. g.fillAll (Colours::black);
  123. // size ration between physical and on-screen blocks
  124. Point<float> ratio (r.getWidth() / block->getWidth(),
  125. r.getHeight() / block->getHeight());
  126. float maxCircleSize = block->getWidth() / 3.0f;
  127. // iterate over the list of current touches and draw them on the onscreen Block
  128. for (auto touch : touches)
  129. {
  130. float circleSize = touch.touch.z * maxCircleSize;
  131. Point<float> touchPosition (touch.touch.x,
  132. touch.touch.y);
  133. auto blob = Rectangle<float> (circleSize, circleSize)
  134. .withCentre (touchPosition) * ratio;
  135. ColourGradient cg (colourArray[touch.touch.index], blob.getCentreX(), blob.getCentreY(),
  136. Colours::transparentBlack, blob.getRight(), blob.getBottom(),
  137. true);
  138. g.setGradientFill (cg);
  139. g.fillEllipse (blob);
  140. }
  141. }
  142. void handleTouchChange (TouchSurface::Touch touch) override { touches.updateTouch (touch); }
  143. private:
  144. /** An Array of colours to use for touches */
  145. Array<Colour> colourArray = { Colours::red,
  146. Colours::blue,
  147. Colours::green,
  148. Colours::yellow,
  149. Colours::white,
  150. Colours::hotpink,
  151. Colours::mediumpurple };
  152. /** A list of current Touch events */
  153. TouchList<TouchSurface::Touch> touches;
  154. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LightpadComponent)
  155. };
  156. //==============================================================================
  157. /**
  158. Class that renders a Control Block on the screen
  159. */
  160. class ControlBlockComponent : public BlockComponent
  161. {
  162. public:
  163. ControlBlockComponent (Block::Ptr blockToUse)
  164. : BlockComponent (blockToUse),
  165. numLeds (block->getLEDRow()->getNumLEDs())
  166. {
  167. addAndMakeVisible (roundedRectangleButton);
  168. // Display the battery level on the LEDRow
  169. auto numLedsToTurnOn = static_cast<int> (numLeds * block->getBatteryLevel());
  170. // add LEDs
  171. for (int i = 0; i < numLeds; ++i)
  172. {
  173. auto ledComponent = new LEDComponent();
  174. ledComponent->setOnState (i < numLedsToTurnOn);
  175. addAndMakeVisible (leds.add (ledComponent));
  176. }
  177. previousNumLedsOn = numLedsToTurnOn;
  178. // add buttons
  179. for (int i = 0; i < 8; ++i)
  180. addAndMakeVisible (circleButtons[i]);
  181. }
  182. void resized() override
  183. {
  184. const auto r = getLocalBounds().reduced (10);
  185. const int rowHeight = r.getHeight() / 5;
  186. const int ledWidth = (r.getWidth() - 70) / numLeds;
  187. const int buttonWidth = (r.getWidth() - 40) / 5;
  188. auto row = r;
  189. auto ledRow = row.removeFromTop (rowHeight) .withSizeKeepingCentre (r.getWidth(), ledWidth);
  190. auto buttonRow1 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth);
  191. auto buttonRow2 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth);
  192. for (auto* led : leds)
  193. {
  194. led->setBounds (ledRow.removeFromLeft (ledWidth).reduced (2));
  195. ledRow.removeFromLeft (5);
  196. }
  197. for (int i = 0; i < 5; ++i)
  198. {
  199. circleButtons[i].setBounds (buttonRow1.removeFromLeft (buttonWidth).reduced (2));
  200. buttonRow1.removeFromLeft (10);
  201. }
  202. for (int i = 5; i < 8; ++i)
  203. {
  204. circleButtons[i].setBounds (buttonRow2.removeFromLeft (buttonWidth).reduced (2));
  205. buttonRow2.removeFromLeft (10);
  206. }
  207. roundedRectangleButton.setBounds (buttonRow2);
  208. }
  209. void paint (Graphics& g) override
  210. {
  211. auto r = getLocalBounds().toFloat();
  212. // Fill a black rectangle for the Control Block
  213. g.setColour (Colours::black);
  214. g.fillRoundedRectangle (r, r.getWidth() / 20.0f);
  215. }
  216. void handleButtonPressed (ControlButton::ButtonFunction function, uint32) override
  217. {
  218. displayButtonInteraction (controlButtonFunctionToIndex (function), true);
  219. }
  220. void handleButtonReleased (ControlButton::ButtonFunction function, uint32) override
  221. {
  222. displayButtonInteraction (controlButtonFunctionToIndex (function), false);
  223. }
  224. void handleBatteryLevelUpdate (float batteryLevel) override
  225. {
  226. // Update the number of LEDs that are on to represent the battery level
  227. int numLedsOn = static_cast<int> (numLeds * batteryLevel);
  228. if (numLedsOn != previousNumLedsOn)
  229. for (int i = 0; i < numLeds; ++i)
  230. leds.getUnchecked (i)->setOnState (i < numLedsOn);
  231. previousNumLedsOn = numLedsOn;
  232. repaint();
  233. }
  234. private:
  235. //==============================================================================
  236. /**
  237. Base class that renders a Control Block button
  238. */
  239. struct ControlBlockSubComponent : public Component,
  240. public TooltipClient
  241. {
  242. ControlBlockSubComponent (Colour componentColourToUse)
  243. : componentColour (componentColourToUse)
  244. {}
  245. /** Subclasses should override this to paint the button on the screen */
  246. virtual void paint (Graphics&) override = 0;
  247. /** Sets the colour of the button */
  248. void setColour (Colour c) { componentColour = c; }
  249. /** Sets the on state of the button */
  250. void setOnState (bool isOn)
  251. {
  252. onState = isOn;
  253. repaint();
  254. }
  255. /** Returns the Control Block tooltip */
  256. String getTooltip() override
  257. {
  258. for (Component* comp = this; comp != nullptr; comp = comp->getParentComponent())
  259. if (auto* sttc = dynamic_cast<SettableTooltipClient*> (comp))
  260. return sttc->getTooltip();
  261. return {};
  262. }
  263. //==============================================================================
  264. Colour componentColour;
  265. bool onState = false;
  266. //==============================================================================
  267. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlBlockSubComponent)
  268. };
  269. /**
  270. Class that renders a Control Block LED on the screen
  271. */
  272. struct LEDComponent : public ControlBlockSubComponent
  273. {
  274. LEDComponent() : ControlBlockSubComponent (Colours::green) {}
  275. void paint (Graphics& g) override
  276. {
  277. g.setColour (componentColour.withAlpha (onState ? 1.0f : 0.2f));
  278. g.fillEllipse (getLocalBounds().toFloat());
  279. }
  280. };
  281. /**
  282. Class that renders a Control Block single circular button on the screen
  283. */
  284. struct CircleButtonComponent : public ControlBlockSubComponent
  285. {
  286. CircleButtonComponent() : ControlBlockSubComponent (Colours::blue) {}
  287. void paint (Graphics& g) override
  288. {
  289. g.setColour (componentColour.withAlpha (onState ? 1.0f : 0.2f));
  290. g.fillEllipse (getLocalBounds().toFloat());
  291. }
  292. };
  293. /**
  294. Class that renders a Control Block rounded rectangular button containing two buttons
  295. on the screen
  296. */
  297. struct RoundedRectangleButtonComponent : public ControlBlockSubComponent
  298. {
  299. RoundedRectangleButtonComponent() : ControlBlockSubComponent (Colours::blue) {}
  300. void paint (Graphics& g) override
  301. {
  302. auto r = getLocalBounds().toFloat();
  303. g.setColour (componentColour.withAlpha (0.2f));
  304. g.fillRoundedRectangle (r.toFloat(), 20.0f);
  305. g.setColour (componentColour.withAlpha (1.0f));
  306. // is a button pressed?
  307. if (doubleButtonOnState[0] || doubleButtonOnState[1])
  308. {
  309. auto semiButtonWidth = r.getWidth() / 2.0f;
  310. auto semiButtonBounds = r.withWidth (semiButtonWidth)
  311. .withX (doubleButtonOnState[1] ? semiButtonWidth : 0)
  312. .reduced (5.0f, 2.0f);
  313. g.fillEllipse (semiButtonBounds);
  314. }
  315. }
  316. void setPressedState (bool isPressed, int button)
  317. {
  318. doubleButtonOnState[button] = isPressed;
  319. repaint();
  320. }
  321. private:
  322. bool doubleButtonOnState[2] = { false, false };
  323. };
  324. /** Displays a button press or release interaction for a button at a given index */
  325. void displayButtonInteraction (int buttonIndex, bool isPressed)
  326. {
  327. if (! isPositiveAndBelow (buttonIndex, 10))
  328. return;
  329. if (buttonIndex >= 8)
  330. roundedRectangleButton.setPressedState (isPressed, buttonIndex == 8);
  331. else
  332. circleButtons[buttonIndex].setOnState (isPressed);
  333. }
  334. //==============================================================================
  335. int numLeds;
  336. OwnedArray<LEDComponent> leds;
  337. CircleButtonComponent circleButtons[8];
  338. RoundedRectangleButtonComponent roundedRectangleButton;
  339. int previousNumLedsOn;
  340. //==============================================================================
  341. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlBlockComponent)
  342. };
  343. #endif // BLOCKCOMPONENTS_H_INCLUDED