juce_live_constant


#1

If you haven't noticed in the git log:
Jules has added new functionality to tweak variables at runtime and immediatly see the result. Awesome!

It's already in the JuceDemo.

 

Thank you Jules!

 

Just a tiny little inconvenience:
Once closed, the editor can't be reopened. This could be avoided if the editor were non-closable.

 

Edit:
The announcement from our fearless leader himself:
http://www.juce.com/forum/topic/very-cool-new-feature-juceliveconstant


#2

Lovely stuff :D

Although, while the docs say that it will call repaint/resized for all visible components, if the topmost children don't actually change size, their descendants won't be notified; such variables used in a component's 'resized' will therefore most likely not get updated without further poking (e.g. resizing the window manually). I hacked a recursive resized call into AllComponentRepainter, and it all worked nicely. Not sure if that's desirable behaviour, but it's certainly more useful!

 

 


#3

Ta!

..I could have sworn I made it do a recursive resize call.. At least, that's what I intended to do! I'll take a look at that - probably just a slip of the keyboard, I was in a hurry when writing that class..


#4

Wow - this is so cool - I just tried it, and I'm like "Oh my god, that is sooo coool" (insert some kind of valley girl accent...)


#5

I'm using it and it is awesome!!


#6

Just want to say I just started using this in my app to help tweak UI response values and it is awesome!! Thank you Jules :)

 

 

 


#7

how do I ensure changed constants get saved in cpp file ?

 

i can see everything working as in JUCEDEMO but then when examining my cpp/hpp in XCode  the values havent changed !

 

I thought maybe if i close any xcode IDE editor window that is displaying the source files in question ( containitng JUCE_LIVE_CONSTANT )  that this would solve things but doesnt seem to. 


#8

No, it doesn't currently touch your source file, it just shows you the new code so you can copy/paste it if you want to. Might add something to save it when we get chance..


#9

:(


#10

OK - I've modified your JUCE_LIVECONSTANTEDITOR  code myself to do this - there is now a SAVE button to do what I requested. 

You might want to fold it back into your next build 

Due to the inability of the forum software to accept my source files -try what may - i've simply pasted the modded spurce code here.

I'll post the CPP file in the reply after this one.   TO SEE WHICH BITS I CHANGED EITHER DO DIFF OR SEARCH FOR "D STENNING"

/*

  ==============================================================================

 

   JUCE_LIVECONSTANTEDITOR.H MODIFIED BY D STENNING

 

   This file is part of the JUCE library.

   Copyright (c) 2013 - Raw Material Software Ltd.

 

   Permission is granted to use this software under the terms of either:

   a) the GPL v2 (or any later version)

   b) the Affero GPL v3

 

   Details of these licenses can be found at: www.gnu.org/licenses

 

   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY

   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR

   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

 

   ------------------------------------------------------------------------------

 

   To release a closed-source product which uses JUCE, commercial licenses are

   available: visit www.juce.com for more information.

 

  ==============================================================================

*/

 

#ifndef JUCE_LIVECONSTANTEDITOR_H_INCLUDED

#define JUCE_LIVECONSTANTEDITOR_H_INCLUDED

 

#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR && ! DOXYGEN

 

//==============================================================================

/** You can safely ignore all the stuff in this namespace - it's a bunch of boilerplate

    code used to implement the JUCE_LIVE_CONSTANT functionality.

*/

namespace LiveConstantEditor

{

    int64 parseInt (String);

    double parseDouble (const String&);

    String intToString (int, bool preferHex);

    String intToString (int64, bool preferHex);

 

    template <typename Type>

    static void setFromString (Type& v,           const String& s)    { v = static_cast<Type> (s); }

    inline void setFromString (char& v,           const String& s)    { v = (char)           parseInt (s); }

    inline void setFromString (unsigned char& v,  const String& s)    { v = (unsigned char)  parseInt (s); }

    inline void setFromString (short& v,          const String& s)    { v = (short)          parseInt (s); }

    inline void setFromString (unsigned short& v, const String& s)    { v = (unsigned short) parseInt (s); }

    inline void setFromString (int& v,            const String& s)    { v = (int)            parseInt (s); }

    inline void setFromString (unsigned int& v,   const String& s)    { v = (unsigned int)   parseInt (s); }

    inline void setFromString (long& v,           const String& s)    { v = (long)           parseInt (s); }

    inline void setFromString (unsigned long& v,  const String& s)    { v = (unsigned long)  parseInt (s); }

    inline void setFromString (int64& v,          const String& s)    { v = (int64)          parseInt (s); }

    inline void setFromString (uint64& v,         const String& s)    { v = (uint64)         parseInt (s); }

    inline void setFromString (double& v,         const String& s)    { v = parseDouble (s); }

    inline void setFromString (float& v,          const String& s)    { v = (float) parseDouble (s); }

    inline void setFromString (String& v,         const String& s)    { v = s; }

    inline void setFromString (Colour& v,         const String& s)    { v = Colour ((int) parseInt (s)); }

 

    template <typename Type>

    inline String getAsString (const Type& v, bool)              { return String (v); }

    inline String getAsString (char v, bool preferHex)           { return intToString ((int) v, preferHex); }

    inline String getAsString (unsigned char v, bool preferHex)  { return intToString ((int) v, preferHex); }

    inline String getAsString (short v, bool preferHex)          { return intToString ((int) v, preferHex); }

    inline String getAsString (unsigned short v, bool preferHex) { return intToString ((int) v, preferHex); }

    inline String getAsString (int v, bool preferHex)            { return intToString ((int) v, preferHex); }

    inline String getAsString (unsigned int v, bool preferHex)   { return intToString ((int) v, preferHex); }

    inline String getAsString (int64 v, bool preferHex)          { return intToString ((int64) v, preferHex); }

    inline String getAsString (uint64 v, bool preferHex)         { return intToString ((int64) v, preferHex); }

    inline String getAsString (Colour v, bool)                   { return intToString ((int) v.getARGB(), true); }

 

    template <typename Type>

    inline String getAsCode (Type& v, bool preferHex)       { return getAsString (v, preferHex); }

    inline String getAsCode (Colour v, bool)                { return "Colour (0x" + String::toHexString ((int) v.getARGB()).paddedLeft ('0', 8) + ")"; }

    inline String getAsCode (const String& v, bool)         { return "\"" + v + "\""; }

    inline String getAsCode (const char* v, bool)           { return getAsCode (String (v), false); }

 

    template <typename Type>

    inline const char* castToCharPointer (const Type&)      { return ""; }

    inline const char* castToCharPointer (const String& s)  { return s.toRawUTF8(); }

 

    struct LivePropertyEditorBase;

 

    //==============================================================================

    struct JUCE_API  LiveValueBase

    {

        LiveValueBase (const char* file, int line);

        virtual ~LiveValueBase();

 

        virtual LivePropertyEditorBase* createPropertyComponent (CodeDocument&) = 0;

        virtual String getStringValue (bool preferHex) const = 0;

        virtual String getCodeValue (bool preferHex) const = 0;

        virtual void setStringValue (const String&) = 0;

        virtual String getOriginalStringValue (bool preferHex) const = 0;

 

        String name, sourceFile;

        int sourceLine;

 

        JUCE_DECLARE_NON_COPYABLE (LiveValueBase)

    };

 

    //==============================================================================

    struct JUCE_API  LivePropertyEditorBase  : public Component,

                                               private TextEditor::Listener,

                                               private ButtonListener

    {

        LivePropertyEditorBase (LiveValueBase&, CodeDocument&);

 

        void paint (Graphics&) override;

        void resized() override;

        void textEditorTextChanged (TextEditor&) override;

        void buttonClicked (Button*) override;

 

        void applyNewValue (const String&);

        void selectOriginalValue();

        void findOriginalValueInCode();

 

        LiveValueBase& value;

        Label name;

        TextEditor valueEditor;

        TextButton resetButton;

        TextButton saveButton;  // D STENNING

        

        CodeDocument& document;

        CPlusPlusCodeTokeniser tokeniser;

        CodeEditorComponent sourceEditor;

        CodeDocument::Position valueStart, valueEnd;

        ScopedPointer<Component> customComp;

        bool wasHex;

 

        JUCE_DECLARE_NON_COPYABLE (LivePropertyEditorBase)

    };

 

    //==============================================================================

    Component* createColourEditor (LivePropertyEditorBase&);

    Component* createIntegerSlider (LivePropertyEditorBase&);

    Component* createFloatSlider (LivePropertyEditorBase&);

 

    template <typename Type> struct CustomEditor    { static Component* create (LivePropertyEditorBase&)   { return nullptr; } };

    template<> struct CustomEditor<char>            { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };

    template<> struct CustomEditor<unsigned char>   { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };

    template<> struct CustomEditor<int>             { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };

    template<> struct CustomEditor<unsigned int>    { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };

    template<> struct CustomEditor<short>           { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };

    template<> struct CustomEditor<unsigned short>  { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };

    template<> struct CustomEditor<int64>           { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };

    template<> struct CustomEditor<uint64>          { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } };

    template<> struct CustomEditor<float>           { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } };

    template<> struct CustomEditor<double>          { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } };

    template<> struct CustomEditor<Colour>          { static Component* create (LivePropertyEditorBase& e) { return createColourEditor (e); } };

 

    template <typename Type>

    struct LivePropertyEditor  : public LivePropertyEditorBase

    {

        template <typename ValueType>

        LivePropertyEditor (ValueType& v, CodeDocument& d)  : LivePropertyEditorBase (v, d)

        {

            addAndMakeVisible (customComp = CustomEditor<Type>::create (*this));

        }

    };

 

    //==============================================================================

    template <typename Type>

    struct LiveValue  : public LiveValueBase

    {

        LiveValue (const char* file, int line, const Type& initialValue)

            : LiveValueBase (file, line), value (initialValue), originalValue (initialValue)

        {}

 

        operator Type() const noexcept   { return value; }

        operator const char*() const     { return castToCharPointer (value); }

 

        LivePropertyEditorBase* createPropertyComponent (CodeDocument& doc) override

        {

            return new LivePropertyEditor<Type> (*this, doc);

        }

 

        String getStringValue (bool preferHex) const override           { return getAsString (value, preferHex); }

        String getCodeValue (bool preferHex) const override             { return getAsCode (value, preferHex); }

        String getOriginalStringValue (bool preferHex) const override   { return getAsString (originalValue, preferHex); }

        void setStringValue (const String& s) override                  { setFromString (value, s); }

 

        Type value, originalValue;

 

        JUCE_DECLARE_NON_COPYABLE (LiveValue)

    };

 

    //==============================================================================

    class JUCE_API ValueList  : private AsyncUpdater,

                                private DeletedAtShutdown

    {

    public:

        ValueList();

        ~ValueList();

 

        static ValueList& getInstance();

 

        template <typename Type>

        LiveValue<Type>& getValue (const char* file, int line, const Type& initialValue)

        {

            const ScopedLock sl (lock);

            typedef LiveValue<Type> ValueType;

 

            for (int i = 0; i < values.size(); ++i)

            {

                LiveValueBase* v = values.getUnchecked(i);

 

                if (v->sourceLine == line && v->sourceFile == file)

                    return *static_cast<ValueType*> (v);

            }

 

            ValueType* v = new ValueType (file, line, initialValue);

            addValue (v);

            return *v;

        }

 

    private:

        OwnedArray<LiveValueBase> values;

        OwnedArray<CodeDocument> documents;

        Array<File> documentFiles;

        class EditorWindow;

        friend class EditorWindow;

        friend struct ContainerDeletePolicy<EditorWindow>;

        Component::SafePointer<EditorWindow> editorWindow;

        CriticalSection lock;

 

        CodeDocument& getDocument (const File&);

        void addValue (LiveValueBase*);

        void handleAsyncUpdate() override;

    };

 

    template <typename Type>

    inline LiveValue<Type>& getValue (const char* file, int line, const Type& initialValue)

    {

        return ValueList::getInstance().getValue (file, line, initialValue);

    }

 

    inline LiveValue<String>& getValue (const char* file, int line, const char* initialValue)

    {

        return getValue (file, line, String (initialValue));

    }

}

 

#endif

 

//==============================================================================

#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR || DOXYGEN

 /**

    This macro wraps a primitive constant value in some cunning boilerplate code that allows

    its value to be interactively tweaked in a popup window while your application is running.

 

    In a release build, this macro disappears and is replaced by only the constant that it

    wraps, but if JUCE_ENABLE_LIVE_CONSTANT_EDITOR is enabled, it injects a class wrapper

    that automatically pops-up a window containing an editor that allows the value to be

    tweaked at run-time. The editor window will also force all visible components to be

    resized and repainted whenever a value is changed, so that if you use this to wrap

    a colour or layout parameter, you'll be able to immediately see the effects of changing it.

 

    The editor will also load the original source-file that contains each JUCE_LIVE_CONSTANT

    macro, and will display a preview of the modified source code as you adjust the values.

 

    Things to note:

 

    - Only one of these per line! The __FILE__ and __LINE__ macros are used to identify

      the value, so things will get confused if you have more than one per line

    - Obviously because it needs to load the source code based on the __FILE__ macro,

      it'll only work if the source files are stored locally in the same location as they

      were when you compiled the program.

    - It's only designed to cope with simple types: primitives, string literals, and

      the Colour class, so if you try using it for other classes or complex expressions,

      good luck!

    - The editor window will get popped up whenever a new value is used for the first

      time. You can close the window, but there's no way to get it back without restarting

      the app!

 

    e.g. in this example the colours, font size, and text used in the paint method can

    all be adjusted live:

    @code

    void MyComp::paint (Graphics& g) override

    {

        g.fillAll (JUCE_LIVE_CONSTANT (Colour (0xffddddff)));

 

        Colour fontColour = JUCE_LIVE_CONSTANT (Colour (0xff005500));

        float fontSize = JUCE_LIVE_CONSTANT (16.0f);

 

        g.setColour (fontColour);

        g.setFont (fontSize);

 

        g.drawFittedText (JUCE_LIVE_CONSTANT ("Hello world!"),

                          getLocalBounds(), Justification::centred, 2);

    }

    @endcode

 */

 #define JUCE_LIVE_CONSTANT(initialValue) \

    (LiveConstantEditor::getValue (__FILE__, __LINE__ - 1, initialValue))

#else

 #define JUCE_LIVE_CONSTANT(initialValue) \

    (initialValue)

#endif

 

 

#endif   // JUCE_LIVECONSTANTEDITOR_H_INCLUDED


#11

/*

  ==============================================================================

 

  JUCE_LIVECONSTANTEDITOR.CPP  MODIFIED BY D STENNING  

 

   This file is part of the JUCE library.

   Copyright (c) 2013 - Raw Material Software Ltd.

 

   Permission is granted to use this software under the terms of either:

   a) the GPL v2 (or any later version)

   b) the Affero GPL v3

 

   Details of these licenses can be found at: www.gnu.org/licenses

 

   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY

   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR

   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

 

   ------------------------------------------------------------------------------

 

   To release a closed-source product which uses JUCE, commercial licenses are

   available: visit www.juce.com for more information.

 

  ==============================================================================

*/

 

#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR

 

namespace LiveConstantEditor

{

 

//==============================================================================

class AllComponentRepainter  : private Timer,

                               private DeletedAtShutdown

{

public:

    AllComponentRepainter() {}

 

    static AllComponentRepainter& getInstance()

    {

        static AllComponentRepainter* instance = new AllComponentRepainter();

        return *instance;

    }

 

    void trigger()

    {

        if (! isTimerRunning())

            startTimer (100);

    }

 

private:

    void timerCallback() override

    {

        stopTimer();

 

        for (int i = TopLevelWindow::getNumTopLevelWindows(); --i >= 0;)

            if (Component* c = TopLevelWindow::getTopLevelWindow(i))

                repaintAndResizeAllComps (c);

    }

 

    static void repaintAndResizeAllComps (Component::SafePointer<Component> c)

    {

        if (c->isVisible())

        {

            c->repaint();

            c->resized();

 

            for (int i = c->getNumChildComponents(); --i >= 0;)

                if (c != nullptr)

                    if (Component* child = c->getChildComponent(i))

                        repaintAndResizeAllComps (child);

        }

    }

};

 

//==============================================================================

int64 parseInt (String s)

{

    s = s.retainCharacters ("0123456789abcdefABCDEFx");

 

    if (s.startsWith ("0x"))

        return s.substring(2).getHexValue64();

 

    return s.getLargeIntValue();

}

 

double parseDouble (const String& s)

{

    return s.retainCharacters ("0123456789.eE-").getDoubleValue();

}

 

String intToString (int   v, bool preferHex)    { return preferHex ? "0x" + String::toHexString (v) : String (v); }

String intToString (int64 v, bool preferHex)    { return preferHex ? "0x" + String::toHexString (v) : String (v); }

 

//==============================================================================

LiveValueBase::LiveValueBase (const char* file, int line)

    : sourceFile (file), sourceLine (line)

{

    name = File (sourceFile).getFileName() + " : " + String (sourceLine);

}

 

LiveValueBase::~LiveValueBase()

{

}

 

//==============================================================================

LivePropertyEditorBase::LivePropertyEditorBase (LiveValueBase& v, CodeDocument& d)

    : value (v), resetButton ("Reset"),saveButton ("Save"), document (d), sourceEditor (document, &tokeniser), wasHex (false)   // D STENNING

{

    setSize (600, 100);

 

    addAndMakeVisible (name);

    addAndMakeVisible (resetButton);

    addAndMakeVisible (saveButton); // D STENNING

    addAndMakeVisible (valueEditor);

    addAndMakeVisible (sourceEditor);

 

    findOriginalValueInCode();

    selectOriginalValue();

    

    name.setFont (13.0f);

    name.setText (v.name, dontSendNotification);

    valueEditor.setText (v.getStringValue (wasHex), dontSendNotification);

    valueEditor.addListener (this);

    sourceEditor.setReadOnly (true);

    resetButton.addListener (this);

    saveButton.addListener (this);  // D STENNING

}

 

void LivePropertyEditorBase::paint (Graphics& g)

{

    g.setColour (Colours::white);

    g.fillRect (getLocalBounds().removeFromBottom (1));

}

 

void LivePropertyEditorBase::resized()

{

    Rectangle<int> r (getLocalBounds().reduced (0, 3).withTrimmedBottom (1));

 

    Rectangle<int> left (r.removeFromLeft (jmax (200, r.getWidth() / 3)));

 

    Rectangle<int> top (left.removeFromTop (25));

    resetButton.setBounds (top.removeFromRight (35).reduced (0, 3));

    saveButton.setBounds ( resetButton.getBounds().translated( -40, 0 ));  // D STENNING

    name.setBounds (top);

    valueEditor.setBounds (left.removeFromTop (25));

    left.removeFromTop (2);

 

    if (customComp != nullptr)

        customComp->setBounds (left);

 

    r.removeFromLeft (4);

    sourceEditor.setBounds (r);

}

 

void LivePropertyEditorBase::textEditorTextChanged (TextEditor&)

{

    applyNewValue (valueEditor.getText());

}

 

void LivePropertyEditorBase::buttonClicked (Button* b)  // D STENNING

{

    // D STENNING

    if ( b->getButtonText() == "Save" )

    {

        File f(value.sourceFile);

        if ( f.hasWriteAccess() )

        {

             FileOutputStream* stream = f.createOutputStream();

            

            if ( stream )

            {

                if ( stream->openedOk() )

                {

                    stream->setPosition(0);

                    stream->truncate();

                   bool bret =  document.writeToStream(*stream);

                   if ( bret )

                       stream->flush();

                }

            }

                

        }

            

    }

    else

    {

        applyNewValue (value.getOriginalStringValue (wasHex));

    }

}

 

void LivePropertyEditorBase::applyNewValue (const String& s)

{

    value.setStringValue (s);

 

    document.replaceSection (valueStart.getPosition(), valueEnd.getPosition(), value.getCodeValue (wasHex));

    document.clearUndoHistory();

    selectOriginalValue();

 

    valueEditor.setText (s, dontSendNotification);

    AllComponentRepainter::getInstance().trigger();

}

 

void LivePropertyEditorBase::selectOriginalValue()

{

    sourceEditor.selectRegion (valueStart, valueEnd);

}

 

void LivePropertyEditorBase::findOriginalValueInCode()

{

    CodeDocument::Position pos (document, value.sourceLine, 0);

    String line (pos.getLineText());

    String::CharPointerType p (line.getCharPointer());

 

    p = CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT"));

 

    if (p.isEmpty())

    {

        // Not sure how this would happen - some kind of mix-up between source code and line numbers..

        jassertfalse;

        return;

    }

 

    p += (int) (sizeof ("JUCE_LIVE_CONSTANT") - 1);

    p = p.findEndOfWhitespace();

 

    if (! CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT")).isEmpty())

    {

        // Aargh! You've added two JUCE_LIVE_CONSTANT macros on the same line!

        // They're identified by their line number, so you must make sure each

        // one goes on a separate line!

        jassertfalse;

    }

 

    if (p.getAndAdvance() == '(')

    {

        String::CharPointerType start (p), end (p);

 

        int depth = 1;

 

        while (! end.isEmpty())

        {

            const juce_wchar c = end.getAndAdvance();

 

            if (c == '(')  ++depth;

            if (c == ')')  --depth;

 

            if (depth == 0)

            {

                --end;

                break;

            }

        }

 

        if (end > start)

        {

            valueStart = CodeDocument::Position (document, value.sourceLine, (int) (start - line.getCharPointer()));

            valueEnd   = CodeDocument::Position (document, value.sourceLine, (int) (end   - line.getCharPointer()));

 

            valueStart.setPositionMaintained (true);

            valueEnd.setPositionMaintained (true);

 

            wasHex = String (start, end).containsIgnoreCase ("0x");

        }

    }

}

 

//==============================================================================

class ValueListHolderComponent  : public Component

{

public:

    ValueListHolderComponent (ValueList& l) : valueList (l)

    {

        setVisible (true);

    }

 

    void addItem (int width, LiveValueBase& v, CodeDocument& doc)

    {

        addAndMakeVisible (editors.add (v.createPropertyComponent (doc)));

        layout (width);

    }

 

    void layout (int width)

    {

        setSize (width, editors.size() * itemHeight);

        resized();

    }

 

    void resized() override

    {

        Rectangle<int> r (getLocalBounds().reduced (2, 0));

 

        for (int i = 0; i < editors.size(); ++i)

            editors.getUnchecked(i)->setBounds (r.removeFromTop (itemHeight));

    }

 

    enum { itemHeight = 120 };

 

    ValueList& valueList;

    OwnedArray<LivePropertyEditorBase> editors;

};

 

//==============================================================================

class ValueList::EditorWindow  : public DocumentWindow,

                                 private DeletedAtShutdown

{

public:

    EditorWindow (ValueList& list)

        : DocumentWindow ("Live Values", Colours::lightgrey, DocumentWindow::closeButton)

    {

        setLookAndFeel (&lookAndFeel);

        setUsingNativeTitleBar (true);

 

        viewport.setViewedComponent (new ValueListHolderComponent (list), true);

        viewport.setSize (700, 600);

        viewport.setScrollBarsShown (true, false);

 

        setContentNonOwned (&viewport, true);

        setResizable (true, false);

        setResizeLimits (500, 400, 10000, 10000);

        centreWithSize (getWidth(), getHeight());

        setVisible (true);

    }

 

    void closeButtonPressed() override

    {

        setVisible (false);

    }

 

    void updateItems (ValueList& list)

    {

        if (ValueListHolderComponent* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))

        {

            while (l->getNumChildComponents() < list.values.size())

            {

                if (LiveValueBase* v = list.values [l->getNumChildComponents()])

                {

                    l->addItem (viewport.getMaximumVisibleWidth(), *v, list.getDocument (v->sourceFile));

                }

                else

                    break;

            }

 

            setVisible (true);

        }

    }

 

    void resized() override

    {

        DocumentWindow::resized();

 

        if (ValueListHolderComponent* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent()))

            l->layout (viewport.getMaximumVisibleWidth());

    }

 

    Viewport viewport;

    LookAndFeel_V3 lookAndFeel;

};

 

//==============================================================================

ValueList::ValueList() {}

ValueList::~ValueList() {}

 

ValueList& ValueList::getInstance()

{

    static ValueList* i = new ValueList();

    return *i;

}

 

void ValueList::addValue (LiveValueBase* v)

{

    values.add (v);

    triggerAsyncUpdate();

}

 

void ValueList::handleAsyncUpdate()

{

    if (editorWindow == nullptr)

        editorWindow = new EditorWindow (*this);

 

    editorWindow->updateItems (*this);

}

 

CodeDocument& ValueList::getDocument (const File& file)

{

    const int index = documentFiles.indexOf (file.getFullPathName());

 

    if (index >= 0)

        return *documents.getUnchecked (index);

 

    CodeDocument* doc = documents.add (new CodeDocument());

    documentFiles.add (file);

    doc->replaceAllContent (file.loadFileAsString());

    doc->clearUndoHistory();

    return *doc;

}

 

//==============================================================================

struct ColourEditorComp  : public Component,

                           private ChangeListener

{

    ColourEditorComp (LivePropertyEditorBase& e)  : editor (e)

    {

        setMouseCursor (MouseCursor::PointingHandCursor);

    }

 

    Colour getColour() const

    {

        return Colour ((int) parseInt (editor.value.getStringValue (false)));

    }

 

    void paint (Graphics& g) override

    {

        g.fillCheckerBoard (getLocalBounds(), 6, 6,

                            Colour (0xffdddddd).overlaidWith (getColour()),

                            Colour (0xffffffff).overlaidWith (getColour()));

    }

 

    void mouseDown (const MouseEvent&) override

    {

        ColourSelector* colourSelector = new ColourSelector();

        colourSelector->setName ("Colour");

        colourSelector->setCurrentColour (getColour());

        colourSelector->addChangeListener (this);

        colourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack);

        colourSelector->setSize (300, 400);

 

        CallOutBox::launchAsynchronously (colourSelector, getScreenBounds(), nullptr);

    }

 

    void changeListenerCallback (ChangeBroadcaster* source) override

    {

        if (ColourSelector* cs = dynamic_cast<ColourSelector*> (source))

            editor.applyNewValue (getAsString (cs->getCurrentColour(), true));

 

        repaint();

    }

 

    LivePropertyEditorBase& editor;

};

 

Component* createColourEditor (LivePropertyEditorBase& editor)

{

    return new ColourEditorComp (editor);

}

 

//==============================================================================

class SliderComp   : public Component,

                     private Slider::Listener

{

public:

    SliderComp (LivePropertyEditorBase& e, bool useFloat)

        : editor (e), isFloat (useFloat)

    {

        slider.setTextBoxStyle (Slider::NoTextBox, true, 0, 0);

        addAndMakeVisible (slider);

        updateRange();

        slider.addListener (this);

    }

 

    void updateRange()

    {

        double v = isFloat ? parseDouble (editor.value.getStringValue (false))

                           : (double) parseInt (editor.value.getStringValue (false));

 

        double range = isFloat ? 10 : 100;

 

        slider.setRange (v - range, v + range);

        slider.setValue (v, dontSendNotification);

    }

 

private:

    LivePropertyEditorBase& editor;

    Slider slider;

    bool isFloat;

 

    void sliderValueChanged (Slider*)

    {

        editor.applyNewValue (isFloat ? getAsString ((double) slider.getValue(), editor.wasHex)

                                      : getAsString ((int64)  slider.getValue(), editor.wasHex));

 

    }

 

    void sliderDragStarted (Slider*)  {}

    void sliderDragEnded (Slider*)    { updateRange(); }

 

    void resized()

    {

        slider.setBounds (getLocalBounds().removeFromTop (25));

    }

};

 

 

Component* createIntegerSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, false); }

Component* createFloatSlider   (LivePropertyEditorBase& editor) { return new SliderComp (editor, true);  }

 

}

 

#endif


#12

Thanks, but the main reason I hadn't done this yet is because a simple implementation like this isn't really good enough for a public release. If it doesn't do stuff like checking whether the file has been modified then people will end up accidentally overwriting changes and grumbling at me! 

It probably needs to do a diff-style comparison of the lines before and after the change so it can cope with additions/deletions in other parts of the file, and certainly needs to update the code editor when the file changes so that you can always see what it will actually save when you hit the button.. This is all a lot more hassle to write, but I will do at some point!


#13

Bear in mind - clicking the Save button only affects a single file.  Would it be possible to send an AppleEvent command to Xcode to save whatever changes might have been done in the Xcode editor but not saved back ? 

Also what are the chances that a file has been modified in XCode and not saved back before RUN is invoked ? 

 

Do not all of us do a build first before we run our projects ?   its only at runtime that the SAVE button operates. And if we are running a project having made changes to source, doesnt Xcode know automatically which source files have been touched and take the appropriate action ? 

 

we are dealing with just-in-time syntax checking and compiling here - seems to me any change I make gets to be saved by xcode before compilation. The chances of some code having been edited/changed in an xcode editor but not saved back to file and recompiled once RUN gets invoked are very slim. 

 

For sure this button is good enough for me. I suspect its good enough for a lot of people. Particularly those- like me who are only using LIVE_CONSTANT to fiddle around with a load of constants that can be defined in a single file. Just as one could do for LookAndFeel. 

 

Am i missing something ?   At the time i want to use this live constant functionality the only code I am thinking about and tweaking is certain colours !  everything else has been saved - commited.