JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

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