| @@ -485,9 +485,7 @@ public: | |||
| if (svgDrawable != nullptr) | |||
| { | |||
| // to make our icon the right size, we'll set its bounding box to the size and position that we want. | |||
| svgDrawable->setBoundingBox (RelativeParallelogram (Point<float> (-100, -100), | |||
| Point<float> (100, -100), | |||
| Point<float> (-100, 100))); | |||
| svgDrawable->setBoundingBox ({ -100.0f, -100.0f, 200.0f, 200.0f }); | |||
| } | |||
| } | |||
| } | |||
| @@ -43,18 +43,18 @@ public: | |||
| if (drawable != nullptr) | |||
| { | |||
| Rectangle<float> contentBounds (drawable->getDrawableBounds()); | |||
| auto contentBounds = drawable->getDrawableBounds(); | |||
| if (DrawableComposite* dc = dynamic_cast<DrawableComposite*> (drawable.get())) | |||
| if (auto* dc = dynamic_cast<DrawableComposite*> (drawable.get())) | |||
| { | |||
| Rectangle<float> r (dc->getContentArea().resolve (nullptr)); | |||
| auto r = dc->getContentArea(); | |||
| if (! r.isEmpty()) | |||
| contentBounds = r; | |||
| } | |||
| Rectangle<float> area = RectanglePlacement (RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize) | |||
| .appliedTo (contentBounds, Rectangle<float> (4.0f, 22.0f, getWidth() - 8.0f, getHeight() - 26.0f)); | |||
| auto area = RectanglePlacement (RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize) | |||
| .appliedTo (contentBounds, Rectangle<float> (4.0f, 22.0f, getWidth() - 8.0f, getHeight() - 26.0f)); | |||
| Path p; | |||
| p.addRectangle (area); | |||
| @@ -2210,8 +2210,7 @@ public: | |||
| /** Attempts to set the component's position to the given rectangle. | |||
| Unlike simply calling Component::setBounds(), this may involve the positioner | |||
| being smart enough to adjust itself to fit the new bounds, e.g. a RelativeRectangle's | |||
| positioner may try to reverse the expressions used to make them fit these new coordinates. | |||
| being smart enough to adjust itself to fit the new bounds. | |||
| */ | |||
| virtual void applyNewBounds (const Rectangle<int>& newBounds) = 0; | |||
| @@ -209,74 +209,4 @@ Drawable* Drawable::createFromImageFile (const File& file) | |||
| return fin.openedOk() ? createFromImageDataStream (fin) : nullptr; | |||
| } | |||
| //============================================================================== | |||
| template <class DrawableClass> | |||
| struct DrawableTypeHandler : public ComponentBuilder::TypeHandler | |||
| { | |||
| DrawableTypeHandler() : ComponentBuilder::TypeHandler (DrawableClass::valueTreeType) | |||
| { | |||
| } | |||
| Component* addNewComponentFromState (const ValueTree& state, Component* parent) | |||
| { | |||
| auto* d = new DrawableClass(); | |||
| if (parent != nullptr) | |||
| parent->addAndMakeVisible (d); | |||
| updateComponentFromState (d, state); | |||
| return d; | |||
| } | |||
| void updateComponentFromState (Component* component, const ValueTree& state) | |||
| { | |||
| if (auto* d = dynamic_cast<DrawableClass*> (component)) | |||
| d->refreshFromValueTree (state, *this->getBuilder()); | |||
| else | |||
| jassertfalse; | |||
| } | |||
| }; | |||
| void Drawable::registerDrawableTypeHandlers (ComponentBuilder& builder) | |||
| { | |||
| builder.registerTypeHandler (new DrawableTypeHandler<DrawablePath>()); | |||
| builder.registerTypeHandler (new DrawableTypeHandler<DrawableComposite>()); | |||
| builder.registerTypeHandler (new DrawableTypeHandler<DrawableRectangle>()); | |||
| builder.registerTypeHandler (new DrawableTypeHandler<DrawableImage>()); | |||
| builder.registerTypeHandler (new DrawableTypeHandler<DrawableText>()); | |||
| } | |||
| Drawable* Drawable::createFromValueTree (const ValueTree& tree, ComponentBuilder::ImageProvider* imageProvider) | |||
| { | |||
| ComponentBuilder builder (tree); | |||
| builder.setImageProvider (imageProvider); | |||
| registerDrawableTypeHandlers (builder); | |||
| ScopedPointer<Component> comp (builder.createComponent()); | |||
| auto* d = dynamic_cast<Drawable*> (static_cast<Component*> (comp)); | |||
| if (d != nullptr) | |||
| comp.release(); | |||
| return d; | |||
| } | |||
| //============================================================================== | |||
| Drawable::ValueTreeWrapperBase::ValueTreeWrapperBase (const ValueTree& s) : state (s) | |||
| { | |||
| } | |||
| String Drawable::ValueTreeWrapperBase::getID() const | |||
| { | |||
| return state [ComponentBuilder::idProperty]; | |||
| } | |||
| void Drawable::ValueTreeWrapperBase::setID (const String& newID) | |||
| { | |||
| if (newID.isEmpty()) | |||
| state.removeProperty (ComponentBuilder::idProperty, nullptr); | |||
| else | |||
| state.setProperty (ComponentBuilder::idProperty, newID, nullptr); | |||
| } | |||
| } // namespace juce | |||
| @@ -33,8 +33,7 @@ namespace juce | |||
| @see DrawableComposite, DrawableImage, DrawablePath, DrawableText | |||
| */ | |||
| class JUCE_API Drawable : public Component, | |||
| public MarkerList::MarkerListHolder | |||
| class JUCE_API Drawable : public Component | |||
| { | |||
| protected: | |||
| //============================================================================== | |||
| @@ -177,22 +176,6 @@ public: | |||
| static Path parseSVGPath (const String& svgPath); | |||
| //============================================================================== | |||
| /** Tries to create a Drawable from a previously-saved ValueTree. | |||
| The ValueTree must have been created by the createValueTree() method. | |||
| If there are any images used within the drawable, you'll need to provide a valid | |||
| ImageProvider object that can be used to retrieve these images from whatever type | |||
| of identifier is used to represent them. | |||
| Internally, this uses a ComponentBuilder, and registerDrawableTypeHandlers(). | |||
| */ | |||
| static Drawable* createFromValueTree (const ValueTree& tree, ComponentBuilder::ImageProvider* imageProvider); | |||
| /** Creates a ValueTree to represent this Drawable. | |||
| The ValueTree that is returned can be turned back into a Drawable with createFromValueTree(). | |||
| If there are any images used in this drawable, you'll need to provide a valid ImageProvider | |||
| object that can be used to create storable representations of them. | |||
| */ | |||
| virtual ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const = 0; | |||
| /** Returns the area that this drawble covers. | |||
| The result is expressed in this drawable's own coordinate space, and does not take | |||
| into account any transforms that may be applied to the component. | |||
| @@ -204,30 +187,6 @@ public: | |||
| */ | |||
| virtual bool replaceColour (Colour originalColour, Colour replacementColour); | |||
| //============================================================================== | |||
| /** Internal class used to manage ValueTrees that represent Drawables. */ | |||
| class ValueTreeWrapperBase | |||
| { | |||
| public: | |||
| ValueTreeWrapperBase (const ValueTree& state); | |||
| ValueTree& getState() noexcept { return state; } | |||
| String getID() const; | |||
| void setID (const String& newID); | |||
| ValueTree state; | |||
| }; | |||
| //============================================================================== | |||
| /** Registers a set of ComponentBuilder::TypeHandler objects that can be used to | |||
| load all the different Drawable types from a saved state. | |||
| @see ComponentBuilder::registerTypeHandler() | |||
| */ | |||
| static void registerDrawableTypeHandlers (ComponentBuilder& componentBuilder); | |||
| MarkerList* getMarkers (bool) override { return nullptr; } | |||
| protected: | |||
| //============================================================================== | |||
| friend class DrawableComposite; | |||
| @@ -245,42 +204,9 @@ protected: | |||
| Point<int> originRelativeToComponent; | |||
| ScopedPointer<Drawable> drawableClipPath; | |||
| #ifndef DOXYGEN | |||
| /** Internal utility class used by Drawables. */ | |||
| template <class DrawableType> | |||
| class Positioner : public RelativeCoordinatePositionerBase | |||
| { | |||
| public: | |||
| Positioner (DrawableType& c) | |||
| : RelativeCoordinatePositionerBase (c), | |||
| owner (c) | |||
| {} | |||
| bool registerCoordinates() override { return owner.registerCoordinates (*this); } | |||
| void applyToComponentBounds() override | |||
| { | |||
| ComponentScope scope (getComponent()); | |||
| owner.recalculateCoordinates (&scope); | |||
| } | |||
| void applyNewBounds (const Rectangle<int>&) override | |||
| { | |||
| jassertfalse; // drawables can't be resized directly! | |||
| } | |||
| private: | |||
| DrawableType& owner; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Positioner) | |||
| }; | |||
| Drawable (const Drawable&); | |||
| #endif | |||
| private: | |||
| void nonConstDraw (Graphics&, float opacity, const AffineTransform&); | |||
| Drawable (const Drawable&); | |||
| Drawable& operator= (const Drawable&); | |||
| JUCE_LEAK_DETECTOR (Drawable) | |||
| }; | |||
| @@ -28,16 +28,15 @@ namespace juce | |||
| { | |||
| DrawableComposite::DrawableComposite() | |||
| : bounds (Point<float>(), Point<float> (100.0f, 0.0f), Point<float> (0.0f, 100.0f)) | |||
| : bounds ({ 0.0f, 0.0f, 100.0f, 100.0f }) | |||
| { | |||
| setContentArea (RelativeRectangle (Rectangle<float> (0.0f, 0.0f, 100.0f, 100.0f))); | |||
| setContentArea ({ 0.0f, 0.0f, 100.0f, 100.0f }); | |||
| } | |||
| DrawableComposite::DrawableComposite (const DrawableComposite& other) | |||
| : Drawable (other), | |||
| bounds (other.bounds), | |||
| markersX (other.markersX), | |||
| markersY (other.markersY) | |||
| contentArea (other.contentArea) | |||
| { | |||
| for (auto* c : other.getChildren()) | |||
| if (auto* d = dynamic_cast<const Drawable*> (c)) | |||
| @@ -67,87 +66,44 @@ Rectangle<float> DrawableComposite::getDrawableBounds() const | |||
| return r; | |||
| } | |||
| MarkerList* DrawableComposite::getMarkers (bool xAxis) | |||
| void DrawableComposite::setContentArea (Rectangle<float> newArea) | |||
| { | |||
| return xAxis ? &markersX : &markersY; | |||
| contentArea = newArea; | |||
| } | |||
| RelativeRectangle DrawableComposite::getContentArea() const | |||
| void DrawableComposite::setBoundingBox (Rectangle<float> newBounds) | |||
| { | |||
| jassert (markersX.getNumMarkers() >= 2 && markersX.getMarker (0)->name == contentLeftMarkerName && markersX.getMarker (1)->name == contentRightMarkerName); | |||
| jassert (markersY.getNumMarkers() >= 2 && markersY.getMarker (0)->name == contentTopMarkerName && markersY.getMarker (1)->name == contentBottomMarkerName); | |||
| return RelativeRectangle (markersX.getMarker(0)->position, markersX.getMarker(1)->position, | |||
| markersY.getMarker(0)->position, markersY.getMarker(1)->position); | |||
| setBoundingBox (Parallelogram<float> (newBounds)); | |||
| } | |||
| void DrawableComposite::setContentArea (const RelativeRectangle& newArea) | |||
| { | |||
| markersX.setMarker (contentLeftMarkerName, newArea.left); | |||
| markersX.setMarker (contentRightMarkerName, newArea.right); | |||
| markersY.setMarker (contentTopMarkerName, newArea.top); | |||
| markersY.setMarker (contentBottomMarkerName, newArea.bottom); | |||
| } | |||
| void DrawableComposite::setBoundingBox (const RelativeParallelogram& newBounds) | |||
| void DrawableComposite::setBoundingBox (Parallelogram<float> newBounds) | |||
| { | |||
| if (bounds != newBounds) | |||
| { | |||
| bounds = newBounds; | |||
| if (bounds.isDynamic()) | |||
| { | |||
| auto p = new Drawable::Positioner<DrawableComposite> (*this); | |||
| setPositioner (p); | |||
| p->apply(); | |||
| } | |||
| else | |||
| { | |||
| setPositioner (nullptr); | |||
| recalculateCoordinates (nullptr); | |||
| } | |||
| auto t = AffineTransform::fromTargetPoints (contentArea.getTopLeft(), bounds.topLeft, | |||
| contentArea.getTopRight(), bounds.topRight, | |||
| contentArea.getBottomLeft(), bounds.bottomLeft); | |||
| if (t.isSingularity()) | |||
| t = {}; | |||
| setTransform (t); | |||
| } | |||
| } | |||
| void DrawableComposite::resetBoundingBoxToContentArea() | |||
| { | |||
| const RelativeRectangle content (getContentArea()); | |||
| setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top), | |||
| RelativePoint (content.right, content.top), | |||
| RelativePoint (content.left, content.bottom))); | |||
| setBoundingBox (contentArea); | |||
| } | |||
| void DrawableComposite::resetContentAreaAndBoundingBoxToFitChildren() | |||
| { | |||
| setContentArea (RelativeRectangle (getDrawableBounds())); | |||
| setContentArea (getDrawableBounds()); | |||
| resetBoundingBoxToContentArea(); | |||
| } | |||
| bool DrawableComposite::registerCoordinates (RelativeCoordinatePositionerBase& pos) | |||
| { | |||
| bool ok = pos.addPoint (bounds.topLeft); | |||
| ok = pos.addPoint (bounds.topRight) && ok; | |||
| return pos.addPoint (bounds.bottomLeft) && ok; | |||
| } | |||
| void DrawableComposite::recalculateCoordinates (Expression::Scope* scope) | |||
| { | |||
| Point<float> resolved[3]; | |||
| bounds.resolveThreePoints (resolved, scope); | |||
| auto content = getContentArea().resolve (scope); | |||
| auto t = AffineTransform::fromTargetPoints (content.getX(), content.getY(), resolved[0].x, resolved[0].y, | |||
| content.getRight(), content.getY(), resolved[1].x, resolved[1].y, | |||
| content.getX(), content.getBottom(), resolved[2].x, resolved[2].y); | |||
| if (t.isSingularity()) | |||
| t = AffineTransform(); | |||
| setTransform (t); | |||
| } | |||
| void DrawableComposite::parentHierarchyChanged() | |||
| { | |||
| if (auto* parent = getParent()) | |||
| @@ -194,131 +150,6 @@ void DrawableComposite::updateBoundsToFitChildren() | |||
| } | |||
| //============================================================================== | |||
| 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 Identifier DrawableComposite::valueTreeType ("Group"); | |||
| const Identifier DrawableComposite::ValueTreeWrapper::topLeft ("topLeft"); | |||
| const Identifier DrawableComposite::ValueTreeWrapper::topRight ("topRight"); | |||
| const Identifier DrawableComposite::ValueTreeWrapper::bottomLeft ("bottomLeft"); | |||
| const Identifier DrawableComposite::ValueTreeWrapper::childGroupTag ("Drawables"); | |||
| const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagX ("MarkersX"); | |||
| const Identifier DrawableComposite::ValueTreeWrapper::markerGroupTagY ("MarkersY"); | |||
| //============================================================================== | |||
| DrawableComposite::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
| : ValueTreeWrapperBase (state_) | |||
| { | |||
| jassert (state.hasType (valueTreeType)); | |||
| } | |||
| ValueTree DrawableComposite::ValueTreeWrapper::getChildList() const | |||
| { | |||
| return state.getChildWithName (childGroupTag); | |||
| } | |||
| ValueTree DrawableComposite::ValueTreeWrapper::getChildListCreating (UndoManager* undoManager) | |||
| { | |||
| return state.getOrCreateChildWithName (childGroupTag, undoManager); | |||
| } | |||
| RelativeParallelogram DrawableComposite::ValueTreeWrapper::getBoundingBox() const | |||
| { | |||
| return RelativeParallelogram (state.getProperty (topLeft, "0, 0"), | |||
| state.getProperty (topRight, "100, 0"), | |||
| state.getProperty (bottomLeft, "0, 100")); | |||
| } | |||
| void DrawableComposite::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); | |||
| state.setProperty (topRight, newBounds.topRight.toString(), undoManager); | |||
| state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); | |||
| } | |||
| void DrawableComposite::ValueTreeWrapper::resetBoundingBoxToContentArea (UndoManager* undoManager) | |||
| { | |||
| const RelativeRectangle content (getContentArea()); | |||
| setBoundingBox (RelativeParallelogram (RelativePoint (content.left, content.top), | |||
| RelativePoint (content.right, content.top), | |||
| RelativePoint (content.left, content.bottom)), undoManager); | |||
| } | |||
| RelativeRectangle DrawableComposite::ValueTreeWrapper::getContentArea() const | |||
| { | |||
| MarkerList::ValueTreeWrapper marksX (getMarkerList (true)); | |||
| MarkerList::ValueTreeWrapper marksY (getMarkerList (false)); | |||
| return RelativeRectangle (marksX.getMarker (marksX.getMarkerState (0)).position, | |||
| marksX.getMarker (marksX.getMarkerState (1)).position, | |||
| marksY.getMarker (marksY.getMarkerState (0)).position, | |||
| marksY.getMarker (marksY.getMarkerState (1)).position); | |||
| } | |||
| void DrawableComposite::ValueTreeWrapper::setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager) | |||
| { | |||
| MarkerList::ValueTreeWrapper marksX (getMarkerListCreating (true, nullptr)); | |||
| MarkerList::ValueTreeWrapper marksY (getMarkerListCreating (false, nullptr)); | |||
| marksX.setMarker (MarkerList::Marker (contentLeftMarkerName, newArea.left), undoManager); | |||
| marksX.setMarker (MarkerList::Marker (contentRightMarkerName, newArea.right), undoManager); | |||
| marksY.setMarker (MarkerList::Marker (contentTopMarkerName, newArea.top), undoManager); | |||
| marksY.setMarker (MarkerList::Marker (contentBottomMarkerName, newArea.bottom), undoManager); | |||
| } | |||
| MarkerList::ValueTreeWrapper DrawableComposite::ValueTreeWrapper::getMarkerList (bool xAxis) const | |||
| { | |||
| return state.getChildWithName (xAxis ? markerGroupTagX : markerGroupTagY); | |||
| } | |||
| MarkerList::ValueTreeWrapper DrawableComposite::ValueTreeWrapper::getMarkerListCreating (bool xAxis, UndoManager* undoManager) | |||
| { | |||
| return state.getOrCreateChildWithName (xAxis ? markerGroupTagX : markerGroupTagY, undoManager); | |||
| } | |||
| //============================================================================== | |||
| void DrawableComposite::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder) | |||
| { | |||
| const ValueTreeWrapper wrapper (tree); | |||
| setComponentID (wrapper.getID()); | |||
| wrapper.getMarkerList (true).applyTo (markersX); | |||
| wrapper.getMarkerList (false).applyTo (markersY); | |||
| setBoundingBox (wrapper.getBoundingBox()); | |||
| builder.updateChildComponents (*this, wrapper.getChildList()); | |||
| } | |||
| ValueTree DrawableComposite::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const | |||
| { | |||
| ValueTree tree (valueTreeType); | |||
| ValueTreeWrapper v (tree); | |||
| v.setID (getComponentID()); | |||
| v.setBoundingBox (bounds, nullptr); | |||
| ValueTree childList (v.getChildListCreating (nullptr)); | |||
| for (auto* c : getChildren()) | |||
| { | |||
| auto* d = dynamic_cast<const Drawable*> (c); | |||
| jassert (d != nullptr); // You can't save a mix of Drawables and normal components! | |||
| childList.appendChild (d->createValueTree (imageProvider), nullptr); | |||
| } | |||
| v.getMarkerListCreating (true, nullptr).readFrom (markersX, nullptr); | |||
| v.getMarkerListCreating (false, nullptr).readFrom (markersY, nullptr); | |||
| return tree; | |||
| } | |||
| Path DrawableComposite::getOutlineAsPath() const | |||
| { | |||
| Path p; | |||
| @@ -55,12 +55,17 @@ public: | |||
| /** Sets the parallelogram that defines the target position of the content rectangle when the drawable is rendered. | |||
| @see setContentArea | |||
| */ | |||
| void setBoundingBox (const RelativeParallelogram& newBoundingBox); | |||
| void setBoundingBox (Parallelogram<float> newBoundingBox); | |||
| /** Sets the rectangle that defines the target position of the content rectangle when the drawable is rendered. | |||
| @see setContentArea | |||
| */ | |||
| void setBoundingBox (Rectangle<float> newBoundingBox); | |||
| /** Returns the parallelogram that defines the target position of the content rectangle when the drawable is rendered. | |||
| @see setBoundingBox | |||
| */ | |||
| const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; } | |||
| Parallelogram<float> getBoundingBox() const noexcept { return bounds; } | |||
| /** Changes the bounding box transform to match the content area, so that any sub-items will | |||
| be drawn at their untransformed positions. | |||
| @@ -68,44 +73,24 @@ public: | |||
| void resetBoundingBoxToContentArea(); | |||
| /** 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 contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName | |||
| */ | |||
| RelativeRectangle getContentArea() const; | |||
| Rectangle<float> getContentArea() const noexcept { return contentArea; } | |||
| /** 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 setBoundingBox, contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName | |||
| */ | |||
| void setContentArea (const RelativeRectangle& newArea); | |||
| void setContentArea (Rectangle<float> newArea); | |||
| /** Resets the content area and the bounding transform to fit around the area occupied | |||
| by the child components (ignoring any markers). | |||
| by the child components. | |||
| */ | |||
| void resetContentAreaAndBoundingBoxToFitChildren(); | |||
| //============================================================================== | |||
| /** 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 */ | |||
| Drawable* createCopy() const override; | |||
| /** @internal */ | |||
| void refreshFromValueTree (const ValueTree&, ComponentBuilder&); | |||
| /** @internal */ | |||
| ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const override; | |||
| /** @internal */ | |||
| static const Identifier valueTreeType; | |||
| /** @internal */ | |||
| Rectangle<float> getDrawableBounds() const override; | |||
| /** @internal */ | |||
| void childBoundsChanged (Component*) override; | |||
| @@ -114,46 +99,14 @@ public: | |||
| /** @internal */ | |||
| void parentHierarchyChanged() override; | |||
| /** @internal */ | |||
| MarkerList* getMarkers (bool xAxis) override; | |||
| /** @internal */ | |||
| Path getOutlineAsPath() const override; | |||
| //============================================================================== | |||
| /** Internally-used class for wrapping a DrawableComposite's state into a ValueTree. */ | |||
| class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase | |||
| { | |||
| public: | |||
| ValueTreeWrapper (const ValueTree& state); | |||
| ValueTree getChildList() const; | |||
| ValueTree getChildListCreating (UndoManager* undoManager); | |||
| RelativeParallelogram getBoundingBox() const; | |||
| void setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager); | |||
| void resetBoundingBoxToContentArea (UndoManager* undoManager); | |||
| RelativeRectangle getContentArea() const; | |||
| void setContentArea (const RelativeRectangle& newArea, UndoManager* undoManager); | |||
| MarkerList::ValueTreeWrapper getMarkerList (bool xAxis) const; | |||
| MarkerList::ValueTreeWrapper getMarkerListCreating (bool xAxis, UndoManager* undoManager); | |||
| static const Identifier topLeft, topRight, bottomLeft; | |||
| private: | |||
| static const Identifier childGroupTag, markerGroupTagX, markerGroupTagY; | |||
| }; | |||
| private: | |||
| //============================================================================== | |||
| RelativeParallelogram bounds; | |||
| MarkerList markersX, markersY; | |||
| Parallelogram<float> bounds; | |||
| Rectangle<float> contentArea; | |||
| bool updateBoundsReentrant = false; | |||
| friend class Drawable::Positioner<DrawableComposite>; | |||
| bool registerCoordinates (RelativeCoordinatePositionerBase&); | |||
| void recalculateCoordinates (Expression::Scope*); | |||
| void updateBoundsToFitChildren(); | |||
| DrawableComposite& operator= (const DrawableComposite&); | |||
| @@ -27,12 +27,8 @@ | |||
| namespace juce | |||
| { | |||
| DrawableImage::DrawableImage() | |||
| : opacity (1.0f), | |||
| overlayColour (0x00000000) | |||
| DrawableImage::DrawableImage() : bounds ({ 0.0f, 0.0f, 1.0f, 1.0f }) | |||
| { | |||
| bounds.topRight = RelativePoint (Point<float> (1.0f, 0.0f)); | |||
| bounds.bottomLeft = RelativePoint (Point<float> (0.0f, 1.0f)); | |||
| } | |||
| DrawableImage::DrawableImage (const DrawableImage& other) | |||
| @@ -49,18 +45,21 @@ DrawableImage::~DrawableImage() | |||
| { | |||
| } | |||
| Drawable* DrawableImage::createCopy() const | |||
| { | |||
| return new DrawableImage (*this); | |||
| } | |||
| //============================================================================== | |||
| void DrawableImage::setImage (const Image& imageToUse) | |||
| { | |||
| image = imageToUse; | |||
| setBounds (imageToUse.getBounds()); | |||
| bounds.topLeft = RelativePoint (Point<float> (0.0f, 0.0f)); | |||
| bounds.topRight = RelativePoint (Point<float> ((float) image.getWidth(), 0.0f)); | |||
| bounds.bottomLeft = RelativePoint (Point<float> (0.0f, (float) image.getHeight())); | |||
| recalculateCoordinates (nullptr); | |||
| repaint(); | |||
| if (image != imageToUse) | |||
| { | |||
| image = imageToUse; | |||
| setBounds (image.getBounds()); | |||
| setBoundingBox (image.getBounds().toFloat()); | |||
| repaint(); | |||
| } | |||
| } | |||
| void DrawableImage::setOpacity (const float newOpacity) | |||
| @@ -73,52 +72,31 @@ void DrawableImage::setOverlayColour (Colour newOverlayColour) | |||
| overlayColour = newOverlayColour; | |||
| } | |||
| void DrawableImage::setBoundingBox (const RelativeParallelogram& newBounds) | |||
| { | |||
| if (bounds != newBounds) | |||
| { | |||
| bounds = newBounds; | |||
| if (bounds.isDynamic()) | |||
| { | |||
| Drawable::Positioner<DrawableImage>* const p = new Drawable::Positioner<DrawableImage> (*this); | |||
| setPositioner (p); | |||
| p->apply(); | |||
| } | |||
| else | |||
| { | |||
| setPositioner (nullptr); | |||
| recalculateCoordinates (nullptr); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| bool DrawableImage::registerCoordinates (RelativeCoordinatePositionerBase& pos) | |||
| void DrawableImage::setBoundingBox (Rectangle<float> newBounds) | |||
| { | |||
| bool ok = pos.addPoint (bounds.topLeft); | |||
| ok = pos.addPoint (bounds.topRight) && ok; | |||
| return pos.addPoint (bounds.bottomLeft) && ok; | |||
| setBoundingBox (Parallelogram<float> (newBounds)); | |||
| } | |||
| void DrawableImage::recalculateCoordinates (Expression::Scope* scope) | |||
| void DrawableImage::setBoundingBox (Parallelogram<float> newBounds) | |||
| { | |||
| if (image.isValid()) | |||
| if (bounds != newBounds) | |||
| { | |||
| Point<float> resolved[3]; | |||
| bounds.resolveThreePoints (resolved, scope); | |||
| bounds = newBounds; | |||
| const Point<float> tr (resolved[0] + (resolved[1] - resolved[0]) / (float) image.getWidth()); | |||
| const Point<float> bl (resolved[0] + (resolved[2] - resolved[0]) / (float) image.getHeight()); | |||
| if (image.isValid()) | |||
| { | |||
| auto tr = bounds.topLeft + (bounds.topRight - bounds.topLeft) / (float) image.getWidth(); | |||
| auto bl = bounds.topLeft + (bounds.bottomLeft - bounds.topLeft) / (float) image.getHeight(); | |||
| AffineTransform t (AffineTransform::fromTargetPoints (resolved[0].x, resolved[0].y, | |||
| tr.x, tr.y, | |||
| bl.x, bl.y)); | |||
| auto t = AffineTransform::fromTargetPoints (bounds.topLeft.x, bounds.topLeft.y, | |||
| tr.x, tr.y, | |||
| bl.x, bl.y); | |||
| if (t.isSingularity()) | |||
| t = AffineTransform(); | |||
| if (t.isSingularity()) | |||
| t = {}; | |||
| setTransform (t); | |||
| setTransform (t); | |||
| } | |||
| } | |||
| } | |||
| @@ -151,149 +129,6 @@ bool DrawableImage::hitTest (int x, int y) | |||
| return Drawable::hitTest (x, y) && image.isValid() && image.getPixelAt (x, y).getAlpha() >= 127; | |||
| } | |||
| Drawable* DrawableImage::createCopy() const | |||
| { | |||
| return new DrawableImage (*this); | |||
| } | |||
| //============================================================================== | |||
| const Identifier DrawableImage::valueTreeType ("Image"); | |||
| const Identifier DrawableImage::ValueTreeWrapper::opacity ("opacity"); | |||
| const Identifier DrawableImage::ValueTreeWrapper::overlay ("overlay"); | |||
| const Identifier DrawableImage::ValueTreeWrapper::image ("image"); | |||
| const Identifier DrawableImage::ValueTreeWrapper::topLeft ("topLeft"); | |||
| const Identifier DrawableImage::ValueTreeWrapper::topRight ("topRight"); | |||
| const Identifier DrawableImage::ValueTreeWrapper::bottomLeft ("bottomLeft"); | |||
| //============================================================================== | |||
| DrawableImage::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
| : ValueTreeWrapperBase (state_) | |||
| { | |||
| jassert (state.hasType (valueTreeType)); | |||
| } | |||
| var DrawableImage::ValueTreeWrapper::getImageIdentifier() const | |||
| { | |||
| return state [image]; | |||
| } | |||
| Value DrawableImage::ValueTreeWrapper::getImageIdentifierValue (UndoManager* undoManager) | |||
| { | |||
| return state.getPropertyAsValue (image, undoManager); | |||
| } | |||
| void DrawableImage::ValueTreeWrapper::setImageIdentifier (const var& newIdentifier, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (image, newIdentifier, undoManager); | |||
| } | |||
| float DrawableImage::ValueTreeWrapper::getOpacity() const | |||
| { | |||
| return (float) state.getProperty (opacity, 1.0); | |||
| } | |||
| Value DrawableImage::ValueTreeWrapper::getOpacityValue (UndoManager* undoManager) | |||
| { | |||
| if (! state.hasProperty (opacity)) | |||
| state.setProperty (opacity, 1.0, undoManager); | |||
| return state.getPropertyAsValue (opacity, undoManager); | |||
| } | |||
| void DrawableImage::ValueTreeWrapper::setOpacity (float newOpacity, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (opacity, newOpacity, undoManager); | |||
| } | |||
| Colour DrawableImage::ValueTreeWrapper::getOverlayColour() const | |||
| { | |||
| return Colour::fromString (state [overlay].toString()); | |||
| } | |||
| void DrawableImage::ValueTreeWrapper::setOverlayColour (Colour newColour, UndoManager* undoManager) | |||
| { | |||
| if (newColour.isTransparent()) | |||
| state.removeProperty (overlay, undoManager); | |||
| else | |||
| state.setProperty (overlay, String::toHexString ((int) newColour.getARGB()), undoManager); | |||
| } | |||
| Value DrawableImage::ValueTreeWrapper::getOverlayColourValue (UndoManager* undoManager) | |||
| { | |||
| return state.getPropertyAsValue (overlay, undoManager); | |||
| } | |||
| RelativeParallelogram DrawableImage::ValueTreeWrapper::getBoundingBox() const | |||
| { | |||
| return RelativeParallelogram (state.getProperty (topLeft, "0, 0"), | |||
| state.getProperty (topRight, "100, 0"), | |||
| state.getProperty (bottomLeft, "0, 100")); | |||
| } | |||
| void DrawableImage::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); | |||
| state.setProperty (topRight, newBounds.topRight.toString(), undoManager); | |||
| state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); | |||
| } | |||
| //============================================================================== | |||
| void DrawableImage::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder) | |||
| { | |||
| const ValueTreeWrapper controller (tree); | |||
| setComponentID (controller.getID()); | |||
| const float newOpacity = controller.getOpacity(); | |||
| const Colour newOverlayColour (controller.getOverlayColour()); | |||
| Image newImage; | |||
| const var imageIdentifier (controller.getImageIdentifier()); | |||
| jassert (builder.getImageProvider() != 0 || imageIdentifier.isVoid()); // if you're using images, you need to provide something that can load and save them! | |||
| if (builder.getImageProvider() != nullptr) | |||
| newImage = builder.getImageProvider()->getImageForIdentifier (imageIdentifier); | |||
| const RelativeParallelogram newBounds (controller.getBoundingBox()); | |||
| if (bounds != newBounds || newOpacity != opacity | |||
| || overlayColour != newOverlayColour || image != newImage) | |||
| { | |||
| repaint(); | |||
| opacity = newOpacity; | |||
| overlayColour = newOverlayColour; | |||
| if (image != newImage) | |||
| setImage (newImage); | |||
| setBoundingBox (newBounds); | |||
| } | |||
| } | |||
| ValueTree DrawableImage::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const | |||
| { | |||
| ValueTree tree (valueTreeType); | |||
| ValueTreeWrapper v (tree); | |||
| v.setID (getComponentID()); | |||
| v.setOpacity (opacity, nullptr); | |||
| v.setOverlayColour (overlayColour, nullptr); | |||
| v.setBoundingBox (bounds, nullptr); | |||
| if (image.isValid()) | |||
| { | |||
| jassert (imageProvider != nullptr); // if you're using images, you need to provide something that can load and save them! | |||
| if (imageProvider != nullptr) | |||
| v.setImageIdentifier (imageProvider->getIdentifierForImage (image), nullptr); | |||
| } | |||
| return tree; | |||
| } | |||
| Path DrawableImage::getOutlineAsPath() const | |||
| { | |||
| return {}; // not applicable for images | |||
| @@ -71,13 +71,16 @@ public: | |||
| Colour getOverlayColour() const noexcept { return overlayColour; } | |||
| /** Sets the bounding box within which the image should be displayed. */ | |||
| void setBoundingBox (const RelativeParallelogram& newBounds); | |||
| void setBoundingBox (Parallelogram<float> newBounds); | |||
| /** Sets the bounding box within which the image should be displayed. */ | |||
| void setBoundingBox (Rectangle<float> newBounds); | |||
| /** Returns the position to which the image's top-left corner should be remapped in the target | |||
| coordinate space when rendering this object. | |||
| @see setTransform | |||
| */ | |||
| const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; } | |||
| Parallelogram<float> getBoundingBox() const noexcept { return bounds; } | |||
| //============================================================================== | |||
| /** @internal */ | |||
| @@ -89,49 +92,14 @@ public: | |||
| /** @internal */ | |||
| Rectangle<float> getDrawableBounds() const override; | |||
| /** @internal */ | |||
| void refreshFromValueTree (const ValueTree& tree, ComponentBuilder&); | |||
| /** @internal */ | |||
| ValueTree createValueTree (ComponentBuilder::ImageProvider*) const override; | |||
| /** @internal */ | |||
| static const Identifier valueTreeType; | |||
| /** @internal */ | |||
| Path getOutlineAsPath() const override; | |||
| //============================================================================== | |||
| /** Internally-used class for wrapping a DrawableImage's state into a ValueTree. */ | |||
| class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase | |||
| { | |||
| public: | |||
| ValueTreeWrapper (const ValueTree& state); | |||
| var getImageIdentifier() const; | |||
| void setImageIdentifier (const var&, UndoManager*); | |||
| Value getImageIdentifierValue (UndoManager*); | |||
| float getOpacity() const; | |||
| void setOpacity (float newOpacity, UndoManager*); | |||
| Value getOpacityValue (UndoManager*); | |||
| Colour getOverlayColour() const; | |||
| void setOverlayColour (Colour newColour, UndoManager*); | |||
| Value getOverlayColourValue (UndoManager*); | |||
| RelativeParallelogram getBoundingBox() const; | |||
| void setBoundingBox (const RelativeParallelogram&, UndoManager*); | |||
| static const Identifier opacity, overlay, image, topLeft, topRight, bottomLeft; | |||
| }; | |||
| private: | |||
| //============================================================================== | |||
| Image image; | |||
| float opacity; | |||
| Colour overlayColour; | |||
| RelativeParallelogram bounds; | |||
| friend class Drawable::Positioner<DrawableImage>; | |||
| bool registerCoordinates (RelativeCoordinatePositionerBase&); | |||
| void recalculateCoordinates (Expression::Scope*); | |||
| float opacity = 1.0f; | |||
| Colour overlayColour { 0 }; | |||
| Parallelogram<float> bounds; | |||
| DrawableImage& operator= (const DrawableImage&); | |||
| JUCE_LEAK_DETECTOR (DrawableImage) | |||
| @@ -27,21 +27,12 @@ | |||
| namespace juce | |||
| { | |||
| DrawablePath::DrawablePath() | |||
| { | |||
| } | |||
| DrawablePath::DrawablePath (const DrawablePath& other) | |||
| : DrawableShape (other) | |||
| { | |||
| if (other.relativePath != nullptr) | |||
| setPath (*other.relativePath); | |||
| else | |||
| setPath (other.path); | |||
| } | |||
| DrawablePath::DrawablePath() {} | |||
| DrawablePath::~DrawablePath() {} | |||
| DrawablePath::~DrawablePath() | |||
| DrawablePath::DrawablePath (const DrawablePath& other) : DrawableShape (other) | |||
| { | |||
| setPath (other.path); | |||
| } | |||
| Drawable* DrawablePath::createCopy() const | |||
| @@ -49,529 +40,19 @@ Drawable* DrawablePath::createCopy() const | |||
| return new DrawablePath (*this); | |||
| } | |||
| //============================================================================== | |||
| void DrawablePath::setPath (const Path& newPath) | |||
| { | |||
| path = newPath; | |||
| pathChanged(); | |||
| } | |||
| const Path& DrawablePath::getPath() const | |||
| { | |||
| return path; | |||
| } | |||
| const Path& DrawablePath::getStrokePath() const | |||
| { | |||
| return strokePath; | |||
| } | |||
| void DrawablePath::applyRelativePath (const RelativePointPath& newRelativePath, Expression::Scope* scope) | |||
| { | |||
| Path newPath; | |||
| newRelativePath.createPath (newPath, scope); | |||
| if (path != newPath) | |||
| { | |||
| path.swapWithPath (newPath); | |||
| pathChanged(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| class DrawablePath::RelativePositioner : public RelativeCoordinatePositionerBase | |||
| { | |||
| public: | |||
| RelativePositioner (DrawablePath& comp) | |||
| : RelativeCoordinatePositionerBase (comp), | |||
| owner (comp) | |||
| { | |||
| } | |||
| bool registerCoordinates() override | |||
| { | |||
| bool ok = true; | |||
| jassert (owner.relativePath != nullptr); | |||
| const RelativePointPath& relPath = *owner.relativePath; | |||
| for (int i = 0; i < relPath.elements.size(); ++i) | |||
| { | |||
| RelativePointPath::ElementBase* const e = relPath.elements.getUnchecked(i); | |||
| int numPoints; | |||
| RelativePoint* const points = e->getControlPoints (numPoints); | |||
| for (int j = numPoints; --j >= 0;) | |||
| ok = addPoint (points[j]) && ok; | |||
| } | |||
| return ok; | |||
| } | |||
| void applyToComponentBounds() override | |||
| { | |||
| jassert (owner.relativePath != nullptr); | |||
| ComponentScope scope (getComponent()); | |||
| owner.applyRelativePath (*owner.relativePath, &scope); | |||
| } | |||
| void applyNewBounds (const Rectangle<int>&) override | |||
| { | |||
| jassertfalse; // drawables can't be resized directly! | |||
| } | |||
| private: | |||
| DrawablePath& owner; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RelativePositioner) | |||
| }; | |||
| void DrawablePath::setPath (const RelativePointPath& newRelativePath) | |||
| { | |||
| if (newRelativePath.containsAnyDynamicPoints()) | |||
| { | |||
| if (relativePath == nullptr || newRelativePath != *relativePath) | |||
| { | |||
| relativePath = new RelativePointPath (newRelativePath); | |||
| RelativePositioner* const p = new RelativePositioner (*this); | |||
| setPositioner (p); | |||
| p->apply(); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| relativePath.reset(); | |||
| applyRelativePath (newRelativePath, nullptr); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| const Identifier DrawablePath::valueTreeType ("Path"); | |||
| const Identifier DrawablePath::ValueTreeWrapper::nonZeroWinding ("nonZeroWinding"); | |||
| const Identifier DrawablePath::ValueTreeWrapper::point1 ("p1"); | |||
| const Identifier DrawablePath::ValueTreeWrapper::point2 ("p2"); | |||
| const Identifier DrawablePath::ValueTreeWrapper::point3 ("p3"); | |||
| //============================================================================== | |||
| DrawablePath::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
| : FillAndStrokeState (state_) | |||
| { | |||
| jassert (state.hasType (valueTreeType)); | |||
| } | |||
| ValueTree DrawablePath::ValueTreeWrapper::getPathState() | |||
| void DrawablePath::setPath (Path&& newPath) | |||
| { | |||
| return state.getOrCreateChildWithName (path, nullptr); | |||
| } | |||
| bool DrawablePath::ValueTreeWrapper::usesNonZeroWinding() const | |||
| { | |||
| return state [nonZeroWinding]; | |||
| } | |||
| void DrawablePath::ValueTreeWrapper::setUsesNonZeroWinding (bool b, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (nonZeroWinding, b, undoManager); | |||
| } | |||
| void DrawablePath::ValueTreeWrapper::readFrom (const RelativePointPath& p, UndoManager* undoManager) | |||
| { | |||
| setUsesNonZeroWinding (p.usesNonZeroWinding, undoManager); | |||
| ValueTree pathTree (getPathState()); | |||
| pathTree.removeAllChildren (undoManager); | |||
| for (int i = 0; i < p.elements.size(); ++i) | |||
| pathTree.appendChild (p.elements.getUnchecked(i)->createTree(), undoManager); | |||
| } | |||
| void DrawablePath::ValueTreeWrapper::writeTo (RelativePointPath& p) const | |||
| { | |||
| p.usesNonZeroWinding = usesNonZeroWinding(); | |||
| RelativePoint points[3]; | |||
| const ValueTree pathTree (state.getChildWithName (path)); | |||
| const int num = pathTree.getNumChildren(); | |||
| for (int i = 0; i < num; ++i) | |||
| { | |||
| const Element e (pathTree.getChild(i)); | |||
| const int numCps = e.getNumControlPoints(); | |||
| for (int j = 0; j < numCps; ++j) | |||
| points[j] = e.getControlPoint (j); | |||
| RelativePointPath::ElementBase* newElement = nullptr; | |||
| const Identifier t (e.getType()); | |||
| if (t == Element::startSubPathElement) newElement = new RelativePointPath::StartSubPath (points[0]); | |||
| else if (t == Element::closeSubPathElement) newElement = new RelativePointPath::CloseSubPath(); | |||
| else if (t == Element::lineToElement) newElement = new RelativePointPath::LineTo (points[0]); | |||
| else if (t == Element::quadraticToElement) newElement = new RelativePointPath::QuadraticTo (points[0], points[1]); | |||
| else if (t == Element::cubicToElement) newElement = new RelativePointPath::CubicTo (points[0], points[1], points[2]); | |||
| else jassertfalse; | |||
| p.addElement (newElement); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| const Identifier DrawablePath::ValueTreeWrapper::Element::mode ("mode"); | |||
| const Identifier DrawablePath::ValueTreeWrapper::Element::startSubPathElement ("Move"); | |||
| const Identifier DrawablePath::ValueTreeWrapper::Element::closeSubPathElement ("Close"); | |||
| const Identifier DrawablePath::ValueTreeWrapper::Element::lineToElement ("Line"); | |||
| const Identifier DrawablePath::ValueTreeWrapper::Element::quadraticToElement ("Quad"); | |||
| const Identifier DrawablePath::ValueTreeWrapper::Element::cubicToElement ("Cubic"); | |||
| const char* DrawablePath::ValueTreeWrapper::Element::cornerMode = "corner"; | |||
| const char* DrawablePath::ValueTreeWrapper::Element::roundedMode = "round"; | |||
| const char* DrawablePath::ValueTreeWrapper::Element::symmetricMode = "symm"; | |||
| DrawablePath::ValueTreeWrapper::Element::Element (const ValueTree& state_) | |||
| : state (state_) | |||
| { | |||
| } | |||
| DrawablePath::ValueTreeWrapper::Element::~Element() | |||
| { | |||
| } | |||
| DrawablePath::ValueTreeWrapper DrawablePath::ValueTreeWrapper::Element::getParent() const | |||
| { | |||
| return ValueTreeWrapper (state.getParent().getParent()); | |||
| } | |||
| DrawablePath::ValueTreeWrapper::Element DrawablePath::ValueTreeWrapper::Element::getPreviousElement() const | |||
| { | |||
| return Element (state.getSibling (-1)); | |||
| } | |||
| int DrawablePath::ValueTreeWrapper::Element::getNumControlPoints() const noexcept | |||
| { | |||
| const Identifier i (state.getType()); | |||
| if (i == startSubPathElement || i == lineToElement) return 1; | |||
| if (i == quadraticToElement) return 2; | |||
| if (i == cubicToElement) return 3; | |||
| return 0; | |||
| } | |||
| RelativePoint DrawablePath::ValueTreeWrapper::Element::getControlPoint (const int index) const | |||
| { | |||
| jassert (index >= 0 && index < getNumControlPoints()); | |||
| return RelativePoint (state [index == 0 ? point1 : (index == 1 ? point2 : point3)].toString()); | |||
| } | |||
| Value DrawablePath::ValueTreeWrapper::Element::getControlPointValue (int index, UndoManager* undoManager) | |||
| { | |||
| jassert (index >= 0 && index < getNumControlPoints()); | |||
| return state.getPropertyAsValue (index == 0 ? point1 : (index == 1 ? point2 : point3), undoManager); | |||
| } | |||
| void DrawablePath::ValueTreeWrapper::Element::setControlPoint (const int index, const RelativePoint& point, UndoManager* undoManager) | |||
| { | |||
| jassert (index >= 0 && index < getNumControlPoints()); | |||
| state.setProperty (index == 0 ? point1 : (index == 1 ? point2 : point3), point.toString(), undoManager); | |||
| } | |||
| RelativePoint DrawablePath::ValueTreeWrapper::Element::getStartPoint() const | |||
| { | |||
| const Identifier i (state.getType()); | |||
| if (i == startSubPathElement) | |||
| return getControlPoint (0); | |||
| jassert (i == lineToElement || i == quadraticToElement || i == cubicToElement || i == closeSubPathElement); | |||
| return getPreviousElement().getEndPoint(); | |||
| } | |||
| RelativePoint DrawablePath::ValueTreeWrapper::Element::getEndPoint() const | |||
| { | |||
| const Identifier i (state.getType()); | |||
| if (i == startSubPathElement || i == lineToElement) return getControlPoint (0); | |||
| if (i == quadraticToElement) return getControlPoint (1); | |||
| if (i == cubicToElement) return getControlPoint (2); | |||
| jassert (i == closeSubPathElement); | |||
| return RelativePoint(); | |||
| } | |||
| float DrawablePath::ValueTreeWrapper::Element::getLength (Expression::Scope* scope) const | |||
| { | |||
| const Identifier i (state.getType()); | |||
| if (i == lineToElement || i == closeSubPathElement) | |||
| return getEndPoint().resolve (scope).getDistanceFrom (getStartPoint().resolve (scope)); | |||
| if (i == cubicToElement) | |||
| { | |||
| Path p; | |||
| p.startNewSubPath (getStartPoint().resolve (scope)); | |||
| p.cubicTo (getControlPoint (0).resolve (scope), getControlPoint (1).resolve (scope), getControlPoint (2).resolve (scope)); | |||
| return p.getLength(); | |||
| } | |||
| if (i == quadraticToElement) | |||
| { | |||
| Path p; | |||
| p.startNewSubPath (getStartPoint().resolve (scope)); | |||
| p.quadraticTo (getControlPoint (0).resolve (scope), getControlPoint (1).resolve (scope)); | |||
| return p.getLength(); | |||
| } | |||
| jassert (i == startSubPathElement); | |||
| return 0; | |||
| } | |||
| String DrawablePath::ValueTreeWrapper::Element::getModeOfEndPoint() const | |||
| { | |||
| return state [mode].toString(); | |||
| } | |||
| void DrawablePath::ValueTreeWrapper::Element::setModeOfEndPoint (const String& newMode, UndoManager* undoManager) | |||
| { | |||
| if (state.hasType (cubicToElement)) | |||
| state.setProperty (mode, newMode, undoManager); | |||
| } | |||
| void DrawablePath::ValueTreeWrapper::Element::convertToLine (UndoManager* undoManager) | |||
| { | |||
| const Identifier i (state.getType()); | |||
| if (i == quadraticToElement || i == cubicToElement) | |||
| { | |||
| ValueTree newState (lineToElement); | |||
| Element e (newState); | |||
| e.setControlPoint (0, getEndPoint(), undoManager); | |||
| state = newState; | |||
| } | |||
| } | |||
| void DrawablePath::ValueTreeWrapper::Element::convertToCubic (Expression::Scope* scope, UndoManager* undoManager) | |||
| { | |||
| const Identifier i (state.getType()); | |||
| if (i == lineToElement || i == quadraticToElement) | |||
| { | |||
| ValueTree newState (cubicToElement); | |||
| Element e (newState); | |||
| const RelativePoint start (getStartPoint()); | |||
| const RelativePoint end (getEndPoint()); | |||
| const Point<float> startResolved (start.resolve (scope)); | |||
| const Point<float> endResolved (end.resolve (scope)); | |||
| e.setControlPoint (0, startResolved + (endResolved - startResolved) * 0.3f, undoManager); | |||
| e.setControlPoint (1, startResolved + (endResolved - startResolved) * 0.7f, undoManager); | |||
| e.setControlPoint (2, end, undoManager); | |||
| state = newState; | |||
| } | |||
| } | |||
| void DrawablePath::ValueTreeWrapper::Element::convertToPathBreak (UndoManager* undoManager) | |||
| { | |||
| const Identifier i (state.getType()); | |||
| if (i != startSubPathElement) | |||
| { | |||
| ValueTree newState (startSubPathElement); | |||
| Element e (newState); | |||
| e.setControlPoint (0, getEndPoint(), undoManager); | |||
| state = newState; | |||
| } | |||
| } | |||
| namespace DrawablePathHelpers | |||
| { | |||
| static Point<float> findCubicSubdivisionPoint (float proportion, const Point<float> points[4]) | |||
| { | |||
| const Point<float> mid1 (points[0] + (points[1] - points[0]) * proportion), | |||
| mid2 (points[1] + (points[2] - points[1]) * proportion), | |||
| mid3 (points[2] + (points[3] - points[2]) * proportion); | |||
| const Point<float> newCp1 (mid1 + (mid2 - mid1) * proportion), | |||
| newCp2 (mid2 + (mid3 - mid2) * proportion); | |||
| return newCp1 + (newCp2 - newCp1) * proportion; | |||
| } | |||
| static Point<float> findQuadraticSubdivisionPoint (float proportion, const Point<float> points[3]) | |||
| { | |||
| const Point<float> mid1 (points[0] + (points[1] - points[0]) * proportion), | |||
| mid2 (points[1] + (points[2] - points[1]) * proportion); | |||
| return mid1 + (mid2 - mid1) * proportion; | |||
| } | |||
| } | |||
| float DrawablePath::ValueTreeWrapper::Element::findProportionAlongLine (Point<float> targetPoint, Expression::Scope* scope) const | |||
| { | |||
| using namespace DrawablePathHelpers; | |||
| const Identifier pointType (state.getType()); | |||
| float bestProp = 0; | |||
| if (pointType == cubicToElement) | |||
| { | |||
| RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint()); | |||
| const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope), rp4.resolve (scope) }; | |||
| float bestDistance = std::numeric_limits<float>::max(); | |||
| for (int i = 110; --i >= 0;) | |||
| { | |||
| float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f)); | |||
| const Point<float> centre (findCubicSubdivisionPoint (prop, points)); | |||
| const float distance = centre.getDistanceFrom (targetPoint); | |||
| if (distance < bestDistance) | |||
| { | |||
| bestProp = prop; | |||
| bestDistance = distance; | |||
| } | |||
| } | |||
| } | |||
| else if (pointType == quadraticToElement) | |||
| { | |||
| RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint()); | |||
| const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope) }; | |||
| float bestDistance = std::numeric_limits<float>::max(); | |||
| for (int i = 110; --i >= 0;) | |||
| { | |||
| float prop = i > 10 ? ((i - 10) / 100.0f) : (bestProp + ((i - 5) / 1000.0f)); | |||
| const Point<float> centre (findQuadraticSubdivisionPoint ((float) prop, points)); | |||
| const float distance = centre.getDistanceFrom (targetPoint); | |||
| if (distance < bestDistance) | |||
| { | |||
| bestProp = prop; | |||
| bestDistance = distance; | |||
| } | |||
| } | |||
| } | |||
| else if (pointType == lineToElement) | |||
| { | |||
| RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint()); | |||
| const Line<float> line (rp1.resolve (scope), rp2.resolve (scope)); | |||
| bestProp = line.findNearestProportionalPositionTo (targetPoint); | |||
| } | |||
| return bestProp; | |||
| } | |||
| ValueTree DrawablePath::ValueTreeWrapper::Element::insertPoint (Point<float> targetPoint, Expression::Scope* scope, UndoManager* undoManager) | |||
| { | |||
| ValueTree newTree; | |||
| const Identifier pointType (state.getType()); | |||
| if (pointType == cubicToElement) | |||
| { | |||
| float bestProp = findProportionAlongLine (targetPoint, scope); | |||
| RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getControlPoint (1)), rp4 (getEndPoint()); | |||
| const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope), rp4.resolve (scope) }; | |||
| const Point<float> mid1 (points[0] + (points[1] - points[0]) * bestProp), | |||
| mid2 (points[1] + (points[2] - points[1]) * bestProp), | |||
| mid3 (points[2] + (points[3] - points[2]) * bestProp); | |||
| const Point<float> newCp1 (mid1 + (mid2 - mid1) * bestProp), | |||
| newCp2 (mid2 + (mid3 - mid2) * bestProp); | |||
| const Point<float> newCentre (newCp1 + (newCp2 - newCp1) * bestProp); | |||
| setControlPoint (0, mid1, undoManager); | |||
| setControlPoint (1, newCp1, undoManager); | |||
| setControlPoint (2, newCentre, undoManager); | |||
| setModeOfEndPoint (roundedMode, undoManager); | |||
| Element newElement (newTree = ValueTree (cubicToElement)); | |||
| newElement.setControlPoint (0, newCp2, nullptr); | |||
| newElement.setControlPoint (1, mid3, nullptr); | |||
| newElement.setControlPoint (2, rp4, nullptr); | |||
| state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); | |||
| } | |||
| else if (pointType == quadraticToElement) | |||
| { | |||
| float bestProp = findProportionAlongLine (targetPoint, scope); | |||
| RelativePoint rp1 (getStartPoint()), rp2 (getControlPoint (0)), rp3 (getEndPoint()); | |||
| const Point<float> points[] = { rp1.resolve (scope), rp2.resolve (scope), rp3.resolve (scope) }; | |||
| const Point<float> mid1 (points[0] + (points[1] - points[0]) * bestProp), | |||
| mid2 (points[1] + (points[2] - points[1]) * bestProp); | |||
| const Point<float> newCentre (mid1 + (mid2 - mid1) * bestProp); | |||
| setControlPoint (0, mid1, undoManager); | |||
| setControlPoint (1, newCentre, undoManager); | |||
| setModeOfEndPoint (roundedMode, undoManager); | |||
| Element newElement (newTree = ValueTree (quadraticToElement)); | |||
| newElement.setControlPoint (0, mid2, nullptr); | |||
| newElement.setControlPoint (1, rp3, nullptr); | |||
| state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); | |||
| } | |||
| else if (pointType == lineToElement) | |||
| { | |||
| RelativePoint rp1 (getStartPoint()), rp2 (getEndPoint()); | |||
| const Line<float> line (rp1.resolve (scope), rp2.resolve (scope)); | |||
| const Point<float> newPoint (line.findNearestPointTo (targetPoint)); | |||
| setControlPoint (0, newPoint, undoManager); | |||
| Element newElement (newTree = ValueTree (lineToElement)); | |||
| newElement.setControlPoint (0, rp2, nullptr); | |||
| state.getParent().addChild (newTree, state.getParent().indexOf (state) + 1, undoManager); | |||
| } | |||
| else if (pointType == closeSubPathElement) | |||
| { | |||
| } | |||
| return newTree; | |||
| } | |||
| void DrawablePath::ValueTreeWrapper::Element::removePoint (UndoManager* undoManager) | |||
| { | |||
| state.getParent().removeChild (state, undoManager); | |||
| } | |||
| //============================================================================== | |||
| void DrawablePath::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder) | |||
| { | |||
| ValueTreeWrapper v (tree); | |||
| setComponentID (v.getID()); | |||
| refreshFillTypes (v, builder.getImageProvider()); | |||
| setStrokeType (v.getStrokeType()); | |||
| RelativePointPath newRelativePath; | |||
| v.writeTo (newRelativePath); | |||
| setPath (newRelativePath); | |||
| path = static_cast<Path&&> (newPath); | |||
| pathChanged(); | |||
| } | |||
| ValueTree DrawablePath::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const | |||
| { | |||
| ValueTree tree (valueTreeType); | |||
| ValueTreeWrapper v (tree); | |||
| v.setID (getComponentID()); | |||
| writeTo (v, imageProvider, nullptr); | |||
| if (relativePath != nullptr) | |||
| v.readFrom (*relativePath, nullptr); | |||
| else | |||
| v.readFrom (RelativePointPath (path), nullptr); | |||
| return tree; | |||
| } | |||
| const Path& DrawablePath::getPath() const { return path; } | |||
| const Path& DrawablePath::getStrokePath() const { return strokePath; } | |||
| } // namespace juce | |||
| @@ -52,11 +52,10 @@ public: | |||
| */ | |||
| void setPath (const Path& newPath); | |||
| /** Sets the path using a RelativePointPath. | |||
| Calling this will set up a Component::Positioner to automatically update the path | |||
| if any of the points in the source path are dynamic. | |||
| /** Changes the path that will be drawn. | |||
| @see setFill, setStrokeType | |||
| */ | |||
| void setPath (const RelativePointPath& newPath); | |||
| void setPath (Path&& newPath); | |||
| /** Returns the current path. */ | |||
| const Path& getPath() const; | |||
| @@ -67,77 +66,9 @@ public: | |||
| //============================================================================== | |||
| /** @internal */ | |||
| Drawable* createCopy() const; | |||
| /** @internal */ | |||
| void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder); | |||
| /** @internal */ | |||
| ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const; | |||
| /** @internal */ | |||
| static const Identifier valueTreeType; | |||
| //============================================================================== | |||
| /** Internally-used class for wrapping a DrawablePath's state into a ValueTree. */ | |||
| class ValueTreeWrapper : public DrawableShape::FillAndStrokeState | |||
| { | |||
| public: | |||
| ValueTreeWrapper (const ValueTree& state); | |||
| bool usesNonZeroWinding() const; | |||
| void setUsesNonZeroWinding (bool b, UndoManager* undoManager); | |||
| class Element | |||
| { | |||
| public: | |||
| explicit Element (const ValueTree& state); | |||
| ~Element(); | |||
| const Identifier getType() const noexcept { return state.getType(); } | |||
| int getNumControlPoints() const noexcept; | |||
| RelativePoint getControlPoint (int index) const; | |||
| Value getControlPointValue (int index, UndoManager*); | |||
| RelativePoint getStartPoint() const; | |||
| RelativePoint getEndPoint() const; | |||
| void setControlPoint (int index, const RelativePoint& point, UndoManager*); | |||
| float getLength (Expression::Scope*) const; | |||
| ValueTreeWrapper getParent() const; | |||
| Element getPreviousElement() const; | |||
| String getModeOfEndPoint() const; | |||
| void setModeOfEndPoint (const String& newMode, UndoManager*); | |||
| void convertToLine (UndoManager*); | |||
| void convertToCubic (Expression::Scope*, UndoManager*); | |||
| void convertToPathBreak (UndoManager* undoManager); | |||
| ValueTree insertPoint (Point<float> targetPoint, Expression::Scope*, UndoManager*); | |||
| void removePoint (UndoManager* undoManager); | |||
| float findProportionAlongLine (Point<float> targetPoint, Expression::Scope*) const; | |||
| static const Identifier mode, startSubPathElement, closeSubPathElement, | |||
| lineToElement, quadraticToElement, cubicToElement; | |||
| static const char* cornerMode; | |||
| static const char* roundedMode; | |||
| static const char* symmetricMode; | |||
| ValueTree state; | |||
| }; | |||
| ValueTree getPathState(); | |||
| void readFrom (const RelativePointPath& relativePath, UndoManager* undoManager); | |||
| void writeTo (RelativePointPath& relativePath) const; | |||
| static const Identifier nonZeroWinding, point1, point2, point3; | |||
| }; | |||
| private: | |||
| //============================================================================== | |||
| ScopedPointer<RelativePointPath> relativePath; | |||
| class RelativePositioner; | |||
| friend class RelativePositioner; | |||
| void applyRelativePath (const RelativePointPath&, Expression::Scope*); | |||
| DrawablePath& operator= (const DrawablePath&); | |||
| JUCE_LEAK_DETECTOR (DrawablePath) | |||
| }; | |||
| @@ -27,9 +27,8 @@ | |||
| namespace juce | |||
| { | |||
| DrawableRectangle::DrawableRectangle() | |||
| { | |||
| } | |||
| DrawableRectangle::DrawableRectangle() {} | |||
| DrawableRectangle::~DrawableRectangle() {} | |||
| DrawableRectangle::DrawableRectangle (const DrawableRectangle& other) | |||
| : DrawableShape (other), | |||
| @@ -39,17 +38,13 @@ DrawableRectangle::DrawableRectangle (const DrawableRectangle& other) | |||
| rebuildPath(); | |||
| } | |||
| DrawableRectangle::~DrawableRectangle() | |||
| { | |||
| } | |||
| Drawable* DrawableRectangle::createCopy() const | |||
| { | |||
| return new DrawableRectangle (*this); | |||
| } | |||
| //============================================================================== | |||
| void DrawableRectangle::setRectangle (const RelativeParallelogram& newBounds) | |||
| void DrawableRectangle::setRectangle (Parallelogram<float> newBounds) | |||
| { | |||
| if (bounds != newBounds) | |||
| { | |||
| @@ -58,7 +53,7 @@ void DrawableRectangle::setRectangle (const RelativeParallelogram& newBounds) | |||
| } | |||
| } | |||
| void DrawableRectangle::setCornerSize (const RelativePoint& newSize) | |||
| void DrawableRectangle::setCornerSize (Point<float> newSize) | |||
| { | |||
| if (cornerSize != newSize) | |||
| { | |||
| @@ -69,48 +64,19 @@ void DrawableRectangle::setCornerSize (const RelativePoint& newSize) | |||
| void DrawableRectangle::rebuildPath() | |||
| { | |||
| if (bounds.isDynamic() || cornerSize.isDynamic()) | |||
| { | |||
| auto p = new Drawable::Positioner<DrawableRectangle> (*this); | |||
| setPositioner (p); | |||
| p->apply(); | |||
| } | |||
| else | |||
| { | |||
| setPositioner (nullptr); | |||
| recalculateCoordinates (nullptr); | |||
| } | |||
| } | |||
| bool DrawableRectangle::registerCoordinates (RelativeCoordinatePositionerBase& pos) | |||
| { | |||
| bool ok = pos.addPoint (bounds.topLeft); | |||
| ok = pos.addPoint (bounds.topRight) && ok; | |||
| ok = pos.addPoint (bounds.bottomLeft) && ok; | |||
| return pos.addPoint (cornerSize) && ok; | |||
| } | |||
| void DrawableRectangle::recalculateCoordinates (Expression::Scope* scope) | |||
| { | |||
| Point<float> points[3]; | |||
| bounds.resolveThreePoints (points, scope); | |||
| auto cornerSizeX = (float) cornerSize.x.resolve (scope); | |||
| auto cornerSizeY = (float) cornerSize.y.resolve (scope); | |||
| auto w = Line<float> (points[0], points[1]).getLength(); | |||
| auto h = Line<float> (points[0], points[2]).getLength(); | |||
| auto w = bounds.getWidth(); | |||
| auto h = bounds.getHeight(); | |||
| Path newPath; | |||
| if (cornerSizeX > 0 && cornerSizeY > 0) | |||
| newPath.addRoundedRectangle (0, 0, w, h, cornerSizeX, cornerSizeY); | |||
| if (cornerSize.x > 0 && cornerSize.y > 0) | |||
| newPath.addRoundedRectangle (0, 0, w, h, cornerSize.x, cornerSize.y); | |||
| else | |||
| newPath.addRectangle (0, 0, w, h); | |||
| newPath.applyTransform (AffineTransform::fromTargetPoints (0, 0, points[0].x, points[0].y, | |||
| w, 0, points[1].x, points[1].y, | |||
| 0, h, points[2].x, points[2].y)); | |||
| newPath.applyTransform (AffineTransform::fromTargetPoints (Point<float>(), bounds.topLeft, | |||
| Point<float> (w, 0), bounds.topRight, | |||
| Point<float> (0, h), bounds.bottomLeft)); | |||
| if (path != newPath) | |||
| { | |||
| @@ -119,72 +85,4 @@ void DrawableRectangle::recalculateCoordinates (Expression::Scope* scope) | |||
| } | |||
| } | |||
| //============================================================================== | |||
| const Identifier DrawableRectangle::valueTreeType ("Rectangle"); | |||
| const Identifier DrawableRectangle::ValueTreeWrapper::topLeft ("topLeft"); | |||
| const Identifier DrawableRectangle::ValueTreeWrapper::topRight ("topRight"); | |||
| const Identifier DrawableRectangle::ValueTreeWrapper::bottomLeft ("bottomLeft"); | |||
| const Identifier DrawableRectangle::ValueTreeWrapper::cornerSize ("cornerSize"); | |||
| //============================================================================== | |||
| DrawableRectangle::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
| : FillAndStrokeState (state_) | |||
| { | |||
| jassert (state.hasType (valueTreeType)); | |||
| } | |||
| RelativeParallelogram DrawableRectangle::ValueTreeWrapper::getRectangle() const | |||
| { | |||
| return RelativeParallelogram (state.getProperty (topLeft, "0, 0"), | |||
| state.getProperty (topRight, "100, 0"), | |||
| state.getProperty (bottomLeft, "0, 100")); | |||
| } | |||
| void DrawableRectangle::ValueTreeWrapper::setRectangle (const RelativeParallelogram& newBounds, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); | |||
| state.setProperty (topRight, newBounds.topRight.toString(), undoManager); | |||
| state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); | |||
| } | |||
| void DrawableRectangle::ValueTreeWrapper::setCornerSize (const RelativePoint& newSize, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (cornerSize, newSize.toString(), undoManager); | |||
| } | |||
| RelativePoint DrawableRectangle::ValueTreeWrapper::getCornerSize() const | |||
| { | |||
| return RelativePoint (state[cornerSize]); | |||
| } | |||
| Value DrawableRectangle::ValueTreeWrapper::getCornerSizeValue (UndoManager* undoManager) | |||
| { | |||
| return state.getPropertyAsValue (cornerSize, undoManager); | |||
| } | |||
| //============================================================================== | |||
| void DrawableRectangle::refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder) | |||
| { | |||
| ValueTreeWrapper v (tree); | |||
| setComponentID (v.getID()); | |||
| refreshFillTypes (v, builder.getImageProvider()); | |||
| setStrokeType (v.getStrokeType()); | |||
| setRectangle (v.getRectangle()); | |||
| setCornerSize (v.getCornerSize()); | |||
| } | |||
| ValueTree DrawableRectangle::createValueTree (ComponentBuilder::ImageProvider* imageProvider) const | |||
| { | |||
| ValueTree tree (valueTreeType); | |||
| ValueTreeWrapper v (tree); | |||
| v.setID (getComponentID()); | |||
| writeTo (v, imageProvider, nullptr); | |||
| v.setRectangle (bounds, nullptr); | |||
| v.setCornerSize (cornerSize, nullptr); | |||
| return tree; | |||
| } | |||
| } // namespace juce | |||
| @@ -47,54 +47,26 @@ public: | |||
| //============================================================================== | |||
| /** Sets the rectangle's bounds. */ | |||
| void setRectangle (const RelativeParallelogram& newBounds); | |||
| void setRectangle (Parallelogram<float> newBounds); | |||
| /** Returns the rectangle's bounds. */ | |||
| const RelativeParallelogram& getRectangle() const noexcept { return bounds; } | |||
| Parallelogram<float> getRectangle() const noexcept { return bounds; } | |||
| /** Returns the corner size to be used. */ | |||
| const RelativePoint& getCornerSize() const noexcept { return cornerSize; } | |||
| Point<float> getCornerSize() const noexcept { return cornerSize; } | |||
| /** Sets a new corner size for the rectangle */ | |||
| void setCornerSize (const RelativePoint& newSize); | |||
| void setCornerSize (Point<float> newSize); | |||
| //============================================================================== | |||
| /** @internal */ | |||
| Drawable* createCopy() const; | |||
| /** @internal */ | |||
| void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder); | |||
| /** @internal */ | |||
| ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const; | |||
| /** @internal */ | |||
| static const Identifier valueTreeType; | |||
| //============================================================================== | |||
| /** Internally-used class for wrapping a DrawableRectangle's state into a ValueTree. */ | |||
| class ValueTreeWrapper : public DrawableShape::FillAndStrokeState | |||
| { | |||
| public: | |||
| ValueTreeWrapper (const ValueTree& state); | |||
| RelativeParallelogram getRectangle() const; | |||
| void setRectangle (const RelativeParallelogram& newBounds, UndoManager*); | |||
| void setCornerSize (const RelativePoint& cornerSize, UndoManager*); | |||
| RelativePoint getCornerSize() const; | |||
| Value getCornerSizeValue (UndoManager*); | |||
| static const Identifier topLeft, topRight, bottomLeft, cornerSize; | |||
| }; | |||
| private: | |||
| friend class Drawable::Positioner<DrawableRectangle>; | |||
| RelativeParallelogram bounds; | |||
| RelativePoint cornerSize; | |||
| Parallelogram<float> bounds; | |||
| Point<float> cornerSize; | |||
| void rebuildPath(); | |||
| bool registerCoordinates (RelativeCoordinatePositionerBase&); | |||
| void recalculateCoordinates (Expression::Scope*); | |||
| DrawableRectangle& operator= (const DrawableRectangle&); | |||
| JUCE_LEAK_DETECTOR (DrawableRectangle) | |||
| @@ -48,87 +48,24 @@ DrawableShape::~DrawableShape() | |||
| } | |||
| //============================================================================== | |||
| class DrawableShape::RelativePositioner : public RelativeCoordinatePositionerBase | |||
| void DrawableShape::setFill (const FillType& newFill) | |||
| { | |||
| public: | |||
| RelativePositioner (DrawableShape& comp, const DrawableShape::RelativeFillType& f, bool isMain) | |||
| : RelativeCoordinatePositionerBase (comp), | |||
| owner (comp), | |||
| fill (f), | |||
| isMainFill (isMain) | |||
| { | |||
| } | |||
| bool registerCoordinates() override | |||
| if (mainFill != newFill) | |||
| { | |||
| bool ok = addPoint (fill.gradientPoint1); | |||
| ok = addPoint (fill.gradientPoint2) && ok; | |||
| return addPoint (fill.gradientPoint3) && ok; | |||
| } | |||
| void applyToComponentBounds() override | |||
| { | |||
| ComponentScope scope (owner); | |||
| if (isMainFill ? owner.mainFill.recalculateCoords (&scope) | |||
| : owner.strokeFill.recalculateCoords (&scope)) | |||
| owner.repaint(); | |||
| } | |||
| void applyNewBounds (const Rectangle<int>&) override | |||
| { | |||
| jassertfalse; // drawables can't be resized directly! | |||
| mainFill = newFill; | |||
| repaint(); | |||
| } | |||
| private: | |||
| DrawableShape& owner; | |||
| const DrawableShape::RelativeFillType fill; | |||
| const bool isMainFill; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RelativePositioner) | |||
| }; | |||
| void DrawableShape::setFill (const FillType& newFill) | |||
| { | |||
| setFill (RelativeFillType (newFill)); | |||
| } | |||
| void DrawableShape::setStrokeFill (const FillType& newFill) | |||
| { | |||
| setStrokeFill (RelativeFillType (newFill)); | |||
| } | |||
| void DrawableShape::setFillInternal (RelativeFillType& fill, const RelativeFillType& newFill, | |||
| ScopedPointer<RelativeCoordinatePositionerBase>& pos) | |||
| { | |||
| if (fill != newFill) | |||
| if (strokeFill != newFill) | |||
| { | |||
| fill = newFill; | |||
| pos.reset(); | |||
| if (fill.isDynamic()) | |||
| { | |||
| pos = new RelativePositioner (*this, fill, true); | |||
| pos->apply(); | |||
| } | |||
| else | |||
| { | |||
| fill.recalculateCoords (nullptr); | |||
| } | |||
| strokeFill = newFill; | |||
| repaint(); | |||
| } | |||
| } | |||
| void DrawableShape::setFill (const RelativeFillType& newFill) | |||
| { | |||
| setFillInternal (mainFill, newFill, mainFillPositioner); | |||
| } | |||
| void DrawableShape::setStrokeFill (const RelativeFillType& newFill) | |||
| { | |||
| setFillInternal (strokeFill, newFill, strokeFillPositioner); | |||
| } | |||
| void DrawableShape::setStrokeType (const PathStrokeType& newStrokeType) | |||
| { | |||
| if (strokeType != newStrokeType) | |||
| @@ -154,20 +91,7 @@ void DrawableShape::setStrokeThickness (const float newThickness) | |||
| bool DrawableShape::isStrokeVisible() const noexcept | |||
| { | |||
| return strokeType.getStrokeThickness() > 0.0f && ! strokeFill.fill.isInvisible(); | |||
| } | |||
| void DrawableShape::refreshFillTypes (const FillAndStrokeState& newState, ComponentBuilder::ImageProvider* imageProvider) | |||
| { | |||
| setFill (newState.getFill (FillAndStrokeState::fill, imageProvider)); | |||
| setStrokeFill (newState.getFill (FillAndStrokeState::stroke, imageProvider)); | |||
| } | |||
| void DrawableShape::writeTo (FillAndStrokeState& state, ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager) const | |||
| { | |||
| state.setFill (FillAndStrokeState::fill, mainFill, imageProvider, undoManager); | |||
| state.setFill (FillAndStrokeState::stroke, strokeFill, imageProvider, undoManager); | |||
| state.setStrokeType (strokeType, undoManager); | |||
| return strokeType.getStrokeThickness() > 0.0f && ! strokeFill.isInvisible(); | |||
| } | |||
| //============================================================================== | |||
| @@ -176,12 +100,12 @@ void DrawableShape::paint (Graphics& g) | |||
| transformContextToCorrectOrigin (g); | |||
| applyDrawableClipPath (g); | |||
| g.setFillType (mainFill.fill); | |||
| g.setFillType (mainFill); | |||
| g.fillPath (path); | |||
| if (isStrokeVisible()) | |||
| { | |||
| g.setFillType (strokeFill.fill); | |||
| g.setFillType (strokeFill); | |||
| g.fillPath (strokePath); | |||
| } | |||
| } | |||
| @@ -222,262 +146,17 @@ bool DrawableShape::hitTest (int x, int y) | |||
| if (! allowsClicksOnThisComponent) | |||
| return false; | |||
| const float globalX = (float) (x - originRelativeToComponent.x); | |||
| const float globalY = (float) (y - originRelativeToComponent.y); | |||
| auto globalX = (float) (x - originRelativeToComponent.x); | |||
| auto globalY = (float) (y - originRelativeToComponent.y); | |||
| return path.contains (globalX, globalY) | |||
| || (isStrokeVisible() && strokePath.contains (globalX, globalY)); | |||
| } | |||
| //============================================================================== | |||
| DrawableShape::RelativeFillType::RelativeFillType() | |||
| { | |||
| } | |||
| DrawableShape::RelativeFillType::RelativeFillType (const FillType& fill_) | |||
| : fill (fill_) | |||
| { | |||
| if (fill.isGradient()) | |||
| { | |||
| const ColourGradient& g = *fill.gradient; | |||
| gradientPoint1 = g.point1.transformedBy (fill.transform); | |||
| gradientPoint2 = g.point2.transformedBy (fill.transform); | |||
| gradientPoint3 = Point<float> (g.point1.x + g.point2.y - g.point1.y, | |||
| g.point1.y + g.point1.x - g.point2.x) | |||
| .transformedBy (fill.transform); | |||
| fill.transform = AffineTransform(); | |||
| } | |||
| } | |||
| DrawableShape::RelativeFillType::RelativeFillType (const RelativeFillType& other) | |||
| : fill (other.fill), | |||
| gradientPoint1 (other.gradientPoint1), | |||
| gradientPoint2 (other.gradientPoint2), | |||
| gradientPoint3 (other.gradientPoint3) | |||
| { | |||
| } | |||
| DrawableShape::RelativeFillType& DrawableShape::RelativeFillType::operator= (const RelativeFillType& other) | |||
| { | |||
| fill = other.fill; | |||
| gradientPoint1 = other.gradientPoint1; | |||
| gradientPoint2 = other.gradientPoint2; | |||
| gradientPoint3 = other.gradientPoint3; | |||
| return *this; | |||
| } | |||
| bool DrawableShape::RelativeFillType::operator== (const RelativeFillType& other) const | |||
| { | |||
| return fill == other.fill | |||
| && ((! fill.isGradient()) | |||
| || (gradientPoint1 == other.gradientPoint1 | |||
| && gradientPoint2 == other.gradientPoint2 | |||
| && gradientPoint3 == other.gradientPoint3)); | |||
| } | |||
| bool DrawableShape::RelativeFillType::operator!= (const RelativeFillType& other) const | |||
| { | |||
| return ! operator== (other); | |||
| } | |||
| bool DrawableShape::RelativeFillType::recalculateCoords (Expression::Scope* scope) | |||
| { | |||
| if (fill.isGradient()) | |||
| { | |||
| const Point<float> g1 (gradientPoint1.resolve (scope)); | |||
| const Point<float> g2 (gradientPoint2.resolve (scope)); | |||
| AffineTransform t; | |||
| ColourGradient& g = *fill.gradient; | |||
| if (g.isRadial) | |||
| { | |||
| const Point<float> g3 (gradientPoint3.resolve (scope)); | |||
| const Point<float> g3Source (g1.x + g2.y - g1.y, | |||
| g1.y + g1.x - g2.x); | |||
| t = AffineTransform::fromTargetPoints (g1.x, g1.y, g1.x, g1.y, | |||
| g2.x, g2.y, g2.x, g2.y, | |||
| g3Source.x, g3Source.y, g3.x, g3.y); | |||
| } | |||
| if (g.point1 != g1 || g.point2 != g2 || fill.transform != t) | |||
| { | |||
| g.point1 = g1; | |||
| g.point2 = g2; | |||
| fill.transform = t; | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| bool DrawableShape::RelativeFillType::isDynamic() const | |||
| { | |||
| return gradientPoint1.isDynamic() || gradientPoint2.isDynamic() || gradientPoint3.isDynamic(); | |||
| } | |||
| void DrawableShape::RelativeFillType::writeTo (ValueTree& v, ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager) const | |||
| { | |||
| if (fill.isColour()) | |||
| { | |||
| v.setProperty (FillAndStrokeState::type, "solid", undoManager); | |||
| v.setProperty (FillAndStrokeState::colour, String::toHexString ((int) fill.colour.getARGB()), undoManager); | |||
| } | |||
| else if (fill.isGradient()) | |||
| { | |||
| v.setProperty (FillAndStrokeState::type, "gradient", undoManager); | |||
| v.setProperty (FillAndStrokeState::gradientPoint1, gradientPoint1.toString(), undoManager); | |||
| v.setProperty (FillAndStrokeState::gradientPoint2, gradientPoint2.toString(), undoManager); | |||
| v.setProperty (FillAndStrokeState::gradientPoint3, gradientPoint3.toString(), undoManager); | |||
| const ColourGradient& cg = *fill.gradient; | |||
| v.setProperty (FillAndStrokeState::radial, cg.isRadial, undoManager); | |||
| String s; | |||
| for (int i = 0; i < cg.getNumColours(); ++i) | |||
| s << ' ' << cg.getColourPosition (i) | |||
| << ' ' << String::toHexString ((int) cg.getColour(i).getARGB()); | |||
| v.setProperty (FillAndStrokeState::colours, s.trimStart(), undoManager); | |||
| } | |||
| else if (fill.isTiledImage()) | |||
| { | |||
| v.setProperty (FillAndStrokeState::type, "image", undoManager); | |||
| if (imageProvider != nullptr) | |||
| v.setProperty (FillAndStrokeState::imageId, imageProvider->getIdentifierForImage (fill.image), undoManager); | |||
| if (fill.getOpacity() < 1.0f) | |||
| v.setProperty (FillAndStrokeState::imageOpacity, fill.getOpacity(), undoManager); | |||
| else | |||
| v.removeProperty (FillAndStrokeState::imageOpacity, undoManager); | |||
| } | |||
| else | |||
| { | |||
| jassertfalse; | |||
| } | |||
| } | |||
| bool DrawableShape::RelativeFillType::readFrom (const ValueTree& v, ComponentBuilder::ImageProvider* imageProvider) | |||
| { | |||
| const String newType (v [FillAndStrokeState::type].toString()); | |||
| if (newType == "solid") | |||
| { | |||
| const String colourString (v [FillAndStrokeState::colour].toString()); | |||
| fill.setColour (colourString.isEmpty() ? Colours::black | |||
| : Colour::fromString (colourString)); | |||
| return true; | |||
| } | |||
| else if (newType == "gradient") | |||
| { | |||
| ColourGradient g; | |||
| g.isRadial = v [FillAndStrokeState::radial]; | |||
| StringArray colourSteps; | |||
| colourSteps.addTokens (v [FillAndStrokeState::colours].toString(), false); | |||
| for (int i = 0; i < colourSteps.size() / 2; ++i) | |||
| g.addColour (colourSteps[i * 2].getDoubleValue(), | |||
| Colour::fromString (colourSteps[i * 2 + 1])); | |||
| fill.setGradient (g); | |||
| gradientPoint1 = RelativePoint (v [FillAndStrokeState::gradientPoint1]); | |||
| gradientPoint2 = RelativePoint (v [FillAndStrokeState::gradientPoint2]); | |||
| gradientPoint3 = RelativePoint (v [FillAndStrokeState::gradientPoint3]); | |||
| return true; | |||
| } | |||
| else if (newType == "image") | |||
| { | |||
| Image im; | |||
| if (imageProvider != nullptr) | |||
| im = imageProvider->getImageForIdentifier (v [FillAndStrokeState::imageId]); | |||
| fill.setTiledImage (im, AffineTransform()); | |||
| fill.setOpacity ((float) v.getProperty (FillAndStrokeState::imageOpacity, 1.0f)); | |||
| return true; | |||
| } | |||
| jassertfalse; | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| const Identifier DrawableShape::FillAndStrokeState::type ("type"); | |||
| const Identifier DrawableShape::FillAndStrokeState::colour ("colour"); | |||
| const Identifier DrawableShape::FillAndStrokeState::colours ("colours"); | |||
| const Identifier DrawableShape::FillAndStrokeState::fill ("Fill"); | |||
| const Identifier DrawableShape::FillAndStrokeState::stroke ("Stroke"); | |||
| const Identifier DrawableShape::FillAndStrokeState::path ("Path"); | |||
| const Identifier DrawableShape::FillAndStrokeState::jointStyle ("jointStyle"); | |||
| const Identifier DrawableShape::FillAndStrokeState::capStyle ("capStyle"); | |||
| const Identifier DrawableShape::FillAndStrokeState::strokeWidth ("strokeWidth"); | |||
| const Identifier DrawableShape::FillAndStrokeState::gradientPoint1 ("point1"); | |||
| const Identifier DrawableShape::FillAndStrokeState::gradientPoint2 ("point2"); | |||
| const Identifier DrawableShape::FillAndStrokeState::gradientPoint3 ("point3"); | |||
| const Identifier DrawableShape::FillAndStrokeState::radial ("radial"); | |||
| const Identifier DrawableShape::FillAndStrokeState::imageId ("imageId"); | |||
| const Identifier DrawableShape::FillAndStrokeState::imageOpacity ("imageOpacity"); | |||
| DrawableShape::FillAndStrokeState::FillAndStrokeState (const ValueTree& state_) | |||
| : Drawable::ValueTreeWrapperBase (state_) | |||
| { | |||
| } | |||
| DrawableShape::RelativeFillType DrawableShape::FillAndStrokeState::getFill (const Identifier& fillOrStrokeType, ComponentBuilder::ImageProvider* imageProvider) const | |||
| { | |||
| DrawableShape::RelativeFillType f; | |||
| f.readFrom (state.getChildWithName (fillOrStrokeType), imageProvider); | |||
| return f; | |||
| } | |||
| ValueTree DrawableShape::FillAndStrokeState::getFillState (const Identifier& fillOrStrokeType) | |||
| { | |||
| ValueTree v (state.getChildWithName (fillOrStrokeType)); | |||
| if (v.isValid()) | |||
| return v; | |||
| setFill (fillOrStrokeType, FillType (Colours::black), nullptr, nullptr); | |||
| return getFillState (fillOrStrokeType); | |||
| } | |||
| void DrawableShape::FillAndStrokeState::setFill (const Identifier& fillOrStrokeType, const RelativeFillType& newFill, | |||
| ComponentBuilder::ImageProvider* imageProvider, UndoManager* undoManager) | |||
| { | |||
| ValueTree v (state.getOrCreateChildWithName (fillOrStrokeType, undoManager)); | |||
| newFill.writeTo (v, imageProvider, undoManager); | |||
| } | |||
| PathStrokeType DrawableShape::FillAndStrokeState::getStrokeType() const | |||
| { | |||
| const String jointStyleString (state [jointStyle].toString()); | |||
| const String capStyleString (state [capStyle].toString()); | |||
| return PathStrokeType (state [strokeWidth], | |||
| jointStyleString == "curved" ? PathStrokeType::curved | |||
| : (jointStyleString == "bevel" ? PathStrokeType::beveled | |||
| : PathStrokeType::mitered), | |||
| capStyleString == "square" ? PathStrokeType::square | |||
| : (capStyleString == "round" ? PathStrokeType::rounded | |||
| : PathStrokeType::butt)); | |||
| } | |||
| void DrawableShape::FillAndStrokeState::setStrokeType (const PathStrokeType& newStrokeType, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (strokeWidth, (double) newStrokeType.getStrokeThickness(), undoManager); | |||
| state.setProperty (jointStyle, newStrokeType.getJointStyle() == PathStrokeType::mitered | |||
| ? "miter" : (newStrokeType.getJointStyle() == PathStrokeType::curved ? "curved" : "bevel"), undoManager); | |||
| state.setProperty (capStyle, newStrokeType.getEndStyle() == PathStrokeType::butt | |||
| ? "butt" : (newStrokeType.getEndStyle() == PathStrokeType::square ? "square" : "round"), undoManager); | |||
| } | |||
| static bool replaceColourInFill (DrawableShape::RelativeFillType& fill, Colour original, Colour replacement) | |||
| static bool replaceColourInFill (FillType& fill, Colour original, Colour replacement) | |||
| { | |||
| if (fill.fill.colour == original && fill.fill.isColour()) | |||
| if (fill.colour == original && fill.isColour()) | |||
| { | |||
| fill = FillType (replacement); | |||
| return true; | |||
| @@ -495,7 +174,7 @@ bool DrawableShape::replaceColour (Colour original, Colour replacement) | |||
| Path DrawableShape::getOutlineAsPath() const | |||
| { | |||
| Path outline (isStrokeVisible() ? strokePath : path); | |||
| auto outline = isStrokeVisible() ? strokePath : path; | |||
| outline.applyTransform (getTransform()); | |||
| return outline; | |||
| } | |||
| @@ -45,31 +45,6 @@ public: | |||
| /** Destructor. */ | |||
| ~DrawableShape(); | |||
| //============================================================================== | |||
| /** A FillType wrapper that allows the gradient coordinates to be implemented using RelativePoint. | |||
| */ | |||
| class RelativeFillType | |||
| { | |||
| public: | |||
| RelativeFillType(); | |||
| RelativeFillType (const FillType& fill); | |||
| RelativeFillType (const RelativeFillType&); | |||
| RelativeFillType& operator= (const RelativeFillType&); | |||
| bool operator== (const RelativeFillType&) const; | |||
| bool operator!= (const RelativeFillType&) const; | |||
| bool isDynamic() const; | |||
| bool recalculateCoords (Expression::Scope* scope); | |||
| void writeTo (ValueTree& v, ComponentBuilder::ImageProvider*, UndoManager*) const; | |||
| bool readFrom (const ValueTree& v, ComponentBuilder::ImageProvider*); | |||
| //============================================================================== | |||
| FillType fill; | |||
| RelativePoint gradientPoint1, gradientPoint2, gradientPoint3; | |||
| }; | |||
| //============================================================================== | |||
| /** Sets a fill type for the path. | |||
| This colour is used to fill the path - if you don't want the path to be | |||
| @@ -80,34 +55,20 @@ public: | |||
| */ | |||
| void setFill (const FillType& newFill); | |||
| /** Sets a fill type for the path. | |||
| This colour is used to fill the path - if you don't want the path to be | |||
| filled (e.g. if you're just drawing an outline), set this to a transparent | |||
| colour. | |||
| @see setPath, setStrokeFill | |||
| */ | |||
| void setFill (const RelativeFillType& newFill); | |||
| /** Returns the current fill type. | |||
| @see setFill | |||
| */ | |||
| const RelativeFillType& getFill() const noexcept { return mainFill; } | |||
| const FillType& getFill() const noexcept { return mainFill; } | |||
| /** Sets the fill type with which the outline will be drawn. | |||
| @see setFill | |||
| */ | |||
| void setStrokeFill (const FillType& newStrokeFill); | |||
| /** Sets the fill type with which the outline will be drawn. | |||
| @see setFill | |||
| */ | |||
| void setStrokeFill (const RelativeFillType& newStrokeFill); | |||
| /** Returns the current stroke fill. | |||
| @see setStrokeFill | |||
| */ | |||
| const RelativeFillType& getStrokeFill() const noexcept { return strokeFill; } | |||
| const FillType& getStrokeFill() const noexcept { return strokeFill; } | |||
| /** Changes the properties of the outline that will be drawn around the path. | |||
| If the stroke has 0 thickness, no stroke will be drawn. | |||
| @@ -130,24 +91,6 @@ public: | |||
| const Array<float>& getDashLengths() const noexcept { return dashLengths; } | |||
| //============================================================================== | |||
| /** @internal */ | |||
| class FillAndStrokeState : public Drawable::ValueTreeWrapperBase | |||
| { | |||
| public: | |||
| FillAndStrokeState (const ValueTree& state); | |||
| ValueTree getFillState (const Identifier& fillOrStrokeType); | |||
| RelativeFillType getFill (const Identifier& fillOrStrokeType, ComponentBuilder::ImageProvider*) const; | |||
| void setFill (const Identifier& fillOrStrokeType, const RelativeFillType& newFill, | |||
| ComponentBuilder::ImageProvider*, UndoManager*); | |||
| PathStrokeType getStrokeType() const; | |||
| void setStrokeType (const PathStrokeType& newStrokeType, UndoManager*); | |||
| static const Identifier type, colour, colours, fill, stroke, path, jointStyle, capStyle, strokeWidth, | |||
| gradientPoint1, gradientPoint2, gradientPoint3, radial, imageId, imageOpacity; | |||
| }; | |||
| /** @internal */ | |||
| Rectangle<float> getDrawableBounds() const override; | |||
| /** @internal */ | |||
| @@ -167,10 +110,6 @@ protected: | |||
| void strokeChanged(); | |||
| /** True if there's a stroke with a non-zero thickness and non-transparent colour. */ | |||
| bool isStrokeVisible() const noexcept; | |||
| /** Updates the details from a FillAndStrokeState object, returning true if something changed. */ | |||
| void refreshFillTypes (const FillAndStrokeState& newState, ComponentBuilder::ImageProvider*); | |||
| /** Writes the stroke and fill details to a FillAndStrokeState object. */ | |||
| void writeTo (FillAndStrokeState& state, ComponentBuilder::ImageProvider*, UndoManager*) const; | |||
| //============================================================================== | |||
| PathStrokeType strokeType; | |||
| @@ -178,12 +117,7 @@ protected: | |||
| Path path, strokePath; | |||
| private: | |||
| class RelativePositioner; | |||
| RelativeFillType mainFill, strokeFill; | |||
| ScopedPointer<RelativeCoordinatePositionerBase> mainFillPositioner, strokeFillPositioner; | |||
| void setFillInternal (RelativeFillType& fill, const RelativeFillType& newFill, | |||
| ScopedPointer<RelativeCoordinatePositionerBase>& positioner); | |||
| FillType mainFill, strokeFill; | |||
| DrawableShape& operator= (const DrawableShape&); | |||
| }; | |||
| @@ -31,9 +31,7 @@ DrawableText::DrawableText() | |||
| : colour (Colours::black), | |||
| justification (Justification::centredLeft) | |||
| { | |||
| setBoundingBox (RelativeParallelogram (RelativePoint (0.0f, 0.0f), | |||
| RelativePoint (50.0f, 0.0f), | |||
| RelativePoint (0.0f, 20.0f))); | |||
| setBoundingBox (Parallelogram<float> ({ 0.0f, 0.0f, 50.0f, 20.0f })); | |||
| setFont (Font (15.0f), true); | |||
| } | |||
| @@ -54,6 +52,11 @@ DrawableText::~DrawableText() | |||
| { | |||
| } | |||
| Drawable* DrawableText::createCopy() const | |||
| { | |||
| return new DrawableText (*this); | |||
| } | |||
| //============================================================================== | |||
| void DrawableText::setText (const String& newText) | |||
| { | |||
| @@ -95,7 +98,7 @@ void DrawableText::setJustification (Justification newJustification) | |||
| repaint(); | |||
| } | |||
| void DrawableText::setBoundingBox (const RelativeParallelogram& newBounds) | |||
| void DrawableText::setBoundingBox (Parallelogram<float> newBounds) | |||
| { | |||
| if (bounds != newBounds) | |||
| { | |||
| @@ -104,7 +107,7 @@ void DrawableText::setBoundingBox (const RelativeParallelogram& newBounds) | |||
| } | |||
| } | |||
| void DrawableText::setFontHeight (const RelativeCoordinate& newHeight) | |||
| void DrawableText::setFontHeight (float newHeight) | |||
| { | |||
| if (fontHeight != newHeight) | |||
| { | |||
| @@ -113,7 +116,7 @@ void DrawableText::setFontHeight (const RelativeCoordinate& newHeight) | |||
| } | |||
| } | |||
| void DrawableText::setFontHorizontalScale (const RelativeCoordinate& newScale) | |||
| void DrawableText::setFontHorizontalScale (float newScale) | |||
| { | |||
| if (fontHScale != newScale) | |||
| { | |||
| @@ -124,37 +127,11 @@ void DrawableText::setFontHorizontalScale (const RelativeCoordinate& newScale) | |||
| void DrawableText::refreshBounds() | |||
| { | |||
| if (bounds.isDynamic() || fontHeight.isDynamic() || fontHScale.isDynamic()) | |||
| { | |||
| Drawable::Positioner<DrawableText>* const p = new Drawable::Positioner<DrawableText> (*this); | |||
| setPositioner (p); | |||
| p->apply(); | |||
| } | |||
| else | |||
| { | |||
| setPositioner (0); | |||
| recalculateCoordinates (0); | |||
| } | |||
| } | |||
| auto w = bounds.getWidth(); | |||
| auto h = bounds.getHeight(); | |||
| bool DrawableText::registerCoordinates (RelativeCoordinatePositionerBase& pos) | |||
| { | |||
| bool ok = pos.addPoint (bounds.topLeft); | |||
| ok = pos.addPoint (bounds.topRight) && ok; | |||
| ok = pos.addPoint (bounds.bottomLeft) && ok; | |||
| ok = pos.addCoordinate (fontHeight) && ok; | |||
| return pos.addCoordinate (fontHScale) && ok; | |||
| } | |||
| void DrawableText::recalculateCoordinates (Expression::Scope* scope) | |||
| { | |||
| bounds.resolveThreePoints (resolvedPoints, scope); | |||
| const float w = Line<float> (resolvedPoints[0], resolvedPoints[1]).getLength(); | |||
| const float h = Line<float> (resolvedPoints[0], resolvedPoints[2]).getLength(); | |||
| const float height = jlimit (0.01f, jmax (0.01f, h), (float) fontHeight.resolve (scope)); | |||
| const float hscale = jlimit (0.01f, jmax (0.01f, w), (float) fontHScale.resolve (scope)); | |||
| auto height = jlimit (0.01f, jmax (0.01f, h), fontHeight); | |||
| auto hscale = jlimit (0.01f, jmax (0.01f, w), fontHScale); | |||
| scaledFont = font; | |||
| scaledFont.setHeight (height); | |||
| @@ -172,17 +149,17 @@ Rectangle<int> DrawableText::getTextArea (float w, float h) const | |||
| AffineTransform DrawableText::getTextTransform (float w, float h) const | |||
| { | |||
| return AffineTransform::fromTargetPoints (0, 0, resolvedPoints[0].x, resolvedPoints[0].y, | |||
| w, 0, resolvedPoints[1].x, resolvedPoints[1].y, | |||
| 0, h, resolvedPoints[2].x, resolvedPoints[2].y); | |||
| return AffineTransform::fromTargetPoints (Point<float>(), bounds.topLeft, | |||
| Point<float> (w, 0), bounds.topRight, | |||
| Point<float> (0, h), bounds.bottomLeft); | |||
| } | |||
| void DrawableText::paint (Graphics& g) | |||
| { | |||
| transformContextToCorrectOrigin (g); | |||
| const float w = Line<float> (resolvedPoints[0], resolvedPoints[1]).getLength(); | |||
| const float h = Line<float> (resolvedPoints[0], resolvedPoints[2]).getLength(); | |||
| auto w = bounds.getWidth(); | |||
| auto h = bounds.getHeight(); | |||
| g.addTransform (getTextTransform (w, h)); | |||
| g.setFont (scaledFont); | |||
| @@ -193,166 +170,14 @@ void DrawableText::paint (Graphics& g) | |||
| Rectangle<float> DrawableText::getDrawableBounds() const | |||
| { | |||
| return RelativeParallelogram::getBoundingBox (resolvedPoints); | |||
| } | |||
| Drawable* DrawableText::createCopy() const | |||
| { | |||
| return new DrawableText (*this); | |||
| } | |||
| //============================================================================== | |||
| const Identifier DrawableText::valueTreeType ("Text"); | |||
| const Identifier DrawableText::ValueTreeWrapper::text ("text"); | |||
| const Identifier DrawableText::ValueTreeWrapper::colour ("colour"); | |||
| const Identifier DrawableText::ValueTreeWrapper::font ("font"); | |||
| const Identifier DrawableText::ValueTreeWrapper::justification ("justification"); | |||
| const Identifier DrawableText::ValueTreeWrapper::topLeft ("topLeft"); | |||
| const Identifier DrawableText::ValueTreeWrapper::topRight ("topRight"); | |||
| const Identifier DrawableText::ValueTreeWrapper::bottomLeft ("bottomLeft"); | |||
| const Identifier DrawableText::ValueTreeWrapper::fontHeight ("fontHeight"); | |||
| const Identifier DrawableText::ValueTreeWrapper::fontHScale ("fontHScale"); | |||
| //============================================================================== | |||
| DrawableText::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
| : ValueTreeWrapperBase (state_) | |||
| { | |||
| jassert (state.hasType (valueTreeType)); | |||
| } | |||
| String DrawableText::ValueTreeWrapper::getText() const | |||
| { | |||
| return state [text].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setText (const String& newText, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (text, newText, undoManager); | |||
| } | |||
| Value DrawableText::ValueTreeWrapper::getTextValue (UndoManager* undoManager) | |||
| { | |||
| return state.getPropertyAsValue (text, undoManager); | |||
| } | |||
| Colour DrawableText::ValueTreeWrapper::getColour() const | |||
| { | |||
| return Colour::fromString (state [colour].toString()); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setColour (Colour newColour, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (colour, newColour.toString(), undoManager); | |||
| } | |||
| Justification DrawableText::ValueTreeWrapper::getJustification() const | |||
| { | |||
| return Justification ((int) state [justification]); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setJustification (Justification newJustification, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (justification, newJustification.getFlags(), undoManager); | |||
| } | |||
| Font DrawableText::ValueTreeWrapper::getFont() const | |||
| { | |||
| return Font::fromString (state [font]); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setFont (const Font& newFont, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (font, newFont.toString(), undoManager); | |||
| } | |||
| Value DrawableText::ValueTreeWrapper::getFontValue (UndoManager* undoManager) | |||
| { | |||
| return state.getPropertyAsValue (font, undoManager); | |||
| } | |||
| RelativeParallelogram DrawableText::ValueTreeWrapper::getBoundingBox() const | |||
| { | |||
| return RelativeParallelogram (state [topLeft].toString(), state [topRight].toString(), state [bottomLeft].toString()); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (topLeft, newBounds.topLeft.toString(), undoManager); | |||
| state.setProperty (topRight, newBounds.topRight.toString(), undoManager); | |||
| state.setProperty (bottomLeft, newBounds.bottomLeft.toString(), undoManager); | |||
| } | |||
| RelativeCoordinate DrawableText::ValueTreeWrapper::getFontHeight() const | |||
| { | |||
| return state [fontHeight].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setFontHeight (const RelativeCoordinate& coord, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (fontHeight, coord.toString(), undoManager); | |||
| } | |||
| RelativeCoordinate DrawableText::ValueTreeWrapper::getFontHorizontalScale() const | |||
| { | |||
| return state [fontHScale].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setFontHorizontalScale (const RelativeCoordinate& coord, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (fontHScale, coord.toString(), undoManager); | |||
| } | |||
| //============================================================================== | |||
| void DrawableText::refreshFromValueTree (const ValueTree& tree, ComponentBuilder&) | |||
| { | |||
| ValueTreeWrapper v (tree); | |||
| setComponentID (v.getID()); | |||
| const RelativeParallelogram newBounds (v.getBoundingBox()); | |||
| const RelativeCoordinate newFontHeight (v.getFontHeight()); | |||
| const RelativeCoordinate newFontHScale (v.getFontHorizontalScale()); | |||
| const Colour newColour (v.getColour()); | |||
| const Justification newJustification (v.getJustification()); | |||
| const String newText (v.getText()); | |||
| const Font newFont (v.getFont()); | |||
| if (text != newText || font != newFont || justification != newJustification | |||
| || colour != newColour || bounds != newBounds | |||
| || newFontHeight != fontHeight || newFontHScale != fontHScale) | |||
| { | |||
| setBoundingBox (newBounds); | |||
| setFontHeight (newFontHeight); | |||
| setFontHorizontalScale (newFontHScale); | |||
| setColour (newColour); | |||
| setFont (newFont, false); | |||
| setJustification (newJustification); | |||
| setText (newText); | |||
| } | |||
| } | |||
| ValueTree DrawableText::createValueTree (ComponentBuilder::ImageProvider*) const | |||
| { | |||
| ValueTree tree (valueTreeType); | |||
| ValueTreeWrapper v (tree); | |||
| v.setID (getComponentID()); | |||
| v.setText (text, nullptr); | |||
| v.setFont (font, nullptr); | |||
| v.setJustification (justification, nullptr); | |||
| v.setColour (colour, nullptr); | |||
| v.setBoundingBox (bounds, nullptr); | |||
| v.setFontHeight (fontHeight, nullptr); | |||
| v.setFontHorizontalScale (fontHScale, nullptr); | |||
| return tree; | |||
| return bounds.getBoundingBox(); | |||
| } | |||
| Path DrawableText::getOutlineAsPath() const | |||
| { | |||
| auto w = Line<float> (resolvedPoints[0], resolvedPoints[1]).getLength(); | |||
| auto h = Line<float> (resolvedPoints[0], resolvedPoints[2]).getLength(); | |||
| const auto area = getTextArea (w, h).toFloat(); | |||
| auto w = bounds.getWidth(); | |||
| auto h = bounds.getHeight(); | |||
| auto area = getTextArea (w, h).toFloat(); | |||
| GlyphArrangement arr; | |||
| arr.addFittedText (scaledFont, text, | |||
| @@ -58,8 +58,8 @@ public: | |||
| Colour getColour() const noexcept { return colour; } | |||
| /** Sets the font to use. | |||
| Note that the font height and horizontal scale are set as RelativeCoordinates using | |||
| setFontHeight and setFontHorizontalScale. If applySizeAndScale is true, then these height | |||
| Note that the font height and horizontal scale are set using setFontHeight() and | |||
| setFontHorizontalScale(). If applySizeAndScale is true, then these height | |||
| and scale values will be changed to match the dimensions of the font supplied; | |||
| if it is false, then the new font object's height and scale are ignored. | |||
| */ | |||
| @@ -75,16 +75,16 @@ public: | |||
| Justification getJustification() const noexcept { return justification; } | |||
| /** Returns the parallelogram that defines the text bounding box. */ | |||
| const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; } | |||
| Parallelogram<float> getBoundingBox() const noexcept { return bounds; } | |||
| /** Sets the bounding box that contains the text. */ | |||
| void setBoundingBox (const RelativeParallelogram& newBounds); | |||
| void setBoundingBox (Parallelogram<float> newBounds); | |||
| const RelativeCoordinate& getFontHeight() const { return fontHeight; } | |||
| void setFontHeight (const RelativeCoordinate& newHeight); | |||
| float getFontHeight() const noexcept { return fontHeight; } | |||
| void setFontHeight (float newHeight); | |||
| const RelativeCoordinate& getFontHorizontalScale() const { return fontHScale; } | |||
| void setFontHorizontalScale (const RelativeCoordinate& newScale); | |||
| float getFontHorizontalScale() const noexcept { return fontHScale; } | |||
| void setFontHorizontalScale (float newScale); | |||
| //============================================================================== | |||
| /** @internal */ | |||
| @@ -92,62 +92,19 @@ public: | |||
| /** @internal */ | |||
| Drawable* createCopy() const override; | |||
| /** @internal */ | |||
| void refreshFromValueTree (const ValueTree& tree, ComponentBuilder& builder); | |||
| /** @internal */ | |||
| ValueTree createValueTree (ComponentBuilder::ImageProvider* imageProvider) const override; | |||
| /** @internal */ | |||
| static const Identifier valueTreeType; | |||
| /** @internal */ | |||
| Rectangle<float> getDrawableBounds() const override; | |||
| /** @internal */ | |||
| Path getOutlineAsPath() const override; | |||
| //============================================================================== | |||
| /** Internally-used class for wrapping a DrawableText's state into a ValueTree. */ | |||
| class ValueTreeWrapper : public Drawable::ValueTreeWrapperBase | |||
| { | |||
| public: | |||
| ValueTreeWrapper (const ValueTree& state); | |||
| String getText() const; | |||
| void setText (const String& newText, UndoManager* undoManager); | |||
| Value getTextValue (UndoManager* undoManager); | |||
| Colour getColour() const; | |||
| void setColour (Colour newColour, UndoManager* undoManager); | |||
| Justification getJustification() const; | |||
| void setJustification (Justification newJustification, UndoManager* undoManager); | |||
| Font getFont() const; | |||
| void setFont (const Font& newFont, UndoManager* undoManager); | |||
| Value getFontValue (UndoManager* undoManager); | |||
| RelativeParallelogram getBoundingBox() const; | |||
| void setBoundingBox (const RelativeParallelogram& newBounds, UndoManager* undoManager); | |||
| RelativeCoordinate getFontHeight() const; | |||
| void setFontHeight (const RelativeCoordinate& newHeight, UndoManager* undoManager); | |||
| RelativeCoordinate getFontHorizontalScale() const; | |||
| void setFontHorizontalScale (const RelativeCoordinate& newScale, UndoManager* undoManager); | |||
| static const Identifier text, colour, font, justification, topLeft, topRight, bottomLeft, fontHeight, fontHScale; | |||
| }; | |||
| private: | |||
| //============================================================================== | |||
| RelativeParallelogram bounds; | |||
| RelativeCoordinate fontHeight, fontHScale; | |||
| Point<float> resolvedPoints[3]; | |||
| Parallelogram<float> bounds; | |||
| float fontHeight, fontHScale; | |||
| Font font, scaledFont; | |||
| String text; | |||
| Colour colour; | |||
| Justification justification; | |||
| friend class Drawable::Positioner<DrawableText>; | |||
| bool registerCoordinates (RelativeCoordinatePositionerBase&); | |||
| void recalculateCoordinates (Expression::Scope*); | |||
| void refreshBounds(); | |||
| Rectangle<int> getTextArea (float width, float height) const; | |||
| AffineTransform getTextTransform (float width, float height) const; | |||
| @@ -196,10 +196,7 @@ public: | |||
| newState.parseSubElements (xml, *drawable); | |||
| drawable->setContentArea (RelativeRectangle (RelativeCoordinate (viewboxXY.x), | |||
| RelativeCoordinate (viewboxXY.x + newState.viewBoxW), | |||
| RelativeCoordinate (viewboxXY.y), | |||
| RelativeCoordinate (viewboxXY.y + newState.viewBoxH))); | |||
| drawable->setContentArea ({ viewboxXY.x, viewboxXY.y, newState.viewBoxW, newState.viewBoxH }); | |||
| drawable->resetBoundingBoxToContentArea(); | |||
| return drawable; | |||
| @@ -182,7 +182,6 @@ ComponentBuilder::TypeHandler* ComponentBuilder::getHandler (const int index) co | |||
| void ComponentBuilder::registerStandardComponentTypes() | |||
| { | |||
| Drawable::registerDrawableTypeHandlers (*this); | |||
| } | |||
| void ComponentBuilder::setImageProvider (ImageProvider* newImageProvider) noexcept | |||
| @@ -95,9 +95,9 @@ AffineTransform RelativeParallelogram::resetToPerpendicular (Expression::Scope* | |||
| topRight.moveToAbsolute (newTopRight, scope); | |||
| bottomLeft.moveToAbsolute (newBottomLeft, scope); | |||
| return AffineTransform::fromTargetPoints (corners[0].x, corners[0].y, corners[0].x, corners[0].y, | |||
| corners[1].x, corners[1].y, newTopRight.x, newTopRight.y, | |||
| corners[2].x, corners[2].y, newBottomLeft.x, newBottomLeft.y); | |||
| return AffineTransform::fromTargetPoints (corners[0], corners[0], | |||
| corners[1], newTopRight, | |||
| corners[2], newBottomLeft); | |||
| } | |||
| bool RelativeParallelogram::isDynamic() const | |||
| @@ -147,13 +147,6 @@ RelativePointPath::StartSubPath::StartSubPath (const RelativePoint& pos) | |||
| { | |||
| } | |||
| ValueTree RelativePointPath::StartSubPath::createTree() const | |||
| { | |||
| ValueTree v (DrawablePath::ValueTreeWrapper::Element::startSubPathElement); | |||
| v.setProperty (DrawablePath::ValueTreeWrapper::point1, startPos.toString(), nullptr); | |||
| return v; | |||
| } | |||
| void RelativePointPath::StartSubPath::addToPath (Path& path, Expression::Scope* scope) const | |||
| { | |||
| path.startNewSubPath (startPos.resolve (scope)); | |||
| @@ -176,11 +169,6 @@ RelativePointPath::CloseSubPath::CloseSubPath() | |||
| { | |||
| } | |||
| ValueTree RelativePointPath::CloseSubPath::createTree() const | |||
| { | |||
| return ValueTree (DrawablePath::ValueTreeWrapper::Element::closeSubPathElement); | |||
| } | |||
| void RelativePointPath::CloseSubPath::addToPath (Path& path, Expression::Scope*) const | |||
| { | |||
| path.closeSubPath(); | |||
| @@ -203,13 +191,6 @@ RelativePointPath::LineTo::LineTo (const RelativePoint& endPoint_) | |||
| { | |||
| } | |||
| ValueTree RelativePointPath::LineTo::createTree() const | |||
| { | |||
| ValueTree v (DrawablePath::ValueTreeWrapper::Element::lineToElement); | |||
| v.setProperty (DrawablePath::ValueTreeWrapper::point1, endPoint.toString(), nullptr); | |||
| return v; | |||
| } | |||
| void RelativePointPath::LineTo::addToPath (Path& path, Expression::Scope* scope) const | |||
| { | |||
| path.lineTo (endPoint.resolve (scope)); | |||
| @@ -234,14 +215,6 @@ RelativePointPath::QuadraticTo::QuadraticTo (const RelativePoint& controlPoint, | |||
| controlPoints[1] = endPoint; | |||
| } | |||
| ValueTree RelativePointPath::QuadraticTo::createTree() const | |||
| { | |||
| ValueTree v (DrawablePath::ValueTreeWrapper::Element::quadraticToElement); | |||
| v.setProperty (DrawablePath::ValueTreeWrapper::point1, controlPoints[0].toString(), nullptr); | |||
| v.setProperty (DrawablePath::ValueTreeWrapper::point2, controlPoints[1].toString(), nullptr); | |||
| return v; | |||
| } | |||
| void RelativePointPath::QuadraticTo::addToPath (Path& path, Expression::Scope* scope) const | |||
| { | |||
| path.quadraticTo (controlPoints[0].resolve (scope), | |||
| @@ -269,15 +242,6 @@ RelativePointPath::CubicTo::CubicTo (const RelativePoint& controlPoint1, const R | |||
| controlPoints[2] = endPoint; | |||
| } | |||
| ValueTree RelativePointPath::CubicTo::createTree() const | |||
| { | |||
| ValueTree v (DrawablePath::ValueTreeWrapper::Element::cubicToElement); | |||
| v.setProperty (DrawablePath::ValueTreeWrapper::point1, controlPoints[0].toString(), nullptr); | |||
| v.setProperty (DrawablePath::ValueTreeWrapper::point2, controlPoints[1].toString(), nullptr); | |||
| v.setProperty (DrawablePath::ValueTreeWrapper::point3, controlPoints[2].toString(), nullptr); | |||
| return v; | |||
| } | |||
| void RelativePointPath::CubicTo::addToPath (Path& path, Expression::Scope* scope) const | |||
| { | |||
| path.cubicTo (controlPoints[0].resolve (scope), | |||
| @@ -80,7 +80,6 @@ public: | |||
| public: | |||
| ElementBase (ElementType type); | |||
| virtual ~ElementBase() {} | |||
| virtual ValueTree createTree() const = 0; | |||
| virtual void addToPath (Path& path, Expression::Scope*) const = 0; | |||
| virtual RelativePoint* getControlPoints (int& numPoints) = 0; | |||
| virtual ElementBase* clone() const = 0; | |||
| @@ -97,7 +96,6 @@ public: | |||
| { | |||
| public: | |||
| StartSubPath (const RelativePoint& pos); | |||
| ValueTree createTree() const; | |||
| void addToPath (Path& path, Expression::Scope*) const; | |||
| RelativePoint* getControlPoints (int& numPoints); | |||
| ElementBase* clone() const; | |||
| @@ -113,7 +111,6 @@ public: | |||
| { | |||
| public: | |||
| CloseSubPath(); | |||
| ValueTree createTree() const; | |||
| void addToPath (Path& path, Expression::Scope*) const; | |||
| RelativePoint* getControlPoints (int& numPoints); | |||
| ElementBase* clone() const; | |||
| @@ -127,7 +124,6 @@ public: | |||
| { | |||
| public: | |||
| LineTo (const RelativePoint& endPoint); | |||
| ValueTree createTree() const; | |||
| void addToPath (Path& path, Expression::Scope*) const; | |||
| RelativePoint* getControlPoints (int& numPoints); | |||
| ElementBase* clone() const; | |||