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.

493 lines
15KB

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