void Path::addRoundedRectangle (const float x, const float y, const float w, const float h, float csx, float csy, const bool curveTopLeft, const bool curveTopRight, const bool curveBottomLeft, const bool curveBottomRight) { csx = jmin (csx, w * 0.5f); csy = jmin (csy, h * 0.5f); const float cs45x = csx * 0.45f; const float cs45y = csy * 0.45f; const float x2 = x + w; const float y2 = y + h; if (curveTopLeft) { startNewSubPath (x, y + csy); cubicTo (x, y + cs45y, x + cs45x, y, x + csx, y); } else { startNewSubPath (x, y); } if (curveTopRight) { lineTo (x2 - csx, y); cubicTo (x2 - cs45x, y, x2, y + cs45y, x2, y + csy); } else { lineTo (x2, y); } if (curveBottomRight) { lineTo (x2, y2 - csy); cubicTo (x2, y2 - cs45y, x2 - cs45x, y2, x2 - csx, y2); } else { lineTo (x2, y2); } if (curveBottomLeft) { lineTo (x + csx, y2); cubicTo (x + cs45x, y2, x, y2 - cs45y, x, y2 - csy); } else { lineTo (x, y2); } closeSubPath(); }
void Path::addTriangle (Point<float> p1, Point<float> p2, Point<float> p3) { startNewSubPath (p1); lineTo (p2); lineTo (p3); closeSubPath(); }
void Path::addStar (const Point<float> centre, const int numberOfPoints, const float innerRadius, const float outerRadius, const float startAngle) { jassert (numberOfPoints > 1); // this would be silly. if (numberOfPoints > 1) { const float angleBetweenPoints = float_Pi * 2.0f / numberOfPoints; for (int i = 0; i < numberOfPoints; ++i) { const float angle = startAngle + i * angleBetweenPoints; const Point<float> p (centre.getPointOnCircumference (outerRadius, angle)); if (i == 0) startNewSubPath (p); else lineTo (p); lineTo (centre.getPointOnCircumference (innerRadius, angle + angleBetweenPoints * 0.5f)); } closeSubPath(); } }
void Path::addTriangle (const float x1, const float y1, const float x2, const float y2, const float x3, const float y3) { startNewSubPath (x1, y1); lineTo (x2, y2); lineTo (x3, y3); closeSubPath(); }
void Path::addPieSegment (const float x, const float y, const float width, const float height, const float fromRadians, const float toRadians, const float innerCircleProportionalSize) { float radiusX = width * 0.5f; float radiusY = height * 0.5f; const Point<float> centre (x + radiusX, y + radiusY); startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, fromRadians)); addArc (x, y, width, height, fromRadians, toRadians); if (std::abs (fromRadians - toRadians) > float_Pi * 1.999f) { closeSubPath(); if (innerCircleProportionalSize > 0) { radiusX *= innerCircleProportionalSize; radiusY *= innerCircleProportionalSize; startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, toRadians)); addArc (centre.x - radiusX, centre.y - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians); } } else { if (innerCircleProportionalSize > 0) { radiusX *= innerCircleProportionalSize; radiusY *= innerCircleProportionalSize; addArc (centre.x - radiusX, centre.y - radiusY, radiusX * 2.0f, radiusY * 2.0f, toRadians, fromRadians); } else { lineTo (centre); } } closeSubPath(); }
void Path::addPath (const Path& other, const AffineTransform& transformToApply) { size_t i = 0; const float* const d = other.data.elements; while (i < other.numElements) { const float type = d [i++]; if (type == closeSubPathMarker) { closeSubPath(); } else { float x = d[i++]; float y = d[i++]; transformToApply.transformPoint (x, y); if (type == moveMarker) { startNewSubPath (x, y); } else if (type == lineMarker) { lineTo (x, y); } else if (type == quadMarker) { float x2 = d [i++]; float y2 = d [i++]; transformToApply.transformPoint (x2, y2); quadraticTo (x, y, x2, y2); } else if (type == cubicMarker) { float x2 = d [i++]; float y2 = d [i++]; float x3 = d [i++]; float y3 = d [i++]; transformToApply.transformPoints (x2, y2, x3, y3); cubicTo (x, y, x2, y2, x3, y3); } else { // something's gone wrong with the element list! jassertfalse; } } } }
//============================================================================== void Path::addLineSegment (const Line<float>& line, float lineThickness) { const Line<float> reversed (line.reversed()); lineThickness *= 0.5f; startNewSubPath (line.getPointAlongLine (0, lineThickness)); lineTo (line.getPointAlongLine (0, -lineThickness)); lineTo (reversed.getPointAlongLine (0, lineThickness)); lineTo (reversed.getPointAlongLine (0, -lineThickness)); closeSubPath(); }
void Path::addQuadrilateral (const float x1, const float y1, const float x2, const float y2, const float x3, const float y3, const float x4, const float y4) { startNewSubPath (x1, y1); lineTo (x2, y2); lineTo (x3, y3); lineTo (x4, y4); closeSubPath(); }
void Path::lineTo (const float x, const float y) { JUCE_CHECK_COORDS_ARE_VALID (x, y); if (numElements == 0) startNewSubPath (0, 0); preallocateSpace (3); data.elements [numElements++] = lineMarker; data.elements [numElements++] = x; data.elements [numElements++] = y; bounds.extend (x, y); }
void Path::addEllipse (Rectangle<float> area) { const float hw = area.getWidth() * 0.5f; const float hw55 = hw * 0.55f; const float hh = area.getHeight() * 0.5f; const float hh55 = hh * 0.55f; const float cx = area.getX() + hw; const float cy = area.getY() + hh; startNewSubPath (cx, cy - hh); cubicTo (cx + hw55, cy - hh, cx + hw, cy - hh55, cx + hw, cy); cubicTo (cx + hw, cy + hh55, cx + hw55, cy + hh, cx, cy + hh); cubicTo (cx - hw55, cy + hh, cx - hw, cy + hh55, cx - hw, cy); cubicTo (cx - hw, cy - hh55, cx - hw55, cy - hh, cx, cy - hh); closeSubPath(); }
void Path::addEllipse (const float x, const float y, const float w, const float h) { const float hw = w * 0.5f; const float hw55 = hw * 0.55f; const float hh = h * 0.5f; const float hh55 = hh * 0.55f; const float cx = x + hw; const float cy = y + hh; startNewSubPath (cx, cy - hh); cubicTo (cx + hw55, cy - hh, cx + hw, cy - hh55, cx + hw, cy); cubicTo (cx + hw, cy + hh55, cx + hw55, cy + hh, cx, cy + hh); cubicTo (cx - hw55, cy + hh, cx - hw, cy + hh55, cx - hw, cy); cubicTo (cx - hw, cy - hh55, cx - hw55, cy - hh, cx, cy - hh); closeSubPath(); }
void Path::addArrow (const Line<float>& line, float lineThickness, float arrowheadWidth, float arrowheadLength) { const Line<float> reversed (line.reversed()); lineThickness *= 0.5f; arrowheadWidth *= 0.5f; arrowheadLength = jmin (arrowheadLength, 0.8f * line.getLength()); startNewSubPath (line.getPointAlongLine (0, lineThickness)); lineTo (line.getPointAlongLine (0, -lineThickness)); lineTo (reversed.getPointAlongLine (arrowheadLength, lineThickness)); lineTo (reversed.getPointAlongLine (arrowheadLength, arrowheadWidth)); lineTo (line.getEnd()); lineTo (reversed.getPointAlongLine (arrowheadLength, -arrowheadWidth)); lineTo (reversed.getPointAlongLine (arrowheadLength, -lineThickness)); closeSubPath(); }
void Path::quadraticTo (const float x1, const float y1, const float x2, const float y2) { JUCE_CHECK_COORDS_ARE_VALID (x1, y1); JUCE_CHECK_COORDS_ARE_VALID (x2, y2); if (numElements == 0) startNewSubPath (0, 0); preallocateSpace (5); data.elements [numElements++] = quadMarker; data.elements [numElements++] = x1; data.elements [numElements++] = y1; data.elements [numElements++] = x2; data.elements [numElements++] = y2; bounds.extend (x1, y1, x2, y2); }
void Path::addCentredArc (const float centreX, const float centreY, const float radiusX, const float radiusY, const float rotationOfEllipse, const float fromRadians, float toRadians, const bool startAsNewSubPath) { if (radiusX > 0.0f && radiusY > 0.0f) { const Point<float> centre (centreX, centreY); const AffineTransform rotation (AffineTransform::rotation (rotationOfEllipse, centreX, centreY)); float angle = fromRadians; if (startAsNewSubPath) startNewSubPath (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); if (fromRadians < toRadians) { if (startAsNewSubPath) angle += PathHelpers::ellipseAngularIncrement; while (angle < toRadians) { lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); angle += PathHelpers::ellipseAngularIncrement; } } else { if (startAsNewSubPath) angle -= PathHelpers::ellipseAngularIncrement; while (angle > toRadians) { lineTo (centre.getPointOnCircumference (radiusX, radiusY, angle).transformedBy (rotation)); angle -= PathHelpers::ellipseAngularIncrement; } } lineTo (centre.getPointOnCircumference (radiusX, radiusY, toRadians).transformedBy (rotation)); } }
void Path::addPath (const Path& other) { size_t i = 0; const float* const d = other.data.elements; while (i < other.numElements) { const float type = d[i++]; if (type == moveMarker) { startNewSubPath (d[i], d[i + 1]); i += 2; } else if (type == lineMarker) { lineTo (d[i], d[i + 1]); i += 2; } else if (type == quadMarker) { quadraticTo (d[i], d[i + 1], d[i + 2], d[i + 3]); i += 4; } else if (type == cubicMarker) { cubicTo (d[i], d[i + 1], d[i + 2], d[i + 3], d[i + 4], d[i + 5]); i += 6; } else if (type == closeSubPathMarker) { closeSubPath(); } else { // something's gone wrong with the element list! jassertfalse; } } }
void Path::addPolygon (const Point<float>& centre, const int numberOfSides, const float radius, const float startAngle) { jassert (numberOfSides > 1); // this would be silly. if (numberOfSides > 1) { const float angleBetweenPoints = float_Pi * 2.0f / numberOfSides; for (int i = 0; i < numberOfSides; ++i) { const float angle = startAngle + i * angleBetweenPoints; const Point<float> p (centre.getPointOnCircumference (radius, angle)); if (i == 0) startNewSubPath (p); else lineTo (p); } closeSubPath(); } }
void Path::addRoundedRectangle (const float x, const float y, const float w, const float h, float csx, float csy) { csx = jmin (csx, w * 0.5f); csy = jmin (csy, h * 0.5f); const float cs45x = csx * 0.45f; const float cs45y = csy * 0.45f; const float x2 = x + w; const float y2 = y + h; startNewSubPath (x + csx, y); lineTo (x2 - csx, y); cubicTo (x2 - cs45x, y, x2, y + cs45y, x2, y + csy); lineTo (x2, y2 - csy); cubicTo (x2, y2 - cs45y, x2 - cs45x, y2, x2 - csx, y2); lineTo (x + csx, y2); cubicTo (x + cs45x, y2, x, y2 - cs45y, x, y2 - csy); lineTo (x, y + csy); cubicTo (x, y + cs45y, x + cs45x, y, x + csx, y); closeSubPath(); }
void Path::cubicTo (const float x1, const float y1, const float x2, const float y2, const float x3, const float y3) { JUCE_CHECK_COORDS_ARE_VALID (x1, y1); JUCE_CHECK_COORDS_ARE_VALID (x2, y2); JUCE_CHECK_COORDS_ARE_VALID (x3, y3); if (numElements == 0) startNewSubPath (0, 0); preallocateSpace (7); data.elements [numElements++] = cubicMarker; data.elements [numElements++] = x1; data.elements [numElements++] = y1; data.elements [numElements++] = x2; data.elements [numElements++] = y2; data.elements [numElements++] = x3; data.elements [numElements++] = y3; bounds.extend (x1, y1, x2, y2); bounds.extend (x3, y3); }
void Path::restoreFromString (StringRef stringVersion) { clear(); setUsingNonZeroWinding (true); String::CharPointerType t (stringVersion.text); juce_wchar marker = 'm'; int numValues = 2; float values [6]; for (;;) { const String token (PathHelpers::nextToken (t)); const juce_wchar firstChar = token[0]; int startNum = 0; if (firstChar == 0) break; if (firstChar == 'm' || firstChar == 'l') { marker = firstChar; numValues = 2; } else if (firstChar == 'q') { marker = firstChar; numValues = 4; } else if (firstChar == 'c') { marker = firstChar; numValues = 6; } else if (firstChar == 'z') { marker = firstChar; numValues = 0; } else if (firstChar == 'a') { setUsingNonZeroWinding (false); continue; } else { ++startNum; values [0] = token.getFloatValue(); } for (int i = startNum; i < numValues; ++i) values [i] = PathHelpers::nextToken (t).getFloatValue(); switch (marker) { case 'm': startNewSubPath (values[0], values[1]); break; case 'l': lineTo (values[0], values[1]); break; case 'q': quadraticTo (values[0], values[1], values[2], values[3]); break; case 'c': cubicTo (values[0], values[1], values[2], values[3], values[4], values[5]); break; case 'z': closeSubPath(); break; default: jassertfalse; break; // illegal string format? } } }
void Path::addBubble (const Rectangle<float>& bodyArea, const Rectangle<float>& maximumArea, const Point<float> arrowTip, const float cornerSize, const float arrowBaseWidth) { const float halfW = bodyArea.getWidth() / 2.0f; const float halfH = bodyArea.getHeight() / 2.0f; const float cornerSizeW = jmin (cornerSize, halfW); const float cornerSizeH = jmin (cornerSize, halfH); const float cornerSizeW2 = 2.0f * cornerSizeW; const float cornerSizeH2 = 2.0f * cornerSizeH; startNewSubPath (bodyArea.getX() + cornerSizeW, bodyArea.getY()); const Rectangle<float> targetLimit (bodyArea.reduced (jmin (halfW - 1.0f, cornerSizeW + arrowBaseWidth), jmin (halfH - 1.0f, cornerSizeH + arrowBaseWidth))); if (Rectangle<float> (targetLimit.getX(), maximumArea.getY(), targetLimit.getWidth(), bodyArea.getY() - maximumArea.getY()).contains (arrowTip)) { lineTo (arrowTip.x - arrowBaseWidth, bodyArea.getY()); lineTo (arrowTip.x, arrowTip.y); lineTo (arrowTip.x + arrowBaseWidth, bodyArea.getY()); } lineTo (bodyArea.getRight() - cornerSizeW, bodyArea.getY()); addArc (bodyArea.getRight() - cornerSizeW2, bodyArea.getY(), cornerSizeW2, cornerSizeH2, 0, float_Pi * 0.5f); if (Rectangle<float> (bodyArea.getRight(), targetLimit.getY(), maximumArea.getRight() - bodyArea.getRight(), targetLimit.getHeight()).contains (arrowTip)) { lineTo (bodyArea.getRight(), arrowTip.y - arrowBaseWidth); lineTo (arrowTip.x, arrowTip.y); lineTo (bodyArea.getRight(), arrowTip.y + arrowBaseWidth); } lineTo (bodyArea.getRight(), bodyArea.getBottom() - cornerSizeH); addArc (bodyArea.getRight() - cornerSizeW2, bodyArea.getBottom() - cornerSizeH2, cornerSizeW2, cornerSizeH2, float_Pi * 0.5f, float_Pi); if (Rectangle<float> (targetLimit.getX(), bodyArea.getBottom(), targetLimit.getWidth(), maximumArea.getBottom() - bodyArea.getBottom()).contains (arrowTip)) { lineTo (arrowTip.x + arrowBaseWidth, bodyArea.getBottom()); lineTo (arrowTip.x, arrowTip.y); lineTo (arrowTip.x - arrowBaseWidth, bodyArea.getBottom()); } lineTo (bodyArea.getX() + cornerSizeW, bodyArea.getBottom()); addArc (bodyArea.getX(), bodyArea.getBottom() - cornerSizeH2, cornerSizeW2, cornerSizeH2, float_Pi, float_Pi * 1.5f); if (Rectangle<float> (maximumArea.getX(), targetLimit.getY(), bodyArea.getX() - maximumArea.getX(), targetLimit.getHeight()).contains (arrowTip)) { lineTo (bodyArea.getX(), arrowTip.y + arrowBaseWidth); lineTo (arrowTip.x, arrowTip.y); lineTo (bodyArea.getX(), arrowTip.y - arrowBaseWidth); } lineTo (bodyArea.getX(), bodyArea.getY() + cornerSizeH); addArc (bodyArea.getX(), bodyArea.getY(), cornerSizeW2, cornerSizeH2, float_Pi * 1.5f, float_Pi * 2.0f - 0.05f); closeSubPath(); }
void Path::addBubble (float x, float y, float w, float h, float cs, float tipX, float tipY, int whichSide, float arrowPos, float arrowWidth) { if (w > 1.0f && h > 1.0f) { cs = jmin (cs, w * 0.5f, h * 0.5f); const float cs2 = 2.0f * cs; startNewSubPath (x + cs, y); if (whichSide == 0) { const float halfArrowW = jmin (arrowWidth, w - cs2) * 0.5f; const float arrowX1 = x + cs + jmax (0.0f, (w - cs2 - arrowWidth) * arrowPos - halfArrowW); lineTo (arrowX1, y); lineTo (tipX, tipY); lineTo (arrowX1 + halfArrowW * 2.0f, y); } lineTo (x + w - cs, y); if (cs > 0.0f) addArc (x + w - cs2, y, cs2, cs2, 0, float_Pi * 0.5f); if (whichSide == 3) { const float halfArrowH = jmin (arrowWidth, h - cs2) * 0.5f; const float arrowY1 = y + cs + jmax (0.0f, (h - cs2 - arrowWidth) * arrowPos - halfArrowH); lineTo (x + w, arrowY1); lineTo (tipX, tipY); lineTo (x + w, arrowY1 + halfArrowH * 2.0f); } lineTo (x + w, y + h - cs); if (cs > 0.0f) addArc (x + w - cs2, y + h - cs2, cs2, cs2, float_Pi * 0.5f, float_Pi); if (whichSide == 2) { const float halfArrowW = jmin (arrowWidth, w - cs2) * 0.5f; const float arrowX1 = x + cs + jmax (0.0f, (w - cs2 - arrowWidth) * arrowPos - halfArrowW); lineTo (arrowX1 + halfArrowW * 2.0f, y + h); lineTo (tipX, tipY); lineTo (arrowX1, y + h); } lineTo (x + cs, y + h); if (cs > 0.0f) addArc (x, y + h - cs2, cs2, cs2, float_Pi, float_Pi * 1.5f); if (whichSide == 1) { const float halfArrowH = jmin (arrowWidth, h - cs2) * 0.5f; const float arrowY1 = y + cs + jmax (0.0f, (h - cs2 - arrowWidth) * arrowPos - halfArrowH); lineTo (x, arrowY1 + halfArrowH * 2.0f); lineTo (tipX, tipY); lineTo (x, arrowY1); } lineTo (x, y + cs); if (cs > 0.0f) addArc (x, y, cs2, cs2, float_Pi * 1.5f, float_Pi * 2.0f - PathHelpers::ellipseAngularIncrement); closeSubPath(); } }
//============================================================================== void Path::loadPathFromStream (InputStream& source) { while (! source.isExhausted()) { switch (source.readByte()) { case 'm': { const float x = source.readFloat(); const float y = source.readFloat(); startNewSubPath (x, y); break; } case 'l': { const float x = source.readFloat(); const float y = source.readFloat(); lineTo (x, y); break; } case 'q': { const float x1 = source.readFloat(); const float y1 = source.readFloat(); const float x2 = source.readFloat(); const float y2 = source.readFloat(); quadraticTo (x1, y1, x2, y2); break; } case 'b': { const float x1 = source.readFloat(); const float y1 = source.readFloat(); const float x2 = source.readFloat(); const float y2 = source.readFloat(); const float x3 = source.readFloat(); const float y3 = source.readFloat(); cubicTo (x1, y1, x2, y2, x3, y3); break; } case 'c': closeSubPath(); break; case 'n': useNonZeroWinding = true; break; case 'z': useNonZeroWinding = false; break; case 'e': return; // end of path marker default: jassertfalse; // illegal char in the stream break; } } }
void Path::startNewSubPath (const Point<float> start) { startNewSubPath (start.x, start.y); }