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.

429 lines
16KB

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