RoundProgressPathConverter.cs

94 lines | 4.527 kB Blame History Raw Download
using System;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;

namespace Common.WPF.Converters
{
    public class RoundProgressPathConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (values?.Contains(DependencyProperty.UnsetValue) != false)
                return DependencyProperty.UnsetValue;

            var v = (double)values[0]; // значение слайдера
            var min = (double)values[1]; // минимальное значение
            var max = (double)values[2]; // максимальное

            var ratio = (v - min) / (max - min); // какую долю окружности закрашивать
            var isFull = ratio >= 1; // для случая полной окружности нужна особая обработка
            var angleRadians = 2 * Math.PI * ratio;
            var angleDegrees = 360 * ratio;

            // внешний радиус примем за 1, растянем в XAML'е.
            var outerR = 1;
            // как параметр передадим долю радиуса, которую занимает внутренняя часть
            var innerR = System.Convert.ToDouble(parameter, System.Globalization.CultureInfo.InvariantCulture) * outerR;
            // вспомогательные штуки: вектор направления вверх
            var vector1 = new Vector(0, -1);
            // ... и на конечную точку дуги
            var vector2 = new Vector(Math.Sin(angleRadians), -Math.Cos(angleRadians));
            var center = new Point();

            var geo = new StreamGeometry();
            geo.FillRule = FillRule.EvenOdd;

            using (var ctx = geo.Open())
            {
                Size outerSize = new Size(outerR, outerR),
                     innerSize = new Size(innerR, innerR);

                if (!isFull)
                {
                    Point p1 = center + vector1 * outerR, p2 = center + vector2 * outerR,
                          p3 = center + vector2 * innerR, p4 = center + vector1 * innerR;

                    ctx.BeginFigure(p1, isFilled: true, isClosed: true);
                    ctx.ArcTo(p2, outerSize, angleDegrees, isLargeArc: angleDegrees > 180,
                        sweepDirection: SweepDirection.Clockwise, isStroked: true,
                        isSmoothJoin: false);
                    ctx.LineTo(p3, isStroked: true, isSmoothJoin: false);
                    ctx.ArcTo(p4, innerSize, -angleDegrees, isLargeArc: angleDegrees > 180,
                        sweepDirection: SweepDirection.Counterclockwise, isStroked: true,
                        isSmoothJoin: false);

                    Point diag1 = new Point(-outerR, -outerR),
                          diag2 = new Point(outerR, outerR);
                    ctx.BeginFigure(diag1, isFilled: false, isClosed: false);
                    ctx.LineTo(diag2, isStroked: false, isSmoothJoin: false);
                }
                else
                {
                    Point p1 = center + vector1 * outerR, p2 = center - vector1 * outerR,
                          p3 = center + vector1 * innerR, p4 = center - vector1 * innerR;

                    ctx.BeginFigure(p1, isFilled: true, isClosed: true);
                    ctx.ArcTo(p2, outerSize, 180, isLargeArc: false,
                        sweepDirection: SweepDirection.Clockwise, isStroked: true,
                        isSmoothJoin: false);
                    ctx.ArcTo(p1, outerSize, 180, isLargeArc: false,
                        sweepDirection: SweepDirection.Clockwise, isStroked: true,
                        isSmoothJoin: false);
                    ctx.BeginFigure(p3, isFilled: true, isClosed: true);
                    ctx.ArcTo(p4, innerSize, 180, isLargeArc: false,
                        sweepDirection: SweepDirection.Clockwise, isStroked: true,
                        isSmoothJoin: false);
                    ctx.ArcTo(p3, innerSize, 180, isLargeArc: false,
                        sweepDirection: SweepDirection.Clockwise, isStroked: true,
                        isSmoothJoin: false);
                }
            }

            geo.Freeze();
            return geo;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
}