git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@@ -0,0 +1,389 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ButtonHandler : public ComponentTypeHandler
{
public:
ButtonHandler (const String& typeDescription_,
const String& className_,
const std::type_info& componentClass,
const int defaultWidth_,
const int defaultHeight_)
: ComponentTypeHandler (typeDescription_, className_, componentClass,
defaultWidth_, defaultHeight_)
{}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ComponentTypeHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
if (auto* b = dynamic_cast<Button*> (component))
{
props.add (new ButtonTextProperty (b, document));
props.add (new ButtonCallbackProperty (b, document));
props.add (new ButtonRadioGroupProperty (b, document));
props.add (new ButtonConnectedEdgeProperty ("connected left", Button::ConnectedOnLeft, b, document));
props.add (new ButtonConnectedEdgeProperty ("connected right", Button::ConnectedOnRight, b, document));
props.add (new ButtonConnectedEdgeProperty ("connected top", Button::ConnectedOnTop, b, document));
props.add (new ButtonConnectedEdgeProperty ("connected bottom", Button::ConnectedOnBottom, b, document));
}
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
Button* const b = dynamic_cast<Button*> (comp);
XmlElement* e = ComponentTypeHandler::createXmlFor (comp, layout);
e->setAttribute ("buttonText", b->getButtonText());
e->setAttribute ("connectedEdges", b->getConnectedEdgeFlags());
e->setAttribute ("needsCallback", needsButtonListener (b));
e->setAttribute ("radioGroupId", b->getRadioGroupId());
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
Button* const b = dynamic_cast<Button*> (comp);
if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
return false;
b->setButtonText (xml.getStringAttribute ("buttonText", b->getButtonText()));
b->setConnectedEdges (xml.getIntAttribute ("connectedEdges", 0));
setNeedsButtonListener (b, xml.getBoolAttribute ("needsCallback", true));
b->setRadioGroupId (xml.getIntAttribute ("radioGroupId", 0));
return true;
}
String getCreationParameters (GeneratedCode&, Component* component) override
{
return quotedString (component->getName(), false);
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
Button* const b = dynamic_cast<Button*> (component);
if (b->getButtonText() != b->getName())
{
code.constructorCode
<< memberVariableName << "->setButtonText ("
<< quotedString (b->getButtonText(), code.shouldUseTransMacro()) << ");\n";
}
if (b->getConnectedEdgeFlags() != 0)
{
StringArray flags;
if (b->isConnectedOnLeft())
flags.add ("juce::Button::ConnectedOnLeft");
if (b->isConnectedOnRight())
flags.add ("juce::Button::ConnectedOnRight");
if (b->isConnectedOnTop())
flags.add ("juce::Button::ConnectedOnTop");
if (b->isConnectedOnBottom())
flags.add ("juce::Button::ConnectedOnBottom");
String s;
s << memberVariableName << "->setConnectedEdges ("
<< flags.joinIntoString (" | ") << ");\n";
code.constructorCode += s;
}
if (b->getRadioGroupId() != 0)
code.constructorCode << memberVariableName << "->setRadioGroupId ("
<< b->getRadioGroupId() << ");\n";
if (needsButtonListener (component))
code.constructorCode << memberVariableName << "->addListener (this);\n";
}
void fillInGeneratedCode (Component* component, GeneratedCode& code) override
{
ComponentTypeHandler::fillInGeneratedCode (component, code);
if (needsButtonListener (component))
{
String& callback = code.getCallbackCode ("public juce::Button::Listener",
"void",
"buttonClicked (juce::Button* buttonThatWasClicked)",
true);
if (callback.isNotEmpty())
callback << "else ";
const String memberVariableName (code.document->getComponentLayout()->getComponentMemberVariableName (component));
const String userCodeComment ("UserButtonCode_" + memberVariableName);
callback
<< "if (buttonThatWasClicked == " << memberVariableName << ".get())\n"
<< "{\n //[" << userCodeComment << "] -- add your button handler code here..\n //[/" << userCodeComment << "]\n}\n";
}
}
static bool needsButtonListener (Component* button)
{
return button->getProperties().getWithDefault ("generateListenerCallback", true);
}
static void setNeedsButtonListener (Component* button, const bool shouldDoCallback)
{
button->getProperties().set ("generateListenerCallback", shouldDoCallback);
}
private:
//==============================================================================
class ButtonTextProperty : public ComponentTextProperty <Button>
{
public:
ButtonTextProperty (Button* button_, JucerDocument& doc)
: ComponentTextProperty <Button> ("text", 100, false, button_, doc)
{
}
void setText (const String& newText) override
{
document.perform (new ButtonTextChangeAction (component, *document.getComponentLayout(), newText),
"Change button text");
}
String getText() const override
{
return component->getButtonText();
}
private:
class ButtonTextChangeAction : public ComponentUndoableAction <Button>
{
public:
ButtonTextChangeAction (Button* const comp, ComponentLayout& l, const String& newName_)
: ComponentUndoableAction <Button> (comp, l),
newName (newName_)
{
oldName = comp->getButtonText();
}
bool perform()
{
showCorrectTab();
getComponent()->setButtonText (newName);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setButtonText (oldName);
changed();
return true;
}
String newName, oldName;
};
};
class ButtonCallbackProperty : public ComponentBooleanProperty <Button>
{
public:
ButtonCallbackProperty (Button* b, JucerDocument& doc)
: ComponentBooleanProperty <Button> ("callback", "Generate ButtonListener", "Generate ButtonListener", b, doc)
{
}
void setState (bool newState)
{
document.perform (new ButtonCallbackChangeAction (component, *document.getComponentLayout(), newState),
"Change button callback");
}
bool getState() const { return needsButtonListener (component); }
private:
class ButtonCallbackChangeAction : public ComponentUndoableAction <Button>
{
public:
ButtonCallbackChangeAction (Button* const comp, ComponentLayout& l, const bool newState_)
: ComponentUndoableAction <Button> (comp, l),
newState (newState_)
{
oldState = needsButtonListener (comp);
}
bool perform()
{
showCorrectTab();
setNeedsButtonListener (getComponent(), newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
setNeedsButtonListener (getComponent(), oldState);
changed();
return true;
}
bool newState, oldState;
};
};
class ButtonRadioGroupProperty : public ComponentTextProperty <Button>
{
public:
ButtonRadioGroupProperty (Button* const button_, JucerDocument& doc)
: ComponentTextProperty <Button> ("radio group", 10, false, button_, doc)
{
}
void setText (const String& newText) override
{
document.perform (new ButtonRadioGroupChangeAction (component, *document.getComponentLayout(), newText.getIntValue()),
"Change radio group ID");
}
String getText() const override
{
return String (component->getRadioGroupId());
}
private:
class ButtonRadioGroupChangeAction : public ComponentUndoableAction <Button>
{
public:
ButtonRadioGroupChangeAction (Button* const comp, ComponentLayout& l, const int newId_)
: ComponentUndoableAction <Button> (comp, l),
newId (newId_)
{
oldId = comp->getRadioGroupId();
}
bool perform()
{
showCorrectTab();
getComponent()->setRadioGroupId (newId);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setRadioGroupId (oldId);
changed();
return true;
}
int newId, oldId;
};
};
class ButtonConnectedEdgeProperty : public ComponentBooleanProperty <Button>
{
public:
ButtonConnectedEdgeProperty (const String& name, const int flag_,
Button* b, JucerDocument& doc)
: ComponentBooleanProperty <Button> (name, "Connected", "Connected", b, doc),
flag (flag_)
{
}
void setState (bool newState)
{
document.perform (new ButtonConnectedChangeAction (component, *document.getComponentLayout(), flag, newState),
"Change button connected edges");
}
bool getState() const
{
return (component->getConnectedEdgeFlags() & flag) != 0;
}
private:
const int flag;
class ButtonConnectedChangeAction : public ComponentUndoableAction <Button>
{
public:
ButtonConnectedChangeAction (Button* const comp, ComponentLayout& l, const int flag_, const bool newState_)
: ComponentUndoableAction <Button> (comp, l),
flag (flag_),
newState (newState_)
{
oldState = ((comp->getConnectedEdgeFlags() & flag) != 0);
}
bool perform()
{
showCorrectTab();
if (newState)
getComponent()->setConnectedEdges (getComponent()->getConnectedEdgeFlags() | flag);
else
getComponent()->setConnectedEdges (getComponent()->getConnectedEdgeFlags() & ~flag);
changed();
return true;
}
bool undo()
{
showCorrectTab();
if (oldState)
getComponent()->setConnectedEdges (getComponent()->getConnectedEdgeFlags() | flag);
else
getComponent()->setConnectedEdges (getComponent()->getConnectedEdgeFlags() & ~flag);
changed();
return true;
}
const int flag;
bool newState, oldState;
};
};
};

View File

@@ -0,0 +1,446 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ComboBoxHandler : public ComponentTypeHandler
{
public:
ComboBoxHandler()
: ComponentTypeHandler ("Combo Box", "juce::ComboBox", typeid (ComboBox), 150, 24)
{}
Component* createNewComponent (JucerDocument*) override
{
return new ComboBox ("new combo box");
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
if (auto* const c = dynamic_cast<ComboBox*> (comp))
{
if (auto* e = ComponentTypeHandler::createXmlFor (comp, layout))
{
e->setAttribute ("editable", c->isTextEditable());
e->setAttribute ("layout", c->getJustificationType().getFlags());
e->setAttribute ("items", c->getProperties() ["items"].toString());
e->setAttribute ("textWhenNonSelected", c->getTextWhenNothingSelected());
e->setAttribute ("textWhenNoItems", c->getTextWhenNoChoicesAvailable());
return e;
}
}
return nullptr;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
return false;
ComboBox defaultBox;
if (ComboBox* const c = dynamic_cast<ComboBox*> (comp))
{
c->setEditableText (xml.getBoolAttribute ("editable", defaultBox.isTextEditable()));
c->setJustificationType (Justification (xml.getIntAttribute ("layout", defaultBox.getJustificationType().getFlags())));
c->getProperties().set ("items", xml.getStringAttribute ("items", String()));
c->setTextWhenNothingSelected (xml.getStringAttribute ("textWhenNonSelected", defaultBox.getTextWhenNothingSelected()));
c->setTextWhenNoChoicesAvailable (xml.getStringAttribute ("textWhenNoItems", defaultBox.getTextWhenNoChoicesAvailable()));
updateItems (c);
return true;
}
return false;
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ComponentTypeHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
if (auto* c = dynamic_cast<ComboBox*> (component))
{
props.add (new ComboItemsProperty (c, document));
props.add (new ComboEditableProperty (c, document));
props.add (new ComboJustificationProperty (c, document));
props.add (new ComboTextWhenNoneSelectedProperty (c, document));
props.add (new ComboTextWhenNoItemsProperty (c, document));
}
}
String getCreationParameters (GeneratedCode&, Component* component) override
{
return quotedString (component->getName(), false);
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
ComboBox* const c = dynamic_cast<ComboBox*> (component);
if (c == nullptr)
{
jassertfalse;
return;
}
String s;
s << memberVariableName << "->setEditableText (" << CodeHelpers::boolLiteral (c->isTextEditable()) << ");\n"
<< memberVariableName << "->setJustificationType (" << CodeHelpers::justificationToCode (c->getJustificationType()) << ");\n"
<< memberVariableName << "->setTextWhenNothingSelected (" << quotedString (c->getTextWhenNothingSelected(), code.shouldUseTransMacro()) << ");\n"
<< memberVariableName << "->setTextWhenNoChoicesAvailable (" << quotedString (c->getTextWhenNoChoicesAvailable(), code.shouldUseTransMacro()) << ");\n";
StringArray lines;
lines.addLines (c->getProperties() ["items"].toString());
int itemId = 1;
for (int i = 0; i < lines.size(); ++i)
{
if (lines[i].trim().isEmpty())
s << memberVariableName << "->addSeparator();\n";
else
s << memberVariableName << "->addItem ("
<< quotedString (lines[i], code.shouldUseTransMacro()) << ", " << itemId++ << ");\n";
}
if (needsCallback (component))
s << memberVariableName << "->addListener (this);\n";
s << '\n';
code.constructorCode += s;
}
void fillInGeneratedCode (Component* component, GeneratedCode& code) override
{
ComponentTypeHandler::fillInGeneratedCode (component, code);
if (needsCallback (component))
{
String& callback = code.getCallbackCode ("public juce::ComboBox::Listener",
"void",
"comboBoxChanged (juce::ComboBox* comboBoxThatHasChanged)",
true);
if (callback.trim().isNotEmpty())
callback << "else ";
const String memberVariableName (code.document->getComponentLayout()->getComponentMemberVariableName (component));
const String userCodeComment ("UserComboBoxCode_" + memberVariableName);
callback
<< "if (comboBoxThatHasChanged == " << memberVariableName << ".get())\n"
<< "{\n //[" << userCodeComment << "] -- add your combo box handling code here..\n //[/" << userCodeComment << "]\n}\n";
}
}
static void updateItems (ComboBox* c)
{
StringArray lines;
lines.addLines (c->getProperties() ["items"].toString());
c->clear();
int itemId = 1;
for (int i = 0; i < lines.size(); ++i)
{
if (lines[i].trim().isEmpty())
c->addSeparator();
else
c->addItem (lines[i], itemId++);
}
}
static bool needsCallback (Component*)
{
return true; // xxx should be configurable
}
private:
class ComboEditableProperty : public ComponentBooleanProperty <ComboBox>
{
public:
ComboEditableProperty (ComboBox* comp, JucerDocument& doc)
: ComponentBooleanProperty <ComboBox> ("editable", "Text is editable", "Text is editable", comp, doc)
{
}
void setState (bool newState)
{
document.perform (new ComboEditableChangeAction (component, *document.getComponentLayout(), newState),
"Change combo box editability");
}
bool getState() const
{
return component->isTextEditable();
}
private:
class ComboEditableChangeAction : public ComponentUndoableAction <ComboBox>
{
public:
ComboEditableChangeAction (ComboBox* const comp, ComponentLayout& l, const bool newState_)
: ComponentUndoableAction <ComboBox> (comp, l),
newState (newState_)
{
oldState = comp->isTextEditable();
}
bool perform()
{
showCorrectTab();
getComponent()->setEditableText (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setEditableText (oldState);
changed();
return true;
}
bool newState, oldState;
};
};
//==============================================================================
class ComboJustificationProperty : public JustificationProperty
{
public:
ComboJustificationProperty (ComboBox* comp, JucerDocument& doc)
: JustificationProperty ("text layout", false),
component (comp),
document (doc)
{
}
void setJustification (Justification newJustification)
{
document.perform (new ComboJustifyChangeAction (component, *document.getComponentLayout(), newJustification),
"Change combo box justification");
}
Justification getJustification() const { return component->getJustificationType(); }
private:
ComboBox* const component;
JucerDocument& document;
class ComboJustifyChangeAction : public ComponentUndoableAction <ComboBox>
{
public:
ComboJustifyChangeAction (ComboBox* const comp, ComponentLayout& l, Justification newState_)
: ComponentUndoableAction <ComboBox> (comp, l),
newState (newState_),
oldState (comp->getJustificationType())
{
}
bool perform()
{
showCorrectTab();
getComponent()->setJustificationType (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setJustificationType (oldState);
changed();
return true;
}
Justification newState, oldState;
};
};
//==============================================================================
class ComboItemsProperty : public ComponentTextProperty <ComboBox>
{
public:
ComboItemsProperty (ComboBox* comp, JucerDocument& doc)
: ComponentTextProperty <ComboBox> ("items", 10000, true, comp, doc)
{}
void setText (const String& newText) override
{
document.perform (new ComboItemsChangeAction (component, *document.getComponentLayout(), newText),
"Change combo box items");
}
String getText() const override
{
return component->getProperties() ["items"];
}
private:
class ComboItemsChangeAction : public ComponentUndoableAction <ComboBox>
{
public:
ComboItemsChangeAction (ComboBox* const comp, ComponentLayout& l, const String& newState_)
: ComponentUndoableAction <ComboBox> (comp, l),
newState (newState_)
{
oldState = comp->getProperties() ["items"];
}
bool perform()
{
showCorrectTab();
getComponent()->getProperties().set ("items", newState);
ComboBoxHandler::updateItems (getComponent());
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->getProperties().set ("items", oldState);
ComboBoxHandler::updateItems (getComponent());
changed();
return true;
}
String newState, oldState;
};
};
//==============================================================================
class ComboTextWhenNoneSelectedProperty : public ComponentTextProperty <ComboBox>
{
public:
ComboTextWhenNoneSelectedProperty (ComboBox* comp, JucerDocument& doc)
: ComponentTextProperty <ComboBox> ("text when none selected", 200, false, comp, doc)
{}
void setText (const String& newText) override
{
document.perform (new ComboNonSelTextChangeAction (component, *document.getComponentLayout(), newText),
"Change combo box text when nothing selected");
}
String getText() const override
{
return component->getTextWhenNothingSelected();
}
private:
class ComboNonSelTextChangeAction : public ComponentUndoableAction <ComboBox>
{
public:
ComboNonSelTextChangeAction (ComboBox* const comp, ComponentLayout& l, const String& newState_)
: ComponentUndoableAction <ComboBox> (comp, l),
newState (newState_)
{
oldState = comp->getTextWhenNothingSelected();
}
bool perform()
{
showCorrectTab();
getComponent()->setTextWhenNothingSelected (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setTextWhenNothingSelected (oldState);
changed();
return true;
}
String newState, oldState;
};
};
//==============================================================================
class ComboTextWhenNoItemsProperty : public ComponentTextProperty <ComboBox>
{
public:
ComboTextWhenNoItemsProperty (ComboBox* comp, JucerDocument& doc)
: ComponentTextProperty <ComboBox> ("text when no items", 200, false, comp, doc)
{}
void setText (const String& newText) override
{
document.perform (new ComboNoItemTextChangeAction (component, *document.getComponentLayout(), newText),
"Change combo box 'no items' text");
}
String getText() const override
{
return component->getTextWhenNoChoicesAvailable();
}
private:
class ComboNoItemTextChangeAction : public ComponentUndoableAction <ComboBox>
{
public:
ComboNoItemTextChangeAction (ComboBox* const comp, ComponentLayout& l, const String& newState_)
: ComponentUndoableAction <ComboBox> (comp, l),
newState (newState_)
{
oldState = comp->getTextWhenNoChoicesAvailable();
}
bool perform()
{
showCorrectTab();
getComponent()->setTextWhenNoChoicesAvailable (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setTextWhenNoChoicesAvailable (oldState);
changed();
return true;
}
String newState, oldState;
};
};
};

View File

@@ -0,0 +1,178 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_ComponentTypeHandler.h"
#include "jucer_ComponentUndoableAction.h"
#include "../Properties/jucer_ComponentTextProperty.h"
//==============================================================================
class ComponentNameProperty : public ComponentTextProperty <Component>
{
public:
ComponentNameProperty (Component* comp, JucerDocument& doc)
: ComponentTextProperty <Component> ("name", 40, false, comp, doc)
{
}
void setText (const String& newText) override
{
document.perform (new CompNameChangeAction (component, *document.getComponentLayout(), newText),
"Change component name");
}
String getText() const override
{
return component->getName();
}
private:
class CompNameChangeAction : public ComponentUndoableAction <Component>
{
public:
CompNameChangeAction (Component* const comp, ComponentLayout& l, const String& nm)
: ComponentUndoableAction <Component> (comp, l),
newName (nm), oldName (comp->getName())
{
}
bool perform()
{
showCorrectTab();
getComponent()->setName (newName);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setName (oldName);
changed();
return true;
}
String newName, oldName;
};
};
//==============================================================================
class ComponentMemberNameProperty : public ComponentTextProperty <Component>
{
public:
ComponentMemberNameProperty (Component* comp, JucerDocument& doc)
: ComponentTextProperty <Component> ("member name", 40, false, comp, doc)
{
}
void setText (const String& newText) override
{
document.perform (new CompMemberNameChangeAction (component, *document.getComponentLayout(), newText),
"Change component member name");
}
String getText() const override
{
return document.getComponentLayout()->getComponentMemberVariableName (component);
}
private:
class CompMemberNameChangeAction : public ComponentUndoableAction <Component>
{
public:
CompMemberNameChangeAction (Component* const comp, ComponentLayout& l, const String& nm)
: ComponentUndoableAction <Component> (comp, l),
newName (nm), oldName (layout.getComponentMemberVariableName (comp))
{
}
bool perform()
{
showCorrectTab();
layout.setComponentMemberVariableName (getComponent(), newName);
return true;
}
bool undo()
{
showCorrectTab();
layout.setComponentMemberVariableName (getComponent(), oldName);
return true;
}
String newName, oldName;
};
};
//==============================================================================
class ComponentVirtualClassProperty : public ComponentTextProperty <Component>
{
public:
ComponentVirtualClassProperty (Component* comp, JucerDocument& doc)
: ComponentTextProperty <Component> ("virtual class", 40, false, comp, doc)
{
}
void setText (const String& newText) override
{
document.perform (new CompVirtualClassChangeAction (component, *document.getComponentLayout(), newText),
"Change component virtual class name");
}
String getText() const override
{
return document.getComponentLayout()->getComponentVirtualClassName (component);
}
private:
class CompVirtualClassChangeAction : public ComponentUndoableAction <Component>
{
public:
CompVirtualClassChangeAction (Component* const comp, ComponentLayout& l, const String& nm)
: ComponentUndoableAction <Component> (comp, l),
newName (nm), oldName (layout.getComponentVirtualClassName (comp))
{
}
bool perform()
{
showCorrectTab();
layout.setComponentVirtualClassName (getComponent(), newName);
return true;
}
bool undo()
{
showCorrectTab();
layout.setComponentVirtualClassName (getComponent(), oldName);
return true;
}
String newName, oldName;
};
};

View File

@@ -0,0 +1,638 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "../../Application/jucer_Application.h"
#include "../jucer_ObjectTypes.h"
#include "../jucer_UtilityFunctions.h"
#include "../UI/jucer_JucerCommandIDs.h"
#include "../UI/jucer_ComponentOverlayComponent.h"
#include "jucer_ComponentNameProperty.h"
#include "../Properties/jucer_PositionPropertyBase.h"
#include "../Properties/jucer_ComponentColourProperty.h"
#include "../UI/jucer_TestComponent.h"
static String getTypeInfoName (const std::type_info& info)
{
#if JUCE_MSVC
return info.raw_name();
#else
return info.name();
#endif
}
//==============================================================================
ComponentTypeHandler::ComponentTypeHandler (const String& typeName_,
const String& className_,
const std::type_info& componentClass_,
const int defaultWidth_,
const int defaultHeight_)
: typeName (typeName_),
className (className_),
componentClassRawName (getTypeInfoName (componentClass_)),
defaultWidth (defaultWidth_),
defaultHeight (defaultHeight_)
{
}
Component* ComponentTypeHandler::createCopyOf (JucerDocument* document, Component& existing)
{
jassert (getHandlerFor (existing) == this);
Component* const newOne = createNewComponent (document);
std::unique_ptr<XmlElement> xml (createXmlFor (&existing, document->getComponentLayout()));
if (xml != nullptr)
restoreFromXml (*xml, newOne, document->getComponentLayout());
return newOne;
}
ComponentOverlayComponent* ComponentTypeHandler::createOverlayComponent (Component* child, ComponentLayout& layout)
{
return new ComponentOverlayComponent (child, layout);
}
static void dummyMenuCallback (int, int) {}
void ComponentTypeHandler::showPopupMenu (Component*, ComponentLayout& layout)
{
PopupMenu m;
ApplicationCommandManager* commandManager = &ProjucerApplication::getCommandManager();
m.addCommandItem (commandManager, JucerCommandIDs::toFront);
m.addCommandItem (commandManager, JucerCommandIDs::toBack);
m.addSeparator();
if (layout.getSelectedSet().getNumSelected() > 1)
{
m.addCommandItem (commandManager, JucerCommandIDs::alignTop);
m.addCommandItem (commandManager, JucerCommandIDs::alignRight);
m.addCommandItem (commandManager, JucerCommandIDs::alignBottom);
m.addCommandItem (commandManager, JucerCommandIDs::alignLeft);
m.addSeparator();
}
m.addCommandItem (commandManager, StandardApplicationCommandIDs::cut);
m.addCommandItem (commandManager, StandardApplicationCommandIDs::copy);
m.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
m.showMenuAsync (PopupMenu::Options(),
ModalCallbackFunction::create (dummyMenuCallback, 0));
}
JucerDocument* ComponentTypeHandler::findParentDocument (Component* component)
{
Component* p = component->getParentComponent();
while (p != nullptr)
{
if (JucerDocumentEditor* const ed = dynamic_cast<JucerDocumentEditor*> (p))
return ed->getDocument();
if (TestComponent* const t = dynamic_cast<TestComponent*> (p))
return t->getDocument();
p = p->getParentComponent();
}
return nullptr;
}
//==============================================================================
bool ComponentTypeHandler::canHandle (Component& component) const
{
return componentClassRawName == getTypeInfoName (typeid (component));
}
ComponentTypeHandler* ComponentTypeHandler::getHandlerFor (Component& component)
{
for (int i = 0; i < ObjectTypes::numComponentTypes; ++i)
if (ObjectTypes::componentTypeHandlers[i]->canHandle (component))
return ObjectTypes::componentTypeHandlers[i];
jassertfalse;
return nullptr;
}
ComponentTypeHandler* ComponentTypeHandler::getHandlerForXmlTag (const String& tagName)
{
for (int i = 0; i < ObjectTypes::numComponentTypes; ++i)
if (ObjectTypes::componentTypeHandlers[i]->getXmlTagName().equalsIgnoreCase (tagName))
return ObjectTypes::componentTypeHandlers[i];
return nullptr;
}
XmlElement* ComponentTypeHandler::createXmlFor (Component* comp, const ComponentLayout* layout)
{
XmlElement* e = new XmlElement (getXmlTagName());
e->setAttribute ("name", comp->getName());
e->setAttribute ("id", String::toHexString (getComponentId (comp)));
e->setAttribute ("memberName", comp->getProperties() ["memberName"].toString());
e->setAttribute ("virtualName", comp->getProperties() ["virtualName"].toString());
e->setAttribute ("explicitFocusOrder", comp->getExplicitFocusOrder());
RelativePositionedRectangle pos (getComponentPosition (comp));
pos.updateFromComponent (*comp, layout);
pos.applyToXml (*e);
if (SettableTooltipClient* const ttc = dynamic_cast<SettableTooltipClient*> (comp))
if (ttc->getTooltip().isNotEmpty())
e->setAttribute ("tooltip", ttc->getTooltip());
for (int i = 0; i < colours.size(); ++i)
{
if (comp->isColourSpecified (colours[i]->colourId))
{
e->setAttribute (colours[i]->xmlTagName,
comp->findColour (colours[i]->colourId).toString());
}
}
return e;
}
bool ComponentTypeHandler::restoreFromXml (const XmlElement& xml,
Component* comp,
const ComponentLayout* layout)
{
jassert (xml.hasTagName (getXmlTagName()));
if (! xml.hasTagName (getXmlTagName()))
return false;
comp->setName (xml.getStringAttribute ("name", comp->getName()));
setComponentId (comp, xml.getStringAttribute ("id").getHexValue64());
comp->getProperties().set ("memberName", xml.getStringAttribute ("memberName"));
comp->getProperties().set ("virtualName", xml.getStringAttribute ("virtualName"));
comp->setExplicitFocusOrder (xml.getIntAttribute ("explicitFocusOrder"));
RelativePositionedRectangle currentPos (getComponentPosition (comp));
currentPos.updateFromComponent (*comp, layout);
RelativePositionedRectangle rpr;
rpr.restoreFromXml (xml, currentPos);
jassert (layout != nullptr);
setComponentPosition (comp, rpr, layout);
if (SettableTooltipClient* const ttc = dynamic_cast<SettableTooltipClient*> (comp))
ttc->setTooltip (xml.getStringAttribute ("tooltip"));
for (int i = 0; i < colours.size(); ++i)
{
const String col (xml.getStringAttribute (colours[i]->xmlTagName, String()));
if (col.isNotEmpty())
comp->setColour (colours[i]->colourId, Colour::fromString (col));
}
return true;
}
//==============================================================================
int64 ComponentTypeHandler::getComponentId (Component* comp)
{
if (comp == nullptr)
return 0;
int64 compId = comp->getProperties() ["jucerCompId"].toString().getHexValue64();
if (compId == 0)
{
compId = Random::getSystemRandom().nextInt64();
setComponentId (comp, compId);
}
return compId;
}
void ComponentTypeHandler::setComponentId (Component* comp, const int64 newID)
{
jassert (comp != nullptr);
if (newID != 0)
comp->getProperties().set ("jucerCompId", String::toHexString (newID));
}
RelativePositionedRectangle ComponentTypeHandler::getComponentPosition (Component* comp)
{
RelativePositionedRectangle rp;
rp.rect = PositionedRectangle (comp->getProperties() ["pos"]);
rp.relativeToX = comp->getProperties() ["relativeToX"].toString().getHexValue64();
rp.relativeToY = comp->getProperties() ["relativeToY"].toString().getHexValue64();
rp.relativeToW = comp->getProperties() ["relativeToW"].toString().getHexValue64();
rp.relativeToH = comp->getProperties() ["relativeToH"].toString().getHexValue64();
return rp;
}
void ComponentTypeHandler::setComponentPosition (Component* comp,
const RelativePositionedRectangle& newPos,
const ComponentLayout* layout)
{
comp->getProperties().set ("pos", newPos.rect.toString());
comp->getProperties().set ("relativeToX", String::toHexString (newPos.relativeToX));
comp->getProperties().set ("relativeToY", String::toHexString (newPos.relativeToY));
comp->getProperties().set ("relativeToW", String::toHexString (newPos.relativeToW));
comp->getProperties().set ("relativeToH", String::toHexString (newPos.relativeToH));
comp->setBounds (newPos.getRectangle (Rectangle<int> (0, 0, comp->getParentWidth(), comp->getParentHeight()),
layout));
}
//==============================================================================
class TooltipProperty : public ComponentTextProperty <Component>
{
public:
TooltipProperty (Component* comp, JucerDocument& doc)
: ComponentTextProperty<Component> ("tooltip", 1024, true, comp, doc)
{
}
String getText() const override
{
SettableTooltipClient* ttc = dynamic_cast<SettableTooltipClient*> (component);
return ttc->getTooltip();
}
void setText (const String& newText) override
{
document.perform (new SetTooltipAction (component, *document.getComponentLayout(), newText),
"Change tooltip");
}
private:
class SetTooltipAction : public ComponentUndoableAction <Component>
{
public:
SetTooltipAction (Component* const comp, ComponentLayout& l, const String& newValue_)
: ComponentUndoableAction<Component> (comp, l),
newValue (newValue_)
{
SettableTooltipClient* ttc = dynamic_cast<SettableTooltipClient*> (comp);
jassert (ttc != nullptr);
oldValue = ttc->getTooltip();
}
bool perform()
{
showCorrectTab();
if (SettableTooltipClient* ttc = dynamic_cast<SettableTooltipClient*> (getComponent()))
{
ttc->setTooltip (newValue);
changed();
return true;
}
return false;
}
bool undo()
{
showCorrectTab();
if (SettableTooltipClient* ttc = dynamic_cast<SettableTooltipClient*> (getComponent()))
{
ttc->setTooltip (oldValue);
changed();
return true;
}
return false;
}
String newValue, oldValue;
};
};
//==============================================================================
class ComponentPositionProperty : public PositionPropertyBase
{
public:
ComponentPositionProperty (Component* comp,
JucerDocument& doc,
const String& name,
ComponentPositionDimension dimension_)
: PositionPropertyBase (comp, name, dimension_,
true, true,
doc.getComponentLayout()),
document (doc)
{
document.addChangeListener (this);
}
~ComponentPositionProperty()
{
document.removeChangeListener (this);
}
void setPosition (const RelativePositionedRectangle& newPos)
{
auto* l = document.getComponentLayout();
if (l->getSelectedSet().getNumSelected() > 1)
positionOtherSelectedComponents (ComponentTypeHandler::getComponentPosition (component), newPos);
l->setComponentPosition (component, newPos, true);
}
RelativePositionedRectangle getPosition() const
{
return ComponentTypeHandler::getComponentPosition (component);
}
private:
JucerDocument& document;
void positionOtherSelectedComponents (const RelativePositionedRectangle& oldPos, const RelativePositionedRectangle& newPos)
{
for (auto* s : document.getComponentLayout()->getSelectedSet())
{
if (s != component)
{
auto currentPos = ComponentTypeHandler::getComponentPosition (s);
auto diff = 0.0;
if (dimension == ComponentPositionDimension::componentX)
{
diff = newPos.rect.getX() - oldPos.rect.getX();
currentPos.rect.setX (currentPos.rect.getX() + diff);
}
else if (dimension == ComponentPositionDimension::componentY)
{
diff = newPos.rect.getY() - oldPos.rect.getY();
currentPos.rect.setY (currentPos.rect.getY() + diff);
}
else if (dimension == ComponentPositionDimension::componentWidth)
{
diff = newPos.rect.getWidth() - oldPos.rect.getWidth();
currentPos.rect.setWidth (currentPos.rect.getWidth() + diff);
}
else if (dimension == ComponentPositionDimension::componentHeight)
{
diff = newPos.rect.getHeight() - oldPos.rect.getHeight();
currentPos.rect.setHeight (currentPos.rect.getHeight() + diff);
}
document.getComponentLayout()->setComponentPosition (s, currentPos, true);
}
}
}
};
//==============================================================================
class FocusOrderProperty : public ComponentTextProperty <Component>
{
public:
FocusOrderProperty (Component* comp, JucerDocument& doc)
: ComponentTextProperty <Component> ("focus order", 8, false, comp, doc)
{
}
String getText() const override
{
return String (component->getExplicitFocusOrder());
}
void setText (const String& newText) override
{
document.perform (new SetFocusOrderAction (component, *document.getComponentLayout(), jmax (0, newText.getIntValue())),
"Change focus order");
}
private:
class SetFocusOrderAction : public ComponentUndoableAction <Component>
{
public:
SetFocusOrderAction (Component* const comp, ComponentLayout& l, const int newOrder_)
: ComponentUndoableAction <Component> (comp, l),
newValue (newOrder_)
{
oldValue = comp->getExplicitFocusOrder();
}
bool perform()
{
showCorrectTab();
getComponent()->setExplicitFocusOrder (newValue);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setExplicitFocusOrder (oldValue);
changed();
return true;
}
int newValue, oldValue;
};
};
//==============================================================================
void ComponentTypeHandler::getEditableProperties (Component* component,
JucerDocument& document,
Array<PropertyComponent*>& props,
bool multipleSelected)
{
if (! multipleSelected)
{
props.add (new ComponentMemberNameProperty (component, document));
props.add (new ComponentNameProperty (component, document));
props.add (new ComponentVirtualClassProperty (component, document));
if (dynamic_cast<SettableTooltipClient*> (component) != nullptr)
props.add (new TooltipProperty (component, document));
props.add (new FocusOrderProperty (component, document));
}
props.add (new ComponentPositionProperty (component, document, "x", ComponentPositionProperty::componentX));
props.add (new ComponentPositionProperty (component, document, "y", ComponentPositionProperty::componentY));
props.add (new ComponentPositionProperty (component, document, "width", ComponentPositionProperty::componentWidth));
props.add (new ComponentPositionProperty (component, document, "height", ComponentPositionProperty::componentHeight));
}
void ComponentTypeHandler::addPropertiesToPropertyPanel (Component* comp, JucerDocument& document,
PropertyPanel& panel, bool multipleSelected)
{
Array <PropertyComponent*> props;
getEditableProperties (comp, document, props, multipleSelected);
panel.addSection (getClassName (comp), props);
}
void ComponentTypeHandler::registerEditableColour (int colourId,
const String& colourIdCode,
const String& colourName, const String& xmlTagName)
{
ComponentColourInfo* const c = new ComponentColourInfo();
c->colourId = colourId;
c->colourIdCode = colourIdCode;
c->colourName = colourName;
c->xmlTagName = xmlTagName;
colours.add (c);
}
void ComponentTypeHandler::addColourProperties (Component* component,
JucerDocument& document,
Array<PropertyComponent*>& props)
{
for (int i = 0; i < colours.size(); ++i)
props.add (new ComponentColourIdProperty (component, document,
colours[i]->colourId,
colours[i]->colourName,
true));
}
String ComponentTypeHandler::getColourIntialisationCode (Component* component,
const String& objectName)
{
String s;
for (int i = 0; i < colours.size(); ++i)
{
if (component->isColourSpecified (colours[i]->colourId))
{
s << objectName << "->setColour ("
<< colours[i]->colourIdCode
<< ", "
<< CodeHelpers::colourToCode (component->findColour (colours[i]->colourId))
<< ");\n";
}
}
return s;
}
//==============================================================================
void ComponentTypeHandler::fillInGeneratedCode (Component* component, GeneratedCode& code)
{
const String memberVariableName (code.document->getComponentLayout()->getComponentMemberVariableName (component));
fillInMemberVariableDeclarations (code, component, memberVariableName);
fillInCreationCode (code, component, memberVariableName);
fillInDeletionCode (code, component, memberVariableName);
fillInResizeCode (code, component, memberVariableName);
}
void ComponentTypeHandler::fillInMemberVariableDeclarations (GeneratedCode& code, Component* component, const String& memberVariableName)
{
String clsName (component->getProperties() ["virtualName"].toString());
if (clsName.isNotEmpty())
clsName = build_tools::makeValidIdentifier (clsName, false, false, true);
else
clsName = getClassName (component);
code.privateMemberDeclarations
<< "std::unique_ptr<" << clsName << "> " << memberVariableName << ";\n";
}
void ComponentTypeHandler::fillInResizeCode (GeneratedCode& code, Component* component, const String& memberVariableName)
{
const RelativePositionedRectangle pos (getComponentPosition (component));
String x, y, w, h, r;
positionToCode (pos, code.document->getComponentLayout(), x, y, w, h);
r << memberVariableName << "->setBounds ("
<< x << ", " << y << ", " << w << ", " << h << ");\n";
if (pos.rect.isPositionAbsolute() && ! code.document->getComponentLayout()->isComponentPositionRelative (component))
code.constructorCode += r + "\n";
else
code.getCallbackCode (String(), "void", "resized()", false) += r;
}
String ComponentTypeHandler::getCreationParameters (GeneratedCode&, Component*)
{
return {};
}
void ComponentTypeHandler::fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName)
{
String params (getCreationParameters (code, component));
const String virtualName (component->getProperties() ["virtualName"].toString());
String s;
s << memberVariableName << ".reset (new ";
if (virtualName.isNotEmpty())
s << build_tools::makeValidIdentifier (virtualName, false, false, true);
else
s << getClassName (component);
if (params.isEmpty())
{
s << "());\n";
}
else
{
StringArray lines;
lines.addLines (params);
params = lines.joinIntoString ("\n" + String::repeatedString (" ", s.length() + 2));
s << " (" << params << "));\n";
}
s << "addAndMakeVisible (" << memberVariableName << ".get());\n";
if (auto* ttc = dynamic_cast<SettableTooltipClient*> (component))
{
if (ttc->getTooltip().isNotEmpty())
{
s << memberVariableName << "->setTooltip ("
<< quotedString (ttc->getTooltip(), code.shouldUseTransMacro())
<< ");\n";
}
}
if (component != nullptr && component->getExplicitFocusOrder() > 0)
s << memberVariableName << "->setExplicitFocusOrder ("
<< component->getExplicitFocusOrder()
<< ");\n";
code.constructorCode += s;
}
void ComponentTypeHandler::fillInDeletionCode (GeneratedCode& code, Component*,
const String& memberVariableName)
{
code.destructorCode
<< memberVariableName << " = nullptr;\n";
}

View File

@@ -0,0 +1,147 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
class ComponentOverlayComponent;
class ComponentLayout;
#include "../jucer_GeneratedCode.h"
#include "../UI/jucer_RelativePositionedRectangle.h"
//==============================================================================
/**
Base class for handlers that can understand the properties of all the component classes.
*/
class ComponentTypeHandler
{
public:
//==============================================================================
ComponentTypeHandler (const String& typeDescription_,
const String& className_,
const std::type_info& componentClass,
const int defaultWidth_,
const int defaultHeight_);
virtual ~ComponentTypeHandler() {}
//==============================================================================
virtual bool canHandle (Component& component) const;
static ComponentTypeHandler* getHandlerFor (Component& component);
//==============================================================================
virtual String getXmlTagName() const noexcept
{
if (className.startsWith ("juce::"))
return className.substring (6).toUpperCase();
return className.toUpperCase();
}
static ComponentTypeHandler* getHandlerForXmlTag (const String& tagName);
virtual XmlElement* createXmlFor (Component* component, const ComponentLayout* layout);
virtual bool restoreFromXml (const XmlElement& xml, Component* component, const ComponentLayout* layout);
virtual void getEditableProperties (Component* component,
JucerDocument& document,
Array<PropertyComponent*>& props,
bool multipleSelected);
virtual void addPropertiesToPropertyPanel (Component* component,
JucerDocument& document,
PropertyPanel& panel,
bool multipleSelected);
void registerEditableColour (int colourId,
const String& colourIdCode,
const String& colourName,
const String& xmlTagName);
#define registerColour(colourId, colourName, xmlTagName) \
registerEditableColour (colourId, #colourId, colourName, xmlTagName)
void addColourProperties (Component* component,
JucerDocument& document,
Array<PropertyComponent*>& props);
String getColourIntialisationCode (Component* component,
const String& objectName);
//==============================================================================
virtual Component* createNewComponent (JucerDocument*) = 0;
virtual Component* createCopyOf (JucerDocument*, Component& existing);
virtual ComponentOverlayComponent* createOverlayComponent (Component* child, ComponentLayout& layout);
virtual void showPopupMenu (Component* component,
ComponentLayout& layout);
//==============================================================================
// Code-generation methods:
virtual void fillInGeneratedCode (Component* component, GeneratedCode& code);
virtual void fillInMemberVariableDeclarations (GeneratedCode&, Component*, const String& memberVariableName);
virtual void fillInResizeCode (GeneratedCode&, Component*, const String& memberVariableName);
virtual void fillInCreationCode (GeneratedCode&, Component*, const String& memberVariableName);
virtual String getCreationParameters (GeneratedCode&, Component*);
virtual void fillInDeletionCode (GeneratedCode&, Component*, const String& memberVariableName);
//==============================================================================
const String& getTypeName() const noexcept { return typeName; }
virtual String getClassName (Component*) const { return className; }
int getDefaultWidth() const noexcept { return defaultWidth; }
int getDefaultHeight() const noexcept { return defaultHeight; }
static int64 getComponentId (Component* comp);
static void setComponentId (Component* comp, const int64 newID);
static RelativePositionedRectangle getComponentPosition (Component* comp);
static void setComponentPosition (Component* comp,
const RelativePositionedRectangle& newPos,
const ComponentLayout* layout);
static JucerDocument* findParentDocument (Component* component);
protected:
//==============================================================================
const String typeName, className, virtualClass, componentClassRawName;
int defaultWidth, defaultHeight;
struct ComponentColourInfo
{
int colourId;
String colourIdCode, colourName, xmlTagName;
};
OwnedArray<ComponentColourInfo> colours;
private:
JUCE_DECLARE_NON_COPYABLE (ComponentTypeHandler)
};

View File

@@ -0,0 +1,75 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../UI/jucer_JucerDocumentEditor.h"
//==============================================================================
template <class ComponentType>
class ComponentUndoableAction : public UndoableAction
{
public:
ComponentUndoableAction (ComponentType* const comp,
ComponentLayout& layout_)
: layout (layout_),
componentIndex (layout_.indexOfComponent (comp))
{
jassert (comp != nullptr);
jassert (componentIndex >= 0);
}
ComponentType* getComponent() const
{
ComponentType* const c = dynamic_cast<ComponentType*> (layout.getComponent (componentIndex));
jassert (c != nullptr);
return c;
}
int getSizeInUnits() { return 2; }
protected:
ComponentLayout& layout;
const int componentIndex;
void changed() const
{
jassert (layout.getDocument() != nullptr);
layout.getDocument()->changed();
}
void showCorrectTab() const
{
if (JucerDocumentEditor* const ed = JucerDocumentEditor::getActiveDocumentHolder())
ed->showLayout();
if (layout.getSelectedSet().getNumSelected() == 0)
if (ComponentType* const c = dynamic_cast<ComponentType*> (layout.getComponent (componentIndex)))
layout.getSelectedSet().selectOnly (getComponent());
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentUndoableAction)
};

View File

@@ -0,0 +1,241 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class GenericComponent : public Component
{
public:
GenericComponent()
: Component ("new component"),
actualClassName ("juce::Component")
{
}
void paint (Graphics& g) override
{
g.fillAll (Colours::white.withAlpha (0.25f));
g.setColour (Colours::black.withAlpha (0.5f));
g.drawRect (getLocalBounds());
g.drawLine (0.0f, 0.0f, (float) getWidth(), (float) getHeight());
g.drawLine (0.0f, (float) getHeight(), (float) getWidth(), 0.0f);
g.setFont (14.0f);
g.drawText (actualClassName, 0, 0, getWidth(), getHeight() / 2, Justification::centred, true);
}
void setClassName (const String& newName)
{
if (actualClassName != newName)
{
actualClassName = newName;
repaint();
}
}
void setParams (const String& newParams)
{
if (constructorParams != newParams)
{
constructorParams = newParams;
repaint();
}
}
String actualClassName, constructorParams;
};
//==============================================================================
class GenericComponentHandler : public ComponentTypeHandler
{
public:
GenericComponentHandler()
: ComponentTypeHandler ("Generic Component", "GenericComponent", typeid (GenericComponent), 150, 24)
{}
Component* createNewComponent (JucerDocument*) override
{
return new GenericComponent();
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
XmlElement* e = ComponentTypeHandler::createXmlFor (comp, layout);
e->setAttribute ("class", ((GenericComponent*) comp)->actualClassName);
e->setAttribute ("params", ((GenericComponent*) comp)->constructorParams);
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
return false;
((GenericComponent*) comp)->actualClassName = xml.getStringAttribute ("class", "juce::Component");
((GenericComponent*) comp)->constructorParams = xml.getStringAttribute ("params", String());
return true;
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ComponentTypeHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
props.add (new GenericCompClassProperty (dynamic_cast<GenericComponent*> (component), document));
props.add (new GenericCompParamsProperty (dynamic_cast<GenericComponent*> (component), document));
}
String getClassName (Component* comp) const override
{
return static_cast<GenericComponent*> (comp)->actualClassName;
}
String getCreationParameters (GeneratedCode&, Component* comp) override
{
return static_cast<GenericComponent*> (comp)->constructorParams;
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
if (component->getName().isNotEmpty())
code.constructorCode
<< memberVariableName << "->setName ("
<< quotedString (component->getName(), false)
<< ");\n\n";
else
code.constructorCode << "\n";
}
private:
class GenericCompClassProperty : public ComponentTextProperty <GenericComponent>
{
public:
GenericCompClassProperty (GenericComponent* comp, JucerDocument& doc)
: ComponentTextProperty <GenericComponent> ("class", 300, false, comp, doc)
{
}
void setText (const String& newText) override
{
document.perform (new GenericCompClassChangeAction (component, *document.getComponentLayout(),
build_tools::makeValidIdentifier (newText, false, false, true)),
"Change generic component class");
}
String getText() const override
{
return component->actualClassName;
}
private:
class GenericCompClassChangeAction : public ComponentUndoableAction <GenericComponent>
{
public:
GenericCompClassChangeAction (GenericComponent* const comp, ComponentLayout& l, const String& newState_)
: ComponentUndoableAction <GenericComponent> (comp, l),
newState (newState_)
{
oldState = comp->actualClassName;
}
bool perform()
{
showCorrectTab();
getComponent()->setClassName (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setClassName (oldState);
changed();
return true;
}
String newState, oldState;
};
};
class GenericCompParamsProperty : public ComponentTextProperty <GenericComponent>
{
public:
GenericCompParamsProperty (GenericComponent* comp, JucerDocument& doc)
: ComponentTextProperty <GenericComponent> ("constructor params", 1024, true, comp, doc)
{
}
void setText (const String& newText) override
{
document.perform (new GenericCompParamsChangeAction (component, *document.getComponentLayout(), newText),
"Change generic component class");
}
String getText() const override
{
return component->constructorParams;
}
private:
class GenericCompParamsChangeAction : public ComponentUndoableAction <GenericComponent>
{
public:
GenericCompParamsChangeAction (GenericComponent* const comp, ComponentLayout& l, const String& newState_)
: ComponentUndoableAction <GenericComponent> (comp, l),
newState (newState_)
{
oldState = comp->constructorParams;
}
bool perform()
{
showCorrectTab();
getComponent()->setParams (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setParams (oldState);
changed();
return true;
}
String newState, oldState;
};
};
};

View File

@@ -0,0 +1,237 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class GroupComponentHandler : public ComponentTypeHandler
{
public:
GroupComponentHandler()
: ComponentTypeHandler ("Group Box", "juce::GroupComponent", typeid (GroupComponent), 200, 150)
{
registerColour (juce::GroupComponent::outlineColourId, "outline", "outlinecol");
registerColour (juce::GroupComponent::textColourId, "text", "textcol");
}
Component* createNewComponent (JucerDocument*) override
{
return new GroupComponent ("new group", "group");
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
GroupComponent* const g = (GroupComponent*) comp;
XmlElement* e = ComponentTypeHandler::createXmlFor (comp, layout);
e->setAttribute ("title", g->getText());
GroupComponent defaultComp;
if (g->getTextLabelPosition().getFlags() != defaultComp.getTextLabelPosition().getFlags())
e->setAttribute ("textpos", g->getTextLabelPosition().getFlags());
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
GroupComponent* const g = (GroupComponent*) comp;
if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
return false;
g->setText (xml.getStringAttribute ("title", g->getText()));
g->setTextLabelPosition (Justification (xml.getIntAttribute ("textpos", g->getTextLabelPosition().getFlags())));
return true;
}
String getCreationParameters (GeneratedCode& code, Component* component) override
{
GroupComponent* g = dynamic_cast<GroupComponent*> (component);
return quotedString (component->getName(), false)
+ ",\n"
+ quotedString (g->getText(), code.shouldUseTransMacro());
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
GroupComponent* const g = dynamic_cast<GroupComponent*> (component);
String s;
GroupComponent defaultComp;
if (g->getTextLabelPosition().getFlags() != defaultComp.getTextLabelPosition().getFlags())
{
s << memberVariableName << "->setTextLabelPosition ("
<< CodeHelpers::justificationToCode (g->getTextLabelPosition())
<< ");\n";
}
s << getColourIntialisationCode (component, memberVariableName)
<< '\n';
code.constructorCode += s;
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ComponentTypeHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
if (auto* gc = dynamic_cast<GroupComponent*> (component))
{
props.add (new GroupTitleProperty (gc, document));
props.add (new GroupJustificationProperty (gc, document));
}
addColourProperties (component, document, props);
}
private:
//==============================================================================
class GroupTitleProperty : public ComponentTextProperty <GroupComponent>
{
public:
GroupTitleProperty (GroupComponent* comp, JucerDocument& doc)
: ComponentTextProperty <GroupComponent> ("text", 200, false, comp, doc)
{}
void setText (const String& newText) override
{
document.perform (new GroupTitleChangeAction (component, *document.getComponentLayout(), newText),
"Change group title");
}
String getText() const override
{
return component->getText();
}
private:
class GroupTitleChangeAction : public ComponentUndoableAction <GroupComponent>
{
public:
GroupTitleChangeAction (GroupComponent* const comp, ComponentLayout& l, const String& newName_)
: ComponentUndoableAction <GroupComponent> (comp, l),
newName (newName_)
{
oldName = comp->getText();
}
bool perform()
{
showCorrectTab();
getComponent()->setText (newName);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setText (oldName);
changed();
return true;
}
String newName, oldName;
};
};
//==============================================================================
class GroupJustificationProperty : public JustificationProperty,
private ChangeListener
{
public:
GroupJustificationProperty (GroupComponent* const group_, JucerDocument& doc)
: JustificationProperty ("layout", true),
group (group_),
document (doc)
{
document.addChangeListener (this);
}
~GroupJustificationProperty() override
{
document.removeChangeListener (this);
}
void setJustification (Justification newJustification) override
{
document.perform (new GroupJustifyChangeAction (group, *document.getComponentLayout(), newJustification),
"Change text label position");
}
Justification getJustification() const override
{
return group->getTextLabelPosition();
}
private:
void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
GroupComponent* const group;
JucerDocument& document;
class GroupJustifyChangeAction : public ComponentUndoableAction <GroupComponent>
{
public:
GroupJustifyChangeAction (GroupComponent* const comp, ComponentLayout& l, Justification newState_)
: ComponentUndoableAction <GroupComponent> (comp, l),
newState (newState_),
oldState (comp->getTextLabelPosition())
{
}
bool perform()
{
showCorrectTab();
getComponent()->setTextLabelPosition (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setTextLabelPosition (oldState);
changed();
return true;
}
Justification newState, oldState;
};
};
};

View File

@@ -0,0 +1,149 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class HyperlinkButtonHandler : public ButtonHandler
{
public:
HyperlinkButtonHandler()
: ButtonHandler ("Hyperlink Button", "juce::HyperlinkButton", typeid (HyperlinkButton), 150, 24)
{
registerColour (juce::HyperlinkButton::textColourId, "text", "textCol");
}
Component* createNewComponent (JucerDocument*) override
{
HyperlinkButton* hb = new HyperlinkButton ("new hyperlink", URL ("http://www.juce.com"));
setNeedsButtonListener (hb, false);
return hb;
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ButtonHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
if (auto* hb = dynamic_cast<HyperlinkButton*> (component))
props.add (new HyperlinkURLProperty (hb, document));
addColourProperties (component, document, props);
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
HyperlinkButton* const hb = (HyperlinkButton*) comp;
XmlElement* const e = ButtonHandler::createXmlFor (comp, layout);
e->setAttribute ("url", hb->getURL().toString (false));
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
HyperlinkButton* const hb = (HyperlinkButton*) comp;
if (! ButtonHandler::restoreFromXml (xml, comp, layout))
return false;
hb->setURL (URL (xml.getStringAttribute ("url", hb->getURL().toString (false))));
return true;
}
String getCreationParameters (GeneratedCode& code, Component* comp) override
{
HyperlinkButton* const hb = dynamic_cast<HyperlinkButton*> (comp);
return quotedString (hb->getButtonText(), code.shouldUseTransMacro())
+ ",\nURL ("
+ quotedString (hb->getURL().toString (false), false)
+ ")";
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ButtonHandler::fillInCreationCode (code, component, memberVariableName);
code.constructorCode << getColourIntialisationCode (component, memberVariableName)
<< '\n';
}
private:
//==============================================================================
class HyperlinkURLProperty : public ComponentTextProperty <HyperlinkButton>
{
public:
HyperlinkURLProperty (HyperlinkButton* comp, JucerDocument& doc)
: ComponentTextProperty <HyperlinkButton> ("URL", 512, false, comp, doc)
{}
void setText (const String& newText) override
{
document.perform (new HyperlinkURLChangeAction (component, *document.getComponentLayout(), URL::createWithoutParsing (newText)),
"Change hyperlink URL");
}
String getText() const override
{
return component->getURL().toString (false);
}
private:
class HyperlinkURLChangeAction : public ComponentUndoableAction <HyperlinkButton>
{
public:
HyperlinkURLChangeAction (HyperlinkButton* const comp, ComponentLayout& l, const URL& newState_)
: ComponentUndoableAction <HyperlinkButton> (comp, l),
newState (newState_)
{
oldState = comp->getURL();
}
bool perform()
{
showCorrectTab();
getComponent()->setURL (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setURL (oldState);
changed();
return true;
}
URL newState, oldState;
};
};
};

View File

@@ -0,0 +1,526 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ImageButtonHandler : public ButtonHandler
{
public:
enum ImageRole
{
normalImage = 0,
overImage = 1,
downImage = 2
};
//==============================================================================
ImageButtonHandler()
: ButtonHandler ("Image Button", "juce::ImageButton", typeid (ImageButton), 150, 24)
{
}
Component* createNewComponent (JucerDocument*) override
{
return new ImageButton ("new button");
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ButtonHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
addColourProperties (component, document, props);
if (auto* ib = dynamic_cast<ImageButton*> (component))
{
auto& layout = *document.getComponentLayout();
props.add (new ImageButtonProportionProperty (layout, ib));
props.add (new ImageButtonResourceProperty (layout, ib, normalImage, "normal image"));
props.add (new ImageButtonOpacityProperty (layout, ib, "opacity", normalImage));
props.add (new ImageButtonColourProperty (layout, ib, "overlay col.", normalImage));
props.add (new ImageButtonResourceProperty (layout, ib, overImage, "over image"));
props.add (new ImageButtonOpacityProperty (layout, ib, "opacity", overImage));
props.add (new ImageButtonColourProperty (layout, ib, "overlay col.", overImage));
props.add (new ImageButtonResourceProperty (layout, ib, downImage, "down image"));
props.add (new ImageButtonOpacityProperty (layout, ib, "opacity", downImage));
props.add (new ImageButtonColourProperty (layout, ib, "overlay col.", downImage));
}
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
XmlElement* e = ButtonHandler::createXmlFor (comp, layout);
ImageButton* const ib = (ImageButton*) comp;
e->setAttribute ("keepProportions", doesImageKeepProportions (ib));
e->setAttribute ("resourceNormal", getImageResource (ib, normalImage));
e->setAttribute ("opacityNormal", getImageOpacity (ib, normalImage));
e->setAttribute ("colourNormal", getImageColour (ib, normalImage).toString());
e->setAttribute ("resourceOver", getImageResource (ib, overImage));
e->setAttribute ("opacityOver", getImageOpacity (ib, overImage));
e->setAttribute ("colourOver", getImageColour (ib, overImage).toString());
e->setAttribute ("resourceDown", getImageResource (ib, downImage));
e->setAttribute ("opacityDown", getImageOpacity (ib, downImage));
e->setAttribute ("colourDown", getImageColour (ib, downImage).toString());
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
if (! ButtonHandler::restoreFromXml (xml, comp, layout))
return false;
ImageButton* const ib = (ImageButton*) comp;
ComponentLayout& l = const_cast<ComponentLayout&> (*layout);
setImageKeepProportions (l, ib, xml.getBoolAttribute ("keepProportions", true), false);
setImageResource (l, ib, normalImage, xml.getStringAttribute ("resourceNormal", String()), false);
setImageOpacity (l, ib, normalImage, (float) xml.getDoubleAttribute ("opacityNormal", 1.0f), false);
setImageColour (l, ib, normalImage, Colour::fromString (xml.getStringAttribute ("colourNormal", "0")), false);
setImageResource (l, ib, overImage, xml.getStringAttribute ("resourceOver", String()), false);
setImageOpacity (l, ib, overImage, (float) xml.getDoubleAttribute ("opacityOver", 1.0f), false);
setImageColour (l, ib, overImage, Colour::fromString (xml.getStringAttribute ("colourOver", "0")), false);
setImageResource (l, ib, downImage, xml.getStringAttribute ("resourceDown", String()), false);
setImageOpacity (l, ib, downImage, (float) xml.getDoubleAttribute ("opacityDown", 1.0f), false);
setImageColour (l, ib, downImage, Colour::fromString (xml.getStringAttribute ("colourDown", "0")), false);
return true;
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ButtonHandler::fillInCreationCode (code, component, memberVariableName);
ImageButton* const ib = dynamic_cast<ImageButton*> (component);
String s;
s << getColourIntialisationCode (component, memberVariableName)
<< '\n';
const String indent (String::repeatedString (" ", memberVariableName.length() + 13));
s << memberVariableName << "->setImages (false, true, "
<< CodeHelpers::boolLiteral (doesImageKeepProportions (ib)) << ",\n"
<< indent
<< getImageCreationCode (ib, normalImage) << ", "
<< CodeHelpers::floatLiteral (getImageOpacity (ib, normalImage), 3) << ", "
<< CodeHelpers::colourToCode (getImageColour (ib, normalImage)) << ",\n"
<< indent
<< getImageCreationCode (ib, overImage) << ", "
<< CodeHelpers::floatLiteral (getImageOpacity (ib, overImage), 3) << ", "
<< CodeHelpers::colourToCode (getImageColour (ib, overImage)) << ",\n"
<< indent
<< getImageCreationCode (ib, downImage) << ", "
<< CodeHelpers::floatLiteral (getImageOpacity (ib, downImage), 3) << ", "
<< CodeHelpers::colourToCode (getImageColour (ib, downImage))
<< ");\n";
code.constructorCode += s;
}
static String getImageCreationCode (ImageButton* ib, const ImageRole role)
{
const String resName (getImageResource (ib, role));
if (resName.isEmpty())
return "juce::Image()";
return "juce::ImageCache::getFromMemory (" + resName + ", " + resName + "Size)";
}
//==============================================================================
class ImageButtonResourceProperty : public ImageResourceProperty<ImageButton>
{
public:
ImageButtonResourceProperty (ComponentLayout& layout_, ImageButton* const owner_, const ImageRole role_, const String& name)
: ImageResourceProperty<ImageButton> (*layout_.getDocument(), owner_, name, true),
role (role_),
layout (layout_)
{
}
void setResource (const String& newName)
{
setImageResource (layout, element, role, newName, true);
}
String getResource() const
{
return getImageResource (element, role);
}
private:
const ImageRole role;
ComponentLayout& layout;
};
class SetImageResourceAction : public ComponentUndoableAction<ImageButton>
{
public:
SetImageResourceAction (ImageButton* const button,
ComponentLayout& layout_,
const ImageRole role_,
const String& newResource_)
: ComponentUndoableAction<ImageButton> (button, layout_),
newResource (newResource_),
role (role_)
{
oldResource = ImageButtonHandler::getImageResource (button, role_);
}
bool perform() override
{
showCorrectTab();
ImageButtonHandler::setImageResource (layout, getComponent(), role, newResource, false);
return true;
}
bool undo() override
{
showCorrectTab();
ImageButtonHandler::setImageResource (layout, getComponent(), role, oldResource, false);
return true;
}
private:
String newResource, oldResource;
const ImageRole role;
};
//==============================================================================
static void setImageResource (ComponentLayout& layout, ImageButton* button, const ImageRole role, const String& newName, const bool undoable)
{
jassert (role < 3);
if (role < 3 && getImageResource (button, role) != newName)
{
if (undoable)
{
layout.getDocument()->perform (new SetImageResourceAction (button, layout, role, newName),
"Change image resource");
}
else
{
button->getProperties().set ("resource" + String ((int) role), newName);
updateButtonImages (*layout.getDocument(), button);
layout.changed();
}
}
}
static String getImageResource (ImageButton* button, const ImageRole role)
{
jassert (role < 3);
return button->getProperties() ["resource" + String ((int) role)].toString();
}
//==============================================================================
class SetImageKeepsPropAction : public ComponentUndoableAction<ImageButton>
{
public:
SetImageKeepsPropAction (ImageButton* const button,
ComponentLayout& layout_,
const bool newState_)
: ComponentUndoableAction<ImageButton> (button, layout_),
newState (newState_)
{
oldState = ImageButtonHandler::doesImageKeepProportions (button);
}
bool perform() override
{
showCorrectTab();
ImageButtonHandler::setImageKeepProportions (layout, getComponent(), newState, false);
return true;
}
bool undo() override
{
showCorrectTab();
ImageButtonHandler::setImageKeepProportions (layout, getComponent(), oldState, false);
return true;
}
private:
bool newState, oldState;
};
static bool doesImageKeepProportions (ImageButton* button)
{
return button->getProperties().getWithDefault ("keepImageProp", true);
}
static void setImageKeepProportions (ComponentLayout& layout, ImageButton* button, const bool newState, const bool undoable)
{
if (undoable)
{
layout.perform (new SetImageKeepsPropAction (button, layout, newState), "change imagebutton proportion mode");
}
else
{
button->getProperties().set ("keepImageProp", newState);
updateButtonImages (*layout.getDocument(), button);
layout.changed();
}
}
class ImageButtonProportionProperty : public ComponentBooleanProperty<ImageButton>
{
public:
ImageButtonProportionProperty (ComponentLayout& layout_, ImageButton* const owner_)
: ComponentBooleanProperty<ImageButton> ("proportional", "maintain image proportions", "scale to fit",
owner_, *layout_.getDocument()),
layout (layout_)
{
}
void setState (bool newState)
{
setImageKeepProportions (layout, component, newState, true);
}
bool getState() const
{
return doesImageKeepProportions (component);
}
private:
ComponentLayout& layout;
};
//==============================================================================
class SetImageOpacityAction : public ComponentUndoableAction<ImageButton>
{
public:
SetImageOpacityAction (ImageButton* const button,
ComponentLayout& layout_,
const ImageRole role_,
const float newState_)
: ComponentUndoableAction<ImageButton> (button, layout_),
role (role_),
newState (newState_)
{
oldState = ImageButtonHandler::getImageOpacity (button, role_);
}
bool perform() override
{
showCorrectTab();
ImageButtonHandler::setImageOpacity (layout, getComponent(), role, newState, false);
return true;
}
bool undo() override
{
showCorrectTab();
ImageButtonHandler::setImageOpacity (layout, getComponent(), role, oldState, false);
return true;
}
private:
const ImageRole role;
float newState, oldState;
};
static float getImageOpacity (ImageButton* button, const ImageRole role)
{
return (float) button->getProperties().getWithDefault ("imageOpacity" + String ((int) role), 1.0f);
}
static void setImageOpacity (ComponentLayout& layout, ImageButton* button, const ImageRole role, const float opacity, const bool undoable)
{
if (undoable)
{
layout.perform (new SetImageOpacityAction (button, layout, role, opacity), "change imagebutton opacity");
}
else
{
button->getProperties().set ("imageOpacity" + String ((int) role), opacity);
updateButtonImages (*layout.getDocument(), button);
layout.changed();
}
}
class ImageButtonOpacityProperty : public SliderPropertyComponent
{
public:
ImageButtonOpacityProperty (ComponentLayout& layout_, ImageButton* const owner_,
const String& name, const ImageRole role_)
: SliderPropertyComponent (name, 0.0, 1.0, 0.0),
owner (owner_),
layout (layout_),
role (role_)
{
}
void setValue (double newValue) override
{
setImageOpacity (layout, owner, role, (float) newValue, true);
}
double getValue() const override
{
return getImageOpacity (owner, role);
}
private:
ImageButton* const owner;
ComponentLayout& layout;
const ImageRole role;
};
//==============================================================================
class SetImageColourAction : public ComponentUndoableAction<ImageButton>
{
public:
SetImageColourAction (ImageButton* const button,
ComponentLayout& layout_,
const ImageRole role_,
Colour newState_)
: ComponentUndoableAction<ImageButton> (button, layout_),
role (role_),
newState (newState_)
{
oldState = ImageButtonHandler::getImageColour (button, role_);
}
bool perform() override
{
showCorrectTab();
ImageButtonHandler::setImageColour (layout, getComponent(), role, newState, false);
return true;
}
bool undo() override
{
showCorrectTab();
ImageButtonHandler::setImageColour (layout, getComponent(), role, oldState, false);
return true;
}
private:
const ImageRole role;
Colour newState, oldState;
};
static Colour getImageColour (ImageButton* button, const ImageRole role)
{
return Colour::fromString (button->getProperties().getWithDefault ("imageColour" + String ((int) role), "0").toString());
}
static void setImageColour (ComponentLayout& layout, ImageButton* button,
const ImageRole role, Colour colour, const bool undoable)
{
if (undoable)
{
layout.perform (new SetImageColourAction (button, layout, role, colour), "change imagebutton colour");
}
else
{
button->getProperties().set ("imageColour" + String ((int) role), colour.toString());
updateButtonImages (*layout.getDocument(), button);
layout.changed();
}
}
class ImageButtonColourProperty : public JucerColourPropertyComponent,
private ChangeListener
{
public:
ImageButtonColourProperty (ComponentLayout& layout_, ImageButton* const owner_,
const String& name, const ImageRole role_)
: JucerColourPropertyComponent (name, false),
owner (owner_),
layout (layout_),
role (role_)
{
layout_.getDocument()->addChangeListener (this);
}
~ImageButtonColourProperty() override
{
layout.getDocument()->removeChangeListener (this);
}
void setColour (Colour newColour) override
{
setImageColour (layout, owner, role, newColour, true);
}
Colour getColour() const override
{
return getImageColour (owner, role);
}
void resetToDefault() override {}
private:
void changeListenerCallback (ChangeBroadcaster*) override
{
refresh();
}
ImageButton* const owner;
ComponentLayout& layout;
const ImageRole role;
};
//==============================================================================
static void updateButtonImages (JucerDocument& document, ImageButton* const ib)
{
Image norm = document.getResources().getImageFromCache (getImageResource (ib, normalImage));
Image over = document.getResources().getImageFromCache (getImageResource (ib, overImage));
Image down = document.getResources().getImageFromCache (getImageResource (ib, downImage));
ib->setImages (false, true, doesImageKeepProportions (ib),
norm,
getImageOpacity (ib, normalImage),
getImageColour (ib, normalImage),
over,
getImageOpacity (ib, overImage),
getImageColour (ib, overImage),
down,
getImageOpacity (ib, downImage),
getImageColour (ib, downImage));
}
};

View File

@@ -0,0 +1,273 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../UI/jucer_TestComponent.h"
#include "../Properties/jucer_FilePropertyComponent.h"
#include "../Properties/jucer_ComponentTextProperty.h"
#include "jucer_ComponentUndoableAction.h"
#include "../../Project/UI/jucer_ProjectContentComponent.h"
//==============================================================================
class JucerComponentHandler : public ComponentTypeHandler
{
public:
JucerComponentHandler()
: ComponentTypeHandler ("Projucer Component", "xxx",
typeid (TestComponent), 300, 200)
{}
Component* createNewComponent (JucerDocument* doc) override
{
return new TestComponent (doc, nullptr, false);
}
String getXmlTagName() const noexcept override { return "JUCERCOMP"; }
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
TestComponent* const tc = dynamic_cast<TestComponent*> (comp);
XmlElement* e = ComponentTypeHandler::createXmlFor (comp, layout);
e->setAttribute ("sourceFile", tc->getFilename());
e->setAttribute ("constructorParams", tc->getConstructorParams());
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
auto tc = dynamic_cast<TestComponent*> (comp);
if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
return false;
tc->setFilename (xml.getStringAttribute ("sourceFile", tc->getFilename()));
tc->setConstructorParams (xml.getStringAttribute ("constructorParams"));
return true;
}
String getClassName (Component* comp) const override
{
auto tc = dynamic_cast<TestComponent*> (comp);
String jucerCompClassName;
if (tc->getDocument() != nullptr)
jucerCompClassName = tc->getDocument()->getClassName();
if (jucerCompClassName.isEmpty())
jucerCompClassName = "juce::Component";
return jucerCompClassName;
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ComponentTypeHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
if (auto tc = dynamic_cast<TestComponent*> (component))
{
props.add (new JucerCompFileProperty (tc, document));
props.add (new ConstructorParamsProperty (tc, document));
props.add (new JucerCompOpenDocProperty (tc));
}
}
String getCreationParameters (GeneratedCode&, Component* component) override
{
return dynamic_cast<TestComponent*> (component)->getConstructorParams().trim();
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
if (auto tc = dynamic_cast<TestComponent*> (component))
code.includeFilesH.add (tc->findFile().withFileExtension (".h"));
else
jassertfalse;
}
//==============================================================================
class JucerCompFileChangeAction : public ComponentUndoableAction <TestComponent>
{
public:
JucerCompFileChangeAction (TestComponent* const comp, ComponentLayout& l, const String& newState_)
: ComponentUndoableAction <TestComponent> (comp, l),
newState (newState_)
{
oldState = comp->getFilename();
}
bool perform()
{
showCorrectTab();
getComponent()->setFilename (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setFilename (oldState);
changed();
return true;
}
String newState, oldState;
};
static void setJucerComponentFile (JucerDocument& document, TestComponent* comp, const String& newFilename)
{
jassert (comp != nullptr);
if (comp != nullptr)
document.perform (new JucerCompFileChangeAction (comp, *document.getComponentLayout(), newFilename),
"Change Projucer component file");
}
private:
//==============================================================================
class JucerCompFileProperty : public FilePropertyComponent,
private ChangeListener
{
public:
JucerCompFileProperty (TestComponent* const comp, JucerDocument& doc)
: FilePropertyComponent ("Jucer file", false, true),
component (comp),
document (doc)
{
document.addChangeListener (this);
}
~JucerCompFileProperty() override
{
document.removeChangeListener (this);
}
void setFile (const File& newFile) override
{
setJucerComponentFile (document, component,
newFile.getRelativePathFrom (document.getCppFile().getParentDirectory())
.replaceCharacter ('\\', '/'));
}
File getFile() const override
{
return component->findFile();
}
private:
void changeListenerCallback (ChangeBroadcaster*) override
{
refresh();
}
TestComponent* const component;
JucerDocument& document;
};
//==============================================================================
struct JucerCompOpenDocProperty : public ButtonPropertyComponent
{
JucerCompOpenDocProperty (TestComponent* const c)
: ButtonPropertyComponent ("edit", false),
component (c)
{
}
void buttonClicked()
{
if (ProjectContentComponent* const pcc = findParentComponentOfClass<ProjectContentComponent>())
pcc->showEditorForFile (component->findFile(), true);
}
String getButtonText() const
{
return "Open file for editing";
}
TestComponent* const component;
};
//==============================================================================
struct ConstructorParamsProperty : public ComponentTextProperty <TestComponent>
{
ConstructorParamsProperty (TestComponent* comp, JucerDocument& doc)
: ComponentTextProperty <TestComponent> ("constructor params", 512, false, comp, doc)
{
}
void setText (const String& newText) override
{
document.perform (new ConstructorParamChangeAction (component, *document.getComponentLayout(), newText),
"Change Viewport content constructor params");
}
String getText() const override
{
return component->getConstructorParams();
}
private:
struct ConstructorParamChangeAction : public ComponentUndoableAction <TestComponent>
{
ConstructorParamChangeAction (TestComponent* const comp, ComponentLayout& l, const String& newValue_)
: ComponentUndoableAction <TestComponent> (comp, l),
newValue (newValue_)
{
oldValue = comp->getConstructorParams();
}
bool perform()
{
showCorrectTab();
getComponent()->setConstructorParams (newValue);
changed();
layout.getDocument()->refreshAllPropertyComps();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setConstructorParams (oldValue);
changed();
layout.getDocument()->refreshAllPropertyComps();
return true;
}
String newValue, oldValue;
};
};
};

View File

@@ -0,0 +1,774 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class LabelHandler : public ComponentTypeHandler
{
public:
LabelHandler()
: ComponentTypeHandler ("Label", "juce::Label", typeid (Label), 150, 24)
{
registerColour (juce::Label::backgroundColourId, "background", "bkgCol");
registerColour (juce::Label::textColourId, "text", "textCol");
registerColour (juce::Label::outlineColourId, "outline", "outlineCol");
registerColour (juce::TextEditor::textColourId, "editor text", "edTextCol");
registerColour (juce::TextEditor::backgroundColourId, "editor bkg", "edBkgCol");
registerColour (juce::TextEditor::highlightColourId, "highlight", "hiliteCol");
}
Component* createNewComponent (JucerDocument*) override
{
return new Label ("new label", "label text");
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
Label* const l = dynamic_cast<Label*> (comp);
XmlElement* e = ComponentTypeHandler::createXmlFor (comp, layout);
e->setAttribute ("labelText", l->getText());
e->setAttribute ("editableSingleClick", l->isEditableOnSingleClick());
e->setAttribute ("editableDoubleClick", l->isEditableOnDoubleClick());
e->setAttribute ("focusDiscardsChanges", l->doesLossOfFocusDiscardChanges());
e->setAttribute ("fontname", l->getProperties().getWithDefault ("typefaceName", FontPropertyComponent::getDefaultFont()).toString());
e->setAttribute ("fontsize", roundToInt (l->getFont().getHeight() * 100.0) / 100.0);
e->setAttribute ("kerning", roundToInt (l->getFont().getExtraKerningFactor() * 1000.0) / 1000.0);
e->setAttribute ("bold", l->getFont().isBold());
e->setAttribute ("italic", l->getFont().isItalic());
e->setAttribute ("justification", l->getJustificationType().getFlags());
if (l->getFont().getTypefaceStyle() != "Regular")
{
e->setAttribute ("typefaceStyle", l->getFont().getTypefaceStyle());
}
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
Label* const l = dynamic_cast<Label*> (comp);
if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
return false;
Label defaultLabel;
Font font;
font.setHeight ((float) xml.getDoubleAttribute ("fontsize", 15.0));
font.setBold (xml.getBoolAttribute ("bold", false));
font.setItalic (xml.getBoolAttribute ("italic", false));
font.setExtraKerningFactor ((float) xml.getDoubleAttribute ("kerning", 0.0));
auto fontStyle = xml.getStringAttribute ("typefaceStyle");
if (! fontStyle.isEmpty())
font.setTypefaceStyle (fontStyle);
l->setFont (font);
l->getProperties().set ("typefaceName", xml.getStringAttribute ("fontname", FontPropertyComponent::getDefaultFont()));
updateLabelFont (l);
l->setJustificationType (Justification (xml.getIntAttribute ("justification", Justification::centred)));
l->setText (xml.getStringAttribute ("labelText", "Label Text"), dontSendNotification);
l->setEditable (xml.getBoolAttribute ("editableSingleClick", defaultLabel.isEditableOnSingleClick()),
xml.getBoolAttribute ("editableDoubleClick", defaultLabel.isEditableOnDoubleClick()),
xml.getBoolAttribute ("focusDiscardsChanges", defaultLabel.doesLossOfFocusDiscardChanges()));
return true;
}
static void updateLabelFont (Label* label)
{
Font f (label->getFont());
f = FontPropertyComponent::applyNameToFont (label->getProperties().getWithDefault ("typefaceName", FontPropertyComponent::getDefaultFont()), f);
label->setFont (f);
}
String getCreationParameters (GeneratedCode& code, Component* component) override
{
Label* const l = dynamic_cast<Label*> (component);
return quotedString (component->getName(), false)
+ ",\n"
+ quotedString (l->getText(), code.shouldUseTransMacro());
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
Label* const l = dynamic_cast<Label*> (component);
String s;
s << memberVariableName << "->setFont ("
<< FontPropertyComponent::getCompleteFontCode (l->getFont(), l->getProperties().getWithDefault ("typefaceName", FontPropertyComponent::getDefaultFont()))
<< ");\n"
<< memberVariableName << "->setJustificationType ("
<< CodeHelpers::justificationToCode (l->getJustificationType())
<< ");\n"
<< memberVariableName << "->setEditable ("
<< CodeHelpers::boolLiteral (l->isEditableOnSingleClick()) << ", "
<< CodeHelpers::boolLiteral (l->isEditableOnDoubleClick()) << ", "
<< CodeHelpers::boolLiteral (l->doesLossOfFocusDiscardChanges()) << ");\n"
<< getColourIntialisationCode (component, memberVariableName);
if (needsCallback (component))
s << memberVariableName << "->addListener (this);\n";
s << '\n';
code.constructorCode += s;
}
void fillInGeneratedCode (Component* component, GeneratedCode& code) override
{
ComponentTypeHandler::fillInGeneratedCode (component, code);
if (needsCallback (component))
{
String& callback = code.getCallbackCode ("public juce::Label::Listener",
"void",
"labelTextChanged (juce::Label* labelThatHasChanged)",
true);
if (callback.trim().isNotEmpty())
callback << "else ";
const String memberVariableName (code.document->getComponentLayout()->getComponentMemberVariableName (component));
const String userCodeComment ("UserLabelCode_" + memberVariableName);
callback
<< "if (labelThatHasChanged == " << memberVariableName << ".get())\n"
<< "{\n //[" << userCodeComment << "] -- add your label text handling code here..\n //[/" << userCodeComment << "]\n}\n";
}
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ComponentTypeHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
if (auto* const l = dynamic_cast<Label*> (component))
{
props.add (new LabelTextProperty (l, document));
props.add (new LabelJustificationProperty (l, document));
props.add (new FontNameProperty (l, document));
props.add (new FontStyleProperty (l, document));
props.add (new FontSizeProperty (l, document));
props.add (new FontKerningProperty (l, document));
props.add (new LabelEditableProperty (l, document));
if (l->isEditableOnDoubleClick() || l->isEditableOnSingleClick())
props.add (new LabelLossOfFocusProperty (l, document));
}
addColourProperties (component, document, props);
}
static bool needsCallback (Component* label)
{
return ((Label*) label)->isEditableOnSingleClick()
|| ((Label*) label)->isEditableOnDoubleClick(); // xxx should be configurable
}
private:
//==============================================================================
class LabelTextProperty : public ComponentTextProperty <Label>
{
public:
LabelTextProperty (Label* comp, JucerDocument& doc)
: ComponentTextProperty <Label> ("text", 10000, true, comp, doc)
{}
void setText (const String& newText) override
{
document.perform (new LabelTextChangeAction (component, *document.getComponentLayout(), newText),
"Change Label text");
}
String getText() const override
{
return component->getText();
}
private:
class LabelTextChangeAction : public ComponentUndoableAction <Label>
{
public:
LabelTextChangeAction (Label* const comp, ComponentLayout& l, const String& newState_)
: ComponentUndoableAction <Label> (comp, l),
newState (newState_)
{
oldState = comp->getText();
}
bool perform()
{
showCorrectTab();
getComponent()->setText (newState, dontSendNotification);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setText (oldState, dontSendNotification);
changed();
return true;
}
String newState, oldState;
};
};
//==============================================================================
class LabelEditableProperty : public ComponentChoiceProperty <Label>
{
public:
LabelEditableProperty (Label* comp, JucerDocument& doc)
: ComponentChoiceProperty <Label> ("editing", comp, doc)
{
choices.add ("read-only");
choices.add ("edit on single-click");
choices.add ("edit on double-click");
}
void setIndex (int newIndex)
{
document.perform (new LabelEditableChangeAction (component, *document.getComponentLayout(), newIndex),
"Change Label editability");
}
int getIndex() const
{
return component->isEditableOnSingleClick()
? 1
: (component->isEditableOnDoubleClick() ? 2 : 0);
}
private:
class LabelEditableChangeAction : public ComponentUndoableAction <Label>
{
public:
LabelEditableChangeAction (Label* const comp, ComponentLayout& l, const int newState_)
: ComponentUndoableAction <Label> (comp, l),
newState (newState_)
{
oldState = comp->isEditableOnSingleClick()
? 1
: (comp->isEditableOnDoubleClick() ? 2 : 0);
}
bool perform()
{
showCorrectTab();
getComponent()->setEditable (newState == 1, newState >= 1, getComponent()->doesLossOfFocusDiscardChanges());
changed();
layout.getSelectedSet().changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setEditable (oldState == 1, oldState >= 1, getComponent()->doesLossOfFocusDiscardChanges());
changed();
layout.getSelectedSet().changed();
return true;
}
int newState, oldState;
};
};
//==============================================================================
class LabelLossOfFocusProperty : public ComponentChoiceProperty <Label>
{
public:
LabelLossOfFocusProperty (Label* comp, JucerDocument& doc)
: ComponentChoiceProperty <Label> ("focus", comp, doc)
{
choices.add ("loss of focus discards changes");
choices.add ("loss of focus commits changes");
}
void setIndex (int newIndex)
{
document.perform (new LabelFocusLossChangeAction (component, *document.getComponentLayout(), newIndex == 0),
"Change Label focus behaviour");
}
int getIndex() const
{
return component->doesLossOfFocusDiscardChanges() ? 0 : 1;
}
private:
class LabelFocusLossChangeAction : public ComponentUndoableAction <Label>
{
public:
LabelFocusLossChangeAction (Label* const comp, ComponentLayout& l, const bool newState_)
: ComponentUndoableAction <Label> (comp, l),
newState (newState_)
{
oldState = comp->doesLossOfFocusDiscardChanges();
}
bool perform()
{
showCorrectTab();
getComponent()->setEditable (getComponent()->isEditableOnSingleClick(),
getComponent()->isEditableOnDoubleClick(),
newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setEditable (getComponent()->isEditableOnSingleClick(),
getComponent()->isEditableOnDoubleClick(),
oldState);
changed();
return true;
}
bool newState, oldState;
};
};
//==============================================================================
class LabelJustificationProperty : public JustificationProperty,
private ChangeListener
{
public:
LabelJustificationProperty (Label* const label_, JucerDocument& doc)
: JustificationProperty ("layout", false),
label (label_),
document (doc)
{
document.addChangeListener (this);
}
~LabelJustificationProperty() override
{
document.removeChangeListener (this);
}
void setJustification (Justification newJustification) override
{
document.perform (new LabelJustifyChangeAction (label, *document.getComponentLayout(), newJustification),
"Change Label justification");
}
Justification getJustification() const override
{
return label->getJustificationType();
}
private:
void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
Label* const label;
JucerDocument& document;
class LabelJustifyChangeAction : public ComponentUndoableAction <Label>
{
public:
LabelJustifyChangeAction (Label* const comp, ComponentLayout& l, Justification newState_)
: ComponentUndoableAction <Label> (comp, l),
newState (newState_),
oldState (comp->getJustificationType())
{
}
bool perform()
{
showCorrectTab();
getComponent()->setJustificationType (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setJustificationType (oldState);
changed();
return true;
}
Justification newState, oldState;
};
};
//==============================================================================
class FontNameProperty : public FontPropertyComponent,
private ChangeListener
{
public:
FontNameProperty (Label* const label_, JucerDocument& doc)
: FontPropertyComponent ("font"),
label (label_),
document (doc)
{
document.addChangeListener (this);
}
~FontNameProperty() override
{
document.removeChangeListener (this);
}
void setTypefaceName (const String& newFontName) override
{
document.perform (new FontNameChangeAction (label, *document.getComponentLayout(), newFontName),
"Change Label typeface");
}
String getTypefaceName() const override
{
return label->getProperties().getWithDefault ("typefaceName", FontPropertyComponent::getDefaultFont());
}
private:
void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
Label* const label;
JucerDocument& document;
class FontNameChangeAction : public ComponentUndoableAction <Label>
{
public:
FontNameChangeAction (Label* const comp, ComponentLayout& l, const String& newState_)
: ComponentUndoableAction <Label> (comp, l),
newState (newState_)
{
oldState = comp->getProperties().getWithDefault ("typefaceName", FontPropertyComponent::getDefaultFont());
}
bool perform()
{
showCorrectTab();
getComponent()->getProperties().set ("typefaceName", newState);
LabelHandler::updateLabelFont (getComponent());
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->getProperties().set ("typefaceName", oldState);
LabelHandler::updateLabelFont (getComponent());
changed();
return true;
}
String newState, oldState;
};
};
//==============================================================================
class FontSizeProperty : public SliderPropertyComponent,
private ChangeListener
{
public:
FontSizeProperty (Label* const label_, JucerDocument& doc)
: SliderPropertyComponent ("size", 1.0, 250.0, 0.1, 0.3),
label (label_),
document (doc)
{
document.addChangeListener (this);
}
~FontSizeProperty() override
{
document.removeChangeListener (this);
}
void setValue (double newValue) override
{
document.getUndoManager().undoCurrentTransactionOnly();
document.perform (new FontSizeChangeAction (label, *document.getComponentLayout(), (float) newValue),
"Change Label font size");
}
double getValue() const override
{
return label->getFont().getHeight();
}
private:
void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
Label* const label;
JucerDocument& document;
class FontSizeChangeAction : public ComponentUndoableAction <Label>
{
public:
FontSizeChangeAction (Label* const comp, ComponentLayout& l, const float newState_)
: ComponentUndoableAction <Label> (comp, l),
newState (newState_)
{
oldState = comp->getFont().getHeight();
}
bool perform()
{
showCorrectTab();
Font f (getComponent()->getFont());
f.setHeight ((float) newState);
getComponent()->setFont (f);
changed();
return true;
}
bool undo()
{
showCorrectTab();
Font f (getComponent()->getFont());
f.setHeight ((float) oldState);
getComponent()->setFont (f);
changed();
return true;
}
float newState, oldState;
};
};
//==============================================================================
class FontStyleProperty : public ChoicePropertyComponent,
private ChangeListener
{
public:
FontStyleProperty (Label* const label_, JucerDocument& doc)
: ChoicePropertyComponent ("style"),
label (label_),
document (doc)
{
document.addChangeListener (this);
updateStylesList (label->getFont());
}
~FontStyleProperty() override
{
document.removeChangeListener (this);
}
void updateStylesList (const Font& newFont)
{
if (getNumChildComponents() > 0)
{
if (auto cb = dynamic_cast<ComboBox*> (getChildComponent (0)))
cb->clear();
getChildComponent (0)->setVisible (false);
removeAllChildren();
}
choices.clear();
choices.add ("Regular");
choices.add ("Bold");
choices.add ("Italic");
choices.add ("Bold Italic");
choices.mergeArray (newFont.getAvailableStyles());
refresh();
}
void setIndex (int newIndex) override
{
Font f (label->getFont());
if (f.getAvailableStyles().contains (choices[newIndex]))
{
f.setBold (false);
f.setItalic (false);
f.setTypefaceStyle (choices[newIndex]);
}
else
{
f.setTypefaceStyle ("Regular");
f.setBold (newIndex == 1 || newIndex == 3);
f.setItalic (newIndex == 2 || newIndex == 3);
}
document.perform (new FontStyleChangeAction (label, *document.getComponentLayout(), f),
"Change Label font style");
}
int getIndex() const override
{
auto f = label->getFont();
const auto typefaceIndex = choices.indexOf (f.getTypefaceStyle());
if (typefaceIndex == -1)
{
if (f.isBold() && f.isItalic())
return 3;
else if (f.isBold())
return 1;
else if (f.isItalic())
return 2;
return 0;
}
return typefaceIndex;
}
private:
void changeListenerCallback (ChangeBroadcaster*) override
{
updateStylesList (label->getFont());
}
Label* const label;
JucerDocument& document;
class FontStyleChangeAction : public ComponentUndoableAction <Label>
{
public:
FontStyleChangeAction (Label* const comp, ComponentLayout& l, const Font& newState_)
: ComponentUndoableAction <Label> (comp, l),
newState (newState_)
{
oldState = comp->getFont();
}
bool perform()
{
showCorrectTab();
getComponent()->setFont (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setFont (oldState);
changed();
return true;
}
Font newState, oldState;
};
};
//==============================================================================
class FontKerningProperty : public SliderPropertyComponent,
private ChangeListener
{
public:
FontKerningProperty (Label* const label_, JucerDocument& doc)
: SliderPropertyComponent ("kerning", -0.5, 0.5, 0.001),
label (label_),
document (doc)
{
document.addChangeListener (this);
}
~FontKerningProperty() override
{
document.removeChangeListener (this);
}
void setValue (double newValue) override
{
document.getUndoManager().undoCurrentTransactionOnly();
document.perform (new FontKerningChangeAction (label, *document.getComponentLayout(), (float) newValue),
"Change Label font kerning");
}
double getValue() const override
{
return label->getFont().getExtraKerningFactor();
}
private:
void changeListenerCallback (ChangeBroadcaster*) override
{
refresh();
}
Label* const label;
JucerDocument& document;
class FontKerningChangeAction : public ComponentUndoableAction <Label>
{
public:
FontKerningChangeAction (Label* const comp, ComponentLayout& l, const float newState_)
: ComponentUndoableAction <Label> (comp, l),
newState (newState_)
{
oldState = comp->getFont().getExtraKerningFactor();
}
bool perform()
{
showCorrectTab();
Font f (getComponent()->getFont());
f.setExtraKerningFactor ((float) newState);
getComponent()->setFont (f);
changed();
return true;
}
bool undo()
{
showCorrectTab();
Font f (getComponent()->getFont());
f.setExtraKerningFactor ((float) oldState);
getComponent()->setFont (f);
changed();
return true;
}
float newState, oldState;
};
};
};

View File

@@ -0,0 +1,709 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
static const Slider::SliderStyle sliderStyleTypes[] =
{
Slider::LinearHorizontal,
Slider::LinearVertical,
Slider::LinearBar,
Slider::LinearBarVertical,
Slider::Rotary,
Slider::RotaryHorizontalDrag,
Slider::RotaryVerticalDrag,
Slider::RotaryHorizontalVerticalDrag,
Slider::IncDecButtons,
Slider::TwoValueHorizontal,
Slider::TwoValueVertical,
Slider::ThreeValueHorizontal,
Slider::ThreeValueVertical
};
static const Slider::TextEntryBoxPosition sliderTextBoxPositions[] =
{
Slider::NoTextBox,
Slider::TextBoxLeft,
Slider::TextBoxRight,
Slider::TextBoxAbove,
Slider::TextBoxBelow
};
struct SliderHandler : public ComponentTypeHandler
{
SliderHandler()
: ComponentTypeHandler ("Slider", "juce::Slider", typeid (Slider), 150, 24)
{
registerColour (juce::Slider::backgroundColourId, "background", "bkgcol");
registerColour (juce::Slider::thumbColourId, "thumb", "thumbcol");
registerColour (juce::Slider::trackColourId, "track", "trackcol");
registerColour (juce::Slider::rotarySliderFillColourId, "rotary fill", "rotarysliderfill");
registerColour (juce::Slider::rotarySliderOutlineColourId, "rotary outln", "rotaryslideroutline");
registerColour (juce::Slider::textBoxTextColourId, "textbox text", "textboxtext");
registerColour (juce::Slider::textBoxBackgroundColourId, "textbox bkgd", "textboxbkgd");
registerColour (juce::Slider::textBoxHighlightColourId, "textbox highlt", "textboxhighlight");
registerColour (juce::Slider::textBoxOutlineColourId, "textbox outln", "textboxoutline");
}
Component* createNewComponent (JucerDocument*) override
{
return new Slider ("new slider");
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
XmlElement* e = ComponentTypeHandler::createXmlFor (comp, layout);
Slider* s = dynamic_cast<Slider*> (comp);
e->setAttribute ("min", s->getMinimum());
e->setAttribute ("max", s->getMaximum());
e->setAttribute ("int", s->getInterval());
e->setAttribute ("style", sliderStyleToString (s->getSliderStyle()));
e->setAttribute ("textBoxPos", textBoxPosToString (s->getTextBoxPosition()));
e->setAttribute ("textBoxEditable", s->isTextBoxEditable());
e->setAttribute ("textBoxWidth", s->getTextBoxWidth());
e->setAttribute ("textBoxHeight", s->getTextBoxHeight());
e->setAttribute ("skewFactor", s->getSkewFactor());
e->setAttribute ("needsCallback", needsSliderListener (s));
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
return false;
Slider* const s = dynamic_cast<Slider*> (comp);
s->setRange (xml.getDoubleAttribute ("min", 0.0),
xml.getDoubleAttribute ("max", 10.0),
xml.getDoubleAttribute ("int", 0.0));
s->setSliderStyle (sliderStringToStyle (xml.getStringAttribute ("style", "LinearHorizontal")));
s->setTextBoxStyle (stringToTextBoxPos (xml.getStringAttribute ("textBoxPos", "TextBoxLeft")),
! xml.getBoolAttribute ("textBoxEditable", true),
xml.getIntAttribute ("textBoxWidth", 80),
xml.getIntAttribute ("textBoxHeight", 20));
s->setSkewFactor (xml.getDoubleAttribute ("skewFactor", 1.0));
setNeedsSliderListener (s, xml.getBoolAttribute ("needsCallback", true));
return true;
}
String getCreationParameters (GeneratedCode&, Component* component) override
{
return quotedString (component->getName(), false);
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
Slider* const s = dynamic_cast<Slider*> (component);
String r;
r << memberVariableName << "->setRange ("
<< s->getMinimum() << ", " << s->getMaximum() << ", " << s->getInterval()
<< ");\n"
<< memberVariableName << "->setSliderStyle (juce::Slider::"
<< sliderStyleToString (s->getSliderStyle()) << ");\n"
<< memberVariableName << "->setTextBoxStyle (juce::Slider::"
<< textBoxPosToString (s->getTextBoxPosition())
<< ", " << CodeHelpers::boolLiteral (! s->isTextBoxEditable())
<< ", " << s->getTextBoxWidth() << ", " << s->getTextBoxHeight() << ");\n"
<< getColourIntialisationCode (component, memberVariableName);
if (needsSliderListener (component))
r << memberVariableName << "->addListener (this);\n";
if (s->getSkewFactor() != 1.0)
r << memberVariableName << "->setSkewFactor (" << s->getSkewFactor() << ");\n";
r << '\n';
code.constructorCode += r;
}
void fillInGeneratedCode (Component* component, GeneratedCode& code) override
{
ComponentTypeHandler::fillInGeneratedCode (component, code);
if (needsSliderListener (component))
{
String& callback = code.getCallbackCode ("public juce::Slider::Listener",
"void",
"sliderValueChanged (juce::Slider* sliderThatWasMoved)",
true);
if (callback.isNotEmpty())
callback << "else ";
const String memberVariableName (code.document->getComponentLayout()->getComponentMemberVariableName (component));
const String userCodeComment ("UserSliderCode_" + memberVariableName);
callback
<< "if (sliderThatWasMoved == " << memberVariableName << ".get())\n"
<< "{\n //[" << userCodeComment << "] -- add your slider handling code here..\n //[/" << userCodeComment << "]\n}\n";
}
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ComponentTypeHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
if (auto* s = dynamic_cast<Slider*> (component))
{
props.add (new SliderRangeProperty (s, document, "minimum", 0));
props.add (new SliderRangeProperty (s, document, "maximum", 1));
props.add (new SliderRangeProperty (s, document, "interval", 2));
props.add (new SliderTypeProperty (s, document));
props.add (new SliderTextboxProperty (s, document));
props.add (new SliderTextboxEditableProperty (s, document));
props.add (new SliderTextboxSizeProperty (s, document, true));
props.add (new SliderTextboxSizeProperty (s, document, false));
props.add (new SliderSkewProperty (s, document));
props.add (new SliderCallbackProperty (s, document));
}
addColourProperties (component, document, props);
}
static bool needsSliderListener (Component* slider)
{
return slider->getProperties().getWithDefault ("generateListenerCallback", true);
}
static void setNeedsSliderListener (Component* slider, bool shouldDoCallback)
{
slider->getProperties().set ("generateListenerCallback", shouldDoCallback);
}
private:
//==============================================================================
struct SliderTypeProperty : public ComponentChoiceProperty<Slider>
{
SliderTypeProperty (Slider* slider, JucerDocument& doc)
: ComponentChoiceProperty<Slider> ("type", slider, doc)
{
choices.add ("Linear Horizontal");
choices.add ("Linear Vertical");
choices.add ("Linear Bar Horizontal");
choices.add ("Linear Bar Vertical");
choices.add ("Rotary");
choices.add ("Rotary HorizontalDrag");
choices.add ("Rotary VerticalDrag");
choices.add ("Rotary HorizontalVerticalDrag");
choices.add ("Inc/Dec Buttons");
choices.add ("Two Value Horizontal");
choices.add ("Two Value Vertical");
choices.add ("Three Value Horizontal");
choices.add ("Three Value Vertical");
}
void setIndex (int newIndex) override
{
if (newIndex >= 0 && newIndex < numElementsInArray (sliderStyleTypes))
document.perform (new SliderTypeChangeAction (component, *document.getComponentLayout(),
sliderStyleTypes[newIndex]),
"Change Slider style");
}
int getIndex() const override
{
for (int i = 0; i < numElementsInArray (sliderStyleTypes); ++i)
if (sliderStyleTypes[i] == dynamic_cast<Slider*> (component)->getSliderStyle())
return i;
return -1;
}
private:
struct SliderTypeChangeAction : public ComponentUndoableAction<Slider>
{
SliderTypeChangeAction (Slider* comp, ComponentLayout& l, Slider::SliderStyle newState_)
: ComponentUndoableAction<Slider> (comp, l),
newState (newState_)
{
oldState = comp->getSliderStyle();
}
bool perform() override
{
showCorrectTab();
getComponent()->setSliderStyle (newState);
changed();
return true;
}
bool undo() override
{
showCorrectTab();
getComponent()->setSliderStyle (oldState);
changed();
return true;
}
Slider::SliderStyle newState, oldState;
};
};
//==============================================================================
struct SliderTextboxProperty : public ComponentChoiceProperty<Slider>
{
SliderTextboxProperty (Slider* slider, JucerDocument& doc)
: ComponentChoiceProperty<Slider> ("text position", slider, doc)
{
choices.add ("No text box");
choices.add ("Text box on left");
choices.add ("Text box on right");
choices.add ("Text box above");
choices.add ("Text box below");
}
void setIndex (int newIndex) override
{
if (newIndex >= 0 && newIndex < numElementsInArray (sliderTextBoxPositions))
document.perform (new SliderTextBoxChangeAction (component, *document.getComponentLayout(),
sliderTextBoxPositions[newIndex]),
"Change Slider textbox");
}
int getIndex() const override
{
for (int i = 0; i < numElementsInArray (sliderTextBoxPositions); ++i)
if (sliderTextBoxPositions[i] == component->getTextBoxPosition())
return i;
return -1;
}
private:
struct SliderTextBoxChangeAction : public ComponentUndoableAction<Slider>
{
SliderTextBoxChangeAction (Slider* comp, ComponentLayout& l, Slider::TextEntryBoxPosition newState_)
: ComponentUndoableAction<Slider> (comp, l),
newState (newState_)
{
oldState = comp->getTextBoxPosition();
}
bool perform() override
{
showCorrectTab();
getComponent()->setTextBoxStyle (newState,
! getComponent()->isTextBoxEditable(),
getComponent()->getTextBoxWidth(),
getComponent()->getTextBoxHeight());
changed();
return true;
}
bool undo() override
{
showCorrectTab();
getComponent()->setTextBoxStyle (oldState,
! getComponent()->isTextBoxEditable(),
getComponent()->getTextBoxWidth(),
getComponent()->getTextBoxHeight());
changed();
return true;
}
Slider::TextEntryBoxPosition newState, oldState;
};
};
//==============================================================================
struct SliderTextboxEditableProperty : public ComponentBooleanProperty<Slider>
{
SliderTextboxEditableProperty (Slider* slider, JucerDocument& doc)
: ComponentBooleanProperty<Slider> ("text box mode", "Editable", "Editable", slider, doc)
{
}
void setState (bool newState) override
{
document.perform (new SliderEditableChangeAction (component, *document.getComponentLayout(), newState),
"Change Slider editability");
}
bool getState() const override
{
return component->isTextBoxEditable();
}
private:
struct SliderEditableChangeAction : public ComponentUndoableAction<Slider>
{
SliderEditableChangeAction (Slider* const comp, ComponentLayout& l, bool newState_)
: ComponentUndoableAction<Slider> (comp, l),
newState (newState_)
{
oldState = comp->isTextBoxEditable();
}
bool perform() override
{
showCorrectTab();
getComponent()->setTextBoxIsEditable (newState);
changed();
return true;
}
bool undo() override
{
showCorrectTab();
getComponent()->setTextBoxIsEditable (oldState);
changed();
return true;
}
bool newState, oldState;
};
};
//==============================================================================
struct SliderCallbackProperty : public ComponentBooleanProperty<Slider>
{
SliderCallbackProperty (Slider* s, JucerDocument& doc)
: ComponentBooleanProperty<Slider> ("callback", "Generate SliderListener",
"Generate SliderListener", s, doc)
{
}
void setState (bool newState) override
{
document.perform (new SliderCallbackChangeAction (component, *document.getComponentLayout(), newState),
"Change button callback");
}
bool getState() const override { return needsSliderListener (component); }
struct SliderCallbackChangeAction : public ComponentUndoableAction<Slider>
{
SliderCallbackChangeAction (Slider* comp, ComponentLayout& l, bool newState_)
: ComponentUndoableAction<Slider> (comp, l),
newState (newState_)
{
oldState = needsSliderListener (comp);
}
bool perform() override
{
showCorrectTab();
setNeedsSliderListener (getComponent(), newState);
changed();
return true;
}
bool undo() override
{
showCorrectTab();
setNeedsSliderListener (getComponent(), oldState);
changed();
return true;
}
bool newState, oldState;
};
};
//==============================================================================
struct SliderTextboxSizeProperty : public ComponentTextProperty<Slider>
{
SliderTextboxSizeProperty (Slider* slider, JucerDocument& doc, bool isWidth_)
: ComponentTextProperty<Slider> (isWidth_ ? "text box width" : "text box height",
12, false, slider, doc),
isWidth (isWidth_)
{
}
void setText (const String& newText) override
{
document.perform (new SliderBoxSizeChangeAction (component, *document.getComponentLayout(), isWidth, newText.getIntValue()),
"Change Slider textbox size");
}
String getText() const override
{
return String (isWidth ? component->getTextBoxWidth()
: component->getTextBoxHeight());
}
private:
const bool isWidth;
struct SliderBoxSizeChangeAction : public ComponentUndoableAction<Slider>
{
SliderBoxSizeChangeAction (Slider* const comp, ComponentLayout& l, bool isWidth_, int newSize_)
: ComponentUndoableAction<Slider> (comp, l),
isWidth (isWidth_),
newSize (newSize_)
{
oldSize = isWidth ? comp->getTextBoxWidth()
: comp->getTextBoxHeight();
}
bool perform() override
{
showCorrectTab();
Slider& c = *getComponent();
if (isWidth)
c.setTextBoxStyle (c.getTextBoxPosition(),
! c.isTextBoxEditable(),
newSize,
c.getTextBoxHeight());
else
c.setTextBoxStyle (c.getTextBoxPosition(),
! c.isTextBoxEditable(),
c.getTextBoxWidth(),
newSize);
changed();
return true;
}
bool undo() override
{
showCorrectTab();
Slider& c = *getComponent();
if (isWidth)
c.setTextBoxStyle (c.getTextBoxPosition(),
! c.isTextBoxEditable(),
oldSize,
c.getTextBoxHeight());
else
c.setTextBoxStyle (c.getTextBoxPosition(),
! c.isTextBoxEditable(),
c.getTextBoxWidth(),
oldSize);
changed();
return true;
}
bool isWidth;
int newSize, oldSize;
};
};
//==============================================================================
struct SliderRangeProperty : public ComponentTextProperty<Slider>
{
SliderRangeProperty (Slider* slider, JucerDocument& doc,
const String& name, int rangeParam_)
: ComponentTextProperty<Slider> (name, 15, false, slider, doc),
rangeParam (rangeParam_)
{
}
void setText (const String& newText) override
{
double state [3];
state [0] = component->getMinimum();
state [1] = component->getMaximum();
state [2] = component->getInterval();
state [rangeParam] = newText.getDoubleValue();
document.perform (new SliderRangeChangeAction (component, *document.getComponentLayout(), state),
"Change Slider range");
}
String getText() const override
{
if (auto* s = dynamic_cast<Slider*> (component))
{
switch (rangeParam)
{
case 0: return String (s->getMinimum());
case 1: return String (s->getMaximum());
case 2: return String (s->getInterval());
default: jassertfalse; break;
}
}
return {};
}
private:
const int rangeParam;
struct SliderRangeChangeAction : public ComponentUndoableAction<Slider>
{
SliderRangeChangeAction (Slider* comp, ComponentLayout& l, const double newState_[3])
: ComponentUndoableAction<Slider> (comp, l)
{
newState[0] = newState_ [0];
newState[1] = newState_ [1];
newState[2] = newState_ [2];
oldState[0] = comp->getMinimum();
oldState[1] = comp->getMaximum();
oldState[2] = comp->getInterval();
}
bool perform() override
{
showCorrectTab();
getComponent()->setRange (newState[0], newState[1], newState[2]);
changed();
return true;
}
bool undo() override
{
showCorrectTab();
getComponent()->setRange (oldState[0], oldState[1], oldState[2]);
changed();
return true;
}
double newState[3], oldState[3];
};
};
//==============================================================================
struct SliderSkewProperty : public ComponentTextProperty<Slider>
{
SliderSkewProperty (Slider* slider, JucerDocument& doc)
: ComponentTextProperty<Slider> ("skew factor", 12, false, slider, doc)
{
}
void setText (const String& newText) override
{
const double skew = jlimit (0.001, 1000.0, newText.getDoubleValue());
document.perform (new SliderSkewChangeAction (component, *document.getComponentLayout(), skew),
"Change Slider skew");
}
String getText() const override
{
if (auto* s = dynamic_cast<Slider*> (component))
return String (s->getSkewFactor());
return {};
}
struct SliderSkewChangeAction : public ComponentUndoableAction<Slider>
{
SliderSkewChangeAction (Slider* comp, ComponentLayout& l, double newValue_)
: ComponentUndoableAction<Slider> (comp, l)
{
newValue = newValue_;
oldValue = comp->getSkewFactor();
}
bool perform() override
{
showCorrectTab();
getComponent()->setSkewFactor (newValue);
changed();
return true;
}
bool undo() override
{
showCorrectTab();
getComponent()->setSkewFactor (oldValue);
changed();
return true;
}
double newValue, oldValue;
};
};
//==============================================================================
static String sliderStyleToString (Slider::SliderStyle style)
{
switch (style)
{
case Slider::LinearHorizontal: return "LinearHorizontal";
case Slider::LinearVertical: return "LinearVertical";
case Slider::LinearBar: return "LinearBar";
case Slider::LinearBarVertical: return "LinearBarVertical";
case Slider::Rotary: return "Rotary";
case Slider::RotaryHorizontalDrag: return "RotaryHorizontalDrag";
case Slider::RotaryVerticalDrag: return "RotaryVerticalDrag";
case Slider::RotaryHorizontalVerticalDrag: return "RotaryHorizontalVerticalDrag";
case Slider::IncDecButtons: return "IncDecButtons";
case Slider::TwoValueHorizontal: return "TwoValueHorizontal";
case Slider::TwoValueVertical: return "TwoValueVertical";
case Slider::ThreeValueHorizontal: return "ThreeValueHorizontal";
case Slider::ThreeValueVertical: return "ThreeValueVertical";
default: jassertfalse; break;
}
return {};
}
static Slider::SliderStyle sliderStringToStyle (const String& s)
{
for (int i = 0; i < numElementsInArray (sliderStyleTypes); ++i)
if (s == sliderStyleToString (sliderStyleTypes[i]))
return sliderStyleTypes[i];
jassertfalse;
return Slider::LinearHorizontal;
}
static String textBoxPosToString (const Slider::TextEntryBoxPosition pos)
{
switch (pos)
{
case Slider::NoTextBox: return "NoTextBox";
case Slider::TextBoxLeft: return "TextBoxLeft";
case Slider::TextBoxRight: return "TextBoxRight";
case Slider::TextBoxAbove: return "TextBoxAbove";
case Slider::TextBoxBelow: return "TextBoxBelow";
default: jassertfalse; break;
}
return {};
}
static Slider::TextEntryBoxPosition stringToTextBoxPos (const String& s)
{
for (int i = 0; i < numElementsInArray (sliderTextBoxPositions); ++i)
if (s == textBoxPosToString (sliderTextBoxPositions[i]))
return sliderTextBoxPositions[i];
jassertfalse;
return Slider::TextBoxLeft;
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class TextButtonHandler : public ButtonHandler
{
public:
TextButtonHandler()
: ButtonHandler ("Text Button", "juce::TextButton", typeid (TextButton), 150, 24)
{
registerColour (juce::TextButton::buttonColourId, "background (normal)", "bgColOff");
registerColour (juce::TextButton::buttonOnColourId, "background (on)", "bgColOn");
registerColour (juce::TextButton::textColourOffId, "text colour (normal)", "textCol");
registerColour (juce::TextButton::textColourOnId, "text colour (on)", "textColOn");
}
Component* createNewComponent (JucerDocument*) override
{
return new TextButton ("new button", String());
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ButtonHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
addColourProperties (component, document, props);
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
return ButtonHandler::createXmlFor (comp, layout);
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
return ButtonHandler::restoreFromXml (xml, comp, layout);
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ButtonHandler::fillInCreationCode (code, component, memberVariableName);
String s;
s << getColourIntialisationCode (component, memberVariableName)
<< '\n';
code.constructorCode += s;
}
};

View File

@@ -0,0 +1,433 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class TextEditorHandler : public ComponentTypeHandler
{
public:
TextEditorHandler()
: ComponentTypeHandler ("Text Editor", "juce::TextEditor", typeid (TextEditor), 150, 24)
{
registerColour (juce::TextEditor::textColourId, "text", "textcol");
registerColour (juce::TextEditor::backgroundColourId, "background", "bkgcol");
registerColour (juce::TextEditor::highlightColourId, "highlight", "hilitecol");
registerColour (juce::TextEditor::outlineColourId, "outline", "outlinecol");
registerColour (juce::TextEditor::shadowColourId, "shadow", "shadowcol");
registerColour (juce::CaretComponent::caretColourId, "caret", "caretcol");
}
Component* createNewComponent (JucerDocument*) override
{
return new TextEditor ("new text editor");
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
XmlElement* e = ComponentTypeHandler::createXmlFor (comp, layout);
TextEditor* te = (TextEditor*) comp;
e->setAttribute ("initialText", comp->getProperties() ["initialText"].toString());
e->setAttribute ("multiline", te->isMultiLine());
e->setAttribute ("retKeyStartsLine", te->getReturnKeyStartsNewLine());
e->setAttribute ("readonly", te->isReadOnly());
e->setAttribute ("scrollbars", te->areScrollbarsShown());
e->setAttribute ("caret", te->isCaretVisible());
e->setAttribute ("popupmenu", te->isPopupMenuEnabled());
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
return false;
TextEditor* te = (TextEditor*) comp;
TextEditor defaultEditor;
te->setMultiLine (xml.getBoolAttribute ("multiline", defaultEditor.isMultiLine()));
te->setReturnKeyStartsNewLine (xml.getBoolAttribute ("retKeyStartsLine", defaultEditor.getReturnKeyStartsNewLine()));
te->setReadOnly (xml.getBoolAttribute ("readonly", defaultEditor.isReadOnly()));
te->setScrollbarsShown (xml.getBoolAttribute ("scrollbars", defaultEditor.areScrollbarsShown()));
te->setCaretVisible (xml.getBoolAttribute ("caret", defaultEditor.isCaretVisible()));
te->setPopupMenuEnabled (xml.getBoolAttribute ("popupmenu", defaultEditor.isPopupMenuEnabled()));
const String initialText (xml.getStringAttribute ("initialText"));
te->setText (initialText, false);
te->getProperties().set ("initialText", initialText);
return true;
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ComponentTypeHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
if (auto* t = dynamic_cast<TextEditor*> (component))
{
props.add (new TextEditorInitialTextProperty (t, document));
props.add (new TextEditorMultiLineProperty (t, document));
props.add (new TextEditorReadOnlyProperty (t, document));
props.add (new TextEditorScrollbarsProperty (t, document));
props.add (new TextEditorCaretProperty (t, document));
props.add (new TextEditorPopupMenuProperty (t, document));
addColourProperties (t, document, props);
}
}
String getCreationParameters (GeneratedCode&, Component* component) override
{
return quotedString (component->getName(), false);
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
if (auto* te = dynamic_cast<TextEditor*> (component))
{
String s;
s << memberVariableName << "->setMultiLine (" << CodeHelpers::boolLiteral (te->isMultiLine()) << ");\n"
<< memberVariableName << "->setReturnKeyStartsNewLine (" << CodeHelpers::boolLiteral (te->getReturnKeyStartsNewLine()) << ");\n"
<< memberVariableName << "->setReadOnly (" << CodeHelpers::boolLiteral (te->isReadOnly()) << ");\n"
<< memberVariableName << "->setScrollbarsShown (" << CodeHelpers::boolLiteral (te->areScrollbarsShown()) << ");\n"
<< memberVariableName << "->setCaretVisible (" << CodeHelpers::boolLiteral (te->isCaretVisible()) << ");\n"
<< memberVariableName << "->setPopupMenuEnabled (" << CodeHelpers::boolLiteral (te->isPopupMenuEnabled()) << ");\n"
<< getColourIntialisationCode (component, memberVariableName)
<< memberVariableName << "->setText (" << quotedString (te->getProperties() ["initialText"].toString(), code.shouldUseTransMacro()) << ");\n\n";
code.constructorCode += s;
}
}
private:
//==============================================================================
class TextEditorMultiLineProperty : public ComponentChoiceProperty <TextEditor>
{
public:
TextEditorMultiLineProperty (TextEditor* comp, JucerDocument& doc)
: ComponentChoiceProperty <TextEditor> ("mode", comp, doc)
{
choices.add ("single line");
choices.add ("multi-line, return key starts new line");
choices.add ("multi-line, return key disabled");
}
void setIndex (int newIndex)
{
document.perform (new TextEditorMultilineChangeAction (component, *document.getComponentLayout(), newIndex),
"Change TextEditor multiline mode");
}
int getIndex() const
{
return component->isMultiLine() ? (component->getReturnKeyStartsNewLine() ? 1 : 2) : 0;
}
private:
class TextEditorMultilineChangeAction : public ComponentUndoableAction <TextEditor>
{
public:
TextEditorMultilineChangeAction (TextEditor* const comp, ComponentLayout& l, const int newState_)
: ComponentUndoableAction <TextEditor> (comp, l),
newState (newState_)
{
oldState = comp->isMultiLine() ? (comp->getReturnKeyStartsNewLine() ? 1 : 2) : 0;
}
bool perform()
{
showCorrectTab();
getComponent()->setMultiLine (newState > 0);
getComponent()->setReturnKeyStartsNewLine (newState == 1);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setMultiLine (oldState > 0);
getComponent()->setReturnKeyStartsNewLine (oldState == 1);
changed();
return true;
}
int newState, oldState;
};
};
//==============================================================================
class TextEditorReadOnlyProperty : public ComponentBooleanProperty <TextEditor>
{
public:
TextEditorReadOnlyProperty (TextEditor* comp, JucerDocument& doc)
: ComponentBooleanProperty <TextEditor> ("editable", "Editable", "Editable", comp, doc)
{
}
void setState (bool newState)
{
document.perform (new TextEditorReadonlyChangeAction (component, *document.getComponentLayout(), ! newState),
"Change TextEditor read-only mode");
}
bool getState() const { return ! component->isReadOnly(); }
private:
class TextEditorReadonlyChangeAction : public ComponentUndoableAction <TextEditor>
{
public:
TextEditorReadonlyChangeAction (TextEditor* const comp, ComponentLayout& l, const bool newState_)
: ComponentUndoableAction <TextEditor> (comp, l),
newState (newState_)
{
oldState = comp->isReadOnly();
}
bool perform()
{
showCorrectTab();
getComponent()->setReadOnly (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setReadOnly (oldState);
changed();
return true;
}
bool newState, oldState;
};
};
//==============================================================================
class TextEditorScrollbarsProperty : public ComponentBooleanProperty <TextEditor>
{
public:
TextEditorScrollbarsProperty (TextEditor* comp, JucerDocument& doc)
: ComponentBooleanProperty <TextEditor> ("scrollbars", "Scrollbars enabled", "Scrollbars enabled", comp, doc)
{
}
void setState (bool newState)
{
document.perform (new TextEditorScrollbarChangeAction (component, *document.getComponentLayout(), newState),
"Change TextEditor scrollbars");
}
bool getState() const { return component->areScrollbarsShown(); }
private:
class TextEditorScrollbarChangeAction : public ComponentUndoableAction <TextEditor>
{
public:
TextEditorScrollbarChangeAction (TextEditor* const comp, ComponentLayout& l, const bool newState_)
: ComponentUndoableAction <TextEditor> (comp, l),
newState (newState_)
{
oldState = comp->areScrollbarsShown();
}
bool perform()
{
showCorrectTab();
getComponent()->setScrollbarsShown (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setScrollbarsShown (oldState);
changed();
return true;
}
bool newState, oldState;
};
};
//==============================================================================
class TextEditorCaretProperty : public ComponentBooleanProperty <TextEditor>
{
public:
TextEditorCaretProperty (TextEditor* comp, JucerDocument& doc)
: ComponentBooleanProperty <TextEditor> ("caret", "Caret visible", "Caret visible", comp, doc)
{
}
void setState (bool newState)
{
document.perform (new TextEditorCaretChangeAction (component, *document.getComponentLayout(), newState),
"Change TextEditor caret");
}
bool getState() const { return component->isCaretVisible(); }
private:
class TextEditorCaretChangeAction : public ComponentUndoableAction <TextEditor>
{
public:
TextEditorCaretChangeAction (TextEditor* const comp, ComponentLayout& l, const bool newState_)
: ComponentUndoableAction <TextEditor> (comp, l),
newState (newState_)
{
oldState = comp->isCaretVisible();
}
bool perform()
{
showCorrectTab();
getComponent()->setCaretVisible (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setCaretVisible (oldState);
changed();
return true;
}
bool newState, oldState;
};
};
//==============================================================================
class TextEditorPopupMenuProperty : public ComponentBooleanProperty <TextEditor>
{
public:
TextEditorPopupMenuProperty (TextEditor* comp, JucerDocument& doc)
: ComponentBooleanProperty <TextEditor> ("popup menu", "Popup menu enabled", "Popup menu enabled", comp, doc)
{
}
void setState (bool newState)
{
document.perform (new TextEditorPopupMenuChangeAction (component, *document.getComponentLayout(), newState),
"Change TextEditor popup menu");
}
bool getState() const { return component->isPopupMenuEnabled(); }
private:
class TextEditorPopupMenuChangeAction : public ComponentUndoableAction <TextEditor>
{
public:
TextEditorPopupMenuChangeAction (TextEditor* const comp, ComponentLayout& l, const bool newState_)
: ComponentUndoableAction <TextEditor> (comp, l),
newState (newState_)
{
oldState = comp->isPopupMenuEnabled();
}
bool perform()
{
showCorrectTab();
getComponent()->setPopupMenuEnabled (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setPopupMenuEnabled (oldState);
changed();
return true;
}
bool newState, oldState;
};
};
//==============================================================================
class TextEditorInitialTextProperty : public ComponentTextProperty <TextEditor>
{
public:
TextEditorInitialTextProperty (TextEditor* comp, JucerDocument& doc)
: ComponentTextProperty <TextEditor> ("initial text", 10000, true, comp, doc)
{}
void setText (const String& newText) override
{
document.perform (new TextEditorInitialTextChangeAction (component, *document.getComponentLayout(), newText),
"Change TextEditor initial text");
}
String getText() const override
{
return component->getProperties() ["initialText"];
}
private:
class TextEditorInitialTextChangeAction : public ComponentUndoableAction <TextEditor>
{
public:
TextEditorInitialTextChangeAction (TextEditor* const comp, ComponentLayout& l, const String& newState_)
: ComponentUndoableAction <TextEditor> (comp, l),
newState (newState_)
{
oldState = comp->getProperties() ["initialText"];
}
bool perform()
{
showCorrectTab();
getComponent()->setText (newState, false);
getComponent()->getProperties().set ("initialText", newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setText (oldState, false);
getComponent()->getProperties().set ("initialText", oldState);
changed();
return true;
}
String newState, oldState;
};
};
};

View File

@@ -0,0 +1,146 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ToggleButtonHandler : public ButtonHandler
{
public:
ToggleButtonHandler()
: ButtonHandler ("Toggle Button", "juce::ToggleButton", typeid (ToggleButton), 150, 24)
{
registerColour (juce::ToggleButton::textColourId, "text colour", "txtcol");
}
Component* createNewComponent (JucerDocument*) override
{
return new ToggleButton ("new toggle button");
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ButtonHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
if (auto* tb = dynamic_cast<ToggleButton*> (component))
props.add (new ToggleButtonStateProperty (tb, document));
addColourProperties (component, document, props);
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
ToggleButton* tb = (ToggleButton*) comp;
XmlElement* e = ButtonHandler::createXmlFor (comp, layout);
e->setAttribute ("state", tb->getToggleState());
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
ToggleButton* const tb = (ToggleButton*) comp;
if (! ButtonHandler::restoreFromXml (xml, comp, layout))
return false;
tb->setToggleState (xml.getBoolAttribute ("state", false), dontSendNotification);
return true;
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
ButtonHandler::fillInCreationCode (code, component, memberVariableName);
ToggleButton* const tb = dynamic_cast<ToggleButton*> (component);
String s;
if (tb->getToggleState())
s << memberVariableName << "->setToggleState (true, juce::dontSendNotification);\n";
s << getColourIntialisationCode (component, memberVariableName)
<< '\n';
code.constructorCode += s;
}
private:
class ToggleButtonStateProperty : public ComponentBooleanProperty <ToggleButton>
{
public:
ToggleButtonStateProperty (ToggleButton* button_, JucerDocument& doc)
: ComponentBooleanProperty <ToggleButton> ("initial state", "on", "off", button_, doc)
{
}
void setState (bool newState)
{
document.perform (new ToggleStateChangeAction (component, *document.getComponentLayout(), newState),
"Change ToggleButton state");
}
bool getState() const
{
return component->getToggleState();
}
private:
class ToggleStateChangeAction : public ComponentUndoableAction <ToggleButton>
{
public:
ToggleStateChangeAction (ToggleButton* const comp, ComponentLayout& l, const bool newState_)
: ComponentUndoableAction <ToggleButton> (comp, l),
newState (newState_)
{
oldState = comp->getToggleState();
}
bool perform()
{
showCorrectTab();
getComponent()->setToggleState (newState, dontSendNotification);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setToggleState (oldState, dontSendNotification);
changed();
return true;
}
bool newState, oldState;
};
};
};

View File

@@ -0,0 +1,265 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class TreeViewHandler : public ComponentTypeHandler
{
public:
TreeViewHandler()
: ComponentTypeHandler ("TreeView", "juce::TreeView", typeid (DemoTreeView), 150, 150)
{
registerColour (juce::TreeView::backgroundColourId, "background", "backgroundColour");
registerColour (juce::TreeView::linesColourId, "lines", "linecol");
}
Component* createNewComponent (JucerDocument*) override
{
return new DemoTreeView();
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
TreeView* const t = dynamic_cast<TreeView*> (comp);
XmlElement* const e = ComponentTypeHandler::createXmlFor (comp, layout);
e->setAttribute ("rootVisible", t->isRootItemVisible());
e->setAttribute ("openByDefault", t->areItemsOpenByDefault());
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
return false;
TreeView defaultTreeView;
TreeView* const t = dynamic_cast<TreeView*> (comp);
t->setRootItemVisible (xml.getBoolAttribute ("rootVisible", defaultTreeView.isRootItemVisible()));
t->setDefaultOpenness (xml.getBoolAttribute ("openByDefault", defaultTreeView.areItemsOpenByDefault()));
return true;
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ComponentTypeHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
auto* t = dynamic_cast<TreeView*> (component);
props.add (new TreeViewRootItemProperty (t, document));
props.add (new TreeViewRootOpennessProperty (t, document));
addColourProperties (t, document, props);
}
String getCreationParameters (GeneratedCode&, Component* comp) override
{
return quotedString (comp->getName(), false);
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
TreeView defaultTreeView;
TreeView* const t = dynamic_cast<TreeView*> (component);
ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
if (defaultTreeView.isRootItemVisible() != t->isRootItemVisible())
{
code.constructorCode
<< memberVariableName << "->setRootItemVisible ("
<< CodeHelpers::boolLiteral (t->isRootItemVisible()) << ");\n";
}
if (defaultTreeView.areItemsOpenByDefault() != t->areItemsOpenByDefault())
{
code.constructorCode
<< memberVariableName << "->setDefaultOpenness ("
<< CodeHelpers::boolLiteral (t->areItemsOpenByDefault()) << ");\n";
}
code.constructorCode << getColourIntialisationCode (component, memberVariableName);
code.constructorCode << "\n";
}
private:
//==============================================================================
class DemoTreeView : public TreeView
{
public:
DemoTreeView()
: TreeView ("new treeview")
{
setRootItem (new DemoTreeViewItem ("Demo root node", 4));
}
~DemoTreeView()
{
deleteRootItem();
}
private:
class DemoTreeViewItem : public TreeViewItem
{
public:
DemoTreeViewItem (const String& name_, const int numItems)
: name (name_)
{
for (int i = 0; i < numItems; ++i)
addSubItem (new DemoTreeViewItem ("Demo sub-node " + String (i), numItems - 1));
}
void paintItem (Graphics& g, int width, int height) override
{
if (isSelected())
g.fillAll (Colours::lightblue);
g.setColour (Colours::black);
g.setFont ((float) height * 0.7f);
g.drawText (name, 4, 0, width - 4, height, Justification::centredLeft, true);
}
bool mightContainSubItems() override
{
return true;
}
const String name;
};
};
//==============================================================================
class TreeViewRootItemProperty : public ComponentBooleanProperty <TreeView>
{
public:
TreeViewRootItemProperty (TreeView* comp, JucerDocument& doc)
: ComponentBooleanProperty <TreeView> ("show root item", "Root item visible", "Root item visible", comp, doc)
{
}
void setState (bool newState)
{
document.perform (new TreeviewRootChangeAction (component, *document.getComponentLayout(), newState),
"Change TreeView root item");
}
bool getState() const
{
return component->isRootItemVisible();
}
private:
class TreeviewRootChangeAction : public ComponentUndoableAction <TreeView>
{
public:
TreeviewRootChangeAction (TreeView* const comp, ComponentLayout& l, const bool newState_)
: ComponentUndoableAction <TreeView> (comp, l),
newState (newState_)
{
oldState = comp->isRootItemVisible();
}
bool perform()
{
showCorrectTab();
getComponent()->setRootItemVisible (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setRootItemVisible (oldState);
changed();
return true;
}
bool newState, oldState;
};
};
//==============================================================================
class TreeViewRootOpennessProperty : public ComponentChoiceProperty <TreeView>
{
public:
TreeViewRootOpennessProperty (TreeView* comp, JucerDocument& doc)
: ComponentChoiceProperty <TreeView> ("default openness", comp, doc)
{
choices.add ("Items open by default");
choices.add ("Items closed by default");
}
void setIndex (int newIndex)
{
document.perform (new TreeviewOpennessChangeAction (component, *document.getComponentLayout(), newIndex == 0),
"Change TreeView openness");
}
int getIndex() const
{
return component->areItemsOpenByDefault() ? 0 : 1;
}
private:
class TreeviewOpennessChangeAction : public ComponentUndoableAction <TreeView>
{
public:
TreeviewOpennessChangeAction (TreeView* const comp, ComponentLayout& l, const bool newState_)
: ComponentUndoableAction <TreeView> (comp, l),
newState (newState_)
{
oldState = comp->areItemsOpenByDefault();
}
bool perform()
{
showCorrectTab();
getComponent()->setDefaultOpenness (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setDefaultOpenness (oldState);
changed();
return true;
}
bool newState, oldState;
};
};
};

View File

@@ -0,0 +1,666 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ViewportHandler : public ComponentTypeHandler
{
public:
ViewportHandler()
: ComponentTypeHandler ("Viewport", "juce::Viewport", typeid (UpdatingViewport), 150, 150)
{}
Component* createNewComponent (JucerDocument*) override
{
Viewport* const v = new UpdatingViewport ("new viewport");
v->setViewedComponent (new ViewportDemoContentComp());
return v;
}
XmlElement* createXmlFor (Component* comp, const ComponentLayout* layout) override
{
Viewport* const v = dynamic_cast<Viewport*> (comp);
XmlElement* const e = ComponentTypeHandler::createXmlFor (comp, layout);
e->setAttribute ("vscroll", v->isVerticalScrollBarShown());
e->setAttribute ("hscroll", v->isHorizontalScrollBarShown());
e->setAttribute ("scrollbarThickness", v->getScrollBarThickness());
e->setAttribute ("contentType", getViewportContentType (v));
e->setAttribute ("jucerFile", getViewportJucerComponentFile (v));
e->setAttribute ("contentClass", getViewportGenericComponentClass (v));
e->setAttribute ("constructorParams", getViewportConstructorParams (v));
return e;
}
bool restoreFromXml (const XmlElement& xml, Component* comp, const ComponentLayout* layout) override
{
if (! ComponentTypeHandler::restoreFromXml (xml, comp, layout))
return false;
Viewport defaultViewport;
Viewport* const v = dynamic_cast<Viewport*> (comp);
v->setScrollBarsShown (xml.getBoolAttribute ("vscroll", defaultViewport.isVerticalScrollBarShown()),
xml.getBoolAttribute ("hscroll", defaultViewport.isHorizontalScrollBarShown()));
v->setScrollBarThickness (xml.getIntAttribute ("scrollbarThickness", defaultViewport.getScrollBarThickness()));
setViewportJucerComponentFile (v, xml.getStringAttribute ("jucerFile", String()));
setViewportGenericComponentClass (v, xml.getStringAttribute ("contentClass"));
setViewportContentType (v, xml.getIntAttribute ("contentType", 0));
setViewportConstructorParams (v, xml.getStringAttribute ("constructorParams"));
return true;
}
void getEditableProperties (Component* component, JucerDocument& document,
Array<PropertyComponent*>& props, bool multipleSelected) override
{
ComponentTypeHandler::getEditableProperties (component, document, props, multipleSelected);
if (multipleSelected)
return;
auto* v = dynamic_cast<Viewport*> (component);
props.add (new ViewportScrollbarShownProperty (v, document, true));
props.add (new ViewportScrollbarShownProperty (v, document, false));
props.add (new ViewportScrollbarSizeProperty (v, document));
props.add (new ViewportContentTypeProperty (v, document));
if (getViewportContentType (v) == 1)
{
props.add (new ViewportJucerFileProperty (v, document));
props.add (new ConstructorParamsProperty (v, document));
}
else if (getViewportContentType (v) == 2)
{
props.add (new ViewportContentClassProperty (v, document));
props.add (new ConstructorParamsProperty (v, document));
}
}
String getCreationParameters (GeneratedCode&, Component* comp) override
{
return quotedString (comp->getName(), false);
}
void fillInCreationCode (GeneratedCode& code, Component* component, const String& memberVariableName) override
{
Viewport defaultViewport;
Viewport* const v = dynamic_cast<Viewport*> (component);
ComponentTypeHandler::fillInCreationCode (code, component, memberVariableName);
if (defaultViewport.isVerticalScrollBarShown() != v->isVerticalScrollBarShown()
|| defaultViewport.isHorizontalScrollBarShown() != v->isHorizontalScrollBarShown())
{
code.constructorCode
<< memberVariableName << "->setScrollBarsShown ("
<< CodeHelpers::boolLiteral (v->isVerticalScrollBarShown()) << ", "
<< CodeHelpers::boolLiteral (v->isHorizontalScrollBarShown()) << ");\n";
}
if (defaultViewport.getScrollBarThickness() != v->getScrollBarThickness())
{
code.constructorCode
<< memberVariableName << "->setScrollBarThickness ("
<< v->getScrollBarThickness() << ");\n";
}
if (getViewportContentType (v) != 0)
{
String classNm (getViewportGenericComponentClass (v));
if (getViewportContentType (v) == 1)
{
File file;
const String filename (getViewportJucerComponentFile (v));
if (filename.isNotEmpty())
file = code.document->getCppFile().getSiblingFile (filename);
std::unique_ptr<JucerDocument> doc (JucerDocument::createForCppFile (nullptr, file));
if (doc != nullptr)
{
code.includeFilesCPP.add (File::createFileWithoutCheckingPath (doc->getHeaderFile()
.getRelativePathFrom (code.document->getCppFile().getParentDirectory())
.replaceCharacter ('\\', '/')));
classNm = doc->getClassName();
}
else
{
classNm = String();
}
}
if (classNm.isNotEmpty())
{
code.constructorCode
<< memberVariableName << "->setViewedComponent (new "
<< classNm;
if (getViewportConstructorParams (v).trim().isNotEmpty())
{
code.constructorCode << " (" << getViewportConstructorParams (v).trim() << "));\n";
}
else
{
code.constructorCode << "());\n";
}
}
}
code.constructorCode << "\n";
}
static void updateViewportContentComp (Viewport* vp)
{
if (getViewportContentType (vp) == 1)
{
JucerDocument* doc = findParentDocument (vp);
auto tc = new TestComponent (doc, nullptr, false);
tc->setFilename (getViewportJucerComponentFile (vp));
tc->setToInitialSize();
vp->setViewedComponent (tc);
}
else
{
vp->setViewedComponent (new ViewportDemoContentComp());
}
}
static int getViewportContentType (Viewport* vp)
{
return vp->getProperties() ["contentType"];
}
static void setViewportContentType (Viewport* vp, int newValue)
{
if (newValue != getViewportContentType (vp))
{
vp->getProperties().set ("contentType", newValue);
updateViewportContentComp (vp);
}
}
static String getViewportJucerComponentFile (Viewport* vp)
{
return vp->getProperties() ["jucerFile"].toString();
}
static void setViewportJucerComponentFile (Viewport* vp, const String& file)
{
if (file != getViewportJucerComponentFile (vp))
{
vp->getProperties().set ("jucerFile", file);
updateViewportContentComp (vp);
}
}
static String getViewportGenericComponentClass (Viewport* vp)
{
return vp->getProperties() ["contentClass"].toString();
}
static void setViewportGenericComponentClass (Viewport* vp, const String& name)
{
if (name != getViewportGenericComponentClass (vp))
{
vp->getProperties().set ("contentClass", name);
updateViewportContentComp (vp);
}
}
static String getViewportConstructorParams (Viewport* vp)
{
return vp->getProperties() ["constructorParams"].toString();
}
static void setViewportConstructorParams (Viewport* vp, const String& newParams)
{
if (newParams != getViewportConstructorParams (vp))
{
vp->getProperties().set ("constructorParams", newParams);
updateViewportContentComp (vp);
}
}
private:
//==============================================================================
class UpdatingViewport : public Viewport
{
public:
UpdatingViewport (const String& name)
: Viewport (name)
{
}
void parentHierarchyChanged()
{
Viewport::parentHierarchyChanged();
updateViewportContentComp (this);
}
};
//==============================================================================
struct ViewportDemoContentComp : public Component
{
ViewportDemoContentComp()
{
setSize (2048, 2048);
}
void paint (Graphics& g) override
{
g.fillCheckerBoard (getLocalBounds().toFloat(), 50.0f, 50.0f,
Colours::lightgrey.withAlpha (0.5f),
Colours::darkgrey.withAlpha (0.5f));
}
};
//==============================================================================
class ViewportScrollbarShownProperty : public ComponentBooleanProperty <Viewport>
{
public:
ViewportScrollbarShownProperty (Viewport* comp, JucerDocument& doc, const bool vertical_)
: ComponentBooleanProperty <Viewport> (vertical_ ? "V scrollbar" : "H scrollbar",
"enabled", "enabled",
comp, doc),
vertical (vertical_)
{
}
void setState (bool newState)
{
document.perform (new ViewportScrollbarChangeAction (component, *document.getComponentLayout(), vertical, newState),
"Change Viewport scrollbar");
}
bool getState() const
{
return vertical ? component->isVerticalScrollBarShown()
: component->isHorizontalScrollBarShown();
}
const bool vertical;
private:
class ViewportScrollbarChangeAction : public ComponentUndoableAction <Viewport>
{
public:
ViewportScrollbarChangeAction (Viewport* const comp, ComponentLayout& l, const bool vertical_, const bool newState_)
: ComponentUndoableAction <Viewport> (comp, l),
vertical (vertical_),
newState (newState_)
{
oldState = vertical ? comp->isVerticalScrollBarShown()
: comp->isHorizontalScrollBarShown();
}
bool perform()
{
showCorrectTab();
if (vertical)
getComponent()->setScrollBarsShown (newState, getComponent()->isHorizontalScrollBarShown());
else
getComponent()->setScrollBarsShown (getComponent()->isVerticalScrollBarShown(), newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
if (vertical)
getComponent()->setScrollBarsShown (oldState, getComponent()->isHorizontalScrollBarShown());
else
getComponent()->setScrollBarsShown (getComponent()->isVerticalScrollBarShown(), oldState);
changed();
return true;
}
bool vertical, newState, oldState;
};
};
//==============================================================================
class ViewportScrollbarSizeProperty : public SliderPropertyComponent,
private ChangeListener
{
public:
ViewportScrollbarSizeProperty (Viewport* comp, JucerDocument& doc)
: SliderPropertyComponent ("scrollbar size", 3.0, 30.0, 1.0, 1.0),
component (comp),
document (doc)
{
document.addChangeListener (this);
}
~ViewportScrollbarSizeProperty() override
{
document.removeChangeListener (this);
}
void setValue (double newValue) override
{
document.getUndoManager().undoCurrentTransactionOnly();
document.perform (new ViewportScrollbarSizeChangeAction (component, *document.getComponentLayout(), roundToInt (newValue)),
"Change Viewport scrollbar size");
}
double getValue() const override
{
return component->getScrollBarThickness();
}
private:
void changeListenerCallback (ChangeBroadcaster*) override
{
refresh();
}
Viewport* component;
JucerDocument& document;
private:
class ViewportScrollbarSizeChangeAction : public ComponentUndoableAction <Viewport>
{
public:
ViewportScrollbarSizeChangeAction (Viewport* const comp, ComponentLayout& l, const int newState_)
: ComponentUndoableAction <Viewport> (comp, l),
newState (newState_)
{
oldState = comp->getScrollBarThickness();
}
bool perform()
{
showCorrectTab();
getComponent()->setScrollBarThickness (newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
getComponent()->setScrollBarThickness (newState);
changed();
return true;
}
int newState, oldState;
};
};
//==============================================================================
class ViewportContentTypeProperty : public ComponentChoiceProperty <Viewport>
{
public:
ViewportContentTypeProperty (Viewport* comp, JucerDocument& doc)
: ComponentChoiceProperty <Viewport> ("content", comp, doc)
{
choices.add ("No content component");
choices.add ("Jucer content component");
choices.add ("Named content component");
}
void setIndex (int newIndex)
{
document.perform (new ViewportContentTypeChangeAction (component, *document.getComponentLayout(), newIndex),
"Change Viewport content type");
}
int getIndex() const
{
return getViewportContentType (component);
}
private:
class ViewportContentTypeChangeAction : public ComponentUndoableAction <Viewport>
{
public:
ViewportContentTypeChangeAction (Viewport* const comp, ComponentLayout& l, const int newValue_)
: ComponentUndoableAction <Viewport> (comp, l),
newValue (newValue_)
{
oldValue = getViewportContentType (comp);
}
bool perform()
{
showCorrectTab();
setViewportContentType (getComponent(), newValue);
changed();
layout.getDocument()->refreshAllPropertyComps();
return true;
}
bool undo()
{
showCorrectTab();
setViewportContentType (getComponent(), oldValue);
changed();
layout.getDocument()->refreshAllPropertyComps();
return true;
}
int newValue, oldValue;
};
};
//==============================================================================
class ViewportJucerFileProperty : public FilePropertyComponent,
private ChangeListener
{
public:
ViewportJucerFileProperty (Viewport* const comp, JucerDocument& doc)
: FilePropertyComponent ("Jucer file", false, true),
component (comp),
document (doc)
{
document.addChangeListener (this);
}
~ViewportJucerFileProperty() override
{
document.removeChangeListener (this);
}
void setFile (const File& newFile) override
{
document.perform (new JucerCompFileChangeAction (component, *document.getComponentLayout(),
newFile.getRelativePathFrom (document.getCppFile().getParentDirectory())
.replaceCharacter ('\\', '/')),
"Change Projucer component file");
}
File getFile() const override
{
auto filename = getViewportJucerComponentFile (component);
if (filename.isEmpty())
return {};
return document.getCppFile().getSiblingFile (filename);
}
private:
void changeListenerCallback (ChangeBroadcaster*) override
{
refresh();
}
Viewport* const component;
JucerDocument& document;
class JucerCompFileChangeAction : public ComponentUndoableAction <Viewport>
{
public:
JucerCompFileChangeAction (Viewport* const comp, ComponentLayout& l, const String& newState_)
: ComponentUndoableAction <Viewport> (comp, l),
newState (newState_)
{
oldState = getViewportJucerComponentFile (comp);
}
bool perform()
{
showCorrectTab();
setViewportJucerComponentFile (getComponent(), newState);
changed();
return true;
}
bool undo()
{
showCorrectTab();
setViewportJucerComponentFile (getComponent(), oldState);
changed();
return true;
}
String newState, oldState;
};
};
//==============================================================================
class ViewportContentClassProperty : public ComponentTextProperty <Viewport>
{
public:
ViewportContentClassProperty (Viewport* comp, JucerDocument& doc)
: ComponentTextProperty <Viewport> ("content class", 256, false, comp, doc)
{
}
void setText (const String& newText) override
{
document.perform (new ViewportClassNameChangeAction (component, *document.getComponentLayout(), newText),
"Change Viewport content class");
}
String getText() const override
{
return getViewportGenericComponentClass (component);
}
private:
class ViewportClassNameChangeAction : public ComponentUndoableAction <Viewport>
{
public:
ViewportClassNameChangeAction (Viewport* const comp, ComponentLayout& l, const String& newValue_)
: ComponentUndoableAction <Viewport> (comp, l),
newValue (newValue_)
{
oldValue = getViewportGenericComponentClass (comp);
}
bool perform()
{
showCorrectTab();
setViewportGenericComponentClass (getComponent(), newValue);
changed();
layout.getDocument()->refreshAllPropertyComps();
return true;
}
bool undo()
{
showCorrectTab();
setViewportGenericComponentClass (getComponent(), oldValue);
changed();
layout.getDocument()->refreshAllPropertyComps();
return true;
}
String newValue, oldValue;
};
};
//==============================================================================
class ConstructorParamsProperty : public ComponentTextProperty <Viewport>
{
public:
ConstructorParamsProperty (Viewport* comp, JucerDocument& doc)
: ComponentTextProperty <Viewport> ("constructor params", 512, false, comp, doc)
{
}
void setText (const String& newText) override
{
document.perform (new ConstructorParamChangeAction (component, *document.getComponentLayout(), newText),
"Change Viewport content constructor params");
}
String getText() const override
{
return getViewportConstructorParams (component);
}
private:
class ConstructorParamChangeAction : public ComponentUndoableAction <Viewport>
{
public:
ConstructorParamChangeAction (Viewport* const comp, ComponentLayout& l, const String& newValue_)
: ComponentUndoableAction <Viewport> (comp, l),
newValue (newValue_)
{
oldValue = getViewportConstructorParams (comp);
}
bool perform()
{
showCorrectTab();
setViewportConstructorParams (getComponent(), newValue);
changed();
layout.getDocument()->refreshAllPropertyComps();
return true;
}
bool undo()
{
showCorrectTab();
setViewportConstructorParams (getComponent(), oldValue);
changed();
layout.getDocument()->refreshAllPropertyComps();
return true;
}
String newValue, oldValue;
};
};
};

View File

@@ -0,0 +1,411 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "jucer_ButtonDocument.h"
#include "../jucer_UtilityFunctions.h"
//==============================================================================
static const int normalOff = 0;
static const int overOff = 1;
static const int downOff = 2;
static const int normalOn = 3;
static const int overOn = 4;
static const int downOn = 5;
static const int background = 6;
//==============================================================================
ButtonDocument::ButtonDocument (SourceCodeDocument* c)
: JucerDocument (c)
{
paintStatesEnabled [normalOff] = true;
paintStatesEnabled [overOff] = true;
paintStatesEnabled [downOff] = true;
paintStatesEnabled [normalOn] = false;
paintStatesEnabled [overOn] = false;
paintStatesEnabled [downOn] = false;
paintStatesEnabled [background] = false;
parentClasses = "public juce::Button";
for (int i = 7; --i >= 0;)
{
paintRoutines[i].reset (new PaintRoutine());
paintRoutines[i]->setDocument (this);
paintRoutines[i]->setBackgroundColour (Colours::transparentBlack);
}
}
ButtonDocument::~ButtonDocument()
{
}
static const char* const stateNames[] =
{
"normal", "over", "down",
"normal on", "over on", "down on",
"common background"
};
static int stateNameToIndex (const String& name)
{
for (int i = 7; --i >= 0;)
if (name.equalsIgnoreCase (stateNames[i]))
return i;
jassertfalse;
return normalOff;
}
int ButtonDocument::getNumPaintRoutines() const
{
int n = 0;
for (int i = 7; --i >= 0;)
if (paintStatesEnabled [i])
++n;
return n;
}
StringArray ButtonDocument::getPaintRoutineNames() const
{
StringArray s;
for (int i = 0; i < 7; ++i)
if (paintStatesEnabled [i])
s.add (stateNames [i]);
return s;
}
PaintRoutine* ButtonDocument::getPaintRoutine (const int index) const
{
int n = 0;
for (int i = 0; i < 7; ++i)
{
if (paintStatesEnabled [i])
{
if (index == n)
return paintRoutines[i].get();
++n;
}
}
jassertfalse;
return {};
}
void ButtonDocument::setStatePaintRoutineEnabled (const int index, bool b)
{
jassert (index > 0 && index < 7);
if (paintStatesEnabled [index] != b)
{
paintStatesEnabled [index] = b;
changed();
}
}
bool ButtonDocument::isStatePaintRoutineEnabled (const int index) const
{
return paintStatesEnabled [index];
}
int ButtonDocument::chooseBestEnabledPaintRoutine (int paintRoutineWanted) const
{
switch (paintRoutineWanted)
{
case normalOff: return normalOff;
case overOff: return paintStatesEnabled [overOff] ? overOff : normalOff;
case downOff: return paintStatesEnabled [downOff] ? downOff : chooseBestEnabledPaintRoutine (overOff);
case normalOn: return paintStatesEnabled [normalOn] ? normalOn : normalOff;
case overOn: return paintStatesEnabled [overOn] ? overOn : (paintStatesEnabled [normalOn] ? normalOn : chooseBestEnabledPaintRoutine (overOff));
case downOn: return paintStatesEnabled [downOn] ? downOn : ((paintStatesEnabled [overOn] || paintStatesEnabled [normalOn])
? chooseBestEnabledPaintRoutine (overOn)
: chooseBestEnabledPaintRoutine (downOff));
default: jassertfalse; break;
}
return normalOff;
}
//==============================================================================
String ButtonDocument::getTypeName() const
{
return "Button";
}
JucerDocument* ButtonDocument::createCopy()
{
auto newOne = new ButtonDocument (cpp);
newOne->resources = resources;
newOne->loadFromXml (*createXml());
return newOne;
}
std::unique_ptr<XmlElement> ButtonDocument::createXml() const
{
auto doc = JucerDocument::createXml();
for (int i = 0; i < 7; ++i)
{
auto e = paintRoutines[i]->createXml();
e->setAttribute ("buttonState", stateNames [i]);
e->setAttribute ("enabled", paintStatesEnabled [i]);
doc->addChildElement (e);
}
return doc;
}
bool ButtonDocument::loadFromXml (const XmlElement& xml)
{
if (JucerDocument::loadFromXml (xml))
{
for (int i = 7; --i >= 0;)
paintStatesEnabled [i] = false;
for (auto* e : xml.getChildWithTagNameIterator (PaintRoutine::xmlTagName))
{
const int stateIndex = stateNameToIndex (e->getStringAttribute ("buttonState"));
paintRoutines [stateIndex]->loadFromXml (*e);
paintStatesEnabled [stateIndex] = e->getBoolAttribute ("enabled", stateIndex < normalOn);
}
changed();
getUndoManager().clearUndoHistory();
return true;
}
return false;
}
void ButtonDocument::getOptionalMethods (StringArray& baseClasses,
StringArray& returnValues,
StringArray& methods,
StringArray& initialContents) const
{
JucerDocument::getOptionalMethods (baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Button", "void", "clicked()", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Button", "void", "buttonStateChanged()", "", baseClasses, returnValues, methods, initialContents);
}
//==============================================================================
class ButtonStatePaintEnabledProperty : public BooleanPropertyComponent,
private ChangeListener
{
public:
ButtonStatePaintEnabledProperty (const String& name, ButtonDocument& doc, const int stateMethod_)
: BooleanPropertyComponent (name, "enabled", "disabled"),
document (doc),
stateMethod (stateMethod_)
{
document.addChangeListener (this);
}
~ButtonStatePaintEnabledProperty()
{
document.removeChangeListener (this);
}
void setState (bool newState)
{
document.setStatePaintRoutineEnabled (stateMethod, newState);
}
bool getState() const
{
return document.isStatePaintRoutineEnabled (stateMethod);
}
private:
void changeListenerCallback (ChangeBroadcaster*)
{
refresh();
}
ButtonDocument& document;
const int stateMethod;
};
void ButtonDocument::addExtraClassProperties (PropertyPanel& panel)
{
Array <PropertyComponent*> props;
for (int i = 1; i < 7; ++i)
props.add (new ButtonStatePaintEnabledProperty (stateNames[i], *this, i));
panel.addSection ("Button paint routines", props);
}
//==============================================================================
class ButtonTestComponent : public Button
{
public:
ButtonTestComponent (ButtonDocument* const doc, const bool fillBackground)
: Button (String()),
document (doc),
alwaysFillBackground (fillBackground)
{
setClickingTogglesState (true);
}
void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override
{
if (document->paintStatesEnabled [background])
{
document->paintRoutines [background]->fillWithBackground (g, alwaysFillBackground);
document->paintRoutines [background]->drawElements (g, getLocalBounds());
}
const int stateIndex
= getToggleState()
? (isButtonDown ? document->chooseBestEnabledPaintRoutine (downOn)
: (isMouseOverButton ? document->chooseBestEnabledPaintRoutine (overOn)
: document->chooseBestEnabledPaintRoutine (normalOn)))
: (isButtonDown ? document->chooseBestEnabledPaintRoutine (downOff)
: (isMouseOverButton ? document->chooseBestEnabledPaintRoutine (overOff)
: normalOff));
document->paintRoutines [stateIndex]->fillWithBackground (g, ! document->paintStatesEnabled [background]);
document->paintRoutines [stateIndex]->drawElements (g, getLocalBounds());
}
private:
ButtonDocument* const document;
const bool alwaysFillBackground;
};
Component* ButtonDocument::createTestComponent (const bool alwaysFillBackground)
{
return new ButtonTestComponent (this, alwaysFillBackground);
}
//==============================================================================
void ButtonDocument::fillInGeneratedCode (GeneratedCode& code) const
{
JucerDocument::fillInGeneratedCode (code);
code.parentClassInitialiser = "Button (" + quotedString (code.componentName, false) + ")";
code.removeCallback ("void", "paint (Graphics& g)");
}
void ButtonDocument::fillInPaintCode (GeneratedCode& code) const
{
jassert (paintStatesEnabled [normalOff]);
String paintCode [7];
for (int i = 0; i < 7; ++i)
if (paintStatesEnabled [i])
paintRoutines[i]->fillInGeneratedCode (code, paintCode [i]);
String& s = code.getCallbackCode ("public juce::Button",
"void",
"paintButton (juce::Graphics& g, bool isMouseOverButton, bool isButtonDown)",
false);
int numPaintRoutines = getNumPaintRoutines();
if (paintStatesEnabled [background])
{
s << paintCode [background] << "\n";
--numPaintRoutines;
}
if (numPaintRoutines == 1)
{
s << paintCode [normalOff];
}
else if (numPaintRoutines == downOff && (paintStatesEnabled [overOff] || paintStatesEnabled [downOff] || paintStatesEnabled [normalOn]))
{
if (paintStatesEnabled [normalOn])
{
s << "if (getToggleState())\n{\n "
<< CodeHelpers::indent (paintCode [normalOn], 4, false).trimEnd();
}
else if (paintStatesEnabled [overOff])
{
s << "if (isButtonDown || isMouseOverButton)\n{\n "
<< CodeHelpers::indent (paintCode [overOff], 4, false).trimEnd();
}
else
{
s << "if (isButtonDown)\n{\n "
<< CodeHelpers::indent (paintCode [downOff], 4, false).trimEnd();
}
s << "\n}\nelse\n{\n "
<< CodeHelpers::indent (paintCode [normalOff], 4, false).trimEnd()
<< "\n}\n";
}
else if (numPaintRoutines == normalOn && paintStatesEnabled [overOff] && paintStatesEnabled [downOff])
{
s << "if (isButtonDown)\n{\n "
<< CodeHelpers::indent (paintCode [downOff], 4, false).trimEnd()
<< "\n}\nelse if (isMouseOverButton)\n{\n "
<< CodeHelpers::indent (paintCode [overOff], 4, false).trimEnd()
<< "\n}\nelse\n{\n "
<< CodeHelpers::indent (paintCode [normalOff], 4, false).trimEnd()
<< "\n}\n";
}
else
{
if (paintStatesEnabled [normalOn] || paintStatesEnabled [overOn] || paintStatesEnabled [downOn])
{
s << "switch (getToggleState() ? (isButtonDown ? "
<< chooseBestEnabledPaintRoutine (downOn) << " : (isMouseOverButton ? "
<< chooseBestEnabledPaintRoutine (overOn) << " : "
<< chooseBestEnabledPaintRoutine (normalOn) << "))\n : (isButtonDown ? "
<< chooseBestEnabledPaintRoutine (downOff) << " : (isMouseOverButton ? "
<< chooseBestEnabledPaintRoutine (overOff) << " : 0)))\n{\n";
}
else
{
s << "switch (isButtonDown ? " << chooseBestEnabledPaintRoutine (downOff)
<< " : (isMouseOverButton ? " << chooseBestEnabledPaintRoutine (overOff)
<< " : 0))\n{\n";
}
for (int i = 0; i < 6; ++i)
{
if (paintStatesEnabled [i])
{
s << "case " << i << ":\n {\n "
<< CodeHelpers::indent (paintCode [i], 8, false).trimEnd()
<< "\n break;\n }\n\n";
}
}
s << "default:\n break;\n}\n";
}
}

View File

@@ -0,0 +1,71 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_JucerDocument.h"
//==============================================================================
class ButtonDocument : public JucerDocument
{
public:
ButtonDocument (SourceCodeDocument* cpp);
~ButtonDocument();
//==============================================================================
String getTypeName() const;
JucerDocument* createCopy();
Component* createTestComponent (const bool alwaysFillBackground);
int getNumPaintRoutines() const;
StringArray getPaintRoutineNames() const;
PaintRoutine* getPaintRoutine (const int index) const;
void setStatePaintRoutineEnabled (const int index, bool b);
bool isStatePaintRoutineEnabled (const int index) const;
int chooseBestEnabledPaintRoutine (int paintRoutineWanted) const;
ComponentLayout* getComponentLayout() const { return nullptr; }
void addExtraClassProperties (PropertyPanel&);
//==============================================================================
std::unique_ptr<XmlElement> createXml() const;
bool loadFromXml (const XmlElement&);
void fillInGeneratedCode (GeneratedCode& code) const;
void fillInPaintCode (GeneratedCode& code) const;
void getOptionalMethods (StringArray& baseClasses,
StringArray& returnValues,
StringArray& methods,
StringArray& initialContents) const;
//==============================================================================
std::unique_ptr<PaintRoutine> paintRoutines[7];
bool paintStatesEnabled [7];
};

View File

@@ -0,0 +1,170 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "jucer_ComponentDocument.h"
//==============================================================================
ComponentDocument::ComponentDocument (SourceCodeDocument* c)
: JucerDocument (c)
{
components.reset (new ComponentLayout());
components->setDocument (this);
backgroundGraphics.reset (new PaintRoutine());
backgroundGraphics->setDocument (this);
}
ComponentDocument::~ComponentDocument()
{
}
//==============================================================================
String ComponentDocument::getTypeName() const
{
return "Component";
}
JucerDocument* ComponentDocument::createCopy()
{
auto newOne = new ComponentDocument (cpp);
newOne->resources = resources;
newOne->loadFromXml (*createXml());
return newOne;
}
std::unique_ptr<XmlElement> ComponentDocument::createXml() const
{
auto doc = JucerDocument::createXml();
doc->addChildElement (backgroundGraphics->createXml());
components->addToXml (*doc);
return doc;
}
bool ComponentDocument::loadFromXml (const XmlElement& xml)
{
if (JucerDocument::loadFromXml (xml))
{
components->clearComponents();
for (auto* e : xml.getChildIterator())
{
if (e->hasTagName (PaintRoutine::xmlTagName))
backgroundGraphics->loadFromXml (*e);
else
components->addComponentFromXml (*e, false);
}
changed();
getUndoManager().clearUndoHistory();
return true;
}
return false;
}
void ComponentDocument::applyCustomPaintSnippets (StringArray& snippets)
{
backgroundGraphics->applyCustomPaintSnippets (snippets);
}
//==============================================================================
class NormalTestComponent : public Component
{
public:
NormalTestComponent (ComponentDocument* const doc, const bool fillBackground)
: document (doc),
alwaysFillBackground (fillBackground)
{
ComponentLayout* const layout = document->getComponentLayout();
for (int i = 0; i < layout->getNumComponents(); ++i)
addAndMakeVisible (layout->getComponent (i));
}
~NormalTestComponent() override
{
for (int i = getNumChildComponents(); --i >= 0;)
removeChildComponent (i);
}
void paint (Graphics& g) override
{
document->getPaintRoutine (0)->fillWithBackground (g, alwaysFillBackground);
document->getPaintRoutine (0)->drawElements (g, getLocalBounds());
}
void resized() override
{
if (! getBounds().isEmpty())
{
int numTimesToTry = 10;
while (--numTimesToTry >= 0)
{
bool anyCompsMoved = false;
for (int i = 0; i < getNumChildComponents(); ++i)
{
Component* comp = getChildComponent (i);
if (ComponentTypeHandler* const type = ComponentTypeHandler::getHandlerFor (*comp))
{
const Rectangle<int> newBounds (type->getComponentPosition (comp)
.getRectangle (getLocalBounds(),
document->getComponentLayout()));
anyCompsMoved = anyCompsMoved || (comp->getBounds() != newBounds);
comp->setBounds (newBounds);
}
}
// repeat this loop until they've all stopped shuffling (might require a few
// loops for all the relative positioned comps to settle down)
if (! anyCompsMoved)
break;
}
}
}
private:
ComponentDocument* const document;
const bool alwaysFillBackground;
};
Component* ComponentDocument::createTestComponent (const bool alwaysFillBackground)
{
return new NormalTestComponent (this, alwaysFillBackground);
}
void ComponentDocument::fillInGeneratedCode (GeneratedCode& code) const
{
JucerDocument::fillInGeneratedCode (code);
}

View File

@@ -0,0 +1,59 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_JucerDocument.h"
//==============================================================================
class ComponentDocument : public JucerDocument
{
public:
ComponentDocument (SourceCodeDocument* cpp);
~ComponentDocument();
//==============================================================================
String getTypeName() const;
JucerDocument* createCopy();
Component* createTestComponent (const bool alwaysFillBackground);
int getNumPaintRoutines() const { return 1; }
StringArray getPaintRoutineNames() const { return StringArray ("Graphics"); }
PaintRoutine* getPaintRoutine (const int index) const { return index == 0 ? backgroundGraphics.get() : nullptr; }
ComponentLayout* getComponentLayout() const { return components.get(); }
//==============================================================================
std::unique_ptr<XmlElement> createXml() const;
bool loadFromXml (const XmlElement& xml);
void fillInGeneratedCode (GeneratedCode& code) const;
void applyCustomPaintSnippets (StringArray&);
private:
std::unique_ptr<ComponentLayout> components;
std::unique_ptr<PaintRoutine> backgroundGraphics;
};

View File

@@ -0,0 +1,936 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "jucer_ColouredElement.h"
#include "jucer_GradientPointComponent.h"
#include "../Properties/jucer_PositionPropertyBase.h"
#include "../Properties/jucer_ColourPropertyComponent.h"
#include "jucer_PaintElementUndoableAction.h"
#include "jucer_PaintElementPath.h"
#include "jucer_ImageResourceProperty.h"
//==============================================================================
class ElementFillModeProperty : public ChoicePropertyComponent
{
public:
ElementFillModeProperty (ColouredElement* const e, const bool isForStroke_)
: ChoicePropertyComponent ("fill mode"), listener (e),
isForStroke (isForStroke_)
{
listener.setPropertyToRefresh (*this);
choices.add ("Solid Colour");
choices.add ("Linear Gradient");
choices.add ("Radial Gradient");
choices.add ("Image Brush");
}
void setIndex (int newIndex)
{
JucerFillType fill (isForStroke ? listener.owner->getStrokeType().fill
: listener.owner->getFillType());
switch (newIndex)
{
case 0: fill.mode = JucerFillType::solidColour; break;
case 1: fill.mode = JucerFillType::linearGradient; break;
case 2: fill.mode = JucerFillType::radialGradient; break;
case 3: fill.mode = JucerFillType::imageBrush; break;
default: jassertfalse; break;
}
if (! isForStroke)
listener.owner->setFillType (fill, true);
else
listener.owner->setStrokeFill (fill, true);
}
int getIndex() const
{
switch (isForStroke ? listener.owner->getStrokeType().fill.mode
: listener.owner->getFillType().mode)
{
case JucerFillType::solidColour: return 0;
case JucerFillType::linearGradient: return 1;
case JucerFillType::radialGradient: return 2;
case JucerFillType::imageBrush: return 3;
default: jassertfalse; break;
}
return 0;
}
private:
ElementListener<ColouredElement> listener;
const bool isForStroke;
};
//==============================================================================
class ElementFillColourProperty : public JucerColourPropertyComponent
{
public:
enum ColourType
{
solidColour,
gradientColour1,
gradientColour2
};
ElementFillColourProperty (const String& name,
ColouredElement* const owner_,
const ColourType type_,
const bool isForStroke_)
: JucerColourPropertyComponent (name, false),
listener (owner_),
type (type_),
isForStroke (isForStroke_)
{
listener.setPropertyToRefresh (*this);
}
void setColour (Colour newColour) override
{
listener.owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
JucerFillType fill (isForStroke ? listener.owner->getStrokeType().fill
: listener.owner->getFillType());
switch (type)
{
case solidColour: fill.colour = newColour; break;
case gradientColour1: fill.gradCol1 = newColour; break;
case gradientColour2: fill.gradCol2 = newColour; break;
default: jassertfalse; break;
}
if (! isForStroke)
listener.owner->setFillType (fill, true);
else
listener.owner->setStrokeFill (fill, true);
}
Colour getColour() const override
{
const JucerFillType fill (isForStroke ? listener.owner->getStrokeType().fill
: listener.owner->getFillType());
switch (type)
{
case solidColour: return fill.colour; break;
case gradientColour1: return fill.gradCol1; break;
case gradientColour2: return fill.gradCol2; break;
default: jassertfalse; break;
}
return Colours::black;
}
void resetToDefault() override
{
jassertfalse; // option shouldn't be visible
}
private:
ElementListener<ColouredElement> listener;
const ColourType type;
const bool isForStroke;
};
//==============================================================================
class ElementFillPositionProperty : public PositionPropertyBase
{
public:
ElementFillPositionProperty (ColouredElement* const owner_,
const String& name,
ComponentPositionDimension dimension_,
const bool isStart_,
const bool isForStroke_)
: PositionPropertyBase (owner_, name, dimension_, false, false,
owner_->getDocument()->getComponentLayout()),
listener (owner_),
isStart (isStart_),
isForStroke (isForStroke_)
{
listener.setPropertyToRefresh (*this);
}
void setPosition (const RelativePositionedRectangle& newPos)
{
JucerFillType fill (isForStroke ? listener.owner->getStrokeType().fill
: listener.owner->getFillType());
if (isStart)
fill.gradPos1 = newPos;
else
fill.gradPos2 = newPos;
if (! isForStroke)
listener.owner->setFillType (fill, true);
else
listener.owner->setStrokeFill (fill, true);
}
RelativePositionedRectangle getPosition() const
{
const JucerFillType fill (isForStroke ? listener.owner->getStrokeType().fill
: listener.owner->getFillType());
return isStart ? fill.gradPos1
: fill.gradPos2;
}
private:
ElementListener<ColouredElement> listener;
const bool isStart, isForStroke;
};
//==============================================================================
class EnableStrokeProperty : public BooleanPropertyComponent
{
public:
explicit EnableStrokeProperty (ColouredElement* const owner_)
: BooleanPropertyComponent ("outline", "Outline enabled", "No outline"),
listener (owner_)
{
listener.setPropertyToRefresh (*this);
}
//==============================================================================
void setState (bool newState) { listener.owner->enableStroke (newState, true); }
bool getState() const { return listener.owner->isStrokeEnabled(); }
ElementListener<ColouredElement> listener;
};
//==============================================================================
class StrokeThicknessProperty : public SliderPropertyComponent
{
public:
explicit StrokeThicknessProperty (ColouredElement* const owner_)
: SliderPropertyComponent ("outline thickness", 0.1, 200.0, 0.1, 0.3),
listener (owner_)
{
listener.setPropertyToRefresh (*this);
}
void setValue (double newValue)
{
listener.owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
listener.owner->setStrokeType (PathStrokeType ((float) newValue,
listener.owner->getStrokeType().stroke.getJointStyle(),
listener.owner->getStrokeType().stroke.getEndStyle()),
true);
}
double getValue() const { return listener.owner->getStrokeType().stroke.getStrokeThickness(); }
ElementListener<ColouredElement> listener;
};
//==============================================================================
class StrokeJointProperty : public ChoicePropertyComponent
{
public:
explicit StrokeJointProperty (ColouredElement* const owner_)
: ChoicePropertyComponent ("joint style"),
listener (owner_)
{
listener.setPropertyToRefresh (*this);
choices.add ("mitered");
choices.add ("curved");
choices.add ("beveled");
}
void setIndex (int newIndex)
{
const PathStrokeType::JointStyle joints[] = { PathStrokeType::mitered,
PathStrokeType::curved,
PathStrokeType::beveled };
if (! isPositiveAndBelow (newIndex, numElementsInArray (joints)))
{
jassertfalse;
return;
}
listener.owner->setStrokeType (PathStrokeType (listener.owner->getStrokeType().stroke.getStrokeThickness(),
joints [newIndex],
listener.owner->getStrokeType().stroke.getEndStyle()),
true);
}
int getIndex() const
{
switch (listener.owner->getStrokeType().stroke.getJointStyle())
{
case PathStrokeType::mitered: return 0;
case PathStrokeType::curved: return 1;
case PathStrokeType::beveled: return 2;
default: jassertfalse; break;
}
return 0;
}
ElementListener<ColouredElement> listener;
};
//==============================================================================
class StrokeEndCapProperty : public ChoicePropertyComponent
{
public:
explicit StrokeEndCapProperty (ColouredElement* const owner_)
: ChoicePropertyComponent ("end-cap style"),
listener (owner_)
{
listener.setPropertyToRefresh (*this);
choices.add ("butt");
choices.add ("square");
choices.add ("round");
}
void setIndex (int newIndex)
{
const PathStrokeType::EndCapStyle ends[] = { PathStrokeType::butt,
PathStrokeType::square,
PathStrokeType::rounded };
if (! isPositiveAndBelow (newIndex, numElementsInArray (ends)))
{
jassertfalse;
return;
}
listener.owner->setStrokeType (PathStrokeType (listener.owner->getStrokeType().stroke.getStrokeThickness(),
listener.owner->getStrokeType().stroke.getJointStyle(),
ends [newIndex]),
true);
}
int getIndex() const
{
switch (listener.owner->getStrokeType().stroke.getEndStyle())
{
case PathStrokeType::butt: return 0;
case PathStrokeType::square: return 1;
case PathStrokeType::rounded: return 2;
default: jassertfalse; break;
}
return 0;
}
ElementListener<ColouredElement> listener;
};
//==============================================================================
class ImageBrushResourceProperty : public ImageResourceProperty <ColouredElement>
{
public:
ImageBrushResourceProperty (ColouredElement* const e, const bool isForStroke_)
: ImageResourceProperty <ColouredElement> (e, isForStroke_ ? "stroke image"
: "fill image"),
isForStroke (isForStroke_)
{
}
//==============================================================================
void setResource (const String& newName)
{
if (element != nullptr)
{
if (isForStroke)
{
JucerFillType type (element->getStrokeType().fill);
type.imageResourceName = newName;
element->setStrokeFill (type, true);
}
else
{
JucerFillType type (element->getFillType());
type.imageResourceName = newName;
element->setFillType (type, true);
}
}
}
String getResource() const
{
if (element == nullptr)
return {};
if (isForStroke)
return element->getStrokeType().fill.imageResourceName;
return element->getFillType().imageResourceName;
}
private:
bool isForStroke;
};
//==============================================================================
class ImageBrushPositionProperty : public PositionPropertyBase
{
public:
ImageBrushPositionProperty (ColouredElement* const owner_,
const String& name,
ComponentPositionDimension dimension_,
const bool isForStroke_)
: PositionPropertyBase (owner_, name, dimension_, false, false,
owner_->getDocument()->getComponentLayout()),
listener (owner_),
isForStroke (isForStroke_)
{
listener.setPropertyToRefresh (*this);
}
void setPosition (const RelativePositionedRectangle& newPos)
{
if (isForStroke)
{
JucerFillType type (listener.owner->getStrokeType().fill);
type.imageAnchor = newPos;
listener.owner->setStrokeFill (type, true);
}
else
{
JucerFillType type (listener.owner->getFillType());
type.imageAnchor = newPos;
listener.owner->setFillType (type, true);
}
}
RelativePositionedRectangle getPosition() const
{
if (isForStroke)
return listener.owner->getStrokeType().fill.imageAnchor;
return listener.owner->getFillType().imageAnchor;
}
private:
ElementListener<ColouredElement> listener;
const bool isForStroke;
};
//==============================================================================
class ImageBrushOpacityProperty : public SliderPropertyComponent
{
public:
ImageBrushOpacityProperty (ColouredElement* const e, const bool isForStroke_)
: SliderPropertyComponent ("opacity", 0.0, 1.0, 0.001),
listener (e),
isForStroke (isForStroke_)
{
listener.setPropertyToRefresh (*this);
}
void setValue (double newValue)
{
if (listener.owner != nullptr)
{
listener.owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
if (isForStroke)
{
JucerFillType type (listener.owner->getStrokeType().fill);
type.imageOpacity = newValue;
listener.owner->setStrokeFill (type, true);
}
else
{
JucerFillType type (listener.owner->getFillType());
type.imageOpacity = newValue;
listener.owner->setFillType (type, true);
}
}
}
double getValue() const
{
if (listener.owner == nullptr)
return 0;
if (isForStroke)
return listener.owner->getStrokeType().fill.imageOpacity;
return listener.owner->getFillType().imageOpacity;
}
private:
ElementListener<ColouredElement> listener;
bool isForStroke;
};
//==============================================================================
ColouredElement::ColouredElement (PaintRoutine* owner_,
const String& name,
const bool showOutline_,
const bool showJointAndEnd_)
: PaintElement (owner_, name),
isStrokePresent (false),
showOutline (showOutline_),
showJointAndEnd (showJointAndEnd_)
{
}
ColouredElement::~ColouredElement()
{
}
//==============================================================================
void ColouredElement::getEditableProperties (Array <PropertyComponent*>& props, bool multipleSelected)
{
PaintElement::getEditableProperties (props, multipleSelected);
if (! multipleSelected)
getColourSpecificProperties (props);
}
void ColouredElement::getColourSpecificProperties (Array <PropertyComponent*>& props)
{
props.add (new ElementFillModeProperty (this, false));
switch (getFillType().mode)
{
case JucerFillType::solidColour:
props.add (new ElementFillColourProperty ("colour", this, ElementFillColourProperty::solidColour, false));
break;
case JucerFillType::linearGradient:
case JucerFillType::radialGradient:
props.add (new ElementFillColourProperty ("colour 1", this, ElementFillColourProperty::gradientColour1, false));
props.add (new ElementFillPositionProperty (this, "x1", PositionPropertyBase::componentX, true, false));
props.add (new ElementFillPositionProperty (this, "y1", PositionPropertyBase::componentY, true, false));
props.add (new ElementFillColourProperty ("colour 2", this, ElementFillColourProperty::gradientColour2, false));
props.add (new ElementFillPositionProperty (this, "x2", PositionPropertyBase::componentX, false, false));
props.add (new ElementFillPositionProperty (this, "y2", PositionPropertyBase::componentY, false, false));
break;
case JucerFillType::imageBrush:
props.add (new ImageBrushResourceProperty (this, false));
props.add (new ImageBrushPositionProperty (this, "anchor x", PositionPropertyBase::componentX, false));
props.add (new ImageBrushPositionProperty (this, "anchor y", PositionPropertyBase::componentY, false));
props.add (new ImageBrushOpacityProperty (this, false));
break;
default:
jassertfalse;
break;
}
if (showOutline)
{
props.add (new EnableStrokeProperty (this));
if (isStrokePresent)
{
props.add (new StrokeThicknessProperty (this));
if (showJointAndEnd)
{
props.add (new StrokeJointProperty (this));
props.add (new StrokeEndCapProperty (this));
}
props.add (new ElementFillModeProperty (this, true));
switch (getStrokeType().fill.mode)
{
case JucerFillType::solidColour:
props.add (new ElementFillColourProperty ("colour", this, ElementFillColourProperty::solidColour, true));
break;
case JucerFillType::linearGradient:
case JucerFillType::radialGradient:
props.add (new ElementFillColourProperty ("colour 1", this, ElementFillColourProperty::gradientColour1, true));
props.add (new ElementFillPositionProperty (this, "x1", PositionPropertyBase::componentX, true, true));
props.add (new ElementFillPositionProperty (this, "y1", PositionPropertyBase::componentY, true, true));
props.add (new ElementFillColourProperty ("colour 2", this, ElementFillColourProperty::gradientColour2, true));
props.add (new ElementFillPositionProperty (this, "x2", PositionPropertyBase::componentX, false, true));
props.add (new ElementFillPositionProperty (this, "y2", PositionPropertyBase::componentY, false, true));
break;
case JucerFillType::imageBrush:
props.add (new ImageBrushResourceProperty (this, true));
props.add (new ImageBrushPositionProperty (this, "stroke anchor x", PositionPropertyBase::componentX, true));
props.add (new ImageBrushPositionProperty (this, "stroke anchor y", PositionPropertyBase::componentY, true));
props.add (new ImageBrushOpacityProperty (this, true));
break;
default:
jassertfalse;
break;
}
}
}
}
//==============================================================================
const JucerFillType& ColouredElement::getFillType() noexcept
{
return fillType;
}
class FillTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
{
public:
FillTypeChangeAction (ColouredElement* const element, const JucerFillType& newState_)
: PaintElementUndoableAction <ColouredElement> (element),
newState (newState_)
{
oldState = element->getFillType();
}
bool perform()
{
showCorrectTab();
getElement()->setFillType (newState, false);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setFillType (oldState, false);
return true;
}
private:
JucerFillType newState, oldState;
};
void ColouredElement::setFillType (const JucerFillType& newType, const bool undoable)
{
if (fillType != newType)
{
if (undoable)
{
perform (new FillTypeChangeAction (this, newType),
"Change fill type");
}
else
{
repaint();
if (fillType.mode != newType.mode)
{
owner->getSelectedElements().changed();
siblingComponentsChanged();
}
fillType = newType;
changed();
}
}
}
//==============================================================================
bool ColouredElement::isStrokeEnabled() const noexcept
{
return isStrokePresent && showOutline;
}
class StrokeEnableChangeAction : public PaintElementUndoableAction <ColouredElement>
{
public:
StrokeEnableChangeAction (ColouredElement* const element, const bool newState_)
: PaintElementUndoableAction <ColouredElement> (element),
newState (newState_)
{
oldState = element->isStrokeEnabled();
}
bool perform()
{
showCorrectTab();
getElement()->enableStroke (newState, false);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->enableStroke (oldState, false);
return true;
}
private:
bool newState, oldState;
};
void ColouredElement::enableStroke (bool enable, const bool undoable)
{
enable = enable && showOutline;
if (isStrokePresent != enable)
{
if (undoable)
{
perform (new StrokeEnableChangeAction (this, enable),
"Change stroke mode");
}
else
{
repaint();
isStrokePresent = enable;
siblingComponentsChanged();
owner->changed();
owner->getSelectedElements().changed();
}
}
}
//==============================================================================
const StrokeType& ColouredElement::getStrokeType() noexcept
{
return strokeType;
}
class StrokeTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
{
public:
StrokeTypeChangeAction (ColouredElement* const element, const PathStrokeType& newState_)
: PaintElementUndoableAction <ColouredElement> (element),
newState (newState_),
oldState (element->getStrokeType().stroke)
{
}
bool perform()
{
showCorrectTab();
getElement()->setStrokeType (newState, false);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setStrokeType (oldState, false);
return true;
}
private:
PathStrokeType newState, oldState;
};
void ColouredElement::setStrokeType (const PathStrokeType& newType, const bool undoable)
{
if (strokeType.stroke != newType)
{
if (undoable)
{
perform (new StrokeTypeChangeAction (this, newType),
"Change stroke type");
}
else
{
repaint();
strokeType.stroke = newType;
changed();
}
}
}
class StrokeFillTypeChangeAction : public PaintElementUndoableAction <ColouredElement>
{
public:
StrokeFillTypeChangeAction (ColouredElement* const element, const JucerFillType& newState_)
: PaintElementUndoableAction <ColouredElement> (element),
newState (newState_)
{
oldState = element->getStrokeType().fill;
}
bool perform()
{
showCorrectTab();
getElement()->setStrokeFill (newState, false);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setStrokeFill (oldState, false);
return true;
}
private:
JucerFillType newState, oldState;
};
void ColouredElement::setStrokeFill (const JucerFillType& newType, const bool undoable)
{
if (strokeType.fill != newType)
{
if (undoable)
{
perform (new StrokeFillTypeChangeAction (this, newType),
"Change stroke fill type");
}
else
{
repaint();
if (strokeType.fill.mode != newType.mode)
{
siblingComponentsChanged();
owner->getSelectedElements().changed();
}
strokeType.fill = newType;
changed();
}
}
}
//==============================================================================
void ColouredElement::createSiblingComponents()
{
{
GradientPointComponent* g1 = new GradientPointComponent (this, false, true);
siblingComponents.add (g1);
GradientPointComponent* g2 = new GradientPointComponent (this, false, false);
siblingComponents.add (g2);
getParentComponent()->addAndMakeVisible (g1);
getParentComponent()->addAndMakeVisible (g2);
g1->updatePosition();
g2->updatePosition();
}
if (isStrokePresent && showOutline)
{
GradientPointComponent* g1 = new GradientPointComponent (this, true, true);
siblingComponents.add (g1);
GradientPointComponent* g2 = new GradientPointComponent (this, true, false);
siblingComponents.add (g2);
getParentComponent()->addAndMakeVisible (g1);
getParentComponent()->addAndMakeVisible (g2);
g1->updatePosition();
g2->updatePosition();
}
}
Rectangle<int> ColouredElement::getCurrentBounds (const Rectangle<int>& parentArea) const
{
int borderSize = 0;
if (isStrokePresent)
borderSize = (int) strokeType.stroke.getStrokeThickness() / 2 + 1;
return position.getRectangle (parentArea, getDocument()->getComponentLayout())
.expanded (borderSize);
}
void ColouredElement::setCurrentBounds (const Rectangle<int>& newBounds,
const Rectangle<int>& parentArea,
const bool undoable)
{
Rectangle<int> r (newBounds);
if (isStrokePresent)
{
r = r.expanded (-((int) strokeType.stroke.getStrokeThickness() / 2 + 1));
r.setSize (jmax (1, r.getWidth()), jmax (1, r.getHeight()));
}
RelativePositionedRectangle pr (position);
pr.updateFrom (r.getX() - parentArea.getX(),
r.getY() - parentArea.getY(),
r.getWidth(), r.getHeight(),
Rectangle<int> (0, 0, parentArea.getWidth(), parentArea.getHeight()),
getDocument()->getComponentLayout());
setPosition (pr, undoable);
updateBounds (parentArea);
}
//==============================================================================
void ColouredElement::addColourAttributes (XmlElement* const e) const
{
e->setAttribute ("fill", fillType.toString());
e->setAttribute ("hasStroke", isStrokePresent);
if (isStrokePresent && showOutline)
{
e->setAttribute ("stroke", strokeType.toString());
e->setAttribute ("strokeColour", strokeType.fill.toString());
}
}
bool ColouredElement::loadColourAttributes (const XmlElement& xml)
{
fillType.restoreFromString (xml.getStringAttribute ("fill", String()));
isStrokePresent = showOutline && xml.getBoolAttribute ("hasStroke", false);
strokeType.restoreFromString (xml.getStringAttribute ("stroke", String()));
strokeType.fill.restoreFromString (xml.getStringAttribute ("strokeColour", String()));
return true;
}
//==============================================================================
void ColouredElement::convertToNewPathElement (const Path& path)
{
if (! path.isEmpty())
{
PaintElementPath newElement (getOwner());
newElement.setToPath (path);
newElement.setFillType (fillType, false);
newElement.enableStroke (isStrokeEnabled(), false);
newElement.setStrokeType (getStrokeType().stroke, false);
newElement.setStrokeFill (getStrokeType().fill, false);
std::unique_ptr<XmlElement> xml (newElement.createXml());
PaintElement* e = getOwner()->addElementFromXml (*xml, getOwner()->indexOfElement (this), true);
getOwner()->getSelectedElements().selectOnly (e);
getOwner()->removeElement (this, true);
}
}

View File

@@ -0,0 +1,80 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_PaintRoutine.h"
#include "../jucer_JucerDocument.h"
#include "jucer_StrokeType.h"
//==============================================================================
/**
Base class for paint elements that have a fill colour and stroke.
*/
class ColouredElement : public PaintElement
{
public:
ColouredElement (PaintRoutine* owner,
const String& name,
const bool showOutline_,
const bool showJointAndEnd_);
~ColouredElement() override;
//==============================================================================
void getEditableProperties (Array<PropertyComponent*>& props, bool multipleSelected) override;
void getColourSpecificProperties (Array<PropertyComponent*>& props);
//==============================================================================
const JucerFillType& getFillType() noexcept;
void setFillType (const JucerFillType& newType, const bool undoable);
bool isStrokeEnabled() const noexcept;
void enableStroke (bool enable, const bool undoable);
const StrokeType& getStrokeType() noexcept;
void setStrokeType (const PathStrokeType& newType, const bool undoable);
void setStrokeFill (const JucerFillType& newType, const bool undoable);
//==============================================================================
Rectangle<int> getCurrentBounds (const Rectangle<int>& parentArea) const override;
void setCurrentBounds (const Rectangle<int>& newBounds, const Rectangle<int>& parentArea, const bool undoable) override;
void createSiblingComponents() override;
//==============================================================================
void addColourAttributes (XmlElement* const e) const;
bool loadColourAttributes (const XmlElement& xml);
protected:
JucerFillType fillType;
bool isStrokePresent;
const bool showOutline, showJointAndEnd;
StrokeType strokeType;
void convertToNewPathElement (const Path& path);
};

View File

@@ -0,0 +1,55 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class ElementSiblingComponent : public Component,
public ChangeListener
{
public:
ElementSiblingComponent (PaintElement* const owner_)
: owner (owner_)
{
setAlwaysOnTop (true);
owner->getDocument()->addChangeListener (this);
}
~ElementSiblingComponent() override
{
owner->getDocument()->removeChangeListener (this);
}
virtual void updatePosition() = 0;
void changeListenerCallback (ChangeBroadcaster*) override
{
updatePosition();
}
protected:
PaintElement* const owner;
};

View File

@@ -0,0 +1,427 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_JucerDocument.h"
#include "../jucer_UtilityFunctions.h"
#include "../../ProjectSaving/jucer_ResourceFile.h"
//==============================================================================
class JucerFillType
{
public:
JucerFillType()
{
reset();
}
JucerFillType (const JucerFillType& other)
{
image = Image();
mode = other.mode;
colour = other.colour;
gradCol1 = other.gradCol1;
gradCol2 = other.gradCol2;
gradPos1 = other.gradPos1;
gradPos2 = other.gradPos2;
imageResourceName = other.imageResourceName;
imageOpacity = other.imageOpacity;
imageAnchor = other.imageAnchor;
}
JucerFillType& operator= (const JucerFillType& other)
{
image = Image();
mode = other.mode;
colour = other.colour;
gradCol1 = other.gradCol1;
gradCol2 = other.gradCol2;
gradPos1 = other.gradPos1;
gradPos2 = other.gradPos2;
imageResourceName = other.imageResourceName;
imageOpacity = other.imageOpacity;
imageAnchor = other.imageAnchor;
return *this;
}
bool operator== (const JucerFillType& other) const
{
return mode == other.mode
&& colour == other.colour
&& gradCol1 == other.gradCol1
&& gradCol2 == other.gradCol2
&& gradPos1 == other.gradPos1
&& gradPos2 == other.gradPos2
&& imageResourceName == other.imageResourceName
&& imageOpacity == other.imageOpacity
&& imageAnchor == other.imageAnchor;
}
bool operator!= (const JucerFillType& other) const
{
return ! operator== (other);
}
//==============================================================================
void setFillType (Graphics& g, JucerDocument* const document, const Rectangle<int>& parentArea)
{
if (document == nullptr)
{
jassertfalse;
return;
}
if (mode == solidColour)
{
image = Image();
g.setColour (colour);
}
else if (mode == imageBrush)
{
loadImage (document);
Rectangle<int> r (imageAnchor.getRectangle (parentArea, document->getComponentLayout()));
g.setTiledImageFill (image, r.getX(), r.getY(), (float) imageOpacity);
}
else
{
image = Image();
Rectangle<int> r1 (gradPos1.getRectangle (parentArea, document->getComponentLayout()));
Rectangle<int> r2 (gradPos2.getRectangle (parentArea, document->getComponentLayout()));
g.setGradientFill (ColourGradient (gradCol1, (float) r1.getX(), (float) r1.getY(),
gradCol2, (float) r2.getX(), (float) r2.getY(),
mode == radialGradient));
}
}
String generateVariablesCode (String type) const
{
String s;
switch (mode)
{
case solidColour:
s << "juce::Colour " << type << "Colour = " << CodeHelpers::colourToCode (colour) << ";\n";
break;
case linearGradient:
case radialGradient:
s << "juce::Colour " << type << "Colour1 = " << CodeHelpers::colourToCode (gradCol1) << ", " << type << "Colour2 = " << CodeHelpers::colourToCode (gradCol2) << ";\n";
break;
case imageBrush:
break;
default:
jassertfalse;
break;
}
return s;
}
void fillInGeneratedCode (String type, RelativePositionedRectangle relativeTo, GeneratedCode& code, String& paintMethodCode) const
{
String s;
switch (mode)
{
case solidColour:
s << "g.setColour (" << type << "Colour);\n";
break;
case linearGradient:
case radialGradient:
{
String x0, y0, x1, y1, w, h, x2, y2;
positionToCode (relativeTo, code.document->getComponentLayout(), x0, y0, w, h);
positionToCode (gradPos1, code.document->getComponentLayout(), x1, y1, w, h);
positionToCode (gradPos2, code.document->getComponentLayout(), x2, y2, w, h);
s << "g.setGradientFill (juce::ColourGradient (";
auto indent = String::repeatedString (" ", s.length());
s << type << "Colour1,\n"
<< indent << castToFloat (x1) << " - " << castToFloat (x0) << " + x,\n"
<< indent << castToFloat (y1) << " - " << castToFloat (y0) << " + y,\n"
<< indent << type << "Colour2,\n"
<< indent << castToFloat (x2) << " - " << castToFloat (x0) << " + x,\n"
<< indent << castToFloat (y2) << " - " << castToFloat (y0) << " + y,\n"
<< indent << CodeHelpers::boolLiteral (mode == radialGradient) << "));\n";
break;
}
case imageBrush:
{
auto imageVariable = "cachedImage_" + imageResourceName.replace ("::", "_") + "_" + String (code.getUniqueSuffix());
code.addImageResourceLoader (imageVariable, imageResourceName);
String x0, y0, x1, y1, w, h;
positionToCode (relativeTo, code.document->getComponentLayout(), x0, y0, w, h);
positionToCode (imageAnchor, code.document->getComponentLayout(), x1, y1, w, h);
s << "g.setTiledImageFill (";
const String indent (String::repeatedString (" ", s.length()));
s << imageVariable << ",\n"
<< indent << x1 << " - " << x0 << " + x,\n"
<< indent << y1 << " - " << y0 << " + y,\n"
<< indent << CodeHelpers::floatLiteral (imageOpacity, 4) << ");\n";
break;
}
default:
jassertfalse;
break;
}
paintMethodCode += s;
}
String toString() const
{
switch (mode)
{
case solidColour:
return "solid: " + colour.toString();
case linearGradient:
case radialGradient:
return (mode == linearGradient ? "linear: "
: " radial: ")
+ gradPos1.toString()
+ ", "
+ gradPos2.toString()
+ ", 0=" + gradCol1.toString()
+ ", 1=" + gradCol2.toString();
case imageBrush:
return "image: " + imageResourceName.replaceCharacter (':', '#')
+ ", "
+ String (imageOpacity)
+ ", "
+ imageAnchor.toString();
default:
jassertfalse;
break;
}
return {};
}
void restoreFromString (const String& s)
{
reset();
if (s.isNotEmpty())
{
StringArray toks;
toks.addTokens (s, ",:", StringRef());
toks.trim();
if (toks[0] == "solid")
{
mode = solidColour;
colour = Colour::fromString (toks[1]);
}
else if (toks[0] == "linear"
|| toks[0] == "radial")
{
mode = (toks[0] == "linear") ? linearGradient : radialGradient;
gradPos1 = RelativePositionedRectangle();
gradPos1.rect = PositionedRectangle (toks[1]);
gradPos2 = RelativePositionedRectangle();
gradPos2.rect = PositionedRectangle (toks[2]);
gradCol1 = Colour::fromString (toks[3].fromFirstOccurrenceOf ("=", false, false));
gradCol2 = Colour::fromString (toks[4].fromFirstOccurrenceOf ("=", false, false));
}
else if (toks[0] == "image")
{
mode = imageBrush;
imageResourceName = toks[1].replaceCharacter ('#', ':');
imageOpacity = toks[2].getDoubleValue();
imageAnchor= RelativePositionedRectangle();
imageAnchor.rect = PositionedRectangle (toks[3]);
}
else
{
jassertfalse;
}
}
}
bool isOpaque() const
{
switch (mode)
{
case solidColour:
return colour.isOpaque();
case linearGradient:
case radialGradient:
return gradCol1.isOpaque() && gradCol2.isOpaque();
case imageBrush:
return image.isValid()
&& imageOpacity >= 1.0f
&& ! image.hasAlphaChannel();
default:
jassertfalse;
break;
}
return false;
}
bool isInvisible() const
{
switch (mode)
{
case solidColour:
return colour.isTransparent();
case linearGradient:
case radialGradient:
return gradCol1.isTransparent() && gradCol2.isTransparent();
case imageBrush:
return imageOpacity == 0.0;
default:
jassertfalse;
break;
}
return false;
}
//==============================================================================
enum FillMode
{
solidColour,
linearGradient,
radialGradient,
imageBrush
};
FillMode mode;
Colour colour, gradCol1, gradCol2;
// just the x, y, of these are used
RelativePositionedRectangle gradPos1, gradPos2;
String imageResourceName;
double imageOpacity;
RelativePositionedRectangle imageAnchor;
//==============================================================================
private:
Image image;
void reset()
{
image = Image();
mode = solidColour;
colour = Colours::brown.withHue (Random::getSystemRandom().nextFloat());
gradCol1 = Colours::red;
gradCol2 = Colours::green;
gradPos1 = RelativePositionedRectangle();
gradPos1.rect = PositionedRectangle ("50 50");
gradPos2 = RelativePositionedRectangle();
gradPos2.rect = PositionedRectangle ("100 100");
imageResourceName.clear();
imageOpacity = 1.0;
imageAnchor = RelativePositionedRectangle();
imageAnchor.rect = PositionedRectangle ("0 0");
}
void loadImage (JucerDocument* const document)
{
if (image.isNull())
{
if (document != nullptr)
{
if (imageResourceName.contains ("::"))
{
if (Project* project = document->getCppDocument().getProject())
{
JucerResourceFile resourceFile (*project);
for (int i = 0; i < resourceFile.getNumFiles(); ++i)
{
const File& file = resourceFile.getFile(i);
if (imageResourceName == resourceFile.getClassName() + "::" + resourceFile.getDataVariableFor (file))
{
image = ImageCache::getFromFile (file);
break;
}
}
}
}
else
{
image = document->getResources().getImageFromCache (imageResourceName);
}
}
if (image.isNull())
{
const int hashCode = 0x3437856f;
image = ImageCache::getFromHashCode (hashCode);
if (image.isNull())
{
image = Image (Image::RGB, 100, 100, true);
Graphics g (image);
g.fillCheckerBoard (image.getBounds().toFloat(),
(float) image.getWidth() * 0.5f, (float) image.getHeight() * 0.5f,
Colours::white, Colours::lightgrey);
g.setFont (12.0f);
g.setColour (Colours::grey);
g.drawText ("(image missing)", 0, 0, image.getWidth(), image.getHeight() / 2, Justification::centred, true);
ImageCache::addImageToCache (image, hashCode);
}
}
}
}
};

View File

@@ -0,0 +1,99 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_PointComponent.h"
#include "jucer_ColouredElement.h"
//==============================================================================
class GradientPointComponent : public PointComponent
{
public:
GradientPointComponent (ColouredElement* const owner_,
const bool isStroke_,
const bool isStart_)
: PointComponent (owner_),
isStroke (isStroke_),
isStart (isStart_)
{
}
RelativePositionedRectangle getPosition()
{
ColouredElement* e = dynamic_cast<ColouredElement*> (owner);
if (isStroke)
return isStart ? e->getStrokeType().fill.gradPos1
: e->getStrokeType().fill.gradPos2;
return isStart ? e->getFillType().gradPos1
: e->getFillType().gradPos2;
}
void setPosition (const RelativePositionedRectangle& newPos)
{
ColouredElement* e = dynamic_cast<ColouredElement*> (owner);
if (isStroke)
{
JucerFillType f (e->getStrokeType().fill);
if (isStart)
f.gradPos1 = newPos;
else
f.gradPos2 = newPos;
e->setStrokeFill (f, true);
}
else
{
JucerFillType f (e->getFillType());
if (isStart)
f.gradPos1 = newPos;
else
f.gradPos2 = newPos;
e->setFillType (f, true);
}
}
void updatePosition()
{
PointComponent::updatePosition();
ColouredElement* e = dynamic_cast<ColouredElement*> (owner);
JucerFillType f (isStroke ? e->getStrokeType().fill
: e->getFillType());
setVisible (f.mode == JucerFillType::linearGradient
|| f.mode == JucerFillType::radialGradient);
}
private:
bool isStroke, isStart;
};

View File

@@ -0,0 +1,144 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../../ProjectSaving/jucer_ResourceFile.h"
//==============================================================================
/**
A property that lets you pick a resource to use as an image, or create a
new one with a file selector.
*/
template <class ElementType>
class ImageResourceProperty : public ChoicePropertyComponent,
private ChangeListener
{
public:
ImageResourceProperty (JucerDocument& doc,
ElementType* const e,
const String& name,
const bool allowChoiceOfNoResource_)
: ChoicePropertyComponent (name),
element (e), document (doc),
allowChoiceOfNoResource (allowChoiceOfNoResource_)
{
refreshChoices();
doc.addChangeListener (this);
}
ImageResourceProperty (ElementType* const e, const String& name,
const bool allowChoiceOfNoResource_ = false)
: ChoicePropertyComponent (name),
element (e), document (*e->getDocument()),
allowChoiceOfNoResource (allowChoiceOfNoResource_)
{
refreshChoices();
document.addChangeListener (this);
}
~ImageResourceProperty()
{
document.removeChangeListener (this);
}
//==============================================================================
virtual void setResource (const String& newName) = 0;
virtual String getResource() const = 0;
//==============================================================================
void setIndex (int newIndex)
{
if (newIndex == 0)
{
document.getResources()
.browseForResource ("Select an image file to add as a resource",
"*.jpg;*.jpeg;*.png;*.gif;*.svg",
File(),
String(),
[this] (String resource)
{
if (resource.isNotEmpty())
setResource (resource);
});
}
else
{
if (choices[newIndex] == getNoneText() && allowChoiceOfNoResource)
setResource (String());
else
setResource (choices [newIndex]);
}
}
int getIndex() const
{
if (getResource().isEmpty())
return -1;
return choices.indexOf (getResource());
}
void changeListenerCallback (ChangeBroadcaster*)
{
refresh();
}
void refreshChoices()
{
choices.clear();
choices.add ("-- create a new image resource -- ");
choices.add (String());
if (allowChoiceOfNoResource)
choices.add (getNoneText());
choices.addArray (document.getResources().getResourceNames());
const SourceCodeDocument& cpp = document.getCppDocument();
if (Project* project = cpp.getProject())
{
JucerResourceFile resourceFile (*project);
for (int i = 0; i < resourceFile.getNumFiles(); ++i)
{
const File& file = resourceFile.getFile(i);
if (ImageFileFormat::findImageFormatForFileExtension(file))
choices.add (resourceFile.getClassName() + "::" + resourceFile.getDataVariableFor (file));
}
}
}
const char* getNoneText() noexcept { return "<< none >>"; }
protected:
mutable Component::SafePointer<ElementType> element;
JucerDocument& document;
const bool allowChoiceOfNoResource;
};

View File

@@ -0,0 +1,688 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "../../Application/jucer_Application.h"
#include "../jucer_PaintRoutine.h"
#include "../jucer_UtilityFunctions.h"
#include "../UI/jucer_JucerCommandIDs.h"
#include "../UI/jucer_PaintRoutineEditor.h"
#include "../Properties/jucer_PositionPropertyBase.h"
#include "jucer_ElementSiblingComponent.h"
#include "jucer_PaintElementUndoableAction.h"
//==============================================================================
PaintElement::PaintElement (PaintRoutine* owner_,
const String& typeName_)
: borderThickness (4),
owner (owner_),
typeName (typeName_),
selected (false),
dragging (false),
originalAspectRatio (1.0)
{
setRepaintsOnMouseActivity (true);
position.rect.setWidth (100);
position.rect.setHeight (100);
setMinimumOnscreenAmounts (0, 0, 0, 0);
setSizeLimits (borderThickness * 2 + 1, borderThickness * 2 + 1, 8192, 8192);
border.reset (new ResizableBorderComponent (this, this));
addChildComponent (border.get());
border->setBorderThickness (BorderSize<int> (borderThickness));
if (owner != nullptr)
owner->getSelectedElements().addChangeListener (this);
selfChangeListenerList.addChangeListener (this);
siblingComponentsChanged();
}
PaintElement::~PaintElement()
{
siblingComponents.clear();
if (owner != nullptr)
{
owner->getSelectedElements().deselect (this);
owner->getSelectedElements().removeChangeListener (this);
}
}
//==============================================================================
void PaintElement::setInitialBounds (int parentWidth, int parentHeight)
{
RelativePositionedRectangle pr (getPosition());
pr.rect.setX (parentWidth / 4 + Random::getSystemRandom().nextInt (parentWidth / 4) - parentWidth / 8);
pr.rect.setY (parentHeight / 3 + Random::getSystemRandom().nextInt (parentHeight / 4) - parentHeight / 8);
setPosition (pr, false);
}
//==============================================================================
const RelativePositionedRectangle& PaintElement::getPosition() const
{
return position;
}
class PaintElementMoveAction : public PaintElementUndoableAction <PaintElement>
{
public:
PaintElementMoveAction (PaintElement* const element, const RelativePositionedRectangle& newState_)
: PaintElementUndoableAction <PaintElement> (element),
newState (newState_),
oldState (element->getPosition())
{
}
bool perform()
{
showCorrectTab();
getElement()->setPosition (newState, false);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setPosition (oldState, false);
return true;
}
RelativePositionedRectangle newState, oldState;
};
class ChangePaintElementBoundsAction : public PaintElementUndoableAction <PaintElement>
{
public:
ChangePaintElementBoundsAction (PaintElement* const element, const Rectangle<int>& bounds)
: PaintElementUndoableAction <PaintElement> (element),
newBounds (bounds),
oldBounds (element->getBounds())
{
}
bool perform()
{
showCorrectTab();
getElement()->setBounds (newBounds);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setBounds (oldBounds);
return true;
}
private:
Rectangle<int> newBounds, oldBounds;
};
class ChangePaintElementBoundsAndPropertiesAction : public PaintElementUndoableAction <PaintElement>
{
public:
ChangePaintElementBoundsAndPropertiesAction (PaintElement* const element, const Rectangle<int>& bounds,
const NamedValueSet& props)
: PaintElementUndoableAction <PaintElement> (element),
newBounds (bounds),
oldBounds (element->getBounds()),
newProps (props),
oldProps (element->getProperties())
{
}
bool perform()
{
showCorrectTab();
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getElement()->getParentComponent()))
getElement()->setCurrentBounds (newBounds, pe->getComponentArea(), false);
getElement()->getProperties() = newProps;
return true;
}
bool undo()
{
showCorrectTab();
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getElement()->getParentComponent()))
getElement()->setCurrentBounds (oldBounds, pe->getComponentArea(), false);
getElement()->getProperties() = oldProps;
return true;
}
private:
Rectangle<int> newBounds, oldBounds;
NamedValueSet newProps, oldProps;
};
void PaintElement::setPosition (const RelativePositionedRectangle& newPosition, const bool undoable)
{
if (position != newPosition)
{
if (undoable)
{
perform (new PaintElementMoveAction (this, newPosition),
"Move " + getTypeName());
}
else
{
position = newPosition;
if (owner != nullptr)
owner->changed();
}
}
}
void PaintElement::setPaintElementBounds (const Rectangle<int>& newBounds, const bool undoable)
{
if (getBounds() != newBounds)
{
if (undoable)
{
perform (new ChangePaintElementBoundsAction (this, newBounds), "Change paint element bounds");
}
else
{
setBounds (newBounds);
changed();
}
}
}
void PaintElement::setPaintElementBoundsAndProperties (PaintElement* elementToPosition, const Rectangle<int>& newBounds,
PaintElement* referenceElement, const bool undoable)
{
auto props = NamedValueSet (elementToPosition->getProperties());
auto rect = elementToPosition->getPosition().rect;
auto referenceElementPosition = referenceElement->getPosition();
auto referenceElementRect = referenceElementPosition.rect;
rect.setModes (referenceElementRect.getAnchorPointX(), referenceElementRect.getPositionModeX(),
referenceElementRect.getAnchorPointY(), referenceElementRect.getPositionModeY(),
referenceElementRect.getWidthMode(), referenceElementRect.getHeightMode(),
elementToPosition->getBounds());
props.set ("pos", rect.toString());
props.set ("relativeToX", String::toHexString (referenceElementPosition.relativeToX));
props.set ("relativeToY", String::toHexString (referenceElementPosition.relativeToY));
props.set ("relativeToW", String::toHexString (referenceElementPosition.relativeToW));
props.set ("relativeToH", String::toHexString (referenceElementPosition.relativeToH));
if (elementToPosition->getBounds() != newBounds || elementToPosition->getProperties() != props)
{
if (undoable)
{
perform (new ChangePaintElementBoundsAndPropertiesAction (elementToPosition, newBounds, props),
"Change paint element bounds");
}
else
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (elementToPosition->getParentComponent()))
elementToPosition->setCurrentBounds (newBounds, pe->getComponentArea(), false);
elementToPosition->getProperties() = props;
owner->changed();
}
}
}
//==============================================================================
Rectangle<int> PaintElement::getCurrentBounds (const Rectangle<int>& parentArea) const
{
return position.getRectangle (parentArea, getDocument()->getComponentLayout());
}
void PaintElement::setCurrentBounds (const Rectangle<int>& newBounds,
const Rectangle<int>& parentArea,
const bool undoable)
{
RelativePositionedRectangle pr (position);
pr.updateFrom (newBounds.getX() - parentArea.getX(),
newBounds.getY() - parentArea.getY(),
jmax (1, newBounds.getWidth()),
jmax (1, newBounds.getHeight()),
Rectangle<int> (0, 0, parentArea.getWidth(), parentArea.getHeight()),
getDocument()->getComponentLayout());
setPosition (pr, undoable);
updateBounds (parentArea);
}
void PaintElement::updateBounds (const Rectangle<int>& parentArea)
{
if (! parentArea.isEmpty())
{
setBounds (getCurrentBounds (parentArea)
.expanded (borderThickness,
borderThickness));
for (int i = siblingComponents.size(); --i >= 0;)
siblingComponents.getUnchecked(i)->updatePosition();
}
}
//==============================================================================
class ElementPositionProperty : public PositionPropertyBase
{
public:
ElementPositionProperty (PaintElement* e, const String& name,
ComponentPositionDimension dimension_)
: PositionPropertyBase (e, name, dimension_, true, false,
e->getDocument()->getComponentLayout()),
listener (e),
element (e)
{
listener.setPropertyToRefresh (*this);
}
void setPosition (const RelativePositionedRectangle& newPos)
{
if (element->getOwner()->getSelectedElements().getNumSelected() > 1)
positionOtherSelectedElements (getPosition(), newPos);
listener.owner->setPosition (newPos, true);
}
RelativePositionedRectangle getPosition() const
{
return listener.owner->getPosition();
}
private:
ElementListener<PaintElement> listener;
PaintElement* element;
void positionOtherSelectedElements (const RelativePositionedRectangle& oldPos, const RelativePositionedRectangle& newPos)
{
for (auto* s : element->getOwner()->getSelectedElements())
{
if (s != element)
{
auto currentPos = s->getPosition();
auto diff = 0.0;
if (dimension == ComponentPositionDimension::componentX)
{
diff = newPos.rect.getX() - oldPos.rect.getX();
currentPos.rect.setX (currentPos.rect.getX() + diff);
}
else if (dimension == ComponentPositionDimension::componentY)
{
diff = newPos.rect.getY() - oldPos.rect.getY();
currentPos.rect.setY (currentPos.rect.getY() + diff);
}
else if (dimension == ComponentPositionDimension::componentWidth)
{
diff = newPos.rect.getWidth() - oldPos.rect.getWidth();
currentPos.rect.setWidth (currentPos.rect.getWidth() + diff);
}
else if (dimension == ComponentPositionDimension::componentHeight)
{
diff = newPos.rect.getHeight() - oldPos.rect.getHeight();
currentPos.rect.setHeight (currentPos.rect.getHeight() + diff);
}
s->setPosition (currentPos, true);
}
}
}
};
//==============================================================================
void PaintElement::getEditableProperties (Array <PropertyComponent*>& props, bool multipleSelected)
{
ignoreUnused (multipleSelected);
props.add (new ElementPositionProperty (this, "x", PositionPropertyBase::componentX));
props.add (new ElementPositionProperty (this, "y", PositionPropertyBase::componentY));
props.add (new ElementPositionProperty (this, "width", PositionPropertyBase::componentWidth));
props.add (new ElementPositionProperty (this, "height", PositionPropertyBase::componentHeight));
}
//==============================================================================
JucerDocument* PaintElement::getDocument() const
{
return owner->getDocument();
}
void PaintElement::changed()
{
repaint();
owner->changed();
}
bool PaintElement::perform (UndoableAction* action, const String& actionName)
{
return owner->perform (action, actionName);
}
void PaintElement::parentHierarchyChanged()
{
updateSiblingComps();
}
//==============================================================================
void PaintElement::drawExtraEditorGraphics (Graphics&, const Rectangle<int>& /*relativeTo*/)
{
}
void PaintElement::paint (Graphics& g)
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
{
auto area = pe->getComponentArea();
g.saveState();
g.setOrigin (area.getPosition() - Component::getPosition());
area.setPosition (0, 0);
g.saveState();
g.reduceClipRegion (0, 0, area.getWidth(), area.getHeight());
draw (g, getDocument()->getComponentLayout(), area);
g.restoreState();
drawExtraEditorGraphics (g, area);
g.restoreState();
if (selected)
{
const BorderSize<int> borderSize (border->getBorderThickness());
auto baseColour = findColour (defaultHighlightColourId);
drawResizableBorder (g, getWidth(), getHeight(), borderSize,
(isMouseOverOrDragging() || border->isMouseOverOrDragging()),
baseColour.withAlpha (owner->getSelectedElements().getSelectedItem (0) == this ? 1.0f : 0.3f));
}
else if (isMouseOverOrDragging())
{
drawMouseOverCorners (g, getWidth(), getHeight());
}
}
}
void PaintElement::resized()
{
border->setBounds (getLocalBounds());
}
void PaintElement::mouseDown (const MouseEvent& e)
{
dragging = false;
if (owner != nullptr)
{
owner->getSelectedPoints().deselectAll();
mouseDownSelectStatus = owner->getSelectedElements().addToSelectionOnMouseDown (this, e.mods);
}
if (e.mods.isPopupMenu())
{
showPopupMenu();
return; // this may be deleted now..
}
}
void PaintElement::mouseDrag (const MouseEvent& e)
{
if (! e.mods.isPopupMenu())
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
{
auto area = pe->getComponentArea();
if (selected && ! dragging)
{
dragging = e.mouseWasDraggedSinceMouseDown();
if (dragging)
owner->startDragging (area);
}
if (dragging)
owner->dragSelectedComps (e.getDistanceFromDragStartX(),
e.getDistanceFromDragStartY(),
area);
}
}
}
void PaintElement::mouseUp (const MouseEvent& e)
{
if (owner != nullptr)
{
if (dragging)
owner->endDragging();
if (owner != nullptr)
owner->getSelectedElements().addToSelectionOnMouseUp (this, e.mods, dragging, mouseDownSelectStatus);
}
}
void PaintElement::resizeStart()
{
if (getHeight() > 0)
originalAspectRatio = getWidth() / (double) getHeight();
else
originalAspectRatio = 1.0;
}
void PaintElement::resizeEnd()
{
}
void PaintElement::checkBounds (Rectangle<int>& b,
const Rectangle<int>& previousBounds,
const Rectangle<int>& limits,
const bool isStretchingTop,
const bool isStretchingLeft,
const bool isStretchingBottom,
const bool isStretchingRight)
{
if (ModifierKeys::currentModifiers.isShiftDown())
setFixedAspectRatio (originalAspectRatio);
else
setFixedAspectRatio (0.0);
ComponentBoundsConstrainer::checkBounds (b, previousBounds, limits, isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight);
if (auto* document = getDocument())
{
if (document->isSnapActive (true))
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
{
auto area = pe->getComponentArea();
int x = b.getX();
int y = b.getY();
int w = b.getWidth();
int h = b.getHeight();
x += borderThickness - area.getX();
y += borderThickness - area.getY();
w -= borderThickness * 2;
h -= borderThickness * 2;
int right = x + w;
int bottom = y + h;
if (isStretchingRight)
right = document->snapPosition (right);
if (isStretchingBottom)
bottom = document->snapPosition (bottom);
if (isStretchingLeft)
x = document->snapPosition (x);
if (isStretchingTop)
y = document->snapPosition (y);
w = (right - x) + borderThickness * 2;
h = (bottom - y) + borderThickness * 2;
x -= borderThickness - area.getX();
y -= borderThickness - area.getY();
b = { x, y, w, h };
}
}
}
}
void PaintElement::applyBoundsToComponent (Component&, Rectangle<int> newBounds)
{
if (getBounds() != newBounds)
{
getDocument()->getUndoManager().undoCurrentTransactionOnly();
auto dX = newBounds.getX() - getX();
auto dY = newBounds.getY() - getY();
auto dW = newBounds.getWidth() - getWidth();
auto dH = newBounds.getHeight() - getHeight();
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
setCurrentBounds (newBounds.expanded (-borderThickness, -borderThickness),
pe->getComponentArea(), true);
if (owner->getSelectedElements().getNumSelected() > 1)
{
for (auto selectedElement : owner->getSelectedElements())
{
if (selectedElement != nullptr && selectedElement != this)
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (selectedElement->getParentComponent()))
{
Rectangle<int> r { selectedElement->getX() + dX, selectedElement->getY() + dY,
selectedElement->getWidth() + dW, selectedElement->getHeight() + dH };
selectedElement->setCurrentBounds (r.expanded (-borderThickness, -borderThickness),
pe->getComponentArea(), true);
}
}
}
}
}
}
Rectangle<int> PaintElement::getCurrentAbsoluteBounds() const
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
return position.getRectangle (pe->getComponentArea(), getDocument()->getComponentLayout());
return {};
}
void PaintElement::getCurrentAbsoluteBoundsDouble (double& x, double& y, double& w, double& h) const
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
position.getRectangleDouble (x, y, w, h, pe->getComponentArea(), getDocument()->getComponentLayout());
}
void PaintElement::changeListenerCallback (ChangeBroadcaster*)
{
const bool nowSelected = owner != nullptr && owner->getSelectedElements().isSelected (this);
if (selected != nowSelected)
{
selected = nowSelected;
border->setVisible (nowSelected);
repaint();
selectionChanged (nowSelected);
}
updateSiblingComps();
}
void PaintElement::selectionChanged (const bool /*isSelected*/)
{
}
void PaintElement::createSiblingComponents()
{
}
void PaintElement::siblingComponentsChanged()
{
siblingComponents.clear();
selfChangeListenerList.sendChangeMessage();
}
void PaintElement::updateSiblingComps()
{
if (selected && getParentComponent() != nullptr && owner->getSelectedElements().getNumSelected() == 1)
{
if (siblingComponents.size() == 0)
createSiblingComponents();
for (int i = siblingComponents.size(); --i >= 0;)
siblingComponents.getUnchecked(i)->updatePosition();
}
else
{
siblingComponents.clear();
}
}
void PaintElement::showPopupMenu()
{
auto* commandManager = &ProjucerApplication::getCommandManager();
PopupMenu m;
m.addCommandItem (commandManager, JucerCommandIDs::toFront);
m.addCommandItem (commandManager, JucerCommandIDs::toBack);
m.addSeparator();
if (owner != nullptr && owner->getSelectedElements().getNumSelected() > 1)
{
m.addCommandItem (commandManager, JucerCommandIDs::alignTop);
m.addCommandItem (commandManager, JucerCommandIDs::alignRight);
m.addCommandItem (commandManager, JucerCommandIDs::alignBottom);
m.addCommandItem (commandManager, JucerCommandIDs::alignLeft);
m.addSeparator();
}
m.addCommandItem (commandManager, StandardApplicationCommandIDs::cut);
m.addCommandItem (commandManager, StandardApplicationCommandIDs::copy);
m.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
m.showMenuAsync ({});
}

View File

@@ -0,0 +1,175 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_GeneratedCode.h"
#include "../UI/jucer_RelativePositionedRectangle.h"
class FillType;
class PaintRoutine;
class JucerDocument;
class ElementSiblingComponent;
//==============================================================================
/**
Base class for objects that can be used in a PaintRoutine.
*/
class PaintElement : public Component,
public ComponentBoundsConstrainer,
private ChangeListener
{
public:
//==============================================================================
PaintElement (PaintRoutine* owner, const String& typeName);
~PaintElement() override;
//==============================================================================
virtual void setInitialBounds (int parentWidth, int parentHeight);
virtual Rectangle<int> getCurrentBounds (const Rectangle<int>& activeArea) const;
virtual void setCurrentBounds (const Rectangle<int>& newBounds, const Rectangle<int>& activeArea, const bool undoable);
const RelativePositionedRectangle& getPosition() const;
void setPosition (const RelativePositionedRectangle& newPosition, const bool undoable);
void setPaintElementBounds (const Rectangle<int>& newBounds, const bool undoable);
void setPaintElementBoundsAndProperties (PaintElement* elementToPosition, const Rectangle<int>& newBounds,
PaintElement* referenceElement, const bool undoable);
void updateBounds (const Rectangle<int>& activeArea);
const String& getTypeName() const noexcept { return typeName; }
PaintRoutine* getOwner() const noexcept { return owner; }
//==============================================================================
virtual void draw (Graphics& g,
const ComponentLayout* layout,
const Rectangle<int>& parentArea) = 0;
virtual void drawExtraEditorGraphics (Graphics& g, const Rectangle<int>& relativeTo);
virtual void getEditableProperties (Array<PropertyComponent*>& props, bool multipleSelected);
virtual void showPopupMenu();
//==============================================================================
virtual XmlElement* createXml() const = 0;
virtual bool loadFromXml (const XmlElement& xml) = 0;
//==============================================================================
virtual void fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) = 0;
JucerDocument* getDocument() const;
virtual void changed();
bool perform (UndoableAction* action, const String& actionName);
//==============================================================================
void paint (Graphics&) override;
void resized() override;
void mouseDown (const MouseEvent&) override;
void mouseDrag (const MouseEvent&) override;
void mouseUp (const MouseEvent&) override;
void changeListenerCallback (ChangeBroadcaster*) override;
void parentHierarchyChanged() override;
virtual void applyCustomPaintSnippets (StringArray&) {}
int borderThickness;
protected:
PaintRoutine* const owner;
RelativePositionedRectangle position;
void resizeStart() override;
void resizeEnd() override;
void checkBounds (Rectangle<int>& bounds,
const Rectangle<int>& previousBounds,
const Rectangle<int>& limits,
bool isStretchingTop,
bool isStretchingLeft,
bool isStretchingBottom,
bool isStretchingRight) override;
void applyBoundsToComponent (Component&, Rectangle<int>) override;
Rectangle<int> getCurrentAbsoluteBounds() const;
void getCurrentAbsoluteBoundsDouble (double& x, double& y, double& w, double& h) const;
virtual void selectionChanged (const bool isSelected);
virtual void createSiblingComponents();
void siblingComponentsChanged();
OwnedArray<ElementSiblingComponent> siblingComponents;
void updateSiblingComps();
private:
std::unique_ptr<ResizableBorderComponent> border;
String typeName;
bool selected, dragging, mouseDownSelectStatus;
double originalAspectRatio;
ChangeBroadcaster selfChangeListenerList;
};
//==============================================================================
template <typename ElementType>
class ElementListener : private ChangeListener
{
public:
ElementListener (ElementType* e)
: owner (e), broadcaster (*owner->getDocument()),
propToRefresh (nullptr)
{
broadcaster.addChangeListener (this);
}
~ElementListener() override
{
jassert (propToRefresh != nullptr);
broadcaster.removeChangeListener (this);
}
void setPropertyToRefresh (PropertyComponent& pc)
{
propToRefresh = &pc;
}
mutable Component::SafePointer<ElementType> owner;
ChangeBroadcaster& broadcaster;
PropertyComponent* propToRefresh;
private:
void changeListenerCallback (ChangeBroadcaster*) override
{
jassert (propToRefresh != nullptr);
if (propToRefresh != nullptr && owner != nullptr)
propToRefresh->refresh();
}
JUCE_DECLARE_NON_COPYABLE (ElementListener)
};

View File

@@ -0,0 +1,173 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_ColouredElement.h"
//==============================================================================
class PaintElementEllipse : public ColouredElement
{
public:
PaintElementEllipse (PaintRoutine* pr)
: ColouredElement (pr, "Ellipse", true, false)
{
}
void draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea) override
{
fillType.setFillType (g, getDocument(), parentArea);
Rectangle<int> r (position.getRectangle (parentArea, layout));
g.fillEllipse ((float) r.getX(), (float) r.getY(), (float) r.getWidth(), (float) r.getHeight());
if (isStrokePresent)
{
strokeType.fill.setFillType (g, getDocument(), parentArea);
g.drawEllipse ((float) r.getX(), (float) r.getY(), (float) r.getWidth(), (float) r.getHeight(),
getStrokeType().stroke.getStrokeThickness());
}
}
void getEditableProperties (Array<PropertyComponent*>& props, bool multipleSelected) override
{
ColouredElement::getEditableProperties (props, multipleSelected);
props.add (new ShapeToPathProperty (this));
}
void fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) override
{
if (fillType.isInvisible() && (strokeType.isInvisible() || ! isStrokePresent))
return;
String x, y, w, h, s;
positionToCode (position, code.document->getComponentLayout(), x, y, w, h);
s << "{\n"
<< " float x = " << castToFloat (x) << ", y = " << castToFloat (y) << ", "
<< "width = " << castToFloat (w) << ", height = " << castToFloat (h) << ";\n";
if (! fillType.isInvisible())
s << " " << fillType.generateVariablesCode ("fill");
if (isStrokePresent && ! strokeType.isInvisible())
s << " " << strokeType.fill.generateVariablesCode ("stroke");
s << " //[UserPaintCustomArguments] Customize the painting arguments here..\n"
<< customPaintCode
<< " //[/UserPaintCustomArguments]\n";
if (! fillType.isInvisible())
{
s << " ";
fillType.fillInGeneratedCode ("fill", position, code, s);
s << " g.fillEllipse (x, y, width, height);\n";
}
if (isStrokePresent && ! strokeType.isInvisible())
{
s << " ";
strokeType.fill.fillInGeneratedCode ("stroke", position, code, s);
s << " g.drawEllipse (x, y, width, height, " << CodeHelpers::floatLiteral (strokeType.stroke.getStrokeThickness(), 3) << ");\n";
}
s << "}\n\n";
paintMethodCode += s;
}
void applyCustomPaintSnippets (StringArray& snippets) override
{
customPaintCode.clear();
if (! snippets.isEmpty() && (! fillType.isInvisible() || (isStrokePresent && ! strokeType.isInvisible())))
{
customPaintCode = snippets[0];
snippets.remove (0);
}
}
static const char* getTagName() noexcept { return "ELLIPSE"; }
XmlElement* createXml() const override
{
XmlElement* e = new XmlElement (getTagName());
position.applyToXml (*e);
addColourAttributes (e);
return e;
}
bool loadFromXml (const XmlElement& xml) override
{
if (xml.hasTagName (getTagName()))
{
position.restoreFromXml (xml, position);
loadColourAttributes (xml);
return true;
}
jassertfalse;
return false;
}
void convertToPath()
{
double x, y, w, h;
getCurrentAbsoluteBoundsDouble (x, y, w, h);
Path path;
path.addEllipse ((float) x, (float) y, (float) w, (float) h);
convertToNewPathElement (path);
}
private:
String customPaintCode;
//==============================================================================
struct ShapeToPathProperty : public ButtonPropertyComponent
{
ShapeToPathProperty (PaintElementEllipse* const e)
: ButtonPropertyComponent ("path", false),
element (e)
{
}
void buttonClicked()
{
element->convertToPath();
}
String getButtonText() const
{
return "convert to a path";
}
PaintElementEllipse* element;
};
};

View File

@@ -0,0 +1,226 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "jucer_PaintElementGroup.h"
PaintElementGroup::PaintElementGroup (PaintRoutine* pr)
: PaintElement (pr, "Group")
{
}
PaintElementGroup::~PaintElementGroup() {}
void PaintElementGroup::ungroup (const bool undoable)
{
getOwner()->getSelectedElements().deselectAll();
getOwner()->getSelectedPoints().deselectAll();
const int index = getOwner()->indexOfElement (this);
for (int i = 0; i < subElements.size(); ++i)
{
std::unique_ptr<XmlElement> xml (subElements.getUnchecked(i)->createXml());
PaintElement* newOne = getOwner()->addElementFromXml (*xml, index, undoable);
getOwner()->getSelectedElements().addToSelection (newOne);
}
getOwner()->removeElement (this, undoable);
}
void PaintElementGroup::groupSelected (PaintRoutine* routine)
{
if (routine->getSelectedElements().getNumSelected() > 1)
{
auto* newGroup = new PaintElementGroup (routine);
int frontIndex = -1;
for (int i = 0; i < routine->getNumElements(); ++i)
{
if (routine->getSelectedElements().isSelected (routine->getElement (i)))
{
std::unique_ptr<XmlElement> xml (routine->getElement(i)->createXml());
if (auto* newOne = ObjectTypes::createElementForXml (xml.get(), routine))
newGroup->subElements.add (newOne);
if (i > frontIndex)
frontIndex = i;
}
}
routine->deleteSelected();
auto* g = routine->addNewElement (newGroup, frontIndex, true);
routine->getSelectedElements().selectOnly (g);
}
}
int PaintElementGroup::getNumElements() const noexcept { return subElements.size(); }
PaintElement* PaintElementGroup::getElement (const int index) const noexcept { return subElements [index]; }
int PaintElementGroup::indexOfElement (const PaintElement* element) const noexcept { return subElements.indexOf (element); }
bool PaintElementGroup::containsElement (const PaintElement* element) const
{
if (subElements.contains (element))
return true;
for (int i = subElements.size(); --i >= 0;)
if (PaintElementGroup* pg = dynamic_cast<PaintElementGroup*> (subElements.getUnchecked(i)))
if (pg->containsElement (element))
return true;
return false;
}
//==============================================================================
void PaintElementGroup::setInitialBounds (int /*parentWidth*/, int /*parentHeight*/)
{
}
Rectangle<int> PaintElementGroup::getCurrentBounds (const Rectangle<int>& parentArea) const
{
Rectangle<int> r;
if (subElements.size() > 0)
{
r = subElements.getUnchecked(0)->getCurrentBounds (parentArea);
for (int i = 1; i < subElements.size(); ++i)
r = r.getUnion (subElements.getUnchecked(i)->getCurrentBounds (parentArea));
}
return r;
}
void PaintElementGroup::setCurrentBounds (const Rectangle<int>& b, const Rectangle<int>& parentArea, const bool undoable)
{
Rectangle<int> newBounds (b);
newBounds.setSize (jmax (1, newBounds.getWidth()),
jmax (1, newBounds.getHeight()));
const Rectangle<int> current (getCurrentBounds (parentArea));
if (newBounds != current)
{
const int dx = newBounds.getX() - current.getX();
const int dy = newBounds.getY() - current.getY();
const double scaleStartX = current.getX();
const double scaleStartY = current.getY();
const double scaleX = newBounds.getWidth() / (double) current.getWidth();
const double scaleY = newBounds.getHeight() / (double) current.getHeight();
for (int i = 0; i < subElements.size(); ++i)
{
PaintElement* const e = subElements.getUnchecked(i);
Rectangle<int> pos (e->getCurrentBounds (parentArea));
const int newX = roundToInt ((pos.getX() - scaleStartX) * scaleX + scaleStartX + dx);
const int newY = roundToInt ((pos.getY() - scaleStartY) * scaleY + scaleStartY + dy);
pos.setBounds (newX, newY,
roundToInt ((pos.getRight() - scaleStartX) * scaleX + scaleStartX + dx) - newX,
roundToInt ((pos.getBottom() - scaleStartY) * scaleY + scaleStartY + dy) - newY);
e->setCurrentBounds (pos, parentArea, undoable);
}
}
}
//==============================================================================
void PaintElementGroup::draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea)
{
for (int i = 0; i < subElements.size(); ++i)
subElements.getUnchecked(i)->draw (g, layout, parentArea);
}
void PaintElementGroup::getEditableProperties (Array<PropertyComponent*>& props, bool multipleSelected)
{
if (! multipleSelected)
props.add (new UngroupProperty (this));
}
void PaintElementGroup::fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode)
{
for (int i = 0; i < subElements.size(); ++i)
subElements.getUnchecked(i)->fillInGeneratedCode (code, paintMethodCode);
}
const char* PaintElementGroup::getTagName() noexcept { return "GROUP"; }
XmlElement* PaintElementGroup::createXml() const
{
XmlElement* e = new XmlElement (getTagName());
for (int i = 0; i < subElements.size(); ++i)
{
XmlElement* const sub = subElements.getUnchecked(i)->createXml();
e->addChildElement (sub);
}
return e;
}
bool PaintElementGroup::loadFromXml (const XmlElement& xml)
{
if (xml.hasTagName (getTagName()))
{
for (auto* e : xml.getChildIterator())
if (PaintElement* const pe = ObjectTypes::createElementForXml (e, owner))
subElements.add (pe);
return true;
}
jassertfalse;
return false;
}
void PaintElementGroup::applyCustomPaintSnippets (StringArray& snippets)
{
for (auto* e : subElements)
e->applyCustomPaintSnippets (snippets);
}
PaintElementGroup::UngroupProperty::UngroupProperty (PaintElementGroup* const e)
: ButtonPropertyComponent ("ungroup", false),
element (e)
{
}
void PaintElementGroup::UngroupProperty::buttonClicked()
{
element->ungroup (true);
}
String PaintElementGroup::UngroupProperty::getButtonText() const
{
return "Ungroup";
}

View File

@@ -0,0 +1,81 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_PaintElement.h"
#include "../jucer_ObjectTypes.h"
//==============================================================================
class PaintElementGroup : public PaintElement
{
public:
PaintElementGroup (PaintRoutine*);
~PaintElementGroup() override;
void ungroup (const bool);
static void groupSelected (PaintRoutine* const);
int getNumElements() const noexcept;
PaintElement* getElement (const int index) const noexcept;
int indexOfElement (const PaintElement* element) const noexcept;
bool containsElement (const PaintElement* element) const;
//==============================================================================
void setInitialBounds (int, int) override;
Rectangle<int> getCurrentBounds (const Rectangle<int>&) const override;
void setCurrentBounds (const Rectangle<int>&, const Rectangle<int>&, const bool) override;
//==============================================================================
void draw (Graphics&, const ComponentLayout*, const Rectangle<int>&) override;
void getEditableProperties (Array<PropertyComponent*>&, bool) override;
void fillInGeneratedCode (GeneratedCode&, String&) override;
static const char* getTagName() noexcept;
XmlElement* createXml() const override;
bool loadFromXml (const XmlElement&) override;
void applyCustomPaintSnippets (StringArray&) override;
private:
OwnedArray<PaintElement> subElements;
struct UngroupProperty : public ButtonPropertyComponent
{
UngroupProperty (PaintElementGroup* const);
void buttonClicked();
String getButtonText() const;
PaintElementGroup* element;
};
};

View File

@@ -0,0 +1,428 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "jucer_PaintElementImage.h"
PaintElementImage::PaintElementImage (PaintRoutine* pr)
: PaintElement (pr, "Image"),
opacity (1.0),
mode (stretched)
{
}
PaintElementImage::~PaintElementImage() {}
const Drawable* PaintElementImage::getDrawable()
{
if (JucerDocument* const document = getDocument())
return document->getResources().getDrawable (resourceName);
return nullptr;
}
void PaintElementImage::draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea)
{
const Rectangle<int> r (position.getRectangle (parentArea, layout));
if (const Drawable* const image = getDrawable())
{
image->drawWithin (g, r.toFloat(),
mode == stretched ? RectanglePlacement::stretchToFit
: (mode == proportionalReducingOnly ? (RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize)
: RectanglePlacement::centred),
(float) opacity);
}
else
{
g.setColour (Colours::grey.withAlpha (0.5f));
g.fillRect (r);
g.setColour (Colours::black);
g.drawText ("(image missing)",
r.getX(), r.getY(), r.getWidth(), r.getHeight(),
Justification::centred, true);
}
}
//==============================================================================
void PaintElementImage::getEditableProperties (Array <PropertyComponent*>& props, bool multipleSelected)
{
PaintElement::getEditableProperties (props, multipleSelected);
props.add (new ImageElementResourceProperty (this));
props.add (new StretchModeProperty (this));
props.add (new OpacityProperty (this));
props.add (new ResetSizeProperty (this));
}
void PaintElementImage::fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode)
{
if (opacity > 0)
{
String x, y, w, h, r;
positionToCode (position, getDocument()->getComponentLayout(), x, y, w, h);
r << "{\n"
<< " int x = " << x << ", y = " << y << ", width = " << w << ", height = " << h << ";\n"
<< " //[UserPaintCustomArguments] Customize the painting arguments here..\n"
<< customPaintCode
<< " //[/UserPaintCustomArguments]\n";
if (dynamic_cast<const DrawableImage*> (getDrawable()))
{
const String imageVariable ("cachedImage_" + resourceName.replace ("::", "_") + "_" + String (code.getUniqueSuffix()));
code.addImageResourceLoader (imageVariable, resourceName);
if (opacity >= 254.0 / 255.0)
r << " g.setColour (juce::Colours::black);\n";
else
r << " g.setColour (juce::Colours::black.withAlpha (" << CodeHelpers::floatLiteral (opacity, 3) << "));\n";
if (mode == stretched)
{
r << " g.drawImage (" << imageVariable << ",\n"
<< " x, y, width, height,\n"
<< " 0, 0, " << imageVariable << ".getWidth(), " << imageVariable << ".getHeight());\n";
}
else
{
r << " g.drawImageWithin (" << imageVariable << ",\n"
<< " x, y, width, height,\n"
<< " ";
if (mode == proportionalReducingOnly)
r << "juce::RectanglePlacement::centred | juce::RectanglePlacement::onlyReduceInSize";
else
r << "juce::RectanglePlacement::centred";
r << ",\n"
<< " false);\n";
}
}
else
{
if (resourceName.isNotEmpty())
{
const String imageVariable ("drawable" + String (code.getUniqueSuffix()));
code.privateMemberDeclarations
<< "std::unique_ptr<juce::Drawable> " << imageVariable << ";\n";
code.constructorCode
<< imageVariable << " = juce::Drawable::createFromImageData ("
<< resourceName << ", " << resourceName << "Size);\n";
code.destructorCode
<< imageVariable << " = nullptr;\n";
if (opacity >= 254.0 / 255.0)
r << " g.setColour (juce::Colours::black);\n";
else
r << " g.setColour (juce::Colours::black.withAlpha (" << CodeHelpers::floatLiteral (opacity, 3) << "));\n";
r << " jassert (" << imageVariable << " != nullptr);\n"
<< " if (" << imageVariable << " != nullptr)\n"
<< " " << imageVariable << "->drawWithin (g, juce::Rectangle<int> (x, y, width, height).toFloat(),\n"
<< " " << String::repeatedString (" ", imageVariable.length() + 18)
<< (mode == stretched ? "juce::RectanglePlacement::stretchToFit"
: (mode == proportionalReducingOnly ? "juce::RectanglePlacement::centred | juce::RectanglePlacement::onlyReduceInSize"
: "juce::RectanglePlacement::centred"))
<< ", " << CodeHelpers::floatLiteral (opacity, 3) << ");\n";
}
}
r << "}\n\n";
paintMethodCode += r;
}
}
void PaintElementImage::applyCustomPaintSnippets (StringArray& snippets)
{
customPaintCode.clear();
if (! snippets.isEmpty() && opacity > 0)
{
customPaintCode = snippets[0];
snippets.remove (0);
}
}
//==============================================================================
PaintElementImage::SetResourceAction::SetResourceAction (PaintElementImage* const element, const String& newResource_)
: PaintElementUndoableAction <PaintElementImage> (element),
newResource (newResource_)
{
oldResource = element->getResource();
}
bool PaintElementImage::SetResourceAction::perform()
{
showCorrectTab();
getElement()->setResource (newResource, false);
return true;
}
bool PaintElementImage::SetResourceAction::undo()
{
showCorrectTab();
getElement()->setResource (oldResource, false);
return true;
}
void PaintElementImage::setResource (const String& newName, const bool undoable)
{
if (resourceName != newName)
{
if (undoable)
{
perform (new SetResourceAction (this, newName),
"Change image resource");
}
else
{
resourceName = newName;
changed();
}
}
repaint();
}
String PaintElementImage::getResource() const
{
return resourceName;
}
//==============================================================================
PaintElementImage::SetOpacityAction::SetOpacityAction (PaintElementImage* const element, const double newOpacity_)
: PaintElementUndoableAction <PaintElementImage> (element),
newOpacity (newOpacity_)
{
oldOpacity = element->getOpacity();
}
bool PaintElementImage::SetOpacityAction::perform()
{
showCorrectTab();
getElement()->setOpacity (newOpacity, false);
return true;
}
bool PaintElementImage::SetOpacityAction::undo()
{
showCorrectTab();
getElement()->setOpacity (oldOpacity, false);
return true;
}
void PaintElementImage::setOpacity (double newOpacity, const bool undoable)
{
newOpacity = jlimit (0.0, 1.0, newOpacity);
if (opacity != newOpacity)
{
if (undoable)
{
perform (new SetOpacityAction (this, newOpacity),
"Change image opacity");
}
else
{
opacity = newOpacity;
changed();
}
}
}
double PaintElementImage::getOpacity() const noexcept { return opacity; }
//==============================================================================
const char* PaintElementImage::getTagName() noexcept { return "IMAGE"; }
void PaintElementImage::resetToImageSize()
{
if (const Drawable* const image = getDrawable())
{
if (PaintRoutineEditor* ed = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
{
const Rectangle<int> parentArea (ed->getComponentArea());
Rectangle<int> r (getCurrentBounds (parentArea));
Rectangle<float> b (image->getDrawableBounds());
r.setSize ((int) (b.getWidth() + 0.999f),
(int) (b.getHeight() + 0.999f));
setCurrentBounds (r, parentArea, true);
}
}
}
//==============================================================================
PaintElementImage::SetStretchModeAction::SetStretchModeAction (PaintElementImage* const element, const StretchMode newValue_)
: PaintElementUndoableAction <PaintElementImage> (element),
newValue (newValue_)
{
oldValue = element->getStretchMode();
}
bool PaintElementImage::SetStretchModeAction::perform()
{
showCorrectTab();
getElement()->setStretchMode (newValue, false);
return true;
}
bool PaintElementImage::SetStretchModeAction::undo()
{
showCorrectTab();
getElement()->setStretchMode (oldValue, false);
return true;
}
PaintElementImage::StretchMode PaintElementImage::getStretchMode() const noexcept { return mode; }
void PaintElementImage::setStretchMode (const StretchMode newMode, const bool undoable)
{
if (mode != newMode)
{
if (undoable)
{
perform (new SetStretchModeAction (this, newMode),
"Change image mode");
}
else
{
mode = newMode;
changed();
}
}
}
//==============================================================================
XmlElement* PaintElementImage::createXml() const
{
XmlElement* e = new XmlElement (getTagName());
position.applyToXml (*e);
e->setAttribute ("resource", resourceName);
e->setAttribute ("opacity", opacity);
e->setAttribute ("mode", (int) mode);
return e;
}
bool PaintElementImage::loadFromXml (const XmlElement& xml)
{
if (xml.hasTagName (getTagName()))
{
position.restoreFromXml (xml, position);
resourceName = xml.getStringAttribute ("resource", String());
opacity = xml.getDoubleAttribute ("opacity", 1.0);
mode = (StretchMode) xml.getIntAttribute ("mode", (int) stretched);
repaint();
return true;
}
jassertfalse;
return false;
}
//==============================================================================
PaintElementImage::ImageElementResourceProperty::ImageElementResourceProperty (PaintElementImage* const e)
: ImageResourceProperty <PaintElementImage> (e, "image source")
{
}
void PaintElementImage::ImageElementResourceProperty::setResource (const String& newName)
{
if (element != nullptr)
element->setResource (newName, true);
}
String PaintElementImage::ImageElementResourceProperty::getResource() const
{
if (element != nullptr)
return element->getResource();
return {};
}
//==============================================================================
PaintElementImage::OpacityProperty::OpacityProperty (PaintElementImage* const e)
: SliderPropertyComponent ("opacity", 0.0, 1.0, 0.001),
listener (e)
{
listener.setPropertyToRefresh (*this);
}
void PaintElementImage::OpacityProperty::setValue (double newValue)
{
listener.owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
listener.owner->setOpacity (newValue, true);
}
double PaintElementImage::OpacityProperty::getValue() const
{
return listener.owner->getOpacity();
}
PaintElementImage::StretchModeProperty::StretchModeProperty (PaintElementImage* const e)
: ChoicePropertyComponent ("stretch mode"),
listener (e)
{
listener.setPropertyToRefresh (*this);
choices.add ("Stretched to fit");
choices.add ("Maintain aspect ratio");
choices.add ("Maintain aspect ratio, only reduce in size");
}
void PaintElementImage::StretchModeProperty::setIndex (int newIndex)
{
listener.owner->setStretchMode ((StretchMode) newIndex, true);
}
int PaintElementImage::StretchModeProperty::getIndex() const
{
return (int) listener.owner->getStretchMode();
}
PaintElementImage::ResetSizeProperty::ResetSizeProperty (PaintElementImage* const e)
: ButtonPropertyComponent ("reset", false),
element (e)
{
}
void PaintElementImage::ResetSizeProperty::buttonClicked()
{
element->resetToImageSize();
}
String PaintElementImage::ResetSizeProperty::getButtonText() const { return "reset to image size"; }

View File

@@ -0,0 +1,165 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_ColouredElement.h"
#include "../Properties/jucer_FilePropertyComponent.h"
#include "jucer_ImageResourceProperty.h"
#include "jucer_PaintElementUndoableAction.h"
//==============================================================================
class PaintElementImage : public PaintElement
{
public:
PaintElementImage (PaintRoutine*);
~PaintElementImage() override;
enum StretchMode
{
stretched = 0,
proportional = 1,
proportionalReducingOnly = 2
};
const Drawable* getDrawable();
void draw (Graphics&, const ComponentLayout*, const Rectangle<int>&) override;
//==============================================================================
void getEditableProperties (Array <PropertyComponent*>&, bool) override;
void fillInGeneratedCode (GeneratedCode&, String&) override;
void applyCustomPaintSnippets (StringArray& snippets) override;
//==============================================================================
class SetResourceAction : public PaintElementUndoableAction <PaintElementImage>
{
public:
SetResourceAction (PaintElementImage* const, const String&);
bool perform();
bool undo();
private:
String newResource, oldResource;
};
void setResource (const String&, const bool);
String getResource() const;
//==============================================================================
class SetOpacityAction : public PaintElementUndoableAction <PaintElementImage>
{
public:
SetOpacityAction (PaintElementImage* const, const double);
bool perform();
bool undo();
private:
double newOpacity, oldOpacity;
};
void setOpacity (double, const bool);
double getOpacity() const noexcept;
//==============================================================================
static const char* getTagName() noexcept;
void resetToImageSize();
//==============================================================================
class SetStretchModeAction : public PaintElementUndoableAction <PaintElementImage>
{
public:
SetStretchModeAction (PaintElementImage* const, const StretchMode);
bool perform();
bool undo();
private:
StretchMode newValue, oldValue;
};
StretchMode getStretchMode() const noexcept;
void setStretchMode (const StretchMode, const bool);
//==============================================================================
XmlElement* createXml() const override;
bool loadFromXml (const XmlElement&) override;
private:
String resourceName;
double opacity;
StretchMode mode;
String customPaintCode;
//==============================================================================
class ImageElementResourceProperty : public ImageResourceProperty <PaintElementImage>
{
public:
ImageElementResourceProperty (PaintElementImage* const);
void setResource (const String&);
String getResource() const;
};
//==============================================================================
class OpacityProperty : public SliderPropertyComponent
{
public:
OpacityProperty (PaintElementImage* const);
void setValue (double);
double getValue() const;
ElementListener<PaintElementImage> listener;
};
class StretchModeProperty : public ChoicePropertyComponent
{
public:
StretchModeProperty (PaintElementImage* const);
void setIndex (int);
int getIndex() const;
ElementListener<PaintElementImage> listener;
};
class ResetSizeProperty : public ButtonPropertyComponent
{
public:
ResetSizeProperty (PaintElementImage* const);
void buttonClicked();
String getButtonText() const;
private:
PaintElementImage* const element;
};
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,176 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_ColouredElement.h"
#include "jucer_ElementSiblingComponent.h"
class PathPointComponent;
class PaintElementPath;
//==============================================================================
class PathPoint
{
public:
PathPoint (PaintElementPath* const owner);
PathPoint (const PathPoint& other);
PathPoint& operator= (const PathPoint& other);
~PathPoint();
static constexpr auto maxRects = 3;
PaintElementPath* owner;
Path::Iterator::PathElementType type;
RelativePositionedRectangle pos [maxRects];
int getNumPoints() const;
void changePointType (const Path::Iterator::PathElementType newType,
const Rectangle<int>& parentArea,
const bool undoable);
void deleteFromPath();
void getEditableProperties (Array<PropertyComponent*>& props, bool multipleSelected);
private:
PathPoint withChangedPointType (const Path::Iterator::PathElementType newType,
const Rectangle<int>& parentArea) const;
};
//==============================================================================
class PaintElementPath : public ColouredElement
{
public:
PaintElementPath (PaintRoutine* owner);
~PaintElementPath() override;
//==============================================================================
void setInitialBounds (int parentWidth, int parentHeight) override;
Rectangle<int> getCurrentBounds (const Rectangle<int>& parentArea) const override;
void setCurrentBounds (const Rectangle<int>& b, const Rectangle<int>& parentArea, const bool undoable) override;
//==============================================================================
bool getPoint (int index, int pointNumber, double& x, double& y, const Rectangle<int>& parentArea) const;
void movePoint (int index, int pointNumber, double newX, double newY, const Rectangle<int>& parentArea, const bool undoable);
RelativePositionedRectangle getPoint (int index, int pointNumber) const;
void setPoint (int index, int pointNumber, const RelativePositionedRectangle& newPoint, const bool undoable);
int getNumPoints() const noexcept { return points.size(); }
PathPoint* getPoint (int index) const noexcept { return points [index]; }
int indexOfPoint (PathPoint* const p) const noexcept { return points.indexOf (p); }
PathPoint* addPoint (int pointIndexToAddItAfter, const bool undoable);
void deletePoint (int pointIndex, const bool undoable);
void pointListChanged();
int findSegmentAtXY (int x, int y) const;
//==============================================================================
bool isSubpathClosed (int pointIndex) const;
void setSubpathClosed (int pointIndex, const bool closed, const bool undoable);
bool isNonZeroWinding() const noexcept { return nonZeroWinding; }
void setNonZeroWinding (const bool nonZero, const bool undoable);
//==============================================================================
void getEditableProperties (Array<PropertyComponent*>& props, bool multipleSelected) override;
void fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) override;
void applyCustomPaintSnippets (StringArray& snippets) override;
//==============================================================================
static const char* getTagName() noexcept { return "PATH"; }
XmlElement* createXml() const override;
bool loadFromXml (const XmlElement& xml) override;
void setToPath (const Path& p);
//==============================================================================
void draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea) override;
void drawExtraEditorGraphics (Graphics& g, const Rectangle<int>& relativeTo) override;
void resized() override;
void parentSizeChanged() override;
void mouseDown (const MouseEvent& e) override;
void mouseDrag (const MouseEvent& e) override;
void mouseUp (const MouseEvent& e) override;
void createSiblingComponents() override;
void changed() override;
private:
friend class PathPoint;
friend class PathPointComponent;
OwnedArray<PathPoint> points;
bool nonZeroWinding;
mutable Path path;
mutable Rectangle<int> lastPathBounds;
int mouseDownOnSegment;
bool mouseDownSelectSegmentStatus;
String customPaintCode;
String pathToString() const;
void restorePathFromString (const String& s);
void updateStoredPath (const ComponentLayout* layout, const Rectangle<int>& parentArea) const;
int getBorderSize() const;
void rescalePoint (RelativePositionedRectangle& pos, int dx, int dy,
double scaleX, double scaleY,
double scaleStartX, double scaleStartY,
const Rectangle<int>& parentArea) const;
};
//==============================================================================
class PathPointComponent : public ElementSiblingComponent
{
public:
PathPointComponent (PaintElementPath* const path_,
const int index, const int pointNumber);
~PathPointComponent();
void updatePosition();
void showPopupMenu();
void paint (Graphics& g);
void mouseDown (const MouseEvent& e);
void mouseDrag (const MouseEvent& e);
void mouseUp (const MouseEvent& e);
void changeListenerCallback (ChangeBroadcaster*);
private:
PaintElementPath* const path;
PaintRoutine* const routine;
const int index;
const int pointNumber;
int dragX, dragY;
bool selected, dragging, mouseDownSelectStatus;
};

View File

@@ -0,0 +1,184 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_ColouredElement.h"
//==============================================================================
class PaintElementRectangle : public ColouredElement
{
public:
PaintElementRectangle (PaintRoutine* pr)
: ColouredElement (pr, "Rectangle", true, false)
{
}
Rectangle<int> getCurrentBounds (const Rectangle<int>& parentArea) const override
{
return PaintElement::getCurrentBounds (parentArea); // bypass the ColouredElement implementation
}
void setCurrentBounds (const Rectangle<int>& newBounds, const Rectangle<int>& parentArea, const bool undoable) override
{
PaintElement::setCurrentBounds (newBounds, parentArea, undoable); // bypass the ColouredElement implementation
}
void draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea) override
{
Component tempParentComp;
tempParentComp.setBounds (parentArea);
fillType.setFillType (g, getDocument(), parentArea);
const Rectangle<int> r (position.getRectangle (parentArea, layout));
g.fillRect (r);
if (isStrokePresent)
{
strokeType.fill.setFillType (g, getDocument(), parentArea);
g.drawRect (r.getX(), r.getY(), r.getWidth(), r.getHeight(),
roundToInt (getStrokeType().stroke.getStrokeThickness()));
}
}
void getEditableProperties (Array <PropertyComponent*>& props, bool multipleSelected) override
{
ColouredElement::getEditableProperties (props, multipleSelected);
props.add (new ShapeToPathProperty (this));
}
void fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) override
{
if (fillType.isInvisible() && (strokeType.isInvisible() || ! isStrokePresent))
return;
String x, y, w, h, s;
positionToCode (position, code.document->getComponentLayout(), x, y, w, h);
s << "{\n"
<< " int x = " << x << ", y = " << y << ", width = " << w << ", height = " << h << ";\n";
if (! fillType.isInvisible())
s << " " << fillType.generateVariablesCode ("fill");
if (isStrokePresent && ! strokeType.isInvisible())
s << " " << strokeType.fill.generateVariablesCode ("stroke");
s << " //[UserPaintCustomArguments] Customize the painting arguments here..\n"
<< customPaintCode
<< " //[/UserPaintCustomArguments]\n";
if (! fillType.isInvisible())
{
s << " ";
fillType.fillInGeneratedCode ("fill", position, code, s);
s << " g.fillRect (x, y, width, height);\n";
}
if (isStrokePresent && ! strokeType.isInvisible())
{
s << " ";
strokeType.fill.fillInGeneratedCode ("stroke", position, code, s);
s << " g.drawRect (x, y, width, height, " << roundToInt (strokeType.stroke.getStrokeThickness()) << ");\n\n";
}
s << "}\n\n";
paintMethodCode += s;
}
void applyCustomPaintSnippets (StringArray& snippets) override
{
customPaintCode.clear();
if (! snippets.isEmpty() && (! fillType.isInvisible() || (isStrokePresent && ! strokeType.isInvisible())))
{
customPaintCode = snippets[0];
snippets.remove (0);
}
}
static const char* getTagName() noexcept { return "RECT"; }
XmlElement* createXml() const override
{
XmlElement* e = new XmlElement (getTagName());
position.applyToXml (*e);
addColourAttributes (e);
return e;
}
bool loadFromXml (const XmlElement& xml) override
{
if (xml.hasTagName (getTagName()))
{
position.restoreFromXml (xml, position);
loadColourAttributes (xml);
return true;
}
jassertfalse;
return false;
}
void convertToPath()
{
Path path;
path.addRectangle (getCurrentAbsoluteBounds());
convertToNewPathElement (path);
}
private:
String customPaintCode;
class ShapeToPathProperty : public ButtonPropertyComponent
{
public:
ShapeToPathProperty (PaintElementRectangle* const e)
: ButtonPropertyComponent ("path", false),
element (e)
{
}
void buttonClicked()
{
element->convertToPath();
}
String getButtonText() const
{
return "convert to a path";
}
private:
PaintElementRectangle* const element;
};
};

View File

@@ -0,0 +1,267 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_ColouredElement.h"
#include "../jucer_UtilityFunctions.h"
//==============================================================================
class PaintElementRoundedRectangle : public ColouredElement
{
public:
PaintElementRoundedRectangle (PaintRoutine* pr)
: ColouredElement (pr, "Rounded Rectangle", true, false)
{
cornerSize = 10.0;
}
void draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea) override
{
double x, y, w, h;
position.getRectangleDouble (x, y, w, h, parentArea, layout);
fillType.setFillType (g, getDocument(), parentArea);
g.fillRoundedRectangle ((float) x, (float) y, (float) w, (float) h, (float) cornerSize);
if (isStrokePresent)
{
strokeType.fill.setFillType (g, getDocument(), parentArea);
g.drawRoundedRectangle ((float) x, (float) y, (float) w, (float) h, (float) cornerSize,
getStrokeType().stroke.getStrokeThickness());
}
}
void getEditableProperties (Array<PropertyComponent*>& props, bool multipleSelected) override
{
props.add (new CornerSizeProperty (this));
ColouredElement::getEditableProperties (props, multipleSelected);
props.add (new ShapeToPathProperty (this));
}
//==============================================================================
class SetCornerSizeAction : public PaintElementUndoableAction <PaintElementRoundedRectangle>
{
public:
SetCornerSizeAction (PaintElementRoundedRectangle* const element, const double newSize_)
: PaintElementUndoableAction <PaintElementRoundedRectangle> (element),
newSize (newSize_)
{
oldSize = element->getCornerSize();
}
bool perform()
{
showCorrectTab();
getElement()->setCornerSize (newSize, false);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setCornerSize (oldSize, false);
return true;
}
private:
double newSize, oldSize;
};
void setCornerSize (const double newSize, const bool undoable)
{
if (newSize != cornerSize)
{
if (undoable)
{
perform (new SetCornerSizeAction (this, newSize),
"Change rounded rectangle corner size");
}
else
{
cornerSize = newSize;
changed();
}
}
}
double getCornerSize() const noexcept { return cornerSize; }
//==============================================================================
void fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) override
{
if (fillType.isInvisible() && (strokeType.isInvisible() || ! isStrokePresent))
return;
String x, y, w, h, s;
positionToCode (position, code.document->getComponentLayout(), x, y, w, h);
s << "{\n"
<< " float x = " << castToFloat (x) << ", y = " << castToFloat (y) << ", "
<< "width = " << castToFloat (w) << ", height = " << castToFloat (h) << ";\n";
if (! fillType.isInvisible())
s << " " << fillType.generateVariablesCode ("fill");
if (isStrokePresent && ! strokeType.isInvisible())
s << " " << strokeType.fill.generateVariablesCode ("stroke");
s << " //[UserPaintCustomArguments] Customize the painting arguments here..\n"
<< customPaintCode
<< " //[/UserPaintCustomArguments]\n";
if (! fillType.isInvisible())
{
s << " ";
fillType.fillInGeneratedCode ("fill", position, code, s);
s << " g.fillRoundedRectangle (x, y, width, height, " << CodeHelpers::floatLiteral (cornerSize, 3) << ");\n";
}
if (isStrokePresent && ! strokeType.isInvisible())
{
s << " ";
strokeType.fill.fillInGeneratedCode ("stroke", position, code, s);
s << " g.drawRoundedRectangle (x, y, width, height, " << CodeHelpers::floatLiteral (cornerSize, 3)
<< ", " << CodeHelpers::floatLiteral (strokeType.stroke.getStrokeThickness(), 3) << ");\n";
}
s << "}\n\n";
paintMethodCode += s;
}
void applyCustomPaintSnippets (StringArray& snippets) override
{
customPaintCode.clear();
if (! snippets.isEmpty() && (! fillType.isInvisible() || (isStrokePresent && ! strokeType.isInvisible())))
{
customPaintCode = snippets[0];
snippets.remove (0);
}
}
static const char* getTagName() noexcept { return "ROUNDRECT"; }
XmlElement* createXml() const override
{
XmlElement* const e = new XmlElement (getTagName());
position.applyToXml (*e);
e->setAttribute ("cornerSize", cornerSize);
addColourAttributes (e);
return e;
}
bool loadFromXml (const XmlElement& xml) override
{
if (xml.hasTagName (getTagName()))
{
position.restoreFromXml (xml, position);
cornerSize = xml.getDoubleAttribute ("cornerSize", 10.0);
loadColourAttributes (xml);
return true;
}
jassertfalse;
return false;
}
void convertToPath()
{
double x, y, w, h;
getCurrentAbsoluteBoundsDouble (x, y, w, h);
Path path;
path.addRoundedRectangle ((float) x, (float) y, (float) w, (float) h, (float) cornerSize);
convertToNewPathElement (path);
}
private:
double cornerSize;
String customPaintCode;
//==============================================================================
class CornerSizeProperty : public SliderPropertyComponent,
private juce::ChangeListener
{
public:
CornerSizeProperty (PaintElementRoundedRectangle* const owner_)
: SliderPropertyComponent ("corner size", 1.0, 200.0, 0.5, 0.4),
owner (owner_)
{
owner->getDocument()->addChangeListener (this);
}
~CornerSizeProperty() override
{
owner->getDocument()->removeChangeListener (this);
}
void setValue (double newValue) override
{
owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
owner->setCornerSize (newValue, true);
}
double getValue() const override { return owner->getCornerSize(); }
private:
void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
PaintElementRoundedRectangle* const owner;
};
//==============================================================================
class ShapeToPathProperty : public ButtonPropertyComponent
{
public:
ShapeToPathProperty (PaintElementRoundedRectangle* const e)
: ButtonPropertyComponent ("path", false),
element (e)
{
}
void buttonClicked()
{
element->convertToPath();
}
String getButtonText() const
{
return "convert to a path";
}
private:
PaintElementRoundedRectangle* const element;
};
};

View File

@@ -0,0 +1,672 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_ColouredElement.h"
#include "../Properties/jucer_FontPropertyComponent.h"
#include "../Properties/jucer_JustificationProperty.h"
//==============================================================================
class PaintElementText : public ColouredElement
{
public:
PaintElementText (PaintRoutine* pr)
: ColouredElement (pr, "Text", false, false),
text ("Your text goes here"),
font (15.0f),
typefaceName (FontPropertyComponent::getDefaultFont()),
justification (Justification::centred)
{
fillType.colour = Colours::black;
position.rect.setWidth (200);
position.rect.setHeight (30);
}
//==============================================================================
void draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea) override
{
fillType.setFillType (g, getDocument(), parentArea);
font = FontPropertyComponent::applyNameToFont (typefaceName, font);
g.setFont (font);
g.drawText (replaceStringTranslations (text, owner->getDocument()),
position.getRectangle (parentArea, layout), justification, true);
}
static String replaceStringTranslations (String s, JucerDocument* document)
{
s = s.replace ("%%getName()%%", document->getComponentName());
s = s.replace ("%%getButtonText()%%", document->getComponentName());
return s;
}
void getEditableProperties (Array<PropertyComponent*>& props, bool multipleSelected) override
{
ColouredElement::getEditableProperties (props, multipleSelected);
if (multipleSelected)
return;
props.add (new TextProperty (this));
props.add (new FontNameProperty (this));
props.add (new FontStyleProperty (this));
props.add (new FontSizeProperty (this));
props.add (new FontKerningProperty (this));
props.add (new TextJustificationProperty (this));
props.add (new TextToPathProperty (this));
}
void fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) override
{
if (! fillType.isInvisible())
{
String x, y, w, h, r;
positionToCode (position, code.document->getComponentLayout(), x, y, w, h);
r << "{\n"
<< " int x = " << x << ", y = " << y << ", width = " << w << ", height = " << h << ";\n"
<< " juce::String text (" << quotedString (text, code.shouldUseTransMacro()) << ");\n"
<< " " << fillType.generateVariablesCode ("fill")
<< " //[UserPaintCustomArguments] Customize the painting arguments here..\n"
<< customPaintCode
<< " //[/UserPaintCustomArguments]\n"
<< " ";
fillType.fillInGeneratedCode ("fill", position, code, r);
r << " g.setFont (" << FontPropertyComponent::getCompleteFontCode (font, typefaceName) << ");\n"
<< " g.drawText (text, x, y, width, height,\n"
<< " " << CodeHelpers::justificationToCode (justification) << ", true);\n"
<< "}\n\n";
paintMethodCode += r;
}
}
void applyCustomPaintSnippets (StringArray& snippets) override
{
customPaintCode.clear();
if (! snippets.isEmpty() && ! fillType.isInvisible())
{
customPaintCode = snippets[0];
snippets.remove (0);
}
}
static const char* getTagName() noexcept { return "TEXT"; }
XmlElement* createXml() const override
{
XmlElement* e = new XmlElement (getTagName());
position.applyToXml (*e);
addColourAttributes (e);
e->setAttribute ("text", text);
e->setAttribute ("fontname", typefaceName);
e->setAttribute ("fontsize", roundToInt (font.getHeight() * 100.0) / 100.0);
e->setAttribute ("kerning", roundToInt (font.getExtraKerningFactor() * 1000.0) / 1000.0);
e->setAttribute ("bold", font.isBold());
e->setAttribute ("italic", font.isItalic());
e->setAttribute ("justification", justification.getFlags());
if (font.getTypefaceStyle() != "Regular")
{
e->setAttribute ("typefaceStyle", font.getTypefaceStyle());
}
return e;
}
bool loadFromXml (const XmlElement& xml) override
{
if (xml.hasTagName (getTagName()))
{
position.restoreFromXml (xml, position);
loadColourAttributes (xml);
text = xml.getStringAttribute ("text", "Hello World");
typefaceName = xml.getStringAttribute ("fontname", FontPropertyComponent::getDefaultFont());
font = FontPropertyComponent::applyNameToFont (typefaceName, font);
font.setHeight ((float) xml.getDoubleAttribute ("fontsize", 15.0));
font.setBold (xml.getBoolAttribute ("bold", false));
font.setItalic (xml.getBoolAttribute ("italic", false));
font.setExtraKerningFactor ((float) xml.getDoubleAttribute ("kerning", 0.0));
justification = Justification (xml.getIntAttribute ("justification", Justification::centred));
auto fontStyle = xml.getStringAttribute ("typefaceStyle");
if (! fontStyle.isEmpty())
font.setTypefaceStyle (fontStyle);
return true;
}
jassertfalse;
return false;
}
//==============================================================================
const String& getText() const noexcept { return text; }
class SetTextAction : public PaintElementUndoableAction <PaintElementText>
{
public:
SetTextAction (PaintElementText* const element, const String& newText_)
: PaintElementUndoableAction <PaintElementText> (element),
newText (newText_),
oldText (element->getText())
{
}
bool perform()
{
showCorrectTab();
getElement()->setText (newText, false);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setText (oldText, false);
return true;
}
private:
String newText, oldText;
};
void setText (const String& t, const bool undoable)
{
if (t != text)
{
if (undoable)
{
perform (new SetTextAction (this, t),
"Change text element text");
}
else
{
text = t;
changed();
}
}
}
//==============================================================================
const Font& getFont() const { return font; }
class SetFontAction : public PaintElementUndoableAction <PaintElementText>
{
public:
SetFontAction (PaintElementText* const element, const Font& newFont_)
: PaintElementUndoableAction <PaintElementText> (element),
newFont (newFont_),
oldFont (element->getFont())
{
}
bool perform()
{
showCorrectTab();
getElement()->setFont (newFont, false);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setFont (oldFont, false);
return true;
}
private:
Font newFont, oldFont;
};
void setFont (const Font& newFont, const bool undoable)
{
if (font != newFont)
{
if (undoable)
{
perform (new SetFontAction (this, newFont),
"Change text element font");
}
else
{
font = newFont;
changed();
}
}
}
//==============================================================================
class SetTypefaceAction : public PaintElementUndoableAction <PaintElementText>
{
public:
SetTypefaceAction (PaintElementText* const element, const String& newValue_)
: PaintElementUndoableAction <PaintElementText> (element),
newValue (newValue_),
oldValue (element->getTypefaceName())
{
}
bool perform()
{
showCorrectTab();
getElement()->setTypefaceName (newValue, false);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setTypefaceName (oldValue, false);
return true;
}
private:
String newValue, oldValue;
};
void setTypefaceName (const String& newFontName, const bool undoable)
{
if (undoable)
{
perform (new SetTypefaceAction (this, newFontName),
"Change text element typeface");
}
else
{
typefaceName = newFontName;
changed();
}
}
String getTypefaceName() const noexcept { return typefaceName; }
//==============================================================================
Justification getJustification() const noexcept { return justification; }
class SetJustifyAction : public PaintElementUndoableAction <PaintElementText>
{
public:
SetJustifyAction (PaintElementText* const element, Justification newValue_)
: PaintElementUndoableAction <PaintElementText> (element),
newValue (newValue_),
oldValue (element->getJustification())
{
}
bool perform()
{
showCorrectTab();
getElement()->setJustification (newValue, false);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setJustification (oldValue, false);
return true;
}
private:
Justification newValue, oldValue;
};
void setJustification (Justification j, const bool undoable)
{
if (justification.getFlags() != j.getFlags())
{
if (undoable)
{
perform (new SetJustifyAction (this, j),
"Change text element justification");
}
else
{
justification = j;
changed();
}
}
}
void convertToPath()
{
if (PaintRoutineEditor* parent = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
{
font = FontPropertyComponent::applyNameToFont (typefaceName, font);
const Rectangle<int> r =
getCurrentBounds (parent->getComponentArea().withZeroOrigin());
GlyphArrangement arr;
arr.addCurtailedLineOfText (font, text,
0.0f, 0.0f, (float) r.getWidth(),
true);
arr.justifyGlyphs (0, arr.getNumGlyphs(),
(float) r.getX(), (float) r.getY(),
(float) r.getWidth(), (float) r.getHeight(),
justification);
Path path;
arr.createPath (path);
convertToNewPathElement (path);
}
else
{
jassertfalse;
}
}
private:
String text;
Font font;
String typefaceName;
Justification justification;
String customPaintCode;
Array <Justification> justificationTypes;
//==============================================================================
class TextProperty : public TextPropertyComponent,
private juce::ChangeListener
{
public:
TextProperty (PaintElementText* const e)
: TextPropertyComponent ("text", 2048, false),
element (e)
{
element->getDocument()->addChangeListener (this);
}
~TextProperty() override
{
element->getDocument()->removeChangeListener (this);
}
void setText (const String& newText) override { element->setText (newText, true); }
String getText() const override { return element->getText(); }
private:
void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
PaintElementText* const element;
};
//==============================================================================
class FontNameProperty : public FontPropertyComponent,
private juce::ChangeListener
{
public:
FontNameProperty (PaintElementText* const e)
: FontPropertyComponent ("font"),
element (e)
{
element->getDocument()->addChangeListener (this);
}
~FontNameProperty()
{
element->getDocument()->removeChangeListener (this);
}
void setTypefaceName (const String& newFontName) { element->setTypefaceName (newFontName, true); }
String getTypefaceName() const { return element->getTypefaceName(); }
private:
void changeListenerCallback (ChangeBroadcaster*) { refresh(); }
PaintElementText* const element;
};
//==============================================================================
class FontStyleProperty : public ChoicePropertyComponent,
private juce::ChangeListener
{
public:
FontStyleProperty (PaintElementText* const e)
: ChoicePropertyComponent ("style"),
element (e)
{
element->getDocument()->addChangeListener (this);
updateStylesList (element->getTypefaceName());
}
~FontStyleProperty()
{
element->getDocument()->removeChangeListener (this);
}
void updateStylesList (const String& name)
{
if (getNumChildComponents() > 0)
{
if (auto cb = dynamic_cast<ComboBox*> (getChildComponent (0)))
cb->clear();
getChildComponent (0)->setVisible (false);
removeAllChildren();
}
choices.clear();
choices.add ("Regular");
choices.add ("Bold");
choices.add ("Italic");
choices.add ("Bold Italic");
choices.mergeArray (Font::findAllTypefaceStyles (name));
refresh();
}
void setIndex (int newIndex)
{
Font f (element->getFont());
if (f.getAvailableStyles().contains (choices[newIndex]))
{
f.setBold (false);
f.setItalic (false);
f.setTypefaceStyle (choices[newIndex]);
}
else
{
f.setTypefaceStyle ("Regular");
f.setBold (newIndex == 1 || newIndex == 3);
f.setItalic (newIndex == 2 || newIndex == 3);
}
element->setFont (f, true);
}
int getIndex() const
{
auto f = element->getFont();
const auto typefaceIndex = choices.indexOf (f.getTypefaceStyle());
if (typefaceIndex == -1)
{
if (f.isBold() && f.isItalic())
return 3;
else if (f.isBold())
return 1;
else if (f.isItalic())
return 2;
return 0;
}
return typefaceIndex;
}
private:
void changeListenerCallback (ChangeBroadcaster*)
{
updateStylesList (element->getTypefaceName());
}
PaintElementText* const element;
};
//==============================================================================
class FontSizeProperty : public SliderPropertyComponent,
private juce::ChangeListener
{
public:
FontSizeProperty (PaintElementText* const e)
: SliderPropertyComponent ("size", 1.0, 250.0, 0.1, 0.3),
element (e)
{
element->getDocument()->addChangeListener (this);
}
~FontSizeProperty() override
{
element->getDocument()->removeChangeListener (this);
}
void setValue (double newValue) override
{
element->getDocument()->getUndoManager().undoCurrentTransactionOnly();
Font f (element->getFont());
f.setHeight ((float) newValue);
element->setFont (f, true);
}
double getValue() const override
{
return element->getFont().getHeight();
}
private:
void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
PaintElementText* const element;
};
//==============================================================================
class FontKerningProperty : public SliderPropertyComponent,
private juce::ChangeListener
{
public:
FontKerningProperty (PaintElementText* const e)
: SliderPropertyComponent ("kerning", -0.5, 0.5, 0.001),
element (e)
{
element->getDocument()->addChangeListener (this);
}
~FontKerningProperty() override
{
element->getDocument()->removeChangeListener (this);
}
void setValue (double newValue) override
{
element->getDocument()->getUndoManager().undoCurrentTransactionOnly();
Font f (element->getFont());
f.setExtraKerningFactor ((float) newValue);
element->setFont (f, true);
}
double getValue() const override
{
return element->getFont().getExtraKerningFactor();
}
private:
void changeListenerCallback (ChangeBroadcaster*) override
{
refresh();
}
PaintElementText* const element;
};
//==============================================================================
class TextJustificationProperty : public JustificationProperty,
private juce::ChangeListener
{
public:
TextJustificationProperty (PaintElementText* const e)
: JustificationProperty ("layout", false),
element (e)
{
element->getDocument()->addChangeListener (this);
}
~TextJustificationProperty() override
{
element->getDocument()->removeChangeListener (this);
}
void setJustification (Justification newJustification) override
{
element->setJustification (newJustification, true);
}
Justification getJustification() const override
{
return element->getJustification();
}
private:
void changeListenerCallback (ChangeBroadcaster*) override { refresh(); }
PaintElementText* const element;
};
//==============================================================================
class TextToPathProperty : public ButtonPropertyComponent
{
public:
TextToPathProperty (PaintElementText* const e)
: ButtonPropertyComponent ("path", false),
element (e)
{
}
void buttonClicked()
{
element->convertToPath();
}
String getButtonText() const
{
return "convert text to a path";
}
private:
PaintElementText* const element;
};
};

View File

@@ -0,0 +1,142 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../UI/jucer_JucerDocumentEditor.h"
#include "jucer_PaintElementGroup.h"
//==============================================================================
template <class ElementType>
class PaintElementUndoableAction : public UndoableAction
{
public:
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011)
PaintElementUndoableAction (ElementType* const element)
: routine (*element->getOwner()),
elementIndex (element->getOwner()->indexOfElement (element))
{
jassert (element != nullptr);
if (element != nullptr && elementIndex < 0)
findGroupIndices (element->getOwner(), element);
jassert (elementIndex >= 0);
}
JUCE_END_IGNORE_WARNINGS_MSVC
ElementType* getElement() const
{
if (containerGroups.size() > 0)
{
auto group = dynamic_cast<PaintElementGroup*> (routine.getElement (containerGroups.getFirst()));
if (group == nullptr)
return nullptr;
for (int i = 1; i < containerGroups.size(); ++i)
{
group = dynamic_cast<PaintElementGroup*> (group->getElement (containerGroups.getUnchecked(i)));
if (group == nullptr)
return nullptr;
}
auto e = dynamic_cast<ElementType*> (group->getElement (elementIndex));
jassert (e != nullptr);
return e;
}
else
{
auto e = dynamic_cast<ElementType*> (routine.getElement (elementIndex));
jassert (e != nullptr);
return e;
}
}
int getSizeInUnits() { return 2; }
protected:
PaintRoutine& routine;
int elementIndex;
Array <int> containerGroups;
void changed() const
{
jassert (routine.getDocument() != nullptr);
routine.getDocument()->changed();
}
void showCorrectTab() const
{
if (JucerDocumentEditor* const docHolder = JucerDocumentEditor::getActiveDocumentHolder())
docHolder->showGraphics (&routine);
if (routine.getSelectedElements().getNumSelected() == 0)
if (ElementType* const e = dynamic_cast<ElementType*> (routine.getElement (elementIndex)))
routine.getSelectedElements().selectOnly (e);
}
private:
void findGroupIndices (PaintRoutine* const pr, PaintElement* const element)
{
for (int i = pr->getNumElements(); --i >= 0;)
{
if (auto pg = dynamic_cast<PaintElementGroup*> (pr->getElement (i)))
{
if (pg->containsElement (element))
{
containerGroups.add (i);
findGroupIndices (pg, element);
}
}
}
}
void findGroupIndices (PaintElementGroup* const group, PaintElement* const element)
{
elementIndex = group->indexOfElement (element);
if (elementIndex < 0)
{
for (int i = group->getNumElements(); --i >= 0;)
{
if (auto pg = dynamic_cast<PaintElementGroup*> (group->getElement (i)))
{
if (pg->containsElement (element))
{
containerGroups.add (i);
findGroupIndices (pg, element);
}
}
}
}
}
PaintElementUndoableAction (const PaintElementUndoableAction&);
PaintElementUndoableAction& operator= (const PaintElementUndoableAction&);
};

View File

@@ -0,0 +1,107 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_ElementSiblingComponent.h"
#include "../UI/jucer_PaintRoutineEditor.h"
//==============================================================================
class PointComponent : public ElementSiblingComponent
{
public:
PointComponent (PaintElement* const e)
: ElementSiblingComponent (e)
{
setSize (11, 11);
setMouseCursor (MouseCursor::UpDownLeftRightResizeCursor);
}
virtual RelativePositionedRectangle getPosition() = 0;
virtual void setPosition (const RelativePositionedRectangle& newPos) = 0;
void updatePosition() override
{
if (dynamic_cast<PaintRoutineEditor*> (getParentComponent()) != nullptr)
{
const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
const Rectangle<int> r (getPosition().getRectangle (area, owner->getDocument()->getComponentLayout()));
setCentrePosition (r.getX(), r.getY());
}
}
//==============================================================================
void paint (Graphics& g) override
{
g.setColour (Colours::white);
g.drawEllipse (2.0f, 2.0f, (float) getWidth() - 4.0f, (float) getHeight() - 4.0f, 2.0f);
g.setColour (Colours::black);
g.drawEllipse (1.0f, 1.0f, (float) getWidth() - 2.0f, (float) getHeight() - 2.0f, 2.0f);
}
//==============================================================================
void mouseDown (const MouseEvent&) override
{
const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
dragX = getX() + getWidth() / 2 - area.getX();
dragY = getY() + getHeight() / 2 - area.getY();
}
void mouseDrag (const MouseEvent& e) override
{
const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
int x = dragX + e.getDistanceFromDragStartX();
int y = dragY + e.getDistanceFromDragStartY();
if (JucerDocument* const document = owner->getDocument())
{
x = document->snapPosition (x);
y = document->snapPosition (y);
const RelativePositionedRectangle original (getPosition());
RelativePositionedRectangle pr (original);
Rectangle<int> r (pr.getRectangle (Rectangle<int> (0, 0, area.getWidth(), area.getHeight()),
document->getComponentLayout()));
r.setPosition (x, y);
pr.updateFrom (r.getX(), r.getY(), r.getWidth(), r.getHeight(),
Rectangle<int> (0, 0, area.getWidth(), area.getHeight()),
document->getComponentLayout());
if (pr != original)
setPosition (pr);
}
}
void mouseUp (const MouseEvent&) override
{
}
private:
int dragX, dragY;
};

View File

@@ -0,0 +1,157 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_FillType.h"
//==============================================================================
class StrokeType
{
public:
StrokeType() : stroke (1.0f)
{
reset();
}
String getPathStrokeCode() const
{
PathStrokeType defaultStroke (1.0f);
String s;
s << "juce::PathStrokeType (" << CodeHelpers::floatLiteral (stroke.getStrokeThickness(), 3);
if (stroke.getJointStyle() != defaultStroke.getJointStyle()
|| stroke.getEndStyle() != defaultStroke.getEndStyle())
{
s << ", ";
switch (stroke.getJointStyle())
{
case PathStrokeType::mitered: s << "juce::PathStrokeType::mitered"; break;
case PathStrokeType::curved: s << "juce::PathStrokeType::curved"; break;
case PathStrokeType::beveled: s << "juce::PathStrokeType::beveled"; break;
default: jassertfalse; break;
}
if (stroke.getEndStyle() != defaultStroke.getEndStyle())
{
s << ", ";
switch (stroke.getEndStyle())
{
case PathStrokeType::butt: s << "juce::PathStrokeType::butt"; break;
case PathStrokeType::square: s << "juce::PathStrokeType::square"; break;
case PathStrokeType::rounded: s << "juce::PathStrokeType::rounded"; break;
default: jassertfalse; break;
}
}
}
s << ")";
return s;
}
String toString() const
{
String s;
s << stroke.getStrokeThickness();
switch (stroke.getJointStyle())
{
case PathStrokeType::mitered: s << ", mitered"; break;
case PathStrokeType::curved: s << ", curved"; break;
case PathStrokeType::beveled: s << ", beveled"; break;
default: jassertfalse; break;
}
switch (stroke.getEndStyle())
{
case PathStrokeType::butt: s << ", butt"; break;
case PathStrokeType::square: s << ", square"; break;
case PathStrokeType::rounded: s << ", rounded"; break;
default: jassertfalse; break;
}
return s;
}
void restoreFromString (const String& s)
{
reset();
if (s.isNotEmpty())
{
const float thickness = (float) s.upToFirstOccurrenceOf (",", false, false).getDoubleValue();
PathStrokeType::JointStyle joint = stroke.getJointStyle();
if (s.containsIgnoreCase ("miter")) joint = PathStrokeType::mitered;
else if (s.containsIgnoreCase ("curve")) joint = PathStrokeType::curved;
else if (s.containsIgnoreCase ("bevel")) joint = PathStrokeType::beveled;
PathStrokeType::EndCapStyle end = stroke.getEndStyle();
if (s.containsIgnoreCase ("butt")) end = PathStrokeType::butt;
else if (s.containsIgnoreCase ("square")) end = PathStrokeType::square;
else if (s.containsIgnoreCase ("round")) end = PathStrokeType::rounded;
stroke = PathStrokeType (thickness, joint, end);
}
}
bool isOpaque() const
{
return fill.isOpaque();
}
bool isInvisible() const
{
return fill.isInvisible() || stroke.getStrokeThickness() <= 0.0f;
}
bool operator== (const StrokeType& other) const noexcept
{
return stroke == other.stroke && fill == other.fill;
}
bool operator!= (const StrokeType& other) const noexcept
{
return ! operator== (other);
}
//==============================================================================
PathStrokeType stroke;
JucerFillType fill;
private:
void reset()
{
stroke = PathStrokeType (5.0f);
fill = JucerFillType();
fill.colour = Colours::black;
}
};

View File

@@ -0,0 +1,209 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class JucerColourPropertyComponent : public PropertyComponent
{
public:
JucerColourPropertyComponent (const String& name,
const bool canReset)
: PropertyComponent (name)
{
colourPropEditor.reset (new ColourPropEditorComponent (this, canReset));
addAndMakeVisible (colourPropEditor.get());
}
virtual void setColour (Colour newColour) = 0;
virtual Colour getColour() const = 0;
virtual void resetToDefault() = 0;
void refresh() override
{
((ColourPropEditorComponent*) getChildComponent (0))->refresh();
}
class ColourEditorComponent : public Component,
private ChangeListener
{
public:
ColourEditorComponent (const bool canReset)
: canResetToDefault (canReset)
{
}
void paint (Graphics& g) override
{
g.fillAll (Colours::grey);
g.fillCheckerBoard (getLocalBounds().reduced (2, 2).toFloat(),
10.0f, 10.0f,
Colour (0xffdddddd).overlaidWith (colour),
Colour (0xffffffff).overlaidWith (colour));
g.setColour (Colours::white.overlaidWith (colour).contrasting());
g.setFont (Font ((float) getHeight() * 0.6f, Font::bold));
g.drawFittedText (colour.toDisplayString (true),
2, 1, getWidth() - 4, getHeight() - 1,
Justification::centred, 1);
}
virtual void setColour (Colour newColour) = 0;
virtual void resetToDefault() = 0;
virtual Colour getColour() const = 0;
void refresh()
{
const Colour col (getColour());
if (col != colour)
{
colour = col;
repaint();
}
}
void mouseDown (const MouseEvent&) override
{
CallOutBox::launchAsynchronously (std::make_unique<ColourSelectorComp> (this, canResetToDefault),
getScreenBounds(),
nullptr);
}
class ColourSelectorComp : public Component
{
public:
ColourSelectorComp (ColourEditorComponent* owner_,
const bool canReset)
: owner (owner_),
defaultButton ("Reset to Default")
{
addAndMakeVisible (selector);
selector.setName ("Colour");
selector.setCurrentColour (owner->getColour());
selector.addChangeListener (owner);
if (canReset)
{
addAndMakeVisible (defaultButton);
defaultButton.onClick = [this]
{
owner->resetToDefault();
owner->refresh();
selector.setCurrentColour (owner->getColour());
};
}
setSize (300, 400);
}
void resized() override
{
if (defaultButton.isVisible())
{
selector.setBounds (0, 0, getWidth(), getHeight() - 30);
defaultButton.changeWidthToFitText (22);
defaultButton.setTopLeftPosition (10, getHeight() - 26);
}
else
{
selector.setBounds (getLocalBounds());
}
}
private:
class ColourSelectorWithSwatches : public ColourSelector
{
public:
ColourSelectorWithSwatches()
{
}
int getNumSwatches() const override
{
return getAppSettings().swatchColours.size();
}
Colour getSwatchColour (int index) const override
{
return getAppSettings().swatchColours [index];
}
void setSwatchColour (int index, const Colour& newColour) override
{
getAppSettings().swatchColours.set (index, newColour);
}
};
ColourEditorComponent* owner;
ColourSelectorWithSwatches selector;
TextButton defaultButton;
};
private:
void changeListenerCallback (ChangeBroadcaster* source) override
{
const ColourSelector* const cs = (const ColourSelector*) source;
if (cs->getCurrentColour() != getColour())
setColour (cs->getCurrentColour());
}
Colour colour;
bool canResetToDefault;
};
class ColourPropEditorComponent : public ColourEditorComponent
{
JucerColourPropertyComponent* const owner;
public:
ColourPropEditorComponent (JucerColourPropertyComponent* const owner_,
const bool canReset)
: ColourEditorComponent (canReset),
owner (owner_)
{}
void setColour (Colour newColour) override
{
owner->setColour (newColour);
}
Colour getColour() const override
{
return owner->getColour();
}
void resetToDefault() override
{
owner->resetToDefault();
}
};
std::unique_ptr<ColourPropEditorComponent> colourPropEditor;
};

View File

@@ -0,0 +1,60 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
template <class ComponentType>
class ComponentBooleanProperty : public BooleanPropertyComponent,
private ChangeListener
{
public:
ComponentBooleanProperty (const String& name,
const String& onText_,
const String& offText_,
ComponentType* comp,
JucerDocument& doc)
: BooleanPropertyComponent (name, onText_, offText_),
component (comp),
document (doc)
{
document.addChangeListener (this);
}
~ComponentBooleanProperty()
{
document.removeChangeListener (this);
}
void changeListenerCallback (ChangeBroadcaster*)
{
refresh();
}
protected:
ComponentType* component;
JucerDocument& document;
};

View File

@@ -0,0 +1,58 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
template <class ComponentType>
class ComponentChoiceProperty : public ChoicePropertyComponent,
private ChangeListener
{
public:
ComponentChoiceProperty (const String& name,
ComponentType* comp,
JucerDocument& doc)
: ChoicePropertyComponent (name),
component (comp),
document (doc)
{
document.addChangeListener (this);
}
~ComponentChoiceProperty()
{
document.removeChangeListener (this);
}
void changeListenerCallback (ChangeBroadcaster*)
{
refresh();
}
protected:
ComponentType* component;
JucerDocument& document;
};

View File

@@ -0,0 +1,165 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_ColourPropertyComponent.h"
//==============================================================================
template <class ComponentType>
class ComponentColourProperty : public JucerColourPropertyComponent,
private ChangeListener
{
public:
ComponentColourProperty (const String& name,
ComponentType* comp,
JucerDocument& doc,
const bool canResetToDefault)
: JucerColourPropertyComponent (name, canResetToDefault),
component (comp),
document (doc)
{
document.addChangeListener (this);
}
~ComponentColourProperty()
{
document.removeChangeListener (this);
}
void changeListenerCallback (ChangeBroadcaster*)
{
refresh();
}
protected:
ComponentType* component;
JucerDocument& document;
};
//==============================================================================
class ComponentColourIdProperty : public ComponentColourProperty <Component>
{
public:
//==============================================================================
ComponentColourIdProperty (Component* const comp,
JucerDocument& doc,
const int colourId_,
const String& name,
const bool canResetToDefault)
: ComponentColourProperty <Component> (name, comp, doc, canResetToDefault),
colourId (colourId_)
{
}
//==============================================================================
Colour getColour() const
{
return component->findColour (colourId);
}
void setColour (Colour newColour)
{
if (component->findColour (colourId) != newColour)
{
document.getUndoManager().undoCurrentTransactionOnly();
document.perform (new ColourChangeAction (component,
*document.getComponentLayout(),
colourId,
newColour,
false),
"Change colour");
}
}
void resetToDefault()
{
document.getUndoManager().undoCurrentTransactionOnly();
document.perform (new ColourChangeAction (component,
*document.getComponentLayout(),
colourId,
Colours::black,
true),
"Reset colour");
}
private:
const int colourId;
class ColourChangeAction : public ComponentUndoableAction <Component>
{
public:
ColourChangeAction (Component* const comp,
ComponentLayout& l,
const int colourId_,
Colour newColour_,
const bool newColourIsDefault)
: ComponentUndoableAction<Component> (comp, l),
colourId (colourId_),
newColour (newColour_),
isDefault (newColourIsDefault)
{
}
bool perform()
{
showCorrectTab();
wasSpecified = getComponent()->isColourSpecified (colourId);
oldColour = getComponent()->findColour (colourId);
if (isDefault)
getComponent()->removeColour (colourId);
else
getComponent()->setColour (colourId, newColour);
changed();
return true;
}
bool undo()
{
showCorrectTab();
if (wasSpecified)
getComponent()->setColour (colourId, oldColour);
else
getComponent()->removeColour (colourId);
if (TextEditor* const te = dynamic_cast<TextEditor*> (getComponent()))
te->applyFontToAllText (te->getFont());
changed();
return true;
}
int colourId;
Colour newColour, oldColour;
bool isDefault, wasSpecified;
};
};

View File

@@ -0,0 +1,60 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
template <class ComponentType>
class ComponentTextProperty : public TextPropertyComponent,
private ChangeListener
{
public:
ComponentTextProperty (const String& name,
const int maxNumChars_,
const bool isMultiLine_,
ComponentType* const comp,
JucerDocument& doc)
: TextPropertyComponent (name, maxNumChars_, isMultiLine_),
component (comp),
document (doc)
{
document.addChangeListener (this);
}
~ComponentTextProperty()
{
document.removeChangeListener (this);
}
void changeListenerCallback (ChangeBroadcaster*)
{
refresh();
}
protected:
ComponentType* component;
JucerDocument& document;
};

View File

@@ -0,0 +1,63 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class FilePropertyComponent : public PropertyComponent,
private FilenameComponentListener
{
public:
FilePropertyComponent (const String& name,
const bool isDirectory,
const bool allowEditingOfFilename,
const String& fileBrowserWildcard = "*")
: PropertyComponent (name),
filenameComp (name, File(), allowEditingOfFilename,
isDirectory, false, fileBrowserWildcard,
String(), String())
{
addAndMakeVisible (filenameComp);
filenameComp.addListener (this);
}
virtual void setFile (const File& newFile) = 0;
virtual File getFile() const = 0;
void refresh() override
{
filenameComp.setCurrentFile (getFile(), false);
}
private:
void filenameComponentChanged (FilenameComponent*) override
{
if (getFile() != filenameComp.getCurrentFile())
setFile (filenameComp.getCurrentFile());
}
FilenameComponent filenameComp;
};

View File

@@ -0,0 +1,142 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class FontPropertyComponent : public ChoicePropertyComponent
{
public:
FontPropertyComponent (const String& name)
: ChoicePropertyComponent (name)
{
choices.add (getDefaultFont());
choices.add (getDefaultSans());
choices.add (getDefaultSerif());
choices.add (getDefaultMono());
choices.add (String());
static StringArray fontNames;
if (fontNames.size() == 0)
{
Array<Font> fonts;
Font::findFonts (fonts);
for (int i = 0; i < fonts.size(); ++i)
fontNames.add (fonts[i].getTypefaceName());
}
choices.addArray (fontNames);
}
static String getDefaultFont() { return "Default font"; }
static String getDefaultSans() { return "Default sans-serif font"; }
static String getDefaultSerif() { return "Default serif font"; }
static String getDefaultMono() { return "Default monospaced font"; }
//==============================================================================
virtual void setTypefaceName (const String& newFontName) = 0;
virtual String getTypefaceName() const = 0;
//==============================================================================
void setIndex (int newIndex)
{
String type (choices [newIndex]);
if (type.isEmpty())
type = getDefaultFont();
if (getTypefaceName() != type)
setTypefaceName (type);
}
int getIndex() const
{
return choices.indexOf (getTypefaceName());
}
static Font applyNameToFont (const String& typefaceName, const Font& font)
{
auto extraKerning = font.getExtraKerningFactor();
if (typefaceName == getDefaultFont()) return Font (font.getHeight(), font.getStyleFlags()).withExtraKerningFactor (extraKerning);
if (typefaceName == getDefaultSans()) return Font (Font::getDefaultSansSerifFontName(), font.getHeight(), font.getStyleFlags()).withExtraKerningFactor (extraKerning);
if (typefaceName == getDefaultSerif()) return Font (Font::getDefaultSerifFontName(), font.getHeight(), font.getStyleFlags()).withExtraKerningFactor (extraKerning);
if (typefaceName == getDefaultMono()) return Font (Font::getDefaultMonospacedFontName(), font.getHeight(), font.getStyleFlags()).withExtraKerningFactor (extraKerning);
auto f = Font (typefaceName, font.getHeight(), font.getStyleFlags()).withExtraKerningFactor (extraKerning);
if (f.getAvailableStyles().contains (font.getTypefaceStyle()))
f.setTypefaceStyle (font.getTypefaceStyle());
return f;
}
static String getTypefaceNameCode (const String& typefaceName)
{
if (typefaceName == getDefaultFont()) return {};
if (typefaceName == getDefaultSans()) return "juce::Font::getDefaultSansSerifFontName(), ";
if (typefaceName == getDefaultSerif()) return "juce::Font::getDefaultSerifFontName(), ";
if (typefaceName == getDefaultMono()) return "juce::Font::getDefaultMonospacedFontName(), ";
return "\"" + typefaceName + "\", ";
}
static String getFontStyleCode (const Font& font)
{
if (font.isBold() && font.isItalic()) return "juce::Font::bold | juce::Font::italic";
if (font.isBold()) return "juce::Font::bold";
if (font.isItalic()) return "juce::Font::italic";
return "juce::Font::plain";
}
static String getCompleteFontCode (const Font& font, const String& typefaceName)
{
String s;
s << "juce::Font ("
<< getTypefaceNameCode (typefaceName)
<< CodeHelpers::floatLiteral (font.getHeight(), 2)
<< ", ";
if (font.getAvailableStyles().contains(font.getTypefaceStyle()))
s << "juce::Font::plain).withTypefaceStyle ("
<< CodeHelpers::stringLiteral (font.getTypefaceStyle())
<< ")";
else
s << getFontStyleCode (font)
<< ")";
if (font.getExtraKerningFactor() != 0.0f)
s << ".withExtraKerningFactor ("
<< CodeHelpers::floatLiteral (font.getExtraKerningFactor(), 3)
<< ")";
return s;
}
};

View File

@@ -0,0 +1,100 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
class JustificationProperty : public ChoicePropertyComponent
{
public:
JustificationProperty (const String& name, const bool onlyHorizontalOptions)
: ChoicePropertyComponent (name)
{
if (onlyHorizontalOptions)
{
choices.add ("centre");
choices.add ("left");
choices.add ("right");
}
else
{
choices.add ("centred");
choices.add ("centred left");
choices.add ("centred right");
choices.add ("centred top");
choices.add ("centred bottom");
choices.add ("top left");
choices.add ("top right");
choices.add ("bottom left");
choices.add ("bottom right");
}
}
//==============================================================================
virtual void setJustification (Justification newJustification) = 0;
virtual Justification getJustification() const = 0;
//==============================================================================
void setIndex (int newIndex)
{
const int types[] = { Justification::centred,
Justification::centredLeft,
Justification::centredRight,
Justification::centredTop,
Justification::centredBottom,
Justification::topLeft,
Justification::topRight,
Justification::bottomLeft,
Justification::bottomRight };
if (((unsigned int) newIndex) < (unsigned int) numElementsInArray (types)
&& types [newIndex] != getJustification().getFlags())
{
setJustification (Justification (types [newIndex]));
}
}
int getIndex() const
{
const int types[] = { Justification::centred,
Justification::centredLeft,
Justification::centredRight,
Justification::centredTop,
Justification::centredBottom,
Justification::topLeft,
Justification::topRight,
Justification::bottomLeft,
Justification::bottomRight };
const int rawFlags = getJustification().getFlags();
for (int i = numElementsInArray (types); --i >= 0;)
if (types[i] == rawFlags)
return i;
return -1;
}
};

View File

@@ -0,0 +1,463 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../UI/jucer_PaintRoutineEditor.h"
#include "../UI/jucer_ComponentLayoutEditor.h"
//==============================================================================
/**
Base class for a property that edits the x, y, w, or h of a PositionedRectangle.
*/
class PositionPropertyBase : public PropertyComponent,
protected ChangeListener
{
public:
enum ComponentPositionDimension
{
componentX = 0,
componentY = 1,
componentWidth = 2,
componentHeight = 3
};
PositionPropertyBase (Component* comp,
const String& name,
ComponentPositionDimension dimension_,
const bool includeAnchorOptions_,
const bool allowRelativeOptions_,
ComponentLayout* layout_)
: PropertyComponent (name),
layout (layout_),
button ("mode"),
component (comp),
dimension (dimension_),
includeAnchorOptions (includeAnchorOptions_),
allowRelativeOptions (allowRelativeOptions_)
{
addAndMakeVisible (button);
button.setTriggeredOnMouseDown (true);
button.setConnectedEdges (TextButton::ConnectedOnLeft | TextButton::ConnectedOnRight);
button.onClick = [this]
{
SafePointer<PositionPropertyBase> safeThis { this };
showMenu (layout, [safeThis] (bool shouldRefresh)
{
if (safeThis == nullptr)
return;
if (shouldRefresh)
safeThis->refresh(); // (to clear the text editor if it's got focus)
});
};
textEditor.reset (new PositionPropLabel (*this));
addAndMakeVisible (textEditor.get());
}
String getText() const
{
RelativePositionedRectangle rpr (getPosition());
PositionedRectangle& p = rpr.rect;
String s;
switch (dimension)
{
case componentX:
if (p.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
s << valueToString (p.getX() * 100.0) << '%';
else
s << valueToString (p.getX());
break;
case componentY:
if (p.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
s << valueToString (p.getY() * 100.0) << '%';
else
s << valueToString (p.getY());
break;
case componentWidth:
if (p.getWidthMode() == PositionedRectangle::proportionalSize)
s << valueToString (p.getWidth() * 100.0) << '%';
else
s << valueToString (p.getWidth());
break;
case componentHeight:
if (p.getHeightMode() == PositionedRectangle::proportionalSize)
s << valueToString (p.getHeight() * 100.0) << '%';
else
s << valueToString (p.getHeight());
break;
default:
jassertfalse;
break;
};
return s;
}
static String valueToString (const double n)
{
return String (roundToInt (n * 1000.0) / 1000.0);
}
void setText (const String& newText)
{
RelativePositionedRectangle rpr (getPosition());
PositionedRectangle p (rpr.rect);
const double value = newText.getDoubleValue();
switch (dimension)
{
case componentX:
if (p.getPositionModeX() == PositionedRectangle::proportionOfParentSize)
p.setX (value / 100.0);
else
p.setX (value);
break;
case componentY:
if (p.getPositionModeY() == PositionedRectangle::proportionOfParentSize)
p.setY (value / 100.0);
else
p.setY (value);
break;
case componentWidth:
if (p.getWidthMode() == PositionedRectangle::proportionalSize)
p.setWidth (value / 100.0);
else
p.setWidth (value);
break;
case componentHeight:
if (p.getHeightMode() == PositionedRectangle::proportionalSize)
p.setHeight (value / 100.0);
else
p.setHeight (value);
break;
default:
jassertfalse;
break;
};
if (p != rpr.rect)
{
rpr.rect = p;
setPosition (rpr);
}
}
void changeListenerCallback (ChangeBroadcaster*)
{
refresh();
}
void showMenu (ComponentLayout* compLayout, std::function<void (bool)> callback)
{
RelativePositionedRectangle rpr (getPosition());
PositionedRectangle p (rpr.rect);
PositionedRectangle::AnchorPoint xAnchor = p.getAnchorPointX();
PositionedRectangle::AnchorPoint yAnchor = p.getAnchorPointY();
PositionedRectangle::PositionMode xMode = p.getPositionModeX();
PositionedRectangle::PositionMode yMode = p.getPositionModeY();
PositionedRectangle::SizeMode sizeW = p.getWidthMode();
PositionedRectangle::SizeMode sizeH = p.getHeightMode();
String relCompName ("parent");
if (Component* const relComp = compLayout != nullptr ? compLayout->getComponentRelativePosTarget (component, (int) dimension)
: nullptr)
relCompName = compLayout->getComponentMemberVariableName (relComp);
jassert (relCompName.isNotEmpty());
PopupMenu m;
if (dimension == componentX || dimension == componentY)
{
const PositionedRectangle::PositionMode posMode = (dimension == componentX) ? xMode : yMode;
m.addItem (10, ((dimension == componentX) ? "Absolute distance from left of "
: "Absolute distance from top of ") + relCompName,
true, posMode == PositionedRectangle::absoluteFromParentTopLeft);
m.addItem (11, ((dimension == componentX) ? "Absolute distance from right of "
: "Absolute distance from bottom of ") + relCompName,
true, posMode == PositionedRectangle::absoluteFromParentBottomRight);
m.addItem (12, "Absolute distance from centre of " + relCompName,
true, posMode == PositionedRectangle::absoluteFromParentCentre);
m.addItem (13, ((dimension == componentX) ? "Percentage of width of "
: "Percentage of height of ") + relCompName,
true, posMode == PositionedRectangle::proportionOfParentSize);
m.addSeparator();
if (includeAnchorOptions)
{
const PositionedRectangle::AnchorPoint anchor = (dimension == componentX) ? xAnchor : yAnchor;
m.addItem (14, (dimension == componentX) ? "Anchored at left of component"
: "Anchored at top of component",
true, anchor == PositionedRectangle::anchorAtLeftOrTop);
m.addItem (15, "Anchored at centre of component", true, anchor == PositionedRectangle::anchorAtCentre);
m.addItem (16, (dimension == componentX) ? "Anchored at right of component"
: "Anchored at bottom of component",
true, anchor == PositionedRectangle::anchorAtRightOrBottom);
}
}
else
{
const PositionedRectangle::SizeMode sizeMode = (dimension == componentWidth) ? sizeW : sizeH;
m.addItem (20, (dimension == componentWidth) ? "Absolute width"
: "Absolute height",
true, sizeMode == PositionedRectangle::absoluteSize);
m.addItem (21, ((dimension == componentWidth) ? "Percentage of width of "
: "Percentage of height of ") + relCompName,
true, sizeMode == PositionedRectangle::proportionalSize);
m.addItem (22, ((dimension == componentWidth) ? "Subtracted from width of "
: "Subtracted from height of ") + relCompName,
true, sizeMode == PositionedRectangle::parentSizeMinusAbsolute);
}
if (allowRelativeOptions && compLayout != nullptr)
{
m.addSeparator();
m.addSubMenu ("Relative to", compLayout->getRelativeTargetMenu (component, (int) dimension));
}
m.showMenuAsync (PopupMenu::Options().withTargetComponent (&button),
[compLayout, callback, xAnchor, yAnchor, xMode, yMode, sizeW, sizeH, p, rpr,
ref = SafePointer<PositionPropertyBase> { this }] (int menuResult) mutable
{
if (menuResult == 0 || ref == nullptr)
{
callback (false);
return;
}
switch (menuResult)
{
case 10:
if (ref->dimension == componentX)
xMode = PositionedRectangle::absoluteFromParentTopLeft;
else
yMode = PositionedRectangle::absoluteFromParentTopLeft;
break;
case 11:
if (ref->dimension == componentX)
xMode = PositionedRectangle::absoluteFromParentBottomRight;
else
yMode = PositionedRectangle::absoluteFromParentBottomRight;
break;
case 12:
if (ref->dimension == componentX)
xMode = PositionedRectangle::absoluteFromParentCentre;
else
yMode = PositionedRectangle::absoluteFromParentCentre;
break;
case 13:
if (ref->dimension == componentX)
xMode = PositionedRectangle::proportionOfParentSize;
else
yMode = PositionedRectangle::proportionOfParentSize;
break;
case 14:
if (ref->dimension == componentX)
xAnchor = PositionedRectangle::anchorAtLeftOrTop;
else
yAnchor = PositionedRectangle::anchorAtLeftOrTop;
break;
case 15:
if (ref->dimension == componentX)
xAnchor = PositionedRectangle::anchorAtCentre;
else
yAnchor = PositionedRectangle::anchorAtCentre;
break;
case 16:
if (ref->dimension == componentX)
xAnchor = PositionedRectangle::anchorAtRightOrBottom;
else
yAnchor = PositionedRectangle::anchorAtRightOrBottom;
break;
case 20:
if (ref->dimension == componentWidth)
sizeW = PositionedRectangle::absoluteSize;
else
sizeH = PositionedRectangle::absoluteSize;
break;
case 21:
if (ref->dimension == componentWidth)
sizeW = PositionedRectangle::proportionalSize;
else
sizeH = PositionedRectangle::proportionalSize;
break;
case 22:
if (ref->dimension == componentWidth)
sizeW = PositionedRectangle::parentSizeMinusAbsolute;
else
sizeH = PositionedRectangle::parentSizeMinusAbsolute;
break;
default:
if (ref->allowRelativeOptions && compLayout != nullptr)
compLayout->processRelativeTargetMenuResult (ref->component, (int) ref->dimension, menuResult);
break;
}
const auto parentArea = [&]() -> Rectangle<int>
{
if (ref->component->findParentComponentOfClass<ComponentLayoutEditor>() != nullptr)
return { ref->component->getParentWidth(), ref->component->getParentHeight() };
if (auto pre = dynamic_cast<PaintRoutineEditor*> (ref->component->getParentComponent()))
return pre->getComponentArea();
jassertfalse;
return {};
}();
int x, xw, y, yh, w, h;
rpr.getRelativeTargetBounds (parentArea, compLayout, x, xw, y, yh, w, h);
PositionedRectangle xyRect (p);
PositionedRectangle whRect (p);
xyRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
Rectangle<int> (x, y, xw, yh));
whRect.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
Rectangle<int> (x, y, w, h));
p.setModes (xAnchor, xMode, yAnchor, yMode, sizeW, sizeH,
Rectangle<int> (x, y, xw, yh));
p.setX (xyRect.getX());
p.setY (xyRect.getY());
p.setWidth (whRect.getWidth());
p.setHeight (whRect.getHeight());
if (p != rpr.rect)
{
rpr.rect = p;
ref->setPosition (rpr);
}
callback (true);
});
}
void resized()
{
const Rectangle<int> r (getLookAndFeel().getPropertyComponentContentPosition (*this));
button.changeWidthToFitText (r.getHeight());
button.setTopRightPosition (r.getRight(), r.getY());
textEditor->setBounds (r.getX(), r.getY(), button.getX() - r.getX(), r.getHeight());
}
void refresh()
{
textEditor->setText (getText(), dontSendNotification);
}
void textWasEdited()
{
const String newText (textEditor->getText());
if (getText() != newText)
setText (newText);
}
//==============================================================================
virtual void setPosition (const RelativePositionedRectangle& newPos) = 0;
virtual RelativePositionedRectangle getPosition() const = 0;
protected:
class PositionPropLabel : public Label
{
PositionPropertyBase& owner;
public:
PositionPropLabel (PositionPropertyBase& owner_)
: Label (String(), String()),
owner (owner_)
{
setEditable (true, true, false);
lookAndFeelChanged();
}
TextEditor* createEditorComponent() override
{
TextEditor* ed = Label::createEditorComponent();
ed->setInputRestrictions (14, "0123456789.-%");
return ed;
}
void textWasEdited() override
{
owner.textWasEdited();
}
void lookAndFeelChanged() override
{
setColour (backgroundColourId, findColour (widgetBackgroundColourId));
setColour (textColourId, findColour (widgetTextColourId));
}
};
ComponentLayout* layout;
std::unique_ptr<PositionPropLabel> textEditor;
TextButton button;
Component* component;
ComponentPositionDimension dimension;
const bool includeAnchorOptions, allowRelativeOptions;
};

View File

@@ -0,0 +1,430 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "../../Application/jucer_Application.h"
#include "jucer_ComponentLayoutEditor.h"
#include "../UI/jucer_JucerCommandIDs.h"
#include "../jucer_ObjectTypes.h"
#include "../Components/jucer_JucerComponentHandler.h"
//==============================================================================
class SubComponentHolderComp : public Component
{
public:
SubComponentHolderComp (JucerDocument& doc,
SnapGridPainter& g)
: document (doc), grid (g),
dontFillBackground (false)
{
setInterceptsMouseClicks (false, false);
setWantsKeyboardFocus (false);
setFocusContainerType (FocusContainerType::keyboardFocusContainer);
}
void paint (Graphics& g) override
{
if (! dontFillBackground)
{
if (PaintRoutine* const background = document.getPaintRoutine (0))
{
background->fillWithBackground (g, false);
background->drawElements (g, getLocalBounds());
grid.draw (g, background);
}
else
{
grid.draw (g, nullptr);
}
}
}
void resized() override
{
if (! getBounds().isEmpty())
{
int numTimesToTry = 10;
while (--numTimesToTry >= 0)
{
bool anyCompsMoved = false;
for (int i = 0; i < getNumChildComponents(); ++i)
{
Component* comp = getChildComponent (i);
if (ComponentTypeHandler* const type = ComponentTypeHandler::getHandlerFor (*comp))
{
const Rectangle<int> newBounds (type->getComponentPosition (comp)
.getRectangle (getLocalBounds(),
document.getComponentLayout()));
anyCompsMoved = anyCompsMoved || (comp->getBounds() != newBounds);
comp->setBounds (newBounds);
}
}
// repeat this loop until they've all stopped shuffling (might require a few
// loops for all the relative positioned comps to settle down)
if (! anyCompsMoved)
break;
}
}
}
void moved() override
{
((ComponentLayoutEditor*) getParentComponent())->updateOverlayPositions();
}
JucerDocument& document;
SnapGridPainter& grid;
bool dontFillBackground;
};
//==============================================================================
ComponentLayoutEditor::ComponentLayoutEditor (JucerDocument& doc, ComponentLayout& cl)
: document (doc), layout (cl), firstResize (true)
{
setWantsKeyboardFocus (true);
addAndMakeVisible (subCompHolder = new SubComponentHolderComp (document, grid));
refreshAllComponents();
setSize (document.getInitialWidth(),
document.getInitialHeight());
}
ComponentLayoutEditor::~ComponentLayoutEditor()
{
document.removeChangeListener (this);
removeChildComponent (&lassoComp);
deleteAllChildren();
}
//==============================================================================
void ComponentLayoutEditor::visibilityChanged()
{
document.beginTransaction();
if (isVisible())
{
refreshAllComponents();
document.addChangeListener (this);
}
else
{
document.removeChangeListener (this);
}
}
void ComponentLayoutEditor::changeListenerCallback (ChangeBroadcaster*)
{
refreshAllComponents();
}
void ComponentLayoutEditor::paint (Graphics&)
{
}
void ComponentLayoutEditor::resized()
{
if (firstResize && getWidth() > 0 && getHeight() > 0)
{
firstResize = false;
refreshAllComponents();
}
subCompHolder->setBounds (getComponentArea());
updateOverlayPositions();
}
Rectangle<int> ComponentLayoutEditor::getComponentArea() const
{
const int editorEdgeGap = 4;
if (document.isFixedSize())
return Rectangle<int> ((getWidth() - document.getInitialWidth()) / 2,
(getHeight() - document.getInitialHeight()) / 2,
document.getInitialWidth(),
document.getInitialHeight());
return Rectangle<int> (editorEdgeGap, editorEdgeGap,
getWidth() - editorEdgeGap * 2,
getHeight() - editorEdgeGap * 2);
}
Image ComponentLayoutEditor::createComponentLayerSnapshot() const
{
((SubComponentHolderComp*) subCompHolder)->dontFillBackground = true;
Image im = subCompHolder->createComponentSnapshot (Rectangle<int> (0, 0, subCompHolder->getWidth(), subCompHolder->getHeight()));
((SubComponentHolderComp*) subCompHolder)->dontFillBackground = false;
return im;
}
void ComponentLayoutEditor::updateOverlayPositions()
{
for (int i = getNumChildComponents(); --i >= 0;)
if (ComponentOverlayComponent* const overlay = dynamic_cast<ComponentOverlayComponent*> (getChildComponent (i)))
overlay->updateBoundsToMatchTarget();
}
void ComponentLayoutEditor::refreshAllComponents()
{
for (int i = getNumChildComponents(); --i >= 0;)
{
std::unique_ptr<ComponentOverlayComponent> overlay (dynamic_cast<ComponentOverlayComponent*> (getChildComponent (i)));
if (overlay != nullptr && layout.containsComponent (overlay->target))
overlay.release();
}
for (int i = subCompHolder->getNumChildComponents(); --i >= 0;)
{
Component* const comp = subCompHolder->getChildComponent (i);
if (! layout.containsComponent (comp))
subCompHolder->removeChildComponent (comp);
}
Component* lastComp = nullptr;
Component* lastOverlay = nullptr;
for (int i = layout.getNumComponents(); --i >= 0;)
{
auto c = layout.getComponent (i);
jassert (c != nullptr);
auto overlay = getOverlayCompFor (c);
bool isNewOverlay = false;
if (overlay == nullptr)
{
auto handler = ComponentTypeHandler::getHandlerFor (*c);
jassert (handler != nullptr);
overlay = handler->createOverlayComponent (c, layout);
addAndMakeVisible (overlay);
isNewOverlay = true;
}
if (lastOverlay != nullptr)
overlay->toBehind (lastOverlay);
else
overlay->toFront (false);
lastOverlay = overlay;
subCompHolder->addAndMakeVisible (c);
if (lastComp != nullptr)
c->toBehind (lastComp);
else
c->toFront (false);
lastComp = c;
c->setWantsKeyboardFocus (false);
c->setFocusContainerType (FocusContainerType::keyboardFocusContainer);
if (isNewOverlay)
overlay->updateBoundsToMatchTarget();
}
if (grid.updateFromDesign (document))
subCompHolder->repaint();
subCompHolder->setBounds (getComponentArea());
subCompHolder->resized();
}
void ComponentLayoutEditor::mouseDown (const MouseEvent& e)
{
if (e.mods.isPopupMenu())
{
auto commandManager = &ProjucerApplication::getCommandManager();
PopupMenu m;
m.addCommandItem (commandManager, JucerCommandIDs::editCompLayout);
m.addCommandItem (commandManager, JucerCommandIDs::editCompGraphics);
m.addSeparator();
for (int i = 0; i < ObjectTypes::numComponentTypes; ++i)
m.addCommandItem (commandManager, JucerCommandIDs::newComponentBase + i);
m.showMenuAsync (PopupMenu::Options());
}
else
{
addChildComponent (lassoComp);
lassoComp.beginLasso (e, this);
}
}
void ComponentLayoutEditor::mouseDrag (const MouseEvent& e)
{
lassoComp.toFront (false);
lassoComp.dragLasso (e);
}
void ComponentLayoutEditor::mouseUp (const MouseEvent& e)
{
lassoComp.endLasso();
removeChildComponent (&lassoComp);
if (! (e.mouseWasDraggedSinceMouseDown() || e.mods.isAnyModifierKeyDown()))
layout.getSelectedSet().deselectAll();
}
static void moveOrStretch (ComponentLayout& layout, int x, int y, bool snap, bool stretch)
{
if (stretch)
layout.stretchSelectedComps (x, y, snap);
else
layout.moveSelectedComps (x, y, snap);
}
bool ComponentLayoutEditor::keyPressed (const KeyPress& key)
{
const bool snap = key.getModifiers().isAltDown();
const bool stretch = key.getModifiers().isShiftDown();
const int amount = snap ? document.getSnappingGridSize() + 1
: 1;
if (key.isKeyCode (KeyPress::rightKey))
{
moveOrStretch (layout, amount, 0, snap, stretch);
}
else if (key.isKeyCode (KeyPress::downKey))
{
moveOrStretch (layout, 0,amount, snap, stretch);
}
else if (key.isKeyCode (KeyPress::leftKey))
{
moveOrStretch (layout, -amount, 0, snap, stretch);
}
else if (key.isKeyCode (KeyPress::upKey))
{
moveOrStretch (layout, 0, -amount, snap, stretch);
}
else
{
return false;
}
return true;
}
bool ComponentLayoutEditor::isInterestedInFileDrag (const StringArray& filenames)
{
const File f (filenames [0]);
return f.hasFileExtension (cppFileExtensions);
}
void ComponentLayoutEditor::filesDropped (const StringArray& filenames, int x, int y)
{
const File f (filenames [0]);
if (JucerDocument::isValidJucerCppFile (f))
{
JucerComponentHandler jucerDocHandler;
layout.getDocument()->beginTransaction();
if (TestComponent* newOne = dynamic_cast<TestComponent*> (layout.addNewComponent (&jucerDocHandler,
x - subCompHolder->getX(),
y - subCompHolder->getY())))
{
JucerComponentHandler::setJucerComponentFile (*layout.getDocument(), newOne,
f.getRelativePathFrom (document.getCppFile().getParentDirectory()));
layout.getSelectedSet().selectOnly (newOne);
}
layout.getDocument()->beginTransaction();
}
}
bool ComponentLayoutEditor::isInterestedInDragSource (const SourceDetails& dragSourceDetails)
{
if (dragSourceDetails.description != projectItemDragType)
return false;
OwnedArray<Project::Item> selectedNodes;
ProjectContentComponent::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
return selectedNodes.size() > 0;
}
void ComponentLayoutEditor::itemDropped (const SourceDetails& dragSourceDetails)
{
OwnedArray<Project::Item> selectedNodes;
ProjectContentComponent::getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
StringArray filenames;
for (int i = 0; i < selectedNodes.size(); ++i)
if (selectedNodes.getUnchecked(i)->getFile().hasFileExtension (cppFileExtensions))
filenames.add (selectedNodes.getUnchecked(i)->getFile().getFullPathName());
filesDropped (filenames, dragSourceDetails.localPosition.x, dragSourceDetails.localPosition.y);
}
ComponentOverlayComponent* ComponentLayoutEditor::getOverlayCompFor (Component* compToFind) const
{
for (int i = getNumChildComponents(); --i >= 0;)
{
if (ComponentOverlayComponent* const overlay = dynamic_cast<ComponentOverlayComponent*> (getChildComponent (i)))
if (overlay->target == compToFind)
return overlay;
}
return nullptr;
}
void ComponentLayoutEditor::findLassoItemsInArea (Array <Component*>& results, const Rectangle<int>& area)
{
const Rectangle<int> lasso (area - subCompHolder->getPosition());
for (int i = 0; i < subCompHolder->getNumChildComponents(); ++i)
{
Component* c = subCompHolder->getChildComponent (i);
if (c->getBounds().intersects (lasso))
results.add (c);
}
}
SelectedItemSet <Component*>& ComponentLayoutEditor::getLassoSelection()
{
return layout.getSelectedSet();
}

View File

@@ -0,0 +1,86 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_ComponentOverlayComponent.h"
#include "../jucer_JucerDocument.h"
#include "jucer_SnapGridPainter.h"
//==============================================================================
class ComponentLayoutEditor : public Component,
public FileDragAndDropTarget,
public DragAndDropTarget,
public LassoSource<Component*>,
private ChangeListener
{
public:
//==============================================================================
ComponentLayoutEditor (JucerDocument&, ComponentLayout&);
~ComponentLayoutEditor() override;
//==============================================================================
void paint (Graphics&) override;
void resized() override;
void visibilityChanged() override;
void mouseDown (const MouseEvent&) override;
void mouseDrag (const MouseEvent&) override;
void mouseUp (const MouseEvent&) override;
bool keyPressed (const KeyPress&) override;
bool isInterestedInFileDrag (const StringArray& files) override;
void filesDropped (const StringArray& filenames, int x, int y) override;
bool isInterestedInDragSource (const SourceDetails& dragSourceDetails) override;
void itemDropped (const SourceDetails& dragSourceDetails) override;
ComponentLayout& getLayout() const noexcept { return layout; }
void findLassoItemsInArea (Array <Component*>& results, const Rectangle<int>& area) override;
SelectedItemSet<Component*>& getLassoSelection() override;
//==============================================================================
void refreshAllComponents();
void updateOverlayPositions();
ComponentOverlayComponent* getOverlayCompFor (Component*) const;
Rectangle<int> getComponentArea() const;
Image createComponentLayerSnapshot() const;
private:
void changeListenerCallback (ChangeBroadcaster*) override;
JucerDocument& document;
ComponentLayout& layout;
Component* subCompHolder;
LassoComponent<Component*> lassoComp;
SnapGridPainter grid;
bool firstResize;
};

View File

@@ -0,0 +1,118 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_ComponentLayoutEditor.h"
#include "jucer_EditingPanelBase.h"
//==============================================================================
class ComponentLayoutPanel : public EditingPanelBase
{
public:
//==============================================================================
ComponentLayoutPanel (JucerDocument& doc, ComponentLayout& l)
: EditingPanelBase (doc,
new LayoutPropsPanel (doc, l),
new ComponentLayoutEditor (doc, l)),
layout (l)
{
}
~ComponentLayoutPanel()
{
deleteAllChildren();
}
void updatePropertiesList()
{
((LayoutPropsPanel*) propsPanel)->updateList();
}
Rectangle<int> getComponentArea() const
{
return ((ComponentLayoutEditor*) editor)->getComponentArea();
}
Image createComponentSnapshot() const
{
return ((ComponentLayoutEditor*) editor)->createComponentLayerSnapshot();
}
ComponentLayout& layout;
private:
class LayoutPropsPanel : public Component,
private ChangeListener
{
public:
LayoutPropsPanel (JucerDocument& doc, ComponentLayout& l)
: document (doc), layout (l)
{
layout.getSelectedSet().addChangeListener (this);
addAndMakeVisible (propsPanel);
}
~LayoutPropsPanel() override
{
layout.getSelectedSet().removeChangeListener (this);
clear();
}
void resized() override
{
propsPanel.setBounds (4, 4, getWidth() - 8, getHeight() - 8);
}
void clear()
{
propsPanel.clear();
}
void updateList()
{
clear();
auto numSelected = layout.getSelectedSet().getNumSelected();
if (numSelected > 0) // xxx need to cope with multiple
{
if (auto* comp = layout.getSelectedSet().getSelectedItem (0))
if (auto* type = ComponentTypeHandler::getHandlerFor (*comp))
type->addPropertiesToPropertyPanel (comp, document, propsPanel, numSelected > 1);
}
}
private:
void changeListenerCallback (ChangeBroadcaster*) override
{
updateList();
}
JucerDocument& document;
ComponentLayout& layout;
PropertyPanel propsPanel;
};
};

View File

@@ -0,0 +1,278 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "jucer_ComponentLayoutEditor.h"
#include "../jucer_UtilityFunctions.h"
//==============================================================================
ComponentOverlayComponent::ComponentOverlayComponent (Component* const target_,
ComponentLayout& layout_)
: target (target_),
borderThickness (4),
layout (layout_),
selected (false),
dragging (false),
originalAspectRatio (1.0)
{
setMinimumOnscreenAmounts (0, 0, 0, 0);
setSizeLimits (borderThickness * 2 + 2, borderThickness * 2 + 2, 8192, 8192);
border.reset (new ResizableBorderComponent (this, this));
addChildComponent (border.get());
border->setBorderThickness (BorderSize<int> (borderThickness));
target->addComponentListener (this);
changeListenerCallback (nullptr);
layout.getSelectedSet().addChangeListener (this);
setRepaintsOnMouseActivity (true);
border->setRepaintsOnMouseActivity (true);
}
ComponentOverlayComponent::~ComponentOverlayComponent()
{
layout.getSelectedSet().removeChangeListener (this);
if (target != nullptr)
target->removeComponentListener (this);
}
void ComponentOverlayComponent::changeListenerCallback (ChangeBroadcaster*)
{
const bool nowSelected = layout.getSelectedSet().isSelected (target);
if (selected != nowSelected)
{
selected = nowSelected;
border->setVisible (nowSelected);
repaint();
}
}
void ComponentOverlayComponent::paint (Graphics& g)
{
jassert (target != nullptr);
border->setColour (backgroundColourId, Colours::transparentBlack);
if (selected)
{
auto selectedItems = layout.getSelectedSet();
auto baseColour = findColour (defaultHighlightColourId);
const BorderSize<int> borderSize (border->getBorderThickness());
drawResizableBorder (g, getWidth(), getHeight(), borderSize,
(isMouseOverOrDragging() || border->isMouseOverOrDragging()),
baseColour.withAlpha (selectedItems.getSelectedItem (0) == target ? 1.0f : 0.3f));
}
else if (isMouseOverOrDragging())
{
drawMouseOverCorners (g, getWidth(), getHeight());
}
}
void ComponentOverlayComponent::resized()
{
jassert (target != nullptr);
border->setBounds (getLocalBounds());
}
void ComponentOverlayComponent::mouseDown (const MouseEvent& e)
{
dragging = false;
mouseDownSelectStatus = layout.getSelectedSet().addToSelectionOnMouseDown (target, e.mods);
if (e.mods.isPopupMenu())
{
showPopupMenu();
return; // this may be deleted now..
}
}
void ComponentOverlayComponent::mouseDrag (const MouseEvent& e)
{
if (! e.mods.isPopupMenu())
{
if (selected && ! dragging)
{
dragging = e.mouseWasDraggedSinceMouseDown();
if (dragging)
layout.startDragging();
}
if (dragging)
{
layout.dragSelectedComps (e.getDistanceFromDragStartX(),
e.getDistanceFromDragStartY());
}
}
}
void ComponentOverlayComponent::mouseUp (const MouseEvent& e)
{
if (dragging)
layout.endDragging();
layout.getSelectedSet().addToSelectionOnMouseUp (target, e.mods, dragging, mouseDownSelectStatus);
}
void ComponentOverlayComponent::componentMovedOrResized (Component&, bool /*wasMoved*/, bool /*wasResized*/)
{
updateBoundsToMatchTarget();
}
void ComponentOverlayComponent::updateBoundsToMatchTarget()
{
if (Component* const parent = target->getParentComponent())
{
const int dx = parent->getX();
const int dy = parent->getY();
setBounds (dx + target->getX() - borderThickness,
dy + target->getY() - borderThickness,
target->getWidth() + borderThickness * 2,
target->getHeight() + borderThickness * 2);
}
if (border->isMouseButtonDown())
layout.changed();
}
void ComponentOverlayComponent::resizeStart()
{
if (getHeight() > 0)
originalAspectRatio = getWidth() / (double) getHeight();
else
originalAspectRatio = 1.0;
layout.getDocument()->beginTransaction ("Resize components");
}
void ComponentOverlayComponent::resizeEnd()
{
layout.getDocument()->beginTransaction();
}
void ComponentOverlayComponent::checkBounds (Rectangle<int>& b,
const Rectangle<int>& previousBounds,
const Rectangle<int>& limits,
const bool isStretchingTop,
const bool isStretchingLeft,
const bool isStretchingBottom,
const bool isStretchingRight)
{
if (ModifierKeys::currentModifiers.isShiftDown())
setFixedAspectRatio (originalAspectRatio);
else
setFixedAspectRatio (0.0);
ComponentBoundsConstrainer::checkBounds (b, previousBounds, limits, isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight);
if (layout.getDocument()->isSnapActive (true))
{
if (Component* const parent = target->getParentComponent())
{
const int dx = parent->getX();
const int dy = parent->getY();
int x = b.getX();
int y = b.getY();
int w = b.getWidth();
int h = b.getHeight();
x += borderThickness - dx;
y += borderThickness - dy;
w -= borderThickness * 2;
h -= borderThickness * 2;
int right = x + w;
int bottom = y + h;
if (isStretchingRight)
right = layout.getDocument()->snapPosition (right);
if (isStretchingBottom)
bottom = layout.getDocument()->snapPosition (bottom);
if (isStretchingLeft)
x = layout.getDocument()->snapPosition (x);
if (isStretchingTop)
y = layout.getDocument()->snapPosition (y);
w = (right - x) + borderThickness * 2;
h = (bottom - y) + borderThickness * 2;
x -= borderThickness - dx;
y -= borderThickness - dy;
b = Rectangle<int> (x, y, w, h);
}
}
}
void ComponentOverlayComponent::applyBoundsToComponent (Component& component, Rectangle<int> b)
{
if (component.getBounds() != b)
{
layout.getDocument()->getUndoManager().undoCurrentTransactionOnly();
auto dX = b.getX() - component.getX();
auto dY = b.getY() - component.getY();
auto dW = b.getWidth() - component.getWidth();
auto dH = b.getHeight() - component.getHeight();
component.setBounds (b);
if (auto* parent = target->getParentComponent())
target->setBounds (b.getX() + borderThickness - parent->getX(),
b.getY() + borderThickness - parent->getY(),
b.getWidth() - borderThickness * 2,
b.getHeight() - borderThickness * 2);
layout.updateStoredComponentPosition (target, true);
if (layout.getSelectedSet().getNumSelected() > 1)
{
for (auto s : layout.getSelectedSet())
{
if (s != target)
{
s->setBounds (s->getX() + dX, s->getY() + dY, s->getWidth() + dW, s->getHeight() + dH);
layout.updateStoredComponentPosition (s, true);
}
}
}
}
}
void ComponentOverlayComponent::showPopupMenu()
{
ComponentTypeHandler::getHandlerFor (*target)->showPopupMenu (target, layout);
}

View File

@@ -0,0 +1,84 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_JucerDocument.h"
//==============================================================================
class ComponentOverlayComponent : public Component,
private ComponentBoundsConstrainer,
private ComponentListener,
private ChangeListener
{
public:
//==============================================================================
ComponentOverlayComponent (Component* const targetComponent,
ComponentLayout& layout);
~ComponentOverlayComponent() override;
//==============================================================================
virtual void showPopupMenu();
//==============================================================================
void paint (Graphics&) override;
void resized() override;
void mouseDown (const MouseEvent&) override;
void mouseDrag (const MouseEvent&) override;
void mouseUp (const MouseEvent&) override;
void updateBoundsToMatchTarget();
//==============================================================================
Component::SafePointer<Component> target;
const int borderThickness;
private:
void resizeStart() override;
void resizeEnd() override;
void checkBounds (Rectangle<int>& bounds,
const Rectangle<int>& previousBounds,
const Rectangle<int>& limits,
bool isStretchingTop,
bool isStretchingLeft,
bool isStretchingBottom,
bool isStretchingRight) override;
void applyBoundsToComponent (Component&, Rectangle<int>) override;
void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
void changeListenerCallback (ChangeBroadcaster*) override;
std::unique_ptr<ResizableBorderComponent> border;
ComponentLayout& layout;
bool selected, dragging, mouseDownSelectStatus;
double originalAspectRatio;
};

View File

@@ -0,0 +1,243 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "jucer_EditingPanelBase.h"
#include "jucer_JucerDocumentEditor.h"
//==============================================================================
class EditingPanelBase::MagnifierComponent : public Component
{
public:
explicit MagnifierComponent (Component* c) : content (c)
{
addAndMakeVisible (content.get());
childBoundsChanged (content.get());
}
void childBoundsChanged (Component* child)
{
auto childArea = getLocalArea (child, child->getLocalBounds());
setSize (childArea.getWidth(), childArea.getHeight());
}
double getScaleFactor() const { return scaleFactor; }
void setScaleFactor (double newScale)
{
scaleFactor = newScale;
content->setTransform (AffineTransform::scale ((float) scaleFactor));
}
private:
double scaleFactor = 1.0;
std::unique_ptr<Component> content;
};
//==============================================================================
class ZoomingViewport : public Viewport
{
public:
explicit ZoomingViewport (EditingPanelBase* p) : panel (p)
{
}
void mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel)
{
if (e.mods.isCtrlDown() || e.mods.isAltDown() || e.mods.isCommandDown())
mouseMagnify (e, 1.0f / (1.0f - wheel.deltaY));
else
Viewport::mouseWheelMove (e, wheel);
}
void mouseMagnify (const MouseEvent& e, float factor)
{
panel->setZoom (panel->getZoom() * factor, e.x, e.y);
}
void dragKeyHeldDown (const bool isKeyDown)
{
if (isSpaceDown != isKeyDown)
{
isSpaceDown = isKeyDown;
if (isSpaceDown)
{
auto dc = new DraggerOverlayComp();
addAndMakeVisible (dc);
dc->setBounds (getLocalBounds());
}
else
{
for (int i = getNumChildComponents(); --i >= 0;)
std::unique_ptr<DraggerOverlayComp> deleter (dynamic_cast<DraggerOverlayComp*> (getChildComponent (i)));
}
}
}
private:
EditingPanelBase* const panel;
bool isSpaceDown = false;
//==============================================================================
class DraggerOverlayComp : public Component
{
public:
DraggerOverlayComp()
{
setMouseCursor (MouseCursor::DraggingHandCursor);
setAlwaysOnTop (true);
}
void mouseDown (const MouseEvent&)
{
if (Viewport* viewport = findParentComponentOfClass<Viewport>())
{
startX = viewport->getViewPositionX();
startY = viewport->getViewPositionY();
}
}
void mouseDrag (const MouseEvent& e)
{
if (Viewport* viewport = findParentComponentOfClass<Viewport>())
viewport->setViewPosition (jlimit (0, jmax (0, viewport->getViewedComponent()->getWidth() - viewport->getViewWidth()),
startX - e.getDistanceFromDragStartX()),
jlimit (0, jmax (0, viewport->getViewedComponent()->getHeight() - viewport->getViewHeight()),
startY - e.getDistanceFromDragStartY()));
}
private:
int startX, startY;
};
};
//==============================================================================
EditingPanelBase::EditingPanelBase (JucerDocument& doc, Component* props, Component* editorComp)
: document (doc),
editor (editorComp),
propsPanel (props)
{
addAndMakeVisible (viewport = new ZoomingViewport (this));
addAndMakeVisible (propsPanel);
viewport->setViewedComponent (magnifier = new MagnifierComponent (editor));
}
EditingPanelBase::~EditingPanelBase()
{
deleteAllChildren();
}
void EditingPanelBase::resized()
{
const int contentW = jmax (1, getWidth() - 260);
propsPanel->setBounds (contentW + 4, 4, jmax (100, getWidth() - contentW - 8), getHeight() - 8);
viewport->setBounds (4, 4, contentW - 8, getHeight() - 8);
if (document.isFixedSize())
editor->setSize (jmax (document.getInitialWidth(),
roundToInt ((viewport->getWidth() - viewport->getScrollBarThickness()) / getZoom())),
jmax (document.getInitialHeight(),
roundToInt ((viewport->getHeight() - viewport->getScrollBarThickness()) / getZoom())));
else
editor->setSize (viewport->getWidth(),
viewport->getHeight());
}
void EditingPanelBase::paint (Graphics& g)
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
void EditingPanelBase::visibilityChanged()
{
if (isVisible())
{
updatePropertiesList();
if (Component* p = getParentComponent())
{
resized();
if (JucerDocumentEditor* const cdh = dynamic_cast<JucerDocumentEditor*> (p->getParentComponent()))
cdh->setViewportToLastPos (viewport, *this);
resized();
}
}
else
{
if (Component* p = getParentComponent())
if (JucerDocumentEditor* const cdh = dynamic_cast<JucerDocumentEditor*> (p->getParentComponent()))
cdh->storeLastViewportPos (viewport, *this);
}
editor->setVisible (isVisible());
}
double EditingPanelBase::getZoom() const
{
return magnifier->getScaleFactor();
}
void EditingPanelBase::setZoom (double newScale)
{
setZoom (jlimit (1.0 / 8.0, 16.0, newScale),
viewport->getWidth() / 2,
viewport->getHeight() / 2);
}
void EditingPanelBase::setZoom (double newScale, int anchorX, int anchorY)
{
Point<int> anchor (editor->getLocalPoint (viewport, Point<int> (anchorX, anchorY)));
magnifier->setScaleFactor (newScale);
resized();
jassert (viewport != nullptr);
anchor = viewport->getLocalPoint (editor, anchor);
viewport->setViewPosition (jlimit (0, jmax (0, viewport->getViewedComponent()->getWidth() - viewport->getViewWidth()),
viewport->getViewPositionX() + anchor.getX() - anchorX),
jlimit (0, jmax (0, viewport->getViewedComponent()->getHeight() - viewport->getViewHeight()),
viewport->getViewPositionY() + anchor.getY() - anchorY));
}
void EditingPanelBase::xyToTargetXY (int& x, int& y) const
{
Point<int> pos (editor->getLocalPoint (this, Point<int> (x, y)));
x = pos.getX();
y = pos.getY();
}
void EditingPanelBase::dragKeyHeldDown (bool isKeyDown)
{
((ZoomingViewport*) viewport)->dragKeyHeldDown (isKeyDown);
}

View File

@@ -0,0 +1,75 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_JucerDocument.h"
#include "jucer_ComponentLayoutEditor.h"
class LayoutPropsPanel;
//==============================================================================
/**
Base class for the layout and graphics panels - this takes care of arranging
the properties panel and managing the viewport for the content.
*/
class EditingPanelBase : public Component
{
public:
//==============================================================================
EditingPanelBase (JucerDocument& document,
Component* propsPanel,
Component* editorComp);
~EditingPanelBase() override;
//==============================================================================
void resized() override;
void paint (Graphics& g) override;
void visibilityChanged() override;
virtual void updatePropertiesList() = 0;
virtual Rectangle<int> getComponentArea() const = 0;
double getZoom() const;
void setZoom (double newScale);
void setZoom (double newScale, int anchorX, int anchorY);
// convert a pos relative to this component into a pos on the editor
void xyToTargetXY (int& x, int& y) const;
void dragKeyHeldDown (bool isKeyDown);
class MagnifierComponent;
protected:
JucerDocument& document;
Viewport* viewport;
MagnifierComponent* magnifier;
Component* editor;
Component* propsPanel;
};

View File

@@ -0,0 +1,71 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
/**
A namespace to hold all the possible command IDs.
*/
namespace JucerCommandIDs
{
enum
{
test = 0xf20009,
toFront = 0xf2000a,
toBack = 0xf2000b,
group = 0xf20017,
ungroup = 0xf20018,
showGrid = 0xf2000e,
enableSnapToGrid = 0xf2000f,
editCompLayout = 0xf20010,
editCompGraphics = 0xf20011,
bringBackLostItems = 0xf20012,
zoomIn = 0xf20013,
zoomOut = 0xf20014,
zoomNormal = 0xf20015,
spaceBarDrag = 0xf20016,
compOverlay0 = 0xf20020,
compOverlay33 = 0xf20021,
compOverlay66 = 0xf20022,
compOverlay100 = 0xf20023,
newDocumentBase = 0xf32001,
newComponentBase = 0xf30001,
newElementBase = 0xf31001,
alignTop = 0xf33000,
alignRight = 0xf33001,
alignBottom = 0xf33002,
alignLeft = 0xf33003,
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_JucerDocument.h"
#include "jucer_ComponentLayoutEditor.h"
#include "jucer_PaintRoutineEditor.h"
#include "jucer_ComponentLayoutPanel.h"
//==============================================================================
class JucerDocumentEditor : public Component,
public ApplicationCommandTarget,
private ChangeListener
{
public:
//==============================================================================
JucerDocumentEditor (JucerDocument* const document);
~JucerDocumentEditor() override;
JucerDocument* getDocument() const noexcept { return document.get(); }
void refreshPropertiesPanel() const;
void updateTabs();
void showLayout();
void showGraphics (PaintRoutine* routine);
void setViewportToLastPos (Viewport* vp, EditingPanelBase& editor);
void storeLastViewportPos (Viewport* vp, EditingPanelBase& editor);
Image createComponentLayerSnapshot() const;
//==============================================================================
void paint (Graphics& g) override;
void resized() override;
bool keyPressed (const KeyPress&) override;
//==============================================================================
ApplicationCommandTarget* getNextCommandTarget() override;
void getAllCommands (Array<CommandID>&) override;
void getCommandInfo (CommandID, ApplicationCommandInfo&) override;
bool perform (const InvocationInfo&) override;
static JucerDocumentEditor* getActiveDocumentHolder();
private:
void changeListenerCallback (ChangeBroadcaster*) override;
std::unique_ptr<JucerDocument> document;
ComponentLayoutPanel* compLayoutPanel = nullptr;
struct JucerDocumentTabs : public TabbedComponent
{
JucerDocumentTabs (JucerDocument* d) : TabbedComponent (TabbedButtonBar::TabsAtTop), document (d) {}
void currentTabChanged (int, const String&) override { document->refreshCustomCodeFromDocument(); }
JucerDocument* document;
};
JucerDocumentTabs tabbedComponent;
int lastViewportX = 0, lastViewportY = 0;
double currentZoomLevel = 1.0;
void saveLastSelectedTab() const;
void restoreLastSelectedTab();
bool isSomethingSelected() const;
bool areMultipleThingsSelected() const;
// only non-zero if a layout tab is selected
ComponentLayout* getCurrentLayout() const;
// only non-zero if a graphics tab is selected
PaintRoutine* getCurrentPaintRoutine() const;
void setZoom (double scale);
double getZoom() const;
void addElement (int index);
void addComponent (int index);
};

View File

@@ -0,0 +1,281 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "../../Application/jucer_Application.h"
#include "../UI/jucer_JucerCommandIDs.h"
#include "jucer_PaintRoutineEditor.h"
#include "../jucer_ObjectTypes.h"
#include "jucer_JucerDocumentEditor.h"
//==============================================================================
PaintRoutineEditor::PaintRoutineEditor (PaintRoutine& pr, JucerDocument& doc,
JucerDocumentEditor* docHolder)
: graphics (pr),
document (doc),
documentHolder (docHolder),
componentOverlay (nullptr),
componentOverlayOpacity (0.0f)
{
refreshAllElements();
setSize (document.getInitialWidth(),
document.getInitialHeight());
}
PaintRoutineEditor::~PaintRoutineEditor()
{
document.removeChangeListener (this);
removeAllElementComps();
removeChildComponent (&lassoComp);
deleteAllChildren();
}
void PaintRoutineEditor::removeAllElementComps()
{
for (int i = getNumChildComponents(); --i >= 0;)
if (PaintElement* const e = dynamic_cast<PaintElement*> (getChildComponent (i)))
removeChildComponent (e);
}
Rectangle<int> PaintRoutineEditor::getComponentArea() const
{
if (document.isFixedSize())
return Rectangle<int> ((getWidth() - document.getInitialWidth()) / 2,
(getHeight() - document.getInitialHeight()) / 2,
document.getInitialWidth(),
document.getInitialHeight());
return getLocalBounds().reduced (4);
}
//==============================================================================
void PaintRoutineEditor::paint (Graphics& g)
{
const Rectangle<int> clip (getComponentArea());
g.reduceClipRegion (clip);
g.setOrigin (clip.getPosition());
graphics.fillWithBackground (g, true);
grid.draw (g, &graphics);
}
void PaintRoutineEditor::paintOverChildren (Graphics& g)
{
if (componentOverlay.isNull() && document.getComponentOverlayOpacity() > 0.0f)
updateComponentOverlay();
if (componentOverlay.isValid())
{
const Rectangle<int> clip (getComponentArea());
g.drawImageAt (componentOverlay, clip.getX(), clip.getY());
}
}
void PaintRoutineEditor::resized()
{
if (getWidth() > 0 && getHeight() > 0)
{
componentOverlay = Image();
refreshAllElements();
}
}
void PaintRoutineEditor::updateChildBounds()
{
const Rectangle<int> clip (getComponentArea());
for (int i = 0; i < getNumChildComponents(); ++i)
if (PaintElement* const e = dynamic_cast<PaintElement*> (getChildComponent (i)))
e->updateBounds (clip);
}
void PaintRoutineEditor::updateComponentOverlay()
{
if (componentOverlay.isValid())
repaint();
componentOverlay = Image();
componentOverlayOpacity = document.getComponentOverlayOpacity();
if (componentOverlayOpacity > 0.0f)
{
if (documentHolder != nullptr)
componentOverlay = documentHolder->createComponentLayerSnapshot();
if (componentOverlay.isValid())
{
componentOverlay.multiplyAllAlphas (componentOverlayOpacity);
repaint();
}
}
}
void PaintRoutineEditor::visibilityChanged()
{
document.beginTransaction();
if (isVisible())
{
refreshAllElements();
document.addChangeListener (this);
}
else
{
document.removeChangeListener (this);
componentOverlay = Image();
}
}
void PaintRoutineEditor::refreshAllElements()
{
for (int i = getNumChildComponents(); --i >= 0;)
if (auto* e = dynamic_cast<PaintElement*> (getChildComponent (i)))
if (! graphics.containsElement (e))
removeChildComponent (e);
Component* last = nullptr;
for (int i = graphics.getNumElements(); --i >= 0;)
{
auto* e = graphics.getElement (i);
addAndMakeVisible (e);
if (last != nullptr)
e->toBehind (last);
else
e->toFront (false);
last = e;
}
updateChildBounds();
if (grid.updateFromDesign (document))
repaint();
if (currentBackgroundColour != graphics.getBackgroundColour())
{
currentBackgroundColour = graphics.getBackgroundColour();
repaint();
}
if (componentOverlayOpacity != document.getComponentOverlayOpacity())
{
componentOverlay = Image();
componentOverlayOpacity = document.getComponentOverlayOpacity();
repaint();
}
}
void PaintRoutineEditor::changeListenerCallback (ChangeBroadcaster*)
{
refreshAllElements();
}
void PaintRoutineEditor::mouseDown (const MouseEvent& e)
{
if (e.mods.isPopupMenu())
{
ApplicationCommandManager* commandManager = &ProjucerApplication::getCommandManager();
PopupMenu m;
m.addCommandItem (commandManager, JucerCommandIDs::editCompLayout);
m.addCommandItem (commandManager, JucerCommandIDs::editCompGraphics);
m.addSeparator();
for (int i = 0; i < ObjectTypes::numElementTypes; ++i)
m.addCommandItem (commandManager, JucerCommandIDs::newElementBase + i);
m.showMenuAsync (PopupMenu::Options());
}
else
{
addChildComponent (lassoComp);
lassoComp.beginLasso (e, this);
}
}
void PaintRoutineEditor::mouseDrag (const MouseEvent& e)
{
lassoComp.toFront (false);
lassoComp.dragLasso (e);
}
void PaintRoutineEditor::mouseUp (const MouseEvent& e)
{
lassoComp.endLasso();
if (! (e.mouseWasDraggedSinceMouseDown() || e.mods.isAnyModifierKeyDown()))
{
graphics.getSelectedElements().deselectAll();
graphics.getSelectedPoints().deselectAll();
}
}
void PaintRoutineEditor::findLassoItemsInArea (Array <PaintElement*>& results, const Rectangle<int>& lasso)
{
for (int i = 0; i < getNumChildComponents(); ++i)
if (PaintElement* const e = dynamic_cast<PaintElement*> (getChildComponent (i)))
if (e->getBounds().expanded (-e->borderThickness).intersects (lasso))
results.add (e);
}
SelectedItemSet <PaintElement*>& PaintRoutineEditor::getLassoSelection()
{
return graphics.getSelectedElements();
}
bool PaintRoutineEditor::isInterestedInFileDrag (const StringArray& files)
{
return File::createFileWithoutCheckingPath (files[0])
.hasFileExtension ("jpg;jpeg;png;gif;svg");
}
void PaintRoutineEditor::filesDropped (const StringArray& filenames, int x, int y)
{
const File f (filenames [0]);
if (f.existsAsFile())
{
std::unique_ptr<Drawable> d (Drawable::createFromImageFile (f));
if (d != nullptr)
{
d.reset();
document.beginTransaction();
graphics.dropImageAt (f,
jlimit (10, getWidth() - 10, x),
jlimit (10, getHeight() - 10, y));
document.beginTransaction();
}
}
}

View File

@@ -0,0 +1,83 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_JucerDocument.h"
#include "../jucer_PaintRoutine.h"
#include "jucer_SnapGridPainter.h"
class JucerDocumentEditor;
//==============================================================================
class PaintRoutineEditor : public Component,
public LassoSource <PaintElement*>,
public FileDragAndDropTarget,
private ChangeListener
{
public:
//==============================================================================
PaintRoutineEditor (PaintRoutine& graphics,
JucerDocument& document,
JucerDocumentEditor* const docHolder);
~PaintRoutineEditor();
//==============================================================================
void paint (Graphics& g);
void paintOverChildren (Graphics& g);
void resized();
void changeListenerCallback (ChangeBroadcaster*);
void mouseDown (const MouseEvent& e);
void mouseDrag (const MouseEvent& e);
void mouseUp (const MouseEvent& e);
void visibilityChanged();
void findLassoItemsInArea (Array <PaintElement*>& results, const Rectangle<int>& area);
SelectedItemSet <PaintElement*>& getLassoSelection();
bool isInterestedInFileDrag (const StringArray& files);
void filesDropped (const StringArray& filenames, int x, int y);
Rectangle<int> getComponentArea() const;
//==============================================================================
void refreshAllElements();
private:
PaintRoutine& graphics;
JucerDocument& document;
JucerDocumentEditor* const documentHolder;
LassoComponent <PaintElement*> lassoComp;
SnapGridPainter grid;
Image componentOverlay;
float componentOverlayOpacity;
Colour currentBackgroundColour;
void removeAllElementComps();
void updateComponentOverlay();
void updateChildBounds();
};

View File

@@ -0,0 +1,185 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "jucer_PaintRoutinePanel.h"
#include "../Properties/jucer_ColourPropertyComponent.h"
#include "../PaintElements/jucer_PaintElementPath.h"
//==============================================================================
class ComponentBackgroundColourProperty : public JucerColourPropertyComponent,
private ChangeListener
{
public:
ComponentBackgroundColourProperty (JucerDocument& doc,
PaintRoutine& routine_)
: JucerColourPropertyComponent ("background", false),
document (doc),
routine (routine_)
{
document.addChangeListener (this);
}
~ComponentBackgroundColourProperty() override
{
document.removeChangeListener (this);
}
void changeListenerCallback (ChangeBroadcaster*) override
{
refresh();
}
void setColour (Colour newColour) override { routine.setBackgroundColour (newColour); }
Colour getColour() const override { return routine.getBackgroundColour(); }
void resetToDefault() override
{
jassertfalse; // option shouldn't be visible
}
protected:
JucerDocument& document;
PaintRoutine& routine;
};
//==============================================================================
class GraphicsPropsPanel : public Component,
private ChangeListener
{
public:
GraphicsPropsPanel (PaintRoutine& paintRoutine_,
JucerDocument* doc)
: paintRoutine (paintRoutine_),
document (doc)
{
paintRoutine.getSelectedElements().addChangeListener (this);
paintRoutine.getSelectedPoints().addChangeListener (this);
addAndMakeVisible (propsPanel = new PropertyPanel());
}
~GraphicsPropsPanel() override
{
paintRoutine.getSelectedPoints().removeChangeListener (this);
paintRoutine.getSelectedElements().removeChangeListener (this);
clear();
deleteAllChildren();
}
void resized() override
{
propsPanel->setBounds (4, 4, getWidth() - 8, getHeight() - 8);
}
void clear()
{
propsPanel->clear();
}
void updateList()
{
auto state = propsPanel->getOpennessState();
clear();
if (document != nullptr)
{
Array <PropertyComponent*> props;
props.add (new ComponentBackgroundColourProperty (*document, paintRoutine));
propsPanel->addSection ("Class Properties", props);
}
if (state != nullptr)
propsPanel->restoreOpennessState (*state);
auto numSelected = paintRoutine.getSelectedElements().getNumSelected();
if (numSelected > 0) // xxx need to cope with multiple
{
if (auto* pe = paintRoutine.getSelectedElements().getSelectedItem (0))
{
if (paintRoutine.containsElement (pe))
{
Array <PropertyComponent*> props;
pe->getEditableProperties (props, numSelected > 1);
propsPanel->addSection (pe->getTypeName(), props);
}
}
}
if (paintRoutine.getSelectedPoints().getNumSelected() == 1) // xxx need to cope with multiple
{
if (auto* point = paintRoutine.getSelectedPoints().getSelectedItem (0))
{
Array <PropertyComponent*> props;
point->getEditableProperties (props, false);
propsPanel->addSection ("Path segment", props);
}
}
}
private:
void changeListenerCallback (ChangeBroadcaster*) override
{
updateList();
}
PaintRoutine& paintRoutine;
JucerDocument* document;
PropertyPanel* propsPanel;
};
//==============================================================================
PaintRoutinePanel::PaintRoutinePanel (JucerDocument& doc, PaintRoutine& pr,
JucerDocumentEditor* documentHolder)
: EditingPanelBase (doc,
new GraphicsPropsPanel (pr, &doc),
new PaintRoutineEditor (pr, doc, documentHolder)),
routine (pr)
{
}
PaintRoutinePanel::~PaintRoutinePanel()
{
deleteAllChildren();
}
void PaintRoutinePanel::updatePropertiesList()
{
((GraphicsPropsPanel*) propsPanel)->updateList();
}
Rectangle<int> PaintRoutinePanel::getComponentArea() const
{
return ((PaintRoutineEditor*) editor)->getComponentArea();
}

View File

@@ -0,0 +1,45 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_PaintRoutineEditor.h"
#include "jucer_EditingPanelBase.h"
//==============================================================================
class PaintRoutinePanel : public EditingPanelBase
{
public:
PaintRoutinePanel (JucerDocument&, PaintRoutine&, JucerDocumentEditor*);
~PaintRoutinePanel();
PaintRoutine& getPaintRoutine() const noexcept { return routine; }
void updatePropertiesList();
Rectangle<int> getComponentArea() const;
private:
PaintRoutine& routine;
};

View File

@@ -0,0 +1,759 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
class ComponentLayout;
//==============================================================================
/**
A rectangle whose coordinates can be defined in terms of absolute or
proportional distances.
Designed mainly for storing component positions, this gives you a lot of
control over how each coordinate is stored, either as an absolute position,
or as a proportion of the size of a parent rectangle.
It also allows you to define the anchor points by which the rectangle is
positioned, so for example you could specify that the top right of the
rectangle should be an absolute distance from its parent's bottom-right corner.
This object can be stored as a string, which takes the form "x y w h", including
symbols like '%' and letters to indicate the anchor point. See its toString()
method for more info.
Example usage:
@code
class MyComponent
{
void resized()
{
// this will set the child component's x to be 20% of our width, its y
// to be 30, its width to be 150, and its height to be 50% of our
// height..
const PositionedRectangle pos1 ("20% 30 150 50%");
pos1.applyToComponent (*myChildComponent1);
// this will inset the child component with a gap of 10 pixels
// around each of its edges..
const PositionedRectangle pos2 ("10 10 20M 20M");
pos2.applyToComponent (*myChildComponent2);
}
};
@endcode
*/
class PositionedRectangle
{
public:
//==============================================================================
/** Creates an empty rectangle with all coordinates set to zero.
The default anchor point is top-left; the default
*/
PositionedRectangle() noexcept
: x (0.0), y (0.0), w (0.0), h (0.0),
xMode ((int) anchorAtLeftOrTop | (int) absoluteFromParentTopLeft),
yMode ((int) anchorAtLeftOrTop | (int) absoluteFromParentTopLeft),
wMode (absoluteSize), hMode (absoluteSize)
{
}
/** Initialises a PositionedRectangle from a saved string version.
The string must be in the format generated by toString().
*/
PositionedRectangle (const String& stringVersion) noexcept
{
StringArray tokens;
tokens.addTokens (stringVersion, false);
decodePosString (tokens [0], xMode, x);
decodePosString (tokens [1], yMode, y);
decodeSizeString (tokens [2], wMode, w);
decodeSizeString (tokens [3], hMode, h);
}
/** Creates a copy of another PositionedRectangle. */
PositionedRectangle (const PositionedRectangle& other) noexcept
: x (other.x), y (other.y), w (other.w), h (other.h),
xMode (other.xMode), yMode (other.yMode),
wMode (other.wMode), hMode (other.hMode)
{
}
/** Copies another PositionedRectangle. */
PositionedRectangle& operator= (const PositionedRectangle& other) noexcept
{
x = other.x;
y = other.y;
w = other.w;
h = other.h;
xMode = other.xMode;
yMode = other.yMode;
wMode = other.wMode;
hMode = other.hMode;
return *this;
}
//==============================================================================
/** Returns a string version of this position, from which it can later be
re-generated.
The format is four coordinates, "x y w h".
- If a coordinate is absolute, it is stored as an integer, e.g. "100".
- If a coordinate is proportional to its parent's width or height, it is stored
as a percentage, e.g. "80%".
- If the X or Y coordinate is relative to the parent's right or bottom edge, the
number has "R" appended to it, e.g. "100R" means a distance of 100 pixels from
the parent's right-hand edge.
- If the X or Y coordinate is relative to the parent's centre, the number has "C"
appended to it, e.g. "-50C" would be 50 pixels left of the parent's centre.
- If the X or Y coordinate should be anchored at the component's right or bottom
edge, then it has "r" appended to it. So "-50Rr" would mean that this component's
right-hand edge should be 50 pixels left of the parent's right-hand edge.
- If the X or Y coordinate should be anchored at the component's centre, then it
has "c" appended to it. So "-50Rc" would mean that this component's
centre should be 50 pixels left of the parent's right-hand edge. "40%c" means that
this component's centre should be placed 40% across the parent's width.
- If it's a width or height that should use the parentSizeMinusAbsolute mode, then
the number has "M" appended to it.
To reload a stored string, use the constructor that takes a string parameter.
*/
String toString() const
{
String s;
s.preallocateBytes (32);
addPosDescription (s, xMode, x); s << ' ';
addPosDescription (s, yMode, y); s << ' ';
addSizeDescription (s, wMode, w); s << ' ';
addSizeDescription (s, hMode, h);
return s;
}
//==============================================================================
/** Calculates the absolute position, given the size of the space that
it should go in.
This will work out any proportional distances and sizes relative to the
target rectangle, and will return the absolute position.
@see applyToComponent
*/
Rectangle<int> getRectangle (const Rectangle<int>& target) const noexcept
{
jassert (! target.isEmpty());
double x_, y_, w_, h_;
applyPosAndSize (x_, w_, x, w, xMode, wMode, target.getX(), target.getWidth());
applyPosAndSize (y_, h_, y, h, yMode, hMode, target.getY(), target.getHeight());
return Rectangle<int> (roundToInt (x_), roundToInt (y_), roundToInt (w_), roundToInt (h_));
}
/** Same as getRectangle(), but returning the values as doubles rather than ints. */
void getRectangleDouble (const Rectangle<int>& target,
double& x_, double& y_, double& w_, double& h_) const noexcept
{
jassert (! target.isEmpty());
applyPosAndSize (x_, w_, x, w, xMode, wMode, target.getX(), target.getWidth());
applyPosAndSize (y_, h_, y, h, yMode, hMode, target.getY(), target.getHeight());
}
/** This sets the bounds of the given component to this position.
This is equivalent to writing:
@code
comp.setBounds (getRectangle (Rectangle<int> (0, 0, comp.getParentWidth(), comp.getParentHeight())));
@endcode
@see getRectangle, updateFromComponent
*/
void applyToComponent (Component& comp) const noexcept
{
comp.setBounds (getRectangle (Rectangle<int> (comp.getParentWidth(), comp.getParentHeight())));
}
//==============================================================================
/** Updates this object's coordinates to match the given rectangle.
This will set all coordinates based on the given rectangle, re-calculating
any proportional distances, and using the current anchor points.
So for example if the x coordinate mode is currently proportional, this will
re-calculate x based on the rectangle's relative position within the target
rectangle's width.
If the target rectangle's width or height are zero then it may not be possible
to re-calculate some proportional coordinates. In this case, those coordinates
will not be changed.
*/
void updateFrom (const Rectangle<int>& newPosition,
const Rectangle<int>& targetSpaceToBeRelativeTo) noexcept
{
updatePosAndSize (x, w, newPosition.getX(), newPosition.getWidth(), xMode, wMode, targetSpaceToBeRelativeTo.getX(), targetSpaceToBeRelativeTo.getWidth());
updatePosAndSize (y, h, newPosition.getY(), newPosition.getHeight(), yMode, hMode, targetSpaceToBeRelativeTo.getY(), targetSpaceToBeRelativeTo.getHeight());
}
/** Same functionality as updateFrom(), but taking doubles instead of ints.
*/
void updateFromDouble (const double newX, const double newY,
const double newW, const double newH,
const Rectangle<int>& target) noexcept
{
updatePosAndSize (x, w, newX, newW, xMode, wMode, target.getX(), target.getWidth());
updatePosAndSize (y, h, newY, newH, yMode, hMode, target.getY(), target.getHeight());
}
/** Updates this object's coordinates to match the bounds of this component.
This is equivalent to calling updateFrom() with the component's bounds and
it parent size.
If the component doesn't currently have a parent, then proportional coordinates
might not be updated because it would need to know the parent's size to do the
maths for this.
*/
void updateFromComponent (const Component& comp) noexcept
{
if (comp.getParentComponent() == nullptr && ! comp.isOnDesktop())
updateFrom (comp.getBounds(), Rectangle<int>());
else
updateFrom (comp.getBounds(), Rectangle<int> (comp.getParentWidth(), comp.getParentHeight()));
}
//==============================================================================
/** Specifies the point within the rectangle, relative to which it should be positioned. */
enum AnchorPoint
{
anchorAtLeftOrTop = 1 << 0, /**< The x or y coordinate specifies where the left or top edge of the rectangle should be. */
anchorAtRightOrBottom = 1 << 1, /**< The x or y coordinate specifies where the right or bottom edge of the rectangle should be. */
anchorAtCentre = 1 << 2 /**< The x or y coordinate specifies where the centre of the rectangle should be. */
};
/** Specifies how an x or y coordinate should be interpreted. */
enum PositionMode
{
absoluteFromParentTopLeft = 1 << 3, /**< The x or y coordinate specifies an absolute distance from the parent's top or left edge. */
absoluteFromParentBottomRight = 1 << 4, /**< The x or y coordinate specifies an absolute distance from the parent's bottom or right edge. */
absoluteFromParentCentre = 1 << 5, /**< The x or y coordinate specifies an absolute distance from the parent's centre. */
proportionOfParentSize = 1 << 6 /**< The x or y coordinate specifies a proportion of the parent's width or height, measured from the parent's top or left. */
};
/** Specifies how the width or height should be interpreted. */
enum SizeMode
{
absoluteSize = 1 << 0, /**< The width or height specifies an absolute size. */
parentSizeMinusAbsolute = 1 << 1, /**< The width or height is an amount that should be subtracted from the parent's width or height. */
proportionalSize = 1 << 2, /**< The width or height specifies a proportion of the parent's width or height. */
};
//==============================================================================
/** Sets all options for all coordinates.
This requires a reference rectangle to be specified, because if you're changing any
of the modes from proportional to absolute or vice-versa, then it'll need to convert
the coordinates, and will need to know the parent size so it can calculate this.
*/
void setModes (const AnchorPoint xAnchor, const PositionMode xMode_,
const AnchorPoint yAnchor, const PositionMode yMode_,
const SizeMode widthMode, const SizeMode heightMode,
const Rectangle<int>& target) noexcept
{
if (xMode != ((int) xAnchor | (int) xMode_) || wMode != widthMode)
{
double tx, tw;
applyPosAndSize (tx, tw, x, w, xMode, wMode, target.getX(), target.getWidth());
xMode = (uint8) ((int) xAnchor | (int) xMode_);
wMode = (uint8) widthMode;
updatePosAndSize (x, w, tx, tw, xMode, wMode, target.getX(), target.getWidth());
}
if (yMode != ((int) yAnchor | (int) yMode_) || hMode != heightMode)
{
double ty, th;
applyPosAndSize (ty, th, y, h, yMode, hMode, target.getY(), target.getHeight());
yMode = (uint8) ((int) yAnchor | (int) yMode_);
hMode = (uint8) heightMode;
updatePosAndSize (y, h, ty, th, yMode, hMode, target.getY(), target.getHeight());
}
}
/** Returns the anchoring mode for the x coordinate.
To change any of the modes, use setModes().
*/
AnchorPoint getAnchorPointX() const noexcept
{
return (AnchorPoint) (xMode & (anchorAtLeftOrTop | anchorAtRightOrBottom | anchorAtCentre));
}
/** Returns the positioning mode for the x coordinate.
To change any of the modes, use setModes().
*/
PositionMode getPositionModeX() const noexcept
{
return (PositionMode) (xMode & (absoluteFromParentTopLeft | absoluteFromParentBottomRight
| absoluteFromParentCentre | proportionOfParentSize));
}
/** Returns the raw x coordinate.
If the x position mode is absolute, then this will be the absolute value. If it's
proportional, then this will be a fractional proportion, where 1.0 means the full
width of the parent space.
*/
double getX() const noexcept { return x; }
/** Sets the raw value of the x coordinate.
See getX() for the meaning of this value.
*/
void setX (const double newX) noexcept { x = newX; }
/** Returns the anchoring mode for the y coordinate.
To change any of the modes, use setModes().
*/
AnchorPoint getAnchorPointY() const noexcept
{
return (AnchorPoint) (yMode & (anchorAtLeftOrTop | anchorAtRightOrBottom | anchorAtCentre));
}
/** Returns the positioning mode for the y coordinate.
To change any of the modes, use setModes().
*/
PositionMode getPositionModeY() const noexcept
{
return (PositionMode) (yMode & (absoluteFromParentTopLeft | absoluteFromParentBottomRight
| absoluteFromParentCentre | proportionOfParentSize));
}
/** Returns the raw y coordinate.
If the y position mode is absolute, then this will be the absolute value. If it's
proportional, then this will be a fractional proportion, where 1.0 means the full
height of the parent space.
*/
double getY() const noexcept { return y; }
/** Sets the raw value of the y coordinate.
See getY() for the meaning of this value.
*/
void setY (const double newY) noexcept { y = newY; }
/** Returns the mode used to calculate the width.
To change any of the modes, use setModes().
*/
SizeMode getWidthMode() const noexcept { return (SizeMode) wMode; }
/** Returns the raw width value.
If the width mode is absolute, then this will be the absolute value. If the mode is
proportional, then this will be a fractional proportion, where 1.0 means the full
width of the parent space.
*/
double getWidth() const noexcept { return w; }
/** Sets the raw width value.
See getWidth() for the details about what this value means.
*/
void setWidth (const double newWidth) noexcept { w = newWidth; }
/** Returns the mode used to calculate the height.
To change any of the modes, use setModes().
*/
SizeMode getHeightMode() const noexcept { return (SizeMode) hMode; }
/** Returns the raw height value.
If the height mode is absolute, then this will be the absolute value. If the mode is
proportional, then this will be a fractional proportion, where 1.0 means the full
height of the parent space.
*/
double getHeight() const noexcept { return h; }
/** Sets the raw height value.
See getHeight() for the details about what this value means.
*/
void setHeight (const double newHeight) noexcept { h = newHeight; }
//==============================================================================
/** If the size and position are constance, and wouldn't be affected by changes
in the parent's size, then this will return true.
*/
bool isPositionAbsolute() const noexcept
{
return (xMode & ~anchorAtLeftOrTop) == absoluteFromParentTopLeft
&& (yMode & ~anchorAtLeftOrTop) == absoluteFromParentTopLeft
&& wMode == absoluteSize
&& hMode == absoluteSize;
}
//==============================================================================
/** Compares two objects. */
bool operator== (const PositionedRectangle& other) const noexcept
{
return x == other.x && y == other.y
&& w == other.w && h == other.h
&& xMode == other.xMode && yMode == other.yMode
&& wMode == other.wMode && hMode == other.hMode;
}
/** Compares two objects. */
bool operator!= (const PositionedRectangle& other) const noexcept
{
return ! operator== (other);
}
private:
//==============================================================================
double x, y, w, h;
uint8 xMode, yMode, wMode, hMode;
void addPosDescription (String& s, const uint8 mode, const double value) const noexcept
{
if ((mode & proportionOfParentSize) != 0)
{
s << (roundToInt (value * 100000.0) / 1000.0) << '%';
}
else
{
s << (roundToInt (value * 100.0) / 100.0);
if ((mode & absoluteFromParentBottomRight) != 0)
s << 'R';
else if ((mode & absoluteFromParentCentre) != 0)
s << 'C';
}
if ((mode & anchorAtRightOrBottom) != 0)
s << 'r';
else if ((mode & anchorAtCentre) != 0)
s << 'c';
}
void addSizeDescription (String& s, const uint8 mode, const double value) const noexcept
{
if (mode == proportionalSize)
s << (roundToInt (value * 100000.0) / 1000.0) << '%';
else if (mode == parentSizeMinusAbsolute)
s << (roundToInt (value * 100.0) / 100.0) << 'M';
else
s << (roundToInt (value * 100.0) / 100.0);
}
void decodePosString (const String& s, uint8& mode, double& value) noexcept
{
if (s.containsChar ('r'))
mode = anchorAtRightOrBottom;
else if (s.containsChar ('c'))
mode = anchorAtCentre;
else
mode = anchorAtLeftOrTop;
if (s.containsChar ('%'))
{
mode |= proportionOfParentSize;
value = s.removeCharacters ("%rcRC").getDoubleValue() / 100.0;
}
else
{
if (s.containsChar ('R'))
mode |= absoluteFromParentBottomRight;
else if (s.containsChar ('C'))
mode |= absoluteFromParentCentre;
else
mode |= absoluteFromParentTopLeft;
value = s.removeCharacters ("rcRC").getDoubleValue();
}
}
void decodeSizeString (const String& s, uint8& mode, double& value) noexcept
{
if (s.containsChar ('%'))
{
mode = proportionalSize;
value = s.upToFirstOccurrenceOf ("%", false, false).getDoubleValue() / 100.0;
}
else if (s.containsChar ('M'))
{
mode = parentSizeMinusAbsolute;
value = s.getDoubleValue();
}
else
{
mode = absoluteSize;
value = s.getDoubleValue();
}
}
void applyPosAndSize (double& xOut, double& wOut, const double x_, const double w_,
const uint8 xMode_, const uint8 wMode_,
const int parentPos, const int parentSize) const noexcept
{
if (wMode_ == proportionalSize)
wOut = roundToInt (w_ * parentSize);
else if (wMode_ == parentSizeMinusAbsolute)
wOut = jmax (0, parentSize - roundToInt (w_));
else
wOut = roundToInt (w_);
if ((xMode_ & proportionOfParentSize) != 0)
xOut = parentPos + x_ * parentSize;
else if ((xMode_ & absoluteFromParentBottomRight) != 0)
xOut = (parentPos + parentSize) - x_;
else if ((xMode_ & absoluteFromParentCentre) != 0)
xOut = x_ + (parentPos + parentSize / 2);
else
xOut = x_ + parentPos;
if ((xMode_ & anchorAtRightOrBottom) != 0)
xOut -= wOut;
else if ((xMode_ & anchorAtCentre) != 0)
xOut -= wOut / 2;
}
void updatePosAndSize (double& xOut, double& wOut, double x_, const double w_,
const uint8 xMode_, const uint8 wMode_,
const int parentPos, const int parentSize) const noexcept
{
if (wMode_ == proportionalSize)
{
if (parentSize > 0)
wOut = w_ / parentSize;
}
else if (wMode_ == parentSizeMinusAbsolute)
wOut = parentSize - w_;
else
wOut = w_;
if ((xMode_ & anchorAtRightOrBottom) != 0)
x_ += w_;
else if ((xMode_ & anchorAtCentre) != 0)
x_ += w_ / 2;
if ((xMode_ & proportionOfParentSize) != 0)
{
if (parentSize > 0)
xOut = (x_ - parentPos) / parentSize;
}
else if ((xMode_ & absoluteFromParentBottomRight) != 0)
xOut = (parentPos + parentSize) - x_;
else if ((xMode_ & absoluteFromParentCentre) != 0)
xOut = x_ - (parentPos + parentSize / 2);
else
xOut = x_ - parentPos;
}
};
//==============================================================================
struct RelativePositionedRectangle
{
//==============================================================================
RelativePositionedRectangle()
: relativeToX (0),
relativeToY (0),
relativeToW (0),
relativeToH (0)
{
}
RelativePositionedRectangle (const RelativePositionedRectangle& other)
: rect (other.rect),
relativeToX (other.relativeToX),
relativeToY (other.relativeToY),
relativeToW (other.relativeToW),
relativeToH (other.relativeToH)
{
}
RelativePositionedRectangle& operator= (const RelativePositionedRectangle& other)
{
rect = other.rect;
relativeToX = other.relativeToX;
relativeToY = other.relativeToY;
relativeToW = other.relativeToW;
relativeToH = other.relativeToH;
return *this;
}
//==============================================================================
bool operator== (const RelativePositionedRectangle& other) const noexcept
{
return rect == other.rect
&& relativeToX == other.relativeToX
&& relativeToY == other.relativeToY
&& relativeToW == other.relativeToW
&& relativeToH == other.relativeToH;
}
bool operator!= (const RelativePositionedRectangle& other) const noexcept
{
return ! operator== (other);
}
template <typename LayoutType>
void getRelativeTargetBounds (const Rectangle<int>& parentArea,
const LayoutType* layout,
int& x, int& xw, int& y, int& yh,
int& w, int& h) const
{
Component* rx = {};
Component* ry = {};
Component* rw = {};
Component* rh = {};
if (layout != nullptr)
{
rx = layout->findComponentWithId (relativeToX);
ry = layout->findComponentWithId (relativeToY);
rw = layout->findComponentWithId (relativeToW);
rh = layout->findComponentWithId (relativeToH);
}
x = parentArea.getX() + (rx != nullptr ? rx->getX() : 0);
y = parentArea.getY() + (ry != nullptr ? ry->getY() : 0);
w = rw != nullptr ? rw->getWidth() : parentArea.getWidth();
h = rh != nullptr ? rh->getHeight() : parentArea.getHeight();
xw = rx != nullptr ? rx->getWidth() : parentArea.getWidth();
yh = ry != nullptr ? ry->getHeight() : parentArea.getHeight();
}
Rectangle<int> getRectangle (const Rectangle<int>& parentArea,
const ComponentLayout* layout) const
{
int x, xw, y, yh, w, h;
getRelativeTargetBounds (parentArea, layout, x, xw, y, yh, w, h);
const Rectangle<int> xyRect ((xw <= 0 || yh <= 0) ? Rectangle<int>()
: rect.getRectangle (Rectangle<int> (x, y, xw, yh)));
const Rectangle<int> whRect ((w <= 0 || h <= 0) ? Rectangle<int>()
: rect.getRectangle (Rectangle<int> (x, y, w, h)));
return Rectangle<int> (xyRect.getX(), xyRect.getY(),
whRect.getWidth(), whRect.getHeight());
}
void getRectangleDouble (double& x, double& y, double& w, double& h,
const Rectangle<int>& parentArea,
const ComponentLayout* layout) const
{
int rx, rxw, ry, ryh, rw, rh;
getRelativeTargetBounds (parentArea, layout, rx, rxw, ry, ryh, rw, rh);
double dummy1, dummy2;
rect.getRectangleDouble (Rectangle<int> (rx, ry, rxw, ryh), x, y, dummy1, dummy2);
rect.getRectangleDouble (Rectangle<int> (rx, ry, rw, rh), dummy1, dummy2, w, h);
}
void updateFromComponent (const Component& comp, const ComponentLayout* layout)
{
int x, xw, y, yh, w, h;
getRelativeTargetBounds (Rectangle<int> (0, 0, comp.getParentWidth(), comp.getParentHeight()),
layout, x, xw, y, yh, w, h);
PositionedRectangle xyRect (rect), whRect (rect);
xyRect.updateFrom (comp.getBounds(), Rectangle<int> (x, y, xw, yh));
whRect.updateFrom (comp.getBounds(), Rectangle<int> (x, y, w, h));
rect.setX (xyRect.getX());
rect.setY (xyRect.getY());
rect.setWidth (whRect.getWidth());
rect.setHeight (whRect.getHeight());
}
void updateFrom (double newX, double newY, double newW, double newH,
const Rectangle<int>& parentArea, const ComponentLayout* layout)
{
int x, xw, y, yh, w, h;
getRelativeTargetBounds (parentArea, layout, x, xw, y, yh, w, h);
PositionedRectangle xyRect (rect), whRect (rect);
xyRect.updateFromDouble (newX, newY, newW, newH, Rectangle<int> (x, y, xw, yh));
whRect.updateFromDouble (newX, newY, newW, newH, Rectangle<int> (x, y, w, h));
rect.setX (xyRect.getX());
rect.setY (xyRect.getY());
rect.setWidth (whRect.getWidth());
rect.setHeight (whRect.getHeight());
}
void applyToXml (XmlElement& e) const
{
e.setAttribute ("pos", rect.toString());
if (relativeToX != 0) e.setAttribute ("posRelativeX", String::toHexString (relativeToX));
if (relativeToY != 0) e.setAttribute ("posRelativeY", String::toHexString (relativeToY));
if (relativeToW != 0) e.setAttribute ("posRelativeW", String::toHexString (relativeToW));
if (relativeToH != 0) e.setAttribute ("posRelativeH", String::toHexString (relativeToH));
}
void restoreFromXml (const XmlElement& e, const RelativePositionedRectangle& defaultPos)
{
rect = PositionedRectangle (e.getStringAttribute ("pos", defaultPos.rect.toString()));
relativeToX = e.getStringAttribute ("posRelativeX", String::toHexString (defaultPos.relativeToX)).getHexValue64();
relativeToY = e.getStringAttribute ("posRelativeY", String::toHexString (defaultPos.relativeToY)).getHexValue64();
relativeToW = e.getStringAttribute ("posRelativeW", String::toHexString (defaultPos.relativeToW)).getHexValue64();
relativeToH = e.getStringAttribute ("posRelativeH", String::toHexString (defaultPos.relativeToH)).getHexValue64();
}
String toString() const
{
StringArray toks;
toks.addTokens (rect.toString(), false);
return toks[0] + " " + toks[1];
}
Point<float> toXY (const Rectangle<int>& parentArea,
const ComponentLayout* layout) const
{
double x, y, w, h;
getRectangleDouble (x, y, w, h, parentArea, layout);
return { (float) x, (float) y };
}
void getXY (double& x, double& y,
const Rectangle<int>& parentArea,
const ComponentLayout* layout) const
{
double w, h;
getRectangleDouble (x, y, w, h, parentArea, layout);
}
//==============================================================================
PositionedRectangle rect;
int64 relativeToX;
int64 relativeToY;
int64 relativeToW;
int64 relativeToH;
};

View File

@@ -0,0 +1,272 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "jucer_ResourceEditorPanel.h"
//==============================================================================
class ResourceListButton : public Component
{
public:
explicit ResourceListButton (JucerDocument& doc)
: document (doc), reloadButton ("Reload"), row (0)
{
setInterceptsMouseClicks (false, true);
addAndMakeVisible (reloadButton);
reloadButton.onClick = [this]
{
if (auto* r = document.getResources() [row])
document.getResources().browseForResource ("Select a file to replace this resource", "*",
File (r->originalFilename), r->name, nullptr);
};
}
void update (int newRow)
{
row = newRow;
reloadButton.setVisible (document.getResources() [row] != nullptr);
}
void resized()
{
reloadButton.setBoundsInset (BorderSize<int> (2));
}
private:
JucerDocument& document;
TextButton reloadButton;
int row;
};
//==============================================================================
ResourceEditorPanel::ResourceEditorPanel (JucerDocument& doc)
: document (doc),
addButton ("Add new resource..."),
reloadAllButton ("Reload all resources"),
delButton ("Delete selected resources")
{
addAndMakeVisible (addButton);
addButton.onClick = [this]
{
document.getResources().browseForResource ("Select a file to add as a resource", "*", {}, {}, nullptr);
};
addAndMakeVisible (reloadAllButton);
reloadAllButton.onClick = [this] { reloadAll(); };
addAndMakeVisible (delButton);
delButton.setEnabled (false);
delButton.onClick = [this] { document.getResources().remove (listBox->getSelectedRow (0)); };
listBox.reset (new TableListBox (String(), this));
addAndMakeVisible (listBox.get());
listBox->getHeader().addColumn ("name", 1, 150, 80, 400);
listBox->getHeader().addColumn ("original file", 2, 350, 80, 800);
listBox->getHeader().addColumn ("size", 3, 100, 40, 150);
listBox->getHeader().addColumn ("reload", 4, 100, 100, 100, TableHeaderComponent::notResizableOrSortable);
listBox->getHeader().setStretchToFitActive (true);
listBox->setOutlineThickness (1);
listBox->updateContent();
document.addChangeListener (this);
handleCommandMessage (1);
lookAndFeelChanged();
}
ResourceEditorPanel::~ResourceEditorPanel()
{
document.removeChangeListener (this);
}
int ResourceEditorPanel::getNumRows()
{
return document.getResources().size();
}
void ResourceEditorPanel::paintRowBackground (Graphics& g, int /*rowNumber*/,
int /*width*/, int /*height*/, bool rowIsSelected)
{
if (rowIsSelected)
g.fillAll (findColour (defaultHighlightColourId));
}
void ResourceEditorPanel::paintCell (Graphics& g, int rowNumber, int columnId, int width, int height,
bool rowIsSelected)
{
if (const BinaryResources::BinaryResource* const r = document.getResources() [rowNumber])
{
String text;
if (columnId == 1)
text = r->name;
else if (columnId == 2)
text = r->originalFilename;
else if (columnId == 3)
text = File::descriptionOfSizeInBytes ((int64) r->data.getSize());
if (rowIsSelected)
g.setColour (findColour (defaultHighlightedTextColourId));
else
g.setColour (findColour (defaultTextColourId));
g.setFont (13.0f);
g.drawText (text, 4, 0, width - 6, height, Justification::centredLeft, true);
}
}
Component* ResourceEditorPanel::refreshComponentForCell (int rowNumber, int columnId, bool /*isRowSelected*/,
Component* existingComponentToUpdate)
{
if (columnId != 4)
return nullptr;
if (existingComponentToUpdate == nullptr)
existingComponentToUpdate = new ResourceListButton (document);
((ResourceListButton*) existingComponentToUpdate)->update (rowNumber);
return existingComponentToUpdate;
}
int ResourceEditorPanel::getColumnAutoSizeWidth (int columnId)
{
if (columnId == 4)
return 0;
Font f (13.0f);
int widest = 40;
for (int i = document.getResources().size(); --i >= 0;)
{
const BinaryResources::BinaryResource* const r = document.getResources() [i];
jassert (r != nullptr);
String text;
if (columnId == 1)
text = r->name;
else if (columnId == 2)
text = r->originalFilename;
else if (columnId == 3)
text = File::descriptionOfSizeInBytes ((int64) r->data.getSize());
widest = jmax (widest, f.getStringWidth (text));
}
return widest + 10;
}
void ResourceEditorPanel::lookAndFeelChanged()
{
listBox->setColour (ListBox::backgroundColourId, findColour (secondaryBackgroundColourId));
listBox->setColour (ListBox::outlineColourId, Colours::transparentBlack);
}
//==============================================================================
class ResourceSorter
{
public:
ResourceSorter (const int columnId_, const bool forwards)
: columnId (columnId_),
direction (forwards ? 1 : -1)
{
}
int compareElements (BinaryResources::BinaryResource* first, BinaryResources::BinaryResource* second)
{
if (columnId == 1) return direction * first->name.compare (second->name);
if (columnId == 2) return direction * first->originalFilename.compare (second->originalFilename);
if (columnId == 3) return direction * (int) first->data.getSize() - (int) second->data.getSize();
return 0;
}
private:
const int columnId, direction;
ResourceSorter (const ResourceSorter&);
ResourceSorter& operator= (const ResourceSorter&);
};
void ResourceEditorPanel::sortOrderChanged (int newSortColumnId, const bool isForwards)
{
ResourceSorter sorter (newSortColumnId, isForwards);
document.getResources().sort (sorter);
}
//==============================================================================
void ResourceEditorPanel::selectedRowsChanged (int /*lastRowSelected*/)
{
delButton.setEnabled (listBox->getNumSelectedRows() > 0);
}
void ResourceEditorPanel::resized()
{
auto bounds = getLocalBounds();
auto buttonSlice = bounds.removeFromBottom (40).reduced (5, 5);
addButton.setBounds (buttonSlice.removeFromLeft (125));
buttonSlice.removeFromLeft (10);
reloadAllButton.setBounds (buttonSlice.removeFromLeft (125));
delButton.setBounds (buttonSlice.removeFromRight (125));
listBox->setBounds (bounds);
}
void ResourceEditorPanel::paint (Graphics& g)
{
g.fillAll (findColour (secondaryBackgroundColourId));
}
void ResourceEditorPanel::visibilityChanged()
{
if (isVisible())
listBox->updateContent();
}
void ResourceEditorPanel::changeListenerCallback (ChangeBroadcaster*)
{
if (isVisible())
listBox->updateContent();
}
void ResourceEditorPanel::reloadAll()
{
StringArray failed;
for (int i = 0; i < document.getResources().size(); ++i)
if (! document.getResources().reload (i))
failed.add (document.getResources().getResourceNames() [i]);
if (failed.size() > 0)
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
TRANS("Reloading resources"),
TRANS("The following resources couldn't be reloaded from their original files:\n\n")
+ failed.joinIntoString (", "));
}

View File

@@ -0,0 +1,59 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_JucerDocument.h"
//==============================================================================
class ResourceEditorPanel : public Component,
private TableListBoxModel,
private ChangeListener
{
public:
ResourceEditorPanel (JucerDocument& document);
~ResourceEditorPanel() override;
void resized() override;
void paint (Graphics& g) override;
void visibilityChanged() override;
void changeListenerCallback (ChangeBroadcaster*) override;
int getNumRows() override;
void paintRowBackground (Graphics& g, int rowNumber, int width, int height, bool rowIsSelected) override;
void paintCell (Graphics& g, int rowNumber, int columnId, int width, int height, bool rowIsSelected) override;
Component* refreshComponentForCell (int rowNumber, int columnId, bool isRowSelected, Component* existingComponentToUpdate) override;
int getColumnAutoSizeWidth (int columnId) override;
void sortOrderChanged (int newSortColumnId, bool isForwards) override;
void selectedRowsChanged (int lastRowSelected) override;
private:
void lookAndFeelChanged() override;
void reloadAll();
JucerDocument& document;
std::unique_ptr<TableListBox> listBox;
TextButton addButton, reloadAllButton, delButton;
};

View File

@@ -0,0 +1,80 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_JucerDocument.h"
#include "../jucer_PaintRoutine.h"
//==============================================================================
class SnapGridPainter
{
public:
SnapGridPainter()
: snapGridSize (-1), snapShown (false)
{
}
bool updateFromDesign (JucerDocument& design)
{
if (snapGridSize != design.getSnappingGridSize()
|| snapShown != (design.isSnapShown() && design.isSnapActive (false)))
{
snapGridSize = design.getSnappingGridSize();
snapShown = design.isSnapShown() && design.isSnapActive (false);
return true;
}
return false;
}
void draw (Graphics& g, PaintRoutine* backgroundGraphics)
{
if (snapShown && snapGridSize > 2)
{
Colour col (Colours::black);
if (backgroundGraphics != nullptr)
col = backgroundGraphics->getBackgroundColour().contrasting();
const Rectangle<int> clip (g.getClipBounds());
RectangleList<float> gridLines;
for (int x = clip.getX() - (clip.getX() % snapGridSize); x < clip.getRight(); x += snapGridSize)
gridLines.addWithoutMerging (Rectangle<float> ((float) x, 0.0f, 1.0f, (float) clip.getBottom()));
for (int y = clip.getY() - (clip.getY() % snapGridSize); y < clip.getBottom(); y += snapGridSize)
gridLines.addWithoutMerging (Rectangle<float> (0.0f, (float) y, (float) clip.getRight(), 1.0f));
g.setColour (col.withAlpha (0.1f));
g.fillRectList (gridLines);
}
}
private:
int snapGridSize;
bool snapShown;
};

View File

@@ -0,0 +1,179 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "jucer_TestComponent.h"
#include "../jucer_ObjectTypes.h"
static Array <TestComponent*> testComponents;
//==============================================================================
TestComponent::TestComponent (JucerDocument* const doc,
JucerDocument* const loaded,
const bool alwaysFill)
: ownerDocument (doc),
loadedDocument (loaded),
alwaysFillBackground (alwaysFill)
{
setToInitialSize();
updateContents();
testComponents.add (this);
setLookAndFeel (&getLookAndFeel());
}
TestComponent::~TestComponent()
{
testComponents.removeFirstMatchingValue (this);
deleteAllChildren();
}
//==============================================================================
void TestComponent::reloadAll()
{
for (int i = testComponents.size(); --i >= 0;)
testComponents.getUnchecked(i)->reload();
}
void TestComponent::reload()
{
if (findFile().exists() && lastModificationTime != findFile().getLastModificationTime())
setFilename (filename);
}
//==============================================================================
static StringArray recursiveFiles;
File TestComponent::findFile() const
{
if (filename.isEmpty())
return {};
if (ownerDocument != nullptr)
return ownerDocument->getCppFile().getSiblingFile (filename);
return File::getCurrentWorkingDirectory().getChildFile (filename);
}
void TestComponent::setFilename (const String& newName)
{
File newFile;
if (newName.isNotEmpty())
{
if (ownerDocument != nullptr)
newFile = ownerDocument->getCppFile().getSiblingFile (newName);
else
newFile = File::getCurrentWorkingDirectory().getChildFile (newName);
}
if (! recursiveFiles.contains (newFile.getFullPathName()))
{
recursiveFiles.add (newFile.getFullPathName());
loadedDocument.reset();
filename = newName;
lastModificationTime = findFile().getLastModificationTime();
loadedDocument.reset (JucerDocument::createForCppFile (nullptr, findFile()));
updateContents();
repaint();
recursiveFiles.remove (recursiveFiles.size() - 1);
}
}
void TestComponent::setConstructorParams (const String& newParams)
{
constructorParams = newParams;
}
void TestComponent::updateContents()
{
deleteAllChildren();
repaint();
if (loadedDocument != nullptr)
{
addAndMakeVisible (loadedDocument->createTestComponent (alwaysFillBackground));
resized();
}
}
void TestComponent::setToInitialSize()
{
if (loadedDocument != nullptr)
setSize (loadedDocument->getInitialWidth(),
loadedDocument->getInitialHeight());
else
setSize (100, 100);
}
//==============================================================================
void TestComponent::paint (Graphics& g)
{
if (loadedDocument == nullptr)
{
g.fillAll (Colours::white.withAlpha (0.25f));
g.setColour (Colours::black.withAlpha (0.5f));
g.drawRect (getLocalBounds());
g.drawLine (0.0f, 0.0f, (float) getWidth(), (float) getHeight());
g.drawLine (0.0f, (float) getHeight(), (float) getWidth(), 0.0f);
g.setFont (14.0f);
g.drawText ("Projucer Component",
0, 0, getWidth(), getHeight() / 2,
Justification::centred, true);
g.drawText ("(no file loaded)",
0, getHeight() / 2, getWidth(), getHeight() / 2,
Justification::centred, true);
}
}
void TestComponent::resized()
{
if (Component* const c = getChildComponent (0))
{
setOpaque (c->isOpaque());
c->setBounds (getLocalBounds());
}
}
//==============================================================================
void TestComponent::showInDialogBox (JucerDocument& document)
{
DialogWindow::LaunchOptions o;
o.content.setOwned (new TestComponent (nullptr, document.createCopy(), true));
o.dialogTitle = "Testing: " + document.getClassName();
o.dialogBackgroundColour = Colours::azure;
o.escapeKeyTriggersCloseButton = true;
o.useNativeTitleBar = false;
o.resizable = true;
o.launchAsync();
}

View File

@@ -0,0 +1,73 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../jucer_JucerDocument.h"
//==============================================================================
class TestComponent : public Component
{
public:
//==============================================================================
TestComponent (JucerDocument* const ownerDocument,
JucerDocument* const loadedDocument,
const bool alwaysFillBackground);
~TestComponent() override;
//==============================================================================
void setFilename (const String& fn);
const String& getFilename() const noexcept { return filename; }
void setConstructorParams (const String& newParams);
const String& getConstructorParams() const noexcept { return constructorParams; }
File findFile() const;
JucerDocument* getDocument() const noexcept { return loadedDocument.get(); }
JucerDocument* getOwnerDocument() const noexcept { return ownerDocument; }
void setToInitialSize();
//==============================================================================
void paint (Graphics&) override;
void resized() override;
static void showInDialogBox (JucerDocument&);
// reloads any test comps that need to do so
static void reloadAll();
private:
JucerDocument* ownerDocument;
std::unique_ptr<JucerDocument> loadedDocument;
String filename, constructorParams;
Time lastModificationTime;
const bool alwaysFillBackground;
void updateContents();
void reload();
};

View File

@@ -0,0 +1,337 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../Application/jucer_Headers.h"
#include "jucer_JucerDocument.h"
//==============================================================================
BinaryResources& BinaryResources::operator= (const BinaryResources& other)
{
for (auto* r : other.resources)
add (r->name, r->originalFilename, r->data);
return *this;
}
void BinaryResources::changed()
{
if (document != nullptr)
{
document->changed();
document->refreshAllPropertyComps();
}
}
//==============================================================================
void BinaryResources::clear()
{
if (resources.size() > 0)
{
resources.clear();
changed();
}
}
StringArray BinaryResources::getResourceNames() const
{
StringArray s;
for (auto* r : resources)
s.add (r->name);
return s;
}
BinaryResources::BinaryResource* BinaryResources::findResource (const String& name) const noexcept
{
for (auto* r : resources)
if (r->name == name)
return r;
return nullptr;
}
const BinaryResources::BinaryResource* BinaryResources::getResource (const String& name) const
{
return findResource (name);
}
const BinaryResources::BinaryResource* BinaryResources::getResourceForFile (const File& file) const
{
for (auto* r : resources)
if (r->originalFilename == file.getFullPathName())
return r;
return nullptr;
}
bool BinaryResources::add (const String& name, const File& file)
{
MemoryBlock mb;
if (! file.loadFileAsData (mb))
return false;
add (name, file.getFullPathName(), mb);
return true;
}
void BinaryResources::add (const String& name, const String& originalFileName, const MemoryBlock& data)
{
auto* r = findResource (name);
if (r == nullptr)
{
resources.add (r = new BinaryResource());
r->name = name;
}
r->originalFilename = originalFileName;
r->data = data;
r->drawable.reset();
changed();
}
bool BinaryResources::reload (const int index)
{
return resources[index] != nullptr
&& add (resources [index]->name,
File (resources [index]->originalFilename));
}
void BinaryResources::browseForResource (const String& title,
const String& wildcard,
const File& fileToStartFrom,
const String& resourceToReplace,
std::function<void (String)> callback)
{
chooser = std::make_unique<FileChooser> (title, fileToStartFrom, wildcard);
auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles;
chooser->launchAsync (flags, [this, resourceToReplace, callback] (const FileChooser& fc)
{
if (fc.getResult() == File{})
callback ({});
String name (resourceToReplace);
if (name.isEmpty())
name = findUniqueName (fc.getResult().getFileName());
if (! add (name, fc.getResult()))
{
AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
TRANS("Adding Resource"),
TRANS("Failed to load the file!"));
name.clear();
}
callback (name);
});
}
String BinaryResources::findUniqueName (const String& rootName) const
{
auto nameRoot = build_tools::makeValidIdentifier (rootName, true, true, false);
auto name = nameRoot;
auto names = getResourceNames();
int suffix = 1;
while (names.contains (name))
name = nameRoot + String (++suffix);
return name;
}
void BinaryResources::remove (int i)
{
if (resources[i] != nullptr)
{
resources.remove (i);
changed();
}
}
const Drawable* BinaryResources::getDrawable (const String& name) const
{
if (auto* res = const_cast<BinaryResources::BinaryResource*> (getResource (name)))
{
if (res->drawable == nullptr && res->data.getSize() > 0)
res->drawable = Drawable::createFromImageData (res->data.getData(),
res->data.getSize());
return res->drawable.get();
}
return nullptr;
}
Image BinaryResources::getImageFromCache (const String& name) const
{
if (auto* res = getResource (name))
if (res->data.getSize() > 0)
return ImageCache::getFromMemory (res->data.getData(), (int) res->data.getSize());
return {};
}
void BinaryResources::loadFromCpp (const File& cppFileLocation, const String& cppFile)
{
StringArray cpp;
cpp.addLines (cppFile);
clear();
for (int i = 0; i < cpp.size(); ++i)
{
if (cpp[i].contains ("JUCER_RESOURCE:"))
{
StringArray tokens;
tokens.addTokens (cpp[i].fromFirstOccurrenceOf (":", false, false), ",", "\"'");
tokens.trim();
tokens.removeEmptyStrings();
auto resourceName = tokens[0];
auto resourceSize = tokens[1].getIntValue();
auto originalFileName = cppFileLocation.getSiblingFile (tokens[2].unquoted()).getFullPathName();
jassert (resourceName.isNotEmpty() && resourceSize > 0);
if (resourceName.isNotEmpty() && resourceSize > 0)
{
auto firstLine = i;
while (i < cpp.size())
if (cpp [i++].contains ("}"))
break;
auto dataString = cpp.joinIntoString (" ", firstLine, i - firstLine)
.fromFirstOccurrenceOf ("{", false, false);
MemoryOutputStream out;
String::CharPointerType t (dataString.getCharPointer());
int n = 0;
while (! t.isEmpty())
{
auto c = t.getAndAdvance();
if (c >= '0' && c <= '9')
{
n = n * 10 + (int) (c - '0');
}
else if (c == ',')
{
out.writeByte ((char) n);
n = 0;
}
else if (c == '}')
{
break;
}
}
jassert (resourceSize < (int) out.getDataSize() && resourceSize > (int) out.getDataSize() - 2);
MemoryBlock mb (out.getData(), out.getDataSize());
mb.setSize ((size_t) resourceSize);
add (resourceName, originalFileName, mb);
}
}
}
}
//==============================================================================
void BinaryResources::fillInGeneratedCode (GeneratedCode& code) const
{
if (resources.size() > 0)
{
code.publicMemberDeclarations << "// Binary resources:\n";
MemoryOutputStream defs;
defs << "//==============================================================================\n";
defs << "// Binary resources - be careful not to edit any of these sections!\n\n";
for (auto* r : resources)
{
code.publicMemberDeclarations
<< "static const char* "
<< r->name
<< ";\nstatic const int "
<< r->name
<< "Size;\n";
auto name = r->name;
auto& mb = r->data;
defs << "// JUCER_RESOURCE: " << name << ", " << (int) mb.getSize()
<< ", \""
<< File (r->originalFilename)
.getRelativePathFrom (code.document->getCppFile())
.replaceCharacter ('\\', '/')
<< "\"\n";
String line1;
line1 << "static const unsigned char resource_"
<< code.className << "_" << name << "[] = { ";
defs << line1;
int charsOnLine = line1.length();
for (size_t j = 0; j < mb.getSize(); ++j)
{
auto num = (int) (unsigned char) mb[j];
defs << num << ',';
charsOnLine += 2;
if (num >= 10) ++charsOnLine;
if (num >= 100) ++charsOnLine;
if (charsOnLine >= 200)
{
charsOnLine = 0;
defs << '\n';
}
}
defs
<< "0,0};\n\n"
"const char* " << code.className << "::" << name
<< " = (const char*) resource_" << code.className << "_" << name
<< ";\nconst int "
<< code.className << "::" << name << "Size = "
<< (int) mb.getSize()
<< ";\n\n";
}
code.staticMemberDefinitions << defs.toString();
}
}

View File

@@ -0,0 +1,96 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
class JucerDocument;
//==============================================================================
/**
Manages a list of binary data objects that a JucerDocument wants to embed in
the code it generates.
*/
class BinaryResources
{
public:
//==============================================================================
BinaryResources& operator= (const BinaryResources& other);
void loadFromCpp (const File& cppFileLocation, const String& cpp);
//==============================================================================
struct BinaryResource
{
String name;
String originalFilename;
MemoryBlock data;
std::unique_ptr<Drawable> drawable;
};
void clear();
bool add (const String& name, const File& file);
void add (const String& name, const String& originalFileName, const MemoryBlock& data);
void remove (const int index);
bool reload (const int index);
void browseForResource (const String& title, const String& wildcard,
const File& fileToStartFrom, const String& resourceToReplace,
std::function<void (String)> callback);
String findUniqueName (const String& rootName) const;
int size() const noexcept { return resources.size(); }
const BinaryResource* operator[] (const int index) const noexcept { return resources [index]; }
const BinaryResource* getResource (const String& resourceName) const;
const BinaryResource* getResourceForFile (const File& file) const;
StringArray getResourceNames() const;
const Drawable* getDrawable (const String& name) const;
Image getImageFromCache (const String& name) const;
template <class ElementComparator>
void sort (ElementComparator& sorter)
{
resources.sort (sorter, true);
changed();
}
//==============================================================================
void setDocument (JucerDocument* const doc) { document = doc; }
JucerDocument* getDocument() const noexcept { return document; }
void fillInGeneratedCode (GeneratedCode& code) const;
private:
//==============================================================================
BinaryResource* findResource (const String& name) const noexcept;
void changed();
//==============================================================================
JucerDocument* document;
OwnedArray<BinaryResource> resources;
std::unique_ptr<FileChooser> chooser;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,138 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "Components/jucer_ComponentTypeHandler.h"
class JucerDocument;
//==============================================================================
/**
Manages the set of sub-components for a JucerDocument.
*/
class ComponentLayout
{
public:
//==============================================================================
ComponentLayout();
~ComponentLayout();
//==============================================================================
void changed();
int getNumComponents() const noexcept { return components.size(); }
Component* getComponent (const int index) const noexcept { return components [index]; }
int indexOfComponent (Component* const comp) const noexcept { return components.indexOf (comp); }
bool containsComponent (Component* const comp) const noexcept { return components.contains (comp); }
//==============================================================================
void clearComponents();
void removeComponent (Component* comp, const bool undoable);
Component* addNewComponent (ComponentTypeHandler* const type, int x, int y);
Component* addComponentFromXml (const XmlElement& xml, const bool undoable);
Component* findComponentWithId (const int64 componentId) const;
//==============================================================================
void componentToFront (Component* comp, const bool undoable);
void componentToBack (Component* comp, const bool undoable);
void setComponentPosition (Component* comp, const RelativePositionedRectangle& newPos, const bool undoable);
void setComponentBoundsAndProperties (Component* comp, const Rectangle<int>& newBounds, Component* referenceComponent, const bool undoable);
void updateStoredComponentPosition (Component* comp, const bool undoable);
//==============================================================================
Component* getComponentRelativePosTarget (Component* comp, int whichDimension) const;
void setComponentRelativeTarget (Component* comp, int whichDimension, Component* compToBeRelativeTo);
// checks recursively whether the comp depends on the given comp for its position
bool dependsOnComponentForRelativePos (Component* comp, Component* possibleDependee) const;
bool isComponentPositionRelative (Component* comp) const;
PopupMenu getRelativeTargetMenu (Component* comp, int whichDimension) const;
void processRelativeTargetMenuResult (Component* comp, int whichDimension, int menuResultID);
//==============================================================================
void setComponentMemberVariableName (Component* comp, const String& newName);
String getComponentMemberVariableName (Component* comp) const;
//==============================================================================
void setComponentVirtualClassName (Component* comp, const String& newName);
String getComponentVirtualClassName (Component* comp) const;
//==============================================================================
SelectedItemSet <Component*>& getSelectedSet() { return selected; }
static const char* const clipboardXmlTag;
void copySelectedToClipboard();
void paste();
void deleteSelected();
void selectAll();
void selectedToFront();
void selectedToBack();
void alignTop();
void alignRight();
void alignBottom();
void alignLeft();
void startDragging();
void dragSelectedComps (int dxFromDragStart, int dyFromDragStart, const bool allowSnap = true);
void endDragging();
void moveSelectedComps (int dx, int dy, bool snap);
void stretchSelectedComps (int dw, int dh, bool allowSnap);
void bringLostItemsBackOnScreen (int width, int height);
//==============================================================================
void setDocument (JucerDocument* const doc) { document = doc; }
JucerDocument* getDocument() const noexcept { return document; }
//==============================================================================
void addToXml (XmlElement& xml) const;
void fillInGeneratedCode (GeneratedCode& code) const;
void perform (UndoableAction* action, const String& actionName);
private:
JucerDocument* document;
OwnedArray<Component> components;
SelectedItemSet <Component*> selected;
int nextCompUID;
String getUnusedMemberName (String nameRoot, Component* comp) const;
friend class FrontBackCompAction;
friend class DeleteCompAction;
void moveComponentZOrder (int oldIndex, int newIndex);
};
void positionToCode (const RelativePositionedRectangle& position,
const ComponentLayout* layout,
String& x, String& y, String& w, String& h);

View File

@@ -0,0 +1,348 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../Application/jucer_Headers.h"
#include "jucer_GeneratedCode.h"
#include "jucer_JucerDocument.h"
//==============================================================================
GeneratedCode::GeneratedCode (const JucerDocument* const doc)
: document (doc), suffix (0)
{
}
GeneratedCode::~GeneratedCode()
{
}
int GeneratedCode::getUniqueSuffix()
{
return ++suffix;
}
//==============================================================================
String& GeneratedCode::getCallbackCode (const String& requiredParentClass,
const String& returnType,
const String& prototype,
const bool hasPrePostUserSections)
{
String parentClass (requiredParentClass);
if (parentClass.isNotEmpty()
&& ! (parentClass.startsWith ("public ")
|| parentClass.startsWith ("private ")
|| parentClass.startsWith ("protected ")))
{
parentClass = "public " + parentClass;
}
for (int i = callbacks.size(); --i >= 0;)
{
CallbackMethod* const cm = callbacks.getUnchecked(i);
if (cm->requiredParentClass == parentClass
&& cm->returnType == returnType
&& cm->prototype == prototype)
return cm->content;
}
CallbackMethod* const cm = new CallbackMethod();
callbacks.add (cm);
cm->requiredParentClass = parentClass;
cm->returnType = returnType;
cm->prototype = prototype;
cm->hasPrePostUserSections = hasPrePostUserSections;
return cm->content;
}
void GeneratedCode::removeCallback (const String& returnType, const String& prototype)
{
for (int i = callbacks.size(); --i >= 0;)
{
CallbackMethod* const cm = callbacks.getUnchecked(i);
if (cm->returnType == returnType && cm->prototype == prototype)
callbacks.remove (i);
}
}
void GeneratedCode::addImageResourceLoader (const String& imageMemberName, const String& resourceName)
{
privateMemberDeclarations
<< "juce::Image " << imageMemberName << ";\n";
if (resourceName.isNotEmpty())
constructorCode << imageMemberName << " = juce::ImageCache::getFromMemory ("
<< resourceName << ", " << resourceName << "Size);\n";
}
StringArray GeneratedCode::getExtraParentClasses() const
{
StringArray s;
for (int i = 0; i < callbacks.size(); ++i)
{
CallbackMethod* const cm = callbacks.getUnchecked(i);
s.add (cm->requiredParentClass);
}
return s;
}
String GeneratedCode::getCallbackDeclarations() const
{
String s;
for (int i = 0; i < callbacks.size(); ++i)
{
CallbackMethod* const cm = callbacks.getUnchecked(i);
s << cm->returnType << " " << cm->prototype << " override;\n";
}
return s;
}
String GeneratedCode::getCallbackDefinitions() const
{
String s;
for (int i = 0; i < callbacks.size(); ++i)
{
CallbackMethod* const cm = callbacks.getUnchecked(i);
const String userCodeBlockName ("User"
+ build_tools::makeValidIdentifier (cm->prototype.upToFirstOccurrenceOf ("(", false, false),
true, true, false).trim());
if (userCodeBlockName.isNotEmpty() && cm->hasPrePostUserSections)
{
s << cm->returnType << " " << className << "::" << cm->prototype
<< "\n{\n //[" << userCodeBlockName << "_Pre]\n //[/" << userCodeBlockName
<< "_Pre]\n\n "
<< CodeHelpers::indent (cm->content.trim(), 4, false)
<< "\n\n //[" << userCodeBlockName << "_Post]\n //[/" << userCodeBlockName
<< "_Post]\n}\n\n";
}
else
{
s << cm->returnType << " " << className << "::" << cm->prototype
<< "\n{\n "
<< CodeHelpers::indent (cm->content.trim(), 4, false)
<< "\n}\n\n";
}
}
return s;
}
//==============================================================================
String GeneratedCode::getClassDeclaration() const
{
StringArray parentClassLines;
parentClassLines.addTokens (parentClasses, ",", StringRef());
parentClassLines.addArray (getExtraParentClasses());
parentClassLines = getCleanedStringArray (parentClassLines);
if (parentClassLines.contains ("public juce::Button", false))
parentClassLines.removeString ("public juce::Component", false);
String r ("class ");
r << className << " : ";
r += parentClassLines.joinIntoString (",\n" + String::repeatedString (" ", r.length()));
return r;
}
String GeneratedCode::getInitialiserList() const
{
StringArray inits (initialisers);
if (parentClassInitialiser.isNotEmpty())
inits.insert (0, parentClassInitialiser);
inits = getCleanedStringArray (inits);
String s;
if (inits.size() == 0)
return s;
s << " : ";
for (int i = 0; i < inits.size(); ++i)
{
String init (inits[i]);
while (init.endsWithChar (','))
init = init.dropLastCharacters (1);
s << init;
if (i < inits.size() - 1)
s << ",\n ";
else
s << "\n";
}
return s;
}
static String getIncludeFileCode (const Array<File>& files, const File& targetFile)
{
String s;
for (int i = 0; i < files.size(); ++i)
s << CodeHelpers::createIncludeStatement (files.getReference(i), targetFile) << newLine;
return s;
}
bool GeneratedCode::shouldUseTransMacro() const noexcept
{
return document->shouldUseTransMacro();
}
//==============================================================================
static void replaceTemplate (String& text, const String& itemName, const String& value)
{
for (;;)
{
const int index = text.indexOf ("%%" + itemName + "%%");
if (index < 0)
break;
int indentLevel = 0;
for (int i = index; --i >= 0;)
{
if (text[i] == '\n')
break;
++indentLevel;
}
text = text.replaceSection (index, itemName.length() + 4,
CodeHelpers::indent (value, indentLevel, false));
}
}
//==============================================================================
static bool getUserSection (const StringArray& lines, const String& tag, StringArray& resultLines)
{
const int start = indexOfLineStartingWith (lines, "//[" + tag + "]", 0);
if (start < 0)
return false;
const int end = indexOfLineStartingWith (lines, "//[/" + tag + "]", start + 1);
for (int i = start + 1; i < end; ++i)
resultLines.add (lines [i]);
return true;
}
static void copyAcrossUserSections (String& dest, const String& src)
{
StringArray srcLines, dstLines;
srcLines.addLines (src);
dstLines.addLines (dest);
for (int i = 0; i < dstLines.size(); ++i)
{
if (dstLines[i].trimStart().startsWith ("//["))
{
String tag (dstLines[i].trimStart().substring (3));
tag = tag.upToFirstOccurrenceOf ("]", false, false);
jassert (! tag.startsWithChar ('/'));
if (! tag.startsWithChar ('/'))
{
const int endLine = indexOfLineStartingWith (dstLines,
"//[/" + tag + "]",
i + 1);
if (endLine > i)
{
StringArray sourceLines;
if (tag != "UserPaintCustomArguments" && getUserSection (srcLines, tag, sourceLines))
{
for (int j = endLine - i; --j > 0;)
dstLines.remove (i + 1);
for (int j = 0; j < sourceLines.size(); ++j)
dstLines.insert (++i, sourceLines [j].trimEnd());
++i;
}
else
{
i = endLine;
}
}
}
}
dstLines.set (i, dstLines[i].trimEnd());
}
dest = dstLines.joinIntoString ("\n") + "\n";
}
//==============================================================================
void GeneratedCode::applyToCode (String& code, const File& targetFile, const String& oldFileWithUserData) const
{
replaceTemplate (code, "version", JUCEApplicationBase::getInstance()->getApplicationVersion());
replaceTemplate (code, "creationTime", Time::getCurrentTime().toString (true, true, true));
replaceTemplate (code, "class_name", className);
replaceTemplate (code, "constructor_params", constructorParams);
replaceTemplate (code, "initialisers", getInitialiserList());
replaceTemplate (code, "class_declaration", getClassDeclaration());
replaceTemplate (code, "private_member_declarations", privateMemberDeclarations);
replaceTemplate (code, "public_member_declarations", getCallbackDeclarations() + newLine + publicMemberDeclarations);
replaceTemplate (code, "method_definitions", getCallbackDefinitions());
replaceTemplate (code, "include_juce", CodeHelpers::createIncludePathIncludeStatement (Project::getJuceSourceHFilename()));
replaceTemplate (code, "include_files_h", getIncludeFileCode (includeFilesH, targetFile));
replaceTemplate (code, "include_files_cpp", getIncludeFileCode (includeFilesCPP, targetFile));
replaceTemplate (code, "constructor", constructorCode);
replaceTemplate (code, "destructor", destructorCode);
replaceTemplate (code, "metadata", jucerMetadata);
replaceTemplate (code, "static_member_definitions", staticMemberDefinitions);
copyAcrossUserSections (code, oldFileWithUserData);
}

View File

@@ -0,0 +1,95 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../Project/jucer_Project.h"
class JucerDocument;
//==============================================================================
/**
A class for collecting the various snippets of c++ that will be assembled into
the final cpp and header files.
*/
class GeneratedCode
{
public:
GeneratedCode (const JucerDocument*);
~GeneratedCode();
//==============================================================================
void applyToCode (String& code, const File& targetFile, const String& oldFileWithUserData) const;
int getUniqueSuffix();
//==============================================================================
const JucerDocument* const document;
String className;
String componentName;
String parentClassInitialiser; // optional parent class initialiser to go before the items in the initialisers list
StringArray initialisers; // (a list of the member variables that need initialising after the constructor declaration)
String parentClasses;
String constructorParams;
String privateMemberDeclarations;
String publicMemberDeclarations;
Array<File> includeFilesH, includeFilesCPP;
String constructorCode;
String destructorCode;
String staticMemberDefinitions;
String jucerMetadata;
struct CallbackMethod
{
String requiredParentClass;
String returnType;
String prototype;
String content;
bool hasPrePostUserSections;
};
OwnedArray<CallbackMethod> callbacks;
String& getCallbackCode (const String& requiredParentClass,
const String& returnType,
const String& prototype,
const bool hasPrePostUserSections);
void removeCallback (const String& returnType, const String& prototype);
void addImageResourceLoader (const String& imageMemberName, const String& resourceName);
String getCallbackDeclarations() const;
String getCallbackDefinitions() const;
StringArray getExtraParentClasses() const;
bool shouldUseTransMacro() const noexcept;
private:
String getClassDeclaration() const;
String getInitialiserList() const;
int suffix;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GeneratedCode)
};

View File

@@ -0,0 +1,835 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../Application/jucer_Headers.h"
#include "../Application/jucer_Application.h"
#include "../Utility/Helpers/jucer_NewFileWizard.h"
#include "jucer_JucerDocument.h"
#include "jucer_ObjectTypes.h"
#include "UI/jucer_JucerDocumentEditor.h"
#include "UI/jucer_TestComponent.h"
#include "jucer_UtilityFunctions.h"
#include "Documents/jucer_ComponentDocument.h"
#include "Documents/jucer_ButtonDocument.h"
const char* const defaultClassName = "NewComponent";
const char* const defaultParentClasses = "public juce::Component";
//==============================================================================
JucerDocument::JucerDocument (SourceCodeDocument* c)
: cpp (c),
className (defaultClassName),
parentClasses (defaultParentClasses)
{
jassert (cpp != nullptr);
resources.setDocument (this);
ProjucerApplication::getCommandManager().commandStatusChanged();
cpp->getCodeDocument().addListener (this);
}
JucerDocument::~JucerDocument()
{
cpp->getCodeDocument().removeListener (this);
ProjucerApplication::getCommandManager().commandStatusChanged();
}
//==============================================================================
void JucerDocument::changed()
{
sendChangeMessage();
ProjucerApplication::getCommandManager().commandStatusChanged();
startTimer (800);
}
struct UserDocChangeTimer : public Timer
{
explicit UserDocChangeTimer (JucerDocument& d) : doc (d) {}
void timerCallback() override { doc.reloadFromDocument(); }
JucerDocument& doc;
};
void JucerDocument::userEditedCpp()
{
if (userDocChangeTimer == nullptr)
userDocChangeTimer.reset (new UserDocChangeTimer (*this));
userDocChangeTimer->startTimer (500);
}
void JucerDocument::beginTransaction()
{
getUndoManager().beginNewTransaction();
}
void JucerDocument::beginTransaction (const String& name)
{
getUndoManager().beginNewTransaction (name);
}
void JucerDocument::timerCallback()
{
if (! Component::isMouseButtonDownAnywhere())
{
stopTimer();
beginTransaction();
flushChangesToDocuments (nullptr, false);
}
}
void JucerDocument::codeDocumentTextInserted (const String&, int) { userEditedCpp(); }
void JucerDocument::codeDocumentTextDeleted (int, int) { userEditedCpp(); }
bool JucerDocument::perform (UndoableAction* const action, const String& actionName)
{
return undoManager.perform (action, actionName);
}
void JucerDocument::refreshAllPropertyComps()
{
if (ComponentLayout* l = getComponentLayout())
l->getSelectedSet().changed();
for (int i = getNumPaintRoutines(); --i >= 0;)
{
getPaintRoutine (i)->getSelectedElements().changed();
getPaintRoutine (i)->getSelectedPoints().changed();
}
}
//==============================================================================
void JucerDocument::setClassName (const String& newName)
{
if (newName != className
&& build_tools::makeValidIdentifier (newName, false, false, true).isNotEmpty())
{
className = build_tools::makeValidIdentifier (newName, false, false, true);
changed();
}
}
void JucerDocument::setComponentName (const String& newName)
{
if (newName != componentName)
{
componentName = newName;
changed();
}
}
void JucerDocument::setParentClasses (const String& classes)
{
if (classes != parentClasses)
{
StringArray parentClassLines (getCleanedStringArray (StringArray::fromTokens (classes, ",", StringRef())));
for (int i = parentClassLines.size(); --i >= 0;)
{
String s (parentClassLines[i]);
String type;
if (s.startsWith ("public ")
|| s.startsWith ("protected ")
|| s.startsWith ("private "))
{
type = s.upToFirstOccurrenceOf (" ", true, false);
s = s.fromFirstOccurrenceOf (" ", false, false);
if (s.trim().isEmpty())
type = s = String();
}
s = type + build_tools::makeValidIdentifier (s.trim(), false, false, true, true);
parentClassLines.set (i, s);
}
parentClasses = parentClassLines.joinIntoString (", ");
changed();
}
}
void JucerDocument::setConstructorParams (const String& newParams)
{
if (constructorParams != newParams)
{
constructorParams = newParams;
changed();
}
}
void JucerDocument::setVariableInitialisers (const String& newInitlialisers)
{
if (variableInitialisers != newInitlialisers)
{
variableInitialisers = newInitlialisers;
changed();
}
}
void JucerDocument::setFixedSize (const bool isFixed)
{
if (fixedSize != isFixed)
{
fixedSize = isFixed;
changed();
}
}
void JucerDocument::setInitialSize (int w, int h)
{
w = jmax (1, w);
h = jmax (1, h);
if (initialWidth != w || initialHeight != h)
{
initialWidth = w;
initialHeight = h;
changed();
}
}
//==============================================================================
bool JucerDocument::isSnapActive (const bool disableIfCtrlKeyDown) const noexcept
{
return snapActive != (disableIfCtrlKeyDown && ModifierKeys::currentModifiers.isCtrlDown());
}
int JucerDocument::snapPosition (int pos) const noexcept
{
if (isSnapActive (true))
{
jassert (snapGridPixels > 0);
pos = ((pos + snapGridPixels * 1024 + snapGridPixels / 2) / snapGridPixels - 1024) * snapGridPixels;
}
return pos;
}
void JucerDocument::setSnappingGrid (const int numPixels, const bool active, const bool shown)
{
if (numPixels != snapGridPixels
|| active != snapActive
|| shown != snapShown)
{
snapGridPixels = numPixels;
snapActive = active;
snapShown = shown;
changed();
}
}
void JucerDocument::setComponentOverlayOpacity (const float alpha)
{
if (alpha != componentOverlayOpacity)
{
componentOverlayOpacity = alpha;
changed();
}
}
//==============================================================================
void JucerDocument::addMethod (const String& base, const String& returnVal, const String& method, const String& initialContent,
StringArray& baseClasses, StringArray& returnValues, StringArray& methods, StringArray& initialContents)
{
baseClasses.add (base);
returnValues.add (returnVal);
methods.add (method);
initialContents.add (initialContent);
}
void JucerDocument::getOptionalMethods (StringArray& baseClasses,
StringArray& returnValues,
StringArray& methods,
StringArray& initialContents) const
{
addMethod ("juce::Component", "void", "visibilityChanged()", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "moved()", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "parentHierarchyChanged()", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "parentSizeChanged()", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "lookAndFeelChanged()", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "bool", "hitTest (int x, int y)", "return true;", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "broughtToFront()", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "filesDropped (const juce::StringArray& filenames, int mouseX, int mouseY)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "handleCommandMessage (int commandId)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "childrenChanged()", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "enablementChanged()", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "mouseMove (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "mouseEnter (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "mouseExit (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "mouseDown (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "mouseDrag (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "mouseUp (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "mouseDoubleClick (const juce::MouseEvent& e)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "mouseWheelMove (const juce::MouseEvent& e, const juce::MouseWheelDetails& wheel)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "bool", "keyPressed (const juce::KeyPress& key)", "return false; // Return true if your handler uses this key event, or false to allow it to be passed-on.", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "bool", "keyStateChanged (bool isKeyDown)", "return false; // Return true if your handler uses this key event, or false to allow it to be passed-on.", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "modifierKeysChanged (const juce::ModifierKeys& modifiers)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "focusGained (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "focusLost (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "focusOfChildComponentChanged (FocusChangeType cause)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "modifierKeysChanged (const juce::ModifierKeys& modifiers)", "", baseClasses, returnValues, methods, initialContents);
addMethod ("juce::Component", "void", "inputAttemptWhenModal()", "", baseClasses, returnValues, methods, initialContents);
}
void JucerDocument::setOptionalMethodEnabled (const String& methodSignature, const bool enable)
{
if (enable)
activeExtraMethods.addIfNotAlreadyThere (methodSignature);
else
activeExtraMethods.removeString (methodSignature);
changed();
}
bool JucerDocument::isOptionalMethodEnabled (const String& sig) const noexcept
{
return activeExtraMethods.contains (sig)
|| activeExtraMethods.contains (sig.replace ("juce::", {}));
}
void JucerDocument::addExtraClassProperties (PropertyPanel&)
{
}
//==============================================================================
const char* const JucerDocument::jucerCompXmlTag = "JUCER_COMPONENT";
std::unique_ptr<XmlElement> JucerDocument::createXml() const
{
auto doc = std::make_unique<XmlElement> (jucerCompXmlTag);
doc->setAttribute ("documentType", getTypeName());
doc->setAttribute ("className", className);
if (templateFile.trim().isNotEmpty())
doc->setAttribute ("template", templateFile);
doc->setAttribute ("componentName", componentName);
doc->setAttribute ("parentClasses", parentClasses);
doc->setAttribute ("constructorParams", constructorParams);
doc->setAttribute ("variableInitialisers", variableInitialisers);
doc->setAttribute ("snapPixels", snapGridPixels);
doc->setAttribute ("snapActive", snapActive);
doc->setAttribute ("snapShown", snapShown);
doc->setAttribute ("overlayOpacity", String (componentOverlayOpacity, 3));
doc->setAttribute ("fixedSize", fixedSize);
doc->setAttribute ("initialWidth", initialWidth);
doc->setAttribute ("initialHeight", initialHeight);
if (activeExtraMethods.size() > 0)
{
XmlElement* extraMethods = new XmlElement ("METHODS");
doc->addChildElement (extraMethods);
for (int i = 0; i < activeExtraMethods.size(); ++i)
{
XmlElement* e = new XmlElement ("METHOD");
extraMethods ->addChildElement (e);
e->setAttribute ("name", activeExtraMethods[i]);
}
}
return doc;
}
bool JucerDocument::loadFromXml (const XmlElement& xml)
{
if (xml.hasTagName (jucerCompXmlTag)
&& getTypeName().equalsIgnoreCase (xml.getStringAttribute ("documentType")))
{
className = xml.getStringAttribute ("className", defaultClassName);
templateFile = xml.getStringAttribute ("template", String());
componentName = xml.getStringAttribute ("componentName", String());
parentClasses = xml.getStringAttribute ("parentClasses", defaultParentClasses);
constructorParams = xml.getStringAttribute ("constructorParams", String());
variableInitialisers = xml.getStringAttribute ("variableInitialisers", String());
fixedSize = xml.getBoolAttribute ("fixedSize", false);
initialWidth = xml.getIntAttribute ("initialWidth", 300);
initialHeight = xml.getIntAttribute ("initialHeight", 200);
snapGridPixels = xml.getIntAttribute ("snapPixels", snapGridPixels);
snapActive = xml.getBoolAttribute ("snapActive", snapActive);
snapShown = xml.getBoolAttribute ("snapShown", snapShown);
componentOverlayOpacity = (float) xml.getDoubleAttribute ("overlayOpacity", 0.0);
activeExtraMethods.clear();
if (XmlElement* const methods = xml.getChildByName ("METHODS"))
for (auto* e : methods->getChildWithTagNameIterator ("METHOD"))
activeExtraMethods.addIfNotAlreadyThere (e->getStringAttribute ("name"));
activeExtraMethods.trim();
activeExtraMethods.removeEmptyStrings();
changed();
getUndoManager().clearUndoHistory();
return true;
}
return false;
}
//==============================================================================
void JucerDocument::fillInGeneratedCode (GeneratedCode& code) const
{
code.className = className;
code.componentName = componentName;
code.parentClasses = parentClasses;
code.constructorParams = constructorParams;
code.initialisers.addLines (variableInitialisers);
if (! componentName.isEmpty())
code.constructorCode << "setName (" + quotedString (componentName, false) + ");\n";
// call these now, just to make sure they're the first two methods in the list.
code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false)
<< "//[UserPrePaint] Add your own custom painting code here..\n//[/UserPrePaint]\n\n";
code.getCallbackCode (String(), "void", "resized()", false)
<< "//[UserPreResize] Add your own custom resize code here..\n//[/UserPreResize]\n\n";
if (ComponentLayout* l = getComponentLayout())
l->fillInGeneratedCode (code);
fillInPaintCode (code);
std::unique_ptr<XmlElement> e (createXml());
jassert (e != nullptr);
code.jucerMetadata = e->toString (XmlElement::TextFormat().withoutHeader());
resources.fillInGeneratedCode (code);
code.constructorCode
<< "\n//[UserPreSize]\n"
"//[/UserPreSize]\n";
if (initialWidth > 0 || initialHeight > 0)
code.constructorCode << "\nsetSize (" << initialWidth << ", " << initialHeight << ");\n";
code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false)
<< "//[UserPaint] Add your own custom painting code here..\n//[/UserPaint]";
code.getCallbackCode (String(), "void", "resized()", false)
<< "//[UserResized] Add your own custom resize handling here..\n//[/UserResized]";
// add optional methods
StringArray baseClasses, returnValues, methods, initialContents;
getOptionalMethods (baseClasses, returnValues, methods, initialContents);
for (int i = 0; i < methods.size(); ++i)
{
if (isOptionalMethodEnabled (methods[i]))
{
String baseClassToAdd (baseClasses[i]);
if (baseClassToAdd == "juce::Component" || baseClassToAdd == "juce::Button")
baseClassToAdd.clear();
String& s = code.getCallbackCode (baseClassToAdd, returnValues[i], methods[i], false);
if (! s.contains ("//["))
{
String userCommentTag ("UserCode_");
userCommentTag += methods[i].upToFirstOccurrenceOf ("(", false, false).trim();
s << "\n//[" << userCommentTag << "] -- Add your code here...\n"
<< initialContents[i];
if (initialContents[i].isNotEmpty() && ! initialContents[i].endsWithChar ('\n'))
s << '\n';
s << "//[/" << userCommentTag << "]\n";
}
}
}
}
void JucerDocument::fillInPaintCode (GeneratedCode& code) const
{
for (int i = 0; i < getNumPaintRoutines(); ++i)
getPaintRoutine (i)
->fillInGeneratedCode (code, code.getCallbackCode (String(), "void", "paint (juce::Graphics& g)", false));
}
void JucerDocument::setTemplateFile (const String& newFile)
{
if (templateFile != newFile)
{
templateFile = newFile;
changed();
}
}
//==============================================================================
bool JucerDocument::findTemplateFiles (String& headerContent, String& cppContent) const
{
if (templateFile.isNotEmpty())
{
const File f (getCppFile().getSiblingFile (templateFile));
const File templateCpp (f.withFileExtension (".cpp"));
const File templateH (f.withFileExtension (".h"));
headerContent = templateH.loadFileAsString();
cppContent = templateCpp.loadFileAsString();
if (headerContent.isNotEmpty() && cppContent.isNotEmpty())
return true;
}
headerContent = BinaryData::jucer_ComponentTemplate_h;
cppContent = BinaryData::jucer_ComponentTemplate_cpp;
return true;
}
bool JucerDocument::flushChangesToDocuments (Project* project, bool isInitial)
{
String headerTemplate, cppTemplate;
if (! findTemplateFiles (headerTemplate, cppTemplate))
return false;
GeneratedCode generated (this);
fillInGeneratedCode (generated);
const File headerFile (getHeaderFile());
generated.includeFilesCPP.insert (0, headerFile);
OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
if (SourceCodeDocument* header = dynamic_cast<SourceCodeDocument*> (odm.openFile (nullptr, headerFile)))
{
String existingHeader (header->getCodeDocument().getAllContent());
String existingCpp (cpp->getCodeDocument().getAllContent());
generated.applyToCode (headerTemplate, headerFile, existingHeader);
generated.applyToCode (cppTemplate, headerFile.withFileExtension (".cpp"), existingCpp);
if (isInitial)
{
jassert (project != nullptr);
auto lineFeed = project->getProjectLineFeed();
headerTemplate = replaceLineFeeds (headerTemplate, lineFeed);
cppTemplate = replaceLineFeeds (cppTemplate, lineFeed);
}
else
{
headerTemplate = replaceLineFeeds (headerTemplate, getLineFeedForFile (existingHeader));
cppTemplate = replaceLineFeeds (cppTemplate, getLineFeedForFile (existingCpp));
}
if (header->getCodeDocument().getAllContent() != headerTemplate)
header->getCodeDocument().replaceAllContent (headerTemplate);
if (cpp->getCodeDocument().getAllContent() != cppTemplate)
cpp->getCodeDocument().replaceAllContent (cppTemplate);
}
userDocChangeTimer.reset();
return true;
}
bool JucerDocument::reloadFromDocument()
{
const String cppContent (cpp->getCodeDocument().getAllContent());
std::unique_ptr<XmlElement> newXML (pullMetaDataFromCppFile (cppContent));
if (newXML == nullptr || ! newXML->hasTagName (jucerCompXmlTag))
return false;
if (currentXML != nullptr && currentXML->isEquivalentTo (newXML.get(), true))
return true;
currentXML.reset (newXML.release());
stopTimer();
resources.loadFromCpp (getCppFile(), cppContent);
bool result = loadFromXml (*currentXML);
extractCustomPaintSnippetsFromCppFile (cppContent);
return result;
}
void JucerDocument::refreshCustomCodeFromDocument()
{
const String cppContent (cpp->getCodeDocument().getAllContent());
extractCustomPaintSnippetsFromCppFile (cppContent);
}
void JucerDocument::extractCustomPaintSnippetsFromCppFile (const String& cppContent)
{
StringArray customPaintSnippets;
auto lines = StringArray::fromLines (cppContent);
int last = 0;
while (last >= 0)
{
const int start = indexOfLineStartingWith (lines, "//[UserPaintCustomArguments]", last);
if (start < 0)
break;
const int end = indexOfLineStartingWith (lines, "//[/UserPaintCustomArguments]", start);
if (end < 0)
break;
last = end + 1;
String result;
for (int i = start + 1; i < end; ++i)
result << lines [i] << newLine;
customPaintSnippets.add (CodeHelpers::unindent (result, 4));
}
applyCustomPaintSnippets (customPaintSnippets);
}
std::unique_ptr<XmlElement> JucerDocument::pullMetaDataFromCppFile (const String& cpp)
{
auto lines = StringArray::fromLines (cpp);
auto startLine = indexOfLineStartingWith (lines, "BEGIN_JUCER_METADATA", 0);
if (startLine > 0)
{
auto endLine = indexOfLineStartingWith (lines, "END_JUCER_METADATA", startLine);
if (endLine > startLine)
return parseXML (lines.joinIntoString ("\n", startLine + 1, endLine - startLine - 1));
}
return nullptr;
}
bool JucerDocument::isValidJucerCppFile (const File& f)
{
if (f.hasFileExtension (cppFileExtensions))
{
std::unique_ptr<XmlElement> xml (pullMetaDataFromCppFile (f.loadFileAsString()));
if (xml != nullptr)
return xml->hasTagName (jucerCompXmlTag);
}
return false;
}
static JucerDocument* createDocument (SourceCodeDocument* cpp)
{
auto& codeDoc = cpp->getCodeDocument();
std::unique_ptr<XmlElement> xml (JucerDocument::pullMetaDataFromCppFile (codeDoc.getAllContent()));
if (xml == nullptr || ! xml->hasTagName (JucerDocument::jucerCompXmlTag))
return nullptr;
const String docType (xml->getStringAttribute ("documentType"));
std::unique_ptr<JucerDocument> newDoc;
if (docType.equalsIgnoreCase ("Button"))
newDoc.reset (new ButtonDocument (cpp));
if (docType.equalsIgnoreCase ("Component") || docType.isEmpty())
newDoc.reset (new ComponentDocument (cpp));
if (newDoc != nullptr && newDoc->reloadFromDocument())
return newDoc.release();
return nullptr;
}
JucerDocument* JucerDocument::createForCppFile (Project* p, const File& file)
{
OpenDocumentManager& odm = ProjucerApplication::getApp().openDocumentManager;
if (SourceCodeDocument* cpp = dynamic_cast<SourceCodeDocument*> (odm.openFile (p, file)))
if (dynamic_cast<SourceCodeDocument*> (odm.openFile (p, file.withFileExtension (".h"))) != nullptr)
return createDocument (cpp);
return nullptr;
}
//==============================================================================
class JucerComponentDocument : public SourceCodeDocument
{
public:
JucerComponentDocument (Project* p, const File& f)
: SourceCodeDocument (p, f)
{
}
void saveAsync (std::function<void (bool)> callback) override
{
SourceCodeDocument::saveAsync ([parent = WeakReference<JucerComponentDocument> { this }, callback] (bool saveResult)
{
if (parent == nullptr)
return;
if (! saveResult)
{
callback (false);
return;
}
parent->saveHeaderAsync ([parent, callback] (bool headerSaveResult)
{
if (parent != nullptr)
callback (headerSaveResult);
});
});
}
void saveHeaderAsync (std::function<void (bool)> callback)
{
auto& odm = ProjucerApplication::getApp().openDocumentManager;
if (auto* header = odm.openFile (nullptr, getFile().withFileExtension (".h")))
{
header->saveAsync ([parent = WeakReference<JucerComponentDocument> { this }, callback] (bool saveResult)
{
if (parent == nullptr)
return;
if (saveResult)
ProjucerApplication::getApp()
.openDocumentManager
.closeFileWithoutSaving (parent->getFile().withFileExtension (".h"));
callback (saveResult);
});
return;
}
callback (false);
}
std::unique_ptr<Component> createEditor() override
{
if (ProjucerApplication::getApp().isGUIEditorEnabled())
{
std::unique_ptr<JucerDocument> jucerDoc (JucerDocument::createForCppFile (getProject(), getFile()));
if (jucerDoc != nullptr)
return std::make_unique<JucerDocumentEditor> (jucerDoc.release());
}
return SourceCodeDocument::createEditor();
}
struct Type : public OpenDocumentManager::DocumentType
{
Type() {}
bool canOpenFile (const File& f) override { return JucerDocument::isValidJucerCppFile (f); }
Document* openFile (Project* p, const File& f) override { return new JucerComponentDocument (p, f); }
};
JUCE_DECLARE_WEAK_REFERENCEABLE (JucerComponentDocument)
};
OpenDocumentManager::DocumentType* createGUIDocumentType();
OpenDocumentManager::DocumentType* createGUIDocumentType()
{
return new JucerComponentDocument::Type();
}
//==============================================================================
struct NewGUIComponentWizard : public NewFileWizard::Type
{
NewGUIComponentWizard (Project& proj)
: project (proj)
{}
String getName() override { return "GUI Component"; }
void createNewFile (Project& p, Project::Item parent) override
{
jassert (&p == &project);
askUserToChooseNewFile (String (defaultClassName) + ".h", "*.h;*.cpp", parent, [this, parent] (File newFile) mutable
{
if (newFile != File())
{
auto headerFile = newFile.withFileExtension (".h");
auto cppFile = newFile.withFileExtension (".cpp");
headerFile.replaceWithText (String());
cppFile.replaceWithText (String());
auto& odm = ProjucerApplication::getApp().openDocumentManager;
if (auto* cpp = dynamic_cast<SourceCodeDocument*> (odm.openFile (&project, cppFile)))
{
if (auto* header = dynamic_cast<SourceCodeDocument*> (odm.openFile (&project, headerFile)))
{
std::unique_ptr<JucerDocument> jucerDoc (new ComponentDocument (cpp));
if (jucerDoc != nullptr)
{
jucerDoc->setClassName (newFile.getFileNameWithoutExtension());
jucerDoc->flushChangesToDocuments (&project, true);
jucerDoc.reset();
for (auto* doc : { cpp, header })
{
doc->saveAsync ([doc] (bool)
{
ProjucerApplication::getApp()
.openDocumentManager
.closeDocumentAsync (doc, OpenDocumentManager::SaveIfNeeded::yes, nullptr);
});
}
parent.addFileRetainingSortOrder (headerFile, true);
parent.addFileRetainingSortOrder (cppFile, true);
}
}
}
}
});
}
Project& project;
};
NewFileWizard::Type* createGUIComponentWizard (Project&);
NewFileWizard::Type* createGUIComponentWizard (Project& p)
{
return new NewGUIComponentWizard (p);
}

View File

@@ -0,0 +1,180 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "../CodeEditor/jucer_OpenDocumentManager.h"
#include "../CodeEditor/jucer_SourceCodeEditor.h"
#include "Components/jucer_ComponentTypeHandler.h"
#include "jucer_PaintRoutine.h"
#include "jucer_ComponentLayout.h"
#include "jucer_BinaryResources.h"
//==============================================================================
class JucerDocument : public ChangeBroadcaster,
private Timer,
private CodeDocument::Listener
{
public:
JucerDocument (SourceCodeDocument* cpp);
~JucerDocument() override;
static bool isValidJucerCppFile (const File&);
static std::unique_ptr<XmlElement> pullMetaDataFromCppFile (const String& cpp);
static JucerDocument* createForCppFile (Project*, const File&);
void changed();
void beginTransaction();
void beginTransaction (const String& name);
virtual JucerDocument* createCopy() = 0;
virtual String getTypeName() const = 0;
SourceCodeDocument& getCppDocument() const { return *cpp; }
File getCppFile() const { return cpp->getFile(); }
File getHeaderFile() const { return getCppFile().withFileExtension (".h"); }
bool flushChangesToDocuments (Project*, bool);
bool reloadFromDocument();
//==============================================================================
UndoManager& getUndoManager() noexcept { return undoManager; }
bool perform (UndoableAction* const action, const String& actionName);
void refreshAllPropertyComps();
//==============================================================================
const String& getClassName() const noexcept { return className; }
void setClassName (const String& newName);
const String& getComponentName() const noexcept { return componentName; }
void setComponentName (const String& newName);
String getParentClassString() const { return parentClasses; }
void setParentClasses (const String& classes);
String getConstructorParams() const { return constructorParams; }
void setConstructorParams (const String& newParams);
String getVariableInitialisers() const { return variableInitialisers; }
void setVariableInitialisers (const String& newInitlialisers);
void setFixedSize (const bool isFixed);
bool isFixedSize() const noexcept { return fixedSize; }
void setInitialSize (int w, int h);
int getInitialWidth() const noexcept { return initialWidth; }
int getInitialHeight() const noexcept { return initialHeight; }
//==============================================================================
virtual int getNumPaintRoutines() const = 0;
virtual StringArray getPaintRoutineNames() const = 0;
virtual PaintRoutine* getPaintRoutine (const int index) const = 0;
virtual ComponentLayout* getComponentLayout() const = 0;
virtual Component* createTestComponent (const bool alwaysFillBackground) = 0;
virtual void addExtraClassProperties (PropertyPanel&);
//==============================================================================
virtual void getOptionalMethods (StringArray& baseClasses,
StringArray& returnValues,
StringArray& methods,
StringArray& initialContents) const;
void setOptionalMethodEnabled (const String& methodSignature, const bool enable);
bool isOptionalMethodEnabled (const String& methodSignature) const noexcept;
//==============================================================================
BinaryResources& getResources() noexcept { return resources; }
//==============================================================================
void setSnappingGrid (const int numPixels, const bool active, const bool shown);
int getSnappingGridSize() const noexcept { return snapGridPixels; }
bool isSnapActive (const bool disableIfCtrlKeyDown) const noexcept;
bool isSnapShown() const noexcept { return snapShown; }
int snapPosition (int pos) const noexcept;
//==============================================================================
void setComponentOverlayOpacity (const float alpha);
float getComponentOverlayOpacity() const noexcept { return componentOverlayOpacity; }
//==============================================================================
static const char* const jucerCompXmlTag;
bool findTemplateFiles (String& templateH, String& templateCpp) const;
String getTemplateFile() const { return templateFile; }
void setTemplateFile (const String&);
static bool shouldUseTransMacro() noexcept { return true; }
//==============================================================================
void refreshCustomCodeFromDocument();
protected:
SourceCodeDocument* cpp;
String className, componentName, templateFile;
String parentClasses, constructorParams, variableInitialisers;
bool fixedSize = false;
int initialWidth = 600, initialHeight = 400;
BinaryResources resources;
virtual std::unique_ptr<XmlElement> createXml() const;
virtual bool loadFromXml (const XmlElement&);
virtual void fillInGeneratedCode (GeneratedCode&) const;
virtual void fillInPaintCode (GeneratedCode&) const;
virtual void applyCustomPaintSnippets (StringArray&) {}
static void addMethod (const String& base, const String& returnVal,
const String& method, const String& initialContent,
StringArray& baseClasses, StringArray& returnValues,
StringArray& methods, StringArray& initialContents);
private:
UndoManager undoManager;
int snapGridPixels = 8;
bool snapActive = true, snapShown = true;
float componentOverlayOpacity = 0.33f;
StringArray activeExtraMethods;
std::unique_ptr<XmlElement> currentXML;
std::unique_ptr<Timer> userDocChangeTimer;
void timerCallback() override;
void codeDocumentTextInserted (const String& newText, int insertIndex) override;
void codeDocumentTextDeleted (int startIndex, int endIndex) override;
void userEditedCpp();
void extractCustomPaintSnippetsFromCppFile (const String& cpp);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JucerDocument)
};

View File

@@ -0,0 +1,163 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../Application/jucer_Headers.h"
#include "jucer_JucerDocument.h"
#include "Components/jucer_ComponentUndoableAction.h"
#include "Properties/jucer_JustificationProperty.h"
#include "Properties/jucer_FontPropertyComponent.h"
#include "Properties/jucer_ComponentBooleanProperty.h"
#include "Properties/jucer_ComponentChoiceProperty.h"
#include "Properties/jucer_ComponentTextProperty.h"
#include "Properties/jucer_ComponentColourProperty.h"
#include "Properties/jucer_FilePropertyComponent.h"
#include "PaintElements/jucer_ImageResourceProperty.h"
#include "jucer_ObjectTypes.h"
#include "PaintElements/jucer_PaintElementUndoableAction.h"
#include "PaintElements/jucer_PaintElementRectangle.h"
#include "PaintElements/jucer_PaintElementRoundedRectangle.h"
#include "PaintElements/jucer_PaintElementImage.h"
#include "PaintElements/jucer_PaintElementEllipse.h"
#include "PaintElements/jucer_PaintElementPath.h"
#include "PaintElements/jucer_PaintElementText.h"
#include "PaintElements/jucer_PaintElementGroup.h"
#include "Components/jucer_ButtonHandler.h"
#include "Components/jucer_TextButtonHandler.h"
#include "Components/jucer_ToggleButtonHandler.h"
#include "Components/jucer_SliderHandler.h"
#include "Components/jucer_LabelHandler.h"
#include "Components/jucer_TextEditorHandler.h"
#include "Components/jucer_ComboBoxHandler.h"
#include "Components/jucer_GroupComponentHandler.h"
#include "Components/jucer_JucerComponentHandler.h"
#include "Components/jucer_HyperlinkButtonHandler.h"
#include "Components/jucer_ViewportHandler.h"
#include "Components/jucer_TabbedComponentHandler.h"
#include "Components/jucer_TreeViewHandler.h"
#include "Components/jucer_GenericComponentHandler.h"
#include "Components/jucer_ImageButtonHandler.h"
namespace ObjectTypes
{
static const char* const elementNames[] =
{
"Rectangle",
"Rounded Rectangle",
"Ellipse",
"Path",
"Image",
"Text",
nullptr
};
const char* const* const elementTypeNames = (const char* const*) elementNames;
const int numElementTypes = (sizeof (elementNames) / sizeof (elementNames[0])) - 1;
PaintElement* createNewElement (const int index, PaintRoutine* owner)
{
switch (index)
{
case 0: return new PaintElementRectangle (owner);
case 1: return new PaintElementRoundedRectangle (owner);
case 2: return new PaintElementEllipse (owner);
case 3: return new PaintElementPath (owner);
case 4: return new PaintElementImage (owner);
case 5: return new PaintElementText (owner);
default: jassertfalse; break;
}
return nullptr;
}
PaintElement* createNewImageElement (PaintRoutine* owner)
{
return new PaintElementImage (owner);
}
PaintElement* createElementForXml (const XmlElement* const e, PaintRoutine* const owner)
{
jassert (e != nullptr);
std::unique_ptr<PaintElement> pe;
if (e->hasTagName (PaintElementRectangle::getTagName())) pe.reset (new PaintElementRectangle (owner));
else if (e->hasTagName (PaintElementRoundedRectangle::getTagName())) pe.reset (new PaintElementRoundedRectangle (owner));
else if (e->hasTagName (PaintElementEllipse::getTagName())) pe.reset (new PaintElementEllipse (owner));
else if (e->hasTagName (PaintElementImage::getTagName())) pe.reset (new PaintElementImage (owner));
else if (e->hasTagName (PaintElementPath::getTagName())) pe.reset (new PaintElementPath (owner));
else if (e->hasTagName (PaintElementText::getTagName())) pe.reset (new PaintElementText (owner));
else if (e->hasTagName (PaintElementGroup::getTagName())) pe.reset (new PaintElementGroup (owner));
if (pe != nullptr && pe->loadFromXml (*e))
return pe.release();
jassertfalse;
return nullptr;
}
//==============================================================================
static TextButtonHandler textButton;
static ToggleButtonHandler toggleButton;
static SliderHandler slider;
static LabelHandler label;
static TextEditorHandler textEditor;
static ComboBoxHandler comboBox;
static JucerComponentHandler jucerCompHandler;
static GroupComponentHandler group;
static HyperlinkButtonHandler hyperlink;
static ViewportHandler viewport;
static TabbedComponentHandler tabbedComp;
static TreeViewHandler treeview;
static GenericComponentHandler genericHandler;
static ImageButtonHandler imageButtonHandler;
static ComponentTypeHandler* const compTypes[] =
{
&textButton,
&toggleButton,
&slider,
&label,
&textEditor,
&comboBox,
&group,
&jucerCompHandler,
&hyperlink,
&viewport,
&tabbedComp,
&treeview,
&genericHandler,
&imageButtonHandler,
nullptr
};
ComponentTypeHandler* const* const componentTypeHandlers = (ComponentTypeHandler* const*) compTypes;
const int numComponentTypes = numElementsInArray (compTypes) - 1;
}

View File

@@ -0,0 +1,47 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "jucer_JucerDocument.h"
#include "PaintElements/jucer_PaintElement.h"
//==============================================================================
namespace ObjectTypes
{
//==============================================================================
// Component types
extern ComponentTypeHandler* const* const componentTypeHandlers;
extern const int numComponentTypes;
//==============================================================================
// Element types
extern const char* const* const elementTypeNames;
extern const int numElementTypes;
PaintElement* createNewElement (const int index, PaintRoutine* owner);
PaintElement* createNewImageElement (PaintRoutine* owner);
PaintElement* createElementForXml (const XmlElement* const e, PaintRoutine* const owner);
}

View File

@@ -0,0 +1,636 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "../Application/jucer_Headers.h"
#include "jucer_PaintRoutine.h"
#include "jucer_JucerDocument.h"
#include "jucer_ObjectTypes.h"
#include "PaintElements/jucer_PaintElementUndoableAction.h"
#include "PaintElements/jucer_PaintElementPath.h"
#include "PaintElements/jucer_PaintElementImage.h"
#include "PaintElements/jucer_PaintElementGroup.h"
#include "UI/jucer_JucerDocumentEditor.h"
#include "../Application/jucer_Application.h"
//==============================================================================
PaintRoutine::PaintRoutine()
: document (nullptr),
backgroundColour (ProjucerApplication::getApp().lookAndFeel.findColour (backgroundColourId))
{
clear();
}
PaintRoutine::~PaintRoutine()
{
elements.clear(); // do this explicitly before the scalar destructor because these
// objects will be listeners on this object
}
//==============================================================================
void PaintRoutine::changed()
{
if (document != nullptr)
document->changed();
}
bool PaintRoutine::perform (UndoableAction* action, const String& actionName)
{
if (document != nullptr)
return document->getUndoManager().perform (action, actionName);
std::unique_ptr<UndoableAction> deleter (action);
action->perform();
return false;
}
void PaintRoutine::setBackgroundColour (Colour newColour) noexcept
{
backgroundColour = newColour;
changed();
}
void PaintRoutine::clear()
{
if (elements.size() > 0)
{
elements.clear();
changed();
}
}
//==============================================================================
class AddXmlElementAction : public UndoableAction
{
public:
AddXmlElementAction (PaintRoutine& routine_, XmlElement* xml_)
: routine (routine_), xml (xml_)
{
}
bool perform()
{
showCorrectTab();
PaintElement* newElement = routine.addElementFromXml (*xml, -1, false);
jassert (newElement != nullptr);
indexAdded = routine.indexOfElement (newElement);
jassert (indexAdded >= 0);
return indexAdded >= 0;
}
bool undo()
{
showCorrectTab();
routine.removeElement (routine.getElement (indexAdded), false);
return true;
}
int getSizeInUnits() { return 10; }
int indexAdded;
private:
PaintRoutine& routine;
std::unique_ptr<XmlElement> xml;
void showCorrectTab() const
{
if (JucerDocumentEditor* const ed = JucerDocumentEditor::getActiveDocumentHolder())
ed->showGraphics (&routine);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AddXmlElementAction)
};
PaintElement* PaintRoutine::addElementFromXml (const XmlElement& xml, const int index, const bool undoable)
{
selectedPoints.deselectAll();
if (undoable && document != nullptr)
{
AddXmlElementAction* action = new AddXmlElementAction (*this, new XmlElement (xml));
document->getUndoManager().perform (action, "Add new element");
return elements [action->indexAdded];
}
if (PaintElement* const newElement = ObjectTypes::createElementForXml (&xml, this))
{
elements.insert (index, newElement);
changed();
return newElement;
}
return nullptr;
}
PaintElement* PaintRoutine::addNewElement (PaintElement* e, const int index, const bool undoable)
{
if (e != nullptr)
{
std::unique_ptr<PaintElement> deleter (e);
std::unique_ptr<XmlElement> xml (e->createXml());
e = addElementFromXml (*xml, index, undoable);
}
return e;
}
//==============================================================================
class DeleteElementAction : public PaintElementUndoableAction <PaintElement>
{
public:
explicit DeleteElementAction (PaintElement* const element)
: PaintElementUndoableAction <PaintElement> (element),
oldIndex (-1)
{
xml.reset (element->createXml());
oldIndex = routine.indexOfElement (element);
}
bool perform()
{
showCorrectTab();
routine.removeElement (getElement(), false);
return true;
}
bool undo()
{
PaintElement* newElement = routine.addElementFromXml (*xml, oldIndex, false);
showCorrectTab();
return newElement != nullptr;
}
int getSizeInUnits() { return 10; }
private:
std::unique_ptr<XmlElement> xml;
int oldIndex;
};
void PaintRoutine::removeElement (PaintElement* element, const bool undoable)
{
if (elements.contains (element))
{
if (undoable)
{
perform (new DeleteElementAction (element),
"Delete " + element->getTypeName());
}
else
{
selectedElements.deselect (element);
selectedPoints.deselectAll();
selectedPoints.changed (true);
selectedElements.changed (true);
elements.removeObject (element);
changed();
}
}
}
//==============================================================================
class FrontOrBackElementAction : public PaintElementUndoableAction <PaintElement>
{
public:
FrontOrBackElementAction (PaintElement* const element, int newIndex_)
: PaintElementUndoableAction <PaintElement> (element),
newIndex (newIndex_)
{
oldIndex = routine.indexOfElement (element);
}
bool perform()
{
showCorrectTab();
PaintElement* e = routine.getElement (oldIndex);
routine.moveElementZOrder (oldIndex, newIndex);
newIndex = routine.indexOfElement (e);
return true;
}
bool undo()
{
showCorrectTab();
routine.moveElementZOrder (newIndex, oldIndex);
return true;
}
private:
int newIndex, oldIndex;
};
void PaintRoutine::moveElementZOrder (int oldIndex, int newIndex)
{
jassert (elements [oldIndex] != nullptr);
if (oldIndex != newIndex && elements [oldIndex] != nullptr)
{
elements.move (oldIndex, newIndex);
changed();
}
}
void PaintRoutine::elementToFront (PaintElement* element, const bool undoable)
{
if (element != nullptr && elements.contains (element))
{
if (undoable)
perform (new FrontOrBackElementAction (element, -1), "Move elements to front");
else
moveElementZOrder (elements.indexOf (element), -1);
}
}
void PaintRoutine::elementToBack (PaintElement* element, const bool undoable)
{
if (element != nullptr && elements.contains (element))
{
if (undoable)
perform (new FrontOrBackElementAction (element, 0), "Move elements to back");
else
moveElementZOrder (elements.indexOf (element), 0);
}
}
//==============================================================================
const char* const PaintRoutine::clipboardXmlTag = "PAINTELEMENTS";
void PaintRoutine::copySelectedToClipboard()
{
if (selectedElements.getNumSelected() == 0)
return;
XmlElement clip (clipboardXmlTag);
for (auto* pe : elements)
if (selectedElements.isSelected (pe))
clip.addChildElement (pe->createXml());
SystemClipboard::copyTextToClipboard (clip.toString());
}
void PaintRoutine::paste()
{
if (auto doc = parseXMLIfTagMatches (SystemClipboard::getTextFromClipboard(), clipboardXmlTag))
{
selectedElements.deselectAll();
selectedPoints.deselectAll();
for (auto* e : doc->getChildIterator())
if (PaintElement* newElement = addElementFromXml (*e, -1, true))
selectedElements.addToSelection (newElement);
}
}
void PaintRoutine::deleteSelected()
{
const SelectedItemSet<PaintElement*> temp1 (selectedElements);
const SelectedItemSet<PathPoint*> temp2 (selectedPoints);
if (temp2.getNumSelected() > 0)
{
selectedPoints.deselectAll();
selectedPoints.changed (true); // synchronous message to get rid of any property components
// if any points are selected, just delete them, and not the element, which may
// also be selected..
for (int i = temp2.getNumSelected(); --i >= 0;)
temp2.getSelectedItem (i)->deleteFromPath();
changed();
}
else if (temp1.getNumSelected() > 0)
{
selectedElements.deselectAll();
selectedElements.changed (true);
for (int i = temp1.getNumSelected(); --i >= 0;)
removeElement (temp1.getSelectedItem (i), true);
changed();
}
}
void PaintRoutine::selectAll()
{
if (selectedPoints.getNumSelected() > 0)
{
if (const PaintElementPath* path = selectedPoints.getSelectedItem (0)->owner)
for (int i = 0; i < path->getNumPoints(); ++i)
selectedPoints.addToSelection (path->getPoint (i));
}
else
{
for (int i = 0; i < elements.size(); ++i)
selectedElements.addToSelection (elements.getUnchecked (i));
}
}
void PaintRoutine::selectedToFront()
{
const SelectedItemSet<PaintElement*> temp (selectedElements);
for (int i = temp.getNumSelected(); --i >= 0;)
elementToFront (temp.getSelectedItem(i), true);
}
void PaintRoutine::selectedToBack()
{
const SelectedItemSet<PaintElement*> temp (selectedElements);
for (int i = 0; i < temp.getNumSelected(); ++i)
elementToBack (temp.getSelectedItem(i), true);
}
void PaintRoutine::alignTop()
{
if (selectedElements.getNumSelected() > 1)
{
auto* main = selectedElements.getSelectedItem (0);
auto yPos = main->getY();
for (auto* other : selectedElements)
{
if (other != main)
other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (other->getX(),
yPos), main, true);
}
}
}
void PaintRoutine::alignRight()
{
if (selectedElements.getNumSelected() > 1)
{
auto* main = selectedElements.getSelectedItem (0);
auto rightPos = main->getRight();
for (auto* other : selectedElements)
{
if (other != main)
other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (rightPos - other->getWidth(),
other->getY()), main, true);
}
}
}
void PaintRoutine::alignBottom()
{
if (selectedElements.getNumSelected() > 1)
{
auto* main = selectedElements.getSelectedItem (0);
auto bottomPos = main->getBottom();
for (auto* other : selectedElements)
{
if (other != main)
other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (other->getX(),
bottomPos - other->getHeight()), main, true);
}
}
}
void PaintRoutine::alignLeft()
{
if (selectedElements.getNumSelected() > 1)
{
auto* main = selectedElements.getSelectedItem (0);
auto xPos = main->getX();
for (auto* other : selectedElements)
{
if (other != main)
other->setPaintElementBoundsAndProperties (other, other->getBounds().withPosition (xPos,
other->getY()), main, true);
}
}
}
void PaintRoutine::groupSelected()
{
PaintElementGroup::groupSelected (this);
}
void PaintRoutine::ungroupSelected()
{
const SelectedItemSet<PaintElement*> temp (selectedElements);
for (int i = 0; i < temp.getNumSelected(); ++i)
if (PaintElementGroup* const pg = dynamic_cast<PaintElementGroup*> (temp.getSelectedItem (i)))
pg->ungroup (true);
}
void PaintRoutine::bringLostItemsBackOnScreen (const Rectangle<int>& parentArea)
{
for (auto* c : elements)
{
auto r = c->getCurrentBounds (parentArea);
if (! r.intersects (parentArea))
{
r.setPosition (parentArea.getCentreX(), parentArea.getCentreY());
c->setCurrentBounds (r, parentArea, true);
}
}
}
void PaintRoutine::startDragging (const Rectangle<int>& parentArea)
{
for (auto* c : elements)
{
auto r = c->getCurrentBounds (parentArea);
c->getProperties().set ("xDragStart", r.getX());
c->getProperties().set ("yDragStart", r.getY());
}
getDocument()->beginTransaction();
}
void PaintRoutine::dragSelectedComps (int dx, int dy, const Rectangle<int>& parentArea)
{
getDocument()->getUndoManager().undoCurrentTransactionOnly();
if (document != nullptr && selectedElements.getNumSelected() > 1)
{
dx = document->snapPosition (dx);
dy = document->snapPosition (dy);
}
for (int i = 0; i < selectedElements.getNumSelected(); ++i)
{
PaintElement* const c = selectedElements.getSelectedItem (i);
const int startX = c->getProperties() ["xDragStart"];
const int startY = c->getProperties() ["yDragStart"];
Rectangle<int> r (c->getCurrentBounds (parentArea));
if (document != nullptr && selectedElements.getNumSelected() == 1)
{
r.setPosition (document->snapPosition (startX + dx),
document->snapPosition (startY + dy));
}
else
{
r.setPosition (startX + dx,
startY + dy);
}
c->setCurrentBounds (r, parentArea, true);
}
changed();
}
void PaintRoutine::endDragging()
{
getDocument()->beginTransaction();
}
//==============================================================================
void PaintRoutine::fillWithBackground (Graphics& g, const bool drawOpaqueBackground)
{
if ((! backgroundColour.isOpaque()) && drawOpaqueBackground)
{
g.fillCheckerBoard (Rectangle<float> ((float) g.getClipBounds().getRight(),
(float) g.getClipBounds().getBottom()),
50.0f, 50.0f,
Colour (0xffdddddd).overlaidWith (backgroundColour),
Colour (0xffffffff).overlaidWith (backgroundColour));
}
else
{
g.fillAll (backgroundColour);
}
}
void PaintRoutine::drawElements (Graphics& g, const Rectangle<int>& relativeTo)
{
Component temp;
temp.setBounds (relativeTo);
for (auto* e : elements)
e->draw (g, getDocument()->getComponentLayout(), relativeTo);
}
//==============================================================================
void PaintRoutine::dropImageAt (const File& f, int x, int y)
{
std::unique_ptr<Drawable> d (Drawable::createFromImageFile (f));
if (d != nullptr)
{
auto bounds = d->getDrawableBounds();
d.reset();
auto* newElement = addNewElement (ObjectTypes::createNewImageElement (this), -1, true);
if (auto* pei = dynamic_cast<PaintElementImage*> (newElement))
{
String resourceName (getDocument()->getResources().findUniqueName (f.getFileName()));
if (auto* existingResource = getDocument()->getResources().getResourceForFile (f))
{
resourceName = existingResource->name;
}
else
{
MemoryBlock data;
f.loadFileAsData (data);
getDocument()->getResources().add (resourceName, f.getFullPathName(), data);
}
pei->setResource (resourceName, true);
const int imageW = (int) (bounds.getRight() + 0.999f);
const int imageH = (int) (bounds.getBottom() + 0.999f);
RelativePositionedRectangle pr;
pr.rect.setX (x - imageW / 2);
pr.rect.setY (y - imageH / 2);
pr.rect.setWidth (imageW);
pr.rect.setHeight (imageH);
pei->setPosition (pr, true);
getSelectedElements().selectOnly (pei);
}
}
}
//==============================================================================
const char* PaintRoutine::xmlTagName = "BACKGROUND";
XmlElement* PaintRoutine::createXml() const
{
auto* xml = new XmlElement (xmlTagName);
xml->setAttribute ("backgroundColour", backgroundColour.toString());
for (auto* e : elements)
xml->addChildElement (e->createXml());
return xml;
}
bool PaintRoutine::loadFromXml (const XmlElement& xml)
{
if (xml.hasTagName (xmlTagName))
{
backgroundColour = Colour::fromString (xml.getStringAttribute ("backgroundColour", Colours::white.toString()));
clear();
for (auto* e : xml.getChildIterator())
if (auto* newElement = ObjectTypes::createElementForXml (e, this))
elements.add (newElement);
return true;
}
return false;
}
void PaintRoutine::fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) const
{
if (! backgroundColour.isTransparent())
paintMethodCode << "g.fillAll (" << CodeHelpers::colourToCode (backgroundColour) << ");\n\n";
for (auto* e : elements)
e->fillInGeneratedCode (code, paintMethodCode);
}
void PaintRoutine::applyCustomPaintSnippets (StringArray& snippets)
{
for (auto* e : elements)
e->applyCustomPaintSnippets (snippets);
}

View File

@@ -0,0 +1,122 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
#include "PaintElements/jucer_PaintElement.h"
class JucerDocument;
class PathPoint;
//==============================================================================
/**
Contains a set of PaintElements that constitute some kind of paint() method.
*/
class PaintRoutine
{
public:
//==============================================================================
PaintRoutine();
~PaintRoutine();
//==============================================================================
void changed();
bool perform (UndoableAction* action, const String& actionName);
//==============================================================================
int getNumElements() const noexcept { return elements.size(); }
PaintElement* getElement (const int index) const noexcept { return elements [index]; }
int indexOfElement (PaintElement* e) const noexcept { return elements.indexOf (e); }
bool containsElement (PaintElement* e) const noexcept { return elements.contains (e); }
//==============================================================================
void clear();
PaintElement* addElementFromXml (const XmlElement& xml, const int index, const bool undoable);
PaintElement* addNewElement (PaintElement* elementToCopy, const int index, const bool undoable);
void removeElement (PaintElement* element, const bool undoable);
void elementToFront (PaintElement* element, const bool undoable);
void elementToBack (PaintElement* element, const bool undoable);
const Colour getBackgroundColour() const noexcept { return backgroundColour; }
void setBackgroundColour (Colour newColour) noexcept;
void fillWithBackground (Graphics& g, const bool drawOpaqueBackground);
void drawElements (Graphics& g, const Rectangle<int>& relativeTo);
void dropImageAt (const File& f, int x, int y);
//==============================================================================
SelectedItemSet <PaintElement*>& getSelectedElements() noexcept { return selectedElements; }
SelectedItemSet <PathPoint*>& getSelectedPoints() noexcept { return selectedPoints; }
static const char* const clipboardXmlTag;
void copySelectedToClipboard();
void paste();
void deleteSelected();
void selectAll();
void selectedToFront();
void selectedToBack();
void alignTop();
void alignRight();
void alignBottom();
void alignLeft();
void groupSelected();
void ungroupSelected();
void startDragging (const Rectangle<int>& parentArea);
void dragSelectedComps (int dxFromDragStart, int dyFromDragStart, const Rectangle<int>& parentArea);
void endDragging();
void bringLostItemsBackOnScreen (const Rectangle<int>& parentArea);
//==============================================================================
void setDocument (JucerDocument* const doc) { document = doc; }
JucerDocument* getDocument() const noexcept { return document; }
//==============================================================================
static const char* xmlTagName;
XmlElement* createXml() const;
bool loadFromXml (const XmlElement& xml);
void fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode) const;
void applyCustomPaintSnippets (StringArray&);
//==============================================================================
private:
OwnedArray<PaintElement> elements;
SelectedItemSet <PaintElement*> selectedElements;
SelectedItemSet <PathPoint*> selectedPoints;
JucerDocument* document;
Colour backgroundColour;
friend class DeleteElementAction;
friend class FrontOrBackElementAction;
void moveElementZOrder (int oldIndex, int newIndex);
};

View File

@@ -0,0 +1,118 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#pragma once
//==============================================================================
inline String quotedString (const String& s, bool wrapInTransMacro)
{
const int embeddedIndex = s.indexOfIgnoreCase ("%%");
if (embeddedIndex >= 0)
{
String s1 (s.substring (0, embeddedIndex));
String s2 (s.substring (embeddedIndex + 2));
String code;
const int closeIndex = s2.indexOf ("%%");
if (closeIndex > 0)
{
code = s2.substring (0, closeIndex).trim();
s2 = s2.substring (closeIndex + 2);
}
if (code.isNotEmpty())
{
String result;
if (s1.isNotEmpty())
result << quotedString (s1, wrapInTransMacro) << " + ";
result << code;
if (s2.isNotEmpty())
result << " + " << quotedString (s2, wrapInTransMacro);
return result;
}
}
String lit (CodeHelpers::stringLiteral (s));
if (wrapInTransMacro && lit.startsWithChar ('"'))
return "TRANS(" + lit + ")";
return lit;
}
inline String castToFloat (const String& expression)
{
if (expression.containsOnly ("0123456789.f"))
{
String s (expression.getFloatValue());
if (s.containsChar ('.'))
return s + "f";
return s + ".0f";
}
return "static_cast<float> (" + expression + ")";
}
inline void drawResizableBorder (Graphics& g, int w, int h,
const BorderSize<int> borderSize,
const bool isMouseOver,
Colour borderColour)
{
ignoreUnused (isMouseOver);
g.setColour (borderColour);
g.fillRect (0, 0, w, borderSize.getTop());
g.fillRect (0, 0, borderSize.getLeft(), h);
g.fillRect (0, h - borderSize.getBottom(), w, borderSize.getBottom());
g.fillRect (w - borderSize.getRight(), 0, borderSize.getRight(), h);
g.drawRect (borderSize.getLeft() - 1, borderSize.getTop() - 1,
w - borderSize.getRight() - borderSize.getLeft() + 2,
h - borderSize.getTop() - borderSize.getBottom() + 2);
}
inline void drawMouseOverCorners (Graphics& g, int w, int h)
{
RectangleList<int> r (Rectangle<int> (0, 0, w, h));
r.subtract (Rectangle<int> (1, 1, w - 2, h - 2));
const int size = jmin (w / 3, h / 3, 12);
r.subtract (Rectangle<int> (size, 0, w - size - size, h));
r.subtract (Rectangle<int> (0, size, w, h - size - size));
g.setColour (Colours::black);
for (int i = r.getNumRectangles(); --i >= 0;)
g.fillRect (r.getRectangle (i));
}