1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-13 00:04:19 +00:00

Added Animated App template and examples

This commit is contained in:
Felix Faire 2014-10-29 15:55:23 +00:00
parent fefcf7aca6
commit ff6520a89a
1141 changed files with 438491 additions and 94 deletions

View file

@ -0,0 +1,251 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
Drawable::Drawable()
{
setInterceptsMouseClicks (false, false);
setPaintingIsUnclipped (true);
}
Drawable::Drawable (const Drawable& other)
: Component (other.getName())
{
setComponentID (other.getComponentID());
}
Drawable::~Drawable()
{
}
//==============================================================================
void Drawable::draw (Graphics& g, float opacity, const AffineTransform& transform) const
{
const_cast <Drawable*> (this)->nonConstDraw (g, opacity, transform);
}
void Drawable::nonConstDraw (Graphics& g, float opacity, const AffineTransform& transform)
{
Graphics::ScopedSaveState ss (g);
g.addTransform (AffineTransform::translation ((float) -(originRelativeToComponent.x),
(float) -(originRelativeToComponent.y))
.followedBy (getTransform())
.followedBy (transform));
if (! g.isClipEmpty())
{
if (opacity < 1.0f)
{
g.beginTransparencyLayer (opacity);
paintEntireComponent (g, true);
g.endTransparencyLayer();
}
else
{
paintEntireComponent (g, true);
}
}
}
void Drawable::drawAt (Graphics& g, float x, float y, float opacity) const
{
draw (g, opacity, AffineTransform::translation (x, y));
}
void Drawable::drawWithin (Graphics& g, const Rectangle<float>& destArea,
RectanglePlacement placement, float opacity) const
{
draw (g, opacity, placement.getTransformToFit (getDrawableBounds(), destArea));
}
//==============================================================================
DrawableComposite* Drawable::getParent() const
{
return dynamic_cast <DrawableComposite*> (getParentComponent());
}
void Drawable::transformContextToCorrectOrigin (Graphics& g)
{
g.setOrigin (originRelativeToComponent);
}
void Drawable::parentHierarchyChanged()
{
setBoundsToEnclose (getDrawableBounds());
}
void Drawable::setBoundsToEnclose (const Rectangle<float>& area)
{
Drawable* const parent = getParent();
Point<int> parentOrigin;
if (parent != nullptr)
parentOrigin = parent->originRelativeToComponent;
const Rectangle<int> newBounds (area.getSmallestIntegerContainer() + parentOrigin);
originRelativeToComponent = parentOrigin - newBounds.getPosition();
setBounds (newBounds);
}
//==============================================================================
bool Drawable::replaceColour (Colour original, Colour replacement)
{
bool changed = false;
for (int i = getNumChildComponents(); --i >= 0;)
if (Drawable* d = dynamic_cast<Drawable*> (getChildComponent(i)))
changed = d->replaceColour (original, replacement) || changed;
return changed;
}
//==============================================================================
void Drawable::setOriginWithOriginalSize (Point<float> originWithinParent)
{
setTransform (AffineTransform::translation (originWithinParent.x, originWithinParent.y));
}
void Drawable::setTransformToFit (const Rectangle<float>& area, RectanglePlacement placement)
{
if (! area.isEmpty())
setTransform (placement.getTransformToFit (getDrawableBounds(), area));
}
//==============================================================================
Drawable* Drawable::createFromImageData (const void* data, const size_t numBytes)
{
Drawable* result = nullptr;
Image image (ImageFileFormat::loadFrom (data, numBytes));
if (image.isValid())
{
DrawableImage* const di = new DrawableImage();
di->setImage (image);
result = di;
}
else
{
const String asString (String::createStringFromData (data, (int) numBytes));
XmlDocument doc (asString);
ScopedPointer <XmlElement> outer (doc.getDocumentElement (true));
if (outer != nullptr && outer->hasTagName ("svg"))
{
ScopedPointer <XmlElement> svg (doc.getDocumentElement());
if (svg != nullptr)
result = Drawable::createFromSVG (*svg);
}
}
return result;
}
Drawable* Drawable::createFromImageDataStream (InputStream& dataSource)
{
MemoryOutputStream mo;
mo << dataSource;
return createFromImageData (mo.getData(), mo.getDataSize());
}
Drawable* Drawable::createFromImageFile (const File& file)
{
FileInputStream fin (file);
return fin.openedOk() ? createFromImageDataStream (fin) : nullptr;
}
//==============================================================================
template <class DrawableClass>
class DrawableTypeHandler : public ComponentBuilder::TypeHandler
{
public:
DrawableTypeHandler()
: ComponentBuilder::TypeHandler (DrawableClass::valueTreeType)
{
}
Component* addNewComponentFromState (const ValueTree& state, Component* parent)
{
DrawableClass* const d = new DrawableClass();
if (parent != nullptr)
parent->addAndMakeVisible (d);
updateComponentFromState (d, state);
return d;
}
void updateComponentFromState (Component* component, const ValueTree& state)
{
DrawableClass* const d = dynamic_cast <DrawableClass*> (component);
jassert (d != nullptr);
d->refreshFromValueTree (state, *this->getBuilder());
}
};
void Drawable::registerDrawableTypeHandlers (ComponentBuilder& builder)
{
builder.registerTypeHandler (new DrawableTypeHandler <DrawablePath>());
builder.registerTypeHandler (new DrawableTypeHandler <DrawableComposite>());
builder.registerTypeHandler (new DrawableTypeHandler <DrawableRectangle>());
builder.registerTypeHandler (new DrawableTypeHandler <DrawableImage>());
builder.registerTypeHandler (new DrawableTypeHandler <DrawableText>());
}
Drawable* Drawable::createFromValueTree (const ValueTree& tree, ComponentBuilder::ImageProvider* imageProvider)
{
ComponentBuilder builder (tree);
builder.setImageProvider (imageProvider);
registerDrawableTypeHandlers (builder);
ScopedPointer<Component> comp (builder.createComponent());
Drawable* const d = dynamic_cast<Drawable*> (static_cast <Component*> (comp));
if (d != nullptr)
comp.release();
return d;
}
//==============================================================================
Drawable::ValueTreeWrapperBase::ValueTreeWrapperBase (const ValueTree& state_)
: state (state_)
{
}
String Drawable::ValueTreeWrapperBase::getID() const
{
return state [ComponentBuilder::idProperty];
}
void Drawable::ValueTreeWrapperBase::setID (const String& newID)
{
if (newID.isEmpty())
state.removeProperty (ComponentBuilder::idProperty, nullptr);
else
state.setProperty (ComponentBuilder::idProperty, newID, nullptr);
}

View file

@ -0,0 +1,259 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_DRAWABLE_H_INCLUDED
#define JUCE_DRAWABLE_H_INCLUDED
//==============================================================================
/**
The base class for objects which can draw themselves, e.g. polygons, images, etc.
@see DrawableComposite, DrawableImage, DrawablePath, DrawableText
*/
class JUCE_API Drawable : public Component
{
protected:
//==============================================================================
/** The base class can't be instantiated directly.
@see DrawableComposite, DrawableImage, DrawablePath, DrawableText
*/
Drawable();
public:
/** Destructor. */
virtual ~Drawable();
//==============================================================================
/** Creates a deep copy of this Drawable object.
Use this to create a new copy of this and any sub-objects in the tree.
*/
virtual Drawable* createCopy() const = 0;
//==============================================================================
/** Renders this Drawable object.
Note that the preferred way to render a drawable in future is by using it
as a component and adding it to a parent, so you might want to consider that
before using this method.
@see drawWithin
*/
void draw (Graphics& g, float opacity,
const AffineTransform& transform = AffineTransform::identity) const;
/** Renders the Drawable at a given offset within the Graphics context.
The coordinates passed-in are used to translate the object relative to its own
origin before drawing it - this is basically a quick way of saying:
@code
draw (g, AffineTransform::translation (x, y)).
@endcode
Note that the preferred way to render a drawable in future is by using it
as a component and adding it to a parent, so you might want to consider that
before using this method.
*/
void drawAt (Graphics& g, float x, float y, float opacity) const;
/** Renders the Drawable within a rectangle, scaling it to fit neatly inside without
changing its aspect-ratio.
The object can placed arbitrarily within the rectangle based on a Justification type,
and can either be made as big as possible, or just reduced to fit.
Note that the preferred way to render a drawable in future is by using it
as a component and adding it to a parent, so you might want to consider that
before using this method.
@param g the graphics context to render onto
@param destArea the target rectangle to fit the drawable into
@param placement defines the alignment and rescaling to use to fit
this object within the target rectangle.
@param opacity the opacity to use, in the range 0 to 1.0
*/
void drawWithin (Graphics& g,
const Rectangle<float>& destArea,
RectanglePlacement placement,
float opacity) const;
//==============================================================================
/** Resets any transformations on this drawable, and positions its origin within
its parent component.
*/
void setOriginWithOriginalSize (Point<float> originWithinParent);
/** Sets a transform for this drawable that will position it within the specified
area of its parent component.
*/
void setTransformToFit (const Rectangle<float>& areaInParent, RectanglePlacement placement);
/** Returns the DrawableComposite that contains this object, if there is one. */
DrawableComposite* getParent() const;
//==============================================================================
/** Tries to turn some kind of image file into a drawable.
The data could be an image that the ImageFileFormat class understands, or it
could be SVG.
*/
static Drawable* createFromImageData (const void* data, size_t numBytes);
/** Tries to turn a stream containing some kind of image data into a drawable.
The data could be an image that the ImageFileFormat class understands, or it
could be SVG.
*/
static Drawable* createFromImageDataStream (InputStream& dataSource);
/** Tries to turn a file containing some kind of image data into a drawable.
The data could be an image that the ImageFileFormat class understands, or it
could be SVG.
*/
static Drawable* createFromImageFile (const File& file);
/** Attempts to parse an SVG (Scalable Vector Graphics) document, and to turn this
into a Drawable tree.
The object returned must be deleted by the caller. If something goes wrong
while parsing, it may return nullptr.
SVG is a pretty large and complex spec, and this doesn't aim to be a full
implementation, but it can return the basic vector objects.
*/
static Drawable* createFromSVG (const XmlElement& svgDocument);
/** Parses an SVG path string and returns it. */
static Path parseSVGPath (const String& svgPath);
//==============================================================================
/** Tries to create a Drawable from a previously-saved ValueTree.
The ValueTree must have been created by the createValueTree() method.
If there are any images used within the drawable, you'll need to provide a valid
ImageProvider object that can be used to retrieve these images from whatever type
of identifier is used to represent them.
Internally, this uses a ComponentBuilder, and registerDrawableTypeHandlers().
*/
static Drawable* createFromValueTree (const ValueTree& tree, ComponentBuilder::ImageProvider* imageProvider);
/** Creates a ValueTree to represent this Drawable.
The ValueTree that is returned can be turned back into a Drawable with createFromValueTree().
If there are any images used in this drawable, you'll need to provide a valid ImageProvider
object that can be used to create storable representations of them.
*/
virtual ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const = 0;
/** Returns the area that this drawble covers.
The result is expressed in this drawable's own coordinate space, and does not take
into account any transforms that may be applied to the component.
*/
virtual Rectangle<float> getDrawableBounds() const = 0;
/** Recursively replaces a colour that might be used for filling or stroking.
return true if any instances of this colour were found.
*/
virtual bool replaceColour (Colour originalColour, Colour replacementColour);
//==============================================================================
/** Internal class used to manage ValueTrees that represent Drawables. */
class ValueTreeWrapperBase
{
public:
ValueTreeWrapperBase (const ValueTree& state);
ValueTree& getState() noexcept { return state; }
String getID() const;
void setID (const String& newID);
ValueTree state;
};
//==============================================================================
/** Registers a set of ComponentBuilder::TypeHandler objects that can be used to
load all the different Drawable types from a saved state.
@see ComponentBuilder::registerTypeHandler()
*/
static void registerDrawableTypeHandlers (ComponentBuilder& componentBuilder);
protected:
//==============================================================================
friend class DrawableComposite;
friend class DrawableShape;
/** @internal */
void transformContextToCorrectOrigin (Graphics&);
/** @internal */
void parentHierarchyChanged() override;
/** @internal */
void setBoundsToEnclose (const Rectangle<float>&);
Point<int> originRelativeToComponent;
#ifndef DOXYGEN
/** Internal utility class used by Drawables. */
template <class DrawableType>
class Positioner : public RelativeCoordinatePositionerBase
{
public:
Positioner (DrawableType& c)
: RelativeCoordinatePositionerBase (c),
owner (c)
{}
bool registerCoordinates() { return owner.registerCoordinates (*this); }
void applyToComponentBounds()
{
ComponentScope scope (getComponent());
owner.recalculateCoordinates (&scope);
}
void applyNewBounds (const Rectangle<int>&)
{
jassertfalse; // drawables can't be resized directly!
}
private:
DrawableType& owner;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Positioner)
};
Drawable (const Drawable&);
#endif
private:
void nonConstDraw (Graphics&, float opacity, const AffineTransform&);
Drawable& operator= (const Drawable&);
JUCE_LEAK_DETECTOR (Drawable)
};
#endif // JUCE_DRAWABLE_H_INCLUDED

View file

@ -0,0 +1,327 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
DrawableComposite::DrawableComposite()
: bounds (Point<float>(), Point<float> (100.0f, 0.0f), Point<float> (0.0f, 100.0f)),
updateBoundsReentrant (false)
{
setContentArea (RelativeRectangle (RelativeCoordinate (0.0),
RelativeCoordinate (100.0),
RelativeCoordinate (0.0),
RelativeCoordinate (100.0)));
}
DrawableComposite::DrawableComposite (const DrawableComposite& other)
: Drawable (other),
bounds (other.bounds),
markersX (other.markersX),
markersY (other.markersY),
updateBoundsReentrant (false)
{
for (int i = 0; i < other.getNumChildComponents(); ++i)
if (const Drawable* const d = dynamic_cast <const Drawable*> (other.getChildComponent(i)))
addAndMakeVisible (d->createCopy());
}
DrawableComposite::~DrawableComposite()
{
deleteAllChildren();
}
Drawable* DrawableComposite::createCopy() const
{
return new DrawableComposite (*this);
}
//==============================================================================
Rectangle<float> DrawableComposite::getDrawableBounds() const
{
Rectangle<float> r;
for (int i = getNumChildComponents(); --i >= 0;)
if (const Drawable* const d = dynamic_cast <const Drawable*> (getChildComponent(i)))
r = r.getUnion (d->isTransformed() ? d->getDrawableBounds().transformedBy (d->getTransform())
: d->getDrawableBounds());
return r;
}
MarkerList* DrawableComposite::getMarkers (bool xAxis)
{
return xAxis ? &markersX : &markersY;
}
RelativeRectangle DrawableComposite::getContentArea() const
{
jassert (markersX.getNumMarkers() >= 2 && markersX.getMarker (0)->name == contentLeftMarkerName && markersX.getMarker (1)->name == contentRightMarkerName);
jassert (markersY.getNumMarkers() >= 2 && markersY.getMarker (0)->name == contentTopMarkerName && markersY.getMarker (1)->name == contentBottomMarkerName);
return RelativeRectangle (markersX.getMarker(0)->position, markersX.getMarker(1)->position,
markersY.getMarker(0)->position, markersY.getMarker(1)->position);
}
void DrawableComposite::setContentArea (const RelativeRectangle& newArea)
{
markersX.setMarker (contentLeftMarkerName, newArea.left);
markersX.setMarker (contentRightMarkerName, newArea.right);
markersY.setMarker (contentTopMarkerName, newArea.top);
markersY.setMarker (contentBottomMarkerName, newArea.bottom);
}
void DrawableComposite::setBoundingBox (const RelativeParallelogram& newBounds)
{
if (bounds != newBounds)
{
bounds = newBounds;
if (bounds.isDynamic())
{
Drawable::Positioner<DrawableComposite>* const p = new Drawable::Positioner<DrawableComposite> (*this);
setPositioner (p);
p->apply();
}
else
{
setPositioner (nullptr);
recalculateCoordinates (nullptr);
}
}
}
void DrawableComposite::resetBoundingBoxToContentArea()
{
const RelativeRectangle content (getContentArea());
setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top),
RelativePoint (content.right, content.top),
RelativePoint (content.left, content.bottom)));
}
void DrawableComposite::resetContentAreaAndBoundingBoxToFitChildren()
{
const Rectangle<float> activeArea (getDrawableBounds());
setContentArea (RelativeRectangle (RelativeCoordinate (activeArea.getX()),
RelativeCoordinate (activeArea.getRight()),
RelativeCoordinate (activeArea.getY()),
RelativeCoordinate (activeArea.getBottom())));
resetBoundingBoxToContentArea();
}
bool DrawableComposite::registerCoordinates (RelativeCoordinatePositionerBase& pos)
{
bool ok = pos.addPoint (bounds.topLeft);
ok = pos.addPoint (bounds.topRight) && ok;
return pos.addPoint (bounds.bottomLeft) && ok;
}
void DrawableComposite::recalculateCoordinates (Expression::Scope* scope)
{
Point<float> resolved[3];
bounds.resolveThreePoints (resolved, scope);
const Rectangle<float> content (getContentArea().resolve (scope));
AffineTransform t (AffineTransform::fromTargetPoints (content.getX(), content.getY(), resolved[0].x, resolved[0].y,
content.getRight(), content.getY(), resolved[1].x, resolved[1].y,
content.getX(), content.getBottom(), resolved[2].x, resolved[2].y));
if (t.isSingularity())
t = AffineTransform::identity;
setTransform (t);
}
void DrawableComposite::parentHierarchyChanged()
{
DrawableComposite* parent = getParent();
if (parent != nullptr)
originRelativeToComponent = parent->originRelativeToComponent - getPosition();
}
void DrawableComposite::childBoundsChanged (Component*)
{
updateBoundsToFitChildren();
}
void DrawableComposite::childrenChanged()
{
updateBoundsToFitChildren();
}
void DrawableComposite::updateBoundsToFitChildren()
{
if (! updateBoundsReentrant)
{
const ScopedValueSetter<bool> setter (updateBoundsReentrant, true, false);
Rectangle<int> childArea;
for (int i = getNumChildComponents(); --i >= 0;)
childArea = childArea.getUnion (getChildComponent(i)->getBoundsInParent());
const Point<int> delta (childArea.getPosition());
childArea += getPosition();
if (childArea != getBounds())
{
if (! delta.isOrigin())
{
originRelativeToComponent -= delta;
for (int i = getNumChildComponents(); --i >= 0;)
if (Component* const c = getChildComponent(i))
c->setBounds (c->getBounds() - delta);
}
setBounds (childArea);
}
}
}
//==============================================================================
const char* const DrawableComposite::contentLeftMarkerName = "left";
const char* const DrawableComposite::contentRightMarkerName = "right";
const char* const DrawableComposite::contentTopMarkerName = "top";
const char* const DrawableComposite::contentBottomMarkerName = "bottom";
//==============================================================================
const Identifier DrawableComposite::valueTreeType ("Group");
const Identifier DrawableComposite::ValueTreeWrapper::topLeft ("topLeft");
const Identifier DrawableComposite::ValueTreeWrapper::topRight ("topRight");
const Identifier DrawableComposite::ValueTreeWrapper::bottomLeft ("bottomLeft");
const Identifier DrawableComposite::ValueTreeWrapper::childGroupTag ("Drawables");
const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagX ("MarkersX");
const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagY ("MarkersY");
//==============================================================================
DrawableComposite::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
: ValueTreeWrapperBase (state_)
{
jassert (state.hasType (valueTreeType));
}
ValueTree DrawableComposite::ValueTreeWrapper::getChildList() const
{
return state.getChildWithName (childGroupTag);
}
ValueTree DrawableComposite::ValueTreeWrapper::getChildListCreating (UndoManager* undoManager)
{
return state.getOrCreateChildWithName (childGroupTag, undoManager);
}
RelativeParallelogram DrawableComposite::ValueTreeWrapper::getBoundingBox() const
{
return RelativeParallelogram (state.getProperty (topLeft, "0, 0"),
state.getProperty (topRight, "100, 0"),
state.getProperty (bottomLeft, "0, 100"));
}
void DrawableComposite::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager)
{
state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager);
state.setProperty (topRight, newBounds.topRight.toString(), undoManager);
state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager);
}
void DrawableComposite::ValueTreeWrapper::resetBoundingBoxToContentArea (UndoManager* undoManager)
{
const RelativeRectangle content (getContentArea());
setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top),
RelativePoint (content.right, content.top),
RelativePoint (content.left, content.bottom)), undoManager);
}
RelativeRectangle DrawableComposite::ValueTreeWrapper::getContentArea() const
{
MarkerList::ValueTreeWrapper marksX (getMarkerList (true));
MarkerList::ValueTreeWrapper marksY (getMarkerList (false));
return RelativeRectangle (marksX.getMarker (marksX.getMarkerState (0)).position,
marksX.getMarker (marksX.getMarkerState (1)).position,
marksY.getMarker (marksY.getMarkerState (0)).position,
marksY.getMarker (marksY.getMarkerState (1)).position);
}
void DrawableComposite::ValueTreeWrapper::setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager)
{
MarkerList::ValueTreeWrapper marksX (getMarkerListCreating (true, nullptr));
MarkerList::ValueTreeWrapper marksY (getMarkerListCreating (false, nullptr));
marksX.setMarker (MarkerList::Marker (contentLeftMarkerName, newArea.left), undoManager);
marksX.setMarker (MarkerList::Marker (contentRightMarkerName, newArea.right), undoManager);
marksY.setMarker (MarkerList::Marker (contentTopMarkerName, newArea.top), undoManager);
marksY.setMarker (MarkerList::Marker (contentBottomMarkerName, newArea.bottom), undoManager);
}
MarkerList::ValueTreeWrapper DrawableComposite::ValueTreeWrapper::getMarkerList (bool xAxis) const
{
return state.getChildWithName (xAxis ? markerGroupTagX : markerGroupTagY);
}
MarkerList::ValueTreeWrapper DrawableComposite::ValueTreeWrapper::getMarkerListCreating (bool xAxis, UndoManager* undoManager)
{
return state.getOrCreateChildWithName (xAxis ? markerGroupTagX : markerGroupTagY, undoManager);
}
//==============================================================================
void DrawableComposite::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder)
{
const ValueTreeWrapper wrapper (tree);
setComponentID (wrapper.getID());
wrapper.getMarkerList (true).applyTo (markersX);
wrapper.getMarkerList (false).applyTo (markersY);
setBoundingBox (wrapper.getBoundingBox());
builder.updateChildComponents (*this, wrapper.getChildList());
}
ValueTree DrawableComposite::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const
{
ValueTree tree (valueTreeType);
ValueTreeWrapper v (tree);
v.setID (getComponentID());
v.setBoundingBox (bounds, nullptr);
ValueTree childList (v.getChildListCreating (nullptr));
for (int i = 0; i < getNumChildComponents(); ++i)
{
const Drawable* const d = dynamic_cast <const Drawable*> (getChildComponent(i));
jassert (d != nullptr); // You can't save a mix of Drawables and normal components!
childList.addChild (d->createValueTree (imageProvider), -1, nullptr);
}
v.getMarkerListCreating (true, nullptr).readFrom (markersX, nullptr);
v.getMarkerListCreating (false, nullptr).readFrom (markersY, nullptr);
return tree;
}

View file

@ -0,0 +1,156 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_DRAWABLECOMPOSITE_H_INCLUDED
#define JUCE_DRAWABLECOMPOSITE_H_INCLUDED
//==============================================================================
/**
A drawable object which acts as a container for a set of other Drawables.
@see Drawable
*/
class JUCE_API DrawableComposite : public Drawable
{
public:
//==============================================================================
/** Creates a composite Drawable. */
DrawableComposite();
/** Creates a copy of a DrawableComposite. */
DrawableComposite (const DrawableComposite&);
/** Destructor. */
~DrawableComposite();
//==============================================================================
/** Sets the parallelogram that defines the target position of the content rectangle when the drawable is rendered.
@see setContentArea
*/
void setBoundingBox (const RelativeParallelogram& newBoundingBox);
/** Returns the parallelogram that defines the target position of the content rectangle when the drawable is rendered.
@see setBoundingBox
*/
const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; }
/** Changes the bounding box transform to match the content area, so that any sub-items will
be drawn at their untransformed positions.
*/
void resetBoundingBoxToContentArea();
/** Returns the main content rectangle.
The content area is actually defined by the markers named "left", "right", "top" and
"bottom", but this method is a shortcut that returns them all at once.
@see contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName
*/
RelativeRectangle getContentArea() const;
/** Changes the main content area.
The content area is actually defined by the markers named "left", "right", "top" and
"bottom", but this method is a shortcut that sets them all at once.
@see setBoundingBox, contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName
*/
void setContentArea (const RelativeRectangle& newArea);
/** Resets the content area and the bounding transform to fit around the area occupied
by the child components (ignoring any markers).
*/
void resetContentAreaAndBoundingBoxToFitChildren();
//==============================================================================
/** The name of the marker that defines the left edge of the content area. */
static const char* const contentLeftMarkerName;
/** The name of the marker that defines the right edge of the content area. */
static const char* const contentRightMarkerName;
/** The name of the marker that defines the top edge of the content area. */
static const char* const contentTopMarkerName;
/** The name of the marker that defines the bottom edge of the content area. */
static const char* const contentBottomMarkerName;
//==============================================================================
/** @internal */
Drawable* createCopy() const;
/** @internal */
void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder);
/** @internal */
ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const;
/** @internal */
static const Identifier valueTreeType;
/** @internal */
Rectangle<float> getDrawableBounds() const;
/** @internal */
void childBoundsChanged (Component*) override;
/** @internal */
void childrenChanged() override;
/** @internal */
void parentHierarchyChanged() override;
/** @internal */
MarkerList* getMarkers (bool xAxis) override;
//==============================================================================
/** Internally-used class for wrapping a DrawableComposite's state into a ValueTree. */
class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase
{
public:
ValueTreeWrapper (const ValueTree& state);
ValueTree getChildList() const;
ValueTree getChildListCreating (UndoManager* undoManager);
RelativeParallelogram getBoundingBox() const;
void setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager);
void resetBoundingBoxToContentArea (UndoManager* undoManager);
RelativeRectangle getContentArea() const;
void setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager);
MarkerList::ValueTreeWrapper getMarkerList (bool xAxis) const;
MarkerList::ValueTreeWrapper getMarkerListCreating (bool xAxis, UndoManager* undoManager);
static const Identifier topLeft, topRight, bottomLeft;
private:
static const Identifier childGroupTag, markerGroupTagX, markerGroupTagY;
};
private:
//==============================================================================
RelativeParallelogram bounds;
MarkerList markersX, markersY;
bool updateBoundsReentrant;
friend class Drawable::Positioner<DrawableComposite>;
bool registerCoordinates (RelativeCoordinatePositionerBase&);
void recalculateCoordinates (Expression::Scope*);
void updateBoundsToFitChildren();
DrawableComposite& operator= (const DrawableComposite&);
JUCE_LEAK_DETECTOR (DrawableComposite)
};
#endif // JUCE_DRAWABLECOMPOSITE_H_INCLUDED

View file

@ -0,0 +1,289 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
DrawableImage::DrawableImage()
: opacity (1.0f),
overlayColour (0x00000000)
{
bounds.topRight = RelativePoint (Point<float> (1.0f, 0.0f));
bounds.bottomLeft = RelativePoint (Point<float> (0.0f, 1.0f));
}
DrawableImage::DrawableImage (const DrawableImage& other)
: Drawable (other),
image (other.image),
opacity (other.opacity),
overlayColour (other.overlayColour),
bounds (other.bounds)
{
}
DrawableImage::~DrawableImage()
{
}
//==============================================================================
void DrawableImage::setImage (const Image& imageToUse)
{
image = imageToUse;
setBounds (imageToUse.getBounds());
bounds.topLeft = RelativePoint (Point<float> (0.0f, 0.0f));
bounds.topRight = RelativePoint (Point<float> ((float) image.getWidth(), 0.0f));
bounds.bottomLeft = RelativePoint (Point<float> (0.0f, (float) image.getHeight()));
recalculateCoordinates (nullptr);
repaint();
}
void DrawableImage::setOpacity (const float newOpacity)
{
opacity = newOpacity;
}
void DrawableImage::setOverlayColour (Colour newOverlayColour)
{
overlayColour = newOverlayColour;
}
void DrawableImage::setBoundingBox (const RelativeParallelogram& newBounds)
{
if (bounds != newBounds)
{
bounds = newBounds;
if (bounds.isDynamic())
{
Drawable::Positioner<DrawableImage>* const p = new Drawable::Positioner<DrawableImage> (*this);
setPositioner (p);
p->apply();
}
else
{
setPositioner (nullptr);
recalculateCoordinates (nullptr);
}
}
}
//==============================================================================
bool DrawableImage::registerCoordinates (RelativeCoordinatePositionerBase& pos)
{
bool ok = pos.addPoint (bounds.topLeft);
ok = pos.addPoint (bounds.topRight) && ok;
return pos.addPoint (bounds.bottomLeft) && ok;
}
void DrawableImage::recalculateCoordinates (Expression::Scope* scope)
{
if (image.isValid())
{
Point<float> resolved[3];
bounds.resolveThreePoints (resolved, scope);
const Point<float> tr (resolved[0] + (resolved[1] - resolved[0]) / (float) image.getWidth());
const Point<float> bl (resolved[0] + (resolved[2] - resolved[0]) / (float) image.getHeight());
AffineTransform t (AffineTransform::fromTargetPoints (resolved[0].x, resolved[0].y,
tr.x, tr.y,
bl.x, bl.y));
if (t.isSingularity())
t = AffineTransform::identity;
setTransform (t);
}
}
//==============================================================================
void DrawableImage::paint (Graphics& g)
{
if (image.isValid())
{
if (opacity > 0.0f && ! overlayColour.isOpaque())
{
g.setOpacity (opacity);
g.drawImageAt (image, 0, 0, false);
}
if (! overlayColour.isTransparent())
{
g.setColour (overlayColour.withMultipliedAlpha (opacity));
g.drawImageAt (image, 0, 0, true);
}
}
}
Rectangle<float> DrawableImage::getDrawableBounds() const
{
return image.getBounds().toFloat();
}
bool DrawableImage::hitTest (int x, int y)
{
return Drawable::hitTest (x, y) && image.isValid() && image.getPixelAt (x, y).getAlpha() >= 127;
}
Drawable* DrawableImage::createCopy() const
{
return new DrawableImage (*this);
}
//==============================================================================
const Identifier DrawableImage::valueTreeType ("Image");
const Identifier DrawableImage::ValueTreeWrapper::opacity ("opacity");
const Identifier DrawableImage::ValueTreeWrapper::overlay ("overlay");
const Identifier DrawableImage::ValueTreeWrapper::image ("image");
const Identifier DrawableImage::ValueTreeWrapper::topLeft ("topLeft");
const Identifier DrawableImage::ValueTreeWrapper::topRight ("topRight");
const Identifier DrawableImage::ValueTreeWrapper::bottomLeft ("bottomLeft");
//==============================================================================
DrawableImage::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
: ValueTreeWrapperBase (state_)
{
jassert (state.hasType (valueTreeType));
}
var DrawableImage::ValueTreeWrapper::getImageIdentifier() const
{
return state [image];
}
Value DrawableImage::ValueTreeWrapper::getImageIdentifierValue (UndoManager* undoManager)
{
return state.getPropertyAsValue (image, undoManager);
}
void DrawableImage::ValueTreeWrapper::setImageIdentifier (const var& newIdentifier, UndoManager* undoManager)
{
state.setProperty (image, newIdentifier, undoManager);
}
float DrawableImage::ValueTreeWrapper::getOpacity() const
{
return (float) state.getProperty (opacity, 1.0);
}
Value DrawableImage::ValueTreeWrapper::getOpacityValue (UndoManager* undoManager)
{
if (! state.hasProperty (opacity))
state.setProperty (opacity, 1.0, undoManager);
return state.getPropertyAsValue (opacity, undoManager);
}
void DrawableImage::ValueTreeWrapper::setOpacity (float newOpacity, UndoManager* undoManager)
{
state.setProperty (opacity, newOpacity, undoManager);
}
Colour DrawableImage::ValueTreeWrapper::getOverlayColour() const
{
return Colour::fromString (state [overlay].toString());
}
void DrawableImage::ValueTreeWrapper::setOverlayColour (Colour newColour, UndoManager* undoManager)
{
if (newColour.isTransparent())
state.removeProperty (overlay, undoManager);
else
state.setProperty (overlay, String::toHexString ((int) newColour.getARGB()), undoManager);
}
Value DrawableImage::ValueTreeWrapper::getOverlayColourValue (UndoManager* undoManager)
{
return state.getPropertyAsValue (overlay, undoManager);
}
RelativeParallelogram DrawableImage::ValueTreeWrapper::getBoundingBox() const
{
return RelativeParallelogram (state.getProperty (topLeft, "0, 0"),
state.getProperty (topRight, "100, 0"),
state.getProperty (bottomLeft, "0, 100"));
}
void DrawableImage::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager)
{
state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager);
state.setProperty (topRight, newBounds.topRight.toString(), undoManager);
state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager);
}
//==============================================================================
void DrawableImage::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder)
{
const ValueTreeWrapper controller (tree);
setComponentID (controller.getID());
const float newOpacity = controller.getOpacity();
const Colour newOverlayColour (controller.getOverlayColour());
Image newImage;
const var imageIdentifier (controller.getImageIdentifier());
jassert (builder.getImageProvider() != 0 || imageIdentifier.isVoid()); // if you're using images, you need to provide something that can load and save them!
if (builder.getImageProvider() != nullptr)
newImage = builder.getImageProvider()->getImageForIdentifier (imageIdentifier);
const RelativeParallelogram newBounds (controller.getBoundingBox());
if (bounds != newBounds || newOpacity != opacity
|| overlayColour != newOverlayColour || image != newImage)
{
repaint();
opacity = newOpacity;
overlayColour = newOverlayColour;
if (image != newImage)
setImage (newImage);
setBoundingBox (newBounds);
}
}
ValueTree DrawableImage::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const
{
ValueTree tree (valueTreeType);
ValueTreeWrapper v (tree);
v.setID (getComponentID());
v.setOpacity (opacity, nullptr);
v.setOverlayColour (overlayColour, nullptr);
v.setBoundingBox (bounds, nullptr);
if (image.isValid())
{
jassert (imageProvider != nullptr); // if you're using images, you need to provide something that can load and save them!
if (imageProvider != nullptr)
v.setImageIdentifier (imageProvider->getIdentifierForImage (image), nullptr);
}
return tree;
}

View file

@ -0,0 +1,138 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_DRAWABLEIMAGE_H_INCLUDED
#define JUCE_DRAWABLEIMAGE_H_INCLUDED
//==============================================================================
/**
A drawable object which is a bitmap image.
@see Drawable
*/
class JUCE_API DrawableImage : public Drawable
{
public:
//==============================================================================
DrawableImage();
DrawableImage (const DrawableImage&);
/** Destructor. */
~DrawableImage();
//==============================================================================
/** Sets the image that this drawable will render. */
void setImage (const Image& imageToUse);
/** Returns the current image. */
const Image& getImage() const noexcept { return image; }
/** Sets the opacity to use when drawing the image. */
void setOpacity (float newOpacity);
/** Returns the image's opacity. */
float getOpacity() const noexcept { return opacity; }
/** Sets a colour to draw over the image's alpha channel.
By default this is transparent so isn't drawn, but if you set a non-transparent
colour here, then it will be overlaid on the image, using the image's alpha
channel as a mask.
This is handy for doing things like darkening or lightening an image by overlaying
it with semi-transparent black or white.
*/
void setOverlayColour (Colour newOverlayColour);
/** Returns the overlay colour. */
Colour getOverlayColour() const noexcept { return overlayColour; }
/** Sets the bounding box within which the image should be displayed. */
void setBoundingBox (const RelativeParallelogram& newBounds);
/** Returns the position to which the image's top-left corner should be remapped in the target
coordinate space when rendering this object.
@see setTransform
*/
const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; }
//==============================================================================
/** @internal */
void paint (Graphics&) override;
/** @internal */
bool hitTest (int x, int y) override;
/** @internal */
Drawable* createCopy() const override;
/** @internal */
Rectangle<float> getDrawableBounds() const override;
/** @internal */
void refreshFromValueTree (const ValueTree& tree, ComponentBuilder&);
/** @internal */
ValueTree createValueTree (ComponentBuilder::ImageProvider*) const override;
/** @internal */
static const Identifier valueTreeType;
//==============================================================================
/** Internally-used class for wrapping a DrawableImage's state into a ValueTree. */
class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase
{
public:
ValueTreeWrapper (const ValueTree& state);
var getImageIdentifier() const;
void setImageIdentifier (const var&, UndoManager*);
Value getImageIdentifierValue (UndoManager*);
float getOpacity() const;
void setOpacity (float newOpacity, UndoManager*);
Value getOpacityValue (UndoManager*);
Colour getOverlayColour() const;
void setOverlayColour (Colour newColour, UndoManager*);
Value getOverlayColourValue (UndoManager*);
RelativeParallelogram getBoundingBox() const;
void setBoundingBox (const RelativeParallelogram&, UndoManager*);
static const Identifier opacity, overlay, image, topLeft, topRight, bottomLeft;
};
private:
//==============================================================================
Image image;
float opacity;
Colour overlayColour;
RelativeParallelogram bounds;
friend class Drawable::Positioner<DrawableImage>;
bool registerCoordinates (RelativeCoordinatePositionerBase&);
void recalculateCoordinates (Expression::Scope*);
DrawableImage& operator= (const DrawableImage&);
JUCE_LEAK_DETECTOR (DrawableImage)
};
#endif // JUCE_DRAWABLEIMAGE_H_INCLUDED

View file

@ -0,0 +1,570 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
DrawablePath::DrawablePath()
{
}
DrawablePath::DrawablePath (const DrawablePath& other)
: DrawableShape (other)
{
if (other.relativePath != nullptr)
setPath (*other.relativePath);
else
setPath (other.path);
}
DrawablePath::~DrawablePath()
{
}
Drawable* DrawablePath::createCopy() const
{
return new DrawablePath (*this);
}
//==============================================================================
void DrawablePath::setPath (const Path& newPath)
{
path = newPath;
pathChanged();
}
const Path& DrawablePath::getPath() const
{
return path;
}
const Path& DrawablePath::getStrokePath() const
{
return strokePath;
}
void DrawablePath::applyRelativePath (const RelativePointPath& newRelativePath, Expression::Scope* scope)
{
Path newPath;
newRelativePath.createPath (newPath, scope);
if (path != newPath)
{
path.swapWithPath (newPath);
pathChanged();
}
}
//==============================================================================
class DrawablePath::RelativePositioner : public RelativeCoordinatePositionerBase
{
public:
RelativePositioner (DrawablePath& comp)
: RelativeCoordinatePositionerBase (comp),
owner (comp)
{
}
bool registerCoordinates()
{
bool ok = true;
jassert (owner.relativePath != nullptr);
const RelativePointPath& path = *owner.relativePath;
for (int i = 0; i < path.elements.size(); ++i)
{
RelativePointPath::ElementBase* const e = path.elements.getUnchecked(i);
int numPoints;
RelativePoint* const points = e->getControlPoints (numPoints);
for (int j = numPoints; --j >= 0;)
ok = addPoint (points[j]) && ok;
}
return ok;
}
void applyToComponentBounds()
{
jassert (owner.relativePath != nullptr);
ComponentScope scope (getComponent());
owner.applyRelativePath (*owner.relativePath, &scope);
}
void applyNewBounds (const Rectangle<int>&)
{
jassertfalse; // drawables can't be resized directly!
}
private:
DrawablePath& owner;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RelativePositioner)
};
void DrawablePath::setPath (const RelativePointPath& newRelativePath)
{
if (newRelativePath.containsAnyDynamicPoints())
{
if (relativePath == nullptr || newRelativePath != *relativePath)
{
relativePath = new RelativePointPath (newRelativePath);
RelativePositioner* const p = new RelativePositioner (*this);
setPositioner (p);
p->apply();
}
}
else
{
relativePath = nullptr;
applyRelativePath (newRelativePath, nullptr);
}
}
//==============================================================================
const Identifier DrawablePath::valueTreeType ("Path");
const Identifier DrawablePath::ValueTreeWrapper::nonZeroWinding ("nonZeroWinding");
const Identifier DrawablePath::ValueTreeWrapper::point1 ("p1");
const Identifier DrawablePath::ValueTreeWrapper::point2 ("p2");
const Identifier DrawablePath::ValueTreeWrapper::point3 ("p3");
//==============================================================================
DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
: FillAndStrokeState (state_)
{
jassert (state.hasType (valueTreeType));
}
ValueTree DrawablePath::ValueTreeWrapper::getPathState()
{
return state.getOrCreateChildWithName (path, nullptr);
}
bool DrawablePath::ValueTreeWrapper::usesNonZeroWinding() const
{
return state [nonZeroWinding];
}
void DrawablePath::ValueTreeWrapper::setUsesNonZeroWinding (bool b, UndoManager* undoManager)
{
state.setProperty (nonZeroWinding, b, undoManager);
}
void DrawablePath::ValueTreeWrapper::readFrom (const RelativePointPath& p, UndoManager* undoManager)
{
setUsesNonZeroWinding (p.usesNonZeroWinding, undoManager);
ValueTree pathTree (getPathState());
pathTree.removeAllChildren (undoManager);
for (int i = 0; i < p.elements.size(); ++i)
pathTree.addChild (p.elements.getUnchecked(i)->createTree(), -1, undoManager);
}
void DrawablePath::ValueTreeWrapper::writeTo (RelativePointPath& p) const
{
p.usesNonZeroWinding = usesNonZeroWinding();
RelativePoint points[3];
const ValueTree pathTree (state.getChildWithName (path));
const int num = pathTree.getNumChildren();
for (int i = 0; i < num; ++i)
{
const Element e (pathTree.getChild(i));
const int numCps = e.getNumControlPoints();
for (int j = 0; j < numCps; ++j)
points[j] = e.getControlPoint (j);
RelativePointPath::ElementBase* newElement = nullptr;
const Identifier t (e.getType());
if (t == Element::startSubPathElement) newElement = new RelativePointPath::StartSubPath (points[0]);
else if (t == Element::closeSubPathElement) newElement = new RelativePointPath::CloseSubPath();
else if (t == Element::lineToElement) newElement = new RelativePointPath::LineTo (points[0]);
else if (t == Element::quadraticToElement) newElement = new RelativePointPath::QuadraticTo (points[0], points[1]);
else if (t == Element::cubicToElement) newElement = new RelativePointPath::CubicTo (points[0], points[1], points[2]);
else jassertfalse;
p.addElement (newElement);
}
}
//==============================================================================
const Identifier DrawablePath::ValueTreeWrapper::Element::mode ("mode");
const Identifier DrawablePath::ValueTreeWrapper::Element::startSubPathElement ("Move");
const Identifier DrawablePath::ValueTreeWrapper::Element::closeSubPathElement ("Close");
const Identifier DrawablePath::ValueTreeWrapper::Element::lineToElement ("Line");
const Identifier DrawablePath::ValueTreeWrapper::Element::quadraticToElement ("Quad");
const Identifier DrawablePath::ValueTreeWrapper::Element::cubicToElement ("Cubic");
const char* DrawablePath::ValueTreeWrapper::Element::cornerMode = "corner";
const char* DrawablePath::ValueTreeWrapper::Element::roundedMode = "round";
const char* DrawablePath::ValueTreeWrapper::Element::symmetricMode = "symm";
DrawablePath::ValueTreeWrapper::Element::Element (const ValueTree& state_)
: state (state_)
{
}
DrawablePath::ValueTreeWrapper::Element::~Element()
{
}
DrawablePath::ValueTreeWrapper DrawablePath::ValueTreeWrapper::Element::getParent() const
{
return ValueTreeWrapper (state.getParent().getParent());
}
DrawablePath::ValueTreeWrapper::Element DrawablePath::ValueTreeWrapper::Element::getPreviousElement() const
{
return Element (state.getSibling (-1));
}
int DrawablePath::ValueTreeWrapper::Element::getNumControlPoints() const noexcept
{
const Identifier i (state.getType());
if (i == startSubPathElement || i == lineToElement) return 1;
if (i == quadraticToElement) return 2;
if (i == cubicToElement) return 3;
return 0;
}
RelativePoint DrawablePath::ValueTreeWrapper::Element::getControlPoint (const int index) const
{
jassert (index >= 0 && index < getNumControlPoints());
return RelativePoint (state [index == 0 ? point1 : (index == 1 ? point2 : point3)].toString());
}
Value DrawablePath::ValueTreeWrapper::Element::getControlPointValue (int index, UndoManager* undoManager)
{
jassert (index >= 0 && index < getNumControlPoints());
return state.getPropertyAsValue (index == 0 ? point1 : (index == 1 ? point2 : point3), undoManager);
}
void DrawablePath::ValueTreeWrapper::Element::setControlPoint (const int index, const RelativePoint& point, UndoManager* undoManager)
{
jassert (index >= 0 && index < getNumControlPoints());
state.setProperty (index == 0 ? point1 : (index == 1 ? point2 : point3), point.toString(), undoManager);
}
RelativePoint DrawablePath::ValueTreeWrapper::Element::getStartPoint() const
{
const Identifier i (state.getType());
if (i == startSubPathElement)
return getControlPoint (0);
jassert (i == lineToElement || i == quadraticToElement || i == cubicToElement || i == closeSubPathElement);
return getPreviousElement().getEndPoint();
}
RelativePoint DrawablePath::ValueTreeWrapper::Element::getEndPoint() const
{
const Identifier i (state.getType());
if (i == startSubPathElement || i == lineToElement) return getControlPoint (0);
if (i == quadraticToElement) return getControlPoint (1);
if (i == cubicToElement) return getControlPoint (2);
jassert (i == closeSubPathElement);
return RelativePoint();
}
float DrawablePath::ValueTreeWrapper::Element::getLength (Expression::Scope* scope) const
{
const Identifier i (state.getType());
if (i == lineToElement || i == closeSubPathElement)
return getEndPoint().resolve (scope).getDistanceFrom (getStartPoint().resolve (scope));
if (i == cubicToElement)
{
Path p;
p.startNewSubPath (getStartPoint().resolve (scope));
p.cubicTo (getControlPoint (0).resolve (scope), getControlPoint (1).resolve (scope), getControlPoint (2).resolve (scope));
return p.getLength();
}
if (i == quadraticToElement)
{
Path p;
p.startNewSubPath (getStartPoint().resolve (scope));
p.quadraticTo (getControlPoint (0).resolve (scope), getControlPoint (1).resolve (scope));
return p.getLength();
}
jassert (i == startSubPathElement);
return 0;
}
String DrawablePath::ValueTreeWrapper::Element::getModeOfEndPoint() const
{
return state [mode].toString();
}
void DrawablePath::ValueTreeWrapper::Element::setModeOfEndPoint (const String& newMode, UndoManager* undoManager)
{
if (state.hasType (cubicToElement))
state.setProperty (mode, newMode, undoManager);
}
void DrawablePath::ValueTreeWrapper::Element::convertToLine (UndoManager* undoManager)
{
const Identifier i (state.getType());
if (i == quadraticToElement || i == cubicToElement)
{
ValueTree newState (lineToElement);
Element e (newState);
e.setControlPoint (0, getEndPoint(), undoManager);
state = newState;
}
}
void DrawablePath::ValueTreeWrapper::Element::convertToCubic (Expression::Scope* scope, UndoManager* undoManager)
{
const Identifier i (state.getType());
if (i == lineToElement || i == quadraticToElement)
{
ValueTree newState (cubicToElement);
Element e (newState);
const RelativePoint start (getStartPoint());
const RelativePoint end (getEndPoint());
const Point<float> startResolved (start.resolve (scope));
const Point<float> endResolved (end.resolve (scope));
e.setControlPoint (0, startResolved + (endResolved - startResolved) * 0.3f, undoManager);
e.setControlPoint (1, startResolved + (endResolved - startResolved) * 0.7f, undoManager);
e.setControlPoint (2, end, undoManager);
state = newState;
}
}
void DrawablePath::ValueTreeWrapper::Element::convertToPathBreak (UndoManager* undoManager)
{
const Identifier i (state.getType());
if (i != startSubPathElement)
{
ValueTree newState (startSubPathElement);
Element e (newState);
e.setControlPoint (0, getEndPoint(), undoManager);
state = newState;
}
}
namespace DrawablePathHelpers
{
static Point<float> findCubicSubdivisionPoint (float proportion, const Point<float> points[4])
{
const Point<float> mid1 (points[0] + (points[1] - points[0]) * proportion),
mid2 (points[1] + (points[2] - points[1]) * proportion),
mid3 (points[2] + (points[3] - points[2]) * proportion);
const Point<float> newCp1 (mid1 + (mid2 - mid1) * proportion),
newCp2 (mid2 + (mid3 - mid2) * proportion);
return newCp1 + (newCp2 - newCp1) * proportion;
}
static Point<float> findQuadraticSubdivisionPoint (float proportion, const Point<float> points[3])
{
const Point<float> mid1 (points[0] + (points[1] - points[0]) * proportion),
mid2 (points[1] + (points[2] - points[1]) * proportion);
return mid1 + (mid2 - mid1) * proportion;
}
}
float DrawablePath::ValueTreeWrapper::Element::findProportionAlongLine (Point<float> targetPoint, Expression::Scope* scope) const
{
using namespace DrawablePathHelpers;
const Identifier pointType (state.getType());
float bestProp = 0;
if (pointType == cubicToElement)
{
RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint());
const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope), rp4.resolve (scope) };
float bestDistance = std::numeric_limits<float>::max();
for (int i = 110; --i >= 0;)
{
float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f));
const Point<float> centre (findCubicSubdivisionPoint (prop, points));
const float distance = centre.getDistanceFrom (targetPoint);
if (distance < bestDistance)
{
bestProp = prop;
bestDistance = distance;
}
}
}
else if (pointType == quadraticToElement)
{
RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint());
const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope) };
float bestDistance = std::numeric_limits<float>::max();
for (int i = 110; --i >= 0;)
{
float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f));
const Point<float> centre (findQuadraticSubdivisionPoint ((float) prop, points));
const float distance = centre.getDistanceFrom (targetPoint);
if (distance < bestDistance)
{
bestProp = prop;
bestDistance = distance;
}
}
}
else if (pointType == lineToElement)
{
RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint());
const Line<float> line (rp1.resolve (scope), rp2.resolve (scope));
bestProp = line.findNearestProportionalPositionTo (targetPoint);
}
return bestProp;
}
ValueTree DrawablePath::ValueTreeWrapper::Element::insertPoint (Point<float> targetPoint, Expression::Scope* scope, UndoManager* undoManager)
{
ValueTree newTree;
const Identifier pointType (state.getType());
if (pointType == cubicToElement)
{
float bestProp = findProportionAlongLine (targetPoint, scope);
RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint());
const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope), rp4.resolve (scope) };
const Point<float> mid1 (points[0] + (points[1] - points[0]) * bestProp),
mid2 (points[1] + (points[2] - points[1]) * bestProp),
mid3 (points[2] + (points[3] - points[2]) * bestProp);
const Point<float> newCp1 (mid1 + (mid2 - mid1) * bestProp),
newCp2 (mid2 + (mid3 - mid2) * bestProp);
const Point<float> newCentre (newCp1 + (newCp2 - newCp1) * bestProp);
setControlPoint (0, mid1, undoManager);
setControlPoint (1, newCp1, undoManager);
setControlPoint (2, newCentre, undoManager);
setModeOfEndPoint (roundedMode, undoManager);
Element newElement (newTree = ValueTree (cubicToElement));
newElement.setControlPoint (0, newCp2, nullptr);
newElement.setControlPoint (1, mid3, nullptr);
newElement.setControlPoint (2, rp4, nullptr);
state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
}
else if (pointType == quadraticToElement)
{
float bestProp = findProportionAlongLine (targetPoint, scope);
RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint());
const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope) };
const Point<float> mid1 (points[0] + (points[1] - points[0]) * bestProp),
mid2 (points[1] + (points[2] - points[1]) * bestProp);
const Point<float> newCentre (mid1 + (mid2 - mid1) * bestProp);
setControlPoint (0, mid1, undoManager);
setControlPoint (1, newCentre, undoManager);
setModeOfEndPoint (roundedMode, undoManager);
Element newElement (newTree = ValueTree (quadraticToElement));
newElement.setControlPoint (0, mid2, nullptr);
newElement.setControlPoint (1, rp3, nullptr);
state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
}
else if (pointType == lineToElement)
{
RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint());
const Line<float> line (rp1.resolve (scope), rp2.resolve (scope));
const Point<float> newPoint (line.findNearestPointTo (targetPoint));
setControlPoint (0, newPoint, undoManager);
Element newElement (newTree = ValueTree (lineToElement));
newElement.setControlPoint (0, rp2, nullptr);
state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager);
}
else if (pointType == closeSubPathElement)
{
}
return newTree;
}
void DrawablePath::ValueTreeWrapper::Element::removePoint (UndoManager* undoManager)
{
state.getParent().removeChild (state, undoManager);
}
//==============================================================================
void DrawablePath::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder)
{
ValueTreeWrapper v (tree);
setComponentID (v.getID());
refreshFillTypes (v, builder.getImageProvider());
setStrokeType (v.getStrokeType());
RelativePointPath newRelativePath;
v.writeTo (newRelativePath);
setPath (newRelativePath);
}
ValueTree DrawablePath::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const
{
ValueTree tree (valueTreeType);
ValueTreeWrapper v (tree);
v.setID (getComponentID());
writeTo (v, imageProvider, nullptr);
if (relativePath != nullptr)
v.readFrom (*relativePath, nullptr);
else
v.readFrom (RelativePointPath (path), nullptr);
return tree;
}

View file

@ -0,0 +1,145 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_DRAWABLEPATH_H_INCLUDED
#define JUCE_DRAWABLEPATH_H_INCLUDED
//==============================================================================
/**
A drawable object which renders a filled or outlined shape.
For details on how to change the fill and stroke, see the DrawableShape class.
@see Drawable, DrawableShape
*/
class JUCE_API DrawablePath : public DrawableShape
{
public:
//==============================================================================
/** Creates a DrawablePath. */
DrawablePath();
DrawablePath (const DrawablePath&);
/** Destructor. */
~DrawablePath();
//==============================================================================
/** Changes the path that will be drawn.
@see setFillColour, setStrokeType
*/
void setPath (const Path& newPath);
/** Sets the path using a RelativePointPath.
Calling this will set up a Component::Positioner to automatically update the path
if any of the points in the source path are dynamic.
*/
void setPath (const RelativePointPath& newPath);
/** Returns the current path. */
const Path& getPath() const;
/** Returns the current path for the outline. */
const Path& getStrokePath() const;
//==============================================================================
/** @internal */
Drawable* createCopy() const;
/** @internal */
void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder);
/** @internal */
ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const;
/** @internal */
static const Identifier valueTreeType;
//==============================================================================
/** Internally-used class for wrapping a DrawablePath's state into a ValueTree. */
class ValueTreeWrapper : public DrawableShape::FillAndStrokeState
{
public:
ValueTreeWrapper (const ValueTree& state);
bool usesNonZeroWinding() const;
void setUsesNonZeroWinding (bool b, UndoManager* undoManager);
class Element
{
public:
explicit Element (const ValueTree& state);
~Element();
const Identifier getType() const noexcept { return state.getType(); }
int getNumControlPoints() const noexcept;
RelativePoint getControlPoint (int index) const;
Value getControlPointValue (int index, UndoManager*);
RelativePoint getStartPoint() const;
RelativePoint getEndPoint() const;
void setControlPoint (int index, const RelativePoint& point, UndoManager*);
float getLength (Expression::Scope*) const;
ValueTreeWrapper getParent() const;
Element getPreviousElement() const;
String getModeOfEndPoint() const;
void setModeOfEndPoint (const String& newMode, UndoManager*);
void convertToLine (UndoManager*);
void convertToCubic (Expression::Scope*, UndoManager*);
void convertToPathBreak (UndoManager* undoManager);
ValueTree insertPoint (Point<float> targetPoint, Expression::Scope*, UndoManager*);
void removePoint (UndoManager* undoManager);
float findProportionAlongLine (Point<float> targetPoint, Expression::Scope*) const;
static const Identifier mode, startSubPathElement, closeSubPathElement,
lineToElement, quadraticToElement, cubicToElement;
static const char* cornerMode;
static const char* roundedMode;
static const char* symmetricMode;
ValueTree state;
};
ValueTree getPathState();
void readFrom (const RelativePointPath& path, UndoManager* undoManager);
void writeTo (RelativePointPath& path) const;
static const Identifier nonZeroWinding, point1, point2, point3;
};
private:
//==============================================================================
ScopedPointer<RelativePointPath> relativePath;
class RelativePositioner;
friend class RelativePositioner;
void applyRelativePath (const RelativePointPath&, Expression::Scope*);
DrawablePath& operator= (const DrawablePath&);
JUCE_LEAK_DETECTOR (DrawablePath)
};
#endif // JUCE_DRAWABLEPATH_H_INCLUDED

View file

@ -0,0 +1,182 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
DrawableRectangle::DrawableRectangle()
{
}
DrawableRectangle::DrawableRectangle (const DrawableRectangle& other)
: DrawableShape (other),
bounds (other.bounds),
cornerSize (other.cornerSize)
{
}
DrawableRectangle::~DrawableRectangle()
{
}
Drawable* DrawableRectangle::createCopy() const
{
return new DrawableRectangle (*this);
}
//==============================================================================
void DrawableRectangle::setRectangle (const RelativeParallelogram& newBounds)
{
if (bounds != newBounds)
{
bounds = newBounds;
rebuildPath();
}
}
void DrawableRectangle::setCornerSize (const RelativePoint& newSize)
{
if (cornerSize != newSize)
{
cornerSize = newSize;
rebuildPath();
}
}
void DrawableRectangle::rebuildPath()
{
if (bounds.isDynamic() || cornerSize.isDynamic())
{
Drawable::Positioner<DrawableRectangle>* const p = new Drawable::Positioner<DrawableRectangle> (*this);
setPositioner (p);
p->apply();
}
else
{
setPositioner (nullptr);
recalculateCoordinates (nullptr);
}
}
bool DrawableRectangle::registerCoordinates (RelativeCoordinatePositionerBase& pos)
{
bool ok = pos.addPoint (bounds.topLeft);
ok = pos.addPoint (bounds.topRight) && ok;
ok = pos.addPoint (bounds.bottomLeft) && ok;
return pos.addPoint (cornerSize) && ok;
}
void DrawableRectangle::recalculateCoordinates (Expression::Scope* scope)
{
Point<float> points[3];
bounds.resolveThreePoints (points, scope);
const float cornerSizeX = (float) cornerSize.x.resolve (scope);
const float cornerSizeY = (float) cornerSize.y.resolve (scope);
const float w = Line<float> (points[0], points[1]).getLength();
const float h = Line<float> (points[0], points[2]).getLength();
Path newPath;
if (cornerSizeX > 0 && cornerSizeY > 0)
newPath.addRoundedRectangle (0, 0, w, h, cornerSizeX, cornerSizeY);
else
newPath.addRectangle (0, 0, w, h);
newPath.applyTransform (AffineTransform::fromTargetPoints (0, 0, points[0].x, points[0].y,
w, 0, points[1].x, points[1].y,
0, h, points[2].x, points[2].y));
if (path != newPath)
{
path.swapWithPath (newPath);
pathChanged();
}
}
//==============================================================================
const Identifier DrawableRectangle::valueTreeType ("Rectangle");
const Identifier DrawableRectangle::ValueTreeWrapper::topLeft ("topLeft");
const Identifier DrawableRectangle::ValueTreeWrapper::topRight ("topRight");
const Identifier DrawableRectangle::ValueTreeWrapper::bottomLeft ("bottomLeft");
const Identifier DrawableRectangle::ValueTreeWrapper::cornerSize ("cornerSize");
//==============================================================================
DrawableRectangle::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
: FillAndStrokeState (state_)
{
jassert (state.hasType (valueTreeType));
}
RelativeParallelogram DrawableRectangle::ValueTreeWrapper::getRectangle() const
{
return RelativeParallelogram (state.getProperty (topLeft, "0, 0"),
state.getProperty (topRight, "100, 0"),
state.getProperty (bottomLeft, "0, 100"));
}
void DrawableRectangle::ValueTreeWrapper::setRectangle (const RelativeParallelogram& newBounds, UndoManager* undoManager)
{
state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager);
state.setProperty (topRight, newBounds.topRight.toString(), undoManager);
state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager);
}
void DrawableRectangle::ValueTreeWrapper::setCornerSize (const RelativePoint& newSize, UndoManager* undoManager)
{
state.setProperty (cornerSize, newSize.toString(), undoManager);
}
RelativePoint DrawableRectangle::ValueTreeWrapper::getCornerSize() const
{
return RelativePoint (state [cornerSize]);
}
Value DrawableRectangle::ValueTreeWrapper::getCornerSizeValue (UndoManager* undoManager)
{
return state.getPropertyAsValue (cornerSize, undoManager);
}
//==============================================================================
void DrawableRectangle::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder)
{
ValueTreeWrapper v (tree);
setComponentID (v.getID());
refreshFillTypes (v, builder.getImageProvider());
setStrokeType (v.getStrokeType());
setRectangle (v.getRectangle());
setCornerSize (v.getCornerSize());
}
ValueTree DrawableRectangle::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const
{
ValueTree tree (valueTreeType);
ValueTreeWrapper v (tree);
v.setID (getComponentID());
writeTo (v, imageProvider, nullptr);
v.setRectangle (bounds, nullptr);
v.setCornerSize (cornerSize, nullptr);
return tree;
}

View file

@ -0,0 +1,103 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_DRAWABLERECTANGLE_H_INCLUDED
#define JUCE_DRAWABLERECTANGLE_H_INCLUDED
//==============================================================================
/**
A Drawable object which draws a rectangle.
For details on how to change the fill and stroke, see the DrawableShape class.
@see Drawable, DrawableShape
*/
class JUCE_API DrawableRectangle : public DrawableShape
{
public:
//==============================================================================
DrawableRectangle();
DrawableRectangle (const DrawableRectangle&);
/** Destructor. */
~DrawableRectangle();
//==============================================================================
/** Sets the rectangle's bounds. */
void setRectangle (const RelativeParallelogram& newBounds);
/** Returns the rectangle's bounds. */
const RelativeParallelogram& getRectangle() const noexcept { return bounds; }
/** Returns the corner size to be used. */
const RelativePoint& getCornerSize() const noexcept { return cornerSize; }
/** Sets a new corner size for the rectangle */
void setCornerSize (const RelativePoint& newSize);
//==============================================================================
/** @internal */
Drawable* createCopy() const;
/** @internal */
void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder);
/** @internal */
ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const;
/** @internal */
static const Identifier valueTreeType;
//==============================================================================
/** Internally-used class for wrapping a DrawableRectangle's state into a ValueTree. */
class ValueTreeWrapper : public DrawableShape::FillAndStrokeState
{
public:
ValueTreeWrapper (const ValueTree& state);
RelativeParallelogram getRectangle() const;
void setRectangle (const RelativeParallelogram& newBounds, UndoManager*);
void setCornerSize (const RelativePoint& cornerSize, UndoManager*);
RelativePoint getCornerSize() const;
Value getCornerSizeValue (UndoManager*);
static const Identifier topLeft, topRight, bottomLeft, cornerSize;
};
private:
friend class Drawable::Positioner<DrawableRectangle>;
RelativeParallelogram bounds;
RelativePoint cornerSize;
void rebuildPath();
bool registerCoordinates (RelativeCoordinatePositionerBase&);
void recalculateCoordinates (Expression::Scope*);
DrawableRectangle& operator= (const DrawableRectangle&);
JUCE_LEAK_DETECTOR (DrawableRectangle)
};
#endif // JUCE_DRAWABLERECTANGLE_H_INCLUDED

View file

@ -0,0 +1,472 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
DrawableShape::DrawableShape()
: strokeType (0.0f),
mainFill (Colours::black),
strokeFill (Colours::black)
{
}
DrawableShape::DrawableShape (const DrawableShape& other)
: Drawable (other),
strokeType (other.strokeType),
mainFill (other.mainFill),
strokeFill (other.strokeFill)
{
}
DrawableShape::~DrawableShape()
{
}
//==============================================================================
class DrawableShape::RelativePositioner : public RelativeCoordinatePositionerBase
{
public:
RelativePositioner (DrawableShape& comp, const DrawableShape::RelativeFillType& f, bool isMain)
: RelativeCoordinatePositionerBase (comp),
owner (comp),
fill (f),
isMainFill (isMain)
{
}
bool registerCoordinates()
{
bool ok = addPoint (fill.gradientPoint1);
ok = addPoint (fill.gradientPoint2) && ok;
return addPoint (fill.gradientPoint3) && ok;
}
void applyToComponentBounds()
{
ComponentScope scope (owner);
if (isMainFill ? owner.mainFill.recalculateCoords (&scope)
: owner.strokeFill.recalculateCoords (&scope))
owner.repaint();
}
void applyNewBounds (const Rectangle<int>&)
{
jassertfalse; // drawables can't be resized directly!
}
private:
DrawableShape& owner;
const DrawableShape::RelativeFillType fill;
const bool isMainFill;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RelativePositioner)
};
void DrawableShape::setFill (const FillType& newFill)
{
setFill (RelativeFillType (newFill));
}
void DrawableShape::setStrokeFill (const FillType& newFill)
{
setStrokeFill (RelativeFillType (newFill));
}
void DrawableShape::setFillInternal (RelativeFillType& fill, const RelativeFillType& newFill,
ScopedPointer<RelativeCoordinatePositionerBase>& pos)
{
if (fill != newFill)
{
fill = newFill;
pos = nullptr;
if (fill.isDynamic())
{
pos = new RelativePositioner (*this, fill, true);
pos->apply();
}
else
{
fill.recalculateCoords (nullptr);
}
repaint();
}
}
void DrawableShape::setFill (const RelativeFillType& newFill)
{
setFillInternal (mainFill, newFill, mainFillPositioner);
}
void DrawableShape::setStrokeFill (const RelativeFillType& newFill)
{
setFillInternal (strokeFill, newFill, strokeFillPositioner);
}
void DrawableShape::setStrokeType (const PathStrokeType& newStrokeType)
{
if (strokeType != newStrokeType)
{
strokeType = newStrokeType;
strokeChanged();
}
}
void DrawableShape::setStrokeThickness (const float newThickness)
{
setStrokeType (PathStrokeType (newThickness, strokeType.getJointStyle(), strokeType.getEndStyle()));
}
bool DrawableShape::isStrokeVisible() const noexcept
{
return strokeType.getStrokeThickness() > 0.0f && ! strokeFill.fill.isInvisible();
}
void DrawableShape::refreshFillTypes (const FillAndStrokeState& newState, ComponentBuilder::ImageProvider* imageProvider)
{
setFill (newState.getFill (FillAndStrokeState::fill, imageProvider));
setStrokeFill (newState.getFill (FillAndStrokeState::stroke, imageProvider));
}
void DrawableShape::writeTo (FillAndStrokeState& state, ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager) const
{
state.setFill (FillAndStrokeState::fill, mainFill, imageProvider, undoManager);
state.setFill (FillAndStrokeState::stroke, strokeFill, imageProvider, undoManager);
state.setStrokeType (strokeType, undoManager);
}
//==============================================================================
void DrawableShape::paint (Graphics& g)
{
transformContextToCorrectOrigin (g);
g.setFillType (mainFill.fill);
g.fillPath (path);
if (isStrokeVisible())
{
g.setFillType (strokeFill.fill);
g.fillPath (strokePath);
}
}
void DrawableShape::pathChanged()
{
strokeChanged();
}
void DrawableShape::strokeChanged()
{
strokePath.clear();
strokeType.createStrokedPath (strokePath, path, AffineTransform::identity, 4.0f);
setBoundsToEnclose (getDrawableBounds());
repaint();
}
Rectangle<float> DrawableShape::getDrawableBounds() const
{
if (isStrokeVisible())
return strokePath.getBounds();
return path.getBounds();
}
bool DrawableShape::hitTest (int x, int y)
{
bool allowsClicksOnThisComponent, allowsClicksOnChildComponents;
getInterceptsMouseClicks (allowsClicksOnThisComponent, allowsClicksOnChildComponents);
if (! allowsClicksOnThisComponent)
return false;
const float globalX = (float) (x - originRelativeToComponent.x);
const float globalY = (float) (y - originRelativeToComponent.y);
return path.contains (globalX, globalY)
|| (isStrokeVisible() && strokePath.contains (globalX, globalY));
}
//==============================================================================
DrawableShape::RelativeFillType::RelativeFillType()
{
}
DrawableShape::RelativeFillType::RelativeFillType (const FillType& fill_)
: fill (fill_)
{
if (fill.isGradient())
{
const ColourGradient& g = *fill.gradient;
gradientPoint1 = g.point1.transformedBy (fill.transform);
gradientPoint2 = g.point2.transformedBy (fill.transform);
gradientPoint3 = Point<float> (g.point1.x + g.point2.y - g.point1.y,
g.point1.y + g.point1.x - g.point2.x)
.transformedBy (fill.transform);
fill.transform = AffineTransform::identity;
}
}
DrawableShape::RelativeFillType::RelativeFillType (const RelativeFillType& other)
: fill (other.fill),
gradientPoint1 (other.gradientPoint1),
gradientPoint2 (other.gradientPoint2),
gradientPoint3 (other.gradientPoint3)
{
}
DrawableShape::RelativeFillType& DrawableShape::RelativeFillType::operator= (const RelativeFillType& other)
{
fill = other.fill;
gradientPoint1 = other.gradientPoint1;
gradientPoint2 = other.gradientPoint2;
gradientPoint3 = other.gradientPoint3;
return *this;
}
bool DrawableShape::RelativeFillType::operator== (const RelativeFillType& other) const
{
return fill == other.fill
&& ((! fill.isGradient())
|| (gradientPoint1 == other.gradientPoint1
&& gradientPoint2 == other.gradientPoint2
&& gradientPoint3 == other.gradientPoint3));
}
bool DrawableShape::RelativeFillType::operator!= (const RelativeFillType& other) const
{
return ! operator== (other);
}
bool DrawableShape::RelativeFillType::recalculateCoords (Expression::Scope* scope)
{
if (fill.isGradient())
{
const Point<float> g1 (gradientPoint1.resolve (scope));
const Point<float> g2 (gradientPoint2.resolve (scope));
AffineTransform t;
ColourGradient& g = *fill.gradient;
if (g.isRadial)
{
const Point<float> g3 (gradientPoint3.resolve (scope));
const Point<float> g3Source (g1.x + g2.y - g1.y,
g1.y + g1.x - g2.x);
t = AffineTransform::fromTargetPoints (g1.x, g1.y, g1.x, g1.y,
g2.x, g2.y, g2.x, g2.y,
g3Source.x, g3Source.y, g3.x, g3.y);
}
if (g.point1 != g1 || g.point2 != g2 || fill.transform != t)
{
g.point1 = g1;
g.point2 = g2;
fill.transform = t;
return true;
}
}
return false;
}
bool DrawableShape::RelativeFillType::isDynamic() const
{
return gradientPoint1.isDynamic() || gradientPoint2.isDynamic() || gradientPoint3.isDynamic();
}
void DrawableShape::RelativeFillType::writeTo (ValueTree& v, ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager) const
{
if (fill.isColour())
{
v.setProperty (FillAndStrokeState::type, "solid", undoManager);
v.setProperty (FillAndStrokeState::colour, String::toHexString ((int) fill.colour.getARGB()), undoManager);
}
else if (fill.isGradient())
{
v.setProperty (FillAndStrokeState::type, "gradient", undoManager);
v.setProperty (FillAndStrokeState::gradientPoint1, gradientPoint1.toString(), undoManager);
v.setProperty (FillAndStrokeState::gradientPoint2, gradientPoint2.toString(), undoManager);
v.setProperty (FillAndStrokeState::gradientPoint3, gradientPoint3.toString(), undoManager);
const ColourGradient& cg = *fill.gradient;
v.setProperty (FillAndStrokeState::radial, cg.isRadial, undoManager);
String s;
for (int i = 0; i < cg.getNumColours(); ++i)
s << ' ' << cg.getColourPosition (i)
<< ' ' << String::toHexString ((int) cg.getColour(i).getARGB());
v.setProperty (FillAndStrokeState::colours, s.trimStart(), undoManager);
}
else if (fill.isTiledImage())
{
v.setProperty (FillAndStrokeState::type, "image", undoManager);
if (imageProvider != nullptr)
v.setProperty (FillAndStrokeState::imageId, imageProvider->getIdentifierForImage (fill.image), undoManager);
if (fill.getOpacity() < 1.0f)
v.setProperty (FillAndStrokeState::imageOpacity, fill.getOpacity(), undoManager);
else
v.removeProperty (FillAndStrokeState::imageOpacity, undoManager);
}
else
{
jassertfalse;
}
}
bool DrawableShape::RelativeFillType::readFrom (const ValueTree& v, ComponentBuilder::ImageProvider* imageProvider)
{
const String newType (v [FillAndStrokeState::type].toString());
if (newType == "solid")
{
const String colourString (v [FillAndStrokeState::colour].toString());
fill.setColour (colourString.isEmpty() ? Colours::black
: Colour::fromString (colourString));
return true;
}
else if (newType == "gradient")
{
ColourGradient g;
g.isRadial = v [FillAndStrokeState::radial];
StringArray colourSteps;
colourSteps.addTokens (v [FillAndStrokeState::colours].toString(), false);
for (int i = 0; i < colourSteps.size() / 2; ++i)
g.addColour (colourSteps[i * 2].getDoubleValue(),
Colour::fromString (colourSteps[i * 2 + 1]));
fill.setGradient (g);
gradientPoint1 = RelativePoint (v [FillAndStrokeState::gradientPoint1]);
gradientPoint2 = RelativePoint (v [FillAndStrokeState::gradientPoint2]);
gradientPoint3 = RelativePoint (v [FillAndStrokeState::gradientPoint3]);
return true;
}
else if (newType == "image")
{
Image im;
if (imageProvider != nullptr)
im = imageProvider->getImageForIdentifier (v [FillAndStrokeState::imageId]);
fill.setTiledImage (im, AffineTransform::identity);
fill.setOpacity ((float) v.getProperty (FillAndStrokeState::imageOpacity, 1.0f));
return true;
}
jassertfalse;
return false;
}
//==============================================================================
const Identifier DrawableShape::FillAndStrokeState::type ("type");
const Identifier DrawableShape::FillAndStrokeState::colour ("colour");
const Identifier DrawableShape::FillAndStrokeState::colours ("colours");
const Identifier DrawableShape::FillAndStrokeState::fill ("Fill");
const Identifier DrawableShape::FillAndStrokeState::stroke ("Stroke");
const Identifier DrawableShape::FillAndStrokeState::path ("Path");
const Identifier DrawableShape::FillAndStrokeState::jointStyle ("jointStyle");
const Identifier DrawableShape::FillAndStrokeState::capStyle ("capStyle");
const Identifier DrawableShape::FillAndStrokeState::strokeWidth ("strokeWidth");
const Identifier DrawableShape::FillAndStrokeState::gradientPoint1 ("point1");
const Identifier DrawableShape::FillAndStrokeState::gradientPoint2 ("point2");
const Identifier DrawableShape::FillAndStrokeState::gradientPoint3 ("point3");
const Identifier DrawableShape::FillAndStrokeState::radial ("radial");
const Identifier DrawableShape::FillAndStrokeState::imageId ("imageId");
const Identifier DrawableShape::FillAndStrokeState::imageOpacity ("imageOpacity");
DrawableShape::FillAndStrokeState::FillAndStrokeState (const ValueTree& state_)
: Drawable::ValueTreeWrapperBase (state_)
{
}
DrawableShape::RelativeFillType DrawableShape::FillAndStrokeState::getFill (const Identifier& fillOrStrokeType, ComponentBuilder::ImageProvider* imageProvider) const
{
DrawableShape::RelativeFillType f;
f.readFrom (state.getChildWithName (fillOrStrokeType), imageProvider);
return f;
}
ValueTree DrawableShape::FillAndStrokeState::getFillState (const Identifier& fillOrStrokeType)
{
ValueTree v (state.getChildWithName (fillOrStrokeType));
if (v.isValid())
return v;
setFill (fillOrStrokeType, FillType (Colours::black), nullptr, nullptr);
return getFillState (fillOrStrokeType);
}
void DrawableShape::FillAndStrokeState::setFill (const Identifier& fillOrStrokeType, const RelativeFillType& newFill,
ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager)
{
ValueTree v (state.getOrCreateChildWithName (fillOrStrokeType, undoManager));
newFill.writeTo (v, imageProvider, undoManager);
}
PathStrokeType DrawableShape::FillAndStrokeState::getStrokeType() const
{
const String jointStyleString (state [jointStyle].toString());
const String capStyleString (state [capStyle].toString());
return PathStrokeType (state [strokeWidth],
jointStyleString == "curved" ? PathStrokeType::curved
: (jointStyleString == "bevel" ? PathStrokeType::beveled
: PathStrokeType::mitered),
capStyleString == "square" ? PathStrokeType::square
: (capStyleString == "round" ? PathStrokeType::rounded
: PathStrokeType::butt));
}
void DrawableShape::FillAndStrokeState::setStrokeType (const PathStrokeType& newStrokeType, UndoManager* undoManager)
{
state.setProperty (strokeWidth, (double) newStrokeType.getStrokeThickness(), undoManager);
state.setProperty (jointStyle, newStrokeType.getJointStyle() == PathStrokeType::mitered
? "miter" : (newStrokeType.getJointStyle() == PathStrokeType::curved ? "curved" : "bevel"), undoManager);
state.setProperty (capStyle, newStrokeType.getEndStyle() == PathStrokeType::butt
? "butt" : (newStrokeType.getEndStyle() == PathStrokeType::square ? "square" : "round"), undoManager);
}
static bool replaceColourInFill (DrawableShape::RelativeFillType& fill, Colour original, Colour replacement)
{
if (fill.fill.colour == original && fill.fill.isColour())
{
fill = FillType (replacement);
return true;
}
return false;
}
bool DrawableShape::replaceColour (Colour original, Colour replacement)
{
bool changed1 = replaceColourInFill (mainFill, original, replacement);
bool changed2 = replaceColourInFill (strokeFill, original, replacement);
return changed1 || changed2;
}

View file

@ -0,0 +1,182 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_DRAWABLESHAPE_H_INCLUDED
#define JUCE_DRAWABLESHAPE_H_INCLUDED
//==============================================================================
/**
A base class implementing common functionality for Drawable classes which
consist of some kind of filled and stroked outline.
@see DrawablePath, DrawableRectangle
*/
class JUCE_API DrawableShape : public Drawable
{
protected:
//==============================================================================
DrawableShape();
DrawableShape (const DrawableShape&);
public:
/** Destructor. */
~DrawableShape();
//==============================================================================
/** A FillType wrapper that allows the gradient coordinates to be implemented using RelativePoint.
*/
class RelativeFillType
{
public:
RelativeFillType();
RelativeFillType (const FillType& fill);
RelativeFillType (const RelativeFillType&);
RelativeFillType& operator= (const RelativeFillType&);
bool operator== (const RelativeFillType&) const;
bool operator!= (const RelativeFillType&) const;
bool isDynamic() const;
bool recalculateCoords (Expression::Scope* scope);
void writeTo (ValueTree& v, ComponentBuilder::ImageProvider*, UndoManager*) const;
bool readFrom (const ValueTree& v, ComponentBuilder::ImageProvider*);
//==============================================================================
FillType fill;
RelativePoint gradientPoint1, gradientPoint2, gradientPoint3;
};
//==============================================================================
/** Sets a fill type for the path.
This colour is used to fill the path - if you don't want the path to be
filled (e.g. if you're just drawing an outline), set this to a transparent
colour.
@see setPath, setStrokeFill
*/
void setFill (const FillType& newFill);
/** Sets a fill type for the path.
This colour is used to fill the path - if you don't want the path to be
filled (e.g. if you're just drawing an outline), set this to a transparent
colour.
@see setPath, setStrokeFill
*/
void setFill (const RelativeFillType& newFill);
/** Returns the current fill type.
@see setFill
*/
const RelativeFillType& getFill() const noexcept { return mainFill; }
/** Sets the fill type with which the outline will be drawn.
@see setFill
*/
void setStrokeFill (const FillType& newStrokeFill);
/** Sets the fill type with which the outline will be drawn.
@see setFill
*/
void setStrokeFill (const RelativeFillType& newStrokeFill);
/** Returns the current stroke fill.
@see setStrokeFill
*/
const RelativeFillType& getStrokeFill() const noexcept { return strokeFill; }
/** Changes the properties of the outline that will be drawn around the path.
If the stroke has 0 thickness, no stroke will be drawn.
@see setStrokeThickness, setStrokeColour
*/
void setStrokeType (const PathStrokeType& newStrokeType);
/** Changes the stroke thickness.
This is a shortcut for calling setStrokeType.
*/
void setStrokeThickness (float newThickness);
/** Returns the current outline style. */
const PathStrokeType& getStrokeType() const noexcept { return strokeType; }
//==============================================================================
/** @internal */
class FillAndStrokeState : public Drawable::ValueTreeWrapperBase
{
public:
FillAndStrokeState (const ValueTree& state);
ValueTree getFillState (const Identifier& fillOrStrokeType);
RelativeFillType getFill (const Identifier& fillOrStrokeType, ComponentBuilder::ImageProvider*) const;
void setFill (const Identifier& fillOrStrokeType, const RelativeFillType& newFill,
ComponentBuilder::ImageProvider*, UndoManager*);
PathStrokeType getStrokeType() const;
void setStrokeType (const PathStrokeType& newStrokeType, UndoManager*);
static const Identifier type, colour, colours, fill, stroke, path, jointStyle, capStyle, strokeWidth,
gradientPoint1, gradientPoint2, gradientPoint3, radial, imageId, imageOpacity;
};
/** @internal */
Rectangle<float> getDrawableBounds() const override;
/** @internal */
void paint (Graphics&) override;
/** @internal */
bool hitTest (int x, int y) override;
/** @internal */
bool replaceColour (Colour originalColour, Colour replacementColour) override;
protected:
//==============================================================================
/** Called when the cached path should be updated. */
void pathChanged();
/** Called when the cached stroke should be updated. */
void strokeChanged();
/** True if there's a stroke with a non-zero thickness and non-transparent colour. */
bool isStrokeVisible() const noexcept;
/** Updates the details from a FillAndStrokeState object, returning true if something changed. */
void refreshFillTypes (const FillAndStrokeState& newState, ComponentBuilder::ImageProvider*);
/** Writes the stroke and fill details to a FillAndStrokeState object. */
void writeTo (FillAndStrokeState& state, ComponentBuilder::ImageProvider*, UndoManager*) const;
//==============================================================================
PathStrokeType strokeType;
Path path, strokePath;
private:
class RelativePositioner;
RelativeFillType mainFill, strokeFill;
ScopedPointer<RelativeCoordinatePositionerBase> mainFillPositioner, strokeFillPositioner;
void setFillInternal (RelativeFillType& fill, const RelativeFillType& newFill,
ScopedPointer<RelativeCoordinatePositionerBase>& positioner);
DrawableShape& operator= (const DrawableShape&);
};
#endif // JUCE_DRAWABLESHAPE_H_INCLUDED

View file

@ -0,0 +1,334 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
DrawableText::DrawableText()
: colour (Colours::black),
justification (Justification::centredLeft)
{
setBoundingBox (RelativeParallelogram (RelativePoint (0.0f, 0.0f),
RelativePoint (50.0f, 0.0f),
RelativePoint (0.0f, 20.0f)));
setFont (Font (15.0f), true);
}
DrawableText::DrawableText (const DrawableText& other)
: Drawable (other),
bounds (other.bounds),
fontHeight (other.fontHeight),
fontHScale (other.fontHScale),
font (other.font),
text (other.text),
colour (other.colour),
justification (other.justification)
{
refreshBounds();
}
DrawableText::~DrawableText()
{
}
//==============================================================================
void DrawableText::setText (const String& newText)
{
if (text != newText)
{
text = newText;
refreshBounds();
}
}
void DrawableText::setColour (Colour newColour)
{
if (colour != newColour)
{
colour = newColour;
repaint();
}
}
void DrawableText::setFont (const Font& newFont, bool applySizeAndScale)
{
if (font != newFont)
{
font = newFont;
if (applySizeAndScale)
{
fontHeight = font.getHeight();
fontHScale = font.getHorizontalScale();
}
refreshBounds();
}
}
void DrawableText::setJustification (Justification newJustification)
{
justification = newJustification;
repaint();
}
void DrawableText::setBoundingBox (const RelativeParallelogram& newBounds)
{
if (bounds != newBounds)
{
bounds = newBounds;
refreshBounds();
}
}
void DrawableText::setFontHeight (const RelativeCoordinate& newHeight)
{
if (fontHeight != newHeight)
{
fontHeight = newHeight;
refreshBounds();
}
}
void DrawableText::setFontHorizontalScale (const RelativeCoordinate& newScale)
{
if (fontHScale != newScale)
{
fontHScale = newScale;
refreshBounds();
}
}
void DrawableText::refreshBounds()
{
if (bounds.isDynamic() || fontHeight.isDynamic() || fontHScale.isDynamic())
{
Drawable::Positioner<DrawableText>* const p = new Drawable::Positioner<DrawableText> (*this);
setPositioner (p);
p->apply();
}
else
{
setPositioner (0);
recalculateCoordinates (0);
}
}
bool DrawableText::registerCoordinates (RelativeCoordinatePositionerBase& pos)
{
bool ok = pos.addPoint (bounds.topLeft);
ok = pos.addPoint (bounds.topRight) && ok;
ok = pos.addPoint (bounds.bottomLeft) && ok;
ok = pos.addCoordinate (fontHeight) && ok;
return pos.addCoordinate (fontHScale) && ok;
}
void DrawableText::recalculateCoordinates (Expression::Scope* scope)
{
bounds.resolveThreePoints (resolvedPoints, scope);
const float w = Line<float> (resolvedPoints[0], resolvedPoints[1]).getLength();
const float h = Line<float> (resolvedPoints[0], resolvedPoints[2]).getLength();
const float height = jlimit (0.01f, jmax (0.01f, h), (float) fontHeight.resolve (scope));
const float hscale = jlimit (0.01f, jmax (0.01f, w), (float) fontHScale.resolve (scope));
scaledFont = font;
scaledFont.setHeight (height);
scaledFont.setHorizontalScale (hscale);
setBoundsToEnclose (getDrawableBounds());
repaint();
}
//==============================================================================
void DrawableText::paint (Graphics& g)
{
transformContextToCorrectOrigin (g);
const float w = Line<float> (resolvedPoints[0], resolvedPoints[1]).getLength();
const float h = Line<float> (resolvedPoints[0], resolvedPoints[2]).getLength();
g.addTransform (AffineTransform::fromTargetPoints (0, 0, resolvedPoints[0].x, resolvedPoints[0].y,
w, 0, resolvedPoints[1].x, resolvedPoints[1].y,
0, h, resolvedPoints[2].x, resolvedPoints[2].y));
g.setFont (scaledFont);
g.setColour (colour);
g.drawFittedText (text, Rectangle<float> (w, h).getSmallestIntegerContainer(), justification, 0x100000);
}
Rectangle<float> DrawableText::getDrawableBounds() const
{
return RelativeParallelogram::getBoundingBox (resolvedPoints);
}
Drawable* DrawableText::createCopy() const
{
return new DrawableText (*this);
}
//==============================================================================
const Identifier DrawableText::valueTreeType ("Text");
const Identifier DrawableText::ValueTreeWrapper::text ("text");
const Identifier DrawableText::ValueTreeWrapper::colour ("colour");
const Identifier DrawableText::ValueTreeWrapper::font ("font");
const Identifier DrawableText::ValueTreeWrapper::justification ("justification");
const Identifier DrawableText::ValueTreeWrapper::topLeft ("topLeft");
const Identifier DrawableText::ValueTreeWrapper::topRight ("topRight");
const Identifier DrawableText::ValueTreeWrapper::bottomLeft ("bottomLeft");
const Identifier DrawableText::ValueTreeWrapper::fontHeight ("fontHeight");
const Identifier DrawableText::ValueTreeWrapper::fontHScale ("fontHScale");
//==============================================================================
DrawableText::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_)
: ValueTreeWrapperBase (state_)
{
jassert (state.hasType (valueTreeType));
}
String DrawableText::ValueTreeWrapper::getText() const
{
return state [text].toString();
}
void DrawableText::ValueTreeWrapper::setText (const String& newText, UndoManager* undoManager)
{
state.setProperty (text, newText, undoManager);
}
Value DrawableText::ValueTreeWrapper::getTextValue (UndoManager* undoManager)
{
return state.getPropertyAsValue (text, undoManager);
}
Colour DrawableText::ValueTreeWrapper::getColour() const
{
return Colour::fromString (state [colour].toString());
}
void DrawableText::ValueTreeWrapper::setColour (Colour newColour, UndoManager* undoManager)
{
state.setProperty (colour, newColour.toString(), undoManager);
}
Justification DrawableText::ValueTreeWrapper::getJustification() const
{
return Justification ((int) state [justification]);
}
void DrawableText::ValueTreeWrapper::setJustification (Justification newJustification, UndoManager* undoManager)
{
state.setProperty (justification, newJustification.getFlags(), undoManager);
}
Font DrawableText::ValueTreeWrapper::getFont() const
{
return Font::fromString (state [font]);
}
void DrawableText::ValueTreeWrapper::setFont (const Font& newFont, UndoManager* undoManager)
{
state.setProperty (font, newFont.toString(), undoManager);
}
Value DrawableText::ValueTreeWrapper::getFontValue (UndoManager* undoManager)
{
return state.getPropertyAsValue (font, undoManager);
}
RelativeParallelogram DrawableText::ValueTreeWrapper::getBoundingBox() const
{
return RelativeParallelogram (state [topLeft].toString(), state [topRight].toString(), state [bottomLeft].toString());
}
void DrawableText::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager)
{
state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager);
state.setProperty (topRight, newBounds.topRight.toString(), undoManager);
state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager);
}
RelativeCoordinate DrawableText::ValueTreeWrapper::getFontHeight() const
{
return state [fontHeight].toString();
}
void DrawableText::ValueTreeWrapper::setFontHeight (const RelativeCoordinate& coord, UndoManager* undoManager)
{
state.setProperty (fontHeight, coord.toString(), undoManager);
}
RelativeCoordinate DrawableText::ValueTreeWrapper::getFontHorizontalScale() const
{
return state [fontHScale].toString();
}
void DrawableText::ValueTreeWrapper::setFontHorizontalScale (const RelativeCoordinate& coord, UndoManager* undoManager)
{
state.setProperty (fontHScale, coord.toString(), undoManager);
}
//==============================================================================
void DrawableText::refreshFromValueTree (const ValueTree& tree, ComponentBuilder&)
{
ValueTreeWrapper v (tree);
setComponentID (v.getID());
const RelativeParallelogram newBounds (v.getBoundingBox());
const RelativeCoordinate newFontHeight (v.getFontHeight());
const RelativeCoordinate newFontHScale (v.getFontHorizontalScale());
const Colour newColour (v.getColour());
const Justification newJustification (v.getJustification());
const String newText (v.getText());
const Font newFont (v.getFont());
if (text != newText || font != newFont || justification != newJustification
|| colour != newColour || bounds != newBounds
|| newFontHeight != fontHeight || newFontHScale != fontHScale)
{
setBoundingBox (newBounds);
setFontHeight (newFontHeight);
setFontHorizontalScale (newFontHScale);
setColour (newColour);
setFont (newFont, false);
setJustification (newJustification);
setText (newText);
}
}
ValueTree DrawableText::createValueTree (ComponentBuilder::ImageProvider*) const
{
ValueTree tree (valueTreeType);
ValueTreeWrapper v (tree);
v.setID (getComponentID());
v.setText (text, nullptr);
v.setFont (font, nullptr);
v.setJustification (justification, nullptr);
v.setColour (colour, nullptr);
v.setBoundingBox (bounds, nullptr);
v.setFontHeight (fontHeight, nullptr);
v.setFontHorizontalScale (fontHScale, nullptr);
return tree;
}

View file

@ -0,0 +1,155 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software Ltd.
Permission is granted to use this software under the terms of either:
a) the GPL v2 (or any later version)
b) the Affero GPL v3
Details of these licenses can be found at: www.gnu.org/licenses
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
------------------------------------------------------------------------------
To release a closed-source product which uses JUCE, commercial licenses are
available: visit www.juce.com for more information.
==============================================================================
*/
#ifndef JUCE_DRAWABLETEXT_H_INCLUDED
#define JUCE_DRAWABLETEXT_H_INCLUDED
//==============================================================================
/**
A drawable object which renders a line of text.
@see Drawable
*/
class JUCE_API DrawableText : public Drawable
{
public:
//==============================================================================
/** Creates a DrawableText object. */
DrawableText();
DrawableText (const DrawableText&);
/** Destructor. */
~DrawableText();
//==============================================================================
/** Sets the text to display.*/
void setText (const String& newText);
/** Returns the currently displayed text */
const String& getText() const noexcept { return text;}
/** Sets the colour of the text. */
void setColour (Colour newColour);
/** Returns the current text colour. */
Colour getColour() const noexcept { return colour; }
/** Sets the font to use.
Note that the font height and horizontal scale are set as RelativeCoordinates using
setFontHeight and setFontHorizontalScale. If applySizeAndScale is true, then these height
and scale values will be changed to match the dimensions of the font supplied;
if it is false, then the new font object's height and scale are ignored.
*/
void setFont (const Font& newFont, bool applySizeAndScale);
/** Returns the current font. */
const Font& getFont() const noexcept { return font; }
/** Changes the justification of the text within the bounding box. */
void setJustification (Justification newJustification);
/** Returns the current justification. */
Justification getJustification() const noexcept { return justification; }
/** Returns the parallelogram that defines the text bounding box. */
const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; }
/** Sets the bounding box that contains the text. */
void setBoundingBox (const RelativeParallelogram& newBounds);
const RelativeCoordinate& getFontHeight() const { return fontHeight; }
void setFontHeight (const RelativeCoordinate& newHeight);
const RelativeCoordinate& getFontHorizontalScale() const { return fontHScale; }
void setFontHorizontalScale (const RelativeCoordinate& newScale);
//==============================================================================
/** @internal */
void paint (Graphics&) override;
/** @internal */
Drawable* createCopy() const override;
/** @internal */
void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder);
/** @internal */
ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const override;
/** @internal */
static const Identifier valueTreeType;
/** @internal */
Rectangle<float> getDrawableBounds() const override;
//==============================================================================
/** Internally-used class for wrapping a DrawableText's state into a ValueTree. */
class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase
{
public:
ValueTreeWrapper (const ValueTree& state);
String getText() const;
void setText (const String& newText, UndoManager* undoManager);
Value getTextValue (UndoManager* undoManager);
Colour getColour() const;
void setColour (Colour newColour, UndoManager* undoManager);
Justification getJustification() const;
void setJustification (Justification newJustification, UndoManager* undoManager);
Font getFont() const;
void setFont (const Font& newFont, UndoManager* undoManager);
Value getFontValue (UndoManager* undoManager);
RelativeParallelogram getBoundingBox() const;
void setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager);
RelativeCoordinate getFontHeight() const;
void setFontHeight (const RelativeCoordinate& newHeight, UndoManager* undoManager);
RelativeCoordinate getFontHorizontalScale() const;
void setFontHorizontalScale (const RelativeCoordinate& newScale, UndoManager* undoManager);
static const Identifier text, colour, font, justification, topLeft, topRight, bottomLeft, fontHeight, fontHScale;
};
private:
//==============================================================================
RelativeParallelogram bounds;
RelativeCoordinate fontHeight, fontHScale;
Point<float> resolvedPoints[3];
Font font, scaledFont;
String text;
Colour colour;
Justification justification;
friend class Drawable::Positioner<DrawableText>;
bool registerCoordinates (RelativeCoordinatePositionerBase&);
void recalculateCoordinates (Expression::Scope*);
void refreshBounds();
DrawableText& operator= (const DrawableText&);
JUCE_LEAK_DETECTOR (DrawableText)
};
#endif // JUCE_DRAWABLETEXT_H_INCLUDED