@@ -87,6 +87,9 @@ void DrawableDocument::checkRootObject() | |||
DrawableComposite::ValueTreeWrapper rootObject (getRootDrawableNode()); | |||
StringArray idCache; | |||
recursivelyUpdateIDs (rootObject, idCache); | |||
if (rootObject.getNumMarkers (true) < 2 || rootObject.getNumMarkers (false) < 2) | |||
rootObject.setContentArea (RelativeRectangle ("0, 0, 100, 100"), 0); | |||
} | |||
const String DrawableDocument::getUniqueId() const | |||
@@ -735,12 +735,13 @@ public: | |||
const RelativePoint getPosition() | |||
{ | |||
DrawableComposite::ValueTreeWrapper wrapper (item); | |||
const RelativeParallelogram bounds (wrapper.getBoundingBox()); | |||
switch (cpNum) | |||
{ | |||
case 0: return wrapper.getTargetPositionForOrigin(); | |||
case 1: return wrapper.getTargetPositionForX1Y0(); | |||
case 2: return wrapper.getTargetPositionForX0Y1(); | |||
case 0: return bounds.topLeft; | |||
case 1: return bounds.topRight; | |||
case 2: return bounds.bottomLeft; | |||
default: jassertfalse; break; | |||
} | |||
@@ -750,14 +751,17 @@ public: | |||
void setPosition (const RelativePoint& newPoint, UndoManager* undoManager) | |||
{ | |||
DrawableComposite::ValueTreeWrapper wrapper (item); | |||
RelativeParallelogram bounds (wrapper.getBoundingBox()); | |||
switch (cpNum) | |||
{ | |||
case 0: wrapper.setTargetPositionForOrigin (newPoint, undoManager); break; | |||
case 1: wrapper.setTargetPositionForX1Y0 (newPoint, undoManager); break; | |||
case 2: wrapper.setTargetPositionForX0Y1 (newPoint, undoManager); break; | |||
case 0: bounds.topLeft = newPoint; break; | |||
case 1: bounds.topRight = newPoint; break; | |||
case 2: bounds.bottomLeft = newPoint; break; | |||
default: jassertfalse; break; | |||
} | |||
wrapper.setBoundingBox (bounds, undoManager); | |||
} | |||
const Value getPositionValue (UndoManager* undoManager) | |||
@@ -809,15 +813,14 @@ public: | |||
void buttonClicked() | |||
{ | |||
RelativePoint topLeft (wrapper.getTargetPositionForOrigin()); | |||
RelativePoint topRight (wrapper.getTargetPositionForX1Y0()); | |||
RelativePoint bottomLeft (wrapper.getTargetPositionForX0Y1()); | |||
RelativeParallelogram bounds (wrapper.getBoundingBox()); | |||
const RelativeRectangle content (wrapper.getContentArea()); | |||
const Rectangle<float> resolved (content.resolve (&item)); | |||
topRight.moveToAbsolute (topLeft.resolve (&item) + Point<float> (1.0f, 0.0f), &item); | |||
bottomLeft.moveToAbsolute (topLeft.resolve (&item) + Point<float> (0.0f, 1.0f), &item); | |||
bounds.topRight.moveToAbsolute (bounds.topLeft.resolve (&item) + Point<float> (resolved.getWidth(), 0), &item); | |||
bounds.bottomLeft.moveToAbsolute (bounds.topLeft.resolve (&item) + Point<float> (0, resolved.getHeight()), &item); | |||
wrapper.setTargetPositionForX1Y0 (topRight, item.getUndoManager()); | |||
wrapper.setTargetPositionForX0Y1 (bottomLeft, item.getUndoManager()); | |||
wrapper.setBoundingBox (bounds, item.getUndoManager()); | |||
} | |||
private: | |||
@@ -68,12 +68,15 @@ public: | |||
{ | |||
Drawable* newDrawable = Drawable::createFromValueTree (doc.getRootDrawableNode().getState(), &doc); | |||
drawable = dynamic_cast <DrawableComposite*> (newDrawable); | |||
drawable->resetBoundingBoxToContentArea(); | |||
jassert (drawable != 0); | |||
getComponentHolder()->repaint(); | |||
} | |||
else | |||
{ | |||
doc.getRootDrawableNode().resetBoundingBoxToContentArea (0); | |||
const Rectangle<float> damage (drawable->refreshFromValueTree (doc.getRootDrawableNode().getState(), &doc)); | |||
getComponentHolder()->repaint (objectSpaceToScreenSpace (damage.getSmallestIntegerContainer())); | |||
} | |||
@@ -302,13 +302,13 @@ public: | |||
if (isX) | |||
{ | |||
const float centre = getWidth() / 2 + 0.5f; | |||
path.addLineSegment (centre, 2.0f, centre, getHeight() + 1.0f, lineThickness); | |||
path.addLineSegment (Line<float> (centre, 2.0f, centre, getHeight() + 1.0f), lineThickness); | |||
path.addTriangle (1.0f, 0.0f, centre * 2.0f - 1.0f, 0.0f, centre, headSize + 1.0f); | |||
} | |||
else | |||
{ | |||
const float centre = getHeight() / 2 + 0.5f; | |||
path.addLineSegment (2.0f, centre, getWidth() + 1.0f, centre, lineThickness); | |||
path.addLineSegment (Line<float> (2.0f, centre, getWidth() + 1.0f, centre), lineThickness); | |||
path.addTriangle (0.0f, centre * 2.0f - 1.0f, 0.0f, 1.0f, headSize + 1.0f, centre); | |||
} | |||
@@ -265,7 +265,7 @@ private: | |||
Path marker; | |||
const float headSize = 4.5f; | |||
marker.addLineSegment (0.0f, -2.0f, 0.0f, previewArea.getHeight() + 2.0f, 1.5f); | |||
marker.addLineSegment (Line<float> (0.0f, -2.0f, 0.0f, previewArea.getHeight() + 2.0f), 1.5f); | |||
marker.addTriangle (0.0f, 1.0f, -headSize, -headSize, headSize, -headSize); | |||
for (int i = 0; i < gradient.getNumColours(); ++i) | |||
@@ -42,7 +42,6 @@ class FontsAndTextDemo : public Component, | |||
Slider* horizontalScaleSlider; | |||
StretchableLayoutManager verticalLayout; | |||
StretchableLayoutManager horizontalLayout; | |||
StretchableLayoutResizerBar* verticalDividerBar; | |||
@@ -104,22 +103,6 @@ public: | |||
verticalDividerBar = new StretchableLayoutResizerBar (&verticalLayout, 1, true); | |||
addAndMakeVisible (verticalDividerBar); | |||
horizontalLayout.setItemLayout (0, -0.2, -1.0, -0.4); // height of the font text box must be | |||
// between 20% and 100%, preferably 40% | |||
horizontalLayout.setItemLayout (1, 8, 8, 8); // the horizontal divider drag-bar thing is always 8 pixels high | |||
horizontalLayout.setItemLayout (2, 2, 5, 5); // a gap between the controls | |||
horizontalLayout.setItemLayout (3, 15, 20, 20); // the italic button would like to be 20 pixels high | |||
horizontalLayout.setItemLayout (4, 2, 5, 5); // a gap between the controls | |||
horizontalLayout.setItemLayout (5, 15, 20, 20); // the bold button would like to be 20 pixels high | |||
horizontalLayout.setItemLayout (6, 2, 5, 5); // a gap between the controls | |||
horizontalLayout.setItemLayout (7, 15, 20, 20); // the italic button would like to be 20 pixels high | |||
horizontalLayout.setItemLayout (8, 2, 5, 5); // a gap between the controls | |||
horizontalLayout.setItemLayout (9, 15, 20, 20); // the copy code button would like to be 20 pixels high | |||
horizontalLayout.setItemLayout (10, 5, -1.0, 5); // add a gap at the bottom that will fill up any | |||
// space left over - this will stop the | |||
// sliders from always sticking to the | |||
// bottom of the window | |||
} | |||
~FontsAndTextDemo() | |||
@@ -178,7 +178,7 @@ private: | |||
float size = (float) jmin (getWidth(), getHeight()); | |||
Path p; | |||
p.addStar (bouncingPointX[1], bouncingPointY[1], 7, | |||
p.addStar (Point<float> (bouncingPointX[1], bouncingPointY[1]), 7, | |||
size * jmax (0.6f, bouncingNumber[4]), | |||
size * jmax (0.7f, bouncingNumber[5]), | |||
bouncingNumber[4]); | |||
@@ -201,8 +201,8 @@ private: | |||
{ | |||
Path p; | |||
p.addRectangle (-50, 0, 100, 100); | |||
p.addStar (100.0f, 0.0f, 7, 30.0f, 70.0f, 0.1f); | |||
p.addStar (-100.0f, 0.0f, 6, 40.0f, 70.0f, 0.1f); | |||
p.addStar (Point<float> (100.0f, 0.0f), 7, 30.0f, 70.0f, 0.1f); | |||
p.addStar (Point<float> (-100.0f, 0.0f), 6, 40.0f, 70.0f, 0.1f); | |||
p.addEllipse (-60.0f, -100.0f, 120.0f, 90.0f); | |||
if (linearGradient || radialGradient) | |||
@@ -336,28 +336,19 @@ private: | |||
ZipFile icons (&iconsFileStream, false); | |||
// Load a random SVG file from our embedded icons.zip file. | |||
InputStream* svgFileStream | |||
= icons.createStreamForEntry (Random::getSystemRandom().nextInt (icons.getNumEntries())); | |||
ScopedPointer<InputStream> svgFileStream (icons.createStreamForEntry (Random::getSystemRandom().nextInt (icons.getNumEntries()))); | |||
if (svgFileStream != 0) | |||
{ | |||
svgDrawable = dynamic_cast <DrawableComposite*> (Drawable::createFromImageDataStream (*svgFileStream)); | |||
delete svgFileStream; | |||
if (svgDrawable != 0) | |||
{ | |||
// to make our icon the right size, we'll put it inside a DrawableComposite, and apply | |||
// a transform to get it to the size we want. | |||
Rectangle<float> bounds = svgDrawable->getBounds(); | |||
const float scaleFactor = 200.0f / jmax (bounds.getWidth(), bounds.getHeight()); | |||
Point<float> topLeft (-bounds.getCentreX() * scaleFactor, | |||
-bounds.getCentreY() * scaleFactor); | |||
svgDrawable->setTransform (topLeft, | |||
topLeft + Point<float> (scaleFactor, 0), | |||
topLeft + Point<float> (0, scaleFactor)); | |||
// to make our icon the right size, we'll put it inside a DrawableComposite, and | |||
// set its bounding box to the size and position that we want. | |||
svgDrawable->setBoundingBox (RelativeParallelogram (Point<float> (-100, -100), | |||
Point<float> (100, -100), | |||
Point<float> (-100, 100))); | |||
} | |||
} | |||
} | |||
@@ -442,7 +442,7 @@ static Component* createRadioButtonPage() | |||
DrawablePath normal, over; | |||
Path p; | |||
p.addStar (0.0f, 0.0f, i + 5, 20.0f, 50.0f, -0.2f); | |||
p.addStar (Point<float>(), i + 5, 20.0f, 50.0f, -0.2f); | |||
normal.setPath (p); | |||
normal.setFill (Colours::lightblue); | |||
normal.setStrokeFill (Colours::black); | |||
@@ -500,12 +500,12 @@ public: | |||
DrawablePath normal, over; | |||
Path p; | |||
p.addStar (0.0f, 0.0f, 5, 20.0f, 50.0f, 0.2f); | |||
p.addStar (Point<float>(), 5, 20.0f, 50.0f, 0.2f); | |||
normal.setPath (p); | |||
normal.setFill (Colours::red); | |||
p.clear(); | |||
p.addStar (0.0f, 0.0f, 7, 30.0f, 50.0f, 0.0f); | |||
p.addStar (Point<float>(), 7, 30.0f, 50.0f, 0.0f); | |||
over.setPath (p); | |||
over.setFill (Colours::pink); | |||
over.setStrokeFill (Colours::black); | |||
@@ -130,8 +130,8 @@ public: | |||
r << "g.drawImage (" << imageVariable << ",\n " | |||
<< x << ", " << y << ", " << w << ", " << h | |||
<< ",\n 0, 0, " | |||
<< imageVariable << "->getWidth(), " | |||
<< imageVariable << "->getHeight());\n\n"; | |||
<< imageVariable << ".getWidth(), " | |||
<< imageVariable << ".getHeight());\n\n"; | |||
} | |||
else | |||
{ | |||
@@ -64,7 +64,7 @@ | |||
*/ | |||
#define JUCE_MAJOR_VERSION 1 | |||
#define JUCE_MINOR_VERSION 52 | |||
#define JUCE_BUILDNUMBER 11 | |||
#define JUCE_BUILDNUMBER 12 | |||
/** Current Juce version number. | |||
@@ -19023,6 +19023,9 @@ public: | |||
/** Copies this point from another one. */ | |||
Point& operator= (const Point& other) throw() { x = other.x; y = other.y; return *this; } | |||
inline bool operator== (const Point& other) const throw() { return x == other.x && y == other.y; } | |||
inline bool operator!= (const Point& other) const throw() { return x != other.x || y != other.y; } | |||
/** Returns true if the point is (0, 0). */ | |||
bool isOrigin() const throw() { return x == ValueType() && y == ValueType(); } | |||
@@ -19050,9 +19053,6 @@ public: | |||
/** Adds a pair of co-ordinates to this value. */ | |||
void addXY (const ValueType xToAdd, const ValueType yToAdd) throw() { x += xToAdd; y += yToAdd; } | |||
inline bool operator== (const Point& other) const throw() { return x == other.x && y == other.y; } | |||
inline bool operator!= (const Point& other) const throw() { return x != other.x || y != other.y; } | |||
/** Adds two points together. */ | |||
const Point operator+ (const Point& other) const throw() { return Point (x + other.x, y + other.y); } | |||
@@ -19093,6 +19093,20 @@ public: | |||
*/ | |||
ValueType getAngleToPoint (const Point& other) const throw() { return (ValueType) std::atan2 (other.x - x, other.y - y); } | |||
/** Taking this point to be the centre of a circle, this returns a point on its circumference. | |||
@param radius the radius of the circle. | |||
@param angle the angle of the point, in radians clockwise from the 12 o'clock position. | |||
*/ | |||
const Point getPointOnCircumference (const float radius, const float angle) const throw() { return Point<float> (x + radius * std::sin (angle), | |||
y - radius * std::cos (angle)); } | |||
/** Taking this point to be the centre of an ellipse, this returns a point on its circumference. | |||
@param radiusX the horizontal radius of the circle. | |||
@param radiusY the vertical radius of the circle. | |||
@param angle the angle of the point, in radians clockwise from the 12 o'clock position. | |||
*/ | |||
const Point getPointOnCircumference (const float radiusX, const float radiusY, const float angle) const throw() { return Point<float> (x + radiusX * std::sin (angle), | |||
y - radiusY * std::cos (angle)); } | |||
/** Uses a transform to change the point's co-ordinates. | |||
This will only compile if ValueType = float! | |||
@see AffineTransform::transformPoint | |||
@@ -19941,6 +19955,9 @@ public: | |||
/** Changes this line's end point */ | |||
void setEnd (const Point<ValueType>& newEnd) throw() { end = newEnd; } | |||
/** Returns a line that is the same as this one, but with the start and end reversed, */ | |||
const Line reversed() const throw() { return Line (end, start); } | |||
/** Applies an affine transform to the line's start and end points. */ | |||
void applyTransform (const AffineTransform& transform) throw() | |||
{ | |||
@@ -20313,6 +20330,18 @@ public: | |||
/** Returns a rectangle with the same size as this one, but a new position. */ | |||
const Rectangle withPosition (const Point<ValueType>& newPos) const throw() { return Rectangle (newPos.getX(), newPos.getY(), w, h); } | |||
/** Returns the rectangle's top-left position as a Point. */ | |||
const Point<ValueType> getTopLeft() const throw() { return getPosition(); } | |||
/** Returns the rectangle's top-right position as a Point. */ | |||
const Point<ValueType> getTopRight() const throw() { return Point<float> (x + w, y); } | |||
/** Returns the rectangle's bottom-left position as a Point. */ | |||
const Point<ValueType> getBottomLeft() const throw() { return Point<float> (x, y + h); } | |||
/** Returns the rectangle's bottom-right position as a Point. */ | |||
const Point<ValueType> getBottomRight() const throw() { return Point<float> (x + w, y + h); } | |||
/** Changes the rectangle's size, leaving the position of its top-left corner unchanged. */ | |||
void setSize (const ValueType newWidth, const ValueType newHeight) throw() { w = newWidth; h = newHeight; } | |||
@@ -21069,6 +21098,19 @@ public: | |||
*/ | |||
void startNewSubPath (float startX, float startY); | |||
/** Begins a new subpath with a given starting position. | |||
This will move the path's current position to the co-ordinates passed in and | |||
make it ready to draw lines or curves starting from this position. | |||
After adding whatever lines and curves are needed, you can either | |||
close the current sub-path using closeSubPath() or call startNewSubPath() | |||
to move to a new sub-path, leaving the old one open-ended. | |||
@see lineTo, quadraticTo, cubicTo, closeSubPath | |||
*/ | |||
void startNewSubPath (const Point<float>& start); | |||
/** Closes a the current sub-path with a line back to its start-point. | |||
When creating a closed shape such as a triangle, don't use 3 lineTo() | |||
@@ -21094,6 +21136,17 @@ public: | |||
*/ | |||
void lineTo (float endX, float endY); | |||
/** Adds a line from the shape's last position to a new end-point. | |||
This will connect the end-point of the last line or curve that was added | |||
to a new point, using a straight line. | |||
See the class description for an example of how to add lines and curves to a path. | |||
@see startNewSubPath, quadraticTo, cubicTo, closeSubPath | |||
*/ | |||
void lineTo (const Point<float>& end); | |||
/** Adds a quadratic bezier curve from the shape's last position to a new position. | |||
This will connect the end-point of the last line or curve that was added | |||
@@ -21108,6 +21161,18 @@ public: | |||
float endPointX, | |||
float endPointY); | |||
/** Adds a quadratic bezier curve from the shape's last position to a new position. | |||
This will connect the end-point of the last line or curve that was added | |||
to a new point, using a quadratic spline with one control-point. | |||
See the class description for an example of how to add lines and curves to a path. | |||
@see startNewSubPath, lineTo, cubicTo, closeSubPath | |||
*/ | |||
void quadraticTo (const Point<float>& controlPoint, | |||
const Point<float>& endPoint); | |||
/** Adds a cubic bezier curve from the shape's last position to a new position. | |||
This will connect the end-point of the last line or curve that was added | |||
@@ -21124,6 +21189,19 @@ public: | |||
float endPointX, | |||
float endPointY); | |||
/** Adds a cubic bezier curve from the shape's last position to a new position. | |||
This will connect the end-point of the last line or curve that was added | |||
to a new point, using a cubic spline with two control-points. | |||
See the class description for an example of how to add lines and curves to a path. | |||
@see startNewSubPath, lineTo, quadraticTo, closeSubPath | |||
*/ | |||
void cubicTo (const Point<float>& controlPoint1, | |||
const Point<float>& controlPoint2, | |||
const Point<float>& endPoint); | |||
/** Returns the last point that was added to the path by one of the drawing methods. | |||
*/ | |||
const Point<float> getCurrentPosition() const; | |||
@@ -21294,26 +21372,29 @@ public: | |||
@see addArrow | |||
*/ | |||
void addLineSegment (float startX, float startY, | |||
float endX, float endY, | |||
float lineThickness); | |||
void addLineSegment (const Line<float>& line, float lineThickness); | |||
/** Adds a line with an arrowhead on the end. | |||
The arrow is added as a new closed sub-path. (Any currently open paths will be | |||
left open). | |||
The arrow is added as a new closed sub-path. (Any currently open paths will be left open). | |||
@see PathStrokeType::createStrokeWithArrowheads | |||
*/ | |||
void addArrow (float startX, float startY, | |||
float endX, float endY, | |||
void addArrow (const Line<float>& line, | |||
float lineThickness, | |||
float arrowheadWidth, | |||
float arrowheadLength); | |||
/** Adds a star shape to the path. | |||
/** Adds a polygon shape to the path. | |||
@see addStar | |||
*/ | |||
void addPolygon (const Point<float>& centre, | |||
int numberOfSides, | |||
float radius, | |||
float startAngle = 0.0f); | |||
/** Adds a star shape to the path. | |||
@see addPolygon | |||
*/ | |||
void addStar (float centreX, | |||
float centreY, | |||
void addStar (const Point<float>& centre, | |||
int numberOfPoints, | |||
float innerRadius, | |||
float outerRadius, | |||
@@ -22184,6 +22265,28 @@ public: | |||
const AffineTransform& transform = AffineTransform::identity, | |||
float extraAccuracy = 1.0f) const; | |||
/** Applies this stroke type to a path and returns the resultant stroke as another Path. | |||
@param destPath the resultant stroked outline shape will be copied into this path. | |||
Note that it's ok for the source and destination Paths to be | |||
the same object, so you can easily turn a path into a stroked version | |||
of itself. | |||
@param sourcePath the path to use as the source | |||
@param transform an optional transform to apply to the points from the source path | |||
as they are being used | |||
@param extraAccuracy if this is greater than 1.0, it will subdivide the path to | |||
a higher resolution, which improved the quality if you'll later want | |||
to enlarge the stroked path | |||
@see createDashedStroke | |||
*/ | |||
void createStrokeWithArrowheads (Path& destPath, | |||
const Path& sourcePath, | |||
float arrowheadStartWidth, float arrowheadStartLength, | |||
float arrowheadEndWidth, float arrowheadEndLength, | |||
const AffineTransform& transform = AffineTransform::identity, | |||
float extraAccuracy = 1.0f) const; | |||
/** Returns the stroke thickness. */ | |||
float getStrokeThickness() const throw() { return thickness; } | |||
@@ -23822,18 +23925,14 @@ public: | |||
const PathStrokeType& strokeType, | |||
const AffineTransform& transform = AffineTransform::identity) const; | |||
/** Draws a line with an arrowhead. | |||
/** Draws a line with an arrowhead at its end. | |||
@param startX the line's start x co-ordinate | |||
@param startY the line's start y co-ordinate | |||
@param endX the line's end x co-ordinate (the tip of the arrowhead) | |||
@param endY the line's end y co-ordinate (the tip of the arrowhead) | |||
@param line the line to draw | |||
@param lineThickness the thickness of the line | |||
@param arrowheadWidth the width of the arrow head (perpendicular to the line) | |||
@param arrowheadLength the length of the arrow head (along the length of the line) | |||
*/ | |||
void drawArrow (float startX, float startY, | |||
float endX, float endY, | |||
void drawArrow (const Line<float>& line, | |||
float lineThickness, | |||
float arrowheadWidth, | |||
float arrowheadLength) const; | |||
@@ -42521,6 +42620,10 @@ public: | |||
/** Creates an absolute rectangle, relative to the origin. */ | |||
explicit RelativeRectangle (const Rectangle<float>& rect, const String& componentName); | |||
/** Creates a rectangle from four coordinates. */ | |||
RelativeRectangle (const RelativeCoordinate& left, const RelativeCoordinate& right, | |||
const RelativeCoordinate& top, const RelativeCoordinate& bottom); | |||
/** Creates a rectangle from a stringified representation. | |||
The string must contain a sequence of 4 coordinates, separated by commas, in the order | |||
left, top, right, bottom. The syntax for the coordinate strings is explained in the | |||
@@ -58409,38 +58512,38 @@ public: | |||
*/ | |||
void bringToFront (int index); | |||
/** Sets the transform to be applied to this drawable, by defining the positions | |||
where three anchor points should end up in the target rendering space. | |||
/** 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 contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName | |||
*/ | |||
const RelativeRectangle getContentArea() const; | |||
@param targetPositionForOrigin the position that the local coordinate (0, 0) should be | |||
mapped onto when rendering this object. | |||
@param targetPositionForX1Y0 the position that the local coordinate (1, 0) should be | |||
mapped onto when rendering this object. | |||
@param targetPositionForX0Y1 the position that the local coordinate (0, 1) should be | |||
mapped onto when rendering this object. | |||
/** 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 setBoundingBox, contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName | |||
*/ | |||
void setTransform (const RelativePoint& targetPositionForOrigin, | |||
const RelativePoint& targetPositionForX1Y0, | |||
const RelativePoint& targetPositionForX0Y1); | |||
void setContentArea (const RelativeRectangle& newArea); | |||
/** Returns the position to which the local coordinate (0, 0) should be remapped in the target | |||
coordinate space when rendering this object. | |||
@see setTransform | |||
/** Sets the parallelogram that defines the target position of the content rectangle when the drawable is rendered. | |||
@see setContentArea | |||
*/ | |||
const RelativePoint& getTargetPositionForOrigin() const throw() { return controlPoints[0]; } | |||
void setBoundingBox (const RelativeParallelogram& newBoundingBox); | |||
/** Returns the position to which the local coordinate (1, 0) should be remapped in the target | |||
coordinate space when rendering this object. | |||
@see setTransform | |||
/** Returns the parallelogram that defines the target position of the content rectangle when the drawable is rendered. | |||
@see setBoundingBox | |||
*/ | |||
const RelativePoint& getTargetPositionForX1Y0() const throw() { return controlPoints[1]; } | |||
const RelativeParallelogram& getBoundingBox() const throw() { return bounds; } | |||
/** Returns the position to which the local coordinate (0, 1) should be remapped in the target | |||
coordinate space when rendering this object. | |||
@see setTransform | |||
/** Changes the bounding box transform to match the content area, so that any sub-items will | |||
be drawn at their untransformed positions. | |||
*/ | |||
const RelativePoint& getTargetPositionForX0Y1() const throw() { return controlPoints[2]; } | |||
void resetBoundingBoxToContentArea(); | |||
/** Represents a named marker position. | |||
@see DrawableComposite::getMarker | |||
*/ | |||
struct Marker | |||
{ | |||
Marker (const Marker&); | |||
@@ -58456,6 +58559,15 @@ public: | |||
void setMarker (const String& name, bool xAxis, const RelativeCoordinate& position); | |||
void removeMarker (bool xAxis, int index); | |||
/** 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 */ | |||
void render (const Drawable::RenderingContext& context) const; | |||
/** @internal */ | |||
@@ -58491,14 +58603,12 @@ public: | |||
void moveDrawableOrder (int currentIndex, int newIndex, UndoManager* undoManager); | |||
void removeDrawable (const ValueTree& child, UndoManager* undoManager); | |||
const RelativePoint getTargetPositionForOrigin() const; | |||
void setTargetPositionForOrigin (const RelativePoint& newPoint, UndoManager* undoManager); | |||
const RelativePoint getTargetPositionForX1Y0() const; | |||
void setTargetPositionForX1Y0 (const RelativePoint& newPoint, UndoManager* undoManager); | |||
const RelativeParallelogram getBoundingBox() const; | |||
void setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager); | |||
void resetBoundingBoxToContentArea (UndoManager* undoManager); | |||
const RelativePoint getTargetPositionForX0Y1() const; | |||
void setTargetPositionForX0Y1 (const RelativePoint& newPoint, UndoManager* undoManager); | |||
const RelativeRectangle getContentArea() const; | |||
void setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager); | |||
int getNumMarkers (bool xAxis) const; | |||
const ValueTree getMarkerState (bool xAxis, int index) const; | |||
@@ -58524,7 +58634,7 @@ public: | |||
private: | |||
OwnedArray <Drawable> drawables; | |||
RelativePoint controlPoints[3]; | |||
RelativeParallelogram bounds; | |||
OwnedArray <Marker> markersX, markersY; | |||
const Rectangle<float> getUntransformedBounds() const; | |||
@@ -387,12 +387,13 @@ bool AudioFormatWriter::writeFromAudioReader (AudioFormatReader& reader, | |||
int64 numSamplesToRead) | |||
{ | |||
const int bufferSize = 16384; | |||
const int maxChans = 128; | |||
AudioSampleBuffer tempBuffer (reader.numChannels, bufferSize); | |||
int* buffers [maxChans]; | |||
AudioSampleBuffer tempBuffer (numChannels, bufferSize); | |||
for (int i = maxChans; --i >= 0;) | |||
buffers[i] = 0; | |||
int* buffers [128]; | |||
zerostruct (buffers); | |||
for (int i = tempBuffer.getNumChannels(); --i >= 0;) | |||
buffers[i] = (int*) tempBuffer.getSampleData (i, 0); | |||
if (numSamplesToRead < 0) | |||
numSamplesToRead = reader.lengthInSamples; | |||
@@ -401,10 +402,7 @@ bool AudioFormatWriter::writeFromAudioReader (AudioFormatReader& reader, | |||
{ | |||
const int numToDo = (int) jmin (numSamplesToRead, (int64) bufferSize); | |||
for (int i = tempBuffer.getNumChannels(); --i >= 0;) | |||
buffers[i] = (int*) tempBuffer.getSampleData (i, 0); | |||
if (! reader.read (buffers, maxChans, startSample, numToDo, false)) | |||
if (! reader.read (buffers, numChannels, startSample, numToDo, false)) | |||
return false; | |||
if (reader.usesFloatingPointData != isFloatingPoint()) | |||
@@ -455,9 +453,12 @@ bool AudioFormatWriter::writeFromAudioSource (AudioSource& source, | |||
int numSamplesToRead, | |||
const int samplesPerBlock) | |||
{ | |||
const int maxChans = 128; | |||
AudioSampleBuffer tempBuffer (getNumChannels(), samplesPerBlock); | |||
int* buffers [maxChans]; | |||
int* buffers [128]; | |||
zerostruct (buffers); | |||
for (int i = tempBuffer.getNumChannels(); --i >= 0;) | |||
buffers[i] = (int*) tempBuffer.getSampleData (i, 0); | |||
while (numSamplesToRead > 0) | |||
{ | |||
@@ -471,13 +472,6 @@ bool AudioFormatWriter::writeFromAudioSource (AudioSource& source, | |||
source.getNextAudioBlock (info); | |||
int i; | |||
for (i = maxChans; --i >= 0;) | |||
buffers[i] = 0; | |||
for (i = tempBuffer.getNumChannels(); --i >= 0;) | |||
buffers[i] = (int*) tempBuffer.getSampleData (i, 0); | |||
if (! isFloatingPoint()) | |||
{ | |||
int** bufferChan = buffers; | |||
@@ -33,7 +33,7 @@ | |||
*/ | |||
#define JUCE_MAJOR_VERSION 1 | |||
#define JUCE_MINOR_VERSION 52 | |||
#define JUCE_BUILDNUMBER 11 | |||
#define JUCE_BUILDNUMBER 12 | |||
/** Current Juce version number. | |||
@@ -154,8 +154,8 @@ public: | |||
} | |||
Path p; | |||
p.addArrow (x1, y1, x2, y2, 1.5f, hw, hl); | |||
p.addArrow (x3, y3, x4, y4, 1.5f, hw, hl); | |||
p.addArrow (Line<float> (x1, y1, x2, y2), 1.5f, hw, hl); | |||
p.addArrow (Line<float> (x3, y3, x4, y4), 1.5f, hw, hl); | |||
g.fillPath (p); | |||
} | |||
} | |||
@@ -58,7 +58,7 @@ FileSearchPathListComponent::FileSearchPathListComponent() | |||
{ | |||
Path arrowPath; | |||
arrowPath.addArrow (50.0f, 100.0f, 50.0f, 0.0f, 40.0f, 100.0f, 50.0f); | |||
arrowPath.addArrow (Line<float> (50.0f, 100.0f, 50.0f, 0.0f), 40.0f, 100.0f, 50.0f); | |||
DrawablePath arrowImage; | |||
arrowImage.setFill (Colours::black.withAlpha (0.4f)); | |||
arrowImage.setPath (arrowPath); | |||
@@ -71,7 +71,7 @@ FileSearchPathListComponent::FileSearchPathListComponent() | |||
{ | |||
Path arrowPath; | |||
arrowPath.addArrow (50.0f, 0.0f, 50.0f, 100.0f, 40.0f, 100.0f, 50.0f); | |||
arrowPath.addArrow (Line<float> (50.0f, 0.0f, 50.0f, 100.0f), 40.0f, 100.0f, 50.0f); | |||
DrawablePath arrowImage; | |||
arrowImage.setFill (Colours::black.withAlpha (0.4f)); | |||
arrowImage.setPath (arrowPath); | |||
@@ -1577,7 +1577,7 @@ void LookAndFeel::drawRotarySlider (Graphics& g, | |||
p.addEllipse (-0.4f * rw, -0.4f * rw, rw * 0.8f, rw * 0.8f); | |||
PathStrokeType (rw * 0.1f).createStrokedPath (p, p); | |||
p.addLineSegment (0.0f, 0.0f, 0.0f, -radius, rw * 0.2f); | |||
p.addLineSegment (Line<float> (0.0f, 0.0f, 0.0f, -radius), rw * 0.2f); | |||
g.fillPath (p, AffineTransform::rotation (angle).translated (centreX, centreY)); | |||
} | |||
@@ -1899,21 +1899,21 @@ Button* LookAndFeel::createDocumentWindowButton (int buttonType) | |||
if (buttonType == DocumentWindow::closeButton) | |||
{ | |||
shape.addLineSegment (0.0f, 0.0f, 1.0f, 1.0f, crossThickness * 1.4f); | |||
shape.addLineSegment (1.0f, 0.0f, 0.0f, 1.0f, crossThickness * 1.4f); | |||
shape.addLineSegment (Line<float> (0.0f, 0.0f, 1.0f, 1.0f), crossThickness * 1.4f); | |||
shape.addLineSegment (Line<float> (1.0f, 0.0f, 0.0f, 1.0f), crossThickness * 1.4f); | |||
return new GlassWindowButton ("close", Colour (0xffdd1100), shape, shape); | |||
} | |||
else if (buttonType == DocumentWindow::minimiseButton) | |||
{ | |||
shape.addLineSegment (0.0f, 0.5f, 1.0f, 0.5f, crossThickness); | |||
shape.addLineSegment (Line<float> (0.0f, 0.5f, 1.0f, 0.5f), crossThickness); | |||
return new GlassWindowButton ("minimise", Colour (0xffaa8811), shape, shape); | |||
} | |||
else if (buttonType == DocumentWindow::maximiseButton) | |||
{ | |||
shape.addLineSegment (0.5f, 0.0f, 0.5f, 1.0f, crossThickness); | |||
shape.addLineSegment (0.0f, 0.5f, 1.0f, 0.5f, crossThickness); | |||
shape.addLineSegment (Line<float> (0.5f, 0.0f, 0.5f, 1.0f), crossThickness); | |||
shape.addLineSegment (Line<float> (0.0f, 0.5f, 1.0f, 0.5f), crossThickness); | |||
Path fullscreenShape; | |||
fullscreenShape.startNewSubPath (45.0f, 100.0f); | |||
@@ -2613,7 +2613,7 @@ Button* LookAndFeel::createFileBrowserGoUpButton() | |||
DrawableButton* goUpButton = new DrawableButton ("up", DrawableButton::ImageOnButtonBackground); | |||
Path arrowPath; | |||
arrowPath.addArrow (50.0f, 100.0f, 50.0f, 0.0f, 40.0f, 100.0f, 50.0f); | |||
arrowPath.addArrow (Line<float> (50.0f, 100.0f, 50.0f, 0.0f), 40.0f, 100.0f, 50.0f); | |||
DrawablePath arrowImage; | |||
arrowImage.setFill (Colours::black.withAlpha (0.4f)); | |||
@@ -538,8 +538,8 @@ Button* OldSchoolLookAndFeel::createDocumentWindowButton (int buttonType) | |||
if (buttonType == DocumentWindow::closeButton) | |||
{ | |||
shape.addLineSegment (0.0f, 0.0f, 1.0f, 1.0f, 0.35f); | |||
shape.addLineSegment (1.0f, 0.0f, 0.0f, 1.0f, 0.35f); | |||
shape.addLineSegment (Line<float> (0.0f, 0.0f, 1.0f, 1.0f), 0.35f); | |||
shape.addLineSegment (Line<float> (1.0f, 0.0f, 0.0f, 1.0f), 0.35f); | |||
ShapeButton* const b = new ShapeButton ("close", | |||
Colour (0x7fff3333), | |||
@@ -551,7 +551,7 @@ Button* OldSchoolLookAndFeel::createDocumentWindowButton (int buttonType) | |||
} | |||
else if (buttonType == DocumentWindow::minimiseButton) | |||
{ | |||
shape.addLineSegment (0.0f, 0.5f, 1.0f, 0.5f, 0.25f); | |||
shape.addLineSegment (Line<float> (0.0f, 0.5f, 1.0f, 0.5f), 0.25f); | |||
DrawableButton* b = new DrawableButton ("minimise", DrawableButton::ImageFitted); | |||
DrawablePath dp; | |||
@@ -562,8 +562,8 @@ Button* OldSchoolLookAndFeel::createDocumentWindowButton (int buttonType) | |||
} | |||
else if (buttonType == DocumentWindow::maximiseButton) | |||
{ | |||
shape.addLineSegment (0.5f, 0.0f, 0.5f, 1.0f, 0.25f); | |||
shape.addLineSegment (0.0f, 0.5f, 1.0f, 0.5f, 0.25f); | |||
shape.addLineSegment (Line<float> (0.5f, 0.0f, 0.5f, 1.0f), 0.25f); | |||
shape.addLineSegment (Line<float> (0.0f, 0.5f, 1.0f, 0.5f), 0.25f); | |||
DrawableButton* b = new DrawableButton ("maximise", DrawableButton::ImageFitted); | |||
DrawablePath dp; | |||
@@ -27,8 +27,8 @@ | |||
BEGIN_JUCE_NAMESPACE | |||
#include "juce_DialogWindow.h" | |||
#include "../../../application/juce_Application.h" | |||
//============================================================================== | |||
@@ -68,6 +68,8 @@ public: | |||
TempDialogWindow (const String& title, const Colour& colour, const bool escapeCloses) | |||
: DialogWindow (title, colour, escapeCloses, true) | |||
{ | |||
if (JUCEApplication::getInstance() == 0) | |||
setAlwaysOnTop (true); // for a plugin, make it always-on-top because the host windows are often top-level | |||
} | |||
~TempDialogWindow() | |||
@@ -473,12 +473,10 @@ void Graphics::drawRoundedRectangle (const Rectangle<float>& r, const float corn | |||
drawRoundedRectangle (r.getX(), r.getY(), r.getWidth(), r.getHeight(), cornerSize, lineThickness); | |||
} | |||
void Graphics::drawArrow (const float startX, const float startY, const float endX, const float endY, | |||
const float lineThickness, const float arrowheadWidth, const float arrowheadLength) const | |||
void Graphics::drawArrow (const Line<float>& line, const float lineThickness, const float arrowheadWidth, const float arrowheadLength) const | |||
{ | |||
Path p; | |||
p.addArrow (startX, startY, endX, endY, | |||
lineThickness, arrowheadWidth, arrowheadLength); | |||
p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength); | |||
fillPath (p); | |||
} | |||
@@ -545,9 +543,7 @@ void Graphics::drawLine (const float startX, const float startY, | |||
const float endX, const float endY, | |||
const float lineThickness) const | |||
{ | |||
Path p; | |||
p.addLineSegment (startX, startY, endX, endY, lineThickness); | |||
fillPath (p); | |||
drawLine (Line<float> (startX, startY, endX, endY),lineThickness); | |||
} | |||
void Graphics::drawLine (const Line<float>& line) const | |||
@@ -557,7 +553,9 @@ void Graphics::drawLine (const Line<float>& line) const | |||
void Graphics::drawLine (const Line<float>& line, const float lineThickness) const | |||
{ | |||
drawLine (line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY(), lineThickness); | |||
Path p; | |||
p.addLineSegment (line, lineThickness); | |||
fillPath (p); | |||
} | |||
void Graphics::drawDashedLine (const float startX, const float startY, | |||
@@ -415,18 +415,14 @@ public: | |||
const PathStrokeType& strokeType, | |||
const AffineTransform& transform = AffineTransform::identity) const; | |||
/** Draws a line with an arrowhead. | |||
/** Draws a line with an arrowhead at its end. | |||
@param startX the line's start x co-ordinate | |||
@param startY the line's start y co-ordinate | |||
@param endX the line's end x co-ordinate (the tip of the arrowhead) | |||
@param endY the line's end y co-ordinate (the tip of the arrowhead) | |||
@param line the line to draw | |||
@param lineThickness the thickness of the line | |||
@param arrowheadWidth the width of the arrow head (perpendicular to the line) | |||
@param arrowheadLength the length of the arrow head (along the length of the line) | |||
*/ | |||
void drawArrow (float startX, float startY, | |||
float endX, float endY, | |||
void drawArrow (const Line<float>& line, | |||
float lineThickness, | |||
float arrowheadWidth, | |||
float arrowheadLength) const; | |||
@@ -502,7 +502,7 @@ void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, co | |||
void LowLevelGraphicsPostScriptRenderer::drawLine (const Line <float>& line) | |||
{ | |||
Path p; | |||
p.addLineSegment (line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY(), 1.0f); | |||
p.addLineSegment (line, 1.0f); | |||
fillPath (p, AffineTransform::identity); | |||
} | |||
@@ -2140,7 +2140,7 @@ void LowLevelGraphicsSoftwareRenderer::drawImage (const Image& sourceImage, cons | |||
void LowLevelGraphicsSoftwareRenderer::drawLine (const Line <float>& line) | |||
{ | |||
Path p; | |||
p.addLineSegment (line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY(), 1.0f); | |||
p.addLineSegment (line, 1.0f); | |||
fillPath (p, AffineTransform::identity); | |||
} | |||
@@ -36,18 +36,19 @@ BEGIN_JUCE_NAMESPACE | |||
//============================================================================== | |||
DrawableComposite::DrawableComposite() | |||
: bounds (Point<float>(), Point<float> (100.0f, 0.0f), Point<float> (0.0f, 100.0f)) | |||
{ | |||
controlPoints[1] = RelativePoint (Point<float> (1.0f, 0.0f)); | |||
controlPoints[2] = RelativePoint (Point<float> (0.0f, 1.0f)); | |||
setContentArea (RelativeRectangle (RelativeCoordinate (0.0, true), | |||
RelativeCoordinate (100.0, true), | |||
RelativeCoordinate (0.0, false), | |||
RelativeCoordinate (100.0, false))); | |||
} | |||
DrawableComposite::DrawableComposite (const DrawableComposite& other) | |||
{ | |||
int i; | |||
for (i = 0; i < 3; ++i) | |||
controlPoints[i] = other.controlPoints[i]; | |||
bounds = other.bounds; | |||
for (i = 0; i < other.drawables.size(); ++i) | |||
for (int i = 0; i < other.drawables.size(); ++i) | |||
drawables.add (other.drawables.getUnchecked(i)->createCopy()); | |||
markersX.addCopiesOf (other.markersX); | |||
@@ -95,13 +96,9 @@ void DrawableComposite::bringToFront (const int index) | |||
drawables.move (index, -1); | |||
} | |||
void DrawableComposite::setTransform (const RelativePoint& targetPositionForOrigin, | |||
const RelativePoint& targetPositionForX1Y0, | |||
const RelativePoint& targetPositionForX0Y1) | |||
void DrawableComposite::setBoundingBox (const RelativeParallelogram& newBoundingBox) | |||
{ | |||
controlPoints[0] = targetPositionForOrigin; | |||
controlPoints[1] = targetPositionForX1Y0; | |||
controlPoints[2] = targetPositionForX0Y1; | |||
bounds = newBoundingBox; | |||
} | |||
//============================================================================== | |||
@@ -121,6 +118,37 @@ bool DrawableComposite::Marker::operator!= (const DrawableComposite::Marker& oth | |||
} | |||
//============================================================================== | |||
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 RelativeRectangle DrawableComposite::getContentArea() const | |||
{ | |||
jassert (markersX.size() >= 2 && getMarker (true, 0)->name == contentLeftMarkerName && getMarker (true, 1)->name == contentRightMarkerName); | |||
jassert (markersY.size() >= 2 && getMarker (false, 0)->name == contentTopMarkerName && getMarker (false, 1)->name == contentBottomMarkerName); | |||
return RelativeRectangle (markersX.getUnchecked(0)->position, markersX.getUnchecked(1)->position, | |||
markersY.getUnchecked(0)->position, markersY.getUnchecked(1)->position); | |||
} | |||
void DrawableComposite::setContentArea (const RelativeRectangle& newArea) | |||
{ | |||
setMarker (contentLeftMarkerName, true, newArea.left); | |||
setMarker (contentRightMarkerName, true, newArea.right); | |||
setMarker (contentTopMarkerName, false, newArea.top); | |||
setMarker (contentBottomMarkerName, false, newArea.bottom); | |||
} | |||
void DrawableComposite::resetBoundingBoxToContentArea() | |||
{ | |||
const RelativeRectangle content (getContentArea()); | |||
setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top), | |||
RelativePoint (content.right, content.top), | |||
RelativePoint (content.left, content.bottom))); | |||
} | |||
int DrawableComposite::getNumMarkers (const bool xAxis) const throw() | |||
{ | |||
return (xAxis ? markersX : markersY).size(); | |||
@@ -156,19 +184,23 @@ void DrawableComposite::setMarker (const String& name, const bool xAxis, const R | |||
void DrawableComposite::removeMarker (const bool xAxis, const int index) | |||
{ | |||
(xAxis ? markersX : markersY).remove (index); | |||
jassert (index >= 2); | |||
if (index >= 2) | |||
(xAxis ? markersX : markersY).remove (index); | |||
} | |||
//============================================================================== | |||
const AffineTransform DrawableComposite::calculateTransform() const | |||
{ | |||
Point<float> resolved[3]; | |||
for (int i = 0; i < 3; ++i) | |||
resolved[i] = controlPoints[i].resolve (parent); | |||
bounds.resolveThreePoints (resolved, parent); | |||
const Rectangle<float> content (getContentArea().resolve (parent)); | |||
return AffineTransform::fromTargetPoints (resolved[0].getX(), resolved[0].getY(), | |||
resolved[1].getX(), resolved[1].getY(), | |||
resolved[2].getX(), resolved[2].getY()); | |||
return AffineTransform::fromTargetPoints (content.getX(), content.getY(), resolved[0].getX(), resolved[0].getY(), | |||
content.getRight(), content.getY(), resolved[1].getX(), resolved[1].getY(), | |||
content.getX(), content.getBottom(), resolved[2].getX(), resolved[2].getY()); | |||
} | |||
void DrawableComposite::render (const Drawable::RenderingContext& context) const | |||
@@ -426,37 +458,43 @@ void DrawableComposite::ValueTreeWrapper::removeDrawable (const ValueTree& child | |||
getChildList().removeChild (child, undoManager); | |||
} | |||
const RelativePoint DrawableComposite::ValueTreeWrapper::getTargetPositionForOrigin() const | |||
const RelativeParallelogram DrawableComposite::ValueTreeWrapper::getBoundingBox() const | |||
{ | |||
const String pos (state [topLeft].toString()); | |||
return pos.isNotEmpty() ? RelativePoint (pos) : RelativePoint (); | |||
return RelativeParallelogram (state.getProperty (topLeft, "0, 0"), | |||
state.getProperty (topRight, "100, 0"), | |||
state.getProperty (bottomLeft, "0, 100")); | |||
} | |||
void DrawableComposite::ValueTreeWrapper::setTargetPositionForOrigin (const RelativePoint& newPoint, UndoManager* undoManager) | |||
void DrawableComposite::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) | |||
{ | |||
state.setProperty (topLeft, newPoint.toString(), undoManager); | |||
state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); | |||
state.setProperty (topRight, newBounds.topRight.toString(), undoManager); | |||
state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); | |||
} | |||
const RelativePoint DrawableComposite::ValueTreeWrapper::getTargetPositionForX1Y0() const | |||
void DrawableComposite::ValueTreeWrapper::resetBoundingBoxToContentArea (UndoManager* undoManager) | |||
{ | |||
const String pos (state [topRight].toString()); | |||
return pos.isNotEmpty() ? RelativePoint (pos) : RelativePoint (Point<float> (1.0f, 0.0f)); | |||
} | |||
const RelativeRectangle content (getContentArea()); | |||
void DrawableComposite::ValueTreeWrapper::setTargetPositionForX1Y0 (const RelativePoint& newPoint, UndoManager* undoManager) | |||
{ | |||
state.setProperty (topRight, newPoint.toString(), undoManager); | |||
setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top), | |||
RelativePoint (content.right, content.top), | |||
RelativePoint (content.left, content.bottom)), undoManager); | |||
} | |||
const RelativePoint DrawableComposite::ValueTreeWrapper::getTargetPositionForX0Y1() const | |||
const RelativeRectangle DrawableComposite::ValueTreeWrapper::getContentArea() const | |||
{ | |||
const String pos (state [bottomLeft].toString()); | |||
return pos.isNotEmpty() ? RelativePoint (pos) : RelativePoint (Point<float> (0.0f, 1.0f)); | |||
return RelativeRectangle (getMarker (true, getMarkerState (true, 0)).position, | |||
getMarker (true, getMarkerState (true, 1)).position, | |||
getMarker (false, getMarkerState (false, 0)).position, | |||
getMarker (false, getMarkerState (false, 1)).position); | |||
} | |||
void DrawableComposite::ValueTreeWrapper::setTargetPositionForX0Y1 (const RelativePoint& newPoint, UndoManager* undoManager) | |||
void DrawableComposite::ValueTreeWrapper::setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager) | |||
{ | |||
state.setProperty (bottomLeft, newPoint.toString(), undoManager); | |||
setMarker (true, Marker (contentLeftMarkerName, newArea.left), undoManager); | |||
setMarker (true, Marker (contentRightMarkerName, newArea.right), undoManager); | |||
setMarker (false, Marker (contentTopMarkerName, newArea.top), undoManager); | |||
setMarker (false, Marker (contentBottomMarkerName, newArea.bottom), undoManager); | |||
} | |||
ValueTree DrawableComposite::ValueTreeWrapper::getMarkerList (bool xAxis) const | |||
@@ -516,58 +554,48 @@ void DrawableComposite::ValueTreeWrapper::setMarker (bool xAxis, const Marker& m | |||
void DrawableComposite::ValueTreeWrapper::removeMarker (bool xAxis, const ValueTree& state, UndoManager* undoManager) | |||
{ | |||
return getMarkerList (xAxis).removeChild (state, undoManager); | |||
if (state [nameProperty].toString() != contentLeftMarkerName | |||
&& state [nameProperty].toString() != contentRightMarkerName | |||
&& state [nameProperty].toString() != contentTopMarkerName | |||
&& state [nameProperty].toString() != contentBottomMarkerName) | |||
return getMarkerList (xAxis).removeChild (state, 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() }; | |||
const ValueTreeWrapper wrapper (tree); | |||
setName (wrapper.getID()); | |||
const RelativeParallelogram newBounds (wrapper.getBoundingBox()); | |||
bool redrawAll = false; | |||
if (controlPoints[0] != newControlPoint[0] | |||
|| controlPoints[1] != newControlPoint[1] | |||
|| controlPoints[2] != newControlPoint[2]) | |||
if (bounds != newBounds) | |||
{ | |||
redrawAll = true; | |||
damageRect = getUntransformedBounds(); | |||
controlPoints[0] = newControlPoint[0]; | |||
controlPoints[1] = newControlPoint[1]; | |||
controlPoints[2] = newControlPoint[2]; | |||
bounds = newBounds; | |||
} | |||
const int numMarkersX = controller.getNumMarkers (true); | |||
const int numMarkersY = controller.getNumMarkers (false); | |||
const int numMarkersX = wrapper.getNumMarkers (true); | |||
const int numMarkersY = wrapper.getNumMarkers (false); | |||
// Remove deleted markers... | |||
int i; | |||
for (i = markersX.size(); --i >= numMarkersX;) | |||
if (markersX.size() > numMarkersX || markersY.size() > numMarkersY) | |||
{ | |||
if (damageRect.isEmpty()) | |||
damageRect = getUntransformedBounds(); | |||
markersX.remove (i); | |||
} | |||
for (i = markersY.size(); --i >= numMarkersY;) | |||
{ | |||
if (damageRect.isEmpty()) | |||
damageRect = getUntransformedBounds(); | |||
markersY.remove (i); | |||
markersX.removeRange (jmax (2, numMarkersX), markersX.size()); | |||
markersY.removeRange (jmax (2, numMarkersY), markersY.size()); | |||
} | |||
// Update markers and add new ones.. | |||
int i; | |||
for (i = 0; i < numMarkersX; ++i) | |||
{ | |||
const Marker newMarker (controller.getMarker (true, controller.getMarkerState (true, i))); | |||
const Marker newMarker (wrapper.getMarker (true, wrapper.getMarkerState (true, i))); | |||
Marker* m = markersX[i]; | |||
if (m == 0 || newMarker != *m) | |||
@@ -585,7 +613,7 @@ const Rectangle<float> DrawableComposite::refreshFromValueTree (const ValueTree& | |||
for (i = 0; i < numMarkersY; ++i) | |||
{ | |||
const Marker newMarker (controller.getMarker (false, controller.getMarkerState (false, i))); | |||
const Marker newMarker (wrapper.getMarker (false, wrapper.getMarkerState (false, i))); | |||
Marker* m = markersY[i]; | |||
if (m == 0 || newMarker != *m) | |||
@@ -602,7 +630,7 @@ const Rectangle<float> DrawableComposite::refreshFromValueTree (const ValueTree& | |||
} | |||
// Remove deleted drawables.. | |||
for (i = drawables.size(); --i >= controller.getNumDrawables();) | |||
for (i = drawables.size(); --i >= wrapper.getNumDrawables();) | |||
{ | |||
Drawable* const d = drawables.getUnchecked(i); | |||
damageRect = damageRect.getUnion (d->getBounds()); | |||
@@ -611,9 +639,9 @@ const Rectangle<float> DrawableComposite::refreshFromValueTree (const ValueTree& | |||
} | |||
// Update drawables and add new ones.. | |||
for (i = 0; i < controller.getNumDrawables(); ++i) | |||
for (i = 0; i < wrapper.getNumDrawables(); ++i) | |||
{ | |||
const ValueTree newDrawable (controller.getDrawableState (i)); | |||
const ValueTree newDrawable (wrapper.getDrawableState (i)); | |||
Drawable* d = drawables[i]; | |||
if (d != 0) | |||
@@ -650,9 +678,7 @@ const ValueTree DrawableComposite::createValueTree (ImageProvider* imageProvider | |||
ValueTreeWrapper v (tree); | |||
v.setID (getName(), 0); | |||
v.setTargetPositionForOrigin (controlPoints[0], 0); | |||
v.setTargetPositionForX1Y0 (controlPoints[1], 0); | |||
v.setTargetPositionForX0Y1 (controlPoints[2], 0); | |||
v.setBoundingBox (bounds, 0); | |||
int i; | |||
for (i = 0; i < drawables.size(); ++i) | |||
@@ -118,39 +118,40 @@ public: | |||
*/ | |||
void bringToFront (int index); | |||
/** Sets the transform to be applied to this drawable, by defining the positions | |||
where three anchor points should end up in the target rendering space. | |||
@param targetPositionForOrigin the position that the local coordinate (0, 0) should be | |||
mapped onto when rendering this object. | |||
@param targetPositionForX1Y0 the position that the local coordinate (1, 0) should be | |||
mapped onto when rendering this object. | |||
@param targetPositionForX0Y1 the position that the local coordinate (0, 1) should be | |||
mapped onto when rendering this object. | |||
//============================================================================== | |||
/** 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 contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName | |||
*/ | |||
const RelativeRectangle getContentArea() const; | |||
/** 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 setBoundingBox, contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName | |||
*/ | |||
void setTransform (const RelativePoint& targetPositionForOrigin, | |||
const RelativePoint& targetPositionForX1Y0, | |||
const RelativePoint& targetPositionForX0Y1); | |||
void setContentArea (const RelativeRectangle& newArea); | |||
/** Returns the position to which the local coordinate (0, 0) should be remapped in the target | |||
coordinate space when rendering this object. | |||
@see setTransform | |||
/** Sets the parallelogram that defines the target position of the content rectangle when the drawable is rendered. | |||
@see setContentArea | |||
*/ | |||
const RelativePoint& getTargetPositionForOrigin() const throw() { return controlPoints[0]; } | |||
void setBoundingBox (const RelativeParallelogram& newBoundingBox); | |||
/** Returns the position to which the local coordinate (1, 0) should be remapped in the target | |||
coordinate space when rendering this object. | |||
@see setTransform | |||
/** Returns the parallelogram that defines the target position of the content rectangle when the drawable is rendered. | |||
@see setBoundingBox | |||
*/ | |||
const RelativePoint& getTargetPositionForX1Y0() const throw() { return controlPoints[1]; } | |||
const RelativeParallelogram& getBoundingBox() const throw() { return bounds; } | |||
/** Returns the position to which the local coordinate (0, 1) should be remapped in the target | |||
coordinate space when rendering this object. | |||
@see setTransform | |||
/** Changes the bounding box transform to match the content area, so that any sub-items will | |||
be drawn at their untransformed positions. | |||
*/ | |||
const RelativePoint& getTargetPositionForX0Y1() const throw() { return controlPoints[2]; } | |||
void resetBoundingBoxToContentArea(); | |||
//============================================================================== | |||
/** Represents a named marker position. | |||
@see DrawableComposite::getMarker | |||
*/ | |||
struct Marker | |||
{ | |||
Marker (const Marker&); | |||
@@ -166,6 +167,15 @@ public: | |||
void setMarker (const String& name, bool xAxis, const RelativeCoordinate& position); | |||
void removeMarker (bool xAxis, int index); | |||
/** 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 */ | |||
void render (const Drawable::RenderingContext& context) const; | |||
@@ -203,14 +213,12 @@ public: | |||
void moveDrawableOrder (int currentIndex, int newIndex, UndoManager* undoManager); | |||
void removeDrawable (const ValueTree& child, UndoManager* undoManager); | |||
const RelativePoint getTargetPositionForOrigin() const; | |||
void setTargetPositionForOrigin (const RelativePoint& newPoint, UndoManager* undoManager); | |||
const RelativePoint getTargetPositionForX1Y0() const; | |||
void setTargetPositionForX1Y0 (const RelativePoint& newPoint, UndoManager* undoManager); | |||
const RelativeParallelogram getBoundingBox() const; | |||
void setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager); | |||
void resetBoundingBoxToContentArea (UndoManager* undoManager); | |||
const RelativePoint getTargetPositionForX0Y1() const; | |||
void setTargetPositionForX0Y1 (const RelativePoint& newPoint, UndoManager* undoManager); | |||
const RelativeRectangle getContentArea() const; | |||
void setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager); | |||
int getNumMarkers (bool xAxis) const; | |||
const ValueTree getMarkerState (bool xAxis, int index) const; | |||
@@ -237,7 +245,7 @@ public: | |||
private: | |||
OwnedArray <Drawable> drawables; | |||
RelativePoint controlPoints[3]; | |||
RelativeParallelogram bounds; | |||
OwnedArray <Marker> markersX, markersY; | |||
const Rectangle<float> getUntransformedBounds() const; | |||
@@ -66,8 +66,8 @@ public: | |||
newState.elementX = getCoordLength (xml.getStringAttribute ("x", String (newState.elementX)), viewBoxW); | |||
newState.elementY = getCoordLength (xml.getStringAttribute ("y", String (newState.elementY)), viewBoxH); | |||
newState.width = getCoordLength (xml.getStringAttribute ("width", String (newState.width)), viewBoxW); | |||
newState.height = getCoordLength (xml.getStringAttribute ("height", String (newState.height)), viewBoxH); | |||
newState.width = getCoordLength (xml.getStringAttribute ("width", String (newState.width)), viewBoxW); | |||
newState.height = getCoordLength (xml.getStringAttribute ("height", String (newState.height)), viewBoxH); | |||
if (xml.hasAttribute ("viewBox")) | |||
{ | |||
@@ -130,6 +130,13 @@ public: | |||
newState.parseSubElements (xml, drawable); | |||
const Rectangle<float> bounds (drawable->getBounds()); | |||
drawable->setContentArea (RelativeRectangle (RelativeCoordinate (bounds.getX(), true), | |||
RelativeCoordinate (bounds.getRight(), true), | |||
RelativeCoordinate (bounds.getY(), false), | |||
RelativeCoordinate (bounds.getBottom(), false))); | |||
drawable->resetBoundingBoxToContentArea(); | |||
return drawable; | |||
} | |||
@@ -147,30 +154,18 @@ private: | |||
{ | |||
Drawable* d = 0; | |||
if (e->hasTagName ("g")) | |||
d = parseGroupElement (*e); | |||
else if (e->hasTagName ("svg")) | |||
d = parseSVGElement (*e); | |||
else if (e->hasTagName ("path")) | |||
d = parsePath (*e); | |||
else if (e->hasTagName ("rect")) | |||
d = parseRect (*e); | |||
else if (e->hasTagName ("circle")) | |||
d = parseCircle (*e); | |||
else if (e->hasTagName ("ellipse")) | |||
d = parseEllipse (*e); | |||
else if (e->hasTagName ("line")) | |||
d = parseLine (*e); | |||
else if (e->hasTagName ("polyline")) | |||
d = parsePolygon (*e, true); | |||
else if (e->hasTagName ("polygon")) | |||
d = parsePolygon (*e, false); | |||
else if (e->hasTagName ("text")) | |||
d = parseText (*e); | |||
else if (e->hasTagName ("switch")) | |||
d = parseSwitch (*e); | |||
else if (e->hasTagName ("style")) | |||
parseCSSStyle (*e); | |||
if (e->hasTagName ("g")) d = parseGroupElement (*e); | |||
else if (e->hasTagName ("svg")) d = parseSVGElement (*e); | |||
else if (e->hasTagName ("path")) d = parsePath (*e); | |||
else if (e->hasTagName ("rect")) d = parseRect (*e); | |||
else if (e->hasTagName ("circle")) d = parseCircle (*e); | |||
else if (e->hasTagName ("ellipse")) d = parseEllipse (*e); | |||
else if (e->hasTagName ("line")) d = parseLine (*e); | |||
else if (e->hasTagName ("polyline")) d = parsePolygon (*e, true); | |||
else if (e->hasTagName ("polygon")) d = parsePolygon (*e, false); | |||
else if (e->hasTagName ("text")) d = parseText (*e); | |||
else if (e->hasTagName ("switch")) d = parseSwitch (*e); | |||
else if (e->hasTagName ("style")) parseCSSStyle (*e); | |||
parentDrawable->insertDrawable (d); | |||
} | |||
@@ -544,8 +539,8 @@ private: | |||
{ | |||
Path ellipse; | |||
const float cx = getCoordLength (xml.getStringAttribute ("cx"), viewBoxW); | |||
const float cy = getCoordLength (xml.getStringAttribute ("cy"), viewBoxH); | |||
const float cx = getCoordLength (xml.getStringAttribute ("cx"), viewBoxW); | |||
const float cy = getCoordLength (xml.getStringAttribute ("cy"), viewBoxH); | |||
const float radiusX = getCoordLength (xml.getStringAttribute ("rx"), viewBoxW); | |||
const float radiusY = getCoordLength (xml.getStringAttribute ("ry"), viewBoxH); | |||
@@ -767,8 +767,8 @@ void GlyphArrangement::draw (const Graphics& g, const AffineTransform& transform | |||
nextX = glyphs.getUnchecked (i + 1)->x; | |||
Path p; | |||
p.addLineSegment (pg->x, pg->y + lineThickness * 2.0f, | |||
nextX, pg->y + lineThickness * 2.0f, | |||
p.addLineSegment (Line<float> (pg->x, pg->y + lineThickness * 2.0f, | |||
nextX, pg->y + lineThickness * 2.0f), | |||
lineThickness); | |||
g.fillPath (p, transform); | |||
@@ -115,6 +115,9 @@ public: | |||
/** Changes this line's end point */ | |||
void setEnd (const Point<ValueType>& newEnd) throw() { end = newEnd; } | |||
/** Returns a line that is the same as this one, but with the start and end reversed, */ | |||
const Line reversed() const throw() { return Line (end, start); } | |||
/** Applies an affine transform to the line's start and end points. */ | |||
void applyTransform (const AffineTransform& transform) throw() | |||
{ | |||
@@ -43,27 +43,6 @@ namespace PathHelpers | |||
{ | |||
static const float ellipseAngularIncrement = 0.05f; | |||
static void perpendicularOffset (const float x1, const float y1, | |||
const float x2, const float y2, | |||
const float offsetX, const float offsetY, | |||
float& resultX, float& resultY) throw() | |||
{ | |||
const float dx = x2 - x1; | |||
const float dy = y2 - y1; | |||
const float len = juce_hypotf (dx, dy); | |||
if (len == 0) | |||
{ | |||
resultX = x1; | |||
resultY = y1; | |||
} | |||
else | |||
{ | |||
resultX = x1 + ((dx * offsetX) - (dy * offsetY)) / len; | |||
resultY = y1 + ((dy * offsetX) + (dx * offsetY)) / len; | |||
} | |||
} | |||
static const String nextToken (const juce_wchar*& t) | |||
{ | |||
while (CharacterFunctions::isWhitespace (*t)) | |||
@@ -246,6 +225,11 @@ void Path::startNewSubPath (const float x, const float y) | |||
data.elements [numElements++] = y; | |||
} | |||
void Path::startNewSubPath (const Point<float>& start) | |||
{ | |||
startNewSubPath (start.getX(), start.getY()); | |||
} | |||
void Path::lineTo (const float x, const float y) | |||
{ | |||
CHECK_COORDS_ARE_VALID (x, y); | |||
@@ -265,6 +249,11 @@ void Path::lineTo (const float x, const float y) | |||
pathYMax = jmax (pathYMax, y); | |||
} | |||
void Path::lineTo (const Point<float>& end) | |||
{ | |||
lineTo (end.getX(), end.getY()); | |||
} | |||
void Path::quadraticTo (const float x1, const float y1, | |||
const float x2, const float y2) | |||
{ | |||
@@ -288,6 +277,13 @@ void Path::quadraticTo (const float x1, const float y1, | |||
pathYMax = jmax (pathYMax, y1, y2); | |||
} | |||
void Path::quadraticTo (const Point<float>& controlPoint, | |||
const Point<float>& endPoint) | |||
{ | |||
quadraticTo (controlPoint.getX(), controlPoint.getY(), | |||
endPoint.getX(), endPoint.getY()); | |||
} | |||
void Path::cubicTo (const float x1, const float y1, | |||
const float x2, const float y2, | |||
const float x3, const float y3) | |||
@@ -315,6 +311,15 @@ void Path::cubicTo (const float x1, const float y1, | |||
pathYMax = jmax (pathYMax, y1, y2, y3); | |||
} | |||
void Path::cubicTo (const Point<float>& controlPoint1, | |||
const Point<float>& controlPoint2, | |||
const Point<float>& endPoint) | |||
{ | |||
cubicTo (controlPoint1.getX(), controlPoint1.getY(), | |||
controlPoint2.getX(), controlPoint2.getY(), | |||
endPoint.getX(), endPoint.getY()); | |||
} | |||
void Path::closeSubPath() | |||
{ | |||
if (numElements > 0 | |||
@@ -495,19 +500,12 @@ void Path::addCentredArc (const float centreX, const float centreY, | |||
{ | |||
if (radiusX > 0.0f && radiusY > 0.0f) | |||
{ | |||
const Point<float> centre (centreX, centreY); | |||
const AffineTransform rotation (AffineTransform::rotation (rotationOfEllipse, centreX, centreY)); | |||
float angle = fromRadians; | |||
if (startAsNewSubPath) | |||
{ | |||
float x = centreX + radiusX * std::sin (angle); | |||
float y = centreY - radiusY * std::cos (angle); | |||
if (rotationOfEllipse != 0) | |||
rotation.transformPoint (x, y); | |||
startNewSubPath (x, y); | |||
} | |||
startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); | |||
if (fromRadians < toRadians) | |||
{ | |||
@@ -516,14 +514,7 @@ void Path::addCentredArc (const float centreX, const float centreY, | |||
while (angle < toRadians) | |||
{ | |||
float x = centreX + radiusX * std::sin (angle); | |||
float y = centreY - radiusY * std::cos (angle); | |||
if (rotationOfEllipse != 0) | |||
rotation.transformPoint (x, y); | |||
lineTo (x, y); | |||
lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); | |||
angle += PathHelpers::ellipseAngularIncrement; | |||
} | |||
} | |||
@@ -534,25 +525,12 @@ void Path::addCentredArc (const float centreX, const float centreY, | |||
while (angle > toRadians) | |||
{ | |||
float x = centreX + radiusX * std::sin (angle); | |||
float y = centreY - radiusY * std::cos (angle); | |||
if (rotationOfEllipse != 0) | |||
rotation.transformPoint (x, y); | |||
lineTo (x, y); | |||
lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); | |||
angle -= PathHelpers::ellipseAngularIncrement; | |||
} | |||
} | |||
float x = centreX + radiusX * std::sin (toRadians); | |||
float y = centreY - radiusY * std::cos (toRadians); | |||
if (rotationOfEllipse != 0) | |||
rotation.transformPoint (x, y); | |||
lineTo (x, y); | |||
lineTo (centre.getPointOnCircumference (radiusX, radiusY, toRadians).transformedBy (rotation)); | |||
} | |||
} | |||
@@ -562,14 +540,11 @@ void Path::addPieSegment (const float x, const float y, | |||
const float toRadians, | |||
const float innerCircleProportionalSize) | |||
{ | |||
float hw = width * 0.5f; | |||
float hh = height * 0.5f; | |||
const float centreX = x + hw; | |||
const float centreY = y + hh; | |||
startNewSubPath (centreX + hw * std::sin (fromRadians), | |||
centreY - hh * std::cos (fromRadians)); | |||
float radiusX = width * 0.5f; | |||
float radiusY = height * 0.5f; | |||
const Point<float> centre (x + radiusX, y + radiusY); | |||
startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, fromRadians)); | |||
addArc (x, y, width, height, fromRadians, toRadians); | |||
if (std::abs (fromRadians - toRadians) > float_Pi * 1.999f) | |||
@@ -578,29 +553,25 @@ void Path::addPieSegment (const float x, const float y, | |||
if (innerCircleProportionalSize > 0) | |||
{ | |||
hw *= innerCircleProportionalSize; | |||
hh *= innerCircleProportionalSize; | |||
radiusX *= innerCircleProportionalSize; | |||
radiusY *= innerCircleProportionalSize; | |||
startNewSubPath (centreX + hw * std::sin (toRadians), | |||
centreY - hh * std::cos (toRadians)); | |||
addArc (centreX - hw, centreY - hh, hw * 2.0f, hh * 2.0f, | |||
toRadians, fromRadians); | |||
startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, toRadians)); | |||
addArc (centre.getX() - radiusX, centre.getY() - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians); | |||
} | |||
} | |||
else | |||
{ | |||
if (innerCircleProportionalSize > 0) | |||
{ | |||
hw *= innerCircleProportionalSize; | |||
hh *= innerCircleProportionalSize; | |||
radiusX *= innerCircleProportionalSize; | |||
radiusY *= innerCircleProportionalSize; | |||
addArc (centreX - hw, centreY - hh, hw * 2.0f, hh * 2.0f, | |||
toRadians, fromRadians); | |||
addArc (centre.getX() - radiusX, centre.getY() - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians); | |||
} | |||
else | |||
{ | |||
lineTo (centreX, centreY); | |||
lineTo (centre); | |||
} | |||
} | |||
@@ -608,83 +579,62 @@ void Path::addPieSegment (const float x, const float y, | |||
} | |||
//============================================================================== | |||
void Path::addLineSegment (const float startX, const float startY, | |||
const float endX, const float endY, | |||
float lineThickness) | |||
void Path::addLineSegment (const Line<float>& line, float lineThickness) | |||
{ | |||
const Line<float> reversed (line.reversed()); | |||
lineThickness *= 0.5f; | |||
float x, y; | |||
PathHelpers::perpendicularOffset (startX, startY, endX, endY, | |||
0, lineThickness, x, y); | |||
startNewSubPath (x, y); | |||
PathHelpers::perpendicularOffset (startX, startY, endX, endY, | |||
0, -lineThickness, x, y); | |||
lineTo (x, y); | |||
PathHelpers::perpendicularOffset (endX, endY, startX, startY, | |||
0, lineThickness, x, y); | |||
lineTo (x, y); | |||
PathHelpers::perpendicularOffset (endX, endY, startX, startY, | |||
0, -lineThickness, x, y); | |||
lineTo (x, y); | |||
startNewSubPath (line.getPointAlongLine (0, lineThickness)); | |||
lineTo (line.getPointAlongLine (0, -lineThickness)); | |||
lineTo (reversed.getPointAlongLine (0, lineThickness)); | |||
lineTo (reversed.getPointAlongLine (0, -lineThickness)); | |||
closeSubPath(); | |||
} | |||
void Path::addArrow (const float startX, const float startY, | |||
const float endX, const float endY, | |||
float lineThickness, | |||
float arrowheadWidth, | |||
float arrowheadLength) | |||
void Path::addArrow (const Line<float>& line, float lineThickness, | |||
float arrowheadWidth, float arrowheadLength) | |||
{ | |||
const Line<float> reversed (line.reversed()); | |||
lineThickness *= 0.5f; | |||
arrowheadWidth *= 0.5f; | |||
arrowheadLength = jmin (arrowheadLength, 0.8f * juce_hypotf (startX - endX, | |||
startY - endY)); | |||
float x, y; | |||
PathHelpers::perpendicularOffset (startX, startY, endX, endY, | |||
0, lineThickness, x, y); | |||
startNewSubPath (x, y); | |||
PathHelpers::perpendicularOffset (startX, startY, endX, endY, | |||
0, -lineThickness, x, y); | |||
lineTo (x, y); | |||
PathHelpers::perpendicularOffset (endX, endY, startX, startY, | |||
arrowheadLength, lineThickness, x, y); | |||
lineTo (x, y); | |||
arrowheadLength = jmin (arrowheadLength, 0.8f * line.getLength()); | |||
startNewSubPath (line.getPointAlongLine (0, lineThickness)); | |||
lineTo (line.getPointAlongLine (0, -lineThickness)); | |||
lineTo (reversed.getPointAlongLine (0, lineThickness)); | |||
lineTo (reversed.getPointAlongLine (arrowheadLength, arrowheadWidth)); | |||
lineTo (line.getEnd()); | |||
lineTo (reversed.getPointAlongLine (arrowheadLength, -arrowheadWidth)); | |||
lineTo (reversed.getPointAlongLine (arrowheadLength, -lineThickness)); | |||
closeSubPath(); | |||
} | |||
PathHelpers::perpendicularOffset (endX, endY, startX, startY, | |||
arrowheadLength, arrowheadWidth, x, y); | |||
lineTo (x, y); | |||
void Path::addPolygon (const Point<float>& centre, const int numberOfSides, | |||
const float radius, const float startAngle) | |||
{ | |||
jassert (numberOfSides > 1); // this would be silly. | |||
PathHelpers::perpendicularOffset (endX, endY, startX, startY, | |||
0, 0, x, y); | |||
lineTo (x, y); | |||
if (numberOfSides > 1) | |||
{ | |||
const float angleBetweenPoints = float_Pi * 2.0f / numberOfSides; | |||
PathHelpers::perpendicularOffset (endX, endY, startX, startY, | |||
arrowheadLength, -arrowheadWidth, x, y); | |||
lineTo (x, y); | |||
for (int i = 0; i < numberOfSides; ++i) | |||
{ | |||
const float angle = startAngle + i * angleBetweenPoints; | |||
const Point<float> p (centre.getPointOnCircumference (radius, angle)); | |||
PathHelpers::perpendicularOffset (endX, endY, startX, startY, | |||
arrowheadLength, -lineThickness, x, y); | |||
lineTo (x, y); | |||
if (i == 0) | |||
startNewSubPath (p); | |||
else | |||
lineTo (p); | |||
} | |||
closeSubPath(); | |||
closeSubPath(); | |||
} | |||
} | |||
void Path::addStar (const float centreX, | |||
const float centreY, | |||
const int numberOfPoints, | |||
const float innerRadius, | |||
const float outerRadius, | |||
const float startAngle) | |||
void Path::addStar (const Point<float>& centre, const int numberOfPoints, | |||
const float innerRadius, const float outerRadius, const float startAngle) | |||
{ | |||
jassert (numberOfPoints > 1); // this would be silly. | |||
@@ -694,20 +644,15 @@ void Path::addStar (const float centreX, | |||
for (int i = 0; i < numberOfPoints; ++i) | |||
{ | |||
float angle = startAngle + i * angleBetweenPoints; | |||
const float x = centreX + outerRadius * std::sin (angle); | |||
const float y = centreY - outerRadius * std::cos (angle); | |||
const float angle = startAngle + i * angleBetweenPoints; | |||
const Point<float> p (centre.getPointOnCircumference (outerRadius, angle)); | |||
if (i == 0) | |||
startNewSubPath (x, y); | |||
startNewSubPath (p); | |||
else | |||
lineTo (x, y); | |||
angle += angleBetweenPoints * 0.5f; | |||
lineTo (p); | |||
lineTo (centreX + innerRadius * std::sin (angle), | |||
centreY - innerRadius * std::cos (angle)); | |||
lineTo (centre.getPointOnCircumference (innerRadius, angle + angleBetweenPoints * 0.5f)); | |||
} | |||
closeSubPath(); | |||
@@ -176,6 +176,19 @@ public: | |||
*/ | |||
void startNewSubPath (float startX, float startY); | |||
/** Begins a new subpath with a given starting position. | |||
This will move the path's current position to the co-ordinates passed in and | |||
make it ready to draw lines or curves starting from this position. | |||
After adding whatever lines and curves are needed, you can either | |||
close the current sub-path using closeSubPath() or call startNewSubPath() | |||
to move to a new sub-path, leaving the old one open-ended. | |||
@see lineTo, quadraticTo, cubicTo, closeSubPath | |||
*/ | |||
void startNewSubPath (const Point<float>& start); | |||
/** Closes a the current sub-path with a line back to its start-point. | |||
When creating a closed shape such as a triangle, don't use 3 lineTo() | |||
@@ -201,6 +214,17 @@ public: | |||
*/ | |||
void lineTo (float endX, float endY); | |||
/** Adds a line from the shape's last position to a new end-point. | |||
This will connect the end-point of the last line or curve that was added | |||
to a new point, using a straight line. | |||
See the class description for an example of how to add lines and curves to a path. | |||
@see startNewSubPath, quadraticTo, cubicTo, closeSubPath | |||
*/ | |||
void lineTo (const Point<float>& end); | |||
/** Adds a quadratic bezier curve from the shape's last position to a new position. | |||
This will connect the end-point of the last line or curve that was added | |||
@@ -215,6 +239,18 @@ public: | |||
float endPointX, | |||
float endPointY); | |||
/** Adds a quadratic bezier curve from the shape's last position to a new position. | |||
This will connect the end-point of the last line or curve that was added | |||
to a new point, using a quadratic spline with one control-point. | |||
See the class description for an example of how to add lines and curves to a path. | |||
@see startNewSubPath, lineTo, cubicTo, closeSubPath | |||
*/ | |||
void quadraticTo (const Point<float>& controlPoint, | |||
const Point<float>& endPoint); | |||
/** Adds a cubic bezier curve from the shape's last position to a new position. | |||
This will connect the end-point of the last line or curve that was added | |||
@@ -231,6 +267,19 @@ public: | |||
float endPointX, | |||
float endPointY); | |||
/** Adds a cubic bezier curve from the shape's last position to a new position. | |||
This will connect the end-point of the last line or curve that was added | |||
to a new point, using a cubic spline with two control-points. | |||
See the class description for an example of how to add lines and curves to a path. | |||
@see startNewSubPath, lineTo, quadraticTo, closeSubPath | |||
*/ | |||
void cubicTo (const Point<float>& controlPoint1, | |||
const Point<float>& controlPoint2, | |||
const Point<float>& endPoint); | |||
/** Returns the last point that was added to the path by one of the drawing methods. | |||
*/ | |||
const Point<float> getCurrentPosition() const; | |||
@@ -402,26 +451,29 @@ public: | |||
@see addArrow | |||
*/ | |||
void addLineSegment (float startX, float startY, | |||
float endX, float endY, | |||
float lineThickness); | |||
void addLineSegment (const Line<float>& line, float lineThickness); | |||
/** Adds a line with an arrowhead on the end. | |||
The arrow is added as a new closed sub-path. (Any currently open paths will be | |||
left open). | |||
The arrow is added as a new closed sub-path. (Any currently open paths will be left open). | |||
@see PathStrokeType::createStrokeWithArrowheads | |||
*/ | |||
void addArrow (float startX, float startY, | |||
float endX, float endY, | |||
void addArrow (const Line<float>& line, | |||
float lineThickness, | |||
float arrowheadWidth, | |||
float arrowheadLength); | |||
/** Adds a star shape to the path. | |||
/** Adds a polygon shape to the path. | |||
@see addStar | |||
*/ | |||
void addPolygon (const Point<float>& centre, | |||
int numberOfSides, | |||
float radius, | |||
float startAngle = 0.0f); | |||
/** Adds a star shape to the path. | |||
@see addPolygon | |||
*/ | |||
void addStar (float centreX, | |||
float centreY, | |||
void addStar (const Point<float>& centre, | |||
int numberOfPoints, | |||
float innerRadius, | |||
float outerRadius, | |||
@@ -74,126 +74,125 @@ bool PathStrokeType::operator!= (const PathStrokeType& other) const throw() | |||
} | |||
//============================================================================== | |||
static bool lineIntersection (const float x1, const float y1, | |||
const float x2, const float y2, | |||
const float x3, const float y3, | |||
const float x4, const float y4, | |||
float& intersectionX, | |||
float& intersectionY, | |||
float& distanceBeyondLine1EndSquared) throw() | |||
namespace PathStrokeHelpers | |||
{ | |||
if (x2 != x3 || y2 != y3) | |||
static bool lineIntersection (const float x1, const float y1, | |||
const float x2, const float y2, | |||
const float x3, const float y3, | |||
const float x4, const float y4, | |||
float& intersectionX, | |||
float& intersectionY, | |||
float& distanceBeyondLine1EndSquared) throw() | |||
{ | |||
const float dx1 = x2 - x1; | |||
const float dy1 = y2 - y1; | |||
const float dx2 = x4 - x3; | |||
const float dy2 = y4 - y3; | |||
const float divisor = dx1 * dy2 - dx2 * dy1; | |||
if (divisor == 0) | |||
if (x2 != x3 || y2 != y3) | |||
{ | |||
if (! ((dx1 == 0 && dy1 == 0) || (dx2 == 0 && dy2 == 0))) | |||
const float dx1 = x2 - x1; | |||
const float dy1 = y2 - y1; | |||
const float dx2 = x4 - x3; | |||
const float dy2 = y4 - y3; | |||
const float divisor = dx1 * dy2 - dx2 * dy1; | |||
if (divisor == 0) | |||
{ | |||
if (dy1 == 0 && dy2 != 0) | |||
if (! ((dx1 == 0 && dy1 == 0) || (dx2 == 0 && dy2 == 0))) | |||
{ | |||
const float along = (y1 - y3) / dy2; | |||
intersectionX = x3 + along * dx2; | |||
intersectionY = y1; | |||
if (dy1 == 0 && dy2 != 0) | |||
{ | |||
const float along = (y1 - y3) / dy2; | |||
intersectionX = x3 + along * dx2; | |||
intersectionY = y1; | |||
distanceBeyondLine1EndSquared = intersectionX - x2; | |||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; | |||
if ((x2 > x1) == (intersectionX < x2)) | |||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; | |||
distanceBeyondLine1EndSquared = intersectionX - x2; | |||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; | |||
if ((x2 > x1) == (intersectionX < x2)) | |||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; | |||
return along >= 0 && along <= 1.0f; | |||
} | |||
else if (dy2 == 0 && dy1 != 0) | |||
{ | |||
const float along = (y3 - y1) / dy1; | |||
intersectionX = x1 + along * dx1; | |||
intersectionY = y3; | |||
return along >= 0 && along <= 1.0f; | |||
} | |||
else if (dy2 == 0 && dy1 != 0) | |||
{ | |||
const float along = (y3 - y1) / dy1; | |||
intersectionX = x1 + along * dx1; | |||
intersectionY = y3; | |||
distanceBeyondLine1EndSquared = (along - 1.0f) * dx1; | |||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; | |||
if (along < 1.0f) | |||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; | |||
distanceBeyondLine1EndSquared = (along - 1.0f) * dx1; | |||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; | |||
if (along < 1.0f) | |||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; | |||
return along >= 0 && along <= 1.0f; | |||
} | |||
else if (dx1 == 0 && dx2 != 0) | |||
{ | |||
const float along = (x1 - x3) / dx2; | |||
intersectionX = x1; | |||
intersectionY = y3 + along * dy2; | |||
return along >= 0 && along <= 1.0f; | |||
} | |||
else if (dx1 == 0 && dx2 != 0) | |||
{ | |||
const float along = (x1 - x3) / dx2; | |||
intersectionX = x1; | |||
intersectionY = y3 + along * dy2; | |||
distanceBeyondLine1EndSquared = intersectionY - y2; | |||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; | |||
distanceBeyondLine1EndSquared = intersectionY - y2; | |||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; | |||
if ((y2 > y1) == (intersectionY < y2)) | |||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; | |||
if ((y2 > y1) == (intersectionY < y2)) | |||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; | |||
return along >= 0 && along <= 1.0f; | |||
} | |||
else if (dx2 == 0 && dx1 != 0) | |||
{ | |||
const float along = (x3 - x1) / dx1; | |||
intersectionX = x3; | |||
intersectionY = y1 + along * dy1; | |||
return along >= 0 && along <= 1.0f; | |||
} | |||
else if (dx2 == 0 && dx1 != 0) | |||
{ | |||
const float along = (x3 - x1) / dx1; | |||
intersectionX = x3; | |||
intersectionY = y1 + along * dy1; | |||
distanceBeyondLine1EndSquared = (along - 1.0f) * dy1; | |||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; | |||
if (along < 1.0f) | |||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; | |||
distanceBeyondLine1EndSquared = (along - 1.0f) * dy1; | |||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; | |||
if (along < 1.0f) | |||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; | |||
return along >= 0 && along <= 1.0f; | |||
return along >= 0 && along <= 1.0f; | |||
} | |||
} | |||
} | |||
intersectionX = 0.5f * (x2 + x3); | |||
intersectionY = 0.5f * (y2 + y3); | |||
distanceBeyondLine1EndSquared = 0.0f; | |||
return false; | |||
} | |||
else | |||
{ | |||
const float along1 = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor; | |||
intersectionX = x1 + along1 * dx1; | |||
intersectionY = y1 + along1 * dy1; | |||
intersectionX = 0.5f * (x2 + x3); | |||
intersectionY = 0.5f * (y2 + y3); | |||
if (along1 >= 0 && along1 <= 1.0f) | |||
distanceBeyondLine1EndSquared = 0.0f; | |||
return false; | |||
} | |||
else | |||
{ | |||
const float along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1); | |||
const float along1 = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor; | |||
if (along2 >= 0 && along2 <= divisor) | |||
intersectionX = x1 + along1 * dx1; | |||
intersectionY = y1 + along1 * dy1; | |||
if (along1 >= 0 && along1 <= 1.0f) | |||
{ | |||
distanceBeyondLine1EndSquared = 0.0f; | |||
return true; | |||
const float along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1); | |||
if (along2 >= 0 && along2 <= divisor) | |||
{ | |||
distanceBeyondLine1EndSquared = 0.0f; | |||
return true; | |||
} | |||
} | |||
} | |||
distanceBeyondLine1EndSquared = along1 - 1.0f; | |||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; | |||
distanceBeyondLine1EndSquared *= (dx1 * dx1 + dy1 * dy1); | |||
distanceBeyondLine1EndSquared = along1 - 1.0f; | |||
distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; | |||
distanceBeyondLine1EndSquared *= (dx1 * dx1 + dy1 * dy1); | |||
if (along1 < 1.0f) | |||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; | |||
if (along1 < 1.0f) | |||
distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; | |||
return false; | |||
return false; | |||
} | |||
} | |||
} | |||
intersectionX = x2; | |||
intersectionY = y2; | |||
intersectionX = x2; | |||
intersectionY = y2; | |||
distanceBeyondLine1EndSquared = 0.0f; | |||
return true; | |||
} | |||
distanceBeyondLine1EndSquared = 0.0f; | |||
return true; | |||
} | |||
namespace PathFunctions | |||
{ | |||
// part of stroke drawing stuff | |||
static void addEdgeAndJoint (Path& destPath, | |||
const PathStrokeType::JointStyle style, | |||
const float maxMiterExtensionSquared, const float width, | |||
@@ -348,23 +347,102 @@ namespace PathFunctions | |||
} | |||
} | |||
struct LineSection | |||
struct Arrowhead | |||
{ | |||
LineSection() {} | |||
LineSection (int) {} | |||
float startWidth, startLength; | |||
float endWidth, endLength; | |||
}; | |||
static void addArrowhead (Path& destPath, | |||
const float x1, const float y1, | |||
const float x2, const float y2, | |||
const float tipX, const float tipY, | |||
const float width, | |||
const float arrowheadWidth) | |||
{ | |||
Line<float> line (x1, y1, x2, y2); | |||
destPath.lineTo (line.getPointAlongLine (-(arrowheadWidth / 2.0f - width), 0)); | |||
destPath.lineTo (tipX, tipY); | |||
destPath.lineTo (line.getPointAlongLine (arrowheadWidth - (arrowheadWidth / 2.0f - width), 0)); | |||
destPath.lineTo (x2, y2); | |||
} | |||
struct LineSection | |||
{ | |||
float x1, y1, x2, y2; // original line | |||
float lx1, ly1, lx2, ly2; // the left-hand stroke | |||
float rx1, ry1, rx2, ry2; // the right-hand stroke | |||
}; | |||
static void addSubPath (Path& destPath, const Array <LineSection>& subPath, | |||
const bool isClosed, | |||
const float width, const float maxMiterExtensionSquared, | |||
const PathStrokeType::JointStyle jointStyle, const PathStrokeType::EndCapStyle endStyle) | |||
static void shortenSubPath (Array<LineSection>& subPath, float amountAtStart, float amountAtEnd) | |||
{ | |||
while (amountAtEnd > 0 && subPath.size() > 0) | |||
{ | |||
LineSection& l = subPath.getReference (subPath.size() - 1); | |||
float dx = l.rx2 - l.rx1; | |||
float dy = l.ry2 - l.ry1; | |||
const float len = juce_hypotf (dx, dy); | |||
if (len <= amountAtEnd && subPath.size() > 1) | |||
{ | |||
LineSection& prev = subPath.getReference (subPath.size() - 2); | |||
prev.x2 = l.x2; | |||
prev.y2 = l.y2; | |||
subPath.removeLast(); | |||
amountAtEnd -= len; | |||
} | |||
else | |||
{ | |||
const float prop = jmin (0.9999f, amountAtEnd / len); | |||
dx *= prop; | |||
dy *= prop; | |||
l.rx1 += dx; | |||
l.ry1 += dy; | |||
l.lx2 += dx; | |||
l.ly2 += dy; | |||
break; | |||
} | |||
} | |||
while (amountAtStart > 0 && subPath.size() > 0) | |||
{ | |||
LineSection& l = subPath.getReference (0); | |||
float dx = l.rx2 - l.rx1; | |||
float dy = l.ry2 - l.ry1; | |||
const float len = juce_hypotf (dx, dy); | |||
if (len <= amountAtStart && subPath.size() > 1) | |||
{ | |||
LineSection& next = subPath.getReference (1); | |||
next.x1 = l.x1; | |||
next.y1 = l.y1; | |||
subPath.remove (0); | |||
amountAtStart -= len; | |||
} | |||
else | |||
{ | |||
const float prop = jmin (0.9999f, amountAtStart / len); | |||
dx *= prop; | |||
dy *= prop; | |||
l.rx2 -= dx; | |||
l.ry2 -= dy; | |||
l.lx1 -= dx; | |||
l.ly1 -= dy; | |||
break; | |||
} | |||
} | |||
} | |||
static void addSubPath (Path& destPath, Array<LineSection>& subPath, | |||
const bool isClosed, const float width, const float maxMiterExtensionSquared, | |||
const PathStrokeType::JointStyle jointStyle, const PathStrokeType::EndCapStyle endStyle, | |||
const Arrowhead* const arrowhead) | |||
{ | |||
jassert (subPath.size() > 0); | |||
if (arrowhead != 0) | |||
shortenSubPath (subPath, arrowhead->startLength, arrowhead->endLength); | |||
const LineSection& firstLine = subPath.getReference (0); | |||
float lastX1 = firstLine.lx1; | |||
@@ -380,10 +458,11 @@ namespace PathFunctions | |||
{ | |||
destPath.startNewSubPath (firstLine.rx2, firstLine.ry2); | |||
addLineEnd (destPath, endStyle, | |||
firstLine.rx2, firstLine.ry2, | |||
lastX1, lastY1, | |||
width); | |||
if (arrowhead != 0) | |||
addArrowhead (destPath, firstLine.rx2, firstLine.ry2, lastX1, lastY1, firstLine.x1, firstLine.y1, | |||
width, arrowhead->startWidth); | |||
else | |||
addLineEnd (destPath, endStyle, firstLine.rx2, firstLine.ry2, lastX1, lastY1, width); | |||
} | |||
int i; | |||
@@ -422,10 +501,11 @@ namespace PathFunctions | |||
{ | |||
destPath.lineTo (lastX2, lastY2); | |||
addLineEnd (destPath, endStyle, | |||
lastX2, lastY2, | |||
lastLine.rx1, lastLine.ry1, | |||
width); | |||
if (arrowhead != 0) | |||
addArrowhead (destPath, lastX2, lastY2, lastLine.rx1, lastLine.ry1, lastLine.x2, lastLine.y2, | |||
width, arrowhead->endWidth); | |||
else | |||
addLineEnd (destPath, endStyle, lastX2, lastY2, lastLine.rx1, lastLine.ry1, width); | |||
} | |||
lastX1 = lastLine.rx1; | |||
@@ -465,114 +545,122 @@ namespace PathFunctions | |||
destPath.closeSubPath(); | |||
} | |||
} | |||
void PathStrokeType::createStrokedPath (Path& destPath, | |||
const Path& source, | |||
const AffineTransform& transform, | |||
const float extraAccuracy) const | |||
{ | |||
if (thickness <= 0) | |||
static void createStroke (const float thickness, const PathStrokeType::JointStyle jointStyle, | |||
const PathStrokeType::EndCapStyle endStyle, | |||
Path& destPath, const Path& source, | |||
const AffineTransform& transform, | |||
const float extraAccuracy, const Arrowhead* const arrowhead) | |||
{ | |||
destPath.clear(); | |||
return; | |||
} | |||
if (thickness <= 0) | |||
{ | |||
destPath.clear(); | |||
return; | |||
} | |||
const Path* sourcePath = &source; | |||
Path temp; | |||
const Path* sourcePath = &source; | |||
Path temp; | |||
if (sourcePath == &destPath) | |||
{ | |||
destPath.swapWithPath (temp); | |||
sourcePath = &temp; | |||
} | |||
else | |||
{ | |||
destPath.clear(); | |||
} | |||
if (sourcePath == &destPath) | |||
{ | |||
destPath.swapWithPath (temp); | |||
sourcePath = &temp; | |||
} | |||
else | |||
{ | |||
destPath.clear(); | |||
} | |||
destPath.setUsingNonZeroWinding (true); | |||
destPath.setUsingNonZeroWinding (true); | |||
const float maxMiterExtensionSquared = 9.0f * thickness * thickness; | |||
const float width = 0.5f * thickness; | |||
const float maxMiterExtensionSquared = 9.0f * thickness * thickness; | |||
const float width = 0.5f * thickness; | |||
// Iterate the path, creating a list of the | |||
// left/right-hand lines along either side of it... | |||
PathFlatteningIterator it (*sourcePath, transform, 9.0f / extraAccuracy); | |||
// Iterate the path, creating a list of the | |||
// left/right-hand lines along either side of it... | |||
PathFlatteningIterator it (*sourcePath, transform, 9.0f / extraAccuracy); | |||
using namespace PathFunctions; | |||
Array <LineSection> subPath; | |||
LineSection l; | |||
l.x1 = 0; | |||
l.y1 = 0; | |||
Array <LineSection> subPath; | |||
subPath.ensureStorageAllocated (512); | |||
LineSection l; | |||
l.x1 = 0; | |||
l.y1 = 0; | |||
const float minSegmentLength = 2.0f / (extraAccuracy * extraAccuracy); | |||
const float minSegmentLength = 2.0f / (extraAccuracy * extraAccuracy); | |||
while (it.next()) | |||
{ | |||
if (it.subPathIndex == 0) | |||
while (it.next()) | |||
{ | |||
if (subPath.size() > 0) | |||
if (it.subPathIndex == 0) | |||
{ | |||
addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle); | |||
subPath.clearQuick(); | |||
} | |||
l.x1 = it.x1; | |||
l.y1 = it.y1; | |||
} | |||
if (subPath.size() > 0) | |||
{ | |||
addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); | |||
subPath.clearQuick(); | |||
} | |||
l.x2 = it.x2; | |||
l.y2 = it.y2; | |||
l.x1 = it.x1; | |||
l.y1 = it.y1; | |||
} | |||
float dx = l.x2 - l.x1; | |||
float dy = l.y2 - l.y1; | |||
l.x2 = it.x2; | |||
l.y2 = it.y2; | |||
const float hypotSquared = dx*dx + dy*dy; | |||
float dx = l.x2 - l.x1; | |||
float dy = l.y2 - l.y1; | |||
if (it.closesSubPath || hypotSquared > minSegmentLength || it.isLastInSubpath()) | |||
{ | |||
const float len = std::sqrt (hypotSquared); | |||
const float hypotSquared = dx*dx + dy*dy; | |||
if (len == 0) | |||
{ | |||
l.rx1 = l.rx2 = l.lx1 = l.lx2 = l.x1; | |||
l.ry1 = l.ry2 = l.ly1 = l.ly2 = l.y1; | |||
} | |||
else | |||
if (it.closesSubPath || hypotSquared > minSegmentLength || it.isLastInSubpath()) | |||
{ | |||
const float offset = width / len; | |||
dx *= offset; | |||
dy *= offset; | |||
l.rx2 = l.x1 - dy; | |||
l.ry2 = l.y1 + dx; | |||
l.lx1 = l.x1 + dy; | |||
l.ly1 = l.y1 - dx; | |||
const float len = std::sqrt (hypotSquared); | |||
l.lx2 = l.x2 + dy; | |||
l.ly2 = l.y2 - dx; | |||
l.rx1 = l.x2 - dy; | |||
l.ry1 = l.y2 + dx; | |||
} | |||
if (len == 0) | |||
{ | |||
l.rx1 = l.rx2 = l.lx1 = l.lx2 = l.x1; | |||
l.ry1 = l.ry2 = l.ly1 = l.ly2 = l.y1; | |||
} | |||
else | |||
{ | |||
const float offset = width / len; | |||
dx *= offset; | |||
dy *= offset; | |||
l.rx2 = l.x1 - dy; | |||
l.ry2 = l.y1 + dx; | |||
l.lx1 = l.x1 + dy; | |||
l.ly1 = l.y1 - dx; | |||
l.lx2 = l.x2 + dy; | |||
l.ly2 = l.y2 - dx; | |||
l.rx1 = l.x2 - dy; | |||
l.ry1 = l.y2 + dx; | |||
} | |||
subPath.add (l); | |||
subPath.add (l); | |||
if (it.closesSubPath) | |||
{ | |||
addSubPath (destPath, subPath, true, width, maxMiterExtensionSquared, jointStyle, endStyle); | |||
subPath.clearQuick(); | |||
} | |||
else | |||
{ | |||
l.x1 = it.x2; | |||
l.y1 = it.y2; | |||
if (it.closesSubPath) | |||
{ | |||
addSubPath (destPath, subPath, true, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); | |||
subPath.clearQuick(); | |||
} | |||
else | |||
{ | |||
l.x1 = it.x2; | |||
l.y1 = it.y2; | |||
} | |||
} | |||
} | |||
if (subPath.size() > 0) | |||
addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead); | |||
} | |||
} | |||
if (subPath.size() > 0) | |||
addSubPath (destPath, subPath, false, width, maxMiterExtensionSquared, jointStyle, endStyle); | |||
void PathStrokeType::createStrokedPath (Path& destPath, const Path& sourcePath, | |||
const AffineTransform& transform, const float extraAccuracy) const | |||
{ | |||
PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle, destPath, sourcePath, | |||
transform, extraAccuracy, 0); | |||
} | |||
void PathStrokeType::createDashedStroke (Path& destPath, | |||
@@ -641,5 +729,21 @@ void PathStrokeType::createDashedStroke (Path& destPath, | |||
} | |||
} | |||
void PathStrokeType::createStrokeWithArrowheads (Path& destPath, | |||
const Path& sourcePath, | |||
const float arrowheadStartWidth, const float arrowheadStartLength, | |||
const float arrowheadEndWidth, const float arrowheadEndLength, | |||
const AffineTransform& transform, | |||
const float extraAccuracy) const | |||
{ | |||
PathStrokeHelpers::Arrowhead head; | |||
head.startWidth = arrowheadStartWidth; | |||
head.startLength = arrowheadStartLength; | |||
head.endWidth = arrowheadEndWidth; | |||
head.endLength = arrowheadEndLength; | |||
PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle, | |||
destPath, sourcePath, transform, extraAccuracy, &head); | |||
} | |||
END_JUCE_NAMESPACE |
@@ -137,6 +137,29 @@ public: | |||
const AffineTransform& transform = AffineTransform::identity, | |||
float extraAccuracy = 1.0f) const; | |||
//============================================================================== | |||
/** Applies this stroke type to a path and returns the resultant stroke as another Path. | |||
@param destPath the resultant stroked outline shape will be copied into this path. | |||
Note that it's ok for the source and destination Paths to be | |||
the same object, so you can easily turn a path into a stroked version | |||
of itself. | |||
@param sourcePath the path to use as the source | |||
@param transform an optional transform to apply to the points from the source path | |||
as they are being used | |||
@param extraAccuracy if this is greater than 1.0, it will subdivide the path to | |||
a higher resolution, which improved the quality if you'll later want | |||
to enlarge the stroked path | |||
@see createDashedStroke | |||
*/ | |||
void createStrokeWithArrowheads (Path& destPath, | |||
const Path& sourcePath, | |||
float arrowheadStartWidth, float arrowheadStartLength, | |||
float arrowheadEndWidth, float arrowheadEndLength, | |||
const AffineTransform& transform = AffineTransform::identity, | |||
float extraAccuracy = 1.0f) const; | |||
//============================================================================== | |||
/** Returns the stroke thickness. */ | |||
float getStrokeThickness() const throw() { return thickness; } | |||
@@ -59,6 +59,9 @@ public: | |||
/** Copies this point from another one. */ | |||
Point& operator= (const Point& other) throw() { x = other.x; y = other.y; return *this; } | |||
inline bool operator== (const Point& other) const throw() { return x == other.x && y == other.y; } | |||
inline bool operator!= (const Point& other) const throw() { return x != other.x || y != other.y; } | |||
/** Returns true if the point is (0, 0). */ | |||
bool isOrigin() const throw() { return x == ValueType() && y == ValueType(); } | |||
@@ -86,9 +89,6 @@ public: | |||
/** Adds a pair of co-ordinates to this value. */ | |||
void addXY (const ValueType xToAdd, const ValueType yToAdd) throw() { x += xToAdd; y += yToAdd; } | |||
inline bool operator== (const Point& other) const throw() { return x == other.x && y == other.y; } | |||
inline bool operator!= (const Point& other) const throw() { return x != other.x || y != other.y; } | |||
/** Adds two points together. */ | |||
const Point operator+ (const Point& other) const throw() { return Point (x + other.x, y + other.y); } | |||
@@ -129,6 +129,20 @@ public: | |||
*/ | |||
ValueType getAngleToPoint (const Point& other) const throw() { return (ValueType) std::atan2 (other.x - x, other.y - y); } | |||
/** Taking this point to be the centre of a circle, this returns a point on its circumference. | |||
@param radius the radius of the circle. | |||
@param angle the angle of the point, in radians clockwise from the 12 o'clock position. | |||
*/ | |||
const Point getPointOnCircumference (const float radius, const float angle) const throw() { return Point<float> (x + radius * std::sin (angle), | |||
y - radius * std::cos (angle)); } | |||
/** Taking this point to be the centre of an ellipse, this returns a point on its circumference. | |||
@param radiusX the horizontal radius of the circle. | |||
@param radiusY the vertical radius of the circle. | |||
@param angle the angle of the point, in radians clockwise from the 12 o'clock position. | |||
*/ | |||
const Point getPointOnCircumference (const float radiusX, const float radiusY, const float angle) const throw() { return Point<float> (x + radiusX * std::sin (angle), | |||
y - radiusY * std::cos (angle)); } | |||
/** Uses a transform to change the point's co-ordinates. | |||
This will only compile if ValueType = float! | |||
@see AffineTransform::transformPoint | |||
@@ -142,6 +142,18 @@ public: | |||
/** Returns a rectangle with the same size as this one, but a new position. */ | |||
const Rectangle withPosition (const Point<ValueType>& newPos) const throw() { return Rectangle (newPos.getX(), newPos.getY(), w, h); } | |||
/** Returns the rectangle's top-left position as a Point. */ | |||
const Point<ValueType> getTopLeft() const throw() { return getPosition(); } | |||
/** Returns the rectangle's top-right position as a Point. */ | |||
const Point<ValueType> getTopRight() const throw() { return Point<float> (x + w, y); } | |||
/** Returns the rectangle's bottom-left position as a Point. */ | |||
const Point<ValueType> getBottomLeft() const throw() { return Point<float> (x, y + h); } | |||
/** Returns the rectangle's bottom-right position as a Point. */ | |||
const Point<ValueType> getBottomRight() const throw() { return Point<float> (x + w, y + h); } | |||
/** Changes the rectangle's size, leaving the position of its top-left corner unchanged. */ | |||
void setSize (const ValueType newWidth, const ValueType newHeight) throw() { w = newWidth; h = newHeight; } | |||
@@ -538,6 +538,12 @@ RelativeRectangle::RelativeRectangle() | |||
{ | |||
} | |||
RelativeRectangle::RelativeRectangle (const RelativeCoordinate& left_, const RelativeCoordinate& right_, | |||
const RelativeCoordinate& top_, const RelativeCoordinate& bottom_) | |||
: left (left_), right (right_), top (top_), bottom (bottom_) | |||
{ | |||
} | |||
RelativeRectangle::RelativeRectangle (const Rectangle<float>& rect, const String& componentName) | |||
: left (rect.getX(), true), | |||
right (rect.getWidth(), componentName + "." + RelativeCoordinate::Strings::left), | |||
@@ -733,8 +739,7 @@ const ValueTree RelativePointPath::StartSubPath::createTree() const | |||
void RelativePointPath::StartSubPath::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
{ | |||
const Point<float> p (startPos.resolve (coordFinder)); | |||
path.startNewSubPath (p.getX(), p.getY()); | |||
path.startNewSubPath (startPos.resolve (coordFinder)); | |||
} | |||
RelativePoint* RelativePointPath::StartSubPath::getControlPoints (int& numPoints) | |||
@@ -780,8 +785,7 @@ const ValueTree RelativePointPath::LineTo::createTree() const | |||
void RelativePointPath::LineTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
{ | |||
const Point<float> p (endPoint.resolve (coordFinder)); | |||
path.lineTo (p.getX(), p.getY()); | |||
path.lineTo (endPoint.resolve (coordFinder)); | |||
} | |||
RelativePoint* RelativePointPath::LineTo::getControlPoints (int& numPoints) | |||
@@ -808,9 +812,8 @@ const ValueTree RelativePointPath::QuadraticTo::createTree() const | |||
void RelativePointPath::QuadraticTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
{ | |||
const Point<float> p1 (controlPoints[0].resolve (coordFinder)); | |||
const Point<float> p2 (controlPoints[1].resolve (coordFinder)); | |||
path.quadraticTo (p1.getX(), p1.getY(), p2.getX(), p2.getY()); | |||
path.quadraticTo (controlPoints[0].resolve (coordFinder), | |||
controlPoints[1].resolve (coordFinder)); | |||
} | |||
RelativePoint* RelativePointPath::QuadraticTo::getControlPoints (int& numPoints) | |||
@@ -839,10 +842,9 @@ const ValueTree RelativePointPath::CubicTo::createTree() const | |||
void RelativePointPath::CubicTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const | |||
{ | |||
const Point<float> p1 (controlPoints[0].resolve (coordFinder)); | |||
const Point<float> p2 (controlPoints[1].resolve (coordFinder)); | |||
const Point<float> p3 (controlPoints[2].resolve (coordFinder)); | |||
path.cubicTo (p1.getX(), p1.getY(), p2.getX(), p2.getY(), p3.getX(), p3.getY()); | |||
path.cubicTo (controlPoints[0].resolve (coordFinder), | |||
controlPoints[1].resolve (coordFinder), | |||
controlPoints[2].resolve (coordFinder)); | |||
} | |||
RelativePoint* RelativePointPath::CubicTo::getControlPoints (int& numPoints) | |||
@@ -896,10 +898,10 @@ void RelativeParallelogram::getPath (Path& path, RelativeCoordinate::NamedCoordi | |||
Point<float> points[4]; | |||
resolveFourCorners (points, coordFinder); | |||
path.startNewSubPath (points[0].getX(), points[0].getY()); | |||
path.lineTo (points[1].getX(), points[1].getY()); | |||
path.lineTo (points[3].getX(), points[3].getY()); | |||
path.lineTo (points[2].getX(), points[2].getY()); | |||
path.startNewSubPath (points[0]); | |||
path.lineTo (points[1]); | |||
path.lineTo (points[3]); | |||
path.lineTo (points[2]); | |||
path.closeSubPath(); | |||
} | |||
@@ -360,6 +360,10 @@ public: | |||
/** Creates an absolute rectangle, relative to the origin. */ | |||
explicit RelativeRectangle (const Rectangle<float>& rect, const String& componentName); | |||
/** Creates a rectangle from four coordinates. */ | |||
RelativeRectangle (const RelativeCoordinate& left, const RelativeCoordinate& right, | |||
const RelativeCoordinate& top, const RelativeCoordinate& bottom); | |||
/** Creates a rectangle from a stringified representation. | |||
The string must contain a sequence of 4 coordinates, separated by commas, in the order | |||
left, top, right, bottom. The syntax for the coordinate strings is explained in the | |||
@@ -245,11 +245,19 @@ public: | |||
CGImageRef image = CoreGraphicsImage::createImage (singleChannelImage, true, greyColourSpace); | |||
if (srcClip != sourceImage.getBounds()) | |||
{ | |||
CGImageRef fullImage = image; | |||
image = CGImageCreateWithImageInRect (fullImage, CGRectMake (srcClip.getX(), srcClip.getY(), | |||
srcClip.getWidth(), srcClip.getHeight())); | |||
CGImageRelease (fullImage); | |||
} | |||
flip(); | |||
AffineTransform t (AffineTransform::scale (1.0f, -1.0f).translated (0, sourceImage.getHeight()).followedBy (transform)); | |||
AffineTransform t (AffineTransform::scale (1.0f, -1.0f).translated (0, srcClip.getHeight()).followedBy (transform)); | |||
applyTransform (t); | |||
CGRect r = CGRectMake (srcClip.getX(), srcClip.getY(), srcClip.getWidth(), srcClip.getHeight()); | |||
CGRect r = CGRectMake (0, 0, srcClip.getWidth(), srcClip.getHeight()); | |||
CGContextClipToMask (context, r, image); | |||
applyTransform (t.inverted()); | |||