| @@ -185,6 +185,10 @@ public: | |||
| DrawablePath::ValueTreeWrapper wrapper (item.getState()); | |||
| props.add (new DrawablePathFillPropComp (item, "Fill", wrapper.getMainFillState())); | |||
| props.add (StrokeThicknessValueSource::create (wrapper, item.getDocument().getUndoManager())); | |||
| props.add (StrokeJoinStyleValueSource::create (wrapper, item.getDocument().getUndoManager())); | |||
| props.add (StrokeCapStyleValueSource::create (wrapper, item.getDocument().getUndoManager())); | |||
| props.add (new DrawablePathFillPropComp (item, "Stroke", wrapper.getStrokeFillState())); | |||
| } | |||
| @@ -327,7 +331,7 @@ public: | |||
| if (stroke.isGradient()) | |||
| { | |||
| points.add (new GradientControlPoint (itemId + "/gs1", item.getState(), true, true)); | |||
| points.add (new GradientControlPoint (itemId + "/gs1", item.getState(), false, true)); | |||
| points.add (new GradientControlPoint (itemId + "/gs2", item.getState(), false, true)); | |||
| } | |||
| } | |||
| @@ -412,6 +416,113 @@ public: | |||
| getGradientControlPoints (wrapper, item, points, itemId); | |||
| } | |||
| //============================================================================== | |||
| class StrokeValueSourceBase : public Value::ValueSource, | |||
| public ValueTree::Listener | |||
| { | |||
| public: | |||
| StrokeValueSourceBase (const DrawablePath::ValueTreeWrapper& wrapper_, UndoManager* undoManager_) | |||
| : wrapper (wrapper_), undoManager (undoManager_) | |||
| { | |||
| wrapper.getState().addListener (this); | |||
| } | |||
| ~StrokeValueSourceBase() {} | |||
| void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const Identifier& property) { sendChangeMessage (true); } | |||
| void valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged) {} | |||
| void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) {} | |||
| protected: | |||
| DrawablePath::ValueTreeWrapper wrapper; | |||
| UndoManager* undoManager; | |||
| }; | |||
| //============================================================================== | |||
| class StrokeThicknessValueSource : public StrokeValueSourceBase | |||
| { | |||
| public: | |||
| StrokeThicknessValueSource (const DrawablePath::ValueTreeWrapper& wrapper_, UndoManager* undoManager_) | |||
| : StrokeValueSourceBase (wrapper_, undoManager_) | |||
| {} | |||
| const var getValue() const | |||
| { | |||
| return wrapper.getStrokeType().getStrokeThickness(); | |||
| } | |||
| void setValue (const var& newValue) | |||
| { | |||
| PathStrokeType s (wrapper.getStrokeType()); | |||
| s.setStrokeThickness (newValue); | |||
| wrapper.setStrokeType (s, undoManager); | |||
| } | |||
| static PropertyComponent* create (const DrawablePath::ValueTreeWrapper& wrapper, UndoManager* undoManager) | |||
| { | |||
| return new SliderPropertyComponent (Value (new StrokeThicknessValueSource (wrapper, undoManager)), | |||
| "Stroke Thickness", 0, 50.0, 0.1); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| class StrokeJoinStyleValueSource : public StrokeValueSourceBase | |||
| { | |||
| public: | |||
| StrokeJoinStyleValueSource (const DrawablePath::ValueTreeWrapper& wrapper_, UndoManager* undoManager_) | |||
| : StrokeValueSourceBase (wrapper_, undoManager_) | |||
| {} | |||
| const var getValue() const | |||
| { | |||
| return (int) wrapper.getStrokeType().getJointStyle(); | |||
| } | |||
| void setValue (const var& newValue) | |||
| { | |||
| PathStrokeType s (wrapper.getStrokeType()); | |||
| s.setJointStyle ((PathStrokeType::JointStyle) (int) newValue); | |||
| wrapper.setStrokeType (s, undoManager); | |||
| } | |||
| static PropertyComponent* create (const DrawablePath::ValueTreeWrapper& wrapper, UndoManager* undoManager) | |||
| { | |||
| const char* types[] = { "Miter", "Curved", "Bevel", 0 }; | |||
| const int mappings[] = { PathStrokeType::mitered, PathStrokeType::curved, PathStrokeType::beveled }; | |||
| return new ChoicePropertyComponent (Value (new StrokeJoinStyleValueSource (wrapper, undoManager)), | |||
| "Joint Style", StringArray (types), Array<var> (mappings, numElementsInArray (mappings))); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| class StrokeCapStyleValueSource : public StrokeValueSourceBase | |||
| { | |||
| public: | |||
| StrokeCapStyleValueSource (const DrawablePath::ValueTreeWrapper& wrapper_, UndoManager* undoManager_) | |||
| : StrokeValueSourceBase (wrapper_, undoManager_) | |||
| {} | |||
| const var getValue() const | |||
| { | |||
| return (int) wrapper.getStrokeType().getEndStyle(); | |||
| } | |||
| void setValue (const var& newValue) | |||
| { | |||
| PathStrokeType s (wrapper.getStrokeType()); | |||
| s.setEndStyle ((PathStrokeType::EndCapStyle) (int) newValue); | |||
| wrapper.setStrokeType (s, undoManager); | |||
| } | |||
| static PropertyComponent* create (const DrawablePath::ValueTreeWrapper& wrapper, UndoManager* undoManager) | |||
| { | |||
| const char* types[] = { "Butt", "Square", "Round", 0 }; | |||
| const int mappings[] = { PathStrokeType::butt, PathStrokeType::square, PathStrokeType::rounded }; | |||
| return new ChoicePropertyComponent (Value (new StrokeCapStyleValueSource (wrapper, undoManager)), | |||
| "Cap Style", StringArray (types), Array<var> (mappings, numElementsInArray (mappings))); | |||
| } | |||
| }; | |||
| }; | |||
| //============================================================================== | |||
| @@ -713,6 +824,119 @@ public: | |||
| }; | |||
| }; | |||
| //============================================================================== | |||
| class DrawableTextHandler : public DrawableTypeHandler | |||
| { | |||
| public: | |||
| DrawableTextHandler() : DrawableTypeHandler ("Text", DrawableText::valueTreeType) {} | |||
| ~DrawableTextHandler() {} | |||
| static const ValueTree createNewInstance (DrawableDocument& document, const Point<float>& approxPosition) | |||
| { | |||
| DrawableText dt; | |||
| dt.setText ("Text"); | |||
| dt.setBounds (RelativePoint (approxPosition), | |||
| RelativePoint (approxPosition + Point<float> (100.0f, 0.0f)), | |||
| RelativePoint (approxPosition + Point<float> (0.0f, 100.0f)), | |||
| RelativePoint (approxPosition + Point<float> (25.0f, 25.0f))); | |||
| dt.setFont (Font (25.0f), true); | |||
| return dt.createValueTree (&document); | |||
| } | |||
| void createPropertyEditors (DrawableTypeInstance& item, Array <PropertyComponent*>& props) | |||
| { | |||
| DrawableText::ValueTreeWrapper wrapper (item.getState()); | |||
| //props.add (new ResetButtonPropertyComponent (item, wrapper)); | |||
| } | |||
| void itemDoubleClicked (const MouseEvent& e, DrawableTypeInstance& item) | |||
| { | |||
| } | |||
| //============================================================================== | |||
| class TextControlPoint : public ControlPoint | |||
| { | |||
| public: | |||
| TextControlPoint (const String& id_, const ValueTree& item_, const int cpNum_) | |||
| : ControlPoint (id_), item (item_), cpNum (cpNum_) | |||
| {} | |||
| ~TextControlPoint() {} | |||
| const RelativePoint getPosition() | |||
| { | |||
| DrawableText::ValueTreeWrapper wrapper (item); | |||
| switch (cpNum) | |||
| { | |||
| case 0: return wrapper.getBoundingBoxTopLeft(); | |||
| case 1: return wrapper.getBoundingBoxTopRight(); | |||
| case 2: return wrapper.getBoundingBoxBottomLeft(); | |||
| case 3: return wrapper.getFontSizeAndScaleAnchor(); | |||
| default: jassertfalse; break; | |||
| } | |||
| return RelativePoint(); | |||
| } | |||
| void setPosition (const RelativePoint& newPoint, UndoManager* undoManager) | |||
| { | |||
| DrawableText::ValueTreeWrapper wrapper (item); | |||
| switch (cpNum) | |||
| { | |||
| case 0: wrapper.setBoundingBoxTopLeft (newPoint, undoManager); break; | |||
| case 1: wrapper.setBoundingBoxTopRight (newPoint, undoManager); break; | |||
| case 2: wrapper.setBoundingBoxBottomLeft (newPoint, undoManager); break; | |||
| case 3: wrapper.setFontSizeAndScaleAnchor (newPoint, undoManager); break; | |||
| default: jassertfalse; break; | |||
| } | |||
| } | |||
| const Value getPositionValue (UndoManager* undoManager) | |||
| { | |||
| DrawableText::ValueTreeWrapper wrapper (item); | |||
| switch (cpNum) | |||
| { | |||
| case 0: return item.getPropertyAsValue (DrawableText::ValueTreeWrapper::topLeft, undoManager); | |||
| case 1: return item.getPropertyAsValue (DrawableText::ValueTreeWrapper::topRight, undoManager); | |||
| case 2: return item.getPropertyAsValue (DrawableText::ValueTreeWrapper::bottomLeft, undoManager); | |||
| case 3: return item.getPropertyAsValue (DrawableText::ValueTreeWrapper::fontSizeAnchor, undoManager); | |||
| default: jassertfalse; break; | |||
| } | |||
| return Value(); | |||
| } | |||
| bool hasLine() { return false; } | |||
| RelativePoint getEndOfLine() { return RelativePoint(); } | |||
| void createProperties (DrawableDocument& document, Array <PropertyComponent*>& props) | |||
| { | |||
| DrawableTypeInstance instance (document, item); | |||
| props.add (new ControlPointPropertyComp (instance, this, "X", true, document.getUndoManager())); | |||
| props.add (new ControlPointPropertyComp (instance, this, "Y", false, document.getUndoManager())); | |||
| } | |||
| private: | |||
| ValueTree item; | |||
| int cpNum; | |||
| }; | |||
| void getAllControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points) | |||
| { | |||
| const String itemIDRoot (item.getID() + "/"); | |||
| for (int i = 0; i < 4; ++i) | |||
| points.add (new TextControlPoint (itemIDRoot + String(i), item.getState(), i)); | |||
| } | |||
| void getVisibleControlPoints (DrawableTypeInstance& item, OwnedArray <ControlPoint>& points, const EditorCanvasBase::SelectedItems&) | |||
| { | |||
| return getAllControlPoints (item, points); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| DrawableTypeManager::DrawableTypeManager() | |||
| @@ -720,6 +944,7 @@ DrawableTypeManager::DrawableTypeManager() | |||
| handlers.add (new DrawablePathHandler()); | |||
| handlers.add (new DrawableImageHandler()); | |||
| handlers.add (new DrawableCompositeHandler()); | |||
| handlers.add (new DrawableTextHandler()); | |||
| } | |||
| DrawableTypeManager::~DrawableTypeManager() | |||
| @@ -738,7 +963,7 @@ DrawableTypeHandler* DrawableTypeManager::getHandlerFor (const Identifier& type) | |||
| const StringArray DrawableTypeManager::getNewItemList() | |||
| { | |||
| const char* types[] = { "New Triangle", "New Rectangle", "New Ellipse", "New Image", 0 }; | |||
| const char* types[] = { "New Triangle", "New Rectangle", "New Ellipse", "New Image", "New Text Object", 0 }; | |||
| return StringArray (types); | |||
| } | |||
| @@ -750,6 +975,7 @@ const ValueTree DrawableTypeManager::createNewItem (const int index, DrawableDoc | |||
| case 1: return DrawablePathHandler::createNewRectangle (document, approxPosition); | |||
| case 2: return DrawablePathHandler::createNewEllipse (document, approxPosition); | |||
| case 3: return DrawableImageHandler::createNewInstance (document, approxPosition); | |||
| case 4: return DrawableTextHandler::createNewInstance (document, approxPosition); | |||
| default: jassertfalse; break; | |||
| } | |||
| @@ -83,7 +83,7 @@ MainWindow::MainWindow() | |||
| // don't want the window to take focus when the title-bar is clicked.. | |||
| setWantsKeyboardFocus (false); | |||
| getPeer()->setCurrentRenderingEngine (0); | |||
| //getPeer()->setCurrentRenderingEngine (0); | |||
| } | |||
| MainWindow::~MainWindow() | |||
| @@ -84706,25 +84706,12 @@ const Rectangle<float> DrawableImage::getBounds() const | |||
| if (image.isNull()) | |||
| return Rectangle<float>(); | |||
| Point<float> resolved[3]; | |||
| for (int i = 0; i < 3; ++i) | |||
| resolved[i] = controlPoints[i].resolve (parent); | |||
| const Point<float> bottomRight (resolved[1] + (resolved[2] - resolved[0])); | |||
| float minX = bottomRight.getX(); | |||
| float maxX = minX; | |||
| float minY = bottomRight.getY(); | |||
| float maxY = minY; | |||
| Point<float> corners[4]; | |||
| for (int i = 0; i < 3; ++i) | |||
| { | |||
| minX = jmin (minX, resolved[i].getX()); | |||
| maxX = jmax (maxX, resolved[i].getX()); | |||
| minY = jmin (minY, resolved[i].getY()); | |||
| maxY = jmax (maxY, resolved[i].getY()); | |||
| } | |||
| corners[i] = controlPoints[i].resolve (parent); | |||
| return Rectangle<float> (minX, minY, maxX - minX, maxY - minY); | |||
| corners[3] = corners[1] + (corners[2] - corners[0]); | |||
| return Rectangle<float>::findAreaContainingPoints (corners, 4); | |||
| } | |||
| bool DrawableImage::hitTest (float x, float y) const | |||
| @@ -85264,6 +85251,7 @@ const Rectangle<float> DrawablePath::refreshFromValueTree (const ValueTree& tree | |||
| { | |||
| damageRect = getBounds(); | |||
| path.swapWithPath (newPath); | |||
| strokeNeedsUpdating = true; | |||
| strokeType = newStroke; | |||
| needsRedraw = true; | |||
| } | |||
| @@ -85307,50 +85295,136 @@ END_JUCE_NAMESPACE | |||
| BEGIN_JUCE_NAMESPACE | |||
| DrawableText::DrawableText() | |||
| : colour (Colours::white) | |||
| : colour (Colours::black), | |||
| justification (Justification::centredLeft) | |||
| { | |||
| setFont (Font (15.0f), true); | |||
| } | |||
| DrawableText::DrawableText (const DrawableText& other) | |||
| : text (other.text), | |||
| colour (other.colour) | |||
| font (other.font), | |||
| colour (other.colour), | |||
| justification (other.justification) | |||
| { | |||
| for (int i = 0; i < numElementsInArray (controlPoints); ++i) | |||
| controlPoints[i] = other.controlPoints[i]; | |||
| } | |||
| DrawableText::~DrawableText() | |||
| { | |||
| } | |||
| void DrawableText::setText (const GlyphArrangement& newText) | |||
| void DrawableText::setText (const String& newText) | |||
| { | |||
| text = newText; | |||
| } | |||
| void DrawableText::setText (const String& newText, const Font& fontToUse) | |||
| void DrawableText::setColour (const Colour& newColour) | |||
| { | |||
| text.clear(); | |||
| text.addLineOfText (fontToUse, newText, 0.0f, 0.0f); | |||
| colour = newColour; | |||
| } | |||
| void DrawableText::setColour (const Colour& newColour) | |||
| void DrawableText::setFont (const Font& newFont, bool applySizeAndScale) | |||
| { | |||
| colour = newColour; | |||
| font = newFont; | |||
| if (applySizeAndScale) | |||
| { | |||
| const Line<float> left (Point<float>(), controlPoints[2].resolve (getParent())); | |||
| const Line<float> top (Point<float>(), controlPoints[1].resolve (getParent())); | |||
| controlPoints[3] = RelativePoint (controlPoints[0].resolve (getParent()) | |||
| + left.getPointAlongLine (font.getHeight()) | |||
| + top.getPointAlongLine (font.getHorizontalScale() * font.getHeight())); | |||
| } | |||
| } | |||
| void DrawableText::setJustification (const Justification& newJustification) | |||
| { | |||
| justification = newJustification; | |||
| } | |||
| void DrawableText::setBounds (const RelativePoint& boundingBoxTopLeft, | |||
| const RelativePoint& boundingBoxTopRight, | |||
| const RelativePoint& boundingBoxBottomLeft, | |||
| const RelativePoint& fontSizeAndScaleAnchor) | |||
| { | |||
| controlPoints[0] = boundingBoxTopLeft; | |||
| controlPoints[1] = boundingBoxTopRight; | |||
| controlPoints[2] = boundingBoxBottomLeft; | |||
| controlPoints[3] = fontSizeAndScaleAnchor; | |||
| } | |||
| static const Point<float> findNormalisedCoordWithinParallelogram (const Point<float>& origin, | |||
| Point<float> topRight, | |||
| Point<float> bottomLeft, | |||
| Point<float> target) | |||
| { | |||
| topRight -= origin; | |||
| bottomLeft -= origin; | |||
| target -= origin; | |||
| return Point<float> (Line<float> (Point<float>(), topRight).getIntersection (Line<float> (target, target - bottomLeft)).getDistanceFromOrigin(), | |||
| Line<float> (Point<float>(), bottomLeft).getIntersection (Line<float> (target, target - topRight)).getDistanceFromOrigin()); | |||
| } | |||
| void DrawableText::render (const Drawable::RenderingContext& context) const | |||
| { | |||
| Point<float> points[4]; | |||
| for (int i = 0; i < 4; ++i) | |||
| points[i] = controlPoints[i].resolve (getParent()); | |||
| const float w = Line<float> (points[0], points[1]).getLength(); | |||
| const float h = Line<float> (points[0], points[2]).getLength(); | |||
| const Point<float> fontCoords (findNormalisedCoordWithinParallelogram (points[0], points[1], points[2], points[3])); | |||
| const float fontHeight = jlimit (1.0f, h, fontCoords.getY()); | |||
| const float fontWidth = jlimit (0.01f, w, fontCoords.getX()); | |||
| Font f (font); | |||
| f.setHeight (fontHeight); | |||
| f.setHorizontalScale (fontWidth / fontHeight); | |||
| context.g.setColour (colour.withMultipliedAlpha (context.opacity)); | |||
| text.draw (context.g, context.transform); | |||
| GlyphArrangement ga; | |||
| ga.addFittedText (f, text, 0, 0, w, h, justification, 0x100000); | |||
| ga.draw (context.g, | |||
| AffineTransform::fromTargetPoints (0, 0, points[0].getX(), points[0].getY(), | |||
| w, 0, points[1].getX(), points[1].getY(), | |||
| 0, h, points[2].getX(), points[2].getY()) | |||
| .followedBy (context.transform)); | |||
| } | |||
| void DrawableText::resolveCorners (Point<float>* const corners) const | |||
| { | |||
| for (int i = 0; i < 3; ++i) | |||
| corners[i] = controlPoints[i].resolve (parent); | |||
| corners[3] = corners[1] + (corners[2] - corners[0]); | |||
| } | |||
| const Rectangle<float> DrawableText::getBounds() const | |||
| { | |||
| return text.getBoundingBox (0, -1, false); | |||
| Point<float> corners[4]; | |||
| resolveCorners (corners); | |||
| return Rectangle<float>::findAreaContainingPoints (corners, 4); | |||
| } | |||
| bool DrawableText::hitTest (float x, float y) const | |||
| { | |||
| return text.findGlyphIndexAt (x, y) >= 0; | |||
| Point<float> corners[4]; | |||
| resolveCorners (corners); | |||
| Path p; | |||
| p.startNewSubPath (corners[0].getX(), corners[0].getY()); | |||
| p.lineTo (corners[1].getX(), corners[1].getY()); | |||
| p.lineTo (corners[3].getX(), corners[3].getY()); | |||
| p.lineTo (corners[2].getX(), corners[2].getY()); | |||
| p.closeSubPath(); | |||
| return p.contains (x, y); | |||
| } | |||
| Drawable* DrawableText::createCopy() const | |||
| @@ -85365,6 +85439,13 @@ void DrawableText::invalidatePoints() | |||
| 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::fontSizeAnchor ("fontSizeAnchor"); | |||
| DrawableText::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
| : ValueTreeWrapperBase (state_) | |||
| @@ -85372,12 +85453,113 @@ DrawableText::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
| jassert (state.hasType (valueTreeType)); | |||
| } | |||
| const String DrawableText::ValueTreeWrapper::getText() const | |||
| { | |||
| return state [text].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setText (const String& newText, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (text, newText, undoManager); | |||
| } | |||
| const Colour DrawableText::ValueTreeWrapper::getColour() const | |||
| { | |||
| return Colour::fromString (state [colour].toString()); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setColour (const Colour& newColour, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (colour, newColour.toString(), undoManager); | |||
| } | |||
| const Justification DrawableText::ValueTreeWrapper::getJustification() const | |||
| { | |||
| return Justification ((int) state [justification]); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setJustification (const Justification& newJustification, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (justification, newJustification.getFlags(), undoManager); | |||
| } | |||
| const 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); | |||
| } | |||
| const RelativePoint DrawableText::ValueTreeWrapper::getBoundingBoxTopLeft() const | |||
| { | |||
| return state [topLeft].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setBoundingBoxTopLeft (const RelativePoint& p, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (topLeft, p.toString(), undoManager); | |||
| } | |||
| const RelativePoint DrawableText::ValueTreeWrapper::getBoundingBoxTopRight() const | |||
| { | |||
| return state [topRight].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setBoundingBoxTopRight (const RelativePoint& p, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (topRight, p.toString(), undoManager); | |||
| } | |||
| const RelativePoint DrawableText::ValueTreeWrapper::getBoundingBoxBottomLeft() const | |||
| { | |||
| return state [bottomLeft].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setBoundingBoxBottomLeft (const RelativePoint& p, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (bottomLeft, p.toString(), undoManager); | |||
| } | |||
| const RelativePoint DrawableText::ValueTreeWrapper::getFontSizeAndScaleAnchor() const | |||
| { | |||
| return state [fontSizeAnchor].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setFontSizeAndScaleAnchor (const RelativePoint& p, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (fontSizeAnchor, p.toString(), undoManager); | |||
| } | |||
| const Rectangle<float> DrawableText::refreshFromValueTree (const ValueTree& tree, ImageProvider*) | |||
| { | |||
| ValueTreeWrapper v (tree); | |||
| setName (v.getID()); | |||
| jassertfalse; // xxx not finished! | |||
| const RelativePoint p1 (v.getBoundingBoxTopLeft()), p2 (v.getBoundingBoxTopRight()), | |||
| p3 (v.getBoundingBoxBottomLeft()), p4 (v.getFontSizeAndScaleAnchor()); | |||
| 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 | |||
| || p1 != controlPoints[0] || p2 != controlPoints[1] || p3 != controlPoints[2] || p4 != controlPoints[3]) | |||
| { | |||
| const Rectangle<float> damage (getBounds()); | |||
| setBounds (p1, p2, p3, p4); | |||
| setColour (newColour); | |||
| setFont (newFont, false); | |||
| setJustification (newJustification); | |||
| setText (newText); | |||
| return damage.getUnion (getBounds()); | |||
| } | |||
| return Rectangle<float>(); | |||
| } | |||
| @@ -85388,8 +85570,14 @@ const ValueTree DrawableText::createValueTree (ImageProvider*) const | |||
| ValueTreeWrapper v (tree); | |||
| v.setID (getName(), 0); | |||
| jassertfalse; // xxx not finished! | |||
| v.setText (text, 0); | |||
| v.setFont (font, 0); | |||
| v.setJustification (justification, 0); | |||
| v.setColour (colour, 0); | |||
| v.setBoundingBoxTopLeft (controlPoints[0], 0); | |||
| v.setBoundingBoxTopRight (controlPoints[1], 0); | |||
| v.setBoundingBoxBottomLeft (controlPoints[2], 0); | |||
| v.setFontSizeAndScaleAnchor (controlPoints[3], 0); | |||
| return tree; | |||
| } | |||
| @@ -88911,6 +89099,15 @@ const AffineTransform AffineTransform::fromTargetPoints (const float x00, const | |||
| y10 - y00, y01 - y00, y00); | |||
| } | |||
| const AffineTransform AffineTransform::fromTargetPoints (const float sx1, const float sy1, const float tx1, const float ty1, | |||
| const float sx2, const float sy2, const float tx2, const float ty2, | |||
| const float sx3, const float sy3, const float tx3, const float ty3) throw() | |||
| { | |||
| return fromTargetPoints (sx1, sy1, sx2, sy2, sx3, sy3) | |||
| .inverted() | |||
| .followedBy (fromTargetPoints (tx1, ty1, tx2, ty2, tx3, ty3)); | |||
| } | |||
| bool AffineTransform::isOnlyTranslation() const throw() | |||
| { | |||
| return (mat01 == 0) | |||
| @@ -263809,7 +264006,8 @@ public: | |||
| : context (context_), | |||
| flipHeight (flipHeight_), | |||
| state (new SavedState()), | |||
| numGradientLookupEntries (0) | |||
| numGradientLookupEntries (0), | |||
| lastClipRectIsValid (false) | |||
| { | |||
| CGContextRetain (context); | |||
| CGContextSaveGState(context); | |||
| @@ -263837,11 +264035,21 @@ public: | |||
| void setOrigin (int x, int y) | |||
| { | |||
| CGContextTranslateCTM (context, x, -y); | |||
| if (lastClipRectIsValid) | |||
| lastClipRect.translate (-x, -y); | |||
| } | |||
| bool clipToRectangle (const Rectangle<int>& r) | |||
| { | |||
| CGContextClipToRect (context, CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight())); | |||
| if (lastClipRectIsValid) | |||
| { | |||
| lastClipRect = lastClipRect.getIntersection (r); | |||
| return ! r.isEmpty(); | |||
| } | |||
| return ! isClipEmpty(); | |||
| } | |||
| @@ -263850,6 +264058,8 @@ public: | |||
| if (clipRegion.isEmpty()) | |||
| { | |||
| CGContextClipToRect (context, CGRectMake (0, 0, 0, 0)); | |||
| lastClipRectIsValid = true; | |||
| lastClipRect = Rectangle<int>(); | |||
| return false; | |||
| } | |||
| else | |||
| @@ -263864,6 +264074,7 @@ public: | |||
| } | |||
| CGContextClipToRects (context, rects, numRects); | |||
| lastClipRectIsValid = false; | |||
| return ! isClipEmpty(); | |||
| } | |||
| } | |||
| @@ -263873,12 +264084,14 @@ public: | |||
| RectangleList remaining (getClipBounds()); | |||
| remaining.subtract (r); | |||
| clipToRectangleList (remaining); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| void clipToPath (const Path& path, const AffineTransform& transform) | |||
| { | |||
| createPath (path, transform); | |||
| CGContextClip (context); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| void clipToImageAlpha (const Image& sourceImage, const Rectangle<int>& srcClip, const AffineTransform& transform) | |||
| @@ -263903,6 +264116,7 @@ public: | |||
| flip(); | |||
| CGImageRelease (image); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| } | |||
| @@ -263913,17 +264127,23 @@ public: | |||
| const Rectangle<int> getClipBounds() const | |||
| { | |||
| CGRect bounds = CGRectIntegral (CGContextGetClipBoundingBox (context)); | |||
| if (! lastClipRectIsValid) | |||
| { | |||
| CGRect bounds = CGRectIntegral (CGContextGetClipBoundingBox (context)); | |||
| lastClipRectIsValid = true; | |||
| lastClipRect.setBounds (roundToInt (bounds.origin.x), | |||
| roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)), | |||
| roundToInt (bounds.size.width), | |||
| roundToInt (bounds.size.height)); | |||
| } | |||
| return Rectangle<int> (roundToInt (bounds.origin.x), | |||
| roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)), | |||
| roundToInt (bounds.size.width), | |||
| roundToInt (bounds.size.height)); | |||
| return lastClipRect; | |||
| } | |||
| bool isClipEmpty() const | |||
| { | |||
| return CGRectIsEmpty (CGContextGetClipBoundingBox (context)); | |||
| return getClipBounds().isEmpty(); | |||
| } | |||
| void saveState() | |||
| @@ -263942,6 +264162,7 @@ public: | |||
| { | |||
| state = top; | |||
| stateStack.removeLast (1, false); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| else | |||
| { | |||
| @@ -264223,6 +264444,8 @@ private: | |||
| const CGFloat flipHeight; | |||
| CGColorSpaceRef rgbColourSpace, greyColourSpace; | |||
| CGFunctionCallbacks gradientCallbacks; | |||
| mutable Rectangle<int> lastClipRect; | |||
| mutable bool lastClipRectIsValid; | |||
| struct SavedState | |||
| { | |||
| @@ -268422,7 +268645,8 @@ public: | |||
| : context (context_), | |||
| flipHeight (flipHeight_), | |||
| state (new SavedState()), | |||
| numGradientLookupEntries (0) | |||
| numGradientLookupEntries (0), | |||
| lastClipRectIsValid (false) | |||
| { | |||
| CGContextRetain (context); | |||
| CGContextSaveGState(context); | |||
| @@ -268450,11 +268674,21 @@ public: | |||
| void setOrigin (int x, int y) | |||
| { | |||
| CGContextTranslateCTM (context, x, -y); | |||
| if (lastClipRectIsValid) | |||
| lastClipRect.translate (-x, -y); | |||
| } | |||
| bool clipToRectangle (const Rectangle<int>& r) | |||
| { | |||
| CGContextClipToRect (context, CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight())); | |||
| if (lastClipRectIsValid) | |||
| { | |||
| lastClipRect = lastClipRect.getIntersection (r); | |||
| return ! r.isEmpty(); | |||
| } | |||
| return ! isClipEmpty(); | |||
| } | |||
| @@ -268463,6 +268697,8 @@ public: | |||
| if (clipRegion.isEmpty()) | |||
| { | |||
| CGContextClipToRect (context, CGRectMake (0, 0, 0, 0)); | |||
| lastClipRectIsValid = true; | |||
| lastClipRect = Rectangle<int>(); | |||
| return false; | |||
| } | |||
| else | |||
| @@ -268477,6 +268713,7 @@ public: | |||
| } | |||
| CGContextClipToRects (context, rects, numRects); | |||
| lastClipRectIsValid = false; | |||
| return ! isClipEmpty(); | |||
| } | |||
| } | |||
| @@ -268486,12 +268723,14 @@ public: | |||
| RectangleList remaining (getClipBounds()); | |||
| remaining.subtract (r); | |||
| clipToRectangleList (remaining); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| void clipToPath (const Path& path, const AffineTransform& transform) | |||
| { | |||
| createPath (path, transform); | |||
| CGContextClip (context); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| void clipToImageAlpha (const Image& sourceImage, const Rectangle<int>& srcClip, const AffineTransform& transform) | |||
| @@ -268516,6 +268755,7 @@ public: | |||
| flip(); | |||
| CGImageRelease (image); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| } | |||
| @@ -268526,17 +268766,23 @@ public: | |||
| const Rectangle<int> getClipBounds() const | |||
| { | |||
| CGRect bounds = CGRectIntegral (CGContextGetClipBoundingBox (context)); | |||
| if (! lastClipRectIsValid) | |||
| { | |||
| CGRect bounds = CGRectIntegral (CGContextGetClipBoundingBox (context)); | |||
| lastClipRectIsValid = true; | |||
| lastClipRect.setBounds (roundToInt (bounds.origin.x), | |||
| roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)), | |||
| roundToInt (bounds.size.width), | |||
| roundToInt (bounds.size.height)); | |||
| } | |||
| return Rectangle<int> (roundToInt (bounds.origin.x), | |||
| roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)), | |||
| roundToInt (bounds.size.width), | |||
| roundToInt (bounds.size.height)); | |||
| return lastClipRect; | |||
| } | |||
| bool isClipEmpty() const | |||
| { | |||
| return CGRectIsEmpty (CGContextGetClipBoundingBox (context)); | |||
| return getClipBounds().isEmpty(); | |||
| } | |||
| void saveState() | |||
| @@ -268555,6 +268801,7 @@ public: | |||
| { | |||
| state = top; | |||
| stateStack.removeLast (1, false); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| else | |||
| { | |||
| @@ -268836,6 +269083,8 @@ private: | |||
| const CGFloat flipHeight; | |||
| CGColorSpaceRef rgbColourSpace, greyColourSpace; | |||
| CGFunctionCallbacks gradientCallbacks; | |||
| mutable Rectangle<int> lastClipRect; | |||
| mutable bool lastClipRectIsValid; | |||
| struct SavedState | |||
| { | |||
| @@ -18946,6 +18946,12 @@ public: | |||
| float x10, float y10, | |||
| float x01, float y01) throw(); | |||
| /** Returns the transform that will map three specified points onto three target points. | |||
| */ | |||
| static const AffineTransform fromTargetPoints (float sourceX1, float sourceY1, float targetX1, float targetY1, | |||
| float sourceX2, float sourceY2, float targetX2, float targetY2, | |||
| float sourceX3, float sourceY3, float targetX3, float targetY3) throw(); | |||
| /** Returns the result of concatenating another transformation after this one. */ | |||
| const AffineTransform followedBy (const AffineTransform& other) const throw(); | |||
| @@ -19981,6 +19987,18 @@ public: | |||
| return findIntersection (start, end, line.start, line.end, intersection); | |||
| } | |||
| /** Finds the intersection between two lines. | |||
| @param line the line to intersect with | |||
| @returns the point at which the lines intersect, even if this lies beyond the end of the lines | |||
| */ | |||
| const Point<ValueType> getIntersection (const Line& line) const throw() | |||
| { | |||
| Point<ValueType> p; | |||
| findIntersection (start, end, line.start, line.end, p); | |||
| return p; | |||
| } | |||
| /** Returns the location of the point which is a given distance along this line. | |||
| @param distanceFromStart the distance to move along the line from its | |||
| @@ -20660,6 +20678,28 @@ public: | |||
| return Rectangle<int> (x1, y1, x2 - x1, y2 - y1); | |||
| } | |||
| /** Returns the smallest Rectangle that can contain a set of points. */ | |||
| static const Rectangle findAreaContainingPoints (const Point<ValueType>* const points, const int numPoints) throw() | |||
| { | |||
| if (numPoints == 0) | |||
| return Rectangle(); | |||
| ValueType minX (points[0].getX()); | |||
| ValueType maxX (minX); | |||
| ValueType minY (points[0].getY()); | |||
| ValueType maxY (minY); | |||
| for (int i = 1; i < numPoints; ++i) | |||
| { | |||
| minX = jmin (minX, points[i].getX()); | |||
| maxX = jmax (maxX, points[i].getX()); | |||
| minY = jmin (minY, points[i].getY()); | |||
| maxY = jmax (maxY, points[i].getY()); | |||
| } | |||
| return Rectangle (minX, minY, maxX - minX, maxY - minY); | |||
| } | |||
| /** Casts this rectangle to a Rectangle<float>. | |||
| Obviously this is mainly useful for rectangles that use integer types. | |||
| @see getSmallestIntegerContainer | |||
| @@ -22147,12 +22187,21 @@ public: | |||
| /** Returns the stroke thickness. */ | |||
| float getStrokeThickness() const throw() { return thickness; } | |||
| /** Sets the stroke thickness. */ | |||
| void setStrokeThickness (float newThickness) throw() { thickness = newThickness; } | |||
| /** Returns the joint style. */ | |||
| JointStyle getJointStyle() const throw() { return jointStyle; } | |||
| /** Sets the joint style. */ | |||
| void setJointStyle (JointStyle newStyle) throw() { jointStyle = newStyle; } | |||
| /** Returns the end-cap style. */ | |||
| EndCapStyle getEndStyle() const throw() { return endStyle; } | |||
| /** Sets the end-cap style. */ | |||
| void setEndStyle (EndCapStyle newStyle) throw() { endStyle = newStyle; } | |||
| juce_UseDebuggingNewOperator | |||
| /** Compares the stroke thickness, joint and end styles of two stroke types. */ | |||
| @@ -31096,9 +31145,16 @@ public: | |||
| */ | |||
| AudioSource* getCurrentSource() const throw() { return source; } | |||
| /** Sets a gain to apply to the audio data. */ | |||
| /** Sets a gain to apply to the audio data. | |||
| @see getGain | |||
| */ | |||
| void setGain (const float newGain) throw(); | |||
| /** Returns the current gain. | |||
| @see setGain | |||
| */ | |||
| float getGain() const throw() { return gain; } | |||
| /** Implementation of the AudioIODeviceCallback method. */ | |||
| void audioDeviceIOCallback (const float** inputChannelData, | |||
| int totalNumInputChannels, | |||
| @@ -58788,19 +58844,8 @@ public: | |||
| /** Destructor. */ | |||
| virtual ~DrawableText(); | |||
| /** Sets the block of text to render */ | |||
| void setText (const GlyphArrangement& newText); | |||
| /** Sets a single line of text to render. | |||
| This is a convenient method of adding a single line - for | |||
| more complex text, use the setText() that takes a | |||
| GlyphArrangement instead. | |||
| */ | |||
| void setText (const String& newText, const Font& fontToUse); | |||
| /** Returns the text arrangement that was set with setText(). */ | |||
| const GlyphArrangement& getText() const throw() { return text; } | |||
| /** Sets the text to display.*/ | |||
| void setText (const String& newText); | |||
| /** Sets the colour of the text. */ | |||
| void setColour (const Colour& newColour); | |||
| @@ -58808,6 +58853,37 @@ public: | |||
| /** Returns the current text colour. */ | |||
| const Colour& getColour() const throw() { return colour; } | |||
| /** Sets the font to use. | |||
| Note that the font height and horizontal scale are actually based upon the position | |||
| of the fontSizeAndScaleAnchor parameter to setBounds(). If applySizeAndScale is true, then | |||
| the height and scale control point will be moved to match the dimensions of the font supplied; | |||
| if it is false, then the new font's height and scale are ignored. | |||
| */ | |||
| void setFont (const Font& newFont, bool applySizeAndScale); | |||
| /** Changes the justification of the text within the bounding box. */ | |||
| void setJustification (const Justification& newJustification); | |||
| /** Sets the bounding box and the control point that controls the font size. | |||
| The three bounding box points define the parallelogram within which the text will be | |||
| placed. The fontSizeAndScaleAnchor specifies a position within that parallelogram, whose | |||
| Y position (relative to the parallelogram's origin and possibly distorted shape) specifies | |||
| the font's height, and its X defines the font's horizontal scale. | |||
| */ | |||
| void setBounds (const RelativePoint& boundingBoxTopLeft, | |||
| const RelativePoint& boundingBoxTopRight, | |||
| const RelativePoint& boundingBoxBottomLeft, | |||
| const RelativePoint& fontSizeAndScaleAnchor); | |||
| /** Returns the origin of the text bounding box. */ | |||
| const RelativePoint& getBoundingBoxTopLeft() const throw() { return controlPoints[0]; } | |||
| /** Returns the top-right of the text bounding box. */ | |||
| const RelativePoint& getBoundingBoxTopRight() const throw() { return controlPoints[1]; } | |||
| /** Returns the bottom-left of the text bounding box. */ | |||
| const RelativePoint& getBoundingBoxBottomLeft() const throw() { return controlPoints[2]; } | |||
| /** Returns the point within the text bounding box which defines the size and scale of the font. */ | |||
| const RelativePoint& getFontSizeAndScaleAnchor() const throw() { return controlPoints[3]; } | |||
| /** @internal */ | |||
| void render (const Drawable::RenderingContext& context) const; | |||
| /** @internal */ | |||
| @@ -58833,17 +58909,43 @@ public: | |||
| public: | |||
| ValueTreeWrapper (const ValueTree& state); | |||
| //xxx todo | |||
| const String getText() const; | |||
| void setText (const String& newText, UndoManager* undoManager); | |||
| private: | |||
| static const Identifier text; | |||
| const Colour getColour() const; | |||
| void setColour (const Colour& newColour, UndoManager* undoManager); | |||
| const Justification getJustification() const; | |||
| void setJustification (const Justification& newJustification, UndoManager* undoManager); | |||
| const Font getFont() const; | |||
| void setFont (const Font& newFont, UndoManager* undoManager); | |||
| const RelativePoint getBoundingBoxTopLeft() const; | |||
| void setBoundingBoxTopLeft (const RelativePoint& p, UndoManager* undoManager); | |||
| const RelativePoint getBoundingBoxTopRight() const; | |||
| void setBoundingBoxTopRight (const RelativePoint& p, UndoManager* undoManager); | |||
| const RelativePoint getBoundingBoxBottomLeft() const; | |||
| void setBoundingBoxBottomLeft (const RelativePoint& p, UndoManager* undoManager); | |||
| const RelativePoint getFontSizeAndScaleAnchor() const; | |||
| void setFontSizeAndScaleAnchor (const RelativePoint& p, UndoManager* undoManager); | |||
| static const Identifier text, colour, font, justification, topLeft, topRight, bottomLeft, fontSizeAnchor; | |||
| }; | |||
| juce_UseDebuggingNewOperator | |||
| private: | |||
| GlyphArrangement text; | |||
| RelativePoint controlPoints[4]; | |||
| Font font; | |||
| String text; | |||
| Colour colour; | |||
| Justification justification; | |||
| void resolveCorners (Point<float>* corners) const; | |||
| DrawableText& operator= (const DrawableText&); | |||
| }; | |||
| @@ -74,9 +74,16 @@ public: | |||
| */ | |||
| AudioSource* getCurrentSource() const throw() { return source; } | |||
| /** Sets a gain to apply to the audio data. */ | |||
| /** Sets a gain to apply to the audio data. | |||
| @see getGain | |||
| */ | |||
| void setGain (const float newGain) throw(); | |||
| /** Returns the current gain. | |||
| @see setGain | |||
| */ | |||
| float getGain() const throw() { return gain; } | |||
| //============================================================================== | |||
| /** Implementation of the AudioIODeviceCallback method. */ | |||
| void audioDeviceIOCallback (const float** inputChannelData, | |||
| @@ -130,25 +130,12 @@ const Rectangle<float> DrawableImage::getBounds() const | |||
| if (image.isNull()) | |||
| return Rectangle<float>(); | |||
| Point<float> resolved[3]; | |||
| Point<float> corners[4]; | |||
| for (int i = 0; i < 3; ++i) | |||
| resolved[i] = controlPoints[i].resolve (parent); | |||
| const Point<float> bottomRight (resolved[1] + (resolved[2] - resolved[0])); | |||
| float minX = bottomRight.getX(); | |||
| float maxX = minX; | |||
| float minY = bottomRight.getY(); | |||
| float maxY = minY; | |||
| for (int i = 0; i < 3; ++i) | |||
| { | |||
| minX = jmin (minX, resolved[i].getX()); | |||
| maxX = jmax (maxX, resolved[i].getX()); | |||
| minY = jmin (minY, resolved[i].getY()); | |||
| maxY = jmax (maxY, resolved[i].getY()); | |||
| } | |||
| corners[i] = controlPoints[i].resolve (parent); | |||
| return Rectangle<float> (minX, minY, maxX - minX, maxY - minY); | |||
| corners[3] = corners[1] + (corners[2] - corners[0]); | |||
| return Rectangle<float>::findAreaContainingPoints (corners, 4); | |||
| } | |||
| bool DrawableImage::hitTest (float x, float y) const | |||
| @@ -382,6 +382,7 @@ const Rectangle<float> DrawablePath::refreshFromValueTree (const ValueTree& tree | |||
| { | |||
| damageRect = getBounds(); | |||
| path.swapWithPath (newPath); | |||
| strokeNeedsUpdating = true; | |||
| strokeType = newStroke; | |||
| needsRedraw = true; | |||
| } | |||
| @@ -28,18 +28,25 @@ | |||
| BEGIN_JUCE_NAMESPACE | |||
| #include "juce_DrawableText.h" | |||
| #include "juce_DrawableComposite.h" | |||
| //============================================================================== | |||
| DrawableText::DrawableText() | |||
| : colour (Colours::white) | |||
| : colour (Colours::black), | |||
| justification (Justification::centredLeft) | |||
| { | |||
| setFont (Font (15.0f), true); | |||
| } | |||
| DrawableText::DrawableText (const DrawableText& other) | |||
| : text (other.text), | |||
| colour (other.colour) | |||
| font (other.font), | |||
| colour (other.colour), | |||
| justification (other.justification) | |||
| { | |||
| for (int i = 0; i < numElementsInArray (controlPoints); ++i) | |||
| controlPoints[i] = other.controlPoints[i]; | |||
| } | |||
| DrawableText::~DrawableText() | |||
| @@ -47,37 +54,117 @@ DrawableText::~DrawableText() | |||
| } | |||
| //============================================================================== | |||
| void DrawableText::setText (const GlyphArrangement& newText) | |||
| void DrawableText::setText (const String& newText) | |||
| { | |||
| text = newText; | |||
| } | |||
| void DrawableText::setText (const String& newText, const Font& fontToUse) | |||
| void DrawableText::setColour (const Colour& newColour) | |||
| { | |||
| text.clear(); | |||
| text.addLineOfText (fontToUse, newText, 0.0f, 0.0f); | |||
| colour = newColour; | |||
| } | |||
| void DrawableText::setColour (const Colour& newColour) | |||
| void DrawableText::setFont (const Font& newFont, bool applySizeAndScale) | |||
| { | |||
| colour = newColour; | |||
| font = newFont; | |||
| if (applySizeAndScale) | |||
| { | |||
| const Line<float> left (Point<float>(), controlPoints[2].resolve (getParent())); | |||
| const Line<float> top (Point<float>(), controlPoints[1].resolve (getParent())); | |||
| controlPoints[3] = RelativePoint (controlPoints[0].resolve (getParent()) | |||
| + left.getPointAlongLine (font.getHeight()) | |||
| + top.getPointAlongLine (font.getHorizontalScale() * font.getHeight())); | |||
| } | |||
| } | |||
| void DrawableText::setJustification (const Justification& newJustification) | |||
| { | |||
| justification = newJustification; | |||
| } | |||
| void DrawableText::setBounds (const RelativePoint& boundingBoxTopLeft, | |||
| const RelativePoint& boundingBoxTopRight, | |||
| const RelativePoint& boundingBoxBottomLeft, | |||
| const RelativePoint& fontSizeAndScaleAnchor) | |||
| { | |||
| controlPoints[0] = boundingBoxTopLeft; | |||
| controlPoints[1] = boundingBoxTopRight; | |||
| controlPoints[2] = boundingBoxBottomLeft; | |||
| controlPoints[3] = fontSizeAndScaleAnchor; | |||
| } | |||
| //============================================================================== | |||
| static const Point<float> findNormalisedCoordWithinParallelogram (const Point<float>& origin, | |||
| Point<float> topRight, | |||
| Point<float> bottomLeft, | |||
| Point<float> target) | |||
| { | |||
| topRight -= origin; | |||
| bottomLeft -= origin; | |||
| target -= origin; | |||
| return Point<float> (Line<float> (Point<float>(), topRight).getIntersection (Line<float> (target, target - bottomLeft)).getDistanceFromOrigin(), | |||
| Line<float> (Point<float>(), bottomLeft).getIntersection (Line<float> (target, target - topRight)).getDistanceFromOrigin()); | |||
| } | |||
| void DrawableText::render (const Drawable::RenderingContext& context) const | |||
| { | |||
| Point<float> points[4]; | |||
| for (int i = 0; i < 4; ++i) | |||
| points[i] = controlPoints[i].resolve (getParent()); | |||
| const float w = Line<float> (points[0], points[1]).getLength(); | |||
| const float h = Line<float> (points[0], points[2]).getLength(); | |||
| const Point<float> fontCoords (findNormalisedCoordWithinParallelogram (points[0], points[1], points[2], points[3])); | |||
| const float fontHeight = jlimit (1.0f, h, fontCoords.getY()); | |||
| const float fontWidth = jlimit (0.01f, w, fontCoords.getX()); | |||
| Font f (font); | |||
| f.setHeight (fontHeight); | |||
| f.setHorizontalScale (fontWidth / fontHeight); | |||
| context.g.setColour (colour.withMultipliedAlpha (context.opacity)); | |||
| text.draw (context.g, context.transform); | |||
| GlyphArrangement ga; | |||
| ga.addFittedText (f, text, 0, 0, w, h, justification, 0x100000); | |||
| ga.draw (context.g, | |||
| AffineTransform::fromTargetPoints (0, 0, points[0].getX(), points[0].getY(), | |||
| w, 0, points[1].getX(), points[1].getY(), | |||
| 0, h, points[2].getX(), points[2].getY()) | |||
| .followedBy (context.transform)); | |||
| } | |||
| void DrawableText::resolveCorners (Point<float>* const corners) const | |||
| { | |||
| for (int i = 0; i < 3; ++i) | |||
| corners[i] = controlPoints[i].resolve (parent); | |||
| corners[3] = corners[1] + (corners[2] - corners[0]); | |||
| } | |||
| const Rectangle<float> DrawableText::getBounds() const | |||
| { | |||
| return text.getBoundingBox (0, -1, false); | |||
| Point<float> corners[4]; | |||
| resolveCorners (corners); | |||
| return Rectangle<float>::findAreaContainingPoints (corners, 4); | |||
| } | |||
| bool DrawableText::hitTest (float x, float y) const | |||
| { | |||
| return text.findGlyphIndexAt (x, y) >= 0; | |||
| Point<float> corners[4]; | |||
| resolveCorners (corners); | |||
| Path p; | |||
| p.startNewSubPath (corners[0].getX(), corners[0].getY()); | |||
| p.lineTo (corners[1].getX(), corners[1].getY()); | |||
| p.lineTo (corners[3].getX(), corners[3].getY()); | |||
| p.lineTo (corners[2].getX(), corners[2].getY()); | |||
| p.closeSubPath(); | |||
| return p.contains (x, y); | |||
| } | |||
| Drawable* DrawableText::createCopy() const | |||
| @@ -93,19 +180,128 @@ void DrawableText::invalidatePoints() | |||
| 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::fontSizeAnchor ("fontSizeAnchor"); | |||
| //============================================================================== | |||
| DrawableText::ValueTreeWrapper::ValueTreeWrapper (const ValueTree& state_) | |||
| : ValueTreeWrapperBase (state_) | |||
| { | |||
| jassert (state.hasType (valueTreeType)); | |||
| } | |||
| const String DrawableText::ValueTreeWrapper::getText() const | |||
| { | |||
| return state [text].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setText (const String& newText, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (text, newText, undoManager); | |||
| } | |||
| const Colour DrawableText::ValueTreeWrapper::getColour() const | |||
| { | |||
| return Colour::fromString (state [colour].toString()); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setColour (const Colour& newColour, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (colour, newColour.toString(), undoManager); | |||
| } | |||
| const Justification DrawableText::ValueTreeWrapper::getJustification() const | |||
| { | |||
| return Justification ((int) state [justification]); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setJustification (const Justification& newJustification, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (justification, newJustification.getFlags(), undoManager); | |||
| } | |||
| const 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); | |||
| } | |||
| const RelativePoint DrawableText::ValueTreeWrapper::getBoundingBoxTopLeft() const | |||
| { | |||
| return state [topLeft].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setBoundingBoxTopLeft (const RelativePoint& p, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (topLeft, p.toString(), undoManager); | |||
| } | |||
| const RelativePoint DrawableText::ValueTreeWrapper::getBoundingBoxTopRight() const | |||
| { | |||
| return state [topRight].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setBoundingBoxTopRight (const RelativePoint& p, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (topRight, p.toString(), undoManager); | |||
| } | |||
| const RelativePoint DrawableText::ValueTreeWrapper::getBoundingBoxBottomLeft() const | |||
| { | |||
| return state [bottomLeft].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setBoundingBoxBottomLeft (const RelativePoint& p, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (bottomLeft, p.toString(), undoManager); | |||
| } | |||
| const RelativePoint DrawableText::ValueTreeWrapper::getFontSizeAndScaleAnchor() const | |||
| { | |||
| return state [fontSizeAnchor].toString(); | |||
| } | |||
| void DrawableText::ValueTreeWrapper::setFontSizeAndScaleAnchor (const RelativePoint& p, UndoManager* undoManager) | |||
| { | |||
| state.setProperty (fontSizeAnchor, p.toString(), undoManager); | |||
| } | |||
| const Rectangle<float> DrawableText::refreshFromValueTree (const ValueTree& tree, ImageProvider*) | |||
| { | |||
| ValueTreeWrapper v (tree); | |||
| setName (v.getID()); | |||
| jassertfalse; // xxx not finished! | |||
| const RelativePoint p1 (v.getBoundingBoxTopLeft()), p2 (v.getBoundingBoxTopRight()), | |||
| p3 (v.getBoundingBoxBottomLeft()), p4 (v.getFontSizeAndScaleAnchor()); | |||
| 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 | |||
| || p1 != controlPoints[0] || p2 != controlPoints[1] || p3 != controlPoints[2] || p4 != controlPoints[3]) | |||
| { | |||
| const Rectangle<float> damage (getBounds()); | |||
| setBounds (p1, p2, p3, p4); | |||
| setColour (newColour); | |||
| setFont (newFont, false); | |||
| setJustification (newJustification); | |||
| setText (newText); | |||
| return damage.getUnion (getBounds()); | |||
| } | |||
| return Rectangle<float>(); | |||
| } | |||
| @@ -116,8 +312,14 @@ const ValueTree DrawableText::createValueTree (ImageProvider*) const | |||
| ValueTreeWrapper v (tree); | |||
| v.setID (getName(), 0); | |||
| jassertfalse; // xxx not finished! | |||
| v.setText (text, 0); | |||
| v.setFont (font, 0); | |||
| v.setJustification (justification, 0); | |||
| v.setColour (colour, 0); | |||
| v.setBoundingBoxTopLeft (controlPoints[0], 0); | |||
| v.setBoundingBoxTopRight (controlPoints[1], 0); | |||
| v.setBoundingBoxBottomLeft (controlPoints[2], 0); | |||
| v.setFontSizeAndScaleAnchor (controlPoints[3], 0); | |||
| return tree; | |||
| } | |||
| @@ -48,19 +48,8 @@ public: | |||
| virtual ~DrawableText(); | |||
| //============================================================================== | |||
| /** Sets the block of text to render */ | |||
| void setText (const GlyphArrangement& newText); | |||
| /** Sets a single line of text to render. | |||
| This is a convenient method of adding a single line - for | |||
| more complex text, use the setText() that takes a | |||
| GlyphArrangement instead. | |||
| */ | |||
| void setText (const String& newText, const Font& fontToUse); | |||
| /** Returns the text arrangement that was set with setText(). */ | |||
| const GlyphArrangement& getText() const throw() { return text; } | |||
| /** Sets the text to display.*/ | |||
| void setText (const String& newText); | |||
| /** Sets the colour of the text. */ | |||
| void setColour (const Colour& newColour); | |||
| @@ -68,6 +57,36 @@ public: | |||
| /** Returns the current text colour. */ | |||
| const Colour& getColour() const throw() { return colour; } | |||
| /** Sets the font to use. | |||
| Note that the font height and horizontal scale are actually based upon the position | |||
| of the fontSizeAndScaleAnchor parameter to setBounds(). If applySizeAndScale is true, then | |||
| the height and scale control point will be moved to match the dimensions of the font supplied; | |||
| if it is false, then the new font's height and scale are ignored. | |||
| */ | |||
| void setFont (const Font& newFont, bool applySizeAndScale); | |||
| /** Changes the justification of the text within the bounding box. */ | |||
| void setJustification (const Justification& newJustification); | |||
| /** Sets the bounding box and the control point that controls the font size. | |||
| The three bounding box points define the parallelogram within which the text will be | |||
| placed. The fontSizeAndScaleAnchor specifies a position within that parallelogram, whose | |||
| Y position (relative to the parallelogram's origin and possibly distorted shape) specifies | |||
| the font's height, and its X defines the font's horizontal scale. | |||
| */ | |||
| void setBounds (const RelativePoint& boundingBoxTopLeft, | |||
| const RelativePoint& boundingBoxTopRight, | |||
| const RelativePoint& boundingBoxBottomLeft, | |||
| const RelativePoint& fontSizeAndScaleAnchor); | |||
| /** Returns the origin of the text bounding box. */ | |||
| const RelativePoint& getBoundingBoxTopLeft() const throw() { return controlPoints[0]; } | |||
| /** Returns the top-right of the text bounding box. */ | |||
| const RelativePoint& getBoundingBoxTopRight() const throw() { return controlPoints[1]; } | |||
| /** Returns the bottom-left of the text bounding box. */ | |||
| const RelativePoint& getBoundingBoxBottomLeft() const throw() { return controlPoints[2]; } | |||
| /** Returns the point within the text bounding box which defines the size and scale of the font. */ | |||
| const RelativePoint& getFontSizeAndScaleAnchor() const throw() { return controlPoints[3]; } | |||
| //============================================================================== | |||
| /** @internal */ | |||
| @@ -96,18 +115,44 @@ public: | |||
| public: | |||
| ValueTreeWrapper (const ValueTree& state); | |||
| //xxx todo | |||
| const String getText() const; | |||
| void setText (const String& newText, UndoManager* undoManager); | |||
| private: | |||
| static const Identifier text; | |||
| const Colour getColour() const; | |||
| void setColour (const Colour& newColour, UndoManager* undoManager); | |||
| const Justification getJustification() const; | |||
| void setJustification (const Justification& newJustification, UndoManager* undoManager); | |||
| const Font getFont() const; | |||
| void setFont (const Font& newFont, UndoManager* undoManager); | |||
| const RelativePoint getBoundingBoxTopLeft() const; | |||
| void setBoundingBoxTopLeft (const RelativePoint& p, UndoManager* undoManager); | |||
| const RelativePoint getBoundingBoxTopRight() const; | |||
| void setBoundingBoxTopRight (const RelativePoint& p, UndoManager* undoManager); | |||
| const RelativePoint getBoundingBoxBottomLeft() const; | |||
| void setBoundingBoxBottomLeft (const RelativePoint& p, UndoManager* undoManager); | |||
| const RelativePoint getFontSizeAndScaleAnchor() const; | |||
| void setFontSizeAndScaleAnchor (const RelativePoint& p, UndoManager* undoManager); | |||
| static const Identifier text, colour, font, justification, topLeft, topRight, bottomLeft, fontSizeAnchor; | |||
| }; | |||
| //============================================================================== | |||
| juce_UseDebuggingNewOperator | |||
| private: | |||
| GlyphArrangement text; | |||
| RelativePoint controlPoints[4]; | |||
| Font font; | |||
| String text; | |||
| Colour colour; | |||
| Justification justification; | |||
| void resolveCorners (Point<float>* corners) const; | |||
| DrawableText& operator= (const DrawableText&); | |||
| }; | |||
| @@ -241,6 +241,15 @@ const AffineTransform AffineTransform::fromTargetPoints (const float x00, const | |||
| y10 - y00, y01 - y00, y00); | |||
| } | |||
| const AffineTransform AffineTransform::fromTargetPoints (const float sx1, const float sy1, const float tx1, const float ty1, | |||
| const float sx2, const float sy2, const float tx2, const float ty2, | |||
| const float sx3, const float sy3, const float tx3, const float ty3) throw() | |||
| { | |||
| return fromTargetPoints (sx1, sy1, sx2, sy2, sx3, sy3) | |||
| .inverted() | |||
| .followedBy (fromTargetPoints (tx1, ty1, tx2, ty2, tx3, ty3)); | |||
| } | |||
| bool AffineTransform::isOnlyTranslation() const throw() | |||
| { | |||
| return (mat01 == 0) | |||
| @@ -156,6 +156,12 @@ public: | |||
| float x10, float y10, | |||
| float x01, float y01) throw(); | |||
| /** Returns the transform that will map three specified points onto three target points. | |||
| */ | |||
| static const AffineTransform fromTargetPoints (float sourceX1, float sourceY1, float targetX1, float targetY1, | |||
| float sourceX2, float sourceY2, float targetX2, float targetY2, | |||
| float sourceX3, float sourceY3, float targetX3, float targetY3) throw(); | |||
| //============================================================================== | |||
| /** Returns the result of concatenating another transformation after this one. */ | |||
| const AffineTransform followedBy (const AffineTransform& other) const throw(); | |||
| @@ -164,6 +164,18 @@ public: | |||
| return findIntersection (start, end, line.start, line.end, intersection); | |||
| } | |||
| /** Finds the intersection between two lines. | |||
| @param line the line to intersect with | |||
| @returns the point at which the lines intersect, even if this lies beyond the end of the lines | |||
| */ | |||
| const Point<ValueType> getIntersection (const Line& line) const throw() | |||
| { | |||
| Point<ValueType> p; | |||
| findIntersection (start, end, line.start, line.end, p); | |||
| return p; | |||
| } | |||
| //============================================================================== | |||
| /** Returns the location of the point which is a given distance along this line. | |||
| @@ -141,12 +141,20 @@ public: | |||
| /** Returns the stroke thickness. */ | |||
| float getStrokeThickness() const throw() { return thickness; } | |||
| /** Sets the stroke thickness. */ | |||
| void setStrokeThickness (float newThickness) throw() { thickness = newThickness; } | |||
| /** Returns the joint style. */ | |||
| JointStyle getJointStyle() const throw() { return jointStyle; } | |||
| /** Sets the joint style. */ | |||
| void setJointStyle (JointStyle newStyle) throw() { jointStyle = newStyle; } | |||
| /** Returns the end-cap style. */ | |||
| EndCapStyle getEndStyle() const throw() { return endStyle; } | |||
| /** Sets the end-cap style. */ | |||
| void setEndStyle (EndCapStyle newStyle) throw() { endStyle = newStyle; } | |||
| //============================================================================== | |||
| juce_UseDebuggingNewOperator | |||
| @@ -509,6 +509,28 @@ public: | |||
| return Rectangle<int> (x1, y1, x2 - x1, y2 - y1); | |||
| } | |||
| /** Returns the smallest Rectangle that can contain a set of points. */ | |||
| static const Rectangle findAreaContainingPoints (const Point<ValueType>* const points, const int numPoints) throw() | |||
| { | |||
| if (numPoints == 0) | |||
| return Rectangle(); | |||
| ValueType minX (points[0].getX()); | |||
| ValueType maxX (minX); | |||
| ValueType minY (points[0].getY()); | |||
| ValueType maxY (minY); | |||
| for (int i = 1; i < numPoints; ++i) | |||
| { | |||
| minX = jmin (minX, points[i].getX()); | |||
| maxX = jmax (maxX, points[i].getX()); | |||
| minY = jmin (minY, points[i].getY()); | |||
| maxY = jmax (maxY, points[i].getY()); | |||
| } | |||
| return Rectangle (minX, minY, maxX - minX, maxY - minY); | |||
| } | |||
| /** Casts this rectangle to a Rectangle<float>. | |||
| Obviously this is mainly useful for rectangles that use integer types. | |||
| @see getSmallestIntegerContainer | |||
| @@ -145,7 +145,8 @@ public: | |||
| : context (context_), | |||
| flipHeight (flipHeight_), | |||
| state (new SavedState()), | |||
| numGradientLookupEntries (0) | |||
| numGradientLookupEntries (0), | |||
| lastClipRectIsValid (false) | |||
| { | |||
| CGContextRetain (context); | |||
| CGContextSaveGState(context); | |||
| @@ -174,11 +175,21 @@ public: | |||
| void setOrigin (int x, int y) | |||
| { | |||
| CGContextTranslateCTM (context, x, -y); | |||
| if (lastClipRectIsValid) | |||
| lastClipRect.translate (-x, -y); | |||
| } | |||
| bool clipToRectangle (const Rectangle<int>& r) | |||
| { | |||
| CGContextClipToRect (context, CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight())); | |||
| if (lastClipRectIsValid) | |||
| { | |||
| lastClipRect = lastClipRect.getIntersection (r); | |||
| return ! r.isEmpty(); | |||
| } | |||
| return ! isClipEmpty(); | |||
| } | |||
| @@ -187,6 +198,8 @@ public: | |||
| if (clipRegion.isEmpty()) | |||
| { | |||
| CGContextClipToRect (context, CGRectMake (0, 0, 0, 0)); | |||
| lastClipRectIsValid = true; | |||
| lastClipRect = Rectangle<int>(); | |||
| return false; | |||
| } | |||
| else | |||
| @@ -201,6 +214,7 @@ public: | |||
| } | |||
| CGContextClipToRects (context, rects, numRects); | |||
| lastClipRectIsValid = false; | |||
| return ! isClipEmpty(); | |||
| } | |||
| } | |||
| @@ -210,12 +224,14 @@ public: | |||
| RectangleList remaining (getClipBounds()); | |||
| remaining.subtract (r); | |||
| clipToRectangleList (remaining); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| void clipToPath (const Path& path, const AffineTransform& transform) | |||
| { | |||
| createPath (path, transform); | |||
| CGContextClip (context); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| void clipToImageAlpha (const Image& sourceImage, const Rectangle<int>& srcClip, const AffineTransform& transform) | |||
| @@ -240,6 +256,7 @@ public: | |||
| flip(); | |||
| CGImageRelease (image); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| } | |||
| @@ -250,17 +267,23 @@ public: | |||
| const Rectangle<int> getClipBounds() const | |||
| { | |||
| CGRect bounds = CGRectIntegral (CGContextGetClipBoundingBox (context)); | |||
| if (! lastClipRectIsValid) | |||
| { | |||
| CGRect bounds = CGRectIntegral (CGContextGetClipBoundingBox (context)); | |||
| lastClipRectIsValid = true; | |||
| lastClipRect.setBounds (roundToInt (bounds.origin.x), | |||
| roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)), | |||
| roundToInt (bounds.size.width), | |||
| roundToInt (bounds.size.height)); | |||
| } | |||
| return Rectangle<int> (roundToInt (bounds.origin.x), | |||
| roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)), | |||
| roundToInt (bounds.size.width), | |||
| roundToInt (bounds.size.height)); | |||
| return lastClipRect; | |||
| } | |||
| bool isClipEmpty() const | |||
| { | |||
| return CGRectIsEmpty (CGContextGetClipBoundingBox (context)); | |||
| return getClipBounds().isEmpty(); | |||
| } | |||
| //============================================================================== | |||
| @@ -280,6 +303,7 @@ public: | |||
| { | |||
| state = top; | |||
| stateStack.removeLast (1, false); | |||
| lastClipRectIsValid = false; | |||
| } | |||
| else | |||
| { | |||
| @@ -564,6 +588,8 @@ private: | |||
| const CGFloat flipHeight; | |||
| CGColorSpaceRef rgbColourSpace, greyColourSpace; | |||
| CGFunctionCallbacks gradientCallbacks; | |||
| mutable Rectangle<int> lastClipRect; | |||
| mutable bool lastClipRectIsValid; | |||
| struct SavedState | |||
| { | |||