|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2015 - ROLI Ltd.
-
- Permission is granted to use this software under the terms of either:
- a) the GPL v2 (or any later version)
- b) the Affero GPL v3
-
- Details of these licenses can be found at: www.gnu.org/licenses
-
- JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- ------------------------------------------------------------------------------
-
- To release a closed-source product which uses JUCE, commercial licenses are
- available: visit www.juce.com for more information.
-
- ==============================================================================
- */
-
- #include "../../jucer_Headers.h"
- #include "jucer_PaintElementPath.h"
- #include "../properties/jucer_PositionPropertyBase.h"
- #include "jucer_PaintElementUndoableAction.h"
- #include "../jucer_UtilityFunctions.h"
-
- //==============================================================================
- class ChangePointAction : public PaintElementUndoableAction <PaintElementPath>
- {
- public:
- ChangePointAction (PathPoint* const point,
- const int pointIndex,
- const PathPoint& newValue_)
- : PaintElementUndoableAction <PaintElementPath> (point->owner),
- index (pointIndex),
- newValue (newValue_),
- oldValue (*point)
- {
- }
-
- ChangePointAction (PathPoint* const point,
- const PathPoint& newValue_)
- : PaintElementUndoableAction <PaintElementPath> (point->owner),
- index (point->owner->indexOfPoint (point)),
- newValue (newValue_),
- oldValue (*point)
- {
- }
-
- bool perform()
- {
- return changeTo (newValue);
- }
-
- bool undo()
- {
- return changeTo (oldValue);
- }
-
- private:
- const int index;
- PathPoint newValue, oldValue;
-
- PathPoint* getPoint() const
- {
- PathPoint* p = getElement()->getPoint (index);
- jassert (p != nullptr);
- return p;
- }
-
- bool changeTo (const PathPoint& value) const
- {
- showCorrectTab();
-
- PaintElementPath* const path = getElement();
- jassert (path != nullptr);
-
- PathPoint* const p = path->getPoint (index);
- jassert (p != nullptr);
-
- const bool typeChanged = (p->type != value.type);
- *p = value;
- p->owner = path;
-
- if (typeChanged)
- path->pointListChanged();
-
- path->changed();
- return true;
- }
- };
-
-
- //==============================================================================
- class PathWindingModeProperty : public ChoicePropertyComponent,
- public ChangeListener
- {
- public:
- PathWindingModeProperty (PaintElementPath* const owner_)
- : ChoicePropertyComponent ("winding rule"),
- owner (owner_)
- {
- choices.add ("Non-zero winding");
- choices.add ("Even/odd winding");
-
- owner->getDocument()->addChangeListener (this);
- }
-
- ~PathWindingModeProperty()
- {
- owner->getDocument()->removeChangeListener (this);
- }
-
- void setIndex (int newIndex) { owner->setNonZeroWinding (newIndex == 0, true); }
- int getIndex() const { return owner->isNonZeroWinding() ? 0 : 1; }
-
- void changeListenerCallback (ChangeBroadcaster*) { refresh(); }
-
- private:
- PaintElementPath* const owner;
- };
-
-
- //==============================================================================
- PaintElementPath::PaintElementPath (PaintRoutine* pr)
- : ColouredElement (pr, "Path", true, true),
- nonZeroWinding (true)
- {
- }
-
- PaintElementPath::~PaintElementPath()
- {
- }
-
- static int randomPos (int size)
- {
- return size / 4 + Random::getSystemRandom().nextInt (size / 4) - size / 8;
- }
-
- void PaintElementPath::setInitialBounds (int w, int h)
- {
- String s;
-
- int x = randomPos (w);
- int y = randomPos (h);
-
- s << "s "
- << x << " " << y << " l "
- << (x + 30) << " " << (y + 50) << " l "
- << (x - 30) << " " << (y + 50) << " x";
-
- restorePathFromString (s);
- }
-
- //==============================================================================
- int PaintElementPath::getBorderSize() const
- {
- return isStrokePresent ? 1 + roundFloatToInt (strokeType.stroke.getStrokeThickness())
- : 0;
- }
-
- Rectangle<int> PaintElementPath::getCurrentBounds (const Rectangle<int>& parentArea) const
- {
- updateStoredPath (getDocument()->getComponentLayout(), parentArea);
-
- Rectangle<float> r (path.getBounds());
-
- const int borderSize = getBorderSize();
-
- return Rectangle<int> ((int) r.getX() - borderSize,
- (int) r.getY() - borderSize,
- (int) r.getWidth() + borderSize * 2,
- (int) r.getHeight() + borderSize * 2);
- }
-
- void PaintElementPath::setCurrentBounds (const Rectangle<int>& b,
- const Rectangle<int>& parentArea,
- const bool /*undoable*/)
- {
- Rectangle<int> newBounds (b);
- newBounds.setSize (jmax (1, newBounds.getWidth()),
- jmax (1, newBounds.getHeight()));
-
- const Rectangle<int> current (getCurrentBounds (parentArea));
-
- if (newBounds != current)
- {
- const int borderSize = getBorderSize();
-
- const int dx = newBounds.getX() - current.getX();
- const int dy = newBounds.getY() - current.getY();
-
- const double scaleStartX = current.getX() + borderSize;
- const double scaleStartY = current.getY() + borderSize;
- const double scaleX = (newBounds.getWidth() - borderSize * 2) / (double) (current.getWidth() - borderSize * 2);
- const double scaleY = (newBounds.getHeight() - borderSize * 2) / (double) (current.getHeight() - borderSize * 2);
-
- for (int i = 0; i < points.size(); ++i)
- {
- PathPoint* const destPoint = points.getUnchecked(i);
- PathPoint p (*destPoint);
-
- for (int j = p.getNumPoints(); --j >= 0;)
- rescalePoint (p.pos[j], dx, dy,
- scaleX, scaleY,
- scaleStartX, scaleStartY,
- parentArea);
-
- perform (new ChangePointAction (destPoint, i, p), "Move path");
- }
- }
- }
-
- void PaintElementPath::rescalePoint (RelativePositionedRectangle& pos, int dx, int dy,
- double scaleX, double scaleY,
- double scaleStartX, double scaleStartY,
- const Rectangle<int>& parentArea) const
- {
- double x, y, w, h;
- pos.getRectangleDouble (x, y, w, h, parentArea, getDocument()->getComponentLayout());
-
- x = (x - scaleStartX) * scaleX + scaleStartX + dx;
- y = (y - scaleStartY) * scaleY + scaleStartY + dy;
-
- pos.updateFrom (x, y, w, h, parentArea, getDocument()->getComponentLayout());
- }
-
- //==============================================================================
- static void drawArrow (Graphics& g, const Point<float> p1, const Point<float> p2)
- {
- g.drawArrow (Line<float> (p1.x, p1.y, (p1.x + p2.x) * 0.5f, (p1.y + p2.y) * 0.5f), 1.0f, 8.0f, 10.0f);
- g.drawLine (p1.x + (p2.x - p1.x) * 0.49f, p1.y + (p2.y - p1.y) * 0.49f, p2.x, p2.y);
- }
-
- void PaintElementPath::draw (Graphics& g, const ComponentLayout* layout, const Rectangle<int>& parentArea)
- {
- updateStoredPath (layout, parentArea);
- path.setUsingNonZeroWinding (nonZeroWinding);
-
- fillType.setFillType (g, getDocument(), parentArea);
- g.fillPath (path);
-
- if (isStrokePresent)
- {
- strokeType.fill.setFillType (g, getDocument(), parentArea);
- g.strokePath (path, getStrokeType().stroke);
- }
- }
-
- void PaintElementPath::drawExtraEditorGraphics (Graphics& g, const Rectangle<int>& relativeTo)
- {
- ComponentLayout* layout = getDocument()->getComponentLayout();
-
- for (int i = 0; i < points.size(); ++i)
- {
- PathPoint* const p = points.getUnchecked (i);
-
- const int numPoints = p->getNumPoints();
-
- if (numPoints > 0)
- {
- if (owner->getSelectedPoints().isSelected (p))
- {
- g.setColour (Colours::red);
- Point<float> p1, p2;
-
- if (numPoints > 2)
- {
- p1 = p->pos[1].toXY (relativeTo, layout);
- p2 = p->pos[2].toXY (relativeTo, layout);
- drawArrow (g, p1, p2);
- }
-
- if (numPoints > 1)
- {
- p1 = p->pos[0].toXY (relativeTo, layout);
- p2 = p->pos[1].toXY (relativeTo, layout);
- drawArrow (g, p1, p2);
- }
-
- p2 = p->pos[0].toXY (relativeTo, layout);
-
- if (const PathPoint* const nextPoint = points [i - 1])
- {
- p1 = nextPoint->pos [nextPoint->getNumPoints() - 1].toXY (relativeTo, layout);
- drawArrow (g, p1, p2);
- }
- }
- }
- }
- }
-
- void PaintElementPath::resized()
- {
- ColouredElement::resized();
- }
-
- void PaintElementPath::parentSizeChanged()
- {
- repaint();
- }
-
- //==============================================================================
- void PaintElementPath::mouseDown (const MouseEvent& e)
- {
- if (e.mods.isPopupMenu() || ! owner->getSelectedElements().isSelected (this))
- mouseDownOnSegment = -1;
- else
- mouseDownOnSegment = findSegmentAtXY (getX() + e.x, getY() + e.y);
-
- if (points [mouseDownOnSegment] != nullptr)
- mouseDownSelectSegmentStatus = owner->getSelectedPoints().addToSelectionOnMouseDown (points [mouseDownOnSegment], e.mods);
- else
- ColouredElement::mouseDown (e);
- }
-
- void PaintElementPath::mouseDrag (const MouseEvent& e)
- {
- if (mouseDownOnSegment < 0)
- ColouredElement::mouseDrag (e);
- }
-
- void PaintElementPath::mouseUp (const MouseEvent& e)
- {
- if (points [mouseDownOnSegment] == 0)
- ColouredElement::mouseUp (e);
- else
- owner->getSelectedPoints().addToSelectionOnMouseUp (points [mouseDownOnSegment],
- e.mods, false, mouseDownSelectSegmentStatus);
- }
-
- //==============================================================================
- void PaintElementPath::changed()
- {
- ColouredElement::changed();
- lastPathBounds = Rectangle<int>();
- }
-
- void PaintElementPath::pointListChanged()
- {
- changed();
- siblingComponentsChanged();
- }
-
- //==============================================================================
- void PaintElementPath::getEditableProperties (Array <PropertyComponent*>& props)
- {
- props.add (new PathWindingModeProperty (this));
- getColourSpecificProperties (props);
- }
-
- //==============================================================================
- static String positionToPairOfValues (const RelativePositionedRectangle& position,
- const ComponentLayout* layout)
- {
- String x, y, w, h;
- positionToCode (position, layout, x, y, w, h);
- return castToFloat (x) + ", " + castToFloat (y);
- }
-
- void PaintElementPath::fillInGeneratedCode (GeneratedCode& code, String& paintMethodCode)
- {
- if (fillType.isInvisible() && (strokeType.isInvisible() || ! isStrokePresent))
- return;
-
- const String pathVariable ("internalPath" + String (code.getUniqueSuffix()));
-
- const ComponentLayout* layout = code.document->getComponentLayout();
-
- code.privateMemberDeclarations
- << "Path " << pathVariable << ";\n";
-
- String r;
- bool somePointsAreRelative = false;
-
- if (! nonZeroWinding)
- r << pathVariable << ".setUsingNonZeroWinding (false);\n";
-
- for (int i = 0; i < points.size(); ++i)
- {
- const PathPoint* const p = points.getUnchecked(i);
-
- switch (p->type)
- {
- case Path::Iterator::startNewSubPath:
- r << pathVariable << ".startNewSubPath (" << positionToPairOfValues (p->pos[0], layout) << ");\n";
- somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
- break;
-
- case Path::Iterator::lineTo:
- r << pathVariable << ".lineTo (" << positionToPairOfValues (p->pos[0], layout) << ");\n";
- somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
- break;
-
- case Path::Iterator::quadraticTo:
- r << pathVariable << ".quadraticTo (" << positionToPairOfValues (p->pos[0], layout)
- << ", " << positionToPairOfValues (p->pos[1], layout) << ");\n";
- somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
- somePointsAreRelative = somePointsAreRelative || ! p->pos[1].rect.isPositionAbsolute();
- break;
-
- case Path::Iterator::cubicTo:
- r << pathVariable << ".cubicTo (" << positionToPairOfValues (p->pos[0], layout)
- << ", " << positionToPairOfValues (p->pos[1], layout)
- << ", " << positionToPairOfValues (p->pos[2], layout) << ");\n";
- somePointsAreRelative = somePointsAreRelative || ! p->pos[0].rect.isPositionAbsolute();
- somePointsAreRelative = somePointsAreRelative || ! p->pos[1].rect.isPositionAbsolute();
- somePointsAreRelative = somePointsAreRelative || ! p->pos[2].rect.isPositionAbsolute();
- break;
-
- case Path::Iterator::closePath:
- r << pathVariable << ".closeSubPath();\n";
- break;
-
- default:
- jassertfalse;
- break;
- }
- }
-
- r << '\n';
-
- if (somePointsAreRelative)
- code.getCallbackCode (String(), "void", "resized()", false)
- << pathVariable << ".clear();\n" << r;
- else
- code.constructorCode << r;
-
- if (! fillType.isInvisible())
- {
- fillType.fillInGeneratedCode (code, paintMethodCode);
-
- paintMethodCode << "g.fillPath (" << pathVariable << ");\n";
- }
-
- if (isStrokePresent && ! strokeType.isInvisible())
- {
- String s;
-
- strokeType.fill.fillInGeneratedCode (code, s);
- s << "g.strokePath (" << pathVariable << ", " << strokeType.getPathStrokeCode() << ");\n";
-
- paintMethodCode += s;
- }
-
- paintMethodCode += "\n";
- }
-
- //==============================================================================
- XmlElement* PaintElementPath::createXml() const
- {
- XmlElement* e = new XmlElement (getTagName());
- position.applyToXml (*e);
- addColourAttributes (e);
- e->setAttribute ("nonZeroWinding", nonZeroWinding);
- e->addTextElement (pathToString());
-
- return e;
- }
-
- bool PaintElementPath::loadFromXml (const XmlElement& xml)
- {
- if (xml.hasTagName (getTagName()))
- {
- position.restoreFromXml (xml, position);
- loadColourAttributes (xml);
- nonZeroWinding = xml.getBoolAttribute ("nonZeroWinding", true);
-
- restorePathFromString (xml.getAllSubText().trim());
-
- return true;
- }
-
- jassertfalse;
- return false;
- }
-
- //==============================================================================
- void PaintElementPath::createSiblingComponents()
- {
- ColouredElement::createSiblingComponents();
-
- for (int i = 0; i < points.size(); ++i)
- {
- switch (points.getUnchecked(i)->type)
- {
- case Path::Iterator::startNewSubPath:
- siblingComponents.add (new PathPointComponent (this, i, 0));
- break;
- case Path::Iterator::lineTo:
- siblingComponents.add (new PathPointComponent (this, i, 0));
- break;
- case Path::Iterator::quadraticTo:
- siblingComponents.add (new PathPointComponent (this, i, 0));
- siblingComponents.add (new PathPointComponent (this, i, 1));
- break;
- case Path::Iterator::cubicTo:
- siblingComponents.add (new PathPointComponent (this, i, 0));
- siblingComponents.add (new PathPointComponent (this, i, 1));
- siblingComponents.add (new PathPointComponent (this, i, 2));
- break;
- case Path::Iterator::closePath:
- break;
-
- default:
- jassertfalse; break;
- }
- }
-
- for (int i = 0; i < siblingComponents.size(); ++i)
- {
- getParentComponent()->addAndMakeVisible (siblingComponents.getUnchecked(i));
- siblingComponents.getUnchecked(i)->updatePosition();
- }
- }
-
-
- String PaintElementPath::pathToString() const
- {
- String s;
-
- for (int i = 0; i < points.size(); ++i)
- {
- const PathPoint* const p = points.getUnchecked(i);
-
- switch (p->type)
- {
- case Path::Iterator::startNewSubPath:
- s << "s " << p->pos[0].toString() << ' ';
- break;
- case Path::Iterator::lineTo:
- s << "l " << p->pos[0].toString() << ' ';
- break;
- case Path::Iterator::quadraticTo:
- s << "q " << p->pos[0].toString()
- << ' ' << p->pos[1].toString() << ' ';
- break;
- case Path::Iterator::cubicTo:
- s << "c " << p->pos[0].toString()
- << ' ' << p->pos[1].toString() << ' '
- << ' ' << p->pos[2].toString() << ' ';
- break;
- case Path::Iterator::closePath:
- s << "x ";
- break;
-
- default:
- jassertfalse; break;
- }
- }
-
- return s.trimEnd();
- }
-
- void PaintElementPath::restorePathFromString (const String& s)
- {
- points.clear();
-
- StringArray tokens;
- tokens.addTokens (s, false);
- tokens.trim();
- tokens.removeEmptyStrings();
-
- for (int i = 0; i < tokens.size(); ++i)
- {
- ScopedPointer<PathPoint> p (new PathPoint (this));
-
- if (tokens[i] == "s")
- {
- p->type = Path::Iterator::startNewSubPath;
- p->pos [0] = RelativePositionedRectangle();
- p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
- i += 2;
- }
- else if (tokens[i] == "l")
- {
- p->type = Path::Iterator::lineTo;
- p->pos [0] = RelativePositionedRectangle();
- p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
- i += 2;
- }
- else if (tokens[i] == "q")
- {
- p->type = Path::Iterator::quadraticTo;
- p->pos [0] = RelativePositionedRectangle();
- p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
- p->pos [1] = RelativePositionedRectangle();
- p->pos [1].rect = PositionedRectangle (tokens [i + 3] + " " + tokens [i + 4]);
- i += 4;
- }
- else if (tokens[i] == "c")
- {
- p->type = Path::Iterator::cubicTo;
- p->pos [0] = RelativePositionedRectangle();
- p->pos [0].rect = PositionedRectangle (tokens [i + 1] + " " + tokens [i + 2]);
- p->pos [1] = RelativePositionedRectangle();
- p->pos [1].rect = PositionedRectangle (tokens [i + 3] + " " + tokens [i + 4]);
- p->pos [2] = RelativePositionedRectangle();
- p->pos [2].rect = PositionedRectangle (tokens [i + 5] + " " + tokens [i + 6]);
- i += 6;
- }
- else if (tokens[i] == "x")
- {
- p->type = Path::Iterator::closePath;
- }
- else
- continue;
-
- points.add (p.release());
- }
- }
-
- void PaintElementPath::setToPath (const Path& newPath)
- {
- points.clear();
-
- Path::Iterator i (newPath);
-
- while (i.next())
- {
- ScopedPointer<PathPoint> p (new PathPoint (this));
- p->type = i.elementType;
-
- if (i.elementType == Path::Iterator::startNewSubPath)
- {
- p->pos [0].rect.setX (i.x1);
- p->pos [0].rect.setY (i.y1);
- }
- else if (i.elementType == Path::Iterator::lineTo)
- {
- p->pos [0].rect.setX (i.x1);
- p->pos [0].rect.setY (i.y1);
- }
- else if (i.elementType == Path::Iterator::quadraticTo)
- {
- p->pos [0].rect.setX (i.x1);
- p->pos [0].rect.setY (i.y1);
- p->pos [1].rect.setX (i.x2);
- p->pos [1].rect.setY (i.y2);
- }
- else if (i.elementType == Path::Iterator::cubicTo)
- {
- p->pos [0].rect.setX (i.x1);
- p->pos [0].rect.setY (i.y1);
- p->pos [1].rect.setX (i.x2);
- p->pos [1].rect.setY (i.y2);
- p->pos [2].rect.setX (i.x3);
- p->pos [2].rect.setY (i.y3);
- }
- else if (i.elementType == Path::Iterator::closePath)
- {
- }
- else
- {
- continue;
- }
-
- points.add (p.release());
- }
- }
-
- void PaintElementPath::updateStoredPath (const ComponentLayout* layout, const Rectangle<int>& relativeTo) const
- {
- if (lastPathBounds != relativeTo && ! relativeTo.isEmpty())
- {
- lastPathBounds = relativeTo;
- path.clear();
-
- for (int i = 0; i < points.size(); ++i)
- {
- const PathPoint* const p = points.getUnchecked(i);
-
- switch (p->type)
- {
- case Path::Iterator::startNewSubPath:
- path.startNewSubPath (p->pos[0].toXY (relativeTo, layout));
- break;
-
- case Path::Iterator::lineTo:
- path.lineTo (p->pos[0].toXY (relativeTo, layout));
- break;
-
- case Path::Iterator::quadraticTo:
- path.quadraticTo (p->pos[0].toXY (relativeTo, layout),
- p->pos[1].toXY (relativeTo, layout));
- break;
-
- case Path::Iterator::cubicTo:
- path.cubicTo (p->pos[0].toXY (relativeTo, layout),
- p->pos[1].toXY (relativeTo, layout),
- p->pos[2].toXY (relativeTo, layout));
- break;
-
- case Path::Iterator::closePath:
- path.closeSubPath();
- break;
-
- default:
- jassertfalse; break;
- }
- }
- }
- }
-
- //==============================================================================
- class ChangeWindingAction : public PaintElementUndoableAction <PaintElementPath>
- {
- public:
- ChangeWindingAction (PaintElementPath* const path, const bool newValue_)
- : PaintElementUndoableAction <PaintElementPath> (path),
- newValue (newValue_),
- oldValue (path->isNonZeroWinding())
- {
- }
-
- bool perform()
- {
- showCorrectTab();
- getElement()->setNonZeroWinding (newValue, false);
- return true;
- }
-
- bool undo()
- {
- showCorrectTab();
- getElement()->setNonZeroWinding (oldValue, false);
- return true;
- }
-
- private:
- bool newValue, oldValue;
- };
-
- void PaintElementPath::setNonZeroWinding (const bool nonZero, const bool undoable)
- {
- if (nonZero != nonZeroWinding)
- {
- if (undoable)
- {
- perform (new ChangeWindingAction (this, nonZero), "Change path winding rule");
- }
- else
- {
- nonZeroWinding = nonZero;
- changed();
- }
- }
- }
-
- bool PaintElementPath::isSubpathClosed (int index) const
- {
- for (int i = index + 1; i < points.size(); ++i)
- {
- if (points.getUnchecked (i)->type == Path::Iterator::closePath)
- return true;
-
- if (points.getUnchecked (i)->type == Path::Iterator::startNewSubPath)
- break;
- }
-
- return false;
- }
-
- //==============================================================================
- void PaintElementPath::setSubpathClosed (int index, const bool closed, const bool undoable)
- {
- if (closed != isSubpathClosed (index))
- {
- for (int i = index + 1; i < points.size(); ++i)
- {
- PathPoint* p = points.getUnchecked (i);
-
- if (p->type == Path::Iterator::closePath)
- {
- jassert (! closed);
-
- deletePoint (i, undoable);
- return;
- }
-
- if (p->type == Path::Iterator::startNewSubPath)
- {
- jassert (closed);
-
- PathPoint* pp = addPoint (i - 1, undoable);
-
- PathPoint p2 (*pp);
- p2.type = Path::Iterator::closePath;
- perform (new ChangePointAction (pp, p2), "Close subpath");
- return;
- }
- }
-
- jassert (closed);
-
- PathPoint* p = addPoint (points.size() - 1, undoable);
- PathPoint p2 (*p);
- p2.type = Path::Iterator::closePath;
- perform (new ChangePointAction (p, p2), "Close subpath");
- }
- }
-
- //==============================================================================
- class AddPointAction : public PaintElementUndoableAction <PaintElementPath>
- {
- public:
- AddPointAction (PaintElementPath* path, int pointIndexToAddItAfter_)
- : PaintElementUndoableAction <PaintElementPath> (path),
- indexAdded (-1),
- pointIndexToAddItAfter (pointIndexToAddItAfter_)
- {
- }
-
- bool perform()
- {
- showCorrectTab();
-
- PaintElementPath* const path = getElement();
- jassert (path != nullptr);
-
- PathPoint* const p = path->addPoint (pointIndexToAddItAfter, false);
- jassert (p != nullptr);
-
- indexAdded = path->indexOfPoint (p);
- jassert (indexAdded >= 0);
- return true;
- }
-
- bool undo()
- {
- showCorrectTab();
-
- PaintElementPath* const path = getElement();
- jassert (path != nullptr);
-
- path->deletePoint (indexAdded, false);
- return true;
- }
-
- int indexAdded;
-
- private:
- int pointIndexToAddItAfter;
- };
-
- PathPoint* PaintElementPath::addPoint (int pointIndexToAddItAfter, const bool undoable)
- {
- if (undoable)
- {
- AddPointAction* action = new AddPointAction (this, pointIndexToAddItAfter);
- perform (action, "Add path point");
- return points [action->indexAdded];
- }
-
- double x1 = 20.0, y1 = 20.0, x2, y2;
-
- ComponentLayout* layout = getDocument()->getComponentLayout();
- const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
-
- if (points [pointIndexToAddItAfter] != nullptr)
- points [pointIndexToAddItAfter]->pos [points [pointIndexToAddItAfter]->getNumPoints() - 1].getXY (x1, y1, area, layout);
- else if (points[0] != nullptr)
- points[0]->pos[0].getXY (x1, y1, area, layout);
-
- x2 = x1 + 50.0;
- y2 = y1 + 50.0;
-
- if (points [pointIndexToAddItAfter + 1] != nullptr)
- {
- if (points [pointIndexToAddItAfter + 1]->type == Path::Iterator::closePath
- || points [pointIndexToAddItAfter + 1]->type == Path::Iterator::startNewSubPath)
- {
- int i = pointIndexToAddItAfter;
- while (i > 0)
- if (points [--i]->type == Path::Iterator::startNewSubPath)
- break;
-
- if (i != pointIndexToAddItAfter)
- points [i]->pos[0].getXY (x2, y2, area, layout);
- }
- else
- {
- points [pointIndexToAddItAfter + 1]->pos[0].getXY (x2, y2, area, layout);
- }
- }
- else
- {
- int i = pointIndexToAddItAfter + 1;
- while (i > 0)
- if (points [--i]->type == Path::Iterator::startNewSubPath)
- break;
-
- points[i]->pos[0].getXY (x2, y2, area, layout);
- }
-
- PathPoint* const p = new PathPoint (this);
-
- p->type = Path::Iterator::lineTo;
- p->pos[0].rect.setX ((x1 + x2) * 0.5f);
- p->pos[0].rect.setY ((y1 + y2) * 0.5f);
-
- points.insert (pointIndexToAddItAfter + 1, p);
-
- pointListChanged();
- return p;
- }
-
- //==============================================================================
- class DeletePointAction : public PaintElementUndoableAction <PaintElementPath>
- {
- public:
- DeletePointAction (PaintElementPath* const path, const int indexToRemove_)
- : PaintElementUndoableAction <PaintElementPath> (path),
- indexToRemove (indexToRemove_),
- oldValue (*path->getPoint (indexToRemove))
- {
- }
-
- bool perform()
- {
- showCorrectTab();
-
- PaintElementPath* const path = getElement();
- jassert (path != nullptr);
-
- path->deletePoint (indexToRemove, false);
- return path != nullptr;
- }
-
- bool undo()
- {
- showCorrectTab();
-
- PaintElementPath* const path = getElement();
- jassert (path != nullptr);
-
- PathPoint* p = path->addPoint (indexToRemove - 1, false);
- *p = oldValue;
-
- return path != nullptr;
- }
-
- int indexToRemove;
-
- private:
- PathPoint oldValue;
- };
-
- void PaintElementPath::deletePoint (int pointIndex, const bool undoable)
- {
- if (undoable)
- {
- perform (new DeletePointAction (this, pointIndex), "Delete path point");
- }
- else
- {
- PathPoint* const p = points [pointIndex];
-
- if (p != nullptr && pointIndex > 0)
- {
- owner->getSelectedPoints().deselect (p);
- owner->getSelectedPoints().changed (true);
-
- points.remove (pointIndex);
- pointListChanged();
- }
- }
- }
-
- //==============================================================================
- bool PaintElementPath::getPoint (int index, int pointNumber, double& x, double& y, const Rectangle<int>& parentArea) const
- {
- const PathPoint* const p = points [index];
-
- if (p == nullptr)
- {
- x = y = 0;
- return false;
- }
-
- jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
- jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
-
- p->pos [pointNumber].getXY (x, y, parentArea, getDocument()->getComponentLayout());
- return true;
- }
-
- int PaintElementPath::findSegmentAtXY (int x, int y) const
- {
- double x1, y1, x2, y2, x3, y3, lastX = 0.0, lastY = 0.0, subPathStartX = 0.0, subPathStartY = 0.0;
-
- ComponentLayout* const layout = getDocument()->getComponentLayout();
- const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
-
- int subpathStartIndex = 0;
-
- float thickness = 10.0f;
- if (isStrokePresent)
- thickness = jmax (thickness, strokeType.stroke.getStrokeThickness());
-
- for (int i = 0; i < points.size(); ++i)
- {
- Path segmentPath;
- PathPoint* const p = points.getUnchecked (i);
-
- switch (p->type)
- {
- case Path::Iterator::startNewSubPath:
- p->pos[0].getXY (lastX, lastY, area, layout);
- subPathStartX = lastX;
- subPathStartY = lastY;
- subpathStartIndex = i;
- break;
-
- case Path::Iterator::lineTo:
- p->pos[0].getXY (x1, y1, area, layout);
-
- segmentPath.addLineSegment (Line<float> ((float) lastX, (float) lastY, (float) x1, (float) y1), thickness);
- if (segmentPath.contains ((float) x, (float) y))
- return i;
-
- lastX = x1;
- lastY = y1;
- break;
-
- case Path::Iterator::quadraticTo:
- p->pos[0].getXY (x1, y1, area, layout);
- p->pos[1].getXY (x2, y2, area, layout);
-
- segmentPath.startNewSubPath ((float) lastX, (float) lastY);
- segmentPath.quadraticTo ((float) x1, (float) y1, (float) x2, (float) y2);
- PathStrokeType (thickness).createStrokedPath (segmentPath, segmentPath);
-
- if (segmentPath.contains ((float) x, (float) y))
- return i;
-
- lastX = x2;
- lastY = y2;
- break;
-
- case Path::Iterator::cubicTo:
- p->pos[0].getXY (x1, y1, area, layout);
- p->pos[1].getXY (x2, y2, area, layout);
- p->pos[2].getXY (x3, y3, area, layout);
-
- segmentPath.startNewSubPath ((float) lastX, (float) lastY);
- segmentPath.cubicTo ((float) x1, (float) y1, (float) x2, (float) y2, (float) x3, (float) y3);
- PathStrokeType (thickness).createStrokedPath (segmentPath, segmentPath);
-
- if (segmentPath.contains ((float) x, (float) y))
- return i;
-
- lastX = x3;
- lastY = y3;
- break;
-
- case Path::Iterator::closePath:
- segmentPath.addLineSegment (Line<float> ((float) lastX, (float) lastY, (float) subPathStartX, (float) subPathStartY), thickness);
- if (segmentPath.contains ((float) x, (float) y))
- return subpathStartIndex;
-
- lastX = subPathStartX;
- lastY = subPathStartY;
- break;
-
- default:
- jassertfalse; break;
- }
- }
-
- return -1;
- }
-
- //==============================================================================
- void PaintElementPath::movePoint (int index, int pointNumber,
- double newX, double newY,
- const Rectangle<int>& parentArea,
- const bool undoable)
- {
- if (PathPoint* const p = points [index])
- {
- PathPoint newPoint (*p);
- jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
- jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
-
- RelativePositionedRectangle& pr = newPoint.pos [pointNumber];
-
- double x, y, w, h;
- pr.getRectangleDouble (x, y, w, h, parentArea, getDocument()->getComponentLayout());
- pr.updateFrom (newX, newY, w, h, parentArea, getDocument()->getComponentLayout());
-
- if (undoable)
- {
- perform (new ChangePointAction (p, index, newPoint), "Move path point");
- }
- else
- {
- *p = newPoint;
- changed();
- }
- }
- }
-
- RelativePositionedRectangle PaintElementPath::getPoint (int index, int pointNumber) const
- {
- if (PathPoint* const p = points [index])
- {
- jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
- jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
-
- return p->pos [pointNumber];
- }
-
- jassertfalse;
- return RelativePositionedRectangle();
- }
-
- void PaintElementPath::setPoint (int index, int pointNumber, const RelativePositionedRectangle& newPos, const bool undoable)
- {
- if (PathPoint* const p = points [index])
- {
- PathPoint newPoint (*p);
-
- jassert (pointNumber < 3 || p->type == Path::Iterator::cubicTo);
- jassert (pointNumber < 2 || p->type == Path::Iterator::cubicTo || p->type == Path::Iterator::quadraticTo);
-
- if (newPoint.pos [pointNumber] != newPos)
- {
- newPoint.pos [pointNumber] = newPos;
-
- if (undoable)
- {
- perform (new ChangePointAction (p, index, newPoint), "Change path point position");
- }
- else
- {
- *p = newPoint;
- changed();
- }
- }
- }
- else
- {
- jassertfalse;
- }
- }
-
- //==============================================================================
- class PathPointTypeProperty : public ChoicePropertyComponent,
- public ChangeListener
- {
- public:
- PathPointTypeProperty (PaintElementPath* const owner_,
- const int index_)
- : ChoicePropertyComponent ("point type"),
- owner (owner_),
- index (index_)
- {
- choices.add ("Start of sub-path");
- choices.add ("Line");
- choices.add ("Quadratic");
- choices.add ("Cubic");
-
- owner->getDocument()->addChangeListener (this);
- }
-
- ~PathPointTypeProperty()
- {
- owner->getDocument()->removeChangeListener (this);
- }
-
- void setIndex (int newIndex)
- {
- Path::Iterator::PathElementType type = Path::Iterator::startNewSubPath;
-
- switch (newIndex)
- {
- case 0: type = Path::Iterator::startNewSubPath; break;
- case 1: type = Path::Iterator::lineTo; break;
- case 2: type = Path::Iterator::quadraticTo; break;
- case 3: type = Path::Iterator::cubicTo; break;
- default: jassertfalse; break;
- }
-
- const Rectangle<int> area (((PaintRoutineEditor*) owner->getParentComponent())->getComponentArea());
- owner->getPoint (index)->changePointType (type, area, true);
- }
-
- int getIndex() const
- {
- const PathPoint* const p = owner->getPoint (index);
- jassert (p != nullptr);
-
- switch (p->type)
- {
- case Path::Iterator::startNewSubPath: return 0;
- case Path::Iterator::lineTo: return 1;
- case Path::Iterator::quadraticTo: return 2;
- case Path::Iterator::cubicTo: return 3;
- case Path::Iterator::closePath: break;
- default: jassertfalse; break;
- }
-
- return 0;
- }
-
- void changeListenerCallback (ChangeBroadcaster*)
- {
- refresh();
- }
-
- private:
- PaintElementPath* const owner;
- const int index;
- };
-
- //==============================================================================
- class PathPointPositionProperty : public PositionPropertyBase
- {
- public:
- PathPointPositionProperty (PaintElementPath* const owner_,
- const int index_, const int pointNumber_,
- const String& name,
- ComponentPositionDimension dimension_)
- : PositionPropertyBase (owner_, name, dimension_, false, false,
- owner_->getDocument()->getComponentLayout()),
- owner (owner_),
- index (index_),
- pointNumber (pointNumber_)
- {
- owner->getDocument()->addChangeListener (this);
- }
-
- ~PathPointPositionProperty()
- {
- owner->getDocument()->removeChangeListener (this);
- }
-
- void setPosition (const RelativePositionedRectangle& newPos)
- {
- owner->setPoint (index, pointNumber, newPos, true);
- }
-
- RelativePositionedRectangle getPosition() const
- {
- return owner->getPoint (index, pointNumber);
- }
-
- private:
- PaintElementPath* const owner;
- const int index, pointNumber;
- };
-
- //==============================================================================
- class PathPointClosedProperty : public ChoicePropertyComponent,
- private ChangeListener
- {
- public:
- PathPointClosedProperty (PaintElementPath* const owner_, const int index_)
- : ChoicePropertyComponent ("openness"),
- owner (owner_),
- index (index_)
- {
- owner->getDocument()->addChangeListener (this);
-
- choices.add ("Subpath is closed");
- choices.add ("Subpath is open-ended");
- }
-
- ~PathPointClosedProperty()
- {
- owner->getDocument()->removeChangeListener (this);
- }
-
- void changeListenerCallback (ChangeBroadcaster*)
- {
- refresh();
- }
-
- void setIndex (int newIndex)
- {
- owner->setSubpathClosed (index, newIndex == 0, true);
- }
-
- int getIndex() const
- {
- return owner->isSubpathClosed (index) ? 0 : 1;
- }
-
- private:
- PaintElementPath* const owner;
- const int index;
- };
-
- //==============================================================================
- class AddNewPointProperty : public ButtonPropertyComponent
- {
- public:
- AddNewPointProperty (PaintElementPath* const owner_, const int index_)
- : ButtonPropertyComponent ("new point", false),
- owner (owner_),
- index (index_)
- {
- }
-
- void buttonClicked()
- {
- owner->addPoint (index, true);
- }
-
- String getButtonText() const { return "Add new point"; }
-
- private:
- PaintElementPath* const owner;
- const int index;
- };
-
-
- //==============================================================================
- PathPoint::PathPoint (PaintElementPath* const owner_)
- : owner (owner_)
- {
- }
-
- PathPoint::PathPoint (const PathPoint& other)
- : owner (other.owner),
- type (other.type)
- {
- pos [0] = other.pos [0];
- pos [1] = other.pos [1];
- pos [2] = other.pos [2];
- }
-
- PathPoint& PathPoint::operator= (const PathPoint& other)
- {
- owner = other.owner;
- type = other.type;
- pos [0] = other.pos [0];
- pos [1] = other.pos [1];
- pos [2] = other.pos [2];
- return *this;
- }
-
- PathPoint::~PathPoint()
- {
- }
-
- int PathPoint::getNumPoints() const
- {
- if (type == Path::Iterator::cubicTo) return 3;
- if (type == Path::Iterator::quadraticTo) return 2;
- if (type == Path::Iterator::closePath) return 0;
-
- return 1;
- }
-
- PathPoint PathPoint::withChangedPointType (const Path::Iterator::PathElementType newType,
- const Rectangle<int>& parentArea) const
- {
- PathPoint p (*this);
-
- if (newType != p.type)
- {
- int oldNumPoints = getNumPoints();
- p.type = newType;
- int numPoints = p.getNumPoints();
-
- if (numPoints != oldNumPoints)
- {
- double lastX, lastY;
- double x, y, w, h;
-
- p.pos [numPoints - 1] = p.pos [oldNumPoints - 1];
- p.pos [numPoints - 1].getRectangleDouble (x, y, w, h, parentArea, owner->getDocument()->getComponentLayout());
-
- const int index = owner->points.indexOf (this);
-
- if (PathPoint* lastPoint = owner->points [index - 1])
- {
- lastPoint->pos [lastPoint->getNumPoints() - 1]
- .getRectangleDouble (lastX, lastY, w, h, parentArea, owner->getDocument()->getComponentLayout());
- }
- else
- {
- jassertfalse;
- lastX = x;
- lastY = y;
- }
-
- for (int i = 0; i < numPoints - 1; ++i)
- {
- p.pos[i] = p.pos [numPoints - 1];
-
- p.pos[i].updateFrom (lastX + (x - lastX) * (i + 1) / numPoints,
- lastY + (y - lastY) * (i + 1) / numPoints,
- w, h,
- parentArea,
- owner->getDocument()->getComponentLayout());
- }
- }
- }
-
- return p;
- }
-
- void PathPoint::changePointType (const Path::Iterator::PathElementType newType,
- const Rectangle<int>& parentArea, const bool undoable)
- {
- if (newType != type)
- {
- if (undoable)
- {
- owner->perform (new ChangePointAction (this, withChangedPointType (newType, parentArea)),
- "Change path point type");
- }
- else
- {
- *this = withChangedPointType (newType, parentArea);
- owner->pointListChanged();
- }
- }
- }
-
- void PathPoint::getEditableProperties (Array<PropertyComponent*>& props)
- {
- const int index = owner->points.indexOf (this);
- jassert (index >= 0);
-
- switch (type)
- {
- case Path::Iterator::startNewSubPath:
- props.add (new PathPointPositionProperty (owner, index, 0, "x", PositionPropertyBase::componentX));
- props.add (new PathPointPositionProperty (owner, index, 0, "y", PositionPropertyBase::componentY));
-
- props.add (new PathPointClosedProperty (owner, index));
- props.add (new AddNewPointProperty (owner, index));
- break;
-
- case Path::Iterator::lineTo:
- props.add (new PathPointTypeProperty (owner, index));
- props.add (new PathPointPositionProperty (owner, index, 0, "x", PositionPropertyBase::componentX));
- props.add (new PathPointPositionProperty (owner, index, 0, "y", PositionPropertyBase::componentY));
- props.add (new AddNewPointProperty (owner, index));
- break;
-
- case Path::Iterator::quadraticTo:
- props.add (new PathPointTypeProperty (owner, index));
- props.add (new PathPointPositionProperty (owner, index, 0, "control pt x", PositionPropertyBase::componentX));
- props.add (new PathPointPositionProperty (owner, index, 0, "control pt y", PositionPropertyBase::componentY));
- props.add (new PathPointPositionProperty (owner, index, 1, "x", PositionPropertyBase::componentX));
- props.add (new PathPointPositionProperty (owner, index, 1, "y", PositionPropertyBase::componentY));
- props.add (new AddNewPointProperty (owner, index));
- break;
-
- case Path::Iterator::cubicTo:
- props.add (new PathPointTypeProperty (owner, index));
- props.add (new PathPointPositionProperty (owner, index, 0, "control pt1 x", PositionPropertyBase::componentX));
- props.add (new PathPointPositionProperty (owner, index, 0, "control pt1 y", PositionPropertyBase::componentY));
- props.add (new PathPointPositionProperty (owner, index, 1, "control pt2 x", PositionPropertyBase::componentX));
- props.add (new PathPointPositionProperty (owner, index, 1, "control pt2 y", PositionPropertyBase::componentY));
- props.add (new PathPointPositionProperty (owner, index, 2, "x", PositionPropertyBase::componentX));
- props.add (new PathPointPositionProperty (owner, index, 2, "y", PositionPropertyBase::componentY));
- props.add (new AddNewPointProperty (owner, index));
- break;
-
- case Path::Iterator::closePath:
- break;
-
- default:
- jassertfalse;
- break;
- }
- }
-
- void PathPoint::deleteFromPath()
- {
- owner->deletePoint (owner->points.indexOf (this), true);
- }
-
- //==============================================================================
- PathPointComponent::PathPointComponent (PaintElementPath* const path_,
- const int index_,
- const int pointNumber_)
- : ElementSiblingComponent (path_),
- path (path_),
- routine (path_->getOwner()),
- index (index_),
- pointNumber (pointNumber_),
- selected (false)
- {
- setSize (11, 11);
- setRepaintsOnMouseActivity (true);
-
- selected = routine->getSelectedPoints().isSelected (path_->points [index]);
- routine->getSelectedPoints().addChangeListener (this);
- }
-
- PathPointComponent::~PathPointComponent()
- {
- routine->getSelectedPoints().removeChangeListener (this);
- }
-
- void PathPointComponent::updatePosition()
- {
- const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
- jassert (getParentComponent() != nullptr);
-
- double x, y;
- path->getPoint (index, pointNumber, x, y, area);
-
- setCentrePosition (roundToInt (x),
- roundToInt (y));
- }
-
- void PathPointComponent::showPopupMenu()
- {
- }
-
- void PathPointComponent::paint (Graphics& g)
- {
- if (isMouseOverOrDragging())
- g.fillAll (Colours::red);
-
- if (selected)
- {
- g.setColour (Colours::red);
- g.drawRect (getLocalBounds());
- }
-
- g.setColour (Colours::white);
- g.fillRect (getWidth() / 2 - 3, getHeight() / 2 - 3, 7, 7);
-
- g.setColour (Colours::black);
-
- if (pointNumber < path->getPoint (index)->getNumPoints() - 1)
- g.drawRect (getWidth() / 2 - 2, getHeight() / 2 - 2, 5, 5);
- else
- g.fillRect (getWidth() / 2 - 2, getHeight() / 2 - 2, 5, 5);
- }
-
- void PathPointComponent::mouseDown (const MouseEvent& e)
- {
- dragging = false;
-
- if (e.mods.isPopupMenu())
- {
- showPopupMenu();
- return; // this may be deleted now..
- }
-
- dragX = getX() + getWidth() / 2;
- dragY = getY() + getHeight() / 2;
-
- mouseDownSelectStatus = routine->getSelectedPoints().addToSelectionOnMouseDown (path->points [index], e.mods);
-
- owner->getDocument()->beginTransaction();
- }
-
- void PathPointComponent::mouseDrag (const MouseEvent& e)
- {
- if (! e.mods.isPopupMenu())
- {
- if (selected && ! dragging)
- dragging = e.mouseWasDraggedSinceMouseDown();
-
- if (dragging)
- {
- const Rectangle<int> area (((PaintRoutineEditor*) getParentComponent())->getComponentArea());
- int x = dragX + e.getDistanceFromDragStartX() - area.getX();
- int y = dragY + e.getDistanceFromDragStartY() - area.getY();
-
- if (JucerDocument* const document = owner->getDocument())
- {
- x = document->snapPosition (x);
- y = document->snapPosition (y);
- }
-
- owner->getDocument()->getUndoManager().undoCurrentTransactionOnly();
- path->movePoint (index, pointNumber, x + area.getX(), y + area.getY(), area, true);
- }
- }
- }
-
- void PathPointComponent::mouseUp (const MouseEvent& e)
- {
- routine->getSelectedPoints().addToSelectionOnMouseUp (path->points [index],
- e.mods, dragging,
- mouseDownSelectStatus);
- }
-
- void PathPointComponent::changeListenerCallback (ChangeBroadcaster* source)
- {
- ElementSiblingComponent::changeListenerCallback (source);
-
- const bool nowSelected = routine->getSelectedPoints().isSelected (path->points [index]);
-
- if (nowSelected != selected)
- {
- selected = nowSelected;
- repaint();
-
- if (Component* parent = getParentComponent())
- parent->repaint();
- }
- }
|