diff --git a/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableDocument.cpp b/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableDocument.cpp index dedc7e47ee..8cadd2cbfb 100644 --- a/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableDocument.cpp +++ b/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableDocument.cpp @@ -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 diff --git a/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableTypeHandler.cpp b/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableTypeHandler.cpp index c666f37601..1f990442d0 100644 --- a/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableTypeHandler.cpp +++ b/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableTypeHandler.cpp @@ -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 resolved (content.resolve (&item)); - topRight.moveToAbsolute (topLeft.resolve (&item) + Point (1.0f, 0.0f), &item); - bottomLeft.moveToAbsolute (topLeft.resolve (&item) + Point (0.0f, 1.0f), &item); + bounds.topRight.moveToAbsolute (bounds.topLeft.resolve (&item) + Point (resolved.getWidth(), 0), &item); + bounds.bottomLeft.moveToAbsolute (bounds.topLeft.resolve (&item) + Point (0, resolved.getHeight()), &item); - wrapper.setTargetPositionForX1Y0 (topRight, item.getUndoManager()); - wrapper.setTargetPositionForX0Y1 (bottomLeft, item.getUndoManager()); + wrapper.setBoundingBox (bounds, item.getUndoManager()); } private: diff --git a/extras/Jucer (experimental)/Source/ui/Drawable Editor/jucer_DrawableEditorCanvas.h b/extras/Jucer (experimental)/Source/ui/Drawable Editor/jucer_DrawableEditorCanvas.h index 19139efdfe..909d933ee5 100644 --- a/extras/Jucer (experimental)/Source/ui/Drawable Editor/jucer_DrawableEditorCanvas.h +++ b/extras/Jucer (experimental)/Source/ui/Drawable Editor/jucer_DrawableEditorCanvas.h @@ -68,12 +68,15 @@ public: { Drawable* newDrawable = Drawable::createFromValueTree (doc.getRootDrawableNode().getState(), &doc); drawable = dynamic_cast (newDrawable); + drawable->resetBoundingBoxToContentArea(); jassert (drawable != 0); getComponentHolder()->repaint(); } else { + doc.getRootDrawableNode().resetBoundingBoxToContentArea (0); const Rectangle damage (drawable->refreshFromValueTree (doc.getRootDrawableNode().getState(), &doc)); + getComponentHolder()->repaint (objectSpaceToScreenSpace (damage.getSmallestIntegerContainer())); } diff --git a/extras/Jucer (experimental)/Source/ui/Editor Base/jucer_EditorCanvas.cpp b/extras/Jucer (experimental)/Source/ui/Editor Base/jucer_EditorCanvas.cpp index f04c9aa8e3..15424acb55 100644 --- a/extras/Jucer (experimental)/Source/ui/Editor Base/jucer_EditorCanvas.cpp +++ b/extras/Jucer (experimental)/Source/ui/Editor Base/jucer_EditorCanvas.cpp @@ -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 (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 (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); } diff --git a/extras/Jucer (experimental)/Source/utility/jucer_FillTypePropertyComponent.h b/extras/Jucer (experimental)/Source/utility/jucer_FillTypePropertyComponent.h index a1062b692b..7be5da1027 100644 --- a/extras/Jucer (experimental)/Source/utility/jucer_FillTypePropertyComponent.h +++ b/extras/Jucer (experimental)/Source/utility/jucer_FillTypePropertyComponent.h @@ -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 (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) diff --git a/extras/juce demo/Source/demos/FontsAndTextDemo.cpp b/extras/juce demo/Source/demos/FontsAndTextDemo.cpp index 3fc1fe32a3..dee9033d56 100644 --- a/extras/juce demo/Source/demos/FontsAndTextDemo.cpp +++ b/extras/juce demo/Source/demos/FontsAndTextDemo.cpp @@ -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() diff --git a/extras/juce demo/Source/demos/RenderingTestComponent.cpp b/extras/juce demo/Source/demos/RenderingTestComponent.cpp index 10dd10c861..750c1a4b81 100644 --- a/extras/juce demo/Source/demos/RenderingTestComponent.cpp +++ b/extras/juce demo/Source/demos/RenderingTestComponent.cpp @@ -178,7 +178,7 @@ private: float size = (float) jmin (getWidth(), getHeight()); Path p; - p.addStar (bouncingPointX[1], bouncingPointY[1], 7, + p.addStar (Point (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 (100.0f, 0.0f), 7, 30.0f, 70.0f, 0.1f); + p.addStar (Point (-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 svgFileStream (icons.createStreamForEntry (Random::getSystemRandom().nextInt (icons.getNumEntries()))); if (svgFileStream != 0) { svgDrawable = dynamic_cast (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 bounds = svgDrawable->getBounds(); - const float scaleFactor = 200.0f / jmax (bounds.getWidth(), bounds.getHeight()); - - Point topLeft (-bounds.getCentreX() * scaleFactor, - -bounds.getCentreY() * scaleFactor); - - svgDrawable->setTransform (topLeft, - topLeft + Point (scaleFactor, 0), - topLeft + Point (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 (-100, -100), + Point (100, -100), + Point (-100, 100))); } } } diff --git a/extras/juce demo/Source/demos/WidgetsDemo.cpp b/extras/juce demo/Source/demos/WidgetsDemo.cpp index 80f6252de2..6cc66b1f55 100644 --- a/extras/juce demo/Source/demos/WidgetsDemo.cpp +++ b/extras/juce demo/Source/demos/WidgetsDemo.cpp @@ -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(), 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(), 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(), 7, 30.0f, 50.0f, 0.0f); over.setPath (p); over.setFill (Colours::pink); over.setStrokeFill (Colours::black); diff --git a/extras/the jucer/src/model/paintelements/jucer_PaintElementImage.h b/extras/the jucer/src/model/paintelements/jucer_PaintElementImage.h index df129dd126..ce47587c07 100644 --- a/extras/the jucer/src/model/paintelements/jucer_PaintElementImage.h +++ b/extras/the jucer/src/model/paintelements/jucer_PaintElementImage.h @@ -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 { diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index 0fdc633497..d875e96b48 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -20139,12 +20139,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; @@ -20153,10 +20154,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()) @@ -20207,9 +20205,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) { @@ -20223,13 +20224,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; @@ -53717,8 +53711,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 (x1, y1, x2, y2), 1.5f, hw, hl); + p.addArrow (Line (x3, y3, x4, y4), 1.5f, hw, hl); g.fillPath (p); } } @@ -58159,7 +58153,7 @@ FileSearchPathListComponent::FileSearchPathListComponent() { Path arrowPath; - arrowPath.addArrow (50.0f, 100.0f, 50.0f, 0.0f, 40.0f, 100.0f, 50.0f); + arrowPath.addArrow (Line (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); @@ -58172,7 +58166,7 @@ FileSearchPathListComponent::FileSearchPathListComponent() { Path arrowPath; - arrowPath.addArrow (50.0f, 0.0f, 50.0f, 100.0f, 40.0f, 100.0f, 50.0f); + arrowPath.addArrow (Line (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); @@ -65104,7 +65098,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 (0.0f, 0.0f, 0.0f, -radius), rw * 0.2f); g.fillPath (p, AffineTransform::rotation (angle).translated (centreX, centreY)); } @@ -65417,21 +65411,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 (0.0f, 0.0f, 1.0f, 1.0f), crossThickness * 1.4f); + shape.addLineSegment (Line (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 (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 (0.5f, 0.0f, 0.5f, 1.0f), crossThickness); + shape.addLineSegment (Line (0.0f, 0.5f, 1.0f, 0.5f), crossThickness); Path fullscreenShape; fullscreenShape.startNewSubPath (45.0f, 100.0f); @@ -66120,7 +66114,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 (50.0f, 100.0f, 50.0f, 0.0f), 40.0f, 100.0f, 50.0f); DrawablePath arrowImage; arrowImage.setFill (Colours::black.withAlpha (0.4f)); @@ -67073,8 +67067,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 (0.0f, 0.0f, 1.0f, 1.0f), 0.35f); + shape.addLineSegment (Line (1.0f, 0.0f, 0.0f, 1.0f), 0.35f); ShapeButton* const b = new ShapeButton ("close", Colour (0x7fff3333), @@ -67086,7 +67080,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 (0.0f, 0.5f, 1.0f, 0.5f), 0.25f); DrawableButton* b = new DrawableButton ("minimise", DrawableButton::ImageFitted); DrawablePath dp; @@ -67097,8 +67091,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 (0.5f, 0.0f, 0.5f, 1.0f), 0.25f); + shape.addLineSegment (Line (0.0f, 0.5f, 1.0f, 0.5f), 0.25f); DrawableButton* b = new DrawableButton ("maximise", DrawableButton::ImageFitted); DrawablePath dp; @@ -76773,6 +76767,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() @@ -80590,12 +80586,10 @@ void Graphics::drawRoundedRectangle (const Rectangle& 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& 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); } @@ -80661,9 +80655,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 (startX, startY, endX, endY),lineThickness); } void Graphics::drawLine (const Line& line) const @@ -80673,7 +80665,9 @@ void Graphics::drawLine (const Line& line) const void Graphics::drawLine (const Line& 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, @@ -81323,7 +81317,7 @@ void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, co void LowLevelGraphicsPostScriptRenderer::drawLine (const Line & line) { Path p; - p.addLineSegment (line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY(), 1.0f); + p.addLineSegment (line, 1.0f); fillPath (p, AffineTransform::identity); } @@ -83439,7 +83433,7 @@ void LowLevelGraphicsSoftwareRenderer::drawImage (const Image& sourceImage, cons void LowLevelGraphicsSoftwareRenderer::drawLine (const Line & line) { Path p; - p.addLineSegment (line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY(), 1.0f); + p.addLineSegment (line, 1.0f); fillPath (p, AffineTransform::identity); } @@ -83977,18 +83971,19 @@ END_JUCE_NAMESPACE BEGIN_JUCE_NAMESPACE DrawableComposite::DrawableComposite() + : bounds (Point(), Point (100.0f, 0.0f), Point (0.0f, 100.0f)) { - controlPoints[1] = RelativePoint (Point (1.0f, 0.0f)); - controlPoints[2] = RelativePoint (Point (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); @@ -84035,13 +84030,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; } DrawableComposite::Marker::Marker (const DrawableComposite::Marker& other) @@ -84059,6 +84050,37 @@ bool DrawableComposite::Marker::operator!= (const DrawableComposite::Marker& oth return name != other.name || position != other.position; } +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(); @@ -84094,18 +84116,22 @@ 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 resolved[3]; - for (int i = 0; i < 3; ++i) - resolved[i] = controlPoints[i].resolve (parent); + bounds.resolveThreePoints (resolved, parent); - return AffineTransform::fromTargetPoints (resolved[0].getX(), resolved[0].getY(), - resolved[1].getX(), resolved[1].getY(), - resolved[2].getX(), resolved[2].getY()); + const Rectangle content (getContentArea().resolve (parent)); + + 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 @@ -84361,37 +84387,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 (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 (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 @@ -84451,57 +84483,47 @@ 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 DrawableComposite::refreshFromValueTree (const ValueTree& tree, ImageProvider* imageProvider) { Rectangle 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 (damageRect.isEmpty()) - damageRect = getUntransformedBounds(); - - markersX.remove (i); - } - - for (i = markersY.size(); --i >= numMarkersY;) + if (markersX.size() > numMarkersX || markersY.size() > 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) @@ -84519,7 +84541,7 @@ const Rectangle 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) @@ -84536,7 +84558,7 @@ const Rectangle 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()); @@ -84545,9 +84567,9 @@ const Rectangle 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) @@ -84584,9 +84606,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) @@ -85535,8 +85555,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")) { @@ -85599,6 +85619,13 @@ public: newState.parseSubElements (xml, drawable); + const Rectangle 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; } @@ -85615,30 +85642,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); } @@ -86011,8 +86026,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); @@ -88089,8 +88104,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 (pg->x, pg->y + lineThickness * 2.0f, + nextX, pg->y + lineThickness * 2.0f), lineThickness); g.fillPath (p, transform); @@ -89172,27 +89187,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)) @@ -89369,6 +89363,11 @@ void Path::startNewSubPath (const float x, const float y) data.elements [numElements++] = y; } +void Path::startNewSubPath (const Point& start) +{ + startNewSubPath (start.getX(), start.getY()); +} + void Path::lineTo (const float x, const float y) { CHECK_COORDS_ARE_VALID (x, y); @@ -89388,6 +89387,11 @@ void Path::lineTo (const float x, const float y) pathYMax = jmax (pathYMax, y); } +void Path::lineTo (const Point& end) +{ + lineTo (end.getX(), end.getY()); +} + void Path::quadraticTo (const float x1, const float y1, const float x2, const float y2) { @@ -89411,6 +89415,13 @@ void Path::quadraticTo (const float x1, const float y1, pathYMax = jmax (pathYMax, y1, y2); } +void Path::quadraticTo (const Point& controlPoint, + const Point& 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) @@ -89438,6 +89449,15 @@ void Path::cubicTo (const float x1, const float y1, pathYMax = jmax (pathYMax, y1, y2, y3); } +void Path::cubicTo (const Point& controlPoint1, + const Point& controlPoint2, + const Point& endPoint) +{ + cubicTo (controlPoint1.getX(), controlPoint1.getY(), + controlPoint2.getX(), controlPoint2.getY(), + endPoint.getX(), endPoint.getY()); +} + void Path::closeSubPath() { if (numElements > 0 @@ -89618,19 +89638,12 @@ void Path::addCentredArc (const float centreX, const float centreY, { if (radiusX > 0.0f && radiusY > 0.0f) { + const Point 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) { @@ -89639,14 +89652,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; } } @@ -89657,25 +89663,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)); } } @@ -89685,14 +89678,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 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) @@ -89701,112 +89691,87 @@ void Path::addPieSegment (const float x, const float y, if (innerCircleProportionalSize > 0) { - hw *= innerCircleProportionalSize; - hh *= innerCircleProportionalSize; - - startNewSubPath (centreX + hw * std::sin (toRadians), - centreY - hh * std::cos (toRadians)); + radiusX *= innerCircleProportionalSize; + radiusY *= innerCircleProportionalSize; - 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); } } closeSubPath(); } -void Path::addLineSegment (const float startX, const float startY, - const float endX, const float endY, - float lineThickness) +void Path::addLineSegment (const Line& line, float lineThickness) { + const Line 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& line, float lineThickness, + float arrowheadWidth, float arrowheadLength) { + const Line 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& 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 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& centre, const int numberOfPoints, + const float innerRadius, const float outerRadius, const float startAngle) { jassert (numberOfPoints > 1); // this would be silly. @@ -89816,20 +89781,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 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(); @@ -91039,126 +90999,125 @@ bool PathStrokeType::operator!= (const PathStrokeType& other) const throw() return ! operator== (other); } -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 = 0.5f * (x2 + x3); + intersectionY = 0.5f * (y2 + y3); - intersectionX = x1 + along1 * dx1; - intersectionY = y1 + along1 * dy1; - - 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, @@ -91313,23 +91272,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 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 & subPath, - const bool isClosed, - const float width, const float maxMiterExtensionSquared, - const PathStrokeType::JointStyle jointStyle, const PathStrokeType::EndCapStyle endStyle) + static void shortenSubPath (Array& 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& 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; @@ -91345,10 +91383,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; @@ -91387,10 +91426,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; @@ -91430,114 +91470,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 subPath; - LineSection l; - l.x1 = 0; - l.y1 = 0; + Array 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; + const float len = std::sqrt (hypotSquared); - 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; - } + 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, @@ -91606,6 +91654,23 @@ 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 /*** End of inlined file: juce_PathStrokeType.cpp ***/ @@ -92960,6 +93025,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& rect, const String& componentName) : left (rect.getX(), true), right (rect.getWidth(), componentName + "." + RelativeCoordinate::Strings::left), @@ -93151,8 +93222,7 @@ const ValueTree RelativePointPath::StartSubPath::createTree() const void RelativePointPath::StartSubPath::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const { - const Point p (startPos.resolve (coordFinder)); - path.startNewSubPath (p.getX(), p.getY()); + path.startNewSubPath (startPos.resolve (coordFinder)); } RelativePoint* RelativePointPath::StartSubPath::getControlPoints (int& numPoints) @@ -93196,8 +93266,7 @@ const ValueTree RelativePointPath::LineTo::createTree() const void RelativePointPath::LineTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const { - const Point p (endPoint.resolve (coordFinder)); - path.lineTo (p.getX(), p.getY()); + path.lineTo (endPoint.resolve (coordFinder)); } RelativePoint* RelativePointPath::LineTo::getControlPoints (int& numPoints) @@ -93223,9 +93292,8 @@ const ValueTree RelativePointPath::QuadraticTo::createTree() const void RelativePointPath::QuadraticTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const { - const Point p1 (controlPoints[0].resolve (coordFinder)); - const Point 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) @@ -93253,10 +93321,9 @@ const ValueTree RelativePointPath::CubicTo::createTree() const void RelativePointPath::CubicTo::addToPath (Path& path, RelativeCoordinate::NamedCoordinateFinder* coordFinder) const { - const Point p1 (controlPoints[0].resolve (coordFinder)); - const Point p2 (controlPoints[1].resolve (coordFinder)); - const Point 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) @@ -93308,10 +93375,10 @@ void RelativeParallelogram::getPath (Path& path, RelativeCoordinate::NamedCoordi Point 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(); } @@ -264114,11 +264181,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()); @@ -268753,11 +268828,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()); diff --git a/juce_amalgamated.h b/juce_amalgamated.h index afe0715ac5..6d7e324a60 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -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 (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 (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& 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& newPos) const throw() { return Rectangle (newPos.getX(), newPos.getY(), w, h); } + /** Returns the rectangle's top-left position as a Point. */ + const Point getTopLeft() const throw() { return getPosition(); } + + /** Returns the rectangle's top-right position as a Point. */ + const Point getTopRight() const throw() { return Point (x + w, y); } + + /** Returns the rectangle's bottom-left position as a Point. */ + const Point getBottomLeft() const throw() { return Point (x, y + h); } + + /** Returns the rectangle's bottom-right position as a Point. */ + const Point getBottomRight() const throw() { return Point (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& 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& 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& controlPoint, + const Point& 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& controlPoint1, + const Point& controlPoint2, + const Point& endPoint); + /** Returns the last point that was added to the path by one of the drawing methods. */ const Point 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& 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& 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& 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& 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& 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& 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 drawables; - RelativePoint controlPoints[3]; + RelativeParallelogram bounds; OwnedArray markersX, markersY; const Rectangle getUntransformedBounds() const; diff --git a/src/audio/audio_file_formats/juce_AudioFormat.cpp b/src/audio/audio_file_formats/juce_AudioFormat.cpp index 88a818cb87..8c9324324e 100644 --- a/src/audio/audio_file_formats/juce_AudioFormat.cpp +++ b/src/audio/audio_file_formats/juce_AudioFormat.cpp @@ -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; diff --git a/src/core/juce_StandardHeader.h b/src/core/juce_StandardHeader.h index cf3540030b..413a8e0e3e 100644 --- a/src/core/juce_StandardHeader.h +++ b/src/core/juce_StandardHeader.h @@ -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. diff --git a/src/gui/components/controls/juce_Toolbar.cpp b/src/gui/components/controls/juce_Toolbar.cpp index 4f96339b7d..0bc02d7cd3 100644 --- a/src/gui/components/controls/juce_Toolbar.cpp +++ b/src/gui/components/controls/juce_Toolbar.cpp @@ -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 (x1, y1, x2, y2), 1.5f, hw, hl); + p.addArrow (Line (x3, y3, x4, y4), 1.5f, hw, hl); g.fillPath (p); } } diff --git a/src/gui/components/filebrowser/juce_FileSearchPathListComponent.cpp b/src/gui/components/filebrowser/juce_FileSearchPathListComponent.cpp index f1ba09f97d..20697f652b 100644 --- a/src/gui/components/filebrowser/juce_FileSearchPathListComponent.cpp +++ b/src/gui/components/filebrowser/juce_FileSearchPathListComponent.cpp @@ -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 (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 (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); diff --git a/src/gui/components/lookandfeel/juce_LookAndFeel.cpp b/src/gui/components/lookandfeel/juce_LookAndFeel.cpp index 2ef01ca9f2..e423b77e9d 100644 --- a/src/gui/components/lookandfeel/juce_LookAndFeel.cpp +++ b/src/gui/components/lookandfeel/juce_LookAndFeel.cpp @@ -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 (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 (0.0f, 0.0f, 1.0f, 1.0f), crossThickness * 1.4f); + shape.addLineSegment (Line (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 (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 (0.5f, 0.0f, 0.5f, 1.0f), crossThickness); + shape.addLineSegment (Line (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 (50.0f, 100.0f, 50.0f, 0.0f), 40.0f, 100.0f, 50.0f); DrawablePath arrowImage; arrowImage.setFill (Colours::black.withAlpha (0.4f)); diff --git a/src/gui/components/lookandfeel/juce_OldSchoolLookAndFeel.cpp b/src/gui/components/lookandfeel/juce_OldSchoolLookAndFeel.cpp index 65d4d2baa5..7b2847bb17 100644 --- a/src/gui/components/lookandfeel/juce_OldSchoolLookAndFeel.cpp +++ b/src/gui/components/lookandfeel/juce_OldSchoolLookAndFeel.cpp @@ -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 (0.0f, 0.0f, 1.0f, 1.0f), 0.35f); + shape.addLineSegment (Line (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 (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 (0.5f, 0.0f, 0.5f, 1.0f), 0.25f); + shape.addLineSegment (Line (0.0f, 0.5f, 1.0f, 0.5f), 0.25f); DrawableButton* b = new DrawableButton ("maximise", DrawableButton::ImageFitted); DrawablePath dp; diff --git a/src/gui/components/windows/juce_DialogWindow.cpp b/src/gui/components/windows/juce_DialogWindow.cpp index 662a9128fe..98e92d5944 100644 --- a/src/gui/components/windows/juce_DialogWindow.cpp +++ b/src/gui/components/windows/juce_DialogWindow.cpp @@ -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() diff --git a/src/gui/graphics/contexts/juce_Graphics.cpp b/src/gui/graphics/contexts/juce_Graphics.cpp index cea38adb48..bc4fed65c9 100644 --- a/src/gui/graphics/contexts/juce_Graphics.cpp +++ b/src/gui/graphics/contexts/juce_Graphics.cpp @@ -473,12 +473,10 @@ void Graphics::drawRoundedRectangle (const Rectangle& 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& 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 (startX, startY, endX, endY),lineThickness); } void Graphics::drawLine (const Line& line) const @@ -557,7 +553,9 @@ void Graphics::drawLine (const Line& line) const void Graphics::drawLine (const Line& 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, diff --git a/src/gui/graphics/contexts/juce_Graphics.h b/src/gui/graphics/contexts/juce_Graphics.h index c7bbe4c550..6bac14d27a 100644 --- a/src/gui/graphics/contexts/juce_Graphics.h +++ b/src/gui/graphics/contexts/juce_Graphics.h @@ -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& line, float lineThickness, float arrowheadWidth, float arrowheadLength) const; diff --git a/src/gui/graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp b/src/gui/graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp index 66b0a9e051..1b9de841b3 100644 --- a/src/gui/graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp +++ b/src/gui/graphics/contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp @@ -502,7 +502,7 @@ void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, co void LowLevelGraphicsPostScriptRenderer::drawLine (const Line & line) { Path p; - p.addLineSegment (line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY(), 1.0f); + p.addLineSegment (line, 1.0f); fillPath (p, AffineTransform::identity); } diff --git a/src/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp b/src/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp index cebf12af04..76b8530c39 100644 --- a/src/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp +++ b/src/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp @@ -2140,7 +2140,7 @@ void LowLevelGraphicsSoftwareRenderer::drawImage (const Image& sourceImage, cons void LowLevelGraphicsSoftwareRenderer::drawLine (const Line & line) { Path p; - p.addLineSegment (line.getStartX(), line.getStartY(), line.getEndX(), line.getEndY(), 1.0f); + p.addLineSegment (line, 1.0f); fillPath (p, AffineTransform::identity); } diff --git a/src/gui/graphics/drawables/juce_DrawableComposite.cpp b/src/gui/graphics/drawables/juce_DrawableComposite.cpp index f726882bed..17c32d2c45 100644 --- a/src/gui/graphics/drawables/juce_DrawableComposite.cpp +++ b/src/gui/graphics/drawables/juce_DrawableComposite.cpp @@ -36,18 +36,19 @@ BEGIN_JUCE_NAMESPACE //============================================================================== DrawableComposite::DrawableComposite() + : bounds (Point(), Point (100.0f, 0.0f), Point (0.0f, 100.0f)) { - controlPoints[1] = RelativePoint (Point (1.0f, 0.0f)); - controlPoints[2] = RelativePoint (Point (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 resolved[3]; - for (int i = 0; i < 3; ++i) - resolved[i] = controlPoints[i].resolve (parent); + bounds.resolveThreePoints (resolved, parent); + + const Rectangle 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 (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 (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 DrawableComposite::refreshFromValueTree (const ValueTree& tree, ImageProvider* imageProvider) { Rectangle 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 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 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 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) diff --git a/src/gui/graphics/drawables/juce_DrawableComposite.h b/src/gui/graphics/drawables/juce_DrawableComposite.h index ffdcc0a241..cf6203eeb9 100644 --- a/src/gui/graphics/drawables/juce_DrawableComposite.h +++ b/src/gui/graphics/drawables/juce_DrawableComposite.h @@ -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 drawables; - RelativePoint controlPoints[3]; + RelativeParallelogram bounds; OwnedArray markersX, markersY; const Rectangle getUntransformedBounds() const; diff --git a/src/gui/graphics/drawables/juce_SVGParser.cpp b/src/gui/graphics/drawables/juce_SVGParser.cpp index 9583b5434b..98eb18c2e9 100644 --- a/src/gui/graphics/drawables/juce_SVGParser.cpp +++ b/src/gui/graphics/drawables/juce_SVGParser.cpp @@ -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 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); diff --git a/src/gui/graphics/fonts/juce_GlyphArrangement.cpp b/src/gui/graphics/fonts/juce_GlyphArrangement.cpp index 7724b9c6e1..490287610f 100644 --- a/src/gui/graphics/fonts/juce_GlyphArrangement.cpp +++ b/src/gui/graphics/fonts/juce_GlyphArrangement.cpp @@ -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 (pg->x, pg->y + lineThickness * 2.0f, + nextX, pg->y + lineThickness * 2.0f), lineThickness); g.fillPath (p, transform); diff --git a/src/gui/graphics/geometry/juce_Line.h b/src/gui/graphics/geometry/juce_Line.h index d5888846ba..24fe0cbaff 100644 --- a/src/gui/graphics/geometry/juce_Line.h +++ b/src/gui/graphics/geometry/juce_Line.h @@ -115,6 +115,9 @@ public: /** Changes this line's end point */ void setEnd (const Point& 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() { diff --git a/src/gui/graphics/geometry/juce_Path.cpp b/src/gui/graphics/geometry/juce_Path.cpp index ddd452e5f0..37804cf925 100644 --- a/src/gui/graphics/geometry/juce_Path.cpp +++ b/src/gui/graphics/geometry/juce_Path.cpp @@ -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& 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& 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& controlPoint, + const Point& 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& controlPoint1, + const Point& controlPoint2, + const Point& 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 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 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& line, float lineThickness) { + const Line 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& line, float lineThickness, + float arrowheadWidth, float arrowheadLength) { + const Line 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& 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 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& 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 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(); diff --git a/src/gui/graphics/geometry/juce_Path.h b/src/gui/graphics/geometry/juce_Path.h index 24052e9ceb..7ab52a6372 100644 --- a/src/gui/graphics/geometry/juce_Path.h +++ b/src/gui/graphics/geometry/juce_Path.h @@ -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& 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& 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& controlPoint, + const Point& 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& controlPoint1, + const Point& controlPoint2, + const Point& endPoint); + /** Returns the last point that was added to the path by one of the drawing methods. */ const Point 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& 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& 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& 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& centre, int numberOfPoints, float innerRadius, float outerRadius, diff --git a/src/gui/graphics/geometry/juce_PathStrokeType.cpp b/src/gui/graphics/geometry/juce_PathStrokeType.cpp index 4015e4ddd3..789c7c5331 100644 --- a/src/gui/graphics/geometry/juce_PathStrokeType.cpp +++ b/src/gui/graphics/geometry/juce_PathStrokeType.cpp @@ -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 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 & subPath, - const bool isClosed, - const float width, const float maxMiterExtensionSquared, - const PathStrokeType::JointStyle jointStyle, const PathStrokeType::EndCapStyle endStyle) + static void shortenSubPath (Array& 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& 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 subPath; - LineSection l; - l.x1 = 0; - l.y1 = 0; + Array 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 diff --git a/src/gui/graphics/geometry/juce_PathStrokeType.h b/src/gui/graphics/geometry/juce_PathStrokeType.h index a48c774681..c488082928 100644 --- a/src/gui/graphics/geometry/juce_PathStrokeType.h +++ b/src/gui/graphics/geometry/juce_PathStrokeType.h @@ -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; } diff --git a/src/gui/graphics/geometry/juce_Point.h b/src/gui/graphics/geometry/juce_Point.h index b0c228d8d5..a7e6e02f2d 100644 --- a/src/gui/graphics/geometry/juce_Point.h +++ b/src/gui/graphics/geometry/juce_Point.h @@ -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 (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 (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 diff --git a/src/gui/graphics/geometry/juce_Rectangle.h b/src/gui/graphics/geometry/juce_Rectangle.h index c8dc107f26..5882d59b8c 100644 --- a/src/gui/graphics/geometry/juce_Rectangle.h +++ b/src/gui/graphics/geometry/juce_Rectangle.h @@ -142,6 +142,18 @@ public: /** Returns a rectangle with the same size as this one, but a new position. */ const Rectangle withPosition (const Point& newPos) const throw() { return Rectangle (newPos.getX(), newPos.getY(), w, h); } + /** Returns the rectangle's top-left position as a Point. */ + const Point getTopLeft() const throw() { return getPosition(); } + + /** Returns the rectangle's top-right position as a Point. */ + const Point getTopRight() const throw() { return Point (x + w, y); } + + /** Returns the rectangle's bottom-left position as a Point. */ + const Point getBottomLeft() const throw() { return Point (x, y + h); } + + /** Returns the rectangle's bottom-right position as a Point. */ + const Point getBottomRight() const throw() { return Point (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; } diff --git a/src/gui/graphics/geometry/juce_RelativeCoordinate.cpp b/src/gui/graphics/geometry/juce_RelativeCoordinate.cpp index 91050e5f8a..89a188d969 100644 --- a/src/gui/graphics/geometry/juce_RelativeCoordinate.cpp +++ b/src/gui/graphics/geometry/juce_RelativeCoordinate.cpp @@ -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& 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 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 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 p1 (controlPoints[0].resolve (coordFinder)); - const Point 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 p1 (controlPoints[0].resolve (coordFinder)); - const Point p2 (controlPoints[1].resolve (coordFinder)); - const Point 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 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(); } diff --git a/src/gui/graphics/geometry/juce_RelativeCoordinate.h b/src/gui/graphics/geometry/juce_RelativeCoordinate.h index 280cef3d65..22a47f893c 100644 --- a/src/gui/graphics/geometry/juce_RelativeCoordinate.h +++ b/src/gui/graphics/geometry/juce_RelativeCoordinate.h @@ -360,6 +360,10 @@ public: /** Creates an absolute rectangle, relative to the origin. */ explicit RelativeRectangle (const Rectangle& 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 diff --git a/src/native/mac/juce_mac_CoreGraphicsContext.mm b/src/native/mac/juce_mac_CoreGraphicsContext.mm index f73d84abc7..c470fa40e9 100644 --- a/src/native/mac/juce_mac_CoreGraphicsContext.mm +++ b/src/native/mac/juce_mac_CoreGraphicsContext.mm @@ -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());