| @@ -485,9 +485,7 @@ public: | |||||
| if (svgDrawable != nullptr) | if (svgDrawable != nullptr) | ||||
| { | { | ||||
| // to make our icon the right size, we'll set its bounding box to the size and position that we want. | // 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) | 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()) | if (! r.isEmpty()) | ||||
| contentBounds = r; | 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; | Path p; | ||||
| p.addRectangle (area); | p.addRectangle (area); | ||||
| @@ -2210,8 +2210,7 @@ public: | |||||
| /** Attempts to set the component's position to the given rectangle. | /** Attempts to set the component's position to the given rectangle. | ||||
| Unlike simply calling Component::setBounds(), this may involve the positioner | 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; | virtual void applyNewBounds (const Rectangle<int>& newBounds) = 0; | ||||
| @@ -209,74 +209,4 @@ Drawable* Drawable::createFromImageFile (const File& file) | |||||
| return fin.openedOk() ? createFromImageDataStream (fin) : nullptr; | 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 | } // namespace juce | ||||
| @@ -33,8 +33,7 @@ namespace juce | |||||
| @see DrawableComposite, DrawableImage, DrawablePath, DrawableText | @see DrawableComposite, DrawableImage, DrawablePath, DrawableText | ||||
| */ | */ | ||||
| class JUCE_API Drawable : public Component, | |||||
| public MarkerList::MarkerListHolder | |||||
| class JUCE_API Drawable : public Component | |||||
| { | { | ||||
| protected: | protected: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -177,22 +176,6 @@ public: | |||||
| static Path parseSVGPath (const String& svgPath); | 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. | /** Returns the area that this drawble covers. | ||||
| The result is expressed in this drawable's own coordinate space, and does not take | 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. | into account any transforms that may be applied to the component. | ||||
| @@ -204,30 +187,6 @@ public: | |||||
| */ | */ | ||||
| virtual bool replaceColour (Colour originalColour, Colour replacementColour); | 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: | protected: | ||||
| //============================================================================== | //============================================================================== | ||||
| friend class DrawableComposite; | friend class DrawableComposite; | ||||
| @@ -245,42 +204,9 @@ protected: | |||||
| Point<int> originRelativeToComponent; | Point<int> originRelativeToComponent; | ||||
| ScopedPointer<Drawable> drawableClipPath; | 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&); | void nonConstDraw (Graphics&, float opacity, const AffineTransform&); | ||||
| Drawable (const Drawable&); | |||||
| Drawable& operator= (const Drawable&); | Drawable& operator= (const Drawable&); | ||||
| JUCE_LEAK_DETECTOR (Drawable) | JUCE_LEAK_DETECTOR (Drawable) | ||||
| }; | }; | ||||
| @@ -28,16 +28,15 @@ namespace juce | |||||
| { | { | ||||
| DrawableComposite::DrawableComposite() | 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) | DrawableComposite::DrawableComposite (const DrawableComposite& other) | ||||
| : Drawable (other), | : Drawable (other), | ||||
| bounds (other.bounds), | bounds (other.bounds), | ||||
| markersX (other.markersX), | |||||
| markersY (other.markersY) | |||||
| contentArea (other.contentArea) | |||||
| { | { | ||||
| for (auto* c : other.getChildren()) | for (auto* c : other.getChildren()) | ||||
| if (auto* d = dynamic_cast<const Drawable*> (c)) | if (auto* d = dynamic_cast<const Drawable*> (c)) | ||||
| @@ -67,87 +66,44 @@ Rectangle<float> DrawableComposite::getDrawableBounds() const | |||||
| return r; | 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) | if (bounds != newBounds) | ||||
| { | { | ||||
| 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() | 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() | void DrawableComposite::resetContentAreaAndBoundingBoxToFitChildren() | ||||
| { | { | ||||
| setContentArea (RelativeRectangle (getDrawableBounds())); | |||||
| setContentArea (getDrawableBounds()); | |||||
| resetBoundingBoxToContentArea(); | 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() | void DrawableComposite::parentHierarchyChanged() | ||||
| { | { | ||||
| if (auto* parent = getParent()) | 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 DrawableComposite::getOutlineAsPath() const | ||||
| { | { | ||||
| Path p; | Path p; | ||||
| @@ -55,12 +55,17 @@ public: | |||||
| /** Sets the parallelogram that defines the target position of the content rectangle when the drawable is rendered. | /** Sets the parallelogram that defines the target position of the content rectangle when the drawable is rendered. | ||||
| @see setContentArea | @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. | /** Returns the parallelogram that defines the target position of the content rectangle when the drawable is rendered. | ||||
| @see setBoundingBox | @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 | /** Changes the bounding box transform to match the content area, so that any sub-items will | ||||
| be drawn at their untransformed positions. | be drawn at their untransformed positions. | ||||
| @@ -68,44 +73,24 @@ public: | |||||
| void resetBoundingBoxToContentArea(); | void resetBoundingBoxToContentArea(); | ||||
| /** Returns the main content rectangle. | /** 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 | @see contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName | ||||
| */ | */ | ||||
| RelativeRectangle getContentArea() const; | |||||
| Rectangle<float> getContentArea() const noexcept { return contentArea; } | |||||
| /** Changes the main content area. | /** 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 | @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 | /** 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(); | 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 */ | /** @internal */ | ||||
| Drawable* createCopy() const override; | Drawable* createCopy() const override; | ||||
| /** @internal */ | /** @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; | Rectangle<float> getDrawableBounds() const override; | ||||
| /** @internal */ | /** @internal */ | ||||
| void childBoundsChanged (Component*) override; | void childBoundsChanged (Component*) override; | ||||
| @@ -114,46 +99,14 @@ public: | |||||
| /** @internal */ | /** @internal */ | ||||
| void parentHierarchyChanged() override; | void parentHierarchyChanged() override; | ||||
| /** @internal */ | /** @internal */ | ||||
| MarkerList* getMarkers (bool xAxis) override; | |||||
| /** @internal */ | |||||
| Path getOutlineAsPath() const override; | 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: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| RelativeParallelogram bounds; | |||||
| MarkerList markersX, markersY; | |||||
| Parallelogram<float> bounds; | |||||
| Rectangle<float> contentArea; | |||||
| bool updateBoundsReentrant = false; | bool updateBoundsReentrant = false; | ||||
| friend class Drawable::Positioner<DrawableComposite>; | |||||
| bool registerCoordinates (RelativeCoordinatePositionerBase&); | |||||
| void recalculateCoordinates (Expression::Scope*); | |||||
| void updateBoundsToFitChildren(); | void updateBoundsToFitChildren(); | ||||
| DrawableComposite& operator= (const DrawableComposite&); | DrawableComposite& operator= (const DrawableComposite&); | ||||
| @@ -27,12 +27,8 @@ | |||||
| namespace juce | 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) | 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) | 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) | void DrawableImage::setOpacity (const float newOpacity) | ||||
| @@ -73,52 +72,31 @@ void DrawableImage::setOverlayColour (Colour newOverlayColour) | |||||
| overlayColour = 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; | 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 | Path DrawableImage::getOutlineAsPath() const | ||||
| { | { | ||||
| return {}; // not applicable for images | return {}; // not applicable for images | ||||
| @@ -71,13 +71,16 @@ public: | |||||
| Colour getOverlayColour() const noexcept { return overlayColour; } | Colour getOverlayColour() const noexcept { return overlayColour; } | ||||
| /** Sets the bounding box within which the image should be displayed. */ | /** 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 | /** Returns the position to which the image's top-left corner should be remapped in the target | ||||
| coordinate space when rendering this object. | coordinate space when rendering this object. | ||||
| @see setTransform | @see setTransform | ||||
| */ | */ | ||||
| const RelativeParallelogram& getBoundingBox() const noexcept { return bounds; } | |||||
| Parallelogram<float> getBoundingBox() const noexcept { return bounds; } | |||||
| //============================================================================== | //============================================================================== | ||||
| /** @internal */ | /** @internal */ | ||||
| @@ -89,49 +92,14 @@ public: | |||||
| /** @internal */ | /** @internal */ | ||||
| Rectangle<float> getDrawableBounds() const override; | Rectangle<float> getDrawableBounds() const override; | ||||
| /** @internal */ | /** @internal */ | ||||
| void refreshFromValueTree (const ValueTree& tree, ComponentBuilder&); | |||||
| /** @internal */ | |||||
| ValueTree createValueTree (ComponentBuilder::ImageProvider*) const override; | |||||
| /** @internal */ | |||||
| static const Identifier valueTreeType; | |||||
| /** @internal */ | |||||
| Path getOutlineAsPath() const override; | 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: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| Image image; | 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&); | DrawableImage& operator= (const DrawableImage&); | ||||
| JUCE_LEAK_DETECTOR (DrawableImage) | JUCE_LEAK_DETECTOR (DrawableImage) | ||||
| @@ -27,21 +27,12 @@ | |||||
| namespace juce | 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 | Drawable* DrawablePath::createCopy() const | ||||
| @@ -49,529 +40,19 @@ Drawable* DrawablePath::createCopy() const | |||||
| return new DrawablePath (*this); | return new DrawablePath (*this); | ||||
| } | } | ||||
| //============================================================================== | |||||
| void DrawablePath::setPath (const Path& newPath) | void DrawablePath::setPath (const Path& newPath) | ||||
| { | { | ||||
| path = newPath; | path = newPath; | ||||
| pathChanged(); | 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 | } // namespace juce | ||||
| @@ -52,11 +52,10 @@ public: | |||||
| */ | */ | ||||
| void setPath (const Path& newPath); | 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. */ | /** Returns the current path. */ | ||||
| const Path& getPath() const; | const Path& getPath() const; | ||||
| @@ -67,77 +66,9 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| /** @internal */ | /** @internal */ | ||||
| Drawable* createCopy() const; | 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: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| ScopedPointer<RelativePointPath> relativePath; | |||||
| class RelativePositioner; | |||||
| friend class RelativePositioner; | |||||
| void applyRelativePath (const RelativePointPath&, Expression::Scope*); | |||||
| DrawablePath& operator= (const DrawablePath&); | DrawablePath& operator= (const DrawablePath&); | ||||
| JUCE_LEAK_DETECTOR (DrawablePath) | JUCE_LEAK_DETECTOR (DrawablePath) | ||||
| }; | }; | ||||
| @@ -27,9 +27,8 @@ | |||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| DrawableRectangle::DrawableRectangle() | |||||
| { | |||||
| } | |||||
| DrawableRectangle::DrawableRectangle() {} | |||||
| DrawableRectangle::~DrawableRectangle() {} | |||||
| DrawableRectangle::DrawableRectangle (const DrawableRectangle& other) | DrawableRectangle::DrawableRectangle (const DrawableRectangle& other) | ||||
| : DrawableShape (other), | : DrawableShape (other), | ||||
| @@ -39,17 +38,13 @@ DrawableRectangle::DrawableRectangle (const DrawableRectangle& other) | |||||
| rebuildPath(); | rebuildPath(); | ||||
| } | } | ||||
| DrawableRectangle::~DrawableRectangle() | |||||
| { | |||||
| } | |||||
| Drawable* DrawableRectangle::createCopy() const | Drawable* DrawableRectangle::createCopy() const | ||||
| { | { | ||||
| return new DrawableRectangle (*this); | return new DrawableRectangle (*this); | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void DrawableRectangle::setRectangle (const RelativeParallelogram& newBounds) | |||||
| void DrawableRectangle::setRectangle (Parallelogram<float> newBounds) | |||||
| { | { | ||||
| if (bounds != 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) | if (cornerSize != newSize) | ||||
| { | { | ||||
| @@ -69,48 +64,19 @@ void DrawableRectangle::setCornerSize (const RelativePoint& newSize) | |||||
| void DrawableRectangle::rebuildPath() | 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; | 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 | else | ||||
| newPath.addRectangle (0, 0, w, h); | 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) | 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 | } // namespace juce | ||||
| @@ -47,54 +47,26 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Sets the rectangle's bounds. */ | /** Sets the rectangle's bounds. */ | ||||
| void setRectangle (const RelativeParallelogram& newBounds); | |||||
| void setRectangle (Parallelogram<float> newBounds); | |||||
| /** Returns the rectangle's bounds. */ | /** 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. */ | /** 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 */ | /** Sets a new corner size for the rectangle */ | ||||
| void setCornerSize (const RelativePoint& newSize); | |||||
| void setCornerSize (Point<float> newSize); | |||||
| //============================================================================== | //============================================================================== | ||||
| /** @internal */ | /** @internal */ | ||||
| Drawable* createCopy() const; | 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: | private: | ||||
| friend class Drawable::Positioner<DrawableRectangle>; | |||||
| RelativeParallelogram bounds; | |||||
| RelativePoint cornerSize; | |||||
| Parallelogram<float> bounds; | |||||
| Point<float> cornerSize; | |||||
| void rebuildPath(); | void rebuildPath(); | ||||
| bool registerCoordinates (RelativeCoordinatePositionerBase&); | |||||
| void recalculateCoordinates (Expression::Scope*); | |||||
| DrawableRectangle& operator= (const DrawableRectangle&); | DrawableRectangle& operator= (const DrawableRectangle&); | ||||
| JUCE_LEAK_DETECTOR (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) | 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(); | 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) | void DrawableShape::setStrokeType (const PathStrokeType& newStrokeType) | ||||
| { | { | ||||
| if (strokeType != newStrokeType) | if (strokeType != newStrokeType) | ||||
| @@ -154,20 +91,7 @@ void DrawableShape::setStrokeThickness (const float newThickness) | |||||
| bool DrawableShape::isStrokeVisible() const noexcept | 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); | transformContextToCorrectOrigin (g); | ||||
| applyDrawableClipPath (g); | applyDrawableClipPath (g); | ||||
| g.setFillType (mainFill.fill); | |||||
| g.setFillType (mainFill); | |||||
| g.fillPath (path); | g.fillPath (path); | ||||
| if (isStrokeVisible()) | if (isStrokeVisible()) | ||||
| { | { | ||||
| g.setFillType (strokeFill.fill); | |||||
| g.setFillType (strokeFill); | |||||
| g.fillPath (strokePath); | g.fillPath (strokePath); | ||||
| } | } | ||||
| } | } | ||||
| @@ -222,262 +146,17 @@ bool DrawableShape::hitTest (int x, int y) | |||||
| if (! allowsClicksOnThisComponent) | if (! allowsClicksOnThisComponent) | ||||
| return false; | 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) | return path.contains (globalX, globalY) | ||||
| || (isStrokeVisible() && strokePath.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); | fill = FillType (replacement); | ||||
| return true; | return true; | ||||
| @@ -495,7 +174,7 @@ bool DrawableShape::replaceColour (Colour original, Colour replacement) | |||||
| Path DrawableShape::getOutlineAsPath() const | Path DrawableShape::getOutlineAsPath() const | ||||
| { | { | ||||
| Path outline (isStrokeVisible() ? strokePath : path); | |||||
| auto outline = isStrokeVisible() ? strokePath : path; | |||||
| outline.applyTransform (getTransform()); | outline.applyTransform (getTransform()); | ||||
| return outline; | return outline; | ||||
| } | } | ||||
| @@ -45,31 +45,6 @@ public: | |||||
| /** Destructor. */ | /** Destructor. */ | ||||
| ~DrawableShape(); | ~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. | /** Sets a fill type for the path. | ||||
| This colour is used to fill the path - if you don't want the path to be | 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); | 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. | /** Returns the current fill type. | ||||
| @see setFill | @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. | /** Sets the fill type with which the outline will be drawn. | ||||
| @see setFill | @see setFill | ||||
| */ | */ | ||||
| void setStrokeFill (const FillType& newStrokeFill); | 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. | /** Returns the current stroke fill. | ||||
| @see setStrokeFill | @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. | /** Changes the properties of the outline that will be drawn around the path. | ||||
| If the stroke has 0 thickness, no stroke will be drawn. | If the stroke has 0 thickness, no stroke will be drawn. | ||||
| @@ -130,24 +91,6 @@ public: | |||||
| const Array<float>& getDashLengths() const noexcept { return dashLengths; } | 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 */ | /** @internal */ | ||||
| Rectangle<float> getDrawableBounds() const override; | Rectangle<float> getDrawableBounds() const override; | ||||
| /** @internal */ | /** @internal */ | ||||
| @@ -167,10 +110,6 @@ protected: | |||||
| void strokeChanged(); | void strokeChanged(); | ||||
| /** True if there's a stroke with a non-zero thickness and non-transparent colour. */ | /** True if there's a stroke with a non-zero thickness and non-transparent colour. */ | ||||
| bool isStrokeVisible() const noexcept; | 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; | PathStrokeType strokeType; | ||||
| @@ -178,12 +117,7 @@ protected: | |||||
| Path path, strokePath; | Path path, strokePath; | ||||
| private: | 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&); | DrawableShape& operator= (const DrawableShape&); | ||||
| }; | }; | ||||
| @@ -31,9 +31,7 @@ DrawableText::DrawableText() | |||||
| : colour (Colours::black), | : colour (Colours::black), | ||||
| justification (Justification::centredLeft) | 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); | 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) | void DrawableText::setText (const String& newText) | ||||
| { | { | ||||
| @@ -95,7 +98,7 @@ void DrawableText::setJustification (Justification newJustification) | |||||
| repaint(); | repaint(); | ||||
| } | } | ||||
| void DrawableText::setBoundingBox (const RelativeParallelogram& newBounds) | |||||
| void DrawableText::setBoundingBox (Parallelogram<float> newBounds) | |||||
| { | { | ||||
| if (bounds != 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) | 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) | if (fontHScale != newScale) | ||||
| { | { | ||||
| @@ -124,37 +127,11 @@ void DrawableText::setFontHorizontalScale (const RelativeCoordinate& newScale) | |||||
| void DrawableText::refreshBounds() | 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 = font; | ||||
| scaledFont.setHeight (height); | scaledFont.setHeight (height); | ||||
| @@ -172,17 +149,17 @@ Rectangle<int> DrawableText::getTextArea (float w, float h) const | |||||
| AffineTransform DrawableText::getTextTransform (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) | void DrawableText::paint (Graphics& g) | ||||
| { | { | ||||
| transformContextToCorrectOrigin (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.addTransform (getTextTransform (w, h)); | ||||
| g.setFont (scaledFont); | g.setFont (scaledFont); | ||||
| @@ -193,166 +170,14 @@ void DrawableText::paint (Graphics& g) | |||||
| Rectangle<float> DrawableText::getDrawableBounds() const | 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 | 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; | GlyphArrangement arr; | ||||
| arr.addFittedText (scaledFont, text, | arr.addFittedText (scaledFont, text, | ||||
| @@ -58,8 +58,8 @@ public: | |||||
| Colour getColour() const noexcept { return colour; } | Colour getColour() const noexcept { return colour; } | ||||
| /** Sets the font to use. | /** 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; | 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. | 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; } | Justification getJustification() const noexcept { return justification; } | ||||
| /** Returns the parallelogram that defines the text bounding box. */ | /** 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. */ | /** 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 */ | /** @internal */ | ||||
| @@ -92,62 +92,19 @@ public: | |||||
| /** @internal */ | /** @internal */ | ||||
| Drawable* createCopy() const override; | Drawable* createCopy() const override; | ||||
| /** @internal */ | /** @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; | Rectangle<float> getDrawableBounds() const override; | ||||
| /** @internal */ | /** @internal */ | ||||
| Path getOutlineAsPath() const override; | 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: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| RelativeParallelogram bounds; | |||||
| RelativeCoordinate fontHeight, fontHScale; | |||||
| Point<float> resolvedPoints[3]; | |||||
| Parallelogram<float> bounds; | |||||
| float fontHeight, fontHScale; | |||||
| Font font, scaledFont; | Font font, scaledFont; | ||||
| String text; | String text; | ||||
| Colour colour; | Colour colour; | ||||
| Justification justification; | Justification justification; | ||||
| friend class Drawable::Positioner<DrawableText>; | |||||
| bool registerCoordinates (RelativeCoordinatePositionerBase&); | |||||
| void recalculateCoordinates (Expression::Scope*); | |||||
| void refreshBounds(); | void refreshBounds(); | ||||
| Rectangle<int> getTextArea (float width, float height) const; | Rectangle<int> getTextArea (float width, float height) const; | ||||
| AffineTransform getTextTransform (float width, float height) const; | AffineTransform getTextTransform (float width, float height) const; | ||||
| @@ -196,10 +196,7 @@ public: | |||||
| newState.parseSubElements (xml, *drawable); | 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(); | drawable->resetBoundingBoxToContentArea(); | ||||
| return drawable; | return drawable; | ||||
| @@ -182,7 +182,6 @@ ComponentBuilder::TypeHandler* ComponentBuilder::getHandler (const int index) co | |||||
| void ComponentBuilder::registerStandardComponentTypes() | void ComponentBuilder::registerStandardComponentTypes() | ||||
| { | { | ||||
| Drawable::registerDrawableTypeHandlers (*this); | |||||
| } | } | ||||
| void ComponentBuilder::setImageProvider (ImageProvider* newImageProvider) noexcept | void ComponentBuilder::setImageProvider (ImageProvider* newImageProvider) noexcept | ||||
| @@ -95,9 +95,9 @@ AffineTransform RelativeParallelogram::resetToPerpendicular (Expression::Scope* | |||||
| topRight.moveToAbsolute (newTopRight, scope); | topRight.moveToAbsolute (newTopRight, scope); | ||||
| bottomLeft.moveToAbsolute (newBottomLeft, 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 | 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 | void RelativePointPath::StartSubPath::addToPath (Path& path, Expression::Scope* scope) const | ||||
| { | { | ||||
| path.startNewSubPath (startPos.resolve (scope)); | 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 | void RelativePointPath::CloseSubPath::addToPath (Path& path, Expression::Scope*) const | ||||
| { | { | ||||
| path.closeSubPath(); | 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 | void RelativePointPath::LineTo::addToPath (Path& path, Expression::Scope* scope) const | ||||
| { | { | ||||
| path.lineTo (endPoint.resolve (scope)); | path.lineTo (endPoint.resolve (scope)); | ||||
| @@ -234,14 +215,6 @@ RelativePointPath::QuadraticTo::QuadraticTo (const RelativePoint& controlPoint, | |||||
| controlPoints[1] = endPoint; | 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 | void RelativePointPath::QuadraticTo::addToPath (Path& path, Expression::Scope* scope) const | ||||
| { | { | ||||
| path.quadraticTo (controlPoints[0].resolve (scope), | path.quadraticTo (controlPoints[0].resolve (scope), | ||||
| @@ -269,15 +242,6 @@ RelativePointPath::CubicTo::CubicTo (const RelativePoint& controlPoint1, const R | |||||
| controlPoints[2] = endPoint; | 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 | void RelativePointPath::CubicTo::addToPath (Path& path, Expression::Scope* scope) const | ||||
| { | { | ||||
| path.cubicTo (controlPoints[0].resolve (scope), | path.cubicTo (controlPoints[0].resolve (scope), | ||||
| @@ -80,7 +80,6 @@ public: | |||||
| public: | public: | ||||
| ElementBase (ElementType type); | ElementBase (ElementType type); | ||||
| virtual ~ElementBase() {} | virtual ~ElementBase() {} | ||||
| virtual ValueTree createTree() const = 0; | |||||
| virtual void addToPath (Path& path, Expression::Scope*) const = 0; | virtual void addToPath (Path& path, Expression::Scope*) const = 0; | ||||
| virtual RelativePoint* getControlPoints (int& numPoints) = 0; | virtual RelativePoint* getControlPoints (int& numPoints) = 0; | ||||
| virtual ElementBase* clone() const = 0; | virtual ElementBase* clone() const = 0; | ||||
| @@ -97,7 +96,6 @@ public: | |||||
| { | { | ||||
| public: | public: | ||||
| StartSubPath (const RelativePoint& pos); | StartSubPath (const RelativePoint& pos); | ||||
| ValueTree createTree() const; | |||||
| void addToPath (Path& path, Expression::Scope*) const; | void addToPath (Path& path, Expression::Scope*) const; | ||||
| RelativePoint* getControlPoints (int& numPoints); | RelativePoint* getControlPoints (int& numPoints); | ||||
| ElementBase* clone() const; | ElementBase* clone() const; | ||||
| @@ -113,7 +111,6 @@ public: | |||||
| { | { | ||||
| public: | public: | ||||
| CloseSubPath(); | CloseSubPath(); | ||||
| ValueTree createTree() const; | |||||
| void addToPath (Path& path, Expression::Scope*) const; | void addToPath (Path& path, Expression::Scope*) const; | ||||
| RelativePoint* getControlPoints (int& numPoints); | RelativePoint* getControlPoints (int& numPoints); | ||||
| ElementBase* clone() const; | ElementBase* clone() const; | ||||
| @@ -127,7 +124,6 @@ public: | |||||
| { | { | ||||
| public: | public: | ||||
| LineTo (const RelativePoint& endPoint); | LineTo (const RelativePoint& endPoint); | ||||
| ValueTree createTree() const; | |||||
| void addToPath (Path& path, Expression::Scope*) const; | void addToPath (Path& path, Expression::Scope*) const; | ||||
| RelativePoint* getControlPoints (int& numPoints); | RelativePoint* getControlPoints (int& numPoints); | ||||
| ElementBase* clone() const; | ElementBase* clone() const; | ||||