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,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;
}
};