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