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.

385 lines
12KB

  1. #ifndef MAINCOMPONENT_H_INCLUDED
  2. #define MAINCOMPONENT_H_INCLUDED
  3. #include "../JuceLibraryCode/JuceHeader.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.0 : 0.1);
  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 Timer
  76. {
  77. public:
  78. MainComponent()
  79. {
  80. setSize (600, 400);
  81. activeLeds.clear();
  82. // Register MainContentComponent as a listener to the PhysicalTopologySource object
  83. topologySource.addListener (this);
  84. }
  85. ~MainComponent()
  86. {
  87. if (activeBlock != nullptr)
  88. detachActiveBlock();
  89. }
  90. void paint (Graphics& g) override
  91. {
  92. g.fillAll (Colours::lightgrey);
  93. g.drawText ("Connect a Lightpad Block to draw.",
  94. getLocalBounds(), Justification::centred, false);
  95. }
  96. void resized() override {}
  97. /** Overridden from TopologySource::Listener. Called when the topology changes */
  98. void topologyChanged() override
  99. {
  100. // Reset the activeBlock object
  101. if (activeBlock != nullptr)
  102. detachActiveBlock();
  103. // Get the array of currently connected Block objects from the PhysicalTopologySource
  104. Block::Array blocks = topologySource.getCurrentTopology().blocks;
  105. // Iterate over the array of Block objects
  106. for (auto b : blocks)
  107. {
  108. // Find the first Lightpad
  109. if (b->getType() == Block::Type::lightPadBlock)
  110. {
  111. activeBlock = b;
  112. // Register MainContentComponent as a listener to the touch surface
  113. if (auto surface = activeBlock->getTouchSurface())
  114. surface->addListener (this);
  115. // Register MainContentComponent as a listener to any buttons
  116. for (auto button : activeBlock->getButtons())
  117. button->addListener (this);
  118. // Get the LEDGrid object from the Lightpad and set its program to the program for the current mode
  119. if (auto grid = activeBlock->getLEDGrid())
  120. {
  121. // Work out scale factors to translate X and Y touches to LED indexes
  122. scaleX = (float) (grid->getNumColumns() - 1) / activeBlock->getWidth();
  123. scaleY = (float) (grid->getNumRows() - 1) / activeBlock->getHeight();
  124. setLEDProgram (grid);
  125. }
  126. break;
  127. }
  128. }
  129. }
  130. private:
  131. /** Overridden from TouchSurface::Listener. Called when a Touch is received on the Lightpad */
  132. void touchChanged (TouchSurface&, const TouchSurface::Touch& touch) override
  133. {
  134. // Translate X and Y touch events to LED indexes
  135. int xLed = roundToInt (touch.x * scaleX);
  136. int yLed = roundToInt (touch.y * scaleY);
  137. if (currentMode == colourPalette)
  138. {
  139. if (layout.setActiveColourForTouch (xLed, yLed))
  140. colourPaletteProgram->setGridFills (layout.numColumns, layout.numRows, layout.gridFillArray);
  141. }
  142. else if (currentMode == canvas)
  143. {
  144. drawLEDs ((uint32) xLed, (uint32) yLed, touch.z, layout.currentColour);
  145. }
  146. }
  147. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is pressed */
  148. void buttonPressed (ControlButton&, Block::Timestamp) override {}
  149. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is released */
  150. void buttonReleased (ControlButton&, Block::Timestamp) override
  151. {
  152. if (currentMode == canvas)
  153. {
  154. // Wait 500ms to see if there is a second press
  155. if (! isTimerRunning())
  156. startTimer (500);
  157. else
  158. doublePress = true;
  159. }
  160. else if (currentMode == colourPalette)
  161. {
  162. // Switch to canvas mode and set the LEDGrid program
  163. currentMode = canvas;
  164. setLEDProgram (activeBlock->getLEDGrid());
  165. }
  166. }
  167. void timerCallback() override
  168. {
  169. if (doublePress)
  170. {
  171. // Clear the LED grid
  172. for (uint32 x = 0; x < 15; ++x)
  173. for (uint32 y = 0; y < 15; ++ y)
  174. canvasProgram->setLED (x, y, Colours::black);
  175. // Clear the ActiveLED array
  176. activeLeds.clear();
  177. // Reset the doublePress flag
  178. doublePress = false;
  179. }
  180. else
  181. {
  182. // Switch to colour palette mode and set the LEDGrid program
  183. currentMode = colourPalette;
  184. setLEDProgram (activeBlock->getLEDGrid());
  185. }
  186. stopTimer();
  187. }
  188. /** Removes TouchSurface and ControlButton listeners and sets activeBlock to nullptr */
  189. void detachActiveBlock()
  190. {
  191. if (auto surface = activeBlock->getTouchSurface())
  192. surface->removeListener (this);
  193. for (auto button : activeBlock->getButtons())
  194. button->removeListener (this);
  195. activeBlock = nullptr;
  196. }
  197. /** Sets the LEDGrid Program for the selected mode */
  198. void setLEDProgram (LEDGrid* grid)
  199. {
  200. if (currentMode == canvas)
  201. {
  202. // Create a new BitmapLEDProgram for the LEDGrid
  203. canvasProgram = new BitmapLEDProgram (*grid);
  204. // Set the LEDGrid program
  205. grid->setProgram (canvasProgram);
  206. // Redraw any previously drawn LEDs
  207. redrawLEDs();
  208. }
  209. else if (currentMode == colourPalette)
  210. {
  211. // Create a new DrumPadGridProgram for the LEDGrid
  212. colourPaletteProgram = new DrumPadGridProgram (*grid);
  213. // Set the LEDGrid program
  214. grid->setProgram (colourPaletteProgram);
  215. // Setup the grid layout
  216. colourPaletteProgram->setGridFills (layout.numColumns,
  217. layout.numRows,
  218. layout.gridFillArray);
  219. }
  220. }
  221. /** Sets an LED on the Lightpad for a given touch co-ordinate and pressure */
  222. void drawLEDs (uint32 x0, uint32 y0, float z, Colour drawColour)
  223. {
  224. // Check if the activeLeds array already contains an ActiveLED object for this LED
  225. auto index = getLEDAt (x0, y0);
  226. // If the colour is black then just set the LED to black and return
  227. if (drawColour == Colours::black)
  228. {
  229. if (index >= 0)
  230. {
  231. canvasProgram->setLED (x0, y0, Colours::black);
  232. activeLeds.remove (index);
  233. }
  234. return;
  235. }
  236. // If there is no ActiveLED obejct for this LED then create one,
  237. // add it to the array, set the LED on the Block and return
  238. if (index < 0)
  239. {
  240. ActiveLED led;
  241. led.x = x0;
  242. led.y = y0;
  243. led.colour = drawColour;
  244. led.brightness = z;
  245. activeLeds.add (led);
  246. canvasProgram->setLED (led.x, led.y, led.colour.withBrightness (led.brightness));
  247. return;
  248. }
  249. // Get the ActiveLED object for this LED
  250. ActiveLED currentLed = activeLeds.getReference (index);
  251. // If the LED colour is the same as the draw colour, add the brightnesses together.
  252. // If it is different, blend the colours
  253. if (currentLed.colour == drawColour)
  254. currentLed.brightness = jmin (currentLed.brightness + z, 1.0f);
  255. else
  256. currentLed.colour = currentLed.colour.interpolatedWith (drawColour, z);
  257. // Set the LED on the Block and change the ActiveLED object in the activeLeds array
  258. canvasProgram->setLED (currentLed.x, currentLed.y, currentLed.colour.withBrightness (currentLed.brightness));
  259. activeLeds.set (index, currentLed);
  260. }
  261. /** Redraws the LEDs on the Lightpad from the activeLeds array */
  262. void redrawLEDs()
  263. {
  264. // Iterate over the activeLeds array and set the LEDs on the Block
  265. for (auto led : activeLeds)
  266. canvasProgram->setLED (led.x, led.y, led.colour.withBrightness (led.brightness));
  267. }
  268. /**
  269. A struct that represents an active LED on the Lightpad.
  270. Has a position, colour and brightness.
  271. */
  272. struct ActiveLED
  273. {
  274. uint32 x, y;
  275. Colour colour;
  276. float brightness;
  277. /** Returns true if this LED occupies the given co-ordinates */
  278. bool occupies (uint32 xPos, uint32 yPos) const
  279. {
  280. return xPos == x && yPos == y;
  281. }
  282. };
  283. Array<ActiveLED> activeLeds;
  284. int getLEDAt (uint32 x, uint32 y) const
  285. {
  286. for (int i = 0; i < activeLeds.size(); ++i)
  287. if (activeLeds.getReference(i).occupies (x, y))
  288. return i;
  289. return -1;
  290. }
  291. //==============================================================================
  292. enum DisplayMode
  293. {
  294. colourPalette = 0,
  295. canvas
  296. };
  297. DisplayMode currentMode = colourPalette;
  298. //==============================================================================
  299. BitmapLEDProgram* canvasProgram = nullptr;
  300. DrumPadGridProgram* colourPaletteProgram = nullptr;
  301. ColourGrid layout { 3, 3 };
  302. PhysicalTopologySource topologySource;
  303. Block::Ptr activeBlock;
  304. float scaleX = 0.0;
  305. float scaleY = 0.0;
  306. bool doublePress = false;
  307. //==============================================================================
  308. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
  309. };
  310. #endif // MAINCOMPONENT_H_INCLUDED