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.

541 lines
18KB

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