diff --git a/PrimeBlazor/src/Components/Knob/PrimeKnob.razor b/PrimeBlazor/src/Components/Knob/PrimeKnob.razor new file mode 100644 index 0000000..b0aed90 --- /dev/null +++ b/PrimeBlazor/src/Components/Knob/PrimeKnob.razor @@ -0,0 +1,216 @@ +@namespace PrimeBlazor +@using System.Globalization + +
+ + + + @if (ShowValue) + { + + @ValueToDisplay + + } + +
+ +@code { + + private double radius = 40; + private double midX = 50; + private double midY = 50; + private double minRadians = 4 * Math.PI / 3; + private double maxRadians = -Math.PI / 3; + private bool mouseMoveActive = false; + private bool touchMoveActive = false; + + [Parameter] + public int Value { get; set; } + + [Parameter] + public int Size { get; set; } = 100; + + [Parameter] + public bool Disabled { get; set; } = false; + + [Parameter] + public bool Readonly { get; set; } = false; + + [Parameter] + public int Step { get; set; } = 1; + + [Parameter] + public int Min { get; set; } = 0; + + [Parameter] + public int Max { get; set; } = 100; + + [Parameter] + public string ValueColor { get; set; } = "var(--primary-color, Black)"; + + [Parameter] + public string RangeColor { get; set; } = "var(--surface-d, LightGray)"; + + [Parameter] + public string TextColor { get; set; } = "var(--text-color-secondary, Black)"; + + [Parameter] + public int StrokeWidth { get; set; } = 14; + + [Parameter] + public bool ShowValue { get; set; } = true; + + [Parameter] + public string ValueTemplate { get; set; } = "{value}"; + + public string ContainerClass + { + get => "p-knob p-component" + (Disabled ? " p-disabled" : ""); + + + } + + public string RangePath + { + get => $"M {MinX.ToString(CultureInfo.InvariantCulture)} {MinY.ToString(CultureInfo.InvariantCulture)} A {radius.ToString(CultureInfo.InvariantCulture)} {radius.ToString(CultureInfo.InvariantCulture)} 0 1 1 {MaxX.ToString(CultureInfo.InvariantCulture)} {MaxY.ToString(CultureInfo.InvariantCulture)}"; + } + + public string ValuePath + { + get => $"M {ZeroX.ToString(CultureInfo.InvariantCulture)} {ZeroY.ToString(CultureInfo.InvariantCulture)} A {radius.ToString(CultureInfo.InvariantCulture)} {radius.ToString(CultureInfo.InvariantCulture)} 0 {LargeArc} {Sweep} {ValueX.ToString(CultureInfo.InvariantCulture)} {ValueY.ToString(CultureInfo.InvariantCulture)}"; + } + + public double ZeroRadians + { + get + { + if (Min > 0 && Max > 0) + return MapRange(Min, Min, Max, minRadians, maxRadians); + else + return MapRange(0, Min, Max, minRadians, maxRadians); + } + } + public double ValueRadians + { + get + { + return MapRange(Value, Min, Max, minRadians, maxRadians); + } + } + public double MinX + { + get => midX + Math.Cos(minRadians) * radius; + } + public double MinY + { + get => midY - Math.Sin(minRadians) * radius; + } + + public double MaxX + { + get => midX + Math.Cos(maxRadians) * radius; + } + public double MaxY + { + get => midY - Math.Sin(maxRadians) * radius; + } + + public double ZeroX + { + get => midX + Math.Cos(ZeroRadians) * radius; + } + public double ZeroY + { + get => midY - Math.Sin(ZeroRadians) * radius; + } + public double ValueX + { + get => midX + Math.Cos(ValueRadians) * radius; + } + public double ValueY + { + get => midY - Math.Sin(ValueRadians) * radius; + } + public int LargeArc + { + get => Math.Abs(ZeroRadians - ValueRadians) < Math.PI ? 0 : 1; + } + public int Sweep + { + get => ValueRadians > ZeroRadians ? 0 : 1; + } + public string ValueToDisplay + { + get => ValueTemplate.Replace("{value}", Value.ToString()); + } + private void UpdateValue(double offsetX, double offsetY) + { + var dx = offsetX - Size / 2; + var dy = Size / 2 - offsetY; + var angle = Math.Atan2(dy, dx); + var start = -Math.PI / 2 - Math.PI / 6; + + UpdateModel(angle, start); + } + + private void UpdateModel(double angle, double start) + { + double mappedValue; + if (angle > maxRadians) + mappedValue = MapRange(angle, minRadians, maxRadians, Min, Max); + else if (angle < start) + mappedValue = MapRange(angle + 2 * Math.PI, minRadians, maxRadians, Min, Max); + else + return; + double newValue = Math.Round((mappedValue - Min) / Step) * Step + Min; + + Value = (int)newValue; + + } + + private double MapRange(double x, double inMin, double inMax, double outMin, double outMax) + { + return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin; + } + + private void OnClick(MouseEventArgs e) + { + if (!Disabled && !Readonly) + { + UpdateValue(e.OffsetX, e.OffsetY); + } + + } + + public void OnMouseDown(MouseEventArgs e) + { + if (!Disabled && !Readonly) + { + + mouseMoveActive = true; + } + } + + public void OnMouseUp(MouseEventArgs e) + { + if (!Disabled && !Readonly) + { + + mouseMoveActive = false; + } + } + + + private void OnMouseMove(MouseEventArgs e) + { + if (!Disabled && !Readonly && mouseMoveActive) + { + UpdateValue(e.OffsetX, e.OffsetY); + } + } + + +} + + diff --git a/PrimeBlazor/src/Components/Knob/PrimeKnob.razor.css b/PrimeBlazor/src/Components/Knob/PrimeKnob.razor.css new file mode 100644 index 0000000..8672e59 --- /dev/null +++ b/PrimeBlazor/src/Components/Knob/PrimeKnob.razor.css @@ -0,0 +1,19 @@ + +@keyframes dash-frame { + 100% { + stroke-dashoffset: 0; + } +} +.p-knob-range { + fill: none; + transition: stroke .1s ease-in; +} +.p-knob-value { + animation-name: dash-frame; + animation-fill-mode: forwards; + fill: none; +} +.p-knob-text { + font-size: 1.3rem; + text-align: center; +}