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.

593 lines
16KB

  1. #ifndef INCLUDE_COMPONENTS_HPP
  2. #define INCLUDE_COMPONENTS_HPP
  3. #include "rack.hpp"
  4. #include <map>
  5. #include <vector>
  6. #include <string>
  7. #include <tuple>
  8. #include <functional>
  9. #include <locale>
  10. #include <thread>
  11. #include "GraduatedFader.hpp"
  12. #include "BufferedDrawFunction.hpp"
  13. using namespace rack;
  14. template <typename T, int px = 4>
  15. struct SevenSegmentLight : T {
  16. int lx, ly, ppl;
  17. std::vector< Rect > unscaledLoc;
  18. int elementsByNum[ 10 ][ 7 ] = {
  19. { 1, 1, 1, 1, 1, 1, 0 },
  20. { 0, 1, 1, 0, 0, 0, 0 },
  21. { 1, 1, 0, 1, 1, 0, 1 },
  22. { 1, 1, 1, 1, 0, 0, 1 },
  23. { 0, 1, 1, 0, 0, 1, 1 },
  24. { 1, 0, 1, 1, 0, 1, 1 },
  25. { 1, 0, 1, 1, 1, 1, 1 },
  26. { 1, 1, 1, 0, 0, 0, 0 },
  27. { 1, 1, 1, 1, 1, 1, 1 },
  28. { 1, 1, 1, 1, 0, 1, 1 }
  29. };
  30. const static int sx = px * 6 + 2;
  31. const static int sy = px * 10 + 2; // match with lx-1 and ly-1 below
  32. int pvalue;
  33. int decimalPos;
  34. BufferedDrawFunctionWidget<SevenSegmentLight<T, px>> *buffer;
  35. SevenSegmentLight( )
  36. {
  37. lx = 7;
  38. ly = 11;
  39. ppl = px;
  40. pvalue = 0;
  41. this->box.size = Vec( sx, sy );
  42. decimalPos = 1;
  43. // https://en.wikipedia.org/wiki/Seven-segment_display#/media/File:7_segment_display_labeled.svg
  44. unscaledLoc.push_back( Rect( Vec( 2, 1 ), Vec( 3, 1 ) ) );
  45. unscaledLoc.push_back( Rect( Vec( 5, 2 ), Vec( 1, 3 ) ) );
  46. unscaledLoc.push_back( Rect( Vec( 5, 6 ), Vec( 1, 3 ) ) );
  47. unscaledLoc.push_back( Rect( Vec( 2, 9 ), Vec( 3, 1 ) ) );
  48. unscaledLoc.push_back( Rect( Vec( 1, 6 ), Vec( 1, 3 ) ) );
  49. unscaledLoc.push_back( Rect( Vec( 1, 2 ), Vec( 1, 3 ) ) );
  50. unscaledLoc.push_back( Rect( Vec( 2, 5 ), Vec( 3, 1 ) ) );
  51. buffer = new BufferedDrawFunctionWidget<SevenSegmentLight<T,px>>( Vec( 0, 0 ), this->box.size,
  52. this,
  53. &SevenSegmentLight<T,px>::drawSegments );
  54. this->addChild( buffer );
  55. }
  56. void draw( NVGcontext *vg ) override
  57. {
  58. float fvalue = this->module->lights[ this->firstLightId ].value;
  59. int value = int( fvalue / decimalPos ) % 10;
  60. if( value != pvalue )
  61. {
  62. buffer->dirty = true;
  63. }
  64. pvalue = value;
  65. buffer->draw( vg );
  66. }
  67. void drawSegments( NVGcontext *vg )
  68. {
  69. // This is now buffered to only be called when the value has changed
  70. int w = this->box.size.x;
  71. int h = this->box.size.y;
  72. nvgBeginPath( vg );
  73. nvgRect( vg, 0, 0, w, h );
  74. nvgFillColor( vg, nvgRGBA( 25, 35, 25, 255 ) );
  75. nvgFill( vg );
  76. int i=0;
  77. // float fvalue = this->module->lights[ this->firstLightId ].value;
  78. // int value = clamp( fvalue, 0.0f, 9.0f );
  79. int *ebn = elementsByNum[ pvalue ];
  80. NVGcolor oncol = this->baseColors[ 0 ];
  81. for( auto it = unscaledLoc.begin(); it < unscaledLoc.end(); ++it )
  82. {
  83. float y = it->pos.y - 0.5;
  84. float x = it->pos.x - 0.5;
  85. int ew = it->size.x;
  86. int eh = it->size.y;
  87. nvgBeginPath( vg );
  88. // New version with corners
  89. float x0 = x * ppl + 1;
  90. float y0 = y * ppl + 1;
  91. float w = ew * ppl;
  92. float h = eh * ppl;
  93. float tri = ppl / 2;
  94. if( eh == 1 )
  95. {
  96. // This is a sideways element
  97. nvgMoveTo( vg, x0, y0 );
  98. nvgLineTo( vg, x0 + w, y0 );
  99. nvgLineTo( vg, x0 + w + tri, y0 + tri );
  100. nvgLineTo( vg, x0 + w, y0 + h);
  101. nvgLineTo( vg, x0, y0 + h);
  102. nvgLineTo( vg, x0 - tri, y0 + tri );
  103. nvgClosePath( vg );
  104. }
  105. else
  106. {
  107. nvgMoveTo( vg, x0, y0 );
  108. nvgLineTo( vg, x0, y0 + h );
  109. nvgLineTo( vg, x0 + tri, y0 + h + tri );
  110. nvgLineTo( vg, x0 + w, y0 + h);
  111. nvgLineTo( vg, x0 + w, y0);
  112. nvgLineTo( vg, x0 + tri, y0 - tri );
  113. }
  114. // Old version nvgRect( vg, x * ppl + 1, y * ppl + 1, ew * ppl, eh * ppl );
  115. if( ebn[ i ] > 0 )
  116. {
  117. nvgFillColor( vg, oncol );
  118. nvgFill( vg );
  119. }
  120. else
  121. {
  122. nvgFillColor( vg, nvgRGBA( 50, 70, 50, 255 ) );
  123. nvgFill( vg );
  124. }
  125. ++i;
  126. }
  127. }
  128. static SevenSegmentLight< T, px > *create(Vec pos, Module *module, int firstLightId, int decimal) {
  129. auto *o = ModuleLightWidget::create<SevenSegmentLight<T,px>>(pos, module, firstLightId);
  130. o->decimalPos = decimal;
  131. return o;
  132. }
  133. };
  134. template <typename colorClass, int px, int digits>
  135. struct MultiDigitSevenSegmentLight : ModuleLightWidget
  136. {
  137. typedef SevenSegmentLight< colorClass, px > LtClass;
  138. MultiDigitSevenSegmentLight() : ModuleLightWidget()
  139. {
  140. this->box.size = Vec( digits * LtClass::sx, LtClass::sy );
  141. }
  142. static MultiDigitSevenSegmentLight< colorClass, px, digits > *create(Vec pos, Module *module, int firstLightId) {
  143. auto *o = ModuleLightWidget::create<MultiDigitSevenSegmentLight<colorClass, px ,digits>>(pos, module, firstLightId);
  144. o->layout();
  145. return o;
  146. }
  147. void layout()
  148. {
  149. int dp = 1;
  150. for( int i=0; i<digits-1; ++i ) dp *= 10;
  151. for( int i=0; i<digits; ++i )
  152. {
  153. addChild( LtClass::create( Vec( i * LtClass::sx, 0 ), module, firstLightId, dp ) );
  154. dp /= 10;
  155. }
  156. }
  157. void draw( NVGcontext *vg ) override
  158. {
  159. for( auto it = children.begin(); it != children.end(); ++it )
  160. {
  161. nvgSave( vg );
  162. nvgTranslate( vg, (*it)->box.pos.x, (*it)->box.pos.y );
  163. (*it)->draw( vg );
  164. nvgRestore( vg );
  165. }
  166. }
  167. };
  168. struct BaconBackground : virtual TransparentWidget
  169. {
  170. static NVGcolor bg;
  171. static NVGcolor bgOutline;
  172. static NVGcolor highlight;
  173. typedef std::tuple< Rect, NVGcolor, bool > col_rect_t;
  174. std::vector< col_rect_t > rects;
  175. int memFont = -1;
  176. std::string title;
  177. enum LabelAt {
  178. ABOVE,
  179. BELOW,
  180. LEFT,
  181. RIGHT
  182. };
  183. enum LabelStyle {
  184. SIG_IN,
  185. SIG_OUT,
  186. OTHER
  187. };
  188. int cx() { return box.size.x / 2; }
  189. int cx( int w ) { return (box.size.x-w) / 2; }
  190. BaconBackground( Vec size, const char* lab );
  191. ~BaconBackground() { }
  192. BaconBackground *addLabel( Vec pos, const char* lab, int px )
  193. {
  194. return addLabel( pos, lab, px, NVG_ALIGN_CENTER | NVG_ALIGN_BOTTOM, COLOR_BLACK );
  195. }
  196. BaconBackground *addLabel( Vec pos, const char* lab, int px, int align )
  197. {
  198. return addLabel( pos, lab, px, align, COLOR_BLACK );
  199. }
  200. BaconBackground *addLabel( Vec pos, const char* lab, int px, int align, NVGcolor col );
  201. BaconBackground *addPlugLabel( Vec plugPos, LabelStyle s, const char* ilabel ) {
  202. return addPlugLabel( plugPos, LabelAt::ABOVE, s, ilabel );
  203. }
  204. BaconBackground *addPlugLabel( Vec plugPos, LabelAt l, LabelStyle s, const char* ilabel );
  205. BaconBackground *addRoundedBorder( Vec pos, Vec sz );
  206. BaconBackground *addRoundedBorder( Vec pos, Vec sz, NVGcolor fill );
  207. BaconBackground *addLabelsForHugeKnob( Vec topLabelPos, const char* knobLabel, const char* zeroLabel, const char* oneLabel, Vec &putKnobHere );
  208. BaconBackground *addLabelsForLargeKnob( Vec topLabelPos, const char* knobLabel, const char* zeroLabel, const char* oneLabel, Vec &putKnobHere );
  209. BaconBackground *addFilledRect( Vec pos, Vec sz, NVGcolor fill )
  210. {
  211. Rect r;
  212. r.pos = pos; r.size = sz;
  213. rects.push_back( col_rect_t( r, fill, true ) );
  214. return this;
  215. }
  216. BaconBackground *addRect( Vec pos, Vec sz, NVGcolor fill )
  217. {
  218. Rect r;
  219. r.pos = pos; r.size = sz;
  220. rects.push_back( col_rect_t( r, fill, false ) );
  221. return this;
  222. }
  223. void draw( NVGcontext *vg ) override;
  224. FramebufferWidget *wrappedInFramebuffer();
  225. };
  226. struct BaconHelpButton : public SVGButton
  227. {
  228. std::string url;
  229. BaconHelpButton( std::string urli ) : url( urli )
  230. {
  231. box.pos = Vec( 0, 0 );
  232. box.size = Vec( 20, 20 );
  233. setSVGs( SVG::load( assetPlugin( plugin, "res/HelpActiveSmall.svg" ) ), NULL );
  234. url = "https://github.com/baconpaul/BaconPlugs/blob/";
  235. #ifdef RELEASE_BRANCH
  236. url += TO_STRING( RELEASE_BRANCH );
  237. #else
  238. url += "master/";
  239. #endif
  240. url += urli;
  241. info( "Help button configured to: %s", url.c_str() );
  242. }
  243. void onAction( EventAction &e ) override {
  244. std::thread t( [this]() {
  245. systemOpenBrowser(url.c_str() );
  246. } );
  247. t.detach();
  248. }
  249. };
  250. template< int NSteps, typename ColorModel >
  251. struct NStepDraggableLEDWidget : public ParamWidget
  252. {
  253. BufferedDrawFunctionWidget<NStepDraggableLEDWidget<NSteps, ColorModel>> *buffer;
  254. bool dragging;
  255. Vec lastDragPos;
  256. ColorModel cm;
  257. NStepDraggableLEDWidget()
  258. {
  259. box.size = Vec( 10, 200 );
  260. dragging = false;
  261. lastDragPos = Vec( -1, -1 );
  262. buffer = new BufferedDrawFunctionWidget<NStepDraggableLEDWidget<NSteps, ColorModel>>( Vec( 0, 0 ), this->box.size,
  263. this,
  264. &NStepDraggableLEDWidget<NSteps, ColorModel>::drawSegments );
  265. }
  266. int getStep()
  267. {
  268. float pvalue = this->module->params[ this->paramId ].value;
  269. int step = (int)pvalue;
  270. return step;
  271. }
  272. int impStep( float yp )
  273. {
  274. float py = (box.size.y - yp)/box.size.y;
  275. return (int)( py * NSteps );
  276. }
  277. void draw( NVGcontext *vg ) override
  278. {
  279. buffer->draw( vg );
  280. }
  281. void valueByMouse( float ey )
  282. {
  283. if( impStep( ey ) != getStep() )
  284. {
  285. buffer->dirty = true;
  286. setValue( impStep( ey ) );
  287. }
  288. }
  289. void onMouseDown( EventMouseDown &e ) override
  290. {
  291. ParamWidget::onMouseDown( e );
  292. valueByMouse( e.pos.y );
  293. dragging = true;
  294. }
  295. void onMouseUp( EventMouseUp &e ) override
  296. {
  297. ParamWidget::onMouseUp( e );
  298. valueByMouse( e.pos.y );
  299. dragging = false;
  300. lastDragPos = Vec( -1, -1 );
  301. }
  302. void onMouseMove( EventMouseMove &e ) override
  303. {
  304. ParamWidget::onMouseMove( e );
  305. if( dragging && ( e.pos.x != lastDragPos.x || e.pos.y != lastDragPos.y ))
  306. {
  307. valueByMouse( e.pos.y );
  308. lastDragPos = e.pos;
  309. }
  310. }
  311. void onMouseLeave( EventMouseLeave &e ) override
  312. {
  313. ParamWidget::onMouseLeave( e );
  314. dragging = false;
  315. lastDragPos = Vec( -1, -1 );
  316. }
  317. void drawSegments( NVGcontext *vg )
  318. {
  319. // This is now buffered to only be called when the value has changed
  320. int w = this->box.size.x;
  321. int h = this->box.size.y;
  322. nvgBeginPath( vg );
  323. nvgRect( vg, 0, 0, w, h );
  324. nvgFillColor( vg, nvgRGB( 40, 40, 40 ) );
  325. nvgFill( vg );
  326. float dy = box.size.y / NSteps;
  327. for( int i=0; i<NSteps; ++i )
  328. {
  329. nvgBeginPath( vg );
  330. nvgRect( vg, 1, i * dy + 1, w - 2, dy - 2 );
  331. nvgFillColor( vg, cm.elementColor( NSteps - 1 - i , NSteps, getStep() ) );
  332. nvgFill( vg );
  333. }
  334. }
  335. };
  336. struct GreenFromZeroColorModel
  337. {
  338. NVGcolor GREEN, BLACK;
  339. GreenFromZeroColorModel() : GREEN( nvgRGB( 10, 255, 10 ) ), BLACK( nvgRGB( 10, 10, 10 ) ) { }
  340. NVGcolor elementColor( int stepNo, int NSteps, int value )
  341. {
  342. if( stepNo <= value )
  343. return nvgRGB( 10, 155 + 1.0f * stepNo / NSteps * 100, 10 );
  344. else
  345. return BLACK;
  346. }
  347. };
  348. struct RedGreenFromMiddleColorModel
  349. {
  350. NVGcolor GREEN, BLACK, RED;
  351. RedGreenFromMiddleColorModel() : GREEN( nvgRGB( 10, 255, 10 ) ), BLACK( nvgRGB( 10, 10, 10 ) ), RED( nvgRGB( 255, 10, 10 ) ) { }
  352. NVGcolor elementColor( int stepNo, int NSteps, int value )
  353. {
  354. // This has the 'midpoint' be 0 so we want to compare with NSteps/2
  355. if( value < NSteps / 2 ) {
  356. // We are in the bottom half.
  357. if( stepNo < value || stepNo >= NSteps / 2) return BLACK;
  358. else
  359. {
  360. int distance = NSteps / 2 - stepNo;
  361. return nvgRGB( 155 + 1.0f * distance / ( NSteps / 2 ) * 100 , 10, 10 );
  362. }
  363. } else {
  364. if( stepNo > value || stepNo < NSteps / 2) return BLACK;
  365. else
  366. {
  367. int distance = stepNo - NSteps / 2;
  368. return nvgRGB( 10, 155 + 1.0f * distance / ( NSteps / 2 ) * 100 , 10 );
  369. }
  370. }
  371. }
  372. };
  373. #include <iostream>
  374. // Think hard about dirty state management ... later
  375. // Pixel Sizing
  376. // Share fontdata
  377. struct DotMatrixLightTextWidget : public Component // Thanks http://scruss.com/blog/tag/font/
  378. {
  379. typedef std::function< std::string( Module * )> stringGetter;
  380. typedef std::function< bool( Module * )> stringDirtyGetter;
  381. BufferedDrawFunctionWidget<DotMatrixLightTextWidget> *buffer;
  382. int charCount;
  383. std::string currentText;
  384. typedef std::map< char, std::vector< bool > > fontData_t;
  385. fontData_t fontData;
  386. float ledSize, padSize;
  387. DotMatrixLightTextWidget() : Component(), buffer( NULL ), currentText( "" )
  388. {
  389. }
  390. void setup()
  391. {
  392. ledSize = 2;
  393. padSize = 1;
  394. box.size = Vec( charCount * ( 5 * ledSize + padSize ) + 2 * padSize, 7 * ledSize + 4.5 * padSize ); // 5 x 7 data structure
  395. buffer = new BufferedDrawFunctionWidget< DotMatrixLightTextWidget >( Vec( 0, 0 ), this->box.size, this,
  396. &DotMatrixLightTextWidget::drawText );
  397. info( "BaconMusic loading DMP json: %s", assetPlugin( plugin, "res/Keypunch029.json" ).c_str() );
  398. json_t *json;
  399. json_error_t error;
  400. json = json_load_file(assetPlugin( plugin, "res/Keypunch029.json" ).c_str(), 0, &error);
  401. if(!json) {
  402. info( "JSON FILE not loaded\n" );
  403. }
  404. const char* key;
  405. json_t *value;
  406. json_object_foreach( json, key, value ) {
  407. fontData_t::mapped_type valmap;
  408. size_t index;
  409. json_t *aval;
  410. json_array_foreach( value, index, aval ) {
  411. std::string s( json_string_value( aval ) );
  412. for( const char* c = s.c_str(); *c != 0; ++c ) {
  413. valmap.push_back( *c == '#' );
  414. }
  415. }
  416. fontData[ key[ 0 ] ] = valmap;
  417. }
  418. }
  419. // create takes a function
  420. static DotMatrixLightTextWidget *create( Vec pos, Module *module, int charCount, stringDirtyGetter dgf, stringGetter gf )
  421. {
  422. DotMatrixLightTextWidget *r = Component::create<DotMatrixLightTextWidget>( pos, module );
  423. r->getfn = gf;
  424. r->dirtyfn = dgf;
  425. r->charCount = charCount;
  426. r->setup();
  427. return r;
  428. }
  429. stringDirtyGetter dirtyfn;
  430. stringGetter getfn;
  431. void draw( NVGcontext *vg ) override
  432. {
  433. if( dirtyfn( this->module ) )
  434. {
  435. currentText = getfn( this->module );
  436. buffer->dirty = true;
  437. }
  438. if( buffer )
  439. buffer->draw( vg );
  440. }
  441. void drawChar( NVGcontext *vg, Vec pos, char c )
  442. {
  443. #define UPPERCASE(a) ((char)( ((a)>='a'&&(a)<='z') ? ((a)&~32) : (a) ))
  444. // fontData_t::iterator k = fontData.find( (cstd::toupper( c ) );
  445. fontData_t::iterator k = fontData.find( UPPERCASE(c) );
  446. if( k != fontData.end() ) {
  447. fontData_t::mapped_type blist = k->second;
  448. int row=0, col=0;
  449. for( auto v = blist.begin(); v != blist.end(); ++v )
  450. {
  451. if( *v )
  452. {
  453. float xo = (col+0.5) * ledSize + pos.x;
  454. float yo = (row+0.5) * ledSize + pos.y;
  455. nvgBeginPath( vg );
  456. // nvgRect( vg, xo, yo, ledSize, ledSize );
  457. nvgCircle( vg, xo + ledSize/2.0f, yo + ledSize/2.0f, ledSize/2.0f * 1.1 );
  458. nvgFillColor( vg, nvgRGBA( 25, 35, 25, 255 ) );
  459. nvgFill( vg );
  460. nvgBeginPath( vg );
  461. // nvgRect( vg, xo, yo, ledSize, ledSize );
  462. nvgCircle( vg, xo + ledSize/2.0f, yo + ledSize/2.0f, ledSize/2.0f );
  463. nvgFillColor( vg, COLOR_BLUE ); // Thanks for having such a nice blue, Rack!!
  464. nvgFill( vg );
  465. }
  466. col++;
  467. if( col == 5 ) {
  468. col = 0;
  469. row ++;
  470. }
  471. }
  472. }
  473. else {
  474. }
  475. }
  476. void drawText( NVGcontext *vg )
  477. {
  478. nvgBeginPath( vg );
  479. nvgRect( vg, 0, 0, box.size.x, box.size.y );
  480. nvgFillColor( vg, nvgRGBA( 15, 15, 55, 255 ) );
  481. nvgFill( vg );
  482. Vec cpos = Vec( padSize, padSize );
  483. for( const char* c = currentText.c_str(); *c != 0; ++c ) {
  484. drawChar( vg, cpos, *c );
  485. cpos.x += ledSize * 5 + padSize;
  486. }
  487. }
  488. void onZoom( EventZoom &e ) override
  489. {
  490. buffer->dirty = true;
  491. }
  492. };
  493. #include "SizeTable.hpp"
  494. #endif