Example #1
0
        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 ();
			}
		}
Example #5
0
        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;
        }
Example #6
0
        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);
        }
Example #8
0
        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;
        }
Example #9
0
        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);
        }
Example #10
0
        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);
        }
Example #11
0
        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);
        }
Example #12
0
        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);
        }
Example #13
0
        /// <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;
        }
Example #14
0
        /// <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);
        }
Example #15
0
        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);
        }
Example #16
0
        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");
                }
        }
Example #17
0
        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);
                }
            }
        }
Example #19
0
        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);
        }
Example #20
0
        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);
				}
			}
		}