1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-20 01:14:20 +00:00
JUCE/src/gui/graphics/drawables/juce_DrawableComposite.cpp

574 lines
19 KiB
C++

/*
==============================================================================
This file is part of the JUCE library - "Jules' Utility Class Extensions"
Copyright 2004-10 by Raw Material Software Ltd.
------------------------------------------------------------------------------
JUCE can be redistributed and/or modified under the terms of the GNU General
Public License (Version 2), as published by the Free Software Foundation.
A copy of the license is included in the JUCE distribution, or can be found
online 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.rawmaterialsoftware.com/juce for more information.
==============================================================================
*/
#include "../../../core/juce_StandardHeader.h"
BEGIN_JUCE_NAMESPACE
#include "juce_DrawableComposite.h"
#include "juce_DrawablePath.h"
#include "juce_DrawableImage.h"
#include "juce_DrawableText.h"
#include "../imaging/juce_Image.h"
//==============================================================================
DrawableComposite::DrawableComposite()
{
controlPoints[1] = RelativePoint (Point<float> (1.0f, 0.0f));
controlPoints[2] = RelativePoint (Point<float> (0.0f, 1.0f));
}
DrawableComposite::DrawableComposite (const DrawableComposite& other)
{
int i;
for (i = 0; i < 3; ++i)
controlPoints[i] = other.controlPoints[i];
for (i = 0; i < drawables.size(); ++i)
drawables.add (other.drawables.getUnchecked(i)->createCopy());
for (i = 0; i < markers.size(); ++i)
markers.add (new Marker (*other.markers.getUnchecked(i)));
}
DrawableComposite::~DrawableComposite()
{
}
//==============================================================================
void DrawableComposite::insertDrawable (Drawable* drawable, const int index)
{
if (drawable != 0)
{
jassert (! drawables.contains (drawable)); // trying to add a drawable that's already in here!
jassert (drawable->parent == 0); // A drawable can only live inside one parent at a time!
drawables.insert (index, drawable);
drawable->parent = this;
}
}
void DrawableComposite::insertDrawable (const Drawable& drawable, const int index)
{
insertDrawable (drawable.createCopy(), index);
}
void DrawableComposite::removeDrawable (const int index, const bool deleteDrawable)
{
drawables.remove (index, deleteDrawable);
}
Drawable* DrawableComposite::getDrawableWithName (const String& name) const throw()
{
for (int i = drawables.size(); --i >= 0;)
if (drawables.getUnchecked(i)->getName() == name)
return drawables.getUnchecked(i);
return 0;
}
void DrawableComposite::bringToFront (const int index)
{
if (index >= 0 && index < drawables.size() - 1)
drawables.move (index, -1);
}
void DrawableComposite::setTransform (const RelativePoint& targetPositionForOrigin,
const RelativePoint& targetPositionForX1Y0,
const RelativePoint& targetPositionForX0Y1)
{
controlPoints[0] = targetPositionForOrigin;
controlPoints[1] = targetPositionForX1Y0;
controlPoints[2] = targetPositionForX0Y1;
}
//==============================================================================
DrawableComposite::Marker::Marker (const DrawableComposite::Marker& other)
: name (other.name), position (other.position), isOnXAxis (other.isOnXAxis)
{
}
DrawableComposite::Marker::Marker (const String& name_, const RelativeCoordinate& position_, const bool isOnXAxis_)
: name (name_), position (position_), isOnXAxis (isOnXAxis_)
{
}
bool DrawableComposite::Marker::operator!= (const DrawableComposite::Marker& other) const throw()
{
return name != other.name || position != other.position || isOnXAxis != other.isOnXAxis;
}
//==============================================================================
int DrawableComposite::getNumMarkers (bool xAxis) const throw()
{
return markers.size();
}
const DrawableComposite::Marker* DrawableComposite::getMarker (int index) const throw()
{
return markers [index];
}
void DrawableComposite::setMarker (const String& name, bool xAxis, const RelativeCoordinate& position)
{
for (int i = 0; i < markers.size(); ++i)
{
Marker* const m = markers.getUnchecked(i);
if (m->name == name)
{
jassert (m->isOnXAxis == xAxis); // trying to either have two markers with the same name on different axes?
if (m->position != position)
{
m->position = position;
invalidatePoints();
}
return;
}
}
markers.add (new Marker (name, position, xAxis));
invalidatePoints();
}
void DrawableComposite::removeMarker (int index)
{
markers.remove (index);
}
//==============================================================================
const AffineTransform DrawableComposite::calculateTransform() const
{
Point<float> resolved[3];
for (int i = 0; i < 3; ++i)
resolved[i] = controlPoints[i].resolve (parent);
return AffineTransform::fromTargetPoints (resolved[0].getX(), resolved[0].getY(),
resolved[1].getX(), resolved[1].getY(),
resolved[2].getX(), resolved[2].getY());
}
void DrawableComposite::render (const Drawable::RenderingContext& context) const
{
if (drawables.size() > 0 && context.opacity > 0)
{
if (context.opacity >= 1.0f || drawables.size() == 1)
{
Drawable::RenderingContext contextCopy (context);
contextCopy.transform = calculateTransform().followedBy (context.transform);
for (int i = 0; i < drawables.size(); ++i)
drawables.getUnchecked(i)->render (contextCopy);
}
else
{
// To correctly render a whole composite layer with an overall transparency,
// we need to render everything opaquely into a temp buffer, then blend that
// with the target opacity...
const Rectangle<int> clipBounds (context.g.getClipBounds());
Image tempImage (Image::ARGB, clipBounds.getWidth(), clipBounds.getHeight(), true);
{
Graphics tempG (tempImage);
tempG.setOrigin (-clipBounds.getX(), -clipBounds.getY());
Drawable::RenderingContext tempContext (tempG, context.transform, 1.0f);
render (tempContext);
}
context.g.setOpacity (context.opacity);
context.g.drawImageAt (&tempImage, clipBounds.getX(), clipBounds.getY());
}
}
}
const RelativeCoordinate DrawableComposite::findNamedCoordinate (const String& objectName, const String& edge) const
{
if (objectName == RelativeCoordinate::Strings::parent)
{
if (edge == RelativeCoordinate::Strings::right)
{
jassertfalse; // a Drawable doesn't have a fixed right-hand edge - use a marker instead if you need a point of reference.
return RelativeCoordinate (100.0, true);
}
if (edge == RelativeCoordinate::Strings::bottom)
{
jassertfalse; // a Drawable doesn't have a fixed bottom edge - use a marker instead if you need a point of reference.
return RelativeCoordinate (100.0, false);
}
}
for (int i = 0; i < markers.size(); ++i)
{
Marker* const m = markers.getUnchecked(i);
if (m->name == objectName)
return m->position;
}
return RelativeCoordinate();
}
const Rectangle<float> DrawableComposite::getUntransformedBounds() const
{
Rectangle<float> bounds;
for (int i = 0; i < drawables.size(); ++i)
bounds = bounds.getUnion (drawables.getUnchecked(i)->getBounds());
return bounds;
}
const Rectangle<float> DrawableComposite::getBounds() const
{
return getUntransformedBounds().transformed (calculateTransform());
}
bool DrawableComposite::hitTest (float x, float y) const
{
calculateTransform().inverted().transformPoint (x, y);
for (int i = 0; i < drawables.size(); ++i)
if (drawables.getUnchecked(i)->hitTest (x, y))
return true;
return false;
}
Drawable* DrawableComposite::createCopy() const
{
return new DrawableComposite (*this);
}
void DrawableComposite::invalidatePoints()
{
for (int i = 0; i < drawables.size(); ++i)
drawables.getUnchecked(i)->invalidatePoints();
}
//==============================================================================
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::markerGroupTag ("Markers");
const Identifier DrawableComposite::ValueTreeWrapper::markerTag ("Marker");
const Identifier DrawableComposite::ValueTreeWrapper::nameProperty ("name");
const Identifier DrawableComposite::ValueTreeWrapper::xAxisProperty ("xAxis");
const Identifier DrawableComposite::ValueTreeWrapper::posProperty ("position");
//==============================================================================
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)
{
const ValueTree childList (getChildList());
if (childList.isValid())
return childList;
state.addChild (ValueTree (childGroupTag), 0, undoManager);
return getChildList();
}
ValueTree DrawableComposite::ValueTreeWrapper::getMarkerList() const
{
return state.getChildWithName (markerGroupTag);
}
ValueTree DrawableComposite::ValueTreeWrapper::getMarkerListCreating (UndoManager* undoManager)
{
const ValueTree markerList (getMarkerList());
if (markerList.isValid())
return markerList;
state.addChild (ValueTree (markerGroupTag), -1, undoManager);
return getMarkerList();
}
int DrawableComposite::ValueTreeWrapper::getNumDrawables() const
{
return getChildList().getNumChildren();
}
ValueTree DrawableComposite::ValueTreeWrapper::getDrawableState (int index) const
{
return getChildList().getChild (index);
}
ValueTree DrawableComposite::ValueTreeWrapper::getDrawableWithId (const String& objectId, bool recursive) const
{
if (getID() == objectId)
return state;
if (! recursive)
{
return getChildList().getChildWithProperty (idProperty, objectId);
}
else
{
const ValueTree childList (getChildList());
for (int i = getNumDrawables(); --i >= 0;)
{
const ValueTree& child = childList.getChild (i);
if (child [Drawable::ValueTreeWrapperBase::idProperty] == objectId)
return child;
if (child.hasType (DrawableComposite::valueTreeType))
{
ValueTree v (DrawableComposite::ValueTreeWrapper (child).getDrawableWithId (objectId, true));
if (v.isValid())
return v;
}
}
return ValueTree::invalid;
}
}
void DrawableComposite::ValueTreeWrapper::addDrawable (const ValueTree& newDrawableState, int index, UndoManager* undoManager)
{
getChildListCreating (undoManager).addChild (newDrawableState, index, undoManager);
}
void DrawableComposite::ValueTreeWrapper::moveDrawableOrder (int currentIndex, int newIndex, UndoManager* undoManager)
{
getChildListCreating (undoManager).moveChild (currentIndex, newIndex, undoManager);
}
void DrawableComposite::ValueTreeWrapper::removeDrawable (int index, UndoManager* undoManager)
{
getChildList().removeChild (index, undoManager);
}
const RelativePoint DrawableComposite::ValueTreeWrapper::getTargetPositionForOrigin() const
{
const String pos (state [topLeft].toString());
return pos.isNotEmpty() ? RelativePoint (pos) : RelativePoint ();
}
void DrawableComposite::ValueTreeWrapper::setTargetPositionForOrigin (const RelativePoint& newPoint, UndoManager* undoManager)
{
state.setProperty (topLeft, newPoint.toString(), undoManager);
}
const RelativePoint DrawableComposite::ValueTreeWrapper::getTargetPositionForX1Y0() const
{
const String pos (state [topRight].toString());
return pos.isNotEmpty() ? RelativePoint (pos) : RelativePoint (Point<float> (1.0f, 0.0f));
}
void DrawableComposite::ValueTreeWrapper::setTargetPositionForX1Y0 (const RelativePoint& newPoint, UndoManager* undoManager)
{
state.setProperty (topRight, newPoint.toString(), undoManager);
}
const RelativePoint DrawableComposite::ValueTreeWrapper::getTargetPositionForX0Y1() const
{
const String pos (state [bottomLeft].toString());
return pos.isNotEmpty() ? RelativePoint (pos) : RelativePoint (Point<float> (0.0f, 1.0f));
}
void DrawableComposite::ValueTreeWrapper::setTargetPositionForX0Y1 (const RelativePoint& newPoint, UndoManager* undoManager)
{
state.setProperty (bottomLeft, newPoint.toString(), undoManager);
}
int DrawableComposite::ValueTreeWrapper::getNumMarkers() const
{
return getMarkerList().getNumChildren();
}
const DrawableComposite::Marker DrawableComposite::ValueTreeWrapper::getMarker (int index) const
{
const ValueTree marker (getMarkerList().getChild (index));
const bool isXAxis = marker [xAxisProperty];
return Marker (marker [nameProperty],
RelativeCoordinate (marker [posProperty].toString(), isXAxis),
isXAxis);
}
void DrawableComposite::ValueTreeWrapper::setMarker (const String& name, bool xAxis, const RelativeCoordinate& position, UndoManager* undoManager)
{
ValueTree markerList (getMarkerListCreating (undoManager));
ValueTree marker (markerList.getChildWithProperty (nameProperty, name));
if (marker.isValid())
{
jassert ((bool) marker [xAxisProperty] == xAxis); // shouldn't change the axis of a marker after it has been created!
marker.setProperty (posProperty, position.toString(), undoManager);
}
else
{
marker = ValueTree (markerTag);
marker.setProperty (nameProperty, name, 0);
marker.setProperty (xAxisProperty, xAxis, 0);
marker.setProperty (posProperty, position.toString(), 0);
markerList.addChild (marker, -1, undoManager);
}
}
void DrawableComposite::ValueTreeWrapper::removeMarker (int index, UndoManager* undoManager)
{
return getMarkerList().removeChild (index, undoManager);
}
//==============================================================================
const Rectangle<float> DrawableComposite::refreshFromValueTree (const ValueTree& tree, ImageProvider* imageProvider)
{
Rectangle<float> damageRect;
const ValueTreeWrapper controller (tree);
setName (controller.getID());
RelativePoint newControlPoint[3] = { controller.getTargetPositionForOrigin(),
controller.getTargetPositionForX1Y0(),
controller.getTargetPositionForX0Y1() };
bool redrawAll = false;
if (controlPoints[0] != newControlPoint[0]
|| controlPoints[1] != newControlPoint[1]
|| controlPoints[2] != newControlPoint[2])
{
redrawAll = true;
damageRect = getUntransformedBounds();
controlPoints[0] = newControlPoint[0];
controlPoints[1] = newControlPoint[1];
controlPoints[2] = newControlPoint[2];
}
// Remove deleted markers...
int i;
for (i = markers.size(); --i >= controller.getNumMarkers();)
{
if (damageRect.isEmpty())
damageRect = getUntransformedBounds();
removeMarker (i);
}
// Update markers and add new ones..
for (i = 0; i < controller.getNumMarkers(); ++i)
{
const Marker newMarker (controller.getMarker (i));
Marker* m = markers[i];
if (m == 0 || newMarker != *m)
{
redrawAll = true;
if (damageRect.isEmpty())
damageRect = getUntransformedBounds();
if (m == 0)
markers.add (new Marker (newMarker));
else
*m = newMarker;
}
}
// Remove deleted drawables..
for (i = drawables.size(); --i >= controller.getNumDrawables();)
{
Drawable* const d = drawables.getUnchecked(i);
damageRect = damageRect.getUnion (d->getBounds());
d->parent = 0;
drawables.remove (i);
}
// Update drawables and add new ones..
for (i = 0; i < controller.getNumDrawables(); ++i)
{
const ValueTree newDrawable (controller.getDrawableState (i));
Drawable* d = drawables[i];
if (d != 0)
{
if (newDrawable.hasType (d->getValueTreeType()))
{
damageRect = damageRect.getUnion (d->refreshFromValueTree (newDrawable, imageProvider));
}
else
{
damageRect = damageRect.getUnion (d->getBounds());
d = createFromValueTree (newDrawable, imageProvider);
d->parent = this;
drawables.set (i, d);
damageRect = damageRect.getUnion (d->getBounds());
}
}
else
{
d = createFromValueTree (newDrawable, imageProvider);
d->parent = this;
drawables.set (i, d);
damageRect = damageRect.getUnion (d->getBounds());
}
}
if (redrawAll)
damageRect = damageRect.getUnion (getUntransformedBounds());
return damageRect.transformed (calculateTransform());
}
const ValueTree DrawableComposite::createValueTree (ImageProvider* imageProvider) const
{
ValueTree tree (valueTreeType);
ValueTreeWrapper v (tree);
v.setID (getName(), 0);
v.setTargetPositionForOrigin (controlPoints[0], 0);
v.setTargetPositionForX1Y0 (controlPoints[1], 0);
v.setTargetPositionForX0Y1 (controlPoints[2], 0);
int i;
for (i = 0; i < drawables.size(); ++i)
v.addDrawable (drawables.getUnchecked(i)->createValueTree (imageProvider), -1, 0);
for (i = 0; i < markers.size(); ++i)
{
const Marker* m = markers.getUnchecked(i);
v.setMarker (m->name, m->isOnXAxis, m->position, 0);
}
return tree;
}
END_JUCE_NAMESPACE