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.

537 lines
18KB

  1. #pragma once
  2. #include "../JuceLibraryCode/JuceHeader.h"
  3. #include "LightpadComponent.h"
  4. //==============================================================================
  5. /**
  6. A struct that handles the setup and layout of the DrumPadGridProgram
  7. */
  8. struct ColourGrid
  9. {
  10. ColourGrid (int cols, int rows)
  11. : numColumns (cols),
  12. numRows (rows)
  13. {
  14. constructGridFillArray();
  15. }
  16. /** Creates a GridFill object for each pad in the grid and sets its colour
  17. and fill before adding it to an array of GridFill objects
  18. */
  19. void constructGridFillArray()
  20. {
  21. gridFillArray.clear();
  22. int counter = 0;
  23. for (int i = 0; i < numColumns; ++i)
  24. {
  25. for (int j = 0; j < numRows; ++j)
  26. {
  27. DrumPadGridProgram::GridFill fill;
  28. Colour colourToUse = colourArray.getUnchecked (counter);
  29. fill.colour = colourToUse.withBrightness (colourToUse == currentColour ? 1.0f : 0.1f);
  30. if (colourToUse == Colours::black)
  31. fill.fillType = DrumPadGridProgram::GridFill::FillType::hollow;
  32. else
  33. fill.fillType = DrumPadGridProgram::GridFill::FillType::filled;
  34. gridFillArray.add (fill);
  35. if (++counter == colourArray.size())
  36. counter = 0;
  37. }
  38. }
  39. }
  40. /** Sets which colour should be active for a given touch co-ordinate. Returns
  41. true if the colour has changed
  42. */
  43. bool setActiveColourForTouch (int x, int y)
  44. {
  45. bool colourHasChanged = false;
  46. int xindex = x / 5;
  47. int yindex = y / 5;
  48. Colour newColour = colourArray.getUnchecked ((yindex * 3) + xindex);
  49. if (currentColour != newColour)
  50. {
  51. currentColour = newColour;
  52. constructGridFillArray();
  53. colourHasChanged = true;
  54. }
  55. return colourHasChanged;
  56. }
  57. //==============================================================================
  58. int numColumns, numRows;
  59. Array<DrumPadGridProgram::GridFill> gridFillArray;
  60. Array<Colour> colourArray = { Colours::white, Colours::red, Colours::green,
  61. Colours::blue, Colours::hotpink, Colours::orange,
  62. Colours::magenta, Colours::cyan, Colours::black };
  63. Colour currentColour = Colours::hotpink;
  64. //==============================================================================
  65. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ColourGrid)
  66. };
  67. //==============================================================================
  68. /**
  69. The main component
  70. */
  71. class MainComponent : public Component,
  72. public TopologySource::Listener,
  73. private TouchSurface::Listener,
  74. private ControlButton::Listener,
  75. private LightpadComponent::Listener,
  76. private Button::Listener,
  77. private Slider::Listener,
  78. private Timer
  79. {
  80. public:
  81. MainComponent()
  82. {
  83. activeLeds.clear();
  84. // Register MainContentComponent as a listener to the PhysicalTopologySource object
  85. topologySource.addListener (this);
  86. infoLabel.setText ("Connect a Lightpad Block to draw.", dontSendNotification);
  87. infoLabel.setJustificationType (Justification::centred);
  88. addAndMakeVisible (infoLabel);
  89. addAndMakeVisible (lightpadComponent);
  90. lightpadComponent.setVisible (false);
  91. lightpadComponent.addListener (this);
  92. clearButton.setButtonText ("Clear");
  93. clearButton.addListener (this);
  94. clearButton.setAlwaysOnTop (true);
  95. addAndMakeVisible (clearButton);
  96. brightnessSlider.setRange (0.0, 1.0);
  97. brightnessSlider.setValue (1.0);
  98. brightnessSlider.setAlwaysOnTop (true);
  99. brightnessSlider.setTextBoxStyle (Slider::TextEntryBoxPosition::NoTextBox, false, 0, 0);
  100. brightnessSlider.addListener (this);
  101. addAndMakeVisible (brightnessSlider);
  102. brightnessLED.setAlwaysOnTop (true);
  103. brightnessLED.setColour (layout.currentColour.withBrightness (static_cast<float> (brightnessSlider.getValue())));
  104. addAndMakeVisible (brightnessLED);
  105. #if JUCE_IOS
  106. connectButton.setButtonText ("Connect");
  107. connectButton.addListener (this);
  108. connectButton.setAlwaysOnTop (true);
  109. addAndMakeVisible (connectButton);
  110. #endif
  111. setSize (600, 600);
  112. }
  113. ~MainComponent()
  114. {
  115. if (activeBlock != nullptr)
  116. detachActiveBlock();
  117. lightpadComponent.removeListener (this);
  118. }
  119. void paint (Graphics& g) override
  120. {
  121. g.fillAll (Colours::lightgrey);
  122. }
  123. void resized() override
  124. {
  125. infoLabel.centreWithSize (getWidth(), 100);
  126. Rectangle<int> bounds = getLocalBounds().reduced (20);
  127. // top buttons
  128. Rectangle<int> topButtonArea = bounds.removeFromTop (getHeight() / 20);
  129. topButtonArea.removeFromLeft (20);
  130. clearButton.setBounds (topButtonArea.removeFromLeft (80));
  131. #if JUCE_IOS
  132. topButtonArea.removeFromRight (20);
  133. connectButton.setBounds (topButtonArea.removeFromRight (80));
  134. #endif
  135. bounds.removeFromTop (20);
  136. // brightness controls
  137. Rectangle<int> brightnessControlBounds;
  138. Desktop::DisplayOrientation orientation = Desktop::getInstance().getCurrentOrientation();
  139. if (orientation == Desktop::DisplayOrientation::upright || orientation == Desktop::DisplayOrientation::upsideDown)
  140. {
  141. brightnessControlBounds = bounds.removeFromBottom (getHeight() / 10);
  142. brightnessSlider.setSliderStyle (Slider::SliderStyle::LinearHorizontal);
  143. brightnessLED.setBounds (brightnessControlBounds.removeFromLeft (getHeight() / 10));
  144. brightnessSlider.setBounds (brightnessControlBounds);
  145. }
  146. else
  147. {
  148. brightnessControlBounds = bounds.removeFromRight (getWidth() / 10);
  149. brightnessSlider.setSliderStyle (Slider::SliderStyle::LinearVertical);
  150. brightnessLED.setBounds (brightnessControlBounds.removeFromTop (getWidth() / 10));
  151. brightnessSlider.setBounds (brightnessControlBounds);
  152. }
  153. // lightpad component
  154. int sideLength = jmin (bounds.getWidth() - 40, bounds.getHeight() - 40);
  155. lightpadComponent.centreWithSize (sideLength, sideLength);
  156. }
  157. /** Overridden from TopologySource::Listener. Called when the topology changes */
  158. void topologyChanged() override
  159. {
  160. lightpadComponent.setVisible (false);
  161. infoLabel.setVisible (true);
  162. // Reset the activeBlock object
  163. if (activeBlock != nullptr)
  164. detachActiveBlock();
  165. // Get the array of currently connected Block objects from the PhysicalTopologySource
  166. Block::Array blocks = topologySource.getCurrentTopology().blocks;
  167. // Iterate over the array of Block objects
  168. for (auto b : blocks)
  169. {
  170. // Find the first Lightpad
  171. if (b->getType() == Block::Type::lightPadBlock)
  172. {
  173. activeBlock = b;
  174. // Register MainContentComponent as a listener to the touch surface
  175. if (auto surface = activeBlock->getTouchSurface())
  176. surface->addListener (this);
  177. // Register MainContentComponent as a listener to any buttons
  178. for (auto button : activeBlock->getButtons())
  179. button->addListener (this);
  180. // Get the LEDGrid object from the Lightpad and set its program to the program for the current mode
  181. if (auto grid = activeBlock->getLEDGrid())
  182. {
  183. // Work out scale factors to translate X and Y touches to LED indexes
  184. scaleX = (float) (grid->getNumColumns()) / activeBlock->getWidth();
  185. scaleY = (float) (grid->getNumRows()) / activeBlock->getHeight();
  186. setLEDProgram (grid);
  187. }
  188. // Make the on screen Lighpad component visible
  189. lightpadComponent.setVisible (true);
  190. infoLabel.setVisible (false);
  191. break;
  192. }
  193. }
  194. }
  195. private:
  196. /** Overridden from TouchSurface::Listener. Called when a Touch is received on the Lightpad */
  197. void touchChanged (TouchSurface&, const TouchSurface::Touch& touch) override
  198. {
  199. // Translate X and Y touch events to LED indexes
  200. int xLed = roundToInt (touch.x * scaleX);
  201. int yLed = roundToInt (touch.y * scaleY);
  202. if (currentMode == colourPalette)
  203. {
  204. if (layout.setActiveColourForTouch (xLed, yLed))
  205. {
  206. colourPaletteProgram->setGridFills (layout.numColumns, layout.numRows, layout.gridFillArray);
  207. brightnessLED.setColour (layout.currentColour.withBrightness (layout.currentColour == Colours::black ? 0.0f
  208. : static_cast<float> (brightnessSlider.getValue())));
  209. }
  210. }
  211. else if (currentMode == canvas)
  212. {
  213. drawLED ((uint32) xLed, (uint32) yLed, touch.z, layout.currentColour);
  214. }
  215. }
  216. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is pressed */
  217. void buttonPressed (ControlButton&, Block::Timestamp) override { }
  218. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is released */
  219. void buttonReleased (ControlButton&, Block::Timestamp) override
  220. {
  221. if (currentMode == canvas)
  222. {
  223. // Wait 500ms to see if there is a second press
  224. if (! isTimerRunning())
  225. startTimer (500);
  226. else
  227. doublePress = true;
  228. }
  229. else if (currentMode == colourPalette)
  230. {
  231. // Switch to canvas mode and set the LEDGrid program
  232. currentMode = canvas;
  233. setLEDProgram (activeBlock->getLEDGrid());
  234. }
  235. }
  236. void buttonClicked (Button* b) override
  237. {
  238. #if JUCE_IOS
  239. if (b == &connectButton)
  240. {
  241. BluetoothMidiDevicePairingDialogue::open();
  242. return;
  243. }
  244. #else
  245. ignoreUnused (b);
  246. #endif
  247. clearLEDs();
  248. }
  249. void sliderValueChanged (Slider* s) override
  250. {
  251. if (s == &brightnessSlider)
  252. brightnessLED.setColour (layout.currentColour.withBrightness (layout.currentColour == Colours::black ? 0.0f
  253. : static_cast<float> (brightnessSlider.getValue())));
  254. }
  255. void timerCallback() override
  256. {
  257. if (doublePress)
  258. {
  259. clearLEDs();
  260. // Reset the doublePress flag
  261. doublePress = false;
  262. }
  263. else
  264. {
  265. // Switch to colour palette mode and set the LEDGrid program
  266. currentMode = colourPalette;
  267. setLEDProgram (activeBlock->getLEDGrid());
  268. }
  269. stopTimer();
  270. }
  271. void ledClicked (int x, int y, float z) override
  272. {
  273. drawLED ((uint32) x, (uint32) y, z == 0.0f ? static_cast<float> (brightnessSlider.getValue())
  274. : z * static_cast<float> (brightnessSlider.getValue()), layout.currentColour);
  275. }
  276. /** Removes TouchSurface and ControlButton listeners and sets activeBlock to nullptr */
  277. void detachActiveBlock()
  278. {
  279. if (auto surface = activeBlock->getTouchSurface())
  280. surface->removeListener (this);
  281. for (auto button : activeBlock->getButtons())
  282. button->removeListener (this);
  283. activeBlock = nullptr;
  284. }
  285. /** Sets the LEDGrid Program for the selected mode */
  286. void setLEDProgram (LEDGrid* grid)
  287. {
  288. canvasProgram = nullptr;
  289. colourPaletteProgram = nullptr;
  290. if (currentMode == canvas)
  291. {
  292. // Create a new BitmapLEDProgram for the LEDGrid
  293. canvasProgram = new BitmapLEDProgram (*grid);
  294. // Set the LEDGrid program
  295. grid->setProgram (canvasProgram);
  296. // Redraw any previously drawn LEDs
  297. redrawLEDs();
  298. }
  299. else if (currentMode == colourPalette)
  300. {
  301. // Create a new DrumPadGridProgram for the LEDGrid
  302. colourPaletteProgram = new DrumPadGridProgram (*grid);
  303. // Set the LEDGrid program
  304. grid->setProgram (colourPaletteProgram);
  305. // Setup the grid layout
  306. colourPaletteProgram->setGridFills (layout.numColumns,
  307. layout.numRows,
  308. layout.gridFillArray);
  309. }
  310. }
  311. void clearLEDs()
  312. {
  313. // Clear the LED grid
  314. for (uint32 x = 0; x < 15; ++x)
  315. {
  316. for (uint32 y = 0; y < 15; ++ y)
  317. {
  318. if (canvasProgram != nullptr)
  319. canvasProgram->setLED (x, y, Colours::black);
  320. lightpadComponent.setLEDColour (x, y, Colours::black);
  321. }
  322. }
  323. // Clear the ActiveLED array
  324. activeLeds.clear();
  325. }
  326. /** Sets an LED on the Lightpad for a given touch co-ordinate and pressure */
  327. void drawLED (uint32 x0, uint32 y0, float z, Colour drawColour)
  328. {
  329. // Check if the activeLeds array already contains an ActiveLED object for this LED
  330. auto index = getLEDAt (x0, y0);
  331. // If the colour is black then just set the LED to black and return
  332. if (drawColour == Colours::black)
  333. {
  334. if (index >= 0)
  335. {
  336. if (canvasProgram != nullptr)
  337. canvasProgram->setLED (x0, y0, Colours::black);
  338. lightpadComponent.setLEDColour (x0, y0, Colours::black);
  339. activeLeds.remove (index);
  340. }
  341. return;
  342. }
  343. // If there is no ActiveLED obejct for this LED then create one,
  344. // add it to the array, set the LED on the Block and return
  345. if (index < 0)
  346. {
  347. ActiveLED led;
  348. led.x = x0;
  349. led.y = y0;
  350. led.colour = drawColour;
  351. led.brightness = z;
  352. activeLeds.add (led);
  353. if (canvasProgram != nullptr)
  354. canvasProgram->setLED (led.x, led.y, led.colour.withBrightness (led.brightness));
  355. lightpadComponent.setLEDColour (led.x, led.y, led.colour.withBrightness (led.brightness));
  356. return;
  357. }
  358. // Get the ActiveLED object for this LED
  359. ActiveLED currentLed = activeLeds.getReference (index);
  360. // If the LED colour is the same as the draw colour, add the brightnesses together.
  361. // If it is different, blend the colours
  362. if (currentLed.colour == drawColour)
  363. currentLed.brightness = jmin (currentLed.brightness + z, 1.0f);
  364. else
  365. currentLed.colour = currentLed.colour.interpolatedWith (drawColour, z);
  366. // Set the LED on the Block and change the ActiveLED object in the activeLeds array
  367. if (canvasProgram != nullptr)
  368. canvasProgram->setLED (currentLed.x, currentLed.y, currentLed.colour.withBrightness (currentLed.brightness));
  369. lightpadComponent.setLEDColour (currentLed.x, currentLed.y, currentLed.colour.withBrightness (currentLed.brightness));
  370. activeLeds.set (index, currentLed);
  371. }
  372. /** Redraws the LEDs on the Lightpad from the activeLeds array */
  373. void redrawLEDs()
  374. {
  375. // Iterate over the activeLeds array and set the LEDs on the Block
  376. for (auto led : activeLeds)
  377. {
  378. canvasProgram->setLED (led.x, led.y, led.colour.withBrightness (led.brightness));
  379. lightpadComponent.setLEDColour (led.x, led.y, led.colour.withBrightness (led.brightness));
  380. }
  381. }
  382. /**
  383. A struct that represents an active LED on the Lightpad.
  384. Has a position, colour and brightness.
  385. */
  386. struct ActiveLED
  387. {
  388. uint32 x, y;
  389. Colour colour;
  390. float brightness;
  391. /** Returns true if this LED occupies the given co-ordinates */
  392. bool occupies (uint32 xPos, uint32 yPos) const
  393. {
  394. return xPos == x && yPos == y;
  395. }
  396. };
  397. Array<ActiveLED> activeLeds;
  398. int getLEDAt (uint32 x, uint32 y) const
  399. {
  400. for (int i = 0; i < activeLeds.size(); ++i)
  401. if (activeLeds.getReference(i).occupies (x, y))
  402. return i;
  403. return -1;
  404. }
  405. //==============================================================================
  406. enum DisplayMode
  407. {
  408. colourPalette = 0,
  409. canvas
  410. };
  411. DisplayMode currentMode = colourPalette;
  412. //==============================================================================
  413. BitmapLEDProgram* canvasProgram = nullptr;
  414. DrumPadGridProgram* colourPaletteProgram = nullptr;
  415. ColourGrid layout { 3, 3 };
  416. PhysicalTopologySource topologySource;
  417. Block::Ptr activeBlock;
  418. float scaleX = 0.0;
  419. float scaleY = 0.0;
  420. bool doublePress = false;
  421. //==============================================================================
  422. Label infoLabel;
  423. LightpadComponent lightpadComponent;
  424. TextButton clearButton;
  425. LEDComponent brightnessLED;
  426. Slider brightnessSlider;
  427. #if JUCE_IOS
  428. TextButton connectButton;
  429. #endif
  430. //==============================================================================
  431. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
  432. };