diff --git a/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableTypeHandler.cpp b/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableTypeHandler.cpp index 66d6d53c29..734b0adca1 100644 --- a/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableTypeHandler.cpp +++ b/extras/Jucer (experimental)/Source/model/Drawable/jucer_DrawableTypeHandler.cpp @@ -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 (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 (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& approxPosition) + { + DrawableText dt; + dt.setText ("Text"); + dt.setBounds (RelativePoint (approxPosition), + RelativePoint (approxPosition + Point (100.0f, 0.0f)), + RelativePoint (approxPosition + Point (0.0f, 100.0f)), + RelativePoint (approxPosition + Point (25.0f, 25.0f))); + dt.setFont (Font (25.0f), true); + return dt.createValueTree (&document); + } + + void createPropertyEditors (DrawableTypeInstance& item, Array & 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 & 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 & 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 & 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; } diff --git a/extras/Jucer (experimental)/Source/ui/jucer_MainWindow.cpp b/extras/Jucer (experimental)/Source/ui/jucer_MainWindow.cpp index 8ae7cea40a..dd5d4b8afd 100644 --- a/extras/Jucer (experimental)/Source/ui/jucer_MainWindow.cpp +++ b/extras/Jucer (experimental)/Source/ui/jucer_MainWindow.cpp @@ -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() diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index 62bd8cda28..8398b7ad42 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -84706,25 +84706,12 @@ const Rectangle DrawableImage::getBounds() const if (image.isNull()) return Rectangle(); - Point resolved[3]; - for (int i = 0; i < 3; ++i) - resolved[i] = controlPoints[i].resolve (parent); - - const Point bottomRight (resolved[1] + (resolved[2] - resolved[0])); - float minX = bottomRight.getX(); - float maxX = minX; - float minY = bottomRight.getY(); - float maxY = minY; - + Point 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 (minX, minY, maxX - minX, maxY - minY); + corners[3] = corners[1] + (corners[2] - corners[0]); + return Rectangle::findAreaContainingPoints (corners, 4); } bool DrawableImage::hitTest (float x, float y) const @@ -85264,6 +85251,7 @@ const Rectangle 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 left (Point(), controlPoints[2].resolve (getParent())); + const Line top (Point(), 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 findNormalisedCoordWithinParallelogram (const Point& origin, + Point topRight, + Point bottomLeft, + Point target) +{ + topRight -= origin; + bottomLeft -= origin; + target -= origin; + + return Point (Line (Point(), topRight).getIntersection (Line (target, target - bottomLeft)).getDistanceFromOrigin(), + Line (Point(), bottomLeft).getIntersection (Line (target, target - topRight)).getDistanceFromOrigin()); } void DrawableText::render (const Drawable::RenderingContext& context) const { + Point points[4]; + for (int i = 0; i < 4; ++i) + points[i] = controlPoints[i].resolve (getParent()); + + const float w = Line (points[0], points[1]).getLength(); + const float h = Line (points[0], points[2]).getLength(); + + const Point 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* 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 DrawableText::getBounds() const { - return text.getBoundingBox (0, -1, false); + Point corners[4]; + resolveCorners (corners); + return Rectangle::findAreaContainingPoints (corners, 4); } bool DrawableText::hitTest (float x, float y) const { - return text.findGlyphIndexAt (x, y) >= 0; + Point 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 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 damage (getBounds()); + + setBounds (p1, p2, p3, p4); + setColour (newColour); + setFont (newFont, false); + setJustification (newJustification); + setText (newText); + + return damage.getUnion (getBounds()); + + } return Rectangle(); } @@ -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& 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(); 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& srcClip, const AffineTransform& transform) @@ -263903,6 +264116,7 @@ public: flip(); CGImageRelease (image); + lastClipRectIsValid = false; } } @@ -263913,17 +264127,23 @@ public: const Rectangle 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 (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 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& 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(); 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& srcClip, const AffineTransform& transform) @@ -268516,6 +268755,7 @@ public: flip(); CGImageRelease (image); + lastClipRectIsValid = false; } } @@ -268526,17 +268766,23 @@ public: const Rectangle 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 (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 lastClipRect; + mutable bool lastClipRectIsValid; struct SavedState { diff --git a/juce_amalgamated.h b/juce_amalgamated.h index 867774fd69..432474c90d 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -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 getIntersection (const Line& line) const throw() + { + Point 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 (x1, y1, x2 - x1, y2 - y1); } + /** Returns the smallest Rectangle that can contain a set of points. */ + static const Rectangle findAreaContainingPoints (const Point* 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. 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* corners) const; DrawableText& operator= (const DrawableText&); }; diff --git a/src/audio/audio_sources/juce_AudioSourcePlayer.h b/src/audio/audio_sources/juce_AudioSourcePlayer.h index d9c6c7929b..91361daf0e 100644 --- a/src/audio/audio_sources/juce_AudioSourcePlayer.h +++ b/src/audio/audio_sources/juce_AudioSourcePlayer.h @@ -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, diff --git a/src/gui/graphics/drawables/juce_DrawableImage.cpp b/src/gui/graphics/drawables/juce_DrawableImage.cpp index d34b41820b..883e30673c 100644 --- a/src/gui/graphics/drawables/juce_DrawableImage.cpp +++ b/src/gui/graphics/drawables/juce_DrawableImage.cpp @@ -130,25 +130,12 @@ const Rectangle DrawableImage::getBounds() const if (image.isNull()) return Rectangle(); - Point resolved[3]; + Point corners[4]; for (int i = 0; i < 3; ++i) - resolved[i] = controlPoints[i].resolve (parent); - - const Point 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 (minX, minY, maxX - minX, maxY - minY); + corners[3] = corners[1] + (corners[2] - corners[0]); + return Rectangle::findAreaContainingPoints (corners, 4); } bool DrawableImage::hitTest (float x, float y) const diff --git a/src/gui/graphics/drawables/juce_DrawablePath.cpp b/src/gui/graphics/drawables/juce_DrawablePath.cpp index e76624d24b..481ee649d1 100644 --- a/src/gui/graphics/drawables/juce_DrawablePath.cpp +++ b/src/gui/graphics/drawables/juce_DrawablePath.cpp @@ -382,6 +382,7 @@ const Rectangle DrawablePath::refreshFromValueTree (const ValueTree& tree { damageRect = getBounds(); path.swapWithPath (newPath); + strokeNeedsUpdating = true; strokeType = newStroke; needsRedraw = true; } diff --git a/src/gui/graphics/drawables/juce_DrawableText.cpp b/src/gui/graphics/drawables/juce_DrawableText.cpp index 76d2cb24a8..ec75aad6b3 100644 --- a/src/gui/graphics/drawables/juce_DrawableText.cpp +++ b/src/gui/graphics/drawables/juce_DrawableText.cpp @@ -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 left (Point(), controlPoints[2].resolve (getParent())); + const Line top (Point(), 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 findNormalisedCoordWithinParallelogram (const Point& origin, + Point topRight, + Point bottomLeft, + Point target) +{ + topRight -= origin; + bottomLeft -= origin; + target -= origin; + + return Point (Line (Point(), topRight).getIntersection (Line (target, target - bottomLeft)).getDistanceFromOrigin(), + Line (Point(), bottomLeft).getIntersection (Line (target, target - topRight)).getDistanceFromOrigin()); +} + void DrawableText::render (const Drawable::RenderingContext& context) const { + Point points[4]; + for (int i = 0; i < 4; ++i) + points[i] = controlPoints[i].resolve (getParent()); + + const float w = Line (points[0], points[1]).getLength(); + const float h = Line (points[0], points[2]).getLength(); + + const Point 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* 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 DrawableText::getBounds() const { - return text.getBoundingBox (0, -1, false); + Point corners[4]; + resolveCorners (corners); + return Rectangle::findAreaContainingPoints (corners, 4); } bool DrawableText::hitTest (float x, float y) const { - return text.findGlyphIndexAt (x, y) >= 0; + Point 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 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 damage (getBounds()); + + setBounds (p1, p2, p3, p4); + setColour (newColour); + setFont (newFont, false); + setJustification (newJustification); + setText (newText); + + return damage.getUnion (getBounds()); + + } return Rectangle(); } @@ -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; } diff --git a/src/gui/graphics/drawables/juce_DrawableText.h b/src/gui/graphics/drawables/juce_DrawableText.h index 63ced63342..f41705f597 100644 --- a/src/gui/graphics/drawables/juce_DrawableText.h +++ b/src/gui/graphics/drawables/juce_DrawableText.h @@ -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* corners) const; DrawableText& operator= (const DrawableText&); }; diff --git a/src/gui/graphics/geometry/juce_AffineTransform.cpp b/src/gui/graphics/geometry/juce_AffineTransform.cpp index f835f0c920..f1830a417e 100644 --- a/src/gui/graphics/geometry/juce_AffineTransform.cpp +++ b/src/gui/graphics/geometry/juce_AffineTransform.cpp @@ -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) diff --git a/src/gui/graphics/geometry/juce_AffineTransform.h b/src/gui/graphics/geometry/juce_AffineTransform.h index e1379b57a6..65147acbb4 100644 --- a/src/gui/graphics/geometry/juce_AffineTransform.h +++ b/src/gui/graphics/geometry/juce_AffineTransform.h @@ -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(); diff --git a/src/gui/graphics/geometry/juce_Line.h b/src/gui/graphics/geometry/juce_Line.h index b8587ead4b..d5888846ba 100644 --- a/src/gui/graphics/geometry/juce_Line.h +++ b/src/gui/graphics/geometry/juce_Line.h @@ -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 getIntersection (const Line& line) const throw() + { + Point 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. diff --git a/src/gui/graphics/geometry/juce_PathStrokeType.h b/src/gui/graphics/geometry/juce_PathStrokeType.h index 47c7201486..a48c774681 100644 --- a/src/gui/graphics/geometry/juce_PathStrokeType.h +++ b/src/gui/graphics/geometry/juce_PathStrokeType.h @@ -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 diff --git a/src/gui/graphics/geometry/juce_Rectangle.h b/src/gui/graphics/geometry/juce_Rectangle.h index 26be95302e..c8dc107f26 100644 --- a/src/gui/graphics/geometry/juce_Rectangle.h +++ b/src/gui/graphics/geometry/juce_Rectangle.h @@ -509,6 +509,28 @@ public: return Rectangle (x1, y1, x2 - x1, y2 - y1); } + /** Returns the smallest Rectangle that can contain a set of points. */ + static const Rectangle findAreaContainingPoints (const Point* 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. Obviously this is mainly useful for rectangles that use integer types. @see getSmallestIntegerContainer diff --git a/src/native/mac/juce_mac_CoreGraphicsContext.mm b/src/native/mac/juce_mac_CoreGraphicsContext.mm index 94e79574c4..9bb62e4016 100644 --- a/src/native/mac/juce_mac_CoreGraphicsContext.mm +++ b/src/native/mac/juce_mac_CoreGraphicsContext.mm @@ -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& 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(); 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& srcClip, const AffineTransform& transform) @@ -240,6 +256,7 @@ public: flip(); CGImageRelease (image); + lastClipRectIsValid = false; } } @@ -250,17 +267,23 @@ public: const Rectangle 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 (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 lastClipRect; + mutable bool lastClipRectIsValid; struct SavedState {