Audio plugin host https://kx.studio/carla
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.

juce_LiveConstantEditor.cpp 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. #if JUCE_ENABLE_LIVE_CONSTANT_EDITOR
  21. namespace LiveConstantEditor
  22. {
  23. //==============================================================================
  24. class AllComponentRepainter : private Timer,
  25. private DeletedAtShutdown
  26. {
  27. public:
  28. AllComponentRepainter() {}
  29. ~AllComponentRepainter() override { clearSingletonInstance(); }
  30. JUCE_DECLARE_SINGLETON (AllComponentRepainter, false)
  31. void trigger()
  32. {
  33. if (! isTimerRunning())
  34. startTimer (100);
  35. }
  36. private:
  37. void timerCallback() override
  38. {
  39. stopTimer();
  40. Array<Component*> alreadyDone;
  41. for (int i = TopLevelWindow::getNumTopLevelWindows(); --i >= 0;)
  42. if (auto* c = TopLevelWindow::getTopLevelWindow(i))
  43. repaintAndResizeAllComps (c, alreadyDone);
  44. auto& desktop = Desktop::getInstance();
  45. for (int i = desktop.getNumComponents(); --i >= 0;)
  46. if (auto* c = desktop.getComponent(i))
  47. repaintAndResizeAllComps (c, alreadyDone);
  48. }
  49. static void repaintAndResizeAllComps (Component::SafePointer<Component> c,
  50. Array<Component*>& alreadyDone)
  51. {
  52. if (c->isVisible() && ! alreadyDone.contains (c))
  53. {
  54. c->repaint();
  55. c->resized();
  56. for (int i = c->getNumChildComponents(); --i >= 0;)
  57. {
  58. if (auto* child = c->getChildComponent(i))
  59. {
  60. repaintAndResizeAllComps (child, alreadyDone);
  61. alreadyDone.add (child);
  62. }
  63. if (c == nullptr)
  64. break;
  65. }
  66. }
  67. }
  68. };
  69. JUCE_IMPLEMENT_SINGLETON (AllComponentRepainter)
  70. JUCE_IMPLEMENT_SINGLETON (ValueList)
  71. //==============================================================================
  72. int64 parseInt (String s)
  73. {
  74. s = s.trimStart();
  75. if (s.startsWithChar ('-'))
  76. return -parseInt (s.substring (1));
  77. if (s.startsWith ("0x"))
  78. return s.substring(2).getHexValue64();
  79. return s.getLargeIntValue();
  80. }
  81. double parseDouble (const String& s)
  82. {
  83. return s.retainCharacters ("0123456789.eE-").getDoubleValue();
  84. }
  85. String intToString (int v, bool preferHex) { return preferHex ? "0x" + String::toHexString (v) : String (v); }
  86. String intToString (int64 v, bool preferHex) { return preferHex ? "0x" + String::toHexString (v) : String (v); }
  87. //==============================================================================
  88. LiveValueBase::LiveValueBase (const char* file, int line)
  89. : sourceFile (file), sourceLine (line)
  90. {
  91. name = File (sourceFile).getFileName() + " : " + String (sourceLine);
  92. }
  93. LiveValueBase::~LiveValueBase()
  94. {
  95. }
  96. //==============================================================================
  97. LivePropertyEditorBase::LivePropertyEditorBase (LiveValueBase& v, CodeDocument& d)
  98. : value (v), document (d), sourceEditor (document, &tokeniser)
  99. {
  100. setSize (600, 100);
  101. addAndMakeVisible (name);
  102. addAndMakeVisible (resetButton);
  103. addAndMakeVisible (valueEditor);
  104. addAndMakeVisible (sourceEditor);
  105. findOriginalValueInCode();
  106. selectOriginalValue();
  107. name.setFont (13.0f);
  108. name.setText (v.name, dontSendNotification);
  109. valueEditor.setMultiLine (v.isString());
  110. valueEditor.setReturnKeyStartsNewLine (v.isString());
  111. valueEditor.setText (v.getStringValue (wasHex), dontSendNotification);
  112. valueEditor.onTextChange = [this] { applyNewValue (valueEditor.getText()); };
  113. sourceEditor.setReadOnly (true);
  114. sourceEditor.setFont (sourceEditor.getFont().withHeight (13.0f));
  115. resetButton.onClick = [this] { applyNewValue (value.getOriginalStringValue (wasHex)); };
  116. }
  117. void LivePropertyEditorBase::paint (Graphics& g)
  118. {
  119. g.setColour (Colours::white);
  120. g.fillRect (getLocalBounds().removeFromBottom (1));
  121. }
  122. void LivePropertyEditorBase::resized()
  123. {
  124. auto r = getLocalBounds().reduced (0, 3).withTrimmedBottom (1);
  125. auto left = r.removeFromLeft (jmax (200, r.getWidth() / 3));
  126. auto top = left.removeFromTop (25);
  127. resetButton.setBounds (top.removeFromRight (35).reduced (0, 3));
  128. name.setBounds (top);
  129. if (customComp != nullptr)
  130. {
  131. valueEditor.setBounds (left.removeFromTop (25));
  132. left.removeFromTop (2);
  133. customComp->setBounds (left);
  134. }
  135. else
  136. {
  137. valueEditor.setBounds (left);
  138. }
  139. r.removeFromLeft (4);
  140. sourceEditor.setBounds (r);
  141. }
  142. void LivePropertyEditorBase::applyNewValue (const String& s)
  143. {
  144. value.setStringValue (s);
  145. document.replaceSection (valueStart.getPosition(), valueEnd.getPosition(), value.getCodeValue (wasHex));
  146. document.clearUndoHistory();
  147. selectOriginalValue();
  148. valueEditor.setText (s, dontSendNotification);
  149. AllComponentRepainter::getInstance()->trigger();
  150. }
  151. void LivePropertyEditorBase::selectOriginalValue()
  152. {
  153. sourceEditor.selectRegion (valueStart, valueEnd);
  154. }
  155. void LivePropertyEditorBase::findOriginalValueInCode()
  156. {
  157. CodeDocument::Position pos (document, value.sourceLine, 0);
  158. auto line = pos.getLineText();
  159. auto p = line.getCharPointer();
  160. p = CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT"));
  161. if (p.isEmpty())
  162. {
  163. // Not sure how this would happen - some kind of mix-up between source code and line numbers..
  164. jassertfalse;
  165. return;
  166. }
  167. p += (int) (sizeof ("JUCE_LIVE_CONSTANT") - 1);
  168. p.incrementToEndOfWhitespace();
  169. if (! CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT")).isEmpty())
  170. {
  171. // Aargh! You've added two JUCE_LIVE_CONSTANT macros on the same line!
  172. // They're identified by their line number, so you must make sure each
  173. // one goes on a separate line!
  174. jassertfalse;
  175. }
  176. if (p.getAndAdvance() == '(')
  177. {
  178. auto start = p, end = p;
  179. int depth = 1;
  180. while (! end.isEmpty())
  181. {
  182. auto c = end.getAndAdvance();
  183. if (c == '(') ++depth;
  184. if (c == ')') --depth;
  185. if (depth == 0)
  186. {
  187. --end;
  188. break;
  189. }
  190. }
  191. if (end > start)
  192. {
  193. valueStart = CodeDocument::Position (document, value.sourceLine, (int) (start - line.getCharPointer()));
  194. valueEnd = CodeDocument::Position (document, value.sourceLine, (int) (end - line.getCharPointer()));
  195. valueStart.setPositionMaintained (true);
  196. valueEnd.setPositionMaintained (true);
  197. wasHex = String (start, end).containsIgnoreCase ("0x");
  198. }
  199. }
  200. }
  201. //==============================================================================
  202. class ValueListHolderComponent : public Component
  203. {
  204. public:
  205. ValueListHolderComponent (ValueList& l) : valueList (l)
  206. {
  207. setVisible (true);
  208. }
  209. void addItem (int width, LiveValueBase& v, CodeDocument& doc)
  210. {
  211. addAndMakeVisible (editors.add (v.createPropertyComponent (doc)));
  212. layout (width);
  213. }
  214. void layout (int width)
  215. {
  216. setSize (width, editors.size() * itemHeight);
  217. resized();
  218. }
  219. void resized() override
  220. {
  221. auto r = getLocalBounds().reduced (2, 0);
  222. for (int i = 0; i < editors.size(); ++i)
  223. editors.getUnchecked(i)->setBounds (r.removeFromTop (itemHeight));
  224. }
  225. enum { itemHeight = 120 };
  226. ValueList& valueList;
  227. OwnedArray<LivePropertyEditorBase> editors;
  228. };
  229. //==============================================================================
  230. class ValueList::EditorWindow : public DocumentWindow,
  231. private DeletedAtShutdown
  232. {
  233. public:
  234. EditorWindow (ValueList& list)
  235. : DocumentWindow ("Live Values", Colours::lightgrey, DocumentWindow::closeButton)
  236. {
  237. setLookAndFeel (&lookAndFeel);
  238. setUsingNativeTitleBar (true);
  239. viewport.setViewedComponent (new ValueListHolderComponent (list), true);
  240. viewport.setSize (700, 600);
  241. viewport.setScrollBarsShown (true, false);
  242. setContentNonOwned (&viewport, true);
  243. setResizable (true, false);
  244. setResizeLimits (500, 400, 10000, 10000);
  245. centreWithSize (getWidth(), getHeight());
  246. setVisible (true);
  247. }
  248. ~EditorWindow() override
  249. {
  250. setLookAndFeel (nullptr);
  251. }
  252. void closeButtonPressed() override
  253. {
  254. setVisible (false);
  255. }
  256. void updateItems (ValueList& list)
  257. {
  258. if (auto* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))
  259. {
  260. while (l->getNumChildComponents() < list.values.size())
  261. {
  262. if (auto* v = list.values [l->getNumChildComponents()])
  263. l->addItem (viewport.getMaximumVisibleWidth(), *v, list.getDocument (v->sourceFile));
  264. else
  265. break;
  266. }
  267. setVisible (true);
  268. }
  269. }
  270. void resized() override
  271. {
  272. DocumentWindow::resized();
  273. if (auto* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))
  274. l->layout (viewport.getMaximumVisibleWidth());
  275. }
  276. Viewport viewport;
  277. LookAndFeel_V3 lookAndFeel;
  278. };
  279. //==============================================================================
  280. ValueList::ValueList() {}
  281. ValueList::~ValueList() { clearSingletonInstance(); }
  282. void ValueList::addValue (LiveValueBase* v)
  283. {
  284. values.add (v);
  285. triggerAsyncUpdate();
  286. }
  287. void ValueList::handleAsyncUpdate()
  288. {
  289. if (editorWindow == nullptr)
  290. editorWindow = new EditorWindow (*this);
  291. editorWindow->updateItems (*this);
  292. }
  293. CodeDocument& ValueList::getDocument (const File& file)
  294. {
  295. const int index = documentFiles.indexOf (file.getFullPathName());
  296. if (index >= 0)
  297. return *documents.getUnchecked (index);
  298. auto* doc = documents.add (new CodeDocument());
  299. documentFiles.add (file);
  300. doc->replaceAllContent (file.loadFileAsString());
  301. doc->clearUndoHistory();
  302. return *doc;
  303. }
  304. //==============================================================================
  305. struct ColourEditorComp : public Component,
  306. private ChangeListener
  307. {
  308. ColourEditorComp (LivePropertyEditorBase& e) : editor (e)
  309. {
  310. setMouseCursor (MouseCursor::PointingHandCursor);
  311. }
  312. Colour getColour() const
  313. {
  314. return Colour ((uint32) parseInt (editor.value.getStringValue (false)));
  315. }
  316. void paint (Graphics& g) override
  317. {
  318. g.fillCheckerBoard (getLocalBounds().toFloat(), 6.0f, 6.0f,
  319. Colour (0xffdddddd).overlaidWith (getColour()),
  320. Colour (0xffffffff).overlaidWith (getColour()));
  321. }
  322. void mouseDown (const MouseEvent&) override
  323. {
  324. auto colourSelector = std::make_unique<ColourSelector>();
  325. colourSelector->setName ("Colour");
  326. colourSelector->setCurrentColour (getColour());
  327. colourSelector->addChangeListener (this);
  328. colourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);
  329. colourSelector->setSize (300, 400);
  330. CallOutBox::launchAsynchronously (std::move (colourSelector), getScreenBounds(), nullptr);
  331. }
  332. void changeListenerCallback (ChangeBroadcaster* source) override
  333. {
  334. if (auto* cs = dynamic_cast<ColourSelector*> (source))
  335. editor.applyNewValue (getAsString (cs->getCurrentColour(), true));
  336. repaint();
  337. }
  338. LivePropertyEditorBase& editor;
  339. };
  340. Component* createColourEditor (LivePropertyEditorBase& editor)
  341. {
  342. return new ColourEditorComp (editor);
  343. }
  344. //==============================================================================
  345. struct SliderComp : public Component
  346. {
  347. SliderComp (LivePropertyEditorBase& e, bool useFloat)
  348. : editor (e), isFloat (useFloat)
  349. {
  350. slider.setTextBoxStyle (Slider::NoTextBox, true, 0, 0);
  351. addAndMakeVisible (slider);
  352. updateRange();
  353. slider.onDragEnd = [this] { updateRange(); };
  354. slider.onValueChange = [this]
  355. {
  356. editor.applyNewValue (isFloat ? getAsString ((double) slider.getValue(), editor.wasHex)
  357. : getAsString ((int64) slider.getValue(), editor.wasHex));
  358. };
  359. }
  360. virtual void updateRange()
  361. {
  362. double v = isFloat ? parseDouble (editor.value.getStringValue (false))
  363. : (double) parseInt (editor.value.getStringValue (false));
  364. double range = isFloat ? 10 : 100;
  365. slider.setRange (v - range, v + range);
  366. slider.setValue (v, dontSendNotification);
  367. }
  368. void resized() override
  369. {
  370. slider.setBounds (getLocalBounds().removeFromTop (25));
  371. }
  372. LivePropertyEditorBase& editor;
  373. Slider slider;
  374. bool isFloat;
  375. };
  376. //==============================================================================
  377. struct BoolSliderComp : public SliderComp
  378. {
  379. BoolSliderComp (LivePropertyEditorBase& e)
  380. : SliderComp (e, false)
  381. {
  382. slider.onValueChange = [this] { editor.applyNewValue (slider.getValue() > 0.5 ? "true" : "false"); };
  383. }
  384. void updateRange() override
  385. {
  386. slider.setRange (0.0, 1.0, dontSendNotification);
  387. slider.setValue (editor.value.getStringValue (false) == "true", dontSendNotification);
  388. }
  389. };
  390. Component* createIntegerSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, false); }
  391. Component* createFloatSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, true); }
  392. Component* createBoolSlider (LivePropertyEditorBase& editor) { return new BoolSliderComp (editor); }
  393. }
  394. #endif
  395. } // namespace juce