private void DrawBubbleLeftShape(CGRect frame, CGPath path) { path.AddArcToPoint( frame.X, frame.Y, frame.X + frame.Width, frame.Y, this.CornerRadius); path.AddArcToPoint( frame.X + frame.Width, frame.Y, frame.X + frame.Width, frame.Y + frame.Height, this.CornerRadius); path.AddArcToPoint( frame.X + frame.Width, frame.Y + frame.Height, frame.X, frame.Y + frame.Height, this.CornerRadius); path.AddArcToPoint( frame.X, frame.Y + frame.Height, frame.X, frame.Y, this.CornerRadius); }
CGPath CreateTrianglePath(nfloat x, nfloat y, nfloat width, nfloat height, nfloat cornerRadius) { //calc 3 points for the triangle var point1 = new CGPoint(x + width, y + height); //mid right var point2 = new CGPoint(x, y); //upper left var point3 = new CGPoint(x, y + height); //mid left var point4 = new CGPoint(x, y + Frame.Height); //lower left var point5 = new CGPoint(x + width, y + Frame.Height); //lower right //calc the center of an edge var baseMidpoint = new CGPoint((point4.X + point5.X) / 2.0f, (point4.Y + point5.Y) / 2.0f); //create the path starting with base midpoint and adding arc counter clockwise following the points var path = new CGPath(); path.MoveToPoint(baseMidpoint.X, baseMidpoint.Y); path.AddArcToPoint(point5.X, point5.Y, point1.X, point1.Y, cornerRadius); path.AddArcToPoint(point1.X, point1.Y, point2.X, point2.Y, cornerRadius); path.AddArcToPoint(point2.X, point2.Y, point3.X, point3.Y, cornerRadius); path.AddArcToPoint(point3.X, point3.Y, point4.X, point4.Y, cornerRadius); path.AddArcToPoint(point4.X, point4.Y, point5.X, point5.Y, cornerRadius); path.CloseSubpath(); return(path); }
public override void Draw(RectangleF rect) { WeatherForecastAnnotation annotation; CGPath path; base.Draw(rect); annotation = Annotation as WeatherForecastAnnotation; if (annotation == null) { return; } // Get the current graphics context CGContext context = UIGraphics.GetCurrentContext(); context.SetLineWidth(1.0f); // Draw the gray pointed shape: path = new CGPath(); path.MoveToPoint(14.0f, 0.0f); path.AddLineToPoint(0.0f, 0.0f); path.AddLineToPoint(55.0f, 50.0f); context.AddPath(path); context.SetFillColor(UIColor.LightGray.CGColor); context.SetStrokeColor(UIColor.Gray.CGColor); context.DrawPath(CGPathDrawingMode.FillStroke); // Draw the cyan rounded box path = new CGPath(); path.MoveToPoint(15.0f, 0.5f); path.AddArcToPoint(59.5f, 00.5f, 59.5f, 05.0f, 5.0f); path.AddArcToPoint(59.5f, 69.5f, 55.5f, 69.5f, 5.0f); path.AddArcToPoint(10.5f, 69.5f, 10.5f, 64.0f, 5.0f); path.AddArcToPoint(10.5f, 00.5f, 15.5f, 00.5f, 5.0f); context.AddPath(path); context.SetFillColor(UIColor.Cyan.CGColor); context.SetStrokeColor(UIColor.Blue.CGColor); context.DrawPath(CGPathDrawingMode.FillStroke); // Create the location & temperature string WeatherForecast forecast = annotation.Forecast; NSString temperature = new NSString(string.Format("{0}\n{1} / {2}", forecast.Place, forecast.High, forecast.Low)); // Draw the text in black UIColor.Black.SetColor(); temperature.DrawString(new RectangleF(15.0f, 5.0f, 50.0f, 40.0f), UIFont.SystemFontOfSize(11.0f)); temperature.Dispose(); // Draw the icon for the weather condition string imageName = string.Format("WeatherMap.WeatherIcons.{0}.png", forecast.Condition); UIImage image = UIImage.FromResource(typeof(WeatherAnnotationView).Assembly, imageName); image.Draw(new RectangleF(12.5f, 28.0f, 45.0f, 45.0f)); image.Dispose(); }
public override void Draw (CGRect rect) { WeatherForecastAnnotation annotation; CGPath path; base.Draw (rect); annotation = Annotation as WeatherForecastAnnotation; if (annotation == null) return; // Get the current graphics context using (var context = UIGraphics.GetCurrentContext ()) { context.SetLineWidth (1.0f); // Draw the gray pointed shape: path = new CGPath (); path.MoveToPoint (14.0f, 0.0f); path.AddLineToPoint (0.0f, 0.0f); path.AddLineToPoint (55.0f, 50.0f); context.AddPath (path); context.SetFillColor (UIColor.LightGray.CGColor); context.SetStrokeColor (UIColor.Gray.CGColor); context.DrawPath (CGPathDrawingMode.FillStroke); // Draw the cyan rounded box path = new CGPath (); path.MoveToPoint (15.0f, 0.5f); path.AddArcToPoint (59.5f, 00.5f, 59.5f, 05.0f, 5.0f); path.AddArcToPoint (59.5f, 69.5f, 55.5f, 69.5f, 5.0f); path.AddArcToPoint (10.5f, 69.5f, 10.5f, 64.0f, 5.0f); path.AddArcToPoint (10.5f, 00.5f, 15.5f, 00.5f, 5.0f); context.AddPath (path); context.SetFillColor (UIColor.Cyan.CGColor); context.SetStrokeColor (UIColor.Blue.CGColor); context.DrawPath (CGPathDrawingMode.FillStroke); // Create the location & temperature string WeatherForecast forecast = annotation.Forecast; NSString temperature = new NSString (string.Format ("{0}\n{1} / {2}", forecast.Place, forecast.High, forecast.Low)); // Draw the text in black UIColor.Black.SetColor (); temperature.DrawString (new CGRect (15.0f, 5.0f, 50.0f, 40.0f), UIFont.SystemFontOfSize (11.0f)); temperature.Dispose (); // Draw the icon for the weather condition string imageName = string.Format ("WeatherMap.WeatherIcons.{0}.png", forecast.Condition); UIImage image = UIImage.FromResource (typeof(WeatherAnnotationView).Assembly, imageName); image.Draw (new CGRect (12.5f, 28.0f, 45.0f, 45.0f)); image.Dispose (); } }
public static CGPath MakeRoundedPath(float size, float radius) { float hsize = size / 2; var path = new CGPath(); path.MoveToPoint(size, hsize); path.AddArcToPoint(size, size, hsize, size, radius); path.AddArcToPoint(0, size, 0, hsize, radius); path.AddArcToPoint(0, 0, hsize, 0, radius); path.AddArcToPoint(size, 0, size, hsize, radius); path.CloseSubpath(); return path; }
internal static CGPath CreateClippingPath(CGRect rect, float radius) { var path = new CGPath(); path.MoveToPoint(rect.GetMinX(), rect.GetMinY()); path.AddLineToPoint(rect.GetMinX(), rect.GetMaxY() - radius); path.AddArcToPoint(rect.GetMinX(), rect.GetMaxY(), rect.GetMinX() + radius, rect.GetMaxY(), radius); path.AddLineToPoint(rect.GetMaxX() - radius, rect.GetMaxY()); path.AddArcToPoint(rect.GetMaxX(), rect.GetMaxY(), rect.GetMaxX(), rect.GetMaxY() - radius, radius); path.AddLineToPoint(rect.GetMaxX(), rect.GetMinY()); path.CloseSubpath(); return(path); }
private static CGPath GetRoundedPath(CornerRadius cornerRadius, CGRect area, CGPath path = null) { path ??= new CGPath(); // How AddArcToPoint works: // http://www.twistedape.me.uk/blog/2013/09/23/what-arctopointdoes/ path.MoveToPoint(area.GetMidX(), area.Y); path.AddArcToPoint(area.Right, area.Top, area.Right, area.GetMidY(), (float)cornerRadius.TopRight); path.AddArcToPoint(area.Right, area.Bottom, area.GetMidX(), area.Bottom, (float)cornerRadius.BottomRight); path.AddArcToPoint(area.Left, area.Bottom, area.Left, area.GetMidY(), (float)cornerRadius.BottomLeft); path.AddArcToPoint(area.Left, area.Top, area.GetMidX(), area.Top, (float)cornerRadius.TopLeft); path.AddLineToPoint(area.GetMidX(), area.Y); return(path); }
internal static CGPath MakeRoundedPath(float size) { float hsize = size/2; var path = new CGPath (); path.MoveToPoint (size, hsize); path.AddArcToPoint (size, size, hsize, size, 4); path.AddArcToPoint (0, size, 0, hsize, 4); path.AddArcToPoint (0, 0, hsize, 0, 4); path.AddArcToPoint (size, 0, size, hsize, 4); path.CloseSubpath (); return path; }
public static CGPath MakeRoundedPath(float size, float radius) { float hsize = size / 2; var path = new CGPath(); path.MoveToPoint(size, hsize); path.AddArcToPoint(size, size, hsize, size, radius); path.AddArcToPoint(0, size, 0, hsize, radius); path.AddArcToPoint(0, 0, hsize, 0, radius); path.AddArcToPoint(size, 0, size, hsize, radius); path.CloseSubpath(); return(path); }
static internal CGPath MakeRoundedPath(float size) { float hsize = size / 2; var path = new CGPath(); path.MoveToPoint(size, hsize); path.AddArcToPoint(size, size, hsize, size, 4); path.AddArcToPoint(0, size, 0, hsize, 4); path.AddArcToPoint(0, 0, hsize, 0, 4); path.AddArcToPoint(size, 0, size, hsize, 4); path.CloseSubpath(); return(path); }
protected virtual void DrawHeart(CGContext context, float x, float y, float width, float height, float cornerRadius, bool fill, bool stroke) { var length = Math.Min(height, width); var startPoint = new CGPoint(x, y + 2f * length / 3f); var p1 = new CGPoint(x, y + length); var p2 = new CGPoint(x + 2f * length / 3f, y + length); var c1 = new CGPoint(x + 2f * length / 3f, y + 2f * length / 3f); var c2 = new CGPoint(x + length / 3f, y + length / 3f); var radius = length / 3f; var path = new CGPath(); path.MoveToPoint(startPoint.X, startPoint.Y); path.AddArcToPoint(p1.X, p1.Y, p2.X, p2.Y, cornerRadius); path.AddLineToPoint(p2.X, p2.Y); path.AddArc(c1.X, c1.Y, radius, (float)-Math.PI / 2f, (float)Math.PI / 2f, false); path.AddArc(c2.X, c2.Y, radius, 0f, (float)Math.PI, true); path.CloseSubpath(); var transform = CGAffineTransform.MakeTranslation(-length / 3f, -length * 2f / 3f); transform.Rotate((float)-Math.PI / 4f); transform.Scale(0.85f, 0.85f); transform.Translate(width / 2f, 1.1f * height / 2f); path = path.CopyByTransformingPath(transform); context.AddPath(path); this.DrawPath(context, fill, stroke); }
protected virtual void DrawPoints(CGContext context, List <CGPoint> points, float cornerRadius, bool fill, bool stroke, float x = 0f, float y = 0f) { if (points == null || points.Count == 0) { return; } var midPoint = new CGPoint(0.5 * (points[0].X + points[1].X), 0.5 * (points[0].Y + points[1].Y)); var path = new CGPath(); path.MoveToPoint(midPoint); for (var i = 0; i < points.Count; ++i) { path.AddArcToPoint(points[(i + 1) % points.Count].X, points[(i + 1) % points.Count].Y, points[(i + 2) % points.Count].X, points[(i + 2) % points.Count].Y, cornerRadius); } path.CloseSubpath(); var transform = CGAffineTransform.MakeTranslation(x, y); //path = path.CopyByTransformingPath(transform); context.AddPath(path); this.DrawPath(context, fill, stroke); }
/// <summary> /// Creates a path for a rectangle with rounded corners /// </summary> /// <param name="rect"> /// The <see cref="CGRect"/> rectangle bounds /// </param> /// <param name="radius"> /// The <see cref="System.Single"/> size of the rounded corners /// </param> /// <returns> /// A <see cref="CGPath"/> that can be used to stroke the rounded rectangle /// </returns> public static CGPath MakeRoundedRectPath(CGRect rect, float radius) { nfloat minx = rect.Left; nfloat midx = rect.Left + (rect.Width) / 2; nfloat maxx = rect.Right; nfloat miny = rect.Top; nfloat midy = rect.Y + rect.Size.Height / 2; nfloat maxy = rect.Bottom; var path = new CGPath(); path.MoveToPoint(minx, midy); path.AddArcToPoint(minx, miny, midx, miny, radius); path.AddArcToPoint(maxx, miny, maxx, midy, radius); path.AddArcToPoint(maxx, maxy, midx, maxy, radius); path.AddArcToPoint(minx, maxy, minx, midy, radius); path.CloseSubpath(); return path; }
/// <summary> /// Creates a path for a rectangle with rounded corners /// </summary> /// <param name="rect"> /// The <see cref="RectangleF"/> rectangle bounds /// </param> /// <param name="radius"> /// The <see cref="System.Single"/> size of the rounded corners /// </param> /// <returns> /// A <see cref="CGPath"/> that can be used to stroke the rounded rectangle /// </returns> public static CGPath MakeRoundedRectPath(CGRect rect, float radius) { var minx = rect.Left; var midx = rect.Left + (rect.Width) / 2; var maxx = rect.Right; var miny = rect.Top; var midy = rect.Y + rect.Size.Height / 2; var maxy = rect.Bottom; var path = new CGPath(); path.MoveToPoint(minx, midy); path.AddArcToPoint(minx, miny, midx, miny, radius); path.AddArcToPoint(maxx, miny, maxx, midy, radius); path.AddArcToPoint(maxx, maxy, midx, maxy, radius); path.AddArcToPoint(minx, maxy, minx, midy, radius); path.CloseSubpath(); return(path); }
private CGPath GenerateRandomPath(CGPoint start) { var endingPoint = getEndingPoint(); var path = new CGPath(); path.MoveToPoint(start); path.AddArcToPoint(start.X, start.Y, endingPoint.X, endingPoint.Y, 10); //path.AddLineToPoint(endingPoint.X, endingPoint.Y); return(path); }
public void AddArcToPoint() { var matrix = CGAffineTransform.MakeIdentity(); using (CGPath p1 = new CGPath()) using (CGPath p2 = new CGPath()) { Assert.IsTrue(p1.IsEmpty, "IsEmpty-1"); p1.MoveToPoint(0, 0); p1.AddArcToPoint(0, 0, 10, 0, 90); p2.MoveToPoint(0, 0); p2.AddArcToPoint(matrix, 0, 0, 10, 0, 90); Assert.IsFalse(p1.IsEmpty, "IsEmpty-2"); Assert.That(p1, Is.EqualTo(p2), "CGPathEqualToPath"); } }
private CGPath NewContentPathWithBorderWidth(float borderWidth, FPPopoverArrowDirection direction) { float w = Bounds.Size.Width; float h = Bounds.Size.Height; float ah = FP_POPOVER_ARROW_HEIGHT; //is the height of the triangle of the arrow float aw = FP_POPOVER_ARROW_BASE / 2.0f; //is the 1/2 of the base of the arrow float radius = FP_POPOVER_RADIUS; float b = borderWidth; //RectangleF rect; SizeF rectSize = new SizeF(); PointF rectLocation = new PointF(); if (direction == FPPopoverArrowDirection.FPPopoverArrowDirectionUp) { rectSize.Width = w - 2f * b; rectSize.Height = h - ah - 2f * b; rectLocation.X = b; rectLocation.Y = ah + b; } else if (direction == FPPopoverArrowDirection.FPPopoverArrowDirectionDown) { rectSize.Width = w - 2f * b; rectSize.Height = h - ah - 2f * b; rectLocation.X = b; rectLocation.Y = b; } else if (direction == FPPopoverArrowDirection.FPPopoverArrowDirectionRight) { rectSize.Width = w - ah - 2f * b; rectSize.Height = h - 2f * b; rectLocation.X = b; rectLocation.Y = b; } else { //Assuming direction == FPPopoverArrowDirectionLeft to suppress static analyzer warnings rectSize.Width = w - ah - 2f * b; rectSize.Height = h - 2f * b; rectLocation.X = ah + b; rectLocation.Y = b; } //the arrow will be near the origin float ax = RelativeOrigin.X - aw; //the start of the arrow when UP or DOWN if (ax < aw + b) { ax = aw + b; } else if (ax + 2 * aw + 2 * b > Bounds.Size.Width) { ax = Bounds.Size.Width - 2 * aw - 2 * b; } float ay = RelativeOrigin.Y - aw; //the start of the arrow when RIGHT or LEFT if (ay < aw + b) { ay = aw + b; } else if (ay + 2f * aw + 2f * b > Bounds.Size.Height) { ay = Bounds.Size.Height - 2f * aw - 2f * b; } //ROUNDED RECT // arrow UP var rect = new RectangleF(rectLocation, rectSize); var innerRect = RectangleF.Inflate(rect, -radius, -radius); float inside_right = innerRect.Location.X + innerRect.Size.Width; float outside_right = rect.Location.X + rect.Size.Width; float inside_bottom = innerRect.Location.Y + innerRect.Size.Height; float outside_bottom = rect.Location.Y + rect.Size.Height; float inside_top = innerRect.Location.Y; float outside_top = rect.Location.Y; float outside_left = rect.Location.X; //drawing the border with arrow var path = new CGPath(); path.MoveToPoint(innerRect.Location.X, outside_top); //top arrow if (direction == FPPopoverArrowDirection.FPPopoverArrowDirectionUp) { path.AddLineToPoint(ax, ah + b); path.AddLineToPoint(ax + aw, b); path.AddLineToPoint(ax + 2f * aw, ah + b); } path.AddLineToPoint(inside_right, outside_top); path.AddArcToPoint(outside_right, outside_top, outside_right, inside_top, radius); //right arrow if (direction == FPPopoverArrowDirection.FPPopoverArrowDirectionRight) { path.AddLineToPoint(outside_right, ay); path.AddLineToPoint(outside_right + ah + b, ay + aw); path.AddLineToPoint(outside_right, ay + 2f * aw); } path.AddLineToPoint(outside_right, inside_bottom); path.AddArcToPoint(outside_right, outside_bottom, inside_right, outside_bottom, radius); //down arrow if (direction == FPPopoverArrowDirection.FPPopoverArrowDirectionDown) { path.AddLineToPoint(ax + 2f * aw, outside_bottom); path.AddLineToPoint(ax + aw, outside_bottom + ah); path.AddLineToPoint(ax, outside_bottom); } path.AddLineToPoint(innerRect.Location.X, outside_bottom); path.AddArcToPoint(outside_left, outside_bottom, outside_left, inside_bottom, radius); //left arrow if (direction == FPPopoverArrowDirection.FPPopoverArrowDirectionLeft) { path.AddLineToPoint(outside_left, ay + 2f * aw); path.AddLineToPoint(outside_left - ah - b, ay + aw); path.AddLineToPoint(outside_left, ay); } path.AddLineToPoint(outside_left, inside_top); path.AddArcToPoint(outside_left, outside_top, innerRect.Location.X, outside_top, radius); path.CloseSubpath(); return(path); }
/// <summary> /// Create a vector-based speech bubble using Bezier Paths /// </summary> /// <returns>The speech bubble around rect.</returns> /// <param name="rect">Rect.</param> /// <param name="arrowDirection">Arrow can be on left or right and top or bottom</param> /// <param name = "borderColor"></param> /// <param name = "backgroundColor"></param> /// <param name = "strokeWidth"></param> /// <param name = "borderRadius"></param> /// <param name = "triangleHeight"></param> /// <param name = "triangleWidth"></param> public static void DrawSpeechBubbleAroundRect(CGRect rect, ArrowDirections arrowDirection, UIColor borderColor, UIColor fillColor, UIColor gradientFillColor, float borderWidth, float cornerRadius, float arrowHeight = 16f, float arrowWidth = 12f, float arrowOffset = 0f) { if (rect == CGRect.Empty) { return; // Nothing to draw here folks } Console.WriteLine("VectorSpeechBubbleUIView: DrawSpeechBubbleAroundRect"); bool hasUpArrow = arrowDirection.IsUp(); bool hasDownArrow = arrowDirection.IsDown(); bool hasLeftArrow = arrowDirection.IsLeft(); bool hasRightArrow = arrowDirection.IsRight(); float boxWidth = (float)rect.Width - (hasLeftArrow ? arrowHeight : 0f) - (hasRightArrow ? arrowHeight : 0f); float boxHeight = (float)rect.Height - (hasUpArrow ? arrowHeight : 0f) - (hasDownArrow ? arrowHeight : 0f); if (boxWidth < 0f) { System.Diagnostics.Debug.Fail("Not enough space for the left and/or right arrows: funky rendering is likely!"); } if (boxHeight < 0f) { System.Diagnostics.Debug.Fail("Not enough space for the top and/or bottom arrows: funky rendering is likely!"); } bool useGradient = true; if (fillColor == gradientFillColor || gradientFillColor == null) { useGradient = false; } // Adjust the corner radius to prevent strange things happening when the width/height of the control is very small if (cornerRadius > (boxWidth - arrowWidth) / 2f) { cornerRadius = (boxWidth - arrowWidth) / 2f; } if (cornerRadius > (boxHeight - arrowWidth) / 2f) { cornerRadius = (boxHeight - arrowWidth) / 2f; } // Adjust the arrow offset to be within acceptable bounds if (arrowOffset < cornerRadius) { arrowOffset = cornerRadius; // don't start the arrow on the rounded part of the rounded rectangle } // Reduce the border width if it is bigger than the width or the height of the control // Strange things happen at the edge :) if (borderWidth > rect.Width) { borderWidth = (float)rect.Width; } if (borderWidth > rect.Height) { borderWidth = (float)rect.Height; } // Reduce the arrow width if there is not enough space to render it // TODO: This doesn't seem to have any effect if (arrowDirection.IsLeft() || arrowDirection.IsRight()) { if (arrowWidth > rect.Height) { arrowWidth = (float)rect.Height; } } if (arrowDirection.IsUp() || arrowDirection.IsDown()) { if (arrowHeight > rect.Width) { arrowWidth = (float)rect.Width; } } using (var context = UIGraphics.GetCurrentContext()) { context.SetLineJoin(CGLineJoin.Round); context.SetLineWidth(borderWidth); context.SetStrokeColor(borderColor.CGColor); context.SetFillColor(fillColor.CGColor); var path = new CGPath(); var midArrowWidth = arrowWidth / 2.0f; var leftX = 0.5f; if (hasLeftArrow) { leftX = leftX + arrowHeight; } float rightX = (float)Math.Round(rect.Width) - 0.5f; if (hasRightArrow) { rightX = rightX - arrowHeight; } var topY = 0.5f; if (hasUpArrow) { topY = topY + arrowHeight; } float bottomY = (float)rect.Height - 0.5f; if (hasDownArrow) { bottomY = bottomY - arrowHeight; } var midY = (nfloat)Math.Round((topY + bottomY) / 2.0f) + 0.5f; var midX = (nfloat)Math.Round((leftX + rightX) / 2.0f) + 0.5f; // Starts at top left corner path.MoveToPoint(leftX + cornerRadius, topY); // Determine offset for the arrow position based on whether its left, center, or right, and the ArrowOffset property nfloat arrowOffsetX = 0f; nfloat arrowOffsetY = 0f; if (arrowDirection == ArrowDirections.UpLeft || arrowDirection == ArrowDirections.DownLeft) { arrowOffsetX = (nfloat)(-1 * ((rect.Width / 2f) - midArrowWidth - cornerRadius - 4f)); // Move the up/down arrow to the left corner // Items on the left shouldn't be right of the midline if (arrowOffsetX > 0f) { arrowOffsetX = 0f; } } if (arrowDirection == ArrowDirections.UpRight || arrowDirection == ArrowDirections.DownRight) { arrowOffsetX = (nfloat)((rect.Width / 2f) - midArrowWidth - cornerRadius - 4f); // Move the up/down arrow to the right corner // Items on the right shouldn't be left of the midline if (arrowOffsetX < 0f) { arrowOffsetX = 0f; } } if (arrowDirection == ArrowDirections.LeftTop || arrowDirection == ArrowDirections.RightTop) { arrowOffsetY = (nfloat)(-1 * ((rect.Height / 2f) - midArrowWidth - cornerRadius - 4f)); // Move the left/tight arrow to the top corner // Items on the top shouldn't be below the midline if (arrowOffsetY > 0f) { arrowOffsetY = 0f; } } if (arrowDirection == ArrowDirections.LeftBottom || arrowDirection == ArrowDirections.RightBottom) { arrowOffsetY = (nfloat)((rect.Height / 2f) - midArrowWidth - cornerRadius - 4f); // Move the up/down arrow to the bottom corner // Items on the bottom shouldn't be above the midline if (arrowOffsetY < 0f) { arrowOffsetY = 0f; } } if (hasUpArrow) { // Adds a line to where the arrow starts path.AddLineToPoint((nfloat)Math.Round(midX - midArrowWidth) + 0.5f + arrowOffsetX, topY); // Draws the arrow up, and then down again path.AddLineToPoint(midX + arrowOffsetX, 0.5f); path.AddLineToPoint(midX + midArrowWidth + 0.5f + arrowOffsetX, arrowHeight + 0.5f); } // Top right corner path.AddArcToPoint(rightX, topY, rightX, bottomY, cornerRadius); if (hasRightArrow) { // Adds a line to where the arrow starts path.AddLineToPoint(rightX, midY - midArrowWidth + arrowOffsetY); // Draws the arrow right, and then left again path.AddLineToPoint(rightX + arrowHeight, midY + arrowOffsetY); path.AddLineToPoint(rightX, midY + midArrowWidth + arrowOffsetY); } // To Bottom right corner (curling towards bottom left corner) path.AddArcToPoint(rightX, bottomY, leftX, bottomY, cornerRadius); if (hasDownArrow) { // Adds a line to where the arrow starts path.AddLineToPoint(midX + midArrowWidth + arrowOffsetX, bottomY); // Draws the arrow up, and then down again path.AddLineToPoint(midX + arrowOffsetX, bottomY + arrowHeight); path.AddLineToPoint(midX - midArrowWidth + arrowOffsetX, bottomY); } // To bottom left corner (curling up in direction of top left corner) path.AddArcToPoint(leftX, bottomY, leftX, topY, cornerRadius); if (hasLeftArrow) { // Adds a line to where the arrow starts path.AddLineToPoint(leftX, midY + midArrowWidth + arrowOffsetY); // Draws the arrow right, and then left again path.AddLineToPoint(leftX - arrowHeight, midY + arrowOffsetY); path.AddLineToPoint(leftX, midY - midArrowWidth + arrowOffsetY); } // To top left corner (curling in direction of top right corner) path.AddArcToPoint(leftX, topY, rightX, topY, cornerRadius); path.CloseSubpath(); context.AddPath(path); if (useGradient) { context.Clip(); using (var rgb = CGColorSpace.CreateDeviceRGB()) { CGGradient gradient = new CGGradient(rgb, new CGColor[] { gradientFillColor.CGColor, fillColor.CGColor, }); context.DrawLinearGradient(gradient, new CGPoint(path.BoundingBox.Left, path.BoundingBox.Top), new CGPoint(path.BoundingBox.GetMidX(), path.BoundingBox.Bottom), CGGradientDrawingOptions.DrawsBeforeStartLocation | CGGradientDrawingOptions.DrawsAfterEndLocation); } context.AddPath(path); context.DrawPath(CGPathDrawingMode.Stroke); } else { context.Clip(); context.AddPath(path); // Single color only context.DrawPath(CGPathDrawingMode.FillStroke); } } }
public CGPath GetCellBorderPath(RectangleF rect) { var cornerRadius = 10; float minx = rect.GetMinX(), midx = rect.GetMidX(), maxx = rect.GetMaxX(); float miny = rect.GetMinY(), midy = rect.GetMidY(), maxy = rect.GetMaxY(); CGPath path = new CGPath(); var cellPosition = CellPosition; if (cellPosition == CellPosition.Top) { minx = minx + 1; miny = miny + 1; maxx = maxx - 1; path.MoveToPoint(minx, maxy); path.AddArcToPoint(minx, miny, midx, miny, cornerRadius); path.AddArcToPoint(maxx, miny, maxx, maxy, cornerRadius); path.AddLineToPoint(maxx, maxy); } else if (cellPosition == CellPosition.Bottom) { minx = minx + 1; maxx = maxx - 1; maxy = maxy - 1; path.MoveToPoint(minx, miny); path.AddArcToPoint(minx, maxy, midx, maxy, cornerRadius); path.AddArcToPoint(maxx, maxy, maxx, miny, cornerRadius); path.AddLineToPoint(maxx, miny); } else if (cellPosition == CellPosition.Middle) { minx = minx + 1; maxx = maxx - 1; path.MoveToPoint(minx, miny); path.AddLineToPoint(maxx, miny); path.AddLineToPoint(maxx, maxy); path.AddLineToPoint(minx, maxy); } else if (cellPosition == CellPosition.Single) { minx = minx + 1; miny = miny + 1; maxx = maxx - 1; maxy = maxy - 1; path.MoveToPoint(minx, midy); path.AddArcToPoint(minx, miny, midx, miny, cornerRadius); path.AddArcToPoint(maxx, miny, maxx, midy, cornerRadius); path.AddArcToPoint(maxx, maxy, midx, maxy, cornerRadius); path.AddArcToPoint(minx, maxy, minx, midy, cornerRadius); } path.CloseSubpath(); return(path); }
private static IDisposable InnerCreateLayer(CALayer parent, CGRect area, Brush background, Thickness borderThickness, Brush borderBrush, CornerRadius cornerRadius) { var disposables = new CompositeDisposable(); var sublayers = new List <CALayer>(); var adjustedLineWidth = borderThickness.Top; var adjustedLineWidthOffset = adjustedLineWidth / 2; var adjustedArea = area; adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); if (cornerRadius != CornerRadius.None) { var maxRadius = Math.Max(0, Math.Min((float)area.Width / 2 - adjustedLineWidthOffset, (float)area.Height / 2 - adjustedLineWidthOffset)); cornerRadius = new CornerRadius( Math.Min(cornerRadius.TopLeft, maxRadius), Math.Min(cornerRadius.TopRight, maxRadius), Math.Min(cornerRadius.BottomRight, maxRadius), Math.Min(cornerRadius.BottomLeft, maxRadius)); CAShapeLayer layer = new CAShapeLayer(); layer.LineWidth = (nfloat)adjustedLineWidth; layer.FillColor = null; var path = new CGPath(); Brush.AssignAndObserveBrush(borderBrush, color => layer.StrokeColor = color) .DisposeWith(disposables); // How AddArcToPoint works: // http://www.twistedape.me.uk/blog/2013/09/23/what-arctopointdoes/ path.MoveToPoint(adjustedArea.GetMidX(), adjustedArea.Y); path.AddArcToPoint(adjustedArea.Right, adjustedArea.Top, adjustedArea.Right, adjustedArea.GetMidY(), (float)cornerRadius.TopRight); path.AddArcToPoint(adjustedArea.Right, adjustedArea.Bottom, adjustedArea.GetMidX(), adjustedArea.Bottom, (float)cornerRadius.BottomRight); path.AddArcToPoint(adjustedArea.Left, adjustedArea.Bottom, adjustedArea.Left, adjustedArea.GetMidY(), (float)cornerRadius.BottomLeft); path.AddArcToPoint(adjustedArea.Left, adjustedArea.Top, adjustedArea.GetMidX(), adjustedArea.Top, (float)cornerRadius.TopLeft); path.CloseSubpath(); var lgbBackground = background as LinearGradientBrush; var scbBackground = background as SolidColorBrush; var imgBackground = background as ImageBrush; var insertionIndex = 0; if (lgbBackground != null) { var fillMask = new CAShapeLayer() { Path = path, Frame = area, // We only use the fill color to create the mask area FillColor = UIColor.White.CGColor, }; // We reduce the adjustedArea again so that the gradient is inside the border (like in Windows) adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); CreateLinearGradientBrushLayers(area, adjustedArea, parent, sublayers, ref insertionIndex, lgbBackground, fillMask); } else if (scbBackground != null) { Brush.AssignAndObserveBrush(scbBackground, color => layer.FillColor = color) .DisposeWith(disposables); } else if (imgBackground != null) { var uiImage = imgBackground.ImageSource?.ImageData; if (uiImage != null && uiImage.Size != CGSize.Empty) { var fillMask = new CAShapeLayer() { Path = path, Frame = area, // We only use the fill color to create the mask area FillColor = UIColor.White.CGColor, }; // We reduce the adjustedArea again so that the image is inside the border (like in Windows) adjustedArea = adjustedArea.Shrink((nfloat)adjustedLineWidthOffset); CreateImageBrushLayers(area, adjustedArea, parent, sublayers, ref insertionIndex, imgBackground, fillMask); } } else { layer.FillColor = Colors.Transparent; } layer.Path = path; sublayers.Add(layer); parent.InsertSublayer(layer, insertionIndex); } else { var lgbBackground = background as LinearGradientBrush; var scbBackground = background as SolidColorBrush; var imgBackground = background as ImageBrush; if (lgbBackground != null) { var fullArea = new CGRect( area.X + borderThickness.Left, area.Y + borderThickness.Top, area.Width - borderThickness.Left - borderThickness.Right, area.Height - borderThickness.Top - borderThickness.Bottom); var insideArea = new CGRect(CGPoint.Empty, fullArea.Size); var insertionIndex = 0; CreateLinearGradientBrushLayers(fullArea, insideArea, parent, sublayers, ref insertionIndex, lgbBackground, fillMask: null); } else if (scbBackground != null) { Brush.AssignAndObserveBrush(scbBackground, c => parent.BackgroundColor = c) .DisposeWith(disposables); // This is required because changing the CornerRadius changes the background drawing // implementation and we don't want a rectangular background behind a rounded background. Disposable.Create(() => parent.BackgroundColor = null) .DisposeWith(disposables); } else if (imgBackground != null) { var uiImage = imgBackground.ImageSource?.ImageData; if (uiImage != null && uiImage.Size != CGSize.Empty) { var fullArea = new CGRect( area.X + borderThickness.Left, area.Y + borderThickness.Top, area.Width - borderThickness.Left - borderThickness.Right, area.Height - borderThickness.Top - borderThickness.Bottom); var insideArea = new CGRect(CGPoint.Empty, fullArea.Size); var insertionIndex = 0; CreateImageBrushLayers(fullArea, insideArea, parent, sublayers, ref insertionIndex, imgBackground, fillMask: null); } } else { parent.BackgroundColor = Colors.Transparent; } if (borderThickness != Thickness.Empty) { var strokeColor = borderBrush ?? SolidColorBrushHelper.Transparent; Action <Action <CAShapeLayer, CGPath> > createLayer = builder => { CAShapeLayer layer = new CAShapeLayer(); var path = new CGPath(); Brush.AssignAndObserveBrush(borderBrush, c => layer.StrokeColor = c) .DisposeWith(disposables); builder(layer, path); layer.Path = path; // Must be inserted below the other subviews, which may happen when // the current view has subviews. sublayers.Add(layer); parent.InsertSublayer(layer, 0); }; if (borderThickness.Top != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Top; var lineWidthAdjust = (nfloat)(borderThickness.Top / 2); path.MoveToPoint(area.X, area.Y + lineWidthAdjust); path.AddLineToPoint(area.X + area.Width, area.Y + lineWidthAdjust); path.CloseSubpath(); }); } if (borderThickness.Bottom != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Bottom; var lineWidthAdjust = borderThickness.Bottom / 2; path.MoveToPoint(area.X, (nfloat)(area.Y + area.Height - lineWidthAdjust)); path.AddLineToPoint(area.X + area.Width, (nfloat)(area.Y + area.Height - lineWidthAdjust)); path.CloseSubpath(); }); } if (borderThickness.Left != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Left; var lineWidthAdjust = borderThickness.Left / 2; path.MoveToPoint((nfloat)(area.X + lineWidthAdjust), area.Y); path.AddLineToPoint((nfloat)(area.X + lineWidthAdjust), area.Y + area.Height); path.CloseSubpath(); }); } if (borderThickness.Right != 0) { createLayer((l, path) => { l.LineWidth = (nfloat)borderThickness.Right; var lineWidthAdjust = borderThickness.Right / 2; path.MoveToPoint((nfloat)(area.X + area.Width - lineWidthAdjust), area.Y); path.AddLineToPoint((nfloat)(area.X + area.Width - lineWidthAdjust), area.Y + area.Height); path.CloseSubpath(); }); } } } disposables.Add(() => { foreach (var sl in sublayers) { sl.RemoveFromSuperLayer(); sl.Dispose(); } } ); return(disposables); }
public CGPath GetCellBorderPath(RectangleF rect) { var cornerRadius = 10; float minx = rect.GetMinX(), midx = rect.GetMidX(), maxx = rect.GetMaxX(); float miny = rect.GetMinY(), midy = rect.GetMidY(), maxy = rect.GetMaxY(); CGPath path = new CGPath(); var cellPosition = CellPosition; if (cellPosition == CellPosition.Top) { minx = minx + 1; miny = miny + 1; maxx = maxx - 1; path.MoveToPoint(minx, maxy); path.AddArcToPoint(minx, miny, midx, miny, cornerRadius); path.AddArcToPoint(maxx, miny, maxx, maxy, cornerRadius); path.AddLineToPoint(maxx, maxy); } else if (cellPosition == CellPosition.Bottom) { minx = minx + 1; maxx = maxx - 1; maxy = maxy - 1; path.MoveToPoint(minx, miny); path.AddArcToPoint(minx, maxy, midx, maxy, cornerRadius); path.AddArcToPoint(maxx, maxy, maxx, miny, cornerRadius); path.AddLineToPoint(maxx, miny); } else if (cellPosition == CellPosition.Middle) { minx = minx + 1; maxx = maxx - 1; path.MoveToPoint(minx, miny); path.AddLineToPoint(maxx, miny); path.AddLineToPoint(maxx, maxy); path.AddLineToPoint(minx, maxy); } else if (cellPosition == CellPosition.Single) { minx = minx + 1; miny = miny + 1; maxx = maxx - 1; maxy = maxy - 1; path.MoveToPoint(minx, midy); path.AddArcToPoint(minx, miny, midx, miny, cornerRadius); path.AddArcToPoint(maxx, miny, maxx, midy, cornerRadius); path.AddArcToPoint(maxx, maxy, midx, maxy, cornerRadius); path.AddArcToPoint(minx, maxy, minx, midy, cornerRadius); } path.CloseSubpath(); return path; }
/// <summary> /// Create a vector-based speech bubble using Bezier Paths /// </summary> /// <returns>The speech bubble around rect.</returns> /// <param name="rect">Rect.</param> /// <param name="arrowDirection">Arrow can be on left or right and top or bottom</param> /// <param name = "borderColor"></param> /// <param name = "backgroundColor"></param> /// <param name = "strokeWidth"></param> /// <param name = "borderRadius"></param> /// <param name = "triangleHeight"></param> /// <param name = "triangleWidth"></param> public static void DrawSpeechBubbleAroundRect(CGRect rect, ArrowDirections arrowDirection, UIColor borderColor, UIColor fillColor, UIColor gradientFillColor, float borderWidth, float cornerRadius, float arrowHeight = 16f, float arrowWidth = 12f, float arrowOffset = 0f) { if (rect == CGRect.Empty) return; // Nothing to draw here folks Console.WriteLine("VectorSpeechBubbleUIView: DrawSpeechBubbleAroundRect"); bool hasUpArrow = arrowDirection.IsUp(); bool hasDownArrow = arrowDirection.IsDown(); bool hasLeftArrow = arrowDirection.IsLeft(); bool hasRightArrow = arrowDirection.IsRight(); float boxWidth = (float)rect.Width - (hasLeftArrow ? arrowHeight : 0f) - (hasRightArrow ? arrowHeight : 0f); float boxHeight = (float)rect.Height - (hasUpArrow ? arrowHeight : 0f) - (hasDownArrow ? arrowHeight : 0f); if (boxWidth < 0f) { System.Diagnostics.Debug.Fail("Not enough space for the left and/or right arrows: funky rendering is likely!"); } if (boxHeight < 0f) { System.Diagnostics.Debug.Fail("Not enough space for the top and/or bottom arrows: funky rendering is likely!"); } bool useGradient = true; if (fillColor == gradientFillColor || gradientFillColor == null) { useGradient = false; } // Adjust the corner radius to prevent strange things happening when the width/height of the control is very small if (cornerRadius > (boxWidth - arrowWidth)/2f) { cornerRadius = (boxWidth - arrowWidth)/2f; } if (cornerRadius > (boxHeight - arrowWidth)/2f) { cornerRadius = (boxHeight - arrowWidth)/2f; } // Adjust the arrow offset to be within acceptable bounds if (arrowOffset < cornerRadius) { arrowOffset = cornerRadius; // don't start the arrow on the rounded part of the rounded rectangle } // Reduce the border width if it is bigger than the width or the height of the control // Strange things happen at the edge :) if (borderWidth > rect.Width) borderWidth = (float)rect.Width; if (borderWidth > rect.Height) borderWidth = (float)rect.Height; // Reduce the arrow width if there is not enough space to render it // TODO: This doesn't seem to have any effect if (arrowDirection.IsLeft () || arrowDirection.IsRight ()) { if (arrowWidth > rect.Height) arrowWidth = (float)rect.Height; } if (arrowDirection.IsUp () || arrowDirection.IsDown ()) { if (arrowHeight > rect.Width) arrowWidth = (float)rect.Width; } using (var context = UIGraphics.GetCurrentContext ()) { context.SetLineJoin (CGLineJoin.Round); context.SetLineWidth (borderWidth); context.SetStrokeColor (borderColor.CGColor); context.SetFillColor (fillColor.CGColor); var path = new CGPath (); var midArrowWidth = arrowWidth / 2.0f; var leftX = 0.5f; if (hasLeftArrow) leftX = leftX + arrowHeight; float rightX = (float)Math.Round(rect.Width) - 0.5f; if (hasRightArrow) rightX = rightX - arrowHeight; var topY = 0.5f; if (hasUpArrow) { topY = topY + arrowHeight; } float bottomY = (float)rect.Height - 0.5f; if (hasDownArrow) { bottomY = bottomY - arrowHeight; } var midY = (nfloat)Math.Round ((topY + bottomY) / 2.0f) + 0.5f; var midX = (nfloat)Math.Round ((leftX + rightX) / 2.0f) + 0.5f; // Starts at top left corner path.MoveToPoint (leftX + cornerRadius, topY); // Determine offset for the arrow position based on whether its left, center, or right, and the ArrowOffset property nfloat arrowOffsetX = 0f; nfloat arrowOffsetY = 0f; if (arrowDirection == ArrowDirections.UpLeft || arrowDirection == ArrowDirections.DownLeft) { arrowOffsetX = (nfloat)(-1 * ((rect.Width / 2f) - midArrowWidth - cornerRadius - 4f)); // Move the up/down arrow to the left corner // Items on the left shouldn't be right of the midline if (arrowOffsetX > 0f) arrowOffsetX = 0f; } if (arrowDirection == ArrowDirections.UpRight || arrowDirection == ArrowDirections.DownRight) { arrowOffsetX = (nfloat)((rect.Width / 2f) - midArrowWidth - cornerRadius - 4f); // Move the up/down arrow to the right corner // Items on the right shouldn't be left of the midline if (arrowOffsetX < 0f) arrowOffsetX = 0f; } if (arrowDirection == ArrowDirections.LeftTop || arrowDirection == ArrowDirections.RightTop) { arrowOffsetY = (nfloat)(-1 * ((rect.Height / 2f) - midArrowWidth - cornerRadius - 4f)); // Move the left/tight arrow to the top corner // Items on the top shouldn't be below the midline if (arrowOffsetY > 0f) arrowOffsetY = 0f; } if (arrowDirection == ArrowDirections.LeftBottom || arrowDirection == ArrowDirections.RightBottom) { arrowOffsetY = (nfloat)((rect.Height / 2f) - midArrowWidth - cornerRadius - 4f); // Move the up/down arrow to the bottom corner // Items on the bottom shouldn't be above the midline if (arrowOffsetY < 0f) arrowOffsetY = 0f; } if (hasUpArrow) { // Adds a line to where the arrow starts path.AddLineToPoint ((nfloat)Math.Round(midX - midArrowWidth) + 0.5f + arrowOffsetX, topY); // Draws the arrow up, and then down again path.AddLineToPoint (midX + arrowOffsetX, 0.5f); path.AddLineToPoint (midX + midArrowWidth + 0.5f + arrowOffsetX, arrowHeight + 0.5f); } // Top right corner path.AddArcToPoint (rightX, topY, rightX, bottomY, cornerRadius); if (hasRightArrow) { // Adds a line to where the arrow starts path.AddLineToPoint (rightX, midY - midArrowWidth + arrowOffsetY ); // Draws the arrow right, and then left again path.AddLineToPoint (rightX + arrowHeight, midY + arrowOffsetY); path.AddLineToPoint (rightX, midY + midArrowWidth + arrowOffsetY); } // To Bottom right corner (curling towards bottom left corner) path.AddArcToPoint(rightX, bottomY, leftX, bottomY, cornerRadius); if (hasDownArrow) { // Adds a line to where the arrow starts path.AddLineToPoint (midX + midArrowWidth + arrowOffsetX, bottomY); // Draws the arrow up, and then down again path.AddLineToPoint (midX + arrowOffsetX, bottomY + arrowHeight); path.AddLineToPoint (midX - midArrowWidth + arrowOffsetX, bottomY); } // To bottom left corner (curling up in direction of top left corner) path.AddArcToPoint (leftX, bottomY, leftX, topY, cornerRadius); if (hasLeftArrow) { // Adds a line to where the arrow starts path.AddLineToPoint (leftX, midY + midArrowWidth + arrowOffsetY ); // Draws the arrow right, and then left again path.AddLineToPoint (leftX - arrowHeight, midY + arrowOffsetY); path.AddLineToPoint (leftX, midY - midArrowWidth + arrowOffsetY); } // To top left corner (curling in direction of top right corner) path.AddArcToPoint(leftX, topY, rightX, topY, cornerRadius); path.CloseSubpath (); context.AddPath (path); if (useGradient) { context.Clip (); using (var rgb = CGColorSpace.CreateDeviceRGB ()) { CGGradient gradient = new CGGradient (rgb, new CGColor[] { gradientFillColor.CGColor, fillColor.CGColor, }); context.DrawLinearGradient (gradient, new CGPoint (path.BoundingBox.Left, path.BoundingBox.Top), new CGPoint (path.BoundingBox.GetMidX (), path.BoundingBox.Bottom), CGGradientDrawingOptions.DrawsBeforeStartLocation | CGGradientDrawingOptions.DrawsAfterEndLocation); } context.AddPath (path); context.DrawPath (CGPathDrawingMode.Stroke); } else { context.Clip (); context.AddPath (path); // Single color only context.DrawPath (CGPathDrawingMode.FillStroke); } } }