/// <summary>Calculate major anchor points</summary> /// <param name="info"></param> /// <param name="canvas"></param> private void CalcFrame(SKImageInfo info, SKCanvas canvas) { // the vertical center line float vertCenter = Convert.ToSingle(info.Width / 2.0); // length to use for outer square - shortest of vertical vs horizontal int edgeLen = new int[] { info.Width, info.Height }.Min(); // 1/2 the distance float halfEdge = edgeLen / 2.0F; // set the arc line thickness this.ArcLineWidth = (edgeLen / 20.0F) * this.ArcLineThickness; // inset border of the graph frame based on the arc line width float border = this.ArcLineWidth * BorderFactor; // center of the drawing area this.Origin = new SKPoint(vertCenter, info.Height / 2.0F); // the square to draw the ring inside of this.OuterRect = new SKRect(this.Origin.X - halfEdge + border, this.Origin.Y - halfEdge + border, this.Origin.X + halfEdge - 1 - border, this.Origin.Y + halfEdge - 1 - border); // the square inscribed inside the circle this.InscribedRect = QuickCalc.InscribeSquare(this.Origin, halfEdge - border); // calculate the font for the labels, the same width is used for both which could cause issues this.PrimeLabelMetrics = this.CalculateLabels(this.PrimeLabel, this.PrimeLabelAttributes, this.PrimeLabelFontScale); this.SubLabelMetrics = this.CalculateLabels(this.SubLabel, this.SubLabelAttributes, this.SubLabelFontScale); }
/// <summary>Calculates the needed font size for the X labels</summary> /// <returns></returns> /// <remarks>Right now only calculates the prime label text box</remarks> internal LabelMetrics CalculateLabels(string label, FontAttributes attr, float fontScale = 100.0F) { // if the label is empty if (String.IsNullOrEmpty(label)) { return(LabelMetrics.Default()); } if (fontScale < 1) { fontScale = 1.0F; } float maxHeight = 0.0F; float fontSize = 250.0F; float desc = 0.0F; // set the max label width to some default // use the inscribed square width //this.MaxPrimeLabelWidth = this.OuterRect.Width - (this.ArcLineWidth * BorderFactor * 2.0F); this.MaxPrimeLabelWidth = this.InscribedRect.Width; // determine the ideal font using (SKPaint textPaint = new SKPaint()) { // init the font with all its properties this.SetFont(textPaint, fontSize, attr); // find the min font size for the width LabelMetrics widthMet = QuickCalc.CalcSizeForWidth(textPaint, this.MaxPrimeLabelWidth, label); textPaint.TextSize = widthMet.FontSize; // get the NEW font metrics which includes max height SKFontMetrics metrics; textPaint.GetFontMetrics(out metrics); // set the calculated values thus far desc = metrics.Descent; maxHeight = metrics.Descent - metrics.Ascent; // for fun calculate the width of the circle at the top of the text //float chord = Convert.ToSingle( QuickCalc.CalcChord( this.OuterRect.Width / 2.0F, maxHeight / 2.0F ) ); //this.MaxPrimeLabelWidth = chord - ( this.ArcLineWidth * BorderFactor * 2.0F ); // now check its not too tall if (maxHeight > this.InscribedRect.Height) { // scale the font further based on height float scale = this.InscribedRect.Height / maxHeight; textPaint.TextSize = textPaint.TextSize * scale; } // now scale by the relative user set scale if (fontScale < 100.0F) { textPaint.TextSize = textPaint.TextSize * (fontScale / 100.0F); } // remeasure after user set scaling textPaint.GetFontMetrics(out metrics); fontSize = textPaint.TextSize; desc = metrics.Descent; maxHeight = metrics.Descent - metrics.Ascent; } return(new LabelMetrics(fontSize, maxHeight, desc)); }
/// <summary>Draw a pie meter</summary> private void PaintPie(SKImageInfo info, SKCanvas canvas, bool handles = false) { float arcWidth = (info.Width / 15.0F) * 1.0F; // always draw a background ring using (SKPath path = new SKPath()) { path.AddArc(this.OuterRect, 0, 360); canvas.DrawPath(path, new SKPaint() { Color = _disabled, StrokeWidth = this.ArcLineWidth, Style = SKPaintStyle.Stroke, IsAntialias = true }); } // calc the sum float total = this.Value1 + this.Value2; // bail if both are 0 if (total <= 0.0) { return; } // calc the sweep angle float sweep = (this.Value1 / total) * 360.0F; // calc the angles, 0 is to the right, start from overhead float rewardSweep = (sweep > 360.0) ? 360.0F : sweep; float bonusStartAngle = QuickCalc.Revolution(270.0F + rewardSweep); // draw arc 1 using (SKPath path = new SKPath()) { using (SKPaint arcPaint = new SKPaint { Style = SKPaintStyle.Stroke, Color = this.ArcColor1.ToSKColor(), StrokeWidth = this.ArcLineWidth, IsAntialias = true }) { path.AddArc(this.OuterRect, 270, rewardSweep); canvas.DrawPath(path, arcPaint); } } // draw arc 2 using (SKPath path = new SKPath()) { using (SKPaint arcPaint = new SKPaint { Style = SKPaintStyle.Stroke, Color = this.ArcColor2.ToSKColor(), StrokeWidth = this.ArcLineWidth, IsAntialias = true }) { path.AddArc(this.OuterRect, bonusStartAngle, 360 - rewardSweep); canvas.DrawPath(path, arcPaint); } } if (!handles) { return; } // draw handles using (SKPaint handlePaint = new SKPaint() { Color = this.ArcColor1.ToSKColor(), StrokeCap = SKStrokeCap.Round, StrokeWidth = this.ArcLineWidth * 2, IsAntialias = true }) { canvas.DrawPoint(this.VerticalCenter, this.OuterRect.Top, handlePaint); // where's the other handle? float rad = QuickCalc.Deg2Rad(QuickCalc.Revolution(360 - rewardSweep + 90)); Tuple <float, float> pt = QuickCalc.Transform(rad, this.OuterRect.Width / 2.0F); handlePaint.Color = this.ArcColor2.ToSKColor(); canvas.DrawPoint(pt.Item1 + info.Rect.MidX, info.Rect.MidY - pt.Item2, handlePaint); } }
/// <summary>Draw a progress meter</summary> /// <remarks> /// Value 1 is the part and Value 2 is the total possible /// Value 1 can exceed value 2 for mutiple revolutions /// </remarks> private void PaintProgress(SKImageInfo info, SKCanvas canvas) { // set up the gradient colors SKColor[] colors = new SKColor[2] { this.ArcColor1.ToSKColor(), this.ArcColor2.ToSKColor() }; // sweep is in DEG -> normalize the sweep angle between 0 and 360 //float sweep = QuickCalc.Revolution( ( this.Value1 / this.Value2 ) * 360.0F ); //sweep = ( sweep > 360.0 ) ? 360.0F : sweep; float sweep = (this.Value1 % this.Value2 / this.Value2) * 360.0F; // we have to roate the drawing canvas 90 degrees CCW canvas.RotateDegrees(-90, info.Width / 2, info.Height / 2); // no value if (this.Value1 <= 0.0) { // draw background ring using (SKPath path = new SKPath()) { using (SKPaint bkgPaint = new SKPaint() { Color = _disabled, StrokeWidth = this.ArcLineWidth, Style = SKPaintStyle.Stroke, IsAntialias = true }) { path.AddArc(this.OuterRect, 0, 360.0F); canvas.DrawPath(path, bkgPaint); } } } // less than 1 revolution else if (this.Value1 < this.Value2) { // draw background ring using (SKPath path = new SKPath()) { using (SKPaint bkgPaint = new SKPaint() { Color = _disabled, StrokeWidth = this.ArcLineWidth, Style = SKPaintStyle.Stroke, IsAntialias = true }) { path.AddArc(this.OuterRect, 0, 360.0F); canvas.DrawPath(path, bkgPaint); } } // draw the partial arc using (SKPath path = new SKPath()) { using (SKPaint arcPaint = new SKPaint { Style = SKPaintStyle.Stroke, Color = this.ArcColor1.ToSKColor() }) { arcPaint.StrokeWidth = this.ArcLineWidth; arcPaint.StrokeCap = SKStrokeCap.Butt; arcPaint.IsAntialias = true; arcPaint.Shader = SKShader.CreateSweepGradient(this.Origin, colors, new Single[] { 0, 1 }, SKShaderTileMode.Clamp, 0, sweep); // create an arc to sweep along path.AddArc(this.OuterRect, 0, sweep); canvas.DrawPath(path, arcPaint); } } } else // 1 or more revolution { // draw background ring using (SKPath path = new SKPath()) { using (SKPaint bkgPaint = new SKPaint() { Color = this.ArcColor1.ToSKColor(), StrokeWidth = this.ArcLineWidth }) { bkgPaint.Style = SKPaintStyle.Stroke; bkgPaint.IsAntialias = true; path.AddArc(this.OuterRect, 0, 360.0F); canvas.DrawPath(path, bkgPaint); } } // rotate the canvas by the sweep angle so we always start at 0 canvas.RotateDegrees(sweep - 180, info.Width / 2, info.Height / 2); // draw the partial gradiant arc using (SKPath path = new SKPath()) { using (SKPaint arcPaint = new SKPaint { Style = SKPaintStyle.Stroke }) { arcPaint.Color = this.ArcColor2.ToSKColor(); arcPaint.StrokeWidth = this.ArcLineWidth; arcPaint.StrokeCap = SKStrokeCap.Butt; arcPaint.IsAntialias = true; // sweep gradient uses start angle to end angle arcPaint.Shader = SKShader.CreateSweepGradient(this.Origin, colors, new Single[] { 0, 1 }, SKShaderTileMode.Clamp, 0, 180); // create an arc to sweep along - uses start angle and then how many degrees to rotate from start path.AddArc(this.OuterRect, 0, 180); canvas.DrawPath(path, arcPaint); } } canvas.RotateDegrees(-(sweep - 180), info.Width / 2, info.Height / 2); } // calc pts for the trailing handle Tuple <float, float> pt1 = QuickCalc.Transform(QuickCalc.Deg2Rad(sweep + (this.ArcLineWidth * 0.075F)), this.OuterRect.Width / 2.0F); Tuple <float, float> pt2 = QuickCalc.Transform(QuickCalc.Deg2Rad(sweep), this.OuterRect.Width / 2.0F); // draw the trailing point with shadow using (SKPaint handlePaint = new SKPaint() { Color = this.BackgroundColor.ToSKColor() }) { handlePaint.StrokeCap = SKStrokeCap.Round; handlePaint.StrokeWidth = this.ArcLineWidth * 2; handlePaint.IsAntialias = true; // shadow canvas.DrawPoint(pt1.Item1 + info.Rect.MidX, info.Rect.MidY + pt1.Item2, handlePaint); // change color handlePaint.Color = this.ArcColor2.ToSKColor(); // handle canvas.DrawPoint(pt2.Item1 + info.Rect.MidX, info.Rect.MidY + pt2.Item2, handlePaint); } // rotate it all back canvas.RotateDegrees(90, info.Width / 2, info.Height / 2); }